source: uz/src/UploadZone.vue@ d45005e

Last change on this file since d45005e was d45005e, checked in by js29a <js29a@…>, 2 years ago

events

  • Property mode set to 100644
File size: 9.0 KB
RevLine 
[54176ee]1<script setup lang="ts">
2
[64249d6]3import { ref, computed, defineProps, withDefaults, defineEmits, getCurrentInstance, defineExpose, watch, nextTick } from 'vue'
[54176ee]4
5import $ from 'jQuery'
6
7interface Props {
8 target: string
9 tag?: string
10 maxJobs?: number
11 autoStart?: boolean | string
12 autoReset?: boolean | string
13 keepGoing?: boolean | string
14}
15
16const props = withDefaults(defineProps<Props>(), {
17 maxJobs: 3,
18 autoStart: false,
19 autoReset: false,
20 keepGoing: false,
21 tag: 'div'
22})
23
[d45005e]24enum State {
25 s_idle = 'idle',
26 s_hover = 'hover',
27 s_wait = 'wait',
28 s_uploading = 'uploading',
29 s_done = 'done',
30 s_error = 'error',
31 s_with_errors = 'with_errors',
32 s_aborted = 'aborted'
33}
34
[54176ee]35const emit = defineEmits<{
[64249d6]36 (e: 'update:canReset', flag: boolean): void
37 (e: 'update:canPick', flag: boolean): void
38 (e: 'update:canStart', flag: boolean): void
39 (e: 'update:canAbort', flag: boolean): void
[d45005e]40 (e: 'update:state', new_state: State): void
[54176ee]41
[d45005e]42 (e: 'files:add', evt: any, files: any[]): void
43 (e: 'files:clear'): void
[54176ee]44
[d45005e]45 (e: 'drag:over', evt: any): void
46 (e: 'drag:out', evt: any): void
47
48 (e: 'upload:start', queue: any[]): void
49 (e: 'upload:progress', progress: any[], current: any[], queue: any[]): void
50
51 (e: 'upload:done', result: any[], errors: any[]): void
52 (e: 'upload:error', error: any): void
53 (e: 'upload:aborted', progress: any[], current: any[], queue: any[], errors: any[]): void
54
55 (e: 'debug', ... args: any[]): void
56}>()
57
58const do_emit = (code: string, ... args: any[]) => {
59 // @ts-ignore
60 emit.apply(null, [code].concat(args))
61 emit.apply(null, ['debug', code, args])
[54176ee]62}
63
[d45005e]64const inst = getCurrentInstance()
65
66const state = ref(State.s_idle)
[54176ee]67
68const queue = ref([] as any[])
69const progress = ref({} as any)
70const results = ref([] as any[])
71const current = ref([] as any[])
72const error = ref({} as any)
73
74const errors = ref([] as any)
75
76let files: any[] = []
77
78let cur_jobs = 0
79let cur_file = 0
80
81let aborted = false
82
83const extract_files = (evt: any): any[] => {
84 const files = []
85
86 if(evt.dataTransfer && evt.dataTransfer.items !== undefined) {
87 for(const item of evt.dataTransfer.items)
88 if(item.kind == 'file')
89 files.push(item.getAsFile())
90 }
91 else
92 if(evt.dataTransfer)
93 for(const file of evt.dataTransfer.files)
94 files.push(file)
[d45005e]95 else
[54176ee]96 for(const file of evt.target.files)
97 files.push(file)
98
99 return files
100}
101
102const drop = (evt: any): void => {
[d45005e]103 if(state.value != State.s_hover && state.value != State.s_idle && state.value != State.s_wait)
[54176ee]104 return
105
[d45005e]106 state.value = State.s_wait
[54176ee]107 files = files.concat(extract_files(evt))
[d45005e]108 do_emit('files:add', [evt, files])
[54176ee]109 queue.value = files
110
111 if(props.autoStart || props.autoStart === '')
112 start_cb()
113}
114
115const drag_over = (evt: any): void => {
[d45005e]116 if(state.value != State.s_idle)
[54176ee]117 return
118
[d45005e]119 state.value = State.s_hover
120
121 do_emit('drag:over', evt)
[54176ee]122}
123
124const drag_out = (evt: any): void => {
[d45005e]125 if(state.value != State.s_hover)
[54176ee]126 return
127
[d45005e]128 state.value = State.s_idle
129
130 do_emit('drag:out', evt)
[54176ee]131}
132
133const start_cb = (): void => {
[d45005e]134 if(state.value != State.s_wait)
[54176ee]135 return
136
[d45005e]137 do_emit('upload:start', [files])
138
[54176ee]139 aborted = false
140
[d45005e]141 state.value = State.s_uploading
[54176ee]142
143 results.value = []
144 progress.value = []
145 current.value = []
146 errors.value = []
147
148 error.value = {}
149 cur_file = 0
150 cur_jobs = 0
151
152 while(cur_jobs < props.maxJobs && cur_file < files.length)
153 start_job()
154}
155
156const reset_cb = (): void => {
[d45005e]157 if(state.value == State.s_idle)
[64249d6]158 return
159
[d45005e]160 state.value = State.s_idle
[54176ee]161 files = []
[d45005e]162 do_emit('files:clear', [])
[54176ee]163}
164
165const pick_cb = (): void => {
[d45005e]166 if(state.value != State.s_idle && state.value != State.s_wait)
[54176ee]167 return
168
169 const inp = document.createElement('input')
170 inp.setAttribute('type', 'file')
171 inp.setAttribute('multiple', 'true')
172 inp.addEventListener('input', (evt: any): void => {
173 drop(evt)
174 })
[d45005e]175
[54176ee]176 inp.click()
177}
178
179const abort_cb = (): void => {
[d45005e]180 if(state.value != State.s_uploading)
[54176ee]181 return
182
183 aborted = true
184}
185
186const start_job = () => {
187 cur_jobs += 1
188
189 const fd = new FormData()
190 const cur = files[cur_file]
191 fd.append('file', files[cur_file])
192
193 current.value.push({
194 file: files[cur_file],
195 current: 0,
196 total: files[cur_file].size
197 })
198
[64249d6]199 queue.value = queue.value.filter((file) => {
[54176ee]200 return file !== cur
201 })
202
[d45005e]203 do_emit('upload:progress', progress.value, current.value, queue.value)
204
[54176ee]205 cur_file += 1
206
207 $.ajax({
208 url: props.target,
209 type: 'POST',
210 data: fd,
211 contentType: false,
212 processData: false,
213 xhr: (): any => {
214 const xhr = new window.XMLHttpRequest()
215
216 xhr.upload.addEventListener('progress', (evt): void => {
[64249d6]217 current.value.forEach((item) => {
[54176ee]218 if(item.file === cur) {
219 item.current = evt.loaded
220 item.total = evt.total
221 }
222 })
[d45005e]223 do_emit('upload:progress', progress.value, current.value, queue.value)
[54176ee]224 })
225
226 return xhr
227 }
228 })
229 .then((res: any) => {
[64249d6]230 current.value = current.value.filter((item) => {
[54176ee]231 return item.file !== cur
232 })
233
[d45005e]234 do_emit('upload:progress', progress.value, current.value, queue.value)
235
[54176ee]236 progress.value.push(res)
237 results.value.push(res)
238
239 cur_jobs -= 1
240
241 if(aborted) {
242 if(props.autoReset || props.autoReset === '') {
[d45005e]243 state.value = State.s_idle
244 files = []
245 do_emit('files:clear', [])
[54176ee]246 }
247 else
[d45005e]248 if(state.value != State.s_aborted) {
249 state.value = State.s_aborted
250 do_emit('upload:aborted', progress.value, current.value, queue.value, errors.value)
251 }
[54176ee]252
253 return
254 }
255
256 if(cur_file == files.length && !cur_jobs) {
[d45005e]257 files = []
258 do_emit('files:clear', [])
[54176ee]259
[64249d6]260 if(errors.value.length)
[d45005e]261 if(props.keepGoing || props.keepGoing === '') {
262 state.value = State.s_with_errors
263 do_emit('upload:done', results.value, errors.value)
264 }
[54176ee]265 else
[d45005e]266 state.value = State.s_error
[54176ee]267 else
268 if(props.autoReset || props.autoReset === '')
[d45005e]269 state.value = State.s_idle
270 else {
271 state.value = State.s_done
272 do_emit('upload:done', results.value, errors.value)
273 }
[54176ee]274
275 return
276 }
277
278 while(cur_jobs < props.maxJobs && cur_file < files.length)
279 start_job()
280 })
281 .catch((err: any) => {
282 if(props.keepGoing || props.keepGoing === '') {
[64249d6]283 current.value = current.value.filter((item) => {
[54176ee]284 return item.file !== cur
285 })
286
287 errors.value.push({
288 error: err,
289 file: cur
290 })
291
[d45005e]292 do_emit('upload:error', err)
293
[54176ee]294 cur_jobs -= 1
295
296 if(cur_file == files.length && !cur_jobs) {
[d45005e]297 state.value = State.s_with_errors
298 do_emit('upload:done', results.value, errors.value)
[54176ee]299 return
300 }
301
302 while(cur_jobs < props.maxJobs && cur_file < files.length)
303 start_job()
304 }
305 else {
[d45005e]306 state.value = State.s_error
[54176ee]307 error.value = err
[d45005e]308 do_emit('upload:error', err)
[54176ee]309 }
310 })
311}
312
[64249d6]313const can_pick = computed({
314 get: () => {
[d45005e]315 return state.value == State.s_idle || state.value == State.s_wait
[64249d6]316 },
317 set: () => {
318 }
319})
320
321const can_start = computed({
322 get: () => {
[d45005e]323 return state.value == State.s_wait
[64249d6]324 },
325 set: () => {
326 }
327})
328
329const can_abort = computed({
330 get: () => {
[d45005e]331 return state.value == State.s_uploading
[64249d6]332 },
333 set: () => {
334 }
335})
336
337const can_reset = computed({
338 get: () => {
[d45005e]339 return state.value != State.s_idle
[64249d6]340 },
341 set: () => {
342 }
343})
344
[d45005e]345const on_state_changed = (): void => {
346 do_emit('update:canPick', can_pick.value)
347 do_emit('update:canStart', can_start.value)
348 do_emit('update:canAbort', can_abort.value)
349 do_emit('update:canReset', can_reset.value)
350
351 do_emit('update:state', state.value)
[64249d6]352}
353
[d45005e]354watch(() => { return state.value }, (t: State, f: State) => { nextTick(on_state_changed) } )
[64249d6]355
[d45005e]356nextTick(on_state_changed)
[64249d6]357
[54176ee]358defineExpose({
359 reset: reset_cb,
360 start: start_cb,
361 pick: pick_cb,
[64249d6]362 abort: abort_cb,
363
364 can_pick,
365 can_start,
366 can_abort,
367 can_reset
[54176ee]368})
369
370</script>
371
372<template bindings="">
373 <component is='props.tag' @drop.prevent='drop' @dragover.prevent='drag_over' @dragleave.prevent='drag_out'>
[d45005e]374 <slot name='idle' v-if='state == "idle"' v-bind:pick='pick_cb' />
375 <slot name='hover' v-if='state == "hover"' />
376 <slot name='wait' v-if='state == "wait"' v-bind:start='start_cb'
[54176ee]377 v-bind:queue='queue' v-bind:reset='reset_cb' />
[d45005e]378 <slot name='uploading' v-if='state == "uploading"'
[54176ee]379 v-bind:progress='progress'
380 v-bind:current='current'
381 v-bind:queue='queue'
382 v-bind:errors='errors'
383 v-bind:abort='abort_cb' />
[d45005e]384 <slot name='done' v-if='state == "done"' v-bind:result='results' v-bind:reset='reset_cb' />
385 <slot name='error' v-if='state == "error"' v-bind:error='error' v-bind:reset='reset_cb' />
386 <slot name='aborted' v-if='state == "aborted"' v-bind:result='results' v-bind:reset='reset_cb' />
387 <slot name='with-errors' v-if='state == "with_errors"' v-bind:result='results' v-bind:errors='errors'
[54176ee]388 v-bind:reset='reset_cb' />
389 </component>
390</template>
391
Note: See TracBrowser for help on using the repository browser.