@jupytergis/base 0.2.0 → 0.3.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 +1 -1
- package/lib/commands.js +30 -1
- package/lib/constants.d.ts +1 -0
- package/lib/constants.js +1 -0
- package/lib/dialogs/formdialog.d.ts +5 -0
- package/lib/dialogs/formdialog.js +2 -2
- package/lib/dialogs/symbology/components/color_ramp/ModeSelectRow.js +2 -1
- package/lib/dialogs/symbology/hooks/useGetBandInfo.d.ts +27 -0
- package/lib/dialogs/symbology/hooks/useGetBandInfo.js +59 -0
- package/lib/dialogs/symbology/hooks/useGetProperties.js +6 -1
- package/lib/dialogs/symbology/tiff_layer/TiffRendering.d.ts +1 -1
- package/lib/dialogs/symbology/tiff_layer/TiffRendering.js +14 -1
- 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 +84 -0
- package/lib/dialogs/symbology/tiff_layer/types/SingleBandPseudoColor.d.ts +0 -19
- package/lib/dialogs/symbology/tiff_layer/types/SingleBandPseudoColor.js +18 -59
- package/lib/formbuilder/creationform.d.ts +5 -0
- package/lib/formbuilder/creationform.js +2 -2
- package/lib/formbuilder/editform.d.ts +1 -0
- package/lib/formbuilder/editform.js +8 -3
- package/lib/formbuilder/formselectors.js +7 -0
- package/lib/formbuilder/objectform/baseform.d.ts +10 -0
- package/lib/formbuilder/objectform/baseform.js +39 -0
- package/lib/formbuilder/objectform/fileselectorwidget.d.ts +2 -0
- package/lib/formbuilder/objectform/fileselectorwidget.js +81 -0
- package/lib/formbuilder/objectform/geojsonsource.d.ts +5 -7
- package/lib/formbuilder/objectform/geojsonsource.js +8 -24
- 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/icons.d.ts +1 -0
- package/lib/icons.js +5 -0
- package/lib/keybindings.json +62 -0
- package/lib/mainview/mainView.d.ts +22 -5
- package/lib/mainview/mainView.js +228 -76
- package/lib/panelview/components/filter-panel/Filter.js +6 -1
- package/lib/statusbar/StatusBar.d.ts +13 -0
- package/lib/statusbar/StatusBar.js +52 -0
- package/lib/toolbar/widget.js +0 -5
- package/lib/tools.d.ts +40 -1
- package/lib/tools.js +308 -0
- package/package.json +16 -5
- package/style/base.css +1 -0
- package/style/icons/logo_mini_qgz.svg +31 -0
- package/style/leftPanel.css +8 -0
- package/style/statusBar.css +16 -0
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import { deepCopy } from '../tools';
|
|
2
2
|
import * as React from 'react';
|
|
3
3
|
import { getLayerTypeForm, getSourceTypeForm } from './formselectors';
|
|
4
|
+
import { Signal } from '@lumino/signaling';
|
|
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;
|
|
@@ -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",
|
|
55
|
+
React.createElement("h3", { style: { paddingLeft: '5px' } }, "Layer Properties"),
|
|
51
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) => {
|
|
52
57
|
this.syncObjectProperties(this.props.layer, properties);
|
|
53
58
|
} }))),
|
|
54
59
|
this.props.source && SourceForm && (React.createElement("div", null,
|
|
55
|
-
React.createElement("h3",
|
|
60
|
+
React.createElement("h3", { style: { paddingLeft: '5px' } }, "Source Properties"),
|
|
56
61
|
React.createElement(SourceForm, { formContext: "create", model: this.props.context.model, filePath: `${this.props.context.path}::panel`, 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
|
}
|
|
@@ -6,6 +6,7 @@ import { TileSourcePropertiesForm } from './objectform/tilesourceform';
|
|
|
6
6
|
import { VectorLayerPropertiesForm } from './objectform/vectorlayerform';
|
|
7
7
|
import { WebGlLayerPropertiesForm } from './objectform/webGlLayerForm';
|
|
8
8
|
import { GeoTiffSourcePropertiesForm } from './objectform/geotiffsource';
|
|
9
|
+
import { PathBasedSourcePropertiesForm } from './objectform/pathbasedsource';
|
|
9
10
|
export function getLayerTypeForm(layerType) {
|
|
10
11
|
let LayerForm = LayerPropertiesForm;
|
|
11
12
|
switch (layerType) {
|
|
@@ -29,6 +30,12 @@ export function getSourceTypeForm(sourceType) {
|
|
|
29
30
|
case 'GeoJSONSource':
|
|
30
31
|
SourceForm = GeoJSONSourcePropertiesForm;
|
|
31
32
|
break;
|
|
33
|
+
case 'ImageSource':
|
|
34
|
+
SourceForm = PathBasedSourcePropertiesForm;
|
|
35
|
+
break;
|
|
36
|
+
case 'ShapefileSource':
|
|
37
|
+
SourceForm = PathBasedSourcePropertiesForm;
|
|
38
|
+
break;
|
|
32
39
|
case 'GeoTiffSource':
|
|
33
40
|
SourceForm = GeoTiffSourcePropertiesForm;
|
|
34
41
|
break;
|
|
@@ -4,6 +4,7 @@ import { IJupyterGISModel } from '@jupytergis/schema';
|
|
|
4
4
|
import { Dialog } from '@jupyterlab/apputils';
|
|
5
5
|
import { Signal } from '@lumino/signaling';
|
|
6
6
|
import { IDict } from '../../types';
|
|
7
|
+
import { SourceType } from '@jupytergis/schema';
|
|
7
8
|
export interface IBaseFormStates {
|
|
8
9
|
schema?: IDict;
|
|
9
10
|
extraErrors?: any;
|
|
@@ -51,6 +52,15 @@ export interface IBaseFormProps {
|
|
|
51
52
|
* extra errors or not.
|
|
52
53
|
*/
|
|
53
54
|
formErrorSignal?: Signal<Dialog<any>, boolean>;
|
|
55
|
+
/**
|
|
56
|
+
* Configuration options for the dialog, including settings for layer data, source data,
|
|
57
|
+
* and other form-related parameters.
|
|
58
|
+
*/
|
|
59
|
+
dialogOptions?: any;
|
|
60
|
+
/**
|
|
61
|
+
* Source type property
|
|
62
|
+
*/
|
|
63
|
+
sourceType: SourceType;
|
|
54
64
|
}
|
|
55
65
|
/**
|
|
56
66
|
* 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.
|
|
@@ -13,6 +13,7 @@ import { FormComponent } from '@jupyterlab/ui-components';
|
|
|
13
13
|
import validatorAjv8 from '@rjsf/validator-ajv8';
|
|
14
14
|
import * as React from 'react';
|
|
15
15
|
import { deepCopy } from '../../tools';
|
|
16
|
+
import { Slider } from '@jupyter/react-components';
|
|
16
17
|
const WrappedFormComponent = (props) => {
|
|
17
18
|
const { fields } = props, rest = __rest(props, ["fields"]);
|
|
18
19
|
return (React.createElement(FormComponent, Object.assign({}, rest, { validator: validatorAjv8, fields: Object.assign({}, fields) })));
|
|
@@ -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, value: 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,81 @@
|
|
|
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 a 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(formOptions.filePath, selectedFilePath);
|
|
44
|
+
setServerFilePath(relativePath);
|
|
45
|
+
setUrlPath('');
|
|
46
|
+
props.onChange(relativePath);
|
|
47
|
+
if (dialogElement) {
|
|
48
|
+
formOptions.dialogOptions.sourceData = Object.assign(Object.assign({}, formOptions.sourceData), { path: relativePath });
|
|
49
|
+
const formDialog = new CreationFormDialog(Object.assign({}, formOptions.dialogOptions));
|
|
50
|
+
await formDialog.launch();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
if (dialogElement) {
|
|
55
|
+
const formDialog = new CreationFormDialog(Object.assign({}, formOptions.dialogOptions));
|
|
56
|
+
await formDialog.launch();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
catch (e) {
|
|
61
|
+
console.error('Error handling file dialog:', e);
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
const handleURLChange = (event) => {
|
|
65
|
+
const url = event.target.value.trim();
|
|
66
|
+
isTypingURL.current = true;
|
|
67
|
+
setUrlPath(url);
|
|
68
|
+
setServerFilePath('');
|
|
69
|
+
props.onChange(url);
|
|
70
|
+
};
|
|
71
|
+
const handleURLBlur = () => {
|
|
72
|
+
isTypingURL.current = false;
|
|
73
|
+
};
|
|
74
|
+
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")),
|
|
78
|
+
React.createElement("div", null,
|
|
79
|
+
React.createElement("h3", { className: "jp-FormGroup-fieldLabel jp-FormGroup-contentItem" }, "Or enter external URL"),
|
|
80
|
+
React.createElement("input", { type: "text", id: "root_path", className: "jp-mod-styled", onChange: handleURLChange, onBlur: handleURLBlur, value: urlPath || '', style: { width: '100%' } }))));
|
|
81
|
+
};
|
|
@@ -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,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
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { showErrorMessage } from '@jupyterlab/apputils';
|
|
2
|
+
import { BaseForm } from './baseform';
|
|
3
|
+
import { loadFile } from '../../tools';
|
|
4
|
+
import { FileSelectorWidget } from './fileselectorwidget';
|
|
5
|
+
/**
|
|
6
|
+
* The form to modify a PathBasedSource source.
|
|
7
|
+
*/
|
|
8
|
+
export class PathBasedSourcePropertiesForm extends BaseForm {
|
|
9
|
+
constructor(props) {
|
|
10
|
+
var _a, _b;
|
|
11
|
+
super(props);
|
|
12
|
+
if (this.props.sourceType !== 'GeoJSONSource') {
|
|
13
|
+
this._validatePath((_b = (_a = props.sourceData) === null || _a === void 0 ? void 0 : _a.path) !== null && _b !== void 0 ? _b : '');
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
processSchema(data, schema, uiSchema) {
|
|
17
|
+
var _a;
|
|
18
|
+
super.processSchema(data, schema, uiSchema);
|
|
19
|
+
if (!schema.properties || !data) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
// Customize the widget for path field
|
|
23
|
+
if (schema.properties && schema.properties.path) {
|
|
24
|
+
const docManager = (_a = this.props.formChangedSignal) === null || _a === void 0 ? void 0 : _a.sender.props.formSchemaRegistry.getDocManager();
|
|
25
|
+
uiSchema.path = {
|
|
26
|
+
'ui:widget': FileSelectorWidget,
|
|
27
|
+
'ui:options': {
|
|
28
|
+
docManager,
|
|
29
|
+
formOptions: this.props
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
// This is not user-editable
|
|
34
|
+
delete schema.properties.valid;
|
|
35
|
+
}
|
|
36
|
+
onFormBlur(id, value) {
|
|
37
|
+
// Is there a better way to spot the path text entry?
|
|
38
|
+
if (!id.endsWith('_path')) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
this._validatePath(value);
|
|
42
|
+
}
|
|
43
|
+
// we need to use `onFormChange` instead of `onFormBlur` because it's no longer a text field
|
|
44
|
+
onFormChange(e) {
|
|
45
|
+
var _a;
|
|
46
|
+
super.onFormChange(e);
|
|
47
|
+
if (((_a = e.formData) === null || _a === void 0 ? void 0 : _a.path) !== undefined) {
|
|
48
|
+
this._validatePath(e.formData.path);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
onFormSubmit(e) {
|
|
52
|
+
var _a, _b, _c;
|
|
53
|
+
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) {
|
|
54
|
+
showErrorMessage('Invalid file', this.state.extraErrors.path.__errors[0]);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
super.onFormSubmit(e);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Validate the path, to avoid invalid path.
|
|
61
|
+
*
|
|
62
|
+
* @param path - the path to validate.
|
|
63
|
+
*/
|
|
64
|
+
async _validatePath(path) {
|
|
65
|
+
const extraErrors = this.state.extraErrors;
|
|
66
|
+
let error = '';
|
|
67
|
+
let valid = true;
|
|
68
|
+
if (!path) {
|
|
69
|
+
valid = false;
|
|
70
|
+
error = 'Path is required';
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
try {
|
|
74
|
+
await loadFile({
|
|
75
|
+
filepath: path,
|
|
76
|
+
type: this.props.sourceType,
|
|
77
|
+
model: this.props.model
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
catch (e) {
|
|
81
|
+
valid = false;
|
|
82
|
+
error = `"${path}" is not a valid ${this.props.sourceType} file.`;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (!valid) {
|
|
86
|
+
extraErrors.path = {
|
|
87
|
+
__errors: [error]
|
|
88
|
+
};
|
|
89
|
+
this.setState(old => (Object.assign(Object.assign({}, old), { extraErrors })));
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
this.setState(old => (Object.assign(Object.assign({}, old), { extraErrors: Object.assign(Object.assign({}, extraErrors), { path: { __errors: [] } }) })));
|
|
93
|
+
}
|
|
94
|
+
if (this.props.formErrorSignal) {
|
|
95
|
+
this.props.formErrorSignal.emit(!valid);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
package/lib/icons.d.ts
CHANGED
package/lib/icons.js
CHANGED
|
@@ -12,6 +12,7 @@ import visibilitySvgStr from '../style/icons/visibility.svg';
|
|
|
12
12
|
import nonVisibilitySvgStr from '../style/icons/nonvisibility.svg';
|
|
13
13
|
import geoJsonSvgStr from '../style/icons/geojson.svg';
|
|
14
14
|
import moundSvgStr from '../style/icons/mound.svg';
|
|
15
|
+
import logoMiniQGZ from '../style/icons/logo_mini_qgz.svg';
|
|
15
16
|
export const logoIcon = new LabIcon({
|
|
16
17
|
name: 'jupytergis::logo',
|
|
17
18
|
svgstr: logoSvgStr
|
|
@@ -44,3 +45,7 @@ export const moundIcon = new LabIcon({
|
|
|
44
45
|
name: 'jupytergis::mound',
|
|
45
46
|
svgstr: moundSvgStr
|
|
46
47
|
});
|
|
48
|
+
export const logoMiniIconQGZ = new LabIcon({
|
|
49
|
+
name: 'jupytergis::logoQGZ',
|
|
50
|
+
svgstr: logoMiniQGZ
|
|
51
|
+
});
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"command": "jupytergis:undo",
|
|
4
|
+
"keys": ["Accel Z"],
|
|
5
|
+
"selector": ".data-jgis-keybinding"
|
|
6
|
+
},
|
|
7
|
+
{
|
|
8
|
+
"command": "jupytergis:redo",
|
|
9
|
+
"keys": ["Accel Shift Z"],
|
|
10
|
+
"selector": ".data-jgis-keybinding"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"command": "jupytergis:identify",
|
|
14
|
+
"keys": ["Escape"],
|
|
15
|
+
"selector": ".data-jgis-keybinding"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"command": "jupytergis:removeSource",
|
|
19
|
+
"keys": ["Delete"],
|
|
20
|
+
"selector": ".data-jgis-keybinding .jp-gis-source.jp-gis-sourceUnused"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"command": "jupytergis:renameSource",
|
|
24
|
+
"keys": ["F2"],
|
|
25
|
+
"selector": ".data-jgis-keybinding .jp-gis-source"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"command": "jupytergis:removeLayer",
|
|
29
|
+
"keys": ["Delete"],
|
|
30
|
+
"selector": ".data-jgis-keybinding .jp-gis-layerItem"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"command": "jupytergis:renameLayer",
|
|
34
|
+
"keys": ["F2"],
|
|
35
|
+
"selector": ".data-jgis-keybinding .jp-gis-layerItem"
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"command": "jupytergis:removeGroup",
|
|
39
|
+
"keys": ["Delete"],
|
|
40
|
+
"selector": ".data-jgis-keybinding .jp-gis-layerGroupHeader"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"command": "jupytergis:renameGroup",
|
|
44
|
+
"keys": ["F2"],
|
|
45
|
+
"selector": ".data-jgis-keybinding .jp-gis-layerGroupHeader"
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"command": "jupytergis:executeConsole",
|
|
49
|
+
"keys": ["Shift Enter"],
|
|
50
|
+
"selector": ".jpgis-console .jp-CodeConsole[data-jp-interaction-mode='notebook'] .jp-CodeConsole-promptCell"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"command": "jupytergis:invokeConsoleCompleter",
|
|
54
|
+
"keys": ["Tab"],
|
|
55
|
+
"selector": ".jpgis-console .jp-CodeConsole-promptCell .jp-mod-completer-enabled"
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"command": "jupytergis:selectConsoleCompleter",
|
|
59
|
+
"keys": ["Enter"],
|
|
60
|
+
"selector": ".jpgis-console .jp-ConsolePanel .jp-mod-completer-active"
|
|
61
|
+
}
|
|
62
|
+
]
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { IAnnotation, IDict, IJGISLayer, IJGISSource } from '@jupytergis/schema';
|
|
2
2
|
import { User } from '@jupyterlab/services';
|
|
3
|
-
import
|
|
3
|
+
import { Layer } from 'ol/layer';
|
|
4
4
|
import * as React from 'react';
|
|
5
|
-
import { MainViewModel } from './mainviewmodel';
|
|
6
5
|
import { ClientPointer } from './CollaboratorPointers';
|
|
6
|
+
import { MainViewModel } from './mainviewmodel';
|
|
7
7
|
interface IProps {
|
|
8
8
|
viewModel: MainViewModel;
|
|
9
9
|
}
|
|
@@ -15,14 +15,20 @@ interface IStates {
|
|
|
15
15
|
firstLoad: boolean;
|
|
16
16
|
annotations: IDict<IAnnotation>;
|
|
17
17
|
clientPointers: IDict<ClientPointer>;
|
|
18
|
+
viewProjection: {
|
|
19
|
+
code: string;
|
|
20
|
+
units: string;
|
|
21
|
+
};
|
|
22
|
+
loadingLayer: boolean;
|
|
23
|
+
scale: number;
|
|
18
24
|
}
|
|
19
25
|
export declare class MainView extends React.Component<IProps, IStates> {
|
|
20
26
|
constructor(props: IProps);
|
|
21
27
|
componentDidMount(): Promise<void>;
|
|
22
28
|
componentWillUnmount(): void;
|
|
23
29
|
generateScene(): Promise<void>;
|
|
30
|
+
createSelectInteraction: () => void;
|
|
24
31
|
addContextMenu: () => void;
|
|
25
|
-
private _loadShapefileAsGeoJSON;
|
|
26
32
|
private _loadGeoTIFFWithCache;
|
|
27
33
|
/**
|
|
28
34
|
* Add a source in the map.
|
|
@@ -66,6 +72,7 @@ export declare class MainView extends React.Component<IProps, IStates> {
|
|
|
66
72
|
* @returns - the map layer.
|
|
67
73
|
*/
|
|
68
74
|
private _buildMapLayer;
|
|
75
|
+
addProjection(newMapLayer: Layer): void;
|
|
69
76
|
/**
|
|
70
77
|
* Add a layer to the map.
|
|
71
78
|
*
|
|
@@ -90,7 +97,16 @@ export declare class MainView extends React.Component<IProps, IStates> {
|
|
|
90
97
|
* @param id - id of the layer.
|
|
91
98
|
* @param layer - the layer object.
|
|
92
99
|
*/
|
|
93
|
-
updateLayer(id: string, layer: IJGISLayer, mapLayer:
|
|
100
|
+
updateLayer(id: string, layer: IJGISLayer, mapLayer: Layer): Promise<void>;
|
|
101
|
+
/**
|
|
102
|
+
* Wait for all layers to be loaded.
|
|
103
|
+
*/
|
|
104
|
+
private _waitForReady;
|
|
105
|
+
/**
|
|
106
|
+
* Wait for a layers source state to be 'ready'
|
|
107
|
+
* @param layer The Layer to check
|
|
108
|
+
*/
|
|
109
|
+
private _waitForSourceReady;
|
|
94
110
|
/**
|
|
95
111
|
* Remove a layer from the map.
|
|
96
112
|
*
|
|
@@ -129,7 +145,7 @@ export declare class MainView extends React.Component<IProps, IStates> {
|
|
|
129
145
|
private _onSharedMetadataChanged;
|
|
130
146
|
private _computeAnnotationPosition;
|
|
131
147
|
private _updateAnnotation;
|
|
132
|
-
private
|
|
148
|
+
private _onZoomToPosition;
|
|
133
149
|
private _moveToPosition;
|
|
134
150
|
private _onPointerMove;
|
|
135
151
|
private _syncPointer;
|
|
@@ -149,5 +165,6 @@ export declare class MainView extends React.Component<IProps, IStates> {
|
|
|
149
165
|
private _sourceToLayerMap;
|
|
150
166
|
private _documentPath?;
|
|
151
167
|
private _contextMenu;
|
|
168
|
+
private _loadingLayers;
|
|
152
169
|
}
|
|
153
170
|
export {};
|