elk/components/common/CommonInputImage.vue
2023-01-09 22:27:03 +02:00

104 lines
2.5 KiB
Vue

<script lang="ts" setup>
import { fileOpen } from 'browser-fs-access'
import type { FileWithHandle } from 'browser-fs-access'
const props = withDefaults(defineProps<{
/** The image src before change */
original?: string
/** Allowed file types */
allowedFileTypes?: string[]
/** Allowed file size */
allowedFileSize?: number
imgClass?: string
loading?: boolean
}>(), {
allowedFileTypes: () => ['image/jpeg', 'image/png'],
allowedFileSize: 1024 * 1024 * 5, // 5 MB
})
const emit = defineEmits<{
(event: 'pick', value: FileWithHandle): void
(event: 'error', code: number, message: string): void
}>()
const { modelValue: file } = defineModel<{
modelValue: FileWithHandle | null
}>()
const { $t } = useFluent()
const defaultImage = computed(() => props.original || '')
/** Preview of selected images */
const previewImage = ref('')
/** The current images on display */
const imageSrc = computed<string>(() => previewImage.value || defaultImage.value)
const pickImage = async () => {
const image = await fileOpen({
description: 'Image',
mimeTypes: props.allowedFileTypes,
})
if (!props.allowedFileTypes.includes(image.type)) {
emit('error', 1, $t('error_unsupported_file_format'))
return
}
else if (image.size > props.allowedFileSize) {
emit('error', 2, $t('error_file_size_cannot_exceed_n_mb', { size: props.allowedFileSize / 1024 / 1024 }))
return
}
file.value = image
emit('pick', file.value)
}
watch(file, (image, _, onCleanup) => {
let expired = false
onCleanup(() => expired = true)
if (!image) {
previewImage.value = ''
return
}
const reader = new FileReader()
reader.readAsDataURL(image)
reader.onload = (evt) => {
if (expired)
return
previewImage.value = evt.target?.result as string
}
})
</script>
<template>
<label
class="bg-slate-500/10 focus-within:(outline outline-primary)"
relative
flex justify-center items-center
cursor-pointer
of-hidden
@click="pickImage"
>
<img
v-if="imageSrc"
:src="imageSrc"
:class="imgClass || ''"
object-cover
w-full
h-full
>
<div absolute bg="black/50" text-white rounded-full text-xl w12 h12 flex justify-center items-center hover="bg-black/40 text-primary">
<div i-ri:upload-line />
</div>
<div
v-if="loading"
absolute inset-0
bg="black/30" text-white
flex justify-center items-center
>
<div class="i-ri:loader-4-line animate-spin animate-duration-[2.5s]" text-4xl />
</div>
</label>
</template>