adminforth 2.26.0 → 2.27.0-next.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/commands/createApp/templates/package.json.hbs +1 -1
- package/dist/modules/restApi.d.ts +1 -0
- package/dist/modules/restApi.d.ts.map +1 -1
- package/dist/modules/restApi.js +25 -1
- package/dist/modules/restApi.js.map +1 -1
- package/dist/modules/styles.js +2 -2
- package/dist/modules/styles.js.map +1 -1
- package/dist/servers/express.d.ts.map +1 -1
- package/dist/servers/express.js +7 -1
- package/dist/servers/express.js.map +1 -1
- package/dist/spa/package-lock.json +44 -7
- package/dist/spa/package.json +1 -1
- package/dist/spa/pnpm-lock.yaml +301 -299
- package/dist/spa/src/App.vue +1 -1
- package/dist/spa/src/adminforth.ts +17 -29
- package/dist/spa/src/afcl/Input.vue +1 -1
- package/dist/spa/src/afcl/Modal.vue +12 -1
- package/dist/spa/src/afcl/Select.vue +4 -2
- package/dist/spa/src/afcl/Table.vue +27 -13
- package/dist/spa/src/components/AcceptModal.vue +2 -0
- package/dist/spa/src/components/ColumnValueInputWrapper.vue +11 -3
- package/dist/spa/src/components/CustomRangePicker.vue +16 -67
- package/dist/spa/src/components/ListActionsThreeDots.vue +9 -8
- package/dist/spa/src/components/RangePicker.vue +236 -0
- package/dist/spa/src/components/ResourceListTable.vue +24 -27
- package/dist/spa/src/components/Sidebar.vue +1 -1
- package/dist/spa/src/components/ThreeDotsMenu.vue +9 -8
- package/dist/spa/src/i18n.ts +1 -1
- package/dist/spa/src/stores/core.ts +4 -2
- package/dist/spa/src/types/Back.ts +7 -3
- package/dist/spa/src/types/Common.ts +25 -5
- package/dist/spa/src/types/FrontendAPI.ts +6 -1
- package/dist/spa/src/utils/listUtils.ts +8 -2
- package/dist/spa/src/utils/utils.ts +29 -10
- package/dist/spa/src/views/CreateView.vue +10 -10
- package/dist/spa/src/views/EditView.vue +10 -9
- package/dist/spa/src/views/ListView.vue +58 -16
- package/dist/spa/src/views/LoginView.vue +13 -13
- package/dist/spa/src/views/ShowView.vue +16 -16
- package/dist/spa/tsconfig.app.json +1 -1
- package/dist/types/Back.d.ts +4 -4
- package/dist/types/Back.d.ts.map +1 -1
- package/dist/types/Back.js.map +1 -1
- package/dist/types/Common.d.ts +20 -5
- package/dist/types/Common.d.ts.map +1 -1
- package/dist/types/Common.js.map +1 -1
- package/dist/types/FrontendAPI.d.ts +13 -1
- package/dist/types/FrontendAPI.d.ts.map +1 -1
- package/dist/types/FrontendAPI.js.map +1 -1
- package/package.json +6 -2
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="range-slider" ref="trackRef" @mousedown="onTrackMouseDown">
|
|
3
|
+
<div class="track"></div>
|
|
4
|
+
<div class="range bg-lightPrimary/30" :style="rangeStyle"></div>
|
|
5
|
+
|
|
6
|
+
<div
|
|
7
|
+
class="bg-lightPrimary thumb"
|
|
8
|
+
:style="minThumbStyle"
|
|
9
|
+
@mousedown.stop.prevent="startDrag('min', $event)"
|
|
10
|
+
@mouseenter="minHovered = true"
|
|
11
|
+
@mouseleave="minHovered = false"
|
|
12
|
+
></div>
|
|
13
|
+
<div v-if="minHovered || activeThumb === 'min'" class="thumb-tooltip" :style="minTooltipStyle">{{ minVal }}</div>
|
|
14
|
+
|
|
15
|
+
<div
|
|
16
|
+
class="bg-lightPrimary thumb"
|
|
17
|
+
:style="maxThumbStyle"
|
|
18
|
+
@mousedown.stop.prevent="startDrag('max', $event)"
|
|
19
|
+
@mouseenter="maxHovered = true"
|
|
20
|
+
@mouseleave="maxHovered = false"
|
|
21
|
+
></div>
|
|
22
|
+
<div v-if="maxHovered || activeThumb === 'max'" class="thumb-tooltip" :style="maxTooltipStyle">{{ maxVal }}</div>
|
|
23
|
+
|
|
24
|
+
</div>
|
|
25
|
+
</template>
|
|
26
|
+
|
|
27
|
+
<script setup lang="ts">
|
|
28
|
+
import { computed, ref, watch, onBeforeUnmount } from 'vue'
|
|
29
|
+
|
|
30
|
+
const props = defineProps({
|
|
31
|
+
modelValue: {
|
|
32
|
+
type: Array as unknown as () => [number, number],
|
|
33
|
+
default: () => [0, 100]
|
|
34
|
+
},
|
|
35
|
+
min: { type: Number, default: 0 },
|
|
36
|
+
max: { type: Number, default: 100 },
|
|
37
|
+
dotSize: { type: Number, default: 20 },
|
|
38
|
+
height: { type: String, default: '8px' }
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
const emit = defineEmits(['update:modelValue'])
|
|
42
|
+
|
|
43
|
+
const trackRef = ref<HTMLElement | null>(null)
|
|
44
|
+
|
|
45
|
+
const minVal = ref(props.modelValue[0])
|
|
46
|
+
const maxVal = ref(props.modelValue[1])
|
|
47
|
+
|
|
48
|
+
watch(() => props.modelValue, (val) => {
|
|
49
|
+
if (!val) return
|
|
50
|
+
minVal.value = val[0]
|
|
51
|
+
maxVal.value = val[1]
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
function clamp(val: number) {
|
|
55
|
+
return Math.min(props.max, Math.max(props.min, val))
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function valueToPercent(val: number) {
|
|
59
|
+
return ((val - props.min) / (props.max - props.min)) * 100
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function percentToValue(percent: number) {
|
|
63
|
+
return props.min + ((props.max - props.min) * percent) / 100
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const minPercent = computed(() => valueToPercent(minVal.value))
|
|
67
|
+
const maxPercent = computed(() => valueToPercent(maxVal.value))
|
|
68
|
+
|
|
69
|
+
const rangeStyle = computed(() => ({
|
|
70
|
+
left: `${minPercent.value}%`,
|
|
71
|
+
width: `${maxPercent.value - minPercent.value}%`,
|
|
72
|
+
transition: isAnimating.value ? 'left 0.18s ease, width 0.18s ease' : 'none'
|
|
73
|
+
}))
|
|
74
|
+
|
|
75
|
+
const minThumbStyle = computed(() => ({
|
|
76
|
+
left: `calc(${minPercent.value}% - ${props.dotSize / 2}px)`,
|
|
77
|
+
width: `${props.dotSize}px`,
|
|
78
|
+
height: `${props.dotSize}px`,
|
|
79
|
+
transition: isAnimating.value ? 'left 0.18s ease' : 'none',
|
|
80
|
+
zIndex: activeThumb.value === 'min' ? 3 : 2
|
|
81
|
+
}))
|
|
82
|
+
|
|
83
|
+
const maxThumbStyle = computed(() => ({
|
|
84
|
+
left: `calc(${maxPercent.value}% - ${props.dotSize / 2}px)`,
|
|
85
|
+
width: `${props.dotSize}px`,
|
|
86
|
+
height: `${props.dotSize}px`,
|
|
87
|
+
transition: isAnimating.value ? 'left 0.18s ease' : 'none',
|
|
88
|
+
zIndex: activeThumb.value === 'max' ? 3 : 2
|
|
89
|
+
}))
|
|
90
|
+
|
|
91
|
+
const minTooltipStyle = computed(() => ({
|
|
92
|
+
left: `${minPercent.value}%`,
|
|
93
|
+
transition: isAnimating.value ? 'left 0.18s ease' : 'none'
|
|
94
|
+
}))
|
|
95
|
+
|
|
96
|
+
const maxTooltipStyle = computed(() => ({
|
|
97
|
+
left: `${maxPercent.value}%`,
|
|
98
|
+
transition: isAnimating.value ? 'left 0.18s ease' : 'none'
|
|
99
|
+
}))
|
|
100
|
+
|
|
101
|
+
const activeThumb = ref<'min' | 'max' | null>(null)
|
|
102
|
+
const isAnimating = ref(false)
|
|
103
|
+
const minHovered = ref(false)
|
|
104
|
+
const maxHovered = ref(false)
|
|
105
|
+
|
|
106
|
+
function startDrag(type: 'min' | 'max', e: MouseEvent) {
|
|
107
|
+
activeThumb.value = type
|
|
108
|
+
document.addEventListener('mousemove', onMouseMove)
|
|
109
|
+
document.addEventListener('mouseup', stopDrag)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function onMouseMove(e: MouseEvent) {
|
|
113
|
+
if (!trackRef.value || !activeThumb.value) return
|
|
114
|
+
|
|
115
|
+
const rect = trackRef.value.getBoundingClientRect()
|
|
116
|
+
const percent = ((e.clientX - rect.left) / rect.width) * 100
|
|
117
|
+
const value = Math.round(clamp(percentToValue(percent)))
|
|
118
|
+
|
|
119
|
+
if (activeThumb.value === 'min') {
|
|
120
|
+
if (value > maxVal.value) {
|
|
121
|
+
// cross over: become the max thumb
|
|
122
|
+
minVal.value = maxVal.value
|
|
123
|
+
maxVal.value = value
|
|
124
|
+
activeThumb.value = 'max'
|
|
125
|
+
} else {
|
|
126
|
+
minVal.value = value
|
|
127
|
+
}
|
|
128
|
+
} else {
|
|
129
|
+
if (value < minVal.value) {
|
|
130
|
+
// cross over: become the min thumb
|
|
131
|
+
maxVal.value = minVal.value
|
|
132
|
+
minVal.value = value
|
|
133
|
+
activeThumb.value = 'min'
|
|
134
|
+
} else {
|
|
135
|
+
maxVal.value = value
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
emit('update:modelValue', [minVal.value, maxVal.value])
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function stopDrag() {
|
|
143
|
+
document.removeEventListener('mousemove', onMouseMove)
|
|
144
|
+
document.removeEventListener('mouseup', stopDrag)
|
|
145
|
+
activeThumb.value = null
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function onTrackMouseDown(e: MouseEvent) {
|
|
149
|
+
if (!trackRef.value) return
|
|
150
|
+
|
|
151
|
+
const rect = trackRef.value.getBoundingClientRect()
|
|
152
|
+
const percent = ((e.clientX - rect.left) / rect.width) * 100
|
|
153
|
+
const value = percentToValue(percent)
|
|
154
|
+
|
|
155
|
+
const distToMin = Math.abs(value - minVal.value)
|
|
156
|
+
const distToMax = Math.abs(value - maxVal.value)
|
|
157
|
+
|
|
158
|
+
isAnimating.value = true
|
|
159
|
+
if (distToMin < distToMax) {
|
|
160
|
+
minVal.value = Math.round(Math.min(clamp(value), maxVal.value))
|
|
161
|
+
} else {
|
|
162
|
+
maxVal.value = Math.round(Math.max(clamp(value), minVal.value))
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
emit('update:modelValue', [minVal.value, maxVal.value])
|
|
166
|
+
|
|
167
|
+
setTimeout(() => { isAnimating.value = false }, 200)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
onBeforeUnmount(() => {
|
|
171
|
+
stopDrag()
|
|
172
|
+
})
|
|
173
|
+
</script>
|
|
174
|
+
|
|
175
|
+
<style scoped>
|
|
176
|
+
.range-slider {
|
|
177
|
+
position: relative;
|
|
178
|
+
width: 100%;
|
|
179
|
+
height: 20px;
|
|
180
|
+
display: flex;
|
|
181
|
+
align-items: center;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.track {
|
|
185
|
+
position: absolute;
|
|
186
|
+
width: 100%;
|
|
187
|
+
height: 8px;
|
|
188
|
+
background: #e5e7eb;
|
|
189
|
+
border-radius: 9999px;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.range {
|
|
193
|
+
position: absolute;
|
|
194
|
+
height: 8px;
|
|
195
|
+
border-radius: 9999px;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.thumb {
|
|
199
|
+
position: absolute;
|
|
200
|
+
top: 50%;
|
|
201
|
+
transform: translateY(-50%);
|
|
202
|
+
border-radius: 9999px;
|
|
203
|
+
cursor: pointer;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
.thumb-tooltip {
|
|
207
|
+
position: absolute;
|
|
208
|
+
top: -28px;
|
|
209
|
+
transform: translateX(-50%);
|
|
210
|
+
background: rgba(0, 0, 0, 0.75);
|
|
211
|
+
color: #fff;
|
|
212
|
+
font-size: 14px;
|
|
213
|
+
font-weight: 500;
|
|
214
|
+
line-height: 1;
|
|
215
|
+
padding: 6px 6px;
|
|
216
|
+
border-radius: 4px;
|
|
217
|
+
white-space: nowrap;
|
|
218
|
+
pointer-events: none;
|
|
219
|
+
animation: tooltip-in 0.12s ease;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.thumb-tooltip::after {
|
|
223
|
+
content: '';
|
|
224
|
+
position: absolute;
|
|
225
|
+
top: 100%;
|
|
226
|
+
left: 50%;
|
|
227
|
+
transform: translateX(-50%);
|
|
228
|
+
border: 4px solid transparent;
|
|
229
|
+
border-top-color: rgba(0, 0, 0, 0.75);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
@keyframes tooltip-in {
|
|
233
|
+
from { opacity: 0; transform: translateX(-50%) translateY(4px); }
|
|
234
|
+
to { opacity: 1; transform: translateX(-50%) translateY(0); }
|
|
235
|
+
}
|
|
236
|
+
</style>
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<!-- table -->
|
|
3
|
-
<div
|
|
4
|
-
class="relative shadow-listTableShadow dark:shadow-darkListTableShadow overflow-auto "
|
|
3
|
+
<div class="relative shadow-listTableShadow dark:shadow-darkListTableShadow overflow-auto border dark:border-gray-700"
|
|
5
4
|
:class="{'rounded-default': !noRoundings}"
|
|
6
5
|
:style="isVirtualScrollEnabled ? { maxHeight: `${containerHeight}px` } : {}"
|
|
7
6
|
@scroll="handleScroll"
|
|
@@ -21,7 +20,7 @@
|
|
|
21
20
|
|
|
22
21
|
<tbody>
|
|
23
22
|
<!-- table header -->
|
|
24
|
-
<tr class="t-header sticky z-20 top-0 text-xs text-lightListTableHeadingText bg-lightListTableHeading dark:bg-darkListTableHeading dark:text-darkListTableHeadingText">
|
|
23
|
+
<tr class="border-b dark:border-gray-700 t-header sticky z-20 top-0 text-xs text-lightListTableHeadingText bg-lightListTableHeading dark:bg-darkListTableHeading dark:text-darkListTableHeadingText">
|
|
25
24
|
<td scope="col" class="list-table-header-cell p-4 sticky-column bg-lightListTableHeading dark:bg-darkListTableHeading">
|
|
26
25
|
<Checkbox
|
|
27
26
|
:modelValue="allFromThisPageChecked"
|
|
@@ -34,10 +33,8 @@
|
|
|
34
33
|
|
|
35
34
|
<td v-for="c in columnsListed" ref="headerRefs" scope="col" class="list-table-header-cell px-2 md:px-3 lg:px-6 py-3" :class="{'sticky-column bg-lightListTableHeading dark:bg-darkListTableHeading': c.listSticky}">
|
|
36
35
|
|
|
37
|
-
<div
|
|
38
|
-
|
|
39
|
-
class="flex items-center " :class="{'cursor-pointer':c.sortable}"
|
|
40
|
-
>
|
|
36
|
+
<div @click="(evt) => c.sortable && onSortButtonClick(evt, c.name)"
|
|
37
|
+
class="flex items-center font-semibold" :class="{'cursor-pointer':c.sortable}">
|
|
41
38
|
{{ c.label }}
|
|
42
39
|
|
|
43
40
|
<div v-if="c.sortable">
|
|
@@ -67,7 +64,7 @@
|
|
|
67
64
|
</div>
|
|
68
65
|
</td>
|
|
69
66
|
|
|
70
|
-
<td scope="col" class="px-6 py-3">
|
|
67
|
+
<td scope="col" class="px-6 py-3 font-semibold">
|
|
71
68
|
{{ $t('Actions') }}
|
|
72
69
|
</td>
|
|
73
70
|
</tr>
|
|
@@ -103,12 +100,12 @@
|
|
|
103
100
|
|
|
104
101
|
<component
|
|
105
102
|
v-for="(row, rowI) in rowsToRender"
|
|
106
|
-
:is="tableRowReplaceInjection ? getCustomComponent(tableRowReplaceInjection) : 'tr'"
|
|
103
|
+
:is="tableRowReplaceInjection ? getCustomComponent(formatComponent(tableRowReplaceInjection)) : 'tr'"
|
|
107
104
|
:key="`row_${row._primaryKeyValue}`"
|
|
108
105
|
:record="row"
|
|
109
106
|
:resource="resource"
|
|
110
107
|
:adminUser="coreStore.adminUser"
|
|
111
|
-
:meta="tableRowReplaceInjection ? tableRowReplaceInjection.meta : undefined"
|
|
108
|
+
:meta="tableRowReplaceInjection ? formatComponent(tableRowReplaceInjection).meta : undefined"
|
|
112
109
|
@click="onClick($event, row)"
|
|
113
110
|
ref="rowRefs"
|
|
114
111
|
class="list-table-body-row bg-lightListTable dark:bg-darkListTable border-lightListBorder dark:border-gray-700 hover:bg-lightListTableRowHover dark:hover:bg-darkListTableRowHover"
|
|
@@ -203,17 +200,17 @@
|
|
|
203
200
|
:key="action.id"
|
|
204
201
|
>
|
|
205
202
|
<component
|
|
206
|
-
|
|
207
|
-
:
|
|
203
|
+
v-if="action.customComponent"
|
|
204
|
+
:is="action.customComponent ? getCustomComponent(formatComponent(action.customComponent)) : CallActionWrapper"
|
|
205
|
+
:meta="formatComponent(action.customComponent).meta"
|
|
208
206
|
:row="row"
|
|
209
207
|
:resource="resource"
|
|
210
|
-
:adminUser="adminUser"
|
|
211
|
-
@callAction="(payload? : Object) => startCustomAction(action.id, row, payload)"
|
|
208
|
+
:adminUser="coreStore.adminUser"
|
|
209
|
+
@callAction="(payload? : Object) => startCustomAction(action.id as string | number, row, payload)"
|
|
212
210
|
>
|
|
213
211
|
<button
|
|
214
212
|
type="button"
|
|
215
213
|
class="border border-gray-300 dark:border-gray-700 dark:border-opacity-0 border-opacity-0 hover:border-opacity-100 dark:hover:border-opacity-100 rounded-md hover:bg-gray-100 dark:hover:bg-gray-800 cursor-pointer"
|
|
216
|
-
:disabled="rowActionLoadingStates?.[action.id]"
|
|
217
214
|
>
|
|
218
215
|
<component
|
|
219
216
|
v-if="action.icon"
|
|
@@ -236,7 +233,7 @@
|
|
|
236
233
|
:deleteRecord="deleteRecord"
|
|
237
234
|
:resourceId="resource.resourceId"
|
|
238
235
|
:startCustomAction="startCustomAction"
|
|
239
|
-
:customActionIconsThreeDotsMenuItems="customActionIconsThreeDotsMenuItems"
|
|
236
|
+
:customActionIconsThreeDotsMenuItems="customActionIconsThreeDotsMenuItems ?? []"
|
|
240
237
|
/>
|
|
241
238
|
</div>
|
|
242
239
|
|
|
@@ -256,12 +253,12 @@
|
|
|
256
253
|
-->
|
|
257
254
|
<div class="af-pagination-container flex flex-row items-center mt-4 xs:flex-row xs:justify-between xs:items-center gap-3">
|
|
258
255
|
|
|
259
|
-
<div class="af-pagination-buttons-container inline-flex "
|
|
256
|
+
<div class="af-pagination-buttons-container af-button-shadow inline-flex rounded "
|
|
260
257
|
v-if="(rows || totalRows) && totalRows >= pageSize && totalRows > 0"
|
|
261
258
|
>
|
|
262
259
|
<!-- Buttons -->
|
|
263
260
|
<button
|
|
264
|
-
class="af-pagination-prev-button flex items-center py-1 px-3 gap-1 text-sm font-medium text-lightListTablePaginationText focus:outline-none bg-lightListTablePaginationBackgoround border-r-0 rounded-s border border-lightListTablePaginationBorder hover:bg-lightListTablePaginationBackgoroundHover hover:text-lightListTablePaginationTextHover focus:z-
|
|
261
|
+
class="af-pagination-prev-button flex items-center py-1 px-3 gap-1 text-sm font-medium text-lightListTablePaginationText focus:outline-none bg-lightListTablePaginationBackgoround border-r-0 rounded-s border border-lightListTablePaginationBorder hover:bg-lightListTablePaginationBackgoroundHover hover:text-lightListTablePaginationTextHover focus:z-20 focus:ring-4 focus:ring-lightListTablePaginationFocusRing dark:focus:ring-darkListTablePaginationFocusRing dark:bg-darkListTablePaginationBackgoround dark:text-darkListTablePaginationText dark:border-darkListTablePaginationBorder dark:hover:text-darkListTablePaginationTextHover dark:hover:bg-darkListTablePaginationBackgoroundHover disabled:opacity-50"
|
|
265
262
|
@click="page--; pageInput = page.toString();"
|
|
266
263
|
:disabled="page <= 1"
|
|
267
264
|
>
|
|
@@ -273,7 +270,7 @@
|
|
|
273
270
|
</span>
|
|
274
271
|
</button>
|
|
275
272
|
<button
|
|
276
|
-
class="af-pagination-first-page-button flex items-center py-1 px-3 text-sm font-medium text-lightListTablePaginationText focus:outline-none bg-lightListTablePaginationBackgoround border-r-0 border border-lightListTablePaginationBorder hover:bg-lightListTablePaginationBackgoroundHover hover:text-lightListTablePaginationTextHover focus:z-
|
|
273
|
+
class="af-pagination-first-page-button flex items-center py-1 px-3 text-sm font-medium text-lightListTablePaginationText focus:outline-none bg-lightListTablePaginationBackgoround border-r-0 border border-lightListTablePaginationBorder hover:bg-lightListTablePaginationBackgoroundHover hover:text-lightListTablePaginationTextHover z-10 focus:z-20 focus:ring-4 focus:ring-lightListTablePaginationFocusRing dark:focus:ring-darkListTablePaginationFocusRing dark:bg-darkListTablePaginationBackgoround dark:text-darkListTablePaginationText dark:border-darkListTablePaginationBorder dark:hover:text-darkListTablePaginationTextHover dark:hover:bg-darkListTablePaginationBackgoroundHover disabled:opacity-50"
|
|
277
274
|
@click="page = 1;
|
|
278
275
|
pageInput = page.toString();"
|
|
279
276
|
:disabled="page <= 1"
|
|
@@ -284,13 +281,13 @@
|
|
|
284
281
|
type="text"
|
|
285
282
|
v-model="pageInput"
|
|
286
283
|
:style="{ width: `${Math.max(1, pageInput.length+4)}ch` }"
|
|
287
|
-
class="af-pagination-input min-w-10 outline-none inline-block py-1.5 px-3 text-sm text-center text-lightListTablePaginationCurrentPageText border border-lightListTablePaginationBorder dark:border-darkListTablePaginationBorder dark:text-darkListTablePaginationCurrentPageText dark:bg-darkListTablePaginationBackgoround
|
|
284
|
+
class="af-pagination-input z-10 min-w-10 outline-none inline-block py-1.5 px-3 text-sm text-center text-lightListTablePaginationCurrentPageText border border-lightListTablePaginationBorder dark:border-darkListTablePaginationBorder dark:text-darkListTablePaginationCurrentPageText dark:bg-darkListTablePaginationBackgoround"
|
|
288
285
|
@keydown="onPageKeydown($event)"
|
|
289
286
|
@blur="validatePageInput()"
|
|
290
287
|
/>
|
|
291
288
|
|
|
292
289
|
<button
|
|
293
|
-
class="af-pagination-last-page-button flex items-center py-1 px-3 text-sm font-medium text-lightListTablePaginationText focus:outline-none bg-lightListTablePaginationBackgoround border-l-0 border border-lightListTablePaginationBorder hover:bg-lightListTablePaginationBackgoroundHover hover:text-lightListTablePaginationTextHover focus:
|
|
290
|
+
class="af-pagination-last-page-button z-10 flex items-center py-1 px-3 text-sm font-medium text-lightListTablePaginationText focus:outline-none bg-lightListTablePaginationBackgoround border-l-0 border border-lightListTablePaginationBorder hover:bg-lightListTablePaginationBackgoroundHover hover:text-lightListTablePaginationTextHover focus:ring-4 focus:z-20 focus:ring-lightListTablePaginationFocusRing dark:focus:ring-darkListTablePaginationFocusRing dark:bg-darkListTablePaginationBackgoround dark:text-darkListTablePaginationText dark:border-darkListTablePaginationBorder dark:hover:text-white dark:hover:bg-darkListTablePaginationBackgoroundHover disabled:opacity-50"
|
|
294
291
|
@click="page = totalPages; pageInput = page.toString();" :disabled="page >= totalPages">
|
|
295
292
|
{{ totalPages }}
|
|
296
293
|
</button>
|
|
@@ -347,7 +344,7 @@ import { computed, onMounted, ref, watch, useTemplateRef, nextTick, type Ref } f
|
|
|
347
344
|
import { callAdminForthApi } from '@/utils';
|
|
348
345
|
import { useI18n } from 'vue-i18n';
|
|
349
346
|
import ValueRenderer from '@/components/ValueRenderer.vue';
|
|
350
|
-
import { getCustomComponent } from '@/utils';
|
|
347
|
+
import { getCustomComponent, formatComponent } from '@/utils';
|
|
351
348
|
import { useCoreStore } from '@/stores/core';
|
|
352
349
|
import { showSuccesTost, showErrorTost } from '@/composables/useFrontendApi';
|
|
353
350
|
import SkeleteLoader from '@/components/SkeleteLoader.vue';
|
|
@@ -360,7 +357,7 @@ import {
|
|
|
360
357
|
} from '@iconify-prerendered/vue-flowbite';
|
|
361
358
|
import router from '@/router';
|
|
362
359
|
import { Tooltip } from '@/afcl';
|
|
363
|
-
import type { AdminForthResourceCommon, AdminForthResourceColumnCommon, AdminForthComponentDeclarationFull } from '@/types/Common';
|
|
360
|
+
import type { AdminForthResourceCommon, AdminForthResourceColumnCommon, AdminForthComponentDeclarationFull, AdminForthComponentDeclaration } from '@/types/Common';
|
|
364
361
|
import { useAdminforth } from '@/adminforth';
|
|
365
362
|
import Checkbox from '@/afcl/Checkbox.vue';
|
|
366
363
|
import ListActionsThreeDots from '@/components/ListActionsThreeDots.vue';
|
|
@@ -383,8 +380,8 @@ const props = defineProps<{
|
|
|
383
380
|
containerHeight?: number,
|
|
384
381
|
itemHeight?: number,
|
|
385
382
|
bufferSize?: number,
|
|
386
|
-
customActionIconsThreeDotsMenuItems?:
|
|
387
|
-
tableRowReplaceInjection?:
|
|
383
|
+
customActionIconsThreeDotsMenuItems?: AdminForthComponentDeclaration[]
|
|
384
|
+
tableRowReplaceInjection?: AdminForthComponentDeclaration,
|
|
388
385
|
isVirtualScrollEnabled: boolean
|
|
389
386
|
}>();
|
|
390
387
|
|
|
@@ -414,7 +411,7 @@ const sort: Ref<Array<{field: string, direction: string}>> = ref([]);
|
|
|
414
411
|
const showListActionsThreeDots = computed(() => {
|
|
415
412
|
return props.resource?.options?.actions?.some(a => a.showIn?.listThreeDotsMenu) // show if any action is set to show in three dots menu
|
|
416
413
|
|| (props.customActionIconsThreeDotsMenuItems && props.customActionIconsThreeDotsMenuItems.length > 0) // or if there are custom action icons for three dots menu
|
|
417
|
-
|| !props.resource?.options
|
|
414
|
+
|| !props.resource?.options?.baseActionsAsQuickIcons // or if there is no baseActionsAsQuickIcons
|
|
418
415
|
|| (props.resource?.options.baseActionsAsQuickIcons && props.resource?.options.baseActionsAsQuickIcons.length < 3) // if there all 3 base actions are shown as quick icons - hide three dots icon
|
|
419
416
|
})
|
|
420
417
|
|
|
@@ -609,7 +606,7 @@ async function deleteRecord(row: any) {
|
|
|
609
606
|
|
|
610
607
|
const actionLoadingStates = ref<Record<string | number, boolean>>({});
|
|
611
608
|
|
|
612
|
-
async function startCustomAction(actionId: string, row: any, extraData: Record<string, any> = {}) {
|
|
609
|
+
async function startCustomAction(actionId: string | number, row: any, extraData: Record<string, any> = {}) {
|
|
613
610
|
|
|
614
611
|
actionLoadingStates.value[actionId] = true;
|
|
615
612
|
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
</div>
|
|
53
53
|
</div>
|
|
54
54
|
|
|
55
|
-
<div v-if="coreStore
|
|
55
|
+
<div v-if="coreStore?.config?.defaultUserExists && !isLocalhost" class="p-4 mb-4 text-white rounded-lg bg-red-700/80 fill-white text-sm">
|
|
56
56
|
<IconExclamationCircleOutline class="inline-block align-text-bottom mr-0,5 w-5 h-5" />
|
|
57
57
|
Default user <strong>"adminforth"</strong> detected. Delete it and create your own account.
|
|
58
58
|
</div>
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
<template >
|
|
2
|
-
<div class="relative" v-if="threeDotsDropdownItems?.length || customActions?.length || (bulkActions?.some((action:
|
|
2
|
+
<div class="relative" v-if="threeDotsDropdownItems?.length || customActions?.length || (bulkActions?.some((action: AdminForthBulkActionFront) => action.showInThreeDotsDropdown))">
|
|
3
3
|
<button
|
|
4
4
|
ref="buttonTriggerRef"
|
|
5
5
|
@click="toggleDropdownVisibility"
|
|
6
|
-
class="flex items-center py-2 px-2 text-sm font-medium text-lightThreeDotsMenuIconDots focus:outline-none bg-lightThreeDotsMenuIconBackground rounded border border-lightThreeDotsMenuIconBackgroundBorder hover:bg-lightThreeDotsMenuIconBackgroundHover hover:text-lightThreeDotsMenuIconDotsHover focus:z-10 focus:ring-4 focus:ring-lightThreeDotsMenuIconFocus dark:focus:ring-darkThreeDotsMenuIconFocus dark:bg-darkThreeDotsMenuIconBackground dark:text-darkThreeDotsMenuIconDots dark:border-darkThreeDotsMenuIconBackgroundBorder dark:hover:text-darkThreeDotsMenuIconDotsHover dark:hover:bg-darkThreeDotsMenuIconBackgroundHover rounded-default"
|
|
6
|
+
class="flex transition-all items-center af-button-shadow py-2.5 px-2.5 text-sm font-medium text-lightThreeDotsMenuIconDots focus:outline-none bg-lightThreeDotsMenuIconBackground rounded border border-lightThreeDotsMenuIconBackgroundBorder hover:bg-lightThreeDotsMenuIconBackgroundHover hover:text-lightThreeDotsMenuIconDotsHover focus:z-10 focus:ring-4 focus:ring-lightThreeDotsMenuIconFocus dark:focus:ring-darkThreeDotsMenuIconFocus dark:bg-darkThreeDotsMenuIconBackground dark:text-darkThreeDotsMenuIconDots dark:border-darkThreeDotsMenuIconBackgroundBorder dark:hover:text-darkThreeDotsMenuIconDotsHover dark:hover:bg-darkThreeDotsMenuIconBackgroundHover rounded-default"
|
|
7
7
|
>
|
|
8
8
|
<svg class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 4 15">
|
|
9
9
|
<path d="M3.5 1.5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0Zm0 6.041a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0Zm0 5.959a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0Z"/>
|
|
@@ -46,8 +46,9 @@
|
|
|
46
46
|
<li v-for="action in customActions" :key="action.id">
|
|
47
47
|
<div class="wrapper">
|
|
48
48
|
<component
|
|
49
|
-
|
|
50
|
-
:
|
|
49
|
+
v-if="action.customComponent"
|
|
50
|
+
:is="(action.customComponent && getCustomComponent(formatComponent(action.customComponent))) || CallActionWrapper"
|
|
51
|
+
:meta="formatComponent(action.customComponent).meta"
|
|
51
52
|
@callAction="(payload? : Object) => handleActionClick(action, payload)"
|
|
52
53
|
>
|
|
53
54
|
<a @click.prevent class="block px-4 py-2 hover:text-lightThreeDotsMenuBodyTextHover hover:bg-lightThreeDotsMenuBodyBackgroundHover dark:hover:bg-darkThreeDotsMenuBodyBackgroundHover dark:hover:text-darkThreeDotsMenuBodyTextHover">
|
|
@@ -88,14 +89,14 @@
|
|
|
88
89
|
|
|
89
90
|
|
|
90
91
|
<script setup lang="ts">
|
|
91
|
-
import { getCustomComponent, getIcon } from '@/utils';
|
|
92
|
+
import { getCustomComponent, getIcon, formatComponent } from '@/utils';
|
|
92
93
|
import { useCoreStore } from '@/stores/core';
|
|
93
94
|
import { useAdminforth } from '@/adminforth';
|
|
94
95
|
import { callAdminForthApi } from '@/utils';
|
|
95
96
|
import { useRoute, useRouter } from 'vue-router';
|
|
96
97
|
import CallActionWrapper from '@/components/CallActionWrapper.vue'
|
|
97
98
|
import { ref, type ComponentPublicInstance, onMounted, onUnmounted } from 'vue';
|
|
98
|
-
import type {
|
|
99
|
+
import type { AdminForthActionFront, AdminForthBulkActionFront, AdminForthComponentDeclarationFull } from '@/types/Common';
|
|
99
100
|
import type { AdminForthActionInput } from '@/types/Back';
|
|
100
101
|
|
|
101
102
|
const { list, alert} = useAdminforth();
|
|
@@ -109,8 +110,8 @@ const buttonTriggerRef = ref<HTMLElement | null>(null);
|
|
|
109
110
|
|
|
110
111
|
const props = defineProps({
|
|
111
112
|
threeDotsDropdownItems: Array<AdminForthComponentDeclarationFull>,
|
|
112
|
-
customActions: Array<
|
|
113
|
-
bulkActions: Array<
|
|
113
|
+
customActions: Array<AdminForthActionFront>,
|
|
114
|
+
bulkActions: Array<AdminForthBulkActionFront>,
|
|
114
115
|
checkboxes: Array,
|
|
115
116
|
updateList: {
|
|
116
117
|
type: Function,
|
package/dist/spa/src/i18n.ts
CHANGED
|
@@ -4,9 +4,11 @@ import { callAdminForthApi } from '@/utils';
|
|
|
4
4
|
import websocket from '@/websocket';
|
|
5
5
|
import { useAdminforth } from '@/adminforth';
|
|
6
6
|
|
|
7
|
-
import type { AdminForthResourceCommon, AdminForthResourceColumnCommon, GetBaseConfigResponse, ResourceVeryShort, AdminUser, UserData, AdminForthConfigMenuItem, AdminForthConfigForFrontend } from '@/types/Common';
|
|
7
|
+
import type { AdminForthResourceCommon, AdminForthResourceColumnCommon, GetBaseConfigResponse, ResourceVeryShort, AdminUser, UserData, AdminForthConfigMenuItem, AdminForthConfigForFrontend, AdminForthResourceFrontend } from '@/types/Common';
|
|
8
8
|
import type { Ref } from 'vue'
|
|
9
9
|
|
|
10
|
+
|
|
11
|
+
|
|
10
12
|
export const useCoreStore = defineStore('core', () => {
|
|
11
13
|
const { alert } = useAdminforth();
|
|
12
14
|
const resourceById: Ref<Record<string, ResourceVeryShort>> = ref({});
|
|
@@ -15,7 +17,7 @@ export const useCoreStore = defineStore('core', () => {
|
|
|
15
17
|
const menu: Ref<AdminForthConfigMenuItem[]> = ref([]);
|
|
16
18
|
const config: Ref<AdminForthConfigForFrontend | null> = ref(null);
|
|
17
19
|
const record: Ref<any | null> = ref({});
|
|
18
|
-
const resource: Ref<
|
|
20
|
+
const resource: Ref<AdminForthResourceFrontend | null> = ref(null);
|
|
19
21
|
const userData: Ref<UserData | null> = ref(null);
|
|
20
22
|
const isResourceFetching = ref(false);
|
|
21
23
|
const isInternetError = ref(false);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Express, Request } from 'express';
|
|
1
|
+
import type { Express, Request, Response } from 'express';
|
|
2
2
|
import type { Writable } from 'stream';
|
|
3
3
|
|
|
4
4
|
import { ActionCheckSource, AdminForthFilterOperators, AdminForthSortDirections, AllowedActionsEnum, AdminForthResourcePages,
|
|
@@ -67,6 +67,10 @@ export interface IHttpServer {
|
|
|
67
67
|
headers: {[key: string]: string},
|
|
68
68
|
cookies: {[key: string]: string},
|
|
69
69
|
response: IAdminForthHttpResponse,
|
|
70
|
+
requestUrl: string,
|
|
71
|
+
abortSignal: AbortSignal,
|
|
72
|
+
_raw_express_req: Request,
|
|
73
|
+
_raw_express_res: Response,
|
|
70
74
|
) => void,
|
|
71
75
|
}): void;
|
|
72
76
|
|
|
@@ -1637,7 +1641,7 @@ export interface AdminForthConfigCustomization extends Omit<AdminForthInputConfi
|
|
|
1637
1641
|
|
|
1638
1642
|
loginPageInjections: {
|
|
1639
1643
|
underInputs: Array<AdminForthComponentDeclarationFull>,
|
|
1640
|
-
underLoginButton
|
|
1644
|
+
underLoginButton: Array<AdminForthComponentDeclarationFull>,
|
|
1641
1645
|
panelHeader: Array<AdminForthComponentDeclarationFull>,
|
|
1642
1646
|
},
|
|
1643
1647
|
|
|
@@ -1825,7 +1829,7 @@ export type AllowedActions = {
|
|
|
1825
1829
|
/**
|
|
1826
1830
|
* General options for resource.
|
|
1827
1831
|
*/
|
|
1828
|
-
export interface ResourceOptionsInput extends Omit<NonNullable<AdminForthResourceInputCommon['options']>, 'allowedActions' | 'bulkActions'> {
|
|
1832
|
+
export interface ResourceOptionsInput extends Omit<NonNullable<AdminForthResourceInputCommon['options']>, 'allowedActions' | 'bulkActions' | 'actions'> {
|
|
1829
1833
|
|
|
1830
1834
|
/**
|
|
1831
1835
|
* Custom bulk actions list. Bulk actions available in list view when user selects multiple records by
|
|
@@ -314,6 +314,25 @@ export type FieldGroup = {
|
|
|
314
314
|
noTitle?: boolean;
|
|
315
315
|
};
|
|
316
316
|
|
|
317
|
+
export interface AdminForthActionFront extends Omit<AdminForthActionInput, 'id'> {
|
|
318
|
+
id: string;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
export interface AdminForthBulkActionFront extends Omit<AdminForthBulkActionCommon, 'id'> {
|
|
322
|
+
id: string,
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
type AdminforthOptionsCommon = NonNullable<AdminForthResourceCommon['options']>;
|
|
326
|
+
|
|
327
|
+
export interface AdminForthOptionsForFrontend extends Omit<AdminforthOptionsCommon, 'actions' | 'bulkActions'> {
|
|
328
|
+
actions?: AdminForthActionFront[],
|
|
329
|
+
bulkActions?: AdminForthBulkActionFront[],
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
export interface AdminForthResourceFrontend extends Omit<AdminForthResourceCommon, 'options'> {
|
|
333
|
+
options: AdminForthOptionsForFrontend;
|
|
334
|
+
}
|
|
335
|
+
|
|
317
336
|
/**
|
|
318
337
|
* Resource describes one table or collection in database.
|
|
319
338
|
* AdminForth generates set of pages for 'list', 'show', 'edit', 'create', 'filter' operations for each resource.
|
|
@@ -361,17 +380,17 @@ export interface AdminForthResourceInputCommon {
|
|
|
361
380
|
recordLabel?: (item: any) => string,
|
|
362
381
|
|
|
363
382
|
|
|
364
|
-
/**
|
|
365
|
-
* If true, user will not see warning about unsaved changes when tries to leave edit or create page with unsaved changes.
|
|
366
|
-
* default is false
|
|
367
|
-
*/
|
|
368
|
-
dontShowWarningAboutUnsavedChanges?: boolean,
|
|
369
383
|
|
|
370
384
|
/**
|
|
371
385
|
* General options for resource.
|
|
372
386
|
*/
|
|
373
387
|
options?: {
|
|
374
388
|
|
|
389
|
+
/**
|
|
390
|
+
* If true, user will not see warning about unsaved changes when tries to leave edit or create page with unsaved changes.
|
|
391
|
+
* default is false
|
|
392
|
+
*/
|
|
393
|
+
dontShowWarningAboutUnsavedChanges?: boolean,
|
|
375
394
|
|
|
376
395
|
/**
|
|
377
396
|
* Show quick action icons for base actions (show, edit, delete) in list view.
|
|
@@ -1172,6 +1191,7 @@ export interface AdminForthConfigForFrontend {
|
|
|
1172
1191
|
loginPageInjections: {
|
|
1173
1192
|
underInputs: Array<AdminForthComponentDeclaration>,
|
|
1174
1193
|
panelHeader: Array<AdminForthComponentDeclaration>,
|
|
1194
|
+
underLoginButton: Array<AdminForthComponentDeclaration>,
|
|
1175
1195
|
},
|
|
1176
1196
|
rememberMeDuration: string,
|
|
1177
1197
|
showBrandNameInSidebar: boolean,
|
|
@@ -144,7 +144,7 @@ export interface FrontendAPIInterface {
|
|
|
144
144
|
/**
|
|
145
145
|
* Run save interceptors for a specific resource or all resources if no resourceId is provided
|
|
146
146
|
*/
|
|
147
|
-
runSaveInterceptors(params: { action: 'create'|'edit'; values: any; resource: any; resourceId: string; }): Promise<{ ok: boolean; error?: string | null; extra?:
|
|
147
|
+
runSaveInterceptors(params: { action: 'create'|'edit'; values: any; resource: any; resourceId: string; }): Promise<{ ok: boolean; error?: string | null; extra?: any; }>;
|
|
148
148
|
|
|
149
149
|
/**
|
|
150
150
|
* Clear save interceptors for a specific resource or all resources if no resourceId is provided
|
|
@@ -152,6 +152,11 @@ export interface FrontendAPIInterface {
|
|
|
152
152
|
* @param resourceId - The resource ID to clear interceptors for
|
|
153
153
|
*/
|
|
154
154
|
clearSaveInterceptors(resourceId?: string): void;
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Register a save interceptor for a specific resource
|
|
158
|
+
*/
|
|
159
|
+
registerSaveInterceptor(handler: (ctx: { action: 'create'|'edit'; values: any; resource: any; }) => Promise<{ ok: boolean; error?: string | null; extra?: any; }>): void;
|
|
155
160
|
}
|
|
156
161
|
|
|
157
162
|
export type ConfirmParams = {
|
|
@@ -4,13 +4,18 @@ import { type AdminForthResourceCommon } from '../types/Common';
|
|
|
4
4
|
import { useAdminforth } from '@/adminforth';
|
|
5
5
|
import { showErrorTost } from '@/composables/useFrontendApi'
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
let getResourceDataLastAbortController: AbortController | null = null;
|
|
8
8
|
export async function getList(resource: AdminForthResourceCommon, isPageLoaded: boolean, page: number | null , pageSize: number, sort: any, checkboxes:{ value: any[] }, filters: any = [] ) {
|
|
9
9
|
let rows: any[] = [];
|
|
10
10
|
let totalRows: number | null = null;
|
|
11
11
|
if (!isPageLoaded) {
|
|
12
12
|
return;
|
|
13
13
|
}
|
|
14
|
+
const abortController = new AbortController();
|
|
15
|
+
if (getResourceDataLastAbortController) {
|
|
16
|
+
getResourceDataLastAbortController.abort();
|
|
17
|
+
}
|
|
18
|
+
getResourceDataLastAbortController = abortController;
|
|
14
19
|
const data = await callAdminForthApi({
|
|
15
20
|
path: '/get_resource_data',
|
|
16
21
|
method: 'POST',
|
|
@@ -21,7 +26,8 @@ export async function getList(resource: AdminForthResourceCommon, isPageLoaded:
|
|
|
21
26
|
offset: ((page || 1) - 1) * pageSize,
|
|
22
27
|
filters: filters,
|
|
23
28
|
sort: sort,
|
|
24
|
-
}
|
|
29
|
+
},
|
|
30
|
+
abortSignal: abortController.signal
|
|
25
31
|
});
|
|
26
32
|
if (data.error) {
|
|
27
33
|
showErrorTost(data.error);
|