@jupytergis/base 0.13.2 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/lib/commands/BaseCommandIDs.d.ts +14 -13
  2. package/lib/commands/BaseCommandIDs.js +14 -14
  3. package/lib/commands/index.js +528 -130
  4. package/lib/commands/operationCommands.d.ts +22 -0
  5. package/lib/commands/operationCommands.js +305 -0
  6. package/lib/constants.js +11 -9
  7. package/lib/dialogs/ProcessingFormDialog.d.ts +1 -1
  8. package/lib/dialogs/ProcessingFormDialog.js +2 -2
  9. package/lib/dialogs/layerBrowserDialog.d.ts +2 -0
  10. package/lib/dialogs/layerBrowserDialog.js +12 -5
  11. package/lib/dialogs/layerCreationFormDialog.d.ts +7 -0
  12. package/lib/dialogs/layerCreationFormDialog.js +10 -2
  13. package/lib/dialogs/symbology/components/color_ramp/ColorRampControls.d.ts +2 -1
  14. package/lib/dialogs/symbology/components/color_ramp/ColorRampControls.js +21 -19
  15. package/lib/dialogs/symbology/components/color_ramp/ColorRampSelector.d.ts +3 -1
  16. package/lib/dialogs/symbology/components/color_ramp/ColorRampSelector.js +13 -5
  17. package/lib/dialogs/symbology/tiff_layer/types/SingleBandPseudoColor.js +6 -4
  18. package/lib/dialogs/symbology/vector_layer/types/Categorized.js +7 -11
  19. package/lib/dialogs/symbology/vector_layer/types/Graduated.js +7 -10
  20. package/lib/dialogs/symbology/vector_layer/types/Heatmap.js +7 -8
  21. package/lib/formbuilder/creationform.d.ts +8 -8
  22. package/lib/formbuilder/creationform.js +130 -85
  23. package/lib/formbuilder/editform.d.ts +1 -7
  24. package/lib/formbuilder/editform.js +64 -52
  25. package/lib/formbuilder/formselectors.d.ts +5 -4
  26. package/lib/formbuilder/index.d.ts +1 -1
  27. package/lib/formbuilder/index.js +1 -1
  28. package/lib/formbuilder/objectform/SchemaForm.d.ts +36 -0
  29. package/lib/formbuilder/objectform/SchemaForm.js +77 -0
  30. package/lib/formbuilder/objectform/StoryEditorForm.d.ts +3 -9
  31. package/lib/formbuilder/objectform/StoryEditorForm.js +20 -14
  32. package/lib/formbuilder/objectform/components/LayerSelect.js +3 -8
  33. package/lib/formbuilder/objectform/components/SegmentFormSymbology.js +23 -10
  34. package/lib/formbuilder/objectform/components/SourcePropertiesField.d.ts +7 -0
  35. package/lib/formbuilder/objectform/components/SourcePropertiesField.js +29 -0
  36. package/lib/formbuilder/objectform/components/StorySegmentReset.js +1 -1
  37. package/lib/formbuilder/objectform/fileselectorwidget.js +1 -1
  38. package/lib/formbuilder/objectform/layer/heatmapLayerForm.d.ts +3 -12
  39. package/lib/formbuilder/objectform/layer/heatmapLayerForm.js +87 -55
  40. package/lib/formbuilder/objectform/layer/hillshadeLayerForm.d.ts +3 -8
  41. package/lib/formbuilder/objectform/layer/hillshadeLayerForm.js +36 -10
  42. package/lib/formbuilder/objectform/layer/layerform.d.ts +7 -9
  43. package/lib/formbuilder/objectform/layer/layerform.js +33 -20
  44. package/lib/formbuilder/objectform/layer/storySegmentLayerForm.d.ts +3 -5
  45. package/lib/formbuilder/objectform/layer/storySegmentLayerForm.js +73 -29
  46. package/lib/formbuilder/objectform/layer/vectorlayerform.d.ts +3 -14
  47. package/lib/formbuilder/objectform/layer/vectorlayerform.js +36 -29
  48. package/lib/formbuilder/objectform/layer/webGlLayerForm.d.ts +3 -10
  49. package/lib/formbuilder/objectform/layer/webGlLayerForm.js +37 -13
  50. package/lib/formbuilder/objectform/process/dissolveProcessForm.d.ts +8 -18
  51. package/lib/formbuilder/objectform/process/dissolveProcessForm.js +68 -56
  52. package/lib/formbuilder/objectform/processingForm.d.ts +12 -0
  53. package/lib/formbuilder/objectform/processingForm.js +33 -0
  54. package/lib/formbuilder/objectform/schemaUtils.d.ts +16 -0
  55. package/lib/formbuilder/objectform/schemaUtils.js +59 -0
  56. package/lib/formbuilder/objectform/source/geojsonsource.d.ts +3 -19
  57. package/lib/formbuilder/objectform/source/geojsonsource.js +94 -53
  58. package/lib/formbuilder/objectform/source/geotiffsource.d.ts +3 -20
  59. package/lib/formbuilder/objectform/source/geotiffsource.js +73 -74
  60. package/lib/formbuilder/objectform/source/pathbasedsource.d.ts +3 -19
  61. package/lib/formbuilder/objectform/source/pathbasedsource.js +76 -75
  62. package/lib/formbuilder/objectform/source/sourceform.d.ts +7 -8
  63. package/lib/formbuilder/objectform/source/sourceform.js +28 -11
  64. package/lib/formbuilder/objectform/source/tilesourceform.d.ts +3 -7
  65. package/lib/formbuilder/objectform/source/tilesourceform.js +63 -53
  66. package/lib/formbuilder/objectform/useSchemaFormState.d.ts +48 -0
  67. package/lib/formbuilder/objectform/useSchemaFormState.js +35 -0
  68. package/lib/index.d.ts +0 -1
  69. package/lib/index.js +0 -1
  70. package/lib/keybindings.json +10 -10
  71. package/lib/mainview/mainView.d.ts +11 -7
  72. package/lib/mainview/mainView.js +63 -36
  73. package/lib/mainview/mainviewmodel.js +2 -2
  74. package/lib/menus.js +8 -8
  75. package/lib/panelview/components/layers.js +5 -2
  76. package/lib/panelview/objectproperties.js +2 -2
  77. package/lib/panelview/rightpanel.js +17 -2
  78. package/lib/panelview/story-maps/SpectaPanel.d.ts +15 -0
  79. package/lib/panelview/story-maps/SpectaPanel.js +35 -0
  80. package/lib/panelview/story-maps/StoryViewerPanel.d.ts +24 -9
  81. package/lib/panelview/story-maps/StoryViewerPanel.js +22 -268
  82. package/lib/panelview/story-maps/{PreviewModeSwitch.js → components/PreviewModeSwitch.js} +1 -1
  83. package/lib/panelview/story-maps/components/SpectaDesktopView.d.ts +21 -0
  84. package/lib/panelview/story-maps/components/SpectaDesktopView.js +49 -0
  85. package/lib/panelview/story-maps/components/SpectaMobileView.d.ts +17 -0
  86. package/lib/panelview/story-maps/{MobileSpectaPanel.js → components/SpectaMobileView.js} +8 -22
  87. package/lib/panelview/story-maps/{StoryNavBar.d.ts → components/StoryNavBar.d.ts} +1 -1
  88. package/lib/panelview/story-maps/{StoryNavBar.js → components/StoryNavBar.js} +2 -4
  89. package/lib/panelview/story-maps/hooks/useStoryMap.d.ts +39 -0
  90. package/lib/panelview/story-maps/hooks/useStoryMap.js +252 -0
  91. package/lib/processing/index.d.ts +2 -2
  92. package/lib/processing/index.js +62 -35
  93. package/lib/processing/processingCommands.d.ts +1 -1
  94. package/lib/processing/processingCommands.js +26 -6
  95. package/lib/shared/components/Collapsible.d.ts +6 -0
  96. package/lib/shared/components/Collapsible.js +26 -0
  97. package/lib/statusbar/SpectaPresentationProgressBar.d.ts +7 -0
  98. package/lib/statusbar/SpectaPresentationProgressBar.js +40 -0
  99. package/lib/toolbar/widget.js +4 -2
  100. package/lib/tools.d.ts +6 -0
  101. package/lib/tools.js +9 -0
  102. package/lib/types.d.ts +29 -2
  103. package/lib/widget.js +14 -2
  104. package/package.json +2 -4
  105. package/style/base.css +23 -1
  106. package/style/dialog.css +5 -0
  107. package/style/leftPanel.css +14 -37
  108. package/style/shared/button.css +0 -10
  109. package/style/shared/switch.css +8 -7
  110. package/style/spectaProgressBar.css +144 -0
  111. package/style/storyPanel.css +33 -0
  112. package/style/symbologyDialog.css +2 -2
  113. package/lib/formbuilder/objectform/baseform.d.ts +0 -91
  114. package/lib/formbuilder/objectform/baseform.js +0 -231
  115. package/lib/panelview/story-maps/MobileSpectaPanel.d.ts +0 -7
  116. /package/lib/panelview/story-maps/{PreviewModeSwitch.d.ts → components/PreviewModeSwitch.d.ts} +0 -0
@@ -45,11 +45,9 @@ import { debounce, isLightTheme, loadFile, throttle } from "../tools";
45
45
  import CollaboratorPointers from './CollaboratorPointers';
46
46
  import { FollowIndicator } from './FollowIndicator';
47
47
  import TemporalSlider from './TemporalSlider';
48
- import { hexToRgb } from '../dialogs/symbology/colorRampUtils';
49
48
  import { markerIcon } from '../icons';
50
49
  import { LeftPanel, RightPanel } from '../panelview';
51
- import { MobileSpectaPanel } from '../panelview/story-maps/MobileSpectaPanel';
52
- import StoryViewerPanel from '../panelview/story-maps/StoryViewerPanel';
50
+ import { SpectaPanel } from '../panelview/story-maps/SpectaPanel';
53
51
  export class MainView extends React.Component {
54
52
  constructor(props) {
55
53
  super(props);
@@ -135,6 +133,16 @@ export class MainView extends React.Component {
135
133
  };
136
134
  this.addContextMenu = () => {
137
135
  this._commands.addCommand(CommandIDs.addAnnotation, {
136
+ label: 'Add annotation',
137
+ describedBy: {
138
+ args: {
139
+ type: 'object',
140
+ properties: {},
141
+ },
142
+ },
143
+ isEnabled: () => {
144
+ return !!this._Map;
145
+ },
138
146
  execute: () => {
139
147
  var _a;
140
148
  if (!this._Map) {
@@ -152,10 +160,6 @@ export class MainView extends React.Component {
152
160
  open: true,
153
161
  });
154
162
  },
155
- label: 'Add annotation',
156
- isEnabled: () => {
157
- return !!this._Map;
158
- },
159
163
  });
160
164
  this._contextMenu.addItem({
161
165
  command: CommandIDs.addAnnotation,
@@ -417,8 +421,6 @@ export class MainView extends React.Component {
417
421
  this._setupSpectaMode = () => {
418
422
  this._removeAllInteractions();
419
423
  this._setupStoryScrollListener();
420
- // Update colors CSS variables with colors from story
421
- this._updateSpectaPresentationColors();
422
424
  };
423
425
  this._removeAllInteractions = () => {
424
426
  // Remove all default interactions
@@ -524,27 +526,6 @@ export class MainView extends React.Component {
524
526
  this._storyScrollHandler = null;
525
527
  }
526
528
  };
527
- this._updateSpectaPresentationColors = () => {
528
- var _a;
529
- // Try ref first, fallback to querySelector if ref not available yet
530
- const container = this.spectaContainerRef.current ||
531
- ((_a = this.divRef.current) === null || _a === void 0 ? void 0 : _a.querySelector('.jgis-specta-story-panel-container'));
532
- if (!container) {
533
- return;
534
- }
535
- const story = this._model.getSelectedStory().story;
536
- const bgColor = story === null || story === void 0 ? void 0 : story.presentationBgColor;
537
- const textColor = story === null || story === void 0 ? void 0 : story.presentationTextColor;
538
- // Set background color
539
- if (bgColor) {
540
- const rgb = hexToRgb(bgColor);
541
- container.style.setProperty('--jgis-specta-bg-color', `rgba(${rgb[0]}, ${rgb[1]}, ${rgb[2]})`);
542
- }
543
- // Set text color
544
- if (textColor) {
545
- container.style.setProperty('--jgis-specta-text-color', textColor);
546
- }
547
- };
548
529
  this._onSharedMetadataChanged = (_, changes) => {
549
530
  const newState = Object.assign({}, this.state.annotations);
550
531
  changes.forEach((val, key) => {
@@ -588,6 +569,32 @@ export class MainView extends React.Component {
588
569
  this._handleWindowResize = () => {
589
570
  // TODO SOMETHING
590
571
  };
572
+ this._handleSpectaTouchStart = (e) => {
573
+ if (e.touches.length > 0) {
574
+ this._spectaTouchStartX = e.touches[0].clientX;
575
+ }
576
+ };
577
+ this._handleSpectaTouchEnd = (e) => {
578
+ var _a, _b, _c;
579
+ if (e.changedTouches.length === 0) {
580
+ return;
581
+ }
582
+ const endX = e.changedTouches[0].clientX;
583
+ const deltaX = endX - this._spectaTouchStartX;
584
+ const threshold = 50;
585
+ const story = this._model.getSelectedStory().story;
586
+ const segmentCount = (_b = (_a = story === null || story === void 0 ? void 0 : story.storySegments) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0;
587
+ if (segmentCount === 0) {
588
+ return;
589
+ }
590
+ const current = (_c = this._model.getCurrentSegmentIndex()) !== null && _c !== void 0 ? _c : 0;
591
+ if (deltaX > threshold && current > 0) {
592
+ this._model.setCurrentSegmentIndex(current - 1);
593
+ }
594
+ else if (deltaX < -threshold && current < segmentCount - 1) {
595
+ this._model.setCurrentSegmentIndex(current + 1);
596
+ }
597
+ };
591
598
  this._isPositionInitialized = false;
592
599
  this.divRef = React.createRef(); // Reference of render div
593
600
  this.controlsToolbarRef = React.createRef();
@@ -600,6 +607,7 @@ export class MainView extends React.Component {
600
607
  this._isSpectaPresentationInitialized = false;
601
608
  this._storyScrollHandler = null;
602
609
  this._pendingStoryScrollRafId = null;
610
+ this._spectaTouchStartX = 0;
603
611
  this._state = props.state;
604
612
  this._formSchemaRegistry = props.formSchemaRegistry;
605
613
  this._annotationModel = props.annotationModel;
@@ -663,7 +671,8 @@ export class MainView extends React.Component {
663
671
  displayTemporalController: false,
664
672
  filterStates: {},
665
673
  jgisSettings: this._model.jgisSettings,
666
- isSpectaPresentation: false,
674
+ isSpectaPresentation: this._model.isSpectaMode(),
675
+ initialLayersReady: false,
667
676
  };
668
677
  this._sources = [];
669
678
  this._loadingLayers = new Set();
@@ -709,6 +718,8 @@ export class MainView extends React.Component {
709
718
  this._mainViewModel.dispose();
710
719
  }
711
720
  async generateMap(center, zoom) {
721
+ const layers = this._model.getLayers();
722
+ this._initialLayersCount = Object.values(layers).filter(layer => layer.type !== 'StorySegmentLayer').length;
712
723
  const scaleLine = new ScaleLine({
713
724
  target: this.controlsToolbarRef.current || undefined,
714
725
  });
@@ -1047,11 +1058,16 @@ export class MainView extends React.Component {
1047
1058
  });
1048
1059
  // Replace color placeholder in SVG with the parameter color
1049
1060
  const markerColor = parameters.color || '#3463a0';
1050
- const svgString = markerIcon.svgstr.replace('{{COLOR}}', markerColor);
1061
+ const svgString = markerIcon.svgstr
1062
+ .replace('{{COLOR}}', markerColor)
1063
+ .replace('<svg', '<svg width="128" height="128"');
1051
1064
  const iconStyle = new Style({
1052
1065
  image: new Icon({
1053
1066
  src: `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgString)}`,
1054
1067
  scale: 0.25,
1068
+ anchor: [0.5, 1],
1069
+ anchorXUnits: 'fraction',
1070
+ anchorYUnits: 'fraction',
1055
1071
  }),
1056
1072
  });
1057
1073
  marker.setStyle(iconStyle);
@@ -1346,6 +1362,11 @@ export class MainView extends React.Component {
1346
1362
  const numLayers = this._Map.getLayers().getLength();
1347
1363
  const safeIndex = Math.min(index, numLayers);
1348
1364
  this._Map.getLayers().insertAt(safeIndex, newMapLayer);
1365
+ // doing +1 instead of calling method again
1366
+ if (!this.state.initialLayersReady &&
1367
+ numLayers + 1 === this._initialLayersCount) {
1368
+ this.setState(old => (Object.assign(Object.assign({}, old), { initialLayersReady: true })));
1369
+ }
1349
1370
  }
1350
1371
  }
1351
1372
  catch (error) {
@@ -2039,7 +2060,11 @@ export class MainView extends React.Component {
2039
2060
  // ? could send just the filters object and modify that instead of emitting whole layer
2040
2061
  const json = JSON.parse(args);
2041
2062
  const { layerId, layer: jgisLayer } = json;
2063
+ const isSourceType = typeof (jgisLayer === null || jgisLayer === void 0 ? void 0 : jgisLayer.type) === 'string' && jgisLayer.type.includes('Source');
2042
2064
  const olLayer = this.getLayer(layerId);
2065
+ if (isSourceType) {
2066
+ this.updateSource(layerId, jgisLayer);
2067
+ }
2043
2068
  if (!jgisLayer || !olLayer) {
2044
2069
  console.error('Failed to update layer -- layer not found');
2045
2070
  return;
@@ -2086,7 +2111,11 @@ export class MainView extends React.Component {
2086
2111
  border: this.state.remoteUser
2087
2112
  ? `solid 3px ${this.state.remoteUser.color}`
2088
2113
  : 'unset',
2089
- } },
2114
+ }, onTouchStart: this.state.isSpectaPresentation && this.props.isMobile
2115
+ ? this._handleSpectaTouchStart
2116
+ : undefined, onTouchEnd: this.state.isSpectaPresentation && this.props.isMobile
2117
+ ? this._handleSpectaTouchEnd
2118
+ : undefined },
2090
2119
  React.createElement(LoadingOverlay, { loading: this.state.loading }),
2091
2120
  React.createElement(FollowIndicator, { remoteUser: this.state.remoteUser }),
2092
2121
  React.createElement(CollaboratorPointers, { clients: this.state.clientPointers }),
@@ -2096,9 +2125,7 @@ export class MainView extends React.Component {
2096
2125
  } },
2097
2126
  React.createElement("div", { className: "jgis-panels-wrapper" }, !this.state.isSpectaPresentation ? (React.createElement(React.Fragment, null,
2098
2127
  this._state && (React.createElement(LeftPanel, { model: this._model, commands: this._mainViewModel.commands, state: this._state, settings: this.state.jgisSettings })),
2099
- this._formSchemaRegistry && this._annotationModel && (React.createElement(RightPanel, { model: this._model, commands: this._mainViewModel.commands, formSchemaRegistry: this._formSchemaRegistry, annotationModel: this._annotationModel, addLayer: this.addLayer.bind(this), removeLayer: this.removeLayer.bind(this), settings: this.state.jgisSettings })))) : this.props.isMobile ? (React.createElement(MobileSpectaPanel, { model: this._model })) : (React.createElement("div", { className: "jgis-specta-right-panel-container-mod jgis-right-panel-container" },
2100
- React.createElement("div", { ref: this.spectaContainerRef, className: "jgis-specta-story-panel-container" },
2101
- React.createElement(StoryViewerPanel, { ref: this.storyViewerPanelRef, model: this._model, isSpecta: this.state.isSpectaPresentation, className: "jgis-story-viewer-panel-specta-mod", onSegmentTransitionEnd: () => this._clearStoryScrollGuard() }))))),
2128
+ this._formSchemaRegistry && this._annotationModel && (React.createElement(RightPanel, { model: this._model, commands: this._mainViewModel.commands, formSchemaRegistry: this._formSchemaRegistry, annotationModel: this._annotationModel, addLayer: this.addLayer.bind(this), removeLayer: this.removeLayer.bind(this), settings: this.state.jgisSettings })))) : (this.state.initialLayersReady && (React.createElement(SpectaPanel, { model: this._model, isSpecta: this.state.isSpectaPresentation, isMobile: this.props.isMobile, onSegmentTransitionEnd: () => this._clearStoryScrollGuard(), containerRef: this.spectaContainerRef, storyViewerPanelRef: this.storyViewerPanelRef, addLayer: this.addLayer.bind(this), removeLayer: this.removeLayer.bind(this) })))),
2102
2129
  React.createElement("div", { ref: this.controlsToolbarRef, className: "jgis-controls-toolbar" }))),
2103
2130
  !this.state.isSpectaPresentation && (React.createElement(StatusBar, { jgisModel: this._model, loading: this.state.loadingLayer, projection: this.state.viewProjection, scale: this.state.scale })))));
2104
2131
  }
@@ -1,4 +1,4 @@
1
- import { v4 as uuid } from 'uuid';
1
+ import { UUID } from '@lumino/coreutils';
2
2
  export class MainViewModel {
3
3
  constructor(options) {
4
4
  this._isDisposed = false;
@@ -33,7 +33,7 @@ export class MainViewModel {
33
33
  }
34
34
  addAnnotation(value) {
35
35
  var _a;
36
- (_a = this._jGISModel.annotationModel) === null || _a === void 0 ? void 0 : _a.addAnnotation(uuid(), value);
36
+ (_a = this._jGISModel.annotationModel) === null || _a === void 0 ? void 0 : _a.addAnnotation(UUID.uuid4(), value);
37
37
  }
38
38
  async _onsharedLayersChanged(_, change) {
39
39
  if (change.layerChange) {
package/lib/menus.js CHANGED
@@ -8,19 +8,19 @@ export const vectorSubMenu = (commands) => {
8
8
  subMenu.id = 'jp-gis-toolbar-vector-menu';
9
9
  subMenu.addItem({
10
10
  type: 'command',
11
- command: CommandIDs.newVectorTileEntry,
11
+ command: CommandIDs.openNewVectorTileDialog,
12
12
  });
13
13
  subMenu.addItem({
14
14
  type: 'command',
15
- command: CommandIDs.newGeoJSONEntry,
15
+ command: CommandIDs.openNewGeoJSONDialog,
16
16
  });
17
17
  subMenu.addItem({
18
18
  type: 'command',
19
- command: CommandIDs.newShapefileEntry,
19
+ command: CommandIDs.openNewShapefileDialog,
20
20
  });
21
21
  subMenu.addItem({
22
22
  type: 'command',
23
- command: CommandIDs.newGeoParquetEntry,
23
+ command: CommandIDs.openNewGeoParquetDialog,
24
24
  });
25
25
  return subMenu;
26
26
  };
@@ -31,19 +31,19 @@ export const rasterSubMenu = (commands) => {
31
31
  subMenu.id = 'jp-gis-toolbar-raster-menu';
32
32
  subMenu.addItem({
33
33
  type: 'command',
34
- command: CommandIDs.newRasterEntry,
34
+ command: CommandIDs.openNewRasterDialog,
35
35
  });
36
36
  subMenu.addItem({
37
37
  type: 'command',
38
- command: CommandIDs.newHillshadeEntry,
38
+ command: CommandIDs.openNewHillshadeDialog,
39
39
  });
40
40
  subMenu.addItem({
41
41
  type: 'command',
42
- command: CommandIDs.newImageEntry,
42
+ command: CommandIDs.openNewImageDialog,
43
43
  });
44
44
  subMenu.addItem({
45
45
  type: 'command',
46
- command: CommandIDs.newGeoTiffEntry,
46
+ command: CommandIDs.openNewGeoTiffDialog,
47
47
  });
48
48
  return subMenu;
49
49
  };
@@ -261,6 +261,7 @@ const LayerComponent = props => {
261
261
  model: gisModel,
262
262
  });
263
263
  const hasSupportedSymbology = (symbology === null || symbology === void 0 ? void 0 : symbology.symbologyState) !== undefined;
264
+ const isStorySegmentLayer = layer.type === 'StorySegmentLayer';
264
265
  const name = layer.name;
265
266
  useEffect(() => {
266
267
  setId(DOMUtils.createDomID());
@@ -361,14 +362,16 @@ const LayerComponent = props => {
361
362
  const slideNum = story.storySegments.indexOf(layerId) + 1;
362
363
  return slideNum;
363
364
  };
364
- return (React.createElement("div", { className: `${LAYER_ITEM_CLASS} ${LAYER_CLASS}${selected ? ' jp-mod-selected' : ''}`, draggable: true, onDragStart: Private.onDragStart, onDragOver: Private.onDragOver, onDragEnd: Private.onDragEnd, "data-id": layerId, style: { display: 'flex', flexDirection: 'column' } },
365
+ return (React.createElement("div", { className: `${LAYER_ITEM_CLASS} ${LAYER_CLASS}
366
+ ${isStorySegmentLayer ? 'jp-gis-storySegmentLayer' : ''}
367
+ ${selected ? ' jp-mod-selected' : ''}`, draggable: true, onDragStart: Private.onDragStart, onDragOver: Private.onDragOver, onDragEnd: Private.onDragEnd, "data-id": layerId, style: { display: 'flex', flexDirection: 'column' } },
365
368
  React.createElement("div", { className: LAYER_TITLE_CLASS, onClick: setSelection, onContextMenu: setSelection, style: { display: 'flex' } },
366
369
  hasSupportedSymbology && (React.createElement(Button, { minimal: true, onClick: e => {
367
370
  e.stopPropagation();
368
371
  setExpanded(v => !v);
369
372
  }, title: expanded ? 'Hide legend' : 'Show legend' },
370
373
  React.createElement(LabIcon.resolveReact, { icon: expanded ? caretDownIcon : caretRightIcon, tag: "span" }))),
371
- layer.type === 'StorySegmentLayer' ? (React.createElement("span", { className: LAYER_SLIDE_NUMBER_CLASS, title: "Slide number" }, getSlideNumber())) : (React.createElement(Button, { title: layer.visible ? 'Hide layer' : 'Show layer', onClick: toggleVisibility, minimal: true },
374
+ isStorySegmentLayer ? (React.createElement("span", { className: LAYER_SLIDE_NUMBER_CLASS, title: "Slide number" }, getSlideNumber())) : (React.createElement(Button, { title: layer.visible ? 'Hide layer' : 'Show layer', onClick: toggleVisibility, minimal: true },
372
375
  React.createElement(LabIcon.resolveReact, { icon: layer.visible ? visibilityIcon : nonVisibilityIcon, className: `${LAYER_ICON_CLASS}${layer.visible ? '' : ' jp-gis-mod-hidden'}`, tag: "span" }))),
373
376
  icons.has(layer.type) && (React.createElement(LabIcon.resolveReact, Object.assign({}, icons.get(layer.type), { className: LAYER_ICON_CLASS }))),
374
377
  isEditing ? (React.createElement("input", { type: "text", value: editValue, onChange: e => setEditValue(e.target.value), onKeyDown: handleRenameKeyDown, onBlur: handleRenameSave, className: LAYER_TEXT_CLASS, style: {
@@ -1,5 +1,5 @@
1
+ import { UUID } from '@lumino/coreutils';
1
2
  import * as React from 'react';
2
- import { v4 as uuid } from 'uuid';
3
3
  import { EditForm } from "../formbuilder/editform";
4
4
  export class ObjectPropertiesReact extends React.Component {
5
5
  constructor(props) {
@@ -37,7 +37,7 @@ export class ObjectPropertiesReact extends React.Component {
37
37
  };
38
38
  this.state = {
39
39
  clientId: props.model.getClientId(),
40
- id: uuid(),
40
+ id: UUID.uuid4(),
41
41
  model: props.model,
42
42
  selectedObject: props.selectedObject,
43
43
  setSelectedObject: props.setSelectedObject,
@@ -3,10 +3,25 @@ import Draggable from 'react-draggable';
3
3
  import { AnnotationsPanel } from './annotationPanel';
4
4
  import { IdentifyPanelComponent } from './identify-panel/IdentifyPanel';
5
5
  import { ObjectPropertiesReact } from './objectproperties';
6
- import { PreviewModeSwitch } from './story-maps/PreviewModeSwitch';
7
6
  import StoryEditorPanel from './story-maps/StoryEditorPanel';
8
7
  import StoryViewerPanel from './story-maps/StoryViewerPanel';
8
+ import { PreviewModeSwitch } from './story-maps/components/PreviewModeSwitch';
9
+ import { useStoryMap, } from './story-maps/hooks/useStoryMap';
9
10
  import { PanelTabs, TabsContent, TabsList, TabsTrigger, } from '../shared/components/Tabs';
11
+ /** Story viewer + useStoryMap hook
12
+ * only mounted when story tab is active to avoid the hook causing re-renders when tab is hidden.
13
+ **/
14
+ function RightPanelStoryViewer({ model, addLayer, removeLayer, }) {
15
+ const overrideLayerEntriesRef = React.useRef([]);
16
+ const { storyData, currentIndex, setIndex, handlePrev, handleNext, hasPrev, hasNext, activeSlide, layerName, } = useStoryMap({
17
+ model,
18
+ overrideLayerEntriesRef,
19
+ removeLayer,
20
+ addLayer,
21
+ isSpecta: false,
22
+ });
23
+ return (React.createElement(StoryViewerPanel, { model: model, isSpecta: false, storyData: storyData, currentIndex: currentIndex, activeSlide: activeSlide, layerName: layerName, handlePrev: handlePrev, handleNext: handleNext, hasPrev: hasPrev, hasNext: hasNext, setIndex: setIndex }));
24
+ }
10
25
  export const RightPanel = props => {
11
26
  var _a;
12
27
  const [editorMode, setEditorMode] = React.useState(true);
@@ -90,7 +105,7 @@ export const RightPanel = props => {
90
105
  React.createElement(ObjectPropertiesReact, { setSelectedObject: setSelectedObjectProperties, selectedObject: selectedObjectProperties, formSchemaRegistry: props.formSchemaRegistry, model: props.model }))),
91
106
  !props.settings.storyMapsDisabled && (React.createElement(TabsContent, { value: "storyPanel", className: "jgis-panel-tab-content", style: { paddingTop: 0 } },
92
107
  !storyMapPresentationMode && (React.createElement(PreviewModeSwitch, { checked: !editorMode, onCheckedChange: toggleEditor })),
93
- showEditor ? (React.createElement(StoryEditorPanel, { model: props.model, commands: props.commands })) : (React.createElement(StoryViewerPanel, { model: props.model, isSpecta: false, addLayer: props.addLayer, removeLayer: props.removeLayer })))),
108
+ showEditor ? (React.createElement(StoryEditorPanel, { model: props.model, commands: props.commands })) : curTab === 'storyPanel' ? (React.createElement(RightPanelStoryViewer, { model: props.model, addLayer: props.addLayer, removeLayer: props.removeLayer })) : null)),
94
109
  !props.settings.annotationsDisabled && (React.createElement(TabsContent, { value: "annotations", className: "jgis-panel-tab-content" },
95
110
  React.createElement(AnnotationsPanel, { annotationModel: props.annotationModel, jgisModel: props.model }))),
96
111
  !props.settings.identifyDisabled && (React.createElement(TabsContent, { value: "identifyPanel", className: "jgis-panel-tab-content" },
@@ -0,0 +1,15 @@
1
+ import { IJGISLayer, IJupyterGISModel } from '@jupytergis/schema';
2
+ import React, { RefObject } from 'react';
3
+ import type { IStoryViewerPanelHandle } from './StoryViewerPanel';
4
+ interface ISpectaPanelProps {
5
+ model: IJupyterGISModel;
6
+ isSpecta: boolean;
7
+ isMobile: boolean;
8
+ onSegmentTransitionEnd: () => void;
9
+ containerRef: RefObject<HTMLDivElement>;
10
+ storyViewerPanelRef: RefObject<IStoryViewerPanelHandle>;
11
+ addLayer?: (id: string, layer: IJGISLayer, index: number) => Promise<void>;
12
+ removeLayer?: (id: string) => void;
13
+ }
14
+ export declare function SpectaPanel({ model, isSpecta, isMobile, onSegmentTransitionEnd, containerRef, storyViewerPanelRef, addLayer, removeLayer, }: ISpectaPanelProps): React.JSX.Element;
15
+ export {};
@@ -0,0 +1,35 @@
1
+ import React, { useEffect, useRef } from 'react';
2
+ import { SpectaDesktopView } from './components/SpectaDesktopView';
3
+ import { SpectaMobileView } from './components/SpectaMobileView';
4
+ import { useStoryMap } from './hooks/useStoryMap';
5
+ export function SpectaPanel({ model, isSpecta, isMobile, onSegmentTransitionEnd, containerRef, storyViewerPanelRef, addLayer, removeLayer, }) {
6
+ const overrideLayerEntriesRef = useRef([]);
7
+ const segmentContainerRef = useRef(null);
8
+ const { storyData, currentIndex, setIndex, handlePrev, handleNext, hasPrev, hasNext, activeSlide, layerName, } = useStoryMap({
9
+ model,
10
+ overrideLayerEntriesRef,
11
+ removeLayer,
12
+ addLayer,
13
+ panelRef: isMobile ? undefined : containerRef,
14
+ isSpecta,
15
+ });
16
+ // Notify when segment transition animation ends
17
+ useEffect(() => {
18
+ const el = segmentContainerRef.current;
19
+ if (!el || !onSegmentTransitionEnd) {
20
+ return;
21
+ }
22
+ const handleAnimationEnd = (e) => {
23
+ if (e.animationName === 'fadeIn') {
24
+ el.removeEventListener('animationend', handleAnimationEnd);
25
+ onSegmentTransitionEnd();
26
+ }
27
+ };
28
+ el.addEventListener('animationend', handleAnimationEnd);
29
+ return () => el.removeEventListener('animationend', handleAnimationEnd);
30
+ }, [currentIndex, onSegmentTransitionEnd]);
31
+ if (isMobile) {
32
+ return (React.createElement(SpectaMobileView, { model: model, segmentContainerRef: segmentContainerRef, storyData: storyData, currentIndex: currentIndex, activeSlide: activeSlide, layerName: layerName, handlePrev: handlePrev, handleNext: handleNext, hasPrev: hasPrev, hasNext: hasNext, setIndex: setIndex }));
33
+ }
34
+ return (React.createElement(SpectaDesktopView, { model: model, isSpecta: isSpecta, containerRef: containerRef, storyViewerPanelRef: storyViewerPanelRef, segmentContainerRef: segmentContainerRef, storyData: storyData, currentIndex: currentIndex, activeSlide: activeSlide, layerName: layerName, handlePrev: handlePrev, handleNext: handleNext, hasPrev: hasPrev, hasNext: hasNext, setIndex: setIndex }));
35
+ }
@@ -1,14 +1,22 @@
1
- import { IJGISLayer, IJupyterGISModel } from '@jupytergis/schema';
2
- import React from 'react';
1
+ import { IJGISStoryMap, IJupyterGISModel, IStorySegmentLayer } from '@jupytergis/schema';
2
+ import React, { RefObject } from 'react';
3
+ /** Props: story state and callbacks come from useStoryMap in parent (SpectaPanel or SpectaMobileView). */
3
4
  interface IStoryViewerPanelProps {
4
5
  model: IJupyterGISModel;
5
6
  isSpecta: boolean;
6
7
  isMobile?: boolean;
7
8
  className?: string;
8
- addLayer?: (id: string, layer: IJGISLayer, index: number) => Promise<void>;
9
- removeLayer?: (id: string) => void;
10
- /** Called when the segment transition animation has finished (e.g. for scroll-guard cleanup). */
11
- onSegmentTransitionEnd?: () => void;
9
+ /** Ref for the segment container (SpectaPanel uses it for animationend). */
10
+ segmentContainerRef?: RefObject<HTMLDivElement>;
11
+ storyData: IJGISStoryMap | null;
12
+ currentIndex: number;
13
+ activeSlide: IStorySegmentLayer['parameters'] | undefined;
14
+ layerName: string;
15
+ handlePrev: () => void;
16
+ handleNext: () => void;
17
+ hasPrev: boolean;
18
+ hasNext: boolean;
19
+ setIndex: (index: number) => void;
12
20
  }
13
21
  export interface IStoryViewerPanelHandle {
14
22
  handlePrev: () => void;
@@ -26,8 +34,15 @@ export interface IStoryViewerPanelHandle {
26
34
  * - below-title: normal mode, guided, no image (under the title)
27
35
  * - over-image: normal mode, guided, with image (over the image)
28
36
  * - subtitle-specta: specta mode desktop (next to subtitle, fixed centered)
29
- * - subtitle-specta-mobile: specta mode mobile (in line with subtitle)
37
+ * Specta mode mobile returns null (nav hidden).
30
38
  */
31
- export type StoryNavPlacement = 'below-title' | 'over-image' | 'subtitle-specta' | 'subtitle-specta-mobile';
32
- declare const StoryViewerPanel: React.ForwardRefExoticComponent<IStoryViewerPanelProps & React.RefAttributes<IStoryViewerPanelHandle>>;
39
+ export type StoryNavPlacement = 'below-title' | 'over-image' | 'subtitle-specta';
40
+ /**
41
+ * Story viewer (presentational). Receives story state and callbacks from parent.
42
+ * Desktop scroll/sentinel/imperative handle live in SpectaDesktopView.
43
+ */
44
+ declare function StoryViewerPanel({ model, isSpecta, isMobile, className, segmentContainerRef, storyData, currentIndex, activeSlide, layerName, handlePrev, handleNext, hasPrev, hasNext, setIndex, }: IStoryViewerPanelProps): React.JSX.Element;
45
+ declare namespace StoryViewerPanel {
46
+ var displayName: string;
47
+ }
33
48
  export default StoryViewerPanel;