@jupytergis/base 0.10.1 → 0.11.1

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 (49) hide show
  1. package/lib/commands/BaseCommandIDs.d.ts +1 -0
  2. package/lib/commands/BaseCommandIDs.js +2 -0
  3. package/lib/commands/index.js +14 -0
  4. package/lib/constants.js +1 -0
  5. package/lib/dialogs/symbology/vector_layer/types/Categorized.js +1 -5
  6. package/lib/formbuilder/formselectors.js +5 -1
  7. package/lib/formbuilder/objectform/StoryEditorForm.d.ts +8 -0
  8. package/lib/formbuilder/objectform/StoryEditorForm.js +10 -0
  9. package/lib/formbuilder/objectform/components/StorySegmentReset.d.ts +8 -0
  10. package/lib/formbuilder/objectform/components/StorySegmentReset.js +24 -0
  11. package/lib/formbuilder/objectform/layer/index.d.ts +1 -0
  12. package/lib/formbuilder/objectform/layer/index.js +1 -0
  13. package/lib/formbuilder/objectform/layer/storySegmentLayerForm.d.ts +5 -0
  14. package/lib/formbuilder/objectform/layer/storySegmentLayerForm.js +32 -0
  15. package/lib/mainview/mainView.js +61 -7
  16. package/lib/panelview/components/layers.d.ts +2 -1
  17. package/lib/panelview/components/layers.js +31 -23
  18. package/lib/panelview/components/story-maps/PreviewModeSwitch.d.ts +7 -0
  19. package/lib/panelview/components/story-maps/PreviewModeSwitch.js +12 -0
  20. package/lib/panelview/components/story-maps/StoryEditorPanel.d.ts +7 -0
  21. package/lib/panelview/components/story-maps/StoryEditorPanel.js +29 -0
  22. package/lib/panelview/components/story-maps/StoryNavBar.d.ts +9 -0
  23. package/lib/panelview/components/story-maps/StoryNavBar.js +11 -0
  24. package/lib/panelview/components/story-maps/StoryViewerPanel.d.ts +7 -0
  25. package/lib/panelview/components/story-maps/StoryViewerPanel.js +166 -0
  26. package/lib/panelview/leftpanel.js +87 -5
  27. package/lib/panelview/rightpanel.js +32 -2
  28. package/lib/shared/components/Calendar.d.ts +1 -1
  29. package/lib/shared/components/Command.d.ts +18 -0
  30. package/lib/shared/components/Command.js +60 -0
  31. package/lib/shared/components/Dialog.d.ts +15 -0
  32. package/lib/shared/components/Dialog.js +62 -0
  33. package/lib/shared/components/RadioGroup.d.ts +5 -0
  34. package/lib/shared/components/RadioGroup.js +26 -0
  35. package/lib/shared/components/Switch.d.ts +4 -0
  36. package/lib/shared/components/Switch.js +20 -0
  37. package/lib/toolbar/widget.d.ts +10 -0
  38. package/lib/toolbar/widget.js +49 -0
  39. package/lib/tools.js +1 -1
  40. package/package.json +8 -3
  41. package/style/base.css +4 -0
  42. package/style/leftPanel.css +18 -0
  43. package/style/shared/button.css +1 -1
  44. package/style/shared/dialog.css +177 -0
  45. package/style/shared/radioGroup.css +55 -0
  46. package/style/shared/switch.css +63 -0
  47. package/style/shared/tabs.css +3 -2
  48. package/style/storyPanel.css +68 -0
  49. package/style/tabPanel.css +1 -2
@@ -40,3 +40,4 @@ export declare const showFiltersTab = "jupytergis:showFiltersTab";
40
40
  export declare const showObjectPropertiesTab = "jupytergis:showObjectPropertiesTab";
41
41
  export declare const showAnnotationsTab = "jupytergis:showAnnotationsTab";
42
42
  export declare const showIdentifyPanelTab = "jupytergis:showIdentifyPanelTab";
43
+ export declare const addStorySegment = "jupytergis:addStorySegment";
@@ -55,3 +55,5 @@ export const showFiltersTab = 'jupytergis:showFiltersTab';
55
55
  export const showObjectPropertiesTab = 'jupytergis:showObjectPropertiesTab';
56
56
  export const showAnnotationsTab = 'jupytergis:showAnnotationsTab';
57
57
  export const showIdentifyPanelTab = 'jupytergis:showIdentifyPanelTab';
58
+ // Story maps
59
+ export const addStorySegment = 'jupytergis:addStorySegment';
@@ -818,6 +818,20 @@ export function addCommands(app, tracker, translator, formSchemaRegistry, layerB
818
818
  current.model.toggleMode('marking');
819
819
  commands.notifyCommandChanged(CommandIDs.addMarker);
820
820
  } }, icons.get(CommandIDs.addMarker)));
821
+ commands.addCommand(CommandIDs.addStorySegment, Object.assign({ label: trans.__('Add Story Segment'), isEnabled: () => {
822
+ const current = tracker.currentWidget;
823
+ if (!current) {
824
+ return false;
825
+ }
826
+ return (current.model.sharedModel.editable &&
827
+ !current.model.jgisSettings.storyMapsDisabled);
828
+ }, execute: args => {
829
+ const current = tracker.currentWidget;
830
+ if (!current) {
831
+ return;
832
+ }
833
+ current.model.addStorySegment();
834
+ } }, icons.get(CommandIDs.addStorySegment)));
821
835
  loadKeybindings(commands, keybindings);
822
836
  }
823
837
  var Private;
package/lib/constants.js CHANGED
@@ -35,6 +35,7 @@ const iconObject = {
35
35
  [CommandIDs.identify]: { icon: infoIcon },
36
36
  [CommandIDs.temporalController]: { icon: clockIcon },
37
37
  [CommandIDs.addMarker]: { icon: markerIcon },
38
+ [CommandIDs.addStorySegment]: { iconClass: 'fa fa-link' },
38
39
  };
39
40
  /**
40
41
  * The registered icons
@@ -90,7 +90,7 @@ const Categorized = ({ model, state, okSignalPromise, cancel, layerId, symbology
90
90
  setStopRows(valueColorPairs);
91
91
  };
92
92
  const handleOk = () => {
93
- var _a, _b, _c;
93
+ var _a;
94
94
  if (!layer.parameters) {
95
95
  return;
96
96
  }
@@ -122,8 +122,6 @@ const Categorized = ({ model, state, okSignalPromise, cancel, layerId, symbology
122
122
  renderType: 'Categorized',
123
123
  value: selectedAttributeRef.current,
124
124
  colorRamp: (_a = colorRampOptionsRef.current) === null || _a === void 0 ? void 0 : _a.selectedRamp,
125
- nClasses: (_b = colorRampOptionsRef.current) === null || _b === void 0 ? void 0 : _b.numberOfShades,
126
- mode: (_c = colorRampOptionsRef.current) === null || _c === void 0 ? void 0 : _c.selectedMode,
127
125
  symbologyTab,
128
126
  reverse: reverseRamp,
129
127
  };
@@ -149,8 +147,6 @@ const Categorized = ({ model, state, okSignalPromise, cancel, layerId, symbology
149
147
  // Reset color classification options
150
148
  if (layer.parameters.symbologyState) {
151
149
  layer.parameters.symbologyState.colorRamp = undefined;
152
- layer.parameters.symbologyState.nClasses = undefined;
153
- layer.parameters.symbologyState.mode = undefined;
154
150
  }
155
151
  }
156
152
  if (method === 'radius') {
@@ -1,4 +1,4 @@
1
- import { HeatmapLayerPropertiesForm, HillshadeLayerPropertiesForm, LayerPropertiesForm, VectorLayerPropertiesForm, WebGlLayerPropertiesForm, } from './objectform/layer';
1
+ import { HeatmapLayerPropertiesForm, HillshadeLayerPropertiesForm, StorySegmentLayerPropertiesForm, LayerPropertiesForm, VectorLayerPropertiesForm, WebGlLayerPropertiesForm, } from './objectform/layer';
2
2
  import { GeoJSONSourcePropertiesForm, GeoTiffSourcePropertiesForm, PathBasedSourcePropertiesForm, TileSourcePropertiesForm, SourcePropertiesForm, } from './objectform/source';
3
3
  export function getLayerTypeForm(layerType) {
4
4
  let LayerForm = LayerPropertiesForm;
@@ -15,6 +15,10 @@ export function getLayerTypeForm(layerType) {
15
15
  break;
16
16
  case 'HeatmapLayer':
17
17
  LayerForm = HeatmapLayerPropertiesForm;
18
+ break;
19
+ case 'StorySegmentLayer':
20
+ LayerForm = StorySegmentLayerPropertiesForm;
21
+ break;
18
22
  // ADD MORE FORM TYPES HERE
19
23
  }
20
24
  return LayerForm;
@@ -0,0 +1,8 @@
1
+ import { IDict } from '@jupytergis/schema';
2
+ import { BaseForm } from './baseform';
3
+ /**
4
+ * The form to modify a hillshade layer.
5
+ */
6
+ export declare class StoryEditorPropertiesForm extends BaseForm {
7
+ protected processSchema(data: IDict<any> | undefined, schema: IDict, uiSchema: IDict): void;
8
+ }
@@ -0,0 +1,10 @@
1
+ import { BaseForm } from './baseform';
2
+ /**
3
+ * The form to modify a hillshade layer.
4
+ */
5
+ export class StoryEditorPropertiesForm extends BaseForm {
6
+ processSchema(data, schema, uiSchema) {
7
+ super.processSchema(data, schema, uiSchema);
8
+ this.removeFormEntry('storySegments', data, schema, uiSchema);
9
+ }
10
+ }
@@ -0,0 +1,8 @@
1
+ import { IJupyterGISModel } from '@jupytergis/schema';
2
+ import React from 'react';
3
+ interface IStorySegmentResetProps {
4
+ model?: IJupyterGISModel;
5
+ layerId?: string;
6
+ }
7
+ declare function StorySegmentReset({ model, layerId }: IStorySegmentResetProps): React.JSX.Element;
8
+ export default StorySegmentReset;
@@ -0,0 +1,24 @@
1
+ import { LabIcon } from '@jupyterlab/ui-components';
2
+ import React from 'react';
3
+ import { targetWithCenterIcon } from "../../../icons";
4
+ import { Button } from "../../../shared/components/Button";
5
+ function StorySegmentReset({ model, layerId }) {
6
+ const handleSetStorySegmentToCurrentView = () => {
7
+ if (!model || !layerId) {
8
+ return;
9
+ }
10
+ const layer = model.getLayer(layerId);
11
+ if (!layer) {
12
+ return;
13
+ }
14
+ const { zoom, extent } = model.getOptions();
15
+ const updatedLayer = Object.assign(Object.assign({}, layer), { parameters: Object.assign(Object.assign({}, layer.parameters), { zoom,
16
+ extent }) });
17
+ model.sharedModel.updateLayer(layerId, updatedLayer);
18
+ };
19
+ return (React.createElement("div", null,
20
+ React.createElement(Button, { title: "Set story segment to current viewport", onClick: handleSetStorySegmentToCurrentView },
21
+ React.createElement(LabIcon.resolveReact, { icon: targetWithCenterIcon, className: "jp-gis-layerIcon", tag: "span" }),
22
+ "Set Story Segment Extent")));
23
+ }
24
+ export default StorySegmentReset;
@@ -3,3 +3,4 @@ export * from './hillshadeLayerForm';
3
3
  export * from './layerform';
4
4
  export * from './vectorlayerform';
5
5
  export * from './webGlLayerForm';
6
+ export * from './storySegmentLayerForm';
@@ -3,3 +3,4 @@ export * from './hillshadeLayerForm';
3
3
  export * from './layerform';
4
4
  export * from './vectorlayerform';
5
5
  export * from './webGlLayerForm';
6
+ export * from './storySegmentLayerForm';
@@ -0,0 +1,5 @@
1
+ import { IDict } from '@jupytergis/schema';
2
+ import { LayerPropertiesForm } from './layerform';
3
+ export declare class StorySegmentLayerPropertiesForm extends LayerPropertiesForm {
4
+ protected processSchema(data: IDict<any> | undefined, schema: IDict, uiSchema: IDict): void;
5
+ }
@@ -0,0 +1,32 @@
1
+ import * as React from 'react';
2
+ import { LayerPropertiesForm } from './layerform';
3
+ import StorySegmentReset from '../components/StorySegmentReset';
4
+ export class StorySegmentLayerPropertiesForm extends LayerPropertiesForm {
5
+ processSchema(data, schema, uiSchema) {
6
+ super.processSchema(data, schema, uiSchema);
7
+ if (!this.props.model.selected) {
8
+ return;
9
+ }
10
+ let layerId = undefined;
11
+ const selectedKeys = Object.keys(this.props.model.selected);
12
+ // Find the first selected story segment
13
+ // ! TODO we still need to handle selections better, like there should at least be a getFirstSelected
14
+ for (const key of selectedKeys) {
15
+ const layer = this.props.model.getLayer(key);
16
+ if (layer && layer.type === 'StorySegmentLayer') {
17
+ layerId = key;
18
+ break;
19
+ }
20
+ }
21
+ uiSchema['extent'] = {
22
+ 'ui:field': (props) => React.createElement(StorySegmentReset, Object.assign(Object.assign({}, props), { model: this.props.model, layerId })),
23
+ };
24
+ uiSchema['content'] = Object.assign(Object.assign({}, uiSchema['content']), { markdown: {
25
+ 'ui:widget': 'textarea',
26
+ 'ui:options': {
27
+ rows: 10,
28
+ },
29
+ } });
30
+ this.removeFormEntry('zoom', data, schema, uiSchema);
31
+ }
32
+ }
@@ -18,6 +18,7 @@ import { Collection, Map as OlMap, View, getUid, } from 'ol';
18
18
  import Feature from 'ol/Feature';
19
19
  import { FullScreen, ScaleLine } from 'ol/control';
20
20
  import { singleClick } from 'ol/events/condition';
21
+ import { getCenter } from 'ol/extent';
21
22
  import { GeoJSON, MVT } from 'ol/format';
22
23
  import { Point } from 'ol/geom';
23
24
  import { DragAndDrop, Select } from 'ol/interaction';
@@ -997,7 +998,8 @@ export class MainView extends React.Component {
997
998
  let layerParameters;
998
999
  let sourceId;
999
1000
  let source;
1000
- if (layer.type !== 'StacLayer') {
1001
+ // Sourceless layers
1002
+ if (!['StacLayer', 'StorySegmentLayer'].includes(layer.type)) {
1001
1003
  sourceId = (_a = layer.parameters) === null || _a === void 0 ? void 0 : _a.source;
1002
1004
  if (!sourceId) {
1003
1005
  return;
@@ -1099,6 +1101,10 @@ export class MainView extends React.Component {
1099
1101
  this.setState(old => (Object.assign(Object.assign({}, old), { metadata: layerParameters.data.properties })));
1100
1102
  break;
1101
1103
  }
1104
+ case 'StorySegmentLayer': {
1105
+ // Special layer not for this
1106
+ return;
1107
+ }
1102
1108
  }
1103
1109
  // OpenLayers doesn't have name/id field so add it
1104
1110
  newMapLayer.set('id', id);
@@ -1164,7 +1170,6 @@ export class MainView extends React.Component {
1164
1170
  }
1165
1171
  catch (error) {
1166
1172
  if (this.state.loadingErrors.find(item => item.id === id && item.error === error.message)) {
1167
- this._loadingLayers.delete(id);
1168
1173
  return;
1169
1174
  }
1170
1175
  await showErrorMessage(`Error Adding ${layer.name}`, `Failed to add ${layer.name}: ${error.message || 'invalid file path'}`);
@@ -1174,7 +1179,10 @@ export class MainView extends React.Component {
1174
1179
  error: error.message || 'invalid file path',
1175
1180
  index,
1176
1181
  });
1182
+ }
1183
+ finally {
1177
1184
  this._loadingLayers.delete(id);
1185
+ this.setState(old => (Object.assign(Object.assign({}, old), { loadingLayer: false })));
1178
1186
  }
1179
1187
  }
1180
1188
  /**
@@ -1185,7 +1193,7 @@ export class MainView extends React.Component {
1185
1193
  */
1186
1194
  async updateLayer(id, layer, mapLayer, oldLayer) {
1187
1195
  var _a, _b, _c, _d, _e, _f, _g, _h;
1188
- mapLayer.setVisible(layer.visible);
1196
+ layer.type !== 'StorySegmentLayer' && mapLayer.setVisible(layer.visible);
1189
1197
  switch (layer.type) {
1190
1198
  case 'RasterLayer': {
1191
1199
  mapLayer.setOpacity(((_a = layer.parameters) === null || _a === void 0 ? void 0 : _a.opacity) || 1);
@@ -1545,8 +1553,9 @@ export class MainView extends React.Component {
1545
1553
  }
1546
1554
  });
1547
1555
  }
1556
+ // TODO this and flyToPosition need a rework
1548
1557
  _onZoomToPosition(_, id) {
1549
- var _a;
1558
+ var _a, _b;
1550
1559
  // Check if the id is an annotation
1551
1560
  const annotation = (_a = this._model.annotationModel) === null || _a === void 0 ? void 0 : _a.getAnnotation(id);
1552
1561
  if (annotation) {
@@ -1557,6 +1566,18 @@ export class MainView extends React.Component {
1557
1566
  let extent;
1558
1567
  const layer = this.getLayer(id);
1559
1568
  const source = layer === null || layer === void 0 ? void 0 : layer.getSource();
1569
+ // TODO: Story segment layers don't have an associated OL layer
1570
+ // This could be better
1571
+ if (!layer) {
1572
+ const jgisLayer = this._model.getLayer(id);
1573
+ const layerParams = jgisLayer === null || jgisLayer === void 0 ? void 0 : jgisLayer.parameters;
1574
+ const coords = getCenter(layerParams.extent);
1575
+ // TODO: Should pass args through signal??
1576
+ // const { story } = this._model.getSelectedStory();
1577
+ this._flyToPosition({ x: coords[0], y: coords[1] }, layerParams.zoom, ((_b = layerParams.transition.time) !== null && _b !== void 0 ? _b : 1) * 1000, // seconds -> ms
1578
+ layerParams.transition.type);
1579
+ return;
1580
+ }
1560
1581
  if (source instanceof VectorSource) {
1561
1582
  extent = source.getExtent();
1562
1583
  }
@@ -1596,10 +1617,43 @@ export class MainView extends React.Component {
1596
1617
  });
1597
1618
  }
1598
1619
  }
1599
- _flyToPosition(center, zoom, duration = 1000) {
1620
+ _flyToPosition(center, zoom, duration = 1000, transitionType) {
1600
1621
  const view = this._Map.getView();
1601
- view.animate({ zoom, duration });
1602
- view.animate({ center: [center.x, center.y], duration });
1622
+ // Cancel any in-progress animations before starting new ones
1623
+ view.cancelAnimations();
1624
+ const targetCenter = [center.x, center.y];
1625
+ if (transitionType === 'linear') {
1626
+ // Linear: direct zoom
1627
+ view.animate({
1628
+ center: targetCenter,
1629
+ zoom: zoom,
1630
+ duration,
1631
+ });
1632
+ return;
1633
+ }
1634
+ if (transitionType === 'smooth') {
1635
+ // Smooth: zoom out, center, and zoom in
1636
+ // Centering takes full duration, zoom out completes halfway, zoom in starts halfway
1637
+ // 3 shows most of the map
1638
+ const zoomOutLevel = 3;
1639
+ // Start centering (full duration) and zoom out (50% duration) simultaneously
1640
+ view.animate({
1641
+ center: targetCenter,
1642
+ duration: duration,
1643
+ });
1644
+ // Chain zoom out -> zoom in (zoom in starts when zoom out completes)
1645
+ view.animate({
1646
+ zoom: zoomOutLevel,
1647
+ duration: duration * 0.5,
1648
+ }, {
1649
+ zoom: zoom,
1650
+ duration: duration * 0.5,
1651
+ });
1652
+ return;
1653
+ }
1654
+ // Immediate move
1655
+ view.setCenter(targetCenter);
1656
+ view.setZoom(zoom);
1603
1657
  }
1604
1658
  _onPointerMove(e) {
1605
1659
  const pixel = this._Map.getEventPixel(e);
@@ -1,4 +1,4 @@
1
- import { IJupyterGISModel } from '@jupytergis/schema';
1
+ import { IJGISLayerTree, IJupyterGISModel } from '@jupytergis/schema';
2
2
  import { IStateDB } from '@jupyterlab/statedb';
3
3
  import { CommandRegistry } from '@lumino/commands';
4
4
  import React from 'react';
@@ -6,6 +6,7 @@ interface IBodyProps {
6
6
  model: IJupyterGISModel;
7
7
  commands: CommandRegistry;
8
8
  state: IStateDB;
9
+ layerTree: IJGISLayerTree;
9
10
  }
10
11
  export declare const LayersBodyComponent: React.FC<IBodyProps>;
11
12
  export {};
@@ -3,7 +3,7 @@ import { Button, LabIcon, caretDownIcon, caretRightIcon, } from '@jupyterlab/ui-
3
3
  import React, { useEffect, useState, } from 'react';
4
4
  import { CommandIDs, icons } from "../../constants";
5
5
  import { useGetSymbology } from "../../dialogs/symbology/hooks/useGetSymbology";
6
- import { nonVisibilityIcon, visibilityIcon } from "../../icons";
6
+ import { nonVisibilityIcon, targetWithCenterIcon, visibilityIcon, } from "../../icons";
7
7
  import { LegendItem } from './legendItem';
8
8
  const LAYER_GROUP_CLASS = 'jp-gis-layerGroup';
9
9
  const LAYER_GROUP_HEADER_CLASS = 'jp-gis-layerGroupHeader';
@@ -13,9 +13,10 @@ const LAYER_CLASS = 'jp-gis-layer';
13
13
  const LAYER_TITLE_CLASS = 'jp-gis-layerTitle';
14
14
  const LAYER_ICON_CLASS = 'jp-gis-layerIcon';
15
15
  const LAYER_TEXT_CLASS = 'jp-gis-layerText data-jgis-keybinding';
16
+ const LAYER_SLIDE_NUMBER_CLASS = 'jp-gis-layerSlideNumber';
16
17
  export const LayersBodyComponent = props => {
17
18
  const model = props.model;
18
- const [layerTree, setLayerTree] = useState((model === null || model === void 0 ? void 0 : model.getLayerTree()) || []);
19
+ const [layerTree, setLayerTree] = useState(props.layerTree || []);
19
20
  const notifyCommands = () => {
20
21
  // Notify commands that need updating
21
22
  props.commands.notifyCommandChanged(CommandIDs.identify);
@@ -117,25 +118,13 @@ export const LayersBodyComponent = props => {
117
118
  const onItemClick = ({ type, item, event }) => {
118
119
  onSelect({ type, item, event });
119
120
  };
120
- /**
121
- * Listen to the layers and layer tree changes.
122
- */
121
+ // Update layerTree when prop changes
123
122
  useEffect(() => {
124
- const updateLayers = () => {
125
- setLayerTree((model === null || model === void 0 ? void 0 : model.getLayerTree()) || []);
126
- };
127
- model === null || model === void 0 ? void 0 : model.sharedModel.layersChanged.connect(updateLayers);
128
- model === null || model === void 0 ? void 0 : model.sharedModel.layerTreeChanged.connect(updateLayers);
129
- updateLayers();
130
- return () => {
131
- model === null || model === void 0 ? void 0 : model.sharedModel.layersChanged.disconnect(updateLayers);
132
- model === null || model === void 0 ? void 0 : model.sharedModel.layerTreeChanged.disconnect(updateLayers);
133
- };
134
- }, [model]);
135
- return (React.createElement("div", { id: "jp-gis-layer-tree", onDrop: _onDrop, onDragOver: _onDragOver }, layerTree
136
- .slice()
137
- .reverse()
138
- .map(layer => typeof layer === 'string' ? (React.createElement(LayerComponent, { key: layer, gisModel: model, layerId: layer, onClick: onItemClick })) : (React.createElement(LayerGroupComponent, { key: layer.name, gisModel: model, group: layer, onClick: onItemClick, state: props.state })))));
123
+ if (props.layerTree) {
124
+ setLayerTree(props.layerTree);
125
+ }
126
+ }, [props.layerTree]);
127
+ return (React.createElement("div", { id: "jp-gis-layer-tree", onDrop: _onDrop, onDragOver: _onDragOver }, layerTree.map(layer => typeof layer === 'string' ? (React.createElement(LayerComponent, { key: layer, gisModel: model, layerId: layer, onClick: onItemClick })) : (React.createElement(LayerGroupComponent, { key: layer.name, gisModel: model, group: layer, onClick: onItemClick, state: props.state })))));
139
128
  };
140
129
  /**
141
130
  * The component to handle group of layers.
@@ -350,6 +339,23 @@ const LayerComponent = props => {
350
339
  handleRenameCancel();
351
340
  }
352
341
  };
342
+ /**
343
+ * Set layer to current map view.
344
+ */
345
+ const moveToExtent = () => {
346
+ gisModel === null || gisModel === void 0 ? void 0 : gisModel.centerOnPosition(layerId);
347
+ };
348
+ const getSlideNumber = () => {
349
+ if (!gisModel) {
350
+ return;
351
+ }
352
+ const { story } = gisModel.getSelectedStory();
353
+ if (!(story === null || story === void 0 ? void 0 : story.storySegments)) {
354
+ return;
355
+ }
356
+ const slideNum = story.storySegments.indexOf(layerId) + 1;
357
+ return slideNum;
358
+ };
353
359
  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' } },
354
360
  React.createElement("div", { className: LAYER_TITLE_CLASS, onClick: setSelection, onContextMenu: setSelection, style: { display: 'flex' } },
355
361
  hasSupportedSymbology && (React.createElement(Button, { minimal: true, onClick: e => {
@@ -357,8 +363,8 @@ const LayerComponent = props => {
357
363
  setExpanded(v => !v);
358
364
  }, title: expanded ? 'Hide legend' : 'Show legend' },
359
365
  React.createElement(LabIcon.resolveReact, { icon: expanded ? caretDownIcon : caretRightIcon, tag: "span" }))),
360
- React.createElement(Button, { title: layer.visible ? 'Hide layer' : 'Show layer', onClick: toggleVisibility, minimal: true },
361
- React.createElement(LabIcon.resolveReact, { icon: layer.visible ? visibilityIcon : nonVisibilityIcon, className: `${LAYER_ICON_CLASS}${layer.visible ? '' : ' jp-gis-mod-hidden'}`, tag: "span" })),
366
+ 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 },
367
+ React.createElement(LabIcon.resolveReact, { icon: layer.visible ? visibilityIcon : nonVisibilityIcon, className: `${LAYER_ICON_CLASS}${layer.visible ? '' : ' jp-gis-mod-hidden'}`, tag: "span" }))),
362
368
  icons.has(layer.type) && (React.createElement(LabIcon.resolveReact, Object.assign({}, icons.get(layer.type), { className: LAYER_ICON_CLASS }))),
363
369
  isEditing ? (React.createElement("input", { type: "text", value: editValue, onChange: e => setEditValue(e.target.value), onKeyDown: handleRenameKeyDown, onBlur: handleRenameSave, className: LAYER_TEXT_CLASS, style: {
364
370
  flex: 1,
@@ -367,7 +373,9 @@ const LayerComponent = props => {
367
373
  padding: '2px 4px',
368
374
  fontSize: 'inherit',
369
375
  fontFamily: 'inherit',
370
- }, autoFocus: true })) : (React.createElement("span", { id: id, className: LAYER_TEXT_CLASS, tabIndex: -2 }, name))),
376
+ }, autoFocus: true })) : (React.createElement("span", { id: id, className: LAYER_TEXT_CLASS, tabIndex: -2 }, name)),
377
+ React.createElement(Button, { title: 'Move map to the extent of the layer', onClick: moveToExtent, minimal: true },
378
+ React.createElement(LabIcon.resolveReact, { icon: targetWithCenterIcon, className: LAYER_ICON_CLASS, tag: "span" }))),
371
379
  expanded && gisModel && hasSupportedSymbology && (React.createElement("div", { style: { marginTop: 6, width: '100%' } },
372
380
  React.createElement(LegendItem, { layerId: layerId, model: gisModel })))));
373
381
  };
@@ -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,12 @@
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 1rem 1rem 0',
9
+ } },
10
+ React.createElement("label", { htmlFor: "preview-mode-switch", style: { fontSize: '0.875rem' } }, "Preview Mode"),
11
+ React.createElement(Switch, { id: "preview-mode-switch", checked: checked, onCheckedChange: onCheckedChange })));
12
+ }
@@ -0,0 +1,7 @@
1
+ import { IJupyterGISModel } from '@jupytergis/schema';
2
+ import React from 'react';
3
+ interface IStoryPanelProps {
4
+ model: IJupyterGISModel;
5
+ }
6
+ export declare function StoryEditorPanel({ model }: IStoryPanelProps): React.JSX.Element;
7
+ export default StoryEditorPanel;
@@ -0,0 +1,29 @@
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 { StoryEditorPropertiesForm } from "../../../formbuilder/objectform/StoryEditorForm";
6
+ import { Button } from "../../../shared/components/Button";
7
+ import { deepCopy } from "../../../tools";
8
+ const storyMapSchema = deepCopy(jgisSchema.definitions.jGISStoryMap);
9
+ const AddStorySegmentButton = ({ model }) => (React.createElement(Button, { onClick: () => model.addStorySegment() },
10
+ React.createElement(FontAwesomeIcon, { icon: faLink }),
11
+ " Add Story Segment"));
12
+ export function StoryEditorPanel({ model }) {
13
+ const { storySegmentId, story } = useMemo(() => {
14
+ return model.getSelectedStory();
15
+ }, [model, model.sharedModel.stories]);
16
+ const syncStoryData = (properties) => {
17
+ model.sharedModel.updateStoryMap(storySegmentId, properties);
18
+ };
19
+ if (!story) {
20
+ return (React.createElement("div", null,
21
+ React.createElement("p", null, "No Story Map available."),
22
+ 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."),
23
+ React.createElement(AddStorySegmentButton, { model: model })));
24
+ }
25
+ return (React.createElement("div", { style: { padding: '0 0.5rem 0.5rem 0.5rem' } },
26
+ React.createElement(StoryEditorPropertiesForm, { formContext: "update", sourceData: story, model: model, schema: storyMapSchema, syncData: syncStoryData, filePath: model.filePath }),
27
+ React.createElement(AddStorySegmentButton, { model: model })));
28
+ }
29
+ export default StoryEditorPanel;
@@ -0,0 +1,9 @@
1
+ import React from 'react';
2
+ interface IStoryNavBarProps {
3
+ onPrev: () => void;
4
+ onNext: () => void;
5
+ hasPrev: boolean;
6
+ hasNext: boolean;
7
+ }
8
+ declare function StoryNavBar({ onPrev, onNext, hasPrev, hasNext }: IStoryNavBarProps): React.JSX.Element;
9
+ 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 }) {
5
+ return (React.createElement("div", { style: { display: 'flex', gap: '8px', justifyContent: 'center' } },
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,7 @@
1
+ import { IJupyterGISModel } from '@jupytergis/schema';
2
+ import React from 'react';
3
+ interface IStoryViewerPanelProps {
4
+ model: IJupyterGISModel;
5
+ }
6
+ declare function StoryViewerPanel({ model }: IStoryViewerPanelProps): React.JSX.Element;
7
+ export default StoryViewerPanel;