Skip to content
Source
Sign inSign up
Open History to see the last commit

DesktopAssets.ts

143 lines4.4 KB

1import * as Context from "effect/Context";
2import * as Effect from "effect/Effect";
3import * as FileSystem from "effect/FileSystem";
4import * as Layer from "effect/Layer";
5import * as Option from "effect/Option";
6import * as Schema from "effect/Schema";
7​
8import * as DesktopEnvironment from "./DesktopEnvironment.ts";
9​
10export interface DesktopIconPaths {
11 readonly ico: Option.Option<string>;
12 readonly icns: Option.Option<string>;
13 readonly png: Option.Option<string>;
15​
16export class DesktopAssetProbeError extends Schema.TaggedError<DesktopAssetProbeError>()(
17 "DesktopAssetProbeError",
18 {
19 fileName: Schema.String,
20 candidatePath: Schema.String,
21 cause: Schema.Defect(),
22 },
23) {
24 override get message(): string {
25 return `Failed to probe desktop asset "${this.fileName}" at ${this.candidatePath}.`;
26 }
28​
29export class DesktopAssets extends Context.Service<
30 DesktopAssets,
31 {
32 readonly iconPaths: Effect.Effect<DesktopIconPaths>;
33 readonly resolveResourcePath: (
34 fileName: string,
35 ) => Effect.Effect<Option.Option<string>, DesktopAssetProbeError>;
36 }
37>()("@t3tools/desktop/app/DesktopAssets") {}
38​
39const resolveResourcePath = Effect.fn("desktop.assets.resolveResourcePath")(function* (
40 fileName: string,
41): Effect.fn.Return<
42 Option.Option<string>,
43 DesktopAssetProbeError,
44 FileSystem.FileSystem | DesktopEnvironment.DesktopEnvironment
45> {
46 const fileSystem = yield* FileSystem.FileSystem;
47 const environment = yield* DesktopEnvironment.DesktopEnvironment;
48 const candidates = environment.resolveResourcePathCandidates(fileName);
49 for (const candidate of candidates) {
50 const exists = yield* fileSystem
51 .exists(candidate)
52 .pipe(
53 Effect.mapError(
54 (cause) => new DesktopAssetProbeError({ fileName, candidatePath: candidate, cause }),
55 ),
56 );
57 if (exists) {
58 return Option.some(candidate);
59 }
60 }
61 return Option.none<string>();
62});
63​
64const sourceTreeIconFileNames = {
65 dev: {
66 ico: "blueprint-windows.ico",
67 macPng: "blueprint-macos-1024.png",
68 universalPng: "blueprint-universal-1024.png",
69 },
70 prod: {
71 ico: "t3-black-windows.ico",
72 macPng: "black-macos-1024.png",
73 universalPng: "black-universal-1024.png",
74 },
75} as const;
76​
77function resolveSourceTreeIconPath(
78 environment: DesktopEnvironment.DesktopEnvironment["Service"],
79 ext: keyof DesktopIconPaths,
80): string | undefined {
81 if (environment.isPackaged || ext === "icns") return undefined;
82 const brand = environment.isDevelopment ? "dev" : "prod";
83 const fileNames = sourceTreeIconFileNames[brand];
84 const fileName =
85 ext === "ico"
86 ? fileNames.ico
87 : environment.platform === "darwin"
88 ? fileNames.macPng
89 : fileNames.universalPng;
90 return environment.path.join(environment.rootDir, "assets", brand, fileName);
92​
93const resolveIconPath = Effect.fn("desktop.assets.resolveIconPath")(function* (
94 ext: keyof DesktopIconPaths,
95): Effect.fn.Return<
96 Option.Option<string>,
97 DesktopAssetProbeError,
98 FileSystem.FileSystem | DesktopEnvironment.DesktopEnvironment
99> {
100 const fileSystem = yield* FileSystem.FileSystem;
101 const environment = yield* DesktopEnvironment.DesktopEnvironment;
102 const sourceTreeIconPath = resolveSourceTreeIconPath(environment, ext);
103 if (sourceTreeIconPath !== undefined) {
104 const sourceTreeIconExists = yield* fileSystem.exists(sourceTreeIconPath).pipe(
105 Effect.mapError(
106 (cause) =>
107 new DesktopAssetProbeError({
108 fileName: `icon.${ext}`,
109 candidatePath: sourceTreeIconPath,
110 cause,
111 }),
112 ),
113 );
114 if (sourceTreeIconExists) {
115 return Option.some(sourceTreeIconPath);
116 }
117 }
118​
119 return yield* resolveResourcePath(`icon.${ext}`);
120});
121​
122/** @public */
123export const make = Effect.gen(function* () {
124 const context = yield* Effect.context<
125 FileSystem.FileSystem | DesktopEnvironment.DesktopEnvironment
126 >();
127 const [ico, icns, png] = yield* Effect.all(
128 [resolveIconPath("ico"), resolveIconPath("icns"), resolveIconPath("png")] as const,
129 { concurrency: "unbounded" },
130 );
131 const iconPaths = { ico, icns, png } satisfies DesktopIconPaths;
132​
133 return DesktopAssets.of({
134 iconPaths: Effect.succeed(iconPaths),
135 resolveResourcePath: Effect.fn("desktop.assets.resolveResourcePath.scoped")(
136 function* (fileName) {
137 return yield* resolveResourcePath(fileName).pipe(Effect.provide(context));
138 },
139 ),
140 });
141});
142​
143export const layer = Layer.effect(DesktopAssets, make);