diff --git a/dev-packages/application-package/src/application-props.ts b/dev-packages/application-package/src/application-props.ts index 1c78feacccb0b..a6e0701728cb3 100644 --- a/dev-packages/application-package/src/application-props.ts +++ b/dev-packages/application-package/src/application-props.ts @@ -16,6 +16,129 @@ import type { BrowserWindowConstructorOptions } from 'electron'; +/** deepmerge/dist/umd */ +const merge = require('deepmerge/dist/umd'); +export { merge }; + +export type RequiredRecursive = { + [K in keyof T]-?: T[K] extends object ? RequiredRecursive : T[K] +}; + +/** + * Base configuration for the Theia application. + */ +export interface ApplicationConfig { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + readonly [key: string]: any; +} + +export type ElectronFrontendApplicationConfig = RequiredRecursive; +export namespace ElectronFrontendApplicationConfig { + export const DEFAULT: ElectronFrontendApplicationConfig = { + disallowReloadKeybinding: false, + windowOptions: {} + }; + export interface Partial { + + /** + * If set to `true`, reloading the current browser window won't be possible with the `Ctrl/Cmd + R` keybinding. + * + * Has no effect if not in an electron environment. + * + * Defaults to `false`. + */ + readonly disallowReloadKeybinding?: boolean; + + /** + * Override or add properties to the electron `windowOptions`. + * + * Defaults to `{}`. + */ + readonly windowOptions?: BrowserWindowConstructorOptions; + } +} + +/** + * Application configuration for the frontend. The following properties will be injected into the `index.html`. + */ +export type FrontendApplicationConfig = RequiredRecursive; +export namespace FrontendApplicationConfig { + export const DEFAULT: FrontendApplicationConfig = { + applicationName: 'Eclipse Theia', + defaultTheme: 'dark', + defaultIconTheme: 'none', + electron: ElectronFrontendApplicationConfig.DEFAULT + }; + export interface Partial extends ApplicationConfig { + + /** + * The default theme for the application. + * + * Defaults to `dark`. + */ + readonly defaultTheme?: string; + + /** + * The default icon theme for the application. + * + * Defaults to `none`. + */ + readonly defaultIconTheme?: string; + + /** + * The name of the application. + * + * Defaults to `Eclipse Theia`. + */ + readonly applicationName?: string; + + /** + * Electron specific configuration. + * + * Defaults to `ElectronFrontendApplicationConfig.DEFAULT`. + */ + readonly electron?: ElectronFrontendApplicationConfig.Partial; + } +} + +/** + * Application configuration for the backend. + */ +export type BackendApplicationConfig = RequiredRecursive; +export namespace BackendApplicationConfig { + export const DEFAULT: BackendApplicationConfig = { + singleInstance: false, + }; + export interface Partial extends ApplicationConfig { + + /** + * If true and in Electron mode, only one instance of the application is allowed to run at a time. + * + * Defaults to `false`. + */ + readonly singleInstance?: boolean; + } +} + +/** + * Configuration for the generator. + */ +export type GeneratorConfig = RequiredRecursive; +export namespace GeneratorConfig { + export const DEFAULT: GeneratorConfig = { + preloadTemplate: '' + }; + export interface Partial { + + /** + * Template to use for extra preload content markup (file path or HTML). + * + * Defaults to `''`. + */ + readonly preloadTemplate?: string; + } +} + export interface NpmRegistryProps { /** @@ -52,116 +175,42 @@ export interface ApplicationProps extends NpmRegistryProps { /** * Frontend related properties. */ - readonly frontend: Readonly<{ config: FrontendApplicationConfig }>; + readonly frontend: { + readonly config: FrontendApplicationConfig + }; /** * Backend specific properties. */ - readonly backend: Readonly<{ config: BackendApplicationConfig }>; + readonly backend: { + readonly config: BackendApplicationConfig + }; /** * Generator specific properties. */ - readonly generator: Readonly<{ config: GeneratorConfig }>; + readonly generator: { + readonly config: GeneratorConfig + }; } export namespace ApplicationProps { + export type Target = keyof typeof ApplicationTarget; export enum ApplicationTarget { browser = 'browser', electron = 'electron' }; - - export type Target = keyof typeof ApplicationTarget; - export const DEFAULT: ApplicationProps = { ...NpmRegistryProps.DEFAULT, target: 'browser', backend: { - config: {} + config: BackendApplicationConfig.DEFAULT }, frontend: { - config: { - applicationName: 'Eclipse Theia', - defaultTheme: 'dark', - defaultIconTheme: 'none' - } + config: FrontendApplicationConfig.DEFAULT }, generator: { - config: { - preloadTemplate: '' - } + config: GeneratorConfig.DEFAULT } }; } - -/** - * Base configuration for the Theia application. - */ -export interface ApplicationConfig { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - readonly [key: string]: any; -} - -/** - * Application configuration for the frontend. The following properties will be injected into the `index.html`. - */ -export interface FrontendApplicationConfig extends ApplicationConfig { - - /** - * The default theme for the application. If not given, defaults to `dark`. If invalid theme is given, also defaults to `dark`. - */ - readonly defaultTheme: string; - - /** - * The default icon theme for the application. If not given, defaults to `none`. If invalid theme is given, also defaults to `none`. - */ - readonly defaultIconTheme: string; - - /** - * The name of the application. `Eclipse Theia` by default. - */ - readonly applicationName: string; - - /** - * Electron specific configuration. - */ - readonly electron?: Readonly; -} - -export interface ElectronFrontendApplicationConfig { - - /** - * If set to `true`, reloading the current browser window won't be possible with the `Ctrl/Cmd + R` keybinding. - * It is `false` by default. Has no effect if not in an electron environment. - */ - readonly disallowReloadKeybinding?: boolean; - - /** - * Override or add properties to the electron `windowOptions`. - */ - readonly windowOptions?: BrowserWindowConstructorOptions; -} - -/** - * Application configuration for the backend. - */ -export interface BackendApplicationConfig extends ApplicationConfig { - - /** - * If true and in Electron mode, only one instance of the application is allowed to run at a time. - */ - singleInstance?: boolean; - -} - -/** - * Configuration for the generator. - */ -export interface GeneratorConfig { - - /** - * Template to use for extra preload content markup (file path or HTML) - */ - readonly preloadTemplate: string; - -} diff --git a/packages/core/src/browser/frontend-application-config-provider.spec.ts b/packages/core/src/browser/frontend-application-config-provider.spec.ts new file mode 100644 index 0000000000000..6521730f00afe --- /dev/null +++ b/packages/core/src/browser/frontend-application-config-provider.spec.ts @@ -0,0 +1,49 @@ +/******************************************************************************** + * Copyright (C) 2021 Ericsson and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { enableJSDOM } from '../browser/test/jsdom'; +let disableJSDOM = enableJSDOM(); + +import { FrontendApplicationConfig } from '@theia/application-package/lib/'; +import { expect } from 'chai'; +import { FrontendApplicationConfigProvider } from './frontend-application-config-provider'; + +disableJSDOM(); + +const { DEFAULT } = FrontendApplicationConfig; + +describe('FrontendApplicationConfigProvider', function (): void { + + before(() => disableJSDOM = enableJSDOM()); + after(() => disableJSDOM()); + + it('should use defaults when calling `set`', function (): void { + FrontendApplicationConfigProvider.set({ + applicationName: DEFAULT.applicationName + ' Something Else', + electron: { + disallowReloadKeybinding: !DEFAULT.electron.disallowReloadKeybinding + } + }); + const config = FrontendApplicationConfigProvider.get(); + // custom values + expect(config.applicationName).not.equal(DEFAULT.applicationName); + expect(config.electron.disallowReloadKeybinding).not.equal(DEFAULT.electron.disallowReloadKeybinding); + // defaults + expect(config.defaultIconTheme).equal(DEFAULT.defaultIconTheme); + expect(config.defaultTheme).equal(DEFAULT.defaultTheme); + expect(config.electron.windowOptions).deep.equal(DEFAULT.electron.windowOptions); + }); +}); diff --git a/packages/core/src/browser/frontend-application-config-provider.ts b/packages/core/src/browser/frontend-application-config-provider.ts index d422dd2169993..381da426dfce3 100644 --- a/packages/core/src/browser/frontend-application-config-provider.ts +++ b/packages/core/src/browser/frontend-application-config-provider.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { FrontendApplicationConfig } from '@theia/application-package/lib/application-props'; +import { FrontendApplicationConfig, merge } from '@theia/application-package/lib/application-props'; export class FrontendApplicationConfigProvider { @@ -28,14 +28,14 @@ export class FrontendApplicationConfigProvider { return config; } - static set(config: FrontendApplicationConfig): void { + static set(config: FrontendApplicationConfig.Partial): void { if (FrontendApplicationConfigProvider.doGet() !== undefined) { throw new Error('The configuration is already set.'); } // eslint-disable-next-line @typescript-eslint/no-explicit-any const globalObject = window as any; const key = FrontendApplicationConfigProvider.KEY; - globalObject[key] = config; + globalObject[key] = merge(FrontendApplicationConfig.DEFAULT, config); } private static doGet(): FrontendApplicationConfig | undefined { diff --git a/packages/core/src/electron-main/electron-main-application.ts b/packages/core/src/electron-main/electron-main-application.ts index 06de8e06bc87d..65b801e85e914 100644 --- a/packages/core/src/electron-main/electron-main-application.ts +++ b/packages/core/src/electron-main/electron-main-application.ts @@ -225,7 +225,7 @@ export class ElectronMainApplication { } protected async getDefaultBrowserWindowOptions(): Promise { - const windowOptionsFromConfig = this.config.electron?.windowOptions || {}; + const windowOptionsFromConfig = this.config.electron.windowOptions; let windowState: TheiaBrowserWindowOptions | undefined = this.electronStore.get('windowstate', undefined); if (!windowState) { windowState = this.getDefaultWindowState(); diff --git a/packages/core/src/node/backend-application-config-provider.spec.ts b/packages/core/src/node/backend-application-config-provider.spec.ts new file mode 100644 index 0000000000000..b321e422ccee3 --- /dev/null +++ b/packages/core/src/node/backend-application-config-provider.spec.ts @@ -0,0 +1,29 @@ +/******************************************************************************** + * Copyright (C) 2021 Ericsson and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { BackendApplicationConfig } from '@theia/application-package/lib/'; +import { expect } from 'chai'; +import { BackendApplicationConfigProvider } from './backend-application-config-provider'; + +const { DEFAULT } = BackendApplicationConfig; + +describe('BackendApplicationConfigProvider', function (): void { + it('should use defaults when calling `set`', function (): void { + BackendApplicationConfigProvider.set({}); + const config = BackendApplicationConfigProvider.get(); + expect(config.singleInstance).equal(DEFAULT.singleInstance); + }); +}); diff --git a/packages/core/src/node/backend-application-config-provider.ts b/packages/core/src/node/backend-application-config-provider.ts index 319a769107d9b..2d0446b711866 100644 --- a/packages/core/src/node/backend-application-config-provider.ts +++ b/packages/core/src/node/backend-application-config-provider.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { BackendApplicationConfig } from '@theia/application-package/lib/application-props'; +import { BackendApplicationConfig, merge } from '@theia/application-package/lib/application-props'; export class BackendApplicationConfigProvider { @@ -28,14 +28,14 @@ export class BackendApplicationConfigProvider { return config; } - static set(config: BackendApplicationConfig): void { + static set(config: BackendApplicationConfig.Partial): void { if (BackendApplicationConfigProvider.doGet() !== undefined) { throw new Error('The configuration is already set.'); } // eslint-disable-next-line @typescript-eslint/no-explicit-any const globalObject = global as any; const key = BackendApplicationConfigProvider.KEY; - globalObject[key] = config; + globalObject[key] = merge(BackendApplicationConfig.DEFAULT, config); } private static doGet(): BackendApplicationConfig | undefined {