Skip to content

Commit 7332f6c

Browse files
authored
Add a source panel (#60)
* restore the geoJSON icon for geoJSON layer button * Add a sources panel * order the source by name * lint * Add context menu to create source * Add context menu to rename sources and remove unused sources * lint * Minimal height for the source and layer panels * Update properties panel when a source is removed * lint * Add tests on sources * lint * Register the icons in a single Map object
1 parent ab33f5b commit 7332f6c

File tree

17 files changed

+756
-95
lines changed

17 files changed

+756
-95
lines changed

packages/base/src/commands.ts

Lines changed: 83 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -7,40 +7,13 @@ import {
77
SelectionType
88
} from '@jupytergis/schema';
99
import { JupyterFrontEnd } from '@jupyterlab/application';
10-
import { WidgetTracker } from '@jupyterlab/apputils';
10+
import { showErrorMessage, WidgetTracker } from '@jupyterlab/apputils';
1111
import { ITranslator } from '@jupyterlab/translation';
12-
import { redoIcon, undoIcon } from '@jupyterlab/ui-components';
1312

13+
import { CommandIDs, icons } from './constants';
1414
import { LayerBrowserWidget } from './dialogs/layerBrowserDialog';
1515
import { CreationFormDialog } from './dialogs/formdialog';
1616
import { JupyterGISWidget } from './widget';
17-
import { geoJSONIcon } from './icons';
18-
19-
/**
20-
* The command IDs.
21-
*/
22-
export namespace CommandIDs {
23-
export const createNew = 'jupytergis:create-new-jGIS-file';
24-
export const redo = 'jupytergis:redo';
25-
export const undo = 'jupytergis:undo';
26-
27-
export const openLayerBrowser = 'jupytergis:openLayerBrowser';
28-
29-
export const newGeoJSONLayer = 'jupytergis:newGeoJSONLayer';
30-
export const newGeoJSONSource = 'jupytergis:newGeoJSONSource';
31-
32-
export const newVectorTileLayer = 'jupytergis:newVectorTileLayer';
33-
34-
export const newVectorLayer = 'jupytergis:newVectorLayer';
35-
36-
export const renameLayer = 'jupytergis:renameLayer';
37-
export const removeLayer = 'jupytergis:removeLayer';
38-
export const renameGroup = 'jupytergis:renameGroup';
39-
export const removeGroup = 'jupytergis:removeGroup';
40-
41-
export const moveLayersToGroup = 'jupytergis:moveLayersToGroup';
42-
export const moveLayerToNewGroup = 'jupytergis:moveLayerToNewGroup';
43-
}
4417

4518
/**
4619
* Add the commands to the application's command registry.
@@ -69,7 +42,7 @@ export function addCommands(
6942
return current.context.model.sharedModel.redo();
7043
}
7144
},
72-
icon: redoIcon
45+
...icons.get(CommandIDs.redo)?.icon
7346
});
7447

7548
commands.addCommand(CommandIDs.undo, {
@@ -86,24 +59,99 @@ export function addCommands(
8659
return current.context.model.sharedModel.undo();
8760
}
8861
},
89-
icon: undoIcon
62+
...icons.get(CommandIDs.undo)
9063
});
9164

65+
/**
66+
* SOURCES and LAYERS creation commands.
67+
*/
9268
commands.addCommand(CommandIDs.openLayerBrowser, {
9369
label: trans.__('Open Layer Browser'),
9470
isEnabled: () => {
9571
return tracker.currentWidget
9672
? tracker.currentWidget.context.model.sharedModel.editable
9773
: false;
9874
},
99-
iconClass: 'fa fa-book-open',
10075
execute: Private.createLayerBrowser(
10176
tracker,
10277
layerBrowserRegistry,
10378
formSchemaRegistry
104-
)
79+
),
80+
...icons.get(CommandIDs.openLayerBrowser)
81+
});
82+
83+
commands.addCommand(CommandIDs.newGeoJSONLayer, {
84+
label: trans.__('New geoJSON layer'),
85+
isEnabled: () => {
86+
return tracker.currentWidget
87+
? tracker.currentWidget.context.model.sharedModel.editable
88+
: false;
89+
},
90+
execute: Private.createGeoJSONLayer(tracker, formSchemaRegistry),
91+
...icons.get(CommandIDs.newGeoJSONLayer)
10592
});
10693

94+
commands.addCommand(CommandIDs.newVectorTileLayer, {
95+
label: trans.__('New vector tile layer'),
96+
isEnabled: () => {
97+
return tracker.currentWidget
98+
? tracker.currentWidget.context.model.sharedModel.editable
99+
: false;
100+
},
101+
execute: Private.createVectorTileLayer(tracker, formSchemaRegistry),
102+
...icons.get(CommandIDs.newVectorTileLayer)
103+
});
104+
105+
/**
106+
* SOURCES only commands.
107+
*/
108+
commands.addCommand(CommandIDs.newGeoJSONSource, {
109+
label: args =>
110+
args.from === 'contextMenu'
111+
? trans.__('GeoJSON')
112+
: trans.__('Add GeoJSON data from file'),
113+
isEnabled: () => {
114+
return tracker.currentWidget
115+
? tracker.currentWidget.context.model.sharedModel.editable
116+
: false;
117+
},
118+
execute: Private.createGeoJSONSource(tracker, formSchemaRegistry),
119+
...icons.get(CommandIDs.newGeoJSONSource)?.icon
120+
});
121+
122+
commands.addCommand(CommandIDs.removeSource, {
123+
label: trans.__('Remove Source'),
124+
execute: () => {
125+
const model = tracker.currentWidget?.context.model;
126+
Private.removeSelectedItems(model, 'source', selection => {
127+
if (!(model?.getLayersBySource(selection).length ?? true)) {
128+
model?.sharedModel.removeSource(selection);
129+
} else {
130+
showErrorMessage(
131+
'Remove source error',
132+
'The source is used by a layer.'
133+
);
134+
}
135+
});
136+
}
137+
});
138+
139+
commands.addCommand(CommandIDs.renameSource, {
140+
label: trans.__('Rename Source'),
141+
execute: async () => {
142+
const model = tracker.currentWidget?.context.model;
143+
await Private.renameSelectedItem(model, 'source', (sourceId, newName) => {
144+
const source = model?.getSource(sourceId);
145+
if (source) {
146+
source.name = newName;
147+
model?.sharedModel.updateSource(sourceId, source);
148+
}
149+
});
150+
}
151+
});
152+
/**
153+
* LAYERS and LAYER GROUPS only commands.
154+
*/
107155
commands.addCommand(CommandIDs.removeLayer, {
108156
label: trans.__('Remove Layer'),
109157
execute: () => {
@@ -228,48 +276,15 @@ export function addCommands(
228276
}
229277
});
230278

231-
commands.addCommand(CommandIDs.newGeoJSONLayer, {
232-
label: trans.__('New vector layer'),
233-
isEnabled: () => {
234-
return tracker.currentWidget
235-
? tracker.currentWidget.context.model.sharedModel.editable
236-
: false;
237-
},
238-
iconClass: 'fa fa-vector-square',
239-
execute: Private.createGeoJSONLayer(tracker, formSchemaRegistry)
240-
});
241-
242279
commands.addCommand(CommandIDs.newVectorLayer, {
243280
label: trans.__('New vector layer'),
244281
isEnabled: () => {
245282
return tracker.currentWidget
246283
? tracker.currentWidget.context.model.sharedModel.editable
247284
: false;
248285
},
249-
iconClass: 'fa fa-vector-square',
250-
execute: Private.createVectorLayer(tracker, formSchemaRegistry)
251-
});
252-
253-
commands.addCommand(CommandIDs.newVectorTileLayer, {
254-
label: trans.__('New vector tile layer'),
255-
isEnabled: () => {
256-
return tracker.currentWidget
257-
? tracker.currentWidget.context.model.sharedModel.editable
258-
: false;
259-
},
260-
iconClass: 'fa fa-vector-square',
261-
execute: Private.createVectorTileLayer(tracker, formSchemaRegistry)
262-
});
263-
264-
commands.addCommand(CommandIDs.newGeoJSONSource, {
265-
label: trans.__('Add GeoJSON data from file'),
266-
isEnabled: () => {
267-
return tracker.currentWidget
268-
? tracker.currentWidget.context.model.sharedModel.editable
269-
: false;
270-
},
271-
icon: geoJSONIcon,
272-
execute: Private.createGeoJSONSource(tracker, formSchemaRegistry)
286+
execute: Private.createVectorLayer(tracker, formSchemaRegistry),
287+
...icons.get(CommandIDs.newVectorLayer)
273288
});
274289
}
275290

packages/base/src/constants.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { LabIcon, redoIcon, undoIcon } from '@jupyterlab/ui-components';
2+
import { geoJSONIcon, rasterIcon } from './icons';
3+
4+
/**
5+
* The command IDs.
6+
*/
7+
export namespace CommandIDs {
8+
export const createNew = 'jupytergis:create-new-jGIS-file';
9+
export const redo = 'jupytergis:redo';
10+
export const undo = 'jupytergis:undo';
11+
12+
// Layers and sources commands
13+
export const openLayerBrowser = 'jupytergis:openLayerBrowser';
14+
export const newGeoJSONLayer = 'jupytergis:newGeoJSONLayer';
15+
export const newVectorTileLayer = 'jupytergis:newVectorTileLayer';
16+
17+
// Sources only commands
18+
export const newGeoJSONSource = 'jupytergis:newGeoJSONSource';
19+
export const removeSource = 'jupytergis:removeSource';
20+
export const renameSource = 'jupytergis:renameSource';
21+
22+
// Layers only commands
23+
export const newVectorLayer = 'jupytergis:newVectorLayer';
24+
25+
export const renameLayer = 'jupytergis:renameLayer';
26+
export const removeLayer = 'jupytergis:removeLayer';
27+
export const renameGroup = 'jupytergis:renameGroup';
28+
export const removeGroup = 'jupytergis:removeGroup';
29+
30+
export const moveLayersToGroup = 'jupytergis:moveLayersToGroup';
31+
export const moveLayerToNewGroup = 'jupytergis:moveLayerToNewGroup';
32+
}
33+
34+
interface IRegisteredIcon {
35+
icon?: LabIcon;
36+
iconClass?: string;
37+
}
38+
39+
const iconObject = {
40+
RasterSource: { icon: rasterIcon },
41+
GeoJSONSource: { icon: geoJSONIcon },
42+
VectorTileSource: { iconClass: 'fa fa-vector-square' },
43+
RasterLayer: { icon: rasterIcon },
44+
[CommandIDs.redo]: { icon: redoIcon },
45+
[CommandIDs.undo]: { icon: undoIcon },
46+
[CommandIDs.openLayerBrowser]: { iconClass: 'fa fa-book-open' },
47+
[CommandIDs.newGeoJSONLayer]: { icon: geoJSONIcon },
48+
[CommandIDs.newVectorTileLayer]: { iconClass: 'fa fa-vector-square' },
49+
[CommandIDs.newGeoJSONSource]: { icon: geoJSONIcon },
50+
[CommandIDs.newVectorLayer]: { iconClass: 'fa fa-vector-square' }
51+
};
52+
53+
/**
54+
* The registered icons
55+
*/
56+
export const icons = new Map<string, IRegisteredIcon>(
57+
Object.entries(iconObject)
58+
);

packages/base/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export * from './commands';
2+
export * from './constants';
23
export * from './dialogs/formdialog';
34
export * from './mainview';
45
export * from './tools';

packages/base/src/panelview/components/layers.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ import {
1515
} from '@jupyterlab/ui-components';
1616
import { Panel } from '@lumino/widgets';
1717
import React, { MouseEvent, useEffect, useState } from 'react';
18-
import { nonVisibilityIcon, rasterIcon, visibilityIcon } from '../../icons';
18+
import { icons } from '../../constants';
19+
import { nonVisibilityIcon, visibilityIcon } from '../../icons';
1920
import { IControlPanelModel } from '../../types';
2021

2122
const LAYERS_PANEL_CLASS = 'jp-gis-layerPanel';
@@ -368,9 +369,9 @@ function LayerComponent(props: ILayerProps): JSX.Element {
368369
onClick={setSelection}
369370
onContextMenu={setSelection}
370371
>
371-
{layer.type === 'RasterLayer' && (
372+
{icons.has(layer.type) && (
372373
<LabIcon.resolveReact
373-
icon={rasterIcon}
374+
{...icons.get(layer.type)}
374375
className={LAYER_ICON_CLASS}
375376
/>
376377
)}

0 commit comments

Comments
 (0)