From b64ca73c5009b63c2d9cb57d9991472193c7dc17 Mon Sep 17 00:00:00 2001 From: Duc Trung Le Date: Sun, 21 Dec 2025 22:48:12 +0100 Subject: [PATCH 1/2] new top bar style --- demo/files/blog.ipynb | 33 ++++++++++++++++----------------- demo/jupyter-lite.json | 8 ++------ schema/app-meta.json | 19 +++++++++++++++++++ src/components/icon/gear.tsx | 15 +++++++++++---- src/document/factory.tsx | 2 +- src/document/plugin.ts | 12 ++++++------ src/tool.ts | 24 ++++++++++++++++++++++-- src/topbar/widget.tsx | 6 +++--- style/base.css | 8 ++++++-- style/style.css | 5 +++-- 10 files changed, 89 insertions(+), 43 deletions(-) diff --git a/demo/files/blog.ipynb b/demo/files/blog.ipynb index c3ab19f..71f6d7b 100644 --- a/demo/files/blog.ipynb +++ b/demo/files/blog.ipynb @@ -389,23 +389,22 @@ } ], "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.13.5" - } + "kernelspec": { + "name": "xpython", + "display_name": "Python 3.13 (XPython)", + "language": "python" + }, + "language_info": { + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "version": "3.13.1" + }, + "specta": { + "hideTopbar": "No", + "slidesTheme": null, + "defaultLayout": "article" + } }, "nbformat": 4, "nbformat_minor": 5 diff --git a/demo/jupyter-lite.json b/demo/jupyter-lite.json index 4b4fe2a..ecfc610 100644 --- a/demo/jupyter-lite.json +++ b/demo/jupyter-lite.json @@ -9,12 +9,8 @@ "defaultLayout": "default", "perFileConfig": { "blog.ipynb": { - "hideTopbar": true, - "defaultLayout": "article", - "topBar": { - "title": "ipecharts blog", - "themeToggle": false - } + "hideTopbar": false, + "defaultLayout": "article" }, "slides.ipynb": { "hideTopbar": true, diff --git a/schema/app-meta.json b/schema/app-meta.json index 2b2417a..bceb2ee 100644 --- a/schema/app-meta.json +++ b/schema/app-meta.json @@ -9,6 +9,17 @@ "metadataSchema": { "type": "object", "properties": { + "/specta/topbarTitle": { + "title": "Top Bar Title", + "type": "string", + "default": "" + }, + "/specta/topbarThemeToggle": { + "title": "Top Bar Theme Toggle", + "type": "string", + "enum": ["Yes", "No"], + "default": "Yes" + }, "/specta/defaultLayout": { "title": "Page layout", "type": "string", @@ -55,6 +66,14 @@ "/specta/slidesTheme": { "metadataLevel": "notebook", "writeDefault": false + }, + "/specta/topbarTitle": { + "metadataLevel": "notebook", + "writeDefault": false + }, + "/specta/topbarThemeToggle": { + "metadataLevel": "notebook", + "writeDefault": false } } } diff --git a/src/components/icon/gear.tsx b/src/components/icon/gear.tsx index 34e9e32..618f496 100644 --- a/src/components/icon/gear.tsx +++ b/src/components/icon/gear.tsx @@ -3,11 +3,18 @@ import React from 'react'; export const GearIcon = ({ ...props }: React.SVGProps) => ( - + + + ); diff --git a/src/document/factory.tsx b/src/document/factory.tsx index 6af0e7c..353e8d3 100644 --- a/src/document/factory.tsx +++ b/src/document/factory.tsx @@ -59,7 +59,7 @@ export class NotebookGridWidgetFactory extends ABCWidgetFactory< } else { // Specta app, add topbar to layout topbar.id = 'specta-topbar-widget'; - this._shell.add(topbar, 'top'); + this._shell.add(topbar, 'top', { rank: 100 }); } } else if (isSpecta) { this._shell.hideTopBar(); diff --git a/src/document/plugin.ts b/src/document/plugin.ts index 1b72656..6bec53b 100644 --- a/src/document/plugin.ts +++ b/src/document/plugin.ts @@ -91,12 +91,11 @@ export const spectaOpener: JupyterFrontEndPlugin = { defaultBrowser: IDefaultFileBrowser ): Promise => { const urlParams = new URLSearchParams(window.location.search); - const path = urlParams.get('path'); if (!isSpectaApp()) { + // Not a specta app + const path = urlParams.get('specta-path'); + if (!path) { - app.restored.then(async () => { - await app.commands.execute('application:reset-layout'); - }); return; } app.restored.then(async () => { @@ -105,7 +104,7 @@ export const spectaOpener: JupyterFrontEndPlugin = { if (PathExt.extname(path) === '.ipynb') { const commands = app.commands; const spectaConfig = readSpectaConfig({}); - + console.log('spectaConfig', spectaConfig); await configLabLayout({ config: spectaConfig.labConfig, labShell, @@ -117,9 +116,10 @@ export const spectaOpener: JupyterFrontEndPlugin = { } } }); - // Not a specta app return; } else { + // Specta app + const path = urlParams.get('path'); if (!path) { const browser = createFileBrowser({ defaultBrowser }); app.shell.add(browser, 'main', { rank: 100 }); diff --git a/src/tool.ts b/src/tool.ts index 4fcac48..8592e31 100644 --- a/src/tool.ts +++ b/src/tool.ts @@ -165,11 +165,14 @@ export function readSpectaConfig({ pathWithoutDrive = paths[1]; } const { perFileConfig, ...globalConfig } = JSON.parse(rawConfig); + let spectaConfig: ISpectaAppConfig = { ...(globalConfig ?? {}) }; if (perFileConfig && pathWithoutDrive && perFileConfig[pathWithoutDrive]) { spectaConfig = { ...spectaConfig, ...perFileConfig[pathWithoutDrive] }; } + const spectaMetadata = JSON.parse(JSON.stringify(nbMetadata?.specta ?? {})); + if (spectaMetadata.hideTopbar === 'Yes') { spectaMetadata.hideTopbar = true; } else if (spectaMetadata.hideTopbar === 'No') { @@ -179,11 +182,28 @@ export function readSpectaConfig({ spectaConfig.hideTopbar === null || spectaConfig.hideTopbar === undefined ) { - // Hide topbar by default if not specified in the global config + // Show topbar by default if not specified in the global config // and notebook metadata - spectaMetadata.hideTopbar = true; + spectaMetadata.hideTopbar = false; + } + } + if (!spectaMetadata.hideTopbar) { + spectaMetadata.topBar = {}; + if (spectaMetadata.topbarTitle && spectaMetadata.topbarTitle !== '') { + spectaMetadata.topBar.title = spectaMetadata.topbarTitle; + delete spectaMetadata.topbarTitle; + } else if (!spectaConfig.topBar?.title) { + spectaMetadata.topBar.title = pathWithoutDrive; + } + if (spectaMetadata.topbarThemeToggle === 'No') { + spectaMetadata.topBar.themeToggle = false; + } else { + spectaMetadata.topBar.themeToggle = true; } + delete spectaMetadata.topbarThemeToggle; } + + // merge spectaConfig and spectaMetadata (spectaMetadata has higher priority return mergeObjects(spectaConfig, spectaMetadata); } diff --git a/src/topbar/widget.tsx b/src/topbar/widget.tsx index 85b10ce..7fa41b9 100644 --- a/src/topbar/widget.tsx +++ b/src/topbar/widget.tsx @@ -15,7 +15,7 @@ interface IProps { export function TopbarElement(props: IProps): JSX.Element { const config = React.useMemo((): ITopbarConfig => { return { - background: props.config?.background ?? 'var(--jp-layout-color2)', + background: props.config?.background ?? 'var(--jp-layout-color1)', title: props.config?.title ?? 'Specta', themeToggle: Boolean(props.config?.themeToggle), kernelActivity: Boolean(props.config?.kernelActivity), @@ -42,11 +42,11 @@ export function TopbarElement(props: IProps): JSX.Element { document.addEventListener('mousedown', handleClickOutside); return () => document.removeEventListener('mousedown', handleClickOutside); }, []); - + console.log('config.background ', config.background); return (
diff --git a/style/base.css b/style/base.css index 770e391..5c3d787 100644 --- a/style/base.css +++ b/style/base.css @@ -69,11 +69,13 @@ } #specta-top-panel { - min-height: 36px; + min-height: 28px; display: flex; box-shadow: unset !important; z-index: 100; contain: unset !important; + border-bottom: var(--jp-border-width) solid var(--jp-border-color0); + background: var(--jp-layout-color1); } .specta-topbar-element { @@ -84,7 +86,7 @@ .specta-topbar { display: flex; flex-direction: row; - height: 36px; + height: 28px; width: calc(100% + 20px); border-bottom: solid 0.5px var(--jp-border-color1); gap: 10px; @@ -128,7 +130,9 @@ border-radius: 50% !important; transition: background 0.2s ease; background: transparent; + padding: 0 !important; } + .specta-icon-button:hover { background-color: var('--jp-layout-color3'); } diff --git a/style/style.css b/style/style.css index 0666edc..2ea0e69 100644 --- a/style/style.css +++ b/style/style.css @@ -6,6 +6,7 @@ .specta-document-viewer #jp-main-content-panel jp-toolbar:first-of-type { display: none; + height: 0px !important; } .specta-file-browser { @@ -28,6 +29,6 @@ } .specta-main-content-panel { - padding-left: 5px; - padding-right: 5px; + padding-left: 0px; + padding-right: 0px; } From ca7aea0aede357b37284960b520eefb89689a294 Mon Sep 17 00:00:00 2001 From: Duc Trung Le Date: Sun, 21 Dec 2025 22:55:32 +0100 Subject: [PATCH 2/2] fix title handler --- src/tool.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/tool.ts b/src/tool.ts index 8592e31..8129b24 100644 --- a/src/tool.ts +++ b/src/tool.ts @@ -130,6 +130,7 @@ export function mergeObjects( for (const obj of objects) { for (const [key, value] of Object.entries(obj)) { if (value !== null && value !== undefined) { + console.log('setting', key, value); result[key] = value; } } @@ -167,8 +168,10 @@ export function readSpectaConfig({ const { perFileConfig, ...globalConfig } = JSON.parse(rawConfig); let spectaConfig: ISpectaAppConfig = { ...(globalConfig ?? {}) }; + let fileConfig: ISpectaAppConfig = {}; if (perFileConfig && pathWithoutDrive && perFileConfig[pathWithoutDrive]) { - spectaConfig = { ...spectaConfig, ...perFileConfig[pathWithoutDrive] }; + fileConfig = perFileConfig[pathWithoutDrive]; + spectaConfig = { ...spectaConfig, ...fileConfig }; } const spectaMetadata = JSON.parse(JSON.stringify(nbMetadata?.specta ?? {})); @@ -192,7 +195,7 @@ export function readSpectaConfig({ if (spectaMetadata.topbarTitle && spectaMetadata.topbarTitle !== '') { spectaMetadata.topBar.title = spectaMetadata.topbarTitle; delete spectaMetadata.topbarTitle; - } else if (!spectaConfig.topBar?.title) { + } else if (!fileConfig?.topBar?.title) { spectaMetadata.topBar.title = pathWithoutDrive; } if (spectaMetadata.topbarThemeToggle === 'No') {