| author | |
| committer | |
| log | 8331511b972242301bdaec328148a88b9b6cfa6c |
| tree | 98b8ee7b5a36cbc545c4e51ffd338eae43383285 |
| parent | 08650a7424f0f4d28c36d50938c0027e5b4cafff |
| signature | Signed by PGP key B5690EEEBB952194 |
Co-authored-by: codex <codex@users.noreply.github.com>2 files changed, 44 insertions(+), 5 deletions(-)
apps/desktop/src/electron/ElectronTheme.test.ts+22| ... | ... | @@ -8,6 +8,7 @@ const { onMock, removeListenerMock, themeState } = vi.hoisted(() => ({ |
| 8 | 8 | themeState: { |
| 9 | 9 | shouldUseDarkColors: true, |
| 10 | 10 | themeSource: "system", |
| 11 | setSourceError: null as unknown, | |
| 11 | 12 | }, |
| 12 | 13 | })); |
| 13 | 14 | |
| ... | ... | @@ -17,6 +18,9 @@ vi.mock("electron", () => ({ |
| 17 | 18 | return themeState.shouldUseDarkColors; |
| 18 | 19 | }, |
| 19 | 20 | set themeSource(value: string) { |
| 21 | if (themeState.setSourceError !== null) { | |
| 22 | throw themeState.setSourceError; | |
| 23 | } | |
| 20 | 24 | themeState.themeSource = value; |
| 21 | 25 | }, |
| 22 | 26 | on: onMock, |
| ... | ... | @@ -32,6 +36,7 @@ describe("ElectronTheme", () => { |
| 32 | 36 | removeListenerMock.mockClear(); |
| 33 | 37 | themeState.shouldUseDarkColors = true; |
| 34 | 38 | themeState.themeSource = "system"; |
| 39 | themeState.setSourceError = null; | |
| 35 | 40 | }); |
| 36 | 41 | |
| 37 | 42 | it.effect("scopes native theme update listeners", () => |
| ... | ... | @@ -49,4 +54,21 @@ describe("ElectronTheme", () => { |
| 49 | 54 | assert.deepEqual(removeListenerMock.mock.calls, [["updated", listener]]); |
| 50 | 55 | }).pipe(Effect.provide(ElectronTheme.layer)), |
| 51 | 56 | ); |
| 57 | ||
| 58 | it.effect("preserves the requested source and cause when setting the theme fails", () => | |
| 59 | Effect.gen(function* () { | |
| 60 | const cause = new Error("theme source failed"); | |
| 61 | themeState.setSourceError = cause; | |
| 62 | const electronTheme = yield* ElectronTheme.ElectronTheme; | |
| 63 | ||
| 64 | const error = yield* Effect.flip(electronTheme.setSource("dark")); | |
| 65 | ||
| 66 | assert.instanceOf(error, ElectronTheme.ElectronThemeSetSourceError); | |
| 67 | assert.isTrue(ElectronTheme.isElectronThemeSetSourceError(error)); | |
| 68 | assert.strictEqual(error.source, "dark"); | |
| 69 | assert.strictEqual(error.cause, cause); | |
| 70 | assert.include(error.message, "dark"); | |
| 71 | assert.notInclude(error.message, cause.message); | |
| 72 | }).pipe(Effect.provide(ElectronTheme.layer)), | |
| 73 | ); | |
| 52 | 74 | }); |
apps/desktop/src/electron/ElectronTheme.ts+22-5| ... | ... | @@ -1,16 +1,31 @@ |
| 1 | import type { DesktopTheme } from "@t3tools/contracts"; | |
| 1 | import { DesktopThemeSchema, type DesktopTheme } from "@t3tools/contracts"; | |
| 2 | 2 | import * as Context from "effect/Context"; |
| 3 | 3 | import * as Effect from "effect/Effect"; |
| 4 | 4 | import * as Layer from "effect/Layer"; |
| 5 | import * as Schema from "effect/Schema"; | |
| 5 | 6 | import * as Scope from "effect/Scope"; |
| 6 | 7 | |
| 7 | 8 | import * as Electron from "electron"; |
| 8 | 9 | |
| 10 | export class ElectronThemeSetSourceError extends Schema.TaggedErrorClass<ElectronThemeSetSourceError>()( | |
| 11 | "ElectronThemeSetSourceError", | |
| 12 | { | |
| 13 | source: DesktopThemeSchema, | |
| 14 | cause: Schema.Defect(), | |
| 15 | }, | |
| 16 | ) { | |
| 17 | override get message(): string { | |
| 18 | return `Failed to set the Electron theme source to ${this.source}.`; | |
| 19 | } | |
| 20 | } | |
| 21 | ||
| 22 | export const isElectronThemeSetSourceError = Schema.is(ElectronThemeSetSourceError); | |
| 23 | ||
| 9 | 24 | export class ElectronTheme extends Context.Service< |
| 10 | 25 | ElectronTheme, |
| 11 | 26 | { |
| 12 | 27 | readonly shouldUseDarkColors: Effect.Effect<boolean>; |
| 13 | readonly setSource: (theme: DesktopTheme) => Effect.Effect<void>; | |
| 28 | readonly setSource: (theme: DesktopTheme) => Effect.Effect<void, ElectronThemeSetSourceError>; | |
| 14 | 29 | readonly onUpdated: (listener: () => void) => Effect.Effect<void, never, Scope.Scope>; |
| 15 | 30 | } |
| 16 | 31 | >()("@t3tools/desktop/electron/ElectronTheme") {} |
| ... | ... | @@ -18,9 +33,11 @@ export class ElectronTheme extends Context.Service< |
| 18 | 33 | export const make = ElectronTheme.of({ |
| 19 | 34 | shouldUseDarkColors: Effect.sync(() => Electron.nativeTheme.shouldUseDarkColors), |
| 20 | 35 | setSource: (theme) => |
| 21 | Effect.suspend(() => { | |
| 22 | Electron.nativeTheme.themeSource = theme; | |
| 23 | return Effect.void; | |
| 36 | Effect.try({ | |
| 37 | try: () => { | |
| 38 | Electron.nativeTheme.themeSource = theme; | |
| 39 | }, | |
| 40 | catch: (cause) => new ElectronThemeSetSourceError({ source: theme, cause }), | |
| 24 | 41 | }), |
| 25 | 42 | onUpdated: (listener) => |
| 26 | 43 | Effect.acquireRelease( |