Skip to content

Commit aa9d813

Browse files
committed
fix: improve resource loading handling and skeleton display in ResourceListTable
AdminForth/1968/issue-725--list-view--the-tabl
1 parent ce61373 commit aa9d813

3 files changed

Lines changed: 52 additions & 23 deletions

File tree

‎adminforth/spa/src/components/ResourceListTable.vue‎

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,25 @@
66
@scroll="handleScroll"
77
ref="containerRef"
88
>
9-
<!-- skelet loader -->
10-
<div
11-
role="status" v-if="!resource || !resource.columns"
12-
class="max-w p-4 space-y-4 divide-y divide-gray-200 rounded shadow animate-pulse dark:divide-gray-700 md:p-6 dark:border-gray-700"
13-
>
14-
<div role="status" class="max-w-sm animate-pulse">
15-
<div class="h-2 bg-lightListSkeletLoader rounded-full dark:bg-darkListSkeletLoader max-w-[360px]"></div>
16-
</div>
17-
</div>
9+
<!-- skelet loader: resource is not loaded yet, so its columns are unknown -->
10+
<table v-if="!resource" :class="TABLE_CLASSES">
11+
<tbody>
12+
<tr :class="HEADER_ROW_CLASSES">
13+
<td v-for="i in SKELETON_COLUMNS_COUNT" :key="i" class="list-table-header-cell px-2 md:px-3 lg:px-6 py-3">
14+
<div role="status" class="h-4 flex items-center animate-pulse">
15+
<div class="h-2 w-full max-w-[120px] bg-lightSkeletonBackgroundColor rounded-full dark:bg-darkSkeletonBackgroundColor"></div>
16+
</div>
17+
</td>
18+
</tr>
19+
<SkeleteLoader :columns="SKELETON_COLUMNS_COUNT" :rows="3" />
20+
</tbody>
21+
</table>
1822

19-
<table v-else class="w-full text-sm text-left rtl:text-right text-lightListTableText dark:text-darkListTableText rounded-default">
23+
<table v-else :class="TABLE_CLASSES">
2024

2125
<tbody>
2226
<!-- table header -->
23-
<tr class="border-b dark:border-gray-700 t-header sticky z-20 top-0 text-xs text-lightListTableHeadingText bg-lightListTableHeading dark:bg-darkListTableHeading dark:text-darkListTableHeadingText">
27+
<tr :class="HEADER_ROW_CLASSES">
2428
<td scope="col" class="list-table-header-cell p-4 sticky-column bg-lightListTableHeading dark:bg-darkListTableHeading">
2529
<Checkbox
2630
:modelValue="allFromThisPageChecked"
@@ -402,6 +406,10 @@ import ListActionsThreeDots from '@/components/ListActionsThreeDots.vue';
402406
import CallActionWrapper from '@/components/CallActionWrapper.vue'
403407
import { Select } from '@/afcl';
404408
409+
const TABLE_CLASSES = 'w-full text-sm text-left rtl:text-right text-lightListTableText dark:text-darkListTableText rounded-default';
410+
const HEADER_ROW_CLASSES = 'border-b dark:border-gray-700 t-header sticky z-20 top-0 text-xs text-lightListTableHeadingText bg-lightListTableHeading dark:bg-darkListTableHeading dark:text-darkListTableHeadingText';
411+
const SKELETON_COLUMNS_COUNT = 5;
412+
405413
const coreStore = useCoreStore();
406414
const { t } = useI18n();
407415
const { alert, confirm } = useAdminforth();

‎adminforth/spa/src/stores/core.ts‎

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -53,14 +53,17 @@ export const useCoreStore = defineStore('core', () => {
5353
const resourceColumnsError: Ref<string> = ref('');
5454
const resourceColumnsId: Ref<string | null> = ref(null);
5555
const adminUser: Ref<null | AdminUser> = ref(null);
56+
// /get_resource responses, valid until config is refetched or user logs out
57+
const fullResourceCache = new Map<string, AdminForthResourceFrontend>();
58+
5659

57-
5860
async function resetAdminUser() {
5961
adminUser.value = null;
6062
}
6163

6264
async function resetResource() {
6365
resource.value = null;
66+
fullResourceCache.clear();
6467
}
6568

6669
async function toggleTheme() {
@@ -99,6 +102,7 @@ export const useCoreStore = defineStore('core', () => {
99102
}
100103

101104
menu.value = resp.menu;
105+
fullResourceCache.clear();
102106
resourceById.value = resp.resources.reduce((acc: Record<string, ResourceVeryShort>, resource: ResourceVeryShort) => {
103107
acc[resource.resourceId] = resource;
104108
return acc;
@@ -237,33 +241,47 @@ export const useCoreStore = defineStore('core', () => {
237241

238242
}
239243

244+
function applyResource(fullResource: AdminForthResourceFrontend) {
245+
resource.value = fullResource;
246+
resourceOptions.value = fullResource.options;
247+
}
248+
240249
async function fetchResourceFull({ resourceId, forceFetch }: { resourceId: string, forceFetch?: boolean }) {
241-
if (resourceColumnsId.value === resourceId && resource.value && !forceFetch) {
242-
// already fetched
250+
resourceColumnsId.value = resourceId;
251+
resourceColumnsError.value = '';
252+
const cached = fullResourceCache.get(resourceId);
253+
if (cached && !forceFetch) {
254+
// applied synchronously so the page renders real columns on the first frame
255+
// flag is reset because a request for previously opened resource may still be in flight
256+
isResourceFetching.value = false;
257+
applyResource(cached);
243258
return;
244259
}
245260
isResourceFetching.value = true;
246-
resourceColumnsId.value = resourceId;
247-
resourceColumnsError.value = '';
248261
const res = await callAdminForthApi({
249262
path: '/get_resource',
250263
method: 'POST',
251264
body: {
252265
resourceId,
253266
}
254267
});
268+
if (res && !res.error) {
269+
fullResourceCache.set(resourceId, res.resource);
270+
resourceById.value[resourceId] = res.resource;
271+
}
272+
if (resourceColumnsId.value !== resourceId) {
273+
// user switched to another resource while this request was in flight, that fetch owns the state now
274+
return;
275+
}
276+
isResourceFetching.value = false;
255277
if (!res) {
256-
isResourceFetching.value = false;
257278
return;
258279
}
259280
if (res.error) {
260281
resourceColumnsError.value = res.error;
261282
} else {
262-
resourceById.value[resourceId] = res.resource;
263-
resource.value = res.resource;
264-
resourceOptions.value = res.resource.options;
283+
applyResource(res.resource);
265284
}
266-
isResourceFetching.value = false;
267285
}
268286

269287
async function getLoginFormConfig() {

‎adminforth/spa/src/views/ListView.vue‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,8 +147,8 @@
147147
:adminUser="coreStore.adminUser"
148148
/>
149149
<ResourceListTable
150-
v-if="!coreStore.isResourceFetching"
151-
:resource="coreStore.resource"
150+
v-if="!coreStore.resourceColumnsError"
151+
:resource="listResource"
152152
:rows="rows"
153153
:page="page"
154154
@update:page="page = $event"
@@ -268,6 +268,9 @@ const syncPageSize = () => {
268268
}
269269
};
270270
271+
// store keeps previous resource until /get_resource for this route resolves, table shows skeleton meanwhile
272+
const listResource = computed(() => coreStore.resource?.resourceId === route.params.resourceId ? coreStore.resource : null);
273+
271274
const isVirtualScrollEnabled = computed(() => coreStore.resource?.options?.listVirtualScrollEnabled || false);
272275
const listBufferSize = computed(() => coreStore.resource?.options?.listBufferSize || 30);
273276

0 commit comments

Comments
 (0)