adminforth 2.4.0-next.31 → 2.4.0-next.310
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/callTsProxy.js +14 -4
- package/commands/createApp/templates/api.ts.hbs +10 -0
- package/commands/createApp/templates/custom/tsconfig.json.hbs +2 -3
- package/commands/createApp/templates/index.ts.hbs +12 -1
- package/commands/createApp/templates/package.json.hbs +1 -1
- package/commands/createApp/templates/prisma.config.ts.hbs +8 -0
- package/commands/createApp/templates/schema.prisma.hbs +0 -1
- package/commands/createApp/utils.js +10 -0
- package/commands/createCustomComponent/configLoader.js +17 -4
- package/commands/createCustomComponent/main.js +13 -7
- package/commands/createCustomComponent/templates/customCrud/beforeActionButtons.vue.hbs +38 -0
- package/commands/createCustomComponent/templates/customCrud/saveButton.vue.hbs +28 -0
- package/commands/createPlugin/templates/custom/tsconfig.json.hbs +2 -5
- package/commands/createPlugin/templates/package.json.hbs +1 -1
- package/commands/generateModels.js +30 -22
- package/dist/auth.d.ts +9 -1
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +21 -2
- package/dist/auth.js.map +1 -1
- package/dist/dataConnectors/baseConnector.d.ts +1 -1
- package/dist/dataConnectors/baseConnector.d.ts.map +1 -1
- package/dist/dataConnectors/baseConnector.js +69 -17
- package/dist/dataConnectors/baseConnector.js.map +1 -1
- package/dist/dataConnectors/clickhouse.d.ts.map +1 -1
- package/dist/dataConnectors/clickhouse.js +15 -0
- package/dist/dataConnectors/clickhouse.js.map +1 -1
- package/dist/dataConnectors/mongo.d.ts.map +1 -1
- package/dist/dataConnectors/mongo.js +50 -15
- package/dist/dataConnectors/mongo.js.map +1 -1
- package/dist/dataConnectors/mysql.d.ts.map +1 -1
- package/dist/dataConnectors/mysql.js +11 -0
- package/dist/dataConnectors/mysql.js.map +1 -1
- package/dist/dataConnectors/postgres.d.ts.map +1 -1
- package/dist/dataConnectors/postgres.js +43 -14
- package/dist/dataConnectors/postgres.js.map +1 -1
- package/dist/dataConnectors/sqlite.d.ts.map +1 -1
- package/dist/dataConnectors/sqlite.js +11 -0
- package/dist/dataConnectors/sqlite.js.map +1 -1
- package/dist/index.d.ts +12 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +45 -22
- package/dist/index.js.map +1 -1
- package/dist/modules/codeInjector.d.ts +2 -0
- package/dist/modules/codeInjector.d.ts.map +1 -1
- package/dist/modules/codeInjector.js +62 -6
- package/dist/modules/codeInjector.js.map +1 -1
- package/dist/modules/configValidator.d.ts +6 -0
- package/dist/modules/configValidator.d.ts.map +1 -1
- package/dist/modules/configValidator.js +202 -25
- package/dist/modules/configValidator.js.map +1 -1
- package/dist/modules/restApi.d.ts +1 -1
- package/dist/modules/restApi.d.ts.map +1 -1
- package/dist/modules/restApi.js +172 -31
- package/dist/modules/restApi.js.map +1 -1
- package/dist/modules/styles.d.ts +499 -13
- package/dist/modules/styles.d.ts.map +1 -1
- package/dist/modules/styles.js +555 -31
- package/dist/modules/styles.js.map +1 -1
- package/dist/modules/utils.d.ts +7 -15
- package/dist/modules/utils.d.ts.map +1 -1
- package/dist/modules/utils.js +45 -68
- package/dist/modules/utils.js.map +1 -1
- package/dist/servers/express.d.ts +5 -0
- package/dist/servers/express.d.ts.map +1 -1
- package/dist/servers/express.js +40 -1
- package/dist/servers/express.js.map +1 -1
- package/dist/spa/index.html +1 -1
- package/dist/spa/package-lock.json +1208 -708
- package/dist/spa/package.json +34 -34
- package/dist/spa/src/App.vue +59 -174
- package/dist/spa/src/adminforth.ts +42 -18
- package/dist/spa/src/afcl/AreaChart.vue +0 -1
- package/dist/spa/src/afcl/BarChart.vue +2 -2
- package/dist/spa/src/afcl/Button.vue +6 -6
- package/dist/spa/src/afcl/ButtonGroup.vue +91 -0
- package/dist/spa/src/afcl/Card.vue +25 -0
- package/dist/spa/src/afcl/Checkbox.vue +21 -13
- package/dist/spa/src/afcl/CountryFlag.vue +4 -1
- package/dist/spa/src/{components/CustomDatePicker.vue → afcl/DatePicker.vue} +95 -9
- package/dist/spa/src/afcl/Dialog.vue +47 -27
- package/dist/spa/src/afcl/Dropzone.vue +127 -48
- package/dist/spa/src/afcl/Input.vue +14 -6
- package/dist/spa/src/afcl/JsonViewer.vue +25 -0
- package/dist/spa/src/afcl/LinkButton.vue +3 -3
- package/dist/spa/src/afcl/PieChart.vue +5 -5
- package/dist/spa/src/afcl/ProgressBar.vue +7 -7
- package/dist/spa/src/afcl/Select.vue +82 -34
- package/dist/spa/src/afcl/Skeleton.vue +6 -6
- package/dist/spa/src/afcl/Table.vue +315 -73
- package/dist/spa/src/afcl/Textarea.vue +31 -0
- package/dist/spa/src/afcl/Toggle.vue +32 -0
- package/dist/spa/src/afcl/Tooltip.vue +28 -18
- package/dist/spa/src/afcl/VerticalTabs.vue +16 -7
- package/dist/spa/src/afcl/index.ts +6 -3
- package/dist/spa/src/components/AcceptModal.vue +48 -14
- package/dist/spa/src/components/Breadcrumbs.vue +5 -5
- package/dist/spa/src/components/CallActionWrapper.vue +15 -0
- package/dist/spa/src/components/ColumnValueInput.vue +38 -18
- package/dist/spa/src/components/ColumnValueInputWrapper.vue +4 -3
- package/dist/spa/src/components/CustomDateRangePicker.vue +9 -8
- package/dist/spa/src/components/CustomRangePicker.vue +37 -21
- package/dist/spa/src/components/ErrorMessage.vue +21 -0
- package/dist/spa/src/components/Filters.vue +195 -132
- package/dist/spa/src/components/GroupsTable.vue +9 -8
- package/dist/spa/src/components/MenuLink.vue +90 -23
- package/dist/spa/src/components/ResourceForm.vue +94 -51
- package/dist/spa/src/components/ResourceListTable.vue +115 -85
- package/dist/spa/src/components/ResourceListTableVirtual.vue +114 -80
- package/dist/spa/src/components/ShowTable.vue +21 -15
- package/dist/spa/src/components/Sidebar.vue +470 -0
- package/dist/spa/src/components/SingleSkeletLoader.vue +6 -6
- package/dist/spa/src/components/SkeleteLoader.vue +3 -3
- package/dist/spa/src/components/ThreeDotsMenu.vue +84 -15
- package/dist/spa/src/components/Toast.vue +40 -29
- package/dist/spa/src/components/UserMenuSettingsButton.vue +69 -0
- package/dist/spa/src/components/ValueRenderer.vue +44 -17
- package/dist/spa/src/controls/BoolToggle.vue +34 -0
- package/dist/spa/src/i18n.ts +5 -3
- package/dist/spa/src/main.ts +1 -1
- package/dist/spa/src/renderers/CompactField.vue +1 -1
- package/dist/spa/src/renderers/CompactUUID.vue +1 -1
- package/dist/spa/src/router/index.ts +8 -0
- package/dist/spa/src/shims-vue.d.ts +5 -0
- package/dist/spa/src/spa_types/core.ts +13 -1
- package/dist/spa/src/stores/core.ts +13 -1
- package/dist/spa/src/stores/filters.ts +33 -2
- package/dist/spa/src/stores/modal.ts +6 -1
- package/dist/spa/src/stores/toast.ts +22 -3
- package/dist/spa/src/types/Back.ts +163 -23
- package/dist/spa/src/types/Common.ts +91 -32
- package/dist/spa/src/types/FrontendAPI.ts +31 -5
- package/dist/spa/src/types/adapters/CaptchaAdapter.ts +34 -0
- package/dist/spa/src/types/adapters/EmailAdapter.ts +2 -2
- package/dist/spa/src/types/adapters/ImageVisionAdapter.ts +30 -0
- package/dist/spa/src/types/adapters/KeyValueAdapter.ts +16 -0
- package/dist/spa/src/types/adapters/index.ts +8 -0
- package/dist/spa/src/utils.ts +291 -11
- package/dist/spa/src/views/CreateView.vue +63 -21
- package/dist/spa/src/views/EditView.vue +55 -22
- package/dist/spa/src/views/ListView.vue +144 -87
- package/dist/spa/src/views/LoginView.vue +26 -35
- package/dist/spa/src/views/ResourceParent.vue +2 -2
- package/dist/spa/src/views/SettingsView.vue +121 -0
- package/dist/spa/src/views/ShowView.vue +83 -53
- package/dist/spa/src/websocket.ts +6 -1
- package/dist/spa/tsconfig.app.json +1 -1
- package/dist/spa/vite.config.ts +45 -2
- package/dist/types/Back.d.ts +146 -14
- package/dist/types/Back.d.ts.map +1 -1
- package/dist/types/Back.js +15 -0
- package/dist/types/Back.js.map +1 -1
- package/dist/types/Common.d.ts +106 -29
- package/dist/types/Common.d.ts.map +1 -1
- package/dist/types/Common.js.map +1 -1
- package/dist/types/FrontendAPI.d.ts +31 -3
- package/dist/types/FrontendAPI.d.ts.map +1 -1
- package/dist/types/FrontendAPI.js.map +1 -1
- package/dist/types/adapters/CaptchaAdapter.d.ts +30 -0
- package/dist/types/adapters/CaptchaAdapter.d.ts.map +1 -0
- package/dist/types/adapters/CaptchaAdapter.js +5 -0
- package/dist/types/adapters/CaptchaAdapter.js.map +1 -0
- package/dist/types/adapters/EmailAdapter.d.ts +1 -1
- package/dist/types/adapters/ImageVisionAdapter.d.ts +25 -0
- package/dist/types/adapters/ImageVisionAdapter.d.ts.map +1 -0
- package/dist/types/adapters/ImageVisionAdapter.js +2 -0
- package/dist/types/adapters/ImageVisionAdapter.js.map +1 -0
- package/dist/types/adapters/KeyValueAdapter.d.ts +10 -0
- package/dist/types/adapters/KeyValueAdapter.d.ts.map +1 -0
- package/dist/types/adapters/KeyValueAdapter.js +2 -0
- package/dist/types/adapters/KeyValueAdapter.js.map +1 -0
- package/dist/types/adapters/index.d.ts +9 -0
- package/dist/types/adapters/index.d.ts.map +1 -0
- package/dist/types/adapters/index.js +2 -0
- package/dist/types/adapters/index.js.map +1 -0
- package/package.json +4 -2
- package/dist/spa/src/types/adapters/index.js +0 -5
|
@@ -55,7 +55,7 @@ export interface FrontendAPIInterface {
|
|
|
55
55
|
*
|
|
56
56
|
* @param params - The parameters of the alert
|
|
57
57
|
*/
|
|
58
|
-
alert(params:AlertParams): void;
|
|
58
|
+
alert(params:AlertParams): void | Promise<string> | string;
|
|
59
59
|
|
|
60
60
|
|
|
61
61
|
list: {
|
|
@@ -82,27 +82,40 @@ export interface FrontendAPIInterface {
|
|
|
82
82
|
*/
|
|
83
83
|
closeThreeDotsDropdown(): void;
|
|
84
84
|
|
|
85
|
-
|
|
86
85
|
/**
|
|
87
|
-
* Set a filter in the list
|
|
88
|
-
* Works only when user located on the list page.
|
|
86
|
+
* Set a filter in the list.
|
|
87
|
+
* Works only when user located on the list page. If filter already exists, it will be replaced with the new one.
|
|
89
88
|
* Can be used to set filter from charts or other components in pageInjections.
|
|
90
89
|
*
|
|
90
|
+
* Filters are automatically marked as hidden (won't count in badge) if:
|
|
91
|
+
* - Column has showIn.filter: false
|
|
92
|
+
*
|
|
91
93
|
* Example:
|
|
92
94
|
*
|
|
93
95
|
* ```ts
|
|
94
96
|
* import adminforth from '@/adminforth'
|
|
95
97
|
*
|
|
98
|
+
* // Regular filter (will show in badge if column.showIn.filter !== false)
|
|
96
99
|
* adminforth.list.setFilter({field: 'name', operator: 'ilike', value: 'john'})
|
|
100
|
+
*
|
|
101
|
+
* // Hidden filter (won't show in badge if column.showIn.filter === false)
|
|
102
|
+
* adminforth.list.setFilter({field: 'internal_status', operator: 'eq', value: 'active'})
|
|
97
103
|
* ```
|
|
98
104
|
*
|
|
105
|
+
* Please note that you can set/update filter even for fields which have showIn.filter=false in resource configuration.
|
|
106
|
+
* Also you can set filter for virtual columns. For example Universal search plugin calls updateFilter for virtual column which has showIn.filter=false (because we dont want to show this column in filter dropdown, plugin renders its own filter UI)
|
|
107
|
+
*
|
|
99
108
|
* @param filter - The filter to set
|
|
100
109
|
*/
|
|
101
110
|
setFilter(filter: FilterParams): void;
|
|
102
111
|
|
|
103
112
|
/**
|
|
113
|
+
* DEPRECATED: does the same as setFilter, kept for backward compatibility
|
|
104
114
|
* Update a filter in the list
|
|
105
115
|
*
|
|
116
|
+
* Filters visibility in badge is automatically determined by column configuration:
|
|
117
|
+
* - Hidden if column has showIn.filter: false
|
|
118
|
+
*
|
|
106
119
|
* Example:
|
|
107
120
|
*
|
|
108
121
|
* ```ts
|
|
@@ -121,6 +134,14 @@ export interface FrontendAPIInterface {
|
|
|
121
134
|
clearFilters(): void;
|
|
122
135
|
}
|
|
123
136
|
|
|
137
|
+
show: {
|
|
138
|
+
/**
|
|
139
|
+
* Full refresh the current record on the show page. Loader may be shown during fetching.
|
|
140
|
+
* Fire-and-forget; you don't need to await it.
|
|
141
|
+
*/
|
|
142
|
+
refresh(): void;
|
|
143
|
+
}
|
|
144
|
+
|
|
124
145
|
menu: {
|
|
125
146
|
/**
|
|
126
147
|
* Refreshes the badges in the menu, by recalling the badge function for each menu item
|
|
@@ -171,7 +192,12 @@ export type AlertParams = {
|
|
|
171
192
|
* Default is 10 seconds;
|
|
172
193
|
*/
|
|
173
194
|
timeout?: number | 'unlimited';
|
|
174
|
-
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Optional buttons to display in the alert
|
|
198
|
+
*/
|
|
199
|
+
buttons?: {value: any, label: string}[];
|
|
200
|
+
|
|
175
201
|
}
|
|
176
202
|
|
|
177
203
|
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interface for Captcha adapters.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export interface CaptchaAdapter {
|
|
6
|
+
/**
|
|
7
|
+
* Returns the script source URL for the captcha widget.
|
|
8
|
+
*/
|
|
9
|
+
getScriptSrc(): string;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Returns the site key for the captcha.
|
|
13
|
+
*/
|
|
14
|
+
getSiteKey(): string;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Returns the widget ID for the captcha.
|
|
18
|
+
*/
|
|
19
|
+
getWidgetId(): string;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Returns the script HTML for the captcha widget.
|
|
23
|
+
*/
|
|
24
|
+
getRenderWidgetCode(): string;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Returns the function name to render the captcha widget.
|
|
28
|
+
*/
|
|
29
|
+
getRenderWidgetFunctionName(): string;
|
|
30
|
+
/**
|
|
31
|
+
* Validates the captcha token.
|
|
32
|
+
*/
|
|
33
|
+
validate(token: string, ip: string): Promise<Record<string, any>>;
|
|
34
|
+
}
|
|
@@ -2,7 +2,7 @@ export interface EmailAdapter {
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* This method is called to validate the configuration of the adapter
|
|
5
|
-
* and should throw a clear user-
|
|
5
|
+
* and should throw a clear user-readable error if the configuration is invalid.
|
|
6
6
|
*/
|
|
7
7
|
validate(): Promise<void>;
|
|
8
8
|
|
|
@@ -24,4 +24,4 @@ export interface EmailAdapter {
|
|
|
24
24
|
error?: string;
|
|
25
25
|
ok?: boolean;
|
|
26
26
|
}>;
|
|
27
|
-
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export interface ImageVisionAdapter {
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* This method is called to validate the configuration of the adapter
|
|
5
|
+
* and should throw a clear user-readable error if the configuration is invalid.
|
|
6
|
+
*/
|
|
7
|
+
validate(): void;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Input file extension supported
|
|
11
|
+
*/
|
|
12
|
+
inputFileExtensionSupported(): string[];
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* This method should generate an image based on the provided prompt and input files.
|
|
16
|
+
* @param prompt - The prompt to generate the image
|
|
17
|
+
* @param inputFileUrls - An array of input file paths (optional)
|
|
18
|
+
* @returns A promise that resolves to an object containing the generated image and any error message
|
|
19
|
+
*/
|
|
20
|
+
generate({
|
|
21
|
+
prompt,
|
|
22
|
+
inputFileUrls,
|
|
23
|
+
}: {
|
|
24
|
+
prompt: string,
|
|
25
|
+
inputFileUrls: string[],
|
|
26
|
+
}): Promise<{
|
|
27
|
+
response: string;
|
|
28
|
+
error?: string;
|
|
29
|
+
}>;
|
|
30
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
|
|
2
|
+
/**
|
|
3
|
+
* Might have implementations like RAM, Redis, Memcached,
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
export interface KeyValueAdapter {
|
|
7
|
+
|
|
8
|
+
get(key: string): Promise<string | null>;
|
|
9
|
+
|
|
10
|
+
set(key: string, value: string, expiresInSeconds?: number): Promise<void>;
|
|
11
|
+
|
|
12
|
+
delete(key: string): Promise<void>;
|
|
13
|
+
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export type { EmailAdapter } from './EmailAdapter.js';
|
|
2
|
+
export type { CompletionAdapter } from './CompletionAdapter.js';
|
|
3
|
+
export type { ImageGenerationAdapter } from './ImageGenerationAdapter.js';
|
|
4
|
+
export type { KeyValueAdapter } from './KeyValueAdapter.js';
|
|
5
|
+
export type { ImageVisionAdapter } from './ImageVisionAdapter.js';
|
|
6
|
+
export type { OAuth2Adapter } from './OAuth2Adapter.js';
|
|
7
|
+
export type { StorageAdapter } from './StorageAdapter.js';
|
|
8
|
+
export type { CaptchaAdapter } from './CaptchaAdapter.js';
|
package/dist/spa/src/utils.ts
CHANGED
|
@@ -1,24 +1,32 @@
|
|
|
1
1
|
import { onMounted, ref, resolveComponent } from 'vue';
|
|
2
2
|
import type { CoreConfig } from './spa_types/core';
|
|
3
|
-
import type { ValidationObject } from './types/
|
|
3
|
+
import type { ValidationObject } from './types/Common.js';
|
|
4
4
|
import router from "./router";
|
|
5
5
|
import { useCoreStore } from './stores/core';
|
|
6
6
|
import { useUserStore } from './stores/user';
|
|
7
7
|
import { Dropdown } from 'flowbite';
|
|
8
8
|
import adminforth from './adminforth';
|
|
9
9
|
import sanitizeHtml from 'sanitize-html'
|
|
10
|
+
import debounce from 'debounce';
|
|
11
|
+
import type { AdminForthResourceColumnInputCommon, Predicate } from '@/types/Common';
|
|
12
|
+
import { i18nInstance } from './i18n'
|
|
10
13
|
|
|
11
14
|
const LS_LANG_KEY = `afLanguage`;
|
|
15
|
+
const MAX_CONSECUTIVE_EMPTY_RESULTS = 2;
|
|
16
|
+
const ITEMS_PER_PAGE_LIMIT = 100;
|
|
12
17
|
|
|
13
|
-
export async function callApi({path, method, body
|
|
18
|
+
export async function callApi({path, method, body, headers}: {
|
|
14
19
|
path: string, method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'
|
|
15
20
|
body?: any
|
|
21
|
+
headers?: Record<string, string>
|
|
16
22
|
}): Promise<any> {
|
|
23
|
+
const t = i18nInstance?.global.t || ((s: string) => s)
|
|
17
24
|
const options = {
|
|
18
25
|
method,
|
|
19
26
|
headers: {
|
|
20
27
|
'Content-Type': 'application/json',
|
|
21
28
|
'accept-language': localStorage.getItem(LS_LANG_KEY) || 'en',
|
|
29
|
+
...headers
|
|
22
30
|
},
|
|
23
31
|
body: JSON.stringify(body),
|
|
24
32
|
};
|
|
@@ -27,6 +35,7 @@ export async function callApi({path, method, body=undefined}: {
|
|
|
27
35
|
const r = await fetch(fullPath, options);
|
|
28
36
|
if (r.status == 401 ) {
|
|
29
37
|
useUserStore().unauthorize();
|
|
38
|
+
useCoreStore().resetAdminUser();
|
|
30
39
|
await router.push({ name: 'login' });
|
|
31
40
|
return null;
|
|
32
41
|
}
|
|
@@ -35,18 +44,18 @@ export async function callApi({path, method, body=undefined}: {
|
|
|
35
44
|
// if it is internal error, say to user
|
|
36
45
|
if (e instanceof TypeError && e.message === 'Failed to fetch') {
|
|
37
46
|
// this is a network error
|
|
38
|
-
adminforth.alert({variant:'danger', message:
|
|
47
|
+
adminforth.alert({variant:'danger', message: t('Network error, please check your Internet connection and try again'),})
|
|
39
48
|
return null;
|
|
40
49
|
}
|
|
41
50
|
|
|
42
|
-
adminforth.alert({variant:'danger', message:
|
|
51
|
+
adminforth.alert({variant:'danger', message: t('Something went wrong, please try again later'),})
|
|
43
52
|
console.error(`error in callApi ${path}`, e);
|
|
44
53
|
}
|
|
45
54
|
}
|
|
46
55
|
|
|
47
56
|
export async function callAdminForthApi({ path, method, body=undefined, headers=undefined }: {
|
|
48
57
|
path: string,
|
|
49
|
-
method: 'GET' | 'POST' | 'PUT' | 'DELETE',
|
|
58
|
+
method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH',
|
|
50
59
|
body?: any,
|
|
51
60
|
headers?: Record<string, string>
|
|
52
61
|
}): Promise<any> {
|
|
@@ -92,13 +101,14 @@ export const loadFile = (file: string) => {
|
|
|
92
101
|
}
|
|
93
102
|
|
|
94
103
|
export function checkEmptyValues(value: any, viewType: 'show' | 'list' ) {
|
|
95
|
-
const config: CoreConfig | {} = useCoreStore().config;
|
|
104
|
+
const config: CoreConfig | {} | null = useCoreStore().config;
|
|
96
105
|
let emptyFieldPlaceholder = '';
|
|
97
|
-
if (config
|
|
98
|
-
|
|
99
|
-
|
|
106
|
+
if (config && 'emptyFieldPlaceholder' in config) {
|
|
107
|
+
const efp = (config as CoreConfig).emptyFieldPlaceholder;
|
|
108
|
+
if(typeof efp === 'string') {
|
|
109
|
+
emptyFieldPlaceholder = efp;
|
|
100
110
|
} else {
|
|
101
|
-
emptyFieldPlaceholder =
|
|
111
|
+
emptyFieldPlaceholder = efp?.[viewType] || '';
|
|
102
112
|
}
|
|
103
113
|
if (value === null || value === undefined || value === '') {
|
|
104
114
|
return emptyFieldPlaceholder;
|
|
@@ -179,7 +189,7 @@ export function verySimpleHash(str: string): string {
|
|
|
179
189
|
return `${str.split('').reduce((a, b)=>{a=((a<<5)-a)+b.charCodeAt(0);return a&a},0)}`;
|
|
180
190
|
}
|
|
181
191
|
|
|
182
|
-
export function humanifySize(size) {
|
|
192
|
+
export function humanifySize(size: number) {
|
|
183
193
|
if (!size) {
|
|
184
194
|
return '';
|
|
185
195
|
}
|
|
@@ -208,4 +218,274 @@ export function protectAgainstXSS(value: string) {
|
|
|
208
218
|
'img': [ 'src', 'srcset', 'alt', 'title', 'width', 'height', 'loading' ]
|
|
209
219
|
}
|
|
210
220
|
});
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export function isPolymorphicColumn(column: any): boolean {
|
|
224
|
+
return !!(column.foreignResource?.polymorphicResources && column.foreignResource.polymorphicResources.length > 0);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export function handleForeignResourcePagination(
|
|
228
|
+
column: any,
|
|
229
|
+
items: any[],
|
|
230
|
+
emptyResultsCount: number = 0,
|
|
231
|
+
isSearching: boolean = false
|
|
232
|
+
): { hasMore: boolean; emptyResultsCount: number } {
|
|
233
|
+
const isPolymorphic = isPolymorphicColumn(column);
|
|
234
|
+
|
|
235
|
+
if (isPolymorphic) {
|
|
236
|
+
if (isSearching) {
|
|
237
|
+
return {
|
|
238
|
+
hasMore: items.length > 0,
|
|
239
|
+
emptyResultsCount: 0
|
|
240
|
+
};
|
|
241
|
+
} else {
|
|
242
|
+
if (items.length === 0) {
|
|
243
|
+
const newEmptyCount = emptyResultsCount + 1;
|
|
244
|
+
return {
|
|
245
|
+
hasMore: newEmptyCount < MAX_CONSECUTIVE_EMPTY_RESULTS, // Stop loading after 2 consecutive empty results
|
|
246
|
+
emptyResultsCount: newEmptyCount
|
|
247
|
+
};
|
|
248
|
+
} else {
|
|
249
|
+
return {
|
|
250
|
+
hasMore: true,
|
|
251
|
+
emptyResultsCount: 0
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
} else {
|
|
256
|
+
return {
|
|
257
|
+
hasMore: items.length === ITEMS_PER_PAGE_LIMIT,
|
|
258
|
+
emptyResultsCount: 0
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export async function loadMoreForeignOptions({
|
|
264
|
+
columnName,
|
|
265
|
+
searchTerm = '',
|
|
266
|
+
columns,
|
|
267
|
+
resourceId,
|
|
268
|
+
columnOptions,
|
|
269
|
+
columnLoadingState,
|
|
270
|
+
columnOffsets,
|
|
271
|
+
columnEmptyResultsCount
|
|
272
|
+
}: {
|
|
273
|
+
columnName: string;
|
|
274
|
+
searchTerm?: string;
|
|
275
|
+
columns: any[];
|
|
276
|
+
resourceId: string;
|
|
277
|
+
columnOptions: any;
|
|
278
|
+
columnLoadingState: any;
|
|
279
|
+
columnOffsets: any;
|
|
280
|
+
columnEmptyResultsCount: any;
|
|
281
|
+
}) {
|
|
282
|
+
const column = columns?.find(c => c.name === columnName);
|
|
283
|
+
if (!column || !column.foreignResource) return;
|
|
284
|
+
|
|
285
|
+
const state = columnLoadingState[columnName];
|
|
286
|
+
if (state.loading || !state.hasMore) return;
|
|
287
|
+
|
|
288
|
+
state.loading = true;
|
|
289
|
+
|
|
290
|
+
try {
|
|
291
|
+
const list = await callAdminForthApi({
|
|
292
|
+
method: 'POST',
|
|
293
|
+
path: `/get_resource_foreign_data`,
|
|
294
|
+
body: {
|
|
295
|
+
resourceId,
|
|
296
|
+
column: columnName,
|
|
297
|
+
limit: 100,
|
|
298
|
+
offset: columnOffsets[columnName],
|
|
299
|
+
search: searchTerm,
|
|
300
|
+
},
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
if (!list || !Array.isArray(list.items)) {
|
|
304
|
+
console.warn(`Unexpected API response for column ${columnName}:`, list);
|
|
305
|
+
state.hasMore = false;
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (!columnOptions.value) {
|
|
310
|
+
columnOptions.value = {};
|
|
311
|
+
}
|
|
312
|
+
if (!columnOptions.value[columnName]) {
|
|
313
|
+
columnOptions.value[columnName] = [];
|
|
314
|
+
}
|
|
315
|
+
columnOptions.value[columnName].push(...list.items);
|
|
316
|
+
|
|
317
|
+
columnOffsets[columnName] += 100;
|
|
318
|
+
|
|
319
|
+
const paginationResult = handleForeignResourcePagination(
|
|
320
|
+
column,
|
|
321
|
+
list.items,
|
|
322
|
+
columnEmptyResultsCount[columnName] || 0,
|
|
323
|
+
false // not searching
|
|
324
|
+
);
|
|
325
|
+
|
|
326
|
+
columnEmptyResultsCount[columnName] = paginationResult.emptyResultsCount;
|
|
327
|
+
state.hasMore = paginationResult.hasMore;
|
|
328
|
+
|
|
329
|
+
} catch (error) {
|
|
330
|
+
console.error('Error loading more options:', error);
|
|
331
|
+
} finally {
|
|
332
|
+
state.loading = false;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
export async function searchForeignOptions({
|
|
337
|
+
columnName,
|
|
338
|
+
searchTerm,
|
|
339
|
+
columns,
|
|
340
|
+
resourceId,
|
|
341
|
+
columnOptions,
|
|
342
|
+
columnLoadingState,
|
|
343
|
+
columnOffsets,
|
|
344
|
+
columnEmptyResultsCount
|
|
345
|
+
}: {
|
|
346
|
+
columnName: string;
|
|
347
|
+
searchTerm: string;
|
|
348
|
+
columns: any[];
|
|
349
|
+
resourceId: string;
|
|
350
|
+
columnOptions: any;
|
|
351
|
+
columnLoadingState: any;
|
|
352
|
+
columnOffsets: any;
|
|
353
|
+
columnEmptyResultsCount: any;
|
|
354
|
+
}) {
|
|
355
|
+
const column = columns?.find(c => c.name === columnName);
|
|
356
|
+
|
|
357
|
+
if (!column || !column.foreignResource || !column.foreignResource.searchableFields) {
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const state = columnLoadingState[columnName];
|
|
362
|
+
if (state.loading) return;
|
|
363
|
+
|
|
364
|
+
state.loading = true;
|
|
365
|
+
|
|
366
|
+
try {
|
|
367
|
+
const list = await callAdminForthApi({
|
|
368
|
+
method: 'POST',
|
|
369
|
+
path: `/get_resource_foreign_data`,
|
|
370
|
+
body: {
|
|
371
|
+
resourceId,
|
|
372
|
+
column: columnName,
|
|
373
|
+
limit: 100,
|
|
374
|
+
offset: 0,
|
|
375
|
+
search: searchTerm,
|
|
376
|
+
},
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
if (!list || !Array.isArray(list.items)) {
|
|
380
|
+
console.warn(`Unexpected API response for column ${columnName}:`, list);
|
|
381
|
+
state.hasMore = false;
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
if (!columnOptions.value) {
|
|
386
|
+
columnOptions.value = {};
|
|
387
|
+
}
|
|
388
|
+
columnOptions.value[columnName] = list.items;
|
|
389
|
+
columnOffsets[columnName] = 100;
|
|
390
|
+
|
|
391
|
+
const paginationResult = handleForeignResourcePagination(
|
|
392
|
+
column,
|
|
393
|
+
list.items,
|
|
394
|
+
columnEmptyResultsCount[columnName] || 0,
|
|
395
|
+
true // is searching
|
|
396
|
+
);
|
|
397
|
+
|
|
398
|
+
columnEmptyResultsCount[columnName] = paginationResult.emptyResultsCount;
|
|
399
|
+
state.hasMore = paginationResult.hasMore;
|
|
400
|
+
|
|
401
|
+
} catch (error) {
|
|
402
|
+
console.error('Error searching options:', error);
|
|
403
|
+
} finally {
|
|
404
|
+
state.loading = false;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
export function createSearchInputHandlers(
|
|
409
|
+
columns: any[],
|
|
410
|
+
searchFunction: (columnName: string, searchTerm: string) => void,
|
|
411
|
+
getDebounceMs?: (column: any) => number
|
|
412
|
+
) {
|
|
413
|
+
if (!columns) return {};
|
|
414
|
+
|
|
415
|
+
return columns.reduce((acc, c) => {
|
|
416
|
+
if (c.foreignResource && c.foreignResource.searchableFields) {
|
|
417
|
+
const debounceMs = getDebounceMs ? getDebounceMs(c) : 300;
|
|
418
|
+
return {
|
|
419
|
+
...acc,
|
|
420
|
+
[c.name]: debounce((searchTerm: string) => {
|
|
421
|
+
searchFunction(c.name, searchTerm);
|
|
422
|
+
}, debounceMs),
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
return acc;
|
|
426
|
+
}, {} as Record<string, (searchTerm: string) => void>);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
export function checkShowIf(c: AdminForthResourceColumnInputCommon, record: Record<string, any>) {
|
|
430
|
+
if (!c.showIf) return true;
|
|
431
|
+
|
|
432
|
+
const evaluatePredicate = (predicate: Predicate): boolean => {
|
|
433
|
+
const results: boolean[] = [];
|
|
434
|
+
|
|
435
|
+
if ("$and" in predicate) {
|
|
436
|
+
results.push(predicate.$and.every(evaluatePredicate));
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
if ("$or" in predicate) {
|
|
440
|
+
results.push(predicate.$or.some(evaluatePredicate));
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
const fieldEntries = Object.entries(predicate).filter(([key]) => !key.startsWith('$'));
|
|
444
|
+
if (fieldEntries.length > 0) {
|
|
445
|
+
const fieldResult = fieldEntries.every(([field, condition]) => {
|
|
446
|
+
const recordValue = record[field];
|
|
447
|
+
|
|
448
|
+
if (condition === undefined) {
|
|
449
|
+
return true;
|
|
450
|
+
}
|
|
451
|
+
if (typeof condition !== "object" || condition === null) {
|
|
452
|
+
return recordValue === condition;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
if ("$eq" in condition) return recordValue === condition.$eq;
|
|
456
|
+
if ("$not" in condition) return recordValue !== condition.$not;
|
|
457
|
+
if ("$gt" in condition) return recordValue > condition.$gt;
|
|
458
|
+
if ("$gte" in condition) return recordValue >= condition.$gte;
|
|
459
|
+
if ("$lt" in condition) return recordValue < condition.$lt;
|
|
460
|
+
if ("$lte" in condition) return recordValue <= condition.$lte;
|
|
461
|
+
if ("$in" in condition) return (Array.isArray(condition.$in) && condition.$in.includes(recordValue));
|
|
462
|
+
if ("$nin" in condition) return (Array.isArray(condition.$nin) && !condition.$nin.includes(recordValue));
|
|
463
|
+
if ("$includes" in condition)
|
|
464
|
+
return (
|
|
465
|
+
Array.isArray(recordValue) &&
|
|
466
|
+
recordValue.includes(condition.$includes)
|
|
467
|
+
);
|
|
468
|
+
if ("$nincludes" in condition)
|
|
469
|
+
return (
|
|
470
|
+
Array.isArray(recordValue) &&
|
|
471
|
+
!recordValue.includes(condition.$nicludes)
|
|
472
|
+
);
|
|
473
|
+
|
|
474
|
+
return true;
|
|
475
|
+
});
|
|
476
|
+
results.push(fieldResult);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
return results.every(result => result);
|
|
480
|
+
};
|
|
481
|
+
|
|
482
|
+
return evaluatePredicate(c.showIf);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
export function btoa_function(source: string): string {
|
|
486
|
+
return btoa(source);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
export function atob_function(source: string): string {
|
|
490
|
+
return atob(source);
|
|
211
491
|
}
|