@vryzel/file-next-ui 0.4.1 → 0.4.3
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/dist/index.cjs +323 -177
- 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 +323 -177
- 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,
|
|
@@ -834,24 +938,26 @@ function ExplorerContextMenu(props) {
|
|
|
834
938
|
/* @__PURE__ */ jsx(RotateCcw, { "aria-hidden": "true", className: "size-4" }),
|
|
835
939
|
labels.restore
|
|
836
940
|
] }),
|
|
837
|
-
/* @__PURE__ */
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
941
|
+
onRequestPurge || actions.purgeNode ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
942
|
+
/* @__PURE__ */ jsx("div", { className: "my-1 h-px bg-border" }),
|
|
943
|
+
/* @__PURE__ */ jsxs(
|
|
944
|
+
"button",
|
|
945
|
+
{
|
|
946
|
+
type: "button",
|
|
947
|
+
role: "menuitem",
|
|
948
|
+
className: cn(itemClass, "text-destructive"),
|
|
949
|
+
onClick: () => {
|
|
950
|
+
close();
|
|
951
|
+
if (onRequestPurge) onRequestPurge(target.file);
|
|
952
|
+
else void actions.purgeNode?.({ id: target.file.id });
|
|
953
|
+
},
|
|
954
|
+
children: [
|
|
955
|
+
/* @__PURE__ */ jsx(X, { "aria-hidden": "true", className: "size-4" }),
|
|
956
|
+
labels.deleteForever
|
|
957
|
+
]
|
|
958
|
+
}
|
|
959
|
+
)
|
|
960
|
+
] }) : null
|
|
855
961
|
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
856
962
|
target.file.kind === "file" && onPreview ? /* @__PURE__ */ jsxs("button", { type: "button", role: "menuitem", className: itemClass, onClick: () => {
|
|
857
963
|
close();
|
|
@@ -892,7 +998,7 @@ function ExplorerContextMenu(props) {
|
|
|
892
998
|
/* @__PURE__ */ jsx(Copy, { "aria-hidden": "true", className: "size-4" }),
|
|
893
999
|
labels.paste
|
|
894
1000
|
] }) : null,
|
|
895
|
-
actions.createShare ? /* @__PURE__ */ jsxs(
|
|
1001
|
+
target.file.kind === "file" && actions.createShare ? /* @__PURE__ */ jsxs(
|
|
896
1002
|
"button",
|
|
897
1003
|
{
|
|
898
1004
|
type: "button",
|
|
@@ -901,7 +1007,7 @@ function ExplorerContextMenu(props) {
|
|
|
901
1007
|
onClick: () => {
|
|
902
1008
|
close();
|
|
903
1009
|
void actions.createShare({ id: target.file.id }).then(
|
|
904
|
-
(
|
|
1010
|
+
(link) => navigator.clipboard.writeText(absoluteShareUrl(link))
|
|
905
1011
|
);
|
|
906
1012
|
},
|
|
907
1013
|
children: [
|
|
@@ -961,92 +1067,6 @@ function Overlay({
|
|
|
961
1067
|
}
|
|
962
1068
|
);
|
|
963
1069
|
}
|
|
964
|
-
function CreateFolderDialog({
|
|
965
|
-
open,
|
|
966
|
-
onOpenChange,
|
|
967
|
-
onCreate,
|
|
968
|
-
title,
|
|
969
|
-
description,
|
|
970
|
-
confirmLabel,
|
|
971
|
-
initialName = ""
|
|
972
|
-
}) {
|
|
973
|
-
const labels = useExplorerLabels();
|
|
974
|
-
const [name, setName] = useState(initialName);
|
|
975
|
-
const [saving, setSaving] = useState(false);
|
|
976
|
-
const [error, setError] = useState(null);
|
|
977
|
-
const inputRef = useRef(null);
|
|
978
|
-
useEffect(() => {
|
|
979
|
-
if (!open) {
|
|
980
|
-
setSaving(false);
|
|
981
|
-
setError(null);
|
|
982
|
-
return;
|
|
983
|
-
}
|
|
984
|
-
setName(initialName);
|
|
985
|
-
const id = window.setTimeout(() => {
|
|
986
|
-
inputRef.current?.focus();
|
|
987
|
-
inputRef.current?.select();
|
|
988
|
-
}, 0);
|
|
989
|
-
return () => window.clearTimeout(id);
|
|
990
|
-
}, [initialName, open]);
|
|
991
|
-
async function submit() {
|
|
992
|
-
const trimmed = name.trim();
|
|
993
|
-
if (!trimmed || saving) return;
|
|
994
|
-
setSaving(true);
|
|
995
|
-
setError(null);
|
|
996
|
-
try {
|
|
997
|
-
await onCreate(trimmed);
|
|
998
|
-
onOpenChange(false);
|
|
999
|
-
} catch (cause) {
|
|
1000
|
-
const code = cause && typeof cause === "object" && "code" in cause ? String(cause.code) : "";
|
|
1001
|
-
setError(code === "Conflict" ? labels.folderExists : labels.folderCreateFailed);
|
|
1002
|
-
} finally {
|
|
1003
|
-
setSaving(false);
|
|
1004
|
-
}
|
|
1005
|
-
}
|
|
1006
|
-
return /* @__PURE__ */ jsxs(Overlay, { open, onClose: () => onOpenChange(false), children: [
|
|
1007
|
-
/* @__PURE__ */ jsx("h2", { className: "text-base font-semibold", children: title ?? labels.newFolder }),
|
|
1008
|
-
/* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: description ?? labels.folderNameHint }),
|
|
1009
|
-
/* @__PURE__ */ jsx(
|
|
1010
|
-
"input",
|
|
1011
|
-
{
|
|
1012
|
-
ref: inputRef,
|
|
1013
|
-
value: name,
|
|
1014
|
-
onChange: (event) => setName(event.target.value),
|
|
1015
|
-
onKeyDown: (event) => {
|
|
1016
|
-
if (event.key === "Enter") {
|
|
1017
|
-
event.preventDefault();
|
|
1018
|
-
void submit();
|
|
1019
|
-
}
|
|
1020
|
-
},
|
|
1021
|
-
placeholder: labels.folderName,
|
|
1022
|
-
"aria-invalid": Boolean(error),
|
|
1023
|
-
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"
|
|
1024
|
-
}
|
|
1025
|
-
),
|
|
1026
|
-
error ? /* @__PURE__ */ jsx("p", { className: "mt-2 text-sm text-destructive", children: error }) : null,
|
|
1027
|
-
/* @__PURE__ */ jsxs("div", { className: "mt-4 flex justify-end gap-2", children: [
|
|
1028
|
-
/* @__PURE__ */ jsx(
|
|
1029
|
-
"button",
|
|
1030
|
-
{
|
|
1031
|
-
type: "button",
|
|
1032
|
-
className: "inline-flex h-8 items-center rounded-[10px] border border-border px-3 text-sm",
|
|
1033
|
-
onClick: () => onOpenChange(false),
|
|
1034
|
-
children: labels.cancel
|
|
1035
|
-
}
|
|
1036
|
-
),
|
|
1037
|
-
/* @__PURE__ */ jsx(
|
|
1038
|
-
"button",
|
|
1039
|
-
{
|
|
1040
|
-
type: "button",
|
|
1041
|
-
disabled: !name.trim() || saving,
|
|
1042
|
-
className: "inline-flex h-8 items-center rounded-[10px] bg-primary px-3 text-sm text-primary-foreground disabled:opacity-50",
|
|
1043
|
-
onClick: () => void submit(),
|
|
1044
|
-
children: confirmLabel ?? labels.createFolder
|
|
1045
|
-
}
|
|
1046
|
-
)
|
|
1047
|
-
] })
|
|
1048
|
-
] });
|
|
1049
|
-
}
|
|
1050
1070
|
function ConfirmDeleteDialog({
|
|
1051
1071
|
open,
|
|
1052
1072
|
names,
|
|
@@ -1512,6 +1532,32 @@ function UploadQueueProvider({
|
|
|
1512
1532
|
}
|
|
1513
1533
|
var PAGE_SIZE = 50;
|
|
1514
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
|
+
}
|
|
1515
1561
|
function reviveFile(node) {
|
|
1516
1562
|
const asDate = (value) => value instanceof Date ? value : new Date(value);
|
|
1517
1563
|
return {
|
|
@@ -1635,7 +1681,7 @@ function FileExplorerInner(props) {
|
|
|
1635
1681
|
const [prefsReady, setPrefsReady] = useState(!persistViewKey);
|
|
1636
1682
|
const skipPrefsWrite = useRef(!persistViewKey);
|
|
1637
1683
|
const [menu, setMenu] = useState(null);
|
|
1638
|
-
const [
|
|
1684
|
+
const [draftFolderName, setDraftFolderName] = useState(null);
|
|
1639
1685
|
const [renameTarget, setRenameTarget] = useState(null);
|
|
1640
1686
|
const [deleteTargets, setDeleteTargets] = useState([]);
|
|
1641
1687
|
const [purgeTargets, setPurgeTargets] = useState([]);
|
|
@@ -1817,6 +1863,7 @@ function FileExplorerInner(props) {
|
|
|
1817
1863
|
}, [refreshKey]);
|
|
1818
1864
|
const handleActivate = useCallback(
|
|
1819
1865
|
(file) => {
|
|
1866
|
+
if (file.id === DRAFT_FOLDER_ID) return;
|
|
1820
1867
|
if (file.kind === "folder") {
|
|
1821
1868
|
onOpenFolder?.(file);
|
|
1822
1869
|
return;
|
|
@@ -1828,6 +1875,7 @@ function FileExplorerInner(props) {
|
|
|
1828
1875
|
[onActivate, onDownload, onOpenFolder, onPreview]
|
|
1829
1876
|
);
|
|
1830
1877
|
const handleContextMenu = useCallback((file, event) => {
|
|
1878
|
+
if (file.id === DRAFT_FOLDER_ID) return;
|
|
1831
1879
|
setMenu({ kind: "item", file, x: event.clientX, y: event.clientY });
|
|
1832
1880
|
}, []);
|
|
1833
1881
|
const clearSelection = useCallback(() => {
|
|
@@ -1839,6 +1887,7 @@ function FileExplorerInner(props) {
|
|
|
1839
1887
|
}, [clearSelection, parentId, trashOpen]);
|
|
1840
1888
|
const handleSelect = useCallback(
|
|
1841
1889
|
(file, event) => {
|
|
1890
|
+
if (file.id === DRAFT_FOLDER_ID) return;
|
|
1842
1891
|
const id = file.id;
|
|
1843
1892
|
if (event.shiftKey && anchorId.current) {
|
|
1844
1893
|
const from = sortedFiles.findIndex((item) => item.id === anchorId.current);
|
|
@@ -1952,7 +2001,7 @@ function FileExplorerInner(props) {
|
|
|
1952
2001
|
if ((event.key === "Delete" || event.key === "Backspace") && selectedFiles.length > 0) {
|
|
1953
2002
|
event.preventDefault();
|
|
1954
2003
|
if (trashOpen) {
|
|
1955
|
-
setPurgeTargets(selectedFiles);
|
|
2004
|
+
if (actions.purgeNode) setPurgeTargets(selectedFiles);
|
|
1956
2005
|
return;
|
|
1957
2006
|
}
|
|
1958
2007
|
if (selectedFiles.some((file) => protectedSet.has(file.id))) return;
|
|
@@ -1977,6 +2026,37 @@ function FileExplorerInner(props) {
|
|
|
1977
2026
|
trashOpen
|
|
1978
2027
|
]
|
|
1979
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;
|
|
1980
2060
|
const openUpload = () => uploadInputRef.current?.click();
|
|
1981
2061
|
return /* @__PURE__ */ jsxs(
|
|
1982
2062
|
"div",
|
|
@@ -2025,7 +2105,7 @@ function FileExplorerInner(props) {
|
|
|
2025
2105
|
view: explorer.view,
|
|
2026
2106
|
onViewChange: explorer.setView,
|
|
2027
2107
|
onUpload: requestUpload && !trashOpen ? openUpload : void 0,
|
|
2028
|
-
onNewFolder: actions.createFolder && !trashOpen ?
|
|
2108
|
+
onNewFolder: actions.createFolder && !trashOpen ? startCreateFolder : void 0,
|
|
2029
2109
|
query,
|
|
2030
2110
|
onQueryChange: searchFiles ? setQuery : void 0,
|
|
2031
2111
|
trashOpen,
|
|
@@ -2040,7 +2120,7 @@ function FileExplorerInner(props) {
|
|
|
2040
2120
|
}
|
|
2041
2121
|
)
|
|
2042
2122
|
] }),
|
|
2043
|
-
/* @__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: [
|
|
2044
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 }),
|
|
2045
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
|
|
2046
2126
|
] }) : explorer.view === "list" ? /* @__PURE__ */ jsx(
|
|
@@ -2053,7 +2133,7 @@ function FileExplorerInner(props) {
|
|
|
2053
2133
|
children: /* @__PURE__ */ jsx(
|
|
2054
2134
|
ExplorerListView,
|
|
2055
2135
|
{
|
|
2056
|
-
files:
|
|
2136
|
+
files: displayFiles,
|
|
2057
2137
|
protectedIds: protectedSet,
|
|
2058
2138
|
selectedIds,
|
|
2059
2139
|
onSelect: handleSelect,
|
|
@@ -2068,7 +2148,10 @@ function FileExplorerInner(props) {
|
|
|
2068
2148
|
onDragOverRow: explorer.setDropTarget,
|
|
2069
2149
|
sortKey,
|
|
2070
2150
|
sortDirection: sortDir,
|
|
2071
|
-
onSortChange: handleSortChange
|
|
2151
|
+
onSortChange: handleSortChange,
|
|
2152
|
+
renamingId,
|
|
2153
|
+
onRenameCommit: commitRename,
|
|
2154
|
+
onRenameCancel: cancelRename
|
|
2072
2155
|
}
|
|
2073
2156
|
)
|
|
2074
2157
|
}
|
|
@@ -2082,7 +2165,7 @@ function FileExplorerInner(props) {
|
|
|
2082
2165
|
children: /* @__PURE__ */ jsx(
|
|
2083
2166
|
ExplorerGridView,
|
|
2084
2167
|
{
|
|
2085
|
-
files:
|
|
2168
|
+
files: displayFiles,
|
|
2086
2169
|
protectedIds: protectedSet,
|
|
2087
2170
|
selectedIds,
|
|
2088
2171
|
onSelect: handleSelect,
|
|
@@ -2094,7 +2177,10 @@ function FileExplorerInner(props) {
|
|
|
2094
2177
|
draggingId: explorer.draggingId,
|
|
2095
2178
|
draggingIds,
|
|
2096
2179
|
dropTargetId: explorer.dropTargetId,
|
|
2097
|
-
onDragOverRow: explorer.setDropTarget
|
|
2180
|
+
onDragOverRow: explorer.setDropTarget,
|
|
2181
|
+
renamingId,
|
|
2182
|
+
onRenameCommit: commitRename,
|
|
2183
|
+
onRenameCancel: cancelRename
|
|
2098
2184
|
}
|
|
2099
2185
|
)
|
|
2100
2186
|
}
|
|
@@ -2155,10 +2241,10 @@ function FileExplorerInner(props) {
|
|
|
2155
2241
|
onDownload,
|
|
2156
2242
|
onRename: setRenameTarget,
|
|
2157
2243
|
onRequestDelete: (file) => setDeleteTargets([file]),
|
|
2158
|
-
onRequestPurge: (file) => setPurgeTargets([file]),
|
|
2244
|
+
onRequestPurge: actions.purgeNode ? (file) => setPurgeTargets([file]) : void 0,
|
|
2159
2245
|
onCopy: (file) => copyToClipboard([file]),
|
|
2160
2246
|
onPaste: clipboard.length > 0 && !trashOpen ? (folder) => void pasteClipboard(folder ? folder.id : parentId) : void 0,
|
|
2161
|
-
onNewFolder: actions.createFolder ?
|
|
2247
|
+
onNewFolder: actions.createFolder ? startCreateFolder : void 0,
|
|
2162
2248
|
onUpload: requestUpload ? openUpload : void 0
|
|
2163
2249
|
}
|
|
2164
2250
|
),
|
|
@@ -2176,7 +2262,11 @@ function FileExplorerInner(props) {
|
|
|
2176
2262
|
if (file.kind !== "file") continue;
|
|
2177
2263
|
tokens.push(await actions.createShare({ id: file.id }));
|
|
2178
2264
|
}
|
|
2179
|
-
if (tokens.length > 0)
|
|
2265
|
+
if (tokens.length > 0) {
|
|
2266
|
+
await navigator.clipboard.writeText(
|
|
2267
|
+
tokens.map(absoluteShareUrl).join("\n")
|
|
2268
|
+
);
|
|
2269
|
+
}
|
|
2180
2270
|
} : void 0,
|
|
2181
2271
|
onDelete: () => setDeleteTargets(selectedFiles),
|
|
2182
2272
|
onRestore: actions.restoreNode ? async () => {
|
|
@@ -2184,7 +2274,7 @@ function FileExplorerInner(props) {
|
|
|
2184
2274
|
clearSelection();
|
|
2185
2275
|
await reload();
|
|
2186
2276
|
} : void 0,
|
|
2187
|
-
onPurge: () => setPurgeTargets(selectedFiles),
|
|
2277
|
+
onPurge: actions.purgeNode ? () => setPurgeTargets(selectedFiles) : void 0,
|
|
2188
2278
|
onClear: clearSelection
|
|
2189
2279
|
}
|
|
2190
2280
|
),
|
|
@@ -2196,36 +2286,6 @@ function FileExplorerInner(props) {
|
|
|
2196
2286
|
onClear: () => setClipboard([])
|
|
2197
2287
|
}
|
|
2198
2288
|
) }),
|
|
2199
|
-
/* @__PURE__ */ jsx(
|
|
2200
|
-
CreateFolderDialog,
|
|
2201
|
-
{
|
|
2202
|
-
open: folderOpen,
|
|
2203
|
-
onOpenChange: setFolderOpen,
|
|
2204
|
-
onCreate: async (name) => {
|
|
2205
|
-
await actions.createFolder?.({ name, parentId });
|
|
2206
|
-
await reload();
|
|
2207
|
-
}
|
|
2208
|
-
}
|
|
2209
|
-
),
|
|
2210
|
-
/* @__PURE__ */ jsx(
|
|
2211
|
-
CreateFolderDialog,
|
|
2212
|
-
{
|
|
2213
|
-
open: Boolean(renameTarget),
|
|
2214
|
-
onOpenChange: (open) => {
|
|
2215
|
-
if (!open) setRenameTarget(null);
|
|
2216
|
-
},
|
|
2217
|
-
title: labels.rename,
|
|
2218
|
-
description: labels.renameHint,
|
|
2219
|
-
confirmLabel: labels.rename,
|
|
2220
|
-
initialName: renameTarget?.name ?? "",
|
|
2221
|
-
onCreate: async (name) => {
|
|
2222
|
-
if (!renameTarget) return;
|
|
2223
|
-
await actions.renameFile(renameTarget.id, name);
|
|
2224
|
-
setRenameTarget(null);
|
|
2225
|
-
await reload();
|
|
2226
|
-
}
|
|
2227
|
-
}
|
|
2228
|
-
),
|
|
2229
2289
|
/* @__PURE__ */ jsx(
|
|
2230
2290
|
ConfirmDeleteDialog,
|
|
2231
2291
|
{
|
|
@@ -2380,6 +2440,92 @@ function FilePreviewDialog({
|
|
|
2380
2440
|
) })
|
|
2381
2441
|
] });
|
|
2382
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
|
+
}
|
|
2383
2529
|
|
|
2384
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 };
|
|
2385
2531
|
//# sourceMappingURL=index.js.map
|