s3kit 0.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 (56) hide show
  1. package/README.md +398 -0
  2. package/dist/adapters/express.cjs +305 -0
  3. package/dist/adapters/express.cjs.map +1 -0
  4. package/dist/adapters/express.d.cts +10 -0
  5. package/dist/adapters/express.d.ts +10 -0
  6. package/dist/adapters/express.js +278 -0
  7. package/dist/adapters/express.js.map +1 -0
  8. package/dist/adapters/fetch.cjs +298 -0
  9. package/dist/adapters/fetch.cjs.map +1 -0
  10. package/dist/adapters/fetch.d.cts +9 -0
  11. package/dist/adapters/fetch.d.ts +9 -0
  12. package/dist/adapters/fetch.js +271 -0
  13. package/dist/adapters/fetch.js.map +1 -0
  14. package/dist/adapters/next.cjs +796 -0
  15. package/dist/adapters/next.cjs.map +1 -0
  16. package/dist/adapters/next.d.cts +28 -0
  17. package/dist/adapters/next.d.ts +28 -0
  18. package/dist/adapters/next.js +775 -0
  19. package/dist/adapters/next.js.map +1 -0
  20. package/dist/client/index.cjs +153 -0
  21. package/dist/client/index.cjs.map +1 -0
  22. package/dist/client/index.d.cts +59 -0
  23. package/dist/client/index.d.ts +59 -0
  24. package/dist/client/index.js +126 -0
  25. package/dist/client/index.js.map +1 -0
  26. package/dist/core/index.cjs +452 -0
  27. package/dist/core/index.cjs.map +1 -0
  28. package/dist/core/index.d.cts +11 -0
  29. package/dist/core/index.d.ts +11 -0
  30. package/dist/core/index.js +430 -0
  31. package/dist/core/index.js.map +1 -0
  32. package/dist/http/index.cjs +270 -0
  33. package/dist/http/index.cjs.map +1 -0
  34. package/dist/http/index.d.cts +49 -0
  35. package/dist/http/index.d.ts +49 -0
  36. package/dist/http/index.js +243 -0
  37. package/dist/http/index.js.map +1 -0
  38. package/dist/index.cjs +808 -0
  39. package/dist/index.cjs.map +1 -0
  40. package/dist/index.d.cts +6 -0
  41. package/dist/index.d.ts +6 -0
  42. package/dist/index.js +784 -0
  43. package/dist/index.js.map +1 -0
  44. package/dist/manager-BbmXpgXN.d.ts +29 -0
  45. package/dist/manager-gIjo-t8h.d.cts +29 -0
  46. package/dist/react/index.cjs +4320 -0
  47. package/dist/react/index.cjs.map +1 -0
  48. package/dist/react/index.css +155 -0
  49. package/dist/react/index.css.map +1 -0
  50. package/dist/react/index.d.cts +79 -0
  51. package/dist/react/index.d.ts +79 -0
  52. package/dist/react/index.js +4315 -0
  53. package/dist/react/index.js.map +1 -0
  54. package/dist/types-g2IYvH3O.d.cts +123 -0
  55. package/dist/types-g2IYvH3O.d.ts +123 -0
  56. package/package.json +100 -0
@@ -0,0 +1,4320 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/react/index.ts
21
+ var react_exports = {};
22
+ __export(react_exports, {
23
+ FileManager: () => FileManager,
24
+ FilePicker: () => FilePicker
25
+ });
26
+ module.exports = __toCommonJS(react_exports);
27
+
28
+ // src/react/FileManager.tsx
29
+ var import_react6 = require("react");
30
+
31
+ // src/client/client.ts
32
+ function normalizeBasePath(basePath) {
33
+ if (!basePath) return "";
34
+ if (basePath === "/") return "";
35
+ return basePath.startsWith("/") ? basePath.replace(/\/+$/, "") : `/${basePath.replace(/\/+$/, "")}`;
36
+ }
37
+ function normalizeApiRootFromParts(baseUrl, basePath) {
38
+ const b = baseUrl.replace(/\/+$/, "");
39
+ const p = normalizeBasePath(basePath);
40
+ return p ? `${b}${p}` : b;
41
+ }
42
+ async function fetchJson(f, url, body) {
43
+ const res = await f(url, {
44
+ method: "POST",
45
+ headers: { "content-type": "application/json" },
46
+ body: JSON.stringify(body)
47
+ });
48
+ if (!res.ok) {
49
+ const text = await res.text().catch(() => "");
50
+ throw new Error(text || `Request failed: ${res.status}`);
51
+ }
52
+ if (res.status === 204) return void 0;
53
+ return await res.json();
54
+ }
55
+ function uploadWithXhr(upload, file, hooks) {
56
+ return new Promise((resolve, reject) => {
57
+ const xhr = new XMLHttpRequest();
58
+ xhr.open(upload.method, upload.url);
59
+ for (const [k, v] of Object.entries(upload.headers ?? {})) {
60
+ xhr.setRequestHeader(k, v);
61
+ }
62
+ xhr.upload.onprogress = (evt) => {
63
+ hooks?.onUploadProgress?.({
64
+ path: upload.path,
65
+ loaded: evt.loaded,
66
+ total: evt.total
67
+ });
68
+ };
69
+ xhr.onload = async () => {
70
+ if (xhr.status >= 200 && xhr.status < 300) {
71
+ await hooks?.onUploadComplete?.({ path: upload.path });
72
+ resolve();
73
+ return;
74
+ }
75
+ const responseText = xhr.responseText ? ` - ${xhr.responseText.slice(0, 200)}` : "";
76
+ const error = new Error(`Upload failed: ${xhr.status}${responseText}`);
77
+ await hooks?.onUploadError?.({ path: upload.path, error });
78
+ reject(error);
79
+ };
80
+ xhr.onerror = async () => {
81
+ const error = new Error("Upload failed: network error");
82
+ await hooks?.onUploadError?.({ path: upload.path, error });
83
+ reject(error);
84
+ };
85
+ xhr.send(file);
86
+ });
87
+ }
88
+ var S3FileManagerClient = class {
89
+ apiRoot;
90
+ f;
91
+ constructor(options) {
92
+ this.apiRoot = "apiUrl" in options ? options.apiUrl.replace(/\/+$/, "") : normalizeApiRootFromParts(options.baseUrl, options.basePath);
93
+ this.f = options.fetch ?? fetch;
94
+ }
95
+ endpoint(path) {
96
+ const p = path.startsWith("/") ? path : `/${path}`;
97
+ return `${this.apiRoot}${p}`;
98
+ }
99
+ list(options) {
100
+ return fetchJson(this.f, this.endpoint("/list"), options);
101
+ }
102
+ search(options) {
103
+ return fetchJson(this.f, this.endpoint("/search"), options);
104
+ }
105
+ createFolder(options) {
106
+ return fetchJson(this.f, this.endpoint("/folder/create"), options);
107
+ }
108
+ deleteFolder(options) {
109
+ return fetchJson(this.f, this.endpoint("/folder/delete"), options);
110
+ }
111
+ deleteFiles(options) {
112
+ return fetchJson(this.f, this.endpoint("/files/delete"), options);
113
+ }
114
+ copy(options) {
115
+ return fetchJson(this.f, this.endpoint("/files/copy"), options);
116
+ }
117
+ move(options) {
118
+ return fetchJson(this.f, this.endpoint("/files/move"), options);
119
+ }
120
+ prepareUploads(options) {
121
+ return fetchJson(this.f, this.endpoint("/upload/prepare"), options);
122
+ }
123
+ getPreviewUrl(options) {
124
+ return fetchJson(this.f, this.endpoint("/preview"), options);
125
+ }
126
+ async uploadFiles(args) {
127
+ const prepare = {
128
+ items: args.files.map((f) => ({
129
+ path: f.path,
130
+ contentType: f.contentType ?? f.file.type
131
+ })),
132
+ ...args.expiresInSeconds !== void 0 ? { expiresInSeconds: args.expiresInSeconds } : {}
133
+ };
134
+ const uploads = await this.prepareUploads(prepare);
135
+ const byPath = new Map(uploads.map((u) => [u.path, u]));
136
+ const tasks = args.files.map(({ file, path }) => {
137
+ const upload = byPath.get(path);
138
+ if (!upload) throw new Error(`Missing presigned upload for: ${path}`);
139
+ return () => uploadWithXhr(upload, file, args.hooks);
140
+ });
141
+ const parallel = Math.max(1, args.parallel ?? 4);
142
+ let i = 0;
143
+ const runWorker = async () => {
144
+ while (true) {
145
+ const idx = i++;
146
+ if (idx >= tasks.length) return;
147
+ await tasks[idx]();
148
+ }
149
+ };
150
+ await Promise.all(Array.from({ length: Math.min(parallel, tasks.length) }, runWorker));
151
+ }
152
+ };
153
+
154
+ // src/react/FileManager.tsx
155
+ var import_react7 = require("@phosphor-icons/react");
156
+ var import_react8 = require("@base-ui/react");
157
+ var import_react9 = require("@base-ui/react");
158
+ var import_react10 = require("@base-ui/react");
159
+
160
+ // src/react/FileManager.module.css
161
+ var FileManager_default = {};
162
+
163
+ // src/react/components/Button.tsx
164
+ var import_react = require("@base-ui/react");
165
+ var import_jsx_runtime = require("react/jsx-runtime");
166
+ function Button({
167
+ children,
168
+ variant = "secondary",
169
+ onClick,
170
+ style,
171
+ theme,
172
+ disabled = false
173
+ }) {
174
+ const baseStyle = {
175
+ display: "inline-flex",
176
+ alignItems: "center",
177
+ gap: 8,
178
+ padding: "8px 16px",
179
+ border: "1px solid transparent",
180
+ cursor: disabled ? "not-allowed" : "pointer",
181
+ fontSize: 13,
182
+ fontWeight: 500,
183
+ transition: "all 0.15s",
184
+ opacity: disabled ? 0.6 : 1,
185
+ ...style
186
+ };
187
+ const variants = {
188
+ primary: {
189
+ backgroundColor: theme.accent,
190
+ color: theme.bg,
191
+ borderColor: theme.accent
192
+ },
193
+ secondary: {
194
+ backgroundColor: theme.bg,
195
+ color: theme.text,
196
+ borderColor: theme.border
197
+ },
198
+ danger: {
199
+ backgroundColor: theme.bg,
200
+ color: theme.danger,
201
+ borderColor: theme.danger
202
+ }
203
+ };
204
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
205
+ import_react.Button,
206
+ {
207
+ type: "button",
208
+ style: { ...baseStyle, ...variants[variant] },
209
+ onClick: disabled ? void 0 : onClick,
210
+ disabled,
211
+ children
212
+ }
213
+ );
214
+ }
215
+
216
+ // src/react/components/Modal.tsx
217
+ var import_react2 = require("@base-ui/react");
218
+
219
+ // src/react/components/UiIcon.tsx
220
+ var import_jsx_runtime2 = require("react/jsx-runtime");
221
+ function UiIcon(props) {
222
+ const { icon: Icon, size, weight, color, boxed = false, boxStyle, iconStyle } = props;
223
+ const iconProps = {
224
+ size,
225
+ style: { display: "block", ...iconStyle },
226
+ ...weight !== void 0 ? { weight } : {},
227
+ ...color !== void 0 ? { color } : {}
228
+ };
229
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
230
+ "span",
231
+ {
232
+ style: {
233
+ width: size,
234
+ height: size,
235
+ display: "inline-flex",
236
+ alignItems: "center",
237
+ justifyContent: "center",
238
+ flexShrink: 0,
239
+ lineHeight: 0,
240
+ verticalAlign: "middle",
241
+ ...boxed ? {
242
+ backgroundColor: "var(--s3kit-icon-bg, transparent)",
243
+ border: "1px solid var(--s3kit-icon-border, transparent)",
244
+ borderRadius: "var(--s3kit-icon-radius, 8px)",
245
+ boxSizing: "border-box"
246
+ } : {},
247
+ ...boxStyle
248
+ },
249
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Icon, { ...iconProps })
250
+ }
251
+ );
252
+ }
253
+
254
+ // src/react/components/Modal.tsx
255
+ var import_react3 = require("@phosphor-icons/react");
256
+ var import_jsx_runtime3 = require("react/jsx-runtime");
257
+ function Modal({
258
+ open,
259
+ onClose,
260
+ title,
261
+ children,
262
+ theme,
263
+ portalContainer,
264
+ closeDisabled = false
265
+ }) {
266
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
267
+ import_react2.Dialog.Root,
268
+ {
269
+ open,
270
+ onOpenChange: (open2) => {
271
+ if (!open2 && !closeDisabled) onClose();
272
+ },
273
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react2.Dialog.Portal, { container: portalContainer, children: [
274
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
275
+ import_react2.Dialog.Backdrop,
276
+ {
277
+ style: {
278
+ position: "fixed",
279
+ inset: 0,
280
+ zIndex: 100,
281
+ backgroundColor: theme.bg === "#ffffff" ? "rgba(255,255,255,0.8)" : "rgba(0,0,0,0.8)",
282
+ backdropFilter: "blur(2px)",
283
+ display: "flex",
284
+ alignItems: "center",
285
+ justifyContent: "center"
286
+ }
287
+ }
288
+ ),
289
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
290
+ import_react2.Dialog.Popup,
291
+ {
292
+ style: {
293
+ position: "fixed",
294
+ top: "50%",
295
+ left: "50%",
296
+ transform: "translate(-50%, -50%)",
297
+ zIndex: 101,
298
+ width: "100%",
299
+ maxWidth: 400,
300
+ backgroundColor: theme.bg,
301
+ border: `1px solid ${theme.border}`,
302
+ boxShadow: "0 10px 40px rgba(0,0,0,0.1)",
303
+ outline: "none"
304
+ },
305
+ children: [
306
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
307
+ "div",
308
+ {
309
+ style: {
310
+ display: "flex",
311
+ alignItems: "center",
312
+ justifyContent: "space-between",
313
+ padding: "16px 20px",
314
+ borderBottom: `1px solid ${theme.border}`
315
+ },
316
+ children: [
317
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
318
+ import_react2.Dialog.Title,
319
+ {
320
+ style: {
321
+ margin: 0,
322
+ fontSize: 13,
323
+ fontWeight: 600,
324
+ textTransform: "uppercase",
325
+ letterSpacing: "0.05em",
326
+ color: theme.text
327
+ },
328
+ children: title
329
+ }
330
+ ),
331
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
332
+ import_react2.Dialog.Close,
333
+ {
334
+ disabled: closeDisabled,
335
+ style: {
336
+ background: "none",
337
+ border: "none",
338
+ cursor: closeDisabled ? "not-allowed" : "pointer",
339
+ padding: 4,
340
+ display: "flex",
341
+ alignItems: "center",
342
+ justifyContent: "center",
343
+ color: theme.text,
344
+ opacity: closeDisabled ? 0.5 : 1
345
+ },
346
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(UiIcon, { icon: import_react3.X, size: 18 })
347
+ }
348
+ )
349
+ ]
350
+ }
351
+ ),
352
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { padding: 24 }, children })
353
+ ]
354
+ }
355
+ )
356
+ ] })
357
+ }
358
+ );
359
+ }
360
+
361
+ // src/react/components/FileIcons.tsx
362
+ var import_react4 = require("@phosphor-icons/react");
363
+ var import_jsx_runtime4 = require("react/jsx-runtime");
364
+ function getFileIcon(filename, size = 20) {
365
+ const ext = filename.split(".").pop()?.toLowerCase();
366
+ const weight = "light";
367
+ if (["jpg", "jpeg", "png", "gif", "webp", "svg"].includes(ext || "")) {
368
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(UiIcon, { icon: import_react4.Image, size, weight, boxed: true });
369
+ }
370
+ if (["mp4", "webm", "mov"].includes(ext || "")) {
371
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(UiIcon, { icon: import_react4.FilmStrip, size, weight, boxed: true });
372
+ }
373
+ if (["mp3", "wav", "ogg"].includes(ext || "")) {
374
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(UiIcon, { icon: import_react4.MusicNote, size, weight, boxed: true });
375
+ }
376
+ if (["js", "ts", "jsx", "tsx", "json", "html", "css"].includes(ext || "")) {
377
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(UiIcon, { icon: import_react4.Code, size, weight, boxed: true });
378
+ }
379
+ if (["pdf", "txt", "md", "doc", "docx"].includes(ext || "")) {
380
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(UiIcon, { icon: import_react4.FileText, size, weight, boxed: true });
381
+ }
382
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(UiIcon, { icon: import_react4.File, size, weight, boxed: true });
383
+ }
384
+
385
+ // src/react/theme/fileManagerTheme.ts
386
+ var import_react5 = require("react");
387
+ var lightTheme = {
388
+ bg: "#ffffff",
389
+ bgSecondary: "#fafafa",
390
+ text: "#111111",
391
+ textSecondary: "#666666",
392
+ border: "#eaeaea",
393
+ accent: "#000000",
394
+ hover: "#f4f4f4",
395
+ selected: "#eeeeee",
396
+ danger: "#d32f2f"
397
+ };
398
+ var darkTheme = {
399
+ bg: "#0a0a0a",
400
+ bgSecondary: "#1a1a1a",
401
+ text: "#ffffff",
402
+ textSecondary: "#a1a1aa",
403
+ border: "#27272a",
404
+ accent: "#ffffff",
405
+ hover: "#18181b",
406
+ selected: "#27272a",
407
+ danger: "#ef4444"
408
+ };
409
+ function useTheme(themeMode = "light") {
410
+ const [systemTheme, setSystemTheme] = (0, import_react5.useState)("light");
411
+ (0, import_react5.useEffect)(() => {
412
+ if (themeMode === "system") {
413
+ const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
414
+ setSystemTheme(mediaQuery.matches ? "dark" : "light");
415
+ const handleChange = (e) => {
416
+ setSystemTheme(e.matches ? "dark" : "light");
417
+ };
418
+ mediaQuery.addEventListener("change", handleChange);
419
+ return () => mediaQuery.removeEventListener("change", handleChange);
420
+ }
421
+ }, [themeMode]);
422
+ const activeTheme = themeMode === "system" ? systemTheme : themeMode;
423
+ return activeTheme === "dark" ? darkTheme : lightTheme;
424
+ }
425
+
426
+ // src/react/utils/fileManagerConstants.ts
427
+ var TRASH_PATH = ".trash";
428
+
429
+ // src/react/utils/fileManagerFilters.ts
430
+ var MIME_EXTENSION_MAP = {
431
+ "image/jpeg": ["jpg", "jpeg"],
432
+ "image/png": ["png"],
433
+ "image/gif": ["gif"],
434
+ "image/webp": ["webp"],
435
+ "image/svg+xml": ["svg"],
436
+ "image/heic": ["heic"],
437
+ "image/heif": ["heif"],
438
+ "image/avif": ["avif"],
439
+ "application/pdf": ["pdf"],
440
+ "text/plain": ["txt"],
441
+ "text/markdown": ["md"],
442
+ "text/csv": ["csv"],
443
+ "application/json": ["json"],
444
+ "application/zip": ["zip"],
445
+ "application/x-zip-compressed": ["zip"],
446
+ "audio/mpeg": ["mp3"],
447
+ "audio/wav": ["wav"],
448
+ "audio/ogg": ["ogg"],
449
+ "video/mp4": ["mp4"],
450
+ "video/webm": ["webm"],
451
+ "video/quicktime": ["mov"]
452
+ };
453
+ function buildAllowedExtensions(extensions, mimeTypes) {
454
+ const allowed = /* @__PURE__ */ new Set();
455
+ if (extensions) {
456
+ for (const ext of extensions) {
457
+ const trimmed = ext.replace(/^\./, "").toLowerCase();
458
+ if (trimmed) allowed.add(trimmed);
459
+ }
460
+ }
461
+ if (mimeTypes) {
462
+ for (const type of mimeTypes) {
463
+ const exts = MIME_EXTENSION_MAP[type.toLowerCase()] ?? [];
464
+ for (const ext of exts) allowed.add(ext);
465
+ }
466
+ }
467
+ return allowed.size > 0 ? allowed : null;
468
+ }
469
+ function isImageFileName(filename) {
470
+ const ext = filename.split(".").pop()?.toLowerCase();
471
+ return ["jpg", "jpeg", "png", "gif", "webp", "svg"].includes(ext || "");
472
+ }
473
+
474
+ // src/react/utils/fileManagerFormat.ts
475
+ function formatBytes(bytes) {
476
+ if (bytes === 0) return "0 B";
477
+ const k = 1024;
478
+ const sizes = ["B", "KB", "MB", "GB"];
479
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
480
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + " " + sizes[i];
481
+ }
482
+ function formatDate(date) {
483
+ const d = new Date(date);
484
+ return d.toLocaleDateString(void 0, { month: "short", day: "numeric", year: "numeric" });
485
+ }
486
+
487
+ // src/react/utils/fileManagerNames.ts
488
+ function splitName(name) {
489
+ const idx = name.lastIndexOf(".");
490
+ if (idx <= 0) return { base: name, ext: "" };
491
+ return { base: name.slice(0, idx), ext: name.slice(idx) };
492
+ }
493
+ function buildUniqueName(name, existing) {
494
+ if (!existing.has(name)) return name;
495
+ const { base, ext } = splitName(name);
496
+ let i = 1;
497
+ while (true) {
498
+ const candidate = `${base}_copy_${i}${ext}`;
499
+ if (!existing.has(candidate)) return candidate;
500
+ i += 1;
501
+ }
502
+ }
503
+
504
+ // src/react/utils/fileManagerPaths.ts
505
+ function joinPath(folder, name) {
506
+ const f = folder.replace(/\/+$/, "");
507
+ if (!f) return name;
508
+ return `${f}/${name}`;
509
+ }
510
+ function normalizeRelativePath(path) {
511
+ return path.replace(/^\/+/, "").replace(/\\/g, "/");
512
+ }
513
+ function getRelativePathFromFile(file) {
514
+ const rel = file.webkitRelativePath;
515
+ if (!rel) return null;
516
+ return normalizeRelativePath(rel);
517
+ }
518
+ function getParentPath(path) {
519
+ const p = path.replace(/\/+$/, "");
520
+ const parts = p.split("/").filter(Boolean);
521
+ parts.pop();
522
+ return parts.join("/");
523
+ }
524
+
525
+ // src/react/FileManager.tsx
526
+ var import_jsx_runtime5 = require("react/jsx-runtime");
527
+ function FileManager({
528
+ apiUrl,
529
+ fetch: customFetch,
530
+ onFileSelect,
531
+ onSelectionChange,
532
+ onConfirm,
533
+ onNavigate,
534
+ className,
535
+ style,
536
+ theme: themeMode = "light",
537
+ mode = "manager",
538
+ selection = "multiple",
539
+ allowActions,
540
+ confirmLabel = "Select",
541
+ hideTrash = false,
542
+ filterExtensions,
543
+ filterMimeTypes,
544
+ toolbar,
545
+ viewMode: viewModeProp,
546
+ defaultViewMode = "grid",
547
+ onViewModeChange,
548
+ labels
549
+ }) {
550
+ const theme = useTheme(themeMode);
551
+ const showSearch = toolbar?.search ?? true;
552
+ const showViewSwitcher = toolbar?.viewSwitcher ?? true;
553
+ const showSort = toolbar?.sort ?? true;
554
+ const showBreadcrumbs = toolbar?.breadcrumbs ?? true;
555
+ const labelText = {
556
+ upload: "Upload",
557
+ newFolder: "New Folder",
558
+ delete: "Delete",
559
+ deleteForever: "Delete Forever",
560
+ restore: "Restore",
561
+ emptyTrash: "Empty Trash",
562
+ confirm: confirmLabel,
563
+ searchPlaceholder: "Search files and folders...",
564
+ ...labels
565
+ };
566
+ const allowedExtensions = (0, import_react6.useMemo)(
567
+ () => buildAllowedExtensions(filterExtensions, filterMimeTypes),
568
+ [filterExtensions, filterMimeTypes]
569
+ );
570
+ const can = {
571
+ upload: mode === "manager",
572
+ createFolder: mode === "manager",
573
+ delete: mode === "manager",
574
+ rename: mode === "manager",
575
+ move: mode === "manager",
576
+ copy: mode === "manager",
577
+ restore: mode === "manager",
578
+ ...allowActions
579
+ };
580
+ const [isMobile, setIsMobile] = (0, import_react6.useState)(false);
581
+ const portalContainer = typeof document === "undefined" ? void 0 : document.body;
582
+ (0, import_react6.useEffect)(() => {
583
+ if (typeof window === "undefined") return;
584
+ const mediaQuery = window.matchMedia("(max-width: 720px)");
585
+ const update = () => setIsMobile(mediaQuery.matches);
586
+ update();
587
+ mediaQuery.addEventListener("change", update);
588
+ return () => mediaQuery.removeEventListener("change", update);
589
+ }, []);
590
+ const client = (0, import_react6.useMemo)(() => {
591
+ return new S3FileManagerClient({
592
+ apiUrl,
593
+ ...customFetch ? { fetch: customFetch } : {}
594
+ });
595
+ }, [apiUrl, customFetch]);
596
+ const [view, setView] = (0, import_react6.useState)("files");
597
+ const [path, setPath] = (0, import_react6.useState)("");
598
+ const [entries, setEntries] = (0, import_react6.useState)([]);
599
+ const [searchResults, setSearchResults] = (0, import_react6.useState)([]);
600
+ const [loading, setLoading] = (0, import_react6.useState)(false);
601
+ const [searching, setSearching] = (0, import_react6.useState)(false);
602
+ const [loadingMore, setLoadingMore] = (0, import_react6.useState)(false);
603
+ const [selected, setSelected] = (0, import_react6.useState)(/* @__PURE__ */ new Set());
604
+ const [lastSelected, setLastSelected] = (0, import_react6.useState)(null);
605
+ const [hoverRow, setHoverRow] = (0, import_react6.useState)(null);
606
+ const [previewData, setPreviewData] = (0, import_react6.useState)(null);
607
+ const [previewDisplay, setPreviewDisplay] = (0, import_react6.useState)(null);
608
+ const [isPreviewClosing, setIsPreviewClosing] = (0, import_react6.useState)(false);
609
+ const [sidebarWidth, setSidebarWidth] = (0, import_react6.useState)(320);
610
+ const [isResizing, setIsResizing] = (0, import_react6.useState)(false);
611
+ const [inlinePreviews, setInlinePreviews] = (0, import_react6.useState)({});
612
+ const [isSelectionMode, setIsSelectionMode] = (0, import_react6.useState)(false);
613
+ const [searchQuery, setSearchQuery] = (0, import_react6.useState)("");
614
+ const [sortBy, setSortBy] = (0, import_react6.useState)("name");
615
+ const [sortOrder, setSortOrder] = (0, import_react6.useState)("asc");
616
+ const [internalViewMode, setInternalViewMode] = (0, import_react6.useState)(defaultViewMode);
617
+ const viewMode = viewModeProp ?? internalViewMode;
618
+ const setViewMode = (next) => {
619
+ if (!viewModeProp) setInternalViewMode(next);
620
+ onViewModeChange?.(next);
621
+ };
622
+ const toolbarControlHeight = 34;
623
+ const headerLabelStyle = {
624
+ display: "flex",
625
+ alignItems: "center",
626
+ gap: 4,
627
+ fontSize: 12,
628
+ fontWeight: 600,
629
+ color: theme.textSecondary,
630
+ textTransform: "uppercase",
631
+ letterSpacing: "0.05em",
632
+ padding: 0
633
+ };
634
+ const [nextCursor, setNextCursor] = (0, import_react6.useState)(void 0);
635
+ const [searchCursor, setSearchCursor] = (0, import_react6.useState)(void 0);
636
+ const [hasMore, setHasMore] = (0, import_react6.useState)(false);
637
+ const [searchHasMore, setSearchHasMore] = (0, import_react6.useState)(false);
638
+ const [isDragOver, setIsDragOver] = (0, import_react6.useState)(false);
639
+ const [_dragCounter, setDragCounter] = (0, import_react6.useState)(0);
640
+ const [createFolderOpen, setCreateFolderOpen] = (0, import_react6.useState)(false);
641
+ const [newFolderName, setNewFolderName] = (0, import_react6.useState)("");
642
+ const [deleteOpen, setDeleteOpen] = (0, import_react6.useState)(false);
643
+ const [emptyTrashOpen, setEmptyTrashOpen] = (0, import_react6.useState)(false);
644
+ const [renameOpen, setRenameOpen] = (0, import_react6.useState)(false);
645
+ const [renameName, setRenameName] = (0, import_react6.useState)("");
646
+ const [renameTarget, setRenameTarget] = (0, import_react6.useState)(null);
647
+ const [isRenaming, setIsRenaming] = (0, import_react6.useState)(false);
648
+ const [isDeleting, setIsDeleting] = (0, import_react6.useState)(false);
649
+ const [isEmptyingTrash, setIsEmptyingTrash] = (0, import_react6.useState)(false);
650
+ const [uploadItems, setUploadItems] = (0, import_react6.useState)([]);
651
+ const [uploadCardOpen, setUploadCardOpen] = (0, import_react6.useState)(false);
652
+ const fileInputRef = (0, import_react6.useRef)(null);
653
+ const rootRef = (0, import_react6.useRef)(null);
654
+ const listRef = (0, import_react6.useRef)(null);
655
+ const previewPortalRef = (0, import_react6.useRef)(null);
656
+ const previewPortalContainer = previewPortalRef.current ?? rootRef.current ?? portalContainer;
657
+ const downloadUrlCacheRef = (0, import_react6.useRef)(/* @__PURE__ */ new Map());
658
+ const selectionAnchorRef = (0, import_react6.useRef)(null);
659
+ const longPressTimerRef = (0, import_react6.useRef)(null);
660
+ const suppressClickRef = (0, import_react6.useRef)(false);
661
+ const dragSelectionBaseRef = (0, import_react6.useRef)(/* @__PURE__ */ new Set());
662
+ const lastSelectionSigRef = (0, import_react6.useRef)("");
663
+ const [dragSelect, setDragSelect] = (0, import_react6.useState)(null);
664
+ const handleKeyDown = (0, import_react6.useCallback)(
665
+ (e) => {
666
+ if (e.key === "Escape") {
667
+ e.preventDefault();
668
+ e.stopPropagation();
669
+ if (createFolderOpen) {
670
+ setCreateFolderOpen(false);
671
+ setNewFolderName("");
672
+ return;
673
+ }
674
+ if (renameOpen) {
675
+ setRenameOpen(false);
676
+ setRenameTarget(null);
677
+ return;
678
+ }
679
+ if (deleteOpen) {
680
+ setDeleteOpen(false);
681
+ return;
682
+ }
683
+ if (emptyTrashOpen) {
684
+ setEmptyTrashOpen(false);
685
+ return;
686
+ }
687
+ if (isDragOver) {
688
+ setIsDragOver(false);
689
+ setDragCounter(0);
690
+ return;
691
+ }
692
+ if (dragSelect) {
693
+ setDragSelect(null);
694
+ return;
695
+ }
696
+ if (selected.size > 0) {
697
+ setSelected(/* @__PURE__ */ new Set());
698
+ setLastSelected(null);
699
+ setPreviewData(null);
700
+ return;
701
+ }
702
+ if (searchQuery.trim()) {
703
+ setSearchQuery("");
704
+ return;
705
+ }
706
+ if (uploadCardOpen) {
707
+ const hasActiveUploads = uploadItems.some((item) => item.status === "uploading");
708
+ if (!hasActiveUploads) {
709
+ setUploadCardOpen(false);
710
+ }
711
+ }
712
+ return;
713
+ }
714
+ if ((e.ctrlKey || e.metaKey) && e.key === "v" && view === "files") {
715
+ console.log("Paste detected - use drag and drop or upload button for files");
716
+ }
717
+ },
718
+ [
719
+ view,
720
+ createFolderOpen,
721
+ renameOpen,
722
+ deleteOpen,
723
+ emptyTrashOpen,
724
+ isDragOver,
725
+ dragSelect,
726
+ selected,
727
+ searchQuery,
728
+ uploadCardOpen,
729
+ uploadItems
730
+ ]
731
+ );
732
+ const handleDragEnter = (0, import_react6.useCallback)(
733
+ (e) => {
734
+ e.preventDefault();
735
+ e.stopPropagation();
736
+ if (!can.upload || view !== "files") return;
737
+ if (e.dataTransfer.types.includes("Files")) {
738
+ setDragCounter((prev) => prev + 1);
739
+ setIsDragOver(true);
740
+ }
741
+ },
742
+ [can.upload, view]
743
+ );
744
+ const handleDragLeave = (0, import_react6.useCallback)(
745
+ (e) => {
746
+ e.preventDefault();
747
+ e.stopPropagation();
748
+ if (!can.upload || view !== "files") return;
749
+ setDragCounter((prev) => {
750
+ const newCounter = prev - 1;
751
+ if (newCounter <= 0) {
752
+ setIsDragOver(false);
753
+ return 0;
754
+ }
755
+ return newCounter;
756
+ });
757
+ },
758
+ [can.upload, view]
759
+ );
760
+ const handleDragOver = (0, import_react6.useCallback)(
761
+ (e) => {
762
+ e.preventDefault();
763
+ e.stopPropagation();
764
+ if (e.dataTransfer.types.includes("Files")) {
765
+ e.dataTransfer.dropEffect = can.upload && view === "files" ? "copy" : "none";
766
+ }
767
+ },
768
+ [can.upload, view]
769
+ );
770
+ const handleDrop = (0, import_react6.useCallback)(
771
+ async (e) => {
772
+ e.preventDefault();
773
+ e.stopPropagation();
774
+ setIsDragOver(false);
775
+ setDragCounter(0);
776
+ if (!can.upload || view !== "files") return;
777
+ const items = e.dataTransfer.items;
778
+ if (items && items.length > 0) {
779
+ const dropped = await getDroppedItems(items);
780
+ if (dropped.length > 0) {
781
+ await onUpload(dropped);
782
+ return;
783
+ }
784
+ }
785
+ const files = e.dataTransfer.files;
786
+ if (files && files.length > 0) {
787
+ await onUpload(files);
788
+ }
789
+ },
790
+ [can.upload, view]
791
+ );
792
+ const refresh = (0, import_react6.useCallback)(
793
+ async (loadMore = false) => {
794
+ if (!loadMore) {
795
+ setLoading(true);
796
+ setSelected(/* @__PURE__ */ new Set());
797
+ setLastSelected(null);
798
+ setPreviewData(null);
799
+ } else {
800
+ setLoadingMore(true);
801
+ }
802
+ try {
803
+ const fetchPath = view === "trash" ? path ? joinPath(TRASH_PATH, path) : TRASH_PATH : path;
804
+ const out = await client.list({
805
+ path: fetchPath,
806
+ limit: 100,
807
+ ...loadMore && nextCursor ? { cursor: nextCursor } : {}
808
+ });
809
+ let items = out.entries;
810
+ if (allowedExtensions) {
811
+ items = items.filter((entry) => {
812
+ if (entry.type === "folder") return true;
813
+ const ext = entry.name.split(".").pop()?.toLowerCase();
814
+ return ext ? allowedExtensions.has(ext) : false;
815
+ });
816
+ }
817
+ if (hideTrash) {
818
+ items = items.filter((entry) => entry.path !== `${TRASH_PATH}/`);
819
+ }
820
+ if (loadMore) {
821
+ setEntries((prev) => [...prev, ...items]);
822
+ } else {
823
+ setEntries(items);
824
+ }
825
+ setNextCursor(out.nextCursor);
826
+ setHasMore(!!out.nextCursor);
827
+ } catch (e) {
828
+ console.error(e);
829
+ if (!loadMore) {
830
+ setEntries([]);
831
+ }
832
+ } finally {
833
+ setLoading(false);
834
+ setLoadingMore(false);
835
+ }
836
+ },
837
+ [client, path, view, nextCursor, allowedExtensions, hideTrash]
838
+ );
839
+ const performSearch = (0, import_react6.useCallback)(
840
+ async (query, loadMore = false) => {
841
+ if (!query.trim()) {
842
+ setSearchResults([]);
843
+ setSearching(false);
844
+ setSearchCursor(void 0);
845
+ setSearchHasMore(false);
846
+ return;
847
+ }
848
+ if (!loadMore) {
849
+ setSearching(true);
850
+ setSelected(/* @__PURE__ */ new Set());
851
+ setLastSelected(null);
852
+ setPreviewData(null);
853
+ } else {
854
+ setLoadingMore(true);
855
+ }
856
+ try {
857
+ try {
858
+ const searchOptions = {
859
+ query: query.trim(),
860
+ recursive: true,
861
+ limit: 100,
862
+ ...loadMore && searchCursor ? { cursor: searchCursor } : {}
863
+ };
864
+ if (view === "trash") {
865
+ searchOptions.path = TRASH_PATH;
866
+ }
867
+ const out = await client.search(searchOptions);
868
+ let items = out.entries;
869
+ if (view === "files") {
870
+ items = items.filter((e) => {
871
+ if (!e.path.startsWith(TRASH_PATH)) return true;
872
+ return e.path === `${TRASH_PATH}/`;
873
+ });
874
+ }
875
+ if (allowedExtensions) {
876
+ items = items.filter((entry) => {
877
+ if (entry.type === "folder") return true;
878
+ const ext = entry.name.split(".").pop()?.toLowerCase();
879
+ return ext ? allowedExtensions.has(ext) : false;
880
+ });
881
+ }
882
+ if (hideTrash) {
883
+ items = items.filter((entry) => entry.path !== `${TRASH_PATH}/`);
884
+ }
885
+ if (loadMore) {
886
+ setSearchResults((prev) => [...prev, ...items]);
887
+ } else {
888
+ setSearchResults(items);
889
+ }
890
+ setSearchCursor(out.nextCursor);
891
+ setSearchHasMore(!!out.nextCursor);
892
+ } catch (serverError) {
893
+ if (serverError.message?.includes("404") || serverError.message?.includes("Request failed: 404")) {
894
+ console.log("Server search not available, using client-side search");
895
+ const searchTerm = query.trim().toLowerCase();
896
+ const filtered = entries.filter(
897
+ (entry) => entry.name.toLowerCase().includes(searchTerm)
898
+ );
899
+ setSearchResults(filtered);
900
+ setSearchHasMore(false);
901
+ } else {
902
+ throw serverError;
903
+ }
904
+ }
905
+ } catch (e) {
906
+ console.error("Search failed:", e);
907
+ if (!loadMore) {
908
+ setSearchResults([]);
909
+ }
910
+ } finally {
911
+ setSearching(false);
912
+ setLoadingMore(false);
913
+ }
914
+ },
915
+ [client, view, entries, searchCursor, allowedExtensions, hideTrash]
916
+ );
917
+ (0, import_react6.useEffect)(() => {
918
+ setNextCursor(void 0);
919
+ setHasMore(false);
920
+ refresh();
921
+ }, [client, path, view]);
922
+ (0, import_react6.useEffect)(() => {
923
+ if (hideTrash && view === "trash") {
924
+ setView("files");
925
+ setPath("");
926
+ }
927
+ }, [hideTrash, view]);
928
+ (0, import_react6.useEffect)(() => {
929
+ const timeoutId = setTimeout(() => {
930
+ performSearch(searchQuery);
931
+ }, 300);
932
+ return () => clearTimeout(timeoutId);
933
+ }, [searchQuery, view]);
934
+ (0, import_react6.useEffect)(() => {
935
+ onNavigate?.(path);
936
+ }, [path, onNavigate]);
937
+ (0, import_react6.useEffect)(() => {
938
+ const source = searchQuery.trim() ? searchResults : entries;
939
+ const selectedEntries = source.filter((entry) => selected.has(entry.path));
940
+ const sig = selectedEntries.map((entry) => entry.path).sort().join("|");
941
+ if (sig === lastSelectionSigRef.current) return;
942
+ lastSelectionSigRef.current = sig;
943
+ onSelectionChange?.(selectedEntries);
944
+ }, [entries, searchResults, selected, searchQuery, onSelectionChange]);
945
+ (0, import_react6.useEffect)(() => {
946
+ const source = searchQuery.trim() ? searchResults : entries;
947
+ const selectedFiles = source.filter((entry) => selected.has(entry.path)).filter((entry) => entry.type === "file");
948
+ const toPrepare = selectedFiles.slice(0, 25).map((file) => file.path).filter((p) => !downloadUrlCacheRef.current.has(p));
949
+ if (toPrepare.length === 0) return;
950
+ let cancelled = false;
951
+ const run = async () => {
952
+ await Promise.all(
953
+ toPrepare.map(async (p) => {
954
+ try {
955
+ const out = await client.getPreviewUrl({ path: p, inline: false });
956
+ if (cancelled) return;
957
+ downloadUrlCacheRef.current.set(p, out.url);
958
+ } catch {
959
+ }
960
+ })
961
+ );
962
+ };
963
+ void run();
964
+ return () => {
965
+ cancelled = true;
966
+ };
967
+ }, [client, entries, searchResults, searchQuery, selected]);
968
+ (0, import_react6.useEffect)(() => {
969
+ if (selected.size === 1 && lastSelected?.type === "file" && selected.has(lastSelected.path)) {
970
+ const fetchPreview = async () => {
971
+ try {
972
+ const out = await client.getPreviewUrl({
973
+ path: lastSelected.path,
974
+ inline: true
975
+ });
976
+ setPreviewData({ url: out.url, entry: lastSelected });
977
+ } catch (e) {
978
+ console.error("Failed to load preview", e);
979
+ }
980
+ };
981
+ fetchPreview();
982
+ } else {
983
+ setPreviewData(null);
984
+ }
985
+ }, [selected, lastSelected, client]);
986
+ (0, import_react6.useEffect)(() => {
987
+ if (previewData) {
988
+ setPreviewDisplay(previewData);
989
+ setIsPreviewClosing(false);
990
+ return;
991
+ }
992
+ if (!previewDisplay) return;
993
+ setIsPreviewClosing(true);
994
+ const timeoutId = window.setTimeout(() => {
995
+ setPreviewDisplay(null);
996
+ setIsPreviewClosing(false);
997
+ }, 200);
998
+ return () => window.clearTimeout(timeoutId);
999
+ }, [previewData, previewDisplay]);
1000
+ (0, import_react6.useEffect)(() => {
1001
+ const prevent = (e) => {
1002
+ e.preventDefault();
1003
+ };
1004
+ window.addEventListener("dragover", prevent);
1005
+ window.addEventListener("drop", prevent);
1006
+ return () => {
1007
+ window.removeEventListener("dragover", prevent);
1008
+ window.removeEventListener("drop", prevent);
1009
+ };
1010
+ }, []);
1011
+ (0, import_react6.useEffect)(() => {
1012
+ if (selected.size === 0 && isSelectionMode) {
1013
+ setIsSelectionMode(false);
1014
+ selectionAnchorRef.current = null;
1015
+ }
1016
+ }, [selected, isSelectionMode]);
1017
+ (0, import_react6.useEffect)(() => {
1018
+ if (view !== "files") return;
1019
+ if (!selected.has(`${TRASH_PATH}/`)) return;
1020
+ setSelected((prev) => {
1021
+ const next = new Set(prev);
1022
+ next.delete(`${TRASH_PATH}/`);
1023
+ return next;
1024
+ });
1025
+ }, [selected, view]);
1026
+ (0, import_react6.useEffect)(() => {
1027
+ if (!dragSelect?.active) return;
1028
+ const handleMove = (e) => {
1029
+ const container = listRef.current;
1030
+ if (!container) return;
1031
+ const rect = container.getBoundingClientRect();
1032
+ const x = e.clientX - rect.left + container.scrollLeft;
1033
+ const y = e.clientY - rect.top + container.scrollTop;
1034
+ setDragSelect((prev) => prev ? { ...prev, x, y } : prev);
1035
+ const left = Math.min(dragSelect.startX, x);
1036
+ const top = Math.min(dragSelect.startY, y);
1037
+ const right = Math.max(dragSelect.startX, x);
1038
+ const bottom = Math.max(dragSelect.startY, y);
1039
+ const elements = Array.from(container.querySelectorAll("[data-entry-path]"));
1040
+ const selectedPaths = /* @__PURE__ */ new Set();
1041
+ for (const el of elements) {
1042
+ const pathAttr = el.getAttribute("data-entry-path");
1043
+ const selectable = el.getAttribute("data-entry-selectable");
1044
+ if (selectable === "false") continue;
1045
+ if (!pathAttr) continue;
1046
+ const elRect = el.getBoundingClientRect();
1047
+ const elLeft = elRect.left - rect.left + container.scrollLeft;
1048
+ const elTop = elRect.top - rect.top + container.scrollTop;
1049
+ const elRight = elLeft + elRect.width;
1050
+ const elBottom = elTop + elRect.height;
1051
+ const intersects = elLeft < right && elRight > left && elTop < bottom && elBottom > top;
1052
+ if (intersects) selectedPaths.add(pathAttr);
1053
+ }
1054
+ setSelected(() => {
1055
+ const base = dragSelect.additive ? new Set(dragSelectionBaseRef.current) : /* @__PURE__ */ new Set();
1056
+ for (const path2 of selectedPaths) base.add(path2);
1057
+ return base;
1058
+ });
1059
+ setLastSelected(null);
1060
+ setIsSelectionMode(true);
1061
+ };
1062
+ const handleUp = () => {
1063
+ setDragSelect(null);
1064
+ };
1065
+ window.addEventListener("mousemove", handleMove);
1066
+ window.addEventListener("mouseup", handleUp);
1067
+ return () => {
1068
+ window.removeEventListener("mousemove", handleMove);
1069
+ window.removeEventListener("mouseup", handleUp);
1070
+ };
1071
+ }, [dragSelect]);
1072
+ const updateSidebarWidth = (0, import_react6.useCallback)((clientX) => {
1073
+ const availableWidth = rootRef.current?.getBoundingClientRect().width ?? window.innerWidth;
1074
+ const maxWidth = Math.min(520, availableWidth * 0.6);
1075
+ const minWidth = Math.min(280, maxWidth);
1076
+ const next = window.innerWidth - clientX;
1077
+ const clamped = Math.max(minWidth, Math.min(maxWidth, next));
1078
+ setSidebarWidth(clamped);
1079
+ }, []);
1080
+ (0, import_react6.useEffect)(() => {
1081
+ if (!isResizing) return;
1082
+ const onMouseMove = (e) => {
1083
+ e.preventDefault();
1084
+ updateSidebarWidth(e.clientX);
1085
+ };
1086
+ const onTouchMove = (e) => {
1087
+ if (!e.touches[0]) return;
1088
+ e.preventDefault();
1089
+ updateSidebarWidth(e.touches[0].clientX);
1090
+ };
1091
+ const onStop = () => setIsResizing(false);
1092
+ document.addEventListener("mousemove", onMouseMove);
1093
+ document.addEventListener("mouseup", onStop);
1094
+ document.addEventListener("mouseleave", onStop);
1095
+ document.addEventListener("touchmove", onTouchMove, { passive: false });
1096
+ document.addEventListener("touchend", onStop);
1097
+ document.addEventListener("touchcancel", onStop);
1098
+ return () => {
1099
+ document.removeEventListener("mousemove", onMouseMove);
1100
+ document.removeEventListener("mouseup", onStop);
1101
+ document.removeEventListener("mouseleave", onStop);
1102
+ document.removeEventListener("touchmove", onTouchMove);
1103
+ document.removeEventListener("touchend", onStop);
1104
+ document.removeEventListener("touchcancel", onStop);
1105
+ };
1106
+ }, [isResizing, updateSidebarWidth]);
1107
+ function handleNavigate(newPath) {
1108
+ setPath(newPath);
1109
+ }
1110
+ async function onCreateFolder() {
1111
+ if (!can.createFolder || view !== "files") return;
1112
+ if (!newFolderName.trim()) return;
1113
+ const existingNames = new Set(entries.map((entry) => entry.name));
1114
+ const uniqueName = buildUniqueName(newFolderName.trim(), existingNames);
1115
+ await client.createFolder({ path: joinPath(path, uniqueName) });
1116
+ setNewFolderName("");
1117
+ setCreateFolderOpen(false);
1118
+ refresh();
1119
+ }
1120
+ async function onRename() {
1121
+ if (!can.rename || view !== "files") return;
1122
+ if (!renameName.trim()) return;
1123
+ if (isRenaming) return;
1124
+ const target = renameTarget ?? lastSelected;
1125
+ if (!target) return;
1126
+ const oldPath = target.path;
1127
+ const parent = getParentPath(oldPath);
1128
+ const existingNames = new Set(
1129
+ entries.filter((entry) => entry.path !== oldPath).map((entry) => entry.name)
1130
+ );
1131
+ const uniqueName = buildUniqueName(renameName.trim(), existingNames);
1132
+ const nextPath = parent ? joinPath(parent, uniqueName) : uniqueName;
1133
+ const newPath = target.type === "folder" ? `${nextPath.replace(/\/+$/, "")}/` : nextPath;
1134
+ if (newPath === oldPath) {
1135
+ setRenameOpen(false);
1136
+ setRenameTarget(null);
1137
+ return;
1138
+ }
1139
+ try {
1140
+ setIsRenaming(true);
1141
+ await client.move({ fromPath: oldPath, toPath: newPath });
1142
+ setRenameOpen(false);
1143
+ setRenameTarget(null);
1144
+ refresh();
1145
+ } catch (e) {
1146
+ console.error("Rename failed", e);
1147
+ alert("Rename failed");
1148
+ } finally {
1149
+ setIsRenaming(false);
1150
+ }
1151
+ }
1152
+ async function deleteEntries(targets) {
1153
+ if (!can.delete) return;
1154
+ if (view === "files") {
1155
+ for (const target of targets) {
1156
+ if (target.path.startsWith(TRASH_PATH)) continue;
1157
+ const dest = joinPath(TRASH_PATH, target.path);
1158
+ await client.move({ fromPath: target.path, toPath: dest });
1159
+ }
1160
+ } else {
1161
+ const files = targets.filter((e) => e.type === "file").map((e) => e.path);
1162
+ const folders = targets.filter((e) => e.type === "folder");
1163
+ if (files.length > 0) await client.deleteFiles({ paths: files });
1164
+ for (const folder of folders) {
1165
+ await client.deleteFolder({ path: folder.path, recursive: true });
1166
+ }
1167
+ }
1168
+ }
1169
+ async function onDelete() {
1170
+ if (!can.delete) return;
1171
+ if (isDeleting) return;
1172
+ const source = searchQuery.trim() ? searchResults : entries;
1173
+ const targets = source.filter((e) => selected.has(e.path));
1174
+ try {
1175
+ setIsDeleting(true);
1176
+ await deleteEntries(targets);
1177
+ setDeleteOpen(false);
1178
+ refresh();
1179
+ } catch (e) {
1180
+ console.error("Delete failed", e);
1181
+ alert("Delete failed");
1182
+ } finally {
1183
+ setIsDeleting(false);
1184
+ }
1185
+ }
1186
+ async function restoreEntries(targets) {
1187
+ if (!can.restore) return;
1188
+ for (const target of targets) {
1189
+ if (!target.path.startsWith(TRASH_PATH)) continue;
1190
+ const originalPath = target.path.slice(TRASH_PATH.length + 1);
1191
+ if (!originalPath) continue;
1192
+ await client.move({ fromPath: target.path, toPath: originalPath });
1193
+ }
1194
+ }
1195
+ async function onRestore() {
1196
+ if (!can.restore) return;
1197
+ const source = searchQuery.trim() ? searchResults : entries;
1198
+ const targets = source.filter((e) => selected.has(e.path));
1199
+ await restoreEntries(targets);
1200
+ refresh();
1201
+ }
1202
+ async function onEmptyTrash() {
1203
+ if (!can.restore) return;
1204
+ if (isEmptyingTrash) return;
1205
+ try {
1206
+ setIsEmptyingTrash(true);
1207
+ await client.deleteFolder({ path: TRASH_PATH, recursive: true });
1208
+ setEmptyTrashOpen(false);
1209
+ refresh();
1210
+ } catch (e) {
1211
+ console.error("Empty trash failed", e);
1212
+ alert("Empty trash failed");
1213
+ } finally {
1214
+ setIsEmptyingTrash(false);
1215
+ }
1216
+ }
1217
+ async function onUpload(files) {
1218
+ if (!can.upload || view !== "files") return;
1219
+ if (!files || files.length === 0) return;
1220
+ const baseItems = Array.isArray(files) ? files : Array.from(files).map((file) => {
1221
+ const rel = getRelativePathFromFile(file);
1222
+ return {
1223
+ file,
1224
+ path: joinPath(path, rel ?? file.name)
1225
+ };
1226
+ });
1227
+ const existingByFolder = /* @__PURE__ */ new Map();
1228
+ const currentFolderNames = new Set(entries.map((entry) => entry.name));
1229
+ existingByFolder.set(path, currentFolderNames);
1230
+ const items = baseItems.map((item) => {
1231
+ const parent = getParentPath(item.path);
1232
+ const name = item.path.split("/").pop() ?? item.path;
1233
+ const existing = existingByFolder.get(parent) ?? /* @__PURE__ */ new Set();
1234
+ const uniqueName = buildUniqueName(name, existing);
1235
+ existing.add(uniqueName);
1236
+ existingByFolder.set(parent, existing);
1237
+ const uniquePath = parent ? joinPath(parent, uniqueName) : uniqueName;
1238
+ return { ...item, path: uniquePath };
1239
+ });
1240
+ const initialUploadItems = items.map((item) => ({
1241
+ file: item.file,
1242
+ path: item.path,
1243
+ name: item.path.split("/").pop() ?? item.path,
1244
+ loaded: 0,
1245
+ total: item.file.size ?? 0,
1246
+ status: "uploading",
1247
+ error: void 0
1248
+ }));
1249
+ setUploadCardOpen(true);
1250
+ setUploadItems(initialUploadItems);
1251
+ await client.uploadFiles({
1252
+ files: items,
1253
+ parallel: 4,
1254
+ hooks: {
1255
+ onUploadProgress: ({ path: p, loaded, total }) => {
1256
+ if (!total) return;
1257
+ setUploadItems(
1258
+ (prev) => prev.map(
1259
+ (item) => item.path === p ? {
1260
+ ...item,
1261
+ loaded,
1262
+ total: total ?? item.total,
1263
+ status: "uploading",
1264
+ error: void 0
1265
+ } : item
1266
+ )
1267
+ );
1268
+ },
1269
+ onUploadComplete: ({ path: p }) => setUploadItems(
1270
+ (prev) => prev.map(
1271
+ (item) => item.path === p ? {
1272
+ ...item,
1273
+ loaded: item.total,
1274
+ status: "done",
1275
+ error: void 0
1276
+ } : item
1277
+ )
1278
+ ),
1279
+ onUploadError: ({ path: p, error }) => setUploadItems(
1280
+ (prev) => prev.map((item) => {
1281
+ if (item.path !== p) return item;
1282
+ const message = error instanceof Error ? error.message : error ? String(error) : "Upload failed";
1283
+ return { ...item, status: "error", error: message };
1284
+ })
1285
+ )
1286
+ }
1287
+ });
1288
+ refresh();
1289
+ }
1290
+ async function retryUpload(item) {
1291
+ if (!can.upload || view !== "files") return;
1292
+ if (!item.file) {
1293
+ window.alert("Original file not available. Please re-upload.");
1294
+ return;
1295
+ }
1296
+ const file = item.file;
1297
+ setUploadCardOpen(true);
1298
+ setUploadItems(
1299
+ (prev) => prev.map(
1300
+ (entry) => entry.path === item.path ? {
1301
+ ...entry,
1302
+ loaded: 0,
1303
+ total: file.size ?? entry.total,
1304
+ status: "uploading",
1305
+ error: void 0
1306
+ } : entry
1307
+ )
1308
+ );
1309
+ await client.uploadFiles({
1310
+ files: [{ file, path: item.path }],
1311
+ parallel: 1,
1312
+ hooks: {
1313
+ onUploadProgress: ({ path: p, loaded, total }) => {
1314
+ if (!total) return;
1315
+ setUploadItems(
1316
+ (prev) => prev.map(
1317
+ (entry) => entry.path === p ? {
1318
+ ...entry,
1319
+ loaded,
1320
+ total: total ?? entry.total,
1321
+ status: "uploading",
1322
+ error: void 0
1323
+ } : entry
1324
+ )
1325
+ );
1326
+ },
1327
+ onUploadComplete: ({ path: p }) => setUploadItems(
1328
+ (prev) => prev.map(
1329
+ (entry) => entry.path === p ? {
1330
+ ...entry,
1331
+ loaded: entry.total,
1332
+ status: "done",
1333
+ error: void 0
1334
+ } : entry
1335
+ )
1336
+ ),
1337
+ onUploadError: ({ path: p, error }) => setUploadItems(
1338
+ (prev) => prev.map((entry) => {
1339
+ if (entry.path !== p) return entry;
1340
+ const message = error instanceof Error ? error.message : error ? String(error) : "Upload failed";
1341
+ return { ...entry, status: "error", error: message };
1342
+ })
1343
+ )
1344
+ }
1345
+ });
1346
+ refresh();
1347
+ }
1348
+ function openEntry(entry) {
1349
+ if (entry.type === "folder") {
1350
+ let nextPath = entry.path;
1351
+ if (view === "trash") {
1352
+ nextPath = entry.path.slice(TRASH_PATH.length + 1);
1353
+ setPath(nextPath);
1354
+ return;
1355
+ }
1356
+ if (entry.path.startsWith(TRASH_PATH)) {
1357
+ setView("trash");
1358
+ const relPath = entry.path.slice(TRASH_PATH.length + 1);
1359
+ setPath(relPath);
1360
+ return;
1361
+ }
1362
+ setPath(nextPath);
1363
+ return;
1364
+ }
1365
+ setSelected(/* @__PURE__ */ new Set([entry.path]));
1366
+ selectionAnchorRef.current = entry.path;
1367
+ setIsSelectionMode(true);
1368
+ setLastSelected(entry);
1369
+ onFileSelect?.(entry);
1370
+ }
1371
+ function selectRange(entries2, startPath, endPath) {
1372
+ const startIndex = entries2.findIndex((entry) => entry.path === startPath);
1373
+ const endIndex = entries2.findIndex((entry) => entry.path === endPath);
1374
+ if (startIndex === -1 || endIndex === -1) {
1375
+ return /* @__PURE__ */ new Set([endPath]);
1376
+ }
1377
+ const [from, to] = startIndex < endIndex ? [startIndex, endIndex] : [endIndex, startIndex];
1378
+ const next = /* @__PURE__ */ new Set();
1379
+ for (let i = from; i <= to; i += 1) {
1380
+ next.add(entries2[i].path);
1381
+ }
1382
+ return next;
1383
+ }
1384
+ function handleEntryClickWithSelection(entry, index, entries2, e) {
1385
+ const isTrashFolder = view === "files" && entry.type === "folder" && entry.path === `${TRASH_PATH}/`;
1386
+ if (isTrashFolder) {
1387
+ openEntry(entry);
1388
+ return;
1389
+ }
1390
+ if (suppressClickRef.current) {
1391
+ suppressClickRef.current = false;
1392
+ return;
1393
+ }
1394
+ if (e.shiftKey && selectionAnchorRef.current) {
1395
+ if (selection === "single") {
1396
+ setSelected(/* @__PURE__ */ new Set([entry.path]));
1397
+ setLastSelected(null);
1398
+ setIsSelectionMode(true);
1399
+ return;
1400
+ }
1401
+ const next = selectRange(entries2, selectionAnchorRef.current, entry.path);
1402
+ setSelected(next);
1403
+ setLastSelected(null);
1404
+ setIsSelectionMode(true);
1405
+ return;
1406
+ }
1407
+ if (isSelectionMode || e.metaKey || e.ctrlKey) {
1408
+ setSelected((prev) => {
1409
+ const next = new Set(prev);
1410
+ if (next.has(entry.path)) {
1411
+ next.delete(entry.path);
1412
+ } else {
1413
+ if (selection === "single") {
1414
+ next.clear();
1415
+ }
1416
+ next.add(entry.path);
1417
+ }
1418
+ setIsSelectionMode(next.size > 0);
1419
+ return next;
1420
+ });
1421
+ selectionAnchorRef.current = entry.path;
1422
+ setLastSelected(null);
1423
+ return;
1424
+ }
1425
+ setSelected((prev) => {
1426
+ const next = new Set(prev);
1427
+ const alreadySelected = next.has(entry.path);
1428
+ if (selection === "single") {
1429
+ next.clear();
1430
+ if (!alreadySelected) {
1431
+ next.add(entry.path);
1432
+ }
1433
+ } else if (alreadySelected) {
1434
+ next.delete(entry.path);
1435
+ } else {
1436
+ next.add(entry.path);
1437
+ }
1438
+ setIsSelectionMode(next.size > 0);
1439
+ return next;
1440
+ });
1441
+ selectionAnchorRef.current = entry.path;
1442
+ setLastSelected(null);
1443
+ }
1444
+ function handleLongPressSelect(entry) {
1445
+ if (view === "files" && entry.type === "folder" && entry.path === `${TRASH_PATH}/`) {
1446
+ return;
1447
+ }
1448
+ setIsSelectionMode(true);
1449
+ selectionAnchorRef.current = entry.path;
1450
+ setSelected((prev) => {
1451
+ const next = new Set(prev);
1452
+ if (selection === "single") {
1453
+ next.clear();
1454
+ }
1455
+ next.add(entry.path);
1456
+ return next;
1457
+ });
1458
+ setLastSelected(null);
1459
+ suppressClickRef.current = true;
1460
+ }
1461
+ async function bulkDownload(entriesToDownload) {
1462
+ const files = entriesToDownload.filter((entry) => entry.type === "file");
1463
+ if (files.length === 0) return;
1464
+ const cachedUrls = files.map((f) => downloadUrlCacheRef.current.get(f.path)).filter((u) => typeof u === "string");
1465
+ if (cachedUrls.length !== files.length) {
1466
+ if (files.length === 1) {
1467
+ try {
1468
+ const out = await client.getPreviewUrl({
1469
+ path: files[0].path,
1470
+ inline: false
1471
+ });
1472
+ downloadUrlCacheRef.current.set(files[0].path, out.url);
1473
+ const a = document.createElement("a");
1474
+ a.href = out.url;
1475
+ a.target = "_blank";
1476
+ a.rel = "noopener noreferrer";
1477
+ document.body.appendChild(a);
1478
+ a.click();
1479
+ document.body.removeChild(a);
1480
+ } catch (e) {
1481
+ console.error(e);
1482
+ }
1483
+ return;
1484
+ }
1485
+ window.alert("Preparing download links\u2026 please click Download again in a moment.");
1486
+ return;
1487
+ }
1488
+ if (cachedUrls.length > 20) {
1489
+ const ok = window.confirm(
1490
+ `Download ${cachedUrls.length} files? Your browser may ask to allow multiple downloads.`
1491
+ );
1492
+ if (!ok) return;
1493
+ }
1494
+ for (const url of cachedUrls) {
1495
+ const a = document.createElement("a");
1496
+ a.href = url;
1497
+ a.target = "_blank";
1498
+ a.rel = "noopener noreferrer";
1499
+ document.body.appendChild(a);
1500
+ a.click();
1501
+ document.body.removeChild(a);
1502
+ }
1503
+ }
1504
+ async function bulkCopy(entriesToCopy) {
1505
+ if (!can.copy) return;
1506
+ const dest = window.prompt("Copy to folder path", path || "");
1507
+ if (!dest) return;
1508
+ const baseDest = dest.replace(/\/+$/, "");
1509
+ for (const entry of entriesToCopy) {
1510
+ if (entry.path.startsWith(TRASH_PATH)) continue;
1511
+ const targetName = entry.name || entry.path.split("/").pop() || entry.path;
1512
+ const toPath = entry.type === "folder" ? `${joinPath(baseDest, targetName)}/` : joinPath(baseDest, targetName);
1513
+ await client.copy({ fromPath: entry.path, toPath });
1514
+ }
1515
+ refresh();
1516
+ }
1517
+ async function bulkMove(entriesToMove) {
1518
+ if (!can.move) return;
1519
+ const dest = window.prompt("Move to folder path", path || "");
1520
+ if (!dest) return;
1521
+ const baseDest = dest.replace(/\/+$/, "");
1522
+ for (const entry of entriesToMove) {
1523
+ if (entry.path.startsWith(TRASH_PATH)) continue;
1524
+ const targetName = entry.name || entry.path.split("/").pop() || entry.path;
1525
+ const toPath = entry.type === "folder" ? `${joinPath(baseDest, targetName)}/` : joinPath(baseDest, targetName);
1526
+ await client.move({ fromPath: entry.path, toPath });
1527
+ }
1528
+ refresh();
1529
+ }
1530
+ const breadcrumbs = (0, import_react6.useMemo)(() => {
1531
+ const parts = path.split("/").filter(Boolean);
1532
+ const crumbs = [{ name: view === "trash" ? "Trash" : "Home", path: "" }];
1533
+ let cur = "";
1534
+ for (const part of parts) {
1535
+ cur = cur ? `${cur}/${part}` : part;
1536
+ crumbs.push({ name: part, path: cur });
1537
+ }
1538
+ return crumbs;
1539
+ }, [path, view]);
1540
+ const handleScroll = (0, import_react6.useCallback)(
1541
+ (e) => {
1542
+ const { scrollTop, scrollHeight, clientHeight } = e.currentTarget;
1543
+ const isNearBottom = scrollHeight - scrollTop <= clientHeight + 100;
1544
+ if (isNearBottom && !loadingMore && !loading && !searching) {
1545
+ const isSearching = searchQuery.trim();
1546
+ const hasMoreItems = isSearching ? searchHasMore : hasMore;
1547
+ if (hasMoreItems) {
1548
+ if (isSearching) {
1549
+ performSearch(searchQuery, true);
1550
+ } else {
1551
+ refresh(true);
1552
+ }
1553
+ }
1554
+ }
1555
+ },
1556
+ [loadingMore, loading, searching, searchQuery, searchHasMore, hasMore, performSearch, refresh]
1557
+ );
1558
+ const currentEntries = (0, import_react6.useMemo)(() => {
1559
+ return searchQuery.trim() ? searchResults : entries;
1560
+ }, [searchQuery, searchResults, entries]);
1561
+ (0, import_react6.useEffect)(() => {
1562
+ let cancelled = false;
1563
+ const toFetch = currentEntries.filter((entry) => entry.type === "file" && isImageFileName(entry.name)).filter((entry) => !inlinePreviews[entry.path]).slice(0, 50);
1564
+ if (toFetch.length === 0) return;
1565
+ const run = async () => {
1566
+ for (const entry of toFetch) {
1567
+ try {
1568
+ const out = await client.getPreviewUrl({
1569
+ path: entry.path,
1570
+ inline: true
1571
+ });
1572
+ if (cancelled) return;
1573
+ setInlinePreviews(
1574
+ (prev) => prev[entry.path] ? prev : { ...prev, [entry.path]: out.url }
1575
+ );
1576
+ } catch {
1577
+ }
1578
+ }
1579
+ };
1580
+ void run();
1581
+ return () => {
1582
+ cancelled = true;
1583
+ };
1584
+ }, [currentEntries, inlinePreviews, client, allowedExtensions]);
1585
+ const showSidebar = previewDisplay !== null;
1586
+ const showSidebarOnLayout = !isMobile && mode !== "picker";
1587
+ const isSidebarVisible = showSidebarOnLayout && showSidebar;
1588
+ async function getDroppedItems(items) {
1589
+ const entries2 = [];
1590
+ for (const item of Array.from(items)) {
1591
+ const entry = item.webkitGetAsEntry?.();
1592
+ if (entry) entries2.push(entry);
1593
+ }
1594
+ if (entries2.length === 0) return [];
1595
+ const results = [];
1596
+ const readEntries = (reader) => new Promise((resolve) => {
1597
+ reader.readEntries((batch) => resolve(batch));
1598
+ });
1599
+ const traverse = async (entry, prefix) => {
1600
+ if (entry.isFile) {
1601
+ const file = await new Promise((resolve) => entry.file(resolve));
1602
+ const rel = normalizeRelativePath(prefix ? `${prefix}/${file.name}` : file.name);
1603
+ results.push({ file, path: joinPath(path, rel) });
1604
+ return;
1605
+ }
1606
+ if (entry.isDirectory) {
1607
+ const reader = entry.createReader();
1608
+ let batch = await readEntries(reader);
1609
+ while (batch.length > 0) {
1610
+ for (const child of batch) {
1611
+ await traverse(child, prefix ? `${prefix}/${entry.name}` : entry.name);
1612
+ }
1613
+ batch = await readEntries(reader);
1614
+ }
1615
+ }
1616
+ };
1617
+ for (const entry of entries2) {
1618
+ await traverse(entry, "");
1619
+ }
1620
+ return results;
1621
+ }
1622
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1623
+ "div",
1624
+ {
1625
+ style: {
1626
+ minHeight: isMobile ? 0 : 500,
1627
+ maxHeight: isMobile ? "100%" : "100vh",
1628
+ ...style,
1629
+ ...{
1630
+ "--s3kit-bg": theme.bg,
1631
+ "--s3kit-bg-secondary": theme.bgSecondary,
1632
+ "--s3kit-border": theme.border,
1633
+ "--s3kit-text": theme.text,
1634
+ "--s3kit-text-secondary": theme.textSecondary,
1635
+ "--s3kit-icon-bg": theme.bgSecondary,
1636
+ "--s3kit-icon-border": theme.border,
1637
+ "--s3kit-icon-radius": "8px"
1638
+ }
1639
+ },
1640
+ className: `${FileManager_default.root}${className ? ` ${className}` : ""}`,
1641
+ ref: rootRef,
1642
+ onDragEnter: handleDragEnter,
1643
+ onDragLeave: handleDragLeave,
1644
+ onDragOver: handleDragOver,
1645
+ onDrop: handleDrop,
1646
+ onKeyDown: handleKeyDown,
1647
+ tabIndex: 0,
1648
+ children: [
1649
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1650
+ "div",
1651
+ {
1652
+ className: FileManager_default.main,
1653
+ style: {
1654
+ ...isDragOver ? {
1655
+ backgroundColor: theme.bg === "#ffffff" ? "#fafafa" : "#1a1a1a"
1656
+ } : void 0
1657
+ },
1658
+ children: [
1659
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1660
+ "div",
1661
+ {
1662
+ className: FileManager_default.header,
1663
+ style: {
1664
+ padding: isMobile ? "12px 16px" : "16px 24px",
1665
+ ...isMobile ? { gap: 8 } : {}
1666
+ },
1667
+ children: [
1668
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1669
+ "div",
1670
+ {
1671
+ style: {
1672
+ display: "flex",
1673
+ alignItems: "center",
1674
+ flex: 1,
1675
+ minWidth: 0
1676
+ },
1677
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1678
+ "div",
1679
+ {
1680
+ className: FileManager_default.navTabs,
1681
+ style: { flexWrap: isMobile ? "wrap" : "nowrap" },
1682
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1683
+ "button",
1684
+ {
1685
+ type: "button",
1686
+ className: FileManager_default.navTab,
1687
+ style: {
1688
+ backgroundColor: view === "files" ? theme.selected : "transparent",
1689
+ color: view === "files" ? theme.text : theme.textSecondary,
1690
+ ...isMobile ? { flex: "1 1 auto", justifyContent: "center" } : {}
1691
+ },
1692
+ onClick: () => {
1693
+ setView("files");
1694
+ setPath("");
1695
+ setSelected(/* @__PURE__ */ new Set());
1696
+ setLastSelected(null);
1697
+ },
1698
+ children: [
1699
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(UiIcon, { icon: import_react7.House, size: 16 }),
1700
+ "Home"
1701
+ ]
1702
+ }
1703
+ )
1704
+ }
1705
+ )
1706
+ }
1707
+ ),
1708
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1709
+ "div",
1710
+ {
1711
+ style: {
1712
+ display: "flex",
1713
+ gap: 8,
1714
+ flexWrap: "nowrap",
1715
+ flexShrink: 0
1716
+ },
1717
+ children: [
1718
+ view === "trash" && selected.size === 0 && !hideTrash && can.restore && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Button, { variant: "danger", onClick: () => setEmptyTrashOpen(true), theme, children: labelText.emptyTrash }),
1719
+ view === "trash" && selected.size > 0 && !hideTrash && can.restore && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Button, { onClick: onRestore, theme, children: [
1720
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(UiIcon, { icon: import_react7.ArrowCounterClockwise, size: 16 }),
1721
+ " ",
1722
+ labelText.restore
1723
+ ] }),
1724
+ selected.size > 0 ? can.delete && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Button, { variant: "danger", onClick: () => setDeleteOpen(true), theme, children: [
1725
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(UiIcon, { icon: import_react7.Trash, size: 16 }),
1726
+ " ",
1727
+ view === "trash" ? labelText.deleteForever : labelText.delete
1728
+ ] }) : view === "files" ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
1729
+ can.createFolder && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Button, { onClick: () => setCreateFolderOpen(true), theme, children: [
1730
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(UiIcon, { icon: import_react7.FolderPlus, size: 16 }),
1731
+ " ",
1732
+ labelText.newFolder
1733
+ ] }),
1734
+ can.upload && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1735
+ Button,
1736
+ {
1737
+ variant: "primary",
1738
+ onClick: () => fileInputRef.current?.click(),
1739
+ theme,
1740
+ children: [
1741
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(UiIcon, { icon: import_react7.UploadSimple, size: 16 }),
1742
+ " ",
1743
+ labelText.upload
1744
+ ]
1745
+ }
1746
+ )
1747
+ ] }) : null,
1748
+ mode === "picker" && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1749
+ Button,
1750
+ {
1751
+ variant: "primary",
1752
+ onClick: () => {
1753
+ const source = searchQuery.trim() ? searchResults : entries;
1754
+ const selectedEntries = source.filter((entry) => selected.has(entry.path));
1755
+ onConfirm?.(selectedEntries);
1756
+ },
1757
+ disabled: selected.size === 0,
1758
+ theme,
1759
+ children: labelText.confirm
1760
+ }
1761
+ ),
1762
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1763
+ "input",
1764
+ {
1765
+ type: "file",
1766
+ multiple: true,
1767
+ ref: fileInputRef,
1768
+ style: { display: "none" },
1769
+ disabled: !can.upload || view !== "files",
1770
+ onChange: (e) => onUpload(e.target.files)
1771
+ }
1772
+ )
1773
+ ]
1774
+ }
1775
+ )
1776
+ ]
1777
+ }
1778
+ ),
1779
+ showBreadcrumbs && (path || view === "trash") && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1780
+ "div",
1781
+ {
1782
+ style: {
1783
+ display: "flex",
1784
+ alignItems: "center",
1785
+ gap: 6,
1786
+ fontSize: 13,
1787
+ padding: isMobile ? "8px 16px" : "10px 24px",
1788
+ borderBottom: `1px solid ${theme.border}`,
1789
+ overflowX: isMobile ? "auto" : "visible",
1790
+ whiteSpace: isMobile ? "nowrap" : "normal"
1791
+ },
1792
+ children: breadcrumbs.map((crumb, idx) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [
1793
+ idx > 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(UiIcon, { icon: import_react7.CaretRight, size: 12, color: theme.textSecondary }),
1794
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1795
+ "button",
1796
+ {
1797
+ type: "button",
1798
+ onClick: () => handleNavigate(crumb.path),
1799
+ style: {
1800
+ background: "none",
1801
+ border: "none",
1802
+ padding: 0,
1803
+ cursor: "pointer",
1804
+ fontSize: 13,
1805
+ fontWeight: idx === breadcrumbs.length - 1 ? 600 : 400,
1806
+ color: idx === breadcrumbs.length - 1 ? theme.text : theme.textSecondary
1807
+ },
1808
+ children: crumb.name
1809
+ }
1810
+ )
1811
+ ] }, idx))
1812
+ }
1813
+ ),
1814
+ (showSearch || showSort || mode !== "viewer" && showViewSwitcher) && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1815
+ "div",
1816
+ {
1817
+ style: {
1818
+ display: "flex",
1819
+ alignItems: "center",
1820
+ justifyContent: showSearch ? "flex-start" : "flex-end",
1821
+ gap: isMobile ? 8 : 12,
1822
+ padding: isMobile ? "10px 16px" : "12px 24px",
1823
+ borderBottom: `1px solid ${theme.border}`,
1824
+ flexWrap: "nowrap"
1825
+ },
1826
+ children: [
1827
+ showSearch && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1828
+ "div",
1829
+ {
1830
+ style: {
1831
+ display: "flex",
1832
+ alignItems: "center",
1833
+ gap: 8,
1834
+ flex: 1,
1835
+ minWidth: 0
1836
+ },
1837
+ children: [
1838
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1839
+ "div",
1840
+ {
1841
+ style: {
1842
+ display: "flex",
1843
+ alignItems: "center",
1844
+ gap: 8,
1845
+ flex: 1,
1846
+ minWidth: 0,
1847
+ height: toolbarControlHeight,
1848
+ padding: "0 10px",
1849
+ border: `1px solid ${theme.border}`,
1850
+ backgroundColor: theme.bg,
1851
+ color: theme.text,
1852
+ boxSizing: "border-box"
1853
+ },
1854
+ children: [
1855
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(UiIcon, { icon: import_react7.MagnifyingGlass, size: 16, color: theme.textSecondary }),
1856
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1857
+ "input",
1858
+ {
1859
+ type: "text",
1860
+ placeholder: labelText.searchPlaceholder,
1861
+ value: searchQuery,
1862
+ onChange: (e) => setSearchQuery(e.target.value),
1863
+ style: {
1864
+ flex: 1,
1865
+ minWidth: 0,
1866
+ height: "100%",
1867
+ padding: 0,
1868
+ fontSize: 13,
1869
+ border: "none",
1870
+ backgroundColor: "transparent",
1871
+ color: theme.text,
1872
+ outline: "none"
1873
+ }
1874
+ }
1875
+ )
1876
+ ]
1877
+ }
1878
+ ),
1879
+ (searchQuery || searching) && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1880
+ "button",
1881
+ {
1882
+ type: "button",
1883
+ onClick: () => {
1884
+ setSearchQuery("");
1885
+ setSearchResults([]);
1886
+ },
1887
+ disabled: searching,
1888
+ style: {
1889
+ background: "none",
1890
+ border: "none",
1891
+ cursor: searching ? "default" : "pointer",
1892
+ width: toolbarControlHeight,
1893
+ height: toolbarControlHeight,
1894
+ padding: 0,
1895
+ display: "flex",
1896
+ alignItems: "center",
1897
+ justifyContent: "center",
1898
+ color: theme.textSecondary,
1899
+ opacity: searching ? 0.5 : 1
1900
+ },
1901
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(UiIcon, { icon: import_react7.X, size: 14 })
1902
+ }
1903
+ )
1904
+ ]
1905
+ }
1906
+ ),
1907
+ (showSort || mode !== "viewer" && showViewSwitcher) && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1908
+ "div",
1909
+ {
1910
+ style: {
1911
+ display: "flex",
1912
+ alignItems: "center",
1913
+ gap: 8,
1914
+ flexShrink: 0
1915
+ },
1916
+ children: [
1917
+ mode !== "viewer" && showViewSwitcher && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1918
+ "div",
1919
+ {
1920
+ style: {
1921
+ display: "flex",
1922
+ border: `1px solid ${theme.border}`,
1923
+ flexShrink: 0,
1924
+ height: toolbarControlHeight
1925
+ },
1926
+ children: [
1927
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1928
+ "button",
1929
+ {
1930
+ type: "button",
1931
+ onClick: () => setViewMode("list"),
1932
+ style: {
1933
+ background: viewMode === "list" ? theme.selected : "none",
1934
+ border: "none",
1935
+ cursor: "pointer",
1936
+ padding: 0,
1937
+ display: "flex",
1938
+ alignItems: "center",
1939
+ justifyContent: "center",
1940
+ color: theme.text,
1941
+ width: toolbarControlHeight,
1942
+ height: toolbarControlHeight
1943
+ },
1944
+ title: "List View",
1945
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(UiIcon, { icon: import_react7.List, size: 16 })
1946
+ }
1947
+ ),
1948
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1949
+ "button",
1950
+ {
1951
+ type: "button",
1952
+ onClick: () => setViewMode("grid"),
1953
+ style: {
1954
+ background: viewMode === "grid" ? theme.selected : "none",
1955
+ border: "none",
1956
+ cursor: "pointer",
1957
+ padding: 0,
1958
+ display: "flex",
1959
+ alignItems: "center",
1960
+ justifyContent: "center",
1961
+ color: theme.text,
1962
+ width: toolbarControlHeight,
1963
+ height: toolbarControlHeight
1964
+ },
1965
+ title: "Grid View",
1966
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(UiIcon, { icon: import_react7.SquaresFour, size: 16 })
1967
+ }
1968
+ )
1969
+ ]
1970
+ }
1971
+ ),
1972
+ showSort && viewMode === "grid" && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1973
+ "button",
1974
+ {
1975
+ type: "button",
1976
+ onClick: () => setSortOrder(sortOrder === "asc" ? "desc" : "asc"),
1977
+ style: {
1978
+ background: theme.bg,
1979
+ border: `1px solid ${theme.border}`,
1980
+ cursor: "pointer",
1981
+ padding: 0,
1982
+ display: "flex",
1983
+ alignItems: "center",
1984
+ justifyContent: "center",
1985
+ color: theme.text,
1986
+ width: toolbarControlHeight,
1987
+ height: toolbarControlHeight
1988
+ },
1989
+ title: `Sort ${sortOrder === "asc" ? "Descending" : "Ascending"}`,
1990
+ children: sortOrder === "asc" ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(UiIcon, { icon: import_react7.ArrowUp, size: 16 }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(UiIcon, { icon: import_react7.ArrowDown, size: 16 })
1991
+ }
1992
+ )
1993
+ ]
1994
+ }
1995
+ )
1996
+ ]
1997
+ }
1998
+ ),
1999
+ showSearch && searchQuery.trim() && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2000
+ "div",
2001
+ {
2002
+ style: {
2003
+ padding: "8px 24px",
2004
+ backgroundColor: theme.bgSecondary,
2005
+ borderBottom: `1px solid ${theme.border}`,
2006
+ fontSize: 12,
2007
+ color: theme.textSecondary,
2008
+ display: "flex",
2009
+ alignItems: "center",
2010
+ justifyContent: "space-between"
2011
+ },
2012
+ children: [
2013
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [
2014
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(UiIcon, { icon: import_react7.MagnifyingGlass, size: 14 }),
2015
+ searching ? `Searching for "${searchQuery}"...` : `Found ${currentEntries.length} result${currentEntries.length !== 1 ? "s" : ""} for "${searchQuery}"`
2016
+ ] }),
2017
+ searchHasMore && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: { fontSize: 11, opacity: 0.7 }, children: "More results available" })
2018
+ ]
2019
+ }
2020
+ ),
2021
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2022
+ "div",
2023
+ {
2024
+ style: {
2025
+ flex: 1,
2026
+ overflow: "auto",
2027
+ minHeight: 0,
2028
+ // This is important for flex children to shrink properly
2029
+ WebkitOverflowScrolling: "touch",
2030
+ position: "relative"
2031
+ },
2032
+ ref: listRef,
2033
+ onScroll: handleScroll,
2034
+ onMouseDown: (e) => {
2035
+ if (isMobile) return;
2036
+ if (e.button !== 0) return;
2037
+ const target = e.target;
2038
+ if (target.closest("[data-entry-path]")) return;
2039
+ const container = listRef.current;
2040
+ if (!container) return;
2041
+ e.preventDefault();
2042
+ const rect = container.getBoundingClientRect();
2043
+ const startX = e.clientX - rect.left + container.scrollLeft;
2044
+ const startY = e.clientY - rect.top + container.scrollTop;
2045
+ dragSelectionBaseRef.current = new Set(selected);
2046
+ setDragSelect({
2047
+ active: true,
2048
+ startX,
2049
+ startY,
2050
+ x: startX,
2051
+ y: startY,
2052
+ additive: e.metaKey || e.ctrlKey
2053
+ });
2054
+ },
2055
+ children: [
2056
+ dragSelect?.active && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2057
+ "div",
2058
+ {
2059
+ style: {
2060
+ position: "absolute",
2061
+ left: Math.min(dragSelect.startX, dragSelect.x),
2062
+ top: Math.min(dragSelect.startY, dragSelect.y),
2063
+ width: Math.abs(dragSelect.x - dragSelect.startX),
2064
+ height: Math.abs(dragSelect.y - dragSelect.startY),
2065
+ border: `1px solid ${theme.accent}`,
2066
+ backgroundColor: theme.bg === "#ffffff" ? "rgba(0,0,0,0.04)" : "rgba(255,255,255,0.08)",
2067
+ pointerEvents: "none",
2068
+ zIndex: 3
2069
+ }
2070
+ }
2071
+ ),
2072
+ viewMode === "list" && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2073
+ "div",
2074
+ {
2075
+ className: FileManager_default.tableHeader,
2076
+ style: {
2077
+ gridTemplateColumns: isMobile ? "1fr 48px" : "1fr 120px 100px 48px",
2078
+ padding: isMobile ? "10px 16px" : "10px 24px"
2079
+ },
2080
+ children: [
2081
+ showSort ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2082
+ "button",
2083
+ {
2084
+ type: "button",
2085
+ onClick: () => {
2086
+ if (sortBy === "name") {
2087
+ setSortOrder(sortOrder === "asc" ? "desc" : "asc");
2088
+ } else {
2089
+ setSortBy("name");
2090
+ setSortOrder("asc");
2091
+ }
2092
+ },
2093
+ style: {
2094
+ background: "none",
2095
+ border: "none",
2096
+ cursor: "pointer",
2097
+ display: "flex",
2098
+ alignItems: "center",
2099
+ gap: 4,
2100
+ fontSize: 12,
2101
+ fontWeight: 600,
2102
+ color: sortBy === "name" ? theme.text : theme.textSecondary,
2103
+ textTransform: "uppercase",
2104
+ letterSpacing: "0.05em",
2105
+ padding: 0
2106
+ },
2107
+ children: [
2108
+ "Name",
2109
+ sortBy === "name" && (sortOrder === "asc" ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(UiIcon, { icon: import_react7.CaretUp, size: 12 }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(UiIcon, { icon: import_react7.CaretDown, size: 12 }))
2110
+ ]
2111
+ }
2112
+ ) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: headerLabelStyle, children: "Name" }),
2113
+ !isMobile && (showSort ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2114
+ "button",
2115
+ {
2116
+ type: "button",
2117
+ onClick: () => {
2118
+ if (sortBy === "date") {
2119
+ setSortOrder(sortOrder === "asc" ? "desc" : "asc");
2120
+ } else {
2121
+ setSortBy("date");
2122
+ setSortOrder("desc");
2123
+ }
2124
+ },
2125
+ style: {
2126
+ background: "none",
2127
+ border: "none",
2128
+ cursor: "pointer",
2129
+ display: "flex",
2130
+ alignItems: "center",
2131
+ gap: 4,
2132
+ fontSize: 12,
2133
+ fontWeight: 600,
2134
+ color: sortBy === "date" ? theme.text : theme.textSecondary,
2135
+ textTransform: "uppercase",
2136
+ letterSpacing: "0.05em",
2137
+ padding: 0
2138
+ },
2139
+ children: [
2140
+ "Date",
2141
+ sortBy === "date" && (sortOrder === "asc" ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(UiIcon, { icon: import_react7.CaretUp, size: 12 }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(UiIcon, { icon: import_react7.CaretDown, size: 12 }))
2142
+ ]
2143
+ }
2144
+ ) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: headerLabelStyle, children: "Date" })),
2145
+ !isMobile && (showSort ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2146
+ "button",
2147
+ {
2148
+ type: "button",
2149
+ onClick: () => {
2150
+ if (sortBy === "size") {
2151
+ setSortOrder(sortOrder === "asc" ? "desc" : "asc");
2152
+ } else {
2153
+ setSortBy("size");
2154
+ setSortOrder("desc");
2155
+ }
2156
+ },
2157
+ style: {
2158
+ background: "none",
2159
+ border: "none",
2160
+ cursor: "pointer",
2161
+ display: "flex",
2162
+ alignItems: "center",
2163
+ gap: 4,
2164
+ fontSize: 12,
2165
+ fontWeight: 600,
2166
+ color: sortBy === "size" ? theme.text : theme.textSecondary,
2167
+ textTransform: "uppercase",
2168
+ letterSpacing: "0.05em",
2169
+ padding: 0
2170
+ },
2171
+ children: [
2172
+ "Size",
2173
+ sortBy === "size" && (sortOrder === "asc" ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(UiIcon, { icon: import_react7.CaretUp, size: 12 }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(UiIcon, { icon: import_react7.CaretDown, size: 12 }))
2174
+ ]
2175
+ }
2176
+ ) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: headerLabelStyle, children: "Size" })),
2177
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", {})
2178
+ ]
2179
+ }
2180
+ ),
2181
+ loading || searching ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2182
+ "div",
2183
+ {
2184
+ style: {
2185
+ padding: 40,
2186
+ textAlign: "center",
2187
+ color: theme.textSecondary
2188
+ },
2189
+ children: searching ? "Searching..." : "Loading..."
2190
+ }
2191
+ ) : viewMode === "grid" ? (
2192
+ // Grid View
2193
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_jsx_runtime5.Fragment, { children: (() => {
2194
+ const gridTileGap = 10;
2195
+ const gridTileMinHeight = isMobile ? 120 : 140;
2196
+ const gridThumbSize = 64;
2197
+ const gridIconSize = 48;
2198
+ const gridThumbStyle = {
2199
+ width: gridThumbSize,
2200
+ height: gridThumbSize,
2201
+ display: "flex",
2202
+ alignItems: "center",
2203
+ justifyContent: "center",
2204
+ flexShrink: 0
2205
+ };
2206
+ const renderGridThumb = (entry) => {
2207
+ if (entry.type === "folder") {
2208
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2209
+ UiIcon,
2210
+ {
2211
+ icon: entry.path === `${TRASH_PATH}/` ? import_react7.Trash : import_react7.Folder,
2212
+ size: gridIconSize,
2213
+ weight: "fill",
2214
+ color: theme.text,
2215
+ boxed: true
2216
+ }
2217
+ );
2218
+ }
2219
+ const previewUrl = inlinePreviews[entry.path];
2220
+ if (previewUrl && isImageFileName(entry.name)) {
2221
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2222
+ "img",
2223
+ {
2224
+ src: previewUrl,
2225
+ alt: entry.name,
2226
+ loading: "lazy",
2227
+ decoding: "async",
2228
+ style: {
2229
+ width: gridThumbSize,
2230
+ height: gridThumbSize,
2231
+ objectFit: "cover",
2232
+ borderRadius: 8,
2233
+ border: `1px solid ${theme.border}`
2234
+ }
2235
+ }
2236
+ );
2237
+ }
2238
+ return getFileIcon(entry.name, gridIconSize);
2239
+ };
2240
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2241
+ "div",
2242
+ {
2243
+ style: {
2244
+ display: "grid",
2245
+ gridTemplateColumns: isMobile ? "repeat(auto-fill, minmax(120px, 1fr))" : "repeat(auto-fill, minmax(140px, 1fr))",
2246
+ gap: 16,
2247
+ padding: isMobile ? 16 : 24
2248
+ },
2249
+ children: (() => {
2250
+ const processEntries = (entries2) => {
2251
+ let filtered = entries2;
2252
+ filtered = [...filtered].sort((a, b) => {
2253
+ let comparison = 0;
2254
+ const aIsTrash = view === "files" && a.type === "folder" && a.path === `${TRASH_PATH}/`;
2255
+ const bIsTrash = view === "files" && b.type === "folder" && b.path === `${TRASH_PATH}/`;
2256
+ if (aIsTrash !== bIsTrash) return aIsTrash ? 1 : -1;
2257
+ if (a.type !== b.type) {
2258
+ return a.type === "folder" ? -1 : 1;
2259
+ }
2260
+ switch (sortBy) {
2261
+ case "name":
2262
+ comparison = a.name.localeCompare(b.name);
2263
+ break;
2264
+ case "date":
2265
+ const aDate = a.type === "file" && a.lastModified ? new Date(a.lastModified).getTime() : 0;
2266
+ const bDate = b.type === "file" && b.lastModified ? new Date(b.lastModified).getTime() : 0;
2267
+ comparison = aDate - bDate;
2268
+ break;
2269
+ case "size":
2270
+ comparison = (a.type === "file" ? a.size || 0 : 0) - (b.type === "file" ? b.size || 0 : 0);
2271
+ break;
2272
+ case "type":
2273
+ const aExt = a.name.split(".").pop()?.toLowerCase() || "";
2274
+ const bExt = b.name.split(".").pop()?.toLowerCase() || "";
2275
+ comparison = aExt.localeCompare(bExt);
2276
+ break;
2277
+ }
2278
+ return sortOrder === "asc" ? comparison : -comparison;
2279
+ });
2280
+ return filtered;
2281
+ };
2282
+ const filteredEntries = processEntries(currentEntries);
2283
+ const selectedEntries = filteredEntries.filter(
2284
+ (item) => selected.has(item.path)
2285
+ );
2286
+ return filteredEntries.map((entry, index) => {
2287
+ const isSelected = selected.has(entry.path);
2288
+ const isTrashFolder = view === "files" && entry.type === "folder" && entry.path === `${TRASH_PATH}/`;
2289
+ const entryLabel = isTrashFolder ? "Trash" : entry.name;
2290
+ const isMultiSelected = selectedEntries.length > 1 && isSelected;
2291
+ const actionEntries = isMultiSelected ? selectedEntries : [entry];
2292
+ const hasContextMenuItems = !isMultiSelected && can.rename || actionEntries.some((item) => item.type === "file") || view === "files" && can.copy || isMultiSelected && view === "files" && can.move || isMultiSelected && view === "trash" && can.restore || can.delete;
2293
+ if (isTrashFolder) {
2294
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2295
+ "div",
2296
+ {
2297
+ "data-entry-path": entry.path,
2298
+ "data-entry-selectable": "false",
2299
+ style: {
2300
+ display: "flex",
2301
+ flexDirection: "column",
2302
+ alignItems: "center",
2303
+ justifyContent: "center",
2304
+ textAlign: "center",
2305
+ gap: gridTileGap,
2306
+ padding: 16,
2307
+ minHeight: gridTileMinHeight,
2308
+ backgroundColor: isSelected ? theme.selected : hoverRow === entry.path ? theme.hover : theme.bg,
2309
+ border: `1px solid ${isSelected ? theme.accent : theme.border}`,
2310
+ cursor: "pointer",
2311
+ outline: "none",
2312
+ transition: "all 0.15s",
2313
+ position: "relative"
2314
+ },
2315
+ onClick: (e) => handleEntryClickWithSelection(entry, index, filteredEntries, e),
2316
+ onDoubleClick: () => openEntry(entry),
2317
+ onMouseEnter: () => setHoverRow(entry.path),
2318
+ onMouseLeave: () => setHoverRow(null),
2319
+ onTouchStart: () => {
2320
+ if (!isMobile) return;
2321
+ if (longPressTimerRef.current)
2322
+ window.clearTimeout(longPressTimerRef.current);
2323
+ longPressTimerRef.current = window.setTimeout(() => {
2324
+ handleLongPressSelect(entry);
2325
+ }, 350);
2326
+ },
2327
+ onTouchMove: () => {
2328
+ if (longPressTimerRef.current) {
2329
+ window.clearTimeout(longPressTimerRef.current);
2330
+ longPressTimerRef.current = null;
2331
+ }
2332
+ },
2333
+ onTouchEnd: () => {
2334
+ if (longPressTimerRef.current) {
2335
+ window.clearTimeout(longPressTimerRef.current);
2336
+ longPressTimerRef.current = null;
2337
+ }
2338
+ },
2339
+ children: [
2340
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: gridThumbStyle, children: renderGridThumb(entry) }),
2341
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2342
+ "div",
2343
+ {
2344
+ style: {
2345
+ fontSize: 12,
2346
+ fontWeight: 500,
2347
+ wordBreak: "break-word",
2348
+ color: theme.text,
2349
+ width: "100%"
2350
+ },
2351
+ children: entryLabel
2352
+ }
2353
+ )
2354
+ ]
2355
+ },
2356
+ entry.path
2357
+ );
2358
+ }
2359
+ if (!hasContextMenuItems) {
2360
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2361
+ "div",
2362
+ {
2363
+ "data-entry-path": entry.path,
2364
+ "data-entry-selectable": isTrashFolder ? "false" : "true",
2365
+ style: {
2366
+ display: "flex",
2367
+ flexDirection: "column",
2368
+ alignItems: "center",
2369
+ justifyContent: "center",
2370
+ textAlign: "center",
2371
+ gap: gridTileGap,
2372
+ padding: 16,
2373
+ minHeight: gridTileMinHeight,
2374
+ backgroundColor: isSelected ? theme.selected : hoverRow === entry.path ? theme.hover : theme.bg,
2375
+ border: `1px solid ${isSelected ? theme.accent : theme.border}`,
2376
+ cursor: "pointer",
2377
+ outline: "none",
2378
+ transition: "all 0.15s",
2379
+ position: "relative"
2380
+ },
2381
+ onClick: (e) => handleEntryClickWithSelection(entry, index, filteredEntries, e),
2382
+ onDoubleClick: () => openEntry(entry),
2383
+ onMouseEnter: () => setHoverRow(entry.path),
2384
+ onMouseLeave: () => setHoverRow(null),
2385
+ onTouchStart: () => {
2386
+ if (!isMobile) return;
2387
+ if (longPressTimerRef.current)
2388
+ window.clearTimeout(longPressTimerRef.current);
2389
+ longPressTimerRef.current = window.setTimeout(() => {
2390
+ handleLongPressSelect(entry);
2391
+ }, 350);
2392
+ },
2393
+ onTouchMove: () => {
2394
+ if (longPressTimerRef.current) {
2395
+ window.clearTimeout(longPressTimerRef.current);
2396
+ longPressTimerRef.current = null;
2397
+ }
2398
+ },
2399
+ onTouchEnd: () => {
2400
+ if (longPressTimerRef.current) {
2401
+ window.clearTimeout(longPressTimerRef.current);
2402
+ longPressTimerRef.current = null;
2403
+ }
2404
+ },
2405
+ children: [
2406
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: gridThumbStyle, children: renderGridThumb(entry) }),
2407
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2408
+ "div",
2409
+ {
2410
+ style: {
2411
+ fontSize: 12,
2412
+ fontWeight: 500,
2413
+ wordBreak: "break-word",
2414
+ color: theme.text,
2415
+ width: "100%"
2416
+ },
2417
+ children: entryLabel
2418
+ }
2419
+ )
2420
+ ]
2421
+ },
2422
+ entry.path
2423
+ );
2424
+ }
2425
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2426
+ import_react8.ContextMenu.Root,
2427
+ {
2428
+ onOpenChange: (open) => {
2429
+ if (open) {
2430
+ if (isTrashFolder) return;
2431
+ if (!selected.has(entry.path)) {
2432
+ setSelected(/* @__PURE__ */ new Set([entry.path]));
2433
+ selectionAnchorRef.current = entry.path;
2434
+ setLastSelected(null);
2435
+ setIsSelectionMode(true);
2436
+ }
2437
+ }
2438
+ },
2439
+ children: [
2440
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2441
+ import_react8.ContextMenu.Trigger,
2442
+ {
2443
+ "data-entry-path": entry.path,
2444
+ "data-entry-selectable": isTrashFolder ? "false" : "true",
2445
+ style: {
2446
+ display: "flex",
2447
+ flexDirection: "column",
2448
+ alignItems: "center",
2449
+ justifyContent: "center",
2450
+ textAlign: "center",
2451
+ gap: gridTileGap,
2452
+ padding: 16,
2453
+ minHeight: gridTileMinHeight,
2454
+ backgroundColor: isSelected ? theme.selected : hoverRow === entry.path ? theme.hover : theme.bg,
2455
+ border: `1px solid ${isSelected ? theme.accent : theme.border}`,
2456
+ cursor: "pointer",
2457
+ outline: "none",
2458
+ transition: "all 0.15s",
2459
+ position: "relative"
2460
+ },
2461
+ onClick: (e) => handleEntryClickWithSelection(entry, index, filteredEntries, e),
2462
+ onDoubleClick: () => openEntry(entry),
2463
+ onContextMenu: (e) => {
2464
+ if (isTrashFolder) {
2465
+ e.preventDefault();
2466
+ e.stopPropagation();
2467
+ }
2468
+ },
2469
+ onMouseEnter: () => setHoverRow(entry.path),
2470
+ onMouseLeave: () => setHoverRow(null),
2471
+ onTouchStart: () => {
2472
+ if (!isMobile) return;
2473
+ if (longPressTimerRef.current)
2474
+ window.clearTimeout(longPressTimerRef.current);
2475
+ longPressTimerRef.current = window.setTimeout(() => {
2476
+ handleLongPressSelect(entry);
2477
+ }, 350);
2478
+ },
2479
+ onTouchMove: () => {
2480
+ if (longPressTimerRef.current) {
2481
+ window.clearTimeout(longPressTimerRef.current);
2482
+ longPressTimerRef.current = null;
2483
+ }
2484
+ },
2485
+ onTouchEnd: () => {
2486
+ if (longPressTimerRef.current) {
2487
+ window.clearTimeout(longPressTimerRef.current);
2488
+ longPressTimerRef.current = null;
2489
+ }
2490
+ },
2491
+ children: [
2492
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: gridThumbStyle, children: renderGridThumb(entry) }),
2493
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2494
+ "div",
2495
+ {
2496
+ style: {
2497
+ fontSize: 12,
2498
+ fontWeight: 500,
2499
+ wordBreak: "break-word",
2500
+ color: theme.text,
2501
+ width: "100%"
2502
+ },
2503
+ children: entryLabel
2504
+ }
2505
+ )
2506
+ ]
2507
+ }
2508
+ ),
2509
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react8.ContextMenu.Portal, { container: portalContainer, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react8.ContextMenu.Positioner, { style: { zIndex: 1e4 }, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2510
+ import_react8.ContextMenu.Popup,
2511
+ {
2512
+ style: {
2513
+ zIndex: 1e4,
2514
+ backgroundColor: theme.bg,
2515
+ border: `1px solid ${theme.border}`,
2516
+ boxShadow: "0 4px 12px rgba(0,0,0,0.1)",
2517
+ padding: 4,
2518
+ display: "flex",
2519
+ flexDirection: "column",
2520
+ minWidth: 160,
2521
+ outline: "none"
2522
+ },
2523
+ children: [
2524
+ !isMultiSelected && can.rename && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2525
+ import_react8.ContextMenu.Item,
2526
+ {
2527
+ style: {
2528
+ display: "flex",
2529
+ alignItems: "center",
2530
+ gap: 10,
2531
+ padding: "8px 12px",
2532
+ border: "none",
2533
+ background: "none",
2534
+ cursor: "pointer",
2535
+ fontSize: 13,
2536
+ textAlign: "left",
2537
+ color: theme.text,
2538
+ outline: "none"
2539
+ },
2540
+ onClick: () => {
2541
+ setRenameName(entry.name || "");
2542
+ setRenameTarget(entry);
2543
+ setRenameOpen(true);
2544
+ },
2545
+ children: [
2546
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(UiIcon, { icon: import_react7.PencilSimple, size: 16 }),
2547
+ " Rename"
2548
+ ]
2549
+ }
2550
+ ),
2551
+ actionEntries.some((item) => item.type === "file") && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2552
+ import_react8.ContextMenu.Item,
2553
+ {
2554
+ style: {
2555
+ display: "flex",
2556
+ alignItems: "center",
2557
+ gap: 10,
2558
+ padding: "8px 12px",
2559
+ border: "none",
2560
+ background: "none",
2561
+ cursor: "pointer",
2562
+ fontSize: 13,
2563
+ textAlign: "left",
2564
+ color: theme.text,
2565
+ outline: "none"
2566
+ },
2567
+ onClick: () => bulkDownload(actionEntries),
2568
+ children: [
2569
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(UiIcon, { icon: import_react7.DownloadSimple, size: 16 }),
2570
+ " Download"
2571
+ ]
2572
+ }
2573
+ ),
2574
+ isMultiSelected && view === "files" && can.move && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2575
+ import_react8.ContextMenu.Item,
2576
+ {
2577
+ style: {
2578
+ display: "flex",
2579
+ alignItems: "center",
2580
+ gap: 10,
2581
+ padding: "8px 12px",
2582
+ border: "none",
2583
+ background: "none",
2584
+ cursor: "pointer",
2585
+ fontSize: 13,
2586
+ textAlign: "left",
2587
+ color: theme.text,
2588
+ outline: "none"
2589
+ },
2590
+ onClick: () => bulkMove(actionEntries),
2591
+ children: [
2592
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(UiIcon, { icon: import_react7.ArrowRight, size: 16 }),
2593
+ " Move"
2594
+ ]
2595
+ }
2596
+ ),
2597
+ view === "files" && can.copy && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2598
+ import_react8.ContextMenu.Item,
2599
+ {
2600
+ style: {
2601
+ display: "flex",
2602
+ alignItems: "center",
2603
+ gap: 10,
2604
+ padding: "8px 12px",
2605
+ border: "none",
2606
+ background: "none",
2607
+ cursor: "pointer",
2608
+ fontSize: 13,
2609
+ textAlign: "left",
2610
+ color: theme.text,
2611
+ outline: "none"
2612
+ },
2613
+ onClick: () => bulkCopy(actionEntries),
2614
+ children: [
2615
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(UiIcon, { icon: import_react7.Copy, size: 16 }),
2616
+ " Copy"
2617
+ ]
2618
+ }
2619
+ ),
2620
+ isMultiSelected && view === "trash" && can.restore && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2621
+ import_react8.ContextMenu.Item,
2622
+ {
2623
+ style: {
2624
+ display: "flex",
2625
+ alignItems: "center",
2626
+ gap: 10,
2627
+ padding: "8px 12px",
2628
+ border: "none",
2629
+ background: "none",
2630
+ cursor: "pointer",
2631
+ fontSize: 13,
2632
+ textAlign: "left",
2633
+ color: theme.text,
2634
+ outline: "none"
2635
+ },
2636
+ onClick: () => restoreEntries(actionEntries),
2637
+ children: [
2638
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(UiIcon, { icon: import_react7.ArrowCounterClockwise, size: 16 }),
2639
+ " Restore"
2640
+ ]
2641
+ }
2642
+ ),
2643
+ can.delete && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2644
+ import_react8.ContextMenu.Item,
2645
+ {
2646
+ style: {
2647
+ display: "flex",
2648
+ alignItems: "center",
2649
+ gap: 10,
2650
+ padding: "8px 12px",
2651
+ border: "none",
2652
+ background: "none",
2653
+ cursor: "pointer",
2654
+ fontSize: 13,
2655
+ textAlign: "left",
2656
+ color: theme.danger,
2657
+ outline: "none"
2658
+ },
2659
+ onClick: () => {
2660
+ setSelected(new Set(actionEntries.map((item) => item.path)));
2661
+ setLastSelected(null);
2662
+ setIsSelectionMode(true);
2663
+ setDeleteOpen(true);
2664
+ },
2665
+ children: [
2666
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(UiIcon, { icon: import_react7.Trash, size: 16 }),
2667
+ " Delete"
2668
+ ]
2669
+ }
2670
+ )
2671
+ ]
2672
+ }
2673
+ ) }) })
2674
+ ]
2675
+ },
2676
+ entry.path
2677
+ );
2678
+ });
2679
+ })()
2680
+ }
2681
+ );
2682
+ })() })
2683
+ ) : (
2684
+ // List View
2685
+ (() => {
2686
+ const processEntries = (entries2) => {
2687
+ let filtered = entries2;
2688
+ filtered = [...filtered].sort((a, b) => {
2689
+ let comparison = 0;
2690
+ const aIsTrash = view === "files" && a.type === "folder" && a.path === `${TRASH_PATH}/`;
2691
+ const bIsTrash = view === "files" && b.type === "folder" && b.path === `${TRASH_PATH}/`;
2692
+ if (aIsTrash !== bIsTrash) return aIsTrash ? 1 : -1;
2693
+ if (a.type !== b.type) {
2694
+ return a.type === "folder" ? -1 : 1;
2695
+ }
2696
+ switch (sortBy) {
2697
+ case "name":
2698
+ comparison = a.name.localeCompare(b.name);
2699
+ break;
2700
+ case "date":
2701
+ const aDate = a.type === "file" && a.lastModified ? new Date(a.lastModified).getTime() : 0;
2702
+ const bDate = b.type === "file" && b.lastModified ? new Date(b.lastModified).getTime() : 0;
2703
+ comparison = aDate - bDate;
2704
+ break;
2705
+ case "size":
2706
+ comparison = (a.type === "file" ? a.size || 0 : 0) - (b.type === "file" ? b.size || 0 : 0);
2707
+ break;
2708
+ case "type":
2709
+ const aExt = a.name.split(".").pop()?.toLowerCase() || "";
2710
+ const bExt = b.name.split(".").pop()?.toLowerCase() || "";
2711
+ comparison = aExt.localeCompare(bExt);
2712
+ break;
2713
+ }
2714
+ return sortOrder === "asc" ? comparison : -comparison;
2715
+ });
2716
+ return filtered;
2717
+ };
2718
+ const filteredEntries = processEntries(currentEntries);
2719
+ const selectedEntries = filteredEntries.filter((item) => selected.has(item.path));
2720
+ return filteredEntries.map((entry, index) => {
2721
+ const isSelected = selected.has(entry.path);
2722
+ const isHovered = hoverRow === entry.path;
2723
+ const isTrashFolder = view === "files" && entry.type === "folder" && entry.path === `${TRASH_PATH}/`;
2724
+ const entryLabel = isTrashFolder ? "Trash" : entry.name;
2725
+ const isMultiSelected = selectedEntries.length > 1 && isSelected;
2726
+ const actionEntries = isMultiSelected ? selectedEntries : [entry];
2727
+ const hasContextMenuItems = !isMultiSelected && can.rename || actionEntries.some((item) => item.type === "file") || view === "files" && can.copy || isMultiSelected && view === "files" && can.move || isMultiSelected && view === "trash" && can.restore || can.delete;
2728
+ if (isTrashFolder) {
2729
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2730
+ "div",
2731
+ {
2732
+ "data-entry-path": entry.path,
2733
+ "data-entry-selectable": "false",
2734
+ className: FileManager_default.row,
2735
+ style: {
2736
+ gridTemplateColumns: isMobile ? "1fr 48px" : "1fr 120px 100px 48px",
2737
+ padding: isMobile ? "12px 16px" : "12px 24px",
2738
+ backgroundColor: isSelected ? theme.selected : isHovered ? theme.hover : theme.bg,
2739
+ outline: "none"
2740
+ },
2741
+ onClick: (e) => {
2742
+ if (e.button !== 0) return;
2743
+ handleEntryClickWithSelection(entry, index, filteredEntries, e);
2744
+ },
2745
+ onDoubleClick: () => openEntry(entry),
2746
+ onMouseEnter: () => setHoverRow(entry.path),
2747
+ onMouseLeave: () => setHoverRow(null),
2748
+ onTouchStart: () => {
2749
+ if (!isMobile) return;
2750
+ if (longPressTimerRef.current)
2751
+ window.clearTimeout(longPressTimerRef.current);
2752
+ longPressTimerRef.current = window.setTimeout(() => {
2753
+ handleLongPressSelect(entry);
2754
+ }, 350);
2755
+ },
2756
+ onTouchMove: () => {
2757
+ if (longPressTimerRef.current) {
2758
+ window.clearTimeout(longPressTimerRef.current);
2759
+ longPressTimerRef.current = null;
2760
+ }
2761
+ },
2762
+ onTouchEnd: () => {
2763
+ if (longPressTimerRef.current) {
2764
+ window.clearTimeout(longPressTimerRef.current);
2765
+ longPressTimerRef.current = null;
2766
+ }
2767
+ },
2768
+ children: [
2769
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2770
+ "div",
2771
+ {
2772
+ style: {
2773
+ display: "flex",
2774
+ alignItems: isMobile ? "flex-start" : "center",
2775
+ gap: 12,
2776
+ fontWeight: 500,
2777
+ flexDirection: isMobile ? "column" : "row"
2778
+ },
2779
+ children: [
2780
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2781
+ "div",
2782
+ {
2783
+ style: {
2784
+ display: "flex",
2785
+ alignItems: "center",
2786
+ gap: 12
2787
+ },
2788
+ children: [
2789
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(UiIcon, { icon: import_react7.Trash, size: 20, weight: "fill", color: theme.text, boxed: true }),
2790
+ entryLabel
2791
+ ]
2792
+ }
2793
+ ),
2794
+ isMobile && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2795
+ "div",
2796
+ {
2797
+ style: {
2798
+ fontSize: 11,
2799
+ color: theme.textSecondary,
2800
+ fontWeight: 400
2801
+ },
2802
+ children: "Folder"
2803
+ }
2804
+ )
2805
+ ]
2806
+ }
2807
+ ),
2808
+ !isMobile && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: { color: theme.textSecondary }, children: "--" }),
2809
+ !isMobile && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2810
+ "div",
2811
+ {
2812
+ style: {
2813
+ color: theme.textSecondary,
2814
+ fontFamily: "monospace"
2815
+ },
2816
+ children: "--"
2817
+ }
2818
+ ),
2819
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", {})
2820
+ ]
2821
+ },
2822
+ entry.path
2823
+ );
2824
+ }
2825
+ if (!hasContextMenuItems) {
2826
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2827
+ "div",
2828
+ {
2829
+ "data-entry-path": entry.path,
2830
+ "data-entry-selectable": isTrashFolder ? "false" : "true",
2831
+ className: FileManager_default.row,
2832
+ style: {
2833
+ gridTemplateColumns: isMobile ? "1fr 48px" : "1fr 120px 100px 48px",
2834
+ padding: isMobile ? "12px 16px" : "12px 24px",
2835
+ backgroundColor: isSelected ? theme.selected : isHovered ? theme.hover : theme.bg,
2836
+ outline: "none"
2837
+ },
2838
+ onClick: (e) => {
2839
+ if (e.button !== 0) return;
2840
+ handleEntryClickWithSelection(entry, index, filteredEntries, e);
2841
+ },
2842
+ onDoubleClick: () => openEntry(entry),
2843
+ onMouseEnter: () => setHoverRow(entry.path),
2844
+ onMouseLeave: () => setHoverRow(null),
2845
+ onTouchStart: () => {
2846
+ if (!isMobile) return;
2847
+ if (longPressTimerRef.current)
2848
+ window.clearTimeout(longPressTimerRef.current);
2849
+ longPressTimerRef.current = window.setTimeout(() => {
2850
+ handleLongPressSelect(entry);
2851
+ }, 350);
2852
+ },
2853
+ onTouchMove: () => {
2854
+ if (longPressTimerRef.current) {
2855
+ window.clearTimeout(longPressTimerRef.current);
2856
+ longPressTimerRef.current = null;
2857
+ }
2858
+ },
2859
+ onTouchEnd: () => {
2860
+ if (longPressTimerRef.current) {
2861
+ window.clearTimeout(longPressTimerRef.current);
2862
+ longPressTimerRef.current = null;
2863
+ }
2864
+ },
2865
+ children: [
2866
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2867
+ "div",
2868
+ {
2869
+ style: {
2870
+ display: "flex",
2871
+ alignItems: isMobile ? "flex-start" : "center",
2872
+ gap: 12,
2873
+ fontWeight: 500,
2874
+ flexDirection: isMobile ? "column" : "row"
2875
+ },
2876
+ children: [
2877
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2878
+ "div",
2879
+ {
2880
+ style: {
2881
+ display: "flex",
2882
+ alignItems: "center",
2883
+ gap: 12
2884
+ },
2885
+ children: [
2886
+ entry.type === "folder" ? isTrashFolder ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2887
+ UiIcon,
2888
+ {
2889
+ icon: import_react7.Trash,
2890
+ size: 20,
2891
+ weight: "fill",
2892
+ color: theme.text,
2893
+ boxed: true
2894
+ }
2895
+ ) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2896
+ UiIcon,
2897
+ {
2898
+ icon: import_react7.Folder,
2899
+ size: 20,
2900
+ weight: "fill",
2901
+ color: theme.text,
2902
+ boxed: true
2903
+ }
2904
+ ) : (() => {
2905
+ const previewUrl = inlinePreviews[entry.path];
2906
+ if (previewUrl && isImageFileName(entry.name)) {
2907
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2908
+ "img",
2909
+ {
2910
+ src: previewUrl,
2911
+ alt: entry.name,
2912
+ loading: "lazy",
2913
+ decoding: "async",
2914
+ style: {
2915
+ width: 20,
2916
+ height: 20,
2917
+ objectFit: "cover",
2918
+ borderRadius: 4,
2919
+ border: `1px solid ${theme.border}`
2920
+ }
2921
+ }
2922
+ );
2923
+ }
2924
+ return getFileIcon(entry.name);
2925
+ })(),
2926
+ entryLabel
2927
+ ]
2928
+ }
2929
+ ),
2930
+ isMobile && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2931
+ "div",
2932
+ {
2933
+ style: {
2934
+ fontSize: 11,
2935
+ color: theme.textSecondary,
2936
+ fontWeight: 400
2937
+ },
2938
+ children: entry.type === "file" ? `${entry.lastModified ? formatDate(entry.lastModified) : "--"} | ${formatBytes(entry.size || 0)}` : "Folder"
2939
+ }
2940
+ )
2941
+ ]
2942
+ }
2943
+ ),
2944
+ !isMobile && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: { color: theme.textSecondary }, children: entry.type === "file" && entry.lastModified ? formatDate(entry.lastModified) : "--" }),
2945
+ !isMobile && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2946
+ "div",
2947
+ {
2948
+ style: {
2949
+ color: theme.textSecondary,
2950
+ fontFamily: "monospace"
2951
+ },
2952
+ children: entry.type === "file" ? formatBytes(entry.size || 0) : "--"
2953
+ }
2954
+ ),
2955
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", {})
2956
+ ]
2957
+ },
2958
+ entry.path
2959
+ );
2960
+ }
2961
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2962
+ import_react8.ContextMenu.Root,
2963
+ {
2964
+ onOpenChange: (open) => {
2965
+ if (open) {
2966
+ if (isTrashFolder) return;
2967
+ if (!selected.has(entry.path)) {
2968
+ setSelected(/* @__PURE__ */ new Set([entry.path]));
2969
+ selectionAnchorRef.current = entry.path;
2970
+ setLastSelected(null);
2971
+ setIsSelectionMode(true);
2972
+ }
2973
+ }
2974
+ },
2975
+ children: [
2976
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2977
+ import_react8.ContextMenu.Trigger,
2978
+ {
2979
+ "data-entry-path": entry.path,
2980
+ "data-entry-selectable": isTrashFolder ? "false" : "true",
2981
+ ...FileManager_default.row ? { className: FileManager_default.row } : {},
2982
+ style: {
2983
+ gridTemplateColumns: isMobile ? "1fr 48px" : "1fr 120px 100px 48px",
2984
+ padding: isMobile ? "12px 16px" : "12px 24px",
2985
+ backgroundColor: isSelected ? theme.selected : isHovered ? theme.hover : theme.bg,
2986
+ outline: "none"
2987
+ },
2988
+ onClick: (e) => {
2989
+ if (e.button !== 0) return;
2990
+ handleEntryClickWithSelection(entry, index, filteredEntries, e);
2991
+ },
2992
+ onDoubleClick: () => openEntry(entry),
2993
+ onContextMenu: (e) => {
2994
+ if (isTrashFolder) {
2995
+ e.preventDefault();
2996
+ e.stopPropagation();
2997
+ return;
2998
+ }
2999
+ e.stopPropagation();
3000
+ },
3001
+ onMouseEnter: () => setHoverRow(entry.path),
3002
+ onMouseLeave: () => setHoverRow(null),
3003
+ onTouchStart: () => {
3004
+ if (!isMobile) return;
3005
+ if (longPressTimerRef.current)
3006
+ window.clearTimeout(longPressTimerRef.current);
3007
+ longPressTimerRef.current = window.setTimeout(() => {
3008
+ handleLongPressSelect(entry);
3009
+ }, 350);
3010
+ },
3011
+ onTouchMove: () => {
3012
+ if (longPressTimerRef.current) {
3013
+ window.clearTimeout(longPressTimerRef.current);
3014
+ longPressTimerRef.current = null;
3015
+ }
3016
+ },
3017
+ onTouchEnd: () => {
3018
+ if (longPressTimerRef.current) {
3019
+ window.clearTimeout(longPressTimerRef.current);
3020
+ longPressTimerRef.current = null;
3021
+ }
3022
+ },
3023
+ children: [
3024
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3025
+ "div",
3026
+ {
3027
+ style: {
3028
+ display: "flex",
3029
+ alignItems: isMobile ? "flex-start" : "center",
3030
+ gap: 12,
3031
+ fontWeight: 500,
3032
+ flexDirection: isMobile ? "column" : "row"
3033
+ },
3034
+ children: [
3035
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3036
+ "div",
3037
+ {
3038
+ style: {
3039
+ display: "flex",
3040
+ alignItems: "center",
3041
+ gap: 12
3042
+ },
3043
+ children: [
3044
+ entry.type === "folder" ? isTrashFolder ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3045
+ UiIcon,
3046
+ {
3047
+ icon: import_react7.Trash,
3048
+ size: 20,
3049
+ weight: "fill",
3050
+ color: theme.text,
3051
+ boxed: true
3052
+ }
3053
+ ) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3054
+ UiIcon,
3055
+ {
3056
+ icon: import_react7.Folder,
3057
+ size: 20,
3058
+ weight: "fill",
3059
+ color: theme.text,
3060
+ boxed: true
3061
+ }
3062
+ ) : (() => {
3063
+ const previewUrl = inlinePreviews[entry.path];
3064
+ if (previewUrl && isImageFileName(entry.name)) {
3065
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3066
+ "img",
3067
+ {
3068
+ src: previewUrl,
3069
+ alt: entry.name,
3070
+ loading: "lazy",
3071
+ decoding: "async",
3072
+ style: {
3073
+ width: 20,
3074
+ height: 20,
3075
+ objectFit: "cover",
3076
+ borderRadius: 4,
3077
+ border: `1px solid ${theme.border}`
3078
+ }
3079
+ }
3080
+ );
3081
+ }
3082
+ return getFileIcon(entry.name);
3083
+ })(),
3084
+ entryLabel
3085
+ ]
3086
+ }
3087
+ ),
3088
+ isMobile && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3089
+ "div",
3090
+ {
3091
+ style: {
3092
+ fontSize: 11,
3093
+ color: theme.textSecondary,
3094
+ fontWeight: 400
3095
+ },
3096
+ children: entry.type === "file" ? `${entry.lastModified ? formatDate(entry.lastModified) : "--"} | ${formatBytes(entry.size || 0)}` : "Folder"
3097
+ }
3098
+ )
3099
+ ]
3100
+ }
3101
+ ),
3102
+ !isMobile && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: { color: theme.textSecondary }, children: entry.type === "file" && entry.lastModified ? formatDate(entry.lastModified) : "--" }),
3103
+ !isMobile && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3104
+ "div",
3105
+ {
3106
+ style: {
3107
+ color: theme.textSecondary,
3108
+ fontFamily: "monospace"
3109
+ },
3110
+ children: entry.type === "file" ? formatBytes(entry.size || 0) : "--"
3111
+ }
3112
+ ),
3113
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3114
+ "div",
3115
+ {
3116
+ onClick: (e) => {
3117
+ e.stopPropagation();
3118
+ e.preventDefault();
3119
+ },
3120
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react9.Menu.Root, { children: [
3121
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3122
+ import_react9.Menu.Trigger,
3123
+ {
3124
+ style: {
3125
+ background: "none",
3126
+ border: "none",
3127
+ cursor: "pointer",
3128
+ padding: 4,
3129
+ display: "flex",
3130
+ opacity: isMobile ? 1 : isHovered ? 1 : 0,
3131
+ transition: "opacity 0.2s",
3132
+ outline: "none"
3133
+ },
3134
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(UiIcon, { icon: import_react7.DotsThree, size: 24, color: theme.textSecondary })
3135
+ }
3136
+ ),
3137
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react9.Menu.Portal, { container: portalContainer, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3138
+ import_react9.Menu.Positioner,
3139
+ {
3140
+ side: "bottom",
3141
+ align: "end",
3142
+ sideOffset: 5,
3143
+ style: { zIndex: 1e4 },
3144
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3145
+ import_react9.Menu.Popup,
3146
+ {
3147
+ style: {
3148
+ zIndex: 1e4,
3149
+ backgroundColor: theme.bg,
3150
+ border: `1px solid ${theme.border}`,
3151
+ boxShadow: "0 4px 12px rgba(0,0,0,0.1)",
3152
+ padding: 4,
3153
+ display: "flex",
3154
+ flexDirection: "column",
3155
+ minWidth: 160,
3156
+ outline: "none"
3157
+ },
3158
+ children: [
3159
+ !isMultiSelected && can.rename && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3160
+ import_react9.Menu.Item,
3161
+ {
3162
+ style: {
3163
+ display: "flex",
3164
+ alignItems: "center",
3165
+ gap: 10,
3166
+ padding: "8px 12px",
3167
+ border: "none",
3168
+ background: "none",
3169
+ cursor: "pointer",
3170
+ fontSize: 13,
3171
+ textAlign: "left",
3172
+ color: theme.text,
3173
+ outline: "none"
3174
+ },
3175
+ onClick: () => {
3176
+ setRenameName(entry.name || "");
3177
+ setRenameTarget(entry);
3178
+ setRenameOpen(true);
3179
+ },
3180
+ children: [
3181
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(UiIcon, { icon: import_react7.PencilSimple, size: 16 }),
3182
+ " Rename"
3183
+ ]
3184
+ }
3185
+ ),
3186
+ actionEntries.some((item) => item.type === "file") && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3187
+ import_react9.Menu.Item,
3188
+ {
3189
+ style: {
3190
+ display: "flex",
3191
+ alignItems: "center",
3192
+ gap: 10,
3193
+ padding: "8px 12px",
3194
+ border: "none",
3195
+ background: "none",
3196
+ cursor: "pointer",
3197
+ fontSize: 13,
3198
+ textAlign: "left",
3199
+ color: theme.text,
3200
+ outline: "none"
3201
+ },
3202
+ onClick: () => bulkDownload(actionEntries),
3203
+ children: [
3204
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(UiIcon, { icon: import_react7.DownloadSimple, size: 16 }),
3205
+ " Download"
3206
+ ]
3207
+ }
3208
+ ),
3209
+ isMultiSelected && view === "files" && can.move && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3210
+ import_react9.Menu.Item,
3211
+ {
3212
+ style: {
3213
+ display: "flex",
3214
+ alignItems: "center",
3215
+ gap: 10,
3216
+ padding: "8px 12px",
3217
+ border: "none",
3218
+ background: "none",
3219
+ cursor: "pointer",
3220
+ fontSize: 13,
3221
+ textAlign: "left",
3222
+ color: theme.text,
3223
+ outline: "none"
3224
+ },
3225
+ onClick: () => bulkMove(actionEntries),
3226
+ children: [
3227
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(UiIcon, { icon: import_react7.ArrowRight, size: 16 }),
3228
+ " Move"
3229
+ ]
3230
+ }
3231
+ ),
3232
+ view === "files" && can.copy && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3233
+ import_react9.Menu.Item,
3234
+ {
3235
+ style: {
3236
+ display: "flex",
3237
+ alignItems: "center",
3238
+ gap: 10,
3239
+ padding: "8px 12px",
3240
+ border: "none",
3241
+ background: "none",
3242
+ cursor: "pointer",
3243
+ fontSize: 13,
3244
+ textAlign: "left",
3245
+ color: theme.text,
3246
+ outline: "none"
3247
+ },
3248
+ onClick: () => bulkCopy(actionEntries),
3249
+ children: [
3250
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(UiIcon, { icon: import_react7.Copy, size: 16 }),
3251
+ " Copy"
3252
+ ]
3253
+ }
3254
+ ),
3255
+ isMultiSelected && view === "trash" && can.restore && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3256
+ import_react9.Menu.Item,
3257
+ {
3258
+ style: {
3259
+ display: "flex",
3260
+ alignItems: "center",
3261
+ gap: 10,
3262
+ padding: "8px 12px",
3263
+ border: "none",
3264
+ background: "none",
3265
+ cursor: "pointer",
3266
+ fontSize: 13,
3267
+ textAlign: "left",
3268
+ color: theme.text,
3269
+ outline: "none"
3270
+ },
3271
+ onClick: () => restoreEntries(actionEntries),
3272
+ children: [
3273
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(UiIcon, { icon: import_react7.ArrowCounterClockwise, size: 16 }),
3274
+ " Restore"
3275
+ ]
3276
+ }
3277
+ ),
3278
+ can.delete && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3279
+ import_react9.Menu.Item,
3280
+ {
3281
+ style: {
3282
+ display: "flex",
3283
+ alignItems: "center",
3284
+ gap: 10,
3285
+ padding: "8px 12px",
3286
+ border: "none",
3287
+ background: "none",
3288
+ cursor: "pointer",
3289
+ fontSize: 13,
3290
+ textAlign: "left",
3291
+ color: theme.danger,
3292
+ outline: "none"
3293
+ },
3294
+ onClick: () => {
3295
+ setSelected(new Set(actionEntries.map((item) => item.path)));
3296
+ setLastSelected(null);
3297
+ setIsSelectionMode(true);
3298
+ setDeleteOpen(true);
3299
+ },
3300
+ children: [
3301
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(UiIcon, { icon: import_react7.Trash, size: 16 }),
3302
+ " Delete"
3303
+ ]
3304
+ }
3305
+ )
3306
+ ]
3307
+ }
3308
+ )
3309
+ }
3310
+ ) })
3311
+ ] })
3312
+ }
3313
+ )
3314
+ ]
3315
+ }
3316
+ ),
3317
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react8.ContextMenu.Portal, { container: portalContainer, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react8.ContextMenu.Positioner, { style: { zIndex: 1e4 }, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3318
+ import_react8.ContextMenu.Popup,
3319
+ {
3320
+ style: {
3321
+ zIndex: 1e4,
3322
+ backgroundColor: theme.bg,
3323
+ border: `1px solid ${theme.border}`,
3324
+ boxShadow: "0 4px 12px rgba(0,0,0,0.1)",
3325
+ padding: 4,
3326
+ display: "flex",
3327
+ flexDirection: "column",
3328
+ minWidth: 160,
3329
+ outline: "none"
3330
+ },
3331
+ children: [
3332
+ !isMultiSelected && can.rename && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3333
+ import_react8.ContextMenu.Item,
3334
+ {
3335
+ style: {
3336
+ display: "flex",
3337
+ alignItems: "center",
3338
+ gap: 10,
3339
+ padding: "8px 12px",
3340
+ border: "none",
3341
+ background: "none",
3342
+ cursor: "pointer",
3343
+ fontSize: 13,
3344
+ textAlign: "left",
3345
+ color: theme.text,
3346
+ outline: "none"
3347
+ },
3348
+ onClick: () => {
3349
+ setRenameName(entry.name || "");
3350
+ setRenameTarget(entry);
3351
+ setRenameOpen(true);
3352
+ },
3353
+ children: [
3354
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(UiIcon, { icon: import_react7.PencilSimple, size: 16 }),
3355
+ " Rename"
3356
+ ]
3357
+ }
3358
+ ),
3359
+ actionEntries.some((item) => item.type === "file") && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3360
+ import_react8.ContextMenu.Item,
3361
+ {
3362
+ style: {
3363
+ display: "flex",
3364
+ alignItems: "center",
3365
+ gap: 10,
3366
+ padding: "8px 12px",
3367
+ border: "none",
3368
+ background: "none",
3369
+ cursor: "pointer",
3370
+ fontSize: 13,
3371
+ textAlign: "left",
3372
+ color: theme.text,
3373
+ outline: "none"
3374
+ },
3375
+ onClick: () => bulkDownload(actionEntries),
3376
+ children: [
3377
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(UiIcon, { icon: import_react7.DownloadSimple, size: 16 }),
3378
+ " Download"
3379
+ ]
3380
+ }
3381
+ ),
3382
+ isMultiSelected && view === "files" && can.move && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3383
+ import_react8.ContextMenu.Item,
3384
+ {
3385
+ style: {
3386
+ display: "flex",
3387
+ alignItems: "center",
3388
+ gap: 10,
3389
+ padding: "8px 12px",
3390
+ border: "none",
3391
+ background: "none",
3392
+ cursor: "pointer",
3393
+ fontSize: 13,
3394
+ textAlign: "left",
3395
+ color: theme.text,
3396
+ outline: "none"
3397
+ },
3398
+ onClick: () => bulkMove(actionEntries),
3399
+ children: [
3400
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(UiIcon, { icon: import_react7.ArrowRight, size: 16 }),
3401
+ " Move"
3402
+ ]
3403
+ }
3404
+ ),
3405
+ view === "files" && can.copy && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3406
+ import_react8.ContextMenu.Item,
3407
+ {
3408
+ style: {
3409
+ display: "flex",
3410
+ alignItems: "center",
3411
+ gap: 10,
3412
+ padding: "8px 12px",
3413
+ border: "none",
3414
+ background: "none",
3415
+ cursor: "pointer",
3416
+ fontSize: 13,
3417
+ textAlign: "left",
3418
+ color: theme.text,
3419
+ outline: "none"
3420
+ },
3421
+ onClick: () => bulkCopy(actionEntries),
3422
+ children: [
3423
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(UiIcon, { icon: import_react7.Copy, size: 16 }),
3424
+ " Copy"
3425
+ ]
3426
+ }
3427
+ ),
3428
+ isMultiSelected && view === "trash" && can.restore && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3429
+ import_react8.ContextMenu.Item,
3430
+ {
3431
+ style: {
3432
+ display: "flex",
3433
+ alignItems: "center",
3434
+ gap: 10,
3435
+ padding: "8px 12px",
3436
+ border: "none",
3437
+ background: "none",
3438
+ cursor: "pointer",
3439
+ fontSize: 13,
3440
+ textAlign: "left",
3441
+ color: theme.text,
3442
+ outline: "none"
3443
+ },
3444
+ onClick: () => restoreEntries(actionEntries),
3445
+ children: [
3446
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(UiIcon, { icon: import_react7.ArrowCounterClockwise, size: 16 }),
3447
+ " Restore"
3448
+ ]
3449
+ }
3450
+ ),
3451
+ can.delete && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3452
+ import_react8.ContextMenu.Item,
3453
+ {
3454
+ style: {
3455
+ display: "flex",
3456
+ alignItems: "center",
3457
+ gap: 10,
3458
+ padding: "8px 12px",
3459
+ border: "none",
3460
+ background: "none",
3461
+ cursor: "pointer",
3462
+ fontSize: 13,
3463
+ textAlign: "left",
3464
+ color: theme.danger,
3465
+ outline: "none"
3466
+ },
3467
+ onClick: () => {
3468
+ setSelected(new Set(actionEntries.map((item) => item.path)));
3469
+ setLastSelected(null);
3470
+ setIsSelectionMode(true);
3471
+ setDeleteOpen(true);
3472
+ },
3473
+ children: [
3474
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(UiIcon, { icon: import_react7.Trash, size: 16 }),
3475
+ " Delete"
3476
+ ]
3477
+ }
3478
+ )
3479
+ ]
3480
+ }
3481
+ ) }) })
3482
+ ]
3483
+ },
3484
+ entry.path
3485
+ );
3486
+ });
3487
+ })()
3488
+ ),
3489
+ (() => {
3490
+ const isSearching = searchQuery.trim();
3491
+ const hasMoreItems = isSearching ? searchHasMore : hasMore;
3492
+ if (loadingMore) {
3493
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3494
+ "div",
3495
+ {
3496
+ style: {
3497
+ padding: "20px 24px",
3498
+ textAlign: "center",
3499
+ borderTop: `1px solid ${theme.border}`,
3500
+ color: theme.textSecondary,
3501
+ fontSize: 13
3502
+ },
3503
+ children: "Loading more..."
3504
+ }
3505
+ );
3506
+ }
3507
+ if (hasMoreItems && currentEntries.length > 0) {
3508
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3509
+ "div",
3510
+ {
3511
+ style: {
3512
+ padding: "20px 24px",
3513
+ textAlign: "center",
3514
+ borderTop: `1px solid ${theme.border}`
3515
+ },
3516
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3517
+ Button,
3518
+ {
3519
+ onClick: () => {
3520
+ if (isSearching) {
3521
+ performSearch(searchQuery, true);
3522
+ } else {
3523
+ refresh(true);
3524
+ }
3525
+ },
3526
+ disabled: loadingMore,
3527
+ style: {
3528
+ minWidth: 120,
3529
+ opacity: loadingMore ? 0.6 : 1
3530
+ },
3531
+ theme,
3532
+ children: "Load More"
3533
+ }
3534
+ )
3535
+ }
3536
+ );
3537
+ }
3538
+ return null;
3539
+ })(),
3540
+ (() => {
3541
+ if (currentEntries.length === 0 && !loading && !searching) {
3542
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3543
+ "div",
3544
+ {
3545
+ style: {
3546
+ padding: 40,
3547
+ textAlign: "center",
3548
+ color: theme.textSecondary
3549
+ },
3550
+ children: searchQuery.trim() ? `No results found for "${searchQuery}"` : view === "trash" ? "Trash is empty" : /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { children: [
3551
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: { marginBottom: 16 }, children: "No files found" }),
3552
+ can.upload && view === "files" && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3553
+ "div",
3554
+ {
3555
+ style: {
3556
+ fontSize: 12,
3557
+ opacity: 0.7,
3558
+ display: "flex",
3559
+ alignItems: "center",
3560
+ justifyContent: "center",
3561
+ gap: 8
3562
+ },
3563
+ children: [
3564
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(UiIcon, { icon: import_react7.UploadSimple, size: 16 }),
3565
+ "Drag and drop files here or click Upload"
3566
+ ]
3567
+ }
3568
+ )
3569
+ ] })
3570
+ }
3571
+ );
3572
+ }
3573
+ return null;
3574
+ })()
3575
+ ]
3576
+ }
3577
+ )
3578
+ ]
3579
+ }
3580
+ ),
3581
+ showSidebarOnLayout && previewDisplay && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3582
+ import_react10.Dialog.Root,
3583
+ {
3584
+ open: !!previewDisplay,
3585
+ onOpenChange: (open) => {
3586
+ if (!open) {
3587
+ setSelected(/* @__PURE__ */ new Set());
3588
+ setLastSelected(null);
3589
+ }
3590
+ },
3591
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react10.Dialog.Portal, { container: previewPortalContainer, children: [
3592
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3593
+ import_react10.Dialog.Backdrop,
3594
+ {
3595
+ style: {
3596
+ position: "absolute",
3597
+ inset: 0,
3598
+ backgroundColor: "rgba(0,0,0,0.04)",
3599
+ zIndex: 9,
3600
+ pointerEvents: "auto"
3601
+ }
3602
+ }
3603
+ ),
3604
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3605
+ import_react10.Dialog.Popup,
3606
+ {
3607
+ ...FileManager_default.sidebarRight ? { className: FileManager_default.sidebarRight } : {},
3608
+ style: {
3609
+ backgroundColor: theme.bg,
3610
+ borderLeft: isSidebarVisible ? `1px solid ${theme.border}` : "none",
3611
+ animation: isPreviewClosing ? "tokoPreviewSlideOut 200ms ease-in" : "tokoPreviewSlideIn 200ms ease-out",
3612
+ opacity: isSidebarVisible ? 1 : 0,
3613
+ pointerEvents: isPreviewClosing ? "none" : isSidebarVisible ? "auto" : "none",
3614
+ width: sidebarWidth,
3615
+ ...isSidebarVisible ? {
3616
+ boxShadow: "0 0 0 1px rgba(0,0,0,0.03), -8px 0 24px rgba(0,0,0,0.08)"
3617
+ } : {}
3618
+ },
3619
+ children: [
3620
+ isSidebarVisible && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3621
+ "div",
3622
+ {
3623
+ onMouseDown: (e) => {
3624
+ e.preventDefault();
3625
+ setIsResizing(true);
3626
+ updateSidebarWidth(e.clientX);
3627
+ },
3628
+ onTouchStart: (e) => {
3629
+ const touch = e.touches[0];
3630
+ if (!touch) return;
3631
+ setIsResizing(true);
3632
+ updateSidebarWidth(touch.clientX);
3633
+ },
3634
+ style: {
3635
+ position: "absolute",
3636
+ left: 0,
3637
+ top: 0,
3638
+ bottom: 0,
3639
+ width: 8,
3640
+ cursor: "col-resize",
3641
+ zIndex: 2,
3642
+ background: isResizing ? "rgba(0,0,0,0.06)" : "transparent"
3643
+ },
3644
+ "aria-hidden": true,
3645
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3646
+ "div",
3647
+ {
3648
+ style: {
3649
+ position: "absolute",
3650
+ left: 3,
3651
+ top: 0,
3652
+ bottom: 0,
3653
+ width: 2,
3654
+ backgroundColor: isResizing ? theme.textSecondary : theme.border,
3655
+ opacity: 0.4
3656
+ }
3657
+ }
3658
+ )
3659
+ }
3660
+ ),
3661
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3662
+ import_react10.Dialog.Close,
3663
+ {
3664
+ style: {
3665
+ position: "absolute",
3666
+ top: 8,
3667
+ right: 8,
3668
+ zIndex: 3,
3669
+ background: theme.bg,
3670
+ border: `1px solid ${theme.border}`,
3671
+ borderRadius: 4,
3672
+ padding: 4,
3673
+ cursor: "pointer",
3674
+ display: "flex",
3675
+ alignItems: "center",
3676
+ color: theme.textSecondary
3677
+ },
3678
+ "aria-label": "Close preview",
3679
+ title: "Close preview",
3680
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(UiIcon, { icon: import_react7.X, size: 16 })
3681
+ }
3682
+ ),
3683
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: FileManager_default.previewBox, children: ["jpg", "png", "gif", "jpeg", "webp"].some(
3684
+ (ext) => previewDisplay.entry.path.toLowerCase().endsWith(ext)
3685
+ ) ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3686
+ "img",
3687
+ {
3688
+ src: previewDisplay.url,
3689
+ alt: "preview",
3690
+ style: {
3691
+ maxWidth: "100%",
3692
+ maxHeight: "100%",
3693
+ objectFit: "contain"
3694
+ }
3695
+ }
3696
+ ) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(UiIcon, { icon: import_react7.FileText, size: 64, weight: "thin", color: theme.textSecondary }) }),
3697
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: FileManager_default.metadata, style: { color: theme.text }, children: [
3698
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3699
+ "h3",
3700
+ {
3701
+ style: {
3702
+ margin: "0 0 20px",
3703
+ fontSize: 16,
3704
+ fontWeight: 600,
3705
+ color: theme.text
3706
+ },
3707
+ children: previewDisplay.entry.name
3708
+ }
3709
+ ),
3710
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3711
+ "div",
3712
+ {
3713
+ style: {
3714
+ display: "grid",
3715
+ gridTemplateColumns: "1fr 1fr",
3716
+ gap: 20
3717
+ },
3718
+ children: [
3719
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: FileManager_default.metaItem, children: [
3720
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: FileManager_default.metaLabel, children: "Type" }),
3721
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: FileManager_default.metaValue, children: previewDisplay.entry.name.split(".").pop()?.toUpperCase() || "FILE" })
3722
+ ] }),
3723
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: FileManager_default.metaItem, children: [
3724
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: FileManager_default.metaLabel, children: "Size" }),
3725
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: FileManager_default.metaValue, children: previewDisplay.entry.type === "file" ? formatBytes(previewDisplay.entry.size || 0) : "0 B" })
3726
+ ] })
3727
+ ]
3728
+ }
3729
+ ),
3730
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: FileManager_default.metaItem, children: [
3731
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: FileManager_default.metaLabel, children: "Location" }),
3732
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: FileManager_default.metaValue, children: getParentPath(previewDisplay.entry.path) || "/" })
3733
+ ] }),
3734
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: FileManager_default.metaItem, children: [
3735
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: FileManager_default.metaLabel, children: "Modified" }),
3736
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: FileManager_default.metaValue, children: previewDisplay.entry.type === "file" && previewDisplay.entry.lastModified ? new Date(previewDisplay.entry.lastModified).toLocaleString() : "--" })
3737
+ ] }),
3738
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3739
+ "div",
3740
+ {
3741
+ style: {
3742
+ marginTop: 20,
3743
+ display: "flex",
3744
+ flexDirection: "column",
3745
+ gap: 8
3746
+ },
3747
+ children: view === "files" ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
3748
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3749
+ Button,
3750
+ {
3751
+ style: { width: "100%", justifyContent: "center" },
3752
+ theme,
3753
+ onClick: async () => {
3754
+ try {
3755
+ const out = await client.getPreviewUrl({
3756
+ path: previewDisplay.entry.path,
3757
+ inline: false
3758
+ });
3759
+ const link = document.createElement("a");
3760
+ link.href = out.url;
3761
+ link.download = previewDisplay.entry.name;
3762
+ document.body.appendChild(link);
3763
+ link.click();
3764
+ document.body.removeChild(link);
3765
+ } catch (e) {
3766
+ console.error(e);
3767
+ }
3768
+ },
3769
+ children: [
3770
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(UiIcon, { icon: import_react7.DownloadSimple, size: 16 }),
3771
+ " Download"
3772
+ ]
3773
+ }
3774
+ ),
3775
+ can.delete && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3776
+ Button,
3777
+ {
3778
+ variant: "danger",
3779
+ onClick: () => {
3780
+ setSelected(/* @__PURE__ */ new Set([previewDisplay.entry.path]));
3781
+ setLastSelected(previewDisplay.entry);
3782
+ setDeleteOpen(true);
3783
+ },
3784
+ style: { width: "100%", justifyContent: "center" },
3785
+ theme,
3786
+ children: [
3787
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(UiIcon, { icon: import_react7.Trash, size: 16 }),
3788
+ " Delete"
3789
+ ]
3790
+ }
3791
+ )
3792
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3793
+ Button,
3794
+ {
3795
+ onClick: onRestore,
3796
+ style: { width: "100%", justifyContent: "center" },
3797
+ theme,
3798
+ children: [
3799
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(UiIcon, { icon: import_react7.ArrowCounterClockwise, size: 16 }),
3800
+ " Restore"
3801
+ ]
3802
+ }
3803
+ )
3804
+ }
3805
+ )
3806
+ ] })
3807
+ ]
3808
+ }
3809
+ )
3810
+ ] })
3811
+ }
3812
+ ),
3813
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3814
+ Modal,
3815
+ {
3816
+ open: createFolderOpen,
3817
+ onClose: () => setCreateFolderOpen(false),
3818
+ title: "New Folder",
3819
+ theme,
3820
+ ...portalContainer ? { portalContainer } : {},
3821
+ children: [
3822
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3823
+ "input",
3824
+ {
3825
+ type: "text",
3826
+ placeholder: "Name",
3827
+ value: newFolderName,
3828
+ onChange: (e) => setNewFolderName(e.target.value),
3829
+ onKeyDown: (e) => e.key === "Enter" && onCreateFolder(),
3830
+ style: {
3831
+ width: "100%",
3832
+ padding: "10px",
3833
+ fontSize: 14,
3834
+ border: `1px solid ${theme.border}`,
3835
+ outline: "none",
3836
+ backgroundColor: theme.bg,
3837
+ color: theme.text
3838
+ },
3839
+ autoFocus: true
3840
+ }
3841
+ ),
3842
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3843
+ "div",
3844
+ {
3845
+ style: {
3846
+ marginTop: 20,
3847
+ display: "flex",
3848
+ justifyContent: "flex-end",
3849
+ gap: 10
3850
+ },
3851
+ children: [
3852
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Button, { onClick: () => setCreateFolderOpen(false), theme, children: "Cancel" }),
3853
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Button, { variant: "primary", onClick: onCreateFolder, theme, children: "Create Folder" })
3854
+ ]
3855
+ }
3856
+ )
3857
+ ]
3858
+ }
3859
+ ),
3860
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3861
+ Modal,
3862
+ {
3863
+ open: renameOpen,
3864
+ onClose: () => {
3865
+ if (isRenaming) return;
3866
+ setRenameOpen(false);
3867
+ setRenameTarget(null);
3868
+ },
3869
+ title: "Rename",
3870
+ theme,
3871
+ ...portalContainer ? { portalContainer } : {},
3872
+ closeDisabled: isRenaming,
3873
+ children: [
3874
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3875
+ "input",
3876
+ {
3877
+ type: "text",
3878
+ placeholder: "Name",
3879
+ value: renameName,
3880
+ onChange: (e) => setRenameName(e.target.value),
3881
+ onKeyDown: (e) => e.key === "Enter" && onRename(),
3882
+ style: {
3883
+ width: "100%",
3884
+ padding: "10px",
3885
+ fontSize: 14,
3886
+ border: `1px solid ${theme.border}`,
3887
+ outline: "none",
3888
+ backgroundColor: theme.bg,
3889
+ color: theme.text
3890
+ },
3891
+ disabled: isRenaming,
3892
+ autoFocus: true
3893
+ }
3894
+ ),
3895
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3896
+ "div",
3897
+ {
3898
+ style: {
3899
+ marginTop: 20,
3900
+ display: "flex",
3901
+ justifyContent: "flex-end",
3902
+ gap: 10
3903
+ },
3904
+ children: [
3905
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Button, { onClick: () => setRenameOpen(false), theme, disabled: isRenaming, children: "Cancel" }),
3906
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Button, { variant: "primary", onClick: onRename, theme, disabled: isRenaming, children: isRenaming ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
3907
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: FileManager_default.spinner }),
3908
+ "Renaming..."
3909
+ ] }) : "Rename" })
3910
+ ]
3911
+ }
3912
+ )
3913
+ ]
3914
+ }
3915
+ ),
3916
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3917
+ Modal,
3918
+ {
3919
+ open: deleteOpen,
3920
+ onClose: () => setDeleteOpen(false),
3921
+ title: view === "trash" ? "Delete Forever" : "Delete items",
3922
+ theme,
3923
+ ...portalContainer ? { portalContainer } : {},
3924
+ closeDisabled: isDeleting,
3925
+ children: [
3926
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("p", { style: { margin: "0 0 20px", color: theme.textSecondary }, children: [
3927
+ "Are you sure you want to ",
3928
+ view === "trash" ? "permanently" : "",
3929
+ " delete ",
3930
+ selected.size,
3931
+ " ",
3932
+ "item",
3933
+ selected.size > 1 ? "s" : "",
3934
+ "?",
3935
+ view === "files" && " (Items will be moved to Trash)"
3936
+ ] }),
3937
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: { display: "flex", justifyContent: "flex-end", gap: 10 }, children: [
3938
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Button, { onClick: () => setDeleteOpen(false), theme, disabled: isDeleting, children: "Cancel" }),
3939
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Button, { variant: "danger", onClick: onDelete, theme, disabled: isDeleting, children: isDeleting ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
3940
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: FileManager_default.spinner }),
3941
+ "Deleting..."
3942
+ ] }) : "Delete" })
3943
+ ] })
3944
+ ]
3945
+ }
3946
+ ),
3947
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3948
+ Modal,
3949
+ {
3950
+ open: emptyTrashOpen,
3951
+ onClose: () => setEmptyTrashOpen(false),
3952
+ title: "Empty Trash",
3953
+ theme,
3954
+ ...portalContainer ? { portalContainer } : {},
3955
+ closeDisabled: isEmptyingTrash,
3956
+ children: [
3957
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { style: { margin: "0 0 20px", color: theme.textSecondary }, children: "Are you sure you want to empty the trash? All items will be permanently deleted." }),
3958
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: { display: "flex", justifyContent: "flex-end", gap: 10 }, children: [
3959
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Button, { onClick: () => setEmptyTrashOpen(false), theme, disabled: isEmptyingTrash, children: "Cancel" }),
3960
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Button, { variant: "danger", onClick: onEmptyTrash, theme, disabled: isEmptyingTrash, children: isEmptyingTrash ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
3961
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: FileManager_default.spinner }),
3962
+ "Emptying..."
3963
+ ] }) : "Empty Trash" })
3964
+ ] })
3965
+ ]
3966
+ }
3967
+ ),
3968
+ isDragOver && view === "files" && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3969
+ "div",
3970
+ {
3971
+ style: {
3972
+ position: "absolute",
3973
+ top: 0,
3974
+ left: 0,
3975
+ right: 0,
3976
+ bottom: 0,
3977
+ backgroundColor: theme.bg === "#ffffff" ? "rgba(0, 0, 0, 0.05)" : "rgba(255, 255, 255, 0.05)",
3978
+ backdropFilter: "blur(2px)",
3979
+ zIndex: 1e3,
3980
+ display: "flex",
3981
+ alignItems: "center",
3982
+ justifyContent: "center",
3983
+ border: `2px dashed ${theme.accent}`,
3984
+ margin: 4,
3985
+ animation: "fadeIn 0.2s ease-in-out"
3986
+ },
3987
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3988
+ "div",
3989
+ {
3990
+ style: {
3991
+ textAlign: "center",
3992
+ padding: 40,
3993
+ backgroundColor: theme.bg,
3994
+ border: `1px solid ${theme.border}`,
3995
+ color: theme.text,
3996
+ fontSize: 16,
3997
+ fontWeight: 500,
3998
+ transform: "scale(1.02)",
3999
+ transition: "transform 0.2s ease"
4000
+ },
4001
+ children: [
4002
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(UiIcon, { icon: import_react7.UploadSimple, size: 48, boxStyle: { marginBottom: 16, opacity: 0.7 } }),
4003
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { children: "Drop files here to upload" }),
4004
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: { fontSize: 13, color: theme.textSecondary, marginTop: 8 }, children: [
4005
+ "Files will be uploaded to ",
4006
+ path || "root directory"
4007
+ ] })
4008
+ ]
4009
+ }
4010
+ )
4011
+ }
4012
+ ),
4013
+ uploadCardOpen && (() => {
4014
+ const totalItems = uploadItems.length;
4015
+ const activeItems = uploadItems.filter((item) => item.status === "uploading");
4016
+ const completedItems = uploadItems.filter((item) => item.status === "done");
4017
+ const failedItems = uploadItems.filter((item) => item.status === "error");
4018
+ const totalBytes = uploadItems.reduce((acc, item) => acc + (item.total || 0), 0);
4019
+ const loadedBytes = uploadItems.reduce((acc, item) => acc + (item.loaded || 0), 0);
4020
+ const totalPct = totalBytes > 0 ? Math.round(loadedBytes / totalBytes * 100) : 0;
4021
+ const hasActiveUploads = activeItems.length > 0;
4022
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
4023
+ "div",
4024
+ {
4025
+ style: {
4026
+ position: "absolute",
4027
+ right: 16,
4028
+ bottom: 16,
4029
+ width: 340,
4030
+ maxWidth: "calc(100% - 32px)",
4031
+ backgroundColor: theme.bg,
4032
+ border: `1px solid ${theme.border}`,
4033
+ boxShadow: "0 8px 24px rgba(0,0,0,0.12)",
4034
+ padding: 14,
4035
+ zIndex: 1200
4036
+ },
4037
+ children: [
4038
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
4039
+ "div",
4040
+ {
4041
+ style: {
4042
+ display: "flex",
4043
+ alignItems: "center",
4044
+ justifyContent: "space-between",
4045
+ marginBottom: 10
4046
+ },
4047
+ children: [
4048
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: { fontSize: 12, fontWeight: 600 }, children: hasActiveUploads ? `Uploading ${completedItems.length}/${totalItems}` : "Uploads complete" }),
4049
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [
4050
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: { fontSize: 12, color: theme.textSecondary }, children: [
4051
+ totalItems,
4052
+ " file",
4053
+ totalItems !== 1 ? "s" : "",
4054
+ " \u2022 ",
4055
+ totalPct,
4056
+ "%"
4057
+ ] }),
4058
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
4059
+ "button",
4060
+ {
4061
+ type: "button",
4062
+ onClick: () => setUploadCardOpen(false),
4063
+ disabled: hasActiveUploads,
4064
+ style: {
4065
+ background: "none",
4066
+ border: "none",
4067
+ cursor: hasActiveUploads ? "not-allowed" : "pointer",
4068
+ padding: 2,
4069
+ display: "flex",
4070
+ alignItems: "center",
4071
+ color: theme.textSecondary,
4072
+ opacity: hasActiveUploads ? 0.5 : 1
4073
+ },
4074
+ title: hasActiveUploads ? "Uploads in progress" : "Close",
4075
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(UiIcon, { icon: import_react7.X, size: 14 })
4076
+ }
4077
+ )
4078
+ ] })
4079
+ ]
4080
+ }
4081
+ ),
4082
+ totalItems > 1 && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
4083
+ "div",
4084
+ {
4085
+ style: {
4086
+ height: 6,
4087
+ backgroundColor: theme.bgSecondary,
4088
+ borderRadius: 999,
4089
+ overflow: "hidden",
4090
+ marginBottom: 10
4091
+ },
4092
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
4093
+ "div",
4094
+ {
4095
+ style: {
4096
+ height: "100%",
4097
+ width: `${totalPct}%`,
4098
+ backgroundColor: theme.accent,
4099
+ transition: "width 120ms linear"
4100
+ }
4101
+ }
4102
+ )
4103
+ }
4104
+ ),
4105
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
4106
+ "div",
4107
+ {
4108
+ style: {
4109
+ display: "flex",
4110
+ flexDirection: "column",
4111
+ gap: 8,
4112
+ maxHeight: 220,
4113
+ overflow: "auto"
4114
+ },
4115
+ children: [
4116
+ uploadItems.map((item) => {
4117
+ const pct = item.total ? Math.round(item.loaded / item.total * 100) : 0;
4118
+ const statusColor = item.status === "error" ? theme.danger : item.status === "done" ? theme.text : theme.textSecondary;
4119
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
4120
+ "div",
4121
+ {
4122
+ style: {
4123
+ display: "flex",
4124
+ flexDirection: "column",
4125
+ gap: 6
4126
+ },
4127
+ children: [
4128
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
4129
+ "div",
4130
+ {
4131
+ style: {
4132
+ display: "flex",
4133
+ alignItems: "baseline",
4134
+ gap: 10
4135
+ },
4136
+ children: [
4137
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
4138
+ "div",
4139
+ {
4140
+ style: {
4141
+ flex: 1,
4142
+ fontSize: 12,
4143
+ color: theme.textSecondary,
4144
+ overflow: "hidden",
4145
+ textOverflow: "ellipsis",
4146
+ whiteSpace: "nowrap"
4147
+ },
4148
+ children: item.name
4149
+ }
4150
+ ),
4151
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: { fontSize: 11, color: theme.textSecondary }, children: [
4152
+ formatBytes(item.loaded),
4153
+ item.total ? ` / ${formatBytes(item.total)}` : ""
4154
+ ] }),
4155
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
4156
+ "div",
4157
+ {
4158
+ style: {
4159
+ display: "flex",
4160
+ alignItems: "center",
4161
+ gap: 8,
4162
+ justifyContent: "flex-end",
4163
+ minWidth: 90
4164
+ },
4165
+ children: [
4166
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
4167
+ "div",
4168
+ {
4169
+ style: {
4170
+ fontSize: 12,
4171
+ color: statusColor,
4172
+ minWidth: 42,
4173
+ textAlign: "right"
4174
+ },
4175
+ children: item.status === "error" ? "Error" : `${pct}%`
4176
+ }
4177
+ ),
4178
+ item.status === "error" && item.file && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
4179
+ "button",
4180
+ {
4181
+ type: "button",
4182
+ onClick: () => retryUpload(item),
4183
+ style: {
4184
+ background: theme.bg,
4185
+ border: `1px solid ${theme.border}`,
4186
+ color: theme.text,
4187
+ fontSize: 11,
4188
+ padding: "4px 8px",
4189
+ cursor: "pointer"
4190
+ },
4191
+ children: "Retry"
4192
+ }
4193
+ )
4194
+ ]
4195
+ }
4196
+ )
4197
+ ]
4198
+ }
4199
+ ),
4200
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
4201
+ "div",
4202
+ {
4203
+ style: {
4204
+ width: "100%",
4205
+ height: 6,
4206
+ backgroundColor: theme.bgSecondary,
4207
+ borderRadius: 999,
4208
+ overflow: "hidden"
4209
+ },
4210
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
4211
+ "div",
4212
+ {
4213
+ style: {
4214
+ height: "100%",
4215
+ width: `${item.status === "error" ? 100 : pct}%`,
4216
+ backgroundColor: item.status === "error" ? theme.danger : theme.accent,
4217
+ transition: "width 120ms linear"
4218
+ }
4219
+ }
4220
+ )
4221
+ }
4222
+ ),
4223
+ item.status === "error" && item.error && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: { fontSize: 11, color: theme.danger }, children: item.error })
4224
+ ]
4225
+ },
4226
+ item.path
4227
+ );
4228
+ }),
4229
+ failedItems.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: { fontSize: 11, color: theme.danger }, children: [
4230
+ failedItems.length,
4231
+ " upload",
4232
+ failedItems.length !== 1 ? "s" : "",
4233
+ " failed"
4234
+ ] })
4235
+ ]
4236
+ }
4237
+ )
4238
+ ]
4239
+ }
4240
+ );
4241
+ })(),
4242
+ isDragOver && view === "trash" && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
4243
+ "div",
4244
+ {
4245
+ style: {
4246
+ position: "absolute",
4247
+ top: 0,
4248
+ left: 0,
4249
+ right: 0,
4250
+ bottom: 0,
4251
+ backgroundColor: theme.bg === "#ffffff" ? "rgba(220, 38, 38, 0.1)" : "rgba(239, 68, 68, 0.1)",
4252
+ backdropFilter: "blur(2px)",
4253
+ zIndex: 1e3,
4254
+ display: "flex",
4255
+ alignItems: "center",
4256
+ justifyContent: "center",
4257
+ border: `2px dashed ${theme.danger}`,
4258
+ margin: 4
4259
+ },
4260
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
4261
+ "div",
4262
+ {
4263
+ style: {
4264
+ textAlign: "center",
4265
+ padding: 40,
4266
+ backgroundColor: theme.bg,
4267
+ border: `1px solid ${theme.border}`,
4268
+ color: theme.danger,
4269
+ fontSize: 16,
4270
+ fontWeight: 500
4271
+ },
4272
+ children: [
4273
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(UiIcon, { icon: import_react7.Warning, size: 48, boxStyle: { marginBottom: 16, opacity: 0.7 } }),
4274
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { children: "Cannot upload to trash" }),
4275
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: { fontSize: 13, color: theme.textSecondary, marginTop: 8 }, children: "Switch to files view to upload" })
4276
+ ]
4277
+ }
4278
+ )
4279
+ }
4280
+ ),
4281
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { ref: previewPortalRef, className: FileManager_default.previewPortal })
4282
+ ]
4283
+ }
4284
+ );
4285
+ }
4286
+
4287
+ // src/react/FilePicker.tsx
4288
+ var import_jsx_runtime6 = require("react/jsx-runtime");
4289
+ function FilePicker({
4290
+ selection = "single",
4291
+ allowActions,
4292
+ confirmLabel = "Select",
4293
+ ...props
4294
+ }) {
4295
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
4296
+ FileManager,
4297
+ {
4298
+ ...props,
4299
+ mode: "picker",
4300
+ selection,
4301
+ confirmLabel,
4302
+ allowActions: {
4303
+ upload: true,
4304
+ createFolder: true,
4305
+ delete: false,
4306
+ rename: false,
4307
+ move: false,
4308
+ copy: false,
4309
+ restore: false,
4310
+ ...allowActions
4311
+ }
4312
+ }
4313
+ );
4314
+ }
4315
+ // Annotate the CommonJS export names for ESM import in node:
4316
+ 0 && (module.exports = {
4317
+ FileManager,
4318
+ FilePicker
4319
+ });
4320
+ //# sourceMappingURL=index.cjs.map