@jupytergis/base 0.9.2 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/lib/annotations/components/Annotation.js +1 -1
  2. package/lib/commands/BaseCommandIDs.d.ts +1 -0
  3. package/lib/commands/BaseCommandIDs.js +2 -0
  4. package/lib/commands/index.js +31 -73
  5. package/lib/constants.js +2 -1
  6. package/lib/dialogs/ProcessingFormDialog.js +2 -2
  7. package/lib/dialogs/symbology/colorRampUtils.d.ts +2 -1
  8. package/lib/dialogs/symbology/colorRampUtils.js +10 -4
  9. package/lib/dialogs/symbology/components/color_ramp/ColorRampControls.d.ts +36 -0
  10. package/lib/dialogs/symbology/components/color_ramp/ColorRampControls.js +82 -0
  11. package/lib/dialogs/symbology/components/color_ramp/ColorRampSelector.d.ts +20 -0
  12. package/lib/dialogs/symbology/components/color_ramp/{CanvasSelectComponent.js → ColorRampSelector.js} +25 -7
  13. package/lib/dialogs/symbology/components/color_ramp/ColorRampSelectorEntry.d.ts +20 -0
  14. package/lib/dialogs/symbology/components/color_ramp/{ColorRampEntry.js → ColorRampSelectorEntry.js} +17 -3
  15. package/lib/dialogs/symbology/components/color_ramp/ModeSelectRow.d.ts +6 -5
  16. package/lib/dialogs/symbology/components/color_ramp/ModeSelectRow.js +7 -2
  17. package/lib/dialogs/symbology/hooks/useGetProperties.js +12 -6
  18. package/lib/dialogs/symbology/symbologyUtils.d.ts +2 -1
  19. package/lib/dialogs/symbology/tiff_layer/types/SingleBandPseudoColor.js +8 -4
  20. package/lib/dialogs/symbology/vector_layer/VectorRendering.js +2 -2
  21. package/lib/dialogs/symbology/vector_layer/types/Categorized.js +4 -5
  22. package/lib/dialogs/symbology/vector_layer/types/Graduated.js +16 -28
  23. package/lib/dialogs/symbology/vector_layer/types/Heatmap.js +3 -3
  24. package/lib/formbuilder/editform.js +4 -3
  25. package/lib/formbuilder/objectform/layer/heatmapLayerForm.d.ts +2 -1
  26. package/lib/formbuilder/objectform/layer/heatmapLayerForm.js +4 -0
  27. package/lib/formbuilder/objectform/layer/vectorlayerform.d.ts +2 -1
  28. package/lib/formbuilder/objectform/layer/vectorlayerform.js +4 -0
  29. package/lib/formbuilder/objectform/layer/webGlLayerForm.d.ts +2 -0
  30. package/lib/formbuilder/objectform/layer/webGlLayerForm.js +4 -0
  31. package/lib/icons.d.ts +1 -0
  32. package/lib/icons.js +5 -0
  33. package/lib/index.d.ts +0 -1
  34. package/lib/index.js +0 -1
  35. package/lib/mainview/mainView.d.ts +1 -0
  36. package/lib/mainview/mainView.js +62 -12
  37. package/lib/panelview/annotationPanel.js +1 -1
  38. package/lib/panelview/components/filter-panel/Filter.js +1 -1
  39. package/lib/panelview/components/layers.js +152 -51
  40. package/lib/panelview/components/legendItem.js +36 -1
  41. package/lib/panelview/leftpanel.d.ts +0 -1
  42. package/lib/panelview/leftpanel.js +4 -4
  43. package/lib/panelview/rightpanel.js +4 -4
  44. package/lib/processing/processingFormToParam.js +3 -0
  45. package/lib/shared/components/ToggleGroup.d.ts +2 -2
  46. package/lib/stacBrowser/hooks/useStacSearch.js +2 -2
  47. package/lib/toolbar/widget.js +19 -28
  48. package/lib/tools.d.ts +1 -0
  49. package/lib/tools.js +2 -2
  50. package/lib/types.d.ts +2 -0
  51. package/lib/types.js +8 -0
  52. package/package.json +2 -2
  53. package/style/base.css +2 -0
  54. package/style/icons/book_open.svg +1 -1
  55. package/style/icons/clock-solid.svg +1 -1
  56. package/style/icons/geolocation.svg +1 -1
  57. package/style/icons/info-solid.svg +1 -1
  58. package/style/icons/logo_mini.svg +1 -1
  59. package/style/icons/marker.svg +5 -0
  60. package/style/icons/target_without_center.svg +1 -1
  61. package/style/icons/vector_square.svg +1 -1
  62. package/style/shared/tabs.css +1 -0
  63. package/style/symbologyDialog.css +12 -4
  64. package/lib/classificationModes.d.ts +0 -13
  65. package/lib/classificationModes.js +0 -326
  66. package/lib/dialogs/symbology/components/color_ramp/CanvasSelectComponent.d.ts +0 -11
  67. package/lib/dialogs/symbology/components/color_ramp/ColorRamp.d.ts +0 -16
  68. package/lib/dialogs/symbology/components/color_ramp/ColorRamp.js +0 -34
  69. package/lib/dialogs/symbology/components/color_ramp/ColorRampEntry.d.ts +0 -9
@@ -1,6 +1,5 @@
1
1
  import { DOMUtils } from '@jupyterlab/apputils';
2
2
  import { Button, LabIcon, caretDownIcon, caretRightIcon, } from '@jupyterlab/ui-components';
3
- import { UUID } from '@lumino/coreutils';
4
3
  import React, { useEffect, useState, } from 'react';
5
4
  import { CommandIDs, icons } from "../../constants";
6
5
  import { useGetSymbology } from "../../dialogs/symbology/hooks/useGetSymbology";
@@ -16,7 +15,6 @@ const LAYER_ICON_CLASS = 'jp-gis-layerIcon';
16
15
  const LAYER_TEXT_CLASS = 'jp-gis-layerText data-jgis-keybinding';
17
16
  export const LayersBodyComponent = props => {
18
17
  const model = props.model;
19
- const id = UUID.uuid4();
20
18
  const [layerTree, setLayerTree] = useState((model === null || model === void 0 ? void 0 : model.getLayerTree()) || []);
21
19
  const notifyCommands = () => {
22
20
  // Notify commands that need updating
@@ -63,62 +61,61 @@ export const LayersBodyComponent = props => {
63
61
  }
64
62
  model === null || model === void 0 ? void 0 : model.moveItemRelatedTo(draggedId, dragOverId, dragOverPosition === 'above');
65
63
  };
66
- const onSelect = ({ type, item, nodeId, event, }) => {
67
- var _a, _b;
68
- if (!props.model || !nodeId) {
64
+ const onSelect = ({ type, item, event }) => {
65
+ if (!props.model) {
69
66
  return;
70
67
  }
71
- const selectedValue = (_b = (_a = props.model.localState) === null || _a === void 0 ? void 0 : _a.selected) === null || _b === void 0 ? void 0 : _b.value;
72
- const node = document.getElementById(nodeId);
73
- if (!node) {
68
+ const selectedValue = props.model.selected;
69
+ // Don't want to reset selected if right clicking a selected item
70
+ if (selectedValue &&
71
+ !event.ctrlKey &&
72
+ event.button === 2 &&
73
+ item in selectedValue) {
74
74
  return;
75
75
  }
76
- node.tabIndex = 0;
77
- node.focus();
78
- // Early return if no selection exists
76
+ // Calculate the new selection value
77
+ let newSelection;
78
+ // Early return if no selection exists - single selection
79
79
  if (!selectedValue) {
80
- resetSelected(type, nodeId, item);
81
- return;
82
- }
83
- // Don't want to reset selected if right clicking a selected item
84
- if (!event.ctrlKey && event.button === 2 && item in selectedValue) {
85
- return;
80
+ newSelection = {
81
+ [item]: {
82
+ type,
83
+ },
84
+ };
86
85
  }
87
- // Reset selection for normal left click
88
- if (!event.ctrlKey) {
89
- resetSelected(type, nodeId, item);
90
- return;
86
+ else if (!event.ctrlKey) {
87
+ // Reset selection for normal left click - single selection
88
+ newSelection = {
89
+ [item]: {
90
+ type,
91
+ },
92
+ };
91
93
  }
92
- if (nodeId) {
94
+ else {
93
95
  // Check if new selection is the same type as previous selections
94
96
  const isSelectedSameType = Object.values(selectedValue).some(selection => selection.type === type);
95
97
  if (!isSelectedSameType) {
96
- // Selecting a new type, so reset selected
97
- resetSelected(type, nodeId, item);
98
- return;
98
+ // Selecting a new type, so reset selected - single selection
99
+ newSelection = {
100
+ [item]: {
101
+ type,
102
+ },
103
+ };
104
+ }
105
+ else {
106
+ // If types are the same add the selection - multi-selection
107
+ newSelection = Object.assign(Object.assign({}, selectedValue), { [item]: { type } });
99
108
  }
100
- // If types are the same add the selection
101
- const updatedSelectedValue = Object.assign(Object.assign({}, selectedValue), { [item]: { type, selectedNodeId: nodeId } });
102
- props.model.syncSelected(updatedSelectedValue, id);
103
- notifyCommands();
104
- }
105
- };
106
- const resetSelected = (type, nodeId, item) => {
107
- const selection = {};
108
- if (item && nodeId) {
109
- selection[item] = {
110
- type,
111
- selectedNodeId: nodeId,
112
- };
113
109
  }
114
- props.model.syncSelected(selection, id);
110
+ // Set the selection
111
+ props.model.selected = newSelection;
115
112
  notifyCommands();
116
113
  };
117
114
  /**
118
115
  * Propagate the layer selection.
119
116
  */
120
- const onItemClick = ({ type, item, nodeId, event, }) => {
121
- onSelect({ type, item, nodeId, event });
117
+ const onItemClick = ({ type, item, event }) => {
118
+ onSelect({ type, item, event });
122
119
  };
123
120
  /**
124
121
  * Listen to the layers and layer tree changes.
@@ -156,6 +153,8 @@ const LayerGroupComponent = props => {
156
153
  const [selected, setSelected] = useState(
157
154
  // TODO Support multi-selection as `model?.jGISModel?.localState?.selected.value` does
158
155
  isSelected(group.name, gisModel));
156
+ const [isEditing, setIsEditing] = useState(false);
157
+ const [editValue, setEditValue] = useState('');
159
158
  useEffect(() => {
160
159
  setId(DOMUtils.createDomID());
161
160
  const getExpandedState = async () => {
@@ -177,20 +176,70 @@ const LayerGroupComponent = props => {
177
176
  return () => {
178
177
  gisModel === null || gisModel === void 0 ? void 0 : gisModel.clientStateChanged.disconnect(onClientSharedStateChanged);
179
178
  };
180
- }, [gisModel]);
179
+ }, [gisModel, group.name]);
180
+ /**
181
+ * Listen to editing state changes.
182
+ */
183
+ useEffect(() => {
184
+ const onEditingChanged = (sender, editing) => {
185
+ if ((editing === null || editing === void 0 ? void 0 : editing.type) === 'group' && editing.itemId === name) {
186
+ setIsEditing(true);
187
+ setEditValue(name);
188
+ }
189
+ else {
190
+ setIsEditing(false);
191
+ }
192
+ };
193
+ // Check initial editing state
194
+ const editing = gisModel === null || gisModel === void 0 ? void 0 : gisModel.editing;
195
+ if ((editing === null || editing === void 0 ? void 0 : editing.type) === 'group' && editing.itemId === name) {
196
+ setIsEditing(true);
197
+ setEditValue(name);
198
+ }
199
+ gisModel === null || gisModel === void 0 ? void 0 : gisModel.editingChanged.connect(onEditingChanged);
200
+ return () => {
201
+ gisModel === null || gisModel === void 0 ? void 0 : gisModel.editingChanged.disconnect(onEditingChanged);
202
+ };
203
+ }, [gisModel, name]);
181
204
  const handleRightClick = (event) => {
182
- var _a;
183
- const childId = (_a = event.currentTarget.children.namedItem(id)) === null || _a === void 0 ? void 0 : _a.id;
184
- onClick({ type: 'group', item: name, nodeId: childId, event });
205
+ onClick({ type: 'group', item: name, event });
185
206
  };
186
207
  const handleExpand = async () => {
187
208
  state.save(`jupytergis:${group.name}`, { expanded: !open });
188
209
  setOpen(!open);
189
210
  };
211
+ const handleRenameSave = () => {
212
+ const newName = editValue.trim();
213
+ if (newName && newName !== name && gisModel) {
214
+ gisModel.renameLayerGroup(name, newName);
215
+ }
216
+ gisModel === null || gisModel === void 0 ? void 0 : gisModel.clearEditingItem();
217
+ };
218
+ const handleRenameCancel = () => {
219
+ setEditValue(name);
220
+ gisModel === null || gisModel === void 0 ? void 0 : gisModel.clearEditingItem();
221
+ };
222
+ const handleRenameKeyDown = (e) => {
223
+ if (e.key === 'Enter') {
224
+ e.preventDefault();
225
+ handleRenameSave();
226
+ }
227
+ else if (e.key === 'Escape') {
228
+ e.preventDefault();
229
+ handleRenameCancel();
230
+ }
231
+ };
190
232
  return (React.createElement("div", { className: `${LAYER_ITEM_CLASS} ${LAYER_GROUP_CLASS}`, draggable: true, onDragStart: Private.onDragStart, onDragEnd: Private.onDragEnd, "data-id": name },
191
233
  React.createElement("div", { onClick: handleExpand, onContextMenu: handleRightClick, className: `${LAYER_GROUP_HEADER_CLASS}${selected ? ' jp-mod-selected' : ''}`, onDragOver: Private.onDragOver, "data-id": name },
192
234
  React.createElement(LabIcon.resolveReact, { icon: caretDownIcon, className: `${LAYER_GROUP_COLLAPSER_CLASS}${open ? ' jp-mod-expanded' : ''}`, tag: 'span' }),
193
- React.createElement("span", { id: id, className: LAYER_TEXT_CLASS, tabIndex: -2 }, name)),
235
+ isEditing ? (React.createElement("input", { type: "text", value: editValue, onChange: e => setEditValue(e.target.value), onKeyDown: handleRenameKeyDown, onBlur: handleRenameSave, className: LAYER_TEXT_CLASS, style: {
236
+ flex: 1,
237
+ border: '1px solid var(--jp-border-color1)',
238
+ borderRadius: '2px',
239
+ padding: '2px 4px',
240
+ fontSize: 'inherit',
241
+ fontFamily: 'inherit',
242
+ }, autoFocus: true })) : (React.createElement("span", { id: id, className: LAYER_TEXT_CLASS, tabIndex: -2 }, name))),
194
243
  open && (React.createElement("div", null, layers
195
244
  .slice()
196
245
  .reverse()
@@ -216,6 +265,8 @@ const LayerComponent = props => {
216
265
  // TODO Support multi-selection as `model?.jGISModel?.localState?.selected.value` does
217
266
  isSelected(layerId, gisModel));
218
267
  const [expanded, setExpanded] = useState(false);
268
+ const [isEditing, setIsEditing] = useState(false);
269
+ const [editValue, setEditValue] = useState('');
219
270
  const { symbology } = useGetSymbology({
220
271
  layerId,
221
272
  model: gisModel,
@@ -237,7 +288,31 @@ const LayerComponent = props => {
237
288
  return () => {
238
289
  gisModel === null || gisModel === void 0 ? void 0 : gisModel.clientStateChanged.disconnect(onClientSharedStateChanged);
239
290
  };
240
- }, [gisModel]);
291
+ }, [gisModel, layerId]);
292
+ /**
293
+ * Listen to editing state changes.
294
+ */
295
+ useEffect(() => {
296
+ const onEditingChanged = (sender, editing) => {
297
+ if ((editing === null || editing === void 0 ? void 0 : editing.type) === 'layer' && editing.itemId === layerId) {
298
+ setIsEditing(true);
299
+ setEditValue(name);
300
+ }
301
+ else {
302
+ setIsEditing(false);
303
+ }
304
+ };
305
+ // Check initial editing state
306
+ const editing = gisModel === null || gisModel === void 0 ? void 0 : gisModel.editing;
307
+ if ((editing === null || editing === void 0 ? void 0 : editing.type) === 'layer' && editing.itemId === layerId) {
308
+ setIsEditing(true);
309
+ setEditValue(name);
310
+ }
311
+ gisModel === null || gisModel === void 0 ? void 0 : gisModel.editingChanged.connect(onEditingChanged);
312
+ return () => {
313
+ gisModel === null || gisModel === void 0 ? void 0 : gisModel.editingChanged.disconnect(onEditingChanged);
314
+ };
315
+ }, [gisModel, layerId, name]);
241
316
  /**
242
317
  * Toggle layer visibility.
243
318
  */
@@ -247,15 +322,34 @@ const LayerComponent = props => {
247
322
  (_a = gisModel === null || gisModel === void 0 ? void 0 : gisModel.sharedModel) === null || _a === void 0 ? void 0 : _a.updateLayer(layerId, layer);
248
323
  };
249
324
  const setSelection = (event) => {
250
- var _a;
251
- const childId = (_a = event.currentTarget.children.namedItem(id)) === null || _a === void 0 ? void 0 : _a.id;
252
325
  onClick({
253
326
  type: 'layer',
254
327
  item: layerId,
255
- nodeId: childId,
256
328
  event,
257
329
  });
258
330
  };
331
+ const handleRenameSave = () => {
332
+ const newName = editValue.trim();
333
+ if (newName && newName !== name && gisModel) {
334
+ const updatedLayer = Object.assign(Object.assign({}, layer), { name: newName });
335
+ gisModel.sharedModel.updateLayer(layerId, updatedLayer);
336
+ }
337
+ gisModel === null || gisModel === void 0 ? void 0 : gisModel.clearEditingItem();
338
+ };
339
+ const handleRenameCancel = () => {
340
+ setEditValue(name);
341
+ gisModel === null || gisModel === void 0 ? void 0 : gisModel.clearEditingItem();
342
+ };
343
+ const handleRenameKeyDown = (e) => {
344
+ if (e.key === 'Enter') {
345
+ e.preventDefault();
346
+ handleRenameSave();
347
+ }
348
+ else if (e.key === 'Escape') {
349
+ e.preventDefault();
350
+ handleRenameCancel();
351
+ }
352
+ };
259
353
  return (React.createElement("div", { className: `${LAYER_ITEM_CLASS} ${LAYER_CLASS}${selected ? ' jp-mod-selected' : ''}`, draggable: true, onDragStart: Private.onDragStart, onDragOver: Private.onDragOver, onDragEnd: Private.onDragEnd, "data-id": layerId, style: { display: 'flex', flexDirection: 'column' } },
260
354
  React.createElement("div", { className: LAYER_TITLE_CLASS, onClick: setSelection, onContextMenu: setSelection, style: { display: 'flex' } },
261
355
  hasSupportedSymbology && (React.createElement(Button, { minimal: true, onClick: e => {
@@ -266,7 +360,14 @@ const LayerComponent = props => {
266
360
  React.createElement(Button, { title: layer.visible ? 'Hide layer' : 'Show layer', onClick: toggleVisibility, minimal: true },
267
361
  React.createElement(LabIcon.resolveReact, { icon: layer.visible ? visibilityIcon : nonVisibilityIcon, className: `${LAYER_ICON_CLASS}${layer.visible ? '' : ' jp-gis-mod-hidden'}`, tag: "span" })),
268
362
  icons.has(layer.type) && (React.createElement(LabIcon.resolveReact, Object.assign({}, icons.get(layer.type), { className: LAYER_ICON_CLASS }))),
269
- React.createElement("span", { id: id, className: LAYER_TEXT_CLASS, tabIndex: -2 }, name)),
363
+ isEditing ? (React.createElement("input", { type: "text", value: editValue, onChange: e => setEditValue(e.target.value), onKeyDown: handleRenameKeyDown, onBlur: handleRenameSave, className: LAYER_TEXT_CLASS, style: {
364
+ flex: 1,
365
+ border: '1px solid var(--jp-border-color1)',
366
+ borderRadius: '2px',
367
+ padding: '2px 4px',
368
+ fontSize: 'inherit',
369
+ fontFamily: 'inherit',
370
+ }, autoFocus: true })) : (React.createElement("span", { id: id, className: LAYER_TEXT_CLASS, tabIndex: -2 }, name))),
270
371
  expanded && gisModel && hasSupportedSymbology && (React.createElement("div", { style: { marginTop: 6, width: '100%' } },
271
372
  React.createElement(LegendItem, { layerId: layerId, model: gisModel })))));
272
373
  };
@@ -42,7 +42,7 @@ export const LegendItem = ({ layerId, model }) => {
42
42
  return categories;
43
43
  };
44
44
  useEffect(() => {
45
- var _a, _b, _c, _d, _e, _f, _g, _h;
45
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
46
46
  if (isLoading) {
47
47
  setContent(React.createElement("p", { style: { fontSize: '0.8em' } }, "Loading\u2026"));
48
48
  return;
@@ -157,6 +157,41 @@ export const LegendItem = ({ layerId, model }) => {
157
157
  React.createElement("span", { style: { fontSize: '0.75em' } }, String(c.category))))))));
158
158
  return;
159
159
  }
160
+ // Heatmap
161
+ if (renderType === 'Heatmap') {
162
+ const colors = Array.isArray(symbology.color) ? symbology.color : [];
163
+ if (!colors.length) {
164
+ setContent(React.createElement("p", { style: { fontSize: '0.8em' } }, "No heatmap colors"));
165
+ return;
166
+ }
167
+ const gradient = `linear-gradient(to right, ${colors.join(', ')})`;
168
+ const reversed = (_j = symbology.symbologyState) === null || _j === void 0 ? void 0 : _j.reverse;
169
+ setContent(React.createElement("div", { style: { padding: 6, width: '90%' } },
170
+ React.createElement("div", { style: { fontSize: '1em', marginBottom: 10 } },
171
+ React.createElement("strong", null, "Heatmap")),
172
+ React.createElement("div", { style: {
173
+ height: 12,
174
+ background: gradient,
175
+ border: '1px solid #ccc',
176
+ borderRadius: 3,
177
+ marginBottom: 4,
178
+ } }),
179
+ React.createElement("div", { style: {
180
+ display: 'flex',
181
+ justifyContent: 'space-between',
182
+ fontSize: '0.75em',
183
+ marginBottom: 8,
184
+ } },
185
+ React.createElement("span", { style: { fontWeight: 'bold' } }, "Low"),
186
+ React.createElement("span", { style: { fontWeight: 'bold' } }, "High")),
187
+ React.createElement("div", { style: {
188
+ fontSize: '0.75em',
189
+ display: 'flex',
190
+ flexDirection: 'column',
191
+ gap: 2,
192
+ } }, reversed && (React.createElement("span", { style: { fontWeight: 'bold' } }, "Reversed ramp")))));
193
+ return;
194
+ }
160
195
  setContent(React.createElement("p", null,
161
196
  "Unsupported symbology: ",
162
197
  String(renderType)));
@@ -6,7 +6,6 @@ import * as React from 'react';
6
6
  export interface ILeftPanelClickHandlerParams {
7
7
  type: SelectionType;
8
8
  item: string;
9
- nodeId?: string;
10
9
  event: ReactMouseEvent;
11
10
  }
12
11
  interface ILeftPanelProps {
@@ -28,14 +28,14 @@ export const LeftPanel = (props) => {
28
28
  const [curTab, setCurTab] = React.useState(tabInfo.length > 0 ? tabInfo[0].name : undefined);
29
29
  return (React.createElement("div", { className: "jgis-left-panel-container", style: { display: leftPanelVisible ? 'block' : 'none' } },
30
30
  React.createElement(PanelTabs, { curTab: curTab, className: "jgis-panel-tabs" },
31
- React.createElement(TabsList, null, tabInfo.map(e => (React.createElement(TabsTrigger, { className: "jGIS-layer-browser-category", value: e.name, onClick: () => {
32
- if (curTab !== e.name) {
33
- setCurTab(e.name);
31
+ React.createElement(TabsList, null, tabInfo.map(tab => (React.createElement(TabsTrigger, { className: "jGIS-layer-browser-category", key: tab.name, value: tab.name, onClick: () => {
32
+ if (curTab !== tab.name) {
33
+ setCurTab(tab.name);
34
34
  }
35
35
  else {
36
36
  setCurTab('');
37
37
  }
38
- } }, e.title)))),
38
+ } }, tab.title)))),
39
39
  !settings.layersDisabled && (React.createElement(TabsContent, { value: "layers", className: "jgis-panel-tab-content jp-gis-layerPanel" },
40
40
  React.createElement(LayersBodyComponent, { model: props.model, commands: props.commands, state: props.state }))),
41
41
  !settings.stacBrowserDisabled && (React.createElement(TabsContent, { value: "stac", className: "jgis-panel-tab-content" },
@@ -47,14 +47,14 @@ export const RightPanel = props => {
47
47
  const [selectedObjectProperties, setSelectedObjectProperties] = React.useState(undefined);
48
48
  return (React.createElement("div", { className: "jgis-right-panel-container", style: { display: rightPanelVisible ? 'block' : 'none' } },
49
49
  React.createElement(PanelTabs, { className: "jgis-panel-tabs", curTab: curTab },
50
- React.createElement(TabsList, null, tabInfo.map(e => (React.createElement(TabsTrigger, { className: "jGIS-layer-browser-category", value: e.name, onClick: () => {
51
- if (curTab !== e.name) {
52
- setCurTab(e.name);
50
+ React.createElement(TabsList, null, tabInfo.map(tab => (React.createElement(TabsTrigger, { className: "jGIS-layer-browser-category", key: tab.name, value: tab.name, onClick: () => {
51
+ if (curTab !== tab.name) {
52
+ setCurTab(tab.name);
53
53
  }
54
54
  else {
55
55
  setCurTab('');
56
56
  }
57
- } }, e.title)))),
57
+ } }, tab.title)))),
58
58
  !settings.objectPropertiesDisabled && (React.createElement(TabsContent, { value: "objectProperties", className: "jgis-panel-tab-content" },
59
59
  React.createElement(ObjectPropertiesReact, { setSelectedObject: setSelectedObjectProperties, selectedObject: selectedObjectProperties, formSchemaRegistry: props.formSchemaRegistry, model: props.model }))),
60
60
  !settings.annotationsDisabled && (React.createElement(TabsContent, { value: "annotations", className: "jgis-panel-tab-content" },
@@ -5,6 +5,9 @@ export function processingFormToParam(formValues, processingType) {
5
5
  return;
6
6
  }
7
7
  const processingElement = ProcessingMerge.find(e => e.description === processingType);
8
+ if (!processingElement) {
9
+ return;
10
+ }
8
11
  const params = processingElement.operationParams;
9
12
  const out = {};
10
13
  for (let i = 0; i < params.length; i++) {
@@ -3,10 +3,10 @@ import { type VariantProps } from 'class-variance-authority';
3
3
  import * as React from 'react';
4
4
  declare const ToggleGroup: React.ForwardRefExoticComponent<((Omit<ToggleGroupPrimitive.ToggleGroupSingleProps & React.RefAttributes<HTMLDivElement>, "ref"> | Omit<ToggleGroupPrimitive.ToggleGroupMultipleProps & React.RefAttributes<HTMLDivElement>, "ref">) & VariantProps<(props?: ({
5
5
  variant?: "default" | "outline" | null | undefined;
6
- size?: "sm" | "lg" | "default" | null | undefined;
6
+ size?: "default" | "sm" | "lg" | null | undefined;
7
7
  } & import("class-variance-authority/dist/types").ClassProp) | undefined) => string>) & React.RefAttributes<HTMLDivElement>>;
8
8
  declare const ToggleGroupItem: React.ForwardRefExoticComponent<Omit<ToggleGroupPrimitive.ToggleGroupItemProps & React.RefAttributes<HTMLButtonElement>, "ref"> & VariantProps<(props?: ({
9
9
  variant?: "default" | "outline" | null | undefined;
10
- size?: "sm" | "lg" | "default" | null | undefined;
10
+ size?: "default" | "sm" | "lg" | null | undefined;
11
11
  } & import("class-variance-authority/dist/types").ClassProp) | undefined) => string> & React.RefAttributes<HTMLButtonElement>>;
12
12
  export { ToggleGroup, ToggleGroupItem };
@@ -138,7 +138,7 @@ function useStacSearch({ model }) {
138
138
  //@ts-expect-error Jupyter requires X-XSRFToken header
139
139
  options, 'internal'));
140
140
  if (!data) {
141
- console.log('No Results found');
141
+ console.debug('STAC search failed -- no results found');
142
142
  setResults([]);
143
143
  setTotalPages(1);
144
144
  setTotalResults(0);
@@ -150,7 +150,7 @@ function useStacSearch({ model }) {
150
150
  setTotalResults(data.context.matched);
151
151
  }
152
152
  catch (error) {
153
- console.error('Error fetching data:', error);
153
+ console.error('STAC search failed -- error fetching data:', error);
154
154
  setResults([]);
155
155
  setTotalPages(1);
156
156
  setTotalResults(0);
@@ -1,6 +1,6 @@
1
1
  import { UsersItem, DefaultIconRenderer } from '@jupyter/collaboration';
2
2
  import { CommandToolbarButton } from '@jupyterlab/apputils';
3
- import { MenuSvg, ReactWidget, ReactiveToolbar, ToolbarButton, addIcon, redoIcon, undoIcon, } from '@jupyterlab/ui-components';
3
+ import { MenuSvg, ReactWidget, ReactiveToolbar, ToolbarButton, addIcon, } from '@jupyterlab/ui-components';
4
4
  import { Widget } from '@lumino/widgets';
5
5
  import * as React from 'react';
6
6
  import { CommandIDs } from "../constants";
@@ -42,31 +42,6 @@ export class ToolbarWidget extends ReactiveToolbar {
42
42
  this._model = options.model;
43
43
  this.addClass('jGIS-toolbar-widget');
44
44
  if (options.commands) {
45
- const undoButton = new CommandToolbarButton({
46
- id: CommandIDs.undo,
47
- label: '',
48
- icon: undoIcon,
49
- commands: options.commands,
50
- });
51
- this.addItem('undo', undoButton);
52
- undoButton.node.dataset.testid = 'undo-button';
53
- const redoButton = new CommandToolbarButton({
54
- id: CommandIDs.redo,
55
- label: '',
56
- icon: redoIcon,
57
- commands: options.commands,
58
- });
59
- this.addItem('redo', redoButton);
60
- this.addItem('separator0', new Separator());
61
- const toggleConsoleButton = new CommandToolbarButton({
62
- id: CommandIDs.toggleConsole,
63
- commands: options.commands,
64
- label: '',
65
- icon: terminalToolbarIcon,
66
- });
67
- this.addItem('Toggle console', toggleConsoleButton);
68
- toggleConsoleButton.node.dataset.testid = 'toggle-console-button';
69
- this.addItem('separator1', new Separator());
70
45
  const openLayersBrowserButton = new CommandToolbarButton({
71
46
  id: CommandIDs.openLayerBrowser,
72
47
  label: '',
@@ -95,9 +70,9 @@ export class ToolbarWidget extends ReactiveToolbar {
95
70
  NewSubMenu.open(bbox.x, bbox.bottom);
96
71
  },
97
72
  });
98
- NewEntryButton.node.dataset.testid = 'new-entry-button';
99
73
  this.addItem('New', NewEntryButton);
100
- this.addItem('separator2', new Separator());
74
+ NewEntryButton.node.dataset.testid = 'new-entry-button';
75
+ this.addItem('separator1', new Separator());
101
76
  const geolocationButton = new CommandToolbarButton({
102
77
  id: CommandIDs.getGeolocation,
103
78
  commands: options.commands,
@@ -120,6 +95,22 @@ export class ToolbarWidget extends ReactiveToolbar {
120
95
  this.addItem('temporalController', temporalControllerButton);
121
96
  temporalControllerButton.node.dataset.testid =
122
97
  'temporal-controller-button';
98
+ const addMarkerButton = new CommandToolbarButton({
99
+ id: CommandIDs.addMarker,
100
+ label: '',
101
+ commands: options.commands,
102
+ });
103
+ this.addItem('addMarker', addMarkerButton);
104
+ addMarkerButton.node.dataset.testid = 'add-marker-controller-button';
105
+ this.addItem('separator2', new Separator());
106
+ const toggleConsoleButton = new CommandToolbarButton({
107
+ id: CommandIDs.toggleConsole,
108
+ commands: options.commands,
109
+ label: '',
110
+ icon: terminalToolbarIcon,
111
+ });
112
+ this.addItem('Toggle console', toggleConsoleButton);
113
+ toggleConsoleButton.node.dataset.testid = 'toggle-console-button';
123
114
  this.addItem('spacer', ReactiveToolbar.createSpacerItem());
124
115
  // Users
125
116
  const iconRenderer = createUserIconRenderer(this._model);
package/lib/tools.d.ts CHANGED
@@ -116,6 +116,7 @@ export declare const getMimeType: (filename: string) => string;
116
116
  * @returns An ArrayBuffer.
117
117
  */
118
118
  export declare const stringToArrayBuffer: (content: string) => Promise<ArrayBuffer>;
119
+ export declare const getFeatureAttributes: <T>(featureProperties: Record<string, Set<any>>, predicate?: (key: string, value: any) => boolean) => Record<string, Set<T>>;
119
120
  /**
120
121
  * Get attributes of the feature which are numeric.
121
122
  *
package/lib/tools.js CHANGED
@@ -81,7 +81,7 @@ export async function requestAPI(endPoint = '', init = {}) {
81
81
  data = JSON.parse(data);
82
82
  }
83
83
  catch (error) {
84
- console.log('Not a JSON response body.', response);
84
+ console.error('Jupyter API request failed -- not a JSON response body:', response);
85
85
  }
86
86
  }
87
87
  if (!response.ok) {
@@ -701,7 +701,7 @@ export const stringToArrayBuffer = async (content) => {
701
701
  const base64Response = await fetch(`data:application/octet-stream;base64,${content}`);
702
702
  return await base64Response.arrayBuffer();
703
703
  };
704
- const getFeatureAttributes = (featureProperties, predicate = (key, value) => true) => {
704
+ export const getFeatureAttributes = (featureProperties, predicate = (key, value) => true) => {
705
705
  const filteredRecord = {};
706
706
  for (const [key, set] of Object.entries(featureProperties)) {
707
707
  const firstValue = set.values().next().value;
package/lib/types.d.ts CHANGED
@@ -25,3 +25,5 @@ declare global {
25
25
  };
26
26
  }
27
27
  }
28
+ declare const classificationModes: readonly ["quantile", "equal interval", "jenks", "pretty", "logarithmic", "continuous"];
29
+ export type ClassificationMode = (typeof classificationModes)[number];
package/lib/types.js CHANGED
@@ -1 +1,9 @@
1
+ const classificationModes = [
2
+ 'quantile',
3
+ 'equal interval',
4
+ 'jenks',
5
+ 'pretty',
6
+ 'logarithmic',
7
+ 'continuous',
8
+ ];
1
9
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jupytergis/base",
3
- "version": "0.9.2",
3
+ "version": "0.10.0",
4
4
  "description": "A JupyterLab extension for 3D modelling.",
5
5
  "keywords": [
6
6
  "jupyter",
@@ -44,7 +44,7 @@
44
44
  "@jupyter/collaboration": "^3.1.0",
45
45
  "@jupyter/react-components": "^0.16.6",
46
46
  "@jupyter/ydoc": "^2.0.0 || ^3.0.0",
47
- "@jupytergis/schema": "^0.9.2",
47
+ "@jupytergis/schema": "^0.10.0",
48
48
  "@jupyterlab/application": "^4.3.0",
49
49
  "@jupyterlab/apputils": "^4.3.0",
50
50
  "@jupyterlab/completer": "^4.3.0",
package/style/base.css CHANGED
@@ -92,6 +92,7 @@ button.jp-mod-styled.jp-mod-reject {
92
92
  right: 0px;
93
93
  position: absolute;
94
94
  margin: 5px;
95
+ z-index: 40;
95
96
  }
96
97
 
97
98
  .jgis-left-panel-container {
@@ -100,6 +101,7 @@ button.jp-mod-styled.jp-mod-reject {
100
101
  top: 30px;
101
102
  left: 0px;
102
103
  margin: 5px;
104
+ z-index: 40;
103
105
  }
104
106
 
105
107
  @media (max-width: 768px) {
@@ -4,7 +4,7 @@
4
4
  version="1.1"
5
5
  id="svg1"
6
6
  xmlns="http://www.w3.org/2000/svg"
7
- xmlns:svg="http://www.w3.org/2000/svg">
7
+ >
8
8
  <defs
9
9
  id="defs1" />
10
10
  <!--!Font
@@ -4,7 +4,7 @@
4
4
  version="1.1"
5
5
  id="svg1"
6
6
  xmlns="http://www.w3.org/2000/svg"
7
- xmlns:svg="http://www.w3.org/2000/svg">
7
+ >
8
8
  <defs
9
9
  id="defs1" />
10
10
  <!--!FontAwesome Free 6.7.2 by @fontawesome - https://fontawesome.com License -https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.-->
@@ -4,7 +4,7 @@
4
4
  version="1.1"
5
5
  id="svg1"
6
6
  xmlns="http://www.w3.org/2000/svg"
7
- xmlns:svg="http://www.w3.org/2000/svg">
7
+ >
8
8
  <defs
9
9
  id="defs1" />
10
10
  <!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.-->
@@ -4,7 +4,7 @@
4
4
  version="1.1"
5
5
  id="svg1"
6
6
  xmlns="http://www.w3.org/2000/svg"
7
- xmlns:svg="http://www.w3.org/2000/svg">
7
+ >
8
8
  <defs
9
9
  id="defs1" />
10
10
  <!--!Font
@@ -11,7 +11,7 @@
11
11
  xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
12
12
  xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
13
13
  xmlns="http://www.w3.org/2000/svg"
14
- xmlns:svg="http://www.w3.org/2000/svg">
14
+ >
15
15
  <defs
16
16
  class="jp-icon-selectable jgis-main-logo">
17
17
  <clipPath