<script setup lang="ts"> import BootstrapModal from "@/components/modals/BootstrapModal.vue"; import { computed, onMounted, reactive, ref, watch } from "vue"; import { Modal, Toast } from "bootstrap"; import { partial } from "filesize"; import { useS3ObjectStore } from "@/stores/s3objects"; const fsize = partial({ base: 2, standard: "jedec" }); const objectRepository = useS3ObjectStore(); const props = defineProps<{ modalID: string; bucketName: string; keyPrefix: string; editObjectFileName?: string; }>(); const randomIDSuffix = Math.random().toString(16).substring(2, 8); const objectFileInput = ref<HTMLInputElement | undefined>(undefined); let uploadModal: Modal | null = null; let successToast: Toast | null = null; let errorToast: Toast | null = null; const currentFolders = computed<string[]>(() => props.keyPrefix.split("/")); watch( () => props.editObjectFileName, (nextFileName) => { formState.key = nextFileName ?? ""; }, ); const formState = reactive({ file: {}, key: "", uploading: false, uploadDone: 0, uploadTotal: 1, } as { file: File; key: string; uploading: boolean; uploadDone: number; uploadTotal: number; }); const uploadProgress = computed<number>(() => Math.round((100 * formState.uploadDone) / formState.uploadTotal), ); const editObject = computed<boolean>( () => props.editObjectFileName !== undefined, ); function uploadObject() { const key = props.keyPrefix.length > 0 ? props.keyPrefix + "/" + formState.key : formState.key; formState.uploadDone = 0; formState.uploading = true; objectRepository .uploadObjectFile(props.bucketName, key, formState.file, (progress) => { if (progress.loaded != null && progress.total != null) { formState.uploadDone = progress.loaded; formState.uploadTotal = progress.total; } }) .then(() => { uploadModal?.hide(); successToast?.show(); formState.key = ""; if (objectFileInput.value != undefined) { objectFileInput.value.value = ""; } }) .catch((e) => { console.error(e); errorToast?.show(); }) .finally(() => { formState.uploading = false; }); } function fileChange() { if (objectFileInput.value != undefined) { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion formState.file = objectFileInput.value.files![0]; if (!editObject.value) { formState.key = formState.file.name; } } } onMounted(() => { uploadModal = new Modal("#" + props.modalID); successToast = new Toast("#successToast-" + randomIDSuffix); errorToast = new Toast("#errorToast-" + randomIDSuffix); }); </script> <template> <div class="toast-container position-fixed top-toast end-0 p-3"> <div role="alert" aria-live="assertive" aria-atomic="true" class="toast text-bg-success align-items-center border-0" data-bs-autohide="true" :id="'successToast-' + randomIDSuffix" > <div class="d-flex"> <div class="toast-body">Successfully uploaded file</div> <button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Close" ></button> </div> </div> </div> <div class="toast-container position-fixed top-toast end-0 p-3"> <div role="alert" aria-live="assertive" aria-atomic="true" class="toast text-bg-danger align-items-center border-0" data-bs-autohide="true" :id="'errorToast-' + randomIDSuffix" > <div class="d-flex"> <div class="toast-body"> There has been some Error.<br /> Try again later </div> <button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Close" ></button> </div> </div> </div> <bootstrap-modal :modalID="modalID" :static-backdrop="true" modal-label="Upload Object Modal" > <template v-slot:header> <h4>Upload file to</h4> <ol class="breadcrumb"> <li class="breadcrumb-item">{{ props.bucketName }}</li> <li class="breadcrumb-item" v-for="folder in currentFolders" :key="folder" > {{ folder }} </li> </ol> </template> <template v-slot:body> <div class="container-fluid"> <div class="row"> <form class="col-7" :id="'uploadObjectForm' + randomIDSuffix" @submit.prevent="uploadObject" > <div class="mb-3"> <label :for="'objectFile' + randomIDSuffix" class="form-label" v-if="editObject" > New File Content * </label> <label :for="'objectFile' + randomIDSuffix" class="form-label" v-else > File * </label> <input class="form-control" type="file" :id="'objectFile' + randomIDSuffix" ref="objectFileInput" required @change="fileChange" /> </div> <div class="mb-3"> <label :for="'objectKey' + randomIDSuffix" class="form-label" >Filename</label > <input type="text" :class="{ 'form-control-plaintext': editObject, 'form-control': !editObject, }" :id="'objectKey' + randomIDSuffix" required :disabled="editObject" v-model="formState.key" /> </div> </form> <div class="col-5"> Note: Delimiters ('/') are allowed in the file name to place the new file into a folder that will be created when the file is uploaded (to any depth of folders). </div> </div> </div> </template> <template v-slot:footer> <div class="w-50 me-auto" v-if="formState.uploading"> <div class="progress"> <div class="progress-bar bg-info" role="progressbar" aria-label="Basic example" :style="{ width: uploadProgress + '%' }" :aria-valuenow="uploadProgress" aria-valuemin="0" aria-valuemax="100" > {{ uploadProgress }}% </div> </div> <span v-if="formState.uploadDone > 0"> {{ fsize(formState.uploadDone) }} / {{ fsize(formState.uploadTotal) }} </span> </div> <button type="button" class="btn btn-secondary" data-bs-dismiss="modal"> Close </button> <button :disabled="formState.uploading" type="submit" :form="'uploadObjectForm' + randomIDSuffix" class="btn btn-primary" > <span v-if="formState.uploading" class="spinner-border spinner-border-sm" role="status" aria-hidden="true" ></span> Upload </button> </template> </bootstrap-modal> </template> <style scoped></style>