From bec511f4dccd7e51f03ecba92fc30ce66277aa25 Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Tue, 28 Oct 2025 17:10:21 +0530 Subject: [PATCH 01/54] Add `describedBy` to commands --- packages/base/src/commands/index.ts | 243 ++++++++++++++++++ packages/base/src/mainview/mainView.tsx | 14 +- .../base/src/processing/processingCommands.ts | 6 + .../jupytergis_core/src/jgisplugin/plugins.ts | 16 ++ python/jupytergis_qgis/src/plugins.ts | 12 + 5 files changed, 287 insertions(+), 4 deletions(-) diff --git a/packages/base/src/commands/index.ts b/packages/base/src/commands/index.ts index 47ee758b8..1eba95b76 100644 --- a/packages/base/src/commands/index.ts +++ b/packages/base/src/commands/index.ts @@ -72,6 +72,17 @@ export function addCommands( commands.addCommand(CommandIDs.symbology, { label: trans.__('Edit Symbology'), + describedBy: { + args: { + type: 'object', + properties: { + selected: { + type: 'object', + description: 'Currently selected layer(s) in the map view' + } + } + } + }, isEnabled: () => { const model = tracker.currentWidget?.model; const localState = model?.sharedModel.awareness.getLocalState(); @@ -110,6 +121,12 @@ export function addCommands( commands.addCommand(CommandIDs.redo, { label: trans.__('Redo'), + describedBy: { + args: { + type: 'object', + properties: {} + } + }, isEnabled: () => { return tracker.currentWidget ? tracker.currentWidget.model.sharedModel.editable @@ -127,6 +144,12 @@ export function addCommands( commands.addCommand(CommandIDs.undo, { label: trans.__('Undo'), + describedBy: { + args: { + type: 'object', + properties: {} + } + }, isEnabled: () => { return tracker.currentWidget ? tracker.currentWidget.model.sharedModel.editable @@ -144,6 +167,12 @@ export function addCommands( commands.addCommand(CommandIDs.identify, { label: trans.__('Identify'), + describedBy: { + args: { + type: 'object', + properties: {} + } + }, isToggled: () => { const current = tracker.currentWidget; if (!current) { @@ -214,6 +243,12 @@ export function addCommands( commands.addCommand(CommandIDs.temporalController, { label: trans.__('Temporal Controller'), + describedBy: { + args: { + type: 'object', + properties: {} + } + }, isToggled: () => { return tracker.currentWidget?.model.isTemporalControllerActive || false; }, @@ -266,6 +301,12 @@ export function addCommands( */ commands.addCommand(CommandIDs.openLayerBrowser, { label: trans.__('Open Layer Browser'), + describedBy: { + args: { + type: 'object', + properties: {} + } + }, isEnabled: () => { return tracker.currentWidget ? tracker.currentWidget.model.sharedModel.editable @@ -284,6 +325,12 @@ export function addCommands( */ commands.addCommand(CommandIDs.newRasterEntry, { label: trans.__('New Raster Tile Layer'), + describedBy: { + args: { + type: 'object', + properties: {} + } + }, isEnabled: () => { return tracker.currentWidget ? tracker.currentWidget.model.sharedModel.editable @@ -308,6 +355,12 @@ export function addCommands( commands.addCommand(CommandIDs.newVectorTileEntry, { label: trans.__('New Vector Tile Layer'), + describedBy: { + args: { + type: 'object', + properties: {} + } + }, isEnabled: () => { return tracker.currentWidget ? tracker.currentWidget.model.sharedModel.editable @@ -329,6 +382,12 @@ export function addCommands( commands.addCommand(CommandIDs.newGeoParquetEntry, { label: trans.__('New GeoParquet Layer'), + describedBy: { + args: { + type: 'object', + properties: {} + } + }, isEnabled: () => { return tracker.currentWidget ? tracker.currentWidget.model.sharedModel.editable @@ -350,6 +409,12 @@ export function addCommands( commands.addCommand(CommandIDs.newGeoJSONEntry, { label: trans.__('New GeoJSON layer'), + describedBy: { + args: { + type: 'object', + properties: {} + } + }, isEnabled: () => { return tracker.currentWidget ? tracker.currentWidget.model.sharedModel.editable @@ -373,6 +438,12 @@ export function addCommands( commands.addCommand(CommandIDs.newHillshadeEntry, { label: trans.__('New Hillshade layer'), + describedBy: { + args: { + type: 'object', + properties: {}, + }, + }, isEnabled: () => { return tracker.currentWidget ? tracker.currentWidget.model.sharedModel.editable @@ -393,6 +464,12 @@ export function addCommands( commands.addCommand(CommandIDs.newImageEntry, { label: trans.__('New Image layer'), + describedBy: { + args: { + type: 'object', + properties: {}, + }, + }, isEnabled: () => { return tracker.currentWidget ? tracker.currentWidget.model.sharedModel.editable @@ -423,6 +500,12 @@ export function addCommands( commands.addCommand(CommandIDs.newVideoEntry, { label: trans.__('New Video layer'), + describedBy: { + args: { + type: 'object', + properties: {}, + }, + }, isEnabled: () => { return tracker.currentWidget ? tracker.currentWidget.model.sharedModel.editable @@ -456,6 +539,12 @@ export function addCommands( commands.addCommand(CommandIDs.newGeoTiffEntry, { label: trans.__('New GeoTiff layer'), + describedBy: { + args: { + type: 'object', + properties: {}, + }, + }, isEnabled: () => { return tracker.currentWidget ? tracker.currentWidget.model.sharedModel.editable @@ -480,6 +569,12 @@ export function addCommands( commands.addCommand(CommandIDs.newShapefileEntry, { label: trans.__('New Shapefile Layer'), + describedBy: { + args: { + type: 'object', + properties: {}, + }, + }, isEnabled: () => { return tracker.currentWidget ? tracker.currentWidget.model.sharedModel.editable @@ -504,6 +599,12 @@ export function addCommands( */ commands.addCommand(CommandIDs.renameLayer, { label: trans.__('Rename Layer'), + describedBy: { + args: { + type: 'object', + properties: {}, + }, + }, execute: async () => { const model = tracker.currentWidget?.model; await Private.renameSelectedItem(model, 'layer', (layerId, newName) => { @@ -518,6 +619,12 @@ export function addCommands( commands.addCommand(CommandIDs.removeLayer, { label: trans.__('Remove Layer'), + describedBy: { + args: { + type: 'object', + properties: {}, + }, + }, execute: () => { const model = tracker.currentWidget?.model; Private.removeSelectedItems(model, 'layer', selection => { @@ -528,6 +635,12 @@ export function addCommands( commands.addCommand(CommandIDs.renameGroup, { label: trans.__('Rename Group'), + describedBy: { + args: { + type: 'object', + properties: {}, + }, + }, execute: async () => { const model = tracker.currentWidget?.model; await Private.renameSelectedItem(model, 'group', (groupName, newName) => { @@ -538,6 +651,12 @@ export function addCommands( commands.addCommand(CommandIDs.removeGroup, { label: trans.__('Remove Group'), + describedBy: { + args: { + type: 'object', + properties: {}, + }, + }, execute: async () => { const model = tracker.currentWidget?.model; Private.removeSelectedItems(model, 'group', selection => { @@ -549,6 +668,14 @@ export function addCommands( commands.addCommand(CommandIDs.moveLayersToGroup, { label: args => args['label'] ? (args['label'] as string) : trans.__('Move to Root'), + describedBy: { + args: { + type: 'object', + properties: { + label: { type: 'string' }, + }, + }, + }, execute: args => { const model = tracker.currentWidget?.model; const groupName = args['label'] as string; @@ -565,6 +692,12 @@ export function addCommands( commands.addCommand(CommandIDs.moveLayerToNewGroup, { label: trans.__('Move Selected Layers to New Group'), + describedBy: { + args: { + type: 'object', + properties: {}, + }, + }, execute: async () => { const model = tracker.currentWidget?.model; const selectedLayers = model?.localState?.selected?.value; @@ -631,6 +764,12 @@ export function addCommands( */ commands.addCommand(CommandIDs.renameSource, { label: trans.__('Rename Source'), + describedBy: { + args: { + type: 'object', + properties: {}, + }, + }, execute: async () => { const model = tracker.currentWidget?.model; await Private.renameSelectedItem(model, 'source', (sourceId, newName) => { @@ -645,6 +784,12 @@ export function addCommands( commands.addCommand(CommandIDs.removeSource, { label: trans.__('Remove Source'), + describedBy: { + args: { + type: 'object', + properties: {}, + }, + }, execute: () => { const model = tracker.currentWidget?.model; Private.removeSelectedItems(model, 'source', selection => { @@ -663,6 +808,12 @@ export function addCommands( // Console commands commands.addCommand(CommandIDs.toggleConsole, { label: trans.__('Toggle console'), + describedBy: { + args: { + type: 'object', + properties: {}, + }, + }, isVisible: () => tracker.currentWidget instanceof JupyterGISDocumentWidget, isEnabled: () => { return tracker.currentWidget @@ -681,8 +832,15 @@ export function addCommands( commands.notifyCommandChanged(CommandIDs.toggleConsole); }, }); + commands.addCommand(CommandIDs.executeConsole, { label: trans.__('Execute console'), + describedBy: { + args: { + type: 'object', + properties: {}, + }, + }, isVisible: () => tracker.currentWidget instanceof JupyterGISDocumentWidget, isEnabled: () => { return tracker.currentWidget @@ -691,8 +849,15 @@ export function addCommands( }, execute: () => Private.executeConsole(tracker), }); + commands.addCommand(CommandIDs.removeConsole, { label: trans.__('Remove console'), + describedBy: { + args: { + type: 'object', + properties: {}, + }, + }, isVisible: () => tracker.currentWidget instanceof JupyterGISDocumentWidget, isEnabled: () => { return tracker.currentWidget @@ -704,6 +869,12 @@ export function addCommands( commands.addCommand(CommandIDs.invokeCompleter, { label: trans.__('Display the completion helper.'), + describedBy: { + args: { + type: 'object', + properties: {}, + }, + }, isVisible: () => tracker.currentWidget instanceof JupyterGISDocumentWidget, execute: () => { const currentWidget = tracker.currentWidget; @@ -723,6 +894,12 @@ export function addCommands( commands.addCommand(CommandIDs.selectCompleter, { label: trans.__('Select the completion suggestion.'), + describedBy: { + args: { + type: 'object', + properties: {}, + }, + }, isVisible: () => tracker.currentWidget instanceof JupyterGISDocumentWidget, execute: () => { const currentWidget = tracker.currentWidget; @@ -742,6 +919,12 @@ export function addCommands( commands.addCommand(CommandIDs.zoomToLayer, { label: trans.__('Zoom to Layer'), + describedBy: { + args: { + type: 'object', + properties: {}, + }, + }, execute: () => { const currentWidget = tracker.currentWidget; if (!currentWidget || !completionProviderManager) { @@ -762,6 +945,12 @@ export function addCommands( commands.addCommand(CommandIDs.downloadGeoJSON, { label: trans.__('Download as GeoJSON'), + describedBy: { + args: { + type: 'object', + properties: {}, + }, + }, isEnabled: () => { const selectedLayer = getSingleSelectedLayer(tracker); return selectedLayer @@ -822,6 +1011,12 @@ export function addCommands( commands.addCommand(CommandIDs.getGeolocation, { label: trans.__('Center on Geolocation'), + describedBy: { + args: { + type: 'object', + properties: {}, + }, + }, execute: async () => { const viewModel = tracker.currentWidget?.model; const options = { @@ -853,6 +1048,12 @@ export function addCommands( // Panel visibility commands commands.addCommand(CommandIDs.toggleLeftPanel, { label: trans.__('Toggle Left Panel'), + describedBy: { + args: { + type: 'object', + properties: {}, + }, + }, isEnabled: () => Boolean(tracker.currentWidget), isToggled: () => { const current = tracker.currentWidget; @@ -880,6 +1081,12 @@ export function addCommands( commands.addCommand(CommandIDs.toggleRightPanel, { label: trans.__('Toggle Right Panel'), + describedBy: { + args: { + type: 'object', + properties: {}, + }, + }, isEnabled: () => Boolean(tracker.currentWidget), isToggled: () => { const current = tracker.currentWidget; @@ -908,6 +1115,12 @@ export function addCommands( // Left panel tabs commands.addCommand(CommandIDs.showLayersTab, { label: trans.__('Show Layers Tab'), + describedBy: { + args: { + type: 'object', + properties: {}, + }, + }, isEnabled: () => Boolean(tracker.currentWidget), isToggled: () => tracker.currentWidget @@ -930,6 +1143,12 @@ export function addCommands( commands.addCommand(CommandIDs.showStacBrowserTab, { label: trans.__('Show STAC Browser Tab'), + describedBy: { + args: { + type: 'object', + properties: {}, + }, + }, isEnabled: () => Boolean(tracker.currentWidget), isToggled: () => tracker.currentWidget @@ -952,6 +1171,12 @@ export function addCommands( commands.addCommand(CommandIDs.showFiltersTab, { label: trans.__('Show Filters Tab'), + describedBy: { + args: { + type: 'object', + properties: {}, + }, + }, isEnabled: () => Boolean(tracker.currentWidget), isToggled: () => tracker.currentWidget @@ -975,6 +1200,12 @@ export function addCommands( // Right panel tabs commands.addCommand(CommandIDs.showObjectPropertiesTab, { label: trans.__('Show Object Properties Tab'), + describedBy: { + args: { + type: 'object', + properties: {}, + }, + }, isEnabled: () => Boolean(tracker.currentWidget), isToggled: () => tracker.currentWidget @@ -997,6 +1228,12 @@ export function addCommands( commands.addCommand(CommandIDs.showAnnotationsTab, { label: trans.__('Show Annotations Tab'), + describedBy: { + args: { + type: 'object', + properties: {}, + }, + }, isEnabled: () => Boolean(tracker.currentWidget), isToggled: () => tracker.currentWidget @@ -1019,6 +1256,12 @@ export function addCommands( commands.addCommand(CommandIDs.showIdentifyPanelTab, { label: trans.__('Show Identify Panel Tab'), + describedBy: { + args: { + type: 'object', + properties: {}, + }, + }, isEnabled: () => Boolean(tracker.currentWidget), isToggled: () => tracker.currentWidget diff --git a/packages/base/src/mainview/mainView.tsx b/packages/base/src/mainview/mainView.tsx index 0b7359bcc..850c00d65 100644 --- a/packages/base/src/mainview/mainView.tsx +++ b/packages/base/src/mainview/mainView.tsx @@ -540,6 +540,16 @@ export class MainView extends React.Component { addContextMenu = (): void => { this._commands.addCommand(CommandIDs.addAnnotation, { + label: 'Add annotation', + describedBy: { + args: { + type: 'object', + properties: {}, + }, + }, + isEnabled: () => { + return !!this._Map; + }, execute: () => { if (!this._Map) { return; @@ -557,10 +567,6 @@ export class MainView extends React.Component { open: true, }); }, - label: 'Add annotation', - isEnabled: () => { - return !!this._Map; - }, }); this._contextMenu.addItem({ diff --git a/packages/base/src/processing/processingCommands.ts b/packages/base/src/processing/processingCommands.ts index a5b613928..227c5f075 100644 --- a/packages/base/src/processing/processingCommands.ts +++ b/packages/base/src/processing/processingCommands.ts @@ -51,6 +51,12 @@ export function addProcessingCommands( if (processingElement.type === ProcessingLogicType.vector) { commands.addCommand(processingElement.name, { label: trans.__(processingElement.label), + describedBy: { + args: { + type: 'object', + properties: {}, + }, + }, isEnabled: () => selectedLayerIsOfType(['VectorLayer'], tracker), execute: async () => { await processSelectedLayer( diff --git a/python/jupytergis_core/src/jgisplugin/plugins.ts b/python/jupytergis_core/src/jgisplugin/plugins.ts index f1139b060..8472832d0 100644 --- a/python/jupytergis_core/src/jgisplugin/plugins.ts +++ b/python/jupytergis_core/src/jgisplugin/plugins.ts @@ -161,6 +161,22 @@ const activate = async ( app.commands.addCommand(CommandIDs.createNew, { label: args => (args['label'] as string) ?? 'GIS Project', + describedBy: { + args: { + type: 'object', + properties: { + label: { + type: 'string', + description: 'The label for the file creation command' + }, + cwd: { + type: 'string', + description: + 'The current working directory where the file should be created' + } + } + } + }, caption: 'Create a new JGIS Editor', icon: args => logoIcon, execute: async args => { diff --git a/python/jupytergis_qgis/src/plugins.ts b/python/jupytergis_qgis/src/plugins.ts index 21e5141ce..1e4d3d306 100644 --- a/python/jupytergis_qgis/src/plugins.ts +++ b/python/jupytergis_qgis/src/plugins.ts @@ -214,6 +214,18 @@ const activate = async ( if (installed) { app.commands.addCommand(CommandIDs.exportQgis, { label: 'Export To QGZ', + describedBy: { + args: { + type: 'object', + properties: { + filepath: { + type: 'string', + description: + 'Optional. Destination filename (with or without .qgz extension) for the exported QGIS project.' + } + } + } + }, isEnabled: () => tracker.currentWidget ? tracker.currentWidget.model.sharedModel.editable From c0ed545b3302bd84a74513c2f9ba96909a01ac5c Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Tue, 28 Oct 2025 17:15:56 +0530 Subject: [PATCH 02/54] lint --- packages/base/src/commands/index.ts | 44 +++++++++---------- .../jupytergis_core/src/jgisplugin/plugins.ts | 10 ++--- python/jupytergis_qgis/src/plugins.ts | 8 ++-- 3 files changed, 31 insertions(+), 31 deletions(-) diff --git a/packages/base/src/commands/index.ts b/packages/base/src/commands/index.ts index 1eba95b76..4e6a39b60 100644 --- a/packages/base/src/commands/index.ts +++ b/packages/base/src/commands/index.ts @@ -78,10 +78,10 @@ export function addCommands( properties: { selected: { type: 'object', - description: 'Currently selected layer(s) in the map view' - } - } - } + description: 'Currently selected layer(s) in the map view', + }, + }, + }, }, isEnabled: () => { const model = tracker.currentWidget?.model; @@ -124,8 +124,8 @@ export function addCommands( describedBy: { args: { type: 'object', - properties: {} - } + properties: {}, + }, }, isEnabled: () => { return tracker.currentWidget @@ -147,8 +147,8 @@ export function addCommands( describedBy: { args: { type: 'object', - properties: {} - } + properties: {}, + }, }, isEnabled: () => { return tracker.currentWidget @@ -170,8 +170,8 @@ export function addCommands( describedBy: { args: { type: 'object', - properties: {} - } + properties: {}, + }, }, isToggled: () => { const current = tracker.currentWidget; @@ -246,8 +246,8 @@ export function addCommands( describedBy: { args: { type: 'object', - properties: {} - } + properties: {}, + }, }, isToggled: () => { return tracker.currentWidget?.model.isTemporalControllerActive || false; @@ -304,8 +304,8 @@ export function addCommands( describedBy: { args: { type: 'object', - properties: {} - } + properties: {}, + }, }, isEnabled: () => { return tracker.currentWidget @@ -328,8 +328,8 @@ export function addCommands( describedBy: { args: { type: 'object', - properties: {} - } + properties: {}, + }, }, isEnabled: () => { return tracker.currentWidget @@ -358,8 +358,8 @@ export function addCommands( describedBy: { args: { type: 'object', - properties: {} - } + properties: {}, + }, }, isEnabled: () => { return tracker.currentWidget @@ -385,8 +385,8 @@ export function addCommands( describedBy: { args: { type: 'object', - properties: {} - } + properties: {}, + }, }, isEnabled: () => { return tracker.currentWidget @@ -412,8 +412,8 @@ export function addCommands( describedBy: { args: { type: 'object', - properties: {} - } + properties: {}, + }, }, isEnabled: () => { return tracker.currentWidget diff --git a/python/jupytergis_core/src/jgisplugin/plugins.ts b/python/jupytergis_core/src/jgisplugin/plugins.ts index 8472832d0..33da363ab 100644 --- a/python/jupytergis_core/src/jgisplugin/plugins.ts +++ b/python/jupytergis_core/src/jgisplugin/plugins.ts @@ -167,15 +167,15 @@ const activate = async ( properties: { label: { type: 'string', - description: 'The label for the file creation command' + description: 'The label for the file creation command', }, cwd: { type: 'string', description: - 'The current working directory where the file should be created' - } - } - } + 'The current working directory where the file should be created', + }, + }, + }, }, caption: 'Create a new JGIS Editor', icon: args => logoIcon, diff --git a/python/jupytergis_qgis/src/plugins.ts b/python/jupytergis_qgis/src/plugins.ts index 1e4d3d306..b27c80c41 100644 --- a/python/jupytergis_qgis/src/plugins.ts +++ b/python/jupytergis_qgis/src/plugins.ts @@ -221,10 +221,10 @@ const activate = async ( filepath: { type: 'string', description: - 'Optional. Destination filename (with or without .qgz extension) for the exported QGIS project.' - } - } - } + 'Optional. Destination filename (with or without .qgz extension) for the exported QGIS project.', + }, + }, + }, }, isEnabled: () => tracker.currentWidget From 3e19dcf0780032a00ad20ada03b070512d40eb96 Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Wed, 29 Oct 2025 18:15:10 +0530 Subject: [PATCH 03/54] command to add geojson with params --- packages/base/src/commands/index.ts | 3 + .../base/src/commands/operationCommands.ts | 166 ++++++++++++++++++ 2 files changed, 169 insertions(+) create mode 100644 packages/base/src/commands/operationCommands.ts diff --git a/packages/base/src/commands/index.ts b/packages/base/src/commands/index.ts index 4e6a39b60..6493d37eb 100644 --- a/packages/base/src/commands/index.ts +++ b/packages/base/src/commands/index.ts @@ -32,6 +32,7 @@ import { addProcessingCommands } from '../processing/processingCommands'; import { getGeoJSONDataFromLayerSource, downloadFile } from '../tools'; import { JupyterGISTracker } from '../types'; import { JupyterGISDocumentWidget } from '../widget'; +import { addLayerCreationCommands } from './operationCommands'; interface ICreateEntry { tracker: JupyterGISTracker; @@ -70,6 +71,8 @@ export function addCommands( const trans = translator.load('jupyterlab'); const { commands } = app; + addLayerCreationCommands({ tracker, commands, trans }); + commands.addCommand(CommandIDs.symbology, { label: trans.__('Edit Symbology'), describedBy: { diff --git a/packages/base/src/commands/operationCommands.ts b/packages/base/src/commands/operationCommands.ts new file mode 100644 index 000000000..106bbb1b4 --- /dev/null +++ b/packages/base/src/commands/operationCommands.ts @@ -0,0 +1,166 @@ +import { + IJupyterGISModel, + IJGISLayer, + IJGISSource +} from '@jupytergis/schema'; +import { IRenderMime } from '@jupyterlab/rendermime'; +import { CommandRegistry } from '@lumino/commands'; +import { UUID } from '@lumino/coreutils'; + +import { JupyterGISTracker } from '../types'; + +export namespace LayerCreationCommandIDs { + export const newGeoJSONWithParams = 'jupytergis:newGeoJSONWithParams'; +} + +export function addLayerCreationCommands(options: { + tracker: JupyterGISTracker; + commands: CommandRegistry; + trans: IRenderMime.TranslationBundle; +}) { + const { commands, tracker, trans } = options; + + commands.addCommand(LayerCreationCommandIDs.newGeoJSONWithParams, { + label: trans.__('New GeoJSON Layer From Parameters'), + isEnabled: () => true, + describedBy: { + args: { + type: 'object', + required: ['filePath', 'Name', 'parameters'], + properties: { + filePath: { + type: 'string', + description: 'The path to the .jGIS file to modify' + }, + Name: { + type: 'string', + description: 'The name of the new GeoJSON layer' + }, + parameters: { + type: 'object', + required: ['source', 'opacity', 'symbologyState'], + properties: { + source: { + type: 'object', + description: 'GeoJSON source configuration', + required: [], + properties: { + path: { + type: 'string', + description: 'The path to the GeoJSON file' + }, + data: { + type: 'object', + description: 'The GeoJSON data object' + }, + valid: { + type: 'boolean', + description: 'Whether the data are valid', + readOnly: true + } + } + }, + color: { + type: 'object', + description: 'The color configuration for the layer' + }, + opacity: { + type: 'number', + description: 'Layer opacity', + default: 1, + minimum: 0, + maximum: 1, + multipleOf: 0.1 + }, + symbologyState: { + type: 'object', + description: 'Symbology configuration for the layer', + required: ['renderType'], + properties: { + renderType: { + type: 'string', + enum: ['Single Symbol', 'Graduated', 'Categorized'] + }, + value: { type: 'string' }, + method: { + type: 'string', + enum: ['color', 'radius'] + }, + colorRamp: { + type: 'string', + default: 'viridis' + }, + nClasses: { + type: 'string', + default: '9' + }, + mode: { + type: 'string', + default: 'equal interval', + enum: [ + 'quantile', + 'equal interval', + 'jenks', + 'pretty', + 'logarithmic' + ] + } + } + } + } + } + } + } + }, + execute: (async (args: { + filePath: string; + Name: string; + parameters: { + source: Record; + color?: Record; + opacity?: number; + symbologyState: Record; + }; + }) => { + const { filePath, Name, parameters } = args; + const current = tracker.find(w => w.model.filePath === filePath); + + if (!current) { + console.warn('No JupyterGIS widget found for', filePath); + return; + } + + const model: IJupyterGISModel = current.model; + const sharedModel = model.sharedModel; + if (!sharedModel.editable) { + console.warn('Shared model not editable'); + return; + } + + const sourceId = UUID.uuid4(); + const layerId = UUID.uuid4(); + + const sourceModel: IJGISSource = { + type: 'GeoJSONSource', + name: `${Name} Source`, + parameters: parameters.source + }; + + sharedModel.addSource(sourceId, sourceModel); + + const layerModel: IJGISLayer = { + type: 'VectorLayer', + name: Name, + visible: true, + parameters: { + color: parameters.color ?? {}, + opacity: parameters.opacity ?? 1, + symbologyState: parameters.symbologyState, + source: sourceId + } + }; + + model.addLayer(layerId, layerModel); + }) as any + }); +} From 6f80029dc9c32eb41c8b6e91c5148a9b673c8aa9 Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Wed, 29 Oct 2025 19:18:51 +0530 Subject: [PATCH 04/54] `newRasterWithParams` --- .../base/src/commands/operationCommands.ts | 143 +++++++++++++++++- 1 file changed, 139 insertions(+), 4 deletions(-) diff --git a/packages/base/src/commands/operationCommands.ts b/packages/base/src/commands/operationCommands.ts index 106bbb1b4..c69e96514 100644 --- a/packages/base/src/commands/operationCommands.ts +++ b/packages/base/src/commands/operationCommands.ts @@ -11,6 +11,7 @@ import { JupyterGISTracker } from '../types'; export namespace LayerCreationCommandIDs { export const newGeoJSONWithParams = 'jupytergis:newGeoJSONWithParams'; + export const newRasterWithParams = 'jupytergis:newRasterWithParams'; } export function addLayerCreationCommands(options: { @@ -132,10 +133,6 @@ export function addLayerCreationCommands(options: { const model: IJupyterGISModel = current.model; const sharedModel = model.sharedModel; - if (!sharedModel.editable) { - console.warn('Shared model not editable'); - return; - } const sourceId = UUID.uuid4(); const layerId = UUID.uuid4(); @@ -163,4 +160,142 @@ export function addLayerCreationCommands(options: { model.addLayer(layerId, layerModel); }) as any }); + + commands.addCommand(LayerCreationCommandIDs.newRasterWithParams, { + label: trans.__('New Raster Tile Layer From Parameters'), + isEnabled: () => true, + describedBy: { + args: { + type: 'object', + required: ['filePath', 'Name', 'parameters'], + properties: { + filePath: { + type: 'string', + description: 'Path to the .jGIS file to modify' + }, + Name: { + type: 'string', + description: 'The name of the new Raster Tile Layer' + }, + parameters: { + type: 'object', + required: ['source', 'opacity'], + properties: { + source: { + type: 'object', + description: 'Raster source configuration', + required: ['url', 'maxZoom', 'minZoom'], + properties: { + url: { + type: 'string', + description: 'The URL to the tile provider' + }, + minZoom: { + type: 'number', + minimum: 0, + maximum: 24, + default: 0, + description: 'Minimum zoom level' + }, + maxZoom: { + type: 'number', + minimum: 0, + maximum: 24, + default: 24, + description: 'Maximum zoom level' + }, + attribution: { + type: 'string', + default: '', + description: 'Attribution for the raster source' + }, + htmlAttribution: { + type: 'string', + default: '', + description: 'HTML attribution for the raster source' + }, + provider: { + type: 'string', + default: '', + description: 'Provider name' + }, + bounds: { + type: 'array', + description: 'Bounds of the source', + items: { + type: 'array', + items: { type: 'number' } + }, + default: [] + }, + urlParameters: { + type: 'object', + description: 'Extra URL parameters', + additionalProperties: { type: 'string' }, + default: {} + }, + interpolate: { + type: 'boolean', + description: + 'Interpolate between grid cells when overzooming?', + default: false + } + } + }, + opacity: { + type: 'number', + description: 'Layer opacity', + default: 1, + minimum: 0, + maximum: 1, + multipleOf: 0.1 + } + } + } + } + } + }, + execute: (async (args: { + filePath: string; + Name: string; + parameters: { + source: Record; + opacity: number; + }; + }) => { + const { filePath, Name, parameters } = args; + const current = tracker.find(w => w.model.filePath === filePath); + + if (!current) { + console.warn('No JupyterGIS widget found for', filePath); + return; + } + + const model: IJupyterGISModel = current.model; + const sharedModel = model.sharedModel; + + const sourceId = UUID.uuid4(); + const layerId = UUID.uuid4(); + + const sourceModel: IJGISSource = { + type: 'RasterSource', + name: `${Name} Source`, + parameters: parameters.source + }; + + sharedModel.addSource(sourceId, sourceModel); + + const layerModel: IJGISLayer = { + type: 'RasterLayer', + name: Name, + visible: true, + parameters: { + opacity: parameters.opacity ?? 1, + source: sourceId + } + }; + + model.addLayer(layerId, layerModel); + }) as any + }); } From 8375e739e2732f6e1733d9e3e4fa675a5fc5618d Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Wed, 29 Oct 2025 19:19:31 +0530 Subject: [PATCH 05/54] `newVectorTileWithParams` --- .../base/src/commands/operationCommands.ts | 121 ++++++++++++++++++ 1 file changed, 121 insertions(+) diff --git a/packages/base/src/commands/operationCommands.ts b/packages/base/src/commands/operationCommands.ts index c69e96514..58663b01d 100644 --- a/packages/base/src/commands/operationCommands.ts +++ b/packages/base/src/commands/operationCommands.ts @@ -12,6 +12,7 @@ import { JupyterGISTracker } from '../types'; export namespace LayerCreationCommandIDs { export const newGeoJSONWithParams = 'jupytergis:newGeoJSONWithParams'; export const newRasterWithParams = 'jupytergis:newRasterWithParams'; + export const newVectorTileWithParams = 'jupytergis:newVectorTileWithParams'; } export function addLayerCreationCommands(options: { @@ -298,4 +299,124 @@ export function addLayerCreationCommands(options: { model.addLayer(layerId, layerModel); }) as any }); + + commands.addCommand(LayerCreationCommandIDs.newVectorTileWithParams, { + label: trans.__('New Vector Tile Layer From Parameters'), + isEnabled: () => true, + describedBy: { + args: { + type: 'object', + required: ['filePath', 'Name', 'parameters'], + properties: { + filePath: { + type: 'string', + description: 'Path to the .jGIS file to modify' + }, + Name: { + type: 'string', + description: 'The name of the new Vector Tile Layer' + }, + parameters: { + type: 'object', + required: ['source', 'opacity'], + properties: { + source: { + type: 'object', + description: 'Vector tile source configuration', + required: ['url', 'maxZoom', 'minZoom'], + properties: { + url: { + type: 'string', + description: 'The URL to the tile provider' + }, + minZoom: { + type: 'number', + minimum: 0, + maximum: 24, + description: 'Minimum zoom level for the vector source' + }, + maxZoom: { + type: 'number', + minimum: 0, + maximum: 24, + description: 'Maximum zoom level for the vector source' + }, + attribution: { + type: 'string', + description: 'Attribution for the vector source' + }, + provider: { + type: 'string', + description: 'The map provider', + readOnly: true + }, + urlParameters: { + type: 'object', + description: 'Additional URL parameters', + additionalProperties: { type: 'string' } + } + } + }, + color: { + type: 'object', + description: 'Color styling configuration for the layer' + }, + opacity: { + type: 'number', + description: 'Layer opacity (0–1)', + default: 1, + minimum: 0, + maximum: 1, + multipleOf: 0.1 + } + } + } + } + } + }, + execute: (async (args: { + filePath: string; + Name: string; + parameters: { + source: Record; + color?: Record; + opacity?: number; + }; + }) => { + const { filePath, Name, parameters } = args; + const current = tracker.find(w => w.model.filePath === filePath); + + if (!current) { + console.warn('No JupyterGIS widget found for', filePath); + return; + } + + const model: IJupyterGISModel = current.model; + const sharedModel = model.sharedModel; + + const sourceId = UUID.uuid4(); + const layerId = UUID.uuid4(); + + const sourceModel: IJGISSource = { + type: 'VectorTileSource', + name: `${Name} Source`, + parameters: parameters.source + }; + + sharedModel.addSource(sourceId, sourceModel); + + const layerModel: IJGISLayer = { + type: 'VectorTileLayer', + name: Name, + visible: true, + parameters: { + color: parameters.color ?? {}, + opacity: parameters.opacity ?? 1, + source: sourceId + } + }; + + model.addLayer(layerId, layerModel); + }) as any + }); } From 6e34acf78db5f59b125a27eae33888f9207a6b2f Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Thu, 30 Oct 2025 18:30:11 +0530 Subject: [PATCH 06/54] `newGeoParquetWithParams` --- .../base/src/commands/operationCommands.ts | 147 ++++++++++++++++++ 1 file changed, 147 insertions(+) diff --git a/packages/base/src/commands/operationCommands.ts b/packages/base/src/commands/operationCommands.ts index 58663b01d..df33b7a1f 100644 --- a/packages/base/src/commands/operationCommands.ts +++ b/packages/base/src/commands/operationCommands.ts @@ -13,6 +13,7 @@ export namespace LayerCreationCommandIDs { export const newGeoJSONWithParams = 'jupytergis:newGeoJSONWithParams'; export const newRasterWithParams = 'jupytergis:newRasterWithParams'; export const newVectorTileWithParams = 'jupytergis:newVectorTileWithParams'; + export const newGeoParquetWithParams = 'jupytergis:newGeoParquetWithParams'; } export function addLayerCreationCommands(options: { @@ -419,4 +420,150 @@ export function addLayerCreationCommands(options: { model.addLayer(layerId, layerModel); }) as any }); + + commands.addCommand(LayerCreationCommandIDs.newGeoParquetWithParams, { + label: trans.__('New GeoParquet Layer From Parameters'), + isEnabled: () => true, + describedBy: { + args: { + type: 'object', + required: ['filePath', 'Name', 'parameters'], + properties: { + filePath: { + type: 'string', + description: 'Path to the .jGIS file to modify' + }, + Name: { + type: 'string', + description: 'The name of the new GeoParquet layer' + }, + parameters: { + type: 'object', + required: ['source', 'opacity', 'symbologyState'], + properties: { + source: { + type: 'object', + description: 'GeoParquet source configuration', + required: ['path'], + properties: { + path: { + type: 'string', + description: 'The path to the GeoParquet source' + }, + attribution: { + type: 'string', + readOnly: true, + description: 'Attribution for the GeoParquet source', + default: '' + }, + projection: { + type: 'string', + description: + 'Projection information for the GeoParquet data', + default: 'EPSG:4326' + } + } + }, + color: { + type: 'object', + description: 'Color styling for the layer' + }, + opacity: { + type: 'number', + description: 'Layer opacity (0–1)', + default: 1, + minimum: 0, + maximum: 1, + multipleOf: 0.1 + }, + symbologyState: { + type: 'object', + description: 'Symbology configuration for the layer', + required: ['renderType'], + properties: { + renderType: { + type: 'string', + enum: ['Single Symbol', 'Graduated', 'Categorized'] + }, + value: { + type: 'string' + }, + method: { + type: 'string', + enum: ['color', 'radius'] + }, + colorRamp: { + type: 'string', + default: 'viridis' + }, + nClasses: { + type: 'string', + default: '9' + }, + mode: { + type: 'string', + default: 'equal interval', + enum: [ + 'quantile', + 'equal interval', + 'jenks', + 'pretty', + 'logarithmic' + ] + } + }, + additionalProperties: false + } + } + } + } + } + }, + execute: (async (args: { + filePath: string; + Name: string; + parameters: { + source: Record; + color?: Record; + opacity?: number; + symbologyState: Record; + }; + }) => { + const { filePath, Name, parameters } = args; + const current = tracker.find(w => w.model.filePath === filePath); + + if (!current) { + console.warn('No JupyterGIS widget found for', filePath); + return; + } + + const model: IJupyterGISModel = current.model; + const sharedModel = model.sharedModel; + + const sourceId = UUID.uuid4(); + const layerId = UUID.uuid4(); + + const sourceModel: IJGISSource = { + type: 'GeoParquetSource', + name: `${Name} Source`, + parameters: parameters.source + }; + + sharedModel.addSource(sourceId, sourceModel); + + const layerModel: IJGISLayer = { + type: 'VectorLayer', + name: Name, + visible: true, + parameters: { + color: parameters.color ?? {}, + opacity: parameters.opacity ?? 1, + symbologyState: parameters.symbologyState, + source: sourceId + } + }; + + model.addLayer(layerId, layerModel); + }) as any + }); } From e54d39798f2dcab9c0d344606d6b7411f09c9103 Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Fri, 31 Oct 2025 10:21:42 +0530 Subject: [PATCH 07/54] `newHillshadeWithParams` --- .../base/src/commands/operationCommands.ts | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) diff --git a/packages/base/src/commands/operationCommands.ts b/packages/base/src/commands/operationCommands.ts index df33b7a1f..bf8d0f12c 100644 --- a/packages/base/src/commands/operationCommands.ts +++ b/packages/base/src/commands/operationCommands.ts @@ -14,6 +14,7 @@ export namespace LayerCreationCommandIDs { export const newRasterWithParams = 'jupytergis:newRasterWithParams'; export const newVectorTileWithParams = 'jupytergis:newVectorTileWithParams'; export const newGeoParquetWithParams = 'jupytergis:newGeoParquetWithParams'; + export const newHillshadeWithParams = 'jupytergis:newHillshadeWithParams'; } export function addLayerCreationCommands(options: { @@ -566,4 +567,108 @@ export function addLayerCreationCommands(options: { model.addLayer(layerId, layerModel); }) as any }); + + commands.addCommand(LayerCreationCommandIDs.newHillshadeWithParams, { + label: trans.__('New Hillshade Layer From Parameters'), + isEnabled: () => true, + describedBy: { + args: { + type: 'object', + required: ['filePath', 'Name', 'parameters'], + properties: { + filePath: { + type: 'string', + description: 'Path to the .jGIS file to modify' + }, + Name: { + type: 'string', + description: 'The name of the new Hillshade layer' + }, + parameters: { + type: 'object', + required: ['source'], + properties: { + source: { + type: 'object', + description: 'RasterDem source configuration', + required: ['url'], + properties: { + url: { + type: 'string', + description: 'The URL to the DEM tile provider' + }, + attribution: { + type: 'string', + description: + 'Attribution for the raster-dem source (optional)' + }, + urlParameters: { + type: 'object', + description: + 'Additional URL parameters for the raster-dem source', + additionalProperties: { + type: 'string' + } + }, + interpolate: { + type: 'boolean', + description: + 'Interpolate between grid cells when overzooming', + default: false + } + } + }, + shadowColor: { + type: 'string', + description: 'The color of the shadows', + default: '#473B24' + } + } + } + } + } + }, + execute: (async (args: { + filePath: string; + Name: string; + parameters: { + source: Record; + shadowColor?: string; + }; + }) => { + const { filePath, Name, parameters } = args; + const current = tracker.find(w => w.model.filePath === filePath); + + if (!current) { + console.warn('No JupyterGIS widget found for', filePath); + return; + } + + const model: IJupyterGISModel = current.model; + const sharedModel = model.sharedModel; + + const sourceId = UUID.uuid4(); + const layerId = UUID.uuid4(); + + const sourceModel: IJGISSource = { + type: 'RasterDemSource', + name: `${Name} Source`, + parameters: parameters.source + }; + + sharedModel.addSource(sourceId, sourceModel); + + const layerModel: IJGISLayer = { + type: 'HillshadeLayer', + name: Name, + visible: true, + parameters: { + shadowColor: parameters.shadowColor ?? '#473B24', + source: sourceId + } + }; + + model.addLayer(layerId, layerModel); + }) as any + }); } From 28e19e2509aac70218d298e99202ac3c1c88be7c Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Fri, 31 Oct 2025 10:42:32 +0530 Subject: [PATCH 08/54] `newImageWithParams` --- .../base/src/commands/operationCommands.ts | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) diff --git a/packages/base/src/commands/operationCommands.ts b/packages/base/src/commands/operationCommands.ts index bf8d0f12c..eb9966bce 100644 --- a/packages/base/src/commands/operationCommands.ts +++ b/packages/base/src/commands/operationCommands.ts @@ -15,6 +15,7 @@ export namespace LayerCreationCommandIDs { export const newVectorTileWithParams = 'jupytergis:newVectorTileWithParams'; export const newGeoParquetWithParams = 'jupytergis:newGeoParquetWithParams'; export const newHillshadeWithParams = 'jupytergis:newHillshadeWithParams'; + export const newImageWithParams = 'jupytergis:newImageWithParams'; } export function addLayerCreationCommands(options: { @@ -671,4 +672,119 @@ export function addLayerCreationCommands(options: { model.addLayer(layerId, layerModel); }) as any }); + + commands.addCommand(LayerCreationCommandIDs.newImageWithParams, { + label: trans.__('New Image Layer From Parameters'), + isEnabled: () => true, + describedBy: { + args: { + type: 'object', + required: ['filePath', 'Name', 'parameters'], + properties: { + filePath: { + type: 'string', + description: 'Path to the .jGIS file to modify' + }, + Name: { + type: 'string', + description: 'The name of the new Image layer' + }, + parameters: { + type: 'object', + required: ['source'], + properties: { + source: { + type: 'object', + description: 'Image source configuration', + required: ['path', 'coordinates'], + properties: { + path: { + type: 'string', + description: 'Path that points to the image' + }, + coordinates: { + type: 'array', + description: + 'Four corner coordinates in [lon, lat] pairs defining the image bounds', + minItems: 4, + maxItems: 4, + items: { + type: 'array', + minItems: 2, + maxItems: 2, + items: { type: 'number' } + } + }, + interpolate: { + type: 'boolean', + description: + 'Whether to interpolate between grid cells when overzooming', + default: false + } + } + }, + opacity: { + type: 'number', + description: 'The opacity of the image layer', + default: 1, + minimum: 0, + maximum: 1, + multipleOf: 0.1 + } + } + } + } + } + }, + execute: (async (args: { + filePath: string; + Name: string; + parameters: { + source: { + path: string; + coordinates: number[][]; + interpolate?: boolean; + }; + opacity?: number; + }; + }) => { + const { filePath, Name, parameters } = args; + const current = tracker.find(w => w.model.filePath === filePath); + + if (!current) { + console.warn('No JupyterGIS widget found for', filePath); + return; + } + + const model: IJupyterGISModel = current.model; + const sharedModel = model.sharedModel; + + const sourceId = UUID.uuid4(); + const layerId = UUID.uuid4(); + + const sourceModel: IJGISSource = { + type: 'ImageSource', + name: `${Name} Source`, + parameters: { + path: parameters.source.path, + coordinates: parameters.source.coordinates, + interpolate: parameters.source.interpolate ?? false + } + }; + + sharedModel.addSource(sourceId, sourceModel); + + const layerModel: IJGISLayer = { + type: 'ImageLayer', + name: Name, + visible: true, + parameters: { + source: sourceId, + opacity: parameters.opacity ?? 1 + } + }; + + model.addLayer(layerId, layerModel); + }) as any + }); } From 7b59b1c2b9c20a90e89eb86788ae391b92e720b2 Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Fri, 31 Oct 2025 10:46:24 +0530 Subject: [PATCH 09/54] `newVideoWithParams` --- .../base/src/commands/operationCommands.ts | 110 ++++++++++++++++++ 1 file changed, 110 insertions(+) diff --git a/packages/base/src/commands/operationCommands.ts b/packages/base/src/commands/operationCommands.ts index eb9966bce..402785351 100644 --- a/packages/base/src/commands/operationCommands.ts +++ b/packages/base/src/commands/operationCommands.ts @@ -16,6 +16,7 @@ export namespace LayerCreationCommandIDs { export const newGeoParquetWithParams = 'jupytergis:newGeoParquetWithParams'; export const newHillshadeWithParams = 'jupytergis:newHillshadeWithParams'; export const newImageWithParams = 'jupytergis:newImageWithParams'; + export const newVideoWithParams = 'jupytergis:newVideoWithParams'; } export function addLayerCreationCommands(options: { @@ -787,4 +788,113 @@ export function addLayerCreationCommands(options: { model.addLayer(layerId, layerModel); }) as any }); + + commands.addCommand(LayerCreationCommandIDs.newVideoWithParams, { + label: trans.__('New Video Layer From Parameters'), + isEnabled: () => true, + describedBy: { + args: { + type: 'object', + required: ['filePath', 'Name', 'parameters'], + properties: { + filePath: { + type: 'string', + description: 'Path to the .jGIS file to modify' + }, + Name: { + type: 'string', + description: 'The name of the new Video layer' + }, + parameters: { + type: 'object', + required: ['source'], + properties: { + source: { + type: 'object', + description: 'Video source configuration', + required: ['urls', 'coordinates'], + properties: { + urls: { + type: 'array', + description: 'List of video URLs in preferred format order', + minItems: 1, + items: { type: 'string' } + }, + coordinates: { + type: 'array', + description: + 'Four corner coordinates in [lon, lat] pairs defining the video projection area', + minItems: 4, + maxItems: 4, + items: { + type: 'array', + minItems: 2, + maxItems: 2, + items: { type: 'number' } + } + } + } + }, + opacity: { + type: 'number', + description: 'The opacity of the video layer', + default: 1, + minimum: 0, + maximum: 1, + multipleOf: 0.1 + } + } + } + } + } + }, + execute: (async (args: { + filePath: string; + Name: string; + parameters: { + source: { + urls: string[]; + coordinates: number[][]; + }; + opacity?: number; + }; + }) => { + const { filePath, Name, parameters } = args; + const current = tracker.find(w => w.model.filePath === filePath); + + if (!current) { + console.warn('No JupyterGIS widget found for', filePath); + return; + } + + const model: IJupyterGISModel = current.model; + const sharedModel = model.sharedModel; + + const sourceId = UUID.uuid4(); + const layerId = UUID.uuid4(); + + const sourceModel: IJGISSource = { + type: 'VideoSource', + name: `${Name} Source`, + parameters: { + urls: parameters.source.urls, + coordinates: parameters.source.coordinates + } + }; + + sharedModel.addSource(sourceId, sourceModel); + + const layerModel: IJGISLayer = { + type: 'RasterLayer', + name: Name, + visible: true, + parameters: { + source: sourceId, + opacity: parameters.opacity ?? 1 + } + }; + + model.addLayer(layerId, layerModel); + }) as any + }); } From 89d9a5459a2b8a6dd937e85d415b368c9e0a9948 Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Fri, 31 Oct 2025 10:51:55 +0530 Subject: [PATCH 10/54] `newGeoTiffWithParams` --- .../base/src/commands/operationCommands.ts | 183 ++++++++++++++++++ 1 file changed, 183 insertions(+) diff --git a/packages/base/src/commands/operationCommands.ts b/packages/base/src/commands/operationCommands.ts index 402785351..e37f4a251 100644 --- a/packages/base/src/commands/operationCommands.ts +++ b/packages/base/src/commands/operationCommands.ts @@ -17,6 +17,7 @@ export namespace LayerCreationCommandIDs { export const newHillshadeWithParams = 'jupytergis:newHillshadeWithParams'; export const newImageWithParams = 'jupytergis:newImageWithParams'; export const newVideoWithParams = 'jupytergis:newVideoWithParams'; + export const newGeoTiffWithParams = 'jupytergis:newGeoTiffWithParams'; } export function addLayerCreationCommands(options: { @@ -897,4 +898,186 @@ export function addLayerCreationCommands(options: { model.addLayer(layerId, layerModel); }) as any }); + + commands.addCommand(LayerCreationCommandIDs.newGeoTiffWithParams, { + label: trans.__('New GeoTIFF Layer From Parameters'), + isEnabled: () => true, + describedBy: { + args: { + type: 'object', + required: ['filePath', 'Name', 'parameters'], + properties: { + filePath: { + type: 'string', + description: 'Path to the .jGIS file to modify' + }, + Name: { + type: 'string', + description: 'Name of the new GeoTIFF layer' + }, + parameters: { + type: 'object', + required: ['source'], + properties: { + source: { + type: 'object', + description: 'GeoTIFF source configuration', + required: ['urls'], + properties: { + urls: { + type: 'array', + description: 'List of GeoTIFF URL objects with optional min/max values', + minItems: 1, + items: { + type: 'object', + properties: { + url: { type: 'string', description: 'URL to the GeoTIFF file' }, + min: { type: 'number', description: 'Minimum value for scaling' }, + max: { type: 'number', description: 'Maximum value for scaling' } + } + } + }, + normalize: { + type: 'boolean', + description: + 'Normalize values between 0 and 1 for RGB display; disable to keep raw values', + default: true + }, + wrapX: { + type: 'boolean', + description: 'Wrap map horizontally?', + default: false + }, + interpolate: { + type: 'boolean', + description: 'Interpolate between grid cells when overzooming?', + default: false + } + } + }, + opacity: { + type: 'number', + description: 'Layer opacity (0–1)', + default: 1, + minimum: 0, + maximum: 1, + multipleOf: 0.1 + }, + color: { + oneOf: [ + { type: 'string' }, + { type: 'number' }, + { + type: 'array', + items: { + anyOf: [ + { type: 'string' }, + { type: 'number' }, + { + type: 'array', + items: { + anyOf: [{ type: 'number' }, { type: 'string' }] + } + } + ] + } + } + ], + description: 'Color of the WebGL layer' + }, + symbologyState: { + type: 'object', + description: 'Symbology configuration for the layer', + required: ['renderType'], + properties: { + renderType: { type: 'string' }, + band: { type: 'number' }, + redBand: { type: 'number' }, + greenBand: { type: 'number' }, + blueBand: { type: 'number' }, + alphaBand: { type: 'number' }, + interpolation: { + type: 'string', + enum: ['discrete', 'linear', 'exact'] + }, + colorRamp: { + type: 'string', + default: 'viridis' + }, + nClasses: { + type: 'string', + default: '9' + }, + mode: { + type: 'string', + default: 'equal interval', + enum: ['continuous', 'equal interval', 'quantile'] + } + } + } + } + } + } + } + }, + execute: (async (args: { + filePath: string; + Name: string; + parameters: { + source: { + urls: { url: string; min?: number; max?: number }[]; + normalize?: boolean; + wrapX?: boolean; + interpolate?: boolean; + }; + opacity?: number; + color?: any; + symbologyState?: Record; + }; + }) => { + const { filePath, Name, parameters } = args; + const current = tracker.find(w => w.model.filePath === filePath); + if (!current) { + console.warn('No JupyterGIS widget found for', filePath); + return; + } + + const model: IJupyterGISModel = current.model; + const sharedModel = model.sharedModel; + if (!sharedModel.editable) { + console.warn('Shared model not editable'); + return; + } + + const sourceId = UUID.uuid4(); + const layerId = UUID.uuid4(); + + const sourceModel: IJGISSource = { + type: 'GeoTiffSource', + name: `${Name} Source`, + parameters: { + urls: parameters.source.urls, + normalize: parameters.source.normalize ?? true, + wrapX: parameters.source.wrapX ?? false, + interpolate: parameters.source.interpolate ?? false + } + }; + + sharedModel.addSource(sourceId, sourceModel); + + const layerModel: IJGISLayer = { + type: 'WebGlLayer', + name: Name, + visible: true, + parameters: { + source: sourceId, + opacity: parameters.opacity ?? 1, + color: parameters.color, + symbologyState: parameters.symbologyState ?? { renderType: 'continuous' } + } + }; + + model.addLayer(layerId, layerModel); + }) as any + }); } From f1a942b9e83f32f819022427c72b1e6c27d70f44 Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Fri, 31 Oct 2025 10:55:30 +0530 Subject: [PATCH 11/54] `newShapefileWithParams` --- .../base/src/commands/operationCommands.ts | 170 ++++++++++++++++++ 1 file changed, 170 insertions(+) diff --git a/packages/base/src/commands/operationCommands.ts b/packages/base/src/commands/operationCommands.ts index e37f4a251..2749534ba 100644 --- a/packages/base/src/commands/operationCommands.ts +++ b/packages/base/src/commands/operationCommands.ts @@ -18,6 +18,7 @@ export namespace LayerCreationCommandIDs { export const newImageWithParams = 'jupytergis:newImageWithParams'; export const newVideoWithParams = 'jupytergis:newVideoWithParams'; export const newGeoTiffWithParams = 'jupytergis:newGeoTiffWithParams'; + export const newShapefileWithParams = 'jupytergis:newShapefileWithParams'; } export function addLayerCreationCommands(options: { @@ -1080,4 +1081,173 @@ export function addLayerCreationCommands(options: { model.addLayer(layerId, layerModel); }) as any }); + + commands.addCommand(LayerCreationCommandIDs.newShapefileWithParams, { + label: trans.__('New Shapefile Layer From Parameters'), + isEnabled: () => true, + describedBy: { + args: { + type: 'object', + required: ['filePath', 'Name', 'parameters'], + properties: { + filePath: { + type: 'string', + description: 'Path to the .jGIS file to modify' + }, + Name: { + type: 'string', + description: 'Name of the new Shapefile layer' + }, + parameters: { + type: 'object', + required: ['source'], + properties: { + source: { + type: 'object', + description: 'Shapefile source configuration', + required: ['path'], + properties: { + path: { + type: 'string', + description: 'Path to the shapefile (.shp, .zip, or folder URL)' + }, + attribution: { + type: 'string', + description: 'Attribution for the shapefile source', + default: '' + }, + projection: { + type: 'string', + description: 'Projection for the shapefile (optional)', + default: 'WGS84' + }, + encoding: { + type: 'string', + description: 'DBF encoding (optional)', + default: 'UTF-8' + }, + additionalFiles: { + type: 'object', + description: + 'Additional files (.dbf, .prj, .cpg) associated with the shapefile', + additionalProperties: { type: 'string' }, + default: {} + } + } + }, + color: { + type: 'object', + description: 'Color configuration for the layer' + }, + opacity: { + type: 'number', + description: 'Layer opacity (0–1)', + default: 1, + minimum: 0, + maximum: 1, + multipleOf: 0.1 + }, + symbologyState: { + type: 'object', + description: 'Symbology configuration for the layer', + required: ['renderType'], + properties: { + renderType: { + type: 'string', + enum: ['Single Symbol', 'Graduated', 'Categorized'] + }, + value: { type: 'string' }, + method: { + type: 'string', + enum: ['color', 'radius'] + }, + colorRamp: { + type: 'string', + default: 'viridis' + }, + nClasses: { + type: 'string', + default: '9' + }, + mode: { + type: 'string', + default: 'equal interval', + enum: [ + 'quantile', + 'equal interval', + 'jenks', + 'pretty', + 'logarithmic' + ] + } + } + } + } + } + } + } + }, + execute: (async (args: { + filePath: string; + Name: string; + parameters: { + source: { + path: string; + attribution?: string; + projection?: string; + encoding?: string; + additionalFiles?: Record; + }; + color?: Record; + opacity?: number; + symbologyState?: Record; + }; + }) => { + const { filePath, Name, parameters } = args; + const current = tracker.find(w => w.model.filePath === filePath); + if (!current) { + console.warn('No JupyterGIS widget found for', filePath); + return; + } + + const model: IJupyterGISModel = current.model; + const sharedModel = model.sharedModel; + if (!sharedModel.editable) { + console.warn('Shared model not editable'); + return; + } + + const sourceId = UUID.uuid4(); + const layerId = UUID.uuid4(); + + const sourceModel: IJGISSource = { + type: 'ShapefileSource', + name: `${Name} Source`, + parameters: { + path: parameters.source.path, + attribution: parameters.source.attribution ?? '', + projection: parameters.source.projection ?? 'WGS84', + encoding: parameters.source.encoding ?? 'UTF-8', + additionalFiles: parameters.source.additionalFiles ?? {} + } + }; + + sharedModel.addSource(sourceId, sourceModel); + + const layerModel: IJGISLayer = { + type: 'VectorLayer', + name: Name, + visible: true, + parameters: { + source: sourceId, + color: parameters.color ?? {}, + opacity: parameters.opacity ?? 1, + symbologyState: + parameters.symbologyState ?? { renderType: 'Single Symbol' } + } + }; + + model.addLayer(layerId, layerModel); + }) as any + }); } From afb13c125babaa8565f24c5fe696435bc4541a24 Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Fri, 31 Oct 2025 15:34:35 +0530 Subject: [PATCH 12/54] add undo redo in doc action commands --- .../src/commands/documentActionCommands.ts | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 packages/base/src/commands/documentActionCommands.ts diff --git a/packages/base/src/commands/documentActionCommands.ts b/packages/base/src/commands/documentActionCommands.ts new file mode 100644 index 000000000..827fe7130 --- /dev/null +++ b/packages/base/src/commands/documentActionCommands.ts @@ -0,0 +1,65 @@ +import { IRenderMime } from '@jupyterlab/rendermime'; +import { CommandRegistry } from '@lumino/commands'; + +import { JupyterGISTracker } from '../types'; + +export namespace DocumentActionCommandIDs { + export const undoWithParams = 'jupytergis:undoWithParams'; + export const redoWithParams = 'jupytergis:redoWithParams'; +} + +export function addDocumentActionCommands(options: { + tracker: JupyterGISTracker; + commands: CommandRegistry; + trans: IRenderMime.TranslationBundle; +}) { + const { commands, tracker, trans } = options; + + commands.addCommand(DocumentActionCommandIDs.undoWithParams, { + label: trans.__('Undo from file name'), + isEnabled: () => true, + describedBy: { + args: { + type: 'object', + required: ['filePath'], + properties: { + filePath: { + type: 'string', + description: 'The path to the .jGIS file to be modified' + } + } + } + }, + execute: (async (args: { filePath: string }) => { + const { filePath } = args; + const current = tracker.find(w => w.model.filePath === filePath); + if (current) { + return current.model.sharedModel.undo(); + } + }) as any + }); + + commands.addCommand(DocumentActionCommandIDs.redoWithParams, { + label: trans.__('Redo from file name'), + isEnabled: () => true, + describedBy: { + args: { + type: 'object', + required: ['filePath'], + properties: { + filePath: { + type: 'string', + description: 'The path to the .jGIS file to be modified' + } + } + } + }, + execute: (async (args: { filePath: string }) => { + const { filePath } = args; + const current = tracker.find(w => w.model.filePath === filePath); + if (current) { + return current.model.sharedModel.redo(); + } + }) as any + }); +} From 112774144bc05ef6967ca1e1495ea97f2c6be462 Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Fri, 31 Oct 2025 15:34:59 +0530 Subject: [PATCH 13/54] `identifyWithParams` --- .../src/commands/documentActionCommands.ts | 55 +++++++++++++++++++ packages/base/src/commands/index.ts | 2 + 2 files changed, 57 insertions(+) diff --git a/packages/base/src/commands/documentActionCommands.ts b/packages/base/src/commands/documentActionCommands.ts index 827fe7130..1fa0244d2 100644 --- a/packages/base/src/commands/documentActionCommands.ts +++ b/packages/base/src/commands/documentActionCommands.ts @@ -1,11 +1,13 @@ import { IRenderMime } from '@jupyterlab/rendermime'; import { CommandRegistry } from '@lumino/commands'; +import { getSingleSelectedLayer } from '../processing'; import { JupyterGISTracker } from '../types'; export namespace DocumentActionCommandIDs { export const undoWithParams = 'jupytergis:undoWithParams'; export const redoWithParams = 'jupytergis:redoWithParams'; + export const identifyWithParams = 'jupytergis:identifyWithParams'; } export function addDocumentActionCommands(options: { @@ -62,4 +64,57 @@ export function addDocumentActionCommands(options: { } }) as any }); + + commands.addCommand(DocumentActionCommandIDs.identifyWithParams, { + label: trans.__('Identify features from file name'), + isEnabled: () => true, + describedBy: { + args: { + type: 'object', + required: ['filePath'], + properties: { + filePath: { + type: 'string', + description: + 'The path to the .jGIS file where identify mode will be toggled' + } + } + } + }, + execute: (async (args: { filePath: string }) => { + const { filePath } = args; + const current = tracker.find(w => w.model.filePath === filePath); + if (!current) { + console.warn('No active JupyterGIS widget found for', filePath); + return; + } + + // Get currently selected layer + const selectedLayer = getSingleSelectedLayer(tracker); + if (!selectedLayer) { + console.warn('No selected layer found'); + return; + } + + const canIdentify = [ + 'VectorLayer', + 'ShapefileLayer', + 'WebGlLayer', + 'VectorTileLayer' + ].includes(selectedLayer.type); + + if (current.model.currentMode === 'identifying' && !canIdentify) { + current.model.currentMode = 'panning'; + current.node.classList.remove('jGIS-identify-tool'); + return; + } + + // Toggle identify tool + current.node.classList.toggle('jGIS-identify-tool'); + current.model.toggleIdentify(); + + // Notify change + commands.notifyCommandChanged(DocumentActionCommandIDs.identifyWithParams); + }) as any + }); } diff --git a/packages/base/src/commands/index.ts b/packages/base/src/commands/index.ts index 6493d37eb..eea1d63bf 100644 --- a/packages/base/src/commands/index.ts +++ b/packages/base/src/commands/index.ts @@ -32,6 +32,7 @@ import { addProcessingCommands } from '../processing/processingCommands'; import { getGeoJSONDataFromLayerSource, downloadFile } from '../tools'; import { JupyterGISTracker } from '../types'; import { JupyterGISDocumentWidget } from '../widget'; +import { addDocumentActionCommands } from './documentActionCommands'; import { addLayerCreationCommands } from './operationCommands'; interface ICreateEntry { @@ -72,6 +73,7 @@ export function addCommands( const { commands } = app; addLayerCreationCommands({ tracker, commands, trans }); + addDocumentActionCommands({ tracker, commands, trans }); commands.addCommand(CommandIDs.symbology, { label: trans.__('Edit Symbology'), From ba83fa1a97f0c0aa004feded574f1e2e49894b0b Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Fri, 31 Oct 2025 16:19:25 +0530 Subject: [PATCH 14/54] `temporalControllerWithParams` --- .../src/commands/documentActionCommands.ts | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/packages/base/src/commands/documentActionCommands.ts b/packages/base/src/commands/documentActionCommands.ts index 1fa0244d2..7e7fe4389 100644 --- a/packages/base/src/commands/documentActionCommands.ts +++ b/packages/base/src/commands/documentActionCommands.ts @@ -8,6 +8,7 @@ export namespace DocumentActionCommandIDs { export const undoWithParams = 'jupytergis:undoWithParams'; export const redoWithParams = 'jupytergis:redoWithParams'; export const identifyWithParams = 'jupytergis:identifyWithParams'; + export const temporalControllerWithParams = 'jupytergis:temporalControllerWithParams'; } export function addDocumentActionCommands(options: { @@ -117,4 +118,62 @@ export function addDocumentActionCommands(options: { commands.notifyCommandChanged(DocumentActionCommandIDs.identifyWithParams); }) as any }); + + commands.addCommand(DocumentActionCommandIDs.temporalControllerWithParams, { + label: trans.__('Toggle Temporal Controller from file name'), + isEnabled: () => true, + describedBy: { + args: { + type: 'object', + required: ['filePath'], + properties: { + filePath: { + type: 'string', + description: + 'Path to the .jGIS file whose temporal controller should be toggled' + } + } + } + }, + isToggled: () => { + const current = tracker.currentWidget; + return current?.model.isTemporalControllerActive || false; + }, + execute: (async (args: { filePath: string }) => { + const { filePath } = args; + const current = tracker.find(w => w.model.filePath === filePath); + if (!current) { + console.warn('No JupyterGIS widget found for', filePath); + return; + } + + const model = current.model; + const selectedLayers = model.localState?.selected?.value; + if (!selectedLayers) { + console.warn('No layer selected'); + return; + } + + const layerId = Object.keys(selectedLayers)[0]; + const layerType = model.getLayer(layerId)?.type; + if (!layerType) { + console.warn('Selected layer has no type'); + return; + } + + const isSelectionValid = + Object.keys(selectedLayers).length === 1 && + !model.getSource(layerId) && + ['VectorLayer', 'HeatmapLayer'].includes(layerType); + + if (!isSelectionValid && model.isTemporalControllerActive) { + model.toggleTemporalController(); + commands.notifyCommandChanged(DocumentActionCommandIDs.temporalControllerWithParams); + return; + } + + model.toggleTemporalController(); + commands.notifyCommandChanged(DocumentActionCommandIDs.temporalControllerWithParams); + }) as any + }); } From abdd6702c25293bc27809cf9441dcd25bb7aef1e Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Fri, 31 Oct 2025 17:07:29 +0530 Subject: [PATCH 15/54] `renameLayerWithParams` --- .../src/commands/documentActionCommands.ts | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/packages/base/src/commands/documentActionCommands.ts b/packages/base/src/commands/documentActionCommands.ts index 7e7fe4389..482f0d852 100644 --- a/packages/base/src/commands/documentActionCommands.ts +++ b/packages/base/src/commands/documentActionCommands.ts @@ -9,6 +9,7 @@ export namespace DocumentActionCommandIDs { export const redoWithParams = 'jupytergis:redoWithParams'; export const identifyWithParams = 'jupytergis:identifyWithParams'; export const temporalControllerWithParams = 'jupytergis:temporalControllerWithParams'; + export const renameLayerWithParams = 'jupytergis:renameLayerWithParams'; } export function addDocumentActionCommands(options: { @@ -176,4 +177,46 @@ export function addDocumentActionCommands(options: { commands.notifyCommandChanged(DocumentActionCommandIDs.temporalControllerWithParams); }) as any }); + + commands.addCommand(DocumentActionCommandIDs.renameLayerWithParams, { + label: trans.__('Rename layer from file name'), + isEnabled: () => true, + describedBy: { + args: { + type: 'object', + required: ['filePath', 'layerId', 'newName'], + properties: { + filePath: { + type: 'string', + description: 'The path to the .jGIS file to be modified' + }, + layerId: { + type: 'string', + description: 'The ID of the layer to be renamed' + }, + newName: { + type: 'string', + description: 'The new name for the layer' + } + } + } + }, + execute: (async (args: { filePath: string; layerId: string; newName: string }) => { + const { filePath, layerId, newName } = args; + const current = tracker.find(w => w.model.filePath === filePath); + + if (!current || !current.model.sharedModel.editable) { + return; + } + + const sharedModel = current.model.sharedModel; + const layer = sharedModel.layers[layerId]; + if (!layer) { + return; + } + + layer.name = newName; + sharedModel.updateLayer(layerId, layer); + }) as any +}); } From 9052a4fe1e3002d802ae7efd9f0a7801dd23df13 Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Fri, 31 Oct 2025 17:13:25 +0530 Subject: [PATCH 16/54] lint --- .../src/commands/documentActionCommands.ts | 137 +++--- .../base/src/commands/operationCommands.ts | 465 +++++++++--------- 2 files changed, 312 insertions(+), 290 deletions(-) diff --git a/packages/base/src/commands/documentActionCommands.ts b/packages/base/src/commands/documentActionCommands.ts index 482f0d852..85a99ceb3 100644 --- a/packages/base/src/commands/documentActionCommands.ts +++ b/packages/base/src/commands/documentActionCommands.ts @@ -8,7 +8,8 @@ export namespace DocumentActionCommandIDs { export const undoWithParams = 'jupytergis:undoWithParams'; export const redoWithParams = 'jupytergis:redoWithParams'; export const identifyWithParams = 'jupytergis:identifyWithParams'; - export const temporalControllerWithParams = 'jupytergis:temporalControllerWithParams'; + export const temporalControllerWithParams = + 'jupytergis:temporalControllerWithParams'; export const renameLayerWithParams = 'jupytergis:renameLayerWithParams'; } @@ -29,10 +30,10 @@ export function addDocumentActionCommands(options: { properties: { filePath: { type: 'string', - description: 'The path to the .jGIS file to be modified' - } - } - } + description: 'The path to the .jGIS file to be modified', + }, + }, + }, }, execute: (async (args: { filePath: string }) => { const { filePath } = args; @@ -40,7 +41,7 @@ export function addDocumentActionCommands(options: { if (current) { return current.model.sharedModel.undo(); } - }) as any + }) as any, }); commands.addCommand(DocumentActionCommandIDs.redoWithParams, { @@ -53,10 +54,10 @@ export function addDocumentActionCommands(options: { properties: { filePath: { type: 'string', - description: 'The path to the .jGIS file to be modified' - } - } - } + description: 'The path to the .jGIS file to be modified', + }, + }, + }, }, execute: (async (args: { filePath: string }) => { const { filePath } = args; @@ -64,7 +65,7 @@ export function addDocumentActionCommands(options: { if (current) { return current.model.sharedModel.redo(); } - }) as any + }) as any, }); commands.addCommand(DocumentActionCommandIDs.identifyWithParams, { @@ -78,10 +79,10 @@ export function addDocumentActionCommands(options: { filePath: { type: 'string', description: - 'The path to the .jGIS file where identify mode will be toggled' - } - } - } + 'The path to the .jGIS file where identify mode will be toggled', + }, + }, + }, }, execute: (async (args: { filePath: string }) => { const { filePath } = args; @@ -102,7 +103,7 @@ export function addDocumentActionCommands(options: { 'VectorLayer', 'ShapefileLayer', 'WebGlLayer', - 'VectorTileLayer' + 'VectorTileLayer', ].includes(selectedLayer.type); if (current.model.currentMode === 'identifying' && !canIdentify) { @@ -116,8 +117,10 @@ export function addDocumentActionCommands(options: { current.model.toggleIdentify(); // Notify change - commands.notifyCommandChanged(DocumentActionCommandIDs.identifyWithParams); - }) as any + commands.notifyCommandChanged( + DocumentActionCommandIDs.identifyWithParams, + ); + }) as any, }); commands.addCommand(DocumentActionCommandIDs.temporalControllerWithParams, { @@ -131,10 +134,10 @@ export function addDocumentActionCommands(options: { filePath: { type: 'string', description: - 'Path to the .jGIS file whose temporal controller should be toggled' - } - } - } + 'Path to the .jGIS file whose temporal controller should be toggled', + }, + }, + }, }, isToggled: () => { const current = tracker.currentWidget; @@ -169,54 +172,62 @@ export function addDocumentActionCommands(options: { if (!isSelectionValid && model.isTemporalControllerActive) { model.toggleTemporalController(); - commands.notifyCommandChanged(DocumentActionCommandIDs.temporalControllerWithParams); + commands.notifyCommandChanged( + DocumentActionCommandIDs.temporalControllerWithParams, + ); return; } model.toggleTemporalController(); - commands.notifyCommandChanged(DocumentActionCommandIDs.temporalControllerWithParams); - }) as any + commands.notifyCommandChanged( + DocumentActionCommandIDs.temporalControllerWithParams, + ); + }) as any, }); commands.addCommand(DocumentActionCommandIDs.renameLayerWithParams, { - label: trans.__('Rename layer from file name'), - isEnabled: () => true, - describedBy: { - args: { - type: 'object', - required: ['filePath', 'layerId', 'newName'], - properties: { - filePath: { - type: 'string', - description: 'The path to the .jGIS file to be modified' - }, - layerId: { - type: 'string', - description: 'The ID of the layer to be renamed' + label: trans.__('Rename layer from file name'), + isEnabled: () => true, + describedBy: { + args: { + type: 'object', + required: ['filePath', 'layerId', 'newName'], + properties: { + filePath: { + type: 'string', + description: 'The path to the .jGIS file to be modified', + }, + layerId: { + type: 'string', + description: 'The ID of the layer to be renamed', + }, + newName: { + type: 'string', + description: 'The new name for the layer', + }, }, - newName: { - type: 'string', - description: 'The new name for the layer' - } + }, + }, + execute: (async (args: { + filePath: string; + layerId: string; + newName: string; + }) => { + const { filePath, layerId, newName } = args; + const current = tracker.find(w => w.model.filePath === filePath); + + if (!current || !current.model.sharedModel.editable) { + return; + } + + const sharedModel = current.model.sharedModel; + const layer = sharedModel.layers[layerId]; + if (!layer) { + return; } - } - }, - execute: (async (args: { filePath: string; layerId: string; newName: string }) => { - const { filePath, layerId, newName } = args; - const current = tracker.find(w => w.model.filePath === filePath); - - if (!current || !current.model.sharedModel.editable) { - return; - } - - const sharedModel = current.model.sharedModel; - const layer = sharedModel.layers[layerId]; - if (!layer) { - return; - } - - layer.name = newName; - sharedModel.updateLayer(layerId, layer); - }) as any -}); + + layer.name = newName; + sharedModel.updateLayer(layerId, layer); + }) as any, + }); } diff --git a/packages/base/src/commands/operationCommands.ts b/packages/base/src/commands/operationCommands.ts index 2749534ba..7039f4b48 100644 --- a/packages/base/src/commands/operationCommands.ts +++ b/packages/base/src/commands/operationCommands.ts @@ -1,8 +1,4 @@ -import { - IJupyterGISModel, - IJGISLayer, - IJGISSource -} from '@jupytergis/schema'; +import { IJupyterGISModel, IJGISLayer, IJGISSource } from '@jupytergis/schema'; import { IRenderMime } from '@jupyterlab/rendermime'; import { CommandRegistry } from '@lumino/commands'; import { UUID } from '@lumino/coreutils'; @@ -38,11 +34,11 @@ export function addLayerCreationCommands(options: { properties: { filePath: { type: 'string', - description: 'The path to the .jGIS file to modify' + description: 'The path to the .jGIS file to modify', }, Name: { type: 'string', - description: 'The name of the new GeoJSON layer' + description: 'The name of the new GeoJSON layer', }, parameters: { type: 'object', @@ -55,22 +51,22 @@ export function addLayerCreationCommands(options: { properties: { path: { type: 'string', - description: 'The path to the GeoJSON file' + description: 'The path to the GeoJSON file', }, data: { type: 'object', - description: 'The GeoJSON data object' + description: 'The GeoJSON data object', }, valid: { type: 'boolean', description: 'Whether the data are valid', - readOnly: true - } - } + readOnly: true, + }, + }, }, color: { type: 'object', - description: 'The color configuration for the layer' + description: 'The color configuration for the layer', }, opacity: { type: 'number', @@ -78,7 +74,7 @@ export function addLayerCreationCommands(options: { default: 1, minimum: 0, maximum: 1, - multipleOf: 0.1 + multipleOf: 0.1, }, symbologyState: { type: 'object', @@ -87,20 +83,20 @@ export function addLayerCreationCommands(options: { properties: { renderType: { type: 'string', - enum: ['Single Symbol', 'Graduated', 'Categorized'] + enum: ['Single Symbol', 'Graduated', 'Categorized'], }, value: { type: 'string' }, method: { type: 'string', - enum: ['color', 'radius'] + enum: ['color', 'radius'], }, colorRamp: { type: 'string', - default: 'viridis' + default: 'viridis', }, nClasses: { type: 'string', - default: '9' + default: '9', }, mode: { type: 'string', @@ -110,15 +106,15 @@ export function addLayerCreationCommands(options: { 'equal interval', 'jenks', 'pretty', - 'logarithmic' - ] - } - } - } - } - } - } - } + 'logarithmic', + ], + }, + }, + }, + }, + }, + }, + }, }, execute: (async (args: { filePath: string; @@ -147,7 +143,7 @@ export function addLayerCreationCommands(options: { const sourceModel: IJGISSource = { type: 'GeoJSONSource', name: `${Name} Source`, - parameters: parameters.source + parameters: parameters.source, }; sharedModel.addSource(sourceId, sourceModel); @@ -160,12 +156,12 @@ export function addLayerCreationCommands(options: { color: parameters.color ?? {}, opacity: parameters.opacity ?? 1, symbologyState: parameters.symbologyState, - source: sourceId - } + source: sourceId, + }, }; model.addLayer(layerId, layerModel); - }) as any + }) as any, }); commands.addCommand(LayerCreationCommandIDs.newRasterWithParams, { @@ -178,11 +174,11 @@ export function addLayerCreationCommands(options: { properties: { filePath: { type: 'string', - description: 'Path to the .jGIS file to modify' + description: 'Path to the .jGIS file to modify', }, Name: { type: 'string', - description: 'The name of the new Raster Tile Layer' + description: 'The name of the new Raster Tile Layer', }, parameters: { type: 'object', @@ -195,59 +191,59 @@ export function addLayerCreationCommands(options: { properties: { url: { type: 'string', - description: 'The URL to the tile provider' + description: 'The URL to the tile provider', }, minZoom: { type: 'number', minimum: 0, maximum: 24, default: 0, - description: 'Minimum zoom level' + description: 'Minimum zoom level', }, maxZoom: { type: 'number', minimum: 0, maximum: 24, default: 24, - description: 'Maximum zoom level' + description: 'Maximum zoom level', }, attribution: { type: 'string', default: '', - description: 'Attribution for the raster source' + description: 'Attribution for the raster source', }, htmlAttribution: { type: 'string', default: '', - description: 'HTML attribution for the raster source' + description: 'HTML attribution for the raster source', }, provider: { type: 'string', default: '', - description: 'Provider name' + description: 'Provider name', }, bounds: { type: 'array', description: 'Bounds of the source', items: { type: 'array', - items: { type: 'number' } + items: { type: 'number' }, }, - default: [] + default: [], }, urlParameters: { type: 'object', description: 'Extra URL parameters', additionalProperties: { type: 'string' }, - default: {} + default: {}, }, interpolate: { type: 'boolean', description: 'Interpolate between grid cells when overzooming?', - default: false - } - } + default: false, + }, + }, }, opacity: { type: 'number', @@ -255,12 +251,12 @@ export function addLayerCreationCommands(options: { default: 1, minimum: 0, maximum: 1, - multipleOf: 0.1 - } - } - } - } - } + multipleOf: 0.1, + }, + }, + }, + }, + }, }, execute: (async (args: { filePath: string; @@ -287,7 +283,7 @@ export function addLayerCreationCommands(options: { const sourceModel: IJGISSource = { type: 'RasterSource', name: `${Name} Source`, - parameters: parameters.source + parameters: parameters.source, }; sharedModel.addSource(sourceId, sourceModel); @@ -298,12 +294,12 @@ export function addLayerCreationCommands(options: { visible: true, parameters: { opacity: parameters.opacity ?? 1, - source: sourceId - } + source: sourceId, + }, }; model.addLayer(layerId, layerModel); - }) as any + }) as any, }); commands.addCommand(LayerCreationCommandIDs.newVectorTileWithParams, { @@ -316,11 +312,11 @@ export function addLayerCreationCommands(options: { properties: { filePath: { type: 'string', - description: 'Path to the .jGIS file to modify' + description: 'Path to the .jGIS file to modify', }, Name: { type: 'string', - description: 'The name of the new Vector Tile Layer' + description: 'The name of the new Vector Tile Layer', }, parameters: { type: 'object', @@ -333,39 +329,39 @@ export function addLayerCreationCommands(options: { properties: { url: { type: 'string', - description: 'The URL to the tile provider' + description: 'The URL to the tile provider', }, minZoom: { type: 'number', minimum: 0, maximum: 24, - description: 'Minimum zoom level for the vector source' + description: 'Minimum zoom level for the vector source', }, maxZoom: { type: 'number', minimum: 0, maximum: 24, - description: 'Maximum zoom level for the vector source' + description: 'Maximum zoom level for the vector source', }, attribution: { type: 'string', - description: 'Attribution for the vector source' + description: 'Attribution for the vector source', }, provider: { type: 'string', description: 'The map provider', - readOnly: true + readOnly: true, }, urlParameters: { type: 'object', description: 'Additional URL parameters', - additionalProperties: { type: 'string' } - } - } + additionalProperties: { type: 'string' }, + }, + }, }, color: { type: 'object', - description: 'Color styling configuration for the layer' + description: 'Color styling configuration for the layer', }, opacity: { type: 'number', @@ -373,12 +369,12 @@ export function addLayerCreationCommands(options: { default: 1, minimum: 0, maximum: 1, - multipleOf: 0.1 - } - } - } - } - } + multipleOf: 0.1, + }, + }, + }, + }, + }, }, execute: (async (args: { filePath: string; @@ -406,7 +402,7 @@ export function addLayerCreationCommands(options: { const sourceModel: IJGISSource = { type: 'VectorTileSource', name: `${Name} Source`, - parameters: parameters.source + parameters: parameters.source, }; sharedModel.addSource(sourceId, sourceModel); @@ -418,12 +414,12 @@ export function addLayerCreationCommands(options: { parameters: { color: parameters.color ?? {}, opacity: parameters.opacity ?? 1, - source: sourceId - } + source: sourceId, + }, }; model.addLayer(layerId, layerModel); - }) as any + }) as any, }); commands.addCommand(LayerCreationCommandIDs.newGeoParquetWithParams, { @@ -436,11 +432,11 @@ export function addLayerCreationCommands(options: { properties: { filePath: { type: 'string', - description: 'Path to the .jGIS file to modify' + description: 'Path to the .jGIS file to modify', }, Name: { type: 'string', - description: 'The name of the new GeoParquet layer' + description: 'The name of the new GeoParquet layer', }, parameters: { type: 'object', @@ -453,25 +449,25 @@ export function addLayerCreationCommands(options: { properties: { path: { type: 'string', - description: 'The path to the GeoParquet source' + description: 'The path to the GeoParquet source', }, attribution: { type: 'string', readOnly: true, description: 'Attribution for the GeoParquet source', - default: '' + default: '', }, projection: { type: 'string', description: 'Projection information for the GeoParquet data', - default: 'EPSG:4326' - } - } + default: 'EPSG:4326', + }, + }, }, color: { type: 'object', - description: 'Color styling for the layer' + description: 'Color styling for the layer', }, opacity: { type: 'number', @@ -479,7 +475,7 @@ export function addLayerCreationCommands(options: { default: 1, minimum: 0, maximum: 1, - multipleOf: 0.1 + multipleOf: 0.1, }, symbologyState: { type: 'object', @@ -488,22 +484,22 @@ export function addLayerCreationCommands(options: { properties: { renderType: { type: 'string', - enum: ['Single Symbol', 'Graduated', 'Categorized'] + enum: ['Single Symbol', 'Graduated', 'Categorized'], }, value: { - type: 'string' + type: 'string', }, method: { type: 'string', - enum: ['color', 'radius'] + enum: ['color', 'radius'], }, colorRamp: { type: 'string', - default: 'viridis' + default: 'viridis', }, nClasses: { type: 'string', - default: '9' + default: '9', }, mode: { type: 'string', @@ -513,16 +509,16 @@ export function addLayerCreationCommands(options: { 'equal interval', 'jenks', 'pretty', - 'logarithmic' - ] - } + 'logarithmic', + ], + }, }, - additionalProperties: false - } - } - } - } - } + additionalProperties: false, + }, + }, + }, + }, + }, }, execute: (async (args: { filePath: string; @@ -551,7 +547,7 @@ export function addLayerCreationCommands(options: { const sourceModel: IJGISSource = { type: 'GeoParquetSource', name: `${Name} Source`, - parameters: parameters.source + parameters: parameters.source, }; sharedModel.addSource(sourceId, sourceModel); @@ -564,12 +560,12 @@ export function addLayerCreationCommands(options: { color: parameters.color ?? {}, opacity: parameters.opacity ?? 1, symbologyState: parameters.symbologyState, - source: sourceId - } + source: sourceId, + }, }; model.addLayer(layerId, layerModel); - }) as any + }) as any, }); commands.addCommand(LayerCreationCommandIDs.newHillshadeWithParams, { @@ -582,11 +578,11 @@ export function addLayerCreationCommands(options: { properties: { filePath: { type: 'string', - description: 'Path to the .jGIS file to modify' + description: 'Path to the .jGIS file to modify', }, Name: { type: 'string', - description: 'The name of the new Hillshade layer' + description: 'The name of the new Hillshade layer', }, parameters: { type: 'object', @@ -599,38 +595,38 @@ export function addLayerCreationCommands(options: { properties: { url: { type: 'string', - description: 'The URL to the DEM tile provider' + description: 'The URL to the DEM tile provider', }, attribution: { type: 'string', description: - 'Attribution for the raster-dem source (optional)' + 'Attribution for the raster-dem source (optional)', }, urlParameters: { type: 'object', description: 'Additional URL parameters for the raster-dem source', additionalProperties: { - type: 'string' - } + type: 'string', + }, }, interpolate: { type: 'boolean', description: 'Interpolate between grid cells when overzooming', - default: false - } - } + default: false, + }, + }, }, shadowColor: { type: 'string', description: 'The color of the shadows', - default: '#473B24' - } - } - } - } - } + default: '#473B24', + }, + }, + }, + }, + }, }, execute: (async (args: { filePath: string; @@ -657,7 +653,7 @@ export function addLayerCreationCommands(options: { const sourceModel: IJGISSource = { type: 'RasterDemSource', name: `${Name} Source`, - parameters: parameters.source + parameters: parameters.source, }; sharedModel.addSource(sourceId, sourceModel); @@ -668,12 +664,12 @@ export function addLayerCreationCommands(options: { visible: true, parameters: { shadowColor: parameters.shadowColor ?? '#473B24', - source: sourceId - } + source: sourceId, + }, }; model.addLayer(layerId, layerModel); - }) as any + }) as any, }); commands.addCommand(LayerCreationCommandIDs.newImageWithParams, { @@ -686,11 +682,11 @@ export function addLayerCreationCommands(options: { properties: { filePath: { type: 'string', - description: 'Path to the .jGIS file to modify' + description: 'Path to the .jGIS file to modify', }, Name: { type: 'string', - description: 'The name of the new Image layer' + description: 'The name of the new Image layer', }, parameters: { type: 'object', @@ -703,7 +699,7 @@ export function addLayerCreationCommands(options: { properties: { path: { type: 'string', - description: 'Path that points to the image' + description: 'Path that points to the image', }, coordinates: { type: 'array', @@ -715,16 +711,16 @@ export function addLayerCreationCommands(options: { type: 'array', minItems: 2, maxItems: 2, - items: { type: 'number' } - } + items: { type: 'number' }, + }, }, interpolate: { type: 'boolean', description: 'Whether to interpolate between grid cells when overzooming', - default: false - } - } + default: false, + }, + }, }, opacity: { type: 'number', @@ -732,12 +728,12 @@ export function addLayerCreationCommands(options: { default: 1, minimum: 0, maximum: 1, - multipleOf: 0.1 - } - } - } - } - } + multipleOf: 0.1, + }, + }, + }, + }, + }, }, execute: (async (args: { filePath: string; @@ -771,8 +767,8 @@ export function addLayerCreationCommands(options: { parameters: { path: parameters.source.path, coordinates: parameters.source.coordinates, - interpolate: parameters.source.interpolate ?? false - } + interpolate: parameters.source.interpolate ?? false, + }, }; sharedModel.addSource(sourceId, sourceModel); @@ -783,12 +779,12 @@ export function addLayerCreationCommands(options: { visible: true, parameters: { source: sourceId, - opacity: parameters.opacity ?? 1 - } + opacity: parameters.opacity ?? 1, + }, }; model.addLayer(layerId, layerModel); - }) as any + }) as any, }); commands.addCommand(LayerCreationCommandIDs.newVideoWithParams, { @@ -801,11 +797,11 @@ export function addLayerCreationCommands(options: { properties: { filePath: { type: 'string', - description: 'Path to the .jGIS file to modify' + description: 'Path to the .jGIS file to modify', }, Name: { type: 'string', - description: 'The name of the new Video layer' + description: 'The name of the new Video layer', }, parameters: { type: 'object', @@ -820,7 +816,7 @@ export function addLayerCreationCommands(options: { type: 'array', description: 'List of video URLs in preferred format order', minItems: 1, - items: { type: 'string' } + items: { type: 'string' }, }, coordinates: { type: 'array', @@ -832,10 +828,10 @@ export function addLayerCreationCommands(options: { type: 'array', minItems: 2, maxItems: 2, - items: { type: 'number' } - } - } - } + items: { type: 'number' }, + }, + }, + }, }, opacity: { type: 'number', @@ -843,12 +839,12 @@ export function addLayerCreationCommands(options: { default: 1, minimum: 0, maximum: 1, - multipleOf: 0.1 - } - } - } - } - } + multipleOf: 0.1, + }, + }, + }, + }, + }, }, execute: (async (args: { filePath: string; @@ -880,8 +876,8 @@ export function addLayerCreationCommands(options: { name: `${Name} Source`, parameters: { urls: parameters.source.urls, - coordinates: parameters.source.coordinates - } + coordinates: parameters.source.coordinates, + }, }; sharedModel.addSource(sourceId, sourceModel); @@ -892,12 +888,12 @@ export function addLayerCreationCommands(options: { visible: true, parameters: { source: sourceId, - opacity: parameters.opacity ?? 1 - } + opacity: parameters.opacity ?? 1, + }, }; model.addLayer(layerId, layerModel); - }) as any + }) as any, }); commands.addCommand(LayerCreationCommandIDs.newGeoTiffWithParams, { @@ -910,11 +906,11 @@ export function addLayerCreationCommands(options: { properties: { filePath: { type: 'string', - description: 'Path to the .jGIS file to modify' + description: 'Path to the .jGIS file to modify', }, Name: { type: 'string', - description: 'Name of the new GeoTIFF layer' + description: 'Name of the new GeoTIFF layer', }, parameters: { type: 'object', @@ -927,34 +923,45 @@ export function addLayerCreationCommands(options: { properties: { urls: { type: 'array', - description: 'List of GeoTIFF URL objects with optional min/max values', + description: + 'List of GeoTIFF URL objects with optional min/max values', minItems: 1, items: { type: 'object', properties: { - url: { type: 'string', description: 'URL to the GeoTIFF file' }, - min: { type: 'number', description: 'Minimum value for scaling' }, - max: { type: 'number', description: 'Maximum value for scaling' } - } - } + url: { + type: 'string', + description: 'URL to the GeoTIFF file', + }, + min: { + type: 'number', + description: 'Minimum value for scaling', + }, + max: { + type: 'number', + description: 'Maximum value for scaling', + }, + }, + }, }, normalize: { type: 'boolean', description: 'Normalize values between 0 and 1 for RGB display; disable to keep raw values', - default: true + default: true, }, wrapX: { type: 'boolean', description: 'Wrap map horizontally?', - default: false + default: false, }, interpolate: { type: 'boolean', - description: 'Interpolate between grid cells when overzooming?', - default: false - } - } + description: + 'Interpolate between grid cells when overzooming?', + default: false, + }, + }, }, opacity: { type: 'number', @@ -962,7 +969,7 @@ export function addLayerCreationCommands(options: { default: 1, minimum: 0, maximum: 1, - multipleOf: 0.1 + multipleOf: 0.1, }, color: { oneOf: [ @@ -977,14 +984,14 @@ export function addLayerCreationCommands(options: { { type: 'array', items: { - anyOf: [{ type: 'number' }, { type: 'string' }] - } - } - ] - } - } + anyOf: [{ type: 'number' }, { type: 'string' }], + }, + }, + ], + }, + }, ], - description: 'Color of the WebGL layer' + description: 'Color of the WebGL layer', }, symbologyState: { type: 'object', @@ -999,27 +1006,27 @@ export function addLayerCreationCommands(options: { alphaBand: { type: 'number' }, interpolation: { type: 'string', - enum: ['discrete', 'linear', 'exact'] + enum: ['discrete', 'linear', 'exact'], }, colorRamp: { type: 'string', - default: 'viridis' + default: 'viridis', }, nClasses: { type: 'string', - default: '9' + default: '9', }, mode: { type: 'string', default: 'equal interval', - enum: ['continuous', 'equal interval', 'quantile'] - } - } - } - } - } - } - } + enum: ['continuous', 'equal interval', 'quantile'], + }, + }, + }, + }, + }, + }, + }, }, execute: (async (args: { filePath: string; @@ -1060,8 +1067,8 @@ export function addLayerCreationCommands(options: { urls: parameters.source.urls, normalize: parameters.source.normalize ?? true, wrapX: parameters.source.wrapX ?? false, - interpolate: parameters.source.interpolate ?? false - } + interpolate: parameters.source.interpolate ?? false, + }, }; sharedModel.addSource(sourceId, sourceModel); @@ -1074,12 +1081,14 @@ export function addLayerCreationCommands(options: { source: sourceId, opacity: parameters.opacity ?? 1, color: parameters.color, - symbologyState: parameters.symbologyState ?? { renderType: 'continuous' } - } + symbologyState: parameters.symbologyState ?? { + renderType: 'continuous', + }, + }, }; model.addLayer(layerId, layerModel); - }) as any + }) as any, }); commands.addCommand(LayerCreationCommandIDs.newShapefileWithParams, { @@ -1092,11 +1101,11 @@ export function addLayerCreationCommands(options: { properties: { filePath: { type: 'string', - description: 'Path to the .jGIS file to modify' + description: 'Path to the .jGIS file to modify', }, Name: { type: 'string', - description: 'Name of the new Shapefile layer' + description: 'Name of the new Shapefile layer', }, parameters: { type: 'object', @@ -1109,35 +1118,36 @@ export function addLayerCreationCommands(options: { properties: { path: { type: 'string', - description: 'Path to the shapefile (.shp, .zip, or folder URL)' + description: + 'Path to the shapefile (.shp, .zip, or folder URL)', }, attribution: { type: 'string', description: 'Attribution for the shapefile source', - default: '' + default: '', }, projection: { type: 'string', description: 'Projection for the shapefile (optional)', - default: 'WGS84' + default: 'WGS84', }, encoding: { type: 'string', description: 'DBF encoding (optional)', - default: 'UTF-8' + default: 'UTF-8', }, additionalFiles: { type: 'object', description: 'Additional files (.dbf, .prj, .cpg) associated with the shapefile', additionalProperties: { type: 'string' }, - default: {} - } - } + default: {}, + }, + }, }, color: { type: 'object', - description: 'Color configuration for the layer' + description: 'Color configuration for the layer', }, opacity: { type: 'number', @@ -1145,7 +1155,7 @@ export function addLayerCreationCommands(options: { default: 1, minimum: 0, maximum: 1, - multipleOf: 0.1 + multipleOf: 0.1, }, symbologyState: { type: 'object', @@ -1154,20 +1164,20 @@ export function addLayerCreationCommands(options: { properties: { renderType: { type: 'string', - enum: ['Single Symbol', 'Graduated', 'Categorized'] + enum: ['Single Symbol', 'Graduated', 'Categorized'], }, value: { type: 'string' }, method: { type: 'string', - enum: ['color', 'radius'] + enum: ['color', 'radius'], }, colorRamp: { type: 'string', - default: 'viridis' + default: 'viridis', }, nClasses: { type: 'string', - default: '9' + default: '9', }, mode: { type: 'string', @@ -1177,15 +1187,15 @@ export function addLayerCreationCommands(options: { 'equal interval', 'jenks', 'pretty', - 'logarithmic' - ] - } - } - } - } - } - } - } + 'logarithmic', + ], + }, + }, + }, + }, + }, + }, + }, }, execute: (async (args: { filePath: string; @@ -1228,8 +1238,8 @@ export function addLayerCreationCommands(options: { attribution: parameters.source.attribution ?? '', projection: parameters.source.projection ?? 'WGS84', encoding: parameters.source.encoding ?? 'UTF-8', - additionalFiles: parameters.source.additionalFiles ?? {} - } + additionalFiles: parameters.source.additionalFiles ?? {}, + }, }; sharedModel.addSource(sourceId, sourceModel); @@ -1242,12 +1252,13 @@ export function addLayerCreationCommands(options: { source: sourceId, color: parameters.color ?? {}, opacity: parameters.opacity ?? 1, - symbologyState: - parameters.symbologyState ?? { renderType: 'Single Symbol' } - } + symbologyState: parameters.symbologyState ?? { + renderType: 'Single Symbol', + }, + }, }; model.addLayer(layerId, layerModel); - }) as any + }) as any, }); } From a8c9e9f8b23578e29c3d98d93cef240c017c738f Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Fri, 31 Oct 2025 17:15:20 +0530 Subject: [PATCH 17/54] `removeLayerWithParams` --- .../src/commands/documentActionCommands.ts | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/packages/base/src/commands/documentActionCommands.ts b/packages/base/src/commands/documentActionCommands.ts index 85a99ceb3..c9078609c 100644 --- a/packages/base/src/commands/documentActionCommands.ts +++ b/packages/base/src/commands/documentActionCommands.ts @@ -11,6 +11,7 @@ export namespace DocumentActionCommandIDs { export const temporalControllerWithParams = 'jupytergis:temporalControllerWithParams'; export const renameLayerWithParams = 'jupytergis:renameLayerWithParams'; + export const removeLayerWithParams = 'jupytergis:removeLayerWithParams'; } export function addDocumentActionCommands(options: { @@ -230,4 +231,41 @@ export function addDocumentActionCommands(options: { sharedModel.updateLayer(layerId, layer); }) as any, }); + + commands.addCommand(DocumentActionCommandIDs.removeLayerWithParams, { + label: trans.__('Remove layer from file name'), + isEnabled: () => true, + describedBy: { + args: { + type: 'object', + required: ['filePath', 'layerId'], + properties: { + filePath: { + type: 'string', + description: 'The path to the .jGIS file to be modified', + }, + layerId: { + type: 'string', + description: 'The ID of the layer to be removed', + }, + }, + }, + }, + execute: ((args: { filePath: string; layerId: string }) => { + const { filePath, layerId } = args; + const current = tracker.find(w => w.model.filePath === filePath); + + if (!current || !current.model.sharedModel.editable) { + return; + } + + const sharedModel = current.model.sharedModel; + const existing = sharedModel.layers[layerId]; + if (!existing) { + return; + } + + current.model.removeLayer(layerId); + }) as any, + }); } From be53860f178e63669eb1bdef1ae6fdbe33d61fea Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Fri, 31 Oct 2025 17:50:43 +0530 Subject: [PATCH 18/54] `renameGroupWithParams` --- .../src/commands/documentActionCommands.ts | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/packages/base/src/commands/documentActionCommands.ts b/packages/base/src/commands/documentActionCommands.ts index c9078609c..89c13cdb8 100644 --- a/packages/base/src/commands/documentActionCommands.ts +++ b/packages/base/src/commands/documentActionCommands.ts @@ -12,6 +12,7 @@ export namespace DocumentActionCommandIDs { 'jupytergis:temporalControllerWithParams'; export const renameLayerWithParams = 'jupytergis:renameLayerWithParams'; export const removeLayerWithParams = 'jupytergis:removeLayerWithParams'; + export const renameGroupWithParams = 'jupytergis:renameGroupWithParams'; } export function addDocumentActionCommands(options: { @@ -268,4 +269,39 @@ export function addDocumentActionCommands(options: { current.model.removeLayer(layerId); }) as any, }); + + commands.addCommand(DocumentActionCommandIDs.renameGroupWithParams, { + label: trans.__('Rename Group from file name'), + isEnabled: () => true, + describedBy: { + args: { + type: 'object', + required: ['filePath', 'oldName', 'newName'], + properties: { + filePath: { + type: 'string', + description: 'The path to the .jGIS file to be modified' + }, + oldName: { + type: 'string', + description: 'The existing name of the group to rename' + }, + newName: { + type: 'string', + description: 'The new name for the group' + } + } + } + }, + execute: (async (args: { filePath: string; oldName: string; newName: string }) => { + const { filePath, oldName, newName } = args; + const current = tracker.find(w => w.model.filePath === filePath); + if (!current || !current.model.sharedModel.editable) { + return; + } + + const model = current.model; + model.renameLayerGroup(oldName, newName); + }) as any +}); } From 2b862ef201b7de88719e7c18ee0e31a987d78441 Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Fri, 31 Oct 2025 17:55:45 +0530 Subject: [PATCH 19/54] `removeGroupWithParams`, `moveLayersToGroupWithParams` --- .../src/commands/documentActionCommands.ts | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/packages/base/src/commands/documentActionCommands.ts b/packages/base/src/commands/documentActionCommands.ts index 89c13cdb8..6a2797f23 100644 --- a/packages/base/src/commands/documentActionCommands.ts +++ b/packages/base/src/commands/documentActionCommands.ts @@ -13,6 +13,8 @@ export namespace DocumentActionCommandIDs { export const renameLayerWithParams = 'jupytergis:renameLayerWithParams'; export const removeLayerWithParams = 'jupytergis:removeLayerWithParams'; export const renameGroupWithParams = 'jupytergis:renameGroupWithParams'; + export const removeGroupWithParams = 'jupytergis:removeGroupWithParams'; + export const moveLayersToGroupWithParams = 'jupytergis:moveLayersToGroupWithParams'; } export function addDocumentActionCommands(options: { @@ -304,4 +306,72 @@ export function addDocumentActionCommands(options: { model.renameLayerGroup(oldName, newName); }) as any }); + +commands.addCommand(DocumentActionCommandIDs.removeGroupWithParams, { + label: trans.__('Remove group from file name'), + isEnabled: () => true, + describedBy: { + args: { + type: 'object', + required: ['filePath', 'groupName'], + properties: { + filePath: { + type: 'string', + description: 'The path to the .jGIS file to be modified' + }, + groupName: { + type: 'string', + description: 'The name of the group to remove' + } + } + } + }, + execute: ((args: { filePath: string; groupName: string }) => { + const { filePath, groupName } = args; + const current = tracker.find(w => w.model.filePath === filePath); + if (!current || !current.model.sharedModel.editable) { + return; + } + current.model.removeLayerGroup(groupName); + }) as any + }); + + commands.addCommand(DocumentActionCommandIDs.moveLayersToGroupWithParams, { + label: trans.__('Move layers to group from file name'), + isEnabled: () => true, + describedBy: { + args: { + type: 'object', + required: ['filePath', 'layerIds', 'groupName'], + properties: { + filePath: { + type: 'string', + description: 'The path to the .jGIS file to be modified' + }, + layerIds: { + type: 'array', + description: 'Array of layer IDs to move', + items: { type: 'string' } + }, + groupName: { + type: 'string', + description: + 'The name of the target group. Use empty string for root.' + } + } + } + }, + execute: ((args: { + filePath: string; + layerIds: string[]; + groupName: string; + }) => { + const { filePath, layerIds, groupName } = args; + const current = tracker.find(w => w.model.filePath === filePath); + if (!current || !current.model.sharedModel.editable) { + return; + } + current.model.moveItemsToGroup(layerIds, groupName); + }) as any + }); } From a8c75bd33f98f3f6dbd6b49f401df9f299d45e9e Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Fri, 31 Oct 2025 18:09:12 +0530 Subject: [PATCH 20/54] `moveLayerToNewGroupWithParams` --- .../src/commands/documentActionCommands.ts | 151 ++++++++++++------ 1 file changed, 104 insertions(+), 47 deletions(-) diff --git a/packages/base/src/commands/documentActionCommands.ts b/packages/base/src/commands/documentActionCommands.ts index 6a2797f23..0eec29834 100644 --- a/packages/base/src/commands/documentActionCommands.ts +++ b/packages/base/src/commands/documentActionCommands.ts @@ -1,3 +1,4 @@ +import { IJGISLayerGroup } from '@jupytergis/schema'; import { IRenderMime } from '@jupyterlab/rendermime'; import { CommandRegistry } from '@lumino/commands'; @@ -14,7 +15,10 @@ export namespace DocumentActionCommandIDs { export const removeLayerWithParams = 'jupytergis:removeLayerWithParams'; export const renameGroupWithParams = 'jupytergis:renameGroupWithParams'; export const removeGroupWithParams = 'jupytergis:removeGroupWithParams'; - export const moveLayersToGroupWithParams = 'jupytergis:moveLayersToGroupWithParams'; + export const moveLayersToGroupWithParams = + 'jupytergis:moveLayersToGroupWithParams'; + export const moveLayerToNewGroupWithParams = + 'jupytergis:moveLayerToNewGroupWithParams'; } export function addDocumentActionCommands(options: { @@ -273,41 +277,45 @@ export function addDocumentActionCommands(options: { }); commands.addCommand(DocumentActionCommandIDs.renameGroupWithParams, { - label: trans.__('Rename Group from file name'), - isEnabled: () => true, - describedBy: { - args: { - type: 'object', - required: ['filePath', 'oldName', 'newName'], - properties: { - filePath: { - type: 'string', - description: 'The path to the .jGIS file to be modified' - }, - oldName: { - type: 'string', - description: 'The existing name of the group to rename' + label: trans.__('Rename Group from file name'), + isEnabled: () => true, + describedBy: { + args: { + type: 'object', + required: ['filePath', 'oldName', 'newName'], + properties: { + filePath: { + type: 'string', + description: 'The path to the .jGIS file to be modified', + }, + oldName: { + type: 'string', + description: 'The existing name of the group to rename', + }, + newName: { + type: 'string', + description: 'The new name for the group', + }, }, - newName: { - type: 'string', - description: 'The new name for the group' - } + }, + }, + execute: (async (args: { + filePath: string; + oldName: string; + newName: string; + }) => { + const { filePath, oldName, newName } = args; + const current = tracker.find(w => w.model.filePath === filePath); + if (!current || !current.model.sharedModel.editable) { + return; } - } - }, - execute: (async (args: { filePath: string; oldName: string; newName: string }) => { - const { filePath, oldName, newName } = args; - const current = tracker.find(w => w.model.filePath === filePath); - if (!current || !current.model.sharedModel.editable) { - return; - } - - const model = current.model; - model.renameLayerGroup(oldName, newName); - }) as any -}); - -commands.addCommand(DocumentActionCommandIDs.removeGroupWithParams, { + + const model = current.model; + model.renameLayerGroup(oldName, newName); + }) as any, + }); + + commands.addCommand(DocumentActionCommandIDs.removeGroupWithParams, { label: trans.__('Remove group from file name'), isEnabled: () => true, describedBy: { @@ -317,14 +325,14 @@ commands.addCommand(DocumentActionCommandIDs.removeGroupWithParams, { properties: { filePath: { type: 'string', - description: 'The path to the .jGIS file to be modified' + description: 'The path to the .jGIS file to be modified', }, groupName: { type: 'string', - description: 'The name of the group to remove' - } - } - } + description: 'The name of the group to remove', + }, + }, + }, }, execute: ((args: { filePath: string; groupName: string }) => { const { filePath, groupName } = args; @@ -333,7 +341,7 @@ commands.addCommand(DocumentActionCommandIDs.removeGroupWithParams, { return; } current.model.removeLayerGroup(groupName); - }) as any + }) as any, }); commands.addCommand(DocumentActionCommandIDs.moveLayersToGroupWithParams, { @@ -346,20 +354,20 @@ commands.addCommand(DocumentActionCommandIDs.removeGroupWithParams, { properties: { filePath: { type: 'string', - description: 'The path to the .jGIS file to be modified' + description: 'The path to the .jGIS file to be modified', }, layerIds: { type: 'array', description: 'Array of layer IDs to move', - items: { type: 'string' } + items: { type: 'string' }, }, groupName: { type: 'string', description: - 'The name of the target group. Use empty string for root.' - } - } - } + 'The name of the target group. Use empty string for root.', + }, + }, + }, }, execute: ((args: { filePath: string; @@ -372,6 +380,55 @@ commands.addCommand(DocumentActionCommandIDs.removeGroupWithParams, { return; } current.model.moveItemsToGroup(layerIds, groupName); - }) as any + }) as any, + }); + + commands.addCommand(DocumentActionCommandIDs.moveLayerToNewGroupWithParams, { + label: trans.__('Move selected layers to new group from file name'), + isEnabled: () => true, + describedBy: { + args: { + type: 'object', + required: ['filePath', 'groupName', 'layerIds'], + properties: { + filePath: { + type: 'string', + description: 'The path to the .jGIS file to be modified', + }, + groupName: { + type: 'string', + description: 'The name of the new layer group to create', + }, + layerIds: { + type: 'array', + description: 'Array of layer IDs to move to the new group', + items: { type: 'string' }, + }, + }, + }, + }, + execute: ((args: { + filePath: string; + groupName: string; + layerIds: string[]; + }) => { + const { filePath, groupName, layerIds } = args; + const current = tracker.find(w => w.model.filePath === filePath); + if (!current || !current.model.sharedModel.editable) { + return; + } + + const layerMap: { [key: string]: any } = {}; + layerIds.forEach(id => { + layerMap[id] = { type: 'layer', selectedNodeId: id }; + }); + + const newGroup: IJGISLayerGroup = { + name: groupName, + layers: layerIds, + }; + + current.model.addNewLayerGroup(layerMap, newGroup); + }) as any, }); } From 717071af57eb1a830a031c7f488158a27875c352 Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Fri, 31 Oct 2025 18:30:59 +0530 Subject: [PATCH 21/54] `renameSourceWithParams`, `removeSourceWithParams` --- .../src/commands/documentActionCommands.ts | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/packages/base/src/commands/documentActionCommands.ts b/packages/base/src/commands/documentActionCommands.ts index 0eec29834..a6f131494 100644 --- a/packages/base/src/commands/documentActionCommands.ts +++ b/packages/base/src/commands/documentActionCommands.ts @@ -1,4 +1,5 @@ import { IJGISLayerGroup } from '@jupytergis/schema'; +import { showErrorMessage } from '@jupyterlab/apputils'; import { IRenderMime } from '@jupyterlab/rendermime'; import { CommandRegistry } from '@lumino/commands'; @@ -19,6 +20,8 @@ export namespace DocumentActionCommandIDs { 'jupytergis:moveLayersToGroupWithParams'; export const moveLayerToNewGroupWithParams = 'jupytergis:moveLayerToNewGroupWithParams'; + export const renameSourceWithParams = 'jupytergis:renameSourceWithParams'; + export const removeSourceWithParams = 'jupytergis:removeSourceWithParams'; } export function addDocumentActionCommands(options: { @@ -431,4 +434,90 @@ export function addDocumentActionCommands(options: { current.model.addNewLayerGroup(layerMap, newGroup); }) as any, }); + + commands.addCommand(DocumentActionCommandIDs.renameSourceWithParams, { + label: trans.__('Rename source from file name'), + isEnabled: () => true, + describedBy: { + args: { + type: 'object', + required: ['filePath', 'sourceId', 'newName'], + properties: { + filePath: { + type: 'string', + description: 'Path to the .jGIS file to be modified', + }, + sourceId: { + type: 'string', + description: 'The ID of the source to rename', + }, + newName: { + type: 'string', + description: 'The new name for the source', + }, + }, + }, + }, + execute: (async (args: { + filePath: string; + sourceId: string; + newName: string; + }) => { + const { filePath, sourceId, newName } = args; + const current = tracker.find(w => w.model.filePath === filePath); + + if (!current || !current.model.sharedModel.editable) { + return; + } + + const source = current.model.getSource(sourceId); + if (!source) { + console.warn(`Source with ID ${sourceId} not found`); + return; + } + + source.name = newName; + current.model.sharedModel.updateSource(sourceId, source); + }) as any, + }); + + commands.addCommand(DocumentActionCommandIDs.removeSourceWithParams, { + label: trans.__('Remove source from file name'), + isEnabled: () => true, + describedBy: { + args: { + type: 'object', + required: ['filePath', 'sourceId'], + properties: { + filePath: { + type: 'string', + description: 'Path to the .jGIS file to be modified', + }, + sourceId: { + type: 'string', + description: 'The ID of the source to remove', + }, + }, + }, + }, + execute: ((args: { filePath: string; sourceId: string }) => { + const { filePath, sourceId } = args; + const current = tracker.find(w => w.model.filePath === filePath); + + if (!current || !current.model.sharedModel.editable) { + return; + } + + const layersUsingSource = current.model.getLayersBySource(sourceId); + if (layersUsingSource.length > 0) { + showErrorMessage( + 'Remove source error', + 'The source is used by a layer.', + ); + return; + } + + current.model.sharedModel.removeSource(sourceId); + }) as any, + }); } From 82015478d78e07d77f8c95fe4790862eed0d9f57 Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Fri, 31 Oct 2025 18:35:48 +0530 Subject: [PATCH 22/54] `zoomToLayerWithParams` --- .../src/commands/documentActionCommands.ts | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/packages/base/src/commands/documentActionCommands.ts b/packages/base/src/commands/documentActionCommands.ts index a6f131494..79c4ff615 100644 --- a/packages/base/src/commands/documentActionCommands.ts +++ b/packages/base/src/commands/documentActionCommands.ts @@ -22,6 +22,7 @@ export namespace DocumentActionCommandIDs { 'jupytergis:moveLayerToNewGroupWithParams'; export const renameSourceWithParams = 'jupytergis:renameSourceWithParams'; export const removeSourceWithParams = 'jupytergis:removeSourceWithParams'; + export const zoomToLayerWithParams = 'jupytergis:zoomToLayerWithParams'; } export function addDocumentActionCommands(options: { @@ -520,4 +521,36 @@ export function addDocumentActionCommands(options: { current.model.sharedModel.removeSource(sourceId); }) as any, }); + + commands.addCommand(DocumentActionCommandIDs.zoomToLayerWithParams, { + label: trans.__('Zoom to layer from file name'), + isEnabled: () => true, + describedBy: { + args: { + type: 'object', + required: ['filePath', 'layerId'], + properties: { + filePath: { + type: 'string', + description: 'Path to the .jGIS file containing the layer', + }, + layerId: { + type: 'string', + description: 'The ID of the layer to zoom to', + }, + }, + }, + }, + execute: ((args: { filePath: string; layerId: string }) => { + const { filePath, layerId } = args; + const current = tracker.find(w => w.model.filePath === filePath); + + if (!current || !current.model.sharedModel.editable) { + return; + } + + console.log(`Zooming to layer: ${layerId}`); + current.model.centerOnPosition(layerId); + }) as any, + }); } From 94fdd203b24afda3fb50f46bb3f0f4585359afae Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Fri, 31 Oct 2025 18:43:17 +0530 Subject: [PATCH 23/54] `downloadGeoJSONWithParams` --- .../src/commands/documentActionCommands.ts | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/packages/base/src/commands/documentActionCommands.ts b/packages/base/src/commands/documentActionCommands.ts index 79c4ff615..080da9f31 100644 --- a/packages/base/src/commands/documentActionCommands.ts +++ b/packages/base/src/commands/documentActionCommands.ts @@ -4,6 +4,7 @@ import { IRenderMime } from '@jupyterlab/rendermime'; import { CommandRegistry } from '@lumino/commands'; import { getSingleSelectedLayer } from '../processing'; +import { downloadFile, getGeoJSONDataFromLayerSource } from '../tools'; import { JupyterGISTracker } from '../types'; export namespace DocumentActionCommandIDs { @@ -23,6 +24,8 @@ export namespace DocumentActionCommandIDs { export const renameSourceWithParams = 'jupytergis:renameSourceWithParams'; export const removeSourceWithParams = 'jupytergis:removeSourceWithParams'; export const zoomToLayerWithParams = 'jupytergis:zoomToLayerWithParams'; + export const downloadGeoJSONWithParams = + 'jupytergis:downloadGeoJSONWithParams'; } export function addDocumentActionCommands(options: { @@ -553,4 +556,70 @@ export function addDocumentActionCommands(options: { current.model.centerOnPosition(layerId); }) as any, }); + + commands.addCommand(DocumentActionCommandIDs.downloadGeoJSONWithParams, { + label: trans.__('Download layer as GeoJSON'), + isEnabled: () => true, + describedBy: { + args: { + type: 'object', + required: ['filePath', 'layerId', 'exportFileName'], + properties: { + filePath: { + type: 'string', + description: 'Path to the .jGIS file containing the layer', + }, + layerId: { + type: 'string', + description: 'The ID of the layer to export', + }, + exportFileName: { + type: 'string', + description: 'The desired name of the exported GeoJSON file', + }, + }, + }, + }, + execute: (async (args: { + filePath: string; + layerId: string; + exportFileName: string; + }) => { + const { filePath, layerId, exportFileName } = args; + const current = tracker.find(w => w.model.filePath === filePath); + + if (!current || !current.model.sharedModel.editable) { + console.warn('Invalid or non-editable document'); + return; + } + + const model = current.model; + const layer = model.getLayer(layerId); + + if (!layer || !['VectorLayer', 'ShapefileLayer'].includes(layer.type)) { + console.warn('Layer type not supported for GeoJSON export'); + return; + } + + const sources = model.sharedModel.sources ?? {}; + const sourceId = layer.parameters?.source; + const source = sources[sourceId]; + if (!source) { + console.warn('Source not found for selected layer'); + return; + } + + const geojsonString = await getGeoJSONDataFromLayerSource(source, model); + if (!geojsonString) { + console.warn('Failed to generate GeoJSON data'); + return; + } + + downloadFile( + geojsonString, + `${exportFileName}.geojson`, + 'application/geo+json', + ); + }) as any, + }); } From 078b87cd270e5bfbdf7a9dc1a72d88d6c90d43b7 Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Fri, 31 Oct 2025 18:46:10 +0530 Subject: [PATCH 24/54] `getGeolocationWithParams` --- .../src/commands/documentActionCommands.ts | 58 ++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/packages/base/src/commands/documentActionCommands.ts b/packages/base/src/commands/documentActionCommands.ts index 080da9f31..9c995e228 100644 --- a/packages/base/src/commands/documentActionCommands.ts +++ b/packages/base/src/commands/documentActionCommands.ts @@ -1,7 +1,9 @@ -import { IJGISLayerGroup } from '@jupytergis/schema'; +import { IJGISLayerGroup, JgisCoordinates } from '@jupytergis/schema'; import { showErrorMessage } from '@jupyterlab/apputils'; import { IRenderMime } from '@jupyterlab/rendermime'; import { CommandRegistry } from '@lumino/commands'; +import { Coordinate } from 'ol/coordinate'; +import { fromLonLat } from 'ol/proj'; import { getSingleSelectedLayer } from '../processing'; import { downloadFile, getGeoJSONDataFromLayerSource } from '../tools'; @@ -26,6 +28,7 @@ export namespace DocumentActionCommandIDs { export const zoomToLayerWithParams = 'jupytergis:zoomToLayerWithParams'; export const downloadGeoJSONWithParams = 'jupytergis:downloadGeoJSONWithParams'; + export const getGeolocationWithParams = 'jupytergis:getGeolocationWithParams'; } export function addDocumentActionCommands(options: { @@ -622,4 +625,57 @@ export function addDocumentActionCommands(options: { ); }) as any, }); + + commands.addCommand(DocumentActionCommandIDs.getGeolocationWithParams, { + label: trans.__('Center on Geolocation'), + isEnabled: () => true, + describedBy: { + args: { + type: 'object', + required: ['filePath'], + properties: { + filePath: { + type: 'string', + description: + 'Path to the .jGIS document to center on the user’s geolocation', + }, + }, + }, + }, + execute: (async (args: { filePath: string }) => { + const { filePath } = args; + const current = tracker.find(w => w.model.filePath === filePath); + if (!current) { + console.warn('No document found for provided filePath'); + return; + } + + const viewModel = current.model; + const options = { + enableHighAccuracy: true, + timeout: 5000, + maximumAge: 0, + }; + + const success = (pos: GeolocationPosition) => { + const location: Coordinate = fromLonLat([ + pos.coords.longitude, + pos.coords.latitude, + ]); + + const jgisLocation: JgisCoordinates = { + x: location[0], + y: location[1], + }; + + viewModel.geolocationChanged.emit(jgisLocation); + }; + + const error = (err: GeolocationPositionError) => { + console.warn(`Geolocation error (${err.code}): ${err.message}`); + }; + + navigator.geolocation.getCurrentPosition(success, error, options); + }) as any, + }); } From 9b83900d41345752101711cbba4b4a4cafc2c09a Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Mon, 3 Nov 2025 18:34:50 +0530 Subject: [PATCH 25/54] Processing commands from params [needs review] --- .../commands/processingCommandsFromParams.ts | 112 ++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 packages/base/src/commands/processingCommandsFromParams.ts diff --git a/packages/base/src/commands/processingCommandsFromParams.ts b/packages/base/src/commands/processingCommandsFromParams.ts new file mode 100644 index 000000000..1a6edd1aa --- /dev/null +++ b/packages/base/src/commands/processingCommandsFromParams.ts @@ -0,0 +1,112 @@ +import { + IJGISFormSchemaRegistry, + ProcessingLogicType, + ProcessingType, + ProcessingMerge, +} from '@jupytergis/schema'; +import { JupyterFrontEnd } from '@jupyterlab/application'; +import { CommandRegistry } from '@lumino/commands'; + +import { processSelectedLayer } from '../processing'; +import { replaceInSql } from '../processing/processingCommands'; +import { JupyterGISTracker } from '../types'; + +/** + * Dynamically registers processing commands from schemas and ProcessingMerge metadata. + */ +export function addProcessingCommandsFromParams(options: { + app: JupyterFrontEnd; + commands: CommandRegistry; + tracker: JupyterGISTracker; + trans: any; + formSchemaRegistry: IJGISFormSchemaRegistry; + processingSchemas: Record; +}) { + const { + app, + commands, + tracker, + trans, + formSchemaRegistry, + processingSchemas, + } = options; + + for (const processingElement of ProcessingMerge) { + if (processingElement.type !== ProcessingLogicType.vector) { + console.error( + `Skipping unsupported processing type: ${processingElement.type}`, + ); + continue; + } + + const schema = processingSchemas[processingElement.name]; + if (!schema) { + console.warn( + `No schema found for ${processingElement.name}, skipping command`, + ); + continue; + } + + const commandId = `${processingElement.name}WithParams`; + + commands.addCommand(commandId, { + label: trans.__(`${processingElement.label} from params`), + isEnabled: () => true, + describedBy: { + args: { + type: 'object', + required: ['filePath', 'params'], + properties: { + filePath: { + type: 'string', + description: 'Path to the .jGIS file containing the layer', + }, + params: schema, + }, + }, + }, + execute: (async (args: { + filePath: string; + params: Record; + }) => { + const { filePath, params } = args; + const current = tracker.find(w => w.model.filePath === filePath); + + if (!current) { + console.warn('No JupyterGIS widget found for', filePath); + return; + } + + // Build SQL using replaceInSql() + const sql = replaceInSql( + processingElement.operations.sql, + Object.fromEntries( + Object.entries(params).map(([k, v]) => [k, String(v)]), + ), + params.inputLayer ?? '', + ); + + // Execute using standard processSelectedLayer() + await processSelectedLayer( + tracker, + formSchemaRegistry, + processingElement as unknown as ProcessingType, + { + sqlQueryFn: () => sql, + gdalFunction: processingElement.operations.gdalFunction, + options: (query: string) => [ + '-f', + 'GeoJSON', + '-dialect', + 'SQLITE', + '-sql', + query, + 'output.geojson', + ], + }, + app, + ); + }) as any, + }); + } +} From 34c4a54102989b4870092f1d28158f9ce89b511f Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Mon, 3 Nov 2025 19:06:24 +0530 Subject: [PATCH 26/54] usage --- packages/base/src/commands/index.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/base/src/commands/index.ts b/packages/base/src/commands/index.ts index eea1d63bf..441156198 100644 --- a/packages/base/src/commands/index.ts +++ b/packages/base/src/commands/index.ts @@ -34,6 +34,7 @@ import { JupyterGISTracker } from '../types'; import { JupyterGISDocumentWidget } from '../widget'; import { addDocumentActionCommands } from './documentActionCommands'; import { addLayerCreationCommands } from './operationCommands'; +import { addProcessingCommandsFromParams } from './processingCommandsFromParams'; interface ICreateEntry { tracker: JupyterGISTracker; @@ -440,6 +441,14 @@ export function addCommands( //Add processing commands addProcessingCommands(app, commands, tracker, trans, formSchemaRegistry); + addProcessingCommandsFromParams({ + app, + commands, + tracker, + trans, + formSchemaRegistry, + processingSchemas: Object.fromEntries(formSchemaRegistry.getSchemas()), + }); commands.addCommand(CommandIDs.newHillshadeEntry, { label: trans.__('New Hillshade layer'), From a7abf1c5a9498a256772b5e47b83183ffb15b471 Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Tue, 11 Nov 2025 16:49:23 +0530 Subject: [PATCH 27/54] fix processing commands --- packages/base/src/commands/processingCommandsFromParams.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/base/src/commands/processingCommandsFromParams.ts b/packages/base/src/commands/processingCommandsFromParams.ts index 1a6edd1aa..dc059d5ef 100644 --- a/packages/base/src/commands/processingCommandsFromParams.ts +++ b/packages/base/src/commands/processingCommandsFromParams.ts @@ -39,7 +39,10 @@ export function addProcessingCommandsFromParams(options: { continue; } - const schema = processingSchemas[processingElement.name]; + const schemaKey = Object.keys(processingSchemas).find( + key => key.toLowerCase() === processingElement.name.toLowerCase(), + ); + const schema = schemaKey ? processingSchemas[schemaKey] : undefined; if (!schema) { console.warn( `No schema found for ${processingElement.name}, skipping command`, From 156496d5a833c33c9447286bab292a328c407e3c Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Tue, 11 Nov 2025 18:44:31 +0530 Subject: [PATCH 28/54] refactor layer addition commands --- .../base/src/commands/operationCommands.ts | 1376 +++-------------- 1 file changed, 209 insertions(+), 1167 deletions(-) diff --git a/packages/base/src/commands/operationCommands.ts b/packages/base/src/commands/operationCommands.ts index 7039f4b48..f0390a349 100644 --- a/packages/base/src/commands/operationCommands.ts +++ b/packages/base/src/commands/operationCommands.ts @@ -17,1248 +17,290 @@ export namespace LayerCreationCommandIDs { export const newShapefileWithParams = 'jupytergis:newShapefileWithParams'; } -export function addLayerCreationCommands(options: { - tracker: JupyterGISTracker; - commands: CommandRegistry; - trans: IRenderMime.TranslationBundle; -}) { - const { commands, tracker, trans } = options; - - commands.addCommand(LayerCreationCommandIDs.newGeoJSONWithParams, { - label: trans.__('New GeoJSON Layer From Parameters'), +type LayerCreationSpec = { + id: string; + label: string; + sourceType: string; + layerType: string; + sourceSchema: Record; + layerParamsSchema: Record; + buildParameters: (params: any, sourceId: string) => IJGISLayer['parameters']; +}; + +/** + * Generic command factory for layer creation. + */ +function createLayerCommand( + commands: CommandRegistry, + tracker: JupyterGISTracker, + trans: IRenderMime.TranslationBundle, + spec: LayerCreationSpec, +): void { + commands.addCommand(spec.id, { + label: trans.__(spec.label), isEnabled: () => true, describedBy: { args: { type: 'object', required: ['filePath', 'Name', 'parameters'], properties: { - filePath: { - type: 'string', - description: 'The path to the .jGIS file to modify', - }, - Name: { - type: 'string', - description: 'The name of the new GeoJSON layer', - }, + filePath: { type: 'string', description: 'Path to the .jGIS file' }, + Name: { type: 'string', description: 'Layer name' }, parameters: { type: 'object', - required: ['source', 'opacity', 'symbologyState'], properties: { - source: { - type: 'object', - description: 'GeoJSON source configuration', - required: [], - properties: { - path: { - type: 'string', - description: 'The path to the GeoJSON file', - }, - data: { - type: 'object', - description: 'The GeoJSON data object', - }, - valid: { - type: 'boolean', - description: 'Whether the data are valid', - readOnly: true, - }, - }, - }, - color: { - type: 'object', - description: 'The color configuration for the layer', - }, - opacity: { - type: 'number', - description: 'Layer opacity', - default: 1, - minimum: 0, - maximum: 1, - multipleOf: 0.1, - }, - symbologyState: { - type: 'object', - description: 'Symbology configuration for the layer', - required: ['renderType'], - properties: { - renderType: { - type: 'string', - enum: ['Single Symbol', 'Graduated', 'Categorized'], - }, - value: { type: 'string' }, - method: { - type: 'string', - enum: ['color', 'radius'], - }, - colorRamp: { - type: 'string', - default: 'viridis', - }, - nClasses: { - type: 'string', - default: '9', - }, - mode: { - type: 'string', - default: 'equal interval', - enum: [ - 'quantile', - 'equal interval', - 'jenks', - 'pretty', - 'logarithmic', - ], - }, - }, - }, + source: spec.sourceSchema, + ...spec.layerParamsSchema, }, }, - }, + } as any, }, }, execute: (async (args: { filePath: string; Name: string; - parameters: { - source: Record; - color?: Record; - opacity?: number; - symbologyState: Record; - }; + parameters: Record; }) => { const { filePath, Name, parameters } = args; const current = tracker.find(w => w.model.filePath === filePath); - - if (!current) { - console.warn('No JupyterGIS widget found for', filePath); + if (!current || !current.model.sharedModel.editable) { + console.warn('Invalid or non-editable document for', filePath); return; } const model: IJupyterGISModel = current.model; const sharedModel = model.sharedModel; - const sourceId = UUID.uuid4(); const layerId = UUID.uuid4(); const sourceModel: IJGISSource = { - type: 'GeoJSONSource', + type: spec.sourceType as any, name: `${Name} Source`, parameters: parameters.source, }; - sharedModel.addSource(sourceId, sourceModel); const layerModel: IJGISLayer = { - type: 'VectorLayer', + type: spec.layerType as any, name: Name, visible: true, - parameters: { - color: parameters.color ?? {}, - opacity: parameters.opacity ?? 1, - symbologyState: parameters.symbologyState, - source: sourceId, - }, + parameters: spec.buildParameters(parameters, sourceId), }; - model.addLayer(layerId, layerModel); }) as any, }); +} - commands.addCommand(LayerCreationCommandIDs.newRasterWithParams, { - label: trans.__('New Raster Tile Layer From Parameters'), - isEnabled: () => true, - describedBy: { - args: { +/** + * Register all layer creation commands using declarative specs. + */ +export function addLayerCreationCommands(options: { + tracker: JupyterGISTracker; + commands: CommandRegistry; + trans: IRenderMime.TranslationBundle; +}): void { + const { tracker, commands, trans } = options; + + const specs: LayerCreationSpec[] = [ + { + id: LayerCreationCommandIDs.newGeoJSONWithParams, + label: 'New GeoJSON Layer From Parameters', + sourceType: 'GeoJSONSource', + layerType: 'VectorLayer', + sourceSchema: { type: 'object', - required: ['filePath', 'Name', 'parameters'], - properties: { - filePath: { - type: 'string', - description: 'Path to the .jGIS file to modify', - }, - Name: { - type: 'string', - description: 'The name of the new Raster Tile Layer', - }, - parameters: { - type: 'object', - required: ['source', 'opacity'], - properties: { - source: { - type: 'object', - description: 'Raster source configuration', - required: ['url', 'maxZoom', 'minZoom'], - properties: { - url: { - type: 'string', - description: 'The URL to the tile provider', - }, - minZoom: { - type: 'number', - minimum: 0, - maximum: 24, - default: 0, - description: 'Minimum zoom level', - }, - maxZoom: { - type: 'number', - minimum: 0, - maximum: 24, - default: 24, - description: 'Maximum zoom level', - }, - attribution: { - type: 'string', - default: '', - description: 'Attribution for the raster source', - }, - htmlAttribution: { - type: 'string', - default: '', - description: 'HTML attribution for the raster source', - }, - provider: { - type: 'string', - default: '', - description: 'Provider name', - }, - bounds: { - type: 'array', - description: 'Bounds of the source', - items: { - type: 'array', - items: { type: 'number' }, - }, - default: [], - }, - urlParameters: { - type: 'object', - description: 'Extra URL parameters', - additionalProperties: { type: 'string' }, - default: {}, - }, - interpolate: { - type: 'boolean', - description: - 'Interpolate between grid cells when overzooming?', - default: false, - }, - }, - }, - opacity: { - type: 'number', - description: 'Layer opacity', - default: 1, - minimum: 0, - maximum: 1, - multipleOf: 0.1, - }, - }, - }, - }, + required: ['path'], + properties: { path: { type: 'string' } }, + }, + layerParamsSchema: { + color: { type: 'object' }, + opacity: { type: 'number', default: 1 }, + symbologyState: { type: 'object' }, }, + buildParameters: (p, id) => ({ + source: id, + color: p.color ?? {}, + opacity: p.opacity ?? 1, + symbologyState: p.symbologyState, + }), }, - execute: (async (args: { - filePath: string; - Name: string; - parameters: { - source: Record; - opacity: number; - }; - }) => { - const { filePath, Name, parameters } = args; - const current = tracker.find(w => w.model.filePath === filePath); - - if (!current) { - console.warn('No JupyterGIS widget found for', filePath); - return; - } - - const model: IJupyterGISModel = current.model; - const sharedModel = model.sharedModel; - - const sourceId = UUID.uuid4(); - const layerId = UUID.uuid4(); - - const sourceModel: IJGISSource = { - type: 'RasterSource', - name: `${Name} Source`, - parameters: parameters.source, - }; - - sharedModel.addSource(sourceId, sourceModel); - - const layerModel: IJGISLayer = { - type: 'RasterLayer', - name: Name, - visible: true, - parameters: { - opacity: parameters.opacity ?? 1, - source: sourceId, - }, - }; - - model.addLayer(layerId, layerModel); - }) as any, - }); - - commands.addCommand(LayerCreationCommandIDs.newVectorTileWithParams, { - label: trans.__('New Vector Tile Layer From Parameters'), - isEnabled: () => true, - describedBy: { - args: { + { + id: LayerCreationCommandIDs.newRasterWithParams, + label: 'New Raster Layer From Parameters', + sourceType: 'RasterSource', + layerType: 'RasterLayer', + sourceSchema: { type: 'object', - required: ['filePath', 'Name', 'parameters'], - properties: { - filePath: { - type: 'string', - description: 'Path to the .jGIS file to modify', - }, - Name: { - type: 'string', - description: 'The name of the new Vector Tile Layer', - }, - parameters: { - type: 'object', - required: ['source', 'opacity'], - properties: { - source: { - type: 'object', - description: 'Vector tile source configuration', - required: ['url', 'maxZoom', 'minZoom'], - properties: { - url: { - type: 'string', - description: 'The URL to the tile provider', - }, - minZoom: { - type: 'number', - minimum: 0, - maximum: 24, - description: 'Minimum zoom level for the vector source', - }, - maxZoom: { - type: 'number', - minimum: 0, - maximum: 24, - description: 'Maximum zoom level for the vector source', - }, - attribution: { - type: 'string', - description: 'Attribution for the vector source', - }, - provider: { - type: 'string', - description: 'The map provider', - readOnly: true, - }, - urlParameters: { - type: 'object', - description: 'Additional URL parameters', - additionalProperties: { type: 'string' }, - }, - }, - }, - color: { - type: 'object', - description: 'Color styling configuration for the layer', - }, - opacity: { - type: 'number', - description: 'Layer opacity (0–1)', - default: 1, - minimum: 0, - maximum: 1, - multipleOf: 0.1, - }, - }, - }, - }, + required: ['url'], + properties: { url: { type: 'string' } }, }, + layerParamsSchema: { opacity: { type: 'number', default: 1 } }, + buildParameters: (p, id) => ({ + source: id, + opacity: p.opacity ?? 1, + }), }, - execute: (async (args: { - filePath: string; - Name: string; - parameters: { - source: Record; - color?: Record; - opacity?: number; - }; - }) => { - const { filePath, Name, parameters } = args; - const current = tracker.find(w => w.model.filePath === filePath); - - if (!current) { - console.warn('No JupyterGIS widget found for', filePath); - return; - } - - const model: IJupyterGISModel = current.model; - const sharedModel = model.sharedModel; - - const sourceId = UUID.uuid4(); - const layerId = UUID.uuid4(); - - const sourceModel: IJGISSource = { - type: 'VectorTileSource', - name: `${Name} Source`, - parameters: parameters.source, - }; - - sharedModel.addSource(sourceId, sourceModel); - - const layerModel: IJGISLayer = { - type: 'VectorTileLayer', - name: Name, - visible: true, - parameters: { - color: parameters.color ?? {}, - opacity: parameters.opacity ?? 1, - source: sourceId, - }, - }; - - model.addLayer(layerId, layerModel); - }) as any, - }); - - commands.addCommand(LayerCreationCommandIDs.newGeoParquetWithParams, { - label: trans.__('New GeoParquet Layer From Parameters'), - isEnabled: () => true, - describedBy: { - args: { + { + id: LayerCreationCommandIDs.newVectorTileWithParams, + label: 'New Vector Tile Layer From Parameters', + sourceType: 'VectorTileSource', + layerType: 'VectorTileLayer', + sourceSchema: { type: 'object', - required: ['filePath', 'Name', 'parameters'], - properties: { - filePath: { - type: 'string', - description: 'Path to the .jGIS file to modify', - }, - Name: { - type: 'string', - description: 'The name of the new GeoParquet layer', - }, - parameters: { - type: 'object', - required: ['source', 'opacity', 'symbologyState'], - properties: { - source: { - type: 'object', - description: 'GeoParquet source configuration', - required: ['path'], - properties: { - path: { - type: 'string', - description: 'The path to the GeoParquet source', - }, - attribution: { - type: 'string', - readOnly: true, - description: 'Attribution for the GeoParquet source', - default: '', - }, - projection: { - type: 'string', - description: - 'Projection information for the GeoParquet data', - default: 'EPSG:4326', - }, - }, - }, - color: { - type: 'object', - description: 'Color styling for the layer', - }, - opacity: { - type: 'number', - description: 'Layer opacity (0–1)', - default: 1, - minimum: 0, - maximum: 1, - multipleOf: 0.1, - }, - symbologyState: { - type: 'object', - description: 'Symbology configuration for the layer', - required: ['renderType'], - properties: { - renderType: { - type: 'string', - enum: ['Single Symbol', 'Graduated', 'Categorized'], - }, - value: { - type: 'string', - }, - method: { - type: 'string', - enum: ['color', 'radius'], - }, - colorRamp: { - type: 'string', - default: 'viridis', - }, - nClasses: { - type: 'string', - default: '9', - }, - mode: { - type: 'string', - default: 'equal interval', - enum: [ - 'quantile', - 'equal interval', - 'jenks', - 'pretty', - 'logarithmic', - ], - }, - }, - additionalProperties: false, - }, - }, - }, - }, + required: ['url'], + properties: { url: { type: 'string' } }, }, + layerParamsSchema: { + color: { type: 'object' }, + opacity: { type: 'number', default: 1 }, + }, + buildParameters: (p, id) => ({ + source: id, + color: p.color ?? {}, + opacity: p.opacity ?? 1, + }), }, - execute: (async (args: { - filePath: string; - Name: string; - parameters: { - source: Record; - color?: Record; - opacity?: number; - symbologyState: Record; - }; - }) => { - const { filePath, Name, parameters } = args; - const current = tracker.find(w => w.model.filePath === filePath); - - if (!current) { - console.warn('No JupyterGIS widget found for', filePath); - return; - } - - const model: IJupyterGISModel = current.model; - const sharedModel = model.sharedModel; - - const sourceId = UUID.uuid4(); - const layerId = UUID.uuid4(); - - const sourceModel: IJGISSource = { - type: 'GeoParquetSource', - name: `${Name} Source`, - parameters: parameters.source, - }; - - sharedModel.addSource(sourceId, sourceModel); - - const layerModel: IJGISLayer = { - type: 'VectorLayer', - name: Name, - visible: true, - parameters: { - color: parameters.color ?? {}, - opacity: parameters.opacity ?? 1, - symbologyState: parameters.symbologyState, - source: sourceId, - }, - }; - - model.addLayer(layerId, layerModel); - }) as any, - }); - - commands.addCommand(LayerCreationCommandIDs.newHillshadeWithParams, { - label: trans.__('New Hillshade Layer From Parameters'), - isEnabled: () => true, - describedBy: { - args: { + { + id: LayerCreationCommandIDs.newGeoParquetWithParams, + label: 'New GeoParquet Layer From Parameters', + sourceType: 'GeoParquetSource', + layerType: 'VectorLayer', + sourceSchema: { type: 'object', - required: ['filePath', 'Name', 'parameters'], - properties: { - filePath: { - type: 'string', - description: 'Path to the .jGIS file to modify', - }, - Name: { - type: 'string', - description: 'The name of the new Hillshade layer', - }, - parameters: { - type: 'object', - required: ['source'], - properties: { - source: { - type: 'object', - description: 'RasterDem source configuration', - required: ['url'], - properties: { - url: { - type: 'string', - description: 'The URL to the DEM tile provider', - }, - attribution: { - type: 'string', - description: - 'Attribution for the raster-dem source (optional)', - }, - urlParameters: { - type: 'object', - description: - 'Additional URL parameters for the raster-dem source', - additionalProperties: { - type: 'string', - }, - }, - interpolate: { - type: 'boolean', - description: - 'Interpolate between grid cells when overzooming', - default: false, - }, - }, - }, - shadowColor: { - type: 'string', - description: 'The color of the shadows', - default: '#473B24', - }, - }, - }, - }, + required: ['path'], + properties: { path: { type: 'string' } }, + }, + layerParamsSchema: { + color: { type: 'object' }, + opacity: { type: 'number', default: 1 }, + symbologyState: { type: 'object' }, }, + buildParameters: (p, id) => ({ + source: id, + color: p.color ?? {}, + opacity: p.opacity ?? 1, + symbologyState: p.symbologyState, + }), }, - execute: (async (args: { - filePath: string; - Name: string; - parameters: { - source: Record; - shadowColor?: string; - }; - }) => { - const { filePath, Name, parameters } = args; - const current = tracker.find(w => w.model.filePath === filePath); - - if (!current) { - console.warn('No JupyterGIS widget found for', filePath); - return; - } - - const model: IJupyterGISModel = current.model; - const sharedModel = model.sharedModel; - - const sourceId = UUID.uuid4(); - const layerId = UUID.uuid4(); - - const sourceModel: IJGISSource = { - type: 'RasterDemSource', - name: `${Name} Source`, - parameters: parameters.source, - }; - - sharedModel.addSource(sourceId, sourceModel); - - const layerModel: IJGISLayer = { - type: 'HillshadeLayer', - name: Name, - visible: true, - parameters: { - shadowColor: parameters.shadowColor ?? '#473B24', - source: sourceId, - }, - }; - - model.addLayer(layerId, layerModel); - }) as any, - }); - - commands.addCommand(LayerCreationCommandIDs.newImageWithParams, { - label: trans.__('New Image Layer From Parameters'), - isEnabled: () => true, - describedBy: { - args: { + { + id: LayerCreationCommandIDs.newHillshadeWithParams, + label: 'New Hillshade Layer From Parameters', + sourceType: 'RasterDemSource', + layerType: 'HillshadeLayer', + sourceSchema: { type: 'object', - required: ['filePath', 'Name', 'parameters'], - properties: { - filePath: { - type: 'string', - description: 'Path to the .jGIS file to modify', - }, - Name: { - type: 'string', - description: 'The name of the new Image layer', - }, - parameters: { - type: 'object', - required: ['source'], - properties: { - source: { - type: 'object', - description: 'Image source configuration', - required: ['path', 'coordinates'], - properties: { - path: { - type: 'string', - description: 'Path that points to the image', - }, - coordinates: { - type: 'array', - description: - 'Four corner coordinates in [lon, lat] pairs defining the image bounds', - minItems: 4, - maxItems: 4, - items: { - type: 'array', - minItems: 2, - maxItems: 2, - items: { type: 'number' }, - }, - }, - interpolate: { - type: 'boolean', - description: - 'Whether to interpolate between grid cells when overzooming', - default: false, - }, - }, - }, - opacity: { - type: 'number', - description: 'The opacity of the image layer', - default: 1, - minimum: 0, - maximum: 1, - multipleOf: 0.1, - }, - }, - }, - }, + required: ['url'], + properties: { url: { type: 'string' } }, }, + layerParamsSchema: { + shadowColor: { type: 'string', default: '#473B24' }, + }, + buildParameters: (p, id) => ({ + source: id, + shadowColor: p.shadowColor ?? '#473B24', + }), }, - execute: (async (args: { - filePath: string; - Name: string; - parameters: { - source: { - path: string; - coordinates: number[][]; - interpolate?: boolean; - }; - opacity?: number; - }; - }) => { - const { filePath, Name, parameters } = args; - const current = tracker.find(w => w.model.filePath === filePath); - - if (!current) { - console.warn('No JupyterGIS widget found for', filePath); - return; - } - - const model: IJupyterGISModel = current.model; - const sharedModel = model.sharedModel; - - const sourceId = UUID.uuid4(); - const layerId = UUID.uuid4(); - - const sourceModel: IJGISSource = { - type: 'ImageSource', - name: `${Name} Source`, - parameters: { - path: parameters.source.path, - coordinates: parameters.source.coordinates, - interpolate: parameters.source.interpolate ?? false, - }, - }; - - sharedModel.addSource(sourceId, sourceModel); - - const layerModel: IJGISLayer = { - type: 'ImageLayer', - name: Name, - visible: true, - parameters: { - source: sourceId, - opacity: parameters.opacity ?? 1, - }, - }; - - model.addLayer(layerId, layerModel); - }) as any, - }); - - commands.addCommand(LayerCreationCommandIDs.newVideoWithParams, { - label: trans.__('New Video Layer From Parameters'), - isEnabled: () => true, - describedBy: { - args: { + { + id: LayerCreationCommandIDs.newImageWithParams, + label: 'New Image Layer From Parameters', + sourceType: 'ImageSource', + layerType: 'ImageLayer', + sourceSchema: { type: 'object', - required: ['filePath', 'Name', 'parameters'], + required: ['path', 'coordinates'], properties: { - filePath: { - type: 'string', - description: 'Path to the .jGIS file to modify', - }, - Name: { - type: 'string', - description: 'The name of the new Video layer', - }, - parameters: { - type: 'object', - required: ['source'], - properties: { - source: { - type: 'object', - description: 'Video source configuration', - required: ['urls', 'coordinates'], - properties: { - urls: { - type: 'array', - description: 'List of video URLs in preferred format order', - minItems: 1, - items: { type: 'string' }, - }, - coordinates: { - type: 'array', - description: - 'Four corner coordinates in [lon, lat] pairs defining the video projection area', - minItems: 4, - maxItems: 4, - items: { - type: 'array', - minItems: 2, - maxItems: 2, - items: { type: 'number' }, - }, - }, - }, - }, - opacity: { - type: 'number', - description: 'The opacity of the video layer', - default: 1, - minimum: 0, - maximum: 1, - multipleOf: 0.1, - }, - }, + path: { type: 'string' }, + coordinates: { + type: 'array', + items: { type: 'array', items: { type: 'number' } }, }, }, }, + layerParamsSchema: { opacity: { type: 'number', default: 1 } }, + buildParameters: (p, id) => ({ + source: id, + opacity: p.opacity ?? 1, + }), }, - execute: (async (args: { - filePath: string; - Name: string; - parameters: { - source: { - urls: string[]; - coordinates: number[][]; - }; - opacity?: number; - }; - }) => { - const { filePath, Name, parameters } = args; - const current = tracker.find(w => w.model.filePath === filePath); - - if (!current) { - console.warn('No JupyterGIS widget found for', filePath); - return; - } - - const model: IJupyterGISModel = current.model; - const sharedModel = model.sharedModel; - - const sourceId = UUID.uuid4(); - const layerId = UUID.uuid4(); - - const sourceModel: IJGISSource = { - type: 'VideoSource', - name: `${Name} Source`, - parameters: { - urls: parameters.source.urls, - coordinates: parameters.source.coordinates, - }, - }; - - sharedModel.addSource(sourceId, sourceModel); - - const layerModel: IJGISLayer = { - type: 'RasterLayer', - name: Name, - visible: true, - parameters: { - source: sourceId, - opacity: parameters.opacity ?? 1, - }, - }; - - model.addLayer(layerId, layerModel); - }) as any, - }); - - commands.addCommand(LayerCreationCommandIDs.newGeoTiffWithParams, { - label: trans.__('New GeoTIFF Layer From Parameters'), - isEnabled: () => true, - describedBy: { - args: { + { + id: LayerCreationCommandIDs.newVideoWithParams, + label: 'New Video Layer From Parameters', + sourceType: 'VideoSource', + layerType: 'RasterLayer', + sourceSchema: { type: 'object', - required: ['filePath', 'Name', 'parameters'], + required: ['urls', 'coordinates'], properties: { - filePath: { - type: 'string', - description: 'Path to the .jGIS file to modify', - }, - Name: { - type: 'string', - description: 'Name of the new GeoTIFF layer', - }, - parameters: { - type: 'object', - required: ['source'], - properties: { - source: { - type: 'object', - description: 'GeoTIFF source configuration', - required: ['urls'], - properties: { - urls: { - type: 'array', - description: - 'List of GeoTIFF URL objects with optional min/max values', - minItems: 1, - items: { - type: 'object', - properties: { - url: { - type: 'string', - description: 'URL to the GeoTIFF file', - }, - min: { - type: 'number', - description: 'Minimum value for scaling', - }, - max: { - type: 'number', - description: 'Maximum value for scaling', - }, - }, - }, - }, - normalize: { - type: 'boolean', - description: - 'Normalize values between 0 and 1 for RGB display; disable to keep raw values', - default: true, - }, - wrapX: { - type: 'boolean', - description: 'Wrap map horizontally?', - default: false, - }, - interpolate: { - type: 'boolean', - description: - 'Interpolate between grid cells when overzooming?', - default: false, - }, - }, - }, - opacity: { - type: 'number', - description: 'Layer opacity (0–1)', - default: 1, - minimum: 0, - maximum: 1, - multipleOf: 0.1, - }, - color: { - oneOf: [ - { type: 'string' }, - { type: 'number' }, - { - type: 'array', - items: { - anyOf: [ - { type: 'string' }, - { type: 'number' }, - { - type: 'array', - items: { - anyOf: [{ type: 'number' }, { type: 'string' }], - }, - }, - ], - }, - }, - ], - description: 'Color of the WebGL layer', - }, - symbologyState: { - type: 'object', - description: 'Symbology configuration for the layer', - required: ['renderType'], - properties: { - renderType: { type: 'string' }, - band: { type: 'number' }, - redBand: { type: 'number' }, - greenBand: { type: 'number' }, - blueBand: { type: 'number' }, - alphaBand: { type: 'number' }, - interpolation: { - type: 'string', - enum: ['discrete', 'linear', 'exact'], - }, - colorRamp: { - type: 'string', - default: 'viridis', - }, - nClasses: { - type: 'string', - default: '9', - }, - mode: { - type: 'string', - default: 'equal interval', - enum: ['continuous', 'equal interval', 'quantile'], - }, - }, - }, - }, + urls: { type: 'array', items: { type: 'string' } }, + coordinates: { + type: 'array', + items: { type: 'array', items: { type: 'number' } }, }, }, }, + layerParamsSchema: { opacity: { type: 'number', default: 1 } }, + buildParameters: (p, id) => ({ + source: id, + opacity: p.opacity ?? 1, + }), }, - execute: (async (args: { - filePath: string; - Name: string; - parameters: { - source: { - urls: { url: string; min?: number; max?: number }[]; - normalize?: boolean; - wrapX?: boolean; - interpolate?: boolean; - }; - opacity?: number; - color?: any; - symbologyState?: Record; - }; - }) => { - const { filePath, Name, parameters } = args; - const current = tracker.find(w => w.model.filePath === filePath); - if (!current) { - console.warn('No JupyterGIS widget found for', filePath); - return; - } - - const model: IJupyterGISModel = current.model; - const sharedModel = model.sharedModel; - if (!sharedModel.editable) { - console.warn('Shared model not editable'); - return; - } - - const sourceId = UUID.uuid4(); - const layerId = UUID.uuid4(); - - const sourceModel: IJGISSource = { - type: 'GeoTiffSource', - name: `${Name} Source`, - parameters: { - urls: parameters.source.urls, - normalize: parameters.source.normalize ?? true, - wrapX: parameters.source.wrapX ?? false, - interpolate: parameters.source.interpolate ?? false, - }, - }; - - sharedModel.addSource(sourceId, sourceModel); - - const layerModel: IJGISLayer = { - type: 'WebGlLayer', - name: Name, - visible: true, - parameters: { - source: sourceId, - opacity: parameters.opacity ?? 1, - color: parameters.color, - symbologyState: parameters.symbologyState ?? { - renderType: 'continuous', - }, - }, - }; - - model.addLayer(layerId, layerModel); - }) as any, - }); - - commands.addCommand(LayerCreationCommandIDs.newShapefileWithParams, { - label: trans.__('New Shapefile Layer From Parameters'), - isEnabled: () => true, - describedBy: { - args: { + { + id: LayerCreationCommandIDs.newGeoTiffWithParams, + label: 'New GeoTIFF Layer From Parameters', + sourceType: 'GeoTiffSource', + layerType: 'WebGlLayer', + sourceSchema: { type: 'object', - required: ['filePath', 'Name', 'parameters'], + required: ['urls'], properties: { - filePath: { - type: 'string', - description: 'Path to the .jGIS file to modify', - }, - Name: { - type: 'string', - description: 'Name of the new Shapefile layer', - }, - parameters: { - type: 'object', - required: ['source'], - properties: { - source: { - type: 'object', - description: 'Shapefile source configuration', - required: ['path'], - properties: { - path: { - type: 'string', - description: - 'Path to the shapefile (.shp, .zip, or folder URL)', - }, - attribution: { - type: 'string', - description: 'Attribution for the shapefile source', - default: '', - }, - projection: { - type: 'string', - description: 'Projection for the shapefile (optional)', - default: 'WGS84', - }, - encoding: { - type: 'string', - description: 'DBF encoding (optional)', - default: 'UTF-8', - }, - additionalFiles: { - type: 'object', - description: - 'Additional files (.dbf, .prj, .cpg) associated with the shapefile', - additionalProperties: { type: 'string' }, - default: {}, - }, - }, - }, - color: { - type: 'object', - description: 'Color configuration for the layer', - }, - opacity: { - type: 'number', - description: 'Layer opacity (0–1)', - default: 1, - minimum: 0, - maximum: 1, - multipleOf: 0.1, - }, - symbologyState: { - type: 'object', - description: 'Symbology configuration for the layer', - required: ['renderType'], - properties: { - renderType: { - type: 'string', - enum: ['Single Symbol', 'Graduated', 'Categorized'], - }, - value: { type: 'string' }, - method: { - type: 'string', - enum: ['color', 'radius'], - }, - colorRamp: { - type: 'string', - default: 'viridis', - }, - nClasses: { - type: 'string', - default: '9', - }, - mode: { - type: 'string', - default: 'equal interval', - enum: [ - 'quantile', - 'equal interval', - 'jenks', - 'pretty', - 'logarithmic', - ], - }, - }, + urls: { + type: 'array', + items: { + type: 'object', + properties: { + url: { type: 'string' }, + min: { type: 'number' }, + max: { type: 'number' }, }, }, }, }, }, + layerParamsSchema: { + opacity: { type: 'number', default: 1 }, + color: { type: 'any' }, + symbologyState: { type: 'object' }, + }, + buildParameters: (p, id) => ({ + source: id, + opacity: p.opacity ?? 1, + color: p.color, + symbologyState: p.symbologyState ?? { renderType: 'continuous' }, + }), }, - execute: (async (args: { - filePath: string; - Name: string; - parameters: { - source: { - path: string; - attribution?: string; - projection?: string; - encoding?: string; - additionalFiles?: Record; - }; - color?: Record; - opacity?: number; - symbologyState?: Record; - }; - }) => { - const { filePath, Name, parameters } = args; - const current = tracker.find(w => w.model.filePath === filePath); - if (!current) { - console.warn('No JupyterGIS widget found for', filePath); - return; - } - - const model: IJupyterGISModel = current.model; - const sharedModel = model.sharedModel; - if (!sharedModel.editable) { - console.warn('Shared model not editable'); - return; - } - - const sourceId = UUID.uuid4(); - const layerId = UUID.uuid4(); - - const sourceModel: IJGISSource = { - type: 'ShapefileSource', - name: `${Name} Source`, - parameters: { - path: parameters.source.path, - attribution: parameters.source.attribution ?? '', - projection: parameters.source.projection ?? 'WGS84', - encoding: parameters.source.encoding ?? 'UTF-8', - additionalFiles: parameters.source.additionalFiles ?? {}, - }, - }; - - sharedModel.addSource(sourceId, sourceModel); - - const layerModel: IJGISLayer = { - type: 'VectorLayer', - name: Name, - visible: true, - parameters: { - source: sourceId, - color: parameters.color ?? {}, - opacity: parameters.opacity ?? 1, - symbologyState: parameters.symbologyState ?? { - renderType: 'Single Symbol', - }, - }, - }; + { + id: LayerCreationCommandIDs.newShapefileWithParams, + label: 'New Shapefile Layer From Parameters', + sourceType: 'ShapefileSource', + layerType: 'VectorLayer', + sourceSchema: { + type: 'object', + required: ['path'], + properties: { path: { type: 'string' } }, + }, + layerParamsSchema: { + color: { type: 'object' }, + opacity: { type: 'number', default: 1 }, + symbologyState: { type: 'object' }, + }, + buildParameters: (p, id) => ({ + source: id, + color: p.color ?? {}, + opacity: p.opacity ?? 1, + symbologyState: p.symbologyState ?? { renderType: 'Single Symbol' }, + }), + }, + ]; - model.addLayer(layerId, layerModel); - }) as any, - }); + specs.forEach(spec => createLayerCommand(commands, tracker, trans, spec)); } From b2172c4bd8b407ca371fbcadbc6d4ae8b5990fa1 Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Tue, 11 Nov 2025 23:54:48 +0530 Subject: [PATCH 29/54] processing without dialog --- .../commands/processingCommandsFromParams.ts | 142 +++++++++++------- 1 file changed, 89 insertions(+), 53 deletions(-) diff --git a/packages/base/src/commands/processingCommandsFromParams.ts b/packages/base/src/commands/processingCommandsFromParams.ts index dc059d5ef..37c4aec5b 100644 --- a/packages/base/src/commands/processingCommandsFromParams.ts +++ b/packages/base/src/commands/processingCommandsFromParams.ts @@ -3,16 +3,84 @@ import { ProcessingLogicType, ProcessingType, ProcessingMerge, + IJGISLayer, + IJGISSource, } from '@jupytergis/schema'; import { JupyterFrontEnd } from '@jupyterlab/application'; import { CommandRegistry } from '@lumino/commands'; +import { UUID } from '@lumino/coreutils'; -import { processSelectedLayer } from '../processing'; +import { getGdal } from '../gdal'; +import { getLayerGeoJSON } from '../processing'; import { replaceInSql } from '../processing/processingCommands'; import { JupyterGISTracker } from '../types'; /** - * Dynamically registers processing commands from schemas and ProcessingMerge metadata. + * Execute processing directly from params (no UI dialogs). + */ +async function processLayerFromParams( + tracker: JupyterGISTracker, + processingType: ProcessingType, + options: { + sqlQueryFn: (layerName: string, param: any) => string; + gdalFunction: 'ogr2ogr' | 'gdal_rasterize' | 'gdalwarp' | 'gdal_translate'; + gdalOptions: (sqlQuery: string) => string[]; + }, + app: JupyterFrontEnd, + filePath: string, + params: Record, +): Promise { + const current = tracker.find(w => w.model.filePath === filePath); + if (!current) {return;} + + const model = current.model; + const { sources = {}, layers = {} } = model.sharedModel; + const inputLayerId = params.inputLayer; + const inputLayer = layers[inputLayerId]; + if (!inputLayer) {return;} + + const geojsonString = await getLayerGeoJSON(inputLayer, sources, model); + if (!geojsonString) {return;} + + const Gdal = await getGdal(); + const fileBlob = new Blob([geojsonString], { type: 'application/geo+json' }); + const geoFile = new File([fileBlob], 'input.geojson', { + type: 'application/geo+json', + }); + + const result = await Gdal.open(geoFile); + const dataset = result.datasets[0] as any; + const layerName = dataset.info.layers[0].name; + + const sqlQuery = options.sqlQueryFn(layerName, params); + const fullOptions = options.gdalOptions(sqlQuery); + + const outputFilePath = await Gdal.ogr2ogr(dataset, fullOptions); + const processedBytes = await Gdal.getFileBytes(outputFilePath); + Gdal.close(dataset); + + const processedGeoJSON = JSON.parse(new TextDecoder().decode(processedBytes)); + const newSourceId = UUID.uuid4(); + + const sourceModel: IJGISSource = { + type: 'GeoJSONSource', + name: `${processingType} Output`, + parameters: { data: processedGeoJSON }, + }; + + const layerModel: IJGISLayer = { + type: 'VectorLayer', + name: `${processingType} Layer`, + visible: true, + parameters: { source: newSourceId }, + }; + + model.sharedModel.addSource(newSourceId, sourceModel); + model.addLayer(UUID.uuid4(), layerModel); +} + +/** + * Register all processing commands from schema + ProcessingMerge metadata. */ export function addProcessingCommandsFromParams(options: { app: JupyterFrontEnd; @@ -21,39 +89,22 @@ export function addProcessingCommandsFromParams(options: { trans: any; formSchemaRegistry: IJGISFormSchemaRegistry; processingSchemas: Record; -}) { - const { - app, - commands, - tracker, - trans, - formSchemaRegistry, - processingSchemas, - } = options; - - for (const processingElement of ProcessingMerge) { - if (processingElement.type !== ProcessingLogicType.vector) { - console.error( - `Skipping unsupported processing type: ${processingElement.type}`, - ); - continue; - } +}): void { + const { app, commands, tracker, trans, processingSchemas } = options; + + for (const proc of ProcessingMerge) { + if (proc.type !== ProcessingLogicType.vector) {continue;} const schemaKey = Object.keys(processingSchemas).find( - key => key.toLowerCase() === processingElement.name.toLowerCase(), + k => k.toLowerCase() === proc.name.toLowerCase(), ); const schema = schemaKey ? processingSchemas[schemaKey] : undefined; - if (!schema) { - console.warn( - `No schema found for ${processingElement.name}, skipping command`, - ); - continue; - } + if (!schema) {continue;} - const commandId = `${processingElement.name}WithParams`; + const commandId = `${proc.name}WithParams`; commands.addCommand(commandId, { - label: trans.__(`${processingElement.label} from params`), + label: trans.__(`${proc.label} from params`), isEnabled: () => true, describedBy: { args: { @@ -62,7 +113,7 @@ export function addProcessingCommandsFromParams(options: { properties: { filePath: { type: 'string', - description: 'Path to the .jGIS file containing the layer', + description: 'Path to the .jGIS file', }, params: schema, }, @@ -73,41 +124,26 @@ export function addProcessingCommandsFromParams(options: { params: Record; }) => { const { filePath, params } = args; - const current = tracker.find(w => w.model.filePath === filePath); - - if (!current) { - console.warn('No JupyterGIS widget found for', filePath); - return; - } - - // Build SQL using replaceInSql() - const sql = replaceInSql( - processingElement.operations.sql, - Object.fromEntries( - Object.entries(params).map(([k, v]) => [k, String(v)]), - ), - params.inputLayer ?? '', - ); - - // Execute using standard processSelectedLayer() - await processSelectedLayer( + await processLayerFromParams( tracker, - formSchemaRegistry, - processingElement as unknown as ProcessingType, + proc.name as ProcessingType, { - sqlQueryFn: () => sql, - gdalFunction: processingElement.operations.gdalFunction, - options: (query: string) => [ + sqlQueryFn: (layer, p) => + replaceInSql(proc.operations.sql, p, layer), + gdalFunction: 'ogr2ogr', + gdalOptions: (sql: string) => [ '-f', 'GeoJSON', '-dialect', 'SQLITE', '-sql', - query, + sql, 'output.geojson', ], }, app, + filePath, + params, ); }) as any, }); From 1ed8ed8d5f28e5d080638fc98e7f33ab691cc521 Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Wed, 12 Nov 2025 14:37:04 +0530 Subject: [PATCH 30/54] try fixing CI build --- packages/base/src/commands/documentActionCommands.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/base/src/commands/documentActionCommands.ts b/packages/base/src/commands/documentActionCommands.ts index 9c995e228..2a7a377b1 100644 --- a/packages/base/src/commands/documentActionCommands.ts +++ b/packages/base/src/commands/documentActionCommands.ts @@ -132,7 +132,7 @@ export function addDocumentActionCommands(options: { // Toggle identify tool current.node.classList.toggle('jGIS-identify-tool'); - current.model.toggleIdentify(); + current.model?.toggleIdentify(); // Notify change commands.notifyCommandChanged( From e4ffcfe3891bbfb6eb6355c01a18707af955a8b3 Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Wed, 12 Nov 2025 14:47:37 +0530 Subject: [PATCH 31/54] fix --- packages/base/src/commands/documentActionCommands.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/base/src/commands/documentActionCommands.ts b/packages/base/src/commands/documentActionCommands.ts index 2a7a377b1..28c740255 100644 --- a/packages/base/src/commands/documentActionCommands.ts +++ b/packages/base/src/commands/documentActionCommands.ts @@ -132,7 +132,7 @@ export function addDocumentActionCommands(options: { // Toggle identify tool current.node.classList.toggle('jGIS-identify-tool'); - current.model?.toggleIdentify(); + current.model.toggleMode('identifying'); // Notify change commands.notifyCommandChanged( From 507de69b4d46b75edaa5e8aabfd489fb7ef81a66 Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Wed, 12 Nov 2025 14:48:06 +0530 Subject: [PATCH 32/54] lint --- .../commands/processingCommandsFromParams.ts | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/packages/base/src/commands/processingCommandsFromParams.ts b/packages/base/src/commands/processingCommandsFromParams.ts index 37c4aec5b..099846215 100644 --- a/packages/base/src/commands/processingCommandsFromParams.ts +++ b/packages/base/src/commands/processingCommandsFromParams.ts @@ -31,16 +31,22 @@ async function processLayerFromParams( params: Record, ): Promise { const current = tracker.find(w => w.model.filePath === filePath); - if (!current) {return;} + if (!current) { + return; + } const model = current.model; const { sources = {}, layers = {} } = model.sharedModel; const inputLayerId = params.inputLayer; const inputLayer = layers[inputLayerId]; - if (!inputLayer) {return;} + if (!inputLayer) { + return; + } const geojsonString = await getLayerGeoJSON(inputLayer, sources, model); - if (!geojsonString) {return;} + if (!geojsonString) { + return; + } const Gdal = await getGdal(); const fileBlob = new Blob([geojsonString], { type: 'application/geo+json' }); @@ -93,13 +99,17 @@ export function addProcessingCommandsFromParams(options: { const { app, commands, tracker, trans, processingSchemas } = options; for (const proc of ProcessingMerge) { - if (proc.type !== ProcessingLogicType.vector) {continue;} + if (proc.type !== ProcessingLogicType.vector) { + continue; + } const schemaKey = Object.keys(processingSchemas).find( k => k.toLowerCase() === proc.name.toLowerCase(), ); const schema = schemaKey ? processingSchemas[schemaKey] : undefined; - if (!schema) {continue;} + if (!schema) { + continue; + } const commandId = `${proc.name}WithParams`; From a82ebb0f78eb4ab2ce82bb9670034fd09dab3169 Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Mon, 17 Nov 2025 20:03:45 +0530 Subject: [PATCH 33/54] remove undowithparams command and just modify original undo --- .../src/commands/documentActionCommands.ts | 25 ------------------- packages/base/src/commands/index.ts | 18 ++++++++++--- 2 files changed, 14 insertions(+), 29 deletions(-) diff --git a/packages/base/src/commands/documentActionCommands.ts b/packages/base/src/commands/documentActionCommands.ts index 28c740255..2dc19bb88 100644 --- a/packages/base/src/commands/documentActionCommands.ts +++ b/packages/base/src/commands/documentActionCommands.ts @@ -10,7 +10,6 @@ import { downloadFile, getGeoJSONDataFromLayerSource } from '../tools'; import { JupyterGISTracker } from '../types'; export namespace DocumentActionCommandIDs { - export const undoWithParams = 'jupytergis:undoWithParams'; export const redoWithParams = 'jupytergis:redoWithParams'; export const identifyWithParams = 'jupytergis:identifyWithParams'; export const temporalControllerWithParams = @@ -38,30 +37,6 @@ export function addDocumentActionCommands(options: { }) { const { commands, tracker, trans } = options; - commands.addCommand(DocumentActionCommandIDs.undoWithParams, { - label: trans.__('Undo from file name'), - isEnabled: () => true, - describedBy: { - args: { - type: 'object', - required: ['filePath'], - properties: { - filePath: { - type: 'string', - description: 'The path to the .jGIS file to be modified', - }, - }, - }, - }, - execute: (async (args: { filePath: string }) => { - const { filePath } = args; - const current = tracker.find(w => w.model.filePath === filePath); - if (current) { - return current.model.sharedModel.undo(); - } - }) as any, - }); - commands.addCommand(DocumentActionCommandIDs.redoWithParams, { label: trans.__('Redo from file name'), isEnabled: () => true, diff --git a/packages/base/src/commands/index.ts b/packages/base/src/commands/index.ts index 0f1e8c407..8881d494c 100644 --- a/packages/base/src/commands/index.ts +++ b/packages/base/src/commands/index.ts @@ -155,16 +155,26 @@ export function addCommands( describedBy: { args: { type: 'object', - properties: {}, - }, + required: [], + properties: { + filePath: { + type: 'string', + description: 'Optional .jGIS file path. If omitted, uses the focused file.', + } + } + } }, isEnabled: () => { return tracker.currentWidget ? tracker.currentWidget.model.sharedModel.editable : false; }, - execute: () => { - const current = tracker.currentWidget; + execute: (args: { filePath?: string }) => { + const filePath = args?.filePath; + + const current = filePath + ? tracker.find(w => w.model.filePath === filePath) + : tracker.currentWidget; if (current) { return current.model.sharedModel.undo(); From d5e7f86b5e42c09f4731a958c1612751a8329d48 Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Mon, 17 Nov 2025 20:25:56 +0530 Subject: [PATCH 34/54] just keep identify and remove identify with params --- .../src/commands/documentActionCommands.ts | 57 ------------------- packages/base/src/commands/index.ts | 22 +++++-- 2 files changed, 18 insertions(+), 61 deletions(-) diff --git a/packages/base/src/commands/documentActionCommands.ts b/packages/base/src/commands/documentActionCommands.ts index 2dc19bb88..17105f43f 100644 --- a/packages/base/src/commands/documentActionCommands.ts +++ b/packages/base/src/commands/documentActionCommands.ts @@ -5,13 +5,11 @@ import { CommandRegistry } from '@lumino/commands'; import { Coordinate } from 'ol/coordinate'; import { fromLonLat } from 'ol/proj'; -import { getSingleSelectedLayer } from '../processing'; import { downloadFile, getGeoJSONDataFromLayerSource } from '../tools'; import { JupyterGISTracker } from '../types'; export namespace DocumentActionCommandIDs { export const redoWithParams = 'jupytergis:redoWithParams'; - export const identifyWithParams = 'jupytergis:identifyWithParams'; export const temporalControllerWithParams = 'jupytergis:temporalControllerWithParams'; export const renameLayerWithParams = 'jupytergis:renameLayerWithParams'; @@ -61,61 +59,6 @@ export function addDocumentActionCommands(options: { }) as any, }); - commands.addCommand(DocumentActionCommandIDs.identifyWithParams, { - label: trans.__('Identify features from file name'), - isEnabled: () => true, - describedBy: { - args: { - type: 'object', - required: ['filePath'], - properties: { - filePath: { - type: 'string', - description: - 'The path to the .jGIS file where identify mode will be toggled', - }, - }, - }, - }, - execute: (async (args: { filePath: string }) => { - const { filePath } = args; - const current = tracker.find(w => w.model.filePath === filePath); - if (!current) { - console.warn('No active JupyterGIS widget found for', filePath); - return; - } - - // Get currently selected layer - const selectedLayer = getSingleSelectedLayer(tracker); - if (!selectedLayer) { - console.warn('No selected layer found'); - return; - } - - const canIdentify = [ - 'VectorLayer', - 'ShapefileLayer', - 'WebGlLayer', - 'VectorTileLayer', - ].includes(selectedLayer.type); - - if (current.model.currentMode === 'identifying' && !canIdentify) { - current.model.currentMode = 'panning'; - current.node.classList.remove('jGIS-identify-tool'); - return; - } - - // Toggle identify tool - current.node.classList.toggle('jGIS-identify-tool'); - current.model.toggleMode('identifying'); - - // Notify change - commands.notifyCommandChanged( - DocumentActionCommandIDs.identifyWithParams, - ); - }) as any, - }); - commands.addCommand(DocumentActionCommandIDs.temporalControllerWithParams, { label: trans.__('Toggle Temporal Controller from file name'), isEnabled: () => true, diff --git a/packages/base/src/commands/index.ts b/packages/base/src/commands/index.ts index 8881d494c..e4529ba6b 100644 --- a/packages/base/src/commands/index.ts +++ b/packages/base/src/commands/index.ts @@ -188,9 +188,16 @@ export function addCommands( describedBy: { args: { type: 'object', - properties: {}, - }, + required: [], + properties: { + filePath: { + type: 'string', + description: 'Optional .jGIS file path. If omitted, uses active widget.' + } + } + } }, + isToggled: () => { const current = tracker.currentWidget; if (!current) { @@ -201,6 +208,7 @@ export function addCommands( if (!selectedLayer) { return false; } + const canIdentify = [ 'VectorLayer', 'ShapefileLayer', @@ -231,8 +239,14 @@ export function addCommands( 'VectorTileLayer', ].includes(selectedLayer.type); }, - execute: args => { - const current = tracker.currentWidget; + + execute: (args) => { + const filePath = args?.filePath; + + const current = filePath + ? tracker.find(w => w.model.filePath === filePath) + : tracker.currentWidget; + if (!current) { return; } From b640a66543680d4734e18cacf7eeb0b533a3fc58 Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Mon, 17 Nov 2025 20:26:34 +0530 Subject: [PATCH 35/54] lint --- packages/base/src/commands/index.ts | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/packages/base/src/commands/index.ts b/packages/base/src/commands/index.ts index e4529ba6b..af9b8c9c1 100644 --- a/packages/base/src/commands/index.ts +++ b/packages/base/src/commands/index.ts @@ -159,10 +159,11 @@ export function addCommands( properties: { filePath: { type: 'string', - description: 'Optional .jGIS file path. If omitted, uses the focused file.', - } - } - } + description: + 'Optional .jGIS file path. If omitted, uses the focused file.', + }, + }, + }, }, isEnabled: () => { return tracker.currentWidget @@ -192,10 +193,11 @@ export function addCommands( properties: { filePath: { type: 'string', - description: 'Optional .jGIS file path. If omitted, uses active widget.' - } - } - } + description: + 'Optional .jGIS file path. If omitted, uses active widget.', + }, + }, + }, }, isToggled: () => { @@ -240,7 +242,7 @@ export function addCommands( ].includes(selectedLayer.type); }, - execute: (args) => { + execute: args => { const filePath = args?.filePath; const current = filePath From cfc66b1873f240f6b700eb1f07e3c5a57b61b156 Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Mon, 17 Nov 2025 20:33:48 +0530 Subject: [PATCH 36/54] rename `newGeoJSONEntry` to `openNewGeoJSONDialog` --- packages/base/src/commands/BaseCommandIDs.ts | 2 +- packages/base/src/commands/index.ts | 6 +++--- packages/base/src/constants.ts | 2 +- packages/base/src/menus.ts | 2 +- python/jupytergis_core/src/jgisplugin/plugins.ts | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/base/src/commands/BaseCommandIDs.ts b/packages/base/src/commands/BaseCommandIDs.ts index 2a511d6d3..b08881995 100644 --- a/packages/base/src/commands/BaseCommandIDs.ts +++ b/packages/base/src/commands/BaseCommandIDs.ts @@ -21,7 +21,7 @@ export const openLayerBrowser = 'jupytergis:openLayerBrowser'; export const newRasterEntry = 'jupytergis:newRasterEntry'; export const newVectorTileEntry = 'jupytergis:newVectorTileEntry'; export const newShapefileEntry = 'jupytergis:newShapefileEntry'; -export const newGeoJSONEntry = 'jupytergis:newGeoJSONEntry'; +export const openNewGeoJSONDialog = 'jupytergis:openNewGeoJSONDialog'; export const newHillshadeEntry = 'jupytergis:newHillshadeEntry'; export const newImageEntry = 'jupytergis:newImageEntry'; export const newVideoEntry = 'jupytergis:newVideoEntry'; diff --git a/packages/base/src/commands/index.ts b/packages/base/src/commands/index.ts index af9b8c9c1..84948e1b4 100644 --- a/packages/base/src/commands/index.ts +++ b/packages/base/src/commands/index.ts @@ -441,8 +441,8 @@ export function addCommands( ...icons.get(CommandIDs.newGeoParquetEntry), }); - commands.addCommand(CommandIDs.newGeoJSONEntry, { - label: trans.__('New GeoJSON layer'), + commands.addCommand(CommandIDs.openNewGeoJSONDialog, { + label: trans.__('Open New GeoJSON Dialog'), describedBy: { args: { type: 'object', @@ -464,7 +464,7 @@ export function addCommands( sourceType: 'GeoJSONSource', layerType: 'VectorLayer', }), - ...icons.get(CommandIDs.newGeoJSONEntry), + ...icons.get(CommandIDs.openNewGeoJSONDialog), }); //Add processing commands diff --git a/packages/base/src/constants.ts b/packages/base/src/constants.ts index 215bf79d4..1621105cc 100644 --- a/packages/base/src/constants.ts +++ b/packages/base/src/constants.ts @@ -47,7 +47,7 @@ const iconObject = { [CommandIDs.openLayerBrowser]: { icon: bookOpenIcon }, [CommandIDs.newRasterEntry]: { icon: rasterIcon }, [CommandIDs.newVectorTileEntry]: { icon: vectorSquareIcon }, - [CommandIDs.newGeoJSONEntry]: { icon: geoJSONIcon }, + [CommandIDs.openNewGeoJSONDialog]: { icon: geoJSONIcon }, [CommandIDs.newHillshadeEntry]: { icon: moundIcon }, [CommandIDs.newImageEntry]: { iconClass: 'fa fa-image' }, [CommandIDs.newVideoEntry]: { iconClass: 'fa fa-video' }, diff --git a/packages/base/src/menus.ts b/packages/base/src/menus.ts index 709552402..a93b994cc 100644 --- a/packages/base/src/menus.ts +++ b/packages/base/src/menus.ts @@ -18,7 +18,7 @@ export const vectorSubMenu = (commands: CommandRegistry) => { subMenu.addItem({ type: 'command', - command: CommandIDs.newGeoJSONEntry, + command: CommandIDs.openNewGeoJSONDialog, }); subMenu.addItem({ diff --git a/python/jupytergis_core/src/jgisplugin/plugins.ts b/python/jupytergis_core/src/jgisplugin/plugins.ts index 33da363ab..0480804b4 100644 --- a/python/jupytergis_core/src/jgisplugin/plugins.ts +++ b/python/jupytergis_core/src/jgisplugin/plugins.ts @@ -241,7 +241,7 @@ const activate = async ( }); palette.addItem({ - command: CommandIDs.newGeoJSONEntry, + command: CommandIDs.openNewGeoJSONDialog, category: 'JupyterGIS', }); From 8944cca4ca5e29e07a10dec78c93d206e0588928 Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Tue, 18 Nov 2025 15:53:33 +0530 Subject: [PATCH 37/54] Rename new layer commands --- packages/base/src/commands/BaseCommandIDs.ts | 16 +++---- packages/base/src/commands/index.ts | 48 +++++++++---------- packages/base/src/constants.ts | 16 +++---- packages/base/src/menus.ts | 14 +++--- .../jupytergis_core/src/jgisplugin/plugins.ts | 6 +-- 5 files changed, 50 insertions(+), 50 deletions(-) diff --git a/packages/base/src/commands/BaseCommandIDs.ts b/packages/base/src/commands/BaseCommandIDs.ts index b08881995..422280cb8 100644 --- a/packages/base/src/commands/BaseCommandIDs.ts +++ b/packages/base/src/commands/BaseCommandIDs.ts @@ -18,15 +18,15 @@ export const getGeolocation = 'jupytergis:getGeolocation'; export const openLayerBrowser = 'jupytergis:openLayerBrowser'; // Layer and source -export const newRasterEntry = 'jupytergis:newRasterEntry'; -export const newVectorTileEntry = 'jupytergis:newVectorTileEntry'; -export const newShapefileEntry = 'jupytergis:newShapefileEntry'; +export const opeNewRasterDialog = 'jupytergis:opeNewRasterDialog'; +export const openNewVectorTileDialog = 'jupytergis:openNewVectorTileDialog'; +export const openNewShapefileDialog = 'jupytergis:openNewShapefileDialog'; export const openNewGeoJSONDialog = 'jupytergis:openNewGeoJSONDialog'; -export const newHillshadeEntry = 'jupytergis:newHillshadeEntry'; -export const newImageEntry = 'jupytergis:newImageEntry'; -export const newVideoEntry = 'jupytergis:newVideoEntry'; -export const newGeoTiffEntry = 'jupytergis:newGeoTiffEntry'; -export const newGeoParquetEntry = 'jupytergis:newGeoParquetEntry'; +export const openNewHillshadeDialog = 'jupytergis:openNewHillshadeDialog'; +export const openNewImageDialog = 'jupytergis:openNewImageDialog'; +export const openNewVideoDialog = 'jupytergis:openNewVideoDialog'; +export const openNewGeoTiffDialog = 'jupytergis:openNewGeoTiffDialog'; +export const openNewGeoParquetDialog = 'jupytergis:openNewGeoParquetDialog'; // Layer and group actions export const renameLayer = 'jupytergis:renameLayer'; diff --git a/packages/base/src/commands/index.ts b/packages/base/src/commands/index.ts index 84948e1b4..3969cd0f3 100644 --- a/packages/base/src/commands/index.ts +++ b/packages/base/src/commands/index.ts @@ -357,8 +357,8 @@ export function addCommands( /** * Source and layers */ - commands.addCommand(CommandIDs.newRasterEntry, { - label: trans.__('New Raster Tile Layer'), + commands.addCommand(CommandIDs.opeNewRasterDialog, { + label: trans.__('Open New Raster Tile Dialog'), describedBy: { args: { type: 'object', @@ -384,11 +384,11 @@ export function addCommands( sourceType: 'RasterSource', layerType: 'RasterLayer', }), - ...icons.get(CommandIDs.newRasterEntry), + ...icons.get(CommandIDs.opeNewRasterDialog), }); - commands.addCommand(CommandIDs.newVectorTileEntry, { - label: trans.__('New Vector Tile Layer'), + commands.addCommand(CommandIDs.openNewVectorTileDialog, { + label: trans.__('Open New Vector Tile Dialog'), describedBy: { args: { type: 'object', @@ -411,11 +411,11 @@ export function addCommands( sourceType: 'VectorTileSource', layerType: 'VectorTileLayer', }), - ...icons.get(CommandIDs.newVectorTileEntry), + ...icons.get(CommandIDs.openNewVectorTileDialog), }); - commands.addCommand(CommandIDs.newGeoParquetEntry, { - label: trans.__('New GeoParquet Layer'), + commands.addCommand(CommandIDs.openNewGeoParquetDialog, { + label: trans.__('Open New GeoParquet Dialog'), describedBy: { args: { type: 'object', @@ -438,7 +438,7 @@ export function addCommands( sourceType: 'GeoParquetSource', layerType: 'VectorLayer', }), - ...icons.get(CommandIDs.newGeoParquetEntry), + ...icons.get(CommandIDs.openNewGeoParquetDialog), }); commands.addCommand(CommandIDs.openNewGeoJSONDialog, { @@ -478,8 +478,8 @@ export function addCommands( processingSchemas: Object.fromEntries(formSchemaRegistry.getSchemas()), }); - commands.addCommand(CommandIDs.newHillshadeEntry, { - label: trans.__('New Hillshade layer'), + commands.addCommand(CommandIDs.openNewHillshadeDialog, { + label: trans.__('Open New Hillshade Dialog'), describedBy: { args: { type: 'object', @@ -501,11 +501,11 @@ export function addCommands( sourceType: 'RasterDemSource', layerType: 'HillshadeLayer', }), - ...icons.get(CommandIDs.newHillshadeEntry), + ...icons.get(CommandIDs.openNewHillshadeDialog), }); - commands.addCommand(CommandIDs.newImageEntry, { - label: trans.__('New Image layer'), + commands.addCommand(CommandIDs.openNewImageDialog, { + label: trans.__('Open New Image Dialog'), describedBy: { args: { type: 'object', @@ -537,11 +537,11 @@ export function addCommands( sourceType: 'ImageSource', layerType: 'ImageLayer', }), - ...icons.get(CommandIDs.newImageEntry), + ...icons.get(CommandIDs.openNewImageDialog), }); - commands.addCommand(CommandIDs.newVideoEntry, { - label: trans.__('New Video layer'), + commands.addCommand(CommandIDs.openNewVideoDialog, { + label: trans.__('Open New Video Dialog'), describedBy: { args: { type: 'object', @@ -576,11 +576,11 @@ export function addCommands( sourceType: 'VideoSource', layerType: 'RasterLayer', }), - ...icons.get(CommandIDs.newVideoEntry), + ...icons.get(CommandIDs.openNewVideoDialog), }); - commands.addCommand(CommandIDs.newGeoTiffEntry, { - label: trans.__('New GeoTiff layer'), + commands.addCommand(CommandIDs.openNewGeoTiffDialog, { + label: trans.__('Open New GeoTiff Dialog'), describedBy: { args: { type: 'object', @@ -606,11 +606,11 @@ export function addCommands( sourceType: 'GeoTiffSource', layerType: 'WebGlLayer', }), - ...icons.get(CommandIDs.newGeoTiffEntry), + ...icons.get(CommandIDs.openNewGeoTiffDialog), }); - commands.addCommand(CommandIDs.newShapefileEntry, { - label: trans.__('New Shapefile Layer'), + commands.addCommand(CommandIDs.openNewShapefileDialog, { + label: trans.__('Open New Shapefile Dialog'), describedBy: { args: { type: 'object', @@ -633,7 +633,7 @@ export function addCommands( sourceType: 'ShapefileSource', layerType: 'VectorLayer', }), - ...icons.get(CommandIDs.newShapefileEntry), + ...icons.get(CommandIDs.openNewShapefileDialog), }); /** diff --git a/packages/base/src/constants.ts b/packages/base/src/constants.ts index 1621105cc..60fd4e056 100644 --- a/packages/base/src/constants.ts +++ b/packages/base/src/constants.ts @@ -45,15 +45,15 @@ const iconObject = { [CommandIDs.redo]: { icon: redoIcon }, [CommandIDs.undo]: { icon: undoIcon }, [CommandIDs.openLayerBrowser]: { icon: bookOpenIcon }, - [CommandIDs.newRasterEntry]: { icon: rasterIcon }, - [CommandIDs.newVectorTileEntry]: { icon: vectorSquareIcon }, + [CommandIDs.opeNewRasterDialog]: { icon: rasterIcon }, + [CommandIDs.openNewVectorTileDialog]: { icon: vectorSquareIcon }, [CommandIDs.openNewGeoJSONDialog]: { icon: geoJSONIcon }, - [CommandIDs.newHillshadeEntry]: { icon: moundIcon }, - [CommandIDs.newImageEntry]: { iconClass: 'fa fa-image' }, - [CommandIDs.newVideoEntry]: { iconClass: 'fa fa-video' }, - [CommandIDs.newShapefileEntry]: { iconClass: 'fa fa-file' }, - [CommandIDs.newGeoTiffEntry]: { iconClass: 'fa fa-image' }, - [CommandIDs.newGeoParquetEntry]: { iconClass: 'fa fa-file' }, + [CommandIDs.openNewHillshadeDialog]: { icon: moundIcon }, + [CommandIDs.openNewImageDialog]: { iconClass: 'fa fa-image' }, + [CommandIDs.openNewVideoDialog]: { iconClass: 'fa fa-video' }, + [CommandIDs.openNewShapefileDialog]: { iconClass: 'fa fa-file' }, + [CommandIDs.openNewGeoTiffDialog]: { iconClass: 'fa fa-image' }, + [CommandIDs.openNewGeoParquetDialog]: { iconClass: 'fa fa-file' }, [CommandIDs.symbology]: { iconClass: 'fa fa-brush' }, [CommandIDs.identify]: { icon: infoIcon }, [CommandIDs.temporalController]: { icon: clockIcon }, diff --git a/packages/base/src/menus.ts b/packages/base/src/menus.ts index a93b994cc..af11b070e 100644 --- a/packages/base/src/menus.ts +++ b/packages/base/src/menus.ts @@ -13,7 +13,7 @@ export const vectorSubMenu = (commands: CommandRegistry) => { subMenu.addItem({ type: 'command', - command: CommandIDs.newVectorTileEntry, + command: CommandIDs.openNewVectorTileDialog, }); subMenu.addItem({ @@ -23,12 +23,12 @@ export const vectorSubMenu = (commands: CommandRegistry) => { subMenu.addItem({ type: 'command', - command: CommandIDs.newShapefileEntry, + command: CommandIDs.openNewShapefileDialog, }); subMenu.addItem({ type: 'command', - command: CommandIDs.newGeoParquetEntry, + command: CommandIDs.openNewGeoParquetDialog, }); return subMenu; @@ -43,22 +43,22 @@ export const rasterSubMenu = (commands: CommandRegistry) => { subMenu.addItem({ type: 'command', - command: CommandIDs.newRasterEntry, + command: CommandIDs.opeNewRasterDialog, }); subMenu.addItem({ type: 'command', - command: CommandIDs.newHillshadeEntry, + command: CommandIDs.openNewHillshadeDialog, }); subMenu.addItem({ type: 'command', - command: CommandIDs.newImageEntry, + command: CommandIDs.openNewImageDialog, }); subMenu.addItem({ type: 'command', - command: CommandIDs.newGeoTiffEntry, + command: CommandIDs.openNewGeoTiffDialog, }); return subMenu; diff --git a/python/jupytergis_core/src/jgisplugin/plugins.ts b/python/jupytergis_core/src/jgisplugin/plugins.ts index 0480804b4..c27d20ecf 100644 --- a/python/jupytergis_core/src/jgisplugin/plugins.ts +++ b/python/jupytergis_core/src/jgisplugin/plugins.ts @@ -231,12 +231,12 @@ const activate = async ( // Layers and Sources palette.addItem({ - command: CommandIDs.newRasterEntry, + command: CommandIDs.opeNewRasterDialog, category: 'JupyterGIS', }); palette.addItem({ - command: CommandIDs.newVectorTileEntry, + command: CommandIDs.openNewVectorTileDialog, category: 'JupyterGIS', }); @@ -246,7 +246,7 @@ const activate = async ( }); palette.addItem({ - command: CommandIDs.newHillshadeEntry, + command: CommandIDs.openNewHillshadeDialog, category: 'JupyterGIS', }); From 57bd853370ecef2fc0c568c44da9fba67f46eff6 Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Tue, 18 Nov 2025 16:05:33 +0530 Subject: [PATCH 38/54] redo --- .../src/commands/documentActionCommands.ts | 25 ------------------- packages/base/src/commands/index.ts | 19 ++++++++++---- 2 files changed, 14 insertions(+), 30 deletions(-) diff --git a/packages/base/src/commands/documentActionCommands.ts b/packages/base/src/commands/documentActionCommands.ts index 17105f43f..8c570d66c 100644 --- a/packages/base/src/commands/documentActionCommands.ts +++ b/packages/base/src/commands/documentActionCommands.ts @@ -9,7 +9,6 @@ import { downloadFile, getGeoJSONDataFromLayerSource } from '../tools'; import { JupyterGISTracker } from '../types'; export namespace DocumentActionCommandIDs { - export const redoWithParams = 'jupytergis:redoWithParams'; export const temporalControllerWithParams = 'jupytergis:temporalControllerWithParams'; export const renameLayerWithParams = 'jupytergis:renameLayerWithParams'; @@ -35,30 +34,6 @@ export function addDocumentActionCommands(options: { }) { const { commands, tracker, trans } = options; - commands.addCommand(DocumentActionCommandIDs.redoWithParams, { - label: trans.__('Redo from file name'), - isEnabled: () => true, - describedBy: { - args: { - type: 'object', - required: ['filePath'], - properties: { - filePath: { - type: 'string', - description: 'The path to the .jGIS file to be modified', - }, - }, - }, - }, - execute: (async (args: { filePath: string }) => { - const { filePath } = args; - const current = tracker.find(w => w.model.filePath === filePath); - if (current) { - return current.model.sharedModel.redo(); - } - }) as any, - }); - commands.addCommand(DocumentActionCommandIDs.temporalControllerWithParams, { label: trans.__('Toggle Temporal Controller from file name'), isEnabled: () => true, diff --git a/packages/base/src/commands/index.ts b/packages/base/src/commands/index.ts index 3969cd0f3..5223d25b9 100644 --- a/packages/base/src/commands/index.ts +++ b/packages/base/src/commands/index.ts @@ -132,16 +132,25 @@ export function addCommands( describedBy: { args: { type: 'object', - properties: {}, - }, + properties: { + filePath: { + type: 'string', + description: 'Optional .jGIS file path. If omitted, uses active widget.', + } + } + } }, isEnabled: () => { return tracker.currentWidget ? tracker.currentWidget.model.sharedModel.editable : false; }, - execute: () => { - const current = tracker.currentWidget; + execute: (args?: { filePath?: string }) => { + const filePath = args?.filePath; + + const current = filePath + ? tracker.find(w => w.model.filePath === filePath) + : tracker.currentWidget; if (current) { return current.model.sharedModel.redo(); @@ -160,7 +169,7 @@ export function addCommands( filePath: { type: 'string', description: - 'Optional .jGIS file path. If omitted, uses the focused file.', + 'Optional .jGIS file path. If omitted, uses active widget.', }, }, }, From 92914db8ee6827035d4060271eca3095100b2284 Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Tue, 18 Nov 2025 16:13:25 +0530 Subject: [PATCH 39/54] temporal controller --- .../src/commands/documentActionCommands.ts | 64 ------------------- packages/base/src/commands/index.ts | 19 ++++-- 2 files changed, 15 insertions(+), 68 deletions(-) diff --git a/packages/base/src/commands/documentActionCommands.ts b/packages/base/src/commands/documentActionCommands.ts index 8c570d66c..1853657b8 100644 --- a/packages/base/src/commands/documentActionCommands.ts +++ b/packages/base/src/commands/documentActionCommands.ts @@ -9,8 +9,6 @@ import { downloadFile, getGeoJSONDataFromLayerSource } from '../tools'; import { JupyterGISTracker } from '../types'; export namespace DocumentActionCommandIDs { - export const temporalControllerWithParams = - 'jupytergis:temporalControllerWithParams'; export const renameLayerWithParams = 'jupytergis:renameLayerWithParams'; export const removeLayerWithParams = 'jupytergis:removeLayerWithParams'; export const renameGroupWithParams = 'jupytergis:renameGroupWithParams'; @@ -34,68 +32,6 @@ export function addDocumentActionCommands(options: { }) { const { commands, tracker, trans } = options; - commands.addCommand(DocumentActionCommandIDs.temporalControllerWithParams, { - label: trans.__('Toggle Temporal Controller from file name'), - isEnabled: () => true, - describedBy: { - args: { - type: 'object', - required: ['filePath'], - properties: { - filePath: { - type: 'string', - description: - 'Path to the .jGIS file whose temporal controller should be toggled', - }, - }, - }, - }, - isToggled: () => { - const current = tracker.currentWidget; - return current?.model.isTemporalControllerActive || false; - }, - execute: (async (args: { filePath: string }) => { - const { filePath } = args; - const current = tracker.find(w => w.model.filePath === filePath); - if (!current) { - console.warn('No JupyterGIS widget found for', filePath); - return; - } - - const model = current.model; - const selectedLayers = model.localState?.selected?.value; - if (!selectedLayers) { - console.warn('No layer selected'); - return; - } - - const layerId = Object.keys(selectedLayers)[0]; - const layerType = model.getLayer(layerId)?.type; - if (!layerType) { - console.warn('Selected layer has no type'); - return; - } - - const isSelectionValid = - Object.keys(selectedLayers).length === 1 && - !model.getSource(layerId) && - ['VectorLayer', 'HeatmapLayer'].includes(layerType); - - if (!isSelectionValid && model.isTemporalControllerActive) { - model.toggleTemporalController(); - commands.notifyCommandChanged( - DocumentActionCommandIDs.temporalControllerWithParams, - ); - return; - } - - model.toggleTemporalController(); - commands.notifyCommandChanged( - DocumentActionCommandIDs.temporalControllerWithParams, - ); - }) as any, - }); - commands.addCommand(DocumentActionCommandIDs.renameLayerWithParams, { label: trans.__('Rename layer from file name'), isEnabled: () => true, diff --git a/packages/base/src/commands/index.ts b/packages/base/src/commands/index.ts index 5223d25b9..e929cb224 100644 --- a/packages/base/src/commands/index.ts +++ b/packages/base/src/commands/index.ts @@ -289,8 +289,13 @@ export function addCommands( describedBy: { args: { type: 'object', - properties: {}, - }, + properties: { + filePath: { + type: 'string', + description: 'Optional path to the .jGIS file' + } + } + } }, isToggled: () => { return tracker.currentWidget?.model.isTemporalControllerActive || false; @@ -327,8 +332,14 @@ export function addCommands( return true; }, - execute: () => { - const current = tracker.currentWidget; + + execute: (args?: { filePath?: string }) => { + const filePath = args?.filePath; + + const current = filePath + ? tracker.find(w => w.model.filePath === filePath) + : tracker.currentWidget; + if (!current) { return; } From bd68fdad26e5c2dae1b9d2f3510baabfbfa16ace Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Tue, 18 Nov 2025 16:14:45 +0530 Subject: [PATCH 40/54] rename layer --- .../src/commands/documentActionCommands.ts | 47 ---------------- packages/base/src/commands/index.ts | 56 ++++++++++++++++--- 2 files changed, 48 insertions(+), 55 deletions(-) diff --git a/packages/base/src/commands/documentActionCommands.ts b/packages/base/src/commands/documentActionCommands.ts index 1853657b8..e028a985e 100644 --- a/packages/base/src/commands/documentActionCommands.ts +++ b/packages/base/src/commands/documentActionCommands.ts @@ -9,7 +9,6 @@ import { downloadFile, getGeoJSONDataFromLayerSource } from '../tools'; import { JupyterGISTracker } from '../types'; export namespace DocumentActionCommandIDs { - export const renameLayerWithParams = 'jupytergis:renameLayerWithParams'; export const removeLayerWithParams = 'jupytergis:removeLayerWithParams'; export const renameGroupWithParams = 'jupytergis:renameGroupWithParams'; export const removeGroupWithParams = 'jupytergis:removeGroupWithParams'; @@ -32,52 +31,6 @@ export function addDocumentActionCommands(options: { }) { const { commands, tracker, trans } = options; - commands.addCommand(DocumentActionCommandIDs.renameLayerWithParams, { - label: trans.__('Rename layer from file name'), - isEnabled: () => true, - describedBy: { - args: { - type: 'object', - required: ['filePath', 'layerId', 'newName'], - properties: { - filePath: { - type: 'string', - description: 'The path to the .jGIS file to be modified', - }, - layerId: { - type: 'string', - description: 'The ID of the layer to be renamed', - }, - newName: { - type: 'string', - description: 'The new name for the layer', - }, - }, - }, - }, - execute: (async (args: { - filePath: string; - layerId: string; - newName: string; - }) => { - const { filePath, layerId, newName } = args; - const current = tracker.find(w => w.model.filePath === filePath); - - if (!current || !current.model.sharedModel.editable) { - return; - } - - const sharedModel = current.model.sharedModel; - const layer = sharedModel.layers[layerId]; - if (!layer) { - return; - } - - layer.name = newName; - sharedModel.updateLayer(layerId, layer); - }) as any, - }); - commands.addCommand(DocumentActionCommandIDs.removeLayerWithParams, { label: trans.__('Remove layer from file name'), isEnabled: () => true, diff --git a/packages/base/src/commands/index.ts b/packages/base/src/commands/index.ts index e929cb224..9abf5298b 100644 --- a/packages/base/src/commands/index.ts +++ b/packages/base/src/commands/index.ts @@ -664,16 +664,56 @@ export function addCommands( describedBy: { args: { type: 'object', - properties: {}, - }, + properties: { + filePath: { + type: 'string', + description: 'Optional path to the .jGIS file' + }, + layerId: { + type: 'string', + description: 'Optional ID of the layer to rename' + }, + newName: { + type: 'string', + description: 'Optional new name for the layer' + } + } + } }, - execute: async () => { - const model = tracker.currentWidget?.model; - await Private.renameSelectedItem(model, 'layer', (layerId, newName) => { - const layer = model?.getLayer(layerId); + + execute: async (args?: { + filePath?: string; + layerId?: string; + newName?: string; + }) => { + const { filePath, layerId, newName } = args ?? {}; + + const model = filePath + ? tracker.find(w => w.model.filePath === filePath)?.model + : tracker.currentWidget?.model; + + if (!model || !model.sharedModel.editable) { + return; + } + + // ---- PARAMETER MODE ---- + // If all args are present, use them + if (filePath && layerId && newName) { + const layer = model.sharedModel.layers[layerId]; + if (!layer) { + return; + } + layer.name = newName; + model.sharedModel.updateLayer(layerId, layer); + return; + } + + // ---- FALLBACK TO ORIGINAL BEHAVIOR ---- + await Private.renameSelectedItem(model, 'layer', (id, name) => { + const layer = model.getLayer(id); if (layer) { - layer.name = newName; - model?.sharedModel.updateLayer(layerId, layer); + layer.name = name; + model.sharedModel.updateLayer(id, layer); } }); }, From 940e0b59dbfb5fd59441ec915ca6845007d152eb Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Tue, 18 Nov 2025 16:17:34 +0530 Subject: [PATCH 41/54] remove layer --- .../src/commands/documentActionCommands.ts | 38 ------------------- packages/base/src/commands/index.ts | 38 +++++++++++++++++-- 2 files changed, 34 insertions(+), 42 deletions(-) diff --git a/packages/base/src/commands/documentActionCommands.ts b/packages/base/src/commands/documentActionCommands.ts index e028a985e..4fa49fe13 100644 --- a/packages/base/src/commands/documentActionCommands.ts +++ b/packages/base/src/commands/documentActionCommands.ts @@ -9,7 +9,6 @@ import { downloadFile, getGeoJSONDataFromLayerSource } from '../tools'; import { JupyterGISTracker } from '../types'; export namespace DocumentActionCommandIDs { - export const removeLayerWithParams = 'jupytergis:removeLayerWithParams'; export const renameGroupWithParams = 'jupytergis:renameGroupWithParams'; export const removeGroupWithParams = 'jupytergis:removeGroupWithParams'; export const moveLayersToGroupWithParams = @@ -31,43 +30,6 @@ export function addDocumentActionCommands(options: { }) { const { commands, tracker, trans } = options; - commands.addCommand(DocumentActionCommandIDs.removeLayerWithParams, { - label: trans.__('Remove layer from file name'), - isEnabled: () => true, - describedBy: { - args: { - type: 'object', - required: ['filePath', 'layerId'], - properties: { - filePath: { - type: 'string', - description: 'The path to the .jGIS file to be modified', - }, - layerId: { - type: 'string', - description: 'The ID of the layer to be removed', - }, - }, - }, - }, - execute: ((args: { filePath: string; layerId: string }) => { - const { filePath, layerId } = args; - const current = tracker.find(w => w.model.filePath === filePath); - - if (!current || !current.model.sharedModel.editable) { - return; - } - - const sharedModel = current.model.sharedModel; - const existing = sharedModel.layers[layerId]; - if (!existing) { - return; - } - - current.model.removeLayer(layerId); - }) as any, - }); - commands.addCommand(DocumentActionCommandIDs.renameGroupWithParams, { label: trans.__('Rename Group from file name'), isEnabled: () => true, diff --git a/packages/base/src/commands/index.ts b/packages/base/src/commands/index.ts index 9abf5298b..550c1e84e 100644 --- a/packages/base/src/commands/index.ts +++ b/packages/base/src/commands/index.ts @@ -724,11 +724,41 @@ export function addCommands( describedBy: { args: { type: 'object', - properties: {}, - }, + properties: { + filePath: { + type: 'string', + description: 'Optional path to the .jGIS file' + }, + layerId: { + type: 'string', + description: 'Optional ID of the layer to remove' + } + } + } }, - execute: () => { - const model = tracker.currentWidget?.model; + + execute: (args?: { filePath?: string; layerId?: string }) => { + const { filePath, layerId } = args ?? {}; + + const model = filePath + ? tracker.find(w => w.model.filePath === filePath)?.model + : tracker.currentWidget?.model; + + if (!model || !model.sharedModel.editable) { + return; + } + + // ---- PARAMETER MODE ---- + if (filePath && layerId) { + const exists = model.sharedModel.layers[layerId]; + if (!exists) { + return; + } + model.removeLayer(layerId); + return; + } + + // ---- FALLBACK TO ORIGINAL BEHAVIOR ---- Private.removeSelectedItems(model, 'layer', selection => { model?.removeLayer(selection); }); From a9786dc0de26cd98fe4f12b1c660b38f3ba8585f Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Tue, 18 Nov 2025 16:28:26 +0530 Subject: [PATCH 42/54] rename group --- .../src/commands/documentActionCommands.ts | 40 ------------------ packages/base/src/commands/index.ts | 42 +++++++++++++++++-- 2 files changed, 38 insertions(+), 44 deletions(-) diff --git a/packages/base/src/commands/documentActionCommands.ts b/packages/base/src/commands/documentActionCommands.ts index 4fa49fe13..90f89129c 100644 --- a/packages/base/src/commands/documentActionCommands.ts +++ b/packages/base/src/commands/documentActionCommands.ts @@ -9,7 +9,6 @@ import { downloadFile, getGeoJSONDataFromLayerSource } from '../tools'; import { JupyterGISTracker } from '../types'; export namespace DocumentActionCommandIDs { - export const renameGroupWithParams = 'jupytergis:renameGroupWithParams'; export const removeGroupWithParams = 'jupytergis:removeGroupWithParams'; export const moveLayersToGroupWithParams = 'jupytergis:moveLayersToGroupWithParams'; @@ -30,45 +29,6 @@ export function addDocumentActionCommands(options: { }) { const { commands, tracker, trans } = options; - commands.addCommand(DocumentActionCommandIDs.renameGroupWithParams, { - label: trans.__('Rename Group from file name'), - isEnabled: () => true, - describedBy: { - args: { - type: 'object', - required: ['filePath', 'oldName', 'newName'], - properties: { - filePath: { - type: 'string', - description: 'The path to the .jGIS file to be modified', - }, - oldName: { - type: 'string', - description: 'The existing name of the group to rename', - }, - newName: { - type: 'string', - description: 'The new name for the group', - }, - }, - }, - }, - execute: (async (args: { - filePath: string; - oldName: string; - newName: string; - }) => { - const { filePath, oldName, newName } = args; - const current = tracker.find(w => w.model.filePath === filePath); - if (!current || !current.model.sharedModel.editable) { - return; - } - - const model = current.model; - model.renameLayerGroup(oldName, newName); - }) as any, - }); - commands.addCommand(DocumentActionCommandIDs.removeGroupWithParams, { label: trans.__('Remove group from file name'), isEnabled: () => true, diff --git a/packages/base/src/commands/index.ts b/packages/base/src/commands/index.ts index 550c1e84e..3917d8641 100644 --- a/packages/base/src/commands/index.ts +++ b/packages/base/src/commands/index.ts @@ -770,11 +770,45 @@ export function addCommands( describedBy: { args: { type: 'object', - properties: {}, - }, + properties: { + filePath: { + type: 'string', + description: 'Optional .jGIS file path' + }, + oldName: { + type: 'string', + description: 'Optional existing group name' + }, + newName: { + type: 'string', + description: 'Optional new group name' + } + } + } }, - execute: async () => { - const model = tracker.currentWidget?.model; + + execute: async (args?: { + filePath?: string; + oldName?: string; + newName?: string; + }) => { + const { filePath, oldName, newName } = args ?? {}; + + const model = filePath + ? tracker.find(w => w.model.filePath === filePath)?.model + : tracker.currentWidget?.model; + + if (!model || !model.sharedModel.editable) { + return; + } + + // ---- PARAMETER MODE ---- + if (filePath && oldName && newName) { + model.renameLayerGroup(oldName, newName); + return; + } + + // ---- FALLBACK TO ORIGINAL INTERACTIVE BEHAVIOR ---- await Private.renameSelectedItem(model, 'group', (groupName, newName) => { model?.renameLayerGroup(groupName, newName); }); From 38eac11bab3692c2f2ffee0bb3989094b61ba62a Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Tue, 18 Nov 2025 16:51:34 +0530 Subject: [PATCH 43/54] fix --- packages/base/src/commands/index.ts | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/packages/base/src/commands/index.ts b/packages/base/src/commands/index.ts index ea9e2601e..bb3453cdd 100644 --- a/packages/base/src/commands/index.ts +++ b/packages/base/src/commands/index.ts @@ -709,13 +709,7 @@ export function addCommands( } // ---- FALLBACK TO ORIGINAL BEHAVIOR ---- - await Private.renameSelectedItem(model, 'layer', (id, name) => { - const layer = model.getLayer(id); - if (layer) { - layer.name = name; - model.sharedModel.updateLayer(id, layer); - } - }); + await Private.renameSelectedItem(model, 'layer'); }, }); @@ -809,9 +803,7 @@ export function addCommands( } // ---- FALLBACK TO ORIGINAL INTERACTIVE BEHAVIOR ---- - await Private.renameSelectedItem(model, 'group', (groupName, newName) => { - model?.renameLayerGroup(groupName, newName); - }); + await Private.renameSelectedItem(model, 'group'); }, }); From d9a3736ebf6f878343a6d9ec61b1685bb60c89de Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Tue, 18 Nov 2025 17:00:56 +0530 Subject: [PATCH 44/54] lint --- packages/base/src/commands/index.ts | 51 ++++++++++--------- .../base/src/panelview/components/layers.tsx | 2 +- 2 files changed, 27 insertions(+), 26 deletions(-) diff --git a/packages/base/src/commands/index.ts b/packages/base/src/commands/index.ts index bb3453cdd..3a8f3dfa6 100644 --- a/packages/base/src/commands/index.ts +++ b/packages/base/src/commands/index.ts @@ -135,10 +135,11 @@ export function addCommands( properties: { filePath: { type: 'string', - description: 'Optional .jGIS file path. If omitted, uses active widget.', - } - } - } + description: + 'Optional .jGIS file path. If omitted, uses active widget.', + }, + }, + }, }, isEnabled: () => { return tracker.currentWidget @@ -292,10 +293,10 @@ export function addCommands( properties: { filePath: { type: 'string', - description: 'Optional path to the .jGIS file' - } - } - } + description: 'Optional path to the .jGIS file', + }, + }, + }, }, isToggled: () => { return tracker.currentWidget?.model.isTemporalControllerActive || false; @@ -667,18 +668,18 @@ export function addCommands( properties: { filePath: { type: 'string', - description: 'Optional path to the .jGIS file' + description: 'Optional path to the .jGIS file', }, layerId: { type: 'string', - description: 'Optional ID of the layer to rename' + description: 'Optional ID of the layer to rename', }, newName: { type: 'string', - description: 'Optional new name for the layer' - } - } - } + description: 'Optional new name for the layer', + }, + }, + }, }, execute: async (args?: { @@ -721,14 +722,14 @@ export function addCommands( properties: { filePath: { type: 'string', - description: 'Optional path to the .jGIS file' + description: 'Optional path to the .jGIS file', }, layerId: { type: 'string', - description: 'Optional ID of the layer to remove' - } - } - } + description: 'Optional ID of the layer to remove', + }, + }, + }, }, execute: (args?: { filePath?: string; layerId?: string }) => { @@ -767,18 +768,18 @@ export function addCommands( properties: { filePath: { type: 'string', - description: 'Optional .jGIS file path' + description: 'Optional .jGIS file path', }, oldName: { type: 'string', - description: 'Optional existing group name' + description: 'Optional existing group name', }, newName: { type: 'string', - description: 'Optional new group name' - } - } - } + description: 'Optional new group name', + }, + }, + }, }, execute: async (args?: { diff --git a/packages/base/src/panelview/components/layers.tsx b/packages/base/src/panelview/components/layers.tsx index cdb1873c6..8ade5eb94 100644 --- a/packages/base/src/panelview/components/layers.tsx +++ b/packages/base/src/panelview/components/layers.tsx @@ -150,7 +150,7 @@ export const LayersBodyComponent: React.FC = props => { } else { // Check if new selection is the same type as previous selections const isSelectedSameType = Object.values(selectedValue).some( - selection => (selection as ISelection).type === type, + selection => (selection ).type === type, ); if (!isSelectedSameType) { From 38264e85a94967dcdebaf39db9218088ac9deffc Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Tue, 18 Nov 2025 17:24:13 +0530 Subject: [PATCH 45/54] lint --- packages/base/src/panelview/components/layers.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/base/src/panelview/components/layers.tsx b/packages/base/src/panelview/components/layers.tsx index 8ade5eb94..da3ad7362 100644 --- a/packages/base/src/panelview/components/layers.tsx +++ b/packages/base/src/panelview/components/layers.tsx @@ -150,7 +150,7 @@ export const LayersBodyComponent: React.FC = props => { } else { // Check if new selection is the same type as previous selections const isSelectedSameType = Object.values(selectedValue).some( - selection => (selection ).type === type, + selection => selection.type === type, ); if (!isSelectedSameType) { From 8fcbc1b253363647ab314bbeecb38c5e77803957 Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Tue, 18 Nov 2025 17:35:52 +0530 Subject: [PATCH 46/54] remove group --- .../src/commands/documentActionCommands.ts | 29 -------------- packages/base/src/commands/index.ts | 40 +++++++++++++++---- 2 files changed, 33 insertions(+), 36 deletions(-) diff --git a/packages/base/src/commands/documentActionCommands.ts b/packages/base/src/commands/documentActionCommands.ts index 90f89129c..cd7406628 100644 --- a/packages/base/src/commands/documentActionCommands.ts +++ b/packages/base/src/commands/documentActionCommands.ts @@ -29,35 +29,6 @@ export function addDocumentActionCommands(options: { }) { const { commands, tracker, trans } = options; - commands.addCommand(DocumentActionCommandIDs.removeGroupWithParams, { - label: trans.__('Remove group from file name'), - isEnabled: () => true, - describedBy: { - args: { - type: 'object', - required: ['filePath', 'groupName'], - properties: { - filePath: { - type: 'string', - description: 'The path to the .jGIS file to be modified', - }, - groupName: { - type: 'string', - description: 'The name of the group to remove', - }, - }, - }, - }, - execute: ((args: { filePath: string; groupName: string }) => { - const { filePath, groupName } = args; - const current = tracker.find(w => w.model.filePath === filePath); - if (!current || !current.model.sharedModel.editable) { - return; - } - current.model.removeLayerGroup(groupName); - }) as any, - }); - commands.addCommand(DocumentActionCommandIDs.moveLayersToGroupWithParams, { label: trans.__('Move layers to group from file name'), isEnabled: () => true, diff --git a/packages/base/src/commands/index.ts b/packages/base/src/commands/index.ts index 3a8f3dfa6..4c30b77bd 100644 --- a/packages/base/src/commands/index.ts +++ b/packages/base/src/commands/index.ts @@ -709,7 +709,7 @@ export function addCommands( return; } - // ---- FALLBACK TO ORIGINAL BEHAVIOR ---- + // ---- FALLBACK TO ORIGINAL INTERACTIVE BEHAVIOR ---- await Private.renameSelectedItem(model, 'layer'); }, }); @@ -753,7 +753,7 @@ export function addCommands( return; } - // ---- FALLBACK TO ORIGINAL BEHAVIOR ---- + // ---- FALLBACK TO ORIGINAL INTERACTIVE BEHAVIOR ---- Private.removeSelectedItems(model, 'layer', selection => { model?.removeLayer(selection); }); @@ -813,12 +813,38 @@ export function addCommands( describedBy: { args: { type: 'object', - properties: {}, - }, + properties: { + filePath: { + type: 'string', + description: 'Optional .jGIS file path' + }, + groupName: { + type: 'string', + description: 'Optional group name to remove' + } + } + } }, - execute: async () => { - const model = tracker.currentWidget?.model; - Private.removeSelectedItems(model, 'group', selection => { + + execute: async (args?: { filePath?: string; groupName?: string }) => { + const { filePath, groupName } = args ?? {}; + + const model = filePath + ? tracker.find(w => w.model.filePath === filePath)?.model + : tracker.currentWidget?.model; + + if (!model || !model.sharedModel.editable) { + return; + } + + // ---- PARAMETER MODE ---- + if (filePath && groupName) { + model.removeLayerGroup(groupName); + return; + } + + // ---- FALLBACK TO ORIGINAL INTERACTIVE BEHAVIOR ---- + await Private.removeSelectedItems(model, 'group', selection => { model?.removeLayerGroup(selection); }); }, From fb4b103c66caa0c49cad9ed4fd15a9552a28227e Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Tue, 18 Nov 2025 17:38:28 +0530 Subject: [PATCH 47/54] move layer to group --- .../src/commands/documentActionCommands.ts | 42 ------------------- packages/base/src/commands/index.ts | 37 +++++++++++++--- 2 files changed, 32 insertions(+), 47 deletions(-) diff --git a/packages/base/src/commands/documentActionCommands.ts b/packages/base/src/commands/documentActionCommands.ts index cd7406628..10536832d 100644 --- a/packages/base/src/commands/documentActionCommands.ts +++ b/packages/base/src/commands/documentActionCommands.ts @@ -9,9 +9,6 @@ import { downloadFile, getGeoJSONDataFromLayerSource } from '../tools'; import { JupyterGISTracker } from '../types'; export namespace DocumentActionCommandIDs { - export const removeGroupWithParams = 'jupytergis:removeGroupWithParams'; - export const moveLayersToGroupWithParams = - 'jupytergis:moveLayersToGroupWithParams'; export const moveLayerToNewGroupWithParams = 'jupytergis:moveLayerToNewGroupWithParams'; export const renameSourceWithParams = 'jupytergis:renameSourceWithParams'; @@ -29,45 +26,6 @@ export function addDocumentActionCommands(options: { }) { const { commands, tracker, trans } = options; - commands.addCommand(DocumentActionCommandIDs.moveLayersToGroupWithParams, { - label: trans.__('Move layers to group from file name'), - isEnabled: () => true, - describedBy: { - args: { - type: 'object', - required: ['filePath', 'layerIds', 'groupName'], - properties: { - filePath: { - type: 'string', - description: 'The path to the .jGIS file to be modified', - }, - layerIds: { - type: 'array', - description: 'Array of layer IDs to move', - items: { type: 'string' }, - }, - groupName: { - type: 'string', - description: - 'The name of the target group. Use empty string for root.', - }, - }, - }, - }, - execute: ((args: { - filePath: string; - layerIds: string[]; - groupName: string; - }) => { - const { filePath, layerIds, groupName } = args; - const current = tracker.find(w => w.model.filePath === filePath); - if (!current || !current.model.sharedModel.editable) { - return; - } - current.model.moveItemsToGroup(layerIds, groupName); - }) as any, - }); - commands.addCommand(DocumentActionCommandIDs.moveLayerToNewGroupWithParams, { label: trans.__('Move selected layers to new group from file name'), isEnabled: () => true, diff --git a/packages/base/src/commands/index.ts b/packages/base/src/commands/index.ts index 4c30b77bd..0903fd4f6 100644 --- a/packages/base/src/commands/index.ts +++ b/packages/base/src/commands/index.ts @@ -858,20 +858,47 @@ export function addCommands( type: 'object', properties: { label: { type: 'string' }, + filePath: { type: 'string' }, + layerIds: { + type: 'array', + items: { type: 'string' } + }, + groupName: { type: 'string' } }, }, }, - execute: args => { - const model = tracker.currentWidget?.model; - const groupName = args['label'] as string; - const selectedLayers = model?.localState?.selected?.value; + execute: (args?: { + filePath?: string; + layerIds?: string[]; + groupName?: string; + label?: string; + }) => { + const { filePath, layerIds, groupName } = args ?? {}; + + // Resolve model based on filePath or current widget + const model = filePath + ? tracker.find(w => w.model.filePath === filePath)?.model + : tracker.currentWidget?.model; + if (!model || !model.sharedModel.editable) { + return; + } + + // ---- PARAMETER MODE ---- + if (filePath && layerIds && groupName !== undefined) { + model.moveItemsToGroup(layerIds, groupName); + return; + } + + // ---- FALLBACK TO ORIGINAL INTERACTIVE BEHAVIOR ---- + const selectedLayers = model.localState?.selected?.value; if (!selectedLayers) { return; } - model.moveItemsToGroup(Object.keys(selectedLayers), groupName); + const targetGroup = args?.label as string; + model.moveItemsToGroup(Object.keys(selectedLayers), targetGroup); }, }); From 1cf41e3f87409a2ac7ba9e97c74e78c7a1c4a1e7 Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Tue, 18 Nov 2025 17:45:26 +0530 Subject: [PATCH 48/54] move to new group --- .../src/commands/documentActionCommands.ts | 53 +------------------ packages/base/src/commands/index.ts | 47 ++++++++++++++-- 2 files changed, 43 insertions(+), 57 deletions(-) diff --git a/packages/base/src/commands/documentActionCommands.ts b/packages/base/src/commands/documentActionCommands.ts index 10536832d..95440cbd3 100644 --- a/packages/base/src/commands/documentActionCommands.ts +++ b/packages/base/src/commands/documentActionCommands.ts @@ -1,4 +1,4 @@ -import { IJGISLayerGroup, JgisCoordinates } from '@jupytergis/schema'; +import { JgisCoordinates } from '@jupytergis/schema'; import { showErrorMessage } from '@jupyterlab/apputils'; import { IRenderMime } from '@jupyterlab/rendermime'; import { CommandRegistry } from '@lumino/commands'; @@ -9,8 +9,6 @@ import { downloadFile, getGeoJSONDataFromLayerSource } from '../tools'; import { JupyterGISTracker } from '../types'; export namespace DocumentActionCommandIDs { - export const moveLayerToNewGroupWithParams = - 'jupytergis:moveLayerToNewGroupWithParams'; export const renameSourceWithParams = 'jupytergis:renameSourceWithParams'; export const removeSourceWithParams = 'jupytergis:removeSourceWithParams'; export const zoomToLayerWithParams = 'jupytergis:zoomToLayerWithParams'; @@ -26,55 +24,6 @@ export function addDocumentActionCommands(options: { }) { const { commands, tracker, trans } = options; - commands.addCommand(DocumentActionCommandIDs.moveLayerToNewGroupWithParams, { - label: trans.__('Move selected layers to new group from file name'), - isEnabled: () => true, - describedBy: { - args: { - type: 'object', - required: ['filePath', 'groupName', 'layerIds'], - properties: { - filePath: { - type: 'string', - description: 'The path to the .jGIS file to be modified', - }, - groupName: { - type: 'string', - description: 'The name of the new layer group to create', - }, - layerIds: { - type: 'array', - description: 'Array of layer IDs to move to the new group', - items: { type: 'string' }, - }, - }, - }, - }, - execute: ((args: { - filePath: string; - groupName: string; - layerIds: string[]; - }) => { - const { filePath, groupName, layerIds } = args; - const current = tracker.find(w => w.model.filePath === filePath); - if (!current || !current.model.sharedModel.editable) { - return; - } - - const layerMap: { [key: string]: any } = {}; - layerIds.forEach(id => { - layerMap[id] = { type: 'layer', selectedNodeId: id }; - }); - - const newGroup: IJGISLayerGroup = { - name: groupName, - layers: layerIds, - }; - - current.model.addNewLayerGroup(layerMap, newGroup); - }) as any, - }); - commands.addCommand(DocumentActionCommandIDs.renameSourceWithParams, { label: trans.__('Rename source from file name'), isEnabled: () => true, diff --git a/packages/base/src/commands/index.ts b/packages/base/src/commands/index.ts index 0903fd4f6..3156fc13d 100644 --- a/packages/base/src/commands/index.ts +++ b/packages/base/src/commands/index.ts @@ -907,13 +907,50 @@ export function addCommands( describedBy: { args: { type: 'object', - properties: {}, - }, + properties: { + filePath: { type: 'string' }, + groupName: { type: 'string' }, + layerIds: { + type: 'array', + items: { type: 'string' } + } + } + } }, - execute: async () => { - const model = tracker.currentWidget?.model; - const selectedLayers = model?.localState?.selected?.value; + execute: async (args?: { + filePath?: string; + groupName?: string; + layerIds?: string[]; + }) => { + const { filePath, groupName, layerIds } = args ?? {}; + + const model = filePath + ? tracker.find(w => w.model.filePath === filePath)?.model + : tracker.currentWidget?.model; + + if (!model || !model.sharedModel.editable) { + return; + } + + // ---- PARAMETER MODE ---- + if (filePath && groupName && layerIds) { + const layerMap: { [key: string]: any } = {}; + layerIds.forEach(id => { + layerMap[id] = { type: 'layer', selectedNodeId: id }; + }); + + const newGroup: IJGISLayerGroup = { + name: groupName, + layers: layerIds + }; + + model.addNewLayerGroup(layerMap, newGroup); + return; + } + + // ---- FALLBACK TO ORIGINAL INTERACTIVE BEHAVIOR ---- + const selectedLayers = model.localState?.selected?.value; if (!selectedLayers) { return; } From 4d45e49bed08f03b4af0d6c2dbca3de48ab8e8e9 Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Tue, 18 Nov 2025 17:46:57 +0530 Subject: [PATCH 49/54] rename source --- packages/base/src/commands/index.ts | 40 ++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/packages/base/src/commands/index.ts b/packages/base/src/commands/index.ts index 3156fc13d..a11b234c2 100644 --- a/packages/base/src/commands/index.ts +++ b/packages/base/src/commands/index.ts @@ -1016,11 +1016,43 @@ export function addCommands( describedBy: { args: { type: 'object', - properties: {}, - }, + properties: { + filePath: { type: 'string' }, + sourceId: { type: 'string' }, + newName: { type: 'string' } + } + } }, - execute: async () => { - const model = tracker.currentWidget?.model; + + execute: async (args?: { + filePath?: string; + sourceId?: string; + newName?: string; + }) => { + const { filePath, sourceId, newName } = args ?? {}; + + const model = filePath + ? tracker.find(w => w.model.filePath === filePath)?.model + : tracker.currentWidget?.model; + + if (!model || !model.sharedModel.editable) { + return; + } + + // ---- PARAMETER MODE ---- + if (filePath && sourceId && newName) { + const source = model.getSource(sourceId); + if (!source) { + console.warn(`Source with ID ${sourceId} not found`); + return; + } + + source.name = newName; + model.sharedModel.updateSource(sourceId, source); + return; + } + + // ---- FALLBACK TO ORIGINAL INTERACTIVE BEHAVIOR ---- await Private.renameSelectedItem(model, 'source'); }, }); From 2c1c0108aeeb5feef5cfd3b1eb9328f507abb31c Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Tue, 18 Nov 2025 17:52:04 +0530 Subject: [PATCH 50/54] remove source --- .../src/commands/documentActionCommands.ts | 89 ------------------- packages/base/src/commands/index.ts | 37 +++++++- 2 files changed, 33 insertions(+), 93 deletions(-) diff --git a/packages/base/src/commands/documentActionCommands.ts b/packages/base/src/commands/documentActionCommands.ts index 95440cbd3..a3da09bee 100644 --- a/packages/base/src/commands/documentActionCommands.ts +++ b/packages/base/src/commands/documentActionCommands.ts @@ -1,5 +1,4 @@ import { JgisCoordinates } from '@jupytergis/schema'; -import { showErrorMessage } from '@jupyterlab/apputils'; import { IRenderMime } from '@jupyterlab/rendermime'; import { CommandRegistry } from '@lumino/commands'; import { Coordinate } from 'ol/coordinate'; @@ -9,8 +8,6 @@ import { downloadFile, getGeoJSONDataFromLayerSource } from '../tools'; import { JupyterGISTracker } from '../types'; export namespace DocumentActionCommandIDs { - export const renameSourceWithParams = 'jupytergis:renameSourceWithParams'; - export const removeSourceWithParams = 'jupytergis:removeSourceWithParams'; export const zoomToLayerWithParams = 'jupytergis:zoomToLayerWithParams'; export const downloadGeoJSONWithParams = 'jupytergis:downloadGeoJSONWithParams'; @@ -24,92 +21,6 @@ export function addDocumentActionCommands(options: { }) { const { commands, tracker, trans } = options; - commands.addCommand(DocumentActionCommandIDs.renameSourceWithParams, { - label: trans.__('Rename source from file name'), - isEnabled: () => true, - describedBy: { - args: { - type: 'object', - required: ['filePath', 'sourceId', 'newName'], - properties: { - filePath: { - type: 'string', - description: 'Path to the .jGIS file to be modified', - }, - sourceId: { - type: 'string', - description: 'The ID of the source to rename', - }, - newName: { - type: 'string', - description: 'The new name for the source', - }, - }, - }, - }, - execute: (async (args: { - filePath: string; - sourceId: string; - newName: string; - }) => { - const { filePath, sourceId, newName } = args; - const current = tracker.find(w => w.model.filePath === filePath); - - if (!current || !current.model.sharedModel.editable) { - return; - } - - const source = current.model.getSource(sourceId); - if (!source) { - console.warn(`Source with ID ${sourceId} not found`); - return; - } - - source.name = newName; - current.model.sharedModel.updateSource(sourceId, source); - }) as any, - }); - - commands.addCommand(DocumentActionCommandIDs.removeSourceWithParams, { - label: trans.__('Remove source from file name'), - isEnabled: () => true, - describedBy: { - args: { - type: 'object', - required: ['filePath', 'sourceId'], - properties: { - filePath: { - type: 'string', - description: 'Path to the .jGIS file to be modified', - }, - sourceId: { - type: 'string', - description: 'The ID of the source to remove', - }, - }, - }, - }, - execute: ((args: { filePath: string; sourceId: string }) => { - const { filePath, sourceId } = args; - const current = tracker.find(w => w.model.filePath === filePath); - - if (!current || !current.model.sharedModel.editable) { - return; - } - - const layersUsingSource = current.model.getLayersBySource(sourceId); - if (layersUsingSource.length > 0) { - showErrorMessage( - 'Remove source error', - 'The source is used by a layer.', - ); - return; - } - - current.model.sharedModel.removeSource(sourceId); - }) as any, - }); - commands.addCommand(DocumentActionCommandIDs.zoomToLayerWithParams, { label: trans.__('Zoom to layer from file name'), isEnabled: () => true, diff --git a/packages/base/src/commands/index.ts b/packages/base/src/commands/index.ts index a11b234c2..e7ed53831 100644 --- a/packages/base/src/commands/index.ts +++ b/packages/base/src/commands/index.ts @@ -1062,11 +1062,40 @@ export function addCommands( describedBy: { args: { type: 'object', - properties: {}, - }, + properties: { + filePath: { type: 'string' }, + sourceId: { type: 'string' } + } + } }, - execute: () => { - const model = tracker.currentWidget?.model; + + execute: (args?: { filePath?: string; sourceId?: string }) => { + const { filePath, sourceId } = args ?? {}; + + const model = filePath + ? tracker.find(w => w.model.filePath === filePath)?.model + : tracker.currentWidget?.model; + + if (!model || !model.sharedModel.editable) { + return; + } + + // ---- PARAMETER MODE ---- + if (filePath && sourceId) { + const layersUsingSource = model.getLayersBySource(sourceId); + if (layersUsingSource.length > 0) { + showErrorMessage( + 'Remove source error', + 'The source is used by a layer.' + ); + return; + } + + model.sharedModel.removeSource(sourceId); + return; + } + + // ---- FALLBACK TO ORIGINAL INTERACTIVE BEHAVIOR ---- Private.removeSelectedItems(model, 'source', selection => { if (!(model?.getLayersBySource(selection).length ?? true)) { model?.sharedModel.removeSource(selection); From 47ddbde4da9695755d539bd41eae8d1ad55d40eb Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Tue, 18 Nov 2025 18:03:20 +0530 Subject: [PATCH 51/54] zoom to layer --- .../src/commands/documentActionCommands.ts | 33 ---------------- packages/base/src/commands/index.ts | 39 ++++++++++++++----- 2 files changed, 29 insertions(+), 43 deletions(-) diff --git a/packages/base/src/commands/documentActionCommands.ts b/packages/base/src/commands/documentActionCommands.ts index a3da09bee..05e294942 100644 --- a/packages/base/src/commands/documentActionCommands.ts +++ b/packages/base/src/commands/documentActionCommands.ts @@ -8,7 +8,6 @@ import { downloadFile, getGeoJSONDataFromLayerSource } from '../tools'; import { JupyterGISTracker } from '../types'; export namespace DocumentActionCommandIDs { - export const zoomToLayerWithParams = 'jupytergis:zoomToLayerWithParams'; export const downloadGeoJSONWithParams = 'jupytergis:downloadGeoJSONWithParams'; export const getGeolocationWithParams = 'jupytergis:getGeolocationWithParams'; @@ -21,38 +20,6 @@ export function addDocumentActionCommands(options: { }) { const { commands, tracker, trans } = options; - commands.addCommand(DocumentActionCommandIDs.zoomToLayerWithParams, { - label: trans.__('Zoom to layer from file name'), - isEnabled: () => true, - describedBy: { - args: { - type: 'object', - required: ['filePath', 'layerId'], - properties: { - filePath: { - type: 'string', - description: 'Path to the .jGIS file containing the layer', - }, - layerId: { - type: 'string', - description: 'The ID of the layer to zoom to', - }, - }, - }, - }, - execute: ((args: { filePath: string; layerId: string }) => { - const { filePath, layerId } = args; - const current = tracker.find(w => w.model.filePath === filePath); - - if (!current || !current.model.sharedModel.editable) { - return; - } - - console.log(`Zooming to layer: ${layerId}`); - current.model.centerOnPosition(layerId); - }) as any, - }); - commands.addCommand(DocumentActionCommandIDs.downloadGeoJSONWithParams, { label: trans.__('Download layer as GeoJSON'), isEnabled: () => true, diff --git a/packages/base/src/commands/index.ts b/packages/base/src/commands/index.ts index e7ed53831..76dfe5d2c 100644 --- a/packages/base/src/commands/index.ts +++ b/packages/base/src/commands/index.ts @@ -1226,24 +1226,43 @@ export function addCommands( describedBy: { args: { type: 'object', - properties: {}, - }, + properties: { + filePath: { type: 'string' }, + layerId: { type: 'string' } + } + } }, - execute: () => { - const currentWidget = tracker.currentWidget; - if (!currentWidget || !completionProviderManager) { + + execute: (args?: { filePath?: string; layerId?: string }) => { + const { filePath, layerId } = args ?? {}; + + // Determine model from provided file path or fallback to current widget + const current = filePath + ? tracker.find(w => w.model.filePath === filePath) + : tracker.currentWidget; + + if (!current || !current.model.sharedModel.editable) { return; } - console.log('zooming'); - const model = tracker.currentWidget.model; - const selectedItems = model.localState?.selected.value; + const model = current.model; + + // ----- PARAMETER MODE ----- + if (filePath && layerId) { + console.log(`Zooming to layer: ${layerId}`); + model.centerOnPosition(layerId); + return; + } + + // ---- FALLBACK TO ORIGINAL INTERACTIVE BEHAVIOR ---- + const selectedItems = model.localState?.selected?.value; if (!selectedItems) { return; } - const layerId = Object.keys(selectedItems)[0]; - model.centerOnPosition(layerId); + const selLayerId = Object.keys(selectedItems)[0]; + console.log('zooming'); + model.centerOnPosition(selLayerId); }, }); From 800118167e24ca1b033c8c1b5074d1c5bec5b547 Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Tue, 18 Nov 2025 18:08:35 +0530 Subject: [PATCH 52/54] download as geojson --- .../src/commands/documentActionCommands.ts | 69 ------------------- packages/base/src/commands/index.ts | 63 +++++++++++++++-- 2 files changed, 57 insertions(+), 75 deletions(-) diff --git a/packages/base/src/commands/documentActionCommands.ts b/packages/base/src/commands/documentActionCommands.ts index 05e294942..059b32926 100644 --- a/packages/base/src/commands/documentActionCommands.ts +++ b/packages/base/src/commands/documentActionCommands.ts @@ -4,12 +4,9 @@ import { CommandRegistry } from '@lumino/commands'; import { Coordinate } from 'ol/coordinate'; import { fromLonLat } from 'ol/proj'; -import { downloadFile, getGeoJSONDataFromLayerSource } from '../tools'; import { JupyterGISTracker } from '../types'; export namespace DocumentActionCommandIDs { - export const downloadGeoJSONWithParams = - 'jupytergis:downloadGeoJSONWithParams'; export const getGeolocationWithParams = 'jupytergis:getGeolocationWithParams'; } @@ -20,72 +17,6 @@ export function addDocumentActionCommands(options: { }) { const { commands, tracker, trans } = options; - commands.addCommand(DocumentActionCommandIDs.downloadGeoJSONWithParams, { - label: trans.__('Download layer as GeoJSON'), - isEnabled: () => true, - describedBy: { - args: { - type: 'object', - required: ['filePath', 'layerId', 'exportFileName'], - properties: { - filePath: { - type: 'string', - description: 'Path to the .jGIS file containing the layer', - }, - layerId: { - type: 'string', - description: 'The ID of the layer to export', - }, - exportFileName: { - type: 'string', - description: 'The desired name of the exported GeoJSON file', - }, - }, - }, - }, - execute: (async (args: { - filePath: string; - layerId: string; - exportFileName: string; - }) => { - const { filePath, layerId, exportFileName } = args; - const current = tracker.find(w => w.model.filePath === filePath); - - if (!current || !current.model.sharedModel.editable) { - console.warn('Invalid or non-editable document'); - return; - } - - const model = current.model; - const layer = model.getLayer(layerId); - - if (!layer || !['VectorLayer', 'ShapefileLayer'].includes(layer.type)) { - console.warn('Layer type not supported for GeoJSON export'); - return; - } - - const sources = model.sharedModel.sources ?? {}; - const sourceId = layer.parameters?.source; - const source = sources[sourceId]; - if (!source) { - console.warn('Source not found for selected layer'); - return; - } - - const geojsonString = await getGeoJSONDataFromLayerSource(source, model); - if (!geojsonString) { - console.warn('Failed to generate GeoJSON data'); - return; - } - - downloadFile( - geojsonString, - `${exportFileName}.geojson`, - 'application/geo+json', - ); - }) as any, - }); - commands.addCommand(DocumentActionCommandIDs.getGeolocationWithParams, { label: trans.__('Center on Geolocation'), isEnabled: () => true, diff --git a/packages/base/src/commands/index.ts b/packages/base/src/commands/index.ts index 76dfe5d2c..94db826f8 100644 --- a/packages/base/src/commands/index.ts +++ b/packages/base/src/commands/index.ts @@ -1254,7 +1254,7 @@ export function addCommands( return; } - // ---- FALLBACK TO ORIGINAL INTERACTIVE BEHAVIOR ---- + // ----- FALLBACK TO ORIGINAL INTERACTIVE BEHAVIOR ----- const selectedItems = model.localState?.selected?.value; if (!selectedItems) { return; @@ -1271,8 +1271,12 @@ export function addCommands( describedBy: { args: { type: 'object', - properties: {}, - }, + properties: { + filePath: { type: 'string' }, + layerId: { type: 'string' }, + exportFileName: { type: 'string' } + } + } }, isEnabled: () => { const selectedLayer = getSingleSelectedLayer(tracker); @@ -1280,7 +1284,54 @@ export function addCommands( ? ['VectorLayer', 'ShapefileLayer'].includes(selectedLayer.type) : false; }, - execute: async () => { + + execute: async (args?: { + filePath?: string; + layerId?: string; + exportFileName?: string; + }) => { + const { filePath, layerId, exportFileName } = args ?? {}; + + // ----- PARAMETER MODE ----- + if (filePath && layerId && exportFileName) { + const current = tracker.find(w => w.model.filePath === filePath); + + if (!current || !current.model.sharedModel.editable) { + console.warn('Invalid or non-editable document'); + return; + } + + const model = current.model; + const layer = model.getLayer(layerId); + + if (!layer || !['VectorLayer', 'ShapefileLayer'].includes(layer.type)) { + console.warn('Layer type not supported for GeoJSON export'); + return; + } + + const sources = model.sharedModel.sources ?? {}; + const sourceId = layer.parameters?.source; + const source = sources[sourceId]; + if (!source) { + console.warn('Source not found for selected layer'); + return; + } + + const geojsonString = await getGeoJSONDataFromLayerSource(source, model); + if (!geojsonString) { + console.warn('Failed to generate GeoJSON data'); + return; + } + + downloadFile( + geojsonString, + `${exportFileName}.geojson`, + 'application/geo+json' + ); + return; + } + + // ----- FALLBACK TO ORIGINAL INTERACTIVE BEHAVIOR ----- const selectedLayer = getSingleSelectedLayer(tracker); if (!selectedLayer) { return; @@ -1315,7 +1366,7 @@ export function addCommands( return; } - const exportFileName = formValues.exportFileName; + const outName = formValues.exportFileName; const sourceId = selectedLayer.parameters.source; const source = sources[sourceId]; @@ -1326,7 +1377,7 @@ export function addCommands( downloadFile( geojsonString, - `${exportFileName}.geojson`, + `${outName}.geojson`, 'application/geo+json', ); }, From 98cc5c2918a8aa2391bf0a6eb1294e14f44114b5 Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Tue, 18 Nov 2025 18:14:28 +0530 Subject: [PATCH 53/54] geolocation --- .../src/commands/documentActionCommands.ts | 72 ------------------- packages/base/src/commands/index.ts | 51 +++++++++++-- 2 files changed, 46 insertions(+), 77 deletions(-) delete mode 100644 packages/base/src/commands/documentActionCommands.ts diff --git a/packages/base/src/commands/documentActionCommands.ts b/packages/base/src/commands/documentActionCommands.ts deleted file mode 100644 index 059b32926..000000000 --- a/packages/base/src/commands/documentActionCommands.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { JgisCoordinates } from '@jupytergis/schema'; -import { IRenderMime } from '@jupyterlab/rendermime'; -import { CommandRegistry } from '@lumino/commands'; -import { Coordinate } from 'ol/coordinate'; -import { fromLonLat } from 'ol/proj'; - -import { JupyterGISTracker } from '../types'; - -export namespace DocumentActionCommandIDs { - export const getGeolocationWithParams = 'jupytergis:getGeolocationWithParams'; -} - -export function addDocumentActionCommands(options: { - tracker: JupyterGISTracker; - commands: CommandRegistry; - trans: IRenderMime.TranslationBundle; -}) { - const { commands, tracker, trans } = options; - - commands.addCommand(DocumentActionCommandIDs.getGeolocationWithParams, { - label: trans.__('Center on Geolocation'), - isEnabled: () => true, - describedBy: { - args: { - type: 'object', - required: ['filePath'], - properties: { - filePath: { - type: 'string', - description: - 'Path to the .jGIS document to center on the user’s geolocation', - }, - }, - }, - }, - execute: (async (args: { filePath: string }) => { - const { filePath } = args; - const current = tracker.find(w => w.model.filePath === filePath); - if (!current) { - console.warn('No document found for provided filePath'); - return; - } - - const viewModel = current.model; - const options = { - enableHighAccuracy: true, - timeout: 5000, - maximumAge: 0, - }; - - const success = (pos: GeolocationPosition) => { - const location: Coordinate = fromLonLat([ - pos.coords.longitude, - pos.coords.latitude, - ]); - - const jgisLocation: JgisCoordinates = { - x: location[0], - y: location[1], - }; - - viewModel.geolocationChanged.emit(jgisLocation); - }; - - const error = (err: GeolocationPositionError) => { - console.warn(`Geolocation error (${err.code}): ${err.message}`); - }; - - navigator.geolocation.getCurrentPosition(success, error, options); - }) as any, - }); -} diff --git a/packages/base/src/commands/index.ts b/packages/base/src/commands/index.ts index 94db826f8..fc8a3d93d 100644 --- a/packages/base/src/commands/index.ts +++ b/packages/base/src/commands/index.ts @@ -32,7 +32,6 @@ import { addProcessingCommands } from '../processing/processingCommands'; import { getGeoJSONDataFromLayerSource, downloadFile } from '../tools'; import { JupyterGISTracker } from '../types'; import { JupyterGISDocumentWidget } from '../widget'; -import { addDocumentActionCommands } from './documentActionCommands'; import { addLayerCreationCommands } from './operationCommands'; import { addProcessingCommandsFromParams } from './processingCommandsFromParams'; @@ -76,7 +75,6 @@ export function addCommands( const { commands } = app; addLayerCreationCommands({ tracker, commands, trans }); - addDocumentActionCommands({ tracker, commands, trans }); commands.addCommand(CommandIDs.symbology, { label: trans.__('Edit Symbology'), @@ -1388,10 +1386,53 @@ export function addCommands( describedBy: { args: { type: 'object', - properties: {}, - }, + properties: { + filePath: { type: 'string' } + } + } }, - execute: async () => { + + execute: async (args?: { filePath?: string }) => { + const { filePath } = args ?? {}; + + // ----- PARAMETER MODE ----- + if (filePath) { + const current = tracker.find(w => w.model.filePath === filePath); + if (!current) { + console.warn('No document found for provided filePath'); + return; + } + + const viewModel = current.model; + const options = { + enableHighAccuracy: true, + timeout: 5000, + maximumAge: 0 + }; + + const success = (pos: GeolocationPosition) => { + const location: Coordinate = fromLonLat([ + pos.coords.longitude, + pos.coords.latitude + ]); + + const jgisLocation: JgisCoordinates = { + x: location[0], + y: location[1] + }; + + viewModel.geolocationChanged.emit(jgisLocation); + }; + + const error = (err: GeolocationPositionError) => { + console.warn(`Geolocation error (${err.code}): ${err.message}`); + }; + + navigator.geolocation.getCurrentPosition(success, error, options); + return; + } + + // ----- FALLBACK TO ORIGINAL INTERACTIVE BEHAVIOR ----- const viewModel = tracker.currentWidget?.model; const options = { enableHighAccuracy: true, From 79c789ed3d1c07ea7c6d1874a8c0696f615e3e86 Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Tue, 18 Nov 2025 18:14:44 +0530 Subject: [PATCH 54/54] lint --- packages/base/src/commands/index.ts | 75 ++++++++++++++--------------- 1 file changed, 37 insertions(+), 38 deletions(-) diff --git a/packages/base/src/commands/index.ts b/packages/base/src/commands/index.ts index fc8a3d93d..de2094cd3 100644 --- a/packages/base/src/commands/index.ts +++ b/packages/base/src/commands/index.ts @@ -814,14 +814,14 @@ export function addCommands( properties: { filePath: { type: 'string', - description: 'Optional .jGIS file path' + description: 'Optional .jGIS file path', }, groupName: { type: 'string', - description: 'Optional group name to remove' - } - } - } + description: 'Optional group name to remove', + }, + }, + }, }, execute: async (args?: { filePath?: string; groupName?: string }) => { @@ -859,9 +859,9 @@ export function addCommands( filePath: { type: 'string' }, layerIds: { type: 'array', - items: { type: 'string' } + items: { type: 'string' }, }, - groupName: { type: 'string' } + groupName: { type: 'string' }, }, }, }, @@ -910,10 +910,10 @@ export function addCommands( groupName: { type: 'string' }, layerIds: { type: 'array', - items: { type: 'string' } - } - } - } + items: { type: 'string' }, + }, + }, + }, }, execute: async (args?: { @@ -940,7 +940,7 @@ export function addCommands( const newGroup: IJGISLayerGroup = { name: groupName, - layers: layerIds + layers: layerIds, }; model.addNewLayerGroup(layerMap, newGroup); @@ -1017,9 +1017,9 @@ export function addCommands( properties: { filePath: { type: 'string' }, sourceId: { type: 'string' }, - newName: { type: 'string' } - } - } + newName: { type: 'string' }, + }, + }, }, execute: async (args?: { @@ -1062,9 +1062,9 @@ export function addCommands( type: 'object', properties: { filePath: { type: 'string' }, - sourceId: { type: 'string' } - } - } + sourceId: { type: 'string' }, + }, + }, }, execute: (args?: { filePath?: string; sourceId?: string }) => { @@ -1084,7 +1084,7 @@ export function addCommands( if (layersUsingSource.length > 0) { showErrorMessage( 'Remove source error', - 'The source is used by a layer.' + 'The source is used by a layer.', ); return; } @@ -1226,9 +1226,9 @@ export function addCommands( type: 'object', properties: { filePath: { type: 'string' }, - layerId: { type: 'string' } - } - } + layerId: { type: 'string' }, + }, + }, }, execute: (args?: { filePath?: string; layerId?: string }) => { @@ -1272,9 +1272,9 @@ export function addCommands( properties: { filePath: { type: 'string' }, layerId: { type: 'string' }, - exportFileName: { type: 'string' } - } - } + exportFileName: { type: 'string' }, + }, + }, }, isEnabled: () => { const selectedLayer = getSingleSelectedLayer(tracker); @@ -1315,7 +1315,10 @@ export function addCommands( return; } - const geojsonString = await getGeoJSONDataFromLayerSource(source, model); + const geojsonString = await getGeoJSONDataFromLayerSource( + source, + model, + ); if (!geojsonString) { console.warn('Failed to generate GeoJSON data'); return; @@ -1324,7 +1327,7 @@ export function addCommands( downloadFile( geojsonString, `${exportFileName}.geojson`, - 'application/geo+json' + 'application/geo+json', ); return; } @@ -1373,11 +1376,7 @@ export function addCommands( return; } - downloadFile( - geojsonString, - `${outName}.geojson`, - 'application/geo+json', - ); + downloadFile(geojsonString, `${outName}.geojson`, 'application/geo+json'); }, }); @@ -1387,9 +1386,9 @@ export function addCommands( args: { type: 'object', properties: { - filePath: { type: 'string' } - } - } + filePath: { type: 'string' }, + }, + }, }, execute: async (args?: { filePath?: string }) => { @@ -1407,18 +1406,18 @@ export function addCommands( const options = { enableHighAccuracy: true, timeout: 5000, - maximumAge: 0 + maximumAge: 0, }; const success = (pos: GeolocationPosition) => { const location: Coordinate = fromLonLat([ pos.coords.longitude, - pos.coords.latitude + pos.coords.latitude, ]); const jgisLocation: JgisCoordinates = { x: location[0], - y: location[1] + y: location[1], }; viewModel.geolocationChanged.emit(jgisLocation);