@moontra/moonui-pro 2.11.1 → 2.11.3
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 +1 -17
- package/dist/index.mjs +231 -6
- package/package.json +1 -1
- package/src/components/file-upload/index.tsx +293 -25
package/dist/index.d.ts
CHANGED
|
@@ -1776,23 +1776,7 @@ interface FileUploadProProps {
|
|
|
1776
1776
|
headers?: Record<string, string>;
|
|
1777
1777
|
serviceConfig?: {
|
|
1778
1778
|
type: 'aws-s3' | 'cloudinary' | 'firebase' | 'custom';
|
|
1779
|
-
|
|
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
|
-
};
|
|
1779
|
+
config?: any;
|
|
1796
1780
|
};
|
|
1797
1781
|
onUpload?: (files: FileUploadItem[]) => Promise<void>;
|
|
1798
1782
|
onProgress?: (fileId: string, progress: number) => void;
|
package/dist/index.mjs
CHANGED
|
@@ -58976,6 +58976,10 @@ var MoonUIFileUploadPro = t__default.forwardRef(
|
|
|
58976
58976
|
onPreview,
|
|
58977
58977
|
onBulkSelect,
|
|
58978
58978
|
customValidation,
|
|
58979
|
+
serviceConfig,
|
|
58980
|
+
imageResize,
|
|
58981
|
+
endpoint,
|
|
58982
|
+
headers,
|
|
58979
58983
|
...props
|
|
58980
58984
|
}, ref) => {
|
|
58981
58985
|
const { hasProAccess, isLoading } = useSubscription();
|
|
@@ -59067,6 +59071,213 @@ var MoonUIFileUploadPro = t__default.forwardRef(
|
|
|
59067
59071
|
}
|
|
59068
59072
|
return metadata;
|
|
59069
59073
|
}, [duplicateCheck]);
|
|
59074
|
+
const resizeImage = useCallback(async (file) => {
|
|
59075
|
+
if (!imageResize || !file.type.startsWith("image/"))
|
|
59076
|
+
return file;
|
|
59077
|
+
const { maxWidth, maxHeight, quality } = imageResize;
|
|
59078
|
+
return new Promise((resolve) => {
|
|
59079
|
+
const img = new Image();
|
|
59080
|
+
const reader = new FileReader();
|
|
59081
|
+
reader.onload = (e) => {
|
|
59082
|
+
img.src = e.target?.result;
|
|
59083
|
+
img.onload = () => {
|
|
59084
|
+
const canvas = document.createElement("canvas");
|
|
59085
|
+
const ctx = canvas.getContext("2d");
|
|
59086
|
+
let width = img.width;
|
|
59087
|
+
let height = img.height;
|
|
59088
|
+
if (width > maxWidth || height > maxHeight) {
|
|
59089
|
+
const aspectRatio = width / height;
|
|
59090
|
+
if (width > height) {
|
|
59091
|
+
width = maxWidth;
|
|
59092
|
+
height = maxWidth / aspectRatio;
|
|
59093
|
+
} else {
|
|
59094
|
+
height = maxHeight;
|
|
59095
|
+
width = maxHeight * aspectRatio;
|
|
59096
|
+
}
|
|
59097
|
+
}
|
|
59098
|
+
canvas.width = width;
|
|
59099
|
+
canvas.height = height;
|
|
59100
|
+
ctx.drawImage(img, 0, 0, width, height);
|
|
59101
|
+
canvas.toBlob(
|
|
59102
|
+
(blob) => {
|
|
59103
|
+
if (blob) {
|
|
59104
|
+
const resizedFile = new window.File([blob], file.name, {
|
|
59105
|
+
type: file.type,
|
|
59106
|
+
lastModified: Date.now()
|
|
59107
|
+
});
|
|
59108
|
+
resolve(resizedFile);
|
|
59109
|
+
} else {
|
|
59110
|
+
resolve(file);
|
|
59111
|
+
}
|
|
59112
|
+
},
|
|
59113
|
+
file.type,
|
|
59114
|
+
quality
|
|
59115
|
+
);
|
|
59116
|
+
};
|
|
59117
|
+
};
|
|
59118
|
+
reader.readAsDataURL(file);
|
|
59119
|
+
});
|
|
59120
|
+
}, [imageResize]);
|
|
59121
|
+
const uploadToService = useCallback(async (fileItem) => {
|
|
59122
|
+
if (!serviceConfig)
|
|
59123
|
+
return null;
|
|
59124
|
+
const { file } = fileItem;
|
|
59125
|
+
let processedFile = file;
|
|
59126
|
+
if (imageResize && file.type.startsWith("image/")) {
|
|
59127
|
+
processedFile = await resizeImage(file);
|
|
59128
|
+
}
|
|
59129
|
+
try {
|
|
59130
|
+
switch (serviceConfig.type) {
|
|
59131
|
+
case "aws-s3": {
|
|
59132
|
+
const { config } = serviceConfig;
|
|
59133
|
+
if (!config)
|
|
59134
|
+
throw new Error("AWS S3 config is required");
|
|
59135
|
+
const { bucketUrl, bucketName, region, accessKeyId, policy, signature, algorithm, credential, date, fields } = config;
|
|
59136
|
+
if (!bucketUrl && !bucketName) {
|
|
59137
|
+
throw new Error("AWS S3 requires either bucketUrl or bucketName");
|
|
59138
|
+
}
|
|
59139
|
+
if (bucketUrl && !bucketName) {
|
|
59140
|
+
const formData2 = new FormData();
|
|
59141
|
+
formData2.append("file", processedFile || file);
|
|
59142
|
+
const response2 = await fetch(bucketUrl, {
|
|
59143
|
+
method: "POST",
|
|
59144
|
+
body: formData2,
|
|
59145
|
+
headers: {
|
|
59146
|
+
...headers,
|
|
59147
|
+
...config.headers || {}
|
|
59148
|
+
}
|
|
59149
|
+
});
|
|
59150
|
+
if (!response2.ok) {
|
|
59151
|
+
throw new Error(`Upload failed: ${response2.statusText}`);
|
|
59152
|
+
}
|
|
59153
|
+
return await response2.json();
|
|
59154
|
+
}
|
|
59155
|
+
const s3Endpoint = bucketUrl || `https://${bucketName}.s3.${region || "us-east-1"}.amazonaws.com/`;
|
|
59156
|
+
const key = fields?.key || `uploads/${Date.now()}_${file.name}`;
|
|
59157
|
+
const formData = new FormData();
|
|
59158
|
+
if (fields) {
|
|
59159
|
+
Object.entries(fields).forEach(([k3, v]) => {
|
|
59160
|
+
if (k3 !== "file") {
|
|
59161
|
+
formData.append(k3, v);
|
|
59162
|
+
}
|
|
59163
|
+
});
|
|
59164
|
+
} else {
|
|
59165
|
+
formData.append("key", key);
|
|
59166
|
+
if (accessKeyId)
|
|
59167
|
+
formData.append("AWSAccessKeyId", accessKeyId);
|
|
59168
|
+
formData.append("Content-Type", file.type);
|
|
59169
|
+
if (policy)
|
|
59170
|
+
formData.append("policy", policy);
|
|
59171
|
+
if (signature)
|
|
59172
|
+
formData.append("signature", signature);
|
|
59173
|
+
if (algorithm)
|
|
59174
|
+
formData.append("x-amz-algorithm", algorithm);
|
|
59175
|
+
if (credential)
|
|
59176
|
+
formData.append("x-amz-credential", credential);
|
|
59177
|
+
if (date)
|
|
59178
|
+
formData.append("x-amz-date", date);
|
|
59179
|
+
}
|
|
59180
|
+
formData.append("file", processedFile || file);
|
|
59181
|
+
const response = await fetch(s3Endpoint, {
|
|
59182
|
+
method: "POST",
|
|
59183
|
+
body: formData
|
|
59184
|
+
});
|
|
59185
|
+
if (!response.ok && response.status !== 204) {
|
|
59186
|
+
const errorText = await response.text();
|
|
59187
|
+
throw new Error(`S3 upload failed: ${response.status} - ${errorText}`);
|
|
59188
|
+
}
|
|
59189
|
+
return {
|
|
59190
|
+
url: `${s3Endpoint}${key}`,
|
|
59191
|
+
key,
|
|
59192
|
+
bucket: bucketName,
|
|
59193
|
+
etag: response.headers.get("ETag"),
|
|
59194
|
+
location: response.headers.get("Location") || `${s3Endpoint}${key}`
|
|
59195
|
+
};
|
|
59196
|
+
}
|
|
59197
|
+
case "cloudinary": {
|
|
59198
|
+
const { config } = serviceConfig;
|
|
59199
|
+
if (!config)
|
|
59200
|
+
throw new Error("Cloudinary config is required");
|
|
59201
|
+
const { cloudName, uploadPreset, apiKey } = config;
|
|
59202
|
+
const formData = new FormData();
|
|
59203
|
+
formData.append("file", processedFile || file);
|
|
59204
|
+
formData.append("upload_preset", uploadPreset);
|
|
59205
|
+
if (apiKey) {
|
|
59206
|
+
formData.append("api_key", apiKey);
|
|
59207
|
+
}
|
|
59208
|
+
if (imageResize) {
|
|
59209
|
+
formData.append("eager", `w_${imageResize.maxWidth},h_${imageResize.maxHeight},c_limit,q_${Math.round(imageResize.quality * 100)}`);
|
|
59210
|
+
}
|
|
59211
|
+
const response = await fetch(
|
|
59212
|
+
`https://api.cloudinary.com/v1_1/${cloudName}/auto/upload`,
|
|
59213
|
+
{
|
|
59214
|
+
method: "POST",
|
|
59215
|
+
body: formData
|
|
59216
|
+
}
|
|
59217
|
+
);
|
|
59218
|
+
if (!response.ok)
|
|
59219
|
+
throw new Error("Cloudinary upload failed");
|
|
59220
|
+
const result = await response.json();
|
|
59221
|
+
return {
|
|
59222
|
+
url: result.secure_url,
|
|
59223
|
+
publicId: result.public_id,
|
|
59224
|
+
format: result.format,
|
|
59225
|
+
width: result.width,
|
|
59226
|
+
height: result.height,
|
|
59227
|
+
bytes: result.bytes,
|
|
59228
|
+
cloudName
|
|
59229
|
+
};
|
|
59230
|
+
}
|
|
59231
|
+
case "firebase": {
|
|
59232
|
+
const { config } = serviceConfig;
|
|
59233
|
+
if (!config)
|
|
59234
|
+
throw new Error("Firebase config is required");
|
|
59235
|
+
const { storageBucket, folder = "uploads" } = config;
|
|
59236
|
+
const fileName = `${folder}/${Date.now()}-${file.name}`;
|
|
59237
|
+
const uploadUrl = `https://storage.googleapis.com/upload/storage/v1/b/${storageBucket}/o?uploadType=media&name=${encodeURIComponent(fileName)}`;
|
|
59238
|
+
const response = await fetch(uploadUrl, {
|
|
59239
|
+
method: "POST",
|
|
59240
|
+
body: processedFile || file,
|
|
59241
|
+
headers: {
|
|
59242
|
+
"Content-Type": file.type,
|
|
59243
|
+
...headers
|
|
59244
|
+
// Should include Authorization: Bearer [token]
|
|
59245
|
+
}
|
|
59246
|
+
});
|
|
59247
|
+
if (!response.ok)
|
|
59248
|
+
throw new Error("Firebase upload failed");
|
|
59249
|
+
const result = await response.json();
|
|
59250
|
+
return {
|
|
59251
|
+
url: `https://storage.googleapis.com/${storageBucket}/${fileName}`,
|
|
59252
|
+
name: result.name,
|
|
59253
|
+
bucket: result.bucket,
|
|
59254
|
+
generation: result.generation,
|
|
59255
|
+
contentType: result.contentType,
|
|
59256
|
+
size: result.size
|
|
59257
|
+
};
|
|
59258
|
+
}
|
|
59259
|
+
case "custom": {
|
|
59260
|
+
if (!endpoint)
|
|
59261
|
+
throw new Error("Custom endpoint is required");
|
|
59262
|
+
const formData = new FormData();
|
|
59263
|
+
formData.append("file", file);
|
|
59264
|
+
const response = await fetch(endpoint, {
|
|
59265
|
+
method: "POST",
|
|
59266
|
+
body: formData,
|
|
59267
|
+
headers
|
|
59268
|
+
});
|
|
59269
|
+
if (!response.ok)
|
|
59270
|
+
throw new Error("Custom upload failed");
|
|
59271
|
+
return response.json();
|
|
59272
|
+
}
|
|
59273
|
+
default:
|
|
59274
|
+
throw new Error(`Unsupported service type: ${serviceConfig.type}`);
|
|
59275
|
+
}
|
|
59276
|
+
} catch (error2) {
|
|
59277
|
+
console.error("Upload service error:", error2);
|
|
59278
|
+
throw error2;
|
|
59279
|
+
}
|
|
59280
|
+
}, [serviceConfig, endpoint, headers, imageResize, resizeImage]);
|
|
59070
59281
|
const uploadFileChunked = useCallback(async (fileItem) => {
|
|
59071
59282
|
const { file } = fileItem;
|
|
59072
59283
|
const chunks = [];
|
|
@@ -59088,6 +59299,18 @@ var MoonUIFileUploadPro = t__default.forwardRef(
|
|
|
59088
59299
|
let uploadedBytes = 0;
|
|
59089
59300
|
const startTime = Date.now();
|
|
59090
59301
|
try {
|
|
59302
|
+
let processedFile = file;
|
|
59303
|
+
if (imageResize && file.type.startsWith("image/")) {
|
|
59304
|
+
processedFile = await resizeImage(file);
|
|
59305
|
+
}
|
|
59306
|
+
if (serviceConfig) {
|
|
59307
|
+
const uploadResult = await uploadToService({ ...fileItem, file: processedFile });
|
|
59308
|
+
setFiles((prev) => prev.map(
|
|
59309
|
+
(f) => f.id === fileItem.id ? { ...f, status: "success", progress: 100, result: uploadResult } : f
|
|
59310
|
+
));
|
|
59311
|
+
onComplete?.(fileItem.id, uploadResult);
|
|
59312
|
+
return;
|
|
59313
|
+
}
|
|
59091
59314
|
for (const chunk of chunks) {
|
|
59092
59315
|
if (abortController.signal.aborted) {
|
|
59093
59316
|
throw new Error("Upload cancelled");
|
|
@@ -59124,7 +59347,7 @@ var MoonUIFileUploadPro = t__default.forwardRef(
|
|
|
59124
59347
|
} finally {
|
|
59125
59348
|
uploadQueue.current.delete(fileItem.id);
|
|
59126
59349
|
}
|
|
59127
|
-
}, [chunkSize, onProgress, onComplete, onError]);
|
|
59350
|
+
}, [chunkSize, onProgress, onComplete, onError, serviceConfig, uploadToService, resizeImage, imageResize]);
|
|
59128
59351
|
const processFiles = useCallback(async (fileList) => {
|
|
59129
59352
|
const fileArray = Array.from(fileList);
|
|
59130
59353
|
setError(null);
|
|
@@ -59163,14 +59386,15 @@ var MoonUIFileUploadPro = t__default.forwardRef(
|
|
|
59163
59386
|
const newFileItems = [];
|
|
59164
59387
|
for (const file of validFiles) {
|
|
59165
59388
|
const id = `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
|
|
59166
|
-
const
|
|
59167
|
-
const
|
|
59389
|
+
const processedFile = await resizeImage(file);
|
|
59390
|
+
const preview = await createPreview(processedFile);
|
|
59391
|
+
const metadata = await createMetadata(processedFile);
|
|
59168
59392
|
newFileItems.push({
|
|
59169
59393
|
id,
|
|
59170
|
-
file,
|
|
59394
|
+
file: processedFile,
|
|
59171
59395
|
status: "pending",
|
|
59172
59396
|
progress: 0,
|
|
59173
|
-
totalBytes:
|
|
59397
|
+
totalBytes: processedFile.size,
|
|
59174
59398
|
preview,
|
|
59175
59399
|
metadata
|
|
59176
59400
|
});
|
|
@@ -59196,7 +59420,8 @@ var MoonUIFileUploadPro = t__default.forwardRef(
|
|
|
59196
59420
|
createPreview,
|
|
59197
59421
|
createMetadata,
|
|
59198
59422
|
onUpload,
|
|
59199
|
-
uploadFileChunked
|
|
59423
|
+
uploadFileChunked,
|
|
59424
|
+
resizeImage
|
|
59200
59425
|
]);
|
|
59201
59426
|
const handleDrop = useCallback((e) => {
|
|
59202
59427
|
e.preventDefault();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@moontra/moonui-pro",
|
|
3
|
-
"version": "2.11.
|
|
3
|
+
"version": "2.11.3",
|
|
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",
|
|
@@ -81,23 +81,7 @@ export interface FileUploadProProps {
|
|
|
81
81
|
// Service integrations
|
|
82
82
|
serviceConfig?: {
|
|
83
83
|
type: 'aws-s3' | 'cloudinary' | 'firebase' | 'custom'
|
|
84
|
-
|
|
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
|
-
}
|
|
84
|
+
config?: any
|
|
101
85
|
}
|
|
102
86
|
|
|
103
87
|
// Callbacks
|
|
@@ -477,6 +461,10 @@ export const MoonUIFileUploadPro = React.forwardRef<HTMLDivElement, FileUploadPr
|
|
|
477
461
|
onPreview,
|
|
478
462
|
onBulkSelect,
|
|
479
463
|
customValidation,
|
|
464
|
+
serviceConfig,
|
|
465
|
+
imageResize,
|
|
466
|
+
endpoint,
|
|
467
|
+
headers,
|
|
480
468
|
...props
|
|
481
469
|
}, ref) => {
|
|
482
470
|
|
|
@@ -610,6 +598,261 @@ export const MoonUIFileUploadPro = React.forwardRef<HTMLDivElement, FileUploadPr
|
|
|
610
598
|
return metadata
|
|
611
599
|
}, [duplicateCheck])
|
|
612
600
|
|
|
601
|
+
// Image resize handler
|
|
602
|
+
const resizeImage = useCallback(async (file: File): Promise<File> => {
|
|
603
|
+
if (!imageResize || !file.type.startsWith('image/')) return file
|
|
604
|
+
|
|
605
|
+
const { maxWidth, maxHeight, quality } = imageResize
|
|
606
|
+
|
|
607
|
+
return new Promise((resolve) => {
|
|
608
|
+
const img = new Image()
|
|
609
|
+
const reader = new FileReader()
|
|
610
|
+
|
|
611
|
+
reader.onload = (e) => {
|
|
612
|
+
img.src = e.target?.result as string
|
|
613
|
+
|
|
614
|
+
img.onload = () => {
|
|
615
|
+
const canvas = document.createElement('canvas')
|
|
616
|
+
const ctx = canvas.getContext('2d')!
|
|
617
|
+
|
|
618
|
+
let width = img.width
|
|
619
|
+
let height = img.height
|
|
620
|
+
|
|
621
|
+
// Calculate new dimensions
|
|
622
|
+
if (width > maxWidth || height > maxHeight) {
|
|
623
|
+
const aspectRatio = width / height
|
|
624
|
+
if (width > height) {
|
|
625
|
+
width = maxWidth
|
|
626
|
+
height = maxWidth / aspectRatio
|
|
627
|
+
} else {
|
|
628
|
+
height = maxHeight
|
|
629
|
+
width = maxHeight * aspectRatio
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
canvas.width = width
|
|
634
|
+
canvas.height = height
|
|
635
|
+
ctx.drawImage(img, 0, 0, width, height)
|
|
636
|
+
|
|
637
|
+
canvas.toBlob(
|
|
638
|
+
(blob) => {
|
|
639
|
+
if (blob) {
|
|
640
|
+
// Create a resized file
|
|
641
|
+
const resizedFile = new window.File([blob], file.name, {
|
|
642
|
+
type: file.type,
|
|
643
|
+
lastModified: Date.now()
|
|
644
|
+
})
|
|
645
|
+
resolve(resizedFile)
|
|
646
|
+
} else {
|
|
647
|
+
resolve(file)
|
|
648
|
+
}
|
|
649
|
+
},
|
|
650
|
+
file.type,
|
|
651
|
+
quality
|
|
652
|
+
)
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
reader.readAsDataURL(file)
|
|
657
|
+
})
|
|
658
|
+
}, [imageResize])
|
|
659
|
+
|
|
660
|
+
// Service specific upload handler
|
|
661
|
+
const uploadToService = useCallback(async (fileItem: FileUploadItem): Promise<any> => {
|
|
662
|
+
if (!serviceConfig) return null
|
|
663
|
+
|
|
664
|
+
const { file } = fileItem
|
|
665
|
+
let processedFile = file
|
|
666
|
+
|
|
667
|
+
// Resize image if needed
|
|
668
|
+
if (imageResize && file.type.startsWith('image/')) {
|
|
669
|
+
processedFile = await resizeImage(file)
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
try {
|
|
673
|
+
switch (serviceConfig.type) {
|
|
674
|
+
case 'aws-s3': {
|
|
675
|
+
// AWS S3 Direct Upload
|
|
676
|
+
const { config } = serviceConfig as any
|
|
677
|
+
if (!config) throw new Error('AWS S3 config is required')
|
|
678
|
+
|
|
679
|
+
const { bucketUrl, bucketName, region, accessKeyId, policy, signature, algorithm, credential, date, fields } = config
|
|
680
|
+
|
|
681
|
+
if (!bucketUrl && !bucketName) {
|
|
682
|
+
throw new Error('AWS S3 requires either bucketUrl or bucketName')
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
// Option 1: Direct upload to custom S3-compatible endpoint
|
|
686
|
+
if (bucketUrl && !bucketName) {
|
|
687
|
+
const formData = new FormData()
|
|
688
|
+
formData.append('file', processedFile || file)
|
|
689
|
+
|
|
690
|
+
const response = await fetch(bucketUrl, {
|
|
691
|
+
method: 'POST',
|
|
692
|
+
body: formData,
|
|
693
|
+
headers: {
|
|
694
|
+
...headers,
|
|
695
|
+
...(config.headers || {})
|
|
696
|
+
}
|
|
697
|
+
})
|
|
698
|
+
|
|
699
|
+
if (!response.ok) {
|
|
700
|
+
throw new Error(`Upload failed: ${response.statusText}`)
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
return await response.json()
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
// Option 2: AWS S3 POST upload with policy-based authentication
|
|
707
|
+
// This requires server-side generation of policy and signature
|
|
708
|
+
const s3Endpoint = bucketUrl || `https://${bucketName}.s3.${region || 'us-east-1'}.amazonaws.com/`
|
|
709
|
+
const key = fields?.key || `uploads/${Date.now()}_${file.name}`
|
|
710
|
+
|
|
711
|
+
const formData = new FormData()
|
|
712
|
+
|
|
713
|
+
// AWS S3 POST policy fields must be added in specific order
|
|
714
|
+
if (fields) {
|
|
715
|
+
// Add all policy fields first
|
|
716
|
+
Object.entries(fields).forEach(([k, v]) => {
|
|
717
|
+
if (k !== 'file') {
|
|
718
|
+
formData.append(k, v as string)
|
|
719
|
+
}
|
|
720
|
+
})
|
|
721
|
+
} else {
|
|
722
|
+
// Basic fields for public bucket upload
|
|
723
|
+
formData.append('key', key)
|
|
724
|
+
if (accessKeyId) formData.append('AWSAccessKeyId', accessKeyId)
|
|
725
|
+
formData.append('Content-Type', file.type)
|
|
726
|
+
if (policy) formData.append('policy', policy)
|
|
727
|
+
if (signature) formData.append('signature', signature)
|
|
728
|
+
if (algorithm) formData.append('x-amz-algorithm', algorithm)
|
|
729
|
+
if (credential) formData.append('x-amz-credential', credential)
|
|
730
|
+
if (date) formData.append('x-amz-date', date)
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
// File must be the last field
|
|
734
|
+
formData.append('file', processedFile || file)
|
|
735
|
+
|
|
736
|
+
const response = await fetch(s3Endpoint, {
|
|
737
|
+
method: 'POST',
|
|
738
|
+
body: formData
|
|
739
|
+
})
|
|
740
|
+
|
|
741
|
+
// S3 returns 204 No Content on success for POST uploads
|
|
742
|
+
if (!response.ok && response.status !== 204) {
|
|
743
|
+
const errorText = await response.text()
|
|
744
|
+
throw new Error(`S3 upload failed: ${response.status} - ${errorText}`)
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
return {
|
|
748
|
+
url: `${s3Endpoint}${key}`,
|
|
749
|
+
key,
|
|
750
|
+
bucket: bucketName,
|
|
751
|
+
etag: response.headers.get('ETag'),
|
|
752
|
+
location: response.headers.get('Location') || `${s3Endpoint}${key}`
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
case 'cloudinary': {
|
|
757
|
+
// Cloudinary upload logic
|
|
758
|
+
const { config } = serviceConfig as any
|
|
759
|
+
if (!config) throw new Error('Cloudinary config is required')
|
|
760
|
+
|
|
761
|
+
const { cloudName, uploadPreset, apiKey } = config
|
|
762
|
+
const formData = new FormData()
|
|
763
|
+
formData.append('file', processedFile || file)
|
|
764
|
+
formData.append('upload_preset', uploadPreset)
|
|
765
|
+
|
|
766
|
+
if (apiKey) {
|
|
767
|
+
formData.append('api_key', apiKey)
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
// Add optional transformations
|
|
771
|
+
if (imageResize) {
|
|
772
|
+
formData.append('eager', `w_${imageResize.maxWidth},h_${imageResize.maxHeight},c_limit,q_${Math.round(imageResize.quality * 100)}`)
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
const response = await fetch(
|
|
776
|
+
`https://api.cloudinary.com/v1_1/${cloudName}/auto/upload`,
|
|
777
|
+
{
|
|
778
|
+
method: 'POST',
|
|
779
|
+
body: formData
|
|
780
|
+
}
|
|
781
|
+
)
|
|
782
|
+
|
|
783
|
+
if (!response.ok) throw new Error('Cloudinary upload failed')
|
|
784
|
+
const result = await response.json()
|
|
785
|
+
|
|
786
|
+
return {
|
|
787
|
+
url: result.secure_url,
|
|
788
|
+
publicId: result.public_id,
|
|
789
|
+
format: result.format,
|
|
790
|
+
width: result.width,
|
|
791
|
+
height: result.height,
|
|
792
|
+
bytes: result.bytes,
|
|
793
|
+
cloudName
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
case 'firebase': {
|
|
798
|
+
// Firebase Storage REST API upload (no SDK required)
|
|
799
|
+
const { config } = serviceConfig as any
|
|
800
|
+
if (!config) throw new Error('Firebase config is required')
|
|
801
|
+
|
|
802
|
+
const { storageBucket, folder = 'uploads' } = config
|
|
803
|
+
const fileName = `${folder}/${Date.now()}-${file.name}`
|
|
804
|
+
|
|
805
|
+
// Firebase Storage uses Google Cloud Storage REST API
|
|
806
|
+
const uploadUrl = `https://storage.googleapis.com/upload/storage/v1/b/${storageBucket}/o?uploadType=media&name=${encodeURIComponent(fileName)}`
|
|
807
|
+
|
|
808
|
+
const response = await fetch(uploadUrl, {
|
|
809
|
+
method: 'POST',
|
|
810
|
+
body: processedFile || file,
|
|
811
|
+
headers: {
|
|
812
|
+
'Content-Type': file.type,
|
|
813
|
+
...headers // Should include Authorization: Bearer [token]
|
|
814
|
+
}
|
|
815
|
+
})
|
|
816
|
+
|
|
817
|
+
if (!response.ok) throw new Error('Firebase upload failed')
|
|
818
|
+
const result = await response.json()
|
|
819
|
+
|
|
820
|
+
return {
|
|
821
|
+
url: `https://storage.googleapis.com/${storageBucket}/${fileName}`,
|
|
822
|
+
name: result.name,
|
|
823
|
+
bucket: result.bucket,
|
|
824
|
+
generation: result.generation,
|
|
825
|
+
contentType: result.contentType,
|
|
826
|
+
size: result.size
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
case 'custom': {
|
|
831
|
+
// Custom endpoint upload
|
|
832
|
+
if (!endpoint) throw new Error('Custom endpoint is required')
|
|
833
|
+
|
|
834
|
+
const formData = new FormData()
|
|
835
|
+
formData.append('file', file)
|
|
836
|
+
|
|
837
|
+
const response = await fetch(endpoint, {
|
|
838
|
+
method: 'POST',
|
|
839
|
+
body: formData,
|
|
840
|
+
headers: headers
|
|
841
|
+
})
|
|
842
|
+
|
|
843
|
+
if (!response.ok) throw new Error('Custom upload failed')
|
|
844
|
+
return response.json()
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
default:
|
|
848
|
+
throw new Error(`Unsupported service type: ${serviceConfig.type}`)
|
|
849
|
+
}
|
|
850
|
+
} catch (error) {
|
|
851
|
+
console.error('Upload service error:', error)
|
|
852
|
+
throw error
|
|
853
|
+
}
|
|
854
|
+
}, [serviceConfig, endpoint, headers, imageResize, resizeImage])
|
|
855
|
+
|
|
613
856
|
// Chunked upload simülasyonu
|
|
614
857
|
const uploadFileChunked = useCallback(async (fileItem: FileUploadItem) => {
|
|
615
858
|
const { file } = fileItem
|
|
@@ -641,13 +884,33 @@ export const MoonUIFileUploadPro = React.forwardRef<HTMLDivElement, FileUploadPr
|
|
|
641
884
|
const startTime = Date.now()
|
|
642
885
|
|
|
643
886
|
try {
|
|
644
|
-
//
|
|
887
|
+
// Image resize if needed
|
|
888
|
+
let processedFile = file
|
|
889
|
+
if (imageResize && file.type.startsWith('image/')) {
|
|
890
|
+
processedFile = await resizeImage(file)
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
// If service config exists, do the actual upload
|
|
894
|
+
if (serviceConfig) {
|
|
895
|
+
const uploadResult = await uploadToService({ ...fileItem, file: processedFile })
|
|
896
|
+
|
|
897
|
+
// Update progress to 100% immediately for service uploads
|
|
898
|
+
setFiles(prev => prev.map(f =>
|
|
899
|
+
f.id === fileItem.id
|
|
900
|
+
? { ...f, status: 'success', progress: 100, result: uploadResult }
|
|
901
|
+
: f
|
|
902
|
+
))
|
|
903
|
+
|
|
904
|
+
onComplete?.(fileItem.id, uploadResult)
|
|
905
|
+
return
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
// Regular chunk upload simulation for non-service uploads
|
|
645
909
|
for (const chunk of chunks) {
|
|
646
910
|
if (abortController.signal.aborted) {
|
|
647
911
|
throw new Error('Upload cancelled')
|
|
648
912
|
}
|
|
649
913
|
|
|
650
|
-
// Chunk upload simülasyonu
|
|
651
914
|
await new Promise(resolve => setTimeout(resolve, 100 + Math.random() * 200))
|
|
652
915
|
|
|
653
916
|
uploadedBytes += (chunk.end - chunk.start)
|
|
@@ -697,7 +960,7 @@ export const MoonUIFileUploadPro = React.forwardRef<HTMLDivElement, FileUploadPr
|
|
|
697
960
|
} finally {
|
|
698
961
|
uploadQueue.current.delete(fileItem.id)
|
|
699
962
|
}
|
|
700
|
-
}, [chunkSize, onProgress, onComplete, onError])
|
|
963
|
+
}, [chunkSize, onProgress, onComplete, onError, serviceConfig, uploadToService, resizeImage, imageResize])
|
|
701
964
|
|
|
702
965
|
// Dosya işleme
|
|
703
966
|
const processFiles = useCallback(async (fileList: FileList | File[]) => {
|
|
@@ -752,15 +1015,19 @@ export const MoonUIFileUploadPro = React.forwardRef<HTMLDivElement, FileUploadPr
|
|
|
752
1015
|
|
|
753
1016
|
for (const file of validFiles) {
|
|
754
1017
|
const id = `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`
|
|
755
|
-
|
|
756
|
-
|
|
1018
|
+
|
|
1019
|
+
// Resize image if needed
|
|
1020
|
+
const processedFile = await resizeImage(file)
|
|
1021
|
+
|
|
1022
|
+
const preview = await createPreview(processedFile)
|
|
1023
|
+
const metadata = await createMetadata(processedFile)
|
|
757
1024
|
|
|
758
1025
|
newFileItems.push({
|
|
759
1026
|
id,
|
|
760
|
-
file,
|
|
1027
|
+
file: processedFile,
|
|
761
1028
|
status: 'pending',
|
|
762
1029
|
progress: 0,
|
|
763
|
-
totalBytes:
|
|
1030
|
+
totalBytes: processedFile.size,
|
|
764
1031
|
preview,
|
|
765
1032
|
metadata
|
|
766
1033
|
})
|
|
@@ -790,7 +1057,8 @@ export const MoonUIFileUploadPro = React.forwardRef<HTMLDivElement, FileUploadPr
|
|
|
790
1057
|
createPreview,
|
|
791
1058
|
createMetadata,
|
|
792
1059
|
onUpload,
|
|
793
|
-
uploadFileChunked
|
|
1060
|
+
uploadFileChunked,
|
|
1061
|
+
resizeImage
|
|
794
1062
|
])
|
|
795
1063
|
|
|
796
1064
|
// Drag & Drop handlers
|