@jupytergis/base 0.1.7 → 0.2.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.d.ts +11 -0
- package/lib/annotations/components/Annotation.js +61 -0
- package/lib/annotations/components/AnnotationFloater.d.ts +7 -0
- package/lib/annotations/components/AnnotationFloater.js +30 -0
- package/lib/annotations/components/Message.d.ts +8 -0
- package/lib/annotations/components/Message.js +17 -0
- package/lib/annotations/index.d.ts +3 -0
- package/lib/annotations/index.js +3 -0
- package/lib/annotations/model.d.ts +28 -0
- package/lib/annotations/model.js +67 -0
- package/lib/commands.js +51 -6
- package/lib/constants.d.ts +2 -0
- package/lib/constants.js +5 -1
- package/lib/dialogs/symbology/tiff_layer/types/SingleBandPseudoColor.js +25 -33
- package/lib/formbuilder/formselectors.js +4 -0
- package/lib/formbuilder/objectform/baseform.d.ts +1 -1
- package/lib/formbuilder/objectform/baseform.js +31 -42
- package/lib/formbuilder/objectform/geojsonsource.js +33 -30
- package/lib/formbuilder/objectform/geotiffsource.d.ts +16 -0
- package/lib/formbuilder/objectform/geotiffsource.js +71 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/mainview/CollaboratorPointers.d.ts +17 -0
- package/lib/mainview/CollaboratorPointers.js +37 -0
- package/lib/mainview/FollowIndicator.d.ts +7 -0
- package/lib/mainview/FollowIndicator.js +9 -0
- package/lib/mainview/mainView.d.ts +36 -2
- package/lib/mainview/mainView.js +389 -27
- package/lib/mainview/mainviewmodel.d.ts +2 -1
- package/lib/mainview/mainviewmodel.js +5 -0
- package/lib/panelview/annotationPanel.d.ts +27 -0
- package/lib/panelview/annotationPanel.js +45 -0
- package/lib/panelview/components/filter-panel/Filter.d.ts +7 -2
- package/lib/panelview/components/filter-panel/Filter.js +1 -1
- package/lib/panelview/components/filter-panel/FilterRow.js +3 -3
- package/lib/panelview/components/identify-panel/IdentifyPanel.d.ts +15 -0
- package/lib/panelview/components/identify-panel/IdentifyPanel.js +108 -0
- package/lib/panelview/components/layers.js +4 -4
- package/lib/panelview/leftpanel.js +8 -0
- package/lib/panelview/rightpanel.d.ts +4 -1
- package/lib/panelview/rightpanel.js +28 -7
- package/lib/toolbar/widget.js +11 -1
- package/lib/tools.d.ts +35 -0
- package/lib/tools.js +86 -0
- package/package.json +5 -6
- package/style/base.css +4 -8
- package/style/dialog.css +1 -1
- package/style/icons/logo_mini.svg +70 -148
- package/style/icons/nonvisibility.svg +2 -7
- package/style/icons/visibility.svg +2 -6
- package/style/leftPanel.css +5 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { IAnnotationModel } from '@jupytergis/schema';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { IControlPanelModel } from '../../types';
|
|
4
|
+
export interface IAnnotationProps {
|
|
5
|
+
itemId: string;
|
|
6
|
+
annotationModel: IAnnotationModel;
|
|
7
|
+
rightPanelModel?: IControlPanelModel;
|
|
8
|
+
children?: JSX.Element[] | JSX.Element;
|
|
9
|
+
}
|
|
10
|
+
declare const Annotation: ({ itemId, annotationModel, rightPanelModel, children }: IAnnotationProps) => React.JSX.Element;
|
|
11
|
+
export default Annotation;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { faTrash, faPaperPlane, faArrowsToDot } from '@fortawesome/free-solid-svg-icons';
|
|
2
|
+
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|
3
|
+
import { showDialog, Dialog } from '@jupyterlab/apputils';
|
|
4
|
+
import { Button } from '@jupyterlab/ui-components';
|
|
5
|
+
import React, { useMemo, useState } from 'react';
|
|
6
|
+
import { Message } from './Message';
|
|
7
|
+
const Annotation = ({ itemId, annotationModel, rightPanelModel, children }) => {
|
|
8
|
+
const [messageContent, setMessageContent] = useState('');
|
|
9
|
+
const [jgisModel, setJgisModel] = useState(rightPanelModel === null || rightPanelModel === void 0 ? void 0 : rightPanelModel.jGISModel);
|
|
10
|
+
const annotation = annotationModel.getAnnotation(itemId);
|
|
11
|
+
const contents = useMemo(() => { var _a; return (_a = annotation === null || annotation === void 0 ? void 0 : annotation.contents) !== null && _a !== void 0 ? _a : []; }, [annotation]);
|
|
12
|
+
/**
|
|
13
|
+
* Update the model when it changes.
|
|
14
|
+
*/
|
|
15
|
+
rightPanelModel === null || rightPanelModel === void 0 ? void 0 : rightPanelModel.documentChanged.connect((_, widget) => {
|
|
16
|
+
setJgisModel(widget === null || widget === void 0 ? void 0 : widget.context.model);
|
|
17
|
+
});
|
|
18
|
+
const handleSubmit = () => {
|
|
19
|
+
annotationModel.addContent(itemId, messageContent);
|
|
20
|
+
setMessageContent('');
|
|
21
|
+
};
|
|
22
|
+
const handleDelete = async () => {
|
|
23
|
+
var _a;
|
|
24
|
+
// If the annotation has no content
|
|
25
|
+
// we remove it right away without prompting
|
|
26
|
+
if (!((_a = annotationModel.getAnnotation(itemId)) === null || _a === void 0 ? void 0 : _a.contents.length)) {
|
|
27
|
+
return annotationModel.removeAnnotation(itemId);
|
|
28
|
+
}
|
|
29
|
+
const result = await showDialog({
|
|
30
|
+
title: 'Delete Annotation',
|
|
31
|
+
body: 'Are you sure you want to delete this annotation?',
|
|
32
|
+
buttons: [Dialog.cancelButton(), Dialog.okButton({ label: 'Delete' })]
|
|
33
|
+
});
|
|
34
|
+
if (result.button.accept) {
|
|
35
|
+
annotationModel.removeAnnotation(itemId);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
const centerOnAnnotation = () => {
|
|
39
|
+
jgisModel === null || jgisModel === void 0 ? void 0 : jgisModel.centerOnAnnotation(itemId);
|
|
40
|
+
};
|
|
41
|
+
return (React.createElement("div", { className: "jGIS-Annotation" },
|
|
42
|
+
children,
|
|
43
|
+
React.createElement("div", null, contents.map(content => {
|
|
44
|
+
var _a, _b;
|
|
45
|
+
return (React.createElement(Message, { user: content.user, message: content.value, self: ((_a = annotationModel.user) === null || _a === void 0 ? void 0 : _a.username) === ((_b = content.user) === null || _b === void 0 ? void 0 : _b.username) }));
|
|
46
|
+
})),
|
|
47
|
+
React.createElement("div", { className: "jGIS-Annotation-Message" },
|
|
48
|
+
React.createElement("textarea", { rows: 3, placeholder: 'Ctrl+Enter to submit', value: messageContent, onChange: e => setMessageContent(e.currentTarget.value), onKeyDown: e => {
|
|
49
|
+
if (e.ctrlKey && e.key === 'Enter') {
|
|
50
|
+
handleSubmit();
|
|
51
|
+
}
|
|
52
|
+
} })),
|
|
53
|
+
React.createElement("div", { className: "jGIS-Annotation-Buttons" },
|
|
54
|
+
React.createElement(Button, { className: "jp-mod-styled jp-mod-warn", onClick: handleDelete },
|
|
55
|
+
React.createElement(FontAwesomeIcon, { icon: faTrash })),
|
|
56
|
+
rightPanelModel && (React.createElement(Button, { className: "jp-mod-styled jp-mod-accept", onClick: centerOnAnnotation },
|
|
57
|
+
React.createElement(FontAwesomeIcon, { icon: faArrowsToDot }))),
|
|
58
|
+
React.createElement(Button, { className: "jp-mod-styled jp-mod-accept", onClick: handleSubmit },
|
|
59
|
+
React.createElement(FontAwesomeIcon, { icon: faPaperPlane })))));
|
|
60
|
+
};
|
|
61
|
+
export default Annotation;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { IAnnotationProps } from './Annotation';
|
|
3
|
+
interface IAnnotationFloaterProps extends IAnnotationProps {
|
|
4
|
+
open: boolean;
|
|
5
|
+
}
|
|
6
|
+
declare const AnnotationFloater: ({ itemId, annotationModel: model, open }: IAnnotationFloaterProps) => React.JSX.Element;
|
|
7
|
+
export default AnnotationFloater;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { faWindowMinimize } from '@fortawesome/free-solid-svg-icons';
|
|
3
|
+
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|
4
|
+
import Annotation from './Annotation';
|
|
5
|
+
const AnnotationFloater = ({ itemId, annotationModel: model, open }) => {
|
|
6
|
+
const [isOpen, setIsOpen] = useState(open);
|
|
7
|
+
// Function that either
|
|
8
|
+
// - opens the annotation if `open`
|
|
9
|
+
// - removes the annotation if `!open` and the annotation is empty
|
|
10
|
+
// - closes the annotation if `!open` and the annotation is not empty
|
|
11
|
+
const setOpenOrDelete = (open) => {
|
|
12
|
+
var _a;
|
|
13
|
+
if (open) {
|
|
14
|
+
return setIsOpen(true);
|
|
15
|
+
}
|
|
16
|
+
if (!((_a = model.getAnnotation(itemId)) === null || _a === void 0 ? void 0 : _a.contents.length)) {
|
|
17
|
+
return model.removeAnnotation(itemId);
|
|
18
|
+
}
|
|
19
|
+
setIsOpen(false);
|
|
20
|
+
};
|
|
21
|
+
return (React.createElement(React.Fragment, null,
|
|
22
|
+
React.createElement("div", { className: "jGIS-Annotation-Handler", onClick: () => setOpenOrDelete(!isOpen) }),
|
|
23
|
+
React.createElement("div", { className: "jGIS-FloatingAnnotation", style: { visibility: isOpen ? 'visible' : 'hidden' } },
|
|
24
|
+
React.createElement(Annotation, { itemId: itemId, annotationModel: model },
|
|
25
|
+
React.createElement("div", { className: "jGIS-Popup-Topbar", onClick: () => {
|
|
26
|
+
setOpenOrDelete(false);
|
|
27
|
+
} },
|
|
28
|
+
React.createElement(FontAwesomeIcon, { icon: faWindowMinimize, className: "jGIS-Popup-TopBarIcon" }))))));
|
|
29
|
+
};
|
|
30
|
+
export default AnnotationFloater;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export const Message = (props) => {
|
|
3
|
+
var _a, _b, _c;
|
|
4
|
+
const { self, message, user } = props;
|
|
5
|
+
const color = (_a = user === null || user === void 0 ? void 0 : user.color) !== null && _a !== void 0 ? _a : 'black';
|
|
6
|
+
const author = (_b = user === null || user === void 0 ? void 0 : user.display_name) !== null && _b !== void 0 ? _b : '';
|
|
7
|
+
const initials = (_c = user === null || user === void 0 ? void 0 : user.initials) !== null && _c !== void 0 ? _c : '';
|
|
8
|
+
return (React.createElement("div", { className: "jGIS-Annotation-Message", style: {
|
|
9
|
+
flexFlow: self ? 'row' : 'row-reverse'
|
|
10
|
+
} },
|
|
11
|
+
React.createElement("div", { className: "jGIS-Annotation-User-Icon", style: {
|
|
12
|
+
backgroundColor: color
|
|
13
|
+
}, title: author },
|
|
14
|
+
React.createElement("span", { style: { width: 24, textAlign: 'center' } }, initials)),
|
|
15
|
+
React.createElement("div", { className: "jGIS-Annotation-Message-Content" },
|
|
16
|
+
React.createElement("p", { style: { padding: 7, margin: 0 } }, message))));
|
|
17
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { IAnnotation, IAnnotationModel, IJupyterGISModel } from '@jupytergis/schema';
|
|
2
|
+
import { DocumentRegistry } from '@jupyterlab/docregistry';
|
|
3
|
+
import { User } from '@jupyterlab/services';
|
|
4
|
+
import { ISignal } from '@lumino/signaling';
|
|
5
|
+
export declare class AnnotationModel implements IAnnotationModel {
|
|
6
|
+
constructor(options: AnnotationModel.IOptions);
|
|
7
|
+
get updateSignal(): ISignal<this, null>;
|
|
8
|
+
get user(): User.IIdentity | undefined;
|
|
9
|
+
set context(context: DocumentRegistry.IContext<IJupyterGISModel> | undefined);
|
|
10
|
+
get context(): DocumentRegistry.IContext<IJupyterGISModel> | undefined;
|
|
11
|
+
get contextChanged(): ISignal<this, void>;
|
|
12
|
+
update(): void;
|
|
13
|
+
getAnnotation(id: string): IAnnotation | undefined;
|
|
14
|
+
getAnnotationIds(): string[];
|
|
15
|
+
addAnnotation(key: string, value: IAnnotation): void;
|
|
16
|
+
removeAnnotation(key: string): void;
|
|
17
|
+
addContent(id: string, value: string): void;
|
|
18
|
+
private _context;
|
|
19
|
+
private _contextChanged;
|
|
20
|
+
private _updateSignal;
|
|
21
|
+
private _user?;
|
|
22
|
+
}
|
|
23
|
+
declare namespace AnnotationModel {
|
|
24
|
+
interface IOptions {
|
|
25
|
+
context: DocumentRegistry.IContext<IJupyterGISModel> | undefined;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export {};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { Signal } from '@lumino/signaling';
|
|
2
|
+
export class AnnotationModel {
|
|
3
|
+
constructor(options) {
|
|
4
|
+
this._contextChanged = new Signal(this);
|
|
5
|
+
this._updateSignal = new Signal(this);
|
|
6
|
+
this.context = options.context;
|
|
7
|
+
}
|
|
8
|
+
get updateSignal() {
|
|
9
|
+
return this._updateSignal;
|
|
10
|
+
}
|
|
11
|
+
get user() {
|
|
12
|
+
return this._user;
|
|
13
|
+
}
|
|
14
|
+
set context(context) {
|
|
15
|
+
var _a;
|
|
16
|
+
this._context = context;
|
|
17
|
+
const state = (_a = this._context) === null || _a === void 0 ? void 0 : _a.model.sharedModel.awareness.getLocalState();
|
|
18
|
+
this._user = state === null || state === void 0 ? void 0 : state.user;
|
|
19
|
+
this._contextChanged.emit(void 0);
|
|
20
|
+
}
|
|
21
|
+
get context() {
|
|
22
|
+
return this._context;
|
|
23
|
+
}
|
|
24
|
+
get contextChanged() {
|
|
25
|
+
return this._contextChanged;
|
|
26
|
+
}
|
|
27
|
+
update() {
|
|
28
|
+
this._updateSignal.emit(null);
|
|
29
|
+
}
|
|
30
|
+
getAnnotation(id) {
|
|
31
|
+
var _a;
|
|
32
|
+
const rawData = (_a = this._context) === null || _a === void 0 ? void 0 : _a.model.sharedModel.getMetadata(id);
|
|
33
|
+
if (rawData) {
|
|
34
|
+
return JSON.parse(rawData);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
getAnnotationIds() {
|
|
38
|
+
var _a;
|
|
39
|
+
const annotationIds = [];
|
|
40
|
+
for (const id in (_a = this._context) === null || _a === void 0 ? void 0 : _a.model.sharedModel.metadata) {
|
|
41
|
+
if (id.startsWith('annotation')) {
|
|
42
|
+
annotationIds.push(id);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return annotationIds;
|
|
46
|
+
}
|
|
47
|
+
addAnnotation(key, value) {
|
|
48
|
+
var _a;
|
|
49
|
+
(_a = this._context) === null || _a === void 0 ? void 0 : _a.model.sharedModel.setMetadata(`annotation_${key}`, JSON.stringify(value));
|
|
50
|
+
}
|
|
51
|
+
removeAnnotation(key) {
|
|
52
|
+
var _a;
|
|
53
|
+
(_a = this._context) === null || _a === void 0 ? void 0 : _a.model.removeMetadata(key);
|
|
54
|
+
}
|
|
55
|
+
addContent(id, value) {
|
|
56
|
+
var _a;
|
|
57
|
+
const newContent = {
|
|
58
|
+
value,
|
|
59
|
+
user: this._user
|
|
60
|
+
};
|
|
61
|
+
const currentAnnotation = this.getAnnotation(id);
|
|
62
|
+
if (currentAnnotation) {
|
|
63
|
+
const newAnnotation = Object.assign(Object.assign({}, currentAnnotation), { contents: [...currentAnnotation.contents, newContent] });
|
|
64
|
+
(_a = this._context) === null || _a === void 0 ? void 0 : _a.model.sharedModel.setMetadata(id, JSON.stringify(newAnnotation));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
package/lib/commands.js
CHANGED
|
@@ -11,15 +11,34 @@ export function addCommands(app, tracker, translator, formSchemaRegistry, layerB
|
|
|
11
11
|
const trans = translator.load('jupyterlab');
|
|
12
12
|
const { commands } = app;
|
|
13
13
|
commands.addCommand(CommandIDs.symbology, Object.assign({ label: trans.__('Edit Symbology'), isEnabled: () => {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
var _a, _b;
|
|
15
|
+
const model = (_a = tracker.currentWidget) === null || _a === void 0 ? void 0 : _a.context.model;
|
|
16
|
+
const localState = model === null || model === void 0 ? void 0 : model.sharedModel.awareness.getLocalState();
|
|
17
|
+
if (!model || !localState || !((_b = localState['selected']) === null || _b === void 0 ? void 0 : _b.value)) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
const selectedLayers = localState['selected'].value;
|
|
21
|
+
// Can't open more than one symbology dialog at once
|
|
22
|
+
if (Object.keys(selectedLayers).length > 1) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
const layerId = Object.keys(selectedLayers)[0];
|
|
26
|
+
const layer = model.getLayer(layerId);
|
|
27
|
+
if (!layer) {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
const isValidLayer = [
|
|
31
|
+
'VectorLayer',
|
|
32
|
+
'VectorTileLayer',
|
|
33
|
+
'WebGlLayer'
|
|
34
|
+
].includes(layer.type);
|
|
35
|
+
return isValidLayer;
|
|
17
36
|
}, execute: Private.createSymbologyDialog(tracker, state) }, icons.get(CommandIDs.symbology)));
|
|
18
37
|
commands.addCommand(CommandIDs.redo, Object.assign({ label: trans.__('Redo'), isEnabled: () => {
|
|
19
38
|
return tracker.currentWidget
|
|
20
39
|
? tracker.currentWidget.context.model.sharedModel.editable
|
|
21
40
|
: false;
|
|
22
|
-
}, execute:
|
|
41
|
+
}, execute: () => {
|
|
23
42
|
const current = tracker.currentWidget;
|
|
24
43
|
if (current) {
|
|
25
44
|
return current.context.model.sharedModel.redo();
|
|
@@ -29,12 +48,38 @@ export function addCommands(app, tracker, translator, formSchemaRegistry, layerB
|
|
|
29
48
|
return tracker.currentWidget
|
|
30
49
|
? tracker.currentWidget.context.model.sharedModel.editable
|
|
31
50
|
: false;
|
|
32
|
-
}, execute:
|
|
51
|
+
}, execute: () => {
|
|
33
52
|
const current = tracker.currentWidget;
|
|
34
53
|
if (current) {
|
|
35
54
|
return current.context.model.sharedModel.undo();
|
|
36
55
|
}
|
|
37
56
|
} }, icons.get(CommandIDs.undo)));
|
|
57
|
+
commands.addCommand(CommandIDs.identify, Object.assign({ label: trans.__('Identify'), isToggled: () => {
|
|
58
|
+
var _a;
|
|
59
|
+
return ((_a = tracker.currentWidget) === null || _a === void 0 ? void 0 : _a.context.model.isIdentifying) || false;
|
|
60
|
+
}, isEnabled: () => {
|
|
61
|
+
return tracker.currentWidget
|
|
62
|
+
? tracker.currentWidget.context.model.sharedModel.editable
|
|
63
|
+
: false;
|
|
64
|
+
}, execute: args => {
|
|
65
|
+
const current = tracker.currentWidget;
|
|
66
|
+
if (!current) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const luminoEvent = args['_luminoEvent'];
|
|
70
|
+
if (luminoEvent) {
|
|
71
|
+
const keysPressed = luminoEvent.keys;
|
|
72
|
+
if (keysPressed === null || keysPressed === void 0 ? void 0 : keysPressed.includes('Escape')) {
|
|
73
|
+
current.context.model.isIdentifying = false;
|
|
74
|
+
current.node.classList.remove('jGIS-identify-tool');
|
|
75
|
+
commands.notifyCommandChanged(CommandIDs.identify);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
current.node.classList.toggle('jGIS-identify-tool');
|
|
80
|
+
current.context.model.toggleIdentify();
|
|
81
|
+
commands.notifyCommandChanged(CommandIDs.identify);
|
|
82
|
+
} }, icons.get(CommandIDs.identify)));
|
|
38
83
|
/**
|
|
39
84
|
* SOURCES and LAYERS creation commands.
|
|
40
85
|
*/
|
|
@@ -185,7 +230,7 @@ export function addCommands(app, tracker, translator, formSchemaRegistry, layerB
|
|
|
185
230
|
createSource: true,
|
|
186
231
|
sourceData: {
|
|
187
232
|
name: 'Custom GeoTiff Source',
|
|
188
|
-
urls: [
|
|
233
|
+
urls: [{}]
|
|
189
234
|
},
|
|
190
235
|
layerData: { name: 'Custom GeoTiff Layer' },
|
|
191
236
|
sourceType: 'GeoTiffSource',
|
package/lib/constants.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ export declare namespace CommandIDs {
|
|
|
7
7
|
const redo = "jupytergis:redo";
|
|
8
8
|
const undo = "jupytergis:undo";
|
|
9
9
|
const symbology = "jupytergis:symbology";
|
|
10
|
+
const identify = "jupytergis:identify";
|
|
10
11
|
const openLayerBrowser = "jupytergis:openLayerBrowser";
|
|
11
12
|
const newRasterEntry = "jupytergis:newRasterEntry";
|
|
12
13
|
const newVectorTileEntry = "jupytergis:newVectorTileEntry";
|
|
@@ -43,6 +44,7 @@ export declare namespace CommandIDs {
|
|
|
43
44
|
const removeConsole = "jupytergis:removeConsole";
|
|
44
45
|
const executeConsole = "jupytergis:executeConsole";
|
|
45
46
|
const selectCompleter = "jupytergis:selectConsoleCompleter";
|
|
47
|
+
const addAnnotation = "jupytergis:addAnnotation";
|
|
46
48
|
}
|
|
47
49
|
interface IRegisteredIcon {
|
|
48
50
|
icon?: LabIcon;
|
package/lib/constants.js
CHANGED
|
@@ -9,6 +9,7 @@ export var CommandIDs;
|
|
|
9
9
|
CommandIDs.redo = 'jupytergis:redo';
|
|
10
10
|
CommandIDs.undo = 'jupytergis:undo';
|
|
11
11
|
CommandIDs.symbology = 'jupytergis:symbology';
|
|
12
|
+
CommandIDs.identify = 'jupytergis:identify';
|
|
12
13
|
// Layers and sources creation commands
|
|
13
14
|
CommandIDs.openLayerBrowser = 'jupytergis:openLayerBrowser';
|
|
14
15
|
// Layer and source
|
|
@@ -52,6 +53,8 @@ export var CommandIDs;
|
|
|
52
53
|
CommandIDs.removeConsole = 'jupytergis:removeConsole';
|
|
53
54
|
CommandIDs.executeConsole = 'jupytergis:executeConsole';
|
|
54
55
|
CommandIDs.selectCompleter = 'jupytergis:selectConsoleCompleter';
|
|
56
|
+
// Map Commands
|
|
57
|
+
CommandIDs.addAnnotation = 'jupytergis:addAnnotation';
|
|
55
58
|
})(CommandIDs || (CommandIDs = {}));
|
|
56
59
|
const iconObject = {
|
|
57
60
|
RasterSource: { icon: rasterIcon },
|
|
@@ -77,7 +80,8 @@ const iconObject = {
|
|
|
77
80
|
[CommandIDs.newVideoEntry]: { iconClass: 'fa fa-video' },
|
|
78
81
|
[CommandIDs.newShapefileLayer]: { iconClass: 'fa fa-file' },
|
|
79
82
|
[CommandIDs.newGeoTiffEntry]: { iconClass: 'fa fa-image' },
|
|
80
|
-
[CommandIDs.symbology]: { iconClass: 'fa fa-brush' }
|
|
83
|
+
[CommandIDs.symbology]: { iconClass: 'fa fa-brush' },
|
|
84
|
+
[CommandIDs.identify]: { iconClass: 'fa fa-info' }
|
|
81
85
|
};
|
|
82
86
|
/**
|
|
83
87
|
* The registered icons
|
|
@@ -6,8 +6,8 @@ import BandRow from '../components/BandRow';
|
|
|
6
6
|
import ColorRamp from '../../components/color_ramp/ColorRamp';
|
|
7
7
|
import StopRow from '../../components/color_stops/StopRow';
|
|
8
8
|
import { Utils } from '../../symbologyUtils';
|
|
9
|
-
import { getGdal } from '../../../../gdal';
|
|
10
9
|
import { Spinner } from '../../../../mainview/spinner';
|
|
10
|
+
import { loadGeoTIFFWithCache } from '../../../../tools';
|
|
11
11
|
const SingleBandPseudoColor = ({ context, okSignalPromise, cancel, layerId }) => {
|
|
12
12
|
const functions = ['discrete', 'linear', 'exact'];
|
|
13
13
|
const modeOptions = ['continuous', 'equal interval', 'quantile'];
|
|
@@ -67,46 +67,38 @@ const SingleBandPseudoColor = ({ context, okSignalPromise, cancel, layerId }) =>
|
|
|
67
67
|
setSelectedBand(band);
|
|
68
68
|
setSelectedFunction(interpolation);
|
|
69
69
|
};
|
|
70
|
+
const preloadGeoTiffFile = async (sourceInfo) => {
|
|
71
|
+
return await loadGeoTIFFWithCache(sourceInfo);
|
|
72
|
+
};
|
|
70
73
|
const getBandInfo = async () => {
|
|
71
74
|
var _a, _b;
|
|
72
75
|
const bandsArr = [];
|
|
73
76
|
const source = context.model.getSource((_a = layer === null || layer === void 0 ? void 0 : layer.parameters) === null || _a === void 0 ? void 0 : _a.source);
|
|
74
77
|
const sourceInfo = (_b = source === null || source === void 0 ? void 0 : source.parameters) === null || _b === void 0 ? void 0 : _b.urls[0];
|
|
75
|
-
if (!sourceInfo.url) {
|
|
78
|
+
if (!(sourceInfo === null || sourceInfo === void 0 ? void 0 : sourceInfo.url)) {
|
|
76
79
|
return;
|
|
77
80
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
bandsArr.push({
|
|
97
|
-
band: bandData.band,
|
|
98
|
-
colorInterpretation: bandData.colorInterpretation,
|
|
99
|
-
stats: {
|
|
100
|
-
minimum: (_a = sourceInfo.min) !== null && _a !== void 0 ? _a : bandData.minimum,
|
|
101
|
-
maximum: (_b = sourceInfo.max) !== null && _b !== void 0 ? _b : bandData.maximum,
|
|
102
|
-
mean: bandData.mean,
|
|
103
|
-
stdDev: bandData.stdDev
|
|
104
|
-
},
|
|
105
|
-
metadata: bandData.metadata,
|
|
106
|
-
histogram: bandData.histogram
|
|
81
|
+
// Preload the file only once
|
|
82
|
+
const preloadedFile = await preloadGeoTiffFile(sourceInfo);
|
|
83
|
+
const { file, metadata, sourceUrl } = Object.assign({}, preloadedFile);
|
|
84
|
+
if (file && metadata && sourceUrl === sourceInfo.url) {
|
|
85
|
+
metadata['bands'].forEach((bandData) => {
|
|
86
|
+
var _a, _b;
|
|
87
|
+
bandsArr.push({
|
|
88
|
+
band: bandData.band,
|
|
89
|
+
colorInterpretation: bandData.colorInterpretation,
|
|
90
|
+
stats: {
|
|
91
|
+
minimum: (_a = sourceInfo.min) !== null && _a !== void 0 ? _a : bandData.minimum,
|
|
92
|
+
maximum: (_b = sourceInfo.max) !== null && _b !== void 0 ? _b : bandData.maximum,
|
|
93
|
+
mean: bandData.mean,
|
|
94
|
+
stdDev: bandData.stdDev
|
|
95
|
+
},
|
|
96
|
+
metadata: bandData.metadata,
|
|
97
|
+
histogram: bandData.histogram
|
|
98
|
+
});
|
|
107
99
|
});
|
|
108
|
-
|
|
109
|
-
|
|
100
|
+
setBandRows(bandsArr);
|
|
101
|
+
}
|
|
110
102
|
};
|
|
111
103
|
const buildColorInfo = () => {
|
|
112
104
|
var _a;
|
|
@@ -5,6 +5,7 @@ import { LayerPropertiesForm } from './objectform/layerform';
|
|
|
5
5
|
import { TileSourcePropertiesForm } from './objectform/tilesourceform';
|
|
6
6
|
import { VectorLayerPropertiesForm } from './objectform/vectorlayerform';
|
|
7
7
|
import { WebGlLayerPropertiesForm } from './objectform/webGlLayerForm';
|
|
8
|
+
import { GeoTiffSourcePropertiesForm } from './objectform/geotiffsource';
|
|
8
9
|
export function getLayerTypeForm(layerType) {
|
|
9
10
|
let LayerForm = LayerPropertiesForm;
|
|
10
11
|
switch (layerType) {
|
|
@@ -28,6 +29,9 @@ export function getSourceTypeForm(sourceType) {
|
|
|
28
29
|
case 'GeoJSONSource':
|
|
29
30
|
SourceForm = GeoJSONSourcePropertiesForm;
|
|
30
31
|
break;
|
|
32
|
+
case 'GeoTiffSource':
|
|
33
|
+
SourceForm = GeoTiffSourcePropertiesForm;
|
|
34
|
+
break;
|
|
31
35
|
case 'RasterSource':
|
|
32
36
|
case 'VectorTileSource':
|
|
33
37
|
SourceForm = TileSourcePropertiesForm;
|
|
@@ -52,7 +52,6 @@ export interface IBaseFormProps {
|
|
|
52
52
|
*/
|
|
53
53
|
formErrorSignal?: Signal<Dialog<any>, boolean>;
|
|
54
54
|
}
|
|
55
|
-
export declare const LuminoSchemaForm: (props: React.PropsWithChildren<any>) => JSX.Element;
|
|
56
55
|
/**
|
|
57
56
|
* 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.
|
|
58
57
|
*
|
|
@@ -61,6 +60,7 @@ export declare const LuminoSchemaForm: (props: React.PropsWithChildren<any>) =>
|
|
|
61
60
|
export declare class BaseForm extends React.Component<IBaseFormProps, IBaseFormStates> {
|
|
62
61
|
constructor(props: IBaseFormProps);
|
|
63
62
|
componentDidUpdate(prevProps: IBaseFormProps, prevState: IBaseFormStates): void;
|
|
63
|
+
componentDidMount(): void;
|
|
64
64
|
protected processSchema(data: IDict<any> | undefined, schema: IDict, uiSchema: IDict): void;
|
|
65
65
|
/**
|
|
66
66
|
* Remove a specific entry from the form. Can be used in subclasses if needed while under processSchema.
|
|
@@ -1,35 +1,21 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
2
|
+
var t = {};
|
|
3
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
4
|
+
t[p] = s[p];
|
|
5
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
6
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
7
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
8
|
+
t[p[i]] = s[p[i]];
|
|
9
|
+
}
|
|
10
|
+
return t;
|
|
11
|
+
};
|
|
12
|
+
import { FormComponent } from '@jupyterlab/ui-components';
|
|
13
|
+
import validatorAjv8 from '@rjsf/validator-ajv8';
|
|
4
14
|
import * as React from 'react';
|
|
5
15
|
import { deepCopy } from '../../tools';
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const ref = React.useRef(null);
|
|
10
|
-
const { children } = props;
|
|
11
|
-
React.useEffect(() => {
|
|
12
|
-
const widget = children;
|
|
13
|
-
try {
|
|
14
|
-
MessageLoop.sendMessage(widget, Widget.Msg.BeforeAttach);
|
|
15
|
-
ref.current.insertBefore(widget.node, null);
|
|
16
|
-
MessageLoop.sendMessage(widget, Widget.Msg.AfterAttach);
|
|
17
|
-
}
|
|
18
|
-
catch (e) {
|
|
19
|
-
console.warn('Exception while attaching Lumino widget.', e);
|
|
20
|
-
}
|
|
21
|
-
return () => {
|
|
22
|
-
try {
|
|
23
|
-
if (widget.isAttached || widget.node.isConnected) {
|
|
24
|
-
Widget.detach(widget);
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
catch (e) {
|
|
28
|
-
console.warn('Exception while detaching Lumino widget.', e);
|
|
29
|
-
}
|
|
30
|
-
};
|
|
31
|
-
}, [children]);
|
|
32
|
-
return React.createElement("div", { ref: ref });
|
|
16
|
+
const WrappedFormComponent = (props) => {
|
|
17
|
+
const { fields } = props, rest = __rest(props, ["fields"]);
|
|
18
|
+
return (React.createElement(FormComponent, Object.assign({}, rest, { validator: validatorAjv8, fields: Object.assign({}, fields) })));
|
|
33
19
|
};
|
|
34
20
|
/**
|
|
35
21
|
* 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.
|
|
@@ -52,6 +38,13 @@ export class BaseForm extends React.Component {
|
|
|
52
38
|
this.setState(old => (Object.assign(Object.assign({}, old), { schema })));
|
|
53
39
|
}
|
|
54
40
|
}
|
|
41
|
+
componentDidMount() {
|
|
42
|
+
if (this.props.formErrorSignal) {
|
|
43
|
+
const extraErrors = Object.keys(Object.assign({}, this.state.extraErrors)).length > 0;
|
|
44
|
+
this.setState(old => (Object.assign(Object.assign({}, old), this.state.extraErrors)));
|
|
45
|
+
this.props.formErrorSignal.emit(extraErrors);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
55
48
|
processSchema(data, schema, uiSchema) {
|
|
56
49
|
if (!schema['properties']) {
|
|
57
50
|
return;
|
|
@@ -146,19 +139,15 @@ export class BaseForm extends React.Component {
|
|
|
146
139
|
var _a;
|
|
147
140
|
(_a = submitRef.current) === null || _a === void 0 ? void 0 : _a.click();
|
|
148
141
|
});
|
|
149
|
-
const formSchema = new SchemaForm(schema, {
|
|
150
|
-
liveValidate: true,
|
|
151
|
-
formData,
|
|
152
|
-
onChange: this.onFormChange.bind(this),
|
|
153
|
-
onSubmit: this.onFormSubmit.bind(this),
|
|
154
|
-
onBlur: this.onFormBlur.bind(this),
|
|
155
|
-
uiSchema,
|
|
156
|
-
children: (React.createElement("button", { ref: submitRef, type: "submit", style: { display: 'none' } })),
|
|
157
|
-
extraErrors: this.state.extraErrors
|
|
158
|
-
});
|
|
159
142
|
return (React.createElement("div", { className: "jGIS-property-panel", "data-path": (_b = this.props.filePath) !== null && _b !== void 0 ? _b : '' },
|
|
160
|
-
React.createElement("div", { className: "jGIS-property-outer"
|
|
161
|
-
|
|
143
|
+
React.createElement("div", { className: "jGIS-property-outer", onKeyUp: (e) => {
|
|
144
|
+
var _a;
|
|
145
|
+
if (e.key === 'Enter') {
|
|
146
|
+
e.preventDefault();
|
|
147
|
+
(_a = submitRef.current) === null || _a === void 0 ? void 0 : _a.click();
|
|
148
|
+
}
|
|
149
|
+
} },
|
|
150
|
+
React.createElement(WrappedFormComponent, { schema: schema, uiSchema: uiSchema, formData: formData, onSubmit: this.onFormSubmit.bind(this), onChange: this.onFormChange.bind(this), onBlur: this.onFormBlur.bind(this), liveValidate: true, children: React.createElement("button", { ref: submitRef, type: "submit", style: { display: 'none' } }), extraErrors: this.state.extraErrors })),
|
|
162
151
|
!this.props.ok && (React.createElement("div", { className: "jGIS-property-buttons" },
|
|
163
152
|
React.createElement("button", { className: "jp-Dialog-button jp-mod-accept jp-mod-styled", onClick: () => { var _a; return (_a = submitRef.current) === null || _a === void 0 ? void 0 : _a.click(); } },
|
|
164
153
|
React.createElement("div", { className: "jp-Dialog-buttonLabel" }, "Ok"))))));
|