feat: new icons + dispatch based on the theme

Signed-off-by: Akos Kitta <a.kitta@arduino.cc>
This commit is contained in:
Akos Kitta 2023-03-27 18:33:57 +02:00
parent 2e7f2d94bd
commit c8e447cc57
9 changed files with 81 additions and 28 deletions

View File

@ -1,4 +1,5 @@
import { NativeImage } from '@theia/core/electron-shared/electron'; import { NativeImage } from '@theia/core/electron-shared/electron';
import { ThemeService } from '@theia/core/lib/browser/theming';
import { import {
Disposable, Disposable,
DisposableCollection, DisposableCollection,
@ -9,7 +10,11 @@ import { inject, injectable } from '@theia/core/shared/inversify';
import { SketchesError } from '../../common/protocol'; import { SketchesError } from '../../common/protocol';
import { ConfigServiceClient } from '../config/config-service-client'; import { ConfigServiceClient } from '../config/config-service-client';
import { ArduinoMenus } from '../menu/arduino-menus'; import { ArduinoMenus } from '../menu/arduino-menus';
import { NativeImageCache } from '../native-image-cache'; import {
isThemeNativeImage,
NativeImageCache,
ThemeNativeImage,
} from '../native-image-cache';
import { NotificationCenter } from '../notification-center'; import { NotificationCenter } from '../notification-center';
import { CloudSketchContribution } from './cloud-contribution'; import { CloudSketchContribution } from './cloud-contribution';
import { CommandRegistry, MenuModelRegistry, Sketch } from './contribution'; import { CommandRegistry, MenuModelRegistry, Sketch } from './contribution';
@ -27,21 +32,34 @@ export class OpenRecentSketch extends CloudSketchContribution {
private readonly imageCache: NativeImageCache; private readonly imageCache: NativeImageCache;
@inject(ConfigServiceClient) @inject(ConfigServiceClient)
private readonly configServiceClient: ConfigServiceClient; private readonly configServiceClient: ConfigServiceClient;
@inject(ThemeService)
private readonly themeService: ThemeService;
private readonly toDispose = new DisposableCollection(); private readonly toDisposeBeforeRegister = new DisposableCollection();
private cloudImage: NativeImage | undefined; private readonly toDispose = new DisposableCollection(
this.toDisposeBeforeRegister
);
private cloudImage: NativeImage | ThemeNativeImage;
override onStart(): void { override onStart(): void {
this.toDispose.pushAll([
this.notificationCenter.onRecentSketchesDidChange(({ sketches }) => this.notificationCenter.onRecentSketchesDidChange(({ sketches }) =>
this.refreshMenu(sketches) this.refreshMenu(sketches)
); ),
this.imageCache this.themeService.onDidColorThemeChange(() => this.update()),
.getImage('cloud') ]);
.then((image) => (this.cloudImage = image)); }
onStop(): void {
this.toDispose.dispose();
} }
override async onReady(): Promise<void> { override async onReady(): Promise<void> {
this.update(); this.update();
this.imageCache.getImage('cloud').then((image) => {
this.cloudImage = image;
this.update();
});
} }
override registerMenus(registry: MenuModelRegistry): void { override registerMenus(registry: MenuModelRegistry): void {
@ -65,7 +83,7 @@ export class OpenRecentSketch extends CloudSketchContribution {
private register(sketches: Sketch[]): void { private register(sketches: Sketch[]): void {
const order = 0; const order = 0;
this.toDispose.dispose(); this.toDisposeBeforeRegister.dispose();
for (const sketch of sketches) { for (const sketch of sketches) {
const { uri } = sketch; const { uri } = sketch;
const command = { id: `arduino-open-recent--${uri}` }; const command = { id: `arduino-open-recent--${uri}` };
@ -95,7 +113,7 @@ export class OpenRecentSketch extends CloudSketchContribution {
ArduinoMenus.FILE__OPEN_RECENT_SUBMENU, ArduinoMenus.FILE__OPEN_RECENT_SUBMENU,
menuAction menuAction
); );
this.toDispose.pushAll([ this.toDisposeBeforeRegister.pushAll([
new DisposableCollection( new DisposableCollection(
Disposable.create(() => Disposable.create(() =>
this.commandRegistry.unregisterCommand(command) this.commandRegistry.unregisterCommand(command)
@ -109,13 +127,23 @@ export class OpenRecentSketch extends CloudSketchContribution {
} }
private assignImage(sketch: Sketch, menuAction: MenuAction): MenuAction { private assignImage(sketch: Sketch, menuAction: MenuAction): MenuAction {
if (this.cloudImage) { const image = this.nativeImageForTheme();
if (image) {
const dataDirUri = this.configServiceClient.tryGetDataDirUri(); const dataDirUri = this.configServiceClient.tryGetDataDirUri();
const isCloud = this.createFeatures.isCloud(sketch, dataDirUri); const isCloud = this.createFeatures.isCloud(sketch, dataDirUri);
if (isCloud) { if (isCloud) {
Object.assign(menuAction, { nativeImage: this.cloudImage }); Object.assign(menuAction, { nativeImage: image });
} }
} }
return menuAction; return menuAction;
} }
private nativeImageForTheme(): NativeImage | undefined {
const image = this.cloudImage;
if (isThemeNativeImage(image)) {
const themeType = this.themeService.getCurrentTheme().type;
return themeType === 'light' ? image.light : image.dark;
}
return image;
}
} }

View File

@ -12,17 +12,32 @@ import fetch from 'cross-fetch';
const nativeImageIdentifierLiterals = ['cloud'] as const; const nativeImageIdentifierLiterals = ['cloud'] as const;
export type NativeImageIdentifier = export type NativeImageIdentifier =
typeof nativeImageIdentifierLiterals[number]; typeof nativeImageIdentifierLiterals[number];
export const nativeImages: Record<NativeImageIdentifier, string> = { export const nativeImages: Record<
cloud: 'cloud.png', NativeImageIdentifier,
string | { light: string; dark: string }
> = {
cloud: { light: 'cloud-light.png', dark: 'cloud-dark.png' },
}; };
export interface ThemeNativeImage {
readonly light: NativeImage;
readonly dark: NativeImage;
}
export function isThemeNativeImage(arg: unknown): arg is ThemeNativeImage {
return (
typeof arg === 'object' &&
(<ThemeNativeImage>arg).light !== undefined &&
(<ThemeNativeImage>arg).dark !== undefined
);
}
type Image = NativeImage | ThemeNativeImage;
@injectable() @injectable()
export class NativeImageCache implements FrontendApplicationContribution { export class NativeImageCache implements FrontendApplicationContribution {
private readonly cache = new Map<NativeImageIdentifier, NativeImage>(); private readonly cache = new Map<NativeImageIdentifier, Image>();
private readonly loading = new Map< private readonly loading = new Map<NativeImageIdentifier, Promise<Image>>();
NativeImageIdentifier,
Promise<NativeImage>
>();
onStart(): void { onStart(): void {
Object.keys(nativeImages).forEach((identifier: NativeImageIdentifier) => Object.keys(nativeImages).forEach((identifier: NativeImageIdentifier) =>
@ -30,21 +45,21 @@ export class NativeImageCache implements FrontendApplicationContribution {
); );
} }
tryGetImage(identifier: NativeImageIdentifier): NativeImage | undefined { tryGetImage(identifier: NativeImageIdentifier): Image | undefined {
return this.cache.get(identifier); return this.cache.get(identifier);
} }
async getImage(identifier: NativeImageIdentifier): Promise<NativeImage> { async getImage(identifier: NativeImageIdentifier): Promise<Image> {
const image = this.cache.get(identifier); const image = this.cache.get(identifier);
if (image) { if (image) {
return image; return image;
} }
let loading = this.loading.get(identifier); let loading = this.loading.get(identifier);
if (!loading) { if (!loading) {
const deferred = new Deferred<NativeImage>(); const deferred = new Deferred<Image>();
loading = deferred.promise; loading = deferred.promise;
this.loading.set(identifier, loading); this.loading.set(identifier, loading);
this.fetchIconData(identifier).then( this.fetchImage(identifier).then(
(image) => { (image) => {
if (!this.cache.has(identifier)) { if (!this.cache.has(identifier)) {
this.cache.set(identifier, image); this.cache.set(identifier, image);
@ -61,10 +76,20 @@ export class NativeImageCache implements FrontendApplicationContribution {
return loading; return loading;
} }
private async fetchIconData( private async fetchImage(identifier: NativeImageIdentifier): Promise<Image> {
identifier: NativeImageIdentifier const value = nativeImages[identifier];
): Promise<NativeImage> { if (typeof value === 'string') {
const path = `nativeImage/${nativeImages[identifier]}`; return this.fetchIconData(value);
}
const [light, dark] = await Promise.all([
this.fetchIconData(value.light),
this.fetchIconData(value.dark),
]);
return { light, dark };
}
private async fetchIconData(filename: string): Promise<NativeImage> {
const path = `nativeImage/${filename}`;
const endpoint = new Endpoint({ path }).getRestUrl().toString(); const endpoint = new Endpoint({ path }).getRestUrl().toString();
const response = await fetch(endpoint); const response = await fetch(endpoint);
const arrayBuffer = await response.arrayBuffer(); const arrayBuffer = await response.arrayBuffer();

Binary file not shown.

After

Width:  |  Height:  |  Size: 319 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 586 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 834 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 291 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 509 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 686 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB