@jupytergis/base 0.13.3 → 0.14.1

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 (122) hide show
  1. package/lib/commands/BaseCommandIDs.d.ts +14 -14
  2. package/lib/commands/BaseCommandIDs.js +14 -15
  3. package/lib/commands/index.js +516 -135
  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/VectorRendering.js +6 -5
  19. package/lib/dialogs/symbology/vector_layer/components/ValueSelect.js +3 -1
  20. package/lib/dialogs/symbology/vector_layer/types/Categorized.js +7 -11
  21. package/lib/dialogs/symbology/vector_layer/types/Graduated.js +7 -10
  22. package/lib/dialogs/symbology/vector_layer/types/Heatmap.js +7 -8
  23. package/lib/formbuilder/creationform.d.ts +8 -8
  24. package/lib/formbuilder/creationform.js +130 -85
  25. package/lib/formbuilder/editform.d.ts +1 -7
  26. package/lib/formbuilder/editform.js +64 -52
  27. package/lib/formbuilder/formselectors.d.ts +5 -4
  28. package/lib/formbuilder/index.d.ts +1 -1
  29. package/lib/formbuilder/index.js +1 -1
  30. package/lib/formbuilder/objectform/SchemaForm.d.ts +36 -0
  31. package/lib/formbuilder/objectform/SchemaForm.js +77 -0
  32. package/lib/formbuilder/objectform/StoryEditorForm.d.ts +3 -9
  33. package/lib/formbuilder/objectform/StoryEditorForm.js +20 -14
  34. package/lib/formbuilder/objectform/components/LayerSelect.js +3 -8
  35. package/lib/formbuilder/objectform/components/SegmentFormSymbology.js +23 -10
  36. package/lib/formbuilder/objectform/components/SourcePropertiesField.d.ts +7 -0
  37. package/lib/formbuilder/objectform/components/SourcePropertiesField.js +29 -0
  38. package/lib/formbuilder/objectform/components/StorySegmentReset.js +1 -1
  39. package/lib/formbuilder/objectform/fileselectorwidget.js +1 -1
  40. package/lib/formbuilder/objectform/layer/heatmapLayerForm.d.ts +3 -12
  41. package/lib/formbuilder/objectform/layer/heatmapLayerForm.js +87 -55
  42. package/lib/formbuilder/objectform/layer/hillshadeLayerForm.d.ts +3 -8
  43. package/lib/formbuilder/objectform/layer/hillshadeLayerForm.js +36 -10
  44. package/lib/formbuilder/objectform/layer/layerform.d.ts +7 -9
  45. package/lib/formbuilder/objectform/layer/layerform.js +33 -20
  46. package/lib/formbuilder/objectform/layer/storySegmentLayerForm.d.ts +3 -5
  47. package/lib/formbuilder/objectform/layer/storySegmentLayerForm.js +73 -29
  48. package/lib/formbuilder/objectform/layer/vectorlayerform.d.ts +3 -14
  49. package/lib/formbuilder/objectform/layer/vectorlayerform.js +36 -29
  50. package/lib/formbuilder/objectform/layer/webGlLayerForm.d.ts +3 -10
  51. package/lib/formbuilder/objectform/layer/webGlLayerForm.js +37 -13
  52. package/lib/formbuilder/objectform/process/dissolveProcessForm.d.ts +8 -18
  53. package/lib/formbuilder/objectform/process/dissolveProcessForm.js +68 -56
  54. package/lib/formbuilder/objectform/processingForm.d.ts +12 -0
  55. package/lib/formbuilder/objectform/processingForm.js +33 -0
  56. package/lib/formbuilder/objectform/schemaUtils.d.ts +16 -0
  57. package/lib/formbuilder/objectform/schemaUtils.js +59 -0
  58. package/lib/formbuilder/objectform/source/geojsonsource.d.ts +3 -19
  59. package/lib/formbuilder/objectform/source/geojsonsource.js +94 -53
  60. package/lib/formbuilder/objectform/source/geotiffsource.d.ts +3 -20
  61. package/lib/formbuilder/objectform/source/geotiffsource.js +73 -74
  62. package/lib/formbuilder/objectform/source/pathbasedsource.d.ts +3 -19
  63. package/lib/formbuilder/objectform/source/pathbasedsource.js +76 -75
  64. package/lib/formbuilder/objectform/source/sourceform.d.ts +7 -8
  65. package/lib/formbuilder/objectform/source/sourceform.js +28 -11
  66. package/lib/formbuilder/objectform/source/tilesourceform.d.ts +3 -7
  67. package/lib/formbuilder/objectform/source/tilesourceform.js +63 -53
  68. package/lib/formbuilder/objectform/useSchemaFormState.d.ts +48 -0
  69. package/lib/formbuilder/objectform/useSchemaFormState.js +35 -0
  70. package/lib/index.d.ts +0 -1
  71. package/lib/index.js +0 -1
  72. package/lib/keybindings.json +10 -10
  73. package/lib/mainview/mainView.d.ts +11 -7
  74. package/lib/mainview/mainView.js +62 -35
  75. package/lib/mainview/mainviewmodel.js +2 -2
  76. package/lib/menus.js +8 -8
  77. package/lib/panelview/components/layers.js +5 -2
  78. package/lib/panelview/filter-panel/Filter.d.ts +3 -0
  79. package/lib/panelview/filter-panel/Filter.js +9 -9
  80. package/lib/panelview/leftpanel.js +0 -7
  81. package/lib/panelview/objectproperties.js +2 -2
  82. package/lib/panelview/rightpanel.js +17 -2
  83. package/lib/panelview/story-maps/SpectaPanel.d.ts +15 -0
  84. package/lib/panelview/story-maps/SpectaPanel.js +35 -0
  85. package/lib/panelview/story-maps/StoryViewerPanel.d.ts +24 -10
  86. package/lib/panelview/story-maps/StoryViewerPanel.js +22 -268
  87. package/lib/panelview/story-maps/{PreviewModeSwitch.js → components/PreviewModeSwitch.js} +1 -1
  88. package/lib/panelview/story-maps/components/SpectaDesktopView.d.ts +22 -0
  89. package/lib/panelview/story-maps/components/SpectaDesktopView.js +49 -0
  90. package/lib/panelview/story-maps/components/SpectaMobileView.d.ts +17 -0
  91. package/lib/panelview/story-maps/{MobileSpectaPanel.js → components/SpectaMobileView.js} +8 -22
  92. package/lib/panelview/story-maps/{StoryNavBar.d.ts → components/StoryNavBar.d.ts} +1 -1
  93. package/lib/panelview/story-maps/{StoryNavBar.js → components/StoryNavBar.js} +2 -4
  94. package/lib/panelview/story-maps/hooks/useStoryMap.d.ts +40 -0
  95. package/lib/panelview/story-maps/hooks/useStoryMap.js +255 -0
  96. package/lib/processing/index.d.ts +2 -2
  97. package/lib/processing/index.js +62 -35
  98. package/lib/processing/processingCommands.d.ts +1 -1
  99. package/lib/processing/processingCommands.js +26 -6
  100. package/lib/shared/components/Collapsible.d.ts +6 -0
  101. package/lib/shared/components/Collapsible.js +26 -0
  102. package/lib/stacBrowser/components/filter-extension/QueryableComboBox.js +1 -3
  103. package/lib/statusbar/SpectaPresentationProgressBar.d.ts +7 -0
  104. package/lib/statusbar/SpectaPresentationProgressBar.js +40 -0
  105. package/lib/toolbar/widget.js +4 -2
  106. package/lib/tools.d.ts +6 -0
  107. package/lib/tools.js +9 -0
  108. package/lib/types.d.ts +30 -3
  109. package/package.json +2 -4
  110. package/style/base.css +23 -1
  111. package/style/dialog.css +5 -0
  112. package/style/leftPanel.css +14 -37
  113. package/style/shared/button.css +2 -15
  114. package/style/shared/input.css +2 -2
  115. package/style/shared/switch.css +8 -7
  116. package/style/spectaProgressBar.css +144 -0
  117. package/style/storyPanel.css +38 -0
  118. package/style/symbologyDialog.css +2 -2
  119. package/lib/formbuilder/objectform/baseform.d.ts +0 -91
  120. package/lib/formbuilder/objectform/baseform.js +0 -231
  121. package/lib/panelview/story-maps/MobileSpectaPanel.d.ts +0 -7
  122. /package/lib/panelview/story-maps/{PreviewModeSwitch.d.ts → components/PreviewModeSwitch.d.ts} +0 -0
@@ -0,0 +1,255 @@
1
+ import { UUID } from '@lumino/coreutils';
2
+ import { useCallback, useEffect, useMemo, useState, } from 'react';
3
+ /** Inline style for specta presentation (bg and text color from story). */
4
+ export function getSpectaPresentationStyle(story) {
5
+ const bgColor = story === null || story === void 0 ? void 0 : story.presentationBgColor;
6
+ const textColor = story === null || story === void 0 ? void 0 : story.presentationTextColor;
7
+ const style = {};
8
+ if (bgColor) {
9
+ style['--jgis-specta-bg-color'] = bgColor;
10
+ style.backgroundColor = bgColor;
11
+ }
12
+ if (textColor) {
13
+ style['--jgis-specta-text-color'] = textColor;
14
+ style.color = textColor;
15
+ }
16
+ return style;
17
+ }
18
+ export function useStoryMap({ model, overrideLayerEntriesRef, removeLayer, addLayer, panelRef, isSpecta, }) {
19
+ var _a;
20
+ const [currentIndex, setCurrentIndex] = useState(() => { var _a; return (_a = model.getCurrentSegmentIndex()) !== null && _a !== void 0 ? _a : 0; });
21
+ const [storyData, setStoryData] = useState(() => { var _a; return (_a = model.getSelectedStory().story) !== null && _a !== void 0 ? _a : null; });
22
+ const storySegments = useMemo(() => {
23
+ if (!(storyData === null || storyData === void 0 ? void 0 : storyData.storySegments)) {
24
+ return [];
25
+ }
26
+ return storyData.storySegments
27
+ .map(segmentId => model.getLayer(segmentId))
28
+ .filter((layer) => layer !== undefined);
29
+ }, [storyData, model]);
30
+ const segmentCount = storySegments.length;
31
+ const storySegmentIds = storyData === null || storyData === void 0 ? void 0 : storyData.storySegments;
32
+ const currentStorySegment = useMemo(() => storySegments[currentIndex], [storySegments, currentIndex]);
33
+ const activeSlide = useMemo(() => currentStorySegment === null || currentStorySegment === void 0 ? void 0 : currentStorySegment.parameters, [currentStorySegment]);
34
+ const layerName = useMemo(() => { var _a; return (_a = currentStorySegment === null || currentStorySegment === void 0 ? void 0 : currentStorySegment.name) !== null && _a !== void 0 ? _a : ''; }, [currentStorySegment]);
35
+ const currentStorySegmentId = useMemo(() => storySegmentIds === null || storySegmentIds === void 0 ? void 0 : storySegmentIds[currentIndex], [storySegmentIds, currentIndex]);
36
+ const showGradient = (_a = storyData === null || storyData === void 0 ? void 0 : storyData.showGradient) !== null && _a !== void 0 ? _a : true;
37
+ const hasPrev = currentIndex > 0;
38
+ const hasNext = currentIndex < segmentCount - 1;
39
+ const clearOverrideLayers = useCallback(() => {
40
+ const entries = overrideLayerEntriesRef.current;
41
+ if (!entries) {
42
+ return;
43
+ }
44
+ entries.forEach(({ layerId, action }) => {
45
+ if (action === 'remove') {
46
+ removeLayer === null || removeLayer === void 0 ? void 0 : removeLayer(layerId);
47
+ }
48
+ else {
49
+ const layerOrSource = model.getLayerOrSource(layerId);
50
+ if (layerOrSource) {
51
+ model.triggerLayerUpdate(layerId, layerOrSource);
52
+ }
53
+ }
54
+ });
55
+ entries.length = 0;
56
+ }, [model, overrideLayerEntriesRef, removeLayer]);
57
+ const zoomToCurrentLayer = useCallback(() => {
58
+ if (currentStorySegmentId) {
59
+ model.centerOnPosition(currentStorySegmentId);
60
+ }
61
+ }, [model, currentStorySegmentId]);
62
+ const setIndex = useCallback((index) => {
63
+ model.setCurrentSegmentIndex(index);
64
+ }, [model]);
65
+ const handlePrev = useCallback(() => {
66
+ if (hasPrev) {
67
+ model.setCurrentSegmentIndex(currentIndex - 1);
68
+ }
69
+ }, [model, currentIndex, hasPrev]);
70
+ const handleNext = useCallback(() => {
71
+ if (hasNext) {
72
+ model.setCurrentSegmentIndex(currentIndex + 1);
73
+ }
74
+ }, [model, currentIndex, hasNext]);
75
+ const setSelectedLayerByIndex = useCallback((index) => {
76
+ var _a;
77
+ const storySegmentId = (_a = storyData === null || storyData === void 0 ? void 0 : storyData.storySegments) === null || _a === void 0 ? void 0 : _a[index];
78
+ if (storySegmentId) {
79
+ model.selected = {
80
+ [storySegmentId]: {
81
+ type: 'layer',
82
+ },
83
+ };
84
+ }
85
+ }, [storyData, model]);
86
+ const overrideSymbology = useCallback((index) => {
87
+ var _a;
88
+ if (index < 0 || !storySegments[index]) {
89
+ return;
90
+ }
91
+ const segment = storySegments[index];
92
+ const layerOverrides = (_a = segment.parameters) === null || _a === void 0 ? void 0 : _a.layerOverride;
93
+ if (!Array.isArray(layerOverrides)) {
94
+ return;
95
+ }
96
+ layerOverrides.forEach(override => {
97
+ var _a, _b, _c, _d;
98
+ const { color, opacity, sourceProperties, symbologyState, targetLayer: targetLayerId, visible, } = override;
99
+ if (!targetLayerId) {
100
+ return;
101
+ }
102
+ (_a = overrideLayerEntriesRef.current) === null || _a === void 0 ? void 0 : _a.push({
103
+ layerId: targetLayerId,
104
+ action: 'restore',
105
+ });
106
+ const targetLayer = model.getLayer(targetLayerId);
107
+ if (targetLayer === null || targetLayer === void 0 ? void 0 : targetLayer.parameters) {
108
+ if (symbologyState !== undefined) {
109
+ targetLayer.parameters.symbologyState = symbologyState;
110
+ }
111
+ if (color !== undefined) {
112
+ targetLayer.parameters.color = color;
113
+ }
114
+ if (opacity !== undefined) {
115
+ targetLayer.parameters.opacity = opacity;
116
+ }
117
+ if (visible !== undefined) {
118
+ targetLayer.visible = visible;
119
+ }
120
+ if (sourceProperties !== undefined &&
121
+ Object.keys(sourceProperties).length > 0) {
122
+ const sourceId = (_b = targetLayer.parameters) === null || _b === void 0 ? void 0 : _b.source;
123
+ if (sourceId) {
124
+ const source = model.getSource(sourceId);
125
+ if (!source) {
126
+ return;
127
+ }
128
+ if (source === null || source === void 0 ? void 0 : source.parameters) {
129
+ source.parameters = Object.assign(Object.assign({}, source.parameters), sourceProperties);
130
+ }
131
+ (_c = overrideLayerEntriesRef.current) === null || _c === void 0 ? void 0 : _c.push({
132
+ layerId: sourceId,
133
+ action: 'restore',
134
+ });
135
+ model.triggerLayerUpdate(sourceId, source);
136
+ }
137
+ }
138
+ if ((symbologyState === null || symbologyState === void 0 ? void 0 : symbologyState.renderType) === 'Heatmap') {
139
+ targetLayer.type = 'HeatmapLayer';
140
+ if (addLayer) {
141
+ const newId = UUID.uuid4();
142
+ addLayer(newId, targetLayer, 100);
143
+ (_d = overrideLayerEntriesRef.current) === null || _d === void 0 ? void 0 : _d.push({
144
+ layerId: newId,
145
+ action: 'remove',
146
+ });
147
+ }
148
+ }
149
+ else {
150
+ model.triggerLayerUpdate(targetLayerId, targetLayer);
151
+ }
152
+ }
153
+ });
154
+ }, [addLayer, model, storySegments, overrideLayerEntriesRef]);
155
+ useEffect(() => {
156
+ const onIndexChanged = (_, index) => {
157
+ setCurrentIndex(Math.max(0, index !== null && index !== void 0 ? index : 0));
158
+ };
159
+ model.currentSegmentIndexChanged.connect(onIndexChanged);
160
+ return () => {
161
+ model.currentSegmentIndexChanged.disconnect(onIndexChanged);
162
+ };
163
+ }, [model]);
164
+ useEffect(() => {
165
+ const updateStory = () => {
166
+ var _a, _b;
167
+ clearOverrideLayers();
168
+ setStoryData((_a = model.getSelectedStory().story) !== null && _a !== void 0 ? _a : null);
169
+ setCurrentIndex((_b = model.getCurrentSegmentIndex()) !== null && _b !== void 0 ? _b : 0);
170
+ };
171
+ updateStory();
172
+ model.sharedModel.storyMapsChanged.connect(updateStory);
173
+ return () => {
174
+ model.sharedModel.storyMapsChanged.disconnect(updateStory);
175
+ };
176
+ }, [model, clearOverrideLayers]);
177
+ useEffect(() => {
178
+ return () => {
179
+ var _a;
180
+ clearOverrideLayers();
181
+ (_a = storyData === null || storyData === void 0 ? void 0 : storyData.storySegments) === null || _a === void 0 ? void 0 : _a.forEach(segmentId => {
182
+ var _a;
183
+ const segment = model.getLayer(segmentId);
184
+ const overrides = (_a = segment === null || segment === void 0 ? void 0 : segment.parameters) === null || _a === void 0 ? void 0 : _a.layerOverride;
185
+ if (Array.isArray(overrides)) {
186
+ overrides.forEach((override) => {
187
+ const targetLayerId = override.targetLayer;
188
+ if (targetLayerId) {
189
+ const targetLayer = model.getLayer(targetLayerId);
190
+ targetLayer &&
191
+ model.triggerLayerUpdate(targetLayerId, targetLayer);
192
+ }
193
+ });
194
+ }
195
+ });
196
+ };
197
+ }, []);
198
+ useEffect(() => {
199
+ if (currentStorySegmentId) {
200
+ zoomToCurrentLayer();
201
+ }
202
+ }, [currentStorySegmentId, zoomToCurrentLayer]);
203
+ // Set selected layer and apply symbology when segment changes; remove previous segment's override layers first.
204
+ useEffect(() => {
205
+ if (!(storyData === null || storyData === void 0 ? void 0 : storyData.storySegments) || currentIndex < 0) {
206
+ return;
207
+ }
208
+ clearOverrideLayers();
209
+ setSelectedLayerByIndex(currentIndex);
210
+ overrideSymbology(currentIndex);
211
+ }, [
212
+ storyData,
213
+ currentIndex,
214
+ setSelectedLayerByIndex,
215
+ clearOverrideLayers,
216
+ overrideSymbology,
217
+ ]);
218
+ // Set selected layer on initial render and when story data changes
219
+ useEffect(() => {
220
+ if ((storyData === null || storyData === void 0 ? void 0 : storyData.storySegments) && currentIndex >= 0) {
221
+ setSelectedLayerByIndex(currentIndex);
222
+ }
223
+ }, [storyData, currentIndex, setSelectedLayerByIndex]);
224
+ // Apply story presentation colors (specta) to panel root
225
+ useEffect(() => {
226
+ if (!isSpecta || !(panelRef === null || panelRef === void 0 ? void 0 : panelRef.current)) {
227
+ return;
228
+ }
229
+ const container = panelRef.current;
230
+ const style = getSpectaPresentationStyle(storyData);
231
+ Object.entries(style).forEach(([key, value]) => {
232
+ if (value !== null) {
233
+ container.style.setProperty(key, String(value));
234
+ }
235
+ });
236
+ }, [storyData, isSpecta, panelRef]);
237
+ return {
238
+ storyData,
239
+ storySegments,
240
+ currentIndex,
241
+ showGradient,
242
+ clearOverrideLayers,
243
+ setIndex,
244
+ handlePrev,
245
+ handleNext,
246
+ hasPrev,
247
+ hasNext,
248
+ setSelectedLayerByIndex,
249
+ currentStorySegment,
250
+ activeSlide,
251
+ layerName,
252
+ currentStorySegmentId,
253
+ zoomToCurrentLayer,
254
+ };
255
+ }
@@ -17,9 +17,9 @@ export type GdalFunctions = 'ogr2ogr' | 'gdal_rasterize' | 'gdalwarp' | 'gdal_tr
17
17
  /**
18
18
  * Generalized processing function for Buffer & Dissolve
19
19
  */
20
- export declare function processSelectedLayer(tracker: JupyterGISTracker, formSchemaRegistry: IJGISFormSchemaRegistry, processingType: ProcessingType, processingOptions: {
20
+ export declare function processLayer(tracker: JupyterGISTracker, formSchemaRegistry: IJGISFormSchemaRegistry, processingType: ProcessingType, processingOptions: {
21
21
  sqlQueryFn: (layerName: string, param: any) => string;
22
22
  gdalFunction: GdalFunctions;
23
23
  options: (sqlQuery: string) => string[];
24
- }, app: JupyterFrontEnd): Promise<void>;
24
+ }, app: JupyterFrontEnd, filePath?: string, processingInputs?: Record<string, any>): Promise<void>;
25
25
  export declare function executeSQLProcessing(model: IJupyterGISModel, geojsonString: string, gdalFunction: GdalFunctions, options: string[], layerNamePrefix: string, processingType: ProcessingType, embedOutputLayer: boolean, tracker: JupyterGISTracker, app: JupyterFrontEnd): Promise<void>;
@@ -52,48 +52,75 @@ export async function getLayerGeoJSON(layer, sources, model) {
52
52
  /**
53
53
  * Generalized processing function for Buffer & Dissolve
54
54
  */
55
- export async function processSelectedLayer(tracker, formSchemaRegistry, processingType, processingOptions, app) {
56
- var _a, _b, _c;
57
- const selected = getSingleSelectedLayer(tracker);
58
- if (!selected || !tracker.currentWidget) {
55
+ export async function processLayer(tracker, formSchemaRegistry, processingType, processingOptions, app, filePath, processingInputs) {
56
+ var _a, _b, _c, _d;
57
+ // Resolve widget
58
+ const widget = filePath
59
+ ? tracker.find(w => w.model.filePath === filePath)
60
+ : tracker.currentWidget;
61
+ if (!widget) {
62
+ return;
63
+ }
64
+ const model = widget.model;
65
+ const sources = (_a = model.sharedModel.sources) !== null && _a !== void 0 ? _a : {};
66
+ const layers = (_b = model.sharedModel.layers) !== null && _b !== void 0 ? _b : {};
67
+ // Resolve layer
68
+ let selected = null;
69
+ if (processingInputs === null || processingInputs === void 0 ? void 0 : processingInputs.inputLayer) {
70
+ selected = layers[processingInputs.inputLayer];
71
+ }
72
+ else {
73
+ selected = getSingleSelectedLayer(tracker);
74
+ }
75
+ if (!selected) {
59
76
  return;
60
77
  }
61
- const model = tracker.currentWidget.model;
62
- const sources = (_a = model === null || model === void 0 ? void 0 : model.sharedModel.sources) !== null && _a !== void 0 ? _a : {};
63
78
  const geojsonString = await getLayerGeoJSON(selected, sources, model);
64
79
  if (!geojsonString) {
65
80
  return;
66
81
  }
67
- const schema = Object.assign({}, formSchemaRegistry.getSchemas().get(processingType));
68
- const selectedLayerId = Object.keys(((_c = (_b = model === null || model === void 0 ? void 0 : model.sharedModel.awareness.getLocalState()) === null || _b === void 0 ? void 0 : _b.selected) === null || _c === void 0 ? void 0 : _c.value) || {})[0];
69
- // Open ProcessingFormDialog
70
- const formValues = await new Promise(resolve => {
71
- const dialog = new ProcessingFormDialog({
72
- title: processingType.charAt(0).toUpperCase() + processingType.slice(1),
73
- schema,
74
- model,
75
- sourceData: {
76
- inputLayer: selectedLayerId,
77
- outputLayerName: selected.name,
78
- },
79
- formContext: 'create',
80
- processingType,
81
- syncData: (props) => {
82
- resolve(props);
83
- dialog.dispose();
84
- },
85
- });
86
- dialog.launch();
87
- });
88
- if (!formValues) {
89
- return;
82
+ // Resolve params
83
+ let processParam;
84
+ let embedOutputLayer = true;
85
+ let outputLayerName = selected.name;
86
+ if (processingInputs) {
87
+ processParam = processingInputs;
88
+ outputLayerName = `${processingType} Layer`;
90
89
  }
91
- if (!processingList.includes(processingType)) {
92
- console.error(`Unsupported processing type: ${processingType}`);
93
- return;
90
+ else {
91
+ const schema = Object.assign({}, formSchemaRegistry.getSchemas().get(processingType));
92
+ const selectedLayerId = Object.keys(((_d = (_c = model.sharedModel.awareness.getLocalState()) === null || _c === void 0 ? void 0 : _c.selected) === null || _d === void 0 ? void 0 : _d.value) || {})[0];
93
+ // Open ProcessingFormDialog
94
+ const formValues = await new Promise(resolve => {
95
+ const dialog = new ProcessingFormDialog({
96
+ title: processingType.charAt(0).toUpperCase() + processingType.slice(1),
97
+ schema,
98
+ model,
99
+ sourceData: {
100
+ inputLayer: selectedLayerId,
101
+ outputLayerName: selected.name,
102
+ },
103
+ formContext: 'create',
104
+ processingType,
105
+ syncData: (props) => {
106
+ resolve(props);
107
+ dialog.dispose();
108
+ },
109
+ });
110
+ dialog.launch();
111
+ });
112
+ if (!formValues) {
113
+ return;
114
+ }
115
+ if (!processingList.includes(processingType)) {
116
+ console.error(`Unsupported processing type: ${processingType}`);
117
+ return;
118
+ }
119
+ processParam = processingFormToParam(formValues, processingType);
120
+ embedOutputLayer = formValues.embedOutputLayer;
121
+ outputLayerName = formValues.outputLayerName;
94
122
  }
95
- const processParam = processingFormToParam(formValues, processingType);
96
- const embedOutputLayer = formValues.embedOutputLayer;
123
+ // GDAL pre-processing
97
124
  const fileBlob = new Blob([geojsonString], {
98
125
  type: 'application/geo+json',
99
126
  });
@@ -106,7 +133,7 @@ export async function processSelectedLayer(tracker, formSchemaRegistry, processi
106
133
  const layerName = dataset.info.layers[0].name;
107
134
  const sqlQuery = processingOptions.sqlQueryFn(layerName, processParam);
108
135
  const fullOptions = processingOptions.options(sqlQuery);
109
- await executeSQLProcessing(model, geojsonString, processingOptions.gdalFunction, fullOptions, formValues.outputLayerName, processingType, embedOutputLayer, tracker, app);
136
+ await executeSQLProcessing(model, geojsonString, processingOptions.gdalFunction, fullOptions, outputLayerName, processingType, embedOutputLayer, tracker, app);
110
137
  }
111
138
  export async function executeSQLProcessing(model, geojsonString, gdalFunction, options, layerNamePrefix, processingType, embedOutputLayer, tracker, app) {
112
139
  var _a;
@@ -3,4 +3,4 @@ import { JupyterFrontEnd } from '@jupyterlab/application';
3
3
  import { CommandRegistry } from '@lumino/commands';
4
4
  import { JupyterGISTracker } from '../types';
5
5
  export declare function replaceInSql(sql: string, keyToVal: IDict<string>, layerName: string): string;
6
- export declare function addProcessingCommands(app: JupyterFrontEnd, commands: CommandRegistry, tracker: JupyterGISTracker, trans: any, formSchemaRegistry: IJGISFormSchemaRegistry): void;
6
+ export declare function addProcessingCommands(app: JupyterFrontEnd, commands: CommandRegistry, tracker: JupyterGISTracker, trans: any, formSchemaRegistry: IJGISFormSchemaRegistry, processingSchemas: Record<string, any>): void;
@@ -1,5 +1,5 @@
1
1
  import { ProcessingMerge, ProcessingLogicType, } from '@jupytergis/schema';
2
- import { selectedLayerIsOfType, processSelectedLayer } from './index';
2
+ import { selectedLayerIsOfType, processLayer } from './index';
3
3
  export function replaceInSql(sql, keyToVal, layerName) {
4
4
  const replaceTemplateString = (args) => args.template.replace(RegExp(`{${args.variableName}}`, 'g'), args.value);
5
5
  let out = replaceTemplateString({
@@ -16,14 +16,34 @@ export function replaceInSql(sql, keyToVal, layerName) {
16
16
  }
17
17
  return out;
18
18
  }
19
- export function addProcessingCommands(app, commands, tracker, trans, formSchemaRegistry) {
19
+ export function addProcessingCommands(app, commands, tracker, trans, formSchemaRegistry, processingSchemas) {
20
20
  for (const processingElement of ProcessingMerge) {
21
+ const schemaKey = Object.keys(processingSchemas).find(k => k.toLowerCase() === processingElement.name.toLowerCase());
22
+ if (!schemaKey) {
23
+ continue;
24
+ }
21
25
  if (processingElement.type === ProcessingLogicType.vector) {
22
- commands.addCommand(processingElement.name, {
26
+ commands.addCommand(`jupytergis:${processingElement.name}`, {
23
27
  label: trans.__(processingElement.label),
28
+ describedBy: {
29
+ args: {
30
+ type: 'object',
31
+ properties: {
32
+ filePath: {
33
+ type: 'string',
34
+ description: 'Path to the .jGIS file',
35
+ },
36
+ layerId: {
37
+ type: 'string',
38
+ description: 'Layer ID to process',
39
+ },
40
+ params: processingSchemas[schemaKey],
41
+ },
42
+ },
43
+ },
24
44
  isEnabled: () => selectedLayerIsOfType(['VectorLayer'], tracker),
25
- execute: async () => {
26
- await processSelectedLayer(tracker, formSchemaRegistry, processingElement.description, {
45
+ execute: async (args) => {
46
+ await processLayer(tracker, formSchemaRegistry, processingElement.description, {
27
47
  sqlQueryFn: (layerName, keyToVal) => replaceInSql(processingElement.operations.sql, keyToVal, layerName),
28
48
  gdalFunction: processingElement.operations.gdalFunction,
29
49
  options: (sqlQuery) => [
@@ -35,7 +55,7 @@ export function addProcessingCommands(app, commands, tracker, trans, formSchemaR
35
55
  sqlQuery,
36
56
  'output.geojson',
37
57
  ],
38
- }, app);
58
+ }, app, args === null || args === void 0 ? void 0 : args.filePath, args === null || args === void 0 ? void 0 : args.processingInputs);
39
59
  },
40
60
  });
41
61
  }
@@ -0,0 +1,6 @@
1
+ import { Collapsible as CollapsiblePrimitive } from 'radix-ui';
2
+ import * as React from 'react';
3
+ declare function Collapsible({ ...props }: React.ComponentProps<typeof CollapsiblePrimitive.Root>): React.JSX.Element;
4
+ declare function CollapsibleTrigger({ ...props }: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleTrigger>): React.JSX.Element;
5
+ declare function CollapsibleContent({ ...props }: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleContent>): React.JSX.Element;
6
+ export { Collapsible, CollapsibleTrigger, CollapsibleContent };
@@ -0,0 +1,26 @@
1
+ var __rest = (this && this.__rest) || function (s, e) {
2
+ var t = {};
3
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
4
+ t[p] = s[p];
5
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
6
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
7
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
8
+ t[p[i]] = s[p[i]];
9
+ }
10
+ return t;
11
+ };
12
+ import { Collapsible as CollapsiblePrimitive } from 'radix-ui';
13
+ import * as React from 'react';
14
+ function Collapsible(_a) {
15
+ var props = __rest(_a, []);
16
+ return React.createElement(CollapsiblePrimitive.Root, Object.assign({ "data-slot": "collapsible" }, props));
17
+ }
18
+ function CollapsibleTrigger(_a) {
19
+ var props = __rest(_a, []);
20
+ return (React.createElement(CollapsiblePrimitive.CollapsibleTrigger, Object.assign({ "data-slot": "collapsible-trigger" }, props)));
21
+ }
22
+ function CollapsibleContent(_a) {
23
+ var props = __rest(_a, []);
24
+ return (React.createElement(CollapsiblePrimitive.CollapsibleContent, Object.assign({ "data-slot": "collapsible-content" }, props)));
25
+ }
26
+ export { Collapsible, CollapsibleTrigger, CollapsibleContent };
@@ -105,9 +105,7 @@ export function QueryableComboBox({ queryables, selectedQueryables, updateSelect
105
105
  };
106
106
  return (React.createElement(SingleDatePicker, { date: parseDate(currentValue), onDateChange: handleDateChange, dateFormat: "P", showIcon: true, placeholder: "Select date", className: "jgis-queryable-combo-input jgis-queryable-combo-input-date-picker" }));
107
107
  }
108
- return (React.createElement(Input, { type: "text", className: "jgis-queryable-combo-input",
109
- // style={{borderRadius: 0}}
110
- value: currentValue || '', onChange: e => onChange(e.target.value) }));
108
+ return (React.createElement(Input, { type: "text", value: currentValue || '', onChange: e => onChange(e.target.value) }));
111
109
  case 'number':
112
110
  case 'integer':
113
111
  if (val.enum) {
@@ -0,0 +1,7 @@
1
+ import { IJupyterGISModel } from '@jupytergis/schema';
2
+ import React from 'react';
3
+ interface ISpectaPresentationProgressBarProps {
4
+ model: IJupyterGISModel;
5
+ }
6
+ declare function SpectaPresentationProgressBar({ model, }: ISpectaPresentationProgressBarProps): React.JSX.Element;
7
+ export default SpectaPresentationProgressBar;
@@ -0,0 +1,40 @@
1
+ import React, { useEffect, useRef, useState } from 'react';
2
+ function SpectaPresentationProgressBar({ model, }) {
3
+ var _a, _b, _c, _d, _e, _f;
4
+ const segmentCount = (_c = (_b = (_a = model.getSelectedStory().story) === null || _a === void 0 ? void 0 : _a.storySegments) === null || _b === void 0 ? void 0 : _b.length) !== null && _c !== void 0 ? _c : 0;
5
+ const [currentIndex, setCurrentIndex] = useState(() => { var _a; return Math.max(0, (_a = model.getCurrentSegmentIndex()) !== null && _a !== void 0 ? _a : 0); });
6
+ useEffect(() => {
7
+ const onIndexChanged = (_, index) => {
8
+ setCurrentIndex(Math.max(0, index !== null && index !== void 0 ? index : 0));
9
+ };
10
+ model.currentSegmentIndexChanged.connect(onIndexChanged);
11
+ return () => {
12
+ model.currentSegmentIndexChanged.disconnect(onIndexChanged);
13
+ };
14
+ }, [model]);
15
+ const safeCount = Math.max(0, segmentCount);
16
+ const clampedIndex = safeCount > 0 ? Math.min(currentIndex, safeCount - 1) : 0;
17
+ const prevIndexRef = useRef(clampedIndex);
18
+ const [direction, setDirection] = useState(null);
19
+ useEffect(() => {
20
+ const prev = prevIndexRef.current;
21
+ if (clampedIndex !== prev) {
22
+ setDirection(clampedIndex > prev ? 'next' : 'prev');
23
+ prevIndexRef.current = clampedIndex;
24
+ }
25
+ }, [clampedIndex]);
26
+ const { story } = model.getSelectedStory();
27
+ const segmentIds = (_d = story === null || story === void 0 ? void 0 : story.storySegments) !== null && _d !== void 0 ? _d : [];
28
+ const currentSegmentId = segmentIds[clampedIndex];
29
+ const currentSegment = currentSegmentId
30
+ ? model.getLayer(currentSegmentId)
31
+ : undefined;
32
+ const segmentParams = currentSegment === null || currentSegment === void 0 ? void 0 : currentSegment.parameters;
33
+ const transitionTime = (_f = (_e = segmentParams === null || segmentParams === void 0 ? void 0 : segmentParams.transition) === null || _e === void 0 ? void 0 : _e.time) !== null && _f !== void 0 ? _f : 0.3;
34
+ return (React.createElement("div", { className: "jgis-specta-progress", "data-direction": direction !== null && direction !== void 0 ? direction : undefined, style: {
35
+ '--jgis-specta-transition-duration': `${transitionTime}s`,
36
+ } },
37
+ React.createElement("div", { className: "jgis-specta-progress-bar" }, Array.from({ length: safeCount }, (_, i) => safeCount - 1 - i).map(segmentIndex => (React.createElement("div", { key: segmentIndex, className: "jgis-specta-bar-segment", "data-filled": segmentIndex <= clampedIndex ? '' : undefined, style: { '--segment-index': segmentIndex } },
38
+ React.createElement("button", { type: "button", className: "jgis-specta-progress-input", onClick: () => model.setCurrentSegmentIndex(segmentIndex), "aria-label": `Segment ${segmentIndex + 1} of ${safeCount}`, "aria-pressed": segmentIndex === clampedIndex })))))));
39
+ }
40
+ export default SpectaPresentationProgressBar;
@@ -59,7 +59,7 @@ export class ToolbarWidget extends ReactiveToolbar {
59
59
  }
60
60
  };
61
61
  this._model = options.model;
62
- this.addClass('jGIS-toolbar-widget');
62
+ this.node.classList.add('jGIS-toolbar-widget', 'data-jgis-keybinding');
63
63
  // Listen for settings changes
64
64
  this._model.settingsChanged.connect(this._onSettingsChanged, this);
65
65
  // Listen for options change because it's the dependable signal
@@ -144,7 +144,9 @@ export class ToolbarWidget extends ReactiveToolbar {
144
144
  });
145
145
  this.addItem('Toggle console', toggleConsoleButton);
146
146
  toggleConsoleButton.node.dataset.testid = 'toggle-console-button';
147
- this.addItem('spacer', ReactiveToolbar.createSpacerItem());
147
+ const spacer = ReactiveToolbar.createSpacerItem();
148
+ spacer.node.tabIndex = -1;
149
+ this.addItem('spacer', spacer);
148
150
  // Users
149
151
  const iconRenderer = createUserIconRenderer(this._model);
150
152
  this.addItem('users', ReactWidget.create(React.createElement(UsersItem, { model: this._model, iconRenderer: iconRenderer })));
package/lib/tools.d.ts CHANGED
@@ -135,4 +135,10 @@ export declare function getGeoJSONDataFromLayerSource(source: IJGISSource, model
135
135
  * code when using it.
136
136
  */
137
137
  export declare const objectEntries: <T extends Record<PropertyKey, unknown>>(obj: T) => Array<{ [K in keyof T]: [K, T[K]]; }[keyof T]>;
138
+ /**
139
+ * Extract the layerOverride array index from an RJSF idSchema (e.g. from $id like "root_layerOverride_0_sourceProperties").
140
+ */
141
+ export declare function extractLayerOverrideIndex(idSchema: {
142
+ $id?: string;
143
+ }): number | undefined;
138
144
  export {};
package/lib/tools.js CHANGED
@@ -815,3 +815,12 @@ export async function getGeoJSONDataFromLayerSource(source, model) {
815
815
  * code when using it.
816
816
  */
817
817
  export const objectEntries = Object.entries;
818
+ /**
819
+ * Extract the layerOverride array index from an RJSF idSchema (e.g. from $id like "root_layerOverride_0_sourceProperties").
820
+ */
821
+ export function extractLayerOverrideIndex(idSchema) {
822
+ var _a;
823
+ const id = (_a = idSchema === null || idSchema === void 0 ? void 0 : idSchema.$id) !== null && _a !== void 0 ? _a : '';
824
+ const match = id.match(/layerOverride_(\d+)/);
825
+ return match ? parseInt(match[1], 10) : undefined;
826
+ }
package/lib/types.d.ts CHANGED
@@ -1,10 +1,12 @@
1
- import { IDict, IJupyterGISWidget } from '@jupytergis/schema';
2
- import { WidgetTracker } from '@jupyterlab/apputils';
1
+ import { IDict, IJGISFormSchemaRegistry, IJupyterGISModel, IJupyterGISWidget } from '@jupytergis/schema';
2
+ import { Dialog, WidgetTracker } from '@jupyterlab/apputils';
3
+ import { Signal } from '@lumino/signaling';
4
+ import { RJSFSchema } from '@rjsf/utils';
3
5
  import { Map } from 'ol';
4
6
  export { IDict };
5
7
  export type ValueOf<T> = T[keyof T];
6
8
  export type JupyterGISTracker = WidgetTracker<IJupyterGISWidget>;
7
- export type SymbologyTab = 'color' | 'radius';
9
+ export type SymbologyTab = 'color' | 'radius' | 'filters';
8
10
  type RgbColorValue = [number, number, number] | [number, number, number, number];
9
11
  type HexColorValue = string;
10
12
  type InternalRgbArray = number[];
@@ -28,3 +30,28 @@ declare global {
28
30
  declare const classificationModes: readonly ["quantile", "equal interval", "jenks", "pretty", "logarithmic", "continuous"];
29
31
  export type ClassificationMode = (typeof classificationModes)[number];
30
32
  export declare const SYMBOLOGY_VALID_LAYER_TYPES: string[];
33
+ /** Form context passed to SchemaForm and custom fields. */
34
+ export interface IJupyterGISFormContext<TFormData = IDict | undefined> {
35
+ model: IJupyterGISModel;
36
+ formData: TFormData;
37
+ formSchemaRegistry?: IJGISFormSchemaRegistry;
38
+ }
39
+ /** Optional form state (schema, extraErrors). */
40
+ export interface IBaseFormStates {
41
+ schema?: RJSFSchema;
42
+ extraErrors?: any;
43
+ }
44
+ /** Base props for object forms (layer, source, processing, story editor). */
45
+ export interface IBaseFormProps {
46
+ formContext: 'update' | 'create';
47
+ sourceData: IDict | undefined;
48
+ filePath?: string;
49
+ model: IJupyterGISModel;
50
+ syncData: (properties: IDict) => void;
51
+ schema?: IDict;
52
+ ok?: Signal<Dialog<any>, number>;
53
+ cancel?: () => void;
54
+ formChangedSignal?: Signal<any, IDict<any>>;
55
+ formErrorSignal?: Signal<Dialog<any>, boolean>;
56
+ formSchemaRegistry?: IJGISFormSchemaRegistry;
57
+ }