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 { ThemeService } from '@theia/core/lib/browser/theming';
import {
Disposable,
DisposableCollection,
@ -9,7 +10,11 @@ import { inject, injectable } from '@theia/core/shared/inversify';
import { SketchesError } from '../../common/protocol';
import { ConfigServiceClient } from '../config/config-service-client';
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 { CloudSketchContribution } from './cloud-contribution';
import { CommandRegistry, MenuModelRegistry, Sketch } from './contribution';
@ -27,21 +32,34 @@ export class OpenRecentSketch extends CloudSketchContribution {
private readonly imageCache: NativeImageCache;
@inject(ConfigServiceClient)
private readonly configServiceClient: ConfigServiceClient;
@inject(ThemeService)
private readonly themeService: ThemeService;
private readonly toDispose = new DisposableCollection();
private cloudImage: NativeImage | undefined;
private readonly toDisposeBeforeRegister = new DisposableCollection();
private readonly toDispose = new DisposableCollection(
this.toDisposeBeforeRegister
);
private cloudImage: NativeImage | ThemeNativeImage;
override onStart(): void {
this.notificationCenter.onRecentSketchesDidChange(({ sketches }) =>
this.refreshMenu(sketches)
);
this.imageCache
.getImage('cloud')
.then((image) => (this.cloudImage = image));
this.toDispose.pushAll([
this.notificationCenter.onRecentSketchesDidChange(({ sketches }) =>
this.refreshMenu(sketches)
),
this.themeService.onDidColorThemeChange(() => this.update()),
]);
}
onStop(): void {
this.toDispose.dispose();
}
override async onReady(): Promise<void> {
this.update();
this.imageCache.getImage('cloud').then((image) => {
this.cloudImage = image;
this.update();
});
}
override registerMenus(registry: MenuModelRegistry): void {
@ -65,7 +83,7 @@ export class OpenRecentSketch extends CloudSketchContribution {
private register(sketches: Sketch[]): void {
const order = 0;
this.toDispose.dispose();
this.toDisposeBeforeRegister.dispose();
for (const sketch of sketches) {
const { uri } = sketch;
const command = { id: `arduino-open-recent--${uri}` };
@ -95,7 +113,7 @@ export class OpenRecentSketch extends CloudSketchContribution {
ArduinoMenus.FILE__OPEN_RECENT_SUBMENU,
menuAction
);
this.toDispose.pushAll([
this.toDisposeBeforeRegister.pushAll([
new DisposableCollection(
Disposable.create(() =>
this.commandRegistry.unregisterCommand(command)
@ -109,13 +127,23 @@ export class OpenRecentSketch extends CloudSketchContribution {
}
private assignImage(sketch: Sketch, menuAction: MenuAction): MenuAction {
if (this.cloudImage) {
const image = this.nativeImageForTheme();
if (image) {
const dataDirUri = this.configServiceClient.tryGetDataDirUri();
const isCloud = this.createFeatures.isCloud(sketch, dataDirUri);
if (isCloud) {
Object.assign(menuAction, { nativeImage: this.cloudImage });
Object.assign(menuAction, { nativeImage: image });
}
}
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;
export type NativeImageIdentifier =
typeof nativeImageIdentifierLiterals[number];
export const nativeImages: Record<NativeImageIdentifier, string> = {
cloud: 'cloud.png',
export const nativeImages: Record<
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()
export class NativeImageCache implements FrontendApplicationContribution {
private readonly cache = new Map<NativeImageIdentifier, NativeImage>();
private readonly loading = new Map<
NativeImageIdentifier,
Promise<NativeImage>
>();
private readonly cache = new Map<NativeImageIdentifier, Image>();
private readonly loading = new Map<NativeImageIdentifier, Promise<Image>>();
onStart(): void {
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);
}
async getImage(identifier: NativeImageIdentifier): Promise<NativeImage> {
async getImage(identifier: NativeImageIdentifier): Promise<Image> {
const image = this.cache.get(identifier);
if (image) {
return image;
}
let loading = this.loading.get(identifier);
if (!loading) {
const deferred = new Deferred<NativeImage>();
const deferred = new Deferred<Image>();
loading = deferred.promise;
this.loading.set(identifier, loading);
this.fetchIconData(identifier).then(
this.fetchImage(identifier).then(
(image) => {
if (!this.cache.has(identifier)) {
this.cache.set(identifier, image);
@ -61,10 +76,20 @@ export class NativeImageCache implements FrontendApplicationContribution {
return loading;
}
private async fetchIconData(
identifier: NativeImageIdentifier
): Promise<NativeImage> {
const path = `nativeImage/${nativeImages[identifier]}`;
private async fetchImage(identifier: NativeImageIdentifier): Promise<Image> {
const value = nativeImages[identifier];
if (typeof value === 'string') {
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 response = await fetch(endpoint);
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