adminforth 2.26.0-next.29 → 2.26.0-next.30
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.
- package/dist/spa/package-lock.json +44 -0
- package/dist/spa/package.json +1 -0
- package/dist/spa/pnpm-lock.yaml +20 -0
- package/dist/spa/src/adminforth.ts +17 -29
- package/dist/spa/src/afcl/Input.vue +1 -1
- package/dist/spa/src/afcl/Select.vue +4 -2
- package/dist/spa/src/afcl/Table.vue +1 -1
- package/dist/spa/src/components/ColumnValueInputWrapper.vue +11 -3
- package/dist/spa/src/components/ListActionsThreeDots.vue +9 -8
- package/dist/spa/src/components/ResourceListTable.vue +14 -14
- package/dist/spa/src/components/Sidebar.vue +1 -1
- package/dist/spa/src/components/ThreeDotsMenu.vue +8 -7
- package/dist/spa/src/i18n.ts +1 -1
- package/dist/spa/src/stores/core.ts +4 -2
- package/dist/spa/src/types/Back.ts +2 -2
- package/dist/spa/src/types/Common.ts +25 -5
- package/dist/spa/src/types/FrontendAPI.ts +6 -1
- package/dist/spa/src/utils/utils.ts +9 -1
- package/dist/spa/src/views/CreateView.vue +8 -8
- package/dist/spa/src/views/EditView.vue +8 -7
- package/dist/spa/src/views/ListView.vue +10 -10
- package/dist/spa/src/views/LoginView.vue +13 -13
- package/dist/spa/src/views/ShowView.vue +6 -6
- package/dist/types/Back.d.ts +2 -2
- package/dist/types/Back.d.ts.map +1 -1
- package/dist/types/Common.d.ts +20 -5
- package/dist/types/Common.d.ts.map +1 -1
- package/dist/types/Common.js.map +1 -1
- package/dist/types/FrontendAPI.d.ts +13 -1
- package/dist/types/FrontendAPI.d.ts.map +1 -1
- package/dist/types/FrontendAPI.js.map +1 -1
- package/package.json +1 -1
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
"@tailwindcss/typography": "^0.5.19",
|
|
35
35
|
"@tsconfig/node20": "^20.1.4",
|
|
36
36
|
"@types/node": "^20.12.5",
|
|
37
|
+
"@types/sanitize-html": "^2.16.1",
|
|
37
38
|
"@vitejs/plugin-vue": "^5.0.4",
|
|
38
39
|
"@vue/eslint-config-typescript": "^13.0.0",
|
|
39
40
|
"@vue/tsconfig": "^0.5.1",
|
|
@@ -1642,6 +1643,49 @@
|
|
|
1642
1643
|
"integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==",
|
|
1643
1644
|
"license": "MIT"
|
|
1644
1645
|
},
|
|
1646
|
+
"node_modules/@types/sanitize-html": {
|
|
1647
|
+
"version": "2.16.1",
|
|
1648
|
+
"resolved": "https://registry.npmjs.org/@types/sanitize-html/-/sanitize-html-2.16.1.tgz",
|
|
1649
|
+
"integrity": "sha512-n9wjs8bCOTyN/ynwD8s/nTcTreIHB1vf31vhLMGqUPNHaweKC4/fAl4Dj+hUlCTKYgm4P3k83fmiFfzkZ6sgMA==",
|
|
1650
|
+
"dev": true,
|
|
1651
|
+
"license": "MIT",
|
|
1652
|
+
"dependencies": {
|
|
1653
|
+
"htmlparser2": "^10.1"
|
|
1654
|
+
}
|
|
1655
|
+
},
|
|
1656
|
+
"node_modules/@types/sanitize-html/node_modules/entities": {
|
|
1657
|
+
"version": "7.0.1",
|
|
1658
|
+
"resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz",
|
|
1659
|
+
"integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==",
|
|
1660
|
+
"dev": true,
|
|
1661
|
+
"license": "BSD-2-Clause",
|
|
1662
|
+
"engines": {
|
|
1663
|
+
"node": ">=0.12"
|
|
1664
|
+
},
|
|
1665
|
+
"funding": {
|
|
1666
|
+
"url": "https://github.com/fb55/entities?sponsor=1"
|
|
1667
|
+
}
|
|
1668
|
+
},
|
|
1669
|
+
"node_modules/@types/sanitize-html/node_modules/htmlparser2": {
|
|
1670
|
+
"version": "10.1.0",
|
|
1671
|
+
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.1.0.tgz",
|
|
1672
|
+
"integrity": "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==",
|
|
1673
|
+
"dev": true,
|
|
1674
|
+
"funding": [
|
|
1675
|
+
"https://github.com/fb55/htmlparser2?sponsor=1",
|
|
1676
|
+
{
|
|
1677
|
+
"type": "github",
|
|
1678
|
+
"url": "https://github.com/sponsors/fb55"
|
|
1679
|
+
}
|
|
1680
|
+
],
|
|
1681
|
+
"license": "MIT",
|
|
1682
|
+
"dependencies": {
|
|
1683
|
+
"domelementtype": "^2.3.0",
|
|
1684
|
+
"domhandler": "^5.0.3",
|
|
1685
|
+
"domutils": "^3.2.2",
|
|
1686
|
+
"entities": "^7.0.1"
|
|
1687
|
+
}
|
|
1688
|
+
},
|
|
1645
1689
|
"node_modules/@types/web-bluetooth": {
|
|
1646
1690
|
"version": "0.0.20",
|
|
1647
1691
|
"resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz",
|
package/dist/spa/package.json
CHANGED
package/dist/spa/pnpm-lock.yaml
CHANGED
|
@@ -81,6 +81,9 @@ importers:
|
|
|
81
81
|
'@types/node':
|
|
82
82
|
specifier: ^20.12.5
|
|
83
83
|
version: 20.19.37
|
|
84
|
+
'@types/sanitize-html':
|
|
85
|
+
specifier: ^2.16.1
|
|
86
|
+
version: 2.16.1
|
|
84
87
|
'@vitejs/plugin-vue':
|
|
85
88
|
specifier: ^5.0.4
|
|
86
89
|
version: 5.2.4(vite@5.4.21(@types/node@20.19.37)(sass@1.97.3))(vue@3.5.29(typescript@5.4.5))
|
|
@@ -677,6 +680,9 @@ packages:
|
|
|
677
680
|
'@types/resolve@1.20.2':
|
|
678
681
|
resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==}
|
|
679
682
|
|
|
683
|
+
'@types/sanitize-html@2.16.1':
|
|
684
|
+
resolution: {integrity: sha512-n9wjs8bCOTyN/ynwD8s/nTcTreIHB1vf31vhLMGqUPNHaweKC4/fAl4Dj+hUlCTKYgm4P3k83fmiFfzkZ6sgMA==}
|
|
685
|
+
|
|
680
686
|
'@types/web-bluetooth@0.0.14':
|
|
681
687
|
resolution: {integrity: sha512-5d2RhCard1nQUC3aHcq/gHzWYO6K0WJmAbjO7mQJgCQKtZpgXxv1rOM6O/dBDhDYYVutk1sciOgNSe+5YyfM8A==}
|
|
682
688
|
|
|
@@ -1273,6 +1279,9 @@ packages:
|
|
|
1273
1279
|
hookable@5.5.3:
|
|
1274
1280
|
resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==}
|
|
1275
1281
|
|
|
1282
|
+
htmlparser2@10.1.0:
|
|
1283
|
+
resolution: {integrity: sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==}
|
|
1284
|
+
|
|
1276
1285
|
htmlparser2@8.0.2:
|
|
1277
1286
|
resolution: {integrity: sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==}
|
|
1278
1287
|
|
|
@@ -2265,6 +2274,10 @@ snapshots:
|
|
|
2265
2274
|
|
|
2266
2275
|
'@types/resolve@1.20.2': {}
|
|
2267
2276
|
|
|
2277
|
+
'@types/sanitize-html@2.16.1':
|
|
2278
|
+
dependencies:
|
|
2279
|
+
htmlparser2: 10.1.0
|
|
2280
|
+
|
|
2268
2281
|
'@types/web-bluetooth@0.0.14': {}
|
|
2269
2282
|
|
|
2270
2283
|
'@types/web-bluetooth@0.0.20': {}
|
|
@@ -2972,6 +2985,13 @@ snapshots:
|
|
|
2972
2985
|
|
|
2973
2986
|
hookable@5.5.3: {}
|
|
2974
2987
|
|
|
2988
|
+
htmlparser2@10.1.0:
|
|
2989
|
+
dependencies:
|
|
2990
|
+
domelementtype: 2.3.0
|
|
2991
|
+
domhandler: 5.0.3
|
|
2992
|
+
domutils: 3.2.2
|
|
2993
|
+
entities: 7.0.1
|
|
2994
|
+
|
|
2975
2995
|
htmlparser2@8.0.2:
|
|
2976
2996
|
dependencies:
|
|
2977
2997
|
domelementtype: 2.3.0
|
|
@@ -19,26 +19,13 @@ class FrontendAPI implements FrontendAPIInterface {
|
|
|
19
19
|
public modalStore:any
|
|
20
20
|
public filtersStore:any
|
|
21
21
|
public coreStore:any
|
|
22
|
-
private saveInterceptors: Record<string, Array<
|
|
23
|
-
|
|
24
|
-
public list: {
|
|
25
|
-
refresh(): Promise<{ error? : string }>;
|
|
26
|
-
silentRefresh(): Promise<{ error? : string }>;
|
|
27
|
-
silentRefreshRow(pk: any): Promise<{ error? : string }>;
|
|
28
|
-
closeThreeDotsDropdown(): Promise<{ error? : string }>;
|
|
29
|
-
closeUserMenuDropdown: () => void;
|
|
30
|
-
setFilter: (filter: FilterParams) => void;
|
|
31
|
-
updateFilter: (filter: FilterParams) => void;
|
|
32
|
-
clearFilters: () => void;
|
|
33
|
-
}
|
|
22
|
+
private saveInterceptors: Record<string, Array<Parameters<FrontendAPIInterface['registerSaveInterceptor']>[0]>> = {};
|
|
34
23
|
|
|
35
|
-
public
|
|
36
|
-
refreshMenuBadges: () => void;
|
|
37
|
-
}
|
|
24
|
+
public list: FrontendAPIInterface['list'];
|
|
38
25
|
|
|
39
|
-
public
|
|
40
|
-
|
|
41
|
-
|
|
26
|
+
public menu: FrontendAPIInterface['menu'];
|
|
27
|
+
|
|
28
|
+
public show: FrontendAPIInterface['show'];
|
|
42
29
|
|
|
43
30
|
closeUserMenuDropdown(): void {
|
|
44
31
|
console.log('closeUserMenuDropdown')
|
|
@@ -70,9 +57,6 @@ class FrontendAPI implements FrontendAPIInterface {
|
|
|
70
57
|
console.log('closeThreeDotsDropdown')
|
|
71
58
|
return { error: 'Not implemented' }
|
|
72
59
|
},
|
|
73
|
-
closeUserMenuDropdown: () => {
|
|
74
|
-
console.log('closeUserMenuDropdown')
|
|
75
|
-
},
|
|
76
60
|
setFilter: this.setListFilter.bind(this),
|
|
77
61
|
updateFilter: this.updateListFilter.bind(this),
|
|
78
62
|
clearFilters: this.clearListFilters.bind(this),
|
|
@@ -83,11 +67,15 @@ class FrontendAPI implements FrontendAPIInterface {
|
|
|
83
67
|
console.log('show.refresh')
|
|
84
68
|
}
|
|
85
69
|
}
|
|
70
|
+
|
|
71
|
+
this.closeUserMenuDropdown = () => {
|
|
72
|
+
console.log('closeUserMenuDropdown')
|
|
73
|
+
};
|
|
86
74
|
}
|
|
87
75
|
|
|
88
76
|
registerSaveInterceptor(
|
|
89
|
-
handler:
|
|
90
|
-
):
|
|
77
|
+
handler: Parameters<FrontendAPIInterface['registerSaveInterceptor']>[0]
|
|
78
|
+
): ReturnType<FrontendAPIInterface['registerSaveInterceptor']> {
|
|
91
79
|
const rid = router.currentRoute.value?.params?.resourceId as string;
|
|
92
80
|
if (!rid) {
|
|
93
81
|
return;
|
|
@@ -98,7 +86,7 @@ class FrontendAPI implements FrontendAPIInterface {
|
|
|
98
86
|
this.saveInterceptors[rid].push(handler);
|
|
99
87
|
}
|
|
100
88
|
|
|
101
|
-
async runSaveInterceptors(params:
|
|
89
|
+
async runSaveInterceptors(params: Parameters<FrontendAPIInterface['runSaveInterceptors']>[0]): ReturnType<FrontendAPIInterface['runSaveInterceptors']> {
|
|
102
90
|
const list = this.saveInterceptors[params.resourceId] || [];
|
|
103
91
|
const aggregatedExtra: Record<string, any> = {};
|
|
104
92
|
for (const fn of list) {
|
|
@@ -120,7 +108,7 @@ class FrontendAPI implements FrontendAPIInterface {
|
|
|
120
108
|
return { ok: true, extra: aggregatedExtra };
|
|
121
109
|
}
|
|
122
110
|
|
|
123
|
-
clearSaveInterceptors(resourceId?:
|
|
111
|
+
clearSaveInterceptors(resourceId?: Parameters<FrontendAPIInterface['clearSaveInterceptors']>[0]): ReturnType<FrontendAPIInterface['clearSaveInterceptors']> {
|
|
124
112
|
if (resourceId) {
|
|
125
113
|
delete this.saveInterceptors[resourceId];
|
|
126
114
|
} else {
|
|
@@ -128,7 +116,7 @@ class FrontendAPI implements FrontendAPIInterface {
|
|
|
128
116
|
}
|
|
129
117
|
}
|
|
130
118
|
|
|
131
|
-
confirm(params:
|
|
119
|
+
confirm(params: Parameters<FrontendAPIInterface['confirm']>[0]): ReturnType<FrontendAPIInterface['confirm']> {
|
|
132
120
|
return new Promise((resolve, reject) => {
|
|
133
121
|
this.modalStore.setModalContent({
|
|
134
122
|
content: params.message,
|
|
@@ -142,7 +130,7 @@ class FrontendAPI implements FrontendAPIInterface {
|
|
|
142
130
|
})
|
|
143
131
|
}
|
|
144
132
|
|
|
145
|
-
alert(params:
|
|
133
|
+
alert(params: Parameters<FrontendAPIInterface['alert']>[0]): ReturnType<FrontendAPIInterface['alert']> {
|
|
146
134
|
const toats = {
|
|
147
135
|
message: params.message,
|
|
148
136
|
messageHtml: params.messageHtml,
|
|
@@ -162,14 +150,14 @@ class FrontendAPI implements FrontendAPIInterface {
|
|
|
162
150
|
}
|
|
163
151
|
}
|
|
164
152
|
|
|
165
|
-
listFilterValidation(filter:
|
|
153
|
+
listFilterValidation(filter: Parameters<FrontendAPIInterface['list']['setFilter']>[0]): boolean {
|
|
166
154
|
if(router.currentRoute.value.meta.type !== 'list'){
|
|
167
155
|
throw new Error(`Cannot use ${this.setListFilter.name} filter on a list page`)
|
|
168
156
|
}
|
|
169
157
|
return true
|
|
170
158
|
}
|
|
171
159
|
|
|
172
|
-
setListFilter(filter:
|
|
160
|
+
setListFilter(filter: Parameters<FrontendAPIInterface['list']['setFilter']>[0]): ReturnType<FrontendAPIInterface['list']['setFilter']> {
|
|
173
161
|
if(this.listFilterValidation(filter)){
|
|
174
162
|
const existingFilterIndex = this.filtersStore.filters.findIndex((f: any) => {
|
|
175
163
|
return f.field === filter.field && f.operator === filter.operator
|
|
@@ -119,10 +119,12 @@ import { ref, computed, onMounted, onUnmounted, watch, nextTick,type PropType, t
|
|
|
119
119
|
import { IconCaretDownSolid } from '@iconify-prerendered/vue-flowbite';
|
|
120
120
|
import { useElementSize } from '@vueuse/core'
|
|
121
121
|
|
|
122
|
+
type ISingleSelectModelValue = string | number;
|
|
123
|
+
|
|
122
124
|
const props = defineProps({
|
|
123
125
|
options: Array,
|
|
124
126
|
modelValue: {
|
|
125
|
-
type: Array as PropType<(
|
|
127
|
+
type: Array as PropType<(ISingleSelectModelValue)[] | ISingleSelectModelValue>,
|
|
126
128
|
default: () => [],
|
|
127
129
|
},
|
|
128
130
|
multiple: {
|
|
@@ -201,7 +203,7 @@ function updateFromProps() {
|
|
|
201
203
|
selectedItems.value = [];
|
|
202
204
|
}
|
|
203
205
|
} else {
|
|
204
|
-
selectedItems.value = props.options?.filter((item: any) => props.modelValue?.includes(item.value)) || [];
|
|
206
|
+
selectedItems.value = props.options?.filter((item: any) => (props.modelValue as (ISingleSelectModelValue)[])?.includes(item.value)) || [];
|
|
205
207
|
}
|
|
206
208
|
}
|
|
207
209
|
}
|
|
@@ -16,11 +16,11 @@
|
|
|
16
16
|
:unmasked="unmasked"
|
|
17
17
|
:deletable="!column.editReadonly"
|
|
18
18
|
@update:modelValue="setCurrentValue(column.name, $event, arrayItemIndex)"
|
|
19
|
-
@update:recordFieldValue="
|
|
19
|
+
@update:recordFieldValue="recordFieldValueUpdate"
|
|
20
20
|
@update:unmasked="$emit('update:unmasked', column.name)"
|
|
21
21
|
@update:inValidity="$emit('update:inValidity', { name: column.name, value: $event })"
|
|
22
22
|
@update:emptiness="$emit('update:emptiness', { name: column.name, value: $event })"
|
|
23
|
-
@delete="
|
|
23
|
+
@delete="deleteHandler(arrayItemIndex)"
|
|
24
24
|
/>
|
|
25
25
|
</div>
|
|
26
26
|
<div class="flex items-center">
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
:columnOptions="columnOptions"
|
|
49
49
|
:unmasked="unmasked"
|
|
50
50
|
@update:modelValue="setCurrentValue(column.name, $event)"
|
|
51
|
-
@update:recordFieldValue="
|
|
51
|
+
@update:recordFieldValue="recordFieldValueUpdate"
|
|
52
52
|
@update:unmasked="$emit('update:unmasked', column.name)"
|
|
53
53
|
@update:inValidity="$emit('update:inValidity', { name: column.name, value: $event })"
|
|
54
54
|
@update:emptiness="$emit('update:emptiness', { name: column.name, value: $event })"
|
|
@@ -80,4 +80,12 @@
|
|
|
80
80
|
await nextTick();
|
|
81
81
|
arrayItemRefs.value[arrayItemRefs.value.length - 1].focus();
|
|
82
82
|
}
|
|
83
|
+
|
|
84
|
+
function recordFieldValueUpdate({ fieldName, fieldValue }: { fieldName: string; fieldValue: any }) {
|
|
85
|
+
props.setCurrentValue(fieldName, fieldValue);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function deleteHandler(arrayItemIndex: number | string) {
|
|
89
|
+
props.setCurrentValue(props.column.name, props.currentValues[props.column.name].filter((_: any, index: any) => index !== arrayItemIndex));
|
|
90
|
+
}
|
|
83
91
|
</script>
|
|
@@ -57,14 +57,14 @@
|
|
|
57
57
|
{{ $t('Delete item') }}
|
|
58
58
|
</button>
|
|
59
59
|
</template>
|
|
60
|
-
<div v-for="action in (resourceOptions.actions ?? []).filter(a => a.showIn?.listThreeDotsMenu)" :key="action.id" >
|
|
60
|
+
<div v-for="action in (resourceOptions.actions ?? []).filter((a: AdminForthActionInput) => a.showIn?.listThreeDotsMenu)" :key="action.id" >
|
|
61
61
|
<button class="flex text-nowrap p-1 hover:bg-gray-100 dark:hover:bg-gray-800 w-full text-left px-4 py-2 text-sm text-gray-700 dark:text-gray-300" @click="() => { startCustomAction(action.id, record); showMenu = false; }">
|
|
62
62
|
<component
|
|
63
63
|
:is="action.customComponent ? getCustomComponent(action.customComponent) : CallActionWrapper"
|
|
64
64
|
:meta="action.customComponent?.meta"
|
|
65
65
|
:row="record"
|
|
66
|
-
:resource="resource"
|
|
67
|
-
:adminUser="adminUser"
|
|
66
|
+
:resource="coreStore.resource"
|
|
67
|
+
:adminUser="coreStore.adminUser"
|
|
68
68
|
@callAction="(payload? : Object) => startCustomAction(action.id, record, payload)"
|
|
69
69
|
>
|
|
70
70
|
<component
|
|
@@ -79,8 +79,8 @@
|
|
|
79
79
|
<template v-if="customActionIconsThreeDotsMenuItems">
|
|
80
80
|
<component
|
|
81
81
|
v-for="c in customActionIconsThreeDotsMenuItems"
|
|
82
|
-
:is="getCustomComponent(c)"
|
|
83
|
-
:meta="c.meta"
|
|
82
|
+
:is="getCustomComponent(formatComponent(c))"
|
|
83
|
+
:meta="formatComponent(c).meta"
|
|
84
84
|
:resource="coreStore.resource"
|
|
85
85
|
:adminUser="coreStore.adminUser"
|
|
86
86
|
:record="record"
|
|
@@ -100,9 +100,10 @@ import {
|
|
|
100
100
|
IconDotsHorizontalOutline
|
|
101
101
|
} from '@iconify-prerendered/vue-flowbite';
|
|
102
102
|
import { onMounted, onBeforeUnmount, ref, nextTick, watch } from 'vue';
|
|
103
|
-
import { getIcon, getCustomComponent } from '@/utils';
|
|
103
|
+
import { getIcon, getCustomComponent, formatComponent } from '@/utils';
|
|
104
104
|
import { useCoreStore } from '@/stores/core';
|
|
105
105
|
import CallActionWrapper from '@/components/CallActionWrapper.vue'
|
|
106
|
+
import { type AdminForthActionInput, type AdminForthComponentDeclaration, type AdminForthComponentDeclarationFull } from '@/types/Common';
|
|
106
107
|
|
|
107
108
|
const coreStore = useCoreStore();
|
|
108
109
|
const showMenu = ref(false);
|
|
@@ -113,11 +114,11 @@ const menuStyles = ref<Record<string, string>>({});
|
|
|
113
114
|
const props = defineProps<{
|
|
114
115
|
resourceOptions: any;
|
|
115
116
|
record: any;
|
|
116
|
-
customActionIconsThreeDotsMenuItems:
|
|
117
|
+
customActionIconsThreeDotsMenuItems: AdminForthComponentDeclaration[];
|
|
117
118
|
resourceId: string;
|
|
118
119
|
deleteRecord: (record: any) => void;
|
|
119
120
|
updateRecords: () => void;
|
|
120
|
-
startCustomAction: (actionId: string,
|
|
121
|
+
startCustomAction: (actionId: string, row: any, extraData?: Record<string, any>) => void;
|
|
121
122
|
}>();
|
|
122
123
|
|
|
123
124
|
onMounted(() => {
|
|
@@ -103,12 +103,12 @@
|
|
|
103
103
|
|
|
104
104
|
<component
|
|
105
105
|
v-for="(row, rowI) in rowsToRender"
|
|
106
|
-
:is="tableRowReplaceInjection ? getCustomComponent(tableRowReplaceInjection) : 'tr'"
|
|
106
|
+
:is="tableRowReplaceInjection ? getCustomComponent(formatComponent(tableRowReplaceInjection)) : 'tr'"
|
|
107
107
|
:key="`row_${row._primaryKeyValue}`"
|
|
108
108
|
:record="row"
|
|
109
109
|
:resource="resource"
|
|
110
110
|
:adminUser="coreStore.adminUser"
|
|
111
|
-
:meta="tableRowReplaceInjection ? tableRowReplaceInjection.meta : undefined"
|
|
111
|
+
:meta="tableRowReplaceInjection ? formatComponent(tableRowReplaceInjection).meta : undefined"
|
|
112
112
|
@click="onClick($event, row)"
|
|
113
113
|
ref="rowRefs"
|
|
114
114
|
class="list-table-body-row bg-lightListTable dark:bg-darkListTable border-lightListBorder dark:border-gray-700 hover:bg-lightListTableRowHover dark:hover:bg-darkListTableRowHover"
|
|
@@ -203,17 +203,17 @@
|
|
|
203
203
|
:key="action.id"
|
|
204
204
|
>
|
|
205
205
|
<component
|
|
206
|
-
|
|
207
|
-
:
|
|
206
|
+
v-if="action.customComponent"
|
|
207
|
+
:is="action.customComponent ? getCustomComponent(formatComponent(action.customComponent)) : CallActionWrapper"
|
|
208
|
+
:meta="formatComponent(action.customComponent).meta"
|
|
208
209
|
:row="row"
|
|
209
210
|
:resource="resource"
|
|
210
|
-
:adminUser="adminUser"
|
|
211
|
-
@callAction="(payload? : Object) => startCustomAction(action.id, row, payload)"
|
|
211
|
+
:adminUser="coreStore.adminUser"
|
|
212
|
+
@callAction="(payload? : Object) => startCustomAction(action.id as string | number, row, payload)"
|
|
212
213
|
>
|
|
213
214
|
<button
|
|
214
215
|
type="button"
|
|
215
216
|
class="border border-gray-300 dark:border-gray-700 dark:border-opacity-0 border-opacity-0 hover:border-opacity-100 dark:hover:border-opacity-100 rounded-md hover:bg-gray-100 dark:hover:bg-gray-800 cursor-pointer"
|
|
216
|
-
:disabled="rowActionLoadingStates?.[action.id]"
|
|
217
217
|
>
|
|
218
218
|
<component
|
|
219
219
|
v-if="action.icon"
|
|
@@ -236,7 +236,7 @@
|
|
|
236
236
|
:deleteRecord="deleteRecord"
|
|
237
237
|
:resourceId="resource.resourceId"
|
|
238
238
|
:startCustomAction="startCustomAction"
|
|
239
|
-
:customActionIconsThreeDotsMenuItems="customActionIconsThreeDotsMenuItems"
|
|
239
|
+
:customActionIconsThreeDotsMenuItems="customActionIconsThreeDotsMenuItems ?? []"
|
|
240
240
|
/>
|
|
241
241
|
</div>
|
|
242
242
|
|
|
@@ -347,7 +347,7 @@ import { computed, onMounted, ref, watch, useTemplateRef, nextTick, type Ref } f
|
|
|
347
347
|
import { callAdminForthApi } from '@/utils';
|
|
348
348
|
import { useI18n } from 'vue-i18n';
|
|
349
349
|
import ValueRenderer from '@/components/ValueRenderer.vue';
|
|
350
|
-
import { getCustomComponent } from '@/utils';
|
|
350
|
+
import { getCustomComponent, formatComponent } from '@/utils';
|
|
351
351
|
import { useCoreStore } from '@/stores/core';
|
|
352
352
|
import { showSuccesTost, showErrorTost } from '@/composables/useFrontendApi';
|
|
353
353
|
import SkeleteLoader from '@/components/SkeleteLoader.vue';
|
|
@@ -360,7 +360,7 @@ import {
|
|
|
360
360
|
} from '@iconify-prerendered/vue-flowbite';
|
|
361
361
|
import router from '@/router';
|
|
362
362
|
import { Tooltip } from '@/afcl';
|
|
363
|
-
import type { AdminForthResourceCommon, AdminForthResourceColumnCommon, AdminForthComponentDeclarationFull } from '@/types/Common';
|
|
363
|
+
import type { AdminForthResourceCommon, AdminForthResourceColumnCommon, AdminForthComponentDeclarationFull, AdminForthComponentDeclaration } from '@/types/Common';
|
|
364
364
|
import { useAdminforth } from '@/adminforth';
|
|
365
365
|
import Checkbox from '@/afcl/Checkbox.vue';
|
|
366
366
|
import ListActionsThreeDots from '@/components/ListActionsThreeDots.vue';
|
|
@@ -383,8 +383,8 @@ const props = defineProps<{
|
|
|
383
383
|
containerHeight?: number,
|
|
384
384
|
itemHeight?: number,
|
|
385
385
|
bufferSize?: number,
|
|
386
|
-
customActionIconsThreeDotsMenuItems?:
|
|
387
|
-
tableRowReplaceInjection?:
|
|
386
|
+
customActionIconsThreeDotsMenuItems?: AdminForthComponentDeclaration[]
|
|
387
|
+
tableRowReplaceInjection?: AdminForthComponentDeclaration,
|
|
388
388
|
isVirtualScrollEnabled: boolean
|
|
389
389
|
}>();
|
|
390
390
|
|
|
@@ -414,7 +414,7 @@ const sort: Ref<Array<{field: string, direction: string}>> = ref([]);
|
|
|
414
414
|
const showListActionsThreeDots = computed(() => {
|
|
415
415
|
return props.resource?.options?.actions?.some(a => a.showIn?.listThreeDotsMenu) // show if any action is set to show in three dots menu
|
|
416
416
|
|| (props.customActionIconsThreeDotsMenuItems && props.customActionIconsThreeDotsMenuItems.length > 0) // or if there are custom action icons for three dots menu
|
|
417
|
-
|| !props.resource?.options
|
|
417
|
+
|| !props.resource?.options?.baseActionsAsQuickIcons // or if there is no baseActionsAsQuickIcons
|
|
418
418
|
|| (props.resource?.options.baseActionsAsQuickIcons && props.resource?.options.baseActionsAsQuickIcons.length < 3) // if there all 3 base actions are shown as quick icons - hide three dots icon
|
|
419
419
|
})
|
|
420
420
|
|
|
@@ -609,7 +609,7 @@ async function deleteRecord(row: any) {
|
|
|
609
609
|
|
|
610
610
|
const actionLoadingStates = ref<Record<string | number, boolean>>({});
|
|
611
611
|
|
|
612
|
-
async function startCustomAction(actionId: string, row: any, extraData: Record<string, any> = {}) {
|
|
612
|
+
async function startCustomAction(actionId: string | number, row: any, extraData: Record<string, any> = {}) {
|
|
613
613
|
|
|
614
614
|
actionLoadingStates.value[actionId] = true;
|
|
615
615
|
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
</div>
|
|
53
53
|
</div>
|
|
54
54
|
|
|
55
|
-
<div v-if="coreStore
|
|
55
|
+
<div v-if="coreStore?.config?.defaultUserExists && !isLocalhost" class="p-4 mb-4 text-white rounded-lg bg-red-700/80 fill-white text-sm">
|
|
56
56
|
<IconExclamationCircleOutline class="inline-block align-text-bottom mr-0,5 w-5 h-5" />
|
|
57
57
|
Default user <strong>"adminforth"</strong> detected. Delete it and create your own account.
|
|
58
58
|
</div>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template >
|
|
2
|
-
<div class="relative" v-if="threeDotsDropdownItems?.length || customActions?.length || (bulkActions?.some((action:
|
|
2
|
+
<div class="relative" v-if="threeDotsDropdownItems?.length || customActions?.length || (bulkActions?.some((action: AdminForthBulkActionFront) => action.showInThreeDotsDropdown))">
|
|
3
3
|
<button
|
|
4
4
|
ref="buttonTriggerRef"
|
|
5
5
|
@click="toggleDropdownVisibility"
|
|
@@ -46,8 +46,9 @@
|
|
|
46
46
|
<li v-for="action in customActions" :key="action.id">
|
|
47
47
|
<div class="wrapper">
|
|
48
48
|
<component
|
|
49
|
-
|
|
50
|
-
:
|
|
49
|
+
v-if="action.customComponent"
|
|
50
|
+
:is="(action.customComponent && getCustomComponent(formatComponent(action.customComponent))) || CallActionWrapper"
|
|
51
|
+
:meta="formatComponent(action.customComponent).meta"
|
|
51
52
|
@callAction="(payload? : Object) => handleActionClick(action, payload)"
|
|
52
53
|
>
|
|
53
54
|
<a @click.prevent class="block px-4 py-2 hover:text-lightThreeDotsMenuBodyTextHover hover:bg-lightThreeDotsMenuBodyBackgroundHover dark:hover:bg-darkThreeDotsMenuBodyBackgroundHover dark:hover:text-darkThreeDotsMenuBodyTextHover">
|
|
@@ -88,14 +89,14 @@
|
|
|
88
89
|
|
|
89
90
|
|
|
90
91
|
<script setup lang="ts">
|
|
91
|
-
import { getCustomComponent, getIcon } from '@/utils';
|
|
92
|
+
import { getCustomComponent, getIcon, formatComponent } from '@/utils';
|
|
92
93
|
import { useCoreStore } from '@/stores/core';
|
|
93
94
|
import { useAdminforth } from '@/adminforth';
|
|
94
95
|
import { callAdminForthApi } from '@/utils';
|
|
95
96
|
import { useRoute, useRouter } from 'vue-router';
|
|
96
97
|
import CallActionWrapper from '@/components/CallActionWrapper.vue'
|
|
97
98
|
import { ref, type ComponentPublicInstance, onMounted, onUnmounted } from 'vue';
|
|
98
|
-
import type {
|
|
99
|
+
import type { AdminForthActionFront, AdminForthBulkActionFront, AdminForthComponentDeclarationFull } from '@/types/Common';
|
|
99
100
|
import type { AdminForthActionInput } from '@/types/Back';
|
|
100
101
|
|
|
101
102
|
const { list, alert} = useAdminforth();
|
|
@@ -109,8 +110,8 @@ const buttonTriggerRef = ref<HTMLElement | null>(null);
|
|
|
109
110
|
|
|
110
111
|
const props = defineProps({
|
|
111
112
|
threeDotsDropdownItems: Array<AdminForthComponentDeclarationFull>,
|
|
112
|
-
customActions: Array<
|
|
113
|
-
bulkActions: Array<
|
|
113
|
+
customActions: Array<AdminForthActionFront>,
|
|
114
|
+
bulkActions: Array<AdminForthBulkActionFront>,
|
|
114
115
|
checkboxes: Array,
|
|
115
116
|
updateList: {
|
|
116
117
|
type: Function,
|
package/dist/spa/src/i18n.ts
CHANGED
|
@@ -4,9 +4,11 @@ import { callAdminForthApi } from '@/utils';
|
|
|
4
4
|
import websocket from '@/websocket';
|
|
5
5
|
import { useAdminforth } from '@/adminforth';
|
|
6
6
|
|
|
7
|
-
import type { AdminForthResourceCommon, AdminForthResourceColumnCommon, GetBaseConfigResponse, ResourceVeryShort, AdminUser, UserData, AdminForthConfigMenuItem, AdminForthConfigForFrontend } from '@/types/Common';
|
|
7
|
+
import type { AdminForthResourceCommon, AdminForthResourceColumnCommon, GetBaseConfigResponse, ResourceVeryShort, AdminUser, UserData, AdminForthConfigMenuItem, AdminForthConfigForFrontend, AdminForthResourceFrontend } from '@/types/Common';
|
|
8
8
|
import type { Ref } from 'vue'
|
|
9
9
|
|
|
10
|
+
|
|
11
|
+
|
|
10
12
|
export const useCoreStore = defineStore('core', () => {
|
|
11
13
|
const { alert } = useAdminforth();
|
|
12
14
|
const resourceById: Ref<Record<string, ResourceVeryShort>> = ref({});
|
|
@@ -15,7 +17,7 @@ export const useCoreStore = defineStore('core', () => {
|
|
|
15
17
|
const menu: Ref<AdminForthConfigMenuItem[]> = ref([]);
|
|
16
18
|
const config: Ref<AdminForthConfigForFrontend | null> = ref(null);
|
|
17
19
|
const record: Ref<any | null> = ref({});
|
|
18
|
-
const resource: Ref<
|
|
20
|
+
const resource: Ref<AdminForthResourceFrontend | null> = ref(null);
|
|
19
21
|
const userData: Ref<UserData | null> = ref(null);
|
|
20
22
|
const isResourceFetching = ref(false);
|
|
21
23
|
const isInternetError = ref(false);
|
|
@@ -1641,7 +1641,7 @@ export interface AdminForthConfigCustomization extends Omit<AdminForthInputConfi
|
|
|
1641
1641
|
|
|
1642
1642
|
loginPageInjections: {
|
|
1643
1643
|
underInputs: Array<AdminForthComponentDeclarationFull>,
|
|
1644
|
-
underLoginButton
|
|
1644
|
+
underLoginButton: Array<AdminForthComponentDeclarationFull>,
|
|
1645
1645
|
panelHeader: Array<AdminForthComponentDeclarationFull>,
|
|
1646
1646
|
},
|
|
1647
1647
|
|
|
@@ -1829,7 +1829,7 @@ export type AllowedActions = {
|
|
|
1829
1829
|
/**
|
|
1830
1830
|
* General options for resource.
|
|
1831
1831
|
*/
|
|
1832
|
-
export interface ResourceOptionsInput extends Omit<NonNullable<AdminForthResourceInputCommon['options']>, 'allowedActions' | 'bulkActions'> {
|
|
1832
|
+
export interface ResourceOptionsInput extends Omit<NonNullable<AdminForthResourceInputCommon['options']>, 'allowedActions' | 'bulkActions' | 'actions'> {
|
|
1833
1833
|
|
|
1834
1834
|
/**
|
|
1835
1835
|
* Custom bulk actions list. Bulk actions available in list view when user selects multiple records by
|
|
@@ -314,6 +314,25 @@ export type FieldGroup = {
|
|
|
314
314
|
noTitle?: boolean;
|
|
315
315
|
};
|
|
316
316
|
|
|
317
|
+
export interface AdminForthActionFront extends Omit<AdminForthActionInput, 'id'> {
|
|
318
|
+
id: string;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
export interface AdminForthBulkActionFront extends Omit<AdminForthBulkActionCommon, 'id'> {
|
|
322
|
+
id: string,
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
type AdminforthOptionsCommon = NonNullable<AdminForthResourceCommon['options']>;
|
|
326
|
+
|
|
327
|
+
export interface AdminForthOptionsForFrontend extends Omit<AdminforthOptionsCommon, 'actions' | 'bulkActions'> {
|
|
328
|
+
actions?: AdminForthActionFront[],
|
|
329
|
+
bulkActions?: AdminForthBulkActionFront[],
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
export interface AdminForthResourceFrontend extends Omit<AdminForthResourceCommon, 'options'> {
|
|
333
|
+
options: AdminForthOptionsForFrontend;
|
|
334
|
+
}
|
|
335
|
+
|
|
317
336
|
/**
|
|
318
337
|
* Resource describes one table or collection in database.
|
|
319
338
|
* AdminForth generates set of pages for 'list', 'show', 'edit', 'create', 'filter' operations for each resource.
|
|
@@ -361,17 +380,17 @@ export interface AdminForthResourceInputCommon {
|
|
|
361
380
|
recordLabel?: (item: any) => string,
|
|
362
381
|
|
|
363
382
|
|
|
364
|
-
/**
|
|
365
|
-
* If true, user will not see warning about unsaved changes when tries to leave edit or create page with unsaved changes.
|
|
366
|
-
* default is false
|
|
367
|
-
*/
|
|
368
|
-
dontShowWarningAboutUnsavedChanges?: boolean,
|
|
369
383
|
|
|
370
384
|
/**
|
|
371
385
|
* General options for resource.
|
|
372
386
|
*/
|
|
373
387
|
options?: {
|
|
374
388
|
|
|
389
|
+
/**
|
|
390
|
+
* If true, user will not see warning about unsaved changes when tries to leave edit or create page with unsaved changes.
|
|
391
|
+
* default is false
|
|
392
|
+
*/
|
|
393
|
+
dontShowWarningAboutUnsavedChanges?: boolean,
|
|
375
394
|
|
|
376
395
|
/**
|
|
377
396
|
* Show quick action icons for base actions (show, edit, delete) in list view.
|
|
@@ -1172,6 +1191,7 @@ export interface AdminForthConfigForFrontend {
|
|
|
1172
1191
|
loginPageInjections: {
|
|
1173
1192
|
underInputs: Array<AdminForthComponentDeclaration>,
|
|
1174
1193
|
panelHeader: Array<AdminForthComponentDeclaration>,
|
|
1194
|
+
underLoginButton: Array<AdminForthComponentDeclaration>,
|
|
1175
1195
|
},
|
|
1176
1196
|
rememberMeDuration: string,
|
|
1177
1197
|
showBrandNameInSidebar: boolean,
|
|
@@ -144,7 +144,7 @@ export interface FrontendAPIInterface {
|
|
|
144
144
|
/**
|
|
145
145
|
* Run save interceptors for a specific resource or all resources if no resourceId is provided
|
|
146
146
|
*/
|
|
147
|
-
runSaveInterceptors(params: { action: 'create'|'edit'; values: any; resource: any; resourceId: string; }): Promise<{ ok: boolean; error?: string | null; extra?:
|
|
147
|
+
runSaveInterceptors(params: { action: 'create'|'edit'; values: any; resource: any; resourceId: string; }): Promise<{ ok: boolean; error?: string | null; extra?: any; }>;
|
|
148
148
|
|
|
149
149
|
/**
|
|
150
150
|
* Clear save interceptors for a specific resource or all resources if no resourceId is provided
|
|
@@ -152,6 +152,11 @@ export interface FrontendAPIInterface {
|
|
|
152
152
|
* @param resourceId - The resource ID to clear interceptors for
|
|
153
153
|
*/
|
|
154
154
|
clearSaveInterceptors(resourceId?: string): void;
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Register a save interceptor for a specific resource
|
|
158
|
+
*/
|
|
159
|
+
registerSaveInterceptor(handler: (ctx: { action: 'create'|'edit'; values: any; resource: any; }) => Promise<{ ok: boolean; error?: string | null; extra?: any; }>): void;
|
|
155
160
|
}
|
|
156
161
|
|
|
157
162
|
export type ConfirmParams = {
|