Skip to content

Commit

Permalink
web/satellite: add file version actions
Browse files Browse the repository at this point in the history
This change adds actions - preview, download and delete - to object
version rows. Multiple versions can now be selected for delete.

Issues: #6833
#6894

Change-Id: I8e6dd3ad03662769507857e58a4a82d3af58cb27
  • Loading branch information
wilfred-asomanii authored and Storj Robot committed Apr 22, 2024
1 parent d90e730 commit 34cb6cc
Show file tree
Hide file tree
Showing 6 changed files with 181 additions and 105 deletions.
19 changes: 12 additions & 7 deletions web/satellite/src/components/BrowserRowActions.vue
Expand Up @@ -5,7 +5,7 @@
<div class="text-no-wrap" :class="alignClass">
<v-btn
v-if="file.type !== 'folder'"
variant="text"
:variant="isVersion ? 'outlined' : 'text'"
color="default"
size="small"
class="mr-1 text-caption"
Expand All @@ -16,10 +16,16 @@
@click="onDownloadClick"
>
<icon-download />
<!-- <v-tooltip activator="parent" location="start">Download</v-tooltip> -->
<v-tooltip
activator="parent"
location="top"
>
Download
</v-tooltip>
</v-btn>

<v-btn
v-if="!isVersion"
variant="text"
color="default"
size="small"
Expand All @@ -33,7 +39,7 @@
</v-btn>

<v-btn
variant="text"
:variant="isVersion ? 'outlined' : 'text'"
color="default"
size="small"
class="mr-1 text-caption"
Expand Down Expand Up @@ -74,7 +80,7 @@
</v-list-item>
</template>

<v-list-item density="comfortable" link rounded="lg" @click="emit('shareClick')">
<v-list-item v-if="!isVersion" density="comfortable" link rounded="lg" @click="emit('shareClick')">
<template #prepend>
<icon-share />
</template>
Expand Down Expand Up @@ -110,25 +116,24 @@ import {
VProgressCircular,
VFadeTransition,
VIcon,
VBtn,
VBtn, VTooltip,
} from 'vuetify/components';
import { mdiDotsHorizontal } from '@mdi/js';
import { BrowserObject, useObjectBrowserStore } from '@/store/modules/objectBrowserStore';
import { useNotify } from '@/utils/hooks';
import { AnalyticsErrorEventSource } from '@/utils/constants/analyticsEventNames';
import { useBucketsStore } from '@/store/modules/bucketsStore';
import IconDownload from '@/components/icons/IconDownload.vue';
import IconShare from '@/components/icons/IconShare.vue';
import IconPreview from '@/components/icons/IconPreview.vue';
import IconTrash from '@/components/icons/IconTrash.vue';
const obStore = useObjectBrowserStore();
const bucketsStore = useBucketsStore();
const notify = useNotify();
const props = defineProps<{
isVersion?: boolean;
file: BrowserObject;
align: 'left' | 'right';
}>();
Expand Down
87 changes: 56 additions & 31 deletions web/satellite/src/components/BrowserTableComponent.vue
Expand Up @@ -21,12 +21,12 @@
<v-data-table-server
v-model="selectedFiles"
v-model:options="options"
v-model:expanded="expandedKeys"
v-model:expanded="expandedFiles"
:sort-by="sortBy"
:headers="headers"
:items="tableFiles"
:search="search"
:item-value="(item: BrowserObjectWrapper) => item.browserObject.path + item.browserObject.Key"
:item-value="(item: BrowserObjectWrapper) => item.browserObject"
:page="cursor.page"
hover
must-sort
Expand All @@ -39,18 +39,21 @@
@update:page="onPageChange"
@update:itemsPerPage="onLimitChange"
>
<template #expanded-row="{ columns, internalItem: {key} }">
<template v-if="!versionsCache.get(key)?.length">
<!-- the key of the row is defined by :item-value="(item: BrowserObjectWrapper) => item.browserObject" above -->
<template #expanded-row="{ columns, internalItem: { key } }">
<template v-if="!versionsCache.get(key.path + key.Key)?.length">
<tr>
<td :colspan="columns.length">
<p class="text-center">No older versions stored</p>
</td>
</tr>
</template>
<tr v-for="file in versionsCache.get(key) as BrowserObject[]" v-else :key="file.VersionId" class="bg-altbg">
<td />
<tr v-for="file in versionsCache.get(key.path + key.Key) as BrowserObject[]" v-else :key="file.VersionId" class="bg-altbg">
<td class="v-data-table__td v-data-table-column--no-padding v-data-table-column--align-start">
<v-checkbox-btn :model-value="selectedFiles.includes(file)" hide-details @update:modelValue="(selected) => toggleSelectObjectVersion(selected as boolean, file)" />
</td>
<td>
<v-list-item class="rounded-lg text-caption pl-1 ml-n1" link>
<v-list-item class="rounded-lg text-caption pl-1 ml-n1" link @click="() => onFileClick(file)">
<template #prepend>
<icon-curve-right />
<icon-versioning-clock class="ml-4 mr-3" size="32" dotted />
Expand All @@ -73,7 +76,15 @@
{{ getFormattedDate(file) }}
</p>
</td>
<td />
<td>
<browser-row-actions
:file="file"
is-version
align="right"
@preview-click="onFileClick(file)"
@delete-file-click="onDeleteFileClick(file)"
/>
</td>
<td />
</tr>
</template>
Expand Down Expand Up @@ -140,7 +151,11 @@
</template>
</v-data-table-server>

<file-preview-dialog v-model="previewDialog" />
<file-preview-dialog
v-model="previewDialog"
v-model:current-file="fileToPreview"
:showing-versions="!!fileToPreview?.VersionId"
/>
</v-card>
<v-snackbar
Expand Down Expand Up @@ -193,6 +208,7 @@ import { useRouter } from 'vue-router';
import {
VBtn,
VCard,
VCheckboxBtn,
VCol,
VDataTableRow,
VDataTableServer,
Expand Down Expand Up @@ -271,6 +287,7 @@ const search = ref<string>('');
const previewDialog = ref<boolean>(false);
const options = ref<TableOptions>();
const fileToDelete = ref<BrowserObject | null>(null);
const fileToPreview = ref<BrowserObject | null>(null);
const isDeleteFileDialogShown = ref<boolean>(false);
const fileToShare = ref<BrowserObject | null>(null);
const isShareDialogShown = ref<boolean>(false);
Expand Down Expand Up @@ -315,9 +332,17 @@ const isPaginationEnabled = computed<boolean>(() => config.state.config.objectBr
const versionsCache = computed<Map<string, BrowserObject[]>>(() => obStore.state.objectVersions);
const expandedKeys = computed<string[]>({
get: () => obStore.state.versionsExpandedKeys,
set: (keys: string[]) => obStore.updateVersionsExpandedKeys(keys),
const expandedFiles = computed<BrowserObject[]>({
get: () => {
const files = obStore.state.versionsExpandedKeys.map(name => {
const parts = name.split('/');
const key = parts.pop();
const path = parts.join('/') + (parts.length ? '/' : '');
return allFiles.value.find(f => f.browserObject.Key === key && f.browserObject.path === path)?.browserObject;
});
return files.filter(f => f !== undefined) as BrowserObject[];
},
set: (files: BrowserObject[]) => obStore.updateVersionsExpandedKeys(files.map(f => f.path + f.Key)),
});
/**
Expand Down Expand Up @@ -402,19 +427,9 @@ const tableFiles = computed<BrowserObjectWrapper[]>(() => {
/**
* Returns a list of path+keys for selected files in the table.
*/
const selectedFiles: WritableComputedRef<string[]> = computed({
get: () => obStore.state.selectedFiles.map(f => {
return f.path + f.Key;
}),
set: (names: string[]) => {
const files = names.map(name => {
const parts = name.split('/');
const key = parts.pop();
const path = parts.join('/') + (parts.length ? '/' : '');
return allFiles.value.find(f => f.browserObject.Key === key && f.browserObject.path === path)?.browserObject;
});
obStore.updateSelectedFiles(files.filter(f => f !== undefined) as BrowserObject[]);
},
const selectedFiles: WritableComputedRef<BrowserObject[]> = computed({
get: () => obStore.state.selectedFiles,
set: obStore.updateSelectedFiles,
});
/**
Expand Down Expand Up @@ -471,6 +486,15 @@ function onLimitChange(newLimit: number): void {
obStore.setCursor({ page: options.value?.page ?? 1, limit: newLimit });
}
function toggleSelectObjectVersion(isSelected: boolean, version: BrowserObject) {
const selected = obStore.state.selectedFiles;
if (isSelected) {
obStore.updateSelectedFiles([...selected, version]);
} else {
obStore.updateSelectedFiles(selected.filter(f => f.VersionId !== version.VersionId));
}
}
/**
* Returns the string form of the file's last modified date.
*/
Expand Down Expand Up @@ -522,6 +546,7 @@ function onFileClick(file: BrowserObject): void {
}
obStore.setObjectPathForModal((file.path ?? '') + file.Key);
fileToPreview.value = file;
previewDialog.value = true;
isFileGuideShown.value = false;
dismissFileGuide();
Expand Down Expand Up @@ -593,14 +618,14 @@ watch(filePath, fetchFiles, { immediate: true });
watch(() => props.forceEmpty, v => !v && fetchFiles());
// watch which table rows are expanded and fetch their versions.
watch(expandedKeys, (keys, oldKeys) => {
const newKeys = keys.filter(key => {
return !oldKeys?.some(oldKey => {
return oldKey === key;
watch(expandedFiles, (objects, oldObjects) => {
const newObjects = objects.filter(obj => {
return !oldObjects?.some(oldObj => {
return oldObj.path + oldObj.Key === obj.path + obj.Key;
});
});
newKeys.forEach(key => {
obStore.listVersions(key);
newObjects.forEach(obj => {
obStore.listVersions(obj.path + obj.Key);
});
});
Expand Down
24 changes: 17 additions & 7 deletions web/satellite/src/components/dialogs/DeleteFileDialog.vue
Expand Up @@ -109,21 +109,31 @@ const innerContent = ref<Component | null>(null);
const filePath = computed<string>(() => bucketsStore.state.fileComponentPath);
const fileCount = computed<number>(() => props.files.filter(file => file.type === 'file').length);
const fileCount = computed<number>(() => props.files.filter(file => file.type === 'file' && !file.VersionId).length);
const folderCount = computed<number>(() => props.files.filter(file => file.type === 'folder').length);
const versionsCount = computed<number>(() => props.files.filter(file => !!file.VersionId).length);
/**
* types of objects to be deleted.
*/
const fileTypes = computed<string>(() => {
if (fileCount.value > 0 && folderCount.value > 0) {
return `file${fileCount.value > 1 ? 's' : ''} and folder${folderCount.value > 1 ? 's' : ''}`;
} else if (folderCount.value > 0) {
return `folder${folderCount.value > 1 ? 's' : ''}`;
} else {
return `file${fileCount.value > 1 ? 's' : ''}`;
let result = '';
if (versionsCount.value > 0) {
result += `version${versionsCount.value > 1 ? 's' : ''}`;
}
if (fileCount.value > 0) {
result += `${result ? ' and' : ''} file${fileCount.value > 1 ? 's' : ''}`;
}
if (folderCount.value > 0) {
result += `${result ? ' and' : ''} folder${folderCount.value > 1 ? 's' : ''}`;
}
return result;
});
const isFolder = computed<boolean>(() => {
Expand Down

0 comments on commit 34cb6cc

Please sign in to comment.