Skip to content
Source
Sign inSign up

model.ts

86 lines2.6 KB

1import type { DevicePlatform } from "@t3tools/contracts";
2​
3export type DeviceModelId = "iphone-18-pro" | "iphone-18-pro-max" | "ipad-pro-13-m5" | "iphone-duo";
4​
5export interface DeviceModelSource {
6 readonly id: DeviceModelId;
7 readonly url: string;
8}
9​
10export interface DeviceAccessorySource {
11 readonly id: "ipad-pro-13-m5-magic-keyboard";
12 readonly modelId: "ipad-pro-13-m5";
13 readonly url: string;
15​
16export type DeviceAssetSource = DeviceModelSource | DeviceAccessorySource;
17​
18export function resolveDeviceModelId(platform: DevicePlatform, name: string): DeviceModelId | null {
19 if (platform !== "ios") return null;
20 if (/^iPhone Duo$/i.test(name)) return "iphone-duo";
21 if (/^iPhone 18 Pro Max$/i.test(name)) return "iphone-18-pro-max";
22 if (/^iPhone 18 Pro$/i.test(name)) return "iphone-18-pro";
23 if (/^iPad Pro 13-inch \(M5\)$/i.test(name)) return "ipad-pro-13-m5";
24 return null;
26​
27/** Each request owns its result. A cancelled or superseded parse must still release its resources. */
28export function createDeviceModelSlot<
29 T extends { dispose: () => void },
30 Source extends DeviceAssetSource = DeviceModelSource,
31>(options: {
32 load: (source: Source, signal: AbortSignal) => Promise<T>;
33 install: (model: T | null) => void;
34 onError?: ((cause: unknown) => void) | undefined;
35}) {
36 let request: { source: Source; controller: AbortController } | null = null;
37 let current: T | null = null;
38 let disposed = false;
39 const clear = () => {
40 if (current) options.install(null);
41 current?.dispose();
42 current = null;
43 };
44 return {
45 set(source: Source | null) {
46 if (disposed) return;
47 if (source?.id === request?.source.id && source?.url === request?.source.url) return;
48 request?.controller.abort();
49 request = null;
50 clear();
51 if (!source) return;
52 const next = { source, controller: new AbortController() };
53 request = next;
54 void options.load(source, next.controller.signal).then(
55 (model) => {
56 if (disposed || request !== next) {
57 model.dispose();
58 return;
59 }
60 try {
61 options.install(model);
62 current = model;
63 } catch (cause) {
64 options.install(null);
65 model.dispose();
66 request = null;
67 options.onError?.(cause);
68 }
69 },
70 (cause: unknown) => {
71 if (!disposed && request === next) {
72 request = null;
73 options.onError?.(cause);
74 }
75 },
76 );
77 },
78 dispose() {
79 if (disposed) return;
80 disposed = true;
81 request?.controller.abort();
82 request = null;
83 clear();
84 },
85 };