nuxt-ui-elements 0.1.22 → 0.1.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +199 -160
- package/dist/module.d.mts +1 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +8 -1
- package/dist/runtime/components/DialogAlert.d.vue.ts +3 -3
- package/dist/runtime/components/DialogAlert.vue.d.ts +3 -3
- package/dist/runtime/components/DialogConfirm.d.vue.ts +5 -5
- package/dist/runtime/components/DialogConfirm.vue +1 -1
- package/dist/runtime/components/DialogConfirm.vue.d.ts +5 -5
- package/dist/runtime/composables/useDialog.d.ts +3 -29
- package/dist/runtime/composables/useDialog.js +1 -1
- package/dist/runtime/composables/useUploader/index.d.ts +368 -0
- package/dist/runtime/composables/useUploader/index.js +274 -0
- package/dist/runtime/composables/useUploader/plugins/image-compressor.d.ts +49 -0
- package/dist/runtime/composables/useUploader/plugins/image-compressor.js +111 -0
- package/dist/runtime/composables/useUploader/plugins/index.d.ts +2 -0
- package/dist/runtime/composables/useUploader/plugins/index.js +2 -0
- package/dist/runtime/composables/useUploader/plugins/thumbnail-generator.d.ts +8 -0
- package/dist/runtime/composables/useUploader/plugins/thumbnail-generator.js +65 -0
- package/dist/runtime/composables/useUploader/types.d.ts +155 -0
- package/dist/runtime/composables/useUploader/types.js +0 -0
- package/dist/runtime/composables/useUploader/validators/allowed-file-types.d.ts +6 -0
- package/dist/runtime/composables/useUploader/validators/allowed-file-types.js +12 -0
- package/dist/runtime/composables/useUploader/validators/duplicate-file.d.ts +27 -0
- package/dist/runtime/composables/useUploader/validators/duplicate-file.js +26 -0
- package/dist/runtime/composables/useUploader/validators/index.d.ts +4 -0
- package/dist/runtime/composables/useUploader/validators/index.js +4 -0
- package/dist/runtime/composables/useUploader/validators/max-file-size.d.ts +6 -0
- package/dist/runtime/composables/useUploader/validators/max-file-size.js +12 -0
- package/dist/runtime/composables/useUploader/validators/max-files.d.ts +6 -0
- package/dist/runtime/composables/useUploader/validators/max-files.js +12 -0
- package/dist/runtime/index.d.ts +1 -0
- package/dist/runtime/index.js +1 -0
- package/dist/runtime/types/index.d.ts +4 -0
- package/dist/runtime/types/index.js +4 -0
- package/dist/types.d.mts +2 -0
- package/package.json +3 -2
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
import type { PluginFn, UploaderEvents, UploadFile, UploadOptions, UploadStatus, UploadFn, GetRemoteFileFn } from "./types.js";
|
|
2
|
+
export declare const useUploader: <TUploadResult = any>(_options?: UploadOptions) => {
|
|
3
|
+
files: Readonly<import("vue").Ref<readonly {
|
|
4
|
+
readonly id: string;
|
|
5
|
+
readonly name: string;
|
|
6
|
+
readonly size: number;
|
|
7
|
+
readonly mimeType: string;
|
|
8
|
+
readonly data: {
|
|
9
|
+
readonly lastModified: number;
|
|
10
|
+
readonly name: string;
|
|
11
|
+
readonly webkitRelativePath: string;
|
|
12
|
+
readonly size: number;
|
|
13
|
+
readonly type: string;
|
|
14
|
+
readonly arrayBuffer: {
|
|
15
|
+
(): Promise<ArrayBuffer>;
|
|
16
|
+
(): Promise<ArrayBuffer>;
|
|
17
|
+
};
|
|
18
|
+
readonly bytes: {
|
|
19
|
+
(): Promise<Uint8Array<ArrayBuffer>>;
|
|
20
|
+
(): Promise<Uint8Array<ArrayBuffer>>;
|
|
21
|
+
};
|
|
22
|
+
readonly slice: {
|
|
23
|
+
(start?: number, end?: number, contentType?: string): Blob;
|
|
24
|
+
(start?: number, end?: number, contentType?: string): Blob;
|
|
25
|
+
};
|
|
26
|
+
readonly stream: {
|
|
27
|
+
(): ReadableStream<Uint8Array<ArrayBuffer>>;
|
|
28
|
+
(): ReadableStream<Uint8Array<ArrayBuffer>>;
|
|
29
|
+
};
|
|
30
|
+
readonly text: {
|
|
31
|
+
(): Promise<string>;
|
|
32
|
+
(): Promise<string>;
|
|
33
|
+
};
|
|
34
|
+
} | {
|
|
35
|
+
readonly size: number;
|
|
36
|
+
readonly type: string;
|
|
37
|
+
readonly arrayBuffer: {
|
|
38
|
+
(): Promise<ArrayBuffer>;
|
|
39
|
+
(): Promise<ArrayBuffer>;
|
|
40
|
+
};
|
|
41
|
+
readonly bytes: {
|
|
42
|
+
(): Promise<Uint8Array<ArrayBuffer>>;
|
|
43
|
+
(): Promise<Uint8Array<ArrayBuffer>>;
|
|
44
|
+
};
|
|
45
|
+
readonly slice: {
|
|
46
|
+
(start?: number, end?: number, contentType?: string): Blob;
|
|
47
|
+
(start?: number, end?: number, contentType?: string): Blob;
|
|
48
|
+
};
|
|
49
|
+
readonly stream: {
|
|
50
|
+
(): ReadableStream<Uint8Array<ArrayBuffer>>;
|
|
51
|
+
(): ReadableStream<Uint8Array<ArrayBuffer>>;
|
|
52
|
+
};
|
|
53
|
+
readonly text: {
|
|
54
|
+
(): Promise<string>;
|
|
55
|
+
(): Promise<string>;
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
readonly status: import("./types.js").FileStatus;
|
|
59
|
+
readonly preview?: string | undefined;
|
|
60
|
+
readonly progress: {
|
|
61
|
+
readonly percentage: number;
|
|
62
|
+
};
|
|
63
|
+
readonly error?: {
|
|
64
|
+
readonly message: string;
|
|
65
|
+
readonly details?: Readonly<unknown> | undefined;
|
|
66
|
+
} | undefined;
|
|
67
|
+
readonly uploadResult?: import("vue").DeepReadonly<import("vue").UnwrapRef<TUploadResult>> | undefined;
|
|
68
|
+
readonly isRemote?: boolean | undefined;
|
|
69
|
+
readonly remoteUrl?: string | undefined;
|
|
70
|
+
readonly meta: {
|
|
71
|
+
readonly [x: string]: Readonly<unknown>;
|
|
72
|
+
};
|
|
73
|
+
}[], readonly {
|
|
74
|
+
readonly id: string;
|
|
75
|
+
readonly name: string;
|
|
76
|
+
readonly size: number;
|
|
77
|
+
readonly mimeType: string;
|
|
78
|
+
readonly data: {
|
|
79
|
+
readonly lastModified: number;
|
|
80
|
+
readonly name: string;
|
|
81
|
+
readonly webkitRelativePath: string;
|
|
82
|
+
readonly size: number;
|
|
83
|
+
readonly type: string;
|
|
84
|
+
readonly arrayBuffer: {
|
|
85
|
+
(): Promise<ArrayBuffer>;
|
|
86
|
+
(): Promise<ArrayBuffer>;
|
|
87
|
+
};
|
|
88
|
+
readonly bytes: {
|
|
89
|
+
(): Promise<Uint8Array<ArrayBuffer>>;
|
|
90
|
+
(): Promise<Uint8Array<ArrayBuffer>>;
|
|
91
|
+
};
|
|
92
|
+
readonly slice: {
|
|
93
|
+
(start?: number, end?: number, contentType?: string): Blob;
|
|
94
|
+
(start?: number, end?: number, contentType?: string): Blob;
|
|
95
|
+
};
|
|
96
|
+
readonly stream: {
|
|
97
|
+
(): ReadableStream<Uint8Array<ArrayBuffer>>;
|
|
98
|
+
(): ReadableStream<Uint8Array<ArrayBuffer>>;
|
|
99
|
+
};
|
|
100
|
+
readonly text: {
|
|
101
|
+
(): Promise<string>;
|
|
102
|
+
(): Promise<string>;
|
|
103
|
+
};
|
|
104
|
+
} | {
|
|
105
|
+
readonly size: number;
|
|
106
|
+
readonly type: string;
|
|
107
|
+
readonly arrayBuffer: {
|
|
108
|
+
(): Promise<ArrayBuffer>;
|
|
109
|
+
(): Promise<ArrayBuffer>;
|
|
110
|
+
};
|
|
111
|
+
readonly bytes: {
|
|
112
|
+
(): Promise<Uint8Array<ArrayBuffer>>;
|
|
113
|
+
(): Promise<Uint8Array<ArrayBuffer>>;
|
|
114
|
+
};
|
|
115
|
+
readonly slice: {
|
|
116
|
+
(start?: number, end?: number, contentType?: string): Blob;
|
|
117
|
+
(start?: number, end?: number, contentType?: string): Blob;
|
|
118
|
+
};
|
|
119
|
+
readonly stream: {
|
|
120
|
+
(): ReadableStream<Uint8Array<ArrayBuffer>>;
|
|
121
|
+
(): ReadableStream<Uint8Array<ArrayBuffer>>;
|
|
122
|
+
};
|
|
123
|
+
readonly text: {
|
|
124
|
+
(): Promise<string>;
|
|
125
|
+
(): Promise<string>;
|
|
126
|
+
};
|
|
127
|
+
};
|
|
128
|
+
readonly status: import("./types.js").FileStatus;
|
|
129
|
+
readonly preview?: string | undefined;
|
|
130
|
+
readonly progress: {
|
|
131
|
+
readonly percentage: number;
|
|
132
|
+
};
|
|
133
|
+
readonly error?: {
|
|
134
|
+
readonly message: string;
|
|
135
|
+
readonly details?: Readonly<unknown> | undefined;
|
|
136
|
+
} | undefined;
|
|
137
|
+
readonly uploadResult?: import("vue").DeepReadonly<import("vue").UnwrapRef<TUploadResult>> | undefined;
|
|
138
|
+
readonly isRemote?: boolean | undefined;
|
|
139
|
+
readonly remoteUrl?: string | undefined;
|
|
140
|
+
readonly meta: {
|
|
141
|
+
readonly [x: string]: Readonly<unknown>;
|
|
142
|
+
};
|
|
143
|
+
}[]>>;
|
|
144
|
+
totalProgress: import("vue").ComputedRef<number>;
|
|
145
|
+
addFiles: (newFiles: File[]) => Promise<(UploadFile<any> | null)[]>;
|
|
146
|
+
addFile: (file: File) => Promise<UploadFile<any> | null>;
|
|
147
|
+
onGetRemoteFile: (fn: GetRemoteFileFn) => void;
|
|
148
|
+
onUpload: (fn: UploadFn<TUploadResult>) => void;
|
|
149
|
+
removeFile: (fileId: string) => void;
|
|
150
|
+
removeFiles: (fileIds: string[]) => {
|
|
151
|
+
id: string;
|
|
152
|
+
name: string;
|
|
153
|
+
size: number;
|
|
154
|
+
mimeType: string;
|
|
155
|
+
data: {
|
|
156
|
+
readonly lastModified: number;
|
|
157
|
+
readonly name: string;
|
|
158
|
+
readonly webkitRelativePath: string;
|
|
159
|
+
readonly size: number;
|
|
160
|
+
readonly type: string;
|
|
161
|
+
arrayBuffer: {
|
|
162
|
+
(): Promise<ArrayBuffer>;
|
|
163
|
+
(): Promise<ArrayBuffer>;
|
|
164
|
+
};
|
|
165
|
+
bytes: {
|
|
166
|
+
(): Promise<Uint8Array<ArrayBuffer>>;
|
|
167
|
+
(): Promise<Uint8Array<ArrayBuffer>>;
|
|
168
|
+
};
|
|
169
|
+
slice: {
|
|
170
|
+
(start?: number, end?: number, contentType?: string): Blob;
|
|
171
|
+
(start?: number, end?: number, contentType?: string): Blob;
|
|
172
|
+
};
|
|
173
|
+
stream: {
|
|
174
|
+
(): ReadableStream<Uint8Array<ArrayBuffer>>;
|
|
175
|
+
(): ReadableStream<Uint8Array<ArrayBuffer>>;
|
|
176
|
+
};
|
|
177
|
+
text: {
|
|
178
|
+
(): Promise<string>;
|
|
179
|
+
(): Promise<string>;
|
|
180
|
+
};
|
|
181
|
+
} | {
|
|
182
|
+
readonly size: number;
|
|
183
|
+
readonly type: string;
|
|
184
|
+
arrayBuffer: {
|
|
185
|
+
(): Promise<ArrayBuffer>;
|
|
186
|
+
(): Promise<ArrayBuffer>;
|
|
187
|
+
};
|
|
188
|
+
bytes: {
|
|
189
|
+
(): Promise<Uint8Array<ArrayBuffer>>;
|
|
190
|
+
(): Promise<Uint8Array<ArrayBuffer>>;
|
|
191
|
+
};
|
|
192
|
+
slice: {
|
|
193
|
+
(start?: number, end?: number, contentType?: string): Blob;
|
|
194
|
+
(start?: number, end?: number, contentType?: string): Blob;
|
|
195
|
+
};
|
|
196
|
+
stream: {
|
|
197
|
+
(): ReadableStream<Uint8Array<ArrayBuffer>>;
|
|
198
|
+
(): ReadableStream<Uint8Array<ArrayBuffer>>;
|
|
199
|
+
};
|
|
200
|
+
text: {
|
|
201
|
+
(): Promise<string>;
|
|
202
|
+
(): Promise<string>;
|
|
203
|
+
};
|
|
204
|
+
};
|
|
205
|
+
status: import("./types.js").FileStatus;
|
|
206
|
+
preview?: string | undefined;
|
|
207
|
+
progress: {
|
|
208
|
+
percentage: number;
|
|
209
|
+
};
|
|
210
|
+
error?: {
|
|
211
|
+
message: string;
|
|
212
|
+
details?: unknown;
|
|
213
|
+
} | undefined;
|
|
214
|
+
uploadResult?: import("vue").UnwrapRef<TUploadResult> | undefined;
|
|
215
|
+
isRemote?: boolean | undefined;
|
|
216
|
+
remoteUrl?: string | undefined;
|
|
217
|
+
meta: Record<string, unknown>;
|
|
218
|
+
}[];
|
|
219
|
+
clearFiles: () => {
|
|
220
|
+
id: string;
|
|
221
|
+
name: string;
|
|
222
|
+
size: number;
|
|
223
|
+
mimeType: string;
|
|
224
|
+
data: {
|
|
225
|
+
readonly lastModified: number;
|
|
226
|
+
readonly name: string;
|
|
227
|
+
readonly webkitRelativePath: string;
|
|
228
|
+
readonly size: number;
|
|
229
|
+
readonly type: string;
|
|
230
|
+
arrayBuffer: {
|
|
231
|
+
(): Promise<ArrayBuffer>;
|
|
232
|
+
(): Promise<ArrayBuffer>;
|
|
233
|
+
};
|
|
234
|
+
bytes: {
|
|
235
|
+
(): Promise<Uint8Array<ArrayBuffer>>;
|
|
236
|
+
(): Promise<Uint8Array<ArrayBuffer>>;
|
|
237
|
+
};
|
|
238
|
+
slice: {
|
|
239
|
+
(start?: number, end?: number, contentType?: string): Blob;
|
|
240
|
+
(start?: number, end?: number, contentType?: string): Blob;
|
|
241
|
+
};
|
|
242
|
+
stream: {
|
|
243
|
+
(): ReadableStream<Uint8Array<ArrayBuffer>>;
|
|
244
|
+
(): ReadableStream<Uint8Array<ArrayBuffer>>;
|
|
245
|
+
};
|
|
246
|
+
text: {
|
|
247
|
+
(): Promise<string>;
|
|
248
|
+
(): Promise<string>;
|
|
249
|
+
};
|
|
250
|
+
} | {
|
|
251
|
+
readonly size: number;
|
|
252
|
+
readonly type: string;
|
|
253
|
+
arrayBuffer: {
|
|
254
|
+
(): Promise<ArrayBuffer>;
|
|
255
|
+
(): Promise<ArrayBuffer>;
|
|
256
|
+
};
|
|
257
|
+
bytes: {
|
|
258
|
+
(): Promise<Uint8Array<ArrayBuffer>>;
|
|
259
|
+
(): Promise<Uint8Array<ArrayBuffer>>;
|
|
260
|
+
};
|
|
261
|
+
slice: {
|
|
262
|
+
(start?: number, end?: number, contentType?: string): Blob;
|
|
263
|
+
(start?: number, end?: number, contentType?: string): Blob;
|
|
264
|
+
};
|
|
265
|
+
stream: {
|
|
266
|
+
(): ReadableStream<Uint8Array<ArrayBuffer>>;
|
|
267
|
+
(): ReadableStream<Uint8Array<ArrayBuffer>>;
|
|
268
|
+
};
|
|
269
|
+
text: {
|
|
270
|
+
(): Promise<string>;
|
|
271
|
+
(): Promise<string>;
|
|
272
|
+
};
|
|
273
|
+
};
|
|
274
|
+
status: import("./types.js").FileStatus;
|
|
275
|
+
preview?: string | undefined;
|
|
276
|
+
progress: {
|
|
277
|
+
percentage: number;
|
|
278
|
+
};
|
|
279
|
+
error?: {
|
|
280
|
+
message: string;
|
|
281
|
+
details?: unknown;
|
|
282
|
+
} | undefined;
|
|
283
|
+
uploadResult?: import("vue").UnwrapRef<TUploadResult> | undefined;
|
|
284
|
+
isRemote?: boolean | undefined;
|
|
285
|
+
remoteUrl?: string | undefined;
|
|
286
|
+
meta: Record<string, unknown>;
|
|
287
|
+
}[];
|
|
288
|
+
reorderFile: (oldIndex: number, newIndex: number) => void;
|
|
289
|
+
getFile: (fileId: string) => {
|
|
290
|
+
id: string;
|
|
291
|
+
name: string;
|
|
292
|
+
size: number;
|
|
293
|
+
mimeType: string;
|
|
294
|
+
data: {
|
|
295
|
+
readonly lastModified: number;
|
|
296
|
+
readonly name: string;
|
|
297
|
+
readonly webkitRelativePath: string;
|
|
298
|
+
readonly size: number;
|
|
299
|
+
readonly type: string;
|
|
300
|
+
arrayBuffer: {
|
|
301
|
+
(): Promise<ArrayBuffer>;
|
|
302
|
+
(): Promise<ArrayBuffer>;
|
|
303
|
+
};
|
|
304
|
+
bytes: {
|
|
305
|
+
(): Promise<Uint8Array<ArrayBuffer>>;
|
|
306
|
+
(): Promise<Uint8Array<ArrayBuffer>>;
|
|
307
|
+
};
|
|
308
|
+
slice: {
|
|
309
|
+
(start?: number, end?: number, contentType?: string): Blob;
|
|
310
|
+
(start?: number, end?: number, contentType?: string): Blob;
|
|
311
|
+
};
|
|
312
|
+
stream: {
|
|
313
|
+
(): ReadableStream<Uint8Array<ArrayBuffer>>;
|
|
314
|
+
(): ReadableStream<Uint8Array<ArrayBuffer>>;
|
|
315
|
+
};
|
|
316
|
+
text: {
|
|
317
|
+
(): Promise<string>;
|
|
318
|
+
(): Promise<string>;
|
|
319
|
+
};
|
|
320
|
+
} | {
|
|
321
|
+
readonly size: number;
|
|
322
|
+
readonly type: string;
|
|
323
|
+
arrayBuffer: {
|
|
324
|
+
(): Promise<ArrayBuffer>;
|
|
325
|
+
(): Promise<ArrayBuffer>;
|
|
326
|
+
};
|
|
327
|
+
bytes: {
|
|
328
|
+
(): Promise<Uint8Array<ArrayBuffer>>;
|
|
329
|
+
(): Promise<Uint8Array<ArrayBuffer>>;
|
|
330
|
+
};
|
|
331
|
+
slice: {
|
|
332
|
+
(start?: number, end?: number, contentType?: string): Blob;
|
|
333
|
+
(start?: number, end?: number, contentType?: string): Blob;
|
|
334
|
+
};
|
|
335
|
+
stream: {
|
|
336
|
+
(): ReadableStream<Uint8Array<ArrayBuffer>>;
|
|
337
|
+
(): ReadableStream<Uint8Array<ArrayBuffer>>;
|
|
338
|
+
};
|
|
339
|
+
text: {
|
|
340
|
+
(): Promise<string>;
|
|
341
|
+
(): Promise<string>;
|
|
342
|
+
};
|
|
343
|
+
};
|
|
344
|
+
status: import("./types.js").FileStatus;
|
|
345
|
+
preview?: string | undefined;
|
|
346
|
+
progress: {
|
|
347
|
+
percentage: number;
|
|
348
|
+
};
|
|
349
|
+
error?: {
|
|
350
|
+
message: string;
|
|
351
|
+
details?: unknown;
|
|
352
|
+
} | undefined;
|
|
353
|
+
uploadResult?: import("vue").UnwrapRef<TUploadResult> | undefined;
|
|
354
|
+
isRemote?: boolean | undefined;
|
|
355
|
+
remoteUrl?: string | undefined;
|
|
356
|
+
meta: Record<string, unknown>;
|
|
357
|
+
} | undefined;
|
|
358
|
+
upload: () => Promise<void>;
|
|
359
|
+
reset: () => void;
|
|
360
|
+
status: import("vue").Ref<UploadStatus, UploadStatus>;
|
|
361
|
+
updateFile: (fileId: string, updatedFile: Partial<UploadFile<TUploadResult>>) => void;
|
|
362
|
+
initializeExistingFiles: (initialFiles: Array<Partial<UploadFile>>) => Promise<void>;
|
|
363
|
+
addPlugin: <TOptions>(fn: PluginFn<TOptions>, pluginOptions: TOptions) => void;
|
|
364
|
+
on: {
|
|
365
|
+
<Key extends keyof UploaderEvents<TUploadResult>>(type: Key, handler: import("mitt").Handler<UploaderEvents<TUploadResult>[Key]>): void;
|
|
366
|
+
(type: "*", handler: import("mitt").WildcardHandler<UploaderEvents<TUploadResult>>): void;
|
|
367
|
+
};
|
|
368
|
+
};
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import mitt from "mitt";
|
|
2
|
+
import { computed, readonly, ref } from "vue";
|
|
3
|
+
import { ValidatorAllowedFileTypes, ValidatorMaxfileSize, ValidatorMaxFiles } from "./validators/index.js";
|
|
4
|
+
import { PluginThumbnailGenerator, PluginImageCompressor } from "./plugins/index.js";
|
|
5
|
+
function getExtension(fullFileName) {
|
|
6
|
+
const lastDot = fullFileName.lastIndexOf(".");
|
|
7
|
+
if (lastDot === -1 || lastDot === fullFileName.length - 1) {
|
|
8
|
+
throw new Error("Invalid file name");
|
|
9
|
+
}
|
|
10
|
+
return fullFileName.slice(lastDot + 1).toLocaleLowerCase();
|
|
11
|
+
}
|
|
12
|
+
const defaultOptions = {
|
|
13
|
+
plugins: [],
|
|
14
|
+
maxFileSize: false,
|
|
15
|
+
allowedFileTypes: false,
|
|
16
|
+
maxFiles: false,
|
|
17
|
+
thumbnails: false,
|
|
18
|
+
imageCompression: false,
|
|
19
|
+
autoProceed: false
|
|
20
|
+
};
|
|
21
|
+
export const useUploader = (_options = {}) => {
|
|
22
|
+
const options = { ...defaultOptions, ..._options };
|
|
23
|
+
const files = ref([]);
|
|
24
|
+
const emitter = mitt();
|
|
25
|
+
const status = ref("waiting");
|
|
26
|
+
let uploadFn = async () => {
|
|
27
|
+
throw new Error("No uploader configured");
|
|
28
|
+
};
|
|
29
|
+
let getRemoteFileFn = async () => {
|
|
30
|
+
throw new Error("Function to get remote file not configured");
|
|
31
|
+
};
|
|
32
|
+
const totalProgress = computed(() => {
|
|
33
|
+
if (files.value.length === 0) return 0;
|
|
34
|
+
const sum = files.value.reduce((acc, file) => acc + file.progress.percentage, 0);
|
|
35
|
+
return Math.round(sum / files.value.length);
|
|
36
|
+
});
|
|
37
|
+
const addPlugin = (fn, pluginOptions) => {
|
|
38
|
+
const plugin = fn({ files: files.value, options }, pluginOptions);
|
|
39
|
+
options.plugins?.push(plugin);
|
|
40
|
+
};
|
|
41
|
+
if (options.maxFiles !== false && options.maxFiles !== void 0) {
|
|
42
|
+
addPlugin(ValidatorMaxFiles, { maxFiles: options.maxFiles });
|
|
43
|
+
}
|
|
44
|
+
if (options.maxFileSize !== false && options.maxFileSize !== void 0) {
|
|
45
|
+
addPlugin(ValidatorMaxfileSize, { maxFileSize: options.maxFileSize });
|
|
46
|
+
}
|
|
47
|
+
if (options.allowedFileTypes !== false && options.allowedFileTypes !== void 0 && options.allowedFileTypes.length > 0) {
|
|
48
|
+
addPlugin(ValidatorAllowedFileTypes, { allowedFileTypes: options.allowedFileTypes });
|
|
49
|
+
}
|
|
50
|
+
if (options.thumbnails !== false && options.thumbnails !== void 0) {
|
|
51
|
+
const thumbOpts = options.thumbnails === true ? {} : options.thumbnails || {};
|
|
52
|
+
addPlugin(PluginThumbnailGenerator, {
|
|
53
|
+
width: thumbOpts.width ?? 128,
|
|
54
|
+
height: thumbOpts.height ?? 128,
|
|
55
|
+
quality: thumbOpts.quality ?? 1
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
if (options.imageCompression !== false && options.imageCompression !== void 0) {
|
|
59
|
+
const compressionOpts = options.imageCompression === true ? {} : options.imageCompression || {};
|
|
60
|
+
addPlugin(PluginImageCompressor, {
|
|
61
|
+
maxWidth: compressionOpts.maxWidth ?? 1920,
|
|
62
|
+
maxHeight: compressionOpts.maxHeight ?? 1920,
|
|
63
|
+
quality: compressionOpts.quality ?? 0.85,
|
|
64
|
+
outputFormat: compressionOpts.outputFormat ?? "auto",
|
|
65
|
+
minSizeToCompress: compressionOpts.minSizeToCompress ?? 1e5,
|
|
66
|
+
preserveMetadata: compressionOpts.preserveMetadata ?? true
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
emitter.on("upload:progress", ({ file, progress }) => {
|
|
70
|
+
updateFile(file.id, { progress: { percentage: progress } });
|
|
71
|
+
});
|
|
72
|
+
const updateFile = (fileId, updatedFile) => {
|
|
73
|
+
files.value = files.value.map((file) => file.id === fileId ? { ...file, ...updatedFile } : file);
|
|
74
|
+
};
|
|
75
|
+
const onUpload = (fn) => {
|
|
76
|
+
uploadFn = fn;
|
|
77
|
+
};
|
|
78
|
+
const onGetRemoteFile = (fn) => {
|
|
79
|
+
getRemoteFileFn = fn;
|
|
80
|
+
};
|
|
81
|
+
const initializeExistingFiles = async (initialFiles) => {
|
|
82
|
+
const initializedfiles = await Promise.all(
|
|
83
|
+
initialFiles.map(async (file) => {
|
|
84
|
+
if (!file.id) return null;
|
|
85
|
+
const { mimeType, size, remoteUrl } = await getRemoteFileFn(file.id);
|
|
86
|
+
const existingFile = {
|
|
87
|
+
...file,
|
|
88
|
+
id: file.id,
|
|
89
|
+
name: file.id,
|
|
90
|
+
data: new Blob(),
|
|
91
|
+
status: "complete",
|
|
92
|
+
progress: { percentage: 100 },
|
|
93
|
+
meta: {},
|
|
94
|
+
size,
|
|
95
|
+
mimeType,
|
|
96
|
+
remoteUrl
|
|
97
|
+
};
|
|
98
|
+
const processedFile = await runPluginStage("process", existingFile);
|
|
99
|
+
if (!processedFile) return null;
|
|
100
|
+
return processedFile;
|
|
101
|
+
})
|
|
102
|
+
);
|
|
103
|
+
const filteredFiles = initializedfiles.filter((f) => f !== null);
|
|
104
|
+
files.value = [...filteredFiles];
|
|
105
|
+
};
|
|
106
|
+
const addFile = async (file) => {
|
|
107
|
+
const id = `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
108
|
+
const extension = getExtension(file.name);
|
|
109
|
+
const uploadFile = {
|
|
110
|
+
id: `${id}.${extension}`,
|
|
111
|
+
progress: {
|
|
112
|
+
percentage: 0
|
|
113
|
+
},
|
|
114
|
+
name: file.name,
|
|
115
|
+
size: file.size,
|
|
116
|
+
status: "waiting",
|
|
117
|
+
mimeType: file.type,
|
|
118
|
+
data: file,
|
|
119
|
+
meta: {
|
|
120
|
+
extension
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
const validatedFile = await runPluginStage("validate", uploadFile);
|
|
124
|
+
if (!validatedFile) return null;
|
|
125
|
+
const processedFile = await runPluginStage("process", validatedFile);
|
|
126
|
+
if (!processedFile) return null;
|
|
127
|
+
files.value.push(processedFile);
|
|
128
|
+
emitter.emit("file:added", processedFile);
|
|
129
|
+
if (options.autoProceed) {
|
|
130
|
+
upload();
|
|
131
|
+
}
|
|
132
|
+
return processedFile;
|
|
133
|
+
};
|
|
134
|
+
const addFiles = (newFiles) => {
|
|
135
|
+
return Promise.all(newFiles.map((file) => addFile(file)));
|
|
136
|
+
};
|
|
137
|
+
const removeFile = (fileId) => {
|
|
138
|
+
const file = files.value.find((f) => f.id === fileId);
|
|
139
|
+
if (!file) return;
|
|
140
|
+
files.value = files.value.filter((f) => f.id !== fileId);
|
|
141
|
+
emitter.emit("file:removed", file);
|
|
142
|
+
};
|
|
143
|
+
const removeFiles = (fileIds) => {
|
|
144
|
+
const removedFiles = files.value.filter((f) => fileIds.includes(f.id));
|
|
145
|
+
files.value = files.value.filter((f) => !fileIds.includes(f.id));
|
|
146
|
+
removedFiles.forEach((file) => {
|
|
147
|
+
emitter.emit("file:removed", file);
|
|
148
|
+
});
|
|
149
|
+
return removedFiles;
|
|
150
|
+
};
|
|
151
|
+
const clearFiles = () => {
|
|
152
|
+
const allFiles = [...files.value];
|
|
153
|
+
files.value = [];
|
|
154
|
+
allFiles.forEach((file) => {
|
|
155
|
+
emitter.emit("file:removed", file);
|
|
156
|
+
});
|
|
157
|
+
return allFiles;
|
|
158
|
+
};
|
|
159
|
+
const reorderFile = (oldIndex, newIndex) => {
|
|
160
|
+
if (oldIndex === newIndex) {
|
|
161
|
+
if (import.meta.dev) {
|
|
162
|
+
console.warn("Cannot reorder file to the same index");
|
|
163
|
+
}
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
if (oldIndex < 0 || oldIndex >= files.value.length || newIndex < 0 || newIndex >= files.value.length) {
|
|
167
|
+
if (import.meta.dev) {
|
|
168
|
+
console.warn(`Cannot reorder file from ${oldIndex} to ${newIndex} since it is out of bounds`);
|
|
169
|
+
}
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
const filesCopy = [...files.value];
|
|
173
|
+
const [movedFile] = filesCopy.splice(oldIndex, 1);
|
|
174
|
+
filesCopy.splice(newIndex, 0, movedFile);
|
|
175
|
+
files.value = filesCopy;
|
|
176
|
+
emitter.emit("files:reorder", { oldIndex, newIndex });
|
|
177
|
+
};
|
|
178
|
+
const getFile = (fileId) => {
|
|
179
|
+
return files.value.find((f) => f.id === fileId);
|
|
180
|
+
};
|
|
181
|
+
const upload = async () => {
|
|
182
|
+
const filesToUpload = files.value.filter((f) => f.status === "waiting");
|
|
183
|
+
emitter.emit("upload:start", filesToUpload);
|
|
184
|
+
for (const file of filesToUpload) {
|
|
185
|
+
try {
|
|
186
|
+
updateFile(file.id, { status: "uploading" });
|
|
187
|
+
const onProgress = (progress) => {
|
|
188
|
+
updateFile(file.id, { progress: { percentage: progress } });
|
|
189
|
+
emitter.emit("upload:progress", { file, progress });
|
|
190
|
+
};
|
|
191
|
+
const uploadResult = await uploadFn(file, onProgress);
|
|
192
|
+
updateFile(file.id, { status: "complete", uploadResult });
|
|
193
|
+
} catch (err) {
|
|
194
|
+
const error = {
|
|
195
|
+
message: err instanceof Error ? err.message : "Unknown upload error",
|
|
196
|
+
details: {
|
|
197
|
+
fileName: file.name,
|
|
198
|
+
fileSize: file.size,
|
|
199
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
updateFile(file.id, { status: "error", error });
|
|
203
|
+
emitter.emit("file:error", { file, error });
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
const completed = files.value.filter((f) => f.status === "complete");
|
|
207
|
+
emitter.emit("upload:complete", completed);
|
|
208
|
+
};
|
|
209
|
+
const reset = () => {
|
|
210
|
+
files.value = [];
|
|
211
|
+
};
|
|
212
|
+
const callPluginHook = async (hook, stage, file, context) => {
|
|
213
|
+
switch (stage) {
|
|
214
|
+
case "validate":
|
|
215
|
+
await hook(file, context);
|
|
216
|
+
return file;
|
|
217
|
+
case "process":
|
|
218
|
+
case "complete":
|
|
219
|
+
if (!file) throw new Error("File is required for this hook type");
|
|
220
|
+
await hook(file, context);
|
|
221
|
+
return file;
|
|
222
|
+
default:
|
|
223
|
+
return file;
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
async function runPluginStage(stage, file) {
|
|
227
|
+
if (!options.plugins) return file;
|
|
228
|
+
const context = { files: files.value, options };
|
|
229
|
+
let currentFile = file;
|
|
230
|
+
for (const plugin of options.plugins) {
|
|
231
|
+
const hook = plugin.hooks[stage];
|
|
232
|
+
if (hook) {
|
|
233
|
+
try {
|
|
234
|
+
const result = await callPluginHook(hook, stage, currentFile, context);
|
|
235
|
+
if (!result) continue;
|
|
236
|
+
if (currentFile && "id" in result) {
|
|
237
|
+
currentFile = result;
|
|
238
|
+
}
|
|
239
|
+
} catch (error) {
|
|
240
|
+
if (currentFile) {
|
|
241
|
+
emitter.emit("file:error", { file: currentFile, error });
|
|
242
|
+
}
|
|
243
|
+
console.error(`Plugin ${plugin.id} ${stage} hook error:`, error);
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return currentFile;
|
|
249
|
+
}
|
|
250
|
+
return {
|
|
251
|
+
// State
|
|
252
|
+
files: readonly(files),
|
|
253
|
+
totalProgress,
|
|
254
|
+
// Core Methods
|
|
255
|
+
addFiles,
|
|
256
|
+
addFile,
|
|
257
|
+
onGetRemoteFile,
|
|
258
|
+
onUpload,
|
|
259
|
+
removeFile,
|
|
260
|
+
removeFiles,
|
|
261
|
+
clearFiles,
|
|
262
|
+
reorderFile,
|
|
263
|
+
getFile,
|
|
264
|
+
upload,
|
|
265
|
+
reset,
|
|
266
|
+
status,
|
|
267
|
+
updateFile,
|
|
268
|
+
initializeExistingFiles,
|
|
269
|
+
// Utilities
|
|
270
|
+
addPlugin,
|
|
271
|
+
// Events
|
|
272
|
+
on: emitter.on
|
|
273
|
+
};
|
|
274
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { PluginFn } from "../types.js";
|
|
2
|
+
interface ImageCompressorOptions {
|
|
3
|
+
/**
|
|
4
|
+
* Maximum width in pixels. Images wider than this will be scaled down.
|
|
5
|
+
* @default 1920
|
|
6
|
+
*/
|
|
7
|
+
maxWidth?: number;
|
|
8
|
+
/**
|
|
9
|
+
* Maximum height in pixels. Images taller than this will be scaled down.
|
|
10
|
+
* @default 1920
|
|
11
|
+
*/
|
|
12
|
+
maxHeight?: number;
|
|
13
|
+
/**
|
|
14
|
+
* JPEG/WebP quality (0-1). Lower values = smaller files but worse quality.
|
|
15
|
+
* @default 0.85
|
|
16
|
+
*/
|
|
17
|
+
quality?: number;
|
|
18
|
+
/**
|
|
19
|
+
* Output format. Use 'auto' to preserve original format.
|
|
20
|
+
* @default 'auto'
|
|
21
|
+
*/
|
|
22
|
+
outputFormat?: "jpeg" | "webp" | "png" | "auto";
|
|
23
|
+
/**
|
|
24
|
+
* Only compress images larger than this size (in bytes).
|
|
25
|
+
* @default 100000 (100KB)
|
|
26
|
+
*/
|
|
27
|
+
minSizeToCompress?: number;
|
|
28
|
+
/**
|
|
29
|
+
* Preserve EXIF metadata (orientation, etc)
|
|
30
|
+
* @default true
|
|
31
|
+
*/
|
|
32
|
+
preserveMetadata?: boolean;
|
|
33
|
+
}
|
|
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>;
|
|
49
|
+
export {};
|