feat: new icons + dispatch based on the theme
Signed-off-by: Akos Kitta <a.kitta@arduino.cc>
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
|
BIN
arduino-ide-extension/src/node/static/icons/cloud-dark.png
Normal file
After Width: | Height: | Size: 319 B |
BIN
arduino-ide-extension/src/node/static/icons/cloud-dark@2x.png
Normal file
After Width: | Height: | Size: 586 B |
BIN
arduino-ide-extension/src/node/static/icons/cloud-dark@3x.png
Normal file
After Width: | Height: | Size: 834 B |
BIN
arduino-ide-extension/src/node/static/icons/cloud-light.png
Normal file
After Width: | Height: | Size: 291 B |
BIN
arduino-ide-extension/src/node/static/icons/cloud-light@2x.png
Normal file
After Width: | Height: | Size: 509 B |
BIN
arduino-ide-extension/src/node/static/icons/cloud-light@3x.png
Normal file
After Width: | Height: | Size: 686 B |
Before Width: | Height: | Size: 9.2 KiB |