1import type { ConfirmDialogOptions, ContextMenuItem, LocalApi } from "@t3tools/contracts";
2
3import { requestConfirmDialog } from "./confirmDialog";
4import { dismissContextMenu, showContextMenuFallback } from "./contextMenuFallback";
5import { readBrowserClientSettings, writeBrowserClientSettings } from "./clientPersistenceStorage";
6
7let cachedApi: LocalApi | undefined;
8
9function createBrowserLocalApi(): LocalApi {
10 return {
11 dialogs: {
12 pickFolder: async (options) => {
13 if (!window.desktopBridge) return null;
14 return window.desktopBridge.pickFolder(options);
15 },
16 confirm: async (message, options?: ConfirmDialogOptions) => {
17 return requestConfirmDialog(message, options) ?? false;
18 },
19 },
20 shell: {
21 openExternal: async (url) => {
22 if (window.desktopBridge) {
23 const opened = await window.desktopBridge.openExternal(url);
24 if (!opened) {
25 throw new Error("Unable to open link.");
26 }
27 return;
28 }
29
30 window.open(url, "_blank", "noopener,noreferrer");
31 },
32 // Only the desktop shell can reach the OS; the web build (and older
33 // desktop shells that predate this method) have nothing to open.
34 openSystemSettings: async (pane) => {
35 if (!window.desktopBridge?.openSystemSettings) {
36 throw new Error("Unable to open System Settings.");
37 }
38 const opened = await window.desktopBridge.openSystemSettings(pane);
39 if (!opened) {
40 throw new Error("Unable to open System Settings.");
41 }
42 },
43 },
44 contextMenu: {
45 show: async <T extends string>(
46 items: readonly ContextMenuItem<T>[],
47 position?: { x: number; y: number },
48 ): Promise<T | null> => {
49 if (window.desktopBridge) {
50 return window.desktopBridge.showContextMenu(items, position) as Promise<T | null>;
51 }
52 return showContextMenuFallback(items, position);
53 },
54 // A native desktop menu blocks keyboard input and closes on outside
55 // interaction, so nothing to do there; the DOM fallback needs an explicit
56 // dismiss when the state behind it goes away.
57 close: async () => {
58 if (!window.desktopBridge) {
59 dismissContextMenu();
60 }
61 },
62 },
63 persistence: {
64 getClientSettings: async () => {
65 if (window.desktopBridge) {
66 return window.desktopBridge.getClientSettings();
67 }
68 return readBrowserClientSettings();
69 },
70 setClientSettings: async (settings) => {
71 if (window.desktopBridge) {
72 return window.desktopBridge.setClientSettings(settings);
73 }
74 writeBrowserClientSettings(settings);
75 },
76 },
77 };
78}
79
80export function createLocalApi(): LocalApi {
81 return createBrowserLocalApi();
82}
83
84export function readLocalApi(): LocalApi | undefined {
85 if (typeof window === "undefined") return undefined;
86 if (cachedApi) return cachedApi;
87
88 cachedApi = createLocalApi();
89 return cachedApi;
90}
91
92export function ensureLocalApi(): LocalApi {
93 const api = readLocalApi();
94 if (!api) {
95 throw new Error("Local API not found");
96 }
97 return api;
98}