diff --git a/package-lock.json b/package-lock.json
index a7510273..05549643 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -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",
diff --git a/package.json b/package.json
index 2050af8f..a7296b1a 100644
--- a/package.json
+++ b/package.json
@@ -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",
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index ecd785dd..eddf8dcd 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -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,
diff --git a/src/app/main/content/apps/mail-ngrx/dialogs/compose/compose.component.html b/src/app/main/content/apps/mail-ngrx/dialogs/compose/compose.component.html
new file mode 100644
index 00000000..26e46f24
--- /dev/null
+++ b/src/app/main/content/apps/mail-ngrx/dialogs/compose/compose.component.html
@@ -0,0 +1,111 @@
+
+
+
+ New Message
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/app/main/content/apps/mail-ngrx/dialogs/compose/compose.component.scss b/src/app/main/content/apps/mail-ngrx/dialogs/compose/compose.component.scss
new file mode 100644
index 00000000..87d41f50
--- /dev/null
+++ b/src/app/main/content/apps/mail-ngrx/dialogs/compose/compose.component.scss
@@ -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;
+ }
+}
diff --git a/src/app/main/content/apps/mail-ngrx/dialogs/compose/compose.component.ts b/src/app/main/content/apps/mail-ngrx/dialogs/compose/compose.component.ts
new file mode 100644
index 00000000..a5ae8286
--- /dev/null
+++ b/src/app/main/content/apps/mail-ngrx/dialogs/compose/compose.component.ts
@@ -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,
+ @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: ['']
+ });
+
+ }
+
+}
diff --git a/src/app/main/content/apps/mail-ngrx/i18n/en.ts b/src/app/main/content/apps/mail-ngrx/i18n/en.ts
new file mode 100644
index 00000000..1e3b4b34
--- /dev/null
+++ b/src/app/main/content/apps/mail-ngrx/i18n/en.ts
@@ -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'
+ }
+ }
+};
diff --git a/src/app/main/content/apps/mail-ngrx/i18n/tr.ts b/src/app/main/content/apps/mail-ngrx/i18n/tr.ts
new file mode 100644
index 00000000..03a524de
--- /dev/null
+++ b/src/app/main/content/apps/mail-ngrx/i18n/tr.ts
@@ -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'
+ }
+ }
+};
diff --git a/src/app/main/content/apps/mail-ngrx/mail-details/mail-details.component.html b/src/app/main/content/apps/mail-ngrx/mail-details/mail-details.component.html
new file mode 100644
index 00000000..765aca1c
--- /dev/null
+++ b/src/app/main/content/apps/mail-ngrx/mail-details/mail-details.component.html
@@ -0,0 +1,140 @@
+
+
+ email
+
+
+ {{ 'MAIL.SELECT_A_MESSAGE_TO_READ' | translate }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{mail.from.name[0]}}
+
+
+
+
+
+
+ {{mail.from.name}}
+
+
+
+
to
+
{{mail.to[0].name}}
+
+
+
+
+
+ Show Details
+ Hide Details
+
+
+
+
+
+ From:
+ To:
+ Date:
+
+
+
+ {{mail.from.email}}
+ {{mail.to[0].email}}
+ {{mail.time}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Attachments
+ ({{mail.attachments.length}})
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/app/main/content/apps/mail-ngrx/mail-details/mail-details.component.scss b/src/app/main/content/apps/mail-ngrx/mail-details/mail-details.component.scss
new file mode 100644
index 00000000..eaabb60a
--- /dev/null
+++ b/src/app/main/content/apps/mail-ngrx/mail-details/mail-details.component.scss
@@ -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;
+ }
+ }
+ }
+}
diff --git a/src/app/main/content/apps/mail-ngrx/mail-details/mail-details.component.ts b/src/app/main/content/apps/mail-ngrx/mail-details/mail-details.component.ts
new file mode 100644
index 00000000..f8bbe42e
--- /dev/null
+++ b/src/app/main/content/apps/mail-ngrx/mail-details/mail-details.component.ts
@@ -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;
+ @Input('mail') mailInput: Mail;
+ mail: Mail;
+ showDetails = false;
+
+ constructor(
+ private mailService: MailNgrxService,
+ private store: Store
+ )
+ {
+ 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()
+ {
+ }
+}
diff --git a/src/app/main/content/apps/mail-ngrx/mail-list/mail-list-item/mail-list-item.component.html b/src/app/main/content/apps/mail-ngrx/mail-list/mail-list-item/mail-list-item.component.html
new file mode 100644
index 00000000..80841ec0
--- /dev/null
+++ b/src/app/main/content/apps/mail-ngrx/mail-list/mail-list-item/mail-list-item.component.html
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
+
+
+
{{mail.from?.name[0]}}
+
{{mail.from?.name}}
+
attachment
+
+
+
+
+
+
+
+
+
+
+
+ {{mail.subject}}
+
+
+
+ {{mail.message | htmlToPlaintext | slice:0:180}}{{mail.message.length > 180 ? '...' : ''}}
+
+
+
+
+
+
{{(labels$ | async) | getById:labelId:'title'}}
+
+
+
+
+
+
+
diff --git a/src/app/main/content/apps/mail-ngrx/mail-list/mail-list-item/mail-list-item.component.scss b/src/app/main/content/apps/mail-ngrx/mail-list/mail-list-item/mail-list-item.component.scss
new file mode 100644
index 00000000..45676aff
--- /dev/null
+++ b/src/app/main/content/apps/mail-ngrx/mail-list/mail-list-item/mail-list-item.component.scss
@@ -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%;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/app/main/content/apps/mail-ngrx/mail-list/mail-list-item/mail-list-item.component.ts b/src/app/main/content/apps/mail-ngrx/mail-list/mail-list-item/mail-list-item.component.ts
new file mode 100644
index 00000000..d4a592bc
--- /dev/null
+++ b/src/app/main/content/apps/mail-ngrx/mail-list/mail-list-item/mail-list-item.component.ts
@@ -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;
+ selectedMailIds$: Observable;
+
+ constructor(
+ private mailService: MailNgrxService,
+ private store: Store,
+ 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()
+ {
+ }
+}
diff --git a/src/app/main/content/apps/mail-ngrx/mail-list/mail-list.component.html b/src/app/main/content/apps/mail-ngrx/mail-list/mail-list.component.html
new file mode 100644
index 00000000..bb5903db
--- /dev/null
+++ b/src/app/main/content/apps/mail-ngrx/mail-list/mail-list.component.html
@@ -0,0 +1,9 @@
+
+ {{ 'MAIL.NO_MESSAGES' | translate }}
+
+
+
+
+
+
diff --git a/src/app/main/content/apps/mail-ngrx/mail-list/mail-list.component.scss b/src/app/main/content/apps/mail-ngrx/mail-list/mail-list.component.scss
new file mode 100644
index 00000000..440b75aa
--- /dev/null
+++ b/src/app/main/content/apps/mail-ngrx/mail-list/mail-list.component.scss
@@ -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;
+ }
+}
diff --git a/src/app/main/content/apps/mail-ngrx/mail-list/mail-list.component.ts b/src/app/main/content/apps/mail-ngrx/mail-list/mail-list.component.ts
new file mode 100644
index 00000000..a4baa16e
--- /dev/null
+++ b/src/app/main/content/apps/mail-ngrx/mail-list/mail-list.component.ts
@@ -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()
+ {
+ }
+}
diff --git a/src/app/main/content/apps/mail-ngrx/mail.component.html b/src/app/main/content/apps/mail-ngrx/mail.component.html
new file mode 100644
index 00000000..94c74e97
--- /dev/null
+++ b/src/app/main/content/apps/mail-ngrx/mail.component.html
@@ -0,0 +1,116 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/app/main/content/apps/mail-ngrx/mail.component.scss b/src/app/main/content/apps/mail-ngrx/mail.component.scss
new file mode 100644
index 00000000..9a355577
--- /dev/null
+++ b/src/app/main/content/apps/mail-ngrx/mail.component.scss
@@ -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;
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
diff --git a/src/app/main/content/apps/mail-ngrx/mail.component.ts b/src/app/main/content/apps/mail-ngrx/mail.component.ts
new file mode 100644
index 00000000..4238af45
--- /dev/null
+++ b/src/app/main/content/apps/mail-ngrx/mail.component.ts
@@ -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;
+ folders$: Observable;
+ labels$: Observable;
+ currentMail$: Observable;
+ selectedMailIds$: Observable;
+ searchText$: Observable;
+ mails: Mail[];
+ selectedMailIds: string[];
+
+ constructor(
+ private mailService: MailNgrxService,
+ private translationLoader: FuseTranslationLoaderService,
+ private store: Store,
+ 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();
+ }
+}
diff --git a/src/app/main/content/apps/mail-ngrx/mail.model.ts b/src/app/main/content/apps/mail-ngrx/mail.model.ts
new file mode 100644
index 00000000..42ff6cf5
--- /dev/null
+++ b/src/app/main/content/apps/mail-ngrx/mail.model.ts
@@ -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;
+ }
+}
diff --git a/src/app/main/content/apps/mail-ngrx/mail.module.ts b/src/app/main/content/apps/mail-ngrx/mail.module.ts
new file mode 100644
index 00000000..e3f3a2fc
--- /dev/null
+++ b/src/app/main/content/apps/mail-ngrx/mail.module.ts
@@ -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
+{
+}
diff --git a/src/app/main/content/apps/mail-ngrx/mail.service.ts b/src/app/main/content/apps/mail-ngrx/mail.service.ts
new file mode 100644
index 00000000..e02c78dd
--- /dev/null
+++ b/src/app/main/content/apps/mail-ngrx/mail.service.ts
@@ -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
+ )
+ {
+ 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
+ {
+ return this.http.get('api/mail-mails');
+ }
+
+ getFolders(): Observable
+ {
+ return this.http.get('api/mail-folders');
+ }
+
+ getFilters(): Observable
+ {
+ return this.http.get('api/mail-filters');
+ }
+
+ getLabels(): Observable
+ {
+ return this.http.get('api/mail-labels');
+ }
+
+ getMails(handle): Observable
+ {
+ if ( handle.id === 'labelHandle' )
+ {
+ const labelId = this.labelsArr.find(label => label.handle === handle.value).id;
+ return this.http.get('api/mail-mails?labels=' + labelId);
+ }
+ else if ( handle.id === 'filterHandle' )
+ {
+ return this.http.get('api/mail-mails?' + handle.value + '=true');
+ }
+ else // folderHandle
+ {
+ const folderId = this.foldersArr.find(folder => folder.handle === handle.value).id;
+ return this.http.get('api/mail-mails?folder=' + folderId);
+ }
+ }
+
+ /**
+ * Update the mail
+ * @param mail
+ * @returns {Promise}
+ */
+ updateMail(mail)
+ {
+ return this.http.post('api/mail-mails/' + mail.id, {...mail});
+ }
+}
diff --git a/src/app/main/content/apps/mail-ngrx/sidenavs/main/main-sidenav.component.html b/src/app/main/content/apps/mail-ngrx/sidenavs/main/main-sidenav.component.html
new file mode 100644
index 00000000..826f5e7d
--- /dev/null
+++ b/src/app/main/content/apps/mail-ngrx/sidenavs/main/main-sidenav.component.html
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/app/main/content/apps/mail-ngrx/sidenavs/main/main-sidenav.component.scss b/src/app/main/content/apps/mail-ngrx/sidenavs/main/main-sidenav.component.scss
new file mode 100644
index 00000000..633e6083
--- /dev/null
+++ b/src/app/main/content/apps/mail-ngrx/sidenavs/main/main-sidenav.component.scss
@@ -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 {
+ }
+ }
+}
diff --git a/src/app/main/content/apps/mail-ngrx/sidenavs/main/main-sidenav.component.ts b/src/app/main/content/apps/mail-ngrx/sidenavs/main/main-sidenav.component.ts
new file mode 100644
index 00000000..b261d20a
--- /dev/null
+++ b/src/app/main/content/apps/mail-ngrx/sidenavs/main/main-sidenav.component.ts
@@ -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;
+ filters$: Observable;
+ labels$: Observable;
+
+ constructor(
+ private mailService: MailNgrxService,
+ public dialog: MatDialog,
+ private store: Store
+ )
+ {
+ // 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()
+ {
+ }
+}
diff --git a/src/app/main/content/apps/mail-ngrx/store/actions/filters.actions.ts b/src/app/main/content/apps/mail-ngrx/store/actions/filters.actions.ts
new file mode 100644
index 00000000..eb404a2f
--- /dev/null
+++ b/src/app/main/content/apps/mail-ngrx/store/actions/filters.actions.ts
@@ -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;
diff --git a/src/app/main/content/apps/mail-ngrx/store/actions/folders.actions.ts b/src/app/main/content/apps/mail-ngrx/store/actions/folders.actions.ts
new file mode 100644
index 00000000..c797e2af
--- /dev/null
+++ b/src/app/main/content/apps/mail-ngrx/store/actions/folders.actions.ts
@@ -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;
diff --git a/src/app/main/content/apps/mail-ngrx/store/actions/index.ts b/src/app/main/content/apps/mail-ngrx/store/actions/index.ts
new file mode 100644
index 00000000..5558d987
--- /dev/null
+++ b/src/app/main/content/apps/mail-ngrx/store/actions/index.ts
@@ -0,0 +1,4 @@
+export * from './mails.actions';
+export * from './folders.actions';
+export * from './filters.actions';
+export * from './labels.actions';
diff --git a/src/app/main/content/apps/mail-ngrx/store/actions/labels.actions.ts b/src/app/main/content/apps/mail-ngrx/store/actions/labels.actions.ts
new file mode 100644
index 00000000..03ac32af
--- /dev/null
+++ b/src/app/main/content/apps/mail-ngrx/store/actions/labels.actions.ts
@@ -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;
diff --git a/src/app/main/content/apps/mail-ngrx/store/actions/mails.actions.ts b/src/app/main/content/apps/mail-ngrx/store/actions/mails.actions.ts
new file mode 100644
index 00000000..007c39d5
--- /dev/null
+++ b/src/app/main/content/apps/mail-ngrx/store/actions/mails.actions.ts
@@ -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;
diff --git a/src/app/main/content/apps/mail-ngrx/store/effects/filters.effects.ts b/src/app/main/content/apps/mail-ngrx/store/effects/filters.effects.ts
new file mode 100644
index 00000000..2a073f88
--- /dev/null
+++ b/src/app/main/content/apps/mail-ngrx/store/effects/filters.effects.ts
@@ -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}
+ */
+ @Effect()
+ getFilters: Observable =
+ this.actions
+ .ofType(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)))
+ );
+ }
+ ));
+}
diff --git a/src/app/main/content/apps/mail-ngrx/store/effects/folders.effects.ts b/src/app/main/content/apps/mail-ngrx/store/effects/folders.effects.ts
new file mode 100644
index 00000000..a89270c2
--- /dev/null
+++ b/src/app/main/content/apps/mail-ngrx/store/effects/folders.effects.ts
@@ -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}
+ */
+ @Effect()
+ getFolders: Observable =
+ this.actions
+ .ofType(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)))
+ );
+ }
+ ));
+}
diff --git a/src/app/main/content/apps/mail-ngrx/store/effects/index.ts b/src/app/main/content/apps/mail-ngrx/store/effects/index.ts
new file mode 100644
index 00000000..a4c99efb
--- /dev/null
+++ b/src/app/main/content/apps/mail-ngrx/store/effects/index.ts
@@ -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';
diff --git a/src/app/main/content/apps/mail-ngrx/store/effects/labels.effects.ts b/src/app/main/content/apps/mail-ngrx/store/effects/labels.effects.ts
new file mode 100644
index 00000000..24d4b3f2
--- /dev/null
+++ b/src/app/main/content/apps/mail-ngrx/store/effects/labels.effects.ts
@@ -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}
+ */
+ @Effect()
+ getLabels: Observable =
+ this.actions
+ .ofType(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)))
+ );
+ }
+ ));
+}
diff --git a/src/app/main/content/apps/mail-ngrx/store/effects/mails.effects.ts b/src/app/main/content/apps/mail-ngrx/store/effects/mails.effects.ts
new file mode 100644
index 00000000..c88a9138
--- /dev/null
+++ b/src/app/main/content/apps/mail-ngrx/store/effects/mails.effects.ts
@@ -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
+ )
+ {
+ this.store.select(getRouterState).subscribe(routerState => {
+ if ( routerState )
+ {
+ this.routerState = routerState.state;
+ }
+ });
+ }
+
+ /**
+ * Get Mails with router parameters
+ * @type {Observable}
+ */
+ @Effect()
+ getMails: Observable =
+ this.actions
+ .ofType(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}
+ */
+ @Effect()
+ updateMail: Observable =
+ this.actions
+ .ofType(MailsActions.UPDATE_MAIL)
+ .pipe(
+ exhaustMap((action) => {
+ return this.mailService.updateMail(action.payload)
+ .map(() => {
+ return new MailsActions.UpdateMailSuccess(action.payload);
+ });
+ })
+ );
+
+ /**
+ * UpdateMails
+ * @type {Observable}
+ */
+ @Effect()
+ updateMails: Observable =
+ this.actions
+ .ofType(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}
+ */
+ @Effect()
+ setCurrentMail: Observable =
+ this.actions
+ .ofType(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}
+ */
+ @Effect()
+ checkCurrentMail: Observable =
+ this.actions
+ .ofType(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}
+ */
+ @Effect()
+ getMailsSuccess: Observable =
+ this.actions
+ .ofType(MailsActions.GET_MAILS_SUCCESS)
+ .pipe(
+ mergeMap(() =>
+ [
+ new MailsActions.CheckCurrentMail()
+ ])
+ );
+ /**
+ * On Update Mails Success
+ * @type {Observable}
+ */
+ @Effect()
+ updateMailsSuccess: Observable =
+ this.actions
+ .ofType(MailsActions.UPDATE_MAILS_SUCCESS)
+ .pipe(
+ mergeMap(() =>
+ [
+ new MailsActions.DeselectAllMails(),
+ new MailsActions.GetMails()
+ ])
+ );
+ /**
+ * On Update Mail Success
+ * @type {Observable}
+ */
+ @Effect()
+ updateMailSuccess: Observable =
+ this.actions
+ .ofType(MailsActions.UPDATE_MAIL_SUCCESS)
+ .debounceTime(500)
+ .pipe(
+ map(() => {
+ return new MailsActions.GetMails();
+ })
+ );
+
+ /**
+ * Set Folder on Selected Mails
+ * @type {Observable}
+ */
+ @Effect()
+ setFolderOnSelectedMails: Observable =
+ this.actions
+ .ofType(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}
+ */
+ @Effect()
+ addLabelOnSelectedMails: Observable =
+ this.actions
+ .ofType(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);
+ })
+ );
+}
diff --git a/src/app/main/content/apps/mail-ngrx/store/guards/index.ts b/src/app/main/content/apps/mail-ngrx/store/guards/index.ts
new file mode 100644
index 00000000..1e38e558
--- /dev/null
+++ b/src/app/main/content/apps/mail-ngrx/store/guards/index.ts
@@ -0,0 +1 @@
+export * from './resolve.guard';
diff --git a/src/app/main/content/apps/mail-ngrx/store/guards/resolve.guard.ts b/src/app/main/content/apps/mail-ngrx/store/guards/resolve.guard.ts
new file mode 100644
index 00000000..46c35ebd
--- /dev/null
+++ b/src/app/main/content/apps/mail-ngrx/store/guards/resolve.guard.ts
@@ -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
+ )
+ {
+ this.store.select(getRouterState).subscribe(routerState => {
+ if ( routerState )
+ {
+ this.routerState = routerState.state;
+ }
+ });
+ }
+
+ canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable
+ {
+ return this.checkStore().pipe(
+ switchMap(() => of(true)),
+ catchError(() => of(false))
+ );
+ }
+
+ checkStore(): Observable
+ {
+ 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}
+ */
+ 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}
+ */
+ 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}
+ */
+ 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)
+ );
+ }
+}
diff --git a/src/app/main/content/apps/mail-ngrx/store/index.ts b/src/app/main/content/apps/mail-ngrx/store/index.ts
new file mode 100644
index 00000000..9bab4e30
--- /dev/null
+++ b/src/app/main/content/apps/mail-ngrx/store/index.ts
@@ -0,0 +1,4 @@
+export * from './actions';
+export * from './reducers';
+export * from './selectors';
+export * from './effects';
diff --git a/src/app/main/content/apps/mail-ngrx/store/reducers/filters.reducer.ts b/src/app/main/content/apps/mail-ngrx/store/reducers/filters.reducer.ts
new file mode 100644
index 00000000..eef4eb5f
--- /dev/null
+++ b/src/app/main/content/apps/mail-ngrx/store/reducers/filters.reducer.ts
@@ -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;
+ }
+}
diff --git a/src/app/main/content/apps/mail-ngrx/store/reducers/folders.reducer.ts b/src/app/main/content/apps/mail-ngrx/store/reducers/folders.reducer.ts
new file mode 100644
index 00000000..91efb30e
--- /dev/null
+++ b/src/app/main/content/apps/mail-ngrx/store/reducers/folders.reducer.ts
@@ -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;
+ }
+}
diff --git a/src/app/main/content/apps/mail-ngrx/store/reducers/index.ts b/src/app/main/content/apps/mail-ngrx/store/reducers/index.ts
new file mode 100644
index 00000000..32c38497
--- /dev/null
+++ b/src/app/main/content/apps/mail-ngrx/store/reducers/index.ts
@@ -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(
+ 'mail-app'
+);
+
+export const getAppState = createSelector(
+ getMailAppState,
+ (state: MailAppState) => state
+);
+
+export const reducers: ActionReducerMap = {
+ mails : MailsReducer,
+ folders: FoldersReducer,
+ filters: FiltersReducer,
+ labels : LabelsReducer
+};
+
+export * from './mails.reducer';
+export * from './folders.reducer';
+export * from './filters.reducer';
+export * from './labels.reducer';
diff --git a/src/app/main/content/apps/mail-ngrx/store/reducers/labels.reducer.ts b/src/app/main/content/apps/mail-ngrx/store/reducers/labels.reducer.ts
new file mode 100644
index 00000000..147b4f3c
--- /dev/null
+++ b/src/app/main/content/apps/mail-ngrx/store/reducers/labels.reducer.ts
@@ -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;
+ }
+}
diff --git a/src/app/main/content/apps/mail-ngrx/store/reducers/mails.reducer.ts b/src/app/main/content/apps/mail-ngrx/store/reducers/mails.reducer.ts
new file mode 100644
index 00000000..9095d7c4
--- /dev/null
+++ b/src/app/main/content/apps/mail-ngrx/store/reducers/mails.reducer.ts
@@ -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;
+ }
+}
diff --git a/src/app/main/content/apps/mail-ngrx/store/selectors/filters.selectors.ts b/src/app/main/content/apps/mail-ngrx/store/selectors/filters.selectors.ts
new file mode 100644
index 00000000..5b740ff8
--- /dev/null
+++ b/src/app/main/content/apps/mail-ngrx/store/selectors/filters.selectors.ts
@@ -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])
+);
diff --git a/src/app/main/content/apps/mail-ngrx/store/selectors/folders.selectors.ts b/src/app/main/content/apps/mail-ngrx/store/selectors/folders.selectors.ts
new file mode 100644
index 00000000..814f0113
--- /dev/null
+++ b/src/app/main/content/apps/mail-ngrx/store/selectors/folders.selectors.ts
@@ -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])
+);
diff --git a/src/app/main/content/apps/mail-ngrx/store/selectors/index.ts b/src/app/main/content/apps/mail-ngrx/store/selectors/index.ts
new file mode 100644
index 00000000..7e73a9a4
--- /dev/null
+++ b/src/app/main/content/apps/mail-ngrx/store/selectors/index.ts
@@ -0,0 +1,4 @@
+export * from './mails.selectors';
+export * from './folders.selectors';
+export * from './filters.selectors';
+export * from './labels.selectors';
diff --git a/src/app/main/content/apps/mail-ngrx/store/selectors/labels.selectors.ts b/src/app/main/content/apps/mail-ngrx/store/selectors/labels.selectors.ts
new file mode 100644
index 00000000..c5ae21f5
--- /dev/null
+++ b/src/app/main/content/apps/mail-ngrx/store/selectors/labels.selectors.ts
@@ -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])
+);
diff --git a/src/app/main/content/apps/mail-ngrx/store/selectors/mails.selectors.ts b/src/app/main/content/apps/mail-ngrx/store/selectors/mails.selectors.ts
new file mode 100644
index 00000000..2830a57f
--- /dev/null
+++ b/src/app/main/content/apps/mail-ngrx/store/selectors/mails.selectors.ts
@@ -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
+);
diff --git a/src/app/main/content/apps/mail-ngrx/store/store.module.ts b/src/app/main/content/apps/mail-ngrx/store/store.module.ts
new file mode 100644
index 00000000..32b0a5d0
--- /dev/null
+++ b/src/app/main/content/apps/mail-ngrx/store/store.module.ts
@@ -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
+{
+}
diff --git a/src/app/navigation/i18n/en.ts b/src/app/navigation/i18n/en.ts
index 57caa4bb..e6a6da10 100644
--- a/src/app/navigation/i18n/en.ts
+++ b/src/app/navigation/i18n/en.ts
@@ -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',
diff --git a/src/app/navigation/i18n/tr.ts b/src/app/navigation/i18n/tr.ts
index 629ea1a6..7eb06307 100644
--- a/src/app/navigation/i18n/tr.ts
+++ b/src/app/navigation/i18n/tr.ts
@@ -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',
diff --git a/src/app/navigation/navigation.model.ts b/src/app/navigation/navigation.model.ts
index 544008ef..074c33b8 100644
--- a/src/app/navigation/navigation.model.ts
+++ b/src/app/navigation/navigation.model.ts
@@ -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',
diff --git a/src/app/store/actions/index.ts b/src/app/store/actions/index.ts
new file mode 100644
index 00000000..baf6af0e
--- /dev/null
+++ b/src/app/store/actions/index.ts
@@ -0,0 +1 @@
+export * from './router.action';
diff --git a/src/app/store/actions/router.action.ts b/src/app/store/actions/router.action.ts
new file mode 100644
index 00000000..f766a06e
--- /dev/null
+++ b/src/app/store/actions/router.action.ts
@@ -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;
diff --git a/src/app/store/effects/index.ts b/src/app/store/effects/index.ts
new file mode 100644
index 00000000..8bf11b61
--- /dev/null
+++ b/src/app/store/effects/index.ts
@@ -0,0 +1,5 @@
+import { RouterEffects } from './router.effect';
+
+export const effects: any[] = [RouterEffects];
+
+export * from './router.effect';
diff --git a/src/app/store/effects/router.effect.ts b/src/app/store/effects/router.effect.ts
new file mode 100644
index 00000000..90b9f5f7
--- /dev/null
+++ b/src/app/store/effects/router.effect.ts
@@ -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()));
+}
diff --git a/src/app/store/index.ts b/src/app/store/index.ts
new file mode 100644
index 00000000..115467ce
--- /dev/null
+++ b/src/app/store/index.ts
@@ -0,0 +1,3 @@
+export * from './reducers';
+export * from './actions';
+export * from './effects';
diff --git a/src/app/store/reducers/index.ts b/src/app/store/reducers/index.ts
new file mode 100644
index 00000000..59cd70be
--- /dev/null
+++ b/src/app/store/reducers/index.ts
@@ -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;
+}
+
+export const reducers: ActionReducerMap = {
+ routerReducer: fromRouter.routerReducer,
+};
+
+export const getRouterState = createFeatureSelector<
+ fromRouter.RouterReducerState
+>('routerReducer');
+
+export class CustomSerializer
+ implements fromRouter.RouterStateSerializer {
+ 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 };
+ }
+}
diff --git a/src/app/store/store.module.ts b/src/app/store/store.module.ts
new file mode 100644
index 00000000..29e51c04
--- /dev/null
+++ b/src/app/store/store.module.ts
@@ -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[] = !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
+{
+}