adminforth 2.4.0-next.21 → 2.4.0-next.211
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/cli.js +10 -3
- package/commands/createApp/templates/custom/tsconfig.json.hbs +2 -3
- package/commands/createApp/templates/index.ts.hbs +10 -2
- package/commands/createApp/templates/package.json.hbs +1 -1
- package/commands/createApp/utils.js +27 -2
- package/commands/createCustomComponent/configLoader.js +3 -0
- package/commands/createCustomComponent/main.js +1 -0
- package/commands/createCustomComponent/templates/customCrud/beforeActionButtons.vue.hbs +38 -0
- package/commands/createPlugin/templates/custom/tsconfig.json.hbs +2 -5
- package/commands/createPlugin/templates/package.json.hbs +1 -1
- package/dist/auth.d.ts +9 -1
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +15 -2
- package/dist/auth.js.map +1 -1
- package/dist/dataConnectors/baseConnector.d.ts.map +1 -1
- package/dist/dataConnectors/baseConnector.js +46 -15
- 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 +44 -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 +11 -0
- 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 +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +20 -9
- 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 +52 -8
- package/dist/modules/codeInjector.js.map +1 -1
- package/dist/modules/configValidator.d.ts.map +1 -1
- package/dist/modules/configValidator.js +74 -7
- package/dist/modules/configValidator.js.map +1 -1
- package/dist/modules/restApi.d.ts.map +1 -1
- package/dist/modules/restApi.js +154 -26
- package/dist/modules/restApi.js.map +1 -1
- package/dist/modules/styles.d.ts +503 -13
- package/dist/modules/styles.d.ts.map +1 -1
- package/dist/modules/styles.js +559 -31
- package/dist/modules/styles.js.map +1 -1
- package/dist/modules/utils.d.ts +2 -0
- package/dist/modules/utils.d.ts.map +1 -1
- package/dist/modules/utils.js +16 -0
- package/dist/modules/utils.js.map +1 -1
- package/dist/servers/express.d.ts.map +1 -1
- package/dist/servers/express.js +14 -0
- package/dist/servers/express.js.map +1 -1
- package/dist/spa/index.html +1 -1
- package/dist/spa/package-lock.json +5 -4
- package/dist/spa/package.json +1 -1
- package/dist/spa/src/App.vue +54 -169
- package/dist/spa/src/adminforth.ts +42 -18
- 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 +44 -27
- package/dist/spa/src/afcl/Dropzone.vue +12 -12
- package/dist/spa/src/afcl/Input.vue +6 -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 +68 -34
- package/dist/spa/src/afcl/Skeleton.vue +6 -6
- package/dist/spa/src/afcl/Table.vue +213 -74
- 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 +1 -2
- 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 +7 -7
- package/dist/spa/src/components/Breadcrumbs.vue +5 -5
- 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 -8
- package/dist/spa/src/components/ErrorMessage.vue +21 -0
- package/dist/spa/src/components/Filters.vue +85 -39
- 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 +78 -80
- package/dist/spa/src/components/ResourceListTableVirtual.vue +71 -73
- package/dist/spa/src/components/ShowTable.vue +17 -12
- package/dist/spa/src/components/Sidebar.vue +448 -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 +73 -14
- package/dist/spa/src/components/Toast.vue +27 -9
- package/dist/spa/src/components/UserMenuSettingsButton.vue +70 -0
- package/dist/spa/src/components/ValueRenderer.vue +43 -16
- package/dist/spa/src/controls/BoolToggle.vue +34 -0
- package/dist/spa/src/i18n.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 +1 -1
- package/dist/spa/src/stores/filters.ts +29 -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 +137 -22
- package/dist/spa/src/types/Common.ts +67 -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/CompletionAdapter.ts +25 -0
- package/dist/spa/src/types/adapters/EmailAdapter.ts +27 -0
- package/dist/spa/src/types/adapters/ImageGenerationAdapter.ts +50 -0
- 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/OAuth2Adapter.ts +34 -0
- package/dist/spa/src/types/adapters/StorageAdapter.ts +73 -0
- package/dist/spa/src/types/adapters/index.ts +8 -0
- package/dist/spa/src/utils.ts +219 -8
- package/dist/spa/src/views/CreateView.vue +18 -19
- package/dist/spa/src/views/EditView.vue +25 -19
- package/dist/spa/src/views/ListView.vue +139 -86
- package/dist/spa/src/views/LoginView.vue +31 -37
- 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 +59 -39
- 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 +115 -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 +59 -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/CompletionAdapter.d.ts +20 -0
- package/dist/types/adapters/CompletionAdapter.d.ts.map +1 -0
- package/dist/types/adapters/CompletionAdapter.js +2 -0
- package/dist/types/adapters/CompletionAdapter.js.map +1 -0
- package/dist/types/adapters/EmailAdapter.d.ts +20 -0
- package/dist/types/adapters/EmailAdapter.d.ts.map +1 -0
- package/dist/types/adapters/EmailAdapter.js +2 -0
- package/dist/types/adapters/EmailAdapter.js.map +1 -0
- package/dist/types/adapters/ImageGenerationAdapter.d.ts +37 -0
- package/dist/types/adapters/ImageGenerationAdapter.d.ts.map +1 -0
- package/dist/types/adapters/ImageGenerationAdapter.js +2 -0
- package/dist/types/adapters/ImageGenerationAdapter.js.map +1 -0
- 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/OAuth2Adapter.d.ts +32 -0
- package/dist/types/adapters/OAuth2Adapter.d.ts.map +1 -0
- package/dist/types/adapters/OAuth2Adapter.js +2 -0
- package/dist/types/adapters/OAuth2Adapter.js.map +1 -0
- package/dist/types/adapters/StorageAdapter.d.ts +63 -0
- package/dist/types/adapters/StorageAdapter.d.ts.map +1 -0
- package/dist/types/adapters/StorageAdapter.js +2 -0
- package/dist/types/adapters/StorageAdapter.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 +3 -2
- package/dist/spa/src/types/Adapters.ts +0 -213
- package/dist/types/Adapters.d.ts +0 -168
- package/dist/types/Adapters.d.ts.map +0 -1
- package/dist/types/Adapters.js +0 -2
- package/dist/types/Adapters.js.map +0 -1
|
@@ -1,13 +1,8 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
|
|
3
3
|
|
|
4
|
-
<div class="flex items-center w-full p-4
|
|
4
|
+
<div class="flex items-center w-full p-4 rounded-lg shadow-lg dark:text-darkToastText dark:bg-darkToastBackground bg-lightToastBackground text-lightToastText"
|
|
5
5
|
role="alert"
|
|
6
|
-
:class="
|
|
7
|
-
{
|
|
8
|
-
'danger': 'bg-red-100',
|
|
9
|
-
}[toast.variant]
|
|
10
|
-
"
|
|
11
6
|
>
|
|
12
7
|
<div v-if="toast.variant == 'info'" class="inline-flex items-center justify-center flex-shrink-0 w-8 h-8 text-lightPrimary dark:text-darkPrimary bg-lightPrimaryOpacity rounded-lg dark:bg-blue-800 dark:text-blue-200">
|
|
13
8
|
<svg class="w-4 h-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 18 20">
|
|
@@ -35,8 +30,19 @@
|
|
|
35
30
|
</div>
|
|
36
31
|
|
|
37
32
|
<div class="ms-3 text-sm font-normal max-w-xs pr-2" v-if="toast.messageHtml" v-html="toast.messageHtml"></div>
|
|
38
|
-
<div class="ms-3 text-sm font-normal max-w-xs pr-2" v-else>
|
|
39
|
-
|
|
33
|
+
<div class="ms-3 text-sm font-normal max-w-xs pr-2" v-else>
|
|
34
|
+
<div class="flex flex-col items-center justify-center">
|
|
35
|
+
{{toast.message}}
|
|
36
|
+
<div v-if="toast.buttons" class="flex justify-center mt-2 gap-2">
|
|
37
|
+
<div v-for="button in toast.buttons" class="rounded-md bg-lightButtonsBackground hover:bg-lightButtonsHover text-lightButtonsText dark:bg-darkPrimary dark:hover:bg-darkButtonsBackground dark:text-darkButtonsText">
|
|
38
|
+
<button @click="onButtonClick(button.value)" class="px-2 py-1 rounded hover:bg-black/5 dark:hover:bg-white/10">
|
|
39
|
+
{{ button.label }}
|
|
40
|
+
</button>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
<button @click="closeToast" type="button" class="ms-auto -mx-1.5 -my-1.5 bg-lightToastCloseIconBackground text-lightToastCloseIcon hover:text-lightToastCloseIconHover rounded-lg focus:ring-2 focus:ring-lightToastCloseIconFocusRing p-1.5 hover:bg-lightToastCloseIconBackgroundHover inline-flex items-center justify-center h-8 w-8 dark:text-darkToastCloseIcon dark:hover:text-darkToastCloseIconHover dark:bg-darkToastCloseIconBackground dark:hover:bg-darkToastCloseIconBackgroundHover dark:focus:ring-darkToastCloseIconFocusRing" >
|
|
40
46
|
<svg class="w-3 h-3" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
|
|
41
47
|
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6"/>
|
|
42
48
|
</svg>
|
|
@@ -58,16 +64,28 @@ const props = defineProps<{
|
|
|
58
64
|
variant: string;
|
|
59
65
|
id: string;
|
|
60
66
|
timeout?: number|'unlimited';
|
|
67
|
+
buttons?: { value: any; label: string }[];
|
|
61
68
|
}
|
|
62
69
|
}>();
|
|
63
70
|
function closeToast() {
|
|
71
|
+
// resolve with undefined on close (X button)
|
|
72
|
+
toastStore.resolveToast(props.toast.id);
|
|
73
|
+
emit('close');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function onButtonClick(value: any) {
|
|
77
|
+
toastStore.resolveToast(props.toast.id, value);
|
|
64
78
|
emit('close');
|
|
65
79
|
}
|
|
66
80
|
|
|
67
81
|
onMounted(() => {
|
|
68
82
|
if (props.toast.timeout === 'unlimited') return;
|
|
69
83
|
else {
|
|
70
|
-
setTimeout(() => {
|
|
84
|
+
setTimeout(() => {
|
|
85
|
+
// resolve with undefined on auto-timeout
|
|
86
|
+
toastStore.resolveToast(props.toast.id);
|
|
87
|
+
emit('close');
|
|
88
|
+
}, (props.toast.timeout || 10) * 1e3 );
|
|
71
89
|
}
|
|
72
90
|
});
|
|
73
91
|
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="min-w-40">
|
|
3
|
+
<div class="cursor-pointer flex items-center justify-between gap-1 block px-4 py-2 text-sm
|
|
4
|
+
bg-lightUserMenuSettingsButtonBackground hover:bg-lightUserMenuSettingsButtonBackgroundHover
|
|
5
|
+
text-lightUserMenuSettingsButtonText hover:text-lightUserMenuSettingsButtonTextHover
|
|
6
|
+
dark:bg-darkUserMenuSettingsButtonBackground dark:hover:bg-darkUserMenuSettingsButtonBackgroundHover
|
|
7
|
+
dark:text-darkUserMenuSettingsButtonText dark:hover:text-darkUserMenuSettingsButtonTextHover
|
|
8
|
+
w-full select-none "
|
|
9
|
+
:class="{ 'bg-lightUserMenuSettingsButtonBackgroundExpanded hover:bg-lightUserMenuSettingsButtonBackgroundExpanded dark:bg-darkUserMenuSettingsButtonBackgroundExpanded hover:dark:bg-darkUserMenuSettingsButtonBackgroundExpanded ': showDropdown }"
|
|
10
|
+
@click="showDropdown = !showDropdown"
|
|
11
|
+
>
|
|
12
|
+
<span>Settings</span>
|
|
13
|
+
<IconCaretDownSolid class="h-5 w-5 text-lightPrimary dark:text-gray-400 opacity-50 transition duration-150 ease-in"
|
|
14
|
+
:class="{ 'transform rotate-180': showDropdown }"
|
|
15
|
+
/>
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
<div v-if="showDropdown" >
|
|
19
|
+
|
|
20
|
+
<router-link class="cursor-pointer flex items-center gap-1 block px-4 py-1 text-sm
|
|
21
|
+
bg-lightUserMenuSettingsButtonDropdownItemBackground hover:bg-lightUserMenuSettingsButtonDropdownItemBackgroundHover
|
|
22
|
+
text-lightUserMenuSettingsButtonDropdownItemText hover:text-lightUserMenuSettingsButtonDropdownItemTextHover
|
|
23
|
+
dark:bg-darkUserMenuSettingsButtonDropdownItemBackground dark:hover:bg-darkUserMenuSettingsButtonDropdownItemBackgroundHover
|
|
24
|
+
dark:text-darkUserMenuSettingsButtonDropdownItemText dark:hover:text-darkUserMenuSettingsButtonDropdownItemTextHover
|
|
25
|
+
w-full text-select-none pl-5 select-none"
|
|
26
|
+
v-for="option in options"
|
|
27
|
+
:to="getRoute(option)"
|
|
28
|
+
>
|
|
29
|
+
<span class="mr-1">
|
|
30
|
+
<component v-if="option.icon" :is="getIcon(option.icon)" class="w-5 h-5 transition duration-75" ></component>
|
|
31
|
+
</span>
|
|
32
|
+
<span>{{ option.pageLabel }}</span>
|
|
33
|
+
</router-link>
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
</div>
|
|
38
|
+
</template>
|
|
39
|
+
|
|
40
|
+
<script setup lang="ts">
|
|
41
|
+
import { IconCaretDownSolid } from '@iconify-prerendered/vue-flowbite';
|
|
42
|
+
import { computed, ref, onMounted, watch } from 'vue';
|
|
43
|
+
import { useCoreStore } from '@/stores/core';
|
|
44
|
+
import { getIcon } from '@/utils';
|
|
45
|
+
import { useRouter } from 'vue-router';
|
|
46
|
+
|
|
47
|
+
const router = useRouter();
|
|
48
|
+
const coreStore = useCoreStore();
|
|
49
|
+
|
|
50
|
+
const showDropdown = ref(false);
|
|
51
|
+
const props = defineProps(['meta', 'resource']);
|
|
52
|
+
|
|
53
|
+
const options = computed(() => {
|
|
54
|
+
return coreStore.config?.settingPages?.map((page) => {
|
|
55
|
+
return {
|
|
56
|
+
pageLabel: page.pageLabel,
|
|
57
|
+
slug: page.slug || null,
|
|
58
|
+
icon: page.icon || null,
|
|
59
|
+
};
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
function getRoute(option: { slug?: string | null, pageLabel: string }) {
|
|
64
|
+
return {
|
|
65
|
+
name: 'settings',
|
|
66
|
+
params: { page: option.slug }
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
</script>
|
|
@@ -12,13 +12,40 @@
|
|
|
12
12
|
>
|
|
13
13
|
<RouterLink
|
|
14
14
|
class="font-medium text-lightSidebarText dark:text-darkSidebarText hover:brightness-110 whitespace-nowrap"
|
|
15
|
-
:to="{
|
|
15
|
+
:to="{
|
|
16
|
+
name: 'resource-show',
|
|
17
|
+
params: {
|
|
18
|
+
primaryKey: foreignResource.pk,
|
|
19
|
+
resourceId: column.foreignResource
|
|
20
|
+
? (
|
|
21
|
+
column.foreignResource.resourceId
|
|
22
|
+
|| column.foreignResource.polymorphicResources?.find(
|
|
23
|
+
(pr: any) => pr.whenValue === record[column.foreignResource?.polymorphicOn!]
|
|
24
|
+
)?.resourceId
|
|
25
|
+
)
|
|
26
|
+
: undefined
|
|
27
|
+
}
|
|
28
|
+
}"
|
|
16
29
|
>
|
|
17
30
|
{{ foreignResource.label }}
|
|
18
31
|
</RouterLink>
|
|
19
32
|
</span>
|
|
20
33
|
<RouterLink v-else-if="record[column.name]" class="font-medium text-lightPrimary dark:text-darkPrimary hover:brightness-110 whitespace-nowrap"
|
|
21
|
-
|
|
34
|
+
:to="{
|
|
35
|
+
name: 'resource-show',
|
|
36
|
+
params: {
|
|
37
|
+
primaryKey: record[column.name].pk,
|
|
38
|
+
resourceId: column.foreignResource
|
|
39
|
+
? (
|
|
40
|
+
column.foreignResource.resourceId
|
|
41
|
+
|| column.foreignResource.polymorphicResources?.find(
|
|
42
|
+
(pr: any) => pr.whenValue === record[column.foreignResource?.polymorphicOn!]
|
|
43
|
+
)?.resourceId
|
|
44
|
+
)
|
|
45
|
+
: undefined
|
|
46
|
+
}
|
|
47
|
+
}"
|
|
48
|
+
>
|
|
22
49
|
{{ record[column.name].label }}
|
|
23
50
|
</RouterLink>
|
|
24
51
|
<div v-else>
|
|
@@ -27,8 +54,8 @@
|
|
|
27
54
|
</span>
|
|
28
55
|
|
|
29
56
|
<span v-else-if="column.type === 'boolean'">
|
|
30
|
-
<span v-if="record[column.name] === true" class="bg-green-100 whitespace-nowrap text-green-800 text-xs font-medium me-2 px-2.5 py-0.5 rounded dark:bg-gray-700 dark:text-green-400 border border-green-400">{{ $t('Yes') }}</span>
|
|
31
|
-
<span v-else-if="record[column.name] === false" class="bg-red-100 whitespace-nowrap text-red-
|
|
57
|
+
<span v-if="record[column.name] === true" class="af-true-value-icon bg-green-100 whitespace-nowrap text-green-800 text-xs font-medium me-2 px-2.5 py-0.5 rounded dark:bg-gray-700 dark:text-green-400 border border-green-400">{{ $t('Yes') }}</span>
|
|
58
|
+
<span v-else-if="record[column.name] === false" class="af-false-value-icon bg-red-100 whitespace-nowrap text-red-800 text-xs font-medium me-2 px-2.5 py-0.5 rounded dark:bg-gray-700 dark:text-red-400 border border-red-400">{{ $t('No') }}</span>
|
|
32
59
|
<span v-else class="bg-gray-100 whitespace-nowrap text-gray-800 text-xs font-medium me-2 px-2.5 py-0.5 rounded dark:bg-gray-700 dark:text-gray-400 border border-gray-400">{{ $t('Unset') }}</span>
|
|
33
60
|
</span>
|
|
34
61
|
<span
|
|
@@ -38,14 +65,14 @@
|
|
|
38
65
|
<template v-for="(arrayItem, arrayItemIndex) in record[column.name]">
|
|
39
66
|
<span
|
|
40
67
|
v-if="column.isArray.itemType === 'boolean' && arrayItem"
|
|
41
|
-
:key="`${column.name}-${arrayItemIndex}`"
|
|
42
|
-
class="bg-green-100 whitespace-nowrap text-green-800 text-xs font-medium me-2 px-2.5 py-0.5 rounded dark:bg-gray-700 dark:text-green-400 border border-green-400">
|
|
68
|
+
:key="`${column.name}-${arrayItemIndex}-true`"
|
|
69
|
+
class="af-true-value-icon bg-green-100 whitespace-nowrap text-green-800 text-xs font-medium me-2 px-2.5 py-0.5 rounded dark:bg-gray-700 dark:text-green-400 border border-green-400">
|
|
43
70
|
{{ $t('Yes') }}
|
|
44
71
|
</span>
|
|
45
72
|
<span
|
|
46
73
|
v-else-if="column.isArray.itemType === 'boolean'"
|
|
47
|
-
:key="`${column.name}-${arrayItemIndex}`"
|
|
48
|
-
class="bg-red-100 whitespace-nowrap text-red-800 text-xs font-medium me-2 px-2.5 py-0.5 rounded dark:bg-gray-700 dark:text-red-400 border border-red-400">
|
|
74
|
+
:key="`${column.name}-${arrayItemIndex}-false`"
|
|
75
|
+
class="af-false-value-icon bg-red-100 whitespace-nowrap text-red-800 text-xs font-medium me-2 px-2.5 py-0.5 rounded dark:bg-gray-700 dark:text-red-400 border border-red-400">
|
|
49
76
|
{{ $t('No') }}
|
|
50
77
|
</span>
|
|
51
78
|
<span
|
|
@@ -53,30 +80,30 @@
|
|
|
53
80
|
:key="`${column.name}-${arrayItemIndex}`"
|
|
54
81
|
class="rounded-md m-0.5 bg-lightAnnouncementBG dark:bg-darkAnnouncementBG text-lightAnnouncementText dark:text-darkAnnouncementText py-0.5 px-2.5 text-sm"
|
|
55
82
|
>
|
|
56
|
-
{{ checkEmptyValues(getArrayItemDisplayValue(arrayItem, column), route.meta.type) }}
|
|
83
|
+
{{ checkEmptyValues(getArrayItemDisplayValue(arrayItem, column), route.meta.type as "show" | "list") }}
|
|
57
84
|
</span>
|
|
58
85
|
</template>
|
|
59
86
|
</span>
|
|
60
87
|
<span v-else-if="column.enum">
|
|
61
|
-
{{ checkEmptyValues(column.enum.find(e => e.value === record[column.name])?.label || record[column.name], route.meta.type) }}
|
|
88
|
+
{{ checkEmptyValues(column.enum.find(e => e.value === record[column.name])?.label || record[column.name], route.meta.type as "show" | "list") }}
|
|
62
89
|
</span>
|
|
63
90
|
<span v-else-if="column.type === 'datetime'" class="whitespace-nowrap">
|
|
64
|
-
{{ checkEmptyValues(formatDateTime(record[column.name]), route.meta.type) }}
|
|
91
|
+
{{ checkEmptyValues(formatDateTime(record[column.name]), route.meta.type as "show" | "list") }}
|
|
65
92
|
</span>
|
|
66
93
|
<span v-else-if="column.type === 'date'" class="whitespace-nowrap">
|
|
67
|
-
{{ checkEmptyValues(formatDate(record[column.name]), route.meta.type) }}
|
|
94
|
+
{{ checkEmptyValues(formatDate(record[column.name]), route.meta.type as "show" | "list") }}
|
|
68
95
|
</span>
|
|
69
96
|
<span v-else-if="column.type === 'time'" class="whitespace-nowrap">
|
|
70
|
-
{{ checkEmptyValues(formatTime(record[column.name]), route.meta.type) }}
|
|
97
|
+
{{ checkEmptyValues(formatTime(record[column.name]), route.meta.type as "show" | "list") }}
|
|
71
98
|
</span>
|
|
72
99
|
<span v-else-if="column.type === 'decimal'">
|
|
73
|
-
{{ checkEmptyValues(record[column.name] && parseFloat(record[column.name]), route.meta.type) }}
|
|
100
|
+
{{ checkEmptyValues(record[column.name] && parseFloat(record[column.name]), route.meta.type as "show" | "list") }}
|
|
74
101
|
</span>
|
|
75
102
|
<span v-else-if="column.type === 'json'">
|
|
76
103
|
<JsonViewer class="min-w-[6rem]" :value="record[column.name]" :expandDepth="column.extra?.jsonCollapsedLevel" copyable sort :theme="coreStore.theme"/>
|
|
77
104
|
</span>
|
|
78
105
|
<span v-else>
|
|
79
|
-
{{ checkEmptyValues(record[column.name],route.meta.type) }}
|
|
106
|
+
{{ checkEmptyValues(record[column.name], route.meta.type as "show" | "list") }}
|
|
80
107
|
</span>
|
|
81
108
|
</div>
|
|
82
109
|
</template>
|
|
@@ -122,7 +149,7 @@ function formatTime(time: string) {
|
|
|
122
149
|
return dayjs(`0000-00-00 ${time}`).format(coreStore.config?.timeFormat || 'HH:mm:ss');
|
|
123
150
|
}
|
|
124
151
|
|
|
125
|
-
function getArrayItemDisplayValue(value, column) {
|
|
152
|
+
function getArrayItemDisplayValue(value: any, column: AdminForthResourceColumnCommon) {
|
|
126
153
|
if (column.isArray?.itemType === 'datetime') {
|
|
127
154
|
return formatDateTime(value);
|
|
128
155
|
} else if (column.isArray?.itemType === 'date') {
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Toggle
|
|
3
|
+
:disabled="readonly"
|
|
4
|
+
@update:modelValue="$emit('update:value', $event)"
|
|
5
|
+
:modelValue="valueFromRecord"
|
|
6
|
+
>
|
|
7
|
+
<p>{{text}}</p>
|
|
8
|
+
</Toggle>
|
|
9
|
+
</template>
|
|
10
|
+
|
|
11
|
+
<script setup lang="ts">
|
|
12
|
+
import Toggle from '@/afcl/Toggle.vue';
|
|
13
|
+
import type {
|
|
14
|
+
AdminForthResourceColumnCommon,
|
|
15
|
+
AdminForthResourceCommon,
|
|
16
|
+
AdminUser,
|
|
17
|
+
} from "@/types/Common";
|
|
18
|
+
|
|
19
|
+
const props = defineProps<{
|
|
20
|
+
value: boolean,
|
|
21
|
+
text: string,
|
|
22
|
+
column: AdminForthResourceColumnCommon,
|
|
23
|
+
record: any,
|
|
24
|
+
meta: any,
|
|
25
|
+
resource: AdminForthResourceCommon,
|
|
26
|
+
adminUser: AdminUser,
|
|
27
|
+
readonly: boolean
|
|
28
|
+
}>();
|
|
29
|
+
console.log(JSON.stringify(props));
|
|
30
|
+
console.log("Current mode:", props.meta?.mode)
|
|
31
|
+
defineEmits(['update:value']);
|
|
32
|
+
const valueFromRecord = props.record[props.column.name]
|
|
33
|
+
const editReadOnly = props.column.editReadonly;
|
|
34
|
+
</script>
|
package/dist/spa/src/i18n.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { createApp } from 'vue';
|
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
// taken from here https://vue-i18n.intlify.dev/guide/essentials/pluralization.html#custom-pluralization
|
|
6
|
-
function slavicPluralRule(choice, choicesLength, orgRule) {
|
|
6
|
+
function slavicPluralRule(choice: number, choicesLength: number, orgRule: any) {
|
|
7
7
|
if (choice === 0) {
|
|
8
8
|
return 0
|
|
9
9
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<Tooltip>
|
|
3
3
|
<span class="flex items-center">
|
|
4
|
-
{{ visualValue }} <IconFileCopyAltSolid @click.stop="copyToCB" class="w-5 h-5 text-lightPrimary dark:text-darkPrimary" v-if="visualValue"/>
|
|
4
|
+
{{ visualValue }} <IconFileCopyAltSolid @click.stop="copyToCB" class="min-w-5 min-h-5 text-lightPrimary dark:text-darkPrimary" v-if="visualValue"/>
|
|
5
5
|
</span>
|
|
6
6
|
<template #tooltip v-if="visualValue">
|
|
7
7
|
{{ props.record[props.column.name] }}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<Tooltip>
|
|
3
3
|
<span class="flex items-center">
|
|
4
|
-
{{ visualValue }} <IconFileCopyAltSolid @click.stop="copyToCB" class="w-5 h-5 text-lightPrimary dark:text-darkPrimary" v-if="visualValue"/>
|
|
4
|
+
{{ visualValue }} <IconFileCopyAltSolid @click.stop="copyToCB" class="min-w-5 min-h-5 text-lightPrimary dark:text-darkPrimary" v-if="visualValue"/>
|
|
5
5
|
</span>
|
|
6
6
|
<template #tooltip v-if="visualValue">
|
|
7
7
|
{{ props.record[props.column.name] }}
|
|
@@ -62,6 +62,14 @@ const router = createRouter({
|
|
|
62
62
|
},
|
|
63
63
|
]
|
|
64
64
|
},
|
|
65
|
+
{
|
|
66
|
+
path: '/settings/:page?',
|
|
67
|
+
name: 'settings',
|
|
68
|
+
component: () => import('@/views/SettingsView.vue'),
|
|
69
|
+
meta: {
|
|
70
|
+
title: 'Settings',
|
|
71
|
+
},
|
|
72
|
+
},
|
|
65
73
|
/* IMPORTANT:ADMINFORTH ROUTES */
|
|
66
74
|
{ path: "/:pathMatch(.*)*", component: PageNotFound },
|
|
67
75
|
]
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AdminForthResource, AdminForthResourceColumn } from '../types/
|
|
1
|
+
import type { AdminForthResource, AdminForthResourceColumn } from '../types/Back.js';
|
|
2
2
|
|
|
3
3
|
export type resourceById = {
|
|
4
4
|
[key: string]: AdminForthResource;
|
|
@@ -21,7 +21,12 @@ export type ResourceColumns = {
|
|
|
21
21
|
|
|
22
22
|
export type CoreConfig = {
|
|
23
23
|
brandName: string,
|
|
24
|
+
singleTheme?: 'light' | 'dark',
|
|
24
25
|
brandLogo: string,
|
|
26
|
+
iconOnlySidebar: {
|
|
27
|
+
logo?: string,
|
|
28
|
+
enabled?: boolean,
|
|
29
|
+
},
|
|
25
30
|
title: string,
|
|
26
31
|
datesFormat: string,
|
|
27
32
|
timeFormat: string,
|
|
@@ -33,12 +38,19 @@ export type CoreConfig = {
|
|
|
33
38
|
passwordHashField: string,
|
|
34
39
|
loginBackgroundImage: string,
|
|
35
40
|
loginBackgroundPosition: string,
|
|
41
|
+
removeBackgroundBlendMode: boolean,
|
|
36
42
|
userFullnameField: string,
|
|
37
43
|
},
|
|
38
44
|
emptyFieldPlaceholder?: {
|
|
39
45
|
show?: string,
|
|
40
46
|
list?: string,
|
|
41
47
|
} | string,
|
|
48
|
+
|
|
49
|
+
customHeadItems?: {
|
|
50
|
+
tagName: string;
|
|
51
|
+
attributes: { [key: string]: string | boolean };
|
|
52
|
+
innerCode?: string;
|
|
53
|
+
}[],
|
|
42
54
|
}
|
|
43
55
|
|
|
44
56
|
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { ref, type Ref } from 'vue';
|
|
1
|
+
import { ref, computed, type Ref } from 'vue';
|
|
2
2
|
import { defineStore } from 'pinia';
|
|
3
|
+
import { useCoreStore } from './core';
|
|
3
4
|
|
|
4
5
|
export const useFiltersStore = defineStore('filters', () => {
|
|
5
6
|
const filters: Ref<any[]> = ref([]);
|
|
6
7
|
const sort: Ref<any> = ref({});
|
|
8
|
+
const coreStore = useCoreStore();
|
|
7
9
|
|
|
8
10
|
const setSort = (s: any) => {
|
|
9
11
|
sort.value = s;
|
|
@@ -23,5 +25,30 @@ export const useFiltersStore = defineStore('filters', () => {
|
|
|
23
25
|
const clearFilters = () => {
|
|
24
26
|
filters.value = [];
|
|
25
27
|
}
|
|
26
|
-
|
|
28
|
+
|
|
29
|
+
const shouldFilterBeHidden = (fieldName: string) => {
|
|
30
|
+
if (coreStore.resource?.columns) {
|
|
31
|
+
const column = coreStore.resource.columns.find((col: any) => col.name === fieldName);
|
|
32
|
+
if (column?.showIn?.filter !== true) {
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const visibleFiltersCount = computed(() => {
|
|
40
|
+
return filters.value.filter(f => !shouldFilterBeHidden(f.field)).length;
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
setFilter,
|
|
45
|
+
getFilters,
|
|
46
|
+
clearFilters,
|
|
47
|
+
filters,
|
|
48
|
+
setFilters,
|
|
49
|
+
setSort,
|
|
50
|
+
getSort,
|
|
51
|
+
visibleFiltersCount,
|
|
52
|
+
shouldFilterBeHidden
|
|
53
|
+
}
|
|
27
54
|
})
|
|
@@ -29,7 +29,12 @@ export const useModalStore = defineStore('modal', () => {
|
|
|
29
29
|
onCancelFunction.value = func;
|
|
30
30
|
}
|
|
31
31
|
function setModalContent(content: ModalContentType) {
|
|
32
|
-
modalContent.value =
|
|
32
|
+
modalContent.value = {
|
|
33
|
+
title: content.title || 'title',
|
|
34
|
+
content: content.content || 'content',
|
|
35
|
+
acceptText: content.acceptText || 'acceptText',
|
|
36
|
+
cancelText: content.cancelText || 'cancelText',
|
|
37
|
+
};
|
|
33
38
|
}
|
|
34
39
|
function resetmodalState() {
|
|
35
40
|
isOpened.value = false;
|
|
@@ -12,19 +12,38 @@ export const useToastStore = defineStore('toast', () => {
|
|
|
12
12
|
watch(route, () => {
|
|
13
13
|
// on route change clear all toasts older then 5 seconds
|
|
14
14
|
const now = +new Date();
|
|
15
|
-
toasts.value = toasts.value.filter((t) => now - t.createdAt < 5000);
|
|
15
|
+
toasts.value = toasts.value.filter((t) => t?.timeout === 'unlimited' || now - t.createdAt < 5000);
|
|
16
16
|
});
|
|
17
17
|
|
|
18
|
-
const addToast = (toast: {
|
|
18
|
+
const addToast = (toast: {
|
|
19
|
+
message?: string;
|
|
20
|
+
messageHtml?: string;
|
|
21
|
+
variant: string;
|
|
22
|
+
timeout?: number | 'unlimited';
|
|
23
|
+
buttons?: { value: any; label: string }[];
|
|
24
|
+
onResolve?: (value?: any) => void;
|
|
25
|
+
}): string => {
|
|
19
26
|
const toastId = uuid();
|
|
20
27
|
toasts.value.push({
|
|
21
28
|
...toast,
|
|
22
29
|
id: toastId,
|
|
23
30
|
createdAt: +new Date(),
|
|
24
31
|
});
|
|
32
|
+
return toastId;
|
|
25
33
|
};
|
|
26
34
|
const removeToast = (toast: { id: string }) => {
|
|
27
35
|
toasts.value = toasts.value.filter((t) => t.id !== toast.id);
|
|
28
36
|
};
|
|
29
|
-
|
|
37
|
+
|
|
38
|
+
const resolveToast = (toastId: string, value?: any) => {
|
|
39
|
+
const t = toasts.value.find((x) => x.id === toastId);
|
|
40
|
+
try {
|
|
41
|
+
t?.onResolve?.(value);
|
|
42
|
+
} catch {
|
|
43
|
+
// no-op
|
|
44
|
+
}
|
|
45
|
+
toasts.value = toasts.value.filter((x) => x.id !== toastId);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
return { toasts, addToast, removeToast, resolveToast };
|
|
30
49
|
});
|