source: uz/src/UploadZone.vue@ 64249d6

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

UZ upgrade

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