@ibanzajoe/uploader 0.1.0 → 0.2.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.
- package/README.md +32 -0
- package/dist/index.cjs +709 -289
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +90 -8
- package/dist/index.d.ts +90 -8
- package/dist/index.js +708 -291
- package/dist/index.js.map +1 -1
- package/dist/styles.css +561 -129
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -12,10 +12,10 @@ import {
|
|
|
12
12
|
|
|
13
13
|
// src/react/PickerOverlay.tsx
|
|
14
14
|
import {
|
|
15
|
-
useRef as
|
|
16
|
-
useState as
|
|
17
|
-
useCallback as
|
|
18
|
-
useEffect as
|
|
15
|
+
useRef as useRef5,
|
|
16
|
+
useState as useState4,
|
|
17
|
+
useCallback as useCallback5,
|
|
18
|
+
useEffect as useEffect3,
|
|
19
19
|
useId
|
|
20
20
|
} from "react";
|
|
21
21
|
|
|
@@ -640,8 +640,458 @@ function ImageEditor({
|
|
|
640
640
|
);
|
|
641
641
|
}
|
|
642
642
|
|
|
643
|
+
// src/react/Dropzone.tsx
|
|
644
|
+
import { useRef as useRef3, useCallback as useCallback3 } from "react";
|
|
645
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
646
|
+
function Dropzone({
|
|
647
|
+
onFiles,
|
|
648
|
+
isDragOver,
|
|
649
|
+
onDragEnter,
|
|
650
|
+
onDragLeave,
|
|
651
|
+
onDragOver,
|
|
652
|
+
onDrop,
|
|
653
|
+
accept,
|
|
654
|
+
multiple = true,
|
|
655
|
+
onCamera,
|
|
656
|
+
hint,
|
|
657
|
+
className,
|
|
658
|
+
style
|
|
659
|
+
}) {
|
|
660
|
+
const inputRef = useRef3(null);
|
|
661
|
+
const openFileDialog = useCallback3(() => inputRef.current?.click(), []);
|
|
662
|
+
const handleInputChange = useCallback3(
|
|
663
|
+
(e) => {
|
|
664
|
+
if (e.target.files && e.target.files.length > 0) {
|
|
665
|
+
onFiles(e.target.files);
|
|
666
|
+
e.target.value = "";
|
|
667
|
+
}
|
|
668
|
+
},
|
|
669
|
+
[onFiles]
|
|
670
|
+
);
|
|
671
|
+
return /* @__PURE__ */ jsxs2(
|
|
672
|
+
"div",
|
|
673
|
+
{
|
|
674
|
+
className: ["uploader-dropzone", className ?? ""].filter(Boolean).join(" "),
|
|
675
|
+
"data-drag-over": isDragOver ? "true" : void 0,
|
|
676
|
+
role: "button",
|
|
677
|
+
tabIndex: 0,
|
|
678
|
+
"aria-label": "Drop files here or click to browse",
|
|
679
|
+
style,
|
|
680
|
+
onDragEnter,
|
|
681
|
+
onDragLeave,
|
|
682
|
+
onDragOver,
|
|
683
|
+
onDrop,
|
|
684
|
+
onClick: openFileDialog,
|
|
685
|
+
onKeyDown: (e) => {
|
|
686
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
687
|
+
e.preventDefault();
|
|
688
|
+
openFileDialog();
|
|
689
|
+
}
|
|
690
|
+
},
|
|
691
|
+
children: [
|
|
692
|
+
/* @__PURE__ */ jsx2("div", { className: "uploader-dropzone-icon-wrap", "aria-hidden": "true", children: /* @__PURE__ */ jsx2("div", { className: "uploader-dropzone-icon" }) }),
|
|
693
|
+
/* @__PURE__ */ jsx2("p", { className: "uploader-dropzone-label", children: "Drag & drop files here" }),
|
|
694
|
+
/* @__PURE__ */ jsx2("p", { className: "uploader-dropzone-sublabel", children: "or" }),
|
|
695
|
+
/* @__PURE__ */ jsxs2("div", { className: "uploader-dropzone-actions", children: [
|
|
696
|
+
/* @__PURE__ */ jsx2(
|
|
697
|
+
"button",
|
|
698
|
+
{
|
|
699
|
+
type: "button",
|
|
700
|
+
className: "uploader-btn-browse",
|
|
701
|
+
onClick: (e) => {
|
|
702
|
+
e.stopPropagation();
|
|
703
|
+
openFileDialog();
|
|
704
|
+
},
|
|
705
|
+
children: "Browse files"
|
|
706
|
+
}
|
|
707
|
+
),
|
|
708
|
+
onCamera && /* @__PURE__ */ jsxs2(
|
|
709
|
+
"button",
|
|
710
|
+
{
|
|
711
|
+
type: "button",
|
|
712
|
+
className: "uploader-btn-camera",
|
|
713
|
+
onClick: (e) => {
|
|
714
|
+
e.stopPropagation();
|
|
715
|
+
onCamera();
|
|
716
|
+
},
|
|
717
|
+
children: [
|
|
718
|
+
/* @__PURE__ */ jsxs2(
|
|
719
|
+
"svg",
|
|
720
|
+
{
|
|
721
|
+
className: "uploader-btn-camera-icon",
|
|
722
|
+
width: "16",
|
|
723
|
+
height: "16",
|
|
724
|
+
viewBox: "0 0 24 24",
|
|
725
|
+
fill: "none",
|
|
726
|
+
"aria-hidden": "true",
|
|
727
|
+
children: [
|
|
728
|
+
/* @__PURE__ */ jsx2(
|
|
729
|
+
"path",
|
|
730
|
+
{
|
|
731
|
+
d: "M4 8.5A2.5 2.5 0 0 1 6.5 6h1.2a1 1 0 0 0 .83-.45l.74-1.1A1 1 0 0 1 10.1 4h3.8a1 1 0 0 1 .83.45l.74 1.1a1 1 0 0 0 .83.45h1.2A2.5 2.5 0 0 1 20 8.5v8A2.5 2.5 0 0 1 17.5 19h-11A2.5 2.5 0 0 1 4 16.5v-8Z",
|
|
732
|
+
stroke: "currentColor",
|
|
733
|
+
strokeWidth: "1.6",
|
|
734
|
+
strokeLinejoin: "round"
|
|
735
|
+
}
|
|
736
|
+
),
|
|
737
|
+
/* @__PURE__ */ jsx2("circle", { cx: "12", cy: "12.5", r: "3.2", stroke: "currentColor", strokeWidth: "1.6" })
|
|
738
|
+
]
|
|
739
|
+
}
|
|
740
|
+
),
|
|
741
|
+
"Take photo"
|
|
742
|
+
]
|
|
743
|
+
}
|
|
744
|
+
)
|
|
745
|
+
] }),
|
|
746
|
+
hint && /* @__PURE__ */ jsx2("p", { className: "uploader-dropzone-hint", children: hint }),
|
|
747
|
+
/* @__PURE__ */ jsx2(
|
|
748
|
+
"input",
|
|
749
|
+
{
|
|
750
|
+
ref: inputRef,
|
|
751
|
+
type: "file",
|
|
752
|
+
multiple,
|
|
753
|
+
accept,
|
|
754
|
+
className: "uploader-file-input",
|
|
755
|
+
"aria-hidden": "true",
|
|
756
|
+
tabIndex: -1,
|
|
757
|
+
onChange: handleInputChange
|
|
758
|
+
}
|
|
759
|
+
)
|
|
760
|
+
]
|
|
761
|
+
}
|
|
762
|
+
);
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
// src/react/CameraCapture.tsx
|
|
766
|
+
import { useCallback as useCallback4, useEffect as useEffect2, useRef as useRef4, useState as useState3 } from "react";
|
|
767
|
+
import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
768
|
+
function isCameraSupported() {
|
|
769
|
+
return typeof navigator !== "undefined" && !!navigator.mediaDevices && typeof navigator.mediaDevices.getUserMedia === "function" && typeof document !== "undefined" && typeof HTMLCanvasElement !== "undefined";
|
|
770
|
+
}
|
|
771
|
+
var IMAGE_EXT = /\.(png|jpe?g|gif|webp|bmp|svg|avif|heic|heif)$/i;
|
|
772
|
+
function acceptsImages(accept) {
|
|
773
|
+
if (!accept || accept.length === 0) return true;
|
|
774
|
+
return accept.some((a) => a.startsWith("image/") || a === "image/*" || IMAGE_EXT.test(a.trim()));
|
|
775
|
+
}
|
|
776
|
+
function shouldOfferCamera(options) {
|
|
777
|
+
if (!isCameraSupported()) return false;
|
|
778
|
+
if (!acceptsImages(options?.accept)) return false;
|
|
779
|
+
const sources = options?.fromSources;
|
|
780
|
+
if (sources && !sources.includes("camera")) return false;
|
|
781
|
+
return true;
|
|
782
|
+
}
|
|
783
|
+
function extForType(mime) {
|
|
784
|
+
switch (mime) {
|
|
785
|
+
case "image/png":
|
|
786
|
+
return "png";
|
|
787
|
+
case "image/webp":
|
|
788
|
+
return "webp";
|
|
789
|
+
default:
|
|
790
|
+
return "jpg";
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
function describeCameraError(err) {
|
|
794
|
+
const name = err instanceof DOMException ? err.name : "";
|
|
795
|
+
switch (name) {
|
|
796
|
+
case "NotAllowedError":
|
|
797
|
+
case "SecurityError":
|
|
798
|
+
return "Camera access was blocked. Allow camera permission and try again.";
|
|
799
|
+
case "NotFoundError":
|
|
800
|
+
case "OverconstrainedError":
|
|
801
|
+
return "No camera was found on this device.";
|
|
802
|
+
case "NotReadableError":
|
|
803
|
+
return "The camera is already in use by another application.";
|
|
804
|
+
default:
|
|
805
|
+
return "Could not start the camera.";
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
function CameraCapture({
|
|
809
|
+
onCapture,
|
|
810
|
+
onCancel,
|
|
811
|
+
facingMode = "user",
|
|
812
|
+
outputType = "image/jpeg",
|
|
813
|
+
quality: quality2 = 0.92,
|
|
814
|
+
fileNamePrefix = "camera-photo",
|
|
815
|
+
className,
|
|
816
|
+
style
|
|
817
|
+
}) {
|
|
818
|
+
const videoRef = useRef4(null);
|
|
819
|
+
const streamRef = useRef4(null);
|
|
820
|
+
const blobRef = useRef4(null);
|
|
821
|
+
const [status, setStatus] = useState3("initializing");
|
|
822
|
+
const [error, setError] = useState3(null);
|
|
823
|
+
const [stillUrl, setStillUrl] = useState3("");
|
|
824
|
+
const mirror = facingMode === "user";
|
|
825
|
+
const stopStream = useCallback4(() => {
|
|
826
|
+
streamRef.current?.getTracks().forEach((t) => t.stop());
|
|
827
|
+
streamRef.current = null;
|
|
828
|
+
if (videoRef.current) videoRef.current.srcObject = null;
|
|
829
|
+
}, []);
|
|
830
|
+
const startStream = useCallback4(async () => {
|
|
831
|
+
setError(null);
|
|
832
|
+
setStatus("initializing");
|
|
833
|
+
if (!isCameraSupported()) {
|
|
834
|
+
setError("Camera capture is not supported in this browser.");
|
|
835
|
+
setStatus("error");
|
|
836
|
+
return;
|
|
837
|
+
}
|
|
838
|
+
try {
|
|
839
|
+
const stream = await navigator.mediaDevices.getUserMedia({
|
|
840
|
+
video: { facingMode },
|
|
841
|
+
audio: false
|
|
842
|
+
});
|
|
843
|
+
if (videoRef.current == null) {
|
|
844
|
+
stream.getTracks().forEach((t) => t.stop());
|
|
845
|
+
return;
|
|
846
|
+
}
|
|
847
|
+
streamRef.current = stream;
|
|
848
|
+
videoRef.current.srcObject = stream;
|
|
849
|
+
setStatus("live");
|
|
850
|
+
} catch (err) {
|
|
851
|
+
setError(describeCameraError(err));
|
|
852
|
+
setStatus("error");
|
|
853
|
+
}
|
|
854
|
+
}, [facingMode]);
|
|
855
|
+
useEffect2(() => {
|
|
856
|
+
void startStream();
|
|
857
|
+
return () => {
|
|
858
|
+
stopStream();
|
|
859
|
+
};
|
|
860
|
+
}, [startStream, stopStream]);
|
|
861
|
+
useEffect2(() => {
|
|
862
|
+
if (!stillUrl) return;
|
|
863
|
+
return () => URL.revokeObjectURL(stillUrl);
|
|
864
|
+
}, [stillUrl]);
|
|
865
|
+
const capture = useCallback4(() => {
|
|
866
|
+
const video = videoRef.current;
|
|
867
|
+
if (!video) return;
|
|
868
|
+
const w = video.videoWidth;
|
|
869
|
+
const h = video.videoHeight;
|
|
870
|
+
if (!w || !h) {
|
|
871
|
+
setError("The camera is still warming up \u2014 try again in a moment.");
|
|
872
|
+
return;
|
|
873
|
+
}
|
|
874
|
+
const canvas = document.createElement("canvas");
|
|
875
|
+
canvas.width = w;
|
|
876
|
+
canvas.height = h;
|
|
877
|
+
const ctx = canvas.getContext("2d");
|
|
878
|
+
if (!ctx) {
|
|
879
|
+
setError("Canvas is unavailable for capturing.");
|
|
880
|
+
return;
|
|
881
|
+
}
|
|
882
|
+
if (mirror) {
|
|
883
|
+
ctx.translate(w, 0);
|
|
884
|
+
ctx.scale(-1, 1);
|
|
885
|
+
}
|
|
886
|
+
ctx.drawImage(video, 0, 0, w, h);
|
|
887
|
+
canvas.toBlob(
|
|
888
|
+
(blob) => {
|
|
889
|
+
if (!blob) {
|
|
890
|
+
setError("Failed to capture the photo.");
|
|
891
|
+
return;
|
|
892
|
+
}
|
|
893
|
+
blobRef.current = blob;
|
|
894
|
+
setStillUrl(URL.createObjectURL(blob));
|
|
895
|
+
setStatus("captured");
|
|
896
|
+
stopStream();
|
|
897
|
+
},
|
|
898
|
+
outputType,
|
|
899
|
+
quality2
|
|
900
|
+
);
|
|
901
|
+
}, [mirror, outputType, quality2, stopStream]);
|
|
902
|
+
const retake = useCallback4(() => {
|
|
903
|
+
blobRef.current = null;
|
|
904
|
+
setStillUrl("");
|
|
905
|
+
void startStream();
|
|
906
|
+
}, [startStream]);
|
|
907
|
+
const confirm = useCallback4(() => {
|
|
908
|
+
const blob = blobRef.current;
|
|
909
|
+
if (!blob) return;
|
|
910
|
+
const name = `${fileNamePrefix}-${Date.now()}.${extForType(outputType)}`;
|
|
911
|
+
const file = new File([blob], name, { type: outputType, lastModified: Date.now() });
|
|
912
|
+
onCapture(file);
|
|
913
|
+
}, [fileNamePrefix, outputType, onCapture]);
|
|
914
|
+
return /* @__PURE__ */ jsxs3(
|
|
915
|
+
"div",
|
|
916
|
+
{
|
|
917
|
+
className: ["uploader-camera", className ?? ""].filter(Boolean).join(" "),
|
|
918
|
+
style,
|
|
919
|
+
"data-testid": "camera-capture",
|
|
920
|
+
children: [
|
|
921
|
+
/* @__PURE__ */ jsxs3("div", { className: "uploader-camera-stage", children: [
|
|
922
|
+
status === "error" ? /* @__PURE__ */ jsx3("div", { className: "uploader-camera-error", role: "alert", children: error }) : status === "captured" ? (
|
|
923
|
+
/* eslint-disable-next-line @next/next/no-img-element */
|
|
924
|
+
/* @__PURE__ */ jsx3("img", { src: stillUrl, alt: "Captured preview", className: "uploader-camera-still" })
|
|
925
|
+
) : /* @__PURE__ */ jsx3(
|
|
926
|
+
"video",
|
|
927
|
+
{
|
|
928
|
+
ref: videoRef,
|
|
929
|
+
className: "uploader-camera-video",
|
|
930
|
+
autoPlay: true,
|
|
931
|
+
playsInline: true,
|
|
932
|
+
muted: true,
|
|
933
|
+
"data-mirror": mirror ? "true" : void 0
|
|
934
|
+
}
|
|
935
|
+
),
|
|
936
|
+
status === "initializing" && /* @__PURE__ */ jsx3("div", { className: "uploader-camera-status", "aria-live": "polite", children: "Starting camera\u2026" })
|
|
937
|
+
] }),
|
|
938
|
+
status === "live" && /* @__PURE__ */ jsx3("p", { className: "uploader-camera-hint", children: "Frame your shot, then tap the shutter to capture." }),
|
|
939
|
+
/* @__PURE__ */ jsx3("div", { className: "uploader-camera-actions", children: status === "captured" ? /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
940
|
+
/* @__PURE__ */ jsx3("button", { type: "button", className: "uploader-btn-secondary", onClick: retake, children: "Retake" }),
|
|
941
|
+
/* @__PURE__ */ jsx3("button", { type: "button", className: "uploader-btn-primary", onClick: confirm, children: "Use photo" })
|
|
942
|
+
] }) : status === "error" ? /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
943
|
+
/* @__PURE__ */ jsx3("button", { type: "button", className: "uploader-btn-secondary", onClick: onCancel, children: "Back" }),
|
|
944
|
+
/* @__PURE__ */ jsx3("button", { type: "button", className: "uploader-btn-primary", onClick: () => void startStream(), children: "Try again" })
|
|
945
|
+
] }) : /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
946
|
+
/* @__PURE__ */ jsx3("button", { type: "button", className: "uploader-btn-secondary", onClick: onCancel, children: "Back" }),
|
|
947
|
+
/* @__PURE__ */ jsx3(
|
|
948
|
+
"button",
|
|
949
|
+
{
|
|
950
|
+
type: "button",
|
|
951
|
+
className: "uploader-camera-shutter",
|
|
952
|
+
"aria-label": "Capture photo",
|
|
953
|
+
disabled: status !== "live",
|
|
954
|
+
onClick: capture,
|
|
955
|
+
children: /* @__PURE__ */ jsx3("span", { className: "uploader-camera-shutter-ring", "aria-hidden": "true" })
|
|
956
|
+
}
|
|
957
|
+
),
|
|
958
|
+
/* @__PURE__ */ jsx3("div", { className: "uploader-camera-actions-spacer", "aria-hidden": "true", children: /* @__PURE__ */ jsx3("span", { className: "uploader-btn-secondary", children: "Back" }) })
|
|
959
|
+
] }) })
|
|
960
|
+
]
|
|
961
|
+
}
|
|
962
|
+
);
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
// src/react/ProgressBar.tsx
|
|
966
|
+
import "react";
|
|
967
|
+
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
968
|
+
function ProgressBar({ value, label, size = "sm", className, style }) {
|
|
969
|
+
const clamped = Math.max(0, Math.min(100, value));
|
|
970
|
+
return /* @__PURE__ */ jsx4(
|
|
971
|
+
"div",
|
|
972
|
+
{
|
|
973
|
+
role: "progressbar",
|
|
974
|
+
"aria-valuenow": clamped,
|
|
975
|
+
"aria-valuemin": 0,
|
|
976
|
+
"aria-valuemax": 100,
|
|
977
|
+
"aria-label": label,
|
|
978
|
+
"data-size": size,
|
|
979
|
+
className: ["uploader-progress-bar", className ?? ""].filter(Boolean).join(" "),
|
|
980
|
+
style,
|
|
981
|
+
children: /* @__PURE__ */ jsx4(
|
|
982
|
+
"div",
|
|
983
|
+
{
|
|
984
|
+
className: "uploader-progress-bar-fill",
|
|
985
|
+
style: { width: `${clamped}%` }
|
|
986
|
+
}
|
|
987
|
+
)
|
|
988
|
+
}
|
|
989
|
+
);
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
// src/react/FileQueueRow.tsx
|
|
993
|
+
import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
994
|
+
function statusLabel(entry) {
|
|
995
|
+
switch (entry.state) {
|
|
996
|
+
case "uploading":
|
|
997
|
+
return entry.progress != null ? `${entry.progress}%` : "Uploading\u2026";
|
|
998
|
+
case "done":
|
|
999
|
+
return "Done";
|
|
1000
|
+
case "error":
|
|
1001
|
+
return entry.error ?? "Failed";
|
|
1002
|
+
default:
|
|
1003
|
+
return "Ready";
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
function FileQueueRow({ entry, onRemove, onRetry, onEdit }) {
|
|
1007
|
+
const { id, file, state, progress, previewUrl } = entry;
|
|
1008
|
+
const isUploading = state === "uploading";
|
|
1009
|
+
return /* @__PURE__ */ jsxs4("li", { className: `uploader-file-item uploader-file-${state}`, children: [
|
|
1010
|
+
previewUrl ? /* @__PURE__ */ jsx5(
|
|
1011
|
+
"img",
|
|
1012
|
+
{
|
|
1013
|
+
src: previewUrl,
|
|
1014
|
+
alt: "",
|
|
1015
|
+
"aria-hidden": "true",
|
|
1016
|
+
className: "uploader-file-thumb"
|
|
1017
|
+
}
|
|
1018
|
+
) : /* @__PURE__ */ jsx5("div", { className: "uploader-file-thumb-placeholder", "aria-hidden": "true" }),
|
|
1019
|
+
/* @__PURE__ */ jsxs4("div", { className: "uploader-file-info", children: [
|
|
1020
|
+
/* @__PURE__ */ jsxs4("div", { className: "uploader-file-info-row", children: [
|
|
1021
|
+
/* @__PURE__ */ jsx5("span", { className: "uploader-file-name", children: file.name }),
|
|
1022
|
+
/* @__PURE__ */ jsx5(
|
|
1023
|
+
"span",
|
|
1024
|
+
{
|
|
1025
|
+
className: "uploader-file-status",
|
|
1026
|
+
"data-state": state,
|
|
1027
|
+
role: state === "error" ? "alert" : void 0,
|
|
1028
|
+
children: statusLabel(entry)
|
|
1029
|
+
}
|
|
1030
|
+
)
|
|
1031
|
+
] }),
|
|
1032
|
+
/* @__PURE__ */ jsx5(
|
|
1033
|
+
ProgressBar,
|
|
1034
|
+
{
|
|
1035
|
+
value: isUploading && progress != null ? progress : state === "done" ? 100 : 0,
|
|
1036
|
+
label: `${file.name} upload progress`
|
|
1037
|
+
}
|
|
1038
|
+
)
|
|
1039
|
+
] }),
|
|
1040
|
+
state === "done" && /* @__PURE__ */ jsx5("div", { className: "uploader-file-done-badge", "aria-hidden": "true", children: /* @__PURE__ */ jsx5("svg", { width: "9", height: "7", viewBox: "0 0 9 7", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx5(
|
|
1041
|
+
"path",
|
|
1042
|
+
{
|
|
1043
|
+
d: "M1 3.5L3.5 6L8 1",
|
|
1044
|
+
stroke: "currentColor",
|
|
1045
|
+
strokeWidth: "1.8",
|
|
1046
|
+
strokeLinecap: "round",
|
|
1047
|
+
strokeLinejoin: "round"
|
|
1048
|
+
}
|
|
1049
|
+
) }) }),
|
|
1050
|
+
!isUploading && /* @__PURE__ */ jsxs4("div", { className: "uploader-file-actions", children: [
|
|
1051
|
+
onEdit && previewUrl && (state === "pending" || state === "error") && /* @__PURE__ */ jsx5(
|
|
1052
|
+
"button",
|
|
1053
|
+
{
|
|
1054
|
+
type: "button",
|
|
1055
|
+
className: "uploader-btn-edit",
|
|
1056
|
+
"aria-label": `Edit ${file.name}`,
|
|
1057
|
+
onClick: () => onEdit(id),
|
|
1058
|
+
children: "Edit"
|
|
1059
|
+
}
|
|
1060
|
+
),
|
|
1061
|
+
state === "error" && /* @__PURE__ */ jsx5(
|
|
1062
|
+
"button",
|
|
1063
|
+
{
|
|
1064
|
+
type: "button",
|
|
1065
|
+
className: "uploader-btn-retry",
|
|
1066
|
+
"aria-label": `Retry uploading ${file.name}`,
|
|
1067
|
+
onClick: () => onRetry(id),
|
|
1068
|
+
children: "Retry"
|
|
1069
|
+
}
|
|
1070
|
+
),
|
|
1071
|
+
/* @__PURE__ */ jsx5(
|
|
1072
|
+
"button",
|
|
1073
|
+
{
|
|
1074
|
+
type: "button",
|
|
1075
|
+
className: "uploader-btn-remove",
|
|
1076
|
+
"aria-label": `Remove ${file.name}`,
|
|
1077
|
+
onClick: () => onRemove(id),
|
|
1078
|
+
children: "\xD7"
|
|
1079
|
+
}
|
|
1080
|
+
)
|
|
1081
|
+
] })
|
|
1082
|
+
] });
|
|
1083
|
+
}
|
|
1084
|
+
|
|
643
1085
|
// src/react/PickerOverlay.tsx
|
|
644
|
-
import { Fragment, jsx as
|
|
1086
|
+
import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1087
|
+
function dialogTitle(isUploading, isDone, totalFiles, doneCount, errorCount, editingId, cameraOpen) {
|
|
1088
|
+
if (editingId) return "Edit Image";
|
|
1089
|
+
if (cameraOpen) return "Take a photo";
|
|
1090
|
+
if (isDone && errorCount === 0) return "All files uploaded";
|
|
1091
|
+
if (isDone && errorCount > 0) return `${doneCount} of ${totalFiles} uploaded`;
|
|
1092
|
+
if (isUploading) return `Uploading ${totalFiles} file${totalFiles !== 1 ? "s" : ""}`;
|
|
1093
|
+
return "Upload files";
|
|
1094
|
+
}
|
|
645
1095
|
function PickerOverlay({
|
|
646
1096
|
open,
|
|
647
1097
|
onClose,
|
|
@@ -657,10 +1107,10 @@ function PickerOverlay({
|
|
|
657
1107
|
style
|
|
658
1108
|
}) {
|
|
659
1109
|
const titleId = useId();
|
|
660
|
-
const
|
|
661
|
-
const
|
|
662
|
-
const [
|
|
663
|
-
const [
|
|
1110
|
+
const panelRef = useRef5(null);
|
|
1111
|
+
const [isDragOver, setIsDragOver] = useState4(false);
|
|
1112
|
+
const [editingId, setEditingId] = useState4(null);
|
|
1113
|
+
const [cameraOpen, setCameraOpen] = useState4(false);
|
|
664
1114
|
const { files, addFiles, removeFile, editFile, retryFile, upload, progress, isUploading, isDone } = usePicker({
|
|
665
1115
|
apikey,
|
|
666
1116
|
apiUrl,
|
|
@@ -670,7 +1120,7 @@ function PickerOverlay({
|
|
|
670
1120
|
onFileUploadFinished,
|
|
671
1121
|
onFileUploadFailed
|
|
672
1122
|
});
|
|
673
|
-
|
|
1123
|
+
useEffect3(() => {
|
|
674
1124
|
if (!open) return;
|
|
675
1125
|
const handler = (e) => {
|
|
676
1126
|
if (e.key === "Escape") {
|
|
@@ -681,7 +1131,7 @@ function PickerOverlay({
|
|
|
681
1131
|
document.addEventListener("keydown", handler);
|
|
682
1132
|
return () => document.removeEventListener("keydown", handler);
|
|
683
1133
|
}, [open, onClose, onCancel]);
|
|
684
|
-
|
|
1134
|
+
useEffect3(() => {
|
|
685
1135
|
if (!open || !panelRef.current) return;
|
|
686
1136
|
const panel = panelRef.current;
|
|
687
1137
|
const focusable = panel.querySelectorAll(
|
|
@@ -707,8 +1157,8 @@ function PickerOverlay({
|
|
|
707
1157
|
};
|
|
708
1158
|
document.addEventListener("keydown", trap);
|
|
709
1159
|
return () => document.removeEventListener("keydown", trap);
|
|
710
|
-
}, [open, files, editingId]);
|
|
711
|
-
|
|
1160
|
+
}, [open, files, editingId, cameraOpen]);
|
|
1161
|
+
useEffect3(() => {
|
|
712
1162
|
if (!open) return;
|
|
713
1163
|
const prev = document.body.style.overflow;
|
|
714
1164
|
document.body.style.overflow = "hidden";
|
|
@@ -716,18 +1166,18 @@ function PickerOverlay({
|
|
|
716
1166
|
document.body.style.overflow = prev;
|
|
717
1167
|
};
|
|
718
1168
|
}, [open]);
|
|
719
|
-
const handleDragEnter =
|
|
1169
|
+
const handleDragEnter = useCallback5((e) => {
|
|
720
1170
|
e.preventDefault();
|
|
721
1171
|
setIsDragOver(true);
|
|
722
1172
|
}, []);
|
|
723
|
-
const handleDragLeave =
|
|
1173
|
+
const handleDragLeave = useCallback5((e) => {
|
|
724
1174
|
e.preventDefault();
|
|
725
1175
|
setIsDragOver(false);
|
|
726
1176
|
}, []);
|
|
727
|
-
const handleDragOver =
|
|
1177
|
+
const handleDragOver = useCallback5((e) => {
|
|
728
1178
|
e.preventDefault();
|
|
729
1179
|
}, []);
|
|
730
|
-
const handleDrop =
|
|
1180
|
+
const handleDrop = useCallback5(
|
|
731
1181
|
(e) => {
|
|
732
1182
|
e.preventDefault();
|
|
733
1183
|
setIsDragOver(false);
|
|
@@ -737,58 +1187,63 @@ function PickerOverlay({
|
|
|
737
1187
|
},
|
|
738
1188
|
[addFiles]
|
|
739
1189
|
);
|
|
740
|
-
const
|
|
741
|
-
const handleInputChange = useCallback3(
|
|
742
|
-
(e) => {
|
|
743
|
-
if (e.target.files && e.target.files.length > 0) {
|
|
744
|
-
addFiles(e.target.files);
|
|
745
|
-
e.target.value = "";
|
|
746
|
-
}
|
|
747
|
-
},
|
|
748
|
-
[addFiles]
|
|
749
|
-
);
|
|
750
|
-
const handleClose = useCallback3(() => {
|
|
1190
|
+
const handleClose = useCallback5(() => {
|
|
751
1191
|
onCancel?.();
|
|
752
1192
|
onClose();
|
|
753
1193
|
}, [onCancel, onClose]);
|
|
754
1194
|
const hasPending = files.some((f) => f.state === "pending");
|
|
1195
|
+
const pendingCount = files.filter((f) => f.state === "pending").length;
|
|
1196
|
+
const doneCount = files.filter((f) => f.state === "done").length;
|
|
1197
|
+
const errorCount = files.filter((f) => f.state === "error").length;
|
|
755
1198
|
const accept = pickerOptions?.accept?.join(",");
|
|
1199
|
+
const multiple = !pickerOptions?.maxFiles || pickerOptions.maxFiles > 1;
|
|
756
1200
|
const editingEntry = files.find((f) => f.id === editingId) ?? null;
|
|
1201
|
+
const offerCamera = shouldOfferCamera(pickerOptions);
|
|
1202
|
+
const title = dialogTitle(
|
|
1203
|
+
isUploading,
|
|
1204
|
+
isDone,
|
|
1205
|
+
files.length,
|
|
1206
|
+
doneCount,
|
|
1207
|
+
errorCount,
|
|
1208
|
+
editingId,
|
|
1209
|
+
cameraOpen
|
|
1210
|
+
);
|
|
757
1211
|
if (!open) return null;
|
|
758
1212
|
return (
|
|
759
1213
|
/* Backdrop — clicking outside the panel dismisses the modal */
|
|
760
|
-
/* @__PURE__ */
|
|
1214
|
+
/* @__PURE__ */ jsx6(
|
|
761
1215
|
"div",
|
|
762
1216
|
{
|
|
763
1217
|
className: "uploader-backdrop",
|
|
764
1218
|
"data-testid": "picker-backdrop",
|
|
765
1219
|
onClick: handleClose,
|
|
766
|
-
children: /* @__PURE__ */
|
|
1220
|
+
children: /* @__PURE__ */ jsxs5(
|
|
767
1221
|
"div",
|
|
768
1222
|
{
|
|
769
1223
|
ref: panelRef,
|
|
770
1224
|
role: "dialog",
|
|
771
1225
|
"aria-modal": "true",
|
|
772
1226
|
"aria-labelledby": titleId,
|
|
1227
|
+
"data-drag-over": isDragOver && !editingEntry && files.length === 0 ? "true" : void 0,
|
|
773
1228
|
className: ["uploader-picker-panel", className ?? ""].filter(Boolean).join(" "),
|
|
774
1229
|
style,
|
|
775
1230
|
"data-testid": "picker-panel",
|
|
776
1231
|
onClick: (e) => e.stopPropagation(),
|
|
777
1232
|
children: [
|
|
778
|
-
/* @__PURE__ */
|
|
779
|
-
/* @__PURE__ */
|
|
780
|
-
/* @__PURE__ */
|
|
1233
|
+
/* @__PURE__ */ jsxs5("div", { className: "uploader-picker-header", children: [
|
|
1234
|
+
/* @__PURE__ */ jsx6("h2", { id: titleId, className: "uploader-picker-title", children: title }),
|
|
1235
|
+
/* @__PURE__ */ jsx6(
|
|
781
1236
|
"button",
|
|
782
1237
|
{
|
|
783
1238
|
type: "button",
|
|
784
1239
|
className: "uploader-btn-close",
|
|
785
1240
|
"aria-label": "Close picker",
|
|
786
1241
|
onClick: handleClose,
|
|
787
|
-
children: "\
|
|
1242
|
+
children: "\u2715"
|
|
788
1243
|
}
|
|
789
1244
|
)
|
|
790
1245
|
] }),
|
|
791
|
-
editingEntry ? /* @__PURE__ */
|
|
1246
|
+
editingEntry ? /* @__PURE__ */ jsx6(
|
|
792
1247
|
ImageEditor,
|
|
793
1248
|
{
|
|
794
1249
|
file: editingEntry.file,
|
|
@@ -798,160 +1253,134 @@ function PickerOverlay({
|
|
|
798
1253
|
setEditingId(null);
|
|
799
1254
|
}
|
|
800
1255
|
}
|
|
801
|
-
) :
|
|
802
|
-
/*
|
|
803
|
-
|
|
1256
|
+
) : cameraOpen ? (
|
|
1257
|
+
/* ── Camera capture view ───────────────────────────────────────── */
|
|
1258
|
+
/* @__PURE__ */ jsx6(
|
|
1259
|
+
CameraCapture,
|
|
804
1260
|
{
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
tabIndex: 0,
|
|
811
|
-
"aria-label": "Drop files here or click to browse",
|
|
812
|
-
onDragEnter: handleDragEnter,
|
|
813
|
-
onDragLeave: handleDragLeave,
|
|
814
|
-
onDragOver: handleDragOver,
|
|
815
|
-
onDrop: handleDrop,
|
|
816
|
-
onClick: openFileDialog,
|
|
817
|
-
onKeyDown: (e) => {
|
|
818
|
-
if (e.key === "Enter" || e.key === " ") {
|
|
819
|
-
e.preventDefault();
|
|
820
|
-
openFileDialog();
|
|
821
|
-
}
|
|
822
|
-
},
|
|
823
|
-
children: [
|
|
824
|
-
/* @__PURE__ */ jsx2("span", { className: "uploader-dropzone-icon", "aria-hidden": "true", children: "\u2191" }),
|
|
825
|
-
/* @__PURE__ */ jsx2("p", { className: "uploader-dropzone-label", children: "Drag & drop files here" }),
|
|
826
|
-
/* @__PURE__ */ jsx2(
|
|
827
|
-
"button",
|
|
828
|
-
{
|
|
829
|
-
type: "button",
|
|
830
|
-
className: "uploader-btn-browse",
|
|
831
|
-
onClick: (e) => {
|
|
832
|
-
e.stopPropagation();
|
|
833
|
-
openFileDialog();
|
|
834
|
-
},
|
|
835
|
-
children: "Browse files"
|
|
836
|
-
}
|
|
837
|
-
)
|
|
838
|
-
]
|
|
839
|
-
}
|
|
840
|
-
),
|
|
841
|
-
/* @__PURE__ */ jsx2(
|
|
842
|
-
"input",
|
|
843
|
-
{
|
|
844
|
-
ref: inputRef,
|
|
845
|
-
type: "file",
|
|
846
|
-
multiple: !pickerOptions?.maxFiles || pickerOptions.maxFiles > 1,
|
|
847
|
-
accept,
|
|
848
|
-
className: "uploader-file-input",
|
|
849
|
-
"aria-hidden": "true",
|
|
850
|
-
tabIndex: -1,
|
|
851
|
-
onChange: handleInputChange
|
|
852
|
-
}
|
|
853
|
-
),
|
|
854
|
-
files.length > 0 && /* @__PURE__ */ jsx2("ul", { className: "uploader-file-list", role: "list", "aria-label": "Selected files", children: files.map((entry) => /* @__PURE__ */ jsxs2("li", { className: `uploader-file-item uploader-file-${entry.state}`, children: [
|
|
855
|
-
entry.previewUrl && /* @__PURE__ */ jsx2(
|
|
856
|
-
"img",
|
|
857
|
-
{
|
|
858
|
-
src: entry.previewUrl,
|
|
859
|
-
alt: "",
|
|
860
|
-
"aria-hidden": "true",
|
|
861
|
-
className: "uploader-file-thumb"
|
|
1261
|
+
facingMode: pickerOptions?.cameraFacingMode,
|
|
1262
|
+
onCancel: () => setCameraOpen(false),
|
|
1263
|
+
onCapture: (file) => {
|
|
1264
|
+
addFiles([file]);
|
|
1265
|
+
setCameraOpen(false);
|
|
862
1266
|
}
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
1267
|
+
}
|
|
1268
|
+
)
|
|
1269
|
+
) : isDone ? (
|
|
1270
|
+
/* ── Done state ─────────────────────────────────────────────────── */
|
|
1271
|
+
/* @__PURE__ */ jsxs5("div", { className: "uploader-done-body", children: [
|
|
1272
|
+
/* @__PURE__ */ jsxs5("p", { className: "uploader-done-subtitle", children: [
|
|
1273
|
+
files.length,
|
|
1274
|
+
" file",
|
|
1275
|
+
files.length !== 1 ? "s" : "",
|
|
1276
|
+
" delivered to your bucket"
|
|
868
1277
|
] }),
|
|
869
|
-
|
|
870
|
-
|
|
1278
|
+
/* @__PURE__ */ jsx6("ul", { className: "uploader-file-list uploader-done-list", role: "list", "aria-label": "Uploaded files", children: files.map((entry) => /* @__PURE__ */ jsx6(
|
|
1279
|
+
FileQueueRow,
|
|
871
1280
|
{
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
),
|
|
878
|
-
/* @__PURE__ */
|
|
879
|
-
entry.previewUrl && (entry.state === "pending" || entry.state === "error") && /* @__PURE__ */ jsx2(
|
|
880
|
-
"button",
|
|
881
|
-
{
|
|
882
|
-
type: "button",
|
|
883
|
-
className: "uploader-btn-edit",
|
|
884
|
-
"aria-label": `Edit ${entry.file.name}`,
|
|
885
|
-
onClick: () => setEditingId(entry.id),
|
|
886
|
-
children: "Edit"
|
|
887
|
-
}
|
|
888
|
-
),
|
|
889
|
-
entry.state === "error" && /* @__PURE__ */ jsx2(
|
|
890
|
-
"button",
|
|
891
|
-
{
|
|
892
|
-
type: "button",
|
|
893
|
-
className: "uploader-btn-retry",
|
|
894
|
-
"aria-label": `Retry uploading ${entry.file.name}`,
|
|
895
|
-
onClick: () => retryFile(entry.id),
|
|
896
|
-
children: "Retry"
|
|
897
|
-
}
|
|
898
|
-
),
|
|
899
|
-
entry.state !== "uploading" && /* @__PURE__ */ jsx2(
|
|
900
|
-
"button",
|
|
901
|
-
{
|
|
902
|
-
type: "button",
|
|
903
|
-
className: "uploader-btn-remove",
|
|
904
|
-
"aria-label": `Remove ${entry.file.name}`,
|
|
905
|
-
onClick: () => removeFile(entry.id),
|
|
906
|
-
children: "\xD7"
|
|
907
|
-
}
|
|
908
|
-
)
|
|
909
|
-
] })
|
|
910
|
-
] }, entry.id)) }),
|
|
911
|
-
isUploading && /* @__PURE__ */ jsxs2("div", { className: "uploader-aggregate-progress", role: "status", "aria-live": "polite", children: [
|
|
912
|
-
/* @__PURE__ */ jsx2("progress", { value: progress, max: 100, "aria-label": "Overall upload progress" }),
|
|
913
|
-
/* @__PURE__ */ jsxs2("span", { "aria-live": "polite", children: [
|
|
914
|
-
progress,
|
|
915
|
-
"%"
|
|
916
|
-
] })
|
|
917
|
-
] }),
|
|
918
|
-
/* @__PURE__ */ jsxs2("div", { className: "uploader-picker-footer", children: [
|
|
919
|
-
hasPending && !isUploading && /* @__PURE__ */ jsxs2(
|
|
1281
|
+
entry,
|
|
1282
|
+
onRemove: removeFile,
|
|
1283
|
+
onRetry: retryFile
|
|
1284
|
+
},
|
|
1285
|
+
entry.id
|
|
1286
|
+
)) }),
|
|
1287
|
+
/* @__PURE__ */ jsx6(
|
|
920
1288
|
"button",
|
|
921
1289
|
{
|
|
922
1290
|
type: "button",
|
|
923
|
-
className: "uploader-btn-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
1291
|
+
className: "uploader-btn-primary uploader-btn-done-full",
|
|
1292
|
+
onClick: onClose,
|
|
1293
|
+
children: "Done"
|
|
1294
|
+
}
|
|
1295
|
+
)
|
|
1296
|
+
] })
|
|
1297
|
+
) : (
|
|
1298
|
+
/* ── Normal (idle / uploading / error) view ─────────────────────── */
|
|
1299
|
+
/* @__PURE__ */ jsxs5(Fragment2, { children: [
|
|
1300
|
+
files.length === 0 && /* @__PURE__ */ jsx6(
|
|
1301
|
+
Dropzone,
|
|
1302
|
+
{
|
|
1303
|
+
onFiles: addFiles,
|
|
1304
|
+
isDragOver,
|
|
1305
|
+
onDragEnter: handleDragEnter,
|
|
1306
|
+
onDragLeave: handleDragLeave,
|
|
1307
|
+
onDragOver: handleDragOver,
|
|
1308
|
+
onDrop: handleDrop,
|
|
1309
|
+
accept,
|
|
1310
|
+
multiple,
|
|
1311
|
+
onCamera: offerCamera ? () => setCameraOpen(true) : void 0,
|
|
1312
|
+
hint: "PNG, JPG, PDF, ZIP \xB7 up to 50 MB"
|
|
932
1313
|
}
|
|
933
1314
|
),
|
|
934
|
-
|
|
935
|
-
"
|
|
1315
|
+
files.length > 0 && /* @__PURE__ */ jsx6(
|
|
1316
|
+
"ul",
|
|
936
1317
|
{
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
1318
|
+
className: "uploader-file-list",
|
|
1319
|
+
role: "list",
|
|
1320
|
+
"aria-label": "Selected files",
|
|
1321
|
+
"aria-live": "polite",
|
|
1322
|
+
children: files.map((entry) => /* @__PURE__ */ jsx6(
|
|
1323
|
+
FileQueueRow,
|
|
1324
|
+
{
|
|
1325
|
+
entry,
|
|
1326
|
+
onRemove: removeFile,
|
|
1327
|
+
onRetry: retryFile,
|
|
1328
|
+
onEdit: setEditingId
|
|
1329
|
+
},
|
|
1330
|
+
entry.id
|
|
1331
|
+
))
|
|
941
1332
|
}
|
|
942
1333
|
),
|
|
943
|
-
|
|
944
|
-
"
|
|
1334
|
+
isUploading && /* @__PURE__ */ jsxs5(
|
|
1335
|
+
"div",
|
|
945
1336
|
{
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
1337
|
+
className: "uploader-aggregate-progress",
|
|
1338
|
+
role: "status",
|
|
1339
|
+
"aria-live": "polite",
|
|
1340
|
+
children: [
|
|
1341
|
+
/* @__PURE__ */ jsx6("span", { className: "uploader-aggregate-progress-label", children: "Total progress" }),
|
|
1342
|
+
/* @__PURE__ */ jsx6(
|
|
1343
|
+
ProgressBar,
|
|
1344
|
+
{
|
|
1345
|
+
value: progress,
|
|
1346
|
+
label: "Overall upload progress",
|
|
1347
|
+
size: "lg"
|
|
1348
|
+
}
|
|
1349
|
+
),
|
|
1350
|
+
/* @__PURE__ */ jsxs5("span", { className: "uploader-aggregate-progress-pct", children: [
|
|
1351
|
+
progress,
|
|
1352
|
+
"%"
|
|
1353
|
+
] })
|
|
1354
|
+
]
|
|
951
1355
|
}
|
|
952
|
-
)
|
|
1356
|
+
),
|
|
1357
|
+
/* @__PURE__ */ jsxs5("div", { className: "uploader-picker-footer", children: [
|
|
1358
|
+
/* @__PURE__ */ jsx6("span", { className: "uploader-picker-footer-esc", children: "Esc to close" }),
|
|
1359
|
+
/* @__PURE__ */ jsxs5("div", { className: "uploader-picker-footer-actions", children: [
|
|
1360
|
+
/* @__PURE__ */ jsx6(
|
|
1361
|
+
"button",
|
|
1362
|
+
{
|
|
1363
|
+
type: "button",
|
|
1364
|
+
className: "uploader-btn-secondary uploader-btn-cancel",
|
|
1365
|
+
disabled: isUploading,
|
|
1366
|
+
onClick: handleClose,
|
|
1367
|
+
children: "Cancel"
|
|
1368
|
+
}
|
|
1369
|
+
),
|
|
1370
|
+
/* @__PURE__ */ jsx6(
|
|
1371
|
+
"button",
|
|
1372
|
+
{
|
|
1373
|
+
type: "button",
|
|
1374
|
+
className: "uploader-btn-primary uploader-btn-upload",
|
|
1375
|
+
disabled: isUploading || !hasPending,
|
|
1376
|
+
onClick: () => void upload(),
|
|
1377
|
+
children: isUploading ? "Uploading\u2026" : `Upload${pendingCount > 0 ? ` ${pendingCount} file${pendingCount !== 1 ? "s" : ""}` : ""}`
|
|
1378
|
+
}
|
|
1379
|
+
)
|
|
1380
|
+
] })
|
|
1381
|
+
] })
|
|
953
1382
|
] })
|
|
954
|
-
|
|
1383
|
+
)
|
|
955
1384
|
]
|
|
956
1385
|
}
|
|
957
1386
|
)
|
|
@@ -961,8 +1390,8 @@ function PickerOverlay({
|
|
|
961
1390
|
}
|
|
962
1391
|
|
|
963
1392
|
// src/react/DropPane.tsx
|
|
964
|
-
import {
|
|
965
|
-
import { jsx as
|
|
1393
|
+
import { useState as useState5, useCallback as useCallback6 } from "react";
|
|
1394
|
+
import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
966
1395
|
function DropPane({
|
|
967
1396
|
apikey,
|
|
968
1397
|
apiUrl,
|
|
@@ -972,12 +1401,12 @@ function DropPane({
|
|
|
972
1401
|
onFileUploadFinished,
|
|
973
1402
|
onFileUploadFailed,
|
|
974
1403
|
onCancel,
|
|
1404
|
+
hint = "or click to browse \xB7 max 50 MB",
|
|
975
1405
|
className,
|
|
976
1406
|
style
|
|
977
1407
|
}) {
|
|
978
|
-
const
|
|
979
|
-
const
|
|
980
|
-
const [isDragOver, setIsDragOver] = useState4(false);
|
|
1408
|
+
const [isDragOver, setIsDragOver] = useState5(false);
|
|
1409
|
+
const [cameraOpen, setCameraOpen] = useState5(false);
|
|
981
1410
|
const { files, addFiles, removeFile, retryFile, upload, progress, isUploading, isDone } = usePicker({
|
|
982
1411
|
apikey,
|
|
983
1412
|
apiUrl,
|
|
@@ -987,21 +1416,21 @@ function DropPane({
|
|
|
987
1416
|
onFileUploadFinished,
|
|
988
1417
|
onFileUploadFailed
|
|
989
1418
|
});
|
|
990
|
-
const handleDragEnter =
|
|
1419
|
+
const handleDragEnter = useCallback6((e) => {
|
|
991
1420
|
e.preventDefault();
|
|
992
1421
|
e.stopPropagation();
|
|
993
1422
|
setIsDragOver(true);
|
|
994
1423
|
}, []);
|
|
995
|
-
const handleDragLeave =
|
|
1424
|
+
const handleDragLeave = useCallback6((e) => {
|
|
996
1425
|
e.preventDefault();
|
|
997
1426
|
e.stopPropagation();
|
|
998
1427
|
setIsDragOver(false);
|
|
999
1428
|
}, []);
|
|
1000
|
-
const handleDragOver =
|
|
1429
|
+
const handleDragOver = useCallback6((e) => {
|
|
1001
1430
|
e.preventDefault();
|
|
1002
1431
|
e.stopPropagation();
|
|
1003
1432
|
}, []);
|
|
1004
|
-
const handleDrop =
|
|
1433
|
+
const handleDrop = useCallback6(
|
|
1005
1434
|
(e) => {
|
|
1006
1435
|
e.preventDefault();
|
|
1007
1436
|
e.stopPropagation();
|
|
@@ -1012,141 +1441,126 @@ function DropPane({
|
|
|
1012
1441
|
},
|
|
1013
1442
|
[addFiles]
|
|
1014
1443
|
);
|
|
1015
|
-
const handleInputChange = useCallback4(
|
|
1016
|
-
(e) => {
|
|
1017
|
-
if (e.target.files && e.target.files.length > 0) {
|
|
1018
|
-
addFiles(e.target.files);
|
|
1019
|
-
e.target.value = "";
|
|
1020
|
-
}
|
|
1021
|
-
},
|
|
1022
|
-
[addFiles]
|
|
1023
|
-
);
|
|
1024
|
-
const openFileDialog = useCallback4(() => {
|
|
1025
|
-
inputRef.current?.click();
|
|
1026
|
-
}, []);
|
|
1027
1444
|
const hasPending = files.some((f) => f.state === "pending");
|
|
1445
|
+
const pendingCount = files.filter((f) => f.state === "pending").length;
|
|
1028
1446
|
const accept = pickerOptions?.accept?.join(",");
|
|
1029
|
-
|
|
1447
|
+
const multiple = !pickerOptions?.maxFiles || pickerOptions.maxFiles > 1;
|
|
1448
|
+
const offerCamera = shouldOfferCamera(pickerOptions);
|
|
1449
|
+
if (cameraOpen) {
|
|
1450
|
+
return /* @__PURE__ */ jsx7(
|
|
1451
|
+
"div",
|
|
1452
|
+
{
|
|
1453
|
+
className: ["uploader-drop-pane", className ?? ""].filter(Boolean).join(" "),
|
|
1454
|
+
"data-testid": "drop-pane",
|
|
1455
|
+
style,
|
|
1456
|
+
children: /* @__PURE__ */ jsx7(
|
|
1457
|
+
CameraCapture,
|
|
1458
|
+
{
|
|
1459
|
+
facingMode: pickerOptions?.cameraFacingMode,
|
|
1460
|
+
onCancel: () => setCameraOpen(false),
|
|
1461
|
+
onCapture: (file) => {
|
|
1462
|
+
addFiles([file]);
|
|
1463
|
+
setCameraOpen(false);
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
)
|
|
1467
|
+
}
|
|
1468
|
+
);
|
|
1469
|
+
}
|
|
1470
|
+
return /* @__PURE__ */ jsxs6(
|
|
1030
1471
|
"div",
|
|
1031
1472
|
{
|
|
1032
|
-
className: ["uploader-drop-pane",
|
|
1033
|
-
|
|
1473
|
+
className: ["uploader-drop-pane", className ?? ""].filter(Boolean).join(" "),
|
|
1474
|
+
"data-drag-over": isDragOver ? "true" : void 0,
|
|
1034
1475
|
"data-testid": "drop-pane",
|
|
1476
|
+
style,
|
|
1035
1477
|
children: [
|
|
1036
|
-
/* @__PURE__ */
|
|
1037
|
-
|
|
1478
|
+
files.length === 0 && /* @__PURE__ */ jsx7(
|
|
1479
|
+
Dropzone,
|
|
1038
1480
|
{
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
tabIndex: 0,
|
|
1042
|
-
"aria-label": "Drop files here or click to browse",
|
|
1481
|
+
onFiles: addFiles,
|
|
1482
|
+
isDragOver,
|
|
1043
1483
|
onDragEnter: handleDragEnter,
|
|
1044
1484
|
onDragLeave: handleDragLeave,
|
|
1045
1485
|
onDragOver: handleDragOver,
|
|
1046
1486
|
onDrop: handleDrop,
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
openFileDialog();
|
|
1052
|
-
}
|
|
1053
|
-
},
|
|
1054
|
-
children: [
|
|
1055
|
-
/* @__PURE__ */ jsx3("span", { className: "uploader-dropzone-icon", "aria-hidden": "true", children: "\u2191" }),
|
|
1056
|
-
/* @__PURE__ */ jsxs3("span", { className: "uploader-dropzone-label", children: [
|
|
1057
|
-
"Drag & drop files here or",
|
|
1058
|
-
" ",
|
|
1059
|
-
/* @__PURE__ */ jsx3("label", { htmlFor: inputId, className: "uploader-browse-link", children: "browse" })
|
|
1060
|
-
] })
|
|
1061
|
-
]
|
|
1487
|
+
accept,
|
|
1488
|
+
multiple,
|
|
1489
|
+
onCamera: offerCamera ? () => setCameraOpen(true) : void 0,
|
|
1490
|
+
hint
|
|
1062
1491
|
}
|
|
1063
1492
|
),
|
|
1064
|
-
/* @__PURE__ */
|
|
1065
|
-
"
|
|
1493
|
+
files.length > 0 && isDragOver && /* @__PURE__ */ jsx7(
|
|
1494
|
+
"div",
|
|
1066
1495
|
{
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
className: "uploader-file-input",
|
|
1496
|
+
className: "uploader-drop-pane-drag-overlay",
|
|
1497
|
+
onDragEnter: handleDragEnter,
|
|
1498
|
+
onDragLeave: handleDragLeave,
|
|
1499
|
+
onDragOver: handleDragOver,
|
|
1500
|
+
onDrop: handleDrop,
|
|
1073
1501
|
"aria-hidden": "true",
|
|
1074
|
-
|
|
1075
|
-
onChange: handleInputChange
|
|
1502
|
+
children: /* @__PURE__ */ jsx7("span", { className: "uploader-drop-pane-drag-label", children: "Drop to add files" })
|
|
1076
1503
|
}
|
|
1077
1504
|
),
|
|
1078
|
-
files.length > 0 && /* @__PURE__ */
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
),
|
|
1088
|
-
/* @__PURE__ */ jsxs3("div", { className: "uploader-file-info", children: [
|
|
1089
|
-
/* @__PURE__ */ jsx3("span", { className: "uploader-file-name", children: entry.file.name }),
|
|
1090
|
-
entry.state === "error" && /* @__PURE__ */ jsx3("span", { className: "uploader-file-error", role: "alert", children: entry.error ?? "Upload failed" }),
|
|
1091
|
-
entry.state === "done" && /* @__PURE__ */ jsx3("span", { className: "uploader-file-done", children: "Uploaded" })
|
|
1092
|
-
] }),
|
|
1093
|
-
entry.state === "uploading" && entry.progress != null && /* @__PURE__ */ jsx3(
|
|
1094
|
-
"progress",
|
|
1095
|
-
{
|
|
1096
|
-
className: "uploader-file-progress",
|
|
1097
|
-
value: entry.progress,
|
|
1098
|
-
max: 100,
|
|
1099
|
-
"aria-label": `${entry.file.name} upload progress`
|
|
1100
|
-
}
|
|
1101
|
-
),
|
|
1102
|
-
/* @__PURE__ */ jsxs3("div", { className: "uploader-file-actions", children: [
|
|
1103
|
-
entry.state === "error" && /* @__PURE__ */ jsx3(
|
|
1104
|
-
"button",
|
|
1105
|
-
{
|
|
1106
|
-
type: "button",
|
|
1107
|
-
className: "uploader-btn-retry",
|
|
1108
|
-
"aria-label": `Retry uploading ${entry.file.name}`,
|
|
1109
|
-
onClick: () => retryFile(entry.id),
|
|
1110
|
-
children: "Retry"
|
|
1111
|
-
}
|
|
1112
|
-
),
|
|
1113
|
-
entry.state !== "uploading" && /* @__PURE__ */ jsx3(
|
|
1114
|
-
"button",
|
|
1505
|
+
files.length > 0 && /* @__PURE__ */ jsx7(
|
|
1506
|
+
"ul",
|
|
1507
|
+
{
|
|
1508
|
+
className: "uploader-file-list uploader-drop-pane-file-list",
|
|
1509
|
+
role: "list",
|
|
1510
|
+
"aria-label": "Selected files",
|
|
1511
|
+
"aria-live": "polite",
|
|
1512
|
+
children: files.map((entry) => /* @__PURE__ */ jsx7(
|
|
1513
|
+
FileQueueRow,
|
|
1115
1514
|
{
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
"
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1515
|
+
entry,
|
|
1516
|
+
onRemove: removeFile,
|
|
1517
|
+
onRetry: retryFile
|
|
1518
|
+
},
|
|
1519
|
+
entry.id
|
|
1520
|
+
))
|
|
1521
|
+
}
|
|
1522
|
+
),
|
|
1523
|
+
isUploading && /* @__PURE__ */ jsxs6(
|
|
1524
|
+
"div",
|
|
1525
|
+
{
|
|
1526
|
+
className: "uploader-aggregate-progress",
|
|
1527
|
+
role: "status",
|
|
1528
|
+
"aria-live": "polite",
|
|
1529
|
+
children: [
|
|
1530
|
+
/* @__PURE__ */ jsx7(
|
|
1531
|
+
ProgressBar,
|
|
1532
|
+
{
|
|
1533
|
+
value: progress,
|
|
1534
|
+
label: "Overall upload progress",
|
|
1535
|
+
size: "lg"
|
|
1536
|
+
}
|
|
1537
|
+
),
|
|
1538
|
+
/* @__PURE__ */ jsxs6("span", { className: "uploader-aggregate-progress-pct", children: [
|
|
1539
|
+
progress,
|
|
1540
|
+
"%"
|
|
1541
|
+
] })
|
|
1542
|
+
]
|
|
1543
|
+
}
|
|
1544
|
+
),
|
|
1545
|
+
files.length > 0 && !isDone && /* @__PURE__ */ jsxs6("div", { className: "uploader-drop-pane-actions", children: [
|
|
1546
|
+
/* @__PURE__ */ jsx7(
|
|
1134
1547
|
"button",
|
|
1135
1548
|
{
|
|
1136
1549
|
type: "button",
|
|
1137
|
-
className: "uploader-btn-
|
|
1550
|
+
className: "uploader-btn-secondary uploader-btn-cancel",
|
|
1138
1551
|
disabled: isUploading,
|
|
1139
|
-
onClick:
|
|
1140
|
-
children: "
|
|
1552
|
+
onClick: onCancel,
|
|
1553
|
+
children: "Cancel"
|
|
1141
1554
|
}
|
|
1142
1555
|
),
|
|
1143
|
-
|
|
1556
|
+
hasPending && /* @__PURE__ */ jsx7(
|
|
1144
1557
|
"button",
|
|
1145
1558
|
{
|
|
1146
1559
|
type: "button",
|
|
1147
|
-
className: "uploader-btn-
|
|
1148
|
-
|
|
1149
|
-
|
|
1560
|
+
className: "uploader-btn-primary uploader-btn-upload",
|
|
1561
|
+
disabled: isUploading,
|
|
1562
|
+
onClick: () => void upload(),
|
|
1563
|
+
children: isUploading ? "Uploading\u2026" : `Upload${pendingCount > 0 ? ` ${pendingCount} file${pendingCount !== 1 ? "s" : ""}` : ""}`
|
|
1150
1564
|
}
|
|
1151
1565
|
)
|
|
1152
1566
|
] })
|
|
@@ -1155,6 +1569,7 @@ function DropPane({
|
|
|
1155
1569
|
);
|
|
1156
1570
|
}
|
|
1157
1571
|
export {
|
|
1572
|
+
CameraCapture,
|
|
1158
1573
|
DropPane,
|
|
1159
1574
|
ImageEditor,
|
|
1160
1575
|
PickerOverlay,
|
|
@@ -1163,10 +1578,12 @@ export {
|
|
|
1163
1578
|
editImage,
|
|
1164
1579
|
flip,
|
|
1165
1580
|
flop,
|
|
1581
|
+
isCameraSupported,
|
|
1166
1582
|
output,
|
|
1167
1583
|
quality,
|
|
1168
1584
|
resize,
|
|
1169
1585
|
rotate,
|
|
1586
|
+
shouldOfferCamera,
|
|
1170
1587
|
transformUrl,
|
|
1171
1588
|
usePicker
|
|
1172
1589
|
};
|