Skip to content

Commit

Permalink
application-package: refine config typings
Browse files Browse the repository at this point in the history
Rework how the different application configurations are defined to allow
partial setting when calling configuration providers.

Use default values when setting a partial configuration through the
configuration providers.
  • Loading branch information
paul-marechal committed Jun 9, 2021
1 parent f9c0311 commit bcb8fbb
Show file tree
Hide file tree
Showing 6 changed files with 221 additions and 94 deletions.
223 changes: 136 additions & 87 deletions dev-packages/application-package/src/application-props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,129 @@

import type { BrowserWindowConstructorOptions } from 'electron';

/** deepmerge/dist/umd */
const merge = require('deepmerge/dist/umd');
export { merge };

export type RequiredRecursive<T> = {
[K in keyof T]-?: T[K] extends object ? RequiredRecursive<T[K]> : 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<ElectronFrontendApplicationConfig.Partial>;
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<FrontendApplicationConfig.Partial>;
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<BackendApplicationConfig.Partial>;
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<GeneratorConfig.Partial>;
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 {

/**
Expand Down Expand Up @@ -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<ElectronFrontendApplicationConfig>;
}

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;

}
Original file line number Diff line number Diff line change
@@ -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);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand All @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ export class ElectronMainApplication {
}

protected async getDefaultBrowserWindowOptions(): Promise<TheiaBrowserWindowOptions> {
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();
Expand Down
29 changes: 29 additions & 0 deletions packages/core/src/node/backend-application-config-provider.spec.ts
Original file line number Diff line number Diff line change
@@ -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);
});
});
Loading

0 comments on commit bcb8fbb

Please sign in to comment.