diff --git a/packages/base/src/commands/BaseCommandIDs.ts b/packages/base/src/commands/BaseCommandIDs.ts index b700e6849..95c7491e2 100644 --- a/packages/base/src/commands/BaseCommandIDs.ts +++ b/packages/base/src/commands/BaseCommandIDs.ts @@ -49,3 +49,17 @@ export const selectCompleter = 'jupytergis:selectConsoleCompleter'; export const addAnnotation = 'jupytergis:addAnnotation'; export const zoomToLayer = 'jupytergis:zoomToLayer'; export const downloadGeoJSON = 'jupytergis:downloadGeoJSON'; + +// Panel toggles +export const toggleLeftPanel = 'jupytergis:toggleLeftPanel'; +export const toggleRightPanel = 'jupytergis:toggleRightPanel'; + +// Left panel tabs +export const showLayersTab = 'jupytergis:showLayersTab'; +export const showStacBrowserTab = 'jupytergis:showStacBrowserTab'; +export const showFiltersTab = 'jupytergis:showFiltersTab'; + +// Right panel tabs +export const showObjectPropertiesTab = 'jupytergis:showObjectPropertiesTab'; +export const showAnnotationsTab = 'jupytergis:showAnnotationsTab'; +export const showIdentifyPanelTab = 'jupytergis:showIdentifyPanelTab'; diff --git a/packages/base/src/commands/index.ts b/packages/base/src/commands/index.ts index 10c76d41e..8a414fe3e 100644 --- a/packages/base/src/commands/index.ts +++ b/packages/base/src/commands/index.ts @@ -171,6 +171,9 @@ export function addCommands( return isIdentifying; }, isEnabled: () => { + if (tracker.currentWidget?.model.jgisSettings.identifyDisabled) { + return false; + } const selectedLayer = getSingleSelectedLayer(tracker); if (!selectedLayer) { return false; @@ -847,6 +850,195 @@ export function addCommands( icon: targetWithCenterIcon, }); + // Panel visibility commands + commands.addCommand(CommandIDs.toggleLeftPanel, { + label: trans.__('Toggle Left Panel'), + isEnabled: () => Boolean(tracker.currentWidget), + isToggled: () => { + const current = tracker.currentWidget; + return current ? !current.model.jgisSettings.leftPanelDisabled : false; + }, + execute: async () => { + const current = tracker.currentWidget; + if (!current) { + return; + } + + try { + const settings = await current.model.getSettings(); + const currentValue = + settings?.composite?.leftPanelDisabled ?? + current.model.jgisSettings.leftPanelDisabled ?? + false; + await settings?.set('leftPanelDisabled', !currentValue); + commands.notifyCommandChanged(CommandIDs.toggleLeftPanel); + } catch (err) { + console.error('Failed to toggle Left Panel:', err); + } + }, + }); + + commands.addCommand(CommandIDs.toggleRightPanel, { + label: trans.__('Toggle Right Panel'), + isEnabled: () => Boolean(tracker.currentWidget), + isToggled: () => { + const current = tracker.currentWidget; + return current ? !current.model.jgisSettings.rightPanelDisabled : false; + }, + execute: async () => { + const current = tracker.currentWidget; + if (!current) { + return; + } + + try { + const settings = await current.model.getSettings(); + const currentValue = + settings?.composite?.rightPanelDisabled ?? + current.model.jgisSettings.rightPanelDisabled ?? + false; + await settings?.set('rightPanelDisabled', !currentValue); + commands.notifyCommandChanged(CommandIDs.toggleRightPanel); + } catch (err) { + console.error('Failed to toggle Right Panel:', err); + } + }, + }); + + // Left panel tabs + commands.addCommand(CommandIDs.showLayersTab, { + label: trans.__('Show Layers Tab'), + isEnabled: () => Boolean(tracker.currentWidget), + isToggled: () => + tracker.currentWidget + ? !tracker.currentWidget.model.jgisSettings.layersDisabled + : false, + execute: async () => { + const current = tracker.currentWidget; + if (!current) { + return; + } + const settings = await current.model.getSettings(); + const currentValue = + settings?.composite?.layersDisabled ?? + current.model.jgisSettings.layersDisabled ?? + false; + await settings?.set('layersDisabled', !currentValue); + commands.notifyCommandChanged(CommandIDs.showLayersTab); + }, + }); + + commands.addCommand(CommandIDs.showStacBrowserTab, { + label: trans.__('Show STAC Browser Tab'), + isEnabled: () => Boolean(tracker.currentWidget), + isToggled: () => + tracker.currentWidget + ? !tracker.currentWidget.model.jgisSettings.stacBrowserDisabled + : false, + execute: async () => { + const current = tracker.currentWidget; + if (!current) { + return; + } + const settings = await current.model.getSettings(); + const currentValue = + settings?.composite?.stacBrowserDisabled ?? + current.model.jgisSettings.stacBrowserDisabled ?? + false; + await settings?.set('stacBrowserDisabled', !currentValue); + commands.notifyCommandChanged(CommandIDs.showStacBrowserTab); + }, + }); + + commands.addCommand(CommandIDs.showFiltersTab, { + label: trans.__('Show Filters Tab'), + isEnabled: () => Boolean(tracker.currentWidget), + isToggled: () => + tracker.currentWidget + ? !tracker.currentWidget.model.jgisSettings.filtersDisabled + : false, + execute: async () => { + const current = tracker.currentWidget; + if (!current) { + return; + } + const settings = await current.model.getSettings(); + const currentValue = + settings?.composite?.filtersDisabled ?? + current.model.jgisSettings.filtersDisabled ?? + false; + await settings?.set('filtersDisabled', !currentValue); + commands.notifyCommandChanged(CommandIDs.showFiltersTab); + }, + }); + + // Right panel tabs + commands.addCommand(CommandIDs.showObjectPropertiesTab, { + label: trans.__('Show Object Properties Tab'), + isEnabled: () => Boolean(tracker.currentWidget), + isToggled: () => + tracker.currentWidget + ? !tracker.currentWidget.model.jgisSettings.objectPropertiesDisabled + : false, + execute: async () => { + const current = tracker.currentWidget; + if (!current) { + return; + } + const settings = await current.model.getSettings(); + const currentValue = + settings?.composite?.objectPropertiesDisabled ?? + current.model.jgisSettings.objectPropertiesDisabled ?? + false; + await settings?.set('objectPropertiesDisabled', !currentValue); + commands.notifyCommandChanged(CommandIDs.showObjectPropertiesTab); + }, + }); + + commands.addCommand(CommandIDs.showAnnotationsTab, { + label: trans.__('Show Annotations Tab'), + isEnabled: () => Boolean(tracker.currentWidget), + isToggled: () => + tracker.currentWidget + ? !tracker.currentWidget.model.jgisSettings.annotationsDisabled + : false, + execute: async () => { + const current = tracker.currentWidget; + if (!current) { + return; + } + const settings = await current.model.getSettings(); + const currentValue = + settings?.composite?.annotationsDisabled ?? + current.model.jgisSettings.annotationsDisabled ?? + false; + await settings?.set('annotationsDisabled', !currentValue); + commands.notifyCommandChanged(CommandIDs.showAnnotationsTab); + }, + }); + + commands.addCommand(CommandIDs.showIdentifyPanelTab, { + label: trans.__('Show Identify Panel Tab'), + isEnabled: () => Boolean(tracker.currentWidget), + isToggled: () => + tracker.currentWidget + ? !tracker.currentWidget.model.jgisSettings.identifyDisabled + : false, + execute: async () => { + const current = tracker.currentWidget; + if (!current) { + return; + } + const settings = await current.model.getSettings(); + const currentValue = + settings?.composite?.identifyDisabled ?? + current.model.jgisSettings.identifyDisabled ?? + false; + await settings?.set('identifyDisabled', !currentValue); + commands.notifyCommandChanged(CommandIDs.showIdentifyPanelTab); + }, + }); + loadKeybindings(commands, keybindings); } diff --git a/packages/base/src/panelview/leftpanel.tsx b/packages/base/src/panelview/leftpanel.tsx index 162522b4b..00242f277 100644 --- a/packages/base/src/panelview/leftpanel.tsx +++ b/packages/base/src/panelview/leftpanel.tsx @@ -1,5 +1,4 @@ import { IJupyterGISModel, SelectionType } from '@jupytergis/schema'; -import { PageConfig } from '@jupyterlab/coreutils'; import { IStateDB } from '@jupyterlab/statedb'; import { CommandRegistry } from '@lumino/commands'; import { MouseEvent as ReactMouseEvent } from 'react'; @@ -31,60 +30,86 @@ interface ILeftPanelProps { export const LeftPanel: React.FC = ( props: ILeftPanelProps, ) => { - const hideStacPanel = PageConfig.getOption('HIDE_STAC_PANEL') === 'true'; + const [settings, setSettings] = React.useState(props.model.jgisSettings); + + React.useEffect(() => { + const onSettingsChanged = () => { + setSettings({ ...props.model.jgisSettings }); + }; + + props.model.settingsChanged.connect(onSettingsChanged); + return () => { + props.model.settingsChanged.disconnect(onSettingsChanged); + }; + }, [props.model]); + + const allLeftTabsDisabled = + settings.layersDisabled && + settings.stacBrowserDisabled && + settings.filtersDisabled; + + const leftPanelVisible = !settings.leftPanelDisabled && !allLeftTabsDisabled; const tabInfo = [ - { name: 'layers', title: 'Layers' }, - ...(hideStacPanel ? [] : [{ name: 'stac', title: 'Stac Browser' }]), - { name: 'filters', title: 'Filters' }, - ]; + !settings.layersDisabled ? { name: 'layers', title: 'Layers' } : false, + !settings.stacBrowserDisabled + ? { name: 'stac', title: 'Stac Browser' } + : false, + !settings.filtersDisabled ? { name: 'filters', title: 'Filters' } : false, + ].filter(Boolean) as { name: string; title: string }[]; const [curTab, setCurTab] = React.useState( - tabInfo[0].name, + tabInfo.length > 0 ? tabInfo[0].name : undefined, ); return ( -
+
- {tabInfo.map(e => { - return ( - { - if (curTab !== e.name) { - setCurTab(e.name); - } else { - setCurTab(''); - } - }} - > - {e.title} - - ); - })} + {tabInfo.map(e => ( + { + if (curTab !== e.name) { + setCurTab(e.name); + } else { + setCurTab(''); + } + }} + > + {e.title} + + ))} - - - - {!hideStacPanel && ( - - + {!settings.layersDisabled && ( + + )} - - , - + {!settings.stacBrowserDisabled && ( + + + + )} + + {!settings.filtersDisabled && ( + + + + )}
); diff --git a/packages/base/src/panelview/rightpanel.tsx b/packages/base/src/panelview/rightpanel.tsx index 010c046cc..3b9914ab8 100644 --- a/packages/base/src/panelview/rightpanel.tsx +++ b/packages/base/src/panelview/rightpanel.tsx @@ -3,7 +3,6 @@ import { IJGISFormSchemaRegistry, IJupyterGISModel, } from '@jupytergis/schema'; -import { PageConfig } from '@jupyterlab/coreutils'; import * as React from 'react'; import { AnnotationsPanel } from './annotationPanel'; @@ -23,66 +22,100 @@ interface IRightPanelProps { } export const RightPanel: React.FC = props => { - const hideAnnotationPanel = - PageConfig.getOption('HIDE_ANNOTATION_PANEL') === 'true'; + const [settings, setSettings] = React.useState(props.model.jgisSettings); - const [selectedObjectProperties, setSelectedObjectProperties] = - React.useState(undefined); + React.useEffect(() => { + const onSettingsChanged = () => { + setSettings({ ...props.model.jgisSettings }); + }; + + props.model.settingsChanged.connect(onSettingsChanged); + return () => { + props.model.settingsChanged.disconnect(onSettingsChanged); + }; + }, [props.model]); + + const allRightTabsDisabled = + settings.objectPropertiesDisabled && + settings.annotationsDisabled && + settings.identifyDisabled; + + const rightPanelVisible = + !settings.rightPanelDisabled && !allRightTabsDisabled; const tabInfo = [ - { name: 'objectProperties', title: 'Object Properties' }, - ...(hideAnnotationPanel - ? [] - : [{ name: 'annotations', title: 'Annotations' }]), - { name: 'identifyPanel', title: 'Identified Features' }, - ]; + !settings.objectPropertiesDisabled + ? { name: 'objectProperties', title: 'Object Properties' } + : false, + !settings.annotationsDisabled + ? { name: 'annotations', title: 'Annotations' } + : false, + !settings.identifyDisabled + ? { name: 'identifyPanel', title: 'Identified Features' } + : false, + ].filter(Boolean) as { name: string; title: string }[]; const [curTab, setCurTab] = React.useState( - tabInfo[0].name, + tabInfo.length > 0 ? tabInfo[0].name : undefined, ); + const [selectedObjectProperties, setSelectedObjectProperties] = + React.useState(undefined); + return ( -
+
- {tabInfo.map(e => { - return ( - { - if (curTab !== e.name) { - setCurTab(e.name); - } else { - setCurTab(''); - } - }} - > - {e.title} - - ); - })} + {tabInfo.map(e => ( + { + if (curTab !== e.name) { + setCurTab(e.name); + } else { + setCurTab(''); + } + }} + > + {e.title} + + ))} - - - - - - - - - + + {!settings.objectPropertiesDisabled && ( + + + + )} + + {!settings.annotationsDisabled && ( + + + + )} + + {!settings.identifyDisabled && ( + + + + )}
); diff --git a/packages/schema/src/interfaces.ts b/packages/schema/src/interfaces.ts index e1149c98e..50c2b2e81 100644 --- a/packages/schema/src/interfaces.ts +++ b/packages/schema/src/interfaces.ts @@ -10,6 +10,7 @@ import { IChangedArgs } from '@jupyterlab/coreutils'; import { IDocumentManager } from '@jupyterlab/docmanager'; import { DocumentRegistry, IDocumentWidget } from '@jupyterlab/docregistry'; import { Contents, User } from '@jupyterlab/services'; +import { ISettingRegistry } from '@jupyterlab/settingregistry'; import { JSONObject } from '@lumino/coreutils'; import { ISignal, Signal } from '@lumino/signaling'; import { SplitPanel } from '@lumino/widgets'; @@ -200,7 +201,9 @@ export interface IJupyterGISModel extends DocumentRegistry.IModel { features: FeatureLike[]; }) => void; - getSettings(): IJupyterGISSettings; + getSettings(): Promise; + settingsChanged: ISignal; + jgisSettings: IJupyterGISSettings; getContent(): IJGISContent; getLayers(): IJGISLayers; getLayer(id: string): IJGISLayer | undefined; @@ -370,4 +373,18 @@ export interface IAnnotation { export interface IJupyterGISSettings { proxyUrl: string; + + // Panel visibility + leftPanelDisabled?: boolean; + rightPanelDisabled?: boolean; + + // Left panel tabs + layersDisabled?: boolean; + stacBrowserDisabled?: boolean; + filtersDisabled?: boolean; + + // Right panel tabs + objectPropertiesDisabled?: boolean; + annotationsDisabled?: boolean; + identifyDisabled?: boolean; } diff --git a/packages/schema/src/model.ts b/packages/schema/src/model.ts index 31e9135b1..ad124cc34 100644 --- a/packages/schema/src/model.ts +++ b/packages/schema/src/model.ts @@ -58,24 +58,106 @@ export class JupyterGISModel implements IJupyterGISModel { this.annotationModel = annotationModel; this.settingRegistry = settingRegistry; this._pathChanged = new Signal(this); + this._settingsChanged = new Signal(this); + + this._jgisSettings = { + proxyUrl: 'https://corsproxy.io', + leftPanelDisabled: false, + rightPanelDisabled: false, + layersDisabled: false, + stacBrowserDisabled: false, + filtersDisabled: false, + objectPropertiesDisabled: false, + annotationsDisabled: false, + identifyDisabled: false, + }; + + this.initSettings(); } /** * Initialize custom settings for JupyterLab. */ - async initSettings(): Promise { + async initSettings() { if (this.settingRegistry) { - const setting = await this.settingRegistry.load(SETTINGS_ID); - this._settings = setting.composite as any; - - setting.changed.connect(() => { - this._settings = setting.composite as any; - console.log('JupyterGIS Settings updated:', this._settings); - }); + try { + const setting = await this.settingRegistry.load(SETTINGS_ID); + this._settings = setting; + + this._updateLocalSettings(); + + setting.changed.connect(() => { + const oldSettings = { ...this._jgisSettings }; + this._updateLocalSettings(); + const newSettings = this._jgisSettings; + + const keys: (keyof IJupyterGISSettings)[] = [ + 'proxyUrl', + 'leftPanelDisabled', + 'rightPanelDisabled', + 'layersDisabled', + 'stacBrowserDisabled', + 'filtersDisabled', + 'objectPropertiesDisabled', + 'annotationsDisabled', + 'identifyDisabled', + ]; + + keys.forEach(key => { + if (oldSettings[key] !== newSettings[key]) { + this._settingsChanged.emit(key); + } + }); + }); + } catch (error) { + console.error(`Failed to load settings for ${SETTINGS_ID}:`, error); + this._jgisSettings = { + proxyUrl: 'https://corsproxy.io', + leftPanelDisabled: false, + rightPanelDisabled: false, + layersDisabled: false, + stacBrowserDisabled: false, + filtersDisabled: false, + objectPropertiesDisabled: false, + annotationsDisabled: false, + identifyDisabled: false, + }; + } } } - getSettings(): IJupyterGISSettings { + private _updateLocalSettings(): void { + const composite = this._settings.composite; + + this._jgisSettings = { + proxyUrl: (composite.proxyUrl as string) ?? 'https://corsproxy.io', + leftPanelDisabled: (composite.leftPanelDisabled as boolean) ?? false, + rightPanelDisabled: (composite.rightPanelDisabled as boolean) ?? false, + layersDisabled: (composite.layersDisabled as boolean) ?? false, + stacBrowserDisabled: (composite.stacBrowserDisabled as boolean) ?? false, + filtersDisabled: (composite.filtersDisabled as boolean) ?? false, + objectPropertiesDisabled: + (composite.objectPropertiesDisabled as boolean) ?? false, + annotationsDisabled: (composite.annotationsDisabled as boolean) ?? false, + identifyDisabled: (composite.identifyDisabled as boolean) ?? false, + }; + } + + get jgisSettings(): IJupyterGISSettings { + return this._jgisSettings; + } + + /** + * Expose the settingsChanged signal for external use. + */ + get settingsChanged(): ISignal { + return this._settingsChanged; + } + + /** + * Return stored settings. + */ + async getSettings(): Promise { return this._settings; } @@ -779,7 +861,10 @@ export class JupyterGISModel implements IJupyterGISModel { readonly annotationModel?: IAnnotationModel; readonly settingRegistry?: ISettingRegistry; - private _settings: IJupyterGISSettings; + private _settings: ISettingRegistry.ISettings; + private _settingsChanged: Signal; + private _jgisSettings: IJupyterGISSettings; + private _sharedModel: IJupyterGISDoc; private _filePath: string; private _contentsManager?: Contents.IManager; diff --git a/python/jupytergis_core/schema/jupytergis-settings.json b/python/jupytergis_core/schema/jupytergis-settings.json index 614ec71a9..4d82e2cdb 100644 --- a/python/jupytergis_core/schema/jupytergis-settings.json +++ b/python/jupytergis_core/schema/jupytergis-settings.json @@ -8,6 +8,41 @@ "title": "Proxy URL", "description": "The proxy URL to use for external requests.", "default": "https://corsproxy.io" + }, + "leftPanelDisabled": { + "type": "boolean", + "title": "Disable Left Panel", + "default": false + }, + "stacBrowserDisabled": { + "type": "boolean", + "title": "Disable STAC Browser", + "default": false + }, + "filtersDisabled": { + "type": "boolean", + "title": "Disable Filters", + "default": false + }, + "annotationsDisabled": { + "type": "boolean", + "title": "Disable Annotations", + "default": false + }, + "identifyDisabled": { + "type": "boolean", + "title": "Disable Identify Features", + "default": false + }, + "objectPropertiesDisabled": { + "type": "boolean", + "title": "Disable Object Properties", + "default": false + }, + "rightPanelDisabled": { + "type": "boolean", + "title": "Disable Right Panel", + "default": false } } } diff --git a/python/jupytergis_core/src/jgisplugin/modelfactory.ts b/python/jupytergis_core/src/jgisplugin/modelfactory.ts index ae485f305..a8e8a6b69 100644 --- a/python/jupytergis_core/src/jgisplugin/modelfactory.ts +++ b/python/jupytergis_core/src/jgisplugin/modelfactory.ts @@ -83,14 +83,12 @@ export class JupyterGISModelFactory createNew( options: DocumentRegistry.IModelOptions, ): JupyterGISModel { - const model = new JupyterGISModel({ + return new JupyterGISModel({ sharedModel: options.sharedModel, languagePreference: options.languagePreference, annotationModel: this._annotationModel, settingRegistry: this._settingRegistry, }); - model.initSettings(); - return model; } private _annotationModel: IAnnotationModel; diff --git a/python/jupytergis_lab/src/notebookrenderer.ts b/python/jupytergis_lab/src/notebookrenderer.ts index 50915250a..f34275160 100644 --- a/python/jupytergis_lab/src/notebookrenderer.ts +++ b/python/jupytergis_lab/src/notebookrenderer.ts @@ -20,6 +20,7 @@ import { JupyterFrontEnd, JupyterFrontEndPlugin, } from '@jupyterlab/application'; +import { ISettingRegistry } from '@jupyterlab/settingregistry'; import { showErrorMessage } from '@jupyterlab/apputils'; import { ConsolePanel } from '@jupyterlab/console'; import { PathExt } from '@jupyterlab/coreutils'; @@ -142,6 +143,7 @@ export const notebookRendererPlugin: JupyterFrontEndPlugin = { IStateDB, IJGISFormSchemaRegistryToken, IAnnotationToken, + ISettingRegistry, ], activate: ( app: JupyterFrontEnd, @@ -152,6 +154,7 @@ export const notebookRendererPlugin: JupyterFrontEndPlugin = { formSchemaRegistry?: IJGISFormSchemaRegistry, state?: IStateDB, annotationModel?: IAnnotationModel, + settingRegistry?: ISettingRegistry, ): void => { if (!yWidgetManager) { console.error('Missing IJupyterYWidgetManager token!'); @@ -214,6 +217,7 @@ export const notebookRendererPlugin: JupyterFrontEndPlugin = { })!; this.jupyterGISModel = new JupyterGISModel({ sharedModel: sharedModel as IJupyterGISDoc, + settingRegistry, }); this.jupyterGISModel.contentsManager = app.serviceManager.contents;