adminforth 3.10.0 → 3.11.0
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/spa/src/App.vue +32 -2
- package/dist/spa/src/components/Toast.vue +91 -23
- package/package.json +1 -1
package/dist/spa/src/App.vue
CHANGED
|
@@ -118,7 +118,7 @@
|
|
|
118
118
|
</div>
|
|
119
119
|
</div>
|
|
120
120
|
<AcceptModal />
|
|
121
|
-
<div v-if="toastStore.toasts.length>0" class="fixed bottom-5
|
|
121
|
+
<div v-if="toastStore.toasts.length>0" class="fixed bottom-5 flex gap-1 flex-col-reverse z-[100]" :style="{ right: `calc(${appPaddingRight}px + 1.25rem)` }">
|
|
122
122
|
<transition-group
|
|
123
123
|
name="fade"
|
|
124
124
|
tag="div"
|
|
@@ -185,7 +185,7 @@
|
|
|
185
185
|
</style>
|
|
186
186
|
|
|
187
187
|
<script setup lang="ts">
|
|
188
|
-
import { computed, nextTick, onMounted, ref, watch, onBeforeMount } from 'vue';
|
|
188
|
+
import { computed, nextTick, onMounted, onUnmounted, ref, watch, onBeforeMount } from 'vue';
|
|
189
189
|
import { RouterView } from 'vue-router';
|
|
190
190
|
import { Dropdown } from 'flowbite'
|
|
191
191
|
import './index.scss'
|
|
@@ -296,6 +296,20 @@ const title = computed(() => {
|
|
|
296
296
|
return `${coreStore.config?.title || coreStore.config?.brandName || 'Adminforth'} | ${ resourceTitle || route.meta.title || ' '}`;
|
|
297
297
|
});
|
|
298
298
|
|
|
299
|
+
const appPaddingRight = ref(0);
|
|
300
|
+
let appPaddingObserver: MutationObserver | null = null;
|
|
301
|
+
let appPaddingResizeObserver: ResizeObserver | null = null;
|
|
302
|
+
|
|
303
|
+
function updateAppPaddingRight() {
|
|
304
|
+
const app = document.getElementById('app');
|
|
305
|
+
if (!app) {
|
|
306
|
+
appPaddingRight.value = 0;
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
const style = window.getComputedStyle(app);
|
|
310
|
+
appPaddingRight.value = parseInt(style.paddingRight, 10) || 0;
|
|
311
|
+
}
|
|
312
|
+
|
|
299
313
|
// TODO - useHead not working in this way
|
|
300
314
|
// useHead({
|
|
301
315
|
// title: title.value
|
|
@@ -349,6 +363,22 @@ onMounted(async () => {
|
|
|
349
363
|
|
|
350
364
|
window.addEventListener('online', () => coreStore.isInternetError = false);
|
|
351
365
|
window.addEventListener('offline', () => coreStore.isInternetError = true);
|
|
366
|
+
|
|
367
|
+
const app = document.getElementById('app');
|
|
368
|
+
updateAppPaddingRight();
|
|
369
|
+
if (app) {
|
|
370
|
+
// padding-right changes come in via inline style / class updates on #app
|
|
371
|
+
appPaddingObserver = new MutationObserver(updateAppPaddingRight);
|
|
372
|
+
appPaddingObserver.observe(app, { attributes: true, attributeFilter: ['style', 'class'] });
|
|
373
|
+
// and via viewport/box size changes that affect resolved padding
|
|
374
|
+
appPaddingResizeObserver = new ResizeObserver(updateAppPaddingRight);
|
|
375
|
+
appPaddingResizeObserver.observe(app);
|
|
376
|
+
}
|
|
377
|
+
})
|
|
378
|
+
|
|
379
|
+
onUnmounted(() => {
|
|
380
|
+
appPaddingObserver?.disconnect();
|
|
381
|
+
appPaddingResizeObserver?.disconnect();
|
|
352
382
|
})
|
|
353
383
|
|
|
354
384
|
onBeforeMount(()=>{
|
|
@@ -2,21 +2,30 @@
|
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
<div class="afcl-toast flex items-center w-full p-4 rounded-lg shadow-lg dark:text-darkToastText dark:bg-darkToastBackground bg-lightToastBackground text-lightToastText border-l-4"
|
|
5
|
-
:class="
|
|
5
|
+
:class="variantConfig.borderClass"
|
|
6
6
|
role="alert"
|
|
7
|
+
@mouseenter="pauseTimer"
|
|
8
|
+
@mouseleave="resumeTimer"
|
|
7
9
|
>
|
|
8
|
-
<div
|
|
9
|
-
<
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
<
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
10
|
+
<div class="relative w-8 h-8 flex items-center justify-center">
|
|
11
|
+
<div class="absolute af-toast-icon inline-flex items-center justify-center flex-shrink-0 rounded-lg w-full h-full" :class="variantConfig.iconClass">
|
|
12
|
+
<component :is="variantConfig.icon" class="w-5 h-5" aria-hidden="true" />
|
|
13
|
+
</div>
|
|
14
|
+
<svg v-if="hasTimer" class="absolute inset-0 w-full h-full rotate-180">
|
|
15
|
+
<rect
|
|
16
|
+
x="1"
|
|
17
|
+
y="1"
|
|
18
|
+
width="calc(100% - 2px)"
|
|
19
|
+
height="calc(100% - 2px)"
|
|
20
|
+
:class="variantConfig.strokeClass"
|
|
21
|
+
rx="8"
|
|
22
|
+
fill="none"
|
|
23
|
+
stroke-width="2"
|
|
24
|
+
pathLength="100"
|
|
25
|
+
:stroke-dasharray="`100`"
|
|
26
|
+
:stroke-dashoffset="dashOffset"
|
|
27
|
+
/>
|
|
28
|
+
</svg>
|
|
20
29
|
</div>
|
|
21
30
|
<div class="flex flex-col items-center justify-center break-all overflow-hidden [display:-webkit-box] [-webkit-box-orient:vertical] [-webkit-line-clamp:70]">
|
|
22
31
|
<div class="ms-3 text-sm font-normal max-w-xs pr-2" v-if="toast.messageHtml" v-html="toast.messageHtml"></div>
|
|
@@ -36,14 +45,13 @@
|
|
|
36
45
|
<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"/>
|
|
37
46
|
</svg>
|
|
38
47
|
</button>
|
|
39
|
-
<!-- <div class="h-full ml-3 w-1 rounded-r-lg" :class="toast.variant == 'info' ? 'bg-lightPrimary dark:bg-darkPrimary' : toast.variant == 'danger' ? 'bg-red-500 dark:bg-red-800' : toast.variant == 'warning' ? 'bg-orange-500 dark:bg-orange-700' : 'bg-green-500 dark:bg-green-800'"></div> -->
|
|
40
48
|
</div>
|
|
41
49
|
|
|
42
50
|
|
|
43
51
|
</template>
|
|
44
52
|
|
|
45
53
|
<script setup lang="ts">
|
|
46
|
-
import { onMounted } from 'vue';
|
|
54
|
+
import { onMounted, onUnmounted, computed, ref } from 'vue';
|
|
47
55
|
import { useToastStore } from '@/stores/toast';
|
|
48
56
|
import { IconInfoCircleSolid, IconCloseCircleSolid, IconExclamationCircleSolid, IconCheckCircleSolid } from '@iconify-prerendered/vue-flowbite';
|
|
49
57
|
|
|
@@ -59,6 +67,69 @@ const props = defineProps<{
|
|
|
59
67
|
buttons?: { value: any; label: string }[];
|
|
60
68
|
}
|
|
61
69
|
}>();
|
|
70
|
+
|
|
71
|
+
const VARIANT_CONFIGS = {
|
|
72
|
+
info: {
|
|
73
|
+
icon: IconInfoCircleSolid,
|
|
74
|
+
borderClass: 'border-lightPrimary dark:border-darkPrimary',
|
|
75
|
+
iconClass: 'text-lightPrimary dark:text-darkPrimary bg-lightPrimaryOpacity dark:bg-darkPrimary dark:!text-blue-100',
|
|
76
|
+
strokeClass: 'stroke-lightPrimary dark:stroke-darkPrimary',
|
|
77
|
+
},
|
|
78
|
+
danger: {
|
|
79
|
+
icon: IconCloseCircleSolid,
|
|
80
|
+
borderClass: 'border-red-500 dark:border-red-800',
|
|
81
|
+
iconClass: 'text-red-500 bg-red-100 dark:bg-red-800 dark:text-red-200',
|
|
82
|
+
strokeClass: 'stroke-red-500 dark:stroke-red-800',
|
|
83
|
+
},
|
|
84
|
+
warning: {
|
|
85
|
+
icon: IconExclamationCircleSolid,
|
|
86
|
+
borderClass: 'border-orange-500 dark:border-orange-700',
|
|
87
|
+
iconClass: 'text-orange-500 bg-orange-100 dark:bg-orange-700 dark:text-orange-200',
|
|
88
|
+
strokeClass: 'stroke-orange-500 dark:stroke-orange-700',
|
|
89
|
+
},
|
|
90
|
+
success: {
|
|
91
|
+
icon: IconCheckCircleSolid,
|
|
92
|
+
borderClass: 'border-green-500 dark:border-green-800',
|
|
93
|
+
iconClass: 'text-green-500 bg-green-100 dark:bg-green-800 dark:text-green-200',
|
|
94
|
+
strokeClass: 'stroke-green-500 dark:stroke-green-800',
|
|
95
|
+
},
|
|
96
|
+
} as const;
|
|
97
|
+
|
|
98
|
+
const variantConfig = computed(() => VARIANT_CONFIGS[props.toast.variant as keyof typeof VARIANT_CONFIGS] ?? VARIANT_CONFIGS.success);
|
|
99
|
+
|
|
100
|
+
const hasTimer = computed(() => props.toast.timeout !== 'unlimited');
|
|
101
|
+
const totalMs = (typeof props.toast.timeout === 'number' ? props.toast.timeout : 10) * 1e3;
|
|
102
|
+
const dashOffset = ref(0);
|
|
103
|
+
let rafId: number | null = null;
|
|
104
|
+
let remainingMs = totalMs;
|
|
105
|
+
let startedAt = 0;
|
|
106
|
+
|
|
107
|
+
function frame(now: number) {
|
|
108
|
+
const leftMs = Math.max(0, remainingMs - (now - startedAt));
|
|
109
|
+
dashOffset.value = (1 - leftMs / totalMs) * 100;
|
|
110
|
+
if (leftMs <= 0) {
|
|
111
|
+
rafId = null;
|
|
112
|
+
// resolve with undefined on auto-timeout
|
|
113
|
+
toastStore.resolveToast(props.toast.id);
|
|
114
|
+
emit('close');
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
rafId = requestAnimationFrame(frame);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function pauseTimer() {
|
|
121
|
+
if (rafId === null) return;
|
|
122
|
+
cancelAnimationFrame(rafId);
|
|
123
|
+
rafId = null;
|
|
124
|
+
remainingMs = Math.max(0, remainingMs - (performance.now() - startedAt));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function resumeTimer() {
|
|
128
|
+
if (!hasTimer.value || rafId !== null || remainingMs <= 0) return;
|
|
129
|
+
startedAt = performance.now();
|
|
130
|
+
rafId = requestAnimationFrame(frame);
|
|
131
|
+
}
|
|
132
|
+
|
|
62
133
|
function closeToast() {
|
|
63
134
|
// resolve with undefined on close (X button)
|
|
64
135
|
toastStore.resolveToast(props.toast.id);
|
|
@@ -71,14 +142,11 @@ function onButtonClick(value: any) {
|
|
|
71
142
|
}
|
|
72
143
|
|
|
73
144
|
onMounted(() => {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
emit('close');
|
|
80
|
-
}, (props.toast.timeout || 10) * 1e3 );
|
|
81
|
-
}
|
|
145
|
+
resumeTimer();
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
onUnmounted(() => {
|
|
149
|
+
if (rafId !== null) cancelAnimationFrame(rafId);
|
|
82
150
|
});
|
|
83
151
|
|
|
84
152
|
</script>
|