@fde-desktop/fde-core 0.3.8 → 0.4.2

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,7 @@
1
+ // src/hooks/useMenuBarUtils.ts
2
+ function appendDirtyIndicator(items, isDirty) {
3
+ if (!isDirty) return items;
4
+ return [...items, { type: "dirty-indicator" }];
5
+ }
6
+
7
+ export { appendDirtyIndicator };
@@ -0,0 +1,34 @@
1
+ 'use strict';
2
+
3
+ var zustand = require('zustand');
4
+
5
+ // src/stores/factories/createWindowStateStore.ts
6
+ function createDirtyStateStore() {
7
+ const DEFAULT_STATE = { isDirty: false };
8
+ return zustand.create((set, get) => ({
9
+ states: {},
10
+ getIsDirty: (windowId) => get().states[windowId]?.isDirty ?? false,
11
+ setIsDirty: (windowId, isDirty) => {
12
+ const currentIsDirty = get().states[windowId]?.isDirty ?? false;
13
+ if (currentIsDirty === isDirty) return;
14
+ set((s) => ({
15
+ states: {
16
+ ...s.states,
17
+ [windowId]: { ...s.states[windowId] ?? DEFAULT_STATE, isDirty }
18
+ }
19
+ }));
20
+ },
21
+ reset: (windowId) => {
22
+ set((s) => ({
23
+ states: { ...s.states, [windowId]: DEFAULT_STATE }
24
+ }));
25
+ }
26
+ }));
27
+ }
28
+
29
+ // src/components/Apps/SettingsApp/settingsAppStore.ts
30
+ var useSettingsAppStore = createDirtyStateStore();
31
+ var useIsDirty = (windowId) => useSettingsAppStore((s) => s.states[windowId]?.isDirty ?? false);
32
+
33
+ exports.useIsDirty = useIsDirty;
34
+ exports.useSettingsAppStore = useSettingsAppStore;
@@ -0,0 +1,11 @@
1
+ 'use strict';
2
+
3
+ var chunk7Y7HB7FB_cjs = require('./chunk-7Y7HB7FB.cjs');
4
+
5
+ // src/components/Shared/VscIcon.tsx
6
+ var VscIcon = ({ name, size = 20, color }) => {
7
+ return chunk7Y7HB7FB_cjs.useVscIconElement(name, { size, color }) ?? null;
8
+ };
9
+ var VscIcon_default = VscIcon;
10
+
11
+ exports.VscIcon_default = VscIcon_default;
@@ -0,0 +1,44 @@
1
+ import { useState, useEffect, createElement } from 'react';
2
+
3
+ // src/hooks/useDynamicIcon.ts
4
+ var useDynamicIcon = (name, library) => {
5
+ const [Icon, setIcon] = useState(null);
6
+ useEffect(() => {
7
+ if (!name || typeof window === "undefined") return;
8
+ let cancelled = false;
9
+ const loadIcon = async () => {
10
+ try {
11
+ let mod;
12
+ if (library === "fi") {
13
+ mod = await import('react-icons/fi');
14
+ } else if (library === "vsc") {
15
+ mod = await import('react-icons/vsc');
16
+ } else {
17
+ mod = await import('react-icons/fc');
18
+ }
19
+ if (!cancelled) {
20
+ setIcon(() => mod[name] ?? null);
21
+ }
22
+ } catch {
23
+ }
24
+ };
25
+ loadIcon();
26
+ return () => {
27
+ cancelled = true;
28
+ };
29
+ }, [name, library]);
30
+ return Icon;
31
+ };
32
+ var useDynamicIconElement = (name, library, props) => {
33
+ const Icon = useDynamicIcon(name, library);
34
+ if (!Icon) return null;
35
+ return createElement(Icon, props);
36
+ };
37
+ var useFiIcon = (name) => useDynamicIcon(name, "fi");
38
+ var useFcIcon = (name) => useDynamicIcon(name, "fc");
39
+ var useVscIcon = (name) => useDynamicIcon(name, "vsc");
40
+ var useFiIconElement = (name, props) => useDynamicIconElement(name, "fi", props);
41
+ var useFcIconElement = (name, props) => useDynamicIconElement(name, "fc", props);
42
+ var useVscIconElement = (name, props) => useDynamicIconElement(name, "vsc", props);
43
+
44
+ export { useDynamicIcon, useDynamicIconElement, useFcIcon, useFcIconElement, useFiIcon, useFiIconElement, useVscIcon, useVscIconElement };
@@ -0,0 +1,409 @@
1
+ 'use strict';
2
+
3
+ var chunk5C6IQE42_cjs = require('./chunk-5C6IQE42.cjs');
4
+ var chunkZ5YGWL65_cjs = require('./chunk-Z5YGWL65.cjs');
5
+ var chunkE55VXNLK_cjs = require('./chunk-E55VXNLK.cjs');
6
+ var react = require('react');
7
+ var xterm = require('@xterm/xterm');
8
+ var addonFit = require('@xterm/addon-fit');
9
+ var addonClipboard = require('@xterm/addon-clipboard');
10
+ require('@xterm/xterm/css/xterm.css');
11
+ var jsxRuntime = require('react/jsx-runtime');
12
+ var fc = require('react-icons/fc');
13
+ var reactI18next = require('react-i18next');
14
+
15
+ // src/components/Apps/TerminalApp/Terminal.module.css
16
+ var Terminal_default = {};
17
+ var MAX_RETRIES = 5;
18
+ var BASE_RETRY_DELAY_MS = 1e3;
19
+ var XTermTerminal = ({
20
+ shell,
21
+ scrollback,
22
+ onTerminalReady,
23
+ onConnectionChange
24
+ }) => {
25
+ const containerRef = react.useRef(null);
26
+ const terminalRef = react.useRef(null);
27
+ const fitAddonRef = react.useRef(null);
28
+ const clipboardAddonRef = react.useRef(null);
29
+ const wsRef = react.useRef(null);
30
+ const retryCountRef = react.useRef(0);
31
+ const mountedRef = react.useRef(false);
32
+ const connectingRef = react.useRef(false);
33
+ const shellRef = react.useRef(shell);
34
+ const abortControllerRef = react.useRef(null);
35
+ const initializedRef = react.useRef(false);
36
+ const [status, setStatus] = react.useState("idle");
37
+ const [canReconnect, setCanReconnect] = react.useState(false);
38
+ const onTerminalReadyRef = react.useRef(onTerminalReady);
39
+ const onConnectionChangeRef = react.useRef(onConnectionChange);
40
+ react.useEffect(() => {
41
+ onTerminalReadyRef.current = onTerminalReady;
42
+ }, [onTerminalReady]);
43
+ react.useEffect(() => {
44
+ onConnectionChangeRef.current = onConnectionChange;
45
+ }, [onConnectionChange]);
46
+ react.useEffect(() => {
47
+ shellRef.current = shell;
48
+ }, [shell]);
49
+ const updateStatus = react.useCallback((newStatus) => {
50
+ if (!mountedRef.current) return;
51
+ setStatus(newStatus);
52
+ onConnectionChangeRef.current?.(newStatus);
53
+ }, []);
54
+ const disconnect = react.useCallback(() => {
55
+ abortControllerRef.current?.abort();
56
+ abortControllerRef.current = null;
57
+ const ws = wsRef.current;
58
+ if (ws) {
59
+ wsRef.current = null;
60
+ if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) {
61
+ try {
62
+ ws.send(JSON.stringify({ type: "kill" }));
63
+ } catch {
64
+ }
65
+ ws.close();
66
+ }
67
+ }
68
+ connectingRef.current = false;
69
+ }, []);
70
+ const connect = react.useCallback(() => {
71
+ if (!mountedRef.current) return;
72
+ if (connectingRef.current) return;
73
+ if (wsRef.current?.readyState === WebSocket.OPEN) return;
74
+ connectingRef.current = true;
75
+ const abortController = new AbortController();
76
+ abortControllerRef.current = abortController;
77
+ const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
78
+ const wsUrl = `${protocol}//${window.location.host}/api/pty`;
79
+ console.log(`[XTermTerminal] Connecting (attempt ${retryCountRef.current + 1}/${MAX_RETRIES})`);
80
+ updateStatus("connecting");
81
+ const ws = new WebSocket(wsUrl);
82
+ wsRef.current = ws;
83
+ ws.onopen = () => {
84
+ if (!mountedRef.current) return;
85
+ console.log("[XTermTerminal] WebSocket opened");
86
+ };
87
+ ws.onmessage = (event) => {
88
+ if (!mountedRef.current) return;
89
+ if (typeof event.data !== "string") return;
90
+ try {
91
+ const data = JSON.parse(event.data);
92
+ switch (data.type) {
93
+ case "ready":
94
+ console.log("[XTermTerminal] Server ready, sending start");
95
+ ws.send(
96
+ JSON.stringify({
97
+ type: "start",
98
+ shell: shellRef.current,
99
+ cwd: "/app/workspace",
100
+ cols: terminalRef.current?.cols || 80,
101
+ rows: terminalRef.current?.rows || 24
102
+ })
103
+ );
104
+ break;
105
+ case "started":
106
+ console.log("[XTermTerminal] PTY started");
107
+ retryCountRef.current = 0;
108
+ connectingRef.current = false;
109
+ setCanReconnect(false);
110
+ updateStatus("connected");
111
+ break;
112
+ case "exit":
113
+ console.log("[XTermTerminal] PTY exited:", data.code);
114
+ wsRef.current = null;
115
+ connectingRef.current = false;
116
+ updateStatus("disconnected");
117
+ setCanReconnect(true);
118
+ break;
119
+ case "error":
120
+ console.error("[XTermTerminal] Server error:", data.message);
121
+ connectingRef.current = false;
122
+ updateStatus("error");
123
+ setCanReconnect(true);
124
+ break;
125
+ }
126
+ } catch {
127
+ if (terminalRef.current) {
128
+ terminalRef.current.write(event.data);
129
+ }
130
+ }
131
+ };
132
+ ws.onclose = () => {
133
+ if (!mountedRef.current || abortController.signal.aborted) return;
134
+ console.log("[XTermTerminal] WebSocket closed");
135
+ wsRef.current = null;
136
+ connectingRef.current = false;
137
+ if (retryCountRef.current < MAX_RETRIES) {
138
+ retryCountRef.current++;
139
+ const delay = BASE_RETRY_DELAY_MS * Math.pow(2, retryCountRef.current - 1);
140
+ console.log(`[XTermTerminal] Retrying in ${delay}ms`);
141
+ setTimeout(() => {
142
+ if (mountedRef.current && !wsRef.current && !abortController.signal.aborted) {
143
+ connect();
144
+ }
145
+ }, delay);
146
+ } else {
147
+ updateStatus("disconnected");
148
+ setCanReconnect(true);
149
+ }
150
+ };
151
+ ws.onerror = () => {
152
+ if (!mountedRef.current) return;
153
+ console.error("[XTermTerminal] WebSocket error");
154
+ connectingRef.current = false;
155
+ ws.close();
156
+ updateStatus("error");
157
+ setCanReconnect(true);
158
+ };
159
+ }, [updateStatus]);
160
+ react.useEffect(() => {
161
+ if (initializedRef.current) return void 0;
162
+ initializedRef.current = true;
163
+ mountedRef.current = true;
164
+ if (!containerRef.current) return void 0;
165
+ const terminal = new xterm.Terminal({
166
+ fontFamily: "'Operator Mono', 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace",
167
+ fontSize: 14,
168
+ theme: {
169
+ background: "#1e1e1e",
170
+ foreground: "#d4d4d4",
171
+ cursor: "#ffffff",
172
+ selectionBackground: "#264f78"
173
+ },
174
+ cursorStyle: "underline",
175
+ cursorBlink: true,
176
+ scrollback
177
+ });
178
+ const fitAddon = new addonFit.FitAddon();
179
+ const clipboardAddon = new addonClipboard.ClipboardAddon();
180
+ terminal.loadAddon(fitAddon);
181
+ terminal.loadAddon(clipboardAddon);
182
+ terminal.open(containerRef.current);
183
+ fitAddon.fit();
184
+ terminalRef.current = terminal;
185
+ fitAddonRef.current = fitAddon;
186
+ clipboardAddonRef.current = clipboardAddon;
187
+ terminal.onData((data) => {
188
+ if (wsRef.current?.readyState === WebSocket.OPEN) {
189
+ wsRef.current.send(JSON.stringify({ type: "input", data }));
190
+ }
191
+ });
192
+ terminal.onResize(({ cols, rows }) => {
193
+ if (wsRef.current?.readyState === WebSocket.OPEN) {
194
+ wsRef.current.send(JSON.stringify({ type: "resize", cols, rows }));
195
+ }
196
+ });
197
+ onTerminalReadyRef.current?.({
198
+ clear: () => terminal.clear(),
199
+ reset: () => terminal.reset(),
200
+ write: (data) => terminal.write(data),
201
+ copy: async () => {
202
+ const selection = terminal.getSelection();
203
+ console.log("[XTermTerminal] Copy called, selection length:", selection?.length ?? 0);
204
+ if (!selection) {
205
+ console.log("[XTermTerminal] No selection to copy");
206
+ return;
207
+ }
208
+ try {
209
+ if (!navigator.clipboard) {
210
+ console.warn("[XTermTerminal] Clipboard API not available (non-secure context?)");
211
+ fallbackCopy(selection);
212
+ return;
213
+ }
214
+ await navigator.clipboard.writeText(selection);
215
+ console.log("[XTermTerminal] Copied to clipboard successfully");
216
+ } catch (err) {
217
+ console.error("[XTermTerminal] Clipboard write failed:", err);
218
+ fallbackCopy(selection);
219
+ }
220
+ },
221
+ paste: async () => {
222
+ console.log("[XTermTerminal] Paste called");
223
+ try {
224
+ if (!navigator.clipboard) {
225
+ console.warn("[XTermTerminal] Clipboard API not available");
226
+ return;
227
+ }
228
+ const text = await navigator.clipboard.readText();
229
+ console.log("[XTermTerminal] Paste text length:", text.length);
230
+ terminal.paste(text);
231
+ } catch (err) {
232
+ console.error("[XTermTerminal] Paste failed:", err);
233
+ }
234
+ },
235
+ reconnect
236
+ });
237
+ requestAnimationFrame(() => {
238
+ if (mountedRef.current) {
239
+ connect();
240
+ }
241
+ });
242
+ return () => {
243
+ console.log("[XTermTerminal] Cleanup");
244
+ mountedRef.current = false;
245
+ abortControllerRef.current?.abort();
246
+ abortControllerRef.current = null;
247
+ if (terminalRef.current) {
248
+ terminalRef.current.clear();
249
+ }
250
+ disconnect();
251
+ terminal.dispose();
252
+ terminalRef.current = null;
253
+ fitAddonRef.current = null;
254
+ };
255
+ }, []);
256
+ const reconnect = react.useCallback(() => {
257
+ console.log("[XTermTerminal] Reconnect triggered");
258
+ retryCountRef.current = 0;
259
+ setCanReconnect(false);
260
+ disconnect();
261
+ setTimeout(() => connect(), 100);
262
+ }, [disconnect, connect]);
263
+ react.useEffect(() => {
264
+ if (terminalRef.current) {
265
+ terminalRef.current.options.scrollback = scrollback;
266
+ }
267
+ }, [scrollback]);
268
+ react.useEffect(() => {
269
+ if (!containerRef.current) return void 0;
270
+ const observer = new ResizeObserver(() => {
271
+ fitAddonRef.current?.fit();
272
+ });
273
+ observer.observe(containerRef.current);
274
+ return () => observer.disconnect();
275
+ }, []);
276
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: Terminal_default.terminalContainer, children: [
277
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: Terminal_default.terminalWrapper, children: /* @__PURE__ */ jsxRuntime.jsx("div", { ref: containerRef, className: Terminal_default.terminal, "data-testid": "xterm-terminal" }) }),
278
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: Terminal_default.statusBar, children: [
279
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: Terminal_default.statusIndicator, "data-status": status, children: [
280
+ status === "idle" && "\u25CB",
281
+ status === "connecting" && "\u25CF",
282
+ status === "connected" && "\u25CF",
283
+ status === "disconnected" && "\u25CB",
284
+ status === "error" && "\u25CF"
285
+ ] }),
286
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: Terminal_default.statusText, children: [
287
+ status === "idle" && "Idle",
288
+ status === "connecting" && "Connecting...",
289
+ status === "connected" && "Connected",
290
+ status === "disconnected" && "Disconnected",
291
+ status === "error" && "Error"
292
+ ] }),
293
+ canReconnect && /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: reconnect, className: Terminal_default.reconnectButton, children: "Reconnect" })
294
+ ] })
295
+ ] });
296
+ };
297
+ function fallbackCopy(text) {
298
+ const textarea = document.createElement("textarea");
299
+ textarea.value = text;
300
+ textarea.style.position = "fixed";
301
+ textarea.style.left = "-9999px";
302
+ textarea.style.opacity = "0";
303
+ document.body.appendChild(textarea);
304
+ textarea.select();
305
+ textarea.setSelectionRange(0, text.length);
306
+ try {
307
+ const success = document.execCommand("copy");
308
+ console.log("[XTermTerminal] Fallback copy result:", success ? "success" : "failed");
309
+ } catch (err) {
310
+ console.error("[XTermTerminal] Fallback copy failed:", err);
311
+ } finally {
312
+ document.body.removeChild(textarea);
313
+ }
314
+ }
315
+ var XTermTerminal_default = XTermTerminal;
316
+ var TerminalNotSupported = () => {
317
+ const { t } = reactI18next.useTranslation("apps");
318
+ return /* @__PURE__ */ jsxRuntime.jsx(chunkE55VXNLK_cjs.Center, { "data-testid": "terminal-not-supported", style: { width: "100%", height: "100%" }, children: /* @__PURE__ */ jsxRuntime.jsxs(chunkE55VXNLK_cjs.Stack, { align: "center", gap: "lg", p: "xl", maw: 400, children: [
319
+ /* @__PURE__ */ jsxRuntime.jsx(chunkE55VXNLK_cjs.Text, { size: "3rem", children: /* @__PURE__ */ jsxRuntime.jsx(fc.FcInfo, {}) }),
320
+ /* @__PURE__ */ jsxRuntime.jsx(chunkE55VXNLK_cjs.Text, { size: "lg", fw: 700, ta: "center", children: t("terminal.notAvailable") }),
321
+ /* @__PURE__ */ jsxRuntime.jsx(chunkE55VXNLK_cjs.Text, { size: "sm", c: "dimmed", ta: "center", children: t("terminal.notAvailableMessage") }),
322
+ /* @__PURE__ */ jsxRuntime.jsx(chunkE55VXNLK_cjs.Text, { size: "sm", c: "dimmed", ta: "center", children: t("terminal.dockerRequired") })
323
+ ] }) });
324
+ };
325
+ var TerminalNotSupported_default = TerminalNotSupported;
326
+ var TerminalApp = ({ window: window2, notifyReady }) => {
327
+ const win = window2;
328
+ const windowId = win?.id ?? "";
329
+ const runtime = chunkZ5YGWL65_cjs.getRuntime();
330
+ const scrollback = chunk5C6IQE42_cjs.useScrollback(windowId);
331
+ const storedShell = chunk5C6IQE42_cjs.useShell(windowId);
332
+ const { setScrollback, setShell, reset } = chunk5C6IQE42_cjs.useTerminalStore();
333
+ const terminalActionsRef = react.useRef(null);
334
+ const hasNotifiedRef = react.useRef(false);
335
+ const hasFetchedShellRef = react.useRef(false);
336
+ const [connectionStatus, setConnectionStatus] = react.useState("idle");
337
+ const [shellInfo, setShellInfo] = react.useState(null);
338
+ react.useEffect(() => {
339
+ if (runtime !== "docker" || hasFetchedShellRef.current) return;
340
+ hasFetchedShellRef.current = true;
341
+ const fetchShellInfo = async () => {
342
+ try {
343
+ const response = await fetch("/api/shell");
344
+ if (response.ok) {
345
+ const info = await response.json();
346
+ setShellInfo(info);
347
+ if (!storedShell || storedShell === "/bin/zsh") {
348
+ setShell(windowId, info.default);
349
+ }
350
+ }
351
+ } catch (err) {
352
+ console.error("[TerminalApp] Failed to fetch shell info:", err);
353
+ if (!storedShell) {
354
+ setShell(windowId, "/bin/zsh");
355
+ }
356
+ }
357
+ };
358
+ fetchShellInfo();
359
+ }, [runtime, storedShell, setShell, windowId]);
360
+ react.useEffect(() => {
361
+ reset(windowId);
362
+ }, [windowId, reset]);
363
+ const handleTerminalReady = react.useCallback((actions) => {
364
+ terminalActionsRef.current = actions;
365
+ }, []);
366
+ const handleConnectionChange = react.useCallback((status) => {
367
+ setConnectionStatus(status);
368
+ }, []);
369
+ react.useEffect(() => {
370
+ if (!hasNotifiedRef.current && notifyReady) {
371
+ hasNotifiedRef.current = true;
372
+ notifyReady({
373
+ ...win?.contentData ?? {},
374
+ clear: () => terminalActionsRef.current?.clear(),
375
+ reset: () => terminalActionsRef.current?.reset(),
376
+ copy: () => terminalActionsRef.current?.copy(),
377
+ paste: () => terminalActionsRef.current?.paste(),
378
+ reconnect: () => terminalActionsRef.current?.reconnect(),
379
+ setScrollback: (value) => setScrollback(windowId, value),
380
+ setShell: (value) => setShell(windowId, value),
381
+ connectionStatus
382
+ });
383
+ }
384
+ }, [notifyReady, setScrollback, setShell, windowId, win?.contentData, connectionStatus]);
385
+ react.useEffect(() => {
386
+ if (hasNotifiedRef.current && notifyReady) {
387
+ notifyReady({
388
+ ...win?.contentData ?? {},
389
+ connectionStatus
390
+ });
391
+ }
392
+ }, [connectionStatus, notifyReady, win?.contentData]);
393
+ if (runtime !== "docker") {
394
+ return /* @__PURE__ */ jsxRuntime.jsx(TerminalNotSupported_default, {});
395
+ }
396
+ const activeShell = storedShell || shellInfo?.default || "/bin/zsh";
397
+ return /* @__PURE__ */ jsxRuntime.jsx(
398
+ XTermTerminal_default,
399
+ {
400
+ shell: activeShell,
401
+ scrollback,
402
+ onTerminalReady: handleTerminalReady,
403
+ onConnectionChange: handleConnectionChange
404
+ }
405
+ );
406
+ };
407
+ var TerminalApp_default = TerminalApp;
408
+
409
+ exports.TerminalApp_default = TerminalApp_default;
@@ -0,0 +1,39 @@
1
+ 'use strict';
2
+
3
+ // src/utils/getRuntime.ts
4
+ function getRuntime() {
5
+ if (typeof window === "undefined") {
6
+ return "browser";
7
+ }
8
+ if (window.__FDE_IN_DOCKER__ === true) {
9
+ return "docker";
10
+ }
11
+ if (typeof navigator !== "undefined" && /Electron/i.test(navigator.userAgent)) {
12
+ return "electron";
13
+ }
14
+ return "browser";
15
+ }
16
+ function isDocker() {
17
+ return getRuntime() === "docker";
18
+ }
19
+ function isElectron() {
20
+ return getRuntime() === "electron";
21
+ }
22
+ function isBrowser() {
23
+ return getRuntime() === "browser";
24
+ }
25
+ function isDev() {
26
+ if (typeof window === "undefined") {
27
+ return false;
28
+ }
29
+ if (window.__IS_DEV__ === true) {
30
+ return true;
31
+ }
32
+ return window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1";
33
+ }
34
+
35
+ exports.getRuntime = getRuntime;
36
+ exports.isBrowser = isBrowser;
37
+ exports.isDev = isDev;
38
+ exports.isDocker = isDocker;
39
+ exports.isElectron = isElectron;
@@ -0,0 +1,83 @@
1
+ 'use strict';
2
+
3
+ var chunk6QOUYSEE_cjs = require('./chunk-6QOUYSEE.cjs');
4
+ var zustand = require('zustand');
5
+ var react = require('react');
6
+
7
+ var useCloseModalStore = zustand.create()((set) => ({
8
+ isOpen: false,
9
+ windowId: null,
10
+ onSave: null,
11
+ onDiscard: null,
12
+ openModal: (windowId, onSave, onDiscard) => set({
13
+ isOpen: true,
14
+ windowId,
15
+ onSave,
16
+ onDiscard
17
+ }),
18
+ closeModal: () => set({ isOpen: false, windowId: null, onSave: null, onDiscard: null })
19
+ }));
20
+ var closeInterceptors = /* @__PURE__ */ new Map();
21
+ var registerCloseInterceptor = (windowId, interceptor) => {
22
+ closeInterceptors.set(windowId, interceptor);
23
+ };
24
+ var unregisterCloseInterceptor = (windowId) => {
25
+ closeInterceptors.delete(windowId);
26
+ };
27
+ var getCloseInterceptor = (windowId) => {
28
+ return closeInterceptors.get(windowId);
29
+ };
30
+ function useCloseInterceptor({
31
+ isDirtyGetter,
32
+ windowId,
33
+ onDiscard,
34
+ onSave
35
+ }) {
36
+ const windows = chunk6QOUYSEE_cjs.useDesktopStore((state) => state.windows);
37
+ const closeWindow = chunk6QOUYSEE_cjs.useDesktopStore((state) => state.closeWindow);
38
+ const openModal = useCloseModalStore((state) => state.openModal);
39
+ const activeWindowId = windowId ?? windows.find((w) => w.isOpen)?.id;
40
+ const isDirtyGetterRef = react.useRef(isDirtyGetter);
41
+ const isActiveRef = react.useRef(false);
42
+ const onDiscardRef = react.useRef(onDiscard);
43
+ const onSaveRef = react.useRef(onSave);
44
+ react.useEffect(() => {
45
+ isDirtyGetterRef.current = isDirtyGetter;
46
+ onDiscardRef.current = onDiscard;
47
+ onSaveRef.current = onSave;
48
+ }, [isDirtyGetter, onDiscard, onSave]);
49
+ const handleDiscard = react.useCallback(() => {
50
+ if (!activeWindowId) return;
51
+ onDiscardRef.current?.();
52
+ closeWindow(activeWindowId);
53
+ }, [activeWindowId, closeWindow]);
54
+ const handleSave = react.useCallback(() => {
55
+ if (!activeWindowId) return;
56
+ onSaveRef.current?.();
57
+ closeWindow(activeWindowId);
58
+ }, [activeWindowId, closeWindow]);
59
+ react.useEffect(() => {
60
+ if (!activeWindowId) return;
61
+ isActiveRef.current = true;
62
+ const closeInterceptor = () => {
63
+ if (!isActiveRef.current) return true;
64
+ const isDirty = isDirtyGetterRef.current();
65
+ if (isDirty) {
66
+ openModal(activeWindowId, handleSave, handleDiscard);
67
+ return false;
68
+ }
69
+ return true;
70
+ };
71
+ registerCloseInterceptor(activeWindowId, closeInterceptor);
72
+ return () => {
73
+ isActiveRef.current = false;
74
+ unregisterCloseInterceptor(activeWindowId);
75
+ };
76
+ }, [activeWindowId, openModal, handleSave, handleDiscard]);
77
+ }
78
+
79
+ exports.getCloseInterceptor = getCloseInterceptor;
80
+ exports.registerCloseInterceptor = registerCloseInterceptor;
81
+ exports.unregisterCloseInterceptor = unregisterCloseInterceptor;
82
+ exports.useCloseInterceptor = useCloseInterceptor;
83
+ exports.useCloseModalStore = useCloseModalStore;
@@ -0,0 +1,36 @@
1
+ import { BREAKPOINTS } from './chunk-NWMSWRUD.js';
2
+ import { useState, useCallback, useEffect } from 'react';
3
+
4
+ var DIMENSIONS = {
5
+ mobile: { defaultWidth: 300, defaultHeight: 500, minWidth: 280, minHeight: 400 },
6
+ tablet: { defaultWidth: 600, defaultHeight: 450, minWidth: 400, minHeight: 350 },
7
+ desktop: { defaultWidth: 800, defaultHeight: 600, minWidth: 640, minHeight: 480 },
8
+ largeDesktop: { defaultWidth: 1e3, defaultHeight: 700, minWidth: 800, minHeight: 600 }
9
+ };
10
+ var LARGE_DESKTOP = 1440;
11
+ var getAdaptiveDimensions = () => {
12
+ const width = window.innerWidth;
13
+ if (width < BREAKPOINTS.MOBILE) {
14
+ return DIMENSIONS.mobile;
15
+ }
16
+ if (width < BREAKPOINTS.TABLET) {
17
+ return DIMENSIONS.tablet;
18
+ }
19
+ if (width < LARGE_DESKTOP) {
20
+ return DIMENSIONS.desktop;
21
+ }
22
+ return DIMENSIONS.largeDesktop;
23
+ };
24
+ function useAdaptiveDimensions() {
25
+ const [dimensions, setDimensions] = useState(() => getAdaptiveDimensions());
26
+ const updateDimensions = useCallback(() => {
27
+ setDimensions(getAdaptiveDimensions());
28
+ }, []);
29
+ useEffect(() => {
30
+ window.addEventListener("resize", updateDimensions);
31
+ return () => window.removeEventListener("resize", updateDimensions);
32
+ }, [updateDimensions]);
33
+ return dimensions;
34
+ }
35
+
36
+ export { useAdaptiveDimensions };