adminforth 3.10.0 → 3.11.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.
@@ -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 right-5 flex gap-1 flex-col-reverse z-[100]">
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="toast.variant == 'info' ? 'border-lightPrimary dark:border-darkPrimary' : toast.variant == 'danger' ? 'border-red-500 dark:border-red-800' : toast.variant == 'warning' ? 'border-orange-500 dark:border-orange-700' : 'border-green-500 dark:border-green-800'"
5
+ :class="variantConfig.borderClass"
6
6
  role="alert"
7
+ @mouseenter="pauseTimer"
8
+ @mouseleave="resumeTimer"
7
9
  >
8
- <div v-if="toast.variant == 'info'" class="af-toast-icon inline-flex items-center justify-center flex-shrink-0 w-8 h-8 text-lightPrimary dark:text-darkPrimary bg-lightPrimaryOpacity rounded-lg dark:bg-darkPrimary dark:!text-blue-100">
9
- <IconInfoCircleSolid class="w-5 h-5" aria-hidden="true" />
10
- </div>
11
- <div v-else-if="toast.variant == 'danger'" class="af-toast-icon inline-flex items-center justify-center flex-shrink-0 w-8 h-8 text-red-500 bg-red-100 rounded-lg dark:bg-red-800 dark:text-red-200">
12
- <IconCloseCircleSolid class="w-5 h-5" aria-hidden="true" />
13
- </div>
14
- <div v-else-if="toast.variant == 'warning'" class="af-toast-icon inline-flex items-center justify-center flex-shrink-0 w-8 h-8 text-orange-500 bg-orange-100 rounded-lg dark:bg-orange-700 dark:text-orange-200">
15
- <IconExclamationCircleSolid class="w-5 h-5" aria-hidden="true" />
16
-
17
- </div>
18
- <div v-else class="af-toast-icon inline-flex items-center justify-center flex-shrink-0 w-8 h-8 text-green-500 bg-green-100 rounded-lg dark:bg-green-800 dark:text-green-200">
19
- <IconCheckCircleSolid class="w-5 h-5" aria-hidden="true" />
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/50 dark:!text-blue-100',
76
+ strokeClass: 'stroke-lightPrimary/50 dark:stroke-darkPrimary/30',
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-700',
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-600',
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-600',
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
- if (props.toast.timeout === 'unlimited') return;
75
- else {
76
- setTimeout(() => {
77
- // resolve with undefined on auto-timeout
78
- toastStore.resolveToast(props.toast.id);
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>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "adminforth",
3
- "version": "3.10.0",
3
+ "version": "3.11.1",
4
4
  "description": "OpenSource Agent-Native forth-generation admin panel",
5
5
  "keywords": [
6
6
  "adminforth",