nuxt-ui-elements 0.1.25 → 0.1.26
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/module.mjs +2 -2
- package/dist/runtime/composables/{useUploader → useUploadManager}/index.d.ts +6 -6
- package/dist/runtime/composables/{useUploader → useUploadManager}/index.js +122 -27
- package/dist/runtime/composables/{useUploader → useUploadManager}/plugins/image-compressor.d.ts +23 -16
- package/dist/runtime/composables/{useUploader → useUploadManager}/plugins/image-compressor.js +27 -5
- package/dist/runtime/composables/{useUploader → useUploadManager}/plugins/index.d.ts +1 -0
- package/dist/runtime/composables/{useUploader → useUploadManager}/plugins/index.js +1 -0
- package/dist/runtime/composables/useUploadManager/plugins/storage/azure-datalake.d.ts +45 -0
- package/dist/runtime/composables/useUploadManager/plugins/storage/azure-datalake.js +108 -0
- package/dist/runtime/composables/useUploadManager/plugins/storage/index.d.ts +10 -0
- package/dist/runtime/composables/useUploadManager/plugins/storage/index.js +1 -0
- package/dist/runtime/composables/useUploadManager/plugins/thumbnail-generator.d.ts +7 -0
- package/dist/runtime/composables/{useUploader → useUploadManager}/plugins/thumbnail-generator.js +4 -3
- package/dist/runtime/composables/useUploadManager/types.d.ts +220 -0
- package/dist/runtime/composables/useUploadManager/types.js +3 -0
- package/dist/runtime/composables/useUploadManager/validators/allowed-file-types.d.ts +5 -0
- package/dist/runtime/composables/{useUploader → useUploadManager}/validators/allowed-file-types.js +4 -3
- package/dist/runtime/composables/useUploadManager/validators/duplicate-file.d.ts +13 -0
- package/dist/runtime/composables/{useUploader → useUploadManager}/validators/duplicate-file.js +5 -4
- package/dist/runtime/composables/useUploadManager/validators/max-file-size.d.ts +5 -0
- package/dist/runtime/composables/{useUploader → useUploadManager}/validators/max-file-size.js +4 -3
- package/dist/runtime/composables/useUploadManager/validators/max-files.d.ts +5 -0
- package/dist/runtime/composables/useUploadManager/validators/max-files.js +13 -0
- package/dist/runtime/types/index.d.ts +3 -1
- package/dist/runtime/types/index.js +3 -1
- package/package.json +7 -1
- package/dist/runtime/composables/useUploader/plugins/thumbnail-generator.d.ts +0 -8
- package/dist/runtime/composables/useUploader/types.d.ts +0 -155
- package/dist/runtime/composables/useUploader/types.js +0 -0
- package/dist/runtime/composables/useUploader/validators/allowed-file-types.d.ts +0 -6
- package/dist/runtime/composables/useUploader/validators/duplicate-file.d.ts +0 -27
- package/dist/runtime/composables/useUploader/validators/max-file-size.d.ts +0 -6
- package/dist/runtime/composables/useUploader/validators/max-files.d.ts +0 -6
- package/dist/runtime/composables/useUploader/validators/max-files.js +0 -12
- /package/dist/runtime/composables/{useUploader → useUploadManager}/validators/index.d.ts +0 -0
- /package/dist/runtime/composables/{useUploader → useUploadManager}/validators/index.js +0 -0
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -253,8 +253,8 @@ const module$1 = defineNuxtModule({
|
|
|
253
253
|
addImportsDir(resolver.resolve("./runtime/composables"));
|
|
254
254
|
addImports([
|
|
255
255
|
{
|
|
256
|
-
name: "
|
|
257
|
-
from: resolver.resolve("./runtime/composables/
|
|
256
|
+
name: "useUploadManager",
|
|
257
|
+
from: resolver.resolve("./runtime/composables/useUploadManager")
|
|
258
258
|
}
|
|
259
259
|
]);
|
|
260
260
|
nuxt.options.alias["#std"] = resolver.resolve("./runtime/utils/std");
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
export declare const
|
|
1
|
+
import type { UploaderEvents, UploadFile, UploadOptions, UploadStatus, UploadFn, GetRemoteFileFn, Plugin as UploaderPlugin } from "./types.js";
|
|
2
|
+
export declare const useUploadManager: <TUploadResult = any>(_options?: UploadOptions) => {
|
|
3
3
|
files: Readonly<import("vue").Ref<readonly {
|
|
4
4
|
readonly id: string;
|
|
5
5
|
readonly name: string;
|
|
@@ -146,7 +146,7 @@ export declare const useUploader: <TUploadResult = any>(_options?: UploadOptions
|
|
|
146
146
|
addFile: (file: File) => Promise<UploadFile<any> | null>;
|
|
147
147
|
onGetRemoteFile: (fn: GetRemoteFileFn) => void;
|
|
148
148
|
onUpload: (fn: UploadFn<TUploadResult>) => void;
|
|
149
|
-
removeFile: (fileId: string) => void
|
|
149
|
+
removeFile: (fileId: string) => Promise<void>;
|
|
150
150
|
removeFiles: (fileIds: string[]) => {
|
|
151
151
|
id: string;
|
|
152
152
|
name: string;
|
|
@@ -360,9 +360,9 @@ export declare const useUploader: <TUploadResult = any>(_options?: UploadOptions
|
|
|
360
360
|
status: import("vue").Ref<UploadStatus, UploadStatus>;
|
|
361
361
|
updateFile: (fileId: string, updatedFile: Partial<UploadFile<TUploadResult>>) => void;
|
|
362
362
|
initializeExistingFiles: (initialFiles: Array<Partial<UploadFile>>) => Promise<void>;
|
|
363
|
-
addPlugin:
|
|
363
|
+
addPlugin: (plugin: UploaderPlugin<any, any>) => void;
|
|
364
364
|
on: {
|
|
365
|
-
<
|
|
366
|
-
(type:
|
|
365
|
+
<K extends keyof UploaderEvents<TUploadResult>>(type: K, handler: (event: UploaderEvents<TUploadResult>[K]) => void): void;
|
|
366
|
+
(type: string, handler: (event: any) => void): void;
|
|
367
367
|
};
|
|
368
368
|
};
|
|
@@ -18,11 +18,23 @@ const defaultOptions = {
|
|
|
18
18
|
imageCompression: false,
|
|
19
19
|
autoProceed: false
|
|
20
20
|
};
|
|
21
|
-
export const
|
|
21
|
+
export const useUploadManager = (_options = {}) => {
|
|
22
22
|
const options = { ...defaultOptions, ..._options };
|
|
23
23
|
const files = ref([]);
|
|
24
24
|
const emitter = mitt();
|
|
25
25
|
const status = ref("waiting");
|
|
26
|
+
const pluginEmitFunctions = /* @__PURE__ */ new Map();
|
|
27
|
+
const getPluginEmitFn = (pluginId) => {
|
|
28
|
+
let emitFn = pluginEmitFunctions.get(pluginId);
|
|
29
|
+
if (!emitFn) {
|
|
30
|
+
emitFn = (event, payload) => {
|
|
31
|
+
const prefixedEvent = `${pluginId}:${String(event)}`;
|
|
32
|
+
emitter.emit(prefixedEvent, payload);
|
|
33
|
+
};
|
|
34
|
+
pluginEmitFunctions.set(pluginId, emitFn);
|
|
35
|
+
}
|
|
36
|
+
return emitFn;
|
|
37
|
+
};
|
|
26
38
|
let uploadFn = async () => {
|
|
27
39
|
throw new Error("No uploader configured");
|
|
28
40
|
};
|
|
@@ -34,37 +46,71 @@ export const useUploader = (_options = {}) => {
|
|
|
34
46
|
const sum = files.value.reduce((acc, file) => acc + file.progress.percentage, 0);
|
|
35
47
|
return Math.round(sum / files.value.length);
|
|
36
48
|
});
|
|
37
|
-
const
|
|
38
|
-
|
|
49
|
+
const isStoragePlugin = (plugin) => {
|
|
50
|
+
return !!(plugin.hooks.upload || plugin.hooks.getRemoteFile || plugin.hooks.remove);
|
|
51
|
+
};
|
|
52
|
+
const getStoragePlugin = () => {
|
|
53
|
+
if (!options.plugins) return null;
|
|
54
|
+
for (let i = options.plugins.length - 1; i >= 0; i--) {
|
|
55
|
+
const plugin = options.plugins[i];
|
|
56
|
+
if (plugin && isStoragePlugin(plugin)) {
|
|
57
|
+
return plugin;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return null;
|
|
61
|
+
};
|
|
62
|
+
const addPlugin = (plugin) => {
|
|
63
|
+
if (isStoragePlugin(plugin)) {
|
|
64
|
+
const existingStorage = options.plugins?.find((p) => isStoragePlugin(p));
|
|
65
|
+
if (existingStorage && import.meta.dev) {
|
|
66
|
+
console.warn(
|
|
67
|
+
`[useUploadManager] Multiple storage plugins detected!
|
|
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.
|
|
71
|
+
|
|
72
|
+
\u{1F4A1} If you need multiple storage destinations, create separate uploader instances:
|
|
73
|
+
|
|
74
|
+
const s3Uploader = useUploadManager({ plugins: [PluginS3Storage({ ... })] })
|
|
75
|
+
const azureUploader = useUploadManager({ plugins: [PluginAzureStorage({ ... })] })
|
|
76
|
+
`
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
;
|
|
39
81
|
options.plugins?.push(plugin);
|
|
40
82
|
};
|
|
41
83
|
if (options.maxFiles !== false && options.maxFiles !== void 0) {
|
|
42
|
-
addPlugin(ValidatorMaxFiles
|
|
84
|
+
addPlugin(ValidatorMaxFiles({ maxFiles: options.maxFiles }));
|
|
43
85
|
}
|
|
44
86
|
if (options.maxFileSize !== false && options.maxFileSize !== void 0) {
|
|
45
|
-
addPlugin(ValidatorMaxfileSize
|
|
87
|
+
addPlugin(ValidatorMaxfileSize({ maxFileSize: options.maxFileSize }));
|
|
46
88
|
}
|
|
47
89
|
if (options.allowedFileTypes !== false && options.allowedFileTypes !== void 0 && options.allowedFileTypes.length > 0) {
|
|
48
|
-
addPlugin(ValidatorAllowedFileTypes
|
|
90
|
+
addPlugin(ValidatorAllowedFileTypes({ allowedFileTypes: options.allowedFileTypes }));
|
|
49
91
|
}
|
|
50
92
|
if (options.thumbnails !== false && options.thumbnails !== void 0) {
|
|
51
93
|
const thumbOpts = options.thumbnails === true ? {} : options.thumbnails || {};
|
|
52
|
-
addPlugin(
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
94
|
+
addPlugin(
|
|
95
|
+
PluginThumbnailGenerator({
|
|
96
|
+
width: thumbOpts.width ?? 128,
|
|
97
|
+
height: thumbOpts.height ?? 128,
|
|
98
|
+
quality: thumbOpts.quality ?? 1
|
|
99
|
+
})
|
|
100
|
+
);
|
|
57
101
|
}
|
|
58
102
|
if (options.imageCompression !== false && options.imageCompression !== void 0) {
|
|
59
103
|
const compressionOpts = options.imageCompression === true ? {} : options.imageCompression || {};
|
|
60
|
-
addPlugin(
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
104
|
+
addPlugin(
|
|
105
|
+
PluginImageCompressor({
|
|
106
|
+
maxWidth: compressionOpts.maxWidth ?? 1920,
|
|
107
|
+
maxHeight: compressionOpts.maxHeight ?? 1920,
|
|
108
|
+
quality: compressionOpts.quality ?? 0.85,
|
|
109
|
+
outputFormat: compressionOpts.outputFormat ?? "auto",
|
|
110
|
+
minSizeToCompress: compressionOpts.minSizeToCompress ?? 1e5,
|
|
111
|
+
preserveMetadata: compressionOpts.preserveMetadata ?? true
|
|
112
|
+
})
|
|
113
|
+
);
|
|
68
114
|
}
|
|
69
115
|
emitter.on("upload:progress", ({ file, progress }) => {
|
|
70
116
|
updateFile(file.id, { progress: { percentage: progress } });
|
|
@@ -82,7 +128,21 @@ export const useUploader = (_options = {}) => {
|
|
|
82
128
|
const initializedfiles = await Promise.all(
|
|
83
129
|
initialFiles.map(async (file) => {
|
|
84
130
|
if (!file.id) return null;
|
|
85
|
-
const
|
|
131
|
+
const storagePlugin = getStoragePlugin();
|
|
132
|
+
let remoteFileData;
|
|
133
|
+
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
|
+
};
|
|
142
|
+
remoteFileData = await storagePlugin.hooks.getRemoteFile(file.id, context);
|
|
143
|
+
} else {
|
|
144
|
+
remoteFileData = await getRemoteFileFn(file.id);
|
|
145
|
+
}
|
|
86
146
|
const existingFile = {
|
|
87
147
|
...file,
|
|
88
148
|
id: file.id,
|
|
@@ -91,9 +151,9 @@ export const useUploader = (_options = {}) => {
|
|
|
91
151
|
status: "complete",
|
|
92
152
|
progress: { percentage: 100 },
|
|
93
153
|
meta: {},
|
|
94
|
-
size,
|
|
95
|
-
mimeType,
|
|
96
|
-
remoteUrl
|
|
154
|
+
size: remoteFileData.size,
|
|
155
|
+
mimeType: remoteFileData.mimeType,
|
|
156
|
+
remoteUrl: remoteFileData.remoteUrl
|
|
97
157
|
};
|
|
98
158
|
const processedFile = await runPluginStage("process", existingFile);
|
|
99
159
|
if (!processedFile) return null;
|
|
@@ -134,9 +194,25 @@ export const useUploader = (_options = {}) => {
|
|
|
134
194
|
const addFiles = (newFiles) => {
|
|
135
195
|
return Promise.all(newFiles.map((file) => addFile(file)));
|
|
136
196
|
};
|
|
137
|
-
const removeFile = (fileId) => {
|
|
197
|
+
const removeFile = async (fileId) => {
|
|
138
198
|
const file = files.value.find((f) => f.id === fileId);
|
|
139
199
|
if (!file) return;
|
|
200
|
+
const storagePlugin = getStoragePlugin();
|
|
201
|
+
if (storagePlugin?.hooks.remove) {
|
|
202
|
+
try {
|
|
203
|
+
const context = {
|
|
204
|
+
files: files.value,
|
|
205
|
+
options,
|
|
206
|
+
emit: (event, payload) => {
|
|
207
|
+
const prefixedEvent = `${storagePlugin.id}:${String(event)}`;
|
|
208
|
+
emitter.emit(prefixedEvent, payload);
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
await storagePlugin.hooks.remove(file, context);
|
|
212
|
+
} catch (error) {
|
|
213
|
+
console.error(`Storage plugin remove error:`, error);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
140
216
|
files.value = files.value.filter((f) => f.id !== fileId);
|
|
141
217
|
emitter.emit("file:removed", file);
|
|
142
218
|
};
|
|
@@ -188,7 +264,22 @@ export const useUploader = (_options = {}) => {
|
|
|
188
264
|
updateFile(file.id, { progress: { percentage: progress } });
|
|
189
265
|
emitter.emit("upload:progress", { file, progress });
|
|
190
266
|
};
|
|
191
|
-
const
|
|
267
|
+
const storagePlugin = getStoragePlugin();
|
|
268
|
+
let uploadResult;
|
|
269
|
+
if (storagePlugin?.hooks.upload) {
|
|
270
|
+
const context = {
|
|
271
|
+
files: files.value,
|
|
272
|
+
options,
|
|
273
|
+
onProgress,
|
|
274
|
+
emit: (event, payload) => {
|
|
275
|
+
const prefixedEvent = `${storagePlugin.id}:${String(event)}`;
|
|
276
|
+
emitter.emit(prefixedEvent, payload);
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
uploadResult = await storagePlugin.hooks.upload(file, context);
|
|
280
|
+
} else {
|
|
281
|
+
uploadResult = await uploadFn(file, onProgress);
|
|
282
|
+
}
|
|
192
283
|
updateFile(file.id, { status: "complete", uploadResult });
|
|
193
284
|
} catch (err) {
|
|
194
285
|
const error = {
|
|
@@ -225,12 +316,16 @@ export const useUploader = (_options = {}) => {
|
|
|
225
316
|
};
|
|
226
317
|
async function runPluginStage(stage, file) {
|
|
227
318
|
if (!options.plugins) return file;
|
|
228
|
-
const context = { files: files.value, options };
|
|
229
319
|
let currentFile = file;
|
|
230
320
|
for (const plugin of options.plugins) {
|
|
231
321
|
const hook = plugin.hooks[stage];
|
|
232
322
|
if (hook) {
|
|
233
323
|
try {
|
|
324
|
+
const context = {
|
|
325
|
+
files: files.value,
|
|
326
|
+
options,
|
|
327
|
+
emit: getPluginEmitFn(plugin.id)
|
|
328
|
+
};
|
|
234
329
|
const result = await callPluginHook(hook, stage, currentFile, context);
|
|
235
330
|
if (!result) continue;
|
|
236
331
|
if (currentFile && "id" in result) {
|
|
@@ -268,7 +363,7 @@ export const useUploader = (_options = {}) => {
|
|
|
268
363
|
initializeExistingFiles,
|
|
269
364
|
// Utilities
|
|
270
365
|
addPlugin,
|
|
271
|
-
// Events
|
|
366
|
+
// Events - autocomplete for core events, allow arbitrary strings for plugin events
|
|
272
367
|
on: emitter.on
|
|
273
368
|
};
|
|
274
369
|
};
|
package/dist/runtime/composables/{useUploader → useUploadManager}/plugins/image-compressor.d.ts
RENAMED
|
@@ -1,4 +1,25 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { UploadFile } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Events emitted by the image compressor plugin
|
|
4
|
+
* Note: These are automatically prefixed with "image-compressor:" by the plugin system
|
|
5
|
+
* e.g., "start" becomes "image-compressor:start"
|
|
6
|
+
*/
|
|
7
|
+
type ImageCompressorEvents = {
|
|
8
|
+
start: {
|
|
9
|
+
file: UploadFile;
|
|
10
|
+
originalSize: number;
|
|
11
|
+
};
|
|
12
|
+
complete: {
|
|
13
|
+
file: UploadFile;
|
|
14
|
+
originalSize: number;
|
|
15
|
+
compressedSize: number;
|
|
16
|
+
savedBytes: number;
|
|
17
|
+
};
|
|
18
|
+
skip: {
|
|
19
|
+
file: UploadFile;
|
|
20
|
+
reason: string;
|
|
21
|
+
};
|
|
22
|
+
};
|
|
2
23
|
interface ImageCompressorOptions {
|
|
3
24
|
/**
|
|
4
25
|
* Maximum width in pixels. Images wider than this will be scaled down.
|
|
@@ -31,19 +52,5 @@ interface ImageCompressorOptions {
|
|
|
31
52
|
*/
|
|
32
53
|
preserveMetadata?: boolean;
|
|
33
54
|
}
|
|
34
|
-
|
|
35
|
-
* Compresses images before upload to reduce bandwidth and storage costs.
|
|
36
|
-
* Particularly useful for social media scheduling where images are often large.
|
|
37
|
-
*
|
|
38
|
-
* @example
|
|
39
|
-
* ```ts
|
|
40
|
-
* const uploader = useUpload()
|
|
41
|
-
* uploader.addPlugin(PluginImageCompressor, {
|
|
42
|
-
* maxWidth: 2048,
|
|
43
|
-
* quality: 0.85,
|
|
44
|
-
* outputFormat: 'webp'
|
|
45
|
-
* })
|
|
46
|
-
* ```
|
|
47
|
-
*/
|
|
48
|
-
export declare const PluginImageCompressor: PluginFn<ImageCompressorOptions>;
|
|
55
|
+
export declare const PluginImageCompressor: (options: ImageCompressorOptions) => import("../types.js").Plugin<any, ImageCompressorEvents>;
|
|
49
56
|
export {};
|
package/dist/runtime/composables/{useUploader → useUploadManager}/plugins/image-compressor.js
RENAMED
|
@@ -1,29 +1,36 @@
|
|
|
1
|
-
|
|
1
|
+
import { defineUploaderPlugin } from "../types.js";
|
|
2
|
+
export const PluginImageCompressor = defineUploaderPlugin((pluginOptions) => {
|
|
2
3
|
const {
|
|
3
4
|
maxWidth = 1920,
|
|
4
5
|
maxHeight = 1920,
|
|
5
6
|
quality = 0.85,
|
|
6
7
|
outputFormat = "auto",
|
|
7
|
-
minSizeToCompress = 1e5
|
|
8
|
+
minSizeToCompress = 1e5
|
|
8
9
|
// 100KB
|
|
9
|
-
preserveMetadata = true
|
|
10
10
|
} = pluginOptions;
|
|
11
11
|
return {
|
|
12
12
|
id: "image-compressor",
|
|
13
13
|
hooks: {
|
|
14
|
-
process: async (file) => {
|
|
14
|
+
process: async (file, context) => {
|
|
15
15
|
if (!file.mimeType.startsWith("image/")) {
|
|
16
16
|
return file;
|
|
17
17
|
}
|
|
18
18
|
if (file.mimeType === "image/gif") {
|
|
19
|
+
context.emit("skip", { file, reason: "GIF format not supported" });
|
|
19
20
|
return file;
|
|
20
21
|
}
|
|
21
22
|
if (file.mimeType === "image/svg+xml") {
|
|
23
|
+
context.emit("skip", { file, reason: "SVG already optimized" });
|
|
22
24
|
return file;
|
|
23
25
|
}
|
|
24
26
|
if (file.size < minSizeToCompress) {
|
|
27
|
+
context.emit("skip", {
|
|
28
|
+
file,
|
|
29
|
+
reason: `File size (${file.size} bytes) below minimum threshold`
|
|
30
|
+
});
|
|
25
31
|
return file;
|
|
26
32
|
}
|
|
33
|
+
context.emit("start", { file, originalSize: file.size });
|
|
27
34
|
try {
|
|
28
35
|
const sourceUrl = URL.createObjectURL(file.data);
|
|
29
36
|
const image = new Image();
|
|
@@ -35,6 +42,10 @@ export const PluginImageCompressor = (_, pluginOptions) => {
|
|
|
35
42
|
const needsResize = image.width > maxWidth || image.height > maxHeight;
|
|
36
43
|
if (!needsResize && outputFormat === "auto") {
|
|
37
44
|
URL.revokeObjectURL(sourceUrl);
|
|
45
|
+
context.emit("skip", {
|
|
46
|
+
file,
|
|
47
|
+
reason: "Image within size limits and format is auto"
|
|
48
|
+
});
|
|
38
49
|
return file;
|
|
39
50
|
}
|
|
40
51
|
let targetWidth = image.width;
|
|
@@ -79,6 +90,13 @@ export const PluginImageCompressor = (_, pluginOptions) => {
|
|
|
79
90
|
});
|
|
80
91
|
URL.revokeObjectURL(sourceUrl);
|
|
81
92
|
if (compressedBlob.size < file.size) {
|
|
93
|
+
const savedBytes = file.size - compressedBlob.size;
|
|
94
|
+
context.emit("complete", {
|
|
95
|
+
file,
|
|
96
|
+
originalSize: file.size,
|
|
97
|
+
compressedSize: compressedBlob.size,
|
|
98
|
+
savedBytes
|
|
99
|
+
});
|
|
82
100
|
let newId = file.id;
|
|
83
101
|
if (outputFormat !== "auto" && outputFormat !== file.meta.extension) {
|
|
84
102
|
const extension = outputFormat === "jpeg" ? "jpg" : outputFormat;
|
|
@@ -100,6 +118,10 @@ export const PluginImageCompressor = (_, pluginOptions) => {
|
|
|
100
118
|
}
|
|
101
119
|
};
|
|
102
120
|
}
|
|
121
|
+
context.emit("skip", {
|
|
122
|
+
file,
|
|
123
|
+
reason: "Compressed version larger than original"
|
|
124
|
+
});
|
|
103
125
|
return file;
|
|
104
126
|
} catch (error) {
|
|
105
127
|
console.warn(`Image compression failed for ${file.name}:`, error);
|
|
@@ -108,4 +130,4 @@ export const PluginImageCompressor = (_, pluginOptions) => {
|
|
|
108
130
|
}
|
|
109
131
|
}
|
|
110
132
|
};
|
|
111
|
-
};
|
|
133
|
+
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
type PathHttpHeaders = import("@azure/storage-file-datalake").PathHttpHeaders;
|
|
2
|
+
export interface AzureDataLakeOptions {
|
|
3
|
+
/**
|
|
4
|
+
* Static SAS URL for Azure Data Lake Storage
|
|
5
|
+
*/
|
|
6
|
+
sasURL?: string;
|
|
7
|
+
/**
|
|
8
|
+
* Function to dynamically fetch SAS URL
|
|
9
|
+
* Use this to handle token expiration/refreshing.
|
|
10
|
+
* If provided, it will be called before every file operation.
|
|
11
|
+
*/
|
|
12
|
+
getSASUrl?: () => Promise<string>;
|
|
13
|
+
/**
|
|
14
|
+
* Optional subdirectory path within the container
|
|
15
|
+
* @example "uploads/images"
|
|
16
|
+
*/
|
|
17
|
+
path?: string;
|
|
18
|
+
/**
|
|
19
|
+
* Custom metadata to attach to uploaded files
|
|
20
|
+
*/
|
|
21
|
+
metadata?: Record<string, string>;
|
|
22
|
+
/**
|
|
23
|
+
* Custom HTTP headers for uploaded files
|
|
24
|
+
*/
|
|
25
|
+
pathHttpHeaders?: Omit<PathHttpHeaders, "contentType">;
|
|
26
|
+
/**
|
|
27
|
+
* Automatically try to create the directory if it doesn't exist.
|
|
28
|
+
* Disable this if your SAS token only has 'Write' (Blob) permissions
|
|
29
|
+
* and not 'Create' (Directory) permissions.
|
|
30
|
+
* @default true
|
|
31
|
+
*/
|
|
32
|
+
autoCreateDirectory?: boolean;
|
|
33
|
+
}
|
|
34
|
+
export interface AzureUploadResult {
|
|
35
|
+
/**
|
|
36
|
+
* Full URL to the uploaded file
|
|
37
|
+
*/
|
|
38
|
+
url: string;
|
|
39
|
+
/**
|
|
40
|
+
* File name/path in the storage
|
|
41
|
+
*/
|
|
42
|
+
blobPath: string;
|
|
43
|
+
}
|
|
44
|
+
export declare const PluginAzureDataLake: (options: AzureDataLakeOptions) => import("../../types.js").Plugin<any, Record<string, never>>;
|
|
45
|
+
export {};
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { ref } from "vue";
|
|
2
|
+
import { defineUploaderPlugin } from "../../types.js";
|
|
3
|
+
export const PluginAzureDataLake = defineUploaderPlugin((options) => {
|
|
4
|
+
let DataLakeDirectoryClient;
|
|
5
|
+
const sasURL = ref(options.sasURL || "");
|
|
6
|
+
let refreshPromise = null;
|
|
7
|
+
const directoryCheckedCache = /* @__PURE__ */ new Set();
|
|
8
|
+
if (options.getSASUrl && !options.sasURL) {
|
|
9
|
+
options.getSASUrl().then((url) => {
|
|
10
|
+
sasURL.value = url;
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
const isTokenExpired = (urlStr, bufferMinutes = 5) => {
|
|
14
|
+
if (!urlStr) return true;
|
|
15
|
+
try {
|
|
16
|
+
const url = new URL(urlStr);
|
|
17
|
+
const expiryStr = url.searchParams.get("se");
|
|
18
|
+
if (!expiryStr) return true;
|
|
19
|
+
const expiry = new Date(expiryStr);
|
|
20
|
+
const now = /* @__PURE__ */ new Date();
|
|
21
|
+
return now.getTime() + bufferMinutes * 60 * 1e3 > expiry.getTime();
|
|
22
|
+
} catch {
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
const getFileClient = async (blobName) => {
|
|
27
|
+
if (!DataLakeDirectoryClient) {
|
|
28
|
+
const module = await import("@azure/storage-file-datalake");
|
|
29
|
+
DataLakeDirectoryClient = module.DataLakeDirectoryClient;
|
|
30
|
+
}
|
|
31
|
+
if (options.getSASUrl && isTokenExpired(sasURL.value)) {
|
|
32
|
+
if (!refreshPromise) {
|
|
33
|
+
refreshPromise = options.getSASUrl().then((url) => {
|
|
34
|
+
refreshPromise = null;
|
|
35
|
+
return url;
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
sasURL.value = await refreshPromise;
|
|
39
|
+
}
|
|
40
|
+
let dir = new DataLakeDirectoryClient(sasURL.value);
|
|
41
|
+
if (options.path) {
|
|
42
|
+
const cleanPath = options.path.replace(/^\/+|\/+$/g, "");
|
|
43
|
+
dir = dir.getSubdirectoryClient(cleanPath);
|
|
44
|
+
const shouldCreateDir = options.autoCreateDirectory ?? true;
|
|
45
|
+
if (shouldCreateDir && !directoryCheckedCache.has(cleanPath)) {
|
|
46
|
+
try {
|
|
47
|
+
await dir.createIfNotExists();
|
|
48
|
+
directoryCheckedCache.add(cleanPath);
|
|
49
|
+
} catch (error) {
|
|
50
|
+
if (import.meta.dev) {
|
|
51
|
+
console.debug(`Azure directory already exists or couldn't be created: ${cleanPath}`, error);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return dir.getFileClient(blobName);
|
|
57
|
+
};
|
|
58
|
+
return {
|
|
59
|
+
id: "azure-datalake-storage",
|
|
60
|
+
hooks: {
|
|
61
|
+
/**
|
|
62
|
+
* Upload file to Azure Blob Storage
|
|
63
|
+
*/
|
|
64
|
+
async upload(file, context) {
|
|
65
|
+
const fileClient = await getFileClient(file.id);
|
|
66
|
+
await fileClient.upload(file.data, {
|
|
67
|
+
metadata: {
|
|
68
|
+
...options.metadata,
|
|
69
|
+
mimeType: file.mimeType,
|
|
70
|
+
size: String(file.size),
|
|
71
|
+
originalName: file.name
|
|
72
|
+
},
|
|
73
|
+
pathHttpHeaders: {
|
|
74
|
+
...options.pathHttpHeaders,
|
|
75
|
+
contentType: file.mimeType
|
|
76
|
+
},
|
|
77
|
+
onProgress: ({ loadedBytes }) => {
|
|
78
|
+
const uploadedPercentage = Math.round(loadedBytes / file.size * 100);
|
|
79
|
+
context.onProgress(uploadedPercentage);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
return {
|
|
83
|
+
url: fileClient.url,
|
|
84
|
+
blobPath: fileClient.name
|
|
85
|
+
};
|
|
86
|
+
},
|
|
87
|
+
/**
|
|
88
|
+
* Get remote file metadata from Azure
|
|
89
|
+
*/
|
|
90
|
+
async getRemoteFile(fileId, _context) {
|
|
91
|
+
const fileClient = await getFileClient(fileId);
|
|
92
|
+
const properties = await fileClient.getProperties();
|
|
93
|
+
return {
|
|
94
|
+
size: properties.contentLength || 0,
|
|
95
|
+
mimeType: properties.contentType || "application/octet-stream",
|
|
96
|
+
remoteUrl: fileClient.url
|
|
97
|
+
};
|
|
98
|
+
},
|
|
99
|
+
/**
|
|
100
|
+
* Delete file from Azure Blob Storage
|
|
101
|
+
*/
|
|
102
|
+
async remove(file, _context) {
|
|
103
|
+
const fileClient = await getFileClient(file.id);
|
|
104
|
+
await fileClient.deleteIfExists();
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Storage Adapter Plugins
|
|
3
|
+
*
|
|
4
|
+
* These plugins handle the actual upload, download, and deletion of files
|
|
5
|
+
* to/from various cloud storage providers.
|
|
6
|
+
*
|
|
7
|
+
* Only ONE storage plugin should be active per uploader instance.
|
|
8
|
+
* If you need multiple storage destinations, create multiple uploader instances.
|
|
9
|
+
*/
|
|
10
|
+
export { PluginAzureDataLake, type AzureDataLakeOptions, type AzureUploadResult } from "./azure-datalake.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { PluginAzureDataLake } from "./azure-datalake.js";
|
package/dist/runtime/composables/{useUploader → useUploadManager}/plugins/thumbnail-generator.js
RENAMED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
import { defineUploaderPlugin } from "../types.js";
|
|
2
|
+
export const PluginThumbnailGenerator = defineUploaderPlugin((pluginOptions) => {
|
|
2
3
|
return {
|
|
3
4
|
id: "thumbnail-generator",
|
|
4
5
|
hooks: {
|
|
5
|
-
process: async (file) => {
|
|
6
|
+
process: async (file, _context) => {
|
|
6
7
|
const { width = 100, height = 100, quality = 0.7 } = pluginOptions;
|
|
7
8
|
const sourceUrl = file.isRemote ? file.remoteUrl : URL.createObjectURL(file.data);
|
|
8
9
|
if (file.mimeType.startsWith("image/")) {
|
|
@@ -62,4 +63,4 @@ export const PluginThumbnailGenerator = (_options, pluginOptions) => {
|
|
|
62
63
|
}
|
|
63
64
|
}
|
|
64
65
|
};
|
|
65
|
-
};
|
|
66
|
+
});
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import type { Emitter } from "mitt";
|
|
2
|
+
/**
|
|
3
|
+
* PUBLIC API - Types users commonly need
|
|
4
|
+
* These are exported from the main package
|
|
5
|
+
*/
|
|
6
|
+
export type FileStatus = "waiting" | "preprocessing" | "uploading" | "postprocessing" | "complete" | "error";
|
|
7
|
+
export type UploadStatus = "waiting" | "uploading";
|
|
8
|
+
export interface FileProgress {
|
|
9
|
+
percentage: number;
|
|
10
|
+
}
|
|
11
|
+
export interface FileError {
|
|
12
|
+
message: string;
|
|
13
|
+
details?: unknown;
|
|
14
|
+
}
|
|
15
|
+
export interface UploadFile<TUploadResult = any> {
|
|
16
|
+
id: string;
|
|
17
|
+
name: string;
|
|
18
|
+
size: number;
|
|
19
|
+
mimeType: string;
|
|
20
|
+
data: File | Blob;
|
|
21
|
+
status: FileStatus;
|
|
22
|
+
preview?: string;
|
|
23
|
+
progress: FileProgress;
|
|
24
|
+
error?: FileError;
|
|
25
|
+
uploadResult?: TUploadResult;
|
|
26
|
+
isRemote?: boolean;
|
|
27
|
+
remoteUrl?: string;
|
|
28
|
+
meta: Record<string, unknown>;
|
|
29
|
+
}
|
|
30
|
+
export type UploadFn<TUploadResult = any> = (file: UploadFile<TUploadResult>, onProgress: (progress: number) => void) => Promise<TUploadResult>;
|
|
31
|
+
export type GetRemoteFileFn = (fileId: string) => Promise<MinimumRemoteFileAttributes>;
|
|
32
|
+
export interface UploadOptions {
|
|
33
|
+
/**
|
|
34
|
+
* Custom plugins to add (in addition to built-in plugins)
|
|
35
|
+
*/
|
|
36
|
+
plugins?: Plugin<any, any>[];
|
|
37
|
+
/**
|
|
38
|
+
* Validate maximum number of files
|
|
39
|
+
* - false: disabled
|
|
40
|
+
* - number: enabled with limit
|
|
41
|
+
* @default false
|
|
42
|
+
*/
|
|
43
|
+
maxFiles?: false | number;
|
|
44
|
+
/**
|
|
45
|
+
* Validate maximum file size in bytes
|
|
46
|
+
* - false: disabled
|
|
47
|
+
* - number: enabled with limit
|
|
48
|
+
* @default false
|
|
49
|
+
*/
|
|
50
|
+
maxFileSize?: false | number;
|
|
51
|
+
/**
|
|
52
|
+
* Validate allowed file MIME types
|
|
53
|
+
* - false: disabled
|
|
54
|
+
* - string[]: enabled with allowed types
|
|
55
|
+
* @default false
|
|
56
|
+
*/
|
|
57
|
+
allowedFileTypes?: false | string[];
|
|
58
|
+
/**
|
|
59
|
+
* Generate thumbnail previews for images/videos
|
|
60
|
+
* - false: disabled
|
|
61
|
+
* - true: enabled with defaults
|
|
62
|
+
* - object: enabled with custom options
|
|
63
|
+
* @default false
|
|
64
|
+
*/
|
|
65
|
+
thumbnails?: false | true | ThumbnailOptions;
|
|
66
|
+
/**
|
|
67
|
+
* Compress images before upload
|
|
68
|
+
* - false: disabled
|
|
69
|
+
* - true: enabled with defaults
|
|
70
|
+
* - object: enabled with custom options
|
|
71
|
+
* @default false
|
|
72
|
+
*/
|
|
73
|
+
imageCompression?: false | true | ImageCompressionOptions;
|
|
74
|
+
/**
|
|
75
|
+
* Automatically start upload after files are added
|
|
76
|
+
* @default false
|
|
77
|
+
*/
|
|
78
|
+
autoProceed?: boolean;
|
|
79
|
+
}
|
|
80
|
+
export interface ThumbnailOptions {
|
|
81
|
+
width?: number;
|
|
82
|
+
height?: number;
|
|
83
|
+
quality?: number;
|
|
84
|
+
}
|
|
85
|
+
export interface ImageCompressionOptions {
|
|
86
|
+
maxWidth?: number;
|
|
87
|
+
maxHeight?: number;
|
|
88
|
+
quality?: number;
|
|
89
|
+
outputFormat?: "jpeg" | "webp" | "png" | "auto";
|
|
90
|
+
minSizeToCompress?: number;
|
|
91
|
+
preserveMetadata?: boolean;
|
|
92
|
+
}
|
|
93
|
+
type CoreUploaderEvents<TUploadResult = any> = {
|
|
94
|
+
"file:added": Readonly<UploadFile<TUploadResult>>;
|
|
95
|
+
"file:removed": Readonly<UploadFile<TUploadResult>>;
|
|
96
|
+
"file:processing": Readonly<UploadFile<TUploadResult>>;
|
|
97
|
+
"file:error": {
|
|
98
|
+
file: Readonly<UploadFile<TUploadResult>>;
|
|
99
|
+
error: FileError;
|
|
100
|
+
};
|
|
101
|
+
"upload:start": Array<Readonly<UploadFile<TUploadResult>>>;
|
|
102
|
+
"upload:complete": Array<Required<Readonly<UploadFile<TUploadResult>>>>;
|
|
103
|
+
"upload:error": FileError;
|
|
104
|
+
"upload:progress": {
|
|
105
|
+
file: Readonly<UploadFile<TUploadResult>>;
|
|
106
|
+
progress: number;
|
|
107
|
+
};
|
|
108
|
+
"files:reorder": {
|
|
109
|
+
oldIndex: number;
|
|
110
|
+
newIndex: number;
|
|
111
|
+
};
|
|
112
|
+
};
|
|
113
|
+
export type UploaderEvents<TUploadResult = any> = CoreUploaderEvents<TUploadResult>;
|
|
114
|
+
/**
|
|
115
|
+
* PLUGIN API - Types for building custom plugins
|
|
116
|
+
* Only needed if users want to create custom validators/processors
|
|
117
|
+
*/
|
|
118
|
+
export type PluginContext<TPluginEvents extends Record<string, any> = Record<string, never>> = {
|
|
119
|
+
files: UploadFile[];
|
|
120
|
+
options: UploadOptions;
|
|
121
|
+
/**
|
|
122
|
+
* Emit custom plugin events
|
|
123
|
+
* Events are automatically prefixed with the plugin ID
|
|
124
|
+
*/
|
|
125
|
+
emit: <K extends keyof TPluginEvents>(event: K, payload: TPluginEvents[K]) => void;
|
|
126
|
+
};
|
|
127
|
+
export type ValidationHook<TPluginEvents extends Record<string, any> = Record<string, never>> = (file: UploadFile, context: PluginContext<TPluginEvents>) => Promise<true | UploadFile>;
|
|
128
|
+
export type ProcessingHook<TPluginEvents extends Record<string, any> = Record<string, never>> = (file: UploadFile, context: PluginContext<TPluginEvents>) => Promise<UploadFile>;
|
|
129
|
+
export type SetupHook<TPluginEvents extends Record<string, any> = Record<string, never>> = (context: PluginContext<TPluginEvents>) => Promise<void>;
|
|
130
|
+
/**
|
|
131
|
+
* Storage hooks for handling upload/download/deletion operations
|
|
132
|
+
*/
|
|
133
|
+
export type UploadHook<TUploadResult = any, TPluginEvents extends Record<string, any> = Record<string, never>> = (file: UploadFile<TUploadResult>, context: PluginContext<TPluginEvents> & {
|
|
134
|
+
onProgress: (progress: number) => void;
|
|
135
|
+
}) => Promise<TUploadResult>;
|
|
136
|
+
export type GetRemoteFileHook<TPluginEvents extends Record<string, any> = Record<string, never>> = (fileId: string, context: PluginContext<TPluginEvents>) => Promise<MinimumRemoteFileAttributes>;
|
|
137
|
+
export type RemoveHook<TPluginEvents extends Record<string, any> = Record<string, never>> = (file: UploadFile, context: PluginContext<TPluginEvents>) => Promise<void>;
|
|
138
|
+
export type PluginLifecycleStage = "validate" | "process" | "upload" | "complete";
|
|
139
|
+
export type PluginHooks<TUploadResult = any, TPluginEvents extends Record<string, any> = Record<string, never>> = {
|
|
140
|
+
validate?: ValidationHook<TPluginEvents>;
|
|
141
|
+
process?: ProcessingHook<TPluginEvents>;
|
|
142
|
+
upload?: UploadHook<TUploadResult, TPluginEvents>;
|
|
143
|
+
getRemoteFile?: GetRemoteFileHook<TPluginEvents>;
|
|
144
|
+
remove?: RemoveHook<TPluginEvents>;
|
|
145
|
+
complete?: ProcessingHook<TPluginEvents>;
|
|
146
|
+
};
|
|
147
|
+
export interface Plugin<TUploadResult = any, TPluginEvents extends Record<string, any> = Record<string, never>> {
|
|
148
|
+
id: string;
|
|
149
|
+
hooks: PluginHooks<TUploadResult, TPluginEvents>;
|
|
150
|
+
options?: UploadOptions;
|
|
151
|
+
events?: TPluginEvents;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Define an uploader plugin with type safety, context access, and custom events.
|
|
155
|
+
* This is the universal plugin factory for all plugin types (storage, validators, processors).
|
|
156
|
+
*
|
|
157
|
+
* Hooks receive context as a parameter, making it clear when context is available.
|
|
158
|
+
* Context includes current files, options, and an emit function for custom events.
|
|
159
|
+
*
|
|
160
|
+
* @example Basic Plugin
|
|
161
|
+
* ```typescript
|
|
162
|
+
* export const ValidatorMaxFiles = defineUploaderPlugin<ValidatorOptions>((options) => ({
|
|
163
|
+
* id: 'validator-max-files',
|
|
164
|
+
* hooks: {
|
|
165
|
+
* validate: async (file, context) => {
|
|
166
|
+
* if (context.files.length >= options.maxFiles) {
|
|
167
|
+
* throw { message: 'Too many files' }
|
|
168
|
+
* }
|
|
169
|
+
* return file
|
|
170
|
+
* }
|
|
171
|
+
* }
|
|
172
|
+
* }))
|
|
173
|
+
* ```
|
|
174
|
+
*
|
|
175
|
+
* @example Plugin with Custom Events
|
|
176
|
+
* ```typescript
|
|
177
|
+
* type CompressionEvents = {
|
|
178
|
+
* start: { file: UploadFile; originalSize: number }
|
|
179
|
+
* complete: { file: UploadFile; savedBytes: number }
|
|
180
|
+
* }
|
|
181
|
+
*
|
|
182
|
+
* export const PluginImageCompressor = defineUploaderPlugin<
|
|
183
|
+
* ImageCompressorOptions,
|
|
184
|
+
* CompressionEvents
|
|
185
|
+
* >((options) => ({
|
|
186
|
+
* id: 'image-compressor',
|
|
187
|
+
* hooks: {
|
|
188
|
+
* process: async (file, context) => {
|
|
189
|
+
* context.emit('start', { file, originalSize: file.size })
|
|
190
|
+
* // ... compression logic ...
|
|
191
|
+
* context.emit('complete', { file, savedBytes: 1000 })
|
|
192
|
+
* return file
|
|
193
|
+
* }
|
|
194
|
+
* }
|
|
195
|
+
* }))
|
|
196
|
+
*
|
|
197
|
+
* // Usage - events are automatically prefixed with plugin ID
|
|
198
|
+
* uploader.on('image-compressor:complete', ({ file, savedBytes }) => {
|
|
199
|
+
* console.log(`Saved ${savedBytes} bytes`)
|
|
200
|
+
* })
|
|
201
|
+
* ```
|
|
202
|
+
*/
|
|
203
|
+
export declare function defineUploaderPlugin<TPluginOptions = unknown, TPluginEvents extends Record<string, any> = Record<string, never>>(factory: (options: TPluginOptions) => Plugin<any, TPluginEvents>): (options: TPluginOptions) => Plugin<any, TPluginEvents>;
|
|
204
|
+
/**
|
|
205
|
+
* INTERNAL TYPES - Not commonly needed by users
|
|
206
|
+
* Kept exported for edge cases but not primary API
|
|
207
|
+
*/
|
|
208
|
+
export type PreProcessor = (file: UploadFile) => Promise<UploadFile>;
|
|
209
|
+
export type Uploader = (file: Readonly<UploadFile>, emiter: Emitter<Pick<UploaderEvents, "upload:error" | "upload:progress">>) => Promise<string>;
|
|
210
|
+
export type Validator = (file: UploadFile) => Promise<boolean | FileError>;
|
|
211
|
+
export type Processor = (file: UploadFile) => Promise<File | Blob>;
|
|
212
|
+
export interface UploadBlob {
|
|
213
|
+
blobPath: string;
|
|
214
|
+
}
|
|
215
|
+
type MinimumRemoteFileAttributes = {
|
|
216
|
+
size: number;
|
|
217
|
+
mimeType: string;
|
|
218
|
+
remoteUrl: string;
|
|
219
|
+
};
|
|
220
|
+
export {};
|
package/dist/runtime/composables/{useUploader → useUploadManager}/validators/allowed-file-types.js
RENAMED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
import { defineUploaderPlugin } from "../types.js";
|
|
2
|
+
export const ValidatorAllowedFileTypes = defineUploaderPlugin((options) => {
|
|
2
3
|
return {
|
|
3
4
|
id: "validator-allowed-file-types",
|
|
4
5
|
hooks: {
|
|
5
|
-
validate: async (file) => {
|
|
6
|
+
validate: async (file, _context) => {
|
|
6
7
|
if (options.allowedFileTypes && options.allowedFileTypes.includes(file.mimeType) || (options.allowedFileTypes && options.allowedFileTypes.length) === 0)
|
|
7
8
|
return file;
|
|
8
9
|
throw { message: `File type ${file.mimeType} is not allowed` };
|
|
9
10
|
}
|
|
10
11
|
}
|
|
11
12
|
};
|
|
12
|
-
};
|
|
13
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
interface ValidatorDuplicateFileOptions {
|
|
2
|
+
/**
|
|
3
|
+
* Whether to allow duplicate files
|
|
4
|
+
* @default false
|
|
5
|
+
*/
|
|
6
|
+
allowDuplicates?: boolean;
|
|
7
|
+
/**
|
|
8
|
+
* Custom error message for duplicates
|
|
9
|
+
*/
|
|
10
|
+
errorMessage?: string;
|
|
11
|
+
}
|
|
12
|
+
export declare const ValidatorDuplicateFile: (options: ValidatorDuplicateFileOptions) => import("../types.js").Plugin<any, Record<string, never>>;
|
|
13
|
+
export {};
|
package/dist/runtime/composables/{useUploader → useUploadManager}/validators/duplicate-file.js
RENAMED
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
|
|
1
|
+
import { defineUploaderPlugin } from "../types.js";
|
|
2
|
+
export const ValidatorDuplicateFile = defineUploaderPlugin((options) => {
|
|
2
3
|
const { allowDuplicates = false, errorMessage = "This file has already been added" } = options;
|
|
3
4
|
return {
|
|
4
5
|
id: "validator-duplicate-file",
|
|
5
6
|
hooks: {
|
|
6
|
-
validate: async (file) => {
|
|
7
|
+
validate: async (file, context) => {
|
|
7
8
|
if (allowDuplicates) {
|
|
8
9
|
return file;
|
|
9
10
|
}
|
|
10
|
-
const isDuplicate = files.some((existingFile) => {
|
|
11
|
+
const isDuplicate = context.files.some((existingFile) => {
|
|
11
12
|
const sameSize = existingFile.size === file.size;
|
|
12
13
|
const sameName = existingFile.name === file.name;
|
|
13
14
|
let sameDate = true;
|
|
@@ -23,4 +24,4 @@ export const ValidatorDuplicateFile = ({ files }, options) => {
|
|
|
23
24
|
}
|
|
24
25
|
}
|
|
25
26
|
};
|
|
26
|
-
};
|
|
27
|
+
});
|
package/dist/runtime/composables/{useUploader → useUploadManager}/validators/max-file-size.js
RENAMED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
import { defineUploaderPlugin } from "../types.js";
|
|
2
|
+
export const ValidatorMaxfileSize = defineUploaderPlugin((options) => {
|
|
2
3
|
return {
|
|
3
4
|
id: "validator-max-file-size",
|
|
4
5
|
hooks: {
|
|
5
|
-
validate: async (file) => {
|
|
6
|
+
validate: async (file, _context) => {
|
|
6
7
|
if (options.maxFileSize && options.maxFileSize !== Infinity && file.size <= options.maxFileSize || options.maxFileSize && options.maxFileSize === Infinity)
|
|
7
8
|
return file;
|
|
8
9
|
throw { message: `File size exceeds the maximum limit of ${options.maxFileSize} bytes` };
|
|
9
10
|
}
|
|
10
11
|
}
|
|
11
12
|
};
|
|
12
|
-
};
|
|
13
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { defineUploaderPlugin } from "../types.js";
|
|
2
|
+
export const ValidatorMaxFiles = defineUploaderPlugin((options) => {
|
|
3
|
+
return {
|
|
4
|
+
id: "validator-max-files",
|
|
5
|
+
hooks: {
|
|
6
|
+
validate: async (file, context) => {
|
|
7
|
+
if (options.maxFiles && options.maxFiles !== Infinity && context.files.length < options.maxFiles || options.maxFiles && options.maxFiles === Infinity)
|
|
8
|
+
return file;
|
|
9
|
+
throw { message: `Maximum number of files (${options.maxFiles}) exceeded` };
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
});
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export * from "../components/DialogConfirm.vue.js";
|
|
2
2
|
export * from "../components/DialogAlert.vue.js";
|
|
3
3
|
export * from "./tv.js";
|
|
4
|
-
export * from "../composables/
|
|
4
|
+
export * from "../composables/useUploadManager/types.js";
|
|
5
|
+
export * from "../composables/useUploadManager/plugins/index.js";
|
|
6
|
+
export * from "../composables/useUploadManager/validators/index.js";
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export * from "../components/DialogConfirm.vue";
|
|
2
2
|
export * from "../components/DialogAlert.vue";
|
|
3
3
|
export * from "./tv.js";
|
|
4
|
-
export * from "../composables/
|
|
4
|
+
export * from "../composables/useUploadManager/types.js";
|
|
5
|
+
export * from "../composables/useUploadManager/plugins/index.js";
|
|
6
|
+
export * from "../composables/useUploadManager/validators/index.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nuxt-ui-elements",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.26",
|
|
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",
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
"tailwind-variants": "^3.2.2"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
|
+
"@azure/storage-file-datalake": "^12.28.1",
|
|
37
38
|
"@nuxt/devtools": "^3.1.1",
|
|
38
39
|
"@nuxt/eslint-config": "^1.12.1",
|
|
39
40
|
"@nuxt/module-builder": "^1.0.2",
|
|
@@ -55,6 +56,11 @@
|
|
|
55
56
|
"peerDependencies": {
|
|
56
57
|
"@nuxt/ui": "^4.0.0"
|
|
57
58
|
},
|
|
59
|
+
"peerDependenciesMeta": {
|
|
60
|
+
"@azure/storage-file-datalake": {
|
|
61
|
+
"optional": true
|
|
62
|
+
}
|
|
63
|
+
},
|
|
58
64
|
"scripts": {
|
|
59
65
|
"dev": "pnpm dev:prepare && nuxi dev playground",
|
|
60
66
|
"dev:build": "nuxi build playground",
|
|
@@ -1,155 +0,0 @@
|
|
|
1
|
-
import type { Emitter } from "mitt";
|
|
2
|
-
/**
|
|
3
|
-
* PUBLIC API - Types users commonly need
|
|
4
|
-
* These are exported from the main package
|
|
5
|
-
*/
|
|
6
|
-
export type FileStatus = "waiting" | "preprocessing" | "uploading" | "postprocessing" | "complete" | "error";
|
|
7
|
-
export type UploadStatus = "waiting" | "uploading";
|
|
8
|
-
export interface FileProgress {
|
|
9
|
-
percentage: number;
|
|
10
|
-
}
|
|
11
|
-
export interface FileError {
|
|
12
|
-
message: string;
|
|
13
|
-
details?: unknown;
|
|
14
|
-
}
|
|
15
|
-
export interface UploadFile<TUploadResult = any> {
|
|
16
|
-
id: string;
|
|
17
|
-
name: string;
|
|
18
|
-
size: number;
|
|
19
|
-
mimeType: string;
|
|
20
|
-
data: File | Blob;
|
|
21
|
-
status: FileStatus;
|
|
22
|
-
preview?: string;
|
|
23
|
-
progress: FileProgress;
|
|
24
|
-
error?: FileError;
|
|
25
|
-
uploadResult?: TUploadResult;
|
|
26
|
-
isRemote?: boolean;
|
|
27
|
-
remoteUrl?: string;
|
|
28
|
-
meta: Record<string, unknown>;
|
|
29
|
-
}
|
|
30
|
-
export type UploadFn<TUploadResult = any> = (file: UploadFile<TUploadResult>, onProgress: (progress: number) => void) => Promise<TUploadResult>;
|
|
31
|
-
export type GetRemoteFileFn = (fileId: string) => Promise<MinimumRemoteFileAttributes>;
|
|
32
|
-
export interface UploadOptions {
|
|
33
|
-
/**
|
|
34
|
-
* Custom plugins to add (in addition to built-in plugins)
|
|
35
|
-
*/
|
|
36
|
-
plugins?: Plugin[];
|
|
37
|
-
/**
|
|
38
|
-
* Validate maximum number of files
|
|
39
|
-
* - false: disabled
|
|
40
|
-
* - number: enabled with limit
|
|
41
|
-
* @default false
|
|
42
|
-
*/
|
|
43
|
-
maxFiles?: false | number;
|
|
44
|
-
/**
|
|
45
|
-
* Validate maximum file size in bytes
|
|
46
|
-
* - false: disabled
|
|
47
|
-
* - number: enabled with limit
|
|
48
|
-
* @default false
|
|
49
|
-
*/
|
|
50
|
-
maxFileSize?: false | number;
|
|
51
|
-
/**
|
|
52
|
-
* Validate allowed file MIME types
|
|
53
|
-
* - false: disabled
|
|
54
|
-
* - string[]: enabled with allowed types
|
|
55
|
-
* @default false
|
|
56
|
-
*/
|
|
57
|
-
allowedFileTypes?: false | string[];
|
|
58
|
-
/**
|
|
59
|
-
* Generate thumbnail previews for images/videos
|
|
60
|
-
* - false: disabled
|
|
61
|
-
* - true: enabled with defaults
|
|
62
|
-
* - object: enabled with custom options
|
|
63
|
-
* @default false
|
|
64
|
-
*/
|
|
65
|
-
thumbnails?: false | true | ThumbnailOptions;
|
|
66
|
-
/**
|
|
67
|
-
* Compress images before upload
|
|
68
|
-
* - false: disabled
|
|
69
|
-
* - true: enabled with defaults
|
|
70
|
-
* - object: enabled with custom options
|
|
71
|
-
* @default false
|
|
72
|
-
*/
|
|
73
|
-
imageCompression?: false | true | ImageCompressionOptions;
|
|
74
|
-
/**
|
|
75
|
-
* Automatically start upload after files are added
|
|
76
|
-
* @default false
|
|
77
|
-
*/
|
|
78
|
-
autoProceed?: boolean;
|
|
79
|
-
}
|
|
80
|
-
export interface ThumbnailOptions {
|
|
81
|
-
width?: number;
|
|
82
|
-
height?: number;
|
|
83
|
-
quality?: number;
|
|
84
|
-
}
|
|
85
|
-
export interface ImageCompressionOptions {
|
|
86
|
-
maxWidth?: number;
|
|
87
|
-
maxHeight?: number;
|
|
88
|
-
quality?: number;
|
|
89
|
-
outputFormat?: "jpeg" | "webp" | "png" | "auto";
|
|
90
|
-
minSizeToCompress?: number;
|
|
91
|
-
preserveMetadata?: boolean;
|
|
92
|
-
}
|
|
93
|
-
export type UploaderEvents<TUploadResult = any> = {
|
|
94
|
-
"file:added": Readonly<UploadFile<TUploadResult>>;
|
|
95
|
-
"file:removed": Readonly<UploadFile<TUploadResult>>;
|
|
96
|
-
"file:processing": Readonly<UploadFile<TUploadResult>>;
|
|
97
|
-
"file:error": {
|
|
98
|
-
file: Readonly<UploadFile<TUploadResult>>;
|
|
99
|
-
error: FileError;
|
|
100
|
-
};
|
|
101
|
-
"upload:start": Array<Readonly<UploadFile<TUploadResult>>>;
|
|
102
|
-
"upload:complete": Array<Required<Readonly<UploadFile<TUploadResult>>>>;
|
|
103
|
-
"upload:error": FileError;
|
|
104
|
-
"upload:progress": {
|
|
105
|
-
file: Readonly<UploadFile<TUploadResult>>;
|
|
106
|
-
progress: number;
|
|
107
|
-
};
|
|
108
|
-
"files:reorder": {
|
|
109
|
-
oldIndex: number;
|
|
110
|
-
newIndex: number;
|
|
111
|
-
};
|
|
112
|
-
};
|
|
113
|
-
/**
|
|
114
|
-
* PLUGIN API - Types for building custom plugins
|
|
115
|
-
* Only needed if users want to create custom validators/processors
|
|
116
|
-
*/
|
|
117
|
-
export type PluginContext = {
|
|
118
|
-
files: UploadFile[];
|
|
119
|
-
options: UploadOptions;
|
|
120
|
-
};
|
|
121
|
-
export type ValidationHook = (file: UploadFile, context: PluginContext) => Promise<true | UploadFile>;
|
|
122
|
-
export type ProcessingHook = (file: UploadFile, context: PluginContext) => Promise<UploadFile>;
|
|
123
|
-
export type SetupHook = (context: PluginContext) => Promise<void>;
|
|
124
|
-
export type PluginLifecycleStage = "validate" | "process" | "complete";
|
|
125
|
-
export type PluginHooks = {
|
|
126
|
-
validate?: ValidationHook;
|
|
127
|
-
process?: ProcessingHook;
|
|
128
|
-
complete?: ProcessingHook;
|
|
129
|
-
};
|
|
130
|
-
export interface Plugin {
|
|
131
|
-
id: string;
|
|
132
|
-
hooks: PluginHooks;
|
|
133
|
-
options?: UploadOptions;
|
|
134
|
-
}
|
|
135
|
-
export type PluginFn<TPluginOptions = unknown> = {
|
|
136
|
-
(context: PluginContext, pluginOptions: TPluginOptions): Plugin;
|
|
137
|
-
__pluginOptions?: TPluginOptions;
|
|
138
|
-
};
|
|
139
|
-
/**
|
|
140
|
-
* INTERNAL TYPES - Not commonly needed by users
|
|
141
|
-
* Kept exported for edge cases but not primary API
|
|
142
|
-
*/
|
|
143
|
-
export type PreProcessor = (file: UploadFile) => Promise<UploadFile>;
|
|
144
|
-
export type Uploader = (file: Readonly<UploadFile>, emiter: Emitter<Pick<UploaderEvents, "upload:error" | "upload:progress">>) => Promise<string>;
|
|
145
|
-
export type Validator = (file: UploadFile) => Promise<boolean | FileError>;
|
|
146
|
-
export type Processor = (file: UploadFile) => Promise<File | Blob>;
|
|
147
|
-
export interface UploadBlob {
|
|
148
|
-
blobPath: string;
|
|
149
|
-
}
|
|
150
|
-
type MinimumRemoteFileAttributes = {
|
|
151
|
-
size: number;
|
|
152
|
-
mimeType: string;
|
|
153
|
-
remoteUrl: string;
|
|
154
|
-
};
|
|
155
|
-
export {};
|
|
File without changes
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import type { PluginFn } from "../types.js";
|
|
2
|
-
interface ValidatorDuplicateFileOptions {
|
|
3
|
-
/**
|
|
4
|
-
* Whether to allow duplicate files
|
|
5
|
-
* @default false
|
|
6
|
-
*/
|
|
7
|
-
allowDuplicates?: boolean;
|
|
8
|
-
/**
|
|
9
|
-
* Custom error message for duplicates
|
|
10
|
-
*/
|
|
11
|
-
errorMessage?: string;
|
|
12
|
-
}
|
|
13
|
-
/**
|
|
14
|
-
* Prevents uploading duplicate files based on name, size, and last modified date.
|
|
15
|
-
* Useful for preventing accidental double-uploads in social media scheduling.
|
|
16
|
-
*
|
|
17
|
-
* @example
|
|
18
|
-
* ```ts
|
|
19
|
-
* const uploader = useUpload()
|
|
20
|
-
* uploader.addPlugin(ValidatorDuplicateFile, {
|
|
21
|
-
* allowDuplicates: false,
|
|
22
|
-
* errorMessage: 'This file has already been added'
|
|
23
|
-
* })
|
|
24
|
-
* ```
|
|
25
|
-
*/
|
|
26
|
-
export declare const ValidatorDuplicateFile: PluginFn<ValidatorDuplicateFileOptions>;
|
|
27
|
-
export {};
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
export const ValidatorMaxFiles = ({ files }, options) => {
|
|
2
|
-
return {
|
|
3
|
-
id: "validator-max-files",
|
|
4
|
-
hooks: {
|
|
5
|
-
validate: async (file) => {
|
|
6
|
-
if (options.maxFiles && options.maxFiles !== Infinity && files.length < options.maxFiles || options.maxFiles && options.maxFiles === Infinity)
|
|
7
|
-
return file;
|
|
8
|
-
throw { message: `Maximum number of files (${options.maxFiles}) exceeded` };
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
};
|
|
12
|
-
};
|
|
File without changes
|
|
File without changes
|