@pichaflow/vue 0.1.0
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/LICENSE +21 -0
- package/dist/PichaFlowUpload.vue.d.ts +39 -0
- package/dist/index.css +111 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.mjs +207 -0
- package/dist/index.test.d.ts +1 -0
- package/dist/index.umd.js +211 -0
- package/package.json +45 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 James Sinkala (PichaFlow)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { UploadResponse } from '@pichaflow/sdk';
|
|
2
|
+
type __VLS_Props = {
|
|
3
|
+
apiKey: string;
|
|
4
|
+
baseUrl?: string;
|
|
5
|
+
uploadUrl?: string;
|
|
6
|
+
fetchUrl?: string;
|
|
7
|
+
useSecure?: boolean;
|
|
8
|
+
tags?: string[];
|
|
9
|
+
tenantId?: string;
|
|
10
|
+
directory?: string;
|
|
11
|
+
allowDeletion?: boolean;
|
|
12
|
+
};
|
|
13
|
+
declare var __VLS_1: {};
|
|
14
|
+
type __VLS_Slots = {} & {
|
|
15
|
+
icon?: (props: typeof __VLS_1) => any;
|
|
16
|
+
};
|
|
17
|
+
declare const __VLS_base: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {} & {
|
|
18
|
+
success: (response: UploadResponse) => any;
|
|
19
|
+
"success-all": (responses: UploadResponse[]) => any;
|
|
20
|
+
error: (error: any) => any;
|
|
21
|
+
progress: (progress: number) => any;
|
|
22
|
+
delete: (id: string) => any;
|
|
23
|
+
"delete-error": (error: any) => any;
|
|
24
|
+
}, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
25
|
+
onSuccess?: ((response: UploadResponse) => any) | undefined;
|
|
26
|
+
"onSuccess-all"?: ((responses: UploadResponse[]) => any) | undefined;
|
|
27
|
+
onError?: ((error: any) => any) | undefined;
|
|
28
|
+
onProgress?: ((progress: number) => any) | undefined;
|
|
29
|
+
onDelete?: ((id: string) => any) | undefined;
|
|
30
|
+
"onDelete-error"?: ((error: any) => any) | undefined;
|
|
31
|
+
}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
|
|
32
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
33
|
+
declare const _default: typeof __VLS_export;
|
|
34
|
+
export default _default;
|
|
35
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
36
|
+
new (): {
|
|
37
|
+
$slots: S;
|
|
38
|
+
};
|
|
39
|
+
};
|
package/dist/index.css
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
|
|
2
|
+
.picha-upload-zone[data-v-9fc15f6c] {
|
|
3
|
+
border: 2px dashed #e2e8f0;
|
|
4
|
+
border-radius: 0.75rem;
|
|
5
|
+
padding: 2rem;
|
|
6
|
+
transition: all 0.2s ease;
|
|
7
|
+
background: #f8fafc;
|
|
8
|
+
}
|
|
9
|
+
.picha-upload-zone.is-dragging[data-v-9fc15f6c] {
|
|
10
|
+
border-color: #3b82f6;
|
|
11
|
+
background: #eff6ff;
|
|
12
|
+
}
|
|
13
|
+
.content[data-v-9fc15f6c] {
|
|
14
|
+
text-align: center;
|
|
15
|
+
}
|
|
16
|
+
.icon[data-v-9fc15f6c] { width: 2rem; height: 2rem; color: #94a3b8; margin: 0 auto 1rem;
|
|
17
|
+
}
|
|
18
|
+
.text[data-v-9fc15f6c] { color: #64748b; font-size: 0.875rem;
|
|
19
|
+
}
|
|
20
|
+
.link[data-v-9fc15f6c] { color: #3b82f6; font-weight: 600; cursor: pointer;
|
|
21
|
+
}
|
|
22
|
+
.uploading-list[data-v-9fc15f6c] {
|
|
23
|
+
display: flex;
|
|
24
|
+
flex-direction: column;
|
|
25
|
+
gap: 1rem;
|
|
26
|
+
}
|
|
27
|
+
.task-item[data-v-9fc15f6c] {
|
|
28
|
+
background: white;
|
|
29
|
+
padding: 1rem;
|
|
30
|
+
border-radius: 0.5rem;
|
|
31
|
+
box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
|
|
32
|
+
border: 1px solid #f1f5f9;
|
|
33
|
+
}
|
|
34
|
+
.task-info[data-v-9fc15f6c] {
|
|
35
|
+
display: flex;
|
|
36
|
+
justify-content: space-between;
|
|
37
|
+
align-items: center;
|
|
38
|
+
margin-bottom: 0.5rem;
|
|
39
|
+
}
|
|
40
|
+
.file-name[data-v-9fc15f6c] {
|
|
41
|
+
font-size: 0.875rem;
|
|
42
|
+
font-weight: 500;
|
|
43
|
+
color: #334155;
|
|
44
|
+
white-space: nowrap;
|
|
45
|
+
overflow: hidden;
|
|
46
|
+
text-overflow: ellipsis;
|
|
47
|
+
max-width: 70%;
|
|
48
|
+
}
|
|
49
|
+
.status-text[data-v-9fc15f6c] {
|
|
50
|
+
font-size: 0.75rem;
|
|
51
|
+
font-weight: 600;
|
|
52
|
+
color: #64748b;
|
|
53
|
+
}
|
|
54
|
+
.status-text.success[data-v-9fc15f6c] { color: #10b981;
|
|
55
|
+
}
|
|
56
|
+
.status-text.error[data-v-9fc15f6c] { color: #ef4444;
|
|
57
|
+
}
|
|
58
|
+
.status-text.deleting[data-v-9fc15f6c] { color: #f59e0b;
|
|
59
|
+
}
|
|
60
|
+
.delete-btn[data-v-9fc15f6c] {
|
|
61
|
+
background: none;
|
|
62
|
+
border: none;
|
|
63
|
+
color: #94a3b8;
|
|
64
|
+
cursor: pointer;
|
|
65
|
+
padding: 0.25rem;
|
|
66
|
+
border-radius: 0.25rem;
|
|
67
|
+
display: flex;
|
|
68
|
+
align-items: center;
|
|
69
|
+
justify-content: center;
|
|
70
|
+
transition: all 0.2s;
|
|
71
|
+
}
|
|
72
|
+
.delete-btn[data-v-9fc15f6c]:hover:not(:disabled) {
|
|
73
|
+
color: #ef4444;
|
|
74
|
+
background: #fef2f2;
|
|
75
|
+
}
|
|
76
|
+
.delete-btn[data-v-9fc15f6c]:disabled {
|
|
77
|
+
opacity: 0.5;
|
|
78
|
+
cursor: not-allowed;
|
|
79
|
+
}
|
|
80
|
+
.progress-bar[data-v-9fc15f6c] { background: #e2e8f0; height: 0.5rem; border-radius: 999px; overflow: hidden;
|
|
81
|
+
}
|
|
82
|
+
.progress-fill[data-v-9fc15f6c] { background: #3b82f6; height: 100%; transition: width 0.1s ease;
|
|
83
|
+
}
|
|
84
|
+
.progress-fill.success[data-v-9fc15f6c] { background: #10b981;
|
|
85
|
+
}
|
|
86
|
+
.progress-fill.error[data-v-9fc15f6c] { background: #ef4444;
|
|
87
|
+
}
|
|
88
|
+
.error-text[data-v-9fc15f6c] {
|
|
89
|
+
margin-top: 0.25rem;
|
|
90
|
+
font-size: 0.75rem;
|
|
91
|
+
color: #ef4444;
|
|
92
|
+
}
|
|
93
|
+
.add-more[data-v-9fc15f6c] { text-align: center; margin-top: 1.5rem;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/* Simple dark mode support since component might be used anywhere */
|
|
97
|
+
@media (prefers-color-scheme: dark) {
|
|
98
|
+
.picha-upload-zone[data-v-9fc15f6c] {
|
|
99
|
+
background: #0f172a;
|
|
100
|
+
border-color: #1e293b;
|
|
101
|
+
}
|
|
102
|
+
.task-item[data-v-9fc15f6c] {
|
|
103
|
+
background: #1e293b;
|
|
104
|
+
border-color: #334155;
|
|
105
|
+
}
|
|
106
|
+
.file-name[data-v-9fc15f6c] { color: #f8fafc;
|
|
107
|
+
}
|
|
108
|
+
.progress-bar[data-v-9fc15f6c] { background: #334155;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/*$vite$:1*/
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as PichaFlowUpload } from './PichaFlowUpload.vue';
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { Fragment, createCommentVNode, createElementBlock, createElementVNode, createStaticVNode, createTextVNode, defineComponent, normalizeClass, normalizeStyle, openBlock, ref, renderList, renderSlot, toDisplayString, withModifiers } from "vue";
|
|
2
|
+
import { PichaFlowClient, optimizeImageForUpload } from "@pichaflow/sdk";
|
|
3
|
+
//#region src/PichaFlowUpload.vue?vue&type=script&setup=true&lang.ts
|
|
4
|
+
var _hoisted_1 = {
|
|
5
|
+
key: 0,
|
|
6
|
+
class: "content"
|
|
7
|
+
};
|
|
8
|
+
var _hoisted_2 = { class: "text" };
|
|
9
|
+
var _hoisted_3 = { class: "link" };
|
|
10
|
+
var _hoisted_4 = {
|
|
11
|
+
key: 1,
|
|
12
|
+
class: "uploading-list"
|
|
13
|
+
};
|
|
14
|
+
var _hoisted_5 = { class: "task-info" };
|
|
15
|
+
var _hoisted_6 = { class: "file-name" };
|
|
16
|
+
var _hoisted_7 = { style: {
|
|
17
|
+
"display": "flex",
|
|
18
|
+
"align-items": "center",
|
|
19
|
+
"gap": "0.5rem"
|
|
20
|
+
} };
|
|
21
|
+
var _hoisted_8 = ["onClick", "disabled"];
|
|
22
|
+
var _hoisted_9 = { class: "progress-bar" };
|
|
23
|
+
var _hoisted_10 = {
|
|
24
|
+
key: 0,
|
|
25
|
+
class: "error-text"
|
|
26
|
+
};
|
|
27
|
+
var _hoisted_11 = { class: "add-more mt-4" };
|
|
28
|
+
var _hoisted_12 = { class: "text text-sm" };
|
|
29
|
+
var _hoisted_13 = { class: "link" };
|
|
30
|
+
var PichaFlowUpload_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineComponent({
|
|
31
|
+
__name: "PichaFlowUpload",
|
|
32
|
+
props: {
|
|
33
|
+
apiKey: {},
|
|
34
|
+
baseUrl: {},
|
|
35
|
+
uploadUrl: {},
|
|
36
|
+
fetchUrl: {},
|
|
37
|
+
useSecure: { type: Boolean },
|
|
38
|
+
tags: {},
|
|
39
|
+
tenantId: {},
|
|
40
|
+
directory: {},
|
|
41
|
+
allowDeletion: { type: Boolean }
|
|
42
|
+
},
|
|
43
|
+
emits: [
|
|
44
|
+
"success",
|
|
45
|
+
"success-all",
|
|
46
|
+
"error",
|
|
47
|
+
"progress",
|
|
48
|
+
"delete",
|
|
49
|
+
"delete-error"
|
|
50
|
+
],
|
|
51
|
+
setup(__props, { emit: __emit }) {
|
|
52
|
+
const props = __props;
|
|
53
|
+
const emit = __emit;
|
|
54
|
+
const isDragging = ref(false);
|
|
55
|
+
const tasks = ref([]);
|
|
56
|
+
const client = new PichaFlowClient({
|
|
57
|
+
apiKey: props.apiKey,
|
|
58
|
+
baseUrl: props.baseUrl,
|
|
59
|
+
uploadUrl: props.uploadUrl,
|
|
60
|
+
fetchUrl: props.fetchUrl,
|
|
61
|
+
tenantId: props.tenantId
|
|
62
|
+
});
|
|
63
|
+
const handleFiles = async (files) => {
|
|
64
|
+
if (files.length === 0) return;
|
|
65
|
+
const imageFiles = files.filter((f) => f.type.startsWith("image/"));
|
|
66
|
+
if (imageFiles.length === 0) {
|
|
67
|
+
emit("error", /* @__PURE__ */ new Error("Only image files are supported."));
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const previewTasks = imageFiles.map((file) => ({
|
|
71
|
+
id: crypto.randomUUID(),
|
|
72
|
+
file,
|
|
73
|
+
previewUrl: URL.createObjectURL(file),
|
|
74
|
+
progress: 0,
|
|
75
|
+
status: "pending"
|
|
76
|
+
}));
|
|
77
|
+
tasks.value = [...tasks.value, ...previewTasks];
|
|
78
|
+
Promise.all(previewTasks.map(async (task) => {
|
|
79
|
+
const updateTask = (updates) => {
|
|
80
|
+
const index = tasks.value.findIndex((t) => t.id === task.id);
|
|
81
|
+
if (index !== -1) tasks.value[index] = {
|
|
82
|
+
...tasks.value[index],
|
|
83
|
+
...updates
|
|
84
|
+
};
|
|
85
|
+
};
|
|
86
|
+
try {
|
|
87
|
+
const optimizedFile = await optimizeImageForUpload(task.file);
|
|
88
|
+
updateTask({
|
|
89
|
+
status: "uploading",
|
|
90
|
+
file: optimizedFile
|
|
91
|
+
});
|
|
92
|
+
const options = {
|
|
93
|
+
tags: props.tags,
|
|
94
|
+
directory: props.directory,
|
|
95
|
+
onProgress: (p) => {
|
|
96
|
+
updateTask({ progress: p });
|
|
97
|
+
emit("progress", p);
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
const response = props.useSecure ? await client.secureUpload(optimizedFile, options) : await client.upload(optimizedFile, options);
|
|
101
|
+
updateTask({
|
|
102
|
+
status: "success",
|
|
103
|
+
progress: 100,
|
|
104
|
+
response
|
|
105
|
+
});
|
|
106
|
+
emit("success", response);
|
|
107
|
+
} catch (err) {
|
|
108
|
+
updateTask({
|
|
109
|
+
status: "error",
|
|
110
|
+
error: err.message || "Upload failed"
|
|
111
|
+
});
|
|
112
|
+
emit("error", err);
|
|
113
|
+
}
|
|
114
|
+
})).then(() => {
|
|
115
|
+
if (tasks.value.every((t) => t.status === "success" || t.status === "error")) {
|
|
116
|
+
const successfulResponses = tasks.value.filter((t) => t.status === "success").map((t) => t.response);
|
|
117
|
+
if (successfulResponses.length > 0) emit("success-all", successfulResponses);
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
};
|
|
121
|
+
const onDrop = (e) => {
|
|
122
|
+
isDragging.value = false;
|
|
123
|
+
const files = Array.from(e.dataTransfer?.files || []);
|
|
124
|
+
if (files.length) handleFiles(files);
|
|
125
|
+
};
|
|
126
|
+
const handleDelete = async (taskId, assetId) => {
|
|
127
|
+
const index = tasks.value.findIndex((t) => t.id === taskId);
|
|
128
|
+
if (index === -1) return;
|
|
129
|
+
tasks.value[index].status = "deleting";
|
|
130
|
+
try {
|
|
131
|
+
await client.deleteAsset(assetId);
|
|
132
|
+
tasks.value = tasks.value.filter((t) => t.id !== taskId);
|
|
133
|
+
emit("delete", assetId);
|
|
134
|
+
} catch (err) {
|
|
135
|
+
const errorIndex = tasks.value.findIndex((t) => t.id === taskId);
|
|
136
|
+
if (errorIndex !== -1) tasks.value[errorIndex].status = "success";
|
|
137
|
+
emit("delete-error", err);
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
const onFileSelect = (e) => {
|
|
141
|
+
const target = e.target;
|
|
142
|
+
const files = Array.from(target.files || []);
|
|
143
|
+
if (files.length) handleFiles(files);
|
|
144
|
+
target.value = "";
|
|
145
|
+
};
|
|
146
|
+
return (_ctx, _cache) => {
|
|
147
|
+
return openBlock(), createElementBlock("div", {
|
|
148
|
+
class: normalizeClass(["picha-upload-zone", { "is-dragging": isDragging.value }]),
|
|
149
|
+
onDragover: _cache[0] || (_cache[0] = withModifiers(($event) => isDragging.value = true, ["prevent"])),
|
|
150
|
+
onDragleave: _cache[1] || (_cache[1] = withModifiers(($event) => isDragging.value = false, ["prevent"])),
|
|
151
|
+
onDrop: withModifiers(onDrop, ["prevent"])
|
|
152
|
+
}, [tasks.value.length === 0 ? (openBlock(), createElementBlock("div", _hoisted_1, [renderSlot(_ctx.$slots, "icon", {}, () => [_cache[2] || (_cache[2] = createElementVNode("svg", {
|
|
153
|
+
class: "icon",
|
|
154
|
+
fill: "none",
|
|
155
|
+
stroke: "currentColor",
|
|
156
|
+
viewBox: "0 0 24 24"
|
|
157
|
+
}, [createElementVNode("path", {
|
|
158
|
+
"stroke-linecap": "round",
|
|
159
|
+
"stroke-linejoin": "round",
|
|
160
|
+
"stroke-width": "2",
|
|
161
|
+
d: "M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12"
|
|
162
|
+
})], -1))], true), createElementVNode("p", _hoisted_2, [_cache[4] || (_cache[4] = createTextVNode("Drag & drop or ", -1)), createElementVNode("label", _hoisted_3, [_cache[3] || (_cache[3] = createTextVNode("browse", -1)), createElementVNode("input", {
|
|
163
|
+
type: "file",
|
|
164
|
+
accept: "image/*",
|
|
165
|
+
multiple: "",
|
|
166
|
+
onChange: onFileSelect,
|
|
167
|
+
hidden: ""
|
|
168
|
+
}, null, 32)])])])) : (openBlock(), createElementBlock("div", _hoisted_4, [(openBlock(true), createElementBlock(Fragment, null, renderList(tasks.value, (task) => {
|
|
169
|
+
return openBlock(), createElementBlock("div", {
|
|
170
|
+
key: task.id,
|
|
171
|
+
class: "task-item"
|
|
172
|
+
}, [
|
|
173
|
+
createElementVNode("div", _hoisted_5, [createElementVNode("span", _hoisted_6, toDisplayString(task.file.name), 1), createElementVNode("div", _hoisted_7, [createElementVNode("span", { class: normalizeClass(["status-text", task.status]) }, toDisplayString(task.status === "success" ? "Uploaded" : task.status === "error" ? "Failed" : task.status === "deleting" ? "Deleting..." : `${task.progress}%`), 3), props.allowDeletion && (task.status === "success" || task.status === "deleting") && task.response?.id ? (openBlock(), createElementBlock("button", {
|
|
174
|
+
key: 0,
|
|
175
|
+
onClick: ($event) => handleDelete(task.id, task.response.id),
|
|
176
|
+
class: "delete-btn",
|
|
177
|
+
title: "Delete asset",
|
|
178
|
+
disabled: task.status === "deleting"
|
|
179
|
+
}, [..._cache[5] || (_cache[5] = [createStaticVNode("<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" data-v-9fc15f6c><polyline points=\"3 6 5 6 21 6\" data-v-9fc15f6c></polyline><path d=\"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2\" data-v-9fc15f6c></path><line x1=\"10\" y1=\"11\" x2=\"10\" y2=\"17\" data-v-9fc15f6c></line><line x1=\"14\" y1=\"11\" x2=\"14\" y2=\"17\" data-v-9fc15f6c></line></svg>", 1)])], 8, _hoisted_8)) : createCommentVNode("", true)])]),
|
|
180
|
+
createElementVNode("div", _hoisted_9, [createElementVNode("div", {
|
|
181
|
+
class: normalizeClass(["progress-fill", task.status]),
|
|
182
|
+
style: normalizeStyle({ width: `${task.progress}%` })
|
|
183
|
+
}, null, 6)]),
|
|
184
|
+
task.error ? (openBlock(), createElementBlock("p", _hoisted_10, toDisplayString(task.error), 1)) : createCommentVNode("", true)
|
|
185
|
+
]);
|
|
186
|
+
}), 128)), createElementVNode("div", _hoisted_11, [createElementVNode("p", _hoisted_12, [createElementVNode("label", _hoisted_13, [_cache[6] || (_cache[6] = createTextVNode("Upload more files", -1)), createElementVNode("input", {
|
|
187
|
+
type: "file",
|
|
188
|
+
accept: "image/*",
|
|
189
|
+
multiple: "",
|
|
190
|
+
onChange: onFileSelect,
|
|
191
|
+
hidden: ""
|
|
192
|
+
}, null, 32)])])])]))], 34);
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
//#endregion
|
|
197
|
+
//#region \0plugin-vue:export-helper
|
|
198
|
+
var _plugin_vue_export_helper_default = (sfc, props) => {
|
|
199
|
+
const target = sfc.__vccOpts || sfc;
|
|
200
|
+
for (const [key, val] of props) target[key] = val;
|
|
201
|
+
return target;
|
|
202
|
+
};
|
|
203
|
+
//#endregion
|
|
204
|
+
//#region src/PichaFlowUpload.vue
|
|
205
|
+
var PichaFlowUpload_default = /* @__PURE__ */ _plugin_vue_export_helper_default(PichaFlowUpload_vue_vue_type_script_setup_true_lang_default, [["__scopeId", "data-v-9fc15f6c"]]);
|
|
206
|
+
//#endregion
|
|
207
|
+
export { PichaFlowUpload_default as PichaFlowUpload };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
(function(global, factory) {
|
|
2
|
+
typeof exports === "object" && typeof module !== "undefined" ? factory(exports, require("vue"), require("@pichaflow/sdk")) : typeof define === "function" && define.amd ? define([
|
|
3
|
+
"exports",
|
|
4
|
+
"vue",
|
|
5
|
+
"@pichaflow/sdk"
|
|
6
|
+
], factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self, factory(global.PichaFlowVue = {}, global.Vue, global._pichaflow_sdk));
|
|
7
|
+
})(this, function(exports, vue, _pichaflow_sdk) {
|
|
8
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
9
|
+
//#region src/PichaFlowUpload.vue?vue&type=script&setup=true&lang.ts
|
|
10
|
+
var _hoisted_1 = {
|
|
11
|
+
key: 0,
|
|
12
|
+
class: "content"
|
|
13
|
+
};
|
|
14
|
+
var _hoisted_2 = { class: "text" };
|
|
15
|
+
var _hoisted_3 = { class: "link" };
|
|
16
|
+
var _hoisted_4 = {
|
|
17
|
+
key: 1,
|
|
18
|
+
class: "uploading-list"
|
|
19
|
+
};
|
|
20
|
+
var _hoisted_5 = { class: "task-info" };
|
|
21
|
+
var _hoisted_6 = { class: "file-name" };
|
|
22
|
+
var _hoisted_7 = { style: {
|
|
23
|
+
"display": "flex",
|
|
24
|
+
"align-items": "center",
|
|
25
|
+
"gap": "0.5rem"
|
|
26
|
+
} };
|
|
27
|
+
var _hoisted_8 = ["onClick", "disabled"];
|
|
28
|
+
var _hoisted_9 = { class: "progress-bar" };
|
|
29
|
+
var _hoisted_10 = {
|
|
30
|
+
key: 0,
|
|
31
|
+
class: "error-text"
|
|
32
|
+
};
|
|
33
|
+
var _hoisted_11 = { class: "add-more mt-4" };
|
|
34
|
+
var _hoisted_12 = { class: "text text-sm" };
|
|
35
|
+
var _hoisted_13 = { class: "link" };
|
|
36
|
+
var PichaFlowUpload_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ (0, vue.defineComponent)({
|
|
37
|
+
__name: "PichaFlowUpload",
|
|
38
|
+
props: {
|
|
39
|
+
apiKey: {},
|
|
40
|
+
baseUrl: {},
|
|
41
|
+
uploadUrl: {},
|
|
42
|
+
fetchUrl: {},
|
|
43
|
+
useSecure: { type: Boolean },
|
|
44
|
+
tags: {},
|
|
45
|
+
tenantId: {},
|
|
46
|
+
directory: {},
|
|
47
|
+
allowDeletion: { type: Boolean }
|
|
48
|
+
},
|
|
49
|
+
emits: [
|
|
50
|
+
"success",
|
|
51
|
+
"success-all",
|
|
52
|
+
"error",
|
|
53
|
+
"progress",
|
|
54
|
+
"delete",
|
|
55
|
+
"delete-error"
|
|
56
|
+
],
|
|
57
|
+
setup(__props, { emit: __emit }) {
|
|
58
|
+
const props = __props;
|
|
59
|
+
const emit = __emit;
|
|
60
|
+
const isDragging = (0, vue.ref)(false);
|
|
61
|
+
const tasks = (0, vue.ref)([]);
|
|
62
|
+
const client = new _pichaflow_sdk.PichaFlowClient({
|
|
63
|
+
apiKey: props.apiKey,
|
|
64
|
+
baseUrl: props.baseUrl,
|
|
65
|
+
uploadUrl: props.uploadUrl,
|
|
66
|
+
fetchUrl: props.fetchUrl,
|
|
67
|
+
tenantId: props.tenantId
|
|
68
|
+
});
|
|
69
|
+
const handleFiles = async (files) => {
|
|
70
|
+
if (files.length === 0) return;
|
|
71
|
+
const imageFiles = files.filter((f) => f.type.startsWith("image/"));
|
|
72
|
+
if (imageFiles.length === 0) {
|
|
73
|
+
emit("error", /* @__PURE__ */ new Error("Only image files are supported."));
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const previewTasks = imageFiles.map((file) => ({
|
|
77
|
+
id: crypto.randomUUID(),
|
|
78
|
+
file,
|
|
79
|
+
previewUrl: URL.createObjectURL(file),
|
|
80
|
+
progress: 0,
|
|
81
|
+
status: "pending"
|
|
82
|
+
}));
|
|
83
|
+
tasks.value = [...tasks.value, ...previewTasks];
|
|
84
|
+
Promise.all(previewTasks.map(async (task) => {
|
|
85
|
+
const updateTask = (updates) => {
|
|
86
|
+
const index = tasks.value.findIndex((t) => t.id === task.id);
|
|
87
|
+
if (index !== -1) tasks.value[index] = {
|
|
88
|
+
...tasks.value[index],
|
|
89
|
+
...updates
|
|
90
|
+
};
|
|
91
|
+
};
|
|
92
|
+
try {
|
|
93
|
+
const optimizedFile = await (0, _pichaflow_sdk.optimizeImageForUpload)(task.file);
|
|
94
|
+
updateTask({
|
|
95
|
+
status: "uploading",
|
|
96
|
+
file: optimizedFile
|
|
97
|
+
});
|
|
98
|
+
const options = {
|
|
99
|
+
tags: props.tags,
|
|
100
|
+
directory: props.directory,
|
|
101
|
+
onProgress: (p) => {
|
|
102
|
+
updateTask({ progress: p });
|
|
103
|
+
emit("progress", p);
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
const response = props.useSecure ? await client.secureUpload(optimizedFile, options) : await client.upload(optimizedFile, options);
|
|
107
|
+
updateTask({
|
|
108
|
+
status: "success",
|
|
109
|
+
progress: 100,
|
|
110
|
+
response
|
|
111
|
+
});
|
|
112
|
+
emit("success", response);
|
|
113
|
+
} catch (err) {
|
|
114
|
+
updateTask({
|
|
115
|
+
status: "error",
|
|
116
|
+
error: err.message || "Upload failed"
|
|
117
|
+
});
|
|
118
|
+
emit("error", err);
|
|
119
|
+
}
|
|
120
|
+
})).then(() => {
|
|
121
|
+
if (tasks.value.every((t) => t.status === "success" || t.status === "error")) {
|
|
122
|
+
const successfulResponses = tasks.value.filter((t) => t.status === "success").map((t) => t.response);
|
|
123
|
+
if (successfulResponses.length > 0) emit("success-all", successfulResponses);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
};
|
|
127
|
+
const onDrop = (e) => {
|
|
128
|
+
isDragging.value = false;
|
|
129
|
+
const files = Array.from(e.dataTransfer?.files || []);
|
|
130
|
+
if (files.length) handleFiles(files);
|
|
131
|
+
};
|
|
132
|
+
const handleDelete = async (taskId, assetId) => {
|
|
133
|
+
const index = tasks.value.findIndex((t) => t.id === taskId);
|
|
134
|
+
if (index === -1) return;
|
|
135
|
+
tasks.value[index].status = "deleting";
|
|
136
|
+
try {
|
|
137
|
+
await client.deleteAsset(assetId);
|
|
138
|
+
tasks.value = tasks.value.filter((t) => t.id !== taskId);
|
|
139
|
+
emit("delete", assetId);
|
|
140
|
+
} catch (err) {
|
|
141
|
+
const errorIndex = tasks.value.findIndex((t) => t.id === taskId);
|
|
142
|
+
if (errorIndex !== -1) tasks.value[errorIndex].status = "success";
|
|
143
|
+
emit("delete-error", err);
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
const onFileSelect = (e) => {
|
|
147
|
+
const target = e.target;
|
|
148
|
+
const files = Array.from(target.files || []);
|
|
149
|
+
if (files.length) handleFiles(files);
|
|
150
|
+
target.value = "";
|
|
151
|
+
};
|
|
152
|
+
return (_ctx, _cache) => {
|
|
153
|
+
return (0, vue.openBlock)(), (0, vue.createElementBlock)("div", {
|
|
154
|
+
class: (0, vue.normalizeClass)(["picha-upload-zone", { "is-dragging": isDragging.value }]),
|
|
155
|
+
onDragover: _cache[0] || (_cache[0] = (0, vue.withModifiers)(($event) => isDragging.value = true, ["prevent"])),
|
|
156
|
+
onDragleave: _cache[1] || (_cache[1] = (0, vue.withModifiers)(($event) => isDragging.value = false, ["prevent"])),
|
|
157
|
+
onDrop: (0, vue.withModifiers)(onDrop, ["prevent"])
|
|
158
|
+
}, [tasks.value.length === 0 ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_1, [(0, vue.renderSlot)(_ctx.$slots, "icon", {}, () => [_cache[2] || (_cache[2] = (0, vue.createElementVNode)("svg", {
|
|
159
|
+
class: "icon",
|
|
160
|
+
fill: "none",
|
|
161
|
+
stroke: "currentColor",
|
|
162
|
+
viewBox: "0 0 24 24"
|
|
163
|
+
}, [(0, vue.createElementVNode)("path", {
|
|
164
|
+
"stroke-linecap": "round",
|
|
165
|
+
"stroke-linejoin": "round",
|
|
166
|
+
"stroke-width": "2",
|
|
167
|
+
d: "M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12"
|
|
168
|
+
})], -1))], true), (0, vue.createElementVNode)("p", _hoisted_2, [_cache[4] || (_cache[4] = (0, vue.createTextVNode)("Drag & drop or ", -1)), (0, vue.createElementVNode)("label", _hoisted_3, [_cache[3] || (_cache[3] = (0, vue.createTextVNode)("browse", -1)), (0, vue.createElementVNode)("input", {
|
|
169
|
+
type: "file",
|
|
170
|
+
accept: "image/*",
|
|
171
|
+
multiple: "",
|
|
172
|
+
onChange: onFileSelect,
|
|
173
|
+
hidden: ""
|
|
174
|
+
}, null, 32)])])])) : ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_4, [((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(tasks.value, (task) => {
|
|
175
|
+
return (0, vue.openBlock)(), (0, vue.createElementBlock)("div", {
|
|
176
|
+
key: task.id,
|
|
177
|
+
class: "task-item"
|
|
178
|
+
}, [
|
|
179
|
+
(0, vue.createElementVNode)("div", _hoisted_5, [(0, vue.createElementVNode)("span", _hoisted_6, (0, vue.toDisplayString)(task.file.name), 1), (0, vue.createElementVNode)("div", _hoisted_7, [(0, vue.createElementVNode)("span", { class: (0, vue.normalizeClass)(["status-text", task.status]) }, (0, vue.toDisplayString)(task.status === "success" ? "Uploaded" : task.status === "error" ? "Failed" : task.status === "deleting" ? "Deleting..." : `${task.progress}%`), 3), props.allowDeletion && (task.status === "success" || task.status === "deleting") && task.response?.id ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("button", {
|
|
180
|
+
key: 0,
|
|
181
|
+
onClick: ($event) => handleDelete(task.id, task.response.id),
|
|
182
|
+
class: "delete-btn",
|
|
183
|
+
title: "Delete asset",
|
|
184
|
+
disabled: task.status === "deleting"
|
|
185
|
+
}, [..._cache[5] || (_cache[5] = [(0, vue.createStaticVNode)("<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" data-v-9fc15f6c><polyline points=\"3 6 5 6 21 6\" data-v-9fc15f6c></polyline><path d=\"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2\" data-v-9fc15f6c></path><line x1=\"10\" y1=\"11\" x2=\"10\" y2=\"17\" data-v-9fc15f6c></line><line x1=\"14\" y1=\"11\" x2=\"14\" y2=\"17\" data-v-9fc15f6c></line></svg>", 1)])], 8, _hoisted_8)) : (0, vue.createCommentVNode)("", true)])]),
|
|
186
|
+
(0, vue.createElementVNode)("div", _hoisted_9, [(0, vue.createElementVNode)("div", {
|
|
187
|
+
class: (0, vue.normalizeClass)(["progress-fill", task.status]),
|
|
188
|
+
style: (0, vue.normalizeStyle)({ width: `${task.progress}%` })
|
|
189
|
+
}, null, 6)]),
|
|
190
|
+
task.error ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("p", _hoisted_10, (0, vue.toDisplayString)(task.error), 1)) : (0, vue.createCommentVNode)("", true)
|
|
191
|
+
]);
|
|
192
|
+
}), 128)), (0, vue.createElementVNode)("div", _hoisted_11, [(0, vue.createElementVNode)("p", _hoisted_12, [(0, vue.createElementVNode)("label", _hoisted_13, [_cache[6] || (_cache[6] = (0, vue.createTextVNode)("Upload more files", -1)), (0, vue.createElementVNode)("input", {
|
|
193
|
+
type: "file",
|
|
194
|
+
accept: "image/*",
|
|
195
|
+
multiple: "",
|
|
196
|
+
onChange: onFileSelect,
|
|
197
|
+
hidden: ""
|
|
198
|
+
}, null, 32)])])])]))], 34);
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
//#endregion
|
|
203
|
+
//#region \0plugin-vue:export-helper
|
|
204
|
+
var _plugin_vue_export_helper_default = (sfc, props) => {
|
|
205
|
+
const target = sfc.__vccOpts || sfc;
|
|
206
|
+
for (const [key, val] of props) target[key] = val;
|
|
207
|
+
return target;
|
|
208
|
+
};
|
|
209
|
+
//#endregion
|
|
210
|
+
exports.PichaFlowUpload = /* @__PURE__ */ _plugin_vue_export_helper_default(PichaFlowUpload_vue_vue_type_script_setup_true_lang_default, [["__scopeId", "data-v-9fc15f6c"]]);
|
|
211
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pichaflow/vue",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "PichaFlow SDK for Vue - Edge-native media transformations.",
|
|
5
|
+
"main": "dist/index.umd.js",
|
|
6
|
+
"module": "dist/index.mjs",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./src/index.ts",
|
|
12
|
+
"require": "./src/index.ts",
|
|
13
|
+
"default": "./src/index.ts"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "https://github.com/pichaflow/vue-plugin.git"
|
|
23
|
+
},
|
|
24
|
+
"peerDependencies": {
|
|
25
|
+
"vue": ">=3"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@pichaflow/sdk": "0.1.0"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@testing-library/vue": "^8.1.0",
|
|
32
|
+
"@vue/language-core": "^3.3.7",
|
|
33
|
+
"@vue/test-utils": "^2.4.10",
|
|
34
|
+
"jsdom": "^29.1.1",
|
|
35
|
+
"vite-plugin-css-injected-by-js": "^5.0.1",
|
|
36
|
+
"vitest": "^4.1.8",
|
|
37
|
+
"vue-tsc": "^3.3.7"
|
|
38
|
+
},
|
|
39
|
+
"scripts": {
|
|
40
|
+
"build": "vite build",
|
|
41
|
+
"dev": "vite build --watch",
|
|
42
|
+
"test": "vitest run",
|
|
43
|
+
"test:watch": "vitest"
|
|
44
|
+
}
|
|
45
|
+
}
|