Skip to content

Commit 438eb74

Browse files
authored
feat: add progress bar to file upload dialog (#982)
* feat: add progress bar to file upload dialog * feat: update progress * feat: don't paste filename into editor * feat: show image after upload a glance
1 parent 903d154 commit 438eb74

File tree

4 files changed

+97
-11
lines changed

4 files changed

+97
-11
lines changed

apps/web/src/components/CodemirrorEditor/UploadImgDialog.vue

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -387,15 +387,45 @@ onChange((files) => {
387387
388388
const file = files[0]
389389
390-
beforeImageUpload(file) && emit(`uploadImage`, file)
390+
beforeImageUpload(file) && emitUploads(file)
391391
reset()
392392
})
393393
394394
function onDrop(e: DragEvent) {
395395
dragover.value = false
396396
e.stopPropagation()
397397
const file = Array.from(e.dataTransfer!.files)[0]
398-
beforeImageUpload(file) && emit(`uploadImage`, file)
398+
beforeImageUpload(file) && emitUploads(file)
399+
}
400+
const progressValue = ref(0)
401+
const imageUrl = ref(``)
402+
function emitUploads(file: File) {
403+
progressValue.value = 0
404+
const intervalId = setInterval(() => {
405+
const newProgress = progressValue.value + 1
406+
if (newProgress >= 100) {
407+
return
408+
}
409+
progressValue.value = newProgress
410+
}, 100)
411+
412+
// 监听上传完成事件,在真正完成后清除定时器和设置100%
413+
const cleanup = (_url: string, data: string) => {
414+
clearInterval(intervalId)
415+
progressValue.value = 100 // 设置完成状态
416+
if (data) {
417+
imageUrl.value = `data:image/png;base64,${data}`
418+
}
419+
// 可选:延迟一段时间后重置进度
420+
setTimeout(() => {
421+
progressValue.value = 0
422+
imageUrl.value = ``
423+
}, 1000)
424+
}
425+
426+
// 假设有一个上传完成的事件可以监听
427+
// 或者需要修改 uploadImage 方法使其返回 Promise
428+
emit(`uploadImage`, file, cleanup, true)
399429
}
400430
</script>
401431

@@ -437,7 +467,7 @@ function onDrop(e: DragEvent) {
437467
</Select>
438468
</Label>
439469
<div
440-
class="bg-clip-padding mt-4 h-50 flex flex-col cursor-pointer items-center justify-evenly border-2 rounded border-dashed transition-colors hover:border-gray-700 hover:bg-gray-400/50 dark:hover:border-gray-200 dark:hover:bg-gray-500/50"
470+
class="bg-clip-padding mt-4 h-50 relative flex flex-col cursor-pointer items-center justify-evenly border-2 rounded border-dashed transition-colors hover:border-gray-700 hover:bg-gray-400/50 dark:hover:border-gray-200 dark:hover:bg-gray-500/50"
441471
:class="{
442472
'border-gray-700 bg-gray-400/50 dark:border-gray-200 dark:bg-gray-500/50': dragover,
443473
}"
@@ -446,11 +476,15 @@ function onDrop(e: DragEvent) {
446476
@dragover.prevent="dragover = true"
447477
@dragleave.prevent="dragover = false"
448478
>
479+
<Progress v-model="progressValue" class="absolute left-0 right-0 rounded-none" style="top: -24px; height: 2px;" />
449480
<UploadCloud class="size-20" />
450481
<p>
451482
将图片拖到此处,或
452483
<strong>点击上传</strong>
453484
</p>
485+
<div v-if="imageUrl" class="absolute left-0 right-0 h-full w-full flex items-center justify-center bg-white dark:bg-black">
486+
<img :src="imageUrl" class="max-h-40 object-contain">
487+
</div>
454488
</div>
455489
</TabsContent>
456490

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<script setup lang="ts">
2+
import type { ProgressRootProps } from 'radix-vue'
3+
import { ProgressIndicator, ProgressRoot, useForwardPropsEmits } from 'radix-vue'
4+
5+
const props = defineProps<ProgressRootProps>()
6+
const emits = defineEmits<{
7+
(e: `update:modelValue`, payload: string | number): void
8+
}>()
9+
10+
const modelValue = useVModel(props, `modelValue`, emits, {
11+
passive: true,
12+
defaultValue: 0,
13+
})
14+
const forwarded = useForwardPropsEmits(props, emits)
15+
</script>
16+
17+
<template>
18+
<ProgressRoot
19+
v-bind="forwarded"
20+
v-model="modelValue"
21+
class="relative overflow-hidden bg-blackA9 rounded-full w-full h-4 sm:h-5"
22+
style="transform: translateZ(0)"
23+
>
24+
<ProgressIndicator
25+
class="bg-primary rounded-full w-full h-full transition-transform duration-[660ms] ease-[cubic-bezier(0.65, 0, 0.35, 1)]"
26+
:style="`transform: translateX(-${100 - (modelValue || 0)}%)`"
27+
/>
28+
</ProgressRoot>
29+
</template>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default as progress } from './Progress.vue'

apps/web/src/views/CodemirrorEditor.vue

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,9 @@ function uploaded(imageUrl: string) {
179179
toast.error(`上传图片未知异常`)
180180
return
181181
}
182-
toggleShowUploadImgDialog(false)
182+
setTimeout(() => {
183+
toggleShowUploadImgDialog(false)
184+
}, 1000)
183185
// 上传成功,获取光标
184186
const cursor = editor.value!.getCursor()
185187
const markdownImage = `![](${imageUrl})`
@@ -192,19 +194,23 @@ const isImgLoading = ref(false)
192194
193195
async function uploadImage(
194196
file: File,
195-
cb?: { (url: any): void, (arg0: unknown): void } | undefined,
197+
cb?: { (url: any, data: string): void, (arg0: unknown): void } | undefined,
198+
applyUrl?: boolean,
196199
) {
197200
try {
198201
isImgLoading.value = true
199202
200203
const base64Content = await toBase64(file)
201204
const url = await fileUpload(base64Content, file)
202205
if (cb) {
203-
cb(url)
206+
cb(url, base64Content)
204207
}
205208
else {
206209
uploaded(url)
207210
}
211+
if (applyUrl) {
212+
return uploaded(url)
213+
}
208214
}
209215
catch (err) {
210216
toast.error((err as any).message)
@@ -329,7 +335,7 @@ function mdLocalToRemote() {
329335
const changeTimer = ref<NodeJS.Timeout>()
330336
331337
const editorRef = useTemplateRef<HTMLTextAreaElement>(`editorRef`)
332-
338+
const progressValue = ref(0)
333339
function createFormTextArea(dom: HTMLTextAreaElement) {
334340
const textArea = fromTextArea(dom, {
335341
mode: `text/x-markdown`,
@@ -359,17 +365,32 @@ function createFormTextArea(dom: HTMLTextAreaElement) {
359365
})
360366
361367
// 粘贴上传图片并插入
362-
textArea.on(`paste`, (_editor, event) => {
368+
textArea.on(`paste`, async (_editor, event) => {
363369
if (!(event.clipboardData?.items) || isImgLoading.value) {
364370
return
365371
}
366-
367372
const items = [...event.clipboardData.items].map(item => item.getAsFile()).filter(item => item != null && beforeUpload(item)) as File[]
368-
373+
// start progress
374+
const intervalId = setInterval(() => {
375+
const newProgress = progressValue.value + 1
376+
if (newProgress >= 100) {
377+
return
378+
}
379+
progressValue.value = newProgress
380+
}, 100)
369381
for (const item of items) {
370-
uploadImage(item)
371382
event.preventDefault()
383+
await uploadImage(item)
384+
}
385+
const cleanup = () => {
386+
clearInterval(intervalId)
387+
progressValue.value = 100 // 设置完成状态
388+
// 可选:延迟一段时间后重置进度
389+
setTimeout(() => {
390+
progressValue.value = 0
391+
}, 1000)
372392
}
393+
cleanup()
373394
})
374395
375396
return textArea
@@ -437,6 +458,7 @@ onUnmounted(() => {
437458

438459
<template>
439460
<div class="container flex flex-col">
461+
<Progress v-model="progressValue" class="absolute left-0 right-0 rounded-none" style="height: 2px;" />
440462
<EditorHeader
441463
@start-copy="startCopy"
442464
@end-copy="endCopy"

0 commit comments

Comments
 (0)