diff --git a/frontend/components/root/Navbar.vue b/frontend/components/root/Navbar.vue index 22d95e980b8ad75a6f5873913e0fff3f3b3bc44d..5e2bc95e905e38ff78d1f01417153ea805f6da42 100644 --- a/frontend/components/root/Navbar.vue +++ b/frontend/components/root/Navbar.vue @@ -34,6 +34,16 @@ </div> </b-nav-item> </div> + + <div v-if="role === 'admin'"> + <!-- Upload Dataset --> + <b-nav-item link-classes="side-bar-color mt-3 ml-4" @click="changeActiveElmtID('upload-dataset')"> + <div id="upload-dataset"> + Upload Dataset + </div> + </b-nav-item> + </div> + <div> <b-nav-item v-b-toggle.collapse-settings @@ -176,6 +186,9 @@ export default { case 'edit-dataset': this.$nuxt.$router.replace({ path: '/main/edit'}) break + case 'upload-dataset': + this.$nuxt.$router.replace({ path: '/main/upload'}) + break case 'change-username': this.$nuxt.$router.replace({ path: '/main/change-username'}) break @@ -205,6 +218,8 @@ export default { elmtID ='json-outputs' } else if ((/^\/main\/edit(\/|(\?)|$)/.test(browserURL))) { elmtID ='edit-dataset' + } else if ((/^\/main\/upload(\/|(\?)|$)/.test(browserURL))) { + elmtID ='upload-dataset' } else if ((/^\/main\/change-username(\/|(\?)|$)/.test(browserURL))) { elmtID ='change-username' } else if ((/^\/main\/change-password(\/|(\?)|$)/.test(browserURL))) { diff --git a/frontend/pages/main/upload.vue b/frontend/pages/main/upload.vue new file mode 100644 index 0000000000000000000000000000000000000000..5ebf0357b16fb663ebf388e371b704785327a673 --- /dev/null +++ b/frontend/pages/main/upload.vue @@ -0,0 +1,294 @@ +<template> + <div class="col"> + <div class="ml-4"> + <div class="row"> + <div class="col users-margin"> + <h5 class="title">Upload Dataset</h5> + </div> + </div> + <br> + <br> + <div class="row"> + <div class="col"> + <h5 class="title sub-title">Add Folder</h5> + </div> + </div> + <br> + <div class="row"> + <div class="col"> + <div class="uploader"> + <vue-dropzone ref="myDropzone" id="dropzone" :options="options" :useCustomSlot="true"> + <div class="dropzone-custom-content"> + <h6 class="dropzone-custom-title">Drag and Drop Images here to Upload Content!</h6> + </div> + </vue-dropzone> + </div> + </div> + </div> + <br> + <div class="row"> + <div class="col"> + <button class="btn-action btn-blue" @click="handleUpload">Upload</button> + <button class="btn-action btn-white" @click="removeAllFiles">Cancel</button> + </div> + </div> + </div> + </div> +</template> + +<script> +import vue2Dropzone from 'vue2-dropzone' +import 'vue2-dropzone/dist/vue2Dropzone.min.css' + +export default { + components: { + vueDropzone : vue2Dropzone + }, + data(){ + return{ + files: null, + options: { + url: "http://localhost:3000/api/image/upload", + uploadMultiple: true, + autoQueue: false, + thumbnailWidth: 60, + thumbnailHeight: 60 + } + } + }, + mounted(){ + var instance = this.$refs.myDropzone.dropzone + this.files = instance.files + console.log("files",this.files) + }, + methods:{ + formatBytes(bytes,decimals) { + if(bytes == 0){ + return '0 Bytes' + }else{ + var k = 1024, + dm = decimals <= 0 ? 0 : decimals || 2, + sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'], + i = Math.floor(Math.log(bytes) / Math.log(k)) + return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i] + } + }, + onFileSelected(event){ + this.files = event.target.files + console.log("masuk",this.files) + }, + removeAllFiles(){ + this.files = this.$refs.myDropzone.getAcceptedFiles() + if(this.files.length === 0){ + this.$swal.fire({ + title: "Failed Remove Files", + icon: "error", + text: "No Files", + confirmButtonColor: '#11616F', + }) + }else{ + this.$refs.myDropzone.removeAllFiles() + this.files = [] + } + }, + invalidUpload(){ + this.$swal.fire({ + title: "No Image Selected", + icon: "error", + text: "Choose images first!", + confirmButtonColor: '#11616F', + }) + }, + validUpload(){ + this.files = this.$refs.myDropzone.getAcceptedFiles() + var total = this.files.length + var count = 0 + var size = 0 + this.$swal.fire({ + title: 'Uploading', + html: '<b class="count"></b> of <b class="total"></b>', + allowOutsideClick: false, + allowEscapeKey: false, + timer: 1000, + onBeforeOpen: () =>{ + this.$swal.showLoading() + }, + onOpen: () => { + this.$swal.stopTimer() + Array.from(this.files).forEach(file =>{ + console.log("Size: ",file.dataURL) + if(this.upload(file, file.name)){ + count += 1 + } + size += file.size + const content = this.$swal.getContent() + if(content){ + const countText = content.querySelector('.count') + const totalText = content.querySelector('.total') + if(countText && totalText){ + countText.textContent = count + totalText.textContent = total + } + } + }) + if(count === total){ + console.log("Uploaded : ", count) + this.$swal.resumeTimer() + } + } + }).then((result) =>{ + if(result){ + this.removeAllFiles() + this.$swal.fire({ + title: "Success", + icon: 'success', + confirmButtonColor: '#11616F', + html: 'Uploaded <b id="count"></b> images (<b id="size"></b>)', + onBeforeOpen: () => { + const content = this.$swal.getContent() + if(content){ + const countText = content.querySelector('#count') + const sizeText = content.querySelector('#size') + if(countText && sizeText){ + countText.textContent = count + sizeText.textContent = this.formatBytes(size) + } + } + } + }) + console.log("size total: ",size) + } + }) + }, + async upload(f, name){ + var url = "/api/image/upload" + var dataURL = f.dataURL + var file = this.dataURItoBlob(dataURL) + var formData = new FormData() + formData.append("image", file, name) + const config = { + headers: {'Content-Type':'multipart/form-data'} + } + var response = await this.$axios.post(url, formData, config).catch(error => console.log(error)) + return(response && response.status === 200) + }, + dataURItoBlob(dataURI) { + var byteString = atob(dataURI.split(',')[1]) + var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0] + + var ab = new ArrayBuffer(byteString.length) + var ia = new Uint8Array(ab) + for (var i = 0; i < byteString.length; i++) { + ia[i] = byteString.charCodeAt(i) + } + return new Blob([ab], {type: mimeString}) + }, + async handleUpload(){ + this.files = this.$refs.myDropzone.getAcceptedFiles() + if(this.files.length === 0){ + this.invalidUpload() + }else{ + this.files = [] + this.validUpload() + } + } + } +} +</script> + +<style scoped> + + .form-title, .form-content, .btn-content { + font-size: 0.85rem; + color: #1E889B; + letter-spacing: 0.025rem; + + font-family: 'Open Sans Regular'; + } + + .btn-action .btn-content { + color: white; + margin-top: 5px; + margin-left: -5px; + } + + .form-title-margin { + margin-top: -5px; + } + + #container { + grid-area: main; + align-self: center; + justify-self: center; + width: 18rem; + } + + .btn-white{ + background: #fff; + -webkit-box-shadow: 2px 5px 5px 0px9/main/upload rgba(0,0,0,0.15); + -moz-box-shadow: 2px 5px 5px 0px rgba(0,0,0,0.15); + box-shadow: 2px 5px 5px 0px rgba(0,0,0,0.15); + border: 0; + border-radius: 0.3rem; + overflow: hidden; + cursor: pointer; + color: #1E889B; + /* margin-top: 2.8rem; */ + padding-left: 1.3rem; + padding-right: 1.3rem; + margin-right: 1rem; + margin-bottom: 1rem; + } + + .btn-blue{ + background:#1E889B; + border: 0; + border-radius: 0.3rem; + padding-left: 1.3rem; + padding-right: 1.3rem; + cursor: pointer; + color: #fff; + margin-right: 1rem; + margin-bottom: 1rem; + } + + .btn-blue:hover{ + background: #11616F; + } + + .btn-white:hover{ + background: #e2e2e2; + } + + .uploader{ + width: 75%; + background: #fff; + border-radius: 0.5rem; + height: 11.7rem; + padding:0.7rem; + border: 0.2rem solid #1E889B; + cursor: pointer; + overflow-y: scroll; + } + + #uploader-container h6{ + text-align: center; + } + + .dropzone-custom-content{ + text-align: center; + } + + .dropzone-custom-title{ + margin-top: 0; + color: #11616F; + } + + #dropzone{ + background-color: #fff; + border: 0.3rem solid #11616F; + border-style: dashed; + height: auto; + } + +</style>