From ecec4aa81389440a162c5f6ec528b0a0cd9f1346 Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Sun, 16 Mar 2025 19:56:12 +0530 Subject: [PATCH 01/25] Processing: Add `Dissolve` Command --- packages/base/src/commands.ts | 160 +++++++++++++++++++++++ packages/base/src/constants.ts | 1 + packages/schema/src/schema/dissolve.json | 22 ++++ python/jupytergis_lab/src/index.ts | 4 + 4 files changed, 187 insertions(+) create mode 100644 packages/schema/src/schema/dissolve.json diff --git a/packages/base/src/commands.ts b/packages/base/src/commands.ts index d0668a6c2..d99c6f57c 100644 --- a/packages/base/src/commands.ts +++ b/packages/base/src/commands.ts @@ -454,6 +454,166 @@ export function addCommands( } }); + commands.addCommand(CommandIDs.dissolve, { + label: trans.__('Dissolve'), + isEnabled: () => { + const selectedLayer = getSingleSelectedLayer(tracker); + if (!selectedLayer) { + return false; + } + return ['VectorLayer', 'ShapefileLayer'].includes(selectedLayer.type); + }, + execute: async () => { + const selected = getSingleSelectedLayer(tracker); + if (!selected) { + console.error('No valid selected layer.'); + return; + } + + const layers = tracker.currentWidget?.model.sharedModel.layers ?? {}; + const sources = tracker.currentWidget?.model.sharedModel.sources ?? {}; + const model = tracker.currentWidget?.model; + const localState = model?.sharedModel.awareness.getLocalState(); + + if (!model || !localState || !localState['selected']?.value) { + return; + } + + const selectedLayer = localState['selected'].value; + const selectedLayerId = Object.keys(selectedLayer)[0]; + const inputLayer = layers[selectedLayerId]; + + if (!inputLayer.parameters) { + console.error('Selected layer not found.'); + return; + } + + const sourceId = inputLayer.parameters.source; + const source = sources[sourceId]; + + if (!source || !source.parameters) { + console.error(`Source with ID ${sourceId} not found or missing path.`); + return; + } + + // Load GeoJSON data + const geojsonString = await getGeoJSONDataFromLayerSource(source, model); + if (!geojsonString) { + return; + } + + const geojson = JSON.parse(geojsonString); + if (!geojson.features || geojson.features.length === 0) { + console.error('Invalid GeoJSON: No features found.'); + return; + } + + // Extract field names from the first feature's properties + const properties = geojson.features[0].properties; + const fieldNames = Object.keys(properties); + + if (fieldNames.length === 0) { + console.error('No attribute fields found in GeoJSON.'); + return; + } + + // Retrieve dissolve schema and update fields dynamically + const schema = { + ...(formSchemaRegistry.getSchemas().get('Dissolve') as IDict), + properties: { + ...formSchemaRegistry.getSchemas().get('Dissolve')?.properties, + dissolveField: { + type: 'string', + enum: fieldNames, // Populate dropdown with field names + description: 'Select the field for dissolving features.' + } + } + }; + + // Open form and get user input + const formValues = await new Promise(resolve => { + const dialog = new FormDialog({ + title: 'Dissolve', + schema: schema, + model: model, + sourceData: { + inputLayer: selectedLayerId, + dissolveField: fieldNames[0] // Default to the first field + }, + cancelButton: false, + syncData: (props: IDict) => { + resolve(props); + dialog.dispose(); + } + }); + + dialog.launch(); + }); + + if (!formValues) { + return; + } + + const dissolveField = formValues.dissolveField; + const fileBlob = new Blob([geojsonString], { + type: 'application/geo+json' + }); + const geoFile = new File([fileBlob], 'data.geojson', { + type: 'application/geo+json' + }); + + const Gdal = await getGdal(); + const result = await Gdal.open(geoFile); + + if (result.datasets.length > 0) { + const dataset = result.datasets[0] as any; + const layerName = dataset.info.layers[0].name; + + const sqlQuery = ` + SELECT ST_Union(geometry) AS geometry, ${dissolveField} + FROM ${layerName} + GROUP BY ${dissolveField} + `; + + const options = [ + '-f', + 'GeoJSON', + '-nlt', + 'PROMOTE_TO_MULTI', + '-dialect', + 'sqlite', + '-sql', + sqlQuery, + 'output.geojson' + ]; + + const outputFilePath = await Gdal.ogr2ogr(dataset, options); + const dissolvedBytes = await Gdal.getFileBytes(outputFilePath); + const dissolvedGeoJSONString = new TextDecoder().decode(dissolvedBytes); + Gdal.close(dataset); + + const dissolvedGeoJSON = JSON.parse(dissolvedGeoJSONString); + + const newSourceId = UUID.uuid4(); + const sourceModel: IJGISSource = { + type: 'GeoJSONSource', + name: inputLayer.name + ' Dissolved', + parameters: { data: dissolvedGeoJSON } + }; + + const layerModel: IJGISLayer = { + type: 'VectorLayer', + parameters: { source: newSourceId }, + visible: true, + name: inputLayer.name + ' Dissolved' + }; + + model.sharedModel.addSource(newSourceId, sourceModel); + model.addLayer(UUID.uuid4(), layerModel); + } + } + }); + commands.addCommand(CommandIDs.newGeoJSONEntry, { label: trans.__('New GeoJSON layer'), isEnabled: () => { diff --git a/packages/base/src/constants.ts b/packages/base/src/constants.ts index 4ffce83bd..eb91c975d 100644 --- a/packages/base/src/constants.ts +++ b/packages/base/src/constants.ts @@ -26,6 +26,7 @@ export namespace CommandIDs { // Processing commands export const buffer = 'jupytergis:buffer'; + export const dissolve = 'jupytergis:dissolve'; // Sources only commands export const newRasterSource = 'jupytergis:newRasterSource'; diff --git a/packages/schema/src/schema/dissolve.json b/packages/schema/src/schema/dissolve.json new file mode 100644 index 000000000..7b5cbb635 --- /dev/null +++ b/packages/schema/src/schema/dissolve.json @@ -0,0 +1,22 @@ +{ + "type": "object", + "description": "Dissolve", + "title": "IDissolve", + "required": ["inputLayer", "dissolveField"], + "additionalProperties": false, + "properties": { + "inputLayer": { + "type": "string", + "description": "The input layer for the dissolve operation." + }, + "dissolveField": { + "type": "string", + "description": "The field based on which geometries will be dissolved." + }, + "projection": { + "type": "string", + "description": "The spatial reference system of the output.", + "default": "EPSG:4326" + } + } +} diff --git a/python/jupytergis_lab/src/index.ts b/python/jupytergis_lab/src/index.ts index dd0f625c7..4178bdbd3 100644 --- a/python/jupytergis_lab/src/index.ts +++ b/python/jupytergis_lab/src/index.ts @@ -197,6 +197,10 @@ const plugin: JupyterFrontEndPlugin = { command: CommandIDs.buffer }); + processingSubmenu.addItem({ + command: CommandIDs.dissolve + }); + app.contextMenu.addItem({ type: 'submenu', selector: '.jp-gis-layerItem', From 0e5a41672222a44256b81a7f5e2c9a308cc5beff Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Sun, 16 Mar 2025 20:10:39 +0530 Subject: [PATCH 02/25] not needed --- packages/schema/src/schema/dissolve.json | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/schema/src/schema/dissolve.json b/packages/schema/src/schema/dissolve.json index 7b5cbb635..5470af902 100644 --- a/packages/schema/src/schema/dissolve.json +++ b/packages/schema/src/schema/dissolve.json @@ -12,11 +12,6 @@ "dissolveField": { "type": "string", "description": "The field based on which geometries will be dissolved." - }, - "projection": { - "type": "string", - "description": "The spatial reference system of the output.", - "default": "EPSG:4326" } } } From f7ff8a5d2bebf3f85cc8ccb2db5bfbc5d0be3e56 Mon Sep 17 00:00:00 2001 From: Arjun Verma Date: Mon, 17 Mar 2025 18:16:47 +0530 Subject: [PATCH 03/25] Apply suggestions from code review Co-authored-by: martinRenou --- packages/base/src/commands.ts | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/packages/base/src/commands.ts b/packages/base/src/commands.ts index d99c6f57c..803f0272a 100644 --- a/packages/base/src/commands.ts +++ b/packages/base/src/commands.ts @@ -479,16 +479,8 @@ export function addCommands( return; } - const selectedLayer = localState['selected'].value; - const selectedLayerId = Object.keys(selectedLayer)[0]; - const inputLayer = layers[selectedLayerId]; - - if (!inputLayer.parameters) { - console.error('Selected layer not found.'); - return; - } - const sourceId = inputLayer.parameters.source; + const sourceId = selected.parameters.source; const source = sources[sourceId]; if (!source || !source.parameters) { From 00e4444d6d43e9b4ac62106dfe53339a845e234e Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Mon, 17 Mar 2025 18:27:36 +0530 Subject: [PATCH 04/25] fix --- packages/base/src/commands.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/base/src/commands.ts b/packages/base/src/commands.ts index 803f0272a..7129b92e2 100644 --- a/packages/base/src/commands.ts +++ b/packages/base/src/commands.ts @@ -470,12 +470,11 @@ export function addCommands( return; } - const layers = tracker.currentWidget?.model.sharedModel.layers ?? {}; const sources = tracker.currentWidget?.model.sharedModel.sources ?? {}; const model = tracker.currentWidget?.model; const localState = model?.sharedModel.awareness.getLocalState(); - if (!model || !localState || !localState['selected']?.value) { + if (!model || !localState || !localState['selected']?.value || !selected.parameters) { return; } @@ -522,6 +521,9 @@ export function addCommands( } }; + const selectedLayer = localState['selected'].value; + const selectedLayerId = Object.keys(selectedLayer)[0]; + // Open form and get user input const formValues = await new Promise(resolve => { const dialog = new FormDialog({ @@ -589,7 +591,7 @@ export function addCommands( const newSourceId = UUID.uuid4(); const sourceModel: IJGISSource = { type: 'GeoJSONSource', - name: inputLayer.name + ' Dissolved', + name: selected.name + ' Dissolved', parameters: { data: dissolvedGeoJSON } }; @@ -597,7 +599,7 @@ export function addCommands( type: 'VectorLayer', parameters: { source: newSourceId }, visible: true, - name: inputLayer.name + ' Dissolved' + name: selected.name + ' Dissolved' }; model.sharedModel.addSource(newSourceId, sourceModel); From 817c85bd7f4a6bc20dd9ad904cd69873fabf33e0 Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Mon, 17 Mar 2025 18:34:38 +0530 Subject: [PATCH 05/25] for convinience --- examples/world.jGIS | 48 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 38 insertions(+), 10 deletions(-) diff --git a/examples/world.jGIS b/examples/world.jGIS index 89b6c2e9d..8987187a0 100644 --- a/examples/world.jGIS +++ b/examples/world.jGIS @@ -1,10 +1,11 @@ { "layerTree": [ - "6e55cdae-35b0-4bff-9dd1-c8aa9563b2a6" + "6e55cdae-35b0-4bff-9dd1-c8aa9563b2a6", + "f80d0fa2-3e2b-4922-b7d5-fefd4b085259" ], "layers": { "6e55cdae-35b0-4bff-9dd1-c8aa9563b2a6": { - "name": "Custom GeoJSON Layer", + "name": "World", "parameters": { "color": { "fill-color": [ @@ -95,26 +96,53 @@ }, "type": "VectorLayer", "visible": true + }, + "f80d0fa2-3e2b-4922-b7d5-fefd4b085259": { + "name": "France", + "parameters": { + "color": { + "fill-color": "#ff0000", + "stroke-color": "#3399CC", + "stroke-line-cap": "round", + "stroke-line-join": "round", + "stroke-width": 1.25 + }, + "opacity": 1.0, + "source": "5970d6c9-26be-4cc6-84c9-16593dd3edfe", + "symbologyState": { + "renderType": "Single Symbol" + }, + "type": "line" + }, + "type": "VectorLayer", + "visible": true } }, "metadata": {}, "options": { "bearing": 0.0, "extent": [ - -35938860.79774074, - -17466155.24107265, - 4136155.887837734, - 18813049.299423713 + -19065470.770224877, + -18193969.787702393, + 21009545.91535361, + 20037508.342789244 ], - "latitude": 6.038467945870664, - "longitude": -142.8442794845441, + "latitude": 8.251719751227498, + "longitude": 8.731962081730105, "pitch": 0.0, "projection": "EPSG:3857", - "zoom": 2.100662339005199 + "zoom": 1.834471049984222 }, "sources": { + "5970d6c9-26be-4cc6-84c9-16593dd3edfe": { + "name": "France", + "parameters": { + "path": "https://geodata.ucdavis.edu/gadm/gadm4.1/json/gadm41_FRA_1.json" + }, + "type": "GeoJSONSource" + }, "b4287bea-e217-443c-b527-58f7559c824c": { - "name": "Custom GeoJSON Layer Source", + "name": "World", "parameters": { "path": "https://raw.githubusercontent.com/nvkelso/natural-earth-vector/master/geojson/ne_110m_admin_0_countries.geojson" }, From 5c906276dd27e5fb38345be5fdbacdc88bd4a3fd Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Mon, 17 Mar 2025 21:24:50 +0530 Subject: [PATCH 06/25] extend dialog --- packages/base/src/commands.ts | 3 +- .../objectform/dissolveProcessForm.tsx | 107 ++++++++++++++++++ 2 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 packages/base/src/formbuilder/objectform/dissolveProcessForm.tsx diff --git a/packages/base/src/commands.ts b/packages/base/src/commands.ts index 7129b92e2..34ea1df33 100644 --- a/packages/base/src/commands.ts +++ b/packages/base/src/commands.ts @@ -28,6 +28,7 @@ import { getGeoJSONDataFromLayerSource, downloadFile } from './tools'; import { IJGISLayer, IJGISSource } from '@jupytergis/schema'; import { UUID } from '@lumino/coreutils'; import { FormDialog } from './formbuilder/formdialog'; +import { DissolveFormDialog } from './formbuilder/objectform/dissolveProcessForm'; interface ICreateEntry { tracker: JupyterGISTracker; @@ -526,7 +527,7 @@ export function addCommands( // Open form and get user input const formValues = await new Promise(resolve => { - const dialog = new FormDialog({ + const dialog = new DissolveFormDialog({ title: 'Dissolve', schema: schema, model: model, diff --git a/packages/base/src/formbuilder/objectform/dissolveProcessForm.tsx b/packages/base/src/formbuilder/objectform/dissolveProcessForm.tsx new file mode 100644 index 000000000..a25819fd4 --- /dev/null +++ b/packages/base/src/formbuilder/objectform/dissolveProcessForm.tsx @@ -0,0 +1,107 @@ +import { Dialog } from '@jupyterlab/apputils'; +import { + IDict, + IJupyterGISClientState, + IJupyterGISModel +} from '@jupytergis/schema'; +import * as React from 'react'; +import { ObjectPropertiesForm } from '../formbuilder'; +import { focusInputField, removeStyleFromProperty } from '../utils'; + +export interface IDissolveFormDialogOptions { + schema: IDict; + sourceData: IDict; + title: string; + cancelButton: (() => void) | boolean; + syncData: (props: IDict) => void; + syncSelectedPropField?: ( + id: string | null, + value: any, + parentType: 'dialog' | 'panel' + ) => void; + model: IJupyterGISModel; +} + +export class DissolveFormDialog extends Dialog { + constructor(options: IDissolveFormDialogOptions) { + let cancelCallback: (() => void) | undefined = undefined; + if (options.cancelButton) { + cancelCallback = () => { + if (options.cancelButton !== true && options.cancelButton !== false) { + options.cancelButton(); + } + this.resolve(0); + }; + } + + const layers = options.model.sharedModel.layers ?? {}; + const layerOptions = Object.keys(layers).map(layerId => ({ + value: layerId, + label: layers[layerId].name + })); + + if (options.schema && options.schema.properties?.inputLayer) { + options.schema.properties.inputLayer.enum = layerOptions.map( + option => option.value + ); + options.schema.properties.inputLayer.enumNames = layerOptions.map( + option => option.label + ); + } + + const filePath = options.model.filePath; + const jgisModel = options.model; + const body = ( +
+ +
+ ); + + let lastSelectedPropFieldId: string | undefined = undefined; + + const onClientSharedStateChanged = ( + sender: IJupyterGISModel, + clients: Map + ): void => { + const remoteUser = jgisModel?.localState?.remoteUser; + if (remoteUser) { + const newState = clients.get(remoteUser); + + const id = newState?.selectedPropField?.id; + const value = newState?.selectedPropField?.value; + const parentType = newState?.selectedPropField?.parentType; + + if (parentType === 'dialog') { + lastSelectedPropFieldId = focusInputField( + `${filePath}::dialog`, + id, + value, + newState?.user?.color, + lastSelectedPropFieldId + ); + } + } else { + if (lastSelectedPropFieldId) { + removeStyleFromProperty( + `${filePath}::dialog`, + lastSelectedPropFieldId, + ['border-color', 'box-shadow'] + ); + lastSelectedPropFieldId = undefined; + } + } + }; + + jgisModel?.clientStateChanged.connect(onClientSharedStateChanged); + super({ title: options.title, body, buttons: [Dialog.cancelButton()] }); + this.addClass('jGIS-DissolveFormDialog'); + } +} From 86010f32a96dac03bd15f76e24548c5dc0bcece3 Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Mon, 17 Mar 2025 21:26:45 +0530 Subject: [PATCH 07/25] push it --- .../objectform/dissolveProcessForm.tsx | 199 ++++++++++-------- 1 file changed, 108 insertions(+), 91 deletions(-) diff --git a/packages/base/src/formbuilder/objectform/dissolveProcessForm.tsx b/packages/base/src/formbuilder/objectform/dissolveProcessForm.tsx index a25819fd4..b779fb683 100644 --- a/packages/base/src/formbuilder/objectform/dissolveProcessForm.tsx +++ b/packages/base/src/formbuilder/objectform/dissolveProcessForm.tsx @@ -1,107 +1,124 @@ -import { Dialog } from '@jupyterlab/apputils'; -import { - IDict, - IJupyterGISClientState, - IJupyterGISModel -} from '@jupytergis/schema'; -import * as React from 'react'; -import { ObjectPropertiesForm } from '../formbuilder'; -import { focusInputField, removeStyleFromProperty } from '../utils'; - -export interface IDissolveFormDialogOptions { +import { FormDialog } from '../formdialog'; +import { IDict, IJupyterGISModel, IGeoJSONSource } from '@jupytergis/schema'; +import { IChangeEvent } from '@rjsf/core'; +import { loadFile } from '../../tools'; + +interface IDissolveFormOptions { schema: IDict; sourceData: IDict; title: string; cancelButton: (() => void) | boolean; syncData: (props: IDict) => void; - syncSelectedPropField?: ( - id: string | null, - value: any, - parentType: 'dialog' | 'panel' - ) => void; model: IJupyterGISModel; } -export class DissolveFormDialog extends Dialog { - constructor(options: IDissolveFormDialogOptions) { - let cancelCallback: (() => void) | undefined = undefined; - if (options.cancelButton) { - cancelCallback = () => { - if (options.cancelButton !== true && options.cancelButton !== false) { - options.cancelButton(); - } - this.resolve(0); - }; +export class DissolveFormDialog extends FormDialog { + private model: IJupyterGISModel; + private schema: IDict; + private features: string[] = []; + + constructor(options: IDissolveFormOptions) { + super(options); + this.model = options.model; + this.schema = options.schema; + + console.log('DissolveFormDialog initialized with options:', options); + + this.fetchFieldNames(options.sourceData.inputLayer); + this.handleFormChange = this.handleFormChange.bind(this); + } + + /** + * Fetch field names from the selected layer and update the dropdown. + */ + private async fetchFieldNames(layerId: string) { + const layer = this.model.getLayer(layerId); + console.log('Layer:', layer); + + if (!layer?.parameters?.source) { + console.error(`⚠️ Layer ${layerId} has no associated source!`); + return; } - const layers = options.model.sharedModel.layers ?? {}; - const layerOptions = Object.keys(layers).map(layerId => ({ - value: layerId, - label: layers[layerId].name - })); - - if (options.schema && options.schema.properties?.inputLayer) { - options.schema.properties.inputLayer.enum = layerOptions.map( - option => option.value - ); - options.schema.properties.inputLayer.enumNames = layerOptions.map( - option => option.label - ); + const sourceId = layer.parameters.source; + console.log('🔍 Extracted sourceId:', sourceId); + + // Fetch the actual source + const source = this.model.getSource(sourceId); + if (!source || source.type !== 'GeoJSONSource') { + console.log('Invalid source:', source); + this.features = []; + return; + } + + const sourceData = source.parameters as IGeoJSONSource; + + if (!sourceData?.path) { + console.log('No path found in source data:', sourceData); + this.features = []; + return; } - const filePath = options.model.filePath; - const jgisModel = options.model; - const body = ( -
- -
- ); - - let lastSelectedPropFieldId: string | undefined = undefined; - - const onClientSharedStateChanged = ( - sender: IJupyterGISModel, - clients: Map - ): void => { - const remoteUser = jgisModel?.localState?.remoteUser; - if (remoteUser) { - const newState = clients.get(remoteUser); - - const id = newState?.selectedPropField?.id; - const value = newState?.selectedPropField?.value; - const parentType = newState?.selectedPropField?.parentType; - - if (parentType === 'dialog') { - lastSelectedPropFieldId = focusInputField( - `${filePath}::dialog`, - id, - value, - newState?.user?.color, - lastSelectedPropFieldId - ); - } - } else { - if (lastSelectedPropFieldId) { - removeStyleFromProperty( - `${filePath}::dialog`, - lastSelectedPropFieldId, - ['border-color', 'box-shadow'] - ); - lastSelectedPropFieldId = undefined; - } + try { + console.log('Loading file from path:', sourceData.path); + const jsonData = await loadFile({ + filepath: sourceData.path, + type: 'GeoJSONSource', + model: this.model + }); + + if (!jsonData?.features?.length) { + console.log('No features found in GeoJSON.'); + this.features = []; + return; } - }; - jgisModel?.clientStateChanged.connect(onClientSharedStateChanged); - super({ title: options.title, body, buttons: [Dialog.cancelButton()] }); - this.addClass('jGIS-DissolveFormDialog'); + this.features = Object.keys(jsonData.features[0].properties); + console.log('Extracted features:', this.features); + + this.updateSchema(); + } catch (error) { + console.error('Error loading GeoJSON file:', error); + } + } + + /** + * Handle form changes and update available fields. + */ + public handleFormChange(e: IChangeEvent) { + console.log('Form changed:', e.formData); + + if (e.formData.inputLayer) { + console.log('New inputLayer detected:', e.formData.inputLayer); + this.fetchFieldNames(e.formData.inputLayer); + } + } + + /** + * Updates the schema when new field names are fetched. + */ + private updateSchema() { + console.log('Updating schema with new field names:', this.features); + + if (this.schema.properties?.dissolveField) { + this.schema.properties.dissolveField.enum = this.features; + } + + this.refreshForm(); + } + + /** + * Refreshes the form. + */ + private refreshForm() { + console.log('Refreshing form...'); + + if (typeof (this as any).updateFormData === 'function') { + (this as any).updateFormData(); + } else { + console.warn('updateFormData method not found on FormDialog. Relaunching the dialog.'); + this.launch(); // Relaunching to update UI + + } } } From 948d9ae3a604ba7a649d612f8228cfdb1c8c0630 Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Tue, 18 Mar 2025 14:40:53 +0530 Subject: [PATCH 08/25] make it work with baseForm --- packages/base/src/formbuilder/formbuilder.tsx | 235 ------------------ packages/base/src/formbuilder/formdialog.tsx | 49 +--- packages/base/src/formbuilder/utils.ts | 63 ----- 3 files changed, 6 insertions(+), 341 deletions(-) delete mode 100644 packages/base/src/formbuilder/formbuilder.tsx delete mode 100644 packages/base/src/formbuilder/utils.ts diff --git a/packages/base/src/formbuilder/formbuilder.tsx b/packages/base/src/formbuilder/formbuilder.tsx deleted file mode 100644 index 476679d1e..000000000 --- a/packages/base/src/formbuilder/formbuilder.tsx +++ /dev/null @@ -1,235 +0,0 @@ -import { ISubmitEvent } from '@rjsf/core'; -import * as React from 'react'; -import { FormComponent } from '@jupyterlab/ui-components'; -import validatorAjv8 from '@rjsf/validator-ajv8'; -import { IDict } from '../types'; - -interface IStates { - internalData?: IDict; - schema?: IDict; -} -interface IProps { - parentType: 'dialog' | 'panel'; - sourceData: IDict | undefined; - filePath?: string; - syncData: (properties: IDict) => void; - syncSelectedField?: ( - id: string | null, - value: any, - parentType: 'panel' | 'dialog' - ) => void; - schema?: IDict; - cancel?: () => void; -} - -const WrappedFormComponent = (props: any): JSX.Element => { - const { fields, ...rest } = props; - return ( - - ); -}; - -export class ObjectPropertiesForm extends React.Component { - constructor(props: IProps) { - super(props); - this.state = { - internalData: { ...this.props.sourceData }, - schema: props.schema - }; - } - - setStateByKey = (key: string, value: any): void => { - const floatValue = parseFloat(value); - if (Number.isNaN(floatValue)) { - return; - } - this.setState( - old => ({ - ...old, - internalData: { ...old.internalData, [key]: floatValue } - }), - () => this.props.syncData({ [key]: floatValue }) - ); - }; - - componentDidUpdate(prevProps: IProps): void { - if (prevProps.sourceData !== this.props.sourceData) { - this.setState(old => ({ ...old, internalData: this.props.sourceData })); - } - - if (prevProps.schema !== this.props.schema) { - this.setState(old => ({ ...old, schema: this.props.schema })); - } - } - - buildForm(): JSX.Element[] { - if (!this.props.sourceData || !this.state.internalData) { - return []; - } - const inputs: JSX.Element[] = []; - - for (const [key, value] of Object.entries(this.props.sourceData)) { - let input: JSX.Element; - if (typeof value === 'string' || typeof value === 'number') { - input = ( -
- - this.setStateByKey(key, e.target.value)} - /> -
- ); - inputs.push(input); - } - } - return inputs; - } - - removeArrayButton(schema: IDict, uiSchema: IDict): void { - if (!schema || typeof schema !== 'object') { - return; - } - - if (!schema.properties || typeof schema.properties !== 'object') { - return; - } - - Object.entries(schema.properties as IDict).forEach(([k, v]) => { - if (v && typeof v === 'object') { - if (v['type'] === 'array') { - uiSchema[k] = { - 'ui:options': { - orderable: false, - removable: false, - addable: false - } - }; - } else if (v['type'] === 'object') { - uiSchema[k] = {}; - this.removeArrayButton(v, uiSchema[k]); - } - } - }); - - uiSchema['Color'] = { - 'ui:widget': 'color' - }; - } - - generateUiSchema(schema: IDict): IDict { - const uiSchema = { - additionalProperties: { - 'ui:label': false, - classNames: 'jGIS-hidden-field' - } - }; - this.removeArrayButton(schema, uiSchema); - return uiSchema; - } - - onFormSubmit = (e: ISubmitEvent): void => { - const internalData = { ...this.state.internalData }; - Object.entries(e.formData).forEach(([k, v]) => (internalData[k] = v)); - this.setState( - old => ({ - ...old, - internalData - }), - () => { - this.props.syncData(e.formData); - this.props.cancel && this.props.cancel(); - } - ); - }; - - render(): React.ReactNode { - if (this.props.schema) { - const schema = { ...this.props.schema, additionalProperties: true }; - - const submitRef = React.createRef(); - - return ( -
-
{ - if (e.key === 'Enter') { - e.preventDefault(); - submitRef.current?.click(); - } - }} - > - { - this.props.syncSelectedField - ? this.props.syncSelectedField( - id, - value, - this.props.parentType - ) - : null; - }} - // @ts-ignore - onBlur={(id, value) => { - this.props.syncSelectedField - ? this.props.syncSelectedField( - null, - value, - this.props.parentType - ) - : null; - }} - children={ -
- -
- {this.props.cancel ? ( - - ) : null} - - -
-
- ); - } else { - return
{this.buildForm()}
; - } - } -} diff --git a/packages/base/src/formbuilder/formdialog.tsx b/packages/base/src/formbuilder/formdialog.tsx index f62cd676f..42a5c59d1 100644 --- a/packages/base/src/formbuilder/formdialog.tsx +++ b/packages/base/src/formbuilder/formdialog.tsx @@ -1,12 +1,10 @@ import { IDict, - IJupyterGISClientState, IJupyterGISModel } from '@jupytergis/schema'; import { Dialog } from '@jupyterlab/apputils'; import * as React from 'react'; -import { ObjectPropertiesForm } from './formbuilder'; -import { focusInputField, removeStyleFromProperty } from './utils'; +import { BaseForm } from './objectform/baseform'; export interface IFormDialogOptions { schema: IDict; @@ -53,54 +51,19 @@ export class FormDialog extends Dialog { const jgisModel = options.model; const body = (
-
); - let lastSelectedPropFieldId: string | undefined = undefined; - - const onClientSharedStateChanged = ( - sender: IJupyterGISModel, - clients: Map - ): void => { - const remoteUser = jgisModel?.localState?.remoteUser; - if (remoteUser) { - const newState = clients.get(remoteUser); - - const id = newState?.selectedPropField?.id; - const value = newState?.selectedPropField?.value; - const parentType = newState?.selectedPropField?.parentType; - - if (parentType === 'dialog') { - lastSelectedPropFieldId = focusInputField( - `${filePath}::dialog`, - id, - value, - newState?.user?.color, - lastSelectedPropFieldId - ); - } - } else { - if (lastSelectedPropFieldId) { - removeStyleFromProperty( - `${filePath}::dialog`, - lastSelectedPropFieldId, - ['border-color', 'box-shadow'] - ); - lastSelectedPropFieldId = undefined; - } - } - }; - - jgisModel?.clientStateChanged.connect(onClientSharedStateChanged); super({ title: options.title, body, buttons: [Dialog.cancelButton()] }); this.addClass('jGIS-property-FormDialog'); } diff --git a/packages/base/src/formbuilder/utils.ts b/packages/base/src/formbuilder/utils.ts deleted file mode 100644 index 05ab4e975..000000000 --- a/packages/base/src/formbuilder/utils.ts +++ /dev/null @@ -1,63 +0,0 @@ -export function focusInputField( - filePath?: string, - fieldId?: string | null, - value?: any, - color?: string, - lastSelectedPropFieldId?: string -): string | undefined { - const propsToRemove = ['border-color', 'box-shadow']; - let newSelected: string | undefined; - if (!fieldId) { - if (lastSelectedPropFieldId) { - removeStyleFromProperty(filePath, lastSelectedPropFieldId, propsToRemove); - if (value) { - const el = getElementFromProperty(filePath, lastSelectedPropFieldId); - if (el?.tagName?.toLowerCase() === 'input') { - (el as HTMLInputElement).value = value; - } - } - newSelected = undefined; - } - } else { - if (fieldId !== lastSelectedPropFieldId) { - removeStyleFromProperty(filePath, lastSelectedPropFieldId, propsToRemove); - - const el = getElementFromProperty(filePath, fieldId); - if (el) { - el.style.borderColor = color ?? 'red'; - el.style.boxShadow = `inset 0 0 4px ${color ?? 'red'}`; - } - newSelected = fieldId; - } - } - return newSelected; -} - -export function getElementFromProperty( - filePath?: string | null, - prop?: string | null -): HTMLElement | undefined | null { - if (!filePath || !prop) { - return; - } - const parent = document.querySelector(`[data-path="${filePath}"]`); - - if (parent) { - const el = parent.querySelector(`[id$=${prop}]`); - return el as HTMLElement; - } -} - -export function removeStyleFromProperty( - filePath: string | null | undefined, - prop: string | null | undefined, - properties: string[] -): void { - if (!filePath || !prop || properties.length === 0) { - return; - } - const el = getElementFromProperty(filePath, prop); - if (el) { - properties.forEach(prop => el.style.removeProperty(prop)); - } -} From a508641f189d961e7c288b542d5c1d4fa6b87aed Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Tue, 18 Mar 2025 16:29:31 +0530 Subject: [PATCH 09/25] remove `SourceType` from baseform --- packages/base/src/commands.ts | 3 +++ packages/base/src/formbuilder/creationform.tsx | 1 - packages/base/src/formbuilder/editform.tsx | 1 - packages/base/src/formbuilder/formdialog.tsx | 6 +++--- packages/base/src/formbuilder/objectform/baseform.tsx | 7 +------ .../src/formbuilder/objectform/dissolveProcessForm.tsx | 8 ++++---- .../base/src/formbuilder/objectform/geojsonsource.ts | 9 +++++++-- .../base/src/formbuilder/objectform/geotiffsource.ts | 7 ++++++- .../base/src/formbuilder/objectform/pathbasedsource.ts | 9 +++++++-- 9 files changed, 31 insertions(+), 20 deletions(-) diff --git a/packages/base/src/commands.ts b/packages/base/src/commands.ts index 34ea1df33..a19771533 100644 --- a/packages/base/src/commands.ts +++ b/packages/base/src/commands.ts @@ -360,6 +360,7 @@ export function addCommands( bufferDistance: 10, projection: 'EPSG:4326' }, + formContext: 'create', cancelButton: false, syncData: (props: IDict) => { resolve(props); @@ -535,6 +536,7 @@ export function addCommands( inputLayer: selectedLayerId, dissolveField: fieldNames[0] // Default to the first field }, + formContext: 'create', cancelButton: false, syncData: (props: IDict) => { resolve(props); @@ -1340,6 +1342,7 @@ export function addCommands( schema: exportSchema, model, sourceData: { exportFormat: 'GeoJSON' }, + formContext: 'create', cancelButton: false, syncData: (props: IDict) => { resolve(props); diff --git a/packages/base/src/formbuilder/creationform.tsx b/packages/base/src/formbuilder/creationform.tsx index 646b13dcc..fe6e17118 100644 --- a/packages/base/src/formbuilder/creationform.tsx +++ b/packages/base/src/formbuilder/creationform.tsx @@ -211,7 +211,6 @@ export class CreationForm extends React.Component { formChangedSignal={this.sourceFormChangedSignal} formErrorSignal={this.props.formErrorSignal} dialogOptions={this.props.dialogOptions} - sourceType={this.props.sourceType} /> )} diff --git a/packages/base/src/formbuilder/editform.tsx b/packages/base/src/formbuilder/editform.tsx index c4ac66b81..f46c6116c 100644 --- a/packages/base/src/formbuilder/editform.tsx +++ b/packages/base/src/formbuilder/editform.tsx @@ -117,7 +117,6 @@ export class EditForm extends React.Component { this.syncObjectProperties(this.props.source, properties); }} formChangedSignal={this.sourceFormChangedSignal} - sourceType={source?.type || 'RasterSource'} /> )} diff --git a/packages/base/src/formbuilder/formdialog.tsx b/packages/base/src/formbuilder/formdialog.tsx index 42a5c59d1..f85cb5a8b 100644 --- a/packages/base/src/formbuilder/formdialog.tsx +++ b/packages/base/src/formbuilder/formdialog.tsx @@ -4,9 +4,10 @@ import { } from '@jupytergis/schema'; import { Dialog } from '@jupyterlab/apputils'; import * as React from 'react'; -import { BaseForm } from './objectform/baseform'; +import { BaseForm, IBaseFormProps } from './objectform/baseform'; -export interface IFormDialogOptions { +export interface IFormDialogOptions extends IBaseFormProps{ + formContext: 'update' | 'create'; schema: IDict; sourceData: IDict; title: string; @@ -56,7 +57,6 @@ export class FormDialog extends Dialog { filePath={filePath} model={jgisModel} sourceData={options.sourceData} - sourceType={options.sourceData.type} schema={options.schema} syncData={options.syncData} cancel={cancelCallback} diff --git a/packages/base/src/formbuilder/objectform/baseform.tsx b/packages/base/src/formbuilder/objectform/baseform.tsx index f993ff35a..561cc0b2c 100644 --- a/packages/base/src/formbuilder/objectform/baseform.tsx +++ b/packages/base/src/formbuilder/objectform/baseform.tsx @@ -1,5 +1,5 @@ import { Slider } from '@jupyter/react-components'; -import { IJupyterGISModel, SourceType } from '@jupytergis/schema'; +import { IJupyterGISModel } from '@jupytergis/schema'; import { Dialog } from '@jupyterlab/apputils'; import { FormComponent } from '@jupyterlab/ui-components'; import { Signal } from '@lumino/signaling'; @@ -72,11 +72,6 @@ export interface IBaseFormProps { * and other form-related parameters. */ dialogOptions?: any; - - /** - * Source type property - */ - sourceType: SourceType; } const WrappedFormComponent = (props: any): JSX.Element => { diff --git a/packages/base/src/formbuilder/objectform/dissolveProcessForm.tsx b/packages/base/src/formbuilder/objectform/dissolveProcessForm.tsx index b779fb683..3fd6071d3 100644 --- a/packages/base/src/formbuilder/objectform/dissolveProcessForm.tsx +++ b/packages/base/src/formbuilder/objectform/dissolveProcessForm.tsx @@ -2,8 +2,9 @@ import { FormDialog } from '../formdialog'; import { IDict, IJupyterGISModel, IGeoJSONSource } from '@jupytergis/schema'; import { IChangeEvent } from '@rjsf/core'; import { loadFile } from '../../tools'; +import { IBaseFormProps } from './baseform'; -interface IDissolveFormOptions { +interface IDissolveFormOptions extends IBaseFormProps{ schema: IDict; sourceData: IDict; title: string; @@ -36,8 +37,8 @@ export class DissolveFormDialog extends FormDialog { console.log('Layer:', layer); if (!layer?.parameters?.source) { - console.error(`⚠️ Layer ${layerId} has no associated source!`); - return; + console.error(`⚠️ Layer ${layerId} has no associated source!`); + return; } const sourceId = layer.parameters.source; @@ -118,7 +119,6 @@ export class DissolveFormDialog extends FormDialog { } else { console.warn('updateFormData method not found on FormDialog. Relaunching the dialog.'); this.launch(); // Relaunching to update UI - } } } diff --git a/packages/base/src/formbuilder/objectform/geojsonsource.ts b/packages/base/src/formbuilder/objectform/geojsonsource.ts index 81b3c66a4..cf8e5110e 100644 --- a/packages/base/src/formbuilder/objectform/geojsonsource.ts +++ b/packages/base/src/formbuilder/objectform/geojsonsource.ts @@ -1,4 +1,4 @@ -import { IDict } from '@jupytergis/schema'; +import { IDict, SourceType } from '@jupytergis/schema'; import { Ajv, ValidateFunction } from 'ajv'; import * as geojson from '@jupytergis/schema/src/schema/geojson.json'; @@ -6,13 +6,18 @@ import { IBaseFormProps } from './baseform'; import { PathBasedSourcePropertiesForm } from './pathbasedsource'; import { loadFile } from '../../tools'; +export interface IGeoJSONSourceFormProps extends IBaseFormProps { + sourceType: SourceType; +} + /** * The form to modify a GeoJSON source. */ export class GeoJSONSourcePropertiesForm extends PathBasedSourcePropertiesForm { private _validate: ValidateFunction; + props: IGeoJSONSourceFormProps; - constructor(props: IBaseFormProps) { + constructor(props: IGeoJSONSourceFormProps) { super(props); const ajv = new Ajv(); this._validate = ajv.compile(geojson); diff --git a/packages/base/src/formbuilder/objectform/geotiffsource.ts b/packages/base/src/formbuilder/objectform/geotiffsource.ts index 9662e782f..43a42d080 100644 --- a/packages/base/src/formbuilder/objectform/geotiffsource.ts +++ b/packages/base/src/formbuilder/objectform/geotiffsource.ts @@ -1,15 +1,20 @@ -import { IDict } from '@jupytergis/schema'; +import { IDict, SourceType } from '@jupytergis/schema'; import { showErrorMessage } from '@jupyterlab/apputils'; import { IChangeEvent, ISubmitEvent } from '@rjsf/core'; import { BaseForm, IBaseFormProps } from './baseform'; import { getMimeType } from '../../tools'; +export interface IGeoTiffSourceFormProps extends IBaseFormProps { + sourceType: SourceType; +} + /** * The form to modify a GeoTiff source. */ export class GeoTiffSourcePropertiesForm extends BaseForm { private _isSubmitted: boolean; + props: IGeoTiffSourceFormProps; constructor(props: IBaseFormProps) { super(props); diff --git a/packages/base/src/formbuilder/objectform/pathbasedsource.ts b/packages/base/src/formbuilder/objectform/pathbasedsource.ts index 6d22013b8..66d91dc4b 100644 --- a/packages/base/src/formbuilder/objectform/pathbasedsource.ts +++ b/packages/base/src/formbuilder/objectform/pathbasedsource.ts @@ -1,4 +1,4 @@ -import { IDict } from '@jupytergis/schema'; +import { IDict, SourceType } from '@jupytergis/schema'; import { showErrorMessage } from '@jupyterlab/apputils'; import { IChangeEvent, ISubmitEvent } from '@rjsf/core'; @@ -6,11 +6,16 @@ import { BaseForm, IBaseFormProps } from './baseform'; import { loadFile } from '../../tools'; import { FileSelectorWidget } from './fileselectorwidget'; +export interface IPathBasedSourceFormProps extends IBaseFormProps { + sourceType: SourceType; +} + /** * The form to modify a PathBasedSource source. */ export class PathBasedSourcePropertiesForm extends BaseForm { - constructor(props: IBaseFormProps) { + props: IPathBasedSourceFormProps; + constructor(props: IPathBasedSourceFormProps) { super(props); if (this.props.sourceType !== 'GeoJSONSource') { From 0c5cc955e6353c8df93980e812d9ca022fbe0d39 Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Tue, 18 Mar 2025 16:45:52 +0530 Subject: [PATCH 10/25] move `dialogOptions` to layerpropertiesform --- packages/base/src/formbuilder/creationform.tsx | 2 -- packages/base/src/formbuilder/objectform/baseform.tsx | 6 ------ packages/base/src/formbuilder/objectform/layerform.ts | 6 ++++++ 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/packages/base/src/formbuilder/creationform.tsx b/packages/base/src/formbuilder/creationform.tsx index fe6e17118..0f3ce5420 100644 --- a/packages/base/src/formbuilder/creationform.tsx +++ b/packages/base/src/formbuilder/creationform.tsx @@ -210,7 +210,6 @@ export class CreationForm extends React.Component { cancel={this.props.cancel} formChangedSignal={this.sourceFormChangedSignal} formErrorSignal={this.props.formErrorSignal} - dialogOptions={this.props.dialogOptions} /> )} @@ -231,7 +230,6 @@ export class CreationForm extends React.Component { cancel={this.props.cancel} sourceFormChangedSignal={this.sourceFormChangedSignal} formErrorSignal={this.props.formErrorSignal} - dialogOptions={this.props.dialogOptions} /> )} diff --git a/packages/base/src/formbuilder/objectform/baseform.tsx b/packages/base/src/formbuilder/objectform/baseform.tsx index 561cc0b2c..d943cb500 100644 --- a/packages/base/src/formbuilder/objectform/baseform.tsx +++ b/packages/base/src/formbuilder/objectform/baseform.tsx @@ -66,12 +66,6 @@ export interface IBaseFormProps { * extra errors or not. */ formErrorSignal?: Signal, boolean>; - - /** - * Configuration options for the dialog, including settings for layer data, source data, - * and other form-related parameters. - */ - dialogOptions?: any; } const WrappedFormComponent = (props: any): JSX.Element => { diff --git a/packages/base/src/formbuilder/objectform/layerform.ts b/packages/base/src/formbuilder/objectform/layerform.ts index 5b4acbf00..f6ca71f27 100644 --- a/packages/base/src/formbuilder/objectform/layerform.ts +++ b/packages/base/src/formbuilder/objectform/layerform.ts @@ -13,6 +13,12 @@ export interface ILayerProps extends IBaseFormProps { * The signal emitted when the attached source form has changed, if it exists */ sourceFormChangedSignal?: Signal>; + + /** + * Configuration options for the dialog, including settings for layer data, source data, + * and other form-related parameters. + */ + dialogOptions?: any; } export class LayerPropertiesForm extends BaseForm { From ded0122137f3982c9db006619c467f698ae960b0 Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Tue, 18 Mar 2025 19:10:11 +0530 Subject: [PATCH 11/25] using baseform directly --- .../objectform/dissolveProcessForm.tsx | 167 ++++++------------ 1 file changed, 50 insertions(+), 117 deletions(-) diff --git a/packages/base/src/formbuilder/objectform/dissolveProcessForm.tsx b/packages/base/src/formbuilder/objectform/dissolveProcessForm.tsx index 3fd6071d3..c95bff5ac 100644 --- a/packages/base/src/formbuilder/objectform/dissolveProcessForm.tsx +++ b/packages/base/src/formbuilder/objectform/dissolveProcessForm.tsx @@ -1,124 +1,57 @@ -import { FormDialog } from '../formdialog'; -import { IDict, IJupyterGISModel, IGeoJSONSource } from '@jupytergis/schema'; -import { IChangeEvent } from '@rjsf/core'; -import { loadFile } from '../../tools'; -import { IBaseFormProps } from './baseform'; - -interface IDissolveFormOptions extends IBaseFormProps{ - schema: IDict; - sourceData: IDict; - title: string; - cancelButton: (() => void) | boolean; - syncData: (props: IDict) => void; - model: IJupyterGISModel; +import { IDict } from '@jupytergis/schema'; +import { Dialog } from '@jupyterlab/apputils'; +import * as React from 'react'; +import { BaseForm, IBaseFormProps } from './baseform'; + +export interface IDissolveFormOptions extends IBaseFormProps { +title: string; +cancelButton: (() => void) | boolean; } -export class DissolveFormDialog extends FormDialog { - private model: IJupyterGISModel; - private schema: IDict; - private features: string[] = []; - - constructor(options: IDissolveFormOptions) { - super(options); - this.model = options.model; - this.schema = options.schema; - - console.log('DissolveFormDialog initialized with options:', options); - - this.fetchFieldNames(options.sourceData.inputLayer); - this.handleFormChange = this.handleFormChange.bind(this); - } - - /** - * Fetch field names from the selected layer and update the dropdown. - */ - private async fetchFieldNames(layerId: string) { - const layer = this.model.getLayer(layerId); - console.log('Layer:', layer); - - if (!layer?.parameters?.source) { - console.error(`⚠️ Layer ${layerId} has no associated source!`); - return; +export class DissolveFormDialog extends Dialog { +constructor(options: IDissolveFormOptions) { + let cancelCallback: (() => void) | undefined = undefined; + if (options.cancelButton) { + cancelCallback = () => { + if (options.cancelButton !== true && options.cancelButton !== false) { + options.cancelButton(); + } + this.resolve(0); + }; } - const sourceId = layer.parameters.source; - console.log('🔍 Extracted sourceId:', sourceId); - - // Fetch the actual source - const source = this.model.getSource(sourceId); - if (!source || source.type !== 'GeoJSONSource') { - console.log('Invalid source:', source); - this.features = []; - return; + const layers = options.model.sharedModel.layers ?? {}; + const layerOptions = Object.keys(layers).map(layerId => ({ + value: layerId, + label: layers[layerId].name + })); + + if (options.schema && options.schema.properties?.inputLayer) { + options.schema.properties.inputLayer.enum = layerOptions.map( + option => option.value + ); + options.schema.properties.inputLayer.enumNames = layerOptions.map( + option => option.label + ); } - const sourceData = source.parameters as IGeoJSONSource; - - if (!sourceData?.path) { - console.log('No path found in source data:', sourceData); - this.features = []; - return; - } - - try { - console.log('Loading file from path:', sourceData.path); - const jsonData = await loadFile({ - filepath: sourceData.path, - type: 'GeoJSONSource', - model: this.model - }); - - if (!jsonData?.features?.length) { - console.log('No features found in GeoJSON.'); - this.features = []; - return; - } - - this.features = Object.keys(jsonData.features[0].properties); - console.log('Extracted features:', this.features); - - this.updateSchema(); - } catch (error) { - console.error('Error loading GeoJSON file:', error); - } - } - - /** - * Handle form changes and update available fields. - */ - public handleFormChange(e: IChangeEvent) { - console.log('Form changed:', e.formData); - - if (e.formData.inputLayer) { - console.log('New inputLayer detected:', e.formData.inputLayer); - this.fetchFieldNames(e.formData.inputLayer); - } - } - - /** - * Updates the schema when new field names are fetched. - */ - private updateSchema() { - console.log('Updating schema with new field names:', this.features); - - if (this.schema.properties?.dissolveField) { - this.schema.properties.dissolveField.enum = this.features; - } - - this.refreshForm(); - } - - /** - * Refreshes the form. - */ - private refreshForm() { - console.log('Refreshing form...'); - - if (typeof (this as any).updateFormData === 'function') { - (this as any).updateFormData(); - } else { - console.warn('updateFormData method not found on FormDialog. Relaunching the dialog.'); - this.launch(); // Relaunching to update UI - } - } + const filePath = options.model.filePath; + const jgisModel = options.model; + const body = ( +
+ +
+ ); + + super({ title: options.title, body, buttons: [Dialog.cancelButton()] }); + this.addClass('jGIS-property-DissolveFormDialog'); +} } From 848ff48ce4f8adc9ce05aa0e61303c265c892850 Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Tue, 18 Mar 2025 21:41:05 +0530 Subject: [PATCH 12/25] form doesn't show up --- packages/base/src/commands.ts | 13 ++- packages/base/src/formbuilder/formdialog.tsx | 7 +- .../objectform/dissolveProcessForm.tsx | 110 ++++++++++-------- 3 files changed, 69 insertions(+), 61 deletions(-) diff --git a/packages/base/src/commands.ts b/packages/base/src/commands.ts index a19771533..cc94f0a0c 100644 --- a/packages/base/src/commands.ts +++ b/packages/base/src/commands.ts @@ -476,11 +476,15 @@ export function addCommands( const model = tracker.currentWidget?.model; const localState = model?.sharedModel.awareness.getLocalState(); - if (!model || !localState || !localState['selected']?.value || !selected.parameters) { + if ( + !model || + !localState || + !localState['selected']?.value || + !selected.parameters + ) { return; } - const sourceId = selected.parameters.source; const source = sources[sourceId]; @@ -528,7 +532,7 @@ export function addCommands( // Open form and get user input const formValues = await new Promise(resolve => { - const dialog = new DissolveFormDialog({ + new DissolveFormDialog({ title: 'Dissolve', schema: schema, model: model, @@ -540,11 +544,8 @@ export function addCommands( cancelButton: false, syncData: (props: IDict) => { resolve(props); - dialog.dispose(); } }); - - dialog.launch(); }); if (!formValues) { diff --git a/packages/base/src/formbuilder/formdialog.tsx b/packages/base/src/formbuilder/formdialog.tsx index f85cb5a8b..389b4bedb 100644 --- a/packages/base/src/formbuilder/formdialog.tsx +++ b/packages/base/src/formbuilder/formdialog.tsx @@ -1,12 +1,9 @@ -import { - IDict, - IJupyterGISModel -} from '@jupytergis/schema'; +import { IDict, IJupyterGISModel } from '@jupytergis/schema'; import { Dialog } from '@jupyterlab/apputils'; import * as React from 'react'; import { BaseForm, IBaseFormProps } from './objectform/baseform'; -export interface IFormDialogOptions extends IBaseFormProps{ +export interface IFormDialogOptions extends IBaseFormProps { formContext: 'update' | 'create'; schema: IDict; sourceData: IDict; diff --git a/packages/base/src/formbuilder/objectform/dissolveProcessForm.tsx b/packages/base/src/formbuilder/objectform/dissolveProcessForm.tsx index c95bff5ac..2387a0adb 100644 --- a/packages/base/src/formbuilder/objectform/dissolveProcessForm.tsx +++ b/packages/base/src/formbuilder/objectform/dissolveProcessForm.tsx @@ -1,57 +1,67 @@ -import { IDict } from '@jupytergis/schema'; -import { Dialog } from '@jupyterlab/apputils'; -import * as React from 'react'; -import { BaseForm, IBaseFormProps } from './baseform'; - -export interface IDissolveFormOptions extends IBaseFormProps { -title: string; -cancelButton: (() => void) | boolean; +import { BaseForm, IBaseFormProps } from './baseform'; // Import BaseForm +import { IDict, IJupyterGISModel, IGeoJSONSource } from '@jupytergis/schema'; +import { IChangeEvent } from '@rjsf/core'; +import { loadFile } from '../../tools'; + +interface IDissolveFormOptions extends IBaseFormProps { + schema: IDict; + sourceData: IDict; + title: string; + cancelButton: (() => void) | boolean; + syncData: (props: IDict) => void; + model: IJupyterGISModel; } -export class DissolveFormDialog extends Dialog { -constructor(options: IDissolveFormOptions) { - let cancelCallback: (() => void) | undefined = undefined; - if (options.cancelButton) { - cancelCallback = () => { - if (options.cancelButton !== true && options.cancelButton !== false) { - options.cancelButton(); - } - this.resolve(0); - }; +export class DissolveFormDialog extends BaseForm { + private model: IJupyterGISModel; + private schema: IDict; + private features: string[] = []; + + constructor(options: IDissolveFormOptions) { + super(options); + this.model = options.model; + this.schema = options.schema; + + console.log('DissolveFormDialog initialized with options:', options); + this.fetchFieldNames(options.sourceData.inputLayer); + } + + private async fetchFieldNames(layerId: string) { + const layer = this.model.getLayer(layerId); + if (!layer?.parameters?.source) {return;} + + const source = this.model.getSource(layer.parameters.source); + if (!source || source.type !== 'GeoJSONSource') {return;} + + const sourceData = source.parameters as IGeoJSONSource; + if (!sourceData?.path) {return;} + + try { + console.log('Loading GeoJSON:', sourceData.path); + const jsonData = await loadFile({ + filepath: sourceData.path, + type: 'GeoJSONSource', + model: this.model + }); + + if (!jsonData?.features?.length) {return;} + + this.features = Object.keys(jsonData.features[0].properties); + this.updateSchema(); + } catch (error) { + console.error('Error loading GeoJSON:', error); } + } - const layers = options.model.sharedModel.layers ?? {}; - const layerOptions = Object.keys(layers).map(layerId => ({ - value: layerId, - label: layers[layerId].name - })); - - if (options.schema && options.schema.properties?.inputLayer) { - options.schema.properties.inputLayer.enum = layerOptions.map( - option => option.value - ); - options.schema.properties.inputLayer.enumNames = layerOptions.map( - option => option.label - ); + public handleFormChange(e: IChangeEvent) { + if (e.formData.inputLayer) { + this.fetchFieldNames(e.formData.inputLayer); } + } - const filePath = options.model.filePath; - const jgisModel = options.model; - const body = ( -
- -
- ); - - super({ title: options.title, body, buttons: [Dialog.cancelButton()] }); - this.addClass('jGIS-property-DissolveFormDialog'); -} + private updateSchema() { + if (this.schema.properties?.dissolveField) { + this.schema.properties.dissolveField.enum = this.features; + } + } } From be9d04e42a09c989eef741d963b23c30b83b217b Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Wed, 19 Mar 2025 15:24:30 +0530 Subject: [PATCH 13/25] rename dissolveform --- packages/base/src/commands.ts | 4 ++-- .../base/src/formbuilder/objectform/dissolveProcessForm.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/base/src/commands.ts b/packages/base/src/commands.ts index cc94f0a0c..5aaa25ce9 100644 --- a/packages/base/src/commands.ts +++ b/packages/base/src/commands.ts @@ -28,7 +28,7 @@ import { getGeoJSONDataFromLayerSource, downloadFile } from './tools'; import { IJGISLayer, IJGISSource } from '@jupytergis/schema'; import { UUID } from '@lumino/coreutils'; import { FormDialog } from './formbuilder/formdialog'; -import { DissolveFormDialog } from './formbuilder/objectform/dissolveProcessForm'; +import { DissolveForm } from './formbuilder/objectform/dissolveProcessForm'; interface ICreateEntry { tracker: JupyterGISTracker; @@ -532,7 +532,7 @@ export function addCommands( // Open form and get user input const formValues = await new Promise(resolve => { - new DissolveFormDialog({ + new DissolveForm({ title: 'Dissolve', schema: schema, model: model, diff --git a/packages/base/src/formbuilder/objectform/dissolveProcessForm.tsx b/packages/base/src/formbuilder/objectform/dissolveProcessForm.tsx index 2387a0adb..b614ce450 100644 --- a/packages/base/src/formbuilder/objectform/dissolveProcessForm.tsx +++ b/packages/base/src/formbuilder/objectform/dissolveProcessForm.tsx @@ -12,7 +12,7 @@ interface IDissolveFormOptions extends IBaseFormProps { model: IJupyterGISModel; } -export class DissolveFormDialog extends BaseForm { +export class DissolveForm extends BaseForm { private model: IJupyterGISModel; private schema: IDict; private features: string[] = []; @@ -22,7 +22,7 @@ export class DissolveFormDialog extends BaseForm { this.model = options.model; this.schema = options.schema; - console.log('DissolveFormDialog initialized with options:', options); + console.log('DissolveForm initialized with options:', options); this.fetchFieldNames(options.sourceData.inputLayer); } From aff92d1bddd649e2fd6206055ba6c36a285be4a0 Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Wed, 19 Mar 2025 16:00:11 +0530 Subject: [PATCH 14/25] use `ISourceFormProps` from a common sourceform --- .../src/formbuilder/objectform/geojsonsource.ts | 12 ++++-------- .../src/formbuilder/objectform/geotiffsource.ts | 13 +++++-------- .../src/formbuilder/objectform/pathbasedsource.ts | 13 +++++-------- .../base/src/formbuilder/objectform/sourceform.ts | 6 ++++++ 4 files changed, 20 insertions(+), 24 deletions(-) create mode 100644 packages/base/src/formbuilder/objectform/sourceform.ts diff --git a/packages/base/src/formbuilder/objectform/geojsonsource.ts b/packages/base/src/formbuilder/objectform/geojsonsource.ts index cf8e5110e..7572c4e04 100644 --- a/packages/base/src/formbuilder/objectform/geojsonsource.ts +++ b/packages/base/src/formbuilder/objectform/geojsonsource.ts @@ -1,23 +1,19 @@ -import { IDict, SourceType } from '@jupytergis/schema'; +import { IDict } from '@jupytergis/schema'; import { Ajv, ValidateFunction } from 'ajv'; import * as geojson from '@jupytergis/schema/src/schema/geojson.json'; -import { IBaseFormProps } from './baseform'; import { PathBasedSourcePropertiesForm } from './pathbasedsource'; import { loadFile } from '../../tools'; - -export interface IGeoJSONSourceFormProps extends IBaseFormProps { - sourceType: SourceType; -} +import { ISourceFormProps } from './sourceform'; /** * The form to modify a GeoJSON source. */ export class GeoJSONSourcePropertiesForm extends PathBasedSourcePropertiesForm { private _validate: ValidateFunction; - props: IGeoJSONSourceFormProps; + props: ISourceFormProps; - constructor(props: IGeoJSONSourceFormProps) { + constructor(props: ISourceFormProps) { super(props); const ajv = new Ajv(); this._validate = ajv.compile(geojson); diff --git a/packages/base/src/formbuilder/objectform/geotiffsource.ts b/packages/base/src/formbuilder/objectform/geotiffsource.ts index 43a42d080..b56edc728 100644 --- a/packages/base/src/formbuilder/objectform/geotiffsource.ts +++ b/packages/base/src/formbuilder/objectform/geotiffsource.ts @@ -1,22 +1,19 @@ -import { IDict, SourceType } from '@jupytergis/schema'; +import { IDict } from '@jupytergis/schema'; import { showErrorMessage } from '@jupyterlab/apputils'; import { IChangeEvent, ISubmitEvent } from '@rjsf/core'; -import { BaseForm, IBaseFormProps } from './baseform'; +import { BaseForm } from './baseform'; import { getMimeType } from '../../tools'; - -export interface IGeoTiffSourceFormProps extends IBaseFormProps { - sourceType: SourceType; -} +import { ISourceFormProps } from './sourceform'; /** * The form to modify a GeoTiff source. */ export class GeoTiffSourcePropertiesForm extends BaseForm { private _isSubmitted: boolean; - props: IGeoTiffSourceFormProps; + props: ISourceFormProps; - constructor(props: IBaseFormProps) { + constructor(props: ISourceFormProps) { super(props); this._isSubmitted = false; diff --git a/packages/base/src/formbuilder/objectform/pathbasedsource.ts b/packages/base/src/formbuilder/objectform/pathbasedsource.ts index 66d91dc4b..fdfced161 100644 --- a/packages/base/src/formbuilder/objectform/pathbasedsource.ts +++ b/packages/base/src/formbuilder/objectform/pathbasedsource.ts @@ -1,21 +1,18 @@ -import { IDict, SourceType } from '@jupytergis/schema'; +import { IDict } from '@jupytergis/schema'; import { showErrorMessage } from '@jupyterlab/apputils'; import { IChangeEvent, ISubmitEvent } from '@rjsf/core'; -import { BaseForm, IBaseFormProps } from './baseform'; +import { BaseForm } from './baseform'; import { loadFile } from '../../tools'; import { FileSelectorWidget } from './fileselectorwidget'; - -export interface IPathBasedSourceFormProps extends IBaseFormProps { - sourceType: SourceType; -} +import { ISourceFormProps } from './sourceform'; /** * The form to modify a PathBasedSource source. */ export class PathBasedSourcePropertiesForm extends BaseForm { - props: IPathBasedSourceFormProps; - constructor(props: IPathBasedSourceFormProps) { + props: ISourceFormProps; + constructor(props: ISourceFormProps) { super(props); if (this.props.sourceType !== 'GeoJSONSource') { diff --git a/packages/base/src/formbuilder/objectform/sourceform.ts b/packages/base/src/formbuilder/objectform/sourceform.ts new file mode 100644 index 000000000..1c780c3b4 --- /dev/null +++ b/packages/base/src/formbuilder/objectform/sourceform.ts @@ -0,0 +1,6 @@ +import { IBaseFormProps } from './baseform'; +import { SourceType } from '@jupytergis/schema'; + +export interface ISourceFormProps extends IBaseFormProps { + sourceType: SourceType; +} From 8357322828682eb7b6eb57452c6764004ffa6eb2 Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Wed, 19 Mar 2025 16:21:25 +0530 Subject: [PATCH 15/25] sourcepropertiesform --- .../base/src/formbuilder/creationform.tsx | 3 ++ .../base/src/formbuilder/formselectors.ts | 6 ++-- .../formbuilder/objectform/geotiffsource.ts | 5 ++- .../formbuilder/objectform/pathbasedsource.ts | 5 ++- .../src/formbuilder/objectform/sourceform.ts | 36 +++++++++++++++++-- .../formbuilder/objectform/tilesourceform.ts | 4 +-- 6 files changed, 46 insertions(+), 13 deletions(-) diff --git a/packages/base/src/formbuilder/creationform.tsx b/packages/base/src/formbuilder/creationform.tsx index 0f3ce5420..646b13dcc 100644 --- a/packages/base/src/formbuilder/creationform.tsx +++ b/packages/base/src/formbuilder/creationform.tsx @@ -210,6 +210,8 @@ export class CreationForm extends React.Component { cancel={this.props.cancel} formChangedSignal={this.sourceFormChangedSignal} formErrorSignal={this.props.formErrorSignal} + dialogOptions={this.props.dialogOptions} + sourceType={this.props.sourceType} /> )} @@ -230,6 +232,7 @@ export class CreationForm extends React.Component { cancel={this.props.cancel} sourceFormChangedSignal={this.sourceFormChangedSignal} formErrorSignal={this.props.formErrorSignal} + dialogOptions={this.props.dialogOptions} /> )} diff --git a/packages/base/src/formbuilder/formselectors.ts b/packages/base/src/formbuilder/formselectors.ts index f943511e6..b55789128 100644 --- a/packages/base/src/formbuilder/formselectors.ts +++ b/packages/base/src/formbuilder/formselectors.ts @@ -1,5 +1,4 @@ import { LayerType, SourceType } from '@jupytergis/schema'; -import { BaseForm } from './objectform/baseform'; import { GeoJSONSourcePropertiesForm } from './objectform/geojsonsource'; import { GeoTiffSourcePropertiesForm } from './objectform/geotiffsource'; import { HeatmapLayerPropertiesForm } from './objectform/heatmapLayerForm'; @@ -9,6 +8,7 @@ import { PathBasedSourcePropertiesForm } from './objectform/pathbasedsource'; import { TileSourcePropertiesForm } from './objectform/tilesourceform'; import { VectorLayerPropertiesForm } from './objectform/vectorlayerform'; import { WebGlLayerPropertiesForm } from './objectform/webGlLayerForm'; +import { SourcePropertiesForm } from './objectform/sourceform'; export function getLayerTypeForm( layerType: LayerType @@ -34,8 +34,8 @@ export function getLayerTypeForm( return LayerForm; } -export function getSourceTypeForm(sourceType: SourceType): typeof BaseForm { - let SourceForm = BaseForm; +export function getSourceTypeForm(sourceType: SourceType): typeof SourcePropertiesForm { + let SourceForm = SourcePropertiesForm; switch (sourceType) { case 'GeoJSONSource': SourceForm = GeoJSONSourcePropertiesForm; diff --git a/packages/base/src/formbuilder/objectform/geotiffsource.ts b/packages/base/src/formbuilder/objectform/geotiffsource.ts index b56edc728..ef930607e 100644 --- a/packages/base/src/formbuilder/objectform/geotiffsource.ts +++ b/packages/base/src/formbuilder/objectform/geotiffsource.ts @@ -2,14 +2,13 @@ import { IDict } from '@jupytergis/schema'; import { showErrorMessage } from '@jupyterlab/apputils'; import { IChangeEvent, ISubmitEvent } from '@rjsf/core'; -import { BaseForm } from './baseform'; import { getMimeType } from '../../tools'; -import { ISourceFormProps } from './sourceform'; +import { ISourceFormProps, SourcePropertiesForm } from './sourceform'; /** * The form to modify a GeoTiff source. */ -export class GeoTiffSourcePropertiesForm extends BaseForm { +export class GeoTiffSourcePropertiesForm extends SourcePropertiesForm { private _isSubmitted: boolean; props: ISourceFormProps; diff --git a/packages/base/src/formbuilder/objectform/pathbasedsource.ts b/packages/base/src/formbuilder/objectform/pathbasedsource.ts index fdfced161..0d196228c 100644 --- a/packages/base/src/formbuilder/objectform/pathbasedsource.ts +++ b/packages/base/src/formbuilder/objectform/pathbasedsource.ts @@ -2,15 +2,14 @@ import { IDict } from '@jupytergis/schema'; import { showErrorMessage } from '@jupyterlab/apputils'; import { IChangeEvent, ISubmitEvent } from '@rjsf/core'; -import { BaseForm } from './baseform'; import { loadFile } from '../../tools'; import { FileSelectorWidget } from './fileselectorwidget'; -import { ISourceFormProps } from './sourceform'; +import { ISourceFormProps, SourcePropertiesForm } from './sourceform'; /** * The form to modify a PathBasedSource source. */ -export class PathBasedSourcePropertiesForm extends BaseForm { +export class PathBasedSourcePropertiesForm extends SourcePropertiesForm { props: ISourceFormProps; constructor(props: ISourceFormProps) { super(props); diff --git a/packages/base/src/formbuilder/objectform/sourceform.ts b/packages/base/src/formbuilder/objectform/sourceform.ts index 1c780c3b4..c7915ef9a 100644 --- a/packages/base/src/formbuilder/objectform/sourceform.ts +++ b/packages/base/src/formbuilder/objectform/sourceform.ts @@ -1,6 +1,38 @@ -import { IBaseFormProps } from './baseform'; -import { SourceType } from '@jupytergis/schema'; +import { IDict, SourceType } from '@jupytergis/schema'; +import { BaseForm, IBaseFormProps } from './baseform'; +import { Signal } from '@lumino/signaling'; +import { IChangeEvent } from '@rjsf/core'; export interface ISourceFormProps extends IBaseFormProps { + /** + * The source type for this form. + */ sourceType: SourceType; + + /** + * The signal emitted when the source form has changed. + */ + sourceFormChangedSignal?: Signal>; + + /** + * Configuration options for the dialog, including settings for source data and other parameters. + */ + dialogOptions?: any; +} + +export class SourcePropertiesForm extends BaseForm { + props: ISourceFormProps; + protected sourceFormChangedSignal: Signal> | undefined; + + constructor(props: ISourceFormProps) { + super(props); + this.sourceFormChangedSignal = props.sourceFormChangedSignal; + } + + protected onFormChange(e: IChangeEvent): void { + super.onFormChange(e); + if (this.props.dialogOptions) { + this.props.dialogOptions.sourceData = { ...e.formData }; + } + } } diff --git a/packages/base/src/formbuilder/objectform/tilesourceform.ts b/packages/base/src/formbuilder/objectform/tilesourceform.ts index 999380437..6fb1663b0 100644 --- a/packages/base/src/formbuilder/objectform/tilesourceform.ts +++ b/packages/base/src/formbuilder/objectform/tilesourceform.ts @@ -1,7 +1,7 @@ import { IDict } from '@jupytergis/schema'; -import { BaseForm } from './baseform'; +import { SourcePropertiesForm } from './sourceform'; -export class TileSourcePropertiesForm extends BaseForm { +export class TileSourcePropertiesForm extends SourcePropertiesForm { private _urlParameters: string[] = []; protected processSchema( From 9015772842f55509e4d78b9ae81d424e65875549 Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Wed, 19 Mar 2025 16:25:00 +0530 Subject: [PATCH 16/25] sourceform in editform too --- packages/base/src/formbuilder/editform.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/base/src/formbuilder/editform.tsx b/packages/base/src/formbuilder/editform.tsx index f46c6116c..d91ee4b08 100644 --- a/packages/base/src/formbuilder/editform.tsx +++ b/packages/base/src/formbuilder/editform.tsx @@ -10,7 +10,7 @@ import * as React from 'react'; import { deepCopy } from '../tools'; import { getLayerTypeForm, getSourceTypeForm } from './formselectors'; import { LayerPropertiesForm } from './objectform/layerform'; -import { BaseForm } from './objectform/baseform'; +import { SourcePropertiesForm } from './objectform/sourceform'; export interface IEditFormProps { /** @@ -65,7 +65,7 @@ export class EditForm extends React.Component { } let sourceSchema: IDict | undefined = undefined; - let SourceForm: typeof BaseForm | undefined = undefined; + let SourceForm: typeof SourcePropertiesForm | undefined = undefined; let sourceData: IDict | undefined = undefined; let source: IJGISSource | undefined = undefined; if (this.props.source) { @@ -117,6 +117,7 @@ export class EditForm extends React.Component { this.syncObjectProperties(this.props.source, properties); }} formChangedSignal={this.sourceFormChangedSignal} + sourceType={source?.type || 'RasterSource'} /> )} From d838e06835df49c8d54c41cb8a64cb2b4be8515f Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Wed, 19 Mar 2025 16:37:47 +0530 Subject: [PATCH 17/25] `creationformdialog` -> `layerCreationFormdialog` --- packages/base/src/commands.ts | 4 ++-- packages/base/src/dialogs/layerBrowserDialog.tsx | 2 +- .../dialogs/{formdialog.tsx => layerCreationFormdialog.tsx} | 2 +- .../base/src/formbuilder/objectform/fileselectorwidget.tsx | 6 +++--- packages/base/src/index.ts | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) rename packages/base/src/dialogs/{formdialog.tsx => layerCreationFormdialog.tsx} (98%) diff --git a/packages/base/src/commands.ts b/packages/base/src/commands.ts index 5aaa25ce9..e64086eae 100644 --- a/packages/base/src/commands.ts +++ b/packages/base/src/commands.ts @@ -17,7 +17,7 @@ import { ITranslator } from '@jupyterlab/translation'; import { CommandRegistry } from '@lumino/commands'; import { ReadonlyPartialJSONObject } from '@lumino/coreutils'; import { CommandIDs, icons } from './constants'; -import { CreationFormDialog } from './dialogs/formdialog'; +import { LayerCreationFormDialog } from './dialogs/layerCreationFormdialog'; import { LayerBrowserWidget } from './dialogs/layerBrowserDialog'; import { SymbologyWidget } from './dialogs/symbology/symbologyDialog'; import keybindings from './keybindings.json'; @@ -1437,7 +1437,7 @@ namespace Private { return; } - const dialog = new CreationFormDialog({ + const dialog = new LayerCreationFormDialog({ model: current.model, title, createLayer, diff --git a/packages/base/src/dialogs/layerBrowserDialog.tsx b/packages/base/src/dialogs/layerBrowserDialog.tsx index 882780bff..a55c0d028 100644 --- a/packages/base/src/dialogs/layerBrowserDialog.tsx +++ b/packages/base/src/dialogs/layerBrowserDialog.tsx @@ -15,7 +15,7 @@ import { Signal } from '@lumino/signaling'; import React, { ChangeEvent, MouseEvent, useEffect, useState } from 'react'; import CUSTOM_RASTER_IMAGE from '../../rasterlayer_gallery/custom_raster.png'; -import { CreationFormWrapper } from './formdialog'; +import { CreationFormWrapper } from './layerCreationFormdialog'; interface ILayerBrowserDialogProps { model: IJupyterGISModel; diff --git a/packages/base/src/dialogs/formdialog.tsx b/packages/base/src/dialogs/layerCreationFormdialog.tsx similarity index 98% rename from packages/base/src/dialogs/formdialog.tsx rename to packages/base/src/dialogs/layerCreationFormdialog.tsx index 3815472eb..a86655ce6 100644 --- a/packages/base/src/dialogs/formdialog.tsx +++ b/packages/base/src/dialogs/layerCreationFormdialog.tsx @@ -67,7 +67,7 @@ export const CreationFormWrapper = (props: ICreationFormWrapperProps) => { /** * Form for creating a source, a layer or both at the same time */ -export class CreationFormDialog extends Dialog { +export class LayerCreationFormDialog extends Dialog { constructor(options: ICreationFormDialogOptions) { const cancelCallback = () => { this.resolve(0); diff --git a/packages/base/src/formbuilder/objectform/fileselectorwidget.tsx b/packages/base/src/formbuilder/objectform/fileselectorwidget.tsx index f43af6be6..d795ef776 100644 --- a/packages/base/src/formbuilder/objectform/fileselectorwidget.tsx +++ b/packages/base/src/formbuilder/objectform/fileselectorwidget.tsx @@ -1,7 +1,7 @@ import React, { useState, useEffect, useRef } from 'react'; import { FileDialog } from '@jupyterlab/filebrowser'; import { Dialog } from '@jupyterlab/apputils'; -import { CreationFormDialog } from '../../dialogs/formdialog'; +import { LayerCreationFormDialog } from '../../dialogs/layerCreationFormdialog'; import { PathExt } from '@jupyterlab/coreutils'; export const FileSelectorWidget = (props: any) => { @@ -81,14 +81,14 @@ export const FileSelectorWidget = (props: any) => { }; } - const formDialog = new CreationFormDialog({ + const formDialog = new LayerCreationFormDialog({ ...formOptions.dialogOptions }); await formDialog.launch(); } } else { if (dialogElement) { - const formDialog = new CreationFormDialog({ + const formDialog = new LayerCreationFormDialog({ ...formOptions.dialogOptions }); await formDialog.launch(); diff --git a/packages/base/src/index.ts b/packages/base/src/index.ts index ce0e8029b..79aa78ee9 100644 --- a/packages/base/src/index.ts +++ b/packages/base/src/index.ts @@ -1,7 +1,7 @@ export * from './classificationModes'; export * from './commands'; export * from './constants'; -export * from './dialogs/formdialog'; +export * from './dialogs/layerCreationFormdialog'; export * from './formbuilder/objectform/baseform'; export * from './icons'; export * from './mainview'; From eb1c442ba53846784ff0d2402d4d64a40e000e67 Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Wed, 19 Mar 2025 16:48:58 +0530 Subject: [PATCH 18/25] `ProcessingFormDialog` --- packages/base/src/commands.ts | 9 +++++---- .../formdialog.tsx => dialogs/ProcessingFormDialog.tsx} | 8 ++++---- packages/base/src/dialogs/layerBrowserDialog.tsx | 2 +- .../src/formbuilder/objectform/fileselectorwidget.tsx | 2 +- packages/base/src/index.ts | 2 +- 5 files changed, 12 insertions(+), 11 deletions(-) rename packages/base/src/{formbuilder/formdialog.tsx => dialogs/ProcessingFormDialog.tsx} (87%) diff --git a/packages/base/src/commands.ts b/packages/base/src/commands.ts index e64086eae..ab18126ff 100644 --- a/packages/base/src/commands.ts +++ b/packages/base/src/commands.ts @@ -17,7 +17,7 @@ import { ITranslator } from '@jupyterlab/translation'; import { CommandRegistry } from '@lumino/commands'; import { ReadonlyPartialJSONObject } from '@lumino/coreutils'; import { CommandIDs, icons } from './constants'; -import { LayerCreationFormDialog } from './dialogs/layerCreationFormdialog'; +import { LayerCreationFormDialog } from './dialogs/layerCreationFormDialog'; import { LayerBrowserWidget } from './dialogs/layerBrowserDialog'; import { SymbologyWidget } from './dialogs/symbology/symbologyDialog'; import keybindings from './keybindings.json'; @@ -27,9 +27,10 @@ import { getGdal } from './gdal'; import { getGeoJSONDataFromLayerSource, downloadFile } from './tools'; import { IJGISLayer, IJGISSource } from '@jupytergis/schema'; import { UUID } from '@lumino/coreutils'; -import { FormDialog } from './formbuilder/formdialog'; +import { ProcessingFormDialog } from './dialogs/ProcessingFormDialog'; import { DissolveForm } from './formbuilder/objectform/dissolveProcessForm'; + interface ICreateEntry { tracker: JupyterGISTracker; formSchemaRegistry: IJGISFormSchemaRegistry; @@ -351,7 +352,7 @@ export function addCommands( // Open form and get user input const formValues = await new Promise(resolve => { - const dialog = new FormDialog({ + const dialog = new ProcessingFormDialog({ title: 'Buffer', schema: schema, model: model, @@ -1338,7 +1339,7 @@ export function addCommands( }; const formValues = await new Promise(resolve => { - const dialog = new FormDialog({ + const dialog = new ProcessingFormDialog({ title: 'Download GeoJSON', schema: exportSchema, model, diff --git a/packages/base/src/formbuilder/formdialog.tsx b/packages/base/src/dialogs/ProcessingFormDialog.tsx similarity index 87% rename from packages/base/src/formbuilder/formdialog.tsx rename to packages/base/src/dialogs/ProcessingFormDialog.tsx index 389b4bedb..0854b2a3f 100644 --- a/packages/base/src/formbuilder/formdialog.tsx +++ b/packages/base/src/dialogs/ProcessingFormDialog.tsx @@ -1,9 +1,9 @@ import { IDict, IJupyterGISModel } from '@jupytergis/schema'; import { Dialog } from '@jupyterlab/apputils'; import * as React from 'react'; -import { BaseForm, IBaseFormProps } from './objectform/baseform'; +import { BaseForm, IBaseFormProps } from '../formbuilder/objectform/baseform'; -export interface IFormDialogOptions extends IBaseFormProps { +export interface IProcessingFormDialogOptions extends IBaseFormProps { formContext: 'update' | 'create'; schema: IDict; sourceData: IDict; @@ -18,8 +18,8 @@ export interface IFormDialogOptions extends IBaseFormProps { model: IJupyterGISModel; } -export class FormDialog extends Dialog { - constructor(options: IFormDialogOptions) { +export class ProcessingFormDialog extends Dialog { + constructor(options: IProcessingFormDialogOptions) { let cancelCallback: (() => void) | undefined = undefined; if (options.cancelButton) { cancelCallback = () => { diff --git a/packages/base/src/dialogs/layerBrowserDialog.tsx b/packages/base/src/dialogs/layerBrowserDialog.tsx index a55c0d028..a932d9258 100644 --- a/packages/base/src/dialogs/layerBrowserDialog.tsx +++ b/packages/base/src/dialogs/layerBrowserDialog.tsx @@ -15,7 +15,7 @@ import { Signal } from '@lumino/signaling'; import React, { ChangeEvent, MouseEvent, useEffect, useState } from 'react'; import CUSTOM_RASTER_IMAGE from '../../rasterlayer_gallery/custom_raster.png'; -import { CreationFormWrapper } from './layerCreationFormdialog'; +import { CreationFormWrapper } from './layerCreationFormDialog'; interface ILayerBrowserDialogProps { model: IJupyterGISModel; diff --git a/packages/base/src/formbuilder/objectform/fileselectorwidget.tsx b/packages/base/src/formbuilder/objectform/fileselectorwidget.tsx index d795ef776..b2d119ad2 100644 --- a/packages/base/src/formbuilder/objectform/fileselectorwidget.tsx +++ b/packages/base/src/formbuilder/objectform/fileselectorwidget.tsx @@ -1,7 +1,7 @@ import React, { useState, useEffect, useRef } from 'react'; import { FileDialog } from '@jupyterlab/filebrowser'; import { Dialog } from '@jupyterlab/apputils'; -import { LayerCreationFormDialog } from '../../dialogs/layerCreationFormdialog'; +import { LayerCreationFormDialog } from '../../dialogs/layerCreationFormDialog'; import { PathExt } from '@jupyterlab/coreutils'; export const FileSelectorWidget = (props: any) => { diff --git a/packages/base/src/index.ts b/packages/base/src/index.ts index 79aa78ee9..714b6d5b1 100644 --- a/packages/base/src/index.ts +++ b/packages/base/src/index.ts @@ -1,7 +1,7 @@ export * from './classificationModes'; export * from './commands'; export * from './constants'; -export * from './dialogs/layerCreationFormdialog'; +export * from './dialogs/layerCreationFormDialog'; export * from './formbuilder/objectform/baseform'; export * from './icons'; export * from './mainview'; From 1f0e651521e30f1d51149815d1d0f37bc0ba95c9 Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Wed, 19 Mar 2025 17:05:04 +0530 Subject: [PATCH 19/25] fix filename --- packages/base/src/commands.ts | 3 ++- packages/base/src/dialogs/layerBrowserDialog.tsx | 2 +- ...ayerCreationFormdialog.tsx => layersCreationFormDialog.tsx} | 1 + .../base/src/formbuilder/objectform/fileselectorwidget.tsx | 2 +- packages/base/src/index.ts | 2 +- 5 files changed, 6 insertions(+), 4 deletions(-) rename packages/base/src/dialogs/{layerCreationFormdialog.tsx => layersCreationFormDialog.tsx} (99%) diff --git a/packages/base/src/commands.ts b/packages/base/src/commands.ts index ab18126ff..be8fa0012 100644 --- a/packages/base/src/commands.ts +++ b/packages/base/src/commands.ts @@ -17,7 +17,7 @@ import { ITranslator } from '@jupyterlab/translation'; import { CommandRegistry } from '@lumino/commands'; import { ReadonlyPartialJSONObject } from '@lumino/coreutils'; import { CommandIDs, icons } from './constants'; -import { LayerCreationFormDialog } from './dialogs/layerCreationFormDialog'; +import { LayerCreationFormDialog } from './dialogs/layersCreationFormDialog'; import { LayerBrowserWidget } from './dialogs/layerBrowserDialog'; import { SymbologyWidget } from './dialogs/symbology/symbologyDialog'; import keybindings from './keybindings.json'; @@ -31,6 +31,7 @@ import { ProcessingFormDialog } from './dialogs/ProcessingFormDialog'; import { DissolveForm } from './formbuilder/objectform/dissolveProcessForm'; + interface ICreateEntry { tracker: JupyterGISTracker; formSchemaRegistry: IJGISFormSchemaRegistry; diff --git a/packages/base/src/dialogs/layerBrowserDialog.tsx b/packages/base/src/dialogs/layerBrowserDialog.tsx index a932d9258..efd43598b 100644 --- a/packages/base/src/dialogs/layerBrowserDialog.tsx +++ b/packages/base/src/dialogs/layerBrowserDialog.tsx @@ -15,7 +15,7 @@ import { Signal } from '@lumino/signaling'; import React, { ChangeEvent, MouseEvent, useEffect, useState } from 'react'; import CUSTOM_RASTER_IMAGE from '../../rasterlayer_gallery/custom_raster.png'; -import { CreationFormWrapper } from './layerCreationFormDialog'; +import { CreationFormWrapper } from './layersCreationFormDialog'; interface ILayerBrowserDialogProps { model: IJupyterGISModel; diff --git a/packages/base/src/dialogs/layerCreationFormdialog.tsx b/packages/base/src/dialogs/layersCreationFormDialog.tsx similarity index 99% rename from packages/base/src/dialogs/layerCreationFormdialog.tsx rename to packages/base/src/dialogs/layersCreationFormDialog.tsx index a86655ce6..1dcd3cbe7 100644 --- a/packages/base/src/dialogs/layerCreationFormdialog.tsx +++ b/packages/base/src/dialogs/layersCreationFormDialog.tsx @@ -25,6 +25,7 @@ export interface ICreationFormWrapperProps extends ICreationFormProps { dialogOptions?: any; } + export interface ICreationFormDialogOptions extends ICreationFormProps { title: string; } diff --git a/packages/base/src/formbuilder/objectform/fileselectorwidget.tsx b/packages/base/src/formbuilder/objectform/fileselectorwidget.tsx index b2d119ad2..dccd92c26 100644 --- a/packages/base/src/formbuilder/objectform/fileselectorwidget.tsx +++ b/packages/base/src/formbuilder/objectform/fileselectorwidget.tsx @@ -1,7 +1,7 @@ import React, { useState, useEffect, useRef } from 'react'; import { FileDialog } from '@jupyterlab/filebrowser'; import { Dialog } from '@jupyterlab/apputils'; -import { LayerCreationFormDialog } from '../../dialogs/layerCreationFormDialog'; +import { LayerCreationFormDialog } from '../../dialogs/layersCreationFormDialog'; import { PathExt } from '@jupyterlab/coreutils'; export const FileSelectorWidget = (props: any) => { diff --git a/packages/base/src/index.ts b/packages/base/src/index.ts index 714b6d5b1..31dc3359a 100644 --- a/packages/base/src/index.ts +++ b/packages/base/src/index.ts @@ -1,7 +1,7 @@ export * from './classificationModes'; export * from './commands'; export * from './constants'; -export * from './dialogs/layerCreationFormDialog'; +export * from './dialogs/layersCreationFormDialog'; export * from './formbuilder/objectform/baseform'; export * from './icons'; export * from './mainview'; From d5139bb3281c5d8272d3b4d76de62e4a7aa605d6 Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Wed, 19 Mar 2025 17:06:08 +0530 Subject: [PATCH 20/25] fix filename --- packages/base/src/commands.ts | 4 +--- packages/base/src/dialogs/layerBrowserDialog.tsx | 2 +- ...yersCreationFormDialog.tsx => layerCreationFormDialog.tsx} | 0 .../base/src/formbuilder/objectform/fileselectorwidget.tsx | 2 +- packages/base/src/index.ts | 2 +- 5 files changed, 4 insertions(+), 6 deletions(-) rename packages/base/src/dialogs/{layersCreationFormDialog.tsx => layerCreationFormDialog.tsx} (100%) diff --git a/packages/base/src/commands.ts b/packages/base/src/commands.ts index be8fa0012..b183148c8 100644 --- a/packages/base/src/commands.ts +++ b/packages/base/src/commands.ts @@ -17,7 +17,7 @@ import { ITranslator } from '@jupyterlab/translation'; import { CommandRegistry } from '@lumino/commands'; import { ReadonlyPartialJSONObject } from '@lumino/coreutils'; import { CommandIDs, icons } from './constants'; -import { LayerCreationFormDialog } from './dialogs/layersCreationFormDialog'; +import { LayerCreationFormDialog } from './dialogs/layerCreationFormDialog'; import { LayerBrowserWidget } from './dialogs/layerBrowserDialog'; import { SymbologyWidget } from './dialogs/symbology/symbologyDialog'; import keybindings from './keybindings.json'; @@ -30,8 +30,6 @@ import { UUID } from '@lumino/coreutils'; import { ProcessingFormDialog } from './dialogs/ProcessingFormDialog'; import { DissolveForm } from './formbuilder/objectform/dissolveProcessForm'; - - interface ICreateEntry { tracker: JupyterGISTracker; formSchemaRegistry: IJGISFormSchemaRegistry; diff --git a/packages/base/src/dialogs/layerBrowserDialog.tsx b/packages/base/src/dialogs/layerBrowserDialog.tsx index efd43598b..a932d9258 100644 --- a/packages/base/src/dialogs/layerBrowserDialog.tsx +++ b/packages/base/src/dialogs/layerBrowserDialog.tsx @@ -15,7 +15,7 @@ import { Signal } from '@lumino/signaling'; import React, { ChangeEvent, MouseEvent, useEffect, useState } from 'react'; import CUSTOM_RASTER_IMAGE from '../../rasterlayer_gallery/custom_raster.png'; -import { CreationFormWrapper } from './layersCreationFormDialog'; +import { CreationFormWrapper } from './layerCreationFormDialog'; interface ILayerBrowserDialogProps { model: IJupyterGISModel; diff --git a/packages/base/src/dialogs/layersCreationFormDialog.tsx b/packages/base/src/dialogs/layerCreationFormDialog.tsx similarity index 100% rename from packages/base/src/dialogs/layersCreationFormDialog.tsx rename to packages/base/src/dialogs/layerCreationFormDialog.tsx diff --git a/packages/base/src/formbuilder/objectform/fileselectorwidget.tsx b/packages/base/src/formbuilder/objectform/fileselectorwidget.tsx index dccd92c26..b2d119ad2 100644 --- a/packages/base/src/formbuilder/objectform/fileselectorwidget.tsx +++ b/packages/base/src/formbuilder/objectform/fileselectorwidget.tsx @@ -1,7 +1,7 @@ import React, { useState, useEffect, useRef } from 'react'; import { FileDialog } from '@jupyterlab/filebrowser'; import { Dialog } from '@jupyterlab/apputils'; -import { LayerCreationFormDialog } from '../../dialogs/layersCreationFormDialog'; +import { LayerCreationFormDialog } from '../../dialogs/layerCreationFormDialog'; import { PathExt } from '@jupyterlab/coreutils'; export const FileSelectorWidget = (props: any) => { diff --git a/packages/base/src/index.ts b/packages/base/src/index.ts index 31dc3359a..714b6d5b1 100644 --- a/packages/base/src/index.ts +++ b/packages/base/src/index.ts @@ -1,7 +1,7 @@ export * from './classificationModes'; export * from './commands'; export * from './constants'; -export * from './dialogs/layersCreationFormDialog'; +export * from './dialogs/layerCreationFormDialog'; export * from './formbuilder/objectform/baseform'; export * from './icons'; export * from './mainview'; From 587be4212dea2bfb987961abacd8e48b9e1e6ce7 Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Wed, 19 Mar 2025 18:28:16 +0530 Subject: [PATCH 21/25] dynamic form selection --- packages/base/src/commands.ts | 7 ++++-- .../base/src/dialogs/ProcessingFormDialog.tsx | 25 ++++++++++++++++--- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/packages/base/src/commands.ts b/packages/base/src/commands.ts index b183148c8..e194a646c 100644 --- a/packages/base/src/commands.ts +++ b/packages/base/src/commands.ts @@ -17,6 +17,7 @@ import { ITranslator } from '@jupyterlab/translation'; import { CommandRegistry } from '@lumino/commands'; import { ReadonlyPartialJSONObject } from '@lumino/coreutils'; import { CommandIDs, icons } from './constants'; +// @ts-ignore import { LayerCreationFormDialog } from './dialogs/layerCreationFormDialog'; import { LayerBrowserWidget } from './dialogs/layerBrowserDialog'; import { SymbologyWidget } from './dialogs/symbology/symbologyDialog'; @@ -28,7 +29,6 @@ import { getGeoJSONDataFromLayerSource, downloadFile } from './tools'; import { IJGISLayer, IJGISSource } from '@jupytergis/schema'; import { UUID } from '@lumino/coreutils'; import { ProcessingFormDialog } from './dialogs/ProcessingFormDialog'; -import { DissolveForm } from './formbuilder/objectform/dissolveProcessForm'; interface ICreateEntry { tracker: JupyterGISTracker; @@ -362,6 +362,7 @@ export function addCommands( }, formContext: 'create', cancelButton: false, + processingType: 'buffer', syncData: (props: IDict) => { resolve(props); dialog.dispose(); @@ -532,7 +533,7 @@ export function addCommands( // Open form and get user input const formValues = await new Promise(resolve => { - new DissolveForm({ + new ProcessingFormDialog({ title: 'Dissolve', schema: schema, model: model, @@ -542,6 +543,7 @@ export function addCommands( }, formContext: 'create', cancelButton: false, + processingType: 'dissolve', syncData: (props: IDict) => { resolve(props); } @@ -1345,6 +1347,7 @@ export function addCommands( sourceData: { exportFormat: 'GeoJSON' }, formContext: 'create', cancelButton: false, + processingType: 'export', syncData: (props: IDict) => { resolve(props); dialog.dispose(); diff --git a/packages/base/src/dialogs/ProcessingFormDialog.tsx b/packages/base/src/dialogs/ProcessingFormDialog.tsx index 0854b2a3f..bf0b04c3c 100644 --- a/packages/base/src/dialogs/ProcessingFormDialog.tsx +++ b/packages/base/src/dialogs/ProcessingFormDialog.tsx @@ -2,6 +2,7 @@ import { IDict, IJupyterGISModel } from '@jupytergis/schema'; import { Dialog } from '@jupyterlab/apputils'; import * as React from 'react'; import { BaseForm, IBaseFormProps } from '../formbuilder/objectform/baseform'; +import { DissolveForm } from '../formbuilder/objectform/dissolveProcessForm'; export interface IProcessingFormDialogOptions extends IBaseFormProps { formContext: 'update' | 'create'; @@ -16,6 +17,7 @@ export interface IProcessingFormDialogOptions extends IBaseFormProps { parentType: 'dialog' | 'panel' ) => void; model: IJupyterGISModel; + processingType: 'buffer' | 'dissolve' | 'export'; } export class ProcessingFormDialog extends Dialog { @@ -29,6 +31,7 @@ export class ProcessingFormDialog extends Dialog { this.resolve(0); }; } + const layers = options.model.sharedModel.layers ?? {}; const layerOptions = Object.keys(layers).map(layerId => ({ @@ -47,10 +50,26 @@ export class ProcessingFormDialog extends Dialog { const filePath = options.model.filePath; const jgisModel = options.model; + + let FormComponent; + switch (options.processingType) { + case 'dissolve': + FormComponent = DissolveForm; + break; + case 'buffer': + FormComponent = BaseForm; + break; + case 'export': + FormComponent = BaseForm; + break; + default: + FormComponent = BaseForm; + } + const body = (
- { ); super({ title: options.title, body, buttons: [Dialog.cancelButton()] }); - this.addClass('jGIS-property-FormDialog'); + this.addClass('jGIS-processing-FormDialog'); } } From b9c6d0271610f25f2886a23fcf5d7412c4f103e1 Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Wed, 19 Mar 2025 18:35:41 +0530 Subject: [PATCH 22/25] launch dialog --- packages/base/src/commands.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/base/src/commands.ts b/packages/base/src/commands.ts index e194a646c..5e589cf78 100644 --- a/packages/base/src/commands.ts +++ b/packages/base/src/commands.ts @@ -533,7 +533,7 @@ export function addCommands( // Open form and get user input const formValues = await new Promise(resolve => { - new ProcessingFormDialog({ + const dialog = new ProcessingFormDialog({ title: 'Dissolve', schema: schema, model: model, @@ -546,8 +546,11 @@ export function addCommands( processingType: 'dissolve', syncData: (props: IDict) => { resolve(props); + dialog.dispose(); } }); + + dialog.launch() }); if (!formValues) { From f02f4d820bd2296b93b6fc4d61964cbb7f6bc9e5 Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Wed, 19 Mar 2025 19:00:09 +0530 Subject: [PATCH 23/25] it's working :tada: --- .../objectform/dissolveProcessForm.tsx | 32 ++++++++++++++----- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/packages/base/src/formbuilder/objectform/dissolveProcessForm.tsx b/packages/base/src/formbuilder/objectform/dissolveProcessForm.tsx index b614ce450..3678997ab 100644 --- a/packages/base/src/formbuilder/objectform/dissolveProcessForm.tsx +++ b/packages/base/src/formbuilder/objectform/dissolveProcessForm.tsx @@ -1,4 +1,4 @@ -import { BaseForm, IBaseFormProps } from './baseform'; // Import BaseForm +import { BaseForm, IBaseFormProps, IBaseFormStates } from './baseform'; // Ensure BaseForm imports states import { IDict, IJupyterGISModel, IGeoJSONSource } from '@jupytergis/schema'; import { IChangeEvent } from '@rjsf/core'; import { loadFile } from '../../tools'; @@ -14,15 +14,19 @@ interface IDissolveFormOptions extends IBaseFormProps { export class DissolveForm extends BaseForm { private model: IJupyterGISModel; - private schema: IDict; private features: string[] = []; constructor(options: IDissolveFormOptions) { super(options); this.model = options.model; - this.schema = options.schema; - console.log('DissolveForm initialized with options:', options); + // Ensure initial state matches IBaseFormStates + this.state = { + schema: options.schema ?? {} // Ensure schema is never undefined + }; + + this.onFormChange = this.handleFormChange.bind(this); + this.fetchFieldNames(options.sourceData.inputLayer); } @@ -37,7 +41,6 @@ export class DissolveForm extends BaseForm { if (!sourceData?.path) {return;} try { - console.log('Loading GeoJSON:', sourceData.path); const jsonData = await loadFile({ filepath: sourceData.path, type: 'GeoJSONSource', @@ -54,14 +57,27 @@ export class DissolveForm extends BaseForm { } public handleFormChange(e: IChangeEvent) { + super.onFormChange(e); + if (e.formData.inputLayer) { this.fetchFieldNames(e.formData.inputLayer); } } private updateSchema() { - if (this.schema.properties?.dissolveField) { - this.schema.properties.dissolveField.enum = this.features; - } + this.setState((prevState: IBaseFormStates) => ({ + schema: { + ...prevState.schema, + properties: { + ...prevState.schema?.properties, + dissolveField: { + ...prevState.schema?.properties?.dissolveField, + enum: [...this.features] + } + } + } + }), () => { + this.forceUpdate(); + }); } } From 1cdfdd3228bc5927cd613b45dc8a576ff33b7c82 Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Wed, 19 Mar 2025 19:18:36 +0530 Subject: [PATCH 24/25] lint --- packages/base/src/commands.ts | 2 +- .../src/dialogs/layerCreationFormDialog.tsx | 1 - .../base/src/formbuilder/formselectors.ts | 4 +- .../objectform/dissolveProcessForm.tsx | 41 ++++++++++++------- 4 files changed, 30 insertions(+), 18 deletions(-) diff --git a/packages/base/src/commands.ts b/packages/base/src/commands.ts index 5e589cf78..3d62d7b5b 100644 --- a/packages/base/src/commands.ts +++ b/packages/base/src/commands.ts @@ -550,7 +550,7 @@ export function addCommands( } }); - dialog.launch() + dialog.launch(); }); if (!formValues) { diff --git a/packages/base/src/dialogs/layerCreationFormDialog.tsx b/packages/base/src/dialogs/layerCreationFormDialog.tsx index 1dcd3cbe7..a86655ce6 100644 --- a/packages/base/src/dialogs/layerCreationFormDialog.tsx +++ b/packages/base/src/dialogs/layerCreationFormDialog.tsx @@ -25,7 +25,6 @@ export interface ICreationFormWrapperProps extends ICreationFormProps { dialogOptions?: any; } - export interface ICreationFormDialogOptions extends ICreationFormProps { title: string; } diff --git a/packages/base/src/formbuilder/formselectors.ts b/packages/base/src/formbuilder/formselectors.ts index b55789128..f6d39ddee 100644 --- a/packages/base/src/formbuilder/formselectors.ts +++ b/packages/base/src/formbuilder/formselectors.ts @@ -34,7 +34,9 @@ export function getLayerTypeForm( return LayerForm; } -export function getSourceTypeForm(sourceType: SourceType): typeof SourcePropertiesForm { +export function getSourceTypeForm( + sourceType: SourceType +): typeof SourcePropertiesForm { let SourceForm = SourcePropertiesForm; switch (sourceType) { case 'GeoJSONSource': diff --git a/packages/base/src/formbuilder/objectform/dissolveProcessForm.tsx b/packages/base/src/formbuilder/objectform/dissolveProcessForm.tsx index 3678997ab..cea1b489e 100644 --- a/packages/base/src/formbuilder/objectform/dissolveProcessForm.tsx +++ b/packages/base/src/formbuilder/objectform/dissolveProcessForm.tsx @@ -32,13 +32,19 @@ export class DissolveForm extends BaseForm { private async fetchFieldNames(layerId: string) { const layer = this.model.getLayer(layerId); - if (!layer?.parameters?.source) {return;} + if (!layer?.parameters?.source) { + return; + } const source = this.model.getSource(layer.parameters.source); - if (!source || source.type !== 'GeoJSONSource') {return;} + if (!source || source.type !== 'GeoJSONSource') { + return; + } const sourceData = source.parameters as IGeoJSONSource; - if (!sourceData?.path) {return;} + if (!sourceData?.path) { + return; + } try { const jsonData = await loadFile({ @@ -47,7 +53,9 @@ export class DissolveForm extends BaseForm { model: this.model }); - if (!jsonData?.features?.length) {return;} + if (!jsonData?.features?.length) { + return; + } this.features = Object.keys(jsonData.features[0].properties); this.updateSchema(); @@ -65,19 +73,22 @@ export class DissolveForm extends BaseForm { } private updateSchema() { - this.setState((prevState: IBaseFormStates) => ({ - schema: { - ...prevState.schema, - properties: { - ...prevState.schema?.properties, - dissolveField: { - ...prevState.schema?.properties?.dissolveField, - enum: [...this.features] + this.setState( + (prevState: IBaseFormStates) => ({ + schema: { + ...prevState.schema, + properties: { + ...prevState.schema?.properties, + dissolveField: { + ...prevState.schema?.properties?.dissolveField, + enum: [...this.features] + } } } + }), + () => { + this.forceUpdate(); } - }), () => { - this.forceUpdate(); - }); + ); } } From d93faecf618d10467a36b2a76f394d6b061cd484 Mon Sep 17 00:00:00 2001 From: Matt Fisher Date: Wed, 19 Mar 2025 10:49:00 -0600 Subject: [PATCH 25/25] WIP: Add dynamic buffer form and attempt to display units Units are calculated from projection, which is currently hard-coded. We instead want to find the projection from the layer, but don't know how yet! --- .../base/src/dialogs/ProcessingFormDialog.tsx | 3 +- .../objectform/bufferProcessForm.tsx | 89 +++++++++++++++++++ packages/schema/src/schema/buffer.json | 5 -- 3 files changed, 91 insertions(+), 6 deletions(-) create mode 100644 packages/base/src/formbuilder/objectform/bufferProcessForm.tsx diff --git a/packages/base/src/dialogs/ProcessingFormDialog.tsx b/packages/base/src/dialogs/ProcessingFormDialog.tsx index bf0b04c3c..cc405a247 100644 --- a/packages/base/src/dialogs/ProcessingFormDialog.tsx +++ b/packages/base/src/dialogs/ProcessingFormDialog.tsx @@ -3,6 +3,7 @@ import { Dialog } from '@jupyterlab/apputils'; import * as React from 'react'; import { BaseForm, IBaseFormProps } from '../formbuilder/objectform/baseform'; import { DissolveForm } from '../formbuilder/objectform/dissolveProcessForm'; +import { BufferForm } from '../formbuilder/objectform/bufferProcessForm'; export interface IProcessingFormDialogOptions extends IBaseFormProps { formContext: 'update' | 'create'; @@ -57,7 +58,7 @@ export class ProcessingFormDialog extends Dialog { FormComponent = DissolveForm; break; case 'buffer': - FormComponent = BaseForm; + FormComponent = BufferForm; break; case 'export': FormComponent = BaseForm; diff --git a/packages/base/src/formbuilder/objectform/bufferProcessForm.tsx b/packages/base/src/formbuilder/objectform/bufferProcessForm.tsx new file mode 100644 index 000000000..acf699474 --- /dev/null +++ b/packages/base/src/formbuilder/objectform/bufferProcessForm.tsx @@ -0,0 +1,89 @@ +import { BaseForm, IBaseFormProps, IBaseFormStates } from './baseform'; // Ensure BaseForm imports states +import { IDict, IJupyterGISModel } from '@jupytergis/schema'; +import { IChangeEvent } from '@rjsf/core'; +// import { loadFile } from '../../tools'; +import proj4 from "proj4"; + +interface IBufferFormOptions extends IBaseFormProps { + schema: IDict; + sourceData: IDict; + title: string; + cancelButton: (() => void) | boolean; + syncData: (props: IDict) => void; + model: IJupyterGISModel; +} + +export class BufferForm extends BaseForm { + private model: IJupyterGISModel; + private unit: string = ''; + + constructor(options: IBufferFormOptions) { + super(options); + this.model = options.model; + + // Ensure initial state matches IBaseFormStates + this.state = { + schema: options.schema ?? {} // Ensure schema is never undefined + }; + + this.onFormChange = this.handleFormChange.bind(this); + + this.computeDistanceUnits(options.sourceData.inputLayer); + } + + private async computeDistanceUnits(layerId: string) { + const layer = this.model.getLayer(layerId); + if (!layer?.parameters?.source) { + return; + } + const source = this.model.getSource(layer.parameters.source); + if (!source) return; + + const projection = source.parameters?.projection + console.log(projection); + + // TODO: how to get layer info from OpenLayers? + // const srs = layer.from_ol().srs; + const srs = "EPSG:4326"; + + try { + // console.log(proj4, srs); + this.unit = (proj4(srs) as any).oProj.units; + debugger; + this.updateSchema(); + } catch (error) { + console.error('Error calculating units:', error); + } + } + + public handleFormChange(e: IChangeEvent) { + super.onFormChange(e); + + if (e.formData.inputLayer) { + this.computeDistanceUnits(e.formData.inputLayer); + } + } + + private updateSchema() { + this.setState( + (prevState: IBaseFormStates) => ({ + schema: { + ...prevState.schema, + properties: { + ...prevState.schema?.properties, + bufferDistance: { + ...prevState.schema?.properties?.bufferDistance, + description: prevState.schema?.properties?.bufferDistance.description.replace( + "projection units", + this.unit, + ) + } + } + } + }), + () => { + this.forceUpdate(); + } + ); + } +} diff --git a/packages/schema/src/schema/buffer.json b/packages/schema/src/schema/buffer.json index dd12abcc5..88f9f7884 100644 --- a/packages/schema/src/schema/buffer.json +++ b/packages/schema/src/schema/buffer.json @@ -13,11 +13,6 @@ "type": "number", "default": 10, "description": "The distance used for buffering the geometry (in projection units)." - }, - "projection": { - "type": "string", - "description": "The spatial reference system of the buffered output.", - "default": "EPSG:4326" } } }