gbs-add-block 1.0.5 → 1.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -7
- package/index.cjs +1 -0
- package/package.json +1 -1
- package/source/components/useUploader/useUploader.tsx +303 -0
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# GBS Building Blocks 2.0 (v1.0.
|
|
1
|
+
# GBS Building Blocks 2.0 (v1.0.6)
|
|
2
2
|
|
|
3
3
|
Latest and upgraded version of GBS building blocks with headless UI and removed dependencies.
|
|
4
4
|
|
|
@@ -6,13 +6,9 @@ Latest and upgraded version of GBS building blocks with headless UI and removed
|
|
|
6
6
|
|
|
7
7
|
For detailed documentation on usage and props, Please visit: [Building Block Documentation v2.0](https://blackmax-designs.gitbook.io/building-block-v2.0)
|
|
8
8
|
|
|
9
|
-
## What's New 🎉 (Ver 1.0.
|
|
9
|
+
## What's New 🎉 (Ver 1.0.6)
|
|
10
10
|
|
|
11
|
-
-
|
|
12
|
-
- Deprecated Grid Component, Use New Data Grid instead.
|
|
13
|
-
- More UI/UX Optimization.
|
|
14
|
-
- Animation improvements.
|
|
15
|
-
- Custom Components in SideBar and Mobile Screen Support
|
|
11
|
+
- Uploader Enhancement
|
|
16
12
|
|
|
17
13
|
## Authors
|
|
18
14
|
|
package/index.cjs
CHANGED
package/package.json
CHANGED
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
import { useState, useCallback } from "react";
|
|
2
|
+
|
|
3
|
+
// Types
|
|
4
|
+
interface ChunkInfo {
|
|
5
|
+
file: Blob;
|
|
6
|
+
originalFile: File;
|
|
7
|
+
chunkIndex: number;
|
|
8
|
+
totalChunks: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface UploadProgress {
|
|
12
|
+
[fileName: string]: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface UploadResult {
|
|
16
|
+
fileName: string;
|
|
17
|
+
status: "complete" | "chunk_received" | "error";
|
|
18
|
+
message?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface UseChunkedUploadOptions {
|
|
22
|
+
chunkSize?: number;
|
|
23
|
+
maxConcurrentUploads?: number;
|
|
24
|
+
baseUrl?: string;
|
|
25
|
+
onProgress?: (fileName: string, progress: number) => void;
|
|
26
|
+
onComplete?: (fileName: string, result: UploadResult) => void;
|
|
27
|
+
onError?: (fileName: string, error: Error) => void;
|
|
28
|
+
additionalParams?: any;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface UseChunkedUploadReturn {
|
|
32
|
+
uploadFiles: (files: File[]) => Promise<void>;
|
|
33
|
+
isUploading: boolean;
|
|
34
|
+
uploadProgress: UploadProgress;
|
|
35
|
+
uploadStatus: string;
|
|
36
|
+
cancelUpload: () => void;
|
|
37
|
+
reset: () => void;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Helper function to process files into chunks
|
|
41
|
+
function processFiles(files: File[], chunkSize: number): ChunkInfo[] {
|
|
42
|
+
const finalFiles: ChunkInfo[] = [];
|
|
43
|
+
|
|
44
|
+
files.forEach((file) => {
|
|
45
|
+
if (file.size > chunkSize) {
|
|
46
|
+
const totalChunks = Math.ceil(file.size / chunkSize);
|
|
47
|
+
let offset = 0;
|
|
48
|
+
let chunkIndex = 0;
|
|
49
|
+
|
|
50
|
+
while (offset < file.size) {
|
|
51
|
+
const chunk = file.slice(offset, offset + chunkSize);
|
|
52
|
+
finalFiles.push({
|
|
53
|
+
file: chunk,
|
|
54
|
+
originalFile: file,
|
|
55
|
+
chunkIndex,
|
|
56
|
+
totalChunks,
|
|
57
|
+
});
|
|
58
|
+
offset += chunkSize;
|
|
59
|
+
chunkIndex++;
|
|
60
|
+
}
|
|
61
|
+
} else {
|
|
62
|
+
finalFiles.push({
|
|
63
|
+
file,
|
|
64
|
+
originalFile: file,
|
|
65
|
+
chunkIndex: 0,
|
|
66
|
+
totalChunks: 1,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
return finalFiles;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Helper function to upload a single chunk
|
|
75
|
+
async function uploadChunk(
|
|
76
|
+
chunk: Blob,
|
|
77
|
+
originalFile: File,
|
|
78
|
+
chunkIndex: number,
|
|
79
|
+
totalChunks: number,
|
|
80
|
+
baseUrl: string,
|
|
81
|
+
signal?: AbortSignal,
|
|
82
|
+
additionalParams: any = {}
|
|
83
|
+
): Promise<UploadResult> {
|
|
84
|
+
const formData = new FormData();
|
|
85
|
+
formData.append("chunk", chunk);
|
|
86
|
+
formData.append("fileName", originalFile.name);
|
|
87
|
+
formData.append("chunkIndex", chunkIndex.toString());
|
|
88
|
+
formData.append("totalChunks", totalChunks.toString());
|
|
89
|
+
formData.append("fileSize", originalFile.size.toString());
|
|
90
|
+
|
|
91
|
+
Object.entries(additionalParams).forEach(([key, value]: any) => {
|
|
92
|
+
formData.append(key, value);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const response = await fetch(`${baseUrl}`, {
|
|
96
|
+
method: "POST",
|
|
97
|
+
body: formData,
|
|
98
|
+
signal,
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
if (!response.ok) {
|
|
102
|
+
throw new Error(`Upload failed: ${response.statusText}`);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return response.json();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Main custom hook
|
|
109
|
+
export function useChunkedUpload(
|
|
110
|
+
options: UseChunkedUploadOptions = {}
|
|
111
|
+
): UseChunkedUploadReturn {
|
|
112
|
+
const {
|
|
113
|
+
chunkSize = 1024 * 1024, // 1MB default
|
|
114
|
+
maxConcurrentUploads = 3,
|
|
115
|
+
baseUrl = "",
|
|
116
|
+
onProgress,
|
|
117
|
+
onComplete,
|
|
118
|
+
onError,
|
|
119
|
+
additionalParams = {},
|
|
120
|
+
} = options;
|
|
121
|
+
|
|
122
|
+
const [isUploading, setIsUploading] = useState(false);
|
|
123
|
+
const [uploadProgress, setUploadProgress] = useState<UploadProgress>({});
|
|
124
|
+
const [uploadStatus, setUploadStatus] = useState("");
|
|
125
|
+
const [abortController, setAbortController] =
|
|
126
|
+
useState<AbortController | null>(null);
|
|
127
|
+
|
|
128
|
+
const updateProgress = useCallback(
|
|
129
|
+
(fileName: string, progress: number) => {
|
|
130
|
+
setUploadProgress((prev) => ({
|
|
131
|
+
...prev,
|
|
132
|
+
[fileName]: progress,
|
|
133
|
+
}));
|
|
134
|
+
onProgress?.(fileName, progress);
|
|
135
|
+
},
|
|
136
|
+
[onProgress]
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
const updateStatus = useCallback((status: string) => {
|
|
140
|
+
setUploadStatus(status);
|
|
141
|
+
}, []);
|
|
142
|
+
|
|
143
|
+
const uploadFiles = useCallback(
|
|
144
|
+
async (files: File[]) => {
|
|
145
|
+
if (files.length === 0) {
|
|
146
|
+
throw new Error("No files selected for upload.");
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Create new abort controller
|
|
150
|
+
const controller = new AbortController();
|
|
151
|
+
setAbortController(controller);
|
|
152
|
+
|
|
153
|
+
setIsUploading(true);
|
|
154
|
+
updateStatus("Processing files...");
|
|
155
|
+
|
|
156
|
+
try {
|
|
157
|
+
const processedFiles = processFiles(files, chunkSize);
|
|
158
|
+
|
|
159
|
+
// Group chunks by file
|
|
160
|
+
const fileChunks: { [key: string]: ChunkInfo[] } = {};
|
|
161
|
+
processedFiles.forEach((processedFile) => {
|
|
162
|
+
const fileName = processedFile.originalFile.name;
|
|
163
|
+
if (!fileChunks[fileName]) {
|
|
164
|
+
fileChunks[fileName] = [];
|
|
165
|
+
}
|
|
166
|
+
fileChunks[fileName].push(processedFile);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// Upload files with concurrency control
|
|
170
|
+
const fileNames = Object.keys(fileChunks);
|
|
171
|
+
const uploadPromises = fileNames.map(async (fileName) => {
|
|
172
|
+
const chunks = fileChunks[fileName];
|
|
173
|
+
updateStatus(`Uploading ${fileName}...`);
|
|
174
|
+
|
|
175
|
+
try {
|
|
176
|
+
// Create semaphore for concurrent chunk uploads
|
|
177
|
+
const semaphore = new Array(maxConcurrentUploads).fill(null);
|
|
178
|
+
let chunkIndex = 0;
|
|
179
|
+
const results: UploadResult[] = [];
|
|
180
|
+
|
|
181
|
+
const uploadNextChunk = async (): Promise<void> => {
|
|
182
|
+
if (chunkIndex >= chunks.length) return;
|
|
183
|
+
|
|
184
|
+
const currentChunk = chunks[chunkIndex++];
|
|
185
|
+
const result = await uploadChunk(
|
|
186
|
+
currentChunk.file,
|
|
187
|
+
currentChunk.originalFile,
|
|
188
|
+
currentChunk.chunkIndex,
|
|
189
|
+
currentChunk.totalChunks,
|
|
190
|
+
baseUrl,
|
|
191
|
+
controller.signal,
|
|
192
|
+
additionalParams
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
results.push(result);
|
|
196
|
+
|
|
197
|
+
// Update progress
|
|
198
|
+
const progress = (results.length / chunks.length) * 100;
|
|
199
|
+
updateProgress(fileName, progress);
|
|
200
|
+
|
|
201
|
+
// Continue uploading if there are more chunks
|
|
202
|
+
if (chunkIndex < chunks.length) {
|
|
203
|
+
await uploadNextChunk();
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
// Start concurrent uploads
|
|
208
|
+
await Promise.all(semaphore.map(() => uploadNextChunk()));
|
|
209
|
+
|
|
210
|
+
// Check if file is complete
|
|
211
|
+
const completeResult = results.find((r) => r.status === "complete");
|
|
212
|
+
if (completeResult) {
|
|
213
|
+
updateStatus(`${fileName} uploaded successfully!`);
|
|
214
|
+
onComplete?.(fileName, completeResult);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return { fileName, success: true };
|
|
218
|
+
} catch (error) {
|
|
219
|
+
const errorObj =
|
|
220
|
+
error instanceof Error ? error : new Error(String(error));
|
|
221
|
+
updateStatus(`Failed to upload ${fileName}: ${errorObj.message}`);
|
|
222
|
+
onError?.(fileName, errorObj);
|
|
223
|
+
return { fileName, success: false, error: errorObj };
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
const results = await Promise.all(uploadPromises);
|
|
228
|
+
|
|
229
|
+
const successCount = results.filter((r) => r.success).length;
|
|
230
|
+
const failCount = results.length - successCount;
|
|
231
|
+
|
|
232
|
+
if (failCount === 0) {
|
|
233
|
+
updateStatus("All files uploaded successfully!");
|
|
234
|
+
} else if (successCount === 0) {
|
|
235
|
+
updateStatus("All uploads failed!");
|
|
236
|
+
} else {
|
|
237
|
+
updateStatus(`${successCount} files uploaded, ${failCount} failed`);
|
|
238
|
+
}
|
|
239
|
+
} catch (error) {
|
|
240
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
241
|
+
updateStatus("Upload cancelled");
|
|
242
|
+
} else {
|
|
243
|
+
const errorMessage =
|
|
244
|
+
error instanceof Error ? error.message : String(error);
|
|
245
|
+
updateStatus(`Upload failed: ${errorMessage}`);
|
|
246
|
+
}
|
|
247
|
+
} finally {
|
|
248
|
+
setIsUploading(false);
|
|
249
|
+
setAbortController(null);
|
|
250
|
+
}
|
|
251
|
+
},
|
|
252
|
+
[
|
|
253
|
+
chunkSize,
|
|
254
|
+
maxConcurrentUploads,
|
|
255
|
+
baseUrl,
|
|
256
|
+
updateProgress,
|
|
257
|
+
updateStatus,
|
|
258
|
+
onComplete,
|
|
259
|
+
onError,
|
|
260
|
+
]
|
|
261
|
+
);
|
|
262
|
+
|
|
263
|
+
const cancelUpload = useCallback(() => {
|
|
264
|
+
if (abortController) {
|
|
265
|
+
abortController.abort();
|
|
266
|
+
updateStatus("Cancelling upload...");
|
|
267
|
+
}
|
|
268
|
+
}, [abortController, updateStatus]);
|
|
269
|
+
|
|
270
|
+
const reset = useCallback(() => {
|
|
271
|
+
setUploadProgress({});
|
|
272
|
+
setUploadStatus("");
|
|
273
|
+
setIsUploading(false);
|
|
274
|
+
if (abortController) {
|
|
275
|
+
abortController.abort();
|
|
276
|
+
setAbortController(null);
|
|
277
|
+
}
|
|
278
|
+
}, [abortController]);
|
|
279
|
+
|
|
280
|
+
return {
|
|
281
|
+
uploadFiles,
|
|
282
|
+
isUploading,
|
|
283
|
+
uploadProgress,
|
|
284
|
+
uploadStatus,
|
|
285
|
+
cancelUpload,
|
|
286
|
+
reset,
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Alternative hook with simplified API for basic use cases
|
|
291
|
+
export function useSimpleUpload(baseUrl?: string) {
|
|
292
|
+
const { uploadFiles, isUploading, uploadStatus } = useChunkedUpload({
|
|
293
|
+
baseUrl,
|
|
294
|
+
chunkSize: 1024 * 1024, // 1MB
|
|
295
|
+
maxConcurrentUploads: 3,
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
return {
|
|
299
|
+
upload: uploadFiles,
|
|
300
|
+
isUploading,
|
|
301
|
+
status: uploadStatus,
|
|
302
|
+
};
|
|
303
|
+
}
|