Skip to content

Commit

Permalink
global recycle bin
Browse files Browse the repository at this point in the history
  • Loading branch information
TimCsaky committed Jan 24, 2025
1 parent ae8b120 commit ca4a991
Show file tree
Hide file tree
Showing 21 changed files with 387 additions and 315 deletions.
2 changes: 1 addition & 1 deletion frontend/src/assets/main.scss
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ div:focus-visible {

/* footer */
.gov-footer {
background-color: #003366 !important;
background-color: #003366;
border-top: 2px solid #fcba19;
padding-bottom: 3px;
a {
Expand Down
11 changes: 11 additions & 0 deletions frontend/src/components/layout/Navbar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,17 @@ const { getIsAuthenticated } = storeToRefs(useAuthStore());
My Files
</router-link>
</li>
<li
v-if="getIsAuthenticated"
class="mr-2"
>
<router-link
:to="{ name: RouteNames.LIST_OBJECTS_DELETED }"
aria-label="Recycle Bin"
>
Recycle Bin
</router-link>
</li>
<li class="mr-2">
<a
target="_blank"
Expand Down
117 changes: 70 additions & 47 deletions frontend/src/components/object/DeleteObjectButton.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { storeToRefs } from 'pinia';
import { computed, ref } from 'vue';
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
import { Button, Dialog, useConfirm } from '@/lib/primevue';
import { useNavStore, useObjectStore } from '@/store';
import { Button, Dialog, useConfirm, useToast} from '@/lib/primevue';
import { useNavStore, useObjectStore, useVersionStore } from '@/store';
import { ButtonMode } from '@/utils/enums';
import { onDialogHide } from '@/utils/utils';
Expand All @@ -14,75 +14,98 @@ type Props = {
ids: Array<string>;
mode: ButtonMode;
versionId?: string; // Only use this when deleting a single object
hard?: boolean
hardDelete?: boolean // are we doing a hardDelete delete?
};
const props = withDefaults(defineProps<Props>(), {
versionId: undefined,
hard: false
hardDelete: false
});
// Emits
const emit = defineEmits(['on-deleted-success', 'on-deleted-error']);
const emit = defineEmits(['on-object-deleted', 'on-version-deleted']);
// Store
const objectStore = useObjectStore();
const versionStore = useVersionStore();
const { focusedElement } = storeToRefs(useNavStore());
// Getters
const { getIsVersioningEnabled } = storeToRefs(versionStore);
// State
const displayNoFileDialog = ref(false);
const bucketVersioningEnabled = computed(() => getIsVersioningEnabled.value(props.ids[0]));
// Actions
const confirm = useConfirm();
const toast = useToast();
const confirmDelete = () => {
const confirmDelete = async () => {
focusedElement.value = document.activeElement;
if (props.ids.length) {
const item = props.versionId ? 'version' : 'file';
const msgContext = props.ids.length > 1 ? `the selected ${props.ids.length} ${item}s` : `this ${item}`;
const permText = props.hard || props.versionId ? 'permanently ' : '';
confirm.require({
message: `Please confirm that you want to ${permText}delete ${msgContext}.`,
header: `Delete ${props.ids.length > 1 ? item + 's' : item}`,
acceptLabel: 'Confirm',
rejectLabel: 'Cancel',
accept: () => {
// props.ids?.forEach((id: string) => {
// objectStore
// .deleteObject(id, props.versionId, props.hard)
// .then(() => emit('on-deleted-success',
// props.versionId, // version Id or undefined
// props.versionId || false, // true or false
// props.hard)) // if doing hard delete of object
// .catch(() => {});
// });
for (const id of props.ids) {
objectStore
.deleteObject(id, props.versionId, props.hard)
.then(() => emit('on-deleted-success',
props.versionId, // version Id or undefined
props.versionId || false, // true or false
props.hard)) // if doing hard delete of object
.catch(() => {});
};
const numberOfObjects = props.ids.length;
if (numberOfObjects) {
// refresh version store to determine if bucketVersioningEnabled
await versionStore.fetchVersions({ objectId: props.ids[0] });
// if versionId provided, and versioning is enabled on bucket, delete version
if(props.versionId && bucketVersioningEnabled.value) {
confirm.require({
message: 'Please confirm that you want to delete this version',
header: 'Delete version',
acceptLabel: 'Confirm',
rejectLabel: 'Cancel',
accept: async () => {
try {
await objectStore.deleteVersion(props.ids[0], props.versionId);
emit('on-version-deleted', { versionId: props.versionId });
toast.success('Version deleted');
}
catch (error) {
toast.error('Unable to delete version');
}
},
onHide: () => onDialogHide(),
reject: () => onDialogHide()
});
}
// else deleting an object
else {
const msgContext = numberOfObjects > 1 ? `the selected ${numberOfObjects} files` : 'this file';
const permText = props.hardDelete || !bucketVersioningEnabled.value ? 'permanently ' : '';
confirm.require({
message: `Please confirm that you want to ${permText}delete ${msgContext}.`,
header: `Delete ${numberOfObjects > 1 ? 'files' : 'file'}`,
acceptLabel: 'Confirm',
rejectLabel: 'Cancel',
accept: async () => {
try {
// for (const[i, id] of props.ids.entries()) {
for (const id of props.ids) {
await objectStore.deleteObject(id, props.hardDelete);
};
// to break on single failure (use promise.all ?)
toast.success(`${numberOfObjects} ${numberOfObjects > 1 ? 'files' : 'file'} deleted`);
emit('on-object-deleted', { hardDelete: props.hardDelete });
}
catch (error) {
toast.error('Unable to delete File(s)');
}
},
onHide: () => onDialogHide(),
reject: () => onDialogHide()
});
}
}
},
onHide: () => onDialogHide(),
reject: () => onDialogHide()
});
} else {
else {
displayNoFileDialog.value = true;
}
};
const buttonLabel = computed(() => {
return props.hard ?
return props.hardDelete ?
(props.versionId ?
'Permanently delete version' : (props.ids.length > 1 ?
'Permanently delete these files' : 'Permanently delete file')) :
'Permanently delete selected files' : 'Permanently delete file')) :
(props.versionId ? 'Delete version' : 'Delete file' );
});
</script>
Expand All @@ -106,7 +129,7 @@ const buttonLabel = computed(() => {
<Button
v-if="props.mode === ButtonMode.ICON"
v-tooltip.bottom="buttonLabel"
class="p-button-lg p-button-text p-button-danger"
class="p-button-lg p-button-text"
:disabled="props.disabled"
:aria-label="buttonLabel"
@click="confirmDelete()"
Expand All @@ -116,7 +139,7 @@ const buttonLabel = computed(() => {
<Button
v-else
v-tooltip.bottom="buttonLabel"
class="p-button-outlined p-button-danger"
class="p-button-outlined"
:disabled="props.disabled"
:aria-label="buttonLabel"
@click="confirmDelete()"
Expand Down
68 changes: 56 additions & 12 deletions frontend/src/components/object/ObjectFileDetails.vue
Original file line number Diff line number Diff line change
Expand Up @@ -51,23 +51,31 @@ const versionStore = useVersionStore();
const { getUserId } = storeToRefs(useAuthStore());
const { getObject } = storeToRefs(objectStore);
const { getIsDeleted, getLatestVersionIdByObjectId, getVersionsByObjectId } = storeToRefs(versionStore);
const {
getIsDeleted,
getLatestVersionIdByObjectId,
getLatestNonDmVersionIdByObjectId,
getVersionsByObjectId,
getIsVersioningEnabled
} = storeToRefs(versionStore);
// State
const object: Ref<COMSObject | undefined> = ref(undefined);
const bucketId: Ref<string> = ref('');
const permissionsVisible: Ref<boolean> = ref(false);
// version stuff
const bucketVersioningEnabled = computed(() => getIsVersioningEnabled.value(props.objectId));
const currentVersionId: Ref<string | undefined> = ref(props.versionId);
const latestVersionId = computed(() => getLatestVersionIdByObjectId.value(props.objectId));
const allVersions = computed(() => getVersionsByObjectId.value(props.objectId));
const latestNonDmVersionId = computed(() => getLatestNonDmVersionIdByObjectId.value(props.objectId));
const isDeleted: Ref<boolean> = computed(() => getIsDeleted.value(props.objectId));
async function onVersionsChanged(changedVersionId: string | undefined, isVersion: boolean, hardDelete: boolean) {
// if doing hard delete or no versions left, redirect to object list
const otherVersions = getVersionsByObjectId.value(props.objectId)
.filter(v=>v.id !== changedVersionId);
// if doing hard delete or no versions left, redirect to parent folder
const otherVersions = allVersions.value.filter(v=>v.id !== changedVersionId);
if (hardDelete || (isVersion && otherVersions.length === 0)) {
router.push({ path: '/list/objects', query: { bucketId: bucketId.value }});
}
Expand All @@ -78,7 +86,7 @@ async function onVersionsChanged(changedVersionId: string | undefined, isVersion
metadataStore.fetchMetadata({ objectId: props.objectId }),
tagStore.fetchTagging({ objectId: props.objectId })
]).then(async () => {
currentVersionId.value = latestVersionId.value;
currentVersionId.value = latestNonDmVersionId.value;
await Promise.all([
versionStore.fetchMetadata({ objectId: props.objectId }),
versionStore.fetchTagging({ objectId: props.objectId })
Expand All @@ -87,6 +95,41 @@ async function onVersionsChanged(changedVersionId: string | undefined, isVersion
}
}
async function onObjectDeleted({ hardDelete }: { hardDelete:boolean }) {
// if doing hard delete redirect to parent folder
if (hardDelete || !bucketVersioningEnabled.value) {
router.push({ path: '/list/objects', query: { bucketId: bucketId.value }});
}
// else stay on page
else {
await Promise.all([
versionStore.fetchVersions({ objectId: props.objectId }),
metadataStore.fetchMetadata({ objectId: props.objectId }),
tagStore.fetchTagging({ objectId: props.objectId })
]).then(async () => {
currentVersionId.value = latestNonDmVersionId.value;
await Promise.all([
versionStore.fetchMetadata({ objectId: props.objectId }),
versionStore.fetchTagging({ objectId: props.objectId })
]);
});
}
}
async function onVersionCreated(newVersionId: string) {
await Promise.all([
versionStore.fetchVersions({ objectId: props.objectId }),
metadataStore.fetchMetadata({ objectId: props.objectId }),
tagStore.fetchTagging({ objectId: props.objectId })
]).then(async () => {
currentVersionId.value = newVersionId;
await Promise.all([
versionStore.fetchMetadata({ objectId: props.objectId }),
versionStore.fetchTagging({ objectId: props.objectId })
]);
});
}
onMounted(async () => {
const head = await objectStore.headObject(props.objectId);
Expand Down Expand Up @@ -158,8 +201,8 @@ onMounted(async () => {
v-if="permissionStore.isObjectActionAllowed(object.id, getUserId, Permissions.DELETE, bucketId)"
:mode="ButtonMode.ICON"
:ids="[object.id]"
:hard="isDeleted"
@on-deleted-success="onVersionsChanged"
:hard-delete="isDeleted || !bucketVersioningEnabled"
@on-object-deleted="onObjectDeleted"
/>
</div>
</div>
Expand All @@ -176,7 +219,7 @@ onMounted(async () => {
v-model:version-id="currentVersionId"
:editable="!isDeleted && (currentVersionId === latestVersionId)"
:object-id="object.id"
@on-metadata-success="onVersionsChanged"
@on-metadata-success="onVersionCreated"
/>
</div>
<Divider layout="vertical" />
Expand All @@ -186,15 +229,16 @@ onMounted(async () => {
v-if="permissionStore.isObjectActionAllowed(object.id, getUserId, Permissions.UPDATE, object.bucketId)"
:bucket-id="bucketId"
:object-id="object.id"
@on-file-uploaded="onVersionsChanged"
@on-file-uploaded="onVersionCreated"
/>
</div>
<ObjectVersion
v-model:version-id="currentVersionId"
:bucket-id="bucketId"
:object-id="object.id"
@on-deleted-success="onVersionsChanged"
@on-restored-success="onVersionsChanged"
@on-object-deleted="onObjectDeleted"
@on-version-deleted="onVersionsChanged"
@on-version-restored="onVersionCreated"
/>
<ObjectTag
v-model:version-id="currentVersionId"
Expand Down
17 changes: 6 additions & 11 deletions frontend/src/components/object/ObjectList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
} from '@/components/object';
import { Button } from '@/lib/primevue';
import { useAuthStore, useObjectStore, useNavStore, usePermissionStore } from '@/store';
import { Permissions, RouteNames } from '@/utils/constants';
import { Permissions } from '@/utils/constants';
import { ButtonMode } from '@/utils/enums';
import { onDialogHide } from '@/utils/utils';
Expand Down Expand Up @@ -62,7 +62,7 @@ const closeUpload = () => {
objectTableKey.value += 1;
};
const onDeletedSuccess = () => {
const onObjectDeleted = () => {
objectTableKey.value += 1;
};
</script>
Expand Down Expand Up @@ -94,18 +94,16 @@ const onDeletedSuccess = () => {
Upload
</Button>
<DownloadObjectButton
v-if="selectedObjectIds.length > 0"
:disabled="displayUpload"
:disabled="displayUpload || selectedObjectIds.length === 0"
:ids="selectedObjectIds"
:mode="ButtonMode.BUTTON"
/>
<DeleteObjectButton
v-if="selectedObjectIds.length > 0"
:disabled="displayUpload"
:disabled="displayUpload || selectedObjectIds.length === 0"
:ids="selectedObjectIds"
:mode="ButtonMode.BUTTON"
:hard="false"
@on-deleted-success="onDeletedSuccess"
:hard-delete="false"
@on-object-deleted="onObjectDeleted"
/>
</div>

Expand All @@ -131,9 +129,6 @@ const onDeletedSuccess = () => {
/>
</div>
</div>
<router-link :to="{ name: RouteNames.LIST_OBJECTS_DELETED, query: { bucketId: props.bucketId } }">
Recycle Bin
</router-link>
</div>
</template>

Expand Down
Loading

0 comments on commit ca4a991

Please sign in to comment.