@vryzel/file-next-ui 0.6.0 → 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/dist/index.cjs +155 -56
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +155 -56
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -2773,6 +2773,15 @@ function clampPreviewZoom(scale) {
|
|
|
2773
2773
|
function canPreviewFile(file) {
|
|
2774
2774
|
return previewKind(file.mimeType, file.name) !== "none";
|
|
2775
2775
|
}
|
|
2776
|
+
function pdfFrameMode(src, pageOrigin) {
|
|
2777
|
+
try {
|
|
2778
|
+
const url = new URL(src, pageOrigin);
|
|
2779
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") return "blob";
|
|
2780
|
+
return url.origin === new URL(pageOrigin).origin ? "blob" : "direct";
|
|
2781
|
+
} catch {
|
|
2782
|
+
return "blob";
|
|
2783
|
+
}
|
|
2784
|
+
}
|
|
2776
2785
|
function previewKind(mimeType, name) {
|
|
2777
2786
|
const mime = mimeType.toLowerCase();
|
|
2778
2787
|
const ext = name.split(".").pop()?.toLowerCase() ?? "";
|
|
@@ -2788,6 +2797,74 @@ function previewKind(mimeType, name) {
|
|
|
2788
2797
|
if (mime.startsWith("text/") || ["txt", "csv", "md"].includes(ext)) return "text";
|
|
2789
2798
|
return "none";
|
|
2790
2799
|
}
|
|
2800
|
+
function PreviewSpinner({ label }) {
|
|
2801
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { role: "status", "aria-label": label, className: "absolute inset-0 z-10 flex items-center justify-center", children: [
|
|
2802
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "sr-only", children: label }),
|
|
2803
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2804
|
+
"span",
|
|
2805
|
+
{
|
|
2806
|
+
"aria-hidden": "true",
|
|
2807
|
+
className: "size-8 rounded-full border-2 border-white/25 border-t-white motion-safe:animate-spin"
|
|
2808
|
+
}
|
|
2809
|
+
)
|
|
2810
|
+
] });
|
|
2811
|
+
}
|
|
2812
|
+
function PdfFrame({
|
|
2813
|
+
src,
|
|
2814
|
+
title,
|
|
2815
|
+
onClick
|
|
2816
|
+
}) {
|
|
2817
|
+
const labels = useExplorerLabels();
|
|
2818
|
+
const [frameSrc, setFrameSrc] = react.useState(null);
|
|
2819
|
+
const [failed, setFailed] = react.useState(false);
|
|
2820
|
+
const [painted, setPainted] = react.useState(false);
|
|
2821
|
+
react.useEffect(() => {
|
|
2822
|
+
setPainted(false);
|
|
2823
|
+
if (pdfFrameMode(src, window.location.origin) === "direct") {
|
|
2824
|
+
setFrameSrc(src);
|
|
2825
|
+
setFailed(false);
|
|
2826
|
+
return;
|
|
2827
|
+
}
|
|
2828
|
+
const controller = new AbortController();
|
|
2829
|
+
let cancelled = false;
|
|
2830
|
+
let objectUrl = null;
|
|
2831
|
+
setFrameSrc(null);
|
|
2832
|
+
setFailed(false);
|
|
2833
|
+
void fetch(src, { credentials: "same-origin", signal: controller.signal }).then(async (response) => {
|
|
2834
|
+
if (!response.ok) throw new Error(String(response.status));
|
|
2835
|
+
const blob = new Blob([await response.arrayBuffer()], { type: "application/pdf" });
|
|
2836
|
+
if (cancelled) return;
|
|
2837
|
+
objectUrl = URL.createObjectURL(blob);
|
|
2838
|
+
setFrameSrc(objectUrl);
|
|
2839
|
+
}).catch((error) => {
|
|
2840
|
+
if (cancelled || error instanceof DOMException && error.name === "AbortError") return;
|
|
2841
|
+
setFailed(true);
|
|
2842
|
+
});
|
|
2843
|
+
return () => {
|
|
2844
|
+
cancelled = true;
|
|
2845
|
+
controller.abort();
|
|
2846
|
+
if (objectUrl) URL.revokeObjectURL(objectUrl);
|
|
2847
|
+
};
|
|
2848
|
+
}, [src]);
|
|
2849
|
+
if (failed) {
|
|
2850
|
+
return /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-white/70", children: labels.previewUnavailable });
|
|
2851
|
+
}
|
|
2852
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative h-full w-full max-w-5xl", children: [
|
|
2853
|
+
!painted ? /* @__PURE__ */ jsxRuntime.jsx(PreviewSpinner, { label: labels.loading }) : null,
|
|
2854
|
+
frameSrc ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2855
|
+
"iframe",
|
|
2856
|
+
{
|
|
2857
|
+
src: frameSrc,
|
|
2858
|
+
title,
|
|
2859
|
+
referrerPolicy: "no-referrer",
|
|
2860
|
+
className: painted ? "h-full w-full bg-white" : "absolute h-px w-px opacity-0",
|
|
2861
|
+
onClick,
|
|
2862
|
+
onLoad: () => setPainted(true),
|
|
2863
|
+
onError: () => setPainted(true)
|
|
2864
|
+
}
|
|
2865
|
+
) : null
|
|
2866
|
+
] });
|
|
2867
|
+
}
|
|
2791
2868
|
function FilePreviewDialog({
|
|
2792
2869
|
file,
|
|
2793
2870
|
src,
|
|
@@ -2797,11 +2874,13 @@ function FilePreviewDialog({
|
|
|
2797
2874
|
const labels = useExplorerLabels();
|
|
2798
2875
|
const [scale, setScale] = react.useState(1);
|
|
2799
2876
|
const [pan, setPan] = react.useState({ x: 0, y: 0 });
|
|
2877
|
+
const [ready, setReady] = react.useState(false);
|
|
2800
2878
|
const drag = react.useRef(null);
|
|
2801
2879
|
const closeRef = react.useRef(null);
|
|
2802
2880
|
react.useEffect(() => {
|
|
2803
2881
|
setScale(1);
|
|
2804
2882
|
setPan({ x: 0, y: 0 });
|
|
2883
|
+
setReady(false);
|
|
2805
2884
|
closeRef.current?.focus();
|
|
2806
2885
|
}, [file?.id, src]);
|
|
2807
2886
|
react.useEffect(() => {
|
|
@@ -2851,63 +2930,83 @@ function FilePreviewDialog({
|
|
|
2851
2930
|
}
|
|
2852
2931
|
)
|
|
2853
2932
|
] }),
|
|
2854
|
-
/* @__PURE__ */ jsxRuntime.
|
|
2855
|
-
"
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2933
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex h-full w-full items-center justify-center px-4 pt-12 pb-16", children: [
|
|
2934
|
+
kind !== "pdf" && !ready ? /* @__PURE__ */ jsxRuntime.jsx(PreviewSpinner, { label: labels.loading }) : null,
|
|
2935
|
+
kind === "image" ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2936
|
+
"img",
|
|
2937
|
+
{
|
|
2938
|
+
src,
|
|
2939
|
+
alt: file.name,
|
|
2940
|
+
draggable: false,
|
|
2941
|
+
className: `max-h-full max-w-full cursor-zoom-in select-none object-contain ${ready ? "" : "absolute h-px w-px opacity-0"}`,
|
|
2942
|
+
onLoad: () => setReady(true),
|
|
2943
|
+
onError: () => setReady(true),
|
|
2944
|
+
style: { transform: `translate(${pan.x}px, ${pan.y}px) scale(${scale})` },
|
|
2945
|
+
onClick: stop,
|
|
2946
|
+
onDoubleClick: (event) => {
|
|
2947
|
+
stop(event);
|
|
2948
|
+
zoom(scale === 1 ? 2 : 1);
|
|
2949
|
+
},
|
|
2950
|
+
onWheel: (event) => {
|
|
2951
|
+
event.preventDefault();
|
|
2952
|
+
stop(event);
|
|
2953
|
+
zoom(scale + (event.deltaY < 0 ? 0.25 : -0.25));
|
|
2954
|
+
},
|
|
2955
|
+
onPointerDown: (event) => {
|
|
2956
|
+
if (scale === 1) return;
|
|
2957
|
+
stop(event);
|
|
2958
|
+
event.currentTarget.setPointerCapture(event.pointerId);
|
|
2959
|
+
drag.current = { x: event.clientX, y: event.clientY, px: pan.x, py: pan.y };
|
|
2960
|
+
},
|
|
2961
|
+
onPointerMove: (event) => {
|
|
2962
|
+
if (!drag.current) return;
|
|
2963
|
+
setPan({
|
|
2964
|
+
x: drag.current.px + event.clientX - drag.current.x,
|
|
2965
|
+
y: drag.current.py + event.clientY - drag.current.y
|
|
2966
|
+
});
|
|
2967
|
+
},
|
|
2968
|
+
onPointerUp: () => {
|
|
2969
|
+
drag.current = null;
|
|
2970
|
+
}
|
|
2887
2971
|
}
|
|
2888
|
-
}
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
"video"
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2972
|
+
) : kind === "pdf" ? /* @__PURE__ */ jsxRuntime.jsx(PdfFrame, { src, title: file.name, onClick: stop }) : kind === "text" ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2973
|
+
"iframe",
|
|
2974
|
+
{
|
|
2975
|
+
src,
|
|
2976
|
+
title: file.name,
|
|
2977
|
+
sandbox: "",
|
|
2978
|
+
referrerPolicy: "no-referrer",
|
|
2979
|
+
className: ready ? "h-full w-full max-w-5xl bg-white" : "absolute h-px w-px max-w-5xl opacity-0",
|
|
2980
|
+
onClick: stop,
|
|
2981
|
+
onLoad: () => setReady(true),
|
|
2982
|
+
onError: () => setReady(true)
|
|
2983
|
+
}
|
|
2984
|
+
) : kind === "video" ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2985
|
+
"video",
|
|
2986
|
+
{
|
|
2987
|
+
controls: true,
|
|
2988
|
+
playsInline: true,
|
|
2989
|
+
preload: "metadata",
|
|
2990
|
+
src,
|
|
2991
|
+
className: `max-h-full max-w-full ${ready ? "" : "absolute h-px w-px opacity-0"}`,
|
|
2992
|
+
onClick: stop,
|
|
2993
|
+
onLoadedData: () => setReady(true),
|
|
2994
|
+
onError: () => setReady(true),
|
|
2995
|
+
children: labels.previewUnavailable
|
|
2996
|
+
}
|
|
2997
|
+
) : kind === "audio" ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2998
|
+
"audio",
|
|
2999
|
+
{
|
|
3000
|
+
controls: true,
|
|
3001
|
+
preload: "metadata",
|
|
3002
|
+
src,
|
|
3003
|
+
className: `w-full max-w-md ${ready ? "" : "absolute h-px w-px opacity-0"}`,
|
|
3004
|
+
onClick: stop,
|
|
3005
|
+
onLoadedMetadata: () => setReady(true),
|
|
3006
|
+
onError: () => setReady(true)
|
|
3007
|
+
}
|
|
3008
|
+
) : /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-white/70", children: labels.previewUnavailable })
|
|
3009
|
+
] }),
|
|
2911
3010
|
kind === "image" ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2912
3011
|
"div",
|
|
2913
3012
|
{
|