From 8c555292ac0336f96d0a11743c1eb3795d0ffa2d Mon Sep 17 00:00:00 2001 From: Andrea Chiapparelli - 4Science Date: Fri, 2 Mar 2018 16:19:15 +0100 Subject: [PATCH 01/42] notification start --- src/app/+home-page/home-page.component.html | 14 + src/app/+home-page/home-page.component.ts | 38 ++- src/app/+home-page/home-page.module.ts | 6 +- src/app/app.component.html | 32 ++- src/app/app.component.ts | 19 ++ src/app/app.module.ts | 8 +- src/app/core/core.module.ts | 2 + .../shared/notifications/interfaces/icons.ts | 41 +++ .../interfaces/notification-event.type.ts | 8 + .../interfaces/notification.type.ts | 24 ++ .../notifications/interfaces/options.type.ts | 19 ++ .../notification/notification.component.html | 54 ++++ .../notification/notification.component.scss | 107 +++++++ .../notification/notification.component.ts | 259 +++++++++++++++++ .../notifications-board.component.html | 16 ++ .../notifications-board.component.scss | 43 +++ .../notifications-board.component.ts | 222 +++++++++++++++ .../notifications.service.spec.ts | 263 ++++++++++++++++++ .../notifications/notifications.service.ts | 64 +++++ src/app/shared/shared.module.ts | 19 +- 20 files changed, 1236 insertions(+), 22 deletions(-) create mode 100644 src/app/shared/notifications/interfaces/icons.ts create mode 100644 src/app/shared/notifications/interfaces/notification-event.type.ts create mode 100644 src/app/shared/notifications/interfaces/notification.type.ts create mode 100644 src/app/shared/notifications/interfaces/options.type.ts create mode 100644 src/app/shared/notifications/notification/notification.component.html create mode 100644 src/app/shared/notifications/notification/notification.component.scss create mode 100644 src/app/shared/notifications/notification/notification.component.ts create mode 100644 src/app/shared/notifications/notifications-board/notifications-board.component.html create mode 100644 src/app/shared/notifications/notifications-board/notifications-board.component.scss create mode 100644 src/app/shared/notifications/notifications-board/notifications-board.component.ts create mode 100644 src/app/shared/notifications/notifications.service.spec.ts create mode 100644 src/app/shared/notifications/notifications.service.ts diff --git a/src/app/+home-page/home-page.component.html b/src/app/+home-page/home-page.component.html index 6a3e20ca9d..969c41e779 100644 --- a/src/app/+home-page/home-page.component.html +++ b/src/app/+home-page/home-page.component.html @@ -3,3 +3,17 @@ + + + + + + + + + + + +

Simple example

+ +
diff --git a/src/app/+home-page/home-page.component.ts b/src/app/+home-page/home-page.component.ts index ad25ec0155..7346417e16 100644 --- a/src/app/+home-page/home-page.component.ts +++ b/src/app/+home-page/home-page.component.ts @@ -1,4 +1,6 @@ -import { Component } from '@angular/core'; +import { Component, OnInit, TemplateRef, ViewChild } from '@angular/core'; +import { NotificationsService } from '../shared/notifications/notifications.service'; +import { Options } from '../shared/notifications/interfaces/options.type'; @Component({ selector: 'ds-home-page', @@ -6,5 +8,39 @@ import { Component } from '@angular/core'; templateUrl: './home-page.component.html' }) export class HomePageComponent { + public notificationOptions: Options = { + position: ['top', 'right'], + timeOut: 0, + lastOnBottom: true, + clickIconToClose: false, + showProgressBar: true, + }; + @ViewChild('example') example: TemplateRef; + + constructor(private notificationsService: NotificationsService) { + } + + createNotification() { + const n1 = this.notificationsService.success('Welcome in DSpace', 'Good choice!', + { + showProgressBar: false, + animate: 'rotate', + timeout: 2000}); + const n2 = this.notificationsService.error('Error in DSpace', 'This is a fake error!'); + const n3 = this.notificationsService.info(this.example); + console.log('Notifications pushed'); + console.log(n1); + console.log(n2); + } + + notificationCreated(event) { + console.log('Notification created'); + console.log(event); + } + + notificationDestroyed() { + console.log('Notification destroyed'); + console.log(event); + } } diff --git a/src/app/+home-page/home-page.module.ts b/src/app/+home-page/home-page.module.ts index 0a513260cd..41d733dc6d 100644 --- a/src/app/+home-page/home-page.module.ts +++ b/src/app/+home-page/home-page.module.ts @@ -6,6 +6,8 @@ import { HomePageRoutingModule } from './home-page-routing.module'; import { HomePageComponent } from './home-page.component'; import { TopLevelCommunityListComponent } from './top-level-community-list/top-level-community-list.component'; +import { NotificationComponent } from '../shared/notifications/notification/notification.component'; +import { NotificationsBoardComponent } from '../shared/notifications/notifications-board/notifications-board.component'; @NgModule({ imports: [ @@ -16,7 +18,9 @@ import { TopLevelCommunityListComponent } from './top-level-community-list/top-l declarations: [ HomePageComponent, TopLevelCommunityListComponent, - HomeNewsComponent + HomeNewsComponent, + // NotificationComponent, + // NotificationsBoardComponent ] }) export class HomePageModule { diff --git a/src/app/app.component.html b/src/app/app.component.html index fd1ad55d44..b5922e395f 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -1,11 +1,21 @@ -
-
- - -
- -
- - -
-
+
+
+ + +

Inizio notifiche

+ + +

Fine notifiche

+ +
+ +
+ + +
+
+ + diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 25bdde2d23..5069e04892 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -17,6 +17,7 @@ import { MetadataService } from './core/metadata/metadata.service'; import { HostWindowResizeAction } from './shared/host-window.actions'; import { HostWindowState } from './shared/host-window.reducer'; import { NativeWindowRef, NativeWindowService } from './shared/window.service'; +import { Options } from './shared/notifications/interfaces/options.type'; @Component({ selector: 'ds-app', @@ -27,6 +28,24 @@ import { NativeWindowRef, NativeWindowService } from './shared/window.service'; }) export class AppComponent implements OnInit { + public notificationOptions: Options = { + position: ['top', 'right'], + timeOut: 5000, + lastOnBottom: true, + clickIconToClose: true, + showProgressBar: true + }; + + notificationCreated(event) { + console.log('Notification created'); + console.log(event); + } + + notificationDestroyed() { + console.log('Notification destroyed'); + console.log(event); + } + constructor( @Inject(GLOBAL_CONFIG) public config: GlobalConfig, @Inject(NativeWindowService) private _window: NativeWindowRef, diff --git a/src/app/app.module.ts b/src/app/app.module.ts index d2b0d72b78..12f003cab7 100755 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -1,7 +1,6 @@ import { APP_BASE_HREF, CommonModule } from '@angular/common'; import { HttpClientModule } from '@angular/common/http'; import { NgModule } from '@angular/core'; -import { BrowserTransferStateModule } from '@angular/platform-browser'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; @@ -29,6 +28,9 @@ import { HeaderComponent } from './header/header.component'; import { PageNotFoundComponent } from './pagenotfound/pagenotfound.component'; import { DSpaceRouterStateSerializer } from './shared/ngrx/dspace-router-state-serializer'; +import { NotificationsService } from './shared/notifications/notifications.service'; +import { NotificationsBoardComponent } from './shared/notifications/notifications-board/notifications-board.component'; +import { NotificationComponent } from './shared/notifications/notification/notification.component'; export function getConfig() { return ENV_CONFIG; @@ -85,7 +87,9 @@ if (!ENV_CONFIG.production) { AppComponent, HeaderComponent, FooterComponent, - PageNotFoundComponent + PageNotFoundComponent, + NotificationComponent, + NotificationsBoardComponent ], exports: [AppComponent] }) diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts index 768f05f24b..41cc0b88ce 100644 --- a/src/app/core/core.module.ts +++ b/src/app/core/core.module.ts @@ -38,6 +38,7 @@ import { SubmissionDefinitionsConfigService } from './config/submission-definiti import { SubmissionFormsConfigService } from './config/submission-forms-config.service'; import { SubmissionSectionsConfigService } from './config/submission-sections-config.service'; import { UUIDService } from './shared/uuid.service'; +import { NotificationsService } from '../shared/notifications/notifications.service'; const IMPORTS = [ CommonModule, @@ -77,6 +78,7 @@ const PROVIDERS = [ SubmissionFormsConfigService, SubmissionSectionsConfigService, UUIDService, + NotificationsService, { provide: NativeWindowService, useFactory: NativeWindowFactory } ]; diff --git a/src/app/shared/notifications/interfaces/icons.ts b/src/app/shared/notifications/interfaces/icons.ts new file mode 100644 index 0000000000..bf3066c2ac --- /dev/null +++ b/src/app/shared/notifications/interfaces/icons.ts @@ -0,0 +1,41 @@ +export interface Icons { + alert: string; + error: string; + info: string; + warn: string; + success: string; +} + +export const defaultIcons: Icons = { + alert: ` + + + + + `, + error: ` + + + + + `, + info: ` + + + + + `, + success: ` + + + + + `, + warn: ` + + + + + + ` +}; diff --git a/src/app/shared/notifications/interfaces/notification-event.type.ts b/src/app/shared/notifications/interfaces/notification-event.type.ts new file mode 100644 index 0000000000..978cf3dc69 --- /dev/null +++ b/src/app/shared/notifications/interfaces/notification-event.type.ts @@ -0,0 +1,8 @@ +import {Notification} from './notification.type'; + +export interface NotificationEvent { + add?: boolean; + command: string; + id?: string; + notification?: Notification; +} diff --git a/src/app/shared/notifications/interfaces/notification.type.ts b/src/app/shared/notifications/interfaces/notification.type.ts new file mode 100644 index 0000000000..a6cdc890da --- /dev/null +++ b/src/app/shared/notifications/interfaces/notification.type.ts @@ -0,0 +1,24 @@ +import {EventEmitter} from '@angular/core'; + +export interface Notification { + id?: string + type: string + icon: string + title?: any + content?: any + override?: any + html?: any + state?: string + createdOn?: Date + destroyedOn?: Date + animate?: string + timeOut?: number + maxLength?: number + pauseOnHover?: boolean + clickToClose?: boolean + clickIconToClose?: boolean + theClass?: string + click?: EventEmitter<{}>; + clickIcon?: EventEmitter<{}>; + timeoutEnd?: EventEmitter<{}>; +} diff --git a/src/app/shared/notifications/interfaces/options.type.ts b/src/app/shared/notifications/interfaces/options.type.ts new file mode 100644 index 0000000000..46c4eb9b37 --- /dev/null +++ b/src/app/shared/notifications/interfaces/options.type.ts @@ -0,0 +1,19 @@ +import {Icons} from './icons'; + +export interface Options { + timeOut?: number; + showProgressBar?: boolean; + pauseOnHover?: boolean; + lastOnBottom?: boolean; + clickToClose?: boolean; + clickIconToClose?: boolean; + maxLength?: number; + maxStack?: number; + preventDuplicates?: boolean; + preventLastDuplicates?: boolean | string; + theClass?: string; + rtl?: boolean; + animate?: 'fade' | 'fromTop' | 'fromRight' | 'fromBottom' | 'fromLeft' | 'rotate' | 'scale'; + icons?: Icons; + position?: ['top' | 'bottom' | 'middle', 'right' | 'left' | 'center']; +} diff --git a/src/app/shared/notifications/notification/notification.component.html b/src/app/shared/notifications/notification/notification.component.html new file mode 100644 index 0000000000..b77f3657a4 --- /dev/null +++ b/src/app/shared/notifications/notification/notification.component.html @@ -0,0 +1,54 @@ +
+ +
+ +
+ +
+ + +
+
+ +
+ +
+ + +
+
+ +
+
+
+
+ +
+ + +
+
+ +
+
+ +
+ +
+ +
diff --git a/src/app/shared/notifications/notification/notification.component.scss b/src/app/shared/notifications/notification/notification.component.scss new file mode 100644 index 0000000000..22ad8c0e2d --- /dev/null +++ b/src/app/shared/notifications/notification/notification.component.scss @@ -0,0 +1,107 @@ +.simple-notification { + width: 100%; + padding: 10px 20px; + box-sizing: border-box; + position: relative; + float: left; + margin-bottom: 10px; + color: #fff; + cursor: pointer; + transition: all 0.5s; + min-height: 70px; +} + +.simple-notification .sn-title, +.simple-notification .sn-content, +.simple-notification .sn-html { + margin: 0; +} + +.simple-notification .sn-title { + line-height: 30px; + font-size: 20px; +} + +.simple-notification .sn-content { + font-size: 16px; + line-height: 20px; +} + +.simple-notification.has-icon .sn-title, +.simple-notification.has-icon .sn-content, +.simple-notification.has-icon .sn-html { + padding: 0 50px 0 0; +} + +.simple-notification .icon { + position: absolute; + box-sizing: border-box; + top: 0; + right: 0; + width: 70px; + height: 70px; + padding: 10px; +} + +.simple-notification .icon.icon-hover:hover { + opacity: 0.5; +} + +.simple-notification .icon svg { + fill: #fff; + width: 100%; + height: 100%; +} + +.simple-notification .icon svg g { + fill: #fff; +} + +.simple-notification.rtl-mode.has-icon .sn-title, +.simple-notification.rtl-mode.has-icon .sn-content, +.simple-notification.rtl-mode.has-icon .sn-html { + padding: 0 0 0 50px; +} + +.simple-notification.rtl-mode { + direction: rtl; +} + +.simple-notification.rtl-mode .sn-content { + padding: 0 0 0 50px; +} + +.simple-notification.rtl-mode svg { + left: 0; + right: auto; +} + +.simple-notification.error { background: #F44336; } +.simple-notification.success { background: #8BC34A; } +.simple-notification.alert { background: #ffdb5b; } +.simple-notification.info { background: #03A9F4; } +.simple-notification.warn { background: #ffdb5b; } + +.simple-notification .sn-progress-loader { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 5px; +} + +.simple-notification .sn-progress-loader span { + float: left; + height: 100%; +} + +.simple-notification.success .sn-progress-loader span { background: #689F38; } +.simple-notification.error .sn-progress-loader span { background: #D32F2F; } +.simple-notification.alert .sn-progress-loader span { background: #edc242; } +.simple-notification.info .sn-progress-loader span { background: #0288D1; } +.simple-notification.warn .sn-progress-loader span { background: #edc242; } +.simple-notification.bare .sn-progress-loader span { background: #ccc; } + +.simple-notification.warn div .sn-title, +.simple-notification.warn div .sn-content, +.simple-notification.warn div .sn-html { color: #444; } diff --git a/src/app/shared/notifications/notification/notification.component.ts b/src/app/shared/notifications/notification/notification.component.ts new file mode 100644 index 0000000000..461ca2621f --- /dev/null +++ b/src/app/shared/notifications/notification/notification.component.ts @@ -0,0 +1,259 @@ +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + Input, + NgZone, + OnDestroy, + OnInit, + TemplateRef, + ViewEncapsulation +} from '@angular/core'; +import { animate, state, style, transition, trigger } from '@angular/animations'; +import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; +import { Notification } from '../interfaces/notification.type'; +import { NotificationsService } from '../notifications.service'; + +@Component({ + selector: 'ds-notification', + encapsulation: ViewEncapsulation.None, + animations: [ + trigger('enterLeave', [ + + // Fade + state('fade', style({opacity: 1})), + transition('* => fade', [ + style({opacity: 0}), + animate('400ms ease-in-out') + ]), + state('fadeOut', style({opacity: 0})), + transition('fade => fadeOut', [ + style({opacity: 1}), + animate('300ms ease-in-out') + ]), + + // Enter from top + state('fromTop', style({opacity: 1, transform: 'translateY(0)'})), + transition('* => fromTop', [ + style({opacity: 0, transform: 'translateY(-5%)'}), + animate('400ms ease-in-out') + ]), + state('fromTopOut', style({opacity: 0, transform: 'translateY(5%)'})), + transition('fromTop => fromTopOut', [ + style({opacity: 1, transform: 'translateY(0)'}), + animate('300ms ease-in-out') + ]), + + // Enter from right + state('fromRight', style({opacity: 1, transform: 'translateX(0)'})), + transition('* => fromRight', [ + style({opacity: 0, transform: 'translateX(5%)'}), + animate('400ms ease-in-out') + ]), + state('fromRightOut', style({opacity: 0, transform: 'translateX(-5%)'})), + transition('fromRight => fromRightOut', [ + style({opacity: 1, transform: 'translateX(0)'}), + animate('300ms ease-in-out') + ]), + + // Enter from bottom + state('fromBottom', style({opacity: 1, transform: 'translateY(0)'})), + transition('* => fromBottom', [ + style({opacity: 0, transform: 'translateY(5%)'}), + animate('400ms ease-in-out') + ]), + state('fromBottomOut', style({opacity: 0, transform: 'translateY(-5%)'})), + transition('fromBottom => fromBottomOut', [ + style({opacity: 1, transform: 'translateY(0)'}), + animate('300ms ease-in-out') + ]), + + // Enter from left + state('fromLeft', style({opacity: 1, transform: 'translateX(0)'})), + transition('* => fromLeft', [ + style({opacity: 0, transform: 'translateX(-5%)'}), + animate('400ms ease-in-out') + ]), + state('fromLeftOut', style({opacity: 0, transform: 'translateX(5%)'})), + transition('fromLeft => fromLeftOut', [ + style({opacity: 1, transform: 'translateX(0)'}), + animate('300ms ease-in-out') + ]), + + // Rotate + state('scale', style({opacity: 1, transform: 'scale(1)'})), + transition('* => scale', [ + style({opacity: 0, transform: 'scale(0)'}), + animate('400ms ease-in-out') + ]), + state('scaleOut', style({opacity: 0, transform: 'scale(0)'})), + transition('scale => scaleOut', [ + style({opacity: 1, transform: 'scale(1)'}), + animate('400ms ease-in-out') + ]), + + // Scale + state('rotate', style({opacity: 1, transform: 'rotate(0deg)'})), + transition('* => rotate', [ + style({opacity: 0, transform: 'rotate(5deg)'}), + animate('400ms ease-in-out') + ]), + state('rotateOut', style({opacity: 0, transform: 'rotate(-5deg)'})), + transition('rotate => rotateOut', [ + style({opacity: 1, transform: 'rotate(0deg)'}), + animate('400ms ease-in-out') + ]) + ]) + ], + templateUrl: './notification.component.html', + styleUrls: ['./notification.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush +}) + +export class NotificationComponent implements OnInit, OnDestroy { + + @Input() public timeOut: number; + @Input() public showProgressBar: boolean; + @Input() public pauseOnHover: boolean; + @Input() public clickToClose: boolean; + @Input() public clickIconToClose: boolean; + @Input() public maxLength: number; + @Input() public theClass: string; + @Input() public rtl: boolean; + @Input() public animate: string; + @Input() public position: number; + @Input() public item: Notification; + + // Progress bar variables + public title: any; + public content: any; + + public titleIsTemplate = false; + public contentIsTemplate = false; + public htmlIsTemplate = false; + + public progressWidth = 0; + public safeSvg: SafeHtml; + + private stopTime = false; + private timer: any; + private steps: number; + private speed: number; + private count = 0; + private start: any; + + private diff: any; + private icon: string; + + constructor(private notificationService: NotificationsService, + private domSanitizer: DomSanitizer, + private cdr: ChangeDetectorRef, + private zone: NgZone) { + } + + ngOnInit(): void { + if (this.item.override) { + this.attachOverrides(); + } + + if (this.animate) { + this.item.state = this.animate; + } + + if (this.timeOut !== 0) { + this.startTimeOut(); + } + + this.contentType(this.item.title, 'title'); + this.contentType(this.item.content, 'content'); + this.contentType(this.item.html, 'html'); + + this.safeSvg = this.domSanitizer.bypassSecurityTrustHtml(this.icon || this.item.icon); + } + + startTimeOut(): void { + this.steps = this.timeOut / 10; + this.speed = this.timeOut / this.steps; + this.start = new Date().getTime(); + this.zone.runOutsideAngular(() => this.timer = setTimeout(this.instance, this.speed)); + } + + onEnter(): void { + if (this.pauseOnHover) { + this.stopTime = true; + } + } + + onLeave(): void { + if (this.pauseOnHover) { + this.stopTime = false; + this.zone.runOutsideAngular(() => setTimeout(this.instance, (this.speed - this.diff))); + } + } + + onClick($e: MouseEvent): void { + this.item.click!.emit($e); + + if (this.clickToClose) { + this.remove(); + } + } + + onClickIcon($e: MouseEvent): void { + this.item.clickIcon!.emit($e); + + if (this.clickIconToClose) { + this.remove(); + } + } + + // Attach all the overrides + attachOverrides(): void { + Object.keys(this.item.override).forEach((a) => { + if (this.hasOwnProperty(a)) { + (this as any)[a] = this.item.override[a]; + } + }); + } + + ngOnDestroy(): void { + clearTimeout(this.timer); + } + + private instance = () => { + this.diff = (new Date().getTime() - this.start) - (this.count * this.speed); + + if (this.count++ === this.steps) { + this.remove(); + this.item.timeoutEnd!.emit(); + } else if (!this.stopTime) { + if (this.showProgressBar) { + this.progressWidth += 100 / this.steps; + } + + this.timer = setTimeout(this.instance, (this.speed - this.diff)); + } + this.zone.run(() => this.cdr.detectChanges()); + }; + + private remove() { + if (this.animate) { + this.item.state = this.animate + 'Out'; + setTimeout(() => { + this.notificationService.set(this.item, false); + }, 310); + } else { + this.notificationService.set(this.item, false); + } + } + + private contentType(item: any, key: string) { + if (item instanceof TemplateRef) { + this[key] = item; + } else { + this[key] = this.domSanitizer.bypassSecurityTrustHtml(item); + } + + this[key + 'IsTemplate'] = item instanceof TemplateRef; + } +} diff --git a/src/app/shared/notifications/notifications-board/notifications-board.component.html b/src/app/shared/notifications/notifications-board/notifications-board.component.html new file mode 100644 index 0000000000..0fa83e3edc --- /dev/null +++ b/src/app/shared/notifications/notifications-board/notifications-board.component.html @@ -0,0 +1,16 @@ +
+ + +
diff --git a/src/app/shared/notifications/notifications-board/notifications-board.component.scss b/src/app/shared/notifications/notifications-board/notifications-board.component.scss new file mode 100644 index 0000000000..e95a2a825b --- /dev/null +++ b/src/app/shared/notifications/notifications-board/notifications-board.component.scss @@ -0,0 +1,43 @@ +.simple-notification-wrapper { + position: fixed; + width: 300px; + z-index: 1000; +} + +.simple-notification-wrapper.left { + left: 20px; +} + +.simple-notification-wrapper.top { + top: 20px; +} + +.simple-notification-wrapper.right { + right: 20px; +} + +.simple-notification-wrapper.bottom { + bottom: 20px; +} + +.simple-notification-wrapper.center { + left: 50%; + transform: translateX(-50%); +} + +.simple-notification-wrapper.middle { + top: 50%; + transform: translateY(-50%); +} + +.simple-notification-wrapper.middle.center { + transform: translate(-50%, -50%); +} + +@media (max-width: 340px) { + .simple-notification-wrapper { + width: auto; + left: 20px; + right: 20px; + } +} diff --git a/src/app/shared/notifications/notifications-board/notifications-board.component.ts b/src/app/shared/notifications/notifications-board/notifications-board.component.ts new file mode 100644 index 0000000000..58a68cf980 --- /dev/null +++ b/src/app/shared/notifications/notifications-board/notifications-board.component.ts @@ -0,0 +1,222 @@ +import { + Component, EventEmitter, OnInit, OnDestroy, ViewEncapsulation, Input, Output, + ChangeDetectionStrategy, ChangeDetectorRef +} from '@angular/core'; +import {Subscription} from 'rxjs/Subscription'; +import {Options} from '../interfaces/options.type'; +import {Notification} from '../interfaces/notification.type'; +import {NotificationsService} from '../notifications.service'; + +@Component({ + selector: 'ds-notifications-board', + encapsulation: ViewEncapsulation.None, + templateUrl: './notifications-board.component.html', + styleUrls: ['./notifications-board.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class NotificationsBoardComponent implements OnInit, OnDestroy { + + @Input() set options(opt: Options) { + this.attachChanges(opt); + } + + @Output() onCreate = new EventEmitter(); + @Output() onDestroy = new EventEmitter(); + + public notifications: Notification[] = []; + public position: ['top' | 'bottom' | 'middle', 'right' | 'left' | 'center'] = ['bottom', 'right']; + + private lastNotificationCreated: Notification; + private listener: Subscription; + + // Received values + private lastOnBottom = true; + private maxStack = 8; + private preventLastDuplicates: any = false; + private preventDuplicates = false; + + // Sent values + public timeOut = 0; + public maxLength = 0; + public clickToClose = true; + public clickIconToClose = false; + public showProgressBar = true; + public pauseOnHover = true; + public theClass = ''; + public rtl = false; + public animate: 'fade' | 'fromTop' | 'fromRight' | 'fromBottom' | 'fromLeft' | 'rotate' | 'scale' = 'fromRight'; + + constructor( + private service: NotificationsService, + private cdr: ChangeDetectorRef + ) {} + + ngOnInit(): void { + // Listen for changes in the service + this.listener = this.service.emitter + .subscribe((item) => { + switch (item.command) { + case 'cleanAll': + this.notifications = []; + break; + + case 'clean': + this.cleanSingle(item.id!); + break; + + case 'set': + if (item.add) { + this.add(item.notification!); + } else { + this.defaultBehavior(item); + } + break; + + default: + this.defaultBehavior(item); + break; + } + + this.cdr.markForCheck(); + }); + } + + // Default behavior on event + defaultBehavior(value: any): void { + this.notifications.splice(this.notifications.indexOf(value.notification), 1); + this.onDestroy.emit(this.buildEmit(value.notification, false)); + } + + // Add the new notification to the notification array + add(item: Notification): void { + item.createdOn = new Date(); + + const toBlock: boolean = this.preventLastDuplicates || this.preventDuplicates ? this.block(item) : false; + + // Save this as the last created notification + this.lastNotificationCreated = item; + // Override icon if set + if (item.override && item.override.icons && item.override.icons[item.type]) { + item.icon = item.override.icons[item.type]; + } + + if (!toBlock) { + // Check if the notification should be added at the start or the end of the array + if (this.lastOnBottom) { + if (this.notifications.length >= this.maxStack) { + this.notifications.splice(0, 1); + } + + this.notifications.push(item); + } else { + if (this.notifications.length >= this.maxStack) { + this.notifications.splice(this.notifications.length - 1, 1); + } + + this.notifications.splice(0, 0, item); + } + + this.onCreate.emit(this.buildEmit(item, true)); + } + } + + // Check if notifications should be prevented + block(item: Notification): boolean { + + const toCheck = item.html ? this.checkHtml : this.checkStandard; + + if (this.preventDuplicates && this.notifications.length > 0) { + for (let i = 0; i < this.notifications.length; i++) { + if (toCheck(this.notifications[i], item)) { + return true; + } + } + } + + if (this.preventLastDuplicates) { + + let comp: Notification; + + if (this.preventLastDuplicates === 'visible' && this.notifications.length > 0) { + if (this.lastOnBottom) { + comp = this.notifications[this.notifications.length - 1]; + } else { + comp = this.notifications[0]; + } + } else if (this.preventLastDuplicates === 'all' && this.lastNotificationCreated) { + comp = this.lastNotificationCreated; + } else { + return false; + } + return toCheck(comp, item); + } + + return false; + } + + checkStandard(checker: Notification, item: Notification): boolean { + return checker.type === item.type && checker.title === item.title && checker.content === item.content; + } + + checkHtml(checker: Notification, item: Notification): boolean { + return checker.html ? checker.type === item.type && checker.title === item.title && checker.content === item.content && checker.html === item.html : false; + } + + // Attach all the changes received in the options object + attachChanges(options: any): void { + Object.keys(options).forEach((a) => { + if (this.hasOwnProperty(a)) { + (this as any)[a] = options[a]; + } else if (a === 'icons') { + this.service.icons = options[a]; + } + }); + } + + buildEmit(notification: Notification, to: boolean) { + const toEmit: Notification = { + createdOn: notification.createdOn, + type: notification.type, + icon: notification.icon, + id: notification.id + }; + + if (notification.html) { + toEmit.html = notification.html; + } else { + toEmit.title = notification.title; + toEmit.content = notification.content; + } + + if (!to) { + toEmit.destroyedOn = new Date(); + } + + return toEmit; + } + + cleanSingle(id: string): void { + let indexOfDelete = 0; + let doDelete = false; + let noti; + + this.notifications.forEach((notification, idx) => { + if (notification.id === id) { + indexOfDelete = idx; + noti = notification; + doDelete = true; + } + }); + + if (doDelete) { + this.notifications.splice(indexOfDelete, 1); + this.onDestroy.emit(this.buildEmit(noti, false)); + } + } + + ngOnDestroy(): void { + if (this.listener) { + this.listener.unsubscribe(); + } + } +} diff --git a/src/app/shared/notifications/notifications.service.spec.ts b/src/app/shared/notifications/notifications.service.spec.ts new file mode 100644 index 0000000000..12bf738b03 --- /dev/null +++ b/src/app/shared/notifications/notifications.service.spec.ts @@ -0,0 +1,263 @@ +import {inject, TestBed} from '@angular/core/testing'; +import {NotificationsService} from './notifications.service'; +import {defaultIcons} from '../interfaces/icons'; +import {NotificationEvent} from '../interfaces/notification-event.type'; +import {Notification} from '../interfaces/notification.type'; + +describe('NotificationsService', () => { + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [], + providers: [NotificationsService], + }); + }); + + let defaultNotification = { + id: '0', + title: 'Test title', + type: 'success', + icon: defaultIcons.success, + content: 'Test Content', + timeOut: 0, + maxLength: 0, + clickToClose: true, + clickIconToClose: false, + showProgressBar: true, + pauseOnHover: true, + theClass: 'initial', + rtl: false, + animate: 'fromRight', + createdOn: new Date(), + destroyedOn: new Date() + }; + + it('Service instantiates', + inject([NotificationsService], (service: NotificationsService) => { + expect(service instanceof NotificationsService).toBeTruthy(); + }) + ); + + it('If override is not set, id is randomly generated', + inject([NotificationsService], (service: NotificationsService) => { + + let notificationEvent: NotificationEvent = null; + + service.emitter.subscribe(item => notificationEvent = item); + + service.set(defaultNotification, true); + + expect(notificationEvent.command).toBe('set'); + expect(notificationEvent.notification.id !== '0').toBeTruthy(); + }) + ); + + it('If override id is set its used', + inject([NotificationsService], (service: NotificationsService) => { + let notificationEvent: NotificationEvent = null, + override = {id: '1'}; + + service.emitter.subscribe(item => notificationEvent = item); + + service.set(Object.assign(defaultNotification, {override: override}), true); + + expect(notificationEvent.notification.id).toBe('1'); + }) + ); + + it('Success method', + inject([NotificationsService], (service: NotificationsService) => { + let notificationEvent: NotificationEvent = null; + service.emitter.subscribe(item => notificationEvent = item); + + let notification: Notification = service.success('Title', 'Message'); + + expect(notification.id !== undefined).toBeTruthy(); + expect(notification.type).toBe('success'); + expect(notification.icon).toBe(defaultIcons.success); + + expect(notification.title).toBe('Title'); + expect(notification.content).toBe('Message'); + expect(notification.override).toBeUndefined(); + expect(notification.html).toBeUndefined(); + expect(notification.state).toBeUndefined(); + expect(notification.createdOn).toBeUndefined(); + expect(notification.destroyedOn).toBeUndefined(); + }) + ); + + it('Error method', + inject([NotificationsService], (service: NotificationsService) => { + let notificationEvent: NotificationEvent = null; + service.emitter.subscribe(item => notificationEvent = item); + + let notification: Notification = service.error('Title', 'Message'); + + expect(notification.id !== undefined).toBeTruthy(); + expect(notification.type).toBe('error'); + expect(notification.icon).toBe(defaultIcons.error); + + expect(notification.title).toBe('Title'); + expect(notification.content).toBe('Message'); + expect(notification.override).toBeUndefined(); + expect(notification.html).toBeUndefined(); + expect(notification.state).toBeUndefined(); + expect(notification.createdOn).toBeUndefined(); + expect(notification.destroyedOn).toBeUndefined(); + }) + ); + + + it('Alert method', + inject([NotificationsService], (service: NotificationsService) => { + let notificationEvent: NotificationEvent = null; + service.emitter.subscribe(item => notificationEvent = item); + + let notification: Notification = service.alert('Title', 'Message'); + + expect(notification.id !== undefined).toBeTruthy(); + expect(notification.type).toBe('alert'); + expect(notification.icon).toBe(defaultIcons.alert); + + expect(notification.title).toBe('Title'); + expect(notification.content).toBe('Message'); + expect(notification.override).toBeUndefined(); + expect(notification.html).toBeUndefined(); + expect(notification.state).toBeUndefined(); + expect(notification.createdOn).toBeUndefined(); + expect(notification.destroyedOn).toBeUndefined(); + }) + ); + + + it('Info method', + inject([NotificationsService], (service: NotificationsService) => { + let notificationEvent: NotificationEvent = null; + service.emitter.subscribe(item => notificationEvent = item); + + let notification: Notification = service.info('Title', 'Message'); + + expect(notification.id !== undefined).toBeTruthy(); + expect(notification.type).toBe('info'); + expect(notification.icon).toBe(defaultIcons.info); + + expect(notification.title).toBe('Title'); + expect(notification.content).toBe('Message'); + expect(notification.override).toBeUndefined(); + expect(notification.html).toBeUndefined(); + expect(notification.state).toBeUndefined(); + expect(notification.createdOn).toBeUndefined(); + expect(notification.destroyedOn).toBeUndefined(); + }) + ); + + it('Warn method', + inject([NotificationsService], (service: NotificationsService) => { + let notificationEvent: NotificationEvent = null; + service.emitter.subscribe(item => notificationEvent = item); + + let notification: Notification = service.warn('Title', 'Message'); + + expect(notification.id !== undefined).toBeTruthy(); + expect(notification.type).toBe('warn'); + expect(notification.icon).toBe(defaultIcons.warn); + + expect(notification.title).toBe('Title'); + expect(notification.content).toBe('Message'); + expect(notification.override).toBeUndefined(); + expect(notification.html).toBeUndefined(); + expect(notification.state).toBeUndefined(); + expect(notification.createdOn).toBeUndefined(); + expect(notification.destroyedOn).toBeUndefined(); + }) + ); + + it('Bare method', + inject([NotificationsService], (service: NotificationsService) => { + let notificationEvent: NotificationEvent = null; + service.emitter.subscribe(item => notificationEvent = item); + + let notification: Notification = service.bare('Title', 'Message'); + + expect(notification.id !== undefined).toBeTruthy(); + expect(notification.type).toBe('bare'); + expect(notification.icon).toBe('bare'); + + expect(notification.title).toBe('Title'); + expect(notification.content).toBe('Message'); + expect(notification.override).toBeUndefined(); + expect(notification.html).toBeUndefined(); + expect(notification.state).toBeUndefined(); + expect(notification.createdOn).toBeUndefined(); + expect(notification.destroyedOn).toBeUndefined(); + }) + ); + + + it('Create method', + inject([NotificationsService], (service: NotificationsService) => { + let notificationEvent: NotificationEvent = null; + service.emitter.subscribe(item => notificationEvent = item); + + let notification: Notification = service.create('Title', 'Message', 'create'); + + expect(notification.id !== undefined).toBeTruthy(); + expect(notification.type).toBe('create'); + // expect(notification.icon).toBe('bare'); + + expect(notification.title).toBe('Title'); + expect(notification.content).toBe('Message'); + expect(notification.override).toBeUndefined(); + expect(notification.html).toBeUndefined(); + expect(notification.state).toBeUndefined(); + expect(notification.createdOn).toBeUndefined(); + expect(notification.destroyedOn).toBeUndefined(); + }) + ); + + + it('Html method', + inject([NotificationsService], (service: NotificationsService) => { + let notificationEvent: NotificationEvent = null; + service.emitter.subscribe(item => notificationEvent = item); + + let notification: Notification = service.html('Title', 'success'); + + expect(notification.id !== undefined).toBeTruthy(); + expect(notification.type).toBe('success'); + expect(notification.icon).toBe('bare'); + + expect(notification.title).toBeUndefined(); + expect(notification.content).toBeUndefined(); + expect(notification.override).toBeUndefined(); + expect(notification.html).toBe('Title'); + expect(notification.state).toBeUndefined(); + expect(notification.createdOn).toBeUndefined(); + expect(notification.destroyedOn).toBeUndefined(); + }) + ); + + it('Empty remove emits cleanAll command', + inject([NotificationsService], (service: NotificationsService) => { + let notificationEvent: NotificationEvent = null; + service.emitter.subscribe(item => notificationEvent = item); + + service.remove(); + + expect(notificationEvent.command).toBe('cleanAll'); + }) + ); + + it('Remove with id emits clean command', + inject([NotificationsService], (service: NotificationsService) => { + let notificationEvent: NotificationEvent = null; + service.emitter.subscribe(item => notificationEvent = item); + + service.remove('1'); + + expect(notificationEvent.command).toBe('clean'); + expect(notificationEvent.id).toBe('1'); + }) + ); + +}); diff --git a/src/app/shared/notifications/notifications.service.ts b/src/app/shared/notifications/notifications.service.ts new file mode 100644 index 0000000000..6a7974a052 --- /dev/null +++ b/src/app/shared/notifications/notifications.service.ts @@ -0,0 +1,64 @@ +import {Injectable, EventEmitter} from '@angular/core'; +import {Subject} from 'rxjs/Subject'; +import {NotificationEvent} from './interfaces/notification-event.type'; +import {Notification} from './interfaces/notification.type'; +import {Icons, defaultIcons} from './interfaces/icons'; + +@Injectable() +export class NotificationsService { + + public emitter = new Subject(); + public icons: Icons = defaultIcons; + + set(notification: Notification, to: boolean): Notification { + notification.id = notification.override && notification.override.id ? notification.override.id : Math.random().toString(36).substring(3); + notification.click = new EventEmitter<{}>(); + notification.timeoutEnd = new EventEmitter<{}>(); + + this.emitter.next({command: 'set', notification: notification, add: to}); + return notification; + }; + + success(title: any = '', content: any = '', override?: any): Notification { + return this.set({title: title, content: content || '', type: 'success', icon: this.icons.success, override: override}, true); + } + + error(title: any = '', content: any = '', override?: any): Notification { + return this.set({title: title, content: content || '', type: 'error', icon: this.icons.error, override: override}, true); + } + + alert(title: any = '', content: any = '', override?: any): Notification { + return this.set({title: title, content: content || '', type: 'alert', icon: this.icons.alert, override: override}, true); + } + + info(title: any = '', content: any = '', override?: any): Notification { + return this.set({title: title, content: content || '', type: 'info', icon: this.icons.info, override: override}, true); + } + + warn(title: any = '', content: any = '', override?: any): Notification { + return this.set({title: title, content: content || '', type: 'warn', icon: this.icons.warn, override: override}, true); + } + + bare(title: any = '', content: any = '', override?: any): Notification { + return this.set({title: title, content: content || '', type: 'bare', icon: 'bare', override: override}, true); + } + + // With type method + create(title: any = '', content: any = '', type = 'success', override?: any): Notification { + return this.set({title: title, content: content, type: type, icon: (this.icons as any)[type], override: override}, true); + } + + // HTML Notification method + html(html: any, type = 'success', override?: any, icon = 'bare'): Notification { + return this.set({html: html, type: type, icon: (this.icons as any)[icon], override: override}, true); + } + + // Remove all notifications method + remove(id?: string): void { + if (id) { + this.emitter.next({command: 'clean', id: id}); + } else { + this.emitter.next({command: 'cleanAll'}); + } + } +} diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index ca13067851..06b871e082 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -21,11 +21,11 @@ import { SearchResultListElementComponent } from './object-list/search-result-li import { WrapperListElementComponent } from './object-list/wrapper-list-element/wrapper-list-element.component'; import { ObjectListComponent } from './object-list/object-list.component'; -import { CollectionGridElementComponent} from './object-grid/collection-grid-element/collection-grid-element.component' -import { CommunityGridElementComponent} from './object-grid/community-grid-element/community-grid-element.component' -import { ItemGridElementComponent} from './object-grid/item-grid-element/item-grid-element.component' -import { AbstractListableElementComponent} from './object-collection/shared/object-collection-element/abstract-listable-element.component' -import { WrapperGridElementComponent} from './object-grid/wrapper-grid-element/wrapper-grid-element.component' +import { CollectionGridElementComponent } from './object-grid/collection-grid-element/collection-grid-element.component'; +import { CommunityGridElementComponent } from './object-grid/community-grid-element/community-grid-element.component'; +import { ItemGridElementComponent } from './object-grid/item-grid-element/item-grid-element.component'; +import { AbstractListableElementComponent } from './object-collection/shared/object-collection-element/abstract-listable-element.component'; +import { WrapperGridElementComponent } from './object-grid/wrapper-grid-element/wrapper-grid-element.component'; import { ObjectGridComponent } from './object-grid/object-grid.component'; import { ObjectCollectionComponent } from './object-collection/object-collection.component'; import { ComcolPageContentComponent } from './comcol-page-content/comcol-page-content.component'; @@ -41,6 +41,8 @@ import { SearchResultGridElementComponent } from './object-grid/search-result-gr import { ViewModeSwitchComponent } from './view-mode-switch/view-mode-switch.component'; import { GridThumbnailComponent } from './object-grid/grid-thumbnail/grid-thumbnail.component'; import { VarDirective } from './utils/var.directive'; +import { NotificationComponent } from './notifications/notification/notification.component'; +import { NotificationsBoardComponent } from './notifications/notifications-board/notifications-board.component'; const MODULES = [ // Do NOT include UniversalModule, HttpModule, or JsonpModule here @@ -79,7 +81,9 @@ const COMPONENTS = [ ThumbnailComponent, GridThumbnailComponent, WrapperListElementComponent, - ViewModeSwitchComponent + ViewModeSwitchComponent, + // NotificationComponent, + // NotificationsBoardComponent ]; const ENTRY_COMPONENTS = [ @@ -91,7 +95,8 @@ const ENTRY_COMPONENTS = [ ItemGridElementComponent, CollectionGridElementComponent, CommunityGridElementComponent, - SearchResultGridElementComponent + SearchResultGridElementComponent, + // NotificationComponent, ]; const DIRECTIVES = [ From cc615ac0bfcc0483ab36168f60f9922659a35a85 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Fri, 2 Mar 2018 19:26:24 +0100 Subject: [PATCH 02/42] Added notifications state --- src/app/app.effects.ts | 4 +- src/app/app.reducer.ts | 61 +++++++++-------- .../models/notification-options.model.ts | 28 ++++++++ .../notifications/models/notification-type.ts | 8 +++ .../models/notification.model.ts | 32 +++++++++ .../notifications/notifications.actions.ts | 66 +++++++++++++++++++ .../notifications/notifications.effects.ts | 46 +++++++++++++ .../notifications/notifications.reducers.ts | 39 +++++++++++ 8 files changed, 254 insertions(+), 30 deletions(-) create mode 100644 src/app/shared/notifications/models/notification-options.model.ts create mode 100644 src/app/shared/notifications/models/notification-type.ts create mode 100644 src/app/shared/notifications/models/notification.model.ts create mode 100644 src/app/shared/notifications/notifications.actions.ts create mode 100644 src/app/shared/notifications/notifications.effects.ts create mode 100644 src/app/shared/notifications/notifications.reducers.ts diff --git a/src/app/app.effects.ts b/src/app/app.effects.ts index 7fc42da80d..6a53d7b619 100644 --- a/src/app/app.effects.ts +++ b/src/app/app.effects.ts @@ -1,8 +1,10 @@ import { HeaderEffects } from './header/header.effects'; import { StoreEffects } from './store.effects'; +import { NotificationsEffects } from './shared/notifications/notifications.effects'; export const appEffects = [ StoreEffects, - HeaderEffects + HeaderEffects, + NotificationsEffects ]; diff --git a/src/app/app.reducer.ts b/src/app/app.reducer.ts index 72b29519de..a31cc54ec4 100644 --- a/src/app/app.reducer.ts +++ b/src/app/app.reducer.ts @@ -1,29 +1,32 @@ -import { ActionReducerMap } from '@ngrx/store'; -import * as fromRouter from '@ngrx/router-store'; - -import { headerReducer, HeaderState } from './header/header.reducer'; -import { hostWindowReducer, HostWindowState } from './shared/host-window.reducer'; -import { - SearchSidebarState, - sidebarReducer -} from './+search-page/search-sidebar/search-sidebar.reducer'; -import { - filterReducer, - SearchFiltersState -} from './+search-page/search-filters/search-filter/search-filter.reducer'; - -export interface AppState { - router: fromRouter.RouterReducerState; - hostWindow: HostWindowState; - header: HeaderState; - searchSidebar: SearchSidebarState; - searchFilter: SearchFiltersState; -} - -export const appReducers: ActionReducerMap = { - router: fromRouter.routerReducer, - hostWindow: hostWindowReducer, - header: headerReducer, - searchSidebar: sidebarReducer, - searchFilter: filterReducer -}; +import { ActionReducerMap } from '@ngrx/store'; +import * as fromRouter from '@ngrx/router-store'; + +import { headerReducer, HeaderState } from './header/header.reducer'; +import { hostWindowReducer, HostWindowState } from './shared/host-window.reducer'; +import { + SearchSidebarState, + sidebarReducer +} from './+search-page/search-sidebar/search-sidebar.reducer'; +import { + filterReducer, + SearchFiltersState +} from './+search-page/search-filters/search-filter/search-filter.reducer'; +import { notificationsReducer, NotificationsState } from './shared/notifications/notifications.reducers'; + +export interface AppState { + router: fromRouter.RouterReducerState; + hostWindow: HostWindowState; + header: HeaderState; + notifications: NotificationsState; + searchSidebar: SearchSidebarState; + searchFilter: SearchFiltersState; +} + +export const appReducers: ActionReducerMap = { + router: fromRouter.routerReducer, + hostWindow: hostWindowReducer, + header: headerReducer, + notifications: notificationsReducer, + searchSidebar: sidebarReducer, + searchFilter: filterReducer +}; diff --git a/src/app/shared/notifications/models/notification-options.model.ts b/src/app/shared/notifications/models/notification-options.model.ts new file mode 100644 index 0000000000..b651a0b3d1 --- /dev/null +++ b/src/app/shared/notifications/models/notification-options.model.ts @@ -0,0 +1,28 @@ +export interface INotificationOptions { + timeOut: number; + clickToClose: boolean; + rtl: boolean; + animate: 'fade' | 'fromTop' | 'fromRight' | 'fromBottom' | 'fromLeft' | 'rotate' | 'scale'; + position: ['top' | 'bottom' | 'middle', 'right' | 'left' | 'center']; +} + +export class NotificationOptions implements INotificationOptions { + public timeOut: number; + public clickToClose: boolean; + public rtl: boolean; + public animate: any; + public position: any; + + constructor(timeOut = 0, + clickToClose = true, + animate = 'scale', + position = ['top' , 'right'], + rtl = false) { + + this.timeOut = timeOut; + this.clickToClose = clickToClose; + this.animate = animate; + this.position = position; + this.rtl = rtl; + } +} diff --git a/src/app/shared/notifications/models/notification-type.ts b/src/app/shared/notifications/models/notification-type.ts new file mode 100644 index 0000000000..43680d6933 --- /dev/null +++ b/src/app/shared/notifications/models/notification-type.ts @@ -0,0 +1,8 @@ +export enum NotificationType { + Success = 'success', + Error = 'error', + Alert = 'alert', + Info = 'info', + Warning = 'warn', + Bare = 'bare' +} diff --git a/src/app/shared/notifications/models/notification.model.ts b/src/app/shared/notifications/models/notification.model.ts new file mode 100644 index 0000000000..b909b8ef26 --- /dev/null +++ b/src/app/shared/notifications/models/notification.model.ts @@ -0,0 +1,32 @@ +import { INotificationOptions, NotificationOptions } from './notification-options.model'; +import { NotificationType } from './notification-type'; +import { isEmpty } from '../../empty.util'; + +export interface INotification { + id: string + type: NotificationType + title?: any + content?: any + options?: INotificationOptions +} + +export class Notification implements INotification { + public id: string; + public type: NotificationType; + public title: any; + public content: any; + public options: INotificationOptions; + + constructor(id: string, + type: NotificationType, + title?: any, + content?: any, + options?: INotificationOptions) { + + this.id = id; + this.type = type; + this.title = title; + this.content = content; + this.options = isEmpty(options) ? new NotificationOptions() : options; + } +} diff --git a/src/app/shared/notifications/notifications.actions.ts b/src/app/shared/notifications/notifications.actions.ts new file mode 100644 index 0000000000..e086e409e5 --- /dev/null +++ b/src/app/shared/notifications/notifications.actions.ts @@ -0,0 +1,66 @@ +// import @ngrx +import { Action } from '@ngrx/store'; + +// import type function +import { type } from '../../shared/ngrx/type'; + +// import models +import { Notification } from './models/notification.model'; + +export const NotificationsActionTypes = { + NEW_NOTIFICATION: type('dspace/notifications/NEW_NOTIFICATION'), + NEW_NOTIFICATION_WITH_TIMER: type('dspace/notifications/NEW_NOTIFICATION_WITH_TIMER'), + REMOVE_NOTIFICATION: type('dspace/notifications/REMOVE_NOTIFICATION'), +}; + +/* tslint:disable:max-classes-per-file */ + +/** + * New notification. + * @class NewNotificationAction + * @implements {Action} + */ +export class NewNotificationAction implements Action { + public type: string = NotificationsActionTypes.NEW_NOTIFICATION; + payload: Notification; + + constructor(notification: Notification) { + this.payload = notification; + } +} + +/** + * New notification. + * @class NewNotificationAction + * @implements {Action} + */ +export class NewNotificationWithTimerAction implements Action { + public type: string = NotificationsActionTypes.NEW_NOTIFICATION_WITH_TIMER; + payload: Notification; + + constructor(notification: Notification) { + this.payload = notification; + } +} + +/** + * New notification. + * @class NewNotificationAction + * @implements {Action} + */ +export class RemoveNotificationAction implements Action { + public type: string = NotificationsActionTypes.REMOVE_NOTIFICATION; + + constructor(public payload?: any) { } +} + +/* tslint:enable:max-classes-per-file */ + +/** + * Actions type. + * @type {NotificationsActions} + */ +export type NotificationsActions + = NewNotificationAction + | NewNotificationWithTimerAction + | RemoveNotificationAction; diff --git a/src/app/shared/notifications/notifications.effects.ts b/src/app/shared/notifications/notifications.effects.ts new file mode 100644 index 0000000000..75fd319ba2 --- /dev/null +++ b/src/app/shared/notifications/notifications.effects.ts @@ -0,0 +1,46 @@ +import { Injectable } from '@angular/core'; + +// import @ngrx +import { Effect, Actions } from '@ngrx/effects'; +import { Action, Store } from '@ngrx/store'; + +// import rxjs +import { Observable } from 'rxjs/Observable'; + +// import services + +// import actions + +import { AppState } from '../../app.reducer'; +import { + NewNotificationWithTimerAction, NotificationsActionTypes, + RemoveNotificationAction +} from './notifications.actions'; + +@Injectable() +export class NotificationsEffects { + + /** + * Authenticate user. + * @method authenticate + */ + @Effect() + public timer: Observable = this.actions$ + .ofType(NotificationsActionTypes.NEW_NOTIFICATION_WITH_TIMER) + .debounceTime((action) => action.payload.options.timeOut) + .map(() => new RemoveNotificationAction()); + /* .switchMap((action: NewNotificationWithTimerAction) => Observable + .timer(30000) + .mapTo(() => new RemoveNotificationAction()) + );*/ + + /** + * @constructor + * @param {Actions} actions$ + * @param {AuthService} authService + * @param {Store} store + */ + constructor(private actions$: Actions, + private store: Store) { + } +} diff --git a/src/app/shared/notifications/notifications.reducers.ts b/src/app/shared/notifications/notifications.reducers.ts new file mode 100644 index 0000000000..4b02ee905d --- /dev/null +++ b/src/app/shared/notifications/notifications.reducers.ts @@ -0,0 +1,39 @@ +// import actions +import { NotificationsActions, NotificationsActionTypes } from './notifications.actions'; + +// import models +import { INotification } from './models/notification.model'; + +/** + * The auth state. + * @interface State + */ +export interface NotificationsState { + [index: number]: INotification; +} + +/** + * The initial state. + */ +const initialState: NotificationsState = []; + +/** + * The reducer function. + * @function reducer + * @param {State} state Current state + * @param {NotificationsActions} action Incoming action + */ +export function notificationsReducer(state: any = initialState, action: NotificationsActions): NotificationsState { + + switch (action.type) { + case NotificationsActionTypes.NEW_NOTIFICATION: + case NotificationsActionTypes.NEW_NOTIFICATION_WITH_TIMER: + return [...state, action.payload]; + + case NotificationsActionTypes.REMOVE_NOTIFICATION: + return []; + + default: + return state; + } +} From 2b8df1cc9d13b526084d0688aff7b0c4149711cf Mon Sep 17 00:00:00 2001 From: Andrea Chiapparelli - 4Science Date: Fri, 2 Mar 2018 19:39:56 +0100 Subject: [PATCH 03/42] bootstrap style notification --- src/app/+home-page/home-page.component.html | 2 +- src/app/+home-page/home-page.component.ts | 13 +- src/app/app.component.html | 2 - src/app/app.component.ts | 9 +- .../notifications/interfaces/options.type.ts | 14 +- .../notification/notification.component.html | 34 ++- .../notification/notification.component.scss | 220 ++++++++++-------- .../notification/notification.component.ts | 44 ++-- .../notifications-board.component.html | 13 +- .../notifications-board.component.ts | 14 +- 10 files changed, 210 insertions(+), 155 deletions(-) diff --git a/src/app/+home-page/home-page.component.html b/src/app/+home-page/home-page.component.html index 969c41e779..d015e53850 100644 --- a/src/app/+home-page/home-page.component.html +++ b/src/app/+home-page/home-page.component.html @@ -14,6 +14,6 @@ -

Simple example

+

Simple Error example

diff --git a/src/app/+home-page/home-page.component.ts b/src/app/+home-page/home-page.component.ts index 7346417e16..44d660ee26 100644 --- a/src/app/+home-page/home-page.component.ts +++ b/src/app/+home-page/home-page.component.ts @@ -11,9 +11,10 @@ export class HomePageComponent { public notificationOptions: Options = { position: ['top', 'right'], timeOut: 0, - lastOnBottom: true, - clickIconToClose: false, - showProgressBar: true, + animate: 'fromLeft' + // lastOnBottom: true, + // clickIconToClose: false, + // showProgressBar: true, }; @ViewChild('example') example: TemplateRef; @@ -24,11 +25,11 @@ export class HomePageComponent { createNotification() { const n1 = this.notificationsService.success('Welcome in DSpace', 'Good choice!', { - showProgressBar: false, animate: 'rotate', timeout: 2000}); - const n2 = this.notificationsService.error('Error in DSpace', 'This is a fake error!'); - const n3 = this.notificationsService.info(this.example); + const n2 = this.notificationsService.info('Info in DSpace', 'For your info...!'); + const n3 = this.notificationsService.warn('Warning in DSpace', 'This is a fake alert!'); + const n4 = this.notificationsService.error(this.example); console.log('Notifications pushed'); console.log(n1); console.log(n2); diff --git a/src/app/app.component.html b/src/app/app.component.html index b5922e395f..6fb3c3a31e 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -2,13 +2,11 @@
-

Inizio notifiche

-

Fine notifiche

diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 5069e04892..4fb14f4d75 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -30,10 +30,11 @@ export class AppComponent implements OnInit { public notificationOptions: Options = { position: ['top', 'right'], - timeOut: 5000, - lastOnBottom: true, - clickIconToClose: true, - showProgressBar: true + timeOut: 0, + animate: 'fromLeft' + // lastOnBottom: true, + // clickIconToClose: false, + // showProgressBar: true, }; notificationCreated(event) { diff --git a/src/app/shared/notifications/interfaces/options.type.ts b/src/app/shared/notifications/interfaces/options.type.ts index 46c4eb9b37..cfd45529cd 100644 --- a/src/app/shared/notifications/interfaces/options.type.ts +++ b/src/app/shared/notifications/interfaces/options.type.ts @@ -2,15 +2,15 @@ import {Icons} from './icons'; export interface Options { timeOut?: number; - showProgressBar?: boolean; - pauseOnHover?: boolean; - lastOnBottom?: boolean; - clickToClose?: boolean; - clickIconToClose?: boolean; + // showProgressBar?: boolean; + // pauseOnHover?: boolean; + // lastOnBottom?: boolean; + // clickToClose?: boolean; + // clickIconToClose?: boolean; maxLength?: number; maxStack?: number; - preventDuplicates?: boolean; - preventLastDuplicates?: boolean | string; + // preventDuplicates?: boolean; + // preventLastDuplicates?: boolean | string; theClass?: string; rtl?: boolean; animate?: 'fade' | 'fromTop' | 'fromRight' | 'fromBottom' | 'fromLeft' | 'rotate' | 'scale'; diff --git a/src/app/shared/notifications/notification/notification.component.html b/src/app/shared/notifications/notification/notification.component.html index b77f3657a4..79b75a15e4 100644 --- a/src/app/shared/notifications/notification/notification.component.html +++ b/src/app/shared/notifications/notification/notification.component.html @@ -1,13 +1,12 @@ - -
+ + {{icon}} +
+
@@ -44,11 +52,21 @@
-
+ + {{icon}} +
+
+
diff --git a/src/app/shared/notifications/notification/notification.component.scss b/src/app/shared/notifications/notification/notification.component.scss index 22ad8c0e2d..c703736f18 100644 --- a/src/app/shared/notifications/notification/notification.component.scss +++ b/src/app/shared/notifications/notification/notification.component.scss @@ -1,80 +1,80 @@ -.simple-notification { - width: 100%; - padding: 10px 20px; - box-sizing: border-box; - position: relative; - float: left; - margin-bottom: 10px; - color: #fff; - cursor: pointer; - transition: all 0.5s; - min-height: 70px; -} - -.simple-notification .sn-title, -.simple-notification .sn-content, -.simple-notification .sn-html { - margin: 0; -} - -.simple-notification .sn-title { - line-height: 30px; - font-size: 20px; -} - -.simple-notification .sn-content { - font-size: 16px; - line-height: 20px; -} - -.simple-notification.has-icon .sn-title, -.simple-notification.has-icon .sn-content, -.simple-notification.has-icon .sn-html { - padding: 0 50px 0 0; -} - -.simple-notification .icon { - position: absolute; - box-sizing: border-box; - top: 0; - right: 0; - width: 70px; - height: 70px; - padding: 10px; -} - -.simple-notification .icon.icon-hover:hover { - opacity: 0.5; -} - -.simple-notification .icon svg { - fill: #fff; - width: 100%; - height: 100%; -} - -.simple-notification .icon svg g { - fill: #fff; -} - -.simple-notification.rtl-mode.has-icon .sn-title, -.simple-notification.rtl-mode.has-icon .sn-content, -.simple-notification.rtl-mode.has-icon .sn-html { - padding: 0 0 0 50px; -} - -.simple-notification.rtl-mode { - direction: rtl; -} - -.simple-notification.rtl-mode .sn-content { - padding: 0 0 0 50px; -} - -.simple-notification.rtl-mode svg { - left: 0; - right: auto; -} +//.simple-notification { +// width: 100%; +// padding: 10px 20px; +// box-sizing: border-box; +// position: relative; +// float: left; +// margin-bottom: 10px; +// color: #fff; +// cursor: pointer; +// transition: all 0.5s; +// min-height: 70px; +//} +// +//.simple-notification .sn-title, +//.simple-notification .sn-content, +//.simple-notification .sn-html { +// margin: 0; +//} +// +//.simple-notification .sn-title { +// line-height: 30px; +// font-size: 20px; +//} +// +//.simple-notification .sn-content { +// font-size: 16px; +// line-height: 20px; +//} +// +//.simple-notification.has-icon .sn-title, +//.simple-notification.has-icon .sn-content, +//.simple-notification.has-icon .sn-html { +// padding: 0 50px 0 0; +//} +// +//.simple-notification { +// position: absolute; +// box-sizing: border-box; +// top: 0; +// right: 0; +// width: 70px; +// height: 70px; +// padding: 10px; +//} +// +//.simple-notification .icon.icon-hover:hover { +// opacity: 0.5; +//} +// +//.simple-notification .icon svg { +// fill: #fff; +// width: 100%; +// height: 100%; +//} +// +//.simple-notification .icon svg g { +// fill: #fff; +//} +// +//.simple-notification.rtl-mode.has-icon .sn-title, +//.simple-notification.rtl-mode.has-icon .sn-content, +//.simple-notification.rtl-mode.has-icon .sn-html { +// padding: 0 0 0 50px; +//} +// +//.simple-notification.rtl-mode { +// direction: rtl; +//} +// +//.simple-notification.rtl-mode .sn-content { +// padding: 0 0 0 50px; +//} +// +//.simple-notification.rtl-mode svg { +// left: 0; +// right: auto; +//} .simple-notification.error { background: #F44336; } .simple-notification.success { background: #8BC34A; } @@ -82,18 +82,18 @@ .simple-notification.info { background: #03A9F4; } .simple-notification.warn { background: #ffdb5b; } -.simple-notification .sn-progress-loader { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 5px; -} - -.simple-notification .sn-progress-loader span { - float: left; - height: 100%; -} +//.simple-notification .sn-progress-loader { +// position: absolute; +// top: 0; +// left: 0; +// width: 100%; +// height: 5px; +//} +// +//.simple-notification .sn-progress-loader span { +// float: left; +// height: 100%; +//} .simple-notification.success .sn-progress-loader span { background: #689F38; } .simple-notification.error .sn-progress-loader span { background: #D32F2F; } @@ -102,6 +102,42 @@ .simple-notification.warn .sn-progress-loader span { background: #edc242; } .simple-notification.bare .sn-progress-loader span { background: #ccc; } -.simple-notification.warn div .sn-title, -.simple-notification.warn div .sn-content, -.simple-notification.warn div .sn-html { color: #444; } +//.simple-notification.warn div .sn-title, +//.simple-notification.warn div .sn-content, +//.simple-notification.warn div .sn-html { color: #444; } + + + +.close { + position: absolute; + top: 5px; + right: 5px; +} + +.icon { + position: absolute; + top: 5px; + left: 5px; + color: black; +} +// +//.icon > fa-times-circle { +// color: red; +//} +// +//.icon > fa-exclamation-triangle { +// color: orange; +//} +// +//.icon > fa-info { +// color: white; +//} +// +//.icon > fa-check { +// color: forestgreen; +//} + +.sn-title, .sn-content, .sn-html { + position: relative; + left: 20px; +} diff --git a/src/app/shared/notifications/notification/notification.component.ts b/src/app/shared/notifications/notification/notification.component.ts index 461ca2621f..bc215481db 100644 --- a/src/app/shared/notifications/notification/notification.component.ts +++ b/src/app/shared/notifications/notification/notification.component.ts @@ -113,12 +113,12 @@ import { NotificationsService } from '../notifications.service'; export class NotificationComponent implements OnInit, OnDestroy { @Input() public timeOut: number; - @Input() public showProgressBar: boolean; - @Input() public pauseOnHover: boolean; - @Input() public clickToClose: boolean; - @Input() public clickIconToClose: boolean; - @Input() public maxLength: number; - @Input() public theClass: string; + // @Input() public showProgressBar: boolean; + // @Input() public pauseOnHover: boolean; + // @Input() public clickToClose: boolean; + // @Input() public clickIconToClose: boolean; + // @Input() public maxLength: number; + // @Input() public theClass: string; @Input() public rtl: boolean; @Input() public animate: string; @Input() public position: number; @@ -179,32 +179,32 @@ export class NotificationComponent implements OnInit, OnDestroy { } onEnter(): void { - if (this.pauseOnHover) { - this.stopTime = true; - } + // if (this.pauseOnHover) { + // this.stopTime = true; + // } } onLeave(): void { - if (this.pauseOnHover) { - this.stopTime = false; - this.zone.runOutsideAngular(() => setTimeout(this.instance, (this.speed - this.diff))); - } + // if (this.pauseOnHover) { + // this.stopTime = false; + // this.zone.runOutsideAngular(() => setTimeout(this.instance, (this.speed - this.diff))); + // } } onClick($e: MouseEvent): void { this.item.click!.emit($e); - if (this.clickToClose) { - this.remove(); - } + // if (this.clickToClose) { + // this.remove(); + // } } onClickIcon($e: MouseEvent): void { this.item.clickIcon!.emit($e); - if (this.clickIconToClose) { - this.remove(); - } + // if (this.clickIconToClose) { + // this.remove(); + // } } // Attach all the overrides @@ -227,9 +227,9 @@ export class NotificationComponent implements OnInit, OnDestroy { this.remove(); this.item.timeoutEnd!.emit(); } else if (!this.stopTime) { - if (this.showProgressBar) { - this.progressWidth += 100 / this.steps; - } + // if (this.showProgressBar) { + // this.progressWidth += 100 / this.steps; + // } this.timer = setTimeout(this.instance, (this.speed - this.diff)); } diff --git a/src/app/shared/notifications/notifications-board/notifications-board.component.html b/src/app/shared/notifications/notifications-board/notifications-board.component.html index 0fa83e3edc..a3f322f049 100644 --- a/src/app/shared/notifications/notifications-board/notifications-board.component.html +++ b/src/app/shared/notifications/notifications-board/notifications-board.component.html @@ -3,14 +3,15 @@ *ngFor="let a of notifications; let i = index" [item]="a" [timeOut]="timeOut" - [clickToClose]="clickToClose" - [clickIconToClose]="clickIconToClose" - [maxLength]="maxLength" - [showProgressBar]="showProgressBar" - [pauseOnHover]="pauseOnHover" - [theClass]="theClass" [rtl]="rtl" [animate]="animate" [position]="i"> + + + + + + + diff --git a/src/app/shared/notifications/notifications-board/notifications-board.component.ts b/src/app/shared/notifications/notifications-board/notifications-board.component.ts index 58a68cf980..3e186f9c45 100644 --- a/src/app/shared/notifications/notifications-board/notifications-board.component.ts +++ b/src/app/shared/notifications/notifications-board/notifications-board.component.ts @@ -37,12 +37,12 @@ export class NotificationsBoardComponent implements OnInit, OnDestroy { // Sent values public timeOut = 0; - public maxLength = 0; - public clickToClose = true; - public clickIconToClose = false; - public showProgressBar = true; - public pauseOnHover = true; - public theClass = ''; + // public maxLength = 0; + // public clickToClose = true; + // public clickIconToClose = false; + // public showProgressBar = true; + // public pauseOnHover = true; + // public theClass = ''; public rtl = false; public animate: 'fade' | 'fromTop' | 'fromRight' | 'fromBottom' | 'fromLeft' | 'rotate' | 'scale' = 'fromRight'; @@ -54,7 +54,7 @@ export class NotificationsBoardComponent implements OnInit, OnDestroy { ngOnInit(): void { // Listen for changes in the service this.listener = this.service.emitter - .subscribe((item) => { + .subscribe((item) => { // Subscribe a stato di redux switch (item.command) { case 'cleanAll': this.notifications = []; From e1e7840ec78e1bda734b471f441c671336496eca Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Mon, 5 Mar 2018 13:01:54 +0100 Subject: [PATCH 04/42] Added actions to notifications state --- .../notifications/notifications.actions.ts | 22 ++++++++++++++++--- .../notifications/notifications.effects.ts | 6 ++--- .../notifications/notifications.reducers.ts | 16 ++++++++++---- 3 files changed, 34 insertions(+), 10 deletions(-) diff --git a/src/app/shared/notifications/notifications.actions.ts b/src/app/shared/notifications/notifications.actions.ts index e086e409e5..6bd76a920d 100644 --- a/src/app/shared/notifications/notifications.actions.ts +++ b/src/app/shared/notifications/notifications.actions.ts @@ -10,6 +10,7 @@ import { Notification } from './models/notification.model'; export const NotificationsActionTypes = { NEW_NOTIFICATION: type('dspace/notifications/NEW_NOTIFICATION'), NEW_NOTIFICATION_WITH_TIMER: type('dspace/notifications/NEW_NOTIFICATION_WITH_TIMER'), + REMOVE_ALL_NOTIFICATIONS: type('dspace/notifications/REMOVE_ALL_NOTIFICATIONS'), REMOVE_NOTIFICATION: type('dspace/notifications/REMOVE_NOTIFICATION'), }; @@ -44,14 +45,28 @@ export class NewNotificationWithTimerAction implements Action { } /** - * New notification. - * @class NewNotificationAction + * Remove all notifications. + * @class RemoveAllNotificationsAction + * @implements {Action} + */ +export class RemoveAllNotificationsAction implements Action { + public type: string = NotificationsActionTypes.REMOVE_ALL_NOTIFICATIONS; + + constructor(public payload?: any) { } +} + +/** + * Remove a notification. + * @class RemoveNotificationAction * @implements {Action} */ export class RemoveNotificationAction implements Action { public type: string = NotificationsActionTypes.REMOVE_NOTIFICATION; + payload: any; - constructor(public payload?: any) { } + constructor(notificationId: any) { + this.payload = notificationId; + } } /* tslint:enable:max-classes-per-file */ @@ -63,4 +78,5 @@ export class RemoveNotificationAction implements Action { export type NotificationsActions = NewNotificationAction | NewNotificationWithTimerAction + | RemoveAllNotificationsAction | RemoveNotificationAction; diff --git a/src/app/shared/notifications/notifications.effects.ts b/src/app/shared/notifications/notifications.effects.ts index 75fd319ba2..fe6637b561 100644 --- a/src/app/shared/notifications/notifications.effects.ts +++ b/src/app/shared/notifications/notifications.effects.ts @@ -24,12 +24,12 @@ export class NotificationsEffects { * Authenticate user. * @method authenticate */ - @Effect() + /*@Effect() public timer: Observable = this.actions$ .ofType(NotificationsActionTypes.NEW_NOTIFICATION_WITH_TIMER) - .debounceTime((action) => action.payload.options.timeOut) + .debounceTime((action) => action.payload.options.timeOut as number) .map(() => new RemoveNotificationAction()); - /* .switchMap((action: NewNotificationWithTimerAction) => Observable + .switchMap((action: NewNotificationWithTimerAction) => Observable .timer(30000) .mapTo(() => new RemoveNotificationAction()) );*/ diff --git a/src/app/shared/notifications/notifications.reducers.ts b/src/app/shared/notifications/notifications.reducers.ts index 4b02ee905d..246009fdfa 100644 --- a/src/app/shared/notifications/notifications.reducers.ts +++ b/src/app/shared/notifications/notifications.reducers.ts @@ -1,5 +1,5 @@ // import actions -import { NotificationsActions, NotificationsActionTypes } from './notifications.actions'; +import { NotificationsActions, NotificationsActionTypes, RemoveNotificationAction } from './notifications.actions'; // import models import { INotification } from './models/notification.model'; @@ -8,8 +8,8 @@ import { INotification } from './models/notification.model'; * The auth state. * @interface State */ -export interface NotificationsState { - [index: number]: INotification; +export interface NotificationsState extends Array { + } /** @@ -30,10 +30,18 @@ export function notificationsReducer(state: any = initialState, action: Notifica case NotificationsActionTypes.NEW_NOTIFICATION_WITH_TIMER: return [...state, action.payload]; - case NotificationsActionTypes.REMOVE_NOTIFICATION: + case NotificationsActionTypes.REMOVE_ALL_NOTIFICATIONS: return []; + case NotificationsActionTypes.REMOVE_NOTIFICATION: + return removeNotification(state, action as RemoveNotificationAction); + default: return state; } } + +const removeNotification = (state: NotificationsState, action: RemoveNotificationAction): NotificationsState => { + const newState = state.filter((item: INotification) => item.id !== action.payload); + return newState; +}; From 31edf91c0aeda9f188b05dffa22db389219565bb Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Mon, 5 Mar 2018 15:15:54 +0100 Subject: [PATCH 05/42] Added notifications state selectors --- src/app/shared/notifications/selectors.ts | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 src/app/shared/notifications/selectors.ts diff --git a/src/app/shared/notifications/selectors.ts b/src/app/shared/notifications/selectors.ts new file mode 100644 index 0000000000..a0b9487f42 --- /dev/null +++ b/src/app/shared/notifications/selectors.ts @@ -0,0 +1,9 @@ +/** + * Returns the user state. + * @function getUserState + * @param {AppState} state Top level state. + * @return {AuthState} + */ +import { AppState } from '../../app.reducer'; + +export const notificationsStateSelector = (state: AppState) => state.notifications; From cfcc024bfcae3a8031cd91545a0cfaf13dc72914 Mon Sep 17 00:00:00 2001 From: Andrea Chiapparelli - 4Science Date: Mon, 5 Mar 2018 13:08:29 +0100 Subject: [PATCH 06/42] fix graphics --- src/app/shared/notifications/notifications.effects.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/app/shared/notifications/notifications.effects.ts b/src/app/shared/notifications/notifications.effects.ts index fe6637b561..b8b9fca4d2 100644 --- a/src/app/shared/notifications/notifications.effects.ts +++ b/src/app/shared/notifications/notifications.effects.ts @@ -24,10 +24,11 @@ export class NotificationsEffects { * Authenticate user. * @method authenticate */ - /*@Effect() + @Effect() public timer: Observable = this.actions$ .ofType(NotificationsActionTypes.NEW_NOTIFICATION_WITH_TIMER) - .debounceTime((action) => action.payload.options.timeOut as number) + // .debounceTime((action: any) => action.payload.options.timeOut) + .debounceTime(3000) .map(() => new RemoveNotificationAction()); .switchMap((action: NewNotificationWithTimerAction) => Observable .timer(30000) From add8c7cec91c7f3412251442f21c413831ee137f Mon Sep 17 00:00:00 2001 From: Andrea Chiapparelli - 4Science Date: Mon, 5 Mar 2018 13:08:42 +0100 Subject: [PATCH 07/42] fix graphics --- src/app/+home-page/home-page.component.ts | 4 +- .../interfaces/notification.type.ts | 2 +- .../notifications/models/notification-type.ts | 6 +- .../models/notification.model.ts | 2 + .../notification/notification.component.html | 54 ++++++------ .../notification/notification.component.scss | 17 ++-- .../notification/notification.component.ts | 82 ++++++++++--------- .../notifications/notifications.service.ts | 23 +++--- 8 files changed, 100 insertions(+), 90 deletions(-) diff --git a/src/app/+home-page/home-page.component.ts b/src/app/+home-page/home-page.component.ts index 44d660ee26..0d66d4e503 100644 --- a/src/app/+home-page/home-page.component.ts +++ b/src/app/+home-page/home-page.component.ts @@ -29,10 +29,12 @@ export class HomePageComponent { timeout: 2000}); const n2 = this.notificationsService.info('Info in DSpace', 'For your info...!'); const n3 = this.notificationsService.warn('Warning in DSpace', 'This is a fake alert!'); - const n4 = this.notificationsService.error(this.example); + const n4 = this.notificationsService.danger(this.example); console.log('Notifications pushed'); console.log(n1); console.log(n2); + console.log(n3); + console.log(n4); } notificationCreated(event) { diff --git a/src/app/shared/notifications/interfaces/notification.type.ts b/src/app/shared/notifications/interfaces/notification.type.ts index a6cdc890da..eb69260cbf 100644 --- a/src/app/shared/notifications/interfaces/notification.type.ts +++ b/src/app/shared/notifications/interfaces/notification.type.ts @@ -3,7 +3,7 @@ import {EventEmitter} from '@angular/core'; export interface Notification { id?: string type: string - icon: string + icon?: string title?: any content?: any override?: any diff --git a/src/app/shared/notifications/models/notification-type.ts b/src/app/shared/notifications/models/notification-type.ts index 43680d6933..b21696ab1f 100644 --- a/src/app/shared/notifications/models/notification-type.ts +++ b/src/app/shared/notifications/models/notification-type.ts @@ -1,8 +1,8 @@ export enum NotificationType { Success = 'success', Error = 'error', - Alert = 'alert', + Danger = 'danger', Info = 'info', - Warning = 'warn', - Bare = 'bare' + Warning = 'warning', + // Bare = 'bare' } diff --git a/src/app/shared/notifications/models/notification.model.ts b/src/app/shared/notifications/models/notification.model.ts index b909b8ef26..f7293f0e36 100644 --- a/src/app/shared/notifications/models/notification.model.ts +++ b/src/app/shared/notifications/models/notification.model.ts @@ -8,6 +8,7 @@ export interface INotification { title?: any content?: any options?: INotificationOptions + html?: any; } export class Notification implements INotification { @@ -16,6 +17,7 @@ export class Notification implements INotification { public title: any; public content: any; public options: INotificationOptions; + public html?: any; constructor(id: string, type: NotificationType, diff --git a/src/app/shared/notifications/notification/notification.component.html b/src/app/shared/notifications/notification/notification.component.html index 79b75a15e4..977a1298b1 100644 --- a/src/app/shared/notifications/notification/notification.component.html +++ b/src/app/shared/notifications/notification/notification.component.html @@ -1,21 +1,38 @@ + +
+ +
+ + diff --git a/src/app/shared/notifications/notification/notification.component.scss b/src/app/shared/notifications/notification/notification.component.scss index c703736f18..80acc733ad 100644 --- a/src/app/shared/notifications/notification/notification.component.scss +++ b/src/app/shared/notifications/notification/notification.component.scss @@ -109,16 +109,19 @@ .close { - position: absolute; - top: 5px; - right: 5px; + //position: absolute; + //top: 5px; + //right: 5px; + float: right; + padding-right: 5px; } .icon { - position: absolute; - top: 5px; - left: 5px; - color: black; + //position: absolute; + //top: 5px; + //left: 5px; + float: left; + padding: 5px; } // //.icon > fa-times-circle { diff --git a/src/app/shared/notifications/notification/notification.component.ts b/src/app/shared/notifications/notification/notification.component.ts index bc215481db..fa8f44f5e6 100644 --- a/src/app/shared/notifications/notification/notification.component.ts +++ b/src/app/shared/notifications/notification/notification.component.ts @@ -11,8 +11,8 @@ import { } from '@angular/core'; import { animate, state, style, transition, trigger } from '@angular/animations'; import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; -import { Notification } from '../interfaces/notification.type'; import { NotificationsService } from '../notifications.service'; +import { INotification } from '../models/notification.model'; @Component({ selector: 'ds-notification', @@ -122,7 +122,7 @@ export class NotificationComponent implements OnInit, OnDestroy { @Input() public rtl: boolean; @Input() public animate: string; @Input() public position: number; - @Input() public item: Notification; + @Input() public item: INotification; // Progress bar variables public title: any; @@ -152,13 +152,13 @@ export class NotificationComponent implements OnInit, OnDestroy { } ngOnInit(): void { - if (this.item.override) { - this.attachOverrides(); - } - - if (this.animate) { - this.item.state = this.animate; - } + // if (this.item.override) { + // this.attachOverrides(); + // } + // + // if (this.animate) { + // this.item.state = this.animate; + // } if (this.timeOut !== 0) { this.startTimeOut(); @@ -168,7 +168,7 @@ export class NotificationComponent implements OnInit, OnDestroy { this.contentType(this.item.content, 'content'); this.contentType(this.item.html, 'html'); - this.safeSvg = this.domSanitizer.bypassSecurityTrustHtml(this.icon || this.item.icon); + // this.safeSvg = this.domSanitizer.bypassSecurityTrustHtml(this.icon || this.item.icon); } startTimeOut(): void { @@ -191,30 +191,30 @@ export class NotificationComponent implements OnInit, OnDestroy { // } } - onClick($e: MouseEvent): void { - this.item.click!.emit($e); + // onClick($e: MouseEvent): void { + // this.item.click!.emit($e); + // + // // if (this.clickToClose) { + // // this.remove(); + // // } + // } - // if (this.clickToClose) { - // this.remove(); - // } - } - - onClickIcon($e: MouseEvent): void { - this.item.clickIcon!.emit($e); - - // if (this.clickIconToClose) { - // this.remove(); - // } - } + // onClickIcon($e: MouseEvent): void { + // this.item.clickIcon!.emit($e); + // + // // if (this.clickIconToClose) { + // // this.remove(); + // // } + // } // Attach all the overrides - attachOverrides(): void { - Object.keys(this.item.override).forEach((a) => { - if (this.hasOwnProperty(a)) { - (this as any)[a] = this.item.override[a]; - } - }); - } + // attachOverrides(): void { + // Object.keys(this.item.override).forEach((a) => { + // if (this.hasOwnProperty(a)) { + // (this as any)[a] = this.item.override[a]; + // } + // }); + // } ngOnDestroy(): void { clearTimeout(this.timer); @@ -225,7 +225,7 @@ export class NotificationComponent implements OnInit, OnDestroy { if (this.count++ === this.steps) { this.remove(); - this.item.timeoutEnd!.emit(); + // this.item.timeoutEnd!.emit(); } else if (!this.stopTime) { // if (this.showProgressBar) { // this.progressWidth += 100 / this.steps; @@ -237,14 +237,16 @@ export class NotificationComponent implements OnInit, OnDestroy { }; private remove() { - if (this.animate) { - this.item.state = this.animate + 'Out'; - setTimeout(() => { - this.notificationService.set(this.item, false); - }, 310); - } else { - this.notificationService.set(this.item, false); - } + // if (this.animate) { + // this.item.state = this.animate + 'Out'; + // setTimeout(() => { + // this.notificationService.set(this.item, false); + // }, 310); + // } else { + // this.notificationService.set(this.item, false); + // } + + this.notificationService.set(this.item, false); } private contentType(item: any, key: string) { diff --git a/src/app/shared/notifications/notifications.service.ts b/src/app/shared/notifications/notifications.service.ts index 6a7974a052..de40aff7cf 100644 --- a/src/app/shared/notifications/notifications.service.ts +++ b/src/app/shared/notifications/notifications.service.ts @@ -3,6 +3,7 @@ import {Subject} from 'rxjs/Subject'; import {NotificationEvent} from './interfaces/notification-event.type'; import {Notification} from './interfaces/notification.type'; import {Icons, defaultIcons} from './interfaces/icons'; +import { NotificationType } from './models/notification-type'; @Injectable() export class NotificationsService { @@ -20,28 +21,28 @@ export class NotificationsService { }; success(title: any = '', content: any = '', override?: any): Notification { - return this.set({title: title, content: content || '', type: 'success', icon: this.icons.success, override: override}, true); + return this.set({title: title, content: content || '', type: NotificationType.Success, override: override}, true); } - error(title: any = '', content: any = '', override?: any): Notification { - return this.set({title: title, content: content || '', type: 'error', icon: this.icons.error, override: override}, true); - } + // error(title: any = '', content: any = '', override?: any): Notification { + // return this.set({title: title, content: content || '', type: NotificationType.Error, override: override}, true); + // } - alert(title: any = '', content: any = '', override?: any): Notification { - return this.set({title: title, content: content || '', type: 'alert', icon: this.icons.alert, override: override}, true); + danger(title: any = '', content: any = '', override?: any): Notification { + return this.set({title: title, content: content || '', type: NotificationType.Danger, override: override}, true); } info(title: any = '', content: any = '', override?: any): Notification { - return this.set({title: title, content: content || '', type: 'info', icon: this.icons.info, override: override}, true); + return this.set({title: title, content: content || '', type: NotificationType.Info, override: override}, true); } warn(title: any = '', content: any = '', override?: any): Notification { - return this.set({title: title, content: content || '', type: 'warn', icon: this.icons.warn, override: override}, true); + return this.set({title: title, content: content || '', type: NotificationType.Warning, override: override}, true); } - bare(title: any = '', content: any = '', override?: any): Notification { - return this.set({title: title, content: content || '', type: 'bare', icon: 'bare', override: override}, true); - } + // bare(title: any = '', content: any = '', override?: any): Notification { + // return this.set({title: title, content: content || '', type: 'bare', icon: 'bare', override: override}, true); + // } // With type method create(title: any = '', content: any = '', type = 'success', override?: any): Notification { From f61f246f23920349e8add876a054d03e1464968c Mon Sep 17 00:00:00 2001 From: Andrea Chiapparelli - 4Science Date: Mon, 5 Mar 2018 15:15:40 +0100 Subject: [PATCH 08/42] starting with redux --- src/app/+home-page/home-page.component.ts | 7 +- .../models/notification.model.ts | 2 +- .../notification/notification.component.scss | 8 +- .../notification/notification.component.ts | 3 +- .../notifications-board.component.ts | 190 ++++++++++-------- .../notifications/notifications.effects.ts | 2 +- .../notifications/notifications.service.ts | 145 ++++++++----- 7 files changed, 217 insertions(+), 140 deletions(-) diff --git a/src/app/+home-page/home-page.component.ts b/src/app/+home-page/home-page.component.ts index 0d66d4e503..1acd4951fa 100644 --- a/src/app/+home-page/home-page.component.ts +++ b/src/app/+home-page/home-page.component.ts @@ -1,6 +1,7 @@ import { Component, OnInit, TemplateRef, ViewChild } from '@angular/core'; import { NotificationsService } from '../shared/notifications/notifications.service'; import { Options } from '../shared/notifications/interfaces/options.type'; +import { NotificationOptions } from '../shared/notifications/models/notification-options.model'; @Component({ selector: 'ds-home-page', @@ -24,11 +25,9 @@ export class HomePageComponent { createNotification() { const n1 = this.notificationsService.success('Welcome in DSpace', 'Good choice!', - { - animate: 'rotate', - timeout: 2000}); + new NotificationOptions(2000, false, 'fromLeft')); const n2 = this.notificationsService.info('Info in DSpace', 'For your info...!'); - const n3 = this.notificationsService.warn('Warning in DSpace', 'This is a fake alert!'); + const n3 = this.notificationsService.warning('Warning in DSpace', 'This is a fake alert!'); const n4 = this.notificationsService.danger(this.example); console.log('Notifications pushed'); console.log(n1); diff --git a/src/app/shared/notifications/models/notification.model.ts b/src/app/shared/notifications/models/notification.model.ts index f7293f0e36..91f2f9bbe9 100644 --- a/src/app/shared/notifications/models/notification.model.ts +++ b/src/app/shared/notifications/models/notification.model.ts @@ -23,7 +23,7 @@ export class Notification implements INotification { type: NotificationType, title?: any, content?: any, - options?: INotificationOptions) { + options?: NotificationOptions) { this.id = id; this.type = type; diff --git a/src/app/shared/notifications/notification/notification.component.scss b/src/app/shared/notifications/notification/notification.component.scss index 80acc733ad..6d2a1469d6 100644 --- a/src/app/shared/notifications/notification/notification.component.scss +++ b/src/app/shared/notifications/notification/notification.component.scss @@ -109,19 +109,15 @@ .close { - //position: absolute; - //top: 5px; - //right: 5px; float: right; padding-right: 5px; + color: darkslategray; } .icon { - //position: absolute; - //top: 5px; - //left: 5px; float: left; padding: 5px; + color: darkslategray; } // //.icon > fa-times-circle { diff --git a/src/app/shared/notifications/notification/notification.component.ts b/src/app/shared/notifications/notification/notification.component.ts index fa8f44f5e6..d228018d25 100644 --- a/src/app/shared/notifications/notification/notification.component.ts +++ b/src/app/shared/notifications/notification/notification.component.ts @@ -246,7 +246,8 @@ export class NotificationComponent implements OnInit, OnDestroy { // this.notificationService.set(this.item, false); // } - this.notificationService.set(this.item, false); + // this.notificationService.set(this.item, false); + this.notificationService.remove(this.item); } private contentType(item: any, key: string) { diff --git a/src/app/shared/notifications/notifications-board/notifications-board.component.ts b/src/app/shared/notifications/notifications-board/notifications-board.component.ts index 3e186f9c45..6501bc7653 100644 --- a/src/app/shared/notifications/notifications-board/notifications-board.component.ts +++ b/src/app/shared/notifications/notifications-board/notifications-board.component.ts @@ -2,10 +2,14 @@ import { Component, EventEmitter, OnInit, OnDestroy, ViewEncapsulation, Input, Output, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; -import {Subscription} from 'rxjs/Subscription'; -import {Options} from '../interfaces/options.type'; -import {Notification} from '../interfaces/notification.type'; -import {NotificationsService} from '../notifications.service'; +import { Subscription } from 'rxjs/Subscription'; +import { Options } from '../interfaces/options.type'; +import { Notification } from '../interfaces/notification.type'; +import { NotificationsService } from '../notifications.service'; +import { Store } from '@ngrx/store'; +import { NotificationsActions, NotificationsActionTypes } from '../notifications.actions'; +import { notificationsReducer } from '../notifications.reducers'; +import { AppState } from '../../../app.reducer'; @Component({ selector: 'ds-notifications-board', @@ -30,10 +34,10 @@ export class NotificationsBoardComponent implements OnInit, OnDestroy { private listener: Subscription; // Received values - private lastOnBottom = true; + private lastOnBottom = false; private maxStack = 8; - private preventLastDuplicates: any = false; - private preventDuplicates = false; + // private preventLastDuplicates: any = true; + // private preventDuplicates = true; // Sent values public timeOut = 0; @@ -46,39 +50,59 @@ export class NotificationsBoardComponent implements OnInit, OnDestroy { public rtl = false; public animate: 'fade' | 'fromTop' | 'fromRight' | 'fromBottom' | 'fromLeft' | 'rotate' | 'scale' = 'fromRight'; - constructor( - private service: NotificationsService, - private cdr: ChangeDetectorRef - ) {} + constructor(private service: NotificationsService, + private store: Store, + private cdr: ChangeDetectorRef) { + } ngOnInit(): void { - // Listen for changes in the service - this.listener = this.service.emitter - .subscribe((item) => { // Subscribe a stato di redux - switch (item.command) { - case 'cleanAll': - this.notifications = []; - break; + this.listener = this.store.select() + (action: NotificationsActions) => { + notificationsReducer(this.notifications, action); - case 'clean': - this.cleanSingle(item.id!); - break; - - case 'set': - if (item.add) { - this.add(item.notification!); - } else { - this.defaultBehavior(item); - } - break; - - default: - this.defaultBehavior(item); - break; - } - - this.cdr.markForCheck(); + // switch (action.type) { + // case NotificationsActionTypes.NEW_NOTIFICATION: + // + // break; + // case NotificationsActionTypes.NEW_NOTIFICATION_WITH_TIMER: + // + // break; + // case NotificationsActionTypes.REMOVE_NOTIFICATION: + // + // break; + // case NotificationsActionTypes.REMOVE_ALL_NOTIFICATIONS: + // + // break; + // } }); + + // Listen for changes in the service + // this.listener = this.service.emitter + // .subscribe((item) => { // Subscribe a stato di redux + // switch (item.command) { + // case 'cleanAll': + // this.notifications = []; + // break; + // + // case 'clean': + // this.cleanSingle(item.id!); + // break; + // + // case 'set': + // if (item.add) { + // this.add(item.notification!); + // } else { + // this.defaultBehavior(item); + // } + // break; + // + // default: + // this.defaultBehavior(item); + // break; + // } + // + // this.cdr.markForCheck(); + // }); } // Default behavior on event @@ -91,7 +115,7 @@ export class NotificationsBoardComponent implements OnInit, OnDestroy { add(item: Notification): void { item.createdOn = new Date(); - const toBlock: boolean = this.preventLastDuplicates || this.preventDuplicates ? this.block(item) : false; + // const toBlock: boolean = this.preventLastDuplicates || this.preventDuplicates ? this.block(item) : false; // Save this as the last created notification this.lastNotificationCreated = item; @@ -100,24 +124,24 @@ export class NotificationsBoardComponent implements OnInit, OnDestroy { item.icon = item.override.icons[item.type]; } - if (!toBlock) { - // Check if the notification should be added at the start or the end of the array - if (this.lastOnBottom) { - if (this.notifications.length >= this.maxStack) { - this.notifications.splice(0, 1); - } - - this.notifications.push(item); - } else { - if (this.notifications.length >= this.maxStack) { - this.notifications.splice(this.notifications.length - 1, 1); - } - - this.notifications.splice(0, 0, item); - } - - this.onCreate.emit(this.buildEmit(item, true)); - } + // if (!toBlock) { + // // Check if the notification should be added at the start or the end of the array + // if (this.lastOnBottom) { + // if (this.notifications.length >= this.maxStack) { + // this.notifications.splice(0, 1); + // } + // + // this.notifications.push(item); + // } else { + // if (this.notifications.length >= this.maxStack) { + // this.notifications.splice(this.notifications.length - 1, 1); + // } + // + // this.notifications.splice(0, 0, item); + // } + // + // this.onCreate.emit(this.buildEmit(item, true)); + // } } // Check if notifications should be prevented @@ -125,31 +149,37 @@ export class NotificationsBoardComponent implements OnInit, OnDestroy { const toCheck = item.html ? this.checkHtml : this.checkStandard; - if (this.preventDuplicates && this.notifications.length > 0) { - for (let i = 0; i < this.notifications.length; i++) { - if (toCheck(this.notifications[i], item)) { - return true; - } + this.notifications.forEach((notification) => { + if (toCheck(notification, item)) { + return true; } - } + }); - if (this.preventLastDuplicates) { - - let comp: Notification; - - if (this.preventLastDuplicates === 'visible' && this.notifications.length > 0) { - if (this.lastOnBottom) { - comp = this.notifications[this.notifications.length - 1]; - } else { - comp = this.notifications[0]; - } - } else if (this.preventLastDuplicates === 'all' && this.lastNotificationCreated) { - comp = this.lastNotificationCreated; - } else { - return false; - } - return toCheck(comp, item); - } + // if (this.notifications.length > 0) { + // for (let i = 0; i < this.notifications.length; i++) { + // if (toCheck(this.notifications[i], item)) { + // return true; + // } + // } + // } + // + // if (this.preventLastDuplicates) { + // + // let comp: Notification; + // + // if (this.preventLastDuplicates === 'visible' && this.notifications.length > 0) { + // if (this.lastOnBottom) { + // comp = this.notifications[this.notifications.length - 1]; + // } else { + // comp = this.notifications[0]; + // } + // } else if (this.preventLastDuplicates === 'all' && this.lastNotificationCreated) { + // comp = this.lastNotificationCreated; + // } else { + // return false; + // } + // return toCheck(comp, item); + // } return false; } @@ -164,11 +194,11 @@ export class NotificationsBoardComponent implements OnInit, OnDestroy { // Attach all the changes received in the options object attachChanges(options: any): void { - Object.keys(options).forEach((a) => { + Object.keys(options).forEach((a) => { if (this.hasOwnProperty(a)) { (this as any)[a] = options[a]; } else if (a === 'icons') { - this.service.icons = options[a]; + // this.service.icons = options[a]; } }); } diff --git a/src/app/shared/notifications/notifications.effects.ts b/src/app/shared/notifications/notifications.effects.ts index b8b9fca4d2..345b1084e0 100644 --- a/src/app/shared/notifications/notifications.effects.ts +++ b/src/app/shared/notifications/notifications.effects.ts @@ -24,7 +24,7 @@ export class NotificationsEffects { * Authenticate user. * @method authenticate */ - @Effect() + /* @Effect() public timer: Observable = this.actions$ .ofType(NotificationsActionTypes.NEW_NOTIFICATION_WITH_TIMER) // .debounceTime((action: any) => action.payload.options.timeOut) diff --git a/src/app/shared/notifications/notifications.service.ts b/src/app/shared/notifications/notifications.service.ts index de40aff7cf..4a82ff9eec 100644 --- a/src/app/shared/notifications/notifications.service.ts +++ b/src/app/shared/notifications/notifications.service.ts @@ -1,65 +1,116 @@ -import {Injectable, EventEmitter} from '@angular/core'; -import {Subject} from 'rxjs/Subject'; -import {NotificationEvent} from './interfaces/notification-event.type'; -import {Notification} from './interfaces/notification.type'; -import {Icons, defaultIcons} from './interfaces/icons'; +import { Injectable, EventEmitter } from '@angular/core'; +import { Subject } from 'rxjs/Subject'; +import { NotificationEvent } from './interfaces/notification-event.type'; +import { INotification, Notification } from './models/notification.model'; +// import {Icons, defaultIcons} from './interfaces/icons'; import { NotificationType } from './models/notification-type'; +import { NotificationOptions } from './models/notification-options.model'; +import * as _ from 'lodash'; +import { Store } from '@ngrx/store'; +import { + NewNotificationAction, NewNotificationWithTimerAction, RemoveAllNotificationsAction, + RemoveNotificationAction +} from './notifications.actions'; @Injectable() export class NotificationsService { - public emitter = new Subject(); - public icons: Icons = defaultIcons; + // public emitter = new Subject(); + // public icons: Icons = defaultIcons; + constructor(private store: Store) { - set(notification: Notification, to: boolean): Notification { - notification.id = notification.override && notification.override.id ? notification.override.id : Math.random().toString(36).substring(3); - notification.click = new EventEmitter<{}>(); - notification.timeoutEnd = new EventEmitter<{}>(); + } - this.emitter.next({command: 'set', notification: notification, add: to}); - return notification; - }; + // private set(notification: Notification, to: boolean): Notification { + // notification.id = notification.override && notification.override.id ? notification.override.id : Math.random().toString(36).substring(3); + // notification.click = new EventEmitter<{}>(); + // notification.timeoutEnd = new EventEmitter<{}>(); + // + // this.emitter.next({command: 'set', notification: notification, add: to}); + // return notification; + // }; - success(title: any = '', content: any = '', override?: any): Notification { - return this.set({title: title, content: content || '', type: NotificationType.Success, override: override}, true); + private add(notification: Notification) { + let notificationAction; + if (notification.options.timeOut > 0) { + notificationAction = new NewNotificationWithTimerAction(notification); + } else { + notificationAction = new NewNotificationAction(notification); } + this.store.dispatch(notificationAction); + } - // error(title: any = '', content: any = '', override?: any): Notification { - // return this.set({title: title, content: content || '', type: NotificationType.Error, override: override}, true); - // } + success(title: any = '', content: any = '', options = new NotificationOptions()): Notification { + const notification = new Notification(_.uniqueId(), NotificationType.Success, title, content, options); + this.add(notification); + return notification; + } - danger(title: any = '', content: any = '', override?: any): Notification { - return this.set({title: title, content: content || '', type: NotificationType.Danger, override: override}, true); - } + // error(title: any = '', content: any = '', override?: any): Notification { + // return this.set({title: title, content: content || '', type: NotificationType.Error, override: override}, true); + // } - info(title: any = '', content: any = '', override?: any): Notification { - return this.set({title: title, content: content || '', type: NotificationType.Info, override: override}, true); - } + danger(title: any = '', content: any = '', options = new NotificationOptions()): Notification { + const notification = new Notification(_.uniqueId(), NotificationType.Danger, title, content, options); + this.add(notification); + return notification; + } - warn(title: any = '', content: any = '', override?: any): Notification { - return this.set({title: title, content: content || '', type: NotificationType.Warning, override: override}, true); - } + info(title: any = '', content: any = '', options = new NotificationOptions()): Notification { + const notification = new Notification(_.uniqueId(), NotificationType.Info, title, content, options); + this.add(notification); + return notification; + } - // bare(title: any = '', content: any = '', override?: any): Notification { - // return this.set({title: title, content: content || '', type: 'bare', icon: 'bare', override: override}, true); - // } + warning(title: any = '', content: any = '', options = new NotificationOptions()): Notification { + const notification = new Notification(_.uniqueId(), NotificationType.Warning, title, content, options); + this.add(notification); + return notification; + } - // With type method - create(title: any = '', content: any = '', type = 'success', override?: any): Notification { - return this.set({title: title, content: content, type: type, icon: (this.icons as any)[type], override: override}, true); - } + // bare(title: any = '', content: any = '', override?: any): Notification { + // return this.set({title: title, content: content || '', type: 'bare', icon: 'bare', override: override}, true); + // } - // HTML Notification method - html(html: any, type = 'success', override?: any, icon = 'bare'): Notification { - return this.set({html: html, type: type, icon: (this.icons as any)[icon], override: override}, true); - } + // With type method + // create(title: any = '', content: any = '', type = 'success'): Notification { + // return this.set({ + // title: title, + // content: content, + // type: type, + // icon: (this.icons as any)[type], + // override: override + // }, true); + // } + + // HTML Notification method + // html(html: any, type = 'success', override?: any, icon = 'bare'): Notification { + // return this.set({html: html, type: type, icon: (this.icons as any)[icon], override: override}, true); + // } + + html(html: any, type = NotificationType.Success, options = new NotificationOptions()): Notification { + const notification = new Notification(_.uniqueId(), type, null, null, options); + notification.html = html; + this.add(notification); + return notification; + } + + // Remove all notifications method + // remove(id?: string): void { + // if (id) { + // this.emitter.next({command: 'clean', id: id}); + // } else { + // this.emitter.next({command: 'cleanAll'}); + // } + // } + remove(notification: INotification) { + const actionRemove = new RemoveNotificationAction(notification.id); + this.store.dispatch(actionRemove); + } + + removeAll() { + const actionRemoveAll = new RemoveAllNotificationsAction(); + this.store.dispatch(actionRemoveAll); + } - // Remove all notifications method - remove(id?: string): void { - if (id) { - this.emitter.next({command: 'clean', id: id}); - } else { - this.emitter.next({command: 'cleanAll'}); - } - } } From 74dc5f806759add097e9c78e3d2539dbf1467c00 Mon Sep 17 00:00:00 2001 From: Andrea Chiapparelli - 4Science Date: Tue, 6 Mar 2018 15:45:37 +0100 Subject: [PATCH 09/42] html hybrid store/static var --- src/app/+home-page/home-page.component.html | 8 +- src/app/+home-page/home-page.component.ts | 63 +++-- src/app/app.component.html | 2 +- src/app/app.component.ts | 32 +-- .../shared/notifications/interfaces/icons.ts | 41 --- .../interfaces/notification-event.type.ts | 8 - .../interfaces/notification.type.ts | 24 -- .../notifications/interfaces/options.type.ts | 19 -- .../models/notification-options.model.ts | 7 + .../notifications/models/notification-type.ts | 9 +- .../models/notification.model.ts | 15 +- .../notification/notification.component.html | 44 ++-- .../notification/notification.component.scss | 149 ++--------- .../notification/notification.component.ts | 92 ++----- .../notifications-board.component.html | 6 +- .../notifications-board.component.ts | 243 ++++++------------ .../notifications/notifications.service.ts | 74 ++---- 17 files changed, 229 insertions(+), 607 deletions(-) delete mode 100644 src/app/shared/notifications/interfaces/icons.ts delete mode 100644 src/app/shared/notifications/interfaces/notification-event.type.ts delete mode 100644 src/app/shared/notifications/interfaces/notification.type.ts delete mode 100644 src/app/shared/notifications/interfaces/options.type.ts diff --git a/src/app/+home-page/home-page.component.html b/src/app/+home-page/home-page.component.html index d015e53850..637b975ad3 100644 --- a/src/app/+home-page/home-page.component.html +++ b/src/app/+home-page/home-page.component.html @@ -5,12 +5,8 @@ - - - - - - + + diff --git a/src/app/+home-page/home-page.component.ts b/src/app/+home-page/home-page.component.ts index 1acd4951fa..936d7ccecc 100644 --- a/src/app/+home-page/home-page.component.ts +++ b/src/app/+home-page/home-page.component.ts @@ -1,7 +1,9 @@ -import { Component, OnInit, TemplateRef, ViewChild } from '@angular/core'; +import { Component, TemplateRef, ViewChild } from '@angular/core'; import { NotificationsService } from '../shared/notifications/notifications.service'; -import { Options } from '../shared/notifications/interfaces/options.type'; -import { NotificationOptions } from '../shared/notifications/models/notification-options.model'; +import { + INotificationBoardOptions, + NotificationOptions +} from '../shared/notifications/models/notification-options.model'; @Component({ selector: 'ds-home-page', @@ -9,15 +11,6 @@ import { NotificationOptions } from '../shared/notifications/models/notification templateUrl: './home-page.component.html' }) export class HomePageComponent { - public notificationOptions: Options = { - position: ['top', 'right'], - timeOut: 0, - animate: 'fromLeft' - // lastOnBottom: true, - // clickIconToClose: false, - // showProgressBar: true, - }; - @ViewChild('example') example: TemplateRef; constructor(private notificationsService: NotificationsService) { @@ -25,24 +18,40 @@ export class HomePageComponent { createNotification() { const n1 = this.notificationsService.success('Welcome in DSpace', 'Good choice!', - new NotificationOptions(2000, false, 'fromLeft')); - const n2 = this.notificationsService.info('Info in DSpace', 'For your info...!'); - const n3 = this.notificationsService.warning('Warning in DSpace', 'This is a fake alert!'); - const n4 = this.notificationsService.danger(this.example); - console.log('Notifications pushed'); - console.log(n1); - console.log(n2); - console.log(n3); - console.log(n4); + new NotificationOptions(10000, false, 'fromLeft', ['bottom', 'left'])); + // const n2 = this.notificationsService.info('Info in DSpace', 'For your info...!'); + // const n3 = this.notificationsService.warning('Warning in DSpace', 'This is a fake alert!'); + // // const n4 = this.notificationsService.danger(this.example); + // console.log('Notifications pushed'); + // console.log(n1); + // console.log(n2); + // console.log(n3); + // // console.log(n4); } - notificationCreated(event) { - console.log('Notification created'); - console.log(event); + createNotification1() { + const n1 = this.notificationsService.warning('Welcome in DSpace', 'Good choice!', + new NotificationOptions(10000, false, 'fromLeft', ['bottom', 'left'])); + // const n2 = this.notificationsService.info('Info in DSpace', 'For your info...!'); + // const n3 = this.notificationsService.warning('Warning in DSpace', 'This is a fake alert!'); + // // const n4 = this.notificationsService.danger(this.example); + // console.log('Notifications pushed'); + // console.log(n1); + // console.log(n2); + // console.log(n3); + // // console.log(n4); } - notificationDestroyed() { - console.log('Notification destroyed'); - console.log(event); + createNotification2() { + // const n1 = this.notificationsService.error('Welcome in DSpace', 'Good choice!', + // new NotificationOptions(100000, false, 'fromLeft', ['bottom', 'left'])); + // const n2 = this.notificationsService.info('Info in DSpace', 'For your info...!'); + // const n3 = this.notificationsService.warning('Warning in DSpace', 'This is a fake alert!'); + const n4 = this.notificationsService.html(this.example); + // console.log('Notifications pushed'); + // console.log(n1); + // console.log(n2); + // console.log(n3); + // // console.log(n4); } } diff --git a/src/app/app.component.html b/src/app/app.component.html index 6fb3c3a31e..3e71fafc5c 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -3,7 +3,7 @@ diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 4fb14f4d75..f5ace25acf 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,11 +1,4 @@ -import { - ChangeDetectionStrategy, - Component, - HostListener, - Inject, - OnInit, - ViewEncapsulation -} from '@angular/core'; +import { ChangeDetectionStrategy, Component, HostListener, Inject, OnInit, ViewEncapsulation } from '@angular/core'; import { Store } from '@ngrx/store'; @@ -17,7 +10,7 @@ import { MetadataService } from './core/metadata/metadata.service'; import { HostWindowResizeAction } from './shared/host-window.actions'; import { HostWindowState } from './shared/host-window.reducer'; import { NativeWindowRef, NativeWindowService } from './shared/window.service'; -import { Options } from './shared/notifications/interfaces/options.type'; +import { INotificationBoardOptions } from './shared/notifications/models/notification-options.model'; @Component({ selector: 'ds-app', @@ -28,13 +21,10 @@ import { Options } from './shared/notifications/interfaces/options.type'; }) export class AppComponent implements OnInit { - public notificationOptions: Options = { + public boardOptions: INotificationBoardOptions = { position: ['top', 'right'], - timeOut: 0, - animate: 'fromLeft' - // lastOnBottom: true, - // clickIconToClose: false, - // showProgressBar: true, + maxStack: 5, + rtl: false }; notificationCreated(event) { @@ -47,13 +37,11 @@ export class AppComponent implements OnInit { console.log(event); } - constructor( - @Inject(GLOBAL_CONFIG) public config: GlobalConfig, - @Inject(NativeWindowService) private _window: NativeWindowRef, - private translate: TranslateService, - private store: Store, - private metadata: MetadataService - ) { + constructor(@Inject(GLOBAL_CONFIG) public config: GlobalConfig, + @Inject(NativeWindowService) private _window: NativeWindowRef, + private translate: TranslateService, + private store: Store, + private metadata: MetadataService) { // this language will be used as a fallback when a translation isn't found in the current language translate.setDefaultLang('en'); // the lang to use, if the lang isn't available, it will use the current loader to get them diff --git a/src/app/shared/notifications/interfaces/icons.ts b/src/app/shared/notifications/interfaces/icons.ts deleted file mode 100644 index bf3066c2ac..0000000000 --- a/src/app/shared/notifications/interfaces/icons.ts +++ /dev/null @@ -1,41 +0,0 @@ -export interface Icons { - alert: string; - error: string; - info: string; - warn: string; - success: string; -} - -export const defaultIcons: Icons = { - alert: ` - - - - - `, - error: ` - - - - - `, - info: ` - - - - - `, - success: ` - - - - - `, - warn: ` - - - - - - ` -}; diff --git a/src/app/shared/notifications/interfaces/notification-event.type.ts b/src/app/shared/notifications/interfaces/notification-event.type.ts deleted file mode 100644 index 978cf3dc69..0000000000 --- a/src/app/shared/notifications/interfaces/notification-event.type.ts +++ /dev/null @@ -1,8 +0,0 @@ -import {Notification} from './notification.type'; - -export interface NotificationEvent { - add?: boolean; - command: string; - id?: string; - notification?: Notification; -} diff --git a/src/app/shared/notifications/interfaces/notification.type.ts b/src/app/shared/notifications/interfaces/notification.type.ts deleted file mode 100644 index eb69260cbf..0000000000 --- a/src/app/shared/notifications/interfaces/notification.type.ts +++ /dev/null @@ -1,24 +0,0 @@ -import {EventEmitter} from '@angular/core'; - -export interface Notification { - id?: string - type: string - icon?: string - title?: any - content?: any - override?: any - html?: any - state?: string - createdOn?: Date - destroyedOn?: Date - animate?: string - timeOut?: number - maxLength?: number - pauseOnHover?: boolean - clickToClose?: boolean - clickIconToClose?: boolean - theClass?: string - click?: EventEmitter<{}>; - clickIcon?: EventEmitter<{}>; - timeoutEnd?: EventEmitter<{}>; -} diff --git a/src/app/shared/notifications/interfaces/options.type.ts b/src/app/shared/notifications/interfaces/options.type.ts deleted file mode 100644 index cfd45529cd..0000000000 --- a/src/app/shared/notifications/interfaces/options.type.ts +++ /dev/null @@ -1,19 +0,0 @@ -import {Icons} from './icons'; - -export interface Options { - timeOut?: number; - // showProgressBar?: boolean; - // pauseOnHover?: boolean; - // lastOnBottom?: boolean; - // clickToClose?: boolean; - // clickIconToClose?: boolean; - maxLength?: number; - maxStack?: number; - // preventDuplicates?: boolean; - // preventLastDuplicates?: boolean | string; - theClass?: string; - rtl?: boolean; - animate?: 'fade' | 'fromTop' | 'fromRight' | 'fromBottom' | 'fromLeft' | 'rotate' | 'scale'; - icons?: Icons; - position?: ['top' | 'bottom' | 'middle', 'right' | 'left' | 'center']; -} diff --git a/src/app/shared/notifications/models/notification-options.model.ts b/src/app/shared/notifications/models/notification-options.model.ts index b651a0b3d1..b8357f5269 100644 --- a/src/app/shared/notifications/models/notification-options.model.ts +++ b/src/app/shared/notifications/models/notification-options.model.ts @@ -26,3 +26,10 @@ export class NotificationOptions implements INotificationOptions { this.rtl = rtl; } } + +export interface INotificationBoardOptions { + rtl: boolean; + position: ['top' | 'bottom' | 'middle', 'right' | 'left' | 'center']; + maxStack: number; + maxLength?: number; +} diff --git a/src/app/shared/notifications/models/notification-type.ts b/src/app/shared/notifications/models/notification-type.ts index b21696ab1f..8ef5d790b5 100644 --- a/src/app/shared/notifications/models/notification-type.ts +++ b/src/app/shared/notifications/models/notification-type.ts @@ -1,8 +1,7 @@ export enum NotificationType { - Success = 'success', - Error = 'error', - Danger = 'danger', - Info = 'info', - Warning = 'warning', + Success = 'alert-success', + Error = 'alert-danger', + Info = 'alert-info', + Warning = 'alert-warning', // Bare = 'bare' } diff --git a/src/app/shared/notifications/models/notification.model.ts b/src/app/shared/notifications/models/notification.model.ts index 91f2f9bbe9..e5da5d1bff 100644 --- a/src/app/shared/notifications/models/notification.model.ts +++ b/src/app/shared/notifications/models/notification.model.ts @@ -1,6 +1,10 @@ import { INotificationOptions, NotificationOptions } from './notification-options.model'; import { NotificationType } from './notification-type'; import { isEmpty } from '../../empty.util'; +import { ElementRef, TemplateRef } from '@angular/core'; +import { Deserialize, Serialize, serialize } from 'cerialize'; +import { deserialize } from 'cerialize'; +import { NotificationsService } from '../notifications.service'; export interface INotification { id: string @@ -17,7 +21,6 @@ export class Notification implements INotification { public title: any; public content: any; public options: INotificationOptions; - public html?: any; constructor(id: string, type: NotificationType, @@ -31,4 +34,14 @@ export class Notification implements INotification { this.content = content; this.options = isEmpty(options) ? new NotificationOptions() : options; } + + get html() { + if (this.title === '' && this.content === '') { + return NotificationsService.htmlArray.get(this.id); + } else { + return null; + } + } + + } diff --git a/src/app/shared/notifications/notification/notification.component.html b/src/app/shared/notifications/notification/notification.component.html index 977a1298b1..a50af4b51d 100644 --- a/src/app/shared/notifications/notification/notification.component.html +++ b/src/app/shared/notifications/notification/notification.component.html @@ -1,32 +1,18 @@ - -
+ +
-
- - -