@jupytergis/base 0.13.3 → 0.14.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 (115) hide show
  1. package/lib/commands/BaseCommandIDs.d.ts +14 -13
  2. package/lib/commands/BaseCommandIDs.js +14 -14
  3. package/lib/commands/index.js +523 -117
  4. package/lib/commands/operationCommands.d.ts +22 -0
  5. package/lib/commands/operationCommands.js +305 -0
  6. package/lib/constants.js +9 -9
  7. package/lib/dialogs/ProcessingFormDialog.d.ts +1 -1
  8. package/lib/dialogs/ProcessingFormDialog.js +2 -2
  9. package/lib/dialogs/layerBrowserDialog.d.ts +2 -0
  10. package/lib/dialogs/layerBrowserDialog.js +12 -5
  11. package/lib/dialogs/layerCreationFormDialog.d.ts +7 -0
  12. package/lib/dialogs/layerCreationFormDialog.js +10 -2
  13. package/lib/dialogs/symbology/components/color_ramp/ColorRampControls.d.ts +2 -1
  14. package/lib/dialogs/symbology/components/color_ramp/ColorRampControls.js +21 -19
  15. package/lib/dialogs/symbology/components/color_ramp/ColorRampSelector.d.ts +3 -1
  16. package/lib/dialogs/symbology/components/color_ramp/ColorRampSelector.js +13 -5
  17. package/lib/dialogs/symbology/tiff_layer/types/SingleBandPseudoColor.js +6 -4
  18. package/lib/dialogs/symbology/vector_layer/types/Categorized.js +7 -11
  19. package/lib/dialogs/symbology/vector_layer/types/Graduated.js +7 -10
  20. package/lib/dialogs/symbology/vector_layer/types/Heatmap.js +7 -8
  21. package/lib/formbuilder/creationform.d.ts +8 -8
  22. package/lib/formbuilder/creationform.js +130 -85
  23. package/lib/formbuilder/editform.d.ts +1 -7
  24. package/lib/formbuilder/editform.js +64 -52
  25. package/lib/formbuilder/formselectors.d.ts +5 -4
  26. package/lib/formbuilder/index.d.ts +1 -1
  27. package/lib/formbuilder/index.js +1 -1
  28. package/lib/formbuilder/objectform/SchemaForm.d.ts +36 -0
  29. package/lib/formbuilder/objectform/SchemaForm.js +77 -0
  30. package/lib/formbuilder/objectform/StoryEditorForm.d.ts +3 -9
  31. package/lib/formbuilder/objectform/StoryEditorForm.js +20 -14
  32. package/lib/formbuilder/objectform/components/LayerSelect.js +3 -8
  33. package/lib/formbuilder/objectform/components/SegmentFormSymbology.js +23 -10
  34. package/lib/formbuilder/objectform/components/SourcePropertiesField.d.ts +7 -0
  35. package/lib/formbuilder/objectform/components/SourcePropertiesField.js +29 -0
  36. package/lib/formbuilder/objectform/components/StorySegmentReset.js +1 -1
  37. package/lib/formbuilder/objectform/fileselectorwidget.js +1 -1
  38. package/lib/formbuilder/objectform/layer/heatmapLayerForm.d.ts +3 -12
  39. package/lib/formbuilder/objectform/layer/heatmapLayerForm.js +87 -55
  40. package/lib/formbuilder/objectform/layer/hillshadeLayerForm.d.ts +3 -8
  41. package/lib/formbuilder/objectform/layer/hillshadeLayerForm.js +36 -10
  42. package/lib/formbuilder/objectform/layer/layerform.d.ts +7 -9
  43. package/lib/formbuilder/objectform/layer/layerform.js +33 -20
  44. package/lib/formbuilder/objectform/layer/storySegmentLayerForm.d.ts +3 -5
  45. package/lib/formbuilder/objectform/layer/storySegmentLayerForm.js +73 -29
  46. package/lib/formbuilder/objectform/layer/vectorlayerform.d.ts +3 -14
  47. package/lib/formbuilder/objectform/layer/vectorlayerform.js +36 -29
  48. package/lib/formbuilder/objectform/layer/webGlLayerForm.d.ts +3 -10
  49. package/lib/formbuilder/objectform/layer/webGlLayerForm.js +37 -13
  50. package/lib/formbuilder/objectform/process/dissolveProcessForm.d.ts +8 -18
  51. package/lib/formbuilder/objectform/process/dissolveProcessForm.js +68 -56
  52. package/lib/formbuilder/objectform/processingForm.d.ts +12 -0
  53. package/lib/formbuilder/objectform/processingForm.js +33 -0
  54. package/lib/formbuilder/objectform/schemaUtils.d.ts +16 -0
  55. package/lib/formbuilder/objectform/schemaUtils.js +59 -0
  56. package/lib/formbuilder/objectform/source/geojsonsource.d.ts +3 -19
  57. package/lib/formbuilder/objectform/source/geojsonsource.js +94 -53
  58. package/lib/formbuilder/objectform/source/geotiffsource.d.ts +3 -20
  59. package/lib/formbuilder/objectform/source/geotiffsource.js +73 -74
  60. package/lib/formbuilder/objectform/source/pathbasedsource.d.ts +3 -19
  61. package/lib/formbuilder/objectform/source/pathbasedsource.js +76 -75
  62. package/lib/formbuilder/objectform/source/sourceform.d.ts +7 -8
  63. package/lib/formbuilder/objectform/source/sourceform.js +28 -11
  64. package/lib/formbuilder/objectform/source/tilesourceform.d.ts +3 -7
  65. package/lib/formbuilder/objectform/source/tilesourceform.js +63 -53
  66. package/lib/formbuilder/objectform/useSchemaFormState.d.ts +48 -0
  67. package/lib/formbuilder/objectform/useSchemaFormState.js +35 -0
  68. package/lib/index.d.ts +0 -1
  69. package/lib/index.js +0 -1
  70. package/lib/keybindings.json +10 -10
  71. package/lib/mainview/mainView.d.ts +11 -7
  72. package/lib/mainview/mainView.js +62 -35
  73. package/lib/mainview/mainviewmodel.js +2 -2
  74. package/lib/menus.js +8 -8
  75. package/lib/panelview/components/layers.js +5 -2
  76. package/lib/panelview/objectproperties.js +2 -2
  77. package/lib/panelview/rightpanel.js +17 -2
  78. package/lib/panelview/story-maps/SpectaPanel.d.ts +15 -0
  79. package/lib/panelview/story-maps/SpectaPanel.js +35 -0
  80. package/lib/panelview/story-maps/StoryViewerPanel.d.ts +24 -9
  81. package/lib/panelview/story-maps/StoryViewerPanel.js +22 -268
  82. package/lib/panelview/story-maps/{PreviewModeSwitch.js → components/PreviewModeSwitch.js} +1 -1
  83. package/lib/panelview/story-maps/components/SpectaDesktopView.d.ts +21 -0
  84. package/lib/panelview/story-maps/components/SpectaDesktopView.js +49 -0
  85. package/lib/panelview/story-maps/components/SpectaMobileView.d.ts +17 -0
  86. package/lib/panelview/story-maps/{MobileSpectaPanel.js → components/SpectaMobileView.js} +8 -22
  87. package/lib/panelview/story-maps/{StoryNavBar.d.ts → components/StoryNavBar.d.ts} +1 -1
  88. package/lib/panelview/story-maps/{StoryNavBar.js → components/StoryNavBar.js} +2 -4
  89. package/lib/panelview/story-maps/hooks/useStoryMap.d.ts +39 -0
  90. package/lib/panelview/story-maps/hooks/useStoryMap.js +252 -0
  91. package/lib/processing/index.d.ts +2 -2
  92. package/lib/processing/index.js +62 -35
  93. package/lib/processing/processingCommands.d.ts +1 -1
  94. package/lib/processing/processingCommands.js +26 -6
  95. package/lib/shared/components/Collapsible.d.ts +6 -0
  96. package/lib/shared/components/Collapsible.js +26 -0
  97. package/lib/statusbar/SpectaPresentationProgressBar.d.ts +7 -0
  98. package/lib/statusbar/SpectaPresentationProgressBar.js +40 -0
  99. package/lib/toolbar/widget.js +4 -2
  100. package/lib/tools.d.ts +6 -0
  101. package/lib/tools.js +9 -0
  102. package/lib/types.d.ts +29 -2
  103. package/package.json +2 -4
  104. package/style/base.css +23 -1
  105. package/style/dialog.css +5 -0
  106. package/style/leftPanel.css +14 -37
  107. package/style/shared/button.css +0 -10
  108. package/style/shared/switch.css +8 -7
  109. package/style/spectaProgressBar.css +144 -0
  110. package/style/storyPanel.css +33 -0
  111. package/style/symbologyDialog.css +2 -2
  112. package/lib/formbuilder/objectform/baseform.d.ts +0 -91
  113. package/lib/formbuilder/objectform/baseform.js +0 -231
  114. package/lib/panelview/story-maps/MobileSpectaPanel.d.ts +0 -7
  115. /package/lib/panelview/story-maps/{PreviewModeSwitch.d.ts → components/PreviewModeSwitch.d.ts} +0 -0
@@ -1,4 +1,4 @@
1
- import { showErrorMessage } from '@jupyterlab/apputils';
1
+ import { UUID } from '@lumino/coreutils';
2
2
  import { fromLonLat } from 'ol/proj';
3
3
  import { CommandIDs, icons } from '../constants';
4
4
  import { ProcessingFormDialog } from '../dialogs/ProcessingFormDialog';
@@ -12,6 +12,7 @@ import { addProcessingCommands } from '../processing/processingCommands';
12
12
  import { getGeoJSONDataFromLayerSource, downloadFile } from '../tools';
13
13
  import { SYMBOLOGY_VALID_LAYER_TYPES } from '../types';
14
14
  import { JupyterGISDocumentWidget } from '../widget';
15
+ import { addLayerCreationCommands } from './operationCommands';
15
16
  const POINT_SELECTION_TOOL_CLASS = 'jGIS-point-selection-tool';
16
17
  function loadKeybindings(commands, keybindings) {
17
18
  keybindings.forEach(binding => {
@@ -29,6 +30,7 @@ export function addCommands(app, tracker, translator, formSchemaRegistry, layerB
29
30
  var _a;
30
31
  const trans = translator.load('jupyterlab');
31
32
  const { commands } = app;
33
+ addLayerCreationCommands({ tracker, commands, trans });
32
34
  /**
33
35
  * Wraps a command definition to automatically disable it in Specta mode
34
36
  */
@@ -54,7 +56,17 @@ export function addCommands(app, tracker, translator, formSchemaRegistry, layerB
54
56
  commands.addCommand = (id, options) => {
55
57
  return originalAddCommand(id, createSpectaAwareCommand(options));
56
58
  };
57
- commands.addCommand(CommandIDs.symbology, Object.assign({ label: trans.__('Edit Symbology'), isEnabled: () => {
59
+ commands.addCommand(CommandIDs.symbology, Object.assign({ label: trans.__('Edit Symbology'), caption: 'Open the symbology editor for the currently selected layer.', describedBy: {
60
+ args: {
61
+ type: 'object',
62
+ properties: {
63
+ selected: {
64
+ type: 'object',
65
+ description: 'Currently selected layer(s) in the map view',
66
+ },
67
+ },
68
+ },
69
+ }, isEnabled: () => {
58
70
  var _a, _b;
59
71
  const model = (_a = tracker.currentWidget) === null || _a === void 0 ? void 0 : _a.model;
60
72
  const localState = model === null || model === void 0 ? void 0 : model.sharedModel.awareness.getLocalState();
@@ -74,27 +86,65 @@ export function addCommands(app, tracker, translator, formSchemaRegistry, layerB
74
86
  const isValidLayer = SYMBOLOGY_VALID_LAYER_TYPES.includes(layer.type);
75
87
  return isValidLayer;
76
88
  }, execute: Private.createSymbologyDialog(tracker, state) }, icons.get(CommandIDs.symbology)));
77
- commands.addCommand(CommandIDs.redo, Object.assign({ label: trans.__('Redo'), isEnabled: () => {
89
+ commands.addCommand(CommandIDs.redo, Object.assign({ label: trans.__('Redo'), caption: 'Redo the last undone operation in the specified JupyterGIS document. If filePath is omitted, use the active document.', describedBy: {
90
+ args: {
91
+ type: 'object',
92
+ properties: {
93
+ filePath: {
94
+ type: 'string',
95
+ description: 'Optional .jGIS file path. If omitted, uses active widget.',
96
+ },
97
+ },
98
+ },
99
+ }, isEnabled: () => {
78
100
  return tracker.currentWidget
79
101
  ? tracker.currentWidget.model.sharedModel.editable
80
102
  : false;
81
- }, execute: () => {
82
- const current = tracker.currentWidget;
103
+ }, execute: (args) => {
104
+ const filePath = args === null || args === void 0 ? void 0 : args.filePath;
105
+ const current = filePath
106
+ ? tracker.find(w => w.model.filePath === filePath)
107
+ : tracker.currentWidget;
83
108
  if (current) {
84
109
  return current.model.sharedModel.redo();
85
110
  }
86
111
  } }, (_a = icons.get(CommandIDs.redo)) === null || _a === void 0 ? void 0 : _a.icon));
87
- commands.addCommand(CommandIDs.undo, Object.assign({ label: trans.__('Undo'), isEnabled: () => {
112
+ commands.addCommand(CommandIDs.undo, Object.assign({ label: trans.__('Undo'), caption: 'Undo the last operation in the specified JupyterGIS document. If filePath is omitted, use the active document.', describedBy: {
113
+ args: {
114
+ type: 'object',
115
+ required: [],
116
+ properties: {
117
+ filePath: {
118
+ type: 'string',
119
+ description: 'Optional .jGIS file path. If omitted, uses active widget.',
120
+ },
121
+ },
122
+ },
123
+ }, isEnabled: () => {
88
124
  return tracker.currentWidget
89
125
  ? tracker.currentWidget.model.sharedModel.editable
90
126
  : false;
91
- }, execute: () => {
92
- const current = tracker.currentWidget;
127
+ }, execute: (args) => {
128
+ const filePath = args === null || args === void 0 ? void 0 : args.filePath;
129
+ const current = filePath
130
+ ? tracker.find(w => w.model.filePath === filePath)
131
+ : tracker.currentWidget;
93
132
  if (current) {
94
133
  return current.model.sharedModel.undo();
95
134
  }
96
135
  } }, icons.get(CommandIDs.undo)));
97
- commands.addCommand(CommandIDs.identify, Object.assign({ label: trans.__('Identify'), isToggled: () => {
136
+ commands.addCommand(CommandIDs.identify, Object.assign({ label: trans.__('Identify'), caption: 'Toggle identify mode for the selected vector-compatible layer in the specified JupyterGIS document.', describedBy: {
137
+ args: {
138
+ type: 'object',
139
+ required: [],
140
+ properties: {
141
+ filePath: {
142
+ type: 'string',
143
+ description: 'Optional .jGIS file path. If omitted, uses active widget.',
144
+ },
145
+ },
146
+ },
147
+ }, isToggled: () => {
98
148
  const current = tracker.currentWidget;
99
149
  if (!current) {
100
150
  return false;
@@ -131,7 +181,10 @@ export function addCommands(app, tracker, translator, formSchemaRegistry, layerB
131
181
  'VectorTileLayer',
132
182
  ].includes(selectedLayer.type);
133
183
  }, execute: args => {
134
- const current = tracker.currentWidget;
184
+ const filePath = args === null || args === void 0 ? void 0 : args.filePath;
185
+ const current = filePath
186
+ ? tracker.find(w => w.model.filePath === filePath)
187
+ : tracker.currentWidget;
135
188
  if (!current) {
136
189
  return;
137
190
  }
@@ -149,7 +202,17 @@ export function addCommands(app, tracker, translator, formSchemaRegistry, layerB
149
202
  current.model.toggleMode('identifying');
150
203
  commands.notifyCommandChanged(CommandIDs.identify);
151
204
  } }, icons.get(CommandIDs.identify)));
152
- commands.addCommand(CommandIDs.temporalController, Object.assign({ label: trans.__('Temporal Controller'), isToggled: () => {
205
+ commands.addCommand(CommandIDs.temporalController, Object.assign({ label: trans.__('Temporal Controller'), caption: 'Toggle the temporal controller for the selected vector or heatmap layer in the specified JupyterGIS document.', describedBy: {
206
+ args: {
207
+ type: 'object',
208
+ properties: {
209
+ filePath: {
210
+ type: 'string',
211
+ description: 'Optional path to the .jGIS file',
212
+ },
213
+ },
214
+ },
215
+ }, isToggled: () => {
153
216
  var _a;
154
217
  return ((_a = tracker.currentWidget) === null || _a === void 0 ? void 0 : _a.model.isTemporalControllerActive) || false;
155
218
  }, isEnabled: () => {
@@ -177,8 +240,11 @@ export function addCommands(app, tracker, translator, formSchemaRegistry, layerB
177
240
  return false;
178
241
  }
179
242
  return true;
180
- }, execute: () => {
181
- const current = tracker.currentWidget;
243
+ }, execute: (args) => {
244
+ const filePath = args === null || args === void 0 ? void 0 : args.filePath;
245
+ const current = filePath
246
+ ? tracker.find(w => w.model.filePath === filePath)
247
+ : tracker.currentWidget;
182
248
  if (!current) {
183
249
  return;
184
250
  }
@@ -188,7 +254,12 @@ export function addCommands(app, tracker, translator, formSchemaRegistry, layerB
188
254
  /**
189
255
  * SOURCES and LAYERS creation commands.
190
256
  */
191
- commands.addCommand(CommandIDs.openLayerBrowser, Object.assign({ label: trans.__('Open Layer Browser'), isEnabled: () => {
257
+ commands.addCommand(CommandIDs.openLayerBrowser, Object.assign({ label: trans.__('Open Layer Browser'), caption: 'Open the layer browser dialog to browse and add available layers to the current JupyterGIS document.', describedBy: {
258
+ args: {
259
+ type: 'object',
260
+ properties: {},
261
+ },
262
+ }, isEnabled: () => {
192
263
  return tracker.currentWidget
193
264
  ? tracker.currentWidget.model.sharedModel.editable
194
265
  : false;
@@ -196,7 +267,12 @@ export function addCommands(app, tracker, translator, formSchemaRegistry, layerB
196
267
  /**
197
268
  * Source and layers
198
269
  */
199
- commands.addCommand(CommandIDs.newRasterEntry, Object.assign({ label: trans.__('New Raster Tile Layer'), isEnabled: () => {
270
+ commands.addCommand(CommandIDs.openNewRasterDialog, Object.assign({ label: trans.__('Open New Raster Tile Layer Creation Dialog'), caption: 'Open a dialog to create a new raster tile layer and source in the current JupyterGIS document.', describedBy: {
271
+ args: {
272
+ type: 'object',
273
+ properties: {},
274
+ },
275
+ }, isEnabled: () => {
200
276
  return tracker.currentWidget
201
277
  ? tracker.currentWidget.model.sharedModel.editable
202
278
  : false;
@@ -206,15 +282,16 @@ export function addCommands(app, tracker, translator, formSchemaRegistry, layerB
206
282
  title: 'Create Raster Tile Layer',
207
283
  createLayer: true,
208
284
  createSource: true,
209
- sourceData: {
210
- minZoom: 0,
211
- maxZoom: 24,
212
- },
213
285
  layerData: { name: 'Custom Raster Tile Layer' },
214
286
  sourceType: 'RasterSource',
215
287
  layerType: 'RasterLayer',
216
- }) }, icons.get(CommandIDs.newRasterEntry)));
217
- commands.addCommand(CommandIDs.newVectorTileEntry, Object.assign({ label: trans.__('New Vector Tile Layer'), isEnabled: () => {
288
+ }) }, icons.get(CommandIDs.openNewRasterDialog)));
289
+ commands.addCommand(CommandIDs.openNewVectorTileDialog, Object.assign({ label: trans.__('Open New Vector Tile Layer Creation Dialog'), caption: 'Open a dialog to create a new vector tile layer and source in the current JupyterGIS document.', describedBy: {
290
+ args: {
291
+ type: 'object',
292
+ properties: {},
293
+ },
294
+ }, isEnabled: () => {
218
295
  return tracker.currentWidget
219
296
  ? tracker.currentWidget.model.sharedModel.editable
220
297
  : false;
@@ -224,12 +301,16 @@ export function addCommands(app, tracker, translator, formSchemaRegistry, layerB
224
301
  title: 'Create Vector Tile Layer',
225
302
  createLayer: true,
226
303
  createSource: true,
227
- sourceData: { minZoom: 0, maxZoom: 24 },
228
304
  layerData: { name: 'Custom Vector Tile Layer' },
229
305
  sourceType: 'VectorTileSource',
230
306
  layerType: 'VectorTileLayer',
231
- }) }, icons.get(CommandIDs.newVectorTileEntry)));
232
- commands.addCommand(CommandIDs.newGeoParquetEntry, Object.assign({ label: trans.__('New GeoParquet Layer'), isEnabled: () => {
307
+ }) }, icons.get(CommandIDs.openNewVectorTileDialog)));
308
+ commands.addCommand(CommandIDs.openNewGeoParquetDialog, Object.assign({ label: trans.__('Open New GeoParquet Layer Creation Dialog'), caption: 'Open a dialog to create a new GeoParquet layer and source in the current JupyterGIS document.', describedBy: {
309
+ args: {
310
+ type: 'object',
311
+ properties: {},
312
+ },
313
+ }, isEnabled: () => {
233
314
  return tracker.currentWidget
234
315
  ? tracker.currentWidget.model.sharedModel.editable
235
316
  : false;
@@ -243,8 +324,13 @@ export function addCommands(app, tracker, translator, formSchemaRegistry, layerB
243
324
  layerData: { name: 'Custom GeoParquet Layer' },
244
325
  sourceType: 'GeoParquetSource',
245
326
  layerType: 'VectorLayer',
246
- }) }, icons.get(CommandIDs.newGeoParquetEntry)));
247
- commands.addCommand(CommandIDs.newGeoJSONEntry, Object.assign({ label: trans.__('New GeoJSON layer'), isEnabled: () => {
327
+ }) }, icons.get(CommandIDs.openNewGeoParquetDialog)));
328
+ commands.addCommand(CommandIDs.openNewGeoJSONDialog, Object.assign({ label: trans.__('Open New GeoJSON Layer Creation Dialog'), caption: 'Open a dialog to create a new GeoJSON layer and source in the current JupyterGIS document.', describedBy: {
329
+ args: {
330
+ type: 'object',
331
+ properties: {},
332
+ },
333
+ }, isEnabled: () => {
248
334
  return tracker.currentWidget
249
335
  ? tracker.currentWidget.model.sharedModel.editable
250
336
  : false;
@@ -257,10 +343,15 @@ export function addCommands(app, tracker, translator, formSchemaRegistry, layerB
257
343
  layerData: { name: 'Custom GeoJSON Layer' },
258
344
  sourceType: 'GeoJSONSource',
259
345
  layerType: 'VectorLayer',
260
- }) }, icons.get(CommandIDs.newGeoJSONEntry)));
346
+ }) }, icons.get(CommandIDs.openNewGeoJSONDialog)));
261
347
  //Add processing commands
262
- addProcessingCommands(app, commands, tracker, trans, formSchemaRegistry);
263
- commands.addCommand(CommandIDs.newHillshadeEntry, Object.assign({ label: trans.__('New Hillshade layer'), isEnabled: () => {
348
+ addProcessingCommands(app, commands, tracker, trans, formSchemaRegistry, Object.fromEntries(formSchemaRegistry.getSchemas()));
349
+ commands.addCommand(CommandIDs.openNewHillshadeDialog, Object.assign({ label: trans.__('Open New Hillshade Layer Creation Dialog'), caption: 'Open a dialog to create a new hillshade layer and source in the current JupyterGIS document.', describedBy: {
350
+ args: {
351
+ type: 'object',
352
+ properties: {},
353
+ },
354
+ }, isEnabled: () => {
264
355
  return tracker.currentWidget
265
356
  ? tracker.currentWidget.model.sharedModel.editable
266
357
  : false;
@@ -273,8 +364,13 @@ export function addCommands(app, tracker, translator, formSchemaRegistry, layerB
273
364
  layerData: { name: 'Custom Hillshade Layer' },
274
365
  sourceType: 'RasterDemSource',
275
366
  layerType: 'HillshadeLayer',
276
- }) }, icons.get(CommandIDs.newHillshadeEntry)));
277
- commands.addCommand(CommandIDs.newImageEntry, Object.assign({ label: trans.__('New Image layer'), isEnabled: () => {
367
+ }) }, icons.get(CommandIDs.openNewHillshadeDialog)));
368
+ commands.addCommand(CommandIDs.openNewImageDialog, Object.assign({ label: trans.__('Open New Image Layer Creation Dialog'), caption: 'Open a dialog to create a new image layer and source in the current JupyterGIS document.', describedBy: {
369
+ args: {
370
+ type: 'object',
371
+ properties: {},
372
+ },
373
+ }, isEnabled: () => {
278
374
  return tracker.currentWidget
279
375
  ? tracker.currentWidget.model.sharedModel.editable
280
376
  : false;
@@ -297,8 +393,13 @@ export function addCommands(app, tracker, translator, formSchemaRegistry, layerB
297
393
  layerData: { name: 'Custom Image Layer' },
298
394
  sourceType: 'ImageSource',
299
395
  layerType: 'ImageLayer',
300
- }) }, icons.get(CommandIDs.newImageEntry)));
301
- commands.addCommand(CommandIDs.newVideoEntry, Object.assign({ label: trans.__('New Video layer'), isEnabled: () => {
396
+ }) }, icons.get(CommandIDs.openNewImageDialog)));
397
+ commands.addCommand(CommandIDs.openNewVideoDialog, Object.assign({ label: trans.__('Open New Video Layer Creation Dialog'), caption: 'Open a dialog to create a new video layer and source in the current JupyterGIS document.', describedBy: {
398
+ args: {
399
+ type: 'object',
400
+ properties: {},
401
+ },
402
+ }, isEnabled: () => {
302
403
  return tracker.currentWidget
303
404
  ? tracker.currentWidget.model.sharedModel.editable
304
405
  : false;
@@ -324,8 +425,13 @@ export function addCommands(app, tracker, translator, formSchemaRegistry, layerB
324
425
  layerData: { name: 'Custom Video Layer' },
325
426
  sourceType: 'VideoSource',
326
427
  layerType: 'RasterLayer',
327
- }) }, icons.get(CommandIDs.newVideoEntry)));
328
- commands.addCommand(CommandIDs.newGeoTiffEntry, Object.assign({ label: trans.__('New GeoTiff layer'), isEnabled: () => {
428
+ }) }, icons.get(CommandIDs.openNewVideoDialog)));
429
+ commands.addCommand(CommandIDs.openNewGeoTiffDialog, Object.assign({ label: trans.__('Open New GeoTiff Layer Creation Dialog'), caption: 'Open a dialog to create a new GeoTiff layer and source in the current JupyterGIS document.', describedBy: {
430
+ args: {
431
+ type: 'object',
432
+ properties: {},
433
+ },
434
+ }, isEnabled: () => {
329
435
  return tracker.currentWidget
330
436
  ? tracker.currentWidget.model.sharedModel.editable
331
437
  : false;
@@ -342,8 +448,13 @@ export function addCommands(app, tracker, translator, formSchemaRegistry, layerB
342
448
  layerData: { name: 'Custom GeoTiff Layer' },
343
449
  sourceType: 'GeoTiffSource',
344
450
  layerType: 'WebGlLayer',
345
- }) }, icons.get(CommandIDs.newGeoTiffEntry)));
346
- commands.addCommand(CommandIDs.newShapefileEntry, Object.assign({ label: trans.__('New Shapefile Layer'), isEnabled: () => {
451
+ }) }, icons.get(CommandIDs.openNewGeoTiffDialog)));
452
+ commands.addCommand(CommandIDs.openNewShapefileDialog, Object.assign({ label: trans.__('Open New Shapefile Layer Creation Dialog'), caption: 'Open a dialog to create a new shapefile layer and source in the current JupyterGIS document.', describedBy: {
453
+ args: {
454
+ type: 'object',
455
+ properties: {},
456
+ },
457
+ }, isEnabled: () => {
347
458
  return tracker.currentWidget
348
459
  ? tracker.currentWidget.model.sharedModel.editable
349
460
  : false;
@@ -357,7 +468,7 @@ export function addCommands(app, tracker, translator, formSchemaRegistry, layerB
357
468
  layerData: { name: 'Custom Shapefile Layer' },
358
469
  sourceType: 'ShapefileSource',
359
470
  layerType: 'VectorLayer',
360
- }) }, icons.get(CommandIDs.newShapefileEntry)));
471
+ }) }, icons.get(CommandIDs.openNewShapefileDialog)));
361
472
  /**
362
473
  * LAYERS and LAYER GROUP actions.
363
474
  */
@@ -389,25 +500,116 @@ export function addCommands(app, tracker, translator, formSchemaRegistry, layerB
389
500
  }
390
501
  await Private.removeSelectedItems(model);
391
502
  } }, icons.get(CommandIDs.removeSelected)));
392
- commands.addCommand(CommandIDs.moveLayersToGroup, {
393
- label: args => args['label'] ? args['label'] : trans.__('Move to Root'),
394
- execute: args => {
503
+ commands.addCommand(CommandIDs.duplicateSelected, {
504
+ label: trans.__('Duplicate'),
505
+ isEnabled: () => {
506
+ var _a, _b, _c;
507
+ const model = (_a = tracker.currentWidget) === null || _a === void 0 ? void 0 : _a.model;
508
+ const selected = (_c = (_b = model === null || model === void 0 ? void 0 : model.localState) === null || _b === void 0 ? void 0 : _b.selected) === null || _c === void 0 ? void 0 : _c.value;
509
+ if (!selected) {
510
+ return false;
511
+ }
512
+ return Object.values(selected).some(item => item.type === 'layer');
513
+ },
514
+ execute: () => {
395
515
  var _a, _b, _c;
396
516
  const model = (_a = tracker.currentWidget) === null || _a === void 0 ? void 0 : _a.model;
397
- const groupName = args['label'];
398
- const selectedLayers = (_c = (_b = model === null || model === void 0 ? void 0 : model.localState) === null || _b === void 0 ? void 0 : _b.selected) === null || _c === void 0 ? void 0 : _c.value;
517
+ const selected = (_c = (_b = model === null || model === void 0 ? void 0 : model.localState) === null || _b === void 0 ? void 0 : _b.selected) === null || _c === void 0 ? void 0 : _c.value;
518
+ if (!model || !selected) {
519
+ return;
520
+ }
521
+ for (const [layerId, selectedItem] of Object.entries(selected)) {
522
+ if (selectedItem.type !== 'layer') {
523
+ continue;
524
+ }
525
+ const layer = model.getLayer(layerId);
526
+ if (!layer) {
527
+ continue;
528
+ }
529
+ const clonedLayer = Object.assign(Object.assign({}, layer), { name: Private.generateCopyName(layer.name, model) });
530
+ model.addLayer(UUID.uuid4(), clonedLayer);
531
+ }
532
+ },
533
+ });
534
+ commands.addCommand(CommandIDs.moveSelectedToGroup, {
535
+ label: args => args['label']
536
+ ? args['label']
537
+ : trans.__('Move Selection to Root Group'),
538
+ caption: 'Group layers together in a new group with name "groupName" for the JupyterGIS document "filepath"',
539
+ describedBy: {
540
+ args: {
541
+ type: 'object',
542
+ properties: {
543
+ label: { type: 'string' },
544
+ filePath: { type: 'string' },
545
+ layerIds: {
546
+ type: 'array',
547
+ items: { type: 'string' },
548
+ },
549
+ groupName: { type: 'string' },
550
+ },
551
+ },
552
+ },
553
+ execute: (args) => {
554
+ var _a, _b, _c, _d;
555
+ const { filePath, layerIds, groupName } = args !== null && args !== void 0 ? args : {};
556
+ // Resolve model based on filePath or current widget
557
+ const model = filePath
558
+ ? (_a = tracker.find(w => w.model.filePath === filePath)) === null || _a === void 0 ? void 0 : _a.model
559
+ : (_b = tracker.currentWidget) === null || _b === void 0 ? void 0 : _b.model;
560
+ if (!model || !model.sharedModel.editable) {
561
+ return;
562
+ }
563
+ if (filePath && layerIds && groupName !== undefined) {
564
+ model.moveItemsToGroup(layerIds, groupName);
565
+ return;
566
+ }
567
+ const selectedLayers = (_d = (_c = model.localState) === null || _c === void 0 ? void 0 : _c.selected) === null || _d === void 0 ? void 0 : _d.value;
399
568
  if (!selectedLayers) {
400
569
  return;
401
570
  }
402
- model.moveItemsToGroup(Object.keys(selectedLayers), groupName);
571
+ const targetGroup = args === null || args === void 0 ? void 0 : args.label;
572
+ model.moveItemsToGroup(Object.keys(selectedLayers), targetGroup);
403
573
  },
404
574
  });
405
- commands.addCommand(CommandIDs.moveLayerToNewGroup, {
406
- label: trans.__('Move Selected Layers to New Group'),
407
- execute: async () => {
408
- var _a, _b, _c;
409
- const model = (_a = tracker.currentWidget) === null || _a === void 0 ? void 0 : _a.model;
410
- const selectedLayers = (_c = (_b = model === null || model === void 0 ? void 0 : model.localState) === null || _b === void 0 ? void 0 : _b.selected) === null || _c === void 0 ? void 0 : _c.value;
575
+ commands.addCommand(CommandIDs.moveSelectedToNewGroup, {
576
+ label: trans.__('Move Selection to New Group'),
577
+ caption: 'Move selected layers to a new group in the current JupyterGIS document.',
578
+ describedBy: {
579
+ args: {
580
+ type: 'object',
581
+ properties: {
582
+ filePath: { type: 'string' },
583
+ groupName: { type: 'string' },
584
+ layerIds: {
585
+ type: 'array',
586
+ items: { type: 'string' },
587
+ },
588
+ },
589
+ },
590
+ },
591
+ execute: async (args) => {
592
+ var _a, _b, _c, _d;
593
+ const { filePath, groupName, layerIds } = args !== null && args !== void 0 ? args : {};
594
+ const model = filePath
595
+ ? (_a = tracker.find(w => w.model.filePath === filePath)) === null || _a === void 0 ? void 0 : _a.model
596
+ : (_b = tracker.currentWidget) === null || _b === void 0 ? void 0 : _b.model;
597
+ if (!model || !model.sharedModel.editable) {
598
+ return;
599
+ }
600
+ if (filePath && groupName && layerIds) {
601
+ const layerMap = {};
602
+ layerIds.forEach(id => {
603
+ layerMap[id] = { type: 'layer', selectedNodeId: id };
604
+ });
605
+ const newGroup = {
606
+ name: groupName,
607
+ layers: layerIds,
608
+ };
609
+ model.addNewLayerGroup(layerMap, newGroup);
610
+ return;
611
+ }
612
+ const selectedLayers = (_d = (_c = model.localState) === null || _c === void 0 ? void 0 : _c.selected) === null || _d === void 0 ? void 0 : _d.value;
411
613
  if (!selectedLayers) {
412
614
  return;
413
615
  }
@@ -456,28 +658,16 @@ export function addCommands(app, tracker, translator, formSchemaRegistry, layerB
456
658
  model.addNewLayerGroup(selectedLayers, newLayerGroup);
457
659
  },
458
660
  });
459
- /**
460
- * Source actions
461
- */
462
- commands.addCommand(CommandIDs.renameSource, {
463
- label: trans.__('Rename Source'),
464
- execute: async () => {
465
- var _a;
466
- const model = (_a = tracker.currentWidget) === null || _a === void 0 ? void 0 : _a.model;
467
- await Private.renameSelectedItem(model);
468
- },
469
- });
470
- commands.addCommand(CommandIDs.removeSource, {
471
- label: trans.__('Remove Source'),
472
- execute: () => {
473
- var _a;
474
- const model = (_a = tracker.currentWidget) === null || _a === void 0 ? void 0 : _a.model;
475
- Private.removeSelectedSources(model);
476
- },
477
- });
478
661
  // Console commands
479
662
  commands.addCommand(CommandIDs.toggleConsole, {
480
663
  label: trans.__('Toggle console'),
664
+ caption: 'Toggle the console in the current JupyterGIS document.',
665
+ describedBy: {
666
+ args: {
667
+ type: 'object',
668
+ properties: {},
669
+ },
670
+ },
481
671
  isVisible: () => tracker.currentWidget instanceof JupyterGISDocumentWidget,
482
672
  isEnabled: () => {
483
673
  return tracker.currentWidget
@@ -500,6 +690,13 @@ export function addCommands(app, tracker, translator, formSchemaRegistry, layerB
500
690
  });
501
691
  commands.addCommand(CommandIDs.executeConsole, {
502
692
  label: trans.__('Execute console'),
693
+ caption: 'Execute the console in the current JupyterGIS document.',
694
+ describedBy: {
695
+ args: {
696
+ type: 'object',
697
+ properties: {},
698
+ },
699
+ },
503
700
  isVisible: () => tracker.currentWidget instanceof JupyterGISDocumentWidget,
504
701
  isEnabled: () => {
505
702
  return tracker.currentWidget
@@ -510,6 +707,13 @@ export function addCommands(app, tracker, translator, formSchemaRegistry, layerB
510
707
  });
511
708
  commands.addCommand(CommandIDs.removeConsole, {
512
709
  label: trans.__('Remove console'),
710
+ caption: 'Remove the console from the current JupyterGIS document.',
711
+ describedBy: {
712
+ args: {
713
+ type: 'object',
714
+ properties: {},
715
+ },
716
+ },
513
717
  isVisible: () => tracker.currentWidget instanceof JupyterGISDocumentWidget,
514
718
  isEnabled: () => {
515
719
  return tracker.currentWidget
@@ -520,6 +724,13 @@ export function addCommands(app, tracker, translator, formSchemaRegistry, layerB
520
724
  });
521
725
  commands.addCommand(CommandIDs.invokeCompleter, {
522
726
  label: trans.__('Display the completion helper.'),
727
+ caption: 'Display the completion helper in the current JupyterGIS document.',
728
+ describedBy: {
729
+ args: {
730
+ type: 'object',
731
+ properties: {},
732
+ },
733
+ },
523
734
  isVisible: () => tracker.currentWidget instanceof JupyterGISDocumentWidget,
524
735
  execute: () => {
525
736
  var _a;
@@ -537,6 +748,13 @@ export function addCommands(app, tracker, translator, formSchemaRegistry, layerB
537
748
  });
538
749
  commands.addCommand(CommandIDs.selectCompleter, {
539
750
  label: trans.__('Select the completion suggestion.'),
751
+ caption: 'Select the completion suggestion in the current JupyterGIS document.',
752
+ describedBy: {
753
+ args: {
754
+ type: 'object',
755
+ properties: {},
756
+ },
757
+ },
540
758
  isVisible: () => tracker.currentWidget instanceof JupyterGISDocumentWidget,
541
759
  execute: () => {
542
760
  var _a;
@@ -554,37 +772,98 @@ export function addCommands(app, tracker, translator, formSchemaRegistry, layerB
554
772
  });
555
773
  commands.addCommand(CommandIDs.zoomToLayer, {
556
774
  label: trans.__('Zoom to Layer'),
557
- execute: () => {
558
- var _a;
559
- const currentWidget = tracker.currentWidget;
560
- if (!currentWidget || !completionProviderManager) {
775
+ caption: 'Zoom to the selected layer in the current JupyterGIS document.',
776
+ describedBy: {
777
+ args: {
778
+ type: 'object',
779
+ properties: {
780
+ filePath: { type: 'string' },
781
+ layerId: { type: 'string' },
782
+ },
783
+ },
784
+ },
785
+ execute: (args) => {
786
+ var _a, _b;
787
+ const { filePath, layerId } = args !== null && args !== void 0 ? args : {};
788
+ // Determine model from provided file path or fallback to current widget
789
+ const current = filePath
790
+ ? tracker.find(w => w.model.filePath === filePath)
791
+ : tracker.currentWidget;
792
+ if (!current || !current.model.sharedModel.editable) {
561
793
  return;
562
794
  }
563
- const model = tracker.currentWidget.model;
564
- const selectedItems = (_a = model.localState) === null || _a === void 0 ? void 0 : _a.selected.value;
795
+ const model = current.model;
796
+ if (filePath && layerId) {
797
+ model.centerOnPosition(layerId);
798
+ return;
799
+ }
800
+ const selectedItems = (_b = (_a = model.localState) === null || _a === void 0 ? void 0 : _a.selected) === null || _b === void 0 ? void 0 : _b.value;
565
801
  if (!selectedItems) {
566
802
  return;
567
803
  }
568
- const layerId = Object.keys(selectedItems)[0];
569
- model.centerOnPosition(layerId);
804
+ const selLayerId = Object.keys(selectedItems)[0];
805
+ model.centerOnPosition(selLayerId);
570
806
  },
571
807
  });
572
808
  commands.addCommand(CommandIDs.downloadGeoJSON, {
573
809
  label: trans.__('Download as GeoJSON'),
810
+ caption: 'Download the selected layer as a GeoJSON file in the current JupyterGIS document.',
811
+ describedBy: {
812
+ args: {
813
+ type: 'object',
814
+ properties: {
815
+ filePath: { type: 'string' },
816
+ layerId: { type: 'string' },
817
+ exportFileName: { type: 'string' },
818
+ },
819
+ },
820
+ },
574
821
  isEnabled: () => {
575
- const selectedLayer = getSingleSelectedLayer(tracker);
576
- return selectedLayer
577
- ? ['VectorLayer', 'ShapefileLayer'].includes(selectedLayer.type)
578
- : false;
822
+ const layer = getSingleSelectedLayer(tracker);
823
+ return !!layer && ['VectorLayer', 'ShapefileLayer'].includes(layer.type);
579
824
  },
580
- execute: async () => {
581
- var _a, _b;
825
+ execute: async (args) => {
826
+ var _a;
827
+ const exportLayer = async (model, layer, exportFileName) => {
828
+ var _a, _b;
829
+ if (!['VectorLayer', 'ShapefileLayer'].includes(layer.type)) {
830
+ console.warn('Layer type not supported for GeoJSON export');
831
+ return;
832
+ }
833
+ const sources = (_a = model.sharedModel.sources) !== null && _a !== void 0 ? _a : {};
834
+ const sourceId = (_b = layer.parameters) === null || _b === void 0 ? void 0 : _b.source;
835
+ const source = sources[sourceId];
836
+ if (!source) {
837
+ console.warn('Source not found for selected layer');
838
+ return;
839
+ }
840
+ const geojsonString = await getGeoJSONDataFromLayerSource(source, model);
841
+ if (!geojsonString) {
842
+ console.warn('Failed to generate GeoJSON data');
843
+ return;
844
+ }
845
+ downloadFile(geojsonString, `${exportFileName}.geojson`, 'application/geo+json');
846
+ };
847
+ const { filePath, layerId, exportFileName } = args !== null && args !== void 0 ? args : {};
848
+ if (filePath && layerId && exportFileName) {
849
+ const widget = tracker.find(w => w.model.filePath === filePath);
850
+ if (!widget || !widget.model.sharedModel.editable) {
851
+ console.warn('Invalid or non-editable document');
852
+ return;
853
+ }
854
+ const model = widget.model;
855
+ const layer = model.getLayer(layerId);
856
+ if (!layer) {
857
+ console.warn('Layer not found');
858
+ return;
859
+ }
860
+ return exportLayer(model, layer, exportFileName);
861
+ }
582
862
  const selectedLayer = getSingleSelectedLayer(tracker);
583
- if (!selectedLayer) {
863
+ const model = (_a = tracker.currentWidget) === null || _a === void 0 ? void 0 : _a.model;
864
+ if (!selectedLayer || !model) {
584
865
  return;
585
866
  }
586
- const model = (_a = tracker.currentWidget) === null || _a === void 0 ? void 0 : _a.model;
587
- const sources = (_b = model.sharedModel.sources) !== null && _b !== void 0 ? _b : {};
588
867
  const exportSchema = Object.assign({}, formSchemaRegistry
589
868
  .getSchemas()
590
869
  .get('ExportGeoJSONSchema'));
@@ -606,21 +885,31 @@ export function addCommands(app, tracker, translator, formSchemaRegistry, layerB
606
885
  if (!formValues || !selectedLayer.parameters) {
607
886
  return;
608
887
  }
609
- const exportFileName = formValues.exportFileName;
610
- const sourceId = selectedLayer.parameters.source;
611
- const source = sources[sourceId];
612
- const geojsonString = await getGeoJSONDataFromLayerSource(source, model);
613
- if (!geojsonString) {
614
- return;
615
- }
616
- downloadFile(geojsonString, `${exportFileName}.geojson`, 'application/geo+json');
888
+ return exportLayer(model, selectedLayer, formValues.exportFileName);
617
889
  },
618
890
  });
619
891
  commands.addCommand(CommandIDs.getGeolocation, {
620
892
  label: trans.__('Center on Geolocation'),
621
- execute: async () => {
622
- var _a;
623
- const viewModel = (_a = tracker.currentWidget) === null || _a === void 0 ? void 0 : _a.model;
893
+ caption: "Center the map on the user's current geolocation.",
894
+ describedBy: {
895
+ args: {
896
+ type: 'object',
897
+ properties: {
898
+ filePath: { type: 'string' },
899
+ },
900
+ },
901
+ },
902
+ execute: async (args) => {
903
+ const { filePath } = args !== null && args !== void 0 ? args : {};
904
+ // Resolve widget once
905
+ const current = filePath
906
+ ? tracker.find(w => w.model.filePath === filePath)
907
+ : tracker.currentWidget;
908
+ if (!current) {
909
+ console.warn('No document found');
910
+ return;
911
+ }
912
+ const viewModel = current.model;
624
913
  const options = {
625
914
  enableHighAccuracy: true,
626
915
  timeout: 5000,
@@ -631,16 +920,14 @@ export function addCommands(app, tracker, translator, formSchemaRegistry, layerB
631
920
  pos.coords.longitude,
632
921
  pos.coords.latitude,
633
922
  ]);
634
- const Jgislocation = {
923
+ const jgisLocation = {
635
924
  x: location[0],
636
925
  y: location[1],
637
926
  };
638
- if (viewModel) {
639
- viewModel.geolocationChanged.emit(Jgislocation);
640
- }
927
+ viewModel.geolocationChanged.emit(jgisLocation);
641
928
  };
642
929
  const error = (err) => {
643
- console.warn(`ERROR(${err.code}): ${err.message}`);
930
+ console.warn(`Geolocation error (${err.code}): ${err.message}`);
644
931
  };
645
932
  navigator.geolocation.getCurrentPosition(success, error, options);
646
933
  },
@@ -649,6 +936,13 @@ export function addCommands(app, tracker, translator, formSchemaRegistry, layerB
649
936
  // Panel visibility commands
650
937
  commands.addCommand(CommandIDs.toggleLeftPanel, {
651
938
  label: trans.__('Toggle Left Panel'),
939
+ caption: 'Toggle the left panel in the current JupyterGIS document.',
940
+ describedBy: {
941
+ args: {
942
+ type: 'object',
943
+ properties: {},
944
+ },
945
+ },
652
946
  isEnabled: () => Boolean(tracker.currentWidget),
653
947
  isToggled: () => {
654
948
  const current = tracker.currentWidget;
@@ -673,6 +967,13 @@ export function addCommands(app, tracker, translator, formSchemaRegistry, layerB
673
967
  });
674
968
  commands.addCommand(CommandIDs.toggleRightPanel, {
675
969
  label: trans.__('Toggle Right Panel'),
970
+ caption: 'Toggle the right panel in the current JupyterGIS document.',
971
+ describedBy: {
972
+ args: {
973
+ type: 'object',
974
+ properties: {},
975
+ },
976
+ },
676
977
  isEnabled: () => Boolean(tracker.currentWidget),
677
978
  isToggled: () => {
678
979
  const current = tracker.currentWidget;
@@ -698,6 +999,13 @@ export function addCommands(app, tracker, translator, formSchemaRegistry, layerB
698
999
  // Left panel tabs
699
1000
  commands.addCommand(CommandIDs.showLayersTab, {
700
1001
  label: trans.__('Show Layers Tab'),
1002
+ caption: 'Show the layers tab in the current JupyterGIS document.',
1003
+ describedBy: {
1004
+ args: {
1005
+ type: 'object',
1006
+ properties: {},
1007
+ },
1008
+ },
701
1009
  isEnabled: () => Boolean(tracker.currentWidget),
702
1010
  isToggled: () => tracker.currentWidget
703
1011
  ? !tracker.currentWidget.model.jgisSettings.layersDisabled
@@ -716,6 +1024,13 @@ export function addCommands(app, tracker, translator, formSchemaRegistry, layerB
716
1024
  });
717
1025
  commands.addCommand(CommandIDs.showStacBrowserTab, {
718
1026
  label: trans.__('Show STAC Browser Tab'),
1027
+ caption: 'Show the STAC browser tab in the current JupyterGIS document.',
1028
+ describedBy: {
1029
+ args: {
1030
+ type: 'object',
1031
+ properties: {},
1032
+ },
1033
+ },
719
1034
  isEnabled: () => Boolean(tracker.currentWidget),
720
1035
  isToggled: () => tracker.currentWidget
721
1036
  ? !tracker.currentWidget.model.jgisSettings.stacBrowserDisabled
@@ -734,6 +1049,13 @@ export function addCommands(app, tracker, translator, formSchemaRegistry, layerB
734
1049
  });
735
1050
  commands.addCommand(CommandIDs.showFiltersTab, {
736
1051
  label: trans.__('Show Filters Tab'),
1052
+ caption: 'Show the filters tab in the current JupyterGIS document.',
1053
+ describedBy: {
1054
+ args: {
1055
+ type: 'object',
1056
+ properties: {},
1057
+ },
1058
+ },
737
1059
  isEnabled: () => Boolean(tracker.currentWidget),
738
1060
  isToggled: () => tracker.currentWidget
739
1061
  ? !tracker.currentWidget.model.jgisSettings.filtersDisabled
@@ -753,6 +1075,13 @@ export function addCommands(app, tracker, translator, formSchemaRegistry, layerB
753
1075
  // Right panel tabs
754
1076
  commands.addCommand(CommandIDs.showObjectPropertiesTab, {
755
1077
  label: trans.__('Show Object Properties Tab'),
1078
+ caption: 'Show the object properties tab in the current JupyterGIS document.',
1079
+ describedBy: {
1080
+ args: {
1081
+ type: 'object',
1082
+ properties: {},
1083
+ },
1084
+ },
756
1085
  isEnabled: () => Boolean(tracker.currentWidget),
757
1086
  isToggled: () => tracker.currentWidget
758
1087
  ? !tracker.currentWidget.model.jgisSettings.objectPropertiesDisabled
@@ -771,6 +1100,13 @@ export function addCommands(app, tracker, translator, formSchemaRegistry, layerB
771
1100
  });
772
1101
  commands.addCommand(CommandIDs.showAnnotationsTab, {
773
1102
  label: trans.__('Show Annotations Tab'),
1103
+ caption: 'Show the annotations tab in the current JupyterGIS document.',
1104
+ describedBy: {
1105
+ args: {
1106
+ type: 'object',
1107
+ properties: {},
1108
+ },
1109
+ },
774
1110
  isEnabled: () => Boolean(tracker.currentWidget),
775
1111
  isToggled: () => tracker.currentWidget
776
1112
  ? !tracker.currentWidget.model.jgisSettings.annotationsDisabled
@@ -789,6 +1125,13 @@ export function addCommands(app, tracker, translator, formSchemaRegistry, layerB
789
1125
  });
790
1126
  commands.addCommand(CommandIDs.showIdentifyPanelTab, {
791
1127
  label: trans.__('Show Identify Panel Tab'),
1128
+ caption: 'Show the identify panel tab in the current JupyterGIS document.',
1129
+ describedBy: {
1130
+ args: {
1131
+ type: 'object',
1132
+ properties: {},
1133
+ },
1134
+ },
792
1135
  isEnabled: () => Boolean(tracker.currentWidget),
793
1136
  isToggled: () => tracker.currentWidget
794
1137
  ? !tracker.currentWidget.model.jgisSettings.identifyDisabled
@@ -863,6 +1206,63 @@ export function addCommands(app, tracker, translator, formSchemaRegistry, layerB
863
1206
  current.model.setOptions(Object.assign(Object.assign({}, currentOptions), { storyMapPresentationMode: !currentOptions.storyMapPresentationMode }));
864
1207
  commands.notifyCommandChanged(CommandIDs.toggleStoryPresentationMode);
865
1208
  } }, icons.get(CommandIDs.toggleStoryPresentationMode)));
1209
+ /* Needs to be enabled in Specta mode, so add without Specta-aware wrapper */
1210
+ originalAddCommand(CommandIDs.storyPrev, {
1211
+ label: trans.__('Previous Story Segment'),
1212
+ isEnabled: () => {
1213
+ var _a, _b;
1214
+ const model = (_a = tracker.currentWidget) === null || _a === void 0 ? void 0 : _a.model;
1215
+ const storySegments = (_b = model === null || model === void 0 ? void 0 : model.getSelectedStory().story) === null || _b === void 0 ? void 0 : _b.storySegments;
1216
+ if (!model ||
1217
+ model.jgisSettings.storyMapsDisabled ||
1218
+ !storySegments ||
1219
+ storySegments.length < 1) {
1220
+ return false;
1221
+ }
1222
+ if (!model.isSpectaMode()) {
1223
+ return false;
1224
+ }
1225
+ return model.getCurrentSegmentIndex() > 0;
1226
+ },
1227
+ execute: () => {
1228
+ var _a, _b;
1229
+ const model = (_a = tracker.currentWidget) === null || _a === void 0 ? void 0 : _a.model;
1230
+ if (!model) {
1231
+ return;
1232
+ }
1233
+ const current = (_b = model.getCurrentSegmentIndex()) !== null && _b !== void 0 ? _b : 0;
1234
+ model.setCurrentSegmentIndex(current - 1);
1235
+ },
1236
+ });
1237
+ originalAddCommand(CommandIDs.storyNext, {
1238
+ label: trans.__('Next Story Segment'),
1239
+ isEnabled: () => {
1240
+ var _a, _b, _c;
1241
+ const model = (_a = tracker.currentWidget) === null || _a === void 0 ? void 0 : _a.model;
1242
+ const storySegments = (_b = model === null || model === void 0 ? void 0 : model.getSelectedStory().story) === null || _b === void 0 ? void 0 : _b.storySegments;
1243
+ if (!model ||
1244
+ model.jgisSettings.storyMapsDisabled ||
1245
+ !storySegments ||
1246
+ storySegments.length < 1) {
1247
+ return false;
1248
+ }
1249
+ const isSpecta = model.isSpectaMode();
1250
+ if (!isSpecta) {
1251
+ return false;
1252
+ }
1253
+ const current = (_c = model.getCurrentSegmentIndex()) !== null && _c !== void 0 ? _c : 0;
1254
+ return current < storySegments.length - 1;
1255
+ },
1256
+ execute: () => {
1257
+ var _a, _b;
1258
+ const model = (_a = tracker.currentWidget) === null || _a === void 0 ? void 0 : _a.model;
1259
+ if (!model) {
1260
+ return;
1261
+ }
1262
+ const current = (_b = model.getCurrentSegmentIndex()) !== null && _b !== void 0 ? _b : 0;
1263
+ model.setCurrentSegmentIndex(current + 1);
1264
+ },
1265
+ });
866
1266
  loadKeybindings(commands, keybindings);
867
1267
  }
868
1268
  var Private;
@@ -956,21 +1356,6 @@ var Private;
956
1356
  model.setEditingItem(item.type, itemId);
957
1357
  }
958
1358
  Private.renameSelectedItem = renameSelectedItem;
959
- function removeSelectedSources(model) {
960
- var _a, _b;
961
- const selected = (_b = (_a = model === null || model === void 0 ? void 0 : model.localState) === null || _a === void 0 ? void 0 : _a.selected) === null || _b === void 0 ? void 0 : _b.value;
962
- if (!selected || !model) {
963
- return;
964
- }
965
- for (const id of Object.keys(selected)) {
966
- if (model.getLayersBySource(id).length > 0) {
967
- showErrorMessage('Remove source error', 'The source is used by a layer.');
968
- continue;
969
- }
970
- model.sharedModel.removeSource(id);
971
- }
972
- }
973
- Private.removeSelectedSources = removeSelectedSources;
974
1359
  function executeConsole(tracker) {
975
1360
  const current = tracker.currentWidget;
976
1361
  if (!current || !(current instanceof JupyterGISDocumentWidget)) {
@@ -995,4 +1380,25 @@ var Private;
995
1380
  await current.content.toggleConsole(current.model.filePath);
996
1381
  }
997
1382
  Private.toggleConsole = toggleConsole;
1383
+ function generateCopyName(baseName, model) {
1384
+ const layers = model.getLayers();
1385
+ const existingNames = Object.values(layers).map(l => l.name);
1386
+ const copyRegex = /(.*?)( Copy(_\d+)?)?$/;
1387
+ const match = baseName.match(copyRegex);
1388
+ const cleanBase = match ? match[1].trim() : baseName;
1389
+ const firstCopyName = `${cleanBase} Copy`;
1390
+ if (!existingNames.includes(firstCopyName)) {
1391
+ return firstCopyName;
1392
+ }
1393
+ const pattern = new RegExp(`^${cleanBase} Copy_(\\d+)$`);
1394
+ const numbers = existingNames
1395
+ .map(name => {
1396
+ const m = name.match(pattern);
1397
+ return m ? parseInt(m[1], 10) : null;
1398
+ })
1399
+ .filter((n) => n !== null);
1400
+ const nextNumber = numbers.length > 0 ? Math.max(...numbers) + 1 : 1;
1401
+ return `${cleanBase} Copy_${nextNumber}`;
1402
+ }
1403
+ Private.generateCopyName = generateCopyName;
998
1404
  })(Private || (Private = {}));