@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.
- package/lib/annotations/components/Annotation.js +2 -2
- package/lib/annotations/model.d.ts +6 -7
- package/lib/annotations/model.js +15 -15
- package/lib/commands.d.ts +2 -3
- package/lib/commands.js +146 -62
- package/lib/constants.d.ts +3 -0
- package/lib/constants.js +5 -1
- package/lib/dialogs/formdialog.d.ts +5 -0
- package/lib/dialogs/formdialog.js +2 -2
- package/lib/dialogs/layerBrowserDialog.d.ts +4 -5
- package/lib/dialogs/layerBrowserDialog.js +9 -9
- package/lib/dialogs/symbology/components/color_ramp/ModeSelectRow.js +2 -1
- package/lib/dialogs/symbology/hooks/useGetBandInfo.d.ts +26 -0
- package/lib/dialogs/symbology/hooks/useGetBandInfo.js +64 -0
- package/lib/dialogs/symbology/hooks/useGetProperties.d.ts +1 -1
- package/lib/dialogs/symbology/hooks/useGetProperties.js +12 -9
- package/lib/dialogs/symbology/symbologyDialog.d.ts +2 -3
- package/lib/dialogs/symbology/symbologyDialog.js +10 -9
- package/lib/dialogs/symbology/tiff_layer/TiffRendering.d.ts +1 -1
- package/lib/dialogs/symbology/tiff_layer/TiffRendering.js +16 -3
- package/lib/dialogs/symbology/tiff_layer/components/BandRow.d.ts +16 -3
- package/lib/dialogs/symbology/tiff_layer/components/BandRow.js +21 -7
- package/lib/dialogs/symbology/tiff_layer/types/MultibandColor.d.ts +4 -0
- package/lib/dialogs/symbology/tiff_layer/types/MultibandColor.js +85 -0
- package/lib/dialogs/symbology/tiff_layer/types/SingleBandPseudoColor.d.ts +1 -20
- package/lib/dialogs/symbology/tiff_layer/types/SingleBandPseudoColor.js +25 -65
- package/lib/dialogs/symbology/vector_layer/VectorRendering.d.ts +1 -1
- package/lib/dialogs/symbology/vector_layer/VectorRendering.js +18 -13
- package/lib/dialogs/symbology/vector_layer/types/Categorized.d.ts +1 -1
- package/lib/dialogs/symbology/vector_layer/types/Categorized.js +30 -19
- package/lib/dialogs/symbology/vector_layer/types/Graduated.d.ts +1 -1
- package/lib/dialogs/symbology/vector_layer/types/Graduated.js +16 -13
- package/lib/dialogs/symbology/vector_layer/types/Heatmap.d.ts +4 -0
- package/lib/dialogs/symbology/vector_layer/types/Heatmap.js +77 -0
- package/lib/dialogs/symbology/vector_layer/types/SimpleSymbol.d.ts +1 -1
- package/lib/dialogs/symbology/vector_layer/types/SimpleSymbol.js +4 -3
- package/lib/formbuilder/creationform.d.ts +6 -2
- package/lib/formbuilder/creationform.js +6 -6
- package/lib/formbuilder/editform.d.ts +2 -2
- package/lib/formbuilder/editform.js +14 -9
- package/lib/formbuilder/formselectors.js +11 -1
- package/lib/formbuilder/objectform/baseform.d.ts +12 -3
- package/lib/formbuilder/objectform/baseform.js +39 -0
- package/lib/formbuilder/objectform/fileselectorwidget.d.ts +2 -0
- package/lib/formbuilder/objectform/fileselectorwidget.js +88 -0
- package/lib/formbuilder/objectform/geojsonsource.d.ts +5 -7
- package/lib/formbuilder/objectform/geojsonsource.js +8 -24
- package/lib/formbuilder/objectform/geotiffsource.d.ts +5 -1
- package/lib/formbuilder/objectform/geotiffsource.js +64 -18
- package/lib/formbuilder/objectform/heatmapLayerForm.d.ts +11 -0
- package/lib/formbuilder/objectform/heatmapLayerForm.js +60 -0
- package/lib/formbuilder/objectform/layerform.d.ts +2 -0
- package/lib/formbuilder/objectform/layerform.js +6 -0
- package/lib/formbuilder/objectform/pathbasedsource.d.ts +19 -0
- package/lib/formbuilder/objectform/pathbasedsource.js +98 -0
- package/lib/formbuilder/objectform/vectorlayerform.d.ts +0 -2
- package/lib/formbuilder/objectform/vectorlayerform.js +0 -59
- package/lib/icons.d.ts +1 -0
- package/lib/icons.js +5 -0
- package/lib/keybindings.json +62 -0
- package/lib/mainview/TemporalSlider.d.ts +8 -0
- package/lib/mainview/TemporalSlider.js +303 -0
- package/lib/mainview/mainView.d.ts +46 -8
- package/lib/mainview/mainView.js +431 -144
- package/lib/mainview/mainviewmodel.d.ts +4 -0
- package/lib/mainview/mainviewmodel.js +4 -0
- package/lib/mainview/mainviewwidget.d.ts +0 -2
- package/lib/mainview/mainviewwidget.js +0 -2
- package/lib/panelview/annotationPanel.js +5 -5
- package/lib/panelview/components/filter-panel/Filter.js +8 -24
- package/lib/panelview/components/identify-panel/IdentifyPanel.js +1 -1
- package/lib/panelview/components/layers.js +2 -2
- package/lib/panelview/components/sources.js +1 -1
- package/lib/panelview/leftpanel.d.ts +3 -0
- package/lib/panelview/leftpanel.js +5 -1
- package/lib/panelview/model.js +8 -8
- package/lib/panelview/objectproperties.js +10 -10
- package/lib/panelview/rightpanel.d.ts +1 -1
- package/lib/panelview/rightpanel.js +10 -10
- package/lib/statusbar/StatusBar.d.ts +13 -0
- package/lib/statusbar/StatusBar.js +52 -0
- package/lib/toolbar/widget.d.ts +1 -1
- package/lib/toolbar/widget.js +44 -37
- package/lib/tools.d.ts +50 -7
- package/lib/tools.js +394 -12
- package/lib/types.d.ts +2 -0
- package/lib/widget.d.ts +29 -5
- package/lib/widget.js +41 -7
- package/package.json +17 -5
- package/style/base.css +11 -0
- package/style/icons/logo_mini_qgz.svg +31 -0
- package/style/leftPanel.css +8 -0
- package/style/statusBar.css +16 -0
- package/style/symbologyDialog.css +7 -1
- package/style/temporalSlider.css +47 -0
|
@@ -1,22 +1,27 @@
|
|
|
1
|
-
import {
|
|
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.
|
|
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.
|
|
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.
|
|
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",
|
|
51
|
-
React.createElement(LayerForm, { formContext: "create", sourceType: (source === null || source === void 0 ? void 0 : source.type) || 'RasterSource', model: this.props.
|
|
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",
|
|
56
|
-
React.createElement(SourceForm, { formContext: "create", model: this.props.
|
|
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 {
|
|
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,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 {
|
|
3
|
-
import {
|
|
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
|
|
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
|
-
|
|
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 {
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
if ((
|
|
22
|
-
|
|
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 (
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
+
}
|