diff --git a/src/webviews/apps/plus/graph/graph.tsx b/src/webviews/apps/plus/graph/graph.tsx index d18a70d..65caa3a 100644 --- a/src/webviews/apps/plus/graph/graph.tsx +++ b/src/webviews/apps/plus/graph/graph.tsx @@ -46,11 +46,12 @@ import { UpdateRefsVisibilityCommandType, UpdateSelectionCommandType, } from '../../../../plus/webviews/graph/protocol'; -import { mix, opacity } from '../../../../system/color'; +import { darken, lighten, mix, opacity } from '../../../../system/color'; import { debounce } from '../../../../system/function'; import type { IpcMessage, IpcNotificationType } from '../../../protocol'; import { onIpc } from '../../../protocol'; import { App } from '../../shared/appBase'; +import type { ThemeChangeEvent } from '../../shared/theme'; import { GraphWrapper } from './GraphWrapper'; import './graph.scss'; @@ -320,7 +321,46 @@ export class GraphApp extends App { } } - protected override onThemeUpdated() { + protected override onThemeUpdated(e: ThemeChangeEvent) { + const bodyStyle = document.body.style; + bodyStyle.setProperty('--graph-theme-opacity-factor', e.isLightTheme ? '0.5' : '1'); + + bodyStyle.setProperty( + '--color-graph-actionbar-background', + e.isLightTheme ? darken(e.colors.background, 5) : lighten(e.colors.background, 5), + ); + bodyStyle.setProperty( + '--color-graph-actionbar-selectedBackground', + e.isLightTheme ? darken(e.colors.background, 10) : lighten(e.colors.background, 10), + ); + + bodyStyle.setProperty( + '--color-graph-background', + e.isLightTheme ? darken(e.colors.background, 5) : lighten(e.colors.background, 5), + ); + bodyStyle.setProperty( + '--color-graph-background2', + e.isLightTheme ? darken(e.colors.background, 10) : lighten(e.colors.background, 10), + ); + let color = e.computedStyle.getPropertyValue('--vscode-list-focusOutline').trim(); + bodyStyle.setProperty('--color-graph-contrast-border-color', color); + color = e.computedStyle.getPropertyValue('--vscode-list-activeSelectionBackground').trim(); + bodyStyle.setProperty('--color-graph-selected-row', color); + color = e.computedStyle.getPropertyValue('--vscode-list-hoverBackground').trim(); + bodyStyle.setProperty('--color-graph-hover-row', color); + color = e.computedStyle.getPropertyValue('--vscode-list-activeSelectionForeground').trim(); + bodyStyle.setProperty('--color-graph-text-selected-row', color); + bodyStyle.setProperty('--color-graph-text-dimmed-selected', opacity(color, 50)); + bodyStyle.setProperty('--color-graph-text-dimmed', opacity(e.colors.foreground, 20)); + color = e.computedStyle.getPropertyValue('--vscode-list-hoverForeground').trim(); + bodyStyle.setProperty('--color-graph-text-hovered', color); + bodyStyle.setProperty('--color-graph-text-selected', e.colors.foreground); + bodyStyle.setProperty('--color-graph-text-normal', opacity(e.colors.foreground, 85)); + bodyStyle.setProperty('--color-graph-text-secondary', opacity(e.colors.foreground, 65)); + bodyStyle.setProperty('--color-graph-text-disabled', opacity(e.colors.foreground, 50)); + + if (e.isInitializing) return; + this.state.theming = undefined; this.setState(this.state, 'didChangeTheme'); } diff --git a/src/webviews/apps/shared/appBase.ts b/src/webviews/apps/shared/appBase.ts index 630bfbd..a18960c 100644 --- a/src/webviews/apps/shared/appBase.ts +++ b/src/webviews/apps/shared/appBase.ts @@ -10,6 +10,7 @@ import type { import { onIpc, WebviewFocusChangedCommandType, WebviewReadyCommandType } from '../../protocol'; import { DOM } from './dom'; import type { Disposable } from './events'; +import type { ThemeChangeEvent } from './theme'; import { initializeAndWatchThemeColors, onDidChangeTheme } from './theme'; interface VsCodeApi { @@ -46,10 +47,12 @@ export abstract class App { this._api = acquireVsCodeApi(); + const disposables: Disposable[] = []; + if (this.onThemeUpdated != null) { - onDidChangeTheme(this.onThemeUpdated, this); + disposables.push(onDidChangeTheme(this.onThemeUpdated, this)); } - initializeAndWatchThemeColors(); + disposables.push(initializeAndWatchThemeColors()); requestAnimationFrame(() => { this.log(`${this.appName}.initializing`); @@ -59,7 +62,7 @@ export abstract class App { this.bind(); if (this.onMessageReceived != null) { - window.addEventListener('message', this.onMessageReceived.bind(this)); + disposables.push(DOM.on(window, 'message', this.onMessageReceived.bind(this))); } this.sendCommand(WebviewReadyCommandType, undefined); @@ -73,13 +76,21 @@ export abstract class App { } } }); + + disposables.push( + DOM.on(window, 'pagehide', () => { + disposables?.forEach(d => d.dispose()); + this.bindDisposables?.forEach(d => d.dispose()); + this.bindDisposables = undefined; + }), + ); } protected onInitialize?(): void; protected onBind?(): Disposable[]; protected onInitialized?(): void; protected onMessageReceived?(e: MessageEvent): void; - protected onThemeUpdated?(): void; + protected onThemeUpdated?(e: ThemeChangeEvent): void; private _focused?: boolean; private _inputFocused?: boolean; diff --git a/src/webviews/apps/shared/theme.ts b/src/webviews/apps/shared/theme.ts index b6da142..c2edc99 100644 --- a/src/webviews/apps/shared/theme.ts +++ b/src/webviews/apps/shared/theme.ts @@ -1,19 +1,33 @@ /*global window document MutationObserver*/ import { darken, lighten, opacity } from '../../../system/color'; -import type { Event } from './events'; +import type { Disposable, Event } from './events'; import { Emitter } from './events'; -const _onDidChangeTheme = new Emitter(); -export const onDidChangeTheme: Event = _onDidChangeTheme.event; +export interface ThemeChangeEvent { + colors: { + background: string; + foreground: string; + }; + computedStyle: CSSStyleDeclaration; + + isLightTheme: boolean; + isHighContrastTheme: boolean; + + isInitializing: boolean; +} -export function initializeAndWatchThemeColors() { +const _onDidChangeTheme = new Emitter(); +export const onDidChangeTheme: Event = _onDidChangeTheme.event; + +export function initializeAndWatchThemeColors(): Disposable { const onColorThemeChanged = (mutations?: MutationRecord[]) => { const body = document.body; const computedStyle = window.getComputedStyle(body); const isLightTheme = body.classList.contains('vscode-light') || body.classList.contains('vscode-high-contrast-light'); - // const isHighContrastTheme = body.classList.contains('vscode-high-contrast'); + const isHighContrastTheme = + body.classList.contains('vscode-high-contrast') || body.classList.contains('vscode-high-contrast-light'); const bodyStyle = body.style; @@ -33,6 +47,11 @@ export function initializeAndWatchThemeColors() { const backgroundColor = computedStyle.getPropertyValue('--vscode-editor-background').trim(); + let foregroundColor = computedStyle.getPropertyValue('--vscode-editor-foreground').trim(); + if (!foregroundColor) { + foregroundColor = computedStyle.getPropertyValue('--vscode-foreground').trim(); + } + let color = backgroundColor; bodyStyle.setProperty('--color-background', color); bodyStyle.setProperty('--color-background--lighten-05', lighten(color, 5)); @@ -63,10 +82,6 @@ export function initializeAndWatchThemeColors() { color = computedStyle.getPropertyValue('--vscode-button-foreground').trim(); bodyStyle.setProperty('--color-button-foreground', color); - let foregroundColor = computedStyle.getPropertyValue('--vscode-editor-foreground').trim(); - if (!foregroundColor) { - foregroundColor = computedStyle.getPropertyValue('--vscode-foreground').trim(); - } bodyStyle.setProperty('--color-foreground', foregroundColor); bodyStyle.setProperty('--color-foreground--85', opacity(foregroundColor, 85)); bodyStyle.setProperty('--color-foreground--75', opacity(foregroundColor, 75)); @@ -103,43 +118,6 @@ export function initializeAndWatchThemeColors() { color = computedStyle.getPropertyValue('--vscode-editorHoverWidget-statusBarBackground').trim(); bodyStyle.setProperty('--color-hover-statusBarBackground', color); - // graph-specific colors - bodyStyle.setProperty('--graph-theme-opacity-factor', isLightTheme ? '0.5' : '1'); - - bodyStyle.setProperty( - '--color-graph-actionbar-background', - isLightTheme ? darken(backgroundColor, 5) : lighten(backgroundColor, 5), - ); - bodyStyle.setProperty( - '--color-graph-actionbar-selectedBackground', - isLightTheme ? darken(backgroundColor, 10) : lighten(backgroundColor, 10), - ); - - bodyStyle.setProperty( - '--color-graph-background', - isLightTheme ? darken(backgroundColor, 5) : lighten(backgroundColor, 5), - ); - bodyStyle.setProperty( - '--color-graph-background2', - isLightTheme ? darken(backgroundColor, 10) : lighten(backgroundColor, 10), - ); - color = computedStyle.getPropertyValue('--vscode-list-focusOutline').trim(); - bodyStyle.setProperty('--color-graph-contrast-border-color', color); - color = computedStyle.getPropertyValue('--vscode-list-activeSelectionBackground').trim(); - bodyStyle.setProperty('--color-graph-selected-row', color); - color = computedStyle.getPropertyValue('--vscode-list-hoverBackground').trim(); - bodyStyle.setProperty('--color-graph-hover-row', color); - color = computedStyle.getPropertyValue('--vscode-list-activeSelectionForeground').trim(); - bodyStyle.setProperty('--color-graph-text-selected-row', color); - bodyStyle.setProperty('--color-graph-text-dimmed-selected', opacity(color, 50)); - bodyStyle.setProperty('--color-graph-text-dimmed', opacity(foregroundColor, 20)); - color = computedStyle.getPropertyValue('--vscode-list-hoverForeground').trim(); - bodyStyle.setProperty('--color-graph-text-hovered', color); - bodyStyle.setProperty('--color-graph-text-selected', foregroundColor); - bodyStyle.setProperty('--color-graph-text-normal', opacity(foregroundColor, 85)); - bodyStyle.setProperty('--color-graph-text-secondary', opacity(foregroundColor, 65)); - bodyStyle.setProperty('--color-graph-text-disabled', opacity(foregroundColor, 50)); - // alert colors color = computedStyle.getPropertyValue('--vscode-inputValidation-infoBackground').trim(); bodyStyle.setProperty('--color-alert-infoHoverBackground', isLightTheme ? darken(color, 5) : lighten(color, 5)); @@ -168,14 +146,21 @@ export function initializeAndWatchThemeColors() { bodyStyle.setProperty('--color-alert-neutralBorder', 'var(--vscode-input-foreground)'); bodyStyle.setProperty('--color-alert-foreground', 'var(--vscode-input-foreground)'); - if (mutations != null) { - _onDidChangeTheme.fire(); - } + _onDidChangeTheme.fire({ + colors: { + background: backgroundColor, + foreground: foregroundColor, + }, + computedStyle: computedStyle, + isLightTheme: isLightTheme, + isHighContrastTheme: isHighContrastTheme, + isInitializing: mutations == null, + }); }; onColorThemeChanged(); const observer = new MutationObserver(onColorThemeChanged); observer.observe(document.body, { attributeFilter: ['class'] }); - return observer; + return { dispose: () => observer.disconnect() }; }