@fde-desktop/fde-core 0.3.8 → 0.4.1

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.
Files changed (148) hide show
  1. package/README.md +419 -68
  2. package/dist/CalendarApp-CHLUCAI7.css +744 -0
  3. package/dist/CalendarApp-K7ZOUZ6C.js +2 -0
  4. package/dist/CalendarApp-OTGEERSS.cjs +8 -0
  5. package/dist/CodeServerApp-5KZGO7HL.css +75 -0
  6. package/dist/CodeServerApp-LUZFCQBI.js +3 -0
  7. package/dist/CodeServerApp-P3TMJPLY.cjs +9 -0
  8. package/dist/CreateItemApp-NAZMXOPK.cjs +14 -0
  9. package/dist/CreateItemApp-PQB5GTFG.css +107 -0
  10. package/dist/CreateItemApp-ZHCTSPQE.js +8 -0
  11. package/dist/DeviceInfoApp-R6YNVIGX.cjs +11 -0
  12. package/dist/DeviceInfoApp-YHCYAO6N.js +5 -0
  13. package/dist/DeviceInfoApp-ZSMRSITP.css +7 -0
  14. package/dist/FilesApp-AKCVRTXR.js +8 -0
  15. package/dist/FilesApp-E6L5W3T2.css +1817 -0
  16. package/dist/FilesApp-RW3Y6ILO.cjs +14 -0
  17. package/dist/ImageViewerApp-5UXNSW2O.js +11 -0
  18. package/dist/ImageViewerApp-N2Q7E7WZ.css +215 -0
  19. package/dist/ImageViewerApp-RRRRKSFN.cjs +17 -0
  20. package/dist/ImageViewerMenuBar-I3TFKQPS.cjs +14 -0
  21. package/dist/ImageViewerMenuBar-TV5C6TM2.js +5 -0
  22. package/dist/ImageViewerMenuBar-XLK4LIHW.css +56 -0
  23. package/dist/MenuEditApp-HUZRFEHE.js +9 -0
  24. package/dist/MenuEditApp-MCUHGTKQ.cjs +15 -0
  25. package/dist/MenuEditApp-YA6HSAMJ.css +94 -0
  26. package/dist/MenuEditMenuBar-7VHMZNRM.css +56 -0
  27. package/dist/MenuEditMenuBar-GF6L4PGZ.cjs +15 -0
  28. package/dist/MenuEditMenuBar-IUXFPZE5.js +6 -0
  29. package/dist/NotesApp-37BV33C6.js +10 -0
  30. package/dist/NotesApp-4EVUQEFZ.cjs +16 -0
  31. package/dist/NotesApp-TQ6IHDNX.css +302 -0
  32. package/dist/NotesMenuBar-25LKN3SE.cjs +15 -0
  33. package/dist/NotesMenuBar-MXLOX7OT.css +56 -0
  34. package/dist/NotesMenuBar-SRV3AIAL.js +6 -0
  35. package/dist/PdfApp-5VBDNRMC.cjs +16 -0
  36. package/dist/PdfApp-BUIC5U5H.css +206 -0
  37. package/dist/PdfApp-RH6MZZX5.js +10 -0
  38. package/dist/PdfMenuBar-NLZC6JHS.js +4 -0
  39. package/dist/PdfMenuBar-QUM72EE4.css +56 -0
  40. package/dist/PdfMenuBar-WBRTKMLN.cjs +13 -0
  41. package/dist/SettingsApp-5LDHEHYV.cjs +20 -0
  42. package/dist/SettingsApp-JVOSEFH3.css +283 -0
  43. package/dist/SettingsApp-X6764D7T.js +14 -0
  44. package/dist/SettingsMenuBar-5CBSSMVM.css +56 -0
  45. package/dist/SettingsMenuBar-VLT6TTCM.js +6 -0
  46. package/dist/SettingsMenuBar-Y5QEXDEO.cjs +15 -0
  47. package/dist/StorybookApp-NQ244BER.css +7 -0
  48. package/dist/StorybookApp-NZDV4X3Y.js +1 -0
  49. package/dist/StorybookApp-VF3KIMU3.cjs +7 -0
  50. package/dist/TerminalApp-CDGWRBFJ.cjs +10 -0
  51. package/dist/TerminalApp-EAATMIMX.css +77 -0
  52. package/dist/TerminalApp-GCKJCM55.js +4 -0
  53. package/dist/TerminalMenuBar-3J26O26Q.css +56 -0
  54. package/dist/TerminalMenuBar-7BH7MGNJ.cjs +14 -0
  55. package/dist/TerminalMenuBar-7JAEQUZ4.js +5 -0
  56. package/dist/UploaderApp-2WYRCUQV.js +10 -0
  57. package/dist/UploaderApp-6KV3TGCT.css +1817 -0
  58. package/dist/UploaderApp-EYFC36PM.cjs +16 -0
  59. package/dist/chunk-2FO445RM.cjs +449 -0
  60. package/dist/chunk-2PSTHGTD.cjs +42 -0
  61. package/dist/chunk-2RQX7QBP.cjs +148 -0
  62. package/dist/chunk-3IICBLEA.js +442 -0
  63. package/dist/chunk-43W6UDUZ.cjs +19 -0
  64. package/dist/chunk-4E45FBAH.js +223 -0
  65. package/dist/chunk-4MCFQPKY.js +444 -0
  66. package/dist/chunk-4OH5RPSQ.cjs +38 -0
  67. package/dist/chunk-4XURSNM4.js +43 -0
  68. package/dist/chunk-4ZCRYHL6.js +407 -0
  69. package/dist/chunk-54PYEQLK.js +283 -0
  70. package/dist/chunk-5C6IQE42.cjs +35 -0
  71. package/dist/chunk-5NOHYJNH.js +84 -0
  72. package/dist/chunk-5PYK5ASL.js +162 -0
  73. package/dist/chunk-5YH6AKEO.js +146 -0
  74. package/dist/chunk-657BJOY5.cjs +324 -0
  75. package/dist/chunk-6QOUYSEE.cjs +2303 -0
  76. package/dist/chunk-7SAFECOJ.js +215 -0
  77. package/dist/chunk-7Y7HB7FB.cjs +53 -0
  78. package/dist/chunk-ABIAPZ6S.cjs +45 -0
  79. package/dist/chunk-AQL372JF.cjs +219 -0
  80. package/dist/chunk-AXDUVZVP.cjs +88 -0
  81. package/dist/chunk-AYFNYHMP.js +541 -0
  82. package/dist/chunk-BDO6B7MZ.cjs +451 -0
  83. package/dist/chunk-BKXEA2BK.cjs +286 -0
  84. package/dist/chunk-BLV47DX2.js +47 -0
  85. package/dist/chunk-BQCD5RAF.cjs +48 -0
  86. package/dist/chunk-BQL3YXMV.js +17429 -0
  87. package/dist/chunk-C6BEZNAM.cjs +45 -0
  88. package/dist/chunk-CFWV2JMR.js +234 -0
  89. package/dist/chunk-CV5PUHAE.cjs +86 -0
  90. package/dist/chunk-D5MVFFID.js +42 -0
  91. package/dist/chunk-D7R55WWT.js +1601 -0
  92. package/dist/chunk-DMNF4CNN.cjs +49 -0
  93. package/dist/chunk-DWP2SYF7.js +55 -0
  94. package/dist/chunk-E55VXNLK.cjs +17498 -0
  95. package/dist/chunk-EAELL43F.js +42 -0
  96. package/dist/chunk-EUQLZW6P.js +48 -0
  97. package/dist/chunk-EX5V2ZTU.js +40 -0
  98. package/dist/chunk-FH4ILMKF.js +38 -0
  99. package/dist/chunk-FRHBM2U7.js +33 -0
  100. package/dist/chunk-FX2TPX3L.cjs +45 -0
  101. package/dist/chunk-GCYD6T52.js +32 -0
  102. package/dist/chunk-GRYCUBJZ.js +9 -0
  103. package/dist/chunk-HWHBSAUC.js +40 -0
  104. package/dist/chunk-ICUE6T7J.cjs +50 -0
  105. package/dist/chunk-IDHP3R4I.js +31 -0
  106. package/dist/chunk-IUOQPOEN.js +2293 -0
  107. package/dist/chunk-J7L2S2GT.cjs +34 -0
  108. package/dist/chunk-JEBKLIMU.cjs +123 -0
  109. package/dist/chunk-KQHICFX3.js +121 -0
  110. package/dist/chunk-LMJE6V4N.cjs +42 -0
  111. package/dist/chunk-MVDGM5Y4.js +68 -0
  112. package/dist/chunk-NVEGEK3N.js +31 -0
  113. package/dist/chunk-NWMSWRUD.js +2236 -0
  114. package/dist/chunk-ODXL6BR3.js +77 -0
  115. package/dist/chunk-OJIDKDKF.js +68 -0
  116. package/dist/chunk-PKPQA5NR.js +15 -0
  117. package/dist/chunk-PNDBLFJW.cjs +50 -0
  118. package/dist/chunk-PYTKNRGM.js +280 -0
  119. package/dist/chunk-Q3WA72BF.cjs +70 -0
  120. package/dist/chunk-QB72BLCJ.cjs +237 -0
  121. package/dist/chunk-QHBBLML3.js +86 -0
  122. package/dist/chunk-RDIDAZ3S.cjs +9 -0
  123. package/dist/chunk-RGJPRXYY.js +48 -0
  124. package/dist/chunk-RQ6OZRUW.cjs +41 -0
  125. package/dist/chunk-SBE4SZAN.cjs +226 -0
  126. package/dist/chunk-SYGUWGWK.cjs +2329 -0
  127. package/dist/chunk-TDZ43MUX.cjs +165 -0
  128. package/dist/chunk-TGWMOHAO.js +17 -0
  129. package/dist/chunk-U4RYIS6Z.cjs +548 -0
  130. package/dist/chunk-UIQCTAVM.cjs +59 -0
  131. package/dist/chunk-XVASHRCE.cjs +70 -0
  132. package/dist/chunk-XYSMVQQD.cjs +1608 -0
  133. package/dist/chunk-YAIWI4Z5.js +7 -0
  134. package/dist/chunk-YP2PLNOF.cjs +34 -0
  135. package/dist/chunk-YSOLW4FS.cjs +11 -0
  136. package/dist/chunk-YY6OUR2U.js +44 -0
  137. package/dist/chunk-YZWS7FDT.cjs +409 -0
  138. package/dist/chunk-Z5YGWL65.cjs +39 -0
  139. package/dist/chunk-ZBGWYTCU.cjs +83 -0
  140. package/dist/chunk-ZHB5Q2M6.js +36 -0
  141. package/dist/chunk-ZHNDXNL4.js +45 -0
  142. package/dist/chunk-ZX3EDZ5C.cjs +17 -0
  143. package/dist/index.cjs +4405 -5156
  144. package/dist/index.css +9192 -0
  145. package/dist/index.d.cts +1324 -762
  146. package/dist/index.d.ts +1324 -762
  147. package/dist/index.js +3648 -5038
  148. package/package.json +14 -6
@@ -0,0 +1,407 @@
1
+ import { useScrollback, useShell, useTerminalStore } from './chunk-NVEGEK3N.js';
2
+ import { getRuntime } from './chunk-FRHBM2U7.js';
3
+ import { Center, Stack, Text } from './chunk-BQL3YXMV.js';
4
+ import { useRef, useState, useEffect, useCallback } from 'react';
5
+ import { Terminal } from '@xterm/xterm';
6
+ import { FitAddon } from '@xterm/addon-fit';
7
+ import { ClipboardAddon } from '@xterm/addon-clipboard';
8
+ import '@xterm/xterm/css/xterm.css';
9
+ import { jsx, jsxs } from 'react/jsx-runtime';
10
+ import { FcInfo } from 'react-icons/fc';
11
+ import { useTranslation } from 'react-i18next';
12
+
13
+ // src/components/Apps/TerminalApp/Terminal.module.css
14
+ var Terminal_default = {};
15
+ var MAX_RETRIES = 5;
16
+ var BASE_RETRY_DELAY_MS = 1e3;
17
+ var XTermTerminal = ({
18
+ shell,
19
+ scrollback,
20
+ onTerminalReady,
21
+ onConnectionChange
22
+ }) => {
23
+ const containerRef = useRef(null);
24
+ const terminalRef = useRef(null);
25
+ const fitAddonRef = useRef(null);
26
+ const clipboardAddonRef = useRef(null);
27
+ const wsRef = useRef(null);
28
+ const retryCountRef = useRef(0);
29
+ const mountedRef = useRef(false);
30
+ const connectingRef = useRef(false);
31
+ const shellRef = useRef(shell);
32
+ const abortControllerRef = useRef(null);
33
+ const initializedRef = useRef(false);
34
+ const [status, setStatus] = useState("idle");
35
+ const [canReconnect, setCanReconnect] = useState(false);
36
+ const onTerminalReadyRef = useRef(onTerminalReady);
37
+ const onConnectionChangeRef = useRef(onConnectionChange);
38
+ useEffect(() => {
39
+ onTerminalReadyRef.current = onTerminalReady;
40
+ }, [onTerminalReady]);
41
+ useEffect(() => {
42
+ onConnectionChangeRef.current = onConnectionChange;
43
+ }, [onConnectionChange]);
44
+ useEffect(() => {
45
+ shellRef.current = shell;
46
+ }, [shell]);
47
+ const updateStatus = useCallback((newStatus) => {
48
+ if (!mountedRef.current) return;
49
+ setStatus(newStatus);
50
+ onConnectionChangeRef.current?.(newStatus);
51
+ }, []);
52
+ const disconnect = useCallback(() => {
53
+ abortControllerRef.current?.abort();
54
+ abortControllerRef.current = null;
55
+ const ws = wsRef.current;
56
+ if (ws) {
57
+ wsRef.current = null;
58
+ if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) {
59
+ try {
60
+ ws.send(JSON.stringify({ type: "kill" }));
61
+ } catch {
62
+ }
63
+ ws.close();
64
+ }
65
+ }
66
+ connectingRef.current = false;
67
+ }, []);
68
+ const connect = useCallback(() => {
69
+ if (!mountedRef.current) return;
70
+ if (connectingRef.current) return;
71
+ if (wsRef.current?.readyState === WebSocket.OPEN) return;
72
+ connectingRef.current = true;
73
+ const abortController = new AbortController();
74
+ abortControllerRef.current = abortController;
75
+ const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
76
+ const wsUrl = `${protocol}//${window.location.host}/api/pty`;
77
+ console.log(`[XTermTerminal] Connecting (attempt ${retryCountRef.current + 1}/${MAX_RETRIES})`);
78
+ updateStatus("connecting");
79
+ const ws = new WebSocket(wsUrl);
80
+ wsRef.current = ws;
81
+ ws.onopen = () => {
82
+ if (!mountedRef.current) return;
83
+ console.log("[XTermTerminal] WebSocket opened");
84
+ };
85
+ ws.onmessage = (event) => {
86
+ if (!mountedRef.current) return;
87
+ if (typeof event.data !== "string") return;
88
+ try {
89
+ const data = JSON.parse(event.data);
90
+ switch (data.type) {
91
+ case "ready":
92
+ console.log("[XTermTerminal] Server ready, sending start");
93
+ ws.send(
94
+ JSON.stringify({
95
+ type: "start",
96
+ shell: shellRef.current,
97
+ cwd: "/app/workspace",
98
+ cols: terminalRef.current?.cols || 80,
99
+ rows: terminalRef.current?.rows || 24
100
+ })
101
+ );
102
+ break;
103
+ case "started":
104
+ console.log("[XTermTerminal] PTY started");
105
+ retryCountRef.current = 0;
106
+ connectingRef.current = false;
107
+ setCanReconnect(false);
108
+ updateStatus("connected");
109
+ break;
110
+ case "exit":
111
+ console.log("[XTermTerminal] PTY exited:", data.code);
112
+ wsRef.current = null;
113
+ connectingRef.current = false;
114
+ updateStatus("disconnected");
115
+ setCanReconnect(true);
116
+ break;
117
+ case "error":
118
+ console.error("[XTermTerminal] Server error:", data.message);
119
+ connectingRef.current = false;
120
+ updateStatus("error");
121
+ setCanReconnect(true);
122
+ break;
123
+ }
124
+ } catch {
125
+ if (terminalRef.current) {
126
+ terminalRef.current.write(event.data);
127
+ }
128
+ }
129
+ };
130
+ ws.onclose = () => {
131
+ if (!mountedRef.current || abortController.signal.aborted) return;
132
+ console.log("[XTermTerminal] WebSocket closed");
133
+ wsRef.current = null;
134
+ connectingRef.current = false;
135
+ if (retryCountRef.current < MAX_RETRIES) {
136
+ retryCountRef.current++;
137
+ const delay = BASE_RETRY_DELAY_MS * Math.pow(2, retryCountRef.current - 1);
138
+ console.log(`[XTermTerminal] Retrying in ${delay}ms`);
139
+ setTimeout(() => {
140
+ if (mountedRef.current && !wsRef.current && !abortController.signal.aborted) {
141
+ connect();
142
+ }
143
+ }, delay);
144
+ } else {
145
+ updateStatus("disconnected");
146
+ setCanReconnect(true);
147
+ }
148
+ };
149
+ ws.onerror = () => {
150
+ if (!mountedRef.current) return;
151
+ console.error("[XTermTerminal] WebSocket error");
152
+ connectingRef.current = false;
153
+ ws.close();
154
+ updateStatus("error");
155
+ setCanReconnect(true);
156
+ };
157
+ }, [updateStatus]);
158
+ useEffect(() => {
159
+ if (initializedRef.current) return void 0;
160
+ initializedRef.current = true;
161
+ mountedRef.current = true;
162
+ if (!containerRef.current) return void 0;
163
+ const terminal = new Terminal({
164
+ fontFamily: "'Operator Mono', 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace",
165
+ fontSize: 14,
166
+ theme: {
167
+ background: "#1e1e1e",
168
+ foreground: "#d4d4d4",
169
+ cursor: "#ffffff",
170
+ selectionBackground: "#264f78"
171
+ },
172
+ cursorStyle: "underline",
173
+ cursorBlink: true,
174
+ scrollback
175
+ });
176
+ const fitAddon = new FitAddon();
177
+ const clipboardAddon = new ClipboardAddon();
178
+ terminal.loadAddon(fitAddon);
179
+ terminal.loadAddon(clipboardAddon);
180
+ terminal.open(containerRef.current);
181
+ fitAddon.fit();
182
+ terminalRef.current = terminal;
183
+ fitAddonRef.current = fitAddon;
184
+ clipboardAddonRef.current = clipboardAddon;
185
+ terminal.onData((data) => {
186
+ if (wsRef.current?.readyState === WebSocket.OPEN) {
187
+ wsRef.current.send(JSON.stringify({ type: "input", data }));
188
+ }
189
+ });
190
+ terminal.onResize(({ cols, rows }) => {
191
+ if (wsRef.current?.readyState === WebSocket.OPEN) {
192
+ wsRef.current.send(JSON.stringify({ type: "resize", cols, rows }));
193
+ }
194
+ });
195
+ onTerminalReadyRef.current?.({
196
+ clear: () => terminal.clear(),
197
+ reset: () => terminal.reset(),
198
+ write: (data) => terminal.write(data),
199
+ copy: async () => {
200
+ const selection = terminal.getSelection();
201
+ console.log("[XTermTerminal] Copy called, selection length:", selection?.length ?? 0);
202
+ if (!selection) {
203
+ console.log("[XTermTerminal] No selection to copy");
204
+ return;
205
+ }
206
+ try {
207
+ if (!navigator.clipboard) {
208
+ console.warn("[XTermTerminal] Clipboard API not available (non-secure context?)");
209
+ fallbackCopy(selection);
210
+ return;
211
+ }
212
+ await navigator.clipboard.writeText(selection);
213
+ console.log("[XTermTerminal] Copied to clipboard successfully");
214
+ } catch (err) {
215
+ console.error("[XTermTerminal] Clipboard write failed:", err);
216
+ fallbackCopy(selection);
217
+ }
218
+ },
219
+ paste: async () => {
220
+ console.log("[XTermTerminal] Paste called");
221
+ try {
222
+ if (!navigator.clipboard) {
223
+ console.warn("[XTermTerminal] Clipboard API not available");
224
+ return;
225
+ }
226
+ const text = await navigator.clipboard.readText();
227
+ console.log("[XTermTerminal] Paste text length:", text.length);
228
+ terminal.paste(text);
229
+ } catch (err) {
230
+ console.error("[XTermTerminal] Paste failed:", err);
231
+ }
232
+ },
233
+ reconnect
234
+ });
235
+ requestAnimationFrame(() => {
236
+ if (mountedRef.current) {
237
+ connect();
238
+ }
239
+ });
240
+ return () => {
241
+ console.log("[XTermTerminal] Cleanup");
242
+ mountedRef.current = false;
243
+ abortControllerRef.current?.abort();
244
+ abortControllerRef.current = null;
245
+ if (terminalRef.current) {
246
+ terminalRef.current.clear();
247
+ }
248
+ disconnect();
249
+ terminal.dispose();
250
+ terminalRef.current = null;
251
+ fitAddonRef.current = null;
252
+ };
253
+ }, []);
254
+ const reconnect = useCallback(() => {
255
+ console.log("[XTermTerminal] Reconnect triggered");
256
+ retryCountRef.current = 0;
257
+ setCanReconnect(false);
258
+ disconnect();
259
+ setTimeout(() => connect(), 100);
260
+ }, [disconnect, connect]);
261
+ useEffect(() => {
262
+ if (terminalRef.current) {
263
+ terminalRef.current.options.scrollback = scrollback;
264
+ }
265
+ }, [scrollback]);
266
+ useEffect(() => {
267
+ if (!containerRef.current) return void 0;
268
+ const observer = new ResizeObserver(() => {
269
+ fitAddonRef.current?.fit();
270
+ });
271
+ observer.observe(containerRef.current);
272
+ return () => observer.disconnect();
273
+ }, []);
274
+ return /* @__PURE__ */ jsxs("div", { className: Terminal_default.terminalContainer, children: [
275
+ /* @__PURE__ */ jsx("div", { className: Terminal_default.terminalWrapper, children: /* @__PURE__ */ jsx("div", { ref: containerRef, className: Terminal_default.terminal, "data-testid": "xterm-terminal" }) }),
276
+ /* @__PURE__ */ jsxs("div", { className: Terminal_default.statusBar, children: [
277
+ /* @__PURE__ */ jsxs("span", { className: Terminal_default.statusIndicator, "data-status": status, children: [
278
+ status === "idle" && "\u25CB",
279
+ status === "connecting" && "\u25CF",
280
+ status === "connected" && "\u25CF",
281
+ status === "disconnected" && "\u25CB",
282
+ status === "error" && "\u25CF"
283
+ ] }),
284
+ /* @__PURE__ */ jsxs("span", { className: Terminal_default.statusText, children: [
285
+ status === "idle" && "Idle",
286
+ status === "connecting" && "Connecting...",
287
+ status === "connected" && "Connected",
288
+ status === "disconnected" && "Disconnected",
289
+ status === "error" && "Error"
290
+ ] }),
291
+ canReconnect && /* @__PURE__ */ jsx("button", { onClick: reconnect, className: Terminal_default.reconnectButton, children: "Reconnect" })
292
+ ] })
293
+ ] });
294
+ };
295
+ function fallbackCopy(text) {
296
+ const textarea = document.createElement("textarea");
297
+ textarea.value = text;
298
+ textarea.style.position = "fixed";
299
+ textarea.style.left = "-9999px";
300
+ textarea.style.opacity = "0";
301
+ document.body.appendChild(textarea);
302
+ textarea.select();
303
+ textarea.setSelectionRange(0, text.length);
304
+ try {
305
+ const success = document.execCommand("copy");
306
+ console.log("[XTermTerminal] Fallback copy result:", success ? "success" : "failed");
307
+ } catch (err) {
308
+ console.error("[XTermTerminal] Fallback copy failed:", err);
309
+ } finally {
310
+ document.body.removeChild(textarea);
311
+ }
312
+ }
313
+ var XTermTerminal_default = XTermTerminal;
314
+ var TerminalNotSupported = () => {
315
+ const { t } = useTranslation("apps");
316
+ return /* @__PURE__ */ jsx(Center, { "data-testid": "terminal-not-supported", style: { width: "100%", height: "100%" }, children: /* @__PURE__ */ jsxs(Stack, { align: "center", gap: "lg", p: "xl", maw: 400, children: [
317
+ /* @__PURE__ */ jsx(Text, { size: "3rem", children: /* @__PURE__ */ jsx(FcInfo, {}) }),
318
+ /* @__PURE__ */ jsx(Text, { size: "lg", fw: 700, ta: "center", children: t("terminal.notAvailable") }),
319
+ /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", ta: "center", children: t("terminal.notAvailableMessage") }),
320
+ /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", ta: "center", children: t("terminal.dockerRequired") })
321
+ ] }) });
322
+ };
323
+ var TerminalNotSupported_default = TerminalNotSupported;
324
+ var TerminalApp = ({ window: window2, notifyReady }) => {
325
+ const win = window2;
326
+ const windowId = win?.id ?? "";
327
+ const runtime = getRuntime();
328
+ const scrollback = useScrollback(windowId);
329
+ const storedShell = useShell(windowId);
330
+ const { setScrollback, setShell, reset } = useTerminalStore();
331
+ const terminalActionsRef = useRef(null);
332
+ const hasNotifiedRef = useRef(false);
333
+ const hasFetchedShellRef = useRef(false);
334
+ const [connectionStatus, setConnectionStatus] = useState("idle");
335
+ const [shellInfo, setShellInfo] = useState(null);
336
+ useEffect(() => {
337
+ if (runtime !== "docker" || hasFetchedShellRef.current) return;
338
+ hasFetchedShellRef.current = true;
339
+ const fetchShellInfo = async () => {
340
+ try {
341
+ const response = await fetch("/api/shell");
342
+ if (response.ok) {
343
+ const info = await response.json();
344
+ setShellInfo(info);
345
+ if (!storedShell || storedShell === "/bin/zsh") {
346
+ setShell(windowId, info.default);
347
+ }
348
+ }
349
+ } catch (err) {
350
+ console.error("[TerminalApp] Failed to fetch shell info:", err);
351
+ if (!storedShell) {
352
+ setShell(windowId, "/bin/zsh");
353
+ }
354
+ }
355
+ };
356
+ fetchShellInfo();
357
+ }, [runtime, storedShell, setShell, windowId]);
358
+ useEffect(() => {
359
+ reset(windowId);
360
+ }, [windowId, reset]);
361
+ const handleTerminalReady = useCallback((actions) => {
362
+ terminalActionsRef.current = actions;
363
+ }, []);
364
+ const handleConnectionChange = useCallback((status) => {
365
+ setConnectionStatus(status);
366
+ }, []);
367
+ useEffect(() => {
368
+ if (!hasNotifiedRef.current && notifyReady) {
369
+ hasNotifiedRef.current = true;
370
+ notifyReady({
371
+ ...win?.contentData ?? {},
372
+ clear: () => terminalActionsRef.current?.clear(),
373
+ reset: () => terminalActionsRef.current?.reset(),
374
+ copy: () => terminalActionsRef.current?.copy(),
375
+ paste: () => terminalActionsRef.current?.paste(),
376
+ reconnect: () => terminalActionsRef.current?.reconnect(),
377
+ setScrollback: (value) => setScrollback(windowId, value),
378
+ setShell: (value) => setShell(windowId, value),
379
+ connectionStatus
380
+ });
381
+ }
382
+ }, [notifyReady, setScrollback, setShell, windowId, win?.contentData, connectionStatus]);
383
+ useEffect(() => {
384
+ if (hasNotifiedRef.current && notifyReady) {
385
+ notifyReady({
386
+ ...win?.contentData ?? {},
387
+ connectionStatus
388
+ });
389
+ }
390
+ }, [connectionStatus, notifyReady, win?.contentData]);
391
+ if (runtime !== "docker") {
392
+ return /* @__PURE__ */ jsx(TerminalNotSupported_default, {});
393
+ }
394
+ const activeShell = storedShell || shellInfo?.default || "/bin/zsh";
395
+ return /* @__PURE__ */ jsx(
396
+ XTermTerminal_default,
397
+ {
398
+ shell: activeShell,
399
+ scrollback,
400
+ onTerminalReady: handleTerminalReady,
401
+ onConnectionChange: handleConnectionChange
402
+ }
403
+ );
404
+ };
405
+ var TerminalApp_default = TerminalApp;
406
+
407
+ export { TerminalApp_default };
@@ -0,0 +1,283 @@
1
+ import { useOpenApp } from './chunk-PYTKNRGM.js';
2
+ import { FileIcon_default } from './chunk-EAELL43F.js';
3
+ import { useDesktopStore, generateUUID, getAppIdForMime, sortNodesByMode } from './chunk-NWMSWRUD.js';
4
+ import { Breadcrumbs, Text, Anchor, UnstyledButton } from './chunk-BQL3YXMV.js';
5
+ import { useCallback, useState, useEffect } from 'react';
6
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
7
+
8
+ var useNotifications = () => {
9
+ const notifications = useDesktopStore((state) => state.notifications);
10
+ const addNotification = useDesktopStore((state) => state.addNotification);
11
+ const removeNotification = useDesktopStore((state) => state.removeNotification);
12
+ const notify = useCallback(
13
+ (title, message, options) => {
14
+ const item = {
15
+ id: generateUUID(),
16
+ title,
17
+ message,
18
+ fcIcon: options?.fcIcon,
19
+ onClose: options?.onClose
20
+ };
21
+ addNotification(item);
22
+ return item.id;
23
+ },
24
+ [addNotification]
25
+ );
26
+ const dismiss = useCallback(
27
+ (id) => {
28
+ removeNotification(id);
29
+ },
30
+ [removeNotification]
31
+ );
32
+ return {
33
+ notifications,
34
+ notify,
35
+ dismiss,
36
+ addNotification,
37
+ removeNotification
38
+ };
39
+ };
40
+
41
+ // src/components/Apps/FilesApp/components/FolderTree.module.css
42
+ var FolderTree_default = {};
43
+ var FolderTreeItem = ({
44
+ node,
45
+ allNodes,
46
+ currentFolderId,
47
+ depth,
48
+ onNavigate
49
+ }) => {
50
+ const children = allNodes.filter(
51
+ (n) => n.parentId === node.id && n.type === "folder"
52
+ );
53
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
54
+ /* @__PURE__ */ jsxs(
55
+ UnstyledButton,
56
+ {
57
+ className: FolderTree_default.item,
58
+ "data-active": currentFolderId === node.id || void 0,
59
+ style: { paddingLeft: 8 + depth * 14 },
60
+ onClick: () => onNavigate(node.id),
61
+ "aria-label": node.name,
62
+ "aria-current": currentFolderId === node.id ? "page" : void 0,
63
+ children: [
64
+ /* @__PURE__ */ jsx(FileIcon_default, { type: "folder", folderNode: node, size: 14 }),
65
+ /* @__PURE__ */ jsx(Text, { size: "xs", ml: 6, truncate: true, children: node.name })
66
+ ]
67
+ }
68
+ ),
69
+ children.map((child) => /* @__PURE__ */ jsx(
70
+ FolderTreeItem,
71
+ {
72
+ node: child,
73
+ allNodes,
74
+ currentFolderId,
75
+ depth: depth + 1,
76
+ onNavigate
77
+ },
78
+ child.id
79
+ ))
80
+ ] });
81
+ };
82
+ var FolderTree = ({ allNodes, currentFolderId, onNavigate }) => {
83
+ const rootFolders = allNodes.filter(
84
+ (n) => n.parentId === null && n.type === "folder"
85
+ );
86
+ return /* @__PURE__ */ jsxs("nav", { className: FolderTree_default.root, "aria-label": "Folder tree", children: [
87
+ /* @__PURE__ */ jsxs(
88
+ UnstyledButton,
89
+ {
90
+ className: FolderTree_default.item,
91
+ "data-active": currentFolderId === null || void 0,
92
+ style: { paddingLeft: 8 },
93
+ onClick: () => onNavigate(null),
94
+ "aria-label": "Home",
95
+ "aria-current": currentFolderId === null ? "page" : void 0,
96
+ children: [
97
+ /* @__PURE__ */ jsx(FileIcon_default, { type: "folder", size: 14 }),
98
+ /* @__PURE__ */ jsx(Text, { size: "xs", ml: 6, fw: 500, children: "Home" })
99
+ ]
100
+ }
101
+ ),
102
+ rootFolders.map((folder) => /* @__PURE__ */ jsx(
103
+ FolderTreeItem,
104
+ {
105
+ node: folder,
106
+ allNodes,
107
+ currentFolderId,
108
+ depth: 1,
109
+ onNavigate
110
+ },
111
+ folder.id
112
+ ))
113
+ ] });
114
+ };
115
+ var FolderTree_default2 = FolderTree;
116
+
117
+ // src/components/Apps/FilesApp/components/FileList.module.css
118
+ var FileList_default = {};
119
+ var FileListItem = ({ node, onNavigate, onOpenFile, onContextMenu }) => {
120
+ const handleDoubleClick = () => {
121
+ if (node.type === "folder") {
122
+ onNavigate(node.id);
123
+ } else {
124
+ onOpenFile(node);
125
+ }
126
+ };
127
+ const label = node.type === "folder" ? `Open folder ${node.name}` : `Open file ${node.name}`;
128
+ return /* @__PURE__ */ jsxs(
129
+ UnstyledButton,
130
+ {
131
+ className: FileList_default.item,
132
+ onDoubleClick: handleDoubleClick,
133
+ onContextMenu: (e) => onContextMenu(e, node.id),
134
+ "aria-label": label,
135
+ role: "option",
136
+ children: [
137
+ /* @__PURE__ */ jsx("div", { className: FileList_default.icon, children: /* @__PURE__ */ jsx(
138
+ FileIcon_default,
139
+ {
140
+ type: node.type,
141
+ name: node.name,
142
+ folderNode: node.type === "folder" ? node : void 0,
143
+ size: 32
144
+ }
145
+ ) }),
146
+ /* @__PURE__ */ jsx(Text, { size: "xs", className: FileList_default.name, truncate: true, children: node.name })
147
+ ]
148
+ }
149
+ );
150
+ };
151
+ var FileList = ({ nodes, onNavigate, onOpenFile, onNodeContextMenu }) => {
152
+ const filesSortMode = useDesktopStore((state) => state.filesSortMode);
153
+ if (nodes.length === 0) {
154
+ return /* @__PURE__ */ jsx("div", { className: FileList_default.empty, children: /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: "This folder is empty" }) });
155
+ }
156
+ const sorted = sortNodesByMode(nodes, filesSortMode);
157
+ return /* @__PURE__ */ jsx("div", { className: FileList_default.grid, role: "listbox", "aria-label": "Files", children: sorted.map((node) => /* @__PURE__ */ jsx(
158
+ FileListItem,
159
+ {
160
+ node,
161
+ onNavigate,
162
+ onOpenFile,
163
+ onContextMenu: onNodeContextMenu
164
+ },
165
+ node.id
166
+ )) });
167
+ };
168
+ var FileList_default2 = FileList;
169
+
170
+ // src/components/Apps/FilesApp/FilesApp.module.css
171
+ var FilesApp_default = {};
172
+ var FilesApp = ({ window }) => {
173
+ const win = window;
174
+ const initialFolderId = win?.contentData?.initialFolderId;
175
+ const fsNodes = useDesktopStore((state) => state.fsNodes);
176
+ const openContextMenu = useDesktopStore((state) => state.openContextMenu);
177
+ const setFilesCurrentFolderId = useDesktopStore((state) => state.setFilesCurrentFolderId);
178
+ const openApp = useOpenApp();
179
+ const { notify } = useNotifications();
180
+ const [currentFolderId, setCurrentFolderIdLocal] = useState(
181
+ initialFolderId ?? null
182
+ );
183
+ const setCurrentFolderId = useCallback(
184
+ (id) => {
185
+ setCurrentFolderIdLocal(id);
186
+ setFilesCurrentFolderId(id);
187
+ },
188
+ [setFilesCurrentFolderId]
189
+ );
190
+ useEffect(() => {
191
+ setFilesCurrentFolderId(initialFolderId ?? null);
192
+ }, [initialFolderId, setFilesCurrentFolderId]);
193
+ const currentNodes = currentFolderId === null ? fsNodes.filter((n) => n.parentId === null) : fsNodes.filter((n) => n.parentId === currentFolderId);
194
+ const buildBreadcrumbs = () => {
195
+ const crumbs2 = [{ id: null, name: "Home" }];
196
+ let id = currentFolderId;
197
+ const trail = [];
198
+ while (id !== null) {
199
+ const node = fsNodes.find((n) => n.id === id);
200
+ if (!node) break;
201
+ trail.unshift({ id: node.id, name: node.name });
202
+ id = node.parentId;
203
+ }
204
+ return [...crumbs2, ...trail];
205
+ };
206
+ const handleContextMenu = useCallback(
207
+ (e) => {
208
+ e.preventDefault();
209
+ e.stopPropagation();
210
+ openContextMenu(e.clientX, e.clientY, "files");
211
+ },
212
+ [openContextMenu]
213
+ );
214
+ const handleNodeContextMenu = useCallback(
215
+ (e, nodeId) => {
216
+ e.preventDefault();
217
+ e.stopPropagation();
218
+ openContextMenu(e.clientX, e.clientY, "files", nodeId);
219
+ },
220
+ [openContextMenu]
221
+ );
222
+ const handleOpenFile = useCallback(
223
+ (node) => {
224
+ try {
225
+ const result = getAppIdForMime(node);
226
+ if (result) {
227
+ openApp(result.appId, { contentData: result.contentData });
228
+ } else {
229
+ const mimeType = node.mimeType || "unknown";
230
+ console.warn(`[FilesApp] Unsupported file type: ${node.name} (${mimeType})`);
231
+ notify(
232
+ "Unsupported file type",
233
+ `Cannot open "${node.name}". File type "${mimeType}" is not supported.`,
234
+ { fcIcon: "FcDeleteRow" }
235
+ );
236
+ }
237
+ } catch (error) {
238
+ console.error("[FilesApp] Error opening file:", error);
239
+ notify("Error opening file", `An unexpected error occurred while opening "${node.name}".`, {
240
+ fcIcon: "FcHighPriority"
241
+ });
242
+ }
243
+ },
244
+ [openApp, notify]
245
+ );
246
+ const crumbs = buildBreadcrumbs();
247
+ return /* @__PURE__ */ jsxs("div", { className: FilesApp_default.root, onContextMenu: handleContextMenu, children: [
248
+ /* @__PURE__ */ jsx("div", { className: FilesApp_default.breadcrumbBar, children: /* @__PURE__ */ jsx(Breadcrumbs, { separator: "\u203A", classNames: { separator: FilesApp_default.breadcrumbSep }, children: crumbs.map((crumb, i) => {
249
+ const isLast = i === crumbs.length - 1;
250
+ return isLast ? /* @__PURE__ */ jsx(Text, { size: "xs", fw: 500, children: crumb.name }, crumb.id ?? "root") : /* @__PURE__ */ jsx(
251
+ Anchor,
252
+ {
253
+ size: "xs",
254
+ onClick: () => setCurrentFolderId(crumb.id),
255
+ children: crumb.name
256
+ },
257
+ crumb.id ?? "root"
258
+ );
259
+ }) }) }),
260
+ /* @__PURE__ */ jsxs("div", { className: FilesApp_default.body, children: [
261
+ /* @__PURE__ */ jsx("aside", { className: FilesApp_default.sidebar, children: /* @__PURE__ */ jsx(
262
+ FolderTree_default2,
263
+ {
264
+ allNodes: fsNodes,
265
+ currentFolderId,
266
+ onNavigate: setCurrentFolderId
267
+ }
268
+ ) }),
269
+ /* @__PURE__ */ jsx("main", { className: FilesApp_default.content, children: /* @__PURE__ */ jsx(
270
+ FileList_default2,
271
+ {
272
+ nodes: currentNodes,
273
+ onNavigate: setCurrentFolderId,
274
+ onOpenFile: handleOpenFile,
275
+ onNodeContextMenu: handleNodeContextMenu
276
+ }
277
+ ) })
278
+ ] })
279
+ ] });
280
+ };
281
+ var FilesApp_default2 = FilesApp;
282
+
283
+ export { FilesApp_default2 as FilesApp_default, useNotifications };