adminforth 1.2.41 → 1.2.43

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 (47) hide show
  1. package/dataConnectors/mongo.ts +1 -2
  2. package/dist/dataConnectors/mongo.js +1 -2
  3. package/dist/index.js +5 -0
  4. package/dist/spa/index.html +4 -4
  5. package/dist/spa/package-lock.json +206 -5
  6. package/dist/spa/package.json +8 -2
  7. package/dist/spa/public/assets/favicon.png +0 -0
  8. package/dist/spa/src/App.vue +171 -72
  9. package/dist/spa/src/assets/logo.svg +19 -1
  10. package/dist/spa/src/components/AcceptModal.vue +3 -10
  11. package/dist/spa/src/components/BreadcrumbsWithButtons.vue +1 -1
  12. package/dist/spa/src/components/CustomDatePicker.vue +19 -6
  13. package/dist/spa/src/components/CustomDateRangePicker.vue +14 -5
  14. package/dist/spa/src/components/CustomRangePicker.vue +148 -0
  15. package/dist/spa/src/components/Dropdown.vue +19 -5
  16. package/dist/spa/src/components/Filters.vue +91 -37
  17. package/dist/spa/src/components/MenuLink.vue +2 -2
  18. package/dist/spa/src/components/ResourceForm.vue +163 -103
  19. package/dist/spa/src/components/ResourceListTable.vue +419 -0
  20. package/dist/spa/src/components/Toast.vue +66 -0
  21. package/dist/spa/src/components/ValueRenderer.vue +22 -6
  22. package/dist/spa/src/composables/useFrontendApi.ts +26 -0
  23. package/dist/spa/src/composables/useStores.ts +113 -0
  24. package/dist/spa/src/main.ts +1 -1
  25. package/dist/spa/src/router/index.ts +30 -19
  26. package/dist/spa/src/spa_types/core.ts +51 -0
  27. package/dist/spa/src/stores/core.ts +65 -59
  28. package/dist/spa/src/stores/filters.ts +22 -0
  29. package/dist/spa/src/stores/modal.ts +13 -3
  30. package/dist/spa/src/stores/toast.ts +15 -0
  31. package/dist/spa/src/stores/user.ts +54 -0
  32. package/dist/spa/src/utils.ts +65 -8
  33. package/dist/spa/src/views/CreateView.vue +76 -16
  34. package/dist/spa/src/views/EditView.vue +76 -14
  35. package/dist/spa/src/views/ListView.vue +88 -360
  36. package/dist/spa/src/views/LoginView.vue +17 -5
  37. package/dist/spa/src/views/ResourceParent.vue +1 -1
  38. package/dist/spa/src/views/ShowView.vue +114 -19
  39. package/dist/spa/tailwind.config.js +5 -2
  40. package/dist/spa/vite.config.ts +6 -0
  41. package/index.ts +5 -0
  42. package/package.json +1 -1
  43. package/dist/spa/public/favicon.ico +0 -0
  44. package/dist/spa/spa/public/favicon.ico +0 -0
  45. package/dist/spa/spa/src/views/HomeView.vue +0 -8
  46. package/dist/spa/src/views/HomeView.vue +0 -8
  47. package/dist/types.js +0 -30
@@ -0,0 +1,113 @@
1
+ import type { FrontendAPIInterface, ConfirmParams, AlertParams, FilterParams,Operator } from '../types/FrontendAPI';
2
+ import { useToastStore } from '../stores/toast';
3
+ import { useModalStore } from '../stores/modal';
4
+ import { useCoreStore } from '@/stores/core';
5
+ import { useFiltersStore } from '@/stores/filters';
6
+ import router from '@/router'
7
+ import type { AdminForthResourceColumn } from '@/types/AdminForthConfig';
8
+
9
+
10
+
11
+
12
+
13
+ declare global {
14
+ interface Window {
15
+ adminforth: {
16
+ confirm: (params: ConfirmParams) => Promise<void>;
17
+ alert: (params: AlertParams) => void;
18
+ setListFilter: (filter: any) => void;
19
+ updateListFilter: (filter: any) => void;
20
+ clearListFilters: () => void;
21
+ };
22
+ }
23
+ }
24
+
25
+ export class FrontendAPI implements FrontendAPIInterface {
26
+ private validOperators: Operator[] = ['lte', 'gte', 'in','ilike'];
27
+ private toastStore:any
28
+ private modalStore:any
29
+ private filtersStore:any
30
+ private coreStore:any
31
+ init() {
32
+ if (window.adminforth) {
33
+ throw new Error('adminforth already initialized');
34
+ }
35
+ this.toastStore = useToastStore();
36
+ this.modalStore = useModalStore();
37
+ console.log(this.toastStore, this.modalStore,'init of adminforth frontend api')
38
+ window.adminforth = {
39
+ confirm: this.confirm.bind(this),
40
+ alert: this.alert.bind(this),
41
+ setListFilter: this.setListFilter.bind(this),
42
+ updateListFilter: this.updateListFilter.bind(this),
43
+ clearListFilters: this.clearListFilters.bind(this),
44
+ }
45
+ }
46
+
47
+ confirm(params: ConfirmParams): Promise<void> {
48
+ return new Promise((resolve, reject) => {
49
+ this.modalStore.setModalContent({ content: params.message, acceptText: params.yes, cancelText: params.no })
50
+ this.modalStore.onAcceptFunction = resolve
51
+ this.modalStore.onCancelFunction = reject
52
+ this.modalStore.togleModal()
53
+ })
54
+ }
55
+
56
+ alert(params: AlertParams): void {
57
+ this.toastStore.addToast({
58
+ message: params.message,
59
+ variant: params.variant,
60
+ timeout: params.timeout
61
+ })
62
+ }
63
+
64
+ listFilterValidation(filter: FilterParams): boolean {
65
+ if(router.currentRoute.value.meta.type !== 'list'){
66
+ throw new Error(`Cannot use ${this.setListFilter.name} filter on a list page`)
67
+ } else {
68
+ if(!this.coreStore) this.coreStore = useCoreStore()
69
+ console.log(this.coreStore.resourceColumnsWithFilters,'core store')
70
+ const filterField = this.coreStore.resourceColumnsWithFilters.find((col: AdminForthResourceColumn) => col.name === filter.field)
71
+ if(!filterField){
72
+ throw new Error(`Field ${filter.field} is not available for filtering`)
73
+ }
74
+ if(filterField) {
75
+ if(!this.validOperators.includes(filter.operator)){
76
+ throw new Error(`Operator ${filter.operator} is not valid`)
77
+ }
78
+ }
79
+ }
80
+ return true
81
+ }
82
+
83
+ setListFilter(filter: FilterParams): void {
84
+ if(this.listFilterValidation(filter)){
85
+ this.filtersStore = useFiltersStore()
86
+ if(this.filtersStore.filters.some((f) => {return f.field === filter.field && f.operator === filter.operator})){
87
+ throw new Error(`Filter ${filter.field} with operator ${filter.operator} already exists`)
88
+ } else {
89
+ this.filtersStore.setFilter(filter)
90
+ }
91
+ }
92
+ }
93
+
94
+ clearListFilters(): void {
95
+ this.filtersStore = useFiltersStore()
96
+ this.filtersStore.clearFilters()
97
+ }
98
+
99
+ updateListFilter(filter: FilterParams): void {
100
+ if(this.listFilterValidation(filter)){
101
+ this.filtersStore = useFiltersStore()
102
+ const index = this.filtersStore.filters.findIndex((f: FilterParams) => f.field === filter.field)
103
+ if(index === -1) {
104
+ this.filtersStore.setFilter(filter)
105
+ } else {
106
+ this.filtersStore.setFilters([...this.filtersStore.filters.slice(0, index), filter, ...this.filtersStore.filters.slice(index + 1)])
107
+ }
108
+ }
109
+ }
110
+
111
+
112
+
113
+ }
@@ -9,7 +9,7 @@ import router from './router'
9
9
  const app = createApp(App)
10
10
  /* IMPORTANT:ADMINFORTH COMPONENT REGISTRATIONS */
11
11
 
12
-
12
+
13
13
  app.use(createPinia())
14
14
  app.use(router)
15
15
 
@@ -1,23 +1,24 @@
1
1
  import { createRouter, createWebHistory } from 'vue-router'
2
- import HomeView from '../views/HomeView.vue'
3
2
  import ResourceParent from '@/views/ResourceParent.vue'
4
- import ListView from '@/views/ListView.vue'
5
- import ShowView from '@/views/ShowView.vue'
6
- import EditView from '@/views/EditView.vue'
7
- import CreateView from '@/views/CreateView.vue'
3
+ import { useUserStore } from '@/stores/user'
4
+ /* IMPORTANT:ADMINFORTH ROUTES IMPORTS */
8
5
 
9
6
  const router = createRouter({
10
7
  history: createWebHistory(import.meta.env.BASE_URL),
11
8
  routes: [
12
- {
13
- path: '/',
14
- name: 'home',
15
- component: HomeView
16
- },
17
9
  {
18
10
  path: '/login',
19
11
  name: 'login',
20
- component: () => import('@/views/LoginView.vue')
12
+ component: () => import('@/views/LoginView.vue'),
13
+ meta: { title: 'login' },
14
+ beforeEnter: async (to, from, next) => {
15
+ const userStore = useUserStore()
16
+ if(localStorage.getItem('isAuthorized') === 'true'){
17
+ next({name: 'home'})
18
+ } else {
19
+ next()
20
+ }
21
+ }
21
22
  },
22
23
  {
23
24
  path: '/resource/:resourceId',
@@ -26,23 +27,29 @@ const router = createRouter({
26
27
  children: [
27
28
  {
28
29
  path: '',
29
- component: ListView,
30
- name: 'resource-list'
30
+ component: () => import('@/views/ListView.vue'),
31
+ name: 'resource-list',
32
+ meta: { title: 'list',type: 'list' }
31
33
  },
32
34
  {
33
35
  path: 'show/:primaryKey',
34
- component: ShowView,
35
- name: 'resource-show'
36
+ component: () => import('@/views/ShowView.vue'),
37
+ name: 'resource-show',
38
+ meta: { title: 'show', type: 'show'}
39
+
36
40
  },
37
41
  {
38
42
  path: 'edit/:primaryKey',
39
- component: EditView,
40
- name: 'resource-edit'
43
+ component: () => import('@/views/EditView.vue'),
44
+ name: 'resource-edit',
45
+ meta: { title: 'edit', type: 'edit'}
41
46
  },
42
47
  {
43
48
  path: 'create',
44
- component: CreateView,
45
- name: 'resource-create'
49
+ component: () => import('@/views/CreateView.vue'),
50
+ name: 'resource-create',
51
+ meta: { title: 'create', type: 'create'}
52
+
46
53
  },
47
54
  ]
48
55
  },
@@ -50,4 +57,8 @@ const router = createRouter({
50
57
  ]
51
58
  })
52
59
 
60
+
61
+
62
+
63
+
53
64
  export default router
@@ -0,0 +1,51 @@
1
+ import type { AdminForthResource, AdminForthResourceColumn } from '../types/AdminForthConfig';
2
+
3
+ export type resourceById = {
4
+ [key: string]: AdminForthResource;
5
+ }
6
+
7
+ export type Menu = {
8
+ label: string,
9
+ icon: string,
10
+ resourceId: string,
11
+ children?: Array<Menu>,
12
+ }
13
+
14
+ export type Record = {
15
+ [key: string]: any;
16
+ }
17
+
18
+ export type ResourceColumns = {
19
+ [key: string]: Array<AdminForthResourceColumn>;
20
+ }
21
+
22
+ export type CoreConfig = {
23
+ brandName: string,
24
+ brandLogo: string,
25
+ title: string,
26
+ datesFormat: string,
27
+ usernameField: string,
28
+ usernameFieldName?: string,
29
+ deleteConfirmation?: boolean,
30
+ auth?: {
31
+ resourceId: string,
32
+ usernameField: string,
33
+ passwordHashField: string,
34
+ loginBackgroundImage: string,
35
+ userFullnameField: string,
36
+ },
37
+ emptyFieldPlaceholder?: {
38
+ show: string,
39
+ list: string,
40
+
41
+ } | string,
42
+ }
43
+
44
+
45
+ export type AllowedActions = {
46
+ show: boolean,
47
+ create: boolean,
48
+ edit: boolean,
49
+ delete: boolean,
50
+ }
51
+
@@ -1,96 +1,105 @@
1
1
  import { ref, computed } from 'vue'
2
2
  import { defineStore } from 'pinia'
3
3
  import { callAdminForthApi } from '@/utils';
4
- import router from '@/router';
5
-
6
- async function findHomepage(menu) {
7
- for (const item of menu) {
8
- if (item.homepage) {
9
- return item;
10
- }
11
- if (item.children) {
12
- const res = findHomepage(item.children);
13
- if (res) {
14
- return res;
15
- }
16
- }
17
- }
18
- return null;
19
- }
4
+ import type { AdminForthResource, AdminForthResourceColumn } from '@/types/AdminForthConfig';
5
+ import type { Ref } from 'vue'
20
6
 
21
7
  export const useCoreStore = defineStore('core', () => {
22
- const resourceById = ref([]);
8
+ const resourceById: Ref<Object> = ref({});
23
9
  const menu = ref([]);
24
10
  const config = ref({});
25
- const record = ref({});
26
- const resourceColumns = ref(null);
11
+ const record: Ref<any | null> = ref({});
12
+ const resource: Ref<AdminForthResource | null> = ref(null);
13
+
14
+ const resourceColumnsWithFilters = computed(() => {
15
+ if (!resource.value) {
16
+ return [];
17
+ }
18
+ return resource.value.columns.filter((col: AdminForthResourceColumn) => col.showIn?.includes('filter'));
19
+ })
20
+
21
+ const resourceOptions = ref(null);
27
22
  const resourceColumnsError = ref('');
28
23
  const resourceColumnsId = ref(null);
29
- const user = ref(null);
24
+ const adminUser = ref(null);
30
25
 
31
26
  async function fetchMenuAndResource() {
32
27
  const resp = await callAdminForthApi({
33
28
  path: '/get_base_config',
34
29
  method: 'GET',
35
30
  });
31
+ if(!resp){
32
+ return
33
+ }
36
34
  menu.value = resp.menu;
37
- resourceById.value = resp.resources.reduce((acc, resource) => {
35
+ resourceById.value = resp.resources.reduce((acc: Object, resource: AdminForthResource) => {
38
36
  acc[resource.resourceId] = resource;
39
37
  return acc;
40
38
  }, {});
41
39
  config.value = resp.config;
42
- user.value = resp.user;
43
-
44
- // find homepage:true in menu recuresively
45
- if (import.meta.env.DEV){
46
- return
47
- } else {
48
- const homepage = await findHomepage(menu.value);
49
- if (homepage) {
50
- if (homepage.resourceId) {
51
- // redirect to homepage
52
- router.push({ name: 'resource-list', params: { resourceId: homepage.resourceId } });
53
- } else {
54
- // redirect to path
55
- router.push(homepage.path);
56
- }
57
- }
58
- }
40
+ adminUser.value = resp.user;
41
+ console.log('🌍 AdminForth v', resp.version);
59
42
  }
60
43
 
61
44
  async function fetchRecord({ resourceId, primaryKey }) {
62
45
  record.value = null;
46
+
47
+ if (!resource.value) {
48
+ throw new Error('Columns not fetched yet');
49
+ }
63
50
 
64
- record.value = await callAdminForthApi({
65
- path: '/get_record',
51
+ const respData = await callAdminForthApi({
52
+ path: '/get_resource_data',
66
53
  method: 'POST',
67
54
  body: {
55
+ source: 'show',
68
56
  resourceId: resourceId,
69
- primaryKey: primaryKey,
57
+ filters: [
58
+ {
59
+ field: resource.value.columns.find((col: AdminForthResourceColumn) => col.primaryKey).name,
60
+ operator: 'eq',
61
+ value: primaryKey
62
+ }
63
+ ],
64
+ sort: [],
65
+ limit: 1,
66
+ offset: 0
70
67
  }
71
68
  });
69
+
70
+ if (respData.error) {
71
+ window.adminforth.alert({
72
+ message: respData.error,
73
+ variant: 'danger',
74
+ timeout: 'unlimited'
75
+ });
76
+ record.value = {};
77
+ } else {
78
+ record.value = respData.data[0];
79
+ }
80
+
72
81
  }
73
82
 
74
- async function fetchColumns({ resourceId }) {
75
- if (resourceColumnsId.value === resourceId && resourceColumns.value) {
83
+ async function fetchResourceFull({ resourceId }: { resourceId: string }) {
84
+ if (resourceColumnsId.value === resourceId && resource.value) {
76
85
  // already fetched
77
86
  return;
78
87
  }
79
88
  resourceColumnsId.value = resourceId;
80
- resourceColumns.value = null;
81
89
  resourceColumnsError.value = '';
82
90
  const res = await callAdminForthApi({
83
- path: '/get_resource_columns',
91
+ path: '/get_resource',
84
92
  method: 'POST',
85
93
  body: {
86
94
  resourceId,
87
95
  }
88
- }
89
- );
96
+ });
90
97
  if (res.error) {
91
98
  resourceColumnsError.value = res.error;
92
99
  } else {
93
- resourceColumns.value = res.resource.columns;
100
+ resourceById.value[resourceId] = res.resource;
101
+ resource.value = res.resource;
102
+ resourceOptions.value = res.resource.options;
94
103
  }
95
104
  }
96
105
 
@@ -102,21 +111,16 @@ export const useCoreStore = defineStore('core', () => {
102
111
  config.value = {...config.value, ...res};
103
112
  }
104
113
 
105
- async function logout() {
106
- await callAdminForthApi({
107
- path: '/logout',
108
- method: 'POST',
109
- });
110
- }
114
+
111
115
 
112
116
  const username = computed(() => {
113
117
  const usernameField = config.value.usernameField;
114
- return user.value && user.value[usernameField];
118
+ return adminUser.value && adminUser.value[usernameField];
115
119
  });
116
120
 
117
121
  const userFullname = computed(() => {
118
122
  const userFullnameField = config.value.userFullnameField;
119
- return user.value && user.value[userFullnameField];
123
+ return adminUser.value && adminUser.value[userFullnameField];
120
124
  })
121
125
 
122
126
 
@@ -130,9 +134,11 @@ export const useCoreStore = defineStore('core', () => {
130
134
  fetchMenuAndResource,
131
135
  fetchRecord,
132
136
  record,
133
- resourceColumns,
134
- fetchColumns,
137
+ fetchResourceFull,
135
138
  resourceColumnsError,
136
- logout
139
+ resourceOptions,
140
+ resource,
141
+ adminUser,
142
+ resourceColumnsWithFilters
137
143
  }
138
144
  })
@@ -0,0 +1,22 @@
1
+ import { ref } from 'vue'
2
+ import { defineStore } from 'pinia'
3
+ import { callAdminForthApi } from '@/utils';
4
+
5
+ export const useFiltersStore = defineStore('filters', () => {
6
+ const filters = ref([]);
7
+ const setFilter = (filter: any) => {
8
+ filters.value = [...filters.value, filter];
9
+ }
10
+ const setFilters = (f: any) => {
11
+ filters.value = [...f];
12
+ }
13
+ const getFilters = () => {
14
+ return filters.value;
15
+ }
16
+ const clearFilters = () => {
17
+ filters.value = [];
18
+ }
19
+ return {setFilter, getFilters,clearFilters, filters,setFilters}
20
+ })
21
+
22
+
@@ -1,6 +1,12 @@
1
1
  import { ref } from 'vue'
2
2
  import { defineStore } from 'pinia'
3
- import { callAdminForthApi } from '@/utils';
3
+
4
+ type ModalContentType = {
5
+ title?: string;
6
+ content?: string;
7
+ acceptText?: string;
8
+ cancelText?: string;
9
+ }
4
10
 
5
11
 
6
12
  export const useModalStore = defineStore('modal', () => {
@@ -12,13 +18,17 @@ export const useModalStore = defineStore('modal', () => {
12
18
  });
13
19
  const isOpened = ref(false);
14
20
  const onAcceptFunction: any = ref(()=>{});
21
+ const onCancelFunction: any = ref(()=>{});
15
22
  function togleModal() {
16
23
  isOpened.value = !isOpened.value;
17
24
  }
18
25
  function setOnAcceptFunction(func: Function) {
19
26
  onAcceptFunction.value = func;
20
27
  }
21
- function setModalContent(content: string) {
28
+ function setOnCancelFunction(func: Function) {
29
+ onCancelFunction.value = func;
30
+ }
31
+ function setModalContent(content: ModalContentType) {
22
32
  modalContent.value = content;
23
33
  }
24
34
  function resetmodalState() {
@@ -33,6 +43,6 @@ export const useModalStore = defineStore('modal', () => {
33
43
 
34
44
  }
35
45
 
36
- return {isOpened, setModalContent, togleModal,modalContent, setOnAcceptFunction, onAcceptFunction,resetmodalState}
46
+ return {isOpened, setModalContent,onCancelFunction, togleModal,modalContent, setOnAcceptFunction, onAcceptFunction,resetmodalState,setOnCancelFunction}
37
47
 
38
48
  })
@@ -0,0 +1,15 @@
1
+ import { ref, computed } from 'vue'
2
+ import { defineStore } from 'pinia'
3
+ import { callAdminForthApi } from '@/utils';
4
+ import { v1 as uuid } from 'uuid';
5
+
6
+ export const useToastStore = defineStore('toast', () => {
7
+ const toasts = ref([]);
8
+ const addToast = (toast) => {
9
+ toasts.value.push({...toast, id: uuid()});
10
+ };
11
+ const removeToast = (toast) => {
12
+ toasts.value = toasts.value.filter((t) => t.id !== toast.id);
13
+ };
14
+ return { toasts, addToast, removeToast };
15
+ });
@@ -0,0 +1,54 @@
1
+ import {ref} from 'vue';
2
+ import {defineStore} from 'pinia';
3
+ import { callAdminForthApi } from '@/utils';
4
+
5
+ export const useUserStore = defineStore('user', () => {
6
+ const isAuthorized = ref(false);
7
+
8
+ function authorize() {
9
+ isAuthorized.value = true;
10
+ localStorage.setItem('isAuthorized', 'true');
11
+ }
12
+
13
+ function unauthorize() {
14
+ isAuthorized.value = false;
15
+ localStorage.setItem('isAuthorized', 'false');
16
+ }
17
+
18
+ async function logout() {
19
+ await callAdminForthApi({
20
+ path: '/logout',
21
+ method: 'POST',
22
+ });
23
+ unauthorize();
24
+ }
25
+
26
+ // async function checkAuth( skipApiCall = false){
27
+ // console.log('checkAuth', isAuthorized.value, skipApiCall)
28
+ // if(isAuthorized.value) {
29
+ // return true}
30
+ // else {
31
+ // if(skipApiCall) return false;
32
+ // const resp = await callAdminForthApi({
33
+ // path: '/check_auth',
34
+ // method: 'POST',
35
+ // });
36
+ // if (resp.status !== 401) {
37
+ // authorize();
38
+ // return true;
39
+ // }
40
+ // else {
41
+ // unauthorize();
42
+ // return false;}
43
+ // }
44
+
45
+ // }
46
+
47
+ return {
48
+ isAuthorized,
49
+ authorize,
50
+ unauthorize,
51
+ logout
52
+ }
53
+
54
+ });
@@ -1,6 +1,10 @@
1
1
  import { onMounted, ref, resolveComponent } from 'vue';
2
+ import type { CoreConfig } from './spa_types/core';
2
3
 
3
4
  import router from "./router";
5
+ import { useRouter } from 'vue-router';
6
+ import { useCoreStore } from './stores/core';
7
+ import { useUserStore } from './stores/user';
4
8
 
5
9
  export async function callApi({path, method, body=undefined} ) {
6
10
  const options = {
@@ -13,14 +17,18 @@ export async function callApi({path, method, body=undefined} ) {
13
17
  const fullPath = `${import.meta.env.VITE_ADMINFORTH_PUBLIC_PATH || ''}${path}`;
14
18
  const r = await fetch(fullPath, options);
15
19
  if (r.status == 401) {
16
- console.log('router', router);
17
- router.push({name: 'login'});
20
+ useUserStore().unauthorize();
21
+ router.push({ name: 'login' });
18
22
  return null;
19
- }
23
+ }
20
24
  return await r.json();
21
25
  }
22
26
 
23
- export async function callAdminForthApi({ path, method, body=undefined }) {
27
+ export async function callAdminForthApi({ path, method, body=undefined }: {
28
+ path: string,
29
+ method: 'GET' | 'POST' | 'PUT' | 'DELETE',
30
+ body?: any
31
+ }) {
24
32
  try {
25
33
  return callApi({path: `/adminapi/v1${path}`, method, body} );
26
34
  } catch (e) {
@@ -29,9 +37,11 @@ export async function callAdminForthApi({ path, method, body=undefined }) {
29
37
  }
30
38
  }
31
39
 
32
-
33
-
34
-
40
+ export function getCustomComponent({ file, meta }: { file: string, meta: any }) {
41
+ const name = file.replace(/@/g, '').replace(/\./g, '').replace(/\//g, '');
42
+ console.log('resolving name', name);
43
+ return resolveComponent(name);
44
+ }
35
45
 
36
46
  export function getIcon(icon: string) {
37
47
  // icon format is "feather:icon-name". We need to get IconName in pascal case
@@ -41,4 +51,51 @@ export function getIcon(icon: string) {
41
51
  const [iconSet, iconName] = icon.split(':');
42
52
  const compName = 'Icon' + iconName.split('-').map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join('');
43
53
  return resolveComponent(compName);
44
- }
54
+ }
55
+
56
+ export const loadFile = (file: string) => {
57
+ if (file.startsWith('http')) {
58
+ return file;
59
+ }
60
+ let path;
61
+ let baseUrl = '';
62
+ if (file.startsWith('@/')) {
63
+ path = file.replace('@/', '');
64
+ baseUrl = new URL(`./${path}`, import.meta.url).href;
65
+ } else if (file.startsWith('@@/')) {
66
+ path = file.replace('@@/', '');
67
+ baseUrl = new URL(`./custom/${path}`, import.meta.url).href;
68
+ } else {
69
+ baseUrl = new URL(`./${file}`, import.meta.url).href;
70
+ }
71
+ return baseUrl;
72
+ }
73
+
74
+ export function checkEmptyValues(value: any, viewType:'show' | 'list' ) {
75
+ const config: CoreConfig | {} = useCoreStore().config;
76
+ let emptyFieldPlaceholder = '';
77
+ if (config.emptyFieldPlaceholder) {
78
+ if(typeof config.emptyFieldPlaceholder === 'string') {
79
+ emptyFieldPlaceholder = config.emptyFieldPlaceholder;
80
+ } else {
81
+ emptyFieldPlaceholder = config.emptyFieldPlaceholder?.[viewType] || '';
82
+ }
83
+ if (value === null || value === undefined || value === '') {
84
+ return emptyFieldPlaceholder;
85
+ }
86
+ }
87
+ return value;
88
+ }
89
+
90
+ export function checkAcessByAllowedActions(allowedActions:any, action:any ) {
91
+ if (!allowedActions) {
92
+ console.warn('allowedActions not set');
93
+ return;
94
+ }
95
+ if(allowedActions[action] === false) {
96
+ console.warn(`Action ${action} is not allowed`);
97
+ router.back();
98
+ }
99
+ }
100
+
101
+