Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 60 additions & 50 deletions packages/base/src/panelview/components/layers.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
import {
IJGISLayersGroup,
IJGISLayersTree,
IJupyterGISModel
IJupyterGISClientState,
IJupyterGISModel,
ISelection
} from '@jupytergis/schema';
import {
Button,
LabIcon,
ReactWidget,
caretDownIcon
} from '@jupyterlab/ui-components';
import { ISignal, Signal } from '@lumino/signaling';
import { Panel } from '@lumino/widgets';
import React, { useEffect, useState } from 'react';
import { nonVisibilityIcon, rasterIcon, visibilityIcon } from '../../icons';
import { IControlPanelModel } from '../../types';

const LAYERS_PANEL_CLASS = 'jp-gis-layerPanel';
const LAYERS_GROUP_CLASS = 'jp-gis-layersGroup';
Expand All @@ -31,7 +33,7 @@ export namespace LayersPanel {
* Options of the layers panel widget.
*/
export interface IOptions {
model: IJupyterGISModel | undefined;
model: IControlPanelModel;
}
}

Expand All @@ -48,58 +50,47 @@ export class LayersPanel extends Panel {
ReactWidget.create(
<LayersBodyComponent
model={this._model}
modelChanged={this._modelChanged}
onSelect={this._onSelect}
></LayersBodyComponent>
)
);
}

/**
* Set the GIS model associated to the widget.
*/
set model(value: IJupyterGISModel | undefined) {
this._model = value;
this._modelChanged.emit(value);
}

/**
* A signal emitting when the GIS model changed.
*/
get modelChanged(): ISignal<LayersPanel, IJupyterGISModel | undefined> {
return this._modelChanged;
}

/**
* Function to call when a layer is selected from a component of the panel.
*
* @param layer - the selected layer.
*/
private _onSelect = (layer?: string) => {
if (this._model) {
this._model.currentLayer = layer ?? null;
const selection: { [key: string]: ISelection } = {};
if (layer) {
selection[layer] = {
type: 'layer'
};
}
this._model?.jGISModel?.syncSelected(selection, this.id);
}
};

private _model: IJupyterGISModel | undefined;
private _modelChanged = new Signal<LayersPanel, IJupyterGISModel | undefined>(
this
);
private _model: IControlPanelModel | undefined;
}

/**
* Properties of the layers body component.
*/
interface IBodyProps extends LayersPanel.IOptions {
modelChanged: ISignal<LayersPanel, IJupyterGISModel | undefined>;
interface IBodyProps {
model: IControlPanelModel;
onSelect: (layer?: string) => void;
}

/**
* The body component of the panel.
*/
function LayersBodyComponent(props: IBodyProps): JSX.Element {
const [model, setModel] = useState<IJupyterGISModel | undefined>(props.model);
const [model, setModel] = useState<IJupyterGISModel | undefined>(
props.model?.jGISModel
);
const [layersTree, setLayersTree] = useState<IJGISLayersTree>(
model?.getLayersTree() || []
);
Expand All @@ -118,31 +109,35 @@ function LayersBodyComponent(props: IBodyProps): JSX.Element {
const updateLayers = () => {
setLayersTree(model?.getLayersTree() || []);
};
model?.sharedModel.layersChanged.connect(updateLayers);
model?.sharedModel.layersTreeChanged.connect(updateLayers);
model?.sharedModel?.layersChanged.connect(updateLayers);
model?.sharedModel?.layersTreeChanged.connect(updateLayers);

return () => {
model?.sharedModel.layersChanged.disconnect(updateLayers);
model?.sharedModel.layersTreeChanged.disconnect(updateLayers);
model?.sharedModel?.layersChanged.disconnect(updateLayers);
model?.sharedModel?.layersTreeChanged.disconnect(updateLayers);
};
}, [model]);

/**
* Update the model when it changes.
*/
props.modelChanged.connect((_, model) => {
setModel(model);
setLayersTree(model?.getLayersTree() || []);
props.model?.documentChanged.connect((_, widget) => {
setModel(widget?.context.model);
setLayersTree(widget?.context.model?.getLayersTree() || []);
});

return (
<div>
{layersTree.map(layer =>
typeof layer === 'string' ? (
<LayerComponent model={model} layerId={layer} onClick={onItemClick} />
<LayerComponent
gisModel={model}
layerId={layer}
onClick={onItemClick}
/>
) : (
<LayersGroupComponent
model={model}
gisModel={model}
group={layer}
onClick={onItemClick}
/>
Expand All @@ -155,7 +150,8 @@ function LayersBodyComponent(props: IBodyProps): JSX.Element {
/**
* Properties of the layers group component.
*/
interface ILayersGroupProps extends LayersPanel.IOptions {
interface ILayersGroupProps {
gisModel: IJupyterGISModel | undefined;
group: IJGISLayersGroup | undefined;
onClick: (item?: string) => void;
}
Expand All @@ -164,7 +160,7 @@ interface ILayersGroupProps extends LayersPanel.IOptions {
* The component to handle group of layers.
*/
function LayersGroupComponent(props: ILayersGroupProps): JSX.Element {
const { group, model } = props;
const { group, gisModel } = props;
if (group === undefined) {
return <></>;
}
Expand All @@ -189,13 +185,13 @@ function LayersGroupComponent(props: ILayersGroupProps): JSX.Element {
{layers.map(layer =>
typeof layer === 'string' ? (
<LayerComponent
model={model}
gisModel={gisModel}
layerId={layer}
onClick={props.onClick}
/>
) : (
<LayersGroupComponent
model={model}
gisModel={gisModel}
group={layer}
onClick={props.onClick}
/>
Expand All @@ -210,45 +206,59 @@ function LayersGroupComponent(props: ILayersGroupProps): JSX.Element {
/**
* Properties of the layer component.
*/
interface ILayerProps extends LayersPanel.IOptions {
interface ILayerProps {
gisModel: IJupyterGISModel | undefined;
layerId: string;
onClick: (item?: string) => void;
}

function isSelected(layerId: string, model: IJupyterGISModel | undefined) {
return (
(model?.localState?.selected?.value &&
Object.keys(model?.localState?.selected?.value).includes(layerId)) ||
false
);
}

/**
* The component to display a single layer.
*/
function LayerComponent(props: ILayerProps): JSX.Element {
const { layerId, model } = props;
const layer = model?.getLayer(layerId);
const { layerId, gisModel } = props;
const layer = gisModel?.getLayer(layerId);
if (layer === undefined) {
return <></>;
}
const [selected, setSelected] = useState<boolean>(
model?.currentLayer === layerId
// TODO Support multi-selection as `model?.jGISModel?.localState?.selected.value` does
isSelected(layerId, gisModel)
);
const name = layer.name;

/**
* Listen to the changes on the current layer.
*/
useEffect(() => {
const isSelected = () => {
setSelected(model?.currentLayer === layerId);
const onClientSharedStateChanged = (
sender: IJupyterGISModel,
clients: Map<number, IJupyterGISClientState>
) => {
// TODO Support follow mode and remoteUser state
setSelected(isSelected(layerId, gisModel));
};
model?.currentLayerChanged.connect(isSelected);
gisModel?.clientStateChanged.connect(onClientSharedStateChanged);

return () => {
model?.currentLayerChanged.disconnect(isSelected);
gisModel?.clientStateChanged.disconnect(onClientSharedStateChanged);
};
}, [model]);
}, [gisModel]);

/**
* Toggle layer visibility.
*/
const toggleVisibility = () => {
layer.visible = !layer.visible;
model?.sharedModel.updateLayer(layerId, layer);
gisModel?.sharedModel?.updateLayer(layerId, layer);
};

return (
Expand Down
3 changes: 1 addition & 2 deletions packages/base/src/panelview/leftpanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,12 @@ export class LeftPanelWidget extends SidePanel {
// const datasources = new DataSourceList({ controlPanelModel: this._model });
// this.addWidget(datasources);

const layersTree = new LayersPanel({ model: this._model.jGISModel });
const layersTree = new LayersPanel({ model: this._model });
layersTree.title.caption = 'Layer tree';
layersTree.title.label = 'Layers';
this.addWidget(layersTree);

options.tracker.currentChanged.connect((_, changed) => {
layersTree.model = this._model.jGISModel;
if (changed) {
header.title.label = changed.context.localPath;
} else {
Expand Down
Loading