adminforth 2.17.0-next.3 → 2.17.0-next.31
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/commands/createCustomComponent/main.js +0 -3
- package/commands/createCustomComponent/templates/customCrud/afterBreadcrumbs.vue.hbs +4 -2
- package/commands/createCustomComponent/templates/customCrud/beforeActionButtons.vue.hbs +3 -2
- package/commands/createCustomComponent/templates/customCrud/beforeBreadcrumbs.vue.hbs +4 -2
- package/commands/createCustomComponent/templates/customCrud/bottom.vue.hbs +4 -2
- package/commands/createCustomComponent/templates/customCrud/threeDotsDropdownItems.vue.hbs +4 -2
- package/commands/createPlugin/templates/index.ts.hbs +4 -0
- package/dist/auth.d.ts +1 -1
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +1 -2
- package/dist/auth.js.map +1 -1
- package/dist/basePlugin.d.ts +1 -0
- package/dist/basePlugin.d.ts.map +1 -1
- package/dist/basePlugin.js +3 -0
- package/dist/basePlugin.js.map +1 -1
- package/dist/dataConnectors/baseConnector.d.ts +1 -0
- package/dist/dataConnectors/baseConnector.d.ts.map +1 -1
- package/dist/dataConnectors/baseConnector.js +92 -6
- package/dist/dataConnectors/baseConnector.js.map +1 -1
- package/dist/dataConnectors/clickhouse.d.ts +2 -0
- package/dist/dataConnectors/clickhouse.d.ts.map +1 -1
- package/dist/dataConnectors/clickhouse.js +11 -1
- package/dist/dataConnectors/clickhouse.js.map +1 -1
- package/dist/dataConnectors/mongo.d.ts +8 -1
- package/dist/dataConnectors/mongo.d.ts.map +1 -1
- package/dist/dataConnectors/mongo.js +67 -24
- package/dist/dataConnectors/mongo.js.map +1 -1
- package/dist/dataConnectors/mysql.d.ts +2 -0
- package/dist/dataConnectors/mysql.d.ts.map +1 -1
- package/dist/dataConnectors/mysql.js +12 -2
- package/dist/dataConnectors/mysql.js.map +1 -1
- package/dist/dataConnectors/postgres.d.ts +2 -0
- package/dist/dataConnectors/postgres.d.ts.map +1 -1
- package/dist/dataConnectors/postgres.js +12 -2
- package/dist/dataConnectors/postgres.js.map +1 -1
- package/dist/dataConnectors/sqlite.d.ts +2 -0
- package/dist/dataConnectors/sqlite.d.ts.map +1 -1
- package/dist/dataConnectors/sqlite.js +12 -2
- package/dist/dataConnectors/sqlite.js.map +1 -1
- package/dist/modules/configValidator.d.ts.map +1 -1
- package/dist/modules/configValidator.js +9 -7
- package/dist/modules/configValidator.js.map +1 -1
- package/dist/modules/restApi.d.ts.map +1 -1
- package/dist/modules/restApi.js +3 -3
- package/dist/modules/restApi.js.map +1 -1
- package/dist/spa/src/App.vue +6 -3
- package/dist/spa/src/adminforth.ts +60 -1
- package/dist/spa/src/afcl/Dropzone.vue +6 -4
- package/dist/spa/src/afcl/Tooltip.vue +38 -4
- package/dist/spa/src/components/ColumnValueInput.vue +14 -1
- package/dist/spa/src/components/CustomRangePicker.vue +9 -4
- package/dist/spa/src/components/Filters.vue +4 -4
- package/dist/spa/src/components/ListActionsThreeDots.vue +235 -0
- package/dist/spa/src/components/ResourceForm.vue +5 -5
- package/dist/spa/src/components/ResourceListTable.vue +30 -16
- package/dist/spa/src/components/ResourceListTableVirtual.vue +34 -18
- package/dist/spa/src/components/ShowTable.vue +2 -2
- package/dist/spa/src/components/Sidebar.vue +4 -2
- package/dist/spa/src/components/ThreeDotsMenu.vue +6 -6
- package/dist/spa/src/composables/useFrontendApi.ts +8 -4
- package/dist/spa/src/renderers/CompactField.vue +3 -2
- package/dist/spa/src/renderers/CompactUUID.vue +3 -2
- package/dist/spa/src/renderers/RichText.vue +15 -0
- package/dist/spa/src/stores/core.ts +3 -2
- package/dist/spa/src/stores/filters.ts +1 -1
- package/dist/spa/src/types/Back.ts +22 -5
- package/dist/spa/src/types/Common.ts +3 -10
- package/dist/spa/src/types/FrontendAPI.ts +25 -10
- package/dist/spa/src/utils.ts +8 -3
- package/dist/spa/src/views/CreateView.vue +24 -32
- package/dist/spa/src/views/EditView.vue +27 -31
- package/dist/spa/src/views/ListView.vue +20 -10
- package/dist/spa/src/views/SettingsView.vue +3 -2
- package/dist/spa/src/views/ShowView.vue +7 -6
- package/dist/types/Back.d.ts +15 -4
- package/dist/types/Back.d.ts.map +1 -1
- package/dist/types/Back.js +6 -0
- package/dist/types/Back.js.map +1 -1
- package/dist/types/Common.d.ts +4 -11
- package/dist/types/Common.d.ts.map +1 -1
- package/dist/types/Common.js +2 -0
- package/dist/types/Common.js.map +1 -1
- package/dist/types/FrontendAPI.d.ts +32 -10
- package/dist/types/FrontendAPI.d.ts.map +1 -1
- package/dist/types/FrontendAPI.js.map +1 -1
- package/package.json +1 -1
- package/commands/createCustomComponent/templates/customCrud/saveButton.vue.hbs +0 -28
|
@@ -1,17 +1,21 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { useAdminforth } from '@/adminforth';
|
|
2
|
+
|
|
2
3
|
|
|
3
4
|
export function showSuccesTost(message: string) {
|
|
4
|
-
|
|
5
|
+
const { alert } = useAdminforth();
|
|
6
|
+
alert({ message, variant: 'success' });
|
|
5
7
|
return message;
|
|
6
8
|
}
|
|
7
9
|
|
|
8
10
|
export function showWarningTost(message: string) {
|
|
9
|
-
|
|
11
|
+
const { alert } = useAdminforth();
|
|
12
|
+
alert({ message, variant: 'warning' });
|
|
10
13
|
return message;
|
|
11
14
|
}
|
|
12
15
|
|
|
13
16
|
export function showErrorTost(message: string, timeout?: number) {
|
|
14
|
-
|
|
17
|
+
const { alert } = useAdminforth();
|
|
18
|
+
alert({ message, variant: 'danger', timeout: timeout || 30});
|
|
15
19
|
return message;
|
|
16
20
|
}
|
|
17
21
|
|
|
@@ -15,10 +15,11 @@
|
|
|
15
15
|
import { computed, ref, onMounted, nextTick } from 'vue';
|
|
16
16
|
import { IconFileCopyAltSolid } from '@iconify-prerendered/vue-flowbite';
|
|
17
17
|
import Tooltip from '@/afcl/Tooltip.vue';
|
|
18
|
-
import
|
|
18
|
+
import { useAdminforth } from '@/adminforth';
|
|
19
19
|
import { useI18n } from 'vue-i18n';
|
|
20
20
|
|
|
21
21
|
const { t } = useI18n();
|
|
22
|
+
const { alert } = useAdminforth();
|
|
22
23
|
const visualValue = computed(() => {
|
|
23
24
|
// if lenght is more then 8, show only first 4 and last 4 characters, ... in the middle
|
|
24
25
|
const val = props.record[props.column.name];
|
|
@@ -34,7 +35,7 @@ const id = ref();
|
|
|
34
35
|
|
|
35
36
|
function copyToCB() {
|
|
36
37
|
navigator.clipboard.writeText(props.record[props.column.name]);
|
|
37
|
-
|
|
38
|
+
alert({
|
|
38
39
|
message: t('ID copied to clipboard'),
|
|
39
40
|
variant: 'success',
|
|
40
41
|
})
|
|
@@ -15,10 +15,11 @@
|
|
|
15
15
|
import { computed, ref, onMounted, nextTick } from 'vue';
|
|
16
16
|
import { IconFileCopyAltSolid } from '@iconify-prerendered/vue-flowbite';
|
|
17
17
|
import Tooltip from '@/afcl/Tooltip.vue';
|
|
18
|
-
import
|
|
18
|
+
import { useAdminforth } from '@/adminforth';
|
|
19
19
|
import { useI18n } from 'vue-i18n';
|
|
20
20
|
|
|
21
21
|
const { t } = useI18n();
|
|
22
|
+
const { alert } = useAdminforth();
|
|
22
23
|
const visualValue = computed(() => {
|
|
23
24
|
// if lenght is more then 8, show only first 4 and last 4 characters, ... in the middle
|
|
24
25
|
const val = props.record[props.column.name];
|
|
@@ -34,7 +35,7 @@ const id = ref();
|
|
|
34
35
|
|
|
35
36
|
function copyToCB() {
|
|
36
37
|
navigator.clipboard.writeText(props.record[props.column.name]);
|
|
37
|
-
|
|
38
|
+
alert({
|
|
38
39
|
message: t('ID copied to clipboard'),
|
|
39
40
|
variant: 'success',
|
|
40
41
|
})
|
|
@@ -22,4 +22,19 @@ const htmlContent = protectAgainstXSS(props.record[props.column.name])
|
|
|
22
22
|
/* You can add default styles here if needed */
|
|
23
23
|
word-break: break-word;
|
|
24
24
|
}
|
|
25
|
+
.rich-text :deep(table) {
|
|
26
|
+
border-collapse: collapse;
|
|
27
|
+
border: 1px solid #ddd;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.rich-text :deep(table th),
|
|
31
|
+
.rich-text :deep(table td) {
|
|
32
|
+
border: 1px solid #ddd;
|
|
33
|
+
padding: 8px;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.rich-text :deep(table th) {
|
|
37
|
+
background-color: #f5f5f5;
|
|
38
|
+
font-weight: 600;
|
|
39
|
+
}
|
|
25
40
|
</style>
|
|
@@ -2,12 +2,13 @@ import { ref, computed } from 'vue'
|
|
|
2
2
|
import { defineStore } from 'pinia'
|
|
3
3
|
import { callAdminForthApi } from '@/utils';
|
|
4
4
|
import websocket from '@/websocket';
|
|
5
|
-
import
|
|
5
|
+
import { useAdminforth } from '@/adminforth';
|
|
6
6
|
|
|
7
7
|
import type { AdminForthResourceCommon, AdminForthResourceColumnCommon, GetBaseConfigResponse, ResourceVeryShort, AdminUser, UserData, AdminForthConfigMenuItem, AdminForthConfigForFrontend } from '@/types/Common';
|
|
8
8
|
import type { Ref } from 'vue'
|
|
9
9
|
|
|
10
10
|
export const useCoreStore = defineStore('core', () => {
|
|
11
|
+
const { alert } = useAdminforth();
|
|
11
12
|
const resourceById: Ref<Record<string, ResourceVeryShort>> = ref({});
|
|
12
13
|
const theme: Ref<'light'| 'dark'> = ref(window.localStorage.getItem('af__theme') as ('light'|'dark') || 'light');
|
|
13
14
|
|
|
@@ -157,7 +158,7 @@ export const useCoreStore = defineStore('core', () => {
|
|
|
157
158
|
});
|
|
158
159
|
|
|
159
160
|
if (respData.error) {
|
|
160
|
-
|
|
161
|
+
alert({
|
|
161
162
|
message: respData.error,
|
|
162
163
|
variant: 'danger',
|
|
163
164
|
timeout: 30,
|
|
@@ -15,7 +15,7 @@ export const useFiltersStore = defineStore('filters', () => {
|
|
|
15
15
|
}
|
|
16
16
|
const setFilter = (filter: any) => {
|
|
17
17
|
const index = filters.value.findIndex(f => f.field === filter.field);
|
|
18
|
-
if (filters.value[index]) {
|
|
18
|
+
if (filters.value[index] && filters.value[index].operator === filter.value.operator) {
|
|
19
19
|
filters.value[index] = filter;
|
|
20
20
|
return;
|
|
21
21
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Express, Request } from 'express';
|
|
2
2
|
import type { Writable } from 'stream';
|
|
3
3
|
|
|
4
|
-
import { ActionCheckSource, AdminForthFilterOperators, AdminForthSortDirections, AllowedActionsEnum,
|
|
4
|
+
import { ActionCheckSource, AdminForthFilterOperators, AdminForthSortDirections, AllowedActionsEnum, AdminForthResourcePages,
|
|
5
5
|
type AdminForthComponentDeclaration,
|
|
6
6
|
type AdminForthResourceCommon,
|
|
7
7
|
type AdminUser, type AllowedActionsResolved,
|
|
@@ -12,7 +12,6 @@ import { ActionCheckSource, AdminForthFilterOperators, AdminForthSortDirections,
|
|
|
12
12
|
type AdminForthComponentDeclarationFull,
|
|
13
13
|
type AdminForthConfigMenuItem,
|
|
14
14
|
type AnnouncementBadgeResponse,
|
|
15
|
-
AdminForthResourcePages,
|
|
16
15
|
type AdminForthResourceColumnInputCommon,
|
|
17
16
|
} from './Common.js';
|
|
18
17
|
|
|
@@ -129,7 +128,7 @@ export interface IAdminForthSingleFilter {
|
|
|
129
128
|
operator?: AdminForthFilterOperators.EQ | AdminForthFilterOperators.NE
|
|
130
129
|
| AdminForthFilterOperators.GT | AdminForthFilterOperators.LT | AdminForthFilterOperators.GTE
|
|
131
130
|
| AdminForthFilterOperators.LTE | AdminForthFilterOperators.LIKE | AdminForthFilterOperators.ILIKE
|
|
132
|
-
| AdminForthFilterOperators.IN | AdminForthFilterOperators.NIN;
|
|
131
|
+
| AdminForthFilterOperators.IN | AdminForthFilterOperators.NIN | AdminForthFilterOperators.IS_EMPTY | AdminForthFilterOperators.IS_NOT_EMPTY;
|
|
133
132
|
value?: any;
|
|
134
133
|
rightField?: string;
|
|
135
134
|
insecureRawSQL?: string;
|
|
@@ -307,7 +306,7 @@ export interface IAdminForthAuth {
|
|
|
307
306
|
|
|
308
307
|
removeCustomCookie({response, name}: {response: any, name: string}): void;
|
|
309
308
|
|
|
310
|
-
setCustomCookie({response, payload}: {response: any, payload: {name: string, value: string, expiry
|
|
309
|
+
setCustomCookie({response, payload}: {response: any, payload: {name: string, value: string, expiry?: number, expirySeconds: number, httpOnly: boolean}}): void;
|
|
311
310
|
|
|
312
311
|
getCustomCookie({cookies, name}: {cookies: {key: string, value: string}[], name: string}): string | null;
|
|
313
312
|
|
|
@@ -469,6 +468,13 @@ export interface IAdminForthPlugin {
|
|
|
469
468
|
instanceUniqueRepresentation(pluginOptions: any) : string;
|
|
470
469
|
|
|
471
470
|
|
|
471
|
+
/**
|
|
472
|
+
* If this method returns true, AdminForth will allow only one instance of plugin per whole app
|
|
473
|
+
* (only for case when we are creating copy of resource and activating plugins)
|
|
474
|
+
* If false, multiple instances of plugin can be installed on different resources.
|
|
475
|
+
*/
|
|
476
|
+
shouldHaveSingleInstancePerWholeApp?(): boolean;
|
|
477
|
+
|
|
472
478
|
/**
|
|
473
479
|
* Optional method which will be called after AdminForth discovers all resources and their columns.
|
|
474
480
|
* Can be used to validate types of columns, check if some columns are missing, etc.
|
|
@@ -848,6 +854,7 @@ export interface AdminForthActionInput {
|
|
|
848
854
|
name: string;
|
|
849
855
|
showIn?: {
|
|
850
856
|
list?: boolean,
|
|
857
|
+
listThreeDotsMenu?: boolean,
|
|
851
858
|
showButton?: boolean,
|
|
852
859
|
showThreeDotsMenu?: boolean,
|
|
853
860
|
};
|
|
@@ -861,6 +868,7 @@ export interface AdminForthActionInput {
|
|
|
861
868
|
resource: AdminForthResource;
|
|
862
869
|
recordId: string;
|
|
863
870
|
adminUser: AdminUser;
|
|
871
|
+
response: IAdminForthHttpResponse;
|
|
864
872
|
extra?: HttpExtra;
|
|
865
873
|
tr: Function;
|
|
866
874
|
}) => Promise<{
|
|
@@ -1102,7 +1110,7 @@ export interface AdminForthInputConfig {
|
|
|
1102
1110
|
/**
|
|
1103
1111
|
* Add custom page to the settings page
|
|
1104
1112
|
*/
|
|
1105
|
-
userMenuSettingsPages
|
|
1113
|
+
userMenuSettingsPages?: {
|
|
1106
1114
|
icon?: string,
|
|
1107
1115
|
pageLabel: string,
|
|
1108
1116
|
slug?: string,
|
|
@@ -1282,6 +1290,14 @@ export class Filters {
|
|
|
1282
1290
|
subFilters,
|
|
1283
1291
|
};
|
|
1284
1292
|
}
|
|
1293
|
+
|
|
1294
|
+
static IS_EMPTY(field: string): IAdminForthSingleFilter {
|
|
1295
|
+
return { field, operator: AdminForthFilterOperators.IS_EMPTY, value: null };
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
static IS_NOT_EMPTY(field: string): IAdminForthSingleFilter {
|
|
1299
|
+
return { field, operator: AdminForthFilterOperators.IS_NOT_EMPTY, value: null };
|
|
1300
|
+
}
|
|
1285
1301
|
}
|
|
1286
1302
|
|
|
1287
1303
|
export type FDataSort = (field: string, direction: AdminForthSortDirections) => IAdminForthSort;
|
|
@@ -1358,6 +1374,7 @@ export type AllowedActions = {
|
|
|
1358
1374
|
*/
|
|
1359
1375
|
export interface ResourceOptionsInput extends Omit<NonNullable<AdminForthResourceInputCommon['options']>, 'allowedActions' | 'bulkActions'> {
|
|
1360
1376
|
|
|
1377
|
+
baseActionsAsQuickIcons?: ('show' | 'edit' | 'delete')[],
|
|
1361
1378
|
/**
|
|
1362
1379
|
* Custom bulk actions list. Bulk actions available in list view when user selects multiple records by
|
|
1363
1380
|
* using checkboxes.
|
|
@@ -28,6 +28,8 @@ export enum AdminForthFilterOperators {
|
|
|
28
28
|
NIN = 'nin',
|
|
29
29
|
AND = 'and',
|
|
30
30
|
OR = 'or',
|
|
31
|
+
IS_EMPTY = 'isEmpty',
|
|
32
|
+
IS_NOT_EMPTY = 'isNotEmpty',
|
|
31
33
|
};
|
|
32
34
|
|
|
33
35
|
export type FilterParams = {
|
|
@@ -503,6 +505,7 @@ export interface AdminForthResourceInputCommon {
|
|
|
503
505
|
bottom?: AdminForthComponentDeclaration | Array<AdminForthComponentDeclaration>,
|
|
504
506
|
threeDotsDropdownItems?: AdminForthComponentDeclaration | Array<AdminForthComponentDeclaration>,
|
|
505
507
|
customActionIcons?: AdminForthComponentDeclaration | Array<AdminForthComponentDeclaration>,
|
|
508
|
+
customActionIconsThreeDotsMenuItems?: AdminForthComponentDeclaration | Array<AdminForthComponentDeclaration>,
|
|
506
509
|
tableBodyStart?: AdminForthComponentDeclaration | Array<AdminForthComponentDeclaration>,
|
|
507
510
|
tableRowReplace?: AdminForthComponentDeclaration | Array<AdminForthComponentDeclaration>,
|
|
508
511
|
},
|
|
@@ -529,11 +532,6 @@ export interface AdminForthResourceInputCommon {
|
|
|
529
532
|
afterBreadcrumbs?: AdminForthComponentDeclaration | Array<AdminForthComponentDeclaration>,
|
|
530
533
|
bottom?: AdminForthComponentDeclaration | Array<AdminForthComponentDeclaration>,
|
|
531
534
|
threeDotsDropdownItems?: AdminForthComponentDeclaration | Array<AdminForthComponentDeclaration>,
|
|
532
|
-
/**
|
|
533
|
-
* Custom Save button component for Edit page.
|
|
534
|
-
* Accepts props: [record, resource, adminUser, meta, saving, validating, isValid, disabled, saveRecord]
|
|
535
|
-
*/
|
|
536
|
-
saveButton?: AdminForthComponentDeclaration,
|
|
537
535
|
},
|
|
538
536
|
|
|
539
537
|
/**
|
|
@@ -546,11 +544,6 @@ export interface AdminForthResourceInputCommon {
|
|
|
546
544
|
afterBreadcrumbs?: AdminForthComponentDeclaration | Array<AdminForthComponentDeclaration>,
|
|
547
545
|
bottom?: AdminForthComponentDeclaration | Array<AdminForthComponentDeclaration>,
|
|
548
546
|
threeDotsDropdownItems?: AdminForthComponentDeclaration | Array<AdminForthComponentDeclaration>,
|
|
549
|
-
/**
|
|
550
|
-
* Custom Save button component for Create page.
|
|
551
|
-
* Accepts props: [record, resource, adminUser, meta, saving, validating, isValid, disabled, saveRecord]
|
|
552
|
-
*/
|
|
553
|
-
saveButton?: AdminForthComponentDeclaration,
|
|
554
547
|
},
|
|
555
548
|
}
|
|
556
549
|
},
|
|
@@ -10,9 +10,10 @@ export interface FrontendAPIInterface {
|
|
|
10
10
|
* Example:
|
|
11
11
|
*
|
|
12
12
|
* ```ts
|
|
13
|
-
*
|
|
13
|
+
*import { useAdminforth } from '@/adminforth';
|
|
14
14
|
*
|
|
15
|
-
* const
|
|
15
|
+
* const { confirm } = useAdminforth();
|
|
16
|
+
* const isConfirmed = await confirm({message: 'Are you sure?', yes: 'Yes', no: 'No'})
|
|
16
17
|
* if (isConfirmed) {
|
|
17
18
|
* your code...
|
|
18
19
|
* }
|
|
@@ -31,9 +32,10 @@ export interface FrontendAPIInterface {
|
|
|
31
32
|
* Example:
|
|
32
33
|
*
|
|
33
34
|
* ```ts
|
|
34
|
-
* import
|
|
35
|
+
* import { useAdminforth } from '@/adminforth';
|
|
36
|
+
* const { alert } = useAdminforth();
|
|
35
37
|
*
|
|
36
|
-
*
|
|
38
|
+
* alert({message: 'Hello', variant: 'success'})
|
|
37
39
|
* ```
|
|
38
40
|
*
|
|
39
41
|
* @param params - The parameters of the alert
|
|
@@ -76,13 +78,14 @@ export interface FrontendAPIInterface {
|
|
|
76
78
|
* Example:
|
|
77
79
|
*
|
|
78
80
|
* ```ts
|
|
79
|
-
* import
|
|
81
|
+
* import { useAdminforth } from '@/adminforth';
|
|
80
82
|
*
|
|
83
|
+
* const { list } = useAdminforth();
|
|
81
84
|
* // Regular filter (will show in badge if column.showIn.filter !== false)
|
|
82
|
-
*
|
|
85
|
+
* list.setFilter({field: 'name', operator: 'ilike', value: 'john'})
|
|
83
86
|
*
|
|
84
87
|
* // Hidden filter (won't show in badge if column.showIn.filter === false)
|
|
85
|
-
*
|
|
88
|
+
* list.setFilter({field: 'internal_status', operator: 'eq', value: 'active'})
|
|
86
89
|
* ```
|
|
87
90
|
*
|
|
88
91
|
* Please note that you can set/update filter even for fields which have showIn.filter=false in resource configuration.
|
|
@@ -102,9 +105,9 @@ export interface FrontendAPIInterface {
|
|
|
102
105
|
* Example:
|
|
103
106
|
*
|
|
104
107
|
* ```ts
|
|
105
|
-
* import
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
+
* import { useAdminforth } from '@/adminforth';
|
|
109
|
+
* const { list } = useAdminforth();
|
|
110
|
+
* list.updateFilter({field: 'name', operator: 'ilike', value: 'john'})
|
|
108
111
|
* ```
|
|
109
112
|
*
|
|
110
113
|
* @param filter - The filter to update
|
|
@@ -136,6 +139,18 @@ export interface FrontendAPIInterface {
|
|
|
136
139
|
* Close the user menu dropdown
|
|
137
140
|
*/
|
|
138
141
|
closeUserMenuDropdown(): void;
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Run save interceptors for a specific resource or all resources if no resourceId is provided
|
|
145
|
+
*/
|
|
146
|
+
runSaveInterceptors(params: { action: 'create'|'edit'; values: any; resource: any; resourceId: string; }): Promise<{ ok: boolean; error?: string | null; extra?: object; }>;
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Clear save interceptors for a specific resource or all resources if no resourceId is provided
|
|
150
|
+
*
|
|
151
|
+
* @param resourceId - The resource ID to clear interceptors for
|
|
152
|
+
*/
|
|
153
|
+
clearSaveInterceptors(resourceId?: string): void;
|
|
139
154
|
}
|
|
140
155
|
|
|
141
156
|
export type ConfirmParams = {
|
package/dist/spa/src/utils.ts
CHANGED
|
@@ -446,9 +446,14 @@ export function createSearchInputHandlers(
|
|
|
446
446
|
}, {} as Record<string, (searchTerm: string) => void>);
|
|
447
447
|
}
|
|
448
448
|
|
|
449
|
-
export function checkShowIf(c: AdminForthResourceColumnInputCommon, record: Record<string, any
|
|
449
|
+
export function checkShowIf(c: AdminForthResourceColumnInputCommon, record: Record<string, any>, allColumns: AdminForthResourceColumnInputCommon[]) {
|
|
450
450
|
if (!c.showIf) return true;
|
|
451
|
-
|
|
451
|
+
const recordCopy = { ...record };
|
|
452
|
+
for (const col of allColumns) {
|
|
453
|
+
if (!recordCopy[col.name]) {
|
|
454
|
+
recordCopy[col.name] = null;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
452
457
|
const evaluatePredicate = (predicate: Predicate): boolean => {
|
|
453
458
|
const results: boolean[] = [];
|
|
454
459
|
|
|
@@ -463,7 +468,7 @@ export function checkShowIf(c: AdminForthResourceColumnInputCommon, record: Reco
|
|
|
463
468
|
const fieldEntries = Object.entries(predicate).filter(([key]) => !key.startsWith('$'));
|
|
464
469
|
if (fieldEntries.length > 0) {
|
|
465
470
|
const fieldResult = fieldEntries.every(([field, condition]) => {
|
|
466
|
-
const recordValue =
|
|
471
|
+
const recordValue = recordCopy[field];
|
|
467
472
|
|
|
468
473
|
if (condition === undefined) {
|
|
469
474
|
return true;
|
|
@@ -17,25 +17,7 @@
|
|
|
17
17
|
>
|
|
18
18
|
{{ $t('Cancel') }}
|
|
19
19
|
</button>
|
|
20
|
-
|
|
21
|
-
<!-- Custom Save Button injection -->
|
|
22
|
-
<component
|
|
23
|
-
v-if="createSaveButtonInjection"
|
|
24
|
-
:is="getCustomComponent(createSaveButtonInjection)"
|
|
25
|
-
:meta="createSaveButtonInjection.meta"
|
|
26
|
-
:record="record"
|
|
27
|
-
:resource="coreStore.resource"
|
|
28
|
-
:adminUser="coreStore.adminUser"
|
|
29
|
-
:saving="saving"
|
|
30
|
-
:validating="validating"
|
|
31
|
-
:isValid="isValid"
|
|
32
|
-
:disabled="saving || (validating && !isValid)"
|
|
33
|
-
:saveRecord="saveRecord"
|
|
34
|
-
/>
|
|
35
|
-
|
|
36
|
-
<!-- Default Save Button fallback -->
|
|
37
20
|
<button
|
|
38
|
-
v-else
|
|
39
21
|
@click="() => saveRecord()"
|
|
40
22
|
class="af-save-button flex items-center py-1 px-3 text-sm font-medium rounded-default text-lightCreateViewSaveButtonText focus:outline-none bg-lightCreateViewButtonBackground rounded border border-lightCreateViewButtonBorder hover:bg-lightCreateViewButtonBackgroundHover hover:text-lightCreateViewSaveButtonTextHover focus:z-10 focus:ring-4 focus:ring-lightCreateViewButtonFocusRing dark:focus:ring-darkCreateViewButtonFocusRing dark:bg-darkCreateViewButtonBackground dark:text-darkCreateViewSaveButtonText dark:border-darkCreateViewButtonBorder dark:hover:text-darkCreateViewSaveButtonTextHover dark:hover:bg-darkCreateViewButtonBackgroundHover disabled:opacity-50 gap-1"
|
|
41
23
|
:disabled="saving || (validating && !isValid)"
|
|
@@ -99,12 +81,12 @@ import SingleSkeletLoader from '@/components/SingleSkeletLoader.vue';
|
|
|
99
81
|
import { useCoreStore } from '@/stores/core';
|
|
100
82
|
import { callAdminForthApi, getCustomComponent,checkAcessByAllowedActions, initThreeDotsDropdown, checkShowIf } from '@/utils';
|
|
101
83
|
import { IconFloppyDiskSolid } from '@iconify-prerendered/vue-flowbite';
|
|
102
|
-
import { onMounted, ref, watch, nextTick } from 'vue';
|
|
84
|
+
import { onMounted, onBeforeMount, ref, watch, nextTick } from 'vue';
|
|
103
85
|
import { useRoute, useRouter } from 'vue-router';
|
|
104
86
|
import { computed } from 'vue';
|
|
105
87
|
import { showErrorTost } from '@/composables/useFrontendApi';
|
|
106
88
|
import ThreeDotsMenu from '@/components/ThreeDotsMenu.vue';
|
|
107
|
-
import
|
|
89
|
+
import { useAdminforth } from '@/adminforth';
|
|
108
90
|
import { useI18n } from 'vue-i18n';
|
|
109
91
|
import { type AdminForthComponentDeclarationFull } from '@/types/Common.js';
|
|
110
92
|
import type { AdminForthResourceColumn } from '@/types/Back';
|
|
@@ -121,18 +103,12 @@ const router = useRouter();
|
|
|
121
103
|
const record = ref({});
|
|
122
104
|
|
|
123
105
|
const coreStore = useCoreStore();
|
|
106
|
+
const { clearSaveInterceptors, runSaveInterceptors, alert } = useAdminforth();
|
|
124
107
|
|
|
125
108
|
const { t } = useI18n();
|
|
126
109
|
|
|
127
110
|
const resourceFormRef = ref<InstanceType<typeof ResourceForm> | null>(null);
|
|
128
111
|
|
|
129
|
-
const createSaveButtonInjection = computed<AdminForthComponentDeclarationFull | null>(() => {
|
|
130
|
-
const raw: any = coreStore.resourceOptions?.pageInjections?.create?.saveButton as any;
|
|
131
|
-
if (!raw) return null;
|
|
132
|
-
const item = Array.isArray(raw) ? raw[0] : raw;
|
|
133
|
-
return item as AdminForthComponentDeclarationFull;
|
|
134
|
-
});
|
|
135
|
-
|
|
136
112
|
const initialValues = ref({});
|
|
137
113
|
|
|
138
114
|
const readonlyColumns = ref([]);
|
|
@@ -142,6 +118,10 @@ async function onUpdateRecord(newRecord: any) {
|
|
|
142
118
|
record.value = newRecord;
|
|
143
119
|
}
|
|
144
120
|
|
|
121
|
+
onBeforeMount(() => {
|
|
122
|
+
clearSaveInterceptors(route.params.resourceId as string);
|
|
123
|
+
});
|
|
124
|
+
|
|
145
125
|
onMounted(async () => {
|
|
146
126
|
loading.value = true;
|
|
147
127
|
await coreStore.fetchResourceFull({
|
|
@@ -182,7 +162,7 @@ onMounted(async () => {
|
|
|
182
162
|
initThreeDotsDropdown();
|
|
183
163
|
});
|
|
184
164
|
|
|
185
|
-
async function saveRecord(
|
|
165
|
+
async function saveRecord() {
|
|
186
166
|
if (!isValid.value) {
|
|
187
167
|
validating.value = true;
|
|
188
168
|
await nextTick();
|
|
@@ -192,8 +172,20 @@ async function saveRecord(opts?: { confirmationResult?: any }) {
|
|
|
192
172
|
validating.value = false;
|
|
193
173
|
}
|
|
194
174
|
const requiredColumns = coreStore.resource?.columns.filter(c => c.required?.create === true) || [];
|
|
195
|
-
const requiredColumnsToSkip = requiredColumns.filter(c => checkShowIf(c, record.value) === false);
|
|
175
|
+
const requiredColumnsToSkip = requiredColumns.filter(c => checkShowIf(c, record.value, coreStore.resource?.columns || []) === false);
|
|
196
176
|
saving.value = true;
|
|
177
|
+
const interceptorsResult = await runSaveInterceptors({
|
|
178
|
+
action: 'create',
|
|
179
|
+
values: record.value,
|
|
180
|
+
resource: coreStore.resource,
|
|
181
|
+
resourceId: route.params.resourceId as string,
|
|
182
|
+
});
|
|
183
|
+
if (!interceptorsResult.ok) {
|
|
184
|
+
saving.value = false;
|
|
185
|
+
if (interceptorsResult.error) showErrorTost(interceptorsResult.error);
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
const interceptorConfirmationResult = (interceptorsResult.extra as Record<string, any>)?.confirmationResult;
|
|
197
189
|
const response = await callAdminForthApi({
|
|
198
190
|
method: 'POST',
|
|
199
191
|
path: `/create_record`,
|
|
@@ -202,7 +194,7 @@ async function saveRecord(opts?: { confirmationResult?: any }) {
|
|
|
202
194
|
record: record.value,
|
|
203
195
|
requiredColumnsToSkip,
|
|
204
196
|
meta: {
|
|
205
|
-
...(
|
|
197
|
+
...(interceptorConfirmationResult ? { confirmationResult: interceptorConfirmationResult } : {}),
|
|
206
198
|
},
|
|
207
199
|
},
|
|
208
200
|
});
|
|
@@ -220,7 +212,7 @@ async function saveRecord(opts?: { confirmationResult?: any }) {
|
|
|
220
212
|
primaryKey: response.newRecordId
|
|
221
213
|
}
|
|
222
214
|
});
|
|
223
|
-
|
|
215
|
+
alert({
|
|
224
216
|
message: t('Record created successfully!'),
|
|
225
217
|
variant: 'success'
|
|
226
218
|
});
|
|
@@ -236,7 +228,7 @@ function scrollToInvalidField() {
|
|
|
236
228
|
}
|
|
237
229
|
}
|
|
238
230
|
const errorMessage = t('Failed to save. Please fix errors for the following fields:') + '<ul class="mt-2 list-disc list-inside">' + columnsWithErrors.map(c => `<li><strong>${c.column.label || c.column.name}</strong>: ${c.error}</li>`).join('') + '</ul>';
|
|
239
|
-
|
|
231
|
+
alert({
|
|
240
232
|
messageHtml: errorMessage,
|
|
241
233
|
variant: 'danger'
|
|
242
234
|
});
|
|
@@ -16,25 +16,7 @@
|
|
|
16
16
|
>
|
|
17
17
|
{{ $t('Cancel') }}
|
|
18
18
|
</button>
|
|
19
|
-
|
|
20
|
-
<!-- Custom Save Button injection -->
|
|
21
|
-
<component
|
|
22
|
-
v-if="editSaveButtonInjection"
|
|
23
|
-
:is="getCustomComponent(editSaveButtonInjection)"
|
|
24
|
-
:meta="editSaveButtonInjection.meta"
|
|
25
|
-
:record="editableRecord"
|
|
26
|
-
:resource="coreStore.resource"
|
|
27
|
-
:adminUser="coreStore.adminUser"
|
|
28
|
-
:saving="saving"
|
|
29
|
-
:validating="validating"
|
|
30
|
-
:isValid="isValid"
|
|
31
|
-
:disabled="saving || (validating && !isValid)"
|
|
32
|
-
:saveRecord="saveRecord"
|
|
33
|
-
/>
|
|
34
|
-
|
|
35
|
-
<!-- Default Save Button fallback -->
|
|
36
19
|
<button
|
|
37
|
-
v-else
|
|
38
20
|
@click="() => saveRecord()"
|
|
39
21
|
class="flex items-center py-1 px-3 text-sm font-medium rounded-default text-lightEditViewSaveButtonText focus:outline-none bg-lightEditViewButtonBackground rounded border border-lightEditViewButtonBorder hover:bg-lightEditViewButtonBackgroundHover hover:text-lightEditViewSaveButtonTextHover focus:z-10 focus:ring-4 focus:ring-lightEditViewButtonFocusRing dark:focus:ring-darkEditViewButtonFocusRing dark:bg-darkEditViewButtonBackground dark:text-darkEditViewSaveButtonText dark:border-darkEditViewButtonBorder dark:hover:text-darkEditViewSaveButtonTextHover dark:hover:bg-darkEditViewButtonBackgroundHover disabled:opacity-50 gap-1"
|
|
40
22
|
:disabled="saving || (validating && !isValid)"
|
|
@@ -94,17 +76,18 @@ import SingleSkeletLoader from '@/components/SingleSkeletLoader.vue';
|
|
|
94
76
|
import { useCoreStore } from '@/stores/core';
|
|
95
77
|
import { callAdminForthApi, getCustomComponent,checkAcessByAllowedActions, initThreeDotsDropdown } from '@/utils';
|
|
96
78
|
import { IconFloppyDiskSolid } from '@iconify-prerendered/vue-flowbite';
|
|
97
|
-
import { computed, onMounted, ref, type Ref, nextTick } from 'vue';
|
|
79
|
+
import { computed, onMounted, onBeforeMount, ref, type Ref, nextTick, watch } from 'vue';
|
|
98
80
|
import { useRoute, useRouter } from 'vue-router';
|
|
99
81
|
import { showErrorTost } from '@/composables/useFrontendApi';
|
|
100
82
|
import ThreeDotsMenu from '@/components/ThreeDotsMenu.vue';
|
|
101
|
-
import
|
|
83
|
+
import { useAdminforth } from '@/adminforth';
|
|
102
84
|
import { useI18n } from 'vue-i18n';
|
|
103
85
|
import { type AdminForthComponentDeclarationFull } from '@/types/Common.js';
|
|
104
86
|
import type { AdminForthResourceColumn } from '@/types/Back';
|
|
105
87
|
|
|
106
88
|
const { t } = useI18n();
|
|
107
89
|
const coreStore = useCoreStore();
|
|
90
|
+
const { clearSaveInterceptors, runSaveInterceptors, alert } = useAdminforth();
|
|
108
91
|
|
|
109
92
|
const isValid = ref(false);
|
|
110
93
|
const validating = ref(false);
|
|
@@ -118,14 +101,11 @@ const saving = ref(false);
|
|
|
118
101
|
|
|
119
102
|
const record: Ref<Record<string, any>> = ref({});
|
|
120
103
|
|
|
121
|
-
|
|
104
|
+
watch(record, (newVal) => {
|
|
105
|
+
console.log('Record updated:', newVal);
|
|
106
|
+
}, { deep: true });
|
|
122
107
|
|
|
123
|
-
const
|
|
124
|
-
const raw: any = coreStore.resourceOptions?.pageInjections?.edit?.saveButton as any;
|
|
125
|
-
if (!raw) return null;
|
|
126
|
-
const item = Array.isArray(raw) ? raw[0] : raw;
|
|
127
|
-
return item as AdminForthComponentDeclarationFull;
|
|
128
|
-
});
|
|
108
|
+
const resourceFormRef = ref<InstanceType<typeof ResourceForm> | null>(null);
|
|
129
109
|
|
|
130
110
|
async function onUpdateRecord(newRecord: Record<string, any>) {
|
|
131
111
|
record.value = newRecord;
|
|
@@ -148,6 +128,10 @@ const editableRecord = computed(() => {
|
|
|
148
128
|
return newRecord;
|
|
149
129
|
})
|
|
150
130
|
|
|
131
|
+
onBeforeMount(() => {
|
|
132
|
+
clearSaveInterceptors(route.params.resourceId as string);
|
|
133
|
+
});
|
|
134
|
+
|
|
151
135
|
onMounted(async () => {
|
|
152
136
|
loading.value = true;
|
|
153
137
|
|
|
@@ -169,7 +153,7 @@ onMounted(async () => {
|
|
|
169
153
|
loading.value = false;
|
|
170
154
|
});
|
|
171
155
|
|
|
172
|
-
async function saveRecord(
|
|
156
|
+
async function saveRecord() {
|
|
173
157
|
if (!isValid.value) {
|
|
174
158
|
validating.value = true;
|
|
175
159
|
await nextTick();
|
|
@@ -180,6 +164,18 @@ async function saveRecord(opts?: { confirmationResult?: any }) {
|
|
|
180
164
|
}
|
|
181
165
|
|
|
182
166
|
saving.value = true;
|
|
167
|
+
const interceptorsResult = await runSaveInterceptors({
|
|
168
|
+
action: 'edit',
|
|
169
|
+
values: record.value,
|
|
170
|
+
resource: coreStore.resource,
|
|
171
|
+
resourceId: route.params.resourceId as string,
|
|
172
|
+
});
|
|
173
|
+
if (!interceptorsResult.ok) {
|
|
174
|
+
saving.value = false;
|
|
175
|
+
if (interceptorsResult.error) showErrorTost(interceptorsResult.error);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
const interceptorConfirmationResult = interceptorsResult.extra?.confirmationResult;
|
|
183
179
|
const updates: Record<string, any> = {};
|
|
184
180
|
for (const key in record.value) {
|
|
185
181
|
let columnIsUpdated = false;
|
|
@@ -212,14 +208,14 @@ async function saveRecord(opts?: { confirmationResult?: any }) {
|
|
|
212
208
|
recordId: route.params.primaryKey,
|
|
213
209
|
record: updates,
|
|
214
210
|
meta: {
|
|
215
|
-
...(
|
|
211
|
+
...(interceptorConfirmationResult ? { confirmationResult: interceptorConfirmationResult } : {}),
|
|
216
212
|
},
|
|
217
213
|
},
|
|
218
214
|
});
|
|
219
215
|
if (resp.error && resp.error !== 'Operation aborted by hook') {
|
|
220
216
|
showErrorTost(resp.error);
|
|
221
217
|
} else {
|
|
222
|
-
|
|
218
|
+
alert({
|
|
223
219
|
message: t('Record updated successfully'),
|
|
224
220
|
variant: 'success',
|
|
225
221
|
timeout: 400000
|
|
@@ -238,7 +234,7 @@ function scrollToInvalidField() {
|
|
|
238
234
|
}
|
|
239
235
|
}
|
|
240
236
|
const errorMessage = t('Failed to save. Please fix errors for the following fields:') + '<ul class="mt-2 list-disc list-inside">' + columnsWithErrors.map(c => `<li><strong>${c.column.label || c.column.name}</strong>: ${c.error}</li>`).join('') + '</ul>';
|
|
241
|
-
|
|
237
|
+
alert({
|
|
242
238
|
messageHtml: errorMessage,
|
|
243
239
|
variant: 'danger'
|
|
244
240
|
});
|