@vryzel/file-next-ui 0.5.3 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/index.cjs +199 -80
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +200 -81
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createContext, createElement, useContext, useRef, useState, useLayoutEffect, useEffect, useCallback, useMemo } from 'react';
|
|
2
2
|
import { useFileExplorer } from '@vryzel/file-next-headless';
|
|
3
|
-
import { Music, FileCode, FileVideo, FileAudio, FileImage, Archive, FileText, ChevronRight, File, Folder, Link2, MoreHorizontal, ChevronUp, ChevronDown, List, LayoutGrid, Search, Upload, FolderPlus, Trash, Copy, RotateCcw, X, Eye, Download, Pencil, ListChecks, Inbox, AlertCircleIcon, Minus, Plus } from 'lucide-react';
|
|
3
|
+
import { Music, FileCode, FileVideo, FileAudio, FileImage, Archive, FileText, ChevronRight, File, Folder, Link2, MoreHorizontal, ChevronUp, ChevronDown, List, LayoutGrid, Search, Upload, FolderPlus, Trash, Copy, RotateCcw, X, Eye, Download, Pencil, ListChecks, ListX, Inbox, AlertCircleIcon, Minus, Plus } from 'lucide-react';
|
|
4
4
|
import { clsx } from 'clsx';
|
|
5
5
|
import { twMerge } from 'tailwind-merge';
|
|
6
6
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
@@ -1446,33 +1446,51 @@ function SelectionActions({
|
|
|
1446
1446
|
}
|
|
1447
1447
|
)
|
|
1448
1448
|
] }),
|
|
1449
|
-
/* @__PURE__ */
|
|
1450
|
-
/* @__PURE__ */ jsxs(
|
|
1451
|
-
/* @__PURE__ */
|
|
1452
|
-
|
|
1449
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-1.5", children: [
|
|
1450
|
+
trashOpen ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1451
|
+
/* @__PURE__ */ jsxs("button", { type: "button", className: btn2, onClick: onRestore, children: [
|
|
1452
|
+
/* @__PURE__ */ jsx(RotateCcw, { "aria-hidden": "true", className: "size-3.5" }),
|
|
1453
|
+
labels.restore
|
|
1454
|
+
] }),
|
|
1455
|
+
onPurge ? /* @__PURE__ */ jsxs("button", { type: "button", className: cn(btn2, "text-destructive"), onClick: onPurge, children: [
|
|
1456
|
+
/* @__PURE__ */ jsx(X, { "aria-hidden": "true", className: "size-3.5" }),
|
|
1457
|
+
labels.deleteForever
|
|
1458
|
+
] }) : null
|
|
1459
|
+
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1460
|
+
onDownload && downloadable.length > 0 ? /* @__PURE__ */ jsxs("button", { type: "button", className: btn2, onClick: () => downloadable.forEach(onDownload), children: [
|
|
1461
|
+
/* @__PURE__ */ jsx(Download, { "aria-hidden": "true", className: "size-3.5" }),
|
|
1462
|
+
labels.download
|
|
1463
|
+
] }) : null,
|
|
1464
|
+
onCopy ? /* @__PURE__ */ jsxs("button", { type: "button", className: btn2, onClick: onCopy, children: [
|
|
1465
|
+
/* @__PURE__ */ jsx(Copy, { "aria-hidden": "true", className: "size-3.5" }),
|
|
1466
|
+
labels.copy
|
|
1467
|
+
] }) : null,
|
|
1468
|
+
onShare && downloadable.length > 0 ? /* @__PURE__ */ jsxs("button", { type: "button", className: btn2, onClick: onShare, children: [
|
|
1469
|
+
/* @__PURE__ */ jsx(Link2, { "aria-hidden": "true", className: "size-3.5" }),
|
|
1470
|
+
labels.share
|
|
1471
|
+
] }) : null,
|
|
1472
|
+
/* @__PURE__ */ jsxs("button", { type: "button", disabled: hasProtected, className: cn(btn2, "text-destructive"), onClick: onDelete, children: [
|
|
1473
|
+
/* @__PURE__ */ jsx(X, { "aria-hidden": "true", className: "size-3.5" }),
|
|
1474
|
+
labels.moveToTrash
|
|
1475
|
+
] })
|
|
1453
1476
|
] }),
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
/* @__PURE__ */ jsxs("button", { type: "button", disabled: hasProtected, className: cn(btn2, "text-destructive"), onClick: onDelete, children: [
|
|
1472
|
-
/* @__PURE__ */ jsx(X, { "aria-hidden": "true", className: "size-3.5" }),
|
|
1473
|
-
labels.moveToTrash
|
|
1474
|
-
] })
|
|
1475
|
-
] }) })
|
|
1477
|
+
/* @__PURE__ */ jsxs(
|
|
1478
|
+
"button",
|
|
1479
|
+
{
|
|
1480
|
+
type: "button",
|
|
1481
|
+
className: btn2,
|
|
1482
|
+
onClick: (event) => {
|
|
1483
|
+
event.stopPropagation();
|
|
1484
|
+
swallowClickThrough();
|
|
1485
|
+
onClear();
|
|
1486
|
+
},
|
|
1487
|
+
children: [
|
|
1488
|
+
/* @__PURE__ */ jsx(ListX, { "aria-hidden": "true", className: "size-3.5" }),
|
|
1489
|
+
labels.clearSelection
|
|
1490
|
+
]
|
|
1491
|
+
}
|
|
1492
|
+
)
|
|
1493
|
+
] })
|
|
1476
1494
|
] });
|
|
1477
1495
|
}
|
|
1478
1496
|
function ExplorerClipboardToast({
|
|
@@ -2753,6 +2771,15 @@ function clampPreviewZoom(scale) {
|
|
|
2753
2771
|
function canPreviewFile(file) {
|
|
2754
2772
|
return previewKind(file.mimeType, file.name) !== "none";
|
|
2755
2773
|
}
|
|
2774
|
+
function pdfFrameMode(src, pageOrigin) {
|
|
2775
|
+
try {
|
|
2776
|
+
const url = new URL(src, pageOrigin);
|
|
2777
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") return "blob";
|
|
2778
|
+
return url.origin === new URL(pageOrigin).origin ? "blob" : "direct";
|
|
2779
|
+
} catch {
|
|
2780
|
+
return "blob";
|
|
2781
|
+
}
|
|
2782
|
+
}
|
|
2756
2783
|
function previewKind(mimeType, name) {
|
|
2757
2784
|
const mime = mimeType.toLowerCase();
|
|
2758
2785
|
const ext = name.split(".").pop()?.toLowerCase() ?? "";
|
|
@@ -2768,6 +2795,74 @@ function previewKind(mimeType, name) {
|
|
|
2768
2795
|
if (mime.startsWith("text/") || ["txt", "csv", "md"].includes(ext)) return "text";
|
|
2769
2796
|
return "none";
|
|
2770
2797
|
}
|
|
2798
|
+
function PreviewSpinner({ label }) {
|
|
2799
|
+
return /* @__PURE__ */ jsxs("div", { role: "status", "aria-label": label, className: "absolute inset-0 z-10 flex items-center justify-center", children: [
|
|
2800
|
+
/* @__PURE__ */ jsx("span", { className: "sr-only", children: label }),
|
|
2801
|
+
/* @__PURE__ */ jsx(
|
|
2802
|
+
"span",
|
|
2803
|
+
{
|
|
2804
|
+
"aria-hidden": "true",
|
|
2805
|
+
className: "size-8 rounded-full border-2 border-white/25 border-t-white motion-safe:animate-spin"
|
|
2806
|
+
}
|
|
2807
|
+
)
|
|
2808
|
+
] });
|
|
2809
|
+
}
|
|
2810
|
+
function PdfFrame({
|
|
2811
|
+
src,
|
|
2812
|
+
title,
|
|
2813
|
+
onClick
|
|
2814
|
+
}) {
|
|
2815
|
+
const labels = useExplorerLabels();
|
|
2816
|
+
const [frameSrc, setFrameSrc] = useState(null);
|
|
2817
|
+
const [failed, setFailed] = useState(false);
|
|
2818
|
+
const [painted, setPainted] = useState(false);
|
|
2819
|
+
useEffect(() => {
|
|
2820
|
+
setPainted(false);
|
|
2821
|
+
if (pdfFrameMode(src, window.location.origin) === "direct") {
|
|
2822
|
+
setFrameSrc(src);
|
|
2823
|
+
setFailed(false);
|
|
2824
|
+
return;
|
|
2825
|
+
}
|
|
2826
|
+
const controller = new AbortController();
|
|
2827
|
+
let cancelled = false;
|
|
2828
|
+
let objectUrl = null;
|
|
2829
|
+
setFrameSrc(null);
|
|
2830
|
+
setFailed(false);
|
|
2831
|
+
void fetch(src, { credentials: "same-origin", signal: controller.signal }).then(async (response) => {
|
|
2832
|
+
if (!response.ok) throw new Error(String(response.status));
|
|
2833
|
+
const blob = new Blob([await response.arrayBuffer()], { type: "application/pdf" });
|
|
2834
|
+
if (cancelled) return;
|
|
2835
|
+
objectUrl = URL.createObjectURL(blob);
|
|
2836
|
+
setFrameSrc(objectUrl);
|
|
2837
|
+
}).catch((error) => {
|
|
2838
|
+
if (cancelled || error instanceof DOMException && error.name === "AbortError") return;
|
|
2839
|
+
setFailed(true);
|
|
2840
|
+
});
|
|
2841
|
+
return () => {
|
|
2842
|
+
cancelled = true;
|
|
2843
|
+
controller.abort();
|
|
2844
|
+
if (objectUrl) URL.revokeObjectURL(objectUrl);
|
|
2845
|
+
};
|
|
2846
|
+
}, [src]);
|
|
2847
|
+
if (failed) {
|
|
2848
|
+
return /* @__PURE__ */ jsx("p", { className: "text-sm text-white/70", children: labels.previewUnavailable });
|
|
2849
|
+
}
|
|
2850
|
+
return /* @__PURE__ */ jsxs("div", { className: "relative h-full w-full max-w-5xl", children: [
|
|
2851
|
+
!painted ? /* @__PURE__ */ jsx(PreviewSpinner, { label: labels.loading }) : null,
|
|
2852
|
+
frameSrc ? /* @__PURE__ */ jsx(
|
|
2853
|
+
"iframe",
|
|
2854
|
+
{
|
|
2855
|
+
src: frameSrc,
|
|
2856
|
+
title,
|
|
2857
|
+
referrerPolicy: "no-referrer",
|
|
2858
|
+
className: painted ? "h-full w-full bg-white" : "absolute h-px w-px opacity-0",
|
|
2859
|
+
onClick,
|
|
2860
|
+
onLoad: () => setPainted(true),
|
|
2861
|
+
onError: () => setPainted(true)
|
|
2862
|
+
}
|
|
2863
|
+
) : null
|
|
2864
|
+
] });
|
|
2865
|
+
}
|
|
2771
2866
|
function FilePreviewDialog({
|
|
2772
2867
|
file,
|
|
2773
2868
|
src,
|
|
@@ -2777,11 +2872,13 @@ function FilePreviewDialog({
|
|
|
2777
2872
|
const labels = useExplorerLabels();
|
|
2778
2873
|
const [scale, setScale] = useState(1);
|
|
2779
2874
|
const [pan, setPan] = useState({ x: 0, y: 0 });
|
|
2875
|
+
const [ready, setReady] = useState(false);
|
|
2780
2876
|
const drag = useRef(null);
|
|
2781
2877
|
const closeRef = useRef(null);
|
|
2782
2878
|
useEffect(() => {
|
|
2783
2879
|
setScale(1);
|
|
2784
2880
|
setPan({ x: 0, y: 0 });
|
|
2881
|
+
setReady(false);
|
|
2785
2882
|
closeRef.current?.focus();
|
|
2786
2883
|
}, [file?.id, src]);
|
|
2787
2884
|
useEffect(() => {
|
|
@@ -2831,61 +2928,83 @@ function FilePreviewDialog({
|
|
|
2831
2928
|
}
|
|
2832
2929
|
)
|
|
2833
2930
|
] }),
|
|
2834
|
-
/* @__PURE__ */
|
|
2835
|
-
"
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2931
|
+
/* @__PURE__ */ jsxs("div", { className: "relative flex h-full w-full items-center justify-center px-4 pt-12 pb-16", children: [
|
|
2932
|
+
kind !== "pdf" && !ready ? /* @__PURE__ */ jsx(PreviewSpinner, { label: labels.loading }) : null,
|
|
2933
|
+
kind === "image" ? /* @__PURE__ */ jsx(
|
|
2934
|
+
"img",
|
|
2935
|
+
{
|
|
2936
|
+
src,
|
|
2937
|
+
alt: file.name,
|
|
2938
|
+
draggable: false,
|
|
2939
|
+
className: `max-h-full max-w-full cursor-zoom-in select-none object-contain ${ready ? "" : "absolute h-px w-px opacity-0"}`,
|
|
2940
|
+
onLoad: () => setReady(true),
|
|
2941
|
+
onError: () => setReady(true),
|
|
2942
|
+
style: { transform: `translate(${pan.x}px, ${pan.y}px) scale(${scale})` },
|
|
2943
|
+
onClick: stop,
|
|
2944
|
+
onDoubleClick: (event) => {
|
|
2945
|
+
stop(event);
|
|
2946
|
+
zoom(scale === 1 ? 2 : 1);
|
|
2947
|
+
},
|
|
2948
|
+
onWheel: (event) => {
|
|
2949
|
+
event.preventDefault();
|
|
2950
|
+
stop(event);
|
|
2951
|
+
zoom(scale + (event.deltaY < 0 ? 0.25 : -0.25));
|
|
2952
|
+
},
|
|
2953
|
+
onPointerDown: (event) => {
|
|
2954
|
+
if (scale === 1) return;
|
|
2955
|
+
stop(event);
|
|
2956
|
+
event.currentTarget.setPointerCapture(event.pointerId);
|
|
2957
|
+
drag.current = { x: event.clientX, y: event.clientY, px: pan.x, py: pan.y };
|
|
2958
|
+
},
|
|
2959
|
+
onPointerMove: (event) => {
|
|
2960
|
+
if (!drag.current) return;
|
|
2961
|
+
setPan({
|
|
2962
|
+
x: drag.current.px + event.clientX - drag.current.x,
|
|
2963
|
+
y: drag.current.py + event.clientY - drag.current.y
|
|
2964
|
+
});
|
|
2965
|
+
},
|
|
2966
|
+
onPointerUp: () => {
|
|
2967
|
+
drag.current = null;
|
|
2968
|
+
}
|
|
2867
2969
|
}
|
|
2868
|
-
}
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2970
|
+
) : kind === "pdf" ? /* @__PURE__ */ jsx(PdfFrame, { src, title: file.name, onClick: stop }) : kind === "text" ? /* @__PURE__ */ jsx(
|
|
2971
|
+
"iframe",
|
|
2972
|
+
{
|
|
2973
|
+
src,
|
|
2974
|
+
title: file.name,
|
|
2975
|
+
sandbox: "",
|
|
2976
|
+
referrerPolicy: "no-referrer",
|
|
2977
|
+
className: ready ? "h-full w-full max-w-5xl bg-white" : "absolute h-px w-px max-w-5xl opacity-0",
|
|
2978
|
+
onClick: stop,
|
|
2979
|
+
onLoad: () => setReady(true),
|
|
2980
|
+
onError: () => setReady(true)
|
|
2981
|
+
}
|
|
2982
|
+
) : kind === "video" ? /* @__PURE__ */ jsx(
|
|
2983
|
+
"video",
|
|
2984
|
+
{
|
|
2985
|
+
controls: true,
|
|
2986
|
+
playsInline: true,
|
|
2987
|
+
preload: "metadata",
|
|
2988
|
+
src,
|
|
2989
|
+
className: `max-h-full max-w-full ${ready ? "" : "absolute h-px w-px opacity-0"}`,
|
|
2990
|
+
onClick: stop,
|
|
2991
|
+
onLoadedData: () => setReady(true),
|
|
2992
|
+
onError: () => setReady(true),
|
|
2993
|
+
children: labels.previewUnavailable
|
|
2994
|
+
}
|
|
2995
|
+
) : kind === "audio" ? /* @__PURE__ */ jsx(
|
|
2996
|
+
"audio",
|
|
2997
|
+
{
|
|
2998
|
+
controls: true,
|
|
2999
|
+
preload: "metadata",
|
|
3000
|
+
src,
|
|
3001
|
+
className: `w-full max-w-md ${ready ? "" : "absolute h-px w-px opacity-0"}`,
|
|
3002
|
+
onClick: stop,
|
|
3003
|
+
onLoadedMetadata: () => setReady(true),
|
|
3004
|
+
onError: () => setReady(true)
|
|
3005
|
+
}
|
|
3006
|
+
) : /* @__PURE__ */ jsx("p", { className: "text-sm text-white/70", children: labels.previewUnavailable })
|
|
3007
|
+
] }),
|
|
2889
3008
|
kind === "image" ? /* @__PURE__ */ jsxs(
|
|
2890
3009
|
"div",
|
|
2891
3010
|
{
|