mirror of
				https://github.com/paperless-ngx/paperless-ngx.git
				synced 2025-10-30 03:56:23 -05:00 
			
		
		
		
	Very annoying refactor
This commit is contained in:
		
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,4 +1,4 @@ | ||||
| <pngx-toasts></pngx-toasts> | ||||
| <pngx-notification-list></pngx-notification-list> | ||||
|  | ||||
| <pngx-file-drop> | ||||
|   <ng-container content> | ||||
|   | ||||
| @@ -14,14 +14,17 @@ import { TourNgBootstrapModule, TourService } from 'ngx-ui-tour-ng-bootstrap' | ||||
| import { Subject } from 'rxjs' | ||||
| import { routes } from './app-routing.module' | ||||
| import { AppComponent } from './app.component' | ||||
| import { ToastsComponent } from './components/common/toasts/toasts.component' | ||||
| import { NotificationListComponent } from './components/common/notification-list/notification-list.component' | ||||
| import { FileDropComponent } from './components/file-drop/file-drop.component' | ||||
| import { DirtySavedViewGuard } from './guards/dirty-saved-view.guard' | ||||
| import { PermissionsGuard } from './guards/permissions.guard' | ||||
| import { HotKeyService } from './services/hot-key.service' | ||||
| import { | ||||
|   Notification, | ||||
|   NotificationService, | ||||
| } from './services/notification.service' | ||||
| import { PermissionsService } from './services/permissions.service' | ||||
| import { SettingsService } from './services/settings.service' | ||||
| import { Toast, ToastService } from './services/toast.service' | ||||
| import { | ||||
|   FileStatus, | ||||
|   WebsocketStatusService, | ||||
| @@ -33,7 +36,7 @@ describe('AppComponent', () => { | ||||
|   let tourService: TourService | ||||
|   let websocketStatusService: WebsocketStatusService | ||||
|   let permissionsService: PermissionsService | ||||
|   let toastService: ToastService | ||||
|   let notificationService: NotificationService | ||||
|   let router: Router | ||||
|   let settingsService: SettingsService | ||||
|   let hotKeyService: HotKeyService | ||||
| @@ -46,7 +49,7 @@ describe('AppComponent', () => { | ||||
|         NgxFileDropModule, | ||||
|         NgbModalModule, | ||||
|         AppComponent, | ||||
|         ToastsComponent, | ||||
|         NotificationListComponent, | ||||
|         FileDropComponent, | ||||
|         NgxBootstrapIconsModule.pick(allIcons), | ||||
|       ], | ||||
| @@ -62,7 +65,7 @@ describe('AppComponent', () => { | ||||
|     websocketStatusService = TestBed.inject(WebsocketStatusService) | ||||
|     permissionsService = TestBed.inject(PermissionsService) | ||||
|     settingsService = TestBed.inject(SettingsService) | ||||
|     toastService = TestBed.inject(ToastService) | ||||
|     notificationService = TestBed.inject(NotificationService) | ||||
|     router = TestBed.inject(Router) | ||||
|     hotKeyService = TestBed.inject(HotKeyService) | ||||
|     fixture = TestBed.createComponent(AppComponent) | ||||
| @@ -82,12 +85,14 @@ describe('AppComponent', () => { | ||||
|     expect(document.body.classList).not.toContain('tour-active') | ||||
|   })) | ||||
|  | ||||
|   it('should display toast on document consumed with link if user has access', () => { | ||||
|   it('should display notification on document consumed with link if user has access', () => { | ||||
|     const navigateSpy = jest.spyOn(router, 'navigate') | ||||
|     jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true) | ||||
|     let toast: Toast | ||||
|     toastService.getToasts().subscribe((toasts) => (toast = toasts[0])) | ||||
|     const toastSpy = jest.spyOn(toastService, 'show') | ||||
|     let notification: Notification | ||||
|     notificationService | ||||
|       .getNotifications() | ||||
|       .subscribe((notifications) => (notification = notifications[0])) | ||||
|     const notificationSpy = jest.spyOn(notificationService, 'show') | ||||
|     const fileStatusSubject = new Subject<FileStatus>() | ||||
|     jest | ||||
|       .spyOn(websocketStatusService, 'onDocumentConsumptionFinished') | ||||
| @@ -96,63 +101,65 @@ describe('AppComponent', () => { | ||||
|     const status = new FileStatus() | ||||
|     status.documentId = 1 | ||||
|     fileStatusSubject.next(status) | ||||
|     expect(toastSpy).toHaveBeenCalled() | ||||
|     expect(toast.action).not.toBeUndefined() | ||||
|     toast.action() | ||||
|     expect(notificationSpy).toHaveBeenCalled() | ||||
|     expect(notification.action).not.toBeUndefined() | ||||
|     notification.action() | ||||
|     expect(navigateSpy).toHaveBeenCalledWith(['documents', status.documentId]) | ||||
|   }) | ||||
|  | ||||
|   it('should display toast on document consumed without link if user does not have access', () => { | ||||
|   it('should display notification on document consumed without link if user does not have access', () => { | ||||
|     jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(false) | ||||
|     let toast: Toast | ||||
|     toastService.getToasts().subscribe((toasts) => (toast = toasts[0])) | ||||
|     const toastSpy = jest.spyOn(toastService, 'show') | ||||
|     let notification: Notification | ||||
|     notificationService | ||||
|       .getNotifications() | ||||
|       .subscribe((notifications) => (notification = notifications[0])) | ||||
|     const notificationSpy = jest.spyOn(notificationService, 'show') | ||||
|     const fileStatusSubject = new Subject<FileStatus>() | ||||
|     jest | ||||
|       .spyOn(websocketStatusService, 'onDocumentConsumptionFinished') | ||||
|       .mockReturnValue(fileStatusSubject) | ||||
|     component.ngOnInit() | ||||
|     fileStatusSubject.next(new FileStatus()) | ||||
|     expect(toastSpy).toHaveBeenCalled() | ||||
|     expect(toast.action).toBeUndefined() | ||||
|     expect(notificationSpy).toHaveBeenCalled() | ||||
|     expect(notification.action).toBeUndefined() | ||||
|   }) | ||||
|  | ||||
|   it('should display toast on document added', () => { | ||||
|   it('should display notification on document added', () => { | ||||
|     jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true) | ||||
|     const toastSpy = jest.spyOn(toastService, 'show') | ||||
|     const notificationSpy = jest.spyOn(notificationService, 'show') | ||||
|     const fileStatusSubject = new Subject<FileStatus>() | ||||
|     jest | ||||
|       .spyOn(websocketStatusService, 'onDocumentDetected') | ||||
|       .mockReturnValue(fileStatusSubject) | ||||
|     component.ngOnInit() | ||||
|     fileStatusSubject.next(new FileStatus()) | ||||
|     expect(toastSpy).toHaveBeenCalled() | ||||
|     expect(notificationSpy).toHaveBeenCalled() | ||||
|   }) | ||||
|  | ||||
|   it('should suppress dashboard notifications if set', () => { | ||||
|     jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true) | ||||
|     jest.spyOn(settingsService, 'get').mockReturnValue(true) | ||||
|     jest.spyOn(router, 'url', 'get').mockReturnValue('/dashboard') | ||||
|     const toastSpy = jest.spyOn(toastService, 'show') | ||||
|     const notificationSpy = jest.spyOn(notificationService, 'show') | ||||
|     const fileStatusSubject = new Subject<FileStatus>() | ||||
|     jest | ||||
|       .spyOn(websocketStatusService, 'onDocumentDetected') | ||||
|       .mockReturnValue(fileStatusSubject) | ||||
|     component.ngOnInit() | ||||
|     fileStatusSubject.next(new FileStatus()) | ||||
|     expect(toastSpy).not.toHaveBeenCalled() | ||||
|     expect(notificationSpy).not.toHaveBeenCalled() | ||||
|   }) | ||||
|  | ||||
|   it('should display toast on document failed', () => { | ||||
|   it('should display notification on document failed', () => { | ||||
|     jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true) | ||||
|     const toastSpy = jest.spyOn(toastService, 'showError') | ||||
|     const notificationSpy = jest.spyOn(notificationService, 'showError') | ||||
|     const fileStatusSubject = new Subject<FileStatus>() | ||||
|     jest | ||||
|       .spyOn(websocketStatusService, 'onDocumentConsumptionFailed') | ||||
|       .mockReturnValue(fileStatusSubject) | ||||
|     component.ngOnInit() | ||||
|     fileStatusSubject.next(new FileStatus()) | ||||
|     expect(toastSpy).toHaveBeenCalled() | ||||
|     expect(notificationSpy).toHaveBeenCalled() | ||||
|   }) | ||||
|  | ||||
|   it('should support hotkeys', () => { | ||||
|   | ||||
| @@ -2,11 +2,12 @@ import { Component, OnDestroy, OnInit, Renderer2 } from '@angular/core' | ||||
| import { Router, RouterOutlet } from '@angular/router' | ||||
| import { TourNgBootstrapModule, TourService } from 'ngx-ui-tour-ng-bootstrap' | ||||
| import { first, Subscription } from 'rxjs' | ||||
| import { ToastsComponent } from './components/common/toasts/toasts.component' | ||||
| import { NotificationListComponent } from './components/common/notification-list/notification-list.component' | ||||
| import { FileDropComponent } from './components/file-drop/file-drop.component' | ||||
| import { SETTINGS_KEYS } from './data/ui-settings' | ||||
| import { ComponentRouterService } from './services/component-router.service' | ||||
| import { HotKeyService } from './services/hot-key.service' | ||||
| import { NotificationService } from './services/notification.service' | ||||
| import { | ||||
|   PermissionAction, | ||||
|   PermissionsService, | ||||
| @@ -14,7 +15,6 @@ import { | ||||
| } from './services/permissions.service' | ||||
| import { SettingsService } from './services/settings.service' | ||||
| import { TasksService } from './services/tasks.service' | ||||
| import { ToastService } from './services/toast.service' | ||||
| import { WebsocketStatusService } from './services/websocket-status.service' | ||||
|  | ||||
| @Component({ | ||||
| @@ -23,7 +23,7 @@ import { WebsocketStatusService } from './services/websocket-status.service' | ||||
|   styleUrls: ['./app.component.scss'], | ||||
|   imports: [ | ||||
|     FileDropComponent, | ||||
|     ToastsComponent, | ||||
|     NotificationListComponent, | ||||
|     TourNgBootstrapModule, | ||||
|     RouterOutlet, | ||||
|   ], | ||||
| @@ -36,7 +36,7 @@ export class AppComponent implements OnInit, OnDestroy { | ||||
|   constructor( | ||||
|     private settings: SettingsService, | ||||
|     private websocketStatusService: WebsocketStatusService, | ||||
|     private toastService: ToastService, | ||||
|     private notificationService: NotificationService, | ||||
|     private router: Router, | ||||
|     private tasksService: TasksService, | ||||
|     public tourService: TourService, | ||||
| @@ -91,7 +91,7 @@ export class AppComponent implements OnInit, OnDestroy { | ||||
|               PermissionType.Document | ||||
|             ) | ||||
|           ) { | ||||
|             this.toastService.show({ | ||||
|             this.notificationService.show({ | ||||
|               content: $localize`Document ${status.filename} was added to Paperless-ngx.`, | ||||
|               delay: 10000, | ||||
|               actionName: $localize`Open document`, | ||||
| @@ -100,7 +100,7 @@ export class AppComponent implements OnInit, OnDestroy { | ||||
|               }, | ||||
|             }) | ||||
|           } else { | ||||
|             this.toastService.show({ | ||||
|             this.notificationService.show({ | ||||
|               content: $localize`Document ${status.filename} was added to Paperless-ngx.`, | ||||
|               delay: 10000, | ||||
|             }) | ||||
| @@ -115,7 +115,7 @@ export class AppComponent implements OnInit, OnDestroy { | ||||
|         if ( | ||||
|           this.showNotification(SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_FAILED) | ||||
|         ) { | ||||
|           this.toastService.showError( | ||||
|           this.notificationService.showError( | ||||
|             $localize`Could not add ${status.filename}\: ${status.message}` | ||||
|           ) | ||||
|         } | ||||
| @@ -130,7 +130,7 @@ export class AppComponent implements OnInit, OnDestroy { | ||||
|             SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_NEW_DOCUMENT | ||||
|           ) | ||||
|         ) { | ||||
|           this.toastService.show({ | ||||
|           this.notificationService.show({ | ||||
|             content: $localize`Document ${status.filename} is being processed by Paperless-ngx.`, | ||||
|             delay: 5000, | ||||
|           }) | ||||
|   | ||||
| @@ -10,8 +10,8 @@ import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons' | ||||
| import { of, throwError } from 'rxjs' | ||||
| import { OutputTypeConfig } from 'src/app/data/paperless-config' | ||||
| import { ConfigService } from 'src/app/services/config.service' | ||||
| import { NotificationService } from 'src/app/services/notification.service' | ||||
| import { SettingsService } from 'src/app/services/settings.service' | ||||
| import { ToastService } from 'src/app/services/toast.service' | ||||
| import { FileComponent } from '../../common/input/file/file.component' | ||||
| import { NumberComponent } from '../../common/input/number/number.component' | ||||
| import { SelectComponent } from '../../common/input/select/select.component' | ||||
| @@ -24,7 +24,7 @@ describe('ConfigComponent', () => { | ||||
|   let component: ConfigComponent | ||||
|   let fixture: ComponentFixture<ConfigComponent> | ||||
|   let configService: ConfigService | ||||
|   let toastService: ToastService | ||||
|   let notificationService: NotificationService | ||||
|   let settingService: SettingsService | ||||
|  | ||||
|   beforeEach(async () => { | ||||
| @@ -51,7 +51,7 @@ describe('ConfigComponent', () => { | ||||
|     }).compileComponents() | ||||
|  | ||||
|     configService = TestBed.inject(ConfigService) | ||||
|     toastService = TestBed.inject(ToastService) | ||||
|     notificationService = TestBed.inject(NotificationService) | ||||
|     settingService = TestBed.inject(SettingsService) | ||||
|     fixture = TestBed.createComponent(ConfigComponent) | ||||
|     component = fixture.componentInstance | ||||
| @@ -60,7 +60,7 @@ describe('ConfigComponent', () => { | ||||
|  | ||||
|   it('should load config on init, show error if necessary', () => { | ||||
|     const getSpy = jest.spyOn(configService, 'getConfig') | ||||
|     const errorSpy = jest.spyOn(toastService, 'showError') | ||||
|     const errorSpy = jest.spyOn(notificationService, 'showError') | ||||
|     getSpy.mockReturnValueOnce( | ||||
|       throwError(() => new Error('Error getting config')) | ||||
|     ) | ||||
| @@ -78,7 +78,7 @@ describe('ConfigComponent', () => { | ||||
|  | ||||
|   it('should save config, show error if necessary', () => { | ||||
|     const saveSpy = jest.spyOn(configService, 'saveConfig') | ||||
|     const errorSpy = jest.spyOn(toastService, 'showError') | ||||
|     const errorSpy = jest.spyOn(notificationService, 'showError') | ||||
|     saveSpy.mockReturnValueOnce( | ||||
|       throwError(() => new Error('Error saving config')) | ||||
|     ) | ||||
| @@ -112,7 +112,7 @@ describe('ConfigComponent', () => { | ||||
|  | ||||
|   it('should upload file, show error if necessary', () => { | ||||
|     const uploadSpy = jest.spyOn(configService, 'uploadFile') | ||||
|     const errorSpy = jest.spyOn(toastService, 'showError') | ||||
|     const errorSpy = jest.spyOn(notificationService, 'showError') | ||||
|     uploadSpy.mockReturnValueOnce( | ||||
|       throwError(() => new Error('Error uploading file')) | ||||
|     ) | ||||
|   | ||||
| @@ -25,8 +25,8 @@ import { | ||||
|   PaperlessConfigOptions, | ||||
| } from 'src/app/data/paperless-config' | ||||
| import { ConfigService } from 'src/app/services/config.service' | ||||
| import { NotificationService } from 'src/app/services/notification.service' | ||||
| import { SettingsService } from 'src/app/services/settings.service' | ||||
| import { ToastService } from 'src/app/services/toast.service' | ||||
| import { FileComponent } from '../../common/input/file/file.component' | ||||
| import { NumberComponent } from '../../common/input/number/number.component' | ||||
| import { SelectComponent } from '../../common/input/select/select.component' | ||||
| @@ -79,7 +79,7 @@ export class ConfigComponent | ||||
|  | ||||
|   constructor( | ||||
|     private configService: ConfigService, | ||||
|     private toastService: ToastService, | ||||
|     private notificationService: NotificationService, | ||||
|     private settingsService: SettingsService | ||||
|   ) { | ||||
|     super() | ||||
| @@ -100,7 +100,10 @@ export class ConfigComponent | ||||
|         }, | ||||
|         error: (e) => { | ||||
|           this.loading = false | ||||
|           this.toastService.showError($localize`Error retrieving config`, e) | ||||
|           this.notificationService.showError( | ||||
|             $localize`Error retrieving config`, | ||||
|             e | ||||
|           ) | ||||
|         }, | ||||
|       }) | ||||
|  | ||||
| @@ -170,11 +173,11 @@ export class ConfigComponent | ||||
|           this.initialize(config) | ||||
|           this.store.next(config) | ||||
|           this.settingsService.initializeSettings().subscribe() | ||||
|           this.toastService.showInfo($localize`Configuration updated`) | ||||
|           this.notificationService.showInfo($localize`Configuration updated`) | ||||
|         }, | ||||
|         error: (e) => { | ||||
|           this.loading = false | ||||
|           this.toastService.showError( | ||||
|           this.notificationService.showError( | ||||
|             $localize`An error occurred updating configuration`, | ||||
|             e | ||||
|           ) | ||||
| @@ -197,11 +200,13 @@ export class ConfigComponent | ||||
|           this.initialize(config) | ||||
|           this.store.next(config) | ||||
|           this.settingsService.initializeSettings().subscribe() | ||||
|           this.toastService.showInfo($localize`File successfully updated`) | ||||
|           this.notificationService.showInfo( | ||||
|             $localize`File successfully updated` | ||||
|           ) | ||||
|         }, | ||||
|         error: (e) => { | ||||
|           this.loading = false | ||||
|           this.toastService.showError( | ||||
|           this.notificationService.showError( | ||||
|             $localize`An error occurred uploading file`, | ||||
|             e | ||||
|           ) | ||||
|   | ||||
| @@ -29,12 +29,15 @@ import { IfPermissionsDirective } from 'src/app/directives/if-permissions.direct | ||||
| import { PermissionsGuard } from 'src/app/guards/permissions.guard' | ||||
| import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe' | ||||
| import { SafeHtmlPipe } from 'src/app/pipes/safehtml.pipe' | ||||
| import { | ||||
|   Notification, | ||||
|   NotificationService, | ||||
| } from 'src/app/services/notification.service' | ||||
| import { PermissionsService } from 'src/app/services/permissions.service' | ||||
| import { GroupService } from 'src/app/services/rest/group.service' | ||||
| import { UserService } from 'src/app/services/rest/user.service' | ||||
| import { SettingsService } from 'src/app/services/settings.service' | ||||
| import { SystemStatusService } from 'src/app/services/system-status.service' | ||||
| import { Toast, ToastService } from 'src/app/services/toast.service' | ||||
| import { ConfirmButtonComponent } from '../../common/confirm-button/confirm-button.component' | ||||
| import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component' | ||||
| import { CheckComponent } from '../../common/input/check/check.component' | ||||
| @@ -66,7 +69,7 @@ describe('SettingsComponent', () => { | ||||
|   let settingsService: SettingsService | ||||
|   let activatedRoute: ActivatedRoute | ||||
|   let viewportScroller: ViewportScroller | ||||
|   let toastService: ToastService | ||||
|   let notificationService: NotificationService | ||||
|   let userService: UserService | ||||
|   let permissionsService: PermissionsService | ||||
|   let groupService: GroupService | ||||
| @@ -115,7 +118,7 @@ describe('SettingsComponent', () => { | ||||
|     router = TestBed.inject(Router) | ||||
|     activatedRoute = TestBed.inject(ActivatedRoute) | ||||
|     viewportScroller = TestBed.inject(ViewportScroller) | ||||
|     toastService = TestBed.inject(ToastService) | ||||
|     notificationService = TestBed.inject(NotificationService) | ||||
|     settingsService = TestBed.inject(SettingsService) | ||||
|     settingsService.currentUser = users[0] | ||||
|     userService = TestBed.inject(UserService) | ||||
| @@ -194,8 +197,8 @@ describe('SettingsComponent', () => { | ||||
|  | ||||
|   it('should support save local settings updating appearance settings and calling API, show error', () => { | ||||
|     completeSetup() | ||||
|     const toastErrorSpy = jest.spyOn(toastService, 'showError') | ||||
|     const toastSpy = jest.spyOn(toastService, 'show') | ||||
|     const notificationErrorSpy = jest.spyOn(notificationService, 'showError') | ||||
|     const notificationSpy = jest.spyOn(notificationService, 'show') | ||||
|     const storeSpy = jest.spyOn(settingsService, 'storeSettings') | ||||
|     const appearanceSettingsSpy = jest.spyOn( | ||||
|       settingsService, | ||||
| @@ -209,7 +212,7 @@ describe('SettingsComponent', () => { | ||||
|     ) | ||||
|     component.saveSettings() | ||||
|  | ||||
|     expect(toastErrorSpy).toHaveBeenCalled() | ||||
|     expect(notificationErrorSpy).toHaveBeenCalled() | ||||
|     expect(storeSpy).toHaveBeenCalled() | ||||
|     expect(appearanceSettingsSpy).not.toHaveBeenCalled() | ||||
|     expect(setSpy).toHaveBeenCalledTimes(29) | ||||
| @@ -217,14 +220,14 @@ describe('SettingsComponent', () => { | ||||
|     // succeed | ||||
|     storeSpy.mockReturnValueOnce(of(true)) | ||||
|     component.saveSettings() | ||||
|     expect(toastSpy).toHaveBeenCalled() | ||||
|     expect(notificationSpy).toHaveBeenCalled() | ||||
|     expect(appearanceSettingsSpy).toHaveBeenCalled() | ||||
|   }) | ||||
|  | ||||
|   it('should offer reload if settings changes require', () => { | ||||
|     completeSetup() | ||||
|     let toast: Toast | ||||
|     toastService.getToasts().subscribe((t) => (toast = t[0])) | ||||
|     let toast: Notification | ||||
|     notificationService.getNotifications().subscribe((t) => (toast = t[0])) | ||||
|     component.initialize(true) // reset | ||||
|     component.store.getValue()['displayLanguage'] = 'en-US' | ||||
|     component.store.getValue()['updateCheckingEnabled'] = false | ||||
| @@ -258,7 +261,7 @@ describe('SettingsComponent', () => { | ||||
|   }) | ||||
|  | ||||
|   it('should show errors on load if load users failure', () => { | ||||
|     const toastErrorSpy = jest.spyOn(toastService, 'showError') | ||||
|     const notificationErrorSpy = jest.spyOn(notificationService, 'showError') | ||||
|     jest | ||||
|       .spyOn(userService, 'listAll') | ||||
|       .mockImplementation(() => | ||||
| @@ -266,11 +269,11 @@ describe('SettingsComponent', () => { | ||||
|       ) | ||||
|     completeSetup(userService) | ||||
|     fixture.detectChanges() | ||||
|     expect(toastErrorSpy).toBeCalled() | ||||
|     expect(notificationErrorSpy).toBeCalled() | ||||
|   }) | ||||
|  | ||||
|   it('should show errors on load if load groups failure', () => { | ||||
|     const toastErrorSpy = jest.spyOn(toastService, 'showError') | ||||
|     const notificationErrorSpy = jest.spyOn(notificationService, 'showError') | ||||
|     jest | ||||
|       .spyOn(groupService, 'listAll') | ||||
|       .mockImplementation(() => | ||||
| @@ -278,7 +281,7 @@ describe('SettingsComponent', () => { | ||||
|       ) | ||||
|     completeSetup(groupService) | ||||
|     fixture.detectChanges() | ||||
|     expect(toastErrorSpy).toBeCalled() | ||||
|     expect(notificationErrorSpy).toBeCalled() | ||||
|   }) | ||||
|  | ||||
|   it('should load system status on initialize, show errors if needed', () => { | ||||
|   | ||||
| @@ -43,6 +43,10 @@ import { User } from 'src/app/data/user' | ||||
| import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive' | ||||
| import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe' | ||||
| import { DocumentListViewService } from 'src/app/services/document-list-view.service' | ||||
| import { | ||||
|   Notification, | ||||
|   NotificationService, | ||||
| } from 'src/app/services/notification.service' | ||||
| import { | ||||
|   PermissionAction, | ||||
|   PermissionType, | ||||
| @@ -55,7 +59,6 @@ import { | ||||
|   SettingsService, | ||||
| } from 'src/app/services/settings.service' | ||||
| import { SystemStatusService } from 'src/app/services/system-status.service' | ||||
| import { Toast, ToastService } from 'src/app/services/toast.service' | ||||
| import { CheckComponent } from '../../common/input/check/check.component' | ||||
| import { ColorComponent } from '../../common/input/color/color.component' | ||||
| import { PermissionsGroupComponent } from '../../common/input/permissions/permissions-group/permissions-group.component' | ||||
| @@ -181,7 +184,7 @@ export class SettingsComponent | ||||
|  | ||||
|   constructor( | ||||
|     private documentListViewService: DocumentListViewService, | ||||
|     private toastService: ToastService, | ||||
|     private notificationService: NotificationService, | ||||
|     private settings: SettingsService, | ||||
|     @Inject(LOCALE_ID) public currentLocale: string, | ||||
|     private viewportScroller: ViewportScroller, | ||||
| @@ -217,7 +220,10 @@ export class SettingsComponent | ||||
|             this.users = r.results | ||||
|           }, | ||||
|           error: (e) => { | ||||
|             this.toastService.showError($localize`Error retrieving users`, e) | ||||
|             this.notificationService.showError( | ||||
|               $localize`Error retrieving users`, | ||||
|               e | ||||
|             ) | ||||
|           }, | ||||
|         }) | ||||
|     } | ||||
| @@ -236,7 +242,10 @@ export class SettingsComponent | ||||
|             this.groups = r.results | ||||
|           }, | ||||
|           error: (e) => { | ||||
|             this.toastService.showError($localize`Error retrieving groups`, e) | ||||
|             this.notificationService.showError( | ||||
|               $localize`Error retrieving groups`, | ||||
|               e | ||||
|             ) | ||||
|           }, | ||||
|         }) | ||||
|     } | ||||
| @@ -531,7 +540,7 @@ export class SettingsComponent | ||||
|           this.store.next(this.settingsForm.value) | ||||
|           this.settings.updateAppearanceSettings() | ||||
|           this.settings.initializeDisplayFields() | ||||
|           let savedToast: Toast = { | ||||
|           let savedToast: Notification = { | ||||
|             content: $localize`Settings were saved successfully.`, | ||||
|             delay: 5000, | ||||
|           } | ||||
| @@ -543,10 +552,10 @@ export class SettingsComponent | ||||
|             } | ||||
|           } | ||||
|  | ||||
|           this.toastService.show(savedToast) | ||||
|           this.notificationService.show(savedToast) | ||||
|         }, | ||||
|         error: (error) => { | ||||
|           this.toastService.showError( | ||||
|           this.notificationService.showError( | ||||
|             $localize`An error occurred while saving settings.`, | ||||
|             error | ||||
|           ) | ||||
|   | ||||
| @@ -12,7 +12,7 @@ import { | ||||
| import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons' | ||||
| import { of, throwError } from 'rxjs' | ||||
| import { SafeHtmlPipe } from 'src/app/pipes/safehtml.pipe' | ||||
| import { ToastService } from 'src/app/services/toast.service' | ||||
| import { NotificationService } from 'src/app/services/notification.service' | ||||
| import { TrashService } from 'src/app/services/trash.service' | ||||
| import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component' | ||||
| import { PageHeaderComponent } from '../../common/page-header/page-header.component' | ||||
| @@ -38,7 +38,7 @@ describe('TrashComponent', () => { | ||||
|   let fixture: ComponentFixture<TrashComponent> | ||||
|   let trashService: TrashService | ||||
|   let modalService: NgbModal | ||||
|   let toastService: ToastService | ||||
|   let notificationService: NotificationService | ||||
|   let router: Router | ||||
|  | ||||
|   beforeEach(async () => { | ||||
| @@ -60,7 +60,7 @@ describe('TrashComponent', () => { | ||||
|     fixture = TestBed.createComponent(TrashComponent) | ||||
|     trashService = TestBed.inject(TrashService) | ||||
|     modalService = TestBed.inject(NgbModal) | ||||
|     toastService = TestBed.inject(ToastService) | ||||
|     notificationService = TestBed.inject(NotificationService) | ||||
|     router = TestBed.inject(Router) | ||||
|     component = fixture.componentInstance | ||||
|     fixture.detectChanges() | ||||
| @@ -88,13 +88,13 @@ describe('TrashComponent', () => { | ||||
|     modalService.activeInstances.subscribe((instances) => { | ||||
|       modal = instances[0] | ||||
|     }) | ||||
|     const toastErrorSpy = jest.spyOn(toastService, 'showError') | ||||
|     const notificationErrorSpy = jest.spyOn(notificationService, 'showError') | ||||
|  | ||||
|     // fail first | ||||
|     trashSpy.mockReturnValue(throwError(() => 'Error')) | ||||
|     component.delete(documentsInTrash[0]) | ||||
|     modal.componentInstance.confirmClicked.next() | ||||
|     expect(toastErrorSpy).toHaveBeenCalled() | ||||
|     expect(notificationErrorSpy).toHaveBeenCalled() | ||||
|  | ||||
|     trashSpy.mockReturnValue(of('OK')) | ||||
|     component.delete(documentsInTrash[0]) | ||||
| @@ -109,13 +109,13 @@ describe('TrashComponent', () => { | ||||
|     modalService.activeInstances.subscribe((instances) => { | ||||
|       modal = instances[instances.length - 1] | ||||
|     }) | ||||
|     const toastErrorSpy = jest.spyOn(toastService, 'showError') | ||||
|     const notificationErrorSpy = jest.spyOn(notificationService, 'showError') | ||||
|  | ||||
|     // fail first | ||||
|     trashSpy.mockReturnValue(throwError(() => 'Error')) | ||||
|     component.emptyTrash() | ||||
|     modal.componentInstance.confirmClicked.next() | ||||
|     expect(toastErrorSpy).toHaveBeenCalled() | ||||
|     expect(notificationErrorSpy).toHaveBeenCalled() | ||||
|  | ||||
|     trashSpy.mockReturnValue(of('OK')) | ||||
|     component.emptyTrash() | ||||
| @@ -131,12 +131,12 @@ describe('TrashComponent', () => { | ||||
|   it('should support restore document, show error if needed', () => { | ||||
|     const restoreSpy = jest.spyOn(trashService, 'restoreDocuments') | ||||
|     const reloadSpy = jest.spyOn(component, 'reload') | ||||
|     const toastErrorSpy = jest.spyOn(toastService, 'showError') | ||||
|     const notificationErrorSpy = jest.spyOn(notificationService, 'showError') | ||||
|  | ||||
|     // fail first | ||||
|     restoreSpy.mockReturnValue(throwError(() => 'Error')) | ||||
|     component.restore(documentsInTrash[0]) | ||||
|     expect(toastErrorSpy).toHaveBeenCalled() | ||||
|     expect(notificationErrorSpy).toHaveBeenCalled() | ||||
|     expect(reloadSpy).not.toHaveBeenCalled() | ||||
|  | ||||
|     restoreSpy.mockReturnValue(of('OK')) | ||||
| @@ -148,12 +148,12 @@ describe('TrashComponent', () => { | ||||
|   it('should support restore all documents, show error if needed', () => { | ||||
|     const restoreSpy = jest.spyOn(trashService, 'restoreDocuments') | ||||
|     const reloadSpy = jest.spyOn(component, 'reload') | ||||
|     const toastErrorSpy = jest.spyOn(toastService, 'showError') | ||||
|     const notificationErrorSpy = jest.spyOn(notificationService, 'showError') | ||||
|  | ||||
|     // fail first | ||||
|     restoreSpy.mockReturnValue(throwError(() => 'Error')) | ||||
|     component.restoreAll() | ||||
|     expect(toastErrorSpy).toHaveBeenCalled() | ||||
|     expect(notificationErrorSpy).toHaveBeenCalled() | ||||
|     expect(reloadSpy).not.toHaveBeenCalled() | ||||
|  | ||||
|     restoreSpy.mockReturnValue(of('OK')) | ||||
| @@ -167,7 +167,7 @@ describe('TrashComponent', () => { | ||||
|   it('should offer link to restored document', () => { | ||||
|     let toasts | ||||
|     const navigateSpy = jest.spyOn(router, 'navigate') | ||||
|     toastService.getToasts().subscribe((allToasts) => { | ||||
|     notificationService.getNotifications().subscribe((allToasts) => { | ||||
|       toasts = [...allToasts] | ||||
|     }) | ||||
|     jest.spyOn(trashService, 'restoreDocuments').mockReturnValue(of('OK')) | ||||
|   | ||||
| @@ -10,8 +10,8 @@ import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons' | ||||
| import { delay, takeUntil, tap } from 'rxjs' | ||||
| import { Document } from 'src/app/data/document' | ||||
| import { SETTINGS_KEYS } from 'src/app/data/ui-settings' | ||||
| import { NotificationService } from 'src/app/services/notification.service' | ||||
| import { SettingsService } from 'src/app/services/settings.service' | ||||
| import { ToastService } from 'src/app/services/toast.service' | ||||
| import { TrashService } from 'src/app/services/trash.service' | ||||
| import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component' | ||||
| import { PageHeaderComponent } from '../../common/page-header/page-header.component' | ||||
| @@ -44,7 +44,7 @@ export class TrashComponent | ||||
|  | ||||
|   constructor( | ||||
|     private trashService: TrashService, | ||||
|     private toastService: ToastService, | ||||
|     private notificationService: NotificationService, | ||||
|     private modalService: NgbModal, | ||||
|     private settingsService: SettingsService, | ||||
|     private router: Router | ||||
| @@ -86,14 +86,14 @@ export class TrashComponent | ||||
|         modal.componentInstance.buttonsEnabled = false | ||||
|         this.trashService.emptyTrash([document.id]).subscribe({ | ||||
|           next: () => { | ||||
|             this.toastService.showInfo( | ||||
|             this.notificationService.showInfo( | ||||
|               $localize`Document "${document.title}" deleted` | ||||
|             ) | ||||
|             modal.close() | ||||
|             this.reload() | ||||
|           }, | ||||
|           error: (err) => { | ||||
|             this.toastService.showError( | ||||
|             this.notificationService.showError( | ||||
|               $localize`Error deleting document "${document.title}"`, | ||||
|               err | ||||
|             ) | ||||
| @@ -121,13 +121,13 @@ export class TrashComponent | ||||
|           .emptyTrash(documents ? Array.from(documents) : null) | ||||
|           .subscribe({ | ||||
|             next: () => { | ||||
|               this.toastService.showInfo($localize`Document(s) deleted`) | ||||
|               this.notificationService.showInfo($localize`Document(s) deleted`) | ||||
|               this.allToggled = false | ||||
|               modal.close() | ||||
|               this.reload() | ||||
|             }, | ||||
|             error: (err) => { | ||||
|               this.toastService.showError( | ||||
|               this.notificationService.showError( | ||||
|                 $localize`Error deleting document(s)`, | ||||
|                 err | ||||
|               ) | ||||
| @@ -140,7 +140,7 @@ export class TrashComponent | ||||
|   restore(document: Document) { | ||||
|     this.trashService.restoreDocuments([document.id]).subscribe({ | ||||
|       next: () => { | ||||
|         this.toastService.show({ | ||||
|         this.notificationService.show({ | ||||
|           content: $localize`Document "${document.title}" restored`, | ||||
|           delay: 5000, | ||||
|           actionName: $localize`Open document`, | ||||
| @@ -151,7 +151,7 @@ export class TrashComponent | ||||
|         this.reload() | ||||
|       }, | ||||
|       error: (err) => { | ||||
|         this.toastService.showError( | ||||
|         this.notificationService.showError( | ||||
|           $localize`Error restoring document "${document.title}"`, | ||||
|           err | ||||
|         ) | ||||
| @@ -164,12 +164,12 @@ export class TrashComponent | ||||
|       .restoreDocuments(documents ? Array.from(documents) : null) | ||||
|       .subscribe({ | ||||
|         next: () => { | ||||
|           this.toastService.showInfo($localize`Document(s) restored`) | ||||
|           this.notificationService.showInfo($localize`Document(s) restored`) | ||||
|           this.allToggled = false | ||||
|           this.reload() | ||||
|         }, | ||||
|         error: (err) => { | ||||
|           this.toastService.showError( | ||||
|           this.notificationService.showError( | ||||
|             $localize`Error restoring document(s)`, | ||||
|             err | ||||
|           ) | ||||
|   | ||||
| @@ -14,11 +14,11 @@ import { Group } from 'src/app/data/group' | ||||
| import { User } from 'src/app/data/user' | ||||
| import { PermissionsGuard } from 'src/app/guards/permissions.guard' | ||||
| import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe' | ||||
| import { NotificationService } from 'src/app/services/notification.service' | ||||
| import { PermissionsService } from 'src/app/services/permissions.service' | ||||
| import { GroupService } from 'src/app/services/rest/group.service' | ||||
| import { UserService } from 'src/app/services/rest/user.service' | ||||
| import { SettingsService } from 'src/app/services/settings.service' | ||||
| import { ToastService } from 'src/app/services/toast.service' | ||||
| import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component' | ||||
| import { GroupEditDialogComponent } from '../../common/edit-dialog/group-edit-dialog/group-edit-dialog.component' | ||||
| import { UserEditDialogComponent } from '../../common/edit-dialog/user-edit-dialog/user-edit-dialog.component' | ||||
| @@ -38,7 +38,7 @@ describe('UsersAndGroupsComponent', () => { | ||||
|   let fixture: ComponentFixture<UsersAndGroupsComponent> | ||||
|   let settingsService: SettingsService | ||||
|   let modalService: NgbModal | ||||
|   let toastService: ToastService | ||||
|   let notificationService: NotificationService | ||||
|   let userService: UserService | ||||
|   let permissionsService: PermissionsService | ||||
|   let groupService: GroupService | ||||
| @@ -59,7 +59,7 @@ describe('UsersAndGroupsComponent', () => { | ||||
|     settingsService.currentUser = users[0] | ||||
|     userService = TestBed.inject(UserService) | ||||
|     modalService = TestBed.inject(NgbModal) | ||||
|     toastService = TestBed.inject(ToastService) | ||||
|     notificationService = TestBed.inject(NotificationService) | ||||
|     permissionsService = TestBed.inject(PermissionsService) | ||||
|     jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true) | ||||
|     jest | ||||
| @@ -104,13 +104,13 @@ describe('UsersAndGroupsComponent', () => { | ||||
|     modalService.activeInstances.subscribe((refs) => (modal = refs[0])) | ||||
|     component.editUser(users[0]) | ||||
|     const editDialog = modal.componentInstance as UserEditDialogComponent | ||||
|     const toastErrorSpy = jest.spyOn(toastService, 'showError') | ||||
|     const toastInfoSpy = jest.spyOn(toastService, 'showInfo') | ||||
|     const notificationErrorSpy = jest.spyOn(notificationService, 'showError') | ||||
|     const notificationInfoSpy = jest.spyOn(notificationService, 'showInfo') | ||||
|     editDialog.failed.emit() | ||||
|     expect(toastErrorSpy).toBeCalled() | ||||
|     expect(notificationErrorSpy).toBeCalled() | ||||
|     settingsService.currentUser = users[1] // simulate logged in as different user | ||||
|     editDialog.succeeded.emit(users[0]) | ||||
|     expect(toastInfoSpy).toHaveBeenCalledWith( | ||||
|     expect(notificationInfoSpy).toHaveBeenCalledWith( | ||||
|       `Saved user "${users[0].username}".` | ||||
|     ) | ||||
|     component.editUser() | ||||
| @@ -123,18 +123,18 @@ describe('UsersAndGroupsComponent', () => { | ||||
|     component.deleteUser(users[0]) | ||||
|     const deleteDialog = modal.componentInstance as ConfirmDialogComponent | ||||
|     const deleteSpy = jest.spyOn(userService, 'delete') | ||||
|     const toastErrorSpy = jest.spyOn(toastService, 'showError') | ||||
|     const toastInfoSpy = jest.spyOn(toastService, 'showInfo') | ||||
|     const notificationErrorSpy = jest.spyOn(notificationService, 'showError') | ||||
|     const notificationInfoSpy = jest.spyOn(notificationService, 'showInfo') | ||||
|     const listAllSpy = jest.spyOn(userService, 'listAll') | ||||
|     deleteSpy.mockReturnValueOnce( | ||||
|       throwError(() => new Error('error deleting user')) | ||||
|     ) | ||||
|     deleteDialog.confirm() | ||||
|     expect(toastErrorSpy).toBeCalled() | ||||
|     expect(notificationErrorSpy).toBeCalled() | ||||
|     deleteSpy.mockReturnValueOnce(of(true)) | ||||
|     deleteDialog.confirm() | ||||
|     expect(listAllSpy).toHaveBeenCalled() | ||||
|     expect(toastInfoSpy).toHaveBeenCalledWith('Deleted user "user1"') | ||||
|     expect(notificationInfoSpy).toHaveBeenCalledWith('Deleted user "user1"') | ||||
|   }) | ||||
|  | ||||
|   it('should logout current user if password changed, after delay', fakeAsync(() => { | ||||
| @@ -163,12 +163,12 @@ describe('UsersAndGroupsComponent', () => { | ||||
|     modalService.activeInstances.subscribe((refs) => (modal = refs[0])) | ||||
|     component.editGroup(groups[0]) | ||||
|     const editDialog = modal.componentInstance as GroupEditDialogComponent | ||||
|     const toastErrorSpy = jest.spyOn(toastService, 'showError') | ||||
|     const toastInfoSpy = jest.spyOn(toastService, 'showInfo') | ||||
|     const notificationErrorSpy = jest.spyOn(notificationService, 'showError') | ||||
|     const notificationInfoSpy = jest.spyOn(notificationService, 'showInfo') | ||||
|     editDialog.failed.emit() | ||||
|     expect(toastErrorSpy).toBeCalled() | ||||
|     expect(notificationErrorSpy).toBeCalled() | ||||
|     editDialog.succeeded.emit(groups[0]) | ||||
|     expect(toastInfoSpy).toHaveBeenCalledWith( | ||||
|     expect(notificationInfoSpy).toHaveBeenCalledWith( | ||||
|       `Saved group "${groups[0].name}".` | ||||
|     ) | ||||
|     component.editGroup() | ||||
| @@ -181,18 +181,18 @@ describe('UsersAndGroupsComponent', () => { | ||||
|     component.deleteGroup(groups[0]) | ||||
|     const deleteDialog = modal.componentInstance as ConfirmDialogComponent | ||||
|     const deleteSpy = jest.spyOn(groupService, 'delete') | ||||
|     const toastErrorSpy = jest.spyOn(toastService, 'showError') | ||||
|     const toastInfoSpy = jest.spyOn(toastService, 'showInfo') | ||||
|     const notificationErrorSpy = jest.spyOn(notificationService, 'showError') | ||||
|     const notificationInfoSpy = jest.spyOn(notificationService, 'showInfo') | ||||
|     const listAllSpy = jest.spyOn(groupService, 'listAll') | ||||
|     deleteSpy.mockReturnValueOnce( | ||||
|       throwError(() => new Error('error deleting group')) | ||||
|     ) | ||||
|     deleteDialog.confirm() | ||||
|     expect(toastErrorSpy).toBeCalled() | ||||
|     expect(notificationErrorSpy).toBeCalled() | ||||
|     deleteSpy.mockReturnValueOnce(of(true)) | ||||
|     deleteDialog.confirm() | ||||
|     expect(listAllSpy).toHaveBeenCalled() | ||||
|     expect(toastInfoSpy).toHaveBeenCalledWith('Deleted group "group1"') | ||||
|     expect(notificationInfoSpy).toHaveBeenCalledWith('Deleted group "group1"') | ||||
|   }) | ||||
|  | ||||
|   it('should get group name', () => { | ||||
| @@ -202,7 +202,7 @@ describe('UsersAndGroupsComponent', () => { | ||||
|   }) | ||||
|  | ||||
|   it('should show errors on load if load users failure', () => { | ||||
|     const toastErrorSpy = jest.spyOn(toastService, 'showError') | ||||
|     const notificationErrorSpy = jest.spyOn(notificationService, 'showError') | ||||
|     jest | ||||
|       .spyOn(userService, 'listAll') | ||||
|       .mockImplementation(() => | ||||
| @@ -210,11 +210,11 @@ describe('UsersAndGroupsComponent', () => { | ||||
|       ) | ||||
|     completeSetup(userService) | ||||
|     fixture.detectChanges() | ||||
|     expect(toastErrorSpy).toBeCalled() | ||||
|     expect(notificationErrorSpy).toBeCalled() | ||||
|   }) | ||||
|  | ||||
|   it('should show errors on load if load groups failure', () => { | ||||
|     const toastErrorSpy = jest.spyOn(toastService, 'showError') | ||||
|     const notificationErrorSpy = jest.spyOn(notificationService, 'showError') | ||||
|     jest | ||||
|       .spyOn(groupService, 'listAll') | ||||
|       .mockImplementation(() => | ||||
| @@ -222,6 +222,6 @@ describe('UsersAndGroupsComponent', () => { | ||||
|       ) | ||||
|     completeSetup(groupService) | ||||
|     fixture.detectChanges() | ||||
|     expect(toastErrorSpy).toBeCalled() | ||||
|     expect(notificationErrorSpy).toBeCalled() | ||||
|   }) | ||||
| }) | ||||
|   | ||||
| @@ -5,11 +5,11 @@ import { Subject, first, takeUntil } from 'rxjs' | ||||
| import { Group } from 'src/app/data/group' | ||||
| import { User } from 'src/app/data/user' | ||||
| import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive' | ||||
| import { NotificationService } from 'src/app/services/notification.service' | ||||
| import { PermissionsService } from 'src/app/services/permissions.service' | ||||
| import { GroupService } from 'src/app/services/rest/group.service' | ||||
| import { UserService } from 'src/app/services/rest/user.service' | ||||
| import { SettingsService } from 'src/app/services/settings.service' | ||||
| import { ToastService } from 'src/app/services/toast.service' | ||||
| import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component' | ||||
| import { EditDialogMode } from '../../common/edit-dialog/edit-dialog.component' | ||||
| import { GroupEditDialogComponent } from '../../common/edit-dialog/group-edit-dialog/group-edit-dialog.component' | ||||
| @@ -39,7 +39,7 @@ export class UsersAndGroupsComponent | ||||
|   constructor( | ||||
|     private usersService: UserService, | ||||
|     private groupsService: GroupService, | ||||
|     private toastService: ToastService, | ||||
|     private notificationService: NotificationService, | ||||
|     private modalService: NgbModal, | ||||
|     public permissionsService: PermissionsService, | ||||
|     private settings: SettingsService | ||||
| @@ -56,7 +56,10 @@ export class UsersAndGroupsComponent | ||||
|           this.users = r.results | ||||
|         }, | ||||
|         error: (e) => { | ||||
|           this.toastService.showError($localize`Error retrieving users`, e) | ||||
|           this.notificationService.showError( | ||||
|             $localize`Error retrieving users`, | ||||
|             e | ||||
|           ) | ||||
|         }, | ||||
|       }) | ||||
|  | ||||
| @@ -68,7 +71,10 @@ export class UsersAndGroupsComponent | ||||
|           this.groups = r.results | ||||
|         }, | ||||
|         error: (e) => { | ||||
|           this.toastService.showError($localize`Error retrieving groups`, e) | ||||
|           this.notificationService.showError( | ||||
|             $localize`Error retrieving groups`, | ||||
|             e | ||||
|           ) | ||||
|         }, | ||||
|       }) | ||||
|   } | ||||
| @@ -93,14 +99,14 @@ export class UsersAndGroupsComponent | ||||
|           newUser.id === this.settings.currentUser.id && | ||||
|           (modal.componentInstance as UserEditDialogComponent).passwordIsSet | ||||
|         ) { | ||||
|           this.toastService.showInfo( | ||||
|           this.notificationService.showInfo( | ||||
|             $localize`Password has been changed, you will be logged out momentarily.` | ||||
|           ) | ||||
|           setTimeout(() => { | ||||
|             window.location.href = `${window.location.origin}/accounts/logout/?next=/accounts/login/?next=/` | ||||
|           }, 2500) | ||||
|         } else { | ||||
|           this.toastService.showInfo( | ||||
|           this.notificationService.showInfo( | ||||
|             $localize`Saved user "${newUser.username}".` | ||||
|           ) | ||||
|           this.usersService.listAll().subscribe((r) => { | ||||
| @@ -111,7 +117,7 @@ export class UsersAndGroupsComponent | ||||
|     modal.componentInstance.failed | ||||
|       .pipe(takeUntil(this.unsubscribeNotifier)) | ||||
|       .subscribe((e) => { | ||||
|         this.toastService.showError($localize`Error saving user.`, e) | ||||
|         this.notificationService.showError($localize`Error saving user.`, e) | ||||
|       }) | ||||
|   } | ||||
|  | ||||
| @@ -129,13 +135,15 @@ export class UsersAndGroupsComponent | ||||
|       this.usersService.delete(user).subscribe({ | ||||
|         next: () => { | ||||
|           modal.close() | ||||
|           this.toastService.showInfo($localize`Deleted user "${user.username}"`) | ||||
|           this.notificationService.showInfo( | ||||
|             $localize`Deleted user "${user.username}"` | ||||
|           ) | ||||
|           this.usersService.listAll().subscribe((r) => { | ||||
|             this.users = r.results | ||||
|           }) | ||||
|         }, | ||||
|         error: (e) => { | ||||
|           this.toastService.showError( | ||||
|           this.notificationService.showError( | ||||
|             $localize`Error deleting user "${user.username}".`, | ||||
|             e | ||||
|           ) | ||||
| @@ -156,7 +164,9 @@ export class UsersAndGroupsComponent | ||||
|     modal.componentInstance.succeeded | ||||
|       .pipe(takeUntil(this.unsubscribeNotifier)) | ||||
|       .subscribe((newGroup) => { | ||||
|         this.toastService.showInfo($localize`Saved group "${newGroup.name}".`) | ||||
|         this.notificationService.showInfo( | ||||
|           $localize`Saved group "${newGroup.name}".` | ||||
|         ) | ||||
|         this.groupsService.listAll().subscribe((r) => { | ||||
|           this.groups = r.results | ||||
|         }) | ||||
| @@ -164,7 +174,7 @@ export class UsersAndGroupsComponent | ||||
|     modal.componentInstance.failed | ||||
|       .pipe(takeUntil(this.unsubscribeNotifier)) | ||||
|       .subscribe((e) => { | ||||
|         this.toastService.showError($localize`Error saving group.`, e) | ||||
|         this.notificationService.showError($localize`Error saving group.`, e) | ||||
|       }) | ||||
|   } | ||||
|  | ||||
| @@ -182,13 +192,15 @@ export class UsersAndGroupsComponent | ||||
|       this.groupsService.delete(group).subscribe({ | ||||
|         next: () => { | ||||
|           modal.close() | ||||
|           this.toastService.showInfo($localize`Deleted group "${group.name}"`) | ||||
|           this.notificationService.showInfo( | ||||
|             $localize`Deleted group "${group.name}"` | ||||
|           ) | ||||
|           this.groupsService.listAll().subscribe((r) => { | ||||
|             this.groups = r.results | ||||
|           }) | ||||
|         }, | ||||
|         error: (e) => { | ||||
|           this.toastService.showError( | ||||
|           this.notificationService.showError( | ||||
|             $localize`Error deleting group "${group.name}".`, | ||||
|             e | ||||
|           ) | ||||
|   | ||||
| @@ -30,7 +30,7 @@ | ||||
|     </div> | ||||
|   </div> | ||||
|   <ul ngbNav class="order-sm-3"> | ||||
|     <pngx-toasts-dropdown></pngx-toasts-dropdown> | ||||
|     <pngx-notifications-dropdown></pngx-notifications-dropdown> | ||||
|     <li ngbDropdown class="nav-item dropdown"> | ||||
|       <button class="btn ps-1 border-0" id="userDropdown" ngbDropdownToggle> | ||||
|         <i-bs width="1.3em" height="1.3em" name="person-circle"></i-bs> | ||||
|   | ||||
| @@ -26,13 +26,13 @@ import { | ||||
|   DjangoMessageLevel, | ||||
|   DjangoMessagesService, | ||||
| } from 'src/app/services/django-messages.service' | ||||
| import { NotificationService } from 'src/app/services/notification.service' | ||||
| import { OpenDocumentsService } from 'src/app/services/open-documents.service' | ||||
| import { PermissionsService } from 'src/app/services/permissions.service' | ||||
| import { RemoteVersionService } from 'src/app/services/rest/remote-version.service' | ||||
| import { SavedViewService } from 'src/app/services/rest/saved-view.service' | ||||
| import { SearchService } from 'src/app/services/rest/search.service' | ||||
| import { SettingsService } from 'src/app/services/settings.service' | ||||
| import { ToastService } from 'src/app/services/toast.service' | ||||
| import { environment } from 'src/environments/environment' | ||||
| import { ProfileEditDialogComponent } from '../common/profile-edit-dialog/profile-edit-dialog.component' | ||||
| import { DocumentDetailComponent } from '../document-detail/document-detail.component' | ||||
| @@ -86,7 +86,7 @@ describe('AppFrameComponent', () => { | ||||
|   let settingsService: SettingsService | ||||
|   let permissionsService: PermissionsService | ||||
|   let remoteVersionService: RemoteVersionService | ||||
|   let toastService: ToastService | ||||
|   let notificationService: NotificationService | ||||
|   let messagesService: DjangoMessagesService | ||||
|   let openDocumentsService: OpenDocumentsService | ||||
|   let router: Router | ||||
| @@ -126,7 +126,7 @@ describe('AppFrameComponent', () => { | ||||
|         PermissionsService, | ||||
|         RemoteVersionService, | ||||
|         IfPermissionsDirective, | ||||
|         ToastService, | ||||
|         NotificationService, | ||||
|         DjangoMessagesService, | ||||
|         OpenDocumentsService, | ||||
|         SearchService, | ||||
| @@ -157,7 +157,7 @@ describe('AppFrameComponent', () => { | ||||
|     const savedViewService = TestBed.inject(SavedViewService) | ||||
|     permissionsService = TestBed.inject(PermissionsService) | ||||
|     remoteVersionService = TestBed.inject(RemoteVersionService) | ||||
|     toastService = TestBed.inject(ToastService) | ||||
|     notificationService = TestBed.inject(NotificationService) | ||||
|     messagesService = TestBed.inject(DjangoMessagesService) | ||||
|     openDocumentsService = TestBed.inject(OpenDocumentsService) | ||||
|     modalService = TestBed.inject(NgbModal) | ||||
| @@ -216,7 +216,7 @@ describe('AppFrameComponent', () => { | ||||
|  | ||||
|   it('should show error on toggle update checking if store settings fails', () => { | ||||
|     jest.spyOn(console, 'warn').mockImplementation(() => {}) | ||||
|     const toastSpy = jest.spyOn(toastService, 'showError') | ||||
|     const notificationSpy = jest.spyOn(notificationService, 'showError') | ||||
|     settingsService.set(SETTINGS_KEYS.UPDATE_CHECKING_ENABLED, false) | ||||
|     component.setUpdateChecking(true) | ||||
|     httpTestingController | ||||
| @@ -225,7 +225,7 @@ describe('AppFrameComponent', () => { | ||||
|         status: 500, | ||||
|         statusText: 'error', | ||||
|       }) | ||||
|     expect(toastSpy).toHaveBeenCalled() | ||||
|     expect(notificationSpy).toHaveBeenCalled() | ||||
|   }) | ||||
|  | ||||
|   it('should support toggling slim sidebar and saving', fakeAsync(() => { | ||||
| @@ -245,7 +245,7 @@ describe('AppFrameComponent', () => { | ||||
|  | ||||
|   it('should show error on toggle slim sidebar if store settings fails', () => { | ||||
|     jest.spyOn(console, 'warn').mockImplementation(() => {}) | ||||
|     const toastSpy = jest.spyOn(toastService, 'showError') | ||||
|     const notificationSpy = jest.spyOn(notificationService, 'showError') | ||||
|     component.toggleSlimSidebar() | ||||
|     httpTestingController | ||||
|       .expectOne(`${environment.apiBaseUrl}ui_settings/`) | ||||
| @@ -253,7 +253,7 @@ describe('AppFrameComponent', () => { | ||||
|         status: 500, | ||||
|         statusText: 'error', | ||||
|       }) | ||||
|     expect(toastSpy).toHaveBeenCalled() | ||||
|     expect(notificationSpy).toHaveBeenCalled() | ||||
|   }) | ||||
|  | ||||
|   it('should support collapsible menu', () => { | ||||
| @@ -305,7 +305,7 @@ describe('AppFrameComponent', () => { | ||||
|  | ||||
|   it('should update saved view sorting on drag + drop, show info', () => { | ||||
|     const settingsSpy = jest.spyOn(settingsService, 'updateSidebarViewsSort') | ||||
|     const toastSpy = jest.spyOn(toastService, 'showInfo') | ||||
|     const notificationSpy = jest.spyOn(notificationService, 'showInfo') | ||||
|     jest.spyOn(settingsService, 'storeSettings').mockReturnValue(of(true)) | ||||
|     component.onDrop({ previousIndex: 0, currentIndex: 1 } as CdkDragDrop< | ||||
|       SavedView[] | ||||
| @@ -315,7 +315,7 @@ describe('AppFrameComponent', () => { | ||||
|       saved_views[0], | ||||
|       saved_views[3], | ||||
|     ]) | ||||
|     expect(toastSpy).toHaveBeenCalled() | ||||
|     expect(notificationSpy).toHaveBeenCalled() | ||||
|   }) | ||||
|  | ||||
|   it('should update saved view sorting on drag + drop, show error', () => { | ||||
| @@ -326,14 +326,14 @@ describe('AppFrameComponent', () => { | ||||
|     fixture = TestBed.createComponent(AppFrameComponent) | ||||
|     component = fixture.componentInstance | ||||
|     fixture.detectChanges() | ||||
|     const toastSpy = jest.spyOn(toastService, 'showError') | ||||
|     const notificationSpy = jest.spyOn(notificationService, 'showError') | ||||
|     jest | ||||
|       .spyOn(settingsService, 'storeSettings') | ||||
|       .mockReturnValue(throwError(() => new Error('unable to save'))) | ||||
|     component.onDrop({ previousIndex: 0, currentIndex: 2 } as CdkDragDrop< | ||||
|       SavedView[] | ||||
|     >) | ||||
|     expect(toastSpy).toHaveBeenCalled() | ||||
|     expect(notificationSpy).toHaveBeenCalled() | ||||
|   }) | ||||
|  | ||||
|   it('should support edit profile', () => { | ||||
| @@ -345,9 +345,9 @@ describe('AppFrameComponent', () => { | ||||
|     }) | ||||
|   }) | ||||
|  | ||||
|   it('should show toasts for django messages', () => { | ||||
|     const toastErrorSpy = jest.spyOn(toastService, 'showError') | ||||
|     const toastInfoSpy = jest.spyOn(toastService, 'showInfo') | ||||
|   it('should show notifications for django messages', () => { | ||||
|     const notificationErrorSpy = jest.spyOn(notificationService, 'showError') | ||||
|     const notificationInfoSpy = jest.spyOn(notificationService, 'showInfo') | ||||
|     jest.spyOn(messagesService, 'get').mockReturnValue([ | ||||
|       { level: DjangoMessageLevel.WARNING, message: 'Test warning' }, | ||||
|       { level: DjangoMessageLevel.ERROR, message: 'Test error' }, | ||||
| @@ -356,7 +356,7 @@ describe('AppFrameComponent', () => { | ||||
|       { level: DjangoMessageLevel.DEBUG, message: 'Test debug' }, | ||||
|     ]) | ||||
|     component.ngOnInit() | ||||
|     expect(toastErrorSpy).toHaveBeenCalledTimes(2) | ||||
|     expect(toastInfoSpy).toHaveBeenCalledTimes(3) | ||||
|     expect(notificationErrorSpy).toHaveBeenCalledTimes(2) | ||||
|     expect(notificationInfoSpy).toHaveBeenCalledTimes(3) | ||||
|   }) | ||||
| }) | ||||
|   | ||||
| @@ -29,6 +29,7 @@ import { | ||||
|   DjangoMessageLevel, | ||||
|   DjangoMessagesService, | ||||
| } from 'src/app/services/django-messages.service' | ||||
| import { NotificationService } from 'src/app/services/notification.service' | ||||
| import { OpenDocumentsService } from 'src/app/services/open-documents.service' | ||||
| import { | ||||
|   PermissionAction, | ||||
| @@ -42,13 +43,12 @@ import { | ||||
| import { SavedViewService } from 'src/app/services/rest/saved-view.service' | ||||
| import { SettingsService } from 'src/app/services/settings.service' | ||||
| import { TasksService } from 'src/app/services/tasks.service' | ||||
| import { ToastService } from 'src/app/services/toast.service' | ||||
| import { environment } from 'src/environments/environment' | ||||
| import { ProfileEditDialogComponent } from '../common/profile-edit-dialog/profile-edit-dialog.component' | ||||
| import { DocumentDetailComponent } from '../document-detail/document-detail.component' | ||||
| import { ComponentWithPermissions } from '../with-permissions/with-permissions.component' | ||||
| import { GlobalSearchComponent } from './global-search/global-search.component' | ||||
| import { ToastsDropdownComponent } from './toasts-dropdown/toasts-dropdown.component' | ||||
| import { NotificationsDropdownComponent } from './notifications-dropdown/notifications-dropdown.component' | ||||
|  | ||||
| @Component({ | ||||
|   selector: 'pngx-app-frame', | ||||
| @@ -58,7 +58,7 @@ import { ToastsDropdownComponent } from './toasts-dropdown/toasts-dropdown.compo | ||||
|     GlobalSearchComponent, | ||||
|     DocumentTitlePipe, | ||||
|     IfPermissionsDirective, | ||||
|     ToastsDropdownComponent, | ||||
|     NotificationsDropdownComponent, | ||||
|     RouterModule, | ||||
|     NgClass, | ||||
|     NgbDropdownModule, | ||||
| @@ -89,7 +89,7 @@ export class AppFrameComponent | ||||
|     private remoteVersionService: RemoteVersionService, | ||||
|     public settingsService: SettingsService, | ||||
|     public tasksService: TasksService, | ||||
|     private readonly toastService: ToastService, | ||||
|     private readonly notificationService: NotificationService, | ||||
|     private modalService: NgbModal, | ||||
|     public permissionsService: PermissionsService, | ||||
|     private djangoMessagesService: DjangoMessagesService | ||||
| @@ -123,12 +123,12 @@ export class AppFrameComponent | ||||
|       switch (message.level) { | ||||
|         case DjangoMessageLevel.ERROR: | ||||
|         case DjangoMessageLevel.WARNING: | ||||
|           this.toastService.showError(message.message) | ||||
|           this.notificationService.showError(message.message) | ||||
|           break | ||||
|         case DjangoMessageLevel.SUCCESS: | ||||
|         case DjangoMessageLevel.INFO: | ||||
|         case DjangoMessageLevel.DEBUG: | ||||
|           this.toastService.showInfo(message.message) | ||||
|           this.notificationService.showInfo(message.message) | ||||
|           break | ||||
|       } | ||||
|     }) | ||||
| @@ -157,7 +157,7 @@ export class AppFrameComponent | ||||
|       .pipe(first()) | ||||
|       .subscribe({ | ||||
|         error: (error) => { | ||||
|           this.toastService.showError( | ||||
|           this.notificationService.showError( | ||||
|             $localize`An error occurred while saving settings.` | ||||
|           ) | ||||
|           console.warn(error) | ||||
| @@ -242,10 +242,13 @@ export class AppFrameComponent | ||||
|  | ||||
|     this.settingsService.updateSidebarViewsSort(sidebarViews).subscribe({ | ||||
|       next: () => { | ||||
|         this.toastService.showInfo($localize`Sidebar views updated`) | ||||
|         this.notificationService.showInfo($localize`Sidebar views updated`) | ||||
|       }, | ||||
|       error: (e) => { | ||||
|         this.toastService.showError($localize`Error updating sidebar views`, e) | ||||
|         this.notificationService.showError( | ||||
|           $localize`Error updating sidebar views`, | ||||
|           e | ||||
|         ) | ||||
|       }, | ||||
|     }) | ||||
|   } | ||||
| @@ -265,7 +268,7 @@ export class AppFrameComponent | ||||
|       .pipe(first()) | ||||
|       .subscribe({ | ||||
|         error: (error) => { | ||||
|           this.toastService.showError( | ||||
|           this.notificationService.showError( | ||||
|             $localize`An error occurred while saving update checking settings.` | ||||
|           ) | ||||
|           console.warn(error) | ||||
|   | ||||
| @@ -28,10 +28,10 @@ import { | ||||
| } from 'src/app/data/filter-rule-type' | ||||
| import { GlobalSearchType, SETTINGS_KEYS } from 'src/app/data/ui-settings' | ||||
| import { DocumentListViewService } from 'src/app/services/document-list-view.service' | ||||
| import { NotificationService } from 'src/app/services/notification.service' | ||||
| import { DocumentService } from 'src/app/services/rest/document.service' | ||||
| import { SearchService } from 'src/app/services/rest/search.service' | ||||
| import { SettingsService } from 'src/app/services/settings.service' | ||||
| import { ToastService } from 'src/app/services/toast.service' | ||||
| import { queryParamsFromFilterRules } from 'src/app/utils/query-params' | ||||
| import { CorrespondentEditDialogComponent } from '../../common/edit-dialog/correspondent-edit-dialog/correspondent-edit-dialog.component' | ||||
| import { CustomFieldEditDialogComponent } from '../../common/edit-dialog/custom-field-edit-dialog/custom-field-edit-dialog.component' | ||||
| @@ -133,7 +133,7 @@ describe('GlobalSearchComponent', () => { | ||||
|   let modalService: NgbModal | ||||
|   let documentService: DocumentService | ||||
|   let documentListViewService: DocumentListViewService | ||||
|   let toastService: ToastService | ||||
|   let notificationService: NotificationService | ||||
|   let settingsService: SettingsService | ||||
|  | ||||
|   beforeEach(async () => { | ||||
| @@ -157,7 +157,7 @@ describe('GlobalSearchComponent', () => { | ||||
|     modalService = TestBed.inject(NgbModal) | ||||
|     documentService = TestBed.inject(DocumentService) | ||||
|     documentListViewService = TestBed.inject(DocumentListViewService) | ||||
|     toastService = TestBed.inject(ToastService) | ||||
|     notificationService = TestBed.inject(NotificationService) | ||||
|     settingsService = TestBed.inject(SettingsService) | ||||
|  | ||||
|     fixture = TestBed.createComponent(GlobalSearchComponent) | ||||
| @@ -397,16 +397,16 @@ describe('GlobalSearchComponent', () => { | ||||
|     }) | ||||
|  | ||||
|     const editDialog = modal.componentInstance as CustomFieldEditDialogComponent | ||||
|     const toastErrorSpy = jest.spyOn(toastService, 'showError') | ||||
|     const toastInfoSpy = jest.spyOn(toastService, 'showInfo') | ||||
|     const notificationErrorSpy = jest.spyOn(notificationService, 'showError') | ||||
|     const notificationInfoSpy = jest.spyOn(notificationService, 'showInfo') | ||||
|  | ||||
|     // fail first | ||||
|     editDialog.failed.emit({ error: 'error creating item' }) | ||||
|     expect(toastErrorSpy).toHaveBeenCalled() | ||||
|     expect(notificationErrorSpy).toHaveBeenCalled() | ||||
|  | ||||
|     // succeed | ||||
|     editDialog.succeeded.emit(true) | ||||
|     expect(toastInfoSpy).toHaveBeenCalled() | ||||
|     expect(notificationInfoSpy).toHaveBeenCalled() | ||||
|   }) | ||||
|  | ||||
|   it('should support secondary action', () => { | ||||
| @@ -448,16 +448,16 @@ describe('GlobalSearchComponent', () => { | ||||
|     }) | ||||
|  | ||||
|     const editDialog = modal.componentInstance as CustomFieldEditDialogComponent | ||||
|     const toastErrorSpy = jest.spyOn(toastService, 'showError') | ||||
|     const toastInfoSpy = jest.spyOn(toastService, 'showInfo') | ||||
|     const notificationErrorSpy = jest.spyOn(notificationService, 'showError') | ||||
|     const notificationInfoSpy = jest.spyOn(notificationService, 'showInfo') | ||||
|  | ||||
|     // fail first | ||||
|     editDialog.failed.emit({ error: 'error creating item' }) | ||||
|     expect(toastErrorSpy).toHaveBeenCalled() | ||||
|     expect(notificationErrorSpy).toHaveBeenCalled() | ||||
|  | ||||
|     // succeed | ||||
|     editDialog.succeeded.emit(true) | ||||
|     expect(toastInfoSpy).toHaveBeenCalled() | ||||
|     expect(notificationInfoSpy).toHaveBeenCalled() | ||||
|   }) | ||||
|  | ||||
|   it('should support reset', () => { | ||||
|   | ||||
| @@ -31,6 +31,7 @@ import { GlobalSearchType, SETTINGS_KEYS } from 'src/app/data/ui-settings' | ||||
| import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe' | ||||
| import { DocumentListViewService } from 'src/app/services/document-list-view.service' | ||||
| import { HotKeyService } from 'src/app/services/hot-key.service' | ||||
| import { NotificationService } from 'src/app/services/notification.service' | ||||
| import { | ||||
|   PermissionAction, | ||||
|   PermissionsService, | ||||
| @@ -41,7 +42,6 @@ import { | ||||
|   SearchService, | ||||
| } from 'src/app/services/rest/search.service' | ||||
| import { SettingsService } from 'src/app/services/settings.service' | ||||
| import { ToastService } from 'src/app/services/toast.service' | ||||
| import { paramsFromViewState } from 'src/app/utils/query-params' | ||||
| import { CorrespondentEditDialogComponent } from '../../common/edit-dialog/correspondent-edit-dialog/correspondent-edit-dialog.component' | ||||
| import { CustomFieldEditDialogComponent } from '../../common/edit-dialog/custom-field-edit-dialog/custom-field-edit-dialog.component' | ||||
| @@ -97,7 +97,7 @@ export class GlobalSearchComponent implements OnInit { | ||||
|     private documentService: DocumentService, | ||||
|     private documentListViewService: DocumentListViewService, | ||||
|     private permissionsService: PermissionsService, | ||||
|     private toastService: ToastService, | ||||
|     private notificationService: NotificationService, | ||||
|     private hotkeyService: HotKeyService, | ||||
|     private settingsService: SettingsService | ||||
|   ) { | ||||
| @@ -206,10 +206,15 @@ export class GlobalSearchComponent implements OnInit { | ||||
|       modalRef.componentInstance.dialogMode = EditDialogMode.EDIT | ||||
|       modalRef.componentInstance.object = object | ||||
|       modalRef.componentInstance.succeeded.subscribe(() => { | ||||
|         this.toastService.showInfo($localize`Successfully updated object.`) | ||||
|         this.notificationService.showInfo( | ||||
|           $localize`Successfully updated object.` | ||||
|         ) | ||||
|       }) | ||||
|       modalRef.componentInstance.failed.subscribe((e) => { | ||||
|         this.toastService.showError($localize`Error occurred saving object.`, e) | ||||
|         this.notificationService.showError( | ||||
|           $localize`Error occurred saving object.`, | ||||
|           e | ||||
|         ) | ||||
|       }) | ||||
|     } | ||||
|   } | ||||
| @@ -244,10 +249,15 @@ export class GlobalSearchComponent implements OnInit { | ||||
|       modalRef.componentInstance.dialogMode = EditDialogMode.EDIT | ||||
|       modalRef.componentInstance.object = object | ||||
|       modalRef.componentInstance.succeeded.subscribe(() => { | ||||
|         this.toastService.showInfo($localize`Successfully updated object.`) | ||||
|         this.notificationService.showInfo( | ||||
|           $localize`Successfully updated object.` | ||||
|         ) | ||||
|       }) | ||||
|       modalRef.componentInstance.failed.subscribe((e) => { | ||||
|         this.toastService.showError($localize`Error occurred saving object.`, e) | ||||
|         this.notificationService.showError( | ||||
|           $localize`Error occurred saving object.`, | ||||
|           e | ||||
|         ) | ||||
|       }) | ||||
|     } | ||||
|   } | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| 
 | ||||
| <li ngbDropdown class="nav-item" (openChange)="onOpenChange($event)"> | ||||
|   @if (toasts.length) { | ||||
|     <span class="badge rounded-pill z-3 pe-none bg-secondary me-2 position-absolute top-0 left-0">{{ toasts.length }}</span> | ||||
|   @if (notifications.length) { | ||||
|     <span class="badge rounded-pill z-3 pe-none bg-secondary me-2 position-absolute top-0 left-0">{{ notifications.length }}</span> | ||||
|   } | ||||
|   <button class="btn border-0" id="notificationsDropdown" ngbDropdownToggle> | ||||
|     <i-bs width="1.3em" height="1.3em" name="bell"></i-bs> | ||||
| @@ -11,17 +11,17 @@ | ||||
|       <h6 i18n>Notifications</h6> | ||||
|       <div class="btn-group ms-auto"> | ||||
|         <button class="btn btn-sm btn-outline-secondary mb-2 ms-auto" | ||||
|           (click)="toastService.clearToasts()" | ||||
|           [disabled]="toasts.length === 0" | ||||
|           (click)="notificationService.clearNotifications()" | ||||
|           [disabled]="notifications.length === 0" | ||||
|           i18n>Clear All</button> | ||||
|       </div> | ||||
|     </div> | ||||
|     @if (toasts.length === 0) { | ||||
|     @if (notifications.length === 0) { | ||||
|       <p class="text-center mb-0 small text-muted"><em i18n>No notifications</em></p> | ||||
|     } | ||||
|     <div class="scroll-list"> | ||||
|       @for (toast of toasts; track toast.id) { | ||||
|         <pngx-toast [autohide]="false" [toast]="toast" (hidden)="onHidden(toast)" (close)="toastService.closeToast(toast)"></pngx-toast> | ||||
|       @for (notification of notifications; track notification.id) { | ||||
|         <pngx-notification [autohide]="false" [notification]="notification" (hidden)="onHidden(notification)" (close)="notificationService.closeNotification(notification)"></pngx-notification> | ||||
|       } | ||||
|       </div> | ||||
|   </div> | ||||
| @@ -1,5 +1,5 @@ | ||||
| .dropdown-menu { | ||||
|   width: var(--pngx-toast-max-width); | ||||
|   width: var(--pngx-notification-max-width); | ||||
| } | ||||
| 
 | ||||
| .dropdown-menu .scroll-list { | ||||
| @@ -9,10 +9,13 @@ import { | ||||
| } from '@angular/core/testing' | ||||
| import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons' | ||||
| import { Subject } from 'rxjs' | ||||
| import { Toast, ToastService } from 'src/app/services/toast.service' | ||||
| import { ToastsDropdownComponent } from './toasts-dropdown.component' | ||||
| import { | ||||
|   Notification, | ||||
|   NotificationService, | ||||
| } from 'src/app/services/notification.service' | ||||
| import { NotificationsDropdownComponent } from './notifications-dropdown.component' | ||||
| 
 | ||||
| const toasts = [ | ||||
| const notifications = [ | ||||
|   { | ||||
|     id: 'abc-123', | ||||
|     content: 'foo bar', | ||||
| @@ -38,16 +41,16 @@ const toasts = [ | ||||
|   }, | ||||
| ] | ||||
| 
 | ||||
| describe('ToastsDropdownComponent', () => { | ||||
|   let component: ToastsDropdownComponent | ||||
|   let fixture: ComponentFixture<ToastsDropdownComponent> | ||||
|   let toastService: ToastService | ||||
|   let toastsSubject: Subject<Toast[]> = new Subject() | ||||
| describe('NotificationsDropdownComponent', () => { | ||||
|   let component: NotificationsDropdownComponent | ||||
|   let fixture: ComponentFixture<NotificationsDropdownComponent> | ||||
|   let notificationService: NotificationService | ||||
|   let notificationsSubject: Subject<Notification[]> = new Subject() | ||||
| 
 | ||||
|   beforeEach(async () => { | ||||
|     TestBed.configureTestingModule({ | ||||
|       imports: [ | ||||
|         ToastsDropdownComponent, | ||||
|         NotificationsDropdownComponent, | ||||
|         NgxBootstrapIconsModule.pick(allIcons), | ||||
|       ], | ||||
|       providers: [ | ||||
| @@ -56,24 +59,26 @@ describe('ToastsDropdownComponent', () => { | ||||
|       ], | ||||
|     }).compileComponents() | ||||
| 
 | ||||
|     fixture = TestBed.createComponent(ToastsDropdownComponent) | ||||
|     toastService = TestBed.inject(ToastService) | ||||
|     jest.spyOn(toastService, 'getToasts').mockReturnValue(toastsSubject) | ||||
|     fixture = TestBed.createComponent(NotificationsDropdownComponent) | ||||
|     notificationService = TestBed.inject(NotificationService) | ||||
|     jest | ||||
|       .spyOn(notificationService, 'getNotifications') | ||||
|       .mockReturnValue(notificationsSubject) | ||||
| 
 | ||||
|     component = fixture.componentInstance | ||||
| 
 | ||||
|     fixture.detectChanges() | ||||
|   }) | ||||
| 
 | ||||
|   it('should call getToasts and return toasts', fakeAsync(() => { | ||||
|     const spy = jest.spyOn(toastService, 'getToasts') | ||||
|   it('should call getNotifications and return notifications', fakeAsync(() => { | ||||
|     const spy = jest.spyOn(notificationService, 'getNotifications') | ||||
| 
 | ||||
|     component.ngOnInit() | ||||
|     toastsSubject.next(toasts) | ||||
|     notificationsSubject.next(notifications) | ||||
|     fixture.detectChanges() | ||||
| 
 | ||||
|     expect(spy).toHaveBeenCalled() | ||||
|     expect(component.toasts).toContainEqual({ | ||||
|     expect(component.notifications).toContainEqual({ | ||||
|       id: 'abc-123', | ||||
|       content: 'foo bar', | ||||
|       delay: 5000, | ||||
| @@ -84,9 +89,9 @@ describe('ToastsDropdownComponent', () => { | ||||
|     discardPeriodicTasks() | ||||
|   })) | ||||
| 
 | ||||
|   it('should show a toast', fakeAsync(() => { | ||||
|   it('should show a notification', fakeAsync(() => { | ||||
|     component.ngOnInit() | ||||
|     toastsSubject.next(toasts) | ||||
|     notificationsSubject.next(notifications) | ||||
|     fixture.detectChanges() | ||||
| 
 | ||||
|     expect(fixture.nativeElement.textContent).toContain('foo bar') | ||||
| @@ -96,12 +101,16 @@ describe('ToastsDropdownComponent', () => { | ||||
|     discardPeriodicTasks() | ||||
|   })) | ||||
| 
 | ||||
|   it('should toggle suppressPopupToasts', fakeAsync((finish) => { | ||||
|   it('should toggle suppressPopupNotifications', fakeAsync((finish) => { | ||||
|     component.ngOnInit() | ||||
|     fixture.detectChanges() | ||||
|     toastsSubject.next(toasts) | ||||
|     notificationsSubject.next(notifications) | ||||
| 
 | ||||
|     const spy = jest.spyOn(toastService, 'suppressPopupToasts', 'set') | ||||
|     const spy = jest.spyOn( | ||||
|       notificationService, | ||||
|       'suppressPopupNotifications', | ||||
|       'set' | ||||
|     ) | ||||
|     component.onOpenChange(true) | ||||
|     expect(spy).toHaveBeenCalledWith(true) | ||||
| 
 | ||||
| @@ -0,0 +1,47 @@ | ||||
| import { Component, OnDestroy, OnInit } from '@angular/core' | ||||
| import { | ||||
|   NgbDropdownModule, | ||||
|   NgbProgressbarModule, | ||||
| } from '@ng-bootstrap/ng-bootstrap' | ||||
| import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons' | ||||
| import { Subscription } from 'rxjs' | ||||
| import { | ||||
|   Notification, | ||||
|   NotificationService, | ||||
| } from 'src/app/services/notification.service' | ||||
| import { NotificationComponent } from '../../common/notification/notification.component' | ||||
|  | ||||
| @Component({ | ||||
|   selector: 'pngx-notifications-dropdown', | ||||
|   templateUrl: './notifications-dropdown.component.html', | ||||
|   styleUrls: ['./notifications-dropdown.component.scss'], | ||||
|   imports: [ | ||||
|     NotificationComponent, | ||||
|     NgbDropdownModule, | ||||
|     NgbProgressbarModule, | ||||
|     NgxBootstrapIconsModule, | ||||
|   ], | ||||
| }) | ||||
| export class NotificationsDropdownComponent implements OnInit, OnDestroy { | ||||
|   constructor(public notificationService: NotificationService) {} | ||||
|  | ||||
|   private subscription: Subscription | ||||
|  | ||||
|   public notifications: Notification[] = [] | ||||
|  | ||||
|   ngOnDestroy(): void { | ||||
|     this.subscription?.unsubscribe() | ||||
|   } | ||||
|  | ||||
|   ngOnInit(): void { | ||||
|     this.subscription = this.notificationService | ||||
|       .getNotifications() | ||||
|       .subscribe((notifications) => { | ||||
|         this.notifications = [...notifications] | ||||
|       }) | ||||
|   } | ||||
|  | ||||
|   onOpenChange(open: boolean): void { | ||||
|     this.notificationService.suppressPopupNotifications = open | ||||
|   } | ||||
| } | ||||
| @@ -1,42 +0,0 @@ | ||||
| import { Component, OnDestroy, OnInit } from '@angular/core' | ||||
| import { | ||||
|   NgbDropdownModule, | ||||
|   NgbProgressbarModule, | ||||
| } from '@ng-bootstrap/ng-bootstrap' | ||||
| import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons' | ||||
| import { Subscription } from 'rxjs' | ||||
| import { Toast, ToastService } from 'src/app/services/toast.service' | ||||
| import { ToastComponent } from '../../common/toast/toast.component' | ||||
|  | ||||
| @Component({ | ||||
|   selector: 'pngx-toasts-dropdown', | ||||
|   templateUrl: './toasts-dropdown.component.html', | ||||
|   styleUrls: ['./toasts-dropdown.component.scss'], | ||||
|   imports: [ | ||||
|     ToastComponent, | ||||
|     NgbDropdownModule, | ||||
|     NgbProgressbarModule, | ||||
|     NgxBootstrapIconsModule, | ||||
|   ], | ||||
| }) | ||||
| export class ToastsDropdownComponent implements OnInit, OnDestroy { | ||||
|   constructor(public toastService: ToastService) {} | ||||
|  | ||||
|   private subscription: Subscription | ||||
|  | ||||
|   public toasts: Toast[] = [] | ||||
|  | ||||
|   ngOnDestroy(): void { | ||||
|     this.subscription?.unsubscribe() | ||||
|   } | ||||
|  | ||||
|   ngOnInit(): void { | ||||
|     this.subscription = this.toastService.getToasts().subscribe((toasts) => { | ||||
|       this.toasts = [...toasts] | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|   onOpenChange(open: boolean): void { | ||||
|     this.toastService.suppressPopupToasts = open | ||||
|   } | ||||
| } | ||||
| @@ -18,9 +18,9 @@ import { NgSelectModule } from '@ng-select/ng-select' | ||||
| import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons' | ||||
| import { of } from 'rxjs' | ||||
| import { CustomField, CustomFieldDataType } from 'src/app/data/custom-field' | ||||
| import { NotificationService } from 'src/app/services/notification.service' | ||||
| import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service' | ||||
| import { SettingsService } from 'src/app/services/settings.service' | ||||
| import { ToastService } from 'src/app/services/toast.service' | ||||
| import { CustomFieldEditDialogComponent } from '../edit-dialog/custom-field-edit-dialog/custom-field-edit-dialog.component' | ||||
| import { SelectComponent } from '../input/select/select.component' | ||||
| import { CustomFieldsDropdownComponent } from './custom-fields-dropdown.component' | ||||
| @@ -42,7 +42,7 @@ describe('CustomFieldsDropdownComponent', () => { | ||||
|   let component: CustomFieldsDropdownComponent | ||||
|   let fixture: ComponentFixture<CustomFieldsDropdownComponent> | ||||
|   let customFieldService: CustomFieldsService | ||||
|   let toastService: ToastService | ||||
|   let notificationService: NotificationService | ||||
|   let modalService: NgbModal | ||||
|   let settingsService: SettingsService | ||||
|  | ||||
| @@ -64,7 +64,7 @@ describe('CustomFieldsDropdownComponent', () => { | ||||
|       ], | ||||
|     }) | ||||
|     customFieldService = TestBed.inject(CustomFieldsService) | ||||
|     toastService = TestBed.inject(ToastService) | ||||
|     notificationService = TestBed.inject(NotificationService) | ||||
|     modalService = TestBed.inject(NgbModal) | ||||
|     jest.spyOn(customFieldService, 'listAll').mockReturnValue( | ||||
|       of({ | ||||
| @@ -113,8 +113,8 @@ describe('CustomFieldsDropdownComponent', () => { | ||||
|   it('should support creating field, show error if necessary, then add', fakeAsync(() => { | ||||
|     let modal: NgbModalRef | ||||
|     modalService.activeInstances.subscribe((m) => (modal = m[m.length - 1])) | ||||
|     const toastErrorSpy = jest.spyOn(toastService, 'showError') | ||||
|     const toastInfoSpy = jest.spyOn(toastService, 'showInfo') | ||||
|     const notificationErrorSpy = jest.spyOn(notificationService, 'showError') | ||||
|     const notificationInfoSpy = jest.spyOn(notificationService, 'showInfo') | ||||
|     const getFieldsSpy = jest.spyOn( | ||||
|       CustomFieldsDropdownComponent.prototype as any, | ||||
|       'getFields' | ||||
| @@ -129,13 +129,13 @@ describe('CustomFieldsDropdownComponent', () => { | ||||
|  | ||||
|     // fail first | ||||
|     editDialog.failed.emit({ error: 'error creating field' }) | ||||
|     expect(toastErrorSpy).toHaveBeenCalled() | ||||
|     expect(notificationErrorSpy).toHaveBeenCalled() | ||||
|     expect(getFieldsSpy).not.toHaveBeenCalled() | ||||
|  | ||||
|     // succeed | ||||
|     editDialog.succeeded.emit(fields[0]) | ||||
|     tick(100) | ||||
|     expect(toastInfoSpy).toHaveBeenCalled() | ||||
|     expect(notificationInfoSpy).toHaveBeenCalled() | ||||
|     expect(getFieldsSpy).toHaveBeenCalled() | ||||
|     expect(addFieldSpy).toHaveBeenCalled() | ||||
|   })) | ||||
|   | ||||
| @@ -14,13 +14,13 @@ import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons' | ||||
| import { first, takeUntil } from 'rxjs' | ||||
| import { CustomField, DATA_TYPE_LABELS } from 'src/app/data/custom-field' | ||||
| import { CustomFieldInstance } from 'src/app/data/custom-field-instance' | ||||
| import { NotificationService } from 'src/app/services/notification.service' | ||||
| import { | ||||
|   PermissionAction, | ||||
|   PermissionType, | ||||
|   PermissionsService, | ||||
| } from 'src/app/services/permissions.service' | ||||
| import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service' | ||||
| import { ToastService } from 'src/app/services/toast.service' | ||||
| import { LoadingComponentWithPermissions } from '../../loading-component/loading.component' | ||||
| import { CustomFieldEditDialogComponent } from '../edit-dialog/custom-field-edit-dialog/custom-field-edit-dialog.component' | ||||
|  | ||||
| @@ -78,7 +78,7 @@ export class CustomFieldsDropdownComponent extends LoadingComponentWithPermissio | ||||
|   constructor( | ||||
|     private customFieldsService: CustomFieldsService, | ||||
|     private modalService: NgbModal, | ||||
|     private toastService: ToastService, | ||||
|     private notificationService: NotificationService, | ||||
|     private permissionsService: PermissionsService | ||||
|   ) { | ||||
|     super() | ||||
| @@ -123,7 +123,9 @@ export class CustomFieldsDropdownComponent extends LoadingComponentWithPermissio | ||||
|     modal.componentInstance.succeeded | ||||
|       .pipe(takeUntil(this.unsubscribeNotifier)) | ||||
|       .subscribe((newField) => { | ||||
|         this.toastService.showInfo($localize`Saved field "${newField.name}".`) | ||||
|         this.notificationService.showInfo( | ||||
|           $localize`Saved field "${newField.name}".` | ||||
|         ) | ||||
|         this.customFieldsService.clearCache() | ||||
|         this.getFields() | ||||
|         this.created.emit(newField) | ||||
| @@ -132,7 +134,7 @@ export class CustomFieldsDropdownComponent extends LoadingComponentWithPermissio | ||||
|     modal.componentInstance.failed | ||||
|       .pipe(takeUntil(this.unsubscribeNotifier)) | ||||
|       .subscribe((e) => { | ||||
|         this.toastService.showError($localize`Error saving field.`, e) | ||||
|         this.notificationService.showError($localize`Error saving field.`, e) | ||||
|       }) | ||||
|   } | ||||
|  | ||||
|   | ||||
| @@ -12,11 +12,11 @@ import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons' | ||||
| import { of, throwError } from 'rxjs' | ||||
| import { IfOwnerDirective } from 'src/app/directives/if-owner.directive' | ||||
| import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive' | ||||
| import { NotificationService } from 'src/app/services/notification.service' | ||||
| import { PermissionsService } from 'src/app/services/permissions.service' | ||||
| import { GroupService } from 'src/app/services/rest/group.service' | ||||
| import { UserService } from 'src/app/services/rest/user.service' | ||||
| import { SettingsService } from 'src/app/services/settings.service' | ||||
| import { ToastService } from 'src/app/services/toast.service' | ||||
| import { PasswordComponent } from '../../input/password/password.component' | ||||
| import { PermissionsFormComponent } from '../../input/permissions/permissions-form/permissions-form.component' | ||||
| import { SelectComponent } from '../../input/select/select.component' | ||||
| @@ -29,7 +29,7 @@ describe('UserEditDialogComponent', () => { | ||||
|   let component: UserEditDialogComponent | ||||
|   let settingsService: SettingsService | ||||
|   let permissionsService: PermissionsService | ||||
|   let toastService: ToastService | ||||
|   let notificationService: NotificationService | ||||
|   let fixture: ComponentFixture<UserEditDialogComponent> | ||||
|  | ||||
|   beforeEach(async () => { | ||||
| @@ -75,7 +75,7 @@ describe('UserEditDialogComponent', () => { | ||||
|     settingsService = TestBed.inject(SettingsService) | ||||
|     settingsService.currentUser = { id: 99, username: 'user99' } | ||||
|     permissionsService = TestBed.inject(PermissionsService) | ||||
|     toastService = TestBed.inject(ToastService) | ||||
|     notificationService = TestBed.inject(NotificationService) | ||||
|     component = fixture.componentInstance | ||||
|  | ||||
|     fixture.detectChanges() | ||||
| @@ -133,22 +133,22 @@ describe('UserEditDialogComponent', () => { | ||||
|       component['service'] as UserService, | ||||
|       'deactivateTotp' | ||||
|     ) | ||||
|     const toastErrorSpy = jest.spyOn(toastService, 'showError') | ||||
|     const toastInfoSpy = jest.spyOn(toastService, 'showInfo') | ||||
|     const notificationErrorSpy = jest.spyOn(notificationService, 'showError') | ||||
|     const notificationInfoSpy = jest.spyOn(notificationService, 'showInfo') | ||||
|     deactivateSpy.mockReturnValueOnce(throwError(() => new Error('error'))) | ||||
|     component.deactivateTotp() | ||||
|     expect(deactivateSpy).toHaveBeenCalled() | ||||
|     expect(toastErrorSpy).toHaveBeenCalled() | ||||
|     expect(notificationErrorSpy).toHaveBeenCalled() | ||||
|  | ||||
|     deactivateSpy.mockReturnValueOnce(of(false)) | ||||
|     component.deactivateTotp() | ||||
|     expect(deactivateSpy).toHaveBeenCalled() | ||||
|     expect(toastErrorSpy).toHaveBeenCalled() | ||||
|     expect(notificationErrorSpy).toHaveBeenCalled() | ||||
|  | ||||
|     deactivateSpy.mockReturnValueOnce(of(true)) | ||||
|     component.deactivateTotp() | ||||
|     expect(deactivateSpy).toHaveBeenCalled() | ||||
|     expect(toastInfoSpy).toHaveBeenCalled() | ||||
|     expect(notificationInfoSpy).toHaveBeenCalled() | ||||
|   }) | ||||
|  | ||||
|   it('should check superuser status of current user', () => { | ||||
|   | ||||
| @@ -10,11 +10,11 @@ import { first } from 'rxjs' | ||||
| import { EditDialogComponent } from 'src/app/components/common/edit-dialog/edit-dialog.component' | ||||
| import { Group } from 'src/app/data/group' | ||||
| import { User } from 'src/app/data/user' | ||||
| import { NotificationService } from 'src/app/services/notification.service' | ||||
| import { PermissionsService } from 'src/app/services/permissions.service' | ||||
| import { GroupService } from 'src/app/services/rest/group.service' | ||||
| import { UserService } from 'src/app/services/rest/user.service' | ||||
| import { SettingsService } from 'src/app/services/settings.service' | ||||
| import { ToastService } from 'src/app/services/toast.service' | ||||
| import { PasswordComponent } from '../../input/password/password.component' | ||||
| import { SelectComponent } from '../../input/select/select.component' | ||||
| import { TextComponent } from '../../input/text/text.component' | ||||
| @@ -46,7 +46,7 @@ export class UserEditDialogComponent | ||||
|     activeModal: NgbActiveModal, | ||||
|     groupsService: GroupService, | ||||
|     settingsService: SettingsService, | ||||
|     private toastService: ToastService, | ||||
|     private notificationService: NotificationService, | ||||
|     private permissionsService: PermissionsService | ||||
|   ) { | ||||
|     super(service, activeModal, service, settingsService) | ||||
| @@ -128,15 +128,20 @@ export class UserEditDialogComponent | ||||
|         next: (result) => { | ||||
|           this.totpLoading = false | ||||
|           if (result) { | ||||
|             this.toastService.showInfo($localize`Totp deactivated`) | ||||
|             this.notificationService.showInfo($localize`Totp deactivated`) | ||||
|             this.object.is_mfa_enabled = false | ||||
|           } else { | ||||
|             this.toastService.showError($localize`Totp deactivation failed`) | ||||
|             this.notificationService.showError( | ||||
|               $localize`Totp deactivation failed` | ||||
|             ) | ||||
|           } | ||||
|         }, | ||||
|         error: (e) => { | ||||
|           this.totpLoading = false | ||||
|           this.toastService.showError($localize`Totp deactivation failed`, e) | ||||
|           this.notificationService.showError( | ||||
|             $localize`Totp deactivation failed`, | ||||
|             e | ||||
|           ) | ||||
|         }, | ||||
|       }) | ||||
|   } | ||||
|   | ||||
| @@ -6,9 +6,9 @@ import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap' | ||||
| import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons' | ||||
| import { of, throwError } from 'rxjs' | ||||
| import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive' | ||||
| import { NotificationService } from 'src/app/services/notification.service' | ||||
| import { PermissionsService } from 'src/app/services/permissions.service' | ||||
| import { DocumentService } from 'src/app/services/rest/document.service' | ||||
| import { ToastService } from 'src/app/services/toast.service' | ||||
| import { EmailDocumentDialogComponent } from './email-document-dialog.component' | ||||
|  | ||||
| describe('EmailDocumentDialogComponent', () => { | ||||
| @@ -16,7 +16,7 @@ describe('EmailDocumentDialogComponent', () => { | ||||
|   let fixture: ComponentFixture<EmailDocumentDialogComponent> | ||||
|   let documentService: DocumentService | ||||
|   let permissionsService: PermissionsService | ||||
|   let toastService: ToastService | ||||
|   let notificationService: NotificationService | ||||
|  | ||||
|   beforeEach(async () => { | ||||
|     await TestBed.configureTestingModule({ | ||||
| @@ -34,7 +34,7 @@ describe('EmailDocumentDialogComponent', () => { | ||||
|  | ||||
|     fixture = TestBed.createComponent(EmailDocumentDialogComponent) | ||||
|     documentService = TestBed.inject(DocumentService) | ||||
|     toastService = TestBed.inject(ToastService) | ||||
|     notificationService = TestBed.inject(NotificationService) | ||||
|     component = fixture.componentInstance | ||||
|     fixture.detectChanges() | ||||
|   }) | ||||
| @@ -47,8 +47,8 @@ describe('EmailDocumentDialogComponent', () => { | ||||
|   }) | ||||
|  | ||||
|   it('should support sending document via email, showing error if needed', () => { | ||||
|     const toastErrorSpy = jest.spyOn(toastService, 'showError') | ||||
|     const toastSuccessSpy = jest.spyOn(toastService, 'showInfo') | ||||
|     const notificationErrorSpy = jest.spyOn(notificationService, 'showError') | ||||
|     const notificationSuccessSpy = jest.spyOn(notificationService, 'showInfo') | ||||
|     component.emailAddress = 'hello@paperless-ngx.com' | ||||
|     component.emailSubject = 'Hello' | ||||
|     component.emailMessage = 'World' | ||||
| @@ -56,11 +56,11 @@ describe('EmailDocumentDialogComponent', () => { | ||||
|       .spyOn(documentService, 'emailDocument') | ||||
|       .mockReturnValue(throwError(() => new Error('Unable to email document'))) | ||||
|     component.emailDocument() | ||||
|     expect(toastErrorSpy).toHaveBeenCalled() | ||||
|     expect(notificationErrorSpy).toHaveBeenCalled() | ||||
|  | ||||
|     jest.spyOn(documentService, 'emailDocument').mockReturnValue(of(true)) | ||||
|     component.emailDocument() | ||||
|     expect(toastSuccessSpy).toHaveBeenCalled() | ||||
|     expect(notificationSuccessSpy).toHaveBeenCalled() | ||||
|   }) | ||||
|  | ||||
|   it('should close the dialog', () => { | ||||
|   | ||||
| @@ -2,8 +2,8 @@ import { Component, Input } from '@angular/core' | ||||
| import { FormsModule } from '@angular/forms' | ||||
| import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap' | ||||
| import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons' | ||||
| import { NotificationService } from 'src/app/services/notification.service' | ||||
| import { DocumentService } from 'src/app/services/rest/document.service' | ||||
| import { ToastService } from 'src/app/services/toast.service' | ||||
| import { LoadingComponentWithPermissions } from '../../loading-component/loading.component' | ||||
|  | ||||
| @Component({ | ||||
| @@ -40,7 +40,7 @@ export class EmailDocumentDialogComponent extends LoadingComponentWithPermission | ||||
|   constructor( | ||||
|     private activeModal: NgbActiveModal, | ||||
|     private documentService: DocumentService, | ||||
|     private toastService: ToastService | ||||
|     private notificationService: NotificationService | ||||
|   ) { | ||||
|     super() | ||||
|     this.loading = false | ||||
| @@ -62,11 +62,14 @@ export class EmailDocumentDialogComponent extends LoadingComponentWithPermission | ||||
|           this.emailAddress = '' | ||||
|           this.emailSubject = '' | ||||
|           this.emailMessage = '' | ||||
|           this.toastService.showInfo($localize`Email sent`) | ||||
|           this.notificationService.showInfo($localize`Email sent`) | ||||
|         }, | ||||
|         error: (e) => { | ||||
|           this.loading = false | ||||
|           this.toastService.showError($localize`Error emailing document`, e) | ||||
|           this.notificationService.showError( | ||||
|             $localize`Error emailing document`, | ||||
|             e | ||||
|           ) | ||||
|         }, | ||||
|       }) | ||||
|   } | ||||
|   | ||||
| @@ -0,0 +1,3 @@ | ||||
| @for (notification of notifications; track notification.id) { | ||||
|   <pngx-notification [notification]="notification" [autohide]="true" (close)="closeNotification()"></pngx-notification> | ||||
| } | ||||
| @@ -1,7 +1,7 @@ | ||||
| :host { | ||||
|   position: fixed; | ||||
|   top: 0; | ||||
|   right: calc(50% - (var(--pngx-toast-max-width) / 2)); | ||||
|   right: calc(50% - (var(--pngx-notification-max-width) / 2)); | ||||
|   margin: 0.3em; | ||||
|   z-index: 1200; | ||||
| } | ||||
| @@ -0,0 +1,84 @@ | ||||
| import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http' | ||||
| import { provideHttpClientTesting } from '@angular/common/http/testing' | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing' | ||||
| import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons' | ||||
| import { Subject } from 'rxjs' | ||||
| import { | ||||
|   Notification, | ||||
|   NotificationService, | ||||
| } from 'src/app/services/notification.service' | ||||
| import { NotificationListComponent } from './notification-list.component' | ||||
|  | ||||
| const notification = { | ||||
|   content: 'Error 2 content', | ||||
|   delay: 5000, | ||||
|   error: { | ||||
|     url: 'https://example.com', | ||||
|     status: 500, | ||||
|     statusText: 'Internal Server Error', | ||||
|     message: 'Internal server error 500 message', | ||||
|     error: { detail: 'Error 2 message details' }, | ||||
|   }, | ||||
| } | ||||
|  | ||||
| describe('NotificationListComponent', () => { | ||||
|   let component: NotificationListComponent | ||||
|   let fixture: ComponentFixture<NotificationListComponent> | ||||
|   let notificationService: NotificationService | ||||
|   let notificationSubject: Subject<Notification> = new Subject() | ||||
|  | ||||
|   beforeEach(async () => { | ||||
|     TestBed.configureTestingModule({ | ||||
|       imports: [ | ||||
|         NotificationListComponent, | ||||
|         NgxBootstrapIconsModule.pick(allIcons), | ||||
|       ], | ||||
|       providers: [ | ||||
|         provideHttpClient(withInterceptorsFromDi()), | ||||
|         provideHttpClientTesting(), | ||||
|       ], | ||||
|     }).compileComponents() | ||||
|  | ||||
|     fixture = TestBed.createComponent(NotificationListComponent) | ||||
|     notificationService = TestBed.inject(NotificationService) | ||||
|     jest.replaceProperty( | ||||
|       notificationService, | ||||
|       'showNotification', | ||||
|       notificationSubject | ||||
|     ) | ||||
|  | ||||
|     component = fixture.componentInstance | ||||
|  | ||||
|     fixture.detectChanges() | ||||
|   }) | ||||
|  | ||||
|   it('should create', () => { | ||||
|     expect(component).toBeTruthy() | ||||
|   }) | ||||
|  | ||||
|   it('should close notification', () => { | ||||
|     component.notifications = [notification] | ||||
|     const closenotificationSpy = jest.spyOn( | ||||
|       notificationService, | ||||
|       'closeNotification' | ||||
|     ) | ||||
|     component.closeNotification() | ||||
|     expect(component.notifications).toEqual([]) | ||||
|     expect(closenotificationSpy).toHaveBeenCalledWith(notification) | ||||
|   }) | ||||
|  | ||||
|   it('should unsubscribe', () => { | ||||
|     const unsubscribeSpy = jest.spyOn( | ||||
|       (component as any).subscription, | ||||
|       'unsubscribe' | ||||
|     ) | ||||
|     component.ngOnDestroy() | ||||
|     expect(unsubscribeSpy).toHaveBeenCalled() | ||||
|   }) | ||||
|  | ||||
|   it('should subscribe to notificationService', () => { | ||||
|     component.ngOnInit() | ||||
|     notificationSubject.next(notification) | ||||
|     expect(component.notifications).toEqual([notification]) | ||||
|   }) | ||||
| }) | ||||
| @@ -0,0 +1,48 @@ | ||||
| import { Component, OnDestroy, OnInit } from '@angular/core' | ||||
| import { | ||||
|   NgbAccordionModule, | ||||
|   NgbProgressbarModule, | ||||
| } from '@ng-bootstrap/ng-bootstrap' | ||||
| import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons' | ||||
| import { Subscription } from 'rxjs' | ||||
| import { | ||||
|   Notification, | ||||
|   NotificationService, | ||||
| } from 'src/app/services/notification.service' | ||||
| import { NotificationComponent } from '../notification/notification.component' | ||||
|  | ||||
| @Component({ | ||||
|   selector: 'pngx-notification-list', | ||||
|   templateUrl: './notification-list.component.html', | ||||
|   styleUrls: ['./notification-list.component.scss'], | ||||
|   imports: [ | ||||
|     NotificationComponent, | ||||
|     NgbAccordionModule, | ||||
|     NgbProgressbarModule, | ||||
|     NgxBootstrapIconsModule, | ||||
|   ], | ||||
| }) | ||||
| export class NotificationListComponent implements OnInit, OnDestroy { | ||||
|   constructor(public notificationService: NotificationService) {} | ||||
|  | ||||
|   private subscription: Subscription | ||||
|  | ||||
|   public notifications: Notification[] = [] // array to force change detection | ||||
|  | ||||
|   ngOnDestroy(): void { | ||||
|     this.subscription?.unsubscribe() | ||||
|   } | ||||
|  | ||||
|   ngOnInit(): void { | ||||
|     this.subscription = this.notificationService.showNotification.subscribe( | ||||
|       (notification) => { | ||||
|         this.notifications = notification ? [notification] : [] | ||||
|       } | ||||
|     ) | ||||
|   } | ||||
|  | ||||
|   closeNotification() { | ||||
|     this.notificationService.closeNotification(this.notifications[0]) | ||||
|     this.notifications = [] | ||||
|   } | ||||
| } | ||||
| @@ -1,39 +1,39 @@ | ||||
| <ngb-toast | ||||
|     [autohide]="autohide" | ||||
|     [delay]="toast.delay" | ||||
|     [class]="toast.classname" | ||||
|     [delay]="notification.delay" | ||||
|     [class]="notification.classname" | ||||
|     [class.mb-2]="true" | ||||
|     (shown)="onShown(toast)" | ||||
|     (hidden)="hidden.emit(toast)"> | ||||
|     (shown)="onShown(notification)" | ||||
|     (hidden)="hidden.emit(notification)"> | ||||
|         @if (autohide) { | ||||
|             <ngb-progressbar class="position-absolute h-100 w-100 top-90 start-0 bottom-0 end-0 pe-none" type="dark" [max]="toast.delay" [value]="toast.delayRemaining"></ngb-progressbar> | ||||
|             <span class="visually-hidden">{{ toast.delayRemaining / 1000 | number: '1.0-0' }} seconds</span> | ||||
|             <ngb-progressbar class="position-absolute h-100 w-100 top-90 start-0 bottom-0 end-0 pe-none" type="dark" [max]="notification.delay" [value]="notification.delayRemaining"></ngb-progressbar> | ||||
|             <span class="visually-hidden">{{ notification.delayRemaining / 1000 | number: '1.0-0' }} seconds</span> | ||||
|         } | ||||
|         <div class="d-flex align-items-top"> | ||||
|         @if (!toast.error) { | ||||
|         @if (!notification.error) { | ||||
|             <i-bs width="0.9em" height="0.9em" name="info-circle"></i-bs> | ||||
|         } | ||||
|         @if (toast.error) { | ||||
|         @if (notification.error) { | ||||
|             <i-bs width="0.9em" height="0.9em" name="exclamation-triangle"></i-bs> | ||||
|         } | ||||
|         <div> | ||||
|             <p class="ms-2 mb-0">{{toast.content}}</p> | ||||
|             @if (toast.error) { | ||||
|             <p class="ms-2 mb-0">{{notification.content}}</p> | ||||
|             @if (notification.error) { | ||||
|             <details class="ms-2"> | ||||
|                 <div class="mt-2 ms-n4 me-n2 small"> | ||||
|                 @if (isDetailedError(toast.error)) { | ||||
|                 @if (isDetailedError(notification.error)) { | ||||
|                     <dl class="row mb-0"> | ||||
|                     <dt class="col-sm-3 fw-normal text-end">URL</dt> | ||||
|                     <dd class="col-sm-9">{{ toast.error.url }}</dd> | ||||
|                     <dd class="col-sm-9">{{ notification.error.url }}</dd> | ||||
|                     <dt class="col-sm-3 fw-normal text-end" i18n>Status</dt> | ||||
|                     <dd class="col-sm-9">{{ toast.error.status }} <em>{{ toast.error.statusText }}</em></dd> | ||||
|                     <dd class="col-sm-9">{{ notification.error.status }} <em>{{ notification.error.statusText }}</em></dd> | ||||
|                     <dt class="col-sm-3 fw-normal text-end" i18n>Error</dt> | ||||
|                     <dd class="col-sm-9">{{ getErrorText(toast.error) }}</dd> | ||||
|                     <dd class="col-sm-9">{{ getErrorText(notification.error) }}</dd> | ||||
|                     </dl> | ||||
|                 } | ||||
|                 <div class="row"> | ||||
|                     <div class="col offset-sm-3"> | ||||
|                     <button class="btn btn-sm btn-outline-secondary" (click)="copyError(toast.error)"> | ||||
|                     <button class="btn btn-sm btn-outline-secondary" (click)="copyError(notification.error)"> | ||||
|                         @if (!copied) { | ||||
|                         <i-bs name="clipboard"></i-bs>  | ||||
|                         } | ||||
| @@ -47,10 +47,10 @@ | ||||
|                 </div> | ||||
|             </details> | ||||
|             } | ||||
|             @if (toast.action) { | ||||
|             <p class="mb-0 mt-2"><button class="btn btn-sm btn-outline-secondary" (click)="close.emit(toast); toast.action()">{{toast.actionName}}</button></p> | ||||
|             @if (notification.action) { | ||||
|             <p class="mb-0 mt-2"><button class="btn btn-sm btn-outline-secondary" (click)="close.emit(notification); notification.action()">{{notification.actionName}}</button></p> | ||||
|             } | ||||
|         </div> | ||||
|         <button type="button" class="btn-close ms-auto flex-shrink-0" data-bs-dismiss="toast" aria-label="Close" (click)="close.emit(toast);"></button> | ||||
|         <button type="button" class="btn-close ms-auto flex-shrink-0" data-bs-dismiss="notification" aria-label="Close" (click)="close.emit(notification);"></button> | ||||
|     </div> | ||||
| </ngb-toast> | ||||
| @@ -9,15 +9,15 @@ import { | ||||
| 
 | ||||
| import { Clipboard } from '@angular/cdk/clipboard' | ||||
| import { allIcons, NgxBootstrapIconsModule } from 'ngx-bootstrap-icons' | ||||
| import { ToastComponent } from './toast.component' | ||||
| import { NotificationComponent } from './notification.component' | ||||
| 
 | ||||
| const toast1 = { | ||||
| const notification1 = { | ||||
|   content: 'Error 1 content', | ||||
|   delay: 5000, | ||||
|   error: 'Error 1 string', | ||||
| } | ||||
| 
 | ||||
| const toast2 = { | ||||
| const notification2 = { | ||||
|   content: 'Error 2 content', | ||||
|   delay: 5000, | ||||
|   error: { | ||||
| @@ -29,17 +29,17 @@ const toast2 = { | ||||
|   }, | ||||
| } | ||||
| 
 | ||||
| describe('ToastComponent', () => { | ||||
|   let component: ToastComponent | ||||
|   let fixture: ComponentFixture<ToastComponent> | ||||
| describe('NotificationComponent', () => { | ||||
|   let component: NotificationComponent | ||||
|   let fixture: ComponentFixture<NotificationComponent> | ||||
|   let clipboard: Clipboard | ||||
| 
 | ||||
|   beforeEach(async () => { | ||||
|     await TestBed.configureTestingModule({ | ||||
|       imports: [ToastComponent, NgxBootstrapIconsModule.pick(allIcons)], | ||||
|       imports: [NotificationComponent, NgxBootstrapIconsModule.pick(allIcons)], | ||||
|     }).compileComponents() | ||||
| 
 | ||||
|     fixture = TestBed.createComponent(ToastComponent) | ||||
|     fixture = TestBed.createComponent(NotificationComponent) | ||||
|     clipboard = TestBed.inject(Clipboard) | ||||
|     component = fixture.componentInstance | ||||
|   }) | ||||
| @@ -48,18 +48,18 @@ describe('ToastComponent', () => { | ||||
|     expect(component).toBeTruthy() | ||||
|   }) | ||||
| 
 | ||||
|   it('should countdown toast', fakeAsync(() => { | ||||
|     component.toast = toast2 | ||||
|   it('should countdown notification', fakeAsync(() => { | ||||
|     component.notification = notification2 | ||||
|     fixture.detectChanges() | ||||
|     component.onShown(toast2) | ||||
|     component.onShown(notification2) | ||||
|     tick(5000) | ||||
|     expect(component.toast.delayRemaining).toEqual(0) | ||||
|     expect(component.notification.delayRemaining).toEqual(0) | ||||
|     flush() | ||||
|     discardPeriodicTasks() | ||||
|   })) | ||||
| 
 | ||||
|   it('should show an error if given with toast', fakeAsync(() => { | ||||
|     component.toast = toast1 | ||||
|   it('should show an error if given with notification', fakeAsync(() => { | ||||
|     component.notification = notification1 | ||||
|     fixture.detectChanges() | ||||
| 
 | ||||
|     expect(fixture.nativeElement.querySelector('details')).not.toBeNull() | ||||
| @@ -70,7 +70,7 @@ describe('ToastComponent', () => { | ||||
|   })) | ||||
| 
 | ||||
|   it('should show error details, support copy', fakeAsync(() => { | ||||
|     component.toast = toast2 | ||||
|     component.notification = notification2 | ||||
|     fixture.detectChanges() | ||||
| 
 | ||||
|     expect(fixture.nativeElement.querySelector('details')).not.toBeNull() | ||||
| @@ -79,7 +79,7 @@ describe('ToastComponent', () => { | ||||
|     ) | ||||
| 
 | ||||
|     const copySpy = jest.spyOn(clipboard, 'copy') | ||||
|     component.copyError(toast2.error) | ||||
|     component.copyError(notification2.error) | ||||
|     expect(copySpy).toHaveBeenCalled() | ||||
| 
 | ||||
|     flush() | ||||
| @@ -87,7 +87,7 @@ describe('ToastComponent', () => { | ||||
|   })) | ||||
| 
 | ||||
|   it('should parse error text, add ellipsis', () => { | ||||
|     expect(component.getErrorText(toast2.error)).toEqual( | ||||
|     expect(component.getErrorText(notification2.error)).toEqual( | ||||
|       'Error 2 message details' | ||||
|     ) | ||||
|     expect(component.getErrorText({ error: 'Error string no detail' })).toEqual( | ||||
| @@ -7,42 +7,43 @@ import { | ||||
| } from '@ng-bootstrap/ng-bootstrap' | ||||
| import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons' | ||||
| import { interval, take } from 'rxjs' | ||||
| import { Toast } from 'src/app/services/toast.service' | ||||
| import { Notification } from 'src/app/services/notification.service' | ||||
| 
 | ||||
| @Component({ | ||||
|   selector: 'pngx-toast', | ||||
|   selector: 'pngx-notification', | ||||
|   imports: [ | ||||
|     DecimalPipe, | ||||
|     NgbToastModule, | ||||
|     NgbProgressbarModule, | ||||
|     NgxBootstrapIconsModule, | ||||
|   ], | ||||
|   templateUrl: './toast.component.html', | ||||
|   styleUrl: './toast.component.scss', | ||||
|   templateUrl: './notification.component.html', | ||||
|   styleUrl: './notification.component.scss', | ||||
| }) | ||||
| export class ToastComponent { | ||||
|   @Input() toast: Toast | ||||
| export class NotificationComponent { | ||||
|   @Input() notification: Notification | ||||
| 
 | ||||
|   @Input() autohide: boolean = true | ||||
| 
 | ||||
|   @Output() hidden: EventEmitter<Toast> = new EventEmitter<Toast>() | ||||
|   @Output() hidden: EventEmitter<Notification> = | ||||
|     new EventEmitter<Notification>() | ||||
| 
 | ||||
|   @Output() close: EventEmitter<Toast> = new EventEmitter<Toast>() | ||||
|   @Output() close: EventEmitter<Notification> = new EventEmitter<Notification>() | ||||
| 
 | ||||
|   public copied: boolean = false | ||||
| 
 | ||||
|   constructor(private clipboard: Clipboard) {} | ||||
| 
 | ||||
|   onShown(toast: Toast) { | ||||
|   onShown(notification: Notification) { | ||||
|     if (!this.autohide) return | ||||
| 
 | ||||
|     const refreshInterval = 150 | ||||
|     const delay = toast.delay - 500 // for fade animation
 | ||||
|     const delay = notification.delay - 500 // for fade animation
 | ||||
| 
 | ||||
|     interval(refreshInterval) | ||||
|       .pipe(take(Math.round(delay / refreshInterval))) | ||||
|       .subscribe((count) => { | ||||
|         toast.delayRemaining = Math.max( | ||||
|         notification.delayRemaining = Math.max( | ||||
|           0, | ||||
|           delay - refreshInterval * (count + 1) | ||||
|         ) | ||||
| @@ -16,8 +16,8 @@ import { | ||||
| } from '@ng-bootstrap/ng-bootstrap' | ||||
| import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons' | ||||
| import { of, throwError } from 'rxjs' | ||||
| import { NotificationService } from 'src/app/services/notification.service' | ||||
| import { ProfileService } from 'src/app/services/profile.service' | ||||
| import { ToastService } from 'src/app/services/toast.service' | ||||
| import { ConfirmButtonComponent } from '../confirm-button/confirm-button.component' | ||||
| import { PasswordComponent } from '../input/password/password.component' | ||||
| import { TextComponent } from '../input/text/text.component' | ||||
| @@ -44,7 +44,7 @@ describe('ProfileEditDialogComponent', () => { | ||||
|   let component: ProfileEditDialogComponent | ||||
|   let fixture: ComponentFixture<ProfileEditDialogComponent> | ||||
|   let profileService: ProfileService | ||||
|   let toastService: ToastService | ||||
|   let notificationService: NotificationService | ||||
|   let clipboard: Clipboard | ||||
|  | ||||
|   beforeEach(() => { | ||||
| @@ -64,7 +64,7 @@ describe('ProfileEditDialogComponent', () => { | ||||
|       providers: [NgbActiveModal, provideHttpClient(withInterceptorsFromDi())], | ||||
|     }) | ||||
|     profileService = TestBed.inject(ProfileService) | ||||
|     toastService = TestBed.inject(ToastService) | ||||
|     notificationService = TestBed.inject(NotificationService) | ||||
|     clipboard = TestBed.inject(Clipboard) | ||||
|     fixture = TestBed.createComponent(ProfileEditDialogComponent) | ||||
|     component = fixture.componentInstance | ||||
| @@ -94,13 +94,13 @@ describe('ProfileEditDialogComponent', () => { | ||||
|       auth_token: profile.auth_token, | ||||
|     } | ||||
|     const updateSpy = jest.spyOn(profileService, 'update') | ||||
|     const errorSpy = jest.spyOn(toastService, 'showError') | ||||
|     const errorSpy = jest.spyOn(notificationService, 'showError') | ||||
|     updateSpy.mockReturnValueOnce(throwError(() => new Error('failed to save'))) | ||||
|     component.save() | ||||
|     expect(errorSpy).toHaveBeenCalled() | ||||
|  | ||||
|     updateSpy.mockClear() | ||||
|     const infoSpy = jest.spyOn(toastService, 'showInfo') | ||||
|     const infoSpy = jest.spyOn(notificationService, 'showInfo') | ||||
|     component.form.patchValue(newProfile) | ||||
|     updateSpy.mockReturnValueOnce(of(newProfile)) | ||||
|     component.save() | ||||
| @@ -239,7 +239,7 @@ describe('ProfileEditDialogComponent', () => { | ||||
|     getSpy.mockReturnValue(of(profile)) | ||||
|  | ||||
|     const generateSpy = jest.spyOn(profileService, 'generateAuthToken') | ||||
|     const errorSpy = jest.spyOn(toastService, 'showError') | ||||
|     const errorSpy = jest.spyOn(notificationService, 'showError') | ||||
|     generateSpy.mockReturnValueOnce( | ||||
|       throwError(() => new Error('failed to generate')) | ||||
|     ) | ||||
| @@ -275,7 +275,7 @@ describe('ProfileEditDialogComponent', () => { | ||||
|     getSpy.mockImplementation(() => of(profile)) | ||||
|     component.ngOnInit() | ||||
|  | ||||
|     const errorSpy = jest.spyOn(toastService, 'showError') | ||||
|     const errorSpy = jest.spyOn(notificationService, 'showError') | ||||
|  | ||||
|     expect(component.socialAccounts).toContainEqual(socialAccount) | ||||
|  | ||||
| @@ -300,13 +300,13 @@ describe('ProfileEditDialogComponent', () => { | ||||
|       secret: 'secret', | ||||
|     } | ||||
|     const getSpy = jest.spyOn(profileService, 'getTotpSettings') | ||||
|     const toastSpy = jest.spyOn(toastService, 'showError') | ||||
|     const notificationSpy = jest.spyOn(notificationService, 'showError') | ||||
|     getSpy.mockReturnValueOnce( | ||||
|       throwError(() => new Error('failed to get settings')) | ||||
|     ) | ||||
|     component.gettotpSettings() | ||||
|     expect(getSpy).toHaveBeenCalled() | ||||
|     expect(toastSpy).toHaveBeenCalled() | ||||
|     expect(notificationSpy).toHaveBeenCalled() | ||||
|  | ||||
|     getSpy.mockReturnValue(of(settings)) | ||||
|     component.gettotpSettings() | ||||
| @@ -316,8 +316,8 @@ describe('ProfileEditDialogComponent', () => { | ||||
|  | ||||
|   it('should activate totp', () => { | ||||
|     const activateSpy = jest.spyOn(profileService, 'activateTotp') | ||||
|     const toastErrorSpy = jest.spyOn(toastService, 'showError') | ||||
|     const toastInfoSpy = jest.spyOn(toastService, 'showInfo') | ||||
|     const notificationErrorSpy = jest.spyOn(notificationService, 'showError') | ||||
|     const notificationInfoSpy = jest.spyOn(notificationService, 'showInfo') | ||||
|     const error = new Error('failed to activate totp') | ||||
|     activateSpy.mockReturnValueOnce(throwError(() => error)) | ||||
|     component.totpSettings = { | ||||
| @@ -331,38 +331,44 @@ describe('ProfileEditDialogComponent', () => { | ||||
|       component.totpSettings.secret, | ||||
|       component.form.get('totp_code').value | ||||
|     ) | ||||
|     expect(toastErrorSpy).toHaveBeenCalled() | ||||
|     expect(notificationErrorSpy).toHaveBeenCalled() | ||||
|  | ||||
|     activateSpy.mockReturnValueOnce(of({ success: false, recovery_codes: [] })) | ||||
|     component.activateTotp() | ||||
|     expect(toastErrorSpy).toHaveBeenCalledWith('Error activating TOTP', error) | ||||
|     expect(notificationErrorSpy).toHaveBeenCalledWith( | ||||
|       'Error activating TOTP', | ||||
|       error | ||||
|     ) | ||||
|  | ||||
|     activateSpy.mockReturnValueOnce( | ||||
|       of({ success: true, recovery_codes: ['1', '2', '3'] }) | ||||
|     ) | ||||
|     component.activateTotp() | ||||
|     expect(toastInfoSpy).toHaveBeenCalled() | ||||
|     expect(notificationInfoSpy).toHaveBeenCalled() | ||||
|     expect(component.isTotpEnabled).toBeTruthy() | ||||
|     expect(component.recoveryCodes).toEqual(['1', '2', '3']) | ||||
|   }) | ||||
|  | ||||
|   it('should deactivate totp', () => { | ||||
|     const deactivateSpy = jest.spyOn(profileService, 'deactivateTotp') | ||||
|     const toastErrorSpy = jest.spyOn(toastService, 'showError') | ||||
|     const toastInfoSpy = jest.spyOn(toastService, 'showInfo') | ||||
|     const notificationErrorSpy = jest.spyOn(notificationService, 'showError') | ||||
|     const notificationInfoSpy = jest.spyOn(notificationService, 'showInfo') | ||||
|     const error = new Error('failed to deactivate totp') | ||||
|     deactivateSpy.mockReturnValueOnce(throwError(() => error)) | ||||
|     component.deactivateTotp() | ||||
|     expect(deactivateSpy).toHaveBeenCalled() | ||||
|     expect(toastErrorSpy).toHaveBeenCalled() | ||||
|     expect(notificationErrorSpy).toHaveBeenCalled() | ||||
|  | ||||
|     deactivateSpy.mockReturnValueOnce(of(false)) | ||||
|     component.deactivateTotp() | ||||
|     expect(toastErrorSpy).toHaveBeenCalledWith('Error deactivating TOTP', error) | ||||
|     expect(notificationErrorSpy).toHaveBeenCalledWith( | ||||
|       'Error deactivating TOTP', | ||||
|       error | ||||
|     ) | ||||
|  | ||||
|     deactivateSpy.mockReturnValueOnce(of(true)) | ||||
|     component.deactivateTotp() | ||||
|     expect(toastInfoSpy).toHaveBeenCalled() | ||||
|     expect(notificationInfoSpy).toHaveBeenCalled() | ||||
|     expect(component.isTotpEnabled).toBeFalsy() | ||||
|   }) | ||||
|  | ||||
|   | ||||
| @@ -19,8 +19,8 @@ import { | ||||
|   TotpSettings, | ||||
| } from 'src/app/data/user-profile' | ||||
| import { SafeHtmlPipe } from 'src/app/pipes/safehtml.pipe' | ||||
| import { NotificationService } from 'src/app/services/notification.service' | ||||
| import { ProfileService } from 'src/app/services/profile.service' | ||||
| import { ToastService } from 'src/app/services/toast.service' | ||||
| import { LoadingComponentWithPermissions } from '../../loading-component/loading.component' | ||||
| import { ConfirmButtonComponent } from '../confirm-button/confirm-button.component' | ||||
| import { PasswordComponent } from '../input/password/password.component' | ||||
| @@ -86,7 +86,7 @@ export class ProfileEditDialogComponent | ||||
|   constructor( | ||||
|     private profileService: ProfileService, | ||||
|     public activeModal: NgbActiveModal, | ||||
|     private toastService: ToastService, | ||||
|     private notificationService: NotificationService, | ||||
|     private clipboard: Clipboard | ||||
|   ) { | ||||
|     super() | ||||
| @@ -192,9 +192,11 @@ export class ProfileEditDialogComponent | ||||
|       .pipe(takeUntil(this.unsubscribeNotifier)) | ||||
|       .subscribe({ | ||||
|         next: () => { | ||||
|           this.toastService.showInfo($localize`Profile updated successfully`) | ||||
|           this.notificationService.showInfo( | ||||
|             $localize`Profile updated successfully` | ||||
|           ) | ||||
|           if (passwordChanged) { | ||||
|             this.toastService.showInfo( | ||||
|             this.notificationService.showInfo( | ||||
|               $localize`Password has been changed, you will be logged out momentarily.` | ||||
|             ) | ||||
|             setTimeout(() => { | ||||
| @@ -204,7 +206,10 @@ export class ProfileEditDialogComponent | ||||
|           this.activeModal.close() | ||||
|         }, | ||||
|         error: (error) => { | ||||
|           this.toastService.showError($localize`Error saving profile`, error) | ||||
|           this.notificationService.showError( | ||||
|             $localize`Error saving profile`, | ||||
|             error | ||||
|           ) | ||||
|           this.networkActive = false | ||||
|         }, | ||||
|       }) | ||||
| @@ -220,7 +225,7 @@ export class ProfileEditDialogComponent | ||||
|         this.form.patchValue({ auth_token: token }) | ||||
|       }, | ||||
|       error: (error) => { | ||||
|         this.toastService.showError( | ||||
|         this.notificationService.showError( | ||||
|           $localize`Error generating auth token`, | ||||
|           error | ||||
|         ) | ||||
| @@ -245,7 +250,7 @@ export class ProfileEditDialogComponent | ||||
|           this.socialAccounts = this.socialAccounts.filter((a) => a.id != id) | ||||
|         }, | ||||
|         error: (error) => { | ||||
|           this.toastService.showError( | ||||
|           this.notificationService.showError( | ||||
|             $localize`Error disconnecting social account`, | ||||
|             error | ||||
|           ) | ||||
| @@ -264,7 +269,7 @@ export class ProfileEditDialogComponent | ||||
|           this.totpSettings = totpSettings | ||||
|         }, | ||||
|         error: (error) => { | ||||
|           this.toastService.showError( | ||||
|           this.notificationService.showError( | ||||
|             $localize`Error fetching TOTP settings`, | ||||
|             error | ||||
|           ) | ||||
| @@ -286,15 +291,20 @@ export class ProfileEditDialogComponent | ||||
|           this.recoveryCodes = activationResponse.recovery_codes | ||||
|           this.form.get('totp_code').enable() | ||||
|           if (activationResponse.success) { | ||||
|             this.toastService.showInfo($localize`TOTP activated successfully`) | ||||
|             this.notificationService.showInfo( | ||||
|               $localize`TOTP activated successfully` | ||||
|             ) | ||||
|           } else { | ||||
|             this.toastService.showError($localize`Error activating TOTP`) | ||||
|             this.notificationService.showError($localize`Error activating TOTP`) | ||||
|           } | ||||
|         }, | ||||
|         error: (error) => { | ||||
|           this.totpLoading = false | ||||
|           this.form.get('totp_code').enable() | ||||
|           this.toastService.showError($localize`Error activating TOTP`, error) | ||||
|           this.notificationService.showError( | ||||
|             $localize`Error activating TOTP`, | ||||
|             error | ||||
|           ) | ||||
|         }, | ||||
|       }) | ||||
|   } | ||||
| @@ -310,14 +320,21 @@ export class ProfileEditDialogComponent | ||||
|           this.isTotpEnabled = !success | ||||
|           this.recoveryCodes = null | ||||
|           if (success) { | ||||
|             this.toastService.showInfo($localize`TOTP deactivated successfully`) | ||||
|             this.notificationService.showInfo( | ||||
|               $localize`TOTP deactivated successfully` | ||||
|             ) | ||||
|           } else { | ||||
|             this.toastService.showError($localize`Error deactivating TOTP`) | ||||
|             this.notificationService.showError( | ||||
|               $localize`Error deactivating TOTP` | ||||
|             ) | ||||
|           } | ||||
|         }, | ||||
|         error: (error) => { | ||||
|           this.totpLoading = false | ||||
|           this.toastService.showError($localize`Error deactivating TOTP`, error) | ||||
|           this.notificationService.showError( | ||||
|             $localize`Error deactivating TOTP`, | ||||
|             error | ||||
|           ) | ||||
|         }, | ||||
|       }) | ||||
|   } | ||||
|   | ||||
| @@ -15,8 +15,8 @@ import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap' | ||||
| import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons' | ||||
| import { of, throwError } from 'rxjs' | ||||
| import { FileVersion, ShareLink } from 'src/app/data/share-link' | ||||
| import { NotificationService } from 'src/app/services/notification.service' | ||||
| import { ShareLinkService } from 'src/app/services/rest/share-link.service' | ||||
| import { ToastService } from 'src/app/services/toast.service' | ||||
| import { environment } from 'src/environments/environment' | ||||
| import { ShareLinksDialogComponent } from './share-links-dialog.component' | ||||
|  | ||||
| @@ -24,7 +24,7 @@ describe('ShareLinksDialogComponent', () => { | ||||
|   let component: ShareLinksDialogComponent | ||||
|   let fixture: ComponentFixture<ShareLinksDialogComponent> | ||||
|   let shareLinkService: ShareLinkService | ||||
|   let toastService: ToastService | ||||
|   let notificationService: NotificationService | ||||
|   let httpController: HttpTestingController | ||||
|   let clipboard: Clipboard | ||||
|  | ||||
| @@ -43,7 +43,7 @@ describe('ShareLinksDialogComponent', () => { | ||||
|  | ||||
|     fixture = TestBed.createComponent(ShareLinksDialogComponent) | ||||
|     shareLinkService = TestBed.inject(ShareLinkService) | ||||
|     toastService = TestBed.inject(ToastService) | ||||
|     notificationService = TestBed.inject(NotificationService) | ||||
|     httpController = TestBed.inject(HttpTestingController) | ||||
|     clipboard = TestBed.inject(Clipboard) | ||||
|  | ||||
| @@ -89,7 +89,7 @@ describe('ShareLinksDialogComponent', () => { | ||||
|   }) | ||||
|  | ||||
|   it('should show error on refresh if needed', () => { | ||||
|     const toastSpy = jest.spyOn(toastService, 'showError') | ||||
|     const notificationSpy = jest.spyOn(notificationService, 'showError') | ||||
|     jest | ||||
|       .spyOn(shareLinkService, 'getLinksForDocument') | ||||
|       .mockReturnValueOnce(throwError(() => new Error('Unable to get links'))) | ||||
| @@ -97,7 +97,7 @@ describe('ShareLinksDialogComponent', () => { | ||||
|  | ||||
|     component.ngOnInit() | ||||
|     fixture.detectChanges() | ||||
|     expect(toastSpy).toHaveBeenCalled() | ||||
|     expect(notificationSpy).toHaveBeenCalled() | ||||
|   }) | ||||
|  | ||||
|   it('should support link creation then refresh & copy url', fakeAsync(() => { | ||||
| @@ -138,7 +138,7 @@ describe('ShareLinksDialogComponent', () => { | ||||
|     const expiration = new Date() | ||||
|     expiration.setDate(expiration.getDate() + 7) | ||||
|  | ||||
|     const toastSpy = jest.spyOn(toastService, 'showError') | ||||
|     const notificationSpy = jest.spyOn(notificationService, 'showError') | ||||
|  | ||||
|     component.createLink() | ||||
|  | ||||
| @@ -150,7 +150,7 @@ describe('ShareLinksDialogComponent', () => { | ||||
|       ) | ||||
|     fixture.detectChanges() | ||||
|  | ||||
|     expect(toastSpy).toHaveBeenCalled() | ||||
|     expect(notificationSpy).toHaveBeenCalled() | ||||
|   }) | ||||
|  | ||||
|   it('should support delete links & refresh', () => { | ||||
| @@ -165,13 +165,13 @@ describe('ShareLinksDialogComponent', () => { | ||||
|   }) | ||||
|  | ||||
|   it('should show error on delete if needed', () => { | ||||
|     const toastSpy = jest.spyOn(toastService, 'showError') | ||||
|     const notificationSpy = jest.spyOn(notificationService, 'showError') | ||||
|     jest | ||||
|       .spyOn(shareLinkService, 'delete') | ||||
|       .mockReturnValueOnce(throwError(() => new Error('Unable to delete link'))) | ||||
|     component.delete(null) | ||||
|     fixture.detectChanges() | ||||
|     expect(toastSpy).toHaveBeenCalled() | ||||
|     expect(notificationSpy).toHaveBeenCalled() | ||||
|   }) | ||||
|  | ||||
|   it('should format days remaining', () => { | ||||
|   | ||||
| @@ -5,8 +5,8 @@ import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap' | ||||
| import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons' | ||||
| import { first } from 'rxjs' | ||||
| import { FileVersion, ShareLink } from 'src/app/data/share-link' | ||||
| import { NotificationService } from 'src/app/services/notification.service' | ||||
| import { ShareLinkService } from 'src/app/services/rest/share-link.service' | ||||
| import { ToastService } from 'src/app/services/toast.service' | ||||
| import { environment } from 'src/environments/environment' | ||||
|  | ||||
| @Component({ | ||||
| @@ -61,7 +61,7 @@ export class ShareLinksDialogComponent implements OnInit { | ||||
|   constructor( | ||||
|     private activeModal: NgbActiveModal, | ||||
|     private shareLinkService: ShareLinkService, | ||||
|     private toastService: ToastService, | ||||
|     private notificationService: NotificationService, | ||||
|     private clipboard: Clipboard | ||||
|   ) {} | ||||
|  | ||||
| @@ -81,7 +81,7 @@ export class ShareLinksDialogComponent implements OnInit { | ||||
|           this.shareLinks = results | ||||
|         }, | ||||
|         error: (e) => { | ||||
|           this.toastService.showError( | ||||
|           this.notificationService.showError( | ||||
|             $localize`Error retrieving links`, | ||||
|             10000, | ||||
|             e | ||||
| @@ -130,7 +130,11 @@ export class ShareLinksDialogComponent implements OnInit { | ||||
|         this.refresh() | ||||
|       }, | ||||
|       error: (e) => { | ||||
|         this.toastService.showError($localize`Error deleting link`, 10000, e) | ||||
|         this.notificationService.showError( | ||||
|           $localize`Error deleting link`, | ||||
|           10000, | ||||
|           e | ||||
|         ) | ||||
|       }, | ||||
|     }) | ||||
|   } | ||||
| @@ -158,7 +162,11 @@ export class ShareLinksDialogComponent implements OnInit { | ||||
|         }, | ||||
|         error: (e) => { | ||||
|           this.loading = false | ||||
|           this.toastService.showError($localize`Error creating link`, 10000, e) | ||||
|           this.notificationService.showError( | ||||
|             $localize`Error creating link`, | ||||
|             10000, | ||||
|             e | ||||
|           ) | ||||
|         }, | ||||
|       }) | ||||
|   } | ||||
|   | ||||
| @@ -16,9 +16,9 @@ import { | ||||
|   SystemStatus, | ||||
|   SystemStatusItemStatus, | ||||
| } from 'src/app/data/system-status' | ||||
| import { NotificationService } from 'src/app/services/notification.service' | ||||
| import { SystemStatusService } from 'src/app/services/system-status.service' | ||||
| import { TasksService } from 'src/app/services/tasks.service' | ||||
| import { ToastService } from 'src/app/services/toast.service' | ||||
| import { SystemStatusDialogComponent } from './system-status-dialog.component' | ||||
|  | ||||
| const status: SystemStatus = { | ||||
| @@ -61,7 +61,7 @@ describe('SystemStatusDialogComponent', () => { | ||||
|   let clipboard: Clipboard | ||||
|   let tasksService: TasksService | ||||
|   let systemStatusService: SystemStatusService | ||||
|   let toastService: ToastService | ||||
|   let notificationService: NotificationService | ||||
|  | ||||
|   beforeEach(async () => { | ||||
|     await TestBed.configureTestingModule({ | ||||
| @@ -82,7 +82,7 @@ describe('SystemStatusDialogComponent', () => { | ||||
|     clipboard = TestBed.inject(Clipboard) | ||||
|     tasksService = TestBed.inject(TasksService) | ||||
|     systemStatusService = TestBed.inject(SystemStatusService) | ||||
|     toastService = TestBed.inject(ToastService) | ||||
|     notificationService = TestBed.inject(NotificationService) | ||||
|     fixture.detectChanges() | ||||
|   }) | ||||
|  | ||||
| @@ -116,9 +116,9 @@ describe('SystemStatusDialogComponent', () => { | ||||
|     expect(component.isRunning(PaperlessTaskName.SanityCheck)).toBeFalsy() | ||||
|   }) | ||||
|  | ||||
|   it('should support running tasks, refresh status and show toasts', () => { | ||||
|     const toastSpy = jest.spyOn(toastService, 'showInfo') | ||||
|     const toastErrorSpy = jest.spyOn(toastService, 'showError') | ||||
|   it('should support running tasks, refresh status and show notifications', () => { | ||||
|     const notificationSpy = jest.spyOn(notificationService, 'showInfo') | ||||
|     const notificationErrorSpy = jest.spyOn(notificationService, 'showError') | ||||
|     const getStatusSpy = jest.spyOn(systemStatusService, 'get') | ||||
|     const runSpy = jest.spyOn(tasksService, 'run') | ||||
|  | ||||
| @@ -126,7 +126,7 @@ describe('SystemStatusDialogComponent', () => { | ||||
|     runSpy.mockReturnValue(throwError(() => new Error('error'))) | ||||
|     component.runTask(PaperlessTaskName.IndexOptimize) | ||||
|     expect(runSpy).toHaveBeenCalledWith(PaperlessTaskName.IndexOptimize) | ||||
|     expect(toastErrorSpy).toHaveBeenCalledWith( | ||||
|     expect(notificationErrorSpy).toHaveBeenCalledWith( | ||||
|       `Failed to start task ${PaperlessTaskName.IndexOptimize}, see the logs for more details`, | ||||
|       expect.any(Error) | ||||
|     ) | ||||
| @@ -138,7 +138,7 @@ describe('SystemStatusDialogComponent', () => { | ||||
|     expect(runSpy).toHaveBeenCalledWith(PaperlessTaskName.IndexOptimize) | ||||
|  | ||||
|     expect(getStatusSpy).toHaveBeenCalled() | ||||
|     expect(toastSpy).toHaveBeenCalledWith( | ||||
|     expect(notificationSpy).toHaveBeenCalledWith( | ||||
|       `Task ${PaperlessTaskName.IndexOptimize} started` | ||||
|     ) | ||||
|   }) | ||||
|   | ||||
| @@ -14,10 +14,10 @@ import { | ||||
| } from 'src/app/data/system-status' | ||||
| import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe' | ||||
| import { FileSizePipe } from 'src/app/pipes/file-size.pipe' | ||||
| import { NotificationService } from 'src/app/services/notification.service' | ||||
| import { PermissionsService } from 'src/app/services/permissions.service' | ||||
| import { SystemStatusService } from 'src/app/services/system-status.service' | ||||
| import { TasksService } from 'src/app/services/tasks.service' | ||||
| import { ToastService } from 'src/app/services/toast.service' | ||||
|  | ||||
| @Component({ | ||||
|   selector: 'pngx-system-status-dialog', | ||||
| @@ -51,7 +51,7 @@ export class SystemStatusDialogComponent { | ||||
|     private clipboard: Clipboard, | ||||
|     private systemStatusService: SystemStatusService, | ||||
|     private tasksService: TasksService, | ||||
|     private toastService: ToastService, | ||||
|     private notificationService: NotificationService, | ||||
|     private permissionsService: PermissionsService | ||||
|   ) {} | ||||
|  | ||||
| @@ -79,7 +79,7 @@ export class SystemStatusDialogComponent { | ||||
|  | ||||
|   public runTask(taskName: PaperlessTaskName) { | ||||
|     this.runningTasks.add(taskName) | ||||
|     this.toastService.showInfo(`Task ${taskName} started`) | ||||
|     this.notificationService.showInfo(`Task ${taskName} started`) | ||||
|     this.tasksService.run(taskName).subscribe({ | ||||
|       next: () => { | ||||
|         this.runningTasks.delete(taskName) | ||||
| @@ -91,7 +91,7 @@ export class SystemStatusDialogComponent { | ||||
|       }, | ||||
|       error: (err) => { | ||||
|         this.runningTasks.delete(taskName) | ||||
|         this.toastService.showError( | ||||
|         this.notificationService.showError( | ||||
|           `Failed to start task ${taskName}, see the logs for more details`, | ||||
|           err | ||||
|         ) | ||||
|   | ||||
| @@ -1,3 +0,0 @@ | ||||
| @for (toast of toasts; track toast.id) { | ||||
|   <pngx-toast [toast]="toast" [autohide]="true" (close)="closeToast()"></pngx-toast> | ||||
| } | ||||
| @@ -1,71 +0,0 @@ | ||||
| import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http' | ||||
| import { provideHttpClientTesting } from '@angular/common/http/testing' | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing' | ||||
| import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons' | ||||
| import { Subject } from 'rxjs' | ||||
| import { Toast, ToastService } from 'src/app/services/toast.service' | ||||
| import { ToastsComponent } from './toasts.component' | ||||
|  | ||||
| const toast = { | ||||
|   content: 'Error 2 content', | ||||
|   delay: 5000, | ||||
|   error: { | ||||
|     url: 'https://example.com', | ||||
|     status: 500, | ||||
|     statusText: 'Internal Server Error', | ||||
|     message: 'Internal server error 500 message', | ||||
|     error: { detail: 'Error 2 message details' }, | ||||
|   }, | ||||
| } | ||||
|  | ||||
| describe('ToastsComponent', () => { | ||||
|   let component: ToastsComponent | ||||
|   let fixture: ComponentFixture<ToastsComponent> | ||||
|   let toastService: ToastService | ||||
|   let toastSubject: Subject<Toast> = new Subject() | ||||
|  | ||||
|   beforeEach(async () => { | ||||
|     TestBed.configureTestingModule({ | ||||
|       imports: [ToastsComponent, NgxBootstrapIconsModule.pick(allIcons)], | ||||
|       providers: [ | ||||
|         provideHttpClient(withInterceptorsFromDi()), | ||||
|         provideHttpClientTesting(), | ||||
|       ], | ||||
|     }).compileComponents() | ||||
|  | ||||
|     fixture = TestBed.createComponent(ToastsComponent) | ||||
|     toastService = TestBed.inject(ToastService) | ||||
|     jest.replaceProperty(toastService, 'showToast', toastSubject) | ||||
|  | ||||
|     component = fixture.componentInstance | ||||
|  | ||||
|     fixture.detectChanges() | ||||
|   }) | ||||
|  | ||||
|   it('should create', () => { | ||||
|     expect(component).toBeTruthy() | ||||
|   }) | ||||
|  | ||||
|   it('should close toast', () => { | ||||
|     component.toasts = [toast] | ||||
|     const closeToastSpy = jest.spyOn(toastService, 'closeToast') | ||||
|     component.closeToast() | ||||
|     expect(component.toasts).toEqual([]) | ||||
|     expect(closeToastSpy).toHaveBeenCalledWith(toast) | ||||
|   }) | ||||
|  | ||||
|   it('should unsubscribe', () => { | ||||
|     const unsubscribeSpy = jest.spyOn( | ||||
|       (component as any).subscription, | ||||
|       'unsubscribe' | ||||
|     ) | ||||
|     component.ngOnDestroy() | ||||
|     expect(unsubscribeSpy).toHaveBeenCalled() | ||||
|   }) | ||||
|  | ||||
|   it('should subscribe to toastService', () => { | ||||
|     component.ngOnInit() | ||||
|     toastSubject.next(toast) | ||||
|     expect(component.toasts).toEqual([toast]) | ||||
|   }) | ||||
| }) | ||||
| @@ -1,43 +0,0 @@ | ||||
| import { Component, OnDestroy, OnInit } from '@angular/core' | ||||
| import { | ||||
|   NgbAccordionModule, | ||||
|   NgbProgressbarModule, | ||||
| } from '@ng-bootstrap/ng-bootstrap' | ||||
| import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons' | ||||
| import { Subscription } from 'rxjs' | ||||
| import { Toast, ToastService } from 'src/app/services/toast.service' | ||||
| import { ToastComponent } from '../toast/toast.component' | ||||
|  | ||||
| @Component({ | ||||
|   selector: 'pngx-toasts', | ||||
|   templateUrl: './toasts.component.html', | ||||
|   styleUrls: ['./toasts.component.scss'], | ||||
|   imports: [ | ||||
|     ToastComponent, | ||||
|     NgbAccordionModule, | ||||
|     NgbProgressbarModule, | ||||
|     NgxBootstrapIconsModule, | ||||
|   ], | ||||
| }) | ||||
| export class ToastsComponent implements OnInit, OnDestroy { | ||||
|   constructor(public toastService: ToastService) {} | ||||
|  | ||||
|   private subscription: Subscription | ||||
|  | ||||
|   public toasts: Toast[] = [] // array to force change detection | ||||
|  | ||||
|   ngOnDestroy(): void { | ||||
|     this.subscription?.unsubscribe() | ||||
|   } | ||||
|  | ||||
|   ngOnInit(): void { | ||||
|     this.subscription = this.toastService.showToast.subscribe((toast) => { | ||||
|       this.toasts = toast ? [toast] : [] | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|   closeToast() { | ||||
|     this.toastService.closeToast(this.toasts[0]) | ||||
|     this.toasts = [] | ||||
|   } | ||||
| } | ||||
| @@ -12,10 +12,10 @@ import { SavedView } from 'src/app/data/saved-view' | ||||
| import { SETTINGS_KEYS } from 'src/app/data/ui-settings' | ||||
| import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive' | ||||
| import { PermissionsGuard } from 'src/app/guards/permissions.guard' | ||||
| import { NotificationService } from 'src/app/services/notification.service' | ||||
| import { PermissionsService } from 'src/app/services/permissions.service' | ||||
| import { SavedViewService } from 'src/app/services/rest/saved-view.service' | ||||
| import { SettingsService } from 'src/app/services/settings.service' | ||||
| import { ToastService } from 'src/app/services/toast.service' | ||||
| import { LogoComponent } from '../common/logo/logo.component' | ||||
| import { PageHeaderComponent } from '../common/page-header/page-header.component' | ||||
| import { DashboardComponent } from './dashboard.component' | ||||
| @@ -68,7 +68,7 @@ describe('DashboardComponent', () => { | ||||
|   let fixture: ComponentFixture<DashboardComponent> | ||||
|   let settingsService: SettingsService | ||||
|   let tourService: TourService | ||||
|   let toastService: ToastService | ||||
|   let notificationService: NotificationService | ||||
|  | ||||
|   beforeEach(async () => { | ||||
|     TestBed.configureTestingModule({ | ||||
| @@ -121,7 +121,7 @@ describe('DashboardComponent', () => { | ||||
|       if (key === SETTINGS_KEYS.DASHBOARD_VIEWS_SORT_ORDER) return [0, 2, 3] | ||||
|     }) | ||||
|     tourService = TestBed.inject(TourService) | ||||
|     toastService = TestBed.inject(ToastService) | ||||
|     notificationService = TestBed.inject(NotificationService) | ||||
|     fixture = TestBed.createComponent(DashboardComponent) | ||||
|     component = fixture.componentInstance | ||||
|  | ||||
| @@ -166,7 +166,7 @@ describe('DashboardComponent', () => { | ||||
|  | ||||
|   it('should update saved view sorting on drag + drop, show info', () => { | ||||
|     const settingsSpy = jest.spyOn(settingsService, 'updateDashboardViewsSort') | ||||
|     const toastSpy = jest.spyOn(toastService, 'showInfo') | ||||
|     const notificationSpy = jest.spyOn(notificationService, 'showInfo') | ||||
|     jest.spyOn(settingsService, 'storeSettings').mockReturnValue(of(true)) | ||||
|     component.onDrop({ previousIndex: 0, currentIndex: 1 } as CdkDragDrop< | ||||
|       SavedView[] | ||||
| @@ -176,7 +176,7 @@ describe('DashboardComponent', () => { | ||||
|       saved_views[0], | ||||
|       saved_views[3], | ||||
|     ]) | ||||
|     expect(toastSpy).toHaveBeenCalled() | ||||
|     expect(notificationSpy).toHaveBeenCalled() | ||||
|   }) | ||||
|  | ||||
|   it('should update saved view sorting on drag + drop, show error', () => { | ||||
| @@ -187,13 +187,13 @@ describe('DashboardComponent', () => { | ||||
|     fixture = TestBed.createComponent(DashboardComponent) | ||||
|     component = fixture.componentInstance | ||||
|     fixture.detectChanges() | ||||
|     const toastSpy = jest.spyOn(toastService, 'showError') | ||||
|     const notificationSpy = jest.spyOn(notificationService, 'showError') | ||||
|     jest | ||||
|       .spyOn(settingsService, 'storeSettings') | ||||
|       .mockReturnValue(throwError(() => new Error('unable to save'))) | ||||
|     component.onDrop({ previousIndex: 0, currentIndex: 2 } as CdkDragDrop< | ||||
|       SavedView[] | ||||
|     >) | ||||
|     expect(toastSpy).toHaveBeenCalled() | ||||
|     expect(notificationSpy).toHaveBeenCalled() | ||||
|   }) | ||||
| }) | ||||
|   | ||||
| @@ -9,9 +9,9 @@ import { Component } from '@angular/core' | ||||
| import { TourNgBootstrapModule, TourService } from 'ngx-ui-tour-ng-bootstrap' | ||||
| import { SavedView } from 'src/app/data/saved-view' | ||||
| import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive' | ||||
| import { NotificationService } from 'src/app/services/notification.service' | ||||
| import { SavedViewService } from 'src/app/services/rest/saved-view.service' | ||||
| import { SettingsService } from 'src/app/services/settings.service' | ||||
| import { ToastService } from 'src/app/services/toast.service' | ||||
| import { environment } from 'src/environments/environment' | ||||
| import { LogoComponent } from '../common/logo/logo.component' | ||||
| import { PageHeaderComponent } from '../common/page-header/page-header.component' | ||||
| @@ -43,7 +43,7 @@ export class DashboardComponent extends ComponentWithPermissions { | ||||
|     public settingsService: SettingsService, | ||||
|     public savedViewService: SavedViewService, | ||||
|     private tourService: TourService, | ||||
|     private toastService: ToastService | ||||
|     private notificationService: NotificationService | ||||
|   ) { | ||||
|     super() | ||||
|  | ||||
| @@ -87,10 +87,13 @@ export class DashboardComponent extends ComponentWithPermissions { | ||||
|       .updateDashboardViewsSort(this.dashboardViews) | ||||
|       .subscribe({ | ||||
|         next: () => { | ||||
|           this.toastService.showInfo($localize`Dashboard updated`) | ||||
|           this.notificationService.showInfo($localize`Dashboard updated`) | ||||
|         }, | ||||
|         error: (e) => { | ||||
|           this.toastService.showError($localize`Error updating dashboard`, e) | ||||
|           this.notificationService.showError( | ||||
|             $localize`Error updating dashboard`, | ||||
|             e | ||||
|           ) | ||||
|         }, | ||||
|       }) | ||||
|   } | ||||
|   | ||||
| @@ -48,6 +48,7 @@ import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe' | ||||
| import { DocumentTitlePipe } from 'src/app/pipes/document-title.pipe' | ||||
| import { ComponentRouterService } from 'src/app/services/component-router.service' | ||||
| import { DocumentListViewService } from 'src/app/services/document-list-view.service' | ||||
| import { NotificationService } from 'src/app/services/notification.service' | ||||
| import { OpenDocumentsService } from 'src/app/services/open-documents.service' | ||||
| import { PermissionsService } from 'src/app/services/permissions.service' | ||||
| import { CorrespondentService } from 'src/app/services/rest/correspondent.service' | ||||
| @@ -58,7 +59,6 @@ import { StoragePathService } from 'src/app/services/rest/storage-path.service' | ||||
| import { TagService } from 'src/app/services/rest/tag.service' | ||||
| import { UserService } from 'src/app/services/rest/user.service' | ||||
| import { SettingsService } from 'src/app/services/settings.service' | ||||
| import { ToastService } from 'src/app/services/toast.service' | ||||
| import { environment } from 'src/environments/environment' | ||||
| import { ConfirmDialogComponent } from '../common/confirm-dialog/confirm-dialog.component' | ||||
| import { CustomFieldsDropdownComponent } from '../common/custom-fields-dropdown/custom-fields-dropdown.component' | ||||
| @@ -127,7 +127,7 @@ describe('DocumentDetailComponent', () => { | ||||
|   let documentService: DocumentService | ||||
|   let openDocumentsService: OpenDocumentsService | ||||
|   let modalService: NgbModal | ||||
|   let toastService: ToastService | ||||
|   let notificationService: NotificationService | ||||
|   let documentListViewService: DocumentListViewService | ||||
|   let settingsService: SettingsService | ||||
|   let customFieldsService: CustomFieldsService | ||||
| @@ -264,7 +264,7 @@ describe('DocumentDetailComponent', () => { | ||||
|     openDocumentsService = TestBed.inject(OpenDocumentsService) | ||||
|     documentService = TestBed.inject(DocumentService) | ||||
|     modalService = TestBed.inject(NgbModal) | ||||
|     toastService = TestBed.inject(ToastService) | ||||
|     notificationService = TestBed.inject(NotificationService) | ||||
|     documentListViewService = TestBed.inject(DocumentListViewService) | ||||
|     settingsService = TestBed.inject(SettingsService) | ||||
|     settingsService.currentUser = { id: 1 } | ||||
| @@ -447,68 +447,68 @@ describe('DocumentDetailComponent', () => { | ||||
|     expect(navigateSpy).toHaveBeenCalledWith(['404'], { replaceUrl: true }) | ||||
|   }) | ||||
|  | ||||
|   it('should support save, close and show success toast', () => { | ||||
|   it('should support save, close and show success notification', () => { | ||||
|     initNormally() | ||||
|     component.title = 'Foo Bar' | ||||
|     const closeSpy = jest.spyOn(component, 'close') | ||||
|     const updateSpy = jest.spyOn(documentService, 'update') | ||||
|     const toastSpy = jest.spyOn(toastService, 'showInfo') | ||||
|     const notificationSpy = jest.spyOn(notificationService, 'showInfo') | ||||
|     updateSpy.mockImplementation((o) => of(doc)) | ||||
|     component.save(true) | ||||
|     expect(updateSpy).toHaveBeenCalled() | ||||
|     expect(closeSpy).toHaveBeenCalled() | ||||
|     expect(toastSpy).toHaveBeenCalledWith( | ||||
|     expect(notificationSpy).toHaveBeenCalledWith( | ||||
|       'Document "Doc 3" saved successfully.' | ||||
|     ) | ||||
|   }) | ||||
|  | ||||
|   it('should support save without close and show success toast', () => { | ||||
|   it('should support save without close and show success notification', () => { | ||||
|     initNormally() | ||||
|     component.title = 'Foo Bar' | ||||
|     const closeSpy = jest.spyOn(component, 'close') | ||||
|     const updateSpy = jest.spyOn(documentService, 'update') | ||||
|     const toastSpy = jest.spyOn(toastService, 'showInfo') | ||||
|     const notificationSpy = jest.spyOn(notificationService, 'showInfo') | ||||
|     updateSpy.mockImplementation((o) => of(doc)) | ||||
|     component.save() | ||||
|     expect(updateSpy).toHaveBeenCalled() | ||||
|     expect(closeSpy).not.toHaveBeenCalled() | ||||
|     expect(toastSpy).toHaveBeenCalledWith( | ||||
|     expect(notificationSpy).toHaveBeenCalledWith( | ||||
|       'Document "Doc 3" saved successfully.' | ||||
|     ) | ||||
|   }) | ||||
|  | ||||
|   it('should show toast error on save if error occurs', () => { | ||||
|   it('should show notification error on save if error occurs', () => { | ||||
|     currentUserHasObjectPermissions = true | ||||
|     initNormally() | ||||
|     component.title = 'Foo Bar' | ||||
|     const closeSpy = jest.spyOn(component, 'close') | ||||
|     const updateSpy = jest.spyOn(documentService, 'update') | ||||
|     const toastSpy = jest.spyOn(toastService, 'showError') | ||||
|     const notificationSpy = jest.spyOn(notificationService, 'showError') | ||||
|     const error = new Error('failed to save') | ||||
|     updateSpy.mockImplementation(() => throwError(() => error)) | ||||
|     component.save() | ||||
|     expect(updateSpy).toHaveBeenCalled() | ||||
|     expect(closeSpy).not.toHaveBeenCalled() | ||||
|     expect(toastSpy).toHaveBeenCalledWith( | ||||
|     expect(notificationSpy).toHaveBeenCalledWith( | ||||
|       'Error saving document "Doc 3"', | ||||
|       error | ||||
|     ) | ||||
|   }) | ||||
|  | ||||
|   it('should show error toast on save but close if user can no longer edit', () => { | ||||
|   it('should show error notification on save but close if user can no longer edit', () => { | ||||
|     currentUserHasObjectPermissions = false | ||||
|     initNormally() | ||||
|     component.title = 'Foo Bar' | ||||
|     const closeSpy = jest.spyOn(component, 'close') | ||||
|     const updateSpy = jest.spyOn(documentService, 'update') | ||||
|     const toastSpy = jest.spyOn(toastService, 'showInfo') | ||||
|     const notificationSpy = jest.spyOn(notificationService, 'showInfo') | ||||
|     updateSpy.mockImplementation(() => | ||||
|       throwError(() => new Error('failed to save')) | ||||
|     ) | ||||
|     component.save(true) | ||||
|     expect(updateSpy).toHaveBeenCalled() | ||||
|     expect(closeSpy).toHaveBeenCalled() | ||||
|     expect(toastSpy).toHaveBeenCalledWith( | ||||
|     expect(notificationSpy).toHaveBeenCalledWith( | ||||
|       'Document "Doc 3" saved successfully.' | ||||
|     ) | ||||
|   }) | ||||
| @@ -531,19 +531,19 @@ describe('DocumentDetailComponent', () => { | ||||
|     expect | ||||
|   }) | ||||
|  | ||||
|   it('should show toast error on save & next if error occurs', () => { | ||||
|   it('should show notification error on save & next if error occurs', () => { | ||||
|     currentUserHasObjectPermissions = true | ||||
|     initNormally() | ||||
|     component.title = 'Foo Bar' | ||||
|     const closeSpy = jest.spyOn(component, 'close') | ||||
|     const updateSpy = jest.spyOn(documentService, 'update') | ||||
|     const toastSpy = jest.spyOn(toastService, 'showError') | ||||
|     const notificationSpy = jest.spyOn(notificationService, 'showError') | ||||
|     const error = new Error('failed to save') | ||||
|     updateSpy.mockImplementation(() => throwError(() => error)) | ||||
|     component.saveEditNext() | ||||
|     expect(updateSpy).toHaveBeenCalled() | ||||
|     expect(closeSpy).not.toHaveBeenCalled() | ||||
|     expect(toastSpy).toHaveBeenCalledWith('Error saving document', error) | ||||
|     expect(notificationSpy).toHaveBeenCalledWith('Error saving document', error) | ||||
|   }) | ||||
|  | ||||
|   it('should show save button and save & close or save & next', () => { | ||||
| @@ -668,13 +668,13 @@ describe('DocumentDetailComponent', () => { | ||||
|     let openModal: NgbModalRef | ||||
|     modalService.activeInstances.subscribe((modal) => (openModal = modal[0])) | ||||
|     const modalSpy = jest.spyOn(modalService, 'open') | ||||
|     const toastSpy = jest.spyOn(toastService, 'showInfo') | ||||
|     const notificationSpy = jest.spyOn(notificationService, 'showInfo') | ||||
|     component.reprocess() | ||||
|     const modalCloseSpy = jest.spyOn(openModal, 'close') | ||||
|     openModal.componentInstance.confirmClicked.next() | ||||
|     expect(bulkEditSpy).toHaveBeenCalledWith([doc.id], 'reprocess', {}) | ||||
|     expect(modalSpy).toHaveBeenCalled() | ||||
|     expect(toastSpy).toHaveBeenCalled() | ||||
|     expect(notificationSpy).toHaveBeenCalled() | ||||
|     expect(modalCloseSpy).toHaveBeenCalled() | ||||
|   }) | ||||
|  | ||||
| @@ -683,12 +683,12 @@ describe('DocumentDetailComponent', () => { | ||||
|     const bulkEditSpy = jest.spyOn(documentService, 'bulkEdit') | ||||
|     let openModal: NgbModalRef | ||||
|     modalService.activeInstances.subscribe((modal) => (openModal = modal[0])) | ||||
|     const toastSpy = jest.spyOn(toastService, 'showError') | ||||
|     const notificationSpy = jest.spyOn(notificationService, 'showError') | ||||
|     component.reprocess() | ||||
|     const modalCloseSpy = jest.spyOn(openModal, 'close') | ||||
|     bulkEditSpy.mockReturnValue(throwError(() => new Error('error occurred'))) | ||||
|     openModal.componentInstance.confirmClicked.next() | ||||
|     expect(toastSpy).toHaveBeenCalled() | ||||
|     expect(notificationSpy).toHaveBeenCalled() | ||||
|     expect(modalCloseSpy).not.toHaveBeenCalled() | ||||
|   }) | ||||
|  | ||||
| @@ -942,9 +942,12 @@ describe('DocumentDetailComponent', () => { | ||||
|     jest | ||||
|       .spyOn(documentService, 'getMetadata') | ||||
|       .mockReturnValue(throwError(() => error)) | ||||
|     const toastSpy = jest.spyOn(toastService, 'showError') | ||||
|     const notificationSpy = jest.spyOn(notificationService, 'showError') | ||||
|     initNormally() | ||||
|     expect(toastSpy).toHaveBeenCalledWith('Error retrieving metadata', error) | ||||
|     expect(notificationSpy).toHaveBeenCalledWith( | ||||
|       'Error retrieving metadata', | ||||
|       error | ||||
|     ) | ||||
|   }) | ||||
|  | ||||
|   it('should display custom fields', () => { | ||||
| @@ -1028,7 +1031,7 @@ describe('DocumentDetailComponent', () => { | ||||
|  | ||||
|   it('should show error if needed for get suggestions', () => { | ||||
|     const suggestionsSpy = jest.spyOn(documentService, 'getSuggestions') | ||||
|     const errorSpy = jest.spyOn(toastService, 'showError') | ||||
|     const errorSpy = jest.spyOn(notificationService, 'showError') | ||||
|     suggestionsSpy.mockImplementationOnce(() => | ||||
|       throwError(() => new Error('failed to get suggestions')) | ||||
|     ) | ||||
|   | ||||
| @@ -63,6 +63,7 @@ import { SafeUrlPipe } from 'src/app/pipes/safeurl.pipe' | ||||
| import { ComponentRouterService } from 'src/app/services/component-router.service' | ||||
| import { DocumentListViewService } from 'src/app/services/document-list-view.service' | ||||
| import { HotKeyService } from 'src/app/services/hot-key.service' | ||||
| import { NotificationService } from 'src/app/services/notification.service' | ||||
| import { OpenDocumentsService } from 'src/app/services/open-documents.service' | ||||
| import { | ||||
|   PermissionAction, | ||||
| @@ -76,7 +77,6 @@ import { DocumentService } from 'src/app/services/rest/document.service' | ||||
| import { StoragePathService } from 'src/app/services/rest/storage-path.service' | ||||
| import { UserService } from 'src/app/services/rest/user.service' | ||||
| import { SettingsService } from 'src/app/services/settings.service' | ||||
| import { ToastService } from 'src/app/services/toast.service' | ||||
| import { ISODateAdapter } from 'src/app/utils/ngb-iso-date-adapter' | ||||
| import * as UTIF from 'utif' | ||||
| import { ConfirmDialogComponent } from '../common/confirm-dialog/confirm-dialog.component' | ||||
| @@ -268,7 +268,7 @@ export class DocumentDetailComponent | ||||
|     private openDocumentService: OpenDocumentsService, | ||||
|     private documentListViewService: DocumentListViewService, | ||||
|     private documentTitlePipe: DocumentTitlePipe, | ||||
|     private toastService: ToastService, | ||||
|     private notificationService: NotificationService, | ||||
|     private settings: SettingsService, | ||||
|     private storagePathService: StoragePathService, | ||||
|     private permissionsService: PermissionsService, | ||||
| @@ -628,7 +628,7 @@ export class DocumentDetailComponent | ||||
|         }, | ||||
|         error: (error) => { | ||||
|           this.metadata = {} // allow display to fallback to <object> tag | ||||
|           this.toastService.showError( | ||||
|           this.notificationService.showError( | ||||
|             $localize`Error retrieving metadata`, | ||||
|             error | ||||
|           ) | ||||
| @@ -657,7 +657,7 @@ export class DocumentDetailComponent | ||||
|           }, | ||||
|           error: (error) => { | ||||
|             this.suggestions = null | ||||
|             this.toastService.showError( | ||||
|             this.notificationService.showError( | ||||
|               $localize`Error retrieving suggestions.`, | ||||
|               error | ||||
|             ) | ||||
| @@ -809,7 +809,7 @@ export class DocumentDetailComponent | ||||
|           this.store.next(newValues) | ||||
|           this.openDocumentService.setDirty(this.document, false) | ||||
|           this.openDocumentService.save() | ||||
|           this.toastService.showInfo( | ||||
|           this.notificationService.showInfo( | ||||
|             $localize`Document "${newValues.title}" saved successfully.` | ||||
|           ) | ||||
|           this.networkActive = false | ||||
| @@ -825,13 +825,13 @@ export class DocumentDetailComponent | ||||
|         error: (error) => { | ||||
|           this.networkActive = false | ||||
|           if (!this.userCanEdit) { | ||||
|             this.toastService.showInfo( | ||||
|             this.notificationService.showInfo( | ||||
|               $localize`Document "${this.document.title}" saved successfully.` | ||||
|             ) | ||||
|             close && this.close() | ||||
|           } else { | ||||
|             this.error = error.error | ||||
|             this.toastService.showError( | ||||
|             this.notificationService.showError( | ||||
|               $localize`Error saving document "${this.document.title}"`, | ||||
|               error | ||||
|             ) | ||||
| @@ -877,7 +877,10 @@ export class DocumentDetailComponent | ||||
|         error: (error) => { | ||||
|           this.networkActive = false | ||||
|           this.error = error.error | ||||
|           this.toastService.showError($localize`Error saving document`, error) | ||||
|           this.notificationService.showError( | ||||
|             $localize`Error saving document`, | ||||
|             error | ||||
|           ) | ||||
|         }, | ||||
|       }) | ||||
|   } | ||||
| @@ -931,7 +934,10 @@ export class DocumentDetailComponent | ||||
|           this.close() | ||||
|         }, | ||||
|         error: (error) => { | ||||
|           this.toastService.showError($localize`Error deleting document`, error) | ||||
|           this.notificationService.showError( | ||||
|             $localize`Error deleting document`, | ||||
|             error | ||||
|           ) | ||||
|           modal.componentInstance.buttonsEnabled = true | ||||
|           this.subscribeModalDelete(modal) | ||||
|         }, | ||||
| @@ -962,7 +968,7 @@ export class DocumentDetailComponent | ||||
|         .bulkEdit([this.document.id], 'reprocess', {}) | ||||
|         .subscribe({ | ||||
|           next: () => { | ||||
|             this.toastService.showInfo( | ||||
|             this.notificationService.showInfo( | ||||
|               $localize`Reprocess operation for "${this.document.title}" will begin in the background. Close and re-open or reload this document after the operation has completed to see new content.` | ||||
|             ) | ||||
|             if (modal) { | ||||
| @@ -973,7 +979,7 @@ export class DocumentDetailComponent | ||||
|             if (modal) { | ||||
|               modal.componentInstance.buttonsEnabled = true | ||||
|             } | ||||
|             this.toastService.showError( | ||||
|             this.notificationService.showError( | ||||
|               $localize`Error executing operation`, | ||||
|               error | ||||
|             ) | ||||
| @@ -1020,7 +1026,7 @@ export class DocumentDetailComponent | ||||
|       }, | ||||
|       error: (error) => { | ||||
|         this.downloading = false | ||||
|         this.toastService.showError( | ||||
|         this.notificationService.showError( | ||||
|           $localize`Error downloading document`, | ||||
|           error | ||||
|         ) | ||||
| @@ -1329,7 +1335,7 @@ export class DocumentDetailComponent | ||||
|           .pipe(first(), takeUntil(this.unsubscribeNotifier)) | ||||
|           .subscribe({ | ||||
|             next: () => { | ||||
|               this.toastService.showInfo( | ||||
|               this.notificationService.showInfo( | ||||
|                 $localize`Split operation for "${this.document.title}" will begin in the background.` | ||||
|               ) | ||||
|               modal.close() | ||||
| @@ -1338,7 +1344,7 @@ export class DocumentDetailComponent | ||||
|               if (modal) { | ||||
|                 modal.componentInstance.buttonsEnabled = true | ||||
|               } | ||||
|               this.toastService.showError( | ||||
|               this.notificationService.showError( | ||||
|                 $localize`Error executing split operation`, | ||||
|                 error | ||||
|               ) | ||||
| @@ -1368,7 +1374,7 @@ export class DocumentDetailComponent | ||||
|           .pipe(first(), takeUntil(this.unsubscribeNotifier)) | ||||
|           .subscribe({ | ||||
|             next: () => { | ||||
|               this.toastService.show({ | ||||
|               this.notificationService.show({ | ||||
|                 content: $localize`Rotation of "${this.document.title}" will begin in the background. Close and re-open the document after the operation has completed to see the changes.`, | ||||
|                 delay: 8000, | ||||
|                 action: this.close.bind(this), | ||||
| @@ -1380,7 +1386,7 @@ export class DocumentDetailComponent | ||||
|               if (modal) { | ||||
|                 modal.componentInstance.buttonsEnabled = true | ||||
|               } | ||||
|               this.toastService.showError( | ||||
|               this.notificationService.showError( | ||||
|                 $localize`Error executing rotate operation`, | ||||
|                 error | ||||
|               ) | ||||
| @@ -1408,7 +1414,7 @@ export class DocumentDetailComponent | ||||
|           .pipe(first(), takeUntil(this.unsubscribeNotifier)) | ||||
|           .subscribe({ | ||||
|             next: () => { | ||||
|               this.toastService.showInfo( | ||||
|               this.notificationService.showInfo( | ||||
|                 $localize`Delete pages operation for "${this.document.title}" will begin in the background. Close and re-open or reload this document after the operation has completed to see the changes.` | ||||
|               ) | ||||
|               modal.close() | ||||
| @@ -1417,7 +1423,7 @@ export class DocumentDetailComponent | ||||
|               if (modal) { | ||||
|                 modal.componentInstance.buttonsEnabled = true | ||||
|               } | ||||
|               this.toastService.showError( | ||||
|               this.notificationService.showError( | ||||
|                 $localize`Error executing delete pages operation`, | ||||
|                 error | ||||
|               ) | ||||
|   | ||||
| @@ -16,6 +16,7 @@ import { StoragePath } from 'src/app/data/storage-path' | ||||
| import { Tag } from 'src/app/data/tag' | ||||
| import { FilterPipe } from 'src/app/pipes/filter.pipe' | ||||
| import { DocumentListViewService } from 'src/app/services/document-list-view.service' | ||||
| import { NotificationService } from 'src/app/services/notification.service' | ||||
| import { PermissionsService } from 'src/app/services/permissions.service' | ||||
| import { CorrespondentService } from 'src/app/services/rest/correspondent.service' | ||||
| import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service' | ||||
| @@ -29,7 +30,6 @@ import { StoragePathService } from 'src/app/services/rest/storage-path.service' | ||||
| import { TagService } from 'src/app/services/rest/tag.service' | ||||
| import { UserService } from 'src/app/services/rest/user.service' | ||||
| import { SettingsService } from 'src/app/services/settings.service' | ||||
| import { ToastService } from 'src/app/services/toast.service' | ||||
| import { environment } from 'src/environments/environment' | ||||
| import { CorrespondentEditDialogComponent } from '../../common/edit-dialog/correspondent-edit-dialog/correspondent-edit-dialog.component' | ||||
| import { CustomFieldEditDialogComponent } from '../../common/edit-dialog/custom-field-edit-dialog/custom-field-edit-dialog.component' | ||||
| @@ -64,7 +64,7 @@ describe('BulkEditorComponent', () => { | ||||
|   let permissionsService: PermissionsService | ||||
|   let documentListViewService: DocumentListViewService | ||||
|   let documentService: DocumentService | ||||
|   let toastService: ToastService | ||||
|   let notificationService: NotificationService | ||||
|   let modalService: NgbModal | ||||
|   let tagService: TagService | ||||
|   let correspondentsService: CorrespondentService | ||||
| @@ -160,7 +160,7 @@ describe('BulkEditorComponent', () => { | ||||
|     permissionsService = TestBed.inject(PermissionsService) | ||||
|     documentListViewService = TestBed.inject(DocumentListViewService) | ||||
|     documentService = TestBed.inject(DocumentService) | ||||
|     toastService = TestBed.inject(ToastService) | ||||
|     notificationService = TestBed.inject(NotificationService) | ||||
|     modalService = TestBed.inject(NgbModal) | ||||
|     tagService = TestBed.inject(TagService) | ||||
|     correspondentsService = TestBed.inject(CorrespondentService) | ||||
| @@ -884,7 +884,7 @@ describe('BulkEditorComponent', () => { | ||||
|     expect(button.nativeElement.disabled).toBeTruthy() | ||||
|   }) | ||||
|  | ||||
|   it('should show a warning toast on bulk edit error', () => { | ||||
|   it('should show a warning notification on bulk edit error', () => { | ||||
|     jest | ||||
|       .spyOn(documentService, 'bulkEdit') | ||||
|       .mockReturnValue( | ||||
| @@ -902,12 +902,12 @@ describe('BulkEditorComponent', () => { | ||||
|       .mockReturnValue(true) | ||||
|     component.showConfirmationDialogs = false | ||||
|     fixture.detectChanges() | ||||
|     const toastSpy = jest.spyOn(toastService, 'showError') | ||||
|     const notificationSpy = jest.spyOn(notificationService, 'showError') | ||||
|     component.setTags({ | ||||
|       itemsToAdd: [{ id: 0 }], | ||||
|       itemsToRemove: [], | ||||
|     }) | ||||
|     expect(toastSpy).toHaveBeenCalled() | ||||
|     expect(notificationSpy).toHaveBeenCalled() | ||||
|   }) | ||||
|  | ||||
|   it('should support redo ocr', () => { | ||||
| @@ -1391,8 +1391,14 @@ describe('BulkEditorComponent', () => { | ||||
|       .spyOn(documentListViewService, 'selected', 'get') | ||||
|       .mockReturnValue(new Set([3, 4])) | ||||
|     fixture.detectChanges() | ||||
|     const toastServiceShowInfoSpy = jest.spyOn(toastService, 'showInfo') | ||||
|     const toastServiceShowErrorSpy = jest.spyOn(toastService, 'showError') | ||||
|     const notificationServiceShowInfoSpy = jest.spyOn( | ||||
|       notificationService, | ||||
|       'showInfo' | ||||
|     ) | ||||
|     const notificationServiceShowErrorSpy = jest.spyOn( | ||||
|       notificationService, | ||||
|       'showError' | ||||
|     ) | ||||
|     const listReloadSpy = jest.spyOn(documentListViewService, 'reload') | ||||
|  | ||||
|     component.customFields = [ | ||||
| @@ -1410,11 +1416,11 @@ describe('BulkEditorComponent', () => { | ||||
|     expect(modal.componentInstance.documents).toEqual([3, 4]) | ||||
|  | ||||
|     modal.componentInstance.failed.emit() | ||||
|     expect(toastServiceShowErrorSpy).toHaveBeenCalled() | ||||
|     expect(notificationServiceShowErrorSpy).toHaveBeenCalled() | ||||
|     expect(listReloadSpy).not.toHaveBeenCalled() | ||||
|  | ||||
|     modal.componentInstance.succeeded.emit() | ||||
|     expect(toastServiceShowInfoSpy).toHaveBeenCalled() | ||||
|     expect(notificationServiceShowInfoSpy).toHaveBeenCalled() | ||||
|     expect(listReloadSpy).toHaveBeenCalled() | ||||
|     httpTestingController.match( | ||||
|       `${environment.apiBaseUrl}documents/?page=1&page_size=50&ordering=-created&truncate_content=true` | ||||
|   | ||||
| @@ -23,6 +23,7 @@ import { Tag } from 'src/app/data/tag' | ||||
| import { SETTINGS_KEYS } from 'src/app/data/ui-settings' | ||||
| import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive' | ||||
| import { DocumentListViewService } from 'src/app/services/document-list-view.service' | ||||
| import { NotificationService } from 'src/app/services/notification.service' | ||||
| import { OpenDocumentsService } from 'src/app/services/open-documents.service' | ||||
| import { | ||||
|   PermissionAction, | ||||
| @@ -39,7 +40,6 @@ import { | ||||
| import { StoragePathService } from 'src/app/services/rest/storage-path.service' | ||||
| import { TagService } from 'src/app/services/rest/tag.service' | ||||
| import { SettingsService } from 'src/app/services/settings.service' | ||||
| import { ToastService } from 'src/app/services/toast.service' | ||||
| import { MergeConfirmDialogComponent } from '../../common/confirm-dialog/merge-confirm-dialog/merge-confirm-dialog.component' | ||||
| import { RotateConfirmDialogComponent } from '../../common/confirm-dialog/rotate-confirm-dialog/rotate-confirm-dialog.component' | ||||
| import { CorrespondentEditDialogComponent } from '../../common/edit-dialog/correspondent-edit-dialog/correspondent-edit-dialog.component' | ||||
| @@ -113,7 +113,7 @@ export class BulkEditorComponent | ||||
|     private modalService: NgbModal, | ||||
|     private openDocumentService: OpenDocumentsService, | ||||
|     private settings: SettingsService, | ||||
|     private toastService: ToastService, | ||||
|     private notificationService: NotificationService, | ||||
|     private storagePathService: StoragePathService, | ||||
|     private customFieldService: CustomFieldsService, | ||||
|     private permissionService: PermissionsService | ||||
| @@ -284,7 +284,7 @@ export class BulkEditorComponent | ||||
|           if (modal) { | ||||
|             modal.componentInstance.buttonsEnabled = true | ||||
|           } | ||||
|           this.toastService.showError( | ||||
|           this.notificationService.showError( | ||||
|             $localize`Error executing bulk operation`, | ||||
|             error | ||||
|           ) | ||||
| @@ -859,7 +859,7 @@ export class BulkEditorComponent | ||||
|         } | ||||
|         mergeDialog.buttonsEnabled = false | ||||
|         this.executeBulkOperation(modal, 'merge', args, mergeDialog.documentIDs) | ||||
|         this.toastService.showInfo( | ||||
|         this.notificationService.showInfo( | ||||
|           $localize`Merged document will be queued for consumption.` | ||||
|         ) | ||||
|       }) | ||||
| @@ -882,7 +882,7 @@ export class BulkEditorComponent | ||||
|  | ||||
|     dialog.documents = Array.from(this.list.selected) | ||||
|     dialog.succeeded.subscribe((result) => { | ||||
|       this.toastService.showInfo($localize`Custom fields updated.`) | ||||
|       this.notificationService.showInfo($localize`Custom fields updated.`) | ||||
|       this.list.reload() | ||||
|       this.list.reduceSelectionToFilter() | ||||
|       this.list.selected.forEach((id) => { | ||||
| @@ -890,7 +890,7 @@ export class BulkEditorComponent | ||||
|       }) | ||||
|     }) | ||||
|     dialog.failed.subscribe((error) => { | ||||
|       this.toastService.showError( | ||||
|       this.notificationService.showError( | ||||
|         $localize`Error updating custom fields.`, | ||||
|         error | ||||
|       ) | ||||
|   | ||||
| @@ -39,11 +39,11 @@ import { FilterPipe } from 'src/app/pipes/filter.pipe' | ||||
| import { SafeHtmlPipe } from 'src/app/pipes/safehtml.pipe' | ||||
| import { UsernamePipe } from 'src/app/pipes/username.pipe' | ||||
| import { DocumentListViewService } from 'src/app/services/document-list-view.service' | ||||
| import { NotificationService } from 'src/app/services/notification.service' | ||||
| import { PermissionsService } from 'src/app/services/permissions.service' | ||||
| import { DocumentService } from 'src/app/services/rest/document.service' | ||||
| import { SavedViewService } from 'src/app/services/rest/saved-view.service' | ||||
| import { SettingsService } from 'src/app/services/settings.service' | ||||
| import { ToastService } from 'src/app/services/toast.service' | ||||
| import { | ||||
|   FileStatus, | ||||
|   WebsocketStatusService, | ||||
| @@ -85,7 +85,7 @@ describe('DocumentListComponent', () => { | ||||
|   let savedViewService: SavedViewService | ||||
|   let router: Router | ||||
|   let activatedRoute: ActivatedRoute | ||||
|   let toastService: ToastService | ||||
|   let notificationService: NotificationService | ||||
|   let modalService: NgbModal | ||||
|   let settingsService: SettingsService | ||||
|   let permissionService: PermissionsService | ||||
| @@ -116,7 +116,7 @@ describe('DocumentListComponent', () => { | ||||
|     savedViewService = TestBed.inject(SavedViewService) | ||||
|     router = TestBed.inject(Router) | ||||
|     activatedRoute = TestBed.inject(ActivatedRoute) | ||||
|     toastService = TestBed.inject(ToastService) | ||||
|     notificationService = TestBed.inject(NotificationService) | ||||
|     modalService = TestBed.inject(NgbModal) | ||||
|     settingsService = TestBed.inject(SettingsService) | ||||
|     permissionService = TestBed.inject(PermissionsService) | ||||
| @@ -405,11 +405,11 @@ describe('DocumentListComponent', () => { | ||||
|     delete modifiedView.name | ||||
|     const savedViewServicePatch = jest.spyOn(savedViewService, 'patch') | ||||
|     savedViewServicePatch.mockReturnValue(of(modifiedView)) | ||||
|     const toastSpy = jest.spyOn(toastService, 'showInfo') | ||||
|     const notificationSpy = jest.spyOn(notificationService, 'showInfo') | ||||
|  | ||||
|     component.saveViewConfig() | ||||
|     expect(savedViewServicePatch).toHaveBeenCalledWith(modifiedView) | ||||
|     expect(toastSpy).toHaveBeenCalledWith( | ||||
|     expect(notificationSpy).toHaveBeenCalledWith( | ||||
|       `View "${view.name}" saved successfully.` | ||||
|     ) | ||||
|   }) | ||||
| @@ -427,12 +427,12 @@ describe('DocumentListComponent', () => { | ||||
|         }, | ||||
|       ], | ||||
|     }) | ||||
|     const toastErrorSpy = jest.spyOn(toastService, 'showError') | ||||
|     const notificationErrorSpy = jest.spyOn(notificationService, 'showError') | ||||
|     jest | ||||
|       .spyOn(savedViewService, 'patch') | ||||
|       .mockReturnValueOnce(throwError(() => new Error('Error saving view'))) | ||||
|     component.saveViewConfig() | ||||
|     expect(toastErrorSpy).toHaveBeenCalledWith( | ||||
|     expect(notificationErrorSpy).toHaveBeenCalledWith( | ||||
|       'Failed to save view "Saved View 10".', | ||||
|       expect.any(Error) | ||||
|     ) | ||||
| @@ -467,7 +467,7 @@ describe('DocumentListComponent', () => { | ||||
|     let openModal: NgbModalRef | ||||
|     modalService.activeInstances.subscribe((modal) => (openModal = modal[0])) | ||||
|     const modalSpy = jest.spyOn(modalService, 'open') | ||||
|     const toastSpy = jest.spyOn(toastService, 'showInfo') | ||||
|     const notificationSpy = jest.spyOn(notificationService, 'showInfo') | ||||
|     const savedViewServiceCreate = jest.spyOn(savedViewService, 'create') | ||||
|     savedViewServiceCreate.mockReturnValueOnce(of(modifiedView)) | ||||
|     component.saveViewConfigAs() | ||||
| @@ -480,7 +480,7 @@ describe('DocumentListComponent', () => { | ||||
|     }) | ||||
|     expect(savedViewServiceCreate).toHaveBeenCalled() | ||||
|     expect(modalSpy).toHaveBeenCalled() | ||||
|     expect(toastSpy).toHaveBeenCalled() | ||||
|     expect(notificationSpy).toHaveBeenCalled() | ||||
|     expect(modalCloseSpy).toHaveBeenCalled() | ||||
|   }) | ||||
|  | ||||
|   | ||||
| @@ -45,11 +45,11 @@ import { StoragePathNamePipe } from 'src/app/pipes/storage-path-name.pipe' | ||||
| import { UsernamePipe } from 'src/app/pipes/username.pipe' | ||||
| import { DocumentListViewService } from 'src/app/services/document-list-view.service' | ||||
| import { HotKeyService } from 'src/app/services/hot-key.service' | ||||
| import { NotificationService } from 'src/app/services/notification.service' | ||||
| import { OpenDocumentsService } from 'src/app/services/open-documents.service' | ||||
| import { PermissionsService } from 'src/app/services/permissions.service' | ||||
| import { SavedViewService } from 'src/app/services/rest/saved-view.service' | ||||
| import { SettingsService } from 'src/app/services/settings.service' | ||||
| import { ToastService } from 'src/app/services/toast.service' | ||||
| import { WebsocketStatusService } from 'src/app/services/websocket-status.service' | ||||
| import { | ||||
|   filterRulesDiffer, | ||||
| @@ -111,7 +111,7 @@ export class DocumentListComponent | ||||
|     public savedViewService: SavedViewService, | ||||
|     public route: ActivatedRoute, | ||||
|     private router: Router, | ||||
|     private toastService: ToastService, | ||||
|     private notificationService: NotificationService, | ||||
|     private modalService: NgbModal, | ||||
|     private websocketStatusService: WebsocketStatusService, | ||||
|     public openDocumentsService: OpenDocumentsService, | ||||
| @@ -380,13 +380,13 @@ export class DocumentListComponent | ||||
|         .subscribe({ | ||||
|           next: (view) => { | ||||
|             this.unmodifiedSavedView = view | ||||
|             this.toastService.showInfo( | ||||
|             this.notificationService.showInfo( | ||||
|               $localize`View "${this.list.activeSavedViewTitle}" saved successfully.` | ||||
|             ) | ||||
|             this.unmodifiedFilterRules = this.list.filterRules | ||||
|           }, | ||||
|           error: (err) => { | ||||
|             this.toastService.showError( | ||||
|             this.notificationService.showError( | ||||
|               $localize`Failed to save view "${this.list.activeSavedViewTitle}".`, | ||||
|               err | ||||
|             ) | ||||
| @@ -430,7 +430,7 @@ export class DocumentListComponent | ||||
|         .subscribe({ | ||||
|           next: () => { | ||||
|             modal.close() | ||||
|             this.toastService.showInfo( | ||||
|             this.notificationService.showInfo( | ||||
|               $localize`View "${savedView.name}" created successfully.` | ||||
|             ) | ||||
|           }, | ||||
|   | ||||
| @@ -9,10 +9,10 @@ import { of, throwError } from 'rxjs' | ||||
| import { DocumentNote } from 'src/app/data/document-note' | ||||
| import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive' | ||||
| import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe' | ||||
| import { NotificationService } from 'src/app/services/notification.service' | ||||
| import { PermissionsService } from 'src/app/services/permissions.service' | ||||
| import { DocumentNotesService } from 'src/app/services/rest/document-notes.service' | ||||
| import { UserService } from 'src/app/services/rest/user.service' | ||||
| import { ToastService } from 'src/app/services/toast.service' | ||||
| import { DocumentNotesComponent } from './document-notes.component' | ||||
|  | ||||
| const notes: DocumentNote[] = [ | ||||
| @@ -52,7 +52,7 @@ describe('DocumentNotesComponent', () => { | ||||
|   let component: DocumentNotesComponent | ||||
|   let fixture: ComponentFixture<DocumentNotesComponent> | ||||
|   let notesService: DocumentNotesService | ||||
|   let toastService: ToastService | ||||
|   let notificationService: NotificationService | ||||
|  | ||||
|   beforeEach(async () => { | ||||
|     TestBed.configureTestingModule({ | ||||
| @@ -103,7 +103,7 @@ describe('DocumentNotesComponent', () => { | ||||
|     }).compileComponents() | ||||
|  | ||||
|     notesService = TestBed.inject(DocumentNotesService) | ||||
|     toastService = TestBed.inject(ToastService) | ||||
|     notificationService = TestBed.inject(NotificationService) | ||||
|     fixture = TestBed.createComponent(DocumentNotesComponent) | ||||
|     component = fixture.componentInstance | ||||
|     fixture.detectChanges() | ||||
| @@ -162,11 +162,11 @@ describe('DocumentNotesComponent', () => { | ||||
|     fixture.detectChanges() | ||||
|     const addSpy = jest.spyOn(notesService, 'addNote') | ||||
|     addSpy.mockReturnValueOnce(throwError(() => new Error('error saving note'))) | ||||
|     const toastsSpy = jest.spyOn(toastService, 'showError') | ||||
|     const notificationsSpy = jest.spyOn(notificationService, 'showError') | ||||
|     const addButton = fixture.debugElement.query(By.css('button')) | ||||
|     addButton.triggerEventHandler('click') | ||||
|     expect(addSpy).toHaveBeenCalledWith(12, note) | ||||
|     expect(toastsSpy).toHaveBeenCalled() | ||||
|     expect(notificationsSpy).toHaveBeenCalled() | ||||
|  | ||||
|     addSpy.mockReturnValueOnce( | ||||
|       of([...notes, { id: 31, note, user: { id: 1 } }]) | ||||
| @@ -194,7 +194,7 @@ describe('DocumentNotesComponent', () => { | ||||
|     fixture.detectChanges() | ||||
|     const deleteButton = fixture.debugElement.queryAll(By.css('button'))[1] // 0 is add button | ||||
|     const deleteSpy = jest.spyOn(notesService, 'deleteNote') | ||||
|     const toastsSpy = jest.spyOn(toastService, 'showError') | ||||
|     const toastsSpy = jest.spyOn(notificationService, 'showError') | ||||
|     deleteSpy.mockReturnValueOnce( | ||||
|       throwError(() => new Error('error deleting note')) | ||||
|     ) | ||||
|   | ||||
| @@ -10,9 +10,9 @@ import { DocumentNote } from 'src/app/data/document-note' | ||||
| import { User } from 'src/app/data/user' | ||||
| import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive' | ||||
| import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe' | ||||
| import { NotificationService } from 'src/app/services/notification.service' | ||||
| import { DocumentNotesService } from 'src/app/services/rest/document-notes.service' | ||||
| import { UserService } from 'src/app/services/rest/user.service' | ||||
| import { ToastService } from 'src/app/services/toast.service' | ||||
| import { ComponentWithPermissions } from '../with-permissions/with-permissions.component' | ||||
|  | ||||
| @Component({ | ||||
| @@ -50,7 +50,7 @@ export class DocumentNotesComponent extends ComponentWithPermissions { | ||||
|  | ||||
|   constructor( | ||||
|     private notesService: DocumentNotesService, | ||||
|     private toastService: ToastService, | ||||
|     private notificationService: NotificationService, | ||||
|     private usersService: UserService | ||||
|   ) { | ||||
|     super() | ||||
| @@ -78,7 +78,7 @@ export class DocumentNotesComponent extends ComponentWithPermissions { | ||||
|       }, | ||||
|       error: (e) => { | ||||
|         this.networkActive = false | ||||
|         this.toastService.showError($localize`Error saving note`, e) | ||||
|         this.notificationService.showError($localize`Error saving note`, e) | ||||
|       }, | ||||
|     }) | ||||
|   } | ||||
| @@ -92,7 +92,7 @@ export class DocumentNotesComponent extends ComponentWithPermissions { | ||||
|       }, | ||||
|       error: (e) => { | ||||
|         this.networkActive = false | ||||
|         this.toastService.showError($localize`Error deleting note`, e) | ||||
|         this.notificationService.showError($localize`Error deleting note`, e) | ||||
|       }, | ||||
|     }) | ||||
|   } | ||||
|   | ||||
| @@ -10,24 +10,28 @@ import { | ||||
| } from '@angular/core/testing' | ||||
| import { By } from '@angular/platform-browser' | ||||
| import { NgxFileDropEntry, NgxFileDropModule } from 'ngx-file-drop' | ||||
| import { NotificationService } from 'src/app/services/notification.service' | ||||
| import { PermissionsService } from 'src/app/services/permissions.service' | ||||
| import { SettingsService } from 'src/app/services/settings.service' | ||||
| import { ToastService } from 'src/app/services/toast.service' | ||||
| import { UploadDocumentsService } from 'src/app/services/upload-documents.service' | ||||
| import { ToastsComponent } from '../common/toasts/toasts.component' | ||||
| import { NotificationListComponent } from '../common/notification-list/notification-list.component' | ||||
| import { FileDropComponent } from './file-drop.component' | ||||
|  | ||||
| describe('FileDropComponent', () => { | ||||
|   let component: FileDropComponent | ||||
|   let fixture: ComponentFixture<FileDropComponent> | ||||
|   let permissionsService: PermissionsService | ||||
|   let toastService: ToastService | ||||
|   let notificationService: NotificationService | ||||
|   let settingsService: SettingsService | ||||
|   let uploadDocumentsService: UploadDocumentsService | ||||
|  | ||||
|   beforeEach(() => { | ||||
|     TestBed.configureTestingModule({ | ||||
|       imports: [NgxFileDropModule, FileDropComponent, ToastsComponent], | ||||
|       imports: [ | ||||
|         NgxFileDropModule, | ||||
|         FileDropComponent, | ||||
|         NotificationListComponent, | ||||
|       ], | ||||
|       providers: [ | ||||
|         provideHttpClient(withInterceptorsFromDi()), | ||||
|         provideHttpClientTesting(), | ||||
| @@ -36,7 +40,7 @@ describe('FileDropComponent', () => { | ||||
|  | ||||
|     permissionsService = TestBed.inject(PermissionsService) | ||||
|     settingsService = TestBed.inject(SettingsService) | ||||
|     toastService = TestBed.inject(ToastService) | ||||
|     notificationService = TestBed.inject(NotificationService) | ||||
|     uploadDocumentsService = TestBed.inject(UploadDocumentsService) | ||||
|  | ||||
|     fixture = TestBed.createComponent(FileDropComponent) | ||||
| @@ -101,7 +105,7 @@ describe('FileDropComponent', () => { | ||||
|     fixture.detectChanges() | ||||
|     expect(dropzone.classes['hide']).toBeTruthy() | ||||
|     // drop | ||||
|     const toastSpy = jest.spyOn(toastService, 'show') | ||||
|     const notificationSpy = jest.spyOn(notificationService, 'show') | ||||
|     const uploadSpy = jest.spyOn( | ||||
|       UploadDocumentsService.prototype as any, | ||||
|       'uploadFile' | ||||
| @@ -135,7 +139,7 @@ describe('FileDropComponent', () => { | ||||
|       } as unknown as NgxFileDropEntry, | ||||
|     ]) | ||||
|     tick(3000) | ||||
|     expect(toastSpy).toHaveBeenCalled() | ||||
|     expect(notificationSpy).toHaveBeenCalled() | ||||
|     expect(uploadSpy).toHaveBeenCalled() | ||||
|     discardPeriodicTasks() | ||||
|   })) | ||||
|   | ||||
| @@ -4,13 +4,13 @@ import { | ||||
|   NgxFileDropEntry, | ||||
|   NgxFileDropModule, | ||||
| } from 'ngx-file-drop' | ||||
| import { NotificationService } from 'src/app/services/notification.service' | ||||
| import { | ||||
|   PermissionAction, | ||||
|   PermissionsService, | ||||
|   PermissionType, | ||||
| } from 'src/app/services/permissions.service' | ||||
| import { SettingsService } from 'src/app/services/settings.service' | ||||
| import { ToastService } from 'src/app/services/toast.service' | ||||
| import { UploadDocumentsService } from 'src/app/services/upload-documents.service' | ||||
|  | ||||
| @Component({ | ||||
| @@ -26,7 +26,7 @@ export class FileDropComponent { | ||||
|  | ||||
|   constructor( | ||||
|     private settings: SettingsService, | ||||
|     private toastService: ToastService, | ||||
|     private notificationService: NotificationService, | ||||
|     private uploadDocumentsService: UploadDocumentsService, | ||||
|     private permissionsService: PermissionsService | ||||
|   ) {} | ||||
| @@ -90,7 +90,7 @@ export class FileDropComponent { | ||||
|   public dropped(files: NgxFileDropEntry[]) { | ||||
|     this.uploadDocumentsService.onNgxFileDrop(files) | ||||
|     if (files.length > 0) | ||||
|       this.toastService.showInfo($localize`Initiating upload...`, 3000) | ||||
|       this.notificationService.showInfo($localize`Initiating upload...`, 3000) | ||||
|   } | ||||
|  | ||||
|   @HostListener('window:blur', ['$event']) public onWindowBlur() { | ||||
|   | ||||
| @@ -13,12 +13,12 @@ import { IfPermissionsDirective } from 'src/app/directives/if-permissions.direct | ||||
| import { SortableDirective } from 'src/app/directives/sortable.directive' | ||||
| import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe' | ||||
| import { DocumentListViewService } from 'src/app/services/document-list-view.service' | ||||
| import { NotificationService } from 'src/app/services/notification.service' | ||||
| import { | ||||
|   PermissionsService, | ||||
|   PermissionType, | ||||
| } from 'src/app/services/permissions.service' | ||||
| import { CorrespondentService } from 'src/app/services/rest/correspondent.service' | ||||
| import { ToastService } from 'src/app/services/toast.service' | ||||
| import { CorrespondentEditDialogComponent } from '../../common/edit-dialog/correspondent-edit-dialog/correspondent-edit-dialog.component' | ||||
| import { PageHeaderComponent } from '../../common/page-header/page-header.component' | ||||
| import { ManagementListComponent } from '../management-list/management-list.component' | ||||
| @@ -45,7 +45,7 @@ export class CorrespondentListComponent extends ManagementListComponent<Correspo | ||||
|   constructor( | ||||
|     correspondentsService: CorrespondentService, | ||||
|     modalService: NgbModal, | ||||
|     toastService: ToastService, | ||||
|     notificationService: NotificationService, | ||||
|     documentListViewService: DocumentListViewService, | ||||
|     permissionsService: PermissionsService, | ||||
|     private datePipe: CustomDatePipe | ||||
| @@ -54,7 +54,7 @@ export class CorrespondentListComponent extends ManagementListComponent<Correspo | ||||
|       correspondentsService, | ||||
|       modalService, | ||||
|       CorrespondentEditDialogComponent, | ||||
|       toastService, | ||||
|       notificationService, | ||||
|       documentListViewService, | ||||
|       permissionsService, | ||||
|       FILTER_HAS_CORRESPONDENT_ANY, | ||||
|   | ||||
| @@ -21,10 +21,10 @@ import { | ||||
| import { FILTER_CUSTOM_FIELDS_QUERY } from 'src/app/data/filter-rule-type' | ||||
| import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive' | ||||
| import { DocumentListViewService } from 'src/app/services/document-list-view.service' | ||||
| import { NotificationService } from 'src/app/services/notification.service' | ||||
| import { PermissionsService } from 'src/app/services/permissions.service' | ||||
| import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service' | ||||
| import { SettingsService } from 'src/app/services/settings.service' | ||||
| import { ToastService } from 'src/app/services/toast.service' | ||||
| import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component' | ||||
| import { CustomFieldEditDialogComponent } from '../../common/edit-dialog/custom-field-edit-dialog/custom-field-edit-dialog.component' | ||||
| import { PageHeaderComponent } from '../../common/page-header/page-header.component' | ||||
| @@ -48,7 +48,7 @@ describe('CustomFieldsComponent', () => { | ||||
|   let fixture: ComponentFixture<CustomFieldsComponent> | ||||
|   let customFieldsService: CustomFieldsService | ||||
|   let modalService: NgbModal | ||||
|   let toastService: ToastService | ||||
|   let notificationService: NotificationService | ||||
|   let listViewService: DocumentListViewService | ||||
|   let settingsService: SettingsService | ||||
|  | ||||
| @@ -89,7 +89,7 @@ describe('CustomFieldsComponent', () => { | ||||
|       }) | ||||
|     ) | ||||
|     modalService = TestBed.inject(NgbModal) | ||||
|     toastService = TestBed.inject(ToastService) | ||||
|     notificationService = TestBed.inject(NotificationService) | ||||
|     listViewService = TestBed.inject(DocumentListViewService) | ||||
|     settingsService = TestBed.inject(SettingsService) | ||||
|     settingsService.currentUser = { id: 0, username: 'test' } | ||||
| @@ -104,8 +104,8 @@ describe('CustomFieldsComponent', () => { | ||||
|   it('should support create, show notification on error / success', () => { | ||||
|     let modal: NgbModalRef | ||||
|     modalService.activeInstances.subscribe((m) => (modal = m[m.length - 1])) | ||||
|     const toastErrorSpy = jest.spyOn(toastService, 'showError') | ||||
|     const toastInfoSpy = jest.spyOn(toastService, 'showInfo') | ||||
|     const notificationErrorSpy = jest.spyOn(notificationService, 'showError') | ||||
|     const notificationInfoSpy = jest.spyOn(notificationService, 'showInfo') | ||||
|     const reloadSpy = jest.spyOn(component, 'reload') | ||||
|  | ||||
|     const createButton = fixture.debugElement.queryAll(By.css('button'))[1] | ||||
| @@ -116,12 +116,12 @@ describe('CustomFieldsComponent', () => { | ||||
|  | ||||
|     // fail first | ||||
|     editDialog.failed.emit({ error: 'error creating item' }) | ||||
|     expect(toastErrorSpy).toHaveBeenCalled() | ||||
|     expect(notificationErrorSpy).toHaveBeenCalled() | ||||
|     expect(reloadSpy).not.toHaveBeenCalled() | ||||
|  | ||||
|     // succeed | ||||
|     editDialog.succeeded.emit(fields[0]) | ||||
|     expect(toastInfoSpy).toHaveBeenCalled() | ||||
|     expect(notificationInfoSpy).toHaveBeenCalled() | ||||
|     expect(reloadSpy).toHaveBeenCalled() | ||||
|     jest.advanceTimersByTime(100) | ||||
|   }) | ||||
| @@ -129,8 +129,8 @@ describe('CustomFieldsComponent', () => { | ||||
|   it('should support edit, show notification on error / success', () => { | ||||
|     let modal: NgbModalRef | ||||
|     modalService.activeInstances.subscribe((m) => (modal = m[m.length - 1])) | ||||
|     const toastErrorSpy = jest.spyOn(toastService, 'showError') | ||||
|     const toastInfoSpy = jest.spyOn(toastService, 'showInfo') | ||||
|     const notificationErrorSpy = jest.spyOn(notificationService, 'showError') | ||||
|     const notificationInfoSpy = jest.spyOn(notificationService, 'showInfo') | ||||
|     const reloadSpy = jest.spyOn(component, 'reload') | ||||
|  | ||||
|     const editButton = fixture.debugElement.queryAll(By.css('button'))[2] | ||||
| @@ -142,19 +142,19 @@ describe('CustomFieldsComponent', () => { | ||||
|  | ||||
|     // fail first | ||||
|     editDialog.failed.emit({ error: 'error editing item' }) | ||||
|     expect(toastErrorSpy).toHaveBeenCalled() | ||||
|     expect(notificationErrorSpy).toHaveBeenCalled() | ||||
|     expect(reloadSpy).not.toHaveBeenCalled() | ||||
|  | ||||
|     // succeed | ||||
|     editDialog.succeeded.emit(fields[0]) | ||||
|     expect(toastInfoSpy).toHaveBeenCalled() | ||||
|     expect(notificationInfoSpy).toHaveBeenCalled() | ||||
|     expect(reloadSpy).toHaveBeenCalled() | ||||
|   }) | ||||
|  | ||||
|   it('should support delete, show notification on error / success', () => { | ||||
|     let modal: NgbModalRef | ||||
|     modalService.activeInstances.subscribe((m) => (modal = m[m.length - 1])) | ||||
|     const toastErrorSpy = jest.spyOn(toastService, 'showError') | ||||
|     const notificationErrorSpy = jest.spyOn(notificationService, 'showError') | ||||
|     const deleteSpy = jest.spyOn(customFieldsService, 'delete') | ||||
|     const reloadSpy = jest.spyOn(component, 'reload') | ||||
|  | ||||
| @@ -167,7 +167,7 @@ describe('CustomFieldsComponent', () => { | ||||
|     // fail first | ||||
|     deleteSpy.mockReturnValueOnce(throwError(() => new Error('error deleting'))) | ||||
|     editDialog.confirmClicked.emit() | ||||
|     expect(toastErrorSpy).toHaveBeenCalled() | ||||
|     expect(notificationErrorSpy).toHaveBeenCalled() | ||||
|     expect(reloadSpy).not.toHaveBeenCalled() | ||||
|  | ||||
|     // succeed | ||||
|   | ||||
| @@ -14,12 +14,12 @@ import { | ||||
| import { FILTER_CUSTOM_FIELDS_QUERY } from 'src/app/data/filter-rule-type' | ||||
| import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive' | ||||
| import { DocumentListViewService } from 'src/app/services/document-list-view.service' | ||||
| import { NotificationService } from 'src/app/services/notification.service' | ||||
| import { PermissionsService } from 'src/app/services/permissions.service' | ||||
| import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service' | ||||
| import { DocumentService } from 'src/app/services/rest/document.service' | ||||
| import { SavedViewService } from 'src/app/services/rest/saved-view.service' | ||||
| import { SettingsService } from 'src/app/services/settings.service' | ||||
| import { ToastService } from 'src/app/services/toast.service' | ||||
| import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component' | ||||
| import { CustomFieldEditDialogComponent } from '../../common/edit-dialog/custom-field-edit-dialog/custom-field-edit-dialog.component' | ||||
| import { EditDialogMode } from '../../common/edit-dialog/edit-dialog.component' | ||||
| @@ -48,7 +48,7 @@ export class CustomFieldsComponent | ||||
|     private customFieldsService: CustomFieldsService, | ||||
|     public permissionsService: PermissionsService, | ||||
|     private modalService: NgbModal, | ||||
|     private toastService: ToastService, | ||||
|     private notificationService: NotificationService, | ||||
|     private documentListViewService: DocumentListViewService, | ||||
|     private settingsService: SettingsService, | ||||
|     private documentService: DocumentService, | ||||
| @@ -86,7 +86,9 @@ export class CustomFieldsComponent | ||||
|     modal.componentInstance.succeeded | ||||
|       .pipe(takeUntil(this.unsubscribeNotifier)) | ||||
|       .subscribe((newField) => { | ||||
|         this.toastService.showInfo($localize`Saved field "${newField.name}".`) | ||||
|         this.notificationService.showInfo( | ||||
|           $localize`Saved field "${newField.name}".` | ||||
|         ) | ||||
|         this.customFieldsService.clearCache() | ||||
|         this.settingsService.initializeDisplayFields() | ||||
|         this.documentService.reload() | ||||
| @@ -95,7 +97,7 @@ export class CustomFieldsComponent | ||||
|     modal.componentInstance.failed | ||||
|       .pipe(takeUntil(this.unsubscribeNotifier)) | ||||
|       .subscribe((e) => { | ||||
|         this.toastService.showError($localize`Error saving field.`, e) | ||||
|         this.notificationService.showError($localize`Error saving field.`, e) | ||||
|       }) | ||||
|   } | ||||
|  | ||||
| @@ -113,7 +115,9 @@ export class CustomFieldsComponent | ||||
|       this.customFieldsService.delete(field).subscribe({ | ||||
|         next: () => { | ||||
|           modal.close() | ||||
|           this.toastService.showInfo($localize`Deleted field "${field.name}"`) | ||||
|           this.notificationService.showInfo( | ||||
|             $localize`Deleted field "${field.name}"` | ||||
|           ) | ||||
|           this.customFieldsService.clearCache() | ||||
|           this.settingsService.initializeDisplayFields() | ||||
|           this.documentService.reload() | ||||
| @@ -121,7 +125,7 @@ export class CustomFieldsComponent | ||||
|           this.reload() | ||||
|         }, | ||||
|         error: (e) => { | ||||
|           this.toastService.showError( | ||||
|           this.notificationService.showError( | ||||
|             $localize`Error deleting field "${field.name}".`, | ||||
|             e | ||||
|           ) | ||||
|   | ||||
| @@ -12,12 +12,12 @@ import { FILTER_HAS_DOCUMENT_TYPE_ANY } from 'src/app/data/filter-rule-type' | ||||
| import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive' | ||||
| import { SortableDirective } from 'src/app/directives/sortable.directive' | ||||
| import { DocumentListViewService } from 'src/app/services/document-list-view.service' | ||||
| import { NotificationService } from 'src/app/services/notification.service' | ||||
| import { | ||||
|   PermissionsService, | ||||
|   PermissionType, | ||||
| } from 'src/app/services/permissions.service' | ||||
| import { DocumentTypeService } from 'src/app/services/rest/document-type.service' | ||||
| import { ToastService } from 'src/app/services/toast.service' | ||||
| import { DocumentTypeEditDialogComponent } from '../../common/edit-dialog/document-type-edit-dialog/document-type-edit-dialog.component' | ||||
| import { PageHeaderComponent } from '../../common/page-header/page-header.component' | ||||
| import { ManagementListComponent } from '../management-list/management-list.component' | ||||
| @@ -43,7 +43,7 @@ export class DocumentTypeListComponent extends ManagementListComponent<DocumentT | ||||
|   constructor( | ||||
|     documentTypeService: DocumentTypeService, | ||||
|     modalService: NgbModal, | ||||
|     toastService: ToastService, | ||||
|     notificationService: NotificationService, | ||||
|     documentListViewService: DocumentListViewService, | ||||
|     permissionsService: PermissionsService | ||||
|   ) { | ||||
| @@ -51,7 +51,7 @@ export class DocumentTypeListComponent extends ManagementListComponent<DocumentT | ||||
|       documentTypeService, | ||||
|       modalService, | ||||
|       DocumentTypeEditDialogComponent, | ||||
|       toastService, | ||||
|       notificationService, | ||||
|       documentListViewService, | ||||
|       permissionsService, | ||||
|       FILTER_HAS_DOCUMENT_TYPE_ANY, | ||||
|   | ||||
| @@ -24,11 +24,11 @@ import { IfPermissionsDirective } from 'src/app/directives/if-permissions.direct | ||||
| import { PermissionsGuard } from 'src/app/guards/permissions.guard' | ||||
| import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe' | ||||
| import { SafeHtmlPipe } from 'src/app/pipes/safehtml.pipe' | ||||
| import { NotificationService } from 'src/app/services/notification.service' | ||||
| import { PermissionsService } from 'src/app/services/permissions.service' | ||||
| import { MailAccountService } from 'src/app/services/rest/mail-account.service' | ||||
| import { MailRuleService } from 'src/app/services/rest/mail-rule.service' | ||||
| import { SettingsService } from 'src/app/services/settings.service' | ||||
| import { ToastService } from 'src/app/services/toast.service' | ||||
| import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component' | ||||
| import { EditDialogMode } from '../../common/edit-dialog/edit-dialog.component' | ||||
| import { MailAccountEditDialogComponent } from '../../common/edit-dialog/mail-account-edit-dialog/mail-account-edit-dialog.component' | ||||
| @@ -63,7 +63,7 @@ describe('MailComponent', () => { | ||||
|   let mailAccountService: MailAccountService | ||||
|   let mailRuleService: MailRuleService | ||||
|   let modalService: NgbModal | ||||
|   let toastService: ToastService | ||||
|   let notificationService: NotificationService | ||||
|   let permissionsService: PermissionsService | ||||
|   let activatedRoute: ActivatedRoute | ||||
|   let settingsService: SettingsService | ||||
| @@ -111,7 +111,7 @@ describe('MailComponent', () => { | ||||
|     mailAccountService = TestBed.inject(MailAccountService) | ||||
|     mailRuleService = TestBed.inject(MailRuleService) | ||||
|     modalService = TestBed.inject(NgbModal) | ||||
|     toastService = TestBed.inject(ToastService) | ||||
|     notificationService = TestBed.inject(NotificationService) | ||||
|     permissionsService = TestBed.inject(PermissionsService) | ||||
|     activatedRoute = TestBed.inject(ActivatedRoute) | ||||
|     settingsService = TestBed.inject(SettingsService) | ||||
| @@ -157,25 +157,25 @@ describe('MailComponent', () => { | ||||
|   } | ||||
|  | ||||
|   it('should show errors on load if load mailAccounts failure', () => { | ||||
|     const toastErrorSpy = jest.spyOn(toastService, 'showError') | ||||
|     const notificationErrorSpy = jest.spyOn(notificationService, 'showError') | ||||
|     jest | ||||
|       .spyOn(mailAccountService, 'listAll') | ||||
|       .mockImplementation(() => | ||||
|         throwError(() => new Error('failed to load mail accounts')) | ||||
|       ) | ||||
|     completeSetup(mailAccountService) | ||||
|     expect(toastErrorSpy).toBeCalled() | ||||
|     expect(notificationErrorSpy).toBeCalled() | ||||
|   }) | ||||
|  | ||||
|   it('should show errors on load if load mailRules failure', () => { | ||||
|     const toastErrorSpy = jest.spyOn(toastService, 'showError') | ||||
|     const notificationErrorSpy = jest.spyOn(notificationService, 'showError') | ||||
|     jest | ||||
|       .spyOn(mailRuleService, 'listAll') | ||||
|       .mockImplementation(() => | ||||
|         throwError(() => new Error('failed to load mail rules')) | ||||
|       ) | ||||
|     completeSetup(mailRuleService) | ||||
|     expect(toastErrorSpy).toBeCalled() | ||||
|     expect(notificationErrorSpy).toBeCalled() | ||||
|   }) | ||||
|  | ||||
|   it('should support edit / create mail account, show error if needed', () => { | ||||
| @@ -184,12 +184,12 @@ describe('MailComponent', () => { | ||||
|     modalService.activeInstances.subscribe((refs) => (modal = refs[0])) | ||||
|     component.editMailAccount(mailAccounts[0] as MailAccount) | ||||
|     let editDialog = modal.componentInstance as MailAccountEditDialogComponent | ||||
|     const toastErrorSpy = jest.spyOn(toastService, 'showError') | ||||
|     const toastInfoSpy = jest.spyOn(toastService, 'showInfo') | ||||
|     const notificationErrorSpy = jest.spyOn(notificationService, 'showError') | ||||
|     const notificationInfoSpy = jest.spyOn(notificationService, 'showInfo') | ||||
|     editDialog.failed.emit() | ||||
|     expect(toastErrorSpy).toBeCalled() | ||||
|     expect(notificationErrorSpy).toBeCalled() | ||||
|     editDialog.succeeded.emit(mailAccounts[0]) | ||||
|     expect(toastInfoSpy).toHaveBeenCalledWith( | ||||
|     expect(notificationInfoSpy).toHaveBeenCalledWith( | ||||
|       `Saved account "${mailAccounts[0].name}".` | ||||
|     ) | ||||
|     editDialog.cancel() | ||||
| @@ -203,35 +203,37 @@ describe('MailComponent', () => { | ||||
|     component.deleteMailAccount(mailAccounts[0] as MailAccount) | ||||
|     const deleteDialog = modal.componentInstance as ConfirmDialogComponent | ||||
|     const deleteSpy = jest.spyOn(mailAccountService, 'delete') | ||||
|     const toastErrorSpy = jest.spyOn(toastService, 'showError') | ||||
|     const toastInfoSpy = jest.spyOn(toastService, 'showInfo') | ||||
|     const notificationErrorSpy = jest.spyOn(notificationService, 'showError') | ||||
|     const notificationInfoSpy = jest.spyOn(notificationService, 'showInfo') | ||||
|     const listAllSpy = jest.spyOn(mailAccountService, 'listAll') | ||||
|     deleteSpy.mockReturnValueOnce( | ||||
|       throwError(() => new Error('error deleting mail account')) | ||||
|     ) | ||||
|     deleteDialog.confirm() | ||||
|     expect(toastErrorSpy).toBeCalled() | ||||
|     expect(notificationErrorSpy).toBeCalled() | ||||
|     deleteSpy.mockReturnValueOnce(of(true)) | ||||
|     deleteDialog.confirm() | ||||
|     expect(listAllSpy).toHaveBeenCalled() | ||||
|     expect(toastInfoSpy).toHaveBeenCalledWith('Deleted mail account "account1"') | ||||
|     expect(notificationInfoSpy).toHaveBeenCalledWith( | ||||
|       'Deleted mail account "account1"' | ||||
|     ) | ||||
|   }) | ||||
|  | ||||
|   it('should support process mail account, show error if needed', () => { | ||||
|     completeSetup() | ||||
|     const processSpy = jest.spyOn(mailAccountService, 'processAccount') | ||||
|     const toastErrorSpy = jest.spyOn(toastService, 'showError') | ||||
|     const toastInfoSpy = jest.spyOn(toastService, 'showInfo') | ||||
|     const notificationErrorSpy = jest.spyOn(notificationService, 'showError') | ||||
|     const notificationInfoSpy = jest.spyOn(notificationService, 'showInfo') | ||||
|     component.processAccount(mailAccounts[0] as MailAccount) | ||||
|     expect(processSpy).toHaveBeenCalled() | ||||
|     processSpy.mockReturnValueOnce( | ||||
|       throwError(() => new Error('error processing mail account')) | ||||
|     ) | ||||
|     component.processAccount(mailAccounts[0] as MailAccount) | ||||
|     expect(toastErrorSpy).toHaveBeenCalled() | ||||
|     expect(notificationErrorSpy).toHaveBeenCalled() | ||||
|     processSpy.mockReturnValueOnce(of(true)) | ||||
|     component.processAccount(mailAccounts[0] as MailAccount) | ||||
|     expect(toastInfoSpy).toHaveBeenCalledWith( | ||||
|     expect(notificationInfoSpy).toHaveBeenCalledWith( | ||||
|       'Processing mail account "account1"' | ||||
|     ) | ||||
|   }) | ||||
| @@ -242,12 +244,12 @@ describe('MailComponent', () => { | ||||
|     modalService.activeInstances.subscribe((refs) => (modal = refs[0])) | ||||
|     component.editMailRule(mailRules[0] as MailRule) | ||||
|     const editDialog = modal.componentInstance as MailRuleEditDialogComponent | ||||
|     const toastErrorSpy = jest.spyOn(toastService, 'showError') | ||||
|     const toastInfoSpy = jest.spyOn(toastService, 'showInfo') | ||||
|     const notificationErrorSpy = jest.spyOn(notificationService, 'showError') | ||||
|     const notificationInfoSpy = jest.spyOn(notificationService, 'showInfo') | ||||
|     editDialog.failed.emit() | ||||
|     expect(toastErrorSpy).toBeCalled() | ||||
|     expect(notificationErrorSpy).toBeCalled() | ||||
|     editDialog.succeeded.emit(mailRules[0]) | ||||
|     expect(toastInfoSpy).toHaveBeenCalledWith( | ||||
|     expect(notificationInfoSpy).toHaveBeenCalledWith( | ||||
|       `Saved rule "${mailRules[0].name}".` | ||||
|     ) | ||||
|     editDialog.cancel() | ||||
| @@ -272,18 +274,20 @@ describe('MailComponent', () => { | ||||
|     component.deleteMailRule(mailRules[0] as MailRule) | ||||
|     const deleteDialog = modal.componentInstance as ConfirmDialogComponent | ||||
|     const deleteSpy = jest.spyOn(mailRuleService, 'delete') | ||||
|     const toastErrorSpy = jest.spyOn(toastService, 'showError') | ||||
|     const toastInfoSpy = jest.spyOn(toastService, 'showInfo') | ||||
|     const notificationErrorSpy = jest.spyOn(notificationService, 'showError') | ||||
|     const notificationInfoSpy = jest.spyOn(notificationService, 'showInfo') | ||||
|     const listAllSpy = jest.spyOn(mailRuleService, 'listAll') | ||||
|     deleteSpy.mockReturnValueOnce( | ||||
|       throwError(() => new Error('error deleting mail rule "rule1"')) | ||||
|     ) | ||||
|     deleteDialog.confirm() | ||||
|     expect(toastErrorSpy).toBeCalled() | ||||
|     expect(notificationErrorSpy).toBeCalled() | ||||
|     deleteSpy.mockReturnValueOnce(of(true)) | ||||
|     deleteDialog.confirm() | ||||
|     expect(listAllSpy).toHaveBeenCalled() | ||||
|     expect(toastInfoSpy).toHaveBeenCalledWith('Deleted mail rule "rule1"') | ||||
|     expect(notificationInfoSpy).toHaveBeenCalledWith( | ||||
|       'Deleted mail rule "rule1"' | ||||
|     ) | ||||
|   }) | ||||
|  | ||||
|   it('should support edit permissions on mail rule objects', () => { | ||||
| @@ -303,8 +307,8 @@ describe('MailComponent', () => { | ||||
|     } | ||||
|     let modal: NgbModalRef | ||||
|     modalService.activeInstances.subscribe((refs) => (modal = refs[0])) | ||||
|     const toastErrorSpy = jest.spyOn(toastService, 'showError') | ||||
|     const toastInfoSpy = jest.spyOn(toastService, 'showInfo') | ||||
|     const notificationErrorSpy = jest.spyOn(notificationService, 'showError') | ||||
|     const notificationInfoSpy = jest.spyOn(notificationService, 'showInfo') | ||||
|     const rulePatchSpy = jest.spyOn(mailRuleService, 'patch') | ||||
|     component.editPermissions(mailRules[0] as MailRule) | ||||
|     expect(modal).not.toBeUndefined() | ||||
| @@ -316,10 +320,10 @@ describe('MailComponent', () => { | ||||
|     ) | ||||
|     dialog.confirmClicked.emit({ permissions: perms, merge: true }) | ||||
|     expect(rulePatchSpy).toHaveBeenCalled() | ||||
|     expect(toastErrorSpy).toHaveBeenCalled() | ||||
|     expect(notificationErrorSpy).toHaveBeenCalled() | ||||
|     rulePatchSpy.mockReturnValueOnce(of(mailRules[0] as MailRule)) | ||||
|     dialog.confirmClicked.emit({ permissions: perms, merge: true }) | ||||
|     expect(toastInfoSpy).toHaveBeenCalledWith('Permissions updated') | ||||
|     expect(notificationInfoSpy).toHaveBeenCalledWith('Permissions updated') | ||||
|  | ||||
|     modalService.dismissAll() | ||||
|   }) | ||||
| @@ -356,15 +360,15 @@ describe('MailComponent', () => { | ||||
|     const toggleInput = fixture.debugElement.query( | ||||
|       By.css('input[type="checkbox"]') | ||||
|     ) | ||||
|     const toastErrorSpy = jest.spyOn(toastService, 'showError') | ||||
|     const toastInfoSpy = jest.spyOn(toastService, 'showInfo') | ||||
|     const notificationErrorSpy = jest.spyOn(notificationService, 'showError') | ||||
|     const notificationInfoSpy = jest.spyOn(notificationService, 'showInfo') | ||||
|     // fail first | ||||
|     patchSpy.mockReturnValueOnce( | ||||
|       throwError(() => new Error('Error getting config')) | ||||
|     ) | ||||
|     toggleInput.nativeElement.click() | ||||
|     expect(patchSpy).toHaveBeenCalled() | ||||
|     expect(toastErrorSpy).toHaveBeenCalled() | ||||
|     expect(notificationErrorSpy).toHaveBeenCalled() | ||||
|     // succeed second | ||||
|     patchSpy.mockReturnValueOnce(of(mailRules[0] as MailRule)) | ||||
|     toggleInput.nativeElement.click() | ||||
| @@ -373,7 +377,7 @@ describe('MailComponent', () => { | ||||
|     ) | ||||
|     toggleInput.nativeElement.click() | ||||
|     expect(patchSpy).toHaveBeenCalled() | ||||
|     expect(toastInfoSpy).toHaveBeenCalled() | ||||
|     expect(notificationInfoSpy).toHaveBeenCalled() | ||||
|   }) | ||||
|  | ||||
|   it('should show success message when oauth account is connected', () => { | ||||
| @@ -381,9 +385,9 @@ describe('MailComponent', () => { | ||||
|     jest | ||||
|       .spyOn(activatedRoute, 'queryParamMap', 'get') | ||||
|       .mockReturnValue(of(convertToParamMap(queryParams))) | ||||
|     const toastInfoSpy = jest.spyOn(toastService, 'showInfo') | ||||
|     const notificationInfoSpy = jest.spyOn(notificationService, 'showInfo') | ||||
|     completeSetup() | ||||
|     expect(toastInfoSpy).toHaveBeenCalled() | ||||
|     expect(notificationInfoSpy).toHaveBeenCalled() | ||||
|   }) | ||||
|  | ||||
|   it('should show error message when oauth account connect fails', () => { | ||||
| @@ -391,9 +395,9 @@ describe('MailComponent', () => { | ||||
|     jest | ||||
|       .spyOn(activatedRoute, 'queryParamMap', 'get') | ||||
|       .mockReturnValue(of(convertToParamMap(queryParams))) | ||||
|     const toastErrorSpy = jest.spyOn(toastService, 'showError') | ||||
|     const notificationErrorSpy = jest.spyOn(notificationService, 'showError') | ||||
|     completeSetup() | ||||
|     expect(toastErrorSpy).toHaveBeenCalled() | ||||
|     expect(notificationErrorSpy).toHaveBeenCalled() | ||||
|   }) | ||||
|  | ||||
|   it('should open account edit dialog if oauth account is connected', () => { | ||||
|   | ||||
| @@ -11,6 +11,7 @@ import { ObjectWithPermissions } from 'src/app/data/object-with-permissions' | ||||
| import { SETTINGS_KEYS } from 'src/app/data/ui-settings' | ||||
| import { IfOwnerDirective } from 'src/app/directives/if-owner.directive' | ||||
| import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive' | ||||
| import { NotificationService } from 'src/app/services/notification.service' | ||||
| import { | ||||
|   PermissionAction, | ||||
|   PermissionsService, | ||||
| @@ -19,7 +20,6 @@ import { AbstractPaperlessService } from 'src/app/services/rest/abstract-paperle | ||||
| import { MailAccountService } from 'src/app/services/rest/mail-account.service' | ||||
| import { MailRuleService } from 'src/app/services/rest/mail-rule.service' | ||||
| import { SettingsService } from 'src/app/services/settings.service' | ||||
| import { ToastService } from 'src/app/services/toast.service' | ||||
| import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component' | ||||
| import { EditDialogMode } from '../../common/edit-dialog/edit-dialog.component' | ||||
| import { MailAccountEditDialogComponent } from '../../common/edit-dialog/mail-account-edit-dialog/mail-account-edit-dialog.component' | ||||
| @@ -71,7 +71,7 @@ export class MailComponent | ||||
|   constructor( | ||||
|     public mailAccountService: MailAccountService, | ||||
|     public mailRuleService: MailRuleService, | ||||
|     private toastService: ToastService, | ||||
|     private notificationService: NotificationService, | ||||
|     private modalService: NgbModal, | ||||
|     public permissionsService: PermissionsService, | ||||
|     private settingsService: SettingsService, | ||||
| @@ -104,7 +104,7 @@ export class MailComponent | ||||
|           this.showAccounts = true | ||||
|         }, | ||||
|         error: (e) => { | ||||
|           this.toastService.showError( | ||||
|           this.notificationService.showError( | ||||
|             $localize`Error retrieving mail accounts`, | ||||
|             e | ||||
|           ) | ||||
| @@ -127,7 +127,10 @@ export class MailComponent | ||||
|           this.showRules = true | ||||
|         }, | ||||
|         error: (e) => { | ||||
|           this.toastService.showError($localize`Error retrieving mail rules`, e) | ||||
|           this.notificationService.showError( | ||||
|             $localize`Error retrieving mail rules`, | ||||
|             e | ||||
|           ) | ||||
|         }, | ||||
|       }) | ||||
|  | ||||
| @@ -135,7 +138,9 @@ export class MailComponent | ||||
|       if (params.get('oauth_success')) { | ||||
|         const success = params.get('oauth_success') === '1' | ||||
|         if (success) { | ||||
|           this.toastService.showInfo($localize`OAuth2 authentication success`) | ||||
|           this.notificationService.showInfo( | ||||
|             $localize`OAuth2 authentication success` | ||||
|           ) | ||||
|           this.oAuthAccountId = parseInt(params.get('account_id')) | ||||
|           if (this.mailAccounts.length > 0) { | ||||
|             this.editMailAccount( | ||||
| @@ -145,7 +150,7 @@ export class MailComponent | ||||
|             ) | ||||
|           } | ||||
|         } else { | ||||
|           this.toastService.showError( | ||||
|           this.notificationService.showError( | ||||
|             $localize`OAuth2 authentication failed, see logs for details` | ||||
|           ) | ||||
|         } | ||||
| @@ -169,7 +174,7 @@ export class MailComponent | ||||
|     modal.componentInstance.succeeded | ||||
|       .pipe(takeUntil(this.unsubscribeNotifier)) | ||||
|       .subscribe((newMailAccount) => { | ||||
|         this.toastService.showInfo( | ||||
|         this.notificationService.showInfo( | ||||
|           $localize`Saved account "${newMailAccount.name}".` | ||||
|         ) | ||||
|         this.mailAccountService.clearCache() | ||||
| @@ -182,7 +187,7 @@ export class MailComponent | ||||
|     modal.componentInstance.failed | ||||
|       .pipe(takeUntil(this.unsubscribeNotifier)) | ||||
|       .subscribe((e) => { | ||||
|         this.toastService.showError($localize`Error saving account.`, e) | ||||
|         this.notificationService.showError($localize`Error saving account.`, e) | ||||
|       }) | ||||
|   } | ||||
|  | ||||
| @@ -200,7 +205,7 @@ export class MailComponent | ||||
|       this.mailAccountService.delete(account).subscribe({ | ||||
|         next: () => { | ||||
|           modal.close() | ||||
|           this.toastService.showInfo( | ||||
|           this.notificationService.showInfo( | ||||
|             $localize`Deleted mail account "${account.name}"` | ||||
|           ) | ||||
|           this.mailAccountService.clearCache() | ||||
| @@ -211,7 +216,7 @@ export class MailComponent | ||||
|             }) | ||||
|         }, | ||||
|         error: (e) => { | ||||
|           this.toastService.showError( | ||||
|           this.notificationService.showError( | ||||
|             $localize`Error deleting mail account "${account.name}".`, | ||||
|             e | ||||
|           ) | ||||
| @@ -223,12 +228,12 @@ export class MailComponent | ||||
|   processAccount(account: MailAccount) { | ||||
|     this.mailAccountService.processAccount(account).subscribe({ | ||||
|       next: () => { | ||||
|         this.toastService.showInfo( | ||||
|         this.notificationService.showInfo( | ||||
|           $localize`Processing mail account "${account.name}"` | ||||
|         ) | ||||
|       }, | ||||
|       error: (e) => { | ||||
|         this.toastService.showError( | ||||
|         this.notificationService.showError( | ||||
|           $localize`Error processing mail account "${account.name}"`, | ||||
|           e | ||||
|         ) | ||||
| @@ -247,7 +252,9 @@ export class MailComponent | ||||
|     modal.componentInstance.succeeded | ||||
|       .pipe(takeUntil(this.unsubscribeNotifier)) | ||||
|       .subscribe((newMailRule) => { | ||||
|         this.toastService.showInfo($localize`Saved rule "${newMailRule.name}".`) | ||||
|         this.notificationService.showInfo( | ||||
|           $localize`Saved rule "${newMailRule.name}".` | ||||
|         ) | ||||
|         this.mailRuleService.clearCache() | ||||
|         this.mailRuleService | ||||
|           .listAll(null, null, { full_perms: true }) | ||||
| @@ -258,7 +265,7 @@ export class MailComponent | ||||
|     modal.componentInstance.failed | ||||
|       .pipe(takeUntil(this.unsubscribeNotifier)) | ||||
|       .subscribe((e) => { | ||||
|         this.toastService.showError($localize`Error saving rule.`, e) | ||||
|         this.notificationService.showError($localize`Error saving rule.`, e) | ||||
|       }) | ||||
|   } | ||||
|  | ||||
| @@ -272,14 +279,14 @@ export class MailComponent | ||||
|   onMailRuleEnableToggled(rule: MailRule) { | ||||
|     this.mailRuleService.patch(rule).subscribe({ | ||||
|       next: () => { | ||||
|         this.toastService.showInfo( | ||||
|         this.notificationService.showInfo( | ||||
|           rule.enabled | ||||
|             ? $localize`Rule "${rule.name}" enabled.` | ||||
|             : $localize`Rule "${rule.name}" disabled.` | ||||
|         ) | ||||
|       }, | ||||
|       error: (e) => { | ||||
|         this.toastService.showError( | ||||
|         this.notificationService.showError( | ||||
|           $localize`Error toggling rule "${rule.name}".`, | ||||
|           e | ||||
|         ) | ||||
| @@ -301,7 +308,7 @@ export class MailComponent | ||||
|       this.mailRuleService.delete(rule).subscribe({ | ||||
|         next: () => { | ||||
|           modal.close() | ||||
|           this.toastService.showInfo( | ||||
|           this.notificationService.showInfo( | ||||
|             $localize`Deleted mail rule "${rule.name}"` | ||||
|           ) | ||||
|           this.mailRuleService.clearCache() | ||||
| @@ -312,7 +319,7 @@ export class MailComponent | ||||
|             }) | ||||
|         }, | ||||
|         error: (e) => { | ||||
|           this.toastService.showError( | ||||
|           this.notificationService.showError( | ||||
|             $localize`Error deleting mail rule "${rule.name}".`, | ||||
|             e | ||||
|           ) | ||||
| @@ -337,11 +344,11 @@ export class MailComponent | ||||
|         object['set_permissions'] = permissions['set_permissions'] | ||||
|         service.patch(object).subscribe({ | ||||
|           next: () => { | ||||
|             this.toastService.showInfo($localize`Permissions updated`) | ||||
|             this.notificationService.showInfo($localize`Permissions updated`) | ||||
|             modal.close() | ||||
|           }, | ||||
|           error: (e) => { | ||||
|             this.toastService.showError( | ||||
|             this.notificationService.showError( | ||||
|               $localize`Error updating permissions`, | ||||
|               e | ||||
|             ) | ||||
|   | ||||
| @@ -35,13 +35,13 @@ import { SortableDirective } from 'src/app/directives/sortable.directive' | ||||
| import { PermissionsGuard } from 'src/app/guards/permissions.guard' | ||||
| import { SafeHtmlPipe } from 'src/app/pipes/safehtml.pipe' | ||||
| import { DocumentListViewService } from 'src/app/services/document-list-view.service' | ||||
| import { NotificationService } from 'src/app/services/notification.service' | ||||
| import { | ||||
|   PermissionAction, | ||||
|   PermissionsService, | ||||
| } from 'src/app/services/permissions.service' | ||||
| import { BulkEditObjectOperation } from 'src/app/services/rest/abstract-name-filter-service' | ||||
| import { TagService } from 'src/app/services/rest/tag.service' | ||||
| import { ToastService } from 'src/app/services/toast.service' | ||||
| import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component' | ||||
| import { EditDialogComponent } from '../../common/edit-dialog/edit-dialog.component' | ||||
| import { PageHeaderComponent } from '../../common/page-header/page-header.component' | ||||
| @@ -76,7 +76,7 @@ describe('ManagementListComponent', () => { | ||||
|   let fixture: ComponentFixture<ManagementListComponent<Tag>> | ||||
|   let tagService: TagService | ||||
|   let modalService: NgbModal | ||||
|   let toastService: ToastService | ||||
|   let notificationService: NotificationService | ||||
|   let documentListViewService: DocumentListViewService | ||||
|   let permissionsService: PermissionsService | ||||
|  | ||||
| @@ -129,7 +129,7 @@ describe('ManagementListComponent', () => { | ||||
|       .spyOn(permissionsService, 'currentUserOwnsObject') | ||||
|       .mockReturnValue(true) | ||||
|     modalService = TestBed.inject(NgbModal) | ||||
|     toastService = TestBed.inject(ToastService) | ||||
|     notificationService = TestBed.inject(NotificationService) | ||||
|     documentListViewService = TestBed.inject(DocumentListViewService) | ||||
|     fixture = TestBed.createComponent(TagListComponent) | ||||
|     component = fixture.componentInstance | ||||
| @@ -160,8 +160,8 @@ describe('ManagementListComponent', () => { | ||||
|   it('should support create, show notification on error / success', () => { | ||||
|     let modal: NgbModalRef | ||||
|     modalService.activeInstances.subscribe((m) => (modal = m[m.length - 1])) | ||||
|     const toastErrorSpy = jest.spyOn(toastService, 'showError') | ||||
|     const toastInfoSpy = jest.spyOn(toastService, 'showInfo') | ||||
|     const notificationErrorSpy = jest.spyOn(notificationService, 'showError') | ||||
|     const notificationInfoSpy = jest.spyOn(notificationService, 'showInfo') | ||||
|     const reloadSpy = jest.spyOn(component, 'reloadData') | ||||
|  | ||||
|     const createButton = fixture.debugElement.queryAll(By.css('button'))[3] | ||||
| @@ -172,20 +172,20 @@ describe('ManagementListComponent', () => { | ||||
|  | ||||
|     // fail first | ||||
|     editDialog.failed.emit({ error: 'error creating item' }) | ||||
|     expect(toastErrorSpy).toHaveBeenCalled() | ||||
|     expect(notificationErrorSpy).toHaveBeenCalled() | ||||
|     expect(reloadSpy).not.toHaveBeenCalled() | ||||
|  | ||||
|     // succeed | ||||
|     editDialog.succeeded.emit() | ||||
|     expect(toastInfoSpy).toHaveBeenCalled() | ||||
|     expect(notificationInfoSpy).toHaveBeenCalled() | ||||
|     expect(reloadSpy).toHaveBeenCalled() | ||||
|   }) | ||||
|  | ||||
|   it('should support edit, show notification on error / success', () => { | ||||
|     let modal: NgbModalRef | ||||
|     modalService.activeInstances.subscribe((m) => (modal = m[m.length - 1])) | ||||
|     const toastErrorSpy = jest.spyOn(toastService, 'showError') | ||||
|     const toastInfoSpy = jest.spyOn(toastService, 'showInfo') | ||||
|     const notificationErrorSpy = jest.spyOn(notificationService, 'showError') | ||||
|     const notificationInfoSpy = jest.spyOn(notificationService, 'showInfo') | ||||
|     const reloadSpy = jest.spyOn(component, 'reloadData') | ||||
|  | ||||
|     const editButton = fixture.debugElement.queryAll(By.css('button'))[6] | ||||
| @@ -197,19 +197,19 @@ describe('ManagementListComponent', () => { | ||||
|  | ||||
|     // fail first | ||||
|     editDialog.failed.emit({ error: 'error editing item' }) | ||||
|     expect(toastErrorSpy).toHaveBeenCalled() | ||||
|     expect(notificationErrorSpy).toHaveBeenCalled() | ||||
|     expect(reloadSpy).not.toHaveBeenCalled() | ||||
|  | ||||
|     // succeed | ||||
|     editDialog.succeeded.emit() | ||||
|     expect(toastInfoSpy).toHaveBeenCalled() | ||||
|     expect(notificationInfoSpy).toHaveBeenCalled() | ||||
|     expect(reloadSpy).toHaveBeenCalled() | ||||
|   }) | ||||
|  | ||||
|   it('should support delete, show notification on error / success', () => { | ||||
|     let modal: NgbModalRef | ||||
|     modalService.activeInstances.subscribe((m) => (modal = m[m.length - 1])) | ||||
|     const toastErrorSpy = jest.spyOn(toastService, 'showError') | ||||
|     const notificationErrorSpy = jest.spyOn(notificationService, 'showError') | ||||
|     const deleteSpy = jest.spyOn(tagService, 'delete') | ||||
|     const reloadSpy = jest.spyOn(component, 'reloadData') | ||||
|  | ||||
| @@ -222,7 +222,7 @@ describe('ManagementListComponent', () => { | ||||
|     // fail first | ||||
|     deleteSpy.mockReturnValueOnce(throwError(() => new Error('error deleting'))) | ||||
|     editDialog.confirmClicked.emit() | ||||
|     expect(toastErrorSpy).toHaveBeenCalled() | ||||
|     expect(notificationErrorSpy).toHaveBeenCalled() | ||||
|     expect(reloadSpy).not.toHaveBeenCalled() | ||||
|  | ||||
|     // succeed | ||||
| @@ -293,22 +293,22 @@ describe('ManagementListComponent', () => { | ||||
|     bulkEditPermsSpy.mockReturnValueOnce( | ||||
|       throwError(() => new Error('error setting permissions')) | ||||
|     ) | ||||
|     const errorToastSpy = jest.spyOn(toastService, 'showError') | ||||
|     const errornotificationSpy = jest.spyOn(notificationService, 'showError') | ||||
|     modal.componentInstance.confirmClicked.emit({ | ||||
|       permissions: {}, | ||||
|       merge: true, | ||||
|     }) | ||||
|     expect(bulkEditPermsSpy).toHaveBeenCalled() | ||||
|     expect(errorToastSpy).toHaveBeenCalled() | ||||
|     expect(errornotificationSpy).toHaveBeenCalled() | ||||
|  | ||||
|     const successToastSpy = jest.spyOn(toastService, 'showInfo') | ||||
|     const successnotificationSpy = jest.spyOn(notificationService, 'showInfo') | ||||
|     bulkEditPermsSpy.mockReturnValueOnce(of('OK')) | ||||
|     modal.componentInstance.confirmClicked.emit({ | ||||
|       permissions: {}, | ||||
|       merge: true, | ||||
|     }) | ||||
|     expect(bulkEditPermsSpy).toHaveBeenCalled() | ||||
|     expect(successToastSpy).toHaveBeenCalled() | ||||
|     expect(successnotificationSpy).toHaveBeenCalled() | ||||
|   }) | ||||
|  | ||||
|   it('should support bulk delete objects', () => { | ||||
| @@ -327,19 +327,19 @@ describe('ManagementListComponent', () => { | ||||
|     bulkEditSpy.mockReturnValueOnce( | ||||
|       throwError(() => new Error('error setting permissions')) | ||||
|     ) | ||||
|     const errorToastSpy = jest.spyOn(toastService, 'showError') | ||||
|     const errornotificationSpy = jest.spyOn(notificationService, 'showError') | ||||
|     modal.componentInstance.confirmClicked.emit(null) | ||||
|     expect(bulkEditSpy).toHaveBeenCalledWith( | ||||
|       Array.from(selected), | ||||
|       BulkEditObjectOperation.Delete | ||||
|     ) | ||||
|     expect(errorToastSpy).toHaveBeenCalled() | ||||
|     expect(errornotificationSpy).toHaveBeenCalled() | ||||
|  | ||||
|     const successToastSpy = jest.spyOn(toastService, 'showInfo') | ||||
|     const successnotificationSpy = jest.spyOn(notificationService, 'showInfo') | ||||
|     bulkEditSpy.mockReturnValueOnce(of('OK')) | ||||
|     modal.componentInstance.confirmClicked.emit(null) | ||||
|     expect(bulkEditSpy).toHaveBeenCalled() | ||||
|     expect(successToastSpy).toHaveBeenCalled() | ||||
|     expect(successnotificationSpy).toHaveBeenCalled() | ||||
|   }) | ||||
|  | ||||
|   it('should disallow bulk permissions or delete objects if no global perms', () => { | ||||
|   | ||||
| @@ -27,6 +27,7 @@ import { | ||||
|   SortEvent, | ||||
| } from 'src/app/directives/sortable.directive' | ||||
| import { DocumentListViewService } from 'src/app/services/document-list-view.service' | ||||
| import { NotificationService } from 'src/app/services/notification.service' | ||||
| import { | ||||
|   PermissionAction, | ||||
|   PermissionsService, | ||||
| @@ -36,7 +37,6 @@ import { | ||||
|   AbstractNameFilterService, | ||||
|   BulkEditObjectOperation, | ||||
| } from 'src/app/services/rest/abstract-name-filter-service' | ||||
| import { ToastService } from 'src/app/services/toast.service' | ||||
| import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component' | ||||
| import { EditDialogMode } from '../../common/edit-dialog/edit-dialog.component' | ||||
| import { PermissionsDialogComponent } from '../../common/permissions-dialog/permissions-dialog.component' | ||||
| @@ -63,7 +63,7 @@ export abstract class ManagementListComponent<T extends MatchingModel> | ||||
|     protected service: AbstractNameFilterService<T>, | ||||
|     private modalService: NgbModal, | ||||
|     private editDialogComponent: any, | ||||
|     private toastService: ToastService, | ||||
|     private notificationService: NotificationService, | ||||
|     private documentListViewService: DocumentListViewService, | ||||
|     private permissionsService: PermissionsService, | ||||
|     protected filterRuleType: number, | ||||
| @@ -173,12 +173,12 @@ export abstract class ManagementListComponent<T extends MatchingModel> | ||||
|     activeModal.componentInstance.dialogMode = EditDialogMode.CREATE | ||||
|     activeModal.componentInstance.succeeded.subscribe(() => { | ||||
|       this.reloadData() | ||||
|       this.toastService.showInfo( | ||||
|       this.notificationService.showInfo( | ||||
|         $localize`Successfully created ${this.typeName}.` | ||||
|       ) | ||||
|     }) | ||||
|     activeModal.componentInstance.failed.subscribe((e) => { | ||||
|       this.toastService.showError( | ||||
|       this.notificationService.showError( | ||||
|         $localize`Error occurred while creating ${this.typeName}.`, | ||||
|         e | ||||
|       ) | ||||
| @@ -193,12 +193,12 @@ export abstract class ManagementListComponent<T extends MatchingModel> | ||||
|     activeModal.componentInstance.dialogMode = EditDialogMode.EDIT | ||||
|     activeModal.componentInstance.succeeded.subscribe(() => { | ||||
|       this.reloadData() | ||||
|       this.toastService.showInfo( | ||||
|       this.notificationService.showInfo( | ||||
|         $localize`Successfully updated ${this.typeName} "${object.name}".` | ||||
|       ) | ||||
|     }) | ||||
|     activeModal.componentInstance.failed.subscribe((e) => { | ||||
|       this.toastService.showError( | ||||
|       this.notificationService.showError( | ||||
|         $localize`Error occurred while saving ${this.typeName}.`, | ||||
|         e | ||||
|       ) | ||||
| @@ -234,7 +234,7 @@ export abstract class ManagementListComponent<T extends MatchingModel> | ||||
|           }, | ||||
|           error: (error) => { | ||||
|             activeModal.componentInstance.buttonsEnabled = true | ||||
|             this.toastService.showError( | ||||
|             this.notificationService.showError( | ||||
|               $localize`Error while deleting element`, | ||||
|               error | ||||
|             ) | ||||
| @@ -313,14 +313,14 @@ export abstract class ManagementListComponent<T extends MatchingModel> | ||||
|           .subscribe({ | ||||
|             next: () => { | ||||
|               modal.close() | ||||
|               this.toastService.showInfo( | ||||
|               this.notificationService.showInfo( | ||||
|                 $localize`Permissions updated successfully` | ||||
|               ) | ||||
|               this.reloadData() | ||||
|             }, | ||||
|             error: (error) => { | ||||
|               modal.componentInstance.buttonsEnabled = true | ||||
|               this.toastService.showError( | ||||
|               this.notificationService.showError( | ||||
|                 $localize`Error updating permissions`, | ||||
|                 error | ||||
|               ) | ||||
| @@ -349,12 +349,14 @@ export abstract class ManagementListComponent<T extends MatchingModel> | ||||
|         .subscribe({ | ||||
|           next: () => { | ||||
|             modal.close() | ||||
|             this.toastService.showInfo($localize`Objects deleted successfully`) | ||||
|             this.notificationService.showInfo( | ||||
|               $localize`Objects deleted successfully` | ||||
|             ) | ||||
|             this.reloadData() | ||||
|           }, | ||||
|           error: (error) => { | ||||
|             modal.componentInstance.buttonsEnabled = true | ||||
|             this.toastService.showError( | ||||
|             this.notificationService.showError( | ||||
|               $localize`Error deleting objects`, | ||||
|               error | ||||
|             ) | ||||
|   | ||||
| @@ -10,10 +10,10 @@ import { of, throwError } from 'rxjs' | ||||
| import { SavedView } from 'src/app/data/saved-view' | ||||
| import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive' | ||||
| import { PermissionsGuard } from 'src/app/guards/permissions.guard' | ||||
| import { NotificationService } from 'src/app/services/notification.service' | ||||
| import { PermissionsService } from 'src/app/services/permissions.service' | ||||
| import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service' | ||||
| import { SavedViewService } from 'src/app/services/rest/saved-view.service' | ||||
| import { ToastService } from 'src/app/services/toast.service' | ||||
| import { ConfirmButtonComponent } from '../../common/confirm-button/confirm-button.component' | ||||
| import { CheckComponent } from '../../common/input/check/check.component' | ||||
| import { DragDropSelectComponent } from '../../common/input/drag-drop-select/drag-drop-select.component' | ||||
| @@ -32,7 +32,7 @@ describe('SavedViewsComponent', () => { | ||||
|   let component: SavedViewsComponent | ||||
|   let fixture: ComponentFixture<SavedViewsComponent> | ||||
|   let savedViewService: SavedViewService | ||||
|   let toastService: ToastService | ||||
|   let notificationService: NotificationService | ||||
|  | ||||
|   beforeEach(async () => { | ||||
|     TestBed.configureTestingModule({ | ||||
| @@ -77,7 +77,7 @@ describe('SavedViewsComponent', () => { | ||||
|     }).compileComponents() | ||||
|  | ||||
|     savedViewService = TestBed.inject(SavedViewService) | ||||
|     toastService = TestBed.inject(ToastService) | ||||
|     notificationService = TestBed.inject(NotificationService) | ||||
|     fixture = TestBed.createComponent(SavedViewsComponent) | ||||
|     component = fixture.componentInstance | ||||
|  | ||||
| @@ -93,8 +93,8 @@ describe('SavedViewsComponent', () => { | ||||
|   }) | ||||
|  | ||||
|   it('should support save saved views, show error', () => { | ||||
|     const toastErrorSpy = jest.spyOn(toastService, 'showError') | ||||
|     const toastSpy = jest.spyOn(toastService, 'show') | ||||
|     const notificationErrorSpy = jest.spyOn(notificationService, 'showError') | ||||
|     const notificationSpy = jest.spyOn(notificationService, 'show') | ||||
|     const savedViewPatchSpy = jest.spyOn(savedViewService, 'patchMany') | ||||
|  | ||||
|     const toggle = fixture.debugElement.query( | ||||
| @@ -108,16 +108,16 @@ describe('SavedViewsComponent', () => { | ||||
|       throwError(() => new Error('unable to save saved views')) | ||||
|     ) | ||||
|     component.save() | ||||
|     expect(toastErrorSpy).toHaveBeenCalled() | ||||
|     expect(notificationErrorSpy).toHaveBeenCalled() | ||||
|     expect(savedViewPatchSpy).toHaveBeenCalled() | ||||
|     toastSpy.mockClear() | ||||
|     toastErrorSpy.mockClear() | ||||
|     notificationSpy.mockClear() | ||||
|     notificationErrorSpy.mockClear() | ||||
|     savedViewPatchSpy.mockClear() | ||||
|  | ||||
|     // succeed saved views | ||||
|     savedViewPatchSpy.mockReturnValueOnce(of(savedViews as SavedView[])) | ||||
|     component.save() | ||||
|     expect(toastErrorSpy).not.toHaveBeenCalled() | ||||
|     expect(notificationErrorSpy).not.toHaveBeenCalled() | ||||
|     expect(savedViewPatchSpy).toHaveBeenCalled() | ||||
|   }) | ||||
|  | ||||
| @@ -150,12 +150,12 @@ describe('SavedViewsComponent', () => { | ||||
|   }) | ||||
|  | ||||
|   it('should support delete saved view', () => { | ||||
|     const toastSpy = jest.spyOn(toastService, 'showInfo') | ||||
|     const notificationSpy = jest.spyOn(notificationService, 'showInfo') | ||||
|     const deleteSpy = jest.spyOn(savedViewService, 'delete') | ||||
|     deleteSpy.mockReturnValue(of(true)) | ||||
|     component.deleteSavedView(savedViews[0] as SavedView) | ||||
|     expect(deleteSpy).toHaveBeenCalled() | ||||
|     expect(toastSpy).toHaveBeenCalledWith( | ||||
|     expect(notificationSpy).toHaveBeenCalledWith( | ||||
|       `Saved view "${savedViews[0].name}" deleted.` | ||||
|     ) | ||||
|   }) | ||||
|   | ||||
| @@ -11,9 +11,9 @@ import { BehaviorSubject, Observable, takeUntil } from 'rxjs' | ||||
| import { DisplayMode } from 'src/app/data/document' | ||||
| import { SavedView } from 'src/app/data/saved-view' | ||||
| import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive' | ||||
| import { NotificationService } from 'src/app/services/notification.service' | ||||
| import { SavedViewService } from 'src/app/services/rest/saved-view.service' | ||||
| import { SettingsService } from 'src/app/services/settings.service' | ||||
| import { ToastService } from 'src/app/services/toast.service' | ||||
| import { ConfirmButtonComponent } from '../../common/confirm-button/confirm-button.component' | ||||
| import { DragDropSelectComponent } from '../../common/input/drag-drop-select/drag-drop-select.component' | ||||
| import { NumberComponent } from '../../common/input/number/number.component' | ||||
| @@ -58,7 +58,7 @@ export class SavedViewsComponent | ||||
|   constructor( | ||||
|     private savedViewService: SavedViewService, | ||||
|     private settings: SettingsService, | ||||
|     private toastService: ToastService | ||||
|     private notificationService: NotificationService | ||||
|   ) { | ||||
|     super() | ||||
|     this.settings.organizingSidebarSavedViews = true | ||||
| @@ -129,7 +129,7 @@ export class SavedViewsComponent | ||||
|     this.savedViewService.delete(savedView).subscribe(() => { | ||||
|       this.savedViewsGroup.removeControl(savedView.id.toString()) | ||||
|       this.savedViews.splice(this.savedViews.indexOf(savedView), 1) | ||||
|       this.toastService.showInfo( | ||||
|       this.notificationService.showInfo( | ||||
|         $localize`Saved view "${savedView.name}" deleted.` | ||||
|       ) | ||||
|       this.savedViewService.clearCache() | ||||
| @@ -155,11 +155,13 @@ export class SavedViewsComponent | ||||
|     if (changed.length) { | ||||
|       this.savedViewService.patchMany(changed).subscribe({ | ||||
|         next: () => { | ||||
|           this.toastService.showInfo($localize`Views saved successfully.`) | ||||
|           this.notificationService.showInfo( | ||||
|             $localize`Views saved successfully.` | ||||
|           ) | ||||
|           this.store.next(this.savedViewsForm.value) | ||||
|         }, | ||||
|         error: (error) => { | ||||
|           this.toastService.showError( | ||||
|           this.notificationService.showError( | ||||
|             $localize`Error while saving views.`, | ||||
|             error | ||||
|           ) | ||||
|   | ||||
| @@ -13,12 +13,12 @@ import { IfPermissionsDirective } from 'src/app/directives/if-permissions.direct | ||||
| import { SortableDirective } from 'src/app/directives/sortable.directive' | ||||
| import { SafeHtmlPipe } from 'src/app/pipes/safehtml.pipe' | ||||
| import { DocumentListViewService } from 'src/app/services/document-list-view.service' | ||||
| import { NotificationService } from 'src/app/services/notification.service' | ||||
| import { | ||||
|   PermissionsService, | ||||
|   PermissionType, | ||||
| } from 'src/app/services/permissions.service' | ||||
| import { StoragePathService } from 'src/app/services/rest/storage-path.service' | ||||
| import { ToastService } from 'src/app/services/toast.service' | ||||
| import { StoragePathEditDialogComponent } from '../../common/edit-dialog/storage-path-edit-dialog/storage-path-edit-dialog.component' | ||||
| import { PageHeaderComponent } from '../../common/page-header/page-header.component' | ||||
| import { ManagementListComponent } from '../management-list/management-list.component' | ||||
| @@ -45,7 +45,7 @@ export class StoragePathListComponent extends ManagementListComponent<StoragePat | ||||
|   constructor( | ||||
|     directoryService: StoragePathService, | ||||
|     modalService: NgbModal, | ||||
|     toastService: ToastService, | ||||
|     notificationService: NotificationService, | ||||
|     documentListViewService: DocumentListViewService, | ||||
|     permissionsService: PermissionsService | ||||
|   ) { | ||||
| @@ -53,7 +53,7 @@ export class StoragePathListComponent extends ManagementListComponent<StoragePat | ||||
|       directoryService, | ||||
|       modalService, | ||||
|       StoragePathEditDialogComponent, | ||||
|       toastService, | ||||
|       notificationService, | ||||
|       documentListViewService, | ||||
|       permissionsService, | ||||
|       FILTER_HAS_STORAGE_PATH_ANY, | ||||
|   | ||||
| @@ -13,12 +13,12 @@ import { IfPermissionsDirective } from 'src/app/directives/if-permissions.direct | ||||
| import { SortableDirective } from 'src/app/directives/sortable.directive' | ||||
| import { SafeHtmlPipe } from 'src/app/pipes/safehtml.pipe' | ||||
| import { DocumentListViewService } from 'src/app/services/document-list-view.service' | ||||
| import { NotificationService } from 'src/app/services/notification.service' | ||||
| import { | ||||
|   PermissionsService, | ||||
|   PermissionType, | ||||
| } from 'src/app/services/permissions.service' | ||||
| import { TagService } from 'src/app/services/rest/tag.service' | ||||
| import { ToastService } from 'src/app/services/toast.service' | ||||
| import { TagEditDialogComponent } from '../../common/edit-dialog/tag-edit-dialog/tag-edit-dialog.component' | ||||
| import { PageHeaderComponent } from '../../common/page-header/page-header.component' | ||||
| import { ManagementListComponent } from '../management-list/management-list.component' | ||||
| @@ -45,7 +45,7 @@ export class TagListComponent extends ManagementListComponent<Tag> { | ||||
|   constructor( | ||||
|     tagService: TagService, | ||||
|     modalService: NgbModal, | ||||
|     toastService: ToastService, | ||||
|     notificationService: NotificationService, | ||||
|     documentListViewService: DocumentListViewService, | ||||
|     permissionsService: PermissionsService | ||||
|   ) { | ||||
| @@ -53,7 +53,7 @@ export class TagListComponent extends ManagementListComponent<Tag> { | ||||
|       tagService, | ||||
|       modalService, | ||||
|       TagEditDialogComponent, | ||||
|       toastService, | ||||
|       notificationService, | ||||
|       documentListViewService, | ||||
|       permissionsService, | ||||
|       FILTER_HAS_TAGS_ALL, | ||||
|   | ||||
| @@ -19,9 +19,9 @@ import { | ||||
|   WorkflowTriggerType, | ||||
| } from 'src/app/data/workflow-trigger' | ||||
| import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive' | ||||
| import { NotificationService } from 'src/app/services/notification.service' | ||||
| import { PermissionsService } from 'src/app/services/permissions.service' | ||||
| import { WorkflowService } from 'src/app/services/rest/workflow.service' | ||||
| import { ToastService } from 'src/app/services/toast.service' | ||||
| import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component' | ||||
| import { EditDialogMode } from '../../common/edit-dialog/edit-dialog.component' | ||||
| import { WorkflowEditDialogComponent } from '../../common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component' | ||||
| @@ -77,7 +77,7 @@ describe('WorkflowsComponent', () => { | ||||
|   let fixture: ComponentFixture<WorkflowsComponent> | ||||
|   let workflowService: WorkflowService | ||||
|   let modalService: NgbModal | ||||
|   let toastService: ToastService | ||||
|   let notificationService: NotificationService | ||||
|  | ||||
|   beforeEach(() => { | ||||
|     TestBed.configureTestingModule({ | ||||
| @@ -116,7 +116,7 @@ describe('WorkflowsComponent', () => { | ||||
|       }) | ||||
|     ) | ||||
|     modalService = TestBed.inject(NgbModal) | ||||
|     toastService = TestBed.inject(ToastService) | ||||
|     notificationService = TestBed.inject(NotificationService) | ||||
|     jest.useFakeTimers() | ||||
|     fixture = TestBed.createComponent(WorkflowsComponent) | ||||
|     component = fixture.componentInstance | ||||
| @@ -127,8 +127,8 @@ describe('WorkflowsComponent', () => { | ||||
|   it('should support create, show notification on error / success', () => { | ||||
|     let modal: NgbModalRef | ||||
|     modalService.activeInstances.subscribe((m) => (modal = m[m.length - 1])) | ||||
|     const toastErrorSpy = jest.spyOn(toastService, 'showError') | ||||
|     const toastInfoSpy = jest.spyOn(toastService, 'showInfo') | ||||
|     const notificationErrorSpy = jest.spyOn(notificationService, 'showError') | ||||
|     const notificationInfoSpy = jest.spyOn(notificationService, 'showInfo') | ||||
|     const reloadSpy = jest.spyOn(component, 'reload') | ||||
|  | ||||
|     const createButton = fixture.debugElement.queryAll(By.css('button'))[1] | ||||
| @@ -139,20 +139,20 @@ describe('WorkflowsComponent', () => { | ||||
|  | ||||
|     // fail first | ||||
|     editDialog.failed.emit({ error: 'error creating item' }) | ||||
|     expect(toastErrorSpy).toHaveBeenCalled() | ||||
|     expect(notificationErrorSpy).toHaveBeenCalled() | ||||
|     expect(reloadSpy).not.toHaveBeenCalled() | ||||
|  | ||||
|     // succeed | ||||
|     editDialog.succeeded.emit(workflows[0]) | ||||
|     expect(toastInfoSpy).toHaveBeenCalled() | ||||
|     expect(notificationInfoSpy).toHaveBeenCalled() | ||||
|     expect(reloadSpy).toHaveBeenCalled() | ||||
|   }) | ||||
|  | ||||
|   it('should support edit, show notification on error / success', () => { | ||||
|     let modal: NgbModalRef | ||||
|     modalService.activeInstances.subscribe((m) => (modal = m[m.length - 1])) | ||||
|     const toastErrorSpy = jest.spyOn(toastService, 'showError') | ||||
|     const toastInfoSpy = jest.spyOn(toastService, 'showInfo') | ||||
|     const notificationErrorSpy = jest.spyOn(notificationService, 'showError') | ||||
|     const notificationInfoSpy = jest.spyOn(notificationService, 'showInfo') | ||||
|     const reloadSpy = jest.spyOn(component, 'reload') | ||||
|  | ||||
|     const editButton = fixture.debugElement.queryAll(By.css('button'))[2] | ||||
| @@ -164,12 +164,12 @@ describe('WorkflowsComponent', () => { | ||||
|  | ||||
|     // fail first | ||||
|     editDialog.failed.emit({ error: 'error editing item' }) | ||||
|     expect(toastErrorSpy).toHaveBeenCalled() | ||||
|     expect(notificationErrorSpy).toHaveBeenCalled() | ||||
|     expect(reloadSpy).not.toHaveBeenCalled() | ||||
|  | ||||
|     // succeed | ||||
|     editDialog.succeeded.emit(workflows[0]) | ||||
|     expect(toastInfoSpy).toHaveBeenCalled() | ||||
|     expect(notificationInfoSpy).toHaveBeenCalled() | ||||
|     expect(reloadSpy).toHaveBeenCalled() | ||||
|   }) | ||||
|  | ||||
| @@ -240,7 +240,7 @@ describe('WorkflowsComponent', () => { | ||||
|   it('should support delete, show notification on error / success', () => { | ||||
|     let modal: NgbModalRef | ||||
|     modalService.activeInstances.subscribe((m) => (modal = m[m.length - 1])) | ||||
|     const toastErrorSpy = jest.spyOn(toastService, 'showError') | ||||
|     const notificationErrorSpy = jest.spyOn(notificationService, 'showError') | ||||
|     const deleteSpy = jest.spyOn(workflowService, 'delete') | ||||
|     const reloadSpy = jest.spyOn(component, 'reload') | ||||
|  | ||||
| @@ -253,7 +253,7 @@ describe('WorkflowsComponent', () => { | ||||
|     // fail first | ||||
|     deleteSpy.mockReturnValueOnce(throwError(() => new Error('error deleting'))) | ||||
|     editDialog.confirmClicked.emit() | ||||
|     expect(toastErrorSpy).toHaveBeenCalled() | ||||
|     expect(notificationErrorSpy).toHaveBeenCalled() | ||||
|     expect(reloadSpy).not.toHaveBeenCalled() | ||||
|  | ||||
|     // succeed | ||||
| @@ -267,21 +267,21 @@ describe('WorkflowsComponent', () => { | ||||
|     const toggleInput = fixture.debugElement.query( | ||||
|       By.css('input[type="checkbox"]') | ||||
|     ) | ||||
|     const toastErrorSpy = jest.spyOn(toastService, 'showError') | ||||
|     const toastInfoSpy = jest.spyOn(toastService, 'showInfo') | ||||
|     const notificationErrorSpy = jest.spyOn(notificationService, 'showError') | ||||
|     const notificationInfoSpy = jest.spyOn(notificationService, 'showInfo') | ||||
|     // fail first | ||||
|     patchSpy.mockReturnValueOnce( | ||||
|       throwError(() => new Error('Error getting config')) | ||||
|     ) | ||||
|     toggleInput.nativeElement.click() | ||||
|     expect(patchSpy).toHaveBeenCalled() | ||||
|     expect(toastErrorSpy).toHaveBeenCalled() | ||||
|     expect(notificationErrorSpy).toHaveBeenCalled() | ||||
|     // succeed second | ||||
|     patchSpy.mockReturnValueOnce(of(workflows[0])) | ||||
|     toggleInput.nativeElement.click() | ||||
|     patchSpy.mockReturnValueOnce(of({ ...workflows[0], enabled: false })) | ||||
|     toggleInput.nativeElement.click() | ||||
|     expect(patchSpy).toHaveBeenCalled() | ||||
|     expect(toastInfoSpy).toHaveBeenCalled() | ||||
|     expect(notificationInfoSpy).toHaveBeenCalled() | ||||
|   }) | ||||
| }) | ||||
|   | ||||
| @@ -5,9 +5,9 @@ import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons' | ||||
| import { delay, takeUntil, tap } from 'rxjs' | ||||
| import { Workflow } from 'src/app/data/workflow' | ||||
| import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive' | ||||
| import { NotificationService } from 'src/app/services/notification.service' | ||||
| import { PermissionsService } from 'src/app/services/permissions.service' | ||||
| import { WorkflowService } from 'src/app/services/rest/workflow.service' | ||||
| import { ToastService } from 'src/app/services/toast.service' | ||||
| import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component' | ||||
| import { EditDialogMode } from '../../common/edit-dialog/edit-dialog.component' | ||||
| import { | ||||
| @@ -40,7 +40,7 @@ export class WorkflowsComponent | ||||
|     private workflowService: WorkflowService, | ||||
|     public permissionsService: PermissionsService, | ||||
|     private modalService: NgbModal, | ||||
|     private toastService: ToastService | ||||
|     private notificationService: NotificationService | ||||
|   ) { | ||||
|     super() | ||||
|   } | ||||
| @@ -90,7 +90,7 @@ export class WorkflowsComponent | ||||
|     modal.componentInstance.succeeded | ||||
|       .pipe(takeUntil(this.unsubscribeNotifier)) | ||||
|       .subscribe((newWorkflow) => { | ||||
|         this.toastService.showInfo( | ||||
|         this.notificationService.showInfo( | ||||
|           $localize`Saved workflow "${newWorkflow.name}".` | ||||
|         ) | ||||
|         this.workflowService.clearCache() | ||||
| @@ -99,7 +99,7 @@ export class WorkflowsComponent | ||||
|     modal.componentInstance.failed | ||||
|       .pipe(takeUntil(this.unsubscribeNotifier)) | ||||
|       .subscribe((e) => { | ||||
|         this.toastService.showError($localize`Error saving workflow.`, e) | ||||
|         this.notificationService.showError($localize`Error saving workflow.`, e) | ||||
|       }) | ||||
|   } | ||||
|  | ||||
| @@ -142,14 +142,14 @@ export class WorkflowsComponent | ||||
|       this.workflowService.delete(workflow).subscribe({ | ||||
|         next: () => { | ||||
|           modal.close() | ||||
|           this.toastService.showInfo( | ||||
|           this.notificationService.showInfo( | ||||
|             $localize`Deleted workflow "${workflow.name}".` | ||||
|           ) | ||||
|           this.workflowService.clearCache() | ||||
|           this.reload() | ||||
|         }, | ||||
|         error: (e) => { | ||||
|           this.toastService.showError( | ||||
|           this.notificationService.showError( | ||||
|             $localize`Error deleting workflow "${workflow.name}".`, | ||||
|             e | ||||
|           ) | ||||
| @@ -161,7 +161,7 @@ export class WorkflowsComponent | ||||
|   toggleWorkflowEnabled(workflow: Workflow) { | ||||
|     this.workflowService.patch(workflow).subscribe({ | ||||
|       next: () => { | ||||
|         this.toastService.showInfo( | ||||
|         this.notificationService.showInfo( | ||||
|           workflow.enabled | ||||
|             ? $localize`Enabled workflow "${workflow.name}"` | ||||
|             : $localize`Disabled workflow "${workflow.name}"` | ||||
| @@ -170,7 +170,7 @@ export class WorkflowsComponent | ||||
|         this.reload() | ||||
|       }, | ||||
|       error: (e) => { | ||||
|         this.toastService.showError( | ||||
|         this.notificationService.showError( | ||||
|           $localize`Error toggling workflow "${workflow.name}".`, | ||||
|           e | ||||
|         ) | ||||
|   | ||||
| @@ -1,12 +1,12 @@ | ||||
| import { TestBed } from '@angular/core/testing' | ||||
| import { ActivatedRoute, RouterState } from '@angular/router' | ||||
| import { TourService } from 'ngx-ui-tour-ng-bootstrap' | ||||
| import { NotificationService } from '../services/notification.service' | ||||
| import { | ||||
|   PermissionAction, | ||||
|   PermissionType, | ||||
|   PermissionsService, | ||||
| } from '../services/permissions.service' | ||||
| import { ToastService } from '../services/toast.service' | ||||
| import { PermissionsGuard } from './permissions.guard' | ||||
|  | ||||
| describe('PermissionsGuard', () => { | ||||
| @@ -15,7 +15,7 @@ describe('PermissionsGuard', () => { | ||||
|   let route: ActivatedRoute | ||||
|   let routerState: RouterState | ||||
|   let tourService: TourService | ||||
|   let toastService: ToastService | ||||
|   let notificationService: NotificationService | ||||
|  | ||||
|   beforeEach(() => { | ||||
|     TestBed.configureTestingModule({ | ||||
| @@ -44,13 +44,13 @@ describe('PermissionsGuard', () => { | ||||
|           }, | ||||
|         }, | ||||
|         TourService, | ||||
|         ToastService, | ||||
|         NotificationService, | ||||
|       ], | ||||
|     }) | ||||
|  | ||||
|     permissionsService = TestBed.inject(PermissionsService) | ||||
|     tourService = TestBed.inject(TourService) | ||||
|     toastService = TestBed.inject(ToastService) | ||||
|     notificationService = TestBed.inject(NotificationService) | ||||
|     guard = TestBed.inject(PermissionsGuard) | ||||
|     route = TestBed.inject(ActivatedRoute) | ||||
|     routerState = TestBed.inject(RouterState) | ||||
| @@ -88,11 +88,11 @@ describe('PermissionsGuard', () => { | ||||
|       }) | ||||
|     jest.spyOn(tourService, 'getStatus').mockImplementation(() => 2) | ||||
|  | ||||
|     const toastSpy = jest.spyOn(toastService, 'showError') | ||||
|     const notificationSpy = jest.spyOn(notificationService, 'showError') | ||||
|  | ||||
|     const canActivate = guard.canActivate(route.snapshot, routerState.snapshot) | ||||
|  | ||||
|     expect(canActivate).toHaveProperty('root') // returns UrlTree | ||||
|     expect(toastSpy).toHaveBeenCalled() | ||||
|     expect(notificationSpy).toHaveBeenCalled() | ||||
|   }) | ||||
| }) | ||||
|   | ||||
| @@ -6,15 +6,15 @@ import { | ||||
|   UrlTree, | ||||
| } from '@angular/router' | ||||
| import { TourService } from 'ngx-ui-tour-ng-bootstrap' | ||||
| import { NotificationService } from '../services/notification.service' | ||||
| import { PermissionsService } from '../services/permissions.service' | ||||
| import { ToastService } from '../services/toast.service' | ||||
|  | ||||
| @Injectable() | ||||
| export class PermissionsGuard { | ||||
|   constructor( | ||||
|     private permissionsService: PermissionsService, | ||||
|     private router: Router, | ||||
|     private toastService: ToastService, | ||||
|     private notificationService: NotificationService, | ||||
|     private tourService: TourService | ||||
|   ) {} | ||||
|  | ||||
| @@ -32,7 +32,7 @@ export class PermissionsGuard { | ||||
|     ) { | ||||
|       // Check if tour is running 1 = TourState.ON | ||||
|       if (this.tourService.getStatus() !== 1) { | ||||
|         this.toastService.showError( | ||||
|         this.notificationService.showError( | ||||
|           $localize`You don't have permissions to do that` | ||||
|         ) | ||||
|       } | ||||
|   | ||||
							
								
								
									
										109
									
								
								src-ui/src/app/services/notification.service.spec.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								src-ui/src/app/services/notification.service.spec.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,109 @@ | ||||
| import { TestBed } from '@angular/core/testing' | ||||
| import { NotificationService } from './notification.service' | ||||
|  | ||||
| describe('NotificationService', () => { | ||||
|   let notificationService: NotificationService | ||||
|  | ||||
|   beforeEach(() => { | ||||
|     TestBed.configureTestingModule({ | ||||
|       providers: [NotificationService], | ||||
|     }) | ||||
|  | ||||
|     notificationService = TestBed.inject(NotificationService) | ||||
|   }) | ||||
|  | ||||
|   it('adds notification on show', () => { | ||||
|     const notification = { | ||||
|       title: 'Title', | ||||
|       content: 'content', | ||||
|       delay: 5000, | ||||
|     } | ||||
|     notificationService.show(notification) | ||||
|  | ||||
|     notificationService.getNotifications().subscribe((notifications) => { | ||||
|       expect(notifications).toContainEqual(notification) | ||||
|     }) | ||||
|   }) | ||||
|  | ||||
|   it('adds a unique id to notification on show', () => { | ||||
|     const notification = { | ||||
|       title: 'Title', | ||||
|       content: 'content', | ||||
|       delay: 5000, | ||||
|     } | ||||
|     notificationService.show(notification) | ||||
|  | ||||
|     notificationService.getNotifications().subscribe((notifications) => { | ||||
|       expect(notifications[0].id).toBeDefined() | ||||
|     }) | ||||
|   }) | ||||
|  | ||||
|   it('parses error string to object on show', () => { | ||||
|     const notification = { | ||||
|       title: 'Title', | ||||
|       content: 'content', | ||||
|       delay: 5000, | ||||
|       error: 'Error string', | ||||
|     } | ||||
|     notificationService.show(notification) | ||||
|  | ||||
|     notificationService.getNotifications().subscribe((notifications) => { | ||||
|       expect(notifications[0].error).toEqual('Error string') | ||||
|     }) | ||||
|   }) | ||||
|  | ||||
|   it('creates notifications with defaults on showInfo and showError', () => { | ||||
|     notificationService.showInfo('Info notification') | ||||
|     notificationService.showError('Error notification') | ||||
|  | ||||
|     notificationService.getNotifications().subscribe((notifications) => { | ||||
|       expect(notifications).toContainEqual({ | ||||
|         content: 'Info notification', | ||||
|         delay: 5000, | ||||
|       }) | ||||
|       expect(notifications).toContainEqual({ | ||||
|         content: 'Error notification', | ||||
|         delay: 10000, | ||||
|       }) | ||||
|     }) | ||||
|   }) | ||||
|  | ||||
|   it('removes notification on close', () => { | ||||
|     const notification = { | ||||
|       title: 'Title', | ||||
|       content: 'content', | ||||
|       delay: 5000, | ||||
|     } | ||||
|     notificationService.show(notification) | ||||
|     notificationService.closeNotification(notification) | ||||
|  | ||||
|     notificationService.getNotifications().subscribe((notifications) => { | ||||
|       expect(notifications).toHaveLength(0) | ||||
|     }) | ||||
|   }) | ||||
|  | ||||
|   it('clears all notifications on clear', () => { | ||||
|     notificationService.showInfo('Info notification') | ||||
|     notificationService.showError('Error notification') | ||||
|     notificationService.clearNotifications() | ||||
|  | ||||
|     notificationService.getNotifications().subscribe((notifications) => { | ||||
|       expect(notifications).toHaveLength(0) | ||||
|     }) | ||||
|   }) | ||||
|  | ||||
|   it('suppresses popup notifications if suppressPopupNotifications is true', (finish) => { | ||||
|     notificationService.showNotification.subscribe((notification) => { | ||||
|       expect(notification).not.toBeNull() | ||||
|     }) | ||||
|     notificationService.showInfo('Info notification') | ||||
|  | ||||
|     notificationService.showNotification.subscribe((notification) => { | ||||
|       expect(notification).toBeNull() | ||||
|       finish() | ||||
|     }) | ||||
|  | ||||
|     notificationService.suppressPopupNotifications = true | ||||
|     notificationService.showInfo('Info notification') | ||||
|   }) | ||||
| }) | ||||
							
								
								
									
										87
									
								
								src-ui/src/app/services/notification.service.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								src-ui/src/app/services/notification.service.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,87 @@ | ||||
| import { Injectable } from '@angular/core' | ||||
| import { Subject } from 'rxjs' | ||||
| import { v4 as uuidv4 } from 'uuid' | ||||
|  | ||||
| export interface Notification { | ||||
|   id?: string | ||||
|  | ||||
|   content: string | ||||
|  | ||||
|   delay: number | ||||
|  | ||||
|   delayRemaining?: number | ||||
|  | ||||
|   action?: any | ||||
|  | ||||
|   actionName?: string | ||||
|  | ||||
|   classname?: string | ||||
|  | ||||
|   error?: any | ||||
| } | ||||
|  | ||||
| @Injectable({ | ||||
|   providedIn: 'root', | ||||
| }) | ||||
| export class NotificationService { | ||||
|   constructor() {} | ||||
|   _suppressPopupNotifications: boolean | ||||
|  | ||||
|   set suppressPopupNotifications(value: boolean) { | ||||
|     this._suppressPopupNotifications = value | ||||
|     this.showNotification.next(null) | ||||
|   } | ||||
|  | ||||
|   private notifications: Notification[] = [] | ||||
|  | ||||
|   private notificationsSubject: Subject<Notification[]> = new Subject() | ||||
|  | ||||
|   public showNotification: Subject<Notification> = new Subject() | ||||
|  | ||||
|   show(notification: Notification) { | ||||
|     if (!notification.id) { | ||||
|       notification.id = uuidv4() | ||||
|     } | ||||
|     if (typeof notification.error === 'string') { | ||||
|       try { | ||||
|         notification.error = JSON.parse(notification.error) | ||||
|       } catch (e) {} | ||||
|     } | ||||
|     this.notifications.unshift(notification) | ||||
|     if (!this._suppressPopupNotifications) { | ||||
|       this.showNotification.next(notification) | ||||
|     } | ||||
|     this.notificationsSubject.next(this.notifications) | ||||
|   } | ||||
|  | ||||
|   showError(content: string, error: any = null, delay: number = 10000) { | ||||
|     this.show({ | ||||
|       content: content, | ||||
|       delay: delay, | ||||
|       classname: 'error', | ||||
|       error, | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|   showInfo(content: string, delay: number = 5000) { | ||||
|     this.show({ content: content, delay: delay }) | ||||
|   } | ||||
|  | ||||
|   closeNotification(notification: Notification) { | ||||
|     let index = this.notifications.findIndex((t) => t.id == notification.id) | ||||
|     if (index > -1) { | ||||
|       this.notifications.splice(index, 1) | ||||
|       this.notificationsSubject.next(this.notifications) | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   getNotifications() { | ||||
|     return this.notificationsSubject | ||||
|   } | ||||
|  | ||||
|   clearNotifications() { | ||||
|     this.notifications = [] | ||||
|     this.notificationsSubject.next(this.notifications) | ||||
|     this.showNotification.next(null) | ||||
|   } | ||||
| } | ||||
| @@ -14,10 +14,10 @@ import { CustomFieldDataType } from '../data/custom-field' | ||||
| import { DEFAULT_DISPLAY_FIELDS, DisplayField } from '../data/document' | ||||
| import { SavedView } from '../data/saved-view' | ||||
| import { SETTINGS_KEYS, UiSettings } from '../data/ui-settings' | ||||
| import { NotificationService } from './notification.service' | ||||
| import { PermissionsService } from './permissions.service' | ||||
| import { CustomFieldsService } from './rest/custom-fields.service' | ||||
| import { SettingsService } from './settings.service' | ||||
| import { ToastService } from './toast.service' | ||||
|  | ||||
| const customFields = [ | ||||
|   { | ||||
| @@ -41,7 +41,7 @@ describe('SettingsService', () => { | ||||
|   let customFieldsService: CustomFieldsService | ||||
|   let permissionService: PermissionsService | ||||
|   let subscription: Subscription | ||||
|   let toastService: ToastService | ||||
|   let notificationService: NotificationService | ||||
|  | ||||
|   const ui_settings: UiSettings = { | ||||
|     user: { | ||||
| @@ -105,7 +105,7 @@ describe('SettingsService', () => { | ||||
|     customFieldsService = TestBed.inject(CustomFieldsService) | ||||
|     permissionService = TestBed.inject(PermissionsService) | ||||
|     settingsService = TestBed.inject(SettingsService) | ||||
|     toastService = TestBed.inject(ToastService) | ||||
|     notificationService = TestBed.inject(NotificationService) | ||||
|     // Normally done in app initializer | ||||
|     settingsService.initializeSettings().subscribe() | ||||
|   }) | ||||
| @@ -122,8 +122,8 @@ describe('SettingsService', () => { | ||||
|     expect(req.request.method).toEqual('GET') | ||||
|   }) | ||||
|  | ||||
|   it('should catch error and show toast on retrieve ui_settings error', fakeAsync(() => { | ||||
|     const toastSpy = jest.spyOn(toastService, 'showError') | ||||
|   it('should catch error and show notification on retrieve ui_settings error', fakeAsync(() => { | ||||
|     const notificationSpy = jest.spyOn(notificationService, 'showError') | ||||
|     httpTestingController | ||||
|       .expectOne(`${environment.apiBaseUrl}ui_settings/`) | ||||
|       .flush( | ||||
| @@ -131,7 +131,7 @@ describe('SettingsService', () => { | ||||
|         { status: 403, statusText: 'Forbidden' } | ||||
|       ) | ||||
|     tick(500) | ||||
|     expect(toastSpy).toHaveBeenCalled() | ||||
|     expect(notificationSpy).toHaveBeenCalled() | ||||
|   })) | ||||
|  | ||||
|   it('calls ui_settings api endpoint with POST on store', () => { | ||||
|   | ||||
| @@ -26,13 +26,13 @@ import { | ||||
|   UiSettings, | ||||
| } from '../data/ui-settings' | ||||
| import { User } from '../data/user' | ||||
| import { NotificationService } from './notification.service' | ||||
| import { | ||||
|   PermissionAction, | ||||
|   PermissionsService, | ||||
|   PermissionType, | ||||
| } from './permissions.service' | ||||
| import { CustomFieldsService } from './rest/custom-fields.service' | ||||
| import { ToastService } from './toast.service' | ||||
|  | ||||
| export interface LanguageOption { | ||||
|   code: string | ||||
| @@ -294,7 +294,7 @@ export class SettingsService { | ||||
|     private meta: Meta, | ||||
|     @Inject(LOCALE_ID) private localeId: string, | ||||
|     protected http: HttpClient, | ||||
|     private toastService: ToastService, | ||||
|     private notificationService: NotificationService, | ||||
|     private permissionsService: PermissionsService, | ||||
|     private customFieldsService: CustomFieldsService | ||||
|   ) { | ||||
| @@ -307,7 +307,7 @@ export class SettingsService { | ||||
|       first(), | ||||
|       catchError((error) => { | ||||
|         setTimeout(() => { | ||||
|           this.toastService.showError('Error loading settings', error) | ||||
|           this.notificationService.showError('Error loading settings', error) | ||||
|         }, 500) | ||||
|         return of({ | ||||
|           settings: { | ||||
| @@ -601,7 +601,7 @@ export class SettingsService { | ||||
|           this.cookieService.get(this.getLanguageCookieName()) | ||||
|         ) | ||||
|       } catch (error) { | ||||
|         this.toastService.showError(errorMessage) | ||||
|         this.notificationService.showError(errorMessage) | ||||
|         console.log(error) | ||||
|       } | ||||
|  | ||||
| @@ -610,10 +610,10 @@ export class SettingsService { | ||||
|         .subscribe({ | ||||
|           next: () => { | ||||
|             this.updateAppearanceSettings() | ||||
|             this.toastService.showInfo(successMessage) | ||||
|             this.notificationService.showInfo(successMessage) | ||||
|           }, | ||||
|           error: (e) => { | ||||
|             this.toastService.showError(errorMessage) | ||||
|             this.notificationService.showError(errorMessage) | ||||
|             console.log(e) | ||||
|           }, | ||||
|         }) | ||||
| @@ -633,7 +633,7 @@ export class SettingsService { | ||||
|         .pipe(first()) | ||||
|         .subscribe({ | ||||
|           error: (e) => { | ||||
|             this.toastService.showError( | ||||
|             this.notificationService.showError( | ||||
|               'Error migrating update checking setting' | ||||
|             ) | ||||
|             console.log(e) | ||||
| @@ -663,7 +663,7 @@ export class SettingsService { | ||||
|       this.storeSettings() | ||||
|         .pipe(first()) | ||||
|         .subscribe(() => { | ||||
|           this.toastService.showInfo( | ||||
|           this.notificationService.showInfo( | ||||
|             $localize`You can restart the tour from the settings page.` | ||||
|           ) | ||||
|         }) | ||||
|   | ||||
| @@ -1,109 +0,0 @@ | ||||
| import { TestBed } from '@angular/core/testing' | ||||
| import { ToastService } from './toast.service' | ||||
|  | ||||
| describe('ToastService', () => { | ||||
|   let toastService: ToastService | ||||
|  | ||||
|   beforeEach(() => { | ||||
|     TestBed.configureTestingModule({ | ||||
|       providers: [ToastService], | ||||
|     }) | ||||
|  | ||||
|     toastService = TestBed.inject(ToastService) | ||||
|   }) | ||||
|  | ||||
|   it('adds toast on show', () => { | ||||
|     const toast = { | ||||
|       title: 'Title', | ||||
|       content: 'content', | ||||
|       delay: 5000, | ||||
|     } | ||||
|     toastService.show(toast) | ||||
|  | ||||
|     toastService.getToasts().subscribe((toasts) => { | ||||
|       expect(toasts).toContainEqual(toast) | ||||
|     }) | ||||
|   }) | ||||
|  | ||||
|   it('adds a unique id to toast on show', () => { | ||||
|     const toast = { | ||||
|       title: 'Title', | ||||
|       content: 'content', | ||||
|       delay: 5000, | ||||
|     } | ||||
|     toastService.show(toast) | ||||
|  | ||||
|     toastService.getToasts().subscribe((toasts) => { | ||||
|       expect(toasts[0].id).toBeDefined() | ||||
|     }) | ||||
|   }) | ||||
|  | ||||
|   it('parses error string to object on show', () => { | ||||
|     const toast = { | ||||
|       title: 'Title', | ||||
|       content: 'content', | ||||
|       delay: 5000, | ||||
|       error: 'Error string', | ||||
|     } | ||||
|     toastService.show(toast) | ||||
|  | ||||
|     toastService.getToasts().subscribe((toasts) => { | ||||
|       expect(toasts[0].error).toEqual('Error string') | ||||
|     }) | ||||
|   }) | ||||
|  | ||||
|   it('creates toasts with defaults on showInfo and showError', () => { | ||||
|     toastService.showInfo('Info toast') | ||||
|     toastService.showError('Error toast') | ||||
|  | ||||
|     toastService.getToasts().subscribe((toasts) => { | ||||
|       expect(toasts).toContainEqual({ | ||||
|         content: 'Info toast', | ||||
|         delay: 5000, | ||||
|       }) | ||||
|       expect(toasts).toContainEqual({ | ||||
|         content: 'Error toast', | ||||
|         delay: 10000, | ||||
|       }) | ||||
|     }) | ||||
|   }) | ||||
|  | ||||
|   it('removes toast on close', () => { | ||||
|     const toast = { | ||||
|       title: 'Title', | ||||
|       content: 'content', | ||||
|       delay: 5000, | ||||
|     } | ||||
|     toastService.show(toast) | ||||
|     toastService.closeToast(toast) | ||||
|  | ||||
|     toastService.getToasts().subscribe((toasts) => { | ||||
|       expect(toasts).toHaveLength(0) | ||||
|     }) | ||||
|   }) | ||||
|  | ||||
|   it('clears all toasts on clearToasts', () => { | ||||
|     toastService.showInfo('Info toast') | ||||
|     toastService.showError('Error toast') | ||||
|     toastService.clearToasts() | ||||
|  | ||||
|     toastService.getToasts().subscribe((toasts) => { | ||||
|       expect(toasts).toHaveLength(0) | ||||
|     }) | ||||
|   }) | ||||
|  | ||||
|   it('suppresses popup toasts if suppressPopupToasts is true', (finish) => { | ||||
|     toastService.showToast.subscribe((toast) => { | ||||
|       expect(toast).not.toBeNull() | ||||
|     }) | ||||
|     toastService.showInfo('Info toast') | ||||
|  | ||||
|     toastService.showToast.subscribe((toast) => { | ||||
|       expect(toast).toBeNull() | ||||
|       finish() | ||||
|     }) | ||||
|  | ||||
|     toastService.suppressPopupToasts = true | ||||
|     toastService.showInfo('Info toast') | ||||
|   }) | ||||
| }) | ||||
| @@ -1,87 +0,0 @@ | ||||
| import { Injectable } from '@angular/core' | ||||
| import { Subject } from 'rxjs' | ||||
| import { v4 as uuidv4 } from 'uuid' | ||||
|  | ||||
| export interface Toast { | ||||
|   id?: string | ||||
|  | ||||
|   content: string | ||||
|  | ||||
|   delay: number | ||||
|  | ||||
|   delayRemaining?: number | ||||
|  | ||||
|   action?: any | ||||
|  | ||||
|   actionName?: string | ||||
|  | ||||
|   classname?: string | ||||
|  | ||||
|   error?: any | ||||
| } | ||||
|  | ||||
| @Injectable({ | ||||
|   providedIn: 'root', | ||||
| }) | ||||
| export class ToastService { | ||||
|   constructor() {} | ||||
|   _suppressPopupToasts: boolean | ||||
|  | ||||
|   set suppressPopupToasts(value: boolean) { | ||||
|     this._suppressPopupToasts = value | ||||
|     this.showToast.next(null) | ||||
|   } | ||||
|  | ||||
|   private toasts: Toast[] = [] | ||||
|  | ||||
|   private toastsSubject: Subject<Toast[]> = new Subject() | ||||
|  | ||||
|   public showToast: Subject<Toast> = new Subject() | ||||
|  | ||||
|   show(toast: Toast) { | ||||
|     if (!toast.id) { | ||||
|       toast.id = uuidv4() | ||||
|     } | ||||
|     if (typeof toast.error === 'string') { | ||||
|       try { | ||||
|         toast.error = JSON.parse(toast.error) | ||||
|       } catch (e) {} | ||||
|     } | ||||
|     this.toasts.unshift(toast) | ||||
|     if (!this._suppressPopupToasts) { | ||||
|       this.showToast.next(toast) | ||||
|     } | ||||
|     this.toastsSubject.next(this.toasts) | ||||
|   } | ||||
|  | ||||
|   showError(content: string, error: any = null, delay: number = 10000) { | ||||
|     this.show({ | ||||
|       content: content, | ||||
|       delay: delay, | ||||
|       classname: 'error', | ||||
|       error, | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|   showInfo(content: string, delay: number = 5000) { | ||||
|     this.show({ content: content, delay: delay }) | ||||
|   } | ||||
|  | ||||
|   closeToast(toast: Toast) { | ||||
|     let index = this.toasts.findIndex((t) => t.id == toast.id) | ||||
|     if (index > -1) { | ||||
|       this.toasts.splice(index, 1) | ||||
|       this.toastsSubject.next(this.toasts) | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   getToasts() { | ||||
|     return this.toastsSubject | ||||
|   } | ||||
|  | ||||
|   clearToasts() { | ||||
|     this.toasts = [] | ||||
|     this.toastsSubject.next(this.toasts) | ||||
|     this.showToast.next(null) | ||||
|   } | ||||
| } | ||||
| @@ -571,7 +571,7 @@ table.table { | ||||
| } | ||||
|  | ||||
| .toast { | ||||
|   --bs-toast-max-width: var(--pngx-toast-max-width); | ||||
|   --bs-toast-max-width: var(--pngx-notification-max-width); | ||||
| } | ||||
|  | ||||
| .alert-primary { | ||||
|   | ||||
| @@ -24,11 +24,11 @@ | ||||
|   --pngx-bg-alt2: var(--bs-gray-200); // #e9ecef | ||||
|   --pngx-bg-disabled: #f7f7f7; | ||||
|   --pngx-focus-alpha: 0.3; | ||||
|   --pngx-toast-max-width: 360px; | ||||
|   --pngx-notification-max-width: 360px; | ||||
|   --bs-info: var(--pngx-bg-alt2); | ||||
|   --bs-info-rgb: 233, 236, 239; | ||||
|   @media screen and (min-width: 1024px) { | ||||
|     --pngx-toast-max-width: 450px; | ||||
|     --pngx-notification-max-width: 450px; | ||||
|   } | ||||
| } | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 shamoon
					shamoon