web/satellite/vuetify-poc: unmock file browser table entries
This change shows real file entries in the file browser table, replacing the mock data. Sorting, searching, and folder navigation have been implemented. Resolves #6199 Change-Id: I7360879d2e26605489c20f9d094c3f231fee49cd
This commit is contained in:
parent
ebfbbca1be
commit
0c9c37875d
@ -53,6 +53,11 @@ module.exports = {
|
||||
'pattern': '@/../static/**',
|
||||
'position': 'after',
|
||||
},
|
||||
{
|
||||
'group': 'internal',
|
||||
'pattern': '@poc/assets/**',
|
||||
'position': 'after',
|
||||
},
|
||||
],
|
||||
'newlines-between': 'always',
|
||||
}],
|
||||
|
@ -46,6 +46,7 @@ export class BucketsState {
|
||||
public passphrase = '';
|
||||
public promptForPassphrase = true;
|
||||
public fileComponentBucketName = '';
|
||||
public fileComponentPath = '';
|
||||
public leaveRoute = '';
|
||||
public enterPassphraseCallback: (() => void) | null = null;
|
||||
}
|
||||
@ -210,6 +211,10 @@ export const useBucketsStore = defineStore('buckets', () => {
|
||||
state.fileComponentBucketName = bucketName;
|
||||
}
|
||||
|
||||
function setFileComponentPath(path: string): void {
|
||||
state.fileComponentPath = path;
|
||||
}
|
||||
|
||||
function setEnterPassphraseCallback(fn: (() => void) | null): void {
|
||||
state.enterPassphraseCallback = fn;
|
||||
}
|
||||
@ -304,6 +309,7 @@ export const useBucketsStore = defineStore('buckets', () => {
|
||||
setPassphrase,
|
||||
setApiKey,
|
||||
setFileComponentBucketName,
|
||||
setFileComponentPath,
|
||||
setEnterPassphraseCallback,
|
||||
createBucket,
|
||||
createBucketWithNoPassphrase,
|
||||
|
@ -2,7 +2,7 @@
|
||||
// See LICENSE for copying information.
|
||||
|
||||
<template>
|
||||
<v-breadcrumbs :items="['Buckets', 'Demo']" active-class="font-weight-bold" class="pa-0">
|
||||
<v-breadcrumbs :items="items" active-class="font-weight-bold" class="pa-0">
|
||||
<template #divider>
|
||||
<img src="@poc/assets/icon-right.svg" alt="Breadcrumbs separator" width="10">
|
||||
</template>
|
||||
@ -10,5 +10,52 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { VBreadcrumbs } from 'vuetify/components';
|
||||
|
||||
import { useBucketsStore } from '@/store/modules/bucketsStore';
|
||||
import { useProjectsStore } from '@/store/modules/projectsStore';
|
||||
|
||||
const projectsStore = useProjectsStore();
|
||||
const bucketsStore = useBucketsStore();
|
||||
|
||||
/**
|
||||
* Returns ID of selected project from store.
|
||||
*/
|
||||
const projectId = computed<string>(() => projectsStore.state.selectedProject.id);
|
||||
|
||||
/**
|
||||
* Returns the name of the selected bucket.
|
||||
*/
|
||||
const bucketName = computed<string>(() => bucketsStore.state.fileComponentBucketName);
|
||||
|
||||
/**
|
||||
* Returns the name of the current path within the selected bucket.
|
||||
*/
|
||||
const filePath = computed<string>(() => bucketsStore.state.fileComponentPath);
|
||||
|
||||
type BreadcrumbItem = {
|
||||
title: string;
|
||||
to: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns breadcrumb items corresponding to parts in the file browser path.
|
||||
*/
|
||||
const items = computed<BreadcrumbItem[]>(() => {
|
||||
const bucketsURL = `/projects/${projectId.value}/buckets`;
|
||||
|
||||
const pathParts = [bucketName.value];
|
||||
if (filePath.value) pathParts.push(...filePath.value.split('/'));
|
||||
|
||||
return [
|
||||
{ title: 'Buckets', to: bucketsURL },
|
||||
...pathParts.map<BreadcrumbItem>((part, index) => {
|
||||
const suffix = pathParts.slice(0, index + 1)
|
||||
.map(part => encodeURIComponent(part))
|
||||
.join('/');
|
||||
return { title: part, to: `${bucketsURL}/${suffix}` };
|
||||
}),
|
||||
];
|
||||
});
|
||||
</script>
|
||||
|
@ -17,30 +17,45 @@
|
||||
class="mx-2 mt-2"
|
||||
/>
|
||||
|
||||
<v-data-table
|
||||
<v-data-table-server
|
||||
v-model="selected"
|
||||
v-model:options="options"
|
||||
:sort-by="sortBy"
|
||||
:headers="headers"
|
||||
:items="files"
|
||||
:items="tableFiles"
|
||||
:search="search"
|
||||
class="elevation-1"
|
||||
item-key="path"
|
||||
:item-value="(item: BrowserObjectWrapper) => item.browserObject.Key"
|
||||
show-select
|
||||
hover
|
||||
must-sort
|
||||
:loading="isFetching || loading"
|
||||
:items-length="allFiles.length"
|
||||
>
|
||||
<template #item.name="{ item }">
|
||||
<div>
|
||||
<template #item.name="{ item }: ItemSlotProps">
|
||||
<v-btn
|
||||
class="rounded-lg w-100 pl-1 pr-4 justify-start font-weight-bold"
|
||||
variant="text"
|
||||
height="40"
|
||||
color="default"
|
||||
@click="previewFile"
|
||||
block
|
||||
@click="onFileClick(item.raw.browserObject)"
|
||||
>
|
||||
<img :src="icons.get(item.raw.icon) || fileIcon" alt="Item icon" class="mr-3">
|
||||
{{ item.raw.name }}
|
||||
<img :src="item.raw.typeInfo.icon" :alt="item.raw.typeInfo.title + 'icon'" class="mr-3">
|
||||
{{ item.raw.browserObject.Key }}
|
||||
</v-btn>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #item.type="{ item }: ItemSlotProps">
|
||||
{{ item.raw.typeInfo.title }}
|
||||
</template>
|
||||
|
||||
<template #item.size="{ item }: ItemSlotProps">
|
||||
{{ getFormattedSize(item.raw.browserObject) }}
|
||||
</template>
|
||||
|
||||
<template #item.date="{ item }: ItemSlotProps">
|
||||
{{ getFormattedDate(item.raw.browserObject) }}
|
||||
</template>
|
||||
|
||||
<template #item.actions>
|
||||
@ -70,17 +85,17 @@
|
||||
</v-btn>
|
||||
</div>
|
||||
</template>
|
||||
</v-data-table>
|
||||
</v-data-table-server>
|
||||
|
||||
<v-dialog v-model="previewDialog" transition="fade-transition" class="preview-dialog" fullscreen theme="dark">
|
||||
<v-card class="preview-card">
|
||||
<v-carousel hide-delimiters show-arrows="hover" height="100vh">
|
||||
<template #prev="{ props }">
|
||||
<template #prev="{ props: slotProps }">
|
||||
<v-btn
|
||||
color="default"
|
||||
class="rounded-circle"
|
||||
icon
|
||||
@click="props.onClick"
|
||||
@click="slotProps.onClick"
|
||||
>
|
||||
<svg width="10" height="17" viewBox="0 0 10 17" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_24843_332342)">
|
||||
@ -94,12 +109,12 @@
|
||||
</svg>
|
||||
</v-btn>
|
||||
</template>
|
||||
<template #next="{ props }">
|
||||
<template #next="{ props: slotProps }">
|
||||
<v-btn
|
||||
color="default"
|
||||
class="rounded-circle"
|
||||
icon
|
||||
@click="props.onClick"
|
||||
@click="slotProps.onClick"
|
||||
>
|
||||
<svg width="10" height="17" viewBox="0 0 10 17" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_24843_332338)">
|
||||
@ -182,7 +197,8 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { ref, computed, watch } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import {
|
||||
VCard,
|
||||
VTextField,
|
||||
@ -195,7 +211,19 @@ import {
|
||||
VCarouselItem,
|
||||
VIcon,
|
||||
} from 'vuetify/components';
|
||||
import { VDataTable } from 'vuetify/labs/components';
|
||||
import { VDataTableServer } from 'vuetify/labs/components';
|
||||
|
||||
import { BrowserObject, useObjectBrowserStore } from '@/store/modules/objectBrowserStore';
|
||||
import { useProjectsStore } from '@/store/modules/projectsStore';
|
||||
import { useNotify } from '@/utils/hooks';
|
||||
import { SHORT_MONTHS_NAMES } from '@/utils/constants/date';
|
||||
import { Size } from '@/utils/bytesSize';
|
||||
import { AnalyticsErrorEventSource } from '@/utils/constants/analyticsEventNames';
|
||||
import { useBucketsStore } from '@/store/modules/bucketsStore';
|
||||
|
||||
import IconShare from '@poc/components/icons/IconShare.vue';
|
||||
import IconDownload from '@poc/components/icons/IconDownload.vue';
|
||||
import BrowserActionsMenu from '@poc/components/BrowserActionsMenu.vue';
|
||||
|
||||
import folderIcon from '@poc/assets/icon-folder-tonal.svg';
|
||||
import pdfIcon from '@poc/assets/icon-pdf-tonal.svg';
|
||||
@ -207,25 +235,51 @@ import zipIcon from '@poc/assets/icon-zip-tonal.svg';
|
||||
import spreadsheetIcon from '@poc/assets/icon-spreadsheet-tonal.svg';
|
||||
import fileIcon from '@poc/assets/icon-file-tonal.svg';
|
||||
|
||||
import IconShare from '@poc/components/icons/IconShare.vue';
|
||||
import IconDownload from '@poc/components/icons/IconDownload.vue';
|
||||
import BrowserActionsMenu from '@poc/components/BrowserActionsMenu.vue';
|
||||
type SortKey = 'name' | 'type' | 'size' | 'date';
|
||||
|
||||
type TableOptions = {
|
||||
page: number;
|
||||
itemsPerPage: number;
|
||||
sortBy: {
|
||||
key: SortKey;
|
||||
order: 'asc' | 'desc';
|
||||
}[];
|
||||
};
|
||||
|
||||
type BrowserObjectTypeInfo = {
|
||||
title: string;
|
||||
icon: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Contains extra information to aid in the display, filtering, and sorting of browser objects.
|
||||
*/
|
||||
type BrowserObjectWrapper = {
|
||||
browserObject: BrowserObject;
|
||||
typeInfo: BrowserObjectTypeInfo;
|
||||
lowerName: string;
|
||||
ext: string;
|
||||
};
|
||||
|
||||
type ItemSlotProps = { item: { raw: BrowserObjectWrapper } };
|
||||
|
||||
const props = defineProps<{
|
||||
forceEmpty?: boolean;
|
||||
loading?: boolean;
|
||||
}>();
|
||||
|
||||
const obStore = useObjectBrowserStore();
|
||||
const projectsStore = useProjectsStore();
|
||||
const bucketsStore = useBucketsStore();
|
||||
|
||||
const notify = useNotify();
|
||||
const router = useRouter();
|
||||
|
||||
const isFetching = ref<boolean>(false);
|
||||
const search = ref<string>('');
|
||||
const selected = ref([]);
|
||||
const previewDialog = ref<boolean>(false);
|
||||
|
||||
const icons = new Map<string, string>([
|
||||
['folder', folderIcon],
|
||||
['pdf', pdfIcon],
|
||||
['image', imageIcon],
|
||||
['video', videoIcon],
|
||||
['audio', audioIcon],
|
||||
['text', textIcon],
|
||||
['zip', zipIcon],
|
||||
['spreadsheet', spreadsheetIcon],
|
||||
['file', fileIcon],
|
||||
]);
|
||||
const options = ref<TableOptions>();
|
||||
|
||||
const items = [
|
||||
{ src: 'https://cdn.vuetifyjs.com/images/carousel/squirrel.jpg' },
|
||||
@ -240,97 +294,174 @@ const headers = [
|
||||
align: 'start',
|
||||
key: 'name',
|
||||
},
|
||||
{ title: 'Type', key:'type' },
|
||||
{ title: 'Type', key: 'type' },
|
||||
{ title: 'Size', key: 'size' },
|
||||
{ title: 'Date', key: 'date' },
|
||||
{ title: '', key: 'actions', sortable: false, width: 0 },
|
||||
];
|
||||
const files = [
|
||||
{
|
||||
name: 'Be The Cloud',
|
||||
path: 'folder-1',
|
||||
type: 'Folder',
|
||||
size: '2 GB',
|
||||
date: '02 Mar 2023',
|
||||
icon: 'folder',
|
||||
},
|
||||
{
|
||||
name: 'Folder',
|
||||
path: 'folder-2',
|
||||
type: 'Folder',
|
||||
size: '458 MB',
|
||||
date: '21 Apr 2023',
|
||||
icon: 'folder',
|
||||
},
|
||||
{
|
||||
name: 'Presentation.pdf',
|
||||
path: 'Presentation.pdf',
|
||||
type: 'PDF',
|
||||
size: '150 KB',
|
||||
date: '24 Mar 2023',
|
||||
icon: 'pdf',
|
||||
},
|
||||
{
|
||||
name: 'Image.jpg',
|
||||
path: 'image.jpg',
|
||||
type: 'JPG',
|
||||
size: '500 KB',
|
||||
date: '12 Mar 2023',
|
||||
icon: 'image',
|
||||
},
|
||||
{
|
||||
name: 'Video.mp4',
|
||||
path: 'video.mp4',
|
||||
type: 'MP4',
|
||||
size: '3 MB',
|
||||
date: '01 Apr 2023',
|
||||
icon: 'video',
|
||||
},
|
||||
{
|
||||
name: 'Song.mp3',
|
||||
path: 'Song.mp3',
|
||||
type: 'MP3',
|
||||
size: '8 MB',
|
||||
date: '22 May 2023',
|
||||
icon: 'audio',
|
||||
},
|
||||
{
|
||||
name: 'Text.txt',
|
||||
path: 'text.txt',
|
||||
type: 'TXT',
|
||||
size: '2 KB',
|
||||
date: '21 May 2023',
|
||||
icon: 'text',
|
||||
},
|
||||
{
|
||||
name: 'NewArchive.zip',
|
||||
path: 'newarchive.zip',
|
||||
type: 'ZIP',
|
||||
size: '21 GB',
|
||||
date: '20 May 2023',
|
||||
icon: 'zip',
|
||||
},
|
||||
{
|
||||
name: 'Table.csv',
|
||||
path: 'table.csv',
|
||||
type: 'CSV',
|
||||
size: '3 MB',
|
||||
date: '20 May 2023',
|
||||
icon: 'spreadsheet',
|
||||
},
|
||||
{
|
||||
name: 'Map-export.json',
|
||||
path: 'map-export.json',
|
||||
type: 'JSON',
|
||||
size: '1 MB',
|
||||
date: '23 May 2023',
|
||||
icon: 'file',
|
||||
},
|
||||
];
|
||||
const collator = new Intl.Collator('en', { sensitivity: 'case' });
|
||||
|
||||
const extensionInfos: Map<string[], BrowserObjectTypeInfo> = new Map([
|
||||
[['jpg', 'jpeg', 'png', 'gif'], { title: 'Image', icon: imageIcon }],
|
||||
[['mp4', 'mkv', 'mov'], { title: 'Video', icon: videoIcon }],
|
||||
[['mp3', 'aac', 'wav', 'm4a'], { title: 'Audio', icon: audioIcon }],
|
||||
[['txt', 'docx', 'doc', 'pages'], { title: 'Text', icon: textIcon }],
|
||||
[['pdf'], { title: 'PDF', icon: pdfIcon }],
|
||||
[['zip'], { title: 'ZIP', icon: zipIcon }],
|
||||
[['xls', 'numbers', 'csv', 'xlsx', 'tsv'], { title: 'Spreadsheet', icon: spreadsheetIcon }],
|
||||
]);
|
||||
const folderInfo: BrowserObjectTypeInfo = { title: 'Folder', icon: folderIcon };
|
||||
const fileInfo: BrowserObjectTypeInfo = { title: 'File', icon: fileIcon };
|
||||
|
||||
/**
|
||||
* Returns the name of the selected bucket.
|
||||
*/
|
||||
const bucketName = computed<string>(() => bucketsStore.state.fileComponentBucketName);
|
||||
|
||||
/**
|
||||
* Returns the name of the current path within the selected bucket.
|
||||
*/
|
||||
const filePath = computed<string>(() => bucketsStore.state.fileComponentPath);
|
||||
|
||||
/**
|
||||
* Returns every file under the current path.
|
||||
*/
|
||||
const allFiles = computed<BrowserObjectWrapper[]>(() => {
|
||||
if (props.forceEmpty) return [];
|
||||
return obStore.state.files.map<BrowserObjectWrapper>(file => {
|
||||
const lowerName = file.Key.toLowerCase();
|
||||
const dotIdx = lowerName.indexOf('.');
|
||||
const ext = dotIdx === -1 ? '' : file.Key.slice(dotIdx + 1);
|
||||
return {
|
||||
browserObject: file,
|
||||
typeInfo: getFileTypeInfo(ext, file.type),
|
||||
lowerName,
|
||||
ext,
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Returns every file under the current path that matchs the search query.
|
||||
*/
|
||||
const filteredFiles = computed<BrowserObjectWrapper[]>(() => {
|
||||
if (!search.value) return allFiles.value;
|
||||
const searchLower = search.value.toLowerCase();
|
||||
return allFiles.value.filter(file => file.lowerName.includes(searchLower));
|
||||
});
|
||||
|
||||
/**
|
||||
* Returns the files to be displayed in the table.
|
||||
*/
|
||||
const tableFiles = computed<BrowserObjectWrapper[]>(() => {
|
||||
const opts = options.value;
|
||||
if (!opts) return [];
|
||||
|
||||
const files = [...filteredFiles.value];
|
||||
|
||||
if (opts.sortBy.length) {
|
||||
const sortBy = opts.sortBy[0];
|
||||
|
||||
type CompareFunc = (a: BrowserObjectWrapper, b: BrowserObjectWrapper) => number;
|
||||
const compareFuncs: Record<SortKey, CompareFunc> = {
|
||||
name: (a, b) => collator.compare(a.browserObject.Key, b.browserObject.Key),
|
||||
type: (a, b) => collator.compare(a.typeInfo.title, b.typeInfo.title) || collator.compare(a.ext, b.ext),
|
||||
size: (a, b) => a.browserObject.Size - b.browserObject.Size,
|
||||
date: (a, b) => a.browserObject.LastModified.getTime() - b.browserObject.LastModified.getTime(),
|
||||
};
|
||||
|
||||
files.sort((a, b) => {
|
||||
const objA = a.browserObject, objB = b.browserObject;
|
||||
if (sortBy.key !== 'type') {
|
||||
if (objA.type === 'folder') {
|
||||
if (objB.type !== 'folder') return -1;
|
||||
if (sortBy.key === 'size' || sortBy.key === 'date') return 0;
|
||||
} else if (objB.type === 'folder') {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
const cmp = compareFuncs[sortBy.key](a, b);
|
||||
return sortBy.order === 'asc' ? cmp : -cmp;
|
||||
});
|
||||
}
|
||||
|
||||
if (opts.itemsPerPage === -1) return files;
|
||||
|
||||
return files.slice((opts.page - 1) * opts.itemsPerPage, opts.page * opts.itemsPerPage);
|
||||
});
|
||||
|
||||
/**
|
||||
* Returns the string form of the file's last modified date.
|
||||
*/
|
||||
function getFormattedDate(file: BrowserObject): string {
|
||||
if (file.type === 'folder') return '';
|
||||
const date = file.LastModified;
|
||||
return `${date.getDate()} ${SHORT_MONTHS_NAMES[date.getMonth()]} ${date.getFullYear()}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the string form of the file's size.
|
||||
*/
|
||||
function getFormattedSize(file: BrowserObject): string {
|
||||
if (file.type === 'folder') return '';
|
||||
const size = new Size(file.Size);
|
||||
return `${size.formattedBytes} ${size.label}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the title and icon representing a file's type.
|
||||
*/
|
||||
function getFileTypeInfo(ext: string, type: BrowserObject['type']): BrowserObjectTypeInfo {
|
||||
if (!type) return fileInfo;
|
||||
if (type === 'folder') return folderInfo;
|
||||
|
||||
ext = ext.toLowerCase();
|
||||
for (const [exts, info] of extensionInfos.entries()) {
|
||||
if (exts.indexOf(ext) !== -1) return info;
|
||||
}
|
||||
return fileInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles file click.
|
||||
*/
|
||||
function onFileClick(file: BrowserObject): void {
|
||||
if (!file.type) return;
|
||||
|
||||
if (file.type === 'folder') {
|
||||
const uriParts = [file.Key];
|
||||
if (filePath.value) {
|
||||
uriParts.unshift(...filePath.value.split('/'));
|
||||
}
|
||||
const pathAndKey = uriParts.map(part => encodeURIComponent(part)).join('/');
|
||||
router.push(`/projects/${projectsStore.state.selectedProject.id}/buckets/${bucketName.value}/${pathAndKey}`);
|
||||
return;
|
||||
}
|
||||
|
||||
function previewFile(): void {
|
||||
// Implement logic to fetch the file content for preview or generate a URL for preview
|
||||
// Then, open the preview dialog
|
||||
previewDialog.value = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches all files in the current directory.
|
||||
*/
|
||||
async function fetchFiles(): Promise<void> {
|
||||
if (isFetching.value || props.forceEmpty) return;
|
||||
isFetching.value = true;
|
||||
|
||||
try {
|
||||
await obStore.list(filePath.value ? filePath.value + '/' : '');
|
||||
selected.value = [];
|
||||
} catch (err) {
|
||||
err.message = `Error fetching files. ${err.message}`;
|
||||
notify.notifyError(err, AnalyticsErrorEventSource.FILE_BROWSER_LIST_CALL);
|
||||
}
|
||||
|
||||
isFetching.value = false;
|
||||
}
|
||||
|
||||
watch(filePath, fetchFiles, { immediate: true });
|
||||
watch(() => props.forceEmpty, v => !v && fetchFiles());
|
||||
</script>
|
||||
|
@ -56,8 +56,6 @@ const isLoading = ref<boolean>(true);
|
||||
* all projects dashboard if no such project exists.
|
||||
*/
|
||||
async function selectProject(projectId: string): Promise<void> {
|
||||
isLoading.value = true;
|
||||
|
||||
let projects: Project[];
|
||||
try {
|
||||
projects = await projectsStore.getProjects();
|
||||
@ -74,17 +72,22 @@ async function selectProject(projectId: string): Promise<void> {
|
||||
return;
|
||||
}
|
||||
projectsStore.selectProject(projectId);
|
||||
|
||||
isLoading.value = false;
|
||||
}
|
||||
|
||||
watch(() => route.params.projectId, async newId => selectProject(newId as string));
|
||||
watch(() => route.params.projectId, async newId => {
|
||||
if (newId === undefined) return;
|
||||
isLoading.value = true;
|
||||
await selectProject(newId as string);
|
||||
isLoading.value = false;
|
||||
});
|
||||
|
||||
/**
|
||||
* Lifecycle hook after initial render.
|
||||
* Pre-fetches user`s and project information.
|
||||
*/
|
||||
onBeforeMount(async () => {
|
||||
isLoading.value = true;
|
||||
|
||||
try {
|
||||
await Promise.all([
|
||||
usersStore.getUser(),
|
||||
@ -112,8 +115,10 @@ onBeforeMount(async () => {
|
||||
notify.notifyError(error, AnalyticsErrorEventSource.OVERALL_APP_WRAPPER_ERROR);
|
||||
}
|
||||
|
||||
selectProject(route.params.projectId as string);
|
||||
await selectProject(route.params.projectId as string);
|
||||
|
||||
if (!agStore.state.accessGrantsWebWorker) await agStore.startWorker();
|
||||
|
||||
isLoading.value = false;
|
||||
});
|
||||
</script>
|
||||
|
@ -80,7 +80,7 @@ export default createVuetify({
|
||||
VBtn: {
|
||||
density: 'default',
|
||||
rounded: 'lg',
|
||||
class: 'text-capitalize font-weight-bold',
|
||||
class: 'text-none font-weight-bold',
|
||||
style: 'letter-spacing:0;',
|
||||
},
|
||||
VTooltip: {
|
||||
|
@ -58,7 +58,7 @@ const routes: RouteRecordRaw[] = [
|
||||
component: () => import(/* webpackChunkName: "Buckets" */ '@poc/views/Buckets.vue'),
|
||||
},
|
||||
{
|
||||
path: 'buckets/:bucketName',
|
||||
path: 'buckets/:browserPath+',
|
||||
name: 'Bucket',
|
||||
component: () => import(/* webpackChunkName: "Bucket" */ '@poc/views/Bucket.vue'),
|
||||
},
|
||||
|
@ -2,19 +2,20 @@
|
||||
// See LICENSE for copying information.
|
||||
|
||||
<template>
|
||||
<v-container v-if="hasObjects">
|
||||
<PageTitleComponent title="Browse Files" />
|
||||
<v-container>
|
||||
<page-title-component title="Browse Files" />
|
||||
|
||||
<BrowserBreadcrumbsComponent />
|
||||
<browser-breadcrumbs-component />
|
||||
|
||||
<v-col>
|
||||
<v-row class="mt-2 mb-4">
|
||||
<v-btn
|
||||
color="primary"
|
||||
min-width="120"
|
||||
:disabled="isContentDisabled"
|
||||
@click="snackbar = true"
|
||||
>
|
||||
<BrowserSnackbarComponent :on-cancel="() => { snackbar = false }" />
|
||||
<browser-snackbar-component :on-cancel="() => { snackbar = false }" />
|
||||
<IconUpload />
|
||||
Upload
|
||||
</v-btn>
|
||||
@ -23,26 +24,36 @@
|
||||
variant="outlined"
|
||||
color="default"
|
||||
class="mx-4"
|
||||
:disabled="isContentDisabled"
|
||||
>
|
||||
<IconFolder />
|
||||
<icon-folder />
|
||||
New Folder
|
||||
<BrowserNewFolderDialog />
|
||||
<browser-new-folder-dialog />
|
||||
</v-btn>
|
||||
</v-row>
|
||||
</v-col>
|
||||
|
||||
<BrowserTableComponent />
|
||||
<browser-table-component :loading="isLoading" :force-empty="isContentDisabled" />
|
||||
</v-container>
|
||||
|
||||
<EnterBucketPassphraseDialog v-model="isBucketPassphraseDialogOpen" @passphraseEntered="getObjects" />
|
||||
<enter-bucket-passphrase-dialog
|
||||
v-if="!isLoading"
|
||||
v-model="isBucketPassphraseDialogOpen"
|
||||
@passphrase-entered="initObjectStore"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
import { computed, onMounted, ref, watch } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { VContainer, VCol, VRow, VBtn } from 'vuetify/components';
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
import { useBucketsStore } from '@/store/modules/bucketsStore';
|
||||
import { useObjectBrowserStore } from '@/store/modules/objectBrowserStore';
|
||||
import { useProjectsStore } from '@/store/modules/projectsStore';
|
||||
import { EdgeCredentials } from '@/types/accessGrants';
|
||||
import { useNotify } from '@/utils/hooks';
|
||||
import { AnalyticsErrorEventSource } from '@/utils/constants/analyticsEventNames';
|
||||
|
||||
import PageTitleComponent from '@poc/components/PageTitleComponent.vue';
|
||||
import BrowserBreadcrumbsComponent from '@poc/components/BrowserBreadcrumbsComponent.vue';
|
||||
@ -54,27 +65,93 @@ import IconFolder from '@poc/components/icons/IconFolder.vue';
|
||||
import EnterBucketPassphraseDialog from '@poc/components/dialogs/EnterBucketPassphraseDialog.vue';
|
||||
|
||||
const bucketsStore = useBucketsStore();
|
||||
const obStore = useObjectBrowserStore();
|
||||
const projectsStore = useProjectsStore();
|
||||
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const notify = useNotify();
|
||||
|
||||
const isLoading = ref<boolean>(true);
|
||||
const isContentDisabled = ref<boolean>(true);
|
||||
const snackbar = ref<boolean>(false);
|
||||
const isBucketPassphraseDialogOpen = ref(false);
|
||||
const hasObjects = ref(false);
|
||||
const isBucketPassphraseDialogOpen = ref<boolean>(bucketsStore.state.promptForPassphrase);
|
||||
|
||||
const bucketName = computed(() => {
|
||||
return bucketsStore.state.fileComponentBucketName;
|
||||
});
|
||||
/**
|
||||
* Returns the name of the selected bucket.
|
||||
*/
|
||||
const bucketName = computed<string>(() => bucketsStore.state.fileComponentBucketName);
|
||||
|
||||
function getObjects() {
|
||||
hasObjects.value = true;
|
||||
/**
|
||||
* Returns edge credentials from store.
|
||||
*/
|
||||
const edgeCredentials = computed((): EdgeCredentials => bucketsStore.state.edgeCredentials);
|
||||
|
||||
/**
|
||||
* Returns ID of selected project from store.
|
||||
*/
|
||||
const projectId = computed<string>(() => projectsStore.state.selectedProject.id);
|
||||
|
||||
/**
|
||||
* Returns whether the user should be prompted to enter the passphrase.
|
||||
*/
|
||||
const isPromptForPassphrase = computed<boolean>(() => bucketsStore.state.promptForPassphrase);
|
||||
|
||||
/**
|
||||
* Initializes object browser store.
|
||||
*/
|
||||
function initObjectStore(): void {
|
||||
obStore.init({
|
||||
endpoint: edgeCredentials.value.endpoint,
|
||||
accessKey: edgeCredentials.value.accessKeyId,
|
||||
secretKey: edgeCredentials.value.secretKey,
|
||||
bucket: bucketName.value,
|
||||
browserRoot: '', // unused
|
||||
});
|
||||
isContentDisabled.value = false;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (!bucketName.value) {
|
||||
// navigated here via direct link or reloaded this page
|
||||
bucketsStore.setFileComponentBucketName(route.params['bucketName'] as string);
|
||||
isBucketPassphraseDialogOpen.value = true;
|
||||
watch(isBucketPassphraseDialogOpen, isOpen => {
|
||||
if (isOpen || !isPromptForPassphrase.value) return;
|
||||
router.push(`/projects/${projectId.value}/dashboard`);
|
||||
});
|
||||
|
||||
watch(() => route.params.browserPath, browserPath => {
|
||||
if (browserPath === undefined) return;
|
||||
|
||||
let bucketName = '', filePath = '';
|
||||
if (typeof browserPath === 'string') {
|
||||
bucketName = browserPath;
|
||||
} else {
|
||||
bucketName = browserPath[0];
|
||||
filePath = browserPath.slice(1).join('/');
|
||||
}
|
||||
|
||||
bucketsStore.setFileComponentBucketName(bucketName);
|
||||
bucketsStore.setFileComponentPath(filePath);
|
||||
}, { immediate: true });
|
||||
|
||||
/**
|
||||
* Initializes file browser.
|
||||
*/
|
||||
onMounted(async () => {
|
||||
const dashboardURL = `/projects/${projectId.value}/dashboard`;
|
||||
|
||||
try {
|
||||
await bucketsStore.getAllBucketsNames(projectId.value);
|
||||
} catch (error) {
|
||||
error.message = `Error fetching bucket names. ${error.message}`;
|
||||
notify.notifyError(error, AnalyticsErrorEventSource.UPLOAD_FILE_VIEW);
|
||||
return;
|
||||
}
|
||||
getObjects();
|
||||
|
||||
if (bucketsStore.state.allBucketNames.indexOf(bucketName.value) === -1) {
|
||||
router.push(dashboardURL);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isPromptForPassphrase.value) initObjectStore();
|
||||
|
||||
isLoading.value = false;
|
||||
});
|
||||
</script>
|
||||
|
Loading…
Reference in New Issue
Block a user