From a4c16dfd106a03e604e01e8ef3cac8524d529a62 Mon Sep 17 00:00:00 2001 From: Nakul Date: Mon, 16 Feb 2026 11:49:35 +0530 Subject: [PATCH 1/4] Focusing layer everytime to rename --- packages/base/src/keybindings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/base/src/keybindings.json b/packages/base/src/keybindings.json index d0f7cdf3f..bcac7c6db 100644 --- a/packages/base/src/keybindings.json +++ b/packages/base/src/keybindings.json @@ -37,7 +37,7 @@ { "command": "jupytergis:renameLayer", "keys": ["F2"], - "selector": ".jp-gis-layerItem" + "selector": ".data-jgis-keybinding" }, { "command": "jupytergis:removeGroup", From 3bd93a9fb0fa211167b2aa69ddf65b961a719210 Mon Sep 17 00:00:00 2001 From: Nakul Date: Mon, 16 Feb 2026 21:11:16 +0530 Subject: [PATCH 2/4] renaming only on selected item --- packages/base/src/commands/BaseCommandIDs.ts | 3 +- packages/base/src/commands/index.ts | 51 ++++++++++---------- packages/base/src/keybindings.json | 7 +-- python/jupytergis_lab/src/index.ts | 4 +- 4 files changed, 29 insertions(+), 36 deletions(-) diff --git a/packages/base/src/commands/BaseCommandIDs.ts b/packages/base/src/commands/BaseCommandIDs.ts index 6fbc82318..d055ec8df 100644 --- a/packages/base/src/commands/BaseCommandIDs.ts +++ b/packages/base/src/commands/BaseCommandIDs.ts @@ -29,9 +29,8 @@ export const newGeoTiffEntry = 'jupytergis:newGeoTiffEntry'; export const newGeoParquetEntry = 'jupytergis:newGeoParquetEntry'; // Layer and group actions -export const renameLayer = 'jupytergis:renameLayer'; +export const renameSelected = 'jupytergis:renameSelected'; export const removeLayer = 'jupytergis:removeLayer'; -export const renameGroup = 'jupytergis:renameGroup'; export const removeGroup = 'jupytergis:removeGroup'; export const moveLayersToGroup = 'jupytergis:moveLayersToGroup'; export const moveLayerToNewGroup = 'jupytergis:moveLayerToNewGroup'; diff --git a/packages/base/src/commands/index.ts b/packages/base/src/commands/index.ts index ad4690e72..1ef4819ba 100644 --- a/packages/base/src/commands/index.ts +++ b/packages/base/src/commands/index.ts @@ -534,11 +534,22 @@ export function addCommands( /** * LAYERS and LAYER GROUP actions. */ - commands.addCommand(CommandIDs.renameLayer, { - label: trans.__('Rename Layer'), + commands.addCommand(CommandIDs.renameSelected, { + label: trans.__('Rename Item'), + isEnabled: () => { + const model = tracker.currentWidget?.model; + const selected = model?.localState?.selected?.value; + return !!selected && Object.keys(selected).length === 1; + }, execute: async () => { const model = tracker.currentWidget?.model; - await Private.renameSelectedItem(model, 'layer'); + const selected = model?.localState?.selected?.value; + + if (!model || !selected) { + return; + } + + await Private.renameSelectedItem(model); }, }); @@ -554,14 +565,6 @@ export function addCommands( }, }); - commands.addCommand(CommandIDs.renameGroup, { - label: trans.__('Rename Group'), - execute: async () => { - const model = tracker.currentWidget?.model; - await Private.renameSelectedItem(model, 'group'); - }, - }); - commands.addCommand(CommandIDs.removeGroup, { label: trans.__('Remove Group'), execute: async () => { @@ -659,7 +662,7 @@ export function addCommands( label: trans.__('Rename Source'), execute: async () => { const model = tracker.currentWidget?.model; - await Private.renameSelectedItem(model, 'source'); + await Private.renameSelectedItem(model); }, }); @@ -1251,31 +1254,27 @@ namespace Private { export async function renameSelectedItem( model: IJupyterGISModel | undefined, - itemType: SelectionType, ) { - const selectedItems = model?.localState?.selected.value; + const selectedItems = model?.localState?.selected?.value; if (!selectedItems || !model) { - console.error(`No ${itemType} selected`); + console.error('No item selected'); return; } - let itemId = ''; - - // If more then one item is selected, only rename the first - for (const id in selectedItems) { - if (selectedItems[id].type === itemType) { - itemId = id; - break; - } + const ids = Object.keys(selectedItems); + if (ids.length === 0) { + return; } - if (!itemId) { + const itemId = ids[0]; + const item = selectedItems[itemId]; + + if (!item.type) { return; } - // Set editing state - component will show inline input - model.setEditingItem(itemType, itemId); + model.setEditingItem(item.type, itemId); } export function executeConsole(tracker: JupyterGISTracker): void { diff --git a/packages/base/src/keybindings.json b/packages/base/src/keybindings.json index bcac7c6db..0b33bffab 100644 --- a/packages/base/src/keybindings.json +++ b/packages/base/src/keybindings.json @@ -35,7 +35,7 @@ "selector": ".data-jgis-keybinding .jp-gis-layerItem" }, { - "command": "jupytergis:renameLayer", + "command": "jupytergis:renameSelected", "keys": ["F2"], "selector": ".data-jgis-keybinding" }, @@ -44,11 +44,6 @@ "keys": ["Delete"], "selector": ".data-jgis-keybinding .jp-gis-layerGroupHeader" }, - { - "command": "jupytergis:renameGroup", - "keys": ["F2"], - "selector": ".jp-gis-layerGroupHeader" - }, { "command": "jupytergis:executeConsole", "keys": ["Shift Enter"], diff --git a/python/jupytergis_lab/src/index.ts b/python/jupytergis_lab/src/index.ts index 3b83782f8..4be37db11 100644 --- a/python/jupytergis_lab/src/index.ts +++ b/python/jupytergis_lab/src/index.ts @@ -105,7 +105,7 @@ const plugin: JupyterFrontEndPlugin = { }); app.contextMenu.addItem({ - command: CommandIDs.renameLayer, + command: CommandIDs.renameSelected, selector: '.jp-gis-layerItem', rank: 2, }); @@ -177,7 +177,7 @@ const plugin: JupyterFrontEndPlugin = { }); app.contextMenu.addItem({ - command: CommandIDs.renameGroup, + command: CommandIDs.renameSelected, selector: '.jp-gis-layerGroupHeader', rank: 2, }); From 83e97637e3b4ec1ef157e7211ca5bdd78bfb880d Mon Sep 17 00:00:00 2001 From: Nakul Date: Tue, 17 Feb 2026 14:53:03 +0530 Subject: [PATCH 3/4] refactor remove layer/group command --- packages/base/src/commands/BaseCommandIDs.ts | 3 +- packages/base/src/commands/index.ts | 80 +++++++++++--------- packages/base/src/keybindings.json | 9 +-- python/jupytergis_lab/src/index.ts | 4 +- 4 files changed, 50 insertions(+), 46 deletions(-) diff --git a/packages/base/src/commands/BaseCommandIDs.ts b/packages/base/src/commands/BaseCommandIDs.ts index d055ec8df..b958bebe2 100644 --- a/packages/base/src/commands/BaseCommandIDs.ts +++ b/packages/base/src/commands/BaseCommandIDs.ts @@ -30,8 +30,7 @@ export const newGeoParquetEntry = 'jupytergis:newGeoParquetEntry'; // Layer and group actions export const renameSelected = 'jupytergis:renameSelected'; -export const removeLayer = 'jupytergis:removeLayer'; -export const removeGroup = 'jupytergis:removeGroup'; +export const removeSelected = 'jupytergis:removeSelected'; export const moveLayersToGroup = 'jupytergis:moveLayersToGroup'; export const moveLayerToNewGroup = 'jupytergis:moveLayerToNewGroup'; diff --git a/packages/base/src/commands/index.ts b/packages/base/src/commands/index.ts index 1ef4819ba..39de15f51 100644 --- a/packages/base/src/commands/index.ts +++ b/packages/base/src/commands/index.ts @@ -7,7 +7,6 @@ import { IJupyterGISModel, JgisCoordinates, LayerType, - SelectionType, SourceType, } from '@jupytergis/schema'; import { JupyterFrontEnd } from '@jupyterlab/application'; @@ -553,25 +552,22 @@ export function addCommands( }, }); - commands.addCommand(CommandIDs.removeLayer, { - label: trans.__('Remove Layer'), - execute: () => { + commands.addCommand(CommandIDs.removeSelected, { + label: trans.__('Remove Item'), + isEnabled: () => { const model = tracker.currentWidget?.model; - Private.removeSelectedItems(model, 'layer', selection => { - model?.removeLayer(selection); - }); - - commands.notifyCommandChanged(CommandIDs.toggleStoryPresentationMode); + const selected = model?.localState?.selected?.value; + return !!selected && Object.keys(selected).length > 0; }, - }); - - commands.addCommand(CommandIDs.removeGroup, { - label: trans.__('Remove Group'), execute: async () => { const model = tracker.currentWidget?.model; - Private.removeSelectedItems(model, 'group', selection => { - model?.removeLayerGroup(selection); - }); + const selected = model?.localState?.selected?.value; + + if (!model || !selected) { + return; + } + + await Private.removeSelectedItems(model); }, }); @@ -670,16 +666,7 @@ export function addCommands( label: trans.__('Remove Source'), execute: () => { const model = tracker.currentWidget?.model; - Private.removeSelectedItems(model, 'source', selection => { - if (!(model?.getLayersBySource(selection).length ?? true)) { - model?.sharedModel.removeSource(selection); - } else { - showErrorMessage( - 'Remove source error', - 'The source is used by a layer.', - ); - } - }); + Private.removeSelectedSources(model); }, }); @@ -1233,21 +1220,24 @@ namespace Private { }; } - export function removeSelectedItems( - model: IJupyterGISModel | undefined, - itemTypeToRemove: SelectionType, - removeFunction: (id: string) => void, - ) { + export function removeSelectedItems(model: IJupyterGISModel | undefined) { const selected = model?.localState?.selected?.value; - if (!selected) { + if (!selected || !model) { console.error('Failed to remove selected item -- nothing selected'); return; } - for (const selection in selected) { - if (selected[selection].type === itemTypeToRemove) { - removeFunction(selection); + for (const id of Object.keys(selected)) { + const item = selected[id]; + + switch (item.type) { + case 'layer': + model.removeLayer(id); + break; + case 'group': + model.removeLayerGroup(id); + break; } } } @@ -1277,6 +1267,26 @@ namespace Private { model.setEditingItem(item.type, itemId); } + export function removeSelectedSources(model: IJupyterGISModel | undefined) { + const selected = model?.localState?.selected?.value; + + if (!selected || !model) { + return; + } + + for (const id of Object.keys(selected)) { + if (model.getLayersBySource(id).length > 0) { + showErrorMessage( + 'Remove source error', + 'The source is used by a layer.', + ); + continue; + } + + model.sharedModel.removeSource(id); + } + } + export function executeConsole(tracker: JupyterGISTracker): void { const current = tracker.currentWidget; if (!current || !(current instanceof JupyterGISDocumentWidget)) { diff --git a/packages/base/src/keybindings.json b/packages/base/src/keybindings.json index 0b33bffab..4ea3eafba 100644 --- a/packages/base/src/keybindings.json +++ b/packages/base/src/keybindings.json @@ -30,20 +30,15 @@ "selector": ".data-jgis-keybinding .jp-gis-source" }, { - "command": "jupytergis:removeLayer", + "command": "jupytergis:removeSelected", "keys": ["Delete"], - "selector": ".data-jgis-keybinding .jp-gis-layerItem" + "selector": ".data-jgis-keybinding" }, { "command": "jupytergis:renameSelected", "keys": ["F2"], "selector": ".data-jgis-keybinding" }, - { - "command": "jupytergis:removeGroup", - "keys": ["Delete"], - "selector": ".data-jgis-keybinding .jp-gis-layerGroupHeader" - }, { "command": "jupytergis:executeConsole", "keys": ["Shift Enter"], diff --git a/python/jupytergis_lab/src/index.ts b/python/jupytergis_lab/src/index.ts index 4be37db11..56aa95a96 100644 --- a/python/jupytergis_lab/src/index.ts +++ b/python/jupytergis_lab/src/index.ts @@ -99,7 +99,7 @@ const plugin: JupyterFrontEndPlugin = { }); app.contextMenu.addItem({ - command: CommandIDs.removeLayer, + command: CommandIDs.removeSelected, selector: '.jp-gis-layerItem', rank: 2, }); @@ -171,7 +171,7 @@ const plugin: JupyterFrontEndPlugin = { ); app.contextMenu.addItem({ - command: CommandIDs.removeGroup, + command: CommandIDs.removeSelected, selector: '.jp-gis-layerGroupHeader', rank: 2, }); From 5541db1ee5f5a552bf9117b63242e62ac96e0157 Mon Sep 17 00:00:00 2001 From: Nakul Date: Wed, 18 Feb 2026 10:19:54 +0530 Subject: [PATCH 4/4] only remove/rename buttons for group --- packages/base/src/commands/index.ts | 4 ++-- python/jupytergis_lab/src/index.ts | 18 ++++++++++-------- ui-tests/tests/contextmenu.spec.ts | 12 ++++++------ 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/packages/base/src/commands/index.ts b/packages/base/src/commands/index.ts index 39de15f51..e2407b935 100644 --- a/packages/base/src/commands/index.ts +++ b/packages/base/src/commands/index.ts @@ -534,7 +534,7 @@ export function addCommands( * LAYERS and LAYER GROUP actions. */ commands.addCommand(CommandIDs.renameSelected, { - label: trans.__('Rename Item'), + label: trans.__('Rename'), isEnabled: () => { const model = tracker.currentWidget?.model; const selected = model?.localState?.selected?.value; @@ -553,7 +553,7 @@ export function addCommands( }); commands.addCommand(CommandIDs.removeSelected, { - label: trans.__('Remove Item'), + label: trans.__('Remove'), isEnabled: () => { const model = tracker.currentWidget?.model; const selected = model?.localState?.selected?.value; diff --git a/python/jupytergis_lab/src/index.ts b/python/jupytergis_lab/src/index.ts index 56aa95a96..82c7db63c 100644 --- a/python/jupytergis_lab/src/index.ts +++ b/python/jupytergis_lab/src/index.ts @@ -58,6 +58,8 @@ const plugin: JupyterFrontEndPlugin = { ); }; + const LAYER = '.jp-gis-layerItem:not(.jp-gis-layerGroup)'; + createDefaultLayerRegistry(layerBrowserRegistry); const stateDbManager = GlobalStateDbManager.getInstance(); stateDbManager.initialize(state); @@ -87,32 +89,32 @@ const plugin: JupyterFrontEndPlugin = { // LAYERS and LAYER GROUPS context menu app.contextMenu.addItem({ command: CommandIDs.symbology, - selector: '.jp-gis-layerItem', + selector: LAYER, rank: 1, }); // Separator app.contextMenu.addItem({ type: 'separator', - selector: '.jp-gis-layerPanel', + selector: LAYER, rank: 1, }); app.contextMenu.addItem({ command: CommandIDs.removeSelected, - selector: '.jp-gis-layerItem', + selector: LAYER, rank: 2, }); app.contextMenu.addItem({ command: CommandIDs.renameSelected, - selector: '.jp-gis-layerItem', + selector: LAYER, rank: 2, }); app.contextMenu.addItem({ command: CommandIDs.zoomToLayer, - selector: '.jp-gis-layerItem', + selector: LAYER, rank: 2, }); @@ -128,7 +130,7 @@ const plugin: JupyterFrontEndPlugin = { // Add the Download submenu to the context menu app.contextMenu.addItem({ type: 'submenu', - selector: '.jp-gis-layerItem', + selector: LAYER, rank: 2, submenu: downloadSubmenu, }); @@ -148,7 +150,7 @@ const plugin: JupyterFrontEndPlugin = { app.contextMenu.addItem({ type: 'submenu', - selector: '.jp-gis-layerItem', + selector: LAYER, rank: 2, submenu: processingSubmenu, }); @@ -161,7 +163,7 @@ const plugin: JupyterFrontEndPlugin = { app.contextMenu.addItem({ type: 'submenu', - selector: '.jp-gis-layerItem', + selector: LAYER, rank: 2, submenu: moveLayerSubmenu, }); diff --git a/ui-tests/tests/contextmenu.spec.ts b/ui-tests/tests/contextmenu.spec.ts index bf2c2b1c5..e4dfcdcca 100644 --- a/ui-tests/tests/contextmenu.spec.ts +++ b/ui-tests/tests/contextmenu.spec.ts @@ -24,14 +24,14 @@ test.describe('context menu', () => { .getByText('Open Topo Map') .click({ button: 'right' }); - const text = page.getByRole('menu').getByText('Remove Layer'); + const text = page.getByRole('menu').getByText('Remove'); await expect(text).toBeVisible(); }); test('right click on group should open group menu', async ({ page }) => { await page.getByText('level 1 group').click({ button: 'right' }); - const text = page.getByRole('menu').getByText('Remove Group'); + const text = page.getByRole('menu').getByText('Remove'); await expect(text).toBeVisible(); }); @@ -74,7 +74,7 @@ test.describe('context menu', () => { await expect(page.getByText('new group', { exact: true })).toHaveCount(1); }); - test('clicking remove layer should remove the layer from the tree', async ({ + test('clicking remove should remove the layer from the tree', async ({ page, }) => { // Create new layer first @@ -102,12 +102,12 @@ test.describe('context menu', () => { button: 'right', }); - await page.getByRole('menu').getByText('Remove Layer').click(); + await page.getByRole('menu').getByText('Remove').click(); expect(layerInTree).not.toBeVisible(); }); - test('clicking remove group should remove the group from the tree', async ({ + test('clicking remove should remove the group from the tree', async ({ page, }) => { const firstItem = page @@ -119,7 +119,7 @@ test.describe('context menu', () => { .getByText('level 1 group') .click({ button: 'right' }); - await page.getByRole('menu').getByText('Remove Group').click(); + await page.getByRole('menu').getByText('Remove').click(); await expect(firstItem).not.toBeVisible(); });