@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.
- package/lib/commands/BaseCommandIDs.d.ts +1 -0
- package/lib/commands/BaseCommandIDs.js +2 -0
- package/lib/commands/index.js +14 -0
- package/lib/constants.js +1 -0
- package/lib/dialogs/symbology/vector_layer/types/Categorized.js +1 -5
- package/lib/formbuilder/formselectors.js +5 -1
- package/lib/formbuilder/objectform/StoryEditorForm.d.ts +8 -0
- package/lib/formbuilder/objectform/StoryEditorForm.js +10 -0
- package/lib/formbuilder/objectform/components/StorySegmentReset.d.ts +8 -0
- package/lib/formbuilder/objectform/components/StorySegmentReset.js +24 -0
- package/lib/formbuilder/objectform/layer/index.d.ts +1 -0
- package/lib/formbuilder/objectform/layer/index.js +1 -0
- package/lib/formbuilder/objectform/layer/storySegmentLayerForm.d.ts +5 -0
- package/lib/formbuilder/objectform/layer/storySegmentLayerForm.js +32 -0
- package/lib/mainview/mainView.js +61 -7
- package/lib/panelview/components/layers.d.ts +2 -1
- package/lib/panelview/components/layers.js +31 -23
- package/lib/panelview/components/story-maps/PreviewModeSwitch.d.ts +7 -0
- package/lib/panelview/components/story-maps/PreviewModeSwitch.js +12 -0
- package/lib/panelview/components/story-maps/StoryEditorPanel.d.ts +7 -0
- package/lib/panelview/components/story-maps/StoryEditorPanel.js +29 -0
- package/lib/panelview/components/story-maps/StoryNavBar.d.ts +9 -0
- package/lib/panelview/components/story-maps/StoryNavBar.js +11 -0
- package/lib/panelview/components/story-maps/StoryViewerPanel.d.ts +7 -0
- package/lib/panelview/components/story-maps/StoryViewerPanel.js +166 -0
- package/lib/panelview/leftpanel.js +87 -5
- package/lib/panelview/rightpanel.js +32 -2
- package/lib/shared/components/Calendar.d.ts +1 -1
- package/lib/shared/components/Command.d.ts +18 -0
- package/lib/shared/components/Command.js +60 -0
- package/lib/shared/components/Dialog.d.ts +15 -0
- package/lib/shared/components/Dialog.js +62 -0
- package/lib/shared/components/RadioGroup.d.ts +5 -0
- package/lib/shared/components/RadioGroup.js +26 -0
- package/lib/shared/components/Switch.d.ts +4 -0
- package/lib/shared/components/Switch.js +20 -0
- package/lib/toolbar/widget.d.ts +10 -0
- package/lib/toolbar/widget.js +49 -0
- package/lib/tools.js +1 -1
- package/package.json +8 -3
- package/style/base.css +4 -0
- package/style/leftPanel.css +18 -0
- package/style/shared/button.css +1 -1
- package/style/shared/dialog.css +177 -0
- package/style/shared/radioGroup.css +55 -0
- package/style/shared/switch.css +63 -0
- package/style/shared/tabs.css +3 -2
- package/style/storyPanel.css +68 -0
- 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';
|
package/lib/commands/index.js
CHANGED
|
@@ -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
|
|
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;
|
|
@@ -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
|
+
}
|
package/lib/mainview/mainView.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
1602
|
-
view.
|
|
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(
|
|
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
|
-
|
|
125
|
-
setLayerTree(
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
|
|
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,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;
|