@jupytergis/base 0.12.1 → 0.13.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 (84) hide show
  1. package/lib/commands/index.js +2 -6
  2. package/lib/dialogs/layerBrowserDialog.d.ts +3 -3
  3. package/lib/dialogs/layerBrowserDialog.js +9 -10
  4. package/lib/dialogs/symbology/hooks/useEffectiveSymbologyParams.d.ts +16 -0
  5. package/lib/dialogs/symbology/hooks/useEffectiveSymbologyParams.js +24 -0
  6. package/lib/dialogs/symbology/hooks/useOkSignal.d.ts +6 -0
  7. package/lib/dialogs/symbology/hooks/useOkSignal.js +25 -0
  8. package/lib/dialogs/symbology/symbologyDialog.d.ts +4 -2
  9. package/lib/dialogs/symbology/symbologyDialog.js +6 -10
  10. package/lib/dialogs/symbology/symbologyUtils.d.ts +25 -2
  11. package/lib/dialogs/symbology/symbologyUtils.js +74 -4
  12. package/lib/dialogs/symbology/tiff_layer/TiffRendering.js +3 -3
  13. package/lib/dialogs/symbology/tiff_layer/types/MultibandColor.js +31 -34
  14. package/lib/dialogs/symbology/tiff_layer/types/SingleBandPseudoColor.js +68 -62
  15. package/lib/dialogs/symbology/vector_layer/VectorRendering.js +33 -21
  16. package/lib/dialogs/symbology/vector_layer/types/Canonical.js +23 -24
  17. package/lib/dialogs/symbology/vector_layer/types/Categorized.js +48 -49
  18. package/lib/dialogs/symbology/vector_layer/types/Graduated.js +53 -62
  19. package/lib/dialogs/symbology/vector_layer/types/Heatmap.js +35 -34
  20. package/lib/dialogs/symbology/vector_layer/types/SimpleSymbol.js +45 -47
  21. package/lib/formbuilder/objectform/StoryEditorForm.js +1 -18
  22. package/lib/formbuilder/objectform/baseform.d.ts +6 -0
  23. package/lib/formbuilder/objectform/baseform.js +21 -38
  24. package/lib/formbuilder/objectform/components/LayerSelect.d.ts +7 -0
  25. package/lib/formbuilder/objectform/components/LayerSelect.js +43 -0
  26. package/lib/formbuilder/objectform/components/OpacitySlider.d.ts +4 -0
  27. package/lib/formbuilder/objectform/components/OpacitySlider.js +40 -0
  28. package/lib/formbuilder/objectform/components/SegmentFormSymbology.d.ts +3 -0
  29. package/lib/formbuilder/objectform/components/SegmentFormSymbology.js +59 -0
  30. package/lib/formbuilder/objectform/layer/storySegmentLayerForm.d.ts +2 -2
  31. package/lib/formbuilder/objectform/layer/storySegmentLayerForm.js +19 -0
  32. package/lib/mainview/mainView.d.ts +8 -2
  33. package/lib/mainview/mainView.js +41 -7
  34. package/lib/mainview/mainviewwidget.js +2 -2
  35. package/lib/panelview/leftpanel.d.ts +2 -1
  36. package/lib/panelview/leftpanel.js +28 -20
  37. package/lib/panelview/rightpanel.d.ts +4 -1
  38. package/lib/panelview/rightpanel.js +14 -20
  39. package/lib/panelview/story-maps/MobileSpectaPanel.d.ts +7 -0
  40. package/lib/panelview/story-maps/MobileSpectaPanel.js +114 -0
  41. package/lib/panelview/story-maps/StoryNavBar.d.ts +3 -2
  42. package/lib/panelview/story-maps/StoryNavBar.js +18 -6
  43. package/lib/panelview/story-maps/StoryViewerPanel.d.ts +13 -1
  44. package/lib/panelview/story-maps/StoryViewerPanel.js +168 -34
  45. package/lib/panelview/story-maps/components/StoryImageSection.d.ts +2 -7
  46. package/lib/panelview/story-maps/components/StoryImageSection.js +2 -4
  47. package/lib/panelview/story-maps/components/StorySubtitleSection.d.ts +2 -6
  48. package/lib/panelview/story-maps/components/StorySubtitleSection.js +2 -4
  49. package/lib/panelview/story-maps/components/StoryTitleSection.d.ts +2 -7
  50. package/lib/panelview/story-maps/components/StoryTitleSection.js +2 -3
  51. package/lib/shared/components/Button.js +2 -2
  52. package/lib/shared/components/Calendar.js +0 -1
  53. package/lib/shared/components/Checkbox.d.ts +1 -1
  54. package/lib/shared/components/Checkbox.js +1 -1
  55. package/lib/shared/components/Dialog.d.ts +1 -1
  56. package/lib/shared/components/Dialog.js +1 -1
  57. package/lib/shared/components/Drawer.d.ts +13 -0
  58. package/lib/shared/components/Drawer.js +59 -0
  59. package/lib/shared/components/DropdownMenu.d.ts +1 -1
  60. package/lib/shared/components/DropdownMenu.js +1 -1
  61. package/lib/shared/components/Popover.d.ts +1 -1
  62. package/lib/shared/components/Popover.js +1 -1
  63. package/lib/shared/components/RadioGroup.d.ts +1 -1
  64. package/lib/shared/components/RadioGroup.js +1 -1
  65. package/lib/shared/components/Sheet.d.ts +15 -0
  66. package/lib/shared/components/Sheet.js +64 -0
  67. package/lib/shared/components/Switch.d.ts +1 -1
  68. package/lib/shared/components/Switch.js +1 -1
  69. package/lib/shared/components/Tabs.d.ts +1 -1
  70. package/lib/shared/components/Tabs.js +1 -1
  71. package/lib/shared/components/ToggleGroup.d.ts +1 -1
  72. package/lib/shared/components/ToggleGroup.js +1 -1
  73. package/lib/shared/hooks/useLatest.d.ts +1 -0
  74. package/lib/shared/hooks/useLatest.js +8 -0
  75. package/lib/shared/hooks/useMediaQuery.d.ts +9 -0
  76. package/lib/shared/hooks/useMediaQuery.js +32 -0
  77. package/lib/tools.js +21 -34
  78. package/lib/types.d.ts +1 -0
  79. package/lib/types.js +6 -1
  80. package/package.json +5 -12
  81. package/style/base.css +15 -0
  82. package/style/shared/drawer.css +154 -0
  83. package/style/shared/sheet.css +258 -0
  84. package/style/storyPanel.css +36 -8
@@ -0,0 +1,114 @@
1
+ import React, { useEffect, useState } from 'react';
2
+ import { Button } from "../../shared/components/Button";
3
+ import { Drawer, DrawerContent, DrawerTrigger, } from "../../shared/components/Drawer";
4
+ import StoryViewerPanel from './StoryViewerPanel';
5
+ const MAIN_ID = 'jp-main-content-panel';
6
+ const SEGMENT_PANEL_ID = 'jgis-story-segment-panel';
7
+ const SEGMENT_HEADER_ID = 'jgis-story-segment-header';
8
+ const SNAP_FIRST_MIN = 0.3;
9
+ const SNAP_FIRST_MAX = 0.95;
10
+ const SNAP_FIRST_DEFAULT = 0.7;
11
+ /** Offset (px) for segment header height: margins from p and h1 in story content */
12
+ const SEGMENT_HEADER_OFFSET_PX = 16.8 * 2 + 18.76;
13
+ /**
14
+ * Compute the first snap point so that vaul's --snap-point-height (the
15
+ * transform offset) equals #jgis-story-segment-panel height minus #jgis-story-segment-header height.
16
+ * For a bottom drawer, offset = mainHeight * (1 - snapPoint), so
17
+ * snapPoint = (mainHeight - offset) / mainHeight.
18
+ */
19
+ function getFirstSnapFromSegmentHeader(mainEl, segmentPanelEl, segmentHeaderEl) {
20
+ const mainHeight = mainEl.getBoundingClientRect().height;
21
+ const segmentPanelHeight = segmentPanelEl.getBoundingClientRect().height;
22
+ const segmentHeaderHeight = segmentHeaderEl.getBoundingClientRect().height;
23
+ const offsetPx = segmentPanelHeight - segmentHeaderHeight - SEGMENT_HEADER_OFFSET_PX;
24
+ if (mainHeight <= 0) {
25
+ return SNAP_FIRST_DEFAULT;
26
+ }
27
+ const fraction = (mainHeight - offsetPx) / mainHeight;
28
+ const clamped = Math.max(SNAP_FIRST_MIN, Math.min(SNAP_FIRST_MAX, fraction));
29
+ return clamped;
30
+ }
31
+ /** Build inline styles for specta presentation (bg and text color from story). */
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 }) {
48
+ const [container, setContainer] = useState(null);
49
+ const [snapPoints, setSnapPoints] = useState([
50
+ SNAP_FIRST_DEFAULT,
51
+ 1,
52
+ ]);
53
+ const [snap, setSnap] = useState(snapPoints[0]);
54
+ const presentationStyle = getSpectaPresentationStyle(model);
55
+ // Keep active snap in sync with snapPoints so Vaul's --snap-point-height stays defined.
56
+ useEffect(() => {
57
+ const isInSnapPoints = snapPoints.some(p => p === snap ||
58
+ (typeof p === 'number' &&
59
+ typeof snap === 'number' &&
60
+ Math.abs(p - snap) < 1e-9));
61
+ if (!isInSnapPoints && snapPoints.length > 0) {
62
+ setSnap(snapPoints[0]);
63
+ }
64
+ }, [snapPoints, snap]);
65
+ // Observe #jgis-story-segment-panel (and re-attach when drawer reopens).
66
+ useEffect(() => {
67
+ const mainEl = document.getElementById(MAIN_ID);
68
+ setContainer(mainEl);
69
+ if (!mainEl) {
70
+ return;
71
+ }
72
+ const updateFirstSnap = () => {
73
+ const segmentPanelEl = document.getElementById(SEGMENT_PANEL_ID);
74
+ const segmentHeaderEl = document.getElementById(SEGMENT_HEADER_ID);
75
+ if (segmentPanelEl && segmentHeaderEl) {
76
+ const firstSnap = getFirstSnapFromSegmentHeader(mainEl, segmentPanelEl, segmentHeaderEl);
77
+ setSnapPoints([firstSnap, 1]);
78
+ }
79
+ };
80
+ const resizeObserver = new ResizeObserver(() => updateFirstSnap());
81
+ let observedPanelEl = null;
82
+ const syncHeaderObserver = () => {
83
+ const segmentPanelEl = document.getElementById(SEGMENT_PANEL_ID);
84
+ const segmentHeaderEl = document.getElementById(SEGMENT_HEADER_ID);
85
+ if (!segmentPanelEl ||
86
+ !segmentHeaderEl ||
87
+ segmentPanelEl === observedPanelEl) {
88
+ return;
89
+ }
90
+ if (observedPanelEl) {
91
+ resizeObserver.unobserve(observedPanelEl);
92
+ }
93
+ resizeObserver.observe(segmentPanelEl);
94
+ observedPanelEl = segmentPanelEl;
95
+ updateFirstSnap();
96
+ };
97
+ syncHeaderObserver();
98
+ const mutationObserver = new MutationObserver(syncHeaderObserver);
99
+ mutationObserver.observe(mainEl, {
100
+ childList: true,
101
+ subtree: true,
102
+ });
103
+ return () => {
104
+ resizeObserver.disconnect();
105
+ mutationObserver.disconnect();
106
+ };
107
+ }, []);
108
+ return (React.createElement("div", { className: "jgis-mobile-specta-trigger-wrapper" },
109
+ React.createElement(Drawer, { snapPoints: snapPoints, activeSnapPoint: snap, setActiveSnapPoint: setSnap, direction: "bottom", container: container, noBodyStyles: true },
110
+ React.createElement(DrawerTrigger, { asChild: true },
111
+ React.createElement(Button, null, "Open Story Panel")),
112
+ React.createElement(DrawerContent, { style: presentationStyle },
113
+ React.createElement(StoryViewerPanel, { isSpecta: true, isMobile: true, model: model })))));
114
+ }
@@ -1,10 +1,11 @@
1
1
  import React from 'react';
2
+ import type { StoryNavPlacement } from './StoryViewerPanel';
2
3
  interface IStoryNavBarProps {
4
+ placement: StoryNavPlacement;
3
5
  onPrev: () => void;
4
6
  onNext: () => void;
5
7
  hasPrev: boolean;
6
8
  hasNext: boolean;
7
- isSpecta: boolean;
8
9
  }
9
- declare function StoryNavBar({ onPrev, onNext, hasPrev, hasNext, isSpecta, }: IStoryNavBarProps): React.JSX.Element;
10
+ declare function StoryNavBar({ placement, onPrev, onNext, hasPrev, hasNext, }: IStoryNavBarProps): React.JSX.Element;
10
11
  export default StoryNavBar;
@@ -1,11 +1,23 @@
1
1
  import { ChevronLeft, ChevronRight } from 'lucide-react';
2
2
  import React from 'react';
3
3
  import { Button } from "../../shared/components/Button";
4
- function StoryNavBar({ onPrev, onNext, hasPrev, hasNext, isSpecta, }) {
5
- return (React.createElement("div", { className: `jgis-story-navbar ${isSpecta ? 'jgis-story-navbar-specta-mod' : ''}` },
6
- React.createElement(Button, { onClick: onPrev, disabled: !hasPrev, "aria-label": "Previous slide" },
7
- React.createElement(ChevronLeft, null)),
8
- React.createElement(Button, { onClick: onNext, disabled: !hasNext, "aria-label": "Next slide" },
9
- React.createElement(ChevronRight, null))));
4
+ function StoryNavBar({ placement, onPrev, onNext, hasPrev, hasNext, }) {
5
+ const containerClassName = placement === 'over-image'
6
+ ? 'jgis-story-viewer-nav-container'
7
+ : placement === 'subtitle-specta'
8
+ ? 'jgis-story-viewer-nav-container-specta-mod'
9
+ : placement === 'subtitle-specta-mobile'
10
+ ? 'jgis-story-viewer-nav-container-specta-mobile'
11
+ : undefined;
12
+ const navbarClassName = placement === 'subtitle-specta'
13
+ ? 'jgis-story-navbar jgis-story-navbar-specta-mod'
14
+ : 'jgis-story-navbar';
15
+ return (React.createElement("div", { className: containerClassName },
16
+ React.createElement("div", { className: navbarClassName },
17
+ React.createElement(React.Fragment, null,
18
+ React.createElement(Button, { onClick: onPrev, disabled: !hasPrev, className: "jgis-story-navbar-button", "aria-label": "Previous slide" },
19
+ React.createElement(ChevronLeft, null)),
20
+ React.createElement(Button, { onClick: onNext, disabled: !hasNext, className: "jgis-story-navbar-button", "aria-label": "Next slide" },
21
+ React.createElement(ChevronRight, null))))));
10
22
  }
11
23
  export default StoryNavBar;
@@ -1,13 +1,25 @@
1
- import { IJupyterGISModel } from '@jupytergis/schema';
1
+ import { IJGISLayer, IJupyterGISModel } from '@jupytergis/schema';
2
2
  import React from 'react';
3
3
  interface IStoryViewerPanelProps {
4
4
  model: IJupyterGISModel;
5
5
  isSpecta: boolean;
6
+ isMobile?: boolean;
7
+ className?: string;
8
+ addLayer?: (id: string, layer: IJGISLayer, index: number) => Promise<void>;
9
+ removeLayer?: (id: string) => void;
6
10
  }
7
11
  export interface IStoryViewerPanelHandle {
8
12
  handlePrev: () => void;
9
13
  handleNext: () => void;
10
14
  canNavigate: boolean;
11
15
  }
16
+ /**
17
+ * Where the story nav bar should be rendered in the viewer layout.
18
+ * - below-title: normal mode, guided, no image (under the title)
19
+ * - over-image: normal mode, guided, with image (over the image)
20
+ * - subtitle-specta: specta mode desktop (next to subtitle, fixed centered)
21
+ * - subtitle-specta-mobile: specta mode mobile (in line with subtitle)
22
+ */
23
+ export type StoryNavPlacement = 'below-title' | 'over-image' | 'subtitle-specta' | 'subtitle-specta-mobile';
12
24
  declare const StoryViewerPanel: React.ForwardRefExoticComponent<IStoryViewerPanelProps & React.RefAttributes<IStoryViewerPanelHandle>>;
13
25
  export default StoryViewerPanel;
@@ -1,14 +1,52 @@
1
+ import { UUID } from '@lumino/coreutils';
1
2
  import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState, } from 'react';
3
+ import { cn } from "../../shared/components/utils";
4
+ import StoryNavBar from './StoryNavBar';
2
5
  import StoryContentSection from './components/StoryContentSection';
3
6
  import StoryImageSection from './components/StoryImageSection';
4
7
  import StorySubtitleSection from './components/StorySubtitleSection';
5
8
  import StoryTitleSection from './components/StoryTitleSection';
6
- const StoryViewerPanel = forwardRef(({ model, isSpecta }, ref) => {
9
+ /**
10
+ * Returns which section should render the nav bar, or null if nav should be hidden.
11
+ */
12
+ function getStoryNavPlacement(isSpecta, hasImage, storyType, isMobile) {
13
+ if (isSpecta) {
14
+ return isMobile ? 'subtitle-specta-mobile' : 'subtitle-specta';
15
+ }
16
+ if (storyType !== 'guided') {
17
+ return null;
18
+ }
19
+ return hasImage ? 'over-image' : 'below-title';
20
+ }
21
+ const StoryViewerPanel = forwardRef(({ model, isSpecta, isMobile = false, className, addLayer, removeLayer }, ref) => {
7
22
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
8
- const [currentIndexDisplayed, setCurrentIndexDisplayed] = useState(0);
23
+ const [currentIndexDisplayed, setCurrentIndexDisplayed] = useState(() => model.getCurrentSegmentIndex());
9
24
  const [storyData, setStoryData] = useState((_a = model.getSelectedStory().story) !== null && _a !== void 0 ? _a : null);
10
25
  const [imageLoaded, setImageLoaded] = useState(false);
11
26
  const panelRef = useRef(null);
27
+ const setIndex = useCallback((index) => {
28
+ model.setCurrentSegmentIndex(index);
29
+ setCurrentIndexDisplayed(index);
30
+ }, [model]);
31
+ /** Layers affected by symbology override
32
+ * We want to remove added layers (ie Heatmap)
33
+ * and Restore the original symbology for modified layers
34
+ */
35
+ const overrideLayerEntriesRef = useRef([]);
36
+ const clearOverrideLayers = useCallback(() => {
37
+ overrideLayerEntriesRef.current.forEach(({ layerId, action }) => {
38
+ if (action === 'remove') {
39
+ removeLayer === null || removeLayer === void 0 ? void 0 : removeLayer(layerId);
40
+ }
41
+ else {
42
+ const layer = model.getLayer(layerId);
43
+ if (layer) {
44
+ model.triggerLayerUpdate(layerId, layer);
45
+ }
46
+ }
47
+ });
48
+ overrideLayerEntriesRef.current = [];
49
+ }, [model]);
12
50
  // Derive story segments from story data
13
51
  const storySegments = useMemo(() => {
14
52
  if (!(storyData === null || storyData === void 0 ? void 0 : storyData.storySegments)) {
@@ -26,15 +64,14 @@ const StoryViewerPanel = forwardRef(({ model, isSpecta }, ref) => {
26
64
  const activeSlide = useMemo(() => {
27
65
  return currentStorySegment === null || currentStorySegment === void 0 ? void 0 : currentStorySegment.parameters;
28
66
  }, [currentStorySegment]);
29
- const layerName = useMemo(() => {
30
- var _a;
31
- return (_a = currentStorySegment === null || currentStorySegment === void 0 ? void 0 : currentStorySegment.name) !== null && _a !== void 0 ? _a : '';
32
- }, [currentStorySegment]);
67
+ const layerName = useMemo(() => { var _a; return (_a = currentStorySegment === null || currentStorySegment === void 0 ? void 0 : currentStorySegment.name) !== null && _a !== void 0 ? _a : ''; }, [currentStorySegment]);
33
68
  // Derive story segment ID for zooming
34
69
  const currentStorySegmentId = useMemo(() => {
35
70
  var _a;
36
71
  return (_a = storyData === null || storyData === void 0 ? void 0 : storyData.storySegments) === null || _a === void 0 ? void 0 : _a[currentIndexDisplayed];
37
72
  }, [storyData, currentIndexDisplayed]);
73
+ const hasPrev = currentIndexDisplayed > 0;
74
+ const hasNext = currentIndexDisplayed < storySegments.length - 1;
38
75
  const zoomToCurrentLayer = () => {
39
76
  if (currentStorySegmentId) {
40
77
  model.centerOnPosition(currentStorySegmentId);
@@ -51,19 +88,40 @@ const StoryViewerPanel = forwardRef(({ model, isSpecta }, ref) => {
51
88
  };
52
89
  }
53
90
  }, [storyData, model]);
91
+ // On unmount: remove override layers and restore layer symbology
92
+ useEffect(() => {
93
+ return () => {
94
+ var _a;
95
+ clearOverrideLayers();
96
+ (_a = storyData === null || storyData === void 0 ? void 0 : storyData.storySegments) === null || _a === void 0 ? void 0 : _a.forEach(segmentId => {
97
+ var _a;
98
+ const segment = model.getLayer(segmentId);
99
+ const overrides = (_a = segment === null || segment === void 0 ? void 0 : segment.parameters) === null || _a === void 0 ? void 0 : _a.layerOverride;
100
+ if (Array.isArray(overrides)) {
101
+ overrides.forEach((override) => {
102
+ const targetLayerId = override.targetLayer;
103
+ const targetLayer = model.getLayer(targetLayerId);
104
+ targetLayer &&
105
+ model.triggerLayerUpdate(targetLayerId, targetLayer);
106
+ });
107
+ }
108
+ });
109
+ };
110
+ }, [storyData, model, clearOverrideLayers]);
54
111
  useEffect(() => {
55
112
  const updateStory = () => {
113
+ var _a;
114
+ clearOverrideLayers();
56
115
  const { story } = model.getSelectedStory();
57
116
  setStoryData(story !== null && story !== void 0 ? story : null);
58
- // Reset to first slide when story changes
59
- setCurrentIndexDisplayed(0);
117
+ setIndex((_a = model.getCurrentSegmentIndex()) !== null && _a !== void 0 ? _a : 0);
60
118
  };
61
119
  updateStory();
62
120
  model.sharedModel.storyMapsChanged.connect(updateStory);
63
121
  return () => {
64
122
  model.sharedModel.storyMapsChanged.disconnect(updateStory);
65
123
  };
66
- }, [model]);
124
+ }, [model, setIndex, clearOverrideLayers]);
67
125
  // Prefetch image when slide changes
68
126
  useEffect(() => {
69
127
  var _a;
@@ -95,6 +153,20 @@ const StoryViewerPanel = forwardRef(({ model, isSpecta }, ref) => {
95
153
  zoomToCurrentLayer();
96
154
  }
97
155
  }, [currentStorySegmentId, model]);
156
+ // Set selected layer and apply symbology when segment changes; remove previous segment's override layers first.
157
+ useEffect(() => {
158
+ if (!(storyData === null || storyData === void 0 ? void 0 : storyData.storySegments) || currentIndexDisplayed < 0) {
159
+ return;
160
+ }
161
+ clearOverrideLayers();
162
+ setSelectedLayerByIndex(currentIndexDisplayed);
163
+ overrideSymbology(currentIndexDisplayed);
164
+ }, [
165
+ storyData,
166
+ currentIndexDisplayed,
167
+ setSelectedLayerByIndex,
168
+ clearOverrideLayers,
169
+ ]);
98
170
  // Set selected layer on initial render and when story data changes
99
171
  useEffect(() => {
100
172
  if ((storyData === null || storyData === void 0 ? void 0 : storyData.storySegments) && currentIndexDisplayed >= 0) {
@@ -129,51 +201,113 @@ const StoryViewerPanel = forwardRef(({ model, isSpecta }, ref) => {
129
201
  if (index === undefined || index === -1) {
130
202
  return;
131
203
  }
132
- setCurrentIndexDisplayed(index);
204
+ setIndex(index);
133
205
  };
206
+ // ! TODO really only want to connect this un unguided mode
134
207
  model.sharedModel.awareness.on('change', handleSelectedStorySegmentChange);
135
208
  return () => {
136
209
  model.sharedModel.awareness.off('change', handleSelectedStorySegmentChange);
137
210
  };
138
- }, [model, storyData]);
211
+ }, [model, storyData, setIndex]);
212
+ // Apply symbology overrides for the segment at the given index
213
+ const overrideSymbology = (index) => {
214
+ var _a;
215
+ if (index < 0 || !storySegments[index]) {
216
+ return;
217
+ }
218
+ const segment = storySegments[index];
219
+ const layerOverrides = (_a = segment.parameters) === null || _a === void 0 ? void 0 : _a.layerOverride;
220
+ if (!Array.isArray(layerOverrides)) {
221
+ return;
222
+ }
223
+ // Apply all symbology overrides for this segment
224
+ layerOverrides.forEach(override => {
225
+ const { color, opacity, symbologyState, targetLayer: targetLayerId, visible, } = override;
226
+ if (!targetLayerId) {
227
+ return;
228
+ }
229
+ overrideLayerEntriesRef.current.push({
230
+ layerId: targetLayerId,
231
+ action: 'restore',
232
+ });
233
+ const targetLayer = model.getLayer(targetLayerId);
234
+ if (targetLayer === null || targetLayer === void 0 ? void 0 : targetLayer.parameters) {
235
+ if (symbologyState !== undefined) {
236
+ targetLayer.parameters.symbologyState = symbologyState;
237
+ }
238
+ if (color !== undefined) {
239
+ targetLayer.parameters.color = color;
240
+ }
241
+ if (opacity !== undefined) {
242
+ targetLayer.parameters.opacity = opacity;
243
+ }
244
+ if (visible !== undefined) {
245
+ targetLayer.visible = visible;
246
+ }
247
+ // Heatmaps are actually a different layer, not just symbology
248
+ // so they need special handling
249
+ if ((symbologyState === null || symbologyState === void 0 ? void 0 : symbologyState.renderType) === 'Heatmap') {
250
+ targetLayer.type = 'HeatmapLayer';
251
+ if (addLayer) {
252
+ const newId = UUID.uuid4();
253
+ addLayer(newId, targetLayer, 100);
254
+ overrideLayerEntriesRef.current.push({
255
+ layerId: newId,
256
+ action: 'remove',
257
+ });
258
+ }
259
+ }
260
+ else {
261
+ model.triggerLayerUpdate(targetLayerId, targetLayer);
262
+ }
263
+ }
264
+ });
265
+ };
139
266
  const handlePrev = useCallback(() => {
140
- if (currentIndexDisplayed > 0) {
141
- const newIndex = currentIndexDisplayed - 1;
142
- setCurrentIndexDisplayed(newIndex);
267
+ if (hasPrev) {
268
+ setIndex(currentIndexDisplayed - 1);
143
269
  }
144
- }, [currentIndexDisplayed]);
270
+ }, [currentIndexDisplayed, setIndex]);
145
271
  const handleNext = useCallback(() => {
146
- if (currentIndexDisplayed < storySegments.length - 1) {
147
- const newIndex = currentIndexDisplayed + 1;
148
- setCurrentIndexDisplayed(newIndex);
272
+ if (hasNext) {
273
+ setIndex(currentIndexDisplayed + 1);
149
274
  }
150
- }, [currentIndexDisplayed, storySegments.length]);
151
- // Expose methods via ref for parent component to use
152
- useImperativeHandle(ref, () => ({
153
- handlePrev,
154
- handleNext,
155
- canNavigate: isSpecta,
156
- }), [handlePrev, handleNext, storyData, isSpecta]);
275
+ }, [currentIndexDisplayed, storySegments.length, setIndex]);
157
276
  if (!storyData || ((_c = storyData === null || storyData === void 0 ? void 0 : storyData.storySegments) === null || _c === void 0 ? void 0 : _c.length) === 0) {
158
277
  return (React.createElement("div", { style: { padding: '1rem' } },
159
278
  React.createElement("p", null, "No Segments available. Add one using the Add Layer menu.")));
160
279
  }
161
- const navProps = {
280
+ const storyNavBarProps = {
162
281
  onPrev: handlePrev,
163
282
  onNext: handleNext,
164
- hasPrev: currentIndexDisplayed > 0,
165
- hasNext: currentIndexDisplayed < storySegments.length - 1,
283
+ hasPrev,
284
+ hasNext,
166
285
  };
286
+ // Expose methods via ref for parent component to use
287
+ useImperativeHandle(ref, () => ({
288
+ handlePrev,
289
+ handleNext,
290
+ canNavigate: isSpecta,
291
+ }), [handlePrev, handleNext, storyData, isSpecta]);
292
+ const hasImage = !!(((_d = activeSlide === null || activeSlide === void 0 ? void 0 : activeSlide.content) === null || _d === void 0 ? void 0 : _d.image) && imageLoaded);
293
+ const storyType = (_e = storyData.storyType) !== null && _e !== void 0 ? _e : 'guided';
294
+ const navPlacement = getStoryNavPlacement(isSpecta, hasImage, storyType, isMobile);
295
+ const navSlot = navPlacement !== null ? (React.createElement(StoryNavBar, Object.assign({ placement: navPlacement }, storyNavBarProps))) : null;
167
296
  // Get transition time from current segment, default to 0.3s
168
- const transitionTime = (_e = (_d = activeSlide === null || activeSlide === void 0 ? void 0 : activeSlide.transition) === null || _d === void 0 ? void 0 : _d.time) !== null && _e !== void 0 ? _e : 0.3;
169
- return (React.createElement("div", { ref: panelRef, className: `jgis-story-viewer-panel ${isSpecta ? 'jgis-story-viewer-panel-specta-mod' : ''}` },
297
+ const transitionTime = (_g = (_f = activeSlide === null || activeSlide === void 0 ? void 0 : activeSlide.transition) === null || _f === void 0 ? void 0 : _f.time) !== null && _g !== void 0 ? _g : 0.3;
298
+ return (React.createElement("div", { ref: panelRef, className: cn('jgis-story-viewer-panel', className), id: "jgis-story-segment-panel" },
170
299
  React.createElement("div", { key: currentIndexDisplayed, className: "jgis-story-segment-container", style: {
171
300
  animationDuration: `${transitionTime}s`,
172
301
  } },
173
- React.createElement("h1", { className: "jgis-story-viewer-title" }, layerName !== null && layerName !== void 0 ? layerName : `Slide ${currentIndexDisplayed + 1}`),
174
- ((_f = activeSlide === null || activeSlide === void 0 ? void 0 : activeSlide.content) === null || _f === void 0 ? void 0 : _f.image) && imageLoaded ? (React.createElement(StoryImageSection, Object.assign({ imageUrl: activeSlide.content.image, imageLoaded: imageLoaded, layerName: layerName !== null && layerName !== void 0 ? layerName : '', slideNumber: currentIndexDisplayed, isSpecta: isSpecta, storyType: (_g = storyData.storyType) !== null && _g !== void 0 ? _g : 'guided' }, navProps))) : (React.createElement(StoryTitleSection, Object.assign({ title: (_h = storyData.title) !== null && _h !== void 0 ? _h : '', isSpecta: isSpecta, storyType: (_j = storyData.storyType) !== null && _j !== void 0 ? _j : 'guided' }, navProps))),
175
- React.createElement(StorySubtitleSection, Object.assign({ title: (_l = (_k = activeSlide === null || activeSlide === void 0 ? void 0 : activeSlide.content) === null || _k === void 0 ? void 0 : _k.title) !== null && _l !== void 0 ? _l : '', isSpecta: isSpecta }, navProps)),
176
- React.createElement(StoryContentSection, { markdown: (_o = (_m = activeSlide === null || activeSlide === void 0 ? void 0 : activeSlide.content) === null || _m === void 0 ? void 0 : _m.markdown) !== null && _o !== void 0 ? _o : '' }))));
302
+ React.createElement("div", { id: "jgis-story-segment-header" },
303
+ React.createElement("h1", { className: "jgis-story-viewer-title" }, layerName !== null && layerName !== void 0 ? layerName : `Slide ${currentIndexDisplayed + 1}`),
304
+ ((_h = activeSlide === null || activeSlide === void 0 ? void 0 : activeSlide.content) === null || _h === void 0 ? void 0 : _h.image) && imageLoaded ? (React.createElement(StoryImageSection, { imageUrl: activeSlide.content.image, imageLoaded: imageLoaded, layerName: layerName !== null && layerName !== void 0 ? layerName : '', slideNumber: currentIndexDisplayed, navSlot: navPlacement === 'over-image' ? navSlot : null })) : (React.createElement(StoryTitleSection, { title: (_j = storyData.title) !== null && _j !== void 0 ? _j : '', navSlot: navPlacement === 'below-title' ? navSlot : null })),
305
+ React.createElement(StorySubtitleSection, { title: (_l = (_k = activeSlide === null || activeSlide === void 0 ? void 0 : activeSlide.content) === null || _k === void 0 ? void 0 : _k.title) !== null && _l !== void 0 ? _l : '', navSlot: navPlacement === 'subtitle-specta' ||
306
+ navPlacement === 'subtitle-specta-mobile'
307
+ ? navSlot
308
+ : null })),
309
+ React.createElement("div", { id: "jgis-story-segment-content" },
310
+ React.createElement(StoryContentSection, { markdown: (_o = (_m = activeSlide === null || activeSlide === void 0 ? void 0 : activeSlide.content) === null || _m === void 0 ? void 0 : _m.markdown) !== null && _o !== void 0 ? _o : '' })))));
177
311
  });
178
312
  StoryViewerPanel.displayName = 'StoryViewerPanel';
179
313
  export default StoryViewerPanel;
@@ -4,12 +4,7 @@ interface IStoryImageSectionProps {
4
4
  imageLoaded: boolean;
5
5
  layerName: string;
6
6
  slideNumber: number;
7
- isSpecta: boolean;
8
- storyType: string;
9
- onPrev: () => void;
10
- onNext: () => void;
11
- hasPrev: boolean;
12
- hasNext: boolean;
7
+ navSlot?: React.ReactNode;
13
8
  }
14
- declare function StoryImageSection({ imageUrl, imageLoaded, layerName, slideNumber, isSpecta, storyType, onPrev, onNext, hasPrev, hasNext, }: IStoryImageSectionProps): React.JSX.Element | null;
9
+ declare function StoryImageSection({ imageUrl, imageLoaded, layerName, slideNumber, navSlot, }: IStoryImageSectionProps): React.JSX.Element | null;
15
10
  export default StoryImageSection;
@@ -1,13 +1,11 @@
1
1
  import React from 'react';
2
- import StoryNavBar from '../StoryNavBar';
3
- function StoryImageSection({ imageUrl, imageLoaded, layerName, slideNumber, isSpecta, storyType, onPrev, onNext, hasPrev, hasNext, }) {
2
+ function StoryImageSection({ imageUrl, imageLoaded, layerName, slideNumber, navSlot, }) {
4
3
  if (!imageLoaded) {
5
4
  return null;
6
5
  }
7
6
  return (React.createElement("div", { className: "jgis-story-viewer-image-section" },
8
7
  React.createElement("div", { className: "jgis-story-viewer-image-container" },
9
8
  React.createElement("img", { src: imageUrl, alt: "Story map image", className: "jgis-story-viewer-image" }),
10
- !isSpecta && storyType === 'guided' && (React.createElement("div", { className: "jgis-story-viewer-nav-container" },
11
- React.createElement(StoryNavBar, { onPrev: onPrev, onNext: onNext, hasPrev: hasPrev, hasNext: hasNext, isSpecta: isSpecta }))))));
9
+ navSlot)));
12
10
  }
13
11
  export default StoryImageSection;
@@ -1,11 +1,7 @@
1
1
  import React from 'react';
2
2
  interface IStorySubtitleSectionProps {
3
3
  title: string;
4
- isSpecta: boolean;
5
- onPrev: () => void;
6
- onNext: () => void;
7
- hasPrev: boolean;
8
- hasNext: boolean;
4
+ navSlot?: React.ReactNode;
9
5
  }
10
- declare function StorySubtitleSection({ title, isSpecta, onPrev, onNext, hasPrev, hasNext, }: IStorySubtitleSectionProps): React.JSX.Element;
6
+ declare function StorySubtitleSection({ title, navSlot }: IStorySubtitleSectionProps): React.JSX.Element;
11
7
  export default StorySubtitleSection;
@@ -1,9 +1,7 @@
1
1
  import React from 'react';
2
- import StoryNavBar from '../StoryNavBar';
3
- function StorySubtitleSection({ title, isSpecta, onPrev, onNext, hasPrev, hasNext, }) {
2
+ function StorySubtitleSection({ title, navSlot }) {
4
3
  return (React.createElement("div", { className: "jgis-story-viewer-subtitle-container" },
5
4
  React.createElement("h2", { className: "jgis-story-viewer-subtitle" }, title || 'Slide Title'),
6
- isSpecta && (React.createElement("div", { className: "jgis-story-viewer-nav-container-specta-mod" },
7
- React.createElement(StoryNavBar, { onPrev: onPrev, onNext: onNext, hasPrev: hasPrev, hasNext: hasNext, isSpecta: isSpecta })))));
5
+ navSlot));
8
6
  }
9
7
  export default StorySubtitleSection;
@@ -1,12 +1,7 @@
1
1
  import React from 'react';
2
2
  interface IStoryTitleSectionProps {
3
3
  title: string;
4
- isSpecta: boolean;
5
- storyType: string;
6
- onPrev: () => void;
7
- onNext: () => void;
8
- hasPrev: boolean;
9
- hasNext: boolean;
4
+ navSlot?: React.ReactNode;
10
5
  }
11
- declare function StoryTitleSection({ title, isSpecta, storyType, onPrev, onNext, hasPrev, hasNext, }: IStoryTitleSectionProps): React.JSX.Element;
6
+ declare function StoryTitleSection({ title, navSlot }: IStoryTitleSectionProps): React.JSX.Element;
12
7
  export default StoryTitleSection;
@@ -1,8 +1,7 @@
1
1
  import React from 'react';
2
- import StoryNavBar from '../StoryNavBar';
3
- function StoryTitleSection({ title, isSpecta, storyType, onPrev, onNext, hasPrev, hasNext, }) {
2
+ function StoryTitleSection({ title, navSlot }) {
4
3
  return (React.createElement(React.Fragment, null,
5
4
  React.createElement("h1", { className: "jgis-story-viewer-title" }, title),
6
- !isSpecta && storyType === 'guided' && (React.createElement(StoryNavBar, { onPrev: onPrev, onNext: onNext, hasPrev: hasPrev, hasNext: hasNext, isSpecta: isSpecta }))));
5
+ navSlot));
7
6
  }
8
7
  export default StoryTitleSection;
@@ -9,12 +9,12 @@ var __rest = (this && this.__rest) || function (s, e) {
9
9
  }
10
10
  return t;
11
11
  };
12
- import { Slot } from '@radix-ui/react-slot';
12
+ import { Slot } from 'radix-ui';
13
13
  import * as React from 'react';
14
14
  import { cn } from './utils';
15
15
  const Button = React.forwardRef((_a, ref) => {
16
16
  var { variant, className, size, asChild = false } = _a, props = __rest(_a, ["variant", "className", "size", "asChild"]);
17
- const Comp = asChild ? Slot : 'button';
17
+ const Comp = asChild ? Slot.Root : 'button';
18
18
  return (React.createElement(Comp, Object.assign({ "data-size": size, "data-variant": variant, className: cn('jgis-button', className), ref: ref }, props)));
19
19
  });
20
20
  Button.displayName = 'Button';
@@ -1,4 +1,3 @@
1
- 'use client';
2
1
  var __rest = (this && this.__rest) || function (s, e) {
3
2
  var t = {};
4
3
  for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
@@ -1,4 +1,4 @@
1
- import * as CheckboxPrimitive from '@radix-ui/react-checkbox';
1
+ import { Checkbox as CheckboxPrimitive } from 'radix-ui';
2
2
  import * as React from 'react';
3
3
  declare const Checkbox: React.ForwardRefExoticComponent<Omit<CheckboxPrimitive.CheckboxProps & React.RefAttributes<HTMLButtonElement>, "ref"> & React.RefAttributes<HTMLButtonElement>>;
4
4
  export default Checkbox;
@@ -9,8 +9,8 @@ var __rest = (this && this.__rest) || function (s, e) {
9
9
  }
10
10
  return t;
11
11
  };
12
- import * as CheckboxPrimitive from '@radix-ui/react-checkbox';
13
12
  import { Check } from 'lucide-react';
13
+ import { Checkbox as CheckboxPrimitive } from 'radix-ui';
14
14
  import * as React from 'react';
15
15
  const Checkbox = React.forwardRef((_a, ref) => {
16
16
  var props = __rest(_a, []);
@@ -1,4 +1,4 @@
1
- import * as DialogPrimitive from '@radix-ui/react-dialog';
1
+ import { Dialog as DialogPrimitive } from 'radix-ui';
2
2
  import * as React from 'react';
3
3
  declare function Dialog({ ...props }: React.ComponentProps<typeof DialogPrimitive.Root>): React.JSX.Element;
4
4
  declare function DialogTrigger({ ...props }: React.ComponentProps<typeof DialogPrimitive.Trigger>): React.JSX.Element;
@@ -9,8 +9,8 @@ var __rest = (this && this.__rest) || function (s, e) {
9
9
  }
10
10
  return t;
11
11
  };
12
- import * as DialogPrimitive from '@radix-ui/react-dialog';
13
12
  import { XIcon } from 'lucide-react';
13
+ import { Dialog as DialogPrimitive } from 'radix-ui';
14
14
  import * as React from 'react';
15
15
  import { cn } from './utils';
16
16
  function Dialog(_a) {
@@ -0,0 +1,13 @@
1
+ import * as React from 'react';
2
+ import { Drawer as DrawerPrimitive } from 'vaul';
3
+ declare function Drawer({ ...props }: React.ComponentProps<typeof DrawerPrimitive.Root>): React.JSX.Element;
4
+ declare function DrawerTrigger({ ...props }: React.ComponentProps<typeof DrawerPrimitive.Trigger>): React.JSX.Element;
5
+ declare function DrawerPortal({ ...props }: React.ComponentProps<typeof DrawerPrimitive.Portal>): React.JSX.Element;
6
+ declare function DrawerClose({ ...props }: React.ComponentProps<typeof DrawerPrimitive.Close>): React.JSX.Element;
7
+ declare function DrawerOverlay({ className, ...props }: React.ComponentProps<typeof DrawerPrimitive.Overlay>): React.JSX.Element;
8
+ declare function DrawerContent({ className, children, ...props }: React.ComponentProps<typeof DrawerPrimitive.Content>): React.JSX.Element;
9
+ declare function DrawerHeader({ className, ...props }: React.ComponentProps<'div'>): React.JSX.Element;
10
+ declare function DrawerFooter({ className, ...props }: React.ComponentProps<'div'>): React.JSX.Element;
11
+ declare function DrawerTitle({ className, ...props }: React.ComponentProps<typeof DrawerPrimitive.Title>): React.JSX.Element;
12
+ declare function DrawerDescription({ className, ...props }: React.ComponentProps<typeof DrawerPrimitive.Description>): React.JSX.Element;
13
+ export { Drawer, DrawerPortal, DrawerOverlay, DrawerTrigger, DrawerClose, DrawerContent, DrawerHeader, DrawerFooter, DrawerTitle, DrawerDescription, };