mirror of
https://github.com/ThisIsBenny/wishlist-app.git
synced 2025-06-07 05:57:41 +00:00
allow image file upload for wishlist
Signed-off-by: Benny Samir Hierl <bennysamir@posteo.de>
This commit is contained in:
parent
37817098ae
commit
77d9916517
5 changed files with 156 additions and 22 deletions
106
src/components/InputFile.vue
Normal file
106
src/components/InputFile.vue
Normal file
|
@ -0,0 +1,106 @@
|
|||
<template>
|
||||
<div class="relative mb-8">
|
||||
<label class="mb-1 block w-full" :for="name">{{ label }}</label>
|
||||
<div
|
||||
class="flex h-24 w-full flex-row items-center space-x-10 rounded-md border-2 border-solid border-stone-300 bg-transparent px-2 outline-none dark:border-stone-700"
|
||||
:class="{
|
||||
'border-rose-500': !!errorMessage,
|
||||
'border-dotted bg-stone-200/20': showDropzone,
|
||||
}"
|
||||
@drop.prevent="handleDrop"
|
||||
@dragover.prevent="showDropzone = true"
|
||||
@dragleave.prevent="showDropzone = false"
|
||||
>
|
||||
<div class="h-20 w-20">
|
||||
<ImagePreview :src="value" class="h-full w-full" />
|
||||
</div>
|
||||
<div>
|
||||
<i18n-t
|
||||
keypath="components.wishlist-header.main.form.image-file.text-dropzone"
|
||||
tag="p"
|
||||
for="components.wishlist-header.main.form.image-file.text-dropzone-link"
|
||||
>
|
||||
<button
|
||||
class="cursor-pointer text-cyan-600"
|
||||
@click.prevent="fileInput.click()"
|
||||
>
|
||||
{{
|
||||
t(
|
||||
'components.wishlist-header.main.form.image-file.text-dropzone-link'
|
||||
)
|
||||
}}
|
||||
</button>
|
||||
</i18n-t>
|
||||
<input
|
||||
ref="fileInput"
|
||||
class="hidden"
|
||||
:name="name"
|
||||
:id="name"
|
||||
type="file"
|
||||
@change="handleChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<p class="absolute mt-2 text-sm text-rose-500" v-show="errorMessage">
|
||||
{{ errorMessage }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useField } from 'vee-validate'
|
||||
import { ImagePreview } from './'
|
||||
import { ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
type FileEventTarget = EventTarget & { files: FileList }
|
||||
const fileInput = ref()
|
||||
const showDropzone = ref(false)
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const props = defineProps({
|
||||
accept: {
|
||||
type: String,
|
||||
default: 'image/*',
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
|
||||
const { value, errorMessage } = useField(props.name, undefined, {})
|
||||
|
||||
const convertBase64 = (file: File): Promise<string> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const fileReader = new FileReader()
|
||||
fileReader.readAsDataURL(file)
|
||||
|
||||
fileReader.onload = () => {
|
||||
resolve(fileReader.result as string)
|
||||
}
|
||||
|
||||
fileReader.onerror = (error) => {
|
||||
reject(error)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handleChange = async (event: any) => {
|
||||
const file = (event.target as FileEventTarget).files[0]
|
||||
value.value = await convertBase64(file)
|
||||
}
|
||||
const handleDrop = async (event: any) => {
|
||||
let droppedFiles = event.dataTransfer.files
|
||||
if (!droppedFiles) return
|
||||
value.value = await convertBase64(droppedFiles[0])
|
||||
}
|
||||
</script>
|
|
@ -43,6 +43,10 @@
|
|||
:value="modelValue.imageSrc"
|
||||
:label="t('components.wishlist-header.main.form.image-src.label')"
|
||||
/>
|
||||
<InputFile
|
||||
name="imageFile"
|
||||
:label="t('components.wishlist-header.main.form.image-file.label')"
|
||||
/>
|
||||
<InputText
|
||||
name="slugUrlText"
|
||||
type="text"
|
||||
|
@ -72,6 +76,7 @@ import {
|
|||
ButtonBase,
|
||||
ImageTile,
|
||||
InputText,
|
||||
InputFile,
|
||||
InputCheckbox,
|
||||
InputTextArea,
|
||||
} from '@/components'
|
||||
|
@ -93,7 +98,8 @@ const { update } = useWishlistStore()
|
|||
|
||||
const { t } = useI18n()
|
||||
|
||||
const schema = object({
|
||||
const schema = object().shape(
|
||||
{
|
||||
title: string().required(
|
||||
t('components.wishlist-header.main.form.title.error-requried')
|
||||
),
|
||||
|
@ -105,15 +111,26 @@ const schema = object({
|
|||
slugUrlText: string().required(
|
||||
t('components.wishlist-header.main.form.slug-text.error-requried')
|
||||
),
|
||||
imageSrc: string()
|
||||
.required(
|
||||
imageSrc: string().when('imageFile', {
|
||||
is: (imageFile: string) => !imageFile || imageFile.length === 0,
|
||||
then: string().required(
|
||||
t('components.wishlist-header.main.form.image-src.error-requried')
|
||||
)
|
||||
.url(t('components.wishlist-header.main.form.image-src.error-url')),
|
||||
})
|
||||
),
|
||||
}),
|
||||
imageFile: string().when('imageSrc', {
|
||||
is: (imageSrc: string) => !imageSrc || imageSrc.length === 0,
|
||||
then: string().required(
|
||||
t('components.wishlist-header.main.form.image-file.error-requried')
|
||||
),
|
||||
}),
|
||||
},
|
||||
//@ts-expect-error ...
|
||||
['imageSrc', 'imageFile']
|
||||
)
|
||||
|
||||
const onSubmit = async (values: any): Promise<void> => {
|
||||
try {
|
||||
values.imageSrc = values.imageFile || values.imageSrc
|
||||
await update(values)
|
||||
toast.success(t('common.wishlist.saved.text'))
|
||||
router.push(`/${values.slugUrlText}`)
|
||||
|
|
|
@ -4,6 +4,7 @@ export { default as ImagePreview } from './ImagePreview.vue'
|
|||
export { default as ImageTile } from './ImageTile.vue'
|
||||
export { default as InputCheckbox } from './InputCheckbox.vue'
|
||||
export { default as InputText } from './InputText.vue'
|
||||
export { default as InputFile } from './InputFile.vue'
|
||||
export { default as InputTextArea } from './InputTextArea.vue'
|
||||
export { default as Modal } from './Modal.vue'
|
||||
export { default as WishlistHeader } from './WishlistHeader.vue'
|
||||
|
|
|
@ -98,8 +98,13 @@
|
|||
},
|
||||
"image-src": {
|
||||
"label": "Bild-URL",
|
||||
"error-requried": "Bild-URL wird benötigt",
|
||||
"error-url": "Bild-URL muss eine gültige URL sein"
|
||||
"error-requried": "Bild-URL wird benötigt"
|
||||
},
|
||||
"image-file": {
|
||||
"label": "Bild-Datei",
|
||||
"text-dropzone-link": "hier",
|
||||
"text-dropzone": "Ziehen Sie ein beliebiges Bild hierher oder klicken Sie {0}, um den Dialog zu öffnen.",
|
||||
"error-requried": "Bild-Datei wird benötigt"
|
||||
},
|
||||
"submit": {
|
||||
"text": "Speichern"
|
||||
|
|
|
@ -96,8 +96,13 @@
|
|||
},
|
||||
"image-src": {
|
||||
"label": "Image-URL",
|
||||
"error-requried": "Image-URL is required",
|
||||
"error-url": "Image-URL has to be a valid url"
|
||||
"error-requried": "Image-URL is required"
|
||||
},
|
||||
"image-file": {
|
||||
"label": "Image-File",
|
||||
"text-dropzone-link": "click here",
|
||||
"text-dropzone": "drag and drop any image here or {0} to open dialog.",
|
||||
"error-requried": "Image-File is required"
|
||||
},
|
||||
"submit": {
|
||||
"text": "Save"
|
||||
|
|
Loading…
Add table
Reference in a new issue