@jupytergis/base 0.12.2 → 0.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/lib/commands/index.js +2 -6
  2. package/lib/dialogs/symbology/hooks/useEffectiveSymbologyParams.d.ts +16 -0
  3. package/lib/dialogs/symbology/hooks/useEffectiveSymbologyParams.js +24 -0
  4. package/lib/dialogs/symbology/hooks/useOkSignal.d.ts +6 -0
  5. package/lib/dialogs/symbology/hooks/useOkSignal.js +25 -0
  6. package/lib/dialogs/symbology/symbologyDialog.d.ts +4 -2
  7. package/lib/dialogs/symbology/symbologyDialog.js +6 -10
  8. package/lib/dialogs/symbology/symbologyUtils.d.ts +25 -2
  9. package/lib/dialogs/symbology/symbologyUtils.js +74 -4
  10. package/lib/dialogs/symbology/tiff_layer/TiffRendering.js +3 -3
  11. package/lib/dialogs/symbology/tiff_layer/types/MultibandColor.js +31 -34
  12. package/lib/dialogs/symbology/tiff_layer/types/SingleBandPseudoColor.js +68 -62
  13. package/lib/dialogs/symbology/vector_layer/VectorRendering.js +33 -21
  14. package/lib/dialogs/symbology/vector_layer/types/Canonical.js +23 -24
  15. package/lib/dialogs/symbology/vector_layer/types/Categorized.js +49 -50
  16. package/lib/dialogs/symbology/vector_layer/types/Graduated.js +53 -62
  17. package/lib/dialogs/symbology/vector_layer/types/Heatmap.js +35 -34
  18. package/lib/dialogs/symbology/vector_layer/types/SimpleSymbol.js +45 -47
  19. package/lib/formbuilder/objectform/StoryEditorForm.js +0 -17
  20. package/lib/formbuilder/objectform/baseform.d.ts +11 -0
  21. package/lib/formbuilder/objectform/baseform.js +72 -38
  22. package/lib/formbuilder/objectform/components/LayerSelect.d.ts +7 -0
  23. package/lib/formbuilder/objectform/components/LayerSelect.js +43 -0
  24. package/lib/formbuilder/objectform/components/OpacitySlider.d.ts +4 -0
  25. package/lib/formbuilder/objectform/components/OpacitySlider.js +40 -0
  26. package/lib/formbuilder/objectform/components/SegmentFormSymbology.d.ts +3 -0
  27. package/lib/formbuilder/objectform/components/SegmentFormSymbology.js +59 -0
  28. package/lib/formbuilder/objectform/layer/storySegmentLayerForm.d.ts +2 -2
  29. package/lib/formbuilder/objectform/layer/storySegmentLayerForm.js +19 -0
  30. package/lib/formbuilder/objectform/source/geojsonsource.js +1 -3
  31. package/lib/mainview/mainView.js +6 -1
  32. package/lib/panelview/rightpanel.d.ts +3 -1
  33. package/lib/panelview/rightpanel.js +2 -2
  34. package/lib/panelview/story-maps/StoryViewerPanel.d.ts +3 -1
  35. package/lib/panelview/story-maps/StoryViewerPanel.js +127 -19
  36. package/lib/shared/hooks/useLatest.d.ts +1 -0
  37. package/lib/shared/hooks/useLatest.js +8 -0
  38. package/lib/types.d.ts +1 -0
  39. package/lib/types.js +6 -1
  40. package/package.json +2 -2
  41. package/style/base.css +8 -0
  42. package/style/storyPanel.css +4 -4
@@ -1,10 +1,13 @@
1
- import React, { useEffect, useRef, useState } from 'react';
1
+ import React, { useEffect, useState } from 'react';
2
2
  import { VectorClassifications } from "../../classificationModes";
3
3
  import ColorRampControls from "../../components/color_ramp/ColorRampControls";
4
4
  import StopContainer from "../../components/color_stops/StopContainer";
5
- import { Utils, VectorUtils } from "../../symbologyUtils";
5
+ import { useOkSignal } from "../../hooks/useOkSignal";
6
+ import { saveSymbology, Utils, VectorUtils, } from "../../symbologyUtils";
6
7
  import ValueSelect from "../components/ValueSelect";
7
- const Graduated = ({ model, state, okSignalPromise, cancel, layerId, symbologyTab, selectableAttributesAndValues, }) => {
8
+ import { useLatest } from "../../../../shared/hooks/useLatest";
9
+ import { useEffectiveSymbologyParams } from '../../hooks/useEffectiveSymbologyParams';
10
+ const Graduated = ({ model, okSignalPromise, layerId, symbologyTab, selectableAttributesAndValues, isStorySegmentOverride, segmentId, }) => {
8
11
  const modeOptions = [
9
12
  'quantile',
10
13
  'equal interval',
@@ -12,11 +15,6 @@ const Graduated = ({ model, state, okSignalPromise, cancel, layerId, symbologyTa
12
15
  'pretty',
13
16
  'logarithmic',
14
17
  ];
15
- const selectableAttributeRef = useRef();
16
- const symbologyTabRef = useRef();
17
- const colorStopRowsRef = useRef([]);
18
- const radiusStopRowsRef = useRef([]);
19
- const colorRampOptionsRef = useRef();
20
18
  const [selectedAttribute, setSelectedAttribute] = useState('');
21
19
  const [colorStopRows, setColorStopRows] = useState([]);
22
20
  const [radiusStopRows, setRadiusStopRows] = useState([]);
@@ -29,31 +27,34 @@ const Graduated = ({ model, state, okSignalPromise, cancel, layerId, symbologyTa
29
27
  radius: 5,
30
28
  });
31
29
  const [reverseRamp, setReverseRamp] = useState(false);
32
- const colorManualStyleRef = useRef(colorManualStyle);
33
- const radiusManualStyleRef = useRef(radiusManualStyle);
30
+ const selectableAttributeRef = useLatest(selectedAttribute);
31
+ const symbologyTabRef = useLatest(symbologyTab);
32
+ const colorStopRowsRef = useLatest(colorStopRows);
33
+ const radiusStopRowsRef = useLatest(radiusStopRows);
34
+ const colorRampOptionsRef = useLatest(colorRampOptions);
35
+ const colorManualStyleRef = useLatest(colorManualStyle);
36
+ const radiusManualStyleRef = useLatest(radiusManualStyle);
34
37
  if (!layerId) {
35
38
  return;
36
39
  }
37
40
  const layer = model.getLayer(layerId);
38
- if (!(layer === null || layer === void 0 ? void 0 : layer.parameters)) {
41
+ const params = useEffectiveSymbologyParams({
42
+ model,
43
+ layerId: layerId,
44
+ layer,
45
+ isStorySegmentOverride,
46
+ segmentId,
47
+ });
48
+ if (!params) {
39
49
  return;
40
50
  }
41
51
  useEffect(() => {
42
52
  updateStopRowsBasedOnLayer();
43
- okSignalPromise.promise.then(okSignal => {
44
- okSignal.connect(handleOk, this);
45
- });
46
- return () => {
47
- okSignalPromise.promise.then(okSignal => {
48
- okSignal.disconnect(handleOk, this);
49
- });
50
- };
51
53
  }, []);
52
54
  useEffect(() => {
53
- var _a;
54
- if ((_a = layer === null || layer === void 0 ? void 0 : layer.parameters) === null || _a === void 0 ? void 0 : _a.color) {
55
- const strokeColor = layer.parameters.color['stroke-color'];
56
- const circleStrokeColor = layer.parameters.color['circle-stroke-color'];
55
+ if (params.color) {
56
+ const strokeColor = params.color['stroke-color'];
57
+ const circleStrokeColor = params.color['circle-stroke-color'];
57
58
  const isSimpleColor = (val) => typeof val === 'string' && /^#?[0-9A-Fa-f]{3,8}$/.test(val);
58
59
  setColorManualStyle({
59
60
  strokeColor: isSimpleColor(strokeColor)
@@ -61,51 +62,30 @@ const Graduated = ({ model, state, okSignalPromise, cancel, layerId, symbologyTa
61
62
  : isSimpleColor(circleStrokeColor)
62
63
  ? circleStrokeColor
63
64
  : '#3399CC',
64
- strokeWidth: layer.parameters.color['stroke-width'] ||
65
- layer.parameters.color['circle-stroke-width'] ||
65
+ strokeWidth: params.color['stroke-width'] ||
66
+ params.color['circle-stroke-width'] ||
66
67
  1.25,
67
68
  });
68
69
  setRadiusManualStyle({
69
- radius: layer.parameters.color['circle-radius'] || 5,
70
+ radius: params.color['circle-radius'] || 5,
70
71
  });
71
72
  }
72
73
  }, [layerId]);
73
- useEffect(() => {
74
- colorStopRowsRef.current = colorStopRows;
75
- radiusStopRowsRef.current = radiusStopRows;
76
- selectableAttributeRef.current = selectedAttribute;
77
- symbologyTabRef.current = symbologyTab;
78
- colorRampOptionsRef.current = colorRampOptions;
79
- }, [
80
- colorStopRows,
81
- radiusStopRows,
82
- selectedAttribute,
83
- symbologyTab,
84
- colorRampOptions,
85
- ]);
86
- useEffect(() => {
87
- colorManualStyleRef.current = colorManualStyle;
88
- radiusManualStyleRef.current = radiusManualStyle;
89
- }, [colorManualStyle, radiusManualStyle]);
90
74
  useEffect(() => {
91
75
  var _a, _b;
92
- const layerParams = layer.parameters;
93
- const attribute = (_b = (_a = layerParams.symbologyState) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : Object.keys(selectableAttributesAndValues)[0];
76
+ const attribute = (_b = (_a = params.symbologyState) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : Object.keys(selectableAttributesAndValues)[0];
94
77
  setSelectedAttribute(attribute);
95
78
  }, [selectableAttributesAndValues]);
96
79
  const updateStopRowsBasedOnLayer = () => {
97
80
  if (!layer) {
98
81
  return;
99
82
  }
100
- setColorStopRows(VectorUtils.buildColorInfo(layer));
83
+ setColorStopRows(VectorUtils.buildColorInfo(params));
101
84
  setRadiusStopRows(VectorUtils.buildRadiusInfo(layer));
102
85
  };
103
86
  const handleOk = () => {
104
87
  var _a, _b, _c;
105
- if (!layer.parameters) {
106
- return;
107
- }
108
- const newStyle = Object.assign({}, layer.parameters.color);
88
+ const newStyle = Object.assign({}, params.color);
109
89
  // Apply color symbology
110
90
  if (colorStopRowsRef.current.length > 0) {
111
91
  const colorExpr = [
@@ -145,8 +125,7 @@ const Graduated = ({ model, state, okSignalPromise, cancel, layerId, symbologyTa
145
125
  else {
146
126
  newStyle['circle-radius'] = radiusManualStyleRef.current.radius;
147
127
  }
148
- layer.parameters.color = newStyle;
149
- layer.parameters.symbologyState = {
128
+ const symbologyState = {
150
129
  renderType: 'Graduated',
151
130
  value: selectableAttributeRef.current,
152
131
  method: symbologyTabRef.current,
@@ -155,12 +134,23 @@ const Graduated = ({ model, state, okSignalPromise, cancel, layerId, symbologyTa
155
134
  mode: (_c = colorRampOptionsRef.current) === null || _c === void 0 ? void 0 : _c.selectedMode,
156
135
  reverse: reverseRamp,
157
136
  };
158
- if (layer.type === 'HeatmapLayer') {
159
- layer.type = 'VectorLayer';
160
- }
161
- model.sharedModel.updateLayer(layerId, layer);
162
- cancel();
137
+ saveSymbology({
138
+ model,
139
+ layerId,
140
+ isStorySegmentOverride,
141
+ segmentId,
142
+ payload: {
143
+ symbologyState,
144
+ color: newStyle,
145
+ },
146
+ mutateLayerBeforeSave: targetLayer => {
147
+ if (targetLayer.type === 'HeatmapLayer') {
148
+ targetLayer.type = 'VectorLayer';
149
+ }
150
+ },
151
+ });
163
152
  };
153
+ useOkSignal(okSignalPromise, handleOk);
164
154
  const buildColorInfoFromClassification = (selectedMode, numberOfShades, selectedRamp) => {
165
155
  setColorRampOptions({
166
156
  selectedRamp,
@@ -200,10 +190,7 @@ const Graduated = ({ model, state, okSignalPromise, cancel, layerId, symbologyTa
200
190
  }
201
191
  };
202
192
  const handleReset = (method) => {
203
- if (!(layer === null || layer === void 0 ? void 0 : layer.parameters)) {
204
- return;
205
- }
206
- const newStyle = Object.assign({}, layer.parameters.color);
193
+ const newStyle = Object.assign({}, params.color);
207
194
  if (method === 'color') {
208
195
  delete newStyle['stroke-color'];
209
196
  setColorStopRows([]);
@@ -213,6 +200,10 @@ const Graduated = ({ model, state, okSignalPromise, cancel, layerId, symbologyTa
213
200
  delete newStyle['circle-radius'];
214
201
  setRadiusStopRows([]);
215
202
  }
203
+ const layer = model.getLayer(layerId);
204
+ if (!(layer === null || layer === void 0 ? void 0 : layer.parameters)) {
205
+ return;
206
+ }
216
207
  layer.parameters.color = newStyle;
217
208
  model.sharedModel.updateLayer(layerId, layer);
218
209
  };
@@ -251,7 +242,7 @@ const Graduated = ({ model, state, okSignalPromise, cancel, layerId, symbologyTa
251
242
  React.createElement("label", null,
252
243
  React.createElement("input", { type: "checkbox", checked: reverseRamp, onChange: e => setReverseRamp(e.target.checked) }),
253
244
  "Reverse Color Ramp"))),
254
- React.createElement(ColorRampControls, { layerParams: layer.parameters, modeOptions: modeOptions, classifyFunc: buildColorInfoFromClassification, showModeRow: true, showRampSelector: symbologyTab === 'color' }),
245
+ React.createElement(ColorRampControls, { layerParams: params, modeOptions: modeOptions, classifyFunc: buildColorInfoFromClassification, showModeRow: true, showRampSelector: symbologyTab === 'color' }),
255
246
  React.createElement(StopContainer, { selectedMethod: symbologyTab || 'color', stopRows: symbologyTab === 'color' ? colorStopRows : radiusStopRows, setStopRows: symbologyTab === 'color' ? setColorStopRows : setRadiusStopRows })));
256
247
  }
257
248
  })();
@@ -1,12 +1,23 @@
1
1
  import colormap from 'colormap';
2
- import React, { useEffect, useRef, useState } from 'react';
2
+ import React, { useEffect, useState } from 'react';
3
3
  import ColorRampSelector from "../../components/color_ramp/ColorRampSelector";
4
- const Heatmap = ({ model, state, okSignalPromise, cancel, layerId, }) => {
4
+ import { useOkSignal } from "../../hooks/useOkSignal";
5
+ import { saveSymbology, } from "../../symbologyUtils";
6
+ import { useLatest } from "../../../../shared/hooks/useLatest";
7
+ import { useEffectiveSymbologyParams } from '../../hooks/useEffectiveSymbologyParams';
8
+ const Heatmap = ({ model, okSignalPromise, layerId, isStorySegmentOverride, segmentId, }) => {
5
9
  if (!layerId) {
6
10
  return;
7
11
  }
8
12
  const layer = model.getLayer(layerId);
9
- if (!(layer === null || layer === void 0 ? void 0 : layer.parameters)) {
13
+ const params = useEffectiveSymbologyParams({
14
+ model,
15
+ layerId: layerId,
16
+ layer,
17
+ isStorySegmentOverride,
18
+ segmentId,
19
+ });
20
+ if (!params) {
10
21
  return;
11
22
  }
12
23
  const [selectedRamp, setSelectedRamp] = useState('viridis');
@@ -15,40 +26,21 @@ const Heatmap = ({ model, state, okSignalPromise, cancel, layerId, }) => {
15
26
  blur: 15,
16
27
  });
17
28
  const [reverseRamp, setReverseRamp] = useState(false);
18
- const selectedRampRef = useRef('viridis');
19
- const heatmapOptionsRef = useRef({
20
- radius: 8,
21
- blur: 15,
22
- });
23
- const reverseRampRef = useRef(false);
29
+ const selectedRampRef = useLatest(selectedRamp);
30
+ const heatmapOptionsRef = useLatest(heatmapOptions);
31
+ const reverseRampRef = useLatest(reverseRamp);
24
32
  useEffect(() => {
25
33
  populateOptions();
26
- okSignalPromise.promise.then(okSignal => {
27
- okSignal.connect(handleOk, this);
28
- });
29
- return () => {
30
- okSignalPromise.promise.then(okSignal => {
31
- okSignal.disconnect(handleOk, this);
32
- });
33
- };
34
34
  }, []);
35
- useEffect(() => {
36
- selectedRampRef.current = selectedRamp;
37
- heatmapOptionsRef.current = heatmapOptions;
38
- reverseRampRef.current = reverseRamp;
39
- }, [selectedRamp, heatmapOptions, reverseRamp]);
40
35
  const populateOptions = async () => {
41
36
  var _a;
42
37
  let colorRamp;
43
- if ((_a = layer.parameters) === null || _a === void 0 ? void 0 : _a.symbologyState) {
44
- colorRamp = layer.parameters.symbologyState.colorRamp;
38
+ if ((_a = params.symbologyState) === null || _a === void 0 ? void 0 : _a.colorRamp) {
39
+ colorRamp = params.symbologyState.colorRamp;
45
40
  }
46
41
  setSelectedRamp(colorRamp ? colorRamp : 'viridis');
47
42
  };
48
43
  const handleOk = () => {
49
- if (!layer.parameters) {
50
- return;
51
- }
52
44
  let colorMap = colormap({
53
45
  colormap: selectedRampRef.current,
54
46
  nshades: 9,
@@ -62,14 +54,23 @@ const Heatmap = ({ model, state, okSignalPromise, cancel, layerId, }) => {
62
54
  colorRamp: selectedRampRef.current,
63
55
  reverse: reverseRampRef.current,
64
56
  };
65
- layer.parameters.symbologyState = symbologyState;
66
- layer.parameters.color = colorMap;
67
- layer.parameters.blur = heatmapOptionsRef.current.blur;
68
- layer.parameters.radius = heatmapOptionsRef.current.radius;
69
- layer.type = 'HeatmapLayer';
70
- model.sharedModel.updateLayer(layerId, layer);
71
- cancel();
57
+ saveSymbology({
58
+ model,
59
+ layerId,
60
+ isStorySegmentOverride,
61
+ segmentId,
62
+ payload: {
63
+ symbologyState,
64
+ color: colorMap,
65
+ },
66
+ mutateLayerBeforeSave: targetLayer => {
67
+ targetLayer.parameters.blur = heatmapOptionsRef.current.blur;
68
+ targetLayer.parameters.radius = heatmapOptionsRef.current.radius;
69
+ targetLayer.type = 'HeatmapLayer';
70
+ },
71
+ });
72
72
  };
73
+ useOkSignal(okSignalPromise, handleOk);
73
74
  return (React.createElement("div", { className: "jp-gis-layer-symbology-container" },
74
75
  React.createElement("p", null, "Represent features based on their density using a heatmap."),
75
76
  React.createElement("div", { className: "jp-gis-symbology-row jp-gis-heatmap" },
@@ -1,7 +1,10 @@
1
- import React, { useEffect, useRef, useState } from 'react';
1
+ import React, { useEffect, useState } from 'react';
2
+ import { useEffectiveSymbologyParams } from "../../hooks/useEffectiveSymbologyParams";
3
+ import { useOkSignal } from "../../hooks/useOkSignal";
4
+ import { saveSymbology, } from "../../symbologyUtils";
5
+ import { useLatest } from "../../../../shared/hooks/useLatest";
2
6
  import { parseColor } from "../../../../tools";
3
- const SimpleSymbol = ({ model, state, okSignalPromise, cancel, layerId, symbologyTab, }) => {
4
- const styleRef = useRef();
7
+ const SimpleSymbol = ({ model, okSignalPromise, layerId, symbologyTab, isStorySegmentOverride, segmentId, }) => {
5
8
  const [style, setStyle] = useState({
6
9
  fillColor: '#3399CC',
7
10
  joinStyle: 'round',
@@ -10,49 +13,30 @@ const SimpleSymbol = ({ model, state, okSignalPromise, cancel, layerId, symbolog
10
13
  strokeWidth: 1.25,
11
14
  radius: 5,
12
15
  });
13
- const joinStyleOptions = ['bevel', 'round', 'miter'];
14
- const capStyleOptions = ['butt', 'round', 'square'];
15
- if (!layerId) {
16
- return;
17
- }
18
- const layer = model.getLayer(layerId);
19
- if (!layer) {
20
- return;
21
- }
16
+ const styleRef = useLatest(style);
17
+ const layer = layerId !== undefined ? model.getLayer(layerId) : null;
18
+ const params = useEffectiveSymbologyParams({
19
+ model,
20
+ layerId: layerId,
21
+ layer,
22
+ isStorySegmentOverride,
23
+ segmentId,
24
+ });
22
25
  useEffect(() => {
23
- if (!layer.parameters) {
26
+ var _a;
27
+ if (!params) {
24
28
  return;
25
29
  }
26
- const initStyle = async () => {
27
- var _a;
28
- if (!layer.parameters) {
29
- return;
30
+ if (((_a = params.symbologyState) === null || _a === void 0 ? void 0 : _a.renderType) === 'Single Symbol' && params.color) {
31
+ const parsed = parseColor(params.color);
32
+ if (parsed) {
33
+ setStyle(parsed);
30
34
  }
31
- const renderType = (_a = layer.parameters) === null || _a === void 0 ? void 0 : _a.symbologyState.renderType;
32
- if (renderType === 'Single Symbol') {
33
- // Parse with fallback logic inside
34
- const parsedStyle = parseColor(layer.parameters.color);
35
- if (parsedStyle) {
36
- setStyle(parsedStyle);
37
- }
38
- }
39
- };
40
- initStyle();
41
- okSignalPromise.promise.then(okSignal => {
42
- okSignal.connect(handleOk, this);
43
- });
44
- return () => {
45
- okSignalPromise.promise.then(okSignal => {
46
- okSignal.disconnect(handleOk, this);
47
- });
48
- };
49
- }, []);
50
- useEffect(() => {
51
- styleRef.current = style;
52
- }, [style]);
35
+ }
36
+ }, [params]);
53
37
  const handleOk = () => {
54
38
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
55
- if (!layer.parameters) {
39
+ if (!layerId || !(layer === null || layer === void 0 ? void 0 : layer.parameters)) {
56
40
  return;
57
41
  }
58
42
  const styleExpr = {
@@ -71,14 +55,28 @@ const SimpleSymbol = ({ model, state, okSignalPromise, cancel, layerId, symbolog
71
55
  const symbologyState = {
72
56
  renderType: 'Single Symbol',
73
57
  };
74
- layer.parameters.symbologyState = symbologyState;
75
- layer.parameters.color = styleExpr;
76
- if (layer.type === 'HeatmapLayer') {
77
- layer.type = 'VectorLayer';
78
- }
79
- model.sharedModel.updateLayer(layerId, layer);
80
- cancel();
58
+ saveSymbology({
59
+ model,
60
+ layerId,
61
+ isStorySegmentOverride,
62
+ segmentId,
63
+ payload: {
64
+ symbologyState,
65
+ color: styleExpr,
66
+ },
67
+ mutateLayerBeforeSave: targetLayer => {
68
+ if (targetLayer.type === 'HeatmapLayer') {
69
+ targetLayer.type = 'VectorLayer';
70
+ }
71
+ },
72
+ });
81
73
  };
74
+ useOkSignal(okSignalPromise, handleOk);
75
+ const joinStyleOptions = ['bevel', 'round', 'miter'];
76
+ const capStyleOptions = ['butt', 'round', 'square'];
77
+ if (!layerId || !layer) {
78
+ return null;
79
+ }
82
80
  const renderColorTab = () => (React.createElement(React.Fragment, null,
83
81
  React.createElement("div", { className: "jp-gis-symbology-row" },
84
82
  React.createElement("label", { htmlFor: 'vector-value-select' }, "Fill Color:"),
@@ -1,4 +1,3 @@
1
- import { getCssVarAsColor } from "../../tools";
2
1
  import { BaseForm } from './baseform';
3
2
  /**
4
3
  * The form to modify story map properties.
@@ -13,21 +12,5 @@ export class StoryEditorPropertiesForm extends BaseForm {
13
12
  uiSchema.presentationTextColor = {
14
13
  'ui:widget': 'color',
15
14
  };
16
- // Set default values from theme CSS variables when not already in data
17
- const schemaProps = schema.properties;
18
- if ((schemaProps === null || schemaProps === void 0 ? void 0 : schemaProps.presentationBgColor) &&
19
- (data === null || data === void 0 ? void 0 : data.presentationBgColor) === undefined) {
20
- const defaultBg = getCssVarAsColor('--jp-layout-color0');
21
- if (defaultBg) {
22
- schemaProps.presentationBgColor.default = defaultBg;
23
- }
24
- }
25
- if ((schemaProps === null || schemaProps === void 0 ? void 0 : schemaProps.presentationTextColor) &&
26
- (data === null || data === void 0 ? void 0 : data.presentationTextColor) === undefined) {
27
- const defaultText = getCssVarAsColor('--jp-ui-font-color0');
28
- if (defaultText) {
29
- schemaProps.presentationTextColor.default = defaultText;
30
- }
31
- }
32
15
  }
33
16
  }
@@ -5,6 +5,10 @@ import { IChangeEvent, ISubmitEvent } from '@rjsf/core';
5
5
  import { RJSFSchema, UiSchema } from '@rjsf/utils';
6
6
  import * as React from 'react';
7
7
  import { IDict } from "../../types";
8
+ export interface IJupyterGISFormContext<TFormData = IDict | undefined> {
9
+ model: IJupyterGISModel;
10
+ formData: TFormData;
11
+ }
8
12
  export interface IBaseFormStates {
9
13
  schema?: RJSFSchema;
10
14
  extraErrors?: any;
@@ -59,9 +63,16 @@ export interface IBaseFormProps {
59
63
  * It will be up to the user of this class to actually perform the creation/edit using syncdata.
60
64
  */
61
65
  export declare class BaseForm extends React.Component<IBaseFormProps, IBaseFormStates> {
66
+ /** Skip syncData for the initial onChange (RJSF populating form), only sync on user edits. */
67
+ private isInitialLoadRef;
62
68
  constructor(props: IBaseFormProps);
63
69
  componentDidUpdate(prevProps: IBaseFormProps, prevState: IBaseFormStates): void;
64
70
  componentDidMount(): void;
71
+ /**
72
+ * Fills null/undefined values in data with schema defaults (mutates data).
73
+ * @returns true if any null/undefined was replaced by a default
74
+ */
75
+ protected applySchemaDefaults(data: IDict<any> | undefined, schema: RJSFSchema): boolean;
65
76
  protected processSchema(data: IDict<any> | undefined, schema: RJSFSchema, uiSchema: UiSchema): void;
66
77
  /**
67
78
  * Remove a specific entry from the form. Can be used in subclasses if needed while under processSchema.
@@ -9,13 +9,18 @@ var __rest = (this && this.__rest) || function (s, e) {
9
9
  }
10
10
  return t;
11
11
  };
12
- import { Slider } from '@jupyter/react-components';
13
12
  import { FormComponent } from '@jupyterlab/ui-components';
14
13
  import validatorAjv8 from '@rjsf/validator-ajv8';
15
14
  import * as React from 'react';
16
15
  import { deepCopy } from "../../tools";
16
+ import { LayerSelect } from './components/LayerSelect';
17
+ import OpacitySlider from './components/OpacitySlider';
17
18
  const WrappedFormComponent = props => {
18
- const { fields } = props, rest = __rest(props, ["fields"]);
19
+ const rest = __rest(props, []);
20
+ const fields = {
21
+ opacity: OpacitySlider,
22
+ layerSelect: LayerSelect,
23
+ };
19
24
  return (React.createElement(FormComponent, Object.assign({}, rest, { validator: validatorAjv8, fields: Object.assign({}, fields) })));
20
25
  };
21
26
  /**
@@ -25,8 +30,17 @@ const WrappedFormComponent = props => {
25
30
  */
26
31
  export class BaseForm extends React.Component {
27
32
  constructor(props) {
33
+ var _a;
28
34
  super(props);
35
+ /** Skip syncData for the initial onChange (RJSF populating form), only sync on user edits. */
36
+ this.isInitialLoadRef = true;
29
37
  this.currentFormData = deepCopy(this.props.sourceData);
38
+ if (props.schema) {
39
+ const applied = this.applySchemaDefaults(this.currentFormData, props.schema);
40
+ if (applied) {
41
+ props.syncData((_a = this.currentFormData) !== null && _a !== void 0 ? _a : {});
42
+ }
43
+ }
30
44
  this.state = {
31
45
  schema: props.schema,
32
46
  extraErrors: {},
@@ -35,6 +49,14 @@ export class BaseForm extends React.Component {
35
49
  componentDidUpdate(prevProps, prevState) {
36
50
  if (prevProps.sourceData !== this.props.sourceData) {
37
51
  this.currentFormData = deepCopy(this.props.sourceData);
52
+ // if (this.props.schema) {
53
+ // const applied = this.applySchemaDefaults(
54
+ // this.currentFormData,
55
+ // this.props.schema as RJSFSchema,
56
+ // );
57
+ // if (applied) {
58
+ // this.props.syncData(this.currentFormData ?? {});
59
+ // }
38
60
  const schema = deepCopy(this.props.schema);
39
61
  this.setState(old => (Object.assign(Object.assign({}, old), { schema })));
40
62
  }
@@ -45,6 +67,43 @@ export class BaseForm extends React.Component {
45
67
  this.setState(old => (Object.assign(Object.assign({}, old), this.state.extraErrors)));
46
68
  this.props.formErrorSignal.emit(extraErrors);
47
69
  }
70
+ this.isInitialLoadRef = false;
71
+ }
72
+ /**
73
+ * Fills null/undefined values in data with schema defaults (mutates data).
74
+ * @returns true if any null/undefined was replaced by a default
75
+ */
76
+ applySchemaDefaults(data, schema) {
77
+ if (!data || !schema.properties) {
78
+ return false;
79
+ }
80
+ let applied = false;
81
+ const props = schema.properties;
82
+ for (const [key, propSchema] of Object.entries(props)) {
83
+ if (propSchema === null ||
84
+ propSchema === undefined ||
85
+ typeof propSchema !== 'object') {
86
+ continue;
87
+ }
88
+ const val = data[key];
89
+ if (val === null || val === undefined) {
90
+ if ('default' in propSchema &&
91
+ propSchema.default !== undefined) {
92
+ data[key] = deepCopy(propSchema.default);
93
+ applied = true;
94
+ }
95
+ }
96
+ else if (propSchema.type === 'object' &&
97
+ typeof val === 'object' &&
98
+ val !== null &&
99
+ !Array.isArray(val) &&
100
+ propSchema.properties) {
101
+ if (this.applySchemaDefaults(val, propSchema)) {
102
+ applied = true;
103
+ }
104
+ }
105
+ }
106
+ return applied;
48
107
  }
49
108
  processSchema(data, schema, uiSchema) {
50
109
  if (!schema['properties']) {
@@ -72,40 +131,7 @@ export class BaseForm extends React.Component {
72
131
  }
73
132
  if (k === 'opacity') {
74
133
  uiSchema[k] = {
75
- 'ui:field': (props) => {
76
- const [inputValue, setInputValue] = React.useState(props.formData.toFixed(1));
77
- React.useEffect(() => {
78
- setInputValue(props.formData.toFixed(1));
79
- }, [props.formData]);
80
- const handleSliderChange = (event) => {
81
- const target = event.target;
82
- if (target && '_value' in target) {
83
- const sliderValue = parseFloat(target._value); // Slider value is in 0–10 range
84
- const normalizedValue = sliderValue / 10; // Normalize to 0.1–1 range
85
- props.onChange(normalizedValue);
86
- }
87
- };
88
- const handleInputChange = (event) => {
89
- const value = event.target.value;
90
- setInputValue(value);
91
- const parsedValue = parseFloat(value);
92
- if (!isNaN(parsedValue) &&
93
- parsedValue >= 0.1 &&
94
- parsedValue <= 1) {
95
- props.onChange(parsedValue);
96
- }
97
- };
98
- return (React.createElement("div", { style: { display: 'flex', alignItems: 'center', gap: '8px' } },
99
- React.createElement(Slider, { min: 1, max: 10, step: 1, valueAsNumber: props.formData * 10, onChange: handleSliderChange }),
100
- React.createElement("input", { type: "number", value: inputValue, step: 0.1, min: 0.1, max: 1, onChange: handleInputChange, style: {
101
- width: '50px',
102
- textAlign: 'center',
103
- border: '1px solid #ccc',
104
- borderRadius: '4px',
105
- padding: '4px',
106
- marginBottom: '5px',
107
- } })));
108
- },
134
+ 'ui:field': 'opacity',
109
135
  };
110
136
  }
111
137
  // Don't show readOnly properties when it's a form for updating an object
@@ -154,7 +180,12 @@ export class BaseForm extends React.Component {
154
180
  this.props.formErrorSignal.emit(extraErrors);
155
181
  }
156
182
  if (this.props.formContext === 'update') {
157
- this.syncData(this.currentFormData);
183
+ if (!this.isInitialLoadRef) {
184
+ this.syncData(this.currentFormData);
185
+ }
186
+ else {
187
+ this.isInitialLoadRef = false;
188
+ }
158
189
  }
159
190
  }
160
191
  onFormBlur(id, value) {
@@ -191,7 +222,10 @@ export class BaseForm extends React.Component {
191
222
  (_a = submitRef.current) === null || _a === void 0 ? void 0 : _a.click();
192
223
  }
193
224
  } },
194
- React.createElement(WrappedFormComponent, { schema: schema, uiSchema: uiSchema, formData: formData, onSubmit: this.onFormSubmit.bind(this), onChange: this.onFormChange.bind(this), onBlur: this.onFormBlur.bind(this), ok: this.props.ok, cancel: this.props.cancel, liveValidate: true, children: React.createElement("button", { ref: submitRef, type: "submit", style: { display: 'none' } }), extraErrors: this.state.extraErrors }))));
225
+ React.createElement(WrappedFormComponent, { schema: schema, uiSchema: uiSchema, formData: formData, formContext: {
226
+ model: this.props.model,
227
+ formData,
228
+ }, onSubmit: this.onFormSubmit.bind(this), onChange: this.onFormChange.bind(this), onBlur: this.onFormBlur.bind(this), ok: this.props.ok, cancel: this.props.cancel, liveValidate: true, children: React.createElement("button", { ref: submitRef, type: "submit", style: { display: 'none' } }), extraErrors: this.state.extraErrors }))));
195
229
  }
196
230
  }
197
231
  }
@@ -0,0 +1,7 @@
1
+ import { FieldProps } from '@rjsf/utils';
2
+ import React from 'react';
3
+ /**
4
+ * Simple select populated with layers (valid types only).
5
+ * Used as the targetLayer field inside layerOverride array items.
6
+ */
7
+ export declare function LayerSelect(props: FieldProps): React.JSX.Element | null;