diff options
-rw-r--r-- | components/ModelForm.vue | 333 |
1 files changed, 241 insertions, 92 deletions
diff --git a/components/ModelForm.vue b/components/ModelForm.vue index 12821df..a2658fc 100644 --- a/components/ModelForm.vue +++ b/components/ModelForm.vue @@ -1,97 +1,179 @@ <template lang="pug"> form.mt-3 + .relative.z-10(aria-labelledby="modal-title", role="dialog", aria-modal="true" v-if="boxDeleteModelUpload > 0") + .fixed.inset-0.bg-gray-900.bg-opacity-90.transition-opacity + .fixed.inset-0.z-10.overflow-y-auto + .flex.min-h-full.items-end.justify-center.p-4.text-center(class="sm:items-center sm:p-0") + .relative.transform.overflow-hidden.rounded-lg.bg-white.text-left.shadow-xl.transition-all(class="sm:my-8 sm:w-full sm:max-w-lg") + .bg-white.px-4.pt-5.pb-4(class="sm:p-6 sm:pb-4") + div(class="sm:flex sm:items-start") + .mx-auto.flex.h-12.w-12.flex-shrink-0.items-center.justify-center.rounded-full.bg-red-100(class="sm:mx-0 sm:h-10 sm:w-10") + svg.h-6.w-6.text-red-600(xmlns="http://www.w3.org/2000/svg", fill="none", viewbox="0 0 24 24", stroke-width="1.5", stroke="currentColor", aria-hidden="true") + path(stroke-linecap="round", stroke-linejoin="round", d="M12 10.5v3.75m-9.303 3.376C1.83 19.126 2.914 21 4.645 21h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 4.88c-.866-1.501-3.032-1.501-3.898 0L2.697 17.626zM12 17.25h.007v.008H12v-.008z") + .mt-3.text-center(class="sm:mt-0 sm:ml-4 sm:text-left") + h3#modal-title.text-lg.font-medium.leading-6.text-gray-900 Are you sure? + .mt-2 + p.text-sm.text-gray-500 + | This file will be deleted from '{{ form.name }}' and our servers. This action cannot be undone. + .bg-gray-50.px-4.py-3(class="sm:flex sm:flex-row-reverse sm:px-6") + button.inline-flex.w-full.justify-center.rounded-md.border.border-transparent.bg-red-600.px-4.py-2.text-base.font-medium.text-white.shadow-sm( + type="button" + :class="{'hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 sm:ml-3 sm:w-auto sm:text-sm': true, 'opacity-25 cursor-default': isLoading}" + :disabled="isLoading" + :readonly="isLoading" + @click="deleteFile" + ) + <svg class="animate-spin -ml-1 mr-3 h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" v-if="isLoading"> + <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle> + <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path> + </svg> + | Yes, confirm + button.mt-3.inline-flex.w-full.justify-center.rounded-md.border.border-gray-300.bg-white.px-4.py-2.text-base.font-medium.text-gray-700.shadow-sm( + type="button" + class="hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm" + @click="boxDeleteModelUpload = 0" + ) Cancel .shadow(class="sm:overflow-hidden sm:rounded-md") - .space-y-6.bg-white.px-4.py-5(class="sm:p-6") - div - label.block.text-sm.font-medium.text-gray-700(for="name") Name - .mt-1 - input#name.mt-1.block.w-full.rounded-md.border-gray-300.border-1.px-2.py-1( - name="name" - class="focus:border-green-500 focus:ring-green-500 sm:text-sm" - placeholder="My super project" - required - v-model="form.name" - ) - div - label.block.text-sm.font-medium.text-gray-700(for="description") Description - span.text-gray-300 (Optional) - .mt-1 - textarea#description.mt-1.block.w-full.rounded-md.border-gray-300.border-1.px-2.py-1( - name="description" rows="3" - class="focus:border-green-500 focus:ring-green-500 sm:text-sm" - placeholder="Now this is the story all about how, My life got flipped-turned upside down... " - v-model="form.description" - ) - div - label.block.text-sm.font-medium.text-gray-700(for="duration") Duration - .mt-1 - input#duration.mt-1.block.w-full.rounded-md.border-gray-300.border-1.px-2.py-1( - name="duration" - type="number" - step="0.01" - class="focus:border-green-500 focus:ring-green-500 sm:text-sm" - required - v-model="form.duration" - ) - p.text-sm.text-gray-500 Print duration in minutes - div - label.block.text-sm.font-medium.text-gray-700(for="height") Height - .mt-1 - input#height.mt-1.block.w-full.rounded-md.border-gray-300.border-1.px-2.py-1( - name="height" - type="number" - step="0.01" - class="focus:border-green-500 focus:ring-green-500 sm:text-sm" - required - v-model="form.height" - ) - p.text-sm.text-gray-500 Height in mm - div - label.block.text-sm.font-medium.text-gray-700(for="weight") Weight - .mt-1 - input#weight.mt-1.block.w-full.rounded-md.border-gray-300.border-1.px-2.py-1( - name="weight" - type="number" - step="0.01" - class="focus:border-green-500 focus:ring-green-500 sm:text-sm" - required - v-model="form.weight" - ) - p.text-sm.text-gray-500 Weight in g - div - label.block.text-sm.font-medium.text-gray-700(for="printer") Printer - span.text-gray-300 (Optional) - .mt-1 - input#printer.mt-1.block.w-full.rounded-md.border-gray-300.border-1.px-2.py-1( - name="printer" - class="focus:border-green-500 focus:ring-green-500 sm:text-sm" - placeholder="Formlabs Form 3BL" - v-model="form.printer" - ) - div - label.block.text-sm.font-medium.text-gray-700(for="material") Material - span.text-gray-300 (Optional) - .mt-1 - input#material.mt-1.block.w-full.rounded-md.border-gray-300.border-1.px-2.py-1( - name="material" - class="focus:border-green-500 focus:ring-green-500 sm:text-sm" - placeholder="PLA" - v-model="form.material" - ) - .py-3.text-right(class="sm:px-6") - button.inline-flex.justify-center.rounded-md.border.border-transparent.bg-green-600.py-2.px-4.text-sm.font-medium.text-white.shadow-sm( - type="submit" - :class="{'hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2': true, 'opacity-25 cursor-default': isLoading}" - :disabled="isLoading" - :readonly="isLoading" - @click="save" - ) - <svg class="animate-spin -ml-1 mr-3 h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" v-if="isLoading"> - <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle> - <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path> - </svg> - | Save - p.text-sm.text-gray-700.mt-2(v-if="!form.id") You will add assets like images and STL/OBJ later. + .grid.grid-cols-2(v-if="form.id") + button.p-2.duration-100( + type="button" + :class="{'hover:bg-white': true, 'bg-white border-green-600 border-b-2': tab == 'info'}" + @click="tab = 'info'" + ) Info + button.p-2.duration-100( + type="button" + :class="{'hover:bg-white': true, 'bg-white border-green-600 border-b-2': tab == 'files'}" + @click="tab = 'files'" + ) Files + div(v-if="tab == 'info'") + .space-y-6.bg-white.px-4.py-5(class="sm:p-6") + div + label.block.text-sm.font-medium.text-gray-700(for="name") Name + .mt-1 + input#name.mt-1.block.w-full.rounded-md.border-gray-300.border-1.px-2.py-1( + name="name" + class="focus:border-green-500 focus:ring-green-500 sm:text-sm" + placeholder="My super project" + required + v-model="form.name" + ) + div + label.block.text-sm.font-medium.text-gray-700(for="description") Description + span.text-gray-300 (Optional) + .mt-1 + textarea#description.mt-1.block.w-full.rounded-md.border-gray-300.border-1.px-2.py-1( + name="description" rows="3" + class="focus:border-green-500 focus:ring-green-500 sm:text-sm" + placeholder="Now this is the story all about how, My life got flipped-turned upside down... " + v-model="form.description" + ) + div + label.block.text-sm.font-medium.text-gray-700(for="duration") Duration + .mt-1 + input#duration.mt-1.block.w-full.rounded-md.border-gray-300.border-1.px-2.py-1( + name="duration" + type="number" + step="0.01" + class="focus:border-green-500 focus:ring-green-500 sm:text-sm" + required + v-model="form.duration" + ) + p.text-sm.text-gray-500 Print duration in minutes + div + label.block.text-sm.font-medium.text-gray-700(for="height") Height + .mt-1 + input#height.mt-1.block.w-full.rounded-md.border-gray-300.border-1.px-2.py-1( + name="height" + type="number" + step="0.01" + class="focus:border-green-500 focus:ring-green-500 sm:text-sm" + required + v-model="form.height" + ) + p.text-sm.text-gray-500 Height in mm + div + label.block.text-sm.font-medium.text-gray-700(for="weight") Weight + .mt-1 + input#weight.mt-1.block.w-full.rounded-md.border-gray-300.border-1.px-2.py-1( + name="weight" + type="number" + step="0.01" + class="focus:border-green-500 focus:ring-green-500 sm:text-sm" + required + v-model="form.weight" + ) + p.text-sm.text-gray-500 Weight in g + div + label.block.text-sm.font-medium.text-gray-700(for="printer") Printer + span.text-gray-300 (Optional) + .mt-1 + input#printer.mt-1.block.w-full.rounded-md.border-gray-300.border-1.px-2.py-1( + name="printer" + class="focus:border-green-500 focus:ring-green-500 sm:text-sm" + placeholder="Formlabs Form 3BL" + v-model="form.printer" + ) + div + label.block.text-sm.font-medium.text-gray-700(for="material") Material + span.text-gray-300 (Optional) + .mt-1 + input#material.mt-1.block.w-full.rounded-md.border-gray-300.border-1.px-2.py-1( + name="material" + class="focus:border-green-500 focus:ring-green-500 sm:text-sm" + placeholder="PLA" + v-model="form.material" + ) + .py-3.text-right(class="sm:px-6") + button.inline-flex.justify-center.rounded-md.border.border-transparent.bg-green-600.py-2.px-4.text-sm.font-medium.text-white.shadow-sm( + type="submit" + :class="{'hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2': true, 'opacity-25 cursor-default': isLoading}" + :disabled="isLoading" + :readonly="isLoading" + @click="save" + ) + <svg class="animate-spin -ml-1 mr-3 h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" v-if="isLoading"> + <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle> + <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path> + </svg> + | Save + p.text-sm.text-gray-700.mt-2(v-if="!form.id") You will add assets like images and STL/OBJ later. + div(v-else) + .space-y-6.bg-white.px-4.py-5(class="sm:p-6") + h3 Manage '{{ form.name }}' already uploaded files + ul.divide-y.divide-gray-200.rounded-b-md.border.border-gray-200(role="list") + li.flex.items-center.justify-between.py-3.pl-3.pr-4.text-sm(v-for="upload in form.uploads" :key="upload.id") + .flex.w-0.flex-1.items-center + svg.h-5.w-5.flex-shrink-0.text-gray-400(xmlns="http://www.w3.org/2000/svg", viewbox="0 0 20 20", fill="currentColor", aria-hidden="true") + path(fill-rule="evenodd", d="M15.621 4.379a3 3 0 00-4.242 0l-7 7a3 3 0 004.241 4.243h.001l.497-.5a.75.75 0 011.064 1.057l-.498.501-.002.002a4.5 4.5 0 01-6.364-6.364l7-7a4.5 4.5 0 016.368 6.36l-3.455 3.553A2.625 2.625 0 119.52 9.52l3.45-3.451a.75.75 0 111.061 1.06l-3.45 3.451a1.125 1.125 0 001.587 1.595l3.454-3.553a3 3 0 000-4.242z", clip-rule="evenodd") + span.ml-2.w-0.flex-1.truncate {{ getFileName(upload.filepath) }} + .ml-4 + a.font-medium.text-black-700.cursor-pointer.mr-2(class="hover:underline") Preview + a.font-medium.text-red-700.cursor-pointer(class="hover:underline" @click="boxDeleteModelUpload = upload.id") Delete + hr + h3 Add new uploads + label.inline-flex.leading-6.justify-center.rounded-md.border.border-transparent.bg-green-600.py-2.px-4.text-sm.font-medium.text-white.shadow-sm.cursor-pointer( + class="hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2" + for="upload" + ) + <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="mr-1 w-6 h-6"> + <path stroke-linecap="round" stroke-linejoin="round" d="M12 10.5v6m3-3H9m4.06-7.19l-2.12-2.12a1.5 1.5 0 00-1.061-.44H4.5A2.25 2.25 0 002.25 6v12a2.25 2.25 0 002.25 2.25h15A2.25 2.25 0 0021.75 18V9a2.25 2.25 0 00-2.25-2.25h-5.379a1.5 1.5 0 01-1.06-.44z" /> + </svg> + | Select file(s) + input.hidden( + id="upload" + type="file" + multiple + @change="manageUploads" + ) + ul + li.flex.items-center.justify-between.py-3.pl-3.pr-4.text-sm(v-for="file in filesToUpload" :key="file.lastModified+''+file.name") + .flex.w-0.flex-1.items-center + svg.h-5.w-5.flex-shrink-0.text-gray-400(xmlns="http://www.w3.org/2000/svg", viewbox="0 0 20 20", fill="currentColor", aria-hidden="true") + path(fill-rule="evenodd", d="M15.621 4.379a3 3 0 00-4.242 0l-7 7a3 3 0 004.241 4.243h.001l.497-.5a.75.75 0 011.064 1.057l-.498.501-.002.002a4.5 4.5 0 01-6.364-6.364l7-7a4.5 4.5 0 016.368 6.36l-3.455 3.553A2.625 2.625 0 119.52 9.52l3.45-3.451a.75.75 0 111.061 1.06l-3.45 3.451a1.125 1.125 0 001.587 1.595l3.454-3.553a3 3 0 000-4.242z", clip-rule="evenodd") + span.ml-2.w-0.flex-1.truncate(v-if="checkExt(file.name)") {{ file.name }} {{ file.uploaded }} + span.ml-2.w-0.flex-1.truncate.text-gray-500(v-else) + s.text-red-500.mr-2 {{ file.name }} + | (this file type is not supported, so it'll not be uploaded) </template> <script> @@ -103,6 +185,9 @@ export default { data() { return { form: {}, + tab: "info", + boxDeleteModelUpload: 0, + filesToUpload: [], }; }, computed: { @@ -121,7 +206,7 @@ export default { if (response.status == 201 || response.status == 200) { this.$toast.success("Model has been saved"); setTimeout(() => { - window.location.href = "/models/" + response.data.id; + window.location.href = "/models/" + response.data.id + "/edit"; }, 1000); } else { this.$toast.error(response.data.error); @@ -130,6 +215,70 @@ export default { } event.preventDefault(); }, + getFileName(path) { + return path.split("/").at(-1); + }, + deleteFile() { + this.$store + .dispatch("models/deleteUpload", { + id: this.form.id, + uid: this.boxDeleteModelUpload, + }) + .then((response) => { + if (response.status == 204) { + this.$toast.success("File deleted successfully"); + for (const i in this.form.uploads) { + if (this.form.uploads[i].id == this.boxDeleteModelUpload) { + this.form.uploads.splice(i, 1); + break; + } + } + this.boxDeleteModelUpload = 0; + } else { + this.$toast.error("Error: " + response.data.error); + } + }); + }, + manageUploads(event) { + for (const file of event.target.files) { + if (this.checkExt(file.name)) { + this.$store + .dispatch("models/upload", { id: this.form.id, file }) + .then((response) => { + if (response == 200) { + this.$toast.success(`'${file.name}' has been uploaded`); + this.$store + .dispatch("models/findModel", this.form.id) + .then((response) => { + this.form.uploads = response.data.uploads; + }); + file.uploaded = true; + } else { + this.$toast.error(`Error uploading '${file.name}'`); + file.uploaded = false; + } + }); + } + this.filesToUpload.push(file); + } + }, + checkExt(name) { + const exts = [ + ".stl", + ".obj", + ".png", + ".jpg", + ".jpeg", + ".gif", + ".webp", + ".blend", + ]; + for (const ext of exts) { + if (name.toLowerCase().indexOf(ext) > 0) return true; + } + + return false; + }, }, }; </script> |