Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 5540170

Browse files
Akos Kittakittaakos
Akos Kitta
authored andcommittedApr 14, 2023
feat: removed the non official themes from the UI
Closes #1283 Ref eclipse-theia/theia#11151 Signed-off-by: Akos Kitta <[email protected]>
1 parent 7cc252f commit 5540170

File tree

6 files changed

+642
-34
lines changed

6 files changed

+642
-34
lines changed
 

‎arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,6 @@ import {
238238
UploadFirmwareDialog,
239239
UploadFirmwareDialogProps,
240240
} from './dialogs/firmware-uploader/firmware-uploader-dialog';
241-
242241
import { UploadCertificate } from './contributions/upload-certificate';
243242
import {
244243
ArduinoFirmwareUploader,
@@ -328,9 +327,13 @@ import { NewCloudSketch } from './contributions/new-cloud-sketch';
328327
import { SketchbookCompositeWidget } from './widgets/sketchbook/sketchbook-composite-widget';
329328
import { WindowTitleUpdater } from './theia/core/window-title-updater';
330329
import { WindowTitleUpdater as TheiaWindowTitleUpdater } from '@theia/core/lib/browser/window/window-title-updater';
331-
import { ThemeServiceWithDB } from './theia/core/theming';
332-
import { ThemeServiceWithDB as TheiaThemeServiceWithDB } from '@theia/monaco/lib/browser/monaco-indexed-db';
333-
import { MonacoThemingService } from './theia/monaco/monaco-theming-service';
330+
import {
331+
MonacoThemingService,
332+
CleanupObsoleteThemes,
333+
ThemesRegistrationSummary,
334+
MonacoThemeRegistry,
335+
} from './theia/monaco/monaco-theming-service';
336+
import { MonacoThemeRegistry as TheiaMonacoThemeRegistry } from '@theia/monaco/lib/browser/textmate/monaco-theme-registry';
334337
import { MonacoThemingService as TheiaMonacoThemingService } from '@theia/monaco/lib/browser/monaco-theming-service';
335338
import { TypeHierarchyServiceProvider } from './theia/typehierarchy/type-hierarchy-service';
336339
import { TypeHierarchyServiceProvider as TheiaTypeHierarchyServiceProvider } from '@theia/typehierarchy/lib/browser/typehierarchy-service';
@@ -973,11 +976,19 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
973976
rebind(TheiaWindowTitleUpdater).toService(WindowTitleUpdater);
974977

975978
// register Arduino themes
976-
bind(ThemeServiceWithDB).toSelf().inSingletonScope();
977-
rebind(TheiaThemeServiceWithDB).toService(ThemeServiceWithDB);
978979
bind(MonacoThemingService).toSelf().inSingletonScope();
979980
rebind(TheiaMonacoThemingService).toService(MonacoThemingService);
980981

982+
// workaround for themes cannot be removed after registration
983+
// https://github.com/eclipse-theia/theia/issues/11151
984+
bind(CleanupObsoleteThemes).toSelf().inSingletonScope();
985+
bind(FrontendApplicationContribution).toService(
986+
CleanupObsoleteThemes
987+
);
988+
bind(ThemesRegistrationSummary).toSelf().inSingletonScope();
989+
bind(MonacoThemeRegistry).toSelf().inSingletonScope();
990+
rebind(TheiaMonacoThemeRegistry).toService(MonacoThemeRegistry);
991+
981992
// disable type-hierarchy support
982993
// https://github.com/eclipse-theia/theia/commit/16c88a584bac37f5cf3cc5eb92ffdaa541bda5be
983994
bind(TypeHierarchyServiceProvider).toSelf().inSingletonScope();

‎arduino-ide-extension/src/browser/dialogs/settings/settings-component.tsx

Lines changed: 50 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ import {
2424
} from '@theia/core/lib/common/i18n/localization';
2525
import SettingsStepInput from './settings-step-input';
2626
import { InterfaceScale } from '../../contributions/interface-scale';
27+
import {
28+
userConfigurableThemes,
29+
themeLabelForSettings,
30+
arduinoThemeTypeOf,
31+
} from '../../theia/core/theming';
32+
import { Theme } from '@theia/core/lib/common/theme';
2733

2834
const maxScale = InterfaceScale.ZoomLevel.toPercentage(
2935
InterfaceScale.ZoomLevel.MAX
@@ -218,14 +224,10 @@ export class SettingsComponent extends React.Component<
218224
<div className="flex-line">
219225
<select
220226
className="theia-select"
221-
value={this.props.themeService.getCurrentTheme().label}
227+
value={this.currentThemeLabel}
222228
onChange={this.themeDidChange}
223229
>
224-
{this.props.themeService.getThemes().map(({ id, label }) => (
225-
<option key={id} value={label}>
226-
{label}
227-
</option>
228-
))}
230+
{this.themeSelectOptions}
229231
</select>
230232
</div>
231233
<div className="flex-line">
@@ -333,6 +335,46 @@ export class SettingsComponent extends React.Component<
333335
);
334336
}
335337

338+
private get currentThemeLabel(): string {
339+
const currentTheme = this.props.themeService.getCurrentTheme();
340+
return themeLabelForSettings(currentTheme);
341+
}
342+
343+
private get separatedThemes(): (Theme | string)[] {
344+
const separatedThemes: (Theme | string)[] = [];
345+
const groupedThemes = userConfigurableThemes(this.props.themeService);
346+
for (const group of groupedThemes) {
347+
for (let i = 0; i < group.length; i++) {
348+
const theme = group[i];
349+
if (i === 0 && separatedThemes.length) {
350+
const arduinoThemeType = arduinoThemeTypeOf(theme);
351+
separatedThemes.push(`separator-${arduinoThemeType}`);
352+
}
353+
separatedThemes.push(theme);
354+
}
355+
}
356+
return separatedThemes;
357+
}
358+
359+
private get themeSelectOptions(): React.ReactNode[] {
360+
return this.separatedThemes.map((item) => {
361+
if (typeof item === 'string') {
362+
return (
363+
// &#9472; -> BOX DRAWINGS LIGHT HORIZONTAL
364+
<option key={item} disabled>
365+
&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;
366+
</option>
367+
);
368+
}
369+
const label = themeLabelForSettings(item);
370+
return (
371+
<option key={item.id} value={label}>
372+
{label}
373+
</option>
374+
);
375+
});
376+
}
377+
336378
private toSelectOptions(language: string | LanguageInfo): JSX.Element {
337379
const plain = typeof language === 'string';
338380
const key = plain ? language : language.languageId;
@@ -610,8 +652,8 @@ export class SettingsComponent extends React.Component<
610652
event: React.ChangeEvent<HTMLSelectElement>
611653
): void => {
612654
const { selectedIndex } = event.target.options;
613-
const theme = this.props.themeService.getThemes()[selectedIndex];
614-
if (theme) {
655+
const theme = this.separatedThemes[selectedIndex];
656+
if (theme && typeof theme !== 'string') {
615657
this.setState({ themeId: theme.id });
616658
if (this.props.themeService.getCurrentTheme().id !== theme.id) {
617659
this.props.themeService.setCurrentTheme(theme.id);
Lines changed: 170 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,186 @@
1-
import type { Theme } from '@theia/core/lib/common/theme';
2-
import { injectable } from '@theia/core/shared/inversify';
3-
import { ThemeServiceWithDB as TheiaThemeServiceWithDB } from '@theia/monaco/lib/browser/monaco-indexed-db';
1+
import {
2+
BuiltinThemeProvider,
3+
ThemeService,
4+
} from '@theia/core/lib/browser/theming';
5+
import { nls } from '@theia/core/lib/common/nls';
6+
import type { Theme, ThemeType } from '@theia/core/lib/common/theme';
7+
import { assertUnreachable } from '../../../common/utils';
48

59
export namespace ArduinoThemes {
6-
export const Light: Theme = {
10+
export const light: Theme = {
711
id: 'arduino-theme',
812
type: 'light',
913
label: 'Light (Arduino)',
1014
editorTheme: 'arduino-theme',
1115
};
12-
export const Dark: Theme = {
16+
export const dark: Theme = {
1317
id: 'arduino-theme-dark',
1418
type: 'dark',
1519
label: 'Dark (Arduino)',
1620
editorTheme: 'arduino-theme-dark',
1721
};
1822
}
1923

20-
@injectable()
21-
export class ThemeServiceWithDB extends TheiaThemeServiceWithDB {
22-
protected override init(): void {
23-
this.register(ArduinoThemes.Light, ArduinoThemes.Dark);
24-
super.init();
24+
const builtInThemeIds = new Set(
25+
[
26+
ArduinoThemes.light,
27+
ArduinoThemes.dark,
28+
BuiltinThemeProvider.hcTheme,
29+
// TODO: add the HC light theme after Theia 1.36
30+
].map(({ id }) => id)
31+
);
32+
const deprecatedThemeIds = new Set(
33+
[BuiltinThemeProvider.lightTheme, BuiltinThemeProvider.darkTheme].map(
34+
({ id }) => id
35+
)
36+
);
37+
38+
export const lightThemeLabel = nls.localize('arduino/theme/light', 'Light');
39+
export const darkThemeLabel = nls.localize('arduino/theme/dark', 'Dark');
40+
export const hcThemeLabel = nls.localize('arduino/theme/hc', 'High Contrast');
41+
export function userThemeLabel(theme: Theme): string {
42+
return nls.localize('arduino/theme/user', '{0} (user)', theme.label);
43+
}
44+
export function deprecatedThemeLabel(theme: Theme): string {
45+
return nls.localize(
46+
'arduino/theme/deprecated',
47+
'{0} (deprecated)',
48+
theme.label
49+
);
50+
}
51+
52+
export function themeLabelForSettings(theme: Theme): string {
53+
switch (theme.id) {
54+
case ArduinoThemes.light.id:
55+
return lightThemeLabel;
56+
case ArduinoThemes.dark.id:
57+
return darkThemeLabel;
58+
case BuiltinThemeProvider.hcTheme.id:
59+
return hcThemeLabel;
60+
case BuiltinThemeProvider.lightTheme.id: // fall-through
61+
case BuiltinThemeProvider.darkTheme.id:
62+
return deprecatedThemeLabel(theme);
63+
default:
64+
return userThemeLabel(theme);
65+
}
66+
}
67+
68+
export function compatibleBuiltInTheme(theme: Theme): Theme {
69+
switch (theme.type) {
70+
case 'light':
71+
return ArduinoThemes.light;
72+
case 'dark':
73+
return ArduinoThemes.dark;
74+
case 'hc':
75+
return BuiltinThemeProvider.hcTheme;
76+
default: {
77+
console.warn(
78+
`Unhandled theme type: ${theme.type}. Theme ID: ${theme.id}, label: ${theme.label}`
79+
);
80+
return ArduinoThemes.light;
81+
}
82+
}
83+
}
84+
85+
// For tests without DI
86+
interface ThemeProvider {
87+
themes(): Theme[];
88+
currentTheme(): Theme;
89+
}
90+
91+
/**
92+
* Returns with a list of built-in themes officially supported by IDE2 (https://github.com/arduino/arduino-ide/issues/1283).
93+
* The themes in the array follow the following order:
94+
* - 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
95+
* - followed by user installed (VSIX) themes grouped by theme type, then alphabetical order,
96+
* - if the `currentTheme` is either Light (Theia) or Dark (Theia), the last item of the array will be the selected theme with `(deprecated)` suffix.
97+
*/
98+
export function userConfigurableThemes(service: ThemeService): Theme[][];
99+
export function userConfigurableThemes(provider: ThemeProvider): Theme[][];
100+
export function userConfigurableThemes(
101+
serviceOrProvider: ThemeService | ThemeProvider
102+
): Theme[][] {
103+
const provider =
104+
serviceOrProvider instanceof ThemeService
105+
? {
106+
currentTheme: () => serviceOrProvider.getCurrentTheme(),
107+
themes: () => serviceOrProvider.getThemes(),
108+
}
109+
: serviceOrProvider;
110+
const currentTheme = provider.currentTheme();
111+
const allThemes = provider
112+
.themes()
113+
.map((theme) => ({ ...theme, arduinoThemeType: arduinoThemeTypeOf(theme) }))
114+
.filter(
115+
(theme) =>
116+
theme.arduinoThemeType !== 'deprecated' || currentTheme.id === theme.id
117+
)
118+
.sort((left, right) => {
119+
const leftArduinoThemeType = left.arduinoThemeType;
120+
const rightArduinoThemeType = right.arduinoThemeType;
121+
if (leftArduinoThemeType === rightArduinoThemeType) {
122+
const result = themeTypeOrder[left.type] - themeTypeOrder[right.type];
123+
if (result) {
124+
return result;
125+
}
126+
return left.label.localeCompare(right.label); // alphabetical order
127+
}
128+
return (
129+
arduinoThemeTypeOrder[leftArduinoThemeType] -
130+
arduinoThemeTypeOrder[rightArduinoThemeType]
131+
);
132+
});
133+
const builtInThemes: Theme[] = [];
134+
const userThemes: Theme[] = [];
135+
const deprecatedThemes: Theme[] = [];
136+
allThemes.forEach((theme) => {
137+
const { arduinoThemeType } = theme;
138+
switch (arduinoThemeType) {
139+
case 'built-in':
140+
builtInThemes.push(theme);
141+
break;
142+
case 'user':
143+
userThemes.push(theme);
144+
break;
145+
case 'deprecated':
146+
deprecatedThemes.push(theme);
147+
break;
148+
default:
149+
assertUnreachable(arduinoThemeType);
150+
}
151+
});
152+
const groupedThemes: Theme[][] = [];
153+
if (builtInThemes.length) {
154+
groupedThemes.push(builtInThemes);
155+
}
156+
if (userThemes.length) {
157+
groupedThemes.push(userThemes);
158+
}
159+
if (deprecatedThemes.length) {
160+
groupedThemes.push(deprecatedThemes);
161+
}
162+
return groupedThemes;
163+
}
164+
165+
export type ArduinoThemeType = 'built-in' | 'user' | 'deprecated';
166+
const arduinoThemeTypeOrder: Record<ArduinoThemeType, number> = {
167+
'built-in': 0,
168+
user: 1,
169+
deprecated: 2,
170+
};
171+
const themeTypeOrder: Record<ThemeType, number> = {
172+
light: 0,
173+
dark: 1,
174+
hc: 2,
175+
};
176+
177+
export function arduinoThemeTypeOf(theme: Theme | string): ArduinoThemeType {
178+
const themeId = typeof theme === 'string' ? theme : theme.id;
179+
if (builtInThemeIds.has(themeId)) {
180+
return 'built-in';
181+
}
182+
if (deprecatedThemeIds.has(themeId)) {
183+
return 'deprecated';
25184
}
185+
return 'user';
26186
}
Lines changed: 218 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,231 @@
1-
import { injectable } from '@theia/core/shared/inversify';
2-
import { MonacoThemingService as TheiaMonacoThemingService } from '@theia/monaco/lib/browser/monaco-theming-service';
3-
import { ArduinoThemes } from '../core/theming';
1+
import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application';
2+
import { ThemeService } from '@theia/core/lib/browser/theming';
3+
import {
4+
Disposable,
5+
DisposableCollection,
6+
} from '@theia/core/lib/common/disposable';
7+
import { MessageService } from '@theia/core/lib/common/message-service';
8+
import { nls } from '@theia/core/lib/common/nls';
9+
import { deepClone } from '@theia/core/lib/common/objects';
10+
import { wait } from '@theia/core/lib/common/promise-util';
11+
import { inject, injectable } from '@theia/core/shared/inversify';
12+
import {
13+
MonacoThemeState,
14+
deleteTheme as deleteThemeFromIndexedDB,
15+
getThemes as getThemesFromIndexedDB,
16+
} from '@theia/monaco/lib/browser/monaco-indexed-db';
17+
import {
18+
MonacoTheme,
19+
MonacoThemingService as TheiaMonacoThemingService,
20+
} from '@theia/monaco/lib/browser/monaco-theming-service';
21+
import { MonacoThemeRegistry as TheiaMonacoThemeRegistry } from '@theia/monaco/lib/browser/textmate/monaco-theme-registry';
22+
import type { ThemeMix } from '@theia/monaco/lib/browser/textmate/monaco-theme-types';
23+
import { HostedPluginSupport } from '@theia/plugin-ext/lib/hosted/browser/hosted-plugin';
24+
import { ArduinoThemes, compatibleBuiltInTheme } from '../core/theming';
25+
import { WindowServiceExt } from '../core/window-service-ext';
26+
27+
type MonacoThemeRegistrationSource =
28+
/**
29+
* When reading JS/TS contributed theme from a JSON file. Such as the Arduino themes and the ones contributed by Theia.
30+
*/
31+
| 'compiled'
32+
/**
33+
* When reading and registering previous monaco themes from the `indexedDB`.
34+
*/
35+
| 'indexedDB'
36+
/**
37+
* Contributed by VS Code extensions when starting the app and loading the plugins.
38+
*/
39+
| 'vsix';
40+
41+
@injectable()
42+
export class ThemesRegistrationSummary {
43+
private readonly _summary: Record<MonacoThemeRegistrationSource, string[]> = {
44+
compiled: [],
45+
indexedDB: [],
46+
vsix: [],
47+
};
48+
49+
add(source: MonacoThemeRegistrationSource, themeId: string): void {
50+
const themeIds = this._summary[source];
51+
if (!themeIds.includes(themeId)) {
52+
themeIds.push(themeId);
53+
}
54+
}
55+
56+
get summary(): Record<MonacoThemeRegistrationSource, string[]> {
57+
return deepClone(this._summary);
58+
}
59+
}
60+
61+
@injectable()
62+
export class MonacoThemeRegistry extends TheiaMonacoThemeRegistry {
63+
@inject(ThemesRegistrationSummary)
64+
private readonly summary: ThemesRegistrationSummary;
65+
66+
private initializing = false;
67+
68+
override initializeDefaultThemes(): void {
69+
this.initializing = true;
70+
try {
71+
super.initializeDefaultThemes();
72+
} finally {
73+
this.initializing = false;
74+
}
75+
}
76+
77+
override setTheme(name: string, data: ThemeMix): void {
78+
super.setTheme(name, data);
79+
if (this.initializing) {
80+
this.summary.add('compiled', name);
81+
}
82+
}
83+
}
484

585
@injectable()
686
export class MonacoThemingService extends TheiaMonacoThemingService {
7-
override initialize(): void {
8-
super.initialize();
9-
const { Light, Dark } = ArduinoThemes;
87+
@inject(ThemesRegistrationSummary)
88+
private readonly summary: ThemesRegistrationSummary;
89+
90+
private themeRegistrationSource: MonacoThemeRegistrationSource | undefined;
91+
92+
protected override async restore(): Promise<void> {
93+
// The custom theme registration must happen before restoring the themes.
94+
// Otherwise, theme changes are not picked up.
95+
// https://github.com/arduino/arduino-ide/issues/1251#issuecomment-1436737702
96+
this.registerArduinoThemes();
97+
this.themeRegistrationSource = 'indexedDB';
98+
try {
99+
await super.restore();
100+
} finally {
101+
this.themeRegistrationSource = 'indexedDB';
102+
}
103+
}
104+
105+
private registerArduinoThemes(): void {
106+
const { light, dark } = ArduinoThemes;
10107
this.registerParsedTheme({
11-
id: Light.id,
12-
label: Light.label,
108+
id: light.id,
109+
label: light.label,
13110
uiTheme: 'vs',
14111
json: require('../../../../src/browser/data/default.color-theme.json'),
15112
});
16113
this.registerParsedTheme({
17-
id: Dark.id,
18-
label: Dark.label,
114+
id: dark.id,
115+
label: dark.label,
19116
uiTheme: 'vs-dark',
20117
json: require('../../../../src/browser/data/dark.color-theme.json'),
21118
});
22119
}
120+
121+
protected override doRegisterParsedTheme(
122+
state: MonacoThemeState
123+
): Disposable {
124+
const themeId = state.id;
125+
const source = this.themeRegistrationSource ?? 'compiled';
126+
const disposable = super.doRegisterParsedTheme(state);
127+
this.summary.add(source, themeId);
128+
return disposable;
129+
}
130+
131+
protected override async doRegister(
132+
theme: MonacoTheme,
133+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
134+
pending: { [uri: string]: Promise<any> },
135+
toDispose: DisposableCollection
136+
): Promise<void> {
137+
try {
138+
this.themeRegistrationSource = 'vsix';
139+
await super.doRegister(theme, pending, toDispose);
140+
} finally {
141+
this.themeRegistrationSource = undefined;
142+
}
143+
}
144+
}
145+
146+
/**
147+
* Workaround for removing VSIX themes from the indexedDB if they were not loaded during the app startup.
148+
*/
149+
@injectable()
150+
export class CleanupObsoleteThemes implements FrontendApplicationContribution {
151+
@inject(HostedPluginSupport)
152+
private readonly hostedPlugin: HostedPluginSupport;
153+
@inject(ThemesRegistrationSummary)
154+
private readonly summary: ThemesRegistrationSummary;
155+
@inject(ThemeService)
156+
private readonly themeService: ThemeService;
157+
@inject(MessageService)
158+
private readonly messageService: MessageService;
159+
@inject(WindowServiceExt)
160+
private readonly windowService: WindowServiceExt;
161+
162+
onStart(): void {
163+
this.hostedPlugin.didStart.then(() => this.cleanupObsoleteThemes());
164+
}
165+
166+
private async cleanupObsoleteThemes(): Promise<void> {
167+
const persistedThemes = await getThemesFromIndexedDB();
168+
const obsoleteThemeIds = collectObsoleteThemeIds(
169+
persistedThemes,
170+
this.summary.summary
171+
);
172+
if (!obsoleteThemeIds.length) {
173+
return;
174+
}
175+
const firstWindow = await this.windowService.isFirstWindow();
176+
if (firstWindow) {
177+
await this.removeObsoleteThemesFromIndexedDB(obsoleteThemeIds);
178+
this.unregisterObsoleteThemes(obsoleteThemeIds);
179+
}
180+
}
181+
182+
private removeObsoleteThemesFromIndexedDB(themeIds: string[]): Promise<void> {
183+
return themeIds.reduce(async (previousTask, themeId) => {
184+
await previousTask;
185+
return deleteThemeFromIndexedDB(themeId);
186+
}, Promise.resolve());
187+
}
188+
189+
private unregisterObsoleteThemes(themeIds: string[]): void {
190+
const currentTheme = this.themeService.getCurrentTheme();
191+
const switchToCompatibleTheme = themeIds.includes(currentTheme.id);
192+
for (const themeId of themeIds) {
193+
delete this.themeService['themes'][themeId];
194+
}
195+
this.themeService['doUpdateColorThemePreference']();
196+
if (switchToCompatibleTheme) {
197+
this.themeService.setCurrentTheme(
198+
compatibleBuiltInTheme(currentTheme).id,
199+
true
200+
);
201+
wait(250).then(() =>
202+
requestAnimationFrame(() =>
203+
this.messageService.info(
204+
nls.localize(
205+
'arduino/theme/currentThemeNotFound',
206+
'Could not find the currently selected theme: {0}. Arduino IDE has picked a built-in theme compatible with the missing one.',
207+
currentTheme.label
208+
)
209+
)
210+
)
211+
);
212+
}
213+
}
214+
}
215+
216+
/**
217+
* An indexedDB registered theme is obsolete if it is in the indexedDB but was registered
218+
* from neither a `vsix` nor `compiled` source during the app startup.
219+
*/
220+
export function collectObsoleteThemeIds(
221+
indexedDBThemes: MonacoThemeState[],
222+
summary: Record<MonacoThemeRegistrationSource, string[]>
223+
): string[] {
224+
const vsixThemeIds = summary['vsix'];
225+
const compiledThemeIds = summary['compiled'];
226+
return indexedDBThemes
227+
.map(({ id }) => id)
228+
.filter(
229+
(id) => !vsixThemeIds.includes(id) && !compiledThemeIds.includes(id)
230+
);
23231
}
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
import { enableJSDOM } from '@theia/core/lib/browser/test/jsdom';
2+
const disableJSDOM = enableJSDOM();
3+
4+
import { BuiltinThemeProvider } from '@theia/core/lib/browser/theming';
5+
import { Theme } from '@theia/core/lib/common/theme';
6+
import { expect } from 'chai';
7+
import {
8+
ArduinoThemeType,
9+
ArduinoThemes,
10+
arduinoThemeTypeOf,
11+
darkThemeLabel,
12+
deprecatedThemeLabel,
13+
hcThemeLabel,
14+
lightThemeLabel,
15+
themeLabelForSettings,
16+
userConfigurableThemes,
17+
userThemeLabel,
18+
} from '../../browser/theia/core/theming';
19+
20+
disableJSDOM();
21+
22+
const testTheme: Theme = {
23+
id: 'testTheme',
24+
label: 'Test Theme',
25+
type: 'light',
26+
};
27+
const anotherTestTheme: Theme = {
28+
id: 'anotherTestTheme',
29+
label: 'Another Test Theme',
30+
type: 'light',
31+
};
32+
const darkTestTheme: Theme = {
33+
id: 'darkTestTheme',
34+
label: 'Dark Test Theme',
35+
type: 'dark',
36+
};
37+
const anotherDarkTestTheme: Theme = {
38+
id: 'anotherTestTheme',
39+
label: 'AAAnother Dark Test Theme',
40+
type: 'dark',
41+
};
42+
43+
describe('theming', () => {
44+
describe('userConfigurableThemes', () => {
45+
it('should show only built-in and user installed themes but not deprecated (Theia) ones if current theme is a built-in', () => {
46+
const actual = userConfigurableThemes({
47+
themes: () => [
48+
BuiltinThemeProvider.darkTheme,
49+
BuiltinThemeProvider.lightTheme,
50+
ArduinoThemes.dark,
51+
ArduinoThemes.light,
52+
testTheme,
53+
BuiltinThemeProvider.hcTheme,
54+
anotherTestTheme,
55+
],
56+
currentTheme: () => BuiltinThemeProvider.hcTheme,
57+
}).reduce((acc, curr) => acc.concat(curr), []);
58+
expect(actual.length).to.be.equal(5);
59+
expect(actual[0].id).to.be.equal(ArduinoThemes.light.id);
60+
expect(actual[1].id).to.be.equal(ArduinoThemes.dark.id);
61+
expect(actual[2].id).to.be.equal(BuiltinThemeProvider.hcTheme.id);
62+
expect(actual[3].id).to.be.equal(anotherTestTheme.id);
63+
expect(actual[4].id).to.be.equal(testTheme.id);
64+
});
65+
66+
it('should show only built-in and user installed themes but not deprecated (Theia) ones if current theme is a user', () => {
67+
const actual = userConfigurableThemes({
68+
themes: () => [
69+
BuiltinThemeProvider.hcTheme,
70+
BuiltinThemeProvider.lightTheme,
71+
BuiltinThemeProvider.darkTheme,
72+
ArduinoThemes.dark,
73+
testTheme,
74+
anotherTestTheme,
75+
ArduinoThemes.light,
76+
],
77+
currentTheme: () => testTheme,
78+
}).reduce((acc, curr) => acc.concat(curr), []);
79+
expect(actual.length).to.be.equal(5);
80+
expect(actual[0].id).to.be.equal(ArduinoThemes.light.id);
81+
expect(actual[1].id).to.be.equal(ArduinoThemes.dark.id);
82+
expect(actual[2].id).to.be.equal(BuiltinThemeProvider.hcTheme.id);
83+
expect(actual[3].id).to.be.equal(anotherTestTheme.id);
84+
expect(actual[4].id).to.be.equal(testTheme.id);
85+
});
86+
87+
it('should show built-in, user installed, and deprecated (Theia) themes if current theme is a deprecated (Theia)', () => {
88+
const actual = userConfigurableThemes({
89+
themes: () => [
90+
ArduinoThemes.dark,
91+
ArduinoThemes.light,
92+
testTheme,
93+
BuiltinThemeProvider.hcTheme,
94+
anotherTestTheme,
95+
darkTestTheme,
96+
anotherDarkTestTheme,
97+
BuiltinThemeProvider.lightTheme,
98+
BuiltinThemeProvider.darkTheme,
99+
],
100+
currentTheme: () => BuiltinThemeProvider.lightTheme,
101+
}).reduce((acc, curr) => acc.concat(curr), []);
102+
expect(actual.length).to.be.equal(8);
103+
expect(actual[0].id).to.be.equal(ArduinoThemes.light.id);
104+
expect(actual[1].id).to.be.equal(ArduinoThemes.dark.id);
105+
expect(actual[2].id).to.be.equal(BuiltinThemeProvider.hcTheme.id);
106+
expect(actual[3].id).to.be.equal(anotherTestTheme.id);
107+
expect(actual[4].id).to.be.equal(testTheme.id);
108+
expect(actual[5].id).to.be.equal(anotherDarkTestTheme.id);
109+
expect(actual[6].id).to.be.equal(darkTestTheme.id);
110+
expect(actual[7].id).to.be.equal(BuiltinThemeProvider.lightTheme.id);
111+
});
112+
113+
it('should group the themes by arduino theme types', () => {
114+
const actual = userConfigurableThemes({
115+
themes: () => [
116+
ArduinoThemes.dark,
117+
ArduinoThemes.light,
118+
testTheme,
119+
BuiltinThemeProvider.hcTheme,
120+
anotherTestTheme,
121+
darkTestTheme,
122+
anotherDarkTestTheme,
123+
BuiltinThemeProvider.lightTheme,
124+
BuiltinThemeProvider.darkTheme,
125+
],
126+
currentTheme: () => BuiltinThemeProvider.lightTheme,
127+
});
128+
expect(actual.length).to.be.equal(3);
129+
expect(actual[0].length).to.be.equal(3);
130+
expect(actual[1].length).to.be.equal(4);
131+
expect(actual[2].length).to.be.equal(1);
132+
});
133+
});
134+
135+
describe('arduinoThemeTypeOf', () => {
136+
(
137+
[
138+
[BuiltinThemeProvider.lightTheme, 'deprecated'],
139+
[BuiltinThemeProvider.darkTheme, 'deprecated'],
140+
[BuiltinThemeProvider.hcTheme, 'built-in'],
141+
[ArduinoThemes.light, 'built-in'],
142+
[ArduinoThemes.dark, 'built-in'],
143+
[testTheme, 'user'],
144+
[anotherTestTheme, 'user'],
145+
[darkTestTheme, 'user'],
146+
[anotherDarkTestTheme, 'user'],
147+
] as [Theme, ArduinoThemeType][]
148+
).map(([theme, expected]) =>
149+
it(`should detect the '${theme.label}' theme as '${expected}' theme`, () =>
150+
expect(arduinoThemeTypeOf(theme)).to.be.equal(expected))
151+
);
152+
});
153+
154+
describe('themeLabelForSettings', () => {
155+
(
156+
[
157+
[
158+
BuiltinThemeProvider.lightTheme,
159+
deprecatedThemeLabel(BuiltinThemeProvider.lightTheme),
160+
],
161+
[
162+
BuiltinThemeProvider.darkTheme,
163+
deprecatedThemeLabel(BuiltinThemeProvider.darkTheme),
164+
],
165+
[BuiltinThemeProvider.hcTheme, hcThemeLabel],
166+
[ArduinoThemes.light, lightThemeLabel],
167+
[ArduinoThemes.dark, darkThemeLabel],
168+
[testTheme, userThemeLabel(testTheme)],
169+
[anotherTestTheme, userThemeLabel(anotherTestTheme)],
170+
[darkTestTheme, userThemeLabel(darkTestTheme)],
171+
[anotherDarkTestTheme, userThemeLabel(anotherDarkTestTheme)],
172+
] as [Theme, string][]
173+
).map(([theme, expected]) => {
174+
it(`should map the theme with ID '${theme.id}' to ${expected} in the settings UI`, () => {
175+
expect(themeLabelForSettings(theme)).to.be.equal(expected);
176+
});
177+
});
178+
});
179+
});

‎i18n/en.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,14 @@
474474
"dismissSurvey": "Don't show again",
475475
"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."
476476
},
477+
"theme": {
478+
"currentThemeNotFound": "Could not find the currently selected theme: {0}. Arduino IDE has picked a built-in theme compatible with the missing one.",
479+
"dark": "Dark",
480+
"deprecated": "{0} (deprecated)",
481+
"hc": "High Contrast",
482+
"light": "Light",
483+
"user": "{0} (user)"
484+
},
477485
"title": {
478486
"cloud": "Cloud"
479487
},

0 commit comments

Comments
 (0)
Please sign in to comment.