mirror of
https://github.com/arduino/arduino-ide.git
synced 2025-07-08 20:06:32 +00:00
feat: removed the non official themes from the UI
Closes #1283 Ref eclipse-theia/theia#11151 Signed-off-by: Akos Kitta <a.kitta@arduino.cc>
This commit is contained in:
parent
7cc252fc36
commit
5540170341
@ -238,7 +238,6 @@ import {
|
|||||||
UploadFirmwareDialog,
|
UploadFirmwareDialog,
|
||||||
UploadFirmwareDialogProps,
|
UploadFirmwareDialogProps,
|
||||||
} from './dialogs/firmware-uploader/firmware-uploader-dialog';
|
} from './dialogs/firmware-uploader/firmware-uploader-dialog';
|
||||||
|
|
||||||
import { UploadCertificate } from './contributions/upload-certificate';
|
import { UploadCertificate } from './contributions/upload-certificate';
|
||||||
import {
|
import {
|
||||||
ArduinoFirmwareUploader,
|
ArduinoFirmwareUploader,
|
||||||
@ -328,9 +327,13 @@ import { NewCloudSketch } from './contributions/new-cloud-sketch';
|
|||||||
import { SketchbookCompositeWidget } from './widgets/sketchbook/sketchbook-composite-widget';
|
import { SketchbookCompositeWidget } from './widgets/sketchbook/sketchbook-composite-widget';
|
||||||
import { WindowTitleUpdater } from './theia/core/window-title-updater';
|
import { WindowTitleUpdater } from './theia/core/window-title-updater';
|
||||||
import { WindowTitleUpdater as TheiaWindowTitleUpdater } from '@theia/core/lib/browser/window/window-title-updater';
|
import { WindowTitleUpdater as TheiaWindowTitleUpdater } from '@theia/core/lib/browser/window/window-title-updater';
|
||||||
import { ThemeServiceWithDB } from './theia/core/theming';
|
import {
|
||||||
import { ThemeServiceWithDB as TheiaThemeServiceWithDB } from '@theia/monaco/lib/browser/monaco-indexed-db';
|
MonacoThemingService,
|
||||||
import { MonacoThemingService } from './theia/monaco/monaco-theming-service';
|
CleanupObsoleteThemes,
|
||||||
|
ThemesRegistrationSummary,
|
||||||
|
MonacoThemeRegistry,
|
||||||
|
} from './theia/monaco/monaco-theming-service';
|
||||||
|
import { MonacoThemeRegistry as TheiaMonacoThemeRegistry } from '@theia/monaco/lib/browser/textmate/monaco-theme-registry';
|
||||||
import { MonacoThemingService as TheiaMonacoThemingService } from '@theia/monaco/lib/browser/monaco-theming-service';
|
import { MonacoThemingService as TheiaMonacoThemingService } from '@theia/monaco/lib/browser/monaco-theming-service';
|
||||||
import { TypeHierarchyServiceProvider } from './theia/typehierarchy/type-hierarchy-service';
|
import { TypeHierarchyServiceProvider } from './theia/typehierarchy/type-hierarchy-service';
|
||||||
import { TypeHierarchyServiceProvider as TheiaTypeHierarchyServiceProvider } from '@theia/typehierarchy/lib/browser/typehierarchy-service';
|
import { TypeHierarchyServiceProvider as TheiaTypeHierarchyServiceProvider } from '@theia/typehierarchy/lib/browser/typehierarchy-service';
|
||||||
@ -973,11 +976,19 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
|||||||
rebind(TheiaWindowTitleUpdater).toService(WindowTitleUpdater);
|
rebind(TheiaWindowTitleUpdater).toService(WindowTitleUpdater);
|
||||||
|
|
||||||
// register Arduino themes
|
// register Arduino themes
|
||||||
bind(ThemeServiceWithDB).toSelf().inSingletonScope();
|
|
||||||
rebind(TheiaThemeServiceWithDB).toService(ThemeServiceWithDB);
|
|
||||||
bind(MonacoThemingService).toSelf().inSingletonScope();
|
bind(MonacoThemingService).toSelf().inSingletonScope();
|
||||||
rebind(TheiaMonacoThemingService).toService(MonacoThemingService);
|
rebind(TheiaMonacoThemingService).toService(MonacoThemingService);
|
||||||
|
|
||||||
|
// workaround for themes cannot be removed after registration
|
||||||
|
// https://github.com/eclipse-theia/theia/issues/11151
|
||||||
|
bind(CleanupObsoleteThemes).toSelf().inSingletonScope();
|
||||||
|
bind(FrontendApplicationContribution).toService(
|
||||||
|
CleanupObsoleteThemes
|
||||||
|
);
|
||||||
|
bind(ThemesRegistrationSummary).toSelf().inSingletonScope();
|
||||||
|
bind(MonacoThemeRegistry).toSelf().inSingletonScope();
|
||||||
|
rebind(TheiaMonacoThemeRegistry).toService(MonacoThemeRegistry);
|
||||||
|
|
||||||
// disable type-hierarchy support
|
// disable type-hierarchy support
|
||||||
// https://github.com/eclipse-theia/theia/commit/16c88a584bac37f5cf3cc5eb92ffdaa541bda5be
|
// https://github.com/eclipse-theia/theia/commit/16c88a584bac37f5cf3cc5eb92ffdaa541bda5be
|
||||||
bind(TypeHierarchyServiceProvider).toSelf().inSingletonScope();
|
bind(TypeHierarchyServiceProvider).toSelf().inSingletonScope();
|
||||||
|
@ -24,6 +24,12 @@ import {
|
|||||||
} from '@theia/core/lib/common/i18n/localization';
|
} from '@theia/core/lib/common/i18n/localization';
|
||||||
import SettingsStepInput from './settings-step-input';
|
import SettingsStepInput from './settings-step-input';
|
||||||
import { InterfaceScale } from '../../contributions/interface-scale';
|
import { InterfaceScale } from '../../contributions/interface-scale';
|
||||||
|
import {
|
||||||
|
userConfigurableThemes,
|
||||||
|
themeLabelForSettings,
|
||||||
|
arduinoThemeTypeOf,
|
||||||
|
} from '../../theia/core/theming';
|
||||||
|
import { Theme } from '@theia/core/lib/common/theme';
|
||||||
|
|
||||||
const maxScale = InterfaceScale.ZoomLevel.toPercentage(
|
const maxScale = InterfaceScale.ZoomLevel.toPercentage(
|
||||||
InterfaceScale.ZoomLevel.MAX
|
InterfaceScale.ZoomLevel.MAX
|
||||||
@ -218,14 +224,10 @@ export class SettingsComponent extends React.Component<
|
|||||||
<div className="flex-line">
|
<div className="flex-line">
|
||||||
<select
|
<select
|
||||||
className="theia-select"
|
className="theia-select"
|
||||||
value={this.props.themeService.getCurrentTheme().label}
|
value={this.currentThemeLabel}
|
||||||
onChange={this.themeDidChange}
|
onChange={this.themeDidChange}
|
||||||
>
|
>
|
||||||
{this.props.themeService.getThemes().map(({ id, label }) => (
|
{this.themeSelectOptions}
|
||||||
<option key={id} value={label}>
|
|
||||||
{label}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-line">
|
<div className="flex-line">
|
||||||
@ -333,6 +335,46 @@ export class SettingsComponent extends React.Component<
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private get currentThemeLabel(): string {
|
||||||
|
const currentTheme = this.props.themeService.getCurrentTheme();
|
||||||
|
return themeLabelForSettings(currentTheme);
|
||||||
|
}
|
||||||
|
|
||||||
|
private get separatedThemes(): (Theme | string)[] {
|
||||||
|
const separatedThemes: (Theme | string)[] = [];
|
||||||
|
const groupedThemes = userConfigurableThemes(this.props.themeService);
|
||||||
|
for (const group of groupedThemes) {
|
||||||
|
for (let i = 0; i < group.length; i++) {
|
||||||
|
const theme = group[i];
|
||||||
|
if (i === 0 && separatedThemes.length) {
|
||||||
|
const arduinoThemeType = arduinoThemeTypeOf(theme);
|
||||||
|
separatedThemes.push(`separator-${arduinoThemeType}`);
|
||||||
|
}
|
||||||
|
separatedThemes.push(theme);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return separatedThemes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private get themeSelectOptions(): React.ReactNode[] {
|
||||||
|
return this.separatedThemes.map((item) => {
|
||||||
|
if (typeof item === 'string') {
|
||||||
|
return (
|
||||||
|
// ─ -> BOX DRAWINGS LIGHT HORIZONTAL
|
||||||
|
<option key={item} disabled>
|
||||||
|
──────────
|
||||||
|
</option>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const label = themeLabelForSettings(item);
|
||||||
|
return (
|
||||||
|
<option key={item.id} value={label}>
|
||||||
|
{label}
|
||||||
|
</option>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private toSelectOptions(language: string | LanguageInfo): JSX.Element {
|
private toSelectOptions(language: string | LanguageInfo): JSX.Element {
|
||||||
const plain = typeof language === 'string';
|
const plain = typeof language === 'string';
|
||||||
const key = plain ? language : language.languageId;
|
const key = plain ? language : language.languageId;
|
||||||
@ -610,8 +652,8 @@ export class SettingsComponent extends React.Component<
|
|||||||
event: React.ChangeEvent<HTMLSelectElement>
|
event: React.ChangeEvent<HTMLSelectElement>
|
||||||
): void => {
|
): void => {
|
||||||
const { selectedIndex } = event.target.options;
|
const { selectedIndex } = event.target.options;
|
||||||
const theme = this.props.themeService.getThemes()[selectedIndex];
|
const theme = this.separatedThemes[selectedIndex];
|
||||||
if (theme) {
|
if (theme && typeof theme !== 'string') {
|
||||||
this.setState({ themeId: theme.id });
|
this.setState({ themeId: theme.id });
|
||||||
if (this.props.themeService.getCurrentTheme().id !== theme.id) {
|
if (this.props.themeService.getCurrentTheme().id !== theme.id) {
|
||||||
this.props.themeService.setCurrentTheme(theme.id);
|
this.props.themeService.setCurrentTheme(theme.id);
|
||||||
|
@ -1,15 +1,19 @@
|
|||||||
import type { Theme } from '@theia/core/lib/common/theme';
|
import {
|
||||||
import { injectable } from '@theia/core/shared/inversify';
|
BuiltinThemeProvider,
|
||||||
import { ThemeServiceWithDB as TheiaThemeServiceWithDB } from '@theia/monaco/lib/browser/monaco-indexed-db';
|
ThemeService,
|
||||||
|
} from '@theia/core/lib/browser/theming';
|
||||||
|
import { nls } from '@theia/core/lib/common/nls';
|
||||||
|
import type { Theme, ThemeType } from '@theia/core/lib/common/theme';
|
||||||
|
import { assertUnreachable } from '../../../common/utils';
|
||||||
|
|
||||||
export namespace ArduinoThemes {
|
export namespace ArduinoThemes {
|
||||||
export const Light: Theme = {
|
export const light: Theme = {
|
||||||
id: 'arduino-theme',
|
id: 'arduino-theme',
|
||||||
type: 'light',
|
type: 'light',
|
||||||
label: 'Light (Arduino)',
|
label: 'Light (Arduino)',
|
||||||
editorTheme: 'arduino-theme',
|
editorTheme: 'arduino-theme',
|
||||||
};
|
};
|
||||||
export const Dark: Theme = {
|
export const dark: Theme = {
|
||||||
id: 'arduino-theme-dark',
|
id: 'arduino-theme-dark',
|
||||||
type: 'dark',
|
type: 'dark',
|
||||||
label: 'Dark (Arduino)',
|
label: 'Dark (Arduino)',
|
||||||
@ -17,10 +21,166 @@ export namespace ArduinoThemes {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@injectable()
|
const builtInThemeIds = new Set(
|
||||||
export class ThemeServiceWithDB extends TheiaThemeServiceWithDB {
|
[
|
||||||
protected override init(): void {
|
ArduinoThemes.light,
|
||||||
this.register(ArduinoThemes.Light, ArduinoThemes.Dark);
|
ArduinoThemes.dark,
|
||||||
super.init();
|
BuiltinThemeProvider.hcTheme,
|
||||||
|
// TODO: add the HC light theme after Theia 1.36
|
||||||
|
].map(({ id }) => id)
|
||||||
|
);
|
||||||
|
const deprecatedThemeIds = new Set(
|
||||||
|
[BuiltinThemeProvider.lightTheme, BuiltinThemeProvider.darkTheme].map(
|
||||||
|
({ id }) => id
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
export const lightThemeLabel = nls.localize('arduino/theme/light', 'Light');
|
||||||
|
export const darkThemeLabel = nls.localize('arduino/theme/dark', 'Dark');
|
||||||
|
export const hcThemeLabel = nls.localize('arduino/theme/hc', 'High Contrast');
|
||||||
|
export function userThemeLabel(theme: Theme): string {
|
||||||
|
return nls.localize('arduino/theme/user', '{0} (user)', theme.label);
|
||||||
|
}
|
||||||
|
export function deprecatedThemeLabel(theme: Theme): string {
|
||||||
|
return nls.localize(
|
||||||
|
'arduino/theme/deprecated',
|
||||||
|
'{0} (deprecated)',
|
||||||
|
theme.label
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function themeLabelForSettings(theme: Theme): string {
|
||||||
|
switch (theme.id) {
|
||||||
|
case ArduinoThemes.light.id:
|
||||||
|
return lightThemeLabel;
|
||||||
|
case ArduinoThemes.dark.id:
|
||||||
|
return darkThemeLabel;
|
||||||
|
case BuiltinThemeProvider.hcTheme.id:
|
||||||
|
return hcThemeLabel;
|
||||||
|
case BuiltinThemeProvider.lightTheme.id: // fall-through
|
||||||
|
case BuiltinThemeProvider.darkTheme.id:
|
||||||
|
return deprecatedThemeLabel(theme);
|
||||||
|
default:
|
||||||
|
return userThemeLabel(theme);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function compatibleBuiltInTheme(theme: Theme): Theme {
|
||||||
|
switch (theme.type) {
|
||||||
|
case 'light':
|
||||||
|
return ArduinoThemes.light;
|
||||||
|
case 'dark':
|
||||||
|
return ArduinoThemes.dark;
|
||||||
|
case 'hc':
|
||||||
|
return BuiltinThemeProvider.hcTheme;
|
||||||
|
default: {
|
||||||
|
console.warn(
|
||||||
|
`Unhandled theme type: ${theme.type}. Theme ID: ${theme.id}, label: ${theme.label}`
|
||||||
|
);
|
||||||
|
return ArduinoThemes.light;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For tests without DI
|
||||||
|
interface ThemeProvider {
|
||||||
|
themes(): Theme[];
|
||||||
|
currentTheme(): Theme;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns with a list of built-in themes officially supported by IDE2 (https://github.com/arduino/arduino-ide/issues/1283).
|
||||||
|
* The themes in the array follow the following order:
|
||||||
|
* - built-in themes first (in `Light`, `Dark`, `High Contrast`), // TODO -> High Contrast will be split up to HC Dark and HC Light after the Theia version uplift
|
||||||
|
* - followed by user installed (VSIX) themes grouped by theme type, then alphabetical order,
|
||||||
|
* - if the `currentTheme` is either Light (Theia) or Dark (Theia), the last item of the array will be the selected theme with `(deprecated)` suffix.
|
||||||
|
*/
|
||||||
|
export function userConfigurableThemes(service: ThemeService): Theme[][];
|
||||||
|
export function userConfigurableThemes(provider: ThemeProvider): Theme[][];
|
||||||
|
export function userConfigurableThemes(
|
||||||
|
serviceOrProvider: ThemeService | ThemeProvider
|
||||||
|
): Theme[][] {
|
||||||
|
const provider =
|
||||||
|
serviceOrProvider instanceof ThemeService
|
||||||
|
? {
|
||||||
|
currentTheme: () => serviceOrProvider.getCurrentTheme(),
|
||||||
|
themes: () => serviceOrProvider.getThemes(),
|
||||||
|
}
|
||||||
|
: serviceOrProvider;
|
||||||
|
const currentTheme = provider.currentTheme();
|
||||||
|
const allThemes = provider
|
||||||
|
.themes()
|
||||||
|
.map((theme) => ({ ...theme, arduinoThemeType: arduinoThemeTypeOf(theme) }))
|
||||||
|
.filter(
|
||||||
|
(theme) =>
|
||||||
|
theme.arduinoThemeType !== 'deprecated' || currentTheme.id === theme.id
|
||||||
|
)
|
||||||
|
.sort((left, right) => {
|
||||||
|
const leftArduinoThemeType = left.arduinoThemeType;
|
||||||
|
const rightArduinoThemeType = right.arduinoThemeType;
|
||||||
|
if (leftArduinoThemeType === rightArduinoThemeType) {
|
||||||
|
const result = themeTypeOrder[left.type] - themeTypeOrder[right.type];
|
||||||
|
if (result) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return left.label.localeCompare(right.label); // alphabetical order
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
arduinoThemeTypeOrder[leftArduinoThemeType] -
|
||||||
|
arduinoThemeTypeOrder[rightArduinoThemeType]
|
||||||
|
);
|
||||||
|
});
|
||||||
|
const builtInThemes: Theme[] = [];
|
||||||
|
const userThemes: Theme[] = [];
|
||||||
|
const deprecatedThemes: Theme[] = [];
|
||||||
|
allThemes.forEach((theme) => {
|
||||||
|
const { arduinoThemeType } = theme;
|
||||||
|
switch (arduinoThemeType) {
|
||||||
|
case 'built-in':
|
||||||
|
builtInThemes.push(theme);
|
||||||
|
break;
|
||||||
|
case 'user':
|
||||||
|
userThemes.push(theme);
|
||||||
|
break;
|
||||||
|
case 'deprecated':
|
||||||
|
deprecatedThemes.push(theme);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
assertUnreachable(arduinoThemeType);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const groupedThemes: Theme[][] = [];
|
||||||
|
if (builtInThemes.length) {
|
||||||
|
groupedThemes.push(builtInThemes);
|
||||||
|
}
|
||||||
|
if (userThemes.length) {
|
||||||
|
groupedThemes.push(userThemes);
|
||||||
|
}
|
||||||
|
if (deprecatedThemes.length) {
|
||||||
|
groupedThemes.push(deprecatedThemes);
|
||||||
|
}
|
||||||
|
return groupedThemes;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ArduinoThemeType = 'built-in' | 'user' | 'deprecated';
|
||||||
|
const arduinoThemeTypeOrder: Record<ArduinoThemeType, number> = {
|
||||||
|
'built-in': 0,
|
||||||
|
user: 1,
|
||||||
|
deprecated: 2,
|
||||||
|
};
|
||||||
|
const themeTypeOrder: Record<ThemeType, number> = {
|
||||||
|
light: 0,
|
||||||
|
dark: 1,
|
||||||
|
hc: 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
export function arduinoThemeTypeOf(theme: Theme | string): ArduinoThemeType {
|
||||||
|
const themeId = typeof theme === 'string' ? theme : theme.id;
|
||||||
|
if (builtInThemeIds.has(themeId)) {
|
||||||
|
return 'built-in';
|
||||||
|
}
|
||||||
|
if (deprecatedThemeIds.has(themeId)) {
|
||||||
|
return 'deprecated';
|
||||||
|
}
|
||||||
|
return 'user';
|
||||||
|
}
|
||||||
|
@ -1,23 +1,231 @@
|
|||||||
import { injectable } from '@theia/core/shared/inversify';
|
import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application';
|
||||||
import { MonacoThemingService as TheiaMonacoThemingService } from '@theia/monaco/lib/browser/monaco-theming-service';
|
import { ThemeService } from '@theia/core/lib/browser/theming';
|
||||||
import { ArduinoThemes } from '../core/theming';
|
import {
|
||||||
|
Disposable,
|
||||||
|
DisposableCollection,
|
||||||
|
} from '@theia/core/lib/common/disposable';
|
||||||
|
import { MessageService } from '@theia/core/lib/common/message-service';
|
||||||
|
import { nls } from '@theia/core/lib/common/nls';
|
||||||
|
import { deepClone } from '@theia/core/lib/common/objects';
|
||||||
|
import { wait } from '@theia/core/lib/common/promise-util';
|
||||||
|
import { inject, injectable } from '@theia/core/shared/inversify';
|
||||||
|
import {
|
||||||
|
MonacoThemeState,
|
||||||
|
deleteTheme as deleteThemeFromIndexedDB,
|
||||||
|
getThemes as getThemesFromIndexedDB,
|
||||||
|
} from '@theia/monaco/lib/browser/monaco-indexed-db';
|
||||||
|
import {
|
||||||
|
MonacoTheme,
|
||||||
|
MonacoThemingService as TheiaMonacoThemingService,
|
||||||
|
} from '@theia/monaco/lib/browser/monaco-theming-service';
|
||||||
|
import { MonacoThemeRegistry as TheiaMonacoThemeRegistry } from '@theia/monaco/lib/browser/textmate/monaco-theme-registry';
|
||||||
|
import type { ThemeMix } from '@theia/monaco/lib/browser/textmate/monaco-theme-types';
|
||||||
|
import { HostedPluginSupport } from '@theia/plugin-ext/lib/hosted/browser/hosted-plugin';
|
||||||
|
import { ArduinoThemes, compatibleBuiltInTheme } from '../core/theming';
|
||||||
|
import { WindowServiceExt } from '../core/window-service-ext';
|
||||||
|
|
||||||
|
type MonacoThemeRegistrationSource =
|
||||||
|
/**
|
||||||
|
* When reading JS/TS contributed theme from a JSON file. Such as the Arduino themes and the ones contributed by Theia.
|
||||||
|
*/
|
||||||
|
| 'compiled'
|
||||||
|
/**
|
||||||
|
* When reading and registering previous monaco themes from the `indexedDB`.
|
||||||
|
*/
|
||||||
|
| 'indexedDB'
|
||||||
|
/**
|
||||||
|
* Contributed by VS Code extensions when starting the app and loading the plugins.
|
||||||
|
*/
|
||||||
|
| 'vsix';
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class ThemesRegistrationSummary {
|
||||||
|
private readonly _summary: Record<MonacoThemeRegistrationSource, string[]> = {
|
||||||
|
compiled: [],
|
||||||
|
indexedDB: [],
|
||||||
|
vsix: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
add(source: MonacoThemeRegistrationSource, themeId: string): void {
|
||||||
|
const themeIds = this._summary[source];
|
||||||
|
if (!themeIds.includes(themeId)) {
|
||||||
|
themeIds.push(themeId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get summary(): Record<MonacoThemeRegistrationSource, string[]> {
|
||||||
|
return deepClone(this._summary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class MonacoThemeRegistry extends TheiaMonacoThemeRegistry {
|
||||||
|
@inject(ThemesRegistrationSummary)
|
||||||
|
private readonly summary: ThemesRegistrationSummary;
|
||||||
|
|
||||||
|
private initializing = false;
|
||||||
|
|
||||||
|
override initializeDefaultThemes(): void {
|
||||||
|
this.initializing = true;
|
||||||
|
try {
|
||||||
|
super.initializeDefaultThemes();
|
||||||
|
} finally {
|
||||||
|
this.initializing = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override setTheme(name: string, data: ThemeMix): void {
|
||||||
|
super.setTheme(name, data);
|
||||||
|
if (this.initializing) {
|
||||||
|
this.summary.add('compiled', name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class MonacoThemingService extends TheiaMonacoThemingService {
|
export class MonacoThemingService extends TheiaMonacoThemingService {
|
||||||
override initialize(): void {
|
@inject(ThemesRegistrationSummary)
|
||||||
super.initialize();
|
private readonly summary: ThemesRegistrationSummary;
|
||||||
const { Light, Dark } = ArduinoThemes;
|
|
||||||
|
private themeRegistrationSource: MonacoThemeRegistrationSource | undefined;
|
||||||
|
|
||||||
|
protected override async restore(): Promise<void> {
|
||||||
|
// The custom theme registration must happen before restoring the themes.
|
||||||
|
// Otherwise, theme changes are not picked up.
|
||||||
|
// https://github.com/arduino/arduino-ide/issues/1251#issuecomment-1436737702
|
||||||
|
this.registerArduinoThemes();
|
||||||
|
this.themeRegistrationSource = 'indexedDB';
|
||||||
|
try {
|
||||||
|
await super.restore();
|
||||||
|
} finally {
|
||||||
|
this.themeRegistrationSource = 'indexedDB';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private registerArduinoThemes(): void {
|
||||||
|
const { light, dark } = ArduinoThemes;
|
||||||
this.registerParsedTheme({
|
this.registerParsedTheme({
|
||||||
id: Light.id,
|
id: light.id,
|
||||||
label: Light.label,
|
label: light.label,
|
||||||
uiTheme: 'vs',
|
uiTheme: 'vs',
|
||||||
json: require('../../../../src/browser/data/default.color-theme.json'),
|
json: require('../../../../src/browser/data/default.color-theme.json'),
|
||||||
});
|
});
|
||||||
this.registerParsedTheme({
|
this.registerParsedTheme({
|
||||||
id: Dark.id,
|
id: dark.id,
|
||||||
label: Dark.label,
|
label: dark.label,
|
||||||
uiTheme: 'vs-dark',
|
uiTheme: 'vs-dark',
|
||||||
json: require('../../../../src/browser/data/dark.color-theme.json'),
|
json: require('../../../../src/browser/data/dark.color-theme.json'),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override doRegisterParsedTheme(
|
||||||
|
state: MonacoThemeState
|
||||||
|
): Disposable {
|
||||||
|
const themeId = state.id;
|
||||||
|
const source = this.themeRegistrationSource ?? 'compiled';
|
||||||
|
const disposable = super.doRegisterParsedTheme(state);
|
||||||
|
this.summary.add(source, themeId);
|
||||||
|
return disposable;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async doRegister(
|
||||||
|
theme: MonacoTheme,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
pending: { [uri: string]: Promise<any> },
|
||||||
|
toDispose: DisposableCollection
|
||||||
|
): Promise<void> {
|
||||||
|
try {
|
||||||
|
this.themeRegistrationSource = 'vsix';
|
||||||
|
await super.doRegister(theme, pending, toDispose);
|
||||||
|
} finally {
|
||||||
|
this.themeRegistrationSource = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Workaround for removing VSIX themes from the indexedDB if they were not loaded during the app startup.
|
||||||
|
*/
|
||||||
|
@injectable()
|
||||||
|
export class CleanupObsoleteThemes implements FrontendApplicationContribution {
|
||||||
|
@inject(HostedPluginSupport)
|
||||||
|
private readonly hostedPlugin: HostedPluginSupport;
|
||||||
|
@inject(ThemesRegistrationSummary)
|
||||||
|
private readonly summary: ThemesRegistrationSummary;
|
||||||
|
@inject(ThemeService)
|
||||||
|
private readonly themeService: ThemeService;
|
||||||
|
@inject(MessageService)
|
||||||
|
private readonly messageService: MessageService;
|
||||||
|
@inject(WindowServiceExt)
|
||||||
|
private readonly windowService: WindowServiceExt;
|
||||||
|
|
||||||
|
onStart(): void {
|
||||||
|
this.hostedPlugin.didStart.then(() => this.cleanupObsoleteThemes());
|
||||||
|
}
|
||||||
|
|
||||||
|
private async cleanupObsoleteThemes(): Promise<void> {
|
||||||
|
const persistedThemes = await getThemesFromIndexedDB();
|
||||||
|
const obsoleteThemeIds = collectObsoleteThemeIds(
|
||||||
|
persistedThemes,
|
||||||
|
this.summary.summary
|
||||||
|
);
|
||||||
|
if (!obsoleteThemeIds.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const firstWindow = await this.windowService.isFirstWindow();
|
||||||
|
if (firstWindow) {
|
||||||
|
await this.removeObsoleteThemesFromIndexedDB(obsoleteThemeIds);
|
||||||
|
this.unregisterObsoleteThemes(obsoleteThemeIds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private removeObsoleteThemesFromIndexedDB(themeIds: string[]): Promise<void> {
|
||||||
|
return themeIds.reduce(async (previousTask, themeId) => {
|
||||||
|
await previousTask;
|
||||||
|
return deleteThemeFromIndexedDB(themeId);
|
||||||
|
}, Promise.resolve());
|
||||||
|
}
|
||||||
|
|
||||||
|
private unregisterObsoleteThemes(themeIds: string[]): void {
|
||||||
|
const currentTheme = this.themeService.getCurrentTheme();
|
||||||
|
const switchToCompatibleTheme = themeIds.includes(currentTheme.id);
|
||||||
|
for (const themeId of themeIds) {
|
||||||
|
delete this.themeService['themes'][themeId];
|
||||||
|
}
|
||||||
|
this.themeService['doUpdateColorThemePreference']();
|
||||||
|
if (switchToCompatibleTheme) {
|
||||||
|
this.themeService.setCurrentTheme(
|
||||||
|
compatibleBuiltInTheme(currentTheme).id,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
wait(250).then(() =>
|
||||||
|
requestAnimationFrame(() =>
|
||||||
|
this.messageService.info(
|
||||||
|
nls.localize(
|
||||||
|
'arduino/theme/currentThemeNotFound',
|
||||||
|
'Could not find the currently selected theme: {0}. Arduino IDE has picked a built-in theme compatible with the missing one.',
|
||||||
|
currentTheme.label
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An indexedDB registered theme is obsolete if it is in the indexedDB but was registered
|
||||||
|
* from neither a `vsix` nor `compiled` source during the app startup.
|
||||||
|
*/
|
||||||
|
export function collectObsoleteThemeIds(
|
||||||
|
indexedDBThemes: MonacoThemeState[],
|
||||||
|
summary: Record<MonacoThemeRegistrationSource, string[]>
|
||||||
|
): string[] {
|
||||||
|
const vsixThemeIds = summary['vsix'];
|
||||||
|
const compiledThemeIds = summary['compiled'];
|
||||||
|
return indexedDBThemes
|
||||||
|
.map(({ id }) => id)
|
||||||
|
.filter(
|
||||||
|
(id) => !vsixThemeIds.includes(id) && !compiledThemeIds.includes(id)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
179
arduino-ide-extension/src/test/browser/theming.test.ts
Normal file
179
arduino-ide-extension/src/test/browser/theming.test.ts
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
import { enableJSDOM } from '@theia/core/lib/browser/test/jsdom';
|
||||||
|
const disableJSDOM = enableJSDOM();
|
||||||
|
|
||||||
|
import { BuiltinThemeProvider } from '@theia/core/lib/browser/theming';
|
||||||
|
import { Theme } from '@theia/core/lib/common/theme';
|
||||||
|
import { expect } from 'chai';
|
||||||
|
import {
|
||||||
|
ArduinoThemeType,
|
||||||
|
ArduinoThemes,
|
||||||
|
arduinoThemeTypeOf,
|
||||||
|
darkThemeLabel,
|
||||||
|
deprecatedThemeLabel,
|
||||||
|
hcThemeLabel,
|
||||||
|
lightThemeLabel,
|
||||||
|
themeLabelForSettings,
|
||||||
|
userConfigurableThemes,
|
||||||
|
userThemeLabel,
|
||||||
|
} from '../../browser/theia/core/theming';
|
||||||
|
|
||||||
|
disableJSDOM();
|
||||||
|
|
||||||
|
const testTheme: Theme = {
|
||||||
|
id: 'testTheme',
|
||||||
|
label: 'Test Theme',
|
||||||
|
type: 'light',
|
||||||
|
};
|
||||||
|
const anotherTestTheme: Theme = {
|
||||||
|
id: 'anotherTestTheme',
|
||||||
|
label: 'Another Test Theme',
|
||||||
|
type: 'light',
|
||||||
|
};
|
||||||
|
const darkTestTheme: Theme = {
|
||||||
|
id: 'darkTestTheme',
|
||||||
|
label: 'Dark Test Theme',
|
||||||
|
type: 'dark',
|
||||||
|
};
|
||||||
|
const anotherDarkTestTheme: Theme = {
|
||||||
|
id: 'anotherTestTheme',
|
||||||
|
label: 'AAAnother Dark Test Theme',
|
||||||
|
type: 'dark',
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('theming', () => {
|
||||||
|
describe('userConfigurableThemes', () => {
|
||||||
|
it('should show only built-in and user installed themes but not deprecated (Theia) ones if current theme is a built-in', () => {
|
||||||
|
const actual = userConfigurableThemes({
|
||||||
|
themes: () => [
|
||||||
|
BuiltinThemeProvider.darkTheme,
|
||||||
|
BuiltinThemeProvider.lightTheme,
|
||||||
|
ArduinoThemes.dark,
|
||||||
|
ArduinoThemes.light,
|
||||||
|
testTheme,
|
||||||
|
BuiltinThemeProvider.hcTheme,
|
||||||
|
anotherTestTheme,
|
||||||
|
],
|
||||||
|
currentTheme: () => BuiltinThemeProvider.hcTheme,
|
||||||
|
}).reduce((acc, curr) => acc.concat(curr), []);
|
||||||
|
expect(actual.length).to.be.equal(5);
|
||||||
|
expect(actual[0].id).to.be.equal(ArduinoThemes.light.id);
|
||||||
|
expect(actual[1].id).to.be.equal(ArduinoThemes.dark.id);
|
||||||
|
expect(actual[2].id).to.be.equal(BuiltinThemeProvider.hcTheme.id);
|
||||||
|
expect(actual[3].id).to.be.equal(anotherTestTheme.id);
|
||||||
|
expect(actual[4].id).to.be.equal(testTheme.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show only built-in and user installed themes but not deprecated (Theia) ones if current theme is a user', () => {
|
||||||
|
const actual = userConfigurableThemes({
|
||||||
|
themes: () => [
|
||||||
|
BuiltinThemeProvider.hcTheme,
|
||||||
|
BuiltinThemeProvider.lightTheme,
|
||||||
|
BuiltinThemeProvider.darkTheme,
|
||||||
|
ArduinoThemes.dark,
|
||||||
|
testTheme,
|
||||||
|
anotherTestTheme,
|
||||||
|
ArduinoThemes.light,
|
||||||
|
],
|
||||||
|
currentTheme: () => testTheme,
|
||||||
|
}).reduce((acc, curr) => acc.concat(curr), []);
|
||||||
|
expect(actual.length).to.be.equal(5);
|
||||||
|
expect(actual[0].id).to.be.equal(ArduinoThemes.light.id);
|
||||||
|
expect(actual[1].id).to.be.equal(ArduinoThemes.dark.id);
|
||||||
|
expect(actual[2].id).to.be.equal(BuiltinThemeProvider.hcTheme.id);
|
||||||
|
expect(actual[3].id).to.be.equal(anotherTestTheme.id);
|
||||||
|
expect(actual[4].id).to.be.equal(testTheme.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show built-in, user installed, and deprecated (Theia) themes if current theme is a deprecated (Theia)', () => {
|
||||||
|
const actual = userConfigurableThemes({
|
||||||
|
themes: () => [
|
||||||
|
ArduinoThemes.dark,
|
||||||
|
ArduinoThemes.light,
|
||||||
|
testTheme,
|
||||||
|
BuiltinThemeProvider.hcTheme,
|
||||||
|
anotherTestTheme,
|
||||||
|
darkTestTheme,
|
||||||
|
anotherDarkTestTheme,
|
||||||
|
BuiltinThemeProvider.lightTheme,
|
||||||
|
BuiltinThemeProvider.darkTheme,
|
||||||
|
],
|
||||||
|
currentTheme: () => BuiltinThemeProvider.lightTheme,
|
||||||
|
}).reduce((acc, curr) => acc.concat(curr), []);
|
||||||
|
expect(actual.length).to.be.equal(8);
|
||||||
|
expect(actual[0].id).to.be.equal(ArduinoThemes.light.id);
|
||||||
|
expect(actual[1].id).to.be.equal(ArduinoThemes.dark.id);
|
||||||
|
expect(actual[2].id).to.be.equal(BuiltinThemeProvider.hcTheme.id);
|
||||||
|
expect(actual[3].id).to.be.equal(anotherTestTheme.id);
|
||||||
|
expect(actual[4].id).to.be.equal(testTheme.id);
|
||||||
|
expect(actual[5].id).to.be.equal(anotherDarkTestTheme.id);
|
||||||
|
expect(actual[6].id).to.be.equal(darkTestTheme.id);
|
||||||
|
expect(actual[7].id).to.be.equal(BuiltinThemeProvider.lightTheme.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should group the themes by arduino theme types', () => {
|
||||||
|
const actual = userConfigurableThemes({
|
||||||
|
themes: () => [
|
||||||
|
ArduinoThemes.dark,
|
||||||
|
ArduinoThemes.light,
|
||||||
|
testTheme,
|
||||||
|
BuiltinThemeProvider.hcTheme,
|
||||||
|
anotherTestTheme,
|
||||||
|
darkTestTheme,
|
||||||
|
anotherDarkTestTheme,
|
||||||
|
BuiltinThemeProvider.lightTheme,
|
||||||
|
BuiltinThemeProvider.darkTheme,
|
||||||
|
],
|
||||||
|
currentTheme: () => BuiltinThemeProvider.lightTheme,
|
||||||
|
});
|
||||||
|
expect(actual.length).to.be.equal(3);
|
||||||
|
expect(actual[0].length).to.be.equal(3);
|
||||||
|
expect(actual[1].length).to.be.equal(4);
|
||||||
|
expect(actual[2].length).to.be.equal(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('arduinoThemeTypeOf', () => {
|
||||||
|
(
|
||||||
|
[
|
||||||
|
[BuiltinThemeProvider.lightTheme, 'deprecated'],
|
||||||
|
[BuiltinThemeProvider.darkTheme, 'deprecated'],
|
||||||
|
[BuiltinThemeProvider.hcTheme, 'built-in'],
|
||||||
|
[ArduinoThemes.light, 'built-in'],
|
||||||
|
[ArduinoThemes.dark, 'built-in'],
|
||||||
|
[testTheme, 'user'],
|
||||||
|
[anotherTestTheme, 'user'],
|
||||||
|
[darkTestTheme, 'user'],
|
||||||
|
[anotherDarkTestTheme, 'user'],
|
||||||
|
] as [Theme, ArduinoThemeType][]
|
||||||
|
).map(([theme, expected]) =>
|
||||||
|
it(`should detect the '${theme.label}' theme as '${expected}' theme`, () =>
|
||||||
|
expect(arduinoThemeTypeOf(theme)).to.be.equal(expected))
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('themeLabelForSettings', () => {
|
||||||
|
(
|
||||||
|
[
|
||||||
|
[
|
||||||
|
BuiltinThemeProvider.lightTheme,
|
||||||
|
deprecatedThemeLabel(BuiltinThemeProvider.lightTheme),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
BuiltinThemeProvider.darkTheme,
|
||||||
|
deprecatedThemeLabel(BuiltinThemeProvider.darkTheme),
|
||||||
|
],
|
||||||
|
[BuiltinThemeProvider.hcTheme, hcThemeLabel],
|
||||||
|
[ArduinoThemes.light, lightThemeLabel],
|
||||||
|
[ArduinoThemes.dark, darkThemeLabel],
|
||||||
|
[testTheme, userThemeLabel(testTheme)],
|
||||||
|
[anotherTestTheme, userThemeLabel(anotherTestTheme)],
|
||||||
|
[darkTestTheme, userThemeLabel(darkTestTheme)],
|
||||||
|
[anotherDarkTestTheme, userThemeLabel(anotherDarkTestTheme)],
|
||||||
|
] as [Theme, string][]
|
||||||
|
).map(([theme, expected]) => {
|
||||||
|
it(`should map the theme with ID '${theme.id}' to ${expected} in the settings UI`, () => {
|
||||||
|
expect(themeLabelForSettings(theme)).to.be.equal(expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -474,6 +474,14 @@
|
|||||||
"dismissSurvey": "Don't show again",
|
"dismissSurvey": "Don't show again",
|
||||||
"surveyMessage": "Please help us improve by answering this super short survey. We value our community and would like to get to know our supporters a little better."
|
"surveyMessage": "Please help us improve by answering this super short survey. We value our community and would like to get to know our supporters a little better."
|
||||||
},
|
},
|
||||||
|
"theme": {
|
||||||
|
"currentThemeNotFound": "Could not find the currently selected theme: {0}. Arduino IDE has picked a built-in theme compatible with the missing one.",
|
||||||
|
"dark": "Dark",
|
||||||
|
"deprecated": "{0} (deprecated)",
|
||||||
|
"hc": "High Contrast",
|
||||||
|
"light": "Light",
|
||||||
|
"user": "{0} (user)"
|
||||||
|
},
|
||||||
"title": {
|
"title": {
|
||||||
"cloud": "Cloud"
|
"cloud": "Cloud"
|
||||||
},
|
},
|
||||||
|
Loading…
x
Reference in New Issue
Block a user