nuxt-ui-elements 0.1.30 → 0.1.32
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.js +34 -14
- package/dist/runtime/composables/useUploadManager/plugins/thumbnail-generator.js +1 -1
- package/dist/runtime/composables/useUploadManager/plugins/video-compressor.js +2 -5
- package/dist/runtime/composables/useUploadManager/types.d.ts +85 -4
- package/package.json +1 -1
package/dist/module.json
CHANGED
|
@@ -154,12 +154,12 @@ The LAST storage plugin ("${plugin.id}") will be used for uploads.
|
|
|
154
154
|
size: remoteFileData.size,
|
|
155
155
|
mimeType: remoteFileData.mimeType,
|
|
156
156
|
remoteUrl: remoteFileData.remoteUrl,
|
|
157
|
+
preview: remoteFileData.preview || file.preview || remoteFileData.remoteUrl,
|
|
158
|
+
// Use preview from storage, passed-in value, or fallback to remoteUrl
|
|
157
159
|
source: "storage"
|
|
158
160
|
// File loaded from remote storage
|
|
159
161
|
};
|
|
160
|
-
|
|
161
|
-
if (!processedFile) return null;
|
|
162
|
-
return processedFile;
|
|
162
|
+
return existingFile;
|
|
163
163
|
})
|
|
164
164
|
);
|
|
165
165
|
const filteredFiles = initializedfiles.filter((f) => f !== null);
|
|
@@ -185,14 +185,14 @@ The LAST storage plugin ("${plugin.id}") will be used for uploads.
|
|
|
185
185
|
};
|
|
186
186
|
const validatedFile = await runPluginStage("validate", uploadFile);
|
|
187
187
|
if (!validatedFile) return null;
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
files.value.push(
|
|
191
|
-
emitter.emit("file:added",
|
|
188
|
+
const preprocessedFile = await runPluginStage("preprocess", validatedFile);
|
|
189
|
+
const fileToAdd = preprocessedFile || validatedFile;
|
|
190
|
+
files.value.push(fileToAdd);
|
|
191
|
+
emitter.emit("file:added", fileToAdd);
|
|
192
192
|
if (options.autoProceed) {
|
|
193
193
|
upload();
|
|
194
194
|
}
|
|
195
|
-
return
|
|
195
|
+
return validatedFile;
|
|
196
196
|
};
|
|
197
197
|
const addFiles = (newFiles) => {
|
|
198
198
|
return Promise.all(newFiles.map((file) => addFile(file)));
|
|
@@ -328,10 +328,27 @@ The LAST storage plugin ("${plugin.id}") will be used for uploads.
|
|
|
328
328
|
emitter.emit("upload:start", filesToUpload);
|
|
329
329
|
for (const file of filesToUpload) {
|
|
330
330
|
try {
|
|
331
|
-
|
|
331
|
+
const processedFile = await runPluginStage("process", file);
|
|
332
|
+
if (!processedFile) {
|
|
333
|
+
const error = {
|
|
334
|
+
message: "File processing failed",
|
|
335
|
+
details: {
|
|
336
|
+
fileName: file.name,
|
|
337
|
+
fileSize: file.size,
|
|
338
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
339
|
+
}
|
|
340
|
+
};
|
|
341
|
+
updateFile(file.id, { status: "error", error });
|
|
342
|
+
emitter.emit("file:error", { file, error });
|
|
343
|
+
continue;
|
|
344
|
+
}
|
|
345
|
+
if (processedFile.id !== file.id) {
|
|
346
|
+
files.value = files.value.map((f) => f.id === file.id ? processedFile : f);
|
|
347
|
+
}
|
|
348
|
+
updateFile(processedFile.id, { status: "uploading" });
|
|
332
349
|
const onProgress = (progress) => {
|
|
333
|
-
updateFile(
|
|
334
|
-
emitter.emit("upload:progress", { file, progress });
|
|
350
|
+
updateFile(processedFile.id, { progress: { percentage: progress } });
|
|
351
|
+
emitter.emit("upload:progress", { file: processedFile, progress });
|
|
335
352
|
};
|
|
336
353
|
const storagePlugin = getStoragePlugin();
|
|
337
354
|
let uploadResult;
|
|
@@ -342,12 +359,14 @@ The LAST storage plugin ("${plugin.id}") will be used for uploads.
|
|
|
342
359
|
onProgress,
|
|
343
360
|
emit: getPluginEmitFn(storagePlugin.id)
|
|
344
361
|
};
|
|
345
|
-
uploadResult = await storagePlugin.hooks.upload(
|
|
362
|
+
uploadResult = await storagePlugin.hooks.upload(processedFile, context);
|
|
346
363
|
} else {
|
|
347
|
-
uploadResult = await uploadFn(
|
|
364
|
+
uploadResult = await uploadFn(processedFile, onProgress);
|
|
348
365
|
}
|
|
349
366
|
const remoteUrl = uploadResult?.url;
|
|
350
|
-
|
|
367
|
+
const currentFile = files.value.find((f) => f.id === processedFile.id);
|
|
368
|
+
const preview = currentFile?.preview || remoteUrl;
|
|
369
|
+
updateFile(processedFile.id, { status: "complete", uploadResult, remoteUrl, preview });
|
|
351
370
|
} catch (err) {
|
|
352
371
|
const error = {
|
|
353
372
|
message: err instanceof Error ? err.message : "Unknown upload error",
|
|
@@ -372,6 +391,7 @@ The LAST storage plugin ("${plugin.id}") will be used for uploads.
|
|
|
372
391
|
case "validate":
|
|
373
392
|
await hook(file, context);
|
|
374
393
|
return file;
|
|
394
|
+
case "preprocess":
|
|
375
395
|
case "process":
|
|
376
396
|
case "complete":
|
|
377
397
|
if (!file) throw new Error("File is required for this hook type");
|
|
@@ -3,7 +3,7 @@ export const PluginThumbnailGenerator = defineUploaderPlugin((pluginOptions) =>
|
|
|
3
3
|
return {
|
|
4
4
|
id: "thumbnail-generator",
|
|
5
5
|
hooks: {
|
|
6
|
-
|
|
6
|
+
preprocess: async (file, _context) => {
|
|
7
7
|
const { width = 100, height = 100, quality = 0.7 } = pluginOptions;
|
|
8
8
|
const sourceUrl = file.source === "local" ? URL.createObjectURL(file.data) : file.remoteUrl;
|
|
9
9
|
if (file.mimeType.startsWith("image/")) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { defineUploaderPlugin } from "../types.js";
|
|
2
2
|
import { useFFMpeg } from "../../useFFMpeg.js";
|
|
3
3
|
import { watchEffect } from "vue";
|
|
4
|
-
export const PluginVideoCompressor = defineUploaderPlugin((pluginOptions) => {
|
|
4
|
+
export const PluginVideoCompressor = defineUploaderPlugin((pluginOptions = {}) => {
|
|
5
5
|
return {
|
|
6
6
|
id: "video-compressor",
|
|
7
7
|
hooks: {
|
|
@@ -35,10 +35,7 @@ export const PluginVideoCompressor = defineUploaderPlugin((pluginOptions) => {
|
|
|
35
35
|
try {
|
|
36
36
|
context.emit("start", { file, originalSize: file.size });
|
|
37
37
|
const inputUrl = URL.createObjectURL(file.data);
|
|
38
|
-
const ffmpeg = useFFMpeg({
|
|
39
|
-
inputUrl,
|
|
40
|
-
convertOptions: []
|
|
41
|
-
});
|
|
38
|
+
const ffmpeg = useFFMpeg({ inputUrl });
|
|
42
39
|
await ffmpeg.load();
|
|
43
40
|
watchEffect(() => {
|
|
44
41
|
context.emit("progress", { file, percentage: Math.round(ffmpeg.progress.value * 100) });
|
|
@@ -29,33 +29,112 @@ export type FileSource = 'local' | 'storage' | 'instagram' | 'dropbox' | 'google
|
|
|
29
29
|
* Base properties shared by both local and remote upload files
|
|
30
30
|
*/
|
|
31
31
|
export interface BaseUploadFile<TUploadResult = any> {
|
|
32
|
+
/** Unique identifier for the file */
|
|
32
33
|
id: string;
|
|
34
|
+
/** Original filename (e.g., "vacation-photo.jpg") */
|
|
33
35
|
name: string;
|
|
36
|
+
/** File size in bytes */
|
|
34
37
|
size: number;
|
|
38
|
+
/** MIME type (e.g., "image/jpeg", "video/mp4") */
|
|
35
39
|
mimeType: string;
|
|
40
|
+
/**
|
|
41
|
+
* Current upload status
|
|
42
|
+
*/
|
|
36
43
|
status: FileStatus;
|
|
44
|
+
/**
|
|
45
|
+
* Preview URL for displaying in UI (always populated after initialization)
|
|
46
|
+
* - For local files: Generated by thumbnail plugin as data URL, or remoteUrl after upload
|
|
47
|
+
* - For remote files: Provided by backend/storage, or falls back to remoteUrl
|
|
48
|
+
* - No manual fallback needed: Always available for display
|
|
49
|
+
*/
|
|
37
50
|
preview?: string;
|
|
51
|
+
/** Upload progress tracking */
|
|
38
52
|
progress: FileProgress;
|
|
53
|
+
/** Error information if upload fails */
|
|
39
54
|
error?: FileError;
|
|
55
|
+
/**
|
|
56
|
+
* Result returned by the upload function/storage plugin
|
|
57
|
+
* Can contain additional metadata like etag, key, etc.
|
|
58
|
+
*/
|
|
40
59
|
uploadResult?: TUploadResult;
|
|
60
|
+
/**
|
|
61
|
+
* Custom metadata for storing additional file information
|
|
62
|
+
* Plugins can add data here (e.g., { extension: 'jpg', originalWidth: 4000 })
|
|
63
|
+
*/
|
|
41
64
|
meta: Record<string, unknown>;
|
|
42
65
|
}
|
|
43
66
|
/**
|
|
44
67
|
* Local upload file - originates from user's device
|
|
45
|
-
*
|
|
68
|
+
*
|
|
69
|
+
* Lifecycle:
|
|
70
|
+
* 1. User selects file → created with data, no remoteUrl
|
|
71
|
+
* 2. Validation → runs validate hooks
|
|
72
|
+
* 3. Preprocessing → generates thumbnails (preview set here)
|
|
73
|
+
* 4. User clicks upload → runs process hooks (compression)
|
|
74
|
+
* 5. Upload → remoteUrl set after successful upload
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* ```typescript
|
|
78
|
+
* const localFile: LocalUploadFile = {
|
|
79
|
+
* source: 'local',
|
|
80
|
+
* data: fileBlob, // Has local data
|
|
81
|
+
* remoteUrl: undefined, // Not uploaded yet
|
|
82
|
+
* preview: 'data:image/jpeg;base64,...', // Generated by thumbnail plugin
|
|
83
|
+
* ...
|
|
84
|
+
* }
|
|
85
|
+
* ```
|
|
46
86
|
*/
|
|
47
87
|
export interface LocalUploadFile<TUploadResult = any> extends BaseUploadFile<TUploadResult> {
|
|
88
|
+
/** Always 'local' for files selected from user's device */
|
|
48
89
|
source: 'local';
|
|
90
|
+
/**
|
|
91
|
+
* The actual file data (File from input or Blob)
|
|
92
|
+
* Available for processing, compression, thumbnail generation, etc.
|
|
93
|
+
*/
|
|
49
94
|
data: File | Blob;
|
|
95
|
+
/**
|
|
96
|
+
* URL where file was uploaded (set after successful upload)
|
|
97
|
+
* undefined before upload, populated after upload completes
|
|
98
|
+
* Use to check if file has been uploaded: `if (file.remoteUrl) { ... }`
|
|
99
|
+
*/
|
|
50
100
|
remoteUrl?: string;
|
|
51
101
|
}
|
|
52
102
|
/**
|
|
53
|
-
* Remote upload file - originates from remote source
|
|
54
|
-
*
|
|
103
|
+
* Remote upload file - originates from remote source
|
|
104
|
+
*
|
|
105
|
+
* These files already exist remotely and don't need uploading.
|
|
106
|
+
* Common sources:
|
|
107
|
+
* - 'storage': Previously uploaded files being re-loaded
|
|
108
|
+
* - 'instagram', 'dropbox', etc.: Files from cloud pickers
|
|
109
|
+
*
|
|
110
|
+
* @example
|
|
111
|
+
* ```typescript
|
|
112
|
+
* const remoteFile: RemoteUploadFile = {
|
|
113
|
+
* source: 'storage',
|
|
114
|
+
* data: null, // No local data - file is remote
|
|
115
|
+
* remoteUrl: 'https://storage.com/file.jpg', // Always present
|
|
116
|
+
* preview: 'https://storage.com/thumbnails/file.jpg', // Optional, from backend
|
|
117
|
+
* status: 'complete', // Already uploaded
|
|
118
|
+
* ...
|
|
119
|
+
* }
|
|
120
|
+
* ```
|
|
55
121
|
*/
|
|
56
122
|
export interface RemoteUploadFile<TUploadResult = any> extends BaseUploadFile<TUploadResult> {
|
|
123
|
+
/**
|
|
124
|
+
* Source of the remote file
|
|
125
|
+
* - 'storage': File from your storage (previously uploaded)
|
|
126
|
+
* - 'instagram', 'dropbox', etc.: File from cloud picker
|
|
127
|
+
*/
|
|
57
128
|
source: Exclude<FileSource, 'local'>;
|
|
129
|
+
/**
|
|
130
|
+
* Always null for remote files (no local data available)
|
|
131
|
+
* File exists remotely and is accessed via remoteUrl
|
|
132
|
+
*/
|
|
58
133
|
data: null;
|
|
134
|
+
/**
|
|
135
|
+
* URL to the remote file (always present for remote files)
|
|
136
|
+
* Use this to display/download the file
|
|
137
|
+
*/
|
|
59
138
|
remoteUrl: string;
|
|
60
139
|
}
|
|
61
140
|
/**
|
|
@@ -199,9 +278,10 @@ export type UploadHook<TUploadResult = any, TPluginEvents extends Record<string,
|
|
|
199
278
|
}>;
|
|
200
279
|
export type GetRemoteFileHook<TPluginEvents extends Record<string, any> = Record<string, never>> = (fileId: string, context: PluginContext<TPluginEvents>) => Promise<MinimumRemoteFileAttributes>;
|
|
201
280
|
export type RemoveHook<TPluginEvents extends Record<string, any> = Record<string, never>> = (file: UploadFile, context: PluginContext<TPluginEvents>) => Promise<void>;
|
|
202
|
-
export type PluginLifecycleStage = "validate" | "process" | "upload" | "complete";
|
|
281
|
+
export type PluginLifecycleStage = "validate" | "preprocess" | "process" | "upload" | "complete";
|
|
203
282
|
export type PluginHooks<TUploadResult = any, TPluginEvents extends Record<string, any> = Record<string, never>> = {
|
|
204
283
|
validate?: ValidationHook<TPluginEvents>;
|
|
284
|
+
preprocess?: ProcessingHook<TPluginEvents>;
|
|
205
285
|
process?: ProcessingHook<TPluginEvents>;
|
|
206
286
|
upload?: UploadHook<TUploadResult, TPluginEvents>;
|
|
207
287
|
getRemoteFile?: GetRemoteFileHook<TPluginEvents>;
|
|
@@ -280,5 +360,6 @@ type MinimumRemoteFileAttributes = {
|
|
|
280
360
|
size: number;
|
|
281
361
|
mimeType: string;
|
|
282
362
|
remoteUrl: string;
|
|
363
|
+
preview?: string;
|
|
283
364
|
};
|
|
284
365
|
export {};
|
package/package.json
CHANGED