adminforth 1.0.46 → 1.0.48
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/dataConnectors/sqlite.ts +0 -1
- package/dist/dataConnectors/sqlite.js +0 -1
- package/dist/index.js +70 -41
- package/dist/modules/codeInjector.js +48 -11
- package/dist/spa/spa/package-lock.json +63 -0
- package/dist/spa/spa/package.json +1 -0
- package/dist/spa/spa/src/App.vue +18 -1
- package/dist/spa/spa/src/utils.ts +3 -3
- package/dist/spa/spa/src/views/CreateView.vue +6 -8
- package/dist/spa/spa/src/views/EditView.vue +16 -10
- package/dist/spa/spa/src/views/ListView.vue +7 -8
- package/dist/spa/spa/src/views/ShowView.vue +5 -9
- package/index.ts +45 -9
- package/modules/codeInjector.ts +47 -7
- package/package.json +1 -1
- package/spa/package-lock.json +63 -0
- package/spa/package.json +1 -0
- package/spa/src/App.vue +18 -1
- package/spa/src/utils.ts +3 -3
- package/spa/src/views/CreateView.vue +6 -8
- package/spa/src/views/EditView.vue +16 -10
- package/spa/src/views/ListView.vue +7 -8
- package/spa/src/views/ShowView.vue +5 -9
- package/types/AdminForthConfig.ts +4 -2
|
@@ -41,9 +41,9 @@ import BreadcrumbsWithButtons from '@/components/BreadcrumbsWithButtons.vue';
|
|
|
41
41
|
import ResourceForm from '@/components/ResourceForm.vue';
|
|
42
42
|
import SingleSkeletLoader from '@/components/SingleSkeletLoader.vue';
|
|
43
43
|
import { useCoreStore } from '@/stores/core';
|
|
44
|
-
import { callAdminForthApi } from '@/utils';
|
|
44
|
+
import { callAdminForthApi, getCustomComponent } from '@/utils';
|
|
45
45
|
import { IconFloppyDiskSolid } from '@iconify-prerendered/vue-flowbite';
|
|
46
|
-
import {
|
|
46
|
+
import { computed, onMounted, ref } from 'vue';
|
|
47
47
|
import { useRoute, useRouter } from 'vue-router';
|
|
48
48
|
|
|
49
49
|
const coreStore = useCoreStore();
|
|
@@ -90,13 +90,11 @@ onMounted(async () => {
|
|
|
90
90
|
});
|
|
91
91
|
|
|
92
92
|
editComponentsPerColumn = coreStore.resourceColumns.reduce((acc, column) => {
|
|
93
|
-
if (column.component?.
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
}
|
|
98
|
-
return acc;
|
|
99
|
-
}, {});
|
|
93
|
+
if (column.component?.show) {
|
|
94
|
+
acc[column.name] = getCustomComponent(column.component.edit.replace('@@', '').replace('./custom/', '')).split('.')[0];
|
|
95
|
+
}
|
|
96
|
+
return acc;
|
|
97
|
+
}, {});
|
|
100
98
|
loading.value = false;
|
|
101
99
|
});
|
|
102
100
|
|
|
@@ -109,13 +107,21 @@ async function saveRecord() {
|
|
|
109
107
|
}
|
|
110
108
|
|
|
111
109
|
saving.value = true;
|
|
110
|
+
|
|
111
|
+
const updates = {};
|
|
112
|
+
for (const key in record.value) {
|
|
113
|
+
if (record.value[key] !== coreStore.record[key]) {
|
|
114
|
+
updates[key] = record.value[key];
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
112
118
|
await callAdminForthApi({
|
|
113
119
|
method: 'POST',
|
|
114
120
|
path: `/update_record`,
|
|
115
121
|
body: {
|
|
116
122
|
resourceId: route.params.resourceId,
|
|
117
123
|
recordId: route.params.primaryKey,
|
|
118
|
-
record:
|
|
124
|
+
record: updates,
|
|
119
125
|
},
|
|
120
126
|
});
|
|
121
127
|
saving.value = false;
|
|
@@ -320,10 +320,11 @@ import { useCoreStore } from '@/stores/core';
|
|
|
320
320
|
import { useModalStore } from '@/stores/modal';
|
|
321
321
|
import { callAdminForthApi, getIcon } from '@/utils';
|
|
322
322
|
import { initFlowbite } from 'flowbite';
|
|
323
|
-
import { computed,
|
|
323
|
+
import { computed, onMounted, ref, watch } from 'vue';
|
|
324
324
|
import { useRoute } from 'vue-router';
|
|
325
325
|
|
|
326
326
|
import ValueRenderer from '@/components/ValueRenderer.vue';
|
|
327
|
+
import { getCustomComponent } from '@/utils';
|
|
327
328
|
|
|
328
329
|
import {
|
|
329
330
|
IconInboxOutline,
|
|
@@ -431,13 +432,11 @@ async function getList() {
|
|
|
431
432
|
}
|
|
432
433
|
});
|
|
433
434
|
listComponentsPerColumn = coreStore.resourceColumns.reduce((acc, column) => {
|
|
434
|
-
if (column.component?.
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
}
|
|
439
|
-
return acc;
|
|
440
|
-
}, {});
|
|
435
|
+
if (column.component?.show) {
|
|
436
|
+
acc[column.name] = getCustomComponent(column.component.list.replace('@@', '').replace('./custom/', '')).split('.')[0];
|
|
437
|
+
}
|
|
438
|
+
return acc;
|
|
439
|
+
}, {});
|
|
441
440
|
|
|
442
441
|
fetchStatus.value.pending = false;
|
|
443
442
|
rows.value = data.data?.map(row => {
|
|
@@ -48,7 +48,6 @@
|
|
|
48
48
|
{{ column.label }}
|
|
49
49
|
</td>
|
|
50
50
|
<td class="px-6 py-4 whitespace-nowrap whitespace-pre-wrap">
|
|
51
|
-
<!-- if column.name in showComponentsPerColumn, render it. If not, render ValueRenderer -->
|
|
52
51
|
<component
|
|
53
52
|
:is="showComponentsPerColumn[column.name] || ValueRenderer"
|
|
54
53
|
:column="column"
|
|
@@ -71,8 +70,9 @@
|
|
|
71
70
|
import BreadcrumbsWithButtons from '@/components/BreadcrumbsWithButtons.vue';
|
|
72
71
|
import ValueRenderer from '@/components/ValueRenderer.vue';
|
|
73
72
|
import { useCoreStore } from '@/stores/core';
|
|
73
|
+
import { getCustomComponent } from '@/utils';
|
|
74
74
|
import { IconPenSolid, IconTrashBinSolid } from '@iconify-prerendered/vue-flowbite';
|
|
75
|
-
import {
|
|
75
|
+
import { onMounted, ref } from 'vue';
|
|
76
76
|
import { useRoute } from 'vue-router';
|
|
77
77
|
|
|
78
78
|
|
|
@@ -94,14 +94,10 @@ onMounted(async () => {
|
|
|
94
94
|
});
|
|
95
95
|
showComponentsPerColumn = coreStore.resourceColumns.reduce((acc, column) => {
|
|
96
96
|
if (column.component?.show) {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
acc[column.name] = component;
|
|
101
|
-
}
|
|
102
|
-
return acc;
|
|
97
|
+
acc[column.name] = getCustomComponent(column.component.show.replace('@@', '').replace('./custom/', '')).split('.')[0];
|
|
98
|
+
}
|
|
99
|
+
return acc;
|
|
103
100
|
}, {});
|
|
104
|
-
console.log('showComponentsPerColumn', showComponentsPerColumn);
|
|
105
101
|
loading.value = false;
|
|
106
102
|
});
|
|
107
103
|
|
package/index.ts
CHANGED
|
@@ -230,6 +230,19 @@ class AdminForth {
|
|
|
230
230
|
if (item.component && !item.path) {
|
|
231
231
|
errors.push(`Menu item with component must have path : ${JSON.stringify(item)}`);
|
|
232
232
|
}
|
|
233
|
+
|
|
234
|
+
if (item.type === 'resource' && !item.resourceId) {
|
|
235
|
+
errors.push(`Menu item with type 'resource' must have resourceId : ${JSON.stringify(item)}`);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (item.resourceId && !this.config.resources.find((res) => res.resourceId === item.resourceId)) {
|
|
239
|
+
errors.push(`Menu item with type 'resourceId' has resourceId which is not in resources: ${JSON.stringify(item)}`);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (item.type === 'component' && !item.component) {
|
|
243
|
+
errors.push(`Menu item with type 'component' must have component : ${JSON.stringify(item)}`);
|
|
244
|
+
}
|
|
245
|
+
|
|
233
246
|
// make sure component starts with @@
|
|
234
247
|
if (item.component) {
|
|
235
248
|
if (!item.component.startsWith('@@')) {
|
|
@@ -486,6 +499,7 @@ class AdminForth {
|
|
|
486
499
|
deleteConfirmation: this.config.deleteConfirmation,
|
|
487
500
|
auth: this.config.auth,
|
|
488
501
|
usernameField: this.config.auth.usernameField,
|
|
502
|
+
title: this.config?.title,
|
|
489
503
|
},
|
|
490
504
|
adminUser,
|
|
491
505
|
version: ADMINFORTH_VERSION,
|
|
@@ -603,11 +617,20 @@ class AdminForth {
|
|
|
603
617
|
data.data.forEach((item) => {
|
|
604
618
|
item[col.name] = targetDataMap[item[col.name]];
|
|
605
619
|
});
|
|
620
|
+
|
|
621
|
+
data.data.forEach((item) => {
|
|
622
|
+
Object.keys(item).forEach((key) => {
|
|
623
|
+
if (!targetResource.columns.find((col) => col.name === key) || targetResource.columns.find((col) => col.name === key && col.backendOnly)) {
|
|
624
|
+
delete item[key];
|
|
625
|
+
}
|
|
626
|
+
})
|
|
627
|
+
});
|
|
628
|
+
|
|
606
629
|
})
|
|
607
630
|
);
|
|
608
631
|
|
|
609
632
|
data.data.forEach((item) => {
|
|
610
|
-
item._label = resource.itemLabel(item)
|
|
633
|
+
item._label = resource.itemLabel(item);
|
|
611
634
|
})
|
|
612
635
|
|
|
613
636
|
|
|
@@ -622,6 +645,15 @@ class AdminForth {
|
|
|
622
645
|
}
|
|
623
646
|
}
|
|
624
647
|
|
|
648
|
+
// remove all columns which are not defined in resources, or defined but backendOnly
|
|
649
|
+
data.data.forEach((item) => {
|
|
650
|
+
Object.keys(item).forEach((key) => {
|
|
651
|
+
if (!resource.columns.find((col) => col.name === key) || resource.columns.find((col) => col.name === key && col.backendOnly)) {
|
|
652
|
+
delete item[key];
|
|
653
|
+
}
|
|
654
|
+
})
|
|
655
|
+
});
|
|
656
|
+
|
|
625
657
|
return {...data, options: resource?.options };
|
|
626
658
|
},
|
|
627
659
|
});
|
|
@@ -725,7 +757,6 @@ class AdminForth {
|
|
|
725
757
|
});
|
|
726
758
|
|
|
727
759
|
server.endpoint({
|
|
728
|
-
noAuth: true, // TODO
|
|
729
760
|
method: 'POST',
|
|
730
761
|
path: '/create_record',
|
|
731
762
|
handler: async ({ body, adminUser }) => {
|
|
@@ -800,7 +831,6 @@ class AdminForth {
|
|
|
800
831
|
}
|
|
801
832
|
});
|
|
802
833
|
server.endpoint({
|
|
803
|
-
noAuth: true, // TODO
|
|
804
834
|
method: 'POST',
|
|
805
835
|
path: '/update_record',
|
|
806
836
|
handler: async ({ body, adminUser }) => {
|
|
@@ -831,11 +861,19 @@ class AdminForth {
|
|
|
831
861
|
}
|
|
832
862
|
|
|
833
863
|
const newValues = {};
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
864
|
+
|
|
865
|
+
for (const recordField in record) {
|
|
866
|
+
if (record[recordField] !== oldRecord[recordField]) {
|
|
867
|
+
const column = resource.columns.find((col) => col.name === recordField);
|
|
868
|
+
if (column) {
|
|
869
|
+
newValues[recordField] = connector.setFieldValue(column, record[recordField]);
|
|
870
|
+
} else {
|
|
871
|
+
newValues[recordField] = record[recordField];
|
|
837
872
|
}
|
|
838
|
-
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
console.log('✅ newValues', newValues)
|
|
839
877
|
if (Object.keys(newValues).length > 0) {
|
|
840
878
|
await connector.updateRecord({ resource, recordId, record, newValues});
|
|
841
879
|
}
|
|
@@ -858,7 +896,6 @@ class AdminForth {
|
|
|
858
896
|
}
|
|
859
897
|
});
|
|
860
898
|
server.endpoint({
|
|
861
|
-
noAuth: true, // TODO
|
|
862
899
|
method: 'POST',
|
|
863
900
|
path: '/delete_record',
|
|
864
901
|
handler: async ({ body, adminUser }) => {
|
|
@@ -900,7 +937,6 @@ class AdminForth {
|
|
|
900
937
|
}
|
|
901
938
|
});
|
|
902
939
|
server.endpoint({
|
|
903
|
-
noAuth: true, // TODO
|
|
904
940
|
method: 'POST',
|
|
905
941
|
path: '/start_bulk_action',
|
|
906
942
|
handler: async ({ body }) => {
|
package/modules/codeInjector.ts
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
import fsExtra from 'fs-extra';
|
|
3
|
-
import filewatcher from 'filewatcher';
|
|
4
1
|
import { exec, spawn } from 'child_process';
|
|
5
|
-
import { promisify } from 'util';
|
|
6
|
-
import path from 'path';
|
|
7
|
-
import { fileURLToPath } from 'url';
|
|
8
2
|
import crypto from 'crypto';
|
|
3
|
+
import filewatcher from 'filewatcher';
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
import fsExtra from 'fs-extra';
|
|
9
6
|
import os from 'os';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import { promisify } from 'util';
|
|
10
9
|
import AdminForth from '../index.js';
|
|
11
10
|
import { ADMIN_FORTH_ABSOLUTE_PATH } from './utils.js';
|
|
12
11
|
|
|
@@ -173,6 +172,26 @@ class CodeInjector {
|
|
|
173
172
|
return `import { ${PascalIconName} } from '@iconify-prerendered/vue-${collection}';`;
|
|
174
173
|
}).join('\n');
|
|
175
174
|
|
|
175
|
+
// for each custom component generate import statement
|
|
176
|
+
const customComponentsDir = this.adminforth.config.customization?.customComponentsDir;
|
|
177
|
+
let customComponentsImports = '';
|
|
178
|
+
if (customComponentsDir) {
|
|
179
|
+
// if file - return, if dir - go recursively
|
|
180
|
+
const customComponents = await fs.promises.readdir(customComponentsDir);
|
|
181
|
+
for (const filePath of customComponents) {
|
|
182
|
+
fs.stat(`${customComponentsDir}/${filePath}`, (err, data) => {
|
|
183
|
+
if (err) {
|
|
184
|
+
console.log(err);
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
if (data.isFile()) {
|
|
188
|
+
const componentName = filePath.split('.')[0].replace('/', '');
|
|
189
|
+
customComponentsImports += `import ${componentName} from '@/custom/${filePath}';\n`;
|
|
190
|
+
}
|
|
191
|
+
})
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
176
195
|
// Generate Vue.component statements for each icon
|
|
177
196
|
const iconComponents = uniqueIcons.map((icon) => {
|
|
178
197
|
const [ collection, iconName ] = icon.split(':');
|
|
@@ -182,7 +201,28 @@ class CodeInjector {
|
|
|
182
201
|
return `app.component('${PascalIconName}', ${PascalIconName});`;
|
|
183
202
|
}).join('\n');
|
|
184
203
|
|
|
204
|
+
// Generate Vue.component statements for each custom component
|
|
205
|
+
let customComponentsComponents = '';
|
|
206
|
+
if (customComponentsDir) {
|
|
207
|
+
const customComponents = await fs.promises.readdir(customComponentsDir);
|
|
208
|
+
for (const filePath of customComponents) {
|
|
209
|
+
fs.stat(`${customComponentsDir}/${filePath}`, (err, data) => {
|
|
210
|
+
if (err) {
|
|
211
|
+
console.log('Custom components importing error: ', err);
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
if (data.isFile()) {
|
|
215
|
+
const componentName = filePath.split('.')[0].replace('/', '');
|
|
216
|
+
customComponentsComponents += `app.component('${componentName}', ${componentName});\n`;
|
|
217
|
+
}
|
|
218
|
+
})
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
|
|
185
223
|
let imports = iconImports + '\n';
|
|
224
|
+
imports += customComponentsImports + '\n';
|
|
225
|
+
|
|
186
226
|
|
|
187
227
|
if (this.adminforth.config.customization?.vueUsesFile) {
|
|
188
228
|
imports += `import addCustomUses from '${this.adminforth.config.customization.vueUsesFile}';\n`;
|
|
@@ -192,7 +232,7 @@ class CodeInjector {
|
|
|
192
232
|
const appVuePath = path.join(CodeInjector.SPA_TMP_PATH, 'src', 'main.ts');
|
|
193
233
|
let appVueContent = await fs.promises.readFile(appVuePath, 'utf-8');
|
|
194
234
|
appVueContent = appVueContent.replace('/* IMPORTANT:ADMINFORTH IMPORTS */', imports);
|
|
195
|
-
appVueContent = appVueContent.replace('/* IMPORTANT:ADMINFORTH COMPONENT REGISTRATIONS */', iconComponents + '\n' );
|
|
235
|
+
appVueContent = appVueContent.replace('/* IMPORTANT:ADMINFORTH COMPONENT REGISTRATIONS */', iconComponents + '\n' + customComponentsComponents + '\n');
|
|
196
236
|
if (this.adminforth.config.customization?.vueUsesFile) {
|
|
197
237
|
appVueContent = appVueContent.replace('/* IMPORTANT:ADMINFORTH CUSTOM USES */', 'addCustomUses(app);');
|
|
198
238
|
}
|
package/package.json
CHANGED
package/spa/package-lock.json
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
"flowbite": "^2.3.0",
|
|
16
16
|
"flowbite-datepicker": "^1.2.6",
|
|
17
17
|
"pinia": "^2.1.7",
|
|
18
|
+
"unhead": "^1.9.12",
|
|
18
19
|
"vue": "^3.4.21",
|
|
19
20
|
"vue-router": "^4.3.0",
|
|
20
21
|
"vue-slider-component": "^4.1.0-beta.7"
|
|
@@ -1149,6 +1150,41 @@
|
|
|
1149
1150
|
"integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==",
|
|
1150
1151
|
"dev": true
|
|
1151
1152
|
},
|
|
1153
|
+
"node_modules/@unhead/dom": {
|
|
1154
|
+
"version": "1.9.12",
|
|
1155
|
+
"resolved": "https://registry.npmjs.org/@unhead/dom/-/dom-1.9.12.tgz",
|
|
1156
|
+
"integrity": "sha512-3MY1TbZmEjGNZapi3wvJW0vWNS2CLKHt7/m57sScDHCNvNBe1mTwrIOhtZFDgAndhml2EVQ68RMa0Vhum/M+cw==",
|
|
1157
|
+
"dependencies": {
|
|
1158
|
+
"@unhead/schema": "1.9.12",
|
|
1159
|
+
"@unhead/shared": "1.9.12"
|
|
1160
|
+
},
|
|
1161
|
+
"funding": {
|
|
1162
|
+
"url": "https://github.com/sponsors/harlan-zw"
|
|
1163
|
+
}
|
|
1164
|
+
},
|
|
1165
|
+
"node_modules/@unhead/schema": {
|
|
1166
|
+
"version": "1.9.12",
|
|
1167
|
+
"resolved": "https://registry.npmjs.org/@unhead/schema/-/schema-1.9.12.tgz",
|
|
1168
|
+
"integrity": "sha512-ue2FKyIZKsuZDpWJBMlBGwMm4s+vFeU3NUWsNt8Z+2JkOUIqO/VG43LxNgY1M595bOS71Gdxk+G9VtzfKJ5uEA==",
|
|
1169
|
+
"dependencies": {
|
|
1170
|
+
"hookable": "^5.5.3",
|
|
1171
|
+
"zhead": "^2.2.4"
|
|
1172
|
+
},
|
|
1173
|
+
"funding": {
|
|
1174
|
+
"url": "https://github.com/sponsors/harlan-zw"
|
|
1175
|
+
}
|
|
1176
|
+
},
|
|
1177
|
+
"node_modules/@unhead/shared": {
|
|
1178
|
+
"version": "1.9.12",
|
|
1179
|
+
"resolved": "https://registry.npmjs.org/@unhead/shared/-/shared-1.9.12.tgz",
|
|
1180
|
+
"integrity": "sha512-72wlLXG3FP3sXUrwd42Uv8jYpHSg4R6IFJcsl+QisRjKM89JnjOFSw1DqWO4IOftW5xOxS4J5v7SQyJ4NJo7Bw==",
|
|
1181
|
+
"dependencies": {
|
|
1182
|
+
"@unhead/schema": "1.9.12"
|
|
1183
|
+
},
|
|
1184
|
+
"funding": {
|
|
1185
|
+
"url": "https://github.com/sponsors/harlan-zw"
|
|
1186
|
+
}
|
|
1187
|
+
},
|
|
1152
1188
|
"node_modules/@vitejs/plugin-vue": {
|
|
1153
1189
|
"version": "5.0.4",
|
|
1154
1190
|
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.0.4.tgz",
|
|
@@ -2471,6 +2507,11 @@
|
|
|
2471
2507
|
"he": "bin/he"
|
|
2472
2508
|
}
|
|
2473
2509
|
},
|
|
2510
|
+
"node_modules/hookable": {
|
|
2511
|
+
"version": "5.5.3",
|
|
2512
|
+
"resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz",
|
|
2513
|
+
"integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="
|
|
2514
|
+
},
|
|
2474
2515
|
"node_modules/ignore": {
|
|
2475
2516
|
"version": "5.3.1",
|
|
2476
2517
|
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz",
|
|
@@ -3914,6 +3955,20 @@
|
|
|
3914
3955
|
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
|
|
3915
3956
|
"dev": true
|
|
3916
3957
|
},
|
|
3958
|
+
"node_modules/unhead": {
|
|
3959
|
+
"version": "1.9.12",
|
|
3960
|
+
"resolved": "https://registry.npmjs.org/unhead/-/unhead-1.9.12.tgz",
|
|
3961
|
+
"integrity": "sha512-s6VxcTV45hy8c/IioKQOonFnAO+kBOSpgDfqEHhnU0YVSQYaRPEp9pzW1qSPf0lx+bg9RKeOQyNNbSGGUP26aQ==",
|
|
3962
|
+
"dependencies": {
|
|
3963
|
+
"@unhead/dom": "1.9.12",
|
|
3964
|
+
"@unhead/schema": "1.9.12",
|
|
3965
|
+
"@unhead/shared": "1.9.12",
|
|
3966
|
+
"hookable": "^5.5.3"
|
|
3967
|
+
},
|
|
3968
|
+
"funding": {
|
|
3969
|
+
"url": "https://github.com/sponsors/harlan-zw"
|
|
3970
|
+
}
|
|
3971
|
+
},
|
|
3917
3972
|
"node_modules/update-browserslist-db": {
|
|
3918
3973
|
"version": "1.0.16",
|
|
3919
3974
|
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.16.tgz",
|
|
@@ -4260,6 +4315,14 @@
|
|
|
4260
4315
|
"funding": {
|
|
4261
4316
|
"url": "https://github.com/sponsors/sindresorhus"
|
|
4262
4317
|
}
|
|
4318
|
+
},
|
|
4319
|
+
"node_modules/zhead": {
|
|
4320
|
+
"version": "2.2.4",
|
|
4321
|
+
"resolved": "https://registry.npmjs.org/zhead/-/zhead-2.2.4.tgz",
|
|
4322
|
+
"integrity": "sha512-8F0OI5dpWIA5IGG5NHUg9staDwz/ZPxZtvGVf01j7vHqSyZ0raHY+78atOVxRqb73AotX22uV1pXt3gYSstGag==",
|
|
4323
|
+
"funding": {
|
|
4324
|
+
"url": "https://github.com/sponsors/harlan-zw"
|
|
4325
|
+
}
|
|
4263
4326
|
}
|
|
4264
4327
|
}
|
|
4265
4328
|
}
|
package/spa/package.json
CHANGED
package/spa/src/App.vue
CHANGED
|
@@ -64,6 +64,7 @@
|
|
|
64
64
|
</div>
|
|
65
65
|
</nav>
|
|
66
66
|
|
|
67
|
+
|
|
67
68
|
<aside
|
|
68
69
|
v-if="loggedIn"
|
|
69
70
|
|
|
@@ -132,10 +133,22 @@ import AcceptModal from './components/AcceptModal.vue';
|
|
|
132
133
|
import MenuLink from './components/MenuLink.vue';
|
|
133
134
|
import { useRoute, useRouter } from 'vue-router';
|
|
134
135
|
import { getIcon } from '@/utils';
|
|
136
|
+
import { useHead } from 'unhead'
|
|
137
|
+
import { createHead } from 'unhead'
|
|
138
|
+
const coreStore = useCoreStore();
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
createHead()
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
|
|
135
146
|
|
|
136
147
|
|
|
137
148
|
const route = useRoute();
|
|
138
149
|
const router = useRouter();
|
|
150
|
+
const title = ref('');
|
|
151
|
+
|
|
139
152
|
|
|
140
153
|
const routerIsReady = ref(false);
|
|
141
154
|
|
|
@@ -153,7 +166,6 @@ async function logout() {
|
|
|
153
166
|
router.push({ name: 'login' });
|
|
154
167
|
}
|
|
155
168
|
|
|
156
|
-
const coreStore = useCoreStore();
|
|
157
169
|
|
|
158
170
|
|
|
159
171
|
|
|
@@ -167,6 +179,11 @@ onMounted(async () => {
|
|
|
167
179
|
initRouter();
|
|
168
180
|
initFlowbite();
|
|
169
181
|
await coreStore.fetchMenuAndResource();
|
|
182
|
+
title.value = coreStore.config.title || 'Admin Forth';
|
|
183
|
+
useHead({
|
|
184
|
+
title: title.value,
|
|
185
|
+
|
|
186
|
+
})
|
|
170
187
|
})
|
|
171
188
|
|
|
172
189
|
</script>
|
package/spa/src/utils.ts
CHANGED
|
@@ -29,9 +29,9 @@ export async function callAdminForthApi({ path, method, body=undefined }) {
|
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
export function getCustomComponent(name) {
|
|
33
|
+
return resolveComponent(name);
|
|
34
|
+
}
|
|
35
35
|
|
|
36
36
|
export function getIcon(icon: string) {
|
|
37
37
|
// icon format is "feather:icon-name". We need to get IconName in pascal case
|
|
@@ -45,9 +45,9 @@ import BreadcrumbsWithButtons from '@/components/BreadcrumbsWithButtons.vue';
|
|
|
45
45
|
import ResourceForm from '@/components/ResourceForm.vue';
|
|
46
46
|
import SingleSkeletLoader from '@/components/SingleSkeletLoader.vue';
|
|
47
47
|
import { useCoreStore } from '@/stores/core';
|
|
48
|
-
import { callAdminForthApi } from '@/utils';
|
|
48
|
+
import { callAdminForthApi, getCustomComponent } from '@/utils';
|
|
49
49
|
import { IconFloppyDiskSolid } from '@iconify-prerendered/vue-flowbite';
|
|
50
|
-
import {
|
|
50
|
+
import { onMounted, ref } from 'vue';
|
|
51
51
|
import { useRoute, useRouter } from 'vue-router';
|
|
52
52
|
|
|
53
53
|
const isValid = ref(false);
|
|
@@ -75,12 +75,10 @@ onMounted(async () => {
|
|
|
75
75
|
resourceId: route.params.resourceId
|
|
76
76
|
});
|
|
77
77
|
createComponentsPerColumn = coreStore.resourceColumns.reduce((acc, column) => {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
acc
|
|
82
|
-
}
|
|
83
|
-
return acc;
|
|
78
|
+
if (column.component?.show) {
|
|
79
|
+
acc[column.name] = getCustomComponent(column.component.create.replace('@@', '').replace('./custom/', '')).split('.')[0];
|
|
80
|
+
}
|
|
81
|
+
return acc;
|
|
84
82
|
}, {});
|
|
85
83
|
loading.value = false;
|
|
86
84
|
});
|
|
@@ -41,9 +41,9 @@ import BreadcrumbsWithButtons from '@/components/BreadcrumbsWithButtons.vue';
|
|
|
41
41
|
import ResourceForm from '@/components/ResourceForm.vue';
|
|
42
42
|
import SingleSkeletLoader from '@/components/SingleSkeletLoader.vue';
|
|
43
43
|
import { useCoreStore } from '@/stores/core';
|
|
44
|
-
import { callAdminForthApi } from '@/utils';
|
|
44
|
+
import { callAdminForthApi, getCustomComponent } from '@/utils';
|
|
45
45
|
import { IconFloppyDiskSolid } from '@iconify-prerendered/vue-flowbite';
|
|
46
|
-
import {
|
|
46
|
+
import { computed, onMounted, ref } from 'vue';
|
|
47
47
|
import { useRoute, useRouter } from 'vue-router';
|
|
48
48
|
|
|
49
49
|
const coreStore = useCoreStore();
|
|
@@ -90,13 +90,11 @@ onMounted(async () => {
|
|
|
90
90
|
});
|
|
91
91
|
|
|
92
92
|
editComponentsPerColumn = coreStore.resourceColumns.reduce((acc, column) => {
|
|
93
|
-
if (column.component?.
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
}
|
|
98
|
-
return acc;
|
|
99
|
-
}, {});
|
|
93
|
+
if (column.component?.show) {
|
|
94
|
+
acc[column.name] = getCustomComponent(column.component.edit.replace('@@', '').replace('./custom/', '')).split('.')[0];
|
|
95
|
+
}
|
|
96
|
+
return acc;
|
|
97
|
+
}, {});
|
|
100
98
|
loading.value = false;
|
|
101
99
|
});
|
|
102
100
|
|
|
@@ -109,13 +107,21 @@ async function saveRecord() {
|
|
|
109
107
|
}
|
|
110
108
|
|
|
111
109
|
saving.value = true;
|
|
110
|
+
|
|
111
|
+
const updates = {};
|
|
112
|
+
for (const key in record.value) {
|
|
113
|
+
if (record.value[key] !== coreStore.record[key]) {
|
|
114
|
+
updates[key] = record.value[key];
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
112
118
|
await callAdminForthApi({
|
|
113
119
|
method: 'POST',
|
|
114
120
|
path: `/update_record`,
|
|
115
121
|
body: {
|
|
116
122
|
resourceId: route.params.resourceId,
|
|
117
123
|
recordId: route.params.primaryKey,
|
|
118
|
-
record:
|
|
124
|
+
record: updates,
|
|
119
125
|
},
|
|
120
126
|
});
|
|
121
127
|
saving.value = false;
|
|
@@ -320,10 +320,11 @@ import { useCoreStore } from '@/stores/core';
|
|
|
320
320
|
import { useModalStore } from '@/stores/modal';
|
|
321
321
|
import { callAdminForthApi, getIcon } from '@/utils';
|
|
322
322
|
import { initFlowbite } from 'flowbite';
|
|
323
|
-
import { computed,
|
|
323
|
+
import { computed, onMounted, ref, watch } from 'vue';
|
|
324
324
|
import { useRoute } from 'vue-router';
|
|
325
325
|
|
|
326
326
|
import ValueRenderer from '@/components/ValueRenderer.vue';
|
|
327
|
+
import { getCustomComponent } from '@/utils';
|
|
327
328
|
|
|
328
329
|
import {
|
|
329
330
|
IconInboxOutline,
|
|
@@ -431,13 +432,11 @@ async function getList() {
|
|
|
431
432
|
}
|
|
432
433
|
});
|
|
433
434
|
listComponentsPerColumn = coreStore.resourceColumns.reduce((acc, column) => {
|
|
434
|
-
if (column.component?.
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
}
|
|
439
|
-
return acc;
|
|
440
|
-
}, {});
|
|
435
|
+
if (column.component?.show) {
|
|
436
|
+
acc[column.name] = getCustomComponent(column.component.list.replace('@@', '').replace('./custom/', '')).split('.')[0];
|
|
437
|
+
}
|
|
438
|
+
return acc;
|
|
439
|
+
}, {});
|
|
441
440
|
|
|
442
441
|
fetchStatus.value.pending = false;
|
|
443
442
|
rows.value = data.data?.map(row => {
|
|
@@ -48,7 +48,6 @@
|
|
|
48
48
|
{{ column.label }}
|
|
49
49
|
</td>
|
|
50
50
|
<td class="px-6 py-4 whitespace-nowrap whitespace-pre-wrap">
|
|
51
|
-
<!-- if column.name in showComponentsPerColumn, render it. If not, render ValueRenderer -->
|
|
52
51
|
<component
|
|
53
52
|
:is="showComponentsPerColumn[column.name] || ValueRenderer"
|
|
54
53
|
:column="column"
|
|
@@ -71,8 +70,9 @@
|
|
|
71
70
|
import BreadcrumbsWithButtons from '@/components/BreadcrumbsWithButtons.vue';
|
|
72
71
|
import ValueRenderer from '@/components/ValueRenderer.vue';
|
|
73
72
|
import { useCoreStore } from '@/stores/core';
|
|
73
|
+
import { getCustomComponent } from '@/utils';
|
|
74
74
|
import { IconPenSolid, IconTrashBinSolid } from '@iconify-prerendered/vue-flowbite';
|
|
75
|
-
import {
|
|
75
|
+
import { onMounted, ref } from 'vue';
|
|
76
76
|
import { useRoute } from 'vue-router';
|
|
77
77
|
|
|
78
78
|
|
|
@@ -94,14 +94,10 @@ onMounted(async () => {
|
|
|
94
94
|
});
|
|
95
95
|
showComponentsPerColumn = coreStore.resourceColumns.reduce((acc, column) => {
|
|
96
96
|
if (column.component?.show) {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
acc[column.name] = component;
|
|
101
|
-
}
|
|
102
|
-
return acc;
|
|
97
|
+
acc[column.name] = getCustomComponent(column.component.show.replace('@@', '').replace('./custom/', '')).split('.')[0];
|
|
98
|
+
}
|
|
99
|
+
return acc;
|
|
103
100
|
}, {});
|
|
104
|
-
console.log('showComponentsPerColumn', showComponentsPerColumn);
|
|
105
101
|
loading.value = false;
|
|
106
102
|
});
|
|
107
103
|
|