@fde-desktop/fde-core 0.3.7 → 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 (163) 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 +5641 -0
  144. package/dist/index.css +9192 -0
  145. package/dist/index.d.cts +2019 -0
  146. package/dist/index.d.ts +2019 -11
  147. package/dist/index.js +4794 -6598
  148. package/package.json +26 -14
  149. package/dist/apps/index.d.ts +0 -2
  150. package/dist/components/index.d.ts +0 -1
  151. package/dist/constants/index.d.ts +0 -12
  152. package/dist/domain/index.d.ts +0 -5
  153. package/dist/fc-D_a-n3yF.js +0 -20204
  154. package/dist/fi-DwZ_MW7F.js +0 -9993
  155. package/dist/hooks/index.d.ts +0 -28
  156. package/dist/i18n/index.d.ts +0 -2
  157. package/dist/iconBase-B764UN-4.js +0 -108
  158. package/dist/infrastructure/index.d.ts +0 -6
  159. package/dist/interfaces/index.d.ts +0 -11
  160. package/dist/stores/index.d.ts +0 -8
  161. package/dist/types/index.d.ts +0 -4
  162. package/dist/utils/index.d.ts +0 -14
  163. package/dist/vsc-CeA30gcb.js +0 -9574
@@ -0,0 +1,2236 @@
1
+ import { isBrowser, isDocker } from './chunk-FRHBM2U7.js';
2
+ import { v4 } from 'uuid';
3
+ export { v4 } from 'uuid';
4
+ import { create } from 'zustand';
5
+ import { persist, createJSONStorage } from 'zustand/middleware';
6
+
7
+ function generateUUID() {
8
+ return v4();
9
+ }
10
+
11
+ // src/domain/entities/Window.ts
12
+ var createWindow = (input) => ({
13
+ alwaysOnTop: false,
14
+ ...input,
15
+ id: generateUUID(),
16
+ isOpen: true,
17
+ state: "normal",
18
+ zIndex: 0
19
+ });
20
+
21
+ // src/domain/entities/DesktopIcon.ts
22
+ var createDesktopIcon = (input) => ({
23
+ ...input,
24
+ id: generateUUID()
25
+ });
26
+
27
+ // src/domain/entities/LauncherFolder.ts
28
+ var createLauncherFolder = (input) => ({
29
+ id: generateUUID(),
30
+ name: input.name,
31
+ fcIcon: input.fcIcon,
32
+ appIds: input.appIds ?? [],
33
+ isExpanded: input.isExpanded ?? false,
34
+ isPredefined: input.isPredefined ?? false,
35
+ order: input.order ?? 0
36
+ });
37
+ var createPredefinedLauncherFolder = (input) => ({
38
+ id: generateUUID(),
39
+ name: input.name,
40
+ fcIcon: input.fcIcon,
41
+ appIds: input.appIds ?? [],
42
+ isExpanded: input.isExpanded ?? false,
43
+ isPredefined: true,
44
+ order: input.order ?? 0
45
+ });
46
+ var updateLauncherFolder = (folder, updates) => ({
47
+ ...folder,
48
+ ...updates
49
+ });
50
+
51
+ // src/domain/entities/FileSystem.ts
52
+ var createFolder = (name, parentId = null, options = {}) => ({
53
+ id: generateUUID(),
54
+ name,
55
+ type: "folder",
56
+ parentId,
57
+ children: [],
58
+ createdAt: /* @__PURE__ */ new Date(),
59
+ updatedAt: /* @__PURE__ */ new Date(),
60
+ ...options
61
+ });
62
+ var createFile = (name, content, parentId = null, mimeType, url) => ({
63
+ id: generateUUID(),
64
+ name,
65
+ type: "file",
66
+ content,
67
+ mimeType,
68
+ parentId,
69
+ createdAt: /* @__PURE__ */ new Date(),
70
+ updatedAt: /* @__PURE__ */ new Date(),
71
+ ...url !== void 0 ? { url } : {}
72
+ });
73
+
74
+ // src/infrastructure/adapters/WindowManagerAdapter.ts
75
+ var ALWAYS_ON_TOP_OFFSET = 1e4;
76
+ var WindowManagerAdapter = class {
77
+ windows = /* @__PURE__ */ new Map();
78
+ nextZIndex = 1;
79
+ assignZIndex(alwaysOnTop) {
80
+ const raw = this.nextZIndex++;
81
+ return alwaysOnTop ? ALWAYS_ON_TOP_OFFSET + raw : raw;
82
+ }
83
+ reset() {
84
+ this.windows.clear();
85
+ this.nextZIndex = 1;
86
+ }
87
+ loadWindows(windows) {
88
+ this.windows.clear();
89
+ let maxZIndex = 0;
90
+ for (const w of windows) {
91
+ this.windows.set(w.id, w);
92
+ const raw = w.alwaysOnTop ?? false ? w.zIndex - ALWAYS_ON_TOP_OFFSET : w.zIndex;
93
+ if (raw > maxZIndex) maxZIndex = raw;
94
+ }
95
+ this.nextZIndex = maxZIndex + 1;
96
+ }
97
+ getAll() {
98
+ return Array.from(this.windows.values());
99
+ }
100
+ getById(id) {
101
+ return this.windows.get(id);
102
+ }
103
+ open(input) {
104
+ const window2 = createWindow(input);
105
+ window2.zIndex = this.assignZIndex(window2.alwaysOnTop ?? false);
106
+ this.windows.set(window2.id, window2);
107
+ return window2;
108
+ }
109
+ close(id) {
110
+ this.windows.delete(id);
111
+ }
112
+ minimize(id) {
113
+ this.updateWindow(id, { state: "minimized" });
114
+ }
115
+ maximize(id) {
116
+ this.updateWindow(id, { state: "maximized" });
117
+ }
118
+ restore(id) {
119
+ this.updateWindow(id, { state: "normal" });
120
+ }
121
+ focus(id) {
122
+ const window2 = this.windows.get(id);
123
+ if (!window2) return;
124
+ const newZIndex = this.assignZIndex(window2.alwaysOnTop ?? false);
125
+ this.updateWindow(id, { zIndex: newZIndex });
126
+ }
127
+ move(id, x, y) {
128
+ this.updateWindow(id, { x, y });
129
+ }
130
+ resize(id, width, height) {
131
+ this.updateWindow(id, { width, height });
132
+ }
133
+ updateWindow(id, patch) {
134
+ const window2 = this.windows.get(id);
135
+ if (!window2) return;
136
+ this.windows.set(id, { ...window2, ...patch });
137
+ }
138
+ };
139
+
140
+ // src/utils/getMimeTypeFromExtension.ts
141
+ var EXTENSION_MAP = {
142
+ ".txt": "text/plain",
143
+ ".md": "text/markdown",
144
+ ".markdown": "text/markdown",
145
+ ".pdf": "application/pdf",
146
+ ".jpg": "image/jpeg",
147
+ ".jpeg": "image/jpeg",
148
+ ".png": "image/png",
149
+ ".gif": "image/gif",
150
+ ".svg": "image/svg+xml",
151
+ ".webp": "image/webp",
152
+ ".bmp": "image/bmp",
153
+ ".html": "text/html",
154
+ ".htm": "text/html",
155
+ ".css": "text/css",
156
+ ".js": "text/javascript",
157
+ ".json": "application/json",
158
+ ".xml": "text/xml",
159
+ ".jsdos": "application/jsdos",
160
+ ".zip": "application/zip"
161
+ };
162
+ function getMimeTypeFromExtension(filename) {
163
+ const lastDot = filename.lastIndexOf(".");
164
+ if (lastDot === -1 || lastDot === filename.length - 1) return void 0;
165
+ const ext = filename.slice(lastDot).toLowerCase();
166
+ return EXTENSION_MAP[ext];
167
+ }
168
+
169
+ // src/utils/hashBlob.ts
170
+ function djb2Hash(buffer) {
171
+ let hash = 5381;
172
+ for (let i = 0; i < buffer.length; i++) {
173
+ hash = (hash << 5) + hash ^ buffer[i];
174
+ hash = hash >>> 0;
175
+ }
176
+ return hash.toString(16).padStart(8, "0") + buffer.length.toString(16).padStart(8, "0");
177
+ }
178
+ async function hashBlob(blob) {
179
+ const buffer = await blob.arrayBuffer();
180
+ const bytes = new Uint8Array(buffer);
181
+ if (crypto?.subtle) {
182
+ const hashBuffer = await crypto.subtle.digest("SHA-256", bytes);
183
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
184
+ return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
185
+ }
186
+ console.warn("[hashBlob] crypto.subtle unavailable (non-secure context), using fallback hash");
187
+ return djb2Hash(bytes);
188
+ }
189
+
190
+ // src/infrastructure/adapters/IndexedDBFileSystem.ts
191
+ var DB_NAME = "fde-desktop-fs";
192
+ var NODES_STORE = "nodes";
193
+ var BLOBS_STORE = "blobs";
194
+ var METADATA_STORE = "metadata";
195
+ var DB_VERSION = 2;
196
+ var IndexedDBFileSystem = class {
197
+ db = null;
198
+ nodes = /* @__PURE__ */ new Map();
199
+ ready = false;
200
+ initPromise = null;
201
+ persistQueue = [];
202
+ persistScheduled = false;
203
+ async initialize() {
204
+ if (this.initPromise) return this.initPromise;
205
+ this.initPromise = this.doInitialize();
206
+ return this.initPromise;
207
+ }
208
+ async doInitialize() {
209
+ await this.openDatabase();
210
+ await this.loadNodes();
211
+ await this.migrateFromLocalStorage();
212
+ this.ready = true;
213
+ }
214
+ openDatabase() {
215
+ return new Promise((resolve, reject) => {
216
+ const request = indexedDB.open(DB_NAME, DB_VERSION);
217
+ request.onerror = () => reject(request.error);
218
+ request.onsuccess = () => {
219
+ this.db = request.result;
220
+ resolve();
221
+ };
222
+ request.onupgradeneeded = () => {
223
+ const db = request.result;
224
+ if (!db.objectStoreNames.contains(NODES_STORE)) {
225
+ db.createObjectStore(NODES_STORE);
226
+ }
227
+ if (!db.objectStoreNames.contains(BLOBS_STORE)) {
228
+ db.createObjectStore(BLOBS_STORE);
229
+ }
230
+ if (!db.objectStoreNames.contains(METADATA_STORE)) {
231
+ db.createObjectStore(METADATA_STORE);
232
+ }
233
+ };
234
+ });
235
+ }
236
+ async loadNodes() {
237
+ if (!this.db) return;
238
+ return new Promise((resolve, reject) => {
239
+ const tx = this.db.transaction(NODES_STORE, "readonly");
240
+ const store = tx.objectStore(NODES_STORE);
241
+ const request = store.getAll();
242
+ request.onsuccess = () => {
243
+ const keys = tx.objectStore(NODES_STORE).getAllKeys();
244
+ keys.onsuccess = () => {
245
+ const nodeEntries = request.result;
246
+ const keyEntries = keys.result;
247
+ for (let i = 0; i < nodeEntries.length; i++) {
248
+ const key = String(keyEntries[i]);
249
+ this.nodes.set(key, nodeEntries[i]);
250
+ }
251
+ resolve();
252
+ };
253
+ keys.onerror = () => reject(keys.error);
254
+ };
255
+ request.onerror = () => reject(request.error);
256
+ });
257
+ }
258
+ async migrateFromLocalStorage() {
259
+ const legacyKey = "fran-desktop:filesystem";
260
+ const legacyData = localStorage.getItem(legacyKey);
261
+ if (!legacyData) return;
262
+ if (this.nodes.size === 0) {
263
+ try {
264
+ const entries = JSON.parse(legacyData);
265
+ for (const [id, node] of entries) {
266
+ this.nodes.set(id, node);
267
+ }
268
+ await this.persistAllNodes();
269
+ localStorage.removeItem(legacyKey);
270
+ } catch {
271
+ }
272
+ }
273
+ }
274
+ isReady() {
275
+ return this.ready;
276
+ }
277
+ async reinitialize() {
278
+ if (!this.ready) {
279
+ await this.initialize();
280
+ }
281
+ }
282
+ persistAllNodes() {
283
+ if (!this.db) return Promise.resolve();
284
+ return new Promise((resolve, reject) => {
285
+ const tx = this.db.transaction(NODES_STORE, "readwrite");
286
+ const store = tx.objectStore(NODES_STORE);
287
+ for (const [id, node] of this.nodes.entries()) {
288
+ store.put(node, id);
289
+ }
290
+ tx.oncomplete = () => resolve();
291
+ tx.onerror = () => reject(tx.error);
292
+ });
293
+ }
294
+ schedulePersist() {
295
+ if (this.persistScheduled) return;
296
+ this.persistScheduled = true;
297
+ queueMicrotask(() => {
298
+ this.persistScheduled = false;
299
+ this.flushPersistQueue();
300
+ });
301
+ }
302
+ flushPersistQueue() {
303
+ if (!this.db || this.persistQueue.length === 0) return;
304
+ const items = [...this.persistQueue];
305
+ this.persistQueue = [];
306
+ const tx = this.db.transaction(NODES_STORE, "readwrite");
307
+ const store = tx.objectStore(NODES_STORE);
308
+ tx.onerror = () => {
309
+ console.error("IndexedDB write failed", tx.error);
310
+ };
311
+ for (const item of items) {
312
+ if (item.type === "set") {
313
+ store.put(item.value, item.key);
314
+ } else if (item.type === "delete") {
315
+ store.delete(item.key);
316
+ }
317
+ }
318
+ }
319
+ persistNode(id, node) {
320
+ this.persistQueue.push({ type: "set", key: id, value: node });
321
+ this.schedulePersist();
322
+ }
323
+ deleteNodeFromPersist(id) {
324
+ this.persistQueue.push({ type: "delete", key: id });
325
+ this.schedulePersist();
326
+ }
327
+ async saveBlob(blob) {
328
+ if (!this.db) await this.initialize();
329
+ const hash = await hashBlob(blob);
330
+ const existing = await this.getBlob(`idb://${hash}`);
331
+ if (existing) return `idb://${hash}`;
332
+ return new Promise((resolve, reject) => {
333
+ const tx = this.db.transaction(BLOBS_STORE, "readwrite");
334
+ const store = tx.objectStore(BLOBS_STORE);
335
+ const request = store.put(blob, hash);
336
+ request.onsuccess = () => resolve(`idb://${hash}`);
337
+ request.onerror = () => reject(request.error);
338
+ });
339
+ }
340
+ async getBlob(url) {
341
+ if (!this.db) await this.initialize();
342
+ const hash = url.startsWith("idb://") ? url.slice("idb://".length) : url;
343
+ return new Promise((resolve, reject) => {
344
+ const tx = this.db.transaction(BLOBS_STORE, "readonly");
345
+ const store = tx.objectStore(BLOBS_STORE);
346
+ const request = store.get(hash);
347
+ request.onsuccess = () => resolve(request.result ?? null);
348
+ request.onerror = () => reject(request.error);
349
+ });
350
+ }
351
+ async toBlobUrl(url) {
352
+ if (!url.startsWith("idb://")) return null;
353
+ const blob = await this.getBlob(url);
354
+ if (!blob) return null;
355
+ return URL.createObjectURL(blob);
356
+ }
357
+ async clearBlobs() {
358
+ if (!this.db) return;
359
+ return new Promise((resolve, reject) => {
360
+ const tx = this.db.transaction(BLOBS_STORE, "readwrite");
361
+ const store = tx.objectStore(BLOBS_STORE);
362
+ const request = store.clear();
363
+ request.onsuccess = () => resolve();
364
+ request.onerror = () => reject(request.error);
365
+ });
366
+ }
367
+ async readFile(_path) {
368
+ return "";
369
+ }
370
+ async writeFile(_path, _content) {
371
+ }
372
+ getNode(id) {
373
+ return this.nodes.get(id);
374
+ }
375
+ getChildren(folderId) {
376
+ const folder = this.nodes.get(folderId);
377
+ if (!folder || folder.type !== "folder") return [];
378
+ return folder.children.map((childId) => this.nodes.get(childId)).filter((n) => n !== void 0);
379
+ }
380
+ getRootNodes() {
381
+ return Array.from(this.nodes.values()).filter((n) => n.parentId === null);
382
+ }
383
+ getAllNodes() {
384
+ return Array.from(this.nodes.values());
385
+ }
386
+ isEmpty() {
387
+ return this.nodes.size === 0;
388
+ }
389
+ async createFile(name, content, parentId, url) {
390
+ const mimeType = getMimeTypeFromExtension(name);
391
+ const file = createFile(name, content, parentId, mimeType, url);
392
+ this.nodes.set(file.id, file);
393
+ if (parentId) this.addChildToFolder(parentId, file.id);
394
+ this.persistNode(file.id, file);
395
+ return file;
396
+ }
397
+ async createFolder(name, parentId, iconName, iconColor) {
398
+ const folder = createFolder(name, parentId, { iconName, iconColor });
399
+ this.nodes.set(folder.id, folder);
400
+ if (parentId) this.addChildToFolder(parentId, folder.id);
401
+ this.persistNode(folder.id, folder);
402
+ return folder;
403
+ }
404
+ async updateFile(id, content) {
405
+ const node = this.nodes.get(id);
406
+ if (!node || node.type !== "file") throw new Error(`File not found: ${id}`);
407
+ const updated = { ...node, content, updatedAt: /* @__PURE__ */ new Date() };
408
+ this.nodes.set(id, updated);
409
+ this.persistNode(id, updated);
410
+ return updated;
411
+ }
412
+ async move(id, newParentId) {
413
+ const node = this.nodes.get(id);
414
+ if (!node) throw new Error(`Node not found: ${id}`);
415
+ if (newParentId === id) throw new Error("Cannot move a folder into itself");
416
+ if (newParentId !== null) {
417
+ let currentParentId = newParentId;
418
+ while (currentParentId !== null) {
419
+ if (currentParentId === id)
420
+ throw new Error("Cannot move a folder into one of its descendants");
421
+ const parentNode = this.nodes.get(currentParentId);
422
+ currentParentId = parentNode?.parentId ?? null;
423
+ }
424
+ }
425
+ if (node.parentId) this.removeChildFromParent(node);
426
+ const updated = { ...node, parentId: newParentId };
427
+ this.nodes.set(id, updated);
428
+ if (newParentId) this.addChildToFolder(newParentId, id);
429
+ this.persistNode(id, updated);
430
+ return updated;
431
+ }
432
+ async delete(id) {
433
+ const node = this.nodes.get(id);
434
+ if (!node) return;
435
+ if (node.type === "folder") this.deleteRecursive(node);
436
+ this.removeChildFromParent(node);
437
+ this.nodes.delete(id);
438
+ this.deleteNodeFromPersist(id);
439
+ }
440
+ async getManifestSha() {
441
+ if (!this.db) await this.initialize();
442
+ return new Promise((resolve, reject) => {
443
+ const tx = this.db.transaction(METADATA_STORE, "readonly");
444
+ const store = tx.objectStore(METADATA_STORE);
445
+ const request = store.get("manifestSha");
446
+ request.onsuccess = () => resolve(request.result ?? null);
447
+ request.onerror = () => reject(request.error);
448
+ });
449
+ }
450
+ async saveManifestSha(sha) {
451
+ if (!this.db) await this.initialize();
452
+ return new Promise((resolve, reject) => {
453
+ const tx = this.db.transaction(METADATA_STORE, "readwrite");
454
+ const store = tx.objectStore(METADATA_STORE);
455
+ const request = store.put(sha, "manifestSha");
456
+ request.onsuccess = () => resolve();
457
+ request.onerror = () => reject(request.error);
458
+ });
459
+ }
460
+ async clearAll() {
461
+ this.nodes.clear();
462
+ this.ready = false;
463
+ this.initPromise = null;
464
+ if (this.db) {
465
+ await Promise.all([
466
+ new Promise((resolve, reject) => {
467
+ const tx = this.db.transaction(NODES_STORE, "readwrite");
468
+ tx.objectStore(NODES_STORE).clear();
469
+ tx.oncomplete = () => resolve();
470
+ tx.onerror = () => reject(tx.error);
471
+ }),
472
+ new Promise((resolve, reject) => {
473
+ const tx = this.db.transaction(BLOBS_STORE, "readwrite");
474
+ tx.objectStore(BLOBS_STORE).clear();
475
+ tx.oncomplete = () => resolve();
476
+ tx.onerror = () => reject(tx.error);
477
+ }),
478
+ new Promise((resolve, reject) => {
479
+ const tx = this.db.transaction(METADATA_STORE, "readwrite");
480
+ tx.objectStore(METADATA_STORE).clear();
481
+ tx.oncomplete = () => resolve();
482
+ tx.onerror = () => reject(tx.error);
483
+ })
484
+ ]);
485
+ this.db.close();
486
+ this.db = null;
487
+ }
488
+ }
489
+ async seed(manifest) {
490
+ const folderMap = this.buildFolderMapFromManifest(manifest);
491
+ for (const f of manifest.files) {
492
+ const parent = folderMap.get(f.folder);
493
+ const file = createFile(f.name, "", parent?.id ?? null, f.mimeType, f.url);
494
+ this.nodes.set(file.id, file);
495
+ if (parent) {
496
+ const updated = { ...parent, children: [...parent.children, file.id] };
497
+ this.nodes.set(parent.id, updated);
498
+ folderMap.set(f.folder, updated);
499
+ }
500
+ }
501
+ await this.persistAllNodes();
502
+ }
503
+ async mergeSeed(manifest) {
504
+ const folderMap = this.buildFolderMapFromManifest(manifest, true);
505
+ for (const f of manifest.files) {
506
+ const parent = folderMap.get(f.folder);
507
+ if (!parent) continue;
508
+ const existingNode = this.getChildren(parent.id).find(
509
+ (n) => n.type === "file" && n.name === f.name
510
+ );
511
+ if (existingNode) {
512
+ if (existingNode.url !== f.url) {
513
+ const updated = { ...existingNode, url: f.url };
514
+ this.nodes.set(existingNode.id, updated);
515
+ }
516
+ continue;
517
+ }
518
+ const file = createFile(f.name, "", parent.id, f.mimeType, f.url);
519
+ this.nodes.set(file.id, file);
520
+ const updatedParent = { ...parent, children: [...parent.children, file.id] };
521
+ this.nodes.set(parent.id, updatedParent);
522
+ folderMap.set(f.folder, updatedParent);
523
+ }
524
+ await this.persistAllNodes();
525
+ }
526
+ buildFolderMapFromManifest(manifest, mergeMode = false) {
527
+ const folderMap = /* @__PURE__ */ new Map();
528
+ const allPaths = /* @__PURE__ */ new Set([...manifest.folders, ...manifest.files.map((f) => f.folder)]);
529
+ const sortedPaths = [...allPaths].sort((a, b) => a.split("/").length - b.split("/").length);
530
+ for (const path of sortedPaths) {
531
+ const parts = path.split("/");
532
+ const name = parts[parts.length - 1];
533
+ const parentPath = parts.length > 1 ? parts.slice(0, -1).join("/") : null;
534
+ const parentFolder = parentPath ? folderMap.get(parentPath) : null;
535
+ const parentId = parentFolder?.id ?? null;
536
+ if (mergeMode) {
537
+ const searchIn = parentId ? this.getChildren(parentId) : this.getRootNodes();
538
+ const existing = searchIn.find((n) => n.type === "folder" && n.name === name);
539
+ if (existing) {
540
+ folderMap.set(path, existing);
541
+ continue;
542
+ }
543
+ }
544
+ const folder = createFolder(name, parentId);
545
+ this.nodes.set(folder.id, folder);
546
+ folderMap.set(path, folder);
547
+ if (parentFolder) {
548
+ const updatedParent = {
549
+ ...parentFolder,
550
+ children: [...parentFolder.children, folder.id]
551
+ };
552
+ this.nodes.set(parentFolder.id, updatedParent);
553
+ folderMap.set(parentPath, updatedParent);
554
+ }
555
+ }
556
+ return folderMap;
557
+ }
558
+ addChildToFolder(folderId, childId) {
559
+ const folder = this.nodes.get(folderId);
560
+ if (!folder || folder.type !== "folder") return;
561
+ const updated = { ...folder, children: [...folder.children, childId] };
562
+ this.nodes.set(folderId, updated);
563
+ this.persistNode(folderId, updated);
564
+ }
565
+ removeChildFromParent(node) {
566
+ if (!node.parentId) return;
567
+ const parent = this.nodes.get(node.parentId);
568
+ if (!parent || parent.type !== "folder") return;
569
+ const updated = {
570
+ ...parent,
571
+ children: parent.children.filter((id) => id !== node.id)
572
+ };
573
+ this.nodes.set(node.parentId, updated);
574
+ this.persistNode(node.parentId, updated);
575
+ }
576
+ deleteRecursive(folder) {
577
+ for (const childId of folder.children) {
578
+ const child = this.nodes.get(childId);
579
+ if (!child) continue;
580
+ if (child.type === "folder") this.deleteRecursive(child);
581
+ this.nodes.delete(childId);
582
+ this.deleteNodeFromPersist(childId);
583
+ }
584
+ }
585
+ };
586
+
587
+ // src/infrastructure/adapters/DockerFileSystemAdapter.ts
588
+ var API_BASE = "/api/fs";
589
+ var FETCH_TIMEOUT = 6e4;
590
+ async function fetchWithTimeout(url, options = {}, timeout = FETCH_TIMEOUT) {
591
+ const controller = new AbortController();
592
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
593
+ try {
594
+ return await fetch(url, { ...options, signal: controller.signal });
595
+ } finally {
596
+ clearTimeout(timeoutId);
597
+ }
598
+ }
599
+ var apiBaseUrl = API_BASE;
600
+ function setApiBaseUrl(url) {
601
+ apiBaseUrl = url;
602
+ }
603
+ var DockerFileSystemAdapter = class {
604
+ nodes = /* @__PURE__ */ new Map();
605
+ pathToId = /* @__PURE__ */ new Map();
606
+ ready = false;
607
+ rootFolderId;
608
+ initPromise = null;
609
+ constructor() {
610
+ this.rootFolderId = generateUUID();
611
+ this.pathToId.set("/", this.rootFolderId);
612
+ }
613
+ async initialize() {
614
+ if (this.initPromise) return this.initPromise;
615
+ this.initPromise = this.doInitialize();
616
+ return this.initPromise;
617
+ }
618
+ async doInitialize() {
619
+ try {
620
+ const response = await fetch(`${apiBaseUrl}?action=readdir&path=/`);
621
+ if (response.ok) {
622
+ const entries = await response.json();
623
+ await this.syncFromEntries(entries, null);
624
+ }
625
+ } catch (error) {
626
+ console.warn("[DockerFS] Failed to initialize from server:", error);
627
+ }
628
+ this.ready = true;
629
+ }
630
+ async reinitialize() {
631
+ this.ready = false;
632
+ this.nodes.clear();
633
+ this.pathToId.clear();
634
+ this.rootFolderId = generateUUID();
635
+ this.pathToId.set("/", this.rootFolderId);
636
+ this.initPromise = null;
637
+ await this.initialize();
638
+ }
639
+ isReady() {
640
+ return this.ready;
641
+ }
642
+ isEmpty() {
643
+ return this.nodes.size === 0;
644
+ }
645
+ getNode(id) {
646
+ return this.nodes.get(id);
647
+ }
648
+ getChildren(folderId) {
649
+ const children = [];
650
+ for (const node of this.nodes.values()) {
651
+ if (node.parentId === folderId) {
652
+ children.push(node);
653
+ }
654
+ }
655
+ return children;
656
+ }
657
+ getRootNodes() {
658
+ return Array.from(this.nodes.values()).filter((n) => n.parentId === null);
659
+ }
660
+ getAllNodes() {
661
+ return Array.from(this.nodes.values());
662
+ }
663
+ async createFile(name, content, parentId, url) {
664
+ const id = generateUUID();
665
+ const parentPath = parentId ? this.getPathFromId(parentId) : "/";
666
+ const filePath = parentPath ? `${parentPath}/${name}`.replace("//", "/") : `/${name}`;
667
+ const fileUrl = url ?? `/api/fs?action=file&path=${encodeURIComponent(filePath)}`;
668
+ const file = createFile(name, content, parentId, this.getMimeType(name), fileUrl);
669
+ const fileWithId = { ...file, id };
670
+ this.nodes.set(id, fileWithId);
671
+ this.pathToId.set(filePath, id);
672
+ try {
673
+ await this.writeFileToServer(filePath, content);
674
+ } catch (error) {
675
+ this.nodes.delete(id);
676
+ this.pathToId.delete(filePath);
677
+ throw error;
678
+ }
679
+ return fileWithId;
680
+ }
681
+ async createFolder(name, parentId, iconName, iconColor) {
682
+ const id = generateUUID();
683
+ const parentPath = parentId ? this.getPathFromId(parentId) : "/";
684
+ const folderPath = parentPath ? `${parentPath}/${name}`.replace("//", "/") : `/${name}`;
685
+ const options = {};
686
+ if (iconName) options.iconName = iconName;
687
+ if (iconColor) options.iconColor = iconColor;
688
+ const folder = createFolder(name, parentId, options);
689
+ const folderWithId = { ...folder, id };
690
+ this.nodes.set(id, folderWithId);
691
+ this.pathToId.set(folderPath, id);
692
+ try {
693
+ await this.createDirOnServer(folderPath, options);
694
+ } catch (error) {
695
+ this.nodes.delete(id);
696
+ this.pathToId.delete(folderPath);
697
+ throw error;
698
+ }
699
+ return folderWithId;
700
+ }
701
+ async updateFile(id, content) {
702
+ const node = this.nodes.get(id);
703
+ if (!node || node.type !== "file") {
704
+ throw new Error(`File not found: ${id}`);
705
+ }
706
+ const file = node;
707
+ const path = this.getPathFromId(id);
708
+ const updatedFile = { ...file, content, updatedAt: /* @__PURE__ */ new Date() };
709
+ this.nodes.set(id, updatedFile);
710
+ if (path) {
711
+ this.writeFileToServer(path, content).catch((error) => {
712
+ console.warn(`[DockerFS] Failed to update file on server:`, error);
713
+ });
714
+ }
715
+ return updatedFile;
716
+ }
717
+ async move(id, newParentId) {
718
+ const node = this.nodes.get(id);
719
+ if (!node) throw new Error(`Node not found: ${id}`);
720
+ if (newParentId === id) throw new Error("Cannot move a folder into itself");
721
+ if (newParentId !== null) {
722
+ let cur = newParentId;
723
+ while (cur !== null) {
724
+ if (cur === id) throw new Error("Cannot move a folder into one of its descendants");
725
+ cur = this.nodes.get(cur)?.parentId ?? null;
726
+ }
727
+ }
728
+ const oldPath = this.getPathFromId(id);
729
+ const newParentPath = newParentId ? this.getPathFromId(newParentId) : "/";
730
+ const newPath = newParentPath ? `${newParentPath}/${node.name}`.replace("//", "/") : `/${node.name}`;
731
+ if (!oldPath) throw new Error(`Cannot determine path for node ${id}`);
732
+ if (!newParentPath && newParentId !== null)
733
+ throw new Error(`Cannot determine parent path for ${newParentId}`);
734
+ const response = await fetchWithTimeout(
735
+ `${apiBaseUrl}?action=move&oldPath=${encodeURIComponent(oldPath)}&newPath=${encodeURIComponent(newPath)}`
736
+ );
737
+ if (!response.ok) {
738
+ const errorData = await response.json().catch(() => ({}));
739
+ throw new Error(errorData.error || `HTTP ${response.status}`);
740
+ }
741
+ const movedNode = { ...node, parentId: newParentId };
742
+ this.nodes.set(id, movedNode);
743
+ this.pathToId.delete(oldPath);
744
+ this.pathToId.set(newPath, id);
745
+ return movedNode;
746
+ }
747
+ async delete(id) {
748
+ const path = this.getPathFromId(id);
749
+ this.deleteRecursive(id);
750
+ if (path) {
751
+ try {
752
+ await this.deleteFromServer(path);
753
+ } catch (error) {
754
+ console.warn(`[DockerFS] Failed to delete ${path} on server:`, error);
755
+ throw error;
756
+ }
757
+ }
758
+ }
759
+ async readFile(path) {
760
+ try {
761
+ const response = await fetch(`${apiBaseUrl}?action=read&path=${encodeURIComponent(path)}`);
762
+ if (response.ok) {
763
+ const data = await response.json();
764
+ return data.content;
765
+ }
766
+ } catch (error) {
767
+ console.warn(`[DockerFS] Failed to read file ${path}:`, error);
768
+ }
769
+ return "";
770
+ }
771
+ async writeFile(path, content) {
772
+ await this.writeFileToServer(path, content);
773
+ }
774
+ async saveBlob(blob) {
775
+ const hash = await hashBlob(blob);
776
+ const ext = blob.type.split("/")[1] || "bin";
777
+ const path = `/.blobs/${hash}.${ext}`;
778
+ try {
779
+ await fetch(`${apiBaseUrl}?action=writeBinary&path=${encodeURIComponent(path)}`, {
780
+ method: "POST",
781
+ body: blob
782
+ });
783
+ } catch (error) {
784
+ console.warn(`[DockerFS] Failed to save blob ${path}:`, error);
785
+ }
786
+ return `/api/fs?action=file&path=${encodeURIComponent(path)}`;
787
+ }
788
+ async getBlob(url) {
789
+ if (!url.startsWith("/api/fs")) return null;
790
+ try {
791
+ const response = await fetch(url);
792
+ if (!response.ok) return null;
793
+ return await response.blob();
794
+ } catch {
795
+ return null;
796
+ }
797
+ }
798
+ async toBlobUrl(url) {
799
+ if (!url.startsWith("/api/fs")) return null;
800
+ try {
801
+ const response = await fetch(url);
802
+ if (!response.ok) return null;
803
+ const blob = await response.blob();
804
+ return URL.createObjectURL(blob);
805
+ } catch {
806
+ return null;
807
+ }
808
+ }
809
+ async getManifestSha() {
810
+ return null;
811
+ }
812
+ async saveManifestSha(_sha) {
813
+ }
814
+ seed(_manifest) {
815
+ return Promise.resolve();
816
+ }
817
+ mergeSeed(_manifest) {
818
+ return Promise.resolve();
819
+ }
820
+ async clearAll() {
821
+ this.nodes.clear();
822
+ this.pathToId.clear();
823
+ this.ready = false;
824
+ this.initPromise = null;
825
+ this.rootFolderId = generateUUID();
826
+ this.pathToId.set("/", this.rootFolderId);
827
+ }
828
+ getMimeType(filename) {
829
+ return getMimeTypeFromExtension(filename) || "application/octet-stream";
830
+ }
831
+ getPathFromId(id) {
832
+ if (!id) return null;
833
+ for (const [path, nodeId] of this.pathToId.entries()) {
834
+ if (nodeId === id) return path;
835
+ }
836
+ const node = this.nodes.get(id);
837
+ if (!node) return null;
838
+ if (!node.parentId) return `/${node.name}`;
839
+ const parentPath = this.getPathFromId(node.parentId);
840
+ return parentPath ? `${parentPath}/${node.name}` : null;
841
+ }
842
+ deleteRecursive(id) {
843
+ for (const child of this.getChildren(id)) {
844
+ this.deleteRecursive(child.id);
845
+ }
846
+ this.nodes.delete(id);
847
+ for (const [path, nodeId] of this.pathToId.entries()) {
848
+ if (nodeId === id) this.pathToId.delete(path);
849
+ }
850
+ }
851
+ async syncFromEntries(entries, parentId) {
852
+ for (const entry of entries) {
853
+ const id = generateUUID();
854
+ if (entry.type === "directory") {
855
+ const folder = createFolder(entry.name, parentId);
856
+ this.nodes.set(id, { ...folder, id });
857
+ this.pathToId.set(entry.path, id);
858
+ const children = await this.fetchReaddir(entry.path);
859
+ await this.syncFromEntries(children, id);
860
+ } else {
861
+ const url = `/api/fs?action=file&path=${encodeURIComponent(entry.path)}`;
862
+ const file = createFile(entry.name, "", parentId, this.getMimeType(entry.name), url);
863
+ this.nodes.set(id, { ...file, id });
864
+ this.pathToId.set(entry.path, id);
865
+ }
866
+ }
867
+ }
868
+ async fetchReaddir(path) {
869
+ try {
870
+ const response = await fetch(`${apiBaseUrl}?action=readdir&path=${encodeURIComponent(path)}`);
871
+ if (response.ok) return await response.json();
872
+ } catch (error) {
873
+ console.warn(`[DockerFS] Failed to readdir ${path}:`, error);
874
+ }
875
+ return [];
876
+ }
877
+ async writeFileToServer(path, content) {
878
+ const response = await fetch(`${apiBaseUrl}`, {
879
+ method: "POST",
880
+ headers: { "Content-Type": "application/json" },
881
+ body: JSON.stringify({ path, content })
882
+ });
883
+ if (!response.ok) {
884
+ throw new Error(`Failed to write file ${path}: HTTP ${response.status}`);
885
+ }
886
+ }
887
+ async createDirOnServer(path, options) {
888
+ const response = await fetch(`${apiBaseUrl}`, {
889
+ method: "PUT",
890
+ headers: { "Content-Type": "application/json" },
891
+ body: JSON.stringify({ path, ...options })
892
+ });
893
+ if (!response.ok) {
894
+ throw new Error(`Failed to create directory ${path}: HTTP ${response.status}`);
895
+ }
896
+ }
897
+ async deleteFromServer(path) {
898
+ try {
899
+ await fetch(`${apiBaseUrl}?path=${encodeURIComponent(path)}`, { method: "DELETE" });
900
+ } catch (error) {
901
+ console.warn(`[DockerFS] Failed to delete ${path}:`, error);
902
+ }
903
+ }
904
+ };
905
+
906
+ // src/constants/launcherFolders.ts
907
+ var CUSTOM_APPS_FOLDER_ID = "launcher-folder-custom-apps";
908
+ var PREDEFINED_LAUNCHER_FOLDERS = [
909
+ {
910
+ id: "launcher-folder-social-links",
911
+ name: "Social Links",
912
+ fcIcon: "VscAccount",
913
+ appIds: ["github", "linkedin"],
914
+ isExpanded: false,
915
+ isPredefined: true,
916
+ order: 0
917
+ },
918
+ {
919
+ id: "launcher-folder-office",
920
+ name: "Office and media",
921
+ fcIcon: "VscLibrary",
922
+ appIds: ["notepad", "pdf", "image-viewer"],
923
+ isExpanded: false,
924
+ isPredefined: true,
925
+ order: 1
926
+ },
927
+ {
928
+ id: "launcher-folder-programming",
929
+ name: "Programming",
930
+ fcIcon: "VscGitPullRequest",
931
+ appIds: ["terminal", "code-server", "device-info", "storybook"],
932
+ isExpanded: false,
933
+ isPredefined: true,
934
+ order: 2
935
+ },
936
+ {
937
+ id: "launcher-folder-games",
938
+ name: "Games",
939
+ fcIcon: "VscGame",
940
+ appIds: ["dos-emulator", "doom"],
941
+ isExpanded: false,
942
+ isPredefined: true,
943
+ order: 3
944
+ },
945
+ {
946
+ id: CUSTOM_APPS_FOLDER_ID,
947
+ name: "Custom Apps",
948
+ fcIcon: "VscExtensions",
949
+ appIds: [],
950
+ isExpanded: false,
951
+ isPredefined: true,
952
+ order: 4
953
+ }
954
+ ];
955
+ var DEFAULT_LAUNCHER_FOLDERS = [...PREDEFINED_LAUNCHER_FOLDERS];
956
+
957
+ // src/constants/customApps.ts
958
+ var cachedManifest = null;
959
+ var manifestFetchPromise = null;
960
+ var lastManifestFetch = 0;
961
+ var MANIFEST_CACHE_TTL = 5e3;
962
+ var APPS_MANIFEST_URL = "/dist-apps/apps-manifest.json";
963
+ async function fetchAppManifest(forceRefresh = false) {
964
+ const now = Date.now();
965
+ if (!forceRefresh && cachedManifest && now - lastManifestFetch < MANIFEST_CACHE_TTL) {
966
+ return cachedManifest;
967
+ }
968
+ if (manifestFetchPromise) {
969
+ return manifestFetchPromise;
970
+ }
971
+ const url = `${APPS_MANIFEST_URL}?t=${now}`;
972
+ manifestFetchPromise = fetch(url).then(async (response) => {
973
+ if (!response.ok) {
974
+ if (response.status === 404) {
975
+ console.warn("[CustomApps] No apps manifest found at", APPS_MANIFEST_URL);
976
+ return null;
977
+ }
978
+ throw new Error(`Failed to fetch manifest: ${response.status}`);
979
+ }
980
+ return response.json();
981
+ }).then((data) => {
982
+ cachedManifest = data;
983
+ lastManifestFetch = Date.now();
984
+ manifestFetchPromise = null;
985
+ console.log(`[CustomApps] Loaded manifest with ${data.apps.length} apps`);
986
+ return data;
987
+ }).catch((error) => {
988
+ console.error("[CustomApps] Error fetching manifest:", error);
989
+ manifestFetchPromise = null;
990
+ return null;
991
+ });
992
+ return manifestFetchPromise;
993
+ }
994
+ function getCachedManifest() {
995
+ return cachedManifest;
996
+ }
997
+ function clearManifestCache() {
998
+ cachedManifest = null;
999
+ lastManifestFetch = 0;
1000
+ }
1001
+ function updateManifestCache(manifest) {
1002
+ if (!cachedManifest) {
1003
+ cachedManifest = { version: "1.0.0", generated: (/* @__PURE__ */ new Date()).toISOString(), apps: [manifest] };
1004
+ return;
1005
+ }
1006
+ const exists = cachedManifest.apps.some((a) => a.id === manifest.id);
1007
+ if (exists) {
1008
+ cachedManifest = {
1009
+ ...cachedManifest,
1010
+ apps: cachedManifest.apps.map((a) => a.id === manifest.id ? manifest : a)
1011
+ };
1012
+ } else {
1013
+ cachedManifest = { ...cachedManifest, apps: [...cachedManifest.apps, manifest] };
1014
+ }
1015
+ }
1016
+ function removeFromManifestCache(appId) {
1017
+ if (cachedManifest) {
1018
+ cachedManifest = {
1019
+ ...cachedManifest,
1020
+ apps: cachedManifest.apps.filter((a) => a.id !== appId)
1021
+ };
1022
+ }
1023
+ }
1024
+ async function getCustomApps() {
1025
+ const manifest = await fetchAppManifest();
1026
+ return manifest?.apps ?? [];
1027
+ }
1028
+ async function getCustomAppById(appId) {
1029
+ const apps = await getCustomApps();
1030
+ return apps.find((app) => app.id === appId);
1031
+ }
1032
+ function isCustomApp(appId) {
1033
+ if (!cachedManifest) {
1034
+ return false;
1035
+ }
1036
+ return cachedManifest.apps.some((app) => app.id === appId);
1037
+ }
1038
+ function convertToAppEntry(manifest) {
1039
+ return {
1040
+ id: manifest.id,
1041
+ name: manifest.name,
1042
+ icon: manifest.icon,
1043
+ ...manifest.iconUrl && { iconUrl: manifest.iconUrl },
1044
+ defaultWidth: manifest.window.defaultWidth,
1045
+ defaultHeight: manifest.window.defaultHeight,
1046
+ minWidth: manifest.window.minWidth !== false ? manifest.window.minWidth : void 0,
1047
+ minHeight: manifest.window.minHeight !== false ? manifest.window.minHeight : void 0,
1048
+ canMaximize: manifest.window.canMaximize,
1049
+ alwaysOnTop: manifest.window.alwaysOnTop
1050
+ };
1051
+ }
1052
+ function mergeAppsWithCustomApps(staticApps, customApps) {
1053
+ const customIds = new Set(customApps.map((app) => app.id));
1054
+ const staticAppsFiltered = staticApps.filter((app) => !customIds.has(app.id));
1055
+ const mergedApps = customApps.map((manifest) => {
1056
+ const entry = {
1057
+ id: manifest.id,
1058
+ name: manifest.name,
1059
+ icon: manifest.icon,
1060
+ defaultWidth: manifest.window.defaultWidth ?? 800,
1061
+ defaultHeight: manifest.window.defaultHeight ?? 600,
1062
+ minWidth: manifest.window.minWidth !== false ? manifest.window.minWidth : void 0,
1063
+ minHeight: manifest.window.minHeight !== false ? manifest.window.minHeight : void 0,
1064
+ canMaximize: manifest.window.canMaximize ?? true,
1065
+ alwaysOnTop: manifest.window.alwaysOnTop ?? false
1066
+ };
1067
+ if (manifest.iconUrl) {
1068
+ entry.iconUrl = manifest.iconUrl;
1069
+ }
1070
+ return entry;
1071
+ });
1072
+ return [...staticAppsFiltered, ...mergedApps];
1073
+ }
1074
+ async function syncWithServer(currentRegisteredApps) {
1075
+ clearManifestCache();
1076
+ const manifest = await fetchAppManifest(true);
1077
+ if (!manifest) {
1078
+ return { added: [], removed: [], manifest: null };
1079
+ }
1080
+ const currentIds = Object.keys(currentRegisteredApps);
1081
+ const serverIds = manifest.apps.map((a) => a.id);
1082
+ const added = manifest.apps.filter((a) => !currentIds.includes(a.id));
1083
+ const removed = currentIds.filter((id) => !serverIds.includes(id));
1084
+ return { added, removed, manifest };
1085
+ }
1086
+
1087
+ // src/utils/sortNodes.ts
1088
+ var sortNodes = (nodes) => {
1089
+ const folders = nodes.filter((n) => n.type === "folder");
1090
+ const files = nodes.filter((n) => n.type === "file");
1091
+ const byName = (a, b) => a.name.localeCompare(b.name);
1092
+ return [...folders.sort(byName), ...files.sort(byName)];
1093
+ };
1094
+ var SortOptions = {
1095
+ BY_TYPE: "byType",
1096
+ BY_NAME: "byName",
1097
+ BY_DATE: "byDate"
1098
+ };
1099
+ function getIconType(icon) {
1100
+ if (!icon.nodeId) return "app";
1101
+ return icon.appId === "files" ? "folder" : "file";
1102
+ }
1103
+ var TYPE_ORDER = { app: 0, folder: 1, file: 2 };
1104
+ function sortDesktopIcons(icons, desktopAppsOrder) {
1105
+ return [...icons].sort((a, b) => {
1106
+ const typeA = getIconType(a);
1107
+ const typeB = getIconType(b);
1108
+ if (typeA !== typeB) return TYPE_ORDER[typeA] - TYPE_ORDER[typeB];
1109
+ if (typeA === "app") {
1110
+ if (!desktopAppsOrder) return a.name.localeCompare(b.name);
1111
+ const indexA = desktopAppsOrder.indexOf(a.appId);
1112
+ const indexB = desktopAppsOrder.indexOf(b.appId);
1113
+ const posA = indexA === -1 ? Infinity : indexA;
1114
+ const posB = indexB === -1 ? Infinity : indexB;
1115
+ return posA - posB;
1116
+ }
1117
+ return a.name.localeCompare(b.name);
1118
+ });
1119
+ }
1120
+ var sortDesktopIconsByMode = (icons, mode) => {
1121
+ switch (mode) {
1122
+ case "default":
1123
+ return sortDesktopIcons(icons);
1124
+ case "name-asc":
1125
+ return [...icons].sort((a, b) => a.name.localeCompare(b.name));
1126
+ case "name-desc":
1127
+ return [...icons].sort((a, b) => b.name.localeCompare(a.name));
1128
+ case "type":
1129
+ return [...icons].sort((a, b) => {
1130
+ const typeA = getIconType(a);
1131
+ const typeB = getIconType(b);
1132
+ if (typeA !== typeB) return TYPE_ORDER[typeA] - TYPE_ORDER[typeB];
1133
+ return a.name.localeCompare(b.name);
1134
+ });
1135
+ default:
1136
+ return sortDesktopIcons(icons);
1137
+ }
1138
+ };
1139
+ var sortNodesByMode = (nodes, mode) => {
1140
+ switch (mode) {
1141
+ case "default":
1142
+ case "type":
1143
+ return sortNodes(nodes);
1144
+ case "name-asc":
1145
+ return [...nodes].sort((a, b) => a.name.localeCompare(b.name));
1146
+ case "name-desc":
1147
+ return [...nodes].sort((a, b) => b.name.localeCompare(a.name));
1148
+ default:
1149
+ return sortNodes(nodes);
1150
+ }
1151
+ };
1152
+
1153
+ // src/constants/coreApps.ts
1154
+ var DEFAULT_WINDOW_DIMENSIONS = {
1155
+ defaultWidth: 800,
1156
+ defaultHeight: 600,
1157
+ minWidth: 640,
1158
+ minHeight: 480
1159
+ };
1160
+ var FilesApp = {
1161
+ id: "files",
1162
+ name: "FilesApp",
1163
+ icon: "\u{1F4C1}",
1164
+ fcIcon: "FcOpenedFolder",
1165
+ hasContextMenu: true,
1166
+ ...DEFAULT_WINDOW_DIMENSIONS
1167
+ };
1168
+ var SettingsApp = {
1169
+ id: "settings",
1170
+ name: "Settings",
1171
+ icon: "\u2699\uFE0F",
1172
+ fcIcon: "FcSettings",
1173
+ singleWindow: true,
1174
+ ...DEFAULT_WINDOW_DIMENSIONS
1175
+ };
1176
+ var NotepadApp = {
1177
+ id: "notepad",
1178
+ name: "Notepad",
1179
+ icon: "\u{1F4DD}",
1180
+ fcIcon: "FcEditImage",
1181
+ ...DEFAULT_WINDOW_DIMENSIONS
1182
+ };
1183
+ var ImageViewerApp = {
1184
+ id: "image-viewer",
1185
+ name: "Image Viewer",
1186
+ icon: "\u{1F5BC}\uFE0F",
1187
+ fcIcon: "FcPicture",
1188
+ defaultWidth: 700,
1189
+ defaultHeight: 520,
1190
+ minWidth: 640,
1191
+ minHeight: 480
1192
+ };
1193
+ var PdfApp = {
1194
+ id: "pdf",
1195
+ name: "PDF Viewer",
1196
+ icon: "\u{1F4C4}",
1197
+ fcIcon: "FcDocument",
1198
+ defaultWidth: 780,
1199
+ defaultHeight: 580,
1200
+ minWidth: 640,
1201
+ minHeight: 480
1202
+ };
1203
+ var UploaderApp = {
1204
+ id: "uploader",
1205
+ name: "Uploader",
1206
+ icon: "\u{1F4E4}",
1207
+ fcIcon: "FcUpload",
1208
+ ...DEFAULT_WINDOW_DIMENSIONS
1209
+ };
1210
+ var MenuEditApp = {
1211
+ id: "menuedit",
1212
+ name: "MenuEdit",
1213
+ icon: "\u{1F4C1}",
1214
+ fcIcon: "FcFolder",
1215
+ singleWindow: true,
1216
+ ...DEFAULT_WINDOW_DIMENSIONS
1217
+ };
1218
+ var DeviceInfoApp = {
1219
+ id: "device-info",
1220
+ name: "Device Info",
1221
+ icon: "\u{1F4CA}",
1222
+ fcIcon: "FcBarChart",
1223
+ defaultWidth: 300,
1224
+ defaultHeight: 400,
1225
+ minWidth: 280,
1226
+ minHeight: 350,
1227
+ canMaximize: false,
1228
+ alwaysOnTop: true,
1229
+ singleWindow: true
1230
+ };
1231
+ var CalendarApp = {
1232
+ id: "calendar",
1233
+ name: "Calendar",
1234
+ icon: "\u{1F4C5}",
1235
+ fcIcon: "FcCalendar",
1236
+ defaultWidth: 320,
1237
+ defaultHeight: 370,
1238
+ minWidth: false,
1239
+ minHeight: false,
1240
+ canMaximize: false,
1241
+ singleWindow: true
1242
+ };
1243
+ var CORE_APPS = [
1244
+ FilesApp,
1245
+ SettingsApp,
1246
+ NotepadApp,
1247
+ ImageViewerApp,
1248
+ PdfApp,
1249
+ UploaderApp,
1250
+ MenuEditApp,
1251
+ DeviceInfoApp,
1252
+ CalendarApp
1253
+ ];
1254
+ var CORE_APP_IDS = CORE_APPS.map((app) => app.id);
1255
+
1256
+ // src/constants/extraApps.ts
1257
+ var WelcomeApp = {
1258
+ id: "welcome",
1259
+ name: "Welcome",
1260
+ icon: "\u{1F44B}",
1261
+ fcIcon: "FcBusinessContact",
1262
+ defaultWidth: 900,
1263
+ defaultHeight: 700,
1264
+ minWidth: 600,
1265
+ minHeight: 500
1266
+ };
1267
+ var StorybookApp = {
1268
+ id: "storybook",
1269
+ name: "Storybook",
1270
+ icon: "\u{1F4D6}",
1271
+ fcIcon: "FcReading",
1272
+ defaultWidth: 1100,
1273
+ defaultHeight: 700,
1274
+ minWidth: 640,
1275
+ minHeight: 480
1276
+ };
1277
+ var LinkedinApp = {
1278
+ id: "linkedin",
1279
+ name: "LinkedinApp",
1280
+ icon: "\u{1F517}",
1281
+ fcIcon: "FiLinkedin",
1282
+ iconColor: "#0A66C2",
1283
+ defaultWidth: 1100,
1284
+ defaultHeight: 700
1285
+ };
1286
+ var GithubApp = {
1287
+ id: "github",
1288
+ name: "GithubApp",
1289
+ icon: "\u{1F517}",
1290
+ fcIcon: "FiGithub",
1291
+ iconColor: "#000",
1292
+ defaultWidth: 640,
1293
+ defaultHeight: 480
1294
+ };
1295
+ var TerminalApp = {
1296
+ id: "terminal",
1297
+ name: "Terminal",
1298
+ icon: "\u{1F4BB}",
1299
+ fcIcon: "FcCommandLine",
1300
+ ...DEFAULT_WINDOW_DIMENSIONS
1301
+ };
1302
+ var CodeServerApp = {
1303
+ id: "code-server",
1304
+ name: "VS Code",
1305
+ icon: "\u{1F4D8}",
1306
+ fcIcon: "FcCodesandbox",
1307
+ iconUrl: `${import.meta.env.BASE_URL}vscode.svg`,
1308
+ defaultWidth: 1200,
1309
+ defaultHeight: 800,
1310
+ minWidth: 800,
1311
+ minHeight: 600
1312
+ };
1313
+ var DosEmulatorApp = {
1314
+ id: "dos-emulator",
1315
+ name: "DOS Emulator",
1316
+ icon: "\u{1F3AE}",
1317
+ fcIcon: "FcGamepad",
1318
+ defaultWidth: 900,
1319
+ defaultHeight: 700,
1320
+ minWidth: 640,
1321
+ minHeight: 480
1322
+ };
1323
+ var DoomApp = {
1324
+ id: "doom",
1325
+ name: "DOOM",
1326
+ icon: "\u{1F52B}",
1327
+ fcIcon: "FcVideoGame",
1328
+ iconUrl: "doom.png",
1329
+ defaultWidth: 900,
1330
+ defaultHeight: 700,
1331
+ minWidth: 640,
1332
+ minHeight: 480,
1333
+ autoLoad: { url: "Games/doom.jsdos", name: "DOOM" }
1334
+ };
1335
+ var EXTRA_APPS = [
1336
+ WelcomeApp,
1337
+ StorybookApp,
1338
+ LinkedinApp,
1339
+ GithubApp,
1340
+ TerminalApp,
1341
+ CodeServerApp,
1342
+ DosEmulatorApp,
1343
+ DoomApp
1344
+ ];
1345
+ var EXTRA_APP_IDS = EXTRA_APPS.map((app) => app.id);
1346
+
1347
+ // src/constants/apps.ts
1348
+ var APPS = [...CORE_APPS, ...EXTRA_APPS];
1349
+ var DESKTOP_APPS_ORDER = [
1350
+ "welcome",
1351
+ "uploader",
1352
+ "terminal",
1353
+ "code-server",
1354
+ "storybook",
1355
+ "settings",
1356
+ "notepad",
1357
+ "linkedin",
1358
+ "github",
1359
+ "files",
1360
+ "doom"
1361
+ ];
1362
+ function registerCustomApps(apps) {
1363
+ for (const app of apps) {
1364
+ if (!APPS.find((a) => a.id === app.id)) {
1365
+ APPS.push(app);
1366
+ }
1367
+ }
1368
+ }
1369
+ function registerDesktopApps(appIds) {
1370
+ for (const id of appIds) {
1371
+ if (!DESKTOP_APPS_ORDER.includes(id)) {
1372
+ DESKTOP_APPS_ORDER.push(id);
1373
+ }
1374
+ }
1375
+ }
1376
+
1377
+ // src/utils/getAppIdForMime.ts
1378
+ var APP_FILE_HANDLERS = [];
1379
+ var registerAppFileHandler = (handler) => {
1380
+ APP_FILE_HANDLERS.push(handler);
1381
+ };
1382
+ var IMAGE_MIME_TYPES = /* @__PURE__ */ new Set([
1383
+ "image/jpeg",
1384
+ "image/png",
1385
+ "image/gif",
1386
+ "image/svg+xml",
1387
+ "image/webp",
1388
+ "image/bmp"
1389
+ ]);
1390
+ var TEXT_MIME_TYPES = /* @__PURE__ */ new Set([
1391
+ "text/plain",
1392
+ "text/markdown",
1393
+ "text/x-markdown",
1394
+ "application/json"
1395
+ ]);
1396
+ registerAppFileHandler({
1397
+ appId: "pdf",
1398
+ mimeTypes: ["application/pdf"],
1399
+ transformContentData: (node) => ({ src: node.url ?? node.name })
1400
+ });
1401
+ registerAppFileHandler({
1402
+ appId: "image-viewer",
1403
+ mimeTypes: (mimeType) => IMAGE_MIME_TYPES.has(mimeType) || mimeType.startsWith("image/"),
1404
+ transformContentData: (node) => ({ src: node.url ?? node.name })
1405
+ });
1406
+ registerAppFileHandler({
1407
+ appId: "notepad",
1408
+ mimeTypes: (mimeType) => TEXT_MIME_TYPES.has(mimeType) || mimeType.startsWith("text/"),
1409
+ transformContentData: (node) => ({
1410
+ url: node.url,
1411
+ initialName: node.name,
1412
+ fileId: node.id
1413
+ })
1414
+ });
1415
+ registerAppFileHandler({
1416
+ appId: "dos-emulator",
1417
+ mimeTypes: ["application/jsdos", "application/zip", "application/x-zip-compressed"],
1418
+ transformContentData: (node) => ({
1419
+ autoLoad: { url: node.url ?? node.name, name: node.name }
1420
+ })
1421
+ });
1422
+ function findHandler(mimeType) {
1423
+ if (!mimeType) return null;
1424
+ for (const handler of APP_FILE_HANDLERS) {
1425
+ const matches = typeof handler.mimeTypes === "function" ? handler.mimeTypes(mimeType) : handler.mimeTypes.includes(mimeType);
1426
+ if (matches) return handler;
1427
+ }
1428
+ return null;
1429
+ }
1430
+ function getAppIdForMime(input) {
1431
+ if (typeof input === "string" || input === void 0) {
1432
+ const handler2 = findHandler(input);
1433
+ return handler2?.appId ?? "files";
1434
+ }
1435
+ const handler = findHandler(input.mimeType);
1436
+ if (!handler) return null;
1437
+ return {
1438
+ appId: handler.appId,
1439
+ contentData: handler.transformContentData(input)
1440
+ };
1441
+ }
1442
+ var DEFAULT_APP_STATE = {
1443
+ isLoaded: false,
1444
+ isLoading: false,
1445
+ isReady: false,
1446
+ hasError: false,
1447
+ error: void 0,
1448
+ mountedAt: 0,
1449
+ instance: void 0
1450
+ };
1451
+ var useCustomAppStore = create((set) => ({
1452
+ mountedApps: {},
1453
+ registeredApps: {},
1454
+ hmrConnected: false,
1455
+ hmrError: null,
1456
+ devServerOffline: false,
1457
+ hmrRetryCount: 0,
1458
+ setAppLoading: (appId) => set((state) => ({
1459
+ mountedApps: {
1460
+ ...state.mountedApps,
1461
+ [appId]: {
1462
+ ...DEFAULT_APP_STATE,
1463
+ isLoading: true,
1464
+ mountedAt: Date.now()
1465
+ }
1466
+ }
1467
+ })),
1468
+ setAppLoaded: (appId) => set((state) => ({
1469
+ mountedApps: {
1470
+ ...state.mountedApps,
1471
+ [appId]: {
1472
+ ...state.mountedApps[appId] || DEFAULT_APP_STATE,
1473
+ isLoaded: true,
1474
+ isLoading: false,
1475
+ hasError: false,
1476
+ error: void 0
1477
+ }
1478
+ }
1479
+ })),
1480
+ setAppReady: (appId, instance) => set((state) => ({
1481
+ mountedApps: {
1482
+ ...state.mountedApps,
1483
+ [appId]: {
1484
+ ...state.mountedApps[appId] || DEFAULT_APP_STATE,
1485
+ isReady: true,
1486
+ isLoaded: true,
1487
+ isLoading: false,
1488
+ instance: instance ?? state.mountedApps[appId]?.instance
1489
+ }
1490
+ }
1491
+ })),
1492
+ setAppError: (appId, error) => set((state) => ({
1493
+ mountedApps: {
1494
+ ...state.mountedApps,
1495
+ [appId]: {
1496
+ ...state.mountedApps[appId] || DEFAULT_APP_STATE,
1497
+ isLoaded: false,
1498
+ isLoading: false,
1499
+ isReady: false,
1500
+ hasError: true,
1501
+ error
1502
+ }
1503
+ }
1504
+ })),
1505
+ clearApp: (appId) => set((state) => {
1506
+ const { [appId]: _, ...rest } = state.mountedApps;
1507
+ return { mountedApps: rest };
1508
+ }),
1509
+ registerApp: (manifest) => set((state) => ({
1510
+ registeredApps: {
1511
+ ...state.registeredApps,
1512
+ [manifest.id]: manifest
1513
+ }
1514
+ })),
1515
+ unregisterApp: (appId) => set((state) => {
1516
+ const { [appId]: _, ...rest } = state.registeredApps;
1517
+ return { registeredApps: rest };
1518
+ }),
1519
+ setHmrConnected: (connected) => set({ hmrConnected: connected, devServerOffline: !connected }),
1520
+ setHmrError: (error) => set({ hmrError: error }),
1521
+ setDevServerOffline: (offline) => set({ devServerOffline: offline }),
1522
+ incrementHmrRetry: () => set((state) => ({ hmrRetryCount: state.hmrRetryCount + 1 })),
1523
+ resetHmrRetry: () => set({ hmrRetryCount: 0 })
1524
+ }));
1525
+
1526
+ // src/utils/getBaseUrl.ts
1527
+ var BUILD_TIME_BASE_URL = import.meta.env.BASE_URL || "/";
1528
+ var cachedBaseUrl = null;
1529
+ var testBaseUrl = null;
1530
+ function setTestBaseUrl(url) {
1531
+ testBaseUrl = url;
1532
+ cachedBaseUrl = null;
1533
+ }
1534
+ function getBaseUrl() {
1535
+ if (cachedBaseUrl !== null) {
1536
+ return cachedBaseUrl;
1537
+ }
1538
+ if (testBaseUrl !== null) {
1539
+ cachedBaseUrl = testBaseUrl;
1540
+ return cachedBaseUrl;
1541
+ }
1542
+ const baseElement = document.querySelector("base[href]");
1543
+ if (baseElement) {
1544
+ const href = baseElement.getAttribute("href");
1545
+ if (href) {
1546
+ cachedBaseUrl = href;
1547
+ return href;
1548
+ }
1549
+ }
1550
+ cachedBaseUrl = BUILD_TIME_BASE_URL;
1551
+ return cachedBaseUrl;
1552
+ }
1553
+ function resetBaseUrlCache() {
1554
+ cachedBaseUrl = null;
1555
+ }
1556
+ function resolveUrl(relativeUrl) {
1557
+ if (!relativeUrl) {
1558
+ return relativeUrl;
1559
+ }
1560
+ if (relativeUrl.startsWith("http://") || relativeUrl.startsWith("https://") || relativeUrl.startsWith("idb://") || relativeUrl.startsWith("blob:")) {
1561
+ return relativeUrl;
1562
+ }
1563
+ if (relativeUrl.startsWith("/")) {
1564
+ return relativeUrl;
1565
+ }
1566
+ const base = getBaseUrl();
1567
+ const normalizedBase = base.endsWith("/") ? base : `${base}/`;
1568
+ return normalizedBase + relativeUrl;
1569
+ }
1570
+
1571
+ // src/constants/breakpoints.ts
1572
+ var BREAKPOINTS = {
1573
+ MOBILE: 768,
1574
+ TABLET: 1024,
1575
+ DESKTOP: 1280
1576
+ };
1577
+ var DEFAULT_VIEWPORT = {
1578
+ WIDTH: 1280,
1579
+ HEIGHT: 800
1580
+ };
1581
+
1582
+ // src/constants/layout.ts
1583
+ var TASKBAR_HEIGHT = 48;
1584
+ var ICON_COLUMN_WIDTH = 80;
1585
+ var ICON_ROW_HEIGHT = 80;
1586
+ var ICON_MARGIN = 20;
1587
+ var DEFAULT_VIEWPORT_WIDTH = DEFAULT_VIEWPORT.WIDTH;
1588
+ var DEFAULT_VIEWPORT_HEIGHT = DEFAULT_VIEWPORT.HEIGHT;
1589
+ var windowManager = new WindowManagerAdapter();
1590
+ function createFileSystemAdapter() {
1591
+ if (isDocker()) {
1592
+ console.log("[FileSystem] Using DockerFileSystemAdapter");
1593
+ return new DockerFileSystemAdapter();
1594
+ }
1595
+ console.log("[FileSystem] Using IndexedDBFileSystem");
1596
+ return new IndexedDBFileSystem();
1597
+ }
1598
+ var fileSystem = createFileSystemAdapter();
1599
+ var resetFileSystem = () => {
1600
+ fileSystem.clearAll().catch(() => {
1601
+ });
1602
+ };
1603
+ var resetWindowManager = () => windowManager.reset();
1604
+ async function resolveFileUrl(url, signal) {
1605
+ if (url.startsWith("idb://")) {
1606
+ const blob = await fileSystem.getBlob(url);
1607
+ return blob ? blob.text() : "";
1608
+ }
1609
+ const src = url.startsWith("http") || url.startsWith("/") ? url : `${import.meta.env.BASE_URL}${url}`;
1610
+ const res = await fetch(src, { signal });
1611
+ return res.ok ? res.text() : "";
1612
+ }
1613
+ var getDesktopFolderId = () => {
1614
+ return fileSystem.getRootNodes().find((n) => n.type === "folder" && n.name === "Desktop")?.id ?? null;
1615
+ };
1616
+ var calculateNextFreeSlot = (icons, viewportHeight) => {
1617
+ const occupied = new Set(icons.map((ic) => `${ic.x},${ic.y}`));
1618
+ const maxRows = Math.max(1, Math.floor((viewportHeight - ICON_MARGIN) / ICON_ROW_HEIGHT));
1619
+ for (let col = 0; ; col++) {
1620
+ const x = ICON_MARGIN + col * ICON_COLUMN_WIDTH;
1621
+ for (let row = 0; row < maxRows; row++) {
1622
+ const y = ICON_MARGIN + row * ICON_ROW_HEIGHT;
1623
+ if (!occupied.has(`${x},${y}`)) return { x, y };
1624
+ }
1625
+ }
1626
+ };
1627
+ var calculateSlotByIndex = (index, viewportHeight) => {
1628
+ const maxRows = Math.max(1, Math.floor((viewportHeight - ICON_MARGIN) / ICON_ROW_HEIGHT));
1629
+ const col = Math.floor(index / maxRows);
1630
+ const row = index % maxRows;
1631
+ return {
1632
+ x: ICON_MARGIN + col * ICON_COLUMN_WIDTH,
1633
+ y: ICON_MARGIN + row * ICON_ROW_HEIGHT
1634
+ };
1635
+ };
1636
+ var appendDesktopIcon = (current, input, viewportHeight) => {
1637
+ const pos = calculateNextFreeSlot(current, viewportHeight);
1638
+ return [...current, createDesktopIcon({ ...input, ...pos })];
1639
+ };
1640
+ var syncDesktopIcons = () => {
1641
+ const desktopFolderId = getDesktopFolderId();
1642
+ if (!desktopFolderId || !fileSystem.isReady()) return;
1643
+ const desktopChildren = fileSystem.getChildren(desktopFolderId);
1644
+ const desktopNodeIds = new Set(desktopChildren.map((n) => n.id));
1645
+ const desktopNodeNames = new Set(desktopChildren.map((n) => n.name));
1646
+ const { icons, viewportHeight } = useDesktopStore.getState();
1647
+ let updated = icons.filter(
1648
+ (ic) => !ic.nodeId || desktopNodeIds.has(ic.nodeId) || desktopNodeNames.has(ic.name)
1649
+ );
1650
+ for (const node of desktopChildren) {
1651
+ const existing = updated.find((ic) => ic.name === node.name);
1652
+ if (!existing) {
1653
+ const isFolder = node.type === "folder";
1654
+ const folderNode = isFolder ? node : null;
1655
+ const fileNode = !isFolder ? node : null;
1656
+ const appId = isFolder ? "files" : fileNode ? getAppIdForMime(fileNode)?.appId ?? "" : "";
1657
+ updated = appendDesktopIcon(
1658
+ updated,
1659
+ {
1660
+ name: node.name,
1661
+ icon: isFolder ? "\u{1F4C1}" : "\u{1F4C4}",
1662
+ iconName: folderNode?.iconName,
1663
+ iconColor: folderNode?.iconColor,
1664
+ appId,
1665
+ nodeId: node.id
1666
+ },
1667
+ viewportHeight
1668
+ );
1669
+ } else if (existing.nodeId !== node.id) {
1670
+ updated = updated.map((ic) => ic.name === node.name ? { ...ic, nodeId: node.id } : ic);
1671
+ }
1672
+ }
1673
+ const sorted = sortDesktopIcons(updated);
1674
+ useDesktopStore.setState({
1675
+ icons: sorted.map((icon, i) => ({ ...icon, ...calculateSlotByIndex(i, viewportHeight) }))
1676
+ });
1677
+ };
1678
+ var useDesktopStore = create()(
1679
+ persist(
1680
+ (set, get) => ({
1681
+ windows: [],
1682
+ openWindow: (input) => {
1683
+ const window2 = windowManager.open(input);
1684
+ set((state) => ({ windows: [...state.windows, window2] }));
1685
+ },
1686
+ closeWindow: (id) => {
1687
+ windowManager.close(id);
1688
+ set((state) => ({ windows: state.windows.filter((w) => w.id !== id) }));
1689
+ },
1690
+ minimizeWindow: (id) => {
1691
+ windowManager.minimize(id);
1692
+ set((state) => ({
1693
+ windows: state.windows.map((w) => w.id === id ? { ...w, state: "minimized" } : w)
1694
+ }));
1695
+ },
1696
+ maximizeWindow: (id) => {
1697
+ windowManager.maximize(id);
1698
+ set((state) => ({
1699
+ windows: state.windows.map((w) => w.id === id ? { ...w, state: "maximized" } : w)
1700
+ }));
1701
+ },
1702
+ restoreWindow: (id) => {
1703
+ windowManager.restore(id);
1704
+ set((state) => ({
1705
+ windows: state.windows.map((w) => w.id === id ? { ...w, state: "normal" } : w)
1706
+ }));
1707
+ },
1708
+ focusWindow: (id) => {
1709
+ windowManager.focus(id);
1710
+ const updated = windowManager.getById(id);
1711
+ if (!updated) return;
1712
+ set((state) => ({
1713
+ windows: state.windows.map((w) => w.id === id ? updated : w)
1714
+ }));
1715
+ },
1716
+ moveWindow: (id, x, y) => {
1717
+ windowManager.move(id, x, y);
1718
+ set((state) => ({
1719
+ windows: state.windows.map((w) => w.id === id ? { ...w, x, y } : w)
1720
+ }));
1721
+ },
1722
+ resizeWindow: (id, width, height) => {
1723
+ windowManager.resize(id, width, height);
1724
+ set((state) => ({
1725
+ windows: state.windows.map((w) => w.id === id ? { ...w, width, height } : w)
1726
+ }));
1727
+ },
1728
+ icons: [],
1729
+ addIcon: (input) => {
1730
+ const icon = createDesktopIcon(input);
1731
+ set((state) => ({ icons: [...state.icons, icon] }));
1732
+ },
1733
+ removeIcon: (id) => {
1734
+ set((state) => ({ icons: state.icons.filter((i) => i.id !== id) }));
1735
+ },
1736
+ fsNodes: [],
1737
+ desktopFolderId: null,
1738
+ isFsReady: false,
1739
+ initFs: async () => {
1740
+ try {
1741
+ if (fileSystem.isReady() && fileSystem.reinitialize) {
1742
+ await fileSystem.reinitialize();
1743
+ } else if (!fileSystem.isReady()) {
1744
+ await fileSystem.initialize();
1745
+ }
1746
+ const manifest = await fetch(resolveUrl("fs-manifest.json")).then(
1747
+ (r) => r.json()
1748
+ );
1749
+ const currentSha = manifest.sha ?? null;
1750
+ const savedSha = await fileSystem.getManifestSha();
1751
+ if (fileSystem.isEmpty()) {
1752
+ await fileSystem.seed(manifest);
1753
+ if (currentSha) {
1754
+ await fileSystem.saveManifestSha(currentSha);
1755
+ }
1756
+ } else if (savedSha !== currentSha && currentSha !== null) {
1757
+ await fileSystem.mergeSeed(manifest);
1758
+ await fileSystem.saveManifestSha(currentSha);
1759
+ }
1760
+ set({
1761
+ fsNodes: fileSystem.getAllNodes(),
1762
+ desktopFolderId: getDesktopFolderId(),
1763
+ isFsReady: true
1764
+ });
1765
+ if (!isBrowser()) {
1766
+ let customAppsData = null;
1767
+ try {
1768
+ customAppsData = await fetchAppManifest();
1769
+ if (customAppsData?.apps?.length) {
1770
+ set({ customApps: customAppsData.apps });
1771
+ for (const app of customAppsData.apps) {
1772
+ useCustomAppStore.getState().registerApp(app);
1773
+ }
1774
+ }
1775
+ const serverAppIds = new Set(customAppsData?.apps?.map((a) => a.id) ?? []);
1776
+ const staleAppIds = get().icons.filter(
1777
+ (ic) => ic.appId && !APPS.find((a) => a.id === ic.appId) && !serverAppIds.has(ic.appId)
1778
+ ).map((ic) => ic.appId);
1779
+ if (staleAppIds.length > 0) {
1780
+ set((state) => ({
1781
+ icons: state.icons.filter((ic) => !staleAppIds.includes(ic.appId)),
1782
+ customApps: state.customApps.filter((a) => !staleAppIds.includes(a.id))
1783
+ }));
1784
+ }
1785
+ } catch (error) {
1786
+ console.warn("[DesktopStore] Failed to load custom apps manifest:", error);
1787
+ }
1788
+ }
1789
+ for (const appId of DESKTOP_APPS_ORDER) {
1790
+ const app = APPS.find((a) => a.id === appId);
1791
+ if (!app) continue;
1792
+ if (!get().icons.some((ic) => ic.appId === appId)) {
1793
+ set({
1794
+ icons: appendDesktopIcon(
1795
+ get().icons,
1796
+ { name: app.name, icon: app.icon, appId },
1797
+ get().viewportHeight
1798
+ )
1799
+ });
1800
+ }
1801
+ }
1802
+ if (!isBrowser()) {
1803
+ const customApps = get().customApps;
1804
+ if (customApps.length) {
1805
+ for (const app of customApps) {
1806
+ if (!get().icons.some((ic) => ic.appId === app.id)) {
1807
+ set({
1808
+ icons: appendDesktopIcon(
1809
+ get().icons,
1810
+ { name: app.name, icon: app.icon, appId: app.id },
1811
+ get().viewportHeight
1812
+ )
1813
+ });
1814
+ }
1815
+ }
1816
+ }
1817
+ }
1818
+ const desktopFolderId = getDesktopFolderId();
1819
+ if (!desktopFolderId) return;
1820
+ const desktopFiles = fileSystem.getChildren(desktopFolderId);
1821
+ desktopFiles.forEach((node) => {
1822
+ if (node.type !== "file") return;
1823
+ const existingIcon = get().icons.find((ic) => ic.name === node.name);
1824
+ if (existingIcon) {
1825
+ if (!existingIcon.nodeId) {
1826
+ set({
1827
+ icons: get().icons.map(
1828
+ (ic) => ic.name === node.name ? { ...ic, nodeId: node.id } : ic
1829
+ )
1830
+ });
1831
+ }
1832
+ } else {
1833
+ set({
1834
+ icons: appendDesktopIcon(
1835
+ get().icons,
1836
+ {
1837
+ name: node.name,
1838
+ icon: "\u{1F4C4}",
1839
+ appId: getAppIdForMime(node.mimeType),
1840
+ nodeId: node.id
1841
+ },
1842
+ get().viewportHeight
1843
+ )
1844
+ });
1845
+ }
1846
+ });
1847
+ syncDesktopIcons();
1848
+ } catch (error) {
1849
+ console.error("Failed to initialize filesystem:", error);
1850
+ set({ isFsReady: true });
1851
+ }
1852
+ },
1853
+ createFile: async (name, content, parentId, url) => {
1854
+ const file = await fileSystem.createFile(name, content, parentId, url);
1855
+ const desktopFolderId = getDesktopFolderId();
1856
+ const { icons, viewportHeight } = get();
1857
+ const newIcons = parentId && parentId === desktopFolderId && !icons.some((ic) => ic.name === file.name) ? appendDesktopIcon(
1858
+ icons,
1859
+ {
1860
+ name: file.name,
1861
+ icon: "\u{1F4C4}",
1862
+ appId: getAppIdForMime(file.mimeType),
1863
+ nodeId: file.id
1864
+ },
1865
+ viewportHeight
1866
+ ) : icons;
1867
+ set({ fsNodes: fileSystem.getAllNodes(), icons: newIcons });
1868
+ return file;
1869
+ },
1870
+ createFolder: async (name, parentId, iconName, iconColor) => {
1871
+ const folder = await fileSystem.createFolder(name, parentId, iconName, iconColor);
1872
+ const desktopFolderId = getDesktopFolderId();
1873
+ const { icons, viewportHeight } = get();
1874
+ const newIcons = parentId && parentId === desktopFolderId && !icons.some((ic) => ic.name === folder.name) ? appendDesktopIcon(
1875
+ icons,
1876
+ {
1877
+ name: folder.name,
1878
+ icon: "\u{1F4C1}",
1879
+ iconName: folder.iconName,
1880
+ iconColor: folder.iconColor,
1881
+ appId: "files",
1882
+ nodeId: folder.id
1883
+ },
1884
+ viewportHeight
1885
+ ) : icons;
1886
+ set({ fsNodes: fileSystem.getAllNodes(), icons: newIcons });
1887
+ return folder;
1888
+ },
1889
+ updateFile: async (id, content) => {
1890
+ await fileSystem.updateFile(id, content);
1891
+ set({ fsNodes: fileSystem.getAllNodes() });
1892
+ },
1893
+ deleteNode: async (id) => {
1894
+ const node = fileSystem.getNode(id);
1895
+ await fileSystem.delete(id);
1896
+ set({ fsNodes: fileSystem.getAllNodes() });
1897
+ if (node?.type === "file" || node?.type === "folder") {
1898
+ const desktopFolderId = getDesktopFolderId();
1899
+ if (node.parentId && node.parentId === desktopFolderId) {
1900
+ set((state) => ({
1901
+ icons: state.icons.filter((ic) => ic.name !== node.name)
1902
+ }));
1903
+ }
1904
+ }
1905
+ },
1906
+ moveNode: async (id, newParentId) => {
1907
+ await fileSystem.move(id, newParentId);
1908
+ set({ fsNodes: fileSystem.getAllNodes() });
1909
+ },
1910
+ setFsNodes: (nodes) => {
1911
+ set({ fsNodes: nodes });
1912
+ },
1913
+ syncIcons: () => {
1914
+ syncDesktopIcons();
1915
+ },
1916
+ clipboard: { content: [], action: null },
1917
+ copyToClipboard: (nodes) => {
1918
+ set({ clipboard: { content: nodes, action: "copy" } });
1919
+ },
1920
+ cutToClipboard: (nodes) => {
1921
+ set({ clipboard: { content: nodes, action: "cut" } });
1922
+ },
1923
+ clearClipboard: () => {
1924
+ set({ clipboard: { content: [], action: null } });
1925
+ },
1926
+ filesCurrentFolderId: null,
1927
+ setFilesCurrentFolderId: (id) => {
1928
+ set({ filesCurrentFolderId: id });
1929
+ },
1930
+ contextMenu: { x: 0, y: 0, owner: null },
1931
+ openContextMenu: (x, y, owner, targetNodeId) => {
1932
+ set({ contextMenu: { x, y, owner, targetNodeId } });
1933
+ },
1934
+ closeContextMenu: () => {
1935
+ set({ contextMenu: { x: 0, y: 0, owner: null, targetNodeId: void 0 } });
1936
+ },
1937
+ mergeSeed: async (manifest) => {
1938
+ await fileSystem.mergeSeed(manifest);
1939
+ set({ fsNodes: fileSystem.getAllNodes(), desktopFolderId: getDesktopFolderId() });
1940
+ },
1941
+ mergeDesktopApps: (appIds) => {
1942
+ for (const appId of appIds) {
1943
+ const app = APPS.find((a) => a.id === appId);
1944
+ if (!app) continue;
1945
+ if (get().icons.some((ic) => ic.appId === appId)) continue;
1946
+ set({
1947
+ icons: appendDesktopIcon(
1948
+ get().icons,
1949
+ { name: app.name, icon: app.icon, appId },
1950
+ get().viewportHeight
1951
+ )
1952
+ });
1953
+ }
1954
+ const desktopFolderId = getDesktopFolderId();
1955
+ if (!desktopFolderId) return;
1956
+ for (const node of fileSystem.getChildren(desktopFolderId)) {
1957
+ if (node.type !== "file") continue;
1958
+ if (get().icons.some((ic) => ic.name === node.name)) continue;
1959
+ set({
1960
+ icons: appendDesktopIcon(
1961
+ get().icons,
1962
+ {
1963
+ name: node.name,
1964
+ icon: "\u{1F4C4}",
1965
+ appId: getAppIdForMime(node.mimeType),
1966
+ nodeId: node.id
1967
+ },
1968
+ get().viewportHeight
1969
+ )
1970
+ });
1971
+ }
1972
+ },
1973
+ reorderDesktopApps: (appIds) => {
1974
+ const currentIcons = get().icons;
1975
+ const appIcons = [];
1976
+ const fileIcons = [];
1977
+ for (const icon of currentIcons) {
1978
+ if (icon.appId && !icon.nodeId) {
1979
+ appIcons.push(icon);
1980
+ } else {
1981
+ fileIcons.push(icon);
1982
+ }
1983
+ }
1984
+ const reorderedAppIcons = [];
1985
+ for (const appId of appIds) {
1986
+ const existingIndex = appIcons.findIndex((ic) => ic.appId === appId);
1987
+ if (existingIndex !== -1) {
1988
+ reorderedAppIcons.push(appIcons[existingIndex]);
1989
+ }
1990
+ }
1991
+ for (const icon of appIcons) {
1992
+ if (!reorderedAppIcons.includes(icon)) {
1993
+ reorderedAppIcons.push(icon);
1994
+ }
1995
+ }
1996
+ let currentY = ICON_MARGIN;
1997
+ let currentX = ICON_MARGIN;
1998
+ const viewportHeight = get().viewportHeight;
1999
+ const rowsBeforeFileIcons = Math.max(
2000
+ 1,
2001
+ Math.floor((viewportHeight - TASKBAR_HEIGHT - ICON_MARGIN) / ICON_ROW_HEIGHT)
2002
+ );
2003
+ for (let i = 0; i < reorderedAppIcons.length; i++) {
2004
+ const row = Math.floor(i / 1);
2005
+ if (row >= rowsBeforeFileIcons && row > 0) {
2006
+ currentX += ICON_COLUMN_WIDTH;
2007
+ currentY = ICON_MARGIN;
2008
+ }
2009
+ reorderedAppIcons[i] = {
2010
+ ...reorderedAppIcons[i],
2011
+ x: currentX,
2012
+ y: currentY
2013
+ };
2014
+ currentY += ICON_ROW_HEIGHT;
2015
+ }
2016
+ const allIcons = [...reorderedAppIcons, ...fileIcons];
2017
+ set({ icons: allIcons });
2018
+ },
2019
+ notifications: [],
2020
+ addNotification: (item) => {
2021
+ set((state) => ({ notifications: [...state.notifications, item] }));
2022
+ },
2023
+ removeNotification: (id) => {
2024
+ set((state) => ({ notifications: state.notifications.filter((n) => n.id !== id) }));
2025
+ },
2026
+ viewportWidth: DEFAULT_VIEWPORT_WIDTH,
2027
+ viewportHeight: DEFAULT_VIEWPORT_HEIGHT,
2028
+ isViewportInitialized: false,
2029
+ setViewportSize: (width, height) => {
2030
+ const oldWidth = get().viewportWidth;
2031
+ const oldHeight = get().viewportHeight;
2032
+ const isInitialized = get().isViewportInitialized;
2033
+ if (oldWidth === width && oldHeight === height) return;
2034
+ set({ viewportWidth: width, viewportHeight: height });
2035
+ if (!isInitialized) {
2036
+ set({ isViewportInitialized: true });
2037
+ return;
2038
+ }
2039
+ if (oldHeight !== height) {
2040
+ const currentMaxRows = Math.max(
2041
+ 1,
2042
+ Math.floor((oldHeight - ICON_MARGIN) / ICON_ROW_HEIGHT)
2043
+ );
2044
+ const newMaxRows = Math.max(1, Math.floor((height - ICON_MARGIN) / ICON_ROW_HEIGHT));
2045
+ if (currentMaxRows !== newMaxRows) {
2046
+ const icons = get().icons;
2047
+ const repositioned = icons.map((icon, index) => {
2048
+ const col = Math.floor(index / newMaxRows);
2049
+ const row = index % newMaxRows;
2050
+ return {
2051
+ ...icon,
2052
+ x: ICON_MARGIN + col * ICON_COLUMN_WIDTH,
2053
+ y: ICON_MARGIN + row * ICON_ROW_HEIGHT
2054
+ };
2055
+ });
2056
+ set({ icons: repositioned });
2057
+ }
2058
+ }
2059
+ const isMobile = width < 768;
2060
+ const windows = get().windows.map((win) => {
2061
+ if (win.state !== "normal") return win;
2062
+ if (isMobile) {
2063
+ return { ...win, x: 0, y: 0, width, height, minWidth: void 0, minHeight: void 0 };
2064
+ }
2065
+ const ratioX = oldWidth > 0 ? width / oldWidth : 1;
2066
+ const ratioY = oldHeight > 0 ? height / oldHeight : 1;
2067
+ let newX = Math.round(win.x * ratioX);
2068
+ let newY = Math.round(win.y * ratioY);
2069
+ let newW = win.width;
2070
+ let newH = win.height;
2071
+ if (newW > width) newW = width;
2072
+ if (newH > height) newH = height;
2073
+ newX = Math.max(0, Math.min(newX, width - Math.min(newW, 100)));
2074
+ newY = Math.max(0, Math.min(newY, height - Math.min(newH, 40)));
2075
+ return { ...win, x: newX, y: newY, width: newW, height: newH };
2076
+ });
2077
+ for (const win of windows) {
2078
+ windowManager.move(win.id, win.x, win.y);
2079
+ windowManager.resize(win.id, win.width, win.height);
2080
+ }
2081
+ set({ windows });
2082
+ },
2083
+ desktopSortMode: "default",
2084
+ filesSortMode: "default",
2085
+ setDesktopSortMode: (mode) => {
2086
+ set({ desktopSortMode: mode });
2087
+ const icons = get().icons;
2088
+ const sorted = sortDesktopIconsByMode(icons, mode);
2089
+ const vh = get().viewportHeight;
2090
+ set({
2091
+ icons: sorted.map((icon, i) => ({
2092
+ ...icon,
2093
+ ...calculateSlotByIndex(i, vh)
2094
+ }))
2095
+ });
2096
+ },
2097
+ setFilesSortMode: (mode) => {
2098
+ set({ filesSortMode: mode });
2099
+ },
2100
+ launcherFolders: DEFAULT_LAUNCHER_FOLDERS,
2101
+ setLauncherFolders: (folders) => {
2102
+ set({ launcherFolders: folders });
2103
+ },
2104
+ createLauncherFolder: (input) => {
2105
+ const folder = createLauncherFolder(input);
2106
+ set((state) => ({
2107
+ launcherFolders: [...state.launcherFolders, folder].sort((a, b) => a.order - b.order)
2108
+ }));
2109
+ return folder;
2110
+ },
2111
+ updateLauncherFolder: (id, updates) => {
2112
+ set((state) => ({
2113
+ launcherFolders: state.launcherFolders.map(
2114
+ (f) => f.id === id ? updateLauncherFolder(f, updates) : f
2115
+ )
2116
+ }));
2117
+ },
2118
+ deleteLauncherFolder: (id) => {
2119
+ set((state) => ({
2120
+ launcherFolders: state.launcherFolders.filter((f) => f.id !== id || f.isPredefined)
2121
+ }));
2122
+ },
2123
+ moveLauncherFolder: (id, newOrder) => {
2124
+ set((state) => ({
2125
+ launcherFolders: state.launcherFolders.map((f) => f.id === id ? { ...f, order: newOrder } : f).sort((a, b) => a.order - b.order)
2126
+ }));
2127
+ },
2128
+ addAppToFolder: (appId, folderId) => {
2129
+ set((state) => ({
2130
+ launcherFolders: state.launcherFolders.map(
2131
+ (f) => f.id === folderId && !f.appIds.includes(appId) ? { ...f, appIds: [...f.appIds, appId] } : f
2132
+ )
2133
+ }));
2134
+ },
2135
+ removeAppFromFolder: (appId, folderId) => {
2136
+ set((state) => ({
2137
+ launcherFolders: state.launcherFolders.map(
2138
+ (f) => f.id === folderId ? { ...f, appIds: f.appIds.filter((id) => id !== appId) } : f
2139
+ )
2140
+ }));
2141
+ },
2142
+ customApps: [],
2143
+ setCustomApps: (apps) => {
2144
+ set({ customApps: apps });
2145
+ },
2146
+ handleCustomAppRegistered: (manifest) => {
2147
+ set((state) => {
2148
+ const exists = state.customApps.some((a) => a.id === manifest.id);
2149
+ return {
2150
+ customApps: exists ? state.customApps.map((a) => a.id === manifest.id ? manifest : a) : [...state.customApps, manifest]
2151
+ };
2152
+ });
2153
+ const existingIcon = get().icons.find((ic) => ic.appId === manifest.id);
2154
+ if (!existingIcon) {
2155
+ set((state) => ({
2156
+ icons: appendDesktopIcon(
2157
+ state.icons,
2158
+ {
2159
+ name: manifest.name,
2160
+ icon: manifest.icon || "FcDataSheet",
2161
+ appId: manifest.id
2162
+ },
2163
+ state.viewportHeight
2164
+ )
2165
+ }));
2166
+ }
2167
+ },
2168
+ handleCustomAppUnregistered: (appId) => {
2169
+ set((state) => ({
2170
+ customApps: state.customApps.filter((a) => a.id !== appId)
2171
+ }));
2172
+ set((state) => ({
2173
+ icons: state.icons.filter((ic) => ic.appId !== appId)
2174
+ }));
2175
+ get().windows.filter((w) => w.content === appId).forEach((w) => get().closeWindow(w.id));
2176
+ useCustomAppStore.getState().clearApp(appId);
2177
+ }
2178
+ }),
2179
+ {
2180
+ name: "fde-desktop",
2181
+ storage: createJSONStorage(() => localStorage),
2182
+ partialize: (state) => ({
2183
+ windows: state.windows,
2184
+ icons: state.icons,
2185
+ customApps: state.customApps,
2186
+ desktopSortMode: state.desktopSortMode,
2187
+ filesSortMode: state.filesSortMode,
2188
+ launcherFolders: state.launcherFolders,
2189
+ viewportWidth: state.viewportWidth,
2190
+ viewportHeight: state.viewportHeight
2191
+ }),
2192
+ merge: (persisted, current) => {
2193
+ const p = persisted;
2194
+ const TRANSIENT_WINDOWS = ["createItem"];
2195
+ const mergedWindows = (p.windows ?? []).filter((w) => !TRANSIENT_WINDOWS.includes(w.content)).map((w) => {
2196
+ const app = APPS.find((a) => a.id === w.content);
2197
+ const customApp = (p.customApps ?? current.customApps)?.find((a) => a.id === w.content);
2198
+ return {
2199
+ ...w,
2200
+ icon: w.icon ?? app?.icon ?? customApp?.icon,
2201
+ fcIcon: w.fcIcon ?? app?.fcIcon,
2202
+ iconUrl: w.iconUrl ?? app?.iconUrl,
2203
+ iconColor: app?.iconColor,
2204
+ canMaximize: app?.canMaximize ?? customApp?.window?.canMaximize,
2205
+ minWidth: app?.minWidth === false || customApp?.window?.minWidth === false ? void 0 : app?.minWidth ?? customApp?.window?.minWidth ?? DEFAULT_WINDOW_DIMENSIONS.minWidth,
2206
+ minHeight: app?.minHeight === false || customApp?.window?.minHeight === false ? void 0 : app?.minHeight ?? customApp?.window?.minHeight ?? DEFAULT_WINDOW_DIMENSIONS.minHeight
2207
+ };
2208
+ });
2209
+ windowManager.loadWindows(mergedWindows);
2210
+ const predefinedIds = new Set(DEFAULT_LAUNCHER_FOLDERS.map((f) => f.id));
2211
+ const customFolders = (p.launcherFolders ?? []).filter((f) => !predefinedIds.has(f.id));
2212
+ const mergedLauncherFolders = [...DEFAULT_LAUNCHER_FOLDERS, ...customFolders].sort(
2213
+ (a, b) => a.order - b.order
2214
+ );
2215
+ const hasPersistedViewport = p.viewportWidth != null && p.viewportHeight != null;
2216
+ return {
2217
+ ...current,
2218
+ ...p,
2219
+ windows: mergedWindows,
2220
+ launcherFolders: mergedLauncherFolders,
2221
+ viewportWidth: p.viewportWidth ?? current.viewportWidth,
2222
+ viewportHeight: p.viewportHeight ?? current.viewportHeight,
2223
+ isViewportInitialized: hasPersistedViewport ? true : current.isViewportInitialized
2224
+ };
2225
+ }
2226
+ }
2227
+ )
2228
+ );
2229
+ if (typeof window !== "undefined") {
2230
+ window.__DESKTOP_STATE__ = useDesktopStore.getState();
2231
+ useDesktopStore.subscribe((state) => {
2232
+ window.__DESKTOP_STATE__ = state;
2233
+ });
2234
+ }
2235
+
2236
+ export { APPS, BREAKPOINTS, CORE_APPS, CORE_APP_IDS, CUSTOM_APPS_FOLDER_ID, DEFAULT_LAUNCHER_FOLDERS, DEFAULT_VIEWPORT_HEIGHT, DEFAULT_VIEWPORT_WIDTH, DEFAULT_WINDOW_DIMENSIONS, DESKTOP_APPS_ORDER, DockerFileSystemAdapter, EXTRA_APPS, EXTRA_APP_IDS, ICON_COLUMN_WIDTH, ICON_MARGIN, ICON_ROW_HEIGHT, IMAGE_MIME_TYPES, IndexedDBFileSystem, PREDEFINED_LAUNCHER_FOLDERS, SortOptions, TASKBAR_HEIGHT, TEXT_MIME_TYPES, WindowManagerAdapter, clearManifestCache, convertToAppEntry, createDesktopIcon, createFile, createFolder, createLauncherFolder, createPredefinedLauncherFolder, createWindow, fetchAppManifest, fileSystem, generateUUID, getAppIdForMime, getBaseUrl, getCachedManifest, getCustomAppById, getCustomApps, getMimeTypeFromExtension, hashBlob, isCustomApp, mergeAppsWithCustomApps, registerAppFileHandler, registerCustomApps, registerDesktopApps, removeFromManifestCache, resetBaseUrlCache, resetFileSystem, resetWindowManager, resolveFileUrl, resolveUrl, setApiBaseUrl, setTestBaseUrl, sortDesktopIcons, sortDesktopIconsByMode, sortNodes, sortNodesByMode, syncWithServer, updateLauncherFolder, updateManifestCache, useCustomAppStore, useDesktopStore };