@moontra/moonui-pro 2.11.0 → 2.11.2
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.d.ts +20 -0
- package/dist/index.mjs +192 -60
- package/package.json +1 -1
- package/src/components/file-upload/index.tsx +263 -64
package/dist/index.d.ts
CHANGED
|
@@ -1774,6 +1774,26 @@ interface FileUploadProProps {
|
|
|
1774
1774
|
uploadStrategy?: 'direct' | 'presigned' | 'multipart';
|
|
1775
1775
|
endpoint?: string;
|
|
1776
1776
|
headers?: Record<string, string>;
|
|
1777
|
+
serviceConfig?: {
|
|
1778
|
+
type: 'aws-s3' | 'cloudinary' | 'firebase' | 'custom';
|
|
1779
|
+
awsConfig?: {
|
|
1780
|
+
region: string;
|
|
1781
|
+
bucket: string;
|
|
1782
|
+
accessKeyId?: string;
|
|
1783
|
+
secretAccessKey?: string;
|
|
1784
|
+
sessionToken?: string;
|
|
1785
|
+
};
|
|
1786
|
+
cloudinaryConfig?: {
|
|
1787
|
+
cloudName: string;
|
|
1788
|
+
uploadPreset: string;
|
|
1789
|
+
apiKey?: string;
|
|
1790
|
+
apiSecret?: string;
|
|
1791
|
+
};
|
|
1792
|
+
firebaseConfig?: {
|
|
1793
|
+
storageBucket: string;
|
|
1794
|
+
folder?: string;
|
|
1795
|
+
};
|
|
1796
|
+
};
|
|
1777
1797
|
onUpload?: (files: FileUploadItem[]) => Promise<void>;
|
|
1778
1798
|
onProgress?: (fileId: string, progress: number) => void;
|
|
1779
1799
|
onComplete?: (fileId: string, result: any) => void;
|
package/dist/index.mjs
CHANGED
|
@@ -58771,23 +58771,54 @@ var generateFileHash = async (file) => {
|
|
|
58771
58771
|
};
|
|
58772
58772
|
var createImagePreview = (file) => {
|
|
58773
58773
|
return new Promise((resolve) => {
|
|
58774
|
-
const
|
|
58775
|
-
|
|
58776
|
-
|
|
58777
|
-
|
|
58778
|
-
|
|
58779
|
-
|
|
58780
|
-
|
|
58781
|
-
|
|
58782
|
-
|
|
58774
|
+
const reader = new FileReader();
|
|
58775
|
+
reader.onload = (e) => {
|
|
58776
|
+
const result = e.target?.result;
|
|
58777
|
+
const img = new Image();
|
|
58778
|
+
img.onload = () => {
|
|
58779
|
+
const canvas = document.createElement("canvas");
|
|
58780
|
+
const ctx = canvas.getContext("2d");
|
|
58781
|
+
const maxThumbSize = 400;
|
|
58782
|
+
let thumbWidth = img.width;
|
|
58783
|
+
let thumbHeight = img.height;
|
|
58784
|
+
if (thumbWidth > maxThumbSize || thumbHeight > maxThumbSize) {
|
|
58785
|
+
const aspectRatio = img.width / img.height;
|
|
58786
|
+
if (aspectRatio > 1) {
|
|
58787
|
+
thumbWidth = maxThumbSize;
|
|
58788
|
+
thumbHeight = maxThumbSize / aspectRatio;
|
|
58789
|
+
} else {
|
|
58790
|
+
thumbHeight = maxThumbSize;
|
|
58791
|
+
thumbWidth = maxThumbSize * aspectRatio;
|
|
58792
|
+
}
|
|
58793
|
+
}
|
|
58794
|
+
canvas.width = thumbWidth;
|
|
58795
|
+
canvas.height = thumbHeight;
|
|
58796
|
+
ctx.drawImage(img, 0, 0, thumbWidth, thumbHeight);
|
|
58797
|
+
const thumbnail = canvas.toDataURL("image/jpeg", 0.9);
|
|
58798
|
+
resolve({
|
|
58799
|
+
type: "image",
|
|
58800
|
+
url: result,
|
|
58801
|
+
thumbnail,
|
|
58802
|
+
dimensions: { width: img.width, height: img.height }
|
|
58803
|
+
});
|
|
58804
|
+
};
|
|
58805
|
+
img.onerror = () => {
|
|
58806
|
+
resolve({
|
|
58807
|
+
type: "image",
|
|
58808
|
+
url: result,
|
|
58809
|
+
thumbnail: result
|
|
58810
|
+
});
|
|
58811
|
+
};
|
|
58812
|
+
img.src = result;
|
|
58783
58813
|
};
|
|
58784
|
-
|
|
58814
|
+
reader.onerror = () => {
|
|
58815
|
+
const url = URL.createObjectURL(file);
|
|
58785
58816
|
resolve({
|
|
58786
58817
|
type: "image",
|
|
58787
58818
|
url
|
|
58788
58819
|
});
|
|
58789
58820
|
};
|
|
58790
|
-
|
|
58821
|
+
reader.readAsDataURL(file);
|
|
58791
58822
|
});
|
|
58792
58823
|
};
|
|
58793
58824
|
var createVideoPreview = (file) => {
|
|
@@ -58839,16 +58870,16 @@ var BulkActions = ({
|
|
|
58839
58870
|
children: [
|
|
58840
58871
|
/* @__PURE__ */ jsxs("span", { className: "text-sm font-medium", children: [
|
|
58841
58872
|
selectedIds.length,
|
|
58842
|
-
"
|
|
58873
|
+
" files selected"
|
|
58843
58874
|
] }),
|
|
58844
58875
|
/* @__PURE__ */ jsxs("div", { className: "flex gap-1 ml-auto", children: [
|
|
58845
58876
|
/* @__PURE__ */ jsxs(MoonUIButtonPro, { variant: "ghost", size: "sm", onClick: onBulkDownload, children: [
|
|
58846
58877
|
/* @__PURE__ */ jsx(Download, { className: "h-4 w-4 mr-1" }),
|
|
58847
|
-
"
|
|
58878
|
+
"Download"
|
|
58848
58879
|
] }),
|
|
58849
58880
|
/* @__PURE__ */ jsxs(MoonUIButtonPro, { variant: "ghost", size: "sm", onClick: onBulkRemove, children: [
|
|
58850
58881
|
/* @__PURE__ */ jsx(Trash2, { className: "h-4 w-4 mr-1" }),
|
|
58851
|
-
"
|
|
58882
|
+
"Delete"
|
|
58852
58883
|
] }),
|
|
58853
58884
|
/* @__PURE__ */ jsx(MoonUIButtonPro, { variant: "ghost", size: "sm", onClick: onClearSelection, children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" }) })
|
|
58854
58885
|
] })
|
|
@@ -58870,37 +58901,45 @@ var FilePreviewModal = ({
|
|
|
58870
58901
|
/* @__PURE__ */ jsx(MoonUIBadgePro, { variant: "secondary", children: formatFileSize(file.file.size) })
|
|
58871
58902
|
] }) }),
|
|
58872
58903
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center p-4 bg-muted/20 rounded-lg", children: [
|
|
58873
|
-
file.preview.type === "image" && /* @__PURE__ */
|
|
58874
|
-
|
|
58875
|
-
|
|
58876
|
-
|
|
58877
|
-
|
|
58878
|
-
|
|
58879
|
-
|
|
58880
|
-
|
|
58881
|
-
|
|
58904
|
+
file.preview.type === "image" && /* @__PURE__ */ jsxs("div", { className: "relative", children: [
|
|
58905
|
+
/* @__PURE__ */ jsx(
|
|
58906
|
+
"img",
|
|
58907
|
+
{
|
|
58908
|
+
src: file.preview.url,
|
|
58909
|
+
alt: file.file.name,
|
|
58910
|
+
className: "max-w-full max-h-[60vh] object-contain rounded shadow-lg",
|
|
58911
|
+
loading: "eager"
|
|
58912
|
+
}
|
|
58913
|
+
),
|
|
58914
|
+
/* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex items-center justify-center bg-background/80 rounded opacity-0 pointer-events-none transition-opacity duration-300", children: /* @__PURE__ */ jsx(Loader2, { className: "h-8 w-8 animate-spin text-muted-foreground" }) })
|
|
58915
|
+
] }),
|
|
58916
|
+
file.preview.type === "video" && /* @__PURE__ */ jsx("div", { className: "relative w-full max-w-4xl", children: /* @__PURE__ */ jsx(
|
|
58882
58917
|
"video",
|
|
58883
58918
|
{
|
|
58884
58919
|
src: file.preview.url,
|
|
58885
58920
|
controls: true,
|
|
58886
|
-
className: "
|
|
58921
|
+
className: "w-full max-h-[60vh] rounded shadow-lg",
|
|
58922
|
+
poster: file.preview.thumbnail
|
|
58887
58923
|
}
|
|
58888
|
-
),
|
|
58889
|
-
file.preview.type === "audio" && /* @__PURE__ */ jsx("div", { className: "w-full max-w-md", children: /* @__PURE__ */
|
|
58924
|
+
) }),
|
|
58925
|
+
file.preview.type === "audio" && /* @__PURE__ */ jsx("div", { className: "w-full max-w-md space-y-4", children: /* @__PURE__ */ jsxs("div", { className: "p-8 bg-gradient-to-br from-primary/10 to-primary/5 rounded-lg", children: [
|
|
58926
|
+
/* @__PURE__ */ jsx(Music, { className: "h-16 w-16 mx-auto text-primary mb-4" }),
|
|
58927
|
+
/* @__PURE__ */ jsx("audio", { src: file.preview.url, controls: true, className: "w-full" })
|
|
58928
|
+
] }) }),
|
|
58890
58929
|
!["image", "video", "audio"].includes(file.preview.type) && /* @__PURE__ */ jsxs("div", { className: "text-center py-8", children: [
|
|
58891
58930
|
getFileIcon(file.file.type, "lg"),
|
|
58892
|
-
/* @__PURE__ */ jsx("p", { className: "mt-2 text-muted-foreground", children: "
|
|
58931
|
+
/* @__PURE__ */ jsx("p", { className: "mt-2 text-muted-foreground", children: "Preview not available" })
|
|
58893
58932
|
] })
|
|
58894
58933
|
] }),
|
|
58895
58934
|
file.preview.dimensions && /* @__PURE__ */ jsxs("div", { className: "flex gap-4 text-sm text-muted-foreground", children: [
|
|
58896
58935
|
/* @__PURE__ */ jsxs("span", { children: [
|
|
58897
|
-
"
|
|
58936
|
+
"Dimensions: ",
|
|
58898
58937
|
file.preview.dimensions.width,
|
|
58899
58938
|
" \xD7 ",
|
|
58900
58939
|
file.preview.dimensions.height
|
|
58901
58940
|
] }),
|
|
58902
58941
|
file.preview.duration && /* @__PURE__ */ jsxs("span", { children: [
|
|
58903
|
-
"
|
|
58942
|
+
"Duration: ",
|
|
58904
58943
|
formatTime(file.preview.duration)
|
|
58905
58944
|
] })
|
|
58906
58945
|
] })
|
|
@@ -58937,6 +58976,10 @@ var MoonUIFileUploadPro = t__default.forwardRef(
|
|
|
58937
58976
|
onPreview,
|
|
58938
58977
|
onBulkSelect,
|
|
58939
58978
|
customValidation,
|
|
58979
|
+
serviceConfig,
|
|
58980
|
+
imageResize,
|
|
58981
|
+
endpoint,
|
|
58982
|
+
headers,
|
|
58940
58983
|
...props
|
|
58941
58984
|
}, ref) => {
|
|
58942
58985
|
const { hasProAccess, isLoading } = useSubscription();
|
|
@@ -58952,21 +58995,21 @@ var MoonUIFileUploadPro = t__default.forwardRef(
|
|
|
58952
58995
|
return /* @__PURE__ */ jsx(MoonUICardPro, { className: cn("w-full", className), children: /* @__PURE__ */ jsx(MoonUICardContentPro, { className: "py-12 text-center", children: /* @__PURE__ */ jsxs("div", { className: "max-w-md mx-auto space-y-4", children: [
|
|
58953
58996
|
/* @__PURE__ */ jsx("div", { className: "rounded-full bg-purple-100 dark:bg-purple-900/30 p-3 w-fit mx-auto", children: /* @__PURE__ */ jsx(Lock, { className: "h-6 w-6 text-purple-600 dark:text-purple-400" }) }),
|
|
58954
58997
|
/* @__PURE__ */ jsxs("div", { children: [
|
|
58955
|
-
/* @__PURE__ */ jsx("h3", { className: "font-semibold text-lg mb-2", children: "Pro
|
|
58956
|
-
/* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm mb-4", children: "
|
|
58998
|
+
/* @__PURE__ */ jsx("h3", { className: "font-semibold text-lg mb-2", children: "Pro Feature" }),
|
|
58999
|
+
/* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm mb-4", children: "Advanced File Upload is exclusive to MoonUI Pro subscribers." }),
|
|
58957
59000
|
/* @__PURE__ */ jsx("div", { className: "flex gap-3 justify-center", children: /* @__PURE__ */ jsx("a", { href: "/pricing", children: /* @__PURE__ */ jsxs(MoonUIButtonPro, { size: "sm", children: [
|
|
58958
59001
|
/* @__PURE__ */ jsx(Sparkles, { className: "mr-2 h-4 w-4" }),
|
|
58959
|
-
"Pro
|
|
59002
|
+
"Upgrade to Pro"
|
|
58960
59003
|
] }) }) })
|
|
58961
59004
|
] })
|
|
58962
59005
|
] }) }) });
|
|
58963
59006
|
}
|
|
58964
59007
|
const validateFile = useCallback(async (file) => {
|
|
58965
59008
|
if (file.size > maxSize) {
|
|
58966
|
-
return `
|
|
59009
|
+
return `File size exceeds ${formatFileSize(maxSize)} limit`;
|
|
58967
59010
|
}
|
|
58968
59011
|
if (allowedMimeTypes.length > 0 && !allowedMimeTypes.includes(file.type)) {
|
|
58969
|
-
return `
|
|
59012
|
+
return `File type ${file.type} is not supported`;
|
|
58970
59013
|
}
|
|
58971
59014
|
if (accept !== "*") {
|
|
58972
59015
|
const acceptTypes = accept.split(",").map((t2) => t2.trim());
|
|
@@ -58977,7 +59020,7 @@ var MoonUIFileUploadPro = t__default.forwardRef(
|
|
|
58977
59020
|
return file.type === acceptType;
|
|
58978
59021
|
});
|
|
58979
59022
|
if (!isAccepted) {
|
|
58980
|
-
return `
|
|
59023
|
+
return `File type not accepted`;
|
|
58981
59024
|
}
|
|
58982
59025
|
}
|
|
58983
59026
|
if (customValidation) {
|
|
@@ -59012,7 +59055,7 @@ var MoonUIFileUploadPro = t__default.forwardRef(
|
|
|
59012
59055
|
};
|
|
59013
59056
|
}
|
|
59014
59057
|
} catch (error2) {
|
|
59015
|
-
console.warn("
|
|
59058
|
+
console.warn("Failed to create preview:", error2);
|
|
59016
59059
|
}
|
|
59017
59060
|
return void 0;
|
|
59018
59061
|
}, [showPreview, previewTypes]);
|
|
@@ -59028,6 +59071,90 @@ var MoonUIFileUploadPro = t__default.forwardRef(
|
|
|
59028
59071
|
}
|
|
59029
59072
|
return metadata;
|
|
59030
59073
|
}, [duplicateCheck]);
|
|
59074
|
+
const uploadToService = useCallback(async (fileItem) => {
|
|
59075
|
+
if (!serviceConfig)
|
|
59076
|
+
return null;
|
|
59077
|
+
const { file } = fileItem;
|
|
59078
|
+
switch (serviceConfig.type) {
|
|
59079
|
+
case "aws-s3":
|
|
59080
|
+
if (uploadStrategy === "presigned" && endpoint) {
|
|
59081
|
+
const response = await fetch(endpoint, {
|
|
59082
|
+
method: "PUT",
|
|
59083
|
+
body: file,
|
|
59084
|
+
headers: {
|
|
59085
|
+
"Content-Type": file.type,
|
|
59086
|
+
...headers
|
|
59087
|
+
}
|
|
59088
|
+
});
|
|
59089
|
+
return response.ok ? { url: response.url } : null;
|
|
59090
|
+
}
|
|
59091
|
+
break;
|
|
59092
|
+
case "cloudinary":
|
|
59093
|
+
if (serviceConfig.cloudinaryConfig) {
|
|
59094
|
+
const { cloudName, uploadPreset } = serviceConfig.cloudinaryConfig;
|
|
59095
|
+
const formData = new FormData();
|
|
59096
|
+
formData.append("file", file);
|
|
59097
|
+
formData.append("upload_preset", uploadPreset);
|
|
59098
|
+
const response = await fetch(
|
|
59099
|
+
`https://api.cloudinary.com/v1_1/${cloudName}/auto/upload`,
|
|
59100
|
+
{
|
|
59101
|
+
method: "POST",
|
|
59102
|
+
body: formData
|
|
59103
|
+
}
|
|
59104
|
+
);
|
|
59105
|
+
return response.json();
|
|
59106
|
+
}
|
|
59107
|
+
break;
|
|
59108
|
+
}
|
|
59109
|
+
return null;
|
|
59110
|
+
}, [serviceConfig, uploadStrategy, endpoint, headers]);
|
|
59111
|
+
const resizeImage = useCallback(async (file) => {
|
|
59112
|
+
if (!imageResize || !file.type.startsWith("image/"))
|
|
59113
|
+
return file;
|
|
59114
|
+
const { maxWidth, maxHeight, quality } = imageResize;
|
|
59115
|
+
return new Promise((resolve) => {
|
|
59116
|
+
const img = new Image();
|
|
59117
|
+
const reader = new FileReader();
|
|
59118
|
+
reader.onload = (e) => {
|
|
59119
|
+
img.src = e.target?.result;
|
|
59120
|
+
img.onload = () => {
|
|
59121
|
+
const canvas = document.createElement("canvas");
|
|
59122
|
+
const ctx = canvas.getContext("2d");
|
|
59123
|
+
let width = img.width;
|
|
59124
|
+
let height = img.height;
|
|
59125
|
+
if (width > maxWidth || height > maxHeight) {
|
|
59126
|
+
const aspectRatio = width / height;
|
|
59127
|
+
if (width > height) {
|
|
59128
|
+
width = maxWidth;
|
|
59129
|
+
height = maxWidth / aspectRatio;
|
|
59130
|
+
} else {
|
|
59131
|
+
height = maxHeight;
|
|
59132
|
+
width = maxHeight * aspectRatio;
|
|
59133
|
+
}
|
|
59134
|
+
}
|
|
59135
|
+
canvas.width = width;
|
|
59136
|
+
canvas.height = height;
|
|
59137
|
+
ctx.drawImage(img, 0, 0, width, height);
|
|
59138
|
+
canvas.toBlob(
|
|
59139
|
+
(blob) => {
|
|
59140
|
+
if (blob) {
|
|
59141
|
+
const resizedFile = new window.File([blob], file.name, {
|
|
59142
|
+
type: file.type,
|
|
59143
|
+
lastModified: Date.now()
|
|
59144
|
+
});
|
|
59145
|
+
resolve(resizedFile);
|
|
59146
|
+
} else {
|
|
59147
|
+
resolve(file);
|
|
59148
|
+
}
|
|
59149
|
+
},
|
|
59150
|
+
file.type,
|
|
59151
|
+
quality
|
|
59152
|
+
);
|
|
59153
|
+
};
|
|
59154
|
+
};
|
|
59155
|
+
reader.readAsDataURL(file);
|
|
59156
|
+
});
|
|
59157
|
+
}, [imageResize]);
|
|
59031
59158
|
const uploadFileChunked = useCallback(async (fileItem) => {
|
|
59032
59159
|
const { file } = fileItem;
|
|
59033
59160
|
const chunks = [];
|
|
@@ -59053,7 +59180,10 @@ var MoonUIFileUploadPro = t__default.forwardRef(
|
|
|
59053
59180
|
if (abortController.signal.aborted) {
|
|
59054
59181
|
throw new Error("Upload cancelled");
|
|
59055
59182
|
}
|
|
59056
|
-
|
|
59183
|
+
const serviceResult = await uploadToService(fileItem);
|
|
59184
|
+
if (!serviceResult) {
|
|
59185
|
+
await new Promise((resolve) => setTimeout(resolve, 100 + Math.random() * 200));
|
|
59186
|
+
}
|
|
59057
59187
|
uploadedBytes += chunk.end - chunk.start;
|
|
59058
59188
|
const progress = Math.round(uploadedBytes / file.size * 100);
|
|
59059
59189
|
const elapsedTime = (Date.now() - startTime) / 1e3;
|
|
@@ -59090,14 +59220,14 @@ var MoonUIFileUploadPro = t__default.forwardRef(
|
|
|
59090
59220
|
const fileArray = Array.from(fileList);
|
|
59091
59221
|
setError(null);
|
|
59092
59222
|
if (files.length + fileArray.length > maxFiles) {
|
|
59093
|
-
setError(`
|
|
59223
|
+
setError(`Maximum ${maxFiles} files allowed`);
|
|
59094
59224
|
return;
|
|
59095
59225
|
}
|
|
59096
59226
|
if (maxTotalSize) {
|
|
59097
59227
|
const currentSize = files.reduce((sum, f) => sum + f.file.size, 0);
|
|
59098
59228
|
const newSize = fileArray.reduce((sum, f) => sum + f.size, 0);
|
|
59099
59229
|
if (currentSize + newSize > maxTotalSize) {
|
|
59100
|
-
setError(`
|
|
59230
|
+
setError(`Total file size exceeds ${formatFileSize(maxTotalSize)} limit`);
|
|
59101
59231
|
return;
|
|
59102
59232
|
}
|
|
59103
59233
|
}
|
|
@@ -59111,7 +59241,7 @@ var MoonUIFileUploadPro = t__default.forwardRef(
|
|
|
59111
59241
|
}
|
|
59112
59242
|
const isDuplicate = await checkDuplicate(file);
|
|
59113
59243
|
if (isDuplicate) {
|
|
59114
|
-
errors.push(`${file.name}:
|
|
59244
|
+
errors.push(`${file.name}: File already exists`);
|
|
59115
59245
|
continue;
|
|
59116
59246
|
}
|
|
59117
59247
|
validFiles.push(file);
|
|
@@ -59124,14 +59254,15 @@ var MoonUIFileUploadPro = t__default.forwardRef(
|
|
|
59124
59254
|
const newFileItems = [];
|
|
59125
59255
|
for (const file of validFiles) {
|
|
59126
59256
|
const id = `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
|
|
59127
|
-
const
|
|
59128
|
-
const
|
|
59257
|
+
const processedFile = await resizeImage(file);
|
|
59258
|
+
const preview = await createPreview(processedFile);
|
|
59259
|
+
const metadata = await createMetadata(processedFile);
|
|
59129
59260
|
newFileItems.push({
|
|
59130
59261
|
id,
|
|
59131
|
-
file,
|
|
59262
|
+
file: processedFile,
|
|
59132
59263
|
status: "pending",
|
|
59133
59264
|
progress: 0,
|
|
59134
|
-
totalBytes:
|
|
59265
|
+
totalBytes: processedFile.size,
|
|
59135
59266
|
preview,
|
|
59136
59267
|
metadata
|
|
59137
59268
|
});
|
|
@@ -59157,7 +59288,8 @@ var MoonUIFileUploadPro = t__default.forwardRef(
|
|
|
59157
59288
|
createPreview,
|
|
59158
59289
|
createMetadata,
|
|
59159
59290
|
onUpload,
|
|
59160
|
-
uploadFileChunked
|
|
59291
|
+
uploadFileChunked,
|
|
59292
|
+
resizeImage
|
|
59161
59293
|
]);
|
|
59162
59294
|
const handleDrop = useCallback((e) => {
|
|
59163
59295
|
e.preventDefault();
|
|
@@ -59298,28 +59430,28 @@ var MoonUIFileUploadPro = t__default.forwardRef(
|
|
|
59298
59430
|
}
|
|
59299
59431
|
),
|
|
59300
59432
|
/* @__PURE__ */ jsxs("div", { children: [
|
|
59301
|
-
/* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold", children: isDragOver ? "
|
|
59302
|
-
/* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground mt-1", children: "
|
|
59433
|
+
/* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold", children: isDragOver ? "Drop files here" : "Upload Files" }),
|
|
59434
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground mt-1", children: "Drag and drop files or click to select" }),
|
|
59303
59435
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center gap-4 mt-3 text-xs text-muted-foreground", children: [
|
|
59304
59436
|
/* @__PURE__ */ jsxs("span", { children: [
|
|
59305
|
-
"
|
|
59437
|
+
"Max ",
|
|
59306
59438
|
maxFiles,
|
|
59307
|
-
"
|
|
59439
|
+
" files"
|
|
59308
59440
|
] }),
|
|
59309
59441
|
/* @__PURE__ */ jsx("span", { children: "\u2022" }),
|
|
59310
59442
|
/* @__PURE__ */ jsxs("span", { children: [
|
|
59311
59443
|
formatFileSize(maxSize),
|
|
59312
|
-
"
|
|
59444
|
+
" per file"
|
|
59313
59445
|
] }),
|
|
59314
59446
|
resumable && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
59315
59447
|
/* @__PURE__ */ jsx("span", { children: "\u2022" }),
|
|
59316
|
-
/* @__PURE__ */ jsx("span", { children: "
|
|
59448
|
+
/* @__PURE__ */ jsx("span", { children: "Resumable" })
|
|
59317
59449
|
] })
|
|
59318
59450
|
] })
|
|
59319
59451
|
] }),
|
|
59320
59452
|
/* @__PURE__ */ jsxs(MoonUIButtonPro, { variant: "outline", disabled, type: "button", children: [
|
|
59321
59453
|
/* @__PURE__ */ jsx(Upload, { className: "mr-2 h-4 w-4" }),
|
|
59322
|
-
"
|
|
59454
|
+
"Select Files"
|
|
59323
59455
|
] })
|
|
59324
59456
|
] })
|
|
59325
59457
|
]
|
|
@@ -59358,7 +59490,7 @@ var MoonUIFileUploadPro = t__default.forwardRef(
|
|
|
59358
59490
|
children: [
|
|
59359
59491
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
59360
59492
|
/* @__PURE__ */ jsxs("h4", { className: "text-sm font-medium", children: [
|
|
59361
|
-
"
|
|
59493
|
+
"Uploaded Files (",
|
|
59362
59494
|
files.length,
|
|
59363
59495
|
")"
|
|
59364
59496
|
] }),
|
|
@@ -59368,7 +59500,7 @@ var MoonUIFileUploadPro = t__default.forwardRef(
|
|
|
59368
59500
|
variant: "ghost",
|
|
59369
59501
|
size: "sm",
|
|
59370
59502
|
onClick: selectedIds.length === files.length ? handleClearSelection : handleSelectAll,
|
|
59371
|
-
children: selectedIds.length === files.length ? "
|
|
59503
|
+
children: selectedIds.length === files.length ? "Clear Selection" : "Select All"
|
|
59372
59504
|
}
|
|
59373
59505
|
)
|
|
59374
59506
|
] }),
|
|
@@ -59522,7 +59654,7 @@ var FileUploadItem = ({
|
|
|
59522
59654
|
file.status === "success" && /* @__PURE__ */ jsx(CheckCircle2, { className: "h-3 w-3 mr-1" }),
|
|
59523
59655
|
file.status === "error" && /* @__PURE__ */ jsx(AlertCircle, { className: "h-3 w-3 mr-1" }),
|
|
59524
59656
|
file.status === "paused" && /* @__PURE__ */ jsx(Pause, { className: "h-3 w-3 mr-1" }),
|
|
59525
|
-
file.status === "pending" ? "
|
|
59657
|
+
file.status === "pending" ? "Pending" : file.status === "uploading" ? "Uploading" : file.status === "paused" ? "Paused" : file.status === "success" ? "Completed" : file.status === "error" ? "Error" : "Cancelled"
|
|
59526
59658
|
]
|
|
59527
59659
|
}
|
|
59528
59660
|
)
|
|
@@ -59539,7 +59671,7 @@ var FileUploadItem = ({
|
|
|
59539
59671
|
] }),
|
|
59540
59672
|
file.estimatedTime && file.estimatedTime > 0 && /* @__PURE__ */ jsxs("span", { children: [
|
|
59541
59673
|
formatTime(file.estimatedTime),
|
|
59542
|
-
"
|
|
59674
|
+
" remaining"
|
|
59543
59675
|
] })
|
|
59544
59676
|
] }),
|
|
59545
59677
|
/* @__PURE__ */ jsx(MoonUIProgressPro, { value: file.progress, className: "h-1" })
|
|
@@ -59583,19 +59715,19 @@ var FileUploadItem = ({
|
|
|
59583
59715
|
/* @__PURE__ */ jsxs(MoonUIDropdownMenuContentPro, { align: "end", children: [
|
|
59584
59716
|
canPreview && /* @__PURE__ */ jsxs(MoonUIDropdownMenuItemPro, { onClick: onPreview, children: [
|
|
59585
59717
|
/* @__PURE__ */ jsx(Eye, { className: "mr-2 h-4 w-4" }),
|
|
59586
|
-
"
|
|
59718
|
+
"Preview"
|
|
59587
59719
|
] }),
|
|
59588
59720
|
/* @__PURE__ */ jsxs(MoonUIDropdownMenuItemPro, { children: [
|
|
59589
59721
|
/* @__PURE__ */ jsx(Copy, { className: "mr-2 h-4 w-4" }),
|
|
59590
|
-
"
|
|
59722
|
+
"Copy Link"
|
|
59591
59723
|
] }),
|
|
59592
59724
|
/* @__PURE__ */ jsxs(MoonUIDropdownMenuItemPro, { children: [
|
|
59593
59725
|
/* @__PURE__ */ jsx(Share, { className: "mr-2 h-4 w-4" }),
|
|
59594
|
-
"
|
|
59726
|
+
"Share"
|
|
59595
59727
|
] }),
|
|
59596
59728
|
/* @__PURE__ */ jsxs(MoonUIDropdownMenuItemPro, { onClick: onRemove, className: "text-destructive", children: [
|
|
59597
59729
|
/* @__PURE__ */ jsx(Trash2, { className: "mr-2 h-4 w-4" }),
|
|
59598
|
-
"
|
|
59730
|
+
"Delete"
|
|
59599
59731
|
] })
|
|
59600
59732
|
] })
|
|
59601
59733
|
] })
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@moontra/moonui-pro",
|
|
3
|
-
"version": "2.11.
|
|
3
|
+
"version": "2.11.2",
|
|
4
4
|
"description": "Premium React components for MoonUI - Advanced UI library with 50+ pro components including performance, interactive, and gesture components",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.mjs",
|
|
@@ -78,6 +78,28 @@ export interface FileUploadProProps {
|
|
|
78
78
|
endpoint?: string
|
|
79
79
|
headers?: Record<string, string>
|
|
80
80
|
|
|
81
|
+
// Service integrations
|
|
82
|
+
serviceConfig?: {
|
|
83
|
+
type: 'aws-s3' | 'cloudinary' | 'firebase' | 'custom'
|
|
84
|
+
awsConfig?: {
|
|
85
|
+
region: string
|
|
86
|
+
bucket: string
|
|
87
|
+
accessKeyId?: string
|
|
88
|
+
secretAccessKey?: string
|
|
89
|
+
sessionToken?: string
|
|
90
|
+
}
|
|
91
|
+
cloudinaryConfig?: {
|
|
92
|
+
cloudName: string
|
|
93
|
+
uploadPreset: string
|
|
94
|
+
apiKey?: string
|
|
95
|
+
apiSecret?: string
|
|
96
|
+
}
|
|
97
|
+
firebaseConfig?: {
|
|
98
|
+
storageBucket: string
|
|
99
|
+
folder?: string
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
81
103
|
// Callbacks
|
|
82
104
|
onUpload?: (files: FileUploadItem[]) => Promise<void>
|
|
83
105
|
onProgress?: (fileId: string, progress: number) => void
|
|
@@ -200,26 +222,67 @@ const generateFileHash = async (file: File): Promise<string> => {
|
|
|
200
222
|
|
|
201
223
|
const createImagePreview = (file: File): Promise<FilePreview> => {
|
|
202
224
|
return new Promise((resolve) => {
|
|
203
|
-
const
|
|
204
|
-
const url = URL.createObjectURL(file)
|
|
225
|
+
const reader = new FileReader()
|
|
205
226
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
227
|
+
reader.onload = (e) => {
|
|
228
|
+
const result = e.target?.result as string
|
|
229
|
+
const img = new Image()
|
|
230
|
+
|
|
231
|
+
img.onload = () => {
|
|
232
|
+
// Create high-quality thumbnail
|
|
233
|
+
const canvas = document.createElement('canvas')
|
|
234
|
+
const ctx = canvas.getContext('2d')!
|
|
235
|
+
|
|
236
|
+
// Calculate thumbnail dimensions (max 400px)
|
|
237
|
+
const maxThumbSize = 400
|
|
238
|
+
let thumbWidth = img.width
|
|
239
|
+
let thumbHeight = img.height
|
|
240
|
+
|
|
241
|
+
if (thumbWidth > maxThumbSize || thumbHeight > maxThumbSize) {
|
|
242
|
+
const aspectRatio = img.width / img.height
|
|
243
|
+
if (aspectRatio > 1) {
|
|
244
|
+
thumbWidth = maxThumbSize
|
|
245
|
+
thumbHeight = maxThumbSize / aspectRatio
|
|
246
|
+
} else {
|
|
247
|
+
thumbHeight = maxThumbSize
|
|
248
|
+
thumbWidth = maxThumbSize * aspectRatio
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
canvas.width = thumbWidth
|
|
253
|
+
canvas.height = thumbHeight
|
|
254
|
+
ctx.drawImage(img, 0, 0, thumbWidth, thumbHeight)
|
|
255
|
+
|
|
256
|
+
const thumbnail = canvas.toDataURL('image/jpeg', 0.9)
|
|
257
|
+
|
|
258
|
+
resolve({
|
|
259
|
+
type: 'image',
|
|
260
|
+
url: result,
|
|
261
|
+
thumbnail,
|
|
262
|
+
dimensions: { width: img.width, height: img.height }
|
|
263
|
+
})
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
img.onerror = () => {
|
|
267
|
+
resolve({
|
|
268
|
+
type: 'image',
|
|
269
|
+
url: result,
|
|
270
|
+
thumbnail: result
|
|
271
|
+
})
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
img.src = result
|
|
213
275
|
}
|
|
214
276
|
|
|
215
|
-
|
|
277
|
+
reader.onerror = () => {
|
|
278
|
+
const url = URL.createObjectURL(file)
|
|
216
279
|
resolve({
|
|
217
280
|
type: 'image',
|
|
218
281
|
url
|
|
219
282
|
})
|
|
220
283
|
}
|
|
221
284
|
|
|
222
|
-
|
|
285
|
+
reader.readAsDataURL(file)
|
|
223
286
|
})
|
|
224
287
|
}
|
|
225
288
|
|
|
@@ -284,15 +347,15 @@ const BulkActions = ({
|
|
|
284
347
|
exit={{ opacity: 0, y: -10 }}
|
|
285
348
|
className="flex items-center gap-2 p-3 bg-primary/5 rounded-lg border"
|
|
286
349
|
>
|
|
287
|
-
<span className="text-sm font-medium">{selectedIds.length}
|
|
350
|
+
<span className="text-sm font-medium">{selectedIds.length} files selected</span>
|
|
288
351
|
<div className="flex gap-1 ml-auto">
|
|
289
352
|
<Button variant="ghost" size="sm" onClick={onBulkDownload}>
|
|
290
353
|
<Download className="h-4 w-4 mr-1" />
|
|
291
|
-
|
|
354
|
+
Download
|
|
292
355
|
</Button>
|
|
293
356
|
<Button variant="ghost" size="sm" onClick={onBulkRemove}>
|
|
294
357
|
<Trash2 className="h-4 w-4 mr-1" />
|
|
295
|
-
|
|
358
|
+
Delete
|
|
296
359
|
</Button>
|
|
297
360
|
<Button variant="ghost" size="sm" onClick={onClearSelection}>
|
|
298
361
|
<X className="h-4 w-4" />
|
|
@@ -329,40 +392,53 @@ const FilePreviewModal = ({
|
|
|
329
392
|
|
|
330
393
|
<div className="flex items-center justify-center p-4 bg-muted/20 rounded-lg">
|
|
331
394
|
{file.preview.type === 'image' && (
|
|
332
|
-
<
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
395
|
+
<div className="relative">
|
|
396
|
+
<img
|
|
397
|
+
src={file.preview.url}
|
|
398
|
+
alt={file.file.name}
|
|
399
|
+
className="max-w-full max-h-[60vh] object-contain rounded shadow-lg"
|
|
400
|
+
loading="eager"
|
|
401
|
+
/>
|
|
402
|
+
{/* Image loading indicator */}
|
|
403
|
+
<div className="absolute inset-0 flex items-center justify-center bg-background/80 rounded opacity-0 pointer-events-none transition-opacity duration-300">
|
|
404
|
+
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
|
|
405
|
+
</div>
|
|
406
|
+
</div>
|
|
337
407
|
)}
|
|
338
408
|
|
|
339
409
|
{file.preview.type === 'video' && (
|
|
340
|
-
<
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
410
|
+
<div className="relative w-full max-w-4xl">
|
|
411
|
+
<video
|
|
412
|
+
src={file.preview.url}
|
|
413
|
+
controls
|
|
414
|
+
className="w-full max-h-[60vh] rounded shadow-lg"
|
|
415
|
+
poster={file.preview.thumbnail}
|
|
416
|
+
/>
|
|
417
|
+
</div>
|
|
345
418
|
)}
|
|
346
419
|
|
|
347
420
|
{file.preview.type === 'audio' && (
|
|
348
|
-
<div className="w-full max-w-md">
|
|
349
|
-
<
|
|
421
|
+
<div className="w-full max-w-md space-y-4">
|
|
422
|
+
<div className="p-8 bg-gradient-to-br from-primary/10 to-primary/5 rounded-lg">
|
|
423
|
+
<Music className="h-16 w-16 mx-auto text-primary mb-4" />
|
|
424
|
+
<audio src={file.preview.url} controls className="w-full" />
|
|
425
|
+
</div>
|
|
350
426
|
</div>
|
|
351
427
|
)}
|
|
352
428
|
|
|
353
429
|
{!['image', 'video', 'audio'].includes(file.preview.type) && (
|
|
354
430
|
<div className="text-center py-8">
|
|
355
431
|
{getFileIcon(file.file.type, 'lg')}
|
|
356
|
-
<p className="mt-2 text-muted-foreground"
|
|
432
|
+
<p className="mt-2 text-muted-foreground">Preview not available</p>
|
|
357
433
|
</div>
|
|
358
434
|
)}
|
|
359
435
|
</div>
|
|
360
436
|
|
|
361
437
|
{file.preview.dimensions && (
|
|
362
438
|
<div className="flex gap-4 text-sm text-muted-foreground">
|
|
363
|
-
<span>
|
|
439
|
+
<span>Dimensions: {file.preview.dimensions.width} × {file.preview.dimensions.height}</span>
|
|
364
440
|
{file.preview.duration && (
|
|
365
|
-
<span>
|
|
441
|
+
<span>Duration: {formatTime(file.preview.duration)}</span>
|
|
366
442
|
)}
|
|
367
443
|
</div>
|
|
368
444
|
)}
|
|
@@ -401,6 +477,10 @@ export const MoonUIFileUploadPro = React.forwardRef<HTMLDivElement, FileUploadPr
|
|
|
401
477
|
onPreview,
|
|
402
478
|
onBulkSelect,
|
|
403
479
|
customValidation,
|
|
480
|
+
serviceConfig,
|
|
481
|
+
imageResize,
|
|
482
|
+
endpoint,
|
|
483
|
+
headers,
|
|
404
484
|
...props
|
|
405
485
|
}, ref) => {
|
|
406
486
|
|
|
@@ -428,15 +508,15 @@ export const MoonUIFileUploadPro = React.forwardRef<HTMLDivElement, FileUploadPr
|
|
|
428
508
|
<Lock className="h-6 w-6 text-purple-600 dark:text-purple-400" />
|
|
429
509
|
</div>
|
|
430
510
|
<div>
|
|
431
|
-
<h3 className="font-semibold text-lg mb-2">Pro
|
|
511
|
+
<h3 className="font-semibold text-lg mb-2">Pro Feature</h3>
|
|
432
512
|
<p className="text-muted-foreground text-sm mb-4">
|
|
433
|
-
|
|
513
|
+
Advanced File Upload is exclusive to MoonUI Pro subscribers.
|
|
434
514
|
</p>
|
|
435
515
|
<div className="flex gap-3 justify-center">
|
|
436
516
|
<a href="/pricing">
|
|
437
517
|
<Button size="sm">
|
|
438
518
|
<Sparkles className="mr-2 h-4 w-4" />
|
|
439
|
-
Pro
|
|
519
|
+
Upgrade to Pro
|
|
440
520
|
</Button>
|
|
441
521
|
</a>
|
|
442
522
|
</div>
|
|
@@ -451,12 +531,12 @@ export const MoonUIFileUploadPro = React.forwardRef<HTMLDivElement, FileUploadPr
|
|
|
451
531
|
const validateFile = useCallback(async (file: File): Promise<string | null> => {
|
|
452
532
|
// Boyut kontrolü
|
|
453
533
|
if (file.size > maxSize) {
|
|
454
|
-
return `
|
|
534
|
+
return `File size exceeds ${formatFileSize(maxSize)} limit`
|
|
455
535
|
}
|
|
456
536
|
|
|
457
537
|
// MIME type kontrolü
|
|
458
538
|
if (allowedMimeTypes.length > 0 && !allowedMimeTypes.includes(file.type)) {
|
|
459
|
-
return `
|
|
539
|
+
return `File type ${file.type} is not supported`
|
|
460
540
|
}
|
|
461
541
|
|
|
462
542
|
// Accept kontrolü
|
|
@@ -470,7 +550,7 @@ export const MoonUIFileUploadPro = React.forwardRef<HTMLDivElement, FileUploadPr
|
|
|
470
550
|
})
|
|
471
551
|
|
|
472
552
|
if (!isAccepted) {
|
|
473
|
-
return `
|
|
553
|
+
return `File type not accepted`
|
|
474
554
|
}
|
|
475
555
|
}
|
|
476
556
|
|
|
@@ -512,7 +592,7 @@ export const MoonUIFileUploadPro = React.forwardRef<HTMLDivElement, FileUploadPr
|
|
|
512
592
|
}
|
|
513
593
|
}
|
|
514
594
|
} catch (error) {
|
|
515
|
-
console.warn('
|
|
595
|
+
console.warn('Failed to create preview:', error)
|
|
516
596
|
}
|
|
517
597
|
|
|
518
598
|
return undefined
|
|
@@ -534,6 +614,115 @@ export const MoonUIFileUploadPro = React.forwardRef<HTMLDivElement, FileUploadPr
|
|
|
534
614
|
return metadata
|
|
535
615
|
}, [duplicateCheck])
|
|
536
616
|
|
|
617
|
+
// Service specific upload handler
|
|
618
|
+
const uploadToService = useCallback(async (fileItem: FileUploadItem): Promise<any> => {
|
|
619
|
+
if (!serviceConfig) return null
|
|
620
|
+
|
|
621
|
+
const { file } = fileItem
|
|
622
|
+
|
|
623
|
+
switch (serviceConfig.type) {
|
|
624
|
+
case 'aws-s3':
|
|
625
|
+
// AWS S3 upload logic
|
|
626
|
+
if (uploadStrategy === 'presigned' && endpoint) {
|
|
627
|
+
const response = await fetch(endpoint, {
|
|
628
|
+
method: 'PUT',
|
|
629
|
+
body: file,
|
|
630
|
+
headers: {
|
|
631
|
+
'Content-Type': file.type,
|
|
632
|
+
...headers
|
|
633
|
+
}
|
|
634
|
+
})
|
|
635
|
+
return response.ok ? { url: response.url } : null
|
|
636
|
+
}
|
|
637
|
+
break
|
|
638
|
+
|
|
639
|
+
case 'cloudinary':
|
|
640
|
+
// Cloudinary upload logic
|
|
641
|
+
if (serviceConfig.cloudinaryConfig) {
|
|
642
|
+
const { cloudName, uploadPreset } = serviceConfig.cloudinaryConfig
|
|
643
|
+
const formData = new FormData()
|
|
644
|
+
formData.append('file', file)
|
|
645
|
+
formData.append('upload_preset', uploadPreset)
|
|
646
|
+
|
|
647
|
+
const response = await fetch(
|
|
648
|
+
`https://api.cloudinary.com/v1_1/${cloudName}/auto/upload`,
|
|
649
|
+
{
|
|
650
|
+
method: 'POST',
|
|
651
|
+
body: formData
|
|
652
|
+
}
|
|
653
|
+
)
|
|
654
|
+
return response.json()
|
|
655
|
+
}
|
|
656
|
+
break
|
|
657
|
+
|
|
658
|
+
case 'firebase':
|
|
659
|
+
// Firebase upload would be handled by the onUpload callback
|
|
660
|
+
// as it requires Firebase SDK integration
|
|
661
|
+
break
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
return null
|
|
665
|
+
}, [serviceConfig, uploadStrategy, endpoint, headers])
|
|
666
|
+
|
|
667
|
+
// Image resize handler
|
|
668
|
+
const resizeImage = useCallback(async (file: File): Promise<File> => {
|
|
669
|
+
if (!imageResize || !file.type.startsWith('image/')) return file
|
|
670
|
+
|
|
671
|
+
const { maxWidth, maxHeight, quality } = imageResize
|
|
672
|
+
|
|
673
|
+
return new Promise((resolve) => {
|
|
674
|
+
const img = new Image()
|
|
675
|
+
const reader = new FileReader()
|
|
676
|
+
|
|
677
|
+
reader.onload = (e) => {
|
|
678
|
+
img.src = e.target?.result as string
|
|
679
|
+
|
|
680
|
+
img.onload = () => {
|
|
681
|
+
const canvas = document.createElement('canvas')
|
|
682
|
+
const ctx = canvas.getContext('2d')!
|
|
683
|
+
|
|
684
|
+
let width = img.width
|
|
685
|
+
let height = img.height
|
|
686
|
+
|
|
687
|
+
// Calculate new dimensions
|
|
688
|
+
if (width > maxWidth || height > maxHeight) {
|
|
689
|
+
const aspectRatio = width / height
|
|
690
|
+
if (width > height) {
|
|
691
|
+
width = maxWidth
|
|
692
|
+
height = maxWidth / aspectRatio
|
|
693
|
+
} else {
|
|
694
|
+
height = maxHeight
|
|
695
|
+
width = maxHeight * aspectRatio
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
canvas.width = width
|
|
700
|
+
canvas.height = height
|
|
701
|
+
ctx.drawImage(img, 0, 0, width, height)
|
|
702
|
+
|
|
703
|
+
canvas.toBlob(
|
|
704
|
+
(blob) => {
|
|
705
|
+
if (blob) {
|
|
706
|
+
// Create a resized file
|
|
707
|
+
const resizedFile = new window.File([blob], file.name, {
|
|
708
|
+
type: file.type,
|
|
709
|
+
lastModified: Date.now()
|
|
710
|
+
})
|
|
711
|
+
resolve(resizedFile)
|
|
712
|
+
} else {
|
|
713
|
+
resolve(file)
|
|
714
|
+
}
|
|
715
|
+
},
|
|
716
|
+
file.type,
|
|
717
|
+
quality
|
|
718
|
+
)
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
reader.readAsDataURL(file)
|
|
723
|
+
})
|
|
724
|
+
}, [imageResize])
|
|
725
|
+
|
|
537
726
|
// Chunked upload simülasyonu
|
|
538
727
|
const uploadFileChunked = useCallback(async (fileItem: FileUploadItem) => {
|
|
539
728
|
const { file } = fileItem
|
|
@@ -571,8 +760,13 @@ export const MoonUIFileUploadPro = React.forwardRef<HTMLDivElement, FileUploadPr
|
|
|
571
760
|
throw new Error('Upload cancelled')
|
|
572
761
|
}
|
|
573
762
|
|
|
574
|
-
//
|
|
575
|
-
|
|
763
|
+
// Try service-specific upload first
|
|
764
|
+
const serviceResult = await uploadToService(fileItem)
|
|
765
|
+
|
|
766
|
+
if (!serviceResult) {
|
|
767
|
+
// Fallback to chunk upload simulation
|
|
768
|
+
await new Promise(resolve => setTimeout(resolve, 100 + Math.random() * 200))
|
|
769
|
+
}
|
|
576
770
|
|
|
577
771
|
uploadedBytes += (chunk.end - chunk.start)
|
|
578
772
|
const progress = Math.round((uploadedBytes / file.size) * 100)
|
|
@@ -630,7 +824,7 @@ export const MoonUIFileUploadPro = React.forwardRef<HTMLDivElement, FileUploadPr
|
|
|
630
824
|
|
|
631
825
|
// Toplam dosya sayısı kontrolü
|
|
632
826
|
if (files.length + fileArray.length > maxFiles) {
|
|
633
|
-
setError(`
|
|
827
|
+
setError(`Maximum ${maxFiles} files allowed`)
|
|
634
828
|
return
|
|
635
829
|
}
|
|
636
830
|
|
|
@@ -640,7 +834,7 @@ export const MoonUIFileUploadPro = React.forwardRef<HTMLDivElement, FileUploadPr
|
|
|
640
834
|
const newSize = fileArray.reduce((sum, f) => sum + f.size, 0)
|
|
641
835
|
|
|
642
836
|
if (currentSize + newSize > maxTotalSize) {
|
|
643
|
-
setError(`
|
|
837
|
+
setError(`Total file size exceeds ${formatFileSize(maxTotalSize)} limit`)
|
|
644
838
|
return
|
|
645
839
|
}
|
|
646
840
|
}
|
|
@@ -658,7 +852,7 @@ export const MoonUIFileUploadPro = React.forwardRef<HTMLDivElement, FileUploadPr
|
|
|
658
852
|
|
|
659
853
|
const isDuplicate = await checkDuplicate(file)
|
|
660
854
|
if (isDuplicate) {
|
|
661
|
-
errors.push(`${file.name}:
|
|
855
|
+
errors.push(`${file.name}: File already exists`)
|
|
662
856
|
continue
|
|
663
857
|
}
|
|
664
858
|
|
|
@@ -676,15 +870,19 @@ export const MoonUIFileUploadPro = React.forwardRef<HTMLDivElement, FileUploadPr
|
|
|
676
870
|
|
|
677
871
|
for (const file of validFiles) {
|
|
678
872
|
const id = `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`
|
|
679
|
-
|
|
680
|
-
|
|
873
|
+
|
|
874
|
+
// Resize image if needed
|
|
875
|
+
const processedFile = await resizeImage(file)
|
|
876
|
+
|
|
877
|
+
const preview = await createPreview(processedFile)
|
|
878
|
+
const metadata = await createMetadata(processedFile)
|
|
681
879
|
|
|
682
880
|
newFileItems.push({
|
|
683
881
|
id,
|
|
684
|
-
file,
|
|
882
|
+
file: processedFile,
|
|
685
883
|
status: 'pending',
|
|
686
884
|
progress: 0,
|
|
687
|
-
totalBytes:
|
|
885
|
+
totalBytes: processedFile.size,
|
|
688
886
|
preview,
|
|
689
887
|
metadata
|
|
690
888
|
})
|
|
@@ -714,7 +912,8 @@ export const MoonUIFileUploadPro = React.forwardRef<HTMLDivElement, FileUploadPr
|
|
|
714
912
|
createPreview,
|
|
715
913
|
createMetadata,
|
|
716
914
|
onUpload,
|
|
717
|
-
uploadFileChunked
|
|
915
|
+
uploadFileChunked,
|
|
916
|
+
resizeImage
|
|
718
917
|
])
|
|
719
918
|
|
|
720
919
|
// Drag & Drop handlers
|
|
@@ -886,19 +1085,19 @@ export const MoonUIFileUploadPro = React.forwardRef<HTMLDivElement, FileUploadPr
|
|
|
886
1085
|
|
|
887
1086
|
<div>
|
|
888
1087
|
<h3 className="text-lg font-semibold">
|
|
889
|
-
{isDragOver ? '
|
|
1088
|
+
{isDragOver ? 'Drop files here' : 'Upload Files'}
|
|
890
1089
|
</h3>
|
|
891
1090
|
<p className="text-sm text-muted-foreground mt-1">
|
|
892
|
-
|
|
1091
|
+
Drag and drop files or click to select
|
|
893
1092
|
</p>
|
|
894
1093
|
<div className="flex items-center justify-center gap-4 mt-3 text-xs text-muted-foreground">
|
|
895
|
-
<span>
|
|
1094
|
+
<span>Max {maxFiles} files</span>
|
|
896
1095
|
<span>•</span>
|
|
897
|
-
<span>{formatFileSize(maxSize)}
|
|
1096
|
+
<span>{formatFileSize(maxSize)} per file</span>
|
|
898
1097
|
{resumable && (
|
|
899
1098
|
<>
|
|
900
1099
|
<span>•</span>
|
|
901
|
-
<span>
|
|
1100
|
+
<span>Resumable</span>
|
|
902
1101
|
</>
|
|
903
1102
|
)}
|
|
904
1103
|
</div>
|
|
@@ -906,7 +1105,7 @@ export const MoonUIFileUploadPro = React.forwardRef<HTMLDivElement, FileUploadPr
|
|
|
906
1105
|
|
|
907
1106
|
<Button variant="outline" disabled={disabled} type="button">
|
|
908
1107
|
<Upload className="mr-2 h-4 w-4" />
|
|
909
|
-
|
|
1108
|
+
Select Files
|
|
910
1109
|
</Button>
|
|
911
1110
|
</div>
|
|
912
1111
|
</motion.div>
|
|
@@ -946,7 +1145,7 @@ export const MoonUIFileUploadPro = React.forwardRef<HTMLDivElement, FileUploadPr
|
|
|
946
1145
|
{/* List Header */}
|
|
947
1146
|
<div className="flex items-center justify-between">
|
|
948
1147
|
<h4 className="text-sm font-medium">
|
|
949
|
-
|
|
1148
|
+
Uploaded Files ({files.length})
|
|
950
1149
|
</h4>
|
|
951
1150
|
{allowBulkOperations && files.length > 1 && (
|
|
952
1151
|
<Button
|
|
@@ -954,7 +1153,7 @@ export const MoonUIFileUploadPro = React.forwardRef<HTMLDivElement, FileUploadPr
|
|
|
954
1153
|
size="sm"
|
|
955
1154
|
onClick={selectedIds.length === files.length ? handleClearSelection : handleSelectAll}
|
|
956
1155
|
>
|
|
957
|
-
{selectedIds.length === files.length ? '
|
|
1156
|
+
{selectedIds.length === files.length ? 'Clear Selection' : 'Select All'}
|
|
958
1157
|
</Button>
|
|
959
1158
|
)}
|
|
960
1159
|
</div>
|
|
@@ -1152,11 +1351,11 @@ const FileUploadItem = ({
|
|
|
1152
1351
|
{file.status === 'success' && <CheckCircle2 className="h-3 w-3 mr-1" />}
|
|
1153
1352
|
{file.status === 'error' && <AlertCircle className="h-3 w-3 mr-1" />}
|
|
1154
1353
|
{file.status === 'paused' && <Pause className="h-3 w-3 mr-1" />}
|
|
1155
|
-
{file.status === 'pending' ? '
|
|
1156
|
-
file.status === 'uploading' ? '
|
|
1157
|
-
file.status === 'paused' ? '
|
|
1158
|
-
file.status === 'success' ? '
|
|
1159
|
-
file.status === 'error' ? '
|
|
1354
|
+
{file.status === 'pending' ? 'Pending' :
|
|
1355
|
+
file.status === 'uploading' ? 'Uploading' :
|
|
1356
|
+
file.status === 'paused' ? 'Paused' :
|
|
1357
|
+
file.status === 'success' ? 'Completed' :
|
|
1358
|
+
file.status === 'error' ? 'Error' : 'Cancelled'}
|
|
1160
1359
|
</Badge>
|
|
1161
1360
|
</div>
|
|
1162
1361
|
|
|
@@ -1169,7 +1368,7 @@ const FileUploadItem = ({
|
|
|
1169
1368
|
<span>{formatFileSize(file.speed)}/s</span>
|
|
1170
1369
|
)}
|
|
1171
1370
|
{file.estimatedTime && file.estimatedTime > 0 && (
|
|
1172
|
-
<span>{formatTime(file.estimatedTime)}
|
|
1371
|
+
<span>{formatTime(file.estimatedTime)} remaining</span>
|
|
1173
1372
|
)}
|
|
1174
1373
|
</div>
|
|
1175
1374
|
<Progress value={file.progress} className="h-1" />
|
|
@@ -1232,20 +1431,20 @@ const FileUploadItem = ({
|
|
|
1232
1431
|
{canPreview && (
|
|
1233
1432
|
<DropdownMenuItem onClick={onPreview}>
|
|
1234
1433
|
<Eye className="mr-2 h-4 w-4" />
|
|
1235
|
-
|
|
1434
|
+
Preview
|
|
1236
1435
|
</DropdownMenuItem>
|
|
1237
1436
|
)}
|
|
1238
1437
|
<DropdownMenuItem>
|
|
1239
1438
|
<Copy className="mr-2 h-4 w-4" />
|
|
1240
|
-
|
|
1439
|
+
Copy Link
|
|
1241
1440
|
</DropdownMenuItem>
|
|
1242
1441
|
<DropdownMenuItem>
|
|
1243
1442
|
<Share className="mr-2 h-4 w-4" />
|
|
1244
|
-
|
|
1443
|
+
Share
|
|
1245
1444
|
</DropdownMenuItem>
|
|
1246
1445
|
<DropdownMenuItem onClick={onRemove} className="text-destructive">
|
|
1247
1446
|
<Trash2 className="mr-2 h-4 w-4" />
|
|
1248
|
-
|
|
1447
|
+
Delete
|
|
1249
1448
|
</DropdownMenuItem>
|
|
1250
1449
|
</DropdownMenuContent>
|
|
1251
1450
|
</DropdownMenu>
|