From c8e447cc5746376b2231fe08e03014218bf6c979 Mon Sep 17 00:00:00 2001 From: Akos Kitta Date: Mon, 27 Mar 2023 18:33:57 +0200 Subject: [PATCH] feat: new icons + dispatch based on the theme Signed-off-by: Akos Kitta --- .../contributions/open-recent-sketch.ts | 54 ++++++++++++----- .../src/browser/native-image-cache.ts | 55 +++++++++++++----- .../src/node/static/icons/cloud-dark.png | Bin 0 -> 319 bytes .../src/node/static/icons/cloud-dark@2x.png | Bin 0 -> 586 bytes .../src/node/static/icons/cloud-dark@3x.png | Bin 0 -> 834 bytes .../src/node/static/icons/cloud-light.png | Bin 0 -> 291 bytes .../src/node/static/icons/cloud-light@2x.png | Bin 0 -> 509 bytes .../src/node/static/icons/cloud-light@3x.png | Bin 0 -> 686 bytes .../src/node/static/icons/cloud.png | Bin 9457 -> 0 bytes 9 files changed, 81 insertions(+), 28 deletions(-) create mode 100644 arduino-ide-extension/src/node/static/icons/cloud-dark.png create mode 100644 arduino-ide-extension/src/node/static/icons/cloud-dark@2x.png create mode 100644 arduino-ide-extension/src/node/static/icons/cloud-dark@3x.png create mode 100644 arduino-ide-extension/src/node/static/icons/cloud-light.png create mode 100644 arduino-ide-extension/src/node/static/icons/cloud-light@2x.png create mode 100644 arduino-ide-extension/src/node/static/icons/cloud-light@3x.png delete mode 100644 arduino-ide-extension/src/node/static/icons/cloud.png diff --git a/arduino-ide-extension/src/browser/contributions/open-recent-sketch.ts b/arduino-ide-extension/src/browser/contributions/open-recent-sketch.ts index c17fd79a..dfc02b7a 100644 --- a/arduino-ide-extension/src/browser/contributions/open-recent-sketch.ts +++ b/arduino-ide-extension/src/browser/contributions/open-recent-sketch.ts @@ -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 { 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; + } } diff --git a/arduino-ide-extension/src/browser/native-image-cache.ts b/arduino-ide-extension/src/browser/native-image-cache.ts index 268a96ae..13948fee 100644 --- a/arduino-ide-extension/src/browser/native-image-cache.ts +++ b/arduino-ide-extension/src/browser/native-image-cache.ts @@ -12,17 +12,32 @@ import fetch from 'cross-fetch'; const nativeImageIdentifierLiterals = ['cloud'] as const; export type NativeImageIdentifier = typeof nativeImageIdentifierLiterals[number]; -export const nativeImages: Record = { - 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' && + (arg).light !== undefined && + (arg).dark !== undefined + ); +} + +type Image = NativeImage | ThemeNativeImage; + @injectable() export class NativeImageCache implements FrontendApplicationContribution { - private readonly cache = new Map(); - private readonly loading = new Map< - NativeImageIdentifier, - Promise - >(); + private readonly cache = new Map(); + private readonly loading = new Map>(); 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 { + async getImage(identifier: NativeImageIdentifier): Promise { const image = this.cache.get(identifier); if (image) { return image; } let loading = this.loading.get(identifier); if (!loading) { - const deferred = new Deferred(); + const deferred = new Deferred(); 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 { - const path = `nativeImage/${nativeImages[identifier]}`; + private async fetchImage(identifier: NativeImageIdentifier): Promise { + 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 { + const path = `nativeImage/${filename}`; const endpoint = new Endpoint({ path }).getRestUrl().toString(); const response = await fetch(endpoint); const arrayBuffer = await response.arrayBuffer(); diff --git a/arduino-ide-extension/src/node/static/icons/cloud-dark.png b/arduino-ide-extension/src/node/static/icons/cloud-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..464646dd03e5c702e9fbea9b26f26b82bf197606 GIT binary patch literal 319 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz&H|6fVg?3oVGw3ym^DWND9BhG znBBdfl2dAR;}^)xU)=1$x)z%gTZ7?pXq~>tyedjTx}8F zvPLlP*wGUiM`sm1^eyv9H@g(1Rm}P!TrS(`_op>B+0|jrl_sa{=5GwKUm# KpUXO@geCyIR(5&- literal 0 HcmV?d00001 diff --git a/arduino-ide-extension/src/node/static/icons/cloud-dark@2x.png b/arduino-ide-extension/src/node/static/icons/cloud-dark@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..14af1ce5efa68f16c84535fd297d30f041b899e0 GIT binary patch literal 586 zcmV-Q0=4~#P)3 z!!QsA;5Uc?7{Q%@c7t>Rx&hsQZcuduHh>8b6SPb~H;6Z&8<;!A6+*(-#G#Gt-Xop( zk*cyU$N8gxQc5YMlu}A5rLIkdGjPt;^yh(MN@0Ak1I6CQdsS6Cl!PX}q-b1s{MjXP zN0TrVTNkgPm{P1L-rVmbe);d_`im~2? zrI=GAwHRPX0vh~u_pw-t871L>NH>V!+Rkk-K0~p>08tf?$lE{Sw?B!kg5DM2+D(o@ zI=BZd5$P8FEs&lkdNEu9yP-`nh}T}%uECdF^JA=Ck9|U?9^l#~Z;P}_Ut-a5i9DHr z3@3kN(IE+45|C2I@5|!EK0#as`2Pv{PM#T@*e3)lz?Xi5{E7PnsRC-0LEI-u33x;~ z>d*rz0S4u`4?U22fI~s32mCqLsMBCcqy$)$!-gJ63D~0?HuOMBzz53l8hRi#L0&(3 zBb3Gs>@btinIKpJe&_Uz{B5KIk$4#LRlqmD26?hH`HA!aaT)YgFh`y%f<-%a(bZpw zblM2<7x_}!AZ=W+JjPkqyTRVq;=UScw1*{wG=3c6Jqs_}FGGGLQA#PLlu}A5rPOu! Y29LF^%08_UXaE2J07*qoM6N<$f*H^Hy8r+H literal 0 HcmV?d00001 diff --git a/arduino-ide-extension/src/node/static/icons/cloud-dark@3x.png b/arduino-ide-extension/src/node/static/icons/cloud-dark@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..4d857c1d5a14b41bca08a8c2bae5b9144a234787 GIT binary patch literal 834 zcmV-I1HJr-P)qlui&fC`^!a0>T9L2F(WI39DSMw8?vpl$@Ra zyVE&$Rhv{#dX{B5A^-pY00000000000072jB1aZNnCfAs<%5c zIW%i67dqYDNMR)d(8w?w|ItB$@J0%V!&rvh_))yZxRJuLkAReV8X1pE9R=T|kP-oi zdB2wNh$-l-R4U4vf>Q6VWnAKn=!+DRbp&Vt7J8V=_-u3pyp;lSpM*ckIK^d@S_;S( zfb;?Hl@w1Futz{X~l0~ko<-N}V`axpa8N6lepoAe12FU@=0k9;9hp;5>iv~q7uVX6c?8h=UCe7orV)@X!7G84dF{xW zf@*9yCPsm0FKNWfL7quS}Ius zuoif=mP*zD{1$lKTPj%tuo8H!ER{TKK@T$ST@N|6Is-kkRP@Y(ZUt7?){34H5PN^T z6X)TN#Cz7KHZ*c&?6!J;^9yX9@dng{!AXxK+&Asm? zgPDTz2FFtzJqK7iIJu-tn9G}|DKAgZb`)7zc=>|Kw%@jw%DbYT{!dmfV^V^EqO5Zg z?hlKe7d=k8Rgpd2b+-BRa--l~5z}|2=lq`iL~^?2U+(+YIP9b3V&i_^J`(!-zUqEu zO|9=H>XGuAQ%|c-{BZQT@DZVd83r=nPq|-bIbCuz-z;&`%K)R=kM=VS8NF?IKU^;O zpka|-&su2PSMyPoId+5n|26wm^}_Y$n`oakRsJ}4-HdPN?yHL`-;v-gJo`I#qfEgp zsY_b_9{6r>n46(vz3lM1*$J+j@AyvD|JU7kJI^^Pe7E+&Ql|{rfC=pntQ4;_uzw0H zk>xNwFrokTI_8DtO{SGMqh3$g>k#I|8!@Rr^?G9fv)Gg5o23d@8u&kbJjC$tr{wQ* z>+^lWCoU9x@crufCGR`y@0|amv0+K+R`X;_hQpot?ZG)ssY|B5;aQ=4Zk6rv!2O~U wv+iD*8o|0J?9FfciLx;TbZFuuKWJNLGOK%*&p7rBrmdCgC-o`ff%5U-=>tFO#11*2epB?$!Ps92zhe^%*|87&gnV$W9p8fv21g6G`Epqu(Z6ot>RuuQN z=P#3={am^Egqw@zzsojrPdTkNIlCoT$y#>-XZ2C7UyEi1e9B8dwMH~xX@sks_u2%r zuFwY?pUmzs2o78tadhi5H^()it2fNstk9D2{m(p=xjUCcuzpQjY@Fw`&*N$9dX1wt zs?}f1-O`H_<23)ZuM3tkoPFu^=|H2?w*u=9&rK44XKVa(6YJ6W8##Tf^V8QJ%JS5; zoH12q&+VXZkGMY=R{lP7{bAu1_nTK2{SfEq91ZU zbv&ju&SN{E9?2FdU7%YrbDM9JbO2}EeVyht;t#_tj%C!^ZeOU;z;@=$tp>LbSwLal zAIcS`(;Tm{{Sn>sP8Z0(!4(&MTH$^`65dFa)2t{C&r7w3n& zDA#BjrGL}PP+h6Y9=v*kwWn^SY56RM=%Bdse!`15^}Fp)%PL*I=yLg-;#R%#{ESPI zD@!GNPh39RdoS~p=EwJ0mnLL?y}u;1Yi;2Fg<9*|bB}J~fyXkC(IEJrG5*y28jeQk P+aPgIS3j3^P6zq`3TT32JQBD8=ctDJ~ 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