1import { DesktopThemeSchema, type DesktopTheme } from "@t3tools/contracts";
2import * as Context from "effect/Context";
3import * as Effect from "effect/Effect";
4import * as Layer from "effect/Layer";
5import * as Schema from "effect/Schema";
6import * as Scope from "effect/Scope";
7
8import * as Electron from "electron";
9
10export class ElectronThemeSetSourceError extends Schema.TaggedError<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
22export class ElectronTheme extends Context.Service<
23 ElectronTheme,
24 {
25 readonly shouldUseDarkColors: Effect.Effect<boolean>;
26 readonly setSource: (theme: DesktopTheme) => Effect.Effect<void, ElectronThemeSetSourceError>;
27 readonly onUpdated: (listener: () => void) => Effect.Effect<void, never, Scope.Scope>;
28 }
29>()("@t3tools/desktop/electron/ElectronTheme") {}
30
31/** @public Service construction is part of the canonical Effect module API. */
32export const make = ElectronTheme.of({
33 shouldUseDarkColors: Effect.sync(() => Electron.nativeTheme.shouldUseDarkColors),
34 setSource: (theme) =>
35 Effect.try({
36 try: () => {
37 Electron.nativeTheme.themeSource = theme;
38 },
39 catch: (cause) => new ElectronThemeSetSourceError({ source: theme, cause }),
40 }),
41 onUpdated: (listener) =>
42 Effect.acquireRelease(
43 Effect.suspend(() => {
44 Electron.nativeTheme.on("updated", listener);
45 return Effect.void;
46 }),
47 () =>
48 Effect.suspend(() => {
49 Electron.nativeTheme.removeListener("updated", listener);
50 return Effect.void;
51 }),
52 ),
53});
54
55export const layer = Layer.succeed(ElectronTheme, make);