(Mail-ngrx) Ngrx version of Mail App added.

This commit is contained in:
mustafahlvc 2017-12-12 22:31:50 +03:00
parent 242feaa169
commit abede386c8
60 changed files with 3202 additions and 0 deletions

33
package-lock.json generated
View File

@ -245,6 +245,26 @@
"integrity": "sha1-NOZY7T2jfyOwogDi2lqJvpK7IJ8=",
"dev": true
},
"@ngrx/effects": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/@ngrx/effects/-/effects-4.1.1.tgz",
"integrity": "sha1-y3WLhSeWSyWOpBlR9ZqhROPvn64="
},
"@ngrx/router-store": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/@ngrx/router-store/-/router-store-4.1.1.tgz",
"integrity": "sha1-F/rHwPX/3e+LdemnTtLLCQdPO8o="
},
"@ngrx/store": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/@ngrx/store/-/store-4.1.1.tgz",
"integrity": "sha1-aA403yd16IUnVO13f/rJW9gbfeA="
},
"@ngrx/store-devtools": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/@ngrx/store-devtools/-/store-devtools-4.1.1.tgz",
"integrity": "sha1-IHRcOcdWD9wF+k8iY4RCp+x91nY="
},
"@ngtools/json-schema": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@ngtools/json-schema/-/json-schema-1.1.0.tgz",
@ -2352,6 +2372,11 @@
"integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=",
"dev": true
},
"deep-freeze-strict": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/deep-freeze-strict/-/deep-freeze-strict-1.1.1.tgz",
"integrity": "sha1-d9BYPKJKab5LvZrC+uQV1VUj5bA="
},
"default-require-extensions": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-1.0.0.tgz",
@ -6470,6 +6495,14 @@
"integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=",
"dev": true
},
"ngrx-store-freeze": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/ngrx-store-freeze/-/ngrx-store-freeze-0.2.0.tgz",
"integrity": "sha1-dMIxlHu+GTivci9qcmJNxpeI058=",
"requires": {
"deep-freeze-strict": "1.1.1"
}
},
"ngx-color-picker": {
"version": "5.0.5",
"resolved": "https://registry.npmjs.org/ngx-color-picker/-/ngx-color-picker-5.0.5.tgz",

View File

@ -43,6 +43,11 @@
"highlight.js": "9.12.0",
"intl": "1.2.5",
"moment": "2.19.3",
"@ngrx/effects": "4.1.1",
"@ngrx/router-store": "4.1.1",
"@ngrx/store": "4.1.1",
"@ngrx/store-devtools": "4.1.1",
"ngrx-store-freeze": "0.2.0",
"ngx-color-picker": "5.0.5",
"ngx-cookie-service": "1.0.9",
"perfect-scrollbar": "1.3.0",

View File

@ -21,12 +21,17 @@ import { ServicesModule } from './main/content/services/services.module';
import { FuseAngularMaterialModule } from './main/content/components/angular-material/angular-material.module';
import { MarkdownModule } from 'angular2-markdown';
import { TranslateModule } from '@ngx-translate/core';
import { AppStoreModule } from './store/store.module';
const appRoutes: Routes = [
{
path : 'apps/mail',
loadChildren: './main/content/apps/mail/mail.module#FuseMailModule'
},
{
path : 'apps/mail-ngrx',
loadChildren: './main/content/apps/mail-ngrx/mail.module#FuseMailNgrxModule'
},
{
path : 'apps/chat',
loadChildren: './main/content/apps/chat/chat.module#FuseChatModule'
@ -77,6 +82,7 @@ const appRoutes: Routes = [
delay : 0,
passThruUnknownUrl: true
}),
AppStoreModule,
FuseMainModule,
ProjectModule,
PagesModule,

View File

@ -0,0 +1,111 @@
<div class="dialog-content-wrapper">
<mat-toolbar matDialogTitle class="mat-accent m-0">
<div fxFlex fxLayout="row" fxLayoutAlign="space-between center">
<span class="title dialog-title">New Message</span>
<button mat-button class="mat-icon-button"
(click)="dialogRef.close()"
aria-label="Close dialog">
<mat-icon>close</mat-icon>
</button>
</div>
</mat-toolbar>
<div mat-dialog-content class="p-24 m-0" fusePerfectScrollbar>
<form name="composeForm" [formGroup]="composeForm" class="compose-form" fxLayout="column" fxFlex>
<mat-form-field>
<input matInput name="from"
placeholder="From"
formControlName="from"
type="email">
</mat-form-field>
<mat-form-field>
<input matInput name="to"
placeholder="To"
formControlName="to"
type="email" required>
</mat-form-field>
<mat-form-field>
<input matInput
name="cc"
placeholder="Cc"
formControlName="cc"
type="email">
</mat-form-field>
<mat-form-field>
<input matInput
name="bcc"
placeholder="Bcc"
formControlName="bcc"
type="email">
</mat-form-field>
<mat-form-field>
<input matInput name="subject"
placeholder="Subject"
formControlName="subject">
</mat-form-field>
<mat-form-field>
<textarea matInput name="message"
placeholder="Message"
formControlName="message"
rows="6">
</textarea>
</mat-form-field>
<div class="attachment-list">
<div class="attachment" fxLayout="row" fxLayoutAlign="space-between center">
<div>
<span class="filename">attachment-2.doc</span>
<span class="size">(12 Kb)</span>
</div>
<button mat-icon-button aria-label="Delete attachment">
<mat-icon class="s-16">close</mat-icon>
</button>
</div>
<div class="attachment" fxLayout="row" fxLayoutAlign="space-between center">
<div>
<span class="filename">attachment-1.jpg</span>
<span class="size">(350 Kb)</span>
</div>
<button mat-icon-button aria-label="Delete attachment">
<mat-icon class="s-16">close</mat-icon>
</button>
</div>
</div>
</form>
</div>
<div mat-dialog-actions class="m-0 p-16" fxLayout="row" fxLayoutAlign="space-between center">
<div>
<button mat-raised-button
(click)="dialogRef.close(['send',composeForm])"
class="save-button mat-accent"
[disabled]="composeForm.invalid"
aria-label="SAVE">
SEND
</button>
<button mat-icon-button matTooltip="Attach a file">
<mat-icon>attach_file</mat-icon>
</button>
</div>
<button mat-button
class="mat-icon-button"
(click)="dialogRef.close(['delete',composeForm])"
aria-label="Delete"
matTooltip="Delete">
<mat-icon>delete</mat-icon>
</button>
</div>
</div>

View File

@ -0,0 +1,40 @@
.mail-compose-dialog {
.mat-dialog-container {
padding: 0;
width: 720px;
.compose-form {
.mat-form-field {
width: 100%;
}
.attachment-list {
font-size: 13px;
padding-top: 16px;
.attachment {
background-color: rgba(0, 0, 0, 0.08);
border: 1px solid rgba(0, 0, 0, 0.16);
padding-left: 16px;
margin-top: 8px;
border-radius: 2px;
.filename {
font-weight: 500;
}
&:last-child {
margin-bottom: 0;
}
}
}
}
}
.dialog-content-wrapper {
max-height: 85vh;
display: flex;
flex-direction: column;
}
}

View File

@ -0,0 +1,44 @@
import { Component, Inject, OnInit, ViewEncapsulation } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material';
import { FormBuilder, FormGroup } from '@angular/forms';
@Component({
selector : 'fuse-mail-compose',
templateUrl : './compose.component.html',
styleUrls : ['./compose.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class FuseMailNgrxComposeDialogComponent implements OnInit
{
composeForm: FormGroup;
constructor(
public dialogRef: MatDialogRef<FuseMailNgrxComposeDialogComponent>,
@Inject(MAT_DIALOG_DATA) private data: any,
private formBuilder: FormBuilder
)
{
this.composeForm = this.createComposeForm();
}
ngOnInit()
{
}
createComposeForm()
{
return this.formBuilder.group({
from : {
value : ['johndoe@creapond.com'],
disabled: [true]
},
to : [''],
cc : [''],
bcc : [''],
subject: [''],
message: ['']
});
}
}

View File

@ -0,0 +1,14 @@
export const locale = {
lang: 'en',
data: {
'MAIL': {
'COMPOSE' : 'COMPOSE',
'FOLDERS' : 'FOLDERS',
'FILTERS' : 'FILTERS',
'LABELS' : 'LABELS',
'NO_MESSAGES' : 'There are no messages!',
'SELECT_A_MESSAGE_TO_READ': 'Select a message to read',
'SEARCH_PLACEHOLDER': 'Search for an e-mail or contact'
}
}
};

View File

@ -0,0 +1,14 @@
export const locale = {
lang: 'tr',
data: {
'MAIL': {
'COMPOSE' : 'YENİ E-POSTA',
'FOLDERS' : 'KLASÖRLER',
'FILTERS' : 'FİLTRELER',
'LABELS' : 'ETİKETLER',
'NO_MESSAGES' : 'Mesajiniz bulunmamakta!',
'SELECT_A_MESSAGE_TO_READ': 'Okumak için bir mesaj seçin',
'SEARCH_PLACEHOLDER' : 'E-mail yada bir kişi arayın'
}
}
};

View File

@ -0,0 +1,140 @@
<div *ngIf="!mail" fxLayout="column" fxLayoutAlign="center center" fxFlex>
<mat-icon class="s-128 mb-16 select-message-icon">
email
</mat-icon>
<span class="select-message-text hint-text">
<span>{{ 'MAIL.SELECT_A_MESSAGE_TO_READ' | translate }}</span>
</span>
</div>
<div *ngIf="mail">
<div class="mail-header" fxLayout="row" fxLayoutAlign="space-between center">
<div>
<div class="subject" flex>{{mail.subject}}</div>
<div class="labels" fxLayout="row" fxLayoutWrap>
<div class="label" *ngFor="let labelId of mail.labels"
fxLayout="row" fxLayoutAlign="start center">
<div class="label-color" [ngStyle]="{'background-color': (labels$ | async) | getById:labelId:'color'}"></div>
<div class="label-title">{{(labels$ | async) | getById:labelId:'title'}}</div>
</div>
</div>
</div>
<div class="actions" fxLayout="row" fxLayoutAlign="start center">
<button mat-button class="mat-icon-button" (click)="toggleStar($event)" aria-label="Toggle star">
<mat-icon *ngIf="mail.starred">star</mat-icon>
<mat-icon *ngIf="!mail.starred">star_outline</mat-icon>
</button>
<button mat-button class="mat-icon-button" (click)="toggleImportant($event)" aria-label="Toggle important">
<mat-icon *ngIf="mail.important">label</mat-icon>
<mat-icon *ngIf="!mail.important">label_outline</mat-icon>
</button>
</div>
</div>
<div class="mail-content">
<div class="info" fxLayout="row" fxLayoutAlign="space-between start">
<div fxFlex fxLayout="column" fxLayoutAlign="start start">
<div fxLayout="row" fxLayoutAlign="start start">
<div>
<img *ngIf="mail.from.avatar" alt="{{mail.from.name}}"
src="{{mail.from.avatar}}" class="avatar"/>
<div *ngIf="!mail.from.avatar" class="avatar" ms-random-class="vm.colors">
{{mail.from.name[0]}}
</div>
</div>
<div fxLayout="column" fxLayoutAlign="start start">
<div class="name">
{{mail.from.name}}
</div>
<div class="to" fxLayout="row" fxLayoutAlign="start center">
<div class="to-text">to</div>
<div>{{mail.to[0].name}}</div>
</div>
</div>
</div>
<a class="toggle-details" (click)="showDetails = !showDetails">
<span *ngIf="!showDetails">Show Details</span>
<span *ngIf="showDetails">Hide Details</span>
</a>
<div *ngIf="showDetails" class="details" fxLayout="row" fxLayoutAlign="start start">
<div fxLayout="column">
<span class="title">From:</span>
<span class="title">To:</span>
<span class="title">Date:</span>
</div>
<div fxLayout="column">
<span class="detail">{{mail.from.email}}</span>
<span class="detail">{{mail.to[0].email}}</span>
<span class="detail">{{mail.time}}</span>
</div>
</div>
</div>
<button mat-button [matMenuTriggerFor]="moreMenu" aria-label="More" class="mat-icon-button"
(click)="$event.stopPropagation()">
<mat-icon>more_vert</mat-icon>
</button>
<mat-menu #moreMenu="matMenu">
<button mat-menu-item aria-label="Reply">
<mat-icon>reply</mat-icon>
<span>Reply</span>
</button>
<button mat-menu-item aria-label="Forward">
<mat-icon>forward</mat-icon>
<span>Forward</span>
</button>
<button mat-menu-item aria-label="Print">
<mat-icon>print</mat-icon>
<span>Print</span>
</button>
</mat-menu>
</div>
<div [innerHTML]="mail.message"></div>
</div>
<div *ngIf="mail.attachments" class="mail-attachments">
<div class="title">
<span>Attachments</span>
({{mail.attachments.length}})
</div>
<div class="attachment-list" fxLayout="row" fxLayoutWrap>
<div class="attachment" fxLayout="column"
*ngFor="let attachment of mail.attachments">
<img class="preview" src="{{attachment.preview}}">
<div fxLayout="column">
<a href="#" onclick="event.preventDefault()">View</a>
<a href="#" onclick="event.preventDefault()">Download</a>
<div class="size">({{attachment.size}})</div>
</div>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,118 @@
@import 'src/app/core/scss/fuse';
:host {
display: flex;
flex-direction: column;
flex: 1;
overflow-y: auto;
padding: 24px;
.select-message-text {
font-size: 24px;
font-weight: 300;
}
.mail-header {
padding-bottom: 16px;
border-bottom: 1px solid rgba(0, 0, 0, 0.12);
.actions {
min-width: 88px;
}
.subject {
font-size: 17px;
font-weight: 500;
}
.label {
font-size: 11px;
border-radius: 2px;
margin: 4px 4px 4px 0;
padding: 3px 8px;
background-color: rgba(0, 0, 0, 0.08);
.label-color {
width: 8px;
height: 8px;
margin-right: 8px;
border-radius: 50%;
}
}
}
.mail-content {
padding: 24px 0;
.to {
color: rgba(0, 0, 0, 0.54);
.to-text {
margin-right: 4px;
text-transform: lowercase;
}
}
.info {
padding-bottom: 16px;
.avatar {
margin-right: 16px;
background-color: mat-color($accent);
}
.name {
margin-right: 8px;
font-weight: 500;
}
.toggle-details {
user-select: none;
text-decoration: underline;
padding-top: 16px;
cursor: pointer;
font-weight: 500;
}
.details {
padding-top: 8px;
.title {
font-weight: 500;
margin-right: 6px;
}
.detail {
color: rgba(0, 0, 0, 0.54);
}
}
}
}
.mail-attachments {
padding: 24px 0;
border-top: 1px solid rgba(0, 0, 0, 0.12);
.title {
margin-bottom: 16px;
font-weight: 500;
}
.attachment {
.preview {
width: 100px;
margin: 0 16px 8px 0;
}
.link {
margin-bottom: 2px;
}
.size {
font-size: 11px;
}
}
}
}

View File

@ -0,0 +1,66 @@
import { ChangeDetectionStrategy, Component, Input, OnChanges, OnDestroy, OnInit } from '@angular/core';
import { Mail } from '../mail.model';
import { Observable } from 'rxjs/Observable';
import { Store } from '@ngrx/store';
import * as fromStore from '../store';
import { MailNgrxService } from '../mail.service';
@Component({
selector : 'fuse-mail-details',
templateUrl : './mail-details.component.html',
styleUrls : ['./mail-details.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class FuseMailNgrxDetailsComponent implements OnInit, OnDestroy, OnChanges
{
labels$: Observable<any>;
@Input('mail') mailInput: Mail;
mail: Mail;
showDetails = false;
constructor(
private mailService: MailNgrxService,
private store: Store<fromStore.MailAppState>
)
{
this.labels$ = this.store.select(fromStore.getLabelsArr);
}
ngOnInit()
{
}
ngOnChanges()
{
this.updateModel(this.mailInput);
}
toggleStar(event)
{
event.stopPropagation();
this.mail.toggleStar();
this.updateMail();
}
toggleImportant(event)
{
event.stopPropagation();
this.mail.toggleImportant();
this.updateMail();
}
updateModel(data)
{
this.mail = !data ? null : new Mail({...data});
}
updateMail()
{
this.store.dispatch(new fromStore.UpdateMail(this.mail));
this.updateModel(this.mail);
}
ngOnDestroy()
{
}
}

View File

@ -0,0 +1,48 @@
<div fxLayout="row" fxLayoutAlign="start center">
<mat-checkbox [checked]="selected" (change)="onSelectedChange()"
(click)="$event.stopPropagation();">
</mat-checkbox>
<div class="info" fxFlex FlexLayout="column">
<div class="row-1" fxLayout="row" fxLayoutAlign="start center">
<div class="name" fxLayout="row" fxLayoutAlign="start center" fxFlex>
<img class="avatar" *ngIf="mail.from?.avatar" alt="{{mail.from?.name}}" src="{{mail.from?.avatar}}"/>
<div class="avatar" *ngIf="!mail.from?.avatar">{{mail.from?.name[0]}}</div>
<span class="text-truncate" *ngIf="mail?.from">{{mail.from?.name}}</span>
<mat-icon *ngIf="mail.hasAttachments">attachment</mat-icon>
</div>
<div fxLayout="row" fxLayoutAlign="start center">
<div class="time">{{mail.time}}</div>
</div>
</div>
<div class="row-2" fxLayout="row" fxLayoutAlign="start center">
<div fxLayout="column" fxLayoutAlign="start start">
<div class="subject text-truncate">
{{mail.subject}}
</div>
<div class="message text-truncate" *ngIf="mail?.message">
{{mail.message | htmlToPlaintext | slice:0:180}}{{mail.message.length > 180 ? '...' : ''}}
</div>
<div class="labels" fxLayout="row" fxLayoutWrap fxHide fxShow.gt-sm>
<div class="label" *ngFor="let labelId of mail.labels"
fxLayout="row" fxLayoutAlign="start center">
<div class="label-color"
[ngStyle]="{'background-color': (labels$ | async) | getById:labelId:'color'}"></div>
<div class="label-title">{{(labels$ | async) | getById:labelId:'title'}}</div>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,129 @@
@import 'src/app/core/scss/fuse';
:host {
flex-shrink: 0;
position: relative;
padding: 16px 24px;
border-bottom: 1px solid rgba(0, 0, 0, 0.12);
cursor: pointer;
&.unread {
background: #FFFFFF;
.info {
.name {
font-weight: 700;
}
.row-2 {
.subject {
font-weight: 700;
}
.labels {
background: #FFFFFF;
}
}
}
}
&.selected {
background: #FFF8E1;
.info {
.row-2 {
.labels {
background: #FFF8E1;
}
}
}
}
&.current-mail {
background: #E3F2FD;
.info {
.row-2 {
.labels {
background: #E3F2FD;
}
}
}
}
.info {
overflow: hidden;
width: 0;
margin-left: 16px;
position: relative;
.row-1 {
margin-bottom: 8px;
.name {
font-size: 15px;
font-weight: 500;
.avatar {
min-width: 32px;
width: 32px;
height: 32px;
line-height: 32px;
background-color: mat-color($accent);
}
}
.actions {
margin-left: 4px;
.mat-icon-button {
width: 32px;
height: 32px;
line-height: 1;
}
}
}
.row-2 {
.subject,
.message {
width: 100%;
}
.message {
position: relative;
color: rgba(0, 0, 0, 0.54);
}
.labels {
position: absolute;
background: #FFFFFF;
bottom: 0;
right: 0;
padding-left: 6px;
.label {
font-size: 11px;
border-radius: 2px;
margin: 0 4px 0 0;
padding: 3px 8px;
background-color: rgba(0, 0, 0, 0.08);
.label-color {
width: 8px;
height: 8px;
margin-right: 8px;
border-radius: 50%;
}
}
}
}
}
}

View File

@ -0,0 +1,56 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, HostBinding, Input, OnDestroy, OnInit } from '@angular/core';
import { Mail } from '../../mail.model';
import { MailNgrxService } from '../../mail.service';
import { Store } from '@ngrx/store';
import { Observable } from 'rxjs/Observable';
import * as fromStore from '../../store';
@Component({
selector : 'fuse-mail-list-item',
templateUrl : './mail-list-item.component.html',
styleUrls : ['./mail-list-item.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class FuseMailNgrxListItemComponent implements OnInit, OnDestroy
{
@Input() mail: Mail;
@HostBinding('class.selected') selected: boolean;
labels$: Observable<any>;
selectedMailIds$: Observable<any>;
constructor(
private mailService: MailNgrxService,
private store: Store<fromStore.MailAppState>,
private cd: ChangeDetectorRef
)
{
this.labels$ = this.store.select(fromStore.getLabelsArr);
this.selectedMailIds$ = this.store.select(fromStore.getSelectedMailIds);
this.selected = false;
}
ngOnInit()
{
// Set the initial values
this.mail = new Mail(this.mail);
this.selectedMailIds$.subscribe((selectedMailIds) => {
this.selected = selectedMailIds.length > 0 && selectedMailIds.find(id => id === this.mail.id) !== undefined;
this.refresh();
});
}
refresh()
{
this.cd.markForCheck();
}
onSelectedChange()
{
this.store.dispatch(new fromStore.ToggleInSelectedMails(this.mail.id));
}
ngOnDestroy()
{
}
}

View File

@ -0,0 +1,9 @@
<div *ngIf="mails.length === 0" fxLayout="column" fxLayoutAlign="center center" fxFlexFill>
<span class="no-messages-text hint-text">{{ 'MAIL.NO_MESSAGES' | translate }}</span>
</div>
<div class="mail-list">
<fuse-mail-list-item matRipple *ngFor="let mail of mails" [mail]="mail" (click)="readMail(mail.id)"
[ngClass]="{'current-mail':mail?.id == currentMail?.id}">
</fuse-mail-list-item>
</div>

View File

@ -0,0 +1,21 @@
:host {
position: relative;
display: flex;
flex-direction: column;
flex: 1;
overflow-y: auto;
padding: 0;
border-right: 1px solid rgba(0, 0, 0, .12);
.no-messages-text {
font-size: 24px;
font-weight: 300;
}
.mail-list {
position: relative;
display: flex;
flex-direction: column;
flex: 1;
}
}

View File

@ -0,0 +1,56 @@
import { ChangeDetectionStrategy, Component, Input, OnDestroy, OnInit } from '@angular/core';
import { Mail } from '../mail.model';
import { ActivatedRoute, Router } from '@angular/router';
import { MailNgrxService } from '../mail.service';
@Component({
selector : 'fuse-mail-list',
templateUrl : './mail-list.component.html',
styleUrls : ['./mail-list.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class FuseMailNgrxListComponent implements OnInit, OnDestroy
{
@Input() mails: Mail[];
currentMail: Mail;
constructor(
private route: ActivatedRoute,
private mailService: MailNgrxService,
private router: Router
)
{
}
ngOnInit()
{
}
/**
* Read mail
* @param mailId
*/
readMail(mailId)
{
const labelHandle = this.route.snapshot.params.labelHandle,
filterHandle = this.route.snapshot.params.filterHandle,
folderHandle = this.route.snapshot.params.folderHandle;
if ( labelHandle )
{
this.router.navigate(['apps/mail-ngrx/label/' + labelHandle + '/' + mailId]);
}
else if ( filterHandle )
{
this.router.navigate(['apps/mail-ngrx/filter/' + filterHandle + '/' + mailId]);
}
else
{
this.router.navigate(['apps/mail-ngrx/' + folderHandle + '/' + mailId]);
}
}
ngOnDestroy()
{
}
}

View File

@ -0,0 +1,116 @@
<div id="mail" class="page-layout carded left-sidenav" fusePerfectScrollbar>
<!-- TOP BACKGROUND -->
<div class="top-bg mat-accent-bg"></div>
<!-- / TOP BACKGROUND -->
<mat-sidenav-container>
<!-- SIDENAV -->
<mat-sidenav class="sidenav mat-sidenav-opened" align="start" mode="side" opened="true"
fuseMatSidenavHelper="carded-left-sidenav" mat-is-locked-open="gt-md">
<fuse-mail-main-sidenav></fuse-mail-main-sidenav>
</mat-sidenav>
<!-- / SIDENAV -->
<!-- CENTER -->
<div class="center">
<!-- CONTENT HEADER -->
<div class="header" fxLayout="row" fxLayoutAlign="start center">
<div class="search-wrapper" fxFlex fxLayout="row" fxLayoutAlign="start center">
<button mat-button class="mat-icon-button sidenav-toggle"
fuseMatSidenavToggler="carded-left-sidenav"
fxHide.gt-md aria-label="Toggle Sidenav">
<mat-icon>menu</mat-icon>
</button>
<div class="search mat-white-bg" flex fxLayout="row" fxLayoutAlign="start center">
<mat-icon>search</mat-icon>
<input [formControl]="searchInput" [placeholder]="'MAIL.SEARCH_PLACEHOLDER' | translate" fxFlex>
</div>
</div>
</div>
<!-- / CONTENT HEADER -->
<!-- CONTENT CARD -->
<div class="content-card mat-white-bg" [ngClass]="{'current-mail-selected':currentMail}">
<!-- CONTENT TOOLBAR -->
<div class="toolbar px-24 py-8">
<div class="mail-selection" fxFlex="row" fxLayoutAlign="start center">
<mat-checkbox (click)="toggleSelectAll($event)"
[checked]="hasSelectedMails"
[indeterminate]="isIndeterminate">
</mat-checkbox>
<button mat-icon-button [matMenuTriggerFor]="selectMenu">
<mat-icon>arrow_drop_down</mat-icon>
</button>
<mat-menu #selectMenu="matMenu">
<button mat-menu-item (click)="selectAllMails()">All</button>
<button mat-menu-item (click)="deselectAllMails()">None</button>
<button mat-menu-item (click)="selectMailsByParameter('read', true)">Read</button>
<button mat-menu-item (click)="selectMailsByParameter('read', false)">Unread</button>
<button mat-menu-item (click)="selectMailsByParameter('starred', true)">Starred</button>
<button mat-menu-item (click)="selectMailsByParameter('starred', false)">Unstarred</button>
<button mat-menu-item (click)="selectMailsByParameter('important', true)">Important</button>
<button mat-menu-item (click)="selectMailsByParameter('important', false)">Unimportant</button>
</mat-menu>
<div class="toolbar-separator" *ngIf="hasSelectedMails"></div>
<button mat-icon-button (click)="setFolderOnSelectedMails(4)" *ngIf="hasSelectedMails">
<mat-icon>delete</mat-icon>
</button>
<button mat-icon-button [matMenuTriggerFor]="folderMenu" *ngIf="hasSelectedMails">
<mat-icon>folder</mat-icon>
</button>
<mat-menu #folderMenu="matMenu">
<button mat-menu-item *ngFor="let folder of folders$ | async"
(click)="setFolderOnSelectedMails(folder.id)">{{folder.title}}
</button>
</mat-menu>
<button mat-icon-button [matMenuTriggerFor]="labelMenu" *ngIf="hasSelectedMails">
<mat-icon>label</mat-icon>
</button>
<mat-menu #labelMenu="matMenu">
<button mat-menu-item *ngFor="let label of labels$ | async"
(click)="toggleLabelOnSelectedMails(label.id)">{{label.title}}
</button>
</mat-menu>
</div>
<div *ngIf="currentMail" fxHide.gt-lg>
<button mat-icon-button (click)="deSelectCurrentMail()">
<mat-icon>arrow_back</mat-icon>
</button>
</div>
</div>
<!-- / CONTENT TOOLBAR -->
<!-- CONTENT -->
<div class="content" fxLayoutAlign="row">
<fuse-mail-list fusePerfectScrollbar fxFlex [mails]="mails$ | async"></fuse-mail-list>
<fuse-mail-details [mail]="currentMail$ | async" fusePerfectScrollbar fxFlex></fuse-mail-details>
</div>
<!-- / CONTENT -->
</div>
<!-- / CONTENT CARD -->
</div>
<!-- / CENTER -->
</mat-sidenav-container>
</div>

View File

@ -0,0 +1,81 @@
@import "src/app/core/scss/fuse";
:host {
width: 100%;
.center {
.header {
.search-wrapper {
@include mat-elevation(7);
.sidenav-toggle {
margin: 0;
width: 56px;
height: 56px;
background: #FFF;
border-radius: 0;
border-right: 1px solid rgba(0, 0, 0, .12);
}
.search {
width: 100%;
height: 56px;
line-height: 56px;
padding: 18px;
input {
height: 56px;
padding-left: 16px;
color: rgba(0, 0, 0, 0.54);
border: none;
outline: none;
}
}
}
}
.content-card {
@include media-breakpoint(xs) {
fuse-mail-list {
border-right: none;
}
fuse-mail-list,
fuse-mail-details {
flex: 1 0 100%;
}
fuse-mail-details {
display: none !important;
}
&.current-mail-selected {
.toolbar {
padding-left: 16px !important;
.mail-selection {
display: none !important;
}
}
.content {
fuse-mail-list {
display: none !important;
}
fuse-mail-details {
display: flex !important;
}
}
}
}
}
}
}

View File

@ -0,0 +1,128 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
import { MailNgrxService } from './mail.service';
import { FormControl } from '@angular/forms';
import { Mail } from './mail.model';
import { FuseTranslationLoaderService } from '../../../../core/services/translation-loader.service';
import { locale as english } from './i18n/en';
import { locale as turkish } from './i18n/tr';
import { Store } from '@ngrx/store';
import { Observable } from 'rxjs/Observable';
import * as fromStore from './store';
@Component({
selector : 'fuse-mail',
templateUrl : './mail.component.html',
styleUrls : ['./mail.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class FuseMailNgrxComponent implements OnInit, OnDestroy
{
hasSelectedMails: boolean;
isIndeterminate: boolean;
searchInput: FormControl;
mails$: Observable<any>;
folders$: Observable<any>;
labels$: Observable<any>;
currentMail$: Observable<Mail>;
selectedMailIds$: Observable<string[]>;
searchText$: Observable<string>;
mails: Mail[];
selectedMailIds: string[];
constructor(
private mailService: MailNgrxService,
private translationLoader: FuseTranslationLoaderService,
private store: Store<fromStore.MailAppState>,
private cd: ChangeDetectorRef
)
{
this.searchInput = new FormControl('');
this.translationLoader.loadTranslations(english, turkish);
this.currentMail$ = this.store.select(fromStore.getCurrentMail);
this.mails$ = this.store.select(fromStore.getMailsArr);
this.folders$ = this.store.select(fromStore.getFoldersArr);
this.labels$ = this.store.select(fromStore.getLabelsArr);
this.selectedMailIds$ = this.store.select(fromStore.getSelectedMailIds);
this.searchText$ = this.store.select(fromStore.getSearchText);
this.mails = [];
this.selectedMailIds = [];
}
ngOnInit()
{
this.mails$.subscribe(mails => {
this.mails = mails;
});
this.selectedMailIds$
.subscribe(selectedMailIds => {
this.selectedMailIds = selectedMailIds;
this.hasSelectedMails = selectedMailIds.length > 0;
this.isIndeterminate = (selectedMailIds.length !== this.mails.length && selectedMailIds.length > 0);
this.refresh();
});
this.searchText$.subscribe(searchText => {
this.searchInput.setValue(searchText);
});
this.searchInput.valueChanges
.debounceTime(300)
.distinctUntilChanged()
.subscribe(searchText => {
this.store.dispatch(new fromStore.SetSearchText(searchText));
});
}
toggleSelectAll(ev)
{
ev.preventDefault();
if ( this.selectedMailIds.length && this.selectedMailIds.length > 0 )
{
this.deselectAllMails();
}
else
{
this.selectAllMails();
}
}
selectAllMails()
{
this.store.dispatch(new fromStore.SelectAllMails());
}
deselectAllMails()
{
this.store.dispatch(new fromStore.DeselectAllMails());
}
selectMailsByParameter(parameter, value)
{
this.store.dispatch(new fromStore.SelectMailsByParameter({
parameter,
value
}));
}
toggleLabelOnSelectedMails(labelId)
{
this.store.dispatch(new fromStore.AddLabelOnSelectedMails(labelId));
}
setFolderOnSelectedMails(folderId)
{
this.store.dispatch(new fromStore.SetFolderOnSelectedMails(folderId));
}
refresh()
{
this.cd.markForCheck();
}
ngOnDestroy()
{
this.cd.detach();
}
}

View File

@ -0,0 +1,56 @@
export class Mail
{
id: string;
from: {
name: string,
avatar: string,
email: string
};
to: {
name: string,
email: string
}[];
subject: string;
message: string;
time: string;
read: boolean;
starred: boolean;
important: boolean;
hasAttachments: boolean;
attachments: {
type: string,
fileName: string,
preview: string,
url: string,
size: string
}[];
labels: string[];
folder: string;
constructor(mail)
{
this.id = mail.id;
this.from = mail.from;
this.to = mail.to;
this.subject = mail.subject;
this.message = mail.message;
this.time = mail.time;
this.read = mail.read;
this.starred = mail.starred;
this.important = mail.important;
this.hasAttachments = mail.hasAttachments;
this.attachments = mail.attachments;
this.labels = mail.labels;
this.folder = mail.folder;
}
toggleStar()
{
this.starred = !this.starred;
}
toggleImportant()
{
this.important = !this.important;
}
}

View File

@ -0,0 +1,73 @@
import { NgModule } from '@angular/core';
import { SharedModule } from '../../../../core/modules/shared.module';
import { RouterModule, Routes } from '@angular/router';
import { FuseMailNgrxComponent } from './mail.component';
import { FuseMailNgrxMainSidenavComponent } from './sidenavs/main/main-sidenav.component';
import { FuseMailNgrxListItemComponent } from './mail-list/mail-list-item/mail-list-item.component';
import { FuseMailNgrxListComponent } from './mail-list/mail-list.component';
import { FuseMailNgrxDetailsComponent } from './mail-details/mail-details.component';
import { MailNgrxService } from './mail.service';
import { FuseMailNgrxComposeDialogComponent } from './dialogs/compose/compose.component';
import { MailAppStoreModule } from './store/store.module';
import * as fromGuards from './store/guards/index';
const routes: Routes = [
{
path : 'label/:labelHandle',
component : FuseMailNgrxComponent,
canActivate: [fromGuards.ResolveGuard]
},
{
path : 'label/:labelHandle/:mailId',
component : FuseMailNgrxComponent,
canActivate: [fromGuards.ResolveGuard]
},
{
path : 'filter/:filterHandle',
component: FuseMailNgrxComponent,
canActivate: [fromGuards.ResolveGuard]
},
{
path : 'filter/:filterHandle/:mailId',
component: FuseMailNgrxComponent,
canActivate: [fromGuards.ResolveGuard]
},
{
path : ':folderHandle',
component: FuseMailNgrxComponent,
canActivate: [fromGuards.ResolveGuard]
},
{
path : ':folderHandle/:mailId',
component: FuseMailNgrxComponent,
canActivate: [fromGuards.ResolveGuard]
},
{
path : '**',
redirectTo: 'inbox'
}
];
@NgModule({
declarations : [
FuseMailNgrxComponent,
FuseMailNgrxListComponent,
FuseMailNgrxListItemComponent,
FuseMailNgrxDetailsComponent,
FuseMailNgrxMainSidenavComponent,
FuseMailNgrxComposeDialogComponent
],
imports : [
SharedModule,
RouterModule.forChild(routes),
MailAppStoreModule
],
providers : [
MailNgrxService,
fromGuards.ResolveGuard
],
entryComponents: [FuseMailNgrxComposeDialogComponent]
})
export class FuseMailNgrxModule
{
}

View File

@ -0,0 +1,87 @@
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { HttpClient } from '@angular/common/http';
import { Mail } from './mail.model';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { Store } from '@ngrx/store';
import { MailAppState } from './store/reducers';
import { getFiltersArr, getFoldersArr, getLabelsArr, getMailsArr } from './store/selectors';
@Injectable()
export class MailNgrxService
{
foldersArr: any;
filtersArr: any;
labelsArr: any;
selectedMails: Mail[];
mails: Mail[];
constructor(
private http: HttpClient,
private store: Store<MailAppState>
)
{
this.store.select(getFoldersArr).subscribe(folders => {
this.foldersArr = folders;
});
this.store.select(getFiltersArr).subscribe(filters => {
this.filtersArr = filters;
});
this.store.select(getLabelsArr).subscribe(labels => {
this.labelsArr = labels;
});
this.store.select(getMailsArr).subscribe(mails => {
this.mails = mails;
});
this.selectedMails = [];
}
getAllMails(): Observable<Mail[]>
{
return this.http.get<Mail[]>('api/mail-mails');
}
getFolders(): Observable<any>
{
return this.http.get('api/mail-folders');
}
getFilters(): Observable<any>
{
return this.http.get('api/mail-filters');
}
getLabels(): Observable<any>
{
return this.http.get('api/mail-labels');
}
getMails(handle): Observable<Mail[]>
{
if ( handle.id === 'labelHandle' )
{
const labelId = this.labelsArr.find(label => label.handle === handle.value).id;
return this.http.get<Mail[]>('api/mail-mails?labels=' + labelId);
}
else if ( handle.id === 'filterHandle' )
{
return this.http.get<Mail[]>('api/mail-mails?' + handle.value + '=true');
}
else // folderHandle
{
const folderId = this.foldersArr.find(folder => folder.handle === handle.value).id;
return this.http.get<any>('api/mail-mails?folder=' + folderId);
}
}
/**
* Update the mail
* @param mail
* @returns {Promise<any>}
*/
updateMail(mail)
{
return this.http.post('api/mail-mails/' + mail.id, {...mail});
}
}

View File

@ -0,0 +1,69 @@
<!-- SIDENAV HEADER -->
<div fxLayout="column" fxLayoutAlign="space-between start"
class="header p-24 pb-4" ngClass="mat-accent-bg" ngClass.gt-md="white-fg">
<div class="logo" fxFlex fxLayout="row" fxLayoutAlign="start center">
<mat-icon class="logo-icon s-32">mail</mat-icon>
<span class="logo-text">Mailbox Ngrx</span>
</div>
<div class="account" fxLayout="column">
<div class="title">John Doe</div>
<mat-form-field floatPlaceholder="never">
<mat-select class="account-selection" placeholder="Mail Selection"
[ngModel]="selectedAccount">
<mat-option *ngFor="let account of (accounts | keys)" [value]="account.key">
{{account.value}}
</mat-option>
</mat-select>
</mat-form-field>
</div>
</div>
<!-- / SIDENAV HEADER -->
<!-- SIDENAV CONTENT -->
<div class="content" fusePerfectScrollbar>
<div class="p-24">
<button mat-raised-button fxFlex
class="mat-accent compose-dialog-button"
(click)="composeDialog()"
aria-label="Compose">
{{ 'MAIL.COMPOSE' | translate }}
</button>
</div>
<div class="nav">
<div class="nav-subheader">{{ 'MAIL.FOLDERS' | translate }}</div>
<div class="nav-item" *ngFor="let folder of (folders$ | async)">
<a class="nav-link" matRipple [routerLink]="'/apps/mail-ngrx/' + folder.handle" routerLinkActive="active">
<mat-icon class="nav-link-icon" *ngIf="folder.icon">{{folder.icon}}</mat-icon>
<span>{{folder.title}}</span>
</a>
</div>
<div class="nav-subheader">{{ 'MAIL.FILTERS' | translate }}</div>
<div class="nav-item" *ngFor="let filter of (filters$ | async)">
<a class="nav-link" matRipple [routerLink]="'/apps/mail-ngrx/filter/' + filter.handle" routerLinkActive="active">
<mat-icon class="nav-link-icon" *ngIf="filter.icon">{{filter.icon}}</mat-icon>
<span>{{filter.title}}</span>
</a>
</div>
<div class="nav-subheader">{{ 'MAIL.LABELS' | translate }}</div>
<div class="nav-item" *ngFor="let label of (labels$ | async)">
<a class="nav-link" matRipple [routerLink]="'/apps/mail-ngrx/label/' + label.handle" routerLinkActive="active">
<mat-icon class="nav-link-icon" [ngStyle]="{'color':label.color}">label</mat-icon>
<span>{{label.title}}</span>
</a>
</div>
</div>
</div>
<!-- / SIDENAV CONTENT -->

View File

@ -0,0 +1,30 @@
:host {
display: flex;
flex: 1 0 auto;
flex-direction: column;
height: 100%;
.header {
.logo {
.logo-icon {
margin: 0 16px 0 0;
}
.logo-text {
font-size: 24px;
line-height: 24px;
}
}
.account {
width: 100%;
}
}
.content {
.compose-dialog-button {
}
}
}

View File

@ -0,0 +1,84 @@
import { ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core';
import { FuseMailNgrxComposeDialogComponent } from '../../dialogs/compose/compose.component';
import { MatDialog } from '@angular/material';
import { FormGroup } from '@angular/forms';
import { Observable } from 'rxjs/Observable';
import { Store } from '@ngrx/store';
import * as fromStore from './../../store';
import { MailNgrxService } from '../../mail.service';
@Component({
selector : 'fuse-mail-main-sidenav',
templateUrl : './main-sidenav.component.html',
styleUrls : ['./main-sidenav.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class FuseMailNgrxMainSidenavComponent implements OnInit, OnDestroy
{
labels: any[];
accounts: object;
selectedAccount: string;
dialogRef: any;
folders$: Observable<any>;
filters$: Observable<any>;
labels$: Observable<any>;
constructor(
private mailService: MailNgrxService,
public dialog: MatDialog,
private store: Store<fromStore.MailAppState>
)
{
// Data
this.accounts = {
'creapond' : 'johndoe@creapond.com',
'withinpixels': 'johndoe@withinpixels.com'
};
this.selectedAccount = 'creapond';
this.folders$ = this.store.select(fromStore.getFoldersArr);
this.filters$ = this.store.select(fromStore.getFiltersArr);
this.labels$ = this.store.select(fromStore.getLabelsArr);
}
ngOnInit()
{
}
composeDialog()
{
this.dialogRef = this.dialog.open(FuseMailNgrxComposeDialogComponent, {
panelClass: 'mail-compose-dialog'
});
this.dialogRef.afterClosed()
.subscribe(response => {
if ( !response )
{
return;
}
const actionType: string = response[0];
const formData: FormGroup = response[1];
switch ( actionType )
{
/**
* Send
*/
case 'send':
console.log('new Mail', formData.getRawValue());
break;
/**
* Delete
*/
case 'delete':
console.log('delete Mail');
break;
}
});
}
ngOnDestroy()
{
}
}

View File

@ -0,0 +1,46 @@
import { Action } from '@ngrx/store';
export const GET_FILTERS = '[FILTERS] GET FILTERS';
export const GET_FILTERS_SUCCESS = '[FILTERS] GET FILTERS SUCCESS';
export const GET_FILTERS_FAILED = '[FILTERS] GET FILTERS FAILED';
/**
* Get Filters
*/
export class GetFilters implements Action
{
readonly type = GET_FILTERS;
constructor(public payload: any)
{
}
}
/**
* Get Filters Success
*/
export class GetFiltersSuccess implements Action
{
readonly type = GET_FILTERS_SUCCESS;
constructor(public payload: any)
{
}
}
/**
* Get Filters Failed
*/
export class GetFiltersFailed implements Action
{
readonly type = GET_FILTERS_FAILED;
constructor(public payload: string)
{
}
}
export type FiltersActionsAll
= GetFilters
| GetFiltersSuccess
| GetFiltersFailed;

View File

@ -0,0 +1,46 @@
import { Action } from '@ngrx/store';
export const GET_FOLDERS = '[FOLDERS] GET FOLDERS';
export const GET_FOLDERS_SUCCESS = '[FOLDERS] GET FOLDERS SUCCESS';
export const GET_FOLDERS_FAILED = '[FOLDERS] GET FOLDERS FAILED';
/**
* Get Folders
*/
export class GetFolders implements Action
{
readonly type = GET_FOLDERS;
constructor(public payload: any)
{
}
}
/**
* Get Folders Success
*/
export class GetFoldersSuccess implements Action
{
readonly type = GET_FOLDERS_SUCCESS;
constructor(public payload: any)
{
}
}
/**
* Get Folders Failed
*/
export class GetFoldersFailed implements Action
{
readonly type = GET_FOLDERS_FAILED;
constructor(public payload: string)
{
}
}
export type FoldersActionsAll
= GetFolders
| GetFoldersSuccess
| GetFoldersFailed;

View File

@ -0,0 +1,4 @@
export * from './mails.actions';
export * from './folders.actions';
export * from './filters.actions';
export * from './labels.actions';

View File

@ -0,0 +1,46 @@
import { Action } from '@ngrx/store';
export const GET_LABELS = '[LABELS] GET LABELS';
export const GET_LABELS_SUCCESS = '[LABELS] GET LABELS SUCCESS';
export const GET_LABELS_FAILED = '[LABELS] GET LABELS FAILED';
/**
* Get Labels
*/
export class GetLabels implements Action
{
readonly type = GET_LABELS;
constructor(public payload: any)
{
}
}
/**
* Get Labels Success
*/
export class GetLabelsSuccess implements Action
{
readonly type = GET_LABELS_SUCCESS;
constructor(public payload: any)
{
}
}
/**
* Get Labels Failed
*/
export class GetLabelsFailed implements Action
{
readonly type = GET_LABELS_FAILED;
constructor(public payload: string)
{
}
}
export type LabelsActionsAll
= GetLabels
| GetLabelsSuccess
| GetLabelsFailed;

View File

@ -0,0 +1,243 @@
import { Action } from '@ngrx/store';
import { Mail } from '../../mail.model';
export const GET_MAILS = '[MAILS] GET MAILS';
export const GET_MAILS_SUCCESS = '[MAILS] GET MAILS SUCCESS';
export const GET_MAILS_FAILED = '[MAILS] GET MAILS FAILED';
export const SET_CURRENT_MAIL = '[MAILS] SET CURRENT MAIL';
export const SET_CURRENT_MAIL_SUCCESS = '[MAILS] SET CURRENT MAIL SUCCESS';
export const CHECK_CURRENT_MAIL = '[MAILS] CHECK CURRENT MAIL';
export const UPDATE_MAIL = '[MAILS] UPDATE MAIL';
export const UPDATE_MAIL_SUCCESS = '[MAILS] UPDATE MAIL SUCCESS';
export const UPDATE_MAILS = '[MAILS] UPDATE MAILS';
export const UPDATE_MAILS_SUCCESS = '[MAILS] UPDATE MAILS SUCCESS';
export const SET_SEARCH_TEXT = '[MAILS] SET SEARCH TEXT';
export const SELECT_ALL_MAILS = '[MAILS] SELECT ALL MAILS';
export const DESELECT_ALL_MAILS = '[MAILS] DESELECT ALL MAILS';
export const TOGGLE_IN_SELECTED_MAILS = '[MAILS] TOGGLE IN SELECTED MAILS';
export const SELECT_MAILS_BY_PARAMETER = '[MAILS] SELECT MAILS BY PARAMETER';
export const SET_FOLDER_ON_SELECTED_MAILS = '[MAILS] SET FOLDER ON SELECTED MAILS';
export const ADD_LABEL_ON_SELECTED_MAILS = '[MAILS] ADD LABEL ON SELECTED MAILS';
/**
* Get Mails
*/
export class GetMails implements Action
{
readonly type = GET_MAILS;
constructor()
{
}
}
/**
* Get Mails Success
*/
export class GetMailsSuccess implements Action
{
readonly type = GET_MAILS_SUCCESS;
constructor(public payload: any)
{
}
}
/**
* Get Mails Failed
*/
export class GetMailsFailed implements Action
{
readonly type = GET_MAILS_FAILED;
constructor(public payload: string)
{
}
}
/**
* Set Current Mail
*/
export class SetCurrentMail implements Action
{
readonly type = SET_CURRENT_MAIL;
constructor(public payload: string)
{
}
}
/**
* Set Current Mail Success
*/
export class SetCurrentMailSuccess implements Action
{
readonly type = SET_CURRENT_MAIL_SUCCESS;
constructor(public payload: any)
{
}
}
/**
* Check Current Mail
*/
export class CheckCurrentMail implements Action
{
readonly type = CHECK_CURRENT_MAIL;
constructor()
{
}
}
/**
* Update Mail
*/
export class UpdateMail implements Action
{
readonly type = UPDATE_MAIL;
constructor(public payload: any)
{
}
}
/**
* Update Mail Success
*/
export class UpdateMailSuccess implements Action
{
readonly type = UPDATE_MAIL_SUCCESS;
constructor(public payload: Mail)
{
}
}
/**
* Update Mails
*/
export class UpdateMails implements Action
{
readonly type = UPDATE_MAILS;
constructor(public payload: Mail[])
{
}
}
/**
* Update Mails Success
*/
export class UpdateMailsSuccess implements Action
{
readonly type = UPDATE_MAILS_SUCCESS;
constructor()
{
}
}
/**
* Set Search Text
*/
export class SetSearchText implements Action
{
readonly type = SET_SEARCH_TEXT;
constructor(public payload: string)
{
}
}
/**
* Select All Mails
*/
export class SelectAllMails implements Action
{
readonly type = SELECT_ALL_MAILS;
constructor()
{
}
}
/**
* Deselect All Mails
*/
export class DeselectAllMails implements Action
{
readonly type = DESELECT_ALL_MAILS;
constructor()
{
}
}
/**
* Toggle In Selected Mails
*/
export class ToggleInSelectedMails implements Action
{
readonly type = TOGGLE_IN_SELECTED_MAILS;
constructor(public payload: string)
{
}
}
/**
* Select Mails by Parameter
*/
export class SelectMailsByParameter implements Action
{
readonly type = SELECT_MAILS_BY_PARAMETER;
constructor(public payload: any)
{
}
}
/**
* Set Folder on Selected Mails
*/
export class SetFolderOnSelectedMails implements Action
{
readonly type = SET_FOLDER_ON_SELECTED_MAILS;
constructor(public payload: string)
{
}
}
/**
* Add label on Selected Mails
*/
export class AddLabelOnSelectedMails implements Action
{
readonly type = ADD_LABEL_ON_SELECTED_MAILS;
constructor(public payload: string)
{
}
}
export type MailsActionsAll
= GetMails
| GetMailsSuccess
| GetMailsFailed
| SetCurrentMail
| SetCurrentMailSuccess
| CheckCurrentMail
| UpdateMail
| UpdateMailSuccess
| UpdateMails
| UpdateMailsSuccess
| SetSearchText
| SelectAllMails
| DeselectAllMails
| ToggleInSelectedMails
| SelectMailsByParameter
| SetFolderOnSelectedMails
| AddLabelOnSelectedMails;

View File

@ -0,0 +1,40 @@
import { Injectable } from '@angular/core';
import { Actions, Effect } from '@ngrx/effects';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/delay';
import 'rxjs/add/operator/map';
import { of } from 'rxjs/observable/of';
import { catchError, map, switchMap } from 'rxjs/operators';
import * as FiltersActions from '../actions/filters.actions';
import { MailNgrxService } from '../../mail.service';
@Injectable()
export class FiltersEffect
{
constructor(
private actions: Actions,
private mailService: MailNgrxService
)
{
}
/**
* Get filters from Server
* @type {Observable<any>}
*/
@Effect()
getFilters: Observable<FiltersActions.FiltersActionsAll> =
this.actions
.ofType<FiltersActions.GetFilters>(FiltersActions.GET_FILTERS)
.pipe(
switchMap((action) => {
return this.mailService.getFilters()
.pipe(
map((filters: any) => {
return new FiltersActions.GetFiltersSuccess(filters);
}),
catchError(err => of(new FiltersActions.GetFiltersFailed(err)))
);
}
));
}

View File

@ -0,0 +1,40 @@
import { Injectable } from '@angular/core';
import { Actions, Effect } from '@ngrx/effects';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/delay';
import 'rxjs/add/operator/map';
import { of } from 'rxjs/observable/of';
import { catchError, map, switchMap } from 'rxjs/operators';
import * as FoldersActions from '../actions/folders.actions';
import { MailNgrxService } from '../../mail.service';
@Injectable()
export class FoldersEffect
{
constructor(
private actions: Actions,
private mailService: MailNgrxService
)
{
}
/**
* Get Folders from Server
* @type {Observable<any>}
*/
@Effect()
getFolders: Observable<FoldersActions.FoldersActionsAll> =
this.actions
.ofType<FoldersActions.GetFolders>(FoldersActions.GET_FOLDERS)
.pipe(
switchMap((action) => {
return this.mailService.getFolders()
.pipe(
map((folders: any) => {
return new FoldersActions.GetFoldersSuccess(folders);
}),
catchError(err => of(new FoldersActions.GetFoldersFailed(err)))
);
}
));
}

View File

@ -0,0 +1,16 @@
import { MailsEffect } from './mails.effects';
import { FoldersEffect } from './folders.effects';
import { FiltersEffect } from './filters.effects';
import { LabelsEffect } from './labels.effects';
export const effects = [
MailsEffect,
FoldersEffect,
FiltersEffect,
LabelsEffect
];
export * from './mails.effects';
export * from './folders.effects';
export * from './filters.effects';
export * from './labels.effects';

View File

@ -0,0 +1,40 @@
import { Injectable } from '@angular/core';
import { Actions, Effect } from '@ngrx/effects';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/delay';
import 'rxjs/add/operator/map';
import { of } from 'rxjs/observable/of';
import { catchError, map, switchMap } from 'rxjs/operators';
import * as LabelsActions from '../actions/labels.actions';
import { MailNgrxService } from '../../mail.service';
@Injectable()
export class LabelsEffect
{
constructor(
private actions: Actions,
private mailService: MailNgrxService
)
{
}
/**
* Get Labels from Server
* @type {Observable<any>}
*/
@Effect()
getLabels: Observable<LabelsActions.LabelsActionsAll> =
this.actions
.ofType<LabelsActions.GetLabels>(LabelsActions.GET_LABELS)
.pipe(
switchMap((action) => {
return this.mailService.getLabels()
.pipe(
map((labels: any) => {
return new LabelsActions.GetLabelsSuccess(labels);
}),
catchError(err => of(new LabelsActions.GetLabelsFailed(err)))
);
}
));
}

View File

@ -0,0 +1,254 @@
import { Injectable } from '@angular/core';
import { Action, Store } from '@ngrx/store';
import { Actions, Effect } from '@ngrx/effects';
import { Observable } from 'rxjs/Observable';
import { of } from 'rxjs/observable/of';
import { map, mergeMap, exhaustMap, withLatestFrom } from 'rxjs/operators';
import { getRouterState, State } from '../../../../../../store/reducers';
import { getMailsState } from '../selectors';
import * as MailsActions from '../actions/mails.actions';
import * as fromRoot from '../../../../../../store';
import { MailNgrxService } from '../../mail.service';
import { Mail } from '../../mail.model';
@Injectable()
export class MailsEffect
{
routerState: any;
constructor(
private actions: Actions,
private mailService: MailNgrxService,
private store: Store<State>
)
{
this.store.select(getRouterState).subscribe(routerState => {
if ( routerState )
{
this.routerState = routerState.state;
}
});
}
/**
* Get Mails with router parameters
* @type {Observable<any>}
*/
@Effect()
getMails: Observable<MailsActions.MailsActionsAll> =
this.actions
.ofType<MailsActions.GetMails>(MailsActions.GET_MAILS)
.pipe(
exhaustMap((action) => {
let handle = {
id : '',
value: ''
};
const routeParams = Observable.of('labelHandle', 'filterHandle', 'folderHandle');
routeParams.subscribe(param => {
if ( this.routerState.params[param] )
{
handle = {
id : param,
value: this.routerState.params[param]
};
}
});
return this.mailService.getMails(handle)
.map((mails: Mail[]) => {
return new MailsActions.GetMailsSuccess({
loaded: handle,
mails : mails
});
})
.catch(err => of(new MailsActions.GetMailsFailed(err)));
})
);
/**
* Update Mail
* @type {Observable<any>}
*/
@Effect()
updateMail: Observable<MailsActions.MailsActionsAll> =
this.actions
.ofType<MailsActions.UpdateMail>(MailsActions.UPDATE_MAIL)
.pipe(
exhaustMap((action) => {
return this.mailService.updateMail(action.payload)
.map(() => {
return new MailsActions.UpdateMailSuccess(action.payload);
});
})
);
/**
* UpdateMails
* @type {Observable<any>}
*/
@Effect()
updateMails: Observable<MailsActions.MailsActionsAll> =
this.actions
.ofType<MailsActions.UpdateMails>(MailsActions.UPDATE_MAILS)
.pipe(
exhaustMap((action) => {
return Observable.forkJoin(
action.payload.map(mail => this.mailService.updateMail(mail)),
() => {
return new MailsActions.UpdateMailsSuccess();
});
})
);
/**
* Set Current Mail
* @type {Observable<SetCurrentMailSuccess>}
*/
@Effect()
setCurrentMail: Observable<Action> =
this.actions
.ofType<MailsActions.SetCurrentMail>(MailsActions.SET_CURRENT_MAIL)
.pipe(
withLatestFrom(this.store.select(getMailsState)),
map(([action, state]) => {
return new MailsActions.SetCurrentMailSuccess(state.entities[action.payload]);
})
);
/**
* Check Current Mail
* Navigate to parent directory if not exist in mail list
* Update Current Mail if exist in mail list
* @type {Observable<any>}
*/
@Effect()
checkCurrentMail: Observable<Action> =
this.actions
.ofType<MailsActions.CheckCurrentMail>(MailsActions.CHECK_CURRENT_MAIL)
.pipe(
withLatestFrom(this.store.select(getMailsState)),
map(([action, state]) => {
if ( !state.entities[this.routerState.params.mailId] )
{
return new fromRoot.Go({path: [this.routerState.url.replace(this.routerState.params.mailId, '')]});
}
return new MailsActions.SetCurrentMailSuccess(state.entities[this.routerState.params.mailId]);
})
);
/**
* On Get Mails Success
* @type {Observable<CheckCurrentMail>}
*/
@Effect()
getMailsSuccess: Observable<MailsActions.MailsActionsAll> =
this.actions
.ofType<MailsActions.GetMailsSuccess>(MailsActions.GET_MAILS_SUCCESS)
.pipe(
mergeMap(() =>
[
new MailsActions.CheckCurrentMail()
])
);
/**
* On Update Mails Success
* @type {Observable<DeselectAllMails | GetMails>}
*/
@Effect()
updateMailsSuccess: Observable<MailsActions.MailsActionsAll> =
this.actions
.ofType<MailsActions.UpdateMailsSuccess>(MailsActions.UPDATE_MAILS_SUCCESS)
.pipe(
mergeMap(() =>
[
new MailsActions.DeselectAllMails(),
new MailsActions.GetMails()
])
);
/**
* On Update Mail Success
* @type {Observable<GetMails>}
*/
@Effect()
updateMailSuccess: Observable<MailsActions.MailsActionsAll> =
this.actions
.ofType<MailsActions.UpdateMailSuccess>(MailsActions.UPDATE_MAIL_SUCCESS)
.debounceTime(500)
.pipe(
map(() => {
return new MailsActions.GetMails();
})
);
/**
* Set Folder on Selected Mails
* @type {Observable<UpdateMails>}
*/
@Effect()
setFolderOnSelectedMails: Observable<MailsActions.MailsActionsAll> =
this.actions
.ofType<MailsActions.SetFolderOnSelectedMails>(MailsActions.SET_FOLDER_ON_SELECTED_MAILS)
.pipe(
withLatestFrom(
this.store.select(getMailsState)),
map(([action, state]) => {
const entities = {...state.entities};
let mailsToUpdate = [];
state.selectedMailIds
.map(id => {
mailsToUpdate = [
...mailsToUpdate,
entities[id] = {
...entities[id],
folder: action.payload
}
];
});
return new MailsActions.UpdateMails(mailsToUpdate);
})
);
/**
* Add Label on Selected Mails
* @type {Observable<UpdateMails>}
*/
@Effect()
addLabelOnSelectedMails: Observable<MailsActions.MailsActionsAll> =
this.actions
.ofType<MailsActions.AddLabelOnSelectedMails>(MailsActions.ADD_LABEL_ON_SELECTED_MAILS)
.pipe(
withLatestFrom(this.store.select(getMailsState)),
map(([action, state]) => {
const entities = {...state.entities};
let mailsToUpdate = [];
state.selectedMailIds
.map(id => {
let labels = [...entities[id].labels];
if ( !entities[id].labels.includes(action.payload) )
{
labels = [...labels, action.payload];
}
mailsToUpdate = [
...mailsToUpdate,
entities[id] = {
...entities[id],
labels
}
];
});
return new MailsActions.UpdateMails(mailsToUpdate);
})
);
}

View File

@ -0,0 +1 @@
export * from './resolve.guard';

View File

@ -0,0 +1,134 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate } from '@angular/router';
import { Store } from '@ngrx/store';
import { Observable } from 'rxjs/Observable';
import { of } from 'rxjs/observable/of';
import { map, switchMap, catchError, tap, take, filter } from 'rxjs/operators';
import 'rxjs/add/observable/forkJoin';
import { MailAppState } from '../reducers';
import * as fromStore from '../index';
import { getFiltersLoaded, getFoldersLoaded, getLabelsLoaded, getMailsLoaded } from '../selectors';
import { RouterStateSnapshot } from '@angular/router/src/router_state';
import { getRouterState } from '../../../../../../store/reducers';
@Injectable()
export class ResolveGuard implements CanActivate
{
routerState: any;
constructor(
private store: Store<MailAppState>
)
{
this.store.select(getRouterState).subscribe(routerState => {
if ( routerState )
{
this.routerState = routerState.state;
}
});
}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean>
{
return this.checkStore().pipe(
switchMap(() => of(true)),
catchError(() => of(false))
);
}
checkStore(): Observable<any>
{
return Observable
.forkJoin(
this.getFolders(),
this.getFilters(),
this.getLabels()
)
.pipe(
filter(([foldersLoaded, filtersLoaded, labelsLoaded]) => filtersLoaded && foldersLoaded && labelsLoaded),
take(1),
switchMap(() =>
this.getMails()
),
take(1),
map(() => this.store.dispatch(new fromStore.SetCurrentMail(this.routerState.params.mailId)))
);
}
getFolders()
{
return this.store.select(getFoldersLoaded)
.pipe(
tap(loaded => {
if ( !loaded )
{
this.store.dispatch(new fromStore.GetFolders([]));
}
}),
filter(loaded => loaded),
take(1)
);
}
/**
* Get Filters
* @returns {Observable<any>}
*/
getFilters()
{
return this.store.select(getFiltersLoaded)
.pipe(
tap(loaded => {
if ( !loaded )
{
this.store.dispatch(new fromStore.GetFilters([]));
}
}),
filter(loaded => loaded),
take(1)
);
}
/**
* Get Labels
* @returns {Observable<any>}
*/
getLabels()
{
return this.store.select(getLabelsLoaded)
.pipe(
tap(loaded => {
if ( !loaded )
{
this.store.dispatch(new fromStore.GetLabels([]));
}
}),
filter(loaded => loaded),
take(1)
);
}
/**
* Get Mails
* @returns {Observable<any>}
*/
getMails()
{
return this.store.select(getMailsLoaded)
.pipe(
tap((loaded: any) => {
if ( !this.routerState.params[loaded.id] || this.routerState.params[loaded.id] !== loaded.value )
{
this.store.dispatch(new fromStore.GetMails());
this.store.dispatch(new fromStore.SetSearchText(''));
this.store.dispatch(new fromStore.DeselectAllMails());
}
}),
filter((loaded: any) => {
return this.routerState.params[loaded.id] && this.routerState.params[loaded.id] === loaded.value;
}),
take(1)
);
}
}

View File

@ -0,0 +1,4 @@
export * from './actions';
export * from './reducers';
export * from './selectors';
export * from './effects';

View File

@ -0,0 +1,53 @@
import * as FiltersActions from '../actions/filters.actions';
export interface FiltersState
{
entities: { [id: number]: any };
loading: boolean;
loaded: boolean;
}
export const FiltersInitialState: FiltersState = {
entities: {},
loading : false,
loaded : false
};
export function FiltersReducer(state = FiltersInitialState, action: FiltersActions.FiltersActionsAll): FiltersState
{
switch ( action.type )
{
case FiltersActions.GET_FILTERS:
return {
...state,
loading: true,
loaded : false
};
case FiltersActions.GET_FILTERS_SUCCESS:
const filters = action.payload;
const entities = filters.reduce(
(_entities: { [id: number]: any }, filter: any) => {
return {
..._entities,
[filter.id]: filter
};
}, {});
return {
...state,
loading: false,
loaded : true,
entities
};
case FiltersActions.GET_FILTERS_FAILED:
return {
...state,
loading: false,
loaded : false
};
default:
return state;
}
}

View File

@ -0,0 +1,53 @@
import * as FoldersActions from '../actions/folders.actions';
export interface FoldersState
{
entities: { [id: number]: any };
loading: boolean;
loaded: boolean;
}
export const FoldersInitialState: FoldersState = {
entities: {},
loading : false,
loaded : false
};
export function FoldersReducer(state = FoldersInitialState, action: FoldersActions.FoldersActionsAll): FoldersState
{
switch ( action.type )
{
case FoldersActions.GET_FOLDERS:
return {
...state,
loading: true,
loaded : false
};
case FoldersActions.GET_FOLDERS_SUCCESS:
const folders = action.payload;
const entities = folders.reduce(
(_entities: { [id: number]: any }, folder: any) => {
return {
..._entities,
[folder.id]: folder
};
}, {});
return {
...state,
loading: false,
loaded : true,
entities
};
case FoldersActions.GET_FOLDERS_FAILED:
return {
...state,
loading: false,
loaded : false
};
default:
return state;
}
}

View File

@ -0,0 +1,34 @@
import { ActionReducerMap, createFeatureSelector, createSelector } from '@ngrx/store';
import { MailsReducer, MailsState } from './mails.reducer';
import { FoldersReducer, FoldersState } from './folders.reducer';
import { FiltersReducer, FiltersState } from './filters.reducer';
import { LabelsReducer, LabelsState } from './labels.reducer';
export interface MailAppState
{
mails: MailsState;
folders: FoldersState;
filters: FiltersState;
labels: LabelsState;
}
export const getMailAppState = createFeatureSelector<MailAppState>(
'mail-app'
);
export const getAppState = createSelector(
getMailAppState,
(state: MailAppState) => state
);
export const reducers: ActionReducerMap<MailAppState> = {
mails : MailsReducer,
folders: FoldersReducer,
filters: FiltersReducer,
labels : LabelsReducer
};
export * from './mails.reducer';
export * from './folders.reducer';
export * from './filters.reducer';
export * from './labels.reducer';

View File

@ -0,0 +1,53 @@
import * as LabelsActions from '../actions/labels.actions';
export interface LabelsState
{
entities: { [id: number]: any };
loading: boolean;
loaded: boolean;
}
export const LabelsInitialState: LabelsState = {
entities: {},
loading : false,
loaded : false
};
export function LabelsReducer(state = LabelsInitialState, action: LabelsActions.LabelsActionsAll): LabelsState
{
switch ( action.type )
{
case LabelsActions.GET_LABELS:
return {
...state,
loading: true,
loaded : false
};
case LabelsActions.GET_LABELS_SUCCESS:
const labels = action.payload;
const entities = labels.reduce(
(_entities: { [id: number]: any }, label: any) => {
return {
..._entities,
[label.id]: label
};
}, {});
return {
...state,
loading: false,
loaded : true,
entities
};
case LabelsActions.GET_LABELS_FAILED:
return {
...state,
loading: false,
loaded : false
};
default:
return state;
}
}

View File

@ -0,0 +1,167 @@
import * as MailsActions from '../actions/mails.actions';
import { Mail } from '../../mail.model';
export interface MailsState
{
entities: { [id: number]: Mail };
currentMail: any;
selectedMailIds: string[];
searchText: string;
loading: boolean;
loaded: any;
}
export const MailsInitialState: MailsState = {
entities : {},
currentMail : null,
selectedMailIds: [],
searchText : '',
loading : false,
loaded : false
};
export function MailsReducer(state = MailsInitialState, action: MailsActions.MailsActionsAll): MailsState
{
switch ( action.type )
{
case MailsActions.GET_MAILS:
{
return {
...state,
loading: true
};
}
case MailsActions.GET_MAILS_SUCCESS:
{
const mails = action.payload.mails;
const loaded = action.payload.loaded;
const entities = mails.reduce(
(_entities: { [id: number]: Mail }, mail: Mail) => {
return {
..._entities,
[mail.id]: mail
};
}, {});
return {
...state,
entities,
loading: false,
loaded
};
}
case MailsActions.GET_MAILS_FAILED:
{
return {
...state,
loading: false,
loaded : false
};
}
case MailsActions.SET_CURRENT_MAIL_SUCCESS:
{
return {
...state,
currentMail: action.payload
};
}
case MailsActions.UPDATE_MAIL_SUCCESS:
{
return {
...state,
entities: {
...state.entities,
[action.payload.id]: action.payload
}
};
}
case MailsActions.SET_SEARCH_TEXT:
{
return {
...state,
searchText: action.payload
};
}
case MailsActions.TOGGLE_IN_SELECTED_MAILS:
{
const mailId = action.payload;
let selectedMailIds = [...state.selectedMailIds];
if ( selectedMailIds.find(id => id === mailId) !== undefined )
{
selectedMailIds = selectedMailIds.filter(id => id !== mailId);
}
else
{
selectedMailIds = [...selectedMailIds, mailId];
}
return {
...state,
selectedMailIds
};
}
case MailsActions.SELECT_ALL_MAILS:
{
const arr = Object.keys(state.entities).map(k => state.entities[k]);
const selectedMailIds = arr.map(mail => mail.id);
return {
...state,
selectedMailIds
};
}
case MailsActions.DESELECT_ALL_MAILS:
{
return {
...state,
selectedMailIds: []
};
}
case MailsActions.SELECT_MAILS_BY_PARAMETER:
{
const filter = action.payload;
const arr = Object.keys(state.entities).map(k => state.entities[k]);
const selectedMailIds = arr.filter(mail => mail[filter.parameter] === filter.value)
.map(mail => mail.id);
return {
...state,
selectedMailIds
};
}
case MailsActions.SET_FOLDER_ON_SELECTED_MAILS:
{
const entities = {...state.entities};
state.selectedMailIds.map(id => {
entities[id] = {
...entities[id],
folder: action.payload
};
});
return {
...state,
entities
};
}
default:
return state;
}
}

View File

@ -0,0 +1,22 @@
import { createSelector } from '@ngrx/store';
import { FiltersState, getMailAppState, MailAppState } from '../reducers';
export const getFiltersState = createSelector(
getMailAppState,
(state: MailAppState) => state.filters
);
export const getFilters = createSelector(
getFiltersState,
(state: FiltersState) => state.entities
);
export const getFiltersLoaded = createSelector(
getFiltersState,
(state: FiltersState) => state.loaded
);
export const getFiltersArr = createSelector(
getFilters,
(entities) => Object.keys(entities).map((id) => entities[id])
);

View File

@ -0,0 +1,22 @@
import { createSelector } from '@ngrx/store';
import { FoldersState, getMailAppState, MailAppState } from '../reducers';
export const getFoldersState = createSelector(
getMailAppState,
(state: MailAppState) => state.folders
);
export const getFolders = createSelector(
getFoldersState,
(state: FoldersState) => state.entities
);
export const getFoldersLoaded = createSelector(
getFoldersState,
(state: FoldersState) => state.loaded
);
export const getFoldersArr = createSelector(
getFolders,
(entities) => Object.keys(entities).map((id) => entities[id])
);

View File

@ -0,0 +1,4 @@
export * from './mails.selectors';
export * from './folders.selectors';
export * from './filters.selectors';
export * from './labels.selectors';

View File

@ -0,0 +1,22 @@
import { createSelector } from '@ngrx/store';
import { LabelsState, getMailAppState, MailAppState } from '../reducers';
export const getLabelsState = createSelector(
getMailAppState,
(state: MailAppState) => state.labels
);
export const getLabels = createSelector(
getLabelsState,
(state: LabelsState) => state.entities
);
export const getLabelsLoaded = createSelector(
getLabelsState,
(state: LabelsState) => state.loaded
);
export const getLabelsArr = createSelector(
getLabels,
(entities) => Object.keys(entities).map((id) => entities[id])
);

View File

@ -0,0 +1,42 @@
import { createSelector } from '@ngrx/store';
import { getMailAppState, MailAppState, MailsState } from '../reducers';
import { FuseUtils } from '../../../../../../core/fuseUtils';
export const getMailsState = createSelector(
getMailAppState,
(state: MailAppState) => state.mails
);
export const getMails = createSelector(
getMailsState,
(state: MailsState) => state.entities
);
export const getMailsLoaded = createSelector(
getMailsState,
(state: MailsState) => state.loaded
);
export const getSearchText = createSelector(
getMailsState,
(state: MailsState) => state.searchText
);
export const getMailsArr = createSelector(
getMails,
getSearchText,
(entities, searchText) => {
const arr = Object.keys(entities).map((id) => entities[id]);
return FuseUtils.filterArrayByString(arr, searchText);
}
);
export const getCurrentMail = createSelector(
getMailsState,
(state: MailsState) => state.currentMail
);
export const getSelectedMailIds = createSelector(
getMailsState,
(state: MailsState) => state.selectedMailIds
);

View File

@ -0,0 +1,16 @@
import { StoreModule } from '@ngrx/store';
import { NgModule } from '@angular/core';
import { EffectsModule } from '@ngrx/effects';
import { reducers } from './reducers';
import { effects } from './effects';
@NgModule({
imports : [
StoreModule.forFeature('mail-app', reducers),
EffectsModule.forFeature(effects)
],
providers: []
})
export class MailAppStoreModule
{
}

View File

@ -10,6 +10,10 @@ export const locale = {
'TITLE': 'Mail',
'BADGE': '25'
},
'MAIL_NGRX' : {
'TITLE': 'Mail Ngrx',
'BADGE': '13'
},
'CHAT' : 'Chat',
'FILE_MANAGER': 'File Manager',
'CONTACTS' : 'Contacts',

View File

@ -10,6 +10,10 @@ export const locale = {
'TITLE': 'Posta',
'BADGE': '15'
},
'MAIL_NGRX' : {
'TITLE': 'Posta Ngrx',
'BADGE': '13'
},
'CHAT' : 'Sohbet',
'FILE_MANAGER': 'Dosya Yöneticisi',
'CONTACTS' : 'Kişiler',

View File

@ -94,6 +94,20 @@ export class FuseNavigationModel implements FuseNavigationModelInterface
'fg' : '#FFFFFF'
}
},
{
'id' : 'mail-ngrx',
'title' : 'Mail Ngrx',
'translate': 'NAV.MAIL_NGRX.TITLE',
'type' : 'item',
'icon' : 'email',
'url' : '/apps/mail-ngrx',
'badge' : {
'title' : 13,
'translate': 'NAV.MAIL_NGRX.BADGE',
'bg' : '#EC0C8E',
'fg' : '#FFFFFF'
}
},
{
'id' : 'chat',
'title' : 'Chat',

View File

@ -0,0 +1 @@
export * from './router.action';

View File

@ -0,0 +1,27 @@
import { Action } from '@ngrx/store';
import { NavigationExtras } from '@angular/router';
export const GO = '[Router] Go';
export const BACK = '[Router] Back';
export const FORWARD = '[Router] Forward';
export class Go implements Action {
readonly type = GO;
constructor(
public payload: {
path: any[];
query?: object;
extras?: NavigationExtras;
}
) {}
}
export class Back implements Action {
readonly type = BACK;
}
export class Forward implements Action {
readonly type = FORWARD;
}
export type Actions = Go | Back | Forward;

View File

@ -0,0 +1,5 @@
import { RouterEffects } from './router.effect';
export const effects: any[] = [RouterEffects];
export * from './router.effect';

View File

@ -0,0 +1,35 @@
import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { Location } from '@angular/common';
import { Effect, Actions } from '@ngrx/effects';
import * as RouterActions from '../actions/router.action';
import { tap, map } from 'rxjs/operators';
@Injectable()
export class RouterEffects {
constructor(
private actions$: Actions,
private router: Router,
private location: Location
) {}
@Effect({ dispatch: false })
navigate$ = this.actions$.ofType(RouterActions.GO).pipe(
map((action: RouterActions.Go) => action.payload),
tap(({ path, query: queryParams, extras }) => {
this.router.navigate(path, { queryParams, ...extras });
})
);
@Effect({ dispatch: false })
navigateBack$ = this.actions$
.ofType(RouterActions.BACK)
.pipe(tap(() => this.location.back()));
@Effect({ dispatch: false })
navigateForward$ = this.actions$
.ofType(RouterActions.FORWARD)
.pipe(tap(() => this.location.forward()));
}

3
src/app/store/index.ts Normal file
View File

@ -0,0 +1,3 @@
export * from './reducers';
export * from './actions';
export * from './effects';

View File

@ -0,0 +1,42 @@
import {
ActivatedRouteSnapshot,
RouterStateSnapshot,
Params,
} from '@angular/router';
import { createFeatureSelector, ActionReducerMap } from '@ngrx/store';
import * as fromRouter from '@ngrx/router-store';
export interface RouterStateUrl {
url: string;
queryParams: Params;
params: Params;
}
export interface State {
routerReducer: fromRouter.RouterReducerState<RouterStateUrl>;
}
export const reducers: ActionReducerMap<State> = {
routerReducer: fromRouter.routerReducer,
};
export const getRouterState = createFeatureSelector<
fromRouter.RouterReducerState<RouterStateUrl>
>('routerReducer');
export class CustomSerializer
implements fromRouter.RouterStateSerializer<RouterStateUrl> {
serialize(routerState: RouterStateSnapshot): RouterStateUrl {
const { url } = routerState;
const { queryParams } = routerState.root;
let state: ActivatedRouteSnapshot = routerState.root;
while (state.firstChild) {
state = state.firstChild;
}
const { params } = state;
return { url, queryParams, params };
}
}

View File

@ -0,0 +1,31 @@
import { StoreDevtoolsModule } from '@ngrx/store-devtools';
import { EffectsModule } from '@ngrx/effects';
import { MetaReducer, StoreModule } from '@ngrx/store';
import { NgModule } from '@angular/core';
import { storeFreeze } from 'ngrx-store-freeze';
import { environment } from '../../environments/environment';
import { RouterStateSerializer, StoreRouterConnectingModule } from '@ngrx/router-store';
import { reducers, effects, CustomSerializer } from './index';
export const metaReducers: MetaReducer<any>[] = !environment.production
? [storeFreeze]
: [];
@NgModule({
imports : [
StoreModule.forRoot(reducers, {metaReducers}),
EffectsModule.forRoot(effects),
!environment.production ? StoreDevtoolsModule.instrument() : [],
StoreRouterConnectingModule
],
providers: [
{
provide : RouterStateSerializer,
useClass: CustomSerializer
}
]
})
export class AppStoreModule
{
}