adminforth 1.0.67 → 1.0.69

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 (63) hide show
  1. package/dist/index.js +20 -12
  2. package/dist/modules/codeInjector.js +8 -3
  3. package/dist/servers/express.js +1 -1
  4. package/dist/spa/index.html +2 -2
  5. package/dist/spa/package-lock.json +1 -189
  6. package/dist/spa/package.json +1 -6
  7. package/dist/spa/spa/index.html +1 -1
  8. package/dist/spa/spa/package-lock.json +4 -4
  9. package/dist/spa/spa/package.json +1 -1
  10. package/dist/spa/spa/public/favicon.png +0 -0
  11. package/dist/spa/spa/src/App.vue +34 -16
  12. package/dist/spa/spa/src/assets/logo.svg +19 -1
  13. package/dist/spa/spa/src/components/ShowTableItem.vue +14 -0
  14. package/dist/spa/spa/src/router/index.ts +12 -5
  15. package/dist/spa/spa/src/stores/core.ts +3 -2
  16. package/dist/spa/spa/src/views/CreateView.vue +1 -1
  17. package/dist/spa/spa/src/views/EditView.vue +1 -1
  18. package/dist/spa/spa/src/views/HomeView.vue +8 -0
  19. package/dist/spa/spa/src/views/ListView.vue +2 -2
  20. package/dist/spa/spa/src/views/ShowView.vue +1 -1
  21. package/dist/spa/spa/vite.config.ts +6 -0
  22. package/dist/spa/src/App.vue +57 -76
  23. package/dist/spa/src/components/AcceptModal.vue +1 -1
  24. package/dist/spa/src/components/CustomDatePicker.vue +3 -3
  25. package/dist/spa/src/components/CustomDateRangePicker.vue +3 -3
  26. package/dist/spa/src/components/Dropdown.vue +4 -13
  27. package/dist/spa/src/components/Filters.vue +22 -55
  28. package/dist/spa/src/components/MenuLink.vue +2 -2
  29. package/dist/spa/src/components/ResourceForm.vue +99 -157
  30. package/dist/spa/src/components/ValueRenderer.vue +1 -11
  31. package/dist/spa/src/router/index.ts +2 -3
  32. package/dist/spa/src/stores/core.ts +33 -22
  33. package/dist/spa/src/utils.ts +3 -5
  34. package/dist/spa/src/views/CreateView.vue +7 -15
  35. package/dist/spa/src/views/EditView.vue +8 -36
  36. package/dist/spa/src/views/HomeView.vue +8 -0
  37. package/dist/spa/src/views/ListView.vue +23 -35
  38. package/dist/spa/src/views/LoginView.vue +1 -1
  39. package/dist/spa/src/views/ResourceParent.vue +1 -1
  40. package/dist/spa/src/views/ShowView.vue +9 -20
  41. package/dist/spa/tailwind.config.js +2 -5
  42. package/documentation/docusaurus.config.ts +2 -2
  43. package/documentation/sidebars.ts +1 -1
  44. package/index.ts +23 -13
  45. package/modules/codeInjector.ts +7 -3
  46. package/package.json +1 -1
  47. package/servers/express.ts +1 -1
  48. package/spa/index.html +1 -1
  49. package/spa/package-lock.json +4 -4
  50. package/spa/package.json +1 -1
  51. package/spa/public/favicon.png +0 -0
  52. package/spa/src/App.vue +34 -16
  53. package/spa/src/assets/logo.svg +19 -1
  54. package/spa/src/router/index.ts +12 -5
  55. package/spa/src/stores/core.ts +3 -2
  56. package/spa/src/views/CreateView.vue +1 -1
  57. package/spa/src/views/EditView.vue +1 -1
  58. package/spa/src/views/ListView.vue +2 -2
  59. package/spa/src/views/ShowView.vue +1 -1
  60. package/spa/vite.config.ts +6 -0
  61. package/types/AdminForthConfig.ts +10 -3
  62. package/dist/spa/src/components/CustomRangePicker.vue +0 -148
  63. package/spa/public/favicon.ico +0 -0
@@ -65,7 +65,7 @@
65
65
  </BreadcrumbsWithButtons>
66
66
 
67
67
  <!-- table -->
68
- <div class="relative overflow-x-auto shadow-list-table-shadow dark:shadow-black dark:s">
68
+ <div class="relative overflow-x-auto shadow-md sm:rounded-lg dark:shadow-2xl">
69
69
 
70
70
 
71
71
  <!-- skelet loader -->
@@ -90,7 +90,7 @@
90
90
  </div>
91
91
 
92
92
  <table v-else class="w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400">
93
- <thead class="text-xs text-view-table-heading-text bg-list-view-table-heading dark:bg-gray-700 dark:text-gray-400">
93
+ <thead class="text-xs text-gray-700 bg-gray-50 dark:bg-gray-700 dark:text-gray-400">
94
94
  <tr>
95
95
  <th scope="col" class="p-4">
96
96
  <div v-if="rows && rows.length" class="flex items-center">
@@ -127,7 +127,7 @@
127
127
  </tr>
128
128
  </thead>
129
129
  <tbody>
130
- <tr v-if="!rows" class="bg-list-view-table-bg border-b dark:bg-gray-800 dark:border-gray-700">
130
+ <tr v-if="!rows" class="bg-white border-b dark:bg-gray-800 dark:border-gray-700">
131
131
  <td :colspan="coreStore.resourceColumns.length + 2">
132
132
 
133
133
  <div role="status"
@@ -171,7 +171,7 @@
171
171
  </div>
172
172
  </td>
173
173
  </tr>
174
- <tr v-else-if="rows.length === 0" class="bg-list-view-table-bg border-b dark:bg-gray-800 dark:border-gray-700">
174
+ <tr v-else-if="rows.length === 0" class="bg-white border-b dark:bg-gray-800 dark:border-gray-700">
175
175
  <td :colspan="coreStore.resourceColumns.length + 2">
176
176
 
177
177
  <div id="toast-simple"
@@ -185,7 +185,7 @@
185
185
  </tr>
186
186
 
187
187
  <tr v-else v-for="(row, rowI) in rows" :key="row.id"
188
- class="bg-list-view-table-bg border-b dark:bg-gray-800 border-list-view-border-color dark:border-gray-700 hover:bg-list-view-table-row-hover dark:hover:bg-gray-600">
188
+ class="bg-white border-b dark:bg-gray-800 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-600">
189
189
  <td class="w-4 p-4">
190
190
  <div class="flex items center">
191
191
  <input
@@ -198,12 +198,7 @@
198
198
  </div>
199
199
  </td>
200
200
  <td v-for="c in columnsListed" class="px-6 py-4">
201
- <!-- if c.name in listComponentsPerColumn, render it. If not, render ValueRenderer -->
202
- <component
203
- :is="listComponentsPerColumn[c.name] || ValueRenderer"
204
- :column="c"
205
- :row="row"
206
- />
201
+ <ValueRenderer :column="c" :row="row"/>
207
202
  </td>
208
203
  <td class="flex items-center px-6 py-4">
209
204
 
@@ -315,28 +310,29 @@
315
310
  </template>
316
311
 
317
312
  <script setup>
313
+ import {ref, onMounted, watch, computed} from 'vue';
314
+ import {callAdminForthApi, getIcon} from '@/utils';
315
+ import {useRoute} from 'vue-router';
316
+ import {useCoreStore} from '@/stores/core';
317
+ import {useModalStore} from '@/stores/modal';
318
318
  import BreadcrumbsWithButtons from '@/components/BreadcrumbsWithButtons.vue';
319
- import { useCoreStore } from '@/stores/core';
320
- import { useModalStore } from '@/stores/modal';
321
- import { callAdminForthApi, getIcon } from '@/utils';
322
- import { initFlowbite } from 'flowbite';
323
- import { computed, onMounted, ref, watch } from 'vue';
324
- import { useRoute } from 'vue-router';
319
+ import {initFlowbite} from 'flowbite'
325
320
 
326
321
  import ValueRenderer from '@/components/ValueRenderer.vue';
327
- import { getCustomComponent } from '@/utils';
328
322
 
329
323
  import {
330
- IconInboxOutline,
331
- IconPlusOutline
324
+ IconChevronDoubleLeftOutline,
325
+ IconChevronDoubleRightOutline,
326
+ IconInboxFullSolid,
327
+ IconInboxOutline,
328
+ IconPlusOutline
332
329
  } from '@iconify-prerendered/vue-flowbite';
333
330
 
334
331
  import {
335
- IconBanOutline,
336
- IconEyeSolid,
337
- IconFilterOutline,
338
- IconPenSolid,
339
- IconTrashBinSolid
332
+ IconEyeSolid,
333
+ IconTrashBinSolid,
334
+ IconPenSolid,
335
+ IconFilterOutline, IconBanOutline
340
336
  } from '@iconify-prerendered/vue-flowbite';
341
337
 
342
338
  import Filters from '@/components/Filters.vue';
@@ -361,6 +357,7 @@ const totalRows = ref(0);
361
357
  const allCheckedActions = ref([]);
362
358
  const allowedActions = ref({});
363
359
 
360
+
364
361
  const DEFAULT_PAGE_SIZE = 10;
365
362
 
366
363
  const columnsListed = computed(() => coreStore.resourceColumns?.filter(c => c.showIn.includes('list')));
@@ -379,7 +376,6 @@ async function selectAll(value) {
379
376
 
380
377
  const pageSize = computed(() => coreStore.resourceById[route.params.resourceId]?.pageSize || DEFAULT_PAGE_SIZE);
381
378
  const totalPages = computed(() => Math.ceil(totalRows.value / pageSize.value));
382
- let listComponentsPerColumn = {};
383
379
  const allFromThisPageChecked = computed(() => {
384
380
  if (!rows.value) return false;
385
381
  return rows.value.every((r) => checkboxes.value.includes(r.id));
@@ -423,7 +419,6 @@ async function getList() {
423
419
  path: '/get_resource_data',
424
420
  method: 'POST',
425
421
  body: {
426
- source: 'list',
427
422
  resourceId: route.params.resourceId,
428
423
  limit: pageSize.value,
429
424
  offset: (page.value - 1) * pageSize.value,
@@ -431,14 +426,7 @@ async function getList() {
431
426
  sort: sort.value,
432
427
  }
433
428
  });
434
- listComponentsPerColumn = coreStore.resourceColumns.reduce((acc, column) => {
435
- if (column.component?.list) {
436
- acc[column.name] = getCustomComponent(column.component.list);
437
- }
438
- return acc;
439
- }, {});
440
-
441
- fetchStatus.value.pending = false;
429
+ fetchStatus.value.pending = false;
442
430
  rows.value = data.data?.map(row => {
443
431
  row._primaryKeyValue = row[coreStore.resourceColumns.find(c => c.primaryKey).name];
444
432
  return row;
@@ -11,7 +11,7 @@
11
11
  <div id="authentication-modal" tabindex="-1" class=" overflow-y-auto overflow-x-hidden z-50 min-w-[400px] justify-center items-center md:inset-0 h-[calc(100%-1rem)] max-h-full">
12
12
  <div class="relative p-4 w-full max-w-md max-h-full">
13
13
  <!-- Modal content -->
14
- <div class="relative bg-white rounded-lg shadow dark:bg-gray-700 dark:shadow-black" >
14
+ <div class="relative bg-white rounded-lg shadow dark:bg-gray-700">
15
15
  <!-- Modal header -->
16
16
  <div class="flex items-center justify-between p-4 md:p-5 border-b rounded-t dark:border-gray-600">
17
17
  <h3 class="text-xl font-semibold text-gray-900 dark:text-white">
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <div :key="`${$route?.params.resourceId}---${$route?.params.primaryKey}`">
2
+ <div>
3
3
  <RouterView/>
4
4
  </div>
5
5
  </template>
@@ -27,10 +27,10 @@
27
27
  </div>
28
28
  <div
29
29
  v-else
30
- class="relative overflow-x-auto shadow-resourse-form-shadow "
30
+ class="relative overflow-x-auto shadow-md sm:rounded-lg"
31
31
  >
32
32
  <table class="w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400">
33
- <thead class="text-xs text-gray-700 uppercase bg-form-view-heading dark:bg-gray-700 dark:text-gray-400">
33
+ <thead class="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400">
34
34
  <tr>
35
35
  <th scope="col" class="px-6 py-3">
36
36
  Field
@@ -42,17 +42,13 @@
42
42
  </thead>
43
43
  <tbody>
44
44
  <tr v-for="column in coreStore.resourceColumns?.filter(c => c.showIn.includes('show'))" :key="column.name"
45
- class="bg-form-view-bg odd:dark:bg-gray-900 even:dark:bg-gray-800 border-b dark:border-gray-700"
45
+ class="odd:bg-white odd:dark:bg-gray-900 even:bg-gray-50 even:dark:bg-gray-800 border-b dark:border-gray-700"
46
46
  >
47
47
  <td class="px-6 py-4 whitespace-nowrap">
48
48
  {{ column.label }}
49
49
  </td>
50
50
  <td class="px-6 py-4 whitespace-nowrap whitespace-pre-wrap">
51
- <component
52
- :is="showComponentsPerColumn[column.name] || ValueRenderer"
53
- :column="column"
54
- :row="coreStore.record"
55
- />
51
+ <ValueRenderer :column="column" :row="coreStore.record" />
56
52
  </td>
57
53
  </tr>
58
54
 
@@ -67,19 +63,18 @@
67
63
 
68
64
  <script setup>
69
65
 
66
+ import { ref, computed, onMounted } from 'vue';
67
+ import { useRoute } from 'vue-router';
68
+ import { callAdminForthApi } from '@/utils';
70
69
  import BreadcrumbsWithButtons from '@/components/BreadcrumbsWithButtons.vue';
71
- import ValueRenderer from '@/components/ValueRenderer.vue';
72
- import { useCoreStore } from '@/stores/core';
73
- import { getCustomComponent } from '@/utils';
74
70
  import { IconPenSolid, IconTrashBinSolid } from '@iconify-prerendered/vue-flowbite';
75
- import { onMounted, ref } from 'vue';
76
- import { useRoute } from 'vue-router';
71
+ import { useCoreStore } from '@/stores/core';
72
+ import ValueRenderer from '@/components/ValueRenderer.vue';
77
73
 
78
74
 
79
75
  const item = ref(null);
80
76
  const route = useRoute();
81
77
  const loading = ref(false);
82
- let showComponentsPerColumn = {};
83
78
 
84
79
  const coreStore = useCoreStore();
85
80
 
@@ -92,12 +87,6 @@ onMounted(async () => {
92
87
  resourceId: route.params.resourceId,
93
88
  primaryKey: route.params.primaryKey,
94
89
  });
95
- showComponentsPerColumn = coreStore.resourceColumns.reduce((acc, column) => {
96
- if (column.component?.show) {
97
- acc[column.name] = getCustomComponent(column.component.show);
98
- }
99
- return acc;
100
- }, {});
101
90
  loading.value = false;
102
91
  });
103
92
 
@@ -1,12 +1,9 @@
1
1
  /** @type {import('tailwindcss').Config} */
2
2
  export default {
3
- content: ["./src/**/*.{vue, js}","./src/*.{vue, js}", "./index.html", "./node_modules/flowbite/**/*.js"],
3
+ content: ["./src/**/*.{vue, js}", "./node_modules/flowbite/**/*.js"],
4
4
  theme: {
5
- extend: {
6
- /* IMPORTANT:ADMINFORTH TAILWIND STYLES */
7
- }
5
+ extend: {},
8
6
  },
9
-
10
7
  darkMode: 'class',
11
8
  plugins: [
12
9
  require('flowbite/plugin'),
@@ -92,9 +92,9 @@ const config: Config = {
92
92
  },
93
93
  {
94
94
  type: 'docSidebar',
95
- sidebarId: 'tutorialSidebar',
95
+ sidebarId: 'apiSidebar',
96
96
  position: 'left',
97
- label: 'Tutorial',
97
+ label: 'API',
98
98
  },
99
99
  {to: '/blog', label: 'Blog', position: 'left'},
100
100
  {
@@ -20,7 +20,7 @@ const sidebars: SidebarsConfig = {
20
20
  },
21
21
  ],
22
22
 
23
- mySidebar: [
23
+ apiSidebar: [
24
24
  {
25
25
  type: "category",
26
26
  label: "API",
package/index.ts CHANGED
@@ -110,12 +110,21 @@ class AdminForth {
110
110
  if (!this.config.baseUrl) {
111
111
  this.config.baseUrl = '';
112
112
  }
113
- if (!this.config.brandName) {
114
- this.config.brandName = 'AdminForth';
113
+ if (!this.config?.customization.brandName) {
114
+ this.config.customization.brandName = 'AdminForth';
115
115
  }
116
+ if (this.config.customization.brandLogo) {
117
+ if (!this.config.customization.brandLogo.startsWith('@@/')) {
118
+ errors.push(`Brand logo must start with @@ and be placed in custom directory`);
119
+ }
120
+ // todo check file exist
121
+ }
122
+
123
+
116
124
 
117
- if (!this.config.datesFormat) {
118
- this.config.datesFormat = 'MMM D, YYYY HH:mm:ss';
125
+
126
+ if (!this.config.customization.datesFormat) {
127
+ this.config.customization.datesFormat = 'MMM D, YYYY HH:mm:ss';
119
128
  }
120
129
 
121
130
  if (this.config.resources) {
@@ -469,10 +478,10 @@ class AdminForth {
469
478
  const usernameColumn = resource.columns.find((col) => col.name === usernameField);
470
479
 
471
480
  return {
472
- brandName: this.config.brandName,
481
+ brandName: this.config.customization.brandName,
473
482
  usernameFieldName: usernameColumn.label,
474
483
  loginBackgroundImage: this.config.auth.loginBackgroundImage,
475
- title: this.config?.title,
484
+ title: this.config.customization?.title,
476
485
  };
477
486
  },
478
487
  });
@@ -516,12 +525,13 @@ class AdminForth {
516
525
  })),
517
526
  menu: this.config.menu,
518
527
  config: {
519
- brandName: this.config.brandName,
520
- datesFormat: this.config.datesFormat,
528
+ brandName: this.config.customization.brandName,
529
+ brandLogo: this.config.customization.brandLogo,
530
+ datesFormat: this.config.customization.datesFormat,
521
531
  deleteConfirmation: this.config.deleteConfirmation,
522
532
  auth: this.config.auth,
523
533
  usernameField: this.config.auth.usernameField,
524
- title: this.config?.title,
534
+ title: this.config.customization?.title,
525
535
  },
526
536
  adminUser,
527
537
  version: ADMINFORTH_VERSION,
@@ -643,10 +653,6 @@ class AdminForth {
643
653
  })
644
654
  );
645
655
 
646
- data.data.forEach((item) => {
647
- item._label = resource.itemLabel(item);
648
- })
649
-
650
656
  for (const hook of getFunctionList(resource.hooks?.[source]?.afterDatasourceResponse)) {
651
657
  const resp = await hook({ resource, response: data.data, adminUser });
652
658
  if (!resp || (!resp.ok && !resp.error)) {
@@ -667,6 +673,10 @@ class AdminForth {
667
673
  })
668
674
  });
669
675
 
676
+ data.data.forEach((item) => {
677
+ item._label = resource.itemLabel(item);
678
+ });
679
+
670
680
  return {
671
681
  ...data,
672
682
  options: resource?.options,
@@ -107,11 +107,13 @@ class CodeInjector {
107
107
  path: '${item.path}',
108
108
  name: '${item.path}',
109
109
  component: () => import('${item.component}'),
110
+ meta: { title: '${item?.meta?.title}'}
110
111
  },\n`} else {
111
112
  routes += `{
112
113
  path: '${item.path}',
113
114
  name: '${item.path}',
114
115
  component: ${getComponentNameFromPath(item.component)},
116
+ meta: { title: '${item?.meta?.title}'}
115
117
  },\n`
116
118
  const componentName = `${getComponentNameFromPath(item.component)}`;
117
119
  routerComponents += `import ${componentName} from '${item.component}';\n`;
@@ -122,6 +124,7 @@ class CodeInjector {
122
124
  path: '${item.path}',
123
125
  name: '${item.path}',
124
126
  component: ${getComponentNameFromPath(item.component)},
127
+ meta: { title: '${item?.meta?.title}'}
125
128
  },\n`
126
129
  const componentName = `${getComponentNameFromPath(item.component)}`;
127
130
  routerComponents += `import ${componentName} from '${item.component}';\n`;}
@@ -129,7 +132,8 @@ class CodeInjector {
129
132
  routes += `{
130
133
  path: '${item.path}',
131
134
  name: '${item.path}',
132
- component: () => import('${item.component}'),
135
+ component: () => import('${item.component}'),}
136
+ meta: { title: '${item?.meta?.title}'
133
137
  },\n`
134
138
 
135
139
  }
@@ -239,7 +243,7 @@ class CodeInjector {
239
243
  this.allComponentNames[filePath] = componentName;
240
244
  });
241
245
 
242
- console.log('🔧 Injecting code into Vue sources...', this.allComponentNames);
246
+ // console.log('🔧 Injecting code into Vue sources...', this.allComponentNames);
243
247
 
244
248
  let customComponentsImports = '';
245
249
  for (const [targetPath, component] of Object.entries(this.allComponentNames)) {
@@ -299,7 +303,7 @@ class CodeInjector {
299
303
  // inject title to index.html
300
304
  const indexHtmlPath = path.join(CodeInjector.SPA_TMP_PATH, 'index.html');
301
305
  let indexHtmlContent = await fs.promises.readFile(indexHtmlPath, 'utf-8');
302
- indexHtmlContent = indexHtmlContent.replace('/* IMPORTANT:ADMINFORTH TITLE */', `${this.adminforth.config.title || 'AdminForth'}`);
306
+ indexHtmlContent = indexHtmlContent.replace('/* IMPORTANT:ADMINFORTH TITLE */', `${this.adminforth.config.customization.title || 'AdminForth'}`);
303
307
  await fs.promises.writeFile(indexHtmlPath, indexHtmlContent);
304
308
 
305
309
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "adminforth",
3
- "version": "1.0.67",
3
+ "version": "1.0.69",
4
4
  "description": "OpenSource Vue3 powered forth-generation admin panel",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -135,7 +135,7 @@ class ExpressServer {
135
135
  fileExists = false;
136
136
  }
137
137
  if (!fileExists) {
138
- res.status(500).send(respondNoServer(`${this.adminforth.config.brandName} is still warming up`, 'Please wait a moment...'));
138
+ res.status(500).send(respondNoServer(`${this.adminforth.config.customization.brandName} is still warming up`, 'Please wait a moment...'));
139
139
  return;
140
140
  }
141
141
  res.sendFile(fullPath, {
package/spa/index.html CHANGED
@@ -2,7 +2,7 @@
2
2
  <html lang="en">
3
3
  <head>
4
4
  <meta charset="UTF-8">
5
- <link rel="icon" href="/favicon.ico">
5
+ <link rel="icon" href="/favicon.png">
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
7
  <title>/* IMPORTANT:ADMINFORTH TITLE */</title>
8
8
  <!--
@@ -36,7 +36,7 @@
36
36
  "sass": "^1.77.2",
37
37
  "tailwindcss": "^3.4.3",
38
38
  "typescript": "~5.4.0",
39
- "vite": "^5.2.12",
39
+ "vite": "^5.2.13",
40
40
  "vue-tsc": "^2.0.11"
41
41
  }
42
42
  },
@@ -4033,9 +4033,9 @@
4033
4033
  "dev": true
4034
4034
  },
4035
4035
  "node_modules/vite": {
4036
- "version": "5.2.12",
4037
- "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.12.tgz",
4038
- "integrity": "sha512-/gC8GxzxMK5ntBwb48pR32GGhENnjtY30G4A0jemunsBkiEZFw60s8InGpN8gkhHEkjnRK1aSAxeQgwvFhUHAA==",
4036
+ "version": "5.2.13",
4037
+ "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.13.tgz",
4038
+ "integrity": "sha512-SSq1noJfY9pR3I1TUENL3rQYDQCFqgD+lM6fTRAM8Nv6Lsg5hDLaXkjETVeBt+7vZBCMoibD+6IWnT2mJ+Zb/A==",
4039
4039
  "dev": true,
4040
4040
  "dependencies": {
4041
4041
  "esbuild": "^0.20.1",
package/spa/package.json CHANGED
@@ -40,7 +40,7 @@
40
40
  "sass": "^1.77.2",
41
41
  "tailwindcss": "^3.4.3",
42
42
  "typescript": "~5.4.0",
43
- "vite": "^5.2.12",
43
+ "vite": "^5.2.13",
44
44
  "vue-tsc": "^2.0.11"
45
45
  }
46
46
  }
Binary file
package/spa/src/App.vue CHANGED
@@ -66,11 +66,11 @@
66
66
  <div class="h-full px-3 pb-4 overflow-y-auto bg-nav-menu-bg dark:bg-gray-800">
67
67
 
68
68
  <div class="flex ms-2 md:me-24 m-4 ">
69
- <img src="https://flowbite.com/docs/images/logo.svg" class="h-8 me-3" alt="FlowBite Logo" />
70
- <span class="self-center text-header-text-size font-semibold sm:text-header-text-size whitespace-nowrap dark:text-header-text text-header-logo-color">
71
- {{ coreStore.config?.brandName }}
72
- </span>
73
- </div>
69
+ <img :src="loadFile(coreStore.config?.brandLogo || '@/assets/logo.svg')" :alt="`${ coreStore.config?.brandName } Logo`" class="h-8 me-3" />
70
+ <span class="self-center text-header-text-size font-semibold sm:text-header-text-size whitespace-nowrap dark:text-header-text text-header-logo-color">
71
+ {{ coreStore.config?.brandName }}
72
+ </span>
73
+ </div>
74
74
  <ul class="space-y-2 font-medium">
75
75
  <template v-for="(item, i) in coreStore.menu" :key="`menu-${i}`">
76
76
  <div v-if="item.type === 'divider'" class="border-t border-nav-menu-devider dark:border-gray-700"></div>
@@ -151,6 +151,27 @@ import { createHead } from 'unhead'
151
151
  const coreStore = useCoreStore();
152
152
 
153
153
 
154
+ const splitAtLast = (str: string, separator: string) => {
155
+ const index = str.lastIndexOf(separator);
156
+ return [str.slice(0, index), str.slice(index + 1)];
157
+ }
158
+
159
+ const loadFile = (file: string) => {
160
+ let path;
161
+ let baseUrl = '';
162
+ if (file.startsWith('@/')) {
163
+ path = file.replace('@/', '');
164
+ baseUrl = new URL(`./${path}`, import.meta.url).href;
165
+ } else if (file.startsWith('@@/')) {
166
+ path = file.replace('@@/', '');
167
+ baseUrl = new URL(`./custom/${path}`, import.meta.url).href;
168
+ } else {
169
+ baseUrl = new URL(`./${file}`, import.meta.url).href;
170
+ }
171
+ return baseUrl;
172
+ }
173
+
174
+
154
175
  createHead()
155
176
 
156
177
 
@@ -159,6 +180,13 @@ const sideBarOpen = ref(false);
159
180
 
160
181
 
161
182
  const route = useRoute();
183
+ watch(route, () => {
184
+ title.value = `${coreStore.config?.title || coreStore.config?.brandName || 'Adminforth'} | ${ Object.values(route.params)[0] || route.meta.title || ' '}`;
185
+ useHead({
186
+ title: title.value,
187
+ })
188
+ });
189
+
162
190
  const router = useRouter();
163
191
  const title = ref('');
164
192
  //create a ref to store the opened menu items with ts type;
@@ -207,17 +235,7 @@ onMounted(async () => {
207
235
  }, 100);
208
236
  await coreStore.fetchMenuAndResource();
209
237
  loginRedirectCheckIsReady.value = true;
210
- title.value = coreStore.config?.title || 'AdminForth';
211
- // useHead({
212
- // title: title.value,
213
- // link: [
214
- // {
215
- // rel: 'icon',
216
- // href: coreStore.config.favicon || '/favicon.ico',
217
- // },
218
- // ],
219
-
220
- // })
238
+
221
239
  coreStore.menu.forEach((item, i) => {
222
240
  if (item.open) {
223
241
  opened.value.push(i);
@@ -1 +1,19 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 261.76 226.69"><path d="M161.096.001l-30.225 52.351L100.647.001H-.005l130.877 226.688L261.749.001z" fill="#41b883"/><path d="M161.096.001l-30.225 52.351L100.647.001H52.346l78.526 136.01L209.398.001z" fill="#34495e"/></svg>
1
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <g clip-path="url(#clip0_1_8)">
3
+ <path d="M8.034 6.006V13H6.097V12.194C5.59433 12.8007 4.86633 13.104 3.913 13.104C3.25433 13.104 2.65633 12.9567 2.119 12.662C1.59033 12.3673 1.17433 11.947 0.871 11.401C0.567667 10.855 0.416 10.2223 0.416 9.503C0.416 8.78367 0.567667 8.151 0.871 7.605C1.17433 7.059 1.59033 6.63867 2.119 6.344C2.65633 6.04933 3.25433 5.902 3.913 5.902C4.80567 5.902 5.50333 6.18367 6.006 6.747V6.006H8.034ZM4.264 11.44C4.77533 11.44 5.2 11.2667 5.538 10.92C5.876 10.5647 6.045 10.0923 6.045 9.503C6.045 8.91367 5.876 8.44567 5.538 8.099C5.2 7.74367 4.77533 7.566 4.264 7.566C3.744 7.566 3.315 7.74367 2.977 8.099C2.639 8.44567 2.47 8.91367 2.47 9.503C2.47 10.0923 2.639 10.5647 2.977 10.92C3.315 11.2667 3.744 11.44 4.264 11.44Z" fill="url(#paint0_linear_1_8)"/>
4
+ <path d="M13.317 5.538C12.589 5.538 12.0387 5.69833 11.666 6.019C11.2933 6.331 11.107 6.80333 11.107 7.436V7.995H14.851V9.685H11.107V13H9.001V7.449C9.001 6.279 9.365 5.369 10.093 4.719C10.8297 4.069 11.8567 3.744 13.174 3.744C13.694 3.744 14.1837 3.80033 14.643 3.913C15.1023 4.017 15.501 4.173 15.839 4.381L15.189 6.045C14.669 5.707 14.045 5.538 13.317 5.538Z" fill="url(#paint1_linear_1_8)"/>
5
+ </g>
6
+ <defs>
7
+ <linearGradient id="paint0_linear_1_8" x1="1.5" y1="6.93333" x2="7.31883" y2="11.8926" gradientUnits="userSpaceOnUse">
8
+ <stop stop-color="#656E7A"/>
9
+ <stop offset="1" stop-color="#99A6B8"/>
10
+ </linearGradient>
11
+ <linearGradient id="paint1_linear_1_8" x1="12.5" y1="3.73333" x2="9.68642" y2="12.7016" gradientUnits="userSpaceOnUse">
12
+ <stop stop-color="#48C5FF"/>
13
+ <stop offset="1" stop-color="#A1E1FF"/>
14
+ </linearGradient>
15
+ <clipPath id="clip0_1_8">
16
+ <rect width="16" height="16" fill="white"/>
17
+ </clipPath>
18
+ </defs>
19
+ </svg>
@@ -8,7 +8,8 @@ const router = createRouter({
8
8
  {
9
9
  path: '/login',
10
10
  name: 'login',
11
- component: () => import('@/views/LoginView.vue')
11
+ component: () => import('@/views/LoginView.vue'),
12
+ meta: { title: 'login' }
12
13
  },
13
14
  {
14
15
  path: '/resource/:resourceId',
@@ -18,22 +19,28 @@ const router = createRouter({
18
19
  {
19
20
  path: '',
20
21
  component: () => import('@/views/ListView.vue'),
21
- name: 'resource-list'
22
+ name: 'resource-list',
23
+ meta: { title: 'list' }
22
24
  },
23
25
  {
24
26
  path: 'show/:primaryKey',
25
27
  component: () => import('@/views/ShowView.vue'),
26
- name: 'resource-show'
28
+ name: 'resource-show',
29
+ meta: { title: 'show' }
30
+
27
31
  },
28
32
  {
29
33
  path: 'edit/:primaryKey',
30
34
  component: () => import('@/views/EditView.vue'),
31
- name: 'resource-edit'
35
+ name: 'resource-edit',
36
+ meta: { title: 'edit' }
32
37
  },
33
38
  {
34
39
  path: 'create',
35
40
  component: () => import('@/views/CreateView.vue'),
36
- name: 'resource-create'
41
+ name: 'resource-create',
42
+ meta: { title: 'create' }
43
+
37
44
  },
38
45
  ]
39
46
  },
@@ -66,7 +66,7 @@ export const useCoreStore = defineStore('core', () => {
66
66
  resourceOptions.value = respData.options;
67
67
  }
68
68
 
69
- async function fetchColumns({ resourceId }) {
69
+ async function fetchResourceFull({ resourceId }) {
70
70
  if (resourceColumnsId.value === resourceId && resourceColumns.value) {
71
71
  // already fetched
72
72
  return;
@@ -86,6 +86,7 @@ export const useCoreStore = defineStore('core', () => {
86
86
  resourceColumnsError.value = res.error;
87
87
  } else {
88
88
  resourceColumns.value = res.resource.columns;
89
+ resourceById.value[resourceId] = res.resource;
89
90
  }
90
91
  }
91
92
 
@@ -126,7 +127,7 @@ export const useCoreStore = defineStore('core', () => {
126
127
  fetchRecord,
127
128
  record,
128
129
  resourceColumns,
129
- fetchColumns,
130
+ fetchResourceFull,
130
131
  resourceColumnsError,
131
132
  flowBitIsInitialised,
132
133
  resourceOptions,
@@ -71,7 +71,7 @@ async function onUpdateRecord(newRecord) {
71
71
 
72
72
  onMounted(async () => {
73
73
  loading.value = true;
74
- await coreStore.fetchColumns({
74
+ await coreStore.fetchResourceFull({
75
75
  resourceId: route.params.resourceId
76
76
  });
77
77
  createComponentsPerColumn = coreStore.resourceColumns.reduce((acc, column) => {
@@ -81,7 +81,7 @@ const editableRecord = computed(() => {
81
81
  onMounted(async () => {
82
82
  loading.value = true;
83
83
 
84
- await coreStore.fetchColumns({
84
+ await coreStore.fetchResourceFull({
85
85
  resourceId: route.params.resourceId
86
86
  });
87
87
  await coreStore.fetchRecord({