From 16eb75a7ca40f7df51df109e8a72a3116504a928 Mon Sep 17 00:00:00 2001 From: telamonian Date: Tue, 30 Jul 2019 23:42:31 -0400 Subject: [PATCH 01/24] adding overrides for all of the theme font sizes --- .../apputils-extension/schema/themes.json | 18 ++++++++++ packages/apputils-extension/src/index.ts | 5 ++- packages/apputils/src/thememanager.ts | 33 +++++++++++++++++++ 3 files changed, 55 insertions(+), 1 deletion(-) diff --git a/packages/apputils-extension/schema/themes.json b/packages/apputils-extension/schema/themes.json index e00dfafe1b55..1bfbdc9460bc 100644 --- a/packages/apputils-extension/schema/themes.json +++ b/packages/apputils-extension/schema/themes.json @@ -15,6 +15,24 @@ "title": "Scrollbar Theming", "description": "Enable/disable styling of the application scrollbars", "default": false + }, + "code-font-size": { + "type": "number", + "title": "Code font size", + "description": "Set the code font size. Defaults to the `--jp-code-font-size` CSS variable from the theme.`", + "default": null + }, + "content-font-size": { + "type": "number", + "title": "Content font size", + "description": "Set the content font size. Defaults to the `--jp-content-font-size1` CSS variable from the theme.`", + "default": null + }, + "ui-font-size": { + "type": "number", + "title": "UI font size", + "description": "Set the UI font size. Defaults to the `--jp-ui-font-size1` CSS variable from the theme.`", + "default": null } }, "type": "object" diff --git a/packages/apputils-extension/src/index.ts b/packages/apputils-extension/src/index.ts index c07def7b84f6..dda1d90e1f5c 100644 --- a/packages/apputils-extension/src/index.ts +++ b/packages/apputils-extension/src/index.ts @@ -129,8 +129,8 @@ const themes: JupyterFrontEndPlugin = { // can lead to an incorrect toggle on the currently used theme. let currentTheme: string; - // Set data attributes on the application shell for the current theme. manager.themeChanged.connect((sender, args) => { + // Set data attributes on the application shell for the current theme. currentTheme = args.newValue; document.body.dataset.jpThemeLight = String( manager.isLight(currentTheme) @@ -144,6 +144,9 @@ const themes: JupyterFrontEndPlugin = { manager.themeScrollbars(currentTheme) ); } + + // Set any CSS overrides + commands.notifyCommandChanged(CommandIDs.changeTheme); }); diff --git a/packages/apputils/src/thememanager.ts b/packages/apputils/src/thememanager.ts index 1e03f66f882b..c7f18afbd30a 100644 --- a/packages/apputils/src/thememanager.ts +++ b/packages/apputils/src/thememanager.ts @@ -134,6 +134,39 @@ export class ThemeManager implements IThemeManager { return this._themes[name].isLight; } + get codeFontSize(): number { + return ( + (this._settings.composite['code-font-size'] as number) || + parseFloat( + getComputedStyle(document.documentElement).getPropertyValue( + '---jp-code-font-size' + ) + ) + ); + } + + get contentFontSize(): number { + return ( + (this._settings.composite['content-font-size'] as number) || + parseFloat( + getComputedStyle(document.documentElement).getPropertyValue( + '--jp-content-font-size1' + ) + ) + ); + } + + get uiFontSize(): number { + return ( + (this._settings.composite['ui-font-size'] as number) || + parseFloat( + getComputedStyle(document.documentElement).getPropertyValue( + '--jp-ui-font-size1' + ) + ) + ); + } + /** * Test whether a given theme styles scrollbars, * and if the user has scrollbar styling enabled. From f64325f301baafae4b92e3eb327edd29abda38fd Mon Sep 17 00:00:00 2001 From: telamonian Date: Wed, 31 Jul 2019 02:36:28 -0400 Subject: [PATCH 02/24] the font size settings now work --- .../apputils-extension/schema/themes.json | 12 ++--- packages/apputils-extension/src/index.ts | 4 ++ packages/apputils/src/thememanager.ts | 46 ++++++++----------- 3 files changed, 30 insertions(+), 32 deletions(-) diff --git a/packages/apputils-extension/schema/themes.json b/packages/apputils-extension/schema/themes.json index 1bfbdc9460bc..da059ba57e9c 100644 --- a/packages/apputils-extension/schema/themes.json +++ b/packages/apputils-extension/schema/themes.json @@ -17,22 +17,22 @@ "default": false }, "code-font-size": { - "type": "number", + "type": "string", "title": "Code font size", "description": "Set the code font size. Defaults to the `--jp-code-font-size` CSS variable from the theme.`", - "default": null + "default": "" }, "content-font-size": { - "type": "number", + "type": "string", "title": "Content font size", "description": "Set the content font size. Defaults to the `--jp-content-font-size1` CSS variable from the theme.`", - "default": null + "default": "" }, "ui-font-size": { - "type": "number", + "type": "string", "title": "UI font size", "description": "Set the UI font size. Defaults to the `--jp-ui-font-size1` CSS variable from the theme.`", - "default": null + "default": "" } }, "type": "object" diff --git a/packages/apputils-extension/src/index.ts b/packages/apputils-extension/src/index.ts index dda1d90e1f5c..219abf8cc663 100644 --- a/packages/apputils-extension/src/index.ts +++ b/packages/apputils-extension/src/index.ts @@ -146,6 +146,10 @@ const themes: JupyterFrontEndPlugin = { } // Set any CSS overrides + for (let key in ThemeManager.fontVars) { + // Set the font size overrides + manager.setFontSize(key, ThemeManager.fontVars[key]); + } commands.notifyCommandChanged(CommandIDs.changeTheme); }); diff --git a/packages/apputils/src/thememanager.ts b/packages/apputils/src/thememanager.ts index c7f18afbd30a..1f692b785a91 100644 --- a/packages/apputils/src/thememanager.ts +++ b/packages/apputils/src/thememanager.ts @@ -134,36 +134,24 @@ export class ThemeManager implements IThemeManager { return this._themes[name].isLight; } - get codeFontSize(): number { - return ( - (this._settings.composite['code-font-size'] as number) || - parseFloat( - getComputedStyle(document.documentElement).getPropertyValue( - '---jp-code-font-size' - ) - ) - ); - } - - get contentFontSize(): number { + /** + * Get a font size from the current theme, or the override + * setting if it exists + */ + getFontSize(settingsKey: string, cssKey: string): string { return ( - (this._settings.composite['content-font-size'] as number) || - parseFloat( - getComputedStyle(document.documentElement).getPropertyValue( - '--jp-content-font-size1' - ) - ) + (this._settings.composite[settingsKey] as string) || + getComputedStyle(document.documentElement).getPropertyValue(cssKey) ); } - get uiFontSize(): number { - return ( - (this._settings.composite['ui-font-size'] as number) || - parseFloat( - getComputedStyle(document.documentElement).getPropertyValue( - '--jp-ui-font-size1' - ) - ) + /** + * Set a font size based on the return from getFontSize + */ + setFontSize(settingsKey: string, cssKey: string): void { + document.documentElement.style.setProperty( + cssKey, + this.getFontSize(settingsKey, cssKey) ); } @@ -350,6 +338,12 @@ export namespace ThemeManager { */ url: string; } + + export const fontVars: { [key: string]: string } = { + 'code-font-size': '--jp-code-font-size', + 'content-font-size': '--jp-content-font-size1', + 'ui-font-size': '--jp-ui-font-size1' + }; } /** From 9f32ff4c6319b6a211e6fddac372ba33ed5b9742 Mon Sep 17 00:00:00 2001 From: telamonian Date: Wed, 31 Jul 2019 03:21:21 -0400 Subject: [PATCH 03/24] setting font size to empty now correctly restores original theme size --- packages/apputils/src/thememanager.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/apputils/src/thememanager.ts b/packages/apputils/src/thememanager.ts index 1f692b785a91..cc97667bf96e 100644 --- a/packages/apputils/src/thememanager.ts +++ b/packages/apputils/src/thememanager.ts @@ -139,10 +139,7 @@ export class ThemeManager implements IThemeManager { * setting if it exists */ getFontSize(settingsKey: string, cssKey: string): string { - return ( - (this._settings.composite[settingsKey] as string) || - getComputedStyle(document.documentElement).getPropertyValue(cssKey) - ); + return (this._settings.composite[settingsKey] as string) || `initial`; } /** From a00df0be720a985809dfdf4e73e5d533ae528d6b Mon Sep 17 00:00:00 2001 From: telamonian Date: Fri, 2 Aug 2019 20:32:37 -0400 Subject: [PATCH 04/24] added arbitrary theme CSS overrides to settings --- .../apputils-extension/schema/themes.json | 43 +++++++++++-------- packages/apputils-extension/src/index.ts | 5 +-- packages/apputils/src/thememanager.ts | 41 ++++++++++++------ 3 files changed, 54 insertions(+), 35 deletions(-) diff --git a/packages/apputils-extension/schema/themes.json b/packages/apputils-extension/schema/themes.json index da059ba57e9c..989a9bd5fcf1 100644 --- a/packages/apputils-extension/schema/themes.json +++ b/packages/apputils-extension/schema/themes.json @@ -2,6 +2,7 @@ "title": "Theme", "jupyter.lab.setting-icon-label": "Theme Manager", "description": "Theme manager settings.", + "type": "object", "additionalProperties": false, "properties": { "theme": { @@ -16,24 +17,30 @@ "description": "Enable/disable styling of the application scrollbars", "default": false }, - "code-font-size": { - "type": "string", - "title": "Code font size", - "description": "Set the code font size. Defaults to the `--jp-code-font-size` CSS variable from the theme.`", - "default": "" - }, - "content-font-size": { - "type": "string", - "title": "Content font size", - "description": "Set the content font size. Defaults to the `--jp-content-font-size1` CSS variable from the theme.`", - "default": "" - }, - "ui-font-size": { - "type": "string", - "title": "UI font size", - "description": "Set the UI font size. Defaults to the `--jp-ui-font-size1` CSS variable from the theme.`", - "default": "" + "overrides": { + "title": "Theme CSS Overrides", + "description": "The list of theme CSS overrides", + "items": { "$ref": "#/definitions/theme-override" }, + "type": "array", + "default": [] } }, - "type": "object" + "definitions": { + "theme-override": { + "required": ["key", "value"], + "type": "object", + "properties": { + "key": { + "type": "string", + "title": "Theme CSS variable key", + "description": "The name of a theme CSS variable, minus the leading `--jp-`" + }, + "value": { + "type": "string", + "title": "Theme CSS override value", + "description": "An empty string or a valid CSS value, in which case it is assigned to the CSS variable corresponding to `key`" + } + } + } + } } diff --git a/packages/apputils-extension/src/index.ts b/packages/apputils-extension/src/index.ts index 219abf8cc663..ee07a7265eb9 100644 --- a/packages/apputils-extension/src/index.ts +++ b/packages/apputils-extension/src/index.ts @@ -146,10 +146,7 @@ const themes: JupyterFrontEndPlugin = { } // Set any CSS overrides - for (let key in ThemeManager.fontVars) { - // Set the font size overrides - manager.setFontSize(key, ThemeManager.fontVars[key]); - } + manager.setOverrides(); commands.notifyCommandChanged(CommandIDs.changeTheme); }); diff --git a/packages/apputils/src/thememanager.ts b/packages/apputils/src/thememanager.ts index cc97667bf96e..3fd4770e0d53 100644 --- a/packages/apputils/src/thememanager.ts +++ b/packages/apputils/src/thememanager.ts @@ -5,6 +5,8 @@ import { IChangedArgs, ISettingRegistry, URLExt } from '@jupyterlab/coreutils'; import { each } from '@phosphor/algorithm'; +import { ReadonlyJSONArray } from '@phosphor/coreutils'; + import { DisposableDelegate, IDisposable } from '@phosphor/disposable'; import { Widget } from '@phosphor/widgets'; @@ -27,6 +29,9 @@ const REQUEST_INTERVAL = 75; */ const REQUEST_THRESHOLD = 20; +type Dict = { [key: string]: T }; +type Pair = { key: string; value: T }; + /** * A class that provides theme management. */ @@ -134,22 +139,31 @@ export class ThemeManager implements IThemeManager { return this._themes[name].isLight; } - /** - * Get a font size from the current theme, or the override - * setting if it exists - */ - getFontSize(settingsKey: string, cssKey: string): string { - return (this._settings.composite[settingsKey] as string) || `initial`; + setOverride(key: string) { + document.documentElement.style.setProperty( + `--jp-${key}`, + this._overrides[key] || 'initial' + ); } - /** - * Set a font size based on the return from getFontSize - */ - setFontSize(settingsKey: string, cssKey: string): void { - document.documentElement.style.setProperty( - cssKey, - this.getFontSize(settingsKey, cssKey) + setOverrides() { + let newOverrides: Dict = {}; + (this._settings.composite['overrides'] as ReadonlyJSONArray).forEach( + (x: Pair) => { + newOverrides[x.key] = x.value; + } ); + Object.keys(this._overrides).forEach(key => { + if (!(key in newOverrides)) { + // unset the override + this.setOverride(key); + } + }); + this._overrides = newOverrides; + + Object.keys(this._overrides).forEach(key => { + this.setOverride(key); + }); } /** @@ -296,6 +310,7 @@ export class ThemeManager implements IThemeManager { private _current: string | null = null; private _host: Widget; private _links: HTMLLinkElement[] = []; + private _overrides: Dict = {}; private _outstanding: Promise | null = null; private _pending = 0; private _requests: { [theme: string]: number } = {}; From b6291f1896eac3c87a696c86cf6abcff0a61fa81 Mon Sep 17 00:00:00 2001 From: telamonian Date: Fri, 2 Aug 2019 23:18:25 -0400 Subject: [PATCH 05/24] simplified syntax of theme override settings --- .../apputils-extension/schema/themes.json | 19 +++-------- packages/apputils-extension/src/index.ts | 2 +- packages/apputils/src/thememanager.ts | 32 +++++++++---------- 3 files changed, 20 insertions(+), 33 deletions(-) diff --git a/packages/apputils-extension/schema/themes.json b/packages/apputils-extension/schema/themes.json index 989a9bd5fcf1..ad227efbd6f4 100644 --- a/packages/apputils-extension/schema/themes.json +++ b/packages/apputils-extension/schema/themes.json @@ -20,26 +20,15 @@ "overrides": { "title": "Theme CSS Overrides", "description": "The list of theme CSS overrides", - "items": { "$ref": "#/definitions/theme-override" }, - "type": "array", - "default": [] + "$ref": "#/definitions/theme-overrides" } }, "definitions": { - "theme-override": { - "required": ["key", "value"], + "theme-overrides": { "type": "object", "properties": { - "key": { - "type": "string", - "title": "Theme CSS variable key", - "description": "The name of a theme CSS variable, minus the leading `--jp-`" - }, - "value": { - "type": "string", - "title": "Theme CSS override value", - "description": "An empty string or a valid CSS value, in which case it is assigned to the CSS variable corresponding to `key`" - } + "code-font-size": { "type": "string", "default": "" }, + "ui-font-size1": { "type": "string", "default": "" } } } } diff --git a/packages/apputils-extension/src/index.ts b/packages/apputils-extension/src/index.ts index ee07a7265eb9..3bd0f61ccbaa 100644 --- a/packages/apputils-extension/src/index.ts +++ b/packages/apputils-extension/src/index.ts @@ -146,7 +146,7 @@ const themes: JupyterFrontEndPlugin = { } // Set any CSS overrides - manager.setOverrides(); + manager.setCssOverrides(); commands.notifyCommandChanged(CommandIDs.changeTheme); }); diff --git a/packages/apputils/src/thememanager.ts b/packages/apputils/src/thememanager.ts index 3fd4770e0d53..eb9d5487da91 100644 --- a/packages/apputils/src/thememanager.ts +++ b/packages/apputils/src/thememanager.ts @@ -5,8 +5,6 @@ import { IChangedArgs, ISettingRegistry, URLExt } from '@jupyterlab/coreutils'; import { each } from '@phosphor/algorithm'; -import { ReadonlyJSONArray } from '@phosphor/coreutils'; - import { DisposableDelegate, IDisposable } from '@phosphor/disposable'; import { Widget } from '@phosphor/widgets'; @@ -30,7 +28,7 @@ const REQUEST_INTERVAL = 75; const REQUEST_THRESHOLD = 20; type Dict = { [key: string]: T }; -type Pair = { key: string; value: T }; +// type Pair = { key: string; value: T }; /** * A class that provides theme management. @@ -139,31 +137,31 @@ export class ThemeManager implements IThemeManager { return this._themes[name].isLight; } - setOverride(key: string) { + setCssOverride(key: string) { + const overrides = (this._settings.user['overrides'] as Dict) || {}; + document.documentElement.style.setProperty( `--jp-${key}`, - this._overrides[key] || 'initial' + overrides[key] || 'initial' ); } - setOverrides() { - let newOverrides: Dict = {}; - (this._settings.composite['overrides'] as ReadonlyJSONArray).forEach( - (x: Pair) => { - newOverrides[x.key] = x.value; - } - ); + setCssOverrides() { + const newOverrides = + (this._settings.user['overrides'] as Dict) || {}; + Object.keys(this._overrides).forEach(key => { if (!(key in newOverrides)) { // unset the override - this.setOverride(key); + this.setCssOverride(key); } }); - this._overrides = newOverrides; - - Object.keys(this._overrides).forEach(key => { - this.setOverride(key); + Object.keys(newOverrides).forEach(key => { + // set the override + this.setCssOverride(key); }); + + this._overrides = newOverrides; } /** From c39a4ce9b7790076e0a6de99ff6b8983fdc18976 Mon Sep 17 00:00:00 2001 From: telamonian Date: Sat, 3 Aug 2019 00:31:20 -0400 Subject: [PATCH 06/24] default theme override settings --- .../apputils-extension/schema/themes.json | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/packages/apputils-extension/schema/themes.json b/packages/apputils-extension/schema/themes.json index ad227efbd6f4..19a92f6fa464 100644 --- a/packages/apputils-extension/schema/themes.json +++ b/packages/apputils-extension/schema/themes.json @@ -4,6 +4,16 @@ "description": "Theme manager settings.", "type": "object", "additionalProperties": false, + "definitions": { + "themeOverrides": { + "type": "object", + "properties": { + "code-font-size": { "type": "string", "default": "" }, + "content-font-size1": { "type": "string", "default": "" }, + "ui-font-size1": { "type": "string", "default": "" } + } + } + }, "properties": { "theme": { "type": "string", @@ -19,16 +29,12 @@ }, "overrides": { "title": "Theme CSS Overrides", - "description": "The list of theme CSS overrides", - "$ref": "#/definitions/theme-overrides" - } - }, - "definitions": { - "theme-overrides": { - "type": "object", - "properties": { - "code-font-size": { "type": "string", "default": "" }, - "ui-font-size1": { "type": "string", "default": "" } + "description": "Override theme CSS variables by setting key-value pairs here", + "$ref": "#/definitions/themeOverrides", + "default": { + "code-font-size": "", + "content-font-size1": "", + "ui-font-size1": "" } } } From b1d4db7307cad395c304c0d8ceba0fc6d3b1fc8c Mon Sep 17 00:00:00 2001 From: telamonian Date: Wed, 7 Aug 2019 16:32:45 -0400 Subject: [PATCH 07/24] added theme font size incr/decr items to settings menu --- .../apputils-extension/schema/themes.json | 6 +- packages/apputils-extension/src/index.ts | 68 +++++++++++++- packages/apputils/src/thememanager.ts | 94 ++++++++++++++----- 3 files changed, 139 insertions(+), 29 deletions(-) diff --git a/packages/apputils-extension/schema/themes.json b/packages/apputils-extension/schema/themes.json index 19a92f6fa464..22ed4bf5e124 100644 --- a/packages/apputils-extension/schema/themes.json +++ b/packages/apputils-extension/schema/themes.json @@ -8,9 +8,9 @@ "themeOverrides": { "type": "object", "properties": { - "code-font-size": { "type": "string", "default": "" }, - "content-font-size1": { "type": "string", "default": "" }, - "ui-font-size1": { "type": "string", "default": "" } + "code-font-size": { "type": "string" }, + "content-font-size1": { "type": "string" }, + "ui-font-size1": { "type": "string" } } } }, diff --git a/packages/apputils-extension/src/index.ts b/packages/apputils-extension/src/index.ts index 3bd0f61ccbaa..d2a19f25def9 100644 --- a/packages/apputils-extension/src/index.ts +++ b/packages/apputils-extension/src/index.ts @@ -55,6 +55,12 @@ const SPLASH_RECOVER_TIMEOUT = 12000; namespace CommandIDs { export const changeTheme = 'apputils:change-theme'; + export const themeScrollbars = 'apputils:theme-scrollbars'; + + export const incrFontSize = 'apputils:incr-font-size'; + + export const decrFontSize = 'apputils:decr-font-size'; + export const loadState = 'apputils:load-statedb'; export const print = 'apputils:print'; @@ -146,7 +152,7 @@ const themes: JupyterFrontEndPlugin = { } // Set any CSS overrides - manager.setCssOverrides(); + manager.loadCssOverrides(); commands.notifyCommandChanged(CommandIDs.changeTheme); }); @@ -166,6 +172,22 @@ const themes: JupyterFrontEndPlugin = { } }); + commands.addCommand(CommandIDs.themeScrollbars, { + label: 'Theme Scrollbars', + isToggled: () => manager.themeScrollbars(currentTheme), + execute: () => manager.toggleThemeScrollbars() + }); + + commands.addCommand(CommandIDs.incrFontSize, { + label: args => `Increase ${args['label']} Font Size`, + execute: args => manager.incrFontSize(args['key'] as string) + }); + + commands.addCommand(CommandIDs.decrFontSize, { + label: args => `Decrease ${args['label']} Font Size`, + execute: args => manager.decrFontSize(args['key'] as string) + }); + return manager; }, autoStart: true, @@ -197,11 +219,51 @@ const themesPaletteMenu: JupyterFrontEndPlugin = { const themeMenu = new Menu({ commands }); themeMenu.title.label = 'JupyterLab Theme'; void app.restored.then(() => { - const command = CommandIDs.changeTheme; const isPalette = false; + // choose a theme manager.themes.forEach(theme => { - themeMenu.addItem({ command, args: { isPalette, theme } }); + themeMenu.addItem({ + command: CommandIDs.changeTheme, + args: { isPalette, theme } + }); + }); + themeMenu.addItem({ type: 'separator' }); + + // theme scrollbars + themeMenu.addItem({ command: CommandIDs.themeScrollbars }); + themeMenu.addItem({ type: 'separator' }); + + // increase/decrease code font size + themeMenu.addItem({ + command: CommandIDs.incrFontSize, + args: { label: 'Code', key: 'code-font-size' } + }); + themeMenu.addItem({ + command: CommandIDs.decrFontSize, + args: { label: 'Code', key: 'code-font-size' } + }); + themeMenu.addItem({ type: 'separator' }); + + // increase/decrease content font size + themeMenu.addItem({ + command: CommandIDs.incrFontSize, + args: { label: 'Content', key: 'content-font-size1' } + }); + themeMenu.addItem({ + command: CommandIDs.decrFontSize, + args: { label: 'Content', key: 'content-font-size1' } + }); + themeMenu.addItem({ type: 'separator' }); + + // increase/decrease ui font size + themeMenu.addItem({ + command: CommandIDs.incrFontSize, + args: { label: 'UI', key: 'ui-font-size1' } + }); + themeMenu.addItem({ + command: CommandIDs.decrFontSize, + args: { label: 'UI', key: 'ui-font-size1' } }); }); mainMenu.settingsMenu.addGroup( diff --git a/packages/apputils/src/thememanager.ts b/packages/apputils/src/thememanager.ts index eb9d5487da91..046b5d5f33e5 100644 --- a/packages/apputils/src/thememanager.ts +++ b/packages/apputils/src/thememanager.ts @@ -101,6 +101,43 @@ export class ThemeManager implements IThemeManager { }); } + /** + * Load a CSS override from settings. If no corresponding override + * is found, this function unloads the override instead. + * + * @param key - A Jupyterlab CSS variable, without the leading '--jp-'. + */ + loadCssOverride(key: string): void { + const overrides = (this._settings.user['overrides'] as Dict) || {}; + + document.documentElement.style.setProperty( + `--jp-${key}`, + overrides[key] || 'initial' + ); + } + + /** + * Loads all current CSS overrides from settings. If an override has been + * removed, this function unloads it instead. + */ + loadCssOverrides(): void { + const newOverrides = + (this._settings.user['overrides'] as Dict) || {}; + + Object.keys(this._overrides).forEach(key => { + if (!(key in newOverrides)) { + // unset the override + this.loadCssOverride(key); + } + }); + Object.keys(newOverrides).forEach(key => { + // set the override + this.loadCssOverride(key); + }); + + this._overrides = newOverrides; + } + /** * Register a theme with the theme manager. * @@ -137,31 +174,32 @@ export class ThemeManager implements IThemeManager { return this._themes[name].isLight; } - setCssOverride(key: string) { - const overrides = (this._settings.user['overrides'] as Dict) || {}; - - document.documentElement.style.setProperty( - `--jp-${key}`, - overrides[key] || 'initial' - ); + /** + * Increase a font size w.r.t. its current setting or its value in the + * current theme. + * + * @param key - A Jupyterlab font size CSS variable, + * without the leading '--jp-'. + */ + incrFontSize(key: string): Promise { + const parts = (this._overrides[key] || '1em').split(/([a-zA-Z]+)/); + this._overrides[key] = `${Number(parts[0]) + + (parts[1] === 'em' ? 0.1 : 1)}${parts[1]}`; + return this._settings.set('overrides', this._overrides); } - setCssOverrides() { - const newOverrides = - (this._settings.user['overrides'] as Dict) || {}; - - Object.keys(this._overrides).forEach(key => { - if (!(key in newOverrides)) { - // unset the override - this.setCssOverride(key); - } - }); - Object.keys(newOverrides).forEach(key => { - // set the override - this.setCssOverride(key); - }); - - this._overrides = newOverrides; + /** + * Decrease a font size w.r.t. its current setting or its value in the + * current theme. + * + * @param key - A Jupyterlab font size CSS variable, + * without the leading '--jp-'. + */ + decrFontSize(key: string): Promise { + const parts = (this._overrides[key] || '1em').split(/([a-zA-Z]+)/); + this._overrides[key] = `${Number(parts[0]) - + (parts[1] === 'em' ? 0.1 : 1)}${parts[1]}`; + return this._settings.set('overrides', this._overrides); } /** @@ -175,6 +213,16 @@ export class ThemeManager implements IThemeManager { ); } + /** + * Toggle the `theme-scrollbbars` setting. + */ + toggleThemeScrollbars(): void { + this._settings.set( + 'theme-scrollbars', + !this._settings.composite['theme-scrollbars'] + ); + } + /** * Handle the current settings. */ From c118399ba57ff4626050abad4e64933e57135b10 Mon Sep 17 00:00:00 2001 From: telamonian Date: Wed, 7 Aug 2019 16:47:37 -0400 Subject: [PATCH 08/24] theme palette items now have parity with theme settings menu items --- packages/apputils-extension/src/index.ts | 42 ++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/packages/apputils-extension/src/index.ts b/packages/apputils-extension/src/index.ts index d2a19f25def9..8378f02c6de0 100644 --- a/packages/apputils-extension/src/index.ts +++ b/packages/apputils-extension/src/index.ts @@ -230,7 +230,7 @@ const themesPaletteMenu: JupyterFrontEndPlugin = { }); themeMenu.addItem({ type: 'separator' }); - // theme scrollbars + // toggle scrollbar theming themeMenu.addItem({ command: CommandIDs.themeScrollbars }); themeMenu.addItem({ type: 'separator' }); @@ -280,13 +280,51 @@ const themesPaletteMenu: JupyterFrontEndPlugin = { // If we have a command palette, add theme switching options to it. if (palette) { void app.restored.then(() => { - const category = 'Settings'; + const category = 'Theme'; const command = CommandIDs.changeTheme; const isPalette = true; + // choose a theme manager.themes.forEach(theme => { palette.addItem({ command, args: { isPalette, theme }, category }); }); + + // toggle scrollbar theming + palette.addItem({ command: CommandIDs.themeScrollbars, category }); + + // increase/decrease code font size + palette.addItem({ + command: CommandIDs.incrFontSize, + args: { label: 'Code', key: 'code-font-size' }, + category + }); + palette.addItem({ + command: CommandIDs.decrFontSize, + args: { label: 'Code', key: 'code-font-size' }, + category + }); + // increase/decrease content font size + palette.addItem({ + command: CommandIDs.incrFontSize, + args: { label: 'Content', key: 'content-font-size1' }, + category + }); + palette.addItem({ + command: CommandIDs.decrFontSize, + args: { label: 'Content', key: 'content-font-size1' }, + category + }); + // increase/decrease ui font size + palette.addItem({ + command: CommandIDs.incrFontSize, + args: { label: 'UI', key: 'ui-font-size1' }, + category + }); + palette.addItem({ + command: CommandIDs.decrFontSize, + args: { label: 'UI', key: 'ui-font-size1' }, + category + }); }); } }, From 9ef802ff22a8c07def1f8067b03bcb64cb615025 Mon Sep 17 00:00:00 2001 From: telamonian Date: Wed, 7 Aug 2019 17:37:53 -0400 Subject: [PATCH 09/24] cleanup --- packages/apputils/src/thememanager.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/packages/apputils/src/thememanager.ts b/packages/apputils/src/thememanager.ts index 046b5d5f33e5..c3b26a30187b 100644 --- a/packages/apputils/src/thememanager.ts +++ b/packages/apputils/src/thememanager.ts @@ -396,12 +396,6 @@ export namespace ThemeManager { */ url: string; } - - export const fontVars: { [key: string]: string } = { - 'code-font-size': '--jp-code-font-size', - 'content-font-size': '--jp-content-font-size1', - 'ui-font-size': '--jp-ui-font-size1' - }; } /** From 2488d07eae93c6547e0fb273aa3cc641e82e5b85 Mon Sep 17 00:00:00 2001 From: telamonian Date: Tue, 13 Aug 2019 18:01:35 -0400 Subject: [PATCH 10/24] improved unsettinng of CSS overrides --- packages/apputils/src/thememanager.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/apputils/src/thememanager.ts b/packages/apputils/src/thememanager.ts index c3b26a30187b..15c52d8e1702 100644 --- a/packages/apputils/src/thememanager.ts +++ b/packages/apputils/src/thememanager.ts @@ -110,10 +110,11 @@ export class ThemeManager implements IThemeManager { loadCssOverride(key: string): void { const overrides = (this._settings.user['overrides'] as Dict) || {}; - document.documentElement.style.setProperty( - `--jp-${key}`, - overrides[key] || 'initial' - ); + if (overrides[key]) { + document.documentElement.style.setProperty(`--jp-${key}`, overrides[key]); + } else { + document.documentElement.style.removeProperty(`--jp-${key}`); + } } /** From fc406235186eb705ef57cac56571e7438d1bcfcb Mon Sep 17 00:00:00 2001 From: telamonian Date: Tue, 13 Aug 2019 18:05:06 -0400 Subject: [PATCH 11/24] added `"additionalProperties": false` to CSS overrides schema --- packages/apputils-extension/schema/themes.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/apputils-extension/schema/themes.json b/packages/apputils-extension/schema/themes.json index 22ed4bf5e124..c9ee31f43ace 100644 --- a/packages/apputils-extension/schema/themes.json +++ b/packages/apputils-extension/schema/themes.json @@ -7,6 +7,7 @@ "definitions": { "themeOverrides": { "type": "object", + "additionalProperties": false, "properties": { "code-font-size": { "type": "string" }, "content-font-size1": { "type": "string" }, From c2db5bde6b9f71d6270aa018f67ed42392383294 Mon Sep 17 00:00:00 2001 From: telamonian Date: Tue, 13 Aug 2019 19:31:26 -0400 Subject: [PATCH 12/24] ran `jlpm run lint`, fixed complaints hopefully this fixes the failure of the Linux Integrity CI --- packages/apputils/src/thememanager.ts | 4 ++-- .../statusbar/src/defaults/kernelStatus.tsx | 17 +++++++++++------ 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/packages/apputils/src/thememanager.ts b/packages/apputils/src/thememanager.ts index 15c52d8e1702..1f744227b338 100644 --- a/packages/apputils/src/thememanager.ts +++ b/packages/apputils/src/thememanager.ts @@ -217,8 +217,8 @@ export class ThemeManager implements IThemeManager { /** * Toggle the `theme-scrollbbars` setting. */ - toggleThemeScrollbars(): void { - this._settings.set( + toggleThemeScrollbars(): Promise { + return this._settings.set( 'theme-scrollbars', !this._settings.composite['theme-scrollbars'] ); diff --git a/packages/statusbar/src/defaults/kernelStatus.tsx b/packages/statusbar/src/defaults/kernelStatus.tsx index 23a6f0f59b80..0d76fa145d87 100644 --- a/packages/statusbar/src/defaults/kernelStatus.tsx +++ b/packages/statusbar/src/defaults/kernelStatus.tsx @@ -181,12 +181,17 @@ export namespace KernelStatus { const oldState = this._getAllState(); const { newValue } = change; if (newValue !== null) { - newValue.getSpec().then(spec => { - // sync setting of status and display name - this._kernelStatus = newValue.status; - this._kernelName = spec.display_name; - this._triggerChange(oldState, this._getAllState()); - }); + newValue + .getSpec() + .then(spec => { + // sync setting of status and display name + this._kernelStatus = newValue.status; + this._kernelName = spec.display_name; + this._triggerChange(oldState, this._getAllState()); + }) + .catch(err => { + throw err; + }); } else { this._kernelStatus = 'unknown'; this._kernelName = 'unknown'; From b87c4d081baa435a7531f7c6d1f37c7911f749b1 Mon Sep 17 00:00:00 2001 From: telamonian Date: Tue, 13 Aug 2019 21:23:00 -0400 Subject: [PATCH 13/24] cleanup/simplification --- packages/apputils/src/thememanager.ts | 36 +++++++++------------------ 1 file changed, 12 insertions(+), 24 deletions(-) diff --git a/packages/apputils/src/thememanager.ts b/packages/apputils/src/thememanager.ts index 1f744227b338..dd0fe37dd93c 100644 --- a/packages/apputils/src/thememanager.ts +++ b/packages/apputils/src/thememanager.ts @@ -101,22 +101,6 @@ export class ThemeManager implements IThemeManager { }); } - /** - * Load a CSS override from settings. If no corresponding override - * is found, this function unloads the override instead. - * - * @param key - A Jupyterlab CSS variable, without the leading '--jp-'. - */ - loadCssOverride(key: string): void { - const overrides = (this._settings.user['overrides'] as Dict) || {}; - - if (overrides[key]) { - document.documentElement.style.setProperty(`--jp-${key}`, overrides[key]); - } else { - document.documentElement.style.removeProperty(`--jp-${key}`); - } - } - /** * Loads all current CSS overrides from settings. If an override has been * removed, this function unloads it instead. @@ -125,17 +109,21 @@ export class ThemeManager implements IThemeManager { const newOverrides = (this._settings.user['overrides'] as Dict) || {}; - Object.keys(this._overrides).forEach(key => { - if (!(key in newOverrides)) { - // unset the override - this.loadCssOverride(key); + // iterate over the union of current and new CSS override keys + Object.keys({ ...this._overrides, ...newOverrides }).forEach(key => { + if (newOverrides[key]) { + // if the key is present in newOverrides, the override will be set + document.documentElement.style.setProperty( + `--jp-${key}`, + newOverrides[key] + ); + } else { + // otherwise, the override will be removed + document.documentElement.style.removeProperty(`--jp-${key}`); } }); - Object.keys(newOverrides).forEach(key => { - // set the override - this.loadCssOverride(key); - }); + // replace the current overrides with the new ones this._overrides = newOverrides; } From 8065460b6376a9e93f6d34b666d34927aa194ac8 Mon Sep 17 00:00:00 2001 From: telamonian Date: Tue, 13 Aug 2019 22:00:08 -0400 Subject: [PATCH 14/24] improved incrFontSize to work with non-default font sizes in themes --- .../apputils-extension/schema/themes.json | 12 +++--- packages/apputils-extension/src/index.ts | 2 +- packages/apputils/src/thememanager.ts | 42 +++++++++++++++---- 3 files changed, 40 insertions(+), 16 deletions(-) diff --git a/packages/apputils-extension/schema/themes.json b/packages/apputils-extension/schema/themes.json index c9ee31f43ace..965b0079eef9 100644 --- a/packages/apputils-extension/schema/themes.json +++ b/packages/apputils-extension/schema/themes.json @@ -9,9 +9,9 @@ "type": "object", "additionalProperties": false, "properties": { - "code-font-size": { "type": "string" }, - "content-font-size1": { "type": "string" }, - "ui-font-size1": { "type": "string" } + "code-font-size": { "type": ["string", "null"] }, + "content-font-size1": { "type": ["string", "null"] }, + "ui-font-size1": { "type": ["string", "null"] } } } }, @@ -33,9 +33,9 @@ "description": "Override theme CSS variables by setting key-value pairs here", "$ref": "#/definitions/themeOverrides", "default": { - "code-font-size": "", - "content-font-size1": "", - "ui-font-size1": "" + "code-font-size": null, + "content-font-size1": null, + "ui-font-size1": null } } } diff --git a/packages/apputils-extension/src/index.ts b/packages/apputils-extension/src/index.ts index 8378f02c6de0..47577aa8c662 100644 --- a/packages/apputils-extension/src/index.ts +++ b/packages/apputils-extension/src/index.ts @@ -152,7 +152,7 @@ const themes: JupyterFrontEndPlugin = { } // Set any CSS overrides - manager.loadCssOverrides(); + manager.loadCSSOverrides(); commands.notifyCommandChanged(CommandIDs.changeTheme); }); diff --git a/packages/apputils/src/thememanager.ts b/packages/apputils/src/thememanager.ts index dd0fe37dd93c..dd3b0564883a 100644 --- a/packages/apputils/src/thememanager.ts +++ b/packages/apputils/src/thememanager.ts @@ -73,6 +73,17 @@ export class ThemeManager implements IThemeManager { return this._themeChanged; } + /** + * Get the value of a CSS variable from its key. + * + * @param key - A Jupyterlab CSS variable, without the leading '--jp-'. + */ + getCSS(key: string): string { + return getComputedStyle(document.documentElement).getPropertyValue( + `--jp-${key}` + ); + } + /** * Load a theme CSS file by path. * @@ -105,7 +116,7 @@ export class ThemeManager implements IThemeManager { * Loads all current CSS overrides from settings. If an override has been * removed, this function unloads it instead. */ - loadCssOverrides(): void { + loadCSSOverrides(): void { const newOverrides = (this._settings.user['overrides'] as Dict) || {}; @@ -171,10 +182,7 @@ export class ThemeManager implements IThemeManager { * without the leading '--jp-'. */ incrFontSize(key: string): Promise { - const parts = (this._overrides[key] || '1em').split(/([a-zA-Z]+)/); - this._overrides[key] = `${Number(parts[0]) + - (parts[1] === 'em' ? 0.1 : 1)}${parts[1]}`; - return this._settings.set('overrides', this._overrides); + return this._incrFontSize(key, true); } /** @@ -185,10 +193,7 @@ export class ThemeManager implements IThemeManager { * without the leading '--jp-'. */ decrFontSize(key: string): Promise { - const parts = (this._overrides[key] || '1em').split(/([a-zA-Z]+)/); - this._overrides[key] = `${Number(parts[0]) - - (parts[1] === 'em' ? 0.1 : 1)}${parts[1]}`; - return this._settings.set('overrides', this._overrides); + return this._incrFontSize(key, false); } /** @@ -212,6 +217,25 @@ export class ThemeManager implements IThemeManager { ); } + /** + * Change a font size by a positive or negative increment. + */ + private _incrFontSize(key: string, add: boolean = true): Promise { + // get the numeric and unit parts of the current font size + const parts = (this._overrides[key] || this.getCSS(key) || '13px').split( + /([a-zA-Z]+)/ + ); + + // determine the increment + const incr = parts[1] === 'em' ? 0.1 : 1; + + // increment the font size and set it as an override + this._overrides[key] = `${Number(parts[0]) + (add ? incr : -incr)}${ + parts[1] + }`; + return this._settings.set('overrides', this._overrides); + } + /** * Handle the current settings. */ From 92e809303ba784ef5af63d81f67540134ad557df Mon Sep 17 00:00:00 2001 From: telamonian Date: Tue, 13 Aug 2019 22:06:15 -0400 Subject: [PATCH 15/24] cleanup --- packages/apputils/src/thememanager.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/apputils/src/thememanager.ts b/packages/apputils/src/thememanager.ts index dd3b0564883a..696d2048657a 100644 --- a/packages/apputils/src/thememanager.ts +++ b/packages/apputils/src/thememanager.ts @@ -227,12 +227,10 @@ export class ThemeManager implements IThemeManager { ); // determine the increment - const incr = parts[1] === 'em' ? 0.1 : 1; + const incr = (add ? 1 : -1) * (parts[1] === 'em' ? 0.1 : 1); // increment the font size and set it as an override - this._overrides[key] = `${Number(parts[0]) + (add ? incr : -incr)}${ - parts[1] - }`; + this._overrides[key] = `${Number(parts[0]) + incr}${parts[1]}`; return this._settings.set('overrides', this._overrides); } From d40dddfe468142d7e8bca761ca4f37e6c1595bde Mon Sep 17 00:00:00 2001 From: telamonian Date: Tue, 13 Aug 2019 22:15:06 -0400 Subject: [PATCH 16/24] more cleanup --- packages/apputils-extension/schema/themes.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/apputils-extension/schema/themes.json b/packages/apputils-extension/schema/themes.json index 965b0079eef9..52a46d7cf7d9 100644 --- a/packages/apputils-extension/schema/themes.json +++ b/packages/apputils-extension/schema/themes.json @@ -5,7 +5,7 @@ "type": "object", "additionalProperties": false, "definitions": { - "themeOverrides": { + "cssOverrides": { "type": "object", "additionalProperties": false, "properties": { @@ -31,7 +31,7 @@ "overrides": { "title": "Theme CSS Overrides", "description": "Override theme CSS variables by setting key-value pairs here", - "$ref": "#/definitions/themeOverrides", + "$ref": "#/definitions/cssOverrides", "default": { "code-font-size": null, "content-font-size1": null, From b3f36ad20f575c2a82889b388494f19517c75740 Mon Sep 17 00:00:00 2001 From: telamonian Date: Wed, 14 Aug 2019 15:44:56 -0400 Subject: [PATCH 17/24] added validation of CSS override values --- packages/apputils/src/thememanager.ts | 84 +++++++++++++++++++++------ 1 file changed, 67 insertions(+), 17 deletions(-) diff --git a/packages/apputils/src/thememanager.ts b/packages/apputils/src/thememanager.ts index 696d2048657a..80d4b1892a29 100644 --- a/packages/apputils/src/thememanager.ts +++ b/packages/apputils/src/thememanager.ts @@ -28,7 +28,6 @@ const REQUEST_INTERVAL = 75; const REQUEST_THRESHOLD = 20; type Dict = { [key: string]: T }; -// type Pair = { key: string; value: T }; /** * A class that provides theme management. @@ -114,7 +113,7 @@ export class ThemeManager implements IThemeManager { /** * Loads all current CSS overrides from settings. If an override has been - * removed, this function unloads it instead. + * removed or is invalid, this function unloads it instead. */ loadCSSOverrides(): void { const newOverrides = @@ -123,13 +122,18 @@ export class ThemeManager implements IThemeManager { // iterate over the union of current and new CSS override keys Object.keys({ ...this._overrides, ...newOverrides }).forEach(key => { if (newOverrides[key]) { - // if the key is present in newOverrides, the override will be set - document.documentElement.style.setProperty( - `--jp-${key}`, - newOverrides[key] - ); + // if key is present in newOverrides, validate then set the override + if (ThemeManager.validateCSS(key, newOverrides[key])) { + document.documentElement.style.setProperty( + `--jp-${key}`, + newOverrides[key] + ); + } else { + // if validation failed, the override will be removed + document.documentElement.style.removeProperty(`--jp-${key}`); + } } else { - // otherwise, the override will be removed + // if key is not present, the override will be removed document.documentElement.style.removeProperty(`--jp-${key}`); } }); @@ -160,6 +164,14 @@ export class ThemeManager implements IThemeManager { }); } + /** + * Add a CSS override to the settings. + */ + setCSSOverride(key: string, value: string): Promise { + this._overrides[key] = value; + return this._settings.set('overrides', this._overrides); + } + /** * Set the current theme. */ @@ -178,8 +190,7 @@ export class ThemeManager implements IThemeManager { * Increase a font size w.r.t. its current setting or its value in the * current theme. * - * @param key - A Jupyterlab font size CSS variable, - * without the leading '--jp-'. + * @param key - A Jupyterlab font size CSS variable, without the leading '--jp-'. */ incrFontSize(key: string): Promise { return this._incrFontSize(key, true); @@ -189,8 +200,7 @@ export class ThemeManager implements IThemeManager { * Decrease a font size w.r.t. its current setting or its value in the * current theme. * - * @param key - A Jupyterlab font size CSS variable, - * without the leading '--jp-'. + * @param key - A Jupyterlab font size CSS variable, without the leading '--jp-'. */ decrFontSize(key: string): Promise { return this._incrFontSize(key, false); @@ -222,16 +232,13 @@ export class ThemeManager implements IThemeManager { */ private _incrFontSize(key: string, add: boolean = true): Promise { // get the numeric and unit parts of the current font size - const parts = (this._overrides[key] || this.getCSS(key) || '13px').split( - /([a-zA-Z]+)/ - ); + const parts = (this.getCSS(key) || '13px').split(/([a-zA-Z]+)/); // determine the increment const incr = (add ? 1 : -1) * (parts[1] === 'em' ? 0.1 : 1); // increment the font size and set it as an override - this._overrides[key] = `${Number(parts[0]) + incr}${parts[1]}`; - return this._settings.set('overrides', this._overrides); + return this.setCSSOverride(key, `${Number(parts[0]) + incr}${parts[1]}`); } /** @@ -407,6 +414,49 @@ export namespace ThemeManager { */ url: string; } + + /** + * Some basic CSS properties, corresponding to the naming + * conventions of theme CSS variables + */ + const cssProps = ['color', 'font-family', 'size']; + + /** + * Validate a CSS value w.r.t. a key + * + * @param key - A Jupyterlab CSS variable, without the leading '--jp-'. + * + * @param val - A candidate CSS value + */ + export const validateCSS = (key: string, val: string): boolean => { + // determine the css property corresponding to the key + let prop: string; + for (const p of cssProps) { + if (key.includes(p)) { + prop = p; + break; + } + } + + if (!prop) { + console.warn( + 'CSS validation failed: could not find property corresponding to key.\n' + + `key: '${key}', val: '${val}'` + ); + return false; + } + + // use built-in validation once we have the corresponding property + if (CSS.supports(prop, val)) { + return true; + } else { + console.warn( + 'CSS validation failed: invalid value.\n' + + `key: '${key}', val: '${val}', prop: '${prop}'` + ); + return false; + } + }; } /** From 45bca49a4d898f10306c1bfd09a40dea295bc759 Mon Sep 17 00:00:00 2001 From: telamonian Date: Wed, 14 Aug 2019 16:27:34 -0400 Subject: [PATCH 18/24] simplified some code --- packages/apputils/src/thememanager.ts | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/packages/apputils/src/thememanager.ts b/packages/apputils/src/thememanager.ts index 80d4b1892a29..f85763760ebc 100644 --- a/packages/apputils/src/thememanager.ts +++ b/packages/apputils/src/thememanager.ts @@ -121,19 +121,13 @@ export class ThemeManager implements IThemeManager { // iterate over the union of current and new CSS override keys Object.keys({ ...this._overrides, ...newOverrides }).forEach(key => { - if (newOverrides[key]) { - // if key is present in newOverrides, validate then set the override - if (ThemeManager.validateCSS(key, newOverrides[key])) { - document.documentElement.style.setProperty( - `--jp-${key}`, - newOverrides[key] - ); - } else { - // if validation failed, the override will be removed - document.documentElement.style.removeProperty(`--jp-${key}`); - } + const val = newOverrides[key]; + + if (val && ThemeManager.validateCSS(key, val)) { + // validation succeeded, set the override + document.documentElement.style.setProperty(`--jp-${key}`, val); } else { - // if key is not present, the override will be removed + // if key is not present or validation failed, the override will be removed document.documentElement.style.removeProperty(`--jp-${key}`); } }); From 8cb310869314b6684f6ae2386381d1dfb2956664 Mon Sep 17 00:00:00 2001 From: telamonian Date: Wed, 14 Aug 2019 23:40:05 -0400 Subject: [PATCH 19/24] improved validation of CSS override values --- .../apputils-extension/schema/themes.json | 24 +++-- packages/apputils/src/thememanager.ts | 90 ++++++++++--------- 2 files changed, 57 insertions(+), 57 deletions(-) diff --git a/packages/apputils-extension/schema/themes.json b/packages/apputils-extension/schema/themes.json index 52a46d7cf7d9..d51093657a63 100644 --- a/packages/apputils-extension/schema/themes.json +++ b/packages/apputils-extension/schema/themes.json @@ -4,17 +4,6 @@ "description": "Theme manager settings.", "type": "object", "additionalProperties": false, - "definitions": { - "cssOverrides": { - "type": "object", - "additionalProperties": false, - "properties": { - "code-font-size": { "type": ["string", "null"] }, - "content-font-size1": { "type": ["string", "null"] }, - "ui-font-size1": { "type": ["string", "null"] } - } - } - }, "properties": { "theme": { "type": "string", @@ -29,9 +18,18 @@ "default": false }, "overrides": { + "type": "object", + "additionalProperties": false, "title": "Theme CSS Overrides", - "description": "Override theme CSS variables by setting key-value pairs here", - "$ref": "#/definitions/cssOverrides", + "description": "Override theme CSS variables by setting key-value pairs here. The description field of each item is the CSS property that will be used to validate an override's value.", + "properties": { + "code-font-size": { "type": ["string", "null"], "description": "size" }, + "content-font-size1": { + "type": ["string", "null"], + "description": "size" + }, + "ui-font-size1": { "type": ["string", "null"], "description": "size" } + }, "default": { "code-font-size": null, "content-font-size1": null, diff --git a/packages/apputils/src/thememanager.ts b/packages/apputils/src/thememanager.ts index f85763760ebc..dad65cc5116f 100644 --- a/packages/apputils/src/thememanager.ts +++ b/packages/apputils/src/thememanager.ts @@ -48,6 +48,7 @@ export class ThemeManager implements IThemeManager { this._settings = settings; this._settings.changed.connect(this._loadSettings, this); this._loadSettings(); + this._initOverrideProps(); }); } @@ -76,6 +77,8 @@ export class ThemeManager implements IThemeManager { * Get the value of a CSS variable from its key. * * @param key - A Jupyterlab CSS variable, without the leading '--jp-'. + * + * @return value - The current value of the Jupyterlab CSS variable */ getCSS(key: string): string { return getComputedStyle(document.documentElement).getPropertyValue( @@ -123,7 +126,7 @@ export class ThemeManager implements IThemeManager { Object.keys({ ...this._overrides, ...newOverrides }).forEach(key => { const val = newOverrides[key]; - if (val && ThemeManager.validateCSS(key, val)) { + if (val && this.validateCSS(key, val)) { // validation succeeded, set the override document.documentElement.style.setProperty(`--jp-${key}`, val); } else { @@ -136,6 +139,37 @@ export class ThemeManager implements IThemeManager { this._overrides = newOverrides; } + /** + * Validate a CSS value w.r.t. a key + * + * @param key - A Jupyterlab CSS variable, without the leading '--jp-'. + * + * @param val - A candidate CSS value + */ + validateCSS(key: string, val: string): boolean { + // determine the css property corresponding to the key + const prop = this._overrideProps[key]; + + if (!prop) { + console.warn( + 'CSS validation failed: could not find property corresponding to key.\n' + + `key: '${key}', val: '${val}'` + ); + return false; + } + + // use built-in validation once we have the corresponding property + if (CSS.supports(prop, val)) { + return true; + } else { + console.warn( + 'CSS validation failed: invalid value.\n' + + `key: '${key}', val: '${val}', prop: '${prop}'` + ); + return false; + } + } + /** * Register a theme with the theme manager. * @@ -235,6 +269,16 @@ export class ThemeManager implements IThemeManager { return this.setCSSOverride(key, `${Number(parts[0]) + incr}${parts[1]}`); } + /** + * Initialize the key -> property dict for the overrides + */ + private _initOverrideProps(): void { + const oSchema: any = this._settings.schema.properties.overrides.properties; + Object.keys(oSchema).forEach(key => { + this._overrideProps[key] = oSchema[key].description; + }); + } + /** * Handle the current settings. */ @@ -369,6 +413,7 @@ export class ThemeManager implements IThemeManager { private _host: Widget; private _links: HTMLLinkElement[] = []; private _overrides: Dict = {}; + private _overrideProps: Dict = {}; private _outstanding: Promise | null = null; private _pending = 0; private _requests: { [theme: string]: number } = {}; @@ -408,49 +453,6 @@ export namespace ThemeManager { */ url: string; } - - /** - * Some basic CSS properties, corresponding to the naming - * conventions of theme CSS variables - */ - const cssProps = ['color', 'font-family', 'size']; - - /** - * Validate a CSS value w.r.t. a key - * - * @param key - A Jupyterlab CSS variable, without the leading '--jp-'. - * - * @param val - A candidate CSS value - */ - export const validateCSS = (key: string, val: string): boolean => { - // determine the css property corresponding to the key - let prop: string; - for (const p of cssProps) { - if (key.includes(p)) { - prop = p; - break; - } - } - - if (!prop) { - console.warn( - 'CSS validation failed: could not find property corresponding to key.\n' + - `key: '${key}', val: '${val}'` - ); - return false; - } - - // use built-in validation once we have the corresponding property - if (CSS.supports(prop, val)) { - return true; - } else { - console.warn( - 'CSS validation failed: invalid value.\n' + - `key: '${key}', val: '${val}', prop: '${prop}'` - ); - return false; - } - }; } /** From d6c2bfc6aedf8b25a875637f7d6c9de1d62ad588 Mon Sep 17 00:00:00 2001 From: telamonian Date: Thu, 15 Aug 2019 01:01:18 -0400 Subject: [PATCH 20/24] had to rearrange some json in order to get the defaults to display correctly the default theme override keys weren't showing up in the last commit. For some reason rearranging the JSON fixed it. --- .../apputils-extension/schema/themes.json | 27 +++++++++++-------- packages/apputils/src/thememanager.ts | 6 ++++- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/packages/apputils-extension/schema/themes.json b/packages/apputils-extension/schema/themes.json index d51093657a63..9f2a53af9db7 100644 --- a/packages/apputils-extension/schema/themes.json +++ b/packages/apputils-extension/schema/themes.json @@ -4,6 +4,20 @@ "description": "Theme manager settings.", "type": "object", "additionalProperties": false, + "definitions": { + "cssOverrides": { + "type": "object", + "additionalProperties": false, + "properties": { + "code-font-size": { "type": ["string", "null"], "description": "size" }, + "content-font-size1": { + "type": ["string", "null"], + "description": "size" + }, + "ui-font-size1": { "type": ["string", "null"], "description": "size" } + } + } + }, "properties": { "theme": { "type": "string", @@ -18,18 +32,9 @@ "default": false }, "overrides": { - "type": "object", - "additionalProperties": false, "title": "Theme CSS Overrides", - "description": "Override theme CSS variables by setting key-value pairs here. The description field of each item is the CSS property that will be used to validate an override's value.", - "properties": { - "code-font-size": { "type": ["string", "null"], "description": "size" }, - "content-font-size1": { - "type": ["string", "null"], - "description": "size" - }, - "ui-font-size1": { "type": ["string", "null"], "description": "size" } - }, + "description": "Override theme CSS variables by setting key-value pairs here", + "$ref": "#/definitions/cssOverrides", "default": { "code-font-size": null, "content-font-size1": null, diff --git a/packages/apputils/src/thememanager.ts b/packages/apputils/src/thememanager.ts index dad65cc5116f..b2cf9dd02856 100644 --- a/packages/apputils/src/thememanager.ts +++ b/packages/apputils/src/thememanager.ts @@ -273,7 +273,11 @@ export class ThemeManager implements IThemeManager { * Initialize the key -> property dict for the overrides */ private _initOverrideProps(): void { - const oSchema: any = this._settings.schema.properties.overrides.properties; + const oSchema = (this._settings.schema.definitions as any).cssOverrides + .properties; + + // the description field of each item in the overrides schema stores a + // CSS property that will be used to validate that override's values Object.keys(oSchema).forEach(key => { this._overrideProps[key] = oSchema[key].description; }); From 6bf371e66ca49737e8856d1d011aeb7806037fcc Mon Sep 17 00:00:00 2001 From: telamonian Date: Thu, 15 Aug 2019 01:36:05 -0400 Subject: [PATCH 21/24] added docstring --- packages/apputils-extension/schema/themes.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/apputils-extension/schema/themes.json b/packages/apputils-extension/schema/themes.json index 9f2a53af9db7..8597bf5e76b0 100644 --- a/packages/apputils-extension/schema/themes.json +++ b/packages/apputils-extension/schema/themes.json @@ -8,6 +8,7 @@ "cssOverrides": { "type": "object", "additionalProperties": false, + "description": "The description field of each item is the CSS property that will be used to validate an override's value", "properties": { "code-font-size": { "type": ["string", "null"], "description": "size" }, "content-font-size1": { From 54b9fc0173b632f27a68ad7378c5bf16b86c8b0c Mon Sep 17 00:00:00 2001 From: telamonian Date: Thu, 22 Aug 2019 16:50:08 -0400 Subject: [PATCH 22/24] fix for CSS font size validation in Firefox --- packages/apputils-extension/schema/themes.json | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/apputils-extension/schema/themes.json b/packages/apputils-extension/schema/themes.json index 8597bf5e76b0..e06a7a9412b6 100644 --- a/packages/apputils-extension/schema/themes.json +++ b/packages/apputils-extension/schema/themes.json @@ -10,12 +10,18 @@ "additionalProperties": false, "description": "The description field of each item is the CSS property that will be used to validate an override's value", "properties": { - "code-font-size": { "type": ["string", "null"], "description": "size" }, + "code-font-size": { + "type": ["string", "null"], + "description": "font-size" + }, "content-font-size1": { "type": ["string", "null"], - "description": "size" + "description": "font-size" }, - "ui-font-size1": { "type": ["string", "null"], "description": "size" } + "ui-font-size1": { + "type": ["string", "null"], + "description": "font-size" + } } } }, From 6e9dfd77bc21b25de0f625721a6695e88d7650f9 Mon Sep 17 00:00:00 2001 From: telamonian Date: Thu, 22 Aug 2019 17:19:11 -0400 Subject: [PATCH 23/24] partial fix for scrollbar theming toggle --- packages/application/style/scrollbar.css | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/application/style/scrollbar.css b/packages/application/style/scrollbar.css index fe92a9fccdaa..a8630d3c01d3 100644 --- a/packages/application/style/scrollbar.css +++ b/packages/application/style/scrollbar.css @@ -8,12 +8,13 @@ */ /* use standard opaque scrollbars for most nodes */ -div.jp-LabShell[data-jp-theme-scrollbars='true'] { +[data-jp-theme-scrollbars='true'] { scrollbar-color: rgb(var(--jp-scrollbar-thumb-color)) var(--jp-scrollbar-background-color); } -/* for code nodes, use a transparent style of scrollbar */ +/* for code nodes, use a transparent style of scrollbar. These selectors + * will match lower in the tree, and so will override the above */ [data-jp-theme-scrollbars='true'] .CodeMirror-hscrollbar, [data-jp-theme-scrollbars='true'] .CodeMirror-vscrollbar { scrollbar-color: rgba(var(--jp-scrollbar-thumb-color), 0.5) transparent; From 997b4fe12a894286779f4fd7eb159453909b7857 Mon Sep 17 00:00:00 2001 From: telamonian Date: Thu, 22 Aug 2019 17:50:01 -0400 Subject: [PATCH 24/24] fixed `isToggled` param of 'apputils:theme-scrollbars' command `apputils-extension/src/index.ts` was getting crowded, so I also split out the theme plugins into their own source file --- packages/apputils-extension/src/index.ts | 240 +---------------- .../apputils-extension/src/themeplugins.ts | 252 ++++++++++++++++++ packages/apputils/src/thememanager.ts | 7 + 3 files changed, 263 insertions(+), 236 deletions(-) create mode 100644 packages/apputils-extension/src/themeplugins.ts diff --git a/packages/apputils-extension/src/index.ts b/packages/apputils-extension/src/index.ts index 47577aa8c662..9f1dcdf11c74 100644 --- a/packages/apputils-extension/src/index.ts +++ b/packages/apputils-extension/src/index.ts @@ -14,9 +14,7 @@ import { Dialog, ICommandPalette, ISplashScreen, - IThemeManager, IWindowResolver, - ThemeManager, WindowResolver, Printing } from '@jupyterlab/apputils'; @@ -32,18 +30,16 @@ import { URLExt } from '@jupyterlab/coreutils'; -import { IMainMenu } from '@jupyterlab/mainmenu'; - import { defaultIconRegistry } from '@jupyterlab/ui-components'; import { PromiseDelegate } from '@phosphor/coreutils'; import { DisposableDelegate } from '@phosphor/disposable'; -import { Menu } from '@phosphor/widgets'; - import { Palette } from './palette'; +import { themesPlugin, themesPaletteMenuPlugin } from './themeplugins'; + /** * The interval in milliseconds before recover options appear during splash. */ @@ -53,14 +49,6 @@ const SPLASH_RECOVER_TIMEOUT = 12000; * The command IDs used by the apputils plugin. */ namespace CommandIDs { - export const changeTheme = 'apputils:change-theme'; - - export const themeScrollbars = 'apputils:theme-scrollbars'; - - export const incrFontSize = 'apputils:incr-font-size'; - - export const decrFontSize = 'apputils:decr-font-size'; - export const loadState = 'apputils:load-statedb'; export const print = 'apputils:print'; @@ -111,226 +99,6 @@ const settings: JupyterFrontEndPlugin = { provides: ISettingRegistry }; -/** - * The default theme manager provider. - */ -const themes: JupyterFrontEndPlugin = { - id: '@jupyterlab/apputils-extension:themes', - requires: [ISettingRegistry, JupyterFrontEnd.IPaths], - optional: [ISplashScreen], - activate: ( - app: JupyterFrontEnd, - settings: ISettingRegistry, - paths: JupyterFrontEnd.IPaths, - splash: ISplashScreen | null - ): IThemeManager => { - const host = app.shell; - const commands = app.commands; - const url = URLExt.join(paths.urls.base, paths.urls.themes); - const key = themes.id; - const manager = new ThemeManager({ key, host, settings, splash, url }); - - // Keep a synchronously set reference to the current theme, - // since the asynchronous setting of the theme in `changeTheme` - // can lead to an incorrect toggle on the currently used theme. - let currentTheme: string; - - manager.themeChanged.connect((sender, args) => { - // Set data attributes on the application shell for the current theme. - currentTheme = args.newValue; - document.body.dataset.jpThemeLight = String( - manager.isLight(currentTheme) - ); - document.body.dataset.jpThemeName = currentTheme; - if ( - document.body.dataset.jpThemeScrollbars !== - String(manager.themeScrollbars(currentTheme)) - ) { - document.body.dataset.jpThemeScrollbars = String( - manager.themeScrollbars(currentTheme) - ); - } - - // Set any CSS overrides - manager.loadCSSOverrides(); - - commands.notifyCommandChanged(CommandIDs.changeTheme); - }); - - commands.addCommand(CommandIDs.changeTheme, { - label: args => { - const theme = args['theme'] as string; - return args['isPalette'] ? `Use ${theme} Theme` : theme; - }, - isToggled: args => args['theme'] === currentTheme, - execute: args => { - const theme = args['theme'] as string; - if (theme === manager.theme) { - return; - } - return manager.setTheme(theme); - } - }); - - commands.addCommand(CommandIDs.themeScrollbars, { - label: 'Theme Scrollbars', - isToggled: () => manager.themeScrollbars(currentTheme), - execute: () => manager.toggleThemeScrollbars() - }); - - commands.addCommand(CommandIDs.incrFontSize, { - label: args => `Increase ${args['label']} Font Size`, - execute: args => manager.incrFontSize(args['key'] as string) - }); - - commands.addCommand(CommandIDs.decrFontSize, { - label: args => `Decrease ${args['label']} Font Size`, - execute: args => manager.decrFontSize(args['key'] as string) - }); - - return manager; - }, - autoStart: true, - provides: IThemeManager -}; - -/** - * The default theme manager's UI command palette and main menu functionality. - * - * #### Notes - * This plugin loads separately from the theme manager plugin in order to - * prevent blocking of the theme manager while it waits for the command palette - * and main menu to become available. - */ -const themesPaletteMenu: JupyterFrontEndPlugin = { - id: '@jupyterlab/apputils-extension:themes-palette-menu', - requires: [IThemeManager], - optional: [ICommandPalette, IMainMenu], - activate: ( - app: JupyterFrontEnd, - manager: IThemeManager, - palette: ICommandPalette | null, - mainMenu: IMainMenu | null - ): void => { - const commands = app.commands; - - // If we have a main menu, add the theme manager to the settings menu. - if (mainMenu) { - const themeMenu = new Menu({ commands }); - themeMenu.title.label = 'JupyterLab Theme'; - void app.restored.then(() => { - const isPalette = false; - - // choose a theme - manager.themes.forEach(theme => { - themeMenu.addItem({ - command: CommandIDs.changeTheme, - args: { isPalette, theme } - }); - }); - themeMenu.addItem({ type: 'separator' }); - - // toggle scrollbar theming - themeMenu.addItem({ command: CommandIDs.themeScrollbars }); - themeMenu.addItem({ type: 'separator' }); - - // increase/decrease code font size - themeMenu.addItem({ - command: CommandIDs.incrFontSize, - args: { label: 'Code', key: 'code-font-size' } - }); - themeMenu.addItem({ - command: CommandIDs.decrFontSize, - args: { label: 'Code', key: 'code-font-size' } - }); - themeMenu.addItem({ type: 'separator' }); - - // increase/decrease content font size - themeMenu.addItem({ - command: CommandIDs.incrFontSize, - args: { label: 'Content', key: 'content-font-size1' } - }); - themeMenu.addItem({ - command: CommandIDs.decrFontSize, - args: { label: 'Content', key: 'content-font-size1' } - }); - themeMenu.addItem({ type: 'separator' }); - - // increase/decrease ui font size - themeMenu.addItem({ - command: CommandIDs.incrFontSize, - args: { label: 'UI', key: 'ui-font-size1' } - }); - themeMenu.addItem({ - command: CommandIDs.decrFontSize, - args: { label: 'UI', key: 'ui-font-size1' } - }); - }); - mainMenu.settingsMenu.addGroup( - [ - { - type: 'submenu' as Menu.ItemType, - submenu: themeMenu - } - ], - 0 - ); - } - - // If we have a command palette, add theme switching options to it. - if (palette) { - void app.restored.then(() => { - const category = 'Theme'; - const command = CommandIDs.changeTheme; - const isPalette = true; - - // choose a theme - manager.themes.forEach(theme => { - palette.addItem({ command, args: { isPalette, theme }, category }); - }); - - // toggle scrollbar theming - palette.addItem({ command: CommandIDs.themeScrollbars, category }); - - // increase/decrease code font size - palette.addItem({ - command: CommandIDs.incrFontSize, - args: { label: 'Code', key: 'code-font-size' }, - category - }); - palette.addItem({ - command: CommandIDs.decrFontSize, - args: { label: 'Code', key: 'code-font-size' }, - category - }); - // increase/decrease content font size - palette.addItem({ - command: CommandIDs.incrFontSize, - args: { label: 'Content', key: 'content-font-size1' }, - category - }); - palette.addItem({ - command: CommandIDs.decrFontSize, - args: { label: 'Content', key: 'content-font-size1' }, - category - }); - // increase/decrease ui font size - palette.addItem({ - command: CommandIDs.incrFontSize, - args: { label: 'UI', key: 'ui-font-size1' }, - category - }); - palette.addItem({ - command: CommandIDs.decrFontSize, - args: { label: 'UI', key: 'ui-font-size1' }, - category - }); - }); - } - }, - autoStart: true -}; - /** * The default window name resolver provider. */ @@ -698,8 +466,8 @@ const plugins: JupyterFrontEndPlugin[] = [ settings, state, splash, - themes, - themesPaletteMenu, + themesPlugin, + themesPaletteMenuPlugin, print ]; export default plugins; diff --git a/packages/apputils-extension/src/themeplugins.ts b/packages/apputils-extension/src/themeplugins.ts new file mode 100644 index 000000000000..6ee4d3458aec --- /dev/null +++ b/packages/apputils-extension/src/themeplugins.ts @@ -0,0 +1,252 @@ +/*----------------------------------------------------------------------------- +| Copyright (c) Jupyter Development Team. +| Distributed under the terms of the Modified BSD License. +|----------------------------------------------------------------------------*/ + +import { + JupyterFrontEnd, + JupyterFrontEndPlugin +} from '@jupyterlab/application'; + +import { + ICommandPalette, + ISplashScreen, + IThemeManager, + ThemeManager +} from '@jupyterlab/apputils'; + +import { ISettingRegistry, URLExt } from '@jupyterlab/coreutils'; + +import { IMainMenu } from '@jupyterlab/mainmenu'; + +import { Menu } from '@phosphor/widgets'; + +namespace CommandIDs { + export const changeTheme = 'apputils:change-theme'; + + export const themeScrollbars = 'apputils:theme-scrollbars'; + + export const incrFontSize = 'apputils:incr-font-size'; + + export const decrFontSize = 'apputils:decr-font-size'; +} + +/** + * The default theme manager provider. + */ +export const themesPlugin: JupyterFrontEndPlugin = { + id: '@jupyterlab/apputils-extension:themes', + requires: [ISettingRegistry, JupyterFrontEnd.IPaths], + optional: [ISplashScreen], + activate: ( + app: JupyterFrontEnd, + settings: ISettingRegistry, + paths: JupyterFrontEnd.IPaths, + splash: ISplashScreen | null + ): IThemeManager => { + const host = app.shell; + const commands = app.commands; + const url = URLExt.join(paths.urls.base, paths.urls.themes); + const key = themesPlugin.id; + const manager = new ThemeManager({ key, host, settings, splash, url }); + + // Keep a synchronously set reference to the current theme, + // since the asynchronous setting of the theme in `changeTheme` + // can lead to an incorrect toggle on the currently used theme. + let currentTheme: string; + + manager.themeChanged.connect((sender, args) => { + // Set data attributes on the application shell for the current theme. + currentTheme = args.newValue; + document.body.dataset.jpThemeLight = String( + manager.isLight(currentTheme) + ); + document.body.dataset.jpThemeName = currentTheme; + if ( + document.body.dataset.jpThemeScrollbars !== + String(manager.themeScrollbars(currentTheme)) + ) { + document.body.dataset.jpThemeScrollbars = String( + manager.themeScrollbars(currentTheme) + ); + } + + // Set any CSS overrides + manager.loadCSSOverrides(); + + commands.notifyCommandChanged(CommandIDs.changeTheme); + }); + + commands.addCommand(CommandIDs.changeTheme, { + label: args => { + const theme = args['theme'] as string; + return args['isPalette'] ? `Use ${theme} Theme` : theme; + }, + isToggled: args => args['theme'] === currentTheme, + execute: args => { + const theme = args['theme'] as string; + if (theme === manager.theme) { + return; + } + return manager.setTheme(theme); + } + }); + + commands.addCommand(CommandIDs.themeScrollbars, { + label: 'Theme Scrollbars', + isToggled: () => manager.isToggledThemeScrollbars(), + execute: () => manager.toggleThemeScrollbars() + }); + + commands.addCommand(CommandIDs.incrFontSize, { + label: args => `Increase ${args['label']} Font Size`, + execute: args => manager.incrFontSize(args['key'] as string) + }); + + commands.addCommand(CommandIDs.decrFontSize, { + label: args => `Decrease ${args['label']} Font Size`, + execute: args => manager.decrFontSize(args['key'] as string) + }); + + return manager; + }, + autoStart: true, + provides: IThemeManager +}; + +/** + * The default theme manager's UI command palette and main menu functionality. + * + * #### Notes + * This plugin loads separately from the theme manager plugin in order to + * prevent blocking of the theme manager while it waits for the command palette + * and main menu to become available. + */ +export const themesPaletteMenuPlugin: JupyterFrontEndPlugin = { + id: '@jupyterlab/apputils-extension:themes-palette-menu', + requires: [IThemeManager], + optional: [ICommandPalette, IMainMenu], + activate: ( + app: JupyterFrontEnd, + manager: IThemeManager, + palette: ICommandPalette | null, + mainMenu: IMainMenu | null + ): void => { + const commands = app.commands; + + // If we have a main menu, add the theme manager to the settings menu. + if (mainMenu) { + const themeMenu = new Menu({ commands }); + themeMenu.title.label = 'JupyterLab Theme'; + void app.restored.then(() => { + const isPalette = false; + + // choose a theme + manager.themes.forEach(theme => { + themeMenu.addItem({ + command: CommandIDs.changeTheme, + args: { isPalette, theme } + }); + }); + themeMenu.addItem({ type: 'separator' }); + + // toggle scrollbar theming + themeMenu.addItem({ command: CommandIDs.themeScrollbars }); + themeMenu.addItem({ type: 'separator' }); + + // increase/decrease code font size + themeMenu.addItem({ + command: CommandIDs.incrFontSize, + args: { label: 'Code', key: 'code-font-size' } + }); + themeMenu.addItem({ + command: CommandIDs.decrFontSize, + args: { label: 'Code', key: 'code-font-size' } + }); + themeMenu.addItem({ type: 'separator' }); + + // increase/decrease content font size + themeMenu.addItem({ + command: CommandIDs.incrFontSize, + args: { label: 'Content', key: 'content-font-size1' } + }); + themeMenu.addItem({ + command: CommandIDs.decrFontSize, + args: { label: 'Content', key: 'content-font-size1' } + }); + themeMenu.addItem({ type: 'separator' }); + + // increase/decrease ui font size + themeMenu.addItem({ + command: CommandIDs.incrFontSize, + args: { label: 'UI', key: 'ui-font-size1' } + }); + themeMenu.addItem({ + command: CommandIDs.decrFontSize, + args: { label: 'UI', key: 'ui-font-size1' } + }); + }); + mainMenu.settingsMenu.addGroup( + [ + { + type: 'submenu' as Menu.ItemType, + submenu: themeMenu + } + ], + 0 + ); + } + + // If we have a command palette, add theme switching options to it. + if (palette) { + void app.restored.then(() => { + const category = 'Theme'; + const command = CommandIDs.changeTheme; + const isPalette = true; + + // choose a theme + manager.themes.forEach(theme => { + palette.addItem({ command, args: { isPalette, theme }, category }); + }); + + // toggle scrollbar theming + palette.addItem({ command: CommandIDs.themeScrollbars, category }); + + // increase/decrease code font size + palette.addItem({ + command: CommandIDs.incrFontSize, + args: { label: 'Code', key: 'code-font-size' }, + category + }); + palette.addItem({ + command: CommandIDs.decrFontSize, + args: { label: 'Code', key: 'code-font-size' }, + category + }); + // increase/decrease content font size + palette.addItem({ + command: CommandIDs.incrFontSize, + args: { label: 'Content', key: 'content-font-size1' }, + category + }); + palette.addItem({ + command: CommandIDs.decrFontSize, + args: { label: 'Content', key: 'content-font-size1' }, + category + }); + // increase/decrease ui font size + palette.addItem({ + command: CommandIDs.incrFontSize, + args: { label: 'UI', key: 'ui-font-size1' }, + category + }); + palette.addItem({ + command: CommandIDs.decrFontSize, + args: { label: 'UI', key: 'ui-font-size1' }, + category + }); + }); + } + }, + autoStart: true +}; diff --git a/packages/apputils/src/thememanager.ts b/packages/apputils/src/thememanager.ts index b2cf9dd02856..97a41fa7682b 100644 --- a/packages/apputils/src/thememanager.ts +++ b/packages/apputils/src/thememanager.ts @@ -245,6 +245,13 @@ export class ThemeManager implements IThemeManager { ); } + /** + * Test if the user has scrollbar styling enabled. + */ + isToggledThemeScrollbars(): boolean { + return !!this._settings.composite['theme-scrollbars']; + } + /** * Toggle the `theme-scrollbbars` setting. */