You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
docuseal/app/javascript/submission_form/dropzone.vue

193 lines
4.7 KiB

<template>
<div
id="dropzone"
class="flex h-32 w-full"
@dragover.prevent
@drop.prevent="onDropFiles"
>
<label
:for="inputId"
class="w-full relative bg-base-300 hover:bg-base-200 rounded-md border border-base-content border-dashed file-dropzone"
:class="{ 'opacity-50': isLoading }"
>
<div class="absolute top-0 right-0 left-0 bottom-0 flex items-center justify-center">
<div class="flex flex-col items-center">
<IconInnerShadowTop
v-if="isLoading"
class="animate-spin"
:width="30"
:height="30"
/>
<IconCloudUpload
v-else
:width="30"
:height="30"
/>
<div
v-if="message"
class="font-medium mb-1"
>
{{ message }}
</div>
<div class="text-xs">
<span class="font-medium">{{ t('click_to_upload') }}</span> {{ t('or_drag_and_drop_files') }}
</div>
</div>
</div>
<input
:id="inputId"
ref="input"
:multiple="multiple"
:accept="accept"
type="file"
class="hidden"
@change="onSelectFiles"
>
</label>
</div>
</template>
<script>
import { IconCloudUpload, IconInnerShadowTop } from '@tabler/icons-vue'
export default {
name: 'FileDropzone',
components: {
IconCloudUpload,
IconInnerShadowTop
},
inject: ['baseUrl', 't'],
props: {
message: {
type: String,
required: true
},
submitterSlug: {
type: String,
required: true
},
dryRun: {
type: Boolean,
required: false,
default: false
},
accept: {
type: String,
required: false,
default: '*/*'
},
multiple: {
type: Boolean,
required: false,
default: false
}
},
emits: ['upload'],
data () {
return {
isLoading: false
}
},
computed: {
inputId () {
return 'el' + Math.random().toString(32).split('.')[1]
}
},
methods: {
onDropFiles (e) {
const files = Array.from(e.dataTransfer.files).filter((f) => {
if (this.accept === 'image/*') {
return f.type.startsWith('image')
} else {
return true
}
})
if (this.accept === 'image/*' && !files.length) {
alert(this.t('please_upload_an_image_file'))
} else {
this.uploadFiles(files)
}
},
onSelectFiles (e) {
e.preventDefault()
this.uploadFiles(this.$refs.input.files).then(() => {
if (this.$refs.input) {
this.$refs.input.value = ''
}
})
},
async uploadFiles (files) {
this.isLoading = true
return await Promise.all(
Array.from(files).map(async (file) => {
const formData = new FormData()
if (this.dryRun) {
return new Promise((resolve) => {
const reader = new FileReader()
reader.readAsDataURL(file)
reader.onloadend = () => {
resolve({
url: reader.result,
uuid: Math.random().toString(),
filename: file.name
})
}
})
} else {
if (file.type === 'image/bmp' || file.type === 'image/vnd.microsoft.icon') {
file = await this.convertBmpToPng(file)
}
formData.append('file', file)
formData.append('submitter_slug', this.submitterSlug)
formData.append('name', 'attachments')
return fetch(this.baseUrl + '/api/attachments', {
method: 'POST',
body: formData
}).then(resp => resp.json()).then((data) => {
return data
})
}
})).then((result) => {
this.$emit('upload', result)
}).finally(() => {
this.isLoading = false
})
},
convertBmpToPng (bmpFile) {
return new Promise((resolve, reject) => {
const reader = new FileReader()
reader.onload = function (event) {
const img = new Image()
img.onload = function () {
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
canvas.width = img.width
canvas.height = img.height
ctx.drawImage(img, 0, 0)
canvas.toBlob(function (blob) {
const newFile = new File([blob], bmpFile.name.replace(/\.\w+$/, '.png'), { type: 'image/png' })
resolve(newFile)
}, 'image/png')
}
img.src = event.target.result
}
reader.onerror = reject
reader.readAsDataURL(bmpFile)
})
}
}
}
</script>