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