From eed16137880843e90eb5c737884b8381a7e9be07 Mon Sep 17 00:00:00 2001 From: Nakul Date: Thu, 9 Apr 2026 17:14:36 +0530 Subject: [PATCH 1/5] Auto zoom when adding a layer from the UI --- packages/base/src/mainview/mainView.tsx | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/packages/base/src/mainview/mainView.tsx b/packages/base/src/mainview/mainView.tsx index 64d4dc6a8..f96f0aeca 100644 --- a/packages/base/src/mainview/mainView.tsx +++ b/packages/base/src/mainview/mainView.tsx @@ -1386,8 +1386,6 @@ export class MainView extends React.Component { this.addProjection(newMapLayer); await this._waitForSourceReady(newMapLayer); - - this._trackLayerViewState(id, newMapLayer); } this._loadingLayers.delete(id); @@ -1448,6 +1446,9 @@ export class MainView extends React.Component { const safeIndex = Math.min(index, numLayers); this._Map.getLayers().insertAt(safeIndex, newMapLayer); + const shouldZoom = this.state.initialLayersReady; + this._trackLayerViewState(id, newMapLayer as Layer, shouldZoom); + // doing +1 instead of calling method again if ( !this.state.initialLayersReady && @@ -1959,7 +1960,11 @@ export class MainView extends React.Component { /** * Track layer's extent and zoom in model's view state */ - private _trackLayerViewState(layerId: string, olLayer: Layer): void { + private _trackLayerViewState( + layerId: string, + olLayer: Layer, + shouldZoom = false, + ): void { const source = olLayer.getSource(); const sourceId = source?.get?.('id'); @@ -1979,6 +1984,10 @@ export class MainView extends React.Component { const view: IViewState[string] = { extent, zoom }; this._model.updateLayerViewState(layerId, view); } + + if (shouldZoom) { + this._model.centerOnPosition(layerId); + } } /** From 350a39ff5241cc99e9778dcd42dff5e8897b4fd4 Mon Sep 17 00:00:00 2001 From: Nakul Date: Sun, 12 Apr 2026 17:15:44 +0530 Subject: [PATCH 2/5] stop zoom if users > 0 --- packages/base/src/mainview/mainView.tsx | 6 +++++- packages/schema/src/interfaces.ts | 1 + packages/schema/src/model.ts | 4 +++- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/base/src/mainview/mainView.tsx b/packages/base/src/mainview/mainView.tsx index f96f0aeca..0c9298967 100644 --- a/packages/base/src/mainview/mainView.tsx +++ b/packages/base/src/mainview/mainView.tsx @@ -1446,7 +1446,11 @@ export class MainView extends React.Component { const safeIndex = Math.min(index, numLayers); this._Map.getLayers().insertAt(safeIndex, newMapLayer); - const shouldZoom = this.state.initialLayersReady; + const myId = this._model.getClientId(); + const othersId = this._model.users.filter(u => u.userId !== myId); + + const isColloborative = othersId.length > 0; + const shouldZoom = this.state.initialLayersReady && !isColloborative; this._trackLayerViewState(id, newMapLayer as Layer, shouldZoom); // doing +1 instead of calling method again diff --git a/packages/schema/src/interfaces.ts b/packages/schema/src/interfaces.ts index 9ec6a3717..80952a22b 100644 --- a/packages/schema/src/interfaces.ts +++ b/packages/schema/src/interfaces.ts @@ -221,6 +221,7 @@ export interface IJupyterGISModel extends DocumentRegistry.IModel { viewState?: IViewState; annotationModel?: IAnnotationModel; currentMode: Modes; + users: IUserData[]; themeChanged: Signal< IJupyterGISModel, IChangedArgs diff --git a/packages/schema/src/model.ts b/packages/schema/src/model.ts index 40c36aae4..5b277381a 100644 --- a/packages/schema/src/model.ts +++ b/packages/schema/src/model.ts @@ -219,7 +219,9 @@ export class JupyterGISModel implements IJupyterGISModel { const users: IUserData[] = []; if (this._usersMap) { this._usersMap.forEach((val, key) => { - users.push({ userId: key, userData: val.user }); + if (val.user) { + users.push({ userId: key, userData: val.user }); + } }); } return users; From 18eaf79bd53b75d07c8ec48af3143caf04236021 Mon Sep 17 00:00:00 2001 From: Nakul Date: Mon, 13 Apr 2026 20:06:57 +0530 Subject: [PATCH 3/5] finally zooms on prior who created the layer --- .../base/src/commands/operationCommands.ts | 3 +++ .../base/src/features/layer-browser/index.tsx | 3 +++ .../base/src/features/processing/index.ts | 3 +++ .../context/StacResultsContext.tsx | 3 +++ .../base/src/formbuilder/creationform.tsx | 6 +++++ packages/base/src/mainview/mainView.tsx | 23 +++++++++++++------ packages/schema/src/doc.ts | 5 ++-- packages/schema/src/interfaces.ts | 3 ++- packages/schema/src/model.ts | 4 +--- packages/schema/src/schema/project/jgis.json | 9 ++++++++ 10 files changed, 49 insertions(+), 13 deletions(-) diff --git a/packages/base/src/commands/operationCommands.ts b/packages/base/src/commands/operationCommands.ts index 8d7def51e..9133bd44b 100644 --- a/packages/base/src/commands/operationCommands.ts +++ b/packages/base/src/commands/operationCommands.ts @@ -87,6 +87,9 @@ function createLayerCommand( name: name, visible: true, parameters: spec.buildParameters(parameters, sourceId), + metadata: { + creatorId: model.getClientId(), + }, }; model.addLayer(layerId, layerModel); }) as any, diff --git a/packages/base/src/features/layer-browser/index.tsx b/packages/base/src/features/layer-browser/index.tsx index fa8f1c583..bcd095661 100644 --- a/packages/base/src/features/layer-browser/index.tsx +++ b/packages/base/src/features/layer-browser/index.tsx @@ -111,6 +111,9 @@ export const LayerBrowserComponent: React.FC = ({ parameters: { ...tile.layerParameters, source: sourceId }, visible: true, name: tile.name + ' Layer', + metadata: { + creatorId: model.getClientId(), + }, }; model.sharedModel.addSource(sourceId, sourceModel); diff --git a/packages/base/src/features/processing/index.ts b/packages/base/src/features/processing/index.ts index ab97e968f..7189dee57 100644 --- a/packages/base/src/features/processing/index.ts +++ b/packages/base/src/features/processing/index.ts @@ -278,6 +278,9 @@ export async function executeSQLProcessing( parameters: { source: newSourceId }, visible: true, name: layerName, + metadata: { + creatorId: model.getClientId(), + }, }; model.sharedModel.addSource(newSourceId, sourceModel); diff --git a/packages/base/src/features/stac-browser/context/StacResultsContext.tsx b/packages/base/src/features/stac-browser/context/StacResultsContext.tsx index 82f72574f..ec81692ca 100644 --- a/packages/base/src/features/stac-browser/context/StacResultsContext.tsx +++ b/packages/base/src/features/stac-browser/context/StacResultsContext.tsx @@ -369,6 +369,9 @@ export function StacResultsProvider({ parameters: { data: stacData }, visible: true, name: stacData.properties?.title ?? stacData.id, + metadata: { + creatorId: model.getClientId(), + }, }; model.addLayer(layerId, layerModel); diff --git a/packages/base/src/formbuilder/creationform.tsx b/packages/base/src/formbuilder/creationform.tsx index f19a09921..d2be06528 100644 --- a/packages/base/src/formbuilder/creationform.tsx +++ b/packages/base/src/formbuilder/creationform.tsx @@ -237,6 +237,9 @@ export function CreationForm(props: ICreationFormProps) { parameters: { source: childId }, visible: true, name: `${sourceName ?? 'Layer'} ${tableName} Layer`, + metadata: { + creatorId: currentModel.getClientId(), + }, }; currentModel.addLayer(UUID.uuid4(), layerModel); @@ -276,6 +279,9 @@ export function CreationForm(props: ICreationFormProps) { parameters: layerParams, visible: true, name: actualName, + metadata: { + creatorId: currentModel.getClientId(), + }, }; currentModel.addLayer(UUID.uuid4(), layerModel); diff --git a/packages/base/src/mainview/mainView.tsx b/packages/base/src/mainview/mainView.tsx index 0c9298967..226eed56e 100644 --- a/packages/base/src/mainview/mainView.tsx +++ b/packages/base/src/mainview/mainView.tsx @@ -406,6 +406,9 @@ export class MainView extends React.Component { type: 'line', source: sourceId, }, + metadata: { + creatorId: this._model.getClientId(), + }, }; this.addLayer(layerId, layerModel, this.getLayerIDs().length); @@ -1446,12 +1449,14 @@ export class MainView extends React.Component { const safeIndex = Math.min(index, numLayers); this._Map.getLayers().insertAt(safeIndex, newMapLayer); - const myId = this._model.getClientId(); - const othersId = this._model.users.filter(u => u.userId !== myId); - - const isColloborative = othersId.length > 0; - const shouldZoom = this.state.initialLayersReady && !isColloborative; - this._trackLayerViewState(id, newMapLayer as Layer, shouldZoom); + const creatorId = layer.metadata?.creatorId; + const shouldZoom = this.state.initialLayersReady; + this._trackLayerViewState( + id, + newMapLayer as Layer, + shouldZoom, + creatorId, + ); // doing +1 instead of calling method again if ( @@ -1968,6 +1973,7 @@ export class MainView extends React.Component { layerId: string, olLayer: Layer, shouldZoom = false, + creatorId?: number, ): void { const source = olLayer.getSource(); const sourceId = source?.get?.('id'); @@ -1989,7 +1995,7 @@ export class MainView extends React.Component { this._model.updateLayerViewState(layerId, view); } - if (shouldZoom) { + if (shouldZoom && creatorId === this._model.getClientId()) { this._model.centerOnPosition(layerId); } } @@ -2886,6 +2892,9 @@ export class MainView extends React.Component { visible: true, name: 'Marker', parameters: layerParams, + metadata: { + creatorId: this._model.getClientId(), + }, }; this._model.sharedModel.addSource(sourceId, sourceModel); diff --git a/packages/schema/src/doc.ts b/packages/schema/src/doc.ts index 8eb62dfaf..68dedbe31 100644 --- a/packages/schema/src/doc.ts +++ b/packages/schema/src/doc.ts @@ -12,6 +12,7 @@ import { IJGISSource, IJGISSources, IJGISStoryMap, + IJGISViewState, } from './_interface/project/jgis'; import { SCHEMA_VERSION } from './_interface/version'; import { @@ -195,11 +196,11 @@ export class JupyterGISDoc return JSONExt.deepCopy(this._stories.toJSON()); } - get viewState(): JSONObject { + get viewState(): IJGISViewState { return JSONExt.deepCopy(this._viewState.toJSON()); } - set viewState(viewState: JSONObject) { + set viewState(viewState: IJGISViewState) { this.transact(() => { for (const [key, value] of Object.entries(viewState)) { this._viewState.set(key, value); diff --git a/packages/schema/src/interfaces.ts b/packages/schema/src/interfaces.ts index 80952a22b..fff865e30 100644 --- a/packages/schema/src/interfaces.ts +++ b/packages/schema/src/interfaces.ts @@ -27,6 +27,7 @@ import { IJGISSource, IJGISSources, IJGISStoryMap, + IJGISViewState, LayerType, SourceType, } from './_interface/project/jgis'; @@ -127,6 +128,7 @@ export interface IJupyterGISDoc extends YDocument { sources: IJGISSources; stories: IJGISStoryMaps; layerTree: IJGISLayerTree; + viewState: IJGISViewState; metadata: any; readonly editable: boolean; @@ -221,7 +223,6 @@ export interface IJupyterGISModel extends DocumentRegistry.IModel { viewState?: IViewState; annotationModel?: IAnnotationModel; currentMode: Modes; - users: IUserData[]; themeChanged: Signal< IJupyterGISModel, IChangedArgs diff --git a/packages/schema/src/model.ts b/packages/schema/src/model.ts index 5b277381a..40c36aae4 100644 --- a/packages/schema/src/model.ts +++ b/packages/schema/src/model.ts @@ -219,9 +219,7 @@ export class JupyterGISModel implements IJupyterGISModel { const users: IUserData[] = []; if (this._usersMap) { this._usersMap.forEach((val, key) => { - if (val.user) { - users.push({ userId: key, userData: val.user }); - } + users.push({ userId: key, userData: val.user }); }); } return users; diff --git a/packages/schema/src/schema/project/jgis.json b/packages/schema/src/schema/project/jgis.json index 476252d4e..7afcec725 100644 --- a/packages/schema/src/schema/project/jgis.json +++ b/packages/schema/src/schema/project/jgis.json @@ -90,6 +90,15 @@ }, "filters": { "$ref": "#/definitions/jGISFilter" + }, + "metadata": { + "type": "object", + "additionalProperties": false, + "properties": { + "creatorId": { + "type": "number" + } + } } } }, From b3d79fa50d7ff5db4cecea18151fdc686d12fbce Mon Sep 17 00:00:00 2001 From: Nakul Date: Tue, 14 Apr 2026 12:47:30 +0530 Subject: [PATCH 4/5] sync lastAddedLayer with awareness --- .../base/src/commands/operationCommands.ts | 3 -- .../base/src/features/layer-browser/index.tsx | 3 -- .../base/src/features/processing/index.ts | 3 -- .../context/StacResultsContext.tsx | 3 -- .../base/src/formbuilder/creationform.tsx | 6 --- packages/base/src/mainview/mainView.tsx | 38 ++++++++++--------- packages/schema/src/interfaces.ts | 1 + packages/schema/src/model.ts | 7 ++++ packages/schema/src/schema/project/jgis.json | 9 ----- 9 files changed, 29 insertions(+), 44 deletions(-) diff --git a/packages/base/src/commands/operationCommands.ts b/packages/base/src/commands/operationCommands.ts index 9133bd44b..8d7def51e 100644 --- a/packages/base/src/commands/operationCommands.ts +++ b/packages/base/src/commands/operationCommands.ts @@ -87,9 +87,6 @@ function createLayerCommand( name: name, visible: true, parameters: spec.buildParameters(parameters, sourceId), - metadata: { - creatorId: model.getClientId(), - }, }; model.addLayer(layerId, layerModel); }) as any, diff --git a/packages/base/src/features/layer-browser/index.tsx b/packages/base/src/features/layer-browser/index.tsx index bcd095661..fa8f1c583 100644 --- a/packages/base/src/features/layer-browser/index.tsx +++ b/packages/base/src/features/layer-browser/index.tsx @@ -111,9 +111,6 @@ export const LayerBrowserComponent: React.FC = ({ parameters: { ...tile.layerParameters, source: sourceId }, visible: true, name: tile.name + ' Layer', - metadata: { - creatorId: model.getClientId(), - }, }; model.sharedModel.addSource(sourceId, sourceModel); diff --git a/packages/base/src/features/processing/index.ts b/packages/base/src/features/processing/index.ts index 7189dee57..ab97e968f 100644 --- a/packages/base/src/features/processing/index.ts +++ b/packages/base/src/features/processing/index.ts @@ -278,9 +278,6 @@ export async function executeSQLProcessing( parameters: { source: newSourceId }, visible: true, name: layerName, - metadata: { - creatorId: model.getClientId(), - }, }; model.sharedModel.addSource(newSourceId, sourceModel); diff --git a/packages/base/src/features/stac-browser/context/StacResultsContext.tsx b/packages/base/src/features/stac-browser/context/StacResultsContext.tsx index ec81692ca..82f72574f 100644 --- a/packages/base/src/features/stac-browser/context/StacResultsContext.tsx +++ b/packages/base/src/features/stac-browser/context/StacResultsContext.tsx @@ -369,9 +369,6 @@ export function StacResultsProvider({ parameters: { data: stacData }, visible: true, name: stacData.properties?.title ?? stacData.id, - metadata: { - creatorId: model.getClientId(), - }, }; model.addLayer(layerId, layerModel); diff --git a/packages/base/src/formbuilder/creationform.tsx b/packages/base/src/formbuilder/creationform.tsx index d2be06528..f19a09921 100644 --- a/packages/base/src/formbuilder/creationform.tsx +++ b/packages/base/src/formbuilder/creationform.tsx @@ -237,9 +237,6 @@ export function CreationForm(props: ICreationFormProps) { parameters: { source: childId }, visible: true, name: `${sourceName ?? 'Layer'} ${tableName} Layer`, - metadata: { - creatorId: currentModel.getClientId(), - }, }; currentModel.addLayer(UUID.uuid4(), layerModel); @@ -279,9 +276,6 @@ export function CreationForm(props: ICreationFormProps) { parameters: layerParams, visible: true, name: actualName, - metadata: { - creatorId: currentModel.getClientId(), - }, }; currentModel.addLayer(UUID.uuid4(), layerModel); diff --git a/packages/base/src/mainview/mainView.tsx b/packages/base/src/mainview/mainView.tsx index 226eed56e..2051193ce 100644 --- a/packages/base/src/mainview/mainView.tsx +++ b/packages/base/src/mainview/mainView.tsx @@ -406,9 +406,6 @@ export class MainView extends React.Component { type: 'line', source: sourceId, }, - metadata: { - creatorId: this._model.getClientId(), - }, }; this.addLayer(layerId, layerModel, this.getLayerIDs().length); @@ -1449,14 +1446,8 @@ export class MainView extends React.Component { const safeIndex = Math.min(index, numLayers); this._Map.getLayers().insertAt(safeIndex, newMapLayer); - const creatorId = layer.metadata?.creatorId; const shouldZoom = this.state.initialLayersReady; - this._trackLayerViewState( - id, - newMapLayer as Layer, - shouldZoom, - creatorId, - ); + this._trackLayerViewState(id, newMapLayer as Layer, shouldZoom); // doing +1 instead of calling method again if ( @@ -1966,6 +1957,18 @@ export class MainView extends React.Component { return zoom ?? view.getZoom() ?? 0; } + private _getLayerCreatorId(layerId: string): number | undefined { + const states = this._model.sharedModel.awareness.getStates(); + + for (const state of states.values()) { + if (state?.lastAddedLayer?.layerId === layerId) { + return state.lastAddedLayer.clientId; + } + } + + return undefined; + } + /** * Track layer's extent and zoom in model's view state */ @@ -1973,7 +1976,6 @@ export class MainView extends React.Component { layerId: string, olLayer: Layer, shouldZoom = false, - creatorId?: number, ): void { const source = olLayer.getSource(); const sourceId = source?.get?.('id'); @@ -1993,10 +1995,15 @@ export class MainView extends React.Component { const view: IViewState[string] = { extent, zoom }; this._model.updateLayerViewState(layerId, view); - } - if (shouldZoom && creatorId === this._model.getClientId()) { - this._model.centerOnPosition(layerId); + if (shouldZoom) { + const creatorId = this._getLayerCreatorId(layerId); + const currentClientId = this._model.getClientId(); + + if (creatorId === currentClientId) { + this._model.centerOnPosition(layerId); + } + } } } @@ -2892,9 +2899,6 @@ export class MainView extends React.Component { visible: true, name: 'Marker', parameters: layerParams, - metadata: { - creatorId: this._model.getClientId(), - }, }; this._model.sharedModel.addSource(sourceId, sourceModel); diff --git a/packages/schema/src/interfaces.ts b/packages/schema/src/interfaces.ts index fff865e30..46c219864 100644 --- a/packages/schema/src/interfaces.ts +++ b/packages/schema/src/interfaces.ts @@ -301,6 +301,7 @@ export interface IJupyterGISModel extends DocumentRegistry.IModel { syncViewport(viewport?: IViewPortState, emitter?: string): void; syncSelected(value: { [key: string]: ISelection }, emitter?: string): void; selected: { [key: string]: ISelection } | undefined; + syncLastAddedLayer(layerId: string): void; setEditingItem(type: SelectionType, itemId: string): void; clearEditingItem(): void; readonly editing: { type: SelectionType; itemId: string } | null; diff --git a/packages/schema/src/model.ts b/packages/schema/src/model.ts index 40c36aae4..9d1702cb0 100644 --- a/packages/schema/src/model.ts +++ b/packages/schema/src/model.ts @@ -500,6 +500,7 @@ export class JupyterGISModel implements IJupyterGISModel { ): void { if (!this.getLayer(id)) { this.sharedModel.addLayer(id, layer); + this.syncLastAddedLayer(id); } this._addLayerTreeItem(id, groupName, position); @@ -585,6 +586,12 @@ export class JupyterGISModel implements IJupyterGISModel { }); } + syncLastAddedLayer(layerId: string): void { + this.sharedModel.awareness.setLocalStateField('lastAddedLayer', { + layerId, + clientId: this.getClientId(), + }); + } get selected(): { [key: string]: ISelection } | undefined { return this.localState?.selected?.value; } diff --git a/packages/schema/src/schema/project/jgis.json b/packages/schema/src/schema/project/jgis.json index 7afcec725..476252d4e 100644 --- a/packages/schema/src/schema/project/jgis.json +++ b/packages/schema/src/schema/project/jgis.json @@ -90,15 +90,6 @@ }, "filters": { "$ref": "#/definitions/jGISFilter" - }, - "metadata": { - "type": "object", - "additionalProperties": false, - "properties": { - "creatorId": { - "type": "number" - } - } } } }, From e18331c1719aa797720cd26a2a4afbcb4f3f42a8 Mon Sep 17 00:00:00 2001 From: Nakul Date: Tue, 14 Apr 2026 16:43:47 +0530 Subject: [PATCH 5/5] remove manual clientId --- packages/base/src/mainview/mainView.tsx | 4 ++-- packages/schema/src/model.ts | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/base/src/mainview/mainView.tsx b/packages/base/src/mainview/mainView.tsx index 2051193ce..ac078f94f 100644 --- a/packages/base/src/mainview/mainView.tsx +++ b/packages/base/src/mainview/mainView.tsx @@ -1960,9 +1960,9 @@ export class MainView extends React.Component { private _getLayerCreatorId(layerId: string): number | undefined { const states = this._model.sharedModel.awareness.getStates(); - for (const state of states.values()) { + for (const [clientId, state] of states.entries()) { if (state?.lastAddedLayer?.layerId === layerId) { - return state.lastAddedLayer.clientId; + return clientId; } } diff --git a/packages/schema/src/model.ts b/packages/schema/src/model.ts index 9d1702cb0..10a3727a3 100644 --- a/packages/schema/src/model.ts +++ b/packages/schema/src/model.ts @@ -589,7 +589,6 @@ export class JupyterGISModel implements IJupyterGISModel { syncLastAddedLayer(layerId: string): void { this.sharedModel.awareness.setLocalStateField('lastAddedLayer', { layerId, - clientId: this.getClientId(), }); } get selected(): { [key: string]: ISelection } | undefined {