@uploadista/vue 0.0.3 â 0.0.4
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 -0
- package/dist/components/index.mjs +1 -0
- package/dist/components-DjgxUKKp.mjs +2 -0
- package/dist/components-DjgxUKKp.mjs.map +1 -0
- package/dist/components-DyNRHWp9.css +229 -0
- package/dist/components-DyNRHWp9.css.map +1 -0
- package/dist/composables/index.d.mts +2 -0
- package/dist/composables/index.mjs +1 -0
- package/dist/composables-Biblh8X9.mjs +2 -0
- package/dist/composables-Biblh8X9.mjs.map +1 -0
- package/dist/index-B848U2ke.d.mts +1409 -0
- package/dist/index-B848U2ke.d.mts.map +1 -0
- package/dist/index-Cg1-nrSi.d.mts +254 -0
- package/dist/index-Cg1-nrSi.d.mts.map +1 -0
- package/dist/index-CwuMGgQY.d.mts +62 -0
- package/dist/index-CwuMGgQY.d.mts.map +1 -0
- package/dist/index-DbZiny0Q.d.mts +39 -0
- package/dist/index-DbZiny0Q.d.mts.map +1 -0
- package/dist/index.d.mts +5 -0
- package/dist/index.mjs +1 -0
- package/dist/plugin-HH1lTjcq.mjs +2 -0
- package/dist/plugin-HH1lTjcq.mjs.map +1 -0
- package/dist/providers/index.d.mts +2 -0
- package/dist/providers/index.mjs +1 -0
- package/dist/providers-BQhKoGMk.mjs +2 -0
- package/dist/providers-BQhKoGMk.mjs.map +1 -0
- package/dist/utils/index.d.mts +2 -0
- package/dist/utils/index.mjs +1 -0
- package/dist/utils-BSoozMHK.mjs +2 -0
- package/dist/utils-BSoozMHK.mjs.map +1 -0
- package/package.json +28 -9
- package/src/providers/UploadistaProvider.vue +2 -2
- package/tsdown.config.ts +20 -0
- package/.turbo/turbo-check.log +0 -240
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"../utils-BSoozMHK.mjs";import{i as e,n as t,r as n,t as r}from"../components-DjgxUKKp.mjs";import"../plugin-HH1lTjcq.mjs";import"../composables-Biblh8X9.mjs";export{e as FlowUploadList,n as FlowUploadZone,t as UploadList,r as UploadZone};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{m as e}from"./utils-BSoozMHK.mjs";import{a as t,n,r,s as i}from"./composables-Biblh8X9.mjs";import{Fragment as a,computed as o,createCommentVNode as s,createElementBlock as c,createElementVNode as l,createTextVNode as u,defineComponent as d,normalizeClass as f,normalizeStyle as p,openBlock as m,ref as h,renderList as g,renderSlot as _,toDisplayString as v,unref as y,withKeys as b,withModifiers as x}from"vue";const S={class:`flow-upload-list`},C={class:`flow-upload-list__item-header`},w={class:`flow-upload-list__item-icon`},T={class:`flow-upload-list__item-name`},E={class:`flow-upload-list__item-details`},D={class:`flow-upload-list__item-size`},O={key:0,class:`flow-upload-list__item-job`},ee={key:0,class:`flow-upload-list__item-progress`},k={class:`flow-upload-list__progress-bar`},A={class:`flow-upload-list__progress-text`},j={key:1,class:`flow-upload-list__item-error`},M={key:2,class:`flow-upload-list__item-success`};var N=d({__name:`FlowUploadList`,props:{uploads:{},filter:{type:Function},sortBy:{type:Function}},setup(t){let n=t,r=o(()=>{let e=n.uploads;return n.filter&&(e=e.filter(n.filter)),n.sortBy&&(e=[...e].sort(n.sortBy)),e}),i=o(()=>({pending:r.value.filter(e=>e.status===`pending`),uploading:r.value.filter(e=>e.status===`uploading`),success:r.value.filter(e=>e.status===`success`),error:r.value.filter(e=>e.status===`error`),aborted:r.value.filter(e=>e.status===`aborted`)})),u=e=>{if(e===0)return`0 Bytes`;let t=1024,n=[`Bytes`,`KB`,`MB`,`GB`],r=Math.floor(Math.log(e)/Math.log(t));return`${parseFloat((e/t**r).toFixed(2))} ${n[r]}`},d=e=>{switch(e){case`pending`:return`âŗ`;case`uploading`:return`đ¤`;case`success`:return`â
`;case`error`:return`â`;case`aborted`:return`âšī¸`;default:return`â`}},h=e=>{switch(e){case`pending`:return`#6c757d`;case`uploading`:return`#007bff`;case`success`:return`#28a745`;case`error`:return`#dc3545`;case`aborted`:return`#6c757d`;default:return`#6c757d`}};return(t,n)=>(m(),c(`div`,S,[_(t.$slots,`default`,{items:r.value,itemsByStatus:i.value},()=>[s(` Default rendering: simple list of flow upload items `),(m(!0),c(a,null,g(r.value,(n,r)=>(m(),c(`div`,{key:n.id,class:f([`flow-upload-list__item`,`flow-upload-list__item--${n.status}`])},[_(t.$slots,`item`,{item:n,index:r,isPending:n.status===`pending`,isUploading:n.status===`uploading`,isSuccess:n.status===`success`,isError:n.status===`error`,isAborted:n.status===`aborted`,formatFileSize:u},()=>[s(` Default item template `),l(`div`,C,[l(`span`,w,v(d(n.status)),1),l(`span`,T,v(y(e)(n.file)?n.file.name:`File`),1),l(`span`,{class:`flow-upload-list__item-status`,style:p({color:h(n.status)})},v(n.status.toUpperCase()),5)]),l(`div`,E,[l(`span`,D,v(u(n.totalBytes)),1),n.jobId?(m(),c(`span`,O,` Job: `+v(n.jobId.slice(0,8))+`... `,1)):s(`v-if`,!0)]),n.status===`uploading`?(m(),c(`div`,ee,[l(`div`,k,[l(`div`,{class:`flow-upload-list__progress-fill`,style:p({width:`${n.progress}%`})},null,4)]),l(`span`,A,v(n.progress)+`% âĸ `+v(u(n.bytesUploaded))+` / `+v(u(n.totalBytes)),1)])):s(`v-if`,!0),n.status===`error`&&n.error?(m(),c(`div`,j,v(n.error.message),1)):s(`v-if`,!0),n.status===`success`?(m(),c(`div`,M,` Upload complete `)):s(`v-if`,!0)],!0)],2))),128))],!0)]))}}),P=(e,t)=>{let n=e.__vccOpts||e;for(let[e,r]of t)n[e]=r;return n},F=P(N,[[`__scopeId`,`data-v-eabb787d`]]);const I=[`tabindex`,`aria-disabled`,`onKeydown`],L={class:`flow-upload-zone__content`},R={key:0},z={key:1},B={key:2},V={key:0},H={key:3},U={key:4,class:`flow-upload-zone__error`},W={key:5},te={key:6,class:`flow-upload-zone__progress`},G={class:`flow-upload-zone__progress-bar`},K={key:7,class:`flow-upload-zone__errors`},q=[`multiple`,`accept`,`disabled`];var J=P(d({__name:`FlowUploadZone`,props:{flowConfig:{},options:{},accept:{},multiple:{type:Boolean,default:!1},disabled:{type:Boolean,default:!1},maxFileSize:{}},emits:[`upload-complete`,`upload-error`,`upload-start`,`validation-error`],setup(e,{emit:n}){let r=e,d=n,S=t({...r.options,flowConfig:r.flowConfig,onFlowComplete:e=>{d(`upload-complete`,e),r.options?.onFlowComplete?.(e)},onError:e=>{d(`upload-error`,e),r.options?.onError?.(e)}}),C=i({accept:r.accept?[r.accept]:void 0,multiple:r.multiple,maxFileSize:r.maxFileSize,onFilesReceived:e=>{let t=e[0];t&&(d(`upload-start`,t),S.upload(t))},onValidationError:e=>{d(`validation-error`,e)}}),w=h(),T=()=>{r.disabled||w.value?.click()},E=o(()=>C.state.value.isDragging||C.state.value.isOver);return(t,n)=>(m(),c(`div`,{class:f([`flow-upload-zone`,{"flow-upload-zone--active":E.value,"flow-upload-zone--disabled":e.disabled,"flow-upload-zone--uploading":y(S).isUploading.value}]),onDragenter:n[1]||=t=>!e.disabled&&y(C).onDragEnter,onDragover:n[2]||=t=>!e.disabled&&y(C).onDragOver,onDragleave:n[3]||=t=>!e.disabled&&y(C).onDragLeave,onDrop:n[4]||=t=>!e.disabled&&y(C).onDrop,onClick:T,role:`button`,tabindex:e.disabled?-1:0,"aria-disabled":e.disabled,"aria-label":`Upload file with flow processing`,onKeydown:[b(T,[`enter`]),b(x(T,[`prevent`]),[`space`])]},[_(t.$slots,`default`,{isDragging:y(C).state.value.isDragging,isOver:y(C).state.value.isOver,isUploading:y(S).isUploading.value,isProcessing:y(S).isProcessing.value,progress:y(S).state.value.progress,status:y(S).state.value.status,errors:[...y(C).state.value.errors],openFilePicker:T},()=>[s(` Default slot content `),l(`div`,L,[y(C).state.value.isDragging?(m(),c(`p`,R,`Drop file here...`)):y(S).isUploading.value?(m(),c(`p`,z,` Uploading... `+v(y(S).state.value.progress)+`% `,1)):y(S).isProcessing.value?(m(),c(`p`,B,[n[5]||=u(` Processing... `,-1),y(S).state.value.currentNodeName?(m(),c(`span`,V,` (`+v(y(S).state.value.currentNodeName)+`) `,1)):s(`v-if`,!0)])):y(S).state.value.status===`success`?(m(),c(`p`,H,`Upload complete!`)):y(S).state.value.status===`error`?(m(),c(`p`,U,` Error: `+v(y(S).state.value.error?.message),1)):(m(),c(`p`,W,`Drag a file here or click to select`)),y(S).isUploading.value?(m(),c(`div`,te,[l(`div`,G,[l(`div`,{class:`flow-upload-zone__progress-fill`,style:p({width:`${y(S).state.value.progress}%`})},null,4)])])):s(`v-if`,!0),y(C).state.value.errors.length>0?(m(),c(`div`,K,[(m(!0),c(a,null,g(y(C).state.value.errors,(e,t)=>(m(),c(`p`,{key:t},v(e),1))),128))])):s(`v-if`,!0)])],!0),l(`input`,{ref_key:`fileInputRef`,ref:w,type:`file`,multiple:y(C).inputProps.value.multiple,accept:y(C).inputProps.value.accept,disabled:e.disabled,onChange:n[0]||=(...e)=>y(C).onInputChange&&y(C).onInputChange(...e),style:{display:`none`},"aria-hidden":`true`},null,40,q)],42,I))}}),[[`__scopeId`,`data-v-e6d6e2c2`]]);const Y={class:`upload-list`},X={class:`upload-list__item-header`},Z={class:`upload-list__item-icon`},Q={class:`upload-list__item-name`},ne={key:0,class:`upload-list__item-size`},re={key:1,class:`upload-list__item-progress`},ie={class:`upload-list__progress-bar`},ae={class:`upload-list__progress-text`},oe={key:2,class:`upload-list__item-error`};var $=P(d({__name:`UploadList`,props:{uploads:{},filter:{type:Function},sortBy:{type:Function}},setup(t){let n=t,r=o(()=>{let e=n.uploads;return n.filter&&(e=e.filter(n.filter)),n.sortBy&&(e=[...e].sort(n.sortBy)),e}),i=o(()=>({idle:r.value.filter(e=>e.state.status===`idle`),uploading:r.value.filter(e=>e.state.status===`uploading`),success:r.value.filter(e=>e.state.status===`success`),error:r.value.filter(e=>e.state.status===`error`),aborted:r.value.filter(e=>e.state.status===`aborted`)})),u=e=>{if(e===0)return`0 Bytes`;let t=1024,n=[`Bytes`,`KB`,`MB`,`GB`],r=Math.floor(Math.log(e)/Math.log(t));return`${parseFloat((e/t**r).toFixed(2))} ${n[r]}`},d=e=>{switch(e){case`idle`:return`âŗ`;case`uploading`:return`đ¤`;case`success`:return`â
`;case`error`:return`â`;case`aborted`:return`âšī¸`;default:return`â`}},h=e=>{switch(e){case`idle`:return`#6c757d`;case`uploading`:return`#007bff`;case`success`:return`#28a745`;case`error`:return`#dc3545`;case`aborted`:return`#6c757d`;default:return`#6c757d`}};return(t,n)=>(m(),c(`div`,Y,[_(t.$slots,`default`,{items:r.value,itemsByStatus:i.value},()=>[s(` Default rendering: simple list of upload items `),(m(!0),c(a,null,g(r.value,(n,r)=>(m(),c(`div`,{key:n.id,class:f([`upload-list__item`,`upload-list__item--${n.state.status}`])},[_(t.$slots,`item`,{item:n,index:r,isUploading:n.state.status===`uploading`,isSuccess:n.state.status===`success`,isError:n.state.status===`error`,formatFileSize:u},()=>[s(` Default item template `),l(`div`,X,[l(`span`,Z,v(d(n.state.status)),1),l(`span`,Q,v(y(e)(n.file)?n.file.name:`File`),1),l(`span`,{class:`upload-list__item-status`,style:p({color:h(n.state.status)})},v(n.state.status.toUpperCase()),5)]),n.state.totalBytes?(m(),c(`div`,ne,v(u(n.state.totalBytes)),1)):s(`v-if`,!0),n.state.status===`uploading`?(m(),c(`div`,re,[l(`div`,ie,[l(`div`,{class:`upload-list__progress-fill`,style:p({width:`${n.state.progress}%`})},null,4)]),l(`span`,ae,v(n.state.progress)+`%`,1)])):s(`v-if`,!0),n.state.status===`error`&&n.state.error?(m(),c(`div`,oe,v(n.state.error.message),1)):s(`v-if`,!0)],!0)],2))),128))],!0)]))}}),[[`__scopeId`,`data-v-70c8fe1a`]]);const se=[`tabindex`,`aria-disabled`,`aria-label`,`onKeydown`],ce={class:`upload-zone__content`},le={key:0},ue={key:1},de={key:2,class:`upload-zone__errors`},fe=[`multiple`,`accept`,`disabled`];var pe=P(d({__name:`UploadZone`,props:{accept:{},multiple:{type:Boolean,default:!0},disabled:{type:Boolean,default:!1},maxFileSize:{},validator:{},multiUploadOptions:{},uploadOptions:{}},emits:[`file-select`,`upload-start`,`validation-error`],setup(e,{emit:t}){let u=e,d=t,p=u.multiple?null:n(u.uploadOptions||{}),S=u.multiple?r(u.multiUploadOptions||{}):null,C=i({accept:u.accept,multiple:u.multiple,maxFileSize:u.maxFileSize,validator:u.validator,onFilesReceived:e=>{d(`file-select`,e),d(`upload-start`,e),u.multiple&&S?(S.addFiles(e),setTimeout(()=>S.startAll(),0)):!u.multiple&&p&&e[0]&&p.upload(e[0])},onValidationError:e=>{d(`validation-error`,e)}}),w=h(),T=()=>{u.disabled||w.value?.click()},E=o(()=>C.state.value.isDragging||C.state.value.isOver),D=o(()=>u.multiple&&S?S.state.value.isUploading:!u.multiple&&p?p.state.value.status===`uploading`:!1);return(t,n)=>(m(),c(`div`,{class:f([`upload-zone`,{"upload-zone--active":E.value,"upload-zone--disabled":e.disabled}]),onDragenter:n[1]||=t=>!e.disabled&&y(C).onDragEnter,onDragover:n[2]||=t=>!e.disabled&&y(C).onDragOver,onDragleave:n[3]||=t=>!e.disabled&&y(C).onDragLeave,onDrop:n[4]||=t=>!e.disabled&&y(C).onDrop,onClick:T,role:`button`,tabindex:e.disabled?-1:0,"aria-disabled":e.disabled,"aria-label":e.multiple?`Upload multiple files`:`Upload a file`,onKeydown:[b(T,[`enter`]),b(x(T,[`prevent`]),[`space`])]},[_(t.$slots,`default`,{isDragging:y(C).state.value.isDragging,isOver:y(C).state.value.isOver,isUploading:D.value,errors:[...y(C).state.value.errors],openFilePicker:T},()=>[s(` Default slot content `),l(`div`,ce,[y(C).state.value.isDragging?(m(),c(`p`,le,v(e.multiple?`Drop files here...`:`Drop file here...`),1)):(m(),c(`p`,ue,v(e.multiple?`Drag files here or click to select`:`Drag a file here or click to select`),1)),y(C).state.value.errors.length>0?(m(),c(`div`,de,[(m(!0),c(a,null,g(y(C).state.value.errors,(e,t)=>(m(),c(`p`,{key:t},v(e),1))),128))])):s(`v-if`,!0)])],!0),l(`input`,{ref_key:`fileInputRef`,ref:w,type:`file`,multiple:y(C).inputProps.value.multiple,accept:y(C).inputProps.value.accept,disabled:e.disabled,onChange:n[0]||=(...e)=>y(C).onInputChange&&y(C).onInputChange(...e),style:{display:`none`},"aria-hidden":`true`},null,40,fe)],42,se))}}),[[`__scopeId`,`data-v-8b709bef`]]);export{F as i,$ as n,J as r,pe as t};
|
|
2
|
+
//# sourceMappingURL=components-DjgxUKKp.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"components-DjgxUKKp.mjs","names":[],"sources":["../src/components/FlowUploadList.vue","../src/components/FlowUploadList.vue","../src/components/FlowUploadZone.vue","../src/components/FlowUploadZone.vue","../src/components/UploadList.vue","../src/components/UploadList.vue","../src/components/UploadZone.vue","../src/components/UploadZone.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * FlowUploadList - Display list of flow uploads with processing status\n *\n * Shows the progress and processing status of files being uploaded through flow pipelines.\n * Supports filtering, sorting, and status-based grouping. Provides flexible slot-based\n * customization for rendering each flow upload item.\n *\n * @component\n * @example\n * // Basic flow upload list\n * <FlowUploadList :uploads=\"flowUploads\" />\n *\n * @example\n * // Custom item rendering with flow status\n * <FlowUploadList :uploads=\"flowUploads\">\n * <template #item=\"{ item, isSuccess, isError, isUploading }\">\n * <div class=\"flow-item\">\n * <span>{{ item.filename }}</span>\n * <progress :value=\"item.uploadProgress\" max=\"100\"></progress>\n * <span v-if=\"isUploading\">Processing...</span>\n * <span v-else-if=\"isSuccess\">Complete</span>\n * <span v-else-if=\"isError\">Failed</span>\n * </div>\n * </template>\n * </FlowUploadList>\n *\n * @example\n * // With status grouping\n * <FlowUploadList :uploads=\"flowUploads\">\n * <template #default=\"{ itemsByStatus }\">\n * <div v-if=\"itemsByStatus.uploading.length\">\n * <h3>Uploading...</h3>\n * <div v-for=\"item of itemsByStatus.uploading\" :key=\"item.id\">\n * {{ item.filename }}\n * </div>\n * </div>\n * </template>\n * </FlowUploadList>\n */\nimport type {\n BrowserUploadInput,\n FlowUploadItem,\n} from \"@uploadista/client-browser\";\nimport { computed } from \"vue\";\nimport { isBrowserFile } from \"../utils\";\n\n/**\n * Props for the FlowUploadList component\n * @property {FlowUploadItem[]} uploads - Array of flow upload items to display\n * @property {Function} filter - Optional filter for which items to display\n * @property {Function} sortBy - Optional sorting function for items (a, b) => number\n */\nexport interface FlowUploadListProps {\n /**\n * Array of flow upload items to display\n */\n uploads: FlowUploadItem<BrowserUploadInput>[];\n\n /**\n * Optional filter for which items to display\n */\n filter?: (item: FlowUploadItem<BrowserUploadInput>) => boolean;\n\n /**\n * Optional sorting function for items\n */\n sortBy?: (\n a: FlowUploadItem<BrowserUploadInput>,\n b: FlowUploadItem<BrowserUploadInput>,\n ) => number;\n}\n\nconst props = defineProps<FlowUploadListProps>();\n\ndefineSlots<{\n item(props: {\n item: FlowUploadItem<BrowserUploadInput>;\n index: number;\n isPending: boolean;\n isUploading: boolean;\n isSuccess: boolean;\n isError: boolean;\n isAborted: boolean;\n formatFileSize: (bytes: number) => string;\n }): any;\n default?(props: {\n items: FlowUploadItem<BrowserUploadInput>[];\n itemsByStatus: {\n pending: FlowUploadItem<BrowserUploadInput>[];\n uploading: FlowUploadItem<BrowserUploadInput>[];\n success: FlowUploadItem<BrowserUploadInput>[];\n error: FlowUploadItem<BrowserUploadInput>[];\n aborted: FlowUploadItem<BrowserUploadInput>[];\n };\n }): any;\n}>();\n\n// Apply filtering and sorting\nconst filteredItems = computed(() => {\n let items = props.uploads;\n\n if (props.filter) {\n items = items.filter(props.filter);\n }\n\n if (props.sortBy) {\n items = [...items].sort(props.sortBy);\n }\n\n return items;\n});\n\n// Group items by status\n// biome-ignore lint/correctness/noUnusedVariables: Used in slot templates\nconst itemsByStatus = computed(() => ({\n pending: filteredItems.value.filter((item) => item.status === \"pending\"),\n uploading: filteredItems.value.filter((item) => item.status === \"uploading\"),\n success: filteredItems.value.filter((item) => item.status === \"success\"),\n error: filteredItems.value.filter((item) => item.status === \"error\"),\n aborted: filteredItems.value.filter((item) => item.status === \"aborted\"),\n}));\n\n// Helper function to format file sizes\n// biome-ignore lint/correctness/noUnusedVariables: Used in slot templates\nconst formatFileSize = (bytes: number): string => {\n if (bytes === 0) return \"0 Bytes\";\n const k = 1024;\n const sizes = [\"Bytes\", \"KB\", \"MB\", \"GB\"];\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n return `${parseFloat((bytes / k ** i).toFixed(2))} ${sizes[i]}`;\n};\n\n// Helper function to get status icon\n// biome-ignore lint/correctness/noUnusedVariables: Used in slot templates\nconst getStatusIcon = (status: string): string => {\n switch (status) {\n case \"pending\":\n return \"âŗ\";\n case \"uploading\":\n return \"đ¤\";\n case \"success\":\n return \"â
\";\n case \"error\":\n return \"â\";\n case \"aborted\":\n return \"âšī¸\";\n default:\n return \"â\";\n }\n};\n\n// Helper function to get status color\n// biome-ignore lint/correctness/noUnusedVariables: Used in slot templates\nconst getStatusColor = (status: string): string => {\n switch (status) {\n case \"pending\":\n return \"#6c757d\";\n case \"uploading\":\n return \"#007bff\";\n case \"success\":\n return \"#28a745\";\n case \"error\":\n return \"#dc3545\";\n case \"aborted\":\n return \"#6c757d\";\n default:\n return \"#6c757d\";\n }\n};\n</script>\n\n<template>\n <div class=\"flow-upload-list\">\n <slot :items=\"filteredItems\" :items-by-status=\"itemsByStatus\">\n <!-- Default rendering: simple list of flow upload items -->\n <div\n v-for=\"(item, index) in filteredItems\"\n :key=\"item.id\"\n class=\"flow-upload-list__item\"\n :class=\"`flow-upload-list__item--${item.status}`\"\n >\n <slot\n name=\"item\"\n :item=\"item\"\n :index=\"index\"\n :is-pending=\"item.status === 'pending'\"\n :is-uploading=\"item.status === 'uploading'\"\n :is-success=\"item.status === 'success'\"\n :is-error=\"item.status === 'error'\"\n :is-aborted=\"item.status === 'aborted'\"\n :format-file-size=\"formatFileSize\"\n >\n <!-- Default item template -->\n <div class=\"flow-upload-list__item-header\">\n <span class=\"flow-upload-list__item-icon\">\n {{ getStatusIcon(item.status) }}\n </span>\n <span class=\"flow-upload-list__item-name\">\n {{ isBrowserFile(item.file) ? item.file.name : 'File' }}\n </span>\n <span\n class=\"flow-upload-list__item-status\"\n :style=\"{ color: getStatusColor(item.status) }\"\n >\n {{ item.status.toUpperCase() }}\n </span>\n </div>\n\n <div class=\"flow-upload-list__item-details\">\n <span class=\"flow-upload-list__item-size\">\n {{ formatFileSize(item.totalBytes) }}\n </span>\n <span v-if=\"item.jobId\" class=\"flow-upload-list__item-job\">\n Job: {{ item.jobId.slice(0, 8) }}...\n </span>\n </div>\n\n <div v-if=\"item.status === 'uploading'\" class=\"flow-upload-list__item-progress\">\n <div class=\"flow-upload-list__progress-bar\">\n <div\n class=\"flow-upload-list__progress-fill\"\n :style=\"{ width: `${item.progress}%` }\"\n />\n </div>\n <span class=\"flow-upload-list__progress-text\">\n {{ item.progress }}% âĸ {{ formatFileSize(item.bytesUploaded) }} / {{ formatFileSize(item.totalBytes) }}\n </span>\n </div>\n\n <div v-if=\"item.status === 'error' && item.error\" class=\"flow-upload-list__item-error\">\n {{ item.error.message }}\n </div>\n\n <div v-if=\"item.status === 'success'\" class=\"flow-upload-list__item-success\">\n Upload complete\n </div>\n </slot>\n </div>\n </slot>\n </div>\n</template>\n\n<style scoped>\n.flow-upload-list {\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n}\n\n.flow-upload-list__item {\n padding: 0.75rem;\n border: 1px solid #e0e0e0;\n border-radius: 0.375rem;\n background-color: #fff;\n}\n\n.flow-upload-list__item-header {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n margin-bottom: 0.5rem;\n}\n\n.flow-upload-list__item-icon {\n font-size: 1rem;\n}\n\n.flow-upload-list__item-name {\n flex: 1;\n font-weight: 500;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.flow-upload-list__item-status {\n font-size: 0.75rem;\n font-weight: 600;\n text-transform: uppercase;\n}\n\n.flow-upload-list__item-details {\n display: flex;\n gap: 1rem;\n font-size: 0.75rem;\n color: #666;\n margin-bottom: 0.5rem;\n}\n\n.flow-upload-list__item-size {\n font-weight: 500;\n}\n\n.flow-upload-list__item-job {\n color: #999;\n font-family: monospace;\n}\n\n.flow-upload-list__item-progress {\n display: flex;\n flex-direction: column;\n gap: 0.25rem;\n}\n\n.flow-upload-list__progress-bar {\n width: 100%;\n height: 0.375rem;\n background-color: #e0e0e0;\n border-radius: 0.1875rem;\n overflow: hidden;\n}\n\n.flow-upload-list__progress-fill {\n height: 100%;\n background-color: #007bff;\n transition: width 0.2s ease;\n}\n\n.flow-upload-list__progress-text {\n font-size: 0.75rem;\n color: #666;\n}\n\n.flow-upload-list__item-error {\n margin-top: 0.5rem;\n padding: 0.5rem;\n background-color: #f8d7da;\n color: #721c24;\n font-size: 0.75rem;\n border-radius: 0.25rem;\n}\n\n.flow-upload-list__item-success {\n margin-top: 0.5rem;\n padding: 0.5rem;\n background-color: #d4edda;\n color: #155724;\n font-size: 0.75rem;\n border-radius: 0.25rem;\n}\n</style>\n","<script setup lang=\"ts\">\n/**\n * FlowUploadList - Display list of flow uploads with processing status\n *\n * Shows the progress and processing status of files being uploaded through flow pipelines.\n * Supports filtering, sorting, and status-based grouping. Provides flexible slot-based\n * customization for rendering each flow upload item.\n *\n * @component\n * @example\n * // Basic flow upload list\n * <FlowUploadList :uploads=\"flowUploads\" />\n *\n * @example\n * // Custom item rendering with flow status\n * <FlowUploadList :uploads=\"flowUploads\">\n * <template #item=\"{ item, isSuccess, isError, isUploading }\">\n * <div class=\"flow-item\">\n * <span>{{ item.filename }}</span>\n * <progress :value=\"item.uploadProgress\" max=\"100\"></progress>\n * <span v-if=\"isUploading\">Processing...</span>\n * <span v-else-if=\"isSuccess\">Complete</span>\n * <span v-else-if=\"isError\">Failed</span>\n * </div>\n * </template>\n * </FlowUploadList>\n *\n * @example\n * // With status grouping\n * <FlowUploadList :uploads=\"flowUploads\">\n * <template #default=\"{ itemsByStatus }\">\n * <div v-if=\"itemsByStatus.uploading.length\">\n * <h3>Uploading...</h3>\n * <div v-for=\"item of itemsByStatus.uploading\" :key=\"item.id\">\n * {{ item.filename }}\n * </div>\n * </div>\n * </template>\n * </FlowUploadList>\n */\nimport type {\n BrowserUploadInput,\n FlowUploadItem,\n} from \"@uploadista/client-browser\";\nimport { computed } from \"vue\";\nimport { isBrowserFile } from \"../utils\";\n\n/**\n * Props for the FlowUploadList component\n * @property {FlowUploadItem[]} uploads - Array of flow upload items to display\n * @property {Function} filter - Optional filter for which items to display\n * @property {Function} sortBy - Optional sorting function for items (a, b) => number\n */\nexport interface FlowUploadListProps {\n /**\n * Array of flow upload items to display\n */\n uploads: FlowUploadItem<BrowserUploadInput>[];\n\n /**\n * Optional filter for which items to display\n */\n filter?: (item: FlowUploadItem<BrowserUploadInput>) => boolean;\n\n /**\n * Optional sorting function for items\n */\n sortBy?: (\n a: FlowUploadItem<BrowserUploadInput>,\n b: FlowUploadItem<BrowserUploadInput>,\n ) => number;\n}\n\nconst props = defineProps<FlowUploadListProps>();\n\ndefineSlots<{\n item(props: {\n item: FlowUploadItem<BrowserUploadInput>;\n index: number;\n isPending: boolean;\n isUploading: boolean;\n isSuccess: boolean;\n isError: boolean;\n isAborted: boolean;\n formatFileSize: (bytes: number) => string;\n }): any;\n default?(props: {\n items: FlowUploadItem<BrowserUploadInput>[];\n itemsByStatus: {\n pending: FlowUploadItem<BrowserUploadInput>[];\n uploading: FlowUploadItem<BrowserUploadInput>[];\n success: FlowUploadItem<BrowserUploadInput>[];\n error: FlowUploadItem<BrowserUploadInput>[];\n aborted: FlowUploadItem<BrowserUploadInput>[];\n };\n }): any;\n}>();\n\n// Apply filtering and sorting\nconst filteredItems = computed(() => {\n let items = props.uploads;\n\n if (props.filter) {\n items = items.filter(props.filter);\n }\n\n if (props.sortBy) {\n items = [...items].sort(props.sortBy);\n }\n\n return items;\n});\n\n// Group items by status\n// biome-ignore lint/correctness/noUnusedVariables: Used in slot templates\nconst itemsByStatus = computed(() => ({\n pending: filteredItems.value.filter((item) => item.status === \"pending\"),\n uploading: filteredItems.value.filter((item) => item.status === \"uploading\"),\n success: filteredItems.value.filter((item) => item.status === \"success\"),\n error: filteredItems.value.filter((item) => item.status === \"error\"),\n aborted: filteredItems.value.filter((item) => item.status === \"aborted\"),\n}));\n\n// Helper function to format file sizes\n// biome-ignore lint/correctness/noUnusedVariables: Used in slot templates\nconst formatFileSize = (bytes: number): string => {\n if (bytes === 0) return \"0 Bytes\";\n const k = 1024;\n const sizes = [\"Bytes\", \"KB\", \"MB\", \"GB\"];\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n return `${parseFloat((bytes / k ** i).toFixed(2))} ${sizes[i]}`;\n};\n\n// Helper function to get status icon\n// biome-ignore lint/correctness/noUnusedVariables: Used in slot templates\nconst getStatusIcon = (status: string): string => {\n switch (status) {\n case \"pending\":\n return \"âŗ\";\n case \"uploading\":\n return \"đ¤\";\n case \"success\":\n return \"â
\";\n case \"error\":\n return \"â\";\n case \"aborted\":\n return \"âšī¸\";\n default:\n return \"â\";\n }\n};\n\n// Helper function to get status color\n// biome-ignore lint/correctness/noUnusedVariables: Used in slot templates\nconst getStatusColor = (status: string): string => {\n switch (status) {\n case \"pending\":\n return \"#6c757d\";\n case \"uploading\":\n return \"#007bff\";\n case \"success\":\n return \"#28a745\";\n case \"error\":\n return \"#dc3545\";\n case \"aborted\":\n return \"#6c757d\";\n default:\n return \"#6c757d\";\n }\n};\n</script>\n\n<template>\n <div class=\"flow-upload-list\">\n <slot :items=\"filteredItems\" :items-by-status=\"itemsByStatus\">\n <!-- Default rendering: simple list of flow upload items -->\n <div\n v-for=\"(item, index) in filteredItems\"\n :key=\"item.id\"\n class=\"flow-upload-list__item\"\n :class=\"`flow-upload-list__item--${item.status}`\"\n >\n <slot\n name=\"item\"\n :item=\"item\"\n :index=\"index\"\n :is-pending=\"item.status === 'pending'\"\n :is-uploading=\"item.status === 'uploading'\"\n :is-success=\"item.status === 'success'\"\n :is-error=\"item.status === 'error'\"\n :is-aborted=\"item.status === 'aborted'\"\n :format-file-size=\"formatFileSize\"\n >\n <!-- Default item template -->\n <div class=\"flow-upload-list__item-header\">\n <span class=\"flow-upload-list__item-icon\">\n {{ getStatusIcon(item.status) }}\n </span>\n <span class=\"flow-upload-list__item-name\">\n {{ isBrowserFile(item.file) ? item.file.name : 'File' }}\n </span>\n <span\n class=\"flow-upload-list__item-status\"\n :style=\"{ color: getStatusColor(item.status) }\"\n >\n {{ item.status.toUpperCase() }}\n </span>\n </div>\n\n <div class=\"flow-upload-list__item-details\">\n <span class=\"flow-upload-list__item-size\">\n {{ formatFileSize(item.totalBytes) }}\n </span>\n <span v-if=\"item.jobId\" class=\"flow-upload-list__item-job\">\n Job: {{ item.jobId.slice(0, 8) }}...\n </span>\n </div>\n\n <div v-if=\"item.status === 'uploading'\" class=\"flow-upload-list__item-progress\">\n <div class=\"flow-upload-list__progress-bar\">\n <div\n class=\"flow-upload-list__progress-fill\"\n :style=\"{ width: `${item.progress}%` }\"\n />\n </div>\n <span class=\"flow-upload-list__progress-text\">\n {{ item.progress }}% âĸ {{ formatFileSize(item.bytesUploaded) }} / {{ formatFileSize(item.totalBytes) }}\n </span>\n </div>\n\n <div v-if=\"item.status === 'error' && item.error\" class=\"flow-upload-list__item-error\">\n {{ item.error.message }}\n </div>\n\n <div v-if=\"item.status === 'success'\" class=\"flow-upload-list__item-success\">\n Upload complete\n </div>\n </slot>\n </div>\n </slot>\n </div>\n</template>\n\n<style scoped>\n.flow-upload-list {\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n}\n\n.flow-upload-list__item {\n padding: 0.75rem;\n border: 1px solid #e0e0e0;\n border-radius: 0.375rem;\n background-color: #fff;\n}\n\n.flow-upload-list__item-header {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n margin-bottom: 0.5rem;\n}\n\n.flow-upload-list__item-icon {\n font-size: 1rem;\n}\n\n.flow-upload-list__item-name {\n flex: 1;\n font-weight: 500;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.flow-upload-list__item-status {\n font-size: 0.75rem;\n font-weight: 600;\n text-transform: uppercase;\n}\n\n.flow-upload-list__item-details {\n display: flex;\n gap: 1rem;\n font-size: 0.75rem;\n color: #666;\n margin-bottom: 0.5rem;\n}\n\n.flow-upload-list__item-size {\n font-weight: 500;\n}\n\n.flow-upload-list__item-job {\n color: #999;\n font-family: monospace;\n}\n\n.flow-upload-list__item-progress {\n display: flex;\n flex-direction: column;\n gap: 0.25rem;\n}\n\n.flow-upload-list__progress-bar {\n width: 100%;\n height: 0.375rem;\n background-color: #e0e0e0;\n border-radius: 0.1875rem;\n overflow: hidden;\n}\n\n.flow-upload-list__progress-fill {\n height: 100%;\n background-color: #007bff;\n transition: width 0.2s ease;\n}\n\n.flow-upload-list__progress-text {\n font-size: 0.75rem;\n color: #666;\n}\n\n.flow-upload-list__item-error {\n margin-top: 0.5rem;\n padding: 0.5rem;\n background-color: #f8d7da;\n color: #721c24;\n font-size: 0.75rem;\n border-radius: 0.25rem;\n}\n\n.flow-upload-list__item-success {\n margin-top: 0.5rem;\n padding: 0.5rem;\n background-color: #d4edda;\n color: #155724;\n font-size: 0.75rem;\n border-radius: 0.25rem;\n}\n</style>\n","<script setup lang=\"ts\">\n/**\n * FlowUploadZone - Upload zone with flow processing pipeline\n *\n * Specialized upload component that uploads files through a flow pipeline for processing.\n * Supports drag-and-drop and file picker with flow configuration. Emits events for flow\n * completion, errors, and upload status changes.\n *\n * @component\n * @example\n * // Upload images through image processing flow\n * <FlowUploadZone\n * :flow-config=\"{ flowId: 'image-processor' }\"\n * accept=\"image/*\"\n * @upload-complete=\"handleFlowResult\"\n * @upload-error=\"handleError\"\n * />\n *\n * @example\n * // With custom slot content\n * <FlowUploadZone\n * :flow-config=\"{ flowId: 'image-processor' }\"\n * @upload-complete=\"handleResult\"\n * >\n * <template #default=\"{ isDragging, isProcessing, progress }\">\n * <div :class=\"{ processing: isProcessing }\">\n * <p v-if=\"isProcessing\">Processing... {{ progress }}%</p>\n * <p v-else-if=\"isDragging\">Drop file here</p>\n * <p v-else>Drag file here for processing</p>\n * </div>\n * </template>\n * </FlowUploadZone>\n *\n * @emits upload-complete - Flow processing completed with results\n * @emits upload-error - Upload or processing failed\n * @emits upload-start - File upload started\n * @emits validation-error - File validation failed\n */\nimport type {\n FlowUploadConfig,\n FlowUploadOptions,\n} from \"@uploadista/client-browser\";\nimport { computed, ref } from \"vue\";\nimport { useDragDrop, useFlowUpload } from \"../composables\";\n\n/**\n * Props for the FlowUploadZone component\n * @property {FlowUploadConfig} flowConfig - Flow configuration with flowId\n * @property {FlowUploadOptions} options - Additional flow upload options\n * @property {string} accept - Accepted file types (single MIME type or extension string)\n * @property {boolean} multiple - Allow multiple files (default: false, flow uploads are single-file)\n * @property {boolean} disabled - Disable the upload zone (default: false)\n * @property {number} maxFileSize - Maximum file size in bytes\n */\nexport interface FlowUploadZoneProps {\n /**\n * Flow configuration\n */\n flowConfig: FlowUploadConfig;\n\n /**\n * Additional flow upload options\n */\n options?: Omit<FlowUploadOptions, \"flowConfig\">;\n\n /**\n * Accepted file types (single MIME type or extension string)\n */\n accept?: string;\n\n /**\n * Whether to allow multiple files (currently only single file supported for flow uploads)\n */\n multiple?: boolean;\n\n /**\n * Whether the upload zone is disabled\n */\n disabled?: boolean;\n\n /**\n * Maximum file size in bytes\n */\n maxFileSize?: number;\n}\n\nconst props = withDefaults(defineProps<FlowUploadZoneProps>(), {\n multiple: false,\n disabled: false,\n});\n\n// biome-ignore lint/suspicious/noExplicitAny: Flow result can be any type\nconst emit = defineEmits<{\n // biome-ignore lint/suspicious/noExplicitAny: Flow result can be any type\n \"upload-complete\": [result: any];\n \"upload-error\": [error: Error];\n \"upload-start\": [file: File];\n \"validation-error\": [errors: string[]];\n}>();\n\n// biome-ignore lint/suspicious/noExplicitAny: Vue slot definition requires any\ndefineSlots<{\n // biome-ignore lint/suspicious/noExplicitAny: Vue slot definition requires any\n default(props: {\n isDragging: boolean;\n isOver: boolean;\n isUploading: boolean;\n isProcessing: boolean;\n progress: number;\n status: string;\n errors: string[];\n openFilePicker: () => void;\n }): any;\n}>();\n\n// Initialize flow upload\nconst flowUpload = useFlowUpload({\n ...props.options,\n flowConfig: props.flowConfig,\n onFlowComplete: (outputs) => {\n emit(\"upload-complete\", outputs);\n props.options?.onFlowComplete?.(outputs);\n },\n onError: (error) => {\n emit(\"upload-error\", error);\n props.options?.onError?.(error);\n },\n});\n\n// Handle files received from drag-drop or file picker\nconst handleFilesReceived = (files: File[]) => {\n const file = files[0];\n if (file) {\n emit(\"upload-start\", file);\n flowUpload.upload(file);\n }\n};\n\n// Handle validation errors\nconst handleValidationError = (errors: string[]) => {\n emit(\"validation-error\", errors);\n};\n\n// Initialize drag-drop\nconst dragDrop = useDragDrop({\n accept: props.accept ? [props.accept] : undefined,\n multiple: props.multiple,\n maxFileSize: props.maxFileSize,\n onFilesReceived: handleFilesReceived,\n onValidationError: handleValidationError,\n});\n\n// File input ref\nconst fileInputRef = ref<HTMLInputElement>();\n\n// Open file picker\n// biome-ignore lint/correctness/noUnusedVariables: Used in slot templates\nconst openFilePicker = () => {\n if (!props.disabled) {\n fileInputRef.value?.click();\n }\n};\n\n// Computed states\n// biome-ignore lint/correctness/noUnusedVariables: Used in slot templates\nconst isActive = computed(\n () => dragDrop.state.value.isDragging || dragDrop.state.value.isOver,\n);\n</script>\n\n<template>\n <div\n class=\"flow-upload-zone\"\n :class=\"{\n 'flow-upload-zone--active': isActive,\n 'flow-upload-zone--disabled': disabled,\n 'flow-upload-zone--uploading': flowUpload.isUploading.value\n }\"\n @dragenter=\"!disabled && dragDrop.onDragEnter\"\n @dragover=\"!disabled && dragDrop.onDragOver\"\n @dragleave=\"!disabled && dragDrop.onDragLeave\"\n @drop=\"!disabled && dragDrop.onDrop\"\n @click=\"openFilePicker\"\n role=\"button\"\n :tabindex=\"disabled ? -1 : 0\"\n :aria-disabled=\"disabled\"\n aria-label=\"Upload file with flow processing\"\n @keydown.enter=\"openFilePicker\"\n @keydown.space.prevent=\"openFilePicker\"\n >\n <slot\n :is-dragging=\"dragDrop.state.value.isDragging\"\n :is-over=\"dragDrop.state.value.isOver\"\n :is-uploading=\"flowUpload.isUploading.value\"\n :is-processing=\"flowUpload.isProcessing.value\"\n :progress=\"flowUpload.state.value.progress\"\n :status=\"flowUpload.state.value.status\"\n :errors=\"[...dragDrop.state.value.errors]\"\n :open-file-picker=\"openFilePicker\"\n >\n <!-- Default slot content -->\n <div class=\"flow-upload-zone__content\">\n <p v-if=\"dragDrop.state.value.isDragging\">Drop file here...</p>\n <p v-else-if=\"flowUpload.isUploading.value\">\n Uploading... {{ flowUpload.state.value.progress }}%\n </p>\n <p v-else-if=\"flowUpload.isProcessing.value\">\n Processing...\n <span v-if=\"flowUpload.state.value.currentNodeName\">\n ({{ flowUpload.state.value.currentNodeName }})\n </span>\n </p>\n <p v-else-if=\"flowUpload.state.value.status === 'success'\">Upload complete!</p>\n <p v-else-if=\"flowUpload.state.value.status === 'error'\" class=\"flow-upload-zone__error\">\n Error: {{ flowUpload.state.value.error?.message }}\n </p>\n <p v-else>Drag a file here or click to select</p>\n\n <div v-if=\"flowUpload.isUploading.value\" class=\"flow-upload-zone__progress\">\n <div class=\"flow-upload-zone__progress-bar\">\n <div\n class=\"flow-upload-zone__progress-fill\"\n :style=\"{ width: `${flowUpload.state.value.progress}%` }\"\n />\n </div>\n </div>\n\n <div v-if=\"dragDrop.state.value.errors.length > 0\" class=\"flow-upload-zone__errors\">\n <p v-for=\"(error, index) in dragDrop.state.value.errors\" :key=\"index\">\n {{ error }}\n </p>\n </div>\n </div>\n </slot>\n\n <input\n ref=\"fileInputRef\"\n type=\"file\"\n :multiple=\"dragDrop.inputProps.value.multiple\"\n :accept=\"dragDrop.inputProps.value.accept\"\n :disabled=\"disabled\"\n @change=\"dragDrop.onInputChange\"\n style=\"display: none\"\n aria-hidden=\"true\"\n />\n </div>\n</template>\n\n<style scoped>\n.flow-upload-zone {\n cursor: pointer;\n user-select: none;\n}\n\n.flow-upload-zone--disabled {\n cursor: not-allowed;\n opacity: 0.6;\n}\n\n.flow-upload-zone--uploading {\n pointer-events: none;\n}\n\n.flow-upload-zone__content {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 0.5rem;\n}\n\n.flow-upload-zone__error {\n color: #dc3545;\n}\n\n.flow-upload-zone__progress {\n width: 100%;\n max-width: 300px;\n margin-top: 0.5rem;\n}\n\n.flow-upload-zone__progress-bar {\n width: 100%;\n height: 0.5rem;\n background-color: #e0e0e0;\n border-radius: 0.25rem;\n overflow: hidden;\n}\n\n.flow-upload-zone__progress-fill {\n height: 100%;\n background-color: #007bff;\n transition: width 0.2s ease;\n}\n\n.flow-upload-zone__errors {\n margin-top: 0.5rem;\n color: #dc3545;\n font-size: 0.875rem;\n}\n\n.flow-upload-zone__errors p {\n margin: 0.25rem 0;\n}\n</style>\n","<script setup lang=\"ts\">\n/**\n * FlowUploadZone - Upload zone with flow processing pipeline\n *\n * Specialized upload component that uploads files through a flow pipeline for processing.\n * Supports drag-and-drop and file picker with flow configuration. Emits events for flow\n * completion, errors, and upload status changes.\n *\n * @component\n * @example\n * // Upload images through image processing flow\n * <FlowUploadZone\n * :flow-config=\"{ flowId: 'image-processor' }\"\n * accept=\"image/*\"\n * @upload-complete=\"handleFlowResult\"\n * @upload-error=\"handleError\"\n * />\n *\n * @example\n * // With custom slot content\n * <FlowUploadZone\n * :flow-config=\"{ flowId: 'image-processor' }\"\n * @upload-complete=\"handleResult\"\n * >\n * <template #default=\"{ isDragging, isProcessing, progress }\">\n * <div :class=\"{ processing: isProcessing }\">\n * <p v-if=\"isProcessing\">Processing... {{ progress }}%</p>\n * <p v-else-if=\"isDragging\">Drop file here</p>\n * <p v-else>Drag file here for processing</p>\n * </div>\n * </template>\n * </FlowUploadZone>\n *\n * @emits upload-complete - Flow processing completed with results\n * @emits upload-error - Upload or processing failed\n * @emits upload-start - File upload started\n * @emits validation-error - File validation failed\n */\nimport type {\n FlowUploadConfig,\n FlowUploadOptions,\n} from \"@uploadista/client-browser\";\nimport { computed, ref } from \"vue\";\nimport { useDragDrop, useFlowUpload } from \"../composables\";\n\n/**\n * Props for the FlowUploadZone component\n * @property {FlowUploadConfig} flowConfig - Flow configuration with flowId\n * @property {FlowUploadOptions} options - Additional flow upload options\n * @property {string} accept - Accepted file types (single MIME type or extension string)\n * @property {boolean} multiple - Allow multiple files (default: false, flow uploads are single-file)\n * @property {boolean} disabled - Disable the upload zone (default: false)\n * @property {number} maxFileSize - Maximum file size in bytes\n */\nexport interface FlowUploadZoneProps {\n /**\n * Flow configuration\n */\n flowConfig: FlowUploadConfig;\n\n /**\n * Additional flow upload options\n */\n options?: Omit<FlowUploadOptions, \"flowConfig\">;\n\n /**\n * Accepted file types (single MIME type or extension string)\n */\n accept?: string;\n\n /**\n * Whether to allow multiple files (currently only single file supported for flow uploads)\n */\n multiple?: boolean;\n\n /**\n * Whether the upload zone is disabled\n */\n disabled?: boolean;\n\n /**\n * Maximum file size in bytes\n */\n maxFileSize?: number;\n}\n\nconst props = withDefaults(defineProps<FlowUploadZoneProps>(), {\n multiple: false,\n disabled: false,\n});\n\n// biome-ignore lint/suspicious/noExplicitAny: Flow result can be any type\nconst emit = defineEmits<{\n // biome-ignore lint/suspicious/noExplicitAny: Flow result can be any type\n \"upload-complete\": [result: any];\n \"upload-error\": [error: Error];\n \"upload-start\": [file: File];\n \"validation-error\": [errors: string[]];\n}>();\n\n// biome-ignore lint/suspicious/noExplicitAny: Vue slot definition requires any\ndefineSlots<{\n // biome-ignore lint/suspicious/noExplicitAny: Vue slot definition requires any\n default(props: {\n isDragging: boolean;\n isOver: boolean;\n isUploading: boolean;\n isProcessing: boolean;\n progress: number;\n status: string;\n errors: string[];\n openFilePicker: () => void;\n }): any;\n}>();\n\n// Initialize flow upload\nconst flowUpload = useFlowUpload({\n ...props.options,\n flowConfig: props.flowConfig,\n onFlowComplete: (outputs) => {\n emit(\"upload-complete\", outputs);\n props.options?.onFlowComplete?.(outputs);\n },\n onError: (error) => {\n emit(\"upload-error\", error);\n props.options?.onError?.(error);\n },\n});\n\n// Handle files received from drag-drop or file picker\nconst handleFilesReceived = (files: File[]) => {\n const file = files[0];\n if (file) {\n emit(\"upload-start\", file);\n flowUpload.upload(file);\n }\n};\n\n// Handle validation errors\nconst handleValidationError = (errors: string[]) => {\n emit(\"validation-error\", errors);\n};\n\n// Initialize drag-drop\nconst dragDrop = useDragDrop({\n accept: props.accept ? [props.accept] : undefined,\n multiple: props.multiple,\n maxFileSize: props.maxFileSize,\n onFilesReceived: handleFilesReceived,\n onValidationError: handleValidationError,\n});\n\n// File input ref\nconst fileInputRef = ref<HTMLInputElement>();\n\n// Open file picker\n// biome-ignore lint/correctness/noUnusedVariables: Used in slot templates\nconst openFilePicker = () => {\n if (!props.disabled) {\n fileInputRef.value?.click();\n }\n};\n\n// Computed states\n// biome-ignore lint/correctness/noUnusedVariables: Used in slot templates\nconst isActive = computed(\n () => dragDrop.state.value.isDragging || dragDrop.state.value.isOver,\n);\n</script>\n\n<template>\n <div\n class=\"flow-upload-zone\"\n :class=\"{\n 'flow-upload-zone--active': isActive,\n 'flow-upload-zone--disabled': disabled,\n 'flow-upload-zone--uploading': flowUpload.isUploading.value\n }\"\n @dragenter=\"!disabled && dragDrop.onDragEnter\"\n @dragover=\"!disabled && dragDrop.onDragOver\"\n @dragleave=\"!disabled && dragDrop.onDragLeave\"\n @drop=\"!disabled && dragDrop.onDrop\"\n @click=\"openFilePicker\"\n role=\"button\"\n :tabindex=\"disabled ? -1 : 0\"\n :aria-disabled=\"disabled\"\n aria-label=\"Upload file with flow processing\"\n @keydown.enter=\"openFilePicker\"\n @keydown.space.prevent=\"openFilePicker\"\n >\n <slot\n :is-dragging=\"dragDrop.state.value.isDragging\"\n :is-over=\"dragDrop.state.value.isOver\"\n :is-uploading=\"flowUpload.isUploading.value\"\n :is-processing=\"flowUpload.isProcessing.value\"\n :progress=\"flowUpload.state.value.progress\"\n :status=\"flowUpload.state.value.status\"\n :errors=\"[...dragDrop.state.value.errors]\"\n :open-file-picker=\"openFilePicker\"\n >\n <!-- Default slot content -->\n <div class=\"flow-upload-zone__content\">\n <p v-if=\"dragDrop.state.value.isDragging\">Drop file here...</p>\n <p v-else-if=\"flowUpload.isUploading.value\">\n Uploading... {{ flowUpload.state.value.progress }}%\n </p>\n <p v-else-if=\"flowUpload.isProcessing.value\">\n Processing...\n <span v-if=\"flowUpload.state.value.currentNodeName\">\n ({{ flowUpload.state.value.currentNodeName }})\n </span>\n </p>\n <p v-else-if=\"flowUpload.state.value.status === 'success'\">Upload complete!</p>\n <p v-else-if=\"flowUpload.state.value.status === 'error'\" class=\"flow-upload-zone__error\">\n Error: {{ flowUpload.state.value.error?.message }}\n </p>\n <p v-else>Drag a file here or click to select</p>\n\n <div v-if=\"flowUpload.isUploading.value\" class=\"flow-upload-zone__progress\">\n <div class=\"flow-upload-zone__progress-bar\">\n <div\n class=\"flow-upload-zone__progress-fill\"\n :style=\"{ width: `${flowUpload.state.value.progress}%` }\"\n />\n </div>\n </div>\n\n <div v-if=\"dragDrop.state.value.errors.length > 0\" class=\"flow-upload-zone__errors\">\n <p v-for=\"(error, index) in dragDrop.state.value.errors\" :key=\"index\">\n {{ error }}\n </p>\n </div>\n </div>\n </slot>\n\n <input\n ref=\"fileInputRef\"\n type=\"file\"\n :multiple=\"dragDrop.inputProps.value.multiple\"\n :accept=\"dragDrop.inputProps.value.accept\"\n :disabled=\"disabled\"\n @change=\"dragDrop.onInputChange\"\n style=\"display: none\"\n aria-hidden=\"true\"\n />\n </div>\n</template>\n\n<style scoped>\n.flow-upload-zone {\n cursor: pointer;\n user-select: none;\n}\n\n.flow-upload-zone--disabled {\n cursor: not-allowed;\n opacity: 0.6;\n}\n\n.flow-upload-zone--uploading {\n pointer-events: none;\n}\n\n.flow-upload-zone__content {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 0.5rem;\n}\n\n.flow-upload-zone__error {\n color: #dc3545;\n}\n\n.flow-upload-zone__progress {\n width: 100%;\n max-width: 300px;\n margin-top: 0.5rem;\n}\n\n.flow-upload-zone__progress-bar {\n width: 100%;\n height: 0.5rem;\n background-color: #e0e0e0;\n border-radius: 0.25rem;\n overflow: hidden;\n}\n\n.flow-upload-zone__progress-fill {\n height: 100%;\n background-color: #007bff;\n transition: width 0.2s ease;\n}\n\n.flow-upload-zone__errors {\n margin-top: 0.5rem;\n color: #dc3545;\n font-size: 0.875rem;\n}\n\n.flow-upload-zone__errors p {\n margin: 0.25rem 0;\n}\n</style>\n","<script setup lang=\"ts\">\n/**\n * UploadList - Display a list of uploads with customizable status grouping and sorting\n *\n * Shows the progress and status of multiple file uploads. Supports filtering, sorting,\n * and status-based grouping. Provides flexible slot-based customization for rendering each item.\n *\n * @component\n * @example\n * // Basic upload list\n * <UploadList :uploads=\"uploads\" />\n *\n * @example\n * // Custom item rendering with status indicators\n * <UploadList :uploads=\"uploads\">\n * <template #item=\"{ item, isSuccess, isError }\">\n * <div class=\"upload-item\">\n * <span>{{ item.filename }}</span>\n * <progress :value=\"item.progress\" max=\"100\"></progress>\n * <span :class=\"{ success: isSuccess, error: isError }\">\n * {{ item.state.status }}\n * </span>\n * </div>\n * </template>\n * </UploadList>\n *\n * @example\n * // With filtering and sorting\n * <UploadList\n * :uploads=\"uploads\"\n * :filter=\"item => item.state.status === 'success'\"\n * :sort-by=\"(a, b) => a.uploadedAt - b.uploadedAt\"\n * />\n */\nimport { computed } from \"vue\";\nimport type { UploadItem } from \"../composables\";\nimport { isBrowserFile } from \"../utils\";\n\n/**\n * Props for the UploadList component\n * @property {UploadItem[]} uploads - Array of upload items to display\n * @property {Function} filter - Optional filter for which items to display\n * @property {Function} sortBy - Optional sorting function for items (a, b) => number\n */\nexport interface UploadListProps {\n /**\n * Array of upload items to display\n */\n uploads: UploadItem[];\n\n /**\n * Optional filter for which items to display\n */\n filter?: (item: UploadItem) => boolean;\n\n /**\n * Optional sorting function for items\n */\n sortBy?: (a: UploadItem, b: UploadItem) => number;\n}\n\nconst props = defineProps<UploadListProps>();\n\ndefineSlots<{\n item(props: {\n item: UploadItem;\n index: number;\n isUploading: boolean;\n isSuccess: boolean;\n isError: boolean;\n formatFileSize: (bytes: number) => string;\n }): any;\n default?(props: {\n items: UploadItem[];\n itemsByStatus: {\n idle: UploadItem[];\n uploading: UploadItem[];\n success: UploadItem[];\n error: UploadItem[];\n aborted: UploadItem[];\n };\n }): any;\n}>();\n\n// Apply filtering and sorting\nconst filteredItems = computed(() => {\n let items = props.uploads;\n\n if (props.filter) {\n items = items.filter(props.filter);\n }\n\n if (props.sortBy) {\n items = [...items].sort(props.sortBy);\n }\n\n return items;\n});\n\n// Group items by status\n// biome-ignore lint/correctness/noUnusedVariables: Used in slot templates\nconst itemsByStatus = computed(() => ({\n idle: filteredItems.value.filter((item) => item.state.status === \"idle\"),\n uploading: filteredItems.value.filter(\n (item) => item.state.status === \"uploading\",\n ),\n success: filteredItems.value.filter(\n (item) => item.state.status === \"success\",\n ),\n error: filteredItems.value.filter((item) => item.state.status === \"error\"),\n aborted: filteredItems.value.filter(\n (item) => item.state.status === \"aborted\",\n ),\n}));\n\n// Helper function to format file sizes\n// biome-ignore lint/correctness/noUnusedVariables: Used in slot templates\nconst formatFileSize = (bytes: number): string => {\n if (bytes === 0) return \"0 Bytes\";\n const k = 1024;\n const sizes = [\"Bytes\", \"KB\", \"MB\", \"GB\"];\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n return `${parseFloat((bytes / k ** i).toFixed(2))} ${sizes[i]}`;\n};\n\n// Helper function to get status icon\n// biome-ignore lint/correctness/noUnusedVariables: Used in slot templates\nconst getStatusIcon = (status: string): string => {\n switch (status) {\n case \"idle\":\n return \"âŗ\";\n case \"uploading\":\n return \"đ¤\";\n case \"success\":\n return \"â
\";\n case \"error\":\n return \"â\";\n case \"aborted\":\n return \"âšī¸\";\n default:\n return \"â\";\n }\n};\n\n// Helper function to get status color\n// biome-ignore lint/correctness/noUnusedVariables: Used in slot templates\nconst getStatusColor = (status: string): string => {\n switch (status) {\n case \"idle\":\n return \"#6c757d\";\n case \"uploading\":\n return \"#007bff\";\n case \"success\":\n return \"#28a745\";\n case \"error\":\n return \"#dc3545\";\n case \"aborted\":\n return \"#6c757d\";\n default:\n return \"#6c757d\";\n }\n};\n</script>\n\n<template>\n <div class=\"upload-list\">\n <slot :items=\"filteredItems\" :items-by-status=\"itemsByStatus\">\n <!-- Default rendering: simple list of upload items -->\n <div\n v-for=\"(item, index) in filteredItems\"\n :key=\"item.id\"\n class=\"upload-list__item\"\n :class=\"`upload-list__item--${item.state.status}`\"\n >\n <slot\n name=\"item\"\n :item=\"item\"\n :index=\"index\"\n :is-uploading=\"item.state.status === 'uploading'\"\n :is-success=\"item.state.status === 'success'\"\n :is-error=\"item.state.status === 'error'\"\n :format-file-size=\"formatFileSize\"\n >\n <!-- Default item template -->\n <div class=\"upload-list__item-header\">\n <span class=\"upload-list__item-icon\">\n {{ getStatusIcon(item.state.status) }}\n </span>\n <span class=\"upload-list__item-name\">\n {{ isBrowserFile(item.file) ? item.file.name : 'File' }}\n </span>\n <span\n class=\"upload-list__item-status\"\n :style=\"{ color: getStatusColor(item.state.status) }\"\n >\n {{ item.state.status.toUpperCase() }}\n </span>\n </div>\n\n <div v-if=\"item.state.totalBytes\" class=\"upload-list__item-size\">\n {{ formatFileSize(item.state.totalBytes) }}\n </div>\n\n <div v-if=\"item.state.status === 'uploading'\" class=\"upload-list__item-progress\">\n <div class=\"upload-list__progress-bar\">\n <div\n class=\"upload-list__progress-fill\"\n :style=\"{ width: `${item.state.progress}%` }\"\n />\n </div>\n <span class=\"upload-list__progress-text\">{{ item.state.progress }}%</span>\n </div>\n\n <div v-if=\"item.state.status === 'error' && item.state.error\" class=\"upload-list__item-error\">\n {{ item.state.error.message }}\n </div>\n </slot>\n </div>\n </slot>\n </div>\n</template>\n\n<style scoped>\n.upload-list {\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n}\n\n.upload-list__item {\n padding: 0.75rem;\n border: 1px solid #e0e0e0;\n border-radius: 0.375rem;\n background-color: #fff;\n}\n\n.upload-list__item-header {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n margin-bottom: 0.5rem;\n}\n\n.upload-list__item-icon {\n font-size: 1rem;\n}\n\n.upload-list__item-name {\n flex: 1;\n font-weight: 500;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.upload-list__item-status {\n font-size: 0.75rem;\n font-weight: 600;\n text-transform: uppercase;\n}\n\n.upload-list__item-size {\n font-size: 0.75rem;\n color: #666;\n margin-bottom: 0.5rem;\n}\n\n.upload-list__item-progress {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n}\n\n.upload-list__progress-bar {\n flex: 1;\n height: 0.375rem;\n background-color: #e0e0e0;\n border-radius: 0.1875rem;\n overflow: hidden;\n}\n\n.upload-list__progress-fill {\n height: 100%;\n background-color: #007bff;\n transition: width 0.2s ease;\n}\n\n.upload-list__progress-text {\n font-size: 0.75rem;\n color: #666;\n min-width: 3rem;\n text-align: right;\n}\n\n.upload-list__item-error {\n margin-top: 0.5rem;\n padding: 0.5rem;\n background-color: #f8d7da;\n color: #721c24;\n font-size: 0.75rem;\n border-radius: 0.25rem;\n}\n</style>\n","<script setup lang=\"ts\">\n/**\n * UploadList - Display a list of uploads with customizable status grouping and sorting\n *\n * Shows the progress and status of multiple file uploads. Supports filtering, sorting,\n * and status-based grouping. Provides flexible slot-based customization for rendering each item.\n *\n * @component\n * @example\n * // Basic upload list\n * <UploadList :uploads=\"uploads\" />\n *\n * @example\n * // Custom item rendering with status indicators\n * <UploadList :uploads=\"uploads\">\n * <template #item=\"{ item, isSuccess, isError }\">\n * <div class=\"upload-item\">\n * <span>{{ item.filename }}</span>\n * <progress :value=\"item.progress\" max=\"100\"></progress>\n * <span :class=\"{ success: isSuccess, error: isError }\">\n * {{ item.state.status }}\n * </span>\n * </div>\n * </template>\n * </UploadList>\n *\n * @example\n * // With filtering and sorting\n * <UploadList\n * :uploads=\"uploads\"\n * :filter=\"item => item.state.status === 'success'\"\n * :sort-by=\"(a, b) => a.uploadedAt - b.uploadedAt\"\n * />\n */\nimport { computed } from \"vue\";\nimport type { UploadItem } from \"../composables\";\nimport { isBrowserFile } from \"../utils\";\n\n/**\n * Props for the UploadList component\n * @property {UploadItem[]} uploads - Array of upload items to display\n * @property {Function} filter - Optional filter for which items to display\n * @property {Function} sortBy - Optional sorting function for items (a, b) => number\n */\nexport interface UploadListProps {\n /**\n * Array of upload items to display\n */\n uploads: UploadItem[];\n\n /**\n * Optional filter for which items to display\n */\n filter?: (item: UploadItem) => boolean;\n\n /**\n * Optional sorting function for items\n */\n sortBy?: (a: UploadItem, b: UploadItem) => number;\n}\n\nconst props = defineProps<UploadListProps>();\n\ndefineSlots<{\n item(props: {\n item: UploadItem;\n index: number;\n isUploading: boolean;\n isSuccess: boolean;\n isError: boolean;\n formatFileSize: (bytes: number) => string;\n }): any;\n default?(props: {\n items: UploadItem[];\n itemsByStatus: {\n idle: UploadItem[];\n uploading: UploadItem[];\n success: UploadItem[];\n error: UploadItem[];\n aborted: UploadItem[];\n };\n }): any;\n}>();\n\n// Apply filtering and sorting\nconst filteredItems = computed(() => {\n let items = props.uploads;\n\n if (props.filter) {\n items = items.filter(props.filter);\n }\n\n if (props.sortBy) {\n items = [...items].sort(props.sortBy);\n }\n\n return items;\n});\n\n// Group items by status\n// biome-ignore lint/correctness/noUnusedVariables: Used in slot templates\nconst itemsByStatus = computed(() => ({\n idle: filteredItems.value.filter((item) => item.state.status === \"idle\"),\n uploading: filteredItems.value.filter(\n (item) => item.state.status === \"uploading\",\n ),\n success: filteredItems.value.filter(\n (item) => item.state.status === \"success\",\n ),\n error: filteredItems.value.filter((item) => item.state.status === \"error\"),\n aborted: filteredItems.value.filter(\n (item) => item.state.status === \"aborted\",\n ),\n}));\n\n// Helper function to format file sizes\n// biome-ignore lint/correctness/noUnusedVariables: Used in slot templates\nconst formatFileSize = (bytes: number): string => {\n if (bytes === 0) return \"0 Bytes\";\n const k = 1024;\n const sizes = [\"Bytes\", \"KB\", \"MB\", \"GB\"];\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n return `${parseFloat((bytes / k ** i).toFixed(2))} ${sizes[i]}`;\n};\n\n// Helper function to get status icon\n// biome-ignore lint/correctness/noUnusedVariables: Used in slot templates\nconst getStatusIcon = (status: string): string => {\n switch (status) {\n case \"idle\":\n return \"âŗ\";\n case \"uploading\":\n return \"đ¤\";\n case \"success\":\n return \"â
\";\n case \"error\":\n return \"â\";\n case \"aborted\":\n return \"âšī¸\";\n default:\n return \"â\";\n }\n};\n\n// Helper function to get status color\n// biome-ignore lint/correctness/noUnusedVariables: Used in slot templates\nconst getStatusColor = (status: string): string => {\n switch (status) {\n case \"idle\":\n return \"#6c757d\";\n case \"uploading\":\n return \"#007bff\";\n case \"success\":\n return \"#28a745\";\n case \"error\":\n return \"#dc3545\";\n case \"aborted\":\n return \"#6c757d\";\n default:\n return \"#6c757d\";\n }\n};\n</script>\n\n<template>\n <div class=\"upload-list\">\n <slot :items=\"filteredItems\" :items-by-status=\"itemsByStatus\">\n <!-- Default rendering: simple list of upload items -->\n <div\n v-for=\"(item, index) in filteredItems\"\n :key=\"item.id\"\n class=\"upload-list__item\"\n :class=\"`upload-list__item--${item.state.status}`\"\n >\n <slot\n name=\"item\"\n :item=\"item\"\n :index=\"index\"\n :is-uploading=\"item.state.status === 'uploading'\"\n :is-success=\"item.state.status === 'success'\"\n :is-error=\"item.state.status === 'error'\"\n :format-file-size=\"formatFileSize\"\n >\n <!-- Default item template -->\n <div class=\"upload-list__item-header\">\n <span class=\"upload-list__item-icon\">\n {{ getStatusIcon(item.state.status) }}\n </span>\n <span class=\"upload-list__item-name\">\n {{ isBrowserFile(item.file) ? item.file.name : 'File' }}\n </span>\n <span\n class=\"upload-list__item-status\"\n :style=\"{ color: getStatusColor(item.state.status) }\"\n >\n {{ item.state.status.toUpperCase() }}\n </span>\n </div>\n\n <div v-if=\"item.state.totalBytes\" class=\"upload-list__item-size\">\n {{ formatFileSize(item.state.totalBytes) }}\n </div>\n\n <div v-if=\"item.state.status === 'uploading'\" class=\"upload-list__item-progress\">\n <div class=\"upload-list__progress-bar\">\n <div\n class=\"upload-list__progress-fill\"\n :style=\"{ width: `${item.state.progress}%` }\"\n />\n </div>\n <span class=\"upload-list__progress-text\">{{ item.state.progress }}%</span>\n </div>\n\n <div v-if=\"item.state.status === 'error' && item.state.error\" class=\"upload-list__item-error\">\n {{ item.state.error.message }}\n </div>\n </slot>\n </div>\n </slot>\n </div>\n</template>\n\n<style scoped>\n.upload-list {\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n}\n\n.upload-list__item {\n padding: 0.75rem;\n border: 1px solid #e0e0e0;\n border-radius: 0.375rem;\n background-color: #fff;\n}\n\n.upload-list__item-header {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n margin-bottom: 0.5rem;\n}\n\n.upload-list__item-icon {\n font-size: 1rem;\n}\n\n.upload-list__item-name {\n flex: 1;\n font-weight: 500;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.upload-list__item-status {\n font-size: 0.75rem;\n font-weight: 600;\n text-transform: uppercase;\n}\n\n.upload-list__item-size {\n font-size: 0.75rem;\n color: #666;\n margin-bottom: 0.5rem;\n}\n\n.upload-list__item-progress {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n}\n\n.upload-list__progress-bar {\n flex: 1;\n height: 0.375rem;\n background-color: #e0e0e0;\n border-radius: 0.1875rem;\n overflow: hidden;\n}\n\n.upload-list__progress-fill {\n height: 100%;\n background-color: #007bff;\n transition: width 0.2s ease;\n}\n\n.upload-list__progress-text {\n font-size: 0.75rem;\n color: #666;\n min-width: 3rem;\n text-align: right;\n}\n\n.upload-list__item-error {\n margin-top: 0.5rem;\n padding: 0.5rem;\n background-color: #f8d7da;\n color: #721c24;\n font-size: 0.75rem;\n border-radius: 0.25rem;\n}\n</style>\n","<script setup lang=\"ts\">\n/**\n * UploadZone - A flexible file upload component with drag-and-drop support\n *\n * Provides a drag-and-drop zone and file picker for uploading files. Supports both single\n * and multiple file uploads with validation. Emits events for file selection and upload events.\n *\n * @component\n * @example\n * // Basic single file upload\n * <UploadZone @file-select=\"handleFiles\" />\n *\n * @example\n * // Multiple files with validation\n * <UploadZone\n * multiple\n * accept={[\"image/*\"]}\n * :max-file-size=\"10 * 1024 * 1024\"\n * @file-select=\"handleFiles\"\n * @validation-error=\"handleErrors\"\n * >\n * <template #default=\"{ isDragging, errors, openFilePicker }\">\n * <div :class=\"{ dragging: isDragging }\" @click=\"openFilePicker\">\n * <p>{{ isDragging ? 'Drop files here' : 'Click or drag files here' }}</p>\n * <div v-if=\"errors.length\">\n * <p v-for=\"error in errors\" :key=\"error\">{{ error }}</p>\n * </div>\n * </div>\n * </template>\n * </UploadZone>\n *\n * @emits file-select - When files are selected/dropped\n * @emits upload-start - When upload begins\n * @emits validation-error - When validation fails\n */\nimport type { UploadOptions } from \"@uploadista/client-browser\";\nimport { computed, ref } from \"vue\";\nimport type { MultiUploadOptions } from \"../composables\";\nimport { useDragDrop, useMultiUpload, useUpload } from \"../composables\";\n\n/**\n * Props for the UploadZone component\n * @property {string[]} accept - Accepted file types (MIME types or file extensions)\n * @property {boolean} multiple - Whether to allow multiple files (default: true)\n * @property {boolean} disabled - Whether the upload zone is disabled (default: false)\n * @property {number} maxFileSize - Maximum file size in bytes\n * @property {Function} validator - Custom validation function for files\n * @property {MultiUploadOptions} multiUploadOptions - Multi-upload options (only used when multiple=true)\n * @property {UploadOptions} uploadOptions - Single upload options (only used when multiple=false)\n */\nexport interface UploadZoneProps {\n /**\n * Accepted file types (MIME types or file extensions)\n */\n accept?: string[];\n\n /**\n * Whether to allow multiple files\n */\n multiple?: boolean;\n\n /**\n * Whether the upload zone is disabled\n */\n disabled?: boolean;\n\n /**\n * Maximum file size in bytes\n */\n maxFileSize?: number;\n\n /**\n * Custom validation function for files\n */\n validator?: (files: File[]) => string[] | null;\n\n /**\n * Multi-upload options (only used when multiple=true)\n */\n multiUploadOptions?: MultiUploadOptions;\n\n /**\n * Single upload options (only used when multiple=false)\n */\n uploadOptions?: UploadOptions;\n}\n\nconst props = withDefaults(defineProps<UploadZoneProps>(), {\n multiple: true,\n disabled: false,\n});\n\nconst emit = defineEmits<{\n \"file-select\": [files: File[]];\n \"upload-start\": [files: File[]];\n \"validation-error\": [errors: string[]];\n}>();\n\ndefineSlots<{\n // biome-ignore lint/suspicious/noExplicitAny: Vue slot definition requires any\n default(props: {\n isDragging: boolean;\n isOver: boolean;\n isUploading: boolean;\n errors: string[];\n openFilePicker: () => void;\n }): any;\n}>();\n\n// Initialize upload composables\nconst singleUpload = props.multiple\n ? null\n : useUpload(props.uploadOptions || {});\nconst multiUpload = props.multiple\n ? useMultiUpload(props.multiUploadOptions || {})\n : null;\n\n// Handle files received from drag-drop or file picker\nconst handleFilesReceived = (files: File[]) => {\n emit(\"file-select\", files);\n emit(\"upload-start\", files);\n\n if (props.multiple && multiUpload) {\n multiUpload.addFiles(files);\n setTimeout(() => multiUpload.startAll(), 0);\n } else if (!props.multiple && singleUpload && files[0]) {\n singleUpload.upload(files[0]);\n }\n};\n\n// Handle validation errors\nconst handleValidationError = (errors: string[]) => {\n emit(\"validation-error\", errors);\n};\n\n// Initialize drag-drop\nconst dragDrop = useDragDrop({\n accept: props.accept,\n multiple: props.multiple,\n maxFileSize: props.maxFileSize,\n validator: props.validator,\n onFilesReceived: handleFilesReceived,\n onValidationError: handleValidationError,\n});\n\n// File input ref\nconst fileInputRef = ref<HTMLInputElement>();\n\n// Open file picker\n// biome-ignore lint/correctness/noUnusedVariables: Used in slot templates\nconst openFilePicker = () => {\n if (!props.disabled) {\n fileInputRef.value?.click();\n }\n};\n\n// Computed states\n// biome-ignore lint/correctness/noUnusedVariables: Used in slot templates\nconst isActive = computed(\n () => dragDrop.state.value.isDragging || dragDrop.state.value.isOver,\n);\n\n// biome-ignore lint/correctness/noUnusedVariables: Used in slot templates\nconst isUploading = computed(() => {\n if (props.multiple && multiUpload) {\n return multiUpload.state.value.isUploading;\n } else if (!props.multiple && singleUpload) {\n return singleUpload.state.value.status === \"uploading\";\n }\n return false;\n});\n</script>\n\n<template>\n <div\n class=\"upload-zone\"\n :class=\"{ 'upload-zone--active': isActive, 'upload-zone--disabled': disabled }\"\n @dragenter=\"!disabled && dragDrop.onDragEnter\"\n @dragover=\"!disabled && dragDrop.onDragOver\"\n @dragleave=\"!disabled && dragDrop.onDragLeave\"\n @drop=\"!disabled && dragDrop.onDrop\"\n @click=\"openFilePicker\"\n role=\"button\"\n :tabindex=\"disabled ? -1 : 0\"\n :aria-disabled=\"disabled\"\n :aria-label=\"multiple ? 'Upload multiple files' : 'Upload a file'\"\n @keydown.enter=\"openFilePicker\"\n @keydown.space.prevent=\"openFilePicker\"\n >\n <slot\n :is-dragging=\"dragDrop.state.value.isDragging\"\n :is-over=\"dragDrop.state.value.isOver\"\n :is-uploading=\"isUploading\"\n :errors=\"[...dragDrop.state.value.errors]\"\n :open-file-picker=\"openFilePicker\"\n >\n <!-- Default slot content -->\n <div class=\"upload-zone__content\">\n <p v-if=\"dragDrop.state.value.isDragging\">\n {{ multiple ? 'Drop files here...' : 'Drop file here...' }}\n </p>\n <p v-else>\n {{ multiple ? 'Drag files here or click to select' : 'Drag a file here or click to select' }}\n </p>\n\n <div v-if=\"dragDrop.state.value.errors.length > 0\" class=\"upload-zone__errors\">\n <p v-for=\"(error, index) in dragDrop.state.value.errors\" :key=\"index\">\n {{ error }}\n </p>\n </div>\n </div>\n </slot>\n\n <input\n ref=\"fileInputRef\"\n type=\"file\"\n :multiple=\"dragDrop.inputProps.value.multiple\"\n :accept=\"dragDrop.inputProps.value.accept\"\n :disabled=\"disabled\"\n @change=\"dragDrop.onInputChange\"\n style=\"display: none\"\n aria-hidden=\"true\"\n />\n </div>\n</template>\n\n<style scoped>\n.upload-zone {\n cursor: pointer;\n user-select: none;\n}\n\n.upload-zone--disabled {\n cursor: not-allowed;\n opacity: 0.6;\n}\n\n.upload-zone__content {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n}\n\n.upload-zone__errors {\n margin-top: 0.5rem;\n color: #dc3545;\n font-size: 0.875rem;\n}\n\n.upload-zone__errors p {\n margin: 0.25rem 0;\n}\n</style>\n","<script setup lang=\"ts\">\n/**\n * UploadZone - A flexible file upload component with drag-and-drop support\n *\n * Provides a drag-and-drop zone and file picker for uploading files. Supports both single\n * and multiple file uploads with validation. Emits events for file selection and upload events.\n *\n * @component\n * @example\n * // Basic single file upload\n * <UploadZone @file-select=\"handleFiles\" />\n *\n * @example\n * // Multiple files with validation\n * <UploadZone\n * multiple\n * accept={[\"image/*\"]}\n * :max-file-size=\"10 * 1024 * 1024\"\n * @file-select=\"handleFiles\"\n * @validation-error=\"handleErrors\"\n * >\n * <template #default=\"{ isDragging, errors, openFilePicker }\">\n * <div :class=\"{ dragging: isDragging }\" @click=\"openFilePicker\">\n * <p>{{ isDragging ? 'Drop files here' : 'Click or drag files here' }}</p>\n * <div v-if=\"errors.length\">\n * <p v-for=\"error in errors\" :key=\"error\">{{ error }}</p>\n * </div>\n * </div>\n * </template>\n * </UploadZone>\n *\n * @emits file-select - When files are selected/dropped\n * @emits upload-start - When upload begins\n * @emits validation-error - When validation fails\n */\nimport type { UploadOptions } from \"@uploadista/client-browser\";\nimport { computed, ref } from \"vue\";\nimport type { MultiUploadOptions } from \"../composables\";\nimport { useDragDrop, useMultiUpload, useUpload } from \"../composables\";\n\n/**\n * Props for the UploadZone component\n * @property {string[]} accept - Accepted file types (MIME types or file extensions)\n * @property {boolean} multiple - Whether to allow multiple files (default: true)\n * @property {boolean} disabled - Whether the upload zone is disabled (default: false)\n * @property {number} maxFileSize - Maximum file size in bytes\n * @property {Function} validator - Custom validation function for files\n * @property {MultiUploadOptions} multiUploadOptions - Multi-upload options (only used when multiple=true)\n * @property {UploadOptions} uploadOptions - Single upload options (only used when multiple=false)\n */\nexport interface UploadZoneProps {\n /**\n * Accepted file types (MIME types or file extensions)\n */\n accept?: string[];\n\n /**\n * Whether to allow multiple files\n */\n multiple?: boolean;\n\n /**\n * Whether the upload zone is disabled\n */\n disabled?: boolean;\n\n /**\n * Maximum file size in bytes\n */\n maxFileSize?: number;\n\n /**\n * Custom validation function for files\n */\n validator?: (files: File[]) => string[] | null;\n\n /**\n * Multi-upload options (only used when multiple=true)\n */\n multiUploadOptions?: MultiUploadOptions;\n\n /**\n * Single upload options (only used when multiple=false)\n */\n uploadOptions?: UploadOptions;\n}\n\nconst props = withDefaults(defineProps<UploadZoneProps>(), {\n multiple: true,\n disabled: false,\n});\n\nconst emit = defineEmits<{\n \"file-select\": [files: File[]];\n \"upload-start\": [files: File[]];\n \"validation-error\": [errors: string[]];\n}>();\n\ndefineSlots<{\n // biome-ignore lint/suspicious/noExplicitAny: Vue slot definition requires any\n default(props: {\n isDragging: boolean;\n isOver: boolean;\n isUploading: boolean;\n errors: string[];\n openFilePicker: () => void;\n }): any;\n}>();\n\n// Initialize upload composables\nconst singleUpload = props.multiple\n ? null\n : useUpload(props.uploadOptions || {});\nconst multiUpload = props.multiple\n ? useMultiUpload(props.multiUploadOptions || {})\n : null;\n\n// Handle files received from drag-drop or file picker\nconst handleFilesReceived = (files: File[]) => {\n emit(\"file-select\", files);\n emit(\"upload-start\", files);\n\n if (props.multiple && multiUpload) {\n multiUpload.addFiles(files);\n setTimeout(() => multiUpload.startAll(), 0);\n } else if (!props.multiple && singleUpload && files[0]) {\n singleUpload.upload(files[0]);\n }\n};\n\n// Handle validation errors\nconst handleValidationError = (errors: string[]) => {\n emit(\"validation-error\", errors);\n};\n\n// Initialize drag-drop\nconst dragDrop = useDragDrop({\n accept: props.accept,\n multiple: props.multiple,\n maxFileSize: props.maxFileSize,\n validator: props.validator,\n onFilesReceived: handleFilesReceived,\n onValidationError: handleValidationError,\n});\n\n// File input ref\nconst fileInputRef = ref<HTMLInputElement>();\n\n// Open file picker\n// biome-ignore lint/correctness/noUnusedVariables: Used in slot templates\nconst openFilePicker = () => {\n if (!props.disabled) {\n fileInputRef.value?.click();\n }\n};\n\n// Computed states\n// biome-ignore lint/correctness/noUnusedVariables: Used in slot templates\nconst isActive = computed(\n () => dragDrop.state.value.isDragging || dragDrop.state.value.isOver,\n);\n\n// biome-ignore lint/correctness/noUnusedVariables: Used in slot templates\nconst isUploading = computed(() => {\n if (props.multiple && multiUpload) {\n return multiUpload.state.value.isUploading;\n } else if (!props.multiple && singleUpload) {\n return singleUpload.state.value.status === \"uploading\";\n }\n return false;\n});\n</script>\n\n<template>\n <div\n class=\"upload-zone\"\n :class=\"{ 'upload-zone--active': isActive, 'upload-zone--disabled': disabled }\"\n @dragenter=\"!disabled && dragDrop.onDragEnter\"\n @dragover=\"!disabled && dragDrop.onDragOver\"\n @dragleave=\"!disabled && dragDrop.onDragLeave\"\n @drop=\"!disabled && dragDrop.onDrop\"\n @click=\"openFilePicker\"\n role=\"button\"\n :tabindex=\"disabled ? -1 : 0\"\n :aria-disabled=\"disabled\"\n :aria-label=\"multiple ? 'Upload multiple files' : 'Upload a file'\"\n @keydown.enter=\"openFilePicker\"\n @keydown.space.prevent=\"openFilePicker\"\n >\n <slot\n :is-dragging=\"dragDrop.state.value.isDragging\"\n :is-over=\"dragDrop.state.value.isOver\"\n :is-uploading=\"isUploading\"\n :errors=\"[...dragDrop.state.value.errors]\"\n :open-file-picker=\"openFilePicker\"\n >\n <!-- Default slot content -->\n <div class=\"upload-zone__content\">\n <p v-if=\"dragDrop.state.value.isDragging\">\n {{ multiple ? 'Drop files here...' : 'Drop file here...' }}\n </p>\n <p v-else>\n {{ multiple ? 'Drag files here or click to select' : 'Drag a file here or click to select' }}\n </p>\n\n <div v-if=\"dragDrop.state.value.errors.length > 0\" class=\"upload-zone__errors\">\n <p v-for=\"(error, index) in dragDrop.state.value.errors\" :key=\"index\">\n {{ error }}\n </p>\n </div>\n </div>\n </slot>\n\n <input\n ref=\"fileInputRef\"\n type=\"file\"\n :multiple=\"dragDrop.inputProps.value.multiple\"\n :accept=\"dragDrop.inputProps.value.accept\"\n :disabled=\"disabled\"\n @change=\"dragDrop.onInputChange\"\n style=\"display: none\"\n aria-hidden=\"true\"\n />\n </div>\n</template>\n\n<style scoped>\n.upload-zone {\n cursor: pointer;\n user-select: none;\n}\n\n.upload-zone--disabled {\n cursor: not-allowed;\n opacity: 0.6;\n}\n\n.upload-zone__content {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n}\n\n.upload-zone__errors {\n margin-top: 0.5rem;\n color: #dc3545;\n font-size: 0.875rem;\n}\n\n.upload-zone__errors p {\n margin: 0.25rem 0;\n}\n</style>\n"],"mappings":"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"}
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
|
|
2
|
+
.flow-upload-list[data-v-eabb787d] {
|
|
3
|
+
display: flex;
|
|
4
|
+
flex-direction: column;
|
|
5
|
+
gap: 0.5rem;
|
|
6
|
+
}
|
|
7
|
+
.flow-upload-list__item[data-v-eabb787d] {
|
|
8
|
+
padding: 0.75rem;
|
|
9
|
+
border: 1px solid #e0e0e0;
|
|
10
|
+
border-radius: 0.375rem;
|
|
11
|
+
background-color: #fff;
|
|
12
|
+
}
|
|
13
|
+
.flow-upload-list__item-header[data-v-eabb787d] {
|
|
14
|
+
display: flex;
|
|
15
|
+
align-items: center;
|
|
16
|
+
gap: 0.5rem;
|
|
17
|
+
margin-bottom: 0.5rem;
|
|
18
|
+
}
|
|
19
|
+
.flow-upload-list__item-icon[data-v-eabb787d] {
|
|
20
|
+
font-size: 1rem;
|
|
21
|
+
}
|
|
22
|
+
.flow-upload-list__item-name[data-v-eabb787d] {
|
|
23
|
+
flex: 1;
|
|
24
|
+
font-weight: 500;
|
|
25
|
+
overflow: hidden;
|
|
26
|
+
text-overflow: ellipsis;
|
|
27
|
+
white-space: nowrap;
|
|
28
|
+
}
|
|
29
|
+
.flow-upload-list__item-status[data-v-eabb787d] {
|
|
30
|
+
font-size: 0.75rem;
|
|
31
|
+
font-weight: 600;
|
|
32
|
+
text-transform: uppercase;
|
|
33
|
+
}
|
|
34
|
+
.flow-upload-list__item-details[data-v-eabb787d] {
|
|
35
|
+
display: flex;
|
|
36
|
+
gap: 1rem;
|
|
37
|
+
font-size: 0.75rem;
|
|
38
|
+
color: #666;
|
|
39
|
+
margin-bottom: 0.5rem;
|
|
40
|
+
}
|
|
41
|
+
.flow-upload-list__item-size[data-v-eabb787d] {
|
|
42
|
+
font-weight: 500;
|
|
43
|
+
}
|
|
44
|
+
.flow-upload-list__item-job[data-v-eabb787d] {
|
|
45
|
+
color: #999;
|
|
46
|
+
font-family: monospace;
|
|
47
|
+
}
|
|
48
|
+
.flow-upload-list__item-progress[data-v-eabb787d] {
|
|
49
|
+
display: flex;
|
|
50
|
+
flex-direction: column;
|
|
51
|
+
gap: 0.25rem;
|
|
52
|
+
}
|
|
53
|
+
.flow-upload-list__progress-bar[data-v-eabb787d] {
|
|
54
|
+
width: 100%;
|
|
55
|
+
height: 0.375rem;
|
|
56
|
+
background-color: #e0e0e0;
|
|
57
|
+
border-radius: 0.1875rem;
|
|
58
|
+
overflow: hidden;
|
|
59
|
+
}
|
|
60
|
+
.flow-upload-list__progress-fill[data-v-eabb787d] {
|
|
61
|
+
height: 100%;
|
|
62
|
+
background-color: #007bff;
|
|
63
|
+
transition: width 0.2s ease;
|
|
64
|
+
}
|
|
65
|
+
.flow-upload-list__progress-text[data-v-eabb787d] {
|
|
66
|
+
font-size: 0.75rem;
|
|
67
|
+
color: #666;
|
|
68
|
+
}
|
|
69
|
+
.flow-upload-list__item-error[data-v-eabb787d] {
|
|
70
|
+
margin-top: 0.5rem;
|
|
71
|
+
padding: 0.5rem;
|
|
72
|
+
background-color: #f8d7da;
|
|
73
|
+
color: #721c24;
|
|
74
|
+
font-size: 0.75rem;
|
|
75
|
+
border-radius: 0.25rem;
|
|
76
|
+
}
|
|
77
|
+
.flow-upload-list__item-success[data-v-eabb787d] {
|
|
78
|
+
margin-top: 0.5rem;
|
|
79
|
+
padding: 0.5rem;
|
|
80
|
+
background-color: #d4edda;
|
|
81
|
+
color: #155724;
|
|
82
|
+
font-size: 0.75rem;
|
|
83
|
+
border-radius: 0.25rem;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
.flow-upload-zone[data-v-e6d6e2c2] {
|
|
88
|
+
cursor: pointer;
|
|
89
|
+
user-select: none;
|
|
90
|
+
}
|
|
91
|
+
.flow-upload-zone--disabled[data-v-e6d6e2c2] {
|
|
92
|
+
cursor: not-allowed;
|
|
93
|
+
opacity: 0.6;
|
|
94
|
+
}
|
|
95
|
+
.flow-upload-zone--uploading[data-v-e6d6e2c2] {
|
|
96
|
+
pointer-events: none;
|
|
97
|
+
}
|
|
98
|
+
.flow-upload-zone__content[data-v-e6d6e2c2] {
|
|
99
|
+
display: flex;
|
|
100
|
+
flex-direction: column;
|
|
101
|
+
align-items: center;
|
|
102
|
+
justify-content: center;
|
|
103
|
+
gap: 0.5rem;
|
|
104
|
+
}
|
|
105
|
+
.flow-upload-zone__error[data-v-e6d6e2c2] {
|
|
106
|
+
color: #dc3545;
|
|
107
|
+
}
|
|
108
|
+
.flow-upload-zone__progress[data-v-e6d6e2c2] {
|
|
109
|
+
width: 100%;
|
|
110
|
+
max-width: 300px;
|
|
111
|
+
margin-top: 0.5rem;
|
|
112
|
+
}
|
|
113
|
+
.flow-upload-zone__progress-bar[data-v-e6d6e2c2] {
|
|
114
|
+
width: 100%;
|
|
115
|
+
height: 0.5rem;
|
|
116
|
+
background-color: #e0e0e0;
|
|
117
|
+
border-radius: 0.25rem;
|
|
118
|
+
overflow: hidden;
|
|
119
|
+
}
|
|
120
|
+
.flow-upload-zone__progress-fill[data-v-e6d6e2c2] {
|
|
121
|
+
height: 100%;
|
|
122
|
+
background-color: #007bff;
|
|
123
|
+
transition: width 0.2s ease;
|
|
124
|
+
}
|
|
125
|
+
.flow-upload-zone__errors[data-v-e6d6e2c2] {
|
|
126
|
+
margin-top: 0.5rem;
|
|
127
|
+
color: #dc3545;
|
|
128
|
+
font-size: 0.875rem;
|
|
129
|
+
}
|
|
130
|
+
.flow-upload-zone__errors p[data-v-e6d6e2c2] {
|
|
131
|
+
margin: 0.25rem 0;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
.upload-list[data-v-70c8fe1a] {
|
|
136
|
+
display: flex;
|
|
137
|
+
flex-direction: column;
|
|
138
|
+
gap: 0.5rem;
|
|
139
|
+
}
|
|
140
|
+
.upload-list__item[data-v-70c8fe1a] {
|
|
141
|
+
padding: 0.75rem;
|
|
142
|
+
border: 1px solid #e0e0e0;
|
|
143
|
+
border-radius: 0.375rem;
|
|
144
|
+
background-color: #fff;
|
|
145
|
+
}
|
|
146
|
+
.upload-list__item-header[data-v-70c8fe1a] {
|
|
147
|
+
display: flex;
|
|
148
|
+
align-items: center;
|
|
149
|
+
gap: 0.5rem;
|
|
150
|
+
margin-bottom: 0.5rem;
|
|
151
|
+
}
|
|
152
|
+
.upload-list__item-icon[data-v-70c8fe1a] {
|
|
153
|
+
font-size: 1rem;
|
|
154
|
+
}
|
|
155
|
+
.upload-list__item-name[data-v-70c8fe1a] {
|
|
156
|
+
flex: 1;
|
|
157
|
+
font-weight: 500;
|
|
158
|
+
overflow: hidden;
|
|
159
|
+
text-overflow: ellipsis;
|
|
160
|
+
white-space: nowrap;
|
|
161
|
+
}
|
|
162
|
+
.upload-list__item-status[data-v-70c8fe1a] {
|
|
163
|
+
font-size: 0.75rem;
|
|
164
|
+
font-weight: 600;
|
|
165
|
+
text-transform: uppercase;
|
|
166
|
+
}
|
|
167
|
+
.upload-list__item-size[data-v-70c8fe1a] {
|
|
168
|
+
font-size: 0.75rem;
|
|
169
|
+
color: #666;
|
|
170
|
+
margin-bottom: 0.5rem;
|
|
171
|
+
}
|
|
172
|
+
.upload-list__item-progress[data-v-70c8fe1a] {
|
|
173
|
+
display: flex;
|
|
174
|
+
align-items: center;
|
|
175
|
+
gap: 0.5rem;
|
|
176
|
+
}
|
|
177
|
+
.upload-list__progress-bar[data-v-70c8fe1a] {
|
|
178
|
+
flex: 1;
|
|
179
|
+
height: 0.375rem;
|
|
180
|
+
background-color: #e0e0e0;
|
|
181
|
+
border-radius: 0.1875rem;
|
|
182
|
+
overflow: hidden;
|
|
183
|
+
}
|
|
184
|
+
.upload-list__progress-fill[data-v-70c8fe1a] {
|
|
185
|
+
height: 100%;
|
|
186
|
+
background-color: #007bff;
|
|
187
|
+
transition: width 0.2s ease;
|
|
188
|
+
}
|
|
189
|
+
.upload-list__progress-text[data-v-70c8fe1a] {
|
|
190
|
+
font-size: 0.75rem;
|
|
191
|
+
color: #666;
|
|
192
|
+
min-width: 3rem;
|
|
193
|
+
text-align: right;
|
|
194
|
+
}
|
|
195
|
+
.upload-list__item-error[data-v-70c8fe1a] {
|
|
196
|
+
margin-top: 0.5rem;
|
|
197
|
+
padding: 0.5rem;
|
|
198
|
+
background-color: #f8d7da;
|
|
199
|
+
color: #721c24;
|
|
200
|
+
font-size: 0.75rem;
|
|
201
|
+
border-radius: 0.25rem;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
.upload-zone[data-v-8b709bef] {
|
|
206
|
+
cursor: pointer;
|
|
207
|
+
user-select: none;
|
|
208
|
+
}
|
|
209
|
+
.upload-zone--disabled[data-v-8b709bef] {
|
|
210
|
+
cursor: not-allowed;
|
|
211
|
+
opacity: 0.6;
|
|
212
|
+
}
|
|
213
|
+
.upload-zone__content[data-v-8b709bef] {
|
|
214
|
+
display: flex;
|
|
215
|
+
flex-direction: column;
|
|
216
|
+
align-items: center;
|
|
217
|
+
justify-content: center;
|
|
218
|
+
}
|
|
219
|
+
.upload-zone__errors[data-v-8b709bef] {
|
|
220
|
+
margin-top: 0.5rem;
|
|
221
|
+
color: #dc3545;
|
|
222
|
+
font-size: 0.875rem;
|
|
223
|
+
}
|
|
224
|
+
.upload-zone__errors p[data-v-8b709bef] {
|
|
225
|
+
margin: 0.25rem 0;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
/*# sourceMappingURL=components-DyNRHWp9.css.map*/
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"components-DyNRHWp9.css","names":[],"sources":["../src/components/FlowUploadList.vue?vue&type=style&index=0&scoped=eabb787d&lang.css","../src/components/FlowUploadZone.vue?vue&type=style&index=0&scoped=e6d6e2c2&lang.css","../src/components/UploadList.vue?vue&type=style&index=0&scoped=70c8fe1a&lang.css","../src/components/UploadZone.vue?vue&type=style&index=0&scoped=8b709bef&lang.css"],"sourcesContent":["\n.flow-upload-list[data-v-eabb787d] {\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n}\n.flow-upload-list__item[data-v-eabb787d] {\n padding: 0.75rem;\n border: 1px solid #e0e0e0;\n border-radius: 0.375rem;\n background-color: #fff;\n}\n.flow-upload-list__item-header[data-v-eabb787d] {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n margin-bottom: 0.5rem;\n}\n.flow-upload-list__item-icon[data-v-eabb787d] {\n font-size: 1rem;\n}\n.flow-upload-list__item-name[data-v-eabb787d] {\n flex: 1;\n font-weight: 500;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.flow-upload-list__item-status[data-v-eabb787d] {\n font-size: 0.75rem;\n font-weight: 600;\n text-transform: uppercase;\n}\n.flow-upload-list__item-details[data-v-eabb787d] {\n display: flex;\n gap: 1rem;\n font-size: 0.75rem;\n color: #666;\n margin-bottom: 0.5rem;\n}\n.flow-upload-list__item-size[data-v-eabb787d] {\n font-weight: 500;\n}\n.flow-upload-list__item-job[data-v-eabb787d] {\n color: #999;\n font-family: monospace;\n}\n.flow-upload-list__item-progress[data-v-eabb787d] {\n display: flex;\n flex-direction: column;\n gap: 0.25rem;\n}\n.flow-upload-list__progress-bar[data-v-eabb787d] {\n width: 100%;\n height: 0.375rem;\n background-color: #e0e0e0;\n border-radius: 0.1875rem;\n overflow: hidden;\n}\n.flow-upload-list__progress-fill[data-v-eabb787d] {\n height: 100%;\n background-color: #007bff;\n transition: width 0.2s ease;\n}\n.flow-upload-list__progress-text[data-v-eabb787d] {\n font-size: 0.75rem;\n color: #666;\n}\n.flow-upload-list__item-error[data-v-eabb787d] {\n margin-top: 0.5rem;\n padding: 0.5rem;\n background-color: #f8d7da;\n color: #721c24;\n font-size: 0.75rem;\n border-radius: 0.25rem;\n}\n.flow-upload-list__item-success[data-v-eabb787d] {\n margin-top: 0.5rem;\n padding: 0.5rem;\n background-color: #d4edda;\n color: #155724;\n font-size: 0.75rem;\n border-radius: 0.25rem;\n}\n","\n.flow-upload-zone[data-v-e6d6e2c2] {\n cursor: pointer;\n user-select: none;\n}\n.flow-upload-zone--disabled[data-v-e6d6e2c2] {\n cursor: not-allowed;\n opacity: 0.6;\n}\n.flow-upload-zone--uploading[data-v-e6d6e2c2] {\n pointer-events: none;\n}\n.flow-upload-zone__content[data-v-e6d6e2c2] {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 0.5rem;\n}\n.flow-upload-zone__error[data-v-e6d6e2c2] {\n color: #dc3545;\n}\n.flow-upload-zone__progress[data-v-e6d6e2c2] {\n width: 100%;\n max-width: 300px;\n margin-top: 0.5rem;\n}\n.flow-upload-zone__progress-bar[data-v-e6d6e2c2] {\n width: 100%;\n height: 0.5rem;\n background-color: #e0e0e0;\n border-radius: 0.25rem;\n overflow: hidden;\n}\n.flow-upload-zone__progress-fill[data-v-e6d6e2c2] {\n height: 100%;\n background-color: #007bff;\n transition: width 0.2s ease;\n}\n.flow-upload-zone__errors[data-v-e6d6e2c2] {\n margin-top: 0.5rem;\n color: #dc3545;\n font-size: 0.875rem;\n}\n.flow-upload-zone__errors p[data-v-e6d6e2c2] {\n margin: 0.25rem 0;\n}\n","\n.upload-list[data-v-70c8fe1a] {\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n}\n.upload-list__item[data-v-70c8fe1a] {\n padding: 0.75rem;\n border: 1px solid #e0e0e0;\n border-radius: 0.375rem;\n background-color: #fff;\n}\n.upload-list__item-header[data-v-70c8fe1a] {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n margin-bottom: 0.5rem;\n}\n.upload-list__item-icon[data-v-70c8fe1a] {\n font-size: 1rem;\n}\n.upload-list__item-name[data-v-70c8fe1a] {\n flex: 1;\n font-weight: 500;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.upload-list__item-status[data-v-70c8fe1a] {\n font-size: 0.75rem;\n font-weight: 600;\n text-transform: uppercase;\n}\n.upload-list__item-size[data-v-70c8fe1a] {\n font-size: 0.75rem;\n color: #666;\n margin-bottom: 0.5rem;\n}\n.upload-list__item-progress[data-v-70c8fe1a] {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n}\n.upload-list__progress-bar[data-v-70c8fe1a] {\n flex: 1;\n height: 0.375rem;\n background-color: #e0e0e0;\n border-radius: 0.1875rem;\n overflow: hidden;\n}\n.upload-list__progress-fill[data-v-70c8fe1a] {\n height: 100%;\n background-color: #007bff;\n transition: width 0.2s ease;\n}\n.upload-list__progress-text[data-v-70c8fe1a] {\n font-size: 0.75rem;\n color: #666;\n min-width: 3rem;\n text-align: right;\n}\n.upload-list__item-error[data-v-70c8fe1a] {\n margin-top: 0.5rem;\n padding: 0.5rem;\n background-color: #f8d7da;\n color: #721c24;\n font-size: 0.75rem;\n border-radius: 0.25rem;\n}\n","\n.upload-zone[data-v-8b709bef] {\n cursor: pointer;\n user-select: none;\n}\n.upload-zone--disabled[data-v-8b709bef] {\n cursor: not-allowed;\n opacity: 0.6;\n}\n.upload-zone__content[data-v-8b709bef] {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n}\n.upload-zone__errors[data-v-8b709bef] {\n margin-top: 0.5rem;\n color: #dc3545;\n font-size: 0.875rem;\n}\n.upload-zone__errors p[data-v-8b709bef] {\n margin: 0.25rem 0;\n}\n"],"mappings":";AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;AClFA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;AC7CA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;ACnEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { C as DragDropState, D as UploadistaPluginOptions, O as createUploadistaPlugin, S as DragDropOptions, T as UPLOADISTA_CLIENT_KEY, _ as useUpload, a as useUploadistaClient, b as FlowUploadStatus, c as UploadItem, d as PerformanceInsights, f as UploadInput, g as UploadStatus, h as UploadState, i as UseUploadistaClientReturn, l as useMultiUpload, m as UploadSessionMetrics, n as UseUploadMetricsOptions, o as MultiUploadOptions, p as UploadMetrics, r as useUploadMetrics, s as MultiUploadState, t as FileUploadMetrics, u as ChunkMetrics, v as useMultiFlowUpload, w as useDragDrop, x as useFlowUpload, y as FlowUploadState } from "../index-B848U2ke.mjs";
|
|
2
|
+
export { ChunkMetrics, DragDropOptions, DragDropState, FileUploadMetrics, FlowUploadState, FlowUploadStatus, MultiUploadOptions, MultiUploadState, PerformanceInsights, UPLOADISTA_CLIENT_KEY, UploadInput, UploadItem, UploadMetrics, UploadSessionMetrics, UploadState, UploadStatus, UploadistaPluginOptions, UseUploadMetricsOptions, UseUploadistaClientReturn, createUploadistaPlugin, useDragDrop, useFlowUpload, useMultiFlowUpload, useMultiUpload, useUpload, useUploadMetrics, useUploadistaClient };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{r as e,t}from"../plugin-HH1lTjcq.mjs";import{a as n,i as r,n as i,o as a,r as o,s,t as c}from"../composables-Biblh8X9.mjs";export{t as UPLOADISTA_CLIENT_KEY,e as createUploadistaPlugin,s as useDragDrop,n as useFlowUpload,r as useMultiFlowUpload,o as useMultiUpload,i as useUpload,c as useUploadMetrics,a as useUploadistaClient};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{n as e,t}from"./plugin-HH1lTjcq.mjs";import{computed as n,inject as r,onUnmounted as i,readonly as a,ref as o}from"vue";import{EventType as s}from"@uploadista/core/flow";import{UploadEventType as c}from"@uploadista/core/types";const l={isDragging:!1,isOver:!1,isValid:!0,errors:[]};function u(e={}){let{accept:t,maxFiles:r,maxFileSize:i,multiple:s=!0,validator:c,onFilesReceived:u,onValidationError:d,onDragStateChange:f}=e,p=o({...l}),m=o(0),h=e=>{p.value={...p.value,...e}},g=e=>{let n=[];r&&e.length>r&&n.push(`Maximum ${r} files allowed. You selected ${e.length} files.`);for(let r of e){if(i&&r.size>i){let e=(i/(1024*1024)).toFixed(1),t=(r.size/(1024*1024)).toFixed(1);n.push(`File "${r.name}" (${t}MB) exceeds maximum size of ${e}MB.`)}t&&t.length>0&&(t.some(e=>{if(e.startsWith(`.`))return r.name.toLowerCase().endsWith(e.toLowerCase());if(e.endsWith(`/*`)){let t=e.slice(0,-2);return r.type.startsWith(t)}else return r.type===e})||n.push(`File "${r.name}" type "${r.type}" is not accepted. Accepted types: ${t.join(`, `)}.`))}if(c){let t=c(e);t&&n.push(...t)}return n},_=e=>{let t=Array.from(e),n=g(t);n.length>0?(h({errors:n,isValid:!1}),d?.(n)):(h({errors:[],isValid:!0}),u?.(t))},v=e=>{let t=[];if(e.items)for(let n=0;n<e.items.length;n++){let r=e.items[n];if(r&&r.kind===`file`){let e=r.getAsFile();e&&t.push(e)}}else for(let n=0;n<e.files.length;n++){let r=e.files[n];r&&t.push(r)}return t},y=e=>{e.preventDefault(),e.stopPropagation(),m.value++,m.value===1&&(h({isDragging:!0,isOver:!0}),f?.(!0))},b=e=>{e.preventDefault(),e.stopPropagation(),e.dataTransfer&&(e.dataTransfer.dropEffect=`copy`)},x=e=>{e.preventDefault(),e.stopPropagation(),m.value--,m.value===0&&(h({isDragging:!1,isOver:!1,errors:[]}),f?.(!1))},S=e=>{if(e.preventDefault(),e.stopPropagation(),m.value=0,h({isDragging:!1,isOver:!1}),f?.(!1),e.dataTransfer){let t=v(e.dataTransfer);t.length>0&&_(t)}},C=e=>{let t=e.target;t.files&&t.files.length>0&&_(Array.from(t.files)),t.value=``},w=()=>{p.value={...l},m.value=0},T=n(()=>({type:`file`,multiple:s,accept:t?.join(`, `)}));return{state:a(p),onDragEnter:y,onDragOver:b,onDragLeave:x,onDrop:S,onInputChange:C,inputProps:T,processFiles:_,reset:w}}function d(){let n=r(t);if(!n)throw Error(`useUploadistaClient must be used within a component tree that has the Uploadista plugin or provider installed. Make sure to either use app.use(createUploadistaPlugin({ ... })) in your main app file, or wrap your component tree with <UploadistaProvider>.`);let i=r(e);return{client:n,subscribeToEvents:e=>i?(i.value.add(e),()=>{i.value.delete(e)}):(console.warn(`subscribeToEvents called but no event subscribers provided. Events will not be dispatched. Make sure to use UploadistaProvider or createUploadistaPlugin with proper configuration.`),()=>{})}}function f(e){let t=e;return t.eventType===s.FlowStart||t.eventType===s.FlowEnd||t.eventType===s.FlowError||t.eventType===s.NodeStart||t.eventType===s.NodeEnd||t.eventType===s.NodePause||t.eventType===s.NodeResume||t.eventType===s.NodeError}const p={status:`idle`,progress:0,bytesUploaded:0,totalBytes:null,error:null,result:null,jobId:null,flowStarted:!1,currentNodeName:null,currentNodeType:null,flowOutputs:null};function m(e){let t=d(),r=o(p),l=o(null),u=o(null),m=t=>{if(console.log(`handleFlowEvent`,t),!u.value||t.jobId!==u.value){console.log(`handleFlowEvent - jobId mismatch`,t.jobId,u.value);return}switch(t.eventType){case s.FlowStart:r.value={...r.value,flowStarted:!0,status:`processing`};break;case s.NodeStart:r.value={...r.value,status:`processing`,currentNodeName:t.nodeName,currentNodeType:t.nodeType};break;case s.NodePause:r.value={...r.value,status:`uploading`,currentNodeName:t.nodeName};break;case s.NodeResume:r.value={...r.value,status:`processing`,currentNodeName:t.nodeName,currentNodeType:t.nodeType};break;case s.NodeEnd:r.value={...r.value,status:r.value.status===`uploading`?`processing`:r.value.status,currentNodeName:null,currentNodeType:null};break;case s.FlowEnd:{let n=t.result||null;n&&e.onFlowComplete&&e.onFlowComplete(n);let i=null;n&&(i=e.flowConfig.outputNodeId&&e.flowConfig.outputNodeId in n?n[e.flowConfig.outputNodeId]:Object.values(n)[0]),i&&e.onSuccess&&e.onSuccess(i),r.value={...r.value,status:`success`,currentNodeName:null,currentNodeType:null,result:i,flowOutputs:n};break}case s.FlowError:r.value={...r.value,status:`error`,error:Error(t.error)},e.onError?.(Error(t.error));break;case s.NodeError:r.value={...r.value,status:`error`,error:Error(t.error)},e.onError?.(Error(t.error));break}},h=t.subscribeToEvents(e=>{if(console.log(`subscribeToEvents`,e),f(e)){m(e);return}let t=e;if(t.type===c.UPLOAD_PROGRESS&&t.flow?.jobId===u.value&&t.data){let{progress:e,total:n}=t.data,i=n?Math.round(e/n*100):0;r.value={...r.value,progress:i,bytesUploaded:e,totalBytes:n}}});return i(()=>{h()}),{state:a(r),upload:async n=>{u.value=null,r.value={...p,status:`uploading`,totalBytes:n.size};try{let{abort:i}=await t.client.uploadWithFlow(n,e.flowConfig,{onJobStart:e=>{u.value=e,r.value={...r.value,jobId:e}},onProgress:(t,n,i)=>{let a=i?Math.round(n/i*100):0;r.value={...r.value,progress:a,bytesUploaded:n,totalBytes:i},e.onProgress?.(a,n,i)},onChunkComplete:e.onChunkComplete,onSuccess:e=>{r.value={...r.value,progress:100}},onError:t=>{r.value={...r.value,status:`error`,error:t},e.onError?.(t)},onShouldRetry:e.onShouldRetry});l.value=i}catch(t){r.value={...r.value,status:`error`,error:t},e.onError?.(t)}},abort:()=>{l.value&&(l.value(),l.value=null,r.value={...r.value,status:`aborted`},e.onAbort?.())},reset:()=>{r.value=p,l.value=null,u.value=null},isUploading:n(()=>r.value.status===`uploading`||r.value.status===`processing`),isUploadingFile:n(()=>r.value.status===`uploading`),isProcessing:n(()=>r.value.status===`processing`)}}function h(e){let t=d(),r=o([]),i=o(new Map),s=o([]),c=o(0),l=e.maxConcurrent??3,u=e=>{if(e.length===0)return 0;let t=e.reduce((e,t)=>e+t.progress,0);return Math.round(t/e.length)},f=async()=>{if(c.value>=l||s.value.length===0)return;let n=s.value.shift();if(!n)return;let a=r.value.find(e=>e.id===n);if(!a||a.status!==`pending`){f();return}c.value++,r.value=r.value.map(e=>e.id===n?{...e,status:`uploading`}:e);try{let{abort:o,jobId:s}=await t.client.uploadWithFlow(a.file,e.flowConfig,{onJobStart:e=>{r.value=r.value.map(t=>t.id===n?{...t,jobId:e}:t)},onProgress:(t,i,a)=>{let o=a?Math.round(i/a*100):0;r.value=r.value.map(t=>{if(t.id===n){let n={...t,progress:o,bytesUploaded:i,totalBytes:a||0};return e.onItemProgress?.(n),n}return t})},onSuccess:t=>{r.value=r.value.map(r=>{if(r.id===n){let n={...r,status:`success`,result:t,progress:100};return e.onItemSuccess?.(n),n}return r}),r.value.every(e=>e.status===`success`||e.status===`error`||e.status===`aborted`)&&e.onComplete?.(r.value),i.value.delete(n),c.value--,f()},onError:t=>{r.value=r.value.map(r=>{if(r.id===n){let n={...r,status:`error`,error:t};return e.onItemError?.(n,t),n}return r}),r.value.every(e=>e.status===`success`||e.status===`error`||e.status===`aborted`)&&e.onComplete?.(r.value),i.value.delete(n),c.value--,f()},onShouldRetry:e.onShouldRetry});i.value.set(n,o),r.value=r.value.map(e=>e.id===n?{...e,jobId:s}:e)}catch(e){r.value=r.value.map(t=>t.id===n?{...t,status:`error`,error:e}:t),c.value--,f()}},p=e=>{let t=Array.from(e).map(e=>({id:`${Date.now()}-${Math.random().toString(36).substr(2,9)}`,file:e,status:`pending`,progress:0,bytesUploaded:0,totalBytes:e.size,error:null,result:null,jobId:null}));r.value=[...r.value,...t]},m=e=>{let t=i.value.get(e);t&&(t(),i.value.delete(e)),r.value=r.value.filter(t=>t.id!==e),s.value=s.value.filter(t=>t!==e)},h=()=>{let e=r.value.filter(e=>e.status===`pending`);s.value.push(...e.map(e=>e.id));for(let e=0;e<l;e++)f()},g=e=>{let t=i.value.get(e);t&&(t(),i.value.delete(e),r.value=r.value.map(t=>t.id===e?{...t,status:`aborted`}:t),c.value--,f())},_=()=>{for(let e of i.value.values())e();i.value.clear(),s.value=[],c.value=0,r.value=r.value.map(e=>e.status===`uploading`?{...e,status:`aborted`}:e)},v=()=>{_(),r.value=[]},y=e=>{r.value=r.value.map(t=>t.id===e?{...t,status:`pending`,progress:0,bytesUploaded:0,error:null}:t),s.value.push(e),f()},b=n(()=>({items:r.value,totalProgress:u(r.value),activeUploads:r.value.filter(e=>e.status===`uploading`).length,completedUploads:r.value.filter(e=>e.status===`success`).length,failedUploads:r.value.filter(e=>e.status===`error`).length}));return{state:a(b),addFiles:p,removeFile:m,startUpload:h,abortUpload:g,abortAll:_,clear:v,retryUpload:y,isUploading:n(()=>b.value.activeUploads>0)}}function g(e={}){let t=d(),{maxConcurrent:r=3}=e,i=o([]),s=o(0),c=o(new Set),l=o(new Map),u=()=>`upload-${Date.now()}-${s.value++}`,f=(e,t)=>{i.value=i.value.map(n=>n.id===e?{...n,state:{...n.state,...t}}:n)},p=()=>{if(i.value.every(e=>[`success`,`error`,`aborted`].includes(e.state.status))&&i.value.length>0){let t=i.value.filter(e=>e.state.status===`success`),n=i.value.filter(e=>[`error`,`aborted`].includes(e.state.status));e.onComplete?.({successful:t,failed:n,total:i.value.length})}},m=async()=>{if(c.value.size>=r)return;let n=i.value.find(e=>e.state.status===`idle`&&!c.value.has(e.id));if(n){c.value.add(n.id),e.onUploadStart?.(n),f(n.id,{status:`uploading`});try{let r=await t.client.upload(n.file,{metadata:e.metadata,uploadLengthDeferred:e.uploadLengthDeferred,uploadSize:e.uploadSize,onProgress:(t,r,i)=>{let a=i?Math.round(r/i*100):0;f(n.id,{progress:a,bytesUploaded:r,totalBytes:i}),e.onUploadProgress?.(n,a,r,i)},onChunkComplete:()=>{},onSuccess:t=>{f(n.id,{status:`success`,result:t,progress:100});let r={...n,state:{...n.state,status:`success`,result:t}};e.onUploadSuccess?.(r,t),c.value.delete(n.id),l.value.delete(n.id),m(),p()},onError:t=>{f(n.id,{status:`error`,error:t});let r={...n,state:{...n.state,status:`error`,error:t}};e.onUploadError?.(r,t),c.value.delete(n.id),l.value.delete(n.id),m(),p()},onShouldRetry:e.onShouldRetry});l.value.set(n.id,r)}catch(t){f(n.id,{status:`error`,error:t});let r={...n,state:{...n.state,status:`error`,error:t}};e.onUploadError?.(r,t),c.value.delete(n.id),l.value.delete(n.id),m(),p()}}},h=n(()=>{let e=i.value;return{total:e.length,completed:e.filter(e=>[`success`,`error`,`aborted`].includes(e.state.status)).length,successful:e.filter(e=>e.state.status===`success`).length,failed:e.filter(e=>[`error`,`aborted`].includes(e.state.status)).length,uploading:e.filter(e=>e.state.status===`uploading`).length,progress:e.length>0?Math.round(e.reduce((e,t)=>e+t.state.progress,0)/e.length):0,totalBytesUploaded:e.reduce((e,t)=>e+t.state.bytesUploaded,0),totalBytes:e.reduce((e,t)=>e+(t.state.totalBytes||0),0),isUploading:e.some(e=>e.state.status===`uploading`),isComplete:e.length>0&&e.every(e=>[`success`,`error`,`aborted`].includes(e.state.status))}}),g=e=>{let t=e.map(e=>({id:u(),file:e,state:{status:`idle`,progress:0,bytesUploaded:0,totalBytes:e instanceof File?e.size:null,error:null,result:null}}));i.value=[...i.value,...t]},_=e=>{let t=i.value.find(t=>t.id===e);if(t&&t.state.status===`uploading`){let t=l.value.get(e);t&&(t.abort(),l.value.delete(e))}i.value=i.value.filter(t=>t.id!==e),c.value.delete(e)},v=e=>{let t=i.value.find(t=>t.id===e);if(t&&t.state.status===`uploading`){let t=l.value.get(e);t&&(t.abort(),l.value.delete(e)),c.value.delete(e),i.value=i.value.map(t=>t.id===e?{...t,state:{...t.state,status:`aborted`}}:t),m()}},y=e=>{let t=i.value.find(t=>t.id===e);t&&[`error`,`aborted`].includes(t.state.status)&&(i.value=i.value.map(t=>t.id===e?{...t,state:{...t.state,status:`idle`,error:null}}:t),setTimeout(()=>m(),0))},b=()=>{let e=i.value.filter(e=>e.state.status===`idle`),t=r-c.value.size,n=e.slice(0,t);for(let e of n)m()},x=()=>{i.value.filter(e=>e.state.status===`uploading`).forEach(e=>{let t=l.value.get(e.id);t&&(t.abort(),l.value.delete(e.id))}),c.value.clear(),i.value=i.value.map(e=>e.state.status===`uploading`?{...e,state:{...e.state,status:`aborted`}}:e)};return{state:a(h),items:a(i),addFiles:g,removeItem:_,removeFile:_,startAll:b,abortUpload:v,abortAll:x,retryUpload:y,retryFailed:()=>{let e=i.value.filter(e=>[`error`,`aborted`].includes(e.state.status));e.length>0&&(i.value=i.value.map(t=>e.some(e=>e.id===t.id)?{...t,state:{...t.state,status:`idle`,error:null}}:t),setTimeout(b,0))},clearCompleted:()=>{i.value=i.value.filter(e=>![`success`,`error`,`aborted`].includes(e.state.status))},clearAll:()=>{x(),i.value=[],c.value.clear()},getItemsByStatus:e=>i.value.filter(t=>t.state.status===e),metrics:{getInsights:()=>t.client.getChunkingInsights(),exportMetrics:()=>t.client.exportMetrics(),getNetworkMetrics:()=>t.client.getNetworkMetrics(),getNetworkCondition:()=>t.client.getNetworkCondition(),resetMetrics:()=>t.client.resetMetrics()}}}const _={status:`idle`,progress:0,bytesUploaded:0,totalBytes:null,error:null,result:null};function v(e={}){let t=d(),r=o({..._}),s=o(null),l=o(null),u=e=>{r.value={...r.value,...e}},f=()=>{s.value&&=(s.value.abort(),null),r.value={..._},l.value=null},p=()=>{s.value&&=(s.value.abort(),null),u({status:`aborted`}),e.onAbort?.()},m=n=>{r.value={..._,status:`uploading`,totalBytes:n instanceof File?n.size:null},l.value=n,t.client.upload(n,{metadata:e.metadata,uploadLengthDeferred:e.uploadLengthDeferred,uploadSize:e.uploadSize,onProgress:(t,n,r)=>{let i=r?Math.round(n/r*100):0;u({progress:i,bytesUploaded:n,totalBytes:r}),e.onProgress?.(i,n,r)},onChunkComplete:(t,n,r)=>{e.onChunkComplete?.(t,n,r)},onSuccess:t=>{u({status:`success`,result:t,progress:100,bytesUploaded:t.size||0,totalBytes:t.size||null}),e.onSuccess?.(t),s.value=null},onError:t=>{u({status:`error`,error:t}),e.onError?.(t),s.value=null},onShouldRetry:e.onShouldRetry}).then(e=>{s.value=e}).catch(t=>{u({status:`error`,error:t}),e.onError?.(t),s.value=null})},h=()=>{l.value&&(r.value.status===`error`||r.value.status===`aborted`)&&m(l.value)},g=t.subscribeToEvents(t=>{console.log(`useUpload - subscribeToEvents`,t);let n=t;if(n.type===c.UPLOAD_PROGRESS&&n.data){let{progress:t,total:i}=n.data,a=i?Math.round(t/i*100):0;r.value.status===`uploading`&&(u({progress:a,bytesUploaded:t,totalBytes:i}),e.onProgress?.(a,t,i))}});i(()=>{g()});let v=n(()=>r.value.status===`uploading`),y=n(()=>(r.value.status===`error`||r.value.status===`aborted`)&&l.value!==null);return{state:a(r),upload:m,abort:p,reset:f,retry:h,isUploading:v,canRetry:y,metrics:{getInsights:()=>t.client.getChunkingInsights(),exportMetrics:()=>t.client.exportMetrics(),getNetworkMetrics:()=>t.client.getNetworkMetrics(),getNetworkCondition:()=>t.client.getNetworkCondition(),resetMetrics:()=>t.client.resetMetrics()}}}const y={totalBytesUploaded:0,totalBytes:0,averageSpeed:0,currentSpeed:0,estimatedTimeRemaining:null,totalFiles:0,completedFiles:0,activeUploads:0,progress:0,peakSpeed:0,startTime:null,endTime:null,totalDuration:null,insights:{overallEfficiency:0,chunkingEffectiveness:0,networkStability:0,recommendations:[],optimalChunkSizeRange:{min:256*1024,max:2*1024*1024}},sessionMetrics:[],chunkMetrics:[]};function b(e={}){let{speedCalculationInterval:t=1e3,speedSampleSize:n=10,onMetricsUpdate:r,onFileStart:s,onFileProgress:c,onFileComplete:l}=e,u=d(),f=o({...y}),p=o([]),m=o([]),h=o(0),g=o(null),_=(e,t)=>{let r={time:e,bytes:t};m.value.push(r),m.value.length>n&&(m.value=m.value.slice(-n));let i=0;if(m.value.length>=2){let e=m.value[m.value.length-1],t=m.value[m.value.length-2];if(e&&t){let n=(e.time-t.time)/1e3,r=e.bytes-t.bytes;i=n>0?r/n:0}}let a=0;if(m.value.length>=2){let e=m.value[0],t=m.value[m.value.length-1];if(e&&t){let n=(t.time-e.time)/1e3,r=t.bytes-e.bytes;a=n>0?r/n:0}}return{currentSpeed:i,averageSpeed:a}},v=()=>{let e=Date.now(),t=p.value.reduce((e,t)=>e+t.size,0),n=p.value.reduce((e,t)=>e+t.bytesUploaded,0),i=p.value.filter(e=>e.isComplete).length,a=p.value.filter(e=>!e.isComplete&&e.bytesUploaded>0).length,{currentSpeed:o,averageSpeed:s}=_(e,n),c=t>0?Math.round(n/t*100):0,l=null;o>0&&(l=(t-n)/o*1e3);let d=p.value.filter(e=>e.startTime>0),m=d.length>0?Math.min(...d.map(e=>e.startTime)):null,h=p.value.filter(e=>e.endTime!==null),g=h.length>0&&i===p.value.length?Math.max(...h.map(e=>e.endTime).filter(e=>e!==null)):null,v=m&&g?g-m:null,y={totalBytesUploaded:n,totalBytes:t,averageSpeed:s,currentSpeed:o,estimatedTimeRemaining:l,totalFiles:p.value.length,completedFiles:i,activeUploads:a,progress:c,peakSpeed:Math.max(f.value.peakSpeed,o),startTime:m,endTime:g,totalDuration:v,insights:u.client.getChunkingInsights(),sessionMetrics:[u.client.exportMetrics().session],chunkMetrics:u.client.exportMetrics().chunks};f.value=y,r?.(y)},b=()=>{g.value&&clearInterval(g.value),g.value=setInterval(()=>{p.value.some(e=>!e.isComplete&&e.bytesUploaded>0)&&v()},t)};return i(()=>{g.value&&clearInterval(g.value)}),{metrics:a(f),fileMetrics:a(p),startFileUpload:(e,t,n)=>{let r={id:e,filename:t,size:n,bytesUploaded:0,progress:0,speed:0,startTime:Date.now(),endTime:null,duration:null,isComplete:!1};p.value.find(t=>t.id===e)?p.value=p.value.map(t=>t.id===e?r:t):p.value=[...p.value,r],s?.(r),p.value.filter(e=>!e.isComplete).length===1&&b()},updateFileProgress:(e,t)=>{let n=Date.now();p.value=p.value.map(r=>{if(r.id!==e)return r;let i=(n-r.startTime)/1e3,a=i>0?t/i:0,o=r.size>0?Math.round(t/r.size*100):0,s={...r,bytesUploaded:t,progress:o,speed:a};return c?.(s),s}),setTimeout(v,0)},completeFileUpload:e=>{let t=Date.now();p.value=p.value.map(n=>{if(n.id!==e)return n;let r=t-n.startTime,i=r>0?n.size/r*1e3:0,a={...n,bytesUploaded:n.size,progress:100,speed:i,endTime:t,duration:r,isComplete:!0};return l?.(a),a}),setTimeout(v,0)},removeFile:e=>{p.value=p.value.filter(t=>t.id!==e),setTimeout(v,0)},reset:()=>{g.value&&=(clearInterval(g.value),null),f.value={...y},p.value=[],m.value=[],h.value=0},getFileMetrics:e=>p.value.find(t=>t.id===e),exportMetrics:()=>({overall:f.value,files:p.value,exportTime:Date.now()})}}export{m as a,h as i,v as n,d as o,g as r,u as s,b as t};
|
|
2
|
+
//# sourceMappingURL=composables-Biblh8X9.mjs.map
|