source: uz/src/UploadZone.vue@ 4b96189

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

project files have been added

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