@jupytergis/base 0.8.1 → 0.9.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.
@@ -31,3 +31,11 @@ export declare const selectCompleter = "jupytergis:selectConsoleCompleter";
31
31
  export declare const addAnnotation = "jupytergis:addAnnotation";
32
32
  export declare const zoomToLayer = "jupytergis:zoomToLayer";
33
33
  export declare const downloadGeoJSON = "jupytergis:downloadGeoJSON";
34
+ export declare const toggleLeftPanel = "jupytergis:toggleLeftPanel";
35
+ export declare const toggleRightPanel = "jupytergis:toggleRightPanel";
36
+ export declare const showLayersTab = "jupytergis:showLayersTab";
37
+ export declare const showStacBrowserTab = "jupytergis:showStacBrowserTab";
38
+ export declare const showFiltersTab = "jupytergis:showFiltersTab";
39
+ export declare const showObjectPropertiesTab = "jupytergis:showObjectPropertiesTab";
40
+ export declare const showAnnotationsTab = "jupytergis:showAnnotationsTab";
41
+ export declare const showIdentifyPanelTab = "jupytergis:showIdentifyPanelTab";
@@ -42,3 +42,14 @@ export const selectCompleter = 'jupytergis:selectConsoleCompleter';
42
42
  export const addAnnotation = 'jupytergis:addAnnotation';
43
43
  export const zoomToLayer = 'jupytergis:zoomToLayer';
44
44
  export const downloadGeoJSON = 'jupytergis:downloadGeoJSON';
45
+ // Panel toggles
46
+ export const toggleLeftPanel = 'jupytergis:toggleLeftPanel';
47
+ export const toggleRightPanel = 'jupytergis:toggleRightPanel';
48
+ // Left panel tabs
49
+ export const showLayersTab = 'jupytergis:showLayersTab';
50
+ export const showStacBrowserTab = 'jupytergis:showStacBrowserTab';
51
+ export const showFiltersTab = 'jupytergis:showFiltersTab';
52
+ // Right panel tabs
53
+ export const showObjectPropertiesTab = 'jupytergis:showObjectPropertiesTab';
54
+ export const showAnnotationsTab = 'jupytergis:showAnnotationsTab';
55
+ export const showIdentifyPanelTab = 'jupytergis:showIdentifyPanelTab';
@@ -87,14 +87,17 @@ export function addCommands(app, tracker, translator, formSchemaRegistry, layerB
87
87
  'WebGlLayer',
88
88
  'VectorTileLayer',
89
89
  ].includes(selectedLayer.type);
90
- const isIdentifying = current.model.isIdentifying;
91
- if (isIdentifying && !canIdentify) {
92
- current.model.isIdentifying = false;
90
+ if (current.model.currentMode === 'identifying' && !canIdentify) {
91
+ current.model.currentMode = 'panning';
93
92
  current.node.classList.remove('jGIS-identify-tool');
94
93
  return false;
95
94
  }
96
- return isIdentifying;
95
+ return current.model.currentMode === 'identifying';
97
96
  }, isEnabled: () => {
97
+ var _a;
98
+ if ((_a = tracker.currentWidget) === null || _a === void 0 ? void 0 : _a.model.jgisSettings.identifyDisabled) {
99
+ return false;
100
+ }
98
101
  const selectedLayer = getSingleSelectedLayer(tracker);
99
102
  if (!selectedLayer) {
100
103
  return false;
@@ -114,7 +117,7 @@ export function addCommands(app, tracker, translator, formSchemaRegistry, layerB
114
117
  if (luminoEvent) {
115
118
  const keysPressed = luminoEvent.keys;
116
119
  if (keysPressed === null || keysPressed === void 0 ? void 0 : keysPressed.includes('Escape')) {
117
- current.model.isIdentifying = false;
120
+ current.model.currentMode = 'panning';
118
121
  current.node.classList.remove('jGIS-identify-tool');
119
122
  commands.notifyCommandChanged(CommandIDs.identify);
120
123
  return;
@@ -652,6 +655,165 @@ export function addCommands(app, tracker, translator, formSchemaRegistry, layerB
652
655
  },
653
656
  icon: targetWithCenterIcon,
654
657
  });
658
+ // Panel visibility commands
659
+ commands.addCommand(CommandIDs.toggleLeftPanel, {
660
+ label: trans.__('Toggle Left Panel'),
661
+ isEnabled: () => Boolean(tracker.currentWidget),
662
+ isToggled: () => {
663
+ const current = tracker.currentWidget;
664
+ return current ? !current.model.jgisSettings.leftPanelDisabled : false;
665
+ },
666
+ execute: async () => {
667
+ var _a, _b, _c;
668
+ const current = tracker.currentWidget;
669
+ if (!current) {
670
+ return;
671
+ }
672
+ try {
673
+ const settings = await current.model.getSettings();
674
+ const currentValue = (_c = (_b = (_a = settings === null || settings === void 0 ? void 0 : settings.composite) === null || _a === void 0 ? void 0 : _a.leftPanelDisabled) !== null && _b !== void 0 ? _b : current.model.jgisSettings.leftPanelDisabled) !== null && _c !== void 0 ? _c : false;
675
+ await (settings === null || settings === void 0 ? void 0 : settings.set('leftPanelDisabled', !currentValue));
676
+ commands.notifyCommandChanged(CommandIDs.toggleLeftPanel);
677
+ }
678
+ catch (err) {
679
+ console.error('Failed to toggle Left Panel:', err);
680
+ }
681
+ },
682
+ });
683
+ commands.addCommand(CommandIDs.toggleRightPanel, {
684
+ label: trans.__('Toggle Right Panel'),
685
+ isEnabled: () => Boolean(tracker.currentWidget),
686
+ isToggled: () => {
687
+ const current = tracker.currentWidget;
688
+ return current ? !current.model.jgisSettings.rightPanelDisabled : false;
689
+ },
690
+ execute: async () => {
691
+ var _a, _b, _c;
692
+ const current = tracker.currentWidget;
693
+ if (!current) {
694
+ return;
695
+ }
696
+ try {
697
+ const settings = await current.model.getSettings();
698
+ const currentValue = (_c = (_b = (_a = settings === null || settings === void 0 ? void 0 : settings.composite) === null || _a === void 0 ? void 0 : _a.rightPanelDisabled) !== null && _b !== void 0 ? _b : current.model.jgisSettings.rightPanelDisabled) !== null && _c !== void 0 ? _c : false;
699
+ await (settings === null || settings === void 0 ? void 0 : settings.set('rightPanelDisabled', !currentValue));
700
+ commands.notifyCommandChanged(CommandIDs.toggleRightPanel);
701
+ }
702
+ catch (err) {
703
+ console.error('Failed to toggle Right Panel:', err);
704
+ }
705
+ },
706
+ });
707
+ // Left panel tabs
708
+ commands.addCommand(CommandIDs.showLayersTab, {
709
+ label: trans.__('Show Layers Tab'),
710
+ isEnabled: () => Boolean(tracker.currentWidget),
711
+ isToggled: () => tracker.currentWidget
712
+ ? !tracker.currentWidget.model.jgisSettings.layersDisabled
713
+ : false,
714
+ execute: async () => {
715
+ var _a, _b, _c;
716
+ const current = tracker.currentWidget;
717
+ if (!current) {
718
+ return;
719
+ }
720
+ const settings = await current.model.getSettings();
721
+ const currentValue = (_c = (_b = (_a = settings === null || settings === void 0 ? void 0 : settings.composite) === null || _a === void 0 ? void 0 : _a.layersDisabled) !== null && _b !== void 0 ? _b : current.model.jgisSettings.layersDisabled) !== null && _c !== void 0 ? _c : false;
722
+ await (settings === null || settings === void 0 ? void 0 : settings.set('layersDisabled', !currentValue));
723
+ commands.notifyCommandChanged(CommandIDs.showLayersTab);
724
+ },
725
+ });
726
+ commands.addCommand(CommandIDs.showStacBrowserTab, {
727
+ label: trans.__('Show STAC Browser Tab'),
728
+ isEnabled: () => Boolean(tracker.currentWidget),
729
+ isToggled: () => tracker.currentWidget
730
+ ? !tracker.currentWidget.model.jgisSettings.stacBrowserDisabled
731
+ : false,
732
+ execute: async () => {
733
+ var _a, _b, _c;
734
+ const current = tracker.currentWidget;
735
+ if (!current) {
736
+ return;
737
+ }
738
+ const settings = await current.model.getSettings();
739
+ const currentValue = (_c = (_b = (_a = settings === null || settings === void 0 ? void 0 : settings.composite) === null || _a === void 0 ? void 0 : _a.stacBrowserDisabled) !== null && _b !== void 0 ? _b : current.model.jgisSettings.stacBrowserDisabled) !== null && _c !== void 0 ? _c : false;
740
+ await (settings === null || settings === void 0 ? void 0 : settings.set('stacBrowserDisabled', !currentValue));
741
+ commands.notifyCommandChanged(CommandIDs.showStacBrowserTab);
742
+ },
743
+ });
744
+ commands.addCommand(CommandIDs.showFiltersTab, {
745
+ label: trans.__('Show Filters Tab'),
746
+ isEnabled: () => Boolean(tracker.currentWidget),
747
+ isToggled: () => tracker.currentWidget
748
+ ? !tracker.currentWidget.model.jgisSettings.filtersDisabled
749
+ : false,
750
+ execute: async () => {
751
+ var _a, _b, _c;
752
+ const current = tracker.currentWidget;
753
+ if (!current) {
754
+ return;
755
+ }
756
+ const settings = await current.model.getSettings();
757
+ const currentValue = (_c = (_b = (_a = settings === null || settings === void 0 ? void 0 : settings.composite) === null || _a === void 0 ? void 0 : _a.filtersDisabled) !== null && _b !== void 0 ? _b : current.model.jgisSettings.filtersDisabled) !== null && _c !== void 0 ? _c : false;
758
+ await (settings === null || settings === void 0 ? void 0 : settings.set('filtersDisabled', !currentValue));
759
+ commands.notifyCommandChanged(CommandIDs.showFiltersTab);
760
+ },
761
+ });
762
+ // Right panel tabs
763
+ commands.addCommand(CommandIDs.showObjectPropertiesTab, {
764
+ label: trans.__('Show Object Properties Tab'),
765
+ isEnabled: () => Boolean(tracker.currentWidget),
766
+ isToggled: () => tracker.currentWidget
767
+ ? !tracker.currentWidget.model.jgisSettings.objectPropertiesDisabled
768
+ : false,
769
+ execute: async () => {
770
+ var _a, _b, _c;
771
+ const current = tracker.currentWidget;
772
+ if (!current) {
773
+ return;
774
+ }
775
+ const settings = await current.model.getSettings();
776
+ const currentValue = (_c = (_b = (_a = settings === null || settings === void 0 ? void 0 : settings.composite) === null || _a === void 0 ? void 0 : _a.objectPropertiesDisabled) !== null && _b !== void 0 ? _b : current.model.jgisSettings.objectPropertiesDisabled) !== null && _c !== void 0 ? _c : false;
777
+ await (settings === null || settings === void 0 ? void 0 : settings.set('objectPropertiesDisabled', !currentValue));
778
+ commands.notifyCommandChanged(CommandIDs.showObjectPropertiesTab);
779
+ },
780
+ });
781
+ commands.addCommand(CommandIDs.showAnnotationsTab, {
782
+ label: trans.__('Show Annotations Tab'),
783
+ isEnabled: () => Boolean(tracker.currentWidget),
784
+ isToggled: () => tracker.currentWidget
785
+ ? !tracker.currentWidget.model.jgisSettings.annotationsDisabled
786
+ : false,
787
+ execute: async () => {
788
+ var _a, _b, _c;
789
+ const current = tracker.currentWidget;
790
+ if (!current) {
791
+ return;
792
+ }
793
+ const settings = await current.model.getSettings();
794
+ const currentValue = (_c = (_b = (_a = settings === null || settings === void 0 ? void 0 : settings.composite) === null || _a === void 0 ? void 0 : _a.annotationsDisabled) !== null && _b !== void 0 ? _b : current.model.jgisSettings.annotationsDisabled) !== null && _c !== void 0 ? _c : false;
795
+ await (settings === null || settings === void 0 ? void 0 : settings.set('annotationsDisabled', !currentValue));
796
+ commands.notifyCommandChanged(CommandIDs.showAnnotationsTab);
797
+ },
798
+ });
799
+ commands.addCommand(CommandIDs.showIdentifyPanelTab, {
800
+ label: trans.__('Show Identify Panel Tab'),
801
+ isEnabled: () => Boolean(tracker.currentWidget),
802
+ isToggled: () => tracker.currentWidget
803
+ ? !tracker.currentWidget.model.jgisSettings.identifyDisabled
804
+ : false,
805
+ execute: async () => {
806
+ var _a, _b, _c;
807
+ const current = tracker.currentWidget;
808
+ if (!current) {
809
+ return;
810
+ }
811
+ const settings = await current.model.getSettings();
812
+ const currentValue = (_c = (_b = (_a = settings === null || settings === void 0 ? void 0 : settings.composite) === null || _a === void 0 ? void 0 : _a.identifyDisabled) !== null && _b !== void 0 ? _b : current.model.jgisSettings.identifyDisabled) !== null && _c !== void 0 ? _c : false;
813
+ await (settings === null || settings === void 0 ? void 0 : settings.set('identifyDisabled', !currentValue));
814
+ commands.notifyCommandChanged(CommandIDs.showIdentifyPanelTab);
815
+ },
816
+ });
655
817
  loadKeybindings(commands, keybindings);
656
818
  }
657
819
  var Private;
@@ -0,0 +1,17 @@
1
+ import { IJupyterGISModel } from '@jupytergis/schema';
2
+ interface IUseGetSymbologyProps {
3
+ layerId?: string;
4
+ model: IJupyterGISModel;
5
+ }
6
+ interface IUseGetSymbologyResult {
7
+ symbology: Record<string, any> | null;
8
+ isLoading: boolean;
9
+ error?: Error;
10
+ }
11
+ /**
12
+ * Extracts symbology information (paint/layout + symbologyState)
13
+ * for a given layer from the JupyterGIS model.
14
+ * Keeps symbology updated when the layer changes.
15
+ */
16
+ export declare const useGetSymbology: ({ layerId, model, }: IUseGetSymbologyProps) => IUseGetSymbologyResult;
17
+ export {};
@@ -0,0 +1,65 @@
1
+ var __rest = (this && this.__rest) || function (s, e) {
2
+ var t = {};
3
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
4
+ t[p] = s[p];
5
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
6
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
7
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
8
+ t[p[i]] = s[p[i]];
9
+ }
10
+ return t;
11
+ };
12
+ import { useEffect, useState } from 'react';
13
+ /**
14
+ * Extracts symbology information (paint/layout + symbologyState)
15
+ * for a given layer from the JupyterGIS model.
16
+ * Keeps symbology updated when the layer changes.
17
+ */
18
+ export const useGetSymbology = ({ layerId, model, }) => {
19
+ const [symbology, setSymbology] = useState(null);
20
+ const [isLoading, setIsLoading] = useState(true);
21
+ const [error, setError] = useState();
22
+ useEffect(() => {
23
+ if (!layerId) {
24
+ return;
25
+ }
26
+ let disposed = false;
27
+ const fetchSymbology = () => {
28
+ var _a;
29
+ try {
30
+ setIsLoading(true);
31
+ setError(undefined);
32
+ const layer = model.getLayer(layerId);
33
+ if (!layer) {
34
+ throw new Error(`Layer not found: ${layerId}`);
35
+ }
36
+ const params = (_a = layer.parameters) !== null && _a !== void 0 ? _a : {};
37
+ const { symbologyState, color } = params, rest = __rest(params, ["symbologyState", "color"]);
38
+ const result = Object.assign(Object.assign(Object.assign({}, rest), (color ? { color } : {})), (symbologyState ? { symbologyState } : {}));
39
+ if (!disposed) {
40
+ setSymbology(result);
41
+ }
42
+ }
43
+ catch (err) {
44
+ if (!disposed) {
45
+ setError(err);
46
+ setSymbology(null);
47
+ }
48
+ }
49
+ finally {
50
+ if (!disposed) {
51
+ setIsLoading(false);
52
+ }
53
+ }
54
+ };
55
+ // initial load
56
+ fetchSymbology();
57
+ model.sharedModel.awareness.on('change', () => {
58
+ fetchSymbology();
59
+ });
60
+ return () => {
61
+ disposed = true;
62
+ };
63
+ }, [layerId, model]);
64
+ return { symbology, isLoading, error };
65
+ };
@@ -5,5 +5,5 @@ export declare namespace VectorUtils {
5
5
  const buildRadiusInfo: (layer: IJGISLayer) => IStopRow[];
6
6
  }
7
7
  export declare namespace Utils {
8
- const getValueColorPairs: (stops: number[], selectedRamp: string, nClasses: number) => IStopRow[];
8
+ const getValueColorPairs: (stops: number[], selectedRamp: string, nClasses: number, reverse?: boolean) => IStopRow[];
9
9
  }
@@ -75,12 +75,15 @@ export var VectorUtils;
75
75
  })(VectorUtils || (VectorUtils = {}));
76
76
  export var Utils;
77
77
  (function (Utils) {
78
- Utils.getValueColorPairs = (stops, selectedRamp, nClasses) => {
78
+ Utils.getValueColorPairs = (stops, selectedRamp, nClasses, reverse = false) => {
79
79
  let colorMap = colormap({
80
80
  colormap: selectedRamp,
81
81
  nshades: nClasses > 9 ? nClasses : 9,
82
82
  format: 'rgba',
83
83
  });
84
+ if (reverse) {
85
+ colorMap = [...colorMap].reverse();
86
+ }
84
87
  const valueColorPairs = [];
85
88
  // colormap requires 9 classes to generate the ramp
86
89
  // so we do some tomfoolery to make it work with less than 9 stops
@@ -17,6 +17,7 @@ const Categorized = ({ model, state, okSignalPromise, cancel, layerId, symbology
17
17
  radius: 5,
18
18
  });
19
19
  const manualStyleRef = useRef(manualStyle);
20
+ const [reverseRamp, setReverseRamp] = useState(false);
20
21
  if (!layerId) {
21
22
  return;
22
23
  }
@@ -85,7 +86,7 @@ const Categorized = ({ model, state, okSignalPromise, cancel, layerId, symbology
85
86
  selectedMode: '',
86
87
  });
87
88
  const stops = Array.from(selectableAttributesAndValues[selectedAttribute]).sort((a, b) => a - b);
88
- const valueColorPairs = Utils.getValueColorPairs(stops, selectedRamp, stops.length);
89
+ const valueColorPairs = Utils.getValueColorPairs(stops, selectedRamp, stops.length, reverseRamp);
89
90
  setStopRows(valueColorPairs);
90
91
  };
91
92
  const handleOk = () => {
@@ -124,6 +125,7 @@ const Categorized = ({ model, state, okSignalPromise, cancel, layerId, symbology
124
125
  nClasses: (_b = colorRampOptionsRef.current) === null || _b === void 0 ? void 0 : _b.numberOfShades,
125
126
  mode: (_c = colorRampOptionsRef.current) === null || _c === void 0 ? void 0 : _c.selectedMode,
126
127
  symbologyTab,
128
+ reverse: reverseRamp,
127
129
  };
128
130
  layer.parameters.symbologyState = symbologyState;
129
131
  layer.parameters.color = newStyle;
@@ -188,6 +190,10 @@ const Categorized = ({ model, state, okSignalPromise, cancel, layerId, symbology
188
190
  React.createElement("input", { type: "number", className: "jp-mod-styled", value: manualStyle.radius, onChange: e => {
189
191
  setManualStyle(prev => (Object.assign(Object.assign({}, prev), { radius: +e.target.value })));
190
192
  } })))),
193
+ symbologyTab === 'color' && (React.createElement("div", { className: "jp-gis-symbology-row" },
194
+ React.createElement("label", null,
195
+ React.createElement("input", { type: "checkbox", checked: reverseRamp, onChange: e => setReverseRamp(e.target.checked) }),
196
+ "Reverse Color Ramp"))),
191
197
  React.createElement("div", { className: "jp-gis-layer-symbology-container" },
192
198
  React.createElement(ColorRamp, { layerParams: layer.parameters, modeOptions: [], classifyFunc: buildColorInfoFromClassification, showModeRow: false, showRampSelector: symbologyTab === 'color' }),
193
199
  React.createElement(StopContainer, { selectedMethod: '', stopRows: stopRows, setStopRows: setStopRows }))));
@@ -29,6 +29,7 @@ const Graduated = ({ model, state, okSignalPromise, cancel, layerId, symbologyTa
29
29
  const [radiusManualStyle, setRadiusManualStyle] = useState({
30
30
  radius: 5,
31
31
  });
32
+ const [reverseRamp, setReverseRamp] = useState(false);
32
33
  const colorManualStyleRef = useRef(colorManualStyle);
33
34
  const radiusManualStyleRef = useRef(radiusManualStyle);
34
35
  if (!layerId) {
@@ -161,6 +162,7 @@ const Graduated = ({ model, state, okSignalPromise, cancel, layerId, symbologyTa
161
162
  colorRamp: (_a = colorRampOptionsRef.current) === null || _a === void 0 ? void 0 : _a.selectedRamp,
162
163
  nClasses: (_b = colorRampOptionsRef.current) === null || _b === void 0 ? void 0 : _b.numberOfShades,
163
164
  mode: (_c = colorRampOptionsRef.current) === null || _c === void 0 ? void 0 : _c.selectedMode,
165
+ reverse: reverseRamp,
164
166
  };
165
167
  if (layer.type === 'HeatmapLayer') {
166
168
  layer.type = 'VectorLayer';
@@ -198,7 +200,7 @@ const Graduated = ({ model, state, okSignalPromise, cancel, layerId, symbologyTa
198
200
  }
199
201
  const stopOutputPairs = symbologyTab === 'radius'
200
202
  ? stops.map(v => ({ stop: v, output: v }))
201
- : Utils.getValueColorPairs(stops, selectedRamp, +numberOfShades);
203
+ : Utils.getValueColorPairs(stops, selectedRamp, +numberOfShades, reverseRamp);
202
204
  if (symbologyTab === 'radius') {
203
205
  setRadiusStopRows(stopOutputPairs);
204
206
  }
@@ -257,6 +259,10 @@ const Graduated = ({ model, state, okSignalPromise, cancel, layerId, symbologyTa
257
259
  handleReset('radius');
258
260
  setRadiusManualStyle(Object.assign(Object.assign({}, radiusManualStyle), { radius: +e.target.value }));
259
261
  } })))),
262
+ symbologyTab === 'color' && (React.createElement("div", { className: "jp-gis-symbology-row" },
263
+ React.createElement("label", null,
264
+ React.createElement("input", { type: "checkbox", checked: reverseRamp, onChange: e => setReverseRamp(e.target.checked) }),
265
+ "Reverse Color Ramp"))),
260
266
  React.createElement(ColorRamp, { layerParams: layer.parameters, modeOptions: modeOptions, classifyFunc: buildColorInfoFromClassification, showModeRow: true, showRampSelector: symbologyTab === 'color' }),
261
267
  React.createElement(StopContainer, { selectedMethod: symbologyTab || 'color', stopRows: symbologyTab === 'color' ? colorStopRows : radiusStopRows, setStopRows: symbologyTab === 'color' ? setColorStopRows : setRadiusStopRows })));
262
268
  }
@@ -14,11 +14,13 @@ const Heatmap = ({ model, state, okSignalPromise, cancel, layerId, }) => {
14
14
  radius: 8,
15
15
  blur: 15,
16
16
  });
17
+ const [reverseRamp, setReverseRamp] = useState(false);
17
18
  const selectedRampRef = useRef('viridis');
18
19
  const heatmapOptionsRef = useRef({
19
20
  radius: 8,
20
21
  blur: 15,
21
22
  });
23
+ const reverseRampRef = useRef(false);
22
24
  useEffect(() => {
23
25
  populateOptions();
24
26
  okSignalPromise.promise.then(okSignal => {
@@ -33,7 +35,8 @@ const Heatmap = ({ model, state, okSignalPromise, cancel, layerId, }) => {
33
35
  useEffect(() => {
34
36
  selectedRampRef.current = selectedRamp;
35
37
  heatmapOptionsRef.current = heatmapOptions;
36
- }, [selectedRamp, heatmapOptions]);
38
+ reverseRampRef.current = reverseRamp;
39
+ }, [selectedRamp, heatmapOptions, reverseRamp]);
37
40
  const populateOptions = async () => {
38
41
  var _a;
39
42
  let colorRamp;
@@ -46,14 +49,18 @@ const Heatmap = ({ model, state, okSignalPromise, cancel, layerId, }) => {
46
49
  if (!layer.parameters) {
47
50
  return;
48
51
  }
49
- const colorMap = colormap({
52
+ let colorMap = colormap({
50
53
  colormap: selectedRampRef.current,
51
54
  nshades: 9,
52
55
  format: 'hex',
53
56
  });
57
+ if (reverseRampRef.current) {
58
+ colorMap = [...colorMap].reverse();
59
+ }
54
60
  const symbologyState = {
55
61
  renderType: 'Heatmap',
56
62
  colorRamp: selectedRampRef.current,
63
+ reverse: reverseRampRef.current,
57
64
  };
58
65
  layer.parameters.symbologyState = symbologyState;
59
66
  layer.parameters.color = colorMap;
@@ -68,6 +75,10 @@ const Heatmap = ({ model, state, okSignalPromise, cancel, layerId, }) => {
68
75
  React.createElement("div", { className: "jp-gis-symbology-row jp-gis-heatmap" },
69
76
  React.createElement("label", { htmlFor: "color-ramp-select" }, "Color Ramp:"),
70
77
  React.createElement(CanvasSelectComponent, { selectedRamp: selectedRamp, setSelected: setSelectedRamp })),
78
+ React.createElement("div", { className: "jp-gis-symbology-row" },
79
+ React.createElement("label", null,
80
+ React.createElement("input", { type: "checkbox", checked: reverseRamp, onChange: e => setReverseRamp(e.target.checked) }),
81
+ "Reverse Color Ramp")),
71
82
  React.createElement("div", { className: "jp-gis-symbology-row" },
72
83
  React.createElement("label", { htmlFor: 'vector-value-select' }, "Radius:"),
73
84
  React.createElement("input", { type: "number", value: heatmapOptions.radius, className: "jp-mod-styled", onChange: event => setHetamapOptions(prevState => (Object.assign(Object.assign({}, prevState), { radius: +event.target.value }))) })),
package/lib/gdal.js CHANGED
@@ -11,5 +11,3 @@ export async function getGdal() {
11
11
  useWorker: false,
12
12
  });
13
13
  }
14
- // Early load gdal
15
- getGdal();
@@ -201,5 +201,6 @@ export declare class MainView extends React.Component<IProps, IStates> {
201
201
  private _state?;
202
202
  private _formSchemaRegistry?;
203
203
  private _annotationModel?;
204
+ private _featurePropertyCache;
204
205
  }
205
206
  export {};
@@ -1,3 +1,14 @@
1
+ var __rest = (this && this.__rest) || function (s, e) {
2
+ var t = {};
3
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
4
+ t[p] = s[p];
5
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
6
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
7
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
8
+ t[p[i]] = s[p[i]];
9
+ }
10
+ return t;
11
+ };
1
12
  import { JupyterGISModel, } from '@jupytergis/schema';
2
13
  import { showErrorMessage } from '@jupyterlab/apputils';
3
14
  import { CommandRegistry } from '@lumino/commands';
@@ -103,7 +114,7 @@ export class MainView extends React.Component {
103
114
  return layer === this.getLayer(selectedLayerId);
104
115
  },
105
116
  condition: (event) => {
106
- return singleClick(event) && this._model.isIdentifying;
117
+ return singleClick(event) && this._model.currentMode === 'identifying';
107
118
  },
108
119
  style: styleFunction,
109
120
  });
@@ -441,6 +452,7 @@ export class MainView extends React.Component {
441
452
  this._ready = false;
442
453
  this._sourceToLayerMap = new Map();
443
454
  this._originalFeatures = {};
455
+ this._featurePropertyCache = new Map();
444
456
  this._state = props.state;
445
457
  this._formSchemaRegistry = props.formSchemaRegistry;
446
458
  this._annotationModel = props.annotationModel;
@@ -483,8 +495,7 @@ export class MainView extends React.Component {
483
495
  // Watch isIdentifying and clear the highlight when Identify Tool is turned off
484
496
  this._model.sharedModel.awareness.on('change', () => {
485
497
  var _a;
486
- const isIdentifying = this._model.isIdentifying;
487
- if (!isIdentifying && this._highlightLayer) {
498
+ if (this._model.currentMode !== 'identifying' && this._highlightLayer) {
488
499
  (_a = this._highlightLayer.getSource()) === null || _a === void 0 ? void 0 : _a.clear();
489
500
  }
490
501
  });
@@ -740,9 +751,7 @@ export class MainView extends React.Component {
740
751
  const format = new GeoJSON({
741
752
  featureProjection: this._Map.getView().getProjection(),
742
753
  });
743
- // TODO: Don't hardcode projection
744
754
  const featureArray = format.readFeatures(data, {
745
- dataProjection: 'EPSG:4326',
746
755
  featureProjection: this._Map.getView().getProjection(),
747
756
  });
748
757
  const featureCollection = new Collection(featureArray);
@@ -1580,7 +1589,7 @@ export class MainView extends React.Component {
1580
1589
  }
1581
1590
  _identifyFeature(e) {
1582
1591
  var _a, _b;
1583
- if (!this._model.isIdentifying) {
1592
+ if (this._model.currentMode !== 'identifying') {
1584
1593
  return;
1585
1594
  }
1586
1595
  const localState = (_a = this._model) === null || _a === void 0 ? void 0 : _a.sharedModel.awareness.getLocalState();
@@ -1595,24 +1604,45 @@ export class MainView extends React.Component {
1595
1604
  case 'VectorTileLayer': {
1596
1605
  const geometries = [];
1597
1606
  const features = [];
1607
+ let foundAny = false;
1598
1608
  this._Map.forEachFeatureAtPixel(e.pixel, (feature) => {
1609
+ var _a, _b;
1610
+ foundAny = true;
1599
1611
  let geom;
1612
+ let props = {};
1600
1613
  if (feature instanceof RenderFeature) {
1601
1614
  geom = toGeometry(feature);
1602
1615
  }
1603
1616
  else if ('getGeometry' in feature) {
1604
1617
  geom = feature.getGeometry();
1605
1618
  }
1606
- const props = feature.getProperties();
1619
+ const rawProps = feature.getProperties();
1620
+ const fid = (_b = (_a = feature.getId) === null || _a === void 0 ? void 0 : _a.call(feature)) !== null && _b !== void 0 ? _b : rawProps === null || rawProps === void 0 ? void 0 : rawProps.fid;
1621
+ if (rawProps && Object.keys(rawProps).length > 1) {
1622
+ const { geometry } = rawProps, clean = __rest(rawProps, ["geometry"]);
1623
+ props = clean;
1624
+ if (fid !== null) {
1625
+ // TODO Clean the cache under some condition?
1626
+ this._featurePropertyCache.set(fid, props);
1627
+ }
1628
+ }
1629
+ else if (fid !== null && this._featurePropertyCache.has(fid)) {
1630
+ props = this._featurePropertyCache.get(fid);
1631
+ }
1607
1632
  if (geom) {
1608
1633
  geometries.push(geom);
1609
1634
  }
1610
- features.push(Object.assign({}, props));
1635
+ if (props && Object.keys(props).length > 0) {
1636
+ features.push(props);
1637
+ }
1611
1638
  return true;
1612
1639
  });
1613
1640
  if (features.length > 0) {
1614
1641
  this._model.syncIdentifiedFeatures(features, this._mainViewModel.id);
1615
1642
  }
1643
+ else if (!foundAny) {
1644
+ this._model.syncIdentifiedFeatures([], this._mainViewModel.id);
1645
+ }
1616
1646
  if (geometries.length > 0) {
1617
1647
  for (const geom of geometries) {
1618
1648
  this._model.highlightFeatureSignal.emit(geom);
@@ -1708,7 +1738,8 @@ export class MainView extends React.Component {
1708
1738
  height: '100%',
1709
1739
  } })),
1710
1740
  React.createElement(StatusBar, { jgisModel: this._model, loading: this.state.loadingLayer, projection: this.state.viewProjection, scale: this.state.scale })),
1711
- this._state && (React.createElement(LeftPanel, { model: this._model, commands: this._mainViewModel.commands, state: this._state })),
1712
- this._formSchemaRegistry && this._annotationModel && (React.createElement(RightPanel, { model: this._model, formSchemaRegistry: this._formSchemaRegistry, annotationModel: this._annotationModel }))));
1741
+ React.createElement("div", { className: "jgis-panels-wrapper" },
1742
+ this._state && (React.createElement(LeftPanel, { model: this._model, commands: this._mainViewModel.commands, state: this._state })),
1743
+ this._formSchemaRegistry && this._annotationModel && (React.createElement(RightPanel, { model: this._model, formSchemaRegistry: this._formSchemaRegistry, annotationModel: this._annotationModel })))));
1713
1744
  }
1714
1745
  }
@@ -35,7 +35,8 @@ export const IdentifyPanelComponent = ({ model, }) => {
35
35
  setFeatures({});
36
36
  return;
37
37
  }
38
- if (model.isIdentifying && featuresRef.current !== identifiedFeatures) {
38
+ if (model.currentMode === 'identifying' &&
39
+ featuresRef.current !== identifiedFeatures) {
39
40
  setFeatures(identifiedFeatures);
40
41
  }
41
42
  };
@@ -53,40 +54,49 @@ export const IdentifyPanelComponent = ({ model, }) => {
53
54
  const toggleFeatureVisibility = (index) => {
54
55
  setVisibleFeatures(prev => (Object.assign(Object.assign({}, prev), { [index]: !prev[index] })));
55
56
  };
57
+ const getFeatureNameOrId = (feature, featureIndex) => {
58
+ for (const key of Object.keys(feature)) {
59
+ const lowerCase = key.toLowerCase();
60
+ if ((lowerCase.includes('name') || lowerCase === 'id') && feature[key]) {
61
+ return feature[key];
62
+ }
63
+ }
64
+ return `Feature ${featureIndex + 1}`;
65
+ };
56
66
  return (React.createElement("div", { className: "jgis-identify-wrapper", style: {
57
67
  border: ((_a = model === null || model === void 0 ? void 0 : model.localState) === null || _a === void 0 ? void 0 : _a.remoteUser)
58
68
  ? `solid 3px ${remoteUser === null || remoteUser === void 0 ? void 0 : remoteUser.color}`
59
69
  : 'unset',
60
- } }, features &&
61
- Object.values(features).map((feature, featureIndex) => (React.createElement("div", { key: featureIndex, className: "jgis-identify-grid-item" },
62
- React.createElement("div", { className: "jgis-identify-grid-item-header" },
63
- React.createElement("span", { onClick: () => toggleFeatureVisibility(featureIndex) },
64
- React.createElement(LabIcon.resolveReact, { icon: caretDownIcon, className: `jp-gis-layerGroupCollapser${visibleFeatures[featureIndex] ? ' jp-mod-expanded' : ''}`, tag: 'span' }),
65
- React.createElement("span", null,
66
- "Feature ",
67
- featureIndex + 1)),
68
- (() => {
69
- const isRasterFeature = !feature.geometry &&
70
- !feature._geometry &&
71
- typeof (feature === null || feature === void 0 ? void 0 : feature.x) !== 'number' &&
72
- typeof (feature === null || feature === void 0 ? void 0 : feature.y) !== 'number';
73
- return (React.createElement("button", { className: "jgis-highlight-button", onClick: e => {
74
- e.stopPropagation();
75
- highlightFeatureOnMap(feature);
76
- }, title: isRasterFeature
77
- ? 'Highlight not available for raster features'
78
- : 'Highlight feature on map', disabled: isRasterFeature },
79
- React.createElement(FontAwesomeIcon, { icon: faMagnifyingGlass })));
80
- })()),
81
- visibleFeatures[featureIndex] && (React.createElement(React.Fragment, null, Object.entries(feature)
82
- .filter(([key, value]) => typeof value !== 'object' || value === null)
83
- .sort(([keyA], [keyB]) => keyA.localeCompare(keyB))
84
- .map(([key, value]) => (React.createElement("div", { key: key, className: "jgis-identify-grid-body" },
85
- React.createElement("strong", null,
86
- key,
87
- ":"),
88
- typeof value === 'string' &&
89
- /<\/?[a-z][\s\S]*>/i.test(value) ? (React.createElement("span", { dangerouslySetInnerHTML: {
90
- __html: `${value}`,
91
- } })) : (React.createElement("span", null, String(value)))))))))))));
70
+ } },
71
+ !Object.keys(features !== null && features !== void 0 ? features : {}).length && (React.createElement("div", { style: { textAlign: 'center' } }, "Please select a layer from the layer list, then \"i\" from the toolbar to start identifying features.")),
72
+ features &&
73
+ Object.values(features).map((feature, featureIndex) => (React.createElement("div", { key: featureIndex, className: "jgis-identify-grid-item" },
74
+ React.createElement("div", { className: "jgis-identify-grid-item-header" },
75
+ React.createElement("span", { onClick: () => toggleFeatureVisibility(featureIndex) },
76
+ React.createElement(LabIcon.resolveReact, { icon: caretDownIcon, className: `jp-gis-layerGroupCollapser${visibleFeatures[featureIndex] ? ' jp-mod-expanded' : ''}`, tag: 'span' }),
77
+ React.createElement("span", null, getFeatureNameOrId(feature, featureIndex))),
78
+ (() => {
79
+ const isRasterFeature = !feature.geometry &&
80
+ !feature._geometry &&
81
+ typeof (feature === null || feature === void 0 ? void 0 : feature.x) !== 'number' &&
82
+ typeof (feature === null || feature === void 0 ? void 0 : feature.y) !== 'number';
83
+ return (React.createElement("button", { className: "jgis-highlight-button", onClick: e => {
84
+ e.stopPropagation();
85
+ highlightFeatureOnMap(feature);
86
+ }, title: isRasterFeature
87
+ ? 'Highlight not available for raster features'
88
+ : 'Highlight feature on map', disabled: isRasterFeature },
89
+ React.createElement(FontAwesomeIcon, { icon: faMagnifyingGlass })));
90
+ })()),
91
+ visibleFeatures[featureIndex] && (React.createElement(React.Fragment, null, Object.entries(feature)
92
+ .filter(([key, value]) => typeof value !== 'object' || value === null)
93
+ .sort(([keyA], [keyB]) => keyA.localeCompare(keyB))
94
+ .map(([key, value]) => (React.createElement("div", { key: key, className: "jgis-identify-grid-body" },
95
+ React.createElement("strong", null,
96
+ key,
97
+ ":"),
98
+ typeof value === 'string' &&
99
+ /<\/?[a-z][\s\S]*>/i.test(value) ? (React.createElement("span", { dangerouslySetInnerHTML: {
100
+ __html: `${value}`,
101
+ } })) : (React.createElement("span", null, String(value)))))))))))));
92
102
  };
@@ -1,9 +1,11 @@
1
1
  import { DOMUtils } from '@jupyterlab/apputils';
2
- import { Button, LabIcon, caretDownIcon } from '@jupyterlab/ui-components';
2
+ import { Button, LabIcon, caretDownIcon, caretRightIcon, } from '@jupyterlab/ui-components';
3
3
  import { UUID } from '@lumino/coreutils';
4
4
  import React, { useEffect, useState, } from 'react';
5
5
  import { CommandIDs, icons } from "../../constants";
6
+ import { useGetSymbology } from "../../dialogs/symbology/hooks/useGetSymbology";
6
7
  import { nonVisibilityIcon, visibilityIcon } from "../../icons";
8
+ import { LegendItem } from './legendItem';
7
9
  const LAYER_GROUP_CLASS = 'jp-gis-layerGroup';
8
10
  const LAYER_GROUP_HEADER_CLASS = 'jp-gis-layerGroupHeader';
9
11
  const LAYER_GROUP_COLLAPSER_CLASS = 'jp-gis-layerGroupCollapser';
@@ -213,6 +215,12 @@ const LayerComponent = props => {
213
215
  const [selected, setSelected] = useState(
214
216
  // TODO Support multi-selection as `model?.jGISModel?.localState?.selected.value` does
215
217
  isSelected(layerId, gisModel));
218
+ const [expanded, setExpanded] = useState(false);
219
+ const { symbology } = useGetSymbology({
220
+ layerId,
221
+ model: gisModel,
222
+ });
223
+ const hasSupportedSymbology = (symbology === null || symbology === void 0 ? void 0 : symbology.symbologyState) !== undefined;
216
224
  const name = layer.name;
217
225
  useEffect(() => {
218
226
  setId(DOMUtils.createDomID());
@@ -248,12 +256,19 @@ const LayerComponent = props => {
248
256
  event,
249
257
  });
250
258
  };
251
- 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 },
252
- React.createElement("div", { className: LAYER_TITLE_CLASS, onClick: setSelection, onContextMenu: setSelection },
259
+ 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' } },
260
+ React.createElement("div", { className: LAYER_TITLE_CLASS, onClick: setSelection, onContextMenu: setSelection, style: { display: 'flex' } },
261
+ hasSupportedSymbology && (React.createElement(Button, { minimal: true, onClick: e => {
262
+ e.stopPropagation();
263
+ setExpanded(v => !v);
264
+ }, title: expanded ? 'Hide legend' : 'Show legend' },
265
+ React.createElement(LabIcon.resolveReact, { icon: expanded ? caretDownIcon : caretRightIcon, tag: "span" }))),
253
266
  React.createElement(Button, { title: layer.visible ? 'Hide layer' : 'Show layer', onClick: toggleVisibility, minimal: true },
254
267
  React.createElement(LabIcon.resolveReact, { icon: layer.visible ? visibilityIcon : nonVisibilityIcon, className: `${LAYER_ICON_CLASS}${layer.visible ? '' : ' jp-gis-mod-hidden'}`, tag: "span" })),
255
268
  icons.has(layer.type) && (React.createElement(LabIcon.resolveReact, Object.assign({}, icons.get(layer.type), { className: LAYER_ICON_CLASS }))),
256
- React.createElement("span", { id: id, className: LAYER_TEXT_CLASS, tabIndex: -2 }, name))));
269
+ React.createElement("span", { id: id, className: LAYER_TEXT_CLASS, tabIndex: -2 }, name)),
270
+ expanded && gisModel && hasSupportedSymbology && (React.createElement("div", { style: { marginTop: 6, width: '100%' } },
271
+ React.createElement(LegendItem, { layerId: layerId, model: gisModel })))));
257
272
  };
258
273
  var Private;
259
274
  (function (Private) {
@@ -0,0 +1,6 @@
1
+ import { IJupyterGISModel } from '@jupytergis/schema';
2
+ import React from 'react';
3
+ export declare const LegendItem: React.FC<{
4
+ layerId: string;
5
+ model: IJupyterGISModel;
6
+ }>;
@@ -0,0 +1,165 @@
1
+ import React, { useEffect, useState } from 'react';
2
+ import { useGetSymbology } from "../../dialogs/symbology/hooks/useGetSymbology";
3
+ export const LegendItem = ({ layerId, model }) => {
4
+ const { symbology, isLoading, error } = useGetSymbology({ layerId, model });
5
+ const [content, setContent] = useState(null);
6
+ const parseColorStops = (expr) => {
7
+ if (!Array.isArray(expr) || expr[0] !== 'interpolate') {
8
+ return [];
9
+ }
10
+ const stops = [];
11
+ for (let i = 3; i < expr.length; i += 2) {
12
+ const value = expr[i];
13
+ const rgba = expr[i + 1];
14
+ const color = Array.isArray(rgba)
15
+ ? `rgba(${rgba[0]},${rgba[1]},${rgba[2]},${rgba[3]})`
16
+ : String(rgba);
17
+ stops.push({ value, color });
18
+ }
19
+ return stops;
20
+ };
21
+ const parseCaseCategories = (expr) => {
22
+ if (!Array.isArray(expr) || expr[0] !== 'case') {
23
+ return [];
24
+ }
25
+ const categories = [];
26
+ for (let i = 1; i < expr.length - 1; i += 2) {
27
+ const condition = expr[i];
28
+ const colorExpr = expr[i + 1];
29
+ let category = '';
30
+ if (Array.isArray(condition) && condition[0] === '==') {
31
+ category = condition[2];
32
+ }
33
+ let color = '';
34
+ if (Array.isArray(colorExpr)) {
35
+ color = `rgba(${colorExpr[0]},${colorExpr[1]},${colorExpr[2]},${colorExpr[3]})`;
36
+ }
37
+ else if (typeof colorExpr === 'string') {
38
+ color = colorExpr;
39
+ }
40
+ categories.push({ category, color });
41
+ }
42
+ return categories;
43
+ };
44
+ useEffect(() => {
45
+ var _a, _b, _c, _d, _e, _f, _g, _h;
46
+ if (isLoading) {
47
+ setContent(React.createElement("p", { style: { fontSize: '0.8em' } }, "Loading\u2026"));
48
+ return;
49
+ }
50
+ if (error) {
51
+ setContent(React.createElement("p", { style: { color: 'red', fontSize: '0.8em' } }, error.message));
52
+ return;
53
+ }
54
+ if (!symbology) {
55
+ setContent(React.createElement("p", { style: { fontSize: '0.8em' } }, "No symbology"));
56
+ return;
57
+ }
58
+ const renderType = (_a = symbology.symbologyState) === null || _a === void 0 ? void 0 : _a.renderType;
59
+ const property = (_b = symbology.symbologyState) === null || _b === void 0 ? void 0 : _b.value;
60
+ const fill = (_d = (_c = symbology.color) === null || _c === void 0 ? void 0 : _c['fill-color']) !== null && _d !== void 0 ? _d : (_e = symbology.color) === null || _e === void 0 ? void 0 : _e['circle-fill-color'];
61
+ const stroke = (_g = (_f = symbology.color) === null || _f === void 0 ? void 0 : _f['stroke-color']) !== null && _g !== void 0 ? _g : (_h = symbology.color) === null || _h === void 0 ? void 0 : _h['circle-stroke-color'];
62
+ // Single Symbol
63
+ if (renderType === 'Single Symbol') {
64
+ setContent(React.createElement("div", { style: { display: 'flex', gap: 12, flexWrap: 'wrap', padding: 6 } },
65
+ fill && (React.createElement("div", { style: { display: 'flex', alignItems: 'center', gap: 6 } },
66
+ React.createElement("span", { style: {
67
+ width: 16,
68
+ height: 16,
69
+ border: '1px solid #000',
70
+ background: fill,
71
+ } }),
72
+ React.createElement("span", { style: { fontSize: '0.8em' } }, "Fill"))),
73
+ stroke && (React.createElement("div", { style: { display: 'flex', alignItems: 'center', gap: 6 } },
74
+ React.createElement("span", { style: {
75
+ width: 24,
76
+ height: 2,
77
+ background: stroke,
78
+ border: '1px solid #000',
79
+ } }),
80
+ React.createElement("span", { style: { fontSize: '0.8em' } }, "Stroke"))),
81
+ !fill && !stroke && (React.createElement("span", { style: { fontSize: '0.8em' } }, "No symbol colors"))));
82
+ return;
83
+ }
84
+ // Graduated
85
+ if (renderType === 'Graduated') {
86
+ const stops = parseColorStops(fill || stroke);
87
+ if (!stops.length) {
88
+ setContent(React.createElement("p", { style: { fontSize: '0.8em' } }, "No graduated symbology"));
89
+ return;
90
+ }
91
+ const segments = stops
92
+ .map((s, i) => {
93
+ const pct = (i / (stops.length - 1)) * 100;
94
+ return `${s.color} ${pct}%`;
95
+ })
96
+ .join(', ');
97
+ const gradient = `linear-gradient(to right, ${segments})`;
98
+ setContent(React.createElement("div", { style: { padding: 6, width: '90%' } },
99
+ property && (React.createElement("div", { style: { fontSize: '1em', marginBottom: 20 } },
100
+ React.createElement("strong", null, property))),
101
+ React.createElement("div", { style: {
102
+ position: 'relative',
103
+ height: 12,
104
+ background: gradient,
105
+ border: '1px solid #ccc',
106
+ borderRadius: 3,
107
+ marginBottom: 20,
108
+ marginTop: 10,
109
+ } }, stops.map((s, i) => {
110
+ const left = (i / (stops.length - 1)) * 100;
111
+ const up = i % 2 === 0;
112
+ return (React.createElement("div", { key: i, style: {
113
+ position: 'absolute',
114
+ left: `${left}%`,
115
+ transform: 'translateX(-50%)',
116
+ } },
117
+ React.createElement("div", { style: {
118
+ width: 1,
119
+ height: 8,
120
+ background: '#333',
121
+ margin: '0 auto',
122
+ } }),
123
+ React.createElement("div", { style: {
124
+ position: 'absolute',
125
+ top: up ? -18 : 12,
126
+ fontSize: '0.7em',
127
+ whiteSpace: 'nowrap',
128
+ marginTop: up ? 0 : 4,
129
+ } }, s.value.toFixed(2))));
130
+ }))));
131
+ return;
132
+ }
133
+ // Categorized
134
+ if (renderType === 'Categorized') {
135
+ const cats = parseCaseCategories(fill || stroke);
136
+ if (!cats.length) {
137
+ setContent(React.createElement("p", { style: { fontSize: '0.8em' } }, "No categorized symbology"));
138
+ return;
139
+ }
140
+ setContent(React.createElement("div", { style: { padding: 6 } },
141
+ property && (React.createElement("div", { style: { fontSize: '1em', marginBottom: 6 } },
142
+ React.createElement("strong", null, property))),
143
+ React.createElement("div", { style: {
144
+ display: 'grid',
145
+ gap: 6,
146
+ maxHeight: 200,
147
+ overflowY: 'auto',
148
+ paddingRight: 4,
149
+ } }, cats.map((c, i) => (React.createElement("div", { key: i, style: { display: 'flex', alignItems: 'center', gap: 8 } },
150
+ React.createElement("span", { style: {
151
+ width: 16,
152
+ height: 16,
153
+ background: c.color || '#ccc',
154
+ border: '1px solid #000',
155
+ borderRadius: 2,
156
+ } }),
157
+ React.createElement("span", { style: { fontSize: '0.75em' } }, String(c.category))))))));
158
+ return;
159
+ }
160
+ setContent(React.createElement("p", null,
161
+ "Unsupported symbology: ",
162
+ String(renderType)));
163
+ }, [symbology, isLoading, error]);
164
+ return React.createElement("div", null, content);
165
+ };
@@ -1,34 +1,45 @@
1
- import { PageConfig } from '@jupyterlab/coreutils';
2
1
  import * as React from 'react';
3
2
  import { LayersBodyComponent } from './components/layers';
4
3
  import { PanelTabs, TabsContent, TabsList, TabsTrigger, } from '../shared/components/Tabs';
5
4
  import StacPanel from '../stacBrowser/components/StacPanel';
6
5
  import FilterComponent from './components/filter-panel/Filter';
7
6
  export const LeftPanel = (props) => {
8
- const hideStacPanel = PageConfig.getOption('HIDE_STAC_PANEL') === 'true';
7
+ const [settings, setSettings] = React.useState(props.model.jgisSettings);
8
+ React.useEffect(() => {
9
+ const onSettingsChanged = () => {
10
+ setSettings(Object.assign({}, props.model.jgisSettings));
11
+ };
12
+ props.model.settingsChanged.connect(onSettingsChanged);
13
+ return () => {
14
+ props.model.settingsChanged.disconnect(onSettingsChanged);
15
+ };
16
+ }, [props.model]);
17
+ const allLeftTabsDisabled = settings.layersDisabled &&
18
+ settings.stacBrowserDisabled &&
19
+ settings.filtersDisabled;
20
+ const leftPanelVisible = !settings.leftPanelDisabled && !allLeftTabsDisabled;
9
21
  const tabInfo = [
10
- { name: 'layers', title: 'Layers' },
11
- ...(hideStacPanel ? [] : [{ name: 'stac', title: 'Stac Browser' }]),
12
- { name: 'filters', title: 'Filters' },
13
- ];
14
- const [curTab, setCurTab] = React.useState(tabInfo[0].name);
15
- return (React.createElement("div", { className: "jgis-left-panel-container" },
22
+ !settings.layersDisabled ? { name: 'layers', title: 'Layers' } : false,
23
+ !settings.stacBrowserDisabled
24
+ ? { name: 'stac', title: 'Stac Browser' }
25
+ : false,
26
+ !settings.filtersDisabled ? { name: 'filters', title: 'Filters' } : false,
27
+ ].filter(Boolean);
28
+ const [curTab, setCurTab] = React.useState(tabInfo.length > 0 ? tabInfo[0].name : undefined);
29
+ return (React.createElement("div", { className: "jgis-left-panel-container", style: { display: leftPanelVisible ? 'block' : 'none' } },
16
30
  React.createElement(PanelTabs, { curTab: curTab, className: "jgis-panel-tabs" },
17
- React.createElement(TabsList, null, tabInfo.map(e => {
18
- return (React.createElement(TabsTrigger, { className: "jGIS-layer-browser-category", value: e.name, onClick: () => {
19
- if (curTab !== e.name) {
20
- setCurTab(e.name);
21
- }
22
- else {
23
- setCurTab('');
24
- }
25
- } }, e.title));
26
- })),
27
- React.createElement(TabsContent, { value: "layers", className: "jgis-panel-tab-content jp-gis-layerPanel" },
28
- React.createElement(LayersBodyComponent, { model: props.model, commands: props.commands, state: props.state })),
29
- !hideStacPanel && (React.createElement(TabsContent, { value: "stac" },
31
+ React.createElement(TabsList, null, tabInfo.map(e => (React.createElement(TabsTrigger, { className: "jGIS-layer-browser-category", value: e.name, onClick: () => {
32
+ if (curTab !== e.name) {
33
+ setCurTab(e.name);
34
+ }
35
+ else {
36
+ setCurTab('');
37
+ }
38
+ } }, e.title)))),
39
+ !settings.layersDisabled && (React.createElement(TabsContent, { value: "layers", className: "jgis-panel-tab-content jp-gis-layerPanel" },
40
+ React.createElement(LayersBodyComponent, { model: props.model, commands: props.commands, state: props.state }))),
41
+ !settings.stacBrowserDisabled && (React.createElement(TabsContent, { value: "stac", className: "jgis-panel-tab-content" },
30
42
  React.createElement(StacPanel, { model: props.model }))),
31
- React.createElement(TabsContent, { value: "filters", className: "jgis-panel-tab-content" },
32
- React.createElement(FilterComponent, { model: props.model }),
33
- ","))));
43
+ !settings.filtersDisabled && (React.createElement(TabsContent, { value: "filters", className: "jgis-panel-tab-content" },
44
+ React.createElement(FilterComponent, { model: props.model }))))));
34
45
  };
@@ -1,36 +1,64 @@
1
- import { PageConfig } from '@jupyterlab/coreutils';
2
1
  import * as React from 'react';
3
2
  import { AnnotationsPanel } from './annotationPanel';
4
3
  import { IdentifyPanelComponent } from './components/identify-panel/IdentifyPanel';
5
4
  import { ObjectPropertiesReact } from './objectproperties';
6
5
  import { PanelTabs, TabsContent, TabsList, TabsTrigger, } from '../shared/components/Tabs';
7
6
  export const RightPanel = props => {
8
- const hideAnnotationPanel = PageConfig.getOption('HIDE_ANNOTATION_PANEL') === 'true';
9
- const [selectedObjectProperties, setSelectedObjectProperties] = React.useState(undefined);
7
+ const [settings, setSettings] = React.useState(props.model.jgisSettings);
10
8
  const tabInfo = [
11
- { name: 'objectProperties', title: 'Object Properties' },
12
- ...(hideAnnotationPanel
13
- ? []
14
- : [{ name: 'annotations', title: 'Annotations' }]),
15
- { name: 'identifyPanel', title: 'Identify Features' },
16
- ];
17
- const [curTab, setCurTab] = React.useState(tabInfo[0].name);
18
- return (React.createElement("div", { className: "jgis-right-panel-container" },
9
+ !settings.objectPropertiesDisabled
10
+ ? { name: 'objectProperties', title: 'Object Properties' }
11
+ : false,
12
+ !settings.annotationsDisabled
13
+ ? { name: 'annotations', title: 'Annotations' }
14
+ : false,
15
+ !settings.identifyDisabled
16
+ ? { name: 'identifyPanel', title: 'Identified Features' }
17
+ : false,
18
+ ].filter(Boolean);
19
+ const [curTab, setCurTab] = React.useState(tabInfo.length > 0 ? tabInfo[0].name : undefined);
20
+ React.useEffect(() => {
21
+ const onSettingsChanged = () => {
22
+ setSettings(Object.assign({}, props.model.jgisSettings));
23
+ };
24
+ let currentlyIdentifiedFeatures = undefined;
25
+ const onAwerenessChanged = (_, clients) => {
26
+ var _a;
27
+ const clientId = props.model.getClientId();
28
+ const localState = clientId ? clients.get(clientId) : null;
29
+ if (localState &&
30
+ ((_a = localState.identifiedFeatures) === null || _a === void 0 ? void 0 : _a.value) &&
31
+ localState.identifiedFeatures.value !== currentlyIdentifiedFeatures) {
32
+ currentlyIdentifiedFeatures = localState.identifiedFeatures.value;
33
+ setCurTab('identifyPanel');
34
+ }
35
+ };
36
+ props.model.settingsChanged.connect(onSettingsChanged);
37
+ props.model.clientStateChanged.connect(onAwerenessChanged);
38
+ return () => {
39
+ props.model.settingsChanged.disconnect(onSettingsChanged);
40
+ props.model.clientStateChanged.disconnect(onAwerenessChanged);
41
+ };
42
+ }, [props.model]);
43
+ const allRightTabsDisabled = settings.objectPropertiesDisabled &&
44
+ settings.annotationsDisabled &&
45
+ settings.identifyDisabled;
46
+ const rightPanelVisible = !settings.rightPanelDisabled && !allRightTabsDisabled;
47
+ const [selectedObjectProperties, setSelectedObjectProperties] = React.useState(undefined);
48
+ return (React.createElement("div", { className: "jgis-right-panel-container", style: { display: rightPanelVisible ? 'block' : 'none' } },
19
49
  React.createElement(PanelTabs, { className: "jgis-panel-tabs", curTab: curTab },
20
- React.createElement(TabsList, null, tabInfo.map(e => {
21
- return (React.createElement(TabsTrigger, { className: "jGIS-layer-browser-category", value: e.name, onClick: () => {
22
- if (curTab !== e.name) {
23
- setCurTab(e.name);
24
- }
25
- else {
26
- setCurTab('');
27
- }
28
- } }, e.title));
29
- })),
30
- React.createElement(TabsContent, { value: "objectProperties", className: "jgis-panel-tab-content" },
31
- React.createElement(ObjectPropertiesReact, { setSelectedObject: setSelectedObjectProperties, selectedObject: selectedObjectProperties, formSchemaRegistry: props.formSchemaRegistry, model: props.model })),
32
- React.createElement(TabsContent, { value: "annotations" },
33
- React.createElement(AnnotationsPanel, { annotationModel: props.annotationModel, jgisModel: props.model })),
34
- React.createElement(TabsContent, { value: "identifyPanel", className: "jgis-panel-tab-content" },
35
- React.createElement(IdentifyPanelComponent, { model: props.model })))));
50
+ React.createElement(TabsList, null, tabInfo.map(e => (React.createElement(TabsTrigger, { className: "jGIS-layer-browser-category", value: e.name, onClick: () => {
51
+ if (curTab !== e.name) {
52
+ setCurTab(e.name);
53
+ }
54
+ else {
55
+ setCurTab('');
56
+ }
57
+ } }, e.title)))),
58
+ !settings.objectPropertiesDisabled && (React.createElement(TabsContent, { value: "objectProperties", className: "jgis-panel-tab-content" },
59
+ React.createElement(ObjectPropertiesReact, { setSelectedObject: setSelectedObjectProperties, selectedObject: selectedObjectProperties, formSchemaRegistry: props.formSchemaRegistry, model: props.model }))),
60
+ !settings.annotationsDisabled && (React.createElement(TabsContent, { value: "annotations", className: "jgis-panel-tab-content" },
61
+ React.createElement(AnnotationsPanel, { annotationModel: props.annotationModel, jgisModel: props.model }))),
62
+ !settings.identifyDisabled && (React.createElement(TabsContent, { value: "identifyPanel", className: "jgis-panel-tab-content" },
63
+ React.createElement(IdentifyPanelComponent, { model: props.model }))))));
36
64
  };
package/lib/tools.d.ts CHANGED
@@ -72,7 +72,11 @@ export declare const loadGeoTiff: (sourceInfo: {
72
72
  file: any;
73
73
  metadata: any;
74
74
  sourceUrl: string;
75
- } | null | undefined>;
75
+ } | {
76
+ file: Blob;
77
+ sourceUrl: string;
78
+ metadata?: undefined;
79
+ } | null>;
76
80
  /**
77
81
  * Generalized file reader for different source types.
78
82
  *
package/lib/tools.js CHANGED
@@ -353,6 +353,10 @@ export const loadGeoTiff = async (sourceInfo, model, file) => {
353
353
  else {
354
354
  fileBlob = await base64ToBlob(file.content, mimeType);
355
355
  }
356
+ return {
357
+ file: fileBlob,
358
+ sourceUrl: url,
359
+ };
356
360
  };
357
361
  /**
358
362
  * Generalized file reader for different source types.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jupytergis/base",
3
- "version": "0.8.1",
3
+ "version": "0.9.1",
4
4
  "description": "A JupyterLab extension for 3D modelling.",
5
5
  "keywords": [
6
6
  "jupyter",
@@ -40,11 +40,11 @@
40
40
  "dependencies": {
41
41
  "@fortawesome/fontawesome-svg-core": "^6.5.2",
42
42
  "@fortawesome/free-solid-svg-icons": "^6.5.2",
43
- "@fortawesome/react-fontawesome": "latest",
43
+ "@fortawesome/react-fontawesome": ">=0.2.6 <3.0.0",
44
44
  "@jupyter/collaboration": "^3.1.0",
45
45
  "@jupyter/react-components": "^0.16.6",
46
46
  "@jupyter/ydoc": "^2.0.0 || ^3.0.0",
47
- "@jupytergis/schema": "^0.8.1",
47
+ "@jupytergis/schema": "^0.9.1",
48
48
  "@jupyterlab/application": "^4.3.0",
49
49
  "@jupyterlab/apputils": "^4.3.0",
50
50
  "@jupyterlab/completer": "^4.3.0",
package/style/base.css CHANGED
@@ -101,3 +101,23 @@ button.jp-mod-styled.jp-mod-reject {
101
101
  left: 0px;
102
102
  margin: 5px;
103
103
  }
104
+
105
+ @media (max-width: 768px) {
106
+ .jgis-panels-wrapper {
107
+ position: fixed;
108
+ top: 30px;
109
+ display: flex;
110
+ flex-direction: column;
111
+ align-items: flex-start;
112
+ gap: 10px;
113
+ margin: 0 5px;
114
+ z-index: 1000;
115
+ width: 60%;
116
+ }
117
+
118
+ .jgis-left-panel-container,
119
+ .jgis-right-panel-container {
120
+ position: relative;
121
+ margin: 0;
122
+ }
123
+ }
@@ -41,7 +41,7 @@
41
41
  .jp-gis-source {
42
42
  display: flex;
43
43
  flex-direction: row;
44
- align-items: center;
44
+ /* align-items: center; */
45
45
  color: var(--jp-ui-font-color1);
46
46
  }
47
47