adminforth 3.31.19 → 3.31.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,21 +6,25 @@
6
6
  @scroll="handleScroll"
7
7
  ref="containerRef"
8
8
  >
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>
18
22
 
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">
20
24
 
21
25
  <tbody>
22
26
  <!-- 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">
24
28
  <td scope="col" class="list-table-header-cell p-4 sticky-column bg-lightListTableHeading dark:bg-darkListTableHeading">
25
29
  <Checkbox
26
30
  :modelValue="allFromThisPageChecked"
@@ -402,6 +406,10 @@ import ListActionsThreeDots from '@/components/ListActionsThreeDots.vue';
402
406
  import CallActionWrapper from '@/components/CallActionWrapper.vue'
403
407
  import { Select } from '@/afcl';
404
408
 
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
+
405
413
  const coreStore = useCoreStore();
406
414
  const { t } = useI18n();
407
415
  const { alert, confirm } = useAdminforth();
@@ -53,14 +53,17 @@ export const useCoreStore = defineStore('core', () => {
53
53
  const resourceColumnsError: Ref<string> = ref('');
54
54
  const resourceColumnsId: Ref<string | null> = ref(null);
55
55
  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
+
56
59
 
57
-
58
60
  async function resetAdminUser() {
59
61
  adminUser.value = null;
60
62
  }
61
63
 
62
64
  async function resetResource() {
63
65
  resource.value = null;
66
+ fullResourceCache.clear();
64
67
  }
65
68
 
66
69
  async function toggleTheme() {
@@ -99,6 +102,7 @@ export const useCoreStore = defineStore('core', () => {
99
102
  }
100
103
 
101
104
  menu.value = resp.menu;
105
+ fullResourceCache.clear();
102
106
  resourceById.value = resp.resources.reduce((acc: Record<string, ResourceVeryShort>, resource: ResourceVeryShort) => {
103
107
  acc[resource.resourceId] = resource;
104
108
  return acc;
@@ -237,14 +241,23 @@ export const useCoreStore = defineStore('core', () => {
237
241
 
238
242
  }
239
243
 
244
+ function applyResource(fullResource: AdminForthResourceFrontend) {
245
+ resource.value = fullResource;
246
+ resourceOptions.value = fullResource.options;
247
+ }
248
+
240
249
  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);
243
258
  return;
244
259
  }
245
260
  isResourceFetching.value = true;
246
- resourceColumnsId.value = resourceId;
247
- resourceColumnsError.value = '';
248
261
  const res = await callAdminForthApi({
249
262
  path: '/get_resource',
250
263
  method: 'POST',
@@ -252,18 +265,23 @@ export const useCoreStore = defineStore('core', () => {
252
265
  resourceId,
253
266
  }
254
267
  });
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;
255
277
  if (!res) {
256
- isResourceFetching.value = false;
257
278
  return;
258
279
  }
259
280
  if (res.error) {
260
281
  resourceColumnsError.value = res.error;
261
282
  } else {
262
- resourceById.value[resourceId] = res.resource;
263
- resource.value = res.resource;
264
- resourceOptions.value = res.resource.options;
283
+ applyResource(res.resource);
265
284
  }
266
- isResourceFetching.value = false;
267
285
  }
268
286
 
269
287
  async function getLoginFormConfig() {
@@ -147,8 +147,8 @@
147
147
  :adminUser="coreStore.adminUser"
148
148
  />
149
149
  <ResourceListTable
150
- v-if="!coreStore.isResourceFetching"
151
- :resource="coreStore.resource"
150
+ v-if="!coreStore.resourceColumnsError"
151
+ :resource="listResource"
152
152
  :rows="rows"
153
153
  :page="page"
154
154
  @update:page="page = $event"
@@ -268,6 +268,9 @@ const syncPageSize = () => {
268
268
  }
269
269
  };
270
270
 
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
+
271
274
  const isVirtualScrollEnabled = computed(() => coreStore.resource?.options?.listVirtualScrollEnabled || false);
272
275
  const listBufferSize = computed(() => coreStore.resource?.options?.listBufferSize || 30);
273
276
 
@@ -37,7 +37,22 @@ export default defineConfig({
37
37
  '@': fileURLToPath(new URL('./src', import.meta.url)),
38
38
  '@@': fileURLToPath(new URL('./src/custom', import.meta.url)),
39
39
  }
40
- },
40
+ },
41
+ build: {
42
+ rollupOptions: {
43
+ output: {
44
+ manualChunks(id) {
45
+ // reduce the size of the vendor chunk
46
+ // to only include the package name
47
+ // helps to reduce consumption of memory
48
+ if (id.includes('node_modules/')) {
49
+ // take the last node_modules segment: pnpm nests packages as node_modules/.pnpm/<pkg>@<version>/node_modules/<pkg>
50
+ return id.split('node_modules/').pop()!.split('/')[0];
51
+ }
52
+ },
53
+ },
54
+ },
55
+ },
41
56
  css: {
42
57
  preprocessorOptions: {
43
58
  scss: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "adminforth",
3
- "version": "3.31.19",
3
+ "version": "3.31.21",
4
4
  "description": "OpenSource Agent-Native forth-generation admin panel",
5
5
  "keywords": [
6
6
  "adminforth",