@jupytergis/base 0.12.1 → 0.12.2

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 (52) hide show
  1. package/lib/dialogs/layerBrowserDialog.d.ts +3 -3
  2. package/lib/dialogs/layerBrowserDialog.js +9 -10
  3. package/lib/formbuilder/objectform/StoryEditorForm.js +5 -5
  4. package/lib/mainview/mainView.d.ts +8 -2
  5. package/lib/mainview/mainView.js +36 -7
  6. package/lib/mainview/mainviewwidget.js +2 -2
  7. package/lib/panelview/leftpanel.d.ts +2 -1
  8. package/lib/panelview/leftpanel.js +28 -20
  9. package/lib/panelview/rightpanel.d.ts +2 -1
  10. package/lib/panelview/rightpanel.js +12 -18
  11. package/lib/panelview/story-maps/MobileSpectaPanel.d.ts +7 -0
  12. package/lib/panelview/story-maps/MobileSpectaPanel.js +114 -0
  13. package/lib/panelview/story-maps/StoryNavBar.d.ts +3 -2
  14. package/lib/panelview/story-maps/StoryNavBar.js +18 -6
  15. package/lib/panelview/story-maps/StoryViewerPanel.d.ts +10 -0
  16. package/lib/panelview/story-maps/StoryViewerPanel.js +44 -18
  17. package/lib/panelview/story-maps/components/StoryImageSection.d.ts +2 -7
  18. package/lib/panelview/story-maps/components/StoryImageSection.js +2 -4
  19. package/lib/panelview/story-maps/components/StorySubtitleSection.d.ts +2 -6
  20. package/lib/panelview/story-maps/components/StorySubtitleSection.js +2 -4
  21. package/lib/panelview/story-maps/components/StoryTitleSection.d.ts +2 -7
  22. package/lib/panelview/story-maps/components/StoryTitleSection.js +2 -3
  23. package/lib/shared/components/Button.js +2 -2
  24. package/lib/shared/components/Calendar.js +0 -1
  25. package/lib/shared/components/Checkbox.d.ts +1 -1
  26. package/lib/shared/components/Checkbox.js +1 -1
  27. package/lib/shared/components/Dialog.d.ts +1 -1
  28. package/lib/shared/components/Dialog.js +1 -1
  29. package/lib/shared/components/Drawer.d.ts +13 -0
  30. package/lib/shared/components/Drawer.js +59 -0
  31. package/lib/shared/components/DropdownMenu.d.ts +1 -1
  32. package/lib/shared/components/DropdownMenu.js +1 -1
  33. package/lib/shared/components/Popover.d.ts +1 -1
  34. package/lib/shared/components/Popover.js +1 -1
  35. package/lib/shared/components/RadioGroup.d.ts +1 -1
  36. package/lib/shared/components/RadioGroup.js +1 -1
  37. package/lib/shared/components/Sheet.d.ts +15 -0
  38. package/lib/shared/components/Sheet.js +64 -0
  39. package/lib/shared/components/Switch.d.ts +1 -1
  40. package/lib/shared/components/Switch.js +1 -1
  41. package/lib/shared/components/Tabs.d.ts +1 -1
  42. package/lib/shared/components/Tabs.js +1 -1
  43. package/lib/shared/components/ToggleGroup.d.ts +1 -1
  44. package/lib/shared/components/ToggleGroup.js +1 -1
  45. package/lib/shared/hooks/useMediaQuery.d.ts +9 -0
  46. package/lib/shared/hooks/useMediaQuery.js +32 -0
  47. package/lib/tools.js +21 -34
  48. package/package.json +5 -12
  49. package/style/base.css +7 -0
  50. package/style/shared/drawer.css +154 -0
  51. package/style/shared/sheet.css +258 -0
  52. package/style/storyPanel.css +32 -4
@@ -1,11 +1,11 @@
1
- import { IJGISFormSchemaRegistry, IJupyterGISModel, IRasterLayerGalleryEntry } from '@jupytergis/schema';
1
+ import { IJGISFormSchemaRegistry, IJupyterGISModel, ILayerGalleryEntry } from '@jupytergis/schema';
2
2
  import { Dialog } from '@jupyterlab/apputils';
3
3
  import { PromiseDelegate } from '@lumino/coreutils';
4
4
  import { Signal } from '@lumino/signaling';
5
5
  import React from 'react';
6
6
  interface ILayerBrowserDialogProps {
7
7
  model: IJupyterGISModel;
8
- registry: IRasterLayerGalleryEntry[];
8
+ registry: ILayerGalleryEntry[];
9
9
  formSchemaRegistry: IJGISFormSchemaRegistry;
10
10
  okSignalPromise: PromiseDelegate<Signal<Dialog<any>, number>>;
11
11
  cancel: () => void;
@@ -13,7 +13,7 @@ interface ILayerBrowserDialogProps {
13
13
  export declare const LayerBrowserComponent: React.FC<ILayerBrowserDialogProps>;
14
14
  export interface ILayerBrowserOptions {
15
15
  model: IJupyterGISModel;
16
- registry: IRasterLayerGalleryEntry[];
16
+ registry: ILayerGalleryEntry[];
17
17
  formSchemaRegistry: IJGISFormSchemaRegistry;
18
18
  }
19
19
  export declare class LayerBrowserWidget extends Dialog<boolean> {
@@ -5,14 +5,14 @@ import { PromiseDelegate, UUID } from '@lumino/coreutils';
5
5
  import { Signal } from '@lumino/signaling';
6
6
  import React, { useEffect, useState } from 'react';
7
7
  import { CreationFormWrapper } from './layerCreationFormDialog';
8
- import CUSTOM_RASTER_IMAGE from '../../rasterlayer_gallery/custom_raster.png';
8
+ import CUSTOM_RASTER_IMAGE from '../../layer_gallery/custom_raster.png';
9
9
  export const LayerBrowserComponent = ({ model, registry, formSchemaRegistry, okSignalPromise, cancel, }) => {
10
10
  const [searchTerm, setSearchTerm] = useState('');
11
11
  const [activeLayers, setActiveLayers] = useState([]);
12
12
  const [selectedCategory, setSelectedCategory] = useState();
13
13
  const [creatingCustomRaster, setCreatingCustomRaster] = useState(false);
14
14
  const [galleryWithCategory, setGalleryWithCategory] = useState(registry);
15
- const providers = [...new Set(registry.map(item => item.source.provider))];
15
+ const providers = [...new Set(registry.map(item => item.provider))];
16
16
  const filteredGallery = galleryWithCategory.filter(item => item.name.toLowerCase().includes(searchTerm));
17
17
  useEffect(() => {
18
18
  model.sharedModel.layersChanged.connect(handleLayerChange);
@@ -37,7 +37,7 @@ export const LayerBrowserComponent = ({ model, registry, formSchemaRegistry, okS
37
37
  selectedCategory === null || selectedCategory === void 0 ? void 0 : selectedCategory.classList.remove('jGIS-layer-browser-category-selected');
38
38
  const filteredGallery = sameAsOld
39
39
  ? registry
40
- : registry.filter(item => { var _a; return (_a = item.source.provider) === null || _a === void 0 ? void 0 : _a.includes(categoryTab.innerText); });
40
+ : registry.filter(item => { var _a; return (_a = item.provider) === null || _a === void 0 ? void 0 : _a.includes(categoryTab.innerText); });
41
41
  setGalleryWithCategory(filteredGallery);
42
42
  setSearchTerm('');
43
43
  setSelectedCategory(sameAsOld ? null : categoryTab);
@@ -52,15 +52,13 @@ export const LayerBrowserComponent = ({ model, registry, formSchemaRegistry, okS
52
52
  const handleTileClick = (tile) => {
53
53
  const sourceId = UUID.uuid4();
54
54
  const sourceModel = {
55
- type: 'RasterSource',
55
+ type: tile.sourceType,
56
56
  name: tile.name,
57
- parameters: tile.source,
57
+ parameters: tile.sourceParameters,
58
58
  };
59
59
  const layerModel = {
60
- type: 'RasterLayer',
61
- parameters: {
62
- source: sourceId,
63
- },
60
+ type: tile.layerType,
61
+ parameters: Object.assign(Object.assign({}, tile.layerParameters), { source: sourceId }),
64
62
  visible: true,
65
63
  name: tile.name + ' Layer',
66
64
  };
@@ -113,7 +111,8 @@ export const LayerBrowserComponent = ({ model, registry, formSchemaRegistry, okS
113
111
  React.createElement("div", { className: "jGIS-layer-browser-text-container" },
114
112
  React.createElement("div", { className: "jGIS-layer-browser-text-info" },
115
113
  React.createElement("h3", { className: "jGIS-layer-browser-text-header jGIS-layer-browser-text-general" }, tile.name)),
116
- React.createElement("p", { className: "jGIS-layer-browser-text-general jGIS-layer-browser-text-source" }, tile.source.attribution))))))));
114
+ React.createElement("div", null, tile.sourceType),
115
+ React.createElement("p", { className: "jGIS-layer-browser-text-general jGIS-layer-browser-text-source" }, tile.description))))))));
117
116
  };
118
117
  export class LayerBrowserWidget extends Dialog {
119
118
  constructor(options) {
@@ -1,5 +1,5 @@
1
- import { BaseForm } from './baseform';
2
1
  import { getCssVarAsColor } from "../../tools";
2
+ import { BaseForm } from './baseform';
3
3
  /**
4
4
  * The form to modify story map properties.
5
5
  */
@@ -10,7 +10,7 @@ export class StoryEditorPropertiesForm extends BaseForm {
10
10
  uiSchema.presentationBgColor = {
11
11
  'ui:widget': 'color',
12
12
  };
13
- uiSchema.presentaionTextColor = {
13
+ uiSchema.presentationTextColor = {
14
14
  'ui:widget': 'color',
15
15
  };
16
16
  // Set default values from theme CSS variables when not already in data
@@ -22,11 +22,11 @@ export class StoryEditorPropertiesForm extends BaseForm {
22
22
  schemaProps.presentationBgColor.default = defaultBg;
23
23
  }
24
24
  }
25
- if ((schemaProps === null || schemaProps === void 0 ? void 0 : schemaProps.presentaionTextColor) &&
26
- (data === null || data === void 0 ? void 0 : data.presentaionTextColor) === undefined) {
25
+ if ((schemaProps === null || schemaProps === void 0 ? void 0 : schemaProps.presentationTextColor) &&
26
+ (data === null || data === void 0 ? void 0 : data.presentationTextColor) === undefined) {
27
27
  const defaultText = getCssVarAsColor('--jp-ui-font-color0');
28
28
  if (defaultText) {
29
- schemaProps.presentaionTextColor.default = defaultText;
29
+ schemaProps.presentationTextColor.default = defaultText;
30
30
  }
31
31
  }
32
32
  }
@@ -1,4 +1,4 @@
1
- import { IAnnotation, IAnnotationModel, IDict, IJGISFilterItem, IJGISFormSchemaRegistry, IJGISLayer, IJGISSource } from '@jupytergis/schema';
1
+ import { IAnnotation, IAnnotationModel, IDict, IJGISFilterItem, IJGISFormSchemaRegistry, IJGISLayer, IJGISSource, IJupyterGISSettings } from '@jupytergis/schema';
2
2
  import { User } from '@jupyterlab/services';
3
3
  import { IStateDB } from '@jupyterlab/statedb';
4
4
  import { Layer } from 'ol/layer';
@@ -10,6 +10,8 @@ interface IProps {
10
10
  state?: IStateDB;
11
11
  formSchemaRegistry?: IJGISFormSchemaRegistry;
12
12
  annotationModel?: IAnnotationModel;
13
+ /** True when viewport matches (max-width: 768px). Injected by MainViewWithMediaQuery. */
14
+ isMobile?: boolean;
13
15
  }
14
16
  interface IStates {
15
17
  id: string;
@@ -31,6 +33,7 @@ interface IStates {
31
33
  }>;
32
34
  displayTemporalController: boolean;
33
35
  filterStates: IDict<IJGISFilterItem | undefined>;
36
+ jgisSettings: IJupyterGISSettings;
34
37
  isSpectaPresentation: boolean;
35
38
  }
36
39
  export declare class MainView extends React.Component<IProps, IStates> {
@@ -136,6 +139,7 @@ export declare class MainView extends React.Component<IProps, IStates> {
136
139
  removeLayer(id: string): void;
137
140
  private _onClientSharedStateChanged;
138
141
  private _onSharedOptionsChanged;
142
+ private _syncSettingsFromRegistry;
139
143
  private _onSettingsChanged;
140
144
  private updateOptions;
141
145
  private _onViewChanged;
@@ -222,4 +226,6 @@ export declare class MainView extends React.Component<IProps, IStates> {
222
226
  private _isSpectaPresentationInitialized;
223
227
  private _storyScrollHandler;
224
228
  }
225
- export {};
229
+ /** Thin wrapper that injects isMobile from useMediaQuery so MainView can use it in JSX. */
230
+ declare function MainViewWithMediaQuery(props: IProps): React.JSX.Element;
231
+ export { MainViewWithMediaQuery };
@@ -39,6 +39,7 @@ import * as React from 'react';
39
39
  import AnnotationFloater from "../annotations/components/AnnotationFloater";
40
40
  import { CommandIDs } from "../constants";
41
41
  import { LoadingOverlay } from "../shared/components/loading";
42
+ import useMediaQuery from "../shared/hooks/useMediaQuery";
42
43
  import StatusBar from "../statusbar/StatusBar";
43
44
  import { debounce, isLightTheme, loadFile, throttle } from "../tools";
44
45
  import CollaboratorPointers from './CollaboratorPointers';
@@ -47,6 +48,7 @@ import TemporalSlider from './TemporalSlider';
47
48
  import { hexToRgb } from '../dialogs/symbology/colorRampUtils';
48
49
  import { markerIcon } from '../icons';
49
50
  import { LeftPanel, RightPanel } from '../panelview';
51
+ import { MobileSpectaPanel } from '../panelview/story-maps/MobileSpectaPanel';
50
52
  import StoryViewerPanel from '../panelview/story-maps/StoryViewerPanel';
51
53
  export class MainView extends React.Component {
52
54
  constructor(props) {
@@ -529,7 +531,7 @@ export class MainView extends React.Component {
529
531
  }
530
532
  const story = this._model.getSelectedStory().story;
531
533
  const bgColor = story === null || story === void 0 ? void 0 : story.presentationBgColor;
532
- const textColor = story === null || story === void 0 ? void 0 : story.presentaionTextColor;
534
+ const textColor = story === null || story === void 0 ? void 0 : story.presentationTextColor;
533
535
  // Set background color
534
536
  if (bgColor) {
535
537
  const rgb = hexToRgb(bgColor);
@@ -634,6 +636,9 @@ export class MainView extends React.Component {
634
636
  this._model.geolocationChanged.connect(this._handleGeolocationChanged, this);
635
637
  this._model.flyToGeometrySignal.connect(this.flyToGeometry, this);
636
638
  this._model.highlightFeatureSignal.connect(this.highlightFeatureOnMap, this);
639
+ Promise.resolve().then(() => {
640
+ this._syncSettingsFromRegistry();
641
+ });
637
642
  // Watch isIdentifying and clear the highlight when Identify Tool is turned off
638
643
  this._model.sharedModel.awareness.on('change', () => {
639
644
  var _a;
@@ -653,6 +658,7 @@ export class MainView extends React.Component {
653
658
  loadingErrors: [],
654
659
  displayTemporalController: false,
655
660
  filterStates: {},
661
+ jgisSettings: this._model.jgisSettings,
656
662
  isSpectaPresentation: false,
657
663
  };
658
664
  this._sources = [];
@@ -1564,8 +1570,16 @@ export class MainView extends React.Component {
1564
1570
  this._isPositionInitialized = true;
1565
1571
  }
1566
1572
  }
1567
- _onSettingsChanged(sender, key) {
1568
- if (key !== 'zoomButtonsEnabled' || !this._Map) {
1573
+ async _syncSettingsFromRegistry() {
1574
+ const composite = this._model.jgisSettings;
1575
+ if (composite) {
1576
+ this.setState({ jgisSettings: composite });
1577
+ this._onSettingsChanged();
1578
+ }
1579
+ }
1580
+ _onSettingsChanged() {
1581
+ this.setState({ jgisSettings: this._model.jgisSettings });
1582
+ if (!this._Map) {
1569
1583
  return;
1570
1584
  }
1571
1585
  const enabled = this._model.jgisSettings.zoomButtonsEnabled;
@@ -1795,6 +1809,14 @@ export class MainView extends React.Component {
1795
1809
  if ((jgisLayer === null || jgisLayer === void 0 ? void 0 : jgisLayer.type) === 'StorySegmentLayer') {
1796
1810
  const layerParams = jgisLayer.parameters;
1797
1811
  const coords = getCenter(layerParams.extent);
1812
+ // Don't move map if we're already centered on the segment
1813
+ const viewCenter = this._Map.getView().getCenter();
1814
+ const centersEqual = viewCenter !== undefined &&
1815
+ Math.abs(viewCenter[0] - coords[0]) < 1e-9 &&
1816
+ Math.abs(viewCenter[1] - coords[1]) < 1e-9;
1817
+ if (centersEqual) {
1818
+ return;
1819
+ }
1798
1820
  this._flyToPosition({ x: coords[0], y: coords[1] }, layerParams.zoom, ((_c = layerParams.transition.time) !== null && _c !== void 0 ? _c : 1) * 1000, // seconds -> ms
1799
1821
  layerParams.transition.type);
1800
1822
  return;
@@ -2064,11 +2086,18 @@ export class MainView extends React.Component {
2064
2086
  height: '100%',
2065
2087
  } },
2066
2088
  React.createElement("div", { className: "jgis-panels-wrapper" }, !this.state.isSpectaPresentation ? (React.createElement(React.Fragment, null,
2067
- this._state && (React.createElement(LeftPanel, { model: this._model, commands: this._mainViewModel.commands, state: this._state })),
2068
- this._formSchemaRegistry && this._annotationModel && (React.createElement(RightPanel, { model: this._model, commands: this._mainViewModel.commands, formSchemaRegistry: this._formSchemaRegistry, annotationModel: this._annotationModel })))) : (React.createElement("div", { className: "jgis-specta-right-panel-container-mod jgis-right-panel-container" },
2089
+ this._state && (React.createElement(LeftPanel, { model: this._model, commands: this._mainViewModel.commands, state: this._state, settings: this.state.jgisSettings })),
2090
+ this._formSchemaRegistry && this._annotationModel && (React.createElement(RightPanel, { model: this._model, commands: this._mainViewModel.commands, formSchemaRegistry: this._formSchemaRegistry, annotationModel: this._annotationModel, 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" },
2069
2091
  React.createElement("div", { ref: this.spectaContainerRef, className: "jgis-specta-story-panel-container" },
2070
- React.createElement(StoryViewerPanel, { ref: this.storyViewerPanelRef, model: this._model, isSpecta: this.state.isSpectaPresentation }))))),
2092
+ React.createElement(StoryViewerPanel, { ref: this.storyViewerPanelRef, model: this._model, isSpecta: this.state.isSpectaPresentation, className: "jgis-story-viewer-panel-specta-mod" }))))),
2071
2093
  React.createElement("div", { ref: this.controlsToolbarRef, className: "jgis-controls-toolbar" }))),
2072
- React.createElement(StatusBar, { jgisModel: this._model, loading: this.state.loadingLayer, projection: this.state.viewProjection, scale: this.state.scale }))));
2094
+ !this.state.isSpectaPresentation && (React.createElement(StatusBar, { jgisModel: this._model, loading: this.state.loadingLayer, projection: this.state.viewProjection, scale: this.state.scale })))));
2073
2095
  }
2074
2096
  }
2097
+ // ! TODO make mainview a modern react component instead of a class
2098
+ /** Thin wrapper that injects isMobile from useMediaQuery so MainView can use it in JSX. */
2099
+ function MainViewWithMediaQuery(props) {
2100
+ const isMobile = useMediaQuery('(max-width: 768px)');
2101
+ return React.createElement(MainView, Object.assign({}, props, { isMobile: isMobile }));
2102
+ }
2103
+ export { MainViewWithMediaQuery };
@@ -1,6 +1,6 @@
1
1
  import { ReactWidget } from '@jupyterlab/apputils';
2
2
  import * as React from 'react';
3
- import { MainView } from './mainView';
3
+ import { MainViewWithMediaQuery } from './mainView';
4
4
  export class JupyterGISMainViewPanel extends ReactWidget {
5
5
  /**
6
6
  * Construct a `JupyterGISPanel`.
@@ -12,6 +12,6 @@ export class JupyterGISMainViewPanel extends ReactWidget {
12
12
  this._options = options;
13
13
  }
14
14
  render() {
15
- return (React.createElement(MainView, { state: this._state, viewModel: this._options.mainViewModel, formSchemaRegistry: this._options.formSchemaRegistry, annotationModel: this._options.annotationModel }));
15
+ return (React.createElement(MainViewWithMediaQuery, { state: this._state, viewModel: this._options.mainViewModel, formSchemaRegistry: this._options.formSchemaRegistry, annotationModel: this._options.annotationModel }));
16
16
  }
17
17
  }
@@ -1,4 +1,4 @@
1
- import { IJupyterGISModel, SelectionType } from '@jupytergis/schema';
1
+ import { IJupyterGISModel, SelectionType, IJupyterGISSettings } from '@jupytergis/schema';
2
2
  import { IStateDB } from '@jupyterlab/statedb';
3
3
  import { CommandRegistry } from '@lumino/commands';
4
4
  import { MouseEvent as ReactMouseEvent } from 'react';
@@ -12,6 +12,7 @@ interface ILeftPanelProps {
12
12
  model: IJupyterGISModel;
13
13
  state: IStateDB;
14
14
  commands: CommandRegistry;
15
+ settings: IJupyterGISSettings;
15
16
  }
16
17
  export declare const LeftPanel: React.FC<ILeftPanelProps>;
17
18
  export {};
@@ -6,32 +6,42 @@ import { PanelTabs, TabsContent, TabsList, TabsTrigger, } from '../shared/compon
6
6
  import StacPanel from '../stacBrowser/components/StacPanel';
7
7
  export const LeftPanel = (props) => {
8
8
  var _a;
9
- const [settings, setSettings] = React.useState(props.model.jgisSettings);
10
9
  const [options, setOptions] = React.useState(props.model.getOptions());
11
10
  const storyMapPresentationMode = (_a = options.storyMapPresentationMode) !== null && _a !== void 0 ? _a : false;
12
11
  const [layerTree, setLayerTree] = React.useState(props.model.getLayerTree());
12
+ const hasSyncedInitialSelectionRef = React.useRef(false);
13
13
  const tabInfo = [
14
- !settings.layersDisabled ? { name: 'layers', title: 'Layers' } : false,
15
- !settings.stacBrowserDisabled && !storyMapPresentationMode
14
+ !props.settings.layersDisabled
15
+ ? { name: 'layers', title: 'Layers' }
16
+ : false,
17
+ !props.settings.stacBrowserDisabled && !storyMapPresentationMode
16
18
  ? { name: 'stac', title: 'Stac Browser' }
17
19
  : false,
18
- !settings.filtersDisabled && !storyMapPresentationMode
20
+ !props.settings.filtersDisabled && !storyMapPresentationMode
19
21
  ? { name: 'filters', title: 'Filters' }
20
22
  : false,
21
- !settings.storyMapsDisabled
23
+ !props.settings.storyMapsDisabled
22
24
  ? { name: 'segments', title: 'Segments' }
23
25
  : false,
24
26
  ].filter(Boolean);
25
27
  const [curTab, setCurTab] = React.useState(tabInfo.length > 0 ? tabInfo[0].name : undefined);
26
28
  React.useEffect(() => {
27
- const onSettingsChanged = () => {
28
- setSettings(Object.assign({}, props.model.jgisSettings));
29
- };
30
29
  const onOptionsChanged = () => {
31
30
  setOptions(Object.assign({}, props.model.getOptions()));
32
31
  };
33
32
  const updateLayerTree = () => {
34
- setLayerTree(props.model.getLayerTree() || []);
33
+ const freshTree = props.model.getLayerTree() || [];
34
+ setLayerTree(freshTree);
35
+ // Sync selected to top layer/group only the first time the tree has items
36
+ if (!hasSyncedInitialSelectionRef.current && freshTree.length > 0) {
37
+ hasSyncedInitialSelectionRef.current = true;
38
+ const lastItem = freshTree[freshTree.length - 1];
39
+ const lastId = typeof lastItem === 'string' ? lastItem : lastItem === null || lastItem === void 0 ? void 0 : lastItem.name;
40
+ const lastType = typeof lastItem === 'string' ? 'layer' : 'group';
41
+ if (lastId) {
42
+ props.model.syncSelected({ [lastId]: { type: lastType } }, props.model.getClientId().toString());
43
+ }
44
+ }
35
45
  // Need to let command know when segments get populated
36
46
  props.commands.notifyCommandChanged(CommandIDs.toggleStoryPresentationMode);
37
47
  };
@@ -39,14 +49,12 @@ export const LeftPanel = (props) => {
39
49
  props.model.syncSelected({ [payload.storySegmentId]: { type: 'layer' } }, props.model.getClientId().toString());
40
50
  setCurTab('segments');
41
51
  };
42
- props.model.settingsChanged.connect(onSettingsChanged);
43
52
  props.model.sharedOptionsChanged.connect(onOptionsChanged);
44
53
  props.model.sharedModel.layersChanged.connect(updateLayerTree);
45
54
  props.model.sharedModel.layerTreeChanged.connect(updateLayerTree);
46
55
  props.model.segmentAdded.connect(onSegmentAdded);
47
56
  updateLayerTree();
48
57
  return () => {
49
- props.model.settingsChanged.disconnect(onSettingsChanged);
50
58
  props.model.sharedOptionsChanged.disconnect(onOptionsChanged);
51
59
  props.model.sharedModel.layersChanged.disconnect(updateLayerTree);
52
60
  props.model.sharedModel.layerTreeChanged.disconnect(updateLayerTree);
@@ -110,11 +118,11 @@ export const LeftPanel = (props) => {
110
118
  }
111
119
  props.model.sharedModel.updateStoryMap(storyId, Object.assign(Object.assign({}, story), { storySegments: storySegmentLayerTree }));
112
120
  }, [storySegmentLayerTree]);
113
- const allLeftTabsDisabled = settings.layersDisabled &&
114
- settings.stacBrowserDisabled &&
115
- settings.filtersDisabled &&
116
- settings.storyMapsDisabled;
117
- const leftPanelVisible = !settings.leftPanelDisabled && !allLeftTabsDisabled;
121
+ const allLeftTabsDisabled = props.settings.layersDisabled &&
122
+ props.settings.stacBrowserDisabled &&
123
+ props.settings.filtersDisabled &&
124
+ props.settings.storyMapsDisabled;
125
+ const leftPanelVisible = !props.settings.leftPanelDisabled && !allLeftTabsDisabled;
118
126
  return (React.createElement("div", { className: "jgis-left-panel-container", style: { display: leftPanelVisible ? 'block' : 'none' } },
119
127
  React.createElement(PanelTabs, { curTab: curTab, className: "jgis-panel-tabs" },
120
128
  React.createElement(TabsList, null, tabInfo.map(tab => (React.createElement(TabsTrigger, { className: "jGIS-layer-browser-category", key: tab.name, value: tab.name, onClick: () => {
@@ -125,12 +133,12 @@ export const LeftPanel = (props) => {
125
133
  setCurTab('');
126
134
  }
127
135
  } }, tab.title)))),
128
- !settings.layersDisabled && (React.createElement(TabsContent, { value: "layers", className: "jgis-panel-tab-content jp-gis-layerPanel" },
136
+ !props.settings.layersDisabled && (React.createElement(TabsContent, { value: "layers", className: "jgis-panel-tab-content jp-gis-layerPanel" },
129
137
  React.createElement(LayersBodyComponent, { model: props.model, commands: props.commands, state: props.state, layerTree: filteredLayerTree }))),
130
- !settings.stacBrowserDisabled && (React.createElement(TabsContent, { value: "stac", className: "jgis-panel-tab-content jgis-panel-tab-content-stac-panel" },
138
+ !props.settings.stacBrowserDisabled && (React.createElement(TabsContent, { value: "stac", className: "jgis-panel-tab-content jgis-panel-tab-content-stac-panel" },
131
139
  React.createElement(StacPanel, { model: props.model }))),
132
- !settings.filtersDisabled && (React.createElement(TabsContent, { value: "filters", className: "jgis-panel-tab-content" },
140
+ !props.settings.filtersDisabled && (React.createElement(TabsContent, { value: "filters", className: "jgis-panel-tab-content" },
133
141
  React.createElement(FilterComponent, { model: props.model }))),
134
- !settings.storyMapsDisabled && (React.createElement(TabsContent, { value: "segments", className: "jgis-panel-tab-content" },
142
+ !props.settings.storyMapsDisabled && (React.createElement(TabsContent, { value: "segments", className: "jgis-panel-tab-content" },
135
143
  React.createElement(LayersBodyComponent, { model: props.model, commands: props.commands, state: props.state, layerTree: storySegmentLayerTree }))))));
136
144
  };
@@ -1,4 +1,4 @@
1
- import { IAnnotationModel, IJGISFormSchemaRegistry, IJupyterGISModel } from '@jupytergis/schema';
1
+ import { IAnnotationModel, IJGISFormSchemaRegistry, IJupyterGISModel, IJupyterGISSettings } from '@jupytergis/schema';
2
2
  import { CommandRegistry } from '@lumino/commands';
3
3
  import * as React from 'react';
4
4
  interface IRightPanelProps {
@@ -6,6 +6,7 @@ interface IRightPanelProps {
6
6
  annotationModel: IAnnotationModel;
7
7
  model: IJupyterGISModel;
8
8
  commands: CommandRegistry;
9
+ settings: IJupyterGISSettings;
9
10
  }
10
11
  export declare const RightPanel: React.FC<IRightPanelProps>;
11
12
  export {};
@@ -9,7 +9,6 @@ import { PanelTabs, TabsContent, TabsList, TabsTrigger, } from '../shared/compon
9
9
  export const RightPanel = props => {
10
10
  var _a;
11
11
  const [editorMode, setEditorMode] = React.useState(true);
12
- const [settings, setSettings] = React.useState(props.model.jgisSettings);
13
12
  const [storyMapPresentationMode, setStoryMapPresentationMode] = React.useState((_a = props.model.getOptions().storyMapPresentationMode) !== null && _a !== void 0 ? _a : false);
14
13
  // Only show editor when not in presentation mode and editorMode is true
15
14
  const showEditor = !storyMapPresentationMode && editorMode;
@@ -20,19 +19,19 @@ export const RightPanel = props => {
20
19
  ? 'Story Editor'
21
20
  : 'Story Map';
22
21
  const tabInfo = [
23
- !settings.objectPropertiesDisabled && !storyMapPresentationMode
22
+ !props.settings.objectPropertiesDisabled && !storyMapPresentationMode
24
23
  ? { name: 'objectProperties', title: 'Object Properties' }
25
24
  : false,
26
- !settings.storyMapsDisabled
25
+ !props.settings.storyMapsDisabled
27
26
  ? {
28
27
  name: 'storyPanel',
29
28
  title: storyPanelTitle,
30
29
  }
31
30
  : false,
32
- !settings.annotationsDisabled
31
+ !props.settings.annotationsDisabled
33
32
  ? { name: 'annotations', title: 'Annotations' }
34
33
  : false,
35
- !settings.identifyDisabled
34
+ !props.settings.identifyDisabled
36
35
  ? { name: 'identifyPanel', title: 'Identified Features' }
37
36
  : false,
38
37
  ].filter(Boolean);
@@ -43,9 +42,6 @@ export const RightPanel = props => {
43
42
  return tabInfo.length > 0 ? tabInfo[0].name : '';
44
43
  });
45
44
  React.useEffect(() => {
46
- const onSettingsChanged = () => {
47
- setSettings(Object.assign({}, props.model.jgisSettings));
48
- };
49
45
  const onOptionsChanged = () => {
50
46
  const { storyMapPresentationMode } = props.model.getOptions();
51
47
  setStoryMapPresentationMode(storyMapPresentationMode !== null && storyMapPresentationMode !== void 0 ? storyMapPresentationMode : false);
@@ -63,19 +59,17 @@ export const RightPanel = props => {
63
59
  setCurTab('identifyPanel');
64
60
  }
65
61
  };
66
- props.model.settingsChanged.connect(onSettingsChanged);
67
62
  props.model.sharedOptionsChanged.connect(onOptionsChanged);
68
63
  props.model.clientStateChanged.connect(onAwerenessChanged);
69
64
  return () => {
70
- props.model.settingsChanged.disconnect(onSettingsChanged);
71
65
  props.model.sharedOptionsChanged.disconnect(onOptionsChanged);
72
66
  props.model.clientStateChanged.disconnect(onAwerenessChanged);
73
67
  };
74
68
  }, [props.model]);
75
- const allRightTabsDisabled = settings.objectPropertiesDisabled &&
76
- settings.annotationsDisabled &&
77
- settings.identifyDisabled;
78
- const rightPanelVisible = !settings.rightPanelDisabled && !allRightTabsDisabled;
69
+ const allRightTabsDisabled = props.settings.objectPropertiesDisabled &&
70
+ props.settings.annotationsDisabled &&
71
+ props.settings.identifyDisabled;
72
+ const rightPanelVisible = !props.settings.rightPanelDisabled && !allRightTabsDisabled;
79
73
  const [selectedObjectProperties, setSelectedObjectProperties] = React.useState(undefined);
80
74
  const toggleEditor = () => {
81
75
  setEditorMode(!editorMode);
@@ -90,13 +84,13 @@ export const RightPanel = props => {
90
84
  setCurTab('');
91
85
  }
92
86
  } }, tab.title)))),
93
- !settings.objectPropertiesDisabled && (React.createElement(TabsContent, { value: "objectProperties", className: "jgis-panel-tab-content" },
87
+ !props.settings.objectPropertiesDisabled && (React.createElement(TabsContent, { value: "objectProperties", className: "jgis-panel-tab-content" },
94
88
  React.createElement(ObjectPropertiesReact, { setSelectedObject: setSelectedObjectProperties, selectedObject: selectedObjectProperties, formSchemaRegistry: props.formSchemaRegistry, model: props.model }))),
95
- !settings.storyMapsDisabled && (React.createElement(TabsContent, { value: "storyPanel", className: "jgis-panel-tab-content", style: { paddingTop: 0 } },
89
+ !props.settings.storyMapsDisabled && (React.createElement(TabsContent, { value: "storyPanel", className: "jgis-panel-tab-content", style: { paddingTop: 0 } },
96
90
  !storyMapPresentationMode && (React.createElement(PreviewModeSwitch, { checked: !editorMode, onCheckedChange: toggleEditor })),
97
91
  showEditor ? (React.createElement(StoryEditorPanel, { model: props.model, commands: props.commands })) : (React.createElement(StoryViewerPanel, { model: props.model, isSpecta: false })))),
98
- !settings.annotationsDisabled && (React.createElement(TabsContent, { value: "annotations", className: "jgis-panel-tab-content" },
92
+ !props.settings.annotationsDisabled && (React.createElement(TabsContent, { value: "annotations", className: "jgis-panel-tab-content" },
99
93
  React.createElement(AnnotationsPanel, { annotationModel: props.annotationModel, jgisModel: props.model }))),
100
- !settings.identifyDisabled && (React.createElement(TabsContent, { value: "identifyPanel", className: "jgis-panel-tab-content" },
94
+ !props.settings.identifyDisabled && (React.createElement(TabsContent, { value: "identifyPanel", className: "jgis-panel-tab-content" },
101
95
  React.createElement(IdentifyPanelComponent, { model: props.model }))))));
102
96
  };
@@ -0,0 +1,7 @@
1
+ import { IJupyterGISModel } from '@jupytergis/schema';
2
+ import React from 'react';
3
+ interface IMobileSpectaPanelProps {
4
+ model: IJupyterGISModel;
5
+ }
6
+ export declare function MobileSpectaPanel({ model }: IMobileSpectaPanelProps): React.JSX.Element;
7
+ export {};
@@ -0,0 +1,114 @@
1
+ import React, { useEffect, useState } from 'react';
2
+ import { Button } from "../../shared/components/Button";
3
+ import { Drawer, DrawerContent, DrawerTrigger, } from "../../shared/components/Drawer";
4
+ import StoryViewerPanel from './StoryViewerPanel';
5
+ const MAIN_ID = 'jp-main-content-panel';
6
+ const SEGMENT_PANEL_ID = 'jgis-story-segment-panel';
7
+ const SEGMENT_HEADER_ID = 'jgis-story-segment-header';
8
+ const SNAP_FIRST_MIN = 0.3;
9
+ const SNAP_FIRST_MAX = 0.95;
10
+ const SNAP_FIRST_DEFAULT = 0.7;
11
+ /** Offset (px) for segment header height: margins from p and h1 in story content */
12
+ const SEGMENT_HEADER_OFFSET_PX = 16.8 * 2 + 18.76;
13
+ /**
14
+ * Compute the first snap point so that vaul's --snap-point-height (the
15
+ * transform offset) equals #jgis-story-segment-panel height minus #jgis-story-segment-header height.
16
+ * For a bottom drawer, offset = mainHeight * (1 - snapPoint), so
17
+ * snapPoint = (mainHeight - offset) / mainHeight.
18
+ */
19
+ function getFirstSnapFromSegmentHeader(mainEl, segmentPanelEl, segmentHeaderEl) {
20
+ const mainHeight = mainEl.getBoundingClientRect().height;
21
+ const segmentPanelHeight = segmentPanelEl.getBoundingClientRect().height;
22
+ const segmentHeaderHeight = segmentHeaderEl.getBoundingClientRect().height;
23
+ const offsetPx = segmentPanelHeight - segmentHeaderHeight - SEGMENT_HEADER_OFFSET_PX;
24
+ if (mainHeight <= 0) {
25
+ return SNAP_FIRST_DEFAULT;
26
+ }
27
+ const fraction = (mainHeight - offsetPx) / mainHeight;
28
+ const clamped = Math.max(SNAP_FIRST_MIN, Math.min(SNAP_FIRST_MAX, fraction));
29
+ return clamped;
30
+ }
31
+ /** Build inline styles for specta presentation (bg and text color from story). */
32
+ function getSpectaPresentationStyle(model) {
33
+ const story = model.getSelectedStory().story;
34
+ const bgColor = story === null || story === void 0 ? void 0 : story.presentationBgColor;
35
+ const textColor = story === null || story === void 0 ? void 0 : story.presentationTextColor;
36
+ const style = {};
37
+ if (bgColor) {
38
+ style['--jgis-specta-bg-color'] = bgColor;
39
+ style.backgroundColor = bgColor;
40
+ }
41
+ if (textColor) {
42
+ style['--jgis-specta-text-color'] = textColor;
43
+ style.color = textColor;
44
+ }
45
+ return style;
46
+ }
47
+ export function MobileSpectaPanel({ model }) {
48
+ const [container, setContainer] = useState(null);
49
+ const [snapPoints, setSnapPoints] = useState([
50
+ SNAP_FIRST_DEFAULT,
51
+ 1,
52
+ ]);
53
+ const [snap, setSnap] = useState(snapPoints[0]);
54
+ const presentationStyle = getSpectaPresentationStyle(model);
55
+ // Keep active snap in sync with snapPoints so Vaul's --snap-point-height stays defined.
56
+ useEffect(() => {
57
+ const isInSnapPoints = snapPoints.some(p => p === snap ||
58
+ (typeof p === 'number' &&
59
+ typeof snap === 'number' &&
60
+ Math.abs(p - snap) < 1e-9));
61
+ if (!isInSnapPoints && snapPoints.length > 0) {
62
+ setSnap(snapPoints[0]);
63
+ }
64
+ }, [snapPoints, snap]);
65
+ // Observe #jgis-story-segment-panel (and re-attach when drawer reopens).
66
+ useEffect(() => {
67
+ const mainEl = document.getElementById(MAIN_ID);
68
+ setContainer(mainEl);
69
+ if (!mainEl) {
70
+ return;
71
+ }
72
+ const updateFirstSnap = () => {
73
+ const segmentPanelEl = document.getElementById(SEGMENT_PANEL_ID);
74
+ const segmentHeaderEl = document.getElementById(SEGMENT_HEADER_ID);
75
+ if (segmentPanelEl && segmentHeaderEl) {
76
+ const firstSnap = getFirstSnapFromSegmentHeader(mainEl, segmentPanelEl, segmentHeaderEl);
77
+ setSnapPoints([firstSnap, 1]);
78
+ }
79
+ };
80
+ const resizeObserver = new ResizeObserver(() => updateFirstSnap());
81
+ let observedPanelEl = null;
82
+ const syncHeaderObserver = () => {
83
+ const segmentPanelEl = document.getElementById(SEGMENT_PANEL_ID);
84
+ const segmentHeaderEl = document.getElementById(SEGMENT_HEADER_ID);
85
+ if (!segmentPanelEl ||
86
+ !segmentHeaderEl ||
87
+ segmentPanelEl === observedPanelEl) {
88
+ return;
89
+ }
90
+ if (observedPanelEl) {
91
+ resizeObserver.unobserve(observedPanelEl);
92
+ }
93
+ resizeObserver.observe(segmentPanelEl);
94
+ observedPanelEl = segmentPanelEl;
95
+ updateFirstSnap();
96
+ };
97
+ syncHeaderObserver();
98
+ const mutationObserver = new MutationObserver(syncHeaderObserver);
99
+ mutationObserver.observe(mainEl, {
100
+ childList: true,
101
+ subtree: true,
102
+ });
103
+ return () => {
104
+ resizeObserver.disconnect();
105
+ mutationObserver.disconnect();
106
+ };
107
+ }, []);
108
+ return (React.createElement("div", { className: "jgis-mobile-specta-trigger-wrapper" },
109
+ React.createElement(Drawer, { snapPoints: snapPoints, activeSnapPoint: snap, setActiveSnapPoint: setSnap, direction: "bottom", container: container, noBodyStyles: true },
110
+ React.createElement(DrawerTrigger, { asChild: true },
111
+ React.createElement(Button, null, "Open Story Panel")),
112
+ React.createElement(DrawerContent, { style: presentationStyle },
113
+ React.createElement(StoryViewerPanel, { isSpecta: true, isMobile: true, model: model })))));
114
+ }
@@ -1,10 +1,11 @@
1
1
  import React from 'react';
2
+ import type { StoryNavPlacement } from './StoryViewerPanel';
2
3
  interface IStoryNavBarProps {
4
+ placement: StoryNavPlacement;
3
5
  onPrev: () => void;
4
6
  onNext: () => void;
5
7
  hasPrev: boolean;
6
8
  hasNext: boolean;
7
- isSpecta: boolean;
8
9
  }
9
- declare function StoryNavBar({ onPrev, onNext, hasPrev, hasNext, isSpecta, }: IStoryNavBarProps): React.JSX.Element;
10
+ declare function StoryNavBar({ placement, onPrev, onNext, hasPrev, hasNext, }: IStoryNavBarProps): React.JSX.Element;
10
11
  export default StoryNavBar;