diff --git a/src/components/ImageUpload.vue b/src/components/ImageUpload.vue index 3329ea003c58014e0f1fd4d053b3c1741027d20f..5f0a996c2f788f4c0e1a3239fce3428da11f983a 100644 --- a/src/components/ImageUpload.vue +++ b/src/components/ImageUpload.vue @@ -1,46 +1,5 @@ <template> <div> - <div class="m-2"> - Click on the image's preview to remove the entry from uploading - </div> - <div - v-for="(image, id) in imageList" - :key="image.url" - class="flex flex-row" - > - <img - :src="image.url" - @click="removeImage(id)" - alt="" - class="max-w-xs m-4" - /> - <form :id="'image-update-form-' + id" class="flex flex-col"> - <input - type="text" - placeholder="Source" - name="source" - class="inputBox inputPayload" - /> - <input - type="text" - placeholder="Parent" - name="parent" - class="inputBox inputPayload" - /> - <textarea - type="text" - placeholder="Commentary" - name="commentary" - class="inputBox inputPayload" - /> - <textarea - type="text" - placeholder="Commentary translation" - name="commentary_translation" - class="inputBox inputPayload" - /> - </form> - </div> <div class="h-48 m-4 border-dotted border-4 border-gray-400" @drop.prevent=" @@ -79,7 +38,7 @@ Please upload PNG, JPEG or GIF image only. </WarningBox> <button - v-show="imageList.length" + v-show="imageUploadList.length" @click="submitImage" class=" border border-gray-500 @@ -95,12 +54,68 @@ Submit </button> </div> + <strong class="m-2" v-show="imageUploadList.length"> + Click on the image's preview to remove the entry from uploading + </strong> + <div + v-for="(image, id) in imageUploadList" + :key="image.url" + class="flex flex-row divide-y-4 divide-gray-600 divide-dashed" + > + <img + :src="image.url" + @click="removeImage(id)" + alt="" + class="max-w-xs m-4" + /> + <div class="flex flex-col flex-1"> + <form :id="'image-update-form-' + id" class="flex flex-col flex-1"> + <input + type="text" + placeholder="Source" + name="source" + autocomplete="off" + class="inputBox inputPayload" + /> + <input + type="text" + placeholder="Parent" + name="parent" + autocomplete="off" + class="inputBox inputPayload" + /> + <textarea + type="text" + placeholder="Commentary" + name="commentary" + autocomplete="off" + class="inputBox inputPayload resize-none flex-1" + /> + <textarea + type="text" + placeholder="Commentary translation" + name="commentary_translation" + autocomplete="off" + class="inputBox inputPayload resize-none flex-1" + /> + </form> + <input + type="text" + placeholder="Tags" + name="tags" + autocomplete="off" + :id="'tagsInput' + id" + class="inputBox inputPayload" + /> + </div> + </div> </div> </template> <script> import { mapActions, mapState } from "vuex"; import WarningBox from "@/components/WarningBox.vue"; +import paths from "@/assets/js/paths.js"; export default { components: { @@ -108,7 +123,7 @@ export default { }, data: () => { return { - imageList: [], + imageUploadList: [], invalidType: false, }; }, @@ -118,17 +133,18 @@ export default { methods: { updateWrapper(event) { this.checkImageType(event) - ? this.updateImageList(event) + ? this.updateImageUploadList(event) : (this.$refs.imageInput.value = ""); }, - updateImageList(event) { - this.imageList = this.imageList.concat( + updateImageUploadList(event) { + this.imageUploadList = this.imageUploadList.concat( event - ? this.addMetadata([...event.dataTransfer.files]) - : this.addMetadata([...this.$refs.imageInput.files]) + ? this.mapFilesToPreviews([...event.dataTransfer.files]) + : this.mapFilesToPreviews([...this.$refs.imageInput.files]) ); }, - addMetadata(files) { + // Return an array of objects whose fields are the image's file and the preview's URL + mapFilesToPreviews(files) { let tempArray = []; files.forEach((file) => { tempArray.push({ file, url: URL.createObjectURL(file) }); @@ -137,11 +153,11 @@ export default { }, checkImageType(event) { const acceptedTypes = ["image/png", "image/jpeg", "image/gif"]; - let imageList = event + let imageUploadList = event ? [...event.dataTransfer.files] : [...this.$refs.imageInput.files]; - for (let i = 0; i < imageList.length; i++) { - if (!acceptedTypes.includes(imageList[i].type)) { + for (let i = 0; i < imageUploadList.length; i++) { + if (!acceptedTypes.includes(imageUploadList[i].type)) { this.invalidType = true; return false; } @@ -150,21 +166,22 @@ export default { return true; }, removeImage(id) { - this.imageList.splice(id, 1); + this.imageUploadList.splice(id, 1); }, async submitImage() { let formData = new FormData(); - for (let i = 0; i < this.imageList.length; i++) { - formData.set("image", this.imageList[i].file); - let data = await this.postImage(formData); - this.updateImage( + for (let i = 0; i < this.imageUploadList.length; i++) { + formData.set("image", this.imageUploadList[i].file); + let data = await this.postImageFile(formData); + this.setImageInfo( data.snowflake, this.serializeForm("image-update-form-" + i) ); + this.setImageTags(data.snowflake, "tagsInput" + i); } // Clean up this.invalidType = false; - this.imageList = []; + this.imageUploadList = []; }, // Serialize form inputs into JSON object that can be sent to API serializeForm(formID) { @@ -172,7 +189,9 @@ export default { Object.fromEntries(new FormData(document.getElementById(formID))) ); }, - async updateImage(flake, imageUpdatePayload) { + // Send an image update payload including 4 fields: "source", "parent", "commentary", + // "commentary translation" to paths.ImageField + async setImageInfo(flake, imageUpdatePayload) { const options = { method: "PATCH", headers: { @@ -182,7 +201,20 @@ export default { }; await fetch(`/api/image/${flake}`, options); }, - async postImage(fd) { + async setImageTags(flake, tagsInputID) { + const tags = document + .getElementById(tagsInputID) + .value.split(" ") + .filter((element) => element.length > 0); + const options = { + method: "PUT", + }; + + for (let i = 0; i < tags.length; i++) { + await fetch(paths.ImageTagField(flake, tags[i]), options); + } + }, + async postImageFile(fd) { const options = { method: "POST", headers: {