@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.
- package/lib/commands/index.js +2 -6
- package/lib/dialogs/layerBrowserDialog.d.ts +3 -3
- package/lib/dialogs/layerBrowserDialog.js +9 -10
- package/lib/dialogs/symbology/hooks/useEffectiveSymbologyParams.d.ts +16 -0
- package/lib/dialogs/symbology/hooks/useEffectiveSymbologyParams.js +24 -0
- package/lib/dialogs/symbology/hooks/useOkSignal.d.ts +6 -0
- package/lib/dialogs/symbology/hooks/useOkSignal.js +25 -0
- package/lib/dialogs/symbology/symbologyDialog.d.ts +4 -2
- package/lib/dialogs/symbology/symbologyDialog.js +6 -10
- package/lib/dialogs/symbology/symbologyUtils.d.ts +25 -2
- package/lib/dialogs/symbology/symbologyUtils.js +74 -4
- package/lib/dialogs/symbology/tiff_layer/TiffRendering.js +3 -3
- package/lib/dialogs/symbology/tiff_layer/types/MultibandColor.js +31 -34
- package/lib/dialogs/symbology/tiff_layer/types/SingleBandPseudoColor.js +68 -62
- package/lib/dialogs/symbology/vector_layer/VectorRendering.js +33 -21
- package/lib/dialogs/symbology/vector_layer/types/Canonical.js +23 -24
- package/lib/dialogs/symbology/vector_layer/types/Categorized.js +48 -49
- package/lib/dialogs/symbology/vector_layer/types/Graduated.js +53 -62
- package/lib/dialogs/symbology/vector_layer/types/Heatmap.js +35 -34
- package/lib/dialogs/symbology/vector_layer/types/SimpleSymbol.js +45 -47
- package/lib/formbuilder/objectform/StoryEditorForm.js +1 -18
- package/lib/formbuilder/objectform/baseform.d.ts +6 -0
- package/lib/formbuilder/objectform/baseform.js +21 -38
- package/lib/formbuilder/objectform/components/LayerSelect.d.ts +7 -0
- package/lib/formbuilder/objectform/components/LayerSelect.js +43 -0
- package/lib/formbuilder/objectform/components/OpacitySlider.d.ts +4 -0
- package/lib/formbuilder/objectform/components/OpacitySlider.js +40 -0
- package/lib/formbuilder/objectform/components/SegmentFormSymbology.d.ts +3 -0
- package/lib/formbuilder/objectform/components/SegmentFormSymbology.js +59 -0
- package/lib/formbuilder/objectform/layer/storySegmentLayerForm.d.ts +2 -2
- package/lib/formbuilder/objectform/layer/storySegmentLayerForm.js +19 -0
- package/lib/mainview/mainView.d.ts +8 -2
- package/lib/mainview/mainView.js +41 -7
- package/lib/mainview/mainviewwidget.js +2 -2
- package/lib/panelview/leftpanel.d.ts +2 -1
- package/lib/panelview/leftpanel.js +28 -20
- package/lib/panelview/rightpanel.d.ts +4 -1
- package/lib/panelview/rightpanel.js +14 -20
- package/lib/panelview/story-maps/MobileSpectaPanel.d.ts +7 -0
- package/lib/panelview/story-maps/MobileSpectaPanel.js +114 -0
- package/lib/panelview/story-maps/StoryNavBar.d.ts +3 -2
- package/lib/panelview/story-maps/StoryNavBar.js +18 -6
- package/lib/panelview/story-maps/StoryViewerPanel.d.ts +13 -1
- package/lib/panelview/story-maps/StoryViewerPanel.js +168 -34
- package/lib/panelview/story-maps/components/StoryImageSection.d.ts +2 -7
- package/lib/panelview/story-maps/components/StoryImageSection.js +2 -4
- package/lib/panelview/story-maps/components/StorySubtitleSection.d.ts +2 -6
- package/lib/panelview/story-maps/components/StorySubtitleSection.js +2 -4
- package/lib/panelview/story-maps/components/StoryTitleSection.d.ts +2 -7
- package/lib/panelview/story-maps/components/StoryTitleSection.js +2 -3
- package/lib/shared/components/Button.js +2 -2
- package/lib/shared/components/Calendar.js +0 -1
- package/lib/shared/components/Checkbox.d.ts +1 -1
- package/lib/shared/components/Checkbox.js +1 -1
- package/lib/shared/components/Dialog.d.ts +1 -1
- package/lib/shared/components/Dialog.js +1 -1
- package/lib/shared/components/Drawer.d.ts +13 -0
- package/lib/shared/components/Drawer.js +59 -0
- package/lib/shared/components/DropdownMenu.d.ts +1 -1
- package/lib/shared/components/DropdownMenu.js +1 -1
- package/lib/shared/components/Popover.d.ts +1 -1
- package/lib/shared/components/Popover.js +1 -1
- package/lib/shared/components/RadioGroup.d.ts +1 -1
- package/lib/shared/components/RadioGroup.js +1 -1
- package/lib/shared/components/Sheet.d.ts +15 -0
- package/lib/shared/components/Sheet.js +64 -0
- package/lib/shared/components/Switch.d.ts +1 -1
- package/lib/shared/components/Switch.js +1 -1
- package/lib/shared/components/Tabs.d.ts +1 -1
- package/lib/shared/components/Tabs.js +1 -1
- package/lib/shared/components/ToggleGroup.d.ts +1 -1
- package/lib/shared/components/ToggleGroup.js +1 -1
- package/lib/shared/hooks/useLatest.d.ts +1 -0
- package/lib/shared/hooks/useLatest.js +8 -0
- package/lib/shared/hooks/useMediaQuery.d.ts +9 -0
- package/lib/shared/hooks/useMediaQuery.js +32 -0
- package/lib/tools.js +21 -34
- package/lib/types.d.ts +1 -0
- package/lib/types.js +6 -1
- package/package.json +5 -12
- package/style/base.css +15 -0
- package/style/shared/drawer.css +154 -0
- package/style/shared/sheet.css +258 -0
- 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,
|
|
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,
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
141
|
-
|
|
142
|
-
setCurrentIndexDisplayed(newIndex);
|
|
267
|
+
if (hasPrev) {
|
|
268
|
+
setIndex(currentIndexDisplayed - 1);
|
|
143
269
|
}
|
|
144
|
-
}, [currentIndexDisplayed]);
|
|
270
|
+
}, [currentIndexDisplayed, setIndex]);
|
|
145
271
|
const handleNext = useCallback(() => {
|
|
146
|
-
if (
|
|
147
|
-
|
|
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
|
|
280
|
+
const storyNavBarProps = {
|
|
162
281
|
onPrev: handlePrev,
|
|
163
282
|
onNext: handleNext,
|
|
164
|
-
hasPrev
|
|
165
|
-
hasNext
|
|
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 = (
|
|
169
|
-
return (React.createElement("div", { ref: panelRef, className:
|
|
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("
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
5
|
-
onPrev: () => void;
|
|
6
|
-
onNext: () => void;
|
|
7
|
-
hasPrev: boolean;
|
|
8
|
-
hasNext: boolean;
|
|
4
|
+
navSlot?: React.ReactNode;
|
|
9
5
|
}
|
|
10
|
-
declare function StorySubtitleSection({ title,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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 '
|
|
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,4 @@
|
|
|
1
|
-
import
|
|
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
|
|
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, };
|