@flightdev/desktop 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +184 -0
- package/dist/config.d.ts +108 -0
- package/dist/config.js +54 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +55 -0
- package/dist/index.js +102 -0
- package/dist/index.js.map +1 -0
- package/dist/react/index.d.ts +149 -0
- package/dist/react/index.js +316 -0
- package/dist/react/index.js.map +1 -0
- package/dist/vue/index.d.ts +48 -0
- package/dist/vue/index.js +217 -0
- package/dist/vue/index.js.map +1 -0
- package/package.json +91 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { DesktopPlatformInfo } from '../index.js';
|
|
2
|
+
import '../config.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* React hooks for @flightdev/desktop
|
|
6
|
+
*
|
|
7
|
+
* Provides React-friendly wrappers around Tauri APIs.
|
|
8
|
+
* All hooks gracefully degrade on web.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Get current desktop platform information.
|
|
13
|
+
*/
|
|
14
|
+
declare function usePlatform(): DesktopPlatformInfo;
|
|
15
|
+
interface UseWindowReturn {
|
|
16
|
+
/** Minimize the window */
|
|
17
|
+
minimize: () => Promise<void>;
|
|
18
|
+
/** Maximize or restore the window */
|
|
19
|
+
maximize: () => Promise<void>;
|
|
20
|
+
/** Toggle fullscreen */
|
|
21
|
+
toggleFullscreen: () => Promise<void>;
|
|
22
|
+
/** Close the window */
|
|
23
|
+
close: () => Promise<void>;
|
|
24
|
+
/** Set window title */
|
|
25
|
+
setTitle: (title: string) => Promise<void>;
|
|
26
|
+
/** Center the window */
|
|
27
|
+
center: () => Promise<void>;
|
|
28
|
+
/** Check if window is maximized */
|
|
29
|
+
isMaximized: boolean;
|
|
30
|
+
/** Check if window is fullscreen */
|
|
31
|
+
isFullscreen: boolean;
|
|
32
|
+
/** Window controls available */
|
|
33
|
+
available: boolean;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Window control hook.
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```typescript
|
|
40
|
+
* function TitleBar() {
|
|
41
|
+
* const { minimize, maximize, close, isMaximized } = useWindow();
|
|
42
|
+
*
|
|
43
|
+
* return (
|
|
44
|
+
* <div data-tauri-drag-region className="titlebar">
|
|
45
|
+
* <button onClick={minimize}>_</button>
|
|
46
|
+
* <button onClick={maximize}>[max]</button>
|
|
47
|
+
* <button onClick={close}>×</button>
|
|
48
|
+
* </div>
|
|
49
|
+
* );
|
|
50
|
+
* }
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
declare function useWindow(): UseWindowReturn;
|
|
54
|
+
interface UseFileSystemReturn {
|
|
55
|
+
/** Read file contents as text */
|
|
56
|
+
readFile: (path: string) => Promise<string | null>;
|
|
57
|
+
/** Write text to file */
|
|
58
|
+
writeFile: (path: string, content: string) => Promise<boolean>;
|
|
59
|
+
/** Check if path exists */
|
|
60
|
+
exists: (path: string) => Promise<boolean>;
|
|
61
|
+
/** Create directory */
|
|
62
|
+
mkdir: (path: string) => Promise<boolean>;
|
|
63
|
+
/** List directory contents */
|
|
64
|
+
readDir: (path: string) => Promise<string[]>;
|
|
65
|
+
/** File system available */
|
|
66
|
+
available: boolean;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* File system hook using Tauri FS plugin.
|
|
70
|
+
* Requires @tauri-apps/plugin-fs.
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* ```typescript
|
|
74
|
+
* function FileManager() {
|
|
75
|
+
* const { readFile, writeFile, readDir } = useFileSystem();
|
|
76
|
+
*
|
|
77
|
+
* const loadConfig = async () => {
|
|
78
|
+
* const content = await readFile('~/.config/myapp/settings.json');
|
|
79
|
+
* return content ? JSON.parse(content) : {};
|
|
80
|
+
* };
|
|
81
|
+
*
|
|
82
|
+
* const saveConfig = async (config: object) => {
|
|
83
|
+
* await writeFile('~/.config/myapp/settings.json', JSON.stringify(config));
|
|
84
|
+
* };
|
|
85
|
+
* }
|
|
86
|
+
* ```
|
|
87
|
+
*/
|
|
88
|
+
declare function useFileSystem(): UseFileSystemReturn;
|
|
89
|
+
interface DialogFilter {
|
|
90
|
+
name: string;
|
|
91
|
+
extensions: string[];
|
|
92
|
+
}
|
|
93
|
+
interface UseDialogReturn {
|
|
94
|
+
/** Open file picker */
|
|
95
|
+
selectFile: (options?: {
|
|
96
|
+
filters?: DialogFilter[];
|
|
97
|
+
multiple?: boolean;
|
|
98
|
+
}) => Promise<string | string[] | null>;
|
|
99
|
+
/** Open folder picker */
|
|
100
|
+
selectFolder: () => Promise<string | null>;
|
|
101
|
+
/** Save file dialog */
|
|
102
|
+
saveFile: (options?: {
|
|
103
|
+
defaultPath?: string;
|
|
104
|
+
filters?: DialogFilter[];
|
|
105
|
+
}) => Promise<string | null>;
|
|
106
|
+
/** Show message dialog */
|
|
107
|
+
message: (msg: string, options?: {
|
|
108
|
+
title?: string;
|
|
109
|
+
type?: 'info' | 'warning' | 'error';
|
|
110
|
+
}) => Promise<void>;
|
|
111
|
+
/** Show confirmation dialog */
|
|
112
|
+
confirm: (msg: string, options?: {
|
|
113
|
+
title?: string;
|
|
114
|
+
}) => Promise<boolean>;
|
|
115
|
+
/** Dialog available */
|
|
116
|
+
available: boolean;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Native dialog hook.
|
|
120
|
+
* Requires @tauri-apps/plugin-dialog.
|
|
121
|
+
*/
|
|
122
|
+
declare function useDialog(): UseDialogReturn;
|
|
123
|
+
interface UseClipboardReturn {
|
|
124
|
+
/** Read text from clipboard */
|
|
125
|
+
read: () => Promise<string | null>;
|
|
126
|
+
/** Write text to clipboard */
|
|
127
|
+
write: (text: string) => Promise<boolean>;
|
|
128
|
+
/** Clipboard available */
|
|
129
|
+
available: boolean;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Clipboard hook.
|
|
133
|
+
* Falls back to navigator.clipboard on web.
|
|
134
|
+
*/
|
|
135
|
+
declare function useClipboard(): UseClipboardReturn;
|
|
136
|
+
interface UseShellReturn {
|
|
137
|
+
/** Open URL in default browser */
|
|
138
|
+
openUrl: (url: string) => Promise<void>;
|
|
139
|
+
/** Open path in file manager */
|
|
140
|
+
openPath: (path: string) => Promise<void>;
|
|
141
|
+
/** Shell available */
|
|
142
|
+
available: boolean;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Shell hook for opening URLs and paths.
|
|
146
|
+
*/
|
|
147
|
+
declare function useShell(): UseShellReturn;
|
|
148
|
+
|
|
149
|
+
export { useClipboard, useDialog, useFileSystem, usePlatform, useShell, useWindow };
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
import { useMemo, useState, useEffect, useCallback } from 'react';
|
|
2
|
+
|
|
3
|
+
// src/react/index.ts
|
|
4
|
+
|
|
5
|
+
// src/index.ts
|
|
6
|
+
function getTauri() {
|
|
7
|
+
try {
|
|
8
|
+
return globalThis.__TAURI__;
|
|
9
|
+
} catch {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function isTauri() {
|
|
14
|
+
return getTauri() !== null;
|
|
15
|
+
}
|
|
16
|
+
function getPlatform() {
|
|
17
|
+
const tauri = getTauri();
|
|
18
|
+
if (!tauri) {
|
|
19
|
+
return {
|
|
20
|
+
platform: "web",
|
|
21
|
+
isDesktop: false,
|
|
22
|
+
isWindows: false,
|
|
23
|
+
isMac: false,
|
|
24
|
+
isLinux: false,
|
|
25
|
+
isWeb: true,
|
|
26
|
+
hasTauri: false
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
const userAgent = navigator.userAgent.toLowerCase();
|
|
30
|
+
let platform = "web";
|
|
31
|
+
if (userAgent.includes("win")) {
|
|
32
|
+
platform = "windows";
|
|
33
|
+
} else if (userAgent.includes("mac")) {
|
|
34
|
+
platform = "macos";
|
|
35
|
+
} else if (userAgent.includes("linux")) {
|
|
36
|
+
platform = "linux";
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
platform,
|
|
40
|
+
isDesktop: true,
|
|
41
|
+
isWindows: platform === "windows",
|
|
42
|
+
isMac: platform === "macos",
|
|
43
|
+
isLinux: platform === "linux",
|
|
44
|
+
isWeb: false,
|
|
45
|
+
hasTauri: true
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// src/react/index.ts
|
|
50
|
+
function usePlatform() {
|
|
51
|
+
return useMemo(() => getPlatform(), []);
|
|
52
|
+
}
|
|
53
|
+
function useWindow() {
|
|
54
|
+
const [isMaximized, setIsMaximized] = useState(false);
|
|
55
|
+
const [isFullscreen, setIsFullscreen] = useState(false);
|
|
56
|
+
const available = isTauri();
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
if (!available) return;
|
|
59
|
+
let cleanup;
|
|
60
|
+
(async () => {
|
|
61
|
+
const { getCurrentWindow } = await import('@tauri-apps/api/window');
|
|
62
|
+
const appWindow = getCurrentWindow();
|
|
63
|
+
setIsMaximized(await appWindow.isMaximized());
|
|
64
|
+
setIsFullscreen(await appWindow.isFullscreen());
|
|
65
|
+
const unlisten = await appWindow.onResized(async () => {
|
|
66
|
+
setIsMaximized(await appWindow.isMaximized());
|
|
67
|
+
setIsFullscreen(await appWindow.isFullscreen());
|
|
68
|
+
});
|
|
69
|
+
cleanup = unlisten;
|
|
70
|
+
})();
|
|
71
|
+
return () => {
|
|
72
|
+
cleanup?.();
|
|
73
|
+
};
|
|
74
|
+
}, [available]);
|
|
75
|
+
const minimize = useCallback(async () => {
|
|
76
|
+
if (!available) return;
|
|
77
|
+
const { getCurrentWindow } = await import('@tauri-apps/api/window');
|
|
78
|
+
await getCurrentWindow().minimize();
|
|
79
|
+
}, [available]);
|
|
80
|
+
const maximize = useCallback(async () => {
|
|
81
|
+
if (!available) return;
|
|
82
|
+
const { getCurrentWindow } = await import('@tauri-apps/api/window');
|
|
83
|
+
await getCurrentWindow().toggleMaximize();
|
|
84
|
+
}, [available]);
|
|
85
|
+
const toggleFullscreen = useCallback(async () => {
|
|
86
|
+
if (!available) return;
|
|
87
|
+
const { getCurrentWindow } = await import('@tauri-apps/api/window');
|
|
88
|
+
const window2 = getCurrentWindow();
|
|
89
|
+
const current = await window2.isFullscreen();
|
|
90
|
+
await window2.setFullscreen(!current);
|
|
91
|
+
}, [available]);
|
|
92
|
+
const close = useCallback(async () => {
|
|
93
|
+
if (!available) return;
|
|
94
|
+
const { getCurrentWindow } = await import('@tauri-apps/api/window');
|
|
95
|
+
await getCurrentWindow().close();
|
|
96
|
+
}, [available]);
|
|
97
|
+
const setTitle = useCallback(async (title) => {
|
|
98
|
+
if (!available) {
|
|
99
|
+
document.title = title;
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
const { getCurrentWindow } = await import('@tauri-apps/api/window');
|
|
103
|
+
await getCurrentWindow().setTitle(title);
|
|
104
|
+
}, [available]);
|
|
105
|
+
const center = useCallback(async () => {
|
|
106
|
+
if (!available) return;
|
|
107
|
+
const { getCurrentWindow } = await import('@tauri-apps/api/window');
|
|
108
|
+
await getCurrentWindow().center();
|
|
109
|
+
}, [available]);
|
|
110
|
+
return {
|
|
111
|
+
minimize,
|
|
112
|
+
maximize,
|
|
113
|
+
toggleFullscreen,
|
|
114
|
+
close,
|
|
115
|
+
setTitle,
|
|
116
|
+
center,
|
|
117
|
+
isMaximized,
|
|
118
|
+
isFullscreen,
|
|
119
|
+
available
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
function useFileSystem() {
|
|
123
|
+
const available = isTauri();
|
|
124
|
+
const readFile = useCallback(async (path) => {
|
|
125
|
+
if (!available) return null;
|
|
126
|
+
try {
|
|
127
|
+
const { readTextFile } = await import('@tauri-apps/plugin-fs');
|
|
128
|
+
return await readTextFile(path);
|
|
129
|
+
} catch (e) {
|
|
130
|
+
console.error("[Flight Desktop] Read file error:", e);
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
}, [available]);
|
|
134
|
+
const writeFile = useCallback(async (path, content) => {
|
|
135
|
+
if (!available) return false;
|
|
136
|
+
try {
|
|
137
|
+
const { writeTextFile } = await import('@tauri-apps/plugin-fs');
|
|
138
|
+
await writeTextFile(path, content);
|
|
139
|
+
return true;
|
|
140
|
+
} catch (e) {
|
|
141
|
+
console.error("[Flight Desktop] Write file error:", e);
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
}, [available]);
|
|
145
|
+
const exists = useCallback(async (path) => {
|
|
146
|
+
if (!available) return false;
|
|
147
|
+
try {
|
|
148
|
+
const fs = await import('@tauri-apps/plugin-fs');
|
|
149
|
+
return await fs.exists(path);
|
|
150
|
+
} catch {
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
}, [available]);
|
|
154
|
+
const mkdir = useCallback(async (path) => {
|
|
155
|
+
if (!available) return false;
|
|
156
|
+
try {
|
|
157
|
+
const fs = await import('@tauri-apps/plugin-fs');
|
|
158
|
+
await fs.mkdir(path, { recursive: true });
|
|
159
|
+
return true;
|
|
160
|
+
} catch (e) {
|
|
161
|
+
console.error("[Flight Desktop] Mkdir error:", e);
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
}, [available]);
|
|
165
|
+
const readDir = useCallback(async (path) => {
|
|
166
|
+
if (!available) return [];
|
|
167
|
+
try {
|
|
168
|
+
const fs = await import('@tauri-apps/plugin-fs');
|
|
169
|
+
const entries = await fs.readDir(path);
|
|
170
|
+
return entries.map((e) => e.name);
|
|
171
|
+
} catch (e) {
|
|
172
|
+
console.error("[Flight Desktop] Read dir error:", e);
|
|
173
|
+
return [];
|
|
174
|
+
}
|
|
175
|
+
}, [available]);
|
|
176
|
+
return { readFile, writeFile, exists, mkdir, readDir, available };
|
|
177
|
+
}
|
|
178
|
+
function useDialog() {
|
|
179
|
+
const available = isTauri();
|
|
180
|
+
const selectFile = useCallback(async (options) => {
|
|
181
|
+
if (!available) {
|
|
182
|
+
return new Promise((resolve) => {
|
|
183
|
+
const input = document.createElement("input");
|
|
184
|
+
input.type = "file";
|
|
185
|
+
input.multiple = options?.multiple ?? false;
|
|
186
|
+
if (options?.filters) {
|
|
187
|
+
input.accept = options.filters.flatMap((f) => f.extensions.map((e) => `.${e}`)).join(",");
|
|
188
|
+
}
|
|
189
|
+
input.onchange = () => {
|
|
190
|
+
const files = Array.from(input.files || []);
|
|
191
|
+
resolve(options?.multiple ? files.map((f) => f.name) : files[0]?.name || null);
|
|
192
|
+
};
|
|
193
|
+
input.click();
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
try {
|
|
197
|
+
const { open } = await import('@tauri-apps/plugin-dialog');
|
|
198
|
+
return await open({
|
|
199
|
+
multiple: options?.multiple ?? false,
|
|
200
|
+
filters: options?.filters
|
|
201
|
+
});
|
|
202
|
+
} catch {
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
}, [available]);
|
|
206
|
+
const selectFolder = useCallback(async () => {
|
|
207
|
+
if (!available) return null;
|
|
208
|
+
try {
|
|
209
|
+
const { open } = await import('@tauri-apps/plugin-dialog');
|
|
210
|
+
return await open({ directory: true });
|
|
211
|
+
} catch {
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
}, [available]);
|
|
215
|
+
const saveFile = useCallback(async (options) => {
|
|
216
|
+
if (!available) return null;
|
|
217
|
+
try {
|
|
218
|
+
const { save } = await import('@tauri-apps/plugin-dialog');
|
|
219
|
+
return await save({
|
|
220
|
+
defaultPath: options?.defaultPath,
|
|
221
|
+
filters: options?.filters
|
|
222
|
+
});
|
|
223
|
+
} catch {
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
}, [available]);
|
|
227
|
+
const message = useCallback(async (msg, options) => {
|
|
228
|
+
if (!available) {
|
|
229
|
+
alert(msg);
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
try {
|
|
233
|
+
const dialog = await import('@tauri-apps/plugin-dialog');
|
|
234
|
+
await dialog.message(msg, { title: options?.title, kind: options?.type });
|
|
235
|
+
} catch {
|
|
236
|
+
alert(msg);
|
|
237
|
+
}
|
|
238
|
+
}, [available]);
|
|
239
|
+
const confirm = useCallback(async (msg, options) => {
|
|
240
|
+
if (!available) {
|
|
241
|
+
return window.confirm(msg);
|
|
242
|
+
}
|
|
243
|
+
try {
|
|
244
|
+
const dialog = await import('@tauri-apps/plugin-dialog');
|
|
245
|
+
return await dialog.confirm(msg, { title: options?.title });
|
|
246
|
+
} catch {
|
|
247
|
+
return window.confirm(msg);
|
|
248
|
+
}
|
|
249
|
+
}, [available]);
|
|
250
|
+
return { selectFile, selectFolder, saveFile, message, confirm, available };
|
|
251
|
+
}
|
|
252
|
+
function useClipboard() {
|
|
253
|
+
const available = isTauri();
|
|
254
|
+
const read = useCallback(async () => {
|
|
255
|
+
if (!available) {
|
|
256
|
+
try {
|
|
257
|
+
return await navigator.clipboard.readText();
|
|
258
|
+
} catch {
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
try {
|
|
263
|
+
const { readText } = await import('@tauri-apps/plugin-clipboard-manager');
|
|
264
|
+
return await readText();
|
|
265
|
+
} catch {
|
|
266
|
+
return null;
|
|
267
|
+
}
|
|
268
|
+
}, [available]);
|
|
269
|
+
const write = useCallback(async (text) => {
|
|
270
|
+
if (!available) {
|
|
271
|
+
try {
|
|
272
|
+
await navigator.clipboard.writeText(text);
|
|
273
|
+
return true;
|
|
274
|
+
} catch {
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
try {
|
|
279
|
+
const { writeText } = await import('@tauri-apps/plugin-clipboard-manager');
|
|
280
|
+
await writeText(text);
|
|
281
|
+
return true;
|
|
282
|
+
} catch {
|
|
283
|
+
return false;
|
|
284
|
+
}
|
|
285
|
+
}, [available]);
|
|
286
|
+
return { read, write, available };
|
|
287
|
+
}
|
|
288
|
+
function useShell() {
|
|
289
|
+
const available = isTauri();
|
|
290
|
+
const openUrl = useCallback(async (url) => {
|
|
291
|
+
if (!available) {
|
|
292
|
+
window.open(url, "_blank");
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
try {
|
|
296
|
+
const { open } = await import('@tauri-apps/plugin-shell');
|
|
297
|
+
await open(url);
|
|
298
|
+
} catch {
|
|
299
|
+
window.open(url, "_blank");
|
|
300
|
+
}
|
|
301
|
+
}, [available]);
|
|
302
|
+
const openPath = useCallback(async (path) => {
|
|
303
|
+
if (!available) return;
|
|
304
|
+
try {
|
|
305
|
+
const { open } = await import('@tauri-apps/plugin-shell');
|
|
306
|
+
await open(path);
|
|
307
|
+
} catch (e) {
|
|
308
|
+
console.error("[Flight Desktop] Open path error:", e);
|
|
309
|
+
}
|
|
310
|
+
}, [available]);
|
|
311
|
+
return { openUrl, openPath, available };
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
export { useClipboard, useDialog, useFileSystem, usePlatform, useShell, useWindow };
|
|
315
|
+
//# sourceMappingURL=index.js.map
|
|
316
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/index.ts","../../src/react/index.ts"],"names":["window"],"mappings":"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","file":"index.js","sourcesContent":["/**\r\n * @flightdev/desktop\r\n * \r\n * Desktop app support for Flight Framework via Tauri.\r\n * Build Windows, macOS, and Linux apps with the same codebase.\r\n * \r\n * Philosophy: Flight doesn't impose - install only the Tauri plugins you need.\r\n * All plugins are optional peer dependencies.\r\n * \r\n * @example\r\n * ```typescript\r\n * import { usePlatform, useWindow, useFileSystem } from '@flightdev/desktop/react';\r\n * \r\n * function App() {\r\n * const { isDesktop, isMac, isWindows } = usePlatform();\r\n * const { minimize, maximize, close } = useWindow();\r\n * const { readFile, writeFile, selectFile } = useFileSystem();\r\n * \r\n * return isDesktop ? <DesktopApp /> : <WebApp />;\r\n * }\r\n * ```\r\n */\r\n\r\n// =============================================================================\r\n// Types\r\n// =============================================================================\r\n\r\nexport type Platform = 'windows' | 'macos' | 'linux' | 'web';\r\n\r\nexport interface DesktopPlatformInfo {\r\n /** Current platform */\r\n platform: Platform;\r\n /** True if running as desktop app */\r\n isDesktop: boolean;\r\n /** True if running on Windows */\r\n isWindows: boolean;\r\n /** True if running on macOS */\r\n isMac: boolean;\r\n /** True if running on Linux */\r\n isLinux: boolean;\r\n /** True if running as web app */\r\n isWeb: boolean;\r\n /** True if Tauri is available */\r\n hasTauri: boolean;\r\n}\r\n\r\n// =============================================================================\r\n// Detection\r\n// =============================================================================\r\n\r\n/**\r\n * Check if Tauri is available.\r\n */\r\nfunction getTauri(): any {\r\n try {\r\n return (globalThis as any).__TAURI__;\r\n } catch {\r\n return null;\r\n }\r\n}\r\n\r\n/**\r\n * Check if running in Tauri desktop environment.\r\n */\r\nexport function isTauri(): boolean {\r\n return getTauri() !== null;\r\n}\r\n\r\n/**\r\n * Get current desktop platform.\r\n */\r\nexport function getPlatform(): DesktopPlatformInfo {\r\n const tauri = getTauri();\r\n\r\n if (!tauri) {\r\n return {\r\n platform: 'web',\r\n isDesktop: false,\r\n isWindows: false,\r\n isMac: false,\r\n isLinux: false,\r\n isWeb: true,\r\n hasTauri: false,\r\n };\r\n }\r\n\r\n // Detect OS from navigator\r\n const userAgent = navigator.userAgent.toLowerCase();\r\n let platform: Platform = 'web';\r\n\r\n if (userAgent.includes('win')) {\r\n platform = 'windows';\r\n } else if (userAgent.includes('mac')) {\r\n platform = 'macos';\r\n } else if (userAgent.includes('linux')) {\r\n platform = 'linux';\r\n }\r\n\r\n return {\r\n platform,\r\n isDesktop: true,\r\n isWindows: platform === 'windows',\r\n isMac: platform === 'macos',\r\n isLinux: platform === 'linux',\r\n isWeb: false,\r\n hasTauri: true,\r\n };\r\n}\r\n\r\n/**\r\n * Check if a Tauri plugin is available.\r\n */\r\nexport function isPluginAvailable(pluginName: string): boolean {\r\n const tauri = getTauri();\r\n return tauri && typeof tauri[pluginName] !== 'undefined';\r\n}\r\n\r\n// =============================================================================\r\n// Re-exports\r\n// =============================================================================\r\n\r\nexport * from './config';\r\n","/**\r\n * React hooks for @flightdev/desktop\r\n * \r\n * Provides React-friendly wrappers around Tauri APIs.\r\n * All hooks gracefully degrade on web.\r\n */\r\n\r\nimport { useState, useEffect, useCallback, useMemo } from 'react';\r\nimport { getPlatform, isTauri, type DesktopPlatformInfo } from '../index';\r\n\r\n// =============================================================================\r\n// Platform Hook\r\n// =============================================================================\r\n\r\n/**\r\n * Get current desktop platform information.\r\n */\r\nexport function usePlatform(): DesktopPlatformInfo {\r\n return useMemo(() => getPlatform(), []);\r\n}\r\n\r\n// =============================================================================\r\n// Window Hook\r\n// =============================================================================\r\n\r\ninterface UseWindowReturn {\r\n /** Minimize the window */\r\n minimize: () => Promise<void>;\r\n /** Maximize or restore the window */\r\n maximize: () => Promise<void>;\r\n /** Toggle fullscreen */\r\n toggleFullscreen: () => Promise<void>;\r\n /** Close the window */\r\n close: () => Promise<void>;\r\n /** Set window title */\r\n setTitle: (title: string) => Promise<void>;\r\n /** Center the window */\r\n center: () => Promise<void>;\r\n /** Check if window is maximized */\r\n isMaximized: boolean;\r\n /** Check if window is fullscreen */\r\n isFullscreen: boolean;\r\n /** Window controls available */\r\n available: boolean;\r\n}\r\n\r\n/**\r\n * Window control hook.\r\n * \r\n * @example\r\n * ```typescript\r\n * function TitleBar() {\r\n * const { minimize, maximize, close, isMaximized } = useWindow();\r\n * \r\n * return (\r\n * <div data-tauri-drag-region className=\"titlebar\">\r\n * <button onClick={minimize}>_</button>\r\n * <button onClick={maximize}>[max]</button>\r\n * <button onClick={close}>×</button>\r\n * </div>\r\n * );\r\n * }\r\n * ```\r\n */\r\nexport function useWindow(): UseWindowReturn {\r\n const [isMaximized, setIsMaximized] = useState(false);\r\n const [isFullscreen, setIsFullscreen] = useState(false);\r\n const available = isTauri();\r\n\r\n useEffect(() => {\r\n if (!available) return;\r\n\r\n let cleanup: (() => void) | undefined;\r\n\r\n (async () => {\r\n const { getCurrentWindow } = await import('@tauri-apps/api/window');\r\n const appWindow = getCurrentWindow();\r\n\r\n // Get initial state\r\n setIsMaximized(await appWindow.isMaximized());\r\n setIsFullscreen(await appWindow.isFullscreen());\r\n\r\n // Listen for changes\r\n const unlisten = await appWindow.onResized(async () => {\r\n setIsMaximized(await appWindow.isMaximized());\r\n setIsFullscreen(await appWindow.isFullscreen());\r\n });\r\n\r\n cleanup = unlisten;\r\n })();\r\n\r\n return () => { cleanup?.(); };\r\n }, [available]);\r\n\r\n const minimize = useCallback(async () => {\r\n if (!available) return;\r\n const { getCurrentWindow } = await import('@tauri-apps/api/window');\r\n await getCurrentWindow().minimize();\r\n }, [available]);\r\n\r\n const maximize = useCallback(async () => {\r\n if (!available) return;\r\n const { getCurrentWindow } = await import('@tauri-apps/api/window');\r\n await getCurrentWindow().toggleMaximize();\r\n }, [available]);\r\n\r\n const toggleFullscreen = useCallback(async () => {\r\n if (!available) return;\r\n const { getCurrentWindow } = await import('@tauri-apps/api/window');\r\n const window = getCurrentWindow();\r\n const current = await window.isFullscreen();\r\n await window.setFullscreen(!current);\r\n }, [available]);\r\n\r\n const close = useCallback(async () => {\r\n if (!available) return;\r\n const { getCurrentWindow } = await import('@tauri-apps/api/window');\r\n await getCurrentWindow().close();\r\n }, [available]);\r\n\r\n const setTitle = useCallback(async (title: string) => {\r\n if (!available) {\r\n document.title = title;\r\n return;\r\n }\r\n const { getCurrentWindow } = await import('@tauri-apps/api/window');\r\n await getCurrentWindow().setTitle(title);\r\n }, [available]);\r\n\r\n const center = useCallback(async () => {\r\n if (!available) return;\r\n const { getCurrentWindow } = await import('@tauri-apps/api/window');\r\n await getCurrentWindow().center();\r\n }, [available]);\r\n\r\n return {\r\n minimize,\r\n maximize,\r\n toggleFullscreen,\r\n close,\r\n setTitle,\r\n center,\r\n isMaximized,\r\n isFullscreen,\r\n available,\r\n };\r\n}\r\n\r\n// =============================================================================\r\n// File System Hook\r\n// =============================================================================\r\n\r\ninterface UseFileSystemReturn {\r\n /** Read file contents as text */\r\n readFile: (path: string) => Promise<string | null>;\r\n /** Write text to file */\r\n writeFile: (path: string, content: string) => Promise<boolean>;\r\n /** Check if path exists */\r\n exists: (path: string) => Promise<boolean>;\r\n /** Create directory */\r\n mkdir: (path: string) => Promise<boolean>;\r\n /** List directory contents */\r\n readDir: (path: string) => Promise<string[]>;\r\n /** File system available */\r\n available: boolean;\r\n}\r\n\r\n/**\r\n * File system hook using Tauri FS plugin.\r\n * Requires @tauri-apps/plugin-fs.\r\n * \r\n * @example\r\n * ```typescript\r\n * function FileManager() {\r\n * const { readFile, writeFile, readDir } = useFileSystem();\r\n * \r\n * const loadConfig = async () => {\r\n * const content = await readFile('~/.config/myapp/settings.json');\r\n * return content ? JSON.parse(content) : {};\r\n * };\r\n * \r\n * const saveConfig = async (config: object) => {\r\n * await writeFile('~/.config/myapp/settings.json', JSON.stringify(config));\r\n * };\r\n * }\r\n * ```\r\n */\r\nexport function useFileSystem(): UseFileSystemReturn {\r\n const available = isTauri();\r\n\r\n const readFile = useCallback(async (path: string): Promise<string | null> => {\r\n if (!available) return null;\r\n\r\n try {\r\n const { readTextFile } = await import('@tauri-apps/plugin-fs');\r\n return await readTextFile(path);\r\n } catch (e) {\r\n console.error('[Flight Desktop] Read file error:', e);\r\n return null;\r\n }\r\n }, [available]);\r\n\r\n const writeFile = useCallback(async (path: string, content: string): Promise<boolean> => {\r\n if (!available) return false;\r\n\r\n try {\r\n const { writeTextFile } = await import('@tauri-apps/plugin-fs');\r\n await writeTextFile(path, content);\r\n return true;\r\n } catch (e) {\r\n console.error('[Flight Desktop] Write file error:', e);\r\n return false;\r\n }\r\n }, [available]);\r\n\r\n const exists = useCallback(async (path: string): Promise<boolean> => {\r\n if (!available) return false;\r\n\r\n try {\r\n const fs = await import('@tauri-apps/plugin-fs');\r\n return await fs.exists(path);\r\n } catch {\r\n return false;\r\n }\r\n }, [available]);\r\n\r\n const mkdir = useCallback(async (path: string): Promise<boolean> => {\r\n if (!available) return false;\r\n\r\n try {\r\n const fs = await import('@tauri-apps/plugin-fs');\r\n await fs.mkdir(path, { recursive: true });\r\n return true;\r\n } catch (e) {\r\n console.error('[Flight Desktop] Mkdir error:', e);\r\n return false;\r\n }\r\n }, [available]);\r\n\r\n const readDir = useCallback(async (path: string): Promise<string[]> => {\r\n if (!available) return [];\r\n\r\n try {\r\n const fs = await import('@tauri-apps/plugin-fs');\r\n const entries = await fs.readDir(path);\r\n return entries.map((e: any) => e.name);\r\n } catch (e) {\r\n console.error('[Flight Desktop] Read dir error:', e);\r\n return [];\r\n }\r\n }, [available]);\r\n\r\n return { readFile, writeFile, exists, mkdir, readDir, available };\r\n}\r\n\r\n// =============================================================================\r\n// Dialog Hook\r\n// =============================================================================\r\n\r\ninterface DialogFilter {\r\n name: string;\r\n extensions: string[];\r\n}\r\n\r\ninterface UseDialogReturn {\r\n /** Open file picker */\r\n selectFile: (options?: { filters?: DialogFilter[]; multiple?: boolean }) => Promise<string | string[] | null>;\r\n /** Open folder picker */\r\n selectFolder: () => Promise<string | null>;\r\n /** Save file dialog */\r\n saveFile: (options?: { defaultPath?: string; filters?: DialogFilter[] }) => Promise<string | null>;\r\n /** Show message dialog */\r\n message: (msg: string, options?: { title?: string; type?: 'info' | 'warning' | 'error' }) => Promise<void>;\r\n /** Show confirmation dialog */\r\n confirm: (msg: string, options?: { title?: string }) => Promise<boolean>;\r\n /** Dialog available */\r\n available: boolean;\r\n}\r\n\r\n/**\r\n * Native dialog hook.\r\n * Requires @tauri-apps/plugin-dialog.\r\n */\r\nexport function useDialog(): UseDialogReturn {\r\n const available = isTauri();\r\n\r\n const selectFile = useCallback(async (options?: {\r\n filters?: DialogFilter[];\r\n multiple?: boolean;\r\n }): Promise<string | string[] | null> => {\r\n if (!available) {\r\n // Fallback to web file input\r\n return new Promise((resolve) => {\r\n const input = document.createElement('input');\r\n input.type = 'file';\r\n input.multiple = options?.multiple ?? false;\r\n if (options?.filters) {\r\n input.accept = options.filters.flatMap(f => f.extensions.map(e => `.${e}`)).join(',');\r\n }\r\n input.onchange = () => {\r\n const files = Array.from(input.files || []);\r\n resolve(options?.multiple ? files.map(f => f.name) : files[0]?.name || null);\r\n };\r\n input.click();\r\n });\r\n }\r\n\r\n try {\r\n const { open } = await import('@tauri-apps/plugin-dialog');\r\n return await open({\r\n multiple: options?.multiple ?? false,\r\n filters: options?.filters,\r\n });\r\n } catch {\r\n return null;\r\n }\r\n }, [available]);\r\n\r\n const selectFolder = useCallback(async (): Promise<string | null> => {\r\n if (!available) return null;\r\n\r\n try {\r\n const { open } = await import('@tauri-apps/plugin-dialog');\r\n return await open({ directory: true }) as string | null;\r\n } catch {\r\n return null;\r\n }\r\n }, [available]);\r\n\r\n const saveFile = useCallback(async (options?: {\r\n defaultPath?: string;\r\n filters?: DialogFilter[];\r\n }): Promise<string | null> => {\r\n if (!available) return null;\r\n\r\n try {\r\n const { save } = await import('@tauri-apps/plugin-dialog');\r\n return await save({\r\n defaultPath: options?.defaultPath,\r\n filters: options?.filters,\r\n });\r\n } catch {\r\n return null;\r\n }\r\n }, [available]);\r\n\r\n const message = useCallback(async (msg: string, options?: {\r\n title?: string;\r\n type?: 'info' | 'warning' | 'error';\r\n }): Promise<void> => {\r\n if (!available) {\r\n alert(msg);\r\n return;\r\n }\r\n\r\n try {\r\n const dialog = await import('@tauri-apps/plugin-dialog');\r\n await dialog.message(msg, { title: options?.title, kind: options?.type });\r\n } catch {\r\n alert(msg);\r\n }\r\n }, [available]);\r\n\r\n const confirm = useCallback(async (msg: string, options?: {\r\n title?: string;\r\n }): Promise<boolean> => {\r\n if (!available) {\r\n return window.confirm(msg);\r\n }\r\n\r\n try {\r\n const dialog = await import('@tauri-apps/plugin-dialog');\r\n return await dialog.confirm(msg, { title: options?.title });\r\n } catch {\r\n return window.confirm(msg);\r\n }\r\n }, [available]);\r\n\r\n return { selectFile, selectFolder, saveFile, message, confirm, available };\r\n}\r\n\r\n// =============================================================================\r\n// Clipboard Hook\r\n// =============================================================================\r\n\r\ninterface UseClipboardReturn {\r\n /** Read text from clipboard */\r\n read: () => Promise<string | null>;\r\n /** Write text to clipboard */\r\n write: (text: string) => Promise<boolean>;\r\n /** Clipboard available */\r\n available: boolean;\r\n}\r\n\r\n/**\r\n * Clipboard hook.\r\n * Falls back to navigator.clipboard on web.\r\n */\r\nexport function useClipboard(): UseClipboardReturn {\r\n const available = isTauri();\r\n\r\n const read = useCallback(async (): Promise<string | null> => {\r\n if (!available) {\r\n try {\r\n return await navigator.clipboard.readText();\r\n } catch {\r\n return null;\r\n }\r\n }\r\n\r\n try {\r\n const { readText } = await import('@tauri-apps/plugin-clipboard-manager');\r\n return await readText();\r\n } catch {\r\n return null;\r\n }\r\n }, [available]);\r\n\r\n const write = useCallback(async (text: string): Promise<boolean> => {\r\n if (!available) {\r\n try {\r\n await navigator.clipboard.writeText(text);\r\n return true;\r\n } catch {\r\n return false;\r\n }\r\n }\r\n\r\n try {\r\n const { writeText } = await import('@tauri-apps/plugin-clipboard-manager');\r\n await writeText(text);\r\n return true;\r\n } catch {\r\n return false;\r\n }\r\n }, [available]);\r\n\r\n return { read, write, available };\r\n}\r\n\r\n// =============================================================================\r\n// Shell Hook\r\n// =============================================================================\r\n\r\ninterface UseShellReturn {\r\n /** Open URL in default browser */\r\n openUrl: (url: string) => Promise<void>;\r\n /** Open path in file manager */\r\n openPath: (path: string) => Promise<void>;\r\n /** Shell available */\r\n available: boolean;\r\n}\r\n\r\n/**\r\n * Shell hook for opening URLs and paths.\r\n */\r\nexport function useShell(): UseShellReturn {\r\n const available = isTauri();\r\n\r\n const openUrl = useCallback(async (url: string): Promise<void> => {\r\n if (!available) {\r\n window.open(url, '_blank');\r\n return;\r\n }\r\n\r\n try {\r\n const { open } = await import('@tauri-apps/plugin-shell');\r\n await open(url);\r\n } catch {\r\n window.open(url, '_blank');\r\n }\r\n }, [available]);\r\n\r\n const openPath = useCallback(async (path: string): Promise<void> => {\r\n if (!available) return;\r\n\r\n try {\r\n const { open } = await import('@tauri-apps/plugin-shell');\r\n await open(path);\r\n } catch (e) {\r\n console.error('[Flight Desktop] Open path error:', e);\r\n }\r\n }, [available]);\r\n\r\n return { openUrl, openPath, available };\r\n}\r\n"]}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Ref } from 'vue';
|
|
2
|
+
import { DesktopPlatformInfo } from '../index.js';
|
|
3
|
+
import '../config.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Vue composables for @flightdev/desktop
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
declare function usePlatform(): DesktopPlatformInfo;
|
|
10
|
+
interface UseWindowReturn {
|
|
11
|
+
minimize: () => Promise<void>;
|
|
12
|
+
maximize: () => Promise<void>;
|
|
13
|
+
close: () => Promise<void>;
|
|
14
|
+
setTitle: (title: string) => Promise<void>;
|
|
15
|
+
isMaximized: Ref<boolean>;
|
|
16
|
+
available: boolean;
|
|
17
|
+
}
|
|
18
|
+
declare function useWindow(): UseWindowReturn;
|
|
19
|
+
interface UseFileSystemReturn {
|
|
20
|
+
readFile: (path: string) => Promise<string | null>;
|
|
21
|
+
writeFile: (path: string, content: string) => Promise<boolean>;
|
|
22
|
+
exists: (path: string) => Promise<boolean>;
|
|
23
|
+
available: boolean;
|
|
24
|
+
}
|
|
25
|
+
declare function useFileSystem(): UseFileSystemReturn;
|
|
26
|
+
interface UseDialogReturn {
|
|
27
|
+
selectFile: (options?: {
|
|
28
|
+
multiple?: boolean;
|
|
29
|
+
}) => Promise<string | string[] | null>;
|
|
30
|
+
selectFolder: () => Promise<string | null>;
|
|
31
|
+
message: (msg: string) => Promise<void>;
|
|
32
|
+
confirm: (msg: string) => Promise<boolean>;
|
|
33
|
+
available: boolean;
|
|
34
|
+
}
|
|
35
|
+
declare function useDialog(): UseDialogReturn;
|
|
36
|
+
interface UseClipboardReturn {
|
|
37
|
+
read: () => Promise<string | null>;
|
|
38
|
+
write: (text: string) => Promise<boolean>;
|
|
39
|
+
available: boolean;
|
|
40
|
+
}
|
|
41
|
+
declare function useClipboard(): UseClipboardReturn;
|
|
42
|
+
interface UseShellReturn {
|
|
43
|
+
openUrl: (url: string) => Promise<void>;
|
|
44
|
+
available: boolean;
|
|
45
|
+
}
|
|
46
|
+
declare function useShell(): UseShellReturn;
|
|
47
|
+
|
|
48
|
+
export { useClipboard, useDialog, useFileSystem, usePlatform, useShell, useWindow };
|