@vryzel/file-next-ui 0.4.2 → 0.4.4
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.
- package/README.md +2 -0
- package/dist/index.cjs +300 -156
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +300 -156
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -147,6 +147,62 @@ function useExplorerLabels() {
|
|
|
147
147
|
function formatLabel(template, vars) {
|
|
148
148
|
return template.replace(/\{(\w+)\}/g, (_, key) => String(vars[key] ?? ""));
|
|
149
149
|
}
|
|
150
|
+
function RenameInput({
|
|
151
|
+
name,
|
|
152
|
+
kind,
|
|
153
|
+
ariaLabel,
|
|
154
|
+
onCommit,
|
|
155
|
+
onCancel,
|
|
156
|
+
className
|
|
157
|
+
}) {
|
|
158
|
+
const ref = useRef(null);
|
|
159
|
+
const done = useRef(false);
|
|
160
|
+
useEffect(() => {
|
|
161
|
+
const el = ref.current;
|
|
162
|
+
if (!el) return;
|
|
163
|
+
el.focus();
|
|
164
|
+
const dot = name.lastIndexOf(".");
|
|
165
|
+
if (kind === "file" && dot > 0) el.setSelectionRange(0, dot);
|
|
166
|
+
else el.select();
|
|
167
|
+
}, [kind, name]);
|
|
168
|
+
const finish = (next) => {
|
|
169
|
+
if (done.current) return;
|
|
170
|
+
done.current = true;
|
|
171
|
+
const trimmed = next?.trim() ?? "";
|
|
172
|
+
if (!trimmed) {
|
|
173
|
+
onCancel();
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
onCommit(trimmed);
|
|
177
|
+
};
|
|
178
|
+
return /* @__PURE__ */ jsx(
|
|
179
|
+
"input",
|
|
180
|
+
{
|
|
181
|
+
ref,
|
|
182
|
+
defaultValue: name,
|
|
183
|
+
"aria-label": ariaLabel ?? "Rename",
|
|
184
|
+
className: cn(
|
|
185
|
+
"h-7 min-w-0 flex-1 rounded-[6px] border border-ring bg-background px-1.5 text-sm outline-none",
|
|
186
|
+
className
|
|
187
|
+
),
|
|
188
|
+
onClick: (event) => event.stopPropagation(),
|
|
189
|
+
onDoubleClick: (event) => event.stopPropagation(),
|
|
190
|
+
onPointerDown: (event) => event.stopPropagation(),
|
|
191
|
+
onKeyDown: (event) => {
|
|
192
|
+
event.stopPropagation();
|
|
193
|
+
if (event.key === "Enter") {
|
|
194
|
+
event.preventDefault();
|
|
195
|
+
finish(event.currentTarget.value);
|
|
196
|
+
}
|
|
197
|
+
if (event.key === "Escape") {
|
|
198
|
+
event.preventDefault();
|
|
199
|
+
finish(null);
|
|
200
|
+
}
|
|
201
|
+
},
|
|
202
|
+
onBlur: (event) => finish(event.currentTarget.value)
|
|
203
|
+
}
|
|
204
|
+
);
|
|
205
|
+
}
|
|
150
206
|
var LONG_PRESS_MS = 450;
|
|
151
207
|
var MOVE_CANCEL_PX = 10;
|
|
152
208
|
function useCoarsePointer() {
|
|
@@ -244,6 +300,9 @@ function ExplorerListView(props) {
|
|
|
244
300
|
sortKey,
|
|
245
301
|
sortDirection,
|
|
246
302
|
onSortChange,
|
|
303
|
+
renamingId,
|
|
304
|
+
onRenameCommit,
|
|
305
|
+
onRenameCancel,
|
|
247
306
|
className
|
|
248
307
|
} = props;
|
|
249
308
|
const labels = useExplorerLabels();
|
|
@@ -284,7 +343,10 @@ function ExplorerListView(props) {
|
|
|
284
343
|
onDragStart,
|
|
285
344
|
onDragEnd,
|
|
286
345
|
onDrop,
|
|
287
|
-
onDragOverRow
|
|
346
|
+
onDragOverRow,
|
|
347
|
+
renaming: file.id === renamingId,
|
|
348
|
+
onRenameCommit,
|
|
349
|
+
onRenameCancel
|
|
288
350
|
},
|
|
289
351
|
file.id
|
|
290
352
|
)) })
|
|
@@ -306,7 +368,10 @@ function ExplorerListRow({
|
|
|
306
368
|
onDragStart,
|
|
307
369
|
onDragEnd,
|
|
308
370
|
onDrop,
|
|
309
|
-
onDragOverRow
|
|
371
|
+
onDragOverRow,
|
|
372
|
+
renaming,
|
|
373
|
+
onRenameCommit,
|
|
374
|
+
onRenameCancel
|
|
310
375
|
}) {
|
|
311
376
|
const labels = useExplorerLabels();
|
|
312
377
|
const isFolder = file.kind === "folder";
|
|
@@ -317,17 +382,18 @@ function ExplorerListRow({
|
|
|
317
382
|
role: "row",
|
|
318
383
|
"data-file-id": file.id,
|
|
319
384
|
"aria-selected": selected,
|
|
320
|
-
draggable: !coarse,
|
|
385
|
+
draggable: !coarse && !renaming,
|
|
321
386
|
onDragStart: (e) => {
|
|
322
387
|
e.dataTransfer.setData(DRAG_MIME, file.id);
|
|
323
388
|
e.dataTransfer.effectAllowed = "move";
|
|
324
389
|
onDragStart(file.id);
|
|
325
390
|
},
|
|
326
391
|
onDragEnd,
|
|
327
|
-
onPointerDown: pointer.onPointerDown,
|
|
328
|
-
onClick: pointer.onClick,
|
|
329
|
-
onDoubleClick: pointer.onDoubleClick,
|
|
392
|
+
onPointerDown: renaming ? void 0 : pointer.onPointerDown,
|
|
393
|
+
onClick: renaming ? void 0 : pointer.onClick,
|
|
394
|
+
onDoubleClick: renaming ? void 0 : pointer.onDoubleClick,
|
|
330
395
|
onKeyDown: (e) => {
|
|
396
|
+
if (renaming) return;
|
|
331
397
|
if (e.key === "Enter") {
|
|
332
398
|
e.preventDefault();
|
|
333
399
|
onActivate(file);
|
|
@@ -369,7 +435,16 @@ function ExplorerListRow({
|
|
|
369
435
|
/* @__PURE__ */ jsx("span", { className: "font-mono text-[10px] uppercase tracking-[0.12em] text-muted-foreground tabular-nums", children: String(idx + 1).padStart(2, "0") }),
|
|
370
436
|
/* @__PURE__ */ jsx("span", { className: isFolder ? "text-primary" : "text-muted-foreground", children: /* @__PURE__ */ jsx(FileIcon, { kind: file.kind, mimeType: file.mimeType, className: "size-5 text-current sm:size-4" }) }),
|
|
371
437
|
/* @__PURE__ */ jsxs("span", { className: "flex min-w-0 items-center gap-1.5", children: [
|
|
372
|
-
/* @__PURE__ */ jsx(
|
|
438
|
+
renaming && onRenameCommit && onRenameCancel ? /* @__PURE__ */ jsx(
|
|
439
|
+
RenameInput,
|
|
440
|
+
{
|
|
441
|
+
name: file.name,
|
|
442
|
+
kind: file.kind,
|
|
443
|
+
ariaLabel: labels.rename,
|
|
444
|
+
onCommit: onRenameCommit,
|
|
445
|
+
onCancel: onRenameCancel
|
|
446
|
+
}
|
|
447
|
+
) : /* @__PURE__ */ jsx("span", { className: cn("truncate", isFolder && "text-primary"), children: file.name }),
|
|
373
448
|
isProtected ? /* @__PURE__ */ jsxs("span", { className: "flex shrink-0 items-center gap-0.5 rounded-[10px] bg-primary/10 px-1.5 text-[10px] text-primary", children: [
|
|
374
449
|
/* @__PURE__ */ jsx(Link2, { "aria-hidden": "true", className: "size-2.5" }),
|
|
375
450
|
labels.inUse
|
|
@@ -446,6 +521,9 @@ function ExplorerGridView(props) {
|
|
|
446
521
|
draggingIds,
|
|
447
522
|
dropTargetId,
|
|
448
523
|
onDragOverRow,
|
|
524
|
+
renamingId,
|
|
525
|
+
onRenameCommit,
|
|
526
|
+
onRenameCancel,
|
|
449
527
|
className
|
|
450
528
|
} = props;
|
|
451
529
|
const labels = useExplorerLabels();
|
|
@@ -471,7 +549,10 @@ function ExplorerGridView(props) {
|
|
|
471
549
|
onDragStart,
|
|
472
550
|
onDragEnd,
|
|
473
551
|
onDrop,
|
|
474
|
-
onDragOverRow
|
|
552
|
+
onDragOverRow,
|
|
553
|
+
renaming: file.id === renamingId,
|
|
554
|
+
onRenameCommit,
|
|
555
|
+
onRenameCancel
|
|
475
556
|
},
|
|
476
557
|
file.id
|
|
477
558
|
)) });
|
|
@@ -495,9 +576,42 @@ function ExplorerGridItem({
|
|
|
495
576
|
onDragStart,
|
|
496
577
|
onDragEnd,
|
|
497
578
|
onDrop,
|
|
498
|
-
onDragOverRow
|
|
579
|
+
onDragOverRow,
|
|
580
|
+
renaming,
|
|
581
|
+
onRenameCommit,
|
|
582
|
+
onRenameCancel
|
|
499
583
|
}) {
|
|
584
|
+
const labels = useExplorerLabels();
|
|
500
585
|
const pointer = useExplorerItemPointer({ file, coarse, onSelect, onActivate });
|
|
586
|
+
const bodyClass = cn(
|
|
587
|
+
"group relative flex w-full flex-col items-stretch gap-1 overflow-hidden rounded-[10px] border border-border bg-background p-2 text-left outline-none",
|
|
588
|
+
"touch-manipulation select-none hover:bg-muted focus-visible:ring-2 focus-visible:ring-ring",
|
|
589
|
+
selected && "border-primary bg-muted",
|
|
590
|
+
dragging && "opacity-50",
|
|
591
|
+
dropTarget && "ring-2 ring-primary ring-inset"
|
|
592
|
+
);
|
|
593
|
+
const inner = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
594
|
+
/* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1 pr-8", children: [
|
|
595
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono text-[9px] uppercase tracking-[0.12em] text-muted-foreground tabular-nums", children: String(idx + 1).padStart(2, "0") }),
|
|
596
|
+
isProtected ? /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-0.5 rounded-[10px] bg-primary/10 px-1.5 py-0.5 text-[10px] text-primary", children: [
|
|
597
|
+
/* @__PURE__ */ jsx(Link2, { "aria-hidden": "true", className: "size-2.5" }),
|
|
598
|
+
/* @__PURE__ */ jsx("span", { className: "sr-only", children: inUseLabel })
|
|
599
|
+
] }) : null
|
|
600
|
+
] }),
|
|
601
|
+
/* @__PURE__ */ jsx("span", { className: cn("flex items-center justify-center py-1", file.kind === "folder" ? "text-primary" : "text-muted-foreground"), children: /* @__PURE__ */ jsx(FileIcon, { kind: file.kind, mimeType: file.mimeType, className: "size-12 text-current" }) }),
|
|
602
|
+
renaming && onRenameCommit && onRenameCancel ? /* @__PURE__ */ jsx(
|
|
603
|
+
RenameInput,
|
|
604
|
+
{
|
|
605
|
+
name: file.name,
|
|
606
|
+
kind: file.kind,
|
|
607
|
+
ariaLabel: labels.rename,
|
|
608
|
+
className: "text-[11px]",
|
|
609
|
+
onCommit: onRenameCommit,
|
|
610
|
+
onCancel: onRenameCancel
|
|
611
|
+
}
|
|
612
|
+
) : /* @__PURE__ */ jsx("span", { className: cn("truncate text-[11px]", file.kind === "folder" && "text-primary"), children: file.name }),
|
|
613
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono text-[9px] uppercase tracking-[0.12em] text-muted-foreground", children: file.kind === "folder" ? folderLabel : formatSizeShort(file.size) })
|
|
614
|
+
] });
|
|
501
615
|
return /* @__PURE__ */ jsxs("li", { className: "relative", children: [
|
|
502
616
|
coarse && onContextMenu ? /* @__PURE__ */ jsx(
|
|
503
617
|
"button",
|
|
@@ -513,7 +627,7 @@ function ExplorerGridItem({
|
|
|
513
627
|
children: /* @__PURE__ */ jsx(MoreHorizontal, { "aria-hidden": "true", className: "size-5" })
|
|
514
628
|
}
|
|
515
629
|
) : null,
|
|
516
|
-
/* @__PURE__ */
|
|
630
|
+
renaming ? /* @__PURE__ */ jsx("div", { "data-file-id": file.id, className: bodyClass, children: inner }) : /* @__PURE__ */ jsx(
|
|
517
631
|
"button",
|
|
518
632
|
{
|
|
519
633
|
type: "button",
|
|
@@ -555,25 +669,8 @@ function ExplorerGridItem({
|
|
|
555
669
|
e.preventDefault();
|
|
556
670
|
onDrop(file.id);
|
|
557
671
|
},
|
|
558
|
-
className:
|
|
559
|
-
|
|
560
|
-
"touch-manipulation select-none hover:bg-muted focus-visible:ring-2 focus-visible:ring-ring",
|
|
561
|
-
selected && "border-primary bg-muted",
|
|
562
|
-
dragging && "opacity-50",
|
|
563
|
-
dropTarget && "ring-2 ring-primary ring-inset"
|
|
564
|
-
),
|
|
565
|
-
children: [
|
|
566
|
-
/* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1 pr-8", children: [
|
|
567
|
-
/* @__PURE__ */ jsx("span", { className: "font-mono text-[9px] uppercase tracking-[0.12em] text-muted-foreground tabular-nums", children: String(idx + 1).padStart(2, "0") }),
|
|
568
|
-
isProtected ? /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-0.5 rounded-[10px] bg-primary/10 px-1.5 py-0.5 text-[10px] text-primary", children: [
|
|
569
|
-
/* @__PURE__ */ jsx(Link2, { "aria-hidden": "true", className: "size-2.5" }),
|
|
570
|
-
/* @__PURE__ */ jsx("span", { className: "sr-only", children: inUseLabel })
|
|
571
|
-
] }) : null
|
|
572
|
-
] }),
|
|
573
|
-
/* @__PURE__ */ jsx("span", { className: cn("flex items-center justify-center py-1", file.kind === "folder" ? "text-primary" : "text-muted-foreground"), children: /* @__PURE__ */ jsx(FileIcon, { kind: file.kind, mimeType: file.mimeType, className: "size-12 text-current" }) }),
|
|
574
|
-
/* @__PURE__ */ jsx("span", { className: cn("truncate text-[11px]", file.kind === "folder" && "text-primary"), children: file.name }),
|
|
575
|
-
/* @__PURE__ */ jsx("span", { className: "font-mono text-[9px] uppercase tracking-[0.12em] text-muted-foreground", children: file.kind === "folder" ? folderLabel : formatSizeShort(file.size) })
|
|
576
|
-
]
|
|
672
|
+
className: bodyClass,
|
|
673
|
+
children: inner
|
|
577
674
|
}
|
|
578
675
|
)
|
|
579
676
|
] });
|
|
@@ -742,6 +839,13 @@ function swallowClickThrough() {
|
|
|
742
839
|
window.removeEventListener("pointerup", swallow, true);
|
|
743
840
|
}, 400);
|
|
744
841
|
}
|
|
842
|
+
|
|
843
|
+
// src/share-url.ts
|
|
844
|
+
function absoluteShareUrl(link) {
|
|
845
|
+
if (link.startsWith("http://") || link.startsWith("https://")) return link;
|
|
846
|
+
const origin = typeof window === "undefined" ? "" : window.location.origin;
|
|
847
|
+
return `${origin}${link.startsWith("/") ? link : `/${link}`}`;
|
|
848
|
+
}
|
|
745
849
|
function ExplorerContextMenu(props) {
|
|
746
850
|
const {
|
|
747
851
|
target,
|
|
@@ -894,7 +998,7 @@ function ExplorerContextMenu(props) {
|
|
|
894
998
|
/* @__PURE__ */ jsx(Copy, { "aria-hidden": "true", className: "size-4" }),
|
|
895
999
|
labels.paste
|
|
896
1000
|
] }) : null,
|
|
897
|
-
actions.createShare ? /* @__PURE__ */ jsxs(
|
|
1001
|
+
target.file.kind === "file" && actions.createShare ? /* @__PURE__ */ jsxs(
|
|
898
1002
|
"button",
|
|
899
1003
|
{
|
|
900
1004
|
type: "button",
|
|
@@ -903,7 +1007,7 @@ function ExplorerContextMenu(props) {
|
|
|
903
1007
|
onClick: () => {
|
|
904
1008
|
close();
|
|
905
1009
|
void actions.createShare({ id: target.file.id }).then(
|
|
906
|
-
(
|
|
1010
|
+
(link) => navigator.clipboard.writeText(absoluteShareUrl(link))
|
|
907
1011
|
);
|
|
908
1012
|
},
|
|
909
1013
|
children: [
|
|
@@ -963,92 +1067,6 @@ function Overlay({
|
|
|
963
1067
|
}
|
|
964
1068
|
);
|
|
965
1069
|
}
|
|
966
|
-
function CreateFolderDialog({
|
|
967
|
-
open,
|
|
968
|
-
onOpenChange,
|
|
969
|
-
onCreate,
|
|
970
|
-
title,
|
|
971
|
-
description,
|
|
972
|
-
confirmLabel,
|
|
973
|
-
initialName = ""
|
|
974
|
-
}) {
|
|
975
|
-
const labels = useExplorerLabels();
|
|
976
|
-
const [name, setName] = useState(initialName);
|
|
977
|
-
const [saving, setSaving] = useState(false);
|
|
978
|
-
const [error, setError] = useState(null);
|
|
979
|
-
const inputRef = useRef(null);
|
|
980
|
-
useEffect(() => {
|
|
981
|
-
if (!open) {
|
|
982
|
-
setSaving(false);
|
|
983
|
-
setError(null);
|
|
984
|
-
return;
|
|
985
|
-
}
|
|
986
|
-
setName(initialName);
|
|
987
|
-
const id = window.setTimeout(() => {
|
|
988
|
-
inputRef.current?.focus();
|
|
989
|
-
inputRef.current?.select();
|
|
990
|
-
}, 0);
|
|
991
|
-
return () => window.clearTimeout(id);
|
|
992
|
-
}, [initialName, open]);
|
|
993
|
-
async function submit() {
|
|
994
|
-
const trimmed = name.trim();
|
|
995
|
-
if (!trimmed || saving) return;
|
|
996
|
-
setSaving(true);
|
|
997
|
-
setError(null);
|
|
998
|
-
try {
|
|
999
|
-
await onCreate(trimmed);
|
|
1000
|
-
onOpenChange(false);
|
|
1001
|
-
} catch (cause) {
|
|
1002
|
-
const code = cause && typeof cause === "object" && "code" in cause ? String(cause.code) : "";
|
|
1003
|
-
setError(code === "Conflict" ? labels.folderExists : labels.folderCreateFailed);
|
|
1004
|
-
} finally {
|
|
1005
|
-
setSaving(false);
|
|
1006
|
-
}
|
|
1007
|
-
}
|
|
1008
|
-
return /* @__PURE__ */ jsxs(Overlay, { open, onClose: () => onOpenChange(false), children: [
|
|
1009
|
-
/* @__PURE__ */ jsx("h2", { className: "text-base font-semibold", children: title ?? labels.newFolder }),
|
|
1010
|
-
/* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: description ?? labels.folderNameHint }),
|
|
1011
|
-
/* @__PURE__ */ jsx(
|
|
1012
|
-
"input",
|
|
1013
|
-
{
|
|
1014
|
-
ref: inputRef,
|
|
1015
|
-
value: name,
|
|
1016
|
-
onChange: (event) => setName(event.target.value),
|
|
1017
|
-
onKeyDown: (event) => {
|
|
1018
|
-
if (event.key === "Enter") {
|
|
1019
|
-
event.preventDefault();
|
|
1020
|
-
void submit();
|
|
1021
|
-
}
|
|
1022
|
-
},
|
|
1023
|
-
placeholder: labels.folderName,
|
|
1024
|
-
"aria-invalid": Boolean(error),
|
|
1025
|
-
className: "mt-3 h-9 w-full rounded-[10px] border border-border bg-background px-3 text-sm outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
1026
|
-
}
|
|
1027
|
-
),
|
|
1028
|
-
error ? /* @__PURE__ */ jsx("p", { className: "mt-2 text-sm text-destructive", children: error }) : null,
|
|
1029
|
-
/* @__PURE__ */ jsxs("div", { className: "mt-4 flex justify-end gap-2", children: [
|
|
1030
|
-
/* @__PURE__ */ jsx(
|
|
1031
|
-
"button",
|
|
1032
|
-
{
|
|
1033
|
-
type: "button",
|
|
1034
|
-
className: "inline-flex h-8 items-center rounded-[10px] border border-border px-3 text-sm",
|
|
1035
|
-
onClick: () => onOpenChange(false),
|
|
1036
|
-
children: labels.cancel
|
|
1037
|
-
}
|
|
1038
|
-
),
|
|
1039
|
-
/* @__PURE__ */ jsx(
|
|
1040
|
-
"button",
|
|
1041
|
-
{
|
|
1042
|
-
type: "button",
|
|
1043
|
-
disabled: !name.trim() || saving,
|
|
1044
|
-
className: "inline-flex h-8 items-center rounded-[10px] bg-primary px-3 text-sm text-primary-foreground disabled:opacity-50",
|
|
1045
|
-
onClick: () => void submit(),
|
|
1046
|
-
children: confirmLabel ?? labels.createFolder
|
|
1047
|
-
}
|
|
1048
|
-
)
|
|
1049
|
-
] })
|
|
1050
|
-
] });
|
|
1051
|
-
}
|
|
1052
1070
|
function ConfirmDeleteDialog({
|
|
1053
1071
|
open,
|
|
1054
1072
|
names,
|
|
@@ -1514,6 +1532,32 @@ function UploadQueueProvider({
|
|
|
1514
1532
|
}
|
|
1515
1533
|
var PAGE_SIZE = 50;
|
|
1516
1534
|
var DEFAULT_PREFS = { view: "list", sortKey: "name", sortDir: "asc" };
|
|
1535
|
+
var DRAFT_FOLDER_ID = "__draft-folder__";
|
|
1536
|
+
function unusedFolderName(files, base) {
|
|
1537
|
+
const taken = new Set(files.map((file) => file.name.toLowerCase()));
|
|
1538
|
+
if (!taken.has(base.toLowerCase())) return base;
|
|
1539
|
+
let n = 2;
|
|
1540
|
+
while (taken.has(`${base} ${n}`.toLowerCase())) n += 1;
|
|
1541
|
+
return `${base} ${n}`;
|
|
1542
|
+
}
|
|
1543
|
+
function draftFolder(name, tenantId, parentId) {
|
|
1544
|
+
return {
|
|
1545
|
+
id: DRAFT_FOLDER_ID,
|
|
1546
|
+
tenantId,
|
|
1547
|
+
parentId,
|
|
1548
|
+
name,
|
|
1549
|
+
path: name,
|
|
1550
|
+
kind: "folder",
|
|
1551
|
+
size: 0,
|
|
1552
|
+
mimeType: "",
|
|
1553
|
+
s3Key: "",
|
|
1554
|
+
ownerId: "",
|
|
1555
|
+
metadata: {},
|
|
1556
|
+
createdAt: /* @__PURE__ */ new Date(0),
|
|
1557
|
+
updatedAt: /* @__PURE__ */ new Date(0),
|
|
1558
|
+
deletedAt: null
|
|
1559
|
+
};
|
|
1560
|
+
}
|
|
1517
1561
|
function reviveFile(node) {
|
|
1518
1562
|
const asDate = (value) => value instanceof Date ? value : new Date(value);
|
|
1519
1563
|
return {
|
|
@@ -1637,7 +1681,7 @@ function FileExplorerInner(props) {
|
|
|
1637
1681
|
const [prefsReady, setPrefsReady] = useState(!persistViewKey);
|
|
1638
1682
|
const skipPrefsWrite = useRef(!persistViewKey);
|
|
1639
1683
|
const [menu, setMenu] = useState(null);
|
|
1640
|
-
const [
|
|
1684
|
+
const [draftFolderName, setDraftFolderName] = useState(null);
|
|
1641
1685
|
const [renameTarget, setRenameTarget] = useState(null);
|
|
1642
1686
|
const [deleteTargets, setDeleteTargets] = useState([]);
|
|
1643
1687
|
const [purgeTargets, setPurgeTargets] = useState([]);
|
|
@@ -1819,6 +1863,7 @@ function FileExplorerInner(props) {
|
|
|
1819
1863
|
}, [refreshKey]);
|
|
1820
1864
|
const handleActivate = useCallback(
|
|
1821
1865
|
(file) => {
|
|
1866
|
+
if (file.id === DRAFT_FOLDER_ID) return;
|
|
1822
1867
|
if (file.kind === "folder") {
|
|
1823
1868
|
onOpenFolder?.(file);
|
|
1824
1869
|
return;
|
|
@@ -1830,6 +1875,7 @@ function FileExplorerInner(props) {
|
|
|
1830
1875
|
[onActivate, onDownload, onOpenFolder, onPreview]
|
|
1831
1876
|
);
|
|
1832
1877
|
const handleContextMenu = useCallback((file, event) => {
|
|
1878
|
+
if (file.id === DRAFT_FOLDER_ID) return;
|
|
1833
1879
|
setMenu({ kind: "item", file, x: event.clientX, y: event.clientY });
|
|
1834
1880
|
}, []);
|
|
1835
1881
|
const clearSelection = useCallback(() => {
|
|
@@ -1841,6 +1887,7 @@ function FileExplorerInner(props) {
|
|
|
1841
1887
|
}, [clearSelection, parentId, trashOpen]);
|
|
1842
1888
|
const handleSelect = useCallback(
|
|
1843
1889
|
(file, event) => {
|
|
1890
|
+
if (file.id === DRAFT_FOLDER_ID) return;
|
|
1844
1891
|
const id = file.id;
|
|
1845
1892
|
if (event.shiftKey && anchorId.current) {
|
|
1846
1893
|
const from = sortedFiles.findIndex((item) => item.id === anchorId.current);
|
|
@@ -1979,6 +2026,37 @@ function FileExplorerInner(props) {
|
|
|
1979
2026
|
trashOpen
|
|
1980
2027
|
]
|
|
1981
2028
|
);
|
|
2029
|
+
const commitRename = (name) => {
|
|
2030
|
+
if (draftFolderName != null) {
|
|
2031
|
+
setDraftFolderName(null);
|
|
2032
|
+
void actions.createFolder?.({ name, parentId }).then(() => reload());
|
|
2033
|
+
return;
|
|
2034
|
+
}
|
|
2035
|
+
if (!renameTarget) return;
|
|
2036
|
+
const target = renameTarget;
|
|
2037
|
+
setRenameTarget(null);
|
|
2038
|
+
if (name === target.name) return;
|
|
2039
|
+
void actions.renameFile(target.id, name).then(() => reload());
|
|
2040
|
+
};
|
|
2041
|
+
const cancelRename = () => {
|
|
2042
|
+
setDraftFolderName(null);
|
|
2043
|
+
setRenameTarget(null);
|
|
2044
|
+
};
|
|
2045
|
+
const startCreateFolder = () => {
|
|
2046
|
+
if (!actions.createFolder || trashOpen) return;
|
|
2047
|
+
setMenu(null);
|
|
2048
|
+
setRenameTarget(null);
|
|
2049
|
+
setQuery("");
|
|
2050
|
+
setDraftFolderName(unusedFolderName(pageItems, labels.newFolder));
|
|
2051
|
+
};
|
|
2052
|
+
const displayFiles = useMemo(() => {
|
|
2053
|
+
if (draftFolderName == null) return sortedFiles;
|
|
2054
|
+
return [
|
|
2055
|
+
draftFolder(draftFolderName, tenantId, parentId),
|
|
2056
|
+
...sortedFiles
|
|
2057
|
+
];
|
|
2058
|
+
}, [draftFolderName, parentId, sortedFiles, tenantId]);
|
|
2059
|
+
const renamingId = draftFolderName != null ? DRAFT_FOLDER_ID : renameTarget?.id ?? null;
|
|
1982
2060
|
const openUpload = () => uploadInputRef.current?.click();
|
|
1983
2061
|
return /* @__PURE__ */ jsxs(
|
|
1984
2062
|
"div",
|
|
@@ -2027,7 +2105,7 @@ function FileExplorerInner(props) {
|
|
|
2027
2105
|
view: explorer.view,
|
|
2028
2106
|
onViewChange: explorer.setView,
|
|
2029
2107
|
onUpload: requestUpload && !trashOpen ? openUpload : void 0,
|
|
2030
|
-
onNewFolder: actions.createFolder && !trashOpen ?
|
|
2108
|
+
onNewFolder: actions.createFolder && !trashOpen ? startCreateFolder : void 0,
|
|
2031
2109
|
query,
|
|
2032
2110
|
onQueryChange: searchFiles ? setQuery : void 0,
|
|
2033
2111
|
trashOpen,
|
|
@@ -2042,7 +2120,7 @@ function FileExplorerInner(props) {
|
|
|
2042
2120
|
}
|
|
2043
2121
|
)
|
|
2044
2122
|
] }),
|
|
2045
|
-
/* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 overflow-y-auto", children: (overlayLoading || pageStatus === "loading") &&
|
|
2123
|
+
/* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 overflow-y-auto", children: (overlayLoading || pageStatus === "loading") && displayFiles.length === 0 ? /* @__PURE__ */ jsx("p", { className: "px-4 py-12 text-center font-mono text-[10px] uppercase tracking-[0.12em] text-muted-foreground", children: labels.loading }) : overlayError || pageStatus === "error" && pageError ? /* @__PURE__ */ jsx("div", { className: "border-b border-destructive/30 bg-destructive/5 px-4 py-3 text-sm text-destructive", children: (overlayError ?? pageError)?.message ?? labels.folderCreateFailed }) : displayFiles.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "flex min-h-full flex-col justify-center px-4 py-16 text-center", children: [
|
|
2046
2124
|
/* @__PURE__ */ jsx("p", { className: "font-mono text-[10px] uppercase tracking-[0.12em] text-muted-foreground", children: trashOpen ? labels.emptyTrash : query.trim() ? labels.noMatches : labels.emptyFolder }),
|
|
2047
2125
|
trashOpen ? /* @__PURE__ */ jsx("p", { className: "mt-2 text-sm text-muted-foreground", children: labels.trashRetention }) : !query.trim() ? /* @__PURE__ */ jsx("p", { className: "mt-2 text-sm text-muted-foreground", children: labels.dropHint }) : null
|
|
2048
2126
|
] }) : explorer.view === "list" ? /* @__PURE__ */ jsx(
|
|
@@ -2055,7 +2133,7 @@ function FileExplorerInner(props) {
|
|
|
2055
2133
|
children: /* @__PURE__ */ jsx(
|
|
2056
2134
|
ExplorerListView,
|
|
2057
2135
|
{
|
|
2058
|
-
files:
|
|
2136
|
+
files: displayFiles,
|
|
2059
2137
|
protectedIds: protectedSet,
|
|
2060
2138
|
selectedIds,
|
|
2061
2139
|
onSelect: handleSelect,
|
|
@@ -2070,7 +2148,10 @@ function FileExplorerInner(props) {
|
|
|
2070
2148
|
onDragOverRow: explorer.setDropTarget,
|
|
2071
2149
|
sortKey,
|
|
2072
2150
|
sortDirection: sortDir,
|
|
2073
|
-
onSortChange: handleSortChange
|
|
2151
|
+
onSortChange: handleSortChange,
|
|
2152
|
+
renamingId,
|
|
2153
|
+
onRenameCommit: commitRename,
|
|
2154
|
+
onRenameCancel: cancelRename
|
|
2074
2155
|
}
|
|
2075
2156
|
)
|
|
2076
2157
|
}
|
|
@@ -2084,7 +2165,7 @@ function FileExplorerInner(props) {
|
|
|
2084
2165
|
children: /* @__PURE__ */ jsx(
|
|
2085
2166
|
ExplorerGridView,
|
|
2086
2167
|
{
|
|
2087
|
-
files:
|
|
2168
|
+
files: displayFiles,
|
|
2088
2169
|
protectedIds: protectedSet,
|
|
2089
2170
|
selectedIds,
|
|
2090
2171
|
onSelect: handleSelect,
|
|
@@ -2096,7 +2177,10 @@ function FileExplorerInner(props) {
|
|
|
2096
2177
|
draggingId: explorer.draggingId,
|
|
2097
2178
|
draggingIds,
|
|
2098
2179
|
dropTargetId: explorer.dropTargetId,
|
|
2099
|
-
onDragOverRow: explorer.setDropTarget
|
|
2180
|
+
onDragOverRow: explorer.setDropTarget,
|
|
2181
|
+
renamingId,
|
|
2182
|
+
onRenameCommit: commitRename,
|
|
2183
|
+
onRenameCancel: cancelRename
|
|
2100
2184
|
}
|
|
2101
2185
|
)
|
|
2102
2186
|
}
|
|
@@ -2160,7 +2244,7 @@ function FileExplorerInner(props) {
|
|
|
2160
2244
|
onRequestPurge: actions.purgeNode ? (file) => setPurgeTargets([file]) : void 0,
|
|
2161
2245
|
onCopy: (file) => copyToClipboard([file]),
|
|
2162
2246
|
onPaste: clipboard.length > 0 && !trashOpen ? (folder) => void pasteClipboard(folder ? folder.id : parentId) : void 0,
|
|
2163
|
-
onNewFolder: actions.createFolder ?
|
|
2247
|
+
onNewFolder: actions.createFolder ? startCreateFolder : void 0,
|
|
2164
2248
|
onUpload: requestUpload ? openUpload : void 0
|
|
2165
2249
|
}
|
|
2166
2250
|
),
|
|
@@ -2178,7 +2262,11 @@ function FileExplorerInner(props) {
|
|
|
2178
2262
|
if (file.kind !== "file") continue;
|
|
2179
2263
|
tokens.push(await actions.createShare({ id: file.id }));
|
|
2180
2264
|
}
|
|
2181
|
-
if (tokens.length > 0)
|
|
2265
|
+
if (tokens.length > 0) {
|
|
2266
|
+
await navigator.clipboard.writeText(
|
|
2267
|
+
tokens.map(absoluteShareUrl).join("\n")
|
|
2268
|
+
);
|
|
2269
|
+
}
|
|
2182
2270
|
} : void 0,
|
|
2183
2271
|
onDelete: () => setDeleteTargets(selectedFiles),
|
|
2184
2272
|
onRestore: actions.restoreNode ? async () => {
|
|
@@ -2198,36 +2286,6 @@ function FileExplorerInner(props) {
|
|
|
2198
2286
|
onClear: () => setClipboard([])
|
|
2199
2287
|
}
|
|
2200
2288
|
) }),
|
|
2201
|
-
/* @__PURE__ */ jsx(
|
|
2202
|
-
CreateFolderDialog,
|
|
2203
|
-
{
|
|
2204
|
-
open: folderOpen,
|
|
2205
|
-
onOpenChange: setFolderOpen,
|
|
2206
|
-
onCreate: async (name) => {
|
|
2207
|
-
await actions.createFolder?.({ name, parentId });
|
|
2208
|
-
await reload();
|
|
2209
|
-
}
|
|
2210
|
-
}
|
|
2211
|
-
),
|
|
2212
|
-
/* @__PURE__ */ jsx(
|
|
2213
|
-
CreateFolderDialog,
|
|
2214
|
-
{
|
|
2215
|
-
open: Boolean(renameTarget),
|
|
2216
|
-
onOpenChange: (open) => {
|
|
2217
|
-
if (!open) setRenameTarget(null);
|
|
2218
|
-
},
|
|
2219
|
-
title: labels.rename,
|
|
2220
|
-
description: labels.renameHint,
|
|
2221
|
-
confirmLabel: labels.rename,
|
|
2222
|
-
initialName: renameTarget?.name ?? "",
|
|
2223
|
-
onCreate: async (name) => {
|
|
2224
|
-
if (!renameTarget) return;
|
|
2225
|
-
await actions.renameFile(renameTarget.id, name);
|
|
2226
|
-
setRenameTarget(null);
|
|
2227
|
-
await reload();
|
|
2228
|
-
}
|
|
2229
|
-
}
|
|
2230
|
-
),
|
|
2231
2289
|
/* @__PURE__ */ jsx(
|
|
2232
2290
|
ConfirmDeleteDialog,
|
|
2233
2291
|
{
|
|
@@ -2382,6 +2440,92 @@ function FilePreviewDialog({
|
|
|
2382
2440
|
) })
|
|
2383
2441
|
] });
|
|
2384
2442
|
}
|
|
2443
|
+
function CreateFolderDialog({
|
|
2444
|
+
open,
|
|
2445
|
+
onOpenChange,
|
|
2446
|
+
onCreate,
|
|
2447
|
+
title,
|
|
2448
|
+
description,
|
|
2449
|
+
confirmLabel,
|
|
2450
|
+
initialName = ""
|
|
2451
|
+
}) {
|
|
2452
|
+
const labels = useExplorerLabels();
|
|
2453
|
+
const [name, setName] = useState(initialName);
|
|
2454
|
+
const [saving, setSaving] = useState(false);
|
|
2455
|
+
const [error, setError] = useState(null);
|
|
2456
|
+
const inputRef = useRef(null);
|
|
2457
|
+
useEffect(() => {
|
|
2458
|
+
if (!open) {
|
|
2459
|
+
setSaving(false);
|
|
2460
|
+
setError(null);
|
|
2461
|
+
return;
|
|
2462
|
+
}
|
|
2463
|
+
setName(initialName);
|
|
2464
|
+
const id = window.setTimeout(() => {
|
|
2465
|
+
inputRef.current?.focus();
|
|
2466
|
+
inputRef.current?.select();
|
|
2467
|
+
}, 0);
|
|
2468
|
+
return () => window.clearTimeout(id);
|
|
2469
|
+
}, [initialName, open]);
|
|
2470
|
+
async function submit() {
|
|
2471
|
+
const trimmed = name.trim();
|
|
2472
|
+
if (!trimmed || saving) return;
|
|
2473
|
+
setSaving(true);
|
|
2474
|
+
setError(null);
|
|
2475
|
+
try {
|
|
2476
|
+
await onCreate(trimmed);
|
|
2477
|
+
onOpenChange(false);
|
|
2478
|
+
} catch (cause) {
|
|
2479
|
+
const code = cause && typeof cause === "object" && "code" in cause ? String(cause.code) : "";
|
|
2480
|
+
setError(code === "Conflict" ? labels.folderExists : labels.folderCreateFailed);
|
|
2481
|
+
} finally {
|
|
2482
|
+
setSaving(false);
|
|
2483
|
+
}
|
|
2484
|
+
}
|
|
2485
|
+
return /* @__PURE__ */ jsxs(Overlay, { open, onClose: () => onOpenChange(false), children: [
|
|
2486
|
+
/* @__PURE__ */ jsx("h2", { className: "text-base font-semibold", children: title ?? labels.newFolder }),
|
|
2487
|
+
/* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: description ?? labels.folderNameHint }),
|
|
2488
|
+
/* @__PURE__ */ jsx(
|
|
2489
|
+
"input",
|
|
2490
|
+
{
|
|
2491
|
+
ref: inputRef,
|
|
2492
|
+
value: name,
|
|
2493
|
+
onChange: (event) => setName(event.target.value),
|
|
2494
|
+
onKeyDown: (event) => {
|
|
2495
|
+
if (event.key === "Enter") {
|
|
2496
|
+
event.preventDefault();
|
|
2497
|
+
void submit();
|
|
2498
|
+
}
|
|
2499
|
+
},
|
|
2500
|
+
placeholder: labels.folderName,
|
|
2501
|
+
"aria-invalid": Boolean(error),
|
|
2502
|
+
className: "mt-3 h-9 w-full rounded-[10px] border border-border bg-background px-3 text-sm outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
2503
|
+
}
|
|
2504
|
+
),
|
|
2505
|
+
error ? /* @__PURE__ */ jsx("p", { className: "mt-2 text-sm text-destructive", children: error }) : null,
|
|
2506
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-4 flex justify-end gap-2", children: [
|
|
2507
|
+
/* @__PURE__ */ jsx(
|
|
2508
|
+
"button",
|
|
2509
|
+
{
|
|
2510
|
+
type: "button",
|
|
2511
|
+
className: "inline-flex h-8 items-center rounded-[10px] border border-border px-3 text-sm",
|
|
2512
|
+
onClick: () => onOpenChange(false),
|
|
2513
|
+
children: labels.cancel
|
|
2514
|
+
}
|
|
2515
|
+
),
|
|
2516
|
+
/* @__PURE__ */ jsx(
|
|
2517
|
+
"button",
|
|
2518
|
+
{
|
|
2519
|
+
type: "button",
|
|
2520
|
+
disabled: !name.trim() || saving,
|
|
2521
|
+
className: "inline-flex h-8 items-center rounded-[10px] bg-primary px-3 text-sm text-primary-foreground disabled:opacity-50",
|
|
2522
|
+
onClick: () => void submit(),
|
|
2523
|
+
children: confirmLabel ?? labels.createFolder
|
|
2524
|
+
}
|
|
2525
|
+
)
|
|
2526
|
+
] })
|
|
2527
|
+
] });
|
|
2528
|
+
}
|
|
2385
2529
|
|
|
2386
2530
|
export { Breadcrumbs, ConfirmDeleteDialog, CreateFolderDialog, EmptyState, ErrorState, ExplorerClipboardToast, ExplorerContextMenu, ExplorerGridView, ExplorerLabelsProvider, ExplorerListView, ExplorerSelectionToast, ExplorerToolbar, ExplorerUploadStatus, FILE_ICON_MAP, FileExplorer, FileIcon, FilePreviewDialog, UploadQueueProvider, canPreviewFile, cn, defaultLabels, resolveFileIcon, useExplorerLabels, useUploadEnqueue, useUploadQueue };
|
|
2387
2531
|
//# sourceMappingURL=index.js.map
|