@uploadista/vue 0.0.13 → 0.0.14
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 +2 -2
- package/dist/components/index.mjs +1 -1
- package/dist/{components-DLtfLeUG.mjs → components-DiNTrRSj.mjs} +2 -2
- package/dist/{components-DLtfLeUG.mjs.map → components-DiNTrRSj.mjs.map} +1 -1
- package/dist/composables/index.d.mts +2 -2
- package/dist/composables/index.mjs +1 -1
- package/dist/composables-8ehXYRvI.mjs +2 -0
- package/dist/composables-8ehXYRvI.mjs.map +1 -0
- package/dist/{index-D2Ih8WXM.d.mts → index-9iS--7zH.d.mts} +5 -5
- package/dist/{index-D2Ih8WXM.d.mts.map → index-9iS--7zH.d.mts.map} +1 -1
- package/dist/{index-BifKuQLf.d.mts → index-C3L9eLVn.d.mts} +11 -11
- package/dist/{index-BifKuQLf.d.mts.map → index-C3L9eLVn.d.mts.map} +1 -1
- package/dist/{index-LKMPbCk5.d.mts → index-uqhGbNTo.d.mts} +341 -161
- package/dist/index-uqhGbNTo.d.mts.map +1 -0
- package/dist/index.d.mts +4 -4
- package/dist/index.mjs +1 -1
- package/dist/providers/index.d.mts +1 -1
- package/package.json +6 -5
- package/src/composables/index.ts +0 -1
- package/src/composables/useFlowUpload.ts +112 -263
- package/src/composables/useMultiUpload.ts +2 -6
- package/src/composables/useUpload.ts +57 -212
- package/src/composables/useUploadMetrics.ts +5 -8
- package/dist/composables-Ba9_APZi.mjs +0 -2
- package/dist/composables-Ba9_APZi.mjs.map +0 -1
- package/dist/index-LKMPbCk5.d.mts.map +0 -1
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { i as _default, n as _default$2, r as _default$1, t as _default$3 } from "../index-
|
|
2
|
-
import "../index-
|
|
1
|
+
import { i as _default, n as _default$2, r as _default$1, t as _default$3 } from "../index-C3L9eLVn.mjs";
|
|
2
|
+
import "../index-uqhGbNTo.mjs";
|
|
3
3
|
export { _default as FlowUploadList, _default$1 as FlowUploadZone, _default$2 as UploadList, _default$3 as UploadZone };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import"../utils-CG6LdEtY.mjs";import{i as e,n as t,r as n,t as r}from"../components-
|
|
1
|
+
import"../utils-CG6LdEtY.mjs";import{i as e,n as t,r as n,t as r}from"../components-DiNTrRSj.mjs";import"../plugin-LRx3Bv83.mjs";import"../composables-8ehXYRvI.mjs";export{e as FlowUploadList,n as FlowUploadZone,t as UploadList,r as UploadZone};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{m as e}from"./utils-CG6LdEtY.mjs";import{a as t,n,r,s as i}from"./composables-Ba9_APZi.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";var S=(e,t)=>{let n=e.__vccOpts||e;for(let[e,r]of t)n[e]=r;return n};const C={class:`flow-upload-list`},w={class:`flow-upload-list__item-header`},T={class:`flow-upload-list__item-icon`},E={class:`flow-upload-list__item-name`},D={class:`flow-upload-list__item-details`},ee={class:`flow-upload-list__item-size`},O={key:0,class:`flow-upload-list__item-job`},k={key:0,class:`flow-upload-list__item-progress`},A={class:`flow-upload-list__progress-bar`},j={class:`flow-upload-list__progress-text`},M={key:1,class:`flow-upload-list__item-error`},N={key:2,class:`flow-upload-list__item-success`};var P=S(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`,C,[_(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`,w,[l(`span`,T,v(d(n.status)),1),l(`span`,E,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`,D,[l(`span`,ee,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`,k,[l(`div`,A,[l(`div`,{class:`flow-upload-list__progress-fill`,style:p({width:`${n.progress}%`})},null,4)]),l(`span`,j,v(n.progress)+`% • `+v(u(n.bytesUploaded))+` / `+v(u(n.totalBytes)),1)])):s(`v-if`,!0),n.status===`error`&&n.error?(m(),c(`div`,M,v(n.error.message),1)):s(`v-if`,!0),n.status===`success`?(m(),c(`div`,N,` Upload complete `)):s(`v-if`,!0)],!0)],2))),128))],!0)]))}}),[[`__scopeId`,`data-v-eabb787d`]]);const F=[`tabindex`,`aria-disabled`,`onKeydown`],I={class:`flow-upload-zone__content`},L={key:0},R={key:1},z={key:2},B={key:0},V={key:3},H={key:4,class:`flow-upload-zone__error`},U={key:5},te={key:6,class:`flow-upload-zone__progress`},W={class:`flow-upload-zone__progress-bar`},G={key:7,class:`flow-upload-zone__errors`},K=[`multiple`,`accept`,`disabled`];var q=S(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`,I,[y(C).state.value.isDragging?(m(),c(`p`,L,`Drop file here...`)):y(S).isUploading.value?(m(),c(`p`,R,` Uploading... `+v(y(S).state.value.progress)+`% `,1)):y(S).isProcessing.value?(m(),c(`p`,z,[n[5]||=u(` Processing... `,-1),y(S).state.value.currentNodeName?(m(),c(`span`,B,` (`+v(y(S).state.value.currentNodeName)+`) `,1)):s(`v-if`,!0)])):y(S).state.value.status===`success`?(m(),c(`p`,V,`Upload complete!`)):y(S).state.value.status===`error`?(m(),c(`p`,H,` Error: `+v(y(S).state.value.error?.message),1)):(m(),c(`p`,U,`Drag a file here or click to select`)),y(S).isUploading.value?(m(),c(`div`,te,[l(`div`,W,[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`,G,[(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,K)],42,F))}}),[[`__scopeId`,`data-v-e6d6e2c2`]]);const J={class:`upload-list`},Y={class:`upload-list__item-header`},X={class:`upload-list__item-icon`},Z={class:`upload-list__item-name`},Q={key:0,class:`upload-list__item-size`},ne={key:1,class:`upload-list__item-progress`},re={class:`upload-list__progress-bar`},ie={class:`upload-list__progress-text`},ae={key:2,class:`upload-list__item-error`};var oe=S(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`,J,[_(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`,Y,[l(`span`,X,v(d(n.state.status)),1),l(`span`,Z,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`,Q,v(u(n.state.totalBytes)),1)):s(`v-if`,!0),n.state.status===`uploading`?(m(),c(`div`,ne,[l(`div`,re,[l(`div`,{class:`upload-list__progress-fill`,style:p({width:`${n.state.progress}%`})},null,4)]),l(`span`,ie,v(n.state.progress)+`%`,1)])):s(`v-if`,!0),n.state.status===`error`&&n.state.error?(m(),c(`div`,ae,v(n.state.error.message),1)):s(`v-if`,!0)],!0)],2))),128))],!0)]))}}),[[`__scopeId`,`data-v-70c8fe1a`]]);const $=[`tabindex`,`aria-disabled`,`aria-label`,`onKeydown`],se={class:`upload-zone__content`},ce={key:0},le={key:1},ue={key:2,class:`upload-zone__errors`},de=[`multiple`,`accept`,`disabled`];var fe=S(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`,se,[y(C).state.value.isDragging?(m(),c(`p`,ce,v(e.multiple?`Drop files here...`:`Drop file here...`),1)):(m(),c(`p`,le,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`,ue,[(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,de)],42,$))}}),[[`__scopeId`,`data-v-8b709bef`]]);export{P as i,oe as n,q as r,fe as t};
|
|
2
|
-
//# sourceMappingURL=components-
|
|
1
|
+
import{m as e}from"./utils-CG6LdEtY.mjs";import{a as t,n,r,s as i}from"./composables-8ehXYRvI.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";var S=(e,t)=>{let n=e.__vccOpts||e;for(let[e,r]of t)n[e]=r;return n};const C={class:`flow-upload-list`},w={class:`flow-upload-list__item-header`},T={class:`flow-upload-list__item-icon`},E={class:`flow-upload-list__item-name`},D={class:`flow-upload-list__item-details`},ee={class:`flow-upload-list__item-size`},O={key:0,class:`flow-upload-list__item-job`},k={key:0,class:`flow-upload-list__item-progress`},A={class:`flow-upload-list__progress-bar`},j={class:`flow-upload-list__progress-text`},M={key:1,class:`flow-upload-list__item-error`},N={key:2,class:`flow-upload-list__item-success`};var P=S(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`,C,[_(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`,w,[l(`span`,T,v(d(n.status)),1),l(`span`,E,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`,D,[l(`span`,ee,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`,k,[l(`div`,A,[l(`div`,{class:`flow-upload-list__progress-fill`,style:p({width:`${n.progress}%`})},null,4)]),l(`span`,j,v(n.progress)+`% • `+v(u(n.bytesUploaded))+` / `+v(u(n.totalBytes)),1)])):s(`v-if`,!0),n.status===`error`&&n.error?(m(),c(`div`,M,v(n.error.message),1)):s(`v-if`,!0),n.status===`success`?(m(),c(`div`,N,` Upload complete `)):s(`v-if`,!0)],!0)],2))),128))],!0)]))}}),[[`__scopeId`,`data-v-eabb787d`]]);const F=[`tabindex`,`aria-disabled`,`onKeydown`],I={class:`flow-upload-zone__content`},L={key:0},R={key:1},z={key:2},B={key:0},V={key:3},H={key:4,class:`flow-upload-zone__error`},U={key:5},te={key:6,class:`flow-upload-zone__progress`},W={class:`flow-upload-zone__progress-bar`},G={key:7,class:`flow-upload-zone__errors`},K=[`multiple`,`accept`,`disabled`];var q=S(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`,I,[y(C).state.value.isDragging?(m(),c(`p`,L,`Drop file here...`)):y(S).isUploading.value?(m(),c(`p`,R,` Uploading... `+v(y(S).state.value.progress)+`% `,1)):y(S).isProcessing.value?(m(),c(`p`,z,[n[5]||=u(` Processing... `,-1),y(S).state.value.currentNodeName?(m(),c(`span`,B,` (`+v(y(S).state.value.currentNodeName)+`) `,1)):s(`v-if`,!0)])):y(S).state.value.status===`success`?(m(),c(`p`,V,`Upload complete!`)):y(S).state.value.status===`error`?(m(),c(`p`,H,` Error: `+v(y(S).state.value.error?.message),1)):(m(),c(`p`,U,`Drag a file here or click to select`)),y(S).isUploading.value?(m(),c(`div`,te,[l(`div`,W,[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`,G,[(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,K)],42,F))}}),[[`__scopeId`,`data-v-e6d6e2c2`]]);const J={class:`upload-list`},Y={class:`upload-list__item-header`},X={class:`upload-list__item-icon`},Z={class:`upload-list__item-name`},Q={key:0,class:`upload-list__item-size`},ne={key:1,class:`upload-list__item-progress`},re={class:`upload-list__progress-bar`},ie={class:`upload-list__progress-text`},ae={key:2,class:`upload-list__item-error`};var oe=S(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`,J,[_(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`,Y,[l(`span`,X,v(d(n.state.status)),1),l(`span`,Z,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`,Q,v(u(n.state.totalBytes)),1)):s(`v-if`,!0),n.state.status===`uploading`?(m(),c(`div`,ne,[l(`div`,re,[l(`div`,{class:`upload-list__progress-fill`,style:p({width:`${n.state.progress}%`})},null,4)]),l(`span`,ie,v(n.state.progress)+`%`,1)])):s(`v-if`,!0),n.state.status===`error`&&n.state.error?(m(),c(`div`,ae,v(n.state.error.message),1)):s(`v-if`,!0)],!0)],2))),128))],!0)]))}}),[[`__scopeId`,`data-v-70c8fe1a`]]);const $=[`tabindex`,`aria-disabled`,`aria-label`,`onKeydown`],se={class:`upload-zone__content`},ce={key:0},le={key:1},ue={key:2,class:`upload-zone__errors`},de=[`multiple`,`accept`,`disabled`];var fe=S(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`,se,[y(C).state.value.isDragging?(m(),c(`p`,ce,v(e.multiple?`Drop files here...`:`Drop file here...`),1)):(m(),c(`p`,le,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`,ue,[(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,de)],42,$))}}),[[`__scopeId`,`data-v-8b709bef`]]);export{P as i,oe as n,q as r,fe as t};
|
|
2
|
+
//# sourceMappingURL=components-DiNTrRSj.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"components-DLtfLeUG.mjs","names":[],"sources":["../src/components/FlowUploadList.vue","../src/components/FlowUploadZone.vue","../src/components/UploadList.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 * 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 * 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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"}
|
|
1
|
+
{"version":3,"file":"components-DiNTrRSj.mjs","names":[],"sources":["../src/components/FlowUploadList.vue","../src/components/FlowUploadZone.vue","../src/components/UploadList.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 * 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 * 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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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { C as
|
|
2
|
-
export { ChunkMetrics, DragDropOptions, DragDropState, FileUploadMetrics, FlowUploadState, FlowUploadStatus, MultiUploadOptions, MultiUploadState, PerformanceInsights, UPLOADISTA_CLIENT_KEY, UploadInput, UploadItem,
|
|
1
|
+
import { C as useDragDrop, D as createUploadistaPlugin, E as UploadistaPluginOptions, S as DragDropState, _ as useMultiFlowUpload, a as useUploadistaClient, b as useFlowUpload, c as UploadItem, d as PerformanceInsights, f as UploadInput, g as useUpload, h as UploadStatus, i as UseUploadistaClientReturn, l as useMultiUpload, m as UploadState, n as UseUploadMetricsOptions, o as MultiUploadOptions, p as UploadSessionMetrics, r as useUploadMetrics, s as MultiUploadState, t as FileUploadMetrics, u as ChunkMetrics, v as FlowUploadState, w as UPLOADISTA_CLIENT_KEY, x as DragDropOptions, y as FlowUploadStatus } from "../index-uqhGbNTo.mjs";
|
|
2
|
+
export { ChunkMetrics, DragDropOptions, DragDropState, FileUploadMetrics, FlowUploadState, FlowUploadStatus, MultiUploadOptions, MultiUploadState, PerformanceInsights, UPLOADISTA_CLIENT_KEY, UploadInput, UploadItem, UploadSessionMetrics, UploadState, UploadStatus, UploadistaPluginOptions, UseUploadMetricsOptions, UseUploadistaClientReturn, createUploadistaPlugin, useDragDrop, useFlowUpload, useMultiFlowUpload, useMultiUpload, useUpload, useUploadMetrics, useUploadistaClient };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{r as e,t}from"../plugin-LRx3Bv83.mjs";import{a as n,i as r,n as i,o as a,r as o,s,t as c}from"../composables-
|
|
1
|
+
import{r as e,t}from"../plugin-LRx3Bv83.mjs";import{a as n,i as r,n as i,o as a,r as o,s,t as c}from"../composables-8ehXYRvI.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-LRx3Bv83.mjs";import{computed as n,inject as r,onMounted as i,onUnmounted as a,readonly as o,ref as s}from"vue";import{FlowManager as c,UploadManager as l}from"@uploadista/client-core";import{EventType as u}from"@uploadista/core/flow";import{UploadEventType as d}from"@uploadista/core/types";const f={isDragging:!1,isOver:!1,isValid:!0,errors:[]};function p(e={}){let{accept:t,maxFiles:r,maxFileSize:i,multiple:a=!0,validator:c,onFilesReceived:l,onValidationError:u,onDragStateChange:d}=e,p=s({...f}),m=s(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}),u?.(n)):(h({errors:[],isValid:!0}),l?.(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}),d?.(!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:[]}),d?.(!1))},S=e=>{if(e.preventDefault(),e.stopPropagation(),m.value=0,h({isDragging:!1,isOver:!1}),d?.(!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={...f},m.value=0},T=n(()=>({type:`file`,multiple:a,accept:t?.join(`, `)}));return{state:o(p),onDragEnter:y,onDragOver:b,onDragLeave:x,onDrop:S,onInputChange:C,inputProps:T,processFiles:_,reset:w}}function m(){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 h(e){let t=e;return t.eventType===u.FlowStart||t.eventType===u.FlowEnd||t.eventType===u.FlowError||t.eventType===u.NodeStart||t.eventType===u.NodeEnd||t.eventType===u.NodePause||t.eventType===u.NodeResume||t.eventType===u.NodeError}const g={status:`idle`,progress:0,bytesUploaded:0,totalBytes:null,error:null,result:null,jobId:null,flowStarted:!1,currentNodeName:null,currentNodeType:null,flowOutputs:null};function _(e){let t=m(),r=s(g),l=null,u=null;return i(()=>{l=new c(async(e,n,r)=>{let i=await t.client.uploadWithFlow(e,n,{onJobStart:r.onJobStart,onProgress:r.onProgress,onChunkComplete:r.onChunkComplete,onSuccess:r.onSuccess,onError:r.onError,onShouldRetry:r.onShouldRetry});return{abort:async()=>{await i.abort()},pause:async()=>{await i.pause()}}},{onStateChange:e=>{r.value=e},onProgress:e.onProgress?(t,n,r)=>{let i=r?Math.round(n/r*100):0;e.onProgress?.(i,n,r)}:void 0,onChunkComplete:e.onChunkComplete,onFlowComplete:e.onFlowComplete,onSuccess:e.onSuccess,onError:e.onError,onAbort:e.onAbort},{flowConfig:e.flowConfig,onChunkComplete:e.onChunkComplete,onFlowComplete:e.onFlowComplete,onSuccess:e.onSuccess,onError:e.onError,onAbort:e.onAbort,onShouldRetry:e.onShouldRetry}),u=t.subscribeToEvents(e=>{if(h(e)){l?.handleFlowEvent(e);return}let t=e;if(t.type===d.UPLOAD_PROGRESS&&t.flow?.jobId===l?.getJobId()&&t.data){let{progress:e,total:n}=t.data;l?.handleUploadProgress(t.data.id,e,n)}})}),a(()=>{u?.(),l?.cleanup()}),{state:o(r),upload:async e=>{await l?.upload(e)},abort:()=>{l?.abort()},pause:()=>{l?.pause()},reset:()=>{l?.reset()},isUploading:n(()=>r.value.status===`uploading`||r.value.status===`processing`),isUploadingFile:n(()=>r.value.status===`uploading`),isProcessing:n(()=>r.value.status===`processing`)}}function v(e){let t=m(),r=s([]),i=s(new Map),a=s([]),c=s(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)},d=async()=>{if(c.value>=l||a.value.length===0)return;let n=a.value.shift();if(!n)return;let o=r.value.find(e=>e.id===n);if(!o||o.status!==`pending`){d();return}c.value++,r.value=r.value.map(e=>e.id===n?{...e,status:`uploading`}:e);try{let{abort:a,jobId:s}=await t.client.uploadWithFlow(o.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--,d()},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--,d()},onShouldRetry:e.onShouldRetry});i.value.set(n,a),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--,d()}},f=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]},p=e=>{let t=i.value.get(e);t&&(t(),i.value.delete(e)),r.value=r.value.filter(t=>t.id!==e),a.value=a.value.filter(t=>t!==e)},h=()=>{let e=r.value.filter(e=>e.status===`pending`);a.value.push(...e.map(e=>e.id));for(let e=0;e<l;e++)d()},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--,d())},_=()=>{for(let e of i.value.values())e();i.value.clear(),a.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),a.value.push(e),d()},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:o(b),addFiles:f,removeFile:p,startUpload:h,abortUpload:g,abortAll:_,clear:v,retryUpload:y,isUploading:n(()=>b.value.activeUploads>0)}}function y(e={}){let t=m(),{maxConcurrent:r=3}=e,i=s([]),a=s(0),c=s(new Set),l=s(new Map),u=()=>`upload-${Date.now()}-${a.value++}`,d=(e,t)=>{i.value=i.value.map(n=>n.id===e?{...n,state:{...n.state,...t}}:n)},f=()=>{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})}},p=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),d(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;d(n.id,{progress:a,bytesUploaded:r,totalBytes:i}),e.onUploadProgress?.(n,a,r,i)},onChunkComplete:()=>{},onSuccess:t=>{d(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),p(),f()},onError:t=>{d(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),p(),f()},onShouldRetry:e.onShouldRetry});l.value.set(n.id,r)}catch(t){d(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),p(),f()}}},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),p()}},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(()=>p(),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)p()},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:o(h),items:o(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 b={status:`idle`,progress:0,bytesUploaded:0,totalBytes:null,error:null,result:null};function x(e={}){let t=m(),r=s({...b}),i=null;return i=new l((e,n)=>t.client.upload(e,n),{onStateChange:e=>{r.value=e},onProgress:e.onProgress,onChunkComplete:e.onChunkComplete,onSuccess:e.onSuccess,onError:e.onError,onAbort:e.onAbort},e),a(()=>{i?.cleanup()}),{state:r,upload:e=>{i?.upload(e)},abort:()=>{i?.abort()},reset:()=>{i?.reset()},retry:()=>{i?.retry()},isUploading:n(()=>r.value.status===`uploading`),canRetry:n(()=>i?.canRetry()??!1),metrics:{getInsights:()=>t.client.getChunkingInsights(),exportMetrics:()=>t.client.exportMetrics(),getNetworkMetrics:()=>t.client.getNetworkMetrics(),getNetworkCondition:()=>t.client.getNetworkCondition(),resetMetrics:()=>t.client.resetMetrics()}}}const S={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 C(e={}){let{speedCalculationInterval:t=1e3,speedSampleSize:n=10,onMetricsUpdate:r,onFileStart:i,onFileProgress:c,onFileComplete:l}=e,u=m(),d=s({...S}),f=s([]),p=s([]),h=s(0),g=s(null),_=(e,t)=>{let r={time:e,bytes:t};p.value.push(r),p.value.length>n&&(p.value=p.value.slice(-n));let i=0;if(p.value.length>=2){let e=p.value[p.value.length-1],t=p.value[p.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(p.value.length>=2){let e=p.value[0],t=p.value[p.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=f.value.reduce((e,t)=>e+t.size,0),n=f.value.reduce((e,t)=>e+t.bytesUploaded,0),i=f.value.filter(e=>e.isComplete).length,a=f.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 p=f.value.filter(e=>e.startTime>0),m=p.length>0?Math.min(...p.map(e=>e.startTime)):null,h=f.value.filter(e=>e.endTime!==null),g=h.length>0&&i===f.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:f.value.length,completedFiles:i,activeUploads:a,progress:c,peakSpeed:Math.max(d.value.peakSpeed,o),startTime:m,endTime:g,totalDuration:v,insights:u.client.getChunkingInsights(),sessionMetrics:[u.client.exportMetrics().session],chunkMetrics:u.client.exportMetrics().chunks};d.value=y,r?.(y)},y=()=>{g.value&&clearInterval(g.value),g.value=setInterval(()=>{f.value.some(e=>!e.isComplete&&e.bytesUploaded>0)&&v()},t)};return a(()=>{g.value&&clearInterval(g.value)}),{metrics:o(d),fileMetrics:o(f),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};f.value.find(t=>t.id===e)?f.value=f.value.map(t=>t.id===e?r:t):f.value=[...f.value,r],i?.(r),f.value.filter(e=>!e.isComplete).length===1&&y()},updateFileProgress:(e,t)=>{let n=Date.now();f.value=f.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();f.value=f.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=>{f.value=f.value.filter(t=>t.id!==e),setTimeout(v,0)},reset:()=>{g.value&&=(clearInterval(g.value),null),d.value={...S},f.value=[],p.value=[],h.value=0},getFileMetrics:e=>f.value.find(t=>t.id===e),exportMetrics:()=>({overall:d.value,files:f.value,exportTime:Date.now()})}}export{_ as a,v as i,x as n,m as o,y as r,p as s,C as t};
|
|
2
|
+
//# sourceMappingURL=composables-8ehXYRvI.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"composables-8ehXYRvI.mjs","names":["initialState: DragDropState","initialState","errors: string[]","files: File[]","initialState: FlowUploadState","initialState","manager: FlowManager<File | Blob, TOutput> | null","unsubscribe: (() => void) | null","items","newItems: FlowUploadItem<BrowserUploadInput>[]","state","newItems: UploadItem[]","item","initialState: UploadState","manager: UploadManager | null","uploadFn: UploadFunction<UploadInput>","initialMetrics: UploadMetrics","estimatedTimeRemaining: number | null","newMetrics: UploadMetrics","fileMetric: FileUploadMetrics"],"sources":["../src/composables/useDragDrop.ts","../src/composables/useUploadistaClient.ts","../src/composables/useFlowUpload.ts","../src/composables/useMultiFlowUpload.ts","../src/composables/useMultiUpload.ts","../src/composables/useUpload.ts","../src/composables/useUploadMetrics.ts"],"sourcesContent":["import { computed, readonly, ref } from \"vue\";\n\nexport interface DragDropOptions {\n /**\n * Accept specific file types (MIME types or file extensions)\n */\n accept?: string[];\n\n /**\n * Maximum number of files allowed\n */\n maxFiles?: number;\n\n /**\n * Maximum file size in bytes\n */\n maxFileSize?: number;\n\n /**\n * Whether to allow multiple files\n */\n multiple?: boolean;\n\n /**\n * Custom validation function for files\n */\n validator?: (files: File[]) => string[] | null;\n\n /**\n * Called when files are dropped or selected\n */\n onFilesReceived?: (files: File[]) => void;\n\n /**\n * Called when validation fails\n */\n onValidationError?: (errors: string[]) => void;\n\n /**\n * Called when drag state changes\n */\n onDragStateChange?: (isDragging: boolean) => void;\n}\n\nexport interface DragDropState {\n /**\n * Whether files are currently being dragged over the drop zone\n */\n isDragging: boolean;\n\n /**\n * Whether the drag is currently over the drop zone\n */\n isOver: boolean;\n\n /**\n * Whether the dragged items are valid files\n */\n isValid: boolean;\n\n /**\n * Current validation errors\n */\n errors: string[];\n}\n\nconst initialState: DragDropState = {\n isDragging: false,\n isOver: false,\n isValid: true,\n errors: [],\n};\n\n/**\n * Vue composable for handling drag and drop file uploads with validation.\n * Provides drag state management, file validation, and file picker integration.\n *\n * @param options - Configuration and event handlers\n * @returns Drag and drop state and handlers\n *\n * @example\n * ```vue\n * <script setup lang=\"ts\">\n * import { useDragDrop } from '@uploadista/vue';\n * import { ref } from 'vue';\n *\n * const inputRef = ref<HTMLInputElement>();\n *\n * const dragDrop = useDragDrop({\n * accept: ['image/*', '.pdf'],\n * maxFiles: 5,\n * maxFileSize: 10 * 1024 * 1024, // 10MB\n * multiple: true,\n * onFilesReceived: (files) => {\n * console.log('Received files:', files);\n * // Process files with upload composables\n * },\n * onValidationError: (errors) => {\n * console.error('Validation errors:', errors);\n * },\n * });\n *\n * const openFilePicker = () => {\n * inputRef.value?.click();\n * };\n * </script>\n *\n * <template>\n * <div>\n * <div\n * @dragenter=\"dragDrop.onDragEnter\"\n * @dragover=\"dragDrop.onDragOver\"\n * @dragleave=\"dragDrop.onDragLeave\"\n * @drop=\"dragDrop.onDrop\"\n * @click=\"openFilePicker\"\n * :style=\"{\n * border: dragDrop.state.isDragging ? '2px dashed #007bff' : '2px dashed #ccc',\n * backgroundColor: dragDrop.state.isOver ? '#f8f9fa' : 'transparent',\n * padding: '2rem',\n * textAlign: 'center',\n * cursor: 'pointer',\n * }\"\n * >\n * <p v-if=\"dragDrop.state.isDragging\">Drop files here...</p>\n * <p v-else>Drag files here or click to select</p>\n *\n * <div v-if=\"dragDrop.state.errors.length > 0\" style=\"color: red; margin-top: 1rem\">\n * <p v-for=\"(error, index) in dragDrop.state.errors\" :key=\"index\">{{ error }}</p>\n * </div>\n * </div>\n *\n * <input\n * ref=\"inputRef\"\n * type=\"file\"\n * :multiple=\"dragDrop.inputProps.multiple\"\n * :accept=\"dragDrop.inputProps.accept\"\n * @change=\"dragDrop.onInputChange\"\n * style=\"display: none\"\n * />\n * </div>\n * </template>\n * ```\n */\nexport function useDragDrop(options: DragDropOptions = {}) {\n const {\n accept,\n maxFiles,\n maxFileSize,\n multiple = true,\n validator,\n onFilesReceived,\n onValidationError,\n onDragStateChange,\n } = options;\n\n const state = ref<DragDropState>({ ...initialState });\n const dragCounter = ref(0);\n\n const updateState = (update: Partial<DragDropState>) => {\n state.value = { ...state.value, ...update };\n };\n\n const validateFiles = (files: File[]): string[] => {\n const errors: string[] = [];\n\n // Check file count\n if (maxFiles && files.length > maxFiles) {\n errors.push(\n `Maximum ${maxFiles} files allowed. You selected ${files.length} files.`,\n );\n }\n\n // Check individual files\n for (const file of files) {\n // Check file size\n if (maxFileSize && file.size > maxFileSize) {\n const maxSizeMB = (maxFileSize / (1024 * 1024)).toFixed(1);\n const fileSizeMB = (file.size / (1024 * 1024)).toFixed(1);\n errors.push(\n `File \"${file.name}\" (${fileSizeMB}MB) exceeds maximum size of ${maxSizeMB}MB.`,\n );\n }\n\n // Check file type\n if (accept && accept.length > 0) {\n const isAccepted = accept.some((acceptType) => {\n if (acceptType.startsWith(\".\")) {\n // File extension check\n return file.name.toLowerCase().endsWith(acceptType.toLowerCase());\n } else {\n // MIME type check (supports wildcards like image/*)\n if (acceptType.endsWith(\"/*\")) {\n const baseType = acceptType.slice(0, -2);\n return file.type.startsWith(baseType);\n } else {\n return file.type === acceptType;\n }\n }\n });\n\n if (!isAccepted) {\n errors.push(\n `File \"${file.name}\" type \"${file.type}\" is not accepted. Accepted types: ${accept.join(\", \")}.`,\n );\n }\n }\n }\n\n // Run custom validator\n if (validator) {\n const customErrors = validator(files);\n if (customErrors) {\n errors.push(...customErrors);\n }\n }\n\n return errors;\n };\n\n const processFiles = (files: File[]) => {\n const fileArray = Array.from(files);\n const errors = validateFiles(fileArray);\n\n if (errors.length > 0) {\n updateState({ errors, isValid: false });\n onValidationError?.(errors);\n } else {\n updateState({ errors: [], isValid: true });\n onFilesReceived?.(fileArray);\n }\n };\n\n const getFilesFromDataTransfer = (dataTransfer: DataTransfer): File[] => {\n const files: File[] = [];\n\n if (dataTransfer.items) {\n // Use DataTransferItemList interface\n for (let i = 0; i < dataTransfer.items.length; i++) {\n const item = dataTransfer.items[i];\n if (item && item.kind === \"file\") {\n const file = item.getAsFile();\n if (file) {\n files.push(file);\n }\n }\n }\n } else {\n // Fallback to DataTransfer.files\n for (let i = 0; i < dataTransfer.files.length; i++) {\n const file = dataTransfer.files[i];\n if (file) {\n files.push(file);\n }\n }\n }\n\n return files;\n };\n\n const onDragEnter = (event: DragEvent) => {\n event.preventDefault();\n event.stopPropagation();\n\n dragCounter.value++;\n\n if (dragCounter.value === 1) {\n updateState({ isDragging: true, isOver: true });\n onDragStateChange?.(true);\n }\n };\n\n const onDragOver = (event: DragEvent) => {\n event.preventDefault();\n event.stopPropagation();\n\n // Set dropEffect to indicate what operation is allowed\n if (event.dataTransfer) {\n event.dataTransfer.dropEffect = \"copy\";\n }\n };\n\n const onDragLeave = (event: DragEvent) => {\n event.preventDefault();\n event.stopPropagation();\n\n dragCounter.value--;\n\n if (dragCounter.value === 0) {\n updateState({ isDragging: false, isOver: false, errors: [] });\n onDragStateChange?.(false);\n }\n };\n\n const onDrop = (event: DragEvent) => {\n event.preventDefault();\n event.stopPropagation();\n\n dragCounter.value = 0;\n updateState({ isDragging: false, isOver: false });\n onDragStateChange?.(false);\n\n if (event.dataTransfer) {\n const files = getFilesFromDataTransfer(event.dataTransfer);\n if (files.length > 0) {\n processFiles(files);\n }\n }\n };\n\n const onInputChange = (event: Event) => {\n const input = event.target as HTMLInputElement;\n if (input.files && input.files.length > 0) {\n const files = Array.from(input.files);\n processFiles(files);\n }\n\n // Reset input value to allow selecting the same files again\n input.value = \"\";\n };\n\n const reset = () => {\n state.value = { ...initialState };\n dragCounter.value = 0;\n };\n\n const inputProps = computed(() => ({\n type: \"file\" as const,\n multiple,\n accept: accept?.join(\", \"),\n }));\n\n return {\n state: readonly(state),\n onDragEnter,\n onDragOver,\n onDragLeave,\n onDrop,\n onInputChange,\n inputProps,\n processFiles,\n reset,\n };\n}\n","import type { UploadistaEvent } from \"@uploadista/client-browser\";\nimport { inject, type Ref } from \"vue\";\nimport {\n UPLOADISTA_CLIENT_KEY,\n UPLOADISTA_EVENT_SUBSCRIBERS_KEY,\n} from \"./plugin\";\n\n/**\n * Access the Uploadista client instance from the plugin or provider.\n * Must be used within a component tree that has the Uploadista plugin or provider installed.\n *\n * @returns Uploadista client instance with event subscription\n * @throws Error if used outside of Uploadista plugin/provider context\n *\n * @example\n * ```vue\n * <script setup lang=\"ts\">\n * import { useUploadistaClient } from '@uploadista/vue';\n *\n * const { client, subscribeToEvents } = useUploadistaClient();\n *\n * // Subscribe to all events\n * const unsubscribe = subscribeToEvents((event) => {\n * console.log('Upload event:', event);\n * });\n *\n * // Clean up on unmount\n * onUnmounted(() => {\n * unsubscribe();\n * });\n * </script>\n * ```\n */\nexport function useUploadistaClient() {\n const client = inject(UPLOADISTA_CLIENT_KEY);\n\n if (!client) {\n throw new Error(\n \"useUploadistaClient must be used within a component tree that has the Uploadista plugin or provider installed. \" +\n \"Make sure to either use app.use(createUploadistaPlugin({ ... })) in your main app file, \" +\n \"or wrap your component tree with <UploadistaProvider>.\",\n );\n }\n\n // Try to get the shared event subscribers from the provider\n const eventSubscribersRef = inject<\n Ref<Set<(event: UploadistaEvent) => void>> | undefined\n >(UPLOADISTA_EVENT_SUBSCRIBERS_KEY);\n\n const subscribeToEvents = (handler: (event: UploadistaEvent) => void) => {\n if (!eventSubscribersRef) {\n console.warn(\n \"subscribeToEvents called but no event subscribers provided. Events will not be dispatched. \" +\n \"Make sure to use UploadistaProvider or createUploadistaPlugin with proper configuration.\",\n );\n return () => {\n // No-op unsubscribe if subscribers aren't available\n };\n }\n\n eventSubscribersRef.value.add(handler);\n return () => {\n eventSubscribersRef.value.delete(handler);\n };\n };\n\n return {\n client,\n subscribeToEvents,\n };\n}\n\nexport type UseUploadistaClientReturn = ReturnType<typeof useUploadistaClient>;\n","import type {\n FlowUploadOptions,\n UploadistaEvent,\n} from \"@uploadista/client-browser\";\nimport {\n FlowManager,\n type FlowUploadState,\n type FlowUploadStatus,\n type InternalFlowUploadOptions,\n} from \"@uploadista/client-core\";\nimport { EventType, type FlowEvent } from \"@uploadista/core/flow\";\nimport type { UploadFile } from \"@uploadista/core/types\";\nimport { UploadEventType } from \"@uploadista/core/types\";\nimport { computed, onMounted, onUnmounted, readonly, ref } from \"vue\";\nimport { useUploadistaClient } from \"./useUploadistaClient\";\n\n/**\n * Type guard to check if an event is a flow event\n */\nfunction isFlowEvent(event: UploadistaEvent): event is FlowEvent {\n const flowEvent = event as FlowEvent;\n return (\n flowEvent.eventType === EventType.FlowStart ||\n flowEvent.eventType === EventType.FlowEnd ||\n flowEvent.eventType === EventType.FlowError ||\n flowEvent.eventType === EventType.NodeStart ||\n flowEvent.eventType === EventType.NodeEnd ||\n flowEvent.eventType === EventType.NodePause ||\n flowEvent.eventType === EventType.NodeResume ||\n flowEvent.eventType === EventType.NodeError\n );\n}\n\n// Re-export types from core for convenience\nexport type { FlowUploadState, FlowUploadStatus };\n\nexport interface UseFlowUploadOptions<TOutput = UploadFile> {\n /**\n * Flow configuration\n */\n flowConfig: FlowUploadOptions<TOutput>[\"flowConfig\"];\n\n /**\n * Called when upload progress updates\n */\n onProgress?: (\n progress: number,\n bytesUploaded: number,\n totalBytes: number | null,\n ) => void;\n\n /**\n * Called when a chunk completes\n */\n onChunkComplete?: (\n chunkSize: number,\n bytesAccepted: number,\n bytesTotal: number | null,\n ) => void;\n\n /**\n * Called when the flow completes successfully (receives full flow outputs)\n * This is the recommended callback for multi-output flows\n * Format: { [outputNodeId]: result, ... }\n */\n onFlowComplete?: (outputs: Record<string, unknown>) => void;\n\n /**\n * Called when upload succeeds (legacy, single-output flows)\n * For single-output flows, receives the value from the specified outputNodeId\n * or the first output node if outputNodeId is not specified\n */\n onSuccess?: (result: TOutput) => void;\n\n /**\n * Called when upload fails\n */\n onError?: (error: Error) => void;\n\n /**\n * Called when upload is aborted\n */\n onAbort?: () => void;\n\n /**\n * Custom retry logic\n */\n onShouldRetry?: (error: Error, retryAttempt: number) => boolean;\n}\n\nconst initialState: FlowUploadState = {\n status: \"idle\",\n progress: 0,\n bytesUploaded: 0,\n totalBytes: null,\n error: null,\n result: null,\n jobId: null,\n flowStarted: false,\n currentNodeName: null,\n currentNodeType: null,\n flowOutputs: null,\n};\n\n/**\n * Vue composable for uploading files through a flow.\n *\n * This composable provides a simple interface for uploading files through a flow.\n * The flow handles the upload process and can perform post-processing like\n * saving to storage, optimizing images, etc.\n *\n * Must be used within a component tree that has the Uploadista plugin installed.\n * Events are automatically wired up through the plugin.\n *\n * @example\n * ```vue\n * <script setup lang=\"ts\">\n * import { useFlowUpload } from '@uploadista/vue';\n *\n * const flowUpload = useFlowUpload({\n * flowConfig: {\n * flowId: \"my-upload-flow\",\n * storageId: \"my-storage\",\n * },\n * onSuccess: (result) => {\n * console.log(\"Upload complete:\", result);\n * },\n * });\n *\n * const handleFileChange = (event: Event) => {\n * const file = (event.target as HTMLInputElement).files?.[0];\n * if (file) flowUpload.upload(file);\n * };\n * </script>\n *\n * <template>\n * <input type=\"file\" @change=\"handleFileChange\" />\n * </template>\n * ```\n */\nexport function useFlowUpload<TOutput = UploadFile>(\n options: UseFlowUploadOptions<TOutput>,\n) {\n // Get client\n const client = useUploadistaClient();\n const state = ref<FlowUploadState<TOutput>>(\n initialState as FlowUploadState<TOutput>,\n );\n let manager: FlowManager<File | Blob, TOutput> | null = null;\n let unsubscribe: (() => void) | null = null;\n\n // Create FlowManager instance\n onMounted(() => {\n manager = new FlowManager(\n async (\n file: File | Blob,\n flowConfig: {\n flowId: string;\n storageId: string;\n outputNodeId?: string;\n metadata?: Record<string, string>;\n },\n internalOptions: InternalFlowUploadOptions,\n ) => {\n const result = await client.client.uploadWithFlow(file, flowConfig, {\n onJobStart: internalOptions.onJobStart,\n onProgress: internalOptions.onProgress,\n onChunkComplete: internalOptions.onChunkComplete,\n onSuccess: internalOptions.onSuccess,\n onError: internalOptions.onError,\n onShouldRetry: internalOptions.onShouldRetry,\n });\n // Return only abort and pause (ignore jobId and return value)\n return {\n abort: async () => {\n await result.abort();\n },\n pause: async () => {\n await result.pause();\n // Ignore the FlowJob return value\n },\n };\n },\n {\n onStateChange: (newState) => {\n state.value = newState;\n },\n onProgress: options.onProgress\n ? (_uploadId, bytesUploaded, totalBytes) => {\n const progress = totalBytes\n ? Math.round((bytesUploaded / totalBytes) * 100)\n : 0;\n options.onProgress?.(progress, bytesUploaded, totalBytes);\n }\n : undefined,\n onChunkComplete: options.onChunkComplete,\n onFlowComplete: options.onFlowComplete,\n onSuccess: options.onSuccess,\n onError: options.onError,\n onAbort: options.onAbort,\n },\n {\n flowConfig: options.flowConfig,\n onChunkComplete: options.onChunkComplete,\n onFlowComplete: options.onFlowComplete,\n onSuccess: options.onSuccess,\n onError: options.onError,\n onAbort: options.onAbort,\n onShouldRetry: options.onShouldRetry,\n },\n );\n\n // Subscribe to events and forward them to the manager\n unsubscribe = client.subscribeToEvents((event: UploadistaEvent) => {\n // Handle flow events\n if (isFlowEvent(event)) {\n manager?.handleFlowEvent(event);\n return;\n }\n\n // Handle upload progress events for this job's upload\n const uploadEvent = event as {\n type: string;\n data?: { id: string; progress: number; total: number };\n flow?: { jobId: string };\n };\n if (\n uploadEvent.type === UploadEventType.UPLOAD_PROGRESS &&\n uploadEvent.flow?.jobId === manager?.getJobId() &&\n uploadEvent.data\n ) {\n const { progress: bytesUploaded, total: totalBytes } = uploadEvent.data;\n\n manager?.handleUploadProgress(\n uploadEvent.data.id,\n bytesUploaded,\n totalBytes,\n );\n }\n });\n });\n\n // Cleanup on unmount\n onUnmounted(() => {\n unsubscribe?.();\n manager?.cleanup();\n });\n\n const upload = async (file: File | Blob) => {\n await manager?.upload(file);\n };\n\n const abort = () => {\n manager?.abort();\n };\n\n const pause = () => {\n manager?.pause();\n };\n\n const reset = () => {\n manager?.reset();\n };\n\n return {\n state: readonly(state),\n upload,\n abort,\n pause,\n reset,\n // Derive computed values from state (reactive to state changes)\n isUploading: computed(\n () =>\n state.value.status === \"uploading\" ||\n state.value.status === \"processing\",\n ),\n isUploadingFile: computed(() => state.value.status === \"uploading\"),\n isProcessing: computed(() => state.value.status === \"processing\"),\n };\n}\n","import type {\n BrowserUploadInput,\n FlowUploadItem,\n MultiFlowUploadOptions,\n MultiFlowUploadState,\n} from \"@uploadista/client-browser\";\nimport type { UploadFile } from \"@uploadista/core/types\";\nimport { computed, readonly, ref } from \"vue\";\nimport { useUploadistaClient } from \"./useUploadistaClient\";\n\n/**\n * Vue composable for uploading multiple files through a flow.\n *\n * Must be used within a component tree that has the Uploadista plugin installed.\n *\n * @example\n * ```vue\n * <script setup lang=\"ts\">\n * import { useMultiFlowUpload } from '@uploadista/vue';\n *\n * const multiFlowUpload = useMultiFlowUpload({\n * flowConfig: {\n * flowId: \"batch-upload-flow\",\n * inputNodeId: \"upload-node\",\n * storageId: \"my-storage\",\n * },\n * maxConcurrent: 3,\n * onComplete: (items) => {\n * console.log(\"All uploads complete:\", items);\n * },\n * });\n *\n * const handleFileChange = (event: Event) => {\n * const files = (event.target as HTMLInputElement).files;\n * if (files) {\n * multiFlowUpload.addFiles(files);\n * multiFlowUpload.startUpload();\n * }\n * };\n * </script>\n *\n * <template>\n * <div>\n * <input type=\"file\" multiple @change=\"handleFileChange\" />\n *\n * <div v-for=\"item in multiFlowUpload.state.items\" :key=\"item.id\">\n * <span>{{ item.file.name }}</span>\n * <progress :value=\"item.progress\" :max=\"100\" />\n * <button\n * v-if=\"item.status === 'uploading'\"\n * @click=\"multiFlowUpload.abortUpload(item.id)\"\n * >\n * Cancel\n * </button>\n * </div>\n * </div>\n * </template>\n * ```\n */\nexport function useMultiFlowUpload(\n options: MultiFlowUploadOptions<BrowserUploadInput>,\n) {\n const client = useUploadistaClient();\n const items = ref<FlowUploadItem<BrowserUploadInput>[]>([]);\n const abortFns = ref<Map<string, () => void>>(new Map());\n const queue = ref<string[]>([]);\n const activeCount = ref(0);\n\n const maxConcurrent = options.maxConcurrent ?? 3;\n\n const calculateTotalProgress = (\n items: FlowUploadItem<BrowserUploadInput>[],\n ) => {\n if (items.length === 0) return 0;\n const totalProgress = items.reduce((sum, item) => sum + item.progress, 0);\n return Math.round(totalProgress / items.length);\n };\n\n const processQueue = async () => {\n if (activeCount.value >= maxConcurrent || queue.value.length === 0) {\n return;\n }\n\n const itemId = queue.value.shift();\n if (!itemId) return;\n\n const item = items.value.find((i) => i.id === itemId);\n if (!item || item.status !== \"pending\") {\n processQueue();\n return;\n }\n\n activeCount.value++;\n\n items.value = items.value.map((i) =>\n i.id === itemId ? { ...i, status: \"uploading\" as const } : i,\n );\n\n try {\n const { abort, jobId } = await client.client.uploadWithFlow(\n item.file,\n options.flowConfig,\n {\n onJobStart: (jobId: string) => {\n items.value = items.value.map((i) =>\n i.id === itemId ? { ...i, jobId } : i,\n );\n },\n onProgress: (\n _uploadId: string,\n bytesUploaded: number,\n totalBytes: number | null,\n ) => {\n const progress = totalBytes\n ? Math.round((bytesUploaded / totalBytes) * 100)\n : 0;\n\n items.value = items.value.map((i) => {\n if (i.id === itemId) {\n const updated = {\n ...i,\n progress,\n bytesUploaded,\n totalBytes: totalBytes || 0,\n };\n options.onItemProgress?.(updated);\n return updated;\n }\n return i;\n });\n },\n onSuccess: (result: UploadFile) => {\n items.value = items.value.map((i) => {\n if (i.id === itemId) {\n const updated = {\n ...i,\n status: \"success\" as const,\n result,\n progress: 100,\n };\n options.onItemSuccess?.(updated);\n return updated;\n }\n return i;\n });\n\n // Check if all uploads are complete\n const allComplete = items.value.every(\n (i) =>\n i.status === \"success\" ||\n i.status === \"error\" ||\n i.status === \"aborted\",\n );\n if (allComplete) {\n options.onComplete?.(items.value);\n }\n\n abortFns.value.delete(itemId);\n activeCount.value--;\n processQueue();\n },\n onError: (error: Error) => {\n items.value = items.value.map((i) => {\n if (i.id === itemId) {\n const updated = { ...i, status: \"error\" as const, error };\n options.onItemError?.(updated, error);\n return updated;\n }\n return i;\n });\n\n // Check if all uploads are complete\n const allComplete = items.value.every(\n (i) =>\n i.status === \"success\" ||\n i.status === \"error\" ||\n i.status === \"aborted\",\n );\n if (allComplete) {\n options.onComplete?.(items.value);\n }\n\n abortFns.value.delete(itemId);\n activeCount.value--;\n processQueue();\n },\n onShouldRetry: options.onShouldRetry,\n },\n );\n\n abortFns.value.set(itemId, abort);\n\n items.value = items.value.map((i) =>\n i.id === itemId ? { ...i, jobId } : i,\n );\n } catch (error) {\n items.value = items.value.map((i) =>\n i.id === itemId\n ? { ...i, status: \"error\" as const, error: error as Error }\n : i,\n );\n\n activeCount.value--;\n processQueue();\n }\n };\n\n const addFiles = (files: File[] | FileList) => {\n const fileArray = Array.from(files);\n const newItems: FlowUploadItem<BrowserUploadInput>[] = fileArray.map(\n (file) => ({\n id: `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,\n file,\n status: \"pending\",\n progress: 0,\n bytesUploaded: 0,\n totalBytes: file.size,\n error: null,\n result: null,\n jobId: null,\n }),\n );\n\n items.value = [...items.value, ...newItems];\n };\n\n const removeFile = (id: string) => {\n const abortFn = abortFns.value.get(id);\n if (abortFn) {\n abortFn();\n abortFns.value.delete(id);\n }\n\n items.value = items.value.filter((item) => item.id !== id);\n queue.value = queue.value.filter((queueId) => queueId !== id);\n };\n\n const startUpload = () => {\n const pendingItems = items.value.filter(\n (item) => item.status === \"pending\",\n );\n queue.value.push(...pendingItems.map((item) => item.id));\n\n for (let i = 0; i < maxConcurrent; i++) {\n processQueue();\n }\n };\n\n const abortUpload = (id: string) => {\n const abortFn = abortFns.value.get(id);\n if (abortFn) {\n abortFn();\n abortFns.value.delete(id);\n\n items.value = items.value.map((item) =>\n item.id === id ? { ...item, status: \"aborted\" as const } : item,\n );\n\n activeCount.value--;\n processQueue();\n }\n };\n\n const abortAll = () => {\n for (const abortFn of abortFns.value.values()) {\n abortFn();\n }\n abortFns.value.clear();\n queue.value = [];\n activeCount.value = 0;\n\n items.value = items.value.map((item) =>\n item.status === \"uploading\"\n ? { ...item, status: \"aborted\" as const }\n : item,\n );\n };\n\n const clear = () => {\n abortAll();\n items.value = [];\n };\n\n const retryUpload = (id: string) => {\n items.value = items.value.map((item) =>\n item.id === id\n ? {\n ...item,\n status: \"pending\" as const,\n progress: 0,\n bytesUploaded: 0,\n error: null,\n }\n : item,\n );\n\n queue.value.push(id);\n processQueue();\n };\n\n const state = computed<MultiFlowUploadState<BrowserUploadInput>>(() => ({\n items: items.value,\n totalProgress: calculateTotalProgress(items.value),\n activeUploads: items.value.filter((item) => item.status === \"uploading\")\n .length,\n completedUploads: items.value.filter((item) => item.status === \"success\")\n .length,\n failedUploads: items.value.filter((item) => item.status === \"error\").length,\n }));\n\n return {\n state: readonly(state),\n addFiles,\n removeFile,\n startUpload,\n abortUpload,\n abortAll,\n clear,\n retryUpload,\n isUploading: computed(() => state.value.activeUploads > 0),\n };\n}\n","import type { UploadOptions } from \"@uploadista/client-browser\";\nimport type { UploadMetrics } from \"@uploadista/client-core\";\nimport type { UploadFile } from \"@uploadista/core/types\";\nimport { computed, readonly, ref } from \"vue\";\nimport type { UploadInput, UploadState, UploadStatus } from \"./useUpload\";\nimport { useUploadistaClient } from \"./useUploadistaClient\";\n\nexport interface UploadItem {\n id: string;\n file: UploadInput;\n state: UploadState;\n}\n\nexport interface MultiUploadOptions\n extends Omit<UploadOptions, \"onSuccess\" | \"onError\" | \"onProgress\"> {\n /**\n * Maximum number of concurrent uploads\n */\n maxConcurrent?: number;\n\n /**\n * Called when an individual file upload starts\n */\n onUploadStart?: (item: UploadItem) => void;\n\n /**\n * Called when an individual file upload progresses\n */\n onUploadProgress?: (\n item: UploadItem,\n progress: number,\n bytesUploaded: number,\n totalBytes: number | null,\n ) => void;\n\n /**\n * Called when an individual file upload succeeds\n */\n onUploadSuccess?: (item: UploadItem, result: UploadFile) => void;\n\n /**\n * Called when an individual file upload fails\n */\n onUploadError?: (item: UploadItem, error: Error) => void;\n\n /**\n * Called when all uploads complete (successfully or with errors)\n */\n onComplete?: (results: {\n successful: UploadItem[];\n failed: UploadItem[];\n total: number;\n }) => void;\n}\n\nexport interface MultiUploadState {\n /**\n * Total number of uploads\n */\n total: number;\n\n /**\n * Number of completed uploads (successful + failed)\n */\n completed: number;\n\n /**\n * Number of successful uploads\n */\n successful: number;\n\n /**\n * Number of failed uploads\n */\n failed: number;\n\n /**\n * Number of currently uploading files\n */\n uploading: number;\n\n /**\n * Overall progress as a percentage (0-100)\n */\n progress: number;\n\n /**\n * Total bytes uploaded across all files\n */\n totalBytesUploaded: number;\n\n /**\n * Total bytes to upload across all files\n */\n totalBytes: number;\n\n /**\n * Whether any uploads are currently active\n */\n isUploading: boolean;\n\n /**\n * Whether all uploads have completed\n */\n isComplete: boolean;\n}\n\n/**\n * Vue composable for managing multiple file uploads with queue management,\n * concurrent upload limits, and batch operations.\n *\n * Must be used within a component tree that has the Uploadista plugin installed.\n *\n * @param options - Multi-upload configuration and event handlers\n * @returns Multi-upload state and control methods\n *\n * @example\n * ```vue\n * <script setup lang=\"ts\">\n * import { useMultiUpload } from '@uploadista/vue';\n *\n * const multiUpload = useMultiUpload({\n * maxConcurrent: 3,\n * onUploadSuccess: (item, result) => {\n * console.log(`${item.file.name} uploaded successfully`);\n * },\n * onComplete: (results) => {\n * console.log(`Upload batch complete: ${results.successful.length}/${results.total} successful`);\n * },\n * });\n *\n * const handleFileChange = (event: Event) => {\n * const files = (event.target as HTMLInputElement).files;\n * if (files) {\n * multiUpload.addFiles(Array.from(files));\n * }\n * };\n * </script>\n *\n * <template>\n * <div>\n * <input type=\"file\" multiple @change=\"handleFileChange\" />\n *\n * <div>Progress: {{ multiUpload.state.progress }}%</div>\n * <div>\n * {{ multiUpload.state.uploading }} uploading,\n * {{ multiUpload.state.successful }} successful,\n * {{ multiUpload.state.failed }} failed\n * </div>\n *\n * <button @click=\"multiUpload.startAll\" :disabled=\"multiUpload.state.isUploading\">\n * Start All\n * </button>\n * <button @click=\"multiUpload.abortAll\" :disabled=\"!multiUpload.state.isUploading\">\n * Abort All\n * </button>\n * <button @click=\"multiUpload.retryFailed\" :disabled=\"multiUpload.state.failed === 0\">\n * Retry Failed\n * </button>\n *\n * <div v-for=\"item in multiUpload.items\" :key=\"item.id\">\n * {{ item.file.name }}: {{ item.state.status }} ({{ item.state.progress }}%)\n * </div>\n * </div>\n * </template>\n * ```\n */\nexport function useMultiUpload(options: MultiUploadOptions = {}) {\n const uploadClient = useUploadistaClient();\n const { maxConcurrent = 3 } = options;\n const items = ref<UploadItem[]>([]);\n const nextId = ref(0);\n const activeUploads = ref(new Set<string>());\n\n // Store abort controllers for each upload\n const abortControllers = ref<Map<string, { abort: () => void }>>(new Map());\n\n // Generate a unique ID for each upload item\n const generateId = () => {\n return `upload-${Date.now()}-${nextId.value++}`;\n };\n\n // State update callback for individual uploads\n const onStateUpdate = (id: string, state: Partial<UploadState>) => {\n items.value = items.value.map((item) =>\n item.id === id ? { ...item, state: { ...item.state, ...state } } : item,\n );\n };\n\n // Check if all uploads are complete and trigger completion callback\n const checkForCompletion = () => {\n const allComplete = items.value.every((item) =>\n [\"success\", \"error\", \"aborted\"].includes(item.state.status),\n );\n\n if (allComplete && items.value.length > 0) {\n const successful = items.value.filter(\n (item) => item.state.status === \"success\",\n );\n const failed = items.value.filter((item) =>\n [\"error\", \"aborted\"].includes(item.state.status),\n );\n\n options.onComplete?.({\n successful,\n failed,\n total: items.value.length,\n });\n }\n };\n\n // Start the next available upload if we have capacity\n const startNextUpload = async () => {\n if (activeUploads.value.size >= maxConcurrent) {\n return;\n }\n\n const nextItem = items.value.find(\n (item) =>\n item.state.status === \"idle\" && !activeUploads.value.has(item.id),\n );\n\n if (!nextItem) {\n return;\n }\n\n activeUploads.value.add(nextItem.id);\n options.onUploadStart?.(nextItem);\n\n // Update state to uploading\n onStateUpdate(nextItem.id, { status: \"uploading\" });\n\n try {\n const controller = await uploadClient.client.upload(nextItem.file, {\n metadata: options.metadata,\n uploadLengthDeferred: options.uploadLengthDeferred,\n uploadSize: options.uploadSize,\n\n onProgress: (\n _uploadId: string,\n bytesUploaded: number,\n totalBytes: number | null,\n ) => {\n const progress = totalBytes\n ? Math.round((bytesUploaded / totalBytes) * 100)\n : 0;\n\n onStateUpdate(nextItem.id, {\n progress,\n bytesUploaded,\n totalBytes,\n });\n\n options.onUploadProgress?.(\n nextItem,\n progress,\n bytesUploaded,\n totalBytes,\n );\n },\n\n onChunkComplete: () => {\n // Optional: could expose this as an option\n },\n\n onSuccess: (result: UploadFile) => {\n onStateUpdate(nextItem.id, {\n status: \"success\",\n result,\n progress: 100,\n });\n\n const updatedItem = {\n ...nextItem,\n state: { ...nextItem.state, status: \"success\" as const, result },\n };\n options.onUploadSuccess?.(updatedItem, result);\n\n // Mark complete and start next\n activeUploads.value.delete(nextItem.id);\n abortControllers.value.delete(nextItem.id);\n startNextUpload();\n checkForCompletion();\n },\n\n onError: (error: Error) => {\n onStateUpdate(nextItem.id, {\n status: \"error\",\n error,\n });\n\n const updatedItem = {\n ...nextItem,\n state: { ...nextItem.state, status: \"error\" as const, error },\n };\n options.onUploadError?.(updatedItem, error);\n\n // Mark complete and start next\n activeUploads.value.delete(nextItem.id);\n abortControllers.value.delete(nextItem.id);\n startNextUpload();\n checkForCompletion();\n },\n\n onShouldRetry: options.onShouldRetry,\n });\n\n // Store abort controller\n abortControllers.value.set(nextItem.id, controller);\n } catch (error) {\n onStateUpdate(nextItem.id, {\n status: \"error\",\n error: error as Error,\n });\n\n const updatedItem = {\n ...nextItem,\n state: {\n ...nextItem.state,\n status: \"error\" as const,\n error: error as Error,\n },\n };\n options.onUploadError?.(updatedItem, error as Error);\n\n // Mark complete and start next\n activeUploads.value.delete(nextItem.id);\n abortControllers.value.delete(nextItem.id);\n startNextUpload();\n checkForCompletion();\n }\n };\n\n // Calculate overall state\n const state = computed<MultiUploadState>(() => {\n const itemsList = items.value;\n return {\n total: itemsList.length,\n completed: itemsList.filter((item) =>\n [\"success\", \"error\", \"aborted\"].includes(item.state.status),\n ).length,\n successful: itemsList.filter((item) => item.state.status === \"success\")\n .length,\n failed: itemsList.filter((item) =>\n [\"error\", \"aborted\"].includes(item.state.status),\n ).length,\n uploading: itemsList.filter((item) => item.state.status === \"uploading\")\n .length,\n progress:\n itemsList.length > 0\n ? Math.round(\n itemsList.reduce((sum, item) => sum + item.state.progress, 0) /\n itemsList.length,\n )\n : 0,\n totalBytesUploaded: itemsList.reduce(\n (sum, item) => sum + item.state.bytesUploaded,\n 0,\n ),\n totalBytes: itemsList.reduce(\n (sum, item) => sum + (item.state.totalBytes || 0),\n 0,\n ),\n isUploading: itemsList.some((item) => item.state.status === \"uploading\"),\n isComplete:\n itemsList.length > 0 &&\n itemsList.every((item) =>\n [\"success\", \"error\", \"aborted\"].includes(item.state.status),\n ),\n };\n });\n\n const addFiles = (files: UploadInput[]) => {\n const newItems: UploadItem[] = files.map((file) => {\n const id = generateId();\n return {\n id,\n file,\n state: {\n status: \"idle\",\n progress: 0,\n bytesUploaded: 0,\n totalBytes: file instanceof File ? file.size : null,\n error: null,\n result: null,\n },\n };\n });\n\n items.value = [...items.value, ...newItems];\n };\n\n const removeItem = (id: string) => {\n const item = items.value.find((i) => i.id === id);\n if (item && item.state.status === \"uploading\") {\n // Abort before removing\n const controller = abortControllers.value.get(id);\n if (controller) {\n controller.abort();\n abortControllers.value.delete(id);\n }\n }\n\n items.value = items.value.filter((item) => item.id !== id);\n activeUploads.value.delete(id);\n };\n\n const abortUpload = (id: string) => {\n const item = items.value.find((i) => i.id === id);\n if (item && item.state.status === \"uploading\") {\n const controller = abortControllers.value.get(id);\n if (controller) {\n controller.abort();\n abortControllers.value.delete(id);\n }\n\n activeUploads.value.delete(id);\n\n items.value = items.value.map((i) =>\n i.id === id\n ? { ...i, state: { ...i.state, status: \"aborted\" as const } }\n : i,\n );\n\n // Try to start next upload in queue\n startNextUpload();\n }\n };\n\n const retryUpload = (id: string) => {\n const item = items.value.find((i) => i.id === id);\n if (item && [\"error\", \"aborted\"].includes(item.state.status)) {\n items.value = items.value.map((i) =>\n i.id === id\n ? {\n ...i,\n state: { ...i.state, status: \"idle\" as const, error: null },\n }\n : i,\n );\n\n // Auto-start the upload\n setTimeout(() => startNextUpload(), 0);\n }\n };\n\n const startAll = () => {\n // Start as many uploads as we can up to the concurrent limit\n const idleItems = items.value.filter(\n (item) => item.state.status === \"idle\",\n );\n const slotsAvailable = maxConcurrent - activeUploads.value.size;\n const itemsToStart = idleItems.slice(0, slotsAvailable);\n\n for (const _item of itemsToStart) {\n startNextUpload();\n }\n };\n\n const abortAll = () => {\n items.value\n .filter((item) => item.state.status === \"uploading\")\n .forEach((item) => {\n const controller = abortControllers.value.get(item.id);\n if (controller) {\n controller.abort();\n abortControllers.value.delete(item.id);\n }\n });\n\n activeUploads.value.clear();\n\n // Update all uploading items to aborted status\n items.value = items.value.map((item) =>\n item.state.status === \"uploading\"\n ? { ...item, state: { ...item.state, status: \"aborted\" as const } }\n : item,\n );\n };\n\n const retryFailed = () => {\n const failedItems = items.value.filter((item) =>\n [\"error\", \"aborted\"].includes(item.state.status),\n );\n\n if (failedItems.length > 0) {\n items.value = items.value.map((item) =>\n failedItems.some((f) => f.id === item.id)\n ? {\n ...item,\n state: { ...item.state, status: \"idle\" as const, error: null },\n }\n : item,\n );\n\n // Auto-start uploads if we have capacity\n setTimeout(startAll, 0);\n }\n };\n\n const clearCompleted = () => {\n items.value = items.value.filter(\n (item) => ![\"success\", \"error\", \"aborted\"].includes(item.state.status),\n );\n };\n\n const clearAll = () => {\n abortAll();\n items.value = [];\n activeUploads.value.clear();\n };\n\n const getItemsByStatus = (status: UploadStatus) => {\n return items.value.filter((item) => item.state.status === status);\n };\n\n // Create aggregated metrics object that delegates to the upload client\n const metrics: UploadMetrics = {\n getInsights: () => uploadClient.client.getChunkingInsights(),\n exportMetrics: () => uploadClient.client.exportMetrics(),\n getNetworkMetrics: () => uploadClient.client.getNetworkMetrics(),\n getNetworkCondition: () => uploadClient.client.getNetworkCondition(),\n resetMetrics: () => uploadClient.client.resetMetrics(),\n };\n\n return {\n state: readonly(state),\n items: readonly(items),\n addFiles,\n removeItem,\n removeFile: removeItem, // Alias for consistency\n startAll,\n abortUpload,\n abortAll,\n retryUpload,\n retryFailed,\n clearCompleted,\n clearAll,\n getItemsByStatus,\n metrics,\n };\n}\n","import type {\n ChunkMetrics,\n PerformanceInsights,\n UploadSessionMetrics,\n} from \"@uploadista/client-browser\";\nimport type {\n UploadFunction,\n UploadMetrics,\n UploadOptions,\n} from \"@uploadista/client-core\";\nimport {\n UploadManager,\n type UploadState,\n type UploadStatus,\n} from \"@uploadista/client-core\";\nimport { computed, onUnmounted, ref } from \"vue\";\nimport { useUploadistaClient } from \"./useUploadistaClient\";\n\n// Re-export types from core for convenience\nexport type { UploadState, UploadStatus };\nexport type UploadInput = File | Blob;\nexport type { ChunkMetrics, PerformanceInsights, UploadSessionMetrics };\n\nconst initialState: UploadState = {\n status: \"idle\",\n progress: 0,\n bytesUploaded: 0,\n totalBytes: null,\n error: null,\n result: null,\n};\n\n/**\n * Vue composable for managing individual file uploads with full state management.\n * Provides upload progress tracking, error handling, abort functionality, and retry logic.\n *\n * Must be used within a component tree that has the Uploadista plugin installed.\n *\n * @param options - Upload configuration and event handlers\n * @returns Upload state and control methods\n *\n * @example\n * ```vue\n * <script setup lang=\"ts\">\n * import { useUpload } from '@uploadista/vue';\n *\n * const upload = useUpload({\n * onSuccess: (result) => console.log('Upload complete:', result),\n * onError: (error) => console.error('Upload failed:', error),\n * onProgress: (progress) => console.log('Progress:', progress + '%'),\n * });\n *\n * const handleFileChange = (event: Event) => {\n * const file = (event.target as HTMLInputElement).files?.[0];\n * if (file) upload.upload(file);\n * };\n * </script>\n *\n * <template>\n * <div>\n * <input type=\"file\" @change=\"handleFileChange\" />\n * <div v-if=\"upload.isUploading\">Progress: {{ upload.state.progress }}%</div>\n * <div v-if=\"upload.state.error\">Error: {{ upload.state.error.message }}</div>\n * <button v-if=\"upload.canRetry\" @click=\"upload.retry\">Retry</button>\n * <button @click=\"upload.abort\" :disabled=\"!upload.isUploading\">Abort</button>\n * </div>\n * </template>\n * ```\n */\nexport function useUpload(options: UploadOptions = {}) {\n const uploadistaClient = useUploadistaClient();\n const state = ref<UploadState>({ ...initialState });\n let manager: UploadManager | null = null;\n\n // Wrap the client's upload method to match UploadFunction signature\n const uploadFn: UploadFunction<UploadInput> = (\n input: UploadInput,\n opts: UploadOptions,\n ) => uploadistaClient.client.upload(input, opts);\n\n // Create UploadManager instance\n manager = new UploadManager(\n uploadFn,\n {\n onStateChange: (newState: UploadState) => {\n state.value = newState;\n },\n onProgress: options.onProgress,\n onChunkComplete: options.onChunkComplete,\n onSuccess: options.onSuccess,\n onError: options.onError,\n onAbort: options.onAbort,\n },\n options,\n );\n\n // Clean up manager when component unmounts\n onUnmounted(() => {\n manager?.cleanup();\n });\n\n // Upload function\n const upload = (file: UploadInput) => {\n manager?.upload(file);\n };\n\n // Abort function\n const abort = () => {\n manager?.abort();\n };\n\n // Reset function\n const reset = () => {\n manager?.reset();\n };\n\n // Retry function\n const retry = () => {\n manager?.retry();\n };\n\n // Computed properties\n const isUploading = computed(() => state.value.status === \"uploading\");\n const canRetry = computed(() => manager?.canRetry() ?? false);\n\n // Create metrics object that delegates to the upload client\n const metrics: UploadMetrics = {\n getInsights: () => uploadistaClient.client.getChunkingInsights(),\n exportMetrics: () => uploadistaClient.client.exportMetrics(),\n getNetworkMetrics: () => uploadistaClient.client.getNetworkMetrics(),\n getNetworkCondition: () => uploadistaClient.client.getNetworkCondition(),\n resetMetrics: () => uploadistaClient.client.resetMetrics(),\n };\n\n return {\n state,\n upload,\n abort,\n reset,\n retry,\n isUploading,\n canRetry,\n metrics,\n };\n}\n","import type {\n ChunkMetrics,\n PerformanceInsights,\n UploadSessionMetrics,\n} from \"@uploadista/client-browser\";\nimport { onUnmounted, readonly, ref } from \"vue\";\nimport { useUploadistaClient } from \"./useUploadistaClient\";\n\nexport interface UploadMetrics {\n /**\n * Total bytes uploaded across all files\n */\n totalBytesUploaded: number;\n\n /**\n * Total bytes to upload across all files\n */\n totalBytes: number;\n\n /**\n * Overall upload speed in bytes per second\n */\n averageSpeed: number;\n\n /**\n * Current upload speed in bytes per second\n */\n currentSpeed: number;\n\n /**\n * Estimated time remaining in milliseconds\n */\n estimatedTimeRemaining: number | null;\n\n /**\n * Total number of files being tracked\n */\n totalFiles: number;\n\n /**\n * Number of files completed\n */\n completedFiles: number;\n\n /**\n * Number of files currently uploading\n */\n activeUploads: number;\n\n /**\n * Overall progress as percentage (0-100)\n */\n progress: number;\n\n /**\n * Peak upload speed achieved\n */\n peakSpeed: number;\n\n /**\n * Start time of the first upload\n */\n startTime: number | null;\n\n /**\n * End time of the last completed upload\n */\n endTime: number | null;\n\n /**\n * Total duration of all uploads\n */\n totalDuration: number | null;\n\n /**\n * Detailed performance insights from the upload client\n */\n insights: PerformanceInsights;\n\n /**\n * Session metrics for completed uploads\n */\n sessionMetrics: Partial<UploadSessionMetrics>[];\n\n /**\n * Detailed chunk metrics from recent uploads\n */\n chunkMetrics: ChunkMetrics[];\n}\n\nexport interface FileUploadMetrics {\n id: string;\n filename: string;\n size: number;\n bytesUploaded: number;\n progress: number;\n speed: number;\n startTime: number;\n endTime: number | null;\n duration: number | null;\n isComplete: boolean;\n}\n\nexport interface UseUploadMetricsOptions {\n /**\n * Interval for calculating current speed (in milliseconds)\n */\n speedCalculationInterval?: number;\n\n /**\n * Number of speed samples to keep for average calculation\n */\n speedSampleSize?: number;\n\n /**\n * Called when metrics are updated\n */\n onMetricsUpdate?: (metrics: UploadMetrics) => void;\n\n /**\n * Called when a file upload starts\n */\n onFileStart?: (fileMetrics: FileUploadMetrics) => void;\n\n /**\n * Called when a file upload progresses\n */\n onFileProgress?: (fileMetrics: FileUploadMetrics) => void;\n\n /**\n * Called when a file upload completes\n */\n onFileComplete?: (fileMetrics: FileUploadMetrics) => void;\n}\n\nconst initialMetrics: UploadMetrics = {\n totalBytesUploaded: 0,\n totalBytes: 0,\n averageSpeed: 0,\n currentSpeed: 0,\n estimatedTimeRemaining: null,\n totalFiles: 0,\n completedFiles: 0,\n activeUploads: 0,\n progress: 0,\n peakSpeed: 0,\n startTime: null,\n endTime: null,\n totalDuration: null,\n insights: {\n overallEfficiency: 0,\n chunkingEffectiveness: 0,\n networkStability: 0,\n recommendations: [],\n optimalChunkSizeRange: { min: 256 * 1024, max: 2 * 1024 * 1024 },\n },\n sessionMetrics: [],\n chunkMetrics: [],\n};\n\n/**\n * Vue composable for tracking detailed upload metrics and performance statistics.\n * Provides comprehensive monitoring of upload progress, speed, and timing data.\n *\n * @param options - Configuration and event handlers\n * @returns Upload metrics state and control methods\n *\n * @example\n * ```vue\n * <script setup lang=\"ts\">\n * import { useUploadMetrics } from '@uploadista/vue';\n *\n * const uploadMetrics = useUploadMetrics({\n * speedCalculationInterval: 1000, // Update speed every second\n * speedSampleSize: 10, // Keep last 10 speed samples for average\n * onMetricsUpdate: (metrics) => {\n * console.log(`Overall progress: ${metrics.progress}%`);\n * console.log(`Speed: ${(metrics.currentSpeed / 1024).toFixed(1)} KB/s`);\n * console.log(`ETA: ${metrics.estimatedTimeRemaining}ms`);\n * },\n * onFileComplete: (fileMetrics) => {\n * console.log(`${fileMetrics.filename} completed in ${fileMetrics.duration}ms`);\n * },\n * });\n *\n * // Start tracking a file\n * const handleFileStart = (file: File) => {\n * uploadMetrics.startFileUpload(file.name, file.name, file.size);\n * };\n *\n * // Update progress during upload\n * const handleProgress = (fileId: string, bytesUploaded: number) => {\n * uploadMetrics.updateFileProgress(fileId, bytesUploaded);\n * };\n * </script>\n *\n * <template>\n * <div>\n * <div>Overall Progress: {{ uploadMetrics.metrics.progress }}%</div>\n * <div>Speed: {{ (uploadMetrics.metrics.currentSpeed / 1024).toFixed(1) }} KB/s</div>\n * <div>Files: {{ uploadMetrics.metrics.completedFiles }}/{{ uploadMetrics.metrics.totalFiles }}</div>\n *\n * <div v-if=\"uploadMetrics.metrics.estimatedTimeRemaining\">\n * ETA: {{ Math.round(uploadMetrics.metrics.estimatedTimeRemaining / 1000) }}s\n * </div>\n *\n * <div v-for=\"file in uploadMetrics.fileMetrics\" :key=\"file.id\">\n * {{ file.filename }}: {{ file.progress }}% ({{ (file.speed / 1024).toFixed(1) }} KB/s)\n * </div>\n * </div>\n * </template>\n * ```\n */\nexport function useUploadMetrics(options: UseUploadMetricsOptions = {}) {\n const {\n speedCalculationInterval = 1000,\n speedSampleSize = 10,\n onMetricsUpdate,\n onFileStart,\n onFileProgress,\n onFileComplete,\n } = options;\n\n const uploadClient = useUploadistaClient();\n\n const metrics = ref<UploadMetrics>({ ...initialMetrics });\n const fileMetrics = ref<FileUploadMetrics[]>([]);\n\n const speedSamples = ref<Array<{ time: number; bytes: number }>>([]);\n const lastUpdate = ref<number>(0);\n const interval = ref<ReturnType<typeof setInterval> | null>(null);\n\n const calculateSpeed = (currentTime: number, totalBytesUploaded: number) => {\n const sample = { time: currentTime, bytes: totalBytesUploaded };\n speedSamples.value.push(sample);\n\n // Keep only recent samples\n if (speedSamples.value.length > speedSampleSize) {\n speedSamples.value = speedSamples.value.slice(-speedSampleSize);\n }\n\n // Calculate current speed (bytes per second)\n let currentSpeed = 0;\n if (speedSamples.value.length >= 2) {\n const recent = speedSamples.value[speedSamples.value.length - 1];\n const previous = speedSamples.value[speedSamples.value.length - 2];\n if (recent && previous) {\n const timeDiff = (recent.time - previous.time) / 1000; // Convert to seconds\n const bytesDiff = recent.bytes - previous.bytes;\n currentSpeed = timeDiff > 0 ? bytesDiff / timeDiff : 0;\n }\n }\n\n // Calculate average speed\n let averageSpeed = 0;\n if (speedSamples.value.length >= 2) {\n const first = speedSamples.value[0];\n const last = speedSamples.value[speedSamples.value.length - 1];\n if (first && last) {\n const totalTime = (last.time - first.time) / 1000; // Convert to seconds\n const totalBytes = last.bytes - first.bytes;\n averageSpeed = totalTime > 0 ? totalBytes / totalTime : 0;\n }\n }\n\n return { currentSpeed, averageSpeed };\n };\n\n const updateMetrics = () => {\n const now = Date.now();\n\n // Calculate totals from file metrics\n const totalBytes = fileMetrics.value.reduce(\n (sum, file) => sum + file.size,\n 0,\n );\n const totalBytesUploaded = fileMetrics.value.reduce(\n (sum, file) => sum + file.bytesUploaded,\n 0,\n );\n const completedFiles = fileMetrics.value.filter(\n (file) => file.isComplete,\n ).length;\n const activeUploads = fileMetrics.value.filter(\n (file) => !file.isComplete && file.bytesUploaded > 0,\n ).length;\n\n // Calculate speeds\n const { currentSpeed, averageSpeed } = calculateSpeed(\n now,\n totalBytesUploaded,\n );\n\n // Calculate progress\n const progress =\n totalBytes > 0 ? Math.round((totalBytesUploaded / totalBytes) * 100) : 0;\n\n // Calculate estimated time remaining\n let estimatedTimeRemaining: number | null = null;\n if (currentSpeed > 0) {\n const remainingBytes = totalBytes - totalBytesUploaded;\n estimatedTimeRemaining = (remainingBytes / currentSpeed) * 1000; // Convert to milliseconds\n }\n\n // Find start and end times\n const activeTimes = fileMetrics.value.filter((file) => file.startTime > 0);\n const startTime =\n activeTimes.length > 0\n ? Math.min(...activeTimes.map((file) => file.startTime))\n : null;\n\n const completedTimes = fileMetrics.value.filter(\n (file) => file.endTime !== null,\n );\n const endTime =\n completedTimes.length > 0 && completedFiles === fileMetrics.value.length\n ? Math.max(\n ...(completedTimes\n .map((file) => file.endTime)\n .filter((time) => time !== null) as number[]),\n )\n : null;\n\n const totalDuration = startTime && endTime ? endTime - startTime : null;\n\n const newMetrics: UploadMetrics = {\n totalBytesUploaded,\n totalBytes,\n averageSpeed,\n currentSpeed,\n estimatedTimeRemaining,\n totalFiles: fileMetrics.value.length,\n completedFiles,\n activeUploads,\n progress,\n peakSpeed: Math.max(metrics.value.peakSpeed, currentSpeed),\n startTime,\n endTime,\n totalDuration,\n insights: uploadClient.client.getChunkingInsights(),\n sessionMetrics: [uploadClient.client.exportMetrics().session],\n chunkMetrics: uploadClient.client.exportMetrics().chunks,\n };\n\n metrics.value = newMetrics;\n onMetricsUpdate?.(newMetrics);\n };\n\n // Set up periodic speed calculations\n const setupSpeedCalculation = () => {\n if (interval.value) {\n clearInterval(interval.value);\n }\n\n interval.value = setInterval(() => {\n if (\n fileMetrics.value.some(\n (file) => !file.isComplete && file.bytesUploaded > 0,\n )\n ) {\n updateMetrics();\n }\n }, speedCalculationInterval);\n };\n\n const startFileUpload = (id: string, filename: string, size: number) => {\n const now = Date.now();\n\n const fileMetric: FileUploadMetrics = {\n id,\n filename,\n size,\n bytesUploaded: 0,\n progress: 0,\n speed: 0,\n startTime: now,\n endTime: null,\n duration: null,\n isComplete: false,\n };\n\n const existing = fileMetrics.value.find((file) => file.id === id);\n if (existing) {\n fileMetrics.value = fileMetrics.value.map((file) =>\n file.id === id ? fileMetric : file,\n );\n } else {\n fileMetrics.value = [...fileMetrics.value, fileMetric];\n }\n\n onFileStart?.(fileMetric);\n\n // Start speed calculation if this is the first active upload\n if (fileMetrics.value.filter((file) => !file.isComplete).length === 1) {\n setupSpeedCalculation();\n }\n };\n\n const updateFileProgress = (id: string, bytesUploaded: number) => {\n const now = Date.now();\n\n fileMetrics.value = fileMetrics.value.map((file) => {\n if (file.id !== id) return file;\n\n const timeDiff = (now - file.startTime) / 1000; // seconds\n const speed = timeDiff > 0 ? bytesUploaded / timeDiff : 0;\n const progress =\n file.size > 0 ? Math.round((bytesUploaded / file.size) * 100) : 0;\n\n const updatedFile = {\n ...file,\n bytesUploaded,\n progress,\n speed,\n };\n\n onFileProgress?.(updatedFile);\n return updatedFile;\n });\n\n // Trigger metrics update\n setTimeout(updateMetrics, 0);\n };\n\n const completeFileUpload = (id: string) => {\n const now = Date.now();\n\n fileMetrics.value = fileMetrics.value.map((file) => {\n if (file.id !== id) return file;\n\n const duration = now - file.startTime;\n const speed = duration > 0 ? (file.size / duration) * 1000 : 0; // bytes per second\n\n const completedFile = {\n ...file,\n bytesUploaded: file.size,\n progress: 100,\n speed,\n endTime: now,\n duration,\n isComplete: true,\n };\n\n onFileComplete?.(completedFile);\n return completedFile;\n });\n\n // Trigger metrics update\n setTimeout(updateMetrics, 0);\n };\n\n const removeFile = (id: string) => {\n fileMetrics.value = fileMetrics.value.filter((file) => file.id !== id);\n setTimeout(updateMetrics, 0);\n };\n\n const reset = () => {\n if (interval.value) {\n clearInterval(interval.value);\n interval.value = null;\n }\n\n metrics.value = { ...initialMetrics };\n fileMetrics.value = [];\n speedSamples.value = [];\n lastUpdate.value = 0;\n };\n\n const getFileMetrics = (id: string) => {\n return fileMetrics.value.find((file) => file.id === id);\n };\n\n const exportMetrics = () => {\n return {\n overall: metrics.value,\n files: fileMetrics.value,\n exportTime: Date.now(),\n };\n };\n\n // Cleanup on unmount\n onUnmounted(() => {\n if (interval.value) {\n clearInterval(interval.value);\n }\n });\n\n return {\n metrics: readonly(metrics),\n fileMetrics: readonly(fileMetrics),\n startFileUpload,\n updateFileProgress,\n completeFileUpload,\n removeFile,\n reset,\n getFileMetrics,\n exportMetrics,\n };\n}\n"],"mappings":"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"}
|