adminforth 1.0.51 → 1.0.53

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.
@@ -83,6 +83,10 @@ class PostgresConnector {
83
83
  field.type = AdminForthTypes.BOOLEAN;
84
84
  field._underlineType = 'bool';
85
85
 
86
+ } else if (baseType == 'uuid') {
87
+ field.type = AdminForthTypes.STRING;
88
+ field._underlineType = 'uuid';
89
+
86
90
  } else if (baseType.includes('character varying')) {
87
91
  field.type = AdminForthTypes.STRING;
88
92
  field._underlineType = 'varchar';
@@ -183,6 +187,7 @@ class PostgresConnector {
183
187
  let totalCounter = 1;
184
188
  const where = filters.length ? `WHERE ${filters.map((f, i) => {
185
189
  let placeholder = '$'+(totalCounter);
190
+ const fieldData = resource.dataSourceColumns.find((col) => col.name == f.field);
186
191
  let field = f.field;
187
192
  let operator = this.OperatorsMap[f.operator];
188
193
  if (f.operator == AdminForthFilterOperators.IN || f.operator == AdminForthFilterOperators.NIN) {
@@ -191,7 +196,13 @@ class PostgresConnector {
191
196
  } else {
192
197
  totalCounter += 1;
193
198
  }
194
- return `"${field}" ${operator} ${placeholder}`
199
+
200
+ if (fieldData._underlineType == 'uuid') {
201
+ field = `cast("${field}" as text)`
202
+ } else {
203
+ field = `"${field}"`
204
+ }
205
+ return `${field} ${operator} ${placeholder}`;
195
206
  }).join(' AND ')}` : '';
196
207
 
197
208
  const filterValues = [];
@@ -84,6 +84,10 @@ class PostgresConnector {
84
84
  field.type = AdminForthTypes.BOOLEAN;
85
85
  field._underlineType = 'bool';
86
86
  }
87
+ else if (baseType == 'uuid') {
88
+ field.type = AdminForthTypes.STRING;
89
+ field._underlineType = 'uuid';
90
+ }
87
91
  else if (baseType.includes('character varying')) {
88
92
  field.type = AdminForthTypes.STRING;
89
93
  field._underlineType = 'varchar';
@@ -185,6 +189,7 @@ class PostgresConnector {
185
189
  let totalCounter = 1;
186
190
  const where = filters.length ? `WHERE ${filters.map((f, i) => {
187
191
  let placeholder = '$' + (totalCounter);
192
+ const fieldData = resource.dataSourceColumns.find((col) => col.name == f.field);
188
193
  let field = f.field;
189
194
  let operator = this.OperatorsMap[f.operator];
190
195
  if (f.operator == AdminForthFilterOperators.IN || f.operator == AdminForthFilterOperators.NIN) {
@@ -194,7 +199,13 @@ class PostgresConnector {
194
199
  else {
195
200
  totalCounter += 1;
196
201
  }
197
- return `"${field}" ${operator} ${placeholder}`;
202
+ if (fieldData._underlineType == 'uuid') {
203
+ field = `cast("${field}" as text)`;
204
+ }
205
+ else {
206
+ field = `"${field}"`;
207
+ }
208
+ return `${field} ${operator} ${placeholder}`;
198
209
  }).join(' AND ')}` : '';
199
210
  const filterValues = [];
200
211
  filters.length ? filters.forEach((f) => {
package/dist/index.js CHANGED
@@ -548,8 +548,8 @@ class AdminForth {
548
548
  data.data.forEach((item) => {
549
549
  item._label = resource.itemLabel(item);
550
550
  });
551
- if ((_p = (_o = resource.hooks) === null || _o === void 0 ? void 0 : _o[source]) === null || _p === void 0 ? void 0 : _p.afterDatasourceRequest) {
552
- const resp = yield ((_r = (_q = resource.hooks) === null || _q === void 0 ? void 0 : _q[source]) === null || _r === void 0 ? void 0 : _r.afterDatasourceRequest({ resource, response: data.data, adminUser }));
551
+ if ((_p = (_o = resource.hooks) === null || _o === void 0 ? void 0 : _o[source]) === null || _p === void 0 ? void 0 : _p.afterDatasourceResponse) {
552
+ const resp = yield ((_r = (_q = resource.hooks) === null || _q === void 0 ? void 0 : _q[source]) === null || _r === void 0 ? void 0 : _r.afterDatasourceResponse({ resource, response: data.data, adminUser }));
553
553
  if (!resp || (!resp.ok && !resp.error)) {
554
554
  throw new Error(`Hook must return object with {ok: true} or { error: 'Error' } `);
555
555
  }
@@ -565,7 +565,6 @@ class AdminForth {
565
565
  }
566
566
  });
567
567
  });
568
- console.log('data', data);
569
568
  return Object.assign(Object.assign({}, data), { options: resource === null || resource === void 0 ? void 0 : resource.options });
570
569
  }),
571
570
  });
@@ -72,7 +72,7 @@ class CodeInjector {
72
72
  }
73
73
  prepareSources(_a) {
74
74
  return __awaiter(this, arguments, void 0, function* ({ filesUpdated, verbose = false }) {
75
- var _b, _c, _d, _e, _f, _g, _h;
75
+ var _b, _c, _d, _e, _f, _g, _h, _j, _k;
76
76
  // check SPA_TMP_PATH exists and create if not
77
77
  try {
78
78
  yield fs.promises.access(CodeInjector.SPA_TMP_PATH, fs.constants.F_OK);
@@ -162,6 +162,9 @@ class CodeInjector {
162
162
  // if file - return, if dir - go recursively
163
163
  const customComponents = yield fs.promises.readdir(customComponentsDir);
164
164
  for (const filePath of customComponents) {
165
+ if (!filePath.endsWith('.vue')) {
166
+ continue;
167
+ }
165
168
  fs.stat(`${customComponentsDir}/${filePath}`, (err, data) => {
166
169
  if (err) {
167
170
  console.log(err);
@@ -187,6 +190,9 @@ class CodeInjector {
187
190
  if (customComponentsDir) {
188
191
  const customComponents = yield fs.promises.readdir(customComponentsDir);
189
192
  for (const filePath of customComponents) {
193
+ if (!filePath.endsWith('.vue')) {
194
+ continue;
195
+ }
190
196
  fs.stat(`${customComponentsDir}/${filePath}`, (err, data) => {
191
197
  if (err) {
192
198
  console.log('Custom components importing error: ', err);
@@ -213,13 +219,22 @@ class CodeInjector {
213
219
  appVueContent = appVueContent.replace('/* IMPORTANT:ADMINFORTH CUSTOM USES */', 'addCustomUses(app);');
214
220
  }
215
221
  yield fs.promises.writeFile(appVuePath, appVueContent);
222
+ // generate tailwind extend styles
223
+ if ((_f = this.adminforth.config) === null || _f === void 0 ? void 0 : _f.styles) {
224
+ const tailwindStyles = (_g = this.adminforth.config) === null || _g === void 0 ? void 0 : _g.styles;
225
+ const stylesText = JSON.stringify(tailwindStyles, null, 2).slice(1, -1);
226
+ let tailwindConfigPath = path.join(CodeInjector.SPA_TMP_PATH, 'tailwind.config.js');
227
+ let tailwindConfigContent = yield fs.promises.readFile(tailwindConfigPath, 'utf-8');
228
+ tailwindConfigContent = tailwindConfigContent.replace('/* IMPORTANT:ADMINFORTH TAILWIND STYLES */', stylesText);
229
+ yield fs.promises.writeFile(tailwindConfigPath, tailwindConfigContent);
230
+ }
216
231
  /* generate custom rotes */
217
232
  const homepageMenuItem = this.adminforth.config.menu.find((mi) => mi.homepage);
218
233
  let childrenHomePageMenuItem = this.adminforth.config.menu.find((mi) => { var _a; return mi.children && ((_a = mi.children) === null || _a === void 0 ? void 0 : _a.find((mi) => mi.homepage)); });
219
- let childrenHomepage = (_f = childrenHomePageMenuItem === null || childrenHomePageMenuItem === void 0 ? void 0 : childrenHomePageMenuItem.children) === null || _f === void 0 ? void 0 : _f.find((mi) => mi.homepage);
234
+ let childrenHomepage = (_h = childrenHomePageMenuItem === null || childrenHomePageMenuItem === void 0 ? void 0 : childrenHomePageMenuItem.children) === null || _h === void 0 ? void 0 : _h.find((mi) => mi.homepage);
220
235
  let homePagePath = (homepageMenuItem === null || homepageMenuItem === void 0 ? void 0 : homepageMenuItem.path) || `/resource/${childrenHomepage === null || childrenHomepage === void 0 ? void 0 : childrenHomepage.resourceId}`;
221
236
  if (!homePagePath) {
222
- homePagePath = ((_g = this.adminforth.config.menu.filter((mi) => mi.path)[0]) === null || _g === void 0 ? void 0 : _g.path) || `/resource/${(_h = this.adminforth.config.menu.filter((mi) => mi.children)[0]) === null || _h === void 0 ? void 0 : _h.resourceId}`;
237
+ homePagePath = ((_j = this.adminforth.config.menu.filter((mi) => mi.path)[0]) === null || _j === void 0 ? void 0 : _j.path) || `/resource/${(_k = this.adminforth.config.menu.filter((mi) => mi.children)[0]) === null || _k === void 0 ? void 0 : _k.resourceId}`;
223
238
  }
224
239
  routes += `{
225
240
  path: '/',
@@ -16,8 +16,8 @@
16
16
  </script> -->
17
17
 
18
18
  </head>
19
- <body>
20
- <div id="app" class="min-h-screen dark:bg-gray-800"></div>
19
+ <body class=" ">
20
+ <div id="app" class="min-h-screen bg-html-bg dark:bg-gray-800"></div>
21
21
  <script type="module" src="/src/main.ts"></script>
22
22
  </body>
23
23
  </html>
@@ -9,6 +9,7 @@
9
9
  "version": "0.0.0",
10
10
  "dependencies": {
11
11
  "@iconify-prerendered/vue-flowbite": "^0.23.1714023977",
12
+ "@unhead/vue": "^1.9.12",
12
13
  "@vueuse/core": "^10.10.0",
13
14
  "dayjs": "^1.11.11",
14
15
  "debounce": "^2.1.0",
@@ -1185,6 +1186,23 @@
1185
1186
  "url": "https://github.com/sponsors/harlan-zw"
1186
1187
  }
1187
1188
  },
1189
+ "node_modules/@unhead/vue": {
1190
+ "version": "1.9.12",
1191
+ "resolved": "https://registry.npmjs.org/@unhead/vue/-/vue-1.9.12.tgz",
1192
+ "integrity": "sha512-keE4EuswgzCqVU7zmZprU+ToMvNWc3s8NoLreH5AtJd2u0FgBygD8sxRVyEnZw1KwFYOJ2C7yD2TChSKZioPGQ==",
1193
+ "dependencies": {
1194
+ "@unhead/schema": "1.9.12",
1195
+ "@unhead/shared": "1.9.12",
1196
+ "hookable": "^5.5.3",
1197
+ "unhead": "1.9.12"
1198
+ },
1199
+ "funding": {
1200
+ "url": "https://github.com/sponsors/harlan-zw"
1201
+ },
1202
+ "peerDependencies": {
1203
+ "vue": ">=2.7 || >=3"
1204
+ }
1205
+ },
1188
1206
  "node_modules/@vitejs/plugin-vue": {
1189
1207
  "version": "5.0.4",
1190
1208
  "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.0.4.tgz",
@@ -13,6 +13,7 @@
13
13
  },
14
14
  "dependencies": {
15
15
  "@iconify-prerendered/vue-flowbite": "^0.23.1714023977",
16
+ "@unhead/vue": "^1.9.12",
16
17
  "@vueuse/core": "^10.10.0",
17
18
  "dayjs": "^1.11.11",
18
19
  "debounce": "^2.1.0",
@@ -1,89 +1,84 @@
1
1
  <template>
2
2
  <nav
3
3
  v-if="loggedIn"
4
-
5
- class="fixed h-14 top-0 z-50 w-full bg-white border-b border-gray-200 dark:bg-gray-800 dark:border-gray-700">
4
+ class="fixed h-14 top-0 z-40 w-full border-b shadow-sm bg-header-bg shadow-header-shadow dark:bg-header-bg-hover dark:border-nav-menu-devider">
6
5
  <div class="px-3 py-3 lg:px-5 lg:pl-3">
7
6
  <div class="flex items-center justify-between">
8
7
  <div class="flex items-center justify-start rtl:justify-end">
9
- <button data-drawer-target="logo-sidebar" data-drawer-toggle="logo-sidebar" aria-controls="logo-sidebar" type="button" class="inline-flex items-center p-2 text-sm text-gray-500 rounded-lg sm:hidden hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-gray-200 dark:text-gray-400 dark:hover:bg-gray-700 dark:focus:ring-gray-600">
8
+ <button data-drawer-target="logo-sidebar" data-drawer-toggle="logo-sidebar" aria-controls="logo-sidebar" type="button" class="inline-flex items-center p-2 text-sm rounded-lg sm:hidden hover:bg-nav-menu-bg-hover focus:outline-none focus:ring-2 focus:ring-nav-menu-devider dark:text-nav-menu-icons dark:hover:bg-nav-menu-bg-hover dark:focus:ring-nav-menu-devider">
10
9
  <span class="sr-only">Open sidebar</span>
11
10
  <svg class="w-6 h-6" aria-hidden="true" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
12
11
  <path clip-rule="evenodd" fill-rule="evenodd" d="M2 4.75A.75.75 0 012.75 4h14.5a.75.75 0 010 1.5H2.75A.75.75 0 012 4.75zm0 10.5a.75.75 0 01.75-.75h7.5a.75.75 0 010 1.5h-7.5a.75.75 0 01-.75-.75zM2 10a.75.75 0 01.75-.75h14.5a.75.75 0 010 1.5H2.75A.75.75 0 012 10z"></path>
13
12
  </svg>
14
13
  </button>
15
- <div class="flex ms-2 md:me-24">
16
- <img src="https://flowbite.com/docs/images/logo.svg" class="h-8 me-3" alt="FlowBite Logo" />
17
- <span class="self-center text-xl font-semibold sm:text-2xl whitespace-nowrap dark:text-white">
18
- {{ coreStore.config?.brandName }}
19
- </span>
20
- </div>
14
+
21
15
  </div>
22
16
  <div class="flex items-center">
23
-
24
-
25
- <div class="flex items-center ms-3">
26
- <div>
27
- <button type="button" class="flex text-sm bg-gray-100 rounded-full focus:ring-4 focus:ring-gray-300 dark:focus:ring-gray-600 dark:bg-gray-700" aria-expanded="false" data-dropdown-toggle="dropdown-user">
28
- <span class="sr-only">Open user menu</span>
29
- <svg class="w-8 h-8 text-gray-400 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 24 24">
30
- <path fill-rule="evenodd" d="M12 20a7.966 7.966 0 0 1-5.002-1.756l.002.001v-.683c0-1.794 1.492-3.25 3.333-3.25h3.334c1.84 0 3.333 1.456 3.333 3.25v.683A7.966 7.966 0 0 1 12 20ZM2 12C2 6.477 6.477 2 12 2s10 4.477 10 10c0 5.5-4.44 9.963-9.932 10h-.138C6.438 21.962 2 17.5 2 12Zm10-5c-1.84 0-3.333 1.455-3.333 3.25S10.159 13.5 12 13.5c1.84 0 3.333-1.455 3.333-3.25S13.841 7 12 7Z" clip-rule="evenodd"/>
31
- </svg>
32
-
33
- </button>
17
+ <div class="flex items-center ms-3 ">
18
+ <div>
19
+ <button type="button" class="flex text-sm bg-header-bg-hover rounded-full focus:ring-4 focus:ring-nav-menu-devider dark:focus:ring-nav-menu-devider dark:bg-header-bg-hover" aria-expanded="false" data-dropdown-toggle="dropdown-user">
20
+ <span class="sr-only">Open user menu</span>
21
+ <svg class="w-8 h-8 text-nav-menu-icons dark:text-header-icons" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 24 24">
22
+ <path fill-rule="evenodd" d="M12 20a7.966 7.966 0 0 1-5.002-1.756l.002.001v-.683c0-1.794 1.492-3.25 3.333-3.25h3.334c1.84 0 3.333 1.456 3.333 3.25v.683A7.966 7.966 0 0 1 12 20ZM2 12C2 6.477 6.477 2 12 2s10 4.477 10 10c0 5.5-4.44 9.963-9.932 10h-.138C6.438 21.962 2 17.5 2 12Zm10-5c-1.84 0-3.333 1.455-3.333 3.25S10.159 13.5 12 13.5c1.84 0 3.333-1.455 3.333-3.25S13.841 7 12 7Z" clip-rule="evenodd"/>
23
+ </svg>
24
+ </button>
25
+ </div>
26
+ <div class="z-50 hidden my-4 text-base list-none bg-white divide-y divide-gray-100 rounded shadow dark:shadow-black dark:bg-nav-menu-bg dark:divide-nav-menu-devider dark:shadow-black" id="dropdown-user">
27
+ <div class="px-4 py-3" role="none">
28
+ <p class="text-sm text-gray-900 dark:text-header-text" role="none" v-if="coreStore.userFullname">
29
+ {{ coreStore.userFullname }}
30
+ </p>
31
+ <p class="text-sm font-medium text-gray-900 truncate dark:text-nav-menu-text" role="none">
32
+ {{ coreStore.username }}
33
+ </p>
34
34
  </div>
35
- <div class="z-50 hidden my-4 text-base list-none bg-white divide-y divide-gray-100 rounded shadow dark:shadow-black dark:bg-gray-700 dark:divide-gray-600 dark:shadow-black" id="dropdown-user">
36
- <div class="px-4 py-3" role="none">
37
- <p class="text-sm text-gray-900 dark:text-white" role="none" v-if="coreStore.userFullname">
38
- {{ coreStore.userFullname }}
39
- </p>
40
- <p class="text-sm font-medium text-gray-900 truncate dark:text-gray-300" role="none">
41
- {{ coreStore.username }}
42
- </p>
43
- </div>
44
- <ul class="py-1" role="none">
45
- <li >
46
- <span @click="toggleTheme" class=" cursor-pointer flex items-center gap-1 block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-600 dark:hover:text-white" role="menuitem">
47
- {{ theme === 'dark' ? 'Light' : 'Dark' }}
48
- <IconMoonSolid class="w-5 h-5 text-blue-300
49
- " v-if="theme !== 'dark'"/>
50
- <IconSunSolid class="w-5 h-5 text-yellow-300
51
- " v-else/>
52
- mode
53
- </span>
54
- </li>
55
-
56
- <li>
57
- <button @click="logout" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-600 dark:hover:text-white" role="menuitem">Sign out</button>
35
+ <ul class="py-1" role="none">
36
+ <li>
37
+ <span @click="toggleTheme" class="cursor-pointer flex items-center gap-1 block px-4 py-2 text-sm text-black hover:bg-html-bg dark:text-nav-menu-text-hover dark:hover:bg-nav-menu-hover dark:hover:text-nav-menu-text-active" role="menuitem">
38
+ {{ theme === 'dark' ? 'Light' : 'Dark' }}
39
+ <IconMoonSolid class="w-5 h-5 text-blue-300" v-if="theme !== 'dark'" />
40
+ <IconSunSolid class="w-5 h-5 text-yellow-300" v-else />
41
+ mode
42
+ </span>
58
43
  </li>
59
- </ul>
60
- </div>
44
+ <li>
45
+ <button @click="logout" class="cursor-pointer flex items-center gap-1 block px-4 py-2 text-sm text-black hover:bg-html-bg dark:text-nav-menu-text-hover dark:hover:bg-nav-menu-hover dark:hover:text-nav-menu-text-active w-full" role="menuitem">Sign out</button>
46
+ </li>
47
+ </ul>
61
48
  </div>
62
49
  </div>
50
+ </div>
63
51
  </div>
64
52
  </div>
65
- </nav>
53
+ </nav>
54
+
66
55
 
67
56
 
68
57
  <aside
69
58
  v-if="loggedIn"
70
59
 
71
- id="logo-sidebar" class="fixed top-0 left-0 z-10 w-64 h-screen pt-20 transition-transform -translate-x-full bg-white border-r border-gray-200 sm:translate-x-0 dark:bg-gray-800 dark:border-gray-700" aria-label="Sidebar">
72
- <div class="h-full px-3 pb-4 overflow-y-auto bg-white dark:bg-gray-800">
60
+ id="logo-sidebar" class="fixed border-none top-0 left-0 z-50 w-64 h-screen transition-transform -translate-x-full bg-nav-menu-bg border-r border- sm:translate-x-0 dark:bg-gray-800 dark:border-gray-700" aria-label="Sidebar">
61
+ <div class="h-full px-3 pb-4 overflow-y-auto bg-nav-menu-bg dark:bg-gray-800">
62
+ <div class="flex ms-2 md:me-24 m-4 ">
63
+ <img src="https://flowbite.com/docs/images/logo.svg" class="h-8 me-3" alt="FlowBite Logo" />
64
+ <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">
65
+ {{ coreStore.config?.brandName }}
66
+ </span>
67
+ </div>
73
68
  <ul class="space-y-2 font-medium">
74
69
  <template v-for="(item, i) in coreStore.menu" :key="`menu-${i}`">
75
- <div v-if="item.type === 'divider'" class="border-t border-gray-200 dark:border-gray-700"></div>
70
+ <div v-if="item.type === 'divider'" class="border-t border-nav-menu-devider dark:border-gray-700"></div>
76
71
  <div v-else-if="item.type === 'gap'" class="flex items-center justify-center h-8"></div>
77
72
  <div v-else-if="item.type === 'heading'" class="flex items-center justify-left pl-2 h-8 text-gray-400 dark:text-gray-400
78
73
  ">{{ item.label }}</div>
79
74
 
80
75
 
81
76
  <li v-else-if="item.children">
82
- <button type="button" class="flex items-center w-full p-2 text-base text-gray-900 transition duration-75 rounded-lg group hover:bg-gray-100 dark:text-white dark:hover:bg-gray-700"
77
+ <button type="button" class="flex items-center w-full p-2 text-base text-nav-menu-text transition duration-75 group hover:bg-nav-menu-bg-hover dark:text-white dark:hover:bg-gray-700"
83
78
  :aria-controls="`dropdown-example${i}`"
84
79
  :data-collapse-toggle="`dropdown-example${i}`"
85
80
  >
86
- <component v-if="item.icon" :is="getIcon(item.icon)" class="w-5 h-5 text-gray-500 transition duration-75 dark:text-gray-400 group-hover:text-gray-900 dark:group-hover:text-white" ></component>
81
+ <component v-if="item.icon" :is="getIcon(item.icon)" class="w-5 h-5 text-gray-500 transition duration-75 dark:text-gray-400 dark:group-hover:text-white" ></component>
87
82
 
88
83
  <span class="flex-1 ms-3 text-left rtl:text-right whitespace-nowrap">{{ item.label }}</span>
89
84
  <svg class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 10 6">
@@ -135,6 +130,7 @@ import { useRoute, useRouter } from 'vue-router';
135
130
  import { getIcon } from '@/utils';
136
131
  import { useHead } from 'unhead'
137
132
  import { createHead } from 'unhead'
133
+ // import { link } from 'fs';
138
134
  const coreStore = useCoreStore();
139
135
 
140
136
 
@@ -182,6 +178,12 @@ onMounted(async () => {
182
178
  title.value = coreStore.config.title || 'Admin Forth';
183
179
  useHead({
184
180
  title: title.value,
181
+ link: [
182
+ {
183
+ rel: 'icon',
184
+ href: coreStore.config.favicon || '/favicon.ico',
185
+ },
186
+ ],
185
187
 
186
188
  })
187
189
  })
@@ -9,7 +9,8 @@
9
9
  :placeholder="selectedItems.length ? '' : 'Select...'"
10
10
  />
11
11
  <div class="absolute inset-y-0 left-2 flex items-center pr-2 flex-wrap">
12
- <div v-for="item in selectedItems" :key="item.name" class="bg-blue-100 text-blue-800 text-xs font-medium me-2 px-2.5 py-0.5 rounded dark:bg-blue-900 dark:text-blue-300">
12
+ {{ }}
13
+ <div v-for="item in selectedItems" :key="item?.name" class="bg-blue-100 text-blue-800 text-xs font-medium me-2 px-2.5 py-0.5 rounded dark:bg-blue-900 dark:text-blue-300">
13
14
  <span>{{ item.label }}</span>
14
15
  <button
15
16
  type="button"
@@ -92,7 +92,7 @@
92
92
  </div>
93
93
  </div>
94
94
 
95
- <div v-if="show" drawer-backdrop="" class="bg-gray-900/50 dark:bg-gray-900/80 fixed inset-0 z-30"
95
+ <div v-if="show" drawer-backdrop="" class="bg-gray-900/50 dark:bg-gray-900/80 fixed inset-0 z-50"
96
96
  @click="$emit('hide')">
97
97
  </div>
98
98
  </template>
@@ -1,11 +1,11 @@
1
1
  <template>
2
2
  <RouterLink
3
3
  :to="{name: item.resourceId ? 'resource-list' : item.path, params: item.resourceId ? { resourceId: item.resourceId }: {}}"
4
- class="flex items-center py-2 text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-600 dark:hover:text-white" role="menuitem"
4
+ class="flex items-center py-2 text-nav-menu-text hover:bg-nav-menu-bg-hover active:bg-nav-menu-bg-active" role="menuitem"
5
5
  :class="{
6
6
  'px-4': isChild,
7
7
  'px-2': !isChild,
8
- 'bg-blue-100 dark:bg-gray-700': item.resourceId ?
8
+ 'bg-nav-menu-bg-active dark:bg-gray-700': item.resourceId ?
9
9
  ($route.params.resourceId === item.resourceId && $route.name === 'resource-list') :
10
10
  ($route.name === item.path)
11
11
  }"
@@ -2,11 +2,11 @@
2
2
  <div>
3
3
 
4
4
  <div
5
- class="relative shadow-md sm:rounded-lg dark:shadow-2xl dark:shadow-black"
5
+ class="relative shadow-resourse-form-shadow sm:rounded-lg dark:shadow-2xl dark:shadow-black"
6
6
  >
7
7
  <form autocomplete="off" @submit.prevent>
8
8
  <table class="w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400">
9
- <thead class="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400">
9
+ <thead class="text-xs text-gray-700 uppercase bg-form-view-heading dark:bg-gray-700 dark:text-gray-400">
10
10
  <tr>
11
11
  <th scope="col" class="px-6 py-3">
12
12
  Field
@@ -18,7 +18,7 @@
18
18
  </thead>
19
19
  <tbody>
20
20
  <tr v-for="column, i in editableColumns" :key="column.name"
21
- class="odd:bg-white odd:dark:bg-gray-900 even:bg-gray-50 even:dark:bg-gray-800 border-b dark:border-gray-700"
21
+ class="bg-form-view-bg dark:bg-gray-800 border-b dark:border-gray-700"
22
22
  >
23
23
  <!-- if column is in customComponentsPerColumn, use this component. If not, use this code -->
24
24
  <template v-if="customComponentsPerColumn[column.name]">
@@ -65,7 +65,7 @@
65
65
  </BreadcrumbsWithButtons>
66
66
 
67
67
  <!-- table -->
68
- <div class="relative overflow-x-auto shadow-md dark:shadow-black sm:rounded-lg dark:s">
68
+ <div class="relative overflow-x-auto shadow-list-table-shadow dark:shadow-black dark:s">
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-gray-700 bg-gray-50 dark:bg-gray-700 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">
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-white border-b dark:bg-gray-800 dark:border-gray-700">
130
+ <tr v-if="!rows" class="bg-list-view-table-bg 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-white border-b dark:bg-gray-800 dark:border-gray-700">
174
+ <tr v-else-if="rows.length === 0" class="bg-list-view-table-bg 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-white border-b dark:bg-gray-800 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-600">
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">
189
189
  <td class="w-4 p-4">
190
190
  <div class="flex items center">
191
191
  <input
@@ -27,10 +27,10 @@
27
27
  </div>
28
28
  <div
29
29
  v-else
30
- class="relative overflow-x-auto shadow-md sm:rounded-lg"
30
+ class="relative overflow-x-auto shadow-resourse-form-shadow "
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-gray-50 dark:bg-gray-700 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">
34
34
  <tr>
35
35
  <th scope="col" class="px-6 py-3">
36
36
  Field
@@ -42,7 +42,7 @@
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="odd:bg-white odd:dark:bg-gray-900 even:bg-gray-50 even:dark:bg-gray-800 border-b dark:border-gray-700"
45
+ class="bg-form-view-bg odd:dark:bg-gray-900 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 }}
@@ -1,9 +1,12 @@
1
1
  /** @type {import('tailwindcss').Config} */
2
2
  export default {
3
- content: ["./src/**/*.{vue, js}", "./node_modules/flowbite/**/*.js"],
3
+ content: ["./src/**/*.{vue, js}","./src/*.{vue, js}", "./index.html", "./node_modules/flowbite/**/*.js"],
4
4
  theme: {
5
- extend: {},
5
+ extend: {
6
+ /* IMPORTANT:ADMINFORTH TAILWIND STYLES */
7
+ }
6
8
  },
9
+
7
10
  darkMode: 'class',
8
11
  plugins: [
9
12
  require('flowbite/plugin'),
package/index.ts CHANGED
@@ -16,6 +16,11 @@ const AVAILABLE_SHOW_IN = ['list', 'edit', 'create', 'filter', 'show'];
16
16
  const DEFAULT_ALLOWED_ACTIONS = {create: true, edit: true, show: true, delete: true};
17
17
 
18
18
 
19
+ type ValidationObject = {
20
+ regex: string,
21
+ message: string,
22
+ }
23
+
19
24
  class AdminForth {
20
25
  static Types = AdminForthTypes;
21
26
 
@@ -624,9 +629,8 @@ class AdminForth {
624
629
  item._label = resource.itemLabel(item);
625
630
  })
626
631
 
627
-
628
- if (resource.hooks?.[source]?.afterDatasourceRequest) {
629
- const resp = await resource.hooks?.[source]?.afterDatasourceRequest({ resource, response: data.data, adminUser });
632
+ if (resource.hooks?.[source]?.afterDatasourceResponse) {
633
+ const resp = await resource.hooks?.[source]?.afterDatasourceResponse({ resource, response: data.data, adminUser });
630
634
  if (!resp || (!resp.ok && !resp.error)) {
631
635
  throw new Error(`Hook must return object with {ok: true} or { error: 'Error' } `);
632
636
  }
@@ -644,9 +648,11 @@ class AdminForth {
644
648
  }
645
649
  })
646
650
  });
647
- console.log('data', data);
648
651
 
649
- return {...data, options: resource?.options };
652
+ return {
653
+ ...data,
654
+ options: resource?.options,
655
+ };
650
656
  },
651
657
  });
652
658
  server.endpoint({
@@ -180,6 +180,9 @@ class CodeInjector {
180
180
  // if file - return, if dir - go recursively
181
181
  const customComponents = await fs.promises.readdir(customComponentsDir);
182
182
  for (const filePath of customComponents) {
183
+ if (!filePath.endsWith('.vue')) {
184
+ continue;
185
+ }
183
186
  fs.stat(`${customComponentsDir}/${filePath}`, (err, data) => {
184
187
  if (err) {
185
188
  console.log(err);
@@ -207,16 +210,19 @@ class CodeInjector {
207
210
  if (customComponentsDir) {
208
211
  const customComponents = await fs.promises.readdir(customComponentsDir);
209
212
  for (const filePath of customComponents) {
210
- fs.stat(`${customComponentsDir}/${filePath}`, (err, data) => {
211
- if (err) {
212
- console.log('Custom components importing error: ', err);
213
- return;
214
- }
215
- if (data.isFile()) {
216
- const componentName = getComponentNameFromPath(filePath);
217
- customComponentsComponents += `app.component('${componentName}', ${componentName});\n`;
218
- }
219
- })
213
+ if (!filePath.endsWith('.vue')) {
214
+ continue;
215
+ }
216
+ fs.stat(`${customComponentsDir}/${filePath}`, (err, data) => {
217
+ if (err) {
218
+ console.log('Custom components importing error: ', err);
219
+ return;
220
+ }
221
+ if (data.isFile()) {
222
+ const componentName = getComponentNameFromPath(filePath);
223
+ customComponentsComponents += `app.component('${componentName}', ${componentName});\n`;
224
+ }
225
+ })
220
226
  }
221
227
  }
222
228
 
@@ -239,6 +245,17 @@ class CodeInjector {
239
245
  }
240
246
  await fs.promises.writeFile(appVuePath, appVueContent);
241
247
 
248
+ // generate tailwind extend styles
249
+ if(this.adminforth.config?.styles){
250
+ const tailwindStyles = this.adminforth.config?.styles
251
+ const stylesText = JSON.stringify(tailwindStyles, null, 2).slice(1, -1);
252
+ let tailwindConfigPath = path.join(CodeInjector.SPA_TMP_PATH, 'tailwind.config.js');
253
+ let tailwindConfigContent = await fs.promises.readFile(tailwindConfigPath, 'utf-8');
254
+ tailwindConfigContent = tailwindConfigContent.replace('/* IMPORTANT:ADMINFORTH TAILWIND STYLES */', stylesText);
255
+ await fs.promises.writeFile(tailwindConfigPath, tailwindConfigContent);
256
+ }
257
+
258
+
242
259
 
243
260
  /* generate custom rotes */
244
261
  const homepageMenuItem = this.adminforth.config.menu.find((mi)=>mi.homepage);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "adminforth",
3
- "version": "1.0.51",
3
+ "version": "1.0.53",
4
4
  "description": "OpenSource Vue3 powered forth-generation admin panel",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/spa/index.html CHANGED
@@ -16,8 +16,8 @@
16
16
  </script> -->
17
17
 
18
18
  </head>
19
- <body>
20
- <div id="app" class="min-h-screen dark:bg-gray-800"></div>
19
+ <body class=" ">
20
+ <div id="app" class="min-h-screen bg-html-bg dark:bg-gray-800"></div>
21
21
  <script type="module" src="/src/main.ts"></script>
22
22
  </body>
23
23
  </html>
@@ -9,6 +9,7 @@
9
9
  "version": "0.0.0",
10
10
  "dependencies": {
11
11
  "@iconify-prerendered/vue-flowbite": "^0.23.1714023977",
12
+ "@unhead/vue": "^1.9.12",
12
13
  "@vueuse/core": "^10.10.0",
13
14
  "dayjs": "^1.11.11",
14
15
  "debounce": "^2.1.0",
@@ -1185,6 +1186,23 @@
1185
1186
  "url": "https://github.com/sponsors/harlan-zw"
1186
1187
  }
1187
1188
  },
1189
+ "node_modules/@unhead/vue": {
1190
+ "version": "1.9.12",
1191
+ "resolved": "https://registry.npmjs.org/@unhead/vue/-/vue-1.9.12.tgz",
1192
+ "integrity": "sha512-keE4EuswgzCqVU7zmZprU+ToMvNWc3s8NoLreH5AtJd2u0FgBygD8sxRVyEnZw1KwFYOJ2C7yD2TChSKZioPGQ==",
1193
+ "dependencies": {
1194
+ "@unhead/schema": "1.9.12",
1195
+ "@unhead/shared": "1.9.12",
1196
+ "hookable": "^5.5.3",
1197
+ "unhead": "1.9.12"
1198
+ },
1199
+ "funding": {
1200
+ "url": "https://github.com/sponsors/harlan-zw"
1201
+ },
1202
+ "peerDependencies": {
1203
+ "vue": ">=2.7 || >=3"
1204
+ }
1205
+ },
1188
1206
  "node_modules/@vitejs/plugin-vue": {
1189
1207
  "version": "5.0.4",
1190
1208
  "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.0.4.tgz",
package/spa/package.json CHANGED
@@ -13,6 +13,7 @@
13
13
  },
14
14
  "dependencies": {
15
15
  "@iconify-prerendered/vue-flowbite": "^0.23.1714023977",
16
+ "@unhead/vue": "^1.9.12",
16
17
  "@vueuse/core": "^10.10.0",
17
18
  "dayjs": "^1.11.11",
18
19
  "debounce": "^2.1.0",
package/spa/src/App.vue CHANGED
@@ -1,89 +1,84 @@
1
1
  <template>
2
2
  <nav
3
3
  v-if="loggedIn"
4
-
5
- class="fixed h-14 top-0 z-50 w-full bg-white border-b border-gray-200 dark:bg-gray-800 dark:border-gray-700">
4
+ class="fixed h-14 top-0 z-40 w-full border-b shadow-sm bg-header-bg shadow-header-shadow dark:bg-header-bg-hover dark:border-nav-menu-devider">
6
5
  <div class="px-3 py-3 lg:px-5 lg:pl-3">
7
6
  <div class="flex items-center justify-between">
8
7
  <div class="flex items-center justify-start rtl:justify-end">
9
- <button data-drawer-target="logo-sidebar" data-drawer-toggle="logo-sidebar" aria-controls="logo-sidebar" type="button" class="inline-flex items-center p-2 text-sm text-gray-500 rounded-lg sm:hidden hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-gray-200 dark:text-gray-400 dark:hover:bg-gray-700 dark:focus:ring-gray-600">
8
+ <button data-drawer-target="logo-sidebar" data-drawer-toggle="logo-sidebar" aria-controls="logo-sidebar" type="button" class="inline-flex items-center p-2 text-sm rounded-lg sm:hidden hover:bg-nav-menu-bg-hover focus:outline-none focus:ring-2 focus:ring-nav-menu-devider dark:text-nav-menu-icons dark:hover:bg-nav-menu-bg-hover dark:focus:ring-nav-menu-devider">
10
9
  <span class="sr-only">Open sidebar</span>
11
10
  <svg class="w-6 h-6" aria-hidden="true" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
12
11
  <path clip-rule="evenodd" fill-rule="evenodd" d="M2 4.75A.75.75 0 012.75 4h14.5a.75.75 0 010 1.5H2.75A.75.75 0 012 4.75zm0 10.5a.75.75 0 01.75-.75h7.5a.75.75 0 010 1.5h-7.5a.75.75 0 01-.75-.75zM2 10a.75.75 0 01.75-.75h14.5a.75.75 0 010 1.5H2.75A.75.75 0 012 10z"></path>
13
12
  </svg>
14
13
  </button>
15
- <div class="flex ms-2 md:me-24">
16
- <img src="https://flowbite.com/docs/images/logo.svg" class="h-8 me-3" alt="FlowBite Logo" />
17
- <span class="self-center text-xl font-semibold sm:text-2xl whitespace-nowrap dark:text-white">
18
- {{ coreStore.config?.brandName }}
19
- </span>
20
- </div>
14
+
21
15
  </div>
22
16
  <div class="flex items-center">
23
-
24
-
25
- <div class="flex items-center ms-3">
26
- <div>
27
- <button type="button" class="flex text-sm bg-gray-100 rounded-full focus:ring-4 focus:ring-gray-300 dark:focus:ring-gray-600 dark:bg-gray-700" aria-expanded="false" data-dropdown-toggle="dropdown-user">
28
- <span class="sr-only">Open user menu</span>
29
- <svg class="w-8 h-8 text-gray-400 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 24 24">
30
- <path fill-rule="evenodd" d="M12 20a7.966 7.966 0 0 1-5.002-1.756l.002.001v-.683c0-1.794 1.492-3.25 3.333-3.25h3.334c1.84 0 3.333 1.456 3.333 3.25v.683A7.966 7.966 0 0 1 12 20ZM2 12C2 6.477 6.477 2 12 2s10 4.477 10 10c0 5.5-4.44 9.963-9.932 10h-.138C6.438 21.962 2 17.5 2 12Zm10-5c-1.84 0-3.333 1.455-3.333 3.25S10.159 13.5 12 13.5c1.84 0 3.333-1.455 3.333-3.25S13.841 7 12 7Z" clip-rule="evenodd"/>
31
- </svg>
32
-
33
- </button>
17
+ <div class="flex items-center ms-3 ">
18
+ <div>
19
+ <button type="button" class="flex text-sm bg-header-bg-hover rounded-full focus:ring-4 focus:ring-nav-menu-devider dark:focus:ring-nav-menu-devider dark:bg-header-bg-hover" aria-expanded="false" data-dropdown-toggle="dropdown-user">
20
+ <span class="sr-only">Open user menu</span>
21
+ <svg class="w-8 h-8 text-nav-menu-icons dark:text-header-icons" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 24 24">
22
+ <path fill-rule="evenodd" d="M12 20a7.966 7.966 0 0 1-5.002-1.756l.002.001v-.683c0-1.794 1.492-3.25 3.333-3.25h3.334c1.84 0 3.333 1.456 3.333 3.25v.683A7.966 7.966 0 0 1 12 20ZM2 12C2 6.477 6.477 2 12 2s10 4.477 10 10c0 5.5-4.44 9.963-9.932 10h-.138C6.438 21.962 2 17.5 2 12Zm10-5c-1.84 0-3.333 1.455-3.333 3.25S10.159 13.5 12 13.5c1.84 0 3.333-1.455 3.333-3.25S13.841 7 12 7Z" clip-rule="evenodd"/>
23
+ </svg>
24
+ </button>
25
+ </div>
26
+ <div class="z-50 hidden my-4 text-base list-none bg-white divide-y divide-gray-100 rounded shadow dark:shadow-black dark:bg-nav-menu-bg dark:divide-nav-menu-devider dark:shadow-black" id="dropdown-user">
27
+ <div class="px-4 py-3" role="none">
28
+ <p class="text-sm text-gray-900 dark:text-header-text" role="none" v-if="coreStore.userFullname">
29
+ {{ coreStore.userFullname }}
30
+ </p>
31
+ <p class="text-sm font-medium text-gray-900 truncate dark:text-nav-menu-text" role="none">
32
+ {{ coreStore.username }}
33
+ </p>
34
34
  </div>
35
- <div class="z-50 hidden my-4 text-base list-none bg-white divide-y divide-gray-100 rounded shadow dark:shadow-black dark:bg-gray-700 dark:divide-gray-600 dark:shadow-black" id="dropdown-user">
36
- <div class="px-4 py-3" role="none">
37
- <p class="text-sm text-gray-900 dark:text-white" role="none" v-if="coreStore.userFullname">
38
- {{ coreStore.userFullname }}
39
- </p>
40
- <p class="text-sm font-medium text-gray-900 truncate dark:text-gray-300" role="none">
41
- {{ coreStore.username }}
42
- </p>
43
- </div>
44
- <ul class="py-1" role="none">
45
- <li >
46
- <span @click="toggleTheme" class=" cursor-pointer flex items-center gap-1 block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-600 dark:hover:text-white" role="menuitem">
47
- {{ theme === 'dark' ? 'Light' : 'Dark' }}
48
- <IconMoonSolid class="w-5 h-5 text-blue-300
49
- " v-if="theme !== 'dark'"/>
50
- <IconSunSolid class="w-5 h-5 text-yellow-300
51
- " v-else/>
52
- mode
53
- </span>
54
- </li>
55
-
56
- <li>
57
- <button @click="logout" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-600 dark:hover:text-white" role="menuitem">Sign out</button>
35
+ <ul class="py-1" role="none">
36
+ <li>
37
+ <span @click="toggleTheme" class="cursor-pointer flex items-center gap-1 block px-4 py-2 text-sm text-black hover:bg-html-bg dark:text-nav-menu-text-hover dark:hover:bg-nav-menu-hover dark:hover:text-nav-menu-text-active" role="menuitem">
38
+ {{ theme === 'dark' ? 'Light' : 'Dark' }}
39
+ <IconMoonSolid class="w-5 h-5 text-blue-300" v-if="theme !== 'dark'" />
40
+ <IconSunSolid class="w-5 h-5 text-yellow-300" v-else />
41
+ mode
42
+ </span>
58
43
  </li>
59
- </ul>
60
- </div>
44
+ <li>
45
+ <button @click="logout" class="cursor-pointer flex items-center gap-1 block px-4 py-2 text-sm text-black hover:bg-html-bg dark:text-nav-menu-text-hover dark:hover:bg-nav-menu-hover dark:hover:text-nav-menu-text-active w-full" role="menuitem">Sign out</button>
46
+ </li>
47
+ </ul>
61
48
  </div>
62
49
  </div>
50
+ </div>
63
51
  </div>
64
52
  </div>
65
- </nav>
53
+ </nav>
54
+
66
55
 
67
56
 
68
57
  <aside
69
58
  v-if="loggedIn"
70
59
 
71
- id="logo-sidebar" class="fixed top-0 left-0 z-10 w-64 h-screen pt-20 transition-transform -translate-x-full bg-white border-r border-gray-200 sm:translate-x-0 dark:bg-gray-800 dark:border-gray-700" aria-label="Sidebar">
72
- <div class="h-full px-3 pb-4 overflow-y-auto bg-white dark:bg-gray-800">
60
+ id="logo-sidebar" class="fixed border-none top-0 left-0 z-50 w-64 h-screen transition-transform -translate-x-full bg-nav-menu-bg border-r border- sm:translate-x-0 dark:bg-gray-800 dark:border-gray-700" aria-label="Sidebar">
61
+ <div class="h-full px-3 pb-4 overflow-y-auto bg-nav-menu-bg dark:bg-gray-800">
62
+ <div class="flex ms-2 md:me-24 m-4 ">
63
+ <img src="https://flowbite.com/docs/images/logo.svg" class="h-8 me-3" alt="FlowBite Logo" />
64
+ <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">
65
+ {{ coreStore.config?.brandName }}
66
+ </span>
67
+ </div>
73
68
  <ul class="space-y-2 font-medium">
74
69
  <template v-for="(item, i) in coreStore.menu" :key="`menu-${i}`">
75
- <div v-if="item.type === 'divider'" class="border-t border-gray-200 dark:border-gray-700"></div>
70
+ <div v-if="item.type === 'divider'" class="border-t border-nav-menu-devider dark:border-gray-700"></div>
76
71
  <div v-else-if="item.type === 'gap'" class="flex items-center justify-center h-8"></div>
77
72
  <div v-else-if="item.type === 'heading'" class="flex items-center justify-left pl-2 h-8 text-gray-400 dark:text-gray-400
78
73
  ">{{ item.label }}</div>
79
74
 
80
75
 
81
76
  <li v-else-if="item.children">
82
- <button type="button" class="flex items-center w-full p-2 text-base text-gray-900 transition duration-75 rounded-lg group hover:bg-gray-100 dark:text-white dark:hover:bg-gray-700"
77
+ <button type="button" class="flex items-center w-full p-2 text-base text-nav-menu-text transition duration-75 group hover:bg-nav-menu-bg-hover dark:text-white dark:hover:bg-gray-700"
83
78
  :aria-controls="`dropdown-example${i}`"
84
79
  :data-collapse-toggle="`dropdown-example${i}`"
85
80
  >
86
- <component v-if="item.icon" :is="getIcon(item.icon)" class="w-5 h-5 text-gray-500 transition duration-75 dark:text-gray-400 group-hover:text-gray-900 dark:group-hover:text-white" ></component>
81
+ <component v-if="item.icon" :is="getIcon(item.icon)" class="w-5 h-5 text-gray-500 transition duration-75 dark:text-gray-400 dark:group-hover:text-white" ></component>
87
82
 
88
83
  <span class="flex-1 ms-3 text-left rtl:text-right whitespace-nowrap">{{ item.label }}</span>
89
84
  <svg class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 10 6">
@@ -135,6 +130,7 @@ import { useRoute, useRouter } from 'vue-router';
135
130
  import { getIcon } from '@/utils';
136
131
  import { useHead } from 'unhead'
137
132
  import { createHead } from 'unhead'
133
+ // import { link } from 'fs';
138
134
  const coreStore = useCoreStore();
139
135
 
140
136
 
@@ -182,6 +178,12 @@ onMounted(async () => {
182
178
  title.value = coreStore.config.title || 'Admin Forth';
183
179
  useHead({
184
180
  title: title.value,
181
+ link: [
182
+ {
183
+ rel: 'icon',
184
+ href: coreStore.config.favicon || '/favicon.ico',
185
+ },
186
+ ],
185
187
 
186
188
  })
187
189
  })
@@ -9,7 +9,8 @@
9
9
  :placeholder="selectedItems.length ? '' : 'Select...'"
10
10
  />
11
11
  <div class="absolute inset-y-0 left-2 flex items-center pr-2 flex-wrap">
12
- <div v-for="item in selectedItems" :key="item.name" class="bg-blue-100 text-blue-800 text-xs font-medium me-2 px-2.5 py-0.5 rounded dark:bg-blue-900 dark:text-blue-300">
12
+ {{ }}
13
+ <div v-for="item in selectedItems" :key="item?.name" class="bg-blue-100 text-blue-800 text-xs font-medium me-2 px-2.5 py-0.5 rounded dark:bg-blue-900 dark:text-blue-300">
13
14
  <span>{{ item.label }}</span>
14
15
  <button
15
16
  type="button"
@@ -92,7 +92,7 @@
92
92
  </div>
93
93
  </div>
94
94
 
95
- <div v-if="show" drawer-backdrop="" class="bg-gray-900/50 dark:bg-gray-900/80 fixed inset-0 z-30"
95
+ <div v-if="show" drawer-backdrop="" class="bg-gray-900/50 dark:bg-gray-900/80 fixed inset-0 z-50"
96
96
  @click="$emit('hide')">
97
97
  </div>
98
98
  </template>
@@ -1,11 +1,11 @@
1
1
  <template>
2
2
  <RouterLink
3
3
  :to="{name: item.resourceId ? 'resource-list' : item.path, params: item.resourceId ? { resourceId: item.resourceId }: {}}"
4
- class="flex items-center py-2 text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-600 dark:hover:text-white" role="menuitem"
4
+ class="flex items-center py-2 text-nav-menu-text hover:bg-nav-menu-bg-hover active:bg-nav-menu-bg-active" role="menuitem"
5
5
  :class="{
6
6
  'px-4': isChild,
7
7
  'px-2': !isChild,
8
- 'bg-blue-100 dark:bg-gray-700': item.resourceId ?
8
+ 'bg-nav-menu-bg-active dark:bg-gray-700': item.resourceId ?
9
9
  ($route.params.resourceId === item.resourceId && $route.name === 'resource-list') :
10
10
  ($route.name === item.path)
11
11
  }"
@@ -2,11 +2,11 @@
2
2
  <div>
3
3
 
4
4
  <div
5
- class="relative shadow-md sm:rounded-lg dark:shadow-2xl dark:shadow-black"
5
+ class="relative shadow-resourse-form-shadow sm:rounded-lg dark:shadow-2xl dark:shadow-black"
6
6
  >
7
7
  <form autocomplete="off" @submit.prevent>
8
8
  <table class="w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400">
9
- <thead class="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400">
9
+ <thead class="text-xs text-gray-700 uppercase bg-form-view-heading dark:bg-gray-700 dark:text-gray-400">
10
10
  <tr>
11
11
  <th scope="col" class="px-6 py-3">
12
12
  Field
@@ -18,7 +18,7 @@
18
18
  </thead>
19
19
  <tbody>
20
20
  <tr v-for="column, i in editableColumns" :key="column.name"
21
- class="odd:bg-white odd:dark:bg-gray-900 even:bg-gray-50 even:dark:bg-gray-800 border-b dark:border-gray-700"
21
+ class="bg-form-view-bg dark:bg-gray-800 border-b dark:border-gray-700"
22
22
  >
23
23
  <!-- if column is in customComponentsPerColumn, use this component. If not, use this code -->
24
24
  <template v-if="customComponentsPerColumn[column.name]">
@@ -65,7 +65,7 @@
65
65
  </BreadcrumbsWithButtons>
66
66
 
67
67
  <!-- table -->
68
- <div class="relative overflow-x-auto shadow-md dark:shadow-black sm:rounded-lg dark:s">
68
+ <div class="relative overflow-x-auto shadow-list-table-shadow dark:shadow-black dark:s">
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-gray-700 bg-gray-50 dark:bg-gray-700 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">
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-white border-b dark:bg-gray-800 dark:border-gray-700">
130
+ <tr v-if="!rows" class="bg-list-view-table-bg 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-white border-b dark:bg-gray-800 dark:border-gray-700">
174
+ <tr v-else-if="rows.length === 0" class="bg-list-view-table-bg 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-white border-b dark:bg-gray-800 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-600">
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">
189
189
  <td class="w-4 p-4">
190
190
  <div class="flex items center">
191
191
  <input
@@ -27,10 +27,10 @@
27
27
  </div>
28
28
  <div
29
29
  v-else
30
- class="relative overflow-x-auto shadow-md sm:rounded-lg"
30
+ class="relative overflow-x-auto shadow-resourse-form-shadow "
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-gray-50 dark:bg-gray-700 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">
34
34
  <tr>
35
35
  <th scope="col" class="px-6 py-3">
36
36
  Field
@@ -42,7 +42,7 @@
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="odd:bg-white odd:dark:bg-gray-900 even:bg-gray-50 even:dark:bg-gray-800 border-b dark:border-gray-700"
45
+ class="bg-form-view-bg odd:dark:bg-gray-900 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 }}
@@ -1,9 +1,12 @@
1
1
  /** @type {import('tailwindcss').Config} */
2
2
  export default {
3
- content: ["./src/**/*.{vue, js}", "./node_modules/flowbite/**/*.js"],
3
+ content: ["./src/**/*.{vue, js}","./src/*.{vue, js}", "./index.html", "./node_modules/flowbite/**/*.js"],
4
4
  theme: {
5
- extend: {},
5
+ extend: {
6
+ /* IMPORTANT:ADMINFORTH TAILWIND STYLES */
7
+ }
6
8
  },
9
+
7
10
  darkMode: 'class',
8
11
  plugins: [
9
12
  require('flowbite/plugin'),
@@ -104,12 +104,14 @@ export type AdminForthConfig = {
104
104
  customization?: {
105
105
  customComponentsDir?: string,
106
106
  vueUsesFile?: string,
107
+ customPublicDir?: string,
107
108
  },
108
109
  baseUrl?: string,
109
110
  brandName?: string,
110
111
  datesFormat?: string,
111
112
  deleteConfirmation?: boolean,
112
113
  title?: string,
114
+ styles?: Object,
113
115
  }
114
116
 
115
117
  export type AllowedActions = {