web/satellite: add loader to object browser table

This is a fix based on early feedback from QA team.
Added loader to object browser table so that user can't change pages while request is still in progress because it breaks pagination.

Change-Id: I5cc2ff057955478b3c745c169d520e1a639eff92
This commit is contained in:
Vitalii 2023-09-14 16:34:55 +03:00 committed by Storj Robot
parent bd48a5cbe6
commit 92a69c7de4
4 changed files with 64 additions and 42 deletions

View File

@ -111,6 +111,7 @@
:total-page-count="isPaginationEnabled ? pageCount : 0" :total-page-count="isPaginationEnabled ? pageCount : 0"
:total-items-count="isPaginationEnabled ? fetchedObjectsCount : files.length" :total-items-count="isPaginationEnabled ? fetchedObjectsCount : files.length"
show-select show-select
:loading="isLoading"
class="file-browser-table" class="file-browser-table"
:on-page-change="isPaginationEnabled ? changePageAndLimit : null" :on-page-change="isPaginationEnabled ? changePageAndLimit : null"
@selectAllClicked="toggleSelectAllFiles" @selectAllClicked="toggleSelectAllFiles"
@ -193,7 +194,7 @@
</template> </template>
</v-table> </v-table>
<div <div
v-if="!fetchingFilesSpinner" v-if="!isLoading"
class="upload-help" class="upload-help"
@click="buttonFileUpload" @click="buttonFileUpload"
> >
@ -202,12 +203,6 @@
Drop Files Here to Upload Drop Files Here to Upload
</p> </p>
</div> </div>
<div
v-else
class="d-flex justify-content-center"
>
<div class="spinner-border" />
</div>
</div> </div>
</div> </div>
</div> </div>
@ -239,6 +234,7 @@ import { useBucketsStore } from '@/store/modules/bucketsStore';
import { useConfigStore } from '@/store/modules/configStore'; import { useConfigStore } from '@/store/modules/configStore';
import { useAnalyticsStore } from '@/store/modules/analyticsStore'; import { useAnalyticsStore } from '@/store/modules/analyticsStore';
import { DEFAULT_PAGE_LIMIT } from '@/types/pagination'; import { DEFAULT_PAGE_LIMIT } from '@/types/pagination';
import { useLoading } from '@/composables/useLoading';
import VButton from '@/components/common/VButton.vue'; import VButton from '@/components/common/VButton.vue';
import BucketSettingsNav from '@/components/objects/BucketSettingsNav.vue'; import BucketSettingsNav from '@/components/objects/BucketSettingsNav.vue';
@ -261,11 +257,11 @@ const analyticsStore = useAnalyticsStore();
const router = useRouter(); const router = useRouter();
const route = useRoute(); const route = useRoute();
const notify = useNotify(); const notify = useNotify();
const { isLoading, withLoading } = useLoading();
const folderInput = ref<HTMLInputElement>(); const folderInput = ref<HTMLInputElement>();
const fileInput = ref<HTMLInputElement>(); const fileInput = ref<HTMLInputElement>();
const fetchingFilesSpinner = ref<boolean>(false);
const isUploadDropDownShown = ref<boolean>(false); const isUploadDropDownShown = ref<boolean>(false);
const isLockedBanner = ref<boolean>(true); const isLockedBanner = ref<boolean>(true);
const isTooManyObjectsBanner = ref<boolean>(true); const isTooManyObjectsBanner = ref<boolean>(true);
@ -363,7 +359,7 @@ const objectsCount = computed((): number => {
const lockedFilesEntryDisplayed = computed((): boolean => { const lockedFilesEntryDisplayed = computed((): boolean => {
return lockedFilesCount.value > 0 && return lockedFilesCount.value > 0 &&
objectsCount.value <= NUMBER_OF_DISPLAYED_OBJECTS && objectsCount.value <= NUMBER_OF_DISPLAYED_OBJECTS &&
!fetchingFilesSpinner.value && !isLoading.value &&
!currentPath.value; !currentPath.value;
}); });
@ -444,7 +440,7 @@ const bucket = computed((): string => {
/** /**
* Changes table page and limit. * Changes table page and limit.
*/ */
function changePageAndLimit(page: number, limit: number): void { async function changePageAndLimit(page: number, limit: number): void {
obStore.setCursor({ limit, page }); obStore.setCursor({ limit, page });
const lastObjectOnPage = page * limit; const lastObjectOnPage = page * limit;
@ -454,15 +450,17 @@ function changePageAndLimit(page: number, limit: number): void {
return; return;
} }
const tokenKey = Math.ceil(lastObjectOnPage / MAX_KEY_COUNT) * MAX_KEY_COUNT; await withLoading(async () => {
const tokenKey = Math.ceil(lastObjectOnPage / MAX_KEY_COUNT) * MAX_KEY_COUNT;
const tokenToBeFetched = obStore.state.continuationTokens.get(tokenKey); const tokenToBeFetched = obStore.state.continuationTokens.get(tokenKey);
if (!tokenToBeFetched) { if (!tokenToBeFetched) {
obStore.initList(routePath.value); await obStore.initList(routePath.value);
return; return;
} }
obStore.listByToken(routePath.value, tokenKey, tokenToBeFetched); await obStore.listByToken(routePath.value, tokenKey, tokenToBeFetched);
});
} }
/** /**
@ -496,11 +494,13 @@ async function onRouteChange(): Promise<void> {
routePath.value = calculateRoutePath(); routePath.value = calculateRoutePath();
obStore.closeDropdown(); obStore.closeDropdown();
if (isPaginationEnabled.value) { await withLoading(async () => {
await obStore.initList(routePath.value); if (isPaginationEnabled.value) {
} else { await obStore.initList(routePath.value);
await list(routePath.value); } else {
} await list(routePath.value);
}
});
} }
/** /**
@ -653,24 +653,20 @@ onBeforeMount(async () => {
// clear previous file selections. // clear previous file selections.
obStore.clearAllSelectedFiles(); obStore.clearAllSelectedFiles();
// display the spinner while files are being fetched await withLoading(async () => {
fetchingFilesSpinner.value = true; try {
if (isPaginationEnabled.value) {
try { await obStore.initList('');
if (isPaginationEnabled.value) { } else {
await obStore.initList(''); await Promise.all([
} else { list(''),
await Promise.all([ obStore.getObjectCount(),
list(''), ]);
obStore.getObjectCount(), }
]); } catch (err) {
notify.error(err.message, AnalyticsErrorEventSource.FILE_BROWSER_LIST_CALL);
} }
} catch (err) { });
notify.error(err.message, AnalyticsErrorEventSource.FILE_BROWSER_LIST_CALL);
}
// remove the spinner after files have been fetched
fetchingFilesSpinner.value = false;
}); });
onBeforeUnmount(() => { onBeforeUnmount(() => {

View File

@ -10,7 +10,7 @@
<span v-else class="size-changer__selector__content__label">Size</span> <span v-else class="size-changer__selector__content__label">Size</span>
<arrow-down-icon class="size-changer__selector__content__arrow" :class="{ open: isOpen }" /> <arrow-down-icon class="size-changer__selector__content__arrow" :class="{ open: isOpen }" />
</div> </div>
<div v-if="isOpen" v-click-outside="closeSelector" class="size-changer__selector__dropdown"> <div v-if="isOpen" v-click-outside="closeSelector" class="size-changer__selector__dropdown" :class="{ 'custom-top': !withAllOption }">
<div <div
v-for="(option, index) in options" v-for="(option, index) in options"
:key="index" :key="index"
@ -54,12 +54,16 @@ const options = computed((): {label:string, value:number}[] => {
{ label: '50', value: 50 }, { label: '50', value: 50 },
{ label: '100', value: 100 }, { label: '100', value: 100 },
]; ];
if (props.itemCount && props.itemCount < 1000 && !props.simplePagination) { if (props.itemCount && withAllOption.value) {
return [{ label: 'All', value: props.itemCount }, ...opts]; return [{ label: 'All', value: props.itemCount }, ...opts];
} }
return opts; return opts;
}); });
const withAllOption = computed<boolean>(() => {
return props.itemCount !== undefined && props.itemCount < 1000 && !props.simplePagination;
});
/** /**
* whether the selector drop down is open * whether the selector drop down is open
* */ * */
@ -191,4 +195,8 @@ function toggleSelector() {
} }
} }
} }
.custom-top {
top: -150px;
}
</style> </style>

View File

@ -3,6 +3,9 @@
<template> <template>
<div class="table-wrapper"> <div class="table-wrapper">
<div v-if="loading" class="table-wrapper__loader">
<VLoader width="100px" height="100px" />
</div>
<table class="base-table" border="0" cellpadding="0" cellspacing="0"> <table class="base-table" border="0" cellpadding="0" cellspacing="0">
<thead> <thead>
<tr> <tr>
@ -38,6 +41,7 @@ import { PageChangeCallback } from '@/types/pagination';
import TablePagination from '@/components/common/TablePagination.vue'; import TablePagination from '@/components/common/TablePagination.vue';
import VTableCheckbox from '@/components/common/VTableCheckbox.vue'; import VTableCheckbox from '@/components/common/VTableCheckbox.vue';
import VLoader from '@/components/common/VLoader.vue';
const props = withDefaults(defineProps<{ const props = withDefaults(defineProps<{
itemsLabel?: string, itemsLabel?: string,
@ -52,6 +56,7 @@ const props = withDefaults(defineProps<{
selected?: boolean, selected?: boolean,
showSelect?: boolean, showSelect?: boolean,
simplePagination?: boolean, simplePagination?: boolean,
loading?: boolean,
}>(), { }>(), {
selectable: false, selectable: false,
selected: false, selected: false,
@ -65,6 +70,7 @@ const props = withDefaults(defineProps<{
onNextClicked: null, onNextClicked: null,
onPreviousClicked: null, onPreviousClicked: null,
onPageSizeChanged: null, onPageSizeChanged: null,
loading: false,
}); });
const emit = defineEmits(['selectAllClicked']); const emit = defineEmits(['selectAllClicked']);
@ -74,6 +80,18 @@ const emit = defineEmits(['selectAllClicked']);
.table-wrapper { .table-wrapper {
background: #fff; background: #fff;
border-radius: 12px; border-radius: 12px;
position: relative;
&__loader {
border-radius: 12px;
z-index: 1;
background-color: rgb(0 0 0 / 5%);
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
}
} }
.base-table { .base-table {

View File

@ -8,7 +8,7 @@
transition="fade-transition" transition="fade-transition"
:persistent="isLoading" :persistent="isLoading"
> >
<v-card rounded="xlg" ref="innerContent"> <v-card ref="innerContent" rounded="xlg">
<v-card-item class="pl-7 py-4"> <v-card-item class="pl-7 py-4">
<template #prepend> <template #prepend>
<v-sheet <v-sheet