@uploadista/vue 0.0.20-beta.2 → 0.0.20-beta.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/dist/components/index.d.mts +3 -3
- package/dist/components/index.mjs +1 -1
- package/dist/components-Bhroc6MN.mjs +2 -0
- package/dist/components-Bhroc6MN.mjs.map +1 -0
- package/dist/{components-B731f1UD.css → components-CskPs6sR.css} +1 -49
- package/dist/components-CskPs6sR.css.map +1 -0
- package/dist/composables/index.d.mts +2 -2
- package/dist/composables/index.mjs +1 -1
- package/dist/composables-7rR8DrBp.mjs +2 -0
- package/dist/composables-7rR8DrBp.mjs.map +1 -0
- package/dist/{index-DO_Tr6T7.d.mts → index-5yA_2yjS.d.mts} +15 -86
- package/dist/index-5yA_2yjS.d.mts.map +1 -0
- package/dist/{index-B1BUbFNh.d.mts → index-A87M4l0R.d.mts} +19 -219
- package/dist/index-A87M4l0R.d.mts.map +1 -0
- package/dist/{index-CRkCOgUr.d.mts → index-cDdde3bt.d.mts} +4 -4
- package/dist/{index-CRkCOgUr.d.mts.map → index-cDdde3bt.d.mts.map} +1 -1
- package/dist/index.d.mts +4 -4
- package/dist/index.mjs +1 -1
- package/dist/providers/index.d.mts +1 -1
- package/package.json +7 -7
- package/src/components/index.ts +0 -1
- package/src/composables/index.ts +1 -1
- package/dist/components-B731f1UD.css.map +0 -1
- package/dist/components-Doos0eY5.mjs +0 -2
- package/dist/components-Doos0eY5.mjs.map +0 -1
- package/dist/composables-DkBYStMa.mjs +0 -2
- package/dist/composables-DkBYStMa.mjs.map +0 -1
- package/dist/index-B1BUbFNh.d.mts.map +0 -1
- package/dist/index-DO_Tr6T7.d.mts.map +0 -1
- package/src/components/FlowUploadZone.vue +0 -305
- package/src/composables/useFlowUpload.ts +0 -230
|
@@ -1,305 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
/**
|
|
3
|
-
* FlowUploadZone - Upload zone with flow processing pipeline
|
|
4
|
-
*
|
|
5
|
-
* Specialized upload component that uploads files through a flow pipeline for processing.
|
|
6
|
-
* Supports drag-and-drop and file picker with flow configuration. Emits events for flow
|
|
7
|
-
* completion, errors, and upload status changes.
|
|
8
|
-
*
|
|
9
|
-
* @component
|
|
10
|
-
* @example
|
|
11
|
-
* // Upload images through image processing flow
|
|
12
|
-
* <FlowUploadZone
|
|
13
|
-
* :flow-config="{ flowId: 'image-processor' }"
|
|
14
|
-
* accept="image/*"
|
|
15
|
-
* @upload-complete="handleFlowResult"
|
|
16
|
-
* @upload-error="handleError"
|
|
17
|
-
* />
|
|
18
|
-
*
|
|
19
|
-
* @example
|
|
20
|
-
* // With custom slot content
|
|
21
|
-
* <FlowUploadZone
|
|
22
|
-
* :flow-config="{ flowId: 'image-processor' }"
|
|
23
|
-
* @upload-complete="handleResult"
|
|
24
|
-
* >
|
|
25
|
-
* <template #default="{ isDragging, isProcessing, progress }">
|
|
26
|
-
* <div :class="{ processing: isProcessing }">
|
|
27
|
-
* <p v-if="isProcessing">Processing... {{ progress }}%</p>
|
|
28
|
-
* <p v-else-if="isDragging">Drop file here</p>
|
|
29
|
-
* <p v-else>Drag file here for processing</p>
|
|
30
|
-
* </div>
|
|
31
|
-
* </template>
|
|
32
|
-
* </FlowUploadZone>
|
|
33
|
-
*
|
|
34
|
-
* @emits upload-complete - Flow processing completed with results
|
|
35
|
-
* @emits upload-error - Upload or processing failed
|
|
36
|
-
* @emits upload-start - File upload started
|
|
37
|
-
* @emits validation-error - File validation failed
|
|
38
|
-
*/
|
|
39
|
-
import type {
|
|
40
|
-
FlowUploadConfig,
|
|
41
|
-
FlowUploadOptions,
|
|
42
|
-
} from "@uploadista/client-browser";
|
|
43
|
-
import { computed, ref } from "vue";
|
|
44
|
-
import { useDragDrop, useFlowUpload } from "../composables";
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Props for the FlowUploadZone component
|
|
48
|
-
* @property {FlowUploadConfig} flowConfig - Flow configuration with flowId
|
|
49
|
-
* @property {FlowUploadOptions} options - Additional flow upload options
|
|
50
|
-
* @property {string} accept - Accepted file types (single MIME type or extension string)
|
|
51
|
-
* @property {boolean} multiple - Allow multiple files (default: false, flow uploads are single-file)
|
|
52
|
-
* @property {boolean} disabled - Disable the upload zone (default: false)
|
|
53
|
-
* @property {number} maxFileSize - Maximum file size in bytes
|
|
54
|
-
*/
|
|
55
|
-
export interface FlowUploadZoneProps {
|
|
56
|
-
/**
|
|
57
|
-
* Flow configuration
|
|
58
|
-
*/
|
|
59
|
-
flowConfig: FlowUploadConfig;
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Additional flow upload options
|
|
63
|
-
*/
|
|
64
|
-
options?: Omit<FlowUploadOptions, "flowConfig">;
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Accepted file types (single MIME type or extension string)
|
|
68
|
-
*/
|
|
69
|
-
accept?: string;
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Whether to allow multiple files (currently only single file supported for flow uploads)
|
|
73
|
-
*/
|
|
74
|
-
multiple?: boolean;
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Whether the upload zone is disabled
|
|
78
|
-
*/
|
|
79
|
-
disabled?: boolean;
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Maximum file size in bytes
|
|
83
|
-
*/
|
|
84
|
-
maxFileSize?: number;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
const props = withDefaults(defineProps<FlowUploadZoneProps>(), {
|
|
88
|
-
multiple: false,
|
|
89
|
-
disabled: false,
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
// biome-ignore lint/suspicious/noExplicitAny: Flow result can be any type
|
|
93
|
-
const emit = defineEmits<{
|
|
94
|
-
// biome-ignore lint/suspicious/noExplicitAny: Flow result can be any type
|
|
95
|
-
"upload-complete": [result: any];
|
|
96
|
-
"upload-error": [error: Error];
|
|
97
|
-
"upload-start": [file: File];
|
|
98
|
-
"validation-error": [errors: string[]];
|
|
99
|
-
}>();
|
|
100
|
-
|
|
101
|
-
// biome-ignore lint/suspicious/noExplicitAny: Vue slot definition requires any
|
|
102
|
-
defineSlots<{
|
|
103
|
-
// biome-ignore lint/suspicious/noExplicitAny: Vue slot definition requires any
|
|
104
|
-
default(props: {
|
|
105
|
-
isDragging: boolean;
|
|
106
|
-
isOver: boolean;
|
|
107
|
-
isUploading: boolean;
|
|
108
|
-
isProcessing: boolean;
|
|
109
|
-
progress: number;
|
|
110
|
-
status: string;
|
|
111
|
-
errors: string[];
|
|
112
|
-
openFilePicker: () => void;
|
|
113
|
-
}): any;
|
|
114
|
-
}>();
|
|
115
|
-
|
|
116
|
-
// Initialize flow upload
|
|
117
|
-
const flowUpload = useFlowUpload({
|
|
118
|
-
...props.options,
|
|
119
|
-
flowConfig: props.flowConfig,
|
|
120
|
-
onFlowComplete: (outputs) => {
|
|
121
|
-
emit("upload-complete", outputs);
|
|
122
|
-
props.options?.onFlowComplete?.(outputs);
|
|
123
|
-
},
|
|
124
|
-
onError: (error) => {
|
|
125
|
-
emit("upload-error", error);
|
|
126
|
-
props.options?.onError?.(error);
|
|
127
|
-
},
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
// Handle files received from drag-drop or file picker
|
|
131
|
-
const handleFilesReceived = (files: File[]) => {
|
|
132
|
-
const file = files[0];
|
|
133
|
-
if (file) {
|
|
134
|
-
emit("upload-start", file);
|
|
135
|
-
flowUpload.upload(file);
|
|
136
|
-
}
|
|
137
|
-
};
|
|
138
|
-
|
|
139
|
-
// Handle validation errors
|
|
140
|
-
const handleValidationError = (errors: string[]) => {
|
|
141
|
-
emit("validation-error", errors);
|
|
142
|
-
};
|
|
143
|
-
|
|
144
|
-
// Initialize drag-drop
|
|
145
|
-
const dragDrop = useDragDrop({
|
|
146
|
-
accept: props.accept ? [props.accept] : undefined,
|
|
147
|
-
multiple: props.multiple,
|
|
148
|
-
maxFileSize: props.maxFileSize,
|
|
149
|
-
onFilesReceived: handleFilesReceived,
|
|
150
|
-
onValidationError: handleValidationError,
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
// File input ref
|
|
154
|
-
const fileInputRef = ref<HTMLInputElement>();
|
|
155
|
-
|
|
156
|
-
// Open file picker
|
|
157
|
-
// biome-ignore lint/correctness/noUnusedVariables: Used in slot templates
|
|
158
|
-
const openFilePicker = () => {
|
|
159
|
-
if (!props.disabled) {
|
|
160
|
-
fileInputRef.value?.click();
|
|
161
|
-
}
|
|
162
|
-
};
|
|
163
|
-
|
|
164
|
-
// Computed states
|
|
165
|
-
// biome-ignore lint/correctness/noUnusedVariables: Used in slot templates
|
|
166
|
-
const isActive = computed(
|
|
167
|
-
() => dragDrop.state.value.isDragging || dragDrop.state.value.isOver,
|
|
168
|
-
);
|
|
169
|
-
</script>
|
|
170
|
-
|
|
171
|
-
<template>
|
|
172
|
-
<div
|
|
173
|
-
class="flow-upload-zone"
|
|
174
|
-
:class="{
|
|
175
|
-
'flow-upload-zone--active': isActive,
|
|
176
|
-
'flow-upload-zone--disabled': disabled,
|
|
177
|
-
'flow-upload-zone--uploading': flowUpload.isUploading.value
|
|
178
|
-
}"
|
|
179
|
-
@dragenter="!disabled && dragDrop.onDragEnter"
|
|
180
|
-
@dragover="!disabled && dragDrop.onDragOver"
|
|
181
|
-
@dragleave="!disabled && dragDrop.onDragLeave"
|
|
182
|
-
@drop="!disabled && dragDrop.onDrop"
|
|
183
|
-
@click="openFilePicker"
|
|
184
|
-
role="button"
|
|
185
|
-
:tabindex="disabled ? -1 : 0"
|
|
186
|
-
:aria-disabled="disabled"
|
|
187
|
-
aria-label="Upload file with flow processing"
|
|
188
|
-
@keydown.enter="openFilePicker"
|
|
189
|
-
@keydown.space.prevent="openFilePicker"
|
|
190
|
-
>
|
|
191
|
-
<slot
|
|
192
|
-
:is-dragging="dragDrop.state.value.isDragging"
|
|
193
|
-
:is-over="dragDrop.state.value.isOver"
|
|
194
|
-
:is-uploading="flowUpload.isUploading.value"
|
|
195
|
-
:is-processing="flowUpload.isProcessing.value"
|
|
196
|
-
:progress="flowUpload.state.value.progress"
|
|
197
|
-
:status="flowUpload.state.value.status"
|
|
198
|
-
:errors="[...dragDrop.state.value.errors]"
|
|
199
|
-
:open-file-picker="openFilePicker"
|
|
200
|
-
>
|
|
201
|
-
<!-- Default slot content -->
|
|
202
|
-
<div class="flow-upload-zone__content">
|
|
203
|
-
<p v-if="dragDrop.state.value.isDragging">Drop file here...</p>
|
|
204
|
-
<p v-else-if="flowUpload.isUploading.value">
|
|
205
|
-
Uploading... {{ flowUpload.state.value.progress }}%
|
|
206
|
-
</p>
|
|
207
|
-
<p v-else-if="flowUpload.isProcessing.value">
|
|
208
|
-
Processing...
|
|
209
|
-
<span v-if="flowUpload.state.value.currentNodeName">
|
|
210
|
-
({{ flowUpload.state.value.currentNodeName }})
|
|
211
|
-
</span>
|
|
212
|
-
</p>
|
|
213
|
-
<p v-else-if="flowUpload.state.value.status === 'success'">Upload complete!</p>
|
|
214
|
-
<p v-else-if="flowUpload.state.value.status === 'error'" class="flow-upload-zone__error">
|
|
215
|
-
Error: {{ flowUpload.state.value.error?.message }}
|
|
216
|
-
</p>
|
|
217
|
-
<p v-else>Drag a file here or click to select</p>
|
|
218
|
-
|
|
219
|
-
<div v-if="flowUpload.isUploading.value" class="flow-upload-zone__progress">
|
|
220
|
-
<div class="flow-upload-zone__progress-bar">
|
|
221
|
-
<div
|
|
222
|
-
class="flow-upload-zone__progress-fill"
|
|
223
|
-
:style="{ width: `${flowUpload.state.value.progress}%` }"
|
|
224
|
-
/>
|
|
225
|
-
</div>
|
|
226
|
-
</div>
|
|
227
|
-
|
|
228
|
-
<div v-if="dragDrop.state.value.errors.length > 0" class="flow-upload-zone__errors">
|
|
229
|
-
<p v-for="(error, index) in dragDrop.state.value.errors" :key="index">
|
|
230
|
-
{{ error }}
|
|
231
|
-
</p>
|
|
232
|
-
</div>
|
|
233
|
-
</div>
|
|
234
|
-
</slot>
|
|
235
|
-
|
|
236
|
-
<input
|
|
237
|
-
ref="fileInputRef"
|
|
238
|
-
type="file"
|
|
239
|
-
:multiple="dragDrop.inputProps.value.multiple"
|
|
240
|
-
:accept="dragDrop.inputProps.value.accept"
|
|
241
|
-
:disabled="disabled"
|
|
242
|
-
@change="dragDrop.onInputChange"
|
|
243
|
-
style="display: none"
|
|
244
|
-
aria-hidden="true"
|
|
245
|
-
/>
|
|
246
|
-
</div>
|
|
247
|
-
</template>
|
|
248
|
-
|
|
249
|
-
<style scoped>
|
|
250
|
-
.flow-upload-zone {
|
|
251
|
-
cursor: pointer;
|
|
252
|
-
user-select: none;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
.flow-upload-zone--disabled {
|
|
256
|
-
cursor: not-allowed;
|
|
257
|
-
opacity: 0.6;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
.flow-upload-zone--uploading {
|
|
261
|
-
pointer-events: none;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
.flow-upload-zone__content {
|
|
265
|
-
display: flex;
|
|
266
|
-
flex-direction: column;
|
|
267
|
-
align-items: center;
|
|
268
|
-
justify-content: center;
|
|
269
|
-
gap: 0.5rem;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
.flow-upload-zone__error {
|
|
273
|
-
color: #dc3545;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
.flow-upload-zone__progress {
|
|
277
|
-
width: 100%;
|
|
278
|
-
max-width: 300px;
|
|
279
|
-
margin-top: 0.5rem;
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
.flow-upload-zone__progress-bar {
|
|
283
|
-
width: 100%;
|
|
284
|
-
height: 0.5rem;
|
|
285
|
-
background-color: #e0e0e0;
|
|
286
|
-
border-radius: 0.25rem;
|
|
287
|
-
overflow: hidden;
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
.flow-upload-zone__progress-fill {
|
|
291
|
-
height: 100%;
|
|
292
|
-
background-color: #007bff;
|
|
293
|
-
transition: width 0.2s ease;
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
.flow-upload-zone__errors {
|
|
297
|
-
margin-top: 0.5rem;
|
|
298
|
-
color: #dc3545;
|
|
299
|
-
font-size: 0.875rem;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
.flow-upload-zone__errors p {
|
|
303
|
-
margin: 0.25rem 0;
|
|
304
|
-
}
|
|
305
|
-
</style>
|
|
@@ -1,230 +0,0 @@
|
|
|
1
|
-
import type { FlowUploadOptions } from "@uploadista/client-browser";
|
|
2
|
-
import type {
|
|
3
|
-
FlowManager,
|
|
4
|
-
FlowUploadState,
|
|
5
|
-
FlowUploadStatus,
|
|
6
|
-
} from "@uploadista/client-core";
|
|
7
|
-
import type { TypedOutput } from "@uploadista/core/flow";
|
|
8
|
-
import { computed, onMounted, onUnmounted, readonly, ref } from "vue";
|
|
9
|
-
import { useFlowManagerContext } from "./useFlowManagerContext";
|
|
10
|
-
|
|
11
|
-
// Re-export types from core for convenience
|
|
12
|
-
export type { FlowUploadState, FlowUploadStatus };
|
|
13
|
-
|
|
14
|
-
export interface UseFlowUploadOptions {
|
|
15
|
-
/**
|
|
16
|
-
* Flow configuration
|
|
17
|
-
*/
|
|
18
|
-
flowConfig: FlowUploadOptions["flowConfig"];
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Called when upload progress updates
|
|
22
|
-
*/
|
|
23
|
-
onProgress?: (
|
|
24
|
-
progress: number,
|
|
25
|
-
bytesUploaded: number,
|
|
26
|
-
totalBytes: number | null,
|
|
27
|
-
) => void;
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Called when a chunk completes
|
|
31
|
-
*/
|
|
32
|
-
onChunkComplete?: (
|
|
33
|
-
chunkSize: number,
|
|
34
|
-
bytesAccepted: number,
|
|
35
|
-
bytesTotal: number | null,
|
|
36
|
-
) => void;
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Called when the flow completes successfully (receives full flow outputs)
|
|
40
|
-
* This is the recommended callback for multi-output flows
|
|
41
|
-
* Format: { [outputNodeId]: result, ... }
|
|
42
|
-
*/
|
|
43
|
-
onFlowComplete?: (outputs: Record<string, unknown>) => void;
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Called when upload succeeds (receives typed outputs from all output nodes)
|
|
47
|
-
* Each output includes nodeId, optional nodeType, data, and timestamp.
|
|
48
|
-
*
|
|
49
|
-
* @param outputs - Array of typed outputs from all output nodes
|
|
50
|
-
*/
|
|
51
|
-
onSuccess?: (outputs: TypedOutput[]) => void;
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Called when upload fails
|
|
55
|
-
*/
|
|
56
|
-
onError?: (error: Error) => void;
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Called when upload is aborted
|
|
60
|
-
*/
|
|
61
|
-
onAbort?: () => void;
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Custom retry logic
|
|
65
|
-
*/
|
|
66
|
-
onShouldRetry?: (error: Error, retryAttempt: number) => boolean;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const initialState: FlowUploadState = {
|
|
70
|
-
status: "idle",
|
|
71
|
-
progress: 0,
|
|
72
|
-
bytesUploaded: 0,
|
|
73
|
-
totalBytes: null,
|
|
74
|
-
error: null,
|
|
75
|
-
jobId: null,
|
|
76
|
-
flowStarted: false,
|
|
77
|
-
currentNodeName: null,
|
|
78
|
-
currentNodeType: null,
|
|
79
|
-
flowOutputs: null,
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Vue composable for uploading files through a flow.
|
|
84
|
-
*
|
|
85
|
-
* This composable provides a simple interface for uploading files through a flow.
|
|
86
|
-
* The flow handles the upload process and can perform post-processing like
|
|
87
|
-
* saving to storage, optimizing images, etc.
|
|
88
|
-
*
|
|
89
|
-
* Must be used within FlowManagerProvider (which must be within UploadistaProvider).
|
|
90
|
-
* Flow events are automatically routed by the provider to the appropriate manager.
|
|
91
|
-
*
|
|
92
|
-
* @example
|
|
93
|
-
* ```vue
|
|
94
|
-
* <script setup lang="ts">
|
|
95
|
-
* import { useFlowUpload } from '@uploadista/vue';
|
|
96
|
-
*
|
|
97
|
-
* const flowUpload = useFlowUpload({
|
|
98
|
-
* flowConfig: {
|
|
99
|
-
* flowId: "my-upload-flow",
|
|
100
|
-
* storageId: "my-storage",
|
|
101
|
-
* },
|
|
102
|
-
* onSuccess: (outputs) => {
|
|
103
|
-
* console.log("Flow outputs:", outputs);
|
|
104
|
-
* for (const output of outputs) {
|
|
105
|
-
* console.log(`${output.nodeId}:`, output.data);
|
|
106
|
-
* }
|
|
107
|
-
* },
|
|
108
|
-
* });
|
|
109
|
-
*
|
|
110
|
-
* const handleFileChange = (event: Event) => {
|
|
111
|
-
* const file = (event.target as HTMLInputElement).files?.[0];
|
|
112
|
-
* if (file) flowUpload.upload(file);
|
|
113
|
-
* };
|
|
114
|
-
* </script>
|
|
115
|
-
*
|
|
116
|
-
* <template>
|
|
117
|
-
* <input type="file" @change="handleFileChange" />
|
|
118
|
-
* </template>
|
|
119
|
-
* ```
|
|
120
|
-
*/
|
|
121
|
-
export function useFlowUpload(options: UseFlowUploadOptions) {
|
|
122
|
-
const { getManager, releaseManager } = useFlowManagerContext();
|
|
123
|
-
const state = ref<FlowUploadState>(initialState);
|
|
124
|
-
let manager: FlowManager<unknown> | null = null;
|
|
125
|
-
|
|
126
|
-
// Store latest options in a ref to access in callbacks
|
|
127
|
-
const optionsRef = ref(options);
|
|
128
|
-
|
|
129
|
-
// Get or create manager from context when component mounts
|
|
130
|
-
onMounted(() => {
|
|
131
|
-
const flowId = options.flowConfig.flowId;
|
|
132
|
-
|
|
133
|
-
// Create stable callback wrappers
|
|
134
|
-
const stableCallbacks = {
|
|
135
|
-
onStateChange: (newState: FlowUploadState) => {
|
|
136
|
-
state.value = newState;
|
|
137
|
-
},
|
|
138
|
-
onProgress: (
|
|
139
|
-
_uploadId: string,
|
|
140
|
-
bytesUploaded: number,
|
|
141
|
-
totalBytes: number | null,
|
|
142
|
-
) => {
|
|
143
|
-
if (optionsRef.value.onProgress) {
|
|
144
|
-
const progress = totalBytes
|
|
145
|
-
? Math.round((bytesUploaded / totalBytes) * 100)
|
|
146
|
-
: 0;
|
|
147
|
-
optionsRef.value.onProgress(progress, bytesUploaded, totalBytes);
|
|
148
|
-
}
|
|
149
|
-
},
|
|
150
|
-
onChunkComplete: (
|
|
151
|
-
chunkSize: number,
|
|
152
|
-
bytesAccepted: number,
|
|
153
|
-
bytesTotal: number | null,
|
|
154
|
-
) => {
|
|
155
|
-
optionsRef.value.onChunkComplete?.(
|
|
156
|
-
chunkSize,
|
|
157
|
-
bytesAccepted,
|
|
158
|
-
bytesTotal,
|
|
159
|
-
);
|
|
160
|
-
},
|
|
161
|
-
onFlowComplete: (outputs: TypedOutput[]) => {
|
|
162
|
-
optionsRef.value.onFlowComplete?.(
|
|
163
|
-
outputs as unknown as Record<string, unknown>,
|
|
164
|
-
);
|
|
165
|
-
},
|
|
166
|
-
onSuccess: (outputs: TypedOutput[]) => {
|
|
167
|
-
optionsRef.value.onSuccess?.(outputs);
|
|
168
|
-
},
|
|
169
|
-
onError: (error: Error) => {
|
|
170
|
-
optionsRef.value.onError?.(error);
|
|
171
|
-
},
|
|
172
|
-
onAbort: () => {
|
|
173
|
-
optionsRef.value.onAbort?.();
|
|
174
|
-
},
|
|
175
|
-
};
|
|
176
|
-
|
|
177
|
-
// Get manager from context
|
|
178
|
-
manager = getManager(flowId, stableCallbacks, {
|
|
179
|
-
flowConfig: options.flowConfig,
|
|
180
|
-
onChunkComplete: options.onChunkComplete,
|
|
181
|
-
onFlowComplete: options.onFlowComplete as
|
|
182
|
-
| ((outputs: TypedOutput[]) => void)
|
|
183
|
-
| undefined,
|
|
184
|
-
onSuccess: options.onSuccess,
|
|
185
|
-
onError: options.onError,
|
|
186
|
-
onAbort: options.onAbort,
|
|
187
|
-
onShouldRetry: options.onShouldRetry,
|
|
188
|
-
});
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
// Cleanup on unmount
|
|
192
|
-
onUnmounted(() => {
|
|
193
|
-
if (manager) {
|
|
194
|
-
releaseManager(options.flowConfig.flowId);
|
|
195
|
-
manager = null;
|
|
196
|
-
}
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
const upload = async (file: File | Blob) => {
|
|
200
|
-
await manager?.upload(file);
|
|
201
|
-
};
|
|
202
|
-
|
|
203
|
-
const abort = () => {
|
|
204
|
-
manager?.abort();
|
|
205
|
-
};
|
|
206
|
-
|
|
207
|
-
const pause = () => {
|
|
208
|
-
manager?.pause();
|
|
209
|
-
};
|
|
210
|
-
|
|
211
|
-
const reset = () => {
|
|
212
|
-
manager?.reset();
|
|
213
|
-
};
|
|
214
|
-
|
|
215
|
-
return {
|
|
216
|
-
state: readonly(state),
|
|
217
|
-
upload,
|
|
218
|
-
abort,
|
|
219
|
-
pause,
|
|
220
|
-
reset,
|
|
221
|
-
// Derive computed values from state (reactive to state changes)
|
|
222
|
-
isUploading: computed(
|
|
223
|
-
() =>
|
|
224
|
-
state.value.status === "uploading" ||
|
|
225
|
-
state.value.status === "processing",
|
|
226
|
-
),
|
|
227
|
-
isUploadingFile: computed(() => state.value.status === "uploading"),
|
|
228
|
-
isProcessing: computed(() => state.value.status === "processing"),
|
|
229
|
-
};
|
|
230
|
-
}
|