@sparkstudio/storage-ui 1.0.14 → 1.0.15
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 +51 -26
- package/dist/index.d.cts +15 -13
- package/dist/index.d.ts +15 -13
- package/dist/index.js +51 -26
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -478,6 +478,14 @@ var UploadProgressList = ({
|
|
|
478
478
|
},
|
|
479
479
|
children: [
|
|
480
480
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("i", { className: "bi bi-file-earmark fs-2" }),
|
|
481
|
+
u.status === "uploading" && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
482
|
+
"div",
|
|
483
|
+
{
|
|
484
|
+
className: "position-absolute top-50 start-50 translate-middle",
|
|
485
|
+
style: { pointerEvents: "none" },
|
|
486
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "spinner-border spinner-border-sm text-primary" })
|
|
487
|
+
}
|
|
488
|
+
),
|
|
481
489
|
u.status === "error" && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger", children: "!" })
|
|
482
490
|
]
|
|
483
491
|
}
|
|
@@ -531,9 +539,11 @@ var DesktopFileIcon = ({
|
|
|
531
539
|
null
|
|
532
540
|
);
|
|
533
541
|
const [isHovered, setIsHovered] = (0, import_react4.useState)(false);
|
|
542
|
+
const [isDeleting, setIsDeleting] = (0, import_react4.useState)(false);
|
|
534
543
|
const iconRef = (0, import_react4.useRef)(null);
|
|
535
544
|
const menuRef = (0, import_react4.useRef)(null);
|
|
536
545
|
const handleDoubleClick = () => {
|
|
546
|
+
if (isDeleting) return;
|
|
537
547
|
if (onOpen) {
|
|
538
548
|
onOpen();
|
|
539
549
|
return;
|
|
@@ -550,13 +560,14 @@ var DesktopFileIcon = ({
|
|
|
550
560
|
}
|
|
551
561
|
};
|
|
552
562
|
const handleContextMenu = (e) => {
|
|
563
|
+
if (isDeleting) return;
|
|
553
564
|
e.preventDefault();
|
|
554
565
|
setContextMenuPos({ x: e.clientX, y: e.clientY });
|
|
555
566
|
};
|
|
556
567
|
const closeMenu = () => setContextMenuPos(null);
|
|
557
568
|
const handleDownload = () => {
|
|
558
569
|
closeMenu();
|
|
559
|
-
if (!downloadUrl) return;
|
|
570
|
+
if (!downloadUrl || isDeleting) return;
|
|
560
571
|
const a = document.createElement("a");
|
|
561
572
|
a.href = downloadUrl;
|
|
562
573
|
a.download = name ?? "";
|
|
@@ -568,16 +579,22 @@ var DesktopFileIcon = ({
|
|
|
568
579
|
};
|
|
569
580
|
const handleCopyUrl = async () => {
|
|
570
581
|
closeMenu();
|
|
571
|
-
if (!downloadUrl) return;
|
|
582
|
+
if (!downloadUrl || isDeleting) return;
|
|
572
583
|
try {
|
|
573
584
|
await navigator.clipboard?.writeText(downloadUrl);
|
|
574
585
|
} catch (err) {
|
|
575
586
|
console.error("Failed to copy URL", err);
|
|
576
587
|
}
|
|
577
588
|
};
|
|
578
|
-
const handleDelete = () => {
|
|
589
|
+
const handleDelete = async () => {
|
|
579
590
|
closeMenu();
|
|
580
|
-
onDelete
|
|
591
|
+
if (!onDelete) return;
|
|
592
|
+
try {
|
|
593
|
+
setIsDeleting(true);
|
|
594
|
+
await Promise.resolve(onDelete());
|
|
595
|
+
} catch (err) {
|
|
596
|
+
console.error("Delete failed", err);
|
|
597
|
+
}
|
|
581
598
|
};
|
|
582
599
|
const formattedSize = typeof sizeBytes === "number" ? `${(sizeBytes / 1024).toFixed(1)} KB` : void 0;
|
|
583
600
|
(0, import_react4.useEffect)(() => {
|
|
@@ -602,12 +619,15 @@ var DesktopFileIcon = ({
|
|
|
602
619
|
"div",
|
|
603
620
|
{
|
|
604
621
|
ref: iconRef,
|
|
605
|
-
className: "d-flex flex-column align-items-center rounded-3 p-1 " + (isHovered || contextMenuPos ? "bg-light border" : ""),
|
|
622
|
+
className: "d-flex flex-column align-items-center rounded-3 p-1 " + (isHovered || contextMenuPos ? "bg-light border-primary" : "border-transparent"),
|
|
606
623
|
style: {
|
|
607
624
|
width: 96,
|
|
608
|
-
cursor: "pointer",
|
|
625
|
+
cursor: isDeleting ? "default" : "pointer",
|
|
609
626
|
userSelect: "none",
|
|
610
|
-
transition: "background-color 0.1s ease, border-color 0.1s ease"
|
|
627
|
+
transition: "background-color 0.1s ease, border-color 0.1s ease, opacity 0.1s ease",
|
|
628
|
+
opacity: isDeleting ? 0.6 : 1,
|
|
629
|
+
borderWidth: 1,
|
|
630
|
+
borderStyle: "solid"
|
|
611
631
|
},
|
|
612
632
|
onDoubleClick: handleDoubleClick,
|
|
613
633
|
onContextMenu: handleContextMenu,
|
|
@@ -620,15 +640,18 @@ var DesktopFileIcon = ({
|
|
|
620
640
|
onMouseEnter: () => setIsHovered(true),
|
|
621
641
|
onMouseLeave: () => setIsHovered(false),
|
|
622
642
|
children: [
|
|
623
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.
|
|
643
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
624
644
|
"div",
|
|
625
645
|
{
|
|
626
|
-
className: "bg-white border rounded-3 d-flex align-items-center justify-content-center mb-1 shadow-sm",
|
|
646
|
+
className: "bg-white border rounded-3 d-flex align-items-center justify-content-center mb-1 shadow-sm position-relative",
|
|
627
647
|
style: {
|
|
628
648
|
width: 64,
|
|
629
649
|
height: 64
|
|
630
650
|
},
|
|
631
|
-
children:
|
|
651
|
+
children: [
|
|
652
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("i", { className: "bi bi-file-earmark fs-2" }),
|
|
653
|
+
isDeleting && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "position-absolute top-50 start-50 translate-middle", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "spinner-border spinner-border-sm text-danger" }) })
|
|
654
|
+
]
|
|
632
655
|
}
|
|
633
656
|
),
|
|
634
657
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
@@ -643,7 +666,7 @@ var DesktopFileIcon = ({
|
|
|
643
666
|
]
|
|
644
667
|
}
|
|
645
668
|
),
|
|
646
|
-
contextMenuPos && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
669
|
+
contextMenuPos && !isDeleting && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
647
670
|
"div",
|
|
648
671
|
{
|
|
649
672
|
ref: menuRef,
|
|
@@ -752,6 +775,7 @@ var UploadContainer = ({
|
|
|
752
775
|
className: "w-100",
|
|
753
776
|
style: { minHeight: "260px", alignItems: "stretch" },
|
|
754
777
|
children: [
|
|
778
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "w-100 mb-3", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "d-flex flex-column flex-md-row align-items-start align-items-md-center justify-content-between gap-3", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "flex-grow-1 w-100", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(UploadProgressList, { uploads }) }) }) }),
|
|
755
779
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
756
780
|
"div",
|
|
757
781
|
{
|
|
@@ -761,16 +785,15 @@ var UploadContainer = ({
|
|
|
761
785
|
DesktopFileIcon,
|
|
762
786
|
{
|
|
763
787
|
name: file.Name,
|
|
764
|
-
sizeBytes:
|
|
765
|
-
downloadUrl: file.PublicUrl
|
|
788
|
+
sizeBytes: file.FileSize,
|
|
789
|
+
downloadUrl: file.PublicUrl,
|
|
766
790
|
onOpen: () => handleExistingFileOpen(file),
|
|
767
|
-
onDelete:
|
|
791
|
+
onDelete: () => onDeleteFile?.(file)
|
|
768
792
|
},
|
|
769
793
|
file.Id
|
|
770
794
|
))
|
|
771
795
|
}
|
|
772
|
-
)
|
|
773
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "w-100 mt-3", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "d-flex flex-column flex-md-row align-items-start align-items-md-center justify-content-between gap-3", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "flex-grow-1 w-100", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(UploadProgressList, { uploads }) }) }) })
|
|
796
|
+
)
|
|
774
797
|
]
|
|
775
798
|
}
|
|
776
799
|
);
|
|
@@ -802,6 +825,7 @@ function UseContainers({ apiBaseUrl, parentId }) {
|
|
|
802
825
|
}, [apiBaseUrl, parentId]);
|
|
803
826
|
return {
|
|
804
827
|
containers,
|
|
828
|
+
setContainers,
|
|
805
829
|
loading,
|
|
806
830
|
error,
|
|
807
831
|
reload: load
|
|
@@ -815,17 +839,18 @@ var ContainerUploadPanel = ({
|
|
|
815
839
|
storageApiBaseUrl,
|
|
816
840
|
parentContainerId
|
|
817
841
|
}) => {
|
|
818
|
-
const { containers, reload, loading } = UseContainers({
|
|
842
|
+
const { containers, setContainers, reload, loading } = UseContainers({
|
|
819
843
|
apiBaseUrl: containerApiBaseUrl,
|
|
820
844
|
parentId: parentContainerId
|
|
821
845
|
});
|
|
822
846
|
const getPresignedUrl = async (file) => {
|
|
823
847
|
const sdkDb = new SparkStudioStorageSDK(containerApiBaseUrl);
|
|
824
848
|
const sdkS3 = new SparkStudioStorageSDK(storageApiBaseUrl);
|
|
849
|
+
const contentType = file.type || "application/octet-stream";
|
|
825
850
|
const containerDTO = await sdkDb.container.CreateFileContainer(
|
|
826
851
|
file.name,
|
|
827
852
|
file.size,
|
|
828
|
-
encodeURIComponent(
|
|
853
|
+
encodeURIComponent(contentType)
|
|
829
854
|
);
|
|
830
855
|
async function getPresignedUrlWithRetry(container, attempts = 3) {
|
|
831
856
|
let lastError;
|
|
@@ -850,13 +875,6 @@ var ContainerUploadPanel = ({
|
|
|
850
875
|
const handleUploadError = (file, err) => {
|
|
851
876
|
console.error("Upload failed:", file.name, err);
|
|
852
877
|
};
|
|
853
|
-
const handleOnDeleteFile = async (file) => {
|
|
854
|
-
const sdkDb = new SparkStudioStorageSDK(containerApiBaseUrl);
|
|
855
|
-
const sdkS3 = new SparkStudioStorageSDK(storageApiBaseUrl);
|
|
856
|
-
await sdkDb.container.DeleteContainer(file.Id);
|
|
857
|
-
await sdkS3.s3.DeleteS3(file);
|
|
858
|
-
await reload();
|
|
859
|
-
};
|
|
860
878
|
const handleExistingFileClick = (file) => {
|
|
861
879
|
const a = document.createElement("a");
|
|
862
880
|
a.href = file.PublicUrl ?? "";
|
|
@@ -867,6 +885,13 @@ var ContainerUploadPanel = ({
|
|
|
867
885
|
a.click();
|
|
868
886
|
document.body.removeChild(a);
|
|
869
887
|
};
|
|
888
|
+
const handleDeleteFile = async (file) => {
|
|
889
|
+
const sdkDb = new SparkStudioStorageSDK(containerApiBaseUrl);
|
|
890
|
+
const sdkS3 = new SparkStudioStorageSDK(storageApiBaseUrl);
|
|
891
|
+
await sdkDb.container.DeleteContainer(file.Id);
|
|
892
|
+
await sdkS3.s3.DeleteS3(file);
|
|
893
|
+
setContainers((prev) => prev.filter((c) => c.Id !== file.Id));
|
|
894
|
+
};
|
|
870
895
|
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
871
896
|
UploadContainer,
|
|
872
897
|
{
|
|
@@ -877,7 +902,7 @@ var ContainerUploadPanel = ({
|
|
|
877
902
|
getPresignedUrl,
|
|
878
903
|
onUploadComplete: handleUploadComplete,
|
|
879
904
|
onUploadError: handleUploadError,
|
|
880
|
-
onDeleteFile:
|
|
905
|
+
onDeleteFile: handleDeleteFile
|
|
881
906
|
}
|
|
882
907
|
);
|
|
883
908
|
};
|
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import React__default from 'react';
|
|
2
3
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
3
4
|
|
|
4
5
|
declare enum ContainerType {
|
|
@@ -102,7 +103,7 @@ interface ContainerUploadPanelProps {
|
|
|
102
103
|
/** Optional parent container – if set, we query children instead of roots. */
|
|
103
104
|
parentContainerId?: string;
|
|
104
105
|
}
|
|
105
|
-
declare const ContainerUploadPanel:
|
|
106
|
+
declare const ContainerUploadPanel: React__default.FC<ContainerUploadPanelProps>;
|
|
106
107
|
|
|
107
108
|
interface DesktopFileIconProps {
|
|
108
109
|
name?: string | null;
|
|
@@ -110,10 +111,10 @@ interface DesktopFileIconProps {
|
|
|
110
111
|
downloadUrl?: string | null;
|
|
111
112
|
/** Double-click / open action */
|
|
112
113
|
onOpen?: () => void;
|
|
113
|
-
/** Delete action */
|
|
114
|
-
onDelete?: () => void;
|
|
114
|
+
/** Delete action (can be async) */
|
|
115
|
+
onDelete?: () => Promise<void> | void;
|
|
115
116
|
}
|
|
116
|
-
declare const DesktopFileIcon:
|
|
117
|
+
declare const DesktopFileIcon: React__default.FC<DesktopFileIconProps>;
|
|
117
118
|
|
|
118
119
|
interface UploadContainerProps {
|
|
119
120
|
multiple?: boolean;
|
|
@@ -129,20 +130,20 @@ interface UploadContainerProps {
|
|
|
129
130
|
onUploadComplete?: (file: File, s3Url: string) => void;
|
|
130
131
|
onUploadError?: (file: File, error: Error) => void;
|
|
131
132
|
}
|
|
132
|
-
declare const UploadContainer:
|
|
133
|
+
declare const UploadContainer: React__default.FC<UploadContainerProps>;
|
|
133
134
|
|
|
134
135
|
interface UploadDropzoneProps {
|
|
135
136
|
isDragging: boolean;
|
|
136
|
-
onDragOver?: (e:
|
|
137
|
-
onDragLeave?: (e:
|
|
138
|
-
onDrop?: (e:
|
|
137
|
+
onDragOver?: (e: React__default.DragEvent<HTMLDivElement>) => void;
|
|
138
|
+
onDragLeave?: (e: React__default.DragEvent<HTMLDivElement>) => void;
|
|
139
|
+
onDrop?: (e: React__default.DragEvent<HTMLDivElement>) => void;
|
|
139
140
|
/** Extra className so you can make this the root wrapper */
|
|
140
141
|
className?: string;
|
|
141
|
-
style?:
|
|
142
|
+
style?: React__default.CSSProperties;
|
|
142
143
|
/** Custom content to render inside the dropzone */
|
|
143
|
-
children?:
|
|
144
|
+
children?: React__default.ReactNode;
|
|
144
145
|
}
|
|
145
|
-
declare const UploadDropzone:
|
|
146
|
+
declare const UploadDropzone: React__default.FC<UploadDropzoneProps>;
|
|
146
147
|
|
|
147
148
|
type UploadStatus = "pending" | "uploading" | "success" | "error";
|
|
148
149
|
interface UploadState {
|
|
@@ -158,7 +159,7 @@ interface UploadState {
|
|
|
158
159
|
interface UploadProgressListProps {
|
|
159
160
|
uploads: UploadState[];
|
|
160
161
|
}
|
|
161
|
-
declare const UploadProgressList:
|
|
162
|
+
declare const UploadProgressList: React__default.FC<UploadProgressListProps>;
|
|
162
163
|
|
|
163
164
|
/**
|
|
164
165
|
* Helper: upload a file to a pre-signed S3 URL with progress + retries.
|
|
@@ -171,6 +172,7 @@ interface UseContainersOptions {
|
|
|
171
172
|
}
|
|
172
173
|
declare function UseContainers({ apiBaseUrl, parentId }: UseContainersOptions): {
|
|
173
174
|
containers: ContainerDTO[];
|
|
175
|
+
setContainers: React.Dispatch<React.SetStateAction<ContainerDTO[]>>;
|
|
174
176
|
loading: boolean;
|
|
175
177
|
error: Error | null;
|
|
176
178
|
reload: () => Promise<void>;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import React__default from 'react';
|
|
2
3
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
3
4
|
|
|
4
5
|
declare enum ContainerType {
|
|
@@ -102,7 +103,7 @@ interface ContainerUploadPanelProps {
|
|
|
102
103
|
/** Optional parent container – if set, we query children instead of roots. */
|
|
103
104
|
parentContainerId?: string;
|
|
104
105
|
}
|
|
105
|
-
declare const ContainerUploadPanel:
|
|
106
|
+
declare const ContainerUploadPanel: React__default.FC<ContainerUploadPanelProps>;
|
|
106
107
|
|
|
107
108
|
interface DesktopFileIconProps {
|
|
108
109
|
name?: string | null;
|
|
@@ -110,10 +111,10 @@ interface DesktopFileIconProps {
|
|
|
110
111
|
downloadUrl?: string | null;
|
|
111
112
|
/** Double-click / open action */
|
|
112
113
|
onOpen?: () => void;
|
|
113
|
-
/** Delete action */
|
|
114
|
-
onDelete?: () => void;
|
|
114
|
+
/** Delete action (can be async) */
|
|
115
|
+
onDelete?: () => Promise<void> | void;
|
|
115
116
|
}
|
|
116
|
-
declare const DesktopFileIcon:
|
|
117
|
+
declare const DesktopFileIcon: React__default.FC<DesktopFileIconProps>;
|
|
117
118
|
|
|
118
119
|
interface UploadContainerProps {
|
|
119
120
|
multiple?: boolean;
|
|
@@ -129,20 +130,20 @@ interface UploadContainerProps {
|
|
|
129
130
|
onUploadComplete?: (file: File, s3Url: string) => void;
|
|
130
131
|
onUploadError?: (file: File, error: Error) => void;
|
|
131
132
|
}
|
|
132
|
-
declare const UploadContainer:
|
|
133
|
+
declare const UploadContainer: React__default.FC<UploadContainerProps>;
|
|
133
134
|
|
|
134
135
|
interface UploadDropzoneProps {
|
|
135
136
|
isDragging: boolean;
|
|
136
|
-
onDragOver?: (e:
|
|
137
|
-
onDragLeave?: (e:
|
|
138
|
-
onDrop?: (e:
|
|
137
|
+
onDragOver?: (e: React__default.DragEvent<HTMLDivElement>) => void;
|
|
138
|
+
onDragLeave?: (e: React__default.DragEvent<HTMLDivElement>) => void;
|
|
139
|
+
onDrop?: (e: React__default.DragEvent<HTMLDivElement>) => void;
|
|
139
140
|
/** Extra className so you can make this the root wrapper */
|
|
140
141
|
className?: string;
|
|
141
|
-
style?:
|
|
142
|
+
style?: React__default.CSSProperties;
|
|
142
143
|
/** Custom content to render inside the dropzone */
|
|
143
|
-
children?:
|
|
144
|
+
children?: React__default.ReactNode;
|
|
144
145
|
}
|
|
145
|
-
declare const UploadDropzone:
|
|
146
|
+
declare const UploadDropzone: React__default.FC<UploadDropzoneProps>;
|
|
146
147
|
|
|
147
148
|
type UploadStatus = "pending" | "uploading" | "success" | "error";
|
|
148
149
|
interface UploadState {
|
|
@@ -158,7 +159,7 @@ interface UploadState {
|
|
|
158
159
|
interface UploadProgressListProps {
|
|
159
160
|
uploads: UploadState[];
|
|
160
161
|
}
|
|
161
|
-
declare const UploadProgressList:
|
|
162
|
+
declare const UploadProgressList: React__default.FC<UploadProgressListProps>;
|
|
162
163
|
|
|
163
164
|
/**
|
|
164
165
|
* Helper: upload a file to a pre-signed S3 URL with progress + retries.
|
|
@@ -171,6 +172,7 @@ interface UseContainersOptions {
|
|
|
171
172
|
}
|
|
172
173
|
declare function UseContainers({ apiBaseUrl, parentId }: UseContainersOptions): {
|
|
173
174
|
containers: ContainerDTO[];
|
|
175
|
+
setContainers: React.Dispatch<React.SetStateAction<ContainerDTO[]>>;
|
|
174
176
|
loading: boolean;
|
|
175
177
|
error: Error | null;
|
|
176
178
|
reload: () => Promise<void>;
|
package/dist/index.js
CHANGED
|
@@ -437,6 +437,14 @@ var UploadProgressList = ({
|
|
|
437
437
|
},
|
|
438
438
|
children: [
|
|
439
439
|
/* @__PURE__ */ jsx2("i", { className: "bi bi-file-earmark fs-2" }),
|
|
440
|
+
u.status === "uploading" && /* @__PURE__ */ jsx2(
|
|
441
|
+
"div",
|
|
442
|
+
{
|
|
443
|
+
className: "position-absolute top-50 start-50 translate-middle",
|
|
444
|
+
style: { pointerEvents: "none" },
|
|
445
|
+
children: /* @__PURE__ */ jsx2("div", { className: "spinner-border spinner-border-sm text-primary" })
|
|
446
|
+
}
|
|
447
|
+
),
|
|
440
448
|
u.status === "error" && /* @__PURE__ */ jsx2("span", { className: "position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger", children: "!" })
|
|
441
449
|
]
|
|
442
450
|
}
|
|
@@ -490,9 +498,11 @@ var DesktopFileIcon = ({
|
|
|
490
498
|
null
|
|
491
499
|
);
|
|
492
500
|
const [isHovered, setIsHovered] = useState2(false);
|
|
501
|
+
const [isDeleting, setIsDeleting] = useState2(false);
|
|
493
502
|
const iconRef = useRef(null);
|
|
494
503
|
const menuRef = useRef(null);
|
|
495
504
|
const handleDoubleClick = () => {
|
|
505
|
+
if (isDeleting) return;
|
|
496
506
|
if (onOpen) {
|
|
497
507
|
onOpen();
|
|
498
508
|
return;
|
|
@@ -509,13 +519,14 @@ var DesktopFileIcon = ({
|
|
|
509
519
|
}
|
|
510
520
|
};
|
|
511
521
|
const handleContextMenu = (e) => {
|
|
522
|
+
if (isDeleting) return;
|
|
512
523
|
e.preventDefault();
|
|
513
524
|
setContextMenuPos({ x: e.clientX, y: e.clientY });
|
|
514
525
|
};
|
|
515
526
|
const closeMenu = () => setContextMenuPos(null);
|
|
516
527
|
const handleDownload = () => {
|
|
517
528
|
closeMenu();
|
|
518
|
-
if (!downloadUrl) return;
|
|
529
|
+
if (!downloadUrl || isDeleting) return;
|
|
519
530
|
const a = document.createElement("a");
|
|
520
531
|
a.href = downloadUrl;
|
|
521
532
|
a.download = name ?? "";
|
|
@@ -527,16 +538,22 @@ var DesktopFileIcon = ({
|
|
|
527
538
|
};
|
|
528
539
|
const handleCopyUrl = async () => {
|
|
529
540
|
closeMenu();
|
|
530
|
-
if (!downloadUrl) return;
|
|
541
|
+
if (!downloadUrl || isDeleting) return;
|
|
531
542
|
try {
|
|
532
543
|
await navigator.clipboard?.writeText(downloadUrl);
|
|
533
544
|
} catch (err) {
|
|
534
545
|
console.error("Failed to copy URL", err);
|
|
535
546
|
}
|
|
536
547
|
};
|
|
537
|
-
const handleDelete = () => {
|
|
548
|
+
const handleDelete = async () => {
|
|
538
549
|
closeMenu();
|
|
539
|
-
onDelete
|
|
550
|
+
if (!onDelete) return;
|
|
551
|
+
try {
|
|
552
|
+
setIsDeleting(true);
|
|
553
|
+
await Promise.resolve(onDelete());
|
|
554
|
+
} catch (err) {
|
|
555
|
+
console.error("Delete failed", err);
|
|
556
|
+
}
|
|
540
557
|
};
|
|
541
558
|
const formattedSize = typeof sizeBytes === "number" ? `${(sizeBytes / 1024).toFixed(1)} KB` : void 0;
|
|
542
559
|
useEffect(() => {
|
|
@@ -561,12 +578,15 @@ var DesktopFileIcon = ({
|
|
|
561
578
|
"div",
|
|
562
579
|
{
|
|
563
580
|
ref: iconRef,
|
|
564
|
-
className: "d-flex flex-column align-items-center rounded-3 p-1 " + (isHovered || contextMenuPos ? "bg-light border" : ""),
|
|
581
|
+
className: "d-flex flex-column align-items-center rounded-3 p-1 " + (isHovered || contextMenuPos ? "bg-light border-primary" : "border-transparent"),
|
|
565
582
|
style: {
|
|
566
583
|
width: 96,
|
|
567
|
-
cursor: "pointer",
|
|
584
|
+
cursor: isDeleting ? "default" : "pointer",
|
|
568
585
|
userSelect: "none",
|
|
569
|
-
transition: "background-color 0.1s ease, border-color 0.1s ease"
|
|
586
|
+
transition: "background-color 0.1s ease, border-color 0.1s ease, opacity 0.1s ease",
|
|
587
|
+
opacity: isDeleting ? 0.6 : 1,
|
|
588
|
+
borderWidth: 1,
|
|
589
|
+
borderStyle: "solid"
|
|
570
590
|
},
|
|
571
591
|
onDoubleClick: handleDoubleClick,
|
|
572
592
|
onContextMenu: handleContextMenu,
|
|
@@ -579,15 +599,18 @@ var DesktopFileIcon = ({
|
|
|
579
599
|
onMouseEnter: () => setIsHovered(true),
|
|
580
600
|
onMouseLeave: () => setIsHovered(false),
|
|
581
601
|
children: [
|
|
582
|
-
/* @__PURE__ */
|
|
602
|
+
/* @__PURE__ */ jsxs2(
|
|
583
603
|
"div",
|
|
584
604
|
{
|
|
585
|
-
className: "bg-white border rounded-3 d-flex align-items-center justify-content-center mb-1 shadow-sm",
|
|
605
|
+
className: "bg-white border rounded-3 d-flex align-items-center justify-content-center mb-1 shadow-sm position-relative",
|
|
586
606
|
style: {
|
|
587
607
|
width: 64,
|
|
588
608
|
height: 64
|
|
589
609
|
},
|
|
590
|
-
children:
|
|
610
|
+
children: [
|
|
611
|
+
/* @__PURE__ */ jsx3("i", { className: "bi bi-file-earmark fs-2" }),
|
|
612
|
+
isDeleting && /* @__PURE__ */ jsx3("div", { className: "position-absolute top-50 start-50 translate-middle", children: /* @__PURE__ */ jsx3("div", { className: "spinner-border spinner-border-sm text-danger" }) })
|
|
613
|
+
]
|
|
591
614
|
}
|
|
592
615
|
),
|
|
593
616
|
/* @__PURE__ */ jsx3(
|
|
@@ -602,7 +625,7 @@ var DesktopFileIcon = ({
|
|
|
602
625
|
]
|
|
603
626
|
}
|
|
604
627
|
),
|
|
605
|
-
contextMenuPos && /* @__PURE__ */ jsxs2(
|
|
628
|
+
contextMenuPos && !isDeleting && /* @__PURE__ */ jsxs2(
|
|
606
629
|
"div",
|
|
607
630
|
{
|
|
608
631
|
ref: menuRef,
|
|
@@ -711,6 +734,7 @@ var UploadContainer = ({
|
|
|
711
734
|
className: "w-100",
|
|
712
735
|
style: { minHeight: "260px", alignItems: "stretch" },
|
|
713
736
|
children: [
|
|
737
|
+
/* @__PURE__ */ jsx4("div", { className: "w-100 mb-3", children: /* @__PURE__ */ jsx4("div", { className: "d-flex flex-column flex-md-row align-items-start align-items-md-center justify-content-between gap-3", children: /* @__PURE__ */ jsx4("div", { className: "flex-grow-1 w-100", children: /* @__PURE__ */ jsx4(UploadProgressList, { uploads }) }) }) }),
|
|
714
738
|
/* @__PURE__ */ jsx4(
|
|
715
739
|
"div",
|
|
716
740
|
{
|
|
@@ -720,16 +744,15 @@ var UploadContainer = ({
|
|
|
720
744
|
DesktopFileIcon,
|
|
721
745
|
{
|
|
722
746
|
name: file.Name,
|
|
723
|
-
sizeBytes:
|
|
724
|
-
downloadUrl: file.PublicUrl
|
|
747
|
+
sizeBytes: file.FileSize,
|
|
748
|
+
downloadUrl: file.PublicUrl,
|
|
725
749
|
onOpen: () => handleExistingFileOpen(file),
|
|
726
|
-
onDelete:
|
|
750
|
+
onDelete: () => onDeleteFile?.(file)
|
|
727
751
|
},
|
|
728
752
|
file.Id
|
|
729
753
|
))
|
|
730
754
|
}
|
|
731
|
-
)
|
|
732
|
-
/* @__PURE__ */ jsx4("div", { className: "w-100 mt-3", children: /* @__PURE__ */ jsx4("div", { className: "d-flex flex-column flex-md-row align-items-start align-items-md-center justify-content-between gap-3", children: /* @__PURE__ */ jsx4("div", { className: "flex-grow-1 w-100", children: /* @__PURE__ */ jsx4(UploadProgressList, { uploads }) }) }) })
|
|
755
|
+
)
|
|
733
756
|
]
|
|
734
757
|
}
|
|
735
758
|
);
|
|
@@ -761,6 +784,7 @@ function UseContainers({ apiBaseUrl, parentId }) {
|
|
|
761
784
|
}, [apiBaseUrl, parentId]);
|
|
762
785
|
return {
|
|
763
786
|
containers,
|
|
787
|
+
setContainers,
|
|
764
788
|
loading,
|
|
765
789
|
error,
|
|
766
790
|
reload: load
|
|
@@ -774,17 +798,18 @@ var ContainerUploadPanel = ({
|
|
|
774
798
|
storageApiBaseUrl,
|
|
775
799
|
parentContainerId
|
|
776
800
|
}) => {
|
|
777
|
-
const { containers, reload, loading } = UseContainers({
|
|
801
|
+
const { containers, setContainers, reload, loading } = UseContainers({
|
|
778
802
|
apiBaseUrl: containerApiBaseUrl,
|
|
779
803
|
parentId: parentContainerId
|
|
780
804
|
});
|
|
781
805
|
const getPresignedUrl = async (file) => {
|
|
782
806
|
const sdkDb = new SparkStudioStorageSDK(containerApiBaseUrl);
|
|
783
807
|
const sdkS3 = new SparkStudioStorageSDK(storageApiBaseUrl);
|
|
808
|
+
const contentType = file.type || "application/octet-stream";
|
|
784
809
|
const containerDTO = await sdkDb.container.CreateFileContainer(
|
|
785
810
|
file.name,
|
|
786
811
|
file.size,
|
|
787
|
-
encodeURIComponent(
|
|
812
|
+
encodeURIComponent(contentType)
|
|
788
813
|
);
|
|
789
814
|
async function getPresignedUrlWithRetry(container, attempts = 3) {
|
|
790
815
|
let lastError;
|
|
@@ -809,13 +834,6 @@ var ContainerUploadPanel = ({
|
|
|
809
834
|
const handleUploadError = (file, err) => {
|
|
810
835
|
console.error("Upload failed:", file.name, err);
|
|
811
836
|
};
|
|
812
|
-
const handleOnDeleteFile = async (file) => {
|
|
813
|
-
const sdkDb = new SparkStudioStorageSDK(containerApiBaseUrl);
|
|
814
|
-
const sdkS3 = new SparkStudioStorageSDK(storageApiBaseUrl);
|
|
815
|
-
await sdkDb.container.DeleteContainer(file.Id);
|
|
816
|
-
await sdkS3.s3.DeleteS3(file);
|
|
817
|
-
await reload();
|
|
818
|
-
};
|
|
819
837
|
const handleExistingFileClick = (file) => {
|
|
820
838
|
const a = document.createElement("a");
|
|
821
839
|
a.href = file.PublicUrl ?? "";
|
|
@@ -826,6 +844,13 @@ var ContainerUploadPanel = ({
|
|
|
826
844
|
a.click();
|
|
827
845
|
document.body.removeChild(a);
|
|
828
846
|
};
|
|
847
|
+
const handleDeleteFile = async (file) => {
|
|
848
|
+
const sdkDb = new SparkStudioStorageSDK(containerApiBaseUrl);
|
|
849
|
+
const sdkS3 = new SparkStudioStorageSDK(storageApiBaseUrl);
|
|
850
|
+
await sdkDb.container.DeleteContainer(file.Id);
|
|
851
|
+
await sdkS3.s3.DeleteS3(file);
|
|
852
|
+
setContainers((prev) => prev.filter((c) => c.Id !== file.Id));
|
|
853
|
+
};
|
|
829
854
|
return /* @__PURE__ */ jsx5(
|
|
830
855
|
UploadContainer,
|
|
831
856
|
{
|
|
@@ -836,7 +861,7 @@ var ContainerUploadPanel = ({
|
|
|
836
861
|
getPresignedUrl,
|
|
837
862
|
onUploadComplete: handleUploadComplete,
|
|
838
863
|
onUploadError: handleUploadError,
|
|
839
|
-
onDeleteFile:
|
|
864
|
+
onDeleteFile: handleDeleteFile
|
|
840
865
|
}
|
|
841
866
|
);
|
|
842
867
|
};
|