From c618d444a099d05d9a8ca368210ef16d0b2f8946 Mon Sep 17 00:00:00 2001 From: Anikait Sehwag Date: Mon, 7 Jul 2025 23:43:04 +0530 Subject: [PATCH] mgr/dashboard: Carbonised Notification Header Fixes: https://tracker.ceph.com/issues/71732 Signed-off-by: Anikait Sehwag --- .../app/core/navigation/navigation.module.ts | 19 +++-- .../navigation/navigation.component.ts | 2 +- .../header/notification-header.component.html | 27 +++++++ .../header/notification-header.component.scss | 56 +++++++++++++++ .../notification-header.component.spec.ts | 72 +++++++++++++++++++ .../header/notification-header.component.ts | 38 ++++++++++ .../notification-panel.component.html | 4 ++ .../notification-panel.component.scss | 18 +++++ .../notification-panel.component.spec.ts | 60 ++++++++++++++++ .../notification-panel.component.ts | 19 +++++ .../notifications.component.html | 20 ++++-- .../notifications.component.scss | 28 ++++++++ .../notifications/notifications.component.ts | 17 +++++ .../notifications-sidebar.component.spec.ts | 8 +-- .../notifications-sidebar.component.ts | 16 ++--- .../shared/services/notification.service.ts | 42 +++++++++-- 16 files changed, 410 insertions(+), 36 deletions(-) create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/navigation/notification-panel/header/notification-header.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/navigation/notification-panel/header/notification-header.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/navigation/notification-panel/header/notification-header.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/navigation/notification-panel/header/notification-header.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/navigation/notification-panel/notification-panel.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/navigation/notification-panel/notification-panel.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/navigation/notification-panel/notification-panel.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/navigation/notification-panel/notification-panel.component.ts diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation.module.ts b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation.module.ts index 0b6eaa8d531..a1d1cc41921 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation.module.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation.module.ts @@ -1,5 +1,5 @@ import { CommonModule } from '@angular/common'; -import { NgModule } from '@angular/core'; +import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; import { RouterModule } from '@angular/router'; import { NgbCollapseModule, NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap'; @@ -12,7 +12,9 @@ import { DialogModule, GridModule, BreadcrumbModule, - ModalModule + ModalModule, + ToggleModule, + ButtonModule } from 'carbon-components-angular'; import { AppRoutingModule } from '~/app/app-routing.module'; @@ -26,6 +28,8 @@ import { DashboardHelpComponent } from './dashboard-help/dashboard-help.componen import { IdentityComponent } from './identity/identity.component'; import { NavigationComponent } from './navigation/navigation.component'; import { NotificationsComponent } from './notifications/notifications.component'; +import { NotificationPanelComponent } from './notification-panel/notification-panel.component'; +import { NotificationHeaderComponent } from './notification-panel/header/notification-header.component'; // Icons import UserFilledIcon from '@carbon/icons/es/user--filled/20'; @@ -61,7 +65,9 @@ import { ModalCdsService } from '~/app/shared/services/modal-cds.service'; DialogModule, GridModule, BreadcrumbModule, - ModalModule + ModalModule, + ToggleModule, + ButtonModule ], declarations: [ AboutComponent, @@ -69,12 +75,15 @@ import { ModalCdsService } from '~/app/shared/services/modal-cds.service'; BreadcrumbsComponent, NavigationComponent, NotificationsComponent, + NotificationPanelComponent, + NotificationHeaderComponent, DashboardHelpComponent, AdministrationComponent, IdentityComponent ], - providers: [ModalCdsService], - exports: [NavigationComponent, BreadcrumbsComponent] + exports: [NavigationComponent, BreadcrumbsComponent], + schemas: [CUSTOM_ELEMENTS_SCHEMA], + providers: [ModalCdsService] }) export class NavigationModule { constructor(private iconService: IconService) { diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.ts index cb637e51f78..b276da84ff6 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.ts @@ -209,7 +209,7 @@ export class NavigationComponent implements OnInit, OnDestroy { ); } toggleSidebar() { - this.notificationService.toggleSidebar(); + this.notificationService.toggleSidebar(true, true); } trackByFn(item: any) { return item; diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/notification-panel/header/notification-header.component.html b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/notification-panel/header/notification-header.component.html new file mode 100644 index 00000000000..0038d3c8ae8 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/notification-panel/header/notification-header.component.html @@ -0,0 +1,27 @@ +
+
+
+ Tasks and Notifications +
+ + +
+ +
+ + +
+
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/notification-panel/header/notification-header.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/notification-panel/header/notification-header.component.scss new file mode 100644 index 00000000000..3540b6848f3 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/notification-panel/header/notification-header.component.scss @@ -0,0 +1,56 @@ +@use '@carbon/styles/scss/type'; +@use '@carbon/styles/scss/spacing'; +@use '@carbon/styles/scss/theme'; + +.notification-header { + display: flex; + flex-direction: column; + padding: spacing.$spacing-04; + border-bottom: 1px solid theme.$border-subtle-01; + background-color: theme.$layer-01; + + &__top { + display: flex; + justify-content: space-between; + align-items: center; + width: 100%; + margin-bottom: spacing.$spacing-03; + } + + &__title { + h4 { + @include type.type-style('heading-compact-01'); + + color: theme.$text-primary; + margin: 0; + } + } + + &__dismiss-btn { + color: theme.$text-primary; + + &:hover { + color: theme.$link-primary; + } + } + + &__toggle { + cds-toggle { + margin: 0; + + ::ng-deep { + .cds--toggle__label-text { + color: theme.$text-primary; + } + + .cds--toggle__label { + color: theme.$text-primary; + } + + .cds--toggle__text { + color: theme.$text-primary; + } + } + } + } +} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/notification-panel/header/notification-header.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/notification-panel/header/notification-header.component.spec.ts new file mode 100644 index 00000000000..444a6d11568 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/notification-panel/header/notification-header.component.spec.ts @@ -0,0 +1,72 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { NotificationHeaderComponent } from './notification-header.component'; +import { NotificationService } from '../../../../shared/services/notification.service'; +import { BehaviorSubject } from 'rxjs'; + +describe('NotificationHeaderComponent', () => { + let component: NotificationHeaderComponent; + let fixture: ComponentFixture; + let notificationService: NotificationService; + let muteStateSubject: BehaviorSubject; + + beforeEach(async () => { + muteStateSubject = new BehaviorSubject(false); + await TestBed.configureTestingModule({ + declarations: [NotificationHeaderComponent], + providers: [ + { + provide: NotificationService, + useValue: { + muteState$: muteStateSubject.asObservable(), + removeAll: jasmine.createSpy('removeAll'), + suspendToasties: jasmine.createSpy('suspendToasties') + } + } + ] + }).compileComponents(); + + fixture = TestBed.createComponent(NotificationHeaderComponent); + component = fixture.componentInstance; + notificationService = TestBed.inject(NotificationService); + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should initialize with default mute state', () => { + expect(component.isMuted).toBe(false); + }); + + it('should update mute state when subscription emits', () => { + muteStateSubject.next(true); + fixture.detectChanges(); + expect(component.isMuted).toBe(true); + }); + + it('should emit dismissAll event and call removeAll on dismiss', () => { + spyOn(component.dismissAll, 'emit'); + + component.onDismissAll(); + + expect(component.dismissAll.emit).toHaveBeenCalled(); + expect(notificationService.removeAll).toHaveBeenCalled(); + }); + + it('should toggle mute state', () => { + component.isMuted = false; + component.onToggleMute(); + expect(notificationService.suspendToasties).toHaveBeenCalledWith(true); + + component.isMuted = true; + component.onToggleMute(); + expect(notificationService.suspendToasties).toHaveBeenCalledWith(false); + }); + + it('should unsubscribe on destroy', () => { + spyOn(component['subs'], 'unsubscribe'); + component.ngOnDestroy(); + expect(component['subs'].unsubscribe).toHaveBeenCalled(); + }); +}); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/notification-panel/header/notification-header.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/notification-panel/header/notification-header.component.ts new file mode 100644 index 00000000000..df7698f6ce6 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/notification-panel/header/notification-header.component.ts @@ -0,0 +1,38 @@ +import { Component, Output, EventEmitter, OnInit, OnDestroy } from '@angular/core'; +import { NotificationService } from '../../../../shared/services/notification.service'; +import { Subscription } from 'rxjs'; + +@Component({ + selector: 'cd-notification-header', + templateUrl: './notification-header.component.html', + styleUrls: ['./notification-header.component.scss'] +}) +export class NotificationHeaderComponent implements OnInit, OnDestroy { + @Output() dismissAll = new EventEmitter(); + + isMuted = false; + private subs = new Subscription(); + + constructor(private notificationService: NotificationService) {} + + ngOnInit(): void { + this.subs.add( + this.notificationService.muteState$.subscribe((isMuted) => { + this.isMuted = isMuted; + }) + ); + } + + ngOnDestroy(): void { + this.subs.unsubscribe(); + } + + onDismissAll(): void { + this.dismissAll.emit(); + this.notificationService.removeAll(); + } + + onToggleMute(): void { + this.notificationService.suspendToasties(!this.isMuted); + } +} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/notification-panel/notification-panel.component.html b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/notification-panel/notification-panel.component.html new file mode 100644 index 00000000000..65577e9e07d --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/notification-panel/notification-panel.component.html @@ -0,0 +1,4 @@ +
+ + +
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/notification-panel/notification-panel.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/notification-panel/notification-panel.component.scss new file mode 100644 index 00000000000..b90127b4d29 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/notification-panel/notification-panel.component.scss @@ -0,0 +1,18 @@ +@use '@carbon/styles/scss/theme'; +@use '@carbon/styles/scss/spacing'; +@use '@carbon/styles/scss/themes'; +@use '@carbon/styles/scss/theme' as *; + +.notification-panel { + @include theme.theme(themes.$g10); + + position: absolute; + top: spacing.$spacing-09; + right: 0; + width: 400px; // Keep original width as it doesn't map to a spacing token + background-color: $layer-01; + box-shadow: $shadow; + border: 1px solid $border-subtle-01; + z-index: 6000; + color: $text-primary; +} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/notification-panel/notification-panel.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/notification-panel/notification-panel.component.spec.ts new file mode 100644 index 00000000000..e2df0faf7c4 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/notification-panel/notification-panel.component.spec.ts @@ -0,0 +1,60 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { NotificationPanelComponent } from './notification-panel.component'; +import { NotificationService } from '../../../shared/services/notification.service'; + +describe('NotificationPanelComponent', () => { + let component: NotificationPanelComponent; + let fixture: ComponentFixture; + let notificationService: NotificationService; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [NotificationPanelComponent], + providers: [ + { + provide: NotificationService, + useValue: { + toggleSidebar: jasmine.createSpy('toggleSidebar') + } + } + ] + }).compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(NotificationPanelComponent); + component = fixture.componentInstance; + notificationService = TestBed.inject(NotificationService); + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + describe('handleClickOutside', () => { + it('should close sidebar when clicking outside', () => { + // Create a click event outside the component + const outsideClickEvent = new MouseEvent('click', { + bubbles: true, + cancelable: true + }); + document.dispatchEvent(outsideClickEvent); + + expect(notificationService.toggleSidebar).toHaveBeenCalledWith(false, true); + }); + + it('should not close sidebar when clicking inside', () => { + // Create a click event inside the component + const insideClickEvent = new MouseEvent('click', { + bubbles: true, + cancelable: true + }); + + const componentElement = fixture.nativeElement; + componentElement.dispatchEvent(insideClickEvent); + + expect(notificationService.toggleSidebar).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/notification-panel/notification-panel.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/notification-panel/notification-panel.component.ts new file mode 100644 index 00000000000..b5cdbad4857 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/notification-panel/notification-panel.component.ts @@ -0,0 +1,19 @@ +import { Component, ElementRef, HostListener } from '@angular/core'; +import { NotificationService } from '../../../shared/services/notification.service'; + +@Component({ + selector: 'cd-notification-panel', + templateUrl: './notification-panel.component.html', + styleUrls: ['./notification-panel.component.scss'] +}) +export class NotificationPanelComponent { + constructor(public notificationService: NotificationService, private elementRef: ElementRef) {} + + @HostListener('document:click', ['$event']) + handleClickOutside(event: Event) { + const clickedInside = this.elementRef.nativeElement.contains(event.target); + if (!clickedInside) { + this.notificationService.toggleSidebar(false, true); + } + } +} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/notifications/notifications.component.html b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/notifications/notifications.component.html index f106e6647e3..ed6aecdcec0 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/notifications/notifications.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/notifications/notifications.component.html @@ -1,13 +1,19 @@ - - - + (click)="togglePanel($event)"> +
+ + + {{ notificationCount }} + +
Tasks and Notifications
+ + + diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/notifications/notifications.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/notifications/notifications.component.scss index 8bee3d8ff4e..999ea22523b 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/notifications/notifications.component.scss +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/notifications/notifications.component.scss @@ -1,4 +1,7 @@ @use './src/styles/vendor/variables' as vv; +@use '@carbon/styles/scss/spacing'; +@use '@carbon/styles/scss/theme' as *; +@use '@carbon/styles/scss/type'; .running i { color: vv.$primary; @@ -25,3 +28,28 @@ a { border-color: vv.$primary-500; } } + +.notification-icon-wrapper { + position: relative; + display: inline-flex; + padding: spacing.$spacing-04; + cursor: pointer; + border-radius: spacing.$spacing-01; + transition: background-color 0.2s ease; +} + +.notification-count { + position: absolute; + top: spacing.$spacing-01; + right: spacing.$spacing-01; + min-width: spacing.$spacing-04; + height: spacing.$spacing-04; + padding: 0 spacing.$spacing-01; + border-radius: spacing.$spacing-02; + background-color: $support-error; + color: $text-on-color; + font-size: spacing.$spacing-04; + line-height: spacing.$spacing-04; + text-align: center; + font-weight: 500; +} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/notifications/notifications.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/notifications/notifications.component.ts index b6e5fd41ab4..ca3b58167c7 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/notifications/notifications.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/notifications/notifications.component.ts @@ -16,6 +16,9 @@ export class NotificationsComponent implements OnInit, OnDestroy { icons = Icons; hasRunningTasks = false; hasNotifications = false; + isPanelOpen = false; + useNewPanel = true; + notificationCount = 0; private subs = new Subscription(); constructor( @@ -33,8 +36,22 @@ export class NotificationsComponent implements OnInit, OnDestroy { this.subs.add( this.notificationService.data$.subscribe((notifications: CdNotification[]) => { this.hasNotifications = notifications.length > 0; + this.notificationCount = notifications.length; }) ); + + this.subs.add( + this.notificationService.panelState$.subscribe((state) => { + this.isPanelOpen = state.isOpen; + this.useNewPanel = state.useNewPanel; + }) + ); + } + + togglePanel(event: Event) { + event.preventDefault(); + event.stopPropagation(); + this.notificationService.toggleSidebar(!this.isPanelOpen, this.useNewPanel); } ngOnDestroy(): void { diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/notifications-sidebar/notifications-sidebar.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/notifications-sidebar/notifications-sidebar.component.spec.ts index f3fe9cea3cf..627fefb4fe0 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/notifications-sidebar/notifications-sidebar.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/notifications-sidebar/notifications-sidebar.component.spec.ts @@ -180,14 +180,14 @@ describe('NotificationsSidebarComponent', () => { it('should always close if sidebarSubject value is true', fakeAsync(() => { // Closed before next value expect(component.isSidebarOpened).toBeFalsy(); - notificationService.sidebarSubject.next(true); + notificationService.toggleSidebar(true, true); tick(); expect(component.isSidebarOpened).toBeFalsy(); // Opened before next value component.isSidebarOpened = true; expect(component.isSidebarOpened).toBeTruthy(); - notificationService.sidebarSubject.next(true); + notificationService.toggleSidebar(true, true); tick(); expect(component.isSidebarOpened).toBeFalsy(); })); @@ -195,13 +195,13 @@ describe('NotificationsSidebarComponent', () => { it('should toggle sidebar visibility if sidebarSubject value is false', () => { // Closed before next value expect(component.isSidebarOpened).toBeFalsy(); - notificationService.sidebarSubject.next(false); + notificationService.toggleSidebar(true, false); expect(component.isSidebarOpened).toBeTruthy(); // Opened before next value component.isSidebarOpened = true; expect(component.isSidebarOpened).toBeTruthy(); - notificationService.sidebarSubject.next(false); + notificationService.toggleSidebar(false, false); expect(component.isSidebarOpened).toBeFalsy(); }); }); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/notifications-sidebar/notifications-sidebar.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/notifications-sidebar/notifications-sidebar.component.ts index a662a898b16..8393eb88949 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/notifications-sidebar/notifications-sidebar.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/notifications-sidebar/notifications-sidebar.component.ts @@ -102,17 +102,9 @@ export class NotificationsSidebarComponent implements OnInit, OnDestroy { ); this.subs.add( - this.notificationService.sidebarSubject.subscribe((forceClose) => { - if (forceClose) { - this.isSidebarOpened = false; - } else { - this.isSidebarOpened = !this.isSidebarOpened; - } - - window.clearTimeout(this.timeout); - this.timeout = window.setTimeout(() => { - this.cdRef.detectChanges(); - }, 0); + this.notificationService.panelState$.subscribe((state) => { + this.isSidebarOpened = state.isOpen && !state.useNewPanel; + this.cdRef.detectChanges(); }) ); @@ -167,7 +159,7 @@ export class NotificationsSidebarComponent implements OnInit, OnDestroy { } closeSidebar() { - this.isSidebarOpened = false; + this.notificationService.toggleSidebar(false, false); } trackByFn(index: number) { diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/services/notification.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/notification.service.ts index 52a06e30598..2c326a14666 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/services/notification.service.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/notification.service.ts @@ -2,7 +2,7 @@ import { Injectable } from '@angular/core'; import _ from 'lodash'; import { IndividualConfig, ToastrService } from 'ngx-toastr'; -import { BehaviorSubject, Subject } from 'rxjs'; +import { BehaviorSubject } from 'rxjs'; import { NotificationType } from '../enum/notification-type.enum'; import { CdNotification, CdNotificationConfig } from '../models/cd-notification'; @@ -18,14 +18,20 @@ export class NotificationService { // Data observable private dataSource = new BehaviorSubject([]); - data$ = this.dataSource.asObservable(); + private panelStateSource = new BehaviorSubject<{ isOpen: boolean; useNewPanel: boolean }>({ + isOpen: false, + useNewPanel: true + }); + private muteStateSource = new BehaviorSubject(false); - // Sidebar observable - sidebarSubject = new Subject(); + data$ = this.dataSource.asObservable(); + panelState$ = this.panelStateSource.asObservable(); + muteState$ = this.muteStateSource.asObservable(); private queued: CdNotificationConfig[] = []; private queuedTimeoutId: number; KEY = 'cdNotifications'; + MUTE_KEY = 'cdNotificationsMuted'; constructor( public toastr: ToastrService, @@ -45,6 +51,11 @@ export class NotificationService { } this.dataSource.next(notifications); + + // Load mute state from localStorage + const isMuted = localStorage.getItem(this.MUTE_KEY) === 'true'; + this.hideToasties = isMuted; + this.muteStateSource.next(isMuted); } /** @@ -171,7 +182,14 @@ export class NotificationService { if (this.hideToasties) { return; } - this.toastr[['error', 'info', 'success'][notification.type]]( + const toastrFn = + notification.type === NotificationType.error + ? this.toastr.error.bind(this.toastr) + : notification.type === NotificationType.info + ? this.toastr.info.bind(this.toastr) + : this.toastr.success.bind(this.toastr); + + toastrFn( (notification.message ? notification.message + '
' : '') + this.renderTimeAndApplicationHtml(notification), notification.title, @@ -229,9 +247,19 @@ export class NotificationService { */ suspendToasties(suspend: boolean) { this.hideToasties = suspend; + this.muteStateSource.next(suspend); + localStorage.setItem(this.MUTE_KEY, suspend.toString()); } - toggleSidebar(forceClose = false) { - this.sidebarSubject.next(forceClose); + /** + * Toggle the sidebar/panel visibility + * @param isOpen whether to open or close the panel + * @param useNewPanel which panel type to use + */ + toggleSidebar(isOpen: boolean, useNewPanel: boolean = true) { + this.panelStateSource.next({ + isOpen: isOpen, + useNewPanel: useNewPanel + }); } } -- 2.39.5