adminforth 1.1.2 → 1.1.3

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 (58) hide show
  1. package/dist/index.js +23 -7
  2. package/dist/modules/codeInjector.js +8 -8
  3. package/dist/plugins/ForeignInlineListPlugin/index.js +2 -1
  4. package/dist/spa/spa/src/App.vue +1 -1
  5. package/dist/spa/spa/src/components/ResourceListTable.vue +363 -0
  6. package/dist/spa/spa/src/stores/core.ts +2 -3
  7. package/dist/spa/spa/src/utils.ts +2 -2
  8. package/dist/spa/spa/src/views/CreateView.vue +3 -0
  9. package/dist/spa/spa/src/views/EditView.vue +3 -0
  10. package/dist/spa/spa/src/views/ListView.vue +24 -317
  11. package/dist/spa/spa/src/views/ShowView.vue +8 -11
  12. package/index.ts +29 -11
  13. package/modules/codeInjector.ts +11 -11
  14. package/package.json +1 -1
  15. package/plugins/ForeignInlineListPlugin/custom/InlineList.vue +110 -2
  16. package/plugins/ForeignInlineListPlugin/index.ts +2 -2
  17. package/spa/src/App.vue +1 -1
  18. package/spa/src/components/ResourceListTable.vue +363 -0
  19. package/spa/src/stores/core.ts +2 -3
  20. package/spa/src/utils.ts +2 -2
  21. package/spa/src/views/CreateView.vue +3 -0
  22. package/spa/src/views/EditView.vue +3 -0
  23. package/spa/src/views/ListView.vue +24 -317
  24. package/spa/src/views/ShowView.vue +8 -11
  25. package/types/AdminForthConfig.ts +79 -23
  26. package/documentation/README.md +0 -41
  27. package/documentation/babel.config.js +0 -3
  28. package/documentation/blog/2019-05-28-first-blog-post.md +0 -12
  29. package/documentation/blog/2019-05-29-long-blog-post.md +0 -44
  30. package/documentation/blog/2021-08-01-mdx-blog-post.mdx +0 -20
  31. package/documentation/blog/2021-08-26-welcome/docusaurus-plushie-banner.jpeg +0 -0
  32. package/documentation/blog/2021-08-26-welcome/index.md +0 -25
  33. package/documentation/blog/authors.yml +0 -17
  34. package/documentation/blog/tags.yml +0 -16
  35. package/documentation/docs/Getting Started.md +0 -374
  36. package/documentation/docs/Glossary.md +0 -37
  37. package/documentation/docs/image.png +0 -0
  38. package/documentation/docusaurus.config.ts +0 -171
  39. package/documentation/package-lock.json +0 -14655
  40. package/documentation/package.json +0 -51
  41. package/documentation/sidebars.ts +0 -51
  42. package/documentation/src/components/HomepageFeatures/index.tsx +0 -68
  43. package/documentation/src/components/HomepageFeatures/styles.module.css +0 -11
  44. package/documentation/src/css/custom.css +0 -30
  45. package/documentation/src/pages/index.module.css +0 -24
  46. package/documentation/src/pages/index.tsx +0 -43
  47. package/documentation/src/pages/markdown-page.md +0 -7
  48. package/documentation/static/.nojekyll +0 -0
  49. package/documentation/static/CNAME +0 -1
  50. package/documentation/static/img/css.png +0 -0
  51. package/documentation/static/img/db.png +0 -0
  52. package/documentation/static/img/db2.png +0 -0
  53. package/documentation/static/img/favicon.png +0 -0
  54. package/documentation/static/img/gen.sh +0 -24
  55. package/documentation/static/img/logo.svg +0 -19
  56. package/documentation/static/img/tail.png +0 -0
  57. package/documentation/static/img/vue.png +0 -0
  58. package/documentation/typedoc-plugin.mjs +0 -11
@@ -12,6 +12,7 @@
12
12
  <component
13
13
  v-for="c in coreStore?.resourceOptions?.pageInjections?.list?.beforeBreadcrumbs || []"
14
14
  :is="getCustomComponent(c)"
15
+ :meta="c.meta"
15
16
  :resource="coreStore.resource"
16
17
  :adminUser="coreStore.adminUser"
17
18
  />
@@ -34,7 +35,9 @@
34
35
  </button>
35
36
 
36
37
  <button
37
- v-if="checkboxes.length" v-for="(action,i) in allCheckedActions" :key="action.id"
38
+ v-if="checkboxes.length"
39
+ v-for="(action,i) in coreStore.resource?.options?.bulkActions"
40
+ :key="action.id"
38
41
  @click="startBulkAction(action.id)"
39
42
  class="flex gap-1 items-center py-1 px-3 mb-2 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded border border-gray-300 hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700"
40
43
  :class="{'bg-red-100 text-red-800 border-red-400 dark:bg-red-700 dark:text-red-400 dark:border-red-400':action.state==='danger', 'bg-green-100 text-green-800 border-green-400 dark:bg-green-700 dark:text-green-400 dark:border-green-400':action.state==='success',
@@ -49,7 +52,7 @@
49
52
  {{ `${action.label} (${checkboxes.length})` }}
50
53
  </button>
51
54
 
52
- <RouterLink v-if="allowedActions.create"
55
+ <RouterLink v-if="coreStore.resource?.options?.create"
53
56
  :to="{ name: 'resource-create', params: { resourceId: $route.params.resourceId } }"
54
57
  class="flex items-center py-1 px-3 mb-2 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded border border-gray-300 hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700 rounded-default"
55
58
  >
@@ -74,264 +77,25 @@
74
77
  <component
75
78
  v-for="c in coreStore?.resourceOptions?.pageInjections?.list?.afterBreadcrumbs || []"
76
79
  :is="getCustomComponent(c)"
80
+ :meta="c.meta"
77
81
  :resource="coreStore.resource"
78
82
  :adminUser="coreStore.adminUser"
79
83
  />
80
84
 
81
- <!-- table -->
82
- <div class="relative overflow-x-auto shadow-list-table-shadow dark:shadow-black overflow-y-hidden rounded-default">
83
-
84
-
85
- <!-- skelet loader -->
86
- <div role="status" v-if="!coreStore.resourceColumns"
87
- class="max-w p-4 space-y-4 divide-y divide-gray-200 rounded shadow animate-pulse dark:divide-gray-700 md:p-6 dark:border-gray-700">
88
- <div class="flex items-center justify-between h-16">
89
- <div>
90
- <div class="h-2.5 bg-gray-300 rounded-full dark:bg-gray-600 w-24 mb-2.5"></div>
91
- <div class="w-32 h-2 bg-gray-200 rounded-full dark:bg-gray-700"></div>
92
- </div>
93
- <div class="h-2.5 bg-gray-300 rounded-full dark:bg-gray-700 w-12"></div>
94
- </div>
95
-
96
- <div class="flex items-center justify-between h-16 pt-4" v-for="i in new Array(10)">
97
- <div>
98
- <div class="h-2.5 bg-gray-300 rounded-full dark:bg-gray-600 w-24 mb-2.5"></div>
99
- <div class="w-32 h-2 bg-gray-200 rounded-full dark:bg-gray-700"></div>
100
- </div>
101
- <div class="h-2.5 bg-gray-300 rounded-full dark:bg-gray-700 w-12"></div>
102
- </div>
103
- <span class="sr-only">Loading...</span>
104
- </div>
105
-
106
- <table v-else class="w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400 rounded-default">
107
- <thead class="text-xs text-view-table-heading-text bg-list-view-table-heading dark:bg-gray-700 dark:text-gray-400">
108
- <tr>
109
- <th scope="col" class="p-4">
110
- <div v-if="rows && rows.length" class="flex items-center">
111
- <input id="checkbox-all-search" type="checkbox" :checked="allFromThisPageChecked" @change="selectAll()"
112
- class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 dark:focus:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600">
113
- <label for="checkbox-all-search" class="sr-only">checkbox</label>
114
- </div>
115
- </th>
116
-
117
- <th v-for="c in columnsListed" scope="col" class="px-6 py-3">
118
- <div @click="() => c.sortable && onSortButtonClick(c.name)" class="flex items-center " :class="{'cursor-pointer':c.sortable}">
119
- {{ c.label }}
120
-
121
- <div v-if="c.sortable"
122
- :style="{ 'color':ascArr.includes(c.name)?'green':descArr.includes(c.name)?'red':'currentColor'}">
123
- <svg v-if="ascArr.includes(c.name) || descArr.includes(c.name)" class="w-3 h-3 ms-1.5"
124
- :class="{'rotate-180':descArr.includes(c.name)}" viewBox="0 0 24 24">
125
- <path
126
- d="M8.574 11.024h6.852a2.075 2.075 0 0 0 1.847-1.086 1.9 1.9 0 0 0-.11-1.986L13.736 2.9a2.122 2.122 0 0 0-3.472 0L6.837 7.952a1.9 1.9 0 0 0-.11 1.986 2.074 2.074 0 0 0 1.847"/>
127
- </svg>
128
- <svg v-else class="w-3 h-3 ms-1.5 opacity-30" aria-hidden="true" xmlns="http://www.w3.org/2000/svg"
129
- fill='currentColor'
130
- viewBox="0 0 24 24">
131
- <path
132
- d="M8.574 11.024h6.852a2.075 2.075 0 0 0 1.847-1.086 1.9 1.9 0 0 0-.11-1.986L13.736 2.9a2.122 2.122 0 0 0-3.472 0L6.837 7.952a1.9 1.9 0 0 0-.11 1.986 2.074 2.074 0 0 0 1.847 1.086Zm6.852 1.952H8.574a2.072 2.072 0 0 0-1.847 1.087 1.9 1.9 0 0 0 .11 1.985l3.426 5.05a2.123 2.123 0 0 0 3.472 0l3.427-5.05a1.9 1.9 0 0 0 .11-1.985 2.074 2.074 0 0 0-1.846-1.087Z"/>
133
- </svg>
134
- </div>
135
- </div>
136
- </th>
137
-
138
- <th scope="col" class="px-6 py-3">
139
- Actions
140
- </th>
141
- </tr>
142
- </thead>
143
- <tbody>
144
- <tr v-if="!rows" class="bg-list-view-table-bg border-b dark:bg-gray-800 dark:border-gray-700">
145
- <td :colspan="coreStore.resourceColumns.length + 2">
146
-
147
- <div role="status"
148
- class="max-w p-4 space-y-4 divide-y divide-gray-200 rounded animate-pulse dark:divide-gray-700 md:p-6 dark:border-gray-700">
149
- <div class="flex items-center justify-between">
150
- <div>
151
- <div class="h-2.5 bg-gray-300 rounded-full dark:bg-gray-600 w-24 mb-2.5"></div>
152
- <div class="w-32 h-2 bg-gray-200 rounded-full dark:bg-gray-700"></div>
153
- </div>
154
- <div class="h-2.5 bg-gray-300 rounded-full dark:bg-gray-700 w-12"></div>
155
- </div>
156
- <div class="flex items-center justify-between pt-4">
157
- <div>
158
- <div class="h-2.5 bg-gray-300 rounded-full dark:bg-gray-600 w-24 mb-2.5"></div>
159
- <div class="w-32 h-2 bg-gray-200 rounded-full dark:bg-gray-700"></div>
160
- </div>
161
- <div class="h-2.5 bg-gray-300 rounded-full dark:bg-gray-700 w-12"></div>
162
- </div>
163
- <div class="flex items-center justify-between pt-4">
164
- <div>
165
- <div class="h-2.5 bg-gray-300 rounded-full dark:bg-gray-600 w-24 mb-2.5"></div>
166
- <div class="w-32 h-2 bg-gray-200 rounded-full dark:bg-gray-700"></div>
167
- </div>
168
- <div class="h-2.5 bg-gray-300 rounded-full dark:bg-gray-700 w-12"></div>
169
- </div>
170
- <div class="flex items-center justify-between pt-4">
171
- <div>
172
- <div class="h-2.5 bg-gray-300 rounded-full dark:bg-gray-600 w-24 mb-2.5"></div>
173
- <div class="w-32 h-2 bg-gray-200 rounded-full dark:bg-gray-700"></div>
174
- </div>
175
- <div class="h-2.5 bg-gray-300 rounded-full dark:bg-gray-700 w-12"></div>
176
- </div>
177
- <div class="flex items-center justify-between pt-4">
178
- <div>
179
- <div class="h-2.5 bg-gray-300 rounded-full dark:bg-gray-600 w-24 mb-2.5"></div>
180
- <div class="w-32 h-2 bg-gray-200 rounded-full dark:bg-gray-700"></div>
181
- </div>
182
- <div class="h-2.5 bg-gray-300 rounded-full dark:bg-gray-700 w-12"></div>
183
- </div>
184
- <span class="sr-only">Loading...</span>
185
- </div>
186
- </td>
187
- </tr>
188
- <tr v-else-if="rows.length === 0" class="bg-list-view-table-bg border-b dark:bg-gray-800 dark:border-gray-700">
189
- <td :colspan="coreStore.resourceColumns.length + 2">
190
-
191
- <div id="toast-simple"
192
- class=" mx-auto my-5 flex items-center w-full max-w-xs p-4 space-x-4 rtl:space-x-reverse text-gray-500 bg-white divide-x rtl:divide-x-reverse divide-gray-200 dark:text-gray-400 dark:divide-gray-700 space-x dark:bg-gray-800"
193
- role="alert">
194
- <IconInboxOutline class="w-6 h-6 text-gray-500 dark:text-gray-400"/>
195
- <div class="ps-4 text-sm font-normal">No items here yet</div>
196
- </div>
197
-
198
- </td>
199
- </tr>
200
-
201
- <tr v-else v-for="(row, rowI) in rows" :key="row.id"
202
- 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">
203
- <td class="w-4 p-4">
204
- <div class="flex items center">
205
- <input
206
- id="checkbox-table-search-1"
207
- type="checkbox"
208
- :checked="checkboxes.includes(row.id)"
209
- @change="(e)=>{addToCheckedValues(row.id)}"
210
- class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 dark:focus:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600">
211
- <label for="checkbox-table-search-1" class="sr-only">checkbox</label>
212
- </div>
213
- </td>
214
- <td v-for="c in columnsListed" class="px-6 py-4">
215
- <!-- if c.name in listComponentsPerColumn, render it. If not, render ValueRenderer -->
216
- <component
217
- :is="listComponentsPerColumn[c.name] || ValueRenderer"
218
- :column="c"
219
- :record="row"
220
- :adminUser="coreStore.adminUser"
221
- :resource="coreStore.resource"
222
- />
223
- </td>
224
- <td class=" items-center px-6 py-4">
225
- <div class="flex">
226
- <RouterLink
227
- v-if="allowedActions.show"
228
- :to="{ name: 'resource-show', params: { resourceId: $route.params.resourceId, primaryKey: row._primaryKeyValue } }"
229
- class="font-medium text-blue-600 dark:text-blue-500 hover:underline"
230
- :data-tooltip-target="`tooltip-show-${rowI}`"
231
- >
232
- <IconEyeSolid class="w-5 h-5 me-2"/>
233
- </RouterLink>
234
-
235
- <div :id="`tooltip-show-${rowI}`"
236
- role="tooltip"
237
- class="absolute z-10 invisible inline-block px-3 py-2 text-sm font-medium text-white transition-opacity duration-300 bg-gray-900 rounded-lg shadow-sm opacity-0 tooltip dark:bg-gray-700">
238
- Show item
239
- <div class="tooltip-arrow" data-popper-arrow></div>
240
- </div>
241
-
242
- <RouterLink v-if="allowedActions.edit"
243
- :to="{ name: 'resource-edit', params: { resourceId: $route.params.resourceId, primaryKey: row._primaryKeyValue } }"
244
- class="font-medium text-blue-600 dark:text-blue-500 hover:underline ms-3"
245
- :data-tooltip-target="`tooltip-edit-${rowI}`"
246
- >
247
- <IconPenSolid class="w-5 h-5 me-2"/>
248
- </RouterLink>
249
-
250
- <div :id="`tooltip-edit-${rowI}`"
251
- role="tooltip"
252
- class="absolute z-10 invisible inline-block px-3 py-2 text-sm font-medium text-white transition-opacity duration-300 bg-gray-900 rounded-lg shadow-sm opacity-0 tooltip dark:bg-gray-700">
253
- Edit
254
- <div class="tooltip-arrow" data-popper-arrow></div>
255
- </div>
256
-
257
- <button v-if="allowedActions.delete"
258
- class="font-medium text-red-600 dark:text-red-500 hover:underline ms-3"
259
- :data-tooltip-target="`tooltip-delete-${rowI}`"
260
- @click="showDeleteModal(row)"
261
- >
262
- <IconTrashBinSolid class="w-5 h-5 me-2"/>
263
- </button>
264
-
265
- <div :id="`tooltip-delete-${rowI}`"
266
- role="tooltip"
267
- class="absolute z-10 invisible inline-block px-3 py-2 text-sm font-medium text-white transition-opacity duration-300 bg-gray-900 rounded-lg shadow-sm opacity-0 tooltip dark:bg-gray-700">
268
- Delete
269
- <div class="tooltip-arrow" data-popper-arrow></div>
270
- </div>
271
- </div>
272
- </td>
273
- </tr>
274
- </tbody>
275
- </table>
276
- </div>
277
- <!-- pagination -->
278
- <div class="flex flex-col items-center mt-4 xs:flex-row xs:justify-between xs:items-center"
279
- v-if="rows && totalRows >= pageSize && totalRows > 0">
280
- <!-- Help text -->
281
- <span class="text-sm text-gray-700 dark:text-gray-400">
282
- Showing <span class="font-semibold text-gray-900 dark:text-white">
283
- {{ (page - 1) * pageSize + 1 }}
284
- </span> to <span class="font-semibold text-gray-900 dark:text-white">
285
- {{ Math.min(page * pageSize, totalRows) }}
286
- </span> of <span class="font-semibold text-gray-900 dark:text-white">{{
287
- totalRows
288
- }}</span> Entries
289
- </span>
290
- <div class="inline-flex mt-2 xs:mt-0">
291
- <!-- Buttons -->
292
- <button
293
- class="flex items-center py-1 px-3 text-sm font-medium text-gray-900 focus:outline-none bg-white border-r-0 rounded-s border border-gray-300 hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700 disabled:opacity-50"
294
- @click="page--" :disabled="page <= 1">
295
- <svg class="w-3.5 h-3.5 me-2 rtl:rotate-180" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none"
296
- viewBox="0 0 14 10">
297
- <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
298
- d="M13 5H1m0 0 4 4M1 5l4-4"/>
299
- </svg>
300
- Prev
301
- </button>
302
- <button
303
- class="flex items-center py-1 px-3 text-sm font-medium text-gray-900 focus:outline-none bg-white border-r-0 border border-gray-300 hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700 disabled:opacity-50"
304
- @click="page = 1" :disabled="page <= 1">
305
- <!-- <IconChevronDoubleLeftOutline class="w-4 h-4" /> -->
306
- 1
307
- </button>
308
- <input type="text"
309
- class="w-10 py-1.5 px-3 text-sm text-center text-gray-700 border border-gray-300 dark:border-gray-700 dark:text-gray-400 dark:bg-gray-800 z-10"
310
- v-model="page"/>
311
-
312
- <button
313
- class="flex items-center py-1 px-3 text-sm font-medium text-gray-900 focus:outline-none bg-white border-l-0 border border-gray-300 hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700 disabled:opacity-50"
314
- @click="page = totalPages" :disabled="page >= totalPages">
315
- {{ totalPages }}
316
- <!-- <IconChevronDoubleRightOutline class="w-4 h-4" /> -->
317
- </button>
318
- <button
319
- class="flex items-center py-1 px-3 text-sm font-medium text-gray-900 focus:outline-none bg-white border-l-0 rounded-e border border-gray-300 hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700 disabled:opacity-50"
320
-
321
- @click="page++" :disabled="page >= totalPages">
322
- Next
323
- <svg class="w-3.5 h-3.5 ms-2 rtl:rotate-180" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none"
324
- viewBox="0 0 14 10">
325
- <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
326
- d="M1 5h12m0 0L9 1m4 4L9 9"/>
327
- </svg>
328
- </button>
329
- </div>
330
- </div>
85
+ <ResourceListTable
86
+ :resource="coreStore.resource"
87
+ :rows="rows"
88
+ @update:page="page = $event"
89
+ @update:sort="sort = $event"
90
+ @update:checkboxes="checkboxes = $event"
91
+ :pageSize="pageSize"
92
+ :totalRows="totalRows"
93
+ />
331
94
 
332
95
  <component
333
96
  v-for="c in coreStore?.resourceOptions?.pageInjections?.list?.bottom || []"
334
97
  :is="getCustomComponent(c)"
98
+ :meta="c.meta"
335
99
  :resource="coreStore.resource"
336
100
  :adminUser="coreStore.adminUser"
337
101
  />
@@ -341,6 +105,7 @@
341
105
 
342
106
  <script setup>
343
107
  import BreadcrumbsWithButtons from '@/components/BreadcrumbsWithButtons.vue';
108
+ import ResourceListTable from '@/components/ResourceListTable.vue';
344
109
  import { useCoreStore } from '@/stores/core';
345
110
  import { useModalStore } from '@/stores/modal';
346
111
  import { callAdminForthApi, getIcon } from '@/utils';
@@ -368,52 +133,28 @@ import Filters from '@/components/Filters.vue';
368
133
 
369
134
  const filtersShow = ref(false);
370
135
 
371
-
372
136
  const coreStore = useCoreStore();
373
137
  const modalStore = useModalStore();
374
138
 
375
139
  const route = useRoute();
376
- const checkboxes = ref([]);
377
140
 
378
141
  const page = ref(1);
379
142
  const filters = ref([]);
380
143
  const columnsMinMax = ref({});
381
144
  const sort = ref([]);
382
- const fetchStatus = ref({pending: false, error: null, success: false});
383
145
 
384
146
  const rows = ref(null);
385
- const totalRows = ref(0);
386
- const allCheckedActions = ref([]);
387
- const allowedActions = ref({});
147
+ const totalRows = ref(0);\
148
+ const checkboxes = ref([]);
388
149
 
389
150
  const DEFAULT_PAGE_SIZE = 10;
390
151
 
391
- const columnsListed = computed(() => coreStore.resourceColumns?.filter(c => c.showIn.includes('list')));
392
152
 
393
- async function selectAll(value) {
394
- console.log('select all');
395
- rows.value.forEach((r) => {
396
- if (!checkboxes.value.includes(r.id)) {
397
- checkboxes.value.push(r.id)
398
- } else {
399
- checkboxes.value = checkboxes.value.filter((item) => item !== r.id)
400
- }
401
- });
402
- // checkboxes.value = rows.value.map((v) => v.id);
403
- }
153
+ const pageSize = computed(() => coreStore.resource?.options?.listPageSize || DEFAULT_PAGE_SIZE);
404
154
 
405
- const pageSize = computed(() => coreStore.resourceById[route.params.resourceId]?.options?.listPageSize || DEFAULT_PAGE_SIZE);
406
- const totalPages = computed(() => Math.ceil(totalRows.value / pageSize.value));
407
- let listComponentsPerColumn = {};
408
- const allFromThisPageChecked = computed(() => {
409
- if (!rows.value) return false;
410
- return rows.value.every((r) => checkboxes.value.includes(r.id));
411
- });
412
- const ascArr = computed(() => sort.value.filter((s) => s.direction === 'asc').map((s) => s.field));
413
- const descArr = computed(() => sort.value.filter((s) => s.direction === 'desc').map((s) => s.field));
414
155
 
415
156
  watch([page], async () => {
416
- await init();
157
+ await getList();
417
158
  });
418
159
 
419
160
  watch([filters], async () => {
@@ -422,28 +163,12 @@ watch([filters], async () => {
422
163
  }, {deep: true});
423
164
 
424
165
  watch([sort], async () => {
425
- await init();
166
+ await getList();
426
167
  }, {deep: true});
427
168
 
428
- function onSortButtonClick(field) {
429
- if (fetchStatus.value.pending) return;
430
- const sortIndex = sort.value.findIndex((s) => s.field === field);
431
- console.log('sortIndex', sortIndex);
432
- if (sortIndex === -1) {
433
- sort.value = [{field, direction: 'asc'}, ...sort.value];
434
- } else {
435
- const sortField = sort.value[sortIndex];
436
- if (sortField.direction === 'asc') {
437
- sort.value[sortIndex].direction = 'desc';
438
- } else {
439
- sort.value.splice(sortIndex, 1);
440
- }
441
- }
442
- }
443
169
 
444
170
  async function getList() {
445
171
  rows.value = null;
446
- fetchStatus.value.pending = true;
447
172
  const data = await callAdminForthApi({
448
173
  path: '/get_resource_data',
449
174
  method: 'POST',
@@ -456,24 +181,12 @@ async function getList() {
456
181
  sort: sort.value,
457
182
  }
458
183
  });
459
- listComponentsPerColumn = coreStore.resourceColumns.reduce((acc, column) => {
460
- if (column.components?.list) {
461
- acc[column.name] = getCustomComponent(column.components.list);
462
- }
463
- return acc;
464
- }, {});
465
-
466
- fetchStatus.value.pending = false;
184
+
467
185
  rows.value = data.data?.map(row => {
468
186
  row._primaryKeyValue = row[coreStore.resourceColumns.find(c => c.primaryKey).name];
469
187
  return row;
470
188
  });
471
189
  totalRows.value = data.total;
472
- allCheckedActions.value = data.options?.bulkActions || [];
473
- allowedActions.value = data.options?.allowedActions;
474
-
475
-
476
-
477
190
  }
478
191
 
479
192
 
@@ -525,13 +238,7 @@ async function startBulkAction(actionId) {
525
238
  await getList();
526
239
  }
527
240
 
528
- function addToCheckedValues(id) {
529
- if (checkboxes.value.includes(id)) {
530
- checkboxes.value = checkboxes.value.filter((item) => item !== id);
531
- } else {
532
- checkboxes.value.push(id);
533
- }
534
- }
241
+
535
242
 
536
243
  async function init() {
537
244
 
@@ -3,6 +3,7 @@
3
3
  <component
4
4
  v-for="c in coreStore?.resourceOptions?.pageInjections?.show?.beforeBreadcrumbs || []"
5
5
  :is="getCustomComponent(c)"
6
+ :meta="c.meta"
6
7
  :record="coreStore.record"
7
8
  :resource="coreStore.resource"
8
9
  :adminUser="coreStore.adminUser"
@@ -26,6 +27,7 @@
26
27
  <component
27
28
  v-for="c in coreStore?.resourceOptions?.pageInjections?.show?.afterBreadcrumbs || []"
28
29
  :is="getCustomComponent(c)"
30
+ :meta="c.meta"
29
31
  :record="coreStore.record"
30
32
  :resource="coreStore.resource"
31
33
  :adminUser="coreStore.adminUser"
@@ -60,8 +62,9 @@
60
62
  class="bg-form-view-bg odd:dark:bg-gray-900 even:dark:bg-gray-800 border-b dark:border-gray-700"
61
63
  >
62
64
  <component
63
- v-if="showRowComponentsPerColumn[column.name]"
64
- :is="showRowComponentsPerColumn[column.name]"
65
+ v-if="column.components?.showRow"
66
+ :is="getCustomComponent(column.components.showRow)"
67
+ :meta="column.components.showRow.meta"
65
68
  :column="column"
66
69
  :resource="coreStore.resource"
67
70
  :record="coreStore.record"
@@ -75,6 +78,7 @@
75
78
  v-if="showComponentsPerColumn[column.name]"
76
79
  :is="showComponentsPerColumn[column.name]"
77
80
  :resource="coreStore.resource"
81
+ :meta="column.components.show.meta"
78
82
  :column="column"
79
83
  :record="coreStore.record"
80
84
  />
@@ -91,6 +95,7 @@
91
95
  <component
92
96
  v-for="c in coreStore?.resourceOptions?.pageInjections?.show?.bottom || []"
93
97
  :is="getCustomComponent(c)"
98
+ :meta="c.meta"
94
99
  :column="column"
95
100
  :record="coreStore.record"
96
101
  :resource="coreStore.resource"
@@ -110,7 +115,7 @@ import BreadcrumbsWithButtons from '@/components/BreadcrumbsWithButtons.vue';
110
115
  import ValueRenderer from '@/components/ValueRenderer.vue';
111
116
  import { useCoreStore } from '@/stores/core';
112
117
  import { useModalStore } from '@/stores/modal';
113
- import { getCustomComponent,checkAcessByAllowedActions } from '@/utils';
118
+ import { getCustomComponent, checkAcessByAllowedActions } from '@/utils';
114
119
  import { IconPenSolid, IconTrashBinSolid } from '@iconify-prerendered/vue-flowbite';
115
120
  import { onMounted, ref } from 'vue';
116
121
  import { useRoute,useRouter } from 'vue-router';
@@ -122,7 +127,6 @@ const route = useRoute();
122
127
  const router = useRouter();
123
128
  const loading = ref(false);
124
129
  let showComponentsPerColumn = {};
125
- let showRowComponentsPerColumn = {};
126
130
 
127
131
  console.log(route.params,'showWiev');
128
132
 
@@ -147,13 +151,6 @@ onMounted(async () => {
147
151
  }
148
152
  return acc;
149
153
  }, {});
150
- showRowComponentsPerColumn = coreStore.resourceColumns.reduce((acc, column) => {
151
- if (column.components?.showRow) {
152
- acc[column.name] = getCustomComponent(column.components.showRow);
153
- }
154
- return acc;
155
- }, {});
156
-
157
154
  loading.value = false;
158
155
  });
159
156
 
package/index.ts CHANGED
@@ -9,7 +9,8 @@ import ExpressServer from './servers/express.js';
9
9
  import {v1 as uuid} from 'uuid';
10
10
  import fs from 'fs';
11
11
  import { ADMINFORTH_VERSION } from './modules/utils.js';
12
- import { AdminForthConfig, AdminForthClass, AdminForthFilterOperators, AdminForthDataTypes, AdminForthResourcePages } from './types/AdminForthConfig.js';
12
+ import { AdminForthConfig, AdminForthClass, AdminForthComponentDeclaration,
13
+ AdminForthFilterOperators, AdminForthDataTypes, AdminForthResourcePages } from './types/AdminForthConfig.js';
13
14
  import { getFunctionList } from './modules/utils.js';
14
15
  import path from 'path';
15
16
 
@@ -77,6 +78,20 @@ class AdminForth implements AdminForthClass {
77
78
  return [];
78
79
  }
79
80
 
81
+ validateComponent(component: AdminForthComponentDeclaration, errors: Array<string>): AdminForthComponentDeclaration {
82
+ if (!component) {
83
+ return component;
84
+ }
85
+ let obj: AdminForthComponentDeclaration = component;
86
+ if (typeof obj === 'string') {
87
+ obj = { file: component, meta: {} } as AdminForthComponentDeclaration;
88
+ }
89
+ errors.push(...this.checkCustomFileExists(this.config.auth.loginBackgroundImage));
90
+
91
+ return obj;
92
+ }
93
+
94
+
80
95
  validateConfig() {
81
96
  const errors = [];
82
97
 
@@ -135,9 +150,6 @@ class AdminForth implements AdminForthClass {
135
150
  if (this.config.customization.brandLogo) {
136
151
  errors.push(...this.checkCustomFileExists(this.config.customization.brandLogo));
137
152
  }
138
-
139
-
140
-
141
153
 
142
154
  if (!this.config.customization.datesFormat) {
143
155
  this.config.customization.datesFormat = 'MMM D, YYYY HH:mm:ss';
@@ -245,11 +257,12 @@ class AdminForth implements AdminForthClass {
245
257
  Object.entries(res.options.pageInjections).map(([key, value]) => {
246
258
  Object.entries(value).map(([injection, target]) => {
247
259
  if (possibleInjections.includes(injection)) {
248
- if (typeof target === 'string') {
260
+ if (!Array.isArray(res.options.pageInjections[key][injection])) {
261
+ // not array
249
262
  res.options.pageInjections[key][injection] = [target];
250
263
  }
251
- res.options.pageInjections[key][injection].forEach((target) => {
252
- errors.push(...this.checkCustomFileExists(target));
264
+ res.options.pageInjections[key][injection].forEach((target, i) => {
265
+ res.options.pageInjections[key][injection][i] = this.validateComponent(target, errors);
253
266
  });
254
267
  } else {
255
268
  errors.push(`Resource "${res.resourceId}" has invalid pageInjection key "${injection}", Supported keys are ${possibleInjections.join(', ')}`);
@@ -351,13 +364,18 @@ class AdminForth implements AdminForthClass {
351
364
  for (const resource of this.config.resources) {
352
365
  for (const column of resource.columns) {
353
366
  if (column.components) {
354
- for (const [key, value] of Object.entries(column.components)) {
355
- if (this.codeInjector.allComponentNames[value]) {
367
+ Object.entries(column.components).forEach(([key, comp]) => {
368
+ column.components[key] = this.validateComponent(comp, errors);
369
+ });
370
+ console.log('🔧🔧🔧 Validating components for resource', column.components);
371
+
372
+ for (const [key, { file }] of Object.entries(column.components as {any})) {
373
+ if (this.codeInjector.allComponentNames[file]) {
356
374
  // not obvious, but if we are in this if, it means that this is plugin component
357
375
  // and there is no sense to check if it exists in users folder
358
376
  continue;
359
377
  }
360
- const path = value.replace('@@', this.config.customization.customComponentsDir);
378
+ const path = file.replace('@@', this.config.customization.customComponentsDir);
361
379
  if (!fs.existsSync(path)) {
362
380
  throw new Error(`Component file ${path} does not exist`);
363
381
  }
@@ -589,7 +607,7 @@ class AdminForth implements AdminForthClass {
589
607
 
590
608
  server.endpoint({
591
609
  method: 'POST',
592
- path: '/get_resource_columns',
610
+ path: '/get_resource',
593
611
  handler: async ({ body }) => {
594
612
  const { resourceId } = body;
595
613
  if (!this.statuses.dbDiscover) {
@@ -10,7 +10,7 @@ import AdminForth from '../index.js';
10
10
  import { ADMIN_FORTH_ABSOLUTE_PATH } from './utils.js';
11
11
  import { getComponentNameFromPath } from './utils.js';
12
12
  import { styles } from './styles.js'
13
- import { CodeInjectorType } from '../types/AdminForthConfig.js';
13
+ import { AdminForthComponentDeclaration, CodeInjectorType } from '../types/AdminForthConfig.js';
14
14
 
15
15
 
16
16
 
@@ -179,8 +179,8 @@ class CodeInjector implements CodeInjectorType {
179
179
  const src = path.join(ADMIN_FORTH_ABSOLUTE_PATH, 'spa', file);
180
180
  const dest = path.join(CodeInjector.SPA_TMP_PATH, file);
181
181
 
182
+ // overwrite:true can't be used to not destroy cache
182
183
  await fsExtra.copy(src, dest, {
183
- overwrite: true,
184
184
  dereference: true, // needed to dereference types
185
185
  });
186
186
  if (process.env.HEAVY_DEBUG) {
@@ -200,15 +200,15 @@ class CodeInjector implements CodeInjectorType {
200
200
  // ignore
201
201
  }
202
202
 
203
+ // overwrite can't be used to not destroy cache
203
204
  await fsExtra.copy(path.join(ADMIN_FORTH_ABSOLUTE_PATH, 'spa'), CodeInjector.SPA_TMP_PATH, {
204
205
  filter: (src) => {
205
206
  if (process.env.HEAVY_DEBUG) {
206
207
  console.log('🪲 await fsExtra.copy filtering', src);
207
208
  }
208
209
 
209
- return !src.includes('/adminforth/spa/node_modules') && !src.includes('/adminforth/spa/dist');
210
+ return !src.includes('/adminforth/spa/node_modules') && !src.includes('/adminforth/spa/dist')
210
211
  },
211
- overwrite: true,
212
212
  dereference: true, // needed to dereference types
213
213
  });
214
214
 
@@ -264,18 +264,18 @@ class CodeInjector implements CodeInjectorType {
264
264
  this.adminforth.config.resources.forEach((resource) => {
265
265
  resource.columns.forEach((field) => {
266
266
  if (field.components) {
267
- Object.values(field.components).forEach((filePath) => {
268
- if (!customResourceComponents.includes(filePath)) {
269
- customResourceComponents.push(filePath);
267
+ Object.values(field.components).forEach(({ file }: {file: string}) => {
268
+ if (!customResourceComponents.includes(file)) {
269
+ customResourceComponents.push(file);
270
270
  }
271
271
  });
272
272
  }
273
273
  });
274
274
  (Object.values(resource.options?.pageInjections || {})).forEach((injection) => {
275
- Object.values(injection).forEach((filePathes: string[]) => {
276
- filePathes.forEach((filePath) => {
277
- if (!customResourceComponents.includes(filePath)) {
278
- customResourceComponents.push(filePath);
275
+ Object.values(injection).forEach((filePathes: {file: string}[]) => {
276
+ filePathes.forEach(({ file }) => {
277
+ if (!customResourceComponents.includes(file)) {
278
+ customResourceComponents.push(file);
279
279
  }
280
280
  });
281
281
  });