react-os-shell 0.14.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/dist/Files-V4SZKOGZ.js +12 -0
  2. package/dist/{Files-5N64E375.js.map → Files-V4SZKOGZ.js.map} +1 -1
  3. package/dist/{Notepad-AFAUA4KJ.js → Notepad-6JJ4RT3U.js} +110 -101
  4. package/dist/Notepad-6JJ4RT3U.js.map +1 -0
  5. package/dist/{PomodoroTimer-SBBPQMYB.js → PomodoroTimer-HGPJ5R6V.js} +3 -4
  6. package/dist/PomodoroTimer-HGPJ5R6V.js.map +1 -0
  7. package/dist/Stock-XLC3LZJV.js +152 -0
  8. package/dist/Stock-XLC3LZJV.js.map +1 -0
  9. package/dist/apps/index.d.ts +16 -4
  10. package/dist/apps/index.js +10 -11
  11. package/dist/apps/index.js.map +1 -1
  12. package/dist/chunk-4R4SXMDV.js +98 -0
  13. package/dist/chunk-4R4SXMDV.js.map +1 -0
  14. package/dist/{chunk-MK3HLUO4.js → chunk-5X5LQNOX.js} +172 -3
  15. package/dist/chunk-5X5LQNOX.js.map +1 -0
  16. package/dist/chunk-UXEG2NRI.js +682 -0
  17. package/dist/chunk-UXEG2NRI.js.map +1 -0
  18. package/dist/chunk-VGTEM5RZ.js +89 -0
  19. package/dist/chunk-VGTEM5RZ.js.map +1 -0
  20. package/dist/index.d.ts +9 -1
  21. package/dist/index.js +16 -142
  22. package/dist/index.js.map +1 -1
  23. package/dist/styles.css +26 -2
  24. package/package.json +1 -1
  25. package/dist/Files-5N64E375.js +0 -11
  26. package/dist/Notepad-AFAUA4KJ.js.map +0 -1
  27. package/dist/PomodoroTimer-SBBPQMYB.js.map +0 -1
  28. package/dist/Stock-ICDNFM7U.js +0 -234
  29. package/dist/Stock-ICDNFM7U.js.map +0 -1
  30. package/dist/TodoList-26N6ZTLN.js +0 -309
  31. package/dist/TodoList-26N6ZTLN.js.map +0 -1
  32. package/dist/chunk-D4ZM3K2S.js +0 -605
  33. package/dist/chunk-D4ZM3K2S.js.map +0 -1
  34. package/dist/chunk-MK3HLUO4.js.map +0 -1
  35. package/dist/chunk-OB7T3Q5C.js +0 -46
  36. package/dist/chunk-OB7T3Q5C.js.map +0 -1
  37. package/dist/chunk-QTJ2CHJX.js +0 -174
  38. package/dist/chunk-QTJ2CHJX.js.map +0 -1
@@ -0,0 +1,682 @@
1
+ import { Breadcrumbs, openPreviewFile } from './chunk-4R4SXMDV.js';
2
+ import { SidebarLayout } from './chunk-VGTEM5RZ.js';
3
+ import { toast_default } from './chunk-WIJ45SYD.js';
4
+ import { useWindowManager, WindowTitle } from './chunk-4RXDOSKZ.js';
5
+ import { confirm, prompt } from './chunk-UBN4IUDE.js';
6
+ import { useState, useEffect, useRef, useCallback } from 'react';
7
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
8
+
9
+ var filesDemoTree = null;
10
+ function setFilesDemoTree(tree) {
11
+ filesDemoTree = tree;
12
+ }
13
+ var DEFAULT_SERVER = typeof window !== "undefined" && window.__REACT_OS_SHELL_FILE_SERVER__ || "http://localhost:4000";
14
+ var FILES_VIEW_EVENT = "react-os-shell:files-show-trash";
15
+ function openFilesInTrashMode() {
16
+ if (typeof window === "undefined") return;
17
+ window.__REACT_OS_SHELL_FILES_VIEW__ = "trash";
18
+ window.dispatchEvent(new CustomEvent(FILES_VIEW_EVENT));
19
+ }
20
+ var PREVIEW_EXTS = {
21
+ pdf: "pdf",
22
+ dxf: "dxf",
23
+ csv: "csv",
24
+ jpg: "image",
25
+ jpeg: "image",
26
+ png: "image",
27
+ gif: "image",
28
+ webp: "image",
29
+ svg: "image",
30
+ avif: "image",
31
+ bmp: "image",
32
+ stp: "3d",
33
+ step: "3d",
34
+ stl: "3d",
35
+ obj: "3d",
36
+ gltf: "3d",
37
+ glb: "3d",
38
+ "3mf": "3d",
39
+ iges: "3d",
40
+ igs: "3d",
41
+ ply: "3d",
42
+ fbx: "3d"
43
+ };
44
+ function joinPath(parent, name) {
45
+ if (parent === "/" || parent === "") return "/" + name;
46
+ return parent.replace(/\/$/, "") + "/" + name;
47
+ }
48
+ function parentOf(p) {
49
+ if (p === "/" || p === "") return "/";
50
+ const trimmed = p.replace(/\/$/, "");
51
+ const idx = trimmed.lastIndexOf("/");
52
+ return idx <= 0 ? "/" : trimmed.slice(0, idx);
53
+ }
54
+ function formatSize(bytes) {
55
+ if (!bytes) return "\u2014";
56
+ const units = ["B", "KB", "MB", "GB"];
57
+ let v = bytes, i = 0;
58
+ while (v >= 1024 && i < units.length - 1) {
59
+ v /= 1024;
60
+ i++;
61
+ }
62
+ return `${v < 10 ? v.toFixed(1) : Math.round(v)} ${units[i]}`;
63
+ }
64
+ function formatTime(iso) {
65
+ if (!iso) return "\u2014";
66
+ try {
67
+ const d = new Date(iso);
68
+ if (isNaN(d.getTime())) return "\u2014";
69
+ return d.toLocaleString(void 0, { dateStyle: "short", timeStyle: "short" });
70
+ } catch {
71
+ return iso;
72
+ }
73
+ }
74
+ function demoEntriesAt(dir) {
75
+ if (!filesDemoTree) return [];
76
+ let level = filesDemoTree;
77
+ for (const part of dir.split("/").filter(Boolean)) {
78
+ const node = level.find((n) => n.kind === "folder" && n.name === part);
79
+ if (!node || !node.children) return [];
80
+ level = node.children;
81
+ }
82
+ return level.map((n) => ({
83
+ name: n.name,
84
+ kind: n.kind,
85
+ size: n.size ?? 0,
86
+ modifiedAt: n.modifiedAt ?? ""
87
+ }));
88
+ }
89
+ function Files() {
90
+ const server = DEFAULT_SERVER.replace(/\/$/, "");
91
+ const demoMode = filesDemoTree !== null;
92
+ const [path, setPath] = useState("/");
93
+ const [entries, setEntries] = useState([]);
94
+ const [rootFolders, setRootFolders] = useState([]);
95
+ const [selected, setSelected] = useState(null);
96
+ const [loading, setLoading] = useState(true);
97
+ const [unreachable, setUnreachable] = useState(false);
98
+ const [quota, setQuota] = useState(null);
99
+ const [isDragging, setIsDragging] = useState(false);
100
+ const [view, setView] = useState(() => {
101
+ if (typeof window === "undefined") return "files";
102
+ const w = window;
103
+ if (w.__REACT_OS_SHELL_FILES_VIEW__ === "trash") {
104
+ w.__REACT_OS_SHELL_FILES_VIEW__ = null;
105
+ return "trash";
106
+ }
107
+ return "files";
108
+ });
109
+ const [trash, setTrash] = useState([]);
110
+ useEffect(() => {
111
+ const handler = () => setView("trash");
112
+ window.addEventListener(FILES_VIEW_EVENT, handler);
113
+ return () => window.removeEventListener(FILES_VIEW_EVENT, handler);
114
+ }, []);
115
+ const dragDepthRef = useRef(0);
116
+ const fileRef = useRef(null);
117
+ const { openPage } = useWindowManager();
118
+ const authedFetch = useCallback(
119
+ (url, init = {}) => (
120
+ // `credentials: 'include'` makes the browser send the identity
121
+ // cookie with every cross-origin request, and accept any
122
+ // Set-Cookie response (e.g. the first-visit assignment).
123
+ fetch(url, { ...init, credentials: "include" })
124
+ ),
125
+ []
126
+ );
127
+ const refreshQuota = useCallback(async () => {
128
+ try {
129
+ const res = await authedFetch(`${server}/api/quota`);
130
+ if (res.ok) {
131
+ const q = await res.json();
132
+ setQuota({ used: q.used, limit: q.limit });
133
+ }
134
+ } catch {
135
+ }
136
+ }, [authedFetch, server]);
137
+ const loadDir = useCallback(async (dir) => {
138
+ setSelected(null);
139
+ if (demoMode) {
140
+ const list = demoEntriesAt(dir);
141
+ setEntries(list);
142
+ setPath(dir);
143
+ setUnreachable(false);
144
+ setLoading(false);
145
+ if (dir === "/") setRootFolders(list.filter((e) => e.kind === "folder").map((e) => e.name));
146
+ return;
147
+ }
148
+ setLoading(true);
149
+ try {
150
+ const res = await authedFetch(
151
+ `${server}/api/files?path=${encodeURIComponent(dir)}`
152
+ );
153
+ if (!res.ok) {
154
+ const msg = await res.json().catch(() => ({}));
155
+ toast_default.error(msg.error || `Failed to list (${res.status})`);
156
+ return;
157
+ }
158
+ const data = await res.json();
159
+ const list = data.entries || [];
160
+ setEntries(list);
161
+ setPath(data.path || dir);
162
+ setUnreachable(false);
163
+ if ((data.path || dir) === "/") setRootFolders(list.filter((e) => e.kind === "folder").map((e) => e.name));
164
+ } catch (e) {
165
+ setUnreachable(true);
166
+ } finally {
167
+ setLoading(false);
168
+ }
169
+ refreshQuota();
170
+ }, [authedFetch, server, refreshQuota, demoMode]);
171
+ const loadTrash = useCallback(async () => {
172
+ if (demoMode) {
173
+ setTrash([]);
174
+ setLoading(false);
175
+ return;
176
+ }
177
+ setLoading(true);
178
+ setSelected(null);
179
+ try {
180
+ const res = await authedFetch(`${server}/api/trash`);
181
+ if (!res.ok) {
182
+ const msg = await res.json().catch(() => ({}));
183
+ toast_default.error(msg.error || `Failed to list trash (${res.status})`);
184
+ return;
185
+ }
186
+ const data = await res.json();
187
+ setTrash(data.entries || []);
188
+ setUnreachable(false);
189
+ } catch {
190
+ setUnreachable(true);
191
+ } finally {
192
+ setLoading(false);
193
+ }
194
+ refreshQuota();
195
+ }, [authedFetch, server, refreshQuota, demoMode]);
196
+ useEffect(() => {
197
+ if (view === "files") loadDir(path);
198
+ else loadTrash();
199
+ }, [path, view]);
200
+ const handleRestore = async (entry) => {
201
+ const res = await authedFetch(`${server}/api/trash/restore`, {
202
+ method: "POST",
203
+ headers: { "Content-Type": "application/json" },
204
+ body: JSON.stringify({ id: entry.id })
205
+ });
206
+ if (!res.ok) {
207
+ const msg = await res.json().catch(() => ({}));
208
+ toast_default.error(msg.error || `Restore failed (${res.status})`);
209
+ return;
210
+ }
211
+ toast_default.success(`Restored "${entry.name}"`);
212
+ loadTrash();
213
+ };
214
+ const handleTrashPermanentDelete = async (entry) => {
215
+ const ok = await confirm({
216
+ title: "Permanently delete",
217
+ message: `"${entry.name}" will be permanently removed. This can't be undone.`,
218
+ confirmLabel: "Delete forever",
219
+ variant: "danger"
220
+ });
221
+ if (!ok) return;
222
+ const res = await authedFetch(`${server}/api/trash/${encodeURIComponent(entry.id)}`, {
223
+ method: "DELETE"
224
+ });
225
+ if (!res.ok) {
226
+ const msg = await res.json().catch(() => ({}));
227
+ toast_default.error(msg.error || `Delete failed (${res.status})`);
228
+ return;
229
+ }
230
+ loadTrash();
231
+ };
232
+ const handleEmptyTrash = async () => {
233
+ if (trash.length === 0) return;
234
+ const ok = await confirm({
235
+ title: "Empty trash",
236
+ message: `${trash.length} item${trash.length === 1 ? "" : "s"} will be permanently removed. This can't be undone.`,
237
+ confirmLabel: "Empty trash",
238
+ variant: "danger"
239
+ });
240
+ if (!ok) return;
241
+ const res = await authedFetch(`${server}/api/trash`, { method: "DELETE" });
242
+ if (!res.ok) {
243
+ const msg = await res.json().catch(() => ({}));
244
+ toast_default.error(msg.error || `Empty trash failed (${res.status})`);
245
+ return;
246
+ }
247
+ loadTrash();
248
+ };
249
+ const openFile = async (entry) => {
250
+ if (demoMode) return;
251
+ const fullPath = joinPath(path, entry.name);
252
+ const ext = (entry.name.split(".").pop() || "").toLowerCase();
253
+ const kind = PREVIEW_EXTS[ext];
254
+ if (!kind) {
255
+ downloadFile(entry);
256
+ return;
257
+ }
258
+ await openPreviewFile({
259
+ filePath: fullPath,
260
+ filename: entry.name,
261
+ kind,
262
+ onStaged: (route) => openPage(route)
263
+ });
264
+ };
265
+ const downloadFile = async (entry) => {
266
+ const fullPath = joinPath(path, entry.name);
267
+ try {
268
+ const res = await authedFetch(
269
+ `${server}/api/file?path=${encodeURIComponent(fullPath)}`
270
+ );
271
+ if (!res.ok) {
272
+ toast_default.error(`Download failed (${res.status})`);
273
+ return;
274
+ }
275
+ const blob = await res.blob();
276
+ const url = URL.createObjectURL(blob);
277
+ const a = document.createElement("a");
278
+ a.href = url;
279
+ a.download = entry.name;
280
+ a.click();
281
+ setTimeout(() => URL.revokeObjectURL(url), 1e3);
282
+ } catch (e) {
283
+ toast_default.error(e?.message || "Download failed");
284
+ }
285
+ };
286
+ const handlePick = () => fileRef.current?.click();
287
+ const uploadFiles = async (files) => {
288
+ const arr = Array.from(files);
289
+ for (const file of arr) {
290
+ const form = new FormData();
291
+ form.append("file", file);
292
+ try {
293
+ const res = await authedFetch(
294
+ `${server}/api/upload?path=${encodeURIComponent(path)}`,
295
+ { method: "POST", body: form }
296
+ );
297
+ if (!res.ok) {
298
+ const msg = await res.json().catch(() => ({}));
299
+ if (res.status === 413) {
300
+ const remaining = Math.max(0, (msg.limit || 0) - (msg.used || 0));
301
+ toast_default.error(
302
+ `Quota exceeded \u2014 ${formatSize(remaining)} free, ${file.name} is ${formatSize(msg.attempted || file.size)}`
303
+ );
304
+ } else {
305
+ toast_default.error(`Upload ${file.name}: ${msg.error || res.status}`);
306
+ }
307
+ }
308
+ } catch (e) {
309
+ toast_default.error(`Upload ${file.name}: ${e?.message || "failed"}`);
310
+ }
311
+ }
312
+ loadDir(path);
313
+ };
314
+ const handleNewFolder = async () => {
315
+ const name = await prompt({
316
+ title: "New folder",
317
+ placeholder: "Folder name",
318
+ confirmLabel: "Create"
319
+ });
320
+ if (!name) return;
321
+ if (/[\\/]/.test(name)) {
322
+ toast_default.error("Folder names cannot contain slashes");
323
+ return;
324
+ }
325
+ const target = joinPath(path, name);
326
+ const res = await authedFetch(`${server}/api/folder`, {
327
+ method: "POST",
328
+ headers: { "Content-Type": "application/json" },
329
+ body: JSON.stringify({ path: target })
330
+ });
331
+ if (!res.ok) {
332
+ const msg = await res.json().catch(() => ({}));
333
+ toast_default.error(msg.error || `Create folder failed (${res.status})`);
334
+ return;
335
+ }
336
+ loadDir(path);
337
+ };
338
+ const handleRename = async (entry) => {
339
+ const next = await prompt({
340
+ title: `Rename ${entry.kind}`,
341
+ defaultValue: entry.name,
342
+ confirmLabel: "Rename"
343
+ });
344
+ if (!next || next === entry.name) return;
345
+ if (/[\\/]/.test(next)) {
346
+ toast_default.error("Names cannot contain slashes");
347
+ return;
348
+ }
349
+ const from = joinPath(path, entry.name);
350
+ const to = joinPath(path, next);
351
+ const res = await authedFetch(`${server}/api/rename`, {
352
+ method: "POST",
353
+ headers: { "Content-Type": "application/json" },
354
+ body: JSON.stringify({ from, to })
355
+ });
356
+ if (!res.ok) {
357
+ const msg = await res.json().catch(() => ({}));
358
+ toast_default.error(msg.error || `Rename failed (${res.status})`);
359
+ return;
360
+ }
361
+ loadDir(path);
362
+ };
363
+ const handleDelete = async (entry) => {
364
+ const ok = await confirm({
365
+ title: `Delete ${entry.kind}`,
366
+ message: `"${entry.name}"${entry.kind === "folder" ? " and everything inside" : ""} will be permanently removed.`,
367
+ confirmLabel: "Delete",
368
+ variant: "danger"
369
+ });
370
+ if (!ok) return;
371
+ const target = joinPath(path, entry.name);
372
+ const res = await authedFetch(
373
+ `${server}/api/files?path=${encodeURIComponent(target)}`,
374
+ { method: "DELETE" }
375
+ );
376
+ if (!res.ok) {
377
+ const msg = await res.json().catch(() => ({}));
378
+ toast_default.error(msg.error || `Delete failed (${res.status})`);
379
+ return;
380
+ }
381
+ loadDir(path);
382
+ };
383
+ const resetDrag = () => {
384
+ dragDepthRef.current = 0;
385
+ setIsDragging(false);
386
+ };
387
+ useEffect(() => {
388
+ const reset = () => resetDrag();
389
+ window.addEventListener("dragend", reset);
390
+ window.addEventListener("drop", reset);
391
+ return () => {
392
+ window.removeEventListener("dragend", reset);
393
+ window.removeEventListener("drop", reset);
394
+ };
395
+ }, []);
396
+ if (unreachable) {
397
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col h-full bg-white", children: [
398
+ /* @__PURE__ */ jsx(WindowTitle, { title: "Files - offline" }),
399
+ /* @__PURE__ */ jsx("div", { className: "flex-1 flex items-center justify-center p-8 text-center", children: /* @__PURE__ */ jsxs("div", { className: "max-w-md", children: [
400
+ /* @__PURE__ */ jsxs("svg", { className: "h-12 w-12 mx-auto text-gray-300 mb-3", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.4, children: [
401
+ /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M18.364 5.636l-12.728 12.728M5.636 5.636l12.728 12.728" }),
402
+ /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M12 21a9 9 0 100-18 9 9 0 000 18z" })
403
+ ] }),
404
+ /* @__PURE__ */ jsx("h3", { className: "text-base font-semibold text-gray-800 mb-1", children: "Can't reach the file server" }),
405
+ /* @__PURE__ */ jsxs("p", { className: "text-sm text-gray-500 mb-3", children: [
406
+ "No response from ",
407
+ /* @__PURE__ */ jsx("span", { className: "font-mono", children: server }),
408
+ ". Make sure the server is running \u2014 see ",
409
+ /* @__PURE__ */ jsx("span", { className: "font-mono", children: "examples/file-server/README.md" }),
410
+ "."
411
+ ] }),
412
+ /* @__PURE__ */ jsx("button", { onClick: () => loadDir(path), className: "px-3 py-1.5 text-sm bg-blue-500 text-white rounded hover:bg-blue-600", children: "Retry" })
413
+ ] }) })
414
+ ] });
415
+ }
416
+ const segments = path === "/" ? [] : path.split("/").filter(Boolean);
417
+ const usagePct = quota && quota.limit > 0 ? Math.min(100, Math.round(quota.used / quota.limit * 100)) : 0;
418
+ const usageColor = usagePct > 90 ? "bg-red-500" : usagePct > 75 ? "bg-amber-500" : "bg-blue-500";
419
+ const crumbs = [
420
+ { label: "My files", onClick: () => setPath("/") },
421
+ ...segments.map((seg, i) => ({
422
+ label: seg,
423
+ onClick: () => setPath("/" + segments.slice(0, i + 1).join("/"))
424
+ }))
425
+ ];
426
+ const navBtn = (active) => `flex w-full items-center gap-2 rounded-md px-2.5 py-1.5 text-left text-sm transition-colors ${active ? "bg-blue-50 font-medium text-blue-700" : "text-gray-700 hover:bg-gray-100"}`;
427
+ const navSvg = (d) => /* @__PURE__ */ jsx("svg", { className: "h-4 w-4 shrink-0 text-gray-400", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.6, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d }) });
428
+ const folderNav = /* @__PURE__ */ jsxs("nav", { className: "p-2", children: [
429
+ /* @__PURE__ */ jsx("div", { className: "px-2 py-1.5 text-[11px] font-semibold uppercase tracking-wide text-gray-400", children: "Locations" }),
430
+ /* @__PURE__ */ jsxs("button", { onClick: () => {
431
+ setView("files");
432
+ setPath("/");
433
+ }, className: navBtn(view === "files" && path === "/"), children: [
434
+ navSvg("M2.25 12l8.954-8.955a1.5 1.5 0 012.122 0L21.75 12M4.5 9.75v9.75a.75.75 0 00.75.75H9V15a.75.75 0 01.75-.75h4.5A.75.75 0 0115 15v5.25h3.75a.75.75 0 00.75-.75V9.75"),
435
+ "My files"
436
+ ] }),
437
+ rootFolders.map((name) => /* @__PURE__ */ jsxs("button", { onClick: () => {
438
+ setView("files");
439
+ setPath("/" + name);
440
+ }, className: navBtn(view === "files" && path === "/" + name), children: [
441
+ navSvg("M2.25 12.75V12A2.25 2.25 0 014.5 9.75h15A2.25 2.25 0 0121.75 12v.75m-8.69-6.44l-2.12-2.12a1.5 1.5 0 00-1.061-.44H4.5A2.25 2.25 0 002.25 6v12a2.25 2.25 0 002.25 2.25h15A2.25 2.25 0 0021.75 18V9a2.25 2.25 0 00-2.25-2.25h-5.379a1.5 1.5 0 01-1.06-.44z"),
442
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: name })
443
+ ] }, name)),
444
+ !demoMode && /* @__PURE__ */ jsxs("button", { onClick: () => setView("trash"), className: navBtn(view === "trash"), children: [
445
+ navSvg("M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79"),
446
+ "Trash"
447
+ ] })
448
+ ] });
449
+ return /* @__PURE__ */ jsxs(
450
+ "div",
451
+ {
452
+ className: "relative flex flex-col h-full bg-white",
453
+ onDragEnter: (e) => {
454
+ if (demoMode) return;
455
+ if (!e.dataTransfer?.types?.includes?.("Files")) return;
456
+ e.preventDefault();
457
+ dragDepthRef.current++;
458
+ if (!isDragging) setIsDragging(true);
459
+ },
460
+ onDragOver: (e) => {
461
+ if (!e.dataTransfer?.types?.includes?.("Files")) return;
462
+ e.preventDefault();
463
+ },
464
+ onDragLeave: () => {
465
+ if (dragDepthRef.current > 0) dragDepthRef.current--;
466
+ if (dragDepthRef.current === 0) setIsDragging(false);
467
+ },
468
+ onDrop: (e) => {
469
+ e.preventDefault();
470
+ resetDrag();
471
+ if (demoMode) return;
472
+ if (e.dataTransfer.files?.length) uploadFiles(e.dataTransfer.files);
473
+ },
474
+ children: [
475
+ /* @__PURE__ */ jsx(WindowTitle, { title: `Files${path === "/" ? "" : " - " + path}` }),
476
+ /* @__PURE__ */ jsx(
477
+ "input",
478
+ {
479
+ ref: fileRef,
480
+ type: "file",
481
+ multiple: true,
482
+ className: "hidden",
483
+ onChange: (e) => {
484
+ if (e.target.files?.length) uploadFiles(e.target.files);
485
+ if (fileRef.current) fileRef.current.value = "";
486
+ }
487
+ }
488
+ ),
489
+ /* @__PURE__ */ jsx("div", { className: "flex-1 min-h-0", children: /* @__PURE__ */ jsx(
490
+ SidebarLayout,
491
+ {
492
+ sidebar: folderNav,
493
+ storageKey: "files.sidebarWidth",
494
+ defaultWidth: 200,
495
+ minWidth: 150,
496
+ maxWidth: 320,
497
+ children: /* @__PURE__ */ jsxs("div", { className: "flex h-full min-w-0 flex-col", children: [
498
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 px-2 py-1.5 border-b border-gray-200 bg-gray-50 shrink-0 text-xs", children: [
499
+ view === "files" ? /* @__PURE__ */ jsxs(Fragment, { children: [
500
+ /* @__PURE__ */ jsx(
501
+ "button",
502
+ {
503
+ onClick: () => setPath(parentOf(path)),
504
+ disabled: path === "/",
505
+ className: "px-2 py-1 rounded hover:bg-gray-200 disabled:opacity-30 text-gray-600",
506
+ title: "Parent folder",
507
+ children: /* @__PURE__ */ jsx("svg", { className: "h-3.5 w-3.5", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M10.5 19.5L3 12m0 0l7.5-7.5M3 12h18" }) })
508
+ }
509
+ ),
510
+ /* @__PURE__ */ jsx("div", { className: "flex-1 min-w-0", children: /* @__PURE__ */ jsx(Breadcrumbs, { items: crumbs, maxItems: 5 }) }),
511
+ /* @__PURE__ */ jsx("button", { onClick: () => loadDir(path), className: "px-2 py-1 rounded hover:bg-gray-200 text-gray-600", title: "Refresh", children: /* @__PURE__ */ jsx("svg", { className: "h-3.5 w-3.5", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.8, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M16.023 9.348h4.992V4.356M2.985 19.644v-4.992h4.992M3.05 9.348a9 9 0 0114.85-3.36L21.015 9.348m0 5.304a9 9 0 01-14.85 3.36l-3.115-3.36" }) }) }),
512
+ !demoMode && /* @__PURE__ */ jsxs(Fragment, { children: [
513
+ /* @__PURE__ */ jsxs("button", { onClick: () => setView("trash"), className: "px-2 py-1 rounded hover:bg-gray-200 text-gray-600 flex items-center gap-1", title: "Open trash", children: [
514
+ /* @__PURE__ */ jsx("svg", { className: "h-3.5 w-3.5", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.5, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0" }) }),
515
+ "Trash"
516
+ ] }),
517
+ /* @__PURE__ */ jsxs("button", { onClick: handleNewFolder, className: "px-2 py-1 rounded hover:bg-gray-200 text-gray-600 flex items-center gap-1", children: [
518
+ /* @__PURE__ */ jsx("svg", { className: "h-3.5 w-3.5", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.5, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M12 10.5v6m3-3H9m4.06-7.19l-2.12-2.12a1.5 1.5 0 00-1.061-.44H4.5A2.25 2.25 0 002.25 6v12a2.25 2.25 0 002.25 2.25h15A2.25 2.25 0 0021.75 18V9a2.25 2.25 0 00-2.25-2.25h-5.379a1.5 1.5 0 01-1.06-.44z" }) }),
519
+ "New Folder"
520
+ ] }),
521
+ /* @__PURE__ */ jsxs("button", { onClick: handlePick, className: "px-2 py-1 rounded bg-blue-500 text-white hover:bg-blue-600 flex items-center gap-1", children: [
522
+ /* @__PURE__ */ jsx("svg", { className: "h-3.5 w-3.5", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.8, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5M7.5 7.5L12 3m0 0l4.5 4.5M12 3v13.5" }) }),
523
+ "Upload"
524
+ ] })
525
+ ] })
526
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
527
+ /* @__PURE__ */ jsx(
528
+ "button",
529
+ {
530
+ onClick: () => setView("files"),
531
+ className: "px-2 py-1 rounded hover:bg-gray-200 text-gray-600",
532
+ title: "Back to files",
533
+ children: /* @__PURE__ */ jsx("svg", { className: "h-3.5 w-3.5", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M10.5 19.5L3 12m0 0l7.5-7.5M3 12h18" }) })
534
+ }
535
+ ),
536
+ /* @__PURE__ */ jsx("span", { className: "flex-1 text-gray-700 font-medium px-2", children: "Trash" }),
537
+ /* @__PURE__ */ jsx("button", { onClick: loadTrash, className: "px-2 py-1 rounded hover:bg-gray-200 text-gray-600", title: "Refresh", children: /* @__PURE__ */ jsx("svg", { className: "h-3.5 w-3.5", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.8, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M16.023 9.348h4.992V4.356M2.985 19.644v-4.992h4.992M3.05 9.348a9 9 0 0114.85-3.36L21.015 9.348m0 5.304a9 9 0 01-14.85 3.36l-3.115-3.36" }) }) }),
538
+ /* @__PURE__ */ jsx(
539
+ "button",
540
+ {
541
+ onClick: handleEmptyTrash,
542
+ disabled: trash.length === 0,
543
+ className: "px-2 py-1 rounded text-red-600 hover:bg-red-50 disabled:opacity-30 flex items-center gap-1",
544
+ children: "Empty trash"
545
+ }
546
+ )
547
+ ] }),
548
+ quota && /* @__PURE__ */ jsxs(Fragment, { children: [
549
+ /* @__PURE__ */ jsx("div", { className: "h-4 w-px bg-gray-300 mx-1" }),
550
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", title: `${formatSize(quota.used)} of ${formatSize(quota.limit)} used`, children: [
551
+ /* @__PURE__ */ jsx("div", { className: "w-20 h-1.5 rounded-full bg-gray-200 overflow-hidden", children: /* @__PURE__ */ jsx(
552
+ "div",
553
+ {
554
+ className: `h-full ${usageColor} transition-all`,
555
+ style: { width: `${usagePct}%` }
556
+ }
557
+ ) }),
558
+ /* @__PURE__ */ jsxs("span", { className: "text-[10px] text-gray-500 tabular-nums whitespace-nowrap", children: [
559
+ formatSize(quota.used),
560
+ " / ",
561
+ formatSize(quota.limit)
562
+ ] })
563
+ ] })
564
+ ] })
565
+ ] }),
566
+ /* @__PURE__ */ jsx("div", { className: "flex-1 overflow-auto", children: view === "trash" ? loading && trash.length === 0 ? /* @__PURE__ */ jsx("div", { className: "p-6 text-center text-sm text-gray-400", children: "Loading\u2026" }) : trash.length === 0 ? /* @__PURE__ */ jsx("div", { className: "p-10 text-center text-sm text-gray-400", children: "Trash is empty." }) : /* @__PURE__ */ jsxs("table", { className: "w-full text-sm", children: [
567
+ /* @__PURE__ */ jsx("thead", { className: "bg-gray-50 text-[11px] uppercase tracking-wide text-gray-500", children: /* @__PURE__ */ jsxs("tr", { children: [
568
+ /* @__PURE__ */ jsx("th", { className: "text-left font-medium px-3 py-1.5", children: "Name" }),
569
+ /* @__PURE__ */ jsx("th", { className: "text-left font-medium px-3 py-1.5", children: "Original location" }),
570
+ /* @__PURE__ */ jsx("th", { className: "text-right font-medium px-3 py-1.5 w-24", children: "Size" }),
571
+ /* @__PURE__ */ jsx("th", { className: "text-right font-medium px-3 py-1.5 w-40", children: "Deleted" }),
572
+ /* @__PURE__ */ jsx("th", { className: "w-44" })
573
+ ] }) }),
574
+ /* @__PURE__ */ jsx("tbody", { children: trash.map((e) => /* @__PURE__ */ jsxs("tr", { className: "border-b border-gray-100 hover:bg-gray-50", children: [
575
+ /* @__PURE__ */ jsxs("td", { className: "px-3 py-1.5 flex items-center gap-2", children: [
576
+ e.kind === "folder" ? /* @__PURE__ */ jsx("svg", { className: "h-4 w-4 text-amber-500 shrink-0", fill: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx("path", { d: "M2.25 7.125A2.25 2.25 0 014.5 4.875h4.504c.61 0 1.193.243 1.624.673l1.494 1.494a.75.75 0 00.53.22h7.098A2.25 2.25 0 0122 9.51v8.366A2.25 2.25 0 0119.75 20.125H4.25A2.25 2.25 0 012 17.875V7.125z" }) }) : /* @__PURE__ */ jsx("svg", { className: "h-4 w-4 text-gray-400 shrink-0", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.5, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m2.25 0H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z" }) }),
577
+ /* @__PURE__ */ jsx("span", { className: "truncate", title: e.name, children: e.name })
578
+ ] }),
579
+ /* @__PURE__ */ jsx("td", { className: "px-3 py-1.5 text-gray-500 text-xs font-mono truncate", title: e.originalPath, children: e.originalPath || "\u2014" }),
580
+ /* @__PURE__ */ jsx("td", { className: "px-3 py-1.5 text-right tabular-nums text-gray-500", children: formatSize(e.size) }),
581
+ /* @__PURE__ */ jsx("td", { className: "px-3 py-1.5 text-right text-gray-500 tabular-nums", children: e.deletedAt ? formatTime(e.deletedAt) : "\u2014" }),
582
+ /* @__PURE__ */ jsxs("td", { className: "px-3 py-1.5 text-right whitespace-nowrap", children: [
583
+ /* @__PURE__ */ jsx(
584
+ "button",
585
+ {
586
+ onClick: () => handleRestore(e),
587
+ className: "px-1.5 py-0.5 rounded hover:bg-gray-200 text-blue-600 text-[11px]",
588
+ children: "Restore"
589
+ }
590
+ ),
591
+ /* @__PURE__ */ jsx(
592
+ "button",
593
+ {
594
+ onClick: () => handleTrashPermanentDelete(e),
595
+ className: "px-1.5 py-0.5 rounded hover:bg-red-100 text-red-600 text-[11px] ml-1",
596
+ children: "Delete forever"
597
+ }
598
+ )
599
+ ] })
600
+ ] }, e.id)) })
601
+ ] }) : loading && entries.length === 0 ? /* @__PURE__ */ jsx("div", { className: "p-6 text-center text-sm text-gray-400", children: "Loading\u2026" }) : entries.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "p-10 text-center text-sm text-gray-400", children: [
602
+ "Empty folder.",
603
+ !demoMode && /* @__PURE__ */ jsxs(Fragment, { children: [
604
+ " Drop files here or click ",
605
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: "Upload" }),
606
+ "."
607
+ ] })
608
+ ] }) : /* @__PURE__ */ jsxs("table", { className: "w-full text-sm", children: [
609
+ /* @__PURE__ */ jsx("thead", { className: "bg-gray-50 text-[11px] uppercase tracking-wide text-gray-500", children: /* @__PURE__ */ jsxs("tr", { children: [
610
+ /* @__PURE__ */ jsx("th", { className: "text-left font-medium px-3 py-1.5", children: "Name" }),
611
+ /* @__PURE__ */ jsx("th", { className: "text-right font-medium px-3 py-1.5 w-24", children: "Size" }),
612
+ /* @__PURE__ */ jsx("th", { className: "text-right font-medium px-3 py-1.5 w-40", children: "Modified" }),
613
+ /* @__PURE__ */ jsx("th", { className: "w-32" })
614
+ ] }) }),
615
+ /* @__PURE__ */ jsx("tbody", { children: entries.map((e) => /* @__PURE__ */ jsxs(
616
+ "tr",
617
+ {
618
+ onClick: () => setSelected(e.name),
619
+ onDoubleClick: () => {
620
+ if (e.kind === "folder") setPath(joinPath(path, e.name));
621
+ else openFile(e);
622
+ },
623
+ className: `cursor-default border-b border-gray-100 ${selected === e.name ? "bg-blue-50" : "hover:bg-gray-50"}`,
624
+ children: [
625
+ /* @__PURE__ */ jsxs("td", { className: "px-3 py-1.5 flex items-center gap-2", children: [
626
+ e.kind === "folder" ? /* @__PURE__ */ jsx("svg", { className: "h-4 w-4 text-amber-500 shrink-0", fill: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx("path", { d: "M2.25 7.125A2.25 2.25 0 014.5 4.875h4.504c.61 0 1.193.243 1.624.673l1.494 1.494a.75.75 0 00.53.22h7.098A2.25 2.25 0 0122 9.51v8.366A2.25 2.25 0 0119.75 20.125H4.25A2.25 2.25 0 012 17.875V7.125z" }) }) : /* @__PURE__ */ jsx("svg", { className: "h-4 w-4 text-gray-400 shrink-0", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.5, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m2.25 0H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z" }) }),
627
+ /* @__PURE__ */ jsx("span", { className: "truncate", title: e.name, children: e.name })
628
+ ] }),
629
+ /* @__PURE__ */ jsx("td", { className: "px-3 py-1.5 text-right tabular-nums text-gray-500", children: e.kind === "folder" ? "\u2014" : formatSize(e.size) }),
630
+ /* @__PURE__ */ jsx("td", { className: "px-3 py-1.5 text-right text-gray-500 tabular-nums", children: formatTime(e.modifiedAt) }),
631
+ /* @__PURE__ */ jsx("td", { className: "px-3 py-1.5 text-right whitespace-nowrap", children: !demoMode && /* @__PURE__ */ jsxs(Fragment, { children: [
632
+ e.kind === "file" && /* @__PURE__ */ jsx(
633
+ "button",
634
+ {
635
+ onClick: (ev) => {
636
+ ev.stopPropagation();
637
+ downloadFile(e);
638
+ },
639
+ className: "px-1.5 py-0.5 rounded hover:bg-gray-200 text-gray-500 text-[11px]",
640
+ children: "Download"
641
+ }
642
+ ),
643
+ /* @__PURE__ */ jsx(
644
+ "button",
645
+ {
646
+ onClick: (ev) => {
647
+ ev.stopPropagation();
648
+ handleRename(e);
649
+ },
650
+ className: "px-1.5 py-0.5 rounded hover:bg-gray-200 text-gray-500 text-[11px] ml-1",
651
+ children: "Rename"
652
+ }
653
+ ),
654
+ /* @__PURE__ */ jsx(
655
+ "button",
656
+ {
657
+ onClick: (ev) => {
658
+ ev.stopPropagation();
659
+ handleDelete(e);
660
+ },
661
+ className: "px-1.5 py-0.5 rounded hover:bg-red-100 text-red-600 text-[11px] ml-1",
662
+ children: "Delete"
663
+ }
664
+ )
665
+ ] }) })
666
+ ]
667
+ },
668
+ e.name
669
+ )) })
670
+ ] }) })
671
+ ] })
672
+ }
673
+ ) }),
674
+ isDragging && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-blue-500/15 border-4 border-dashed border-blue-500 pointer-events-none flex items-center justify-center z-20", children: /* @__PURE__ */ jsx("div", { className: "px-4 py-2 rounded-md bg-blue-600 text-white text-sm font-medium shadow-lg", children: "Drop to upload" }) })
675
+ ]
676
+ }
677
+ );
678
+ }
679
+
680
+ export { Files, openFilesInTrashMode, setFilesDemoTree };
681
+ //# sourceMappingURL=chunk-UXEG2NRI.js.map
682
+ //# sourceMappingURL=chunk-UXEG2NRI.js.map