@jupytergis/base 0.3.0 → 0.4.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 (77) hide show
  1. package/lib/annotations/components/Annotation.js +1 -1
  2. package/lib/annotations/model.d.ts +6 -7
  3. package/lib/annotations/model.js +15 -15
  4. package/lib/commands.d.ts +2 -3
  5. package/lib/commands.js +117 -62
  6. package/lib/constants.d.ts +2 -0
  7. package/lib/constants.js +4 -1
  8. package/lib/dialogs/formdialog.js +2 -2
  9. package/lib/dialogs/layerBrowserDialog.d.ts +4 -5
  10. package/lib/dialogs/layerBrowserDialog.js +9 -9
  11. package/lib/dialogs/symbology/hooks/useGetBandInfo.d.ts +3 -8
  12. package/lib/dialogs/symbology/hooks/useGetBandInfo.js +16 -28
  13. package/lib/dialogs/symbology/hooks/useGetProperties.d.ts +1 -1
  14. package/lib/dialogs/symbology/hooks/useGetProperties.js +6 -8
  15. package/lib/dialogs/symbology/symbologyDialog.d.ts +2 -3
  16. package/lib/dialogs/symbology/symbologyDialog.js +10 -9
  17. package/lib/dialogs/symbology/tiff_layer/TiffRendering.d.ts +1 -1
  18. package/lib/dialogs/symbology/tiff_layer/TiffRendering.js +6 -6
  19. package/lib/dialogs/symbology/tiff_layer/components/BandRow.js +3 -1
  20. package/lib/dialogs/symbology/tiff_layer/types/MultibandColor.d.ts +1 -1
  21. package/lib/dialogs/symbology/tiff_layer/types/MultibandColor.js +5 -4
  22. package/lib/dialogs/symbology/tiff_layer/types/SingleBandPseudoColor.d.ts +1 -1
  23. package/lib/dialogs/symbology/tiff_layer/types/SingleBandPseudoColor.js +8 -7
  24. package/lib/dialogs/symbology/vector_layer/VectorRendering.d.ts +1 -1
  25. package/lib/dialogs/symbology/vector_layer/VectorRendering.js +18 -13
  26. package/lib/dialogs/symbology/vector_layer/types/Categorized.d.ts +1 -1
  27. package/lib/dialogs/symbology/vector_layer/types/Categorized.js +30 -19
  28. package/lib/dialogs/symbology/vector_layer/types/Graduated.d.ts +1 -1
  29. package/lib/dialogs/symbology/vector_layer/types/Graduated.js +16 -13
  30. package/lib/dialogs/symbology/vector_layer/types/Heatmap.d.ts +4 -0
  31. package/lib/dialogs/symbology/vector_layer/types/Heatmap.js +77 -0
  32. package/lib/dialogs/symbology/vector_layer/types/SimpleSymbol.d.ts +1 -1
  33. package/lib/dialogs/symbology/vector_layer/types/SimpleSymbol.js +4 -3
  34. package/lib/formbuilder/creationform.d.ts +1 -2
  35. package/lib/formbuilder/creationform.js +4 -4
  36. package/lib/formbuilder/editform.d.ts +1 -2
  37. package/lib/formbuilder/editform.js +7 -7
  38. package/lib/formbuilder/formselectors.js +5 -2
  39. package/lib/formbuilder/objectform/baseform.d.ts +3 -4
  40. package/lib/formbuilder/objectform/baseform.js +2 -2
  41. package/lib/formbuilder/objectform/fileselectorwidget.js +13 -6
  42. package/lib/formbuilder/objectform/geotiffsource.d.ts +5 -1
  43. package/lib/formbuilder/objectform/geotiffsource.js +51 -18
  44. package/lib/formbuilder/objectform/heatmapLayerForm.d.ts +11 -0
  45. package/lib/formbuilder/objectform/heatmapLayerForm.js +60 -0
  46. package/lib/formbuilder/objectform/vectorlayerform.d.ts +0 -2
  47. package/lib/formbuilder/objectform/vectorlayerform.js +0 -59
  48. package/lib/mainview/TemporalSlider.d.ts +8 -0
  49. package/lib/mainview/TemporalSlider.js +303 -0
  50. package/lib/mainview/mainView.d.ts +26 -5
  51. package/lib/mainview/mainView.js +221 -108
  52. package/lib/mainview/mainviewmodel.d.ts +4 -0
  53. package/lib/mainview/mainviewmodel.js +4 -0
  54. package/lib/mainview/mainviewwidget.d.ts +0 -2
  55. package/lib/mainview/mainviewwidget.js +0 -2
  56. package/lib/panelview/annotationPanel.js +5 -5
  57. package/lib/panelview/components/filter-panel/Filter.js +4 -25
  58. package/lib/panelview/components/identify-panel/IdentifyPanel.js +1 -1
  59. package/lib/panelview/components/layers.js +2 -2
  60. package/lib/panelview/components/sources.js +1 -1
  61. package/lib/panelview/leftpanel.d.ts +3 -0
  62. package/lib/panelview/leftpanel.js +5 -1
  63. package/lib/panelview/model.js +8 -8
  64. package/lib/panelview/objectproperties.js +10 -10
  65. package/lib/panelview/rightpanel.d.ts +1 -1
  66. package/lib/panelview/rightpanel.js +10 -10
  67. package/lib/toolbar/widget.d.ts +1 -1
  68. package/lib/toolbar/widget.js +44 -32
  69. package/lib/tools.d.ts +6 -16
  70. package/lib/tools.js +54 -56
  71. package/lib/types.d.ts +2 -0
  72. package/lib/widget.d.ts +30 -6
  73. package/lib/widget.js +43 -9
  74. package/package.json +4 -3
  75. package/style/base.css +10 -0
  76. package/style/symbologyDialog.css +7 -1
  77. package/style/temporalSlider.css +47 -0
@@ -1,4 +1,4 @@
1
1
  import React from 'react';
2
2
  import { ISymbologyDialogProps } from '../../symbologyDialog';
3
- declare const Graduated: ({ context, state, okSignalPromise, cancel, layerId }: ISymbologyDialogProps) => React.JSX.Element | undefined;
3
+ declare const Graduated: ({ model, state, okSignalPromise, cancel, layerId }: ISymbologyDialogProps) => React.JSX.Element | undefined;
4
4
  export default Graduated;
@@ -1,11 +1,12 @@
1
1
  import React, { useEffect, useRef, useState } from 'react';
2
+ import { getNumericFeatureAttributes } from '../../../../tools';
2
3
  import { VectorClassifications } from '../../classificationModes';
3
4
  import ColorRamp from '../../components/color_ramp/ColorRamp';
4
- import ValueSelect from '../components/ValueSelect';
5
5
  import StopContainer from '../../components/color_stops/StopContainer';
6
6
  import { useGetProperties } from '../../hooks/useGetProperties';
7
7
  import { Utils, VectorUtils } from '../../symbologyUtils';
8
- const Graduated = ({ context, state, okSignalPromise, cancel, layerId }) => {
8
+ import ValueSelect from '../components/ValueSelect';
9
+ const Graduated = ({ model, state, okSignalPromise, cancel, layerId }) => {
9
10
  const modeOptions = [
10
11
  'quantile',
11
12
  'equal interval',
@@ -21,17 +22,18 @@ const Graduated = ({ context, state, okSignalPromise, cancel, layerId }) => {
21
22
  const [selectedMethod, setSelectedMethod] = useState('color');
22
23
  const [stopRows, setStopRows] = useState([]);
23
24
  const [methodOptions, setMethodOptions] = useState(['color']);
25
+ const [features, setFeatures] = useState({});
24
26
  const [colorRampOptions, setColorRampOptions] = useState();
25
27
  if (!layerId) {
26
28
  return;
27
29
  }
28
- const layer = context.model.getLayer(layerId);
30
+ const layer = model.getLayer(layerId);
29
31
  if (!(layer === null || layer === void 0 ? void 0 : layer.parameters)) {
30
32
  return;
31
33
  }
32
- const { featureProps } = useGetProperties({
34
+ const { featureProperties } = useGetProperties({
33
35
  layerId,
34
- model: context.model
36
+ model: model
35
37
  });
36
38
  useEffect(() => {
37
39
  var _a, _b;
@@ -61,21 +63,21 @@ const Graduated = ({ context, state, okSignalPromise, cancel, layerId }) => {
61
63
  colorRampOptionsRef.current = colorRampOptions;
62
64
  }, [selectedValue, selectedMethod, stopRows, colorRampOptions]);
63
65
  useEffect(() => {
64
- populateOptions();
65
- }, [featureProps]);
66
- const populateOptions = async () => {
67
66
  var _a, _b, _c, _d, _e;
68
67
  // Set up method options
69
68
  if (((_a = layer === null || layer === void 0 ? void 0 : layer.parameters) === null || _a === void 0 ? void 0 : _a.type) === 'circle') {
70
69
  const options = ['color', 'radius'];
71
70
  setMethodOptions(options);
72
71
  }
72
+ // We only want number values here
73
+ const numericFeatures = getNumericFeatureAttributes(featureProperties);
74
+ setFeatures(numericFeatures);
73
75
  const layerParams = layer.parameters;
74
- const value = (_c = (_b = layerParams.symbologyState) === null || _b === void 0 ? void 0 : _b.value) !== null && _c !== void 0 ? _c : Object.keys(featureProps)[0];
76
+ const value = (_c = (_b = layerParams.symbologyState) === null || _b === void 0 ? void 0 : _b.value) !== null && _c !== void 0 ? _c : Object.keys(numericFeatures)[0];
75
77
  const method = (_e = (_d = layerParams.symbologyState) === null || _d === void 0 ? void 0 : _d.method) !== null && _e !== void 0 ? _e : 'color';
76
78
  setSelectedValue(value);
77
79
  setSelectedMethod(method);
78
- };
80
+ }, [featureProperties]);
79
81
  const handleOk = () => {
80
82
  var _a, _b, _c, _d;
81
83
  if (!layer.parameters) {
@@ -116,7 +118,8 @@ const Graduated = ({ context, state, okSignalPromise, cancel, layerId }) => {
116
118
  };
117
119
  layer.parameters.symbologyState = symbologyState;
118
120
  layer.parameters.color = newStyle;
119
- context.model.sharedModel.updateLayer(layerId, layer);
121
+ layer.type = 'VectorLayer';
122
+ model.sharedModel.updateLayer(layerId, layer);
120
123
  cancel();
121
124
  };
122
125
  const buildColorInfoFromClassification = (selectedMode, numberOfShades, selectedRamp) => {
@@ -126,7 +129,7 @@ const Graduated = ({ context, state, okSignalPromise, cancel, layerId }) => {
126
129
  selectedMode
127
130
  });
128
131
  let stops;
129
- const values = Array.from(featureProps[selectedValue]);
132
+ const values = Array.from(features[selectedValue]);
130
133
  switch (selectedMode) {
131
134
  case 'quantile':
132
135
  stops = VectorClassifications.calculateQuantileBreaks(values, +numberOfShades);
@@ -159,7 +162,7 @@ const Graduated = ({ context, state, okSignalPromise, cancel, layerId }) => {
159
162
  setStopRows(stopOutputPairs);
160
163
  };
161
164
  return (React.createElement("div", { className: "jp-gis-layer-symbology-container" },
162
- React.createElement(ValueSelect, { featureProperties: featureProps, selectedValue: selectedValue, setSelectedValue: setSelectedValue }),
165
+ React.createElement(ValueSelect, { featureProperties: features, selectedValue: selectedValue, setSelectedValue: setSelectedValue }),
163
166
  React.createElement("div", { className: "jp-gis-symbology-row" },
164
167
  React.createElement("label", { htmlFor: 'vector-method-select' }, "Method:"),
165
168
  React.createElement("select", { name: 'vector-method-select', onChange: event => setSelectedMethod(event.target.value), className: "jp-mod-styled" }, methodOptions.map((method, index) => (React.createElement("option", { key: index, value: method, selected: method === selectedMethod, className: "jp-mod-styled" }, method))))),
@@ -0,0 +1,4 @@
1
+ import React from 'react';
2
+ import { ISymbologyDialogProps } from '../../symbologyDialog';
3
+ declare const Heatmap: ({ model, state, okSignalPromise, cancel, layerId }: ISymbologyDialogProps) => React.JSX.Element | undefined;
4
+ export default Heatmap;
@@ -0,0 +1,77 @@
1
+ import colormap from 'colormap';
2
+ import React, { useEffect, useRef, useState } from 'react';
3
+ import CanvasSelectComponent from '../../components/color_ramp/CanvasSelectComponent';
4
+ const Heatmap = ({ model, state, okSignalPromise, cancel, layerId }) => {
5
+ if (!layerId) {
6
+ return;
7
+ }
8
+ const layer = model.getLayer(layerId);
9
+ if (!(layer === null || layer === void 0 ? void 0 : layer.parameters)) {
10
+ return;
11
+ }
12
+ const [selectedRamp, setSelectedRamp] = useState('');
13
+ const [heatmapOptions, setHetamapOptions] = useState({
14
+ radius: 8,
15
+ blur: 15
16
+ });
17
+ const selectedRampRef = useRef('cool');
18
+ const heatmapOptionsRef = useRef({
19
+ radius: 8,
20
+ blur: 15
21
+ });
22
+ useEffect(() => {
23
+ populateOptions();
24
+ okSignalPromise.promise.then(okSignal => {
25
+ okSignal.connect(handleOk, this);
26
+ });
27
+ return () => {
28
+ okSignalPromise.promise.then(okSignal => {
29
+ okSignal.disconnect(handleOk, this);
30
+ });
31
+ };
32
+ }, []);
33
+ useEffect(() => {
34
+ selectedRampRef.current = selectedRamp;
35
+ heatmapOptionsRef.current = heatmapOptions;
36
+ }, [selectedRamp, heatmapOptions]);
37
+ const populateOptions = async () => {
38
+ var _a;
39
+ let colorRamp;
40
+ if ((_a = layer.parameters) === null || _a === void 0 ? void 0 : _a.symbologyState) {
41
+ colorRamp = layer.parameters.symbologyState.colorRamp;
42
+ }
43
+ setSelectedRamp(colorRamp ? colorRamp : 'cool');
44
+ };
45
+ const handleOk = () => {
46
+ if (!layer.parameters) {
47
+ return;
48
+ }
49
+ const colorMap = colormap({
50
+ colormap: selectedRampRef.current,
51
+ nshades: 9,
52
+ format: 'hex'
53
+ });
54
+ const symbologyState = {
55
+ renderType: 'Heatmap',
56
+ colorRamp: selectedRampRef.current
57
+ };
58
+ layer.parameters.symbologyState = symbologyState;
59
+ layer.parameters.color = colorMap;
60
+ layer.parameters.blur = heatmapOptionsRef.current.blur;
61
+ layer.parameters.radius = heatmapOptionsRef.current.radius;
62
+ layer.type = 'HeatmapLayer';
63
+ model.sharedModel.updateLayer(layerId, layer);
64
+ cancel();
65
+ };
66
+ return (React.createElement("div", { className: "jp-gis-layer-symbology-container" },
67
+ React.createElement("div", { className: "jp-gis-symbology-row jp-gis-heatmap" },
68
+ React.createElement("label", { htmlFor: "color-ramp-select" }, "Color Ramp:"),
69
+ React.createElement(CanvasSelectComponent, { selectedRamp: selectedRamp, setSelected: setSelectedRamp })),
70
+ React.createElement("div", { className: "jp-gis-symbology-row" },
71
+ React.createElement("label", { htmlFor: 'vector-value-select' }, "Radius:"),
72
+ 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 }))) })),
73
+ React.createElement("div", { className: "jp-gis-symbology-row" },
74
+ React.createElement("label", { htmlFor: 'vector-value-select' }, "Blur:"),
75
+ React.createElement("input", { type: "number", value: heatmapOptions.blur, className: "jp-mod-styled", onChange: event => setHetamapOptions(prevState => (Object.assign(Object.assign({}, prevState), { blur: +event.target.value }))) }))));
76
+ };
77
+ export default Heatmap;
@@ -1,4 +1,4 @@
1
1
  import React from 'react';
2
2
  import { ISymbologyDialogProps } from '../../symbologyDialog';
3
- declare const SimpleSymbol: ({ context, state, okSignalPromise, cancel, layerId }: ISymbologyDialogProps) => React.JSX.Element | undefined;
3
+ declare const SimpleSymbol: ({ model, state, okSignalPromise, cancel, layerId }: ISymbologyDialogProps) => React.JSX.Element | undefined;
4
4
  export default SimpleSymbol;
@@ -1,6 +1,6 @@
1
1
  import React, { useEffect, useRef, useState } from 'react';
2
2
  import { parseColor } from '../../../../tools';
3
- const SimpleSymbol = ({ context, state, okSignalPromise, cancel, layerId }) => {
3
+ const SimpleSymbol = ({ model, state, okSignalPromise, cancel, layerId }) => {
4
4
  const styleRef = useRef();
5
5
  const [useCircleStuff, setUseCircleStuff] = useState(false);
6
6
  const [style, setStyle] = useState({
@@ -16,7 +16,7 @@ const SimpleSymbol = ({ context, state, okSignalPromise, cancel, layerId }) => {
16
16
  if (!layerId) {
17
17
  return;
18
18
  }
19
- const layer = context.model.getLayer(layerId);
19
+ const layer = model.getLayer(layerId);
20
20
  if (!layer) {
21
21
  return;
22
22
  }
@@ -75,7 +75,8 @@ const SimpleSymbol = ({ context, state, okSignalPromise, cancel, layerId }) => {
75
75
  };
76
76
  layer.parameters.symbologyState = symbologyState;
77
77
  layer.parameters.color = styleExpr;
78
- context.model.sharedModel.updateLayer(layerId, layer);
78
+ layer.type = 'VectorLayer';
79
+ model.sharedModel.updateLayer(layerId, layer);
79
80
  cancel();
80
81
  };
81
82
  return (React.createElement("div", { className: "jp-gis-layer-symbology-container" },
@@ -1,4 +1,3 @@
1
- import { DocumentRegistry } from '@jupyterlab/docregistry';
2
1
  import { IDict, IJGISFormSchemaRegistry, IJupyterGISModel, LayerType, SourceType } from '@jupytergis/schema';
3
2
  import { Dialog } from '@jupyterlab/apputils';
4
3
  import { Signal } from '@lumino/signaling';
@@ -37,7 +36,7 @@ export interface ICreationFormProps {
37
36
  */
38
37
  cancel?: () => void;
39
38
  formSchemaRegistry: IJGISFormSchemaRegistry;
40
- context: DocumentRegistry.IContext<IJupyterGISModel>;
39
+ model: IJupyterGISModel;
41
40
  /**
42
41
  * A signal emitting when the form changed, with a boolean whether there are some
43
42
  * extra errors or not.
@@ -21,8 +21,8 @@ export class CreationForm extends React.Component {
21
21
  constructor(props) {
22
22
  super(props);
23
23
  this.sourceFormChangedSignal = new Signal(this);
24
- this.filePath = props.context.path;
25
- this.jGISModel = props.context.model;
24
+ this.filePath = props.model.filePath;
25
+ this.jGISModel = props.model;
26
26
  }
27
27
  render() {
28
28
  var _a;
@@ -105,12 +105,12 @@ export class CreationForm extends React.Component {
105
105
  return (React.createElement("div", null,
106
106
  this.props.createSource && (React.createElement("div", null,
107
107
  React.createElement("h3", null, "Source Properties"),
108
- React.createElement(SourceForm, { formContext: "create", model: this.jGISModel, filePath: `${this.filePath}::panel`, schema: sourceSchema, sourceData: this.props.sourceData, syncData: (properties) => {
108
+ React.createElement(SourceForm, { formContext: "create", model: this.jGISModel, filePath: this.filePath, schema: sourceSchema, sourceData: this.props.sourceData, syncData: (properties) => {
109
109
  sourceCreationPromise === null || sourceCreationPromise === void 0 ? void 0 : sourceCreationPromise.resolve(properties);
110
110
  }, ok: this.props.ok, cancel: this.props.cancel, formChangedSignal: this.sourceFormChangedSignal, formErrorSignal: this.props.formErrorSignal, dialogOptions: this.props.dialogOptions, sourceType: this.props.sourceType }))),
111
111
  this.props.createLayer && (React.createElement("div", null,
112
112
  React.createElement("h3", null, "Layer Properties"),
113
- React.createElement(LayerForm, { formContext: "create", sourceType: this.props.sourceType, model: this.jGISModel, filePath: `${this.filePath}::panel`, schema: layerSchema, sourceData: layerData, syncData: (properties) => {
113
+ React.createElement(LayerForm, { formContext: "create", sourceType: this.props.sourceType, model: this.jGISModel, filePath: this.filePath, schema: layerSchema, sourceData: layerData, syncData: (properties) => {
114
114
  layerCreationPromise === null || layerCreationPromise === void 0 ? void 0 : layerCreationPromise.resolve(properties);
115
115
  }, ok: this.props.ok, cancel: this.props.cancel, sourceFormChangedSignal: this.sourceFormChangedSignal, formErrorSignal: this.props.formErrorSignal, dialogOptions: this.props.dialogOptions })))));
116
116
  }
@@ -1,4 +1,3 @@
1
- import { DocumentRegistry } from '@jupyterlab/docregistry';
2
1
  import { IJGISFormSchemaRegistry, IJupyterGISModel } from '@jupytergis/schema';
3
2
  import * as React from 'react';
4
3
  export interface IEditFormProps {
@@ -11,7 +10,7 @@ export interface IEditFormProps {
11
10
  */
12
11
  source: string | undefined;
13
12
  formSchemaRegistry: IJGISFormSchemaRegistry;
14
- context: DocumentRegistry.IContext<IJupyterGISModel>;
13
+ model: IJupyterGISModel;
15
14
  }
16
15
  /**
17
16
  * Form for editing a source, a layer or both at the same time
@@ -1,7 +1,7 @@
1
- import { deepCopy } from '../tools';
1
+ import { Signal } from '@lumino/signaling';
2
2
  import * as React from 'react';
3
+ import { deepCopy } from '../tools';
3
4
  import { getLayerTypeForm, getSourceTypeForm } from './formselectors';
4
- import { Signal } from '@lumino/signaling';
5
5
  /**
6
6
  * Form for editing a source, a layer or both at the same time
7
7
  */
@@ -14,14 +14,14 @@ export class EditForm extends React.Component {
14
14
  if (!id) {
15
15
  return;
16
16
  }
17
- this.props.context.model.sharedModel.updateObjectParameters(id, properties);
17
+ this.props.model.sharedModel.updateObjectParameters(id, properties);
18
18
  }
19
19
  render() {
20
20
  let layerSchema = undefined;
21
21
  let LayerForm = undefined;
22
22
  let layerData = undefined;
23
23
  if (this.props.layer) {
24
- const layer = this.props.context.model.getLayer(this.props.layer);
24
+ const layer = this.props.model.getLayer(this.props.layer);
25
25
  if (!layer) {
26
26
  return;
27
27
  }
@@ -38,7 +38,7 @@ export class EditForm extends React.Component {
38
38
  let sourceData = undefined;
39
39
  let source = undefined;
40
40
  if (this.props.source) {
41
- source = this.props.context.model.getSource(this.props.source);
41
+ source = this.props.model.getSource(this.props.source);
42
42
  if (!source) {
43
43
  return;
44
44
  }
@@ -53,12 +53,12 @@ export class EditForm extends React.Component {
53
53
  return (React.createElement("div", null,
54
54
  this.props.layer && LayerForm && (React.createElement("div", null,
55
55
  React.createElement("h3", { style: { paddingLeft: '5px' } }, "Layer Properties"),
56
- React.createElement(LayerForm, { formContext: "create", sourceType: (source === null || source === void 0 ? void 0 : source.type) || 'RasterSource', model: this.props.context.model, filePath: `${this.props.context.path}::panel`, schema: layerSchema, sourceData: layerData, syncData: (properties) => {
56
+ React.createElement(LayerForm, { formContext: "create", sourceType: (source === null || source === void 0 ? void 0 : source.type) || 'RasterSource', model: this.props.model, filePath: this.props.model.filePath, schema: layerSchema, sourceData: layerData, syncData: (properties) => {
57
57
  this.syncObjectProperties(this.props.layer, properties);
58
58
  } }))),
59
59
  this.props.source && SourceForm && (React.createElement("div", null,
60
60
  React.createElement("h3", { style: { paddingLeft: '5px' } }, "Source Properties"),
61
- React.createElement(SourceForm, { formContext: "create", model: this.props.context.model, filePath: `${this.props.context.path}::panel`, schema: sourceSchema, sourceData: sourceData, syncData: (properties) => {
61
+ React.createElement(SourceForm, { formContext: "create", model: this.props.model, filePath: this.props.model.filePath, schema: sourceSchema, sourceData: sourceData, syncData: (properties) => {
62
62
  this.syncObjectProperties(this.props.source, properties);
63
63
  }, formChangedSignal: this.sourceFormChangedSignal, sourceType: (source === null || source === void 0 ? void 0 : source.type) || 'RasterSource' })))));
64
64
  }
@@ -1,12 +1,13 @@
1
1
  import { BaseForm } from './objectform/baseform';
2
2
  import { GeoJSONSourcePropertiesForm } from './objectform/geojsonsource';
3
+ import { GeoTiffSourcePropertiesForm } from './objectform/geotiffsource';
4
+ import { HeatmapLayerPropertiesForm } from './objectform/heatmapLayerForm';
3
5
  import { HillshadeLayerPropertiesForm } from './objectform/hillshadeLayerForm';
4
6
  import { LayerPropertiesForm } from './objectform/layerform';
7
+ import { PathBasedSourcePropertiesForm } from './objectform/pathbasedsource';
5
8
  import { TileSourcePropertiesForm } from './objectform/tilesourceform';
6
9
  import { VectorLayerPropertiesForm } from './objectform/vectorlayerform';
7
10
  import { WebGlLayerPropertiesForm } from './objectform/webGlLayerForm';
8
- import { GeoTiffSourcePropertiesForm } from './objectform/geotiffsource';
9
- import { PathBasedSourcePropertiesForm } from './objectform/pathbasedsource';
10
11
  export function getLayerTypeForm(layerType) {
11
12
  let LayerForm = LayerPropertiesForm;
12
13
  switch (layerType) {
@@ -20,6 +21,8 @@ export function getLayerTypeForm(layerType) {
20
21
  case 'WebGlLayer':
21
22
  LayerForm = WebGlLayerPropertiesForm;
22
23
  break;
24
+ case 'HeatmapLayer':
25
+ LayerForm = HeatmapLayerPropertiesForm;
23
26
  // ADD MORE FORM TYPES HERE
24
27
  }
25
28
  return LayerForm;
@@ -1,10 +1,9 @@
1
- import { IChangeEvent, ISubmitEvent } from '@rjsf/core';
2
- import * as React from 'react';
3
- import { IJupyterGISModel } from '@jupytergis/schema';
1
+ import { IJupyterGISModel, SourceType } from '@jupytergis/schema';
4
2
  import { Dialog } from '@jupyterlab/apputils';
5
3
  import { Signal } from '@lumino/signaling';
4
+ import { IChangeEvent, ISubmitEvent } from '@rjsf/core';
5
+ import * as React from 'react';
6
6
  import { IDict } from '../../types';
7
- import { SourceType } from '@jupytergis/schema';
8
7
  export interface IBaseFormStates {
9
8
  schema?: IDict;
10
9
  extraErrors?: any;
@@ -9,11 +9,11 @@ var __rest = (this && this.__rest) || function (s, e) {
9
9
  }
10
10
  return t;
11
11
  };
12
+ import { Slider } from '@jupyter/react-components';
12
13
  import { FormComponent } from '@jupyterlab/ui-components';
13
14
  import validatorAjv8 from '@rjsf/validator-ajv8';
14
15
  import * as React from 'react';
15
16
  import { deepCopy } from '../../tools';
16
- import { Slider } from '@jupyter/react-components';
17
17
  const WrappedFormComponent = (props) => {
18
18
  const { fields } = props, rest = __rest(props, ["fields"]);
19
19
  return (React.createElement(FormComponent, Object.assign({}, rest, { validator: validatorAjv8, fields: Object.assign({}, fields) })));
@@ -96,7 +96,7 @@ export class BaseForm extends React.Component {
96
96
  }
97
97
  };
98
98
  return (React.createElement("div", { style: { display: 'flex', alignItems: 'center', gap: '8px' } },
99
- React.createElement(Slider, { min: 1, max: 10, step: 1, value: props.formData * 10, onChange: handleSliderChange }),
99
+ React.createElement(Slider, { min: 1, max: 10, step: 1, valueAsNumber: props.formData * 10, onChange: handleSliderChange }),
100
100
  React.createElement("input", { type: "number", value: inputValue, step: 0.1, min: 0.1, max: 1, onChange: handleInputChange, style: {
101
101
  width: '50px',
102
102
  textAlign: 'center',
@@ -35,17 +35,24 @@ export const FileSelectorWidget = (props) => {
35
35
  console.warn('No open dialog found.');
36
36
  }
37
37
  const output = await FileDialog.getOpenFiles({
38
- title: 'Select a File',
38
+ title: `Select ${formOptions.sourceType.split('Source')[0]} File`,
39
39
  manager: docManager
40
40
  });
41
41
  if (output.value && output.value.length > 0) {
42
42
  const selectedFilePath = output.value[0].path;
43
- const relativePath = PathExt.relative(formOptions.filePath, selectedFilePath);
43
+ const relativePath = PathExt.relative(PathExt.dirname(formOptions.filePath), selectedFilePath);
44
44
  setServerFilePath(relativePath);
45
45
  setUrlPath('');
46
46
  props.onChange(relativePath);
47
47
  if (dialogElement) {
48
- formOptions.dialogOptions.sourceData = Object.assign(Object.assign({}, formOptions.sourceData), { path: relativePath });
48
+ if (formOptions.sourceType === 'GeoTiffSource') {
49
+ formOptions.dialogOptions.sourceData = Object.assign(Object.assign({}, formOptions.sourceData), { urls: formOptions.dialogOptions.sourceData.urls.map((urlObject) => {
50
+ return Object.assign(Object.assign({}, urlObject), { url: relativePath });
51
+ }) });
52
+ }
53
+ else {
54
+ formOptions.dialogOptions.sourceData = Object.assign(Object.assign({}, formOptions.sourceData), { path: relativePath });
55
+ }
49
56
  const formDialog = new CreationFormDialog(Object.assign({}, formOptions.dialogOptions));
50
57
  await formDialog.launch();
51
58
  }
@@ -72,9 +79,9 @@ export const FileSelectorWidget = (props) => {
72
79
  isTypingURL.current = false;
73
80
  };
74
81
  return (React.createElement("div", null,
75
- React.createElement("div", null,
76
- React.createElement("input", { type: "text", className: "jp-mod-styled", value: serverFilePath || '', readOnly: true, style: { width: '70%', marginRight: '10px' } }),
77
- React.createElement("button", { className: "jp-mod-styled", onClick: handleBrowseServerFiles }, "Browse Server Files")),
82
+ React.createElement("div", { className: "file-container" },
83
+ React.createElement("button", { className: "jp-mod-styled", onClick: handleBrowseServerFiles }, "Browse Server Files"),
84
+ React.createElement("p", null, serverFilePath || '')),
78
85
  React.createElement("div", null,
79
86
  React.createElement("h3", { className: "jp-FormGroup-fieldLabel jp-FormGroup-contentItem" }, "Or enter external URL"),
80
87
  React.createElement("input", { type: "text", id: "root_path", className: "jp-mod-styled", onChange: handleURLChange, onBlur: handleURLBlur, value: urlPath || '', style: { width: '100%' } }))));
@@ -1,12 +1,16 @@
1
+ import { IDict } from '@jupytergis/schema';
1
2
  import { IChangeEvent, ISubmitEvent } from '@rjsf/core';
2
3
  import { BaseForm, IBaseFormProps } from './baseform';
3
4
  /**
4
5
  * The form to modify a GeoTiff source.
5
6
  */
6
7
  export declare class GeoTiffSourcePropertiesForm extends BaseForm {
8
+ private _isSubmitted;
7
9
  constructor(props: IBaseFormProps);
10
+ protected processSchema(data: IDict<any> | undefined, schema: IDict, uiSchema: IDict): void;
8
11
  protected onFormChange(e: IChangeEvent): void;
9
- protected onFormSubmit(e: ISubmitEvent<any>): void;
12
+ protected onFormBlur(id: string, value: any): void;
13
+ protected onFormSubmit(e: ISubmitEvent<any>): Promise<void>;
10
14
  /**
11
15
  * Validate the URLs, ensuring that there is at least one object with required fields.
12
16
  *
@@ -1,5 +1,6 @@
1
1
  import { showErrorMessage } from '@jupyterlab/apputils';
2
2
  import { BaseForm } from './baseform';
3
+ import { getMimeType } from '../../tools';
3
4
  /**
4
5
  * The form to modify a GeoTiff source.
5
6
  */
@@ -7,8 +8,17 @@ export class GeoTiffSourcePropertiesForm extends BaseForm {
7
8
  constructor(props) {
8
9
  var _a, _b;
9
10
  super(props);
11
+ this._isSubmitted = false;
10
12
  this._validateUrls((_b = (_a = props.sourceData) === null || _a === void 0 ? void 0 : _a.urls) !== null && _b !== void 0 ? _b : []);
11
13
  }
14
+ processSchema(data, schema, uiSchema) {
15
+ super.processSchema(data, schema, uiSchema);
16
+ if (!schema.properties || !data) {
17
+ return;
18
+ }
19
+ // This is not user-editable
20
+ delete schema.properties.valid;
21
+ }
12
22
  onFormChange(e) {
13
23
  var _a;
14
24
  super.onFormChange(e);
@@ -16,10 +26,21 @@ export class GeoTiffSourcePropertiesForm extends BaseForm {
16
26
  this._validateUrls(e.formData.urls);
17
27
  }
18
28
  }
19
- onFormSubmit(e) {
20
- var _a, _b, _c;
21
- if (((_c = (_b = (_a = this.state.extraErrors) === null || _a === void 0 ? void 0 : _a.urls) === null || _b === void 0 ? void 0 : _b.__errors) === null || _c === void 0 ? void 0 : _c.length) >= 1) {
22
- showErrorMessage('Invalid URLs', this.state.extraErrors.urls.__errors[0]);
29
+ onFormBlur(id, value) {
30
+ // Is there a better way to spot the url text entry?
31
+ if (!id.endsWith('_urls')) {
32
+ return;
33
+ }
34
+ this._validateUrls(value);
35
+ }
36
+ async onFormSubmit(e) {
37
+ this._isSubmitted = true;
38
+ // validate urls.url only when submitting for better performance
39
+ const { valid, errors } = await this._validateUrls(e.formData.urls);
40
+ if (!valid) {
41
+ if (errors.length > 0) {
42
+ showErrorMessage('Invalid URLs', errors[0]);
43
+ }
23
44
  return;
24
45
  }
25
46
  super.onFormSubmit(e);
@@ -36,21 +57,32 @@ export class GeoTiffSourcePropertiesForm extends BaseForm {
36
57
  if (urls && urls.length > 0) {
37
58
  for (let i = 0; i < urls.length; i++) {
38
59
  const { url, min, max } = urls[i];
39
- if (!url || typeof url !== 'string' || url.trim() === '') {
40
- errors.push(`URL at index ${i} is required and must be a valid string.`);
41
- valid = false;
42
- }
43
- if (min === undefined || typeof min !== 'number') {
44
- errors.push(`Min value at index ${i} is required and must be a number.`);
45
- valid = false;
46
- }
47
- if (max === undefined || typeof max !== 'number') {
48
- errors.push(`Max value at index ${i} is required and must be a number.`);
49
- valid = false;
60
+ if (this._isSubmitted) {
61
+ const mimeType = getMimeType(url);
62
+ if (!mimeType || !mimeType.startsWith('image/tiff')) {
63
+ valid = false;
64
+ errors.push(`"${url}" is not a valid ${this.props.sourceType} file.`);
65
+ }
50
66
  }
51
- if (typeof min === 'number' && typeof max === 'number' && max <= min) {
52
- errors.push(`Max value at index ${i} must be greater than Min.`);
53
- valid = false;
67
+ else {
68
+ if (!url || typeof url !== 'string' || url.trim() === '') {
69
+ valid = false;
70
+ errors.push(`URL at index ${i} is required and must be a valid string.`);
71
+ }
72
+ if (min === undefined || typeof min !== 'number') {
73
+ errors.push(`Min value at index ${i} is required and must be a number.`);
74
+ valid = false;
75
+ }
76
+ if (max === undefined || typeof max !== 'number') {
77
+ errors.push(`Max value at index ${i} is required and must be a number.`);
78
+ valid = false;
79
+ }
80
+ if (typeof min === 'number' &&
81
+ typeof max === 'number' &&
82
+ max <= min) {
83
+ errors.push(`Max value at index ${i} must be greater than Min.`);
84
+ valid = false;
85
+ }
54
86
  }
55
87
  }
56
88
  }
@@ -67,5 +99,6 @@ export class GeoTiffSourcePropertiesForm extends BaseForm {
67
99
  if (this.props.formErrorSignal) {
68
100
  this.props.formErrorSignal.emit(!valid);
69
101
  }
102
+ return { valid, errors };
70
103
  }
71
104
  }
@@ -0,0 +1,11 @@
1
+ import { IDict, IHeatmapLayer } from '@jupytergis/schema';
2
+ import { IChangeEvent } from '@rjsf/core';
3
+ import { ILayerProps, LayerPropertiesForm } from './layerform';
4
+ export declare class HeatmapLayerPropertiesForm extends LayerPropertiesForm {
5
+ protected currentFormData: IHeatmapLayer;
6
+ private features;
7
+ constructor(props: ILayerProps);
8
+ protected onFormChange(e: IChangeEvent): void;
9
+ protected processSchema(data: IDict<any> | undefined, schema: IDict, uiSchema: IDict): void;
10
+ private fetchFeatureNames;
11
+ }
@@ -0,0 +1,60 @@
1
+ import { loadFile } from '../../tools';
2
+ import { LayerPropertiesForm } from './layerform';
3
+ export class HeatmapLayerPropertiesForm extends LayerPropertiesForm {
4
+ constructor(props) {
5
+ super(props);
6
+ this.features = [];
7
+ this.fetchFeatureNames(this.props.sourceData);
8
+ if (this.sourceFormChangedSignal) {
9
+ this.sourceFormChangedSignal.connect((sender, sourceData) => {
10
+ if (this.props.sourceType === 'GeoJSONSource') {
11
+ this.fetchFeatureNames(this.currentFormData, sourceData);
12
+ }
13
+ });
14
+ }
15
+ }
16
+ onFormChange(e) {
17
+ super.onFormChange(e);
18
+ const source = this.props.model.getSource(e.formData.source);
19
+ if (!source || source.type !== 'GeoJSONSource') {
20
+ return;
21
+ }
22
+ this.fetchFeatureNames(this.currentFormData, source.parameters);
23
+ }
24
+ processSchema(data, schema, uiSchema) {
25
+ this.removeFormEntry('color', data, schema, uiSchema);
26
+ this.removeFormEntry('symbologyState', data, schema, uiSchema);
27
+ this.removeFormEntry('blur', data, schema, uiSchema);
28
+ this.removeFormEntry('radius', data, schema, uiSchema);
29
+ super.processSchema(data, schema, uiSchema);
30
+ uiSchema['feature'] = { enum: this.features };
31
+ if (!data) {
32
+ return;
33
+ }
34
+ }
35
+ async fetchFeatureNames(data, sourceData) {
36
+ var _a;
37
+ if (data && data.source) {
38
+ if (!sourceData) {
39
+ const currentSource = this.props.model.getSource(data.source);
40
+ if (!currentSource || currentSource.type !== 'GeoJSONSource') {
41
+ this.features = [];
42
+ return;
43
+ }
44
+ sourceData = currentSource.parameters;
45
+ }
46
+ }
47
+ const source = this.props.model.getSource(data.source);
48
+ if (!((_a = source === null || source === void 0 ? void 0 : source.parameters) === null || _a === void 0 ? void 0 : _a.path)) {
49
+ return;
50
+ }
51
+ const jsonData = await loadFile({
52
+ filepath: source.parameters.path,
53
+ type: 'GeoJSONSource',
54
+ model: this.props.model
55
+ });
56
+ const featureProps = jsonData.features[0].properties;
57
+ this.features = Object.keys(featureProps);
58
+ this.forceUpdate();
59
+ }
60
+ }
@@ -6,10 +6,8 @@ import { ILayerProps, LayerPropertiesForm } from './layerform';
6
6
  */
7
7
  export declare class VectorLayerPropertiesForm extends LayerPropertiesForm {
8
8
  protected currentFormData: IVectorLayer;
9
- private sourceLayers;
10
9
  private currentSourceId;
11
10
  constructor(props: ILayerProps);
12
11
  protected onFormChange(e: IChangeEvent): void;
13
12
  protected processSchema(data: IVectorLayer | undefined, schema: IDict, uiSchema: IDict): void;
14
- private fetchSourceLayers;
15
13
  }