nuxt-ui-elements 0.1.28 → 0.1.30
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/useFFMpeg.d.ts +14 -0
- package/dist/runtime/composables/useFFMpeg.js +66 -0
- package/dist/runtime/composables/useUploadManager/index.d.ts +132 -29
- package/dist/runtime/composables/useUploadManager/index.js +94 -22
- package/dist/runtime/composables/useUploadManager/plugins/image-compressor.js +4 -0
- package/dist/runtime/composables/useUploadManager/plugins/index.d.ts +1 -0
- package/dist/runtime/composables/useUploadManager/plugins/index.js +1 -0
- package/dist/runtime/composables/useUploadManager/plugins/storage/azure-datalake.d.ts +1 -2
- package/dist/runtime/composables/useUploadManager/plugins/storage/azure-datalake.js +4 -5
- package/dist/runtime/composables/useUploadManager/plugins/thumbnail-generator.js +1 -1
- package/dist/runtime/composables/useUploadManager/plugins/video-compressor.d.ts +72 -0
- package/dist/runtime/composables/useUploadManager/plugins/video-compressor.js +102 -0
- package/dist/runtime/composables/useUploadManager/types.d.ts +69 -5
- package/package.json +11 -3
package/dist/module.json
CHANGED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
interface FFMPegOptions {
|
|
2
|
+
inputUrl: string;
|
|
3
|
+
convertOptions?: string[];
|
|
4
|
+
}
|
|
5
|
+
export declare const useFFMpeg: (options: FFMPegOptions) => {
|
|
6
|
+
status: import("vue").Ref<"success" | "error" | "paused" | "converting", "success" | "error" | "paused" | "converting">;
|
|
7
|
+
progress: import("vue").Ref<number, number>;
|
|
8
|
+
convertedFile: import("vue").Ref<File | undefined, File | undefined>;
|
|
9
|
+
load: () => Promise<void>;
|
|
10
|
+
unload: () => void;
|
|
11
|
+
convert: (convertOptions: string[]) => Promise<File | undefined>;
|
|
12
|
+
onConvertSuccess: (callback: (updatedVideo: File) => void) => (updatedVideo: File) => void;
|
|
13
|
+
};
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { FFmpeg } from "@ffmpeg/ffmpeg";
|
|
2
|
+
import { fetchFile, toBlobURL } from "@ffmpeg/util";
|
|
3
|
+
import { ref } from "vue";
|
|
4
|
+
const defaultOptions = {
|
|
5
|
+
convertOptions: []
|
|
6
|
+
};
|
|
7
|
+
const baseURL = "https://unpkg.com/@ffmpeg/core@0.12.6/dist/esm";
|
|
8
|
+
export const useFFMpeg = (options) => {
|
|
9
|
+
options = { ...defaultOptions, ...options };
|
|
10
|
+
const ffmpeg = new FFmpeg();
|
|
11
|
+
const status = ref("paused");
|
|
12
|
+
const progress = ref(0);
|
|
13
|
+
const originalFile = ref();
|
|
14
|
+
const convertedFile = ref();
|
|
15
|
+
let _onConvertSuccess;
|
|
16
|
+
ffmpeg.on("progress", ({ time }) => {
|
|
17
|
+
progress.value = time / 1e6;
|
|
18
|
+
});
|
|
19
|
+
const load = async () => {
|
|
20
|
+
await ffmpeg.load({
|
|
21
|
+
coreURL: await toBlobURL(`${baseURL}/ffmpeg-core.js`, "text/javascript"),
|
|
22
|
+
wasmURL: await toBlobURL(`${baseURL}/ffmpeg-core.wasm`, "application/wasm")
|
|
23
|
+
});
|
|
24
|
+
};
|
|
25
|
+
const unload = () => ffmpeg.terminate();
|
|
26
|
+
const convert = async (convertOptions) => {
|
|
27
|
+
status.value = "converting";
|
|
28
|
+
const command = ["-i", "input.avi", ...options.convertOptions, ...convertOptions, "-c", "copy", "output.mp4"];
|
|
29
|
+
try {
|
|
30
|
+
originalFile.value = await fetchFile(options.inputUrl);
|
|
31
|
+
await ffmpeg.writeFile("input.avi", originalFile.value);
|
|
32
|
+
await ffmpeg.exec(command);
|
|
33
|
+
convertedFile.value = await getModifiedVideo();
|
|
34
|
+
await ffmpeg.deleteFile("input.avi");
|
|
35
|
+
status.value = "success";
|
|
36
|
+
if (_onConvertSuccess) _onConvertSuccess(convertedFile.value);
|
|
37
|
+
return convertedFile.value;
|
|
38
|
+
} catch (error) {
|
|
39
|
+
if (import.meta.env.DEV) console.error(error);
|
|
40
|
+
status.value = "error";
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
const getModifiedVideo = async () => {
|
|
44
|
+
const data = await ffmpeg.readFile("output.mp4");
|
|
45
|
+
let bytes;
|
|
46
|
+
if (typeof data === "string") {
|
|
47
|
+
bytes = new TextEncoder().encode(data);
|
|
48
|
+
} else {
|
|
49
|
+
const buffer = new ArrayBuffer(data.byteLength);
|
|
50
|
+
new Uint8Array(buffer).set(data);
|
|
51
|
+
bytes = new Uint8Array(buffer);
|
|
52
|
+
}
|
|
53
|
+
const updatedFile = new File([bytes], "output.mp4", { type: "video/mp4" });
|
|
54
|
+
return updatedFile;
|
|
55
|
+
};
|
|
56
|
+
const onConvertSuccess = (callback) => _onConvertSuccess = callback;
|
|
57
|
+
return {
|
|
58
|
+
status,
|
|
59
|
+
progress,
|
|
60
|
+
convertedFile,
|
|
61
|
+
load,
|
|
62
|
+
unload,
|
|
63
|
+
convert,
|
|
64
|
+
onConvertSuccess
|
|
65
|
+
};
|
|
66
|
+
};
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
import type { UploaderEvents, UploadFile, UploadOptions, UploadStatus, UploadFn, GetRemoteFileFn, Plugin as UploaderPlugin } from "./types.js";
|
|
2
2
|
export declare const useUploadManager: <TUploadResult = any>(_options?: UploadOptions) => {
|
|
3
|
-
files: Readonly<import("vue").Ref<readonly {
|
|
4
|
-
readonly
|
|
5
|
-
readonly name: string;
|
|
6
|
-
readonly size: number;
|
|
7
|
-
readonly mimeType: string;
|
|
3
|
+
files: Readonly<import("vue").Ref<readonly ({
|
|
4
|
+
readonly source: "local";
|
|
8
5
|
readonly data: {
|
|
9
6
|
readonly lastModified: number;
|
|
10
7
|
readonly name: string;
|
|
@@ -55,6 +52,11 @@ export declare const useUploadManager: <TUploadResult = any>(_options?: UploadOp
|
|
|
55
52
|
(): Promise<string>;
|
|
56
53
|
};
|
|
57
54
|
};
|
|
55
|
+
readonly remoteUrl?: string | undefined;
|
|
56
|
+
readonly id: string;
|
|
57
|
+
readonly name: string;
|
|
58
|
+
readonly size: number;
|
|
59
|
+
readonly mimeType: string;
|
|
58
60
|
readonly status: import("./types.js").FileStatus;
|
|
59
61
|
readonly preview?: string | undefined;
|
|
60
62
|
readonly progress: {
|
|
@@ -65,16 +67,32 @@ export declare const useUploadManager: <TUploadResult = any>(_options?: UploadOp
|
|
|
65
67
|
readonly details?: Readonly<unknown> | undefined;
|
|
66
68
|
} | undefined;
|
|
67
69
|
readonly uploadResult?: import("vue").DeepReadonly<import("vue").UnwrapRef<TUploadResult>> | undefined;
|
|
68
|
-
readonly isRemote?: boolean | undefined;
|
|
69
|
-
readonly remoteUrl?: string | undefined;
|
|
70
70
|
readonly meta: {
|
|
71
71
|
readonly [x: string]: Readonly<unknown>;
|
|
72
72
|
};
|
|
73
|
-
}
|
|
73
|
+
} | {
|
|
74
|
+
readonly source: Exclude<import("./types.js").FileSource, "local">;
|
|
75
|
+
readonly data: null;
|
|
76
|
+
readonly remoteUrl: string;
|
|
74
77
|
readonly id: string;
|
|
75
78
|
readonly name: string;
|
|
76
79
|
readonly size: number;
|
|
77
80
|
readonly mimeType: string;
|
|
81
|
+
readonly status: import("./types.js").FileStatus;
|
|
82
|
+
readonly preview?: string | undefined;
|
|
83
|
+
readonly progress: {
|
|
84
|
+
readonly percentage: number;
|
|
85
|
+
};
|
|
86
|
+
readonly error?: {
|
|
87
|
+
readonly message: string;
|
|
88
|
+
readonly details?: Readonly<unknown> | undefined;
|
|
89
|
+
} | undefined;
|
|
90
|
+
readonly uploadResult?: import("vue").DeepReadonly<import("vue").UnwrapRef<TUploadResult>> | undefined;
|
|
91
|
+
readonly meta: {
|
|
92
|
+
readonly [x: string]: Readonly<unknown>;
|
|
93
|
+
};
|
|
94
|
+
})[], readonly ({
|
|
95
|
+
readonly source: "local";
|
|
78
96
|
readonly data: {
|
|
79
97
|
readonly lastModified: number;
|
|
80
98
|
readonly name: string;
|
|
@@ -125,6 +143,11 @@ export declare const useUploadManager: <TUploadResult = any>(_options?: UploadOp
|
|
|
125
143
|
(): Promise<string>;
|
|
126
144
|
};
|
|
127
145
|
};
|
|
146
|
+
readonly remoteUrl?: string | undefined;
|
|
147
|
+
readonly id: string;
|
|
148
|
+
readonly name: string;
|
|
149
|
+
readonly size: number;
|
|
150
|
+
readonly mimeType: string;
|
|
128
151
|
readonly status: import("./types.js").FileStatus;
|
|
129
152
|
readonly preview?: string | undefined;
|
|
130
153
|
readonly progress: {
|
|
@@ -135,23 +158,39 @@ export declare const useUploadManager: <TUploadResult = any>(_options?: UploadOp
|
|
|
135
158
|
readonly details?: Readonly<unknown> | undefined;
|
|
136
159
|
} | undefined;
|
|
137
160
|
readonly uploadResult?: import("vue").DeepReadonly<import("vue").UnwrapRef<TUploadResult>> | undefined;
|
|
138
|
-
readonly isRemote?: boolean | undefined;
|
|
139
|
-
readonly remoteUrl?: string | undefined;
|
|
140
161
|
readonly meta: {
|
|
141
162
|
readonly [x: string]: Readonly<unknown>;
|
|
142
163
|
};
|
|
143
|
-
}
|
|
164
|
+
} | {
|
|
165
|
+
readonly source: Exclude<import("./types.js").FileSource, "local">;
|
|
166
|
+
readonly data: null;
|
|
167
|
+
readonly remoteUrl: string;
|
|
168
|
+
readonly id: string;
|
|
169
|
+
readonly name: string;
|
|
170
|
+
readonly size: number;
|
|
171
|
+
readonly mimeType: string;
|
|
172
|
+
readonly status: import("./types.js").FileStatus;
|
|
173
|
+
readonly preview?: string | undefined;
|
|
174
|
+
readonly progress: {
|
|
175
|
+
readonly percentage: number;
|
|
176
|
+
};
|
|
177
|
+
readonly error?: {
|
|
178
|
+
readonly message: string;
|
|
179
|
+
readonly details?: Readonly<unknown> | undefined;
|
|
180
|
+
} | undefined;
|
|
181
|
+
readonly uploadResult?: import("vue").DeepReadonly<import("vue").UnwrapRef<TUploadResult>> | undefined;
|
|
182
|
+
readonly meta: {
|
|
183
|
+
readonly [x: string]: Readonly<unknown>;
|
|
184
|
+
};
|
|
185
|
+
})[]>>;
|
|
144
186
|
totalProgress: import("vue").ComputedRef<number>;
|
|
145
|
-
addFiles: (newFiles: File[]) => Promise<(UploadFile
|
|
146
|
-
addFile: (file: File) => Promise<UploadFile
|
|
187
|
+
addFiles: (newFiles: File[]) => Promise<(UploadFile | null)[]>;
|
|
188
|
+
addFile: (file: File) => Promise<UploadFile | null>;
|
|
147
189
|
onGetRemoteFile: (fn: GetRemoteFileFn) => void;
|
|
148
190
|
onUpload: (fn: UploadFn<TUploadResult>) => void;
|
|
149
191
|
removeFile: (fileId: string) => Promise<void>;
|
|
150
|
-
removeFiles: (fileIds: string[]) => {
|
|
151
|
-
|
|
152
|
-
name: string;
|
|
153
|
-
size: number;
|
|
154
|
-
mimeType: string;
|
|
192
|
+
removeFiles: (fileIds: string[]) => ({
|
|
193
|
+
source: "local";
|
|
155
194
|
data: {
|
|
156
195
|
readonly lastModified: number;
|
|
157
196
|
readonly name: string;
|
|
@@ -202,6 +241,11 @@ export declare const useUploadManager: <TUploadResult = any>(_options?: UploadOp
|
|
|
202
241
|
(): Promise<string>;
|
|
203
242
|
};
|
|
204
243
|
};
|
|
244
|
+
remoteUrl?: string | undefined;
|
|
245
|
+
id: string;
|
|
246
|
+
name: string;
|
|
247
|
+
size: number;
|
|
248
|
+
mimeType: string;
|
|
205
249
|
status: import("./types.js").FileStatus;
|
|
206
250
|
preview?: string | undefined;
|
|
207
251
|
progress: {
|
|
@@ -212,15 +256,29 @@ export declare const useUploadManager: <TUploadResult = any>(_options?: UploadOp
|
|
|
212
256
|
details?: unknown;
|
|
213
257
|
} | undefined;
|
|
214
258
|
uploadResult?: import("vue").UnwrapRef<TUploadResult> | undefined;
|
|
215
|
-
isRemote?: boolean | undefined;
|
|
216
|
-
remoteUrl?: string | undefined;
|
|
217
259
|
meta: Record<string, unknown>;
|
|
218
|
-
}
|
|
219
|
-
|
|
260
|
+
} | {
|
|
261
|
+
source: Exclude<import("./types.js").FileSource, "local">;
|
|
262
|
+
data: null;
|
|
263
|
+
remoteUrl: string;
|
|
220
264
|
id: string;
|
|
221
265
|
name: string;
|
|
222
266
|
size: number;
|
|
223
267
|
mimeType: string;
|
|
268
|
+
status: import("./types.js").FileStatus;
|
|
269
|
+
preview?: string | undefined;
|
|
270
|
+
progress: {
|
|
271
|
+
percentage: number;
|
|
272
|
+
};
|
|
273
|
+
error?: {
|
|
274
|
+
message: string;
|
|
275
|
+
details?: unknown;
|
|
276
|
+
} | undefined;
|
|
277
|
+
uploadResult?: import("vue").UnwrapRef<TUploadResult> | undefined;
|
|
278
|
+
meta: Record<string, unknown>;
|
|
279
|
+
})[];
|
|
280
|
+
clearFiles: () => ({
|
|
281
|
+
source: "local";
|
|
224
282
|
data: {
|
|
225
283
|
readonly lastModified: number;
|
|
226
284
|
readonly name: string;
|
|
@@ -271,6 +329,11 @@ export declare const useUploadManager: <TUploadResult = any>(_options?: UploadOp
|
|
|
271
329
|
(): Promise<string>;
|
|
272
330
|
};
|
|
273
331
|
};
|
|
332
|
+
remoteUrl?: string | undefined;
|
|
333
|
+
id: string;
|
|
334
|
+
name: string;
|
|
335
|
+
size: number;
|
|
336
|
+
mimeType: string;
|
|
274
337
|
status: import("./types.js").FileStatus;
|
|
275
338
|
preview?: string | undefined;
|
|
276
339
|
progress: {
|
|
@@ -281,16 +344,30 @@ export declare const useUploadManager: <TUploadResult = any>(_options?: UploadOp
|
|
|
281
344
|
details?: unknown;
|
|
282
345
|
} | undefined;
|
|
283
346
|
uploadResult?: import("vue").UnwrapRef<TUploadResult> | undefined;
|
|
284
|
-
isRemote?: boolean | undefined;
|
|
285
|
-
remoteUrl?: string | undefined;
|
|
286
347
|
meta: Record<string, unknown>;
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
|
|
348
|
+
} | {
|
|
349
|
+
source: Exclude<import("./types.js").FileSource, "local">;
|
|
350
|
+
data: null;
|
|
351
|
+
remoteUrl: string;
|
|
290
352
|
id: string;
|
|
291
353
|
name: string;
|
|
292
354
|
size: number;
|
|
293
355
|
mimeType: string;
|
|
356
|
+
status: import("./types.js").FileStatus;
|
|
357
|
+
preview?: string | undefined;
|
|
358
|
+
progress: {
|
|
359
|
+
percentage: number;
|
|
360
|
+
};
|
|
361
|
+
error?: {
|
|
362
|
+
message: string;
|
|
363
|
+
details?: unknown;
|
|
364
|
+
} | undefined;
|
|
365
|
+
uploadResult?: import("vue").UnwrapRef<TUploadResult> | undefined;
|
|
366
|
+
meta: Record<string, unknown>;
|
|
367
|
+
})[];
|
|
368
|
+
reorderFile: (oldIndex: number, newIndex: number) => void;
|
|
369
|
+
getFile: (fileId: string) => {
|
|
370
|
+
source: "local";
|
|
294
371
|
data: {
|
|
295
372
|
readonly lastModified: number;
|
|
296
373
|
readonly name: string;
|
|
@@ -341,6 +418,30 @@ export declare const useUploadManager: <TUploadResult = any>(_options?: UploadOp
|
|
|
341
418
|
(): Promise<string>;
|
|
342
419
|
};
|
|
343
420
|
};
|
|
421
|
+
remoteUrl?: string | undefined;
|
|
422
|
+
id: string;
|
|
423
|
+
name: string;
|
|
424
|
+
size: number;
|
|
425
|
+
mimeType: string;
|
|
426
|
+
status: import("./types.js").FileStatus;
|
|
427
|
+
preview?: string | undefined;
|
|
428
|
+
progress: {
|
|
429
|
+
percentage: number;
|
|
430
|
+
};
|
|
431
|
+
error?: {
|
|
432
|
+
message: string;
|
|
433
|
+
details?: unknown;
|
|
434
|
+
} | undefined;
|
|
435
|
+
uploadResult?: import("vue").UnwrapRef<TUploadResult> | undefined;
|
|
436
|
+
meta: Record<string, unknown>;
|
|
437
|
+
} | {
|
|
438
|
+
source: Exclude<import("./types.js").FileSource, "local">;
|
|
439
|
+
data: null;
|
|
440
|
+
remoteUrl: string;
|
|
441
|
+
id: string;
|
|
442
|
+
name: string;
|
|
443
|
+
size: number;
|
|
444
|
+
mimeType: string;
|
|
344
445
|
status: import("./types.js").FileStatus;
|
|
345
446
|
preview?: string | undefined;
|
|
346
447
|
progress: {
|
|
@@ -351,13 +452,15 @@ export declare const useUploadManager: <TUploadResult = any>(_options?: UploadOp
|
|
|
351
452
|
details?: unknown;
|
|
352
453
|
} | undefined;
|
|
353
454
|
uploadResult?: import("vue").UnwrapRef<TUploadResult> | undefined;
|
|
354
|
-
isRemote?: boolean | undefined;
|
|
355
|
-
remoteUrl?: string | undefined;
|
|
356
455
|
meta: Record<string, unknown>;
|
|
357
456
|
} | undefined;
|
|
358
457
|
upload: () => Promise<void>;
|
|
359
458
|
reset: () => void;
|
|
360
459
|
status: import("vue").Ref<UploadStatus, UploadStatus>;
|
|
460
|
+
getFileData: (fileId: string) => Promise<Blob>;
|
|
461
|
+
getFileURL: (fileId: string) => Promise<string>;
|
|
462
|
+
getFileStream: (fileId: string) => Promise<ReadableStream<Uint8Array>>;
|
|
463
|
+
replaceFileData: (fileId: string, newData: Blob, newName?: string) => Promise<void>;
|
|
361
464
|
updateFile: (fileId: string, updatedFile: Partial<UploadFile<TUploadResult>>) => void;
|
|
362
465
|
initializeExistingFiles: (initialFiles: Array<Partial<UploadFile>>) => Promise<void>;
|
|
363
466
|
addPlugin: (plugin: UploaderPlugin<any, any>) => void;
|
|
@@ -147,13 +147,15 @@ The LAST storage plugin ("${plugin.id}") will be used for uploads.
|
|
|
147
147
|
...file,
|
|
148
148
|
id: file.id,
|
|
149
149
|
name: file.id,
|
|
150
|
-
data:
|
|
150
|
+
data: null,
|
|
151
151
|
status: "complete",
|
|
152
152
|
progress: { percentage: 100 },
|
|
153
153
|
meta: {},
|
|
154
154
|
size: remoteFileData.size,
|
|
155
155
|
mimeType: remoteFileData.mimeType,
|
|
156
|
-
remoteUrl: remoteFileData.remoteUrl
|
|
156
|
+
remoteUrl: remoteFileData.remoteUrl,
|
|
157
|
+
source: "storage"
|
|
158
|
+
// File loaded from remote storage
|
|
157
159
|
};
|
|
158
160
|
const processedFile = await runPluginStage("process", existingFile);
|
|
159
161
|
if (!processedFile) return null;
|
|
@@ -176,6 +178,7 @@ The LAST storage plugin ("${plugin.id}") will be used for uploads.
|
|
|
176
178
|
status: "waiting",
|
|
177
179
|
mimeType: file.type,
|
|
178
180
|
data: file,
|
|
181
|
+
source: "local",
|
|
179
182
|
meta: {
|
|
180
183
|
extension
|
|
181
184
|
}
|
|
@@ -195,22 +198,24 @@ The LAST storage plugin ("${plugin.id}") will be used for uploads.
|
|
|
195
198
|
return Promise.all(newFiles.map((file) => addFile(file)));
|
|
196
199
|
};
|
|
197
200
|
const removeFile = async (fileId) => {
|
|
198
|
-
const file =
|
|
201
|
+
const file = getFile(fileId);
|
|
199
202
|
if (!file) return;
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
203
|
+
if (file.remoteUrl) {
|
|
204
|
+
const storagePlugin = getStoragePlugin();
|
|
205
|
+
if (storagePlugin?.hooks.remove) {
|
|
206
|
+
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
|
+
};
|
|
215
|
+
await storagePlugin.hooks.remove(file, context);
|
|
216
|
+
} catch (error) {
|
|
217
|
+
console.error(`Storage plugin remove error:`, error);
|
|
218
|
+
}
|
|
214
219
|
}
|
|
215
220
|
}
|
|
216
221
|
files.value = files.value.filter((f) => f.id !== fileId);
|
|
@@ -232,6 +237,70 @@ The LAST storage plugin ("${plugin.id}") will be used for uploads.
|
|
|
232
237
|
});
|
|
233
238
|
return allFiles;
|
|
234
239
|
};
|
|
240
|
+
const getFileData = async (fileId) => {
|
|
241
|
+
const file = getFile(fileId);
|
|
242
|
+
if (!file) {
|
|
243
|
+
throw new Error(`File not found: ${fileId}`);
|
|
244
|
+
}
|
|
245
|
+
if (file.size > 100 * 1024 * 1024) {
|
|
246
|
+
console.warn(
|
|
247
|
+
`getFileData: Loading large file (${(file.size / 1024 / 1024).toFixed(2)}MB) into memory. Consider using getFileURL() or getFileStream() for better performance.`
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
if (file.source === "local") {
|
|
251
|
+
return file.data;
|
|
252
|
+
}
|
|
253
|
+
const response = await fetch(file.remoteUrl);
|
|
254
|
+
if (!response.ok) {
|
|
255
|
+
throw new Error(`Failed to fetch file: ${response.statusText}`);
|
|
256
|
+
}
|
|
257
|
+
return await response.blob();
|
|
258
|
+
};
|
|
259
|
+
const getFileURL = async (fileId) => {
|
|
260
|
+
const file = getFile(fileId);
|
|
261
|
+
if (!file) {
|
|
262
|
+
throw new Error(`File not found: ${fileId}`);
|
|
263
|
+
}
|
|
264
|
+
if (file.source === "local") {
|
|
265
|
+
return URL.createObjectURL(file.data);
|
|
266
|
+
}
|
|
267
|
+
return file.remoteUrl;
|
|
268
|
+
};
|
|
269
|
+
const getFileStream = async (fileId) => {
|
|
270
|
+
const file = getFile(fileId);
|
|
271
|
+
if (!file) {
|
|
272
|
+
throw new Error(`File not found: ${fileId}`);
|
|
273
|
+
}
|
|
274
|
+
if (file.source === "local") {
|
|
275
|
+
return file.data.stream();
|
|
276
|
+
}
|
|
277
|
+
const response = await fetch(file.remoteUrl);
|
|
278
|
+
if (!response.ok || !response.body) {
|
|
279
|
+
throw new Error(`Failed to fetch file stream: ${response.statusText}`);
|
|
280
|
+
}
|
|
281
|
+
return response.body;
|
|
282
|
+
};
|
|
283
|
+
const replaceFileData = async (fileId, newData, newName) => {
|
|
284
|
+
const file = getFile(fileId);
|
|
285
|
+
if (!file) {
|
|
286
|
+
throw new Error(`File not found: ${fileId}`);
|
|
287
|
+
}
|
|
288
|
+
const updatedFile = {
|
|
289
|
+
...file,
|
|
290
|
+
source: "local",
|
|
291
|
+
data: newData,
|
|
292
|
+
name: newName || file.name,
|
|
293
|
+
size: newData.size,
|
|
294
|
+
status: "waiting",
|
|
295
|
+
// Mark as needing upload
|
|
296
|
+
progress: { percentage: 0 },
|
|
297
|
+
remoteUrl: void 0
|
|
298
|
+
// Clear old remote URL
|
|
299
|
+
};
|
|
300
|
+
const index = files.value.findIndex((f) => f.id === fileId);
|
|
301
|
+
files.value[index] = updatedFile;
|
|
302
|
+
emitter.emit("file:added", updatedFile);
|
|
303
|
+
};
|
|
235
304
|
const reorderFile = (oldIndex, newIndex) => {
|
|
236
305
|
if (oldIndex === newIndex) {
|
|
237
306
|
if (import.meta.dev) {
|
|
@@ -271,16 +340,14 @@ The LAST storage plugin ("${plugin.id}") will be used for uploads.
|
|
|
271
340
|
files: files.value,
|
|
272
341
|
options,
|
|
273
342
|
onProgress,
|
|
274
|
-
emit: (
|
|
275
|
-
const prefixedEvent = `${storagePlugin.id}:${String(event)}`;
|
|
276
|
-
emitter.emit(prefixedEvent, payload);
|
|
277
|
-
}
|
|
343
|
+
emit: getPluginEmitFn(storagePlugin.id)
|
|
278
344
|
};
|
|
279
345
|
uploadResult = await storagePlugin.hooks.upload(file, context);
|
|
280
346
|
} else {
|
|
281
347
|
uploadResult = await uploadFn(file, onProgress);
|
|
282
348
|
}
|
|
283
|
-
|
|
349
|
+
const remoteUrl = uploadResult?.url;
|
|
350
|
+
updateFile(file.id, { status: "complete", uploadResult, remoteUrl });
|
|
284
351
|
} catch (err) {
|
|
285
352
|
const error = {
|
|
286
353
|
message: err instanceof Error ? err.message : "Unknown upload error",
|
|
@@ -359,6 +426,11 @@ The LAST storage plugin ("${plugin.id}") will be used for uploads.
|
|
|
359
426
|
upload,
|
|
360
427
|
reset,
|
|
361
428
|
status,
|
|
429
|
+
// File Data Access (for editing/processing)
|
|
430
|
+
getFileData,
|
|
431
|
+
getFileURL,
|
|
432
|
+
getFileStream,
|
|
433
|
+
replaceFileData,
|
|
362
434
|
updateFile,
|
|
363
435
|
initializeExistingFiles,
|
|
364
436
|
// Utilities
|
|
@@ -15,6 +15,10 @@ export const PluginImageCompressor = defineUploaderPlugin((pluginOptions) => {
|
|
|
15
15
|
if (!file.mimeType.startsWith("image/")) {
|
|
16
16
|
return file;
|
|
17
17
|
}
|
|
18
|
+
if (file.source !== "local") {
|
|
19
|
+
context.emit("skip", { file, reason: "Remote file, no local data to compress" });
|
|
20
|
+
return file;
|
|
21
|
+
}
|
|
18
22
|
if (file.mimeType === "image/gif") {
|
|
19
23
|
context.emit("skip", { file, reason: "GIF format not supported" });
|
|
20
24
|
return file;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
type PathHttpHeaders
|
|
1
|
+
import { type PathHttpHeaders } from "@azure/storage-file-datalake";
|
|
2
2
|
export interface AzureDataLakeOptions {
|
|
3
3
|
/**
|
|
4
4
|
* Static SAS URL for Azure Data Lake Storage
|
|
@@ -42,4 +42,3 @@ export interface AzureUploadResult {
|
|
|
42
42
|
blobPath: string;
|
|
43
43
|
}
|
|
44
44
|
export declare const PluginAzureDataLake: (options: AzureDataLakeOptions) => import("../../types.js").Plugin<any, Record<string, never>>;
|
|
45
|
-
export {};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ref } from "vue";
|
|
2
|
+
import { DataLakeDirectoryClient } from "@azure/storage-file-datalake";
|
|
2
3
|
import { defineUploaderPlugin } from "../../types.js";
|
|
3
4
|
export const PluginAzureDataLake = defineUploaderPlugin((options) => {
|
|
4
|
-
let DataLakeDirectoryClient;
|
|
5
5
|
const sasURL = ref(options.sasURL || "");
|
|
6
6
|
let refreshPromise = null;
|
|
7
7
|
const directoryCheckedCache = /* @__PURE__ */ new Set();
|
|
@@ -24,10 +24,6 @@ export const PluginAzureDataLake = defineUploaderPlugin((options) => {
|
|
|
24
24
|
}
|
|
25
25
|
};
|
|
26
26
|
const getFileClient = async (blobName) => {
|
|
27
|
-
if (!DataLakeDirectoryClient) {
|
|
28
|
-
const module = await import("@azure/storage-file-datalake");
|
|
29
|
-
DataLakeDirectoryClient = module.DataLakeDirectoryClient;
|
|
30
|
-
}
|
|
31
27
|
if (options.getSASUrl && isTokenExpired(sasURL.value)) {
|
|
32
28
|
if (!refreshPromise) {
|
|
33
29
|
refreshPromise = options.getSASUrl().then((url) => {
|
|
@@ -62,6 +58,9 @@ export const PluginAzureDataLake = defineUploaderPlugin((options) => {
|
|
|
62
58
|
* Upload file to Azure Blob Storage
|
|
63
59
|
*/
|
|
64
60
|
async upload(file, context) {
|
|
61
|
+
if (file.source !== "local" || file.data === null) {
|
|
62
|
+
throw new Error("Cannot upload remote file - no local data available");
|
|
63
|
+
}
|
|
65
64
|
const fileClient = await getFileClient(file.id);
|
|
66
65
|
await fileClient.upload(file.data, {
|
|
67
66
|
metadata: {
|
|
@@ -5,7 +5,7 @@ export const PluginThumbnailGenerator = defineUploaderPlugin((pluginOptions) =>
|
|
|
5
5
|
hooks: {
|
|
6
6
|
process: async (file, _context) => {
|
|
7
7
|
const { width = 100, height = 100, quality = 0.7 } = pluginOptions;
|
|
8
|
-
const sourceUrl = file.
|
|
8
|
+
const sourceUrl = file.source === "local" ? URL.createObjectURL(file.data) : file.remoteUrl;
|
|
9
9
|
if (file.mimeType.startsWith("image/")) {
|
|
10
10
|
const image = new Image();
|
|
11
11
|
image.crossOrigin = "anonymous";
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
interface VideoCompressorOptions {
|
|
2
|
+
/**
|
|
3
|
+
* Target video codec
|
|
4
|
+
* @default 'libx264'
|
|
5
|
+
*/
|
|
6
|
+
codec?: string;
|
|
7
|
+
/**
|
|
8
|
+
* Constant Rate Factor (0-51, lower = better quality, larger file)
|
|
9
|
+
* @default 28
|
|
10
|
+
*/
|
|
11
|
+
crf?: number;
|
|
12
|
+
/**
|
|
13
|
+
* Target bitrate (e.g., '1M' = 1 megabit/sec)
|
|
14
|
+
* If specified, overrides CRF
|
|
15
|
+
*/
|
|
16
|
+
bitrate?: string;
|
|
17
|
+
/**
|
|
18
|
+
* Maximum width (maintains aspect ratio)
|
|
19
|
+
*/
|
|
20
|
+
maxWidth?: number;
|
|
21
|
+
/**
|
|
22
|
+
* Maximum height (maintains aspect ratio)
|
|
23
|
+
*/
|
|
24
|
+
maxHeight?: number;
|
|
25
|
+
/**
|
|
26
|
+
* Output format
|
|
27
|
+
* @default 'mp4'
|
|
28
|
+
*/
|
|
29
|
+
format?: "mp4" | "webm" | "mov";
|
|
30
|
+
/**
|
|
31
|
+
* Minimum file size to compress (in bytes)
|
|
32
|
+
* Files smaller than this will be skipped
|
|
33
|
+
* @default 10MB
|
|
34
|
+
*/
|
|
35
|
+
minSizeToCompress?: number;
|
|
36
|
+
/**
|
|
37
|
+
* Audio codec
|
|
38
|
+
* @default 'aac'
|
|
39
|
+
*/
|
|
40
|
+
audioCodec?: string;
|
|
41
|
+
/**
|
|
42
|
+
* Audio bitrate
|
|
43
|
+
* @default '128k'
|
|
44
|
+
*/
|
|
45
|
+
audioBitrate?: string;
|
|
46
|
+
}
|
|
47
|
+
type VideoCompressorEvents = {
|
|
48
|
+
start: {
|
|
49
|
+
file: any;
|
|
50
|
+
originalSize: number;
|
|
51
|
+
};
|
|
52
|
+
progress: {
|
|
53
|
+
file: any;
|
|
54
|
+
percentage: number;
|
|
55
|
+
};
|
|
56
|
+
complete: {
|
|
57
|
+
file: any;
|
|
58
|
+
originalSize: number;
|
|
59
|
+
compressedSize: number;
|
|
60
|
+
savedBytes: number;
|
|
61
|
+
};
|
|
62
|
+
skip: {
|
|
63
|
+
file: any;
|
|
64
|
+
reason: string;
|
|
65
|
+
};
|
|
66
|
+
error: {
|
|
67
|
+
file: any;
|
|
68
|
+
error: Error;
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
export declare const PluginVideoCompressor: (options: VideoCompressorOptions) => import("../types.js").Plugin<any, VideoCompressorEvents>;
|
|
72
|
+
export {};
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { defineUploaderPlugin } from "../types.js";
|
|
2
|
+
import { useFFMpeg } from "../../useFFMpeg.js";
|
|
3
|
+
import { watchEffect } from "vue";
|
|
4
|
+
export const PluginVideoCompressor = defineUploaderPlugin((pluginOptions) => {
|
|
5
|
+
return {
|
|
6
|
+
id: "video-compressor",
|
|
7
|
+
hooks: {
|
|
8
|
+
process: async (file, context) => {
|
|
9
|
+
const {
|
|
10
|
+
codec = "libx264",
|
|
11
|
+
crf = 28,
|
|
12
|
+
bitrate,
|
|
13
|
+
maxWidth,
|
|
14
|
+
maxHeight,
|
|
15
|
+
format = "mp4",
|
|
16
|
+
minSizeToCompress = 10 * 1024 * 1024,
|
|
17
|
+
// 10MB
|
|
18
|
+
audioCodec = "aac",
|
|
19
|
+
audioBitrate = "128k"
|
|
20
|
+
} = pluginOptions;
|
|
21
|
+
if (!file.mimeType.startsWith("video/")) {
|
|
22
|
+
return file;
|
|
23
|
+
}
|
|
24
|
+
if (file.source !== "local") {
|
|
25
|
+
context.emit("skip", { file, reason: "Remote file, no local data to compress" });
|
|
26
|
+
return file;
|
|
27
|
+
}
|
|
28
|
+
if (file.size < minSizeToCompress) {
|
|
29
|
+
context.emit("skip", {
|
|
30
|
+
file,
|
|
31
|
+
reason: `File size (${(file.size / 1024 / 1024).toFixed(2)}MB) is below minimum (${(minSizeToCompress / 1024 / 1024).toFixed(2)}MB)`
|
|
32
|
+
});
|
|
33
|
+
return file;
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
context.emit("start", { file, originalSize: file.size });
|
|
37
|
+
const inputUrl = URL.createObjectURL(file.data);
|
|
38
|
+
const ffmpeg = useFFMpeg({
|
|
39
|
+
inputUrl,
|
|
40
|
+
convertOptions: []
|
|
41
|
+
});
|
|
42
|
+
await ffmpeg.load();
|
|
43
|
+
watchEffect(() => {
|
|
44
|
+
context.emit("progress", { file, percentage: Math.round(ffmpeg.progress.value * 100) });
|
|
45
|
+
});
|
|
46
|
+
const convertOptions = ["-c:v", codec];
|
|
47
|
+
if (bitrate) {
|
|
48
|
+
convertOptions.push("-b:v", bitrate);
|
|
49
|
+
} else {
|
|
50
|
+
convertOptions.push("-crf", crf.toString());
|
|
51
|
+
}
|
|
52
|
+
if (maxWidth || maxHeight) {
|
|
53
|
+
let scaleFilter = "";
|
|
54
|
+
if (maxWidth && maxHeight) {
|
|
55
|
+
scaleFilter = `scale='min(${maxWidth},iw)':'min(${maxHeight},ih)':force_original_aspect_ratio=decrease`;
|
|
56
|
+
} else if (maxWidth) {
|
|
57
|
+
scaleFilter = `scale=${maxWidth}:-2`;
|
|
58
|
+
} else if (maxHeight) {
|
|
59
|
+
scaleFilter = `scale=-2:${maxHeight}`;
|
|
60
|
+
}
|
|
61
|
+
convertOptions.push("-vf", scaleFilter);
|
|
62
|
+
}
|
|
63
|
+
convertOptions.push("-c:a", audioCodec, "-b:a", audioBitrate);
|
|
64
|
+
const compressedFile = await ffmpeg.convert(convertOptions);
|
|
65
|
+
ffmpeg.unload();
|
|
66
|
+
if (!compressedFile) {
|
|
67
|
+
context.emit("error", { file, error: new Error("Compression failed: no output file") });
|
|
68
|
+
return file;
|
|
69
|
+
}
|
|
70
|
+
const originalSize = file.size;
|
|
71
|
+
const compressedSize = compressedFile.size;
|
|
72
|
+
const savedBytes = originalSize - compressedSize;
|
|
73
|
+
if (compressedSize < originalSize) {
|
|
74
|
+
context.emit("complete", {
|
|
75
|
+
file,
|
|
76
|
+
originalSize,
|
|
77
|
+
compressedSize,
|
|
78
|
+
savedBytes
|
|
79
|
+
});
|
|
80
|
+
return {
|
|
81
|
+
...file,
|
|
82
|
+
data: compressedFile,
|
|
83
|
+
size: compressedFile.size,
|
|
84
|
+
name: file.name.replace(/\.[^.]+$/, `.${format}`),
|
|
85
|
+
mimeType: `video/${format}`
|
|
86
|
+
};
|
|
87
|
+
} else {
|
|
88
|
+
context.emit("skip", {
|
|
89
|
+
file,
|
|
90
|
+
reason: `Compressed size (${(compressedSize / 1024 / 1024).toFixed(2)}MB) is larger than original`
|
|
91
|
+
});
|
|
92
|
+
return file;
|
|
93
|
+
}
|
|
94
|
+
} catch (error) {
|
|
95
|
+
context.emit("error", { file, error });
|
|
96
|
+
console.error(`Video compression error for ${file.name}:`, error);
|
|
97
|
+
return file;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
});
|
|
@@ -12,21 +12,68 @@ export interface FileError {
|
|
|
12
12
|
message: string;
|
|
13
13
|
details?: unknown;
|
|
14
14
|
}
|
|
15
|
-
|
|
15
|
+
/**
|
|
16
|
+
* File source - indicates where the file originated from
|
|
17
|
+
*
|
|
18
|
+
* - 'local': File selected from user's device
|
|
19
|
+
* - 'storage': File loaded from remote storage (was previously uploaded)
|
|
20
|
+
* - Cloud picker sources: Files picked from cloud providers (future)
|
|
21
|
+
* - 'instagram': Instagram picker
|
|
22
|
+
* - 'dropbox': Dropbox picker
|
|
23
|
+
* - 'google-drive': Google Drive picker
|
|
24
|
+
* - 'onedrive': OneDrive picker
|
|
25
|
+
* - ... add more as needed
|
|
26
|
+
*/
|
|
27
|
+
export type FileSource = 'local' | 'storage' | 'instagram' | 'dropbox' | 'google-drive' | 'onedrive';
|
|
28
|
+
/**
|
|
29
|
+
* Base properties shared by both local and remote upload files
|
|
30
|
+
*/
|
|
31
|
+
export interface BaseUploadFile<TUploadResult = any> {
|
|
16
32
|
id: string;
|
|
17
33
|
name: string;
|
|
18
34
|
size: number;
|
|
19
35
|
mimeType: string;
|
|
20
|
-
data: File | Blob;
|
|
21
36
|
status: FileStatus;
|
|
22
37
|
preview?: string;
|
|
23
38
|
progress: FileProgress;
|
|
24
39
|
error?: FileError;
|
|
25
40
|
uploadResult?: TUploadResult;
|
|
26
|
-
isRemote?: boolean;
|
|
27
|
-
remoteUrl?: string;
|
|
28
41
|
meta: Record<string, unknown>;
|
|
29
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Local upload file - originates from user's device
|
|
45
|
+
* Has local data (File/Blob) and may get a remoteUrl after upload
|
|
46
|
+
*/
|
|
47
|
+
export interface LocalUploadFile<TUploadResult = any> extends BaseUploadFile<TUploadResult> {
|
|
48
|
+
source: 'local';
|
|
49
|
+
data: File | Blob;
|
|
50
|
+
remoteUrl?: string;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Remote upload file - originates from remote source (cloud pickers, etc.)
|
|
54
|
+
* Has remoteUrl but no local data
|
|
55
|
+
*/
|
|
56
|
+
export interface RemoteUploadFile<TUploadResult = any> extends BaseUploadFile<TUploadResult> {
|
|
57
|
+
source: Exclude<FileSource, 'local'>;
|
|
58
|
+
data: null;
|
|
59
|
+
remoteUrl: string;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Upload file discriminated union
|
|
63
|
+
* Use file.source to narrow the type in your code:
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* ```typescript
|
|
67
|
+
* if (file.source === 'local') {
|
|
68
|
+
* // TypeScript knows: file is LocalUploadFile
|
|
69
|
+
* URL.createObjectURL(file.data)
|
|
70
|
+
* } else {
|
|
71
|
+
* // TypeScript knows: file is RemoteUploadFile
|
|
72
|
+
* console.log(file.remoteUrl)
|
|
73
|
+
* }
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
export type UploadFile<TUploadResult = any> = LocalUploadFile<TUploadResult> | RemoteUploadFile<TUploadResult>;
|
|
30
77
|
export type UploadFn<TUploadResult = any> = (file: UploadFile<TUploadResult>, onProgress: (progress: number) => void) => Promise<TUploadResult>;
|
|
31
78
|
export type GetRemoteFileFn = (fileId: string) => Promise<MinimumRemoteFileAttributes>;
|
|
32
79
|
export interface UploadOptions {
|
|
@@ -129,10 +176,27 @@ export type ProcessingHook<TPluginEvents extends Record<string, any> = Record<st
|
|
|
129
176
|
export type SetupHook<TPluginEvents extends Record<string, any> = Record<string, never>> = (context: PluginContext<TPluginEvents>) => Promise<void>;
|
|
130
177
|
/**
|
|
131
178
|
* Storage hooks for handling upload/download/deletion operations
|
|
179
|
+
*
|
|
180
|
+
* Storage plugins MUST return an object containing a `url` property.
|
|
181
|
+
* This URL will be set as the file's `remoteUrl` after successful upload.
|
|
182
|
+
*
|
|
183
|
+
* @example
|
|
184
|
+
* ```typescript
|
|
185
|
+
* upload: async (file, context) => {
|
|
186
|
+
* // Upload logic...
|
|
187
|
+
* return {
|
|
188
|
+
* url: 'https://storage.example.com/file.jpg', // Required
|
|
189
|
+
* key: 'uploads/file.jpg', // Optional
|
|
190
|
+
* etag: 'abc123' // Optional
|
|
191
|
+
* }
|
|
192
|
+
* }
|
|
193
|
+
* ```
|
|
132
194
|
*/
|
|
133
195
|
export type UploadHook<TUploadResult = any, TPluginEvents extends Record<string, any> = Record<string, never>> = (file: UploadFile<TUploadResult>, context: PluginContext<TPluginEvents> & {
|
|
134
196
|
onProgress: (progress: number) => void;
|
|
135
|
-
}) => Promise<TUploadResult
|
|
197
|
+
}) => Promise<TUploadResult & {
|
|
198
|
+
url: string;
|
|
199
|
+
}>;
|
|
136
200
|
export type GetRemoteFileHook<TPluginEvents extends Record<string, any> = Record<string, never>> = (fileId: string, context: PluginContext<TPluginEvents>) => Promise<MinimumRemoteFileAttributes>;
|
|
137
201
|
export type RemoveHook<TPluginEvents extends Record<string, any> = Record<string, never>> = (file: UploadFile, context: PluginContext<TPluginEvents>) => Promise<void>;
|
|
138
202
|
export type PluginLifecycleStage = "validate" | "process" | "upload" | "complete";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nuxt-ui-elements",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.30",
|
|
4
4
|
"description": "A collection of beautiful, animated UI components for Nuxt applications",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": "https://github.com/genu/nuxt-ui-elements.git",
|
|
@@ -35,6 +35,8 @@
|
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@azure/storage-file-datalake": "^12.28.1",
|
|
38
|
+
"@ffmpeg/ffmpeg": "0.12.15",
|
|
39
|
+
"@ffmpeg/util": "0.12.2",
|
|
38
40
|
"@nuxt/devtools": "^3.1.1",
|
|
39
41
|
"@nuxt/eslint-config": "^1.12.1",
|
|
40
42
|
"@nuxt/module-builder": "^1.0.2",
|
|
@@ -42,7 +44,7 @@
|
|
|
42
44
|
"@nuxt/test-utils": "^3.23.0",
|
|
43
45
|
"@types/culori": "^4.0.1",
|
|
44
46
|
"@types/node": "latest",
|
|
45
|
-
"@vitest/coverage-v8": "^4.0.
|
|
47
|
+
"@vitest/coverage-v8": "^4.0.17",
|
|
46
48
|
"changelogen": "^0.6.2",
|
|
47
49
|
"eslint": "^9.39.2",
|
|
48
50
|
"eslint-config-prettier": "10.1.8",
|
|
@@ -50,7 +52,7 @@
|
|
|
50
52
|
"nuxt": "^4.2.2",
|
|
51
53
|
"prettier": "^3.7.4",
|
|
52
54
|
"typescript": "~5.9.3",
|
|
53
|
-
"vitest": "^4.0.
|
|
55
|
+
"vitest": "^4.0.17",
|
|
54
56
|
"vue-tsc": "^3.2.2"
|
|
55
57
|
},
|
|
56
58
|
"peerDependencies": {
|
|
@@ -59,6 +61,12 @@
|
|
|
59
61
|
"peerDependenciesMeta": {
|
|
60
62
|
"@azure/storage-file-datalake": {
|
|
61
63
|
"optional": true
|
|
64
|
+
},
|
|
65
|
+
"@ffmpeg/ffmpeg": {
|
|
66
|
+
"optional": true
|
|
67
|
+
},
|
|
68
|
+
"@ffmpeg/util": {
|
|
69
|
+
"optional": true
|
|
62
70
|
}
|
|
63
71
|
},
|
|
64
72
|
"scripts": {
|