@jupytergis/base 0.2.1 → 0.4.0

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 (95) hide show
  1. package/lib/annotations/components/Annotation.js +2 -2
  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 +146 -62
  6. package/lib/constants.d.ts +3 -0
  7. package/lib/constants.js +5 -1
  8. package/lib/dialogs/formdialog.d.ts +5 -0
  9. package/lib/dialogs/formdialog.js +2 -2
  10. package/lib/dialogs/layerBrowserDialog.d.ts +4 -5
  11. package/lib/dialogs/layerBrowserDialog.js +9 -9
  12. package/lib/dialogs/symbology/components/color_ramp/ModeSelectRow.js +2 -1
  13. package/lib/dialogs/symbology/hooks/useGetBandInfo.d.ts +26 -0
  14. package/lib/dialogs/symbology/hooks/useGetBandInfo.js +64 -0
  15. package/lib/dialogs/symbology/hooks/useGetProperties.d.ts +1 -1
  16. package/lib/dialogs/symbology/hooks/useGetProperties.js +12 -9
  17. package/lib/dialogs/symbology/symbologyDialog.d.ts +2 -3
  18. package/lib/dialogs/symbology/symbologyDialog.js +10 -9
  19. package/lib/dialogs/symbology/tiff_layer/TiffRendering.d.ts +1 -1
  20. package/lib/dialogs/symbology/tiff_layer/TiffRendering.js +16 -3
  21. package/lib/dialogs/symbology/tiff_layer/components/BandRow.d.ts +16 -3
  22. package/lib/dialogs/symbology/tiff_layer/components/BandRow.js +21 -7
  23. package/lib/dialogs/symbology/tiff_layer/types/MultibandColor.d.ts +4 -0
  24. package/lib/dialogs/symbology/tiff_layer/types/MultibandColor.js +85 -0
  25. package/lib/dialogs/symbology/tiff_layer/types/SingleBandPseudoColor.d.ts +1 -20
  26. package/lib/dialogs/symbology/tiff_layer/types/SingleBandPseudoColor.js +25 -65
  27. package/lib/dialogs/symbology/vector_layer/VectorRendering.d.ts +1 -1
  28. package/lib/dialogs/symbology/vector_layer/VectorRendering.js +18 -13
  29. package/lib/dialogs/symbology/vector_layer/types/Categorized.d.ts +1 -1
  30. package/lib/dialogs/symbology/vector_layer/types/Categorized.js +30 -19
  31. package/lib/dialogs/symbology/vector_layer/types/Graduated.d.ts +1 -1
  32. package/lib/dialogs/symbology/vector_layer/types/Graduated.js +16 -13
  33. package/lib/dialogs/symbology/vector_layer/types/Heatmap.d.ts +4 -0
  34. package/lib/dialogs/symbology/vector_layer/types/Heatmap.js +77 -0
  35. package/lib/dialogs/symbology/vector_layer/types/SimpleSymbol.d.ts +1 -1
  36. package/lib/dialogs/symbology/vector_layer/types/SimpleSymbol.js +4 -3
  37. package/lib/formbuilder/creationform.d.ts +6 -2
  38. package/lib/formbuilder/creationform.js +6 -6
  39. package/lib/formbuilder/editform.d.ts +2 -2
  40. package/lib/formbuilder/editform.js +14 -9
  41. package/lib/formbuilder/formselectors.js +11 -1
  42. package/lib/formbuilder/objectform/baseform.d.ts +12 -3
  43. package/lib/formbuilder/objectform/baseform.js +39 -0
  44. package/lib/formbuilder/objectform/fileselectorwidget.d.ts +2 -0
  45. package/lib/formbuilder/objectform/fileselectorwidget.js +88 -0
  46. package/lib/formbuilder/objectform/geojsonsource.d.ts +5 -7
  47. package/lib/formbuilder/objectform/geojsonsource.js +8 -24
  48. package/lib/formbuilder/objectform/geotiffsource.d.ts +5 -1
  49. package/lib/formbuilder/objectform/geotiffsource.js +64 -18
  50. package/lib/formbuilder/objectform/heatmapLayerForm.d.ts +11 -0
  51. package/lib/formbuilder/objectform/heatmapLayerForm.js +60 -0
  52. package/lib/formbuilder/objectform/layerform.d.ts +2 -0
  53. package/lib/formbuilder/objectform/layerform.js +6 -0
  54. package/lib/formbuilder/objectform/pathbasedsource.d.ts +19 -0
  55. package/lib/formbuilder/objectform/pathbasedsource.js +98 -0
  56. package/lib/formbuilder/objectform/vectorlayerform.d.ts +0 -2
  57. package/lib/formbuilder/objectform/vectorlayerform.js +0 -59
  58. package/lib/icons.d.ts +1 -0
  59. package/lib/icons.js +5 -0
  60. package/lib/keybindings.json +62 -0
  61. package/lib/mainview/TemporalSlider.d.ts +8 -0
  62. package/lib/mainview/TemporalSlider.js +303 -0
  63. package/lib/mainview/mainView.d.ts +46 -8
  64. package/lib/mainview/mainView.js +431 -144
  65. package/lib/mainview/mainviewmodel.d.ts +4 -0
  66. package/lib/mainview/mainviewmodel.js +4 -0
  67. package/lib/mainview/mainviewwidget.d.ts +0 -2
  68. package/lib/mainview/mainviewwidget.js +0 -2
  69. package/lib/panelview/annotationPanel.js +5 -5
  70. package/lib/panelview/components/filter-panel/Filter.js +8 -24
  71. package/lib/panelview/components/identify-panel/IdentifyPanel.js +1 -1
  72. package/lib/panelview/components/layers.js +2 -2
  73. package/lib/panelview/components/sources.js +1 -1
  74. package/lib/panelview/leftpanel.d.ts +3 -0
  75. package/lib/panelview/leftpanel.js +5 -1
  76. package/lib/panelview/model.js +8 -8
  77. package/lib/panelview/objectproperties.js +10 -10
  78. package/lib/panelview/rightpanel.d.ts +1 -1
  79. package/lib/panelview/rightpanel.js +10 -10
  80. package/lib/statusbar/StatusBar.d.ts +13 -0
  81. package/lib/statusbar/StatusBar.js +52 -0
  82. package/lib/toolbar/widget.d.ts +1 -1
  83. package/lib/toolbar/widget.js +44 -37
  84. package/lib/tools.d.ts +50 -7
  85. package/lib/tools.js +394 -12
  86. package/lib/types.d.ts +2 -0
  87. package/lib/widget.d.ts +29 -5
  88. package/lib/widget.js +41 -7
  89. package/package.json +17 -5
  90. package/style/base.css +11 -0
  91. package/style/icons/logo_mini_qgz.svg +31 -0
  92. package/style/leftPanel.css +8 -0
  93. package/style/statusBar.css +16 -0
  94. package/style/symbologyDialog.css +7 -1
  95. package/style/temporalSlider.css +47 -0
@@ -1,22 +1,27 @@
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
5
  /**
5
6
  * Form for editing a source, a layer or both at the same time
6
7
  */
7
8
  export class EditForm extends React.Component {
9
+ constructor() {
10
+ super(...arguments);
11
+ this.sourceFormChangedSignal = new Signal(this);
12
+ }
8
13
  async syncObjectProperties(id, properties) {
9
14
  if (!id) {
10
15
  return;
11
16
  }
12
- this.props.context.model.sharedModel.updateObjectParameters(id, properties);
17
+ this.props.model.sharedModel.updateObjectParameters(id, properties);
13
18
  }
14
19
  render() {
15
20
  let layerSchema = undefined;
16
21
  let LayerForm = undefined;
17
22
  let layerData = undefined;
18
23
  if (this.props.layer) {
19
- const layer = this.props.context.model.getLayer(this.props.layer);
24
+ const layer = this.props.model.getLayer(this.props.layer);
20
25
  if (!layer) {
21
26
  return;
22
27
  }
@@ -33,7 +38,7 @@ export class EditForm extends React.Component {
33
38
  let sourceData = undefined;
34
39
  let source = undefined;
35
40
  if (this.props.source) {
36
- source = this.props.context.model.getSource(this.props.source);
41
+ source = this.props.model.getSource(this.props.source);
37
42
  if (!source) {
38
43
  return;
39
44
  }
@@ -47,14 +52,14 @@ export class EditForm extends React.Component {
47
52
  }
48
53
  return (React.createElement("div", null,
49
54
  this.props.layer && LayerForm && (React.createElement("div", null,
50
- React.createElement("h3", null, "Layer Properties"),
51
- 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) => {
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.model, filePath: this.props.model.filePath, schema: layerSchema, sourceData: layerData, syncData: (properties) => {
52
57
  this.syncObjectProperties(this.props.layer, properties);
53
58
  } }))),
54
59
  this.props.source && SourceForm && (React.createElement("div", null,
55
- React.createElement("h3", null, "Source Properties"),
56
- React.createElement(SourceForm, { formContext: "create", model: this.props.context.model, filePath: `${this.props.context.path}::panel`, schema: sourceSchema, sourceData: sourceData, syncData: (properties) => {
60
+ React.createElement("h3", { style: { paddingLeft: '5px' } }, "Source Properties"),
61
+ React.createElement(SourceForm, { formContext: "create", model: this.props.model, filePath: this.props.model.filePath, schema: sourceSchema, sourceData: sourceData, syncData: (properties) => {
57
62
  this.syncObjectProperties(this.props.source, properties);
58
- } })))));
63
+ }, formChangedSignal: this.sourceFormChangedSignal, sourceType: (source === null || source === void 0 ? void 0 : source.type) || 'RasterSource' })))));
59
64
  }
60
65
  }
@@ -1,11 +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
11
  export function getLayerTypeForm(layerType) {
10
12
  let LayerForm = LayerPropertiesForm;
11
13
  switch (layerType) {
@@ -19,6 +21,8 @@ export function getLayerTypeForm(layerType) {
19
21
  case 'WebGlLayer':
20
22
  LayerForm = WebGlLayerPropertiesForm;
21
23
  break;
24
+ case 'HeatmapLayer':
25
+ LayerForm = HeatmapLayerPropertiesForm;
22
26
  // ADD MORE FORM TYPES HERE
23
27
  }
24
28
  return LayerForm;
@@ -29,6 +33,12 @@ export function getSourceTypeForm(sourceType) {
29
33
  case 'GeoJSONSource':
30
34
  SourceForm = GeoJSONSourcePropertiesForm;
31
35
  break;
36
+ case 'ImageSource':
37
+ SourceForm = PathBasedSourcePropertiesForm;
38
+ break;
39
+ case 'ShapefileSource':
40
+ SourceForm = PathBasedSourcePropertiesForm;
41
+ break;
32
42
  case 'GeoTiffSource':
33
43
  SourceForm = GeoTiffSourcePropertiesForm;
34
44
  break;
@@ -1,8 +1,8 @@
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
7
  export interface IBaseFormStates {
8
8
  schema?: IDict;
@@ -51,6 +51,15 @@ export interface IBaseFormProps {
51
51
  * extra errors or not.
52
52
  */
53
53
  formErrorSignal?: Signal<Dialog<any>, boolean>;
54
+ /**
55
+ * Configuration options for the dialog, including settings for layer data, source data,
56
+ * and other form-related parameters.
57
+ */
58
+ dialogOptions?: any;
59
+ /**
60
+ * Source type property
61
+ */
62
+ sourceType: SourceType;
54
63
  }
55
64
  /**
56
65
  * Generate a form to edit a layer/source type. This class is meant to be sub-classed to create more refined forms for specific layers/sources.
@@ -9,6 +9,7 @@ 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';
@@ -69,6 +70,44 @@ export class BaseForm extends React.Component {
69
70
  if (v['type'] === 'object') {
70
71
  this.processSchema(data, v, uiSchema[k]);
71
72
  }
73
+ if (k === 'opacity') {
74
+ 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
+ }
109
+ };
110
+ }
72
111
  // Don't show readOnly properties when it's a form for updating an object
73
112
  if (v['readOnly']) {
74
113
  if (this.props.formContext === 'create') {
@@ -0,0 +1,2 @@
1
+ import React from 'react';
2
+ export declare const FileSelectorWidget: (props: any) => React.JSX.Element;
@@ -0,0 +1,88 @@
1
+ import React, { useState, useEffect, useRef } from 'react';
2
+ import { FileDialog } from '@jupyterlab/filebrowser';
3
+ import { Dialog } from '@jupyterlab/apputils';
4
+ import { CreationFormDialog } from '../../dialogs/formdialog';
5
+ import { PathExt } from '@jupyterlab/coreutils';
6
+ export const FileSelectorWidget = (props) => {
7
+ const { options } = props;
8
+ const { docManager, formOptions } = options;
9
+ const [serverFilePath, setServerFilePath] = useState('');
10
+ const [urlPath, setUrlPath] = useState('');
11
+ const isTypingURL = useRef(false); // Tracks whether the user is manually typing a URL
12
+ useEffect(() => {
13
+ if (!isTypingURL.current && props.value) {
14
+ if (props.value.startsWith('http://') ||
15
+ props.value.startsWith('https://')) {
16
+ setUrlPath(props.value);
17
+ setServerFilePath('');
18
+ }
19
+ else {
20
+ setServerFilePath(props.value);
21
+ setUrlPath('');
22
+ }
23
+ }
24
+ }, [props.value]);
25
+ const handleBrowseServerFiles = async () => {
26
+ try {
27
+ const dialogElement = document.querySelector('dialog[aria-modal="true"]');
28
+ if (dialogElement) {
29
+ const dialogInstance = Dialog.tracker.find(dialog => dialog.node === dialogElement);
30
+ if (dialogInstance) {
31
+ dialogInstance.resolve(0);
32
+ }
33
+ }
34
+ else {
35
+ console.warn('No open dialog found.');
36
+ }
37
+ const output = await FileDialog.getOpenFiles({
38
+ title: `Select ${formOptions.sourceType.split('Source')[0]} File`,
39
+ manager: docManager
40
+ });
41
+ if (output.value && output.value.length > 0) {
42
+ const selectedFilePath = output.value[0].path;
43
+ const relativePath = PathExt.relative(PathExt.dirname(formOptions.filePath), selectedFilePath);
44
+ setServerFilePath(relativePath);
45
+ setUrlPath('');
46
+ props.onChange(relativePath);
47
+ if (dialogElement) {
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
+ }
56
+ const formDialog = new CreationFormDialog(Object.assign({}, formOptions.dialogOptions));
57
+ await formDialog.launch();
58
+ }
59
+ }
60
+ else {
61
+ if (dialogElement) {
62
+ const formDialog = new CreationFormDialog(Object.assign({}, formOptions.dialogOptions));
63
+ await formDialog.launch();
64
+ }
65
+ }
66
+ }
67
+ catch (e) {
68
+ console.error('Error handling file dialog:', e);
69
+ }
70
+ };
71
+ const handleURLChange = (event) => {
72
+ const url = event.target.value.trim();
73
+ isTypingURL.current = true;
74
+ setUrlPath(url);
75
+ setServerFilePath('');
76
+ props.onChange(url);
77
+ };
78
+ const handleURLBlur = () => {
79
+ isTypingURL.current = false;
80
+ };
81
+ return (React.createElement("div", null,
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 || '')),
85
+ React.createElement("div", null,
86
+ React.createElement("h3", { className: "jp-FormGroup-fieldLabel jp-FormGroup-contentItem" }, "Or enter external URL"),
87
+ React.createElement("input", { type: "text", id: "root_path", className: "jp-mod-styled", onChange: handleURLChange, onBlur: handleURLBlur, value: urlPath || '', style: { width: '100%' } }))));
88
+ };
@@ -1,19 +1,17 @@
1
1
  import { IDict } from '@jupytergis/schema';
2
- import { ISubmitEvent } from '@rjsf/core';
3
- import { BaseForm, IBaseFormProps } from './baseform';
2
+ import { IBaseFormProps } from './baseform';
3
+ import { PathBasedSourcePropertiesForm } from './pathbasedsource';
4
4
  /**
5
5
  * The form to modify a GeoJSON source.
6
6
  */
7
- export declare class GeoJSONSourcePropertiesForm extends BaseForm {
7
+ export declare class GeoJSONSourcePropertiesForm extends PathBasedSourcePropertiesForm {
8
+ private _validate;
8
9
  constructor(props: IBaseFormProps);
9
10
  protected processSchema(data: IDict<any> | undefined, schema: IDict, uiSchema: IDict): void;
10
- protected onFormBlur(id: string, value: any): void;
11
- protected onFormSubmit(e: ISubmitEvent<any>): void;
12
11
  /**
13
12
  * Validate the path, to avoid invalid path or invalid GeoJSON.
14
13
  *
15
14
  * @param path - the path to validate.
16
15
  */
17
- private _validatePath;
18
- private _validate;
16
+ protected _validatePath(path: string): Promise<void>;
19
17
  }
@@ -1,11 +1,11 @@
1
- import { showErrorMessage } from '@jupyterlab/apputils';
2
1
  import { Ajv } from 'ajv';
3
2
  import * as geojson from '@jupytergis/schema/src/schema/geojson.json';
4
- import { BaseForm } from './baseform';
3
+ import { PathBasedSourcePropertiesForm } from './pathbasedsource';
4
+ import { loadFile } from '../../tools';
5
5
  /**
6
6
  * The form to modify a GeoJSON source.
7
7
  */
8
- export class GeoJSONSourcePropertiesForm extends BaseForm {
8
+ export class GeoJSONSourcePropertiesForm extends PathBasedSourcePropertiesForm {
9
9
  constructor(props) {
10
10
  var _a, _b;
11
11
  super(props);
@@ -18,26 +18,6 @@ export class GeoJSONSourcePropertiesForm extends BaseForm {
18
18
  this.removeFormEntry('data', data, schema, uiSchema);
19
19
  }
20
20
  super.processSchema(data, schema, uiSchema);
21
- if (!schema.properties || !data) {
22
- return;
23
- }
24
- // This is not user-editable
25
- delete schema.properties.valid;
26
- }
27
- onFormBlur(id, value) {
28
- // Is there a better way to spot the path text entry?
29
- if (!id.endsWith('_path')) {
30
- return;
31
- }
32
- this._validatePath(value);
33
- }
34
- onFormSubmit(e) {
35
- var _a, _b, _c;
36
- if (((_c = (_b = (_a = this.state.extraErrors) === null || _a === void 0 ? void 0 : _a.path) === null || _b === void 0 ? void 0 : _b.__errors) === null || _c === void 0 ? void 0 : _c.length) >= 1) {
37
- showErrorMessage('Invalid JSON file', this.state.extraErrors.path.__errors[0]);
38
- return;
39
- }
40
- super.onFormSubmit(e);
41
21
  }
42
22
  /**
43
23
  * Validate the path, to avoid invalid path or invalid GeoJSON.
@@ -51,7 +31,11 @@ export class GeoJSONSourcePropertiesForm extends BaseForm {
51
31
  let valid = false;
52
32
  if (path) {
53
33
  try {
54
- const geoJSONData = await this.props.model.readGeoJSON(path);
34
+ const geoJSONData = await loadFile({
35
+ filepath: path,
36
+ type: this.props.sourceType,
37
+ model: this.props.model
38
+ });
55
39
  valid = this._validate(geoJSONData);
56
40
  if (!valid) {
57
41
  error = `"${path}" is not a valid GeoJSON file`;
@@ -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,7 @@
1
1
  import { showErrorMessage } from '@jupyterlab/apputils';
2
2
  import { BaseForm } from './baseform';
3
+ import { FileSelectorWidget } from './fileselectorwidget';
4
+ import { getMimeType } from '../../tools';
3
5
  /**
4
6
  * The form to modify a GeoTiff source.
5
7
  */
@@ -7,8 +9,29 @@ export class GeoTiffSourcePropertiesForm extends BaseForm {
7
9
  constructor(props) {
8
10
  var _a, _b;
9
11
  super(props);
12
+ this._isSubmitted = false;
10
13
  this._validateUrls((_b = (_a = props.sourceData) === null || _a === void 0 ? void 0 : _a.urls) !== null && _b !== void 0 ? _b : []);
11
14
  }
15
+ processSchema(data, schema, uiSchema) {
16
+ var _a;
17
+ super.processSchema(data, schema, uiSchema);
18
+ if (!schema.properties || !data) {
19
+ return;
20
+ }
21
+ // Customize the widget for urls
22
+ if (schema.properties && schema.properties.urls) {
23
+ const docManager = (_a = this.props.formChangedSignal) === null || _a === void 0 ? void 0 : _a.sender.props.formSchemaRegistry.getDocManager();
24
+ uiSchema.urls = Object.assign(Object.assign({}, uiSchema.urls), { items: Object.assign(Object.assign({}, uiSchema.urls.items), { url: {
25
+ 'ui:widget': FileSelectorWidget,
26
+ 'ui:options': {
27
+ docManager,
28
+ formOptions: this.props
29
+ }
30
+ } }) });
31
+ }
32
+ // This is not user-editable
33
+ delete schema.properties.valid;
34
+ }
12
35
  onFormChange(e) {
13
36
  var _a;
14
37
  super.onFormChange(e);
@@ -16,10 +39,21 @@ export class GeoTiffSourcePropertiesForm extends BaseForm {
16
39
  this._validateUrls(e.formData.urls);
17
40
  }
18
41
  }
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]);
42
+ onFormBlur(id, value) {
43
+ // Is there a better way to spot the url text entry?
44
+ if (!id.endsWith('_urls')) {
45
+ return;
46
+ }
47
+ this._validateUrls(value);
48
+ }
49
+ async onFormSubmit(e) {
50
+ this._isSubmitted = true;
51
+ // validate urls.url only when submitting for better performance
52
+ const { valid, errors } = await this._validateUrls(e.formData.urls);
53
+ if (!valid) {
54
+ if (errors.length > 0) {
55
+ showErrorMessage('Invalid URLs', errors[0]);
56
+ }
23
57
  return;
24
58
  }
25
59
  super.onFormSubmit(e);
@@ -36,21 +70,32 @@ export class GeoTiffSourcePropertiesForm extends BaseForm {
36
70
  if (urls && urls.length > 0) {
37
71
  for (let i = 0; i < urls.length; i++) {
38
72
  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;
73
+ if (this._isSubmitted) {
74
+ const mimeType = getMimeType(url);
75
+ if (!mimeType || !mimeType.startsWith('image/tiff')) {
76
+ valid = false;
77
+ errors.push(`"${url}" is not a valid ${this.props.sourceType} file.`);
78
+ }
50
79
  }
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;
80
+ else {
81
+ if (!url || typeof url !== 'string' || url.trim() === '') {
82
+ valid = false;
83
+ errors.push(`URL at index ${i} is required and must be a valid string.`);
84
+ }
85
+ if (min === undefined || typeof min !== 'number') {
86
+ errors.push(`Min value at index ${i} is required and must be a number.`);
87
+ valid = false;
88
+ }
89
+ if (max === undefined || typeof max !== 'number') {
90
+ errors.push(`Max value at index ${i} is required and must be a number.`);
91
+ valid = false;
92
+ }
93
+ if (typeof min === 'number' &&
94
+ typeof max === 'number' &&
95
+ max <= min) {
96
+ errors.push(`Max value at index ${i} must be greater than Min.`);
97
+ valid = false;
98
+ }
54
99
  }
55
100
  }
56
101
  }
@@ -67,5 +112,6 @@ export class GeoTiffSourcePropertiesForm extends BaseForm {
67
112
  if (this.props.formErrorSignal) {
68
113
  this.props.formErrorSignal.emit(!valid);
69
114
  }
115
+ return { valid, errors };
70
116
  }
71
117
  }
@@ -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
+ }
@@ -1,6 +1,7 @@
1
1
  import { IDict, SourceType } from '@jupytergis/schema';
2
2
  import { BaseForm, IBaseFormProps } from './baseform';
3
3
  import { Signal } from '@lumino/signaling';
4
+ import { IChangeEvent } from '@rjsf/core';
4
5
  export interface ILayerProps extends IBaseFormProps {
5
6
  /**
6
7
  * The source type for the layer
@@ -16,4 +17,5 @@ export declare class LayerPropertiesForm extends BaseForm {
16
17
  protected sourceFormChangedSignal: Signal<any, IDict<any>> | undefined;
17
18
  constructor(props: ILayerProps);
18
19
  protected processSchema(data: IDict<any> | undefined, schema: IDict, uiSchema: IDict): void;
20
+ protected onFormChange(e: IChangeEvent): void;
19
21
  }
@@ -14,4 +14,10 @@ export class LayerPropertiesForm extends BaseForm {
14
14
  schema.properties.source.enumNames = Object.values(availableSources);
15
15
  schema.properties.source.enum = Object.keys(availableSources);
16
16
  }
17
+ onFormChange(e) {
18
+ super.onFormChange(e);
19
+ if (this.props.dialogOptions) {
20
+ this.props.dialogOptions.layerData = Object.assign({}, e.formData);
21
+ }
22
+ }
17
23
  }
@@ -0,0 +1,19 @@
1
+ import { IDict } from '@jupytergis/schema';
2
+ import { IChangeEvent, ISubmitEvent } from '@rjsf/core';
3
+ import { BaseForm, IBaseFormProps } from './baseform';
4
+ /**
5
+ * The form to modify a PathBasedSource source.
6
+ */
7
+ export declare class PathBasedSourcePropertiesForm extends BaseForm {
8
+ constructor(props: IBaseFormProps);
9
+ protected processSchema(data: IDict<any> | undefined, schema: IDict, uiSchema: IDict): void;
10
+ protected onFormBlur(id: string, value: any): void;
11
+ protected onFormChange(e: IChangeEvent): void;
12
+ protected onFormSubmit(e: ISubmitEvent<any>): void;
13
+ /**
14
+ * Validate the path, to avoid invalid path.
15
+ *
16
+ * @param path - the path to validate.
17
+ */
18
+ protected _validatePath(path: string): Promise<void>;
19
+ }