@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.
- package/lib/commands/BaseCommandIDs.d.ts +14 -13
- package/lib/commands/BaseCommandIDs.js +14 -14
- package/lib/commands/index.js +523 -117
- package/lib/commands/operationCommands.d.ts +22 -0
- package/lib/commands/operationCommands.js +305 -0
- package/lib/constants.js +9 -9
- package/lib/dialogs/ProcessingFormDialog.d.ts +1 -1
- package/lib/dialogs/ProcessingFormDialog.js +2 -2
- package/lib/dialogs/layerBrowserDialog.d.ts +2 -0
- package/lib/dialogs/layerBrowserDialog.js +12 -5
- package/lib/dialogs/layerCreationFormDialog.d.ts +7 -0
- package/lib/dialogs/layerCreationFormDialog.js +10 -2
- package/lib/dialogs/symbology/components/color_ramp/ColorRampControls.d.ts +2 -1
- package/lib/dialogs/symbology/components/color_ramp/ColorRampControls.js +21 -19
- package/lib/dialogs/symbology/components/color_ramp/ColorRampSelector.d.ts +3 -1
- package/lib/dialogs/symbology/components/color_ramp/ColorRampSelector.js +13 -5
- package/lib/dialogs/symbology/tiff_layer/types/SingleBandPseudoColor.js +6 -4
- package/lib/dialogs/symbology/vector_layer/types/Categorized.js +7 -11
- package/lib/dialogs/symbology/vector_layer/types/Graduated.js +7 -10
- package/lib/dialogs/symbology/vector_layer/types/Heatmap.js +7 -8
- package/lib/formbuilder/creationform.d.ts +8 -8
- package/lib/formbuilder/creationform.js +130 -85
- package/lib/formbuilder/editform.d.ts +1 -7
- package/lib/formbuilder/editform.js +64 -52
- package/lib/formbuilder/formselectors.d.ts +5 -4
- package/lib/formbuilder/index.d.ts +1 -1
- package/lib/formbuilder/index.js +1 -1
- package/lib/formbuilder/objectform/SchemaForm.d.ts +36 -0
- package/lib/formbuilder/objectform/SchemaForm.js +77 -0
- package/lib/formbuilder/objectform/StoryEditorForm.d.ts +3 -9
- package/lib/formbuilder/objectform/StoryEditorForm.js +20 -14
- package/lib/formbuilder/objectform/components/LayerSelect.js +3 -8
- package/lib/formbuilder/objectform/components/SegmentFormSymbology.js +23 -10
- package/lib/formbuilder/objectform/components/SourcePropertiesField.d.ts +7 -0
- package/lib/formbuilder/objectform/components/SourcePropertiesField.js +29 -0
- package/lib/formbuilder/objectform/components/StorySegmentReset.js +1 -1
- package/lib/formbuilder/objectform/fileselectorwidget.js +1 -1
- package/lib/formbuilder/objectform/layer/heatmapLayerForm.d.ts +3 -12
- package/lib/formbuilder/objectform/layer/heatmapLayerForm.js +87 -55
- package/lib/formbuilder/objectform/layer/hillshadeLayerForm.d.ts +3 -8
- package/lib/formbuilder/objectform/layer/hillshadeLayerForm.js +36 -10
- package/lib/formbuilder/objectform/layer/layerform.d.ts +7 -9
- package/lib/formbuilder/objectform/layer/layerform.js +33 -20
- package/lib/formbuilder/objectform/layer/storySegmentLayerForm.d.ts +3 -5
- package/lib/formbuilder/objectform/layer/storySegmentLayerForm.js +73 -29
- package/lib/formbuilder/objectform/layer/vectorlayerform.d.ts +3 -14
- package/lib/formbuilder/objectform/layer/vectorlayerform.js +36 -29
- package/lib/formbuilder/objectform/layer/webGlLayerForm.d.ts +3 -10
- package/lib/formbuilder/objectform/layer/webGlLayerForm.js +37 -13
- package/lib/formbuilder/objectform/process/dissolveProcessForm.d.ts +8 -18
- package/lib/formbuilder/objectform/process/dissolveProcessForm.js +68 -56
- package/lib/formbuilder/objectform/processingForm.d.ts +12 -0
- package/lib/formbuilder/objectform/processingForm.js +33 -0
- package/lib/formbuilder/objectform/schemaUtils.d.ts +16 -0
- package/lib/formbuilder/objectform/schemaUtils.js +59 -0
- package/lib/formbuilder/objectform/source/geojsonsource.d.ts +3 -19
- package/lib/formbuilder/objectform/source/geojsonsource.js +94 -53
- package/lib/formbuilder/objectform/source/geotiffsource.d.ts +3 -20
- package/lib/formbuilder/objectform/source/geotiffsource.js +73 -74
- package/lib/formbuilder/objectform/source/pathbasedsource.d.ts +3 -19
- package/lib/formbuilder/objectform/source/pathbasedsource.js +76 -75
- package/lib/formbuilder/objectform/source/sourceform.d.ts +7 -8
- package/lib/formbuilder/objectform/source/sourceform.js +28 -11
- package/lib/formbuilder/objectform/source/tilesourceform.d.ts +3 -7
- package/lib/formbuilder/objectform/source/tilesourceform.js +63 -53
- package/lib/formbuilder/objectform/useSchemaFormState.d.ts +48 -0
- package/lib/formbuilder/objectform/useSchemaFormState.js +35 -0
- package/lib/index.d.ts +0 -1
- package/lib/index.js +0 -1
- package/lib/keybindings.json +10 -10
- package/lib/mainview/mainView.d.ts +11 -7
- package/lib/mainview/mainView.js +62 -35
- package/lib/mainview/mainviewmodel.js +2 -2
- package/lib/menus.js +8 -8
- package/lib/panelview/components/layers.js +5 -2
- package/lib/panelview/objectproperties.js +2 -2
- package/lib/panelview/rightpanel.js +17 -2
- package/lib/panelview/story-maps/SpectaPanel.d.ts +15 -0
- package/lib/panelview/story-maps/SpectaPanel.js +35 -0
- package/lib/panelview/story-maps/StoryViewerPanel.d.ts +24 -9
- package/lib/panelview/story-maps/StoryViewerPanel.js +22 -268
- package/lib/panelview/story-maps/{PreviewModeSwitch.js → components/PreviewModeSwitch.js} +1 -1
- package/lib/panelview/story-maps/components/SpectaDesktopView.d.ts +21 -0
- package/lib/panelview/story-maps/components/SpectaDesktopView.js +49 -0
- package/lib/panelview/story-maps/components/SpectaMobileView.d.ts +17 -0
- package/lib/panelview/story-maps/{MobileSpectaPanel.js → components/SpectaMobileView.js} +8 -22
- package/lib/panelview/story-maps/{StoryNavBar.d.ts → components/StoryNavBar.d.ts} +1 -1
- package/lib/panelview/story-maps/{StoryNavBar.js → components/StoryNavBar.js} +2 -4
- package/lib/panelview/story-maps/hooks/useStoryMap.d.ts +39 -0
- package/lib/panelview/story-maps/hooks/useStoryMap.js +252 -0
- package/lib/processing/index.d.ts +2 -2
- package/lib/processing/index.js +62 -35
- package/lib/processing/processingCommands.d.ts +1 -1
- package/lib/processing/processingCommands.js +26 -6
- package/lib/shared/components/Collapsible.d.ts +6 -0
- package/lib/shared/components/Collapsible.js +26 -0
- package/lib/statusbar/SpectaPresentationProgressBar.d.ts +7 -0
- package/lib/statusbar/SpectaPresentationProgressBar.js +40 -0
- package/lib/toolbar/widget.js +4 -2
- package/lib/tools.d.ts +6 -0
- package/lib/tools.js +9 -0
- package/lib/types.d.ts +29 -2
- package/package.json +2 -4
- package/style/base.css +23 -1
- package/style/dialog.css +5 -0
- package/style/leftPanel.css +14 -37
- package/style/shared/button.css +0 -10
- package/style/shared/switch.css +8 -7
- package/style/spectaProgressBar.css +144 -0
- package/style/storyPanel.css +33 -0
- package/style/symbologyDialog.css +2 -2
- package/lib/formbuilder/objectform/baseform.d.ts +0 -91
- package/lib/formbuilder/objectform/baseform.js +0 -231
- package/lib/panelview/story-maps/MobileSpectaPanel.d.ts +0 -7
- /package/lib/panelview/story-maps/{PreviewModeSwitch.d.ts → components/PreviewModeSwitch.d.ts} +0 -0
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState, } from 'react';
|
|
3
|
-
import { cn } from "../../shared/components/utils";
|
|
4
|
-
import StoryNavBar from './StoryNavBar';
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
5
2
|
import StoryContentSection from './components/StoryContentSection';
|
|
6
3
|
import StoryImageSection from './components/StoryImageSection';
|
|
4
|
+
import StoryNavBar from './components/StoryNavBar';
|
|
7
5
|
import StorySubtitleSection from './components/StorySubtitleSection';
|
|
8
6
|
import StoryTitleSection from './components/StoryTitleSection';
|
|
9
7
|
/**
|
|
@@ -11,122 +9,20 @@ import StoryTitleSection from './components/StoryTitleSection';
|
|
|
11
9
|
*/
|
|
12
10
|
function getStoryNavPlacement(isSpecta, hasImage, storyType, isMobile) {
|
|
13
11
|
if (isSpecta) {
|
|
14
|
-
return isMobile ?
|
|
12
|
+
return isMobile ? null : 'subtitle-specta';
|
|
15
13
|
}
|
|
16
14
|
if (storyType !== 'guided') {
|
|
17
15
|
return null;
|
|
18
16
|
}
|
|
19
17
|
return hasImage ? 'over-image' : 'below-title';
|
|
20
18
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Story viewer (presentational). Receives story state and callbacks from parent.
|
|
21
|
+
* Desktop scroll/sentinel/imperative handle live in SpectaDesktopView.
|
|
22
|
+
*/
|
|
23
|
+
function StoryViewerPanel({ model, isSpecta, isMobile = false, className, segmentContainerRef, storyData, currentIndex, activeSlide, layerName, handlePrev, handleNext, hasPrev, hasNext, setIndex, }) {
|
|
24
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
|
|
25
25
|
const [imageLoaded, setImageLoaded] = useState(false);
|
|
26
|
-
const panelRef = useRef(null);
|
|
27
|
-
const segmentContainerRef = useRef(null);
|
|
28
|
-
const topSentinelRef = useRef(null);
|
|
29
|
-
const bottomSentinelRef = useRef(null);
|
|
30
|
-
const atTopRef = useRef(false);
|
|
31
|
-
const atBottomRef = useRef(false);
|
|
32
|
-
const setIndex = useCallback((index) => {
|
|
33
|
-
model.setCurrentSegmentIndex(index);
|
|
34
|
-
setCurrentIndexDisplayed(index);
|
|
35
|
-
}, [model]);
|
|
36
|
-
/** Layers affected by layer override
|
|
37
|
-
* We want to remove added layers (ie Heatmap)
|
|
38
|
-
* and Restore the original symbology for modified layers
|
|
39
|
-
*/
|
|
40
|
-
const overrideLayerEntriesRef = useRef([]);
|
|
41
|
-
const clearOverrideLayers = useCallback(() => {
|
|
42
|
-
overrideLayerEntriesRef.current.forEach(({ layerId, action }) => {
|
|
43
|
-
if (action === 'remove') {
|
|
44
|
-
removeLayer === null || removeLayer === void 0 ? void 0 : removeLayer(layerId);
|
|
45
|
-
}
|
|
46
|
-
else {
|
|
47
|
-
const layer = model.getLayer(layerId);
|
|
48
|
-
if (layer) {
|
|
49
|
-
model.triggerLayerUpdate(layerId, layer);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
});
|
|
53
|
-
overrideLayerEntriesRef.current = [];
|
|
54
|
-
}, [model]);
|
|
55
|
-
// Derive story segments from story data
|
|
56
|
-
const storySegments = useMemo(() => {
|
|
57
|
-
if (!(storyData === null || storyData === void 0 ? void 0 : storyData.storySegments)) {
|
|
58
|
-
return [];
|
|
59
|
-
}
|
|
60
|
-
return storyData.storySegments
|
|
61
|
-
.map(storySegmentId => model.getLayer(storySegmentId))
|
|
62
|
-
.filter((layer) => layer !== undefined);
|
|
63
|
-
}, [storyData, model]);
|
|
64
|
-
// Derive current story segment from story segments and currentIndexDisplayed
|
|
65
|
-
const currentStorySegment = useMemo(() => {
|
|
66
|
-
return storySegments[currentIndexDisplayed];
|
|
67
|
-
}, [storySegments, currentIndexDisplayed]);
|
|
68
|
-
// Derive active slide and layer name from current story segment
|
|
69
|
-
const activeSlide = useMemo(() => {
|
|
70
|
-
return currentStorySegment === null || currentStorySegment === void 0 ? void 0 : currentStorySegment.parameters;
|
|
71
|
-
}, [currentStorySegment]);
|
|
72
|
-
const layerName = useMemo(() => { var _a; return (_a = currentStorySegment === null || currentStorySegment === void 0 ? void 0 : currentStorySegment.name) !== null && _a !== void 0 ? _a : ''; }, [currentStorySegment]);
|
|
73
|
-
// Derive story segment ID for zooming
|
|
74
|
-
const currentStorySegmentId = useMemo(() => {
|
|
75
|
-
var _a;
|
|
76
|
-
return (_a = storyData === null || storyData === void 0 ? void 0 : storyData.storySegments) === null || _a === void 0 ? void 0 : _a[currentIndexDisplayed];
|
|
77
|
-
}, [storyData, currentIndexDisplayed]);
|
|
78
|
-
const hasPrev = currentIndexDisplayed > 0;
|
|
79
|
-
const hasNext = currentIndexDisplayed < storySegments.length - 1;
|
|
80
|
-
const zoomToCurrentLayer = () => {
|
|
81
|
-
if (currentStorySegmentId) {
|
|
82
|
-
model.centerOnPosition(currentStorySegmentId);
|
|
83
|
-
}
|
|
84
|
-
};
|
|
85
|
-
const setSelectedLayerByIndex = useCallback((index) => {
|
|
86
|
-
var _a;
|
|
87
|
-
const storySegmentId = (_a = storyData === null || storyData === void 0 ? void 0 : storyData.storySegments) === null || _a === void 0 ? void 0 : _a[index];
|
|
88
|
-
if (storySegmentId) {
|
|
89
|
-
model.selected = {
|
|
90
|
-
[storySegmentId]: {
|
|
91
|
-
type: 'layer',
|
|
92
|
-
},
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
}, [storyData, model]);
|
|
96
|
-
// On unmount: remove override layers and restore layer symbology
|
|
97
|
-
useEffect(() => {
|
|
98
|
-
return () => {
|
|
99
|
-
var _a;
|
|
100
|
-
clearOverrideLayers();
|
|
101
|
-
(_a = storyData === null || storyData === void 0 ? void 0 : storyData.storySegments) === null || _a === void 0 ? void 0 : _a.forEach(segmentId => {
|
|
102
|
-
var _a;
|
|
103
|
-
const segment = model.getLayer(segmentId);
|
|
104
|
-
const overrides = (_a = segment === null || segment === void 0 ? void 0 : segment.parameters) === null || _a === void 0 ? void 0 : _a.layerOverride;
|
|
105
|
-
if (Array.isArray(overrides)) {
|
|
106
|
-
overrides.forEach((override) => {
|
|
107
|
-
const targetLayerId = override.targetLayer;
|
|
108
|
-
const targetLayer = model.getLayer(targetLayerId);
|
|
109
|
-
targetLayer &&
|
|
110
|
-
model.triggerLayerUpdate(targetLayerId, targetLayer);
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
});
|
|
114
|
-
};
|
|
115
|
-
}, [storyData, model, clearOverrideLayers]);
|
|
116
|
-
useEffect(() => {
|
|
117
|
-
const updateStory = () => {
|
|
118
|
-
var _a;
|
|
119
|
-
clearOverrideLayers();
|
|
120
|
-
const { story } = model.getSelectedStory();
|
|
121
|
-
setStoryData(story !== null && story !== void 0 ? story : null);
|
|
122
|
-
setIndex((_a = model.getCurrentSegmentIndex()) !== null && _a !== void 0 ? _a : 0);
|
|
123
|
-
};
|
|
124
|
-
updateStory();
|
|
125
|
-
model.sharedModel.storyMapsChanged.connect(updateStory);
|
|
126
|
-
return () => {
|
|
127
|
-
model.sharedModel.storyMapsChanged.disconnect(updateStory);
|
|
128
|
-
};
|
|
129
|
-
}, [model, setIndex, clearOverrideLayers]);
|
|
130
26
|
// Prefetch image when slide changes
|
|
131
27
|
useEffect(() => {
|
|
132
28
|
var _a;
|
|
@@ -151,33 +47,8 @@ const StoryViewerPanel = forwardRef(({ model, isSpecta, isMobile = false, classN
|
|
|
151
47
|
img.onload = null;
|
|
152
48
|
img.onerror = null;
|
|
153
49
|
};
|
|
154
|
-
}, [(
|
|
155
|
-
//
|
|
156
|
-
useEffect(() => {
|
|
157
|
-
if (currentStorySegmentId) {
|
|
158
|
-
zoomToCurrentLayer();
|
|
159
|
-
}
|
|
160
|
-
}, [currentStorySegmentId, model]);
|
|
161
|
-
// Set selected layer and apply symbology when segment changes; remove previous segment's override layers first.
|
|
162
|
-
useEffect(() => {
|
|
163
|
-
if (!(storyData === null || storyData === void 0 ? void 0 : storyData.storySegments) || currentIndexDisplayed < 0) {
|
|
164
|
-
return;
|
|
165
|
-
}
|
|
166
|
-
clearOverrideLayers();
|
|
167
|
-
setSelectedLayerByIndex(currentIndexDisplayed);
|
|
168
|
-
overrideSymbology(currentIndexDisplayed);
|
|
169
|
-
}, [
|
|
170
|
-
storyData,
|
|
171
|
-
currentIndexDisplayed,
|
|
172
|
-
setSelectedLayerByIndex,
|
|
173
|
-
clearOverrideLayers,
|
|
174
|
-
]);
|
|
175
|
-
// Set selected layer on initial render and when story data changes
|
|
176
|
-
useEffect(() => {
|
|
177
|
-
if ((storyData === null || storyData === void 0 ? void 0 : storyData.storySegments) && currentIndexDisplayed >= 0) {
|
|
178
|
-
setSelectedLayerByIndex(currentIndexDisplayed);
|
|
179
|
-
}
|
|
180
|
-
}, [storyData, currentIndexDisplayed, setSelectedLayerByIndex]);
|
|
50
|
+
}, [(_a = activeSlide === null || activeSlide === void 0 ? void 0 : activeSlide.content) === null || _a === void 0 ? void 0 : _a.image]);
|
|
51
|
+
// ! TODO come back for this
|
|
181
52
|
// Listen for layer selection changes in unguided mode
|
|
182
53
|
useEffect(() => {
|
|
183
54
|
// ! TODO this logic (getting a single selected layer) is also in the processing index.ts, move to tools
|
|
@@ -214,71 +85,7 @@ const StoryViewerPanel = forwardRef(({ model, isSpecta, isMobile = false, classN
|
|
|
214
85
|
model.sharedModel.awareness.off('change', handleSelectedStorySegmentChange);
|
|
215
86
|
};
|
|
216
87
|
}, [model, storyData, setIndex]);
|
|
217
|
-
|
|
218
|
-
const overrideSymbology = (index) => {
|
|
219
|
-
var _a;
|
|
220
|
-
if (index < 0 || !storySegments[index]) {
|
|
221
|
-
return;
|
|
222
|
-
}
|
|
223
|
-
const segment = storySegments[index];
|
|
224
|
-
const layerOverrides = (_a = segment.parameters) === null || _a === void 0 ? void 0 : _a.layerOverride;
|
|
225
|
-
if (!Array.isArray(layerOverrides)) {
|
|
226
|
-
return;
|
|
227
|
-
}
|
|
228
|
-
// Apply all layer overrides for this segment
|
|
229
|
-
layerOverrides.forEach(override => {
|
|
230
|
-
const { color, opacity, symbologyState, targetLayer: targetLayerId, visible, } = override;
|
|
231
|
-
if (!targetLayerId) {
|
|
232
|
-
return;
|
|
233
|
-
}
|
|
234
|
-
overrideLayerEntriesRef.current.push({
|
|
235
|
-
layerId: targetLayerId,
|
|
236
|
-
action: 'restore',
|
|
237
|
-
});
|
|
238
|
-
const targetLayer = model.getLayer(targetLayerId);
|
|
239
|
-
if (targetLayer === null || targetLayer === void 0 ? void 0 : targetLayer.parameters) {
|
|
240
|
-
if (symbologyState !== undefined) {
|
|
241
|
-
targetLayer.parameters.symbologyState = symbologyState;
|
|
242
|
-
}
|
|
243
|
-
if (color !== undefined) {
|
|
244
|
-
targetLayer.parameters.color = color;
|
|
245
|
-
}
|
|
246
|
-
if (opacity !== undefined) {
|
|
247
|
-
targetLayer.parameters.opacity = opacity;
|
|
248
|
-
}
|
|
249
|
-
if (visible !== undefined) {
|
|
250
|
-
targetLayer.visible = visible;
|
|
251
|
-
}
|
|
252
|
-
// Heatmaps are actually a different layer, not just symbology
|
|
253
|
-
// so they need special handling
|
|
254
|
-
if ((symbologyState === null || symbologyState === void 0 ? void 0 : symbologyState.renderType) === 'Heatmap') {
|
|
255
|
-
targetLayer.type = 'HeatmapLayer';
|
|
256
|
-
if (addLayer) {
|
|
257
|
-
const newId = UUID.uuid4();
|
|
258
|
-
addLayer(newId, targetLayer, 100);
|
|
259
|
-
overrideLayerEntriesRef.current.push({
|
|
260
|
-
layerId: newId,
|
|
261
|
-
action: 'remove',
|
|
262
|
-
});
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
else {
|
|
266
|
-
model.triggerLayerUpdate(targetLayerId, targetLayer);
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
});
|
|
270
|
-
};
|
|
271
|
-
const handlePrev = useCallback(() => {
|
|
272
|
-
if (hasPrev) {
|
|
273
|
-
setIndex(currentIndexDisplayed - 1);
|
|
274
|
-
}
|
|
275
|
-
}, [currentIndexDisplayed, setIndex]);
|
|
276
|
-
const handleNext = useCallback(() => {
|
|
277
|
-
if (hasNext) {
|
|
278
|
-
setIndex(currentIndexDisplayed + 1);
|
|
279
|
-
}
|
|
280
|
-
}, [currentIndexDisplayed, storySegments.length, setIndex]);
|
|
281
|
-
if (!storyData || ((_c = storyData === null || storyData === void 0 ? void 0 : storyData.storySegments) === null || _c === void 0 ? void 0 : _c.length) === 0) {
|
|
88
|
+
if (!storyData || ((_b = storyData === null || storyData === void 0 ? void 0 : storyData.storySegments) === null || _b === void 0 ? void 0 : _b.length) === 0) {
|
|
282
89
|
return (React.createElement("div", { style: { padding: '1rem' } },
|
|
283
90
|
React.createElement("p", null, "No Segments available. Add one using the Add Layer menu.")));
|
|
284
91
|
}
|
|
@@ -288,75 +95,22 @@ const StoryViewerPanel = forwardRef(({ model, isSpecta, isMobile = false, classN
|
|
|
288
95
|
hasPrev,
|
|
289
96
|
hasNext,
|
|
290
97
|
};
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
const root = panelRef.current;
|
|
294
|
-
const topEl = topSentinelRef.current;
|
|
295
|
-
const bottomEl = bottomSentinelRef.current;
|
|
296
|
-
if (!root || !topEl || !bottomEl) {
|
|
297
|
-
return;
|
|
298
|
-
}
|
|
299
|
-
const observer = new IntersectionObserver((entries) => {
|
|
300
|
-
for (const entry of entries) {
|
|
301
|
-
if (entry.target === topEl) {
|
|
302
|
-
atTopRef.current = entry.isIntersecting;
|
|
303
|
-
}
|
|
304
|
-
else if (entry.target === bottomEl) {
|
|
305
|
-
atBottomRef.current = entry.isIntersecting;
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
}, { root, threshold: 0, rootMargin: '0px' });
|
|
309
|
-
observer.observe(topEl);
|
|
310
|
-
observer.observe(bottomEl);
|
|
311
|
-
return () => observer.disconnect();
|
|
312
|
-
}, [currentIndexDisplayed]);
|
|
313
|
-
// Expose methods via ref for parent component to use
|
|
314
|
-
useImperativeHandle(ref, () => ({
|
|
315
|
-
handlePrev,
|
|
316
|
-
handleNext,
|
|
317
|
-
spectaMode: isSpecta,
|
|
318
|
-
hasPrev,
|
|
319
|
-
hasNext,
|
|
320
|
-
getAtTop: () => atTopRef.current,
|
|
321
|
-
getAtBottom: () => atBottomRef.current,
|
|
322
|
-
getScrollContainer: () => panelRef.current,
|
|
323
|
-
}), [handlePrev, handleNext, storyData, isSpecta, hasPrev, hasNext]);
|
|
324
|
-
const hasImage = !!(((_d = activeSlide === null || activeSlide === void 0 ? void 0 : activeSlide.content) === null || _d === void 0 ? void 0 : _d.image) && imageLoaded);
|
|
325
|
-
const storyType = (_e = storyData.storyType) !== null && _e !== void 0 ? _e : 'guided';
|
|
98
|
+
const hasImage = !!(((_c = activeSlide === null || activeSlide === void 0 ? void 0 : activeSlide.content) === null || _c === void 0 ? void 0 : _c.image) && imageLoaded);
|
|
99
|
+
const storyType = (_d = storyData.storyType) !== null && _d !== void 0 ? _d : 'guided';
|
|
326
100
|
const navPlacement = getStoryNavPlacement(isSpecta, hasImage, storyType, isMobile);
|
|
327
101
|
const navSlot = navPlacement !== null ? (React.createElement(StoryNavBar, Object.assign({ placement: navPlacement }, storyNavBarProps))) : null;
|
|
328
102
|
// Get transition time from current segment, default to 0.3s
|
|
329
|
-
const transitionTime = (
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
const el = segmentContainerRef.current;
|
|
333
|
-
if (!el || !onSegmentTransitionEnd) {
|
|
334
|
-
return;
|
|
335
|
-
}
|
|
336
|
-
const handleAnimationEnd = (e) => {
|
|
337
|
-
if (e.animationName === 'fadeIn') {
|
|
338
|
-
el.removeEventListener('animationend', handleAnimationEnd);
|
|
339
|
-
onSegmentTransitionEnd();
|
|
340
|
-
}
|
|
341
|
-
};
|
|
342
|
-
el.addEventListener('animationend', handleAnimationEnd);
|
|
343
|
-
return () => el.removeEventListener('animationend', handleAnimationEnd);
|
|
344
|
-
}, [currentIndexDisplayed, onSegmentTransitionEnd]);
|
|
345
|
-
return (React.createElement("div", { ref: panelRef, className: cn('jgis-story-viewer-panel', className), id: "jgis-story-segment-panel" },
|
|
346
|
-
React.createElement("div", { ref: topSentinelRef, "aria-hidden": true, "data-story-scroll-sentinel": "top", style: { height: 1, minHeight: 1, pointerEvents: 'none' } }),
|
|
347
|
-
React.createElement("div", { ref: segmentContainerRef, key: currentIndexDisplayed, className: "jgis-story-segment-container", style: {
|
|
103
|
+
const transitionTime = (_f = (_e = activeSlide === null || activeSlide === void 0 ? void 0 : activeSlide.transition) === null || _e === void 0 ? void 0 : _e.time) !== null && _f !== void 0 ? _f : 0.3;
|
|
104
|
+
return (React.createElement("div", { className: "jgis-story-viewer-panel" },
|
|
105
|
+
React.createElement("div", { ref: segmentContainerRef, key: currentIndex, className: "jgis-story-segment-container", style: {
|
|
348
106
|
animationDuration: `${transitionTime}s`,
|
|
349
107
|
} },
|
|
350
108
|
React.createElement("div", { id: "jgis-story-segment-header" },
|
|
351
|
-
React.createElement("h1", { className: "jgis-story-viewer-title" }, layerName !== null && layerName !== void 0 ? layerName : `Slide ${
|
|
352
|
-
((
|
|
353
|
-
React.createElement(StorySubtitleSection, { title: (
|
|
354
|
-
navPlacement === 'subtitle-specta-mobile'
|
|
355
|
-
? navSlot
|
|
356
|
-
: null })),
|
|
109
|
+
React.createElement("h1", { className: "jgis-story-viewer-title" }, layerName !== null && layerName !== void 0 ? layerName : `Slide ${currentIndex + 1}`),
|
|
110
|
+
((_g = activeSlide === null || activeSlide === void 0 ? void 0 : activeSlide.content) === null || _g === void 0 ? void 0 : _g.image) && imageLoaded ? (React.createElement(StoryImageSection, { imageUrl: activeSlide.content.image, imageLoaded: imageLoaded, layerName: layerName !== null && layerName !== void 0 ? layerName : '', slideNumber: currentIndex, navSlot: navPlacement === 'over-image' ? navSlot : null })) : (React.createElement(StoryTitleSection, { title: (_h = storyData.title) !== null && _h !== void 0 ? _h : '', navSlot: navPlacement === 'below-title' ? navSlot : null })),
|
|
111
|
+
React.createElement(StorySubtitleSection, { title: (_k = (_j = activeSlide === null || activeSlide === void 0 ? void 0 : activeSlide.content) === null || _j === void 0 ? void 0 : _j.title) !== null && _k !== void 0 ? _k : '', navSlot: navPlacement === 'subtitle-specta' ? navSlot : null })),
|
|
357
112
|
React.createElement("div", { id: "jgis-story-segment-content" },
|
|
358
|
-
React.createElement(StoryContentSection, { markdown: (
|
|
359
|
-
|
|
360
|
-
});
|
|
113
|
+
React.createElement(StoryContentSection, { markdown: (_m = (_l = activeSlide === null || activeSlide === void 0 ? void 0 : activeSlide.content) === null || _l === void 0 ? void 0 : _l.markdown) !== null && _m !== void 0 ? _m : '' })))));
|
|
114
|
+
}
|
|
361
115
|
StoryViewerPanel.displayName = 'StoryViewerPanel';
|
|
362
116
|
export default StoryViewerPanel;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import { Switch } from "
|
|
2
|
+
import { Switch } from "../../../shared/components/Switch";
|
|
3
3
|
export function PreviewModeSwitch({ checked, onCheckedChange, }) {
|
|
4
4
|
return (React.createElement("div", { style: {
|
|
5
5
|
display: 'flex',
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { IJGISStoryMap, IJupyterGISModel, IStorySegmentLayer } from '@jupytergis/schema';
|
|
2
|
+
import { RefObject } from 'react';
|
|
3
|
+
import { type IStoryViewerPanelHandle } from "../StoryViewerPanel";
|
|
4
|
+
interface ISpectaDesktopViewProps {
|
|
5
|
+
model: IJupyterGISModel;
|
|
6
|
+
isSpecta: boolean;
|
|
7
|
+
containerRef: RefObject<HTMLDivElement>;
|
|
8
|
+
storyViewerPanelRef: RefObject<IStoryViewerPanelHandle>;
|
|
9
|
+
segmentContainerRef: RefObject<HTMLDivElement>;
|
|
10
|
+
storyData: IJGISStoryMap | null;
|
|
11
|
+
currentIndex: number;
|
|
12
|
+
activeSlide: IStorySegmentLayer['parameters'] | undefined;
|
|
13
|
+
layerName: string;
|
|
14
|
+
handlePrev: () => void;
|
|
15
|
+
handleNext: () => void;
|
|
16
|
+
hasPrev: boolean;
|
|
17
|
+
hasNext: boolean;
|
|
18
|
+
setIndex: (index: number) => void;
|
|
19
|
+
}
|
|
20
|
+
export declare function SpectaDesktopView({ model, isSpecta, containerRef, storyViewerPanelRef, segmentContainerRef, storyData, currentIndex, activeSlide, layerName, handlePrev, handleNext, hasPrev, hasNext, setIndex, }: ISpectaDesktopViewProps): JSX.Element;
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import React, { useEffect, useImperativeHandle, useRef, } from 'react';
|
|
2
|
+
import StoryViewerPanel from "../StoryViewerPanel";
|
|
3
|
+
import SpectaPresentationProgressBar from "../../../statusbar/SpectaPresentationProgressBar";
|
|
4
|
+
export function SpectaDesktopView({ model, isSpecta, containerRef, storyViewerPanelRef, segmentContainerRef, storyData, currentIndex, activeSlide, layerName, handlePrev, handleNext, hasPrev, hasNext, setIndex, }) {
|
|
5
|
+
const scrollContainerRef = useRef(null);
|
|
6
|
+
const topSentinelRef = useRef(null);
|
|
7
|
+
const bottomSentinelRef = useRef(null);
|
|
8
|
+
const atTopRef = useRef(false);
|
|
9
|
+
const atBottomRef = useRef(false);
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
const root = scrollContainerRef.current;
|
|
12
|
+
const topEl = topSentinelRef.current;
|
|
13
|
+
const bottomEl = bottomSentinelRef.current;
|
|
14
|
+
if (!root || !topEl || !bottomEl) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const observer = new IntersectionObserver((entries) => {
|
|
18
|
+
for (const entry of entries) {
|
|
19
|
+
if (entry.target === topEl) {
|
|
20
|
+
atTopRef.current = entry.isIntersecting;
|
|
21
|
+
}
|
|
22
|
+
else if (entry.target === bottomEl) {
|
|
23
|
+
atBottomRef.current = entry.isIntersecting;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}, { root, threshold: 0, rootMargin: '0px' });
|
|
27
|
+
observer.observe(topEl);
|
|
28
|
+
observer.observe(bottomEl);
|
|
29
|
+
return () => observer.disconnect();
|
|
30
|
+
}, [currentIndex]);
|
|
31
|
+
useImperativeHandle(storyViewerPanelRef, () => ({
|
|
32
|
+
handlePrev,
|
|
33
|
+
handleNext,
|
|
34
|
+
spectaMode: isSpecta,
|
|
35
|
+
hasPrev,
|
|
36
|
+
hasNext,
|
|
37
|
+
getAtTop: () => atTopRef.current,
|
|
38
|
+
getAtBottom: () => atBottomRef.current,
|
|
39
|
+
getScrollContainer: () => scrollContainerRef.current,
|
|
40
|
+
}), [handlePrev, handleNext, isSpecta, hasPrev, hasNext]);
|
|
41
|
+
return (React.createElement(React.Fragment, null,
|
|
42
|
+
React.createElement("div", { className: "jgis-specta-right-panel-container-mod jgis-right-panel-container" },
|
|
43
|
+
React.createElement("div", { ref: containerRef, className: "jgis-specta-story-panel-container" },
|
|
44
|
+
React.createElement("div", { ref: scrollContainerRef, className: "jgis-story-viewer-panel-specta-mod", id: "jgis-story-segment-panel" },
|
|
45
|
+
React.createElement("div", { ref: topSentinelRef, "aria-hidden": true, "data-story-scroll-sentinel": "top", style: { height: 1, minHeight: 1, pointerEvents: 'none' } }),
|
|
46
|
+
React.createElement(StoryViewerPanel, { model: model, isSpecta: isSpecta, className: "jgis-story-viewer-panel-specta-mod", segmentContainerRef: segmentContainerRef, storyData: storyData, currentIndex: currentIndex, activeSlide: activeSlide, layerName: layerName, handlePrev: handlePrev, handleNext: handleNext, hasPrev: hasPrev, hasNext: hasNext, setIndex: setIndex }),
|
|
47
|
+
React.createElement("div", { ref: bottomSentinelRef, "aria-hidden": true, "data-story-scroll-sentinel": "bottom", style: { height: 1, minHeight: 1, pointerEvents: 'none' } })))),
|
|
48
|
+
React.createElement(SpectaPresentationProgressBar, { model: model })));
|
|
49
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { IJGISStoryMap, IJupyterGISModel, IStorySegmentLayer } from '@jupytergis/schema';
|
|
2
|
+
import React, { RefObject } from 'react';
|
|
3
|
+
interface ISpectaMobileViewProps {
|
|
4
|
+
model: IJupyterGISModel;
|
|
5
|
+
segmentContainerRef: RefObject<HTMLDivElement>;
|
|
6
|
+
storyData: IJGISStoryMap | null;
|
|
7
|
+
currentIndex: number;
|
|
8
|
+
activeSlide: IStorySegmentLayer['parameters'] | undefined;
|
|
9
|
+
layerName: string;
|
|
10
|
+
handlePrev: () => void;
|
|
11
|
+
handleNext: () => void;
|
|
12
|
+
hasPrev: boolean;
|
|
13
|
+
hasNext: boolean;
|
|
14
|
+
setIndex: (index: number) => void;
|
|
15
|
+
}
|
|
16
|
+
export declare function SpectaMobileView({ model, segmentContainerRef, storyData, currentIndex, activeSlide, layerName, handlePrev, handleNext, hasPrev, hasNext, setIndex, }: ISpectaMobileViewProps): React.JSX.Element;
|
|
17
|
+
export {};
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import React, { useEffect, useState } from 'react';
|
|
2
|
-
import { Button } from "
|
|
3
|
-
import { Drawer, DrawerContent, DrawerTrigger, } from "
|
|
4
|
-
import StoryViewerPanel from '
|
|
2
|
+
import { Button } from "../../../shared/components/Button";
|
|
3
|
+
import { Drawer, DrawerContent, DrawerTrigger, } from "../../../shared/components/Drawer";
|
|
4
|
+
import StoryViewerPanel from '../StoryViewerPanel';
|
|
5
|
+
import { getSpectaPresentationStyle } from '../hooks/useStoryMap';
|
|
5
6
|
const MAIN_ID = 'jp-main-content-panel';
|
|
6
7
|
const SEGMENT_PANEL_ID = 'jgis-story-segment-panel';
|
|
7
8
|
const SEGMENT_HEADER_ID = 'jgis-story-segment-header';
|
|
@@ -28,30 +29,14 @@ function getFirstSnapFromSegmentHeader(mainEl, segmentPanelEl, segmentHeaderEl)
|
|
|
28
29
|
const clamped = Math.max(SNAP_FIRST_MIN, Math.min(SNAP_FIRST_MAX, fraction));
|
|
29
30
|
return clamped;
|
|
30
31
|
}
|
|
31
|
-
|
|
32
|
-
function getSpectaPresentationStyle(model) {
|
|
33
|
-
const story = model.getSelectedStory().story;
|
|
34
|
-
const bgColor = story === null || story === void 0 ? void 0 : story.presentationBgColor;
|
|
35
|
-
const textColor = story === null || story === void 0 ? void 0 : story.presentationTextColor;
|
|
36
|
-
const style = {};
|
|
37
|
-
if (bgColor) {
|
|
38
|
-
style['--jgis-specta-bg-color'] = bgColor;
|
|
39
|
-
style.backgroundColor = bgColor;
|
|
40
|
-
}
|
|
41
|
-
if (textColor) {
|
|
42
|
-
style['--jgis-specta-text-color'] = textColor;
|
|
43
|
-
style.color = textColor;
|
|
44
|
-
}
|
|
45
|
-
return style;
|
|
46
|
-
}
|
|
47
|
-
export function MobileSpectaPanel({ model }) {
|
|
32
|
+
export function SpectaMobileView({ model, segmentContainerRef, storyData, currentIndex, activeSlide, layerName, handlePrev, handleNext, hasPrev, hasNext, setIndex, }) {
|
|
48
33
|
const [container, setContainer] = useState(null);
|
|
49
34
|
const [snapPoints, setSnapPoints] = useState([
|
|
50
35
|
SNAP_FIRST_DEFAULT,
|
|
51
36
|
1,
|
|
52
37
|
]);
|
|
53
38
|
const [snap, setSnap] = useState(snapPoints[0]);
|
|
54
|
-
const presentationStyle = getSpectaPresentationStyle(
|
|
39
|
+
const presentationStyle = getSpectaPresentationStyle(storyData);
|
|
55
40
|
// Keep active snap in sync with snapPoints so Vaul's --snap-point-height stays defined.
|
|
56
41
|
useEffect(() => {
|
|
57
42
|
const isInSnapPoints = snapPoints.some(p => p === snap ||
|
|
@@ -110,5 +95,6 @@ export function MobileSpectaPanel({ model }) {
|
|
|
110
95
|
React.createElement(DrawerTrigger, { asChild: true },
|
|
111
96
|
React.createElement(Button, null, "Open Story Panel")),
|
|
112
97
|
React.createElement(DrawerContent, { style: presentationStyle },
|
|
113
|
-
React.createElement(
|
|
98
|
+
React.createElement("div", { id: SEGMENT_PANEL_ID, className: "jgis-story-viewer-panel" },
|
|
99
|
+
React.createElement(StoryViewerPanel, { model: model, isSpecta: true, isMobile: true, segmentContainerRef: segmentContainerRef, storyData: storyData, currentIndex: currentIndex, activeSlide: activeSlide, layerName: layerName, handlePrev: handlePrev, handleNext: handleNext, hasPrev: hasPrev, hasNext: hasNext, setIndex: setIndex }))))));
|
|
114
100
|
}
|
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
import { ChevronLeft, ChevronRight } from 'lucide-react';
|
|
2
2
|
import React from 'react';
|
|
3
|
-
import { Button } from "
|
|
3
|
+
import { Button } from "../../../shared/components/Button";
|
|
4
4
|
function StoryNavBar({ placement, onPrev, onNext, hasPrev, hasNext, }) {
|
|
5
5
|
const containerClassName = placement === 'over-image'
|
|
6
6
|
? 'jgis-story-viewer-nav-container'
|
|
7
7
|
: placement === 'subtitle-specta'
|
|
8
8
|
? 'jgis-story-viewer-nav-container-specta-mod'
|
|
9
|
-
:
|
|
10
|
-
? 'jgis-story-viewer-nav-container-specta-mobile'
|
|
11
|
-
: undefined;
|
|
9
|
+
: undefined;
|
|
12
10
|
const navbarClassName = placement === 'subtitle-specta'
|
|
13
11
|
? 'jgis-story-navbar jgis-story-navbar-specta-mod'
|
|
14
12
|
: 'jgis-story-navbar';
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { IJGISLayer, IJGISStoryMap, IJupyterGISModel } from '@jupytergis/schema';
|
|
2
|
+
import { CSSProperties, RefObject } from 'react';
|
|
3
|
+
/** Entry for a layer affected by layer override
|
|
4
|
+
* remove if we added a layer or restore if we modified an existing layer.
|
|
5
|
+
**/
|
|
6
|
+
export interface IOverrideLayerEntry {
|
|
7
|
+
layerId: string;
|
|
8
|
+
action: 'remove' | 'restore';
|
|
9
|
+
}
|
|
10
|
+
export interface IUseStoryMapParams {
|
|
11
|
+
model: IJupyterGISModel;
|
|
12
|
+
overrideLayerEntriesRef: RefObject<IOverrideLayerEntry[]>;
|
|
13
|
+
removeLayer?: (id: string) => void;
|
|
14
|
+
addLayer?: (id: string, layer: IJGISLayer, index: number) => Promise<void>;
|
|
15
|
+
isSpecta: boolean;
|
|
16
|
+
/** Panel root element for applying specta presentation CSS variables. */
|
|
17
|
+
panelRef?: RefObject<HTMLDivElement | null>;
|
|
18
|
+
}
|
|
19
|
+
/** Inline style for specta presentation (bg and text color from story). */
|
|
20
|
+
export declare function getSpectaPresentationStyle(story: IJGISStoryMap | null): CSSProperties;
|
|
21
|
+
export declare function useStoryMap({ model, overrideLayerEntriesRef, removeLayer, addLayer, panelRef, isSpecta, }: IUseStoryMapParams): {
|
|
22
|
+
storyData: IJGISStoryMap | null;
|
|
23
|
+
storySegments: IJGISLayer[];
|
|
24
|
+
currentIndex: number;
|
|
25
|
+
clearOverrideLayers: () => void;
|
|
26
|
+
setIndex: (index: number) => void;
|
|
27
|
+
handlePrev: () => void;
|
|
28
|
+
handleNext: () => void;
|
|
29
|
+
hasPrev: boolean;
|
|
30
|
+
hasNext: boolean;
|
|
31
|
+
setSelectedLayerByIndex: (index: number) => void;
|
|
32
|
+
currentStorySegment: IJGISLayer;
|
|
33
|
+
activeSlide: {
|
|
34
|
+
[k: string]: any;
|
|
35
|
+
} | undefined;
|
|
36
|
+
layerName: string;
|
|
37
|
+
currentStorySegmentId: string | undefined;
|
|
38
|
+
zoomToCurrentLayer: () => void;
|
|
39
|
+
};
|