diff --git a/packages/core/src/browser/frontend-application-module.ts b/packages/core/src/browser/frontend-application-module.ts index 5aad42ea55905..f9a049b5f2da2 100644 --- a/packages/core/src/browser/frontend-application-module.ts +++ b/packages/core/src/browser/frontend-application-module.ts @@ -54,7 +54,7 @@ import { LabelParser } from './label-parser'; import { LabelProvider, LabelProviderContribution, DefaultUriLabelProviderContribution } from './label-provider'; import { PreferenceService } from './preferences'; import { ContextMenuRenderer } from './context-menu-renderer'; -import { ThemeService, BuiltinThemeProvider } from './theming'; +import { ThemeService } from './theming'; import { ConnectionStatusService, FrontendConnectionStatusService, ApplicationConnectionStatusContribution, PingService } from './connection-status-service'; import { DiffUriLabelProviderContribution } from './diff-uris'; import { ApplicationServer, applicationPath } from '../common/application-protocol'; @@ -103,10 +103,6 @@ export { bindResourceProvider, bindMessageService, bindPreferenceService }; ColorApplicationContribution.initBackground(); export const frontendApplicationModule = new ContainerModule((bind, unbind, isBound, rebind) => { - const themeService = ThemeService.get(); - themeService.register(...BuiltinThemeProvider.themes); - themeService.startupTheme(); - bind(NoneIconTheme).toSelf().inSingletonScope(); bind(LabelProviderContribution).toService(NoneIconTheme); bind(IconThemeService).toSelf().inSingletonScope(); diff --git a/packages/core/src/browser/theming.ts b/packages/core/src/browser/theming.ts index 7f7e1f9227da4..107a6d59f4d94 100644 --- a/packages/core/src/browser/theming.ts +++ b/packages/core/src/browser/theming.ts @@ -40,20 +40,21 @@ export interface ThemeChangeEvent { export class ThemeService { - private themes: { [id: string]: Theme } = {}; - private activeTheme: Theme | undefined; - private readonly themeChange = new Emitter(); + protected themes: { [id: string]: Theme } = {}; + protected activeTheme: Theme | undefined; + protected readonly themeChange = new Emitter(); readonly onThemeChange: Event = this.themeChange.event; static get(): ThemeService { const global = window as any; // eslint-disable-line @typescript-eslint/no-explicit-any - return global[ThemeServiceSymbol] || new ThemeService(); - } - - protected constructor() { - const global = window as any; // eslint-disable-line @typescript-eslint/no-explicit-any - global[ThemeServiceSymbol] = this; + if (!global[ThemeServiceSymbol]) { + const themeService = new ThemeService(); + themeService.register(...BuiltinThemeProvider.themes); + themeService.startupTheme(); + global[ThemeServiceSymbol] = themeService; + } + return global[ThemeServiceSymbol]; } register(...themes: Theme[]): Disposable { diff --git a/packages/monaco/src/browser/monaco-indexed-db.ts b/packages/monaco/src/browser/monaco-indexed-db.ts index 3311895a6ac3c..dee071b90b2a0 100644 --- a/packages/monaco/src/browser/monaco-indexed-db.ts +++ b/packages/monaco/src/browser/monaco-indexed-db.ts @@ -16,6 +16,7 @@ import * as idb from 'idb'; import { Disposable, DisposableCollection } from '@theia/core/lib/common/disposable'; +import { BuiltinThemeProvider, Theme, ThemeService, ThemeServiceSymbol } from '@theia/core/lib/browser/theming'; type ThemeMix = import('./textmate/monaco-theme-registry').ThemeMix; let _monacoDB: Promise | undefined; @@ -82,3 +83,52 @@ export async function deleteTheme(id: string): Promise { const db = await monacoDB; await db.transaction('themes', 'readwrite').objectStore('themes').delete(id); } + +export function stateToTheme(state: MonacoThemeState): Theme { + const { id, label, description, uiTheme, data } = state; + const type = uiTheme === 'vs' ? 'light' : uiTheme === 'vs-dark' ? 'dark' : 'hc'; + const builtInTheme = uiTheme === 'vs' ? BuiltinThemeProvider.lightCss : BuiltinThemeProvider.darkCss; + return { + type, + id, + label, + description, + editorTheme: data.name!, + activate(): void { + builtInTheme.use(); + }, + deactivate(): void { + builtInTheme.unuse(); + } + }; +} + +async function getThemeFromDB(id: string): Promise { + const matchingState = (await getThemes()).find(theme => theme.id === id); + return matchingState && stateToTheme(matchingState); +} + +export class ThemeServiceWithDB extends ThemeService { + static get(): ThemeService { + const global = window as any; // eslint-disable-line @typescript-eslint/no-explicit-any + if (!global[ThemeServiceSymbol]) { + const themeService = new ThemeServiceWithDB(); + themeService.register(...BuiltinThemeProvider.themes); + themeService.startupTheme(); + global[ThemeServiceSymbol] = themeService; + } + return global[ThemeServiceSymbol]; + } + + loadUserTheme(): void { + this.loadUserThemeWithDB(); + } + + protected async loadUserThemeWithDB(): Promise { + const themeId = window.localStorage.getItem('theme') || this.defaultTheme.id; + const theme = this.themes[themeId] ?? await getThemeFromDB(themeId) ?? this.defaultTheme; + this.setCurrentTheme(theme.id); + } +} + +ThemeService.get = ThemeServiceWithDB.get; diff --git a/packages/monaco/src/browser/monaco-theming-service.ts b/packages/monaco/src/browser/monaco-theming-service.ts index 811d8074ce226..6e5a14923f8aa 100644 --- a/packages/monaco/src/browser/monaco-theming-service.ts +++ b/packages/monaco/src/browser/monaco-theming-service.ts @@ -19,11 +19,11 @@ import { injectable, inject } from '@theia/core/shared/inversify'; import * as jsoncparser from 'jsonc-parser'; import * as plistparser from 'fast-plist'; -import { ThemeService, BuiltinThemeProvider } from '@theia/core/lib/browser/theming'; +import { ThemeService } from '@theia/core/lib/browser/theming'; import URI from '@theia/core/lib/common/uri'; import { Disposable, DisposableCollection } from '@theia/core/lib/common/disposable'; import { MonacoThemeRegistry } from './textmate/monaco-theme-registry'; -import { getThemes, putTheme, MonacoThemeState } from './monaco-indexed-db'; +import { getThemes, putTheme, MonacoThemeState, stateToTheme } from './monaco-indexed-db'; import { FileService } from '@theia/filesystem/lib/browser/file-service'; export interface MonacoTheme { @@ -161,23 +161,8 @@ export class MonacoThemingService { } protected static doRegister(state: MonacoThemeState): Disposable { - const { id, label, description, uiTheme, data } = state; - const type = uiTheme === 'vs' ? 'light' : uiTheme === 'vs-dark' ? 'dark' : 'hc'; - const builtInTheme = uiTheme === 'vs' ? BuiltinThemeProvider.lightCss : BuiltinThemeProvider.darkCss; return new DisposableCollection( - ThemeService.get().register({ - type, - id, - label, - description: description, - editorTheme: data.name!, - activate(): void { - builtInTheme.use(); - }, - deactivate(): void { - builtInTheme.unuse(); - } - }), + ThemeService.get().register(stateToTheme(state)), putTheme(state) ); }