@sparkstudio/storage-ui 1.0.34 → 1.0.35
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 +396 -219
- package/dist/index.d.cts +1 -10
- package/dist/index.d.ts +1 -10
- package/dist/index.js +460 -283
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -521,8 +521,15 @@ var import_react4 = require("react");
|
|
|
521
521
|
// src/components/FileIconCard.tsx
|
|
522
522
|
var import_react3 = require("react");
|
|
523
523
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
524
|
-
function
|
|
525
|
-
|
|
524
|
+
function useIsMobile(breakpoint = 640) {
|
|
525
|
+
const getValue = () => typeof window !== "undefined" ? window.innerWidth <= breakpoint : false;
|
|
526
|
+
const [isMobile, setIsMobile] = (0, import_react3.useState)(getValue);
|
|
527
|
+
(0, import_react3.useEffect)(() => {
|
|
528
|
+
const onResize = () => setIsMobile(getValue());
|
|
529
|
+
window.addEventListener("resize", onResize);
|
|
530
|
+
return () => window.removeEventListener("resize", onResize);
|
|
531
|
+
}, [breakpoint]);
|
|
532
|
+
return isMobile;
|
|
526
533
|
}
|
|
527
534
|
var FileIconCard = (props) => {
|
|
528
535
|
const {
|
|
@@ -532,17 +539,12 @@ var FileIconCard = (props) => {
|
|
|
532
539
|
onSelect,
|
|
533
540
|
onDeleteFile,
|
|
534
541
|
onClickFile,
|
|
535
|
-
icon,
|
|
536
|
-
iconHtml,
|
|
537
542
|
title,
|
|
538
543
|
className
|
|
539
544
|
} = props;
|
|
545
|
+
const isMobile = useIsMobile();
|
|
540
546
|
const [deleting, setDeleting] = (0, import_react3.useState)(false);
|
|
541
547
|
const [error, setError] = (0, import_react3.useState)(null);
|
|
542
|
-
const safeIconHtml = (0, import_react3.useMemo)(() => {
|
|
543
|
-
if (!iconHtml) return null;
|
|
544
|
-
return sanitizeIconHtml(iconHtml);
|
|
545
|
-
}, [iconHtml]);
|
|
546
548
|
const canDelete = !!onDeleteFile && !deleteDisabled && !deleting;
|
|
547
549
|
const handleClick = () => {
|
|
548
550
|
onSelect?.(file);
|
|
@@ -584,69 +586,115 @@ var FileIconCard = (props) => {
|
|
|
584
586
|
title: title ?? file.Name ?? "File",
|
|
585
587
|
style: {
|
|
586
588
|
display: "flex",
|
|
587
|
-
|
|
588
|
-
gap:
|
|
589
|
-
padding: 12,
|
|
589
|
+
flexDirection: "column",
|
|
590
|
+
gap: 10,
|
|
591
|
+
padding: isMobile ? 14 : 12,
|
|
590
592
|
border: `1px solid ${borderColor}`,
|
|
591
593
|
boxShadow,
|
|
592
|
-
borderRadius:
|
|
594
|
+
borderRadius: 16,
|
|
593
595
|
userSelect: "none",
|
|
594
596
|
cursor: "pointer",
|
|
595
|
-
background: "white"
|
|
597
|
+
background: "white",
|
|
598
|
+
minWidth: 0,
|
|
599
|
+
width: "100%",
|
|
600
|
+
boxSizing: "border-box"
|
|
596
601
|
},
|
|
597
602
|
children: [
|
|
598
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.
|
|
603
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
599
604
|
"div",
|
|
600
605
|
{
|
|
601
606
|
style: {
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
flex: "0 0 auto"
|
|
607
|
+
display: "flex",
|
|
608
|
+
alignItems: "center",
|
|
609
|
+
justifyContent: "space-between",
|
|
610
|
+
gap: 12,
|
|
611
|
+
minWidth: 0,
|
|
612
|
+
width: "100%"
|
|
609
613
|
},
|
|
610
|
-
children:
|
|
614
|
+
children: [
|
|
615
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
616
|
+
"div",
|
|
617
|
+
{
|
|
618
|
+
style: {
|
|
619
|
+
minWidth: 0,
|
|
620
|
+
flex: "1 1 auto",
|
|
621
|
+
overflow: "hidden"
|
|
622
|
+
},
|
|
623
|
+
children: [
|
|
624
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
625
|
+
"div",
|
|
626
|
+
{
|
|
627
|
+
style: {
|
|
628
|
+
fontWeight: 700,
|
|
629
|
+
fontSize: isMobile ? 15 : 14,
|
|
630
|
+
lineHeight: 1.2,
|
|
631
|
+
overflow: "hidden",
|
|
632
|
+
textOverflow: "ellipsis",
|
|
633
|
+
whiteSpace: "nowrap"
|
|
634
|
+
},
|
|
635
|
+
children: title ?? file.Name ?? "Untitled"
|
|
636
|
+
}
|
|
637
|
+
),
|
|
638
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
639
|
+
"div",
|
|
640
|
+
{
|
|
641
|
+
style: {
|
|
642
|
+
fontSize: 12,
|
|
643
|
+
opacity: 0.7,
|
|
644
|
+
marginTop: 4,
|
|
645
|
+
overflow: "hidden",
|
|
646
|
+
textOverflow: "ellipsis",
|
|
647
|
+
whiteSpace: "nowrap"
|
|
648
|
+
},
|
|
649
|
+
children: typeof file.FileSize === "number" ? formatBytes(file.FileSize) : ""
|
|
650
|
+
}
|
|
651
|
+
)
|
|
652
|
+
]
|
|
653
|
+
}
|
|
654
|
+
),
|
|
655
|
+
!deleteDisabled ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
656
|
+
"button",
|
|
657
|
+
{
|
|
658
|
+
type: "button",
|
|
659
|
+
onClick: handleDelete,
|
|
660
|
+
disabled: !canDelete,
|
|
661
|
+
"aria-label": deleting ? "Deleting file" : "Delete file",
|
|
662
|
+
title: deleting ? "Deleting..." : "Delete",
|
|
663
|
+
style: {
|
|
664
|
+
flex: "0 0 auto",
|
|
665
|
+
width: isMobile ? 40 : "auto",
|
|
666
|
+
height: isMobile ? 40 : "auto",
|
|
667
|
+
minWidth: isMobile ? 40 : 110,
|
|
668
|
+
border: "1px solid rgba(0,0,0,0.18)",
|
|
669
|
+
background: canDelete ? "white" : "rgba(0,0,0,0.04)",
|
|
670
|
+
borderRadius: 12,
|
|
671
|
+
padding: isMobile ? 0 : "8px 10px",
|
|
672
|
+
cursor: canDelete ? "pointer" : "not-allowed",
|
|
673
|
+
display: "flex",
|
|
674
|
+
alignItems: "center",
|
|
675
|
+
justifyContent: "center",
|
|
676
|
+
gap: 8,
|
|
677
|
+
boxSizing: "border-box"
|
|
678
|
+
},
|
|
679
|
+
children: [
|
|
680
|
+
deleting ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SmallSpinner, {}) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(TrashIcon, {}),
|
|
681
|
+
!isMobile ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { fontSize: 14, fontWeight: 600 }, children: deleting ? "Deleting\u2026" : "Delete" }) : null
|
|
682
|
+
]
|
|
683
|
+
}
|
|
684
|
+
) : null
|
|
685
|
+
]
|
|
611
686
|
}
|
|
612
687
|
),
|
|
613
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.
|
|
614
|
-
|
|
615
|
-
"div",
|
|
616
|
-
{
|
|
617
|
-
style: {
|
|
618
|
-
fontWeight: 600,
|
|
619
|
-
overflow: "hidden",
|
|
620
|
-
textOverflow: "ellipsis",
|
|
621
|
-
whiteSpace: "nowrap"
|
|
622
|
-
},
|
|
623
|
-
children: title ?? file.Name ?? "Untitled"
|
|
624
|
-
}
|
|
625
|
-
),
|
|
626
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { fontSize: 12, opacity: 0.7 }, children: typeof file.FileSize === "number" ? formatBytes(file.FileSize) : "" }),
|
|
627
|
-
error ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { fontSize: 12, color: "crimson", marginTop: 4 }, children: error }) : null
|
|
628
|
-
] }),
|
|
629
|
-
!deleteDisabled ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
630
|
-
"button",
|
|
688
|
+
error ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
689
|
+
"div",
|
|
631
690
|
{
|
|
632
|
-
type: "button",
|
|
633
|
-
onClick: handleDelete,
|
|
634
|
-
disabled: !canDelete,
|
|
635
|
-
"aria-label": "Delete file",
|
|
636
691
|
style: {
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
cursor: canDelete ? "pointer" : "not-allowed",
|
|
642
|
-
display: "flex",
|
|
643
|
-
alignItems: "center",
|
|
644
|
-
gap: 8
|
|
692
|
+
fontSize: 12,
|
|
693
|
+
color: "crimson",
|
|
694
|
+
marginTop: -2,
|
|
695
|
+
wordBreak: "break-word"
|
|
645
696
|
},
|
|
646
|
-
children:
|
|
647
|
-
deleting ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SmallSpinner, {}) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(TrashIcon, {}),
|
|
648
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { fontSize: 13 }, children: deleting ? "Deleting\u2026" : "Delete" })
|
|
649
|
-
]
|
|
697
|
+
children: error
|
|
650
698
|
}
|
|
651
699
|
) : null
|
|
652
700
|
]
|
|
@@ -665,33 +713,33 @@ function formatBytes(bytes) {
|
|
|
665
713
|
return `${v.toFixed(i === 0 ? 0 : 1)} ${units[i]}`;
|
|
666
714
|
}
|
|
667
715
|
function SmallSpinner() {
|
|
668
|
-
return /* @__PURE__ */ (0, import_jsx_runtime2.
|
|
669
|
-
"
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
animation: "fileIconSpin 0.8s linear infinite"
|
|
680
|
-
}
|
|
681
|
-
}
|
|
682
|
-
);
|
|
683
|
-
}
|
|
684
|
-
function DefaultFileIcon() {
|
|
685
|
-
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", "aria-hidden": true, children: [
|
|
716
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
|
|
717
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("style", { children: `
|
|
718
|
+
@keyframes fileIconSpin {
|
|
719
|
+
from {
|
|
720
|
+
transform: rotate(0deg);
|
|
721
|
+
}
|
|
722
|
+
to {
|
|
723
|
+
transform: rotate(360deg);
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
` }),
|
|
686
727
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
687
|
-
"
|
|
728
|
+
"span",
|
|
688
729
|
{
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
730
|
+
"aria-hidden": true,
|
|
731
|
+
style: {
|
|
732
|
+
width: 14,
|
|
733
|
+
height: 14,
|
|
734
|
+
borderRadius: "50%",
|
|
735
|
+
border: "2px solid rgba(0,0,0,0.2)",
|
|
736
|
+
borderTopColor: "rgba(0,0,0,0.7)",
|
|
737
|
+
display: "inline-block",
|
|
738
|
+
boxSizing: "border-box",
|
|
739
|
+
animation: "fileIconSpin 0.8s linear infinite"
|
|
740
|
+
}
|
|
692
741
|
}
|
|
693
|
-
)
|
|
694
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M14 3v4a1 1 0 0 0 1 1h4", stroke: "currentColor", strokeWidth: "2" })
|
|
742
|
+
)
|
|
695
743
|
] });
|
|
696
744
|
}
|
|
697
745
|
function TrashIcon() {
|
|
@@ -727,25 +775,29 @@ function TrashIcon() {
|
|
|
727
775
|
|
|
728
776
|
// src/components/FileIconGrid.tsx
|
|
729
777
|
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
778
|
+
function useIsMobile2(breakpoint = 640) {
|
|
779
|
+
const getValue = () => typeof window !== "undefined" ? window.innerWidth <= breakpoint : false;
|
|
780
|
+
const [isMobile, setIsMobile] = (0, import_react4.useState)(getValue);
|
|
781
|
+
(0, import_react4.useEffect)(() => {
|
|
782
|
+
const onResize = () => setIsMobile(getValue());
|
|
783
|
+
window.addEventListener("resize", onResize);
|
|
784
|
+
return () => window.removeEventListener("resize", onResize);
|
|
785
|
+
}, [breakpoint]);
|
|
786
|
+
return isMobile;
|
|
787
|
+
}
|
|
730
788
|
function FileIconGrid(props) {
|
|
731
|
-
const {
|
|
732
|
-
|
|
733
|
-
deleteDisabled,
|
|
734
|
-
onDeleted,
|
|
735
|
-
selectedId,
|
|
736
|
-
onSelect,
|
|
737
|
-
icon,
|
|
738
|
-
iconHtml,
|
|
739
|
-
className
|
|
740
|
-
} = props;
|
|
789
|
+
const { files, deleteDisabled, onDeleted, selectedId, onSelect, className } = props;
|
|
790
|
+
const isMobile = useIsMobile2();
|
|
741
791
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
742
792
|
"div",
|
|
743
793
|
{
|
|
744
794
|
className,
|
|
745
795
|
style: {
|
|
746
796
|
display: "grid",
|
|
747
|
-
gridTemplateColumns: "repeat(auto-fill, minmax(280px, 1fr))",
|
|
748
|
-
gap: 12
|
|
797
|
+
gridTemplateColumns: isMobile ? "minmax(0, 1fr)" : "repeat(auto-fill, minmax(280px, 1fr))",
|
|
798
|
+
gap: 12,
|
|
799
|
+
width: "100%",
|
|
800
|
+
minWidth: 0
|
|
749
801
|
},
|
|
750
802
|
children: files.map((f) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
751
803
|
FileIconCard,
|
|
@@ -754,9 +806,7 @@ function FileIconGrid(props) {
|
|
|
754
806
|
deleteDisabled,
|
|
755
807
|
onDeleteFile: onDeleted,
|
|
756
808
|
onSelect,
|
|
757
|
-
selected: selectedId === f.Id
|
|
758
|
-
icon,
|
|
759
|
-
iconHtml
|
|
809
|
+
selected: selectedId === f.Id
|
|
760
810
|
},
|
|
761
811
|
f.Id
|
|
762
812
|
))
|
|
@@ -766,6 +816,16 @@ function FileIconGrid(props) {
|
|
|
766
816
|
|
|
767
817
|
// src/components/ContainerIdGridPanel.tsx
|
|
768
818
|
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
819
|
+
function useIsMobile3(breakpoint = 640) {
|
|
820
|
+
const getValue = () => typeof window !== "undefined" ? window.innerWidth <= breakpoint : false;
|
|
821
|
+
const [isMobile, setIsMobile] = (0, import_react5.useState)(getValue);
|
|
822
|
+
(0, import_react5.useEffect)(() => {
|
|
823
|
+
const onResize = () => setIsMobile(getValue());
|
|
824
|
+
window.addEventListener("resize", onResize);
|
|
825
|
+
return () => window.removeEventListener("resize", onResize);
|
|
826
|
+
}, [breakpoint]);
|
|
827
|
+
return isMobile;
|
|
828
|
+
}
|
|
769
829
|
function ContainerIdGridPanel(props) {
|
|
770
830
|
const {
|
|
771
831
|
containerApiBaseUrl,
|
|
@@ -776,12 +836,11 @@ function ContainerIdGridPanel(props) {
|
|
|
776
836
|
multiple = true,
|
|
777
837
|
selectedId,
|
|
778
838
|
onSelect,
|
|
779
|
-
icon,
|
|
780
|
-
iconHtml,
|
|
781
839
|
deleteDisabled,
|
|
782
840
|
className,
|
|
783
841
|
onDeleted
|
|
784
842
|
} = props;
|
|
843
|
+
const isMobile = useIsMobile3();
|
|
785
844
|
const sdkDb = (0, import_react5.useMemo)(
|
|
786
845
|
() => new SparkStudioStorageSDK(containerApiBaseUrl),
|
|
787
846
|
[containerApiBaseUrl]
|
|
@@ -822,12 +881,11 @@ function ContainerIdGridPanel(props) {
|
|
|
822
881
|
if (cancelled) return;
|
|
823
882
|
const map = /* @__PURE__ */ new Map();
|
|
824
883
|
for (const r of results) if (r?.Id) map.set(r.Id, r);
|
|
825
|
-
setFiles(
|
|
826
|
-
ids.map((id) => map.get(id)).filter(Boolean)
|
|
827
|
-
);
|
|
884
|
+
setFiles(ids.map((id) => map.get(id)).filter(Boolean));
|
|
828
885
|
} catch (e) {
|
|
829
|
-
if (!cancelled)
|
|
886
|
+
if (!cancelled) {
|
|
830
887
|
setErrMsg(e instanceof Error ? e.message : "Failed to load files");
|
|
888
|
+
}
|
|
831
889
|
} finally {
|
|
832
890
|
if (!cancelled) setLoading(false);
|
|
833
891
|
}
|
|
@@ -914,106 +972,149 @@ function ContainerIdGridPanel(props) {
|
|
|
914
972
|
if (!list || list.length === 0) return;
|
|
915
973
|
startUploadsIfNeeded(list);
|
|
916
974
|
};
|
|
917
|
-
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
e.preventDefault();
|
|
932
|
-
e.stopPropagation();
|
|
933
|
-
setDragOver(true);
|
|
934
|
-
},
|
|
935
|
-
onDragLeave: (e) => {
|
|
936
|
-
e.preventDefault();
|
|
937
|
-
e.stopPropagation();
|
|
938
|
-
setDragOver(false);
|
|
939
|
-
},
|
|
940
|
-
onDrop,
|
|
941
|
-
style: {
|
|
942
|
-
position: "relative",
|
|
943
|
-
borderRadius: 14,
|
|
944
|
-
border: `2px dashed ${dragOver ? "rgba(13,110,253,0.9)" : "rgba(0,0,0,0.15)"}`,
|
|
945
|
-
background: dragOver ? "rgba(13,110,253,0.06)" : "transparent",
|
|
946
|
-
padding: 12
|
|
947
|
-
},
|
|
948
|
-
children: [
|
|
949
|
-
dragOver ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
950
|
-
"div",
|
|
951
|
-
{
|
|
952
|
-
style: {
|
|
953
|
-
position: "absolute",
|
|
954
|
-
inset: 0,
|
|
955
|
-
borderRadius: 14,
|
|
956
|
-
display: "grid",
|
|
957
|
-
placeItems: "center",
|
|
958
|
-
pointerEvents: "none",
|
|
959
|
-
background: "rgba(13,110,253,0.10)",
|
|
960
|
-
fontWeight: 800
|
|
961
|
-
},
|
|
962
|
-
children: "Drop files to upload"
|
|
963
|
-
}
|
|
964
|
-
) : null,
|
|
965
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
975
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
976
|
+
"div",
|
|
977
|
+
{
|
|
978
|
+
className,
|
|
979
|
+
style: {
|
|
980
|
+
display: "grid",
|
|
981
|
+
gap: 12,
|
|
982
|
+
width: "100%",
|
|
983
|
+
minWidth: 0
|
|
984
|
+
},
|
|
985
|
+
children: [
|
|
986
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { minWidth: 0 }, children: [
|
|
987
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(UploadProgressList, { uploads }),
|
|
988
|
+
errMsg ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
966
989
|
"div",
|
|
967
990
|
{
|
|
968
991
|
style: {
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
992
|
+
fontSize: 12,
|
|
993
|
+
color: "crimson",
|
|
994
|
+
marginTop: 6,
|
|
995
|
+
wordBreak: "break-word"
|
|
972
996
|
},
|
|
973
|
-
children:
|
|
974
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { fontWeight: 700 }, children: "Files" }),
|
|
975
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
976
|
-
"button",
|
|
977
|
-
{
|
|
978
|
-
type: "button",
|
|
979
|
-
onClick: openPicker,
|
|
980
|
-
style: {
|
|
981
|
-
border: "1px solid rgba(0,0,0,0.18)",
|
|
982
|
-
background: "white",
|
|
983
|
-
borderRadius: 10,
|
|
984
|
-
padding: "8px 10px",
|
|
985
|
-
cursor: "pointer",
|
|
986
|
-
fontWeight: 600
|
|
987
|
-
},
|
|
988
|
-
children: "Browse\u2026"
|
|
989
|
-
}
|
|
990
|
-
)
|
|
991
|
-
]
|
|
997
|
+
children: errMsg
|
|
992
998
|
}
|
|
993
|
-
)
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
{
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
999
|
+
) : null
|
|
1000
|
+
] }),
|
|
1001
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
1002
|
+
"div",
|
|
1003
|
+
{
|
|
1004
|
+
onDragEnter: (e) => {
|
|
1005
|
+
e.preventDefault();
|
|
1006
|
+
e.stopPropagation();
|
|
1007
|
+
setDragOver(true);
|
|
1008
|
+
},
|
|
1009
|
+
onDragOver: (e) => {
|
|
1010
|
+
e.preventDefault();
|
|
1011
|
+
e.stopPropagation();
|
|
1012
|
+
setDragOver(true);
|
|
1013
|
+
},
|
|
1014
|
+
onDragLeave: (e) => {
|
|
1015
|
+
e.preventDefault();
|
|
1016
|
+
e.stopPropagation();
|
|
1017
|
+
setDragOver(false);
|
|
1018
|
+
},
|
|
1019
|
+
onDrop,
|
|
1020
|
+
style: {
|
|
1021
|
+
position: "relative",
|
|
1022
|
+
borderRadius: 14,
|
|
1023
|
+
border: `2px dashed ${dragOver ? "rgba(13,110,253,0.9)" : "rgba(0,0,0,0.15)"}`,
|
|
1024
|
+
background: dragOver ? "rgba(13,110,253,0.06)" : "transparent",
|
|
1025
|
+
padding: isMobile ? 10 : 12,
|
|
1026
|
+
width: "100%",
|
|
1027
|
+
minWidth: 0,
|
|
1028
|
+
overflow: "hidden",
|
|
1029
|
+
boxSizing: "border-box"
|
|
1030
|
+
},
|
|
1031
|
+
children: [
|
|
1032
|
+
dragOver ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1033
|
+
"div",
|
|
1034
|
+
{
|
|
1035
|
+
style: {
|
|
1036
|
+
position: "absolute",
|
|
1037
|
+
inset: 0,
|
|
1038
|
+
borderRadius: 14,
|
|
1039
|
+
display: "grid",
|
|
1040
|
+
placeItems: "center",
|
|
1041
|
+
pointerEvents: "none",
|
|
1042
|
+
background: "rgba(13,110,253,0.10)",
|
|
1043
|
+
fontWeight: 800,
|
|
1044
|
+
textAlign: "center",
|
|
1045
|
+
padding: 12
|
|
1046
|
+
},
|
|
1047
|
+
children: "Drop files to upload"
|
|
1048
|
+
}
|
|
1049
|
+
) : null,
|
|
1050
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
1051
|
+
"div",
|
|
1052
|
+
{
|
|
1053
|
+
style: {
|
|
1054
|
+
display: "flex",
|
|
1055
|
+
flexDirection: isMobile ? "column" : "row",
|
|
1056
|
+
alignItems: isMobile ? "stretch" : "center",
|
|
1057
|
+
justifyContent: "space-between",
|
|
1058
|
+
gap: 10,
|
|
1059
|
+
marginBottom: 10,
|
|
1060
|
+
minWidth: 0
|
|
1061
|
+
},
|
|
1062
|
+
children: [
|
|
1063
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1064
|
+
"div",
|
|
1065
|
+
{
|
|
1066
|
+
style: {
|
|
1067
|
+
fontWeight: 700,
|
|
1068
|
+
minWidth: 0
|
|
1069
|
+
},
|
|
1070
|
+
children: "Files"
|
|
1071
|
+
}
|
|
1072
|
+
),
|
|
1073
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1074
|
+
"button",
|
|
1075
|
+
{
|
|
1076
|
+
type: "button",
|
|
1077
|
+
onClick: openPicker,
|
|
1078
|
+
style: {
|
|
1079
|
+
border: "1px solid rgba(0,0,0,0.18)",
|
|
1080
|
+
background: "white",
|
|
1081
|
+
borderRadius: 10,
|
|
1082
|
+
padding: isMobile ? "10px 12px" : "8px 10px",
|
|
1083
|
+
cursor: "pointer",
|
|
1084
|
+
fontWeight: 600,
|
|
1085
|
+
width: isMobile ? "100%" : "auto",
|
|
1086
|
+
maxWidth: "100%",
|
|
1087
|
+
boxSizing: "border-box"
|
|
1088
|
+
},
|
|
1089
|
+
children: "Browse\u2026"
|
|
1090
|
+
}
|
|
1091
|
+
)
|
|
1092
|
+
]
|
|
1093
|
+
}
|
|
1094
|
+
),
|
|
1095
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { width: "100%", minWidth: 0, overflow: "hidden" }, children: loading ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1096
|
+
"div",
|
|
1097
|
+
{
|
|
1098
|
+
className: "d-flex justify-content-center align-items-center",
|
|
1099
|
+
style: { minHeight: 120 },
|
|
1100
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "spinner-border text-secondary", role: "status" })
|
|
1101
|
+
}
|
|
1102
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1103
|
+
FileIconGrid,
|
|
1104
|
+
{
|
|
1105
|
+
files,
|
|
1106
|
+
deleteDisabled,
|
|
1107
|
+
onDeleted: handleDelete,
|
|
1108
|
+
selectedId,
|
|
1109
|
+
onSelect
|
|
1110
|
+
}
|
|
1111
|
+
) })
|
|
1112
|
+
]
|
|
1113
|
+
}
|
|
1114
|
+
)
|
|
1115
|
+
]
|
|
1116
|
+
}
|
|
1117
|
+
);
|
|
1017
1118
|
}
|
|
1018
1119
|
|
|
1019
1120
|
// src/components/ContainerUploadPanel.tsx
|
|
@@ -1592,8 +1693,6 @@ function FileGridUploadPanel(props) {
|
|
|
1592
1693
|
multiple = true,
|
|
1593
1694
|
selectedId,
|
|
1594
1695
|
onSelect,
|
|
1595
|
-
icon,
|
|
1596
|
-
iconHtml,
|
|
1597
1696
|
deleteDisabled
|
|
1598
1697
|
} = props;
|
|
1599
1698
|
const { containers, setContainers, reload, loading } = UseContainers({
|
|
@@ -1732,9 +1831,7 @@ function FileGridUploadPanel(props) {
|
|
|
1732
1831
|
deleteDisabled,
|
|
1733
1832
|
onDeleted: handleDeleteFile,
|
|
1734
1833
|
selectedId,
|
|
1735
|
-
onSelect
|
|
1736
|
-
icon,
|
|
1737
|
-
iconHtml
|
|
1834
|
+
onSelect
|
|
1738
1835
|
}
|
|
1739
1836
|
)
|
|
1740
1837
|
]
|
|
@@ -1940,6 +2037,7 @@ function SingleImageUploadEditor(props) {
|
|
|
1940
2037
|
const pendingContainerRef = (0, import_react13.useRef)(null);
|
|
1941
2038
|
const [isDeleting, setIsDeleting] = (0, import_react13.useState)(false);
|
|
1942
2039
|
const [isLoadingImage, setIsLoadingImage] = (0, import_react13.useState)(false);
|
|
2040
|
+
const [isPickingFile, setIsPickingFile] = (0, import_react13.useState)(false);
|
|
1943
2041
|
const [currentImage, setCurrentImage] = (0, import_react13.useState)(null);
|
|
1944
2042
|
const sdkDb = (0, import_react13.useMemo)(
|
|
1945
2043
|
() => new SparkStudioStorageSDK(containerApiBaseUrl),
|
|
@@ -1949,7 +2047,6 @@ function SingleImageUploadEditor(props) {
|
|
|
1949
2047
|
() => new SparkStudioStorageSDK(storageApiBaseUrl),
|
|
1950
2048
|
[storageApiBaseUrl]
|
|
1951
2049
|
);
|
|
1952
|
-
const busy = disabled || isDeleting || isLoadingImage;
|
|
1953
2050
|
function logError(context, err) {
|
|
1954
2051
|
console.error(`[SingleImageUploadEditor] ${context}`, err);
|
|
1955
2052
|
}
|
|
@@ -1958,6 +2055,12 @@ function SingleImageUploadEditor(props) {
|
|
|
1958
2055
|
setError(null);
|
|
1959
2056
|
pendingContainerRef.current = null;
|
|
1960
2057
|
}
|
|
2058
|
+
function getErrorMessage(err, fallback) {
|
|
2059
|
+
return err instanceof Error ? err.message : fallback;
|
|
2060
|
+
}
|
|
2061
|
+
function isImageFile(file) {
|
|
2062
|
+
return file.type.startsWith("image/");
|
|
2063
|
+
}
|
|
1961
2064
|
(0, import_react13.useEffect)(() => {
|
|
1962
2065
|
let cancelled = false;
|
|
1963
2066
|
async function loadImage() {
|
|
@@ -1990,12 +2093,6 @@ function SingleImageUploadEditor(props) {
|
|
|
1990
2093
|
cancelled = true;
|
|
1991
2094
|
};
|
|
1992
2095
|
}, [value, sdkDb]);
|
|
1993
|
-
function getErrorMessage(err, fallback) {
|
|
1994
|
-
return err instanceof Error ? err.message : fallback;
|
|
1995
|
-
}
|
|
1996
|
-
function isImageFile(file) {
|
|
1997
|
-
return file.type.startsWith("image/");
|
|
1998
|
-
}
|
|
1999
2096
|
async function createContainerForFile(file) {
|
|
2000
2097
|
const contentType = file.type || "application/octet-stream";
|
|
2001
2098
|
return sdkDb.container.CreateFileContainer(
|
|
@@ -2036,6 +2133,7 @@ function SingleImageUploadEditor(props) {
|
|
|
2036
2133
|
onChange?.(uploaded.Id);
|
|
2037
2134
|
}
|
|
2038
2135
|
pendingContainerRef.current = null;
|
|
2136
|
+
setIsPickingFile(false);
|
|
2039
2137
|
},
|
|
2040
2138
|
onUploadError: async (_file, err) => {
|
|
2041
2139
|
const pending = pendingContainerRef.current;
|
|
@@ -2043,10 +2141,14 @@ function SingleImageUploadEditor(props) {
|
|
|
2043
2141
|
try {
|
|
2044
2142
|
await sdkDb.container.DeleteContainer(pending.Id);
|
|
2045
2143
|
} catch (cleanupErr) {
|
|
2046
|
-
logError(
|
|
2144
|
+
logError(
|
|
2145
|
+
"Failed to cleanup pending container after upload error",
|
|
2146
|
+
cleanupErr
|
|
2147
|
+
);
|
|
2047
2148
|
}
|
|
2048
2149
|
}
|
|
2049
2150
|
pendingContainerRef.current = null;
|
|
2151
|
+
setIsPickingFile(false);
|
|
2050
2152
|
logError("Upload failed", err);
|
|
2051
2153
|
setCurrentImage(null);
|
|
2052
2154
|
setError(getErrorMessage(err, "Upload failed"));
|
|
@@ -2054,7 +2156,10 @@ function SingleImageUploadEditor(props) {
|
|
|
2054
2156
|
});
|
|
2055
2157
|
const visibleUploads = uploads.filter((u) => u.status !== "success");
|
|
2056
2158
|
const activeUpload = visibleUploads[0];
|
|
2159
|
+
const isUploading = !!activeUpload;
|
|
2057
2160
|
const showBottomProgress = !!activeUpload;
|
|
2161
|
+
const busy = disabled || isDeleting || isLoadingImage || isPickingFile || isUploading;
|
|
2162
|
+
const busyText = isUploading ? "Uploading..." : isDeleting ? "Removing..." : isLoadingImage ? "Loading..." : isPickingFile ? "Preparing..." : "";
|
|
2058
2163
|
async function replaceWithFile(file) {
|
|
2059
2164
|
if (busy) {
|
|
2060
2165
|
return;
|
|
@@ -2065,12 +2170,15 @@ function SingleImageUploadEditor(props) {
|
|
|
2065
2170
|
}
|
|
2066
2171
|
try {
|
|
2067
2172
|
setError(null);
|
|
2173
|
+
setIsPickingFile(true);
|
|
2068
2174
|
if (value) {
|
|
2069
2175
|
setIsDeleting(true);
|
|
2070
2176
|
try {
|
|
2071
2177
|
await deleteContainerFile(value);
|
|
2072
2178
|
} catch (err) {
|
|
2073
2179
|
logError(`Failed to delete existing container '${String(value)}'`, err);
|
|
2180
|
+
} finally {
|
|
2181
|
+
setIsDeleting(false);
|
|
2074
2182
|
}
|
|
2075
2183
|
setCurrentImage(null);
|
|
2076
2184
|
onChange?.(null);
|
|
@@ -2082,7 +2190,7 @@ function SingleImageUploadEditor(props) {
|
|
|
2082
2190
|
logError("Failed to replace image", err);
|
|
2083
2191
|
resetState();
|
|
2084
2192
|
setError(getErrorMessage(err, "Failed to replace image."));
|
|
2085
|
-
|
|
2193
|
+
setIsPickingFile(false);
|
|
2086
2194
|
setIsDeleting(false);
|
|
2087
2195
|
}
|
|
2088
2196
|
}
|
|
@@ -2124,6 +2232,29 @@ function SingleImageUploadEditor(props) {
|
|
|
2124
2232
|
setIsDeleting(false);
|
|
2125
2233
|
}
|
|
2126
2234
|
}
|
|
2235
|
+
function SpinnerLabel(props2) {
|
|
2236
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
|
|
2237
|
+
"span",
|
|
2238
|
+
{
|
|
2239
|
+
style: {
|
|
2240
|
+
display: "inline-flex",
|
|
2241
|
+
alignItems: "center",
|
|
2242
|
+
gap: 8
|
|
2243
|
+
},
|
|
2244
|
+
children: [
|
|
2245
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
2246
|
+
"span",
|
|
2247
|
+
{
|
|
2248
|
+
className: "spinner-border spinner-border-sm",
|
|
2249
|
+
role: "status",
|
|
2250
|
+
"aria-hidden": "true"
|
|
2251
|
+
}
|
|
2252
|
+
),
|
|
2253
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { children: props2.text })
|
|
2254
|
+
]
|
|
2255
|
+
}
|
|
2256
|
+
);
|
|
2257
|
+
}
|
|
2127
2258
|
return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { style: { display: "grid", gap: 8 }, children: [
|
|
2128
2259
|
error ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { style: { fontSize: 12, color: "crimson" }, children: error }) : null,
|
|
2129
2260
|
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
|
|
@@ -2209,7 +2340,9 @@ function SingleImageUploadEditor(props) {
|
|
|
2209
2340
|
width: "100%",
|
|
2210
2341
|
height,
|
|
2211
2342
|
objectFit: "contain",
|
|
2212
|
-
background: "rgba(0,0,0,0.02)"
|
|
2343
|
+
background: "rgba(0,0,0,0.02)",
|
|
2344
|
+
opacity: busy ? 0.45 : 1,
|
|
2345
|
+
transition: "opacity 120ms ease"
|
|
2213
2346
|
}
|
|
2214
2347
|
}
|
|
2215
2348
|
)
|
|
@@ -2240,9 +2373,10 @@ function SingleImageUploadEditor(props) {
|
|
|
2240
2373
|
padding: "8px 10px",
|
|
2241
2374
|
cursor: busy ? "not-allowed" : "pointer",
|
|
2242
2375
|
fontWeight: 600,
|
|
2243
|
-
boxShadow: "0 2px 8px rgba(0,0,0,0.08)"
|
|
2376
|
+
boxShadow: "0 2px 8px rgba(0,0,0,0.08)",
|
|
2377
|
+
minWidth: 110
|
|
2244
2378
|
},
|
|
2245
|
-
children: "Browse\u2026"
|
|
2379
|
+
children: isUploading || isPickingFile ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(SpinnerLabel, { text: "Uploading..." }) : "Browse\u2026"
|
|
2246
2380
|
}
|
|
2247
2381
|
),
|
|
2248
2382
|
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
@@ -2259,9 +2393,10 @@ function SingleImageUploadEditor(props) {
|
|
|
2259
2393
|
padding: "8px 10px",
|
|
2260
2394
|
cursor: busy ? "not-allowed" : "pointer",
|
|
2261
2395
|
fontWeight: 600,
|
|
2262
|
-
boxShadow: "0 2px 8px rgba(0,0,0,0.08)"
|
|
2396
|
+
boxShadow: "0 2px 8px rgba(0,0,0,0.08)",
|
|
2397
|
+
minWidth: 110
|
|
2263
2398
|
},
|
|
2264
|
-
children: "Remove"
|
|
2399
|
+
children: isDeleting ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(SpinnerLabel, { text: "Removing..." }) : "Remove"
|
|
2265
2400
|
}
|
|
2266
2401
|
)
|
|
2267
2402
|
]
|
|
@@ -2301,17 +2436,59 @@ function SingleImageUploadEditor(props) {
|
|
|
2301
2436
|
borderRadius: 10,
|
|
2302
2437
|
padding: "10px 16px",
|
|
2303
2438
|
cursor: busy ? "not-allowed" : "pointer",
|
|
2304
|
-
fontWeight: 600
|
|
2439
|
+
fontWeight: 600,
|
|
2440
|
+
minWidth: 140
|
|
2305
2441
|
},
|
|
2306
|
-
children: "Browse\u2026"
|
|
2442
|
+
children: isUploading || isPickingFile ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(SpinnerLabel, { text: "Uploading..." }) : isLoadingImage ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(SpinnerLabel, { text: "Loading..." }) : "Browse\u2026"
|
|
2307
2443
|
}
|
|
2308
2444
|
),
|
|
2309
|
-
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { style: { fontSize: 13, opacity: 0.7 }, children: "Drag and drop an image here" })
|
|
2445
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { style: { fontSize: 13, opacity: 0.7 }, children: busyText || "Drag and drop an image here" })
|
|
2310
2446
|
]
|
|
2311
2447
|
}
|
|
2312
2448
|
)
|
|
2313
2449
|
}
|
|
2314
2450
|
),
|
|
2451
|
+
busy ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
2452
|
+
"div",
|
|
2453
|
+
{
|
|
2454
|
+
style: {
|
|
2455
|
+
position: "absolute",
|
|
2456
|
+
inset: 0,
|
|
2457
|
+
display: "grid",
|
|
2458
|
+
placeItems: "center",
|
|
2459
|
+
background: "rgba(255,255,255,0.55)",
|
|
2460
|
+
backdropFilter: "blur(1px)",
|
|
2461
|
+
zIndex: 5
|
|
2462
|
+
},
|
|
2463
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
|
|
2464
|
+
"div",
|
|
2465
|
+
{
|
|
2466
|
+
style: {
|
|
2467
|
+
display: "inline-flex",
|
|
2468
|
+
alignItems: "center",
|
|
2469
|
+
gap: 10,
|
|
2470
|
+
background: "white",
|
|
2471
|
+
border: "1px solid rgba(0,0,0,0.08)",
|
|
2472
|
+
borderRadius: 12,
|
|
2473
|
+
padding: "10px 14px",
|
|
2474
|
+
boxShadow: "0 8px 24px rgba(0,0,0,0.12)",
|
|
2475
|
+
fontWeight: 600
|
|
2476
|
+
},
|
|
2477
|
+
children: [
|
|
2478
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
2479
|
+
"span",
|
|
2480
|
+
{
|
|
2481
|
+
className: "spinner-border spinner-border-sm",
|
|
2482
|
+
role: "status",
|
|
2483
|
+
"aria-hidden": "true"
|
|
2484
|
+
}
|
|
2485
|
+
),
|
|
2486
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { children: busyText || "Working..." })
|
|
2487
|
+
]
|
|
2488
|
+
}
|
|
2489
|
+
)
|
|
2490
|
+
}
|
|
2491
|
+
) : null,
|
|
2315
2492
|
showBottomProgress ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
2316
2493
|
"div",
|
|
2317
2494
|
{
|
|
@@ -2322,7 +2499,7 @@ function SingleImageUploadEditor(props) {
|
|
|
2322
2499
|
bottom: 0,
|
|
2323
2500
|
height: 4,
|
|
2324
2501
|
background: "rgba(0,0,0,0.08)",
|
|
2325
|
-
zIndex:
|
|
2502
|
+
zIndex: 6
|
|
2326
2503
|
},
|
|
2327
2504
|
title: activeUpload.file.name,
|
|
2328
2505
|
children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|