@uploadista/vue 0.0.3
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/.turbo/turbo-check.log +240 -0
- package/LICENSE +21 -0
- package/README.md +554 -0
- package/package.json +36 -0
- package/src/components/FlowUploadList.vue +342 -0
- package/src/components/FlowUploadZone.vue +305 -0
- package/src/components/UploadList.vue +303 -0
- package/src/components/UploadZone.vue +254 -0
- package/src/components/index.ts +11 -0
- package/src/composables/index.ts +44 -0
- package/src/composables/plugin.ts +76 -0
- package/src/composables/useDragDrop.ts +343 -0
- package/src/composables/useFlowUpload.ts +431 -0
- package/src/composables/useMultiFlowUpload.ts +322 -0
- package/src/composables/useMultiUpload.ts +546 -0
- package/src/composables/useUpload.ts +300 -0
- package/src/composables/useUploadMetrics.ts +502 -0
- package/src/composables/useUploadistaClient.ts +73 -0
- package/src/index.ts +28 -0
- package/src/providers/UploadistaProvider.vue +69 -0
- package/src/providers/index.ts +1 -0
- package/src/utils/index.ts +156 -0
- package/src/utils/is-browser-file.ts +2 -0
- package/tsconfig.json +15 -0
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
import { onUnmounted, readonly, ref } from "vue";
|
|
2
|
+
import { useUploadistaClient } from "./useUploadistaClient";
|
|
3
|
+
|
|
4
|
+
// Types
|
|
5
|
+
// biome-ignore lint/suspicious/noExplicitAny: Placeholder for detailed metrics types
|
|
6
|
+
type ChunkMetrics = any;
|
|
7
|
+
// biome-ignore lint/suspicious/noExplicitAny: Placeholder for detailed metrics types
|
|
8
|
+
type PerformanceInsights = any;
|
|
9
|
+
// biome-ignore lint/suspicious/noExplicitAny: Placeholder for detailed metrics types
|
|
10
|
+
type UploadSessionMetrics = any;
|
|
11
|
+
|
|
12
|
+
export interface UploadMetrics {
|
|
13
|
+
/**
|
|
14
|
+
* Total bytes uploaded across all files
|
|
15
|
+
*/
|
|
16
|
+
totalBytesUploaded: number;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Total bytes to upload across all files
|
|
20
|
+
*/
|
|
21
|
+
totalBytes: number;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Overall upload speed in bytes per second
|
|
25
|
+
*/
|
|
26
|
+
averageSpeed: number;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Current upload speed in bytes per second
|
|
30
|
+
*/
|
|
31
|
+
currentSpeed: number;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Estimated time remaining in milliseconds
|
|
35
|
+
*/
|
|
36
|
+
estimatedTimeRemaining: number | null;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Total number of files being tracked
|
|
40
|
+
*/
|
|
41
|
+
totalFiles: number;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Number of files completed
|
|
45
|
+
*/
|
|
46
|
+
completedFiles: number;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Number of files currently uploading
|
|
50
|
+
*/
|
|
51
|
+
activeUploads: number;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Overall progress as percentage (0-100)
|
|
55
|
+
*/
|
|
56
|
+
progress: number;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Peak upload speed achieved
|
|
60
|
+
*/
|
|
61
|
+
peakSpeed: number;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Start time of the first upload
|
|
65
|
+
*/
|
|
66
|
+
startTime: number | null;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* End time of the last completed upload
|
|
70
|
+
*/
|
|
71
|
+
endTime: number | null;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Total duration of all uploads
|
|
75
|
+
*/
|
|
76
|
+
totalDuration: number | null;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Detailed performance insights from the upload client
|
|
80
|
+
*/
|
|
81
|
+
insights: PerformanceInsights;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Session metrics for completed uploads
|
|
85
|
+
*/
|
|
86
|
+
sessionMetrics: Partial<UploadSessionMetrics>[];
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Detailed chunk metrics from recent uploads
|
|
90
|
+
*/
|
|
91
|
+
chunkMetrics: ChunkMetrics[];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export interface FileUploadMetrics {
|
|
95
|
+
id: string;
|
|
96
|
+
filename: string;
|
|
97
|
+
size: number;
|
|
98
|
+
bytesUploaded: number;
|
|
99
|
+
progress: number;
|
|
100
|
+
speed: number;
|
|
101
|
+
startTime: number;
|
|
102
|
+
endTime: number | null;
|
|
103
|
+
duration: number | null;
|
|
104
|
+
isComplete: boolean;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export interface UseUploadMetricsOptions {
|
|
108
|
+
/**
|
|
109
|
+
* Interval for calculating current speed (in milliseconds)
|
|
110
|
+
*/
|
|
111
|
+
speedCalculationInterval?: number;
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Number of speed samples to keep for average calculation
|
|
115
|
+
*/
|
|
116
|
+
speedSampleSize?: number;
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Called when metrics are updated
|
|
120
|
+
*/
|
|
121
|
+
onMetricsUpdate?: (metrics: UploadMetrics) => void;
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Called when a file upload starts
|
|
125
|
+
*/
|
|
126
|
+
onFileStart?: (fileMetrics: FileUploadMetrics) => void;
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Called when a file upload progresses
|
|
130
|
+
*/
|
|
131
|
+
onFileProgress?: (fileMetrics: FileUploadMetrics) => void;
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Called when a file upload completes
|
|
135
|
+
*/
|
|
136
|
+
onFileComplete?: (fileMetrics: FileUploadMetrics) => void;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const initialMetrics: UploadMetrics = {
|
|
140
|
+
totalBytesUploaded: 0,
|
|
141
|
+
totalBytes: 0,
|
|
142
|
+
averageSpeed: 0,
|
|
143
|
+
currentSpeed: 0,
|
|
144
|
+
estimatedTimeRemaining: null,
|
|
145
|
+
totalFiles: 0,
|
|
146
|
+
completedFiles: 0,
|
|
147
|
+
activeUploads: 0,
|
|
148
|
+
progress: 0,
|
|
149
|
+
peakSpeed: 0,
|
|
150
|
+
startTime: null,
|
|
151
|
+
endTime: null,
|
|
152
|
+
totalDuration: null,
|
|
153
|
+
insights: {
|
|
154
|
+
overallEfficiency: 0,
|
|
155
|
+
chunkingEffectiveness: 0,
|
|
156
|
+
networkStability: 0,
|
|
157
|
+
recommendations: [],
|
|
158
|
+
optimalChunkSizeRange: { min: 256 * 1024, max: 2 * 1024 * 1024 },
|
|
159
|
+
},
|
|
160
|
+
sessionMetrics: [],
|
|
161
|
+
chunkMetrics: [],
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Vue composable for tracking detailed upload metrics and performance statistics.
|
|
166
|
+
* Provides comprehensive monitoring of upload progress, speed, and timing data.
|
|
167
|
+
*
|
|
168
|
+
* @param options - Configuration and event handlers
|
|
169
|
+
* @returns Upload metrics state and control methods
|
|
170
|
+
*
|
|
171
|
+
* @example
|
|
172
|
+
* ```vue
|
|
173
|
+
* <script setup lang="ts">
|
|
174
|
+
* import { useUploadMetrics } from '@uploadista/vue';
|
|
175
|
+
*
|
|
176
|
+
* const uploadMetrics = useUploadMetrics({
|
|
177
|
+
* speedCalculationInterval: 1000, // Update speed every second
|
|
178
|
+
* speedSampleSize: 10, // Keep last 10 speed samples for average
|
|
179
|
+
* onMetricsUpdate: (metrics) => {
|
|
180
|
+
* console.log(`Overall progress: ${metrics.progress}%`);
|
|
181
|
+
* console.log(`Speed: ${(metrics.currentSpeed / 1024).toFixed(1)} KB/s`);
|
|
182
|
+
* console.log(`ETA: ${metrics.estimatedTimeRemaining}ms`);
|
|
183
|
+
* },
|
|
184
|
+
* onFileComplete: (fileMetrics) => {
|
|
185
|
+
* console.log(`${fileMetrics.filename} completed in ${fileMetrics.duration}ms`);
|
|
186
|
+
* },
|
|
187
|
+
* });
|
|
188
|
+
*
|
|
189
|
+
* // Start tracking a file
|
|
190
|
+
* const handleFileStart = (file: File) => {
|
|
191
|
+
* uploadMetrics.startFileUpload(file.name, file.name, file.size);
|
|
192
|
+
* };
|
|
193
|
+
*
|
|
194
|
+
* // Update progress during upload
|
|
195
|
+
* const handleProgress = (fileId: string, bytesUploaded: number) => {
|
|
196
|
+
* uploadMetrics.updateFileProgress(fileId, bytesUploaded);
|
|
197
|
+
* };
|
|
198
|
+
* </script>
|
|
199
|
+
*
|
|
200
|
+
* <template>
|
|
201
|
+
* <div>
|
|
202
|
+
* <div>Overall Progress: {{ uploadMetrics.metrics.progress }}%</div>
|
|
203
|
+
* <div>Speed: {{ (uploadMetrics.metrics.currentSpeed / 1024).toFixed(1) }} KB/s</div>
|
|
204
|
+
* <div>Files: {{ uploadMetrics.metrics.completedFiles }}/{{ uploadMetrics.metrics.totalFiles }}</div>
|
|
205
|
+
*
|
|
206
|
+
* <div v-if="uploadMetrics.metrics.estimatedTimeRemaining">
|
|
207
|
+
* ETA: {{ Math.round(uploadMetrics.metrics.estimatedTimeRemaining / 1000) }}s
|
|
208
|
+
* </div>
|
|
209
|
+
*
|
|
210
|
+
* <div v-for="file in uploadMetrics.fileMetrics" :key="file.id">
|
|
211
|
+
* {{ file.filename }}: {{ file.progress }}% ({{ (file.speed / 1024).toFixed(1) }} KB/s)
|
|
212
|
+
* </div>
|
|
213
|
+
* </div>
|
|
214
|
+
* </template>
|
|
215
|
+
* ```
|
|
216
|
+
*/
|
|
217
|
+
export function useUploadMetrics(options: UseUploadMetricsOptions = {}) {
|
|
218
|
+
const {
|
|
219
|
+
speedCalculationInterval = 1000,
|
|
220
|
+
speedSampleSize = 10,
|
|
221
|
+
onMetricsUpdate,
|
|
222
|
+
onFileStart,
|
|
223
|
+
onFileProgress,
|
|
224
|
+
onFileComplete,
|
|
225
|
+
} = options;
|
|
226
|
+
|
|
227
|
+
const uploadClient = useUploadistaClient();
|
|
228
|
+
|
|
229
|
+
const metrics = ref<UploadMetrics>({ ...initialMetrics });
|
|
230
|
+
const fileMetrics = ref<FileUploadMetrics[]>([]);
|
|
231
|
+
|
|
232
|
+
const speedSamples = ref<Array<{ time: number; bytes: number }>>([]);
|
|
233
|
+
const lastUpdate = ref<number>(0);
|
|
234
|
+
const interval = ref<ReturnType<typeof setInterval> | null>(null);
|
|
235
|
+
|
|
236
|
+
const calculateSpeed = (currentTime: number, totalBytesUploaded: number) => {
|
|
237
|
+
const sample = { time: currentTime, bytes: totalBytesUploaded };
|
|
238
|
+
speedSamples.value.push(sample);
|
|
239
|
+
|
|
240
|
+
// Keep only recent samples
|
|
241
|
+
if (speedSamples.value.length > speedSampleSize) {
|
|
242
|
+
speedSamples.value = speedSamples.value.slice(-speedSampleSize);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Calculate current speed (bytes per second)
|
|
246
|
+
let currentSpeed = 0;
|
|
247
|
+
if (speedSamples.value.length >= 2) {
|
|
248
|
+
const recent = speedSamples.value[speedSamples.value.length - 1];
|
|
249
|
+
const previous = speedSamples.value[speedSamples.value.length - 2];
|
|
250
|
+
if (recent && previous) {
|
|
251
|
+
const timeDiff = (recent.time - previous.time) / 1000; // Convert to seconds
|
|
252
|
+
const bytesDiff = recent.bytes - previous.bytes;
|
|
253
|
+
currentSpeed = timeDiff > 0 ? bytesDiff / timeDiff : 0;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Calculate average speed
|
|
258
|
+
let averageSpeed = 0;
|
|
259
|
+
if (speedSamples.value.length >= 2) {
|
|
260
|
+
const first = speedSamples.value[0];
|
|
261
|
+
const last = speedSamples.value[speedSamples.value.length - 1];
|
|
262
|
+
if (first && last) {
|
|
263
|
+
const totalTime = (last.time - first.time) / 1000; // Convert to seconds
|
|
264
|
+
const totalBytes = last.bytes - first.bytes;
|
|
265
|
+
averageSpeed = totalTime > 0 ? totalBytes / totalTime : 0;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return { currentSpeed, averageSpeed };
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
const updateMetrics = () => {
|
|
273
|
+
const now = Date.now();
|
|
274
|
+
|
|
275
|
+
// Calculate totals from file metrics
|
|
276
|
+
const totalBytes = fileMetrics.value.reduce(
|
|
277
|
+
(sum, file) => sum + file.size,
|
|
278
|
+
0,
|
|
279
|
+
);
|
|
280
|
+
const totalBytesUploaded = fileMetrics.value.reduce(
|
|
281
|
+
(sum, file) => sum + file.bytesUploaded,
|
|
282
|
+
0,
|
|
283
|
+
);
|
|
284
|
+
const completedFiles = fileMetrics.value.filter(
|
|
285
|
+
(file) => file.isComplete,
|
|
286
|
+
).length;
|
|
287
|
+
const activeUploads = fileMetrics.value.filter(
|
|
288
|
+
(file) => !file.isComplete && file.bytesUploaded > 0,
|
|
289
|
+
).length;
|
|
290
|
+
|
|
291
|
+
// Calculate speeds
|
|
292
|
+
const { currentSpeed, averageSpeed } = calculateSpeed(
|
|
293
|
+
now,
|
|
294
|
+
totalBytesUploaded,
|
|
295
|
+
);
|
|
296
|
+
|
|
297
|
+
// Calculate progress
|
|
298
|
+
const progress =
|
|
299
|
+
totalBytes > 0 ? Math.round((totalBytesUploaded / totalBytes) * 100) : 0;
|
|
300
|
+
|
|
301
|
+
// Calculate estimated time remaining
|
|
302
|
+
let estimatedTimeRemaining: number | null = null;
|
|
303
|
+
if (currentSpeed > 0) {
|
|
304
|
+
const remainingBytes = totalBytes - totalBytesUploaded;
|
|
305
|
+
estimatedTimeRemaining = (remainingBytes / currentSpeed) * 1000; // Convert to milliseconds
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Find start and end times
|
|
309
|
+
const activeTimes = fileMetrics.value.filter((file) => file.startTime > 0);
|
|
310
|
+
const startTime =
|
|
311
|
+
activeTimes.length > 0
|
|
312
|
+
? Math.min(...activeTimes.map((file) => file.startTime))
|
|
313
|
+
: null;
|
|
314
|
+
|
|
315
|
+
const completedTimes = fileMetrics.value.filter(
|
|
316
|
+
(file) => file.endTime !== null,
|
|
317
|
+
);
|
|
318
|
+
const endTime =
|
|
319
|
+
completedTimes.length > 0 && completedFiles === fileMetrics.value.length
|
|
320
|
+
? Math.max(
|
|
321
|
+
...(completedTimes
|
|
322
|
+
.map((file) => file.endTime)
|
|
323
|
+
.filter((time) => time !== null) as number[]),
|
|
324
|
+
)
|
|
325
|
+
: null;
|
|
326
|
+
|
|
327
|
+
const totalDuration = startTime && endTime ? endTime - startTime : null;
|
|
328
|
+
|
|
329
|
+
const newMetrics: UploadMetrics = {
|
|
330
|
+
totalBytesUploaded,
|
|
331
|
+
totalBytes,
|
|
332
|
+
averageSpeed,
|
|
333
|
+
currentSpeed,
|
|
334
|
+
estimatedTimeRemaining,
|
|
335
|
+
totalFiles: fileMetrics.value.length,
|
|
336
|
+
completedFiles,
|
|
337
|
+
activeUploads,
|
|
338
|
+
progress,
|
|
339
|
+
peakSpeed: Math.max(metrics.value.peakSpeed, currentSpeed),
|
|
340
|
+
startTime,
|
|
341
|
+
endTime,
|
|
342
|
+
totalDuration,
|
|
343
|
+
insights: uploadClient.client.getChunkingInsights(),
|
|
344
|
+
sessionMetrics: [uploadClient.client.exportMetrics().session],
|
|
345
|
+
chunkMetrics: uploadClient.client.exportMetrics().chunks,
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
metrics.value = newMetrics;
|
|
349
|
+
onMetricsUpdate?.(newMetrics);
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
// Set up periodic speed calculations
|
|
353
|
+
const setupSpeedCalculation = () => {
|
|
354
|
+
if (interval.value) {
|
|
355
|
+
clearInterval(interval.value);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
interval.value = setInterval(() => {
|
|
359
|
+
if (
|
|
360
|
+
fileMetrics.value.some(
|
|
361
|
+
(file) => !file.isComplete && file.bytesUploaded > 0,
|
|
362
|
+
)
|
|
363
|
+
) {
|
|
364
|
+
updateMetrics();
|
|
365
|
+
}
|
|
366
|
+
}, speedCalculationInterval);
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
const startFileUpload = (id: string, filename: string, size: number) => {
|
|
370
|
+
const now = Date.now();
|
|
371
|
+
|
|
372
|
+
const fileMetric: FileUploadMetrics = {
|
|
373
|
+
id,
|
|
374
|
+
filename,
|
|
375
|
+
size,
|
|
376
|
+
bytesUploaded: 0,
|
|
377
|
+
progress: 0,
|
|
378
|
+
speed: 0,
|
|
379
|
+
startTime: now,
|
|
380
|
+
endTime: null,
|
|
381
|
+
duration: null,
|
|
382
|
+
isComplete: false,
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
const existing = fileMetrics.value.find((file) => file.id === id);
|
|
386
|
+
if (existing) {
|
|
387
|
+
fileMetrics.value = fileMetrics.value.map((file) =>
|
|
388
|
+
file.id === id ? fileMetric : file,
|
|
389
|
+
);
|
|
390
|
+
} else {
|
|
391
|
+
fileMetrics.value = [...fileMetrics.value, fileMetric];
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
onFileStart?.(fileMetric);
|
|
395
|
+
|
|
396
|
+
// Start speed calculation if this is the first active upload
|
|
397
|
+
if (fileMetrics.value.filter((file) => !file.isComplete).length === 1) {
|
|
398
|
+
setupSpeedCalculation();
|
|
399
|
+
}
|
|
400
|
+
};
|
|
401
|
+
|
|
402
|
+
const updateFileProgress = (id: string, bytesUploaded: number) => {
|
|
403
|
+
const now = Date.now();
|
|
404
|
+
|
|
405
|
+
fileMetrics.value = fileMetrics.value.map((file) => {
|
|
406
|
+
if (file.id !== id) return file;
|
|
407
|
+
|
|
408
|
+
const timeDiff = (now - file.startTime) / 1000; // seconds
|
|
409
|
+
const speed = timeDiff > 0 ? bytesUploaded / timeDiff : 0;
|
|
410
|
+
const progress =
|
|
411
|
+
file.size > 0 ? Math.round((bytesUploaded / file.size) * 100) : 0;
|
|
412
|
+
|
|
413
|
+
const updatedFile = {
|
|
414
|
+
...file,
|
|
415
|
+
bytesUploaded,
|
|
416
|
+
progress,
|
|
417
|
+
speed,
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
onFileProgress?.(updatedFile);
|
|
421
|
+
return updatedFile;
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
// Trigger metrics update
|
|
425
|
+
setTimeout(updateMetrics, 0);
|
|
426
|
+
};
|
|
427
|
+
|
|
428
|
+
const completeFileUpload = (id: string) => {
|
|
429
|
+
const now = Date.now();
|
|
430
|
+
|
|
431
|
+
fileMetrics.value = fileMetrics.value.map((file) => {
|
|
432
|
+
if (file.id !== id) return file;
|
|
433
|
+
|
|
434
|
+
const duration = now - file.startTime;
|
|
435
|
+
const speed = duration > 0 ? (file.size / duration) * 1000 : 0; // bytes per second
|
|
436
|
+
|
|
437
|
+
const completedFile = {
|
|
438
|
+
...file,
|
|
439
|
+
bytesUploaded: file.size,
|
|
440
|
+
progress: 100,
|
|
441
|
+
speed,
|
|
442
|
+
endTime: now,
|
|
443
|
+
duration,
|
|
444
|
+
isComplete: true,
|
|
445
|
+
};
|
|
446
|
+
|
|
447
|
+
onFileComplete?.(completedFile);
|
|
448
|
+
return completedFile;
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
// Trigger metrics update
|
|
452
|
+
setTimeout(updateMetrics, 0);
|
|
453
|
+
};
|
|
454
|
+
|
|
455
|
+
const removeFile = (id: string) => {
|
|
456
|
+
fileMetrics.value = fileMetrics.value.filter((file) => file.id !== id);
|
|
457
|
+
setTimeout(updateMetrics, 0);
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
const reset = () => {
|
|
461
|
+
if (interval.value) {
|
|
462
|
+
clearInterval(interval.value);
|
|
463
|
+
interval.value = null;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
metrics.value = { ...initialMetrics };
|
|
467
|
+
fileMetrics.value = [];
|
|
468
|
+
speedSamples.value = [];
|
|
469
|
+
lastUpdate.value = 0;
|
|
470
|
+
};
|
|
471
|
+
|
|
472
|
+
const getFileMetrics = (id: string) => {
|
|
473
|
+
return fileMetrics.value.find((file) => file.id === id);
|
|
474
|
+
};
|
|
475
|
+
|
|
476
|
+
const exportMetrics = () => {
|
|
477
|
+
return {
|
|
478
|
+
overall: metrics.value,
|
|
479
|
+
files: fileMetrics.value,
|
|
480
|
+
exportTime: Date.now(),
|
|
481
|
+
};
|
|
482
|
+
};
|
|
483
|
+
|
|
484
|
+
// Cleanup on unmount
|
|
485
|
+
onUnmounted(() => {
|
|
486
|
+
if (interval.value) {
|
|
487
|
+
clearInterval(interval.value);
|
|
488
|
+
}
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
return {
|
|
492
|
+
metrics: readonly(metrics),
|
|
493
|
+
fileMetrics: readonly(fileMetrics),
|
|
494
|
+
startFileUpload,
|
|
495
|
+
updateFileProgress,
|
|
496
|
+
completeFileUpload,
|
|
497
|
+
removeFile,
|
|
498
|
+
reset,
|
|
499
|
+
getFileMetrics,
|
|
500
|
+
exportMetrics,
|
|
501
|
+
};
|
|
502
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { UploadistaEvent } from "@uploadista/client-browser";
|
|
2
|
+
import { inject, type Ref } from "vue";
|
|
3
|
+
import {
|
|
4
|
+
UPLOADISTA_CLIENT_KEY,
|
|
5
|
+
UPLOADISTA_EVENT_SUBSCRIBERS_KEY,
|
|
6
|
+
} from "./plugin";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Access the Uploadista client instance from the plugin or provider.
|
|
10
|
+
* Must be used within a component tree that has the Uploadista plugin or provider installed.
|
|
11
|
+
*
|
|
12
|
+
* @returns Uploadista client instance with event subscription
|
|
13
|
+
* @throws Error if used outside of Uploadista plugin/provider context
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```vue
|
|
17
|
+
* <script setup lang="ts">
|
|
18
|
+
* import { useUploadistaClient } from '@uploadista/vue';
|
|
19
|
+
*
|
|
20
|
+
* const { client, subscribeToEvents } = useUploadistaClient();
|
|
21
|
+
*
|
|
22
|
+
* // Subscribe to all events
|
|
23
|
+
* const unsubscribe = subscribeToEvents((event) => {
|
|
24
|
+
* console.log('Upload event:', event);
|
|
25
|
+
* });
|
|
26
|
+
*
|
|
27
|
+
* // Clean up on unmount
|
|
28
|
+
* onUnmounted(() => {
|
|
29
|
+
* unsubscribe();
|
|
30
|
+
* });
|
|
31
|
+
* </script>
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export function useUploadistaClient() {
|
|
35
|
+
const client = inject(UPLOADISTA_CLIENT_KEY);
|
|
36
|
+
|
|
37
|
+
if (!client) {
|
|
38
|
+
throw new Error(
|
|
39
|
+
"useUploadistaClient must be used within a component tree that has the Uploadista plugin or provider installed. " +
|
|
40
|
+
"Make sure to either use app.use(createUploadistaPlugin({ ... })) in your main app file, " +
|
|
41
|
+
"or wrap your component tree with <UploadistaProvider>.",
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Try to get the shared event subscribers from the provider
|
|
46
|
+
const eventSubscribersRef = inject<
|
|
47
|
+
Ref<Set<(event: UploadistaEvent) => void>> | undefined
|
|
48
|
+
>(UPLOADISTA_EVENT_SUBSCRIBERS_KEY);
|
|
49
|
+
|
|
50
|
+
const subscribeToEvents = (handler: (event: UploadistaEvent) => void) => {
|
|
51
|
+
if (!eventSubscribersRef) {
|
|
52
|
+
console.warn(
|
|
53
|
+
"subscribeToEvents called but no event subscribers provided. Events will not be dispatched. " +
|
|
54
|
+
"Make sure to use UploadistaProvider or createUploadistaPlugin with proper configuration.",
|
|
55
|
+
);
|
|
56
|
+
return () => {
|
|
57
|
+
// No-op unsubscribe if subscribers aren't available
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
eventSubscribersRef.value.add(handler);
|
|
62
|
+
return () => {
|
|
63
|
+
eventSubscribersRef.value.delete(handler);
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
client,
|
|
69
|
+
subscribeToEvents,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export type UseUploadistaClientReturn = ReturnType<typeof useUploadistaClient>;
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Uploadista Vue Client
|
|
3
|
+
*
|
|
4
|
+
* Vue 3 composables and components for file uploads with the Uploadista platform.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```typescript
|
|
8
|
+
* import { createUploadistaPlugin } from '@uploadista/vue'
|
|
9
|
+
* import { UploadZone, FlowUploadZone } from '@uploadista/vue'
|
|
10
|
+
*
|
|
11
|
+
* // Install plugin in your Vue app
|
|
12
|
+
* const app = createApp(App)
|
|
13
|
+
* app.use(createUploadistaPlugin({
|
|
14
|
+
* client: uploadClient
|
|
15
|
+
* }))
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
// Re-export all components
|
|
20
|
+
export * from "./components";
|
|
21
|
+
// Re-export all composables
|
|
22
|
+
export * from "./composables";
|
|
23
|
+
// Re-export the plugin
|
|
24
|
+
export * from "./composables/plugin";
|
|
25
|
+
|
|
26
|
+
export * from "./providers";
|
|
27
|
+
// Re-export utilities
|
|
28
|
+
export * from "./utils";
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import {
|
|
3
|
+
createUploadistaClient,
|
|
4
|
+
type UploadistaEvent,
|
|
5
|
+
} from "@uploadista/client-browser";
|
|
6
|
+
import {
|
|
7
|
+
UPLOADISTA_CLIENT_KEY,
|
|
8
|
+
UPLOADISTA_EVENT_SUBSCRIBERS_KEY,
|
|
9
|
+
} from "@uploadista/vue";
|
|
10
|
+
import { onBeforeUnmount, provide, ref } from "vue";
|
|
11
|
+
|
|
12
|
+
const props = withDefaults(
|
|
13
|
+
defineProps<{
|
|
14
|
+
serverUrl: string;
|
|
15
|
+
storageId?: string;
|
|
16
|
+
uploadistaBasePath?: string;
|
|
17
|
+
chunkSize?: number;
|
|
18
|
+
parallelUploads?: number;
|
|
19
|
+
storeFingerprintForResuming?: boolean;
|
|
20
|
+
onEvent?: (event: UploadistaEvent) => void;
|
|
21
|
+
}>(),
|
|
22
|
+
{
|
|
23
|
+
storageId: "local",
|
|
24
|
+
uploadistaBasePath: "uploadista",
|
|
25
|
+
chunkSize: 1024 * 1024,
|
|
26
|
+
parallelUploads: 1,
|
|
27
|
+
storeFingerprintForResuming: true,
|
|
28
|
+
},
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
const emit = defineEmits<{
|
|
32
|
+
/**
|
|
33
|
+
* Emitted when the underlying client dispatches an event.
|
|
34
|
+
*/
|
|
35
|
+
(e: "event", event: UploadistaEvent): void;
|
|
36
|
+
}>();
|
|
37
|
+
|
|
38
|
+
// Create a shared set of event subscribers
|
|
39
|
+
const eventSubscribers = ref(new Set<(event: UploadistaEvent) => void>());
|
|
40
|
+
|
|
41
|
+
const client = createUploadistaClient({
|
|
42
|
+
baseUrl: props.serverUrl,
|
|
43
|
+
storageId: props.storageId,
|
|
44
|
+
uploadistaBasePath: props.uploadistaBasePath,
|
|
45
|
+
chunkSize: props.chunkSize,
|
|
46
|
+
parallelUploads: props.parallelUploads,
|
|
47
|
+
storeFingerprintForResuming: props.storeFingerprintForResuming,
|
|
48
|
+
onEvent: (event) => {
|
|
49
|
+
// Dispatch to all subscribers registered via subscribeToEvents
|
|
50
|
+
eventSubscribers.value.forEach((subscriber) => {
|
|
51
|
+
subscriber(event);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
props.onEvent?.(event);
|
|
55
|
+
emit("event", event);
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
provide(UPLOADISTA_CLIENT_KEY, client);
|
|
60
|
+
provide(UPLOADISTA_EVENT_SUBSCRIBERS_KEY, eventSubscribers);
|
|
61
|
+
|
|
62
|
+
onBeforeUnmount(() => {
|
|
63
|
+
client.closeAllWebSockets();
|
|
64
|
+
});
|
|
65
|
+
</script>
|
|
66
|
+
|
|
67
|
+
<template>
|
|
68
|
+
<slot />
|
|
69
|
+
</template>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as UploadistaProvider } from "./UploadistaProvider.vue";
|