nuxt-ui-elements 0.1.31 → 0.1.33
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/module.json +1 -1
- package/dist/runtime/composables/useUploadManager/index.d.ts +4 -4
- package/dist/runtime/composables/useUploadManager/index.js +132 -87
- package/dist/runtime/composables/useUploadManager/plugins/image-compressor.d.ts +1 -1
- package/dist/runtime/composables/useUploadManager/plugins/image-compressor.js +2 -2
- package/dist/runtime/composables/useUploadManager/plugins/storage/azure-datalake.d.ts +12 -1
- package/dist/runtime/composables/useUploadManager/plugins/storage/azure-datalake.js +62 -32
- package/dist/runtime/composables/useUploadManager/plugins/thumbnail-generator.d.ts +4 -3
- package/dist/runtime/composables/useUploadManager/plugins/thumbnail-generator.js +87 -56
- package/dist/runtime/composables/useUploadManager/plugins/video-compressor.d.ts +1 -1
- package/dist/runtime/composables/useUploadManager/plugins/video-compressor.js +17 -7
- package/dist/runtime/composables/useUploadManager/types.d.ts +213 -9
- package/dist/runtime/composables/useUploadManager/types.js +6 -0
- package/dist/runtime/composables/useUploadManager/utils.d.ts +23 -0
- package/dist/runtime/composables/useUploadManager/utils.js +45 -0
- package/dist/runtime/composables/useUploadManager/validators/allowed-file-types.d.ts +1 -1
- package/dist/runtime/composables/useUploadManager/validators/allowed-file-types.js +7 -3
- package/dist/runtime/composables/useUploadManager/validators/max-file-size.d.ts +2 -2
- package/dist/runtime/composables/useUploadManager/validators/max-file-size.js +7 -3
- package/dist/runtime/composables/useUploadManager/validators/max-files.d.ts +1 -1
- package/dist/runtime/composables/useUploadManager/validators/max-files.js +7 -3
- package/package.json +6 -1
package/dist/module.json
CHANGED
|
@@ -184,8 +184,8 @@ export declare const useUploadManager: <TUploadResult = any>(_options?: UploadOp
|
|
|
184
184
|
};
|
|
185
185
|
})[]>>;
|
|
186
186
|
totalProgress: import("vue").ComputedRef<number>;
|
|
187
|
-
addFiles: (newFiles: File[]) => Promise<
|
|
188
|
-
addFile: (file: File) => Promise<UploadFile
|
|
187
|
+
addFiles: (newFiles: File[]) => Promise<UploadFile[]>;
|
|
188
|
+
addFile: (file: File) => Promise<UploadFile>;
|
|
189
189
|
onGetRemoteFile: (fn: GetRemoteFileFn) => void;
|
|
190
190
|
onUpload: (fn: UploadFn<TUploadResult>) => void;
|
|
191
191
|
removeFile: (fileId: string) => Promise<void>;
|
|
@@ -453,14 +453,14 @@ export declare const useUploadManager: <TUploadResult = any>(_options?: UploadOp
|
|
|
453
453
|
} | undefined;
|
|
454
454
|
uploadResult?: import("vue").UnwrapRef<TUploadResult> | undefined;
|
|
455
455
|
meta: Record<string, unknown>;
|
|
456
|
-
}
|
|
456
|
+
};
|
|
457
457
|
upload: () => Promise<void>;
|
|
458
458
|
reset: () => void;
|
|
459
459
|
status: import("vue").Ref<UploadStatus, UploadStatus>;
|
|
460
460
|
getFileData: (fileId: string) => Promise<Blob>;
|
|
461
461
|
getFileURL: (fileId: string) => Promise<string>;
|
|
462
462
|
getFileStream: (fileId: string) => Promise<ReadableStream<Uint8Array>>;
|
|
463
|
-
replaceFileData: (fileId: string, newData: Blob, newName?: string) => Promise<
|
|
463
|
+
replaceFileData: (fileId: string, newData: Blob, newName?: string, shouldAutoUpload?: boolean) => Promise<UploadFile>;
|
|
464
464
|
updateFile: (fileId: string, updatedFile: Partial<UploadFile<TUploadResult>>) => void;
|
|
465
465
|
initializeExistingFiles: (initialFiles: Array<Partial<UploadFile>>) => Promise<void>;
|
|
466
466
|
addPlugin: (plugin: UploaderPlugin<any, any>) => void;
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import mitt from "mitt";
|
|
2
|
-
import { computed, readonly, ref } from "vue";
|
|
3
|
-
import { ValidatorAllowedFileTypes,
|
|
2
|
+
import { computed, onBeforeUnmount, readonly, ref } from "vue";
|
|
3
|
+
import { ValidatorAllowedFileTypes, ValidatorMaxFileSize, ValidatorMaxFiles } from "./validators/index.js";
|
|
4
4
|
import { PluginThumbnailGenerator, PluginImageCompressor } from "./plugins/index.js";
|
|
5
|
+
import { createPluginContext, createFileError, cleanupObjectURLs } from "./utils.js";
|
|
6
|
+
function isStoragePlugin(plugin) {
|
|
7
|
+
return plugin !== null && "upload" in plugin.hooks;
|
|
8
|
+
}
|
|
5
9
|
function getExtension(fullFileName) {
|
|
6
10
|
const lastDot = fullFileName.lastIndexOf(".");
|
|
7
11
|
if (lastDot === -1 || lastDot === fullFileName.length - 1) {
|
|
@@ -10,6 +14,7 @@ function getExtension(fullFileName) {
|
|
|
10
14
|
return fullFileName.slice(lastDot + 1).toLocaleLowerCase();
|
|
11
15
|
}
|
|
12
16
|
const defaultOptions = {
|
|
17
|
+
storage: void 0,
|
|
13
18
|
plugins: [],
|
|
14
19
|
maxFileSize: false,
|
|
15
20
|
allowedFileTypes: false,
|
|
@@ -23,6 +28,7 @@ export const useUploadManager = (_options = {}) => {
|
|
|
23
28
|
const files = ref([]);
|
|
24
29
|
const emitter = mitt();
|
|
25
30
|
const status = ref("waiting");
|
|
31
|
+
const createdObjectURLs = /* @__PURE__ */ new Map();
|
|
26
32
|
const pluginEmitFunctions = /* @__PURE__ */ new Map();
|
|
27
33
|
const getPluginEmitFn = (pluginId) => {
|
|
28
34
|
let emitFn = pluginEmitFunctions.get(pluginId);
|
|
@@ -46,45 +52,53 @@ export const useUploadManager = (_options = {}) => {
|
|
|
46
52
|
const sum = files.value.reduce((acc, file) => acc + file.progress.percentage, 0);
|
|
47
53
|
return Math.round(sum / files.value.length);
|
|
48
54
|
});
|
|
49
|
-
const isStoragePlugin = (plugin) => {
|
|
50
|
-
return !!(plugin.hooks.upload || plugin.hooks.getRemoteFile || plugin.hooks.remove);
|
|
51
|
-
};
|
|
52
55
|
const getStoragePlugin = () => {
|
|
56
|
+
if (options.storage) {
|
|
57
|
+
return options.storage;
|
|
58
|
+
}
|
|
53
59
|
if (!options.plugins) return null;
|
|
54
60
|
for (let i = options.plugins.length - 1; i >= 0; i--) {
|
|
55
|
-
const plugin = options.plugins[i];
|
|
56
|
-
if (
|
|
61
|
+
const plugin = options.plugins[i] || null;
|
|
62
|
+
if (isStoragePlugin(plugin)) {
|
|
63
|
+
if (import.meta.dev) {
|
|
64
|
+
console.warn(
|
|
65
|
+
`[useUploadManager] Storage plugin "${plugin.id}" found in plugins array.
|
|
66
|
+
This is deprecated. Use the 'storage' option instead:
|
|
67
|
+
|
|
68
|
+
useUploadManager({ storage: ${plugin.id}(...) })`
|
|
69
|
+
);
|
|
70
|
+
}
|
|
57
71
|
return plugin;
|
|
58
72
|
}
|
|
59
73
|
}
|
|
60
74
|
return null;
|
|
61
75
|
};
|
|
62
76
|
const addPlugin = (plugin) => {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
if (
|
|
77
|
+
const hasUploadHook = "upload" in plugin.hooks;
|
|
78
|
+
if (hasUploadHook) {
|
|
79
|
+
if (import.meta.dev) {
|
|
66
80
|
console.warn(
|
|
67
|
-
`[useUploadManager]
|
|
68
|
-
|
|
69
|
-
You're trying to add "${plugin.id}" but "${existingStorage.id}" is already registered.
|
|
70
|
-
The LAST storage plugin ("${plugin.id}") will be used for uploads.
|
|
81
|
+
`[useUploadManager] Storage plugin "${plugin.id}" should use the 'storage' option instead of 'plugins':
|
|
71
82
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
83
|
+
useUploadManager({
|
|
84
|
+
storage: ${plugin.id}({ ... }), // \u2713 Correct
|
|
85
|
+
plugins: [...] // Only for validators/processors
|
|
86
|
+
})
|
|
76
87
|
`
|
|
77
88
|
);
|
|
78
89
|
}
|
|
79
90
|
}
|
|
80
|
-
|
|
81
|
-
|
|
91
|
+
if (options.plugins) {
|
|
92
|
+
options.plugins.push(plugin);
|
|
93
|
+
} else {
|
|
94
|
+
options.plugins = [plugin];
|
|
95
|
+
}
|
|
82
96
|
};
|
|
83
97
|
if (options.maxFiles !== false && options.maxFiles !== void 0) {
|
|
84
98
|
addPlugin(ValidatorMaxFiles({ maxFiles: options.maxFiles }));
|
|
85
99
|
}
|
|
86
100
|
if (options.maxFileSize !== false && options.maxFileSize !== void 0) {
|
|
87
|
-
addPlugin(
|
|
101
|
+
addPlugin(ValidatorMaxFileSize({ maxFileSize: options.maxFileSize }));
|
|
88
102
|
}
|
|
89
103
|
if (options.allowedFileTypes !== false && options.allowedFileTypes !== void 0 && options.allowedFileTypes.length > 0) {
|
|
90
104
|
addPlugin(ValidatorAllowedFileTypes({ allowedFileTypes: options.allowedFileTypes }));
|
|
@@ -93,8 +107,8 @@ The LAST storage plugin ("${plugin.id}") will be used for uploads.
|
|
|
93
107
|
const thumbOpts = options.thumbnails === true ? {} : options.thumbnails || {};
|
|
94
108
|
addPlugin(
|
|
95
109
|
PluginThumbnailGenerator({
|
|
96
|
-
|
|
97
|
-
|
|
110
|
+
maxWidth: thumbOpts.width ?? 128,
|
|
111
|
+
maxHeight: thumbOpts.height ?? 128,
|
|
98
112
|
quality: thumbOpts.quality ?? 1
|
|
99
113
|
})
|
|
100
114
|
);
|
|
@@ -131,14 +145,7 @@ The LAST storage plugin ("${plugin.id}") will be used for uploads.
|
|
|
131
145
|
const storagePlugin = getStoragePlugin();
|
|
132
146
|
let remoteFileData;
|
|
133
147
|
if (storagePlugin?.hooks.getRemoteFile) {
|
|
134
|
-
const context =
|
|
135
|
-
files: files.value,
|
|
136
|
-
options,
|
|
137
|
-
emit: (event, payload) => {
|
|
138
|
-
const prefixedEvent = `${storagePlugin.id}:${String(event)}`;
|
|
139
|
-
emitter.emit(prefixedEvent, payload);
|
|
140
|
-
}
|
|
141
|
-
};
|
|
148
|
+
const context = createPluginContext(storagePlugin.id, files.value, options, emitter);
|
|
142
149
|
remoteFileData = await storagePlugin.hooks.getRemoteFile(file.id, context);
|
|
143
150
|
} else {
|
|
144
151
|
remoteFileData = await getRemoteFileFn(file.id);
|
|
@@ -154,12 +161,12 @@ The LAST storage plugin ("${plugin.id}") will be used for uploads.
|
|
|
154
161
|
size: remoteFileData.size,
|
|
155
162
|
mimeType: remoteFileData.mimeType,
|
|
156
163
|
remoteUrl: remoteFileData.remoteUrl,
|
|
164
|
+
preview: remoteFileData.preview || file.preview || remoteFileData.remoteUrl,
|
|
165
|
+
// Use preview from storage, passed-in value, or fallback to remoteUrl
|
|
157
166
|
source: "storage"
|
|
158
167
|
// File loaded from remote storage
|
|
159
168
|
};
|
|
160
|
-
|
|
161
|
-
if (!processedFile) return null;
|
|
162
|
-
return processedFile;
|
|
169
|
+
return existingFile;
|
|
163
170
|
})
|
|
164
171
|
);
|
|
165
172
|
const filteredFiles = initializedfiles.filter((f) => f !== null);
|
|
@@ -183,46 +190,55 @@ The LAST storage plugin ("${plugin.id}") will be used for uploads.
|
|
|
183
190
|
extension
|
|
184
191
|
}
|
|
185
192
|
};
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
193
|
+
try {
|
|
194
|
+
const validatedFile = await runPluginStage("validate", uploadFile);
|
|
195
|
+
if (!validatedFile) {
|
|
196
|
+
throw new Error(`File validation failed for ${file.name}`);
|
|
197
|
+
}
|
|
198
|
+
const preprocessedFile = await runPluginStage("preprocess", validatedFile);
|
|
199
|
+
const fileToAdd = preprocessedFile || validatedFile;
|
|
200
|
+
files.value.push(fileToAdd);
|
|
201
|
+
emitter.emit("file:added", fileToAdd);
|
|
202
|
+
if (options.autoProceed) {
|
|
203
|
+
upload();
|
|
204
|
+
}
|
|
205
|
+
return validatedFile;
|
|
206
|
+
} catch (err) {
|
|
207
|
+
const error = createFileError(uploadFile, err);
|
|
208
|
+
const fileWithError = { ...uploadFile, status: "error", error };
|
|
209
|
+
files.value.push(fileWithError);
|
|
210
|
+
emitter.emit("file:error", { file: fileWithError, error });
|
|
211
|
+
throw err;
|
|
194
212
|
}
|
|
195
|
-
return processedFile;
|
|
196
213
|
};
|
|
197
|
-
const addFiles = (newFiles) => {
|
|
198
|
-
|
|
214
|
+
const addFiles = async (newFiles) => {
|
|
215
|
+
const results = await Promise.allSettled(newFiles.map((file) => addFile(file)));
|
|
216
|
+
const addedFiles = results.filter((r) => r.status === "fulfilled").map((r) => r.value);
|
|
217
|
+
return addedFiles;
|
|
199
218
|
};
|
|
200
219
|
const removeFile = async (fileId) => {
|
|
201
|
-
const file =
|
|
220
|
+
const file = files.value.find((f) => f.id === fileId);
|
|
202
221
|
if (!file) return;
|
|
203
222
|
if (file.remoteUrl) {
|
|
204
223
|
const storagePlugin = getStoragePlugin();
|
|
205
224
|
if (storagePlugin?.hooks.remove) {
|
|
206
225
|
try {
|
|
207
|
-
const context =
|
|
208
|
-
files: files.value,
|
|
209
|
-
options,
|
|
210
|
-
emit: (event, payload) => {
|
|
211
|
-
const prefixedEvent = `${storagePlugin.id}:${String(event)}`;
|
|
212
|
-
emitter.emit(prefixedEvent, payload);
|
|
213
|
-
}
|
|
214
|
-
};
|
|
226
|
+
const context = createPluginContext(storagePlugin.id, files.value, options, emitter);
|
|
215
227
|
await storagePlugin.hooks.remove(file, context);
|
|
216
228
|
} catch (error) {
|
|
217
229
|
console.error(`Storage plugin remove error:`, error);
|
|
218
230
|
}
|
|
219
231
|
}
|
|
220
232
|
}
|
|
233
|
+
cleanupObjectURLs(createdObjectURLs, file.id);
|
|
221
234
|
files.value = files.value.filter((f) => f.id !== fileId);
|
|
222
235
|
emitter.emit("file:removed", file);
|
|
223
236
|
};
|
|
224
237
|
const removeFiles = (fileIds) => {
|
|
225
238
|
const removedFiles = files.value.filter((f) => fileIds.includes(f.id));
|
|
239
|
+
removedFiles.forEach((file) => {
|
|
240
|
+
cleanupObjectURLs(createdObjectURLs, file.id);
|
|
241
|
+
});
|
|
226
242
|
files.value = files.value.filter((f) => !fileIds.includes(f.id));
|
|
227
243
|
removedFiles.forEach((file) => {
|
|
228
244
|
emitter.emit("file:removed", file);
|
|
@@ -231,6 +247,7 @@ The LAST storage plugin ("${plugin.id}") will be used for uploads.
|
|
|
231
247
|
};
|
|
232
248
|
const clearFiles = () => {
|
|
233
249
|
const allFiles = [...files.value];
|
|
250
|
+
cleanupObjectURLs(createdObjectURLs);
|
|
234
251
|
files.value = [];
|
|
235
252
|
allFiles.forEach((file) => {
|
|
236
253
|
emitter.emit("file:removed", file);
|
|
@@ -239,9 +256,6 @@ The LAST storage plugin ("${plugin.id}") will be used for uploads.
|
|
|
239
256
|
};
|
|
240
257
|
const getFileData = async (fileId) => {
|
|
241
258
|
const file = getFile(fileId);
|
|
242
|
-
if (!file) {
|
|
243
|
-
throw new Error(`File not found: ${fileId}`);
|
|
244
|
-
}
|
|
245
259
|
if (file.size > 100 * 1024 * 1024) {
|
|
246
260
|
console.warn(
|
|
247
261
|
`getFileData: Loading large file (${(file.size / 1024 / 1024).toFixed(2)}MB) into memory. Consider using getFileURL() or getFileStream() for better performance.`
|
|
@@ -258,19 +272,19 @@ The LAST storage plugin ("${plugin.id}") will be used for uploads.
|
|
|
258
272
|
};
|
|
259
273
|
const getFileURL = async (fileId) => {
|
|
260
274
|
const file = getFile(fileId);
|
|
261
|
-
if (!file) {
|
|
262
|
-
throw new Error(`File not found: ${fileId}`);
|
|
263
|
-
}
|
|
264
275
|
if (file.source === "local") {
|
|
265
|
-
|
|
276
|
+
const existingURL = createdObjectURLs.get(file.id);
|
|
277
|
+
if (existingURL) {
|
|
278
|
+
return existingURL;
|
|
279
|
+
}
|
|
280
|
+
const objectURL = URL.createObjectURL(file.data);
|
|
281
|
+
createdObjectURLs.set(file.id, objectURL);
|
|
282
|
+
return objectURL;
|
|
266
283
|
}
|
|
267
284
|
return file.remoteUrl;
|
|
268
285
|
};
|
|
269
286
|
const getFileStream = async (fileId) => {
|
|
270
287
|
const file = getFile(fileId);
|
|
271
|
-
if (!file) {
|
|
272
|
-
throw new Error(`File not found: ${fileId}`);
|
|
273
|
-
}
|
|
274
288
|
if (file.source === "local") {
|
|
275
289
|
return file.data.stream();
|
|
276
290
|
}
|
|
@@ -280,11 +294,9 @@ The LAST storage plugin ("${plugin.id}") will be used for uploads.
|
|
|
280
294
|
}
|
|
281
295
|
return response.body;
|
|
282
296
|
};
|
|
283
|
-
const replaceFileData = async (fileId, newData, newName) => {
|
|
297
|
+
const replaceFileData = async (fileId, newData, newName, shouldAutoUpload) => {
|
|
284
298
|
const file = getFile(fileId);
|
|
285
|
-
|
|
286
|
-
throw new Error(`File not found: ${fileId}`);
|
|
287
|
-
}
|
|
299
|
+
cleanupObjectURLs(createdObjectURLs, fileId);
|
|
288
300
|
const updatedFile = {
|
|
289
301
|
...file,
|
|
290
302
|
source: "local",
|
|
@@ -294,12 +306,25 @@ The LAST storage plugin ("${plugin.id}") will be used for uploads.
|
|
|
294
306
|
status: "waiting",
|
|
295
307
|
// Mark as needing upload
|
|
296
308
|
progress: { percentage: 0 },
|
|
297
|
-
remoteUrl: void 0
|
|
309
|
+
remoteUrl: void 0,
|
|
298
310
|
// Clear old remote URL
|
|
311
|
+
meta: {}
|
|
312
|
+
// Clear old metadata (thumbnails, dimensions, etc.)
|
|
299
313
|
};
|
|
314
|
+
const preprocessedFile = await runPluginStage("preprocess", updatedFile);
|
|
315
|
+
const finalFile = preprocessedFile || updatedFile;
|
|
300
316
|
const index = files.value.findIndex((f) => f.id === fileId);
|
|
301
|
-
|
|
302
|
-
|
|
317
|
+
if (index === -1) {
|
|
318
|
+
throw new Error(`File not found: ${fileId}`);
|
|
319
|
+
}
|
|
320
|
+
files.value[index] = finalFile;
|
|
321
|
+
emitter.emit("file:replaced", finalFile);
|
|
322
|
+
emitter.emit("file:added", finalFile);
|
|
323
|
+
const shouldUpload = shouldAutoUpload ?? options.autoProceed;
|
|
324
|
+
if (shouldUpload) {
|
|
325
|
+
upload();
|
|
326
|
+
}
|
|
327
|
+
return finalFile;
|
|
303
328
|
};
|
|
304
329
|
const reorderFile = (oldIndex, newIndex) => {
|
|
305
330
|
if (oldIndex === newIndex) {
|
|
@@ -321,20 +346,35 @@ The LAST storage plugin ("${plugin.id}") will be used for uploads.
|
|
|
321
346
|
emitter.emit("files:reorder", { oldIndex, newIndex });
|
|
322
347
|
};
|
|
323
348
|
const getFile = (fileId) => {
|
|
324
|
-
|
|
349
|
+
const file = files.value.find((f) => f.id === fileId);
|
|
350
|
+
if (!file) {
|
|
351
|
+
throw new Error(`File not found: ${fileId}`);
|
|
352
|
+
}
|
|
353
|
+
return file;
|
|
325
354
|
};
|
|
326
355
|
const upload = async () => {
|
|
327
356
|
const filesToUpload = files.value.filter((f) => f.status === "waiting");
|
|
328
357
|
emitter.emit("upload:start", filesToUpload);
|
|
329
358
|
for (const file of filesToUpload) {
|
|
330
359
|
try {
|
|
331
|
-
|
|
360
|
+
const processedFile = await runPluginStage("process", file);
|
|
361
|
+
if (!processedFile) {
|
|
362
|
+
const error = createFileError(file, new Error("File processing failed"));
|
|
363
|
+
updateFile(file.id, { status: "error", error });
|
|
364
|
+
emitter.emit("file:error", { file, error });
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
if (processedFile.id !== file.id) {
|
|
368
|
+
files.value = files.value.map((f) => f.id === file.id ? processedFile : f);
|
|
369
|
+
}
|
|
370
|
+
updateFile(processedFile.id, { status: "uploading" });
|
|
332
371
|
const onProgress = (progress) => {
|
|
333
|
-
updateFile(
|
|
334
|
-
emitter.emit("upload:progress", { file, progress });
|
|
372
|
+
updateFile(processedFile.id, { progress: { percentage: progress } });
|
|
373
|
+
emitter.emit("upload:progress", { file: processedFile, progress });
|
|
335
374
|
};
|
|
336
375
|
const storagePlugin = getStoragePlugin();
|
|
337
376
|
let uploadResult;
|
|
377
|
+
let remoteUrl;
|
|
338
378
|
if (storagePlugin?.hooks.upload) {
|
|
339
379
|
const context = {
|
|
340
380
|
files: files.value,
|
|
@@ -342,21 +382,18 @@ The LAST storage plugin ("${plugin.id}") will be used for uploads.
|
|
|
342
382
|
onProgress,
|
|
343
383
|
emit: getPluginEmitFn(storagePlugin.id)
|
|
344
384
|
};
|
|
345
|
-
|
|
385
|
+
const result = await storagePlugin.hooks.upload(processedFile, context);
|
|
386
|
+
uploadResult = result;
|
|
387
|
+
remoteUrl = result.url;
|
|
346
388
|
} else {
|
|
347
|
-
uploadResult = await uploadFn(
|
|
389
|
+
uploadResult = await uploadFn(processedFile, onProgress);
|
|
390
|
+
remoteUrl = typeof uploadResult === "string" ? uploadResult : void 0;
|
|
348
391
|
}
|
|
349
|
-
const
|
|
350
|
-
|
|
392
|
+
const currentFile = files.value.find((f) => f.id === processedFile.id);
|
|
393
|
+
const preview = currentFile?.preview || remoteUrl;
|
|
394
|
+
updateFile(processedFile.id, { status: "complete", uploadResult, remoteUrl, preview });
|
|
351
395
|
} catch (err) {
|
|
352
|
-
const error =
|
|
353
|
-
message: err instanceof Error ? err.message : "Unknown upload error",
|
|
354
|
-
details: {
|
|
355
|
-
fileName: file.name,
|
|
356
|
-
fileSize: file.size,
|
|
357
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
358
|
-
}
|
|
359
|
-
};
|
|
396
|
+
const error = createFileError(file, err);
|
|
360
397
|
updateFile(file.id, { status: "error", error });
|
|
361
398
|
emitter.emit("file:error", { file, error });
|
|
362
399
|
}
|
|
@@ -365,13 +402,21 @@ The LAST storage plugin ("${plugin.id}") will be used for uploads.
|
|
|
365
402
|
emitter.emit("upload:complete", completed);
|
|
366
403
|
};
|
|
367
404
|
const reset = () => {
|
|
405
|
+
cleanupObjectURLs(createdObjectURLs);
|
|
368
406
|
files.value = [];
|
|
369
407
|
};
|
|
408
|
+
onBeforeUnmount(() => {
|
|
409
|
+
createdObjectURLs.forEach((url) => {
|
|
410
|
+
URL.revokeObjectURL(url);
|
|
411
|
+
});
|
|
412
|
+
createdObjectURLs.clear();
|
|
413
|
+
});
|
|
370
414
|
const callPluginHook = async (hook, stage, file, context) => {
|
|
371
415
|
switch (stage) {
|
|
372
416
|
case "validate":
|
|
373
417
|
await hook(file, context);
|
|
374
418
|
return file;
|
|
419
|
+
case "preprocess":
|
|
375
420
|
case "process":
|
|
376
421
|
case "complete":
|
|
377
422
|
if (!file) throw new Error("File is required for this hook type");
|
|
@@ -52,5 +52,5 @@ interface ImageCompressorOptions {
|
|
|
52
52
|
*/
|
|
53
53
|
preserveMetadata?: boolean;
|
|
54
54
|
}
|
|
55
|
-
export declare const PluginImageCompressor: (options: ImageCompressorOptions) => import("../types.js").
|
|
55
|
+
export declare const PluginImageCompressor: (options: ImageCompressorOptions) => import("../types.js").ProcessingPlugin<any, ImageCompressorEvents>;
|
|
56
56
|
export {};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export const PluginImageCompressor =
|
|
1
|
+
import { defineProcessingPlugin } from "../types.js";
|
|
2
|
+
export const PluginImageCompressor = defineProcessingPlugin((pluginOptions) => {
|
|
3
3
|
const {
|
|
4
4
|
maxWidth = 1920,
|
|
5
5
|
maxHeight = 1920,
|
|
@@ -30,6 +30,17 @@ export interface AzureDataLakeOptions {
|
|
|
30
30
|
* @default true
|
|
31
31
|
*/
|
|
32
32
|
autoCreateDirectory?: boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Number of retry attempts for failed operations
|
|
35
|
+
* @default 3
|
|
36
|
+
*/
|
|
37
|
+
retries?: number;
|
|
38
|
+
/**
|
|
39
|
+
* Initial delay between retries in milliseconds
|
|
40
|
+
* Uses exponential backoff: delay * (2 ^ attempt)
|
|
41
|
+
* @default 1000 (1 second)
|
|
42
|
+
*/
|
|
43
|
+
retryDelay?: number;
|
|
33
44
|
}
|
|
34
45
|
export interface AzureUploadResult {
|
|
35
46
|
/**
|
|
@@ -41,4 +52,4 @@ export interface AzureUploadResult {
|
|
|
41
52
|
*/
|
|
42
53
|
blobPath: string;
|
|
43
54
|
}
|
|
44
|
-
export declare const PluginAzureDataLake: (options: AzureDataLakeOptions) => import("../../types.js").
|
|
55
|
+
export declare const PluginAzureDataLake: (options: AzureDataLakeOptions) => import("../../types.js").StoragePlugin<AzureUploadResult, Record<string, never>>;
|
|
@@ -1,10 +1,34 @@
|
|
|
1
1
|
import { ref } from "vue";
|
|
2
2
|
import { DataLakeDirectoryClient } from "@azure/storage-file-datalake";
|
|
3
|
-
import {
|
|
4
|
-
export const PluginAzureDataLake =
|
|
3
|
+
import { defineStoragePlugin } from "../../types.js";
|
|
4
|
+
export const PluginAzureDataLake = defineStoragePlugin((options) => {
|
|
5
5
|
const sasURL = ref(options.sasURL || "");
|
|
6
6
|
let refreshPromise = null;
|
|
7
7
|
const directoryCheckedCache = /* @__PURE__ */ new Set();
|
|
8
|
+
const maxRetries = options.retries ?? 3;
|
|
9
|
+
const initialRetryDelay = options.retryDelay ?? 1e3;
|
|
10
|
+
async function withRetry(operation, operationName) {
|
|
11
|
+
let lastError;
|
|
12
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
13
|
+
try {
|
|
14
|
+
return await operation();
|
|
15
|
+
} catch (error) {
|
|
16
|
+
lastError = error;
|
|
17
|
+
if (attempt === maxRetries) {
|
|
18
|
+
break;
|
|
19
|
+
}
|
|
20
|
+
const delay = initialRetryDelay * Math.pow(2, attempt);
|
|
21
|
+
if (import.meta.dev) {
|
|
22
|
+
console.warn(
|
|
23
|
+
`[Azure Storage] ${operationName} failed (attempt ${attempt + 1}/${maxRetries + 1}). Retrying in ${delay}ms...`,
|
|
24
|
+
error
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
throw new Error(`[Azure Storage] ${operationName} failed after ${maxRetries + 1} attempts: ${lastError?.message}`);
|
|
31
|
+
}
|
|
8
32
|
if (options.getSASUrl && !options.sasURL) {
|
|
9
33
|
options.getSASUrl().then((url) => {
|
|
10
34
|
sasURL.value = url;
|
|
@@ -61,46 +85,52 @@ export const PluginAzureDataLake = defineUploaderPlugin((options) => {
|
|
|
61
85
|
if (file.source !== "local" || file.data === null) {
|
|
62
86
|
throw new Error("Cannot upload remote file - no local data available");
|
|
63
87
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
88
|
+
return withRetry(async () => {
|
|
89
|
+
const fileClient = await getFileClient(file.id);
|
|
90
|
+
await fileClient.upload(file.data, {
|
|
91
|
+
metadata: {
|
|
92
|
+
...options.metadata,
|
|
93
|
+
mimeType: file.mimeType,
|
|
94
|
+
size: String(file.size),
|
|
95
|
+
originalName: file.name
|
|
96
|
+
},
|
|
97
|
+
pathHttpHeaders: {
|
|
98
|
+
...options.pathHttpHeaders,
|
|
99
|
+
contentType: file.mimeType
|
|
100
|
+
},
|
|
101
|
+
onProgress: ({ loadedBytes }) => {
|
|
102
|
+
const uploadedPercentage = Math.round(loadedBytes / file.size * 100);
|
|
103
|
+
context.onProgress(uploadedPercentage);
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
return {
|
|
107
|
+
url: fileClient.url,
|
|
108
|
+
blobPath: fileClient.name
|
|
109
|
+
};
|
|
110
|
+
}, `Upload file "${file.name}"`);
|
|
85
111
|
},
|
|
86
112
|
/**
|
|
87
113
|
* Get remote file metadata from Azure
|
|
88
114
|
*/
|
|
89
115
|
async getRemoteFile(fileId, _context) {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
116
|
+
return withRetry(async () => {
|
|
117
|
+
const fileClient = await getFileClient(fileId);
|
|
118
|
+
const properties = await fileClient.getProperties();
|
|
119
|
+
return {
|
|
120
|
+
size: properties.contentLength || 0,
|
|
121
|
+
mimeType: properties.contentType || "application/octet-stream",
|
|
122
|
+
remoteUrl: fileClient.url
|
|
123
|
+
};
|
|
124
|
+
}, `Get remote file "${fileId}"`);
|
|
97
125
|
},
|
|
98
126
|
/**
|
|
99
127
|
* Delete file from Azure Blob Storage
|
|
100
128
|
*/
|
|
101
129
|
async remove(file, _context) {
|
|
102
|
-
|
|
103
|
-
|
|
130
|
+
return withRetry(async () => {
|
|
131
|
+
const fileClient = await getFileClient(file.id);
|
|
132
|
+
await fileClient.deleteIfExists();
|
|
133
|
+
}, `Delete file "${file.name}"`);
|
|
104
134
|
}
|
|
105
135
|
}
|
|
106
136
|
};
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
interface ThumbnailGeneratorOptions {
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
maxWidth?: number;
|
|
3
|
+
maxHeight?: number;
|
|
4
4
|
quality?: number;
|
|
5
|
+
videoCaptureTime?: number;
|
|
5
6
|
}
|
|
6
|
-
export declare const PluginThumbnailGenerator: (options: ThumbnailGeneratorOptions) => import("../types.js").
|
|
7
|
+
export declare const PluginThumbnailGenerator: (options: ThumbnailGeneratorOptions) => import("../types.js").ProcessingPlugin<any, Record<string, never>>;
|
|
7
8
|
export {};
|