feat: Handle uploading multiple images in AppButtonUpload if !props.post

This commit is contained in:
SkepticMystic 2025-06-28 19:34:14 +00:00
commit 86d146c762
3 changed files with 63 additions and 35 deletions

View file

@ -1,10 +1,11 @@
<template> <template>
<v-form ref="file"> <v-form ref="files">
<input <input
ref="uploader" ref="uploader"
class="d-none" class="d-none"
type="file" type="file"
:accept="accept" :accept="accept"
:multiple="multiple"
@change="onFileChanged" @change="onFileChanged"
> >
<slot v-bind="{ isSelecting, onButtonClick }"> <slot v-bind="{ isSelecting, onButtonClick }">
@ -72,9 +73,13 @@ export default defineNuxtComponent({
type: Boolean, type: Boolean,
default: false, default: false,
}, },
multiple: {
type: Boolean,
default: false,
},
}, },
setup(props, context) { setup(props, context) {
const file = ref<File | null>(null); const files = ref<File[]>([]);
const uploader = ref<HTMLInputElement | null>(null); const uploader = ref<HTMLInputElement | null>(null);
const isSelecting = ref(false); const isSelecting = ref(false);
@ -86,17 +91,33 @@ export default defineNuxtComponent({
const api = useUserApi(); const api = useUserApi();
async function upload() { async function upload() {
if (file.value != null) { if (files.value.length === 0) {
return;
}
isSelecting.value = true; isSelecting.value = true;
if (!props.post) { if (!props.post) {
context.emit(UPLOAD_EVENT, file.value); // NOTE: To preserve behaviour for other parents of this component,
// we emit a single File if !props.multiple.
context.emit(UPLOAD_EVENT, props.multiple ? files.value : files.value[0]);
isSelecting.value = false; isSelecting.value = false;
return; return;
} }
// WARN: My change is only for !props.post.
// I have not added support for multiple files in the API.
// Existing call-sites never passed the `multiple` prop,
// so this case will only be hit if the prop is set to true.
if (props.multiple && files.value.length > 1) {
console.warn("Multiple file uploads are not supported by the API.");
return;
}
const file = files.value[0];
const formData = new FormData(); const formData = new FormData();
formData.append(props.fileName, file.value); formData.append(props.fileName, file);
try { try {
const response = await api.upload.file(props.url, formData); const response = await api.upload.file(props.url, formData);
if (response) { if (response) {
@ -107,14 +128,15 @@ export default defineNuxtComponent({
console.error(e); console.error(e);
context.emit(UPLOAD_EVENT, null); context.emit(UPLOAD_EVENT, null);
} }
isSelecting.value = false; isSelecting.value = false;
} }
}
function onFileChanged(e: Event) { function onFileChanged(e: Event) {
const target = e.target as HTMLInputElement; const target = e.target as HTMLInputElement;
if (target.files !== null && target.files.length > 0 && file.value !== null) {
file.value = target.files[0]; if (target.files !== null && target.files.length > 0) {
files.value = Array.from(target.files);
upload(); upload();
} }
} }
@ -132,7 +154,7 @@ export default defineNuxtComponent({
} }
return { return {
file, files,
uploader, uploader,
isSelecting, isSelecting,
effIcon, effIcon,

View file

@ -662,7 +662,9 @@
}, },
"reset-servings-count": "Reset Servings Count", "reset-servings-count": "Reset Servings Count",
"not-linked-ingredients": "Additional Ingredients", "not-linked-ingredients": "Additional Ingredients",
"upload-another-image": "Upload another image" "upload-another-image": "Upload another image",
"upload-images": "Upload images",
"upload-more-images": "Upload more images"
}, },
"recipe-finder": { "recipe-finder": {
"recipe-finder": "Recipe Finder", "recipe-finder": "Recipe Finder",

View file

@ -15,10 +15,11 @@
url="none" url="none"
file-name="images" file-name="images"
accept="image/*" accept="image/*"
:text="uploadedImages.length ? $t('recipe.upload-another-image') : $t('recipe.upload-image')" :text="uploadedImages.length ? $t('recipe.upload-more-images') : $t('recipe.upload-images')"
:text-btn="false" :text-btn="false"
:post="false" :post="false"
@uploaded="uploadImage" :multiple="true"
@uploaded="uploadImages"
/> />
</v-col> </v-col>
<v-spacer /> <v-spacer />
@ -110,10 +111,13 @@ export default defineNuxtComponent({
const uploadedImagesPreviewUrls = ref<string[]>([]); const uploadedImagesPreviewUrls = ref<string[]>([]);
const shouldTranslate = ref(true); const shouldTranslate = ref(true);
function uploadImage(fileObject: File) { function uploadImages(files: File[]) {
uploadedImages.value = [...uploadedImages.value, fileObject]; uploadedImages.value = [...uploadedImages.value, ...files];
uploadedImageNames.value = [...uploadedImageNames.value, fileObject.name]; uploadedImageNames.value = [...uploadedImageNames.value, ...files.map(file => file.name)];
uploadedImagesPreviewUrls.value = [...uploadedImagesPreviewUrls.value, URL.createObjectURL(fileObject)]; uploadedImagesPreviewUrls.value = [
...uploadedImagesPreviewUrls.value,
...files.map(file => URL.createObjectURL(file)),
];
} }
function clearImage(index: number) { function clearImage(index: number) {
@ -151,7 +155,7 @@ export default defineNuxtComponent({
uploadedImages, uploadedImages,
uploadedImagesPreviewUrls, uploadedImagesPreviewUrls,
shouldTranslate, shouldTranslate,
uploadImage, uploadImages,
clearImage, clearImage,
createRecipe, createRecipe,
updateUploadedImage, updateUploadedImage,