@jupytergis/base 0.10.1 → 0.12.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 (120) hide show
  1. package/lib/commands/BaseCommandIDs.d.ts +2 -0
  2. package/lib/commands/BaseCommandIDs.js +3 -0
  3. package/lib/commands/index.js +66 -0
  4. package/lib/constants.js +4 -0
  5. package/lib/dialogs/symbology/hooks/useGetBandInfo.d.ts +0 -6
  6. package/lib/dialogs/symbology/hooks/useGetBandInfo.js +2 -2
  7. package/lib/dialogs/symbology/tiff_layer/types/MultibandColor.js +4 -4
  8. package/lib/dialogs/symbology/vector_layer/types/Categorized.js +1 -5
  9. package/lib/formbuilder/formselectors.js +5 -1
  10. package/lib/formbuilder/objectform/StoryEditorForm.d.ts +9 -0
  11. package/lib/formbuilder/objectform/StoryEditorForm.js +16 -0
  12. package/lib/formbuilder/objectform/components/StorySegmentReset.d.ts +8 -0
  13. package/lib/formbuilder/objectform/components/StorySegmentReset.js +24 -0
  14. package/lib/formbuilder/objectform/layer/index.d.ts +1 -0
  15. package/lib/formbuilder/objectform/layer/index.js +1 -0
  16. package/lib/formbuilder/objectform/layer/storySegmentLayerForm.d.ts +5 -0
  17. package/lib/formbuilder/objectform/layer/storySegmentLayerForm.js +32 -0
  18. package/lib/mainview/mainView.d.ts +18 -0
  19. package/lib/mainview/mainView.js +293 -14
  20. package/lib/panelview/components/layers.d.ts +2 -1
  21. package/lib/panelview/components/layers.js +31 -23
  22. package/lib/panelview/{components/filter-panel → filter-panel}/Filter.js +1 -1
  23. package/lib/panelview/leftpanel.js +89 -7
  24. package/lib/panelview/rightpanel.d.ts +2 -0
  25. package/lib/panelview/rightpanel.js +41 -4
  26. package/lib/panelview/story-maps/PreviewModeSwitch.d.ts +7 -0
  27. package/lib/panelview/story-maps/PreviewModeSwitch.js +13 -0
  28. package/lib/panelview/story-maps/StoryEditorPanel.d.ts +9 -0
  29. package/lib/panelview/story-maps/StoryEditorPanel.js +34 -0
  30. package/lib/panelview/story-maps/StoryNavBar.d.ts +10 -0
  31. package/lib/panelview/story-maps/StoryNavBar.js +11 -0
  32. package/lib/panelview/story-maps/StoryViewerPanel.d.ts +13 -0
  33. package/lib/panelview/story-maps/StoryViewerPanel.js +179 -0
  34. package/lib/panelview/story-maps/components/StoryContentSection.d.ts +6 -0
  35. package/lib/panelview/story-maps/components/StoryContentSection.js +10 -0
  36. package/lib/panelview/story-maps/components/StoryImageSection.d.ts +15 -0
  37. package/lib/panelview/story-maps/components/StoryImageSection.js +13 -0
  38. package/lib/panelview/story-maps/components/StorySubtitleSection.d.ts +11 -0
  39. package/lib/panelview/story-maps/components/StorySubtitleSection.js +9 -0
  40. package/lib/panelview/story-maps/components/StoryTitleSection.d.ts +12 -0
  41. package/lib/panelview/story-maps/components/StoryTitleSection.js +8 -0
  42. package/lib/shared/components/Calendar.d.ts +1 -1
  43. package/lib/shared/components/Combobox.d.ts +21 -0
  44. package/lib/shared/components/Combobox.js +32 -0
  45. package/lib/shared/components/Command.d.ts +18 -0
  46. package/lib/shared/components/Command.js +60 -0
  47. package/lib/shared/components/Dialog.d.ts +15 -0
  48. package/lib/shared/components/Dialog.js +62 -0
  49. package/lib/shared/components/Input.d.ts +3 -0
  50. package/lib/shared/components/Input.js +18 -0
  51. package/lib/shared/components/Pagination.js +3 -2
  52. package/lib/shared/components/RadioGroup.d.ts +5 -0
  53. package/lib/shared/components/RadioGroup.js +26 -0
  54. package/lib/shared/components/Select.d.ts +19 -0
  55. package/lib/shared/components/Select.js +28 -0
  56. package/lib/shared/components/SingleDatePicker.d.ts +11 -0
  57. package/lib/shared/components/SingleDatePicker.js +16 -0
  58. package/lib/shared/components/Switch.d.ts +4 -0
  59. package/lib/shared/components/Switch.js +20 -0
  60. package/lib/stacBrowser/components/StacPanel.d.ts +9 -1
  61. package/lib/stacBrowser/components/StacPanel.js +53 -9
  62. package/lib/stacBrowser/components/filter-extension/QueryableComboBox.d.ts +9 -0
  63. package/lib/stacBrowser/components/filter-extension/QueryableComboBox.js +179 -0
  64. package/lib/stacBrowser/components/filter-extension/QueryableRow.d.ts +16 -0
  65. package/lib/stacBrowser/components/filter-extension/QueryableRow.js +16 -0
  66. package/lib/stacBrowser/components/filter-extension/StacFilterExtensionPanel.d.ts +7 -0
  67. package/lib/stacBrowser/components/filter-extension/StacFilterExtensionPanel.js +49 -0
  68. package/lib/stacBrowser/components/filter-extension/StacQueryableFilters.d.ts +11 -0
  69. package/lib/stacBrowser/components/filter-extension/StacQueryableFilters.js +19 -0
  70. package/lib/stacBrowser/components/{StacFilterSection.d.ts → geodes/StacFilterSection.d.ts} +1 -1
  71. package/lib/stacBrowser/components/{StacFilterSection.js → geodes/StacFilterSection.js} +3 -3
  72. package/lib/stacBrowser/components/geodes/StacGeodesFilterPanel.d.ts +7 -0
  73. package/lib/stacBrowser/components/geodes/StacGeodesFilterPanel.js +69 -0
  74. package/lib/stacBrowser/components/shared/StacPanelResults.d.ts +3 -0
  75. package/lib/stacBrowser/components/shared/StacPanelResults.js +68 -0
  76. package/lib/stacBrowser/components/shared/StacSpatialExtent.d.ts +8 -0
  77. package/lib/stacBrowser/components/shared/StacSpatialExtent.js +10 -0
  78. package/lib/stacBrowser/components/shared/StacTemporalExtent.d.ts +9 -0
  79. package/lib/stacBrowser/components/shared/StacTemporalExtent.js +9 -0
  80. package/lib/stacBrowser/context/StacResultsContext.d.ts +33 -0
  81. package/lib/stacBrowser/context/StacResultsContext.js +269 -0
  82. package/lib/stacBrowser/hooks/useGeodesSearch.d.ts +24 -0
  83. package/lib/stacBrowser/hooks/useGeodesSearch.js +178 -0
  84. package/lib/stacBrowser/hooks/useStacFilterExtension.d.ts +30 -0
  85. package/lib/stacBrowser/hooks/useStacFilterExtension.js +262 -0
  86. package/lib/stacBrowser/hooks/useStacSearch.d.ts +5 -16
  87. package/lib/stacBrowser/hooks/useStacSearch.js +30 -184
  88. package/lib/stacBrowser/types/types.d.ts +86 -3
  89. package/lib/toolbar/widget.d.ts +15 -0
  90. package/lib/toolbar/widget.js +70 -0
  91. package/lib/tools.d.ts +0 -7
  92. package/lib/tools.js +56 -15
  93. package/package.json +8 -3
  94. package/style/base.css +42 -3
  95. package/style/leftPanel.css +18 -0
  96. package/style/shared/button.css +6 -5
  97. package/style/shared/calendar.css +7 -1
  98. package/style/shared/combobox.css +75 -0
  99. package/style/shared/command.css +178 -0
  100. package/style/shared/dialog.css +177 -0
  101. package/style/shared/input.css +59 -0
  102. package/style/shared/pagination.css +1 -1
  103. package/style/shared/popover.css +1 -0
  104. package/style/shared/radioGroup.css +55 -0
  105. package/style/shared/switch.css +63 -0
  106. package/style/shared/tabs.css +4 -3
  107. package/style/shared/toggle.css +1 -1
  108. package/style/stacBrowser.css +169 -16
  109. package/style/statusBar.css +1 -0
  110. package/style/storyPanel.css +185 -0
  111. package/style/tabPanel.css +1 -88
  112. package/lib/stacBrowser/components/StacPanelFilters.d.ts +0 -14
  113. package/lib/stacBrowser/components/StacPanelFilters.js +0 -81
  114. package/lib/stacBrowser/components/StacPanelResults.d.ts +0 -13
  115. package/lib/stacBrowser/components/StacPanelResults.js +0 -48
  116. /package/lib/panelview/{components/filter-panel → filter-panel}/Filter.d.ts +0 -0
  117. /package/lib/panelview/{components/filter-panel → filter-panel}/FilterRow.d.ts +0 -0
  118. /package/lib/panelview/{components/filter-panel → filter-panel}/FilterRow.js +0 -0
  119. /package/lib/panelview/{components/identify-panel → identify-panel}/IdentifyPanel.d.ts +0 -0
  120. /package/lib/panelview/{components/identify-panel → identify-panel}/IdentifyPanel.js +0 -0
@@ -1,29 +1,109 @@
1
1
  import * as React from 'react';
2
2
  import { LayersBodyComponent } from './components/layers';
3
+ import FilterComponent from './filter-panel/Filter';
3
4
  import { PanelTabs, TabsContent, TabsList, TabsTrigger, } from '../shared/components/Tabs';
4
5
  import StacPanel from '../stacBrowser/components/StacPanel';
5
- import FilterComponent from './components/filter-panel/Filter';
6
6
  export const LeftPanel = (props) => {
7
+ var _a;
7
8
  const [settings, setSettings] = React.useState(props.model.jgisSettings);
9
+ const [options, setOptions] = React.useState(props.model.getOptions());
10
+ const storyMapPresentationMode = (_a = options.storyMapPresentationMode) !== null && _a !== void 0 ? _a : false;
11
+ const [layerTree, setLayerTree] = React.useState(props.model.getLayerTree());
8
12
  React.useEffect(() => {
9
13
  const onSettingsChanged = () => {
10
14
  setSettings(Object.assign({}, props.model.jgisSettings));
11
15
  };
16
+ const onOptionsChanged = () => {
17
+ setOptions(Object.assign({}, props.model.getOptions()));
18
+ };
19
+ const updateLayerTree = () => {
20
+ setLayerTree(props.model.getLayerTree() || []);
21
+ };
12
22
  props.model.settingsChanged.connect(onSettingsChanged);
23
+ props.model.sharedOptionsChanged.connect(onOptionsChanged);
24
+ props.model.sharedModel.layersChanged.connect(updateLayerTree);
25
+ props.model.sharedModel.layerTreeChanged.connect(updateLayerTree);
26
+ updateLayerTree();
13
27
  return () => {
14
28
  props.model.settingsChanged.disconnect(onSettingsChanged);
29
+ props.model.sharedOptionsChanged.disconnect(onOptionsChanged);
30
+ props.model.sharedModel.layersChanged.disconnect(updateLayerTree);
31
+ props.model.sharedModel.layerTreeChanged.disconnect(updateLayerTree);
15
32
  };
16
33
  }, [props.model]);
34
+ // Since story segments are technically layers they are stored in the layer tree, so we separate them
35
+ // from regular layers. Process the tree once to build both filtered and story segment trees.
36
+ const { filteredLayerTree, storySegmentLayerTree } = React.useMemo(() => {
37
+ const filtered = [];
38
+ const storySegments = [];
39
+ const processLayer = (layer) => {
40
+ if (typeof layer === 'string') {
41
+ const layerData = props.model.getLayer(layer);
42
+ const isStorySegment = (layerData === null || layerData === void 0 ? void 0 : layerData.type) === 'StorySegmentLayer';
43
+ return {
44
+ filtered: isStorySegment ? null : layer,
45
+ storySegment: isStorySegment ? layer : null,
46
+ };
47
+ }
48
+ // For layer groups, recursively process their layers
49
+ const filteredGroupLayers = [];
50
+ const storySegmentGroupLayers = [];
51
+ for (const groupLayer of layer.layers) {
52
+ const result = processLayer(groupLayer);
53
+ if (result.filtered !== null) {
54
+ filteredGroupLayers.push(result.filtered);
55
+ }
56
+ if (result.storySegment !== null) {
57
+ storySegmentGroupLayers.push(result.storySegment);
58
+ }
59
+ }
60
+ return {
61
+ filtered: filteredGroupLayers.length > 0
62
+ ? Object.assign(Object.assign({}, layer), { layers: filteredGroupLayers }) : null,
63
+ storySegment: storySegmentGroupLayers.length > 0
64
+ ? Object.assign(Object.assign({}, layer), { layers: storySegmentGroupLayers }) : null,
65
+ };
66
+ };
67
+ for (const layer of layerTree) {
68
+ const result = processLayer(layer);
69
+ if (result.filtered !== null) {
70
+ filtered.push(result.filtered);
71
+ }
72
+ if (result.storySegment !== null) {
73
+ storySegments.push(result.storySegment);
74
+ }
75
+ }
76
+ // Reverse filteredLayerTree before returning
77
+ filtered.reverse();
78
+ return {
79
+ filteredLayerTree: filtered,
80
+ storySegmentLayerTree: storySegments,
81
+ };
82
+ }, [layerTree]);
83
+ // Updates story segments array based on layer tree array
84
+ React.useEffect(() => {
85
+ const { storyId, story } = props.model.getSelectedStory();
86
+ if (!story) {
87
+ return;
88
+ }
89
+ props.model.sharedModel.updateStoryMap(storyId, Object.assign(Object.assign({}, story), { storySegments: storySegmentLayerTree }));
90
+ }, [storySegmentLayerTree]);
17
91
  const allLeftTabsDisabled = settings.layersDisabled &&
18
92
  settings.stacBrowserDisabled &&
19
- settings.filtersDisabled;
93
+ settings.filtersDisabled &&
94
+ settings.storyMapsDisabled;
20
95
  const leftPanelVisible = !settings.leftPanelDisabled && !allLeftTabsDisabled;
21
96
  const tabInfo = [
22
97
  !settings.layersDisabled ? { name: 'layers', title: 'Layers' } : false,
23
- !settings.stacBrowserDisabled
98
+ !settings.stacBrowserDisabled && !storyMapPresentationMode
24
99
  ? { name: 'stac', title: 'Stac Browser' }
25
100
  : false,
26
- !settings.filtersDisabled ? { name: 'filters', title: 'Filters' } : false,
101
+ !settings.filtersDisabled && !storyMapPresentationMode
102
+ ? { name: 'filters', title: 'Filters' }
103
+ : false,
104
+ !settings.storyMapsDisabled
105
+ ? { name: 'segments', title: 'Segments' }
106
+ : false,
27
107
  ].filter(Boolean);
28
108
  const [curTab, setCurTab] = React.useState(tabInfo.length > 0 ? tabInfo[0].name : undefined);
29
109
  return (React.createElement("div", { className: "jgis-left-panel-container", style: { display: leftPanelVisible ? 'block' : 'none' } },
@@ -37,9 +117,11 @@ export const LeftPanel = (props) => {
37
117
  }
38
118
  } }, tab.title)))),
39
119
  !settings.layersDisabled && (React.createElement(TabsContent, { value: "layers", className: "jgis-panel-tab-content jp-gis-layerPanel" },
40
- React.createElement(LayersBodyComponent, { model: props.model, commands: props.commands, state: props.state }))),
41
- !settings.stacBrowserDisabled && (React.createElement(TabsContent, { value: "stac", className: "jgis-panel-tab-content" },
120
+ React.createElement(LayersBodyComponent, { model: props.model, commands: props.commands, state: props.state, layerTree: filteredLayerTree }))),
121
+ !settings.stacBrowserDisabled && (React.createElement(TabsContent, { value: "stac", className: "jgis-panel-tab-content jgis-panel-tab-content-stac-panel" },
42
122
  React.createElement(StacPanel, { model: props.model }))),
43
123
  !settings.filtersDisabled && (React.createElement(TabsContent, { value: "filters", className: "jgis-panel-tab-content" },
44
- React.createElement(FilterComponent, { model: props.model }))))));
124
+ React.createElement(FilterComponent, { model: props.model }))),
125
+ !settings.storyMapsDisabled && (React.createElement(TabsContent, { value: "segments", className: "jgis-panel-tab-content" },
126
+ React.createElement(LayersBodyComponent, { model: props.model, commands: props.commands, state: props.state, layerTree: storySegmentLayerTree }))))));
45
127
  };
@@ -1,9 +1,11 @@
1
1
  import { IAnnotationModel, IJGISFormSchemaRegistry, IJupyterGISModel } from '@jupytergis/schema';
2
+ import { CommandRegistry } from '@lumino/commands';
2
3
  import * as React from 'react';
3
4
  interface IRightPanelProps {
4
5
  formSchemaRegistry: IJGISFormSchemaRegistry;
5
6
  annotationModel: IAnnotationModel;
6
7
  model: IJupyterGISModel;
8
+ commands: CommandRegistry;
7
9
  }
8
10
  export declare const RightPanel: React.FC<IRightPanelProps>;
9
11
  export {};
@@ -1,14 +1,34 @@
1
1
  import * as React from 'react';
2
2
  import { AnnotationsPanel } from './annotationPanel';
3
- import { IdentifyPanelComponent } from './components/identify-panel/IdentifyPanel';
3
+ import { IdentifyPanelComponent } from './identify-panel/IdentifyPanel';
4
4
  import { ObjectPropertiesReact } from './objectproperties';
5
+ import { PreviewModeSwitch } from './story-maps/PreviewModeSwitch';
6
+ import StoryEditorPanel from './story-maps/StoryEditorPanel';
7
+ import StoryViewerPanel from './story-maps/StoryViewerPanel';
5
8
  import { PanelTabs, TabsContent, TabsList, TabsTrigger, } from '../shared/components/Tabs';
6
9
  export const RightPanel = props => {
10
+ var _a;
11
+ const [editorMode, setEditorMode] = React.useState(true);
7
12
  const [settings, setSettings] = React.useState(props.model.jgisSettings);
13
+ const [storyMapPresentationMode, setStoryMapPresentationMode] = React.useState((_a = props.model.getOptions().storyMapPresentationMode) !== null && _a !== void 0 ? _a : false);
14
+ // Only show editor when not in presentation mode and editorMode is true
15
+ const showEditor = !storyMapPresentationMode && editorMode;
16
+ // Tab title: "Story Map" in presentation mode, otherwise based on editorMode
17
+ const storyPanelTitle = storyMapPresentationMode
18
+ ? 'Story Map'
19
+ : editorMode
20
+ ? 'Story Editor'
21
+ : 'Story Map';
8
22
  const tabInfo = [
9
- !settings.objectPropertiesDisabled
23
+ !settings.objectPropertiesDisabled && !storyMapPresentationMode
10
24
  ? { name: 'objectProperties', title: 'Object Properties' }
11
25
  : false,
26
+ !settings.storyMapsDisabled
27
+ ? {
28
+ name: 'storyPanel',
29
+ title: storyPanelTitle,
30
+ }
31
+ : false,
12
32
  !settings.annotationsDisabled
13
33
  ? { name: 'annotations', title: 'Annotations' }
14
34
  : false,
@@ -16,11 +36,20 @@ export const RightPanel = props => {
16
36
  ? { name: 'identifyPanel', title: 'Identified Features' }
17
37
  : false,
18
38
  ].filter(Boolean);
19
- const [curTab, setCurTab] = React.useState(tabInfo.length > 0 ? tabInfo[0].name : undefined);
39
+ const [curTab, setCurTab] = React.useState(() => {
40
+ if (storyMapPresentationMode) {
41
+ return 'storyPanel';
42
+ }
43
+ return tabInfo.length > 0 ? tabInfo[0].name : '';
44
+ });
20
45
  React.useEffect(() => {
21
46
  const onSettingsChanged = () => {
22
47
  setSettings(Object.assign({}, props.model.jgisSettings));
23
48
  };
49
+ const onOptionsChanged = () => {
50
+ const { storyMapPresentationMode } = props.model.getOptions();
51
+ setStoryMapPresentationMode(storyMapPresentationMode !== null && storyMapPresentationMode !== void 0 ? storyMapPresentationMode : false);
52
+ };
24
53
  let currentlyIdentifiedFeatures = undefined;
25
54
  const onAwerenessChanged = (_, clients) => {
26
55
  var _a;
@@ -34,9 +63,11 @@ export const RightPanel = props => {
34
63
  }
35
64
  };
36
65
  props.model.settingsChanged.connect(onSettingsChanged);
66
+ props.model.sharedOptionsChanged.connect(onOptionsChanged);
37
67
  props.model.clientStateChanged.connect(onAwerenessChanged);
38
68
  return () => {
39
69
  props.model.settingsChanged.disconnect(onSettingsChanged);
70
+ props.model.sharedOptionsChanged.disconnect(onOptionsChanged);
40
71
  props.model.clientStateChanged.disconnect(onAwerenessChanged);
41
72
  };
42
73
  }, [props.model]);
@@ -45,9 +76,12 @@ export const RightPanel = props => {
45
76
  settings.identifyDisabled;
46
77
  const rightPanelVisible = !settings.rightPanelDisabled && !allRightTabsDisabled;
47
78
  const [selectedObjectProperties, setSelectedObjectProperties] = React.useState(undefined);
79
+ const toggleEditor = () => {
80
+ setEditorMode(!editorMode);
81
+ };
48
82
  return (React.createElement("div", { className: "jgis-right-panel-container", style: { display: rightPanelVisible ? 'block' : 'none' } },
49
83
  React.createElement(PanelTabs, { className: "jgis-panel-tabs", curTab: curTab },
50
- React.createElement(TabsList, null, tabInfo.map(tab => (React.createElement(TabsTrigger, { className: "jGIS-layer-browser-category", key: tab.name, value: tab.name, onClick: () => {
84
+ React.createElement(TabsList, null, tabInfo.map(tab => (React.createElement(TabsTrigger, { className: "jGIS-layer-browser-category", key: `${tab.name}-${tab.title}`, value: tab.name, onClick: () => {
51
85
  if (curTab !== tab.name) {
52
86
  setCurTab(tab.name);
53
87
  }
@@ -57,6 +91,9 @@ export const RightPanel = props => {
57
91
  } }, tab.title)))),
58
92
  !settings.objectPropertiesDisabled && (React.createElement(TabsContent, { value: "objectProperties", className: "jgis-panel-tab-content" },
59
93
  React.createElement(ObjectPropertiesReact, { setSelectedObject: setSelectedObjectProperties, selectedObject: selectedObjectProperties, formSchemaRegistry: props.formSchemaRegistry, model: props.model }))),
94
+ !settings.storyMapsDisabled && (React.createElement(TabsContent, { value: "storyPanel", className: "jgis-panel-tab-content", style: { paddingTop: 0 } },
95
+ !storyMapPresentationMode && (React.createElement(PreviewModeSwitch, { checked: !editorMode, onCheckedChange: toggleEditor })),
96
+ showEditor ? (React.createElement(StoryEditorPanel, { model: props.model, commands: props.commands })) : (React.createElement(StoryViewerPanel, { model: props.model, isSpecta: false })))),
60
97
  !settings.annotationsDisabled && (React.createElement(TabsContent, { value: "annotations", className: "jgis-panel-tab-content" },
61
98
  React.createElement(AnnotationsPanel, { annotationModel: props.annotationModel, jgisModel: props.model }))),
62
99
  !settings.identifyDisabled && (React.createElement(TabsContent, { value: "identifyPanel", className: "jgis-panel-tab-content" },
@@ -0,0 +1,7 @@
1
+ import * as React from 'react';
2
+ interface IPreviewModeSwitchProps {
3
+ checked: boolean;
4
+ onCheckedChange: () => void;
5
+ }
6
+ export declare function PreviewModeSwitch({ checked, onCheckedChange, }: IPreviewModeSwitchProps): React.JSX.Element;
7
+ export {};
@@ -0,0 +1,13 @@
1
+ import * as React from 'react';
2
+ import { Switch } from "../../shared/components/Switch";
3
+ export function PreviewModeSwitch({ checked, onCheckedChange, }) {
4
+ return (React.createElement("div", { style: {
5
+ display: 'flex',
6
+ alignItems: 'center',
7
+ gap: '0.5rem',
8
+ padding: '1rem',
9
+ paddingBottom: '0',
10
+ } },
11
+ React.createElement("label", { htmlFor: "preview-mode-switch", style: { fontSize: '0.875rem' } }, "Preview Mode"),
12
+ React.createElement(Switch, { id: "preview-mode-switch", checked: checked, onCheckedChange: onCheckedChange })));
13
+ }
@@ -0,0 +1,9 @@
1
+ import { IJupyterGISModel } from '@jupytergis/schema';
2
+ import { CommandRegistry } from '@lumino/commands';
3
+ import React from 'react';
4
+ interface IStoryPanelProps {
5
+ model: IJupyterGISModel;
6
+ commands: CommandRegistry;
7
+ }
8
+ export declare function StoryEditorPanel({ model, commands }: IStoryPanelProps): React.JSX.Element;
9
+ export default StoryEditorPanel;
@@ -0,0 +1,34 @@
1
+ import { faLink } from '@fortawesome/free-solid-svg-icons';
2
+ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
3
+ import jgisSchema from '@jupytergis/schema/lib/schema/project/jgis.json';
4
+ import React, { useMemo } from 'react';
5
+ import { CommandIDs } from "../../constants";
6
+ import { StoryEditorPropertiesForm } from "../../formbuilder/objectform/StoryEditorForm";
7
+ import { Button } from "../../shared/components/Button";
8
+ import { deepCopy } from "../../tools";
9
+ const storyMapSchema = deepCopy(jgisSchema.definitions.jGISStoryMap);
10
+ const AddStorySegmentButton = ({ model, commands }) => (React.createElement("div", { style: { display: 'flex', justifyContent: 'center' } },
11
+ React.createElement(Button, { onClick: () => commands.execute(CommandIDs.addStorySegment) },
12
+ React.createElement(FontAwesomeIcon, { icon: faLink }),
13
+ " Add Story Segment")));
14
+ export function StoryEditorPanel({ model, commands }) {
15
+ const { storyId, story } = useMemo(() => {
16
+ return model.getSelectedStory();
17
+ }, [model, model.sharedModel.stories]);
18
+ const syncStoryData = (properties) => {
19
+ var _a;
20
+ // Preserve storySegments when updating, since the form removes it from the UI
21
+ const updatedStory = Object.assign(Object.assign(Object.assign({}, story), properties), { storySegments: (_a = story === null || story === void 0 ? void 0 : story.storySegments) !== null && _a !== void 0 ? _a : [] });
22
+ model.sharedModel.updateStoryMap(storyId, updatedStory);
23
+ };
24
+ if (!story) {
25
+ return (React.createElement("div", { style: { padding: '1rem' } },
26
+ React.createElement("p", null, "No Story Map available."),
27
+ React.createElement("p", null, "Add a Story Segment from the Add Layer menu. A segment captures the current map view. You can add markdown text and an image to each segment to tell your story."),
28
+ React.createElement(AddStorySegmentButton, { model: model, commands: commands })));
29
+ }
30
+ return (React.createElement("div", { className: "jgis-story-editor-panel" },
31
+ React.createElement(StoryEditorPropertiesForm, { formContext: "update", sourceData: story, model: model, schema: storyMapSchema, syncData: syncStoryData, filePath: model.filePath }),
32
+ React.createElement(AddStorySegmentButton, { model: model, commands: commands })));
33
+ }
34
+ export default StoryEditorPanel;
@@ -0,0 +1,10 @@
1
+ import React from 'react';
2
+ interface IStoryNavBarProps {
3
+ onPrev: () => void;
4
+ onNext: () => void;
5
+ hasPrev: boolean;
6
+ hasNext: boolean;
7
+ isSpecta: boolean;
8
+ }
9
+ declare function StoryNavBar({ onPrev, onNext, hasPrev, hasNext, isSpecta, }: IStoryNavBarProps): React.JSX.Element;
10
+ export default StoryNavBar;
@@ -0,0 +1,11 @@
1
+ import { ChevronLeft, ChevronRight } from 'lucide-react';
2
+ import React from 'react';
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))));
10
+ }
11
+ export default StoryNavBar;
@@ -0,0 +1,13 @@
1
+ import { IJupyterGISModel } from '@jupytergis/schema';
2
+ import React from 'react';
3
+ interface IStoryViewerPanelProps {
4
+ model: IJupyterGISModel;
5
+ isSpecta: boolean;
6
+ }
7
+ export interface IStoryViewerPanelHandle {
8
+ handlePrev: () => void;
9
+ handleNext: () => void;
10
+ canNavigate: boolean;
11
+ }
12
+ declare const StoryViewerPanel: React.ForwardRefExoticComponent<IStoryViewerPanelProps & React.RefAttributes<IStoryViewerPanelHandle>>;
13
+ export default StoryViewerPanel;
@@ -0,0 +1,179 @@
1
+ import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState, } from 'react';
2
+ import StoryContentSection from './components/StoryContentSection';
3
+ import StoryImageSection from './components/StoryImageSection';
4
+ import StorySubtitleSection from './components/StorySubtitleSection';
5
+ import StoryTitleSection from './components/StoryTitleSection';
6
+ const StoryViewerPanel = forwardRef(({ model, isSpecta }, ref) => {
7
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
8
+ const [currentIndexDisplayed, setCurrentIndexDisplayed] = useState(0);
9
+ const [storyData, setStoryData] = useState((_a = model.getSelectedStory().story) !== null && _a !== void 0 ? _a : null);
10
+ const [imageLoaded, setImageLoaded] = useState(false);
11
+ const panelRef = useRef(null);
12
+ // Derive story segments from story data
13
+ const storySegments = useMemo(() => {
14
+ if (!(storyData === null || storyData === void 0 ? void 0 : storyData.storySegments)) {
15
+ return [];
16
+ }
17
+ return storyData.storySegments
18
+ .map(storySegmentId => model.getLayer(storySegmentId))
19
+ .filter((layer) => layer !== undefined);
20
+ }, [storyData, model]);
21
+ // Derive current story segment from story segments and currentIndexDisplayed
22
+ const currentStorySegment = useMemo(() => {
23
+ return storySegments[currentIndexDisplayed];
24
+ }, [storySegments, currentIndexDisplayed]);
25
+ // Derive active slide and layer name from current story segment
26
+ const activeSlide = useMemo(() => {
27
+ return currentStorySegment === null || currentStorySegment === void 0 ? void 0 : currentStorySegment.parameters;
28
+ }, [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]);
33
+ // Derive story segment ID for zooming
34
+ const currentStorySegmentId = useMemo(() => {
35
+ var _a;
36
+ return (_a = storyData === null || storyData === void 0 ? void 0 : storyData.storySegments) === null || _a === void 0 ? void 0 : _a[currentIndexDisplayed];
37
+ }, [storyData, currentIndexDisplayed]);
38
+ const zoomToCurrentLayer = () => {
39
+ if (currentStorySegmentId) {
40
+ model.centerOnPosition(currentStorySegmentId);
41
+ }
42
+ };
43
+ const setSelectedLayerByIndex = useCallback((index) => {
44
+ var _a;
45
+ const storySegmentId = (_a = storyData === null || storyData === void 0 ? void 0 : storyData.storySegments) === null || _a === void 0 ? void 0 : _a[index];
46
+ if (storySegmentId) {
47
+ model.selected = {
48
+ [storySegmentId]: {
49
+ type: 'layer',
50
+ },
51
+ };
52
+ }
53
+ }, [storyData, model]);
54
+ useEffect(() => {
55
+ const updateStory = () => {
56
+ const { story } = model.getSelectedStory();
57
+ setStoryData(story !== null && story !== void 0 ? story : null);
58
+ // Reset to first slide when story changes
59
+ setCurrentIndexDisplayed(0);
60
+ };
61
+ updateStory();
62
+ model.sharedModel.storyMapsChanged.connect(updateStory);
63
+ return () => {
64
+ model.sharedModel.storyMapsChanged.disconnect(updateStory);
65
+ };
66
+ }, [model]);
67
+ // Prefetch image when slide changes
68
+ useEffect(() => {
69
+ var _a;
70
+ const imageUrl = (_a = activeSlide === null || activeSlide === void 0 ? void 0 : activeSlide.content) === null || _a === void 0 ? void 0 : _a.image;
71
+ if (!imageUrl) {
72
+ setImageLoaded(false);
73
+ return;
74
+ }
75
+ // Reset state
76
+ setImageLoaded(false);
77
+ // Preload the image
78
+ const img = new Image();
79
+ img.onload = () => {
80
+ setImageLoaded(true);
81
+ };
82
+ img.onerror = () => {
83
+ setImageLoaded(false);
84
+ };
85
+ img.src = imageUrl;
86
+ // Cleanup: abort loading if component unmounts or slide changes
87
+ return () => {
88
+ img.onload = null;
89
+ img.onerror = null;
90
+ };
91
+ }, [(_b = activeSlide === null || activeSlide === void 0 ? void 0 : activeSlide.content) === null || _b === void 0 ? void 0 : _b.image]);
92
+ // Auto-zoom when slide changes
93
+ useEffect(() => {
94
+ if (currentStorySegmentId) {
95
+ zoomToCurrentLayer();
96
+ }
97
+ }, [currentStorySegmentId, model]);
98
+ // Set selected layer on initial render and when story data changes
99
+ useEffect(() => {
100
+ if ((storyData === null || storyData === void 0 ? void 0 : storyData.storySegments) && currentIndexDisplayed >= 0) {
101
+ setSelectedLayerByIndex(currentIndexDisplayed);
102
+ }
103
+ }, [storyData, currentIndexDisplayed, setSelectedLayerByIndex]);
104
+ // Listen for layer selection changes in unguided mode
105
+ useEffect(() => {
106
+ // ! TODO this logic (getting a single selected layer) is also in the processing index.ts, move to tools
107
+ const handleSelectedStorySegmentChange = () => {
108
+ var _a, _b;
109
+ // This is just to update the displayed content
110
+ // So bail early if we don't need to do that
111
+ if (!storyData || storyData.storyType !== 'unguided') {
112
+ return;
113
+ }
114
+ const localState = model.sharedModel.awareness.getLocalState();
115
+ if (!localState || !((_a = localState['selected']) === null || _a === void 0 ? void 0 : _a.value)) {
116
+ return;
117
+ }
118
+ const selectedLayers = Object.keys(localState['selected'].value);
119
+ // Ensure only one layer is selected
120
+ if (selectedLayers.length !== 1) {
121
+ return;
122
+ }
123
+ const selectedLayerId = selectedLayers[0];
124
+ const selectedLayer = model.getLayer(selectedLayerId);
125
+ if (!selectedLayer || selectedLayer.type !== 'StorySegmentLayer') {
126
+ return;
127
+ }
128
+ const index = (_b = storyData.storySegments) === null || _b === void 0 ? void 0 : _b.indexOf(selectedLayerId);
129
+ if (index === undefined || index === -1) {
130
+ return;
131
+ }
132
+ setCurrentIndexDisplayed(index);
133
+ };
134
+ model.sharedModel.awareness.on('change', handleSelectedStorySegmentChange);
135
+ return () => {
136
+ model.sharedModel.awareness.off('change', handleSelectedStorySegmentChange);
137
+ };
138
+ }, [model, storyData]);
139
+ const handlePrev = useCallback(() => {
140
+ if (currentIndexDisplayed > 0) {
141
+ const newIndex = currentIndexDisplayed - 1;
142
+ setCurrentIndexDisplayed(newIndex);
143
+ }
144
+ }, [currentIndexDisplayed]);
145
+ const handleNext = useCallback(() => {
146
+ if (currentIndexDisplayed < storySegments.length - 1) {
147
+ const newIndex = currentIndexDisplayed + 1;
148
+ setCurrentIndexDisplayed(newIndex);
149
+ }
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]);
157
+ if (!storyData || ((_c = storyData === null || storyData === void 0 ? void 0 : storyData.storySegments) === null || _c === void 0 ? void 0 : _c.length) === 0) {
158
+ return (React.createElement("div", { style: { padding: '1rem' } },
159
+ React.createElement("p", null, "No Segments available. Add one using the Add Layer menu.")));
160
+ }
161
+ const navProps = {
162
+ onPrev: handlePrev,
163
+ onNext: handleNext,
164
+ hasPrev: currentIndexDisplayed > 0,
165
+ hasNext: currentIndexDisplayed < storySegments.length - 1,
166
+ };
167
+ // 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' : ''}` },
170
+ React.createElement("div", { key: currentIndexDisplayed, className: "jgis-story-segment-container", style: {
171
+ animationDuration: `${transitionTime}s`,
172
+ } },
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 : '' }))));
177
+ });
178
+ StoryViewerPanel.displayName = 'StoryViewerPanel';
179
+ export default StoryViewerPanel;
@@ -0,0 +1,6 @@
1
+ import React from 'react';
2
+ interface IStoryContentProps {
3
+ markdown: string;
4
+ }
5
+ declare function StoryContentSection({ markdown }: IStoryContentProps): React.JSX.Element | null;
6
+ export default StoryContentSection;
@@ -0,0 +1,10 @@
1
+ import React from 'react';
2
+ import Markdown from 'react-markdown';
3
+ function StoryContentSection({ markdown }) {
4
+ if (!markdown) {
5
+ return null;
6
+ }
7
+ return (React.createElement("div", { className: "jgis-story-viewer-content" },
8
+ React.createElement(Markdown, null, markdown)));
9
+ }
10
+ export default StoryContentSection;
@@ -0,0 +1,15 @@
1
+ import React from 'react';
2
+ interface IStoryImageSectionProps {
3
+ imageUrl: string;
4
+ imageLoaded: boolean;
5
+ layerName: string;
6
+ slideNumber: number;
7
+ isSpecta: boolean;
8
+ storyType: string;
9
+ onPrev: () => void;
10
+ onNext: () => void;
11
+ hasPrev: boolean;
12
+ hasNext: boolean;
13
+ }
14
+ declare function StoryImageSection({ imageUrl, imageLoaded, layerName, slideNumber, isSpecta, storyType, onPrev, onNext, hasPrev, hasNext, }: IStoryImageSectionProps): React.JSX.Element | null;
15
+ export default StoryImageSection;
@@ -0,0 +1,13 @@
1
+ import React from 'react';
2
+ import StoryNavBar from '../StoryNavBar';
3
+ function StoryImageSection({ imageUrl, imageLoaded, layerName, slideNumber, isSpecta, storyType, onPrev, onNext, hasPrev, hasNext, }) {
4
+ if (!imageLoaded) {
5
+ return null;
6
+ }
7
+ return (React.createElement("div", { className: "jgis-story-viewer-image-section" },
8
+ React.createElement("div", { className: "jgis-story-viewer-image-container" },
9
+ 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 }))))));
12
+ }
13
+ export default StoryImageSection;
@@ -0,0 +1,11 @@
1
+ import React from 'react';
2
+ interface IStorySubtitleSectionProps {
3
+ title: string;
4
+ isSpecta: boolean;
5
+ onPrev: () => void;
6
+ onNext: () => void;
7
+ hasPrev: boolean;
8
+ hasNext: boolean;
9
+ }
10
+ declare function StorySubtitleSection({ title, isSpecta, onPrev, onNext, hasPrev, hasNext, }: IStorySubtitleSectionProps): React.JSX.Element;
11
+ export default StorySubtitleSection;
@@ -0,0 +1,9 @@
1
+ import React from 'react';
2
+ import StoryNavBar from '../StoryNavBar';
3
+ function StorySubtitleSection({ title, isSpecta, onPrev, onNext, hasPrev, hasNext, }) {
4
+ return (React.createElement("div", { className: "jgis-story-viewer-subtitle-container" },
5
+ 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 })))));
8
+ }
9
+ export default StorySubtitleSection;
@@ -0,0 +1,12 @@
1
+ import React from 'react';
2
+ interface IStoryTitleSectionProps {
3
+ title: string;
4
+ isSpecta: boolean;
5
+ storyType: string;
6
+ onPrev: () => void;
7
+ onNext: () => void;
8
+ hasPrev: boolean;
9
+ hasNext: boolean;
10
+ }
11
+ declare function StoryTitleSection({ title, isSpecta, storyType, onPrev, onNext, hasPrev, hasNext, }: IStoryTitleSectionProps): React.JSX.Element;
12
+ export default StoryTitleSection;
@@ -0,0 +1,8 @@
1
+ import React from 'react';
2
+ import StoryNavBar from '../StoryNavBar';
3
+ function StoryTitleSection({ title, isSpecta, storyType, onPrev, onNext, hasPrev, hasNext, }) {
4
+ return (React.createElement(React.Fragment, null,
5
+ 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 }))));
7
+ }
8
+ export default StoryTitleSection;
@@ -7,7 +7,7 @@ export type CalendarProps = DayPickerProps & {
7
7
  */
8
8
  yearRange?: number;
9
9
  /**
10
- * Wether to show the year switcher in the caption.
10
+ * Whether to show the year switcher in the caption.
11
11
  * @default true
12
12
  */
13
13
  showYearSwitcher?: boolean;