From 946750c29d0eae10fe3ced0c60683affd2195d8a Mon Sep 17 00:00:00 2001 From: Nicolas Brichet Date: Wed, 10 Jul 2024 15:31:37 +0200 Subject: [PATCH] Use only one form to create a GeoJSON source and its layer --- examples/test.jGIS | 38 +++--- packages/base/src/commands.ts | 51 ++++++- .../base/src/dialogs/geoJsonLayerDialog.tsx | 125 ++++++++++++++++++ .../layerBrowserDialog.tsx | 2 +- packages/base/src/panelview/formbuilder.tsx | 22 +++ packages/base/src/toolbar/widget.tsx | 29 +--- packages/base/style/dialog.css | 4 + 7 files changed, 221 insertions(+), 50 deletions(-) create mode 100644 packages/base/src/dialogs/geoJsonLayerDialog.tsx rename packages/base/src/{layerBrowser => dialogs}/layerBrowserDialog.tsx (100%) diff --git a/examples/test.jGIS b/examples/test.jGIS index dd915283..8b802562 100644 --- a/examples/test.jGIS +++ b/examples/test.jGIS @@ -1,58 +1,58 @@ { "layers": { "57ef55ef-facb-48a2-ae1d-c9c824be3e8a": { - "name": "Regions France", - "type": "VectorLayer", "parameters": { - "opacity": 0.6, - "color": "#e66100", "source": "7d082e75-69d5-447a-82d8-b05cca5945ba", - "type": "line" + "type": "line", + "color": "#e66100", + "opacity": 0.6 }, - "visible": true + "name": "Regions France", + "visible": true, + "type": "VectorLayer" }, "a0044fd7-f167-445f-b3d1-620a8f94b498": { + "type": "RasterLayer", + "visible": true, "name": "Open Topo Map", "parameters": { "source": "5fd42e3b-4681-4607-b15d-65c3a3e89b32" - }, - "visible": true, - "type": "RasterLayer" + } }, "2467576f-b527-4cb7-998d-fa1d056fb8a1": { - "type": "RasterLayer", - "name": "Open Street Map", "parameters": { "source": "699facc9-e7c4-4f38-acf1-1fd7f02d9f36" }, + "type": "RasterLayer", + "name": "Open Street Map", "visible": true } }, "sources": { "5fd42e3b-4681-4607-b15d-65c3a3e89b32": { - "type": "RasterSource", "name": "Open Topo Map", "parameters": { "minZoom": 0.0, "url": "https://tile.opentopomap.org/{z}/{x}/{y}.png ", "maxZoom": 24.0 - } + }, + "type": "RasterSource" }, "7d082e75-69d5-447a-82d8-b05cca5945ba": { "name": "france_regions", "parameters": { - "valid": true, - "path": "examples/france_regions.json" + "path": "examples/france_regions.json", + "valid": true }, "type": "GeoJSONSource" }, "699facc9-e7c4-4f38-acf1-1fd7f02d9f36": { - "name": "Open Street Map", "type": "RasterSource", + "name": "Open Street Map", "parameters": { + "minZoom": 0.0, "maxZoom": 24.0, - "url": "https://tile.openstreetmap.org/{z}/{x}/{y}.png", - "minZoom": 0.0 + "url": "https://tile.openstreetmap.org/{z}/{x}/{y}.png" } } }, @@ -76,4 +76,4 @@ ] } ] -} +} \ No newline at end of file diff --git a/packages/base/src/commands.ts b/packages/base/src/commands.ts index 87db9659..9cfc3b85 100644 --- a/packages/base/src/commands.ts +++ b/packages/base/src/commands.ts @@ -22,7 +22,8 @@ import { FormDialog } from './formdialog'; import { geoJSONIcon } from './icons'; -import { LayerBrowserWidget } from './layerBrowser/layerBrowserDialog'; +import { LayerBrowserWidget } from './dialogs/layerBrowserDialog'; +import { GeoJSONLayerDialog } from './dialogs/geoJsonLayerDialog'; import { JupyterGISWidget } from './widget'; /** @@ -34,7 +35,8 @@ export namespace CommandIDs { export const openLayerBrowser = 'jupytergis:openLayerBrowser'; - export const newGeoJSONData = 'jupytergis:newGeoJSONData'; + export const newGeoJSONLayer = 'jupytergis:newGeoJSONLayer'; + export const newGeoJSONSource = 'jupytergis:newGeoJSONSource'; export const newVectorLayer = 'jupytergis:newVectorLayer'; } @@ -101,6 +103,17 @@ export function addCommands( ) }); + commands.addCommand(CommandIDs.newGeoJSONLayer, { + label: trans.__('New vector layer'), + isEnabled: () => { + return tracker.currentWidget + ? tracker.currentWidget.context.model.sharedModel.editable + : false; + }, + iconClass: 'fa fa-vector-square', + execute: Private.createGeoJSONLayer(tracker, formSchemaRegistry) + }); + commands.addCommand(CommandIDs.newVectorLayer, { label: trans.__('New vector layer'), isEnabled: () => { @@ -112,7 +125,7 @@ export function addCommands( execute: Private.createVectorLayer(tracker) }); - commands.addCommand(CommandIDs.newGeoJSONData, { + commands.addCommand(CommandIDs.newGeoJSONSource, { label: trans.__('Add GeoJSON data from file'), isEnabled: () => { return tracker.currentWidget @@ -165,6 +178,33 @@ namespace Private { }; } + /** + * Command to create a GeoJSON source and vector layer. + */ + export function createGeoJSONLayer( + tracker: WidgetTracker, + formSchemaRegistry: IJGISFormSchemaRegistry + ) { + return async () => { + const current = tracker.currentWidget; + + if (!current) { + return; + } + + const dialog = new GeoJSONLayerDialog({ + model: current.context.model, + registry: formSchemaRegistry + }); + await dialog.launch(); + }; + } + + /** + * Command to create a GeoJSON source. + * + * This is currently not used. + */ export function createGeoJSONSource( tracker: WidgetTracker ) { @@ -239,6 +279,11 @@ namespace Private { }; } + /** + * Command to create a Vector layer. + * + * This is currently not used. + */ export function createVectorLayer(tracker: WidgetTracker) { return async (arg: any) => { const current = tracker.currentWidget; diff --git a/packages/base/src/dialogs/geoJsonLayerDialog.tsx b/packages/base/src/dialogs/geoJsonLayerDialog.tsx new file mode 100644 index 00000000..65af394f --- /dev/null +++ b/packages/base/src/dialogs/geoJsonLayerDialog.tsx @@ -0,0 +1,125 @@ +import { + IDict, + IJGISFormSchemaRegistry, + IJGISLayer, + IJGISSource, + IJupyterGISModel +} from '@jupytergis/schema'; +import { Dialog } from '@jupyterlab/apputils'; +import { PathExt } from '@jupyterlab/coreutils'; +import { UUID } from '@lumino/coreutils'; +import * as React from 'react'; + +import { GeoJSONLayerPropertiesForm } from '../panelview/formbuilder'; +import { deepCopy } from '../tools'; + +/** + * Properties for the component to create GeoJSON layer. + */ +export interface IGeoJSONLayerComponentProps { + model: IJupyterGISModel; + formSchemaRegistry: IJGISFormSchemaRegistry; + cancel: () => void; +} + +/** + * React component returning a form dedicated to GeoJSON layer (and source) creation. + */ +export const GeoJSONLayerComponent = ({ + model, + formSchemaRegistry, + cancel +}: IGeoJSONLayerComponentProps) => { + const schema = deepCopy(formSchemaRegistry.getSchemas().get('VectorLayer')); + if (!schema) { + return; + } + + delete schema.properties?.source; + schema['properties'] = { + name: { type: 'string', description: 'The name of the vector layer' }, + path: { type: 'string', description: 'The path to the GeoJSON file' }, + ...schema['properties'] + }; + + schema.required = ['name', 'path']; + + const syncData = (props: IDict) => { + const sharedModel = model.sharedModel; + if (!sharedModel) { + return; + } + const { name, path, ...parameters } = props; + const sourceId = UUID.uuid4(); + + const sourceName = PathExt.basename(path, '.json'); + const sourceModel: IJGISSource = { + type: 'GeoJSONSource', + name: sourceName, + parameters: { + path + } + }; + + const layerModel: IJGISLayer = { + type: 'VectorLayer', + parameters: { + source: sourceId, + type: parameters.type, + color: parameters.color, + opacity: parameters.opacity + }, + visible: true, + name: name + }; + + sharedModel.addSource(sourceId, sourceModel); + model.addLayer(UUID.uuid4(), layerModel); + }; + + return ( + + ); +}; + +/** + * Options for the dialog to create GeoJSON layer. + */ +export interface IGeoJSONLayerOptions { + model: IJupyterGISModel; + registry: IJGISFormSchemaRegistry; +} + +/** + * The widget included in the Dialog shown when creating a GeoJSON layer (and source). + */ +export class GeoJSONLayerDialog extends Dialog { + constructor(options: IGeoJSONLayerOptions) { + let cancelCallback: (() => void) | undefined = undefined; + cancelCallback = () => { + this.resolve(0); + }; + + const body = ( + + ); + + super({ body, buttons: [Dialog.cancelButton(), Dialog.okButton()] }); + + this.addClass('jGIS-geoJSONLayer-FormDialog'); + this.id = 'jupytergis::geoJSONLayerDialog'; + } +} diff --git a/packages/base/src/layerBrowser/layerBrowserDialog.tsx b/packages/base/src/dialogs/layerBrowserDialog.tsx similarity index 100% rename from packages/base/src/layerBrowser/layerBrowserDialog.tsx rename to packages/base/src/dialogs/layerBrowserDialog.tsx index 1870bf89..5109f88f 100644 --- a/packages/base/src/layerBrowser/layerBrowserDialog.tsx +++ b/packages/base/src/dialogs/layerBrowserDialog.tsx @@ -9,11 +9,11 @@ import { IJupyterGISModel, IRasterLayerGalleryEntry } from '@jupytergis/schema'; +import { Dialog } from '@jupyterlab/apputils'; import { UUID } from '@lumino/coreutils'; import React, { ChangeEvent, MouseEvent, useEffect, useState } from 'react'; import { RasterSourcePropertiesForm } from '../panelview'; import { deepCopy } from '../tools'; -import { Dialog } from '@jupyterlab/apputils'; import CUSTOM_RASTER_IMAGE from '../../rasterlayer_gallery/custom_raster.png'; diff --git a/packages/base/src/panelview/formbuilder.tsx b/packages/base/src/panelview/formbuilder.tsx index 40324b57..a959ec38 100644 --- a/packages/base/src/panelview/formbuilder.tsx +++ b/packages/base/src/panelview/formbuilder.tsx @@ -351,6 +351,9 @@ export class RasterSourcePropertiesForm extends ObjectPropertiesForm { } } +/** + * The form to modify a vector layer. + */ export class VectorLayerPropertiesForm extends LayerPropertiesForm { protected processSchema( data: IDict | undefined, @@ -364,6 +367,9 @@ export class VectorLayerPropertiesForm extends LayerPropertiesForm { } } +/** + * The form to modify a GeoJSON source. + */ export class GeoJSONSourcePropertiesForm extends ObjectPropertiesForm { constructor(props: IProps) { super(props); @@ -427,3 +433,19 @@ export class GeoJSONSourcePropertiesForm extends ObjectPropertiesForm { private _validate: ValidateFunction; } + +/** + * The form to create a GeoJSON layer. + */ +export class GeoJSONLayerPropertiesForm extends GeoJSONSourcePropertiesForm { + protected processSchema( + data: IDict | undefined, + schema: IDict, + uiSchema: IDict + ) { + super.processSchema(data, schema, uiSchema); + uiSchema['color'] = { + 'ui:widget': 'color' + }; + } +} diff --git a/packages/base/src/toolbar/widget.tsx b/packages/base/src/toolbar/widget.tsx index 9198382d..dc685b8c 100644 --- a/packages/base/src/toolbar/widget.tsx +++ b/packages/base/src/toolbar/widget.tsx @@ -26,18 +26,6 @@ export class Separator extends Widget { } } -export class GroupName extends Widget { - /** - * Construct a new group name widget. - */ - constructor(options: { name: string }) { - const span = document.createElement('span'); - span.textContent = options.name; - super({ node: span }); - this.addClass(TOOLBAR_GROUPNAME_CLASS); - } -} - export class ToolbarWidget extends Toolbar { constructor(options: ToolbarWidget.IOptions) { super(options); @@ -66,7 +54,6 @@ export class ToolbarWidget extends Toolbar { ); this.addItem('separator1', new Separator()); - this.addItem('LayersGroup', new GroupName({ name: 'Layers' })); this.addItem( 'openLayerBrowser', @@ -78,21 +65,9 @@ export class ToolbarWidget extends Toolbar { ); this.addItem( - 'newVectorLayer', - new CommandToolbarButton({ - id: CommandIDs.newVectorLayer, - label: '', - commands: options.commands - }) - ); - - this.addItem('separator2', new Separator()); - this.addItem('SourcesGroup', new GroupName({ name: 'Sources' })); - - this.addItem( - 'newGeoJSONData', + 'newGeoJSONLayer', new CommandToolbarButton({ - id: CommandIDs.newGeoJSONData, + id: CommandIDs.newGeoJSONLayer, label: '', commands: options.commands }) diff --git a/packages/base/style/dialog.css b/packages/base/style/dialog.css index 13e375b7..73a1b11e 100644 --- a/packages/base/style/dialog.css +++ b/packages/base/style/dialog.css @@ -7,6 +7,10 @@ background-image: unset; } +.jGIS-geoJSONLayer-FormDialog .jp-Dialog-footer button { + display: none; +} + .jp-gis-addDataSourceBody > :not(:first-child) { margin-top: 1em; }