From 4d07ae0a1ecc0f2fe72dc0c42c6eed3d2ac60e5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20G=C3=B6bel?= <dgoebel@techfak.uni-bielefeld.de> Date: Tue, 27 Sep 2022 10:44:32 +0200 Subject: [PATCH] Add modal to confirm deletion --- src/components/BucketView.vue | 30 ++++++- src/components/Modals/DeleteModal.vue | 95 +++++++++++++++++++++++ src/components/Modals/PermissionModal.vue | 15 +++- src/views/object-storage/BucketsView.vue | 20 +++++ 4 files changed, 156 insertions(+), 4 deletions(-) create mode 100644 src/components/Modals/DeleteModal.vue diff --git a/src/components/BucketView.vue b/src/components/BucketView.vue index 819a2a2..b43e2c3 100644 --- a/src/components/BucketView.vue +++ b/src/components/BucketView.vue @@ -17,6 +17,7 @@ import CopyObjectModal from "@/components/Modals/CopyObjectModal.vue"; import PermissionModal from "@/components/Modals/PermissionModal.vue"; import ObjectDetailModal from "@/components/Modals/ObjectDetailModal.vue"; import CreateFolderModal from "@/components/Modals/CreateFolderModal.vue"; +import DeleteModal from "@/components/Modals/DeleteModal.vue" import { S3Client, DeleteObjectCommand, @@ -121,6 +122,8 @@ const objectState = reactive({ createdPermission: undefined, deletedItem: "", editObjectKey: "", + potentialObjectToDelete: "", + deleteFolder: true, copyObject: { key: "", size: 0, @@ -141,6 +144,8 @@ const objectState = reactive({ createdPermission: undefined | BucketPermission; deletedItem: string; editObjectKey: string; + potentialObjectToDelete: string; + deleteFolder: boolean; copyObject: S3ObjectMetaInformation; viewDetailObject: S3ObjectMetaInformation; }); @@ -388,11 +393,16 @@ function objectCopied(copiedObject: S3ObjectMetaInformation) { } } +function deleteObject(key: string) { + objectState.potentialObjectToDelete = key; + objectState.deleteFolder = false; +} + /** * Delete an Object in the current folder * @param key Key of the Object */ -function deleteObject(key: string) { +function confirmedDeleteObject(key: string) { const command = new DeleteObjectCommand({ Bucket: props.bucketName, Key: key, @@ -432,11 +442,16 @@ async function downloadObject(key: string, bucket: string) { document.body.removeChild(element); } +function deleteFolder(folderPath: string) { + objectState.potentialObjectToDelete = folderPath; + objectState.deleteFolder = true; +} + /** * Delete a folder in the current Bucket * @param folderPath Path to the folder with a trailing "/", e.g. some/path/to/a/folder/ */ -function deleteFolder(folderPath: string) { +function confirmedDeleteFolder(folderPath: string) { const command = new DeleteObjectsCommand({ Bucket: props.bucketName, Delete: { @@ -506,6 +521,13 @@ watch( </div> </div> </div> + <DeleteModal + modalID="delete-object-modal" + modal-label="some-label" + :object-name-delete="objectState.potentialObjectToDelete" + :back-modal-id="undefined" + @confirm-delete="objectState.deleteFolder ? confirmedDeleteFolder(objectState.potentialObjectToDelete) : confirmedDeleteObject(objectState.potentialObjectToDelete)" + /> <!-- Navbar Breadcrumb --> <nav aria-label="breadcrumb" class="fs-2"> <ol class="breadcrumb"> @@ -805,6 +827,8 @@ watch( class="dropdown-item text-danger align-middle" type="button" @click="deleteObject(obj.key)" + data-bs-toggle="modal" + data-bs-target="#delete-object-modal" :disabled="!writeS3Permission" > <bootstrap-icon @@ -825,6 +849,8 @@ watch( type="button" class="btn btn-danger btn-sm align-middle" :disabled="!writeS3Permission" + data-bs-toggle="modal" + data-bs-target="#delete-object-modal" @click=" deleteFolder( obj.parentFolder.concat(['']).join('/') + obj.name + '/' diff --git a/src/components/Modals/DeleteModal.vue b/src/components/Modals/DeleteModal.vue new file mode 100644 index 0000000..108993d --- /dev/null +++ b/src/components/Modals/DeleteModal.vue @@ -0,0 +1,95 @@ +<script setup lang="ts"> +import { onMounted, ref } from "vue"; +import type { Ref } from "vue"; +import { Modal } from "bootstrap"; +import BootstrapModal from "@/components/Modals/BootstrapModal.vue"; + +const props = defineProps<{ + modalID: string; + modalLabel: string; + objectNameDelete: string; + backModalId: string | undefined; +}>(); + +const confirmDelete: Ref<boolean> = ref(false); +const emit = defineEmits<{ + (e: "confirm-delete"): void; +}>(); + +let deleteModal: Modal | null = null; +const randomIDSuffix = Math.random().toString(16).substr(2, 8); + +function modalClosed() { + confirmDelete.value = false; +} + +function deleteObject() { + emit("confirm-delete"); + deleteModal?.hide(); + confirmDelete.value = false; +} + +onMounted(() => { + deleteModal = Modal.getOrCreateInstance("#" + props.modalID); +}); +</script> + +<template> + <bootstrap-modal + :modalID="props.modalID" + :static-backdrop="true" + :modal-label="props.modalLabel" + v-on="{ 'hidden.bs.modal': modalClosed }" + > + <template v-slot:header> Delete {{ props.objectNameDelete }}</template> + <template v-slot:body> + <p> + Are you sure you want to delete + <strong>{{ props.objectNameDelete }}</strong> + </p> + <div class="form-check"> + <input + class="form-check-input" + type="checkbox" + v-model="confirmDelete" + :id="'checkConfirmDelete' + randomIDSuffix" + /> + <label + class="form-check-label" + :for="'checkConfirmDelete' + randomIDSuffix" + > + <strong>Yes</strong>, I am sure. + </label> + </div> + </template> + <template v-slot:footer> + <button + v-if="backModalId !== undefined" + type="button" + class="btn btn-secondary" + :data-bs-target="'#' + props.backModalId" + data-bs-toggle="modal" + > + Back + </button> + <button + v-else + type="button" + class="btn btn-secondary" + data-bs-dismiss="modal" + > + Close + </button> + <button + type="button" + class="btn btn-danger" + :disabled="!confirmDelete" + @click="deleteObject" + > + Delete + </button> + </template> + </bootstrap-modal> +</template> + +<style scoped></style> diff --git a/src/components/Modals/PermissionModal.vue b/src/components/Modals/PermissionModal.vue index cafe98c..a470e2f 100644 --- a/src/components/Modals/PermissionModal.vue +++ b/src/components/Modals/PermissionModal.vue @@ -1,6 +1,7 @@ <script setup lang="ts"> import { onMounted, reactive, watch, ref, computed } from "vue"; import BootstrapModal from "@/components/Modals/BootstrapModal.vue"; +import DeleteModal from "@/components/Modals/DeleteModal.vue"; import { Modal } from "bootstrap"; import dayjs from "dayjs"; import type { @@ -248,7 +249,7 @@ function formSubmit() { * @param bucketName Bucket to delete * @param uid ID of grantee of the permission */ -function deletePermission(bucketName: string, uid: string) { +function confirmedDeletePermission(bucketName: string, uid: string) { if (!formState.loading) { formState.loading = true; BucketPermissionsService.bucketPermissionsDeletePermissionForBucket( @@ -280,6 +281,15 @@ onMounted(() => { </script> <template> + <DeleteModal + :modalID="'delete-permission-modal' + randomIDSuffix" + modal-label="some-label" + object-name-delete="permission" + :back-modal-id="modalID" + @confirm-delete=" + confirmedDeletePermission(permission.bucket_name, permission.uid) + " + /> <div class="toast-container position-fixed top-0 end-0 p-3"> <div role="alert" @@ -329,7 +339,8 @@ onMounted(() => { fill="currentColor" class="me-2" :class="{ 'delete-icon': !formState.loading }" - @click="deletePermission(permission.bucket_name, permission.uid)" + data-bs-toggle="modal" + :data-bs-target="'#delete-permission-modal' + randomIDSuffix" /> <bootstrap-icon v-if="formState.readonly && props.editable" diff --git a/src/views/object-storage/BucketsView.vue b/src/views/object-storage/BucketsView.vue index c01d35e..58422c9 100644 --- a/src/views/object-storage/BucketsView.vue +++ b/src/views/object-storage/BucketsView.vue @@ -6,8 +6,10 @@ import { BucketPermissionsService, BucketService } from "@/client"; import { useRoute, useRouter } from "vue-router"; import BootstrapIcon from "@/components/BootstrapIcon.vue"; import CreateBucketModal from "@/components/Modals/CreateBucketModal.vue"; +import DeleteModal from "@/components/Modals/DeleteModal.vue"; import BucketListItem from "@/components/BucketListItem.vue"; import { useAuthStore } from "@/stores/auth"; +import { Modal } from "bootstrap"; const route = useRoute(); const router = useRouter(); @@ -16,12 +18,15 @@ const authStore = useAuthStore(); const bucketsState = reactive({ buckets: [], permissions: {}, + potentialDeleteBucketName: "", loading: true, } as { buckets: BucketOut[]; loading: boolean; permissions: Record<string, BucketPermission>; + potentialDeleteBucketName: string; }); +let deleteModal: Modal | null = null; function fetchBuckets() { BucketService.bucketListBuckets() @@ -63,6 +68,11 @@ function addBucket(bucket: BucketOut) { } function deleteBucket(bucketName: string) { + bucketsState.potentialDeleteBucketName = bucketName; + deleteModal?.show(); +} + +function confirmedDeleteBucket(bucketName: string) { BucketService.bucketDeleteBucket(bucketName, true).then(() => { bucketDeleted(bucketName); }); @@ -78,11 +88,21 @@ function bucketDeleted(bucketName: string) { } onMounted(() => { + deleteModal = Modal.getOrCreateInstance("#delete-bucket-modal"); fetchBuckets(); }); </script> <template> + <DeleteModal + modalID="delete-bucket-modal" + modal-label="some-label" + :object-name-delete="bucketsState.potentialDeleteBucketName" + :back-modal-id="undefined" + @confirm-delete=" + confirmedDeleteBucket(bucketsState.potentialDeleteBucketName) + " + /> <div class="row m-2 border-bottom border-light mt-4"> <div class="col-12"></div> <h1 class="mb-2 text-light">Buckets</h1> -- GitLab