@uploadista/vue 0.0.20-beta.6 → 0.0.20-beta.7
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-Dk25ojCY.mjs +2 -0
- package/dist/components-Dk25ojCY.mjs.map +1 -0
- package/dist/composables/index.d.mts +1 -1
- package/dist/composables/index.mjs +1 -1
- package/dist/composables-BZ2c_WgI.mjs +2 -0
- package/dist/composables-BZ2c_WgI.mjs.map +1 -0
- package/dist/index-CDJUpsAf.d.mts +49 -0
- package/dist/index-CDJUpsAf.d.mts.map +1 -0
- package/dist/index-Ci1I0jRB.d.mts +787 -0
- package/dist/index-Ci1I0jRB.d.mts.map +1 -0
- package/dist/{index-Bemg9qdC.d.mts → index-qvkAz1kU.d.mts} +30 -30
- package/dist/index-qvkAz1kU.d.mts.map +1 -0
- package/dist/index.d.mts +4 -4
- package/dist/index.mjs +1 -1
- package/dist/providers/index.d.mts +2 -2
- package/dist/providers/index.mjs +1 -1
- package/dist/providers-DL9Qq-3z.mjs +2 -0
- package/dist/providers-DL9Qq-3z.mjs.map +1 -0
- package/dist/useUploadistaClient-WVuo8jYH.mjs +2 -0
- package/dist/useUploadistaClient-WVuo8jYH.mjs.map +1 -0
- package/package.json +5 -5
- package/src/components/flow/Flow.vue +127 -0
- package/src/components/flow/FlowCancel.vue +26 -0
- package/src/components/flow/FlowDropZone.vue +124 -0
- package/src/components/flow/FlowError.vue +47 -0
- package/src/components/flow/FlowInput.vue +86 -0
- package/src/components/flow/FlowInputDropZone.vue +129 -0
- package/src/components/flow/FlowInputPreview.vue +67 -0
- package/src/components/flow/FlowInputUrlField.vue +44 -0
- package/src/components/flow/FlowInputs.vue +34 -0
- package/src/components/flow/FlowProgress.vue +53 -0
- package/src/components/flow/FlowReset.vue +26 -0
- package/src/components/flow/FlowStatus.vue +55 -0
- package/src/components/flow/FlowSubmit.vue +27 -0
- package/src/components/flow/index.ts +101 -0
- package/src/components/flow/useFlowContext.ts +68 -0
- package/src/components/index.ts +3 -0
- package/src/providers/FlowManagerProvider.vue +4 -4
- package/src/providers/UploadistaProvider.vue +4 -1
- package/src/providers/index.ts +1 -0
- package/dist/components-Bhroc6MN.mjs +0 -2
- package/dist/components-Bhroc6MN.mjs.map +0 -1
- package/dist/composables-7rR8DrBp.mjs +0 -2
- package/dist/composables-7rR8DrBp.mjs.map +0 -1
- package/dist/index-Bemg9qdC.d.mts.map +0 -1
- package/dist/index-C9s0EqbD.d.mts +0 -183
- package/dist/index-C9s0EqbD.d.mts.map +0 -1
- package/dist/index-cDdde3bt.d.mts +0 -33
- package/dist/index-cDdde3bt.d.mts.map +0 -1
- package/dist/plugin-BC-8nlFO.mjs +0 -2
- package/dist/plugin-BC-8nlFO.mjs.map +0 -1
- package/dist/providers-kZkr_iMD.mjs +0 -2
- package/dist/providers-kZkr_iMD.mjs.map +0 -1
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, ref } from "vue";
|
|
3
|
+
import { useDragDrop, type DragDropState } from "../../composables/useDragDrop";
|
|
4
|
+
import { useFlowContext } from "./useFlowContext";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Props for FlowDropZone component.
|
|
8
|
+
*/
|
|
9
|
+
export interface FlowDropZoneProps {
|
|
10
|
+
/** Accepted file types (e.g., "image/*", ".pdf") */
|
|
11
|
+
accept?: string;
|
|
12
|
+
/** Maximum file size in bytes */
|
|
13
|
+
maxFileSize?: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const props = withDefaults(defineProps<FlowDropZoneProps>(), {
|
|
17
|
+
accept: undefined,
|
|
18
|
+
maxFileSize: undefined,
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const flow = useFlowContext();
|
|
22
|
+
const inputRef = ref<HTMLInputElement | null>(null);
|
|
23
|
+
|
|
24
|
+
const dragDrop = useDragDrop({
|
|
25
|
+
onFilesReceived: (files) => {
|
|
26
|
+
const file = files[0];
|
|
27
|
+
if (file) {
|
|
28
|
+
flow.upload(file);
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
accept: props.accept ? props.accept.split(",").map((t) => t.trim()) : undefined,
|
|
32
|
+
maxFileSize: props.maxFileSize,
|
|
33
|
+
multiple: false,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const openFilePicker = () => {
|
|
37
|
+
inputRef.value?.click();
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Slot props provided to the default slot.
|
|
42
|
+
*/
|
|
43
|
+
export interface FlowDropZoneSlotProps {
|
|
44
|
+
/** Whether files are being dragged over */
|
|
45
|
+
isDragging: boolean;
|
|
46
|
+
/** Whether drag is over the zone */
|
|
47
|
+
isOver: boolean;
|
|
48
|
+
/** Upload progress (0-100) */
|
|
49
|
+
progress: number;
|
|
50
|
+
/** Current flow status */
|
|
51
|
+
status: string;
|
|
52
|
+
/** Current drag-drop state */
|
|
53
|
+
dragDropState: DragDropState;
|
|
54
|
+
/** Open file picker programmatically */
|
|
55
|
+
openFilePicker: () => void;
|
|
56
|
+
/** Drag event handlers to spread on the container */
|
|
57
|
+
dragHandlers: {
|
|
58
|
+
onDragenter: (e: DragEvent) => void;
|
|
59
|
+
onDragover: (e: DragEvent) => void;
|
|
60
|
+
onDragleave: (e: DragEvent) => void;
|
|
61
|
+
onDrop: (e: DragEvent) => void;
|
|
62
|
+
};
|
|
63
|
+
/** Input props for the hidden file input */
|
|
64
|
+
inputProps: {
|
|
65
|
+
type: "file";
|
|
66
|
+
multiple: boolean;
|
|
67
|
+
accept: string | undefined;
|
|
68
|
+
};
|
|
69
|
+
/** Input change handler */
|
|
70
|
+
onInputChange: (e: Event) => void;
|
|
71
|
+
/** Ref for the file input element */
|
|
72
|
+
inputRef: typeof inputRef;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const slotProps = computed<FlowDropZoneSlotProps>(() => ({
|
|
76
|
+
isDragging: dragDrop.state.value.isDragging,
|
|
77
|
+
isOver: dragDrop.state.value.isOver,
|
|
78
|
+
progress: flow.state.value.progress,
|
|
79
|
+
status: flow.state.value.status,
|
|
80
|
+
dragDropState: dragDrop.state.value,
|
|
81
|
+
openFilePicker,
|
|
82
|
+
dragHandlers: {
|
|
83
|
+
onDragenter: dragDrop.onDragEnter,
|
|
84
|
+
onDragover: dragDrop.onDragOver,
|
|
85
|
+
onDragleave: dragDrop.onDragLeave,
|
|
86
|
+
onDrop: dragDrop.onDrop,
|
|
87
|
+
},
|
|
88
|
+
inputProps: dragDrop.inputProps.value,
|
|
89
|
+
onInputChange: dragDrop.onInputChange,
|
|
90
|
+
inputRef,
|
|
91
|
+
}));
|
|
92
|
+
</script>
|
|
93
|
+
|
|
94
|
+
<template>
|
|
95
|
+
<slot v-bind="slotProps">
|
|
96
|
+
<!-- Default content if no slot provided -->
|
|
97
|
+
<div
|
|
98
|
+
v-bind="slotProps.dragHandlers"
|
|
99
|
+
@click="openFilePicker"
|
|
100
|
+
:style="{
|
|
101
|
+
border: slotProps.isDragging ? '2px dashed #3b82f6' : '2px dashed #d1d5db',
|
|
102
|
+
borderRadius: '0.5rem',
|
|
103
|
+
padding: '2rem',
|
|
104
|
+
textAlign: 'center',
|
|
105
|
+
cursor: flow.isUploading.value ? 'not-allowed' : 'pointer',
|
|
106
|
+
opacity: flow.isUploading.value ? 0.5 : 1,
|
|
107
|
+
backgroundColor: slotProps.isOver ? '#eff6ff' : 'transparent',
|
|
108
|
+
transition: 'all 0.2s ease',
|
|
109
|
+
}"
|
|
110
|
+
>
|
|
111
|
+
<p v-if="slotProps.isDragging">Drop file here...</p>
|
|
112
|
+
<p v-else-if="flow.isUploading.value">Uploading... {{ slotProps.progress }}%</p>
|
|
113
|
+
<p v-else>Drag and drop a file here, or click to select</p>
|
|
114
|
+
</div>
|
|
115
|
+
<input
|
|
116
|
+
ref="inputRef"
|
|
117
|
+
type="file"
|
|
118
|
+
:multiple="slotProps.inputProps.multiple"
|
|
119
|
+
:accept="slotProps.inputProps.accept"
|
|
120
|
+
@change="slotProps.onInputChange"
|
|
121
|
+
style="display: none"
|
|
122
|
+
/>
|
|
123
|
+
</slot>
|
|
124
|
+
</template>
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from "vue";
|
|
3
|
+
import { useFlowContext } from "./useFlowContext";
|
|
4
|
+
|
|
5
|
+
const flow = useFlowContext();
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Slot props provided to the default slot.
|
|
9
|
+
*/
|
|
10
|
+
export interface FlowErrorSlotProps {
|
|
11
|
+
/** Error object (null if no error) */
|
|
12
|
+
error: Error | null;
|
|
13
|
+
/** Whether there is an error */
|
|
14
|
+
hasError: boolean;
|
|
15
|
+
/** Error message */
|
|
16
|
+
message: string | null;
|
|
17
|
+
/** Reset the flow */
|
|
18
|
+
reset: () => void;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const slotProps = computed<FlowErrorSlotProps>(() => ({
|
|
22
|
+
error: flow.state.value.error,
|
|
23
|
+
hasError: flow.state.value.status === "error",
|
|
24
|
+
message: flow.state.value.error?.message ?? null,
|
|
25
|
+
reset: flow.reset,
|
|
26
|
+
}));
|
|
27
|
+
</script>
|
|
28
|
+
|
|
29
|
+
<template>
|
|
30
|
+
<slot v-bind="slotProps">
|
|
31
|
+
<!-- Default error display -->
|
|
32
|
+
<div
|
|
33
|
+
v-if="slotProps.hasError"
|
|
34
|
+
style="padding: 1rem; background: #fef2f2; border: 1px solid #fecaca; border-radius: 0.5rem; color: #dc2626;"
|
|
35
|
+
>
|
|
36
|
+
<p style="margin: 0; font-weight: 600;">Error</p>
|
|
37
|
+
<p style="margin: 0.25rem 0 0; font-size: 0.875rem;">{{ slotProps.message }}</p>
|
|
38
|
+
<button
|
|
39
|
+
type="button"
|
|
40
|
+
@click="slotProps.reset"
|
|
41
|
+
style="margin-top: 0.75rem; padding: 0.5rem 1rem; background: #dc2626; color: white; border: none; border-radius: 0.375rem; cursor: pointer;"
|
|
42
|
+
>
|
|
43
|
+
Try Again
|
|
44
|
+
</button>
|
|
45
|
+
</div>
|
|
46
|
+
</slot>
|
|
47
|
+
</template>
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, provide } from "vue";
|
|
3
|
+
import { useFlowContext, FLOW_INPUT_CONTEXT_KEY, type FlowInputContextValue } from "./useFlowContext";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Props for FlowInput component.
|
|
7
|
+
*/
|
|
8
|
+
export interface FlowInputProps {
|
|
9
|
+
/** Input node ID */
|
|
10
|
+
nodeId: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const props = defineProps<FlowInputProps>();
|
|
14
|
+
const flow = useFlowContext();
|
|
15
|
+
|
|
16
|
+
// Find metadata for this input
|
|
17
|
+
const metadata = computed(() =>
|
|
18
|
+
flow.inputMetadata.value?.find((m) => m.nodeId === props.nodeId)
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
// Get current value for this input
|
|
22
|
+
const value = computed(() => flow.inputs.value[props.nodeId]);
|
|
23
|
+
|
|
24
|
+
// Get execution state for this input
|
|
25
|
+
const inputState = computed(() => flow.inputStates.value.get(props.nodeId));
|
|
26
|
+
|
|
27
|
+
// Create setValue function scoped to this input
|
|
28
|
+
const setValue = (newValue: unknown) => {
|
|
29
|
+
flow.setInput(props.nodeId, newValue);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// Create a context object with getters that access computed refs
|
|
33
|
+
// This ensures reactivity works while also providing stable function references
|
|
34
|
+
const contextValue: FlowInputContextValue = {
|
|
35
|
+
get nodeId() {
|
|
36
|
+
return props.nodeId;
|
|
37
|
+
},
|
|
38
|
+
get metadata() {
|
|
39
|
+
return metadata.value ?? {
|
|
40
|
+
nodeId: props.nodeId,
|
|
41
|
+
nodeName: "",
|
|
42
|
+
nodeDescription: "",
|
|
43
|
+
required: false,
|
|
44
|
+
};
|
|
45
|
+
},
|
|
46
|
+
get value() {
|
|
47
|
+
return value.value;
|
|
48
|
+
},
|
|
49
|
+
setValue,
|
|
50
|
+
get state() {
|
|
51
|
+
return inputState.value;
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
// Provide context for child components (FlowInputDropZone, etc.)
|
|
56
|
+
provide(FLOW_INPUT_CONTEXT_KEY, contextValue);
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Slot props provided to the default slot.
|
|
60
|
+
*/
|
|
61
|
+
export interface FlowInputSlotProps {
|
|
62
|
+
/** Input node ID */
|
|
63
|
+
nodeId: string;
|
|
64
|
+
/** Input metadata from flow discovery */
|
|
65
|
+
metadata: FlowInputContextValue["metadata"] | undefined;
|
|
66
|
+
/** Current value for this input */
|
|
67
|
+
value: unknown;
|
|
68
|
+
/** Set the value for this input */
|
|
69
|
+
setValue: (value: unknown) => void;
|
|
70
|
+
/** Per-input execution state (if available) */
|
|
71
|
+
state: FlowInputContextValue["state"];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const slotProps = computed<FlowInputSlotProps>(() => ({
|
|
75
|
+
nodeId: props.nodeId,
|
|
76
|
+
metadata: metadata.value,
|
|
77
|
+
value: value.value,
|
|
78
|
+
setValue,
|
|
79
|
+
state: inputState.value,
|
|
80
|
+
}));
|
|
81
|
+
</script>
|
|
82
|
+
|
|
83
|
+
<template>
|
|
84
|
+
<!-- Only render if metadata is found -->
|
|
85
|
+
<slot v-if="metadata" v-bind="slotProps" />
|
|
86
|
+
</template>
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, ref } from "vue";
|
|
3
|
+
import { useDragDrop, type DragDropState } from "../../composables/useDragDrop";
|
|
4
|
+
import { useFlowInputContext } from "./useFlowContext";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Props for FlowInputDropZone component.
|
|
8
|
+
*/
|
|
9
|
+
export interface FlowInputDropZoneProps {
|
|
10
|
+
/** Accepted file types (e.g., "image/*", ".pdf") */
|
|
11
|
+
accept?: string;
|
|
12
|
+
/** Maximum file size in bytes */
|
|
13
|
+
maxFileSize?: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const props = withDefaults(defineProps<FlowInputDropZoneProps>(), {
|
|
17
|
+
accept: undefined,
|
|
18
|
+
maxFileSize: undefined,
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const input = useFlowInputContext();
|
|
22
|
+
const inputRef = ref<HTMLInputElement | null>(null);
|
|
23
|
+
|
|
24
|
+
const dragDrop = useDragDrop({
|
|
25
|
+
onFilesReceived: (files) => {
|
|
26
|
+
const file = files[0];
|
|
27
|
+
if (file) {
|
|
28
|
+
// Set the input value but don't trigger upload yet
|
|
29
|
+
input.setValue(file);
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
accept: props.accept ? props.accept.split(",").map((t) => t.trim()) : undefined,
|
|
33
|
+
maxFileSize: props.maxFileSize,
|
|
34
|
+
multiple: false,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const openFilePicker = () => {
|
|
38
|
+
inputRef.value?.click();
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Slot props provided to the default slot.
|
|
43
|
+
*/
|
|
44
|
+
export interface FlowInputDropZoneSlotProps {
|
|
45
|
+
/** Whether files are being dragged over */
|
|
46
|
+
isDragging: boolean;
|
|
47
|
+
/** Whether drag is over the zone */
|
|
48
|
+
isOver: boolean;
|
|
49
|
+
/** Current value for this input */
|
|
50
|
+
value: unknown;
|
|
51
|
+
/** Per-input progress (if available) */
|
|
52
|
+
progress: number;
|
|
53
|
+
/** Per-input status (if available) */
|
|
54
|
+
status: string;
|
|
55
|
+
/** Current drag-drop state */
|
|
56
|
+
dragDropState: DragDropState;
|
|
57
|
+
/** Open file picker programmatically */
|
|
58
|
+
openFilePicker: () => void;
|
|
59
|
+
/** Drag event handlers to spread on the container */
|
|
60
|
+
dragHandlers: {
|
|
61
|
+
onDragenter: (e: DragEvent) => void;
|
|
62
|
+
onDragover: (e: DragEvent) => void;
|
|
63
|
+
onDragleave: (e: DragEvent) => void;
|
|
64
|
+
onDrop: (e: DragEvent) => void;
|
|
65
|
+
};
|
|
66
|
+
/** Input props for the hidden file input */
|
|
67
|
+
inputProps: {
|
|
68
|
+
type: "file";
|
|
69
|
+
multiple: boolean;
|
|
70
|
+
accept: string | undefined;
|
|
71
|
+
};
|
|
72
|
+
/** Input change handler */
|
|
73
|
+
onInputChange: (e: Event) => void;
|
|
74
|
+
/** Ref for the file input element */
|
|
75
|
+
inputRef: typeof inputRef;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const slotProps = computed<FlowInputDropZoneSlotProps>(() => ({
|
|
79
|
+
isDragging: dragDrop.state.value.isDragging,
|
|
80
|
+
isOver: dragDrop.state.value.isOver,
|
|
81
|
+
value: input.value,
|
|
82
|
+
progress: input.state?.progress ?? 0,
|
|
83
|
+
status: input.state?.status ?? "idle",
|
|
84
|
+
dragDropState: dragDrop.state.value,
|
|
85
|
+
openFilePicker,
|
|
86
|
+
dragHandlers: {
|
|
87
|
+
onDragenter: dragDrop.onDragEnter,
|
|
88
|
+
onDragover: dragDrop.onDragOver,
|
|
89
|
+
onDragleave: dragDrop.onDragLeave,
|
|
90
|
+
onDrop: dragDrop.onDrop,
|
|
91
|
+
},
|
|
92
|
+
inputProps: dragDrop.inputProps.value,
|
|
93
|
+
onInputChange: dragDrop.onInputChange,
|
|
94
|
+
inputRef,
|
|
95
|
+
}));
|
|
96
|
+
</script>
|
|
97
|
+
|
|
98
|
+
<template>
|
|
99
|
+
<slot v-bind="slotProps">
|
|
100
|
+
<!-- Default content if no slot provided -->
|
|
101
|
+
<div
|
|
102
|
+
v-bind="slotProps.dragHandlers"
|
|
103
|
+
@click="openFilePicker"
|
|
104
|
+
:style="{
|
|
105
|
+
border: slotProps.isDragging ? '2px dashed #3b82f6' : '2px dashed #d1d5db',
|
|
106
|
+
borderRadius: '0.5rem',
|
|
107
|
+
padding: '2rem',
|
|
108
|
+
textAlign: 'center',
|
|
109
|
+
cursor: 'pointer',
|
|
110
|
+
backgroundColor: slotProps.isOver ? '#eff6ff' : 'transparent',
|
|
111
|
+
transition: 'all 0.2s ease',
|
|
112
|
+
}"
|
|
113
|
+
>
|
|
114
|
+
<p v-if="slotProps.isDragging">Drop file here...</p>
|
|
115
|
+
<p v-else-if="slotProps.value instanceof File">
|
|
116
|
+
Selected: {{ (slotProps.value as File).name }}
|
|
117
|
+
</p>
|
|
118
|
+
<p v-else>Drag and drop a file here, or click to select</p>
|
|
119
|
+
</div>
|
|
120
|
+
<input
|
|
121
|
+
ref="inputRef"
|
|
122
|
+
type="file"
|
|
123
|
+
:multiple="slotProps.inputProps.multiple"
|
|
124
|
+
:accept="slotProps.inputProps.accept"
|
|
125
|
+
@change="slotProps.onInputChange"
|
|
126
|
+
style="display: none"
|
|
127
|
+
/>
|
|
128
|
+
</slot>
|
|
129
|
+
</template>
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from "vue";
|
|
3
|
+
import { useFlowInputContext } from "./useFlowContext";
|
|
4
|
+
|
|
5
|
+
const input = useFlowInputContext();
|
|
6
|
+
|
|
7
|
+
const isFile = computed(() => input.value instanceof File);
|
|
8
|
+
const isUrl = computed(() => typeof input.value === "string" && (input.value as string).length > 0);
|
|
9
|
+
|
|
10
|
+
const clear = () => {
|
|
11
|
+
input.setValue(undefined);
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Slot props provided to the default slot.
|
|
16
|
+
*/
|
|
17
|
+
export interface FlowInputPreviewSlotProps {
|
|
18
|
+
/** Current value */
|
|
19
|
+
value: unknown;
|
|
20
|
+
/** Whether value is a File */
|
|
21
|
+
isFile: boolean;
|
|
22
|
+
/** Whether value is a URL string */
|
|
23
|
+
isUrl: boolean;
|
|
24
|
+
/** File name (if value is File) */
|
|
25
|
+
fileName: string | null;
|
|
26
|
+
/** File size in bytes (if value is File) */
|
|
27
|
+
fileSize: number | null;
|
|
28
|
+
/** Clear the input value */
|
|
29
|
+
clear: () => void;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const slotProps = computed<FlowInputPreviewSlotProps>(() => ({
|
|
33
|
+
value: input.value,
|
|
34
|
+
isFile: isFile.value,
|
|
35
|
+
isUrl: isUrl.value,
|
|
36
|
+
fileName: isFile.value ? (input.value as File).name : null,
|
|
37
|
+
fileSize: isFile.value ? (input.value as File).size : null,
|
|
38
|
+
clear,
|
|
39
|
+
}));
|
|
40
|
+
</script>
|
|
41
|
+
|
|
42
|
+
<template>
|
|
43
|
+
<slot v-bind="slotProps">
|
|
44
|
+
<!-- Default preview content - only render for File or URL values -->
|
|
45
|
+
<div v-if="slotProps.isFile || slotProps.isUrl" style="display: flex; align-items: center; gap: 0.5rem; padding: 0.5rem; background: #f3f4f6; border-radius: 0.375rem;">
|
|
46
|
+
<div style="flex: 1; min-width: 0;">
|
|
47
|
+
<p v-if="slotProps.isFile" style="margin: 0; font-size: 0.875rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
|
|
48
|
+
{{ slotProps.fileName }}
|
|
49
|
+
<span v-if="slotProps.fileSize" style="color: #6b7280; margin-left: 0.25rem;">
|
|
50
|
+
({{ (slotProps.fileSize / 1024).toFixed(1) }} KB)
|
|
51
|
+
</span>
|
|
52
|
+
</p>
|
|
53
|
+
<p v-else-if="slotProps.isUrl" style="margin: 0; font-size: 0.875rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
|
|
54
|
+
{{ slotProps.value }}
|
|
55
|
+
</p>
|
|
56
|
+
</div>
|
|
57
|
+
<button
|
|
58
|
+
type="button"
|
|
59
|
+
@click="clear"
|
|
60
|
+
style="padding: 0.25rem 0.5rem; background: transparent; border: none; cursor: pointer; color: #6b7280;"
|
|
61
|
+
aria-label="Clear"
|
|
62
|
+
>
|
|
63
|
+
×
|
|
64
|
+
</button>
|
|
65
|
+
</div>
|
|
66
|
+
</slot>
|
|
67
|
+
</template>
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from "vue";
|
|
3
|
+
import { useFlowInputContext } from "./useFlowContext";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Props for FlowInputUrlField component.
|
|
7
|
+
*/
|
|
8
|
+
export interface FlowInputUrlFieldProps {
|
|
9
|
+
/** Placeholder text */
|
|
10
|
+
placeholder?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const props = withDefaults(defineProps<FlowInputUrlFieldProps>(), {
|
|
14
|
+
placeholder: "https://example.com/file",
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const input = useFlowInputContext();
|
|
18
|
+
|
|
19
|
+
// Check if value is a URL string
|
|
20
|
+
const isUrl = computed(() => typeof input.value === "string");
|
|
21
|
+
const urlValue = computed(() => (isUrl.value ? (input.value as string) : ""));
|
|
22
|
+
|
|
23
|
+
const handleInput = (event: Event) => {
|
|
24
|
+
const target = event.target as HTMLInputElement;
|
|
25
|
+
input.setValue(target.value);
|
|
26
|
+
};
|
|
27
|
+
</script>
|
|
28
|
+
|
|
29
|
+
<template>
|
|
30
|
+
<input
|
|
31
|
+
type="url"
|
|
32
|
+
:value="urlValue"
|
|
33
|
+
@input="handleInput"
|
|
34
|
+
:placeholder="placeholder"
|
|
35
|
+
v-bind="$attrs"
|
|
36
|
+
/>
|
|
37
|
+
</template>
|
|
38
|
+
|
|
39
|
+
<script lang="ts">
|
|
40
|
+
// Disable attribute inheritance so we can spread them manually
|
|
41
|
+
export default {
|
|
42
|
+
inheritAttrs: false,
|
|
43
|
+
};
|
|
44
|
+
</script>
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from "vue";
|
|
3
|
+
import type { FlowInputMetadata } from "../../composables/useFlow";
|
|
4
|
+
import { useFlowContext } from "./useFlowContext";
|
|
5
|
+
|
|
6
|
+
const flow = useFlowContext();
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Slot props provided to the default slot.
|
|
10
|
+
*/
|
|
11
|
+
export interface FlowInputsSlotProps {
|
|
12
|
+
/** Discovered input metadata */
|
|
13
|
+
inputs: FlowInputMetadata[];
|
|
14
|
+
/** Whether inputs are still being discovered */
|
|
15
|
+
isLoading: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const slotProps = computed<FlowInputsSlotProps>(() => ({
|
|
19
|
+
inputs: flow.inputMetadata.value ?? [],
|
|
20
|
+
isLoading: flow.isDiscoveringInputs.value,
|
|
21
|
+
}));
|
|
22
|
+
</script>
|
|
23
|
+
|
|
24
|
+
<template>
|
|
25
|
+
<slot v-bind="slotProps">
|
|
26
|
+
<!-- Default loading state if no slot provided -->
|
|
27
|
+
<div v-if="slotProps.isLoading" style="padding: 1rem; text-align: center;">
|
|
28
|
+
Discovering flow inputs...
|
|
29
|
+
</div>
|
|
30
|
+
<div v-else-if="slotProps.inputs.length === 0" style="padding: 1rem; text-align: center; color: #6b7280;">
|
|
31
|
+
No inputs found for this flow.
|
|
32
|
+
</div>
|
|
33
|
+
</slot>
|
|
34
|
+
</template>
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from "vue";
|
|
3
|
+
import type { FlowUploadStatus } from "@uploadista/client-core";
|
|
4
|
+
import { useFlowContext } from "./useFlowContext";
|
|
5
|
+
|
|
6
|
+
const flow = useFlowContext();
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Slot props provided to the default slot.
|
|
10
|
+
*/
|
|
11
|
+
export interface FlowProgressSlotProps {
|
|
12
|
+
/** Progress percentage (0-100) */
|
|
13
|
+
progress: number;
|
|
14
|
+
/** Bytes uploaded so far */
|
|
15
|
+
bytesUploaded: number;
|
|
16
|
+
/** Total bytes to upload (null if unknown) */
|
|
17
|
+
totalBytes: number | null;
|
|
18
|
+
/** Current status */
|
|
19
|
+
status: FlowUploadStatus;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const slotProps = computed<FlowProgressSlotProps>(() => ({
|
|
23
|
+
progress: flow.state.value.progress,
|
|
24
|
+
bytesUploaded: flow.state.value.bytesUploaded,
|
|
25
|
+
totalBytes: flow.state.value.totalBytes,
|
|
26
|
+
status: flow.state.value.status,
|
|
27
|
+
}));
|
|
28
|
+
</script>
|
|
29
|
+
|
|
30
|
+
<template>
|
|
31
|
+
<slot v-bind="slotProps">
|
|
32
|
+
<!-- Default progress display -->
|
|
33
|
+
<div v-if="slotProps.status === 'uploading' || slotProps.status === 'processing'" style="width: 100%;">
|
|
34
|
+
<div style="display: flex; justify-content: space-between; margin-bottom: 0.25rem; font-size: 0.875rem;">
|
|
35
|
+
<span>{{ slotProps.status === 'uploading' ? 'Uploading' : 'Processing' }}</span>
|
|
36
|
+
<span>{{ slotProps.progress.toFixed(1) }}%</span>
|
|
37
|
+
</div>
|
|
38
|
+
<div style="width: 100%; height: 0.5rem; background: #e5e7eb; border-radius: 0.25rem; overflow: hidden;">
|
|
39
|
+
<div
|
|
40
|
+
:style="{
|
|
41
|
+
width: `${slotProps.progress}%`,
|
|
42
|
+
height: '100%',
|
|
43
|
+
background: '#3b82f6',
|
|
44
|
+
transition: 'width 0.2s ease',
|
|
45
|
+
}"
|
|
46
|
+
/>
|
|
47
|
+
</div>
|
|
48
|
+
<div v-if="slotProps.totalBytes" style="margin-top: 0.25rem; font-size: 0.75rem; color: #6b7280;">
|
|
49
|
+
{{ (slotProps.bytesUploaded / 1024).toFixed(0) }} KB / {{ (slotProps.totalBytes / 1024).toFixed(0) }} KB
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
</slot>
|
|
53
|
+
</template>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { useFlowContext } from "./useFlowContext";
|
|
3
|
+
|
|
4
|
+
const flow = useFlowContext();
|
|
5
|
+
|
|
6
|
+
const handleClick = () => {
|
|
7
|
+
flow.reset();
|
|
8
|
+
};
|
|
9
|
+
</script>
|
|
10
|
+
|
|
11
|
+
<template>
|
|
12
|
+
<button
|
|
13
|
+
type="button"
|
|
14
|
+
@click="handleClick"
|
|
15
|
+
v-bind="$attrs"
|
|
16
|
+
>
|
|
17
|
+
<slot>Reset</slot>
|
|
18
|
+
</button>
|
|
19
|
+
</template>
|
|
20
|
+
|
|
21
|
+
<script lang="ts">
|
|
22
|
+
// Disable attribute inheritance so we can spread them manually
|
|
23
|
+
export default {
|
|
24
|
+
inheritAttrs: false,
|
|
25
|
+
};
|
|
26
|
+
</script>
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from "vue";
|
|
3
|
+
import type { FlowUploadStatus } from "@uploadista/client-core";
|
|
4
|
+
import type { TypedOutput } from "@uploadista/core/flow";
|
|
5
|
+
import { useFlowContext } from "./useFlowContext";
|
|
6
|
+
|
|
7
|
+
const flow = useFlowContext();
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Slot props provided to the default slot.
|
|
11
|
+
*/
|
|
12
|
+
export interface FlowStatusSlotProps {
|
|
13
|
+
/** Current status */
|
|
14
|
+
status: FlowUploadStatus;
|
|
15
|
+
/** Current node being processed (if any) */
|
|
16
|
+
currentNodeName: string | null;
|
|
17
|
+
/** Current node type (if any) */
|
|
18
|
+
currentNodeType: string | null;
|
|
19
|
+
/** Error (if status is error) */
|
|
20
|
+
error: Error | null;
|
|
21
|
+
/** Job ID (if started) */
|
|
22
|
+
jobId: string | null;
|
|
23
|
+
/** Whether flow has started */
|
|
24
|
+
flowStarted: boolean;
|
|
25
|
+
/** Flow outputs (if completed) */
|
|
26
|
+
flowOutputs: TypedOutput[] | null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const slotProps = computed<FlowStatusSlotProps>(() => ({
|
|
30
|
+
status: flow.state.value.status,
|
|
31
|
+
currentNodeName: flow.state.value.currentNodeName,
|
|
32
|
+
currentNodeType: flow.state.value.currentNodeType,
|
|
33
|
+
error: flow.state.value.error,
|
|
34
|
+
jobId: flow.state.value.jobId,
|
|
35
|
+
flowStarted: flow.state.value.flowStarted,
|
|
36
|
+
flowOutputs: flow.state.value.flowOutputs,
|
|
37
|
+
}));
|
|
38
|
+
</script>
|
|
39
|
+
|
|
40
|
+
<template>
|
|
41
|
+
<slot v-bind="slotProps">
|
|
42
|
+
<!-- Default status display - only show when not idle -->
|
|
43
|
+
<div v-if="slotProps.status !== 'idle'" style="padding: 0.5rem;">
|
|
44
|
+
<p style="margin: 0; font-size: 0.875rem; color: #6b7280;">
|
|
45
|
+
Status: <strong>{{ slotProps.status }}</strong>
|
|
46
|
+
</p>
|
|
47
|
+
<p v-if="slotProps.currentNodeName" style="margin: 0.25rem 0 0; font-size: 0.75rem; color: #9ca3af;">
|
|
48
|
+
Processing: {{ slotProps.currentNodeName }}
|
|
49
|
+
</p>
|
|
50
|
+
<p v-if="slotProps.jobId" style="margin: 0.25rem 0 0; font-size: 0.75rem; color: #9ca3af;">
|
|
51
|
+
Job ID: {{ slotProps.jobId }}
|
|
52
|
+
</p>
|
|
53
|
+
</div>
|
|
54
|
+
</slot>
|
|
55
|
+
</template>
|