@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.
@@ -0,0 +1,300 @@
1
+ import type {
2
+ UploadistaEvent,
3
+ UploadOptions,
4
+ } from "@uploadista/client-browser";
5
+ import type { UploadFile } from "@uploadista/core/types";
6
+ import { UploadEventType } from "@uploadista/core/types";
7
+ import { computed, onUnmounted, readonly, ref } from "vue";
8
+ import { useUploadistaClient } from "./useUploadistaClient";
9
+
10
+ // Re-export types for convenience
11
+ export type UploadInput = File | Blob;
12
+ export type ChunkMetrics = any;
13
+ export type PerformanceInsights = any;
14
+ export type UploadSessionMetrics = any;
15
+
16
+ export type UploadStatus =
17
+ | "idle"
18
+ | "uploading"
19
+ | "success"
20
+ | "error"
21
+ | "aborted";
22
+
23
+ export interface UploadState {
24
+ status: UploadStatus;
25
+ progress: number;
26
+ bytesUploaded: number;
27
+ totalBytes: number | null;
28
+ error: Error | null;
29
+ result: UploadFile | null;
30
+ }
31
+
32
+ export interface UploadMetrics {
33
+ /**
34
+ * Get performance insights from the upload client
35
+ */
36
+ getInsights: () => PerformanceInsights;
37
+
38
+ /**
39
+ * Export detailed metrics from the upload client
40
+ */
41
+ exportMetrics: () => {
42
+ session: Partial<UploadSessionMetrics>;
43
+ chunks: ChunkMetrics[];
44
+ insights: PerformanceInsights;
45
+ };
46
+
47
+ /**
48
+ * Get current network metrics
49
+ */
50
+ getNetworkMetrics: () => unknown;
51
+
52
+ /**
53
+ * Get current network condition
54
+ */
55
+ getNetworkCondition: () => unknown;
56
+
57
+ /**
58
+ * Reset all metrics
59
+ */
60
+ resetMetrics: () => void;
61
+ }
62
+
63
+ const initialState: UploadState = {
64
+ status: "idle",
65
+ progress: 0,
66
+ bytesUploaded: 0,
67
+ totalBytes: null,
68
+ error: null,
69
+ result: null,
70
+ };
71
+
72
+ /**
73
+ * Vue composable for managing individual file uploads with full state management.
74
+ * Provides upload progress tracking, error handling, abort functionality, and retry logic.
75
+ *
76
+ * Must be used within a component tree that has the Uploadista plugin installed.
77
+ *
78
+ * @param options - Upload configuration and event handlers
79
+ * @returns Upload state and control methods
80
+ *
81
+ * @example
82
+ * ```vue
83
+ * <script setup lang="ts">
84
+ * import { useUpload } from '@uploadista/vue';
85
+ *
86
+ * const upload = useUpload({
87
+ * onSuccess: (result) => console.log('Upload complete:', result),
88
+ * onError: (error) => console.error('Upload failed:', error),
89
+ * onProgress: (progress) => console.log('Progress:', progress + '%'),
90
+ * });
91
+ *
92
+ * const handleFileChange = (event: Event) => {
93
+ * const file = (event.target as HTMLInputElement).files?.[0];
94
+ * if (file) upload.upload(file);
95
+ * };
96
+ * </script>
97
+ *
98
+ * <template>
99
+ * <div>
100
+ * <input type="file" @change="handleFileChange" />
101
+ * <div v-if="upload.isUploading">Progress: {{ upload.state.progress }}%</div>
102
+ * <div v-if="upload.state.error">Error: {{ upload.state.error.message }}</div>
103
+ * <button v-if="upload.canRetry" @click="upload.retry">Retry</button>
104
+ * <button @click="upload.abort" :disabled="!upload.isUploading">Abort</button>
105
+ * </div>
106
+ * </template>
107
+ * ```
108
+ */
109
+ export function useUpload(options: UploadOptions = {}) {
110
+ const uploadistaClient = useUploadistaClient();
111
+ const state = ref<UploadState>({ ...initialState });
112
+ const abortController = ref<{ abort: () => void } | null>(null);
113
+ const lastFile = ref<UploadInput | null>(null);
114
+
115
+ const updateState = (update: Partial<UploadState>) => {
116
+ state.value = { ...state.value, ...update };
117
+ };
118
+
119
+ const reset = () => {
120
+ if (abortController.value) {
121
+ abortController.value.abort();
122
+ abortController.value = null;
123
+ }
124
+ state.value = { ...initialState };
125
+ lastFile.value = null;
126
+ };
127
+
128
+ const abort = () => {
129
+ if (abortController.value) {
130
+ abortController.value.abort();
131
+ abortController.value = null;
132
+ }
133
+
134
+ updateState({
135
+ status: "aborted",
136
+ });
137
+
138
+ options.onAbort?.();
139
+ };
140
+
141
+ const upload = (file: UploadInput) => {
142
+ // Reset any previous state but keep the file reference for retries
143
+ state.value = {
144
+ ...initialState,
145
+ status: "uploading",
146
+ totalBytes: file instanceof File ? file.size : null,
147
+ };
148
+
149
+ lastFile.value = file;
150
+
151
+ // Start the upload and handle the promise
152
+ const uploadPromise = uploadistaClient.client.upload(file, {
153
+ metadata: options.metadata,
154
+ uploadLengthDeferred: options.uploadLengthDeferred,
155
+ uploadSize: options.uploadSize,
156
+
157
+ onProgress: (
158
+ _uploadId: string,
159
+ bytesUploaded: number,
160
+ totalBytes: number | null,
161
+ ) => {
162
+ const progress = totalBytes
163
+ ? Math.round((bytesUploaded / totalBytes) * 100)
164
+ : 0;
165
+
166
+ updateState({
167
+ progress,
168
+ bytesUploaded,
169
+ totalBytes,
170
+ });
171
+
172
+ options.onProgress?.(progress, bytesUploaded, totalBytes);
173
+ },
174
+
175
+ onChunkComplete: (
176
+ chunkSize: number,
177
+ bytesAccepted: number,
178
+ bytesTotal: number | null,
179
+ ) => {
180
+ options.onChunkComplete?.(chunkSize, bytesAccepted, bytesTotal);
181
+ },
182
+
183
+ onSuccess: (result: UploadFile) => {
184
+ updateState({
185
+ status: "success",
186
+ result,
187
+ progress: 100,
188
+ bytesUploaded: result.size || 0,
189
+ totalBytes: result.size || null,
190
+ });
191
+
192
+ options.onSuccess?.(result);
193
+ abortController.value = null;
194
+ },
195
+
196
+ onError: (error: Error) => {
197
+ updateState({
198
+ status: "error",
199
+ error,
200
+ });
201
+
202
+ options.onError?.(error);
203
+ abortController.value = null;
204
+ },
205
+
206
+ onShouldRetry: options.onShouldRetry,
207
+ });
208
+
209
+ // Handle the promise to get the abort controller
210
+ uploadPromise
211
+ .then((controller) => {
212
+ abortController.value = controller;
213
+ })
214
+ .catch((error) => {
215
+ updateState({
216
+ status: "error",
217
+ error: error as Error,
218
+ });
219
+
220
+ options.onError?.(error as Error);
221
+ abortController.value = null;
222
+ });
223
+ };
224
+
225
+ const retry = () => {
226
+ if (
227
+ lastFile.value &&
228
+ (state.value.status === "error" || state.value.status === "aborted")
229
+ ) {
230
+ upload(lastFile.value);
231
+ }
232
+ };
233
+
234
+ // Subscribe to events from context (WebSocket events)
235
+ const unsubscribe = uploadistaClient.subscribeToEvents(
236
+ (event: UploadistaEvent) => {
237
+ console.log("useUpload - subscribeToEvents", event);
238
+ // Handle upload progress events
239
+ const uploadEvent = event as {
240
+ type: string;
241
+ data?: { id: string; progress: number; total: number };
242
+ };
243
+ if (
244
+ uploadEvent.type === UploadEventType.UPLOAD_PROGRESS &&
245
+ uploadEvent.data
246
+ ) {
247
+ const { progress: bytesUploaded, total: totalBytes } = uploadEvent.data;
248
+
249
+ // Update state for this upload
250
+ // Note: We update for all uploads since we don't track upload IDs in single upload mode
251
+ const progress = totalBytes
252
+ ? Math.round((bytesUploaded / totalBytes) * 100)
253
+ : 0;
254
+
255
+ // Only update if we're currently uploading
256
+ if (state.value.status === "uploading") {
257
+ updateState({
258
+ progress,
259
+ bytesUploaded,
260
+ totalBytes,
261
+ });
262
+
263
+ options.onProgress?.(progress, bytesUploaded, totalBytes);
264
+ }
265
+ }
266
+ },
267
+ );
268
+
269
+ // Cleanup on unmount
270
+ onUnmounted(() => {
271
+ unsubscribe();
272
+ });
273
+
274
+ const isUploading = computed(() => state.value.status === "uploading");
275
+ const canRetry = computed(
276
+ () =>
277
+ (state.value.status === "error" || state.value.status === "aborted") &&
278
+ lastFile.value !== null,
279
+ );
280
+
281
+ // Create metrics object that delegates to the upload client
282
+ const metrics: UploadMetrics = {
283
+ getInsights: () => uploadistaClient.client.getChunkingInsights(),
284
+ exportMetrics: () => uploadistaClient.client.exportMetrics(),
285
+ getNetworkMetrics: () => uploadistaClient.client.getNetworkMetrics(),
286
+ getNetworkCondition: () => uploadistaClient.client.getNetworkCondition(),
287
+ resetMetrics: () => uploadistaClient.client.resetMetrics(),
288
+ };
289
+
290
+ return {
291
+ state: readonly(state),
292
+ upload,
293
+ abort,
294
+ reset,
295
+ retry,
296
+ isUploading,
297
+ canRetry,
298
+ metrics,
299
+ };
300
+ }