qdadm 1.5.0 → 1.6.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/package.json +1 -1
- package/src/components/layout/AppLayout.vue +39 -3
- package/src/components/layout/BaseLayout.vue +6 -0
- package/src/components/layout/defaults/DefaultFooter.vue +28 -12
- package/src/index.ts +6 -0
- package/src/kernel/Kernel.ts +34 -0
- package/src/kernel/KernelContext.ts +8 -0
- package/src/notifications/NotificationBadge.vue +31 -0
- package/src/notifications/NotificationListener.vue +71 -0
- package/src/notifications/NotificationModule.ts +76 -0
- package/src/notifications/NotificationPanel.vue +474 -0
- package/src/notifications/NotificationStore.ts +288 -0
- package/src/notifications/index.ts +42 -0
- package/src/notifications/styles.scss +27 -0
- package/src/toast/index.ts +1 -0
- package/src/toast/useSignalToast.ts +30 -15
package/package.json
CHANGED
|
@@ -23,6 +23,7 @@ import Button from 'primevue/button'
|
|
|
23
23
|
import Breadcrumb from 'primevue/breadcrumb'
|
|
24
24
|
import UnsavedChangesDialog from '../dialogs/UnsavedChangesDialog.vue'
|
|
25
25
|
import SidebarBox from './SidebarBox.vue'
|
|
26
|
+
import Zone from './Zone.vue'
|
|
26
27
|
import qdadmLogo from '../../assets/logo.svg'
|
|
27
28
|
import { version as qdadmVersion } from '../../../package.json'
|
|
28
29
|
|
|
@@ -204,10 +205,10 @@ const userSubtitle = computed<string>(() => {
|
|
|
204
205
|
return userData?.email || userData?.role || ''
|
|
205
206
|
})
|
|
206
207
|
|
|
207
|
-
function handleLogout(): void {
|
|
208
|
+
async function handleLogout(): Promise<void> {
|
|
208
209
|
logout()
|
|
210
|
+
await router.push({ name: 'login' })
|
|
209
211
|
signals?.emit('auth:logout', { reason: 'user' })
|
|
210
|
-
router.push({ name: 'login' })
|
|
211
212
|
}
|
|
212
213
|
|
|
213
214
|
/**
|
|
@@ -332,7 +333,11 @@ const showBreadcrumb = computed<boolean>(() => {
|
|
|
332
333
|
|
|
333
334
|
<SidebarBox v-if="features.poweredBy" id="powered-by">
|
|
334
335
|
<template #icon>
|
|
335
|
-
<
|
|
336
|
+
<div class="footer-logo-wrapper">
|
|
337
|
+
<img :src="qdadmLogo" alt="qdadm" />
|
|
338
|
+
<!-- Notification badge overlay on logo -->
|
|
339
|
+
<Zone name="_app:notification-badge" />
|
|
340
|
+
</div>
|
|
336
341
|
</template>
|
|
337
342
|
<template #subtitle-content>
|
|
338
343
|
<span class="sidebar-box-subtitle">
|
|
@@ -340,6 +345,9 @@ const showBreadcrumb = computed<boolean>(() => {
|
|
|
340
345
|
</span>
|
|
341
346
|
</template>
|
|
342
347
|
</SidebarBox>
|
|
348
|
+
|
|
349
|
+
<!-- Always-visible notification status (hidden when collapsed) -->
|
|
350
|
+
<Zone name="_app:notification-status" class="sidebar-notification-status" />
|
|
343
351
|
</aside>
|
|
344
352
|
|
|
345
353
|
<!-- Main content -->
|
|
@@ -394,6 +402,9 @@ const showBreadcrumb = computed<boolean>(() => {
|
|
|
394
402
|
</div>
|
|
395
403
|
</main>
|
|
396
404
|
|
|
405
|
+
<!-- Notification panel (rendered outside sidebar, follows sidebar position) -->
|
|
406
|
+
<Zone name="_app:notifications" />
|
|
407
|
+
|
|
397
408
|
<!-- Unsaved Changes Dialog (auto-rendered when a form registers guardDialog) -->
|
|
398
409
|
<UnsavedChangesDialog
|
|
399
410
|
v-if="guardDialog"
|
|
@@ -652,6 +663,19 @@ const showBreadcrumb = computed<boolean>(() => {
|
|
|
652
663
|
height: 100%;
|
|
653
664
|
}
|
|
654
665
|
|
|
666
|
+
.footer-logo-wrapper {
|
|
667
|
+
position: relative;
|
|
668
|
+
display: inline-flex;
|
|
669
|
+
width: 100%;
|
|
670
|
+
height: 100%;
|
|
671
|
+
align-items: center;
|
|
672
|
+
justify-content: center;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
.footer-logo-wrapper > :deep(.qdadm-zone) {
|
|
676
|
+
display: contents;
|
|
677
|
+
}
|
|
678
|
+
|
|
655
679
|
.powered-by-link {
|
|
656
680
|
color: var(--p-surface-300, #cbd5e1);
|
|
657
681
|
text-decoration: none;
|
|
@@ -988,3 +1012,15 @@ const showBreadcrumb = computed<boolean>(() => {
|
|
|
988
1012
|
}
|
|
989
1013
|
}
|
|
990
1014
|
</style>
|
|
1015
|
+
|
|
1016
|
+
<style>
|
|
1017
|
+
/* Non-scoped: logo alpha blink triggered by notification badge alert state */
|
|
1018
|
+
.footer-logo-wrapper:has(.notification-badge-zone--alert) {
|
|
1019
|
+
animation: logo-alpha-blink 1s ease-in-out infinite;
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
@keyframes logo-alpha-blink {
|
|
1023
|
+
0%, 100% { opacity: 1; filter: none; }
|
|
1024
|
+
50% { opacity: 0.4; filter: brightness(1.2) sepia(1) hue-rotate(-30deg) saturate(3); }
|
|
1025
|
+
}
|
|
1026
|
+
</style>
|
|
@@ -84,6 +84,9 @@ provide('qdadmNavlinksOverride', navlinksOverride)
|
|
|
84
84
|
:name="LAYOUT_ZONES.FOOTER"
|
|
85
85
|
:default-component="DefaultFooter"
|
|
86
86
|
/>
|
|
87
|
+
|
|
88
|
+
<!-- Always-visible notification status (hidden when collapsed) -->
|
|
89
|
+
<Zone name="_app:notification-status" class="sidebar-notification-status" />
|
|
87
90
|
</aside>
|
|
88
91
|
|
|
89
92
|
<!-- Main content area -->
|
|
@@ -113,6 +116,9 @@ provide('qdadmNavlinksOverride', navlinksOverride)
|
|
|
113
116
|
|
|
114
117
|
<!-- Toast is now provided by Kernel at root level -->
|
|
115
118
|
|
|
119
|
+
<!-- Notification panel (rendered outside sidebar, follows sidebar position) -->
|
|
120
|
+
<Zone name="_app:notifications" />
|
|
121
|
+
|
|
116
122
|
<!-- Confirm dialog (global) -->
|
|
117
123
|
<ConfirmDialog />
|
|
118
124
|
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* when no blocks are registered.
|
|
10
10
|
*/
|
|
11
11
|
import { inject } from 'vue'
|
|
12
|
+
import Zone from '../Zone.vue'
|
|
12
13
|
import qdadmLogo from '../../../assets/logo.svg'
|
|
13
14
|
import { version as qdadmVersion } from '../../../../package.json'
|
|
14
15
|
|
|
@@ -21,18 +22,24 @@ const features = inject<Features>('qdadmFeatures', { poweredBy: true })
|
|
|
21
22
|
</script>
|
|
22
23
|
|
|
23
24
|
<template>
|
|
24
|
-
<
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
25
|
+
<div class="default-footer-wrapper">
|
|
26
|
+
<a
|
|
27
|
+
v-if="features.poweredBy"
|
|
28
|
+
href="https://github.com/quazardous/qdadm"
|
|
29
|
+
target="_blank"
|
|
30
|
+
rel="noopener noreferrer"
|
|
31
|
+
class="default-footer"
|
|
32
|
+
>
|
|
33
|
+
<div class="footer-logo-wrapper">
|
|
34
|
+
<img :src="qdadmLogo" alt="qdadm" class="footer-logo" />
|
|
35
|
+
<!-- Notification badge overlay on logo -->
|
|
36
|
+
<Zone name="_app:notification-badge" />
|
|
37
|
+
</div>
|
|
38
|
+
<span class="footer-text">
|
|
39
|
+
powered by <strong>qdadm</strong> v{{ qdadmVersion }}
|
|
40
|
+
</span>
|
|
41
|
+
</a>
|
|
42
|
+
</div>
|
|
36
43
|
</template>
|
|
37
44
|
|
|
38
45
|
<style scoped>
|
|
@@ -52,6 +59,15 @@ const features = inject<Features>('qdadmFeatures', { poweredBy: true })
|
|
|
52
59
|
opacity: 1;
|
|
53
60
|
}
|
|
54
61
|
|
|
62
|
+
.default-footer-wrapper {
|
|
63
|
+
position: relative;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.footer-logo-wrapper {
|
|
67
|
+
position: relative;
|
|
68
|
+
flex-shrink: 0;
|
|
69
|
+
}
|
|
70
|
+
|
|
55
71
|
.footer-logo {
|
|
56
72
|
width: 1.25rem;
|
|
57
73
|
height: 1.25rem;
|
package/src/index.ts
CHANGED
|
@@ -22,6 +22,7 @@ export type {
|
|
|
22
22
|
SecurityConfig,
|
|
23
23
|
SSEConfig,
|
|
24
24
|
DebugBarConfig,
|
|
25
|
+
NotificationsConfig,
|
|
25
26
|
HomeRoute,
|
|
26
27
|
KernelOptions,
|
|
27
28
|
} from './kernel/Kernel'
|
|
@@ -387,6 +388,11 @@ export * from './utils/index'
|
|
|
387
388
|
// ════════════════════════════════════════════════════════════════════════════
|
|
388
389
|
export * from './toast/index'
|
|
389
390
|
|
|
391
|
+
// ════════════════════════════════════════════════════════════════════════════
|
|
392
|
+
// NOTIFICATIONS (optional notification panel)
|
|
393
|
+
// ════════════════════════════════════════════════════════════════════════════
|
|
394
|
+
export * from './notifications/index'
|
|
395
|
+
|
|
390
396
|
// ════════════════════════════════════════════════════════════════════════════
|
|
391
397
|
// DEBUG - NOT exported here to enable tree-shaking.
|
|
392
398
|
// Import from 'qdadm/debug' separately when needed:
|
package/src/kernel/Kernel.ts
CHANGED
|
@@ -66,6 +66,8 @@ import { StackHydrator } from '../chain/StackHydrator.js'
|
|
|
66
66
|
import type { EntityManager } from '../entity/EntityManager'
|
|
67
67
|
import type { RoleProvider } from '../security/RolesProvider'
|
|
68
68
|
import type { EntityAuthAdapter } from '../entity/auth/EntityAuthAdapter'
|
|
69
|
+
import { NotificationModule } from '../notifications/NotificationModule'
|
|
70
|
+
import { createNotificationStore, NOTIFICATION_KEY, type NotificationStore } from '../notifications/NotificationStore'
|
|
69
71
|
|
|
70
72
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
71
73
|
// Types
|
|
@@ -178,6 +180,14 @@ export interface DebugBarConfig {
|
|
|
178
180
|
[key: string]: unknown
|
|
179
181
|
}
|
|
180
182
|
|
|
183
|
+
/**
|
|
184
|
+
* Notifications configuration
|
|
185
|
+
*/
|
|
186
|
+
export interface NotificationsConfig {
|
|
187
|
+
enabled?: boolean
|
|
188
|
+
maxNotifications?: number
|
|
189
|
+
}
|
|
190
|
+
|
|
181
191
|
/**
|
|
182
192
|
* Home route configuration
|
|
183
193
|
*/
|
|
@@ -215,6 +225,7 @@ export interface KernelOptions {
|
|
|
215
225
|
eventRouter?: RoutesConfig
|
|
216
226
|
sse?: SSEConfig
|
|
217
227
|
debugBar?: DebugBarConfig
|
|
228
|
+
notifications?: NotificationsConfig
|
|
218
229
|
toast?: Record<string, unknown>
|
|
219
230
|
debug?: boolean
|
|
220
231
|
onAuthExpired?: (payload: unknown) => void
|
|
@@ -282,6 +293,7 @@ export class Kernel {
|
|
|
282
293
|
moduleLoader: ModuleLoader | null = null
|
|
283
294
|
activeStack: ActiveStack | null = null
|
|
284
295
|
stackHydrator: StackHydrator | null = null
|
|
296
|
+
notificationStore: NotificationStore | null = null
|
|
285
297
|
|
|
286
298
|
/** Pending provides from modules (applied after vueApp creation) */
|
|
287
299
|
_pendingProvides: Map<string | symbol, unknown> = new Map()
|
|
@@ -322,6 +334,13 @@ export class Kernel {
|
|
|
322
334
|
}
|
|
323
335
|
}
|
|
324
336
|
|
|
337
|
+
// Auto-inject NotificationModule if notifications config is provided
|
|
338
|
+
if (options.notifications?.enabled) {
|
|
339
|
+
const notificationModule = new NotificationModule()
|
|
340
|
+
options.moduleDefs = options.moduleDefs || []
|
|
341
|
+
options.moduleDefs.push(notificationModule)
|
|
342
|
+
}
|
|
343
|
+
|
|
325
344
|
this.options = options
|
|
326
345
|
}
|
|
327
346
|
|
|
@@ -1262,6 +1281,14 @@ export class Kernel {
|
|
|
1262
1281
|
app.provide('qdadmPermissionRegistry', this.permissionRegistry)
|
|
1263
1282
|
}
|
|
1264
1283
|
|
|
1284
|
+
// Create and provide notification store if notifications are enabled
|
|
1285
|
+
if (this.options.notifications?.enabled) {
|
|
1286
|
+
this.notificationStore = createNotificationStore({
|
|
1287
|
+
maxNotifications: this.options.notifications.maxNotifications,
|
|
1288
|
+
})
|
|
1289
|
+
app.provide(NOTIFICATION_KEY, this.notificationStore)
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1265
1292
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1266
1293
|
const qdadmOptions: any = {
|
|
1267
1294
|
orchestrator: this.orchestrator,
|
|
@@ -1349,6 +1376,13 @@ export class Kernel {
|
|
|
1349
1376
|
return this.sseBridge
|
|
1350
1377
|
}
|
|
1351
1378
|
|
|
1379
|
+
/**
|
|
1380
|
+
* Get the NotificationStore instance
|
|
1381
|
+
*/
|
|
1382
|
+
getNotificationStore(): NotificationStore | null {
|
|
1383
|
+
return this.notificationStore
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1352
1386
|
/**
|
|
1353
1387
|
* Shorthand accessor for SSE bridge
|
|
1354
1388
|
*/
|
|
@@ -119,6 +119,14 @@ export interface BlockConfig {
|
|
|
119
119
|
props?: Record<string, unknown>
|
|
120
120
|
id?: string
|
|
121
121
|
operation?: 'add' | 'replace' | 'extend' | 'wrap'
|
|
122
|
+
/** Block ID to replace (required if operation='replace') */
|
|
123
|
+
replaces?: string
|
|
124
|
+
/** Block ID to insert before (for operation='extend') */
|
|
125
|
+
before?: string
|
|
126
|
+
/** Block ID to insert after (for operation='extend') */
|
|
127
|
+
after?: string
|
|
128
|
+
/** Block ID to wrap (required if operation='wrap') */
|
|
129
|
+
wraps?: string
|
|
122
130
|
}
|
|
123
131
|
|
|
124
132
|
/**
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* NotificationBadge - Clickable overlay for the sidebar footer logo
|
|
4
|
+
*
|
|
5
|
+
* Always rendered as a transparent click zone over the logo.
|
|
6
|
+
* When there are alerts, the parent layout applies an opacity blink
|
|
7
|
+
* on the logo wrapper via :has(.notification-badge-zone--alert).
|
|
8
|
+
*
|
|
9
|
+
* Click toggles the notification panel open/close.
|
|
10
|
+
*/
|
|
11
|
+
import { useNotifications } from './NotificationStore'
|
|
12
|
+
|
|
13
|
+
const store = useNotifications()
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<template>
|
|
17
|
+
<div
|
|
18
|
+
class="notification-badge-zone"
|
|
19
|
+
:class="{ 'notification-badge-zone--alert': store.hasAlert.value || store.unreadCount.value > 0 }"
|
|
20
|
+
@click.stop.prevent="store.toggle()"
|
|
21
|
+
/>
|
|
22
|
+
</template>
|
|
23
|
+
|
|
24
|
+
<style scoped>
|
|
25
|
+
.notification-badge-zone {
|
|
26
|
+
position: absolute;
|
|
27
|
+
inset: 0;
|
|
28
|
+
cursor: pointer;
|
|
29
|
+
z-index: 1;
|
|
30
|
+
}
|
|
31
|
+
</style>
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* NotificationListener - Captures toast signals to notification store
|
|
4
|
+
*
|
|
5
|
+
* Replaces ToastListener when NotificationModule is active.
|
|
6
|
+
* Intercepts toast:* signals and:
|
|
7
|
+
* - Always captures to NotificationStore
|
|
8
|
+
* - If data.forceToast === true, also shows a classic PrimeVue toast
|
|
9
|
+
*/
|
|
10
|
+
import { onMounted, onUnmounted, inject } from 'vue'
|
|
11
|
+
import { useToast } from 'primevue/usetoast'
|
|
12
|
+
import type { SignalBus } from '../kernel/SignalBus'
|
|
13
|
+
import { useNotifications } from './NotificationStore'
|
|
14
|
+
import type { NotificationSeverity } from './NotificationStore'
|
|
15
|
+
|
|
16
|
+
interface ToastEventData {
|
|
17
|
+
summary?: string
|
|
18
|
+
detail?: string
|
|
19
|
+
life?: number
|
|
20
|
+
emitter?: string
|
|
21
|
+
forceToast?: boolean
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const toast = useToast()
|
|
25
|
+
const signals = inject<SignalBus | null>('qdadmSignals', null)
|
|
26
|
+
const store = useNotifications()
|
|
27
|
+
|
|
28
|
+
let unsubscribe: (() => void) | null = null
|
|
29
|
+
|
|
30
|
+
onMounted(() => {
|
|
31
|
+
if (!signals) {
|
|
32
|
+
console.warn('[NotificationListener] No signals bus injected')
|
|
33
|
+
return
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
unsubscribe = signals.on('toast:*', (event) => {
|
|
37
|
+
const data = event.data as ToastEventData | undefined
|
|
38
|
+
const severity = event.name.split(':')[1] as NotificationSeverity
|
|
39
|
+
|
|
40
|
+
// Always capture to notification store
|
|
41
|
+
store.addNotification({
|
|
42
|
+
severity,
|
|
43
|
+
summary: data?.summary || '',
|
|
44
|
+
detail: data?.detail,
|
|
45
|
+
emitter: data?.emitter,
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
// If forceToast is set, also show classic PrimeVue toast
|
|
49
|
+
if (data?.forceToast) {
|
|
50
|
+
toast.add({
|
|
51
|
+
severity,
|
|
52
|
+
summary: data?.summary,
|
|
53
|
+
detail: data?.detail,
|
|
54
|
+
life: data?.life ?? 3000,
|
|
55
|
+
})
|
|
56
|
+
}
|
|
57
|
+
})
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
onUnmounted(() => {
|
|
61
|
+
if (unsubscribe) {
|
|
62
|
+
unsubscribe()
|
|
63
|
+
unsubscribe = null
|
|
64
|
+
}
|
|
65
|
+
})
|
|
66
|
+
</script>
|
|
67
|
+
|
|
68
|
+
<template>
|
|
69
|
+
<!-- Invisible listener component -->
|
|
70
|
+
<span style="display: none" />
|
|
71
|
+
</template>
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NotificationModule - Optional notification panel system
|
|
3
|
+
*
|
|
4
|
+
* When loaded, this module:
|
|
5
|
+
* - Replaces the default ToastListener with NotificationListener
|
|
6
|
+
* (captures toasts to notification store instead of ephemeral PrimeVue toasts)
|
|
7
|
+
* - Registers NotificationBadge for the sidebar footer logo overlay
|
|
8
|
+
* - Registers NotificationPanel zone for the panel component
|
|
9
|
+
*
|
|
10
|
+
* Without this module, classic toast behavior is unchanged (ToastBridgeModule).
|
|
11
|
+
* With this module, toasts are captured. Use `forceToast: true` in signal data
|
|
12
|
+
* to also show a classic PrimeVue toast.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* // In kernel config
|
|
16
|
+
* const kernel = new Kernel({
|
|
17
|
+
* notifications: { enabled: true, maxNotifications: 100 }
|
|
18
|
+
* })
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { Module } from '../kernel/Module'
|
|
22
|
+
import type { KernelContext } from '../kernel/KernelContext'
|
|
23
|
+
import { TOAST_ZONE } from '../toast/ToastBridgeModule'
|
|
24
|
+
import NotificationListener from './NotificationListener.vue'
|
|
25
|
+
import NotificationBadge from './NotificationBadge.vue'
|
|
26
|
+
import NotificationPanel from './NotificationPanel.vue'
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Zone names for notification components
|
|
30
|
+
*/
|
|
31
|
+
export const NOTIFICATION_ZONE = '_app:notifications'
|
|
32
|
+
export const NOTIFICATION_BADGE_ZONE = '_app:notification-badge'
|
|
33
|
+
export const NOTIFICATION_STATUS_ZONE = '_app:notification-status'
|
|
34
|
+
|
|
35
|
+
export class NotificationModule extends Module {
|
|
36
|
+
static override moduleName = 'notifications'
|
|
37
|
+
static override requires: string[] = []
|
|
38
|
+
static override priority = 5 // Before toast-bridge (10) to intercept first
|
|
39
|
+
|
|
40
|
+
static override styles = () => import('./styles.scss')
|
|
41
|
+
|
|
42
|
+
async connect(ctx: KernelContext): Promise<void> {
|
|
43
|
+
// Define notification zones
|
|
44
|
+
ctx.zone(NOTIFICATION_ZONE)
|
|
45
|
+
ctx.zone(NOTIFICATION_BADGE_ZONE)
|
|
46
|
+
ctx.zone(NOTIFICATION_STATUS_ZONE)
|
|
47
|
+
|
|
48
|
+
// Replace the toast-listener block in the toast zone with our NotificationListener.
|
|
49
|
+
// This intercepts toast signals and captures them to the notification store.
|
|
50
|
+
// The toast zone is defined by ToastBridgeModule - we replace its listener block.
|
|
51
|
+
ctx.zone(TOAST_ZONE) // Ensure the zone exists
|
|
52
|
+
ctx.block(TOAST_ZONE, {
|
|
53
|
+
id: 'toast-listener',
|
|
54
|
+
component: NotificationListener,
|
|
55
|
+
weight: 0,
|
|
56
|
+
operation: 'replace',
|
|
57
|
+
replaces: 'toast-listener',
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
// Register badge component for sidebar footer overlay
|
|
61
|
+
ctx.block(NOTIFICATION_BADGE_ZONE, {
|
|
62
|
+
id: 'notification-badge',
|
|
63
|
+
component: NotificationBadge,
|
|
64
|
+
weight: 0,
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
// Register panel component
|
|
68
|
+
ctx.block(NOTIFICATION_ZONE, {
|
|
69
|
+
id: 'notification-panel',
|
|
70
|
+
component: NotificationPanel,
|
|
71
|
+
weight: 0,
|
|
72
|
+
})
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export default NotificationModule
|