adminforth 2.13.18 → 2.14.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/modules/configValidator.d.ts.map +1 -1
- package/dist/modules/configValidator.js +10 -3
- package/dist/modules/configValidator.js.map +1 -1
- package/dist/spa/package-lock.json +704 -1203
- package/dist/spa/package.json +32 -32
- package/dist/spa/src/App.vue +14 -21
- package/dist/spa/src/afcl/Dialog.vue +65 -5
- package/dist/spa/src/afcl/Input.vue +5 -9
- package/dist/spa/src/afcl/Select.vue +2 -2
- package/dist/spa/src/afcl/Textarea.vue +23 -19
- package/dist/spa/src/components/ResourceListTable.vue +17 -9
- package/dist/spa/src/components/ResourceListTableVirtual.vue +18 -10
- package/dist/spa/src/components/Sidebar.vue +15 -3
- package/dist/spa/src/components/Toast.vue +1 -1
- package/dist/spa/src/components/ValueRenderer.vue +1 -1
- package/dist/spa/src/stores/core.ts +7 -0
- package/dist/spa/src/types/Common.ts +1 -0
- package/dist/spa/src/utils.ts +15 -1
- package/dist/spa/src/views/ListView.vue +6 -0
- package/dist/spa/src/views/LoginView.vue +3 -0
- package/dist/spa/src/views/PageNotFound.vue +2 -2
- package/dist/spa/tsconfig.json +1 -1
- package/dist/types/Common.d.ts +1 -0
- package/dist/types/Common.d.ts.map +1 -1
- package/dist/types/Common.js.map +1 -1
- package/package.json +1 -1
package/dist/spa/package.json
CHANGED
|
@@ -13,47 +13,47 @@
|
|
|
13
13
|
"i18n:extract": "echo {} > i18n-empty.json && vue-i18n-extract report --vueFiles \"./src/**/*.{js,vue,ts}\" --output ./i18n-messages.json --languageFiles \"i18n-empty.json\" --add"
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@iconify-prerendered/vue-flag": "^0.28.
|
|
16
|
+
"@iconify-prerendered/vue-flag": "^0.28.1748584105",
|
|
17
17
|
"@iconify-prerendered/vue-flowbite": "^0.28.1754899090",
|
|
18
|
-
"@unhead/vue": "^1.
|
|
19
|
-
"@vueuse/core": "^10.
|
|
18
|
+
"@unhead/vue": "^1.9.12",
|
|
19
|
+
"@vueuse/core": "^10.10.0",
|
|
20
20
|
"apexcharts": "^4.7.0",
|
|
21
|
-
"dayjs": "^1.11.
|
|
22
|
-
"debounce": "^2.
|
|
23
|
-
"flowbite-datepicker": "^1.
|
|
24
|
-
"javascript-time-ago": "^2.5.
|
|
25
|
-
"pinia": "^2.
|
|
26
|
-
"sanitize-html": "^2.
|
|
27
|
-
"unhead": "^1.
|
|
21
|
+
"dayjs": "^1.11.11",
|
|
22
|
+
"debounce": "^2.1.0",
|
|
23
|
+
"flowbite-datepicker": "^1.2.6",
|
|
24
|
+
"javascript-time-ago": "^2.5.11",
|
|
25
|
+
"pinia": "^2.1.7",
|
|
26
|
+
"sanitize-html": "^2.13.0",
|
|
27
|
+
"unhead": "^1.9.12",
|
|
28
28
|
"uuid": "^10.0.0",
|
|
29
|
-
"vue": "^3.5.
|
|
29
|
+
"vue": "^3.5.12",
|
|
30
30
|
"vue-diff": "^1.2.4",
|
|
31
|
-
"vue-i18n": "^10.0.
|
|
32
|
-
"vue-router": "^4.
|
|
31
|
+
"vue-i18n": "^10.0.5",
|
|
32
|
+
"vue-router": "^4.3.0",
|
|
33
33
|
"vue-slider-component": "^4.1.0-beta.7"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
|
-
"@rushstack/eslint-patch": "^1.
|
|
37
|
-
"@tsconfig/node20": "^20.1.
|
|
38
|
-
"@types/node": "^20.
|
|
39
|
-
"@vitejs/plugin-vue": "^5.
|
|
36
|
+
"@rushstack/eslint-patch": "^1.8.0",
|
|
37
|
+
"@tsconfig/node20": "^20.1.4",
|
|
38
|
+
"@types/node": "^20.12.5",
|
|
39
|
+
"@vitejs/plugin-vue": "^5.0.4",
|
|
40
40
|
"@vue/eslint-config-typescript": "^13.0.0",
|
|
41
|
-
"@vue/tsconfig": "^0.
|
|
42
|
-
"autoprefixer": "^10.4.
|
|
43
|
-
"eslint": "^8.57.
|
|
44
|
-
"eslint-plugin-vue": "^9.
|
|
45
|
-
"flag-icons": "^7.
|
|
41
|
+
"@vue/tsconfig": "^0.5.1",
|
|
42
|
+
"autoprefixer": "^10.4.19",
|
|
43
|
+
"eslint": "^8.57.0",
|
|
44
|
+
"eslint-plugin-vue": "^9.23.0",
|
|
45
|
+
"flag-icons": "^7.2.3",
|
|
46
46
|
"flowbite": "^3.1.2",
|
|
47
|
-
"i18n-iso-countries": "^7.
|
|
48
|
-
"npm-run-all2": "^6.2
|
|
49
|
-
"portfinder": "^1.0.
|
|
50
|
-
"postcss": "^8.
|
|
51
|
-
"sass": "^1.
|
|
52
|
-
"tailwindcss": "^3.4.
|
|
53
|
-
"typescript": "~5.
|
|
54
|
-
"vite": "^5.
|
|
47
|
+
"i18n-iso-countries": "^7.12.0",
|
|
48
|
+
"npm-run-all2": "^6.1.2",
|
|
49
|
+
"portfinder": "^1.0.32",
|
|
50
|
+
"postcss": "^8.4.38",
|
|
51
|
+
"sass": "^1.77.2",
|
|
52
|
+
"tailwindcss": "^3.4.17",
|
|
53
|
+
"typescript": "~5.4.0",
|
|
54
|
+
"vite": "^5.2.13",
|
|
55
55
|
"vue-i18n-extract": "^2.0.7",
|
|
56
|
-
"vue-tsc": "^2.
|
|
57
|
-
"vue3-json-viewer": "^2.
|
|
56
|
+
"vue-tsc": "^2.0.11",
|
|
57
|
+
"vue3-json-viewer": "^2.2.2"
|
|
58
58
|
}
|
|
59
59
|
}
|
package/dist/spa/src/App.vue
CHANGED
|
@@ -73,7 +73,7 @@
|
|
|
73
73
|
</nav>
|
|
74
74
|
|
|
75
75
|
<Sidebar
|
|
76
|
-
v-if="loggedIn && routerIsReady && loginRedirectCheckIsReady && defaultLayout && !headerOnlyLayout"
|
|
76
|
+
v-if="loggedIn && routerIsReady && loginRedirectCheckIsReady && defaultLayout && !headerOnlyLayout && coreStore.menu.length > 0"
|
|
77
77
|
:sideBarOpen="sideBarOpen"
|
|
78
78
|
:forceIconOnly="route.meta?.sidebarAndHeader === 'preferIconOnly'"
|
|
79
79
|
@hideSidebar="hideSidebar"
|
|
@@ -182,8 +182,6 @@ initFrontedAPI()
|
|
|
182
182
|
|
|
183
183
|
createHead()
|
|
184
184
|
const sideBarOpen = ref(false);
|
|
185
|
-
const defaultLayout = ref(true);
|
|
186
|
-
const headerOnlyLayout = ref(false);
|
|
187
185
|
const route = useRoute();
|
|
188
186
|
const router = useRouter();
|
|
189
187
|
const publicConfigLoaded = ref(false);
|
|
@@ -197,6 +195,14 @@ const isSidebarIconOnly = ref(localStorage.getItem('afIconOnlySidebar') === 'tru
|
|
|
197
195
|
|
|
198
196
|
const loggedIn = computed(() => !!coreStore?.adminUser);
|
|
199
197
|
|
|
198
|
+
const defaultLayout = computed(() => {
|
|
199
|
+
return route.meta?.sidebarAndHeader !== 'none';
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
const headerOnlyLayout = computed(() => {
|
|
203
|
+
return route.meta?.sidebarAndHeader === 'headerOnly';
|
|
204
|
+
});
|
|
205
|
+
|
|
200
206
|
const expandedWidth = computed(() => coreStore.config?.iconOnlySidebar?.expandedSidebarWidth || '16.5rem');
|
|
201
207
|
|
|
202
208
|
const theme = ref('light');
|
|
@@ -308,19 +314,6 @@ async function loadMenu() {
|
|
|
308
314
|
loginRedirectCheckIsReady.value = true;
|
|
309
315
|
}
|
|
310
316
|
|
|
311
|
-
function handleCustomLayout() {
|
|
312
|
-
if (route.meta?.sidebarAndHeader === 'none') {
|
|
313
|
-
defaultLayout.value = false;
|
|
314
|
-
} else if (route.meta?.sidebarAndHeader === 'preferIconOnly') {
|
|
315
|
-
defaultLayout.value = true;
|
|
316
|
-
isSidebarIconOnly.value = true;
|
|
317
|
-
} else if (route.meta?.sidebarAndHeader === 'headerOnly') {
|
|
318
|
-
headerOnlyLayout.value = true;
|
|
319
|
-
} else {
|
|
320
|
-
defaultLayout.value = true;
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
|
|
324
317
|
function humanizeSnake(str: string): string {
|
|
325
318
|
if (!str) {
|
|
326
319
|
return '';
|
|
@@ -345,10 +338,11 @@ watch(title, (title) => {
|
|
|
345
338
|
document.title = title;
|
|
346
339
|
})
|
|
347
340
|
|
|
348
|
-
watch(
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
341
|
+
watch(route, () => {
|
|
342
|
+
// Handle preferIconOnly layout
|
|
343
|
+
if (route.meta?.sidebarAndHeader === 'preferIconOnly') {
|
|
344
|
+
isSidebarIconOnly.value = true;
|
|
345
|
+
}
|
|
352
346
|
});
|
|
353
347
|
|
|
354
348
|
|
|
@@ -376,7 +370,6 @@ onMounted(async () => {
|
|
|
376
370
|
loadPublicConfig(); // and this
|
|
377
371
|
// before init flowbite we have to wait router initialized because it affects dom(our v-ifs) and fetch menu
|
|
378
372
|
await initRouter();
|
|
379
|
-
handleCustomLayout();
|
|
380
373
|
|
|
381
374
|
adminforth.menu.refreshMenuBadges = async () => {
|
|
382
375
|
await coreStore.fetchMenuBadges();
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
v-if="headerCloseButton"
|
|
23
23
|
type="button"
|
|
24
24
|
class="text-lightDialogCloseButton bg-transparent hover:bg-lightDialogCloseButtonHoverBackground hover:text-lightDialogCloseButtonHover rounded-lg text-sm w-8 h-8 ms-auto inline-flex justify-center items-center dark:text-darkDialogCloseButton dark:hover:bg-darkDialogCloseButtonHoverBackground dark:hover:text-darkDialogCloseButtonHover"
|
|
25
|
-
@click="
|
|
25
|
+
@click="tryToHideModal"
|
|
26
26
|
>
|
|
27
27
|
<svg class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
|
|
28
28
|
<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"/>
|
|
@@ -51,13 +51,41 @@
|
|
|
51
51
|
</div>
|
|
52
52
|
</div>
|
|
53
53
|
</div>
|
|
54
|
+
<div>
|
|
55
|
+
<!-- Confirmation Modal -->
|
|
56
|
+
<div
|
|
57
|
+
v-if="showConfirmationOnClose"
|
|
58
|
+
class="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50 z-60"
|
|
59
|
+
>
|
|
60
|
+
<div class="bg-white dark:bg-gray-800 p-6 rounded-lg shadow-lg max-w-sm w-full">
|
|
61
|
+
<h2 class="text-lg font-semibold mb-4 text-lightDialogHeaderText dark:text-darkDialogHeaderText">Confirm Close</h2>
|
|
62
|
+
<p class="mb-6 text-lightDialogBodyText dark:text-darkDialogBodyText">{{ props.closeConfirmationText }}</p>
|
|
63
|
+
<div class="flex justify-end">
|
|
64
|
+
<Button
|
|
65
|
+
class="me-3"
|
|
66
|
+
@click="showConfirmationOnClose = false"
|
|
67
|
+
>
|
|
68
|
+
Cancel
|
|
69
|
+
</Button>
|
|
70
|
+
<Button
|
|
71
|
+
@click="
|
|
72
|
+
showConfirmationOnClose = false;
|
|
73
|
+
modal?.hide();
|
|
74
|
+
"
|
|
75
|
+
>
|
|
76
|
+
Confirm
|
|
77
|
+
</Button>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
54
82
|
</div>
|
|
55
83
|
</Teleport>
|
|
56
84
|
</template>
|
|
57
85
|
|
|
58
86
|
<script setup lang="ts">
|
|
59
87
|
import Button from "./Button.vue";
|
|
60
|
-
import { ref, onMounted, nextTick, onUnmounted, type Ref } from 'vue';
|
|
88
|
+
import { ref, onMounted, nextTick, onUnmounted, computed, type Ref } from 'vue';
|
|
61
89
|
import { Modal } from 'flowbite';
|
|
62
90
|
|
|
63
91
|
const modalEl = ref(null);
|
|
@@ -77,20 +105,42 @@ interface DialogProps {
|
|
|
77
105
|
beforeCloseFunction?: (() => void | Promise<void>) | null
|
|
78
106
|
beforeOpenFunction?: (() => void | Promise<void>) | null
|
|
79
107
|
closable?: boolean
|
|
108
|
+
askForCloseConfirmation?: boolean
|
|
109
|
+
closeConfirmationText?: string
|
|
80
110
|
}
|
|
81
111
|
|
|
82
112
|
const props = withDefaults(defineProps<DialogProps>(), {
|
|
83
113
|
header: '',
|
|
84
114
|
headerCloseButton: true,
|
|
85
|
-
buttons: () => [
|
|
86
|
-
{ label: 'Close', onclick: (dialog: any) => dialog.hide(), type: '' },
|
|
87
|
-
],
|
|
115
|
+
buttons: () => [],
|
|
88
116
|
clickToCloseOutside: true,
|
|
89
117
|
beforeCloseFunction: null,
|
|
90
118
|
beforeOpenFunction: null,
|
|
91
119
|
closable: true,
|
|
120
|
+
askForCloseConfirmation: false,
|
|
121
|
+
closeConfirmationText: 'Are you sure you want to close this dialog?',
|
|
92
122
|
})
|
|
93
123
|
|
|
124
|
+
const buttons = computed<DialogButton[]>(() => {
|
|
125
|
+
if (props.buttons && props.buttons.length > 0) {
|
|
126
|
+
return props.buttons;
|
|
127
|
+
}
|
|
128
|
+
return [
|
|
129
|
+
{
|
|
130
|
+
label: 'Close',
|
|
131
|
+
onclick: (dialog: any) => {
|
|
132
|
+
if (!props.askForCloseConfirmation) {
|
|
133
|
+
dialog.hide();
|
|
134
|
+
} else {
|
|
135
|
+
showConfirmationOnClose.value = true;
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
options: {}
|
|
139
|
+
}
|
|
140
|
+
];
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
const showConfirmationOnClose = ref(false);
|
|
94
144
|
onMounted(async () => {
|
|
95
145
|
//await one tick when all is mounted
|
|
96
146
|
await nextTick();
|
|
@@ -129,6 +179,16 @@ function close() {
|
|
|
129
179
|
defineExpose({
|
|
130
180
|
open: open,
|
|
131
181
|
close: close,
|
|
182
|
+
tryToHideModal: tryToHideModal,
|
|
132
183
|
})
|
|
133
184
|
|
|
185
|
+
function tryToHideModal() {
|
|
186
|
+
if (!props.askForCloseConfirmation ) {
|
|
187
|
+
modal.value?.hide();
|
|
188
|
+
} else {
|
|
189
|
+
showConfirmationOnClose.value = true;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
|
|
134
194
|
</script>
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
aria-describedby="helper-text-explanation"
|
|
18
18
|
class="afcl-input inline-flex bg-lightInputBackground border border-lightInputBorder rounded-0 focus:ring-lightPrimary focus:border-lightPrimary dark:focus:ring-darkPrimary dark:focus:border-darkPrimary
|
|
19
19
|
blue-500 focus:border-blue-500 block w-20 p-2.5 dark:bg-darkInputBackground dark:border-darkInputBorder placeholder-lightInputPlaceholderText dark:placeholder-darkInputPlaceholderText dark:text-darkInputText translate-y-0"
|
|
20
|
-
:class="{'rounded-l-md': !$slots.prefix && !prefix, 'rounded-r-md': !$slots.suffix && !suffix, 'w-full': fullWidth, 'text-base':
|
|
20
|
+
:class="{'rounded-l-md': !$slots.prefix && !prefix, 'rounded-r-md': !$slots.suffix && !suffix, 'w-full': fullWidth, 'text-base': isIos, 'text-sm': !isIos }"
|
|
21
21
|
:disabled="readonly"
|
|
22
22
|
>
|
|
23
23
|
|
|
@@ -36,7 +36,10 @@
|
|
|
36
36
|
<script setup lang="ts">
|
|
37
37
|
|
|
38
38
|
import { ref } from 'vue';
|
|
39
|
-
|
|
39
|
+
import { useCoreStore } from '@/stores/core';
|
|
40
|
+
|
|
41
|
+
const coreStore = useCoreStore();
|
|
42
|
+
const isIos = coreStore.isIos;
|
|
40
43
|
|
|
41
44
|
const props = defineProps<{
|
|
42
45
|
type: string,
|
|
@@ -53,12 +56,5 @@ defineExpose({
|
|
|
53
56
|
focus: () => input.value?.focus(),
|
|
54
57
|
});
|
|
55
58
|
|
|
56
|
-
function isIOS() {
|
|
57
|
-
return (
|
|
58
|
-
/iPad|iPhone|iPod/.test(navigator.userAgent) ||
|
|
59
|
-
(navigator.userAgent.includes('Mac') && 'ontouchend' in document)
|
|
60
|
-
)
|
|
61
|
-
}
|
|
62
|
-
|
|
63
59
|
</script>
|
|
64
60
|
|
|
@@ -265,11 +265,11 @@ onMounted(() => {
|
|
|
265
265
|
|
|
266
266
|
watch(() => props.modelValue, (value) => {
|
|
267
267
|
updateFromProps();
|
|
268
|
-
});
|
|
268
|
+
}, {deep: true});
|
|
269
269
|
|
|
270
270
|
watch(() => props.options, () => {
|
|
271
271
|
updateFromProps();
|
|
272
|
-
});
|
|
272
|
+
}, { deep: true });
|
|
273
273
|
|
|
274
274
|
addClickListener();
|
|
275
275
|
|
|
@@ -1,31 +1,35 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
1
|
+
<template>
|
|
2
|
+
<textarea
|
|
3
|
+
ref="input"
|
|
4
|
+
class="afcl-textarea bg-lightInputBackground border border-lightInputBorder text-lightInputText placeholder-lightInputPlaceholderText text-sm rounded-md block w-full p-2.5 dark:bg-darkInputBackground dark:border-darkInputBorder dark:placeholder-darkInputPlaceholderText dark:text-darkInputText dark:border-darkInputBorder focus:ring-lightInputFocusRing focus:border-lightInputFocusBorder dark:focus:ring-darkInputFocusRing dark:focus:border-darkInputFocusBorder"
|
|
5
|
+
:class="`${readonly ? 'opacity-50' : ''} ${isIos ? 'text-md' : 'text-sm'}`"
|
|
6
|
+
:placeholder="placeholder"
|
|
7
|
+
:value="modelValue"
|
|
8
|
+
@input="$emit('update:modelValue', ($event.target as HTMLTextAreaElement).value)"
|
|
9
|
+
:readonly="readonly"
|
|
10
|
+
/>
|
|
12
11
|
</template>
|
|
13
12
|
|
|
14
13
|
<script setup lang="ts">
|
|
15
|
-
|
|
16
14
|
import { ref } from 'vue';
|
|
15
|
+
import { useCoreStore } from '@/stores/core';
|
|
16
|
+
|
|
17
|
+
const coreStore = useCoreStore();
|
|
18
|
+
const isIos = coreStore.isIos;
|
|
17
19
|
|
|
18
20
|
const props = defineProps<{
|
|
19
|
-
modelValue: string
|
|
20
|
-
readonly?: boolean
|
|
21
|
-
placeholder?: string
|
|
21
|
+
modelValue: string
|
|
22
|
+
readonly?: boolean
|
|
23
|
+
placeholder?: string
|
|
22
24
|
}>()
|
|
23
25
|
|
|
24
|
-
const
|
|
26
|
+
const emit = defineEmits<{
|
|
27
|
+
(e: 'update:modelValue', value: string): void
|
|
28
|
+
}>()
|
|
29
|
+
|
|
30
|
+
const input = ref<HTMLTextAreaElement | null>(null)
|
|
25
31
|
|
|
26
32
|
defineExpose({
|
|
27
33
|
focus: () => input.value?.focus(),
|
|
28
|
-
})
|
|
29
|
-
|
|
34
|
+
})
|
|
30
35
|
</script>
|
|
31
|
-
|
|
@@ -83,13 +83,20 @@
|
|
|
83
83
|
</td>
|
|
84
84
|
</tr>
|
|
85
85
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
86
|
+
<component
|
|
87
|
+
v-else
|
|
88
|
+
v-for="(row, rowI) in rows"
|
|
89
|
+
:is="tableRowReplaceInjection ? getCustomComponent(tableRowReplaceInjection) : 'tr'"
|
|
90
|
+
:key="`row_${row._primaryKeyValue}`"
|
|
91
|
+
:record="row"
|
|
92
|
+
:resource="resource"
|
|
93
|
+
:adminUser="coreStore.adminUser"
|
|
94
|
+
:meta="tableRowReplaceInjection ? tableRowReplaceInjection.meta : undefined"
|
|
95
|
+
@click="onClick($event, row)"
|
|
96
|
+
ref="rowRefs"
|
|
97
|
+
class="list-table-body-row bg-lightListTable dark:bg-darkListTable border-lightListBorder dark:border-gray-700 hover:bg-lightListTableRowHover dark:hover:bg-darkListTableRowHover"
|
|
98
|
+
:class="{'border-b': rowI !== rows.length - 1, 'cursor-pointer': row._clickUrl !== null}"
|
|
99
|
+
>
|
|
93
100
|
<td class="w-4 p-4 cursor-default sticky-column bg-lightListTable dark:bg-darkListTable" @click="(e)=>e.stopPropagation()">
|
|
94
101
|
<Checkbox
|
|
95
102
|
:model-value="checkboxesInternal.includes(row._primaryKeyValue)"
|
|
@@ -210,7 +217,7 @@
|
|
|
210
217
|
</div>
|
|
211
218
|
|
|
212
219
|
</td>
|
|
213
|
-
|
|
220
|
+
</component>
|
|
214
221
|
</tbody>
|
|
215
222
|
</table>
|
|
216
223
|
</div>
|
|
@@ -328,7 +335,7 @@ import {
|
|
|
328
335
|
} from '@iconify-prerendered/vue-flowbite';
|
|
329
336
|
import router from '@/router';
|
|
330
337
|
import { Tooltip } from '@/afcl';
|
|
331
|
-
import type { AdminForthResourceCommon, AdminForthResourceColumnInputCommon, AdminForthResourceColumnCommon } from '@/types/Common';
|
|
338
|
+
import type { AdminForthResourceCommon, AdminForthResourceColumnInputCommon, AdminForthResourceColumnCommon, AdminForthComponentDeclaration } from '@/types/Common';
|
|
332
339
|
import adminforth from '@/adminforth';
|
|
333
340
|
import Checkbox from '@/afcl/Checkbox.vue';
|
|
334
341
|
|
|
@@ -345,6 +352,7 @@ const props = defineProps<{
|
|
|
345
352
|
noRoundings?: boolean,
|
|
346
353
|
customActionsInjection?: any[],
|
|
347
354
|
tableBodyStartInjection?: any[],
|
|
355
|
+
tableRowReplaceInjection?: AdminForthComponentDeclaration,
|
|
348
356
|
}>();
|
|
349
357
|
|
|
350
358
|
// emits, update page
|
|
@@ -93,14 +93,21 @@
|
|
|
93
93
|
</tr>
|
|
94
94
|
|
|
95
95
|
<!-- Visible rows -->
|
|
96
|
-
<
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
96
|
+
<component
|
|
97
|
+
v-else
|
|
98
|
+
v-for="(row, rowI) in visibleRows"
|
|
99
|
+
:is="tableRowReplaceInjection ? getCustomComponent(tableRowReplaceInjection) : 'tr'"
|
|
100
|
+
:key="`row_${row._primaryKeyValue}`"
|
|
101
|
+
:record="row"
|
|
102
|
+
:resource="resource"
|
|
103
|
+
:adminUser="coreStore.adminUser"
|
|
104
|
+
:meta="tableRowReplaceInjection ? tableRowReplaceInjection.meta : undefined"
|
|
105
|
+
@click="onClick($event, row)"
|
|
106
|
+
ref="rowRefs"
|
|
107
|
+
class="list-table-body-row bg-lightListTable dark:bg-darkListTable border-lightListBorder dark:border-gray-700 hover:bg-lightListTableRowHover dark:hover:bg-darkListTableRowHover"
|
|
108
|
+
:class="{'border-b': rowI !== visibleRows.length - 1, 'cursor-pointer': row._clickUrl !== null}"
|
|
109
|
+
@mounted="(el: any) => updateRowHeight(`row_${row._primaryKeyValue}`, el.offsetHeight)"
|
|
110
|
+
>
|
|
104
111
|
<td class="w-4 p-4 cursor-default sticky-column bg-lightListTableHeading dark:bg-darkListTableHeading" @click="(e)=>e.stopPropagation()">
|
|
105
112
|
<Checkbox
|
|
106
113
|
:model-value="checkboxesInternal.includes(row._primaryKeyValue)"
|
|
@@ -224,7 +231,7 @@
|
|
|
224
231
|
</template>
|
|
225
232
|
</div>
|
|
226
233
|
</td>
|
|
227
|
-
</
|
|
234
|
+
</component>
|
|
228
235
|
|
|
229
236
|
<!-- Bottom spacer -->
|
|
230
237
|
<tr v-if="totalHeight > 0">
|
|
@@ -350,7 +357,7 @@ import {
|
|
|
350
357
|
} from '@iconify-prerendered/vue-flowbite';
|
|
351
358
|
import router from '@/router';
|
|
352
359
|
import { Tooltip } from '@/afcl';
|
|
353
|
-
import type { AdminForthResourceCommon, AdminForthResourceColumnCommon } from '@/types/Common';
|
|
360
|
+
import type { AdminForthResourceCommon, AdminForthResourceColumnCommon, AdminForthComponentDeclaration } from '@/types/Common';
|
|
354
361
|
import adminforth from '@/adminforth';
|
|
355
362
|
import Checkbox from '@/afcl/Checkbox.vue';
|
|
356
363
|
|
|
@@ -370,6 +377,7 @@ const props = defineProps<{
|
|
|
370
377
|
containerHeight?: number,
|
|
371
378
|
itemHeight?: number,
|
|
372
379
|
bufferSize?: number,
|
|
380
|
+
tableRowReplaceInjection?: AdminForthComponentDeclaration
|
|
373
381
|
}>();
|
|
374
382
|
|
|
375
383
|
// emits, update page
|
|
@@ -14,9 +14,21 @@
|
|
|
14
14
|
aria-label="Sidebar"
|
|
15
15
|
>
|
|
16
16
|
<div class="h-full px-3 pb-20 md:pb-4 bg-lightSidebar dark:bg-darkSidebar border-r border-lightSidebarBorder dark:border-darkSidebarBorder pt-4" :class="{'sidebar-scroll':!isSidebarIconOnly || (isSidebarIconOnly && isSidebarHovering)}">
|
|
17
|
-
<div
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
<div
|
|
18
|
+
class="af-logo-title-wrapper flex relative transition-all duration-300 ease-in-out h-8 items-center"
|
|
19
|
+
:class="{
|
|
20
|
+
'mb-4': isSidebarIconOnly && !isSidebarHovering, 'mx-4 mb-4': !isSidebarIconOnly || (isSidebarIconOnly && isSidebarHovering),
|
|
21
|
+
'justify-center': !(coreStore.config?.showBrandLogoInSidebar !== false && (!iconOnlySidebarEnabled || !isSidebarIconOnly || (isSidebarIconOnly && isSidebarHovering)))
|
|
22
|
+
}"
|
|
23
|
+
>
|
|
24
|
+
<img
|
|
25
|
+
:src="loadFile(coreStore.config?.brandLogo || '@/assets/logo.svg')"
|
|
26
|
+
:alt="`${ coreStore.config?.brandName } Logo`"
|
|
27
|
+
class="af-logo h-8 me-3"
|
|
28
|
+
:class="{
|
|
29
|
+
'hidden': !(coreStore.config?.showBrandLogoInSidebar !== false && (!iconOnlySidebarEnabled || !isSidebarIconOnly || (isSidebarIconOnly && isSidebarHovering))) }"
|
|
30
|
+
/>
|
|
31
|
+
<img :src="loadFile(coreStore.config?.iconOnlySidebar?.logo || '')" :alt="`${ coreStore.config?.brandName } Logo`" class="af-sidebar-icon-only-logo h-8" :class="{ 'hidden': !(coreStore.config?.showBrandLogoInSidebar !== false && coreStore.config?.iconOnlySidebar?.logo && iconOnlySidebarEnabled && isSidebarIconOnly && !isSidebarHovering) }" />
|
|
20
32
|
<span
|
|
21
33
|
v-if="coreStore.config?.showBrandNameInSidebar && (!iconOnlySidebarEnabled || !isSidebarIconOnly || (isSidebarIconOnly && isSidebarHovering))"
|
|
22
34
|
class="af-title self-center text-lightNavbarText-size font-semibold sm:text-lightNavbarText-size whitespace-nowrap dark:text-darkSidebarText text-lightSidebarText"
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
|
|
22
22
|
<div class="ms-3 text-sm font-normal max-w-xs pr-2" v-if="toast.messageHtml" v-html="toast.messageHtml"></div>
|
|
23
23
|
<div class="ms-3 text-sm font-normal max-w-xs pr-2" v-else>
|
|
24
|
-
<div class="flex flex-col items-center justify-center">
|
|
24
|
+
<div class="flex flex-col items-center justify-center break-all">
|
|
25
25
|
{{toast.message}}
|
|
26
26
|
<div v-if="toast.buttons" class="flex justify-center mt-2 gap-2">
|
|
27
27
|
<div v-for="button in toast.buttons" class="af-toast-button rounded-md bg-lightButtonsBackground hover:bg-lightButtonsHover text-lightButtonsText dark:bg-darkPrimary dark:hover:bg-darkButtonsBackground dark:text-darkButtonsText">
|
|
@@ -117,7 +117,7 @@ import timezone from 'dayjs/plugin/timezone';
|
|
|
117
117
|
import {checkEmptyValues} from '@/utils';
|
|
118
118
|
import { useRoute, useRouter } from 'vue-router';
|
|
119
119
|
import { JsonViewer } from "vue3-json-viewer";
|
|
120
|
-
import "vue3-json-viewer/dist/
|
|
120
|
+
import "vue3-json-viewer/dist/index.css";
|
|
121
121
|
import type { AdminForthResourceColumnCommon } from '@/types/Common';
|
|
122
122
|
|
|
123
123
|
import { useCoreStore } from '@/stores/core';
|
|
@@ -221,6 +221,12 @@ export const useCoreStore = defineStore('core', () => {
|
|
|
221
221
|
return userData.value && userFullnameField && userData.value[userFullnameField];
|
|
222
222
|
})
|
|
223
223
|
|
|
224
|
+
const isIos = computed(() => {
|
|
225
|
+
return (
|
|
226
|
+
/iPad|iPhone|iPod/.test(navigator.userAgent) ||
|
|
227
|
+
(navigator.userAgent.includes('Mac') && 'ontouchend' in document)
|
|
228
|
+
)});
|
|
229
|
+
|
|
224
230
|
|
|
225
231
|
return {
|
|
226
232
|
config,
|
|
@@ -245,5 +251,6 @@ export const useCoreStore = defineStore('core', () => {
|
|
|
245
251
|
resetAdminUser,
|
|
246
252
|
resetResource,
|
|
247
253
|
isResourceFetching,
|
|
254
|
+
isIos
|
|
248
255
|
}
|
|
249
256
|
})
|
|
@@ -504,6 +504,7 @@ export interface AdminForthResourceInputCommon {
|
|
|
504
504
|
threeDotsDropdownItems?: AdminForthComponentDeclaration | Array<AdminForthComponentDeclaration>,
|
|
505
505
|
customActionIcons?: AdminForthComponentDeclaration | Array<AdminForthComponentDeclaration>,
|
|
506
506
|
tableBodyStart?: AdminForthComponentDeclaration | Array<AdminForthComponentDeclaration>,
|
|
507
|
+
tableRowReplace?: AdminForthComponentDeclaration | Array<AdminForthComponentDeclaration>,
|
|
507
508
|
},
|
|
508
509
|
|
|
509
510
|
/**
|
package/dist/spa/src/utils.ts
CHANGED
|
@@ -36,7 +36,20 @@ export async function callApi({path, method, body, headers}: {
|
|
|
36
36
|
if (r.status == 401 ) {
|
|
37
37
|
useUserStore().unauthorize();
|
|
38
38
|
useCoreStore().resetAdminUser();
|
|
39
|
-
|
|
39
|
+
const currentPath = router.currentRoute.value.path;
|
|
40
|
+
const homeRoute = router.getRoutes().find(route => route.name === 'home');
|
|
41
|
+
const homePagePath = (homeRoute?.redirect as string) || '/';
|
|
42
|
+
let next = '';
|
|
43
|
+
if (currentPath !== '/login' && currentPath !== homePagePath) {
|
|
44
|
+
if (Object.keys(router.currentRoute.value.query).length > 0) {
|
|
45
|
+
next = currentPath + '?' + Object.entries(router.currentRoute.value.query).map(([key, value]) => `${key}=${value}`).join('&');
|
|
46
|
+
} else {
|
|
47
|
+
next = currentPath;
|
|
48
|
+
}
|
|
49
|
+
await router.push({ name: 'login', query: { next: next } });
|
|
50
|
+
} else {
|
|
51
|
+
await router.push({ name: 'login' });
|
|
52
|
+
}
|
|
40
53
|
return null;
|
|
41
54
|
}
|
|
42
55
|
return await r.json();
|
|
@@ -100,6 +113,7 @@ export const loadFile = (file: string) => {
|
|
|
100
113
|
return baseUrl;
|
|
101
114
|
}
|
|
102
115
|
|
|
116
|
+
|
|
103
117
|
export function checkEmptyValues(value: any, viewType: 'show' | 'list' ) {
|
|
104
118
|
const config: CoreConfig | {} | null = useCoreStore().config;
|
|
105
119
|
let emptyFieldPlaceholder = '';
|
|
@@ -143,6 +143,9 @@
|
|
|
143
143
|
? [coreStore.resourceOptions.pageInjections.list.tableBodyStart]
|
|
144
144
|
: []
|
|
145
145
|
"
|
|
146
|
+
:tableRowReplaceInjection="Array.isArray(coreStore.resourceOptions?.pageInjections?.list?.tableRowReplace)
|
|
147
|
+
? coreStore.resourceOptions.pageInjections.list.tableRowReplace[0]
|
|
148
|
+
: coreStore.resourceOptions?.pageInjections?.list?.tableRowReplace || undefined"
|
|
146
149
|
:container-height="1100"
|
|
147
150
|
:item-height="52.5"
|
|
148
151
|
:buffer-size="listBufferSize"
|
|
@@ -173,6 +176,9 @@
|
|
|
173
176
|
? [coreStore.resourceOptions.pageInjections.list.tableBodyStart]
|
|
174
177
|
: []
|
|
175
178
|
"
|
|
179
|
+
:tableRowReplaceInjection="Array.isArray(coreStore.resourceOptions?.pageInjections?.list?.tableRowReplace)
|
|
180
|
+
? coreStore.resourceOptions.pageInjections.list.tableRowReplace[0]
|
|
181
|
+
: coreStore.resourceOptions?.pageInjections?.list?.tableRowReplace || undefined"
|
|
176
182
|
/>
|
|
177
183
|
|
|
178
184
|
<component
|
|
@@ -199,6 +199,9 @@ async function login() {
|
|
|
199
199
|
if (resp.error) {
|
|
200
200
|
error.value = resp.error;
|
|
201
201
|
} else if (resp.redirectTo) {
|
|
202
|
+
error.value = null;
|
|
203
|
+
user.authorize();
|
|
204
|
+
await coreStore.fetchMenuAndResource();
|
|
202
205
|
router.push(resp.redirectTo);
|
|
203
206
|
} else {
|
|
204
207
|
error.value = null;
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<section class="
|
|
2
|
+
<section class="flex flex-col items-center justify-center">
|
|
3
3
|
<div class="py-8 px-4 mx-auto max-w-screen-xl lg:py-16 lg:px-6">
|
|
4
4
|
<div class="mx-auto max-w-screen-sm text-center">
|
|
5
5
|
<h1 class="mb-4 text-7xl tracking-tight font-extrabold lg:text-9xl text-lightPrimary dark:text-darkPrimary">404</h1>
|
|
6
6
|
<p class="mb-4 text-3xl tracking-tight font-bold text-gray-900 md:text-4xl dark:text-white">{{ $t("Something's missing.") }}</p>
|
|
7
7
|
<p class="mb-4 text-lg font-light text-gray-500 dark:text-gray-400">{{ $t("Sorry, we can't find that page. You'll find lots to explore on the home page.") }} </p>
|
|
8
8
|
<div class="flex justify-center">
|
|
9
|
-
<LinkButton
|
|
9
|
+
<LinkButton to="/">{{ $t('Go back home') }}</LinkButton>
|
|
10
10
|
</div>
|
|
11
11
|
</div>
|
|
12
12
|
</div>
|
package/dist/spa/tsconfig.json
CHANGED