refactor(settings): replace file input with browser-fs-access

zio/stable
三咲智子 2023-01-02 04:47:46 +08:00
parent 645da2f945
commit 23c7e68755
No known key found for this signature in database
GPG Key ID: 69992F2250DFD93E
2 changed files with 38 additions and 56 deletions

View File

@ -1,6 +1,9 @@
<script lang="ts" setup> <script lang="ts" setup>
import { fileOpen } from 'browser-fs-access'
import type { FileWithHandle } from 'browser-fs-access'
const props = withDefaults(defineProps<{ const props = withDefaults(defineProps<{
modelValue?: File modelValue?: FileWithHandle
/** The image src before change */ /** The image src before change */
original?: string original?: string
/** Allowed file types */ /** Allowed file types */
@ -15,70 +18,56 @@ const props = withDefaults(defineProps<{
allowedFileTypes: () => ['image/jpeg', 'image/png'], allowedFileTypes: () => ['image/jpeg', 'image/png'],
allowedFileSize: 1024 * 1024 * 5, // 5 MB allowedFileSize: 1024 * 1024 * 5, // 5 MB
}) })
const emits = defineEmits<{ const emit = defineEmits<{
(event: 'update:modelValue', value: File): void (event: 'update:modelValue', value: FileWithHandle): void
(event: 'pick', value: FileWithHandle): void
(event: 'error', code: number, message: string): void (event: 'error', code: number, message: string): void
}>() }>()
const vmFile = useVModel(props, 'modelValue', emits, { passive: true }) const file = useVModel(props, 'modelValue', emit, { passive: true })
const { t } = useI18n() const { t } = useI18n()
const elInput = ref<HTMLInputElement>()
function clearInput() {
if (elInput.value)
elInput.value.value = ''
}
function selectImage(e: Event) {
const target = e.target as HTMLInputElement
const image = target.files?.[0]
if (!image) {
vmFile.value = image
}
else if (!props.allowedFileTypes.includes(image.type)) {
emits('error', 1, t('error.unsupported_file_format'))
clearInput()
}
else if (image.size > props.allowedFileSize) {
emits('error', 2, t('error.file_size_cannot_exceed_n_mb', [5]))
clearInput()
}
else {
vmFile.value = image
clearInput()
}
}
const defaultImage = computed(() => props.original || '') const defaultImage = computed(() => props.original || '')
/** Preview of selected images */ /** Preview of selected images */
const previewImage = ref('') const previewImage = ref('')
/** The current images on display */ /** The current images on display */
const imageSrc = computed<string>(() => previewImage.value || defaultImage.value) const imageSrc = computed<string>(() => previewImage.value || defaultImage.value)
// Update the preview image when the input file change const pickImage = async () => {
watch(vmFile, (image, _, onCleanup) => { 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', [5]))
return
}
file.value = image
emit('pick', file.value)
}
watch(file, (image, _, onCleanup) => {
let expired = false let expired = false
onCleanup(() => expired = true) onCleanup(() => expired = true)
if (image) { if (!image) {
previewImage.value = ''
return
}
const reader = new FileReader() const reader = new FileReader()
reader.readAsDataURL(image) reader.readAsDataURL(image)
reader.onload = (e) => { reader.onload = (evt) => {
if (expired) if (expired)
return return
previewImage.value = e.target?.result as string previewImage.value = evt.target?.result as string
} }
}
else {
previewImage.value = ''
clearInput()
}
})
defineExpose({
clearInput,
}) })
</script> </script>
@ -89,6 +78,7 @@ defineExpose({
flex justify-center items-center flex justify-center items-center
cursor-pointer cursor-pointer
of-hidden of-hidden
@click="pickImage"
> >
<img <img
v-if="imageSrc" v-if="imageSrc"
@ -110,12 +100,5 @@ defineExpose({
> >
<div class="i-ri:loader-4-line animate-spin animate-duration-[2.5s]" text-4xl /> <div class="i-ri:loader-4-line animate-spin animate-duration-[2.5s]" text-4xl />
</div> </div>
<input
ref="elInput"
type="file"
absolute opacity-0 inset-0 z--1
:accept="allowedFileTypes.join(',')"
@change="selectImage"
>
</label> </label>
</template> </template>

View File

@ -70,7 +70,6 @@ const { submit, submitting } = submitter(async ({ dirtyFields }) => {
<div of-hidden bg="gray-500/20" aspect="3"> <div of-hidden bg="gray-500/20" aspect="3">
<CommonInputImage <CommonInputImage
v-if="isHydrated" v-if="isHydrated"
ref="elInputImage"
v-model="form.header" v-model="form.header"
:original="onlineSrc.header" :original="onlineSrc.header"
w-full h-full w-full h-full