adminforth 3.35.1 → 4.0.1

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.
Files changed (94) hide show
  1. package/dist/modules/codeInjector.d.ts.map +1 -1
  2. package/dist/modules/codeInjector.js +20 -3
  3. package/dist/modules/codeInjector.js.map +1 -1
  4. package/dist/modules/styleGenerator.d.ts +13 -0
  5. package/dist/modules/styleGenerator.d.ts.map +1 -1
  6. package/dist/modules/styleGenerator.js +274 -1
  7. package/dist/modules/styleGenerator.js.map +1 -1
  8. package/dist/modules/styles.d.ts +24 -0
  9. package/dist/modules/styles.d.ts.map +1 -1
  10. package/dist/modules/styles.js +452 -427
  11. package/dist/modules/styles.js.map +1 -1
  12. package/dist/modules/utils.d.ts +1 -13
  13. package/dist/modules/utils.d.ts.map +1 -1
  14. package/dist/modules/utils.js +2 -275
  15. package/dist/modules/utils.js.map +1 -1
  16. package/dist/spa/index.html +2 -2
  17. package/dist/spa/package-lock.json +1373 -1136
  18. package/dist/spa/package.json +18 -15
  19. package/dist/spa/pnpm-lock.yaml +1067 -648
  20. package/dist/spa/src/App.vue +84 -92
  21. package/dist/spa/src/afcl/AreaChart.vue +8 -24
  22. package/dist/spa/src/afcl/BarChart.vue +8 -23
  23. package/dist/spa/src/afcl/Button.vue +8 -2
  24. package/dist/spa/src/afcl/ButtonGroup.vue +9 -9
  25. package/dist/spa/src/afcl/Card.vue +40 -19
  26. package/dist/spa/src/afcl/Checkbox.vue +6 -6
  27. package/dist/spa/src/afcl/CountryFlag.vue +17 -11
  28. package/dist/spa/src/afcl/DatePicker.vue +7 -7
  29. package/dist/spa/src/afcl/Dialog.vue +8 -9
  30. package/dist/spa/src/afcl/DropdownMenu.vue +210 -0
  31. package/dist/spa/src/afcl/DropdownMenuAccordion.vue +56 -0
  32. package/dist/spa/src/afcl/DropdownMenuItem.vue +86 -0
  33. package/dist/spa/src/afcl/Dropzone.vue +10 -26
  34. package/dist/spa/src/afcl/Input.vue +4 -4
  35. package/dist/spa/src/afcl/Link.vue +1 -1
  36. package/dist/spa/src/afcl/LinkButton.vue +15 -1
  37. package/dist/spa/src/afcl/MixedChart.vue +4 -18
  38. package/dist/spa/src/afcl/Modal.vue +9 -9
  39. package/dist/spa/src/afcl/PieChart.vue +8 -29
  40. package/dist/spa/src/afcl/ProgressBar.vue +2 -2
  41. package/dist/spa/src/afcl/Select.vue +20 -20
  42. package/dist/spa/src/afcl/Skeleton.vue +1 -1
  43. package/dist/spa/src/afcl/Spinner.vue +1 -1
  44. package/dist/spa/src/afcl/Table.vue +19 -26
  45. package/dist/spa/src/afcl/Textarea.vue +1 -1
  46. package/dist/spa/src/afcl/Toggle.vue +4 -4
  47. package/dist/spa/src/afcl/Tooltip.vue +1 -1
  48. package/dist/spa/src/afcl/TreeMapChart.vue +5 -20
  49. package/dist/spa/src/afcl/VerticalTabs.vue +4 -4
  50. package/dist/spa/src/afcl/buttonStyles.ts +73 -33
  51. package/dist/spa/src/afcl/chartUtils.ts +19 -0
  52. package/dist/spa/src/afcl/classMerge.ts +11 -0
  53. package/dist/spa/src/afcl/dropdownMenuContext.ts +10 -0
  54. package/dist/spa/src/afcl/fileSize.ts +12 -0
  55. package/dist/spa/src/afcl/index.ts +3 -0
  56. package/dist/spa/src/components/AcceptModal.vue +17 -19
  57. package/dist/spa/src/components/Breadcrumbs.vue +11 -13
  58. package/dist/spa/src/components/BreadcrumbsWithButtons.vue +2 -2
  59. package/dist/spa/src/components/CallActionWrapper.vue +3 -0
  60. package/dist/spa/src/components/ColumnValueInput.vue +5 -5
  61. package/dist/spa/src/components/ColumnValueInputWrapper.vue +7 -6
  62. package/dist/spa/src/components/CustomDateRangePicker.vue +8 -8
  63. package/dist/spa/src/components/CustomRangePicker.vue +2 -2
  64. package/dist/spa/src/components/ErrorMessage.vue +3 -4
  65. package/dist/spa/src/components/Filters.vue +13 -13
  66. package/dist/spa/src/components/GroupsTable.vue +12 -12
  67. package/dist/spa/src/components/ListActionsThreeDots.vue +89 -207
  68. package/dist/spa/src/components/MenuLink.vue +23 -20
  69. package/dist/spa/src/components/RangePicker.vue +9 -10
  70. package/dist/spa/src/components/ResourceListTable.vue +68 -65
  71. package/dist/spa/src/components/ShowTable.vue +8 -8
  72. package/dist/spa/src/components/Sidebar.vue +31 -38
  73. package/dist/spa/src/components/ThreeDotsMenu.vue +88 -136
  74. package/dist/spa/src/components/Toast.vue +16 -23
  75. package/dist/spa/src/components/UserMenuSettingsButton.vue +15 -36
  76. package/dist/spa/src/components/ValueRenderer.vue +13 -8
  77. package/dist/spa/src/components/icons/IconCalendar.vue +3 -3
  78. package/dist/spa/src/components/icons/IconTime.vue +3 -3
  79. package/dist/spa/src/index.scss +6 -2
  80. package/dist/spa/src/main.ts +2 -0
  81. package/dist/spa/src/renderers/CompactField.vue +3 -3
  82. package/dist/spa/src/renderers/CompactUUID.vue +3 -3
  83. package/dist/spa/src/renderers/SensitiveBlurCell.vue +9 -9
  84. package/dist/spa/src/utils/threeDotsMenu.ts +7 -0
  85. package/dist/spa/src/utils/userMenu.ts +5 -0
  86. package/dist/spa/src/utils/utils.ts +12 -39
  87. package/dist/spa/src/views/CreateEditSkeleton.vue +3 -3
  88. package/dist/spa/src/views/CreateView.vue +5 -6
  89. package/dist/spa/src/views/EditView.vue +5 -6
  90. package/dist/spa/src/views/ListView.vue +114 -115
  91. package/dist/spa/src/views/LoginView.vue +4 -4
  92. package/dist/spa/src/views/ShowView.vue +17 -18
  93. package/dist/spa/vite.config.ts +23 -16
  94. package/package.json +1 -2
@@ -4,7 +4,6 @@ import type { AdminForthComponentDeclaration, AdminForthComponentDeclarationFull
4
4
  import router from "../router";
5
5
  import { useCoreStore } from '../stores/core';
6
6
  import { useUserStore } from '../stores/user';
7
- import { Dropdown } from 'flowbite';
8
7
  import adminforth, { useAdminforth } from '../adminforth';
9
8
  import DOMPurify from 'dompurify'
10
9
  import debounce from 'debounce';
@@ -241,22 +240,22 @@ export function getIcon(icon: string) {
241
240
  return resolveComponent(compName);
242
241
  }
243
242
 
243
+ // URLs of the files an app config references as '@/...' (SPA src) or '@@/...' (app custom folder, copied to src/custom).
244
+ // The glob is explicit: Vite turns a template in new URL(`../${path}`, import.meta.url) into a one-level glob,
245
+ // which misses files in nested folders like '@@/assets/logo.svg'.
246
+ const assetUrls = import.meta.glob<string>(
247
+ ['../**', '!../**/*.{vue,ts,tsx,js,mjs,cjs,css,scss,sass,json,yaml,yml,md,html}'],
248
+ { eager: true, query: '?url', import: 'default' },
249
+ );
250
+
244
251
  export const loadFile = (file: string) => {
245
252
  if (file.startsWith('http')) {
246
253
  return file;
247
254
  }
248
- let path;
249
- let baseUrl = '';
250
- if (file.startsWith('@/')) {
251
- path = file.replace('@/', '');
252
- baseUrl = new URL(`../${path}`, import.meta.url).href;
253
- } else if (file.startsWith('@@/')) {
254
- path = file.replace('@@/', '');
255
- baseUrl = new URL(`../custom/${path}`, import.meta.url).href;
256
- } else {
257
- baseUrl = new URL(`../${file}`, import.meta.url).href;
255
+ if (file.startsWith('@@/')) {
256
+ return assetUrls[`../custom/${file.slice('@@/'.length)}`];
258
257
  }
259
- return baseUrl;
258
+ return assetUrls[`../${file.replace('@/', '')}`];
260
259
  }
261
260
 
262
261
 
@@ -288,21 +287,6 @@ export function checkAcessByAllowedActions(allowedActions:any, action:any ) {
288
287
  }
289
288
  }
290
289
 
291
- export function initThreeDotsDropdown() {
292
- const threeDotsDropdown: HTMLElement | null = document.querySelector('#listThreeDotsDropdown');
293
- if (threeDotsDropdown) {
294
- // this resource has three dots dropdown
295
- const dd = new Dropdown(
296
- threeDotsDropdown,
297
- document.querySelector('[data-dropdown-toggle="listThreeDotsDropdown"]') as HTMLElement,
298
- { placement: 'bottom-end' }
299
- );
300
- adminforth.list.closeThreeDotsDropdown = () => {
301
- dd.hide();
302
- }
303
- }
304
- }
305
-
306
290
  export function applyRegexValidation(value: any, validation: ValidationObject[] | undefined) {
307
291
 
308
292
  if ( validation?.length ) {
@@ -350,18 +334,7 @@ export function verySimpleHash(str: string): string {
350
334
  return `${str.split('').reduce((a, b)=>{a=((a<<5)-a)+b.charCodeAt(0);return a&a},0)}`;
351
335
  }
352
336
 
353
- export function humanifySize(size: number) {
354
- if (!size) {
355
- return '';
356
- }
357
- const units = ['B', 'KB', 'MB', 'GB', 'TB']
358
- let i = 0
359
- while (size >= 1024 && i < units.length - 1) {
360
- size /= 1024
361
- i++
362
- }
363
- return `${size.toFixed(1)} ${units[i]}`
364
- }
337
+ export { humanifySize } from '../afcl/fileSize';
365
338
 
366
339
  export function protectAgainstXSS(value: string) {
367
340
  return DOMPurify.sanitize(value, {
@@ -1,8 +1,8 @@
1
1
  <template>
2
2
  <div class="w-full mt-[36px]">
3
- <div class="w-full border dark:border-darkFormBorder border-gray-200 rounded-lg overflow-hidden shadow-resourseFormShadow dark:shadow-darkResourseFormShadow bg-white dark:bg-darkForm">
3
+ <div class="w-full border dark:border-darkFormBorder border-lightFormBorder rounded-lg overflow-hidden shadow-resourseFormShadow dark:shadow-darkResourseFormShadow bg-lightForm dark:bg-darkForm">
4
4
 
5
- <div class="flex px-6 items-center border-b border-gray-200 dark:border-darkFormBorder bg-lightFormHeading dark:bg-darkFormHeading h-[40px]">
5
+ <div class="flex px-5 items-center border-b border-lightFormBorder dark:border-darkFormBorder bg-lightFormHeading dark:bg-darkFormHeading h-[40px]">
6
6
  <div class="w-[208px] flex-shrink-0 pr-6 text-xs text-lightListTableHeadingText uppercase dark:text-darkListTableHeadingText font-bold">
7
7
  {{ $t('Field') }}
8
8
  </div>
@@ -14,7 +14,7 @@
14
14
  <div
15
15
  v-for="i in 10"
16
16
  :key="i"
17
- class="flex items-center bg-lightForm dark:bg-darkForm border-b border-gray-100 dark:border-darkFormBorder h-[75px]"
17
+ class="flex items-center bg-lightForm dark:bg-darkForm border-b border-lightFormBorder dark:border-darkFormBorder h-[58px]"
18
18
  >
19
19
  <div class="w-[208px] flex-shrink-0 px-6 flex items-center">
20
20
  <Skeleton class="w-24 h-[10px]" />
@@ -13,7 +13,7 @@
13
13
  <BreadcrumbsWithButtons>
14
14
  <!-- save and cancle -->
15
15
  <Button @click="() => {cancelButtonClicked = true; $router.back()}"
16
- class="af-cancel-button h-[2.125rem] px-3 mr-2"
16
+ class="af-cancel-button h-[2.125rem] px-3"
17
17
  variant="secondary"
18
18
  >
19
19
  {{ $t('Cancel') }}
@@ -21,11 +21,11 @@
21
21
  <Button
22
22
  @click="() => saveRecord()"
23
23
  class="af-save-button h-[2.125rem] px-3 text-lightCreateViewSaveButtonText hover:text-lightCreateViewSaveButtonTextHover dark:text-darkCreateViewSaveButtonText dark:hover:text-darkCreateViewSaveButtonTextHover"
24
- variant="secondary"
24
+ variant="primary"
25
25
  :disabled="saving || (validatingMode && !isValid) || resourceFormRef?.isValidating"
26
26
  >
27
27
  <Spinner v-if="saving || resourceFormRef?.isValidating" class="w-4 h-4" />
28
- <IconFloppyDiskSolid v-else class="w-4 h-4" />
28
+ <IconSave v-else class="w-4 h-4" />
29
29
  {{ $t('Save') }}
30
30
  </Button>
31
31
 
@@ -79,8 +79,8 @@ import BreadcrumbsWithButtons from '@/components/BreadcrumbsWithButtons.vue';
79
79
  import ResourceForm from '@/components/ResourceForm.vue';
80
80
  import SingleSkeletLoader from '@/components/SingleSkeletLoader.vue';
81
81
  import { useCoreStore } from '@/stores/core';
82
- import { callAdminForthApi, getCustomComponent,checkAcessByAllowedActions, initThreeDotsDropdown, checkShowIf, compareOldAndNewRecord, onBeforeRouteLeaveCreateEditViewGuard, leaveGuardActiveClass, formatComponent, atob_function } from '@/utils';
83
- import { IconFloppyDiskSolid } from '@iconify-prerendered/vue-flowbite';
82
+ import { callAdminForthApi, getCustomComponent,checkAcessByAllowedActions, checkShowIf, compareOldAndNewRecord, onBeforeRouteLeaveCreateEditViewGuard, leaveGuardActiveClass, formatComponent, atob_function } from '@/utils';
83
+ import { IconSave } from '@iconify-prerendered/vue-lucide';
84
84
  import { onMounted, onBeforeMount, onBeforeUnmount, ref } from 'vue';
85
85
  import { useRoute, useRouter } from 'vue-router';
86
86
  import { showErrorTost } from '@/composables/useFrontendApi';
@@ -192,7 +192,6 @@ onMounted(async () => {
192
192
  record.value = initialValues.value;
193
193
  loading.value = false;
194
194
  checkAcessByAllowedActions(coreStore.resourceOptions!.allowedActions,'create');
195
- initThreeDotsDropdown();
196
195
  });
197
196
 
198
197
  async function saveRecord() {
@@ -12,7 +12,7 @@
12
12
  <BreadcrumbsWithButtons>
13
13
  <!-- save and cancle -->
14
14
  <Button @click="() => {cancelButtonClicked = true; $router.back()}"
15
- class="h-[2.125rem] px-3 mr-2 text-lightEditViewButtonText dark:text-darkEditViewButtonText"
15
+ class="h-[2.125rem] px-3 text-lightEditViewButtonText dark:text-darkEditViewButtonText"
16
16
  variant="secondary"
17
17
  >
18
18
  {{ $t('Cancel') }}
@@ -21,10 +21,10 @@
21
21
  @click="() => saveRecord()"
22
22
  class="h-[2.125rem] px-3 text-sm text-lightEditViewSaveButtonText hover:text-lightEditViewSaveButtonTextHover dark:text-darkEditViewSaveButtonText dark:hover:text-darkEditViewSaveButtonTextHover"
23
23
  :disabled="saving || (validatingMode && !isValid) || resourceFormRef?.isValidating"
24
- variant="secondary"
24
+ variant="primary"
25
25
  >
26
26
  <Spinner v-if="saving || resourceFormRef?.isValidating" class="w-4 h-4" />
27
- <IconFloppyDiskSolid v-else class="w-4 h-4" />
27
+ <IconSave v-else class="w-4 h-4" />
28
28
  {{ $t('Save') }}
29
29
  </Button>
30
30
 
@@ -77,8 +77,8 @@ import BreadcrumbsWithButtons from '@/components/BreadcrumbsWithButtons.vue';
77
77
  import ResourceForm from '@/components/ResourceForm.vue';
78
78
  import SingleSkeletLoader from '@/components/SingleSkeletLoader.vue';
79
79
  import { useCoreStore } from '@/stores/core';
80
- import { callAdminForthApi, getCustomComponent,checkAcessByAllowedActions, initThreeDotsDropdown, compareOldAndNewRecord, generateMessageHtmlForRecordChange, leaveGuardActiveClass, onBeforeRouteLeaveCreateEditViewGuard } from '@/utils';
81
- import { IconFloppyDiskSolid } from '@iconify-prerendered/vue-flowbite';
80
+ import { callAdminForthApi, getCustomComponent,checkAcessByAllowedActions, compareOldAndNewRecord, generateMessageHtmlForRecordChange, leaveGuardActiveClass, onBeforeRouteLeaveCreateEditViewGuard } from '@/utils';
81
+ import { IconSave } from '@iconify-prerendered/vue-lucide';
82
82
  import { computed, onMounted, onBeforeMount, ref, type Ref, nextTick, watch, onBeforeUnmount } from 'vue';
83
83
  import { useRoute, useRouter, onBeforeRouteLeave } from 'vue-router';
84
84
  import { showErrorTost } from '@/composables/useFrontendApi';
@@ -170,7 +170,6 @@ onMounted(async () => {
170
170
  await coreStore.fetchResourceFull({
171
171
  resourceId: route.params.resourceId as string
172
172
  });
173
- initThreeDotsDropdown();
174
173
 
175
174
  await coreStore.fetchRecord({
176
175
  resourceId: route.params.resourceId as string,
@@ -12,7 +12,7 @@
12
12
  </Teleport>
13
13
 
14
14
  <component
15
- v-if="!coreStore.isResourceFetching && !initInProcess"
15
+ v-if="isResourceReady"
16
16
  v-for="c in coreStore?.resourceOptions?.pageInjections?.list?.beforeBreadcrumbs as AdminForthComponentDeclaration[] || []"
17
17
  :is="getCustomComponent(formatComponent(c))"
18
18
  :meta="(c as AdminForthComponentDeclarationFull).meta"
@@ -21,125 +21,122 @@
21
21
  />
22
22
 
23
23
  <BreadcrumbsWithButtons>
24
- <component
25
- v-if="!coreStore.isResourceFetching && !initInProcess"
26
- v-for="c in coreStore?.resourceOptions?.pageInjections?.list?.beforeActionButtons as AdminForthComponentDeclaration[] || []"
27
- :is="getCustomComponent(formatComponent(c))"
28
- :meta="(c as AdminForthComponentDeclarationFull).meta"
29
- :resource="coreStore.resource"
30
- :adminUser="coreStore.adminUser"
31
- :checkboxes="checkboxes"
32
- :clearCheckboxes="clearCheckboxes"
33
- />
34
- <Button
35
- @click="()=>{checkboxes = []}"
36
- v-if="checkboxes.length"
37
- data-tooltip-target="tooltip-remove-all"
38
- class="h-[2.125rem] px-2.5"
39
- variant="secondary"
40
- >
41
- <Tooltip>
42
- <IconBanOutline class="w-5 h-5 "/>
43
- <template #tooltip >
44
- Remove selection
45
- </template>
46
- </Tooltip>
47
- </Button>
48
-
49
- <div
50
- v-if="checkboxes.length"
51
- v-for="(action,i) in coreStore.resource?.options?.bulkActions"
52
- >
24
+ <template v-if="isResourceReady">
25
+ <component
26
+ v-for="c in coreStore?.resourceOptions?.pageInjections?.list?.beforeActionButtons as AdminForthComponentDeclaration[] || []"
27
+ :is="getCustomComponent(formatComponent(c))"
28
+ :meta="(c as AdminForthComponentDeclarationFull).meta"
29
+ :resource="coreStore.resource"
30
+ :adminUser="coreStore.adminUser"
31
+ :checkboxes="checkboxes"
32
+ :clearCheckboxes="clearCheckboxes"
33
+ />
53
34
  <Button
54
- v-if="!action.showInThreeDotsDropdown"
55
- :key="action.id"
56
- @click="startBulkActionInner(action.id!)"
57
- class="h-[2.125rem] px-2.5"
58
- :class="action.buttonCustomCssClass || ''"
35
+ @click="()=>{checkboxes = []}"
36
+ v-if="checkboxes.length"
37
+ data-tooltip-target="tooltip-remove-all"
59
38
  variant="secondary"
39
+ iconOnly
60
40
  >
61
- <component
62
- v-if="action.icon && !bulkActionLoadingStates[action.id!]"
63
- :is="getIcon(action.icon)"
64
- class="w-5 h-5 transition duration-75 group-hover:text-gray-900 dark:group-hover:text-white"></component>
65
- <Spinner
66
- v-if="bulkActionLoadingStates[action.id!]"
67
- class="w-5 h-5 text-gray-200 dark:text-gray-500 fill-gray-500 dark:fill-gray-300"
68
- />
69
- {{ `${action.label} (${checkboxes.length})` }}
70
- <div v-if="action.badge" class="text-white bg-gradient-to-r from-purple-500
71
- via-purple-600 to-purple-700 hover:bg-gradient-to-br focus:ring-4 focus:outline-none
72
- focus:ring-purple-300 dark:focus:ring-purple-800
73
- font-medium rounded-default text-xs px-1 ml-1 text-center ">
74
- {{ action.badge }}
75
- </div>
41
+ <Tooltip>
42
+ <IconBan class="w-4 h-4"/>
43
+ <template #tooltip >
44
+ Remove selection
45
+ </template>
46
+ </Tooltip>
76
47
  </Button>
77
- </div>
78
- <div
79
- v-if="checkboxes.length"
80
- v-for="(action,i) in coreStore.resource?.options?.actions?.filter(a => a.showIn?.bulkButton)"
81
- >
48
+
49
+ <div
50
+ v-if="checkboxes.length"
51
+ v-for="(action,i) in coreStore.resource?.options?.bulkActions"
52
+ >
53
+ <Button
54
+ v-if="!action.showInThreeDotsDropdown"
55
+ :key="action.id"
56
+ @click="startBulkActionInner(action.id!)"
57
+ class="h-[2.125rem] px-2.5"
58
+ :class="action.buttonCustomCssClass || ''"
59
+ variant="secondary"
60
+ >
61
+ <component
62
+ v-if="action.icon && !bulkActionLoadingStates[action.id!]"
63
+ :is="getIcon(action.icon)"
64
+ class="w-4 h-4 transition duration-75"></component>
65
+ <Spinner
66
+ v-if="bulkActionLoadingStates[action.id!]"
67
+ class="w-4 h-4 text-lightSkeletonBackgroundColor dark:text-darkSkeletonBackgroundColor fill-lightListTableText dark:fill-darkListTableText"
68
+ />
69
+ {{ `${action.label} (${checkboxes.length})` }}
70
+ <div v-if="action.badge" class="inline-flex items-center h-[1.125rem] px-1.5 ml-1 rounded-full bg-purple-600 text-white text-[11px] font-semibold">
71
+ {{ action.badge }}
72
+ </div>
73
+ </Button>
74
+ </div>
75
+ <div
76
+ v-if="checkboxes.length"
77
+ v-for="(action,i) in coreStore.resource?.options?.actions?.filter(a => a.showIn?.bulkButton)"
78
+ >
79
+ <Button
80
+ :key="action.id"
81
+ @click="startCustomBulkActionInner(action.id!)"
82
+ class="h-[2.125rem] px-2.5"
83
+ variant="secondary"
84
+ >
85
+ <component
86
+ v-if="action.icon && !customActionLoadingStates[action.id!]"
87
+ :is="getIcon(action.icon)"
88
+ class="w-4 h-4 transition duration-75"></component>
89
+ <Spinner
90
+ v-if="customActionLoadingStates[action.id!]"
91
+ class="w-4 h-4 text-lightSkeletonBackgroundColor dark:text-darkSkeletonBackgroundColor fill-lightListTableText dark:fill-darkListTableText"
92
+ />
93
+ {{ `${action.name} (${checkboxes.length})` }}
94
+ </Button>
95
+ </div>
96
+
97
+ <LinkButton v-if="coreStore.resource?.options?.allowedActions?.create"
98
+ :to="{ name: 'resource-create', params: { resourceId: $route.params.resourceId } }"
99
+ class="af-create-button h-[2.125rem] px-2.5"
100
+ >
101
+ <IconPlus class="w-4 h-4"/>
102
+ <span>{{ $t('Create') }}</span>
103
+ </LinkButton>
104
+
82
105
  <Button
83
- :key="action.id"
84
- @click="startCustomBulkActionInner(action.id!)"
85
- class="h-[2.125rem] px-2.5"
106
+ class="af-filter-button h-[2.125rem] px-3 py-3"
107
+ @click="()=>{filtersShow = !filtersShow}"
108
+ v-if="coreStore.resource?.options?.allowedActions?.filter"
86
109
  variant="secondary"
87
110
  >
88
- <component
89
- v-if="action.icon && !customActionLoadingStates[action.id!]"
90
- :is="getIcon(action.icon)"
91
- class="w-5 h-5 transition duration-75 group-hover:text-gray-900 dark:group-hover:text-white"></component>
92
- <Spinner
93
- v-if="customActionLoadingStates[action.id!]"
94
- class="w-5 h-5 text-gray-200 dark:text-gray-500 fill-gray-500 dark:fill-gray-300"
95
- />
96
- {{ `${action.name} (${checkboxes.length})` }}
97
- </Button>
98
- </div>
99
-
100
- <LinkButton v-if="coreStore.resource?.options?.allowedActions?.create"
101
- :to="{ name: 'resource-create', params: { resourceId: $route.params.resourceId } }"
102
- class="af-create-button h-[2.125rem] px-2.5"
103
- >
104
- <IconPlusOutline class="w-4 h-4"/>
105
- <span>{{ $t('Create') }}</span>
106
- </LinkButton>
107
-
108
- <Button
109
- class="af-filter-button h-[2.125rem] px-3 py-3"
110
- @click="()=>{filtersShow = !filtersShow}"
111
- v-if="coreStore.resource?.options?.allowedActions?.filter"
112
- variant="secondary"
113
- >
114
- <IconFilterOutline class="w-4 h-4"/>
115
- <span>{{ $t('Filter') }}</span>
116
- <div v-if="filtersStore.visibleFiltersCount" class="flex items-center gap-1 ms-1">
117
- <span class="bg-red-100 text-red-800 text-xs font-medium px-2.5 py-0.5 rounded dark:bg-gray-700 dark:text-red-400 border border-red-400">
118
- {{ filtersStore.visibleFiltersCount }}
119
- </span>
111
+ <IconListFilter class="w-4 h-4"/>
112
+ <span>{{ $t('Filter') }}</span>
113
+ <div v-if="filtersStore.visibleFiltersCount" class="flex items-center gap-1 ms-1">
114
+ <span class="inline-flex items-center justify-center min-w-[1.125rem] h-[1.125rem] px-1 rounded-full text-[11px] font-semibold tabular-nums bg-lightPrimary text-lightPrimaryContrast dark:bg-darkPrimary dark:text-darkPrimaryContrast">
115
+ {{ filtersStore.visibleFiltersCount }}
116
+ </span>
120
117
 
121
- <div
122
- @click.stop="filtersStore.clearFilters()"
123
- class="p-0.5 hover:bg-gray-200 dark:hover:bg-gray-600 rounded-md transition-colors"
124
- >
125
- <IconCloseOutline class="w-4 h-4 text-red-500" />
118
+ <div
119
+ @click.stop="filtersStore.clearFilters()"
120
+ class="p-0.5 rounded text-lightInputIconColor hover:text-lightInputText hover:bg-lightButtonGroupBackgroundHover dark:text-darkInputIconColor dark:hover:text-darkInputText dark:hover:bg-darkButtonGroupBackgroundHover transition-colors"
121
+ >
122
+ <IconX class="w-3.5 h-3.5" />
123
+ </div>
126
124
  </div>
127
- </div>
128
- </Button>
129
-
130
- <ThreeDotsMenu
131
- v-if="!coreStore.isResourceFetching"
132
- :threeDotsDropdownItems="(coreStore.resourceOptions?.pageInjections?.list?.threeDotsDropdownItems as [])"
133
- :bulkActions="coreStore.resource?.options?.bulkActions"
134
- :checkboxes="checkboxes"
135
- @startBulkAction="startBulkActionInner"
136
- :updateList="getListInner"
137
- :clearCheckboxes="clearCheckboxes"
138
- ></ThreeDotsMenu>
125
+ </Button>
126
+
127
+ <ThreeDotsMenu
128
+ :threeDotsDropdownItems="(coreStore.resourceOptions?.pageInjections?.list?.threeDotsDropdownItems as [])"
129
+ :bulkActions="coreStore.resource?.options?.bulkActions"
130
+ :checkboxes="checkboxes"
131
+ @startBulkAction="startBulkActionInner"
132
+ :updateList="getListInner"
133
+ :clearCheckboxes="clearCheckboxes"
134
+ ></ThreeDotsMenu>
135
+ </template>
139
136
  </BreadcrumbsWithButtons>
140
137
 
141
138
  <component
142
- v-if="!coreStore.isResourceFetching && !initInProcess"
139
+ v-if="isResourceReady"
143
140
  v-for="c in coreStore?.resourceOptions?.pageInjections?.list?.afterBreadcrumbs as AdminForthComponentDeclaration[] || []"
144
141
  :is="getCustomComponent(formatComponent(c))"
145
142
  :meta="(c as AdminForthComponentDeclarationFull).meta"
@@ -209,18 +206,18 @@ import { useFiltersStore } from '@/stores/filters';
209
206
  import { callAdminForthApi, currentQuery, getIcon, setQuery, formatComponent, executeCustomBulkAction } from '@/utils';
210
207
  import { computed, onMounted, onUnmounted, ref, watch, type Ref } from 'vue';
211
208
  import { useRoute } from 'vue-router';
212
- import { getCustomComponent, initThreeDotsDropdown, getList, startBulkAction } from '@/utils';
209
+ import { getCustomComponent, getList, startBulkAction } from '@/utils';
213
210
  import ThreeDotsMenu from '@/components/ThreeDotsMenu.vue';
214
211
  import { Tooltip, Spinner, Button, LinkButton } from '@/afcl'
215
212
  import type { AdminForthComponentDeclaration, AdminForthComponentDeclarationFull, AdminForthFilterOperators, AdminForthResourceColumnCommon } from '@/types/Common';
216
213
  import { useI18n } from 'vue-i18n';
217
214
 
218
215
  import {
219
- IconBanOutline,
220
- IconFilterOutline,
221
- IconPlusOutline,
222
- IconCloseOutline
223
- } from '@iconify-prerendered/vue-flowbite';
216
+ IconBan,
217
+ IconListFilter,
218
+ IconPlus,
219
+ IconX
220
+ } from '@iconify-prerendered/vue-lucide';
224
221
 
225
222
  import Filters from '@/components/Filters.vue';
226
223
  import { useAdminforth } from '@/adminforth';
@@ -233,6 +230,9 @@ const { t } = useI18n();
233
230
 
234
231
  const route = useRoute();
235
232
 
233
+ // previous resource stays in the store until the new one is applied, its injections must not render here
234
+ const isResourceReady = computed(() => coreStore.resource?.resourceId === route.params.resourceId);
235
+
236
236
  const page = ref(1);
237
237
  const columnsMinMax = ref({});
238
238
  const sort = ref();
@@ -524,7 +524,6 @@ watch(() => filtersStore.filters, async (to, from) => {
524
524
  onMounted(async () => {
525
525
  initInProcess = true;
526
526
  await init();
527
- initThreeDotsDropdown();
528
527
  initInProcess = false;
529
528
  });
530
529
 
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <div class="relative flex items-center justify-center min-h-screen bg-lightHtml dark:bg-darkHtml w-screen h-screen"
2
+ <div class="relative flex items-center justify-center min-h-screen bg-lightHtml dark:bg-darkHtml w-full h-screen"
3
3
  :style="coreStore.config?.loginBackgroundImage && backgroundPosition === 'over' ? {
4
4
  'background-image': 'url(' + loadFile(coreStore.config?.loginBackgroundImage) + ')',
5
5
  'background-size': 'cover',
@@ -76,8 +76,8 @@
76
76
  :type="!showPw ? 'password': 'text'" name="password" id="password" placeholder="••••••••" class="w-full" required>
77
77
  <template #rightIcon>
78
78
  <button type="button" @click="showPw = !showPw" class="text-lightLoginViewSubTextColor dark:text-darkLoginViewSubTextColor">
79
- <IconEyeSolid class="w-5 h-5" v-if="!showPw" />
80
- <IconEyeSlashSolid class="w-5 h-5" v-else />
79
+ <IconEye class="w-4 h-4" v-if="!showPw" />
80
+ <IconEyeOff class="w-4 h-4" v-else />
81
81
  </button>
82
82
  </template>
83
83
  </Input>
@@ -142,7 +142,7 @@ import { getCustomComponent, formatComponent } from '@/utils';
142
142
  import { onMounted, ref, computed } from 'vue';
143
143
  import { useCoreStore } from '@/stores/core';
144
144
  import { useUserStore } from '@/stores/user';
145
- import { IconEyeSolid, IconEyeSlashSolid } from '@iconify-prerendered/vue-flowbite';
145
+ import { IconEye, IconEyeOff } from '@iconify-prerendered/vue-lucide';
146
146
  import { callAdminForthApi, loadFile } from '@/utils';
147
147
  import { useRouter, useRoute } from 'vue-router';
148
148
  import { Button, Checkbox, Input, Spinner } from '@/afcl';
@@ -15,10 +15,10 @@
15
15
  :to="nextRecordRoute ?? $route"
16
16
  @click="handleNextClick()"
17
17
  :class="!nextRecordRoute ? 'opacity-50 pointer-events-none cursor-not-allowed' : ''"
18
- class="h-[2.125rem] text-lightListViewButtonText dark:text-darkListViewButtonText px-3 py-3"
18
+ class="h-[2.125rem] text-lightListViewButtonText dark:text-darkListViewButtonText px-3"
19
19
  variant="secondary"
20
20
  >
21
- <Spinner v-if="isFetchingNextPage" class="w-4 h-4 text-gray-200 dark:text-gray-500 fill-gray-500 dark:fill-gray-300" />
21
+ <Spinner v-if="isFetchingNextPage" class="w-4 h-4 text-lightSkeletonBackgroundColor dark:text-darkSkeletonBackgroundColor fill-lightListTableText dark:fill-darkListTableText" />
22
22
  {{ $t('Next') }}
23
23
  </LinkButton>
24
24
 
@@ -40,11 +40,11 @@
40
40
  <component
41
41
  v-if="action.icon && !actionLoadingStates[action.id!]"
42
42
  :is="getIcon(action.icon)"
43
- class="w-4 h-4 me-2 text-lightPrimary dark:text-darkPrimary dark:brightness-200"
43
+ class="w-4 h-4 text-lightInputIconColor dark:text-darkInputIconColor"
44
44
  />
45
45
  <Spinner
46
46
  v-if="actionLoadingStates[action.id!]"
47
- class="w-5 h-5 me-2 text-gray-200 dark:text-gray-500 fill-gray-500 dark:fill-gray-300"
47
+ class="w-4 h-4 text-lightSkeletonBackgroundColor dark:text-darkSkeletonBackgroundColor fill-lightListTableText dark:fill-darkListTableText"
48
48
  />
49
49
  {{ action.name }}
50
50
  </Button>
@@ -56,7 +56,7 @@
56
56
  class="af-add-new-button h-[2.125rem] px-2.5"
57
57
  variant="secondary"
58
58
  >
59
- <IconPlusOutline class="w-4 h-4"/>
59
+ <IconPlus class="w-4 h-4"/>
60
60
  {{ $t('Add new') }}
61
61
  </LinkButton>
62
62
 
@@ -64,15 +64,15 @@
64
64
  class="h-[2.125rem] af-edit-button px-2.5"
65
65
  variant="secondary"
66
66
  >
67
- <IconPenSolid class="w-4 h-4" />
67
+ <IconPencil class="w-4 h-4" />
68
68
  {{ $t('Edit') }}
69
69
  </LinkButton>
70
70
 
71
71
  <Button v-if="coreStore?.resourceOptions?.allowedActions?.delete" @click="deleteRecord"
72
- class="h-[2.125rem] af-delete-button px-3 text-red-600 hover:text-red-700 focus:z-10 dark:text-red-500 dark:hover:text-darkShowViewButtonTextHover"
72
+ class="h-[2.125rem] af-delete-button px-3 text-lightInputErrorColor hover:text-lightInputErrorColor focus-visible:z-10 dark:text-darkInputErrorColor dark:hover:text-darkInputErrorColor"
73
73
  variant="secondary"
74
74
  >
75
- <IconTrashBinSolid class="w-4 h-4" />
75
+ <IconTrash2 class="w-4 h-4" />
76
76
  {{ $t('Delete') }}
77
77
  </Button>
78
78
 
@@ -100,18 +100,18 @@
100
100
  >
101
101
  <div
102
102
  v-if="groups && groups.length > 0"
103
- class="text-md font-semibold px-6 py-3 flex flex-1 items-center bg-lightShowTableHeadingBackground dark:bg-darkShowTableHeadingBackground dark:text-darkShowTableHeadingText border-b border-lightShowTableBodyBorder dark:border-darkShowTableBodyBorder"
103
+ class="text-sm font-semibold px-5 py-3 flex flex-1 items-center bg-lightShowTableHeadingBackground dark:bg-darkShowTableHeadingBackground dark:text-darkShowTableHeadingText border-b border-lightShowTableBodyBorder dark:border-darkShowTableBodyBorder"
104
104
  >
105
- <div class="h-4 bg-gray-300 dark:bg-gray-600 rounded-full w-32"></div>
105
+ <div class="h-3 bg-lightSkeletonBackgroundColor dark:bg-darkSkeletonBackgroundColor rounded-full w-32"></div>
106
106
  </div>
107
107
 
108
108
  <table class="w-full text-sm text-left table-fixed border-collapse">
109
109
  <thead class="bg-lightShowTableUnderHeadingBackground dark:bg-darkShowTableUnderHeadingBackground block md:table-row-group">
110
110
  <tr class="block md:table-row">
111
- <th scope="col" class="px-6 py-3 text-xs uppercase hidden md:w-52 md:table-cell text-lightShowTableUnderHeadingText dark:text-darkShowTableUnderHeadingText">
111
+ <th scope="col" class="px-5 py-2.5 text-xs font-medium hidden md:w-56 md:table-cell text-lightShowTableUnderHeadingText dark:text-darkShowTableUnderHeadingText">
112
112
  {{ $t('Field') }}
113
113
  </th>
114
- <th scope="col" class="px-6 py-3 text-xs uppercase hidden md:table-cell text-lightShowTableUnderHeadingText dark:text-darkShowTableUnderHeadingText">
114
+ <th scope="col" class="px-5 py-2.5 text-xs font-medium hidden md:table-cell text-lightShowTableUnderHeadingText dark:text-darkShowTableUnderHeadingText">
115
115
  {{ $t('Value') }}
116
116
  </th>
117
117
  </tr>
@@ -125,13 +125,13 @@
125
125
  >
126
126
  <td class="px-6 py-[15.5px] relative block md:table-cell pb-0 md:pb-[15.5px]">
127
127
  <div class="md:absolute md:inset-0 flex items-center px-6 py-[15.5px]">
128
- <div class="h-2.5 bg-gray-200 dark:bg-gray-700 rounded-full w-24"></div>
128
+ <div class="h-2.5 bg-lightSkeletonBackgroundColor dark:bg-darkSkeletonBackgroundColor rounded-full w-24"></div>
129
129
  </div>
130
130
  </td>
131
131
 
132
132
  <td class="px-6 py-[15.5px] whitespace-pre-wrap block md:table-cell">
133
133
  <div class="flex items-center h-full min-h-[21px]">
134
- <div class="h-2.5 bg-gray-200 dark:bg-gray-700 rounded-full w-full max-w-[280px]"></div>
134
+ <div class="h-2.5 bg-lightSkeletonBackgroundColor dark:bg-darkSkeletonBackgroundColor rounded-full w-full max-w-[280px]"></div>
135
135
  </div>
136
136
  </td>
137
137
  </tr>
@@ -172,7 +172,7 @@
172
172
  </template>
173
173
  </div>
174
174
 
175
- <div v-else class="text-center text-gray-500 dark:text-gray-400 mt-10">
175
+ <div v-else class="text-center text-sm text-lightListTablePaginationHelpText dark:text-darkListTablePaginationHelpText mt-10">
176
176
  {{ $t('Ooops. Record not found') }}
177
177
  </div>
178
178
 
@@ -196,8 +196,8 @@
196
196
  import BreadcrumbsWithButtons from '@/components/BreadcrumbsWithButtons.vue';
197
197
 
198
198
  import { useCoreStore } from '@/stores/core';
199
- import { getCustomComponent, checkAcessByAllowedActions, initThreeDotsDropdown, formatComponent, executeCustomAction } from '@/utils';
200
- import { IconPenSolid, IconTrashBinSolid, IconPlusOutline } from '@iconify-prerendered/vue-flowbite';
199
+ import { getCustomComponent, checkAcessByAllowedActions, formatComponent, executeCustomAction } from '@/utils';
200
+ import { IconPencil, IconTrash2, IconPlus } from '@iconify-prerendered/vue-lucide';
201
201
  import { onMounted, onUnmounted, ref, computed, watch } from 'vue';
202
202
  import { useRoute,useRouter } from 'vue-router';
203
203
  import {callAdminForthApi} from '@/utils';
@@ -271,7 +271,6 @@ onMounted(async () => {
271
271
  await coreStore.fetchResourceFull({
272
272
  resourceId: route.params.resourceId as string,
273
273
  });
274
- initThreeDotsDropdown();
275
274
  await coreStore.fetchRecord({
276
275
  resourceId: route.params.resourceId as string,
277
276
  primaryKey: route.params.primaryKey as string,