Skip to content

Commit 36aa4e3

Browse files
committed
feat(library): Added minimal feature implementation
- Added Notifier Service, Queue Service - Added models for notification, queue action, and global configuration - Added notification and container components - Added functionality for creating (showing), removing (hiding), and shifting notifications - Added base styles - Created a simple demo app - Updated ignore files, linter settings, and dependencies
1 parent e42dda9 commit 36aa4e3

22 files changed

+771
-18
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
*.log
2121

2222
# Development
23+
/.vscode
2324
*.todo
2425
*.old
2526
*.temp

.npmignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
*.log
3232

3333
# Development
34+
/.vscode
3435
*.todo
3536
*.old
3637
*.temp

demo/app.component.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,29 @@
33
*/
44
import { Component } from '@angular/core';
55

6+
import { NotifierService } from './../index';
7+
68
/**
79
* App component
810
*/
911
@Component( {
1012
selector: 'app',
11-
template: '<h1>Hello World</h1>'
13+
template: '<h1>Hello World</h1><button (click)="test()">Hit me</button><x-notifier-container></x-notifier-container>'
1214
} )
13-
export class AppComponent { }
15+
export class AppComponent {
16+
17+
private notifier: NotifierService;
18+
19+
private counter: number;
20+
21+
public constructor( notifier: NotifierService ) {
22+
this.notifier = notifier;
23+
this.counter = 1;
24+
}
25+
26+
public test(): void {
27+
this.notifier.notify( 'test', `Notification #${ this.counter }` );
28+
this.counter++;
29+
}
30+
31+
}

demo/app.module.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
*/
44
import { NgModule } from '@angular/core';
55
import { BrowserModule } from '@angular/platform-browser';
6+
import { NotifierModule } from '../index';
67

78
/**
89
* Internal imports
@@ -13,8 +14,15 @@ import { AppComponent } from './app.component';
1314
* App module
1415
*/
1516
@NgModule( {
16-
imports: [ BrowserModule ],
17-
declarations: [ AppComponent ],
18-
bootstrap: [ AppComponent ]
17+
bootstrap: [
18+
AppComponent
19+
],
20+
declarations: [
21+
AppComponent
22+
],
23+
imports: [
24+
BrowserModule,
25+
NotifierModule.forRoot( {} )
26+
]
1927
} )
2028
export class AppModule {}

demo/index.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@
2222
</script>
2323

2424
<!-- Load styles -->
25+
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:400,700">
2526
<link href="./style.css" rel="stylesheet">
27+
<link href="./../style.css" rel="stylesheet">
2628

2729
</head>
2830
<body>

demo/style.css

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,24 @@
1+
/**
2+
* General
3+
*/
4+
5+
html {
6+
width: 100%;
7+
height: 100%;
8+
}
9+
110
body {
2-
background-color: #EEE;
11+
width: 100%;
12+
height: 100%;
13+
margin: 0;
14+
box-sizing: border-box;
15+
font-family: "Roboto", "Tahoma", "Trebuchet MS", "Arial", "Helvetica", sans-serif;
16+
font-size: 14px;
17+
}
18+
19+
* {
20+
box-sizing: inherit;
321
}
22+
23+
24+

index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
// TODO
1+
export { NotifierModule } from './src/notifier.module';
2+
export { NotifierService } from './src/services/notifier.service';

package.json

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -38,17 +38,18 @@
3838
"typings": "typings"
3939
},
4040
"peerDependencies": {
41-
"@angular/core": "2.0.x",
42-
"@angular/common": "2.0.x"
41+
"@angular/core": "2.x",
42+
"@angular/common": "2.x",
43+
"rxjs": "5.0.0-beta.12"
4344
},
4445
"devDependencies": {
45-
"@angular/core": "2.0.x",
46-
"@angular/common": "2.0.x",
47-
"@angular/compiler": "2.0.x",
48-
"@angular/platform-browser": "2.0.x",
49-
"@angular/platform-browser-dynamic": "2.0.x",
46+
"@angular/core": "2.x",
47+
"@angular/common": "2.x",
48+
"@angular/compiler": "2.x",
49+
"@angular/platform-browser": "2.x",
50+
"@angular/platform-browser-dynamic": "2.x",
5051
"browser-sync": "2.17.x",
51-
"codelyzer": "1.0.0-beta.0",
52+
"codelyzer": "1.0.0-beta.2",
5253
"conventional-github-releaser": "1.1.x",
5354
"conventional-recommended-bump": "0.3.x",
5455
"core-js": "2.4.x",
@@ -59,7 +60,7 @@
5960
"gulp-conventional-changelog": "1.1.x",
6061
"gulp-git": "1.11.x",
6162
"gulp-insert": "0.5.x",
62-
"gulp-sourcemaps": "1.6.x",
63+
"gulp-sourcemaps": "2.1.x",
6364
"gulp-sass": "2.3.x",
6465
"gulp-sass-lint": "1.2.x",
6566
"gulp-tslint": "6.1.x",
Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
import { Component, OnInit, OnDestroy, Optional } from '@angular/core';
2+
import { Subscription } from 'rxjs/Subscription';
3+
4+
import { NotifierQueueService } from './../services/notifier-queue.service';
5+
import { NotifierNotificationComponent } from './notifier-notification.component';
6+
import { NotifierConfigGlobal } from './../models/notifier-config-global.model';
7+
import { NotifierNotification } from './../models/notifier-notification.model';
8+
import { NotifierAction, NotifierActionType } from './../models/notifier-action.model';
9+
10+
/**
11+
* Notifier container component
12+
*/
13+
@Component( {
14+
host: {
15+
class: 'x-notifier__container'
16+
},
17+
selector: 'x-notifier-container',
18+
template: `
19+
<ul>
20+
<li class="x-notifier__container-list" *ngFor="let notification of notifications">
21+
<x-notifier-notification
22+
[notification]="notification"
23+
(init)="onComponentInit( $event )"
24+
(dismiss)="onClickDismiss( $event )">
25+
</x-notifier-notification>
26+
</li>
27+
</ul>`
28+
} )
29+
export class NotifierContainerComponent implements OnInit, OnDestroy {
30+
31+
/**
32+
* Global configuration
33+
*/
34+
private config: NotifierConfigGlobal;
35+
36+
/**
37+
* Notifier queue service
38+
*/
39+
private queueService: NotifierQueueService;
40+
41+
/**
42+
* List of currently visible notifications
43+
*/
44+
private notifications: Array<NotifierNotification>;
45+
46+
/**
47+
* Queue service observable subscription (used for cleanup later on)
48+
*/
49+
private queueServiceSubscription: Subscription;
50+
51+
/**
52+
* Promise resolve function reference, temporarily used while the notification child component gets created
53+
*/
54+
private tempPromiseResolver: ( value?: null ) => {};
55+
56+
/**
57+
* Constructor
58+
*/
59+
public constructor( notifierQueueService: NotifierQueueService, @Optional() config: NotifierConfigGlobal ) {
60+
this.config = config === null ? new NotifierConfigGlobal() : config;
61+
this.queueService = notifierQueueService;
62+
this.notifications = [];
63+
}
64+
65+
/**
66+
* Component initialization lifecycle hook
67+
* Handles the incoming stream of actions, and also communicates back when an action is finished
68+
*/
69+
public ngOnInit(): void {
70+
this.queueServiceSubscription = this.queueService.actionStream.subscribe( ( action: NotifierAction ) => {
71+
this.handleNewAction( action ).then( () => {
72+
this.queueService.continue();
73+
} );
74+
} );
75+
}
76+
77+
/**
78+
* Component destroy lifecycle hook
79+
* Cleans up observable subsciption
80+
*/
81+
public ngOnDestroy(): void {
82+
this.queueServiceSubscription.unsubscribe();
83+
}
84+
85+
/**
86+
* Handle click on a notification dismiss button
87+
* @param {NotifierNotification} notification
88+
*/
89+
public onClickDismiss( notification: NotifierNotification ): void {
90+
this.queueService.push( {
91+
payload: notification,
92+
type: NotifierActionType.CLEAR
93+
} );
94+
}
95+
96+
/**
97+
* Redirect ... event handler
98+
*/
99+
public onComponentInit( componentRef: NotifierNotificationComponent ): void {
100+
let currentNotification: NotifierNotification = this.notifications[ this.notifications.length - 1 ];
101+
currentNotification.componentRef = componentRef; // Save component Ref
102+
this.showNotification( currentNotification );
103+
};
104+
105+
/**
106+
* Maps action types to component methods, and then runs the correct one
107+
*/
108+
private handleNewAction( action: NotifierAction ): Promise<any> {
109+
switch ( action.type ) {
110+
case NotifierActionType.SHOW:
111+
return this.addNotification( action );
112+
case NotifierActionType.CLEAR:
113+
return this.removeNotification( action );
114+
}
115+
}
116+
117+
/**
118+
* Add a new notification to the list of notifications
119+
* @param {NotifierAction} action Action object
120+
* @returns {Promise<null>} Empty Promise, resolved when finished
121+
*/
122+
private addNotification( action: NotifierAction ): Promise<null> {
123+
return new Promise<null>( ( resolve: ( value?: null ) => {}, reject: ( value?: null ) => {} ) => {
124+
let notification: NotifierNotification = new NotifierNotification( action.payload );
125+
this.tempPromiseResolver = resolve; // Gets resolved when the notification is finally visible
126+
this.notifications.push( notification );
127+
} );
128+
}
129+
130+
/**
131+
* Remove notification
132+
*/
133+
private removeNotification( action: NotifierAction ): Promise<null> {
134+
return new Promise<null>( ( resolve: ( value?: null ) => {}, reject: ( value?: null ) => {} ) => {
135+
136+
// 1. Decision: Only one notification in the list?
137+
if ( this.notifications.length === 1 ) {
138+
this.hideNotification( action.payload ).then( resolve ); // DONE
139+
} else {
140+
this.hideNotification( action.payload );
141+
const notificationIndex: number = this.notifications.findIndex( ( notification: NotifierNotification ) => {
142+
return notification.componentRef === action.payload.componentRef;
143+
} );
144+
const otherNotifications: Array<NotifierNotification> = this.notifications.slice( 0, notificationIndex );
145+
this.shiftNotifications( otherNotifications, action.payload.componentRef.currentElementHeight, false ).then( resolve );
146+
}
147+
148+
} );
149+
}
150+
151+
/**
152+
* Show notification
153+
*/
154+
private showNotification( notification: NotifierNotification ): void {
155+
156+
// 1. Decision: First notification in the list?
157+
if ( this.notifications.length === 1 ) {
158+
notification.componentRef.show().then( this.tempPromiseResolver );
159+
} else {
160+
161+
// 2. Decision: Stacking enabled? (same as stacking value below 2)
162+
if ( this.config.behaviour.stacking === false || this.config.behaviour.stacking < 2 ) {
163+
this.hideNotification( this.notifications[ 0 ] ).then( () => {
164+
notification.componentRef.show().then( this.tempPromiseResolver );
165+
} );
166+
} else {
167+
168+
// 3. Decision: Are there now too many notifications?
169+
if ( this.notifications.length > this.config.behaviour.stacking ) {
170+
171+
this.hideNotification( this.notifications[ 0 ] ); // Hide oldest notification
172+
const otherNotifications: Array<NotifierNotification> = this.notifications.slice( 1, this.notifications.length - 1 );
173+
this.shiftNotifications( otherNotifications, notification.componentRef.currentElementHeight, true );
174+
notification.componentRef.show().then( this.tempPromiseResolver ); // DONE
175+
176+
} else {
177+
178+
const otherNotifications: Array<NotifierNotification> = this.notifications.slice( 0, this.notifications.length - 1 );
179+
this.shiftNotifications( otherNotifications, notification.componentRef.currentElementHeight, true );
180+
notification.componentRef.show().then( this.tempPromiseResolver ); // DONE
181+
182+
}
183+
184+
}
185+
186+
}
187+
188+
}
189+
190+
/**
191+
* Hide notification
192+
*/
193+
private hideNotification( notification: NotifierNotification ): Promise<null> {
194+
return new Promise<null>( ( resolve: ( value?: null ) => {}, reject: ( value?: null ) => {} ) => {
195+
notification.componentRef.hide().then( () => {
196+
this.notifications = this.notifications.filter( ( currentNotification: NotifierNotification ) => {
197+
return currentNotification.componentRef !== notification.componentRef;
198+
} );
199+
resolve(); // DONE
200+
} );
201+
} );
202+
}
203+
204+
/**
205+
* Shift notifications
206+
*/
207+
private shiftNotifications( notifications: Array<NotifierNotification>, distance: number, shiftToMakePlace: boolean ): Promise<null> {
208+
let shiftPromises: Array<Promise<null>> = [];
209+
for ( let notification of notifications ) {
210+
shiftPromises.push( notification.componentRef.shift( distance, shiftToMakePlace ) );
211+
}
212+
return Promise.all( shiftPromises ); // DONE
213+
}
214+
215+
}

0 commit comments

Comments
 (0)