diff --git a/datalayer/serverapplication.py b/datalayer/serverapplication.py index 1263173..a32acb9 100644 --- a/datalayer/serverapplication.py +++ b/datalayer/serverapplication.py @@ -36,7 +36,7 @@ class DatalayerExtensionApp(ExtensionAppJinjaMixin, ExtensionApp): ) launcher_category = Unicode("Datalayer", config=True, - help=("Category to use for the applicaton launcher."), + help=("Category to use for the application launcher."), ) white_label = Bool(False, config=True, diff --git a/src/Datalayer.tsx b/src/Datalayer.tsx index 317934a..27fe85d 100644 --- a/src/Datalayer.tsx +++ b/src/Datalayer.tsx @@ -1,16 +1,19 @@ import { useState, useEffect } from 'react'; import { JupyterFrontEnd } from '@jupyterlab/application'; -import { ThemeProvider, BaseStyles, Box, } from '@primer/react'; +import { ThemeProvider, BaseStyles, Box } from '@primer/react'; import { UnderlineNav } from '@primer/react'; -import { DatalayerGreenPaddingIcon, JupyterLabIcon } from '@datalayer/icons-react'; +import { + DatalayerGreenPaddingIcon, + JupyterLabIcon +} from '@datalayer/icons-react'; import AboutTab from './tabs/AboutTab'; import JupyterLabTab from './tabs/JupyterLabTab'; import { requestAPI } from './jupyterlab/handler'; -import useStore from './state/zustand'; +import useStore from './state'; export type DatalayerProps = { jupyterFrontend?: JupyterFrontEnd; -} +}; const Datalayer = (props: DatalayerProps) => { const { jupyterFrontend } = props; @@ -19,14 +22,14 @@ const Datalayer = (props: DatalayerProps) => { const [version, setVersion] = useState(''); useEffect(() => { requestAPI('config') - .then(data => { - setVersion(data.version); - }) - .catch(reason => { - console.error( - `Error while accessing the jupyter server datalayer extension.\n${reason}` - ); - }); + .then(data => { + setVersion(data.version); + }) + .catch(reason => { + console.error( + `Error while accessing the jupyter server datalayer extension.\n${reason}` + ); + }); }); return ( <> @@ -35,16 +38,34 @@ const Datalayer = (props: DatalayerProps) => { - } onSelect={e => {e.preventDefault(); setTab(0.0);}}> + } + onSelect={e => { + e.preventDefault(); + setTab(0.0); + }} + > JupyterLab - } onSelect={e => {e.preventDefault(); setTab(1.0);}}> + } + onSelect={e => { + e.preventDefault(); + setTab(1.0); + }} + > About - {intTab === 0 && jupyterFrontend && } + {intTab === 0 && jupyterFrontend && ( + + )} {intTab === 1 && } @@ -52,6 +73,6 @@ const Datalayer = (props: DatalayerProps) => { ); -} +}; export default Datalayer; diff --git a/src/index.ts b/src/index.ts index f7bfeaa..6c2119e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,3 @@ export * from './constants'; export * from './jupyterlab'; - +export * from './state'; \ No newline at end of file diff --git a/src/jupyterlab/index.ts b/src/jupyterlab/index.ts index 468baee..f852787 100644 --- a/src/jupyterlab/index.ts +++ b/src/jupyterlab/index.ts @@ -1,46 +1,23 @@ -import { Token } from '@lumino/coreutils'; -import { ISignal, Signal } from '@lumino/signaling'; -import { JupyterFrontEnd, JupyterFrontEndPlugin, ILayoutRestorer } from '@jupyterlab/application'; -import { MainAreaWidget, ICommandPalette, WidgetTracker } from '@jupyterlab/apputils'; +import { + JupyterFrontEnd, + JupyterFrontEndPlugin, + ILayoutRestorer +} from '@jupyterlab/application'; +import { + MainAreaWidget, + ICommandPalette, + WidgetTracker +} from '@jupyterlab/apputils'; import { ISettingRegistry } from '@jupyterlab/settingregistry'; import { ILauncher } from '@jupyterlab/launcher'; +import { PromiseDelegate } from '@lumino/coreutils'; import icon from '@datalayer/icons-react/data1/DatalayerGreenPaddingIconJupyterLab'; import { requestAPI } from './handler'; import { DatalayerWidget } from './widget'; +import { datalayerStore } from '../state'; +import { IDatalayerConfig, IDatalayer, DatalayerConfiguration } from './tokens'; -import '../../style/index.css'; - -export type IDatalayerConfig = { - apiServerUrl: string, - launcherCategory: string, - whiteLabel: boolean, -}; - -export class DatalayerConfiguration { - private _configuration?: IDatalayerConfig; - private _configurationChanged: Signal; - constructor() { - this._configurationChanged = new Signal(this); - } - set configuration(configuration: IDatalayerConfig | undefined) { - this._configuration = configuration; - this._configurationChanged.emit(configuration) - } - get configuration() { - return this._configuration; - } - get configurationChanged(): ISignal { - return this._configurationChanged; - } -} - -export type IDatalayer = { - configuration: DatalayerConfiguration, -}; - -export const IDatalayer = new Token( - '@datalayer/core:plugin' -); +export * from './tokens'; /** * The command IDs used by the plugin.k @@ -65,7 +42,7 @@ const plugin: JupyterFrontEndPlugin = { palette: ICommandPalette, settingRegistry?: ISettingRegistry, launcher?: ILauncher, - restorer?: ILayoutRestorer, + restorer?: ILayoutRestorer ) => { tracker.forEach(widget => widget.dispose()); console.log(`${plugin.id} is deactivated`); @@ -75,31 +52,45 @@ const plugin: JupyterFrontEndPlugin = { palette: ICommandPalette, settingRegistry?: ISettingRegistry, launcher?: ILauncher, - restorer?: ILayoutRestorer, + restorer?: ILayoutRestorer ): IDatalayer => { - const datalayer: IDatalayer = { - configuration: new DatalayerConfiguration(), - } + const ready = new PromiseDelegate(); + const datalayer: IDatalayer = Object.freeze({ + configuration: new DatalayerConfiguration(), + ready: ready.promise + }); + storeConfiguration(datalayer.configuration.configuration); + datalayer.configuration.configurationChanged.connect((_, config) => { + storeConfiguration(config); + }); + requestAPI('config') .then(data => { console.log('Received Datalayer configuration', data); const configuration = { apiServerUrl: data.settings.api_server_url, launcherCategory: data.settings.launcher_category, - whiteLabel: data.settings.white_label, - } + whiteLabel: data.settings.white_label + }; datalayer.configuration.configuration = configuration; + ready.resolve(); + + // Don't add user interface elements in white label + if (configuration.whiteLabel) { + return; + } + const { commands } = app; const command = CommandIDs.create; if (!tracker) { tracker = new WidgetTracker>({ - namespace: 'datalayer', + namespace: 'datalayer' }); } if (restorer) { void restorer.restore(tracker, { command, - name: () => 'datalayer', + name: () => 'datalayer' }); } commands.addCommand(command, { @@ -118,12 +109,13 @@ const plugin: JupyterFrontEndPlugin = { const category = configuration.launcherCategory; palette.addItem({ command, category }); const settingsUpdated = (settings: ISettingRegistry.ISettings) => { - const showInLauncher = settings.get('showInLauncher').composite as boolean; + const showInLauncher = settings.get('showInLauncher') + .composite as boolean; if (launcher && showInLauncher) { launcher.add({ command, category, - rank: 1.1, + rank: 1.1 }); } }; @@ -141,13 +133,18 @@ const plugin: JupyterFrontEndPlugin = { } }) .catch(reason => { + ready.reject(reason); console.error( `Error while accessing the jupyter server extension.\n${reason}` ); - } - ); + }); console.log(`JupyterLab plugin ${plugin.id} is activated.`); return datalayer; + + function storeConfiguration(configuration?: IDatalayerConfig) { + const { setConfiguration } = datalayerStore.getState(); + setConfiguration(configuration); + } } }; diff --git a/src/jupyterlab/tokens.ts b/src/jupyterlab/tokens.ts new file mode 100644 index 0000000..63353f2 --- /dev/null +++ b/src/jupyterlab/tokens.ts @@ -0,0 +1,42 @@ +import { Token } from '@lumino/coreutils'; +import { ISignal, Signal } from '@lumino/signaling'; + +export type IDatalayerConfig = { + apiServerUrl: string; + launcherCategory: string; + whiteLabel: boolean; +}; + +export type IDatalayer = { + configuration: DatalayerConfiguration; + ready: Promise; +}; + +export const IDatalayer = new Token('@datalayer/core:plugin'); + +export class DatalayerConfiguration { + private _configuration?: IDatalayerConfig; + private _configurationChanged: Signal< + DatalayerConfiguration, + IDatalayerConfig | undefined + >; + constructor() { + this._configurationChanged = new Signal< + DatalayerConfiguration, + IDatalayerConfig | undefined + >(this); + } + set configuration(configuration: IDatalayerConfig | undefined) { + this._configuration = configuration; + this._configurationChanged.emit(configuration); + } + get configuration() { + return this._configuration; + } + get configurationChanged(): ISignal< + DatalayerConfiguration, + IDatalayerConfig | undefined + > { + return this._configurationChanged; + } +} diff --git a/src/state/index.ts b/src/state/index.ts new file mode 100644 index 0000000..22aeff1 --- /dev/null +++ b/src/state/index.ts @@ -0,0 +1,36 @@ +import { createStore } from 'zustand/vanilla'; +import { useStore } from 'zustand'; +import type { IDatalayerConfig } from '../jupyterlab/tokens'; + +export type DatalayerState = { + tab: number; + getIntTab: () => number; + setTab: (tab: number) => void; + + /** + * Global Datalayer configuration + */ + configuration?: IDatalayerConfig; + /** + * Set the global datalayer configuration + */ + setConfiguration: (configuration?: IDatalayerConfig) => void; +}; + +export const datalayerStore = createStore((set, get) => ({ + tab: 0.0, + getIntTab: () => Math.floor(get().tab), + setTab: (tab: number) => set((state: DatalayerState) => ({ tab })), + configuration: undefined, + setConfiguration: (configuration?: IDatalayerConfig) => { + set(state => ({ configuration })); + } +})); + +export function useDatalayerStore(): DatalayerState; +export function useDatalayerStore(selector: (state: DatalayerState) => T): T; +export function useDatalayerStore(selector?: (state: DatalayerState) => T) { + return useStore(datalayerStore, selector!); +} + +export default useDatalayerStore; diff --git a/src/state/zustand.ts b/src/state/zustand.ts deleted file mode 100644 index 5f58c49..0000000 --- a/src/state/zustand.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { create } from 'zustand'; - -export type ZustandState = { - tab: number; - getIntTab: () => number; - setTab: (tab: number) => void, -} - -export const useStore = create((set, get) => ({ - tab: 0.0, - getIntTab: () => Math.floor(get().tab), - setTab: (tab: number) => set((state: ZustandState) => ({ tab })), -})); - -export default useStore; diff --git a/src/tabs/JupyterLabTab.tsx b/src/tabs/JupyterLabTab.tsx index f3a2b91..ca3a9e3 100644 --- a/src/tabs/JupyterLabTab.tsx +++ b/src/tabs/JupyterLabTab.tsx @@ -1,6 +1,13 @@ import { - NetworkIcon, WidgetsIcon, PlusIcon, IpyWidgetsIcon, ShuffleIcon, - SettingsIcon, FileIcon, HardDriveIcon, JupyterServerIcon + NetworkIcon, + WidgetsIcon, + PlusIcon, + IpyWidgetsIcon, + ShuffleIcon, + SettingsIcon, + FileIcon, + HardDriveIcon, + JupyterServerIcon } from '@datalayer/icons-react'; import { Box, NavList } from '@primer/react'; import { DatalayerProps } from '../Datalayer'; @@ -13,90 +20,121 @@ import WidgetExtensions from './jupyterlab/WidgetExtensions'; import Settings from './jupyterlab/Settings'; import IPyWidgets from './jupyterlab/IPyWidgets'; import Server from './jupyterlab/Server'; -import useStore from '../state/zustand'; +import useStore from '../state'; const JupyterLabTab = (props: DatalayerProps) => { const { jupyterFrontend } = props; const { tab, setTab } = useStore(); return ( <> - + - *': { - paddingTop: '0px' - } - }}> - setTab(0.0)}> + *': { + paddingTop: '0px' + } + }} + > + setTab(0.0)} + > Plugins - setTab(0.1)}> + setTab(0.1)} + > File Types - setTab(0.2)}> + setTab(0.2)} + > - + Models - setTab(0.3)}> + setTab(0.3)} + > - + Drives - setTab(0.4)}> + setTab(0.4)} + > - + Widgets - setTab(0.5)}> + setTab(0.5)} + > - + Widget Extensions - setTab(0.6)}> + setTab(0.6)} + > - + IPyWidgets - setTab(0.7)}> + setTab(0.7)} + > - + Settings - setTab(0.8)}> + setTab(0.8)} + > - + Server - - {(tab === 0.0) && } - {(tab === 0.1) && } - {(tab === 0.2) && } - {(tab === 0.3) && } - {(tab === 0.4) && } - {(tab === 0.5) && } - {(tab === 0.6) && } - {(tab === 0.7) && } - {(tab === 0.8) && } + + {tab === 0.0 && } + {tab === 0.1 && } + {tab === 0.2 && } + {tab === 0.3 && } + {tab === 0.4 && } + {tab === 0.5 && ( + + )} + {tab === 0.6 && } + {tab === 0.7 && } + {tab === 0.8 && } ); -} +}; export default JupyterLabTab; diff --git a/tsconfig.json b/tsconfig.json index 7b71968..86c7d21 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,7 +8,6 @@ "**/node_modules" ], "compilerOptions": { - "baseUrl": "./", "allowSyntheticDefaultImports": true, "composite": true, "declaration": true,