mirror of
https://github.com/richard-loafle/fuse-angular.git
synced 2025-12-22 22:37:06 +00:00
Compare commits
38 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5f2372cc08 | ||
|
|
6f315aa38e | ||
|
|
0653b5f36b | ||
|
|
f13120bc01 | ||
|
|
a6c56518bc | ||
|
|
ecae48f3d0 | ||
|
|
c74751e0f4 | ||
|
|
2b755fa669 | ||
|
|
ca0f46b414 | ||
|
|
528fa31df6 | ||
|
|
abfb2a6706 | ||
|
|
b1ab11393e | ||
|
|
786883eb10 | ||
|
|
e477f797d0 | ||
|
|
fb196c3864 | ||
|
|
5cf44962fc | ||
|
|
06b0c3775a | ||
|
|
26690990f0 | ||
|
|
377092d9ec | ||
|
|
abede386c8 | ||
|
|
242feaa169 | ||
|
|
7c2494a82c | ||
|
|
5c2e717a40 | ||
|
|
6ae0a9760d | ||
|
|
2a10f3e443 | ||
|
|
db7a00440c | ||
|
|
0e1c589399 | ||
|
|
2f419b1af5 | ||
|
|
effd3cefcb | ||
|
|
21fd488a8e | ||
|
|
37a5c69269 | ||
|
|
9f440b1bf2 | ||
|
|
a65f61cce4 | ||
|
|
0d8fe0be72 | ||
|
|
2bbc90af64 | ||
|
|
ad21d9fed5 | ||
|
|
4c6ef29e20 | ||
|
|
a74c5108fd |
1097
package-lock.json
generated
1097
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
69
package.json
69
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "fuse2",
|
||||
"version": "1.2.3",
|
||||
"license": "",
|
||||
"version": "1.3.1",
|
||||
"license": "https://themeforest.net/licenses/terms/regular",
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
"start": "ng serve",
|
||||
@@ -16,56 +16,61 @@
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@agm/core": "1.0.0-beta.2",
|
||||
"@angular/animations": "5.0.3",
|
||||
"@angular/cdk": "5.0.0-rc.1",
|
||||
"@angular/common": "5.0.3",
|
||||
"@angular/compiler": "5.0.3",
|
||||
"@angular/core": "5.0.3",
|
||||
"@angular/flex-layout": "2.0.0-beta.10-4905443",
|
||||
"@angular/forms": "5.0.3",
|
||||
"@angular/http": "5.0.3",
|
||||
"@angular/material": "5.0.0-rc.1",
|
||||
"@angular/material-moment-adapter": "5.0.0-rc.1",
|
||||
"@angular/platform-browser": "5.0.3",
|
||||
"@angular/platform-browser-dynamic": "5.0.3",
|
||||
"@angular/router": "5.0.3",
|
||||
"@ngx-translate/core": "8.0.0",
|
||||
"@angular/animations": "5.1.2",
|
||||
"@angular/cdk": "5.0.2",
|
||||
"@angular/common": "5.1.2",
|
||||
"@angular/compiler": "5.1.2",
|
||||
"@angular/core": "5.1.2",
|
||||
"@angular/flex-layout": "2.0.0-beta.12",
|
||||
"@angular/forms": "5.1.2",
|
||||
"@angular/http": "5.1.2",
|
||||
"@angular/material": "5.0.2",
|
||||
"@angular/material-moment-adapter": "5.0.2",
|
||||
"@angular/platform-browser": "5.1.2",
|
||||
"@angular/platform-browser-dynamic": "5.1.2",
|
||||
"@angular/router": "5.1.2",
|
||||
"@ngx-translate/core": "9.0.2",
|
||||
"@swimlane/ngx-charts": "7.0.1",
|
||||
"@swimlane/ngx-datatable": "11.1.4",
|
||||
"@withinpixels/ngx-dnd": "3.1.0",
|
||||
"angular-calendar": "0.22.1",
|
||||
"angular-in-memory-web-api": "0.5.1",
|
||||
"@swimlane/ngx-datatable": "11.1.7",
|
||||
"@swimlane/ngx-dnd": "3.1.0",
|
||||
"angular-calendar": "0.23.0",
|
||||
"angular-in-memory-web-api": "0.5.2",
|
||||
"angular2-markdown": "1.6.0",
|
||||
"classlist.js": "1.1.20150312",
|
||||
"core-js": "2.5.1",
|
||||
"core-js": "2.5.3",
|
||||
"d3": "4.12.0",
|
||||
"hammerjs": "2.0.8",
|
||||
"highlight.js": "9.12.0",
|
||||
"intl": "1.2.5",
|
||||
"moment": "2.19.2",
|
||||
"ngx-color-picker": "5.0.4",
|
||||
"moment": "2.20.1",
|
||||
"@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.3.0",
|
||||
"ngx-cookie-service": "1.0.9",
|
||||
"perfect-scrollbar": "1.2.0",
|
||||
"rxjs": "5.5.2",
|
||||
"perfect-scrollbar": "1.3.0",
|
||||
"rxjs": "5.5.6",
|
||||
"web-animations-js": "2.3.1",
|
||||
"zone.js": "0.8.18"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular/cli": "1.5.4",
|
||||
"@angular/compiler-cli": "5.0.3",
|
||||
"@angular/language-service": "5.0.3",
|
||||
"@angular/cli": "1.6.2",
|
||||
"@angular/compiler-cli": "5.1.2",
|
||||
"@angular/language-service": "5.1.2",
|
||||
"@angularclass/hmr": "2.1.3",
|
||||
"@types/jasmine": "2.5.54",
|
||||
"@types/jasminewd2": "2.0.3",
|
||||
"@types/node": "6.0.92",
|
||||
"codelyzer": "4.0.1",
|
||||
"@types/node": "6.0.95",
|
||||
"codelyzer": "4.0.2",
|
||||
"jasmine-core": "2.6.4",
|
||||
"jasmine-spec-reporter": "4.1.1",
|
||||
"karma": "1.7.1",
|
||||
"karma-chrome-launcher": "2.1.1",
|
||||
"karma-cli": "1.0.1",
|
||||
"karma-coverage-istanbul-reporter": "1.3.0",
|
||||
"karma-jasmine": "1.1.0",
|
||||
"karma-coverage-istanbul-reporter": "1.3.1",
|
||||
"karma-jasmine": "1.1.1",
|
||||
"karma-jasmine-html-reporter": "0.2.2",
|
||||
"protractor": "5.1.2",
|
||||
"ts-node": "3.2.2",
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { FuseSplashScreenService } from './core/services/splash-screen.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { FuseTranslationLoaderService } from './core/services/translation-loader.service';
|
||||
|
||||
import { FuseNavigationService } from './core/components/navigation/navigation.service';
|
||||
import { FuseNavigationModel } from './navigation/navigation.model';
|
||||
import { locale as navigationEnglish } from './navigation/i18n/en';
|
||||
import { locale as navigationTurkish } from './navigation/i18n/tr';
|
||||
|
||||
@Component({
|
||||
selector : 'fuse-root',
|
||||
@@ -10,8 +16,10 @@ import { TranslateService } from '@ngx-translate/core';
|
||||
export class AppComponent
|
||||
{
|
||||
constructor(
|
||||
private fuseNavigationService: FuseNavigationService,
|
||||
private fuseSplashScreen: FuseSplashScreenService,
|
||||
private translate: TranslateService
|
||||
private translate: TranslateService,
|
||||
private translationLoader: FuseTranslationLoaderService
|
||||
)
|
||||
{
|
||||
// Add languages
|
||||
@@ -22,5 +30,11 @@ export class AppComponent
|
||||
|
||||
// Use a language
|
||||
this.translate.use('en');
|
||||
|
||||
// Set the navigation model
|
||||
this.fuseNavigationService.setNavigationModel(new FuseNavigationModel());
|
||||
|
||||
// Set the navigation translations
|
||||
this.translationLoader.loadTranslations(navigationEnglish, navigationTurkish);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { HttpModule } from '@angular/http';
|
||||
import { HttpClientModule } from '@angular/common/http';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
@@ -22,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'
|
||||
@@ -40,6 +44,10 @@ const appRoutes: Routes = [
|
||||
path : 'apps/e-commerce',
|
||||
loadChildren: './main/content/apps/e-commerce/e-commerce.module#FuseEcommerceModule'
|
||||
},
|
||||
{
|
||||
path : 'apps/academy',
|
||||
loadChildren: './main/content/apps/academy/academy.module#FuseAcademyModule'
|
||||
},
|
||||
{
|
||||
path : 'apps/todo',
|
||||
loadChildren: './main/content/apps/todo/todo.module#FuseTodoModule'
|
||||
@@ -68,23 +76,19 @@ const appRoutes: Routes = [
|
||||
],
|
||||
imports : [
|
||||
BrowserModule,
|
||||
HttpModule,
|
||||
HttpClientModule,
|
||||
BrowserAnimationsModule,
|
||||
RouterModule.forRoot(appRoutes),
|
||||
SharedModule,
|
||||
MarkdownModule.forRoot(),
|
||||
TranslateModule.forRoot(),
|
||||
|
||||
InMemoryWebApiModule.forRoot(FuseFakeDbService, {
|
||||
delay : 0,
|
||||
passThruUnknownUrl: true
|
||||
}),
|
||||
|
||||
AppStoreModule,
|
||||
FuseMainModule,
|
||||
|
||||
ProjectModule,
|
||||
|
||||
PagesModule,
|
||||
UIModule,
|
||||
ServicesModule,
|
||||
|
||||
@@ -76,6 +76,53 @@ export const fuseAnimations = [
|
||||
transition('0 => 1', animate('300ms ease-in'))
|
||||
]),
|
||||
|
||||
trigger('slideIn', [
|
||||
transition('void => left', [
|
||||
style({
|
||||
transform: 'translateX(100%)'
|
||||
}),
|
||||
animate('300ms ease-in',
|
||||
style({
|
||||
transform: 'translateX(0)'
|
||||
})
|
||||
)
|
||||
]
|
||||
),
|
||||
transition('left => void', [
|
||||
style({
|
||||
transform: 'translateX(0)'
|
||||
}),
|
||||
animate('300ms ease-in',
|
||||
style({
|
||||
transform: 'translateX(-100%)'
|
||||
})
|
||||
)
|
||||
]
|
||||
),
|
||||
transition('void => right', [
|
||||
style({
|
||||
transform: 'translateX(-100%)'
|
||||
}),
|
||||
animate('300ms ease-in',
|
||||
style({
|
||||
transform: 'translateX(0)'
|
||||
})
|
||||
)
|
||||
]
|
||||
),
|
||||
transition('right => void', [
|
||||
style({
|
||||
transform: 'translateX(0)'
|
||||
}),
|
||||
animate('300ms ease-in',
|
||||
style({
|
||||
transform: 'translateX(100%)'
|
||||
})
|
||||
)
|
||||
]
|
||||
),
|
||||
]),
|
||||
|
||||
trigger('slideInLeft', [
|
||||
state('void', style({
|
||||
transform: 'translateX(-100%)',
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
<a class="nav-link" matRipple>
|
||||
<mat-icon class="nav-link-icon" *ngIf="item.icon">{{item.icon}}</mat-icon>
|
||||
<span class="nav-link-title">{{item.title}}</span>
|
||||
<span class="nav-link-title" [translate]="item.translate">{{item.title}}</span>
|
||||
<span class="nav-link-badge" *ngIf="item.badge" [translate]="item.badge.translate"
|
||||
[ngStyle]="{'background-color': item.badge.bg,'color': item.badge.fg}">
|
||||
{{item.badge.title}}
|
||||
</span>
|
||||
<mat-icon class="collapse-arrow">keyboard_arrow_right</mat-icon>
|
||||
</a>
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<a class="nav-link" *ngIf="item.url" [routerLink]="[item.url]" routerLinkActive="active"
|
||||
[routerLinkActiveOptions]="{exact: item.exactMatch || false}" matRipple>
|
||||
<mat-icon class="nav-link-icon" *ngIf="item.icon">{{item.icon}}</mat-icon>
|
||||
<span class="nav-link-title">{{item.title}}</span>
|
||||
<span class="nav-link-badge" *ngIf="item.badge"
|
||||
<span class="nav-link-title" [translate]="item.translate">{{item.title}}</span>
|
||||
<span class="nav-link-badge" *ngIf="item.badge" [translate]="item.badge.translate"
|
||||
[ngStyle]="{'background-color': item.badge.bg,'color': item.badge.fg}">
|
||||
{{item.badge.title}}
|
||||
</span>
|
||||
@@ -10,8 +10,8 @@
|
||||
|
||||
<span class="nav-link" *ngIf="item.function" (click)="item.function()" matRipple>
|
||||
<mat-icon class="nav-link-icon" *ngIf="item.icon">{{item.icon}}</mat-icon>
|
||||
<span class="nav-link-title">{{item.title}}</span>
|
||||
<span class="nav-link-badge" *ngIf="item.badge"
|
||||
<span class="nav-link-title" [translate]="item.translate">{{item.title}}</span>
|
||||
<span class="nav-link-badge" *ngIf="item.badge" [translate]="item.badge.translate"
|
||||
[ngStyle]="{'background-color': item.badge.bg,'color': item.badge.fg}">
|
||||
{{item.badge.title}}
|
||||
</span>
|
||||
|
||||
5
src/app/core/components/navigation/navigation.model.ts
Normal file
5
src/app/core/components/navigation/navigation.model.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export interface FuseNavigationModelInterface
|
||||
{
|
||||
model: any[];
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { EventEmitter, Injectable } from '@angular/core';
|
||||
import { NavigationModel } from '../../../navigation.model';
|
||||
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
|
||||
import { FuseNavigationModelInterface } from './navigation.model';
|
||||
|
||||
@Injectable()
|
||||
export class FuseNavigationService
|
||||
@@ -8,13 +8,11 @@ export class FuseNavigationService
|
||||
onNavCollapseToggle = new EventEmitter<any>();
|
||||
onNavCollapseToggled = new EventEmitter<any>();
|
||||
onNavigationModelChange: BehaviorSubject<any> = new BehaviorSubject({});
|
||||
navigationModel: NavigationModel;
|
||||
navigationModel: FuseNavigationModelInterface;
|
||||
flatNavigation: any[] = [];
|
||||
|
||||
constructor()
|
||||
{
|
||||
this.navigationModel = new NavigationModel();
|
||||
this.onNavigationModelChange.next(this.navigationModel.model);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -155,8 +153,15 @@ export class FuseNavigationService
|
||||
*/
|
||||
getFlatNavigation(navigationItems?)
|
||||
{
|
||||
// If navigation items not provided,
|
||||
// that means we are running the function
|
||||
// for the first time...
|
||||
if ( !navigationItems )
|
||||
{
|
||||
// Reset the flat navigation
|
||||
this.flatNavigation = [];
|
||||
|
||||
// Get the entire navigation model
|
||||
navigationItems = this.navigationModel.model;
|
||||
}
|
||||
|
||||
@@ -181,7 +186,10 @@ export class FuseNavigationService
|
||||
|
||||
if ( navItem.type === 'collapse' || navItem.type === 'group' )
|
||||
{
|
||||
this.getFlatNavigation(navItem.children);
|
||||
if ( navItem.children )
|
||||
{
|
||||
this.getFlatNavigation(navItem.children);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
<a class="nav-link" matRipple (click)="toggleOpen($event)">
|
||||
<mat-icon class="nav-link-icon" *ngIf="item.icon">{{item.icon}}</mat-icon>
|
||||
<span class="nav-link-title">{{item.title}}</span>
|
||||
<span class="nav-link-title" [translate]="item.translate">{{item.title}}</span>
|
||||
<span class="nav-link-badge" *ngIf="item.badge" [translate]="item.badge.translate"
|
||||
[ngStyle]="{'background-color': item.badge.bg,'color': item.badge.fg}">
|
||||
{{item.badge.title}}
|
||||
</span>
|
||||
<mat-icon class="collapse-arrow">keyboard_arrow_right</mat-icon>
|
||||
</a>
|
||||
|
||||
<div class="children" [@slideInOut]="isOpen">
|
||||
<ng-container *ngFor="let item of item.children">
|
||||
<fuse-nav-vertical-item *ngIf="item.type=='item'" [item]="item"></fuse-nav-vertical-item>
|
||||
|
||||
@@ -69,6 +69,20 @@ export class FuseNavVerticalCollapseComponent implements OnInit
|
||||
);
|
||||
}
|
||||
|
||||
ngOnInit()
|
||||
{
|
||||
// Check if the url can be found in
|
||||
// one of the children of this item
|
||||
if ( this.isUrlInChildren(this.item, this.router.url) )
|
||||
{
|
||||
this.expand();
|
||||
}
|
||||
else
|
||||
{
|
||||
this.collapse();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle collapse
|
||||
*
|
||||
@@ -108,6 +122,7 @@ export class FuseNavVerticalCollapseComponent implements OnInit
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this.isOpen = false;
|
||||
this.navigationService.onNavCollapseToggle.emit();
|
||||
}
|
||||
@@ -175,8 +190,4 @@ export class FuseNavVerticalCollapseComponent implements OnInit
|
||||
return false;
|
||||
}
|
||||
|
||||
ngOnInit()
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<div class="group-title">
|
||||
<span class="hint-text">{{ item.title }}</span>
|
||||
<span class="hint-text" [translate]="item.translate">{{ item.title }}</span>
|
||||
</div>
|
||||
|
||||
<div class="group-items">
|
||||
<ng-container *ngFor="let item of item.children">
|
||||
<fuse-nav-vertical-group *ngIf="item.type=='group'" [item]="item"></fuse-nav-vertical-group>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<a class="nav-link" *ngIf="item.url" [routerLink]="[item.url]" routerLinkActive="active"
|
||||
[routerLinkActiveOptions]="{exact: item.exactMatch || false}" matRipple>
|
||||
<mat-icon class="nav-link-icon" *ngIf="item.icon">{{item.icon}}</mat-icon>
|
||||
<span class="nav-link-title">{{item.title}}</span>
|
||||
<span class="nav-link-badge" *ngIf="item.badge"
|
||||
<span class="nav-link-title" [translate]="item.translate">{{item.title}}</span>
|
||||
<span class="nav-link-badge" *ngIf="item.badge" [translate]="item.badge.translate"
|
||||
[ngStyle]="{'background-color': item.badge.bg,'color': item.badge.fg}">
|
||||
{{item.badge.title}}
|
||||
</span>
|
||||
@@ -10,8 +10,8 @@
|
||||
|
||||
<span class="nav-link" *ngIf="item.function" (click)="item.function()" matRipple>
|
||||
<mat-icon class="nav-link-icon" *ngIf="item.icon">{{item.icon}}</mat-icon>
|
||||
<span class="nav-link-title">{{item.title}}</span>
|
||||
<span class="nav-link-badge" *ngIf="item.badge"
|
||||
<span class="nav-link-title" [translate]="item.translate">{{item.title}}</span>
|
||||
<span class="nav-link-badge" *ngIf="item.badge" [translate]="item.badge.translate"
|
||||
[ngStyle]="{'background-color': item.badge.bg,'color': item.badge.fg}">
|
||||
{{item.badge.title}}
|
||||
</span>
|
||||
|
||||
@@ -62,4 +62,23 @@ fuse-widget {
|
||||
transform: rotateY(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.mat-form-field {
|
||||
|
||||
&.mat-form-field-type-mat-select {
|
||||
|
||||
.mat-input-wrapper {
|
||||
padding: 16px 0;
|
||||
|
||||
.mat-input-infix {
|
||||
border: none;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.mat-input-underline {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,9 @@ export class FuseWidgetComponent implements OnInit, AfterContentInit
|
||||
setTimeout(() => {
|
||||
|
||||
this.toggleButtons.forEach(flipButton => {
|
||||
this.renderer.listen(flipButton.el.nativeElement, 'click', () => {
|
||||
this.renderer.listen(flipButton.el.nativeElement, 'click', (event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this.toggle();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,7 +7,7 @@ import { Platform } from '@angular/cdk/platform';
|
||||
@Directive({
|
||||
selector: '[fusePerfectScrollbar]'
|
||||
})
|
||||
export class FusePerfectScrollbarDirective implements OnInit, AfterViewInit, OnDestroy
|
||||
export class FusePerfectScrollbarDirective implements AfterViewInit, OnDestroy
|
||||
{
|
||||
onSettingsChanged: Subscription;
|
||||
isDisableCustomScrollbars = false;
|
||||
@@ -16,7 +16,7 @@ export class FusePerfectScrollbarDirective implements OnInit, AfterViewInit, OnD
|
||||
ps;
|
||||
|
||||
constructor(
|
||||
private element: ElementRef,
|
||||
public element: ElementRef,
|
||||
private fuseConfig: FuseConfigService,
|
||||
private platform: Platform
|
||||
)
|
||||
@@ -35,11 +35,6 @@ export class FusePerfectScrollbarDirective implements OnInit, AfterViewInit, OnD
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
ngAfterViewInit()
|
||||
{
|
||||
if ( this.isMobile || this.isDisableCustomScrollbars )
|
||||
@@ -49,12 +44,14 @@ export class FusePerfectScrollbarDirective implements OnInit, AfterViewInit, OnD
|
||||
}
|
||||
|
||||
// Initialize the perfect-scrollbar
|
||||
this.ps = new PerfectScrollbar(this.element.nativeElement);
|
||||
this.ps = new PerfectScrollbar(this.element.nativeElement, {
|
||||
wheelPropagation: true
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy()
|
||||
{
|
||||
if ( !this.isInitialized )
|
||||
if ( !this.isInitialized || !this.ps )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import { CommonModule } from '@angular/common';
|
||||
import { MaterialModule } from './material.module';
|
||||
import { FlexLayoutModule } from '@angular/flex-layout';
|
||||
import { ColorPickerModule } from 'ngx-color-picker';
|
||||
import { NgxDnDModule } from '@withinpixels/ngx-dnd';
|
||||
import { NgxDnDModule } from '@swimlane/ngx-dnd';
|
||||
import { NgxDatatableModule } from '@swimlane/ngx-datatable';
|
||||
|
||||
import { FuseMatSidenavHelperDirective, FuseMatSidenavTogglerDirective } from '../directives/fuse-mat-sidenav-helper/fuse-mat-sidenav-helper.directive';
|
||||
|
||||
@@ -28,3 +28,51 @@
|
||||
.mat-form-field-underline {
|
||||
background-color: rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
// Fix: "Some idiots using table-cell and inline-table in mat-select"
|
||||
.mat-form-field {
|
||||
|
||||
&.mat-form-field-type-mat-select {
|
||||
|
||||
.mat-input-infix {
|
||||
display: inline-flex;
|
||||
width: auto;
|
||||
|
||||
.mat-select-trigger {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
|
||||
.mat-select-value {
|
||||
display: flex;
|
||||
max-width: none;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.mat-select-arrow-wrapper {
|
||||
display: inline-flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fix: "Stepper icons are broken due to Fuse's icon helpers"
|
||||
mat-horizontal-stepper,
|
||||
mat-vertical-stepper {
|
||||
|
||||
mat-step-header {
|
||||
|
||||
mat-icon {
|
||||
height: 16px !important;
|
||||
width: 16px !important;
|
||||
min-width: 0 !important;
|
||||
min-height: 0 !important;
|
||||
color: rgba(255, 255, 255, 0.87) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mat-vertical-stepper {
|
||||
padding: 16px 0;
|
||||
}
|
||||
@@ -47,14 +47,20 @@
|
||||
}
|
||||
|
||||
.nav-link-badge {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-width: 20px;
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
padding: 0 7px;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
border-radius: 20px;
|
||||
transition: opacity 0.2s ease-in-out 0.1s;
|
||||
margin-left: 8px;
|
||||
|
||||
+ .collapse-arrow {
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
|
||||
@@ -42,8 +42,8 @@
|
||||
fuse-footer,
|
||||
fuse-quick-panel,
|
||||
fuse-theme-options,
|
||||
.ps > .ps__scrollbar-x-rail,
|
||||
.ps > .ps__scrollbar-y-rail {
|
||||
.ps > .ps__rail-x,
|
||||
.ps > .ps__rail-y {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,20 +3,21 @@
|
||||
.color-picker {
|
||||
height: auto !important;
|
||||
border: none !important;
|
||||
|
||||
@include mat-elevation(4);
|
||||
|
||||
.preset-area {
|
||||
//padding: 4px 15px;
|
||||
padding: 0 0 12px 12px !important;
|
||||
padding: 0 0 16px 16px !important;
|
||||
height: 140px;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
|
||||
> hr {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.preset-label {
|
||||
display: none;
|
||||
|
||||
}
|
||||
|
||||
.preset-color {
|
||||
@@ -25,7 +26,8 @@
|
||||
margin: 0 !important;
|
||||
border: none !important;
|
||||
border-radius: 0 !important;
|
||||
&:nth-child(14n+3) {
|
||||
|
||||
&:nth-child(14n+1) {
|
||||
clear: both;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ export class FuseConfigService
|
||||
footer : 'mat-fuse-dark-900-bg'
|
||||
},
|
||||
customScrollbars: true,
|
||||
routerAnimation : 'fadeIn' // fadeIn, slideUp, slideDown, slideRight, slideLeft
|
||||
routerAnimation : 'fadeIn' // fadeIn, slideUp, slideDown, slideRight, slideLeft, none
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
773
src/app/fuse-fake-db/academy.ts
Normal file
773
src/app/fuse-fake-db/academy.ts
Normal file
@@ -0,0 +1,773 @@
|
||||
export class AcademyFakeDb
|
||||
{
|
||||
public static courses = [
|
||||
{
|
||||
'id' : '15459251a6d6b397565',
|
||||
'title' : 'Basics of Angular',
|
||||
'slug' : 'basics-of-angular',
|
||||
'category': 'web',
|
||||
'length' : 30,
|
||||
'updated' : 'Jun 28, 2017'
|
||||
},
|
||||
{
|
||||
'id' : '154588a0864d2881124',
|
||||
'title' : 'Basics of TypeScript',
|
||||
'slug' : 'basics-of-typeScript',
|
||||
'category': 'web',
|
||||
'length' : 60,
|
||||
'updated' : 'Nov 01, 2017'
|
||||
},
|
||||
{
|
||||
'id' : '15453ba60d3baa5daaf',
|
||||
'title' : 'Android N: Quick Settings',
|
||||
'slug' : 'android-n-quick-settings',
|
||||
'category': 'android',
|
||||
'length' : 120,
|
||||
'updated' : 'Jun 28, 2017'
|
||||
},
|
||||
{
|
||||
'id' : '15453a06c08fb021776',
|
||||
'title' : 'Keep Sensitive Data Safe and Private',
|
||||
'slug' : 'keep-sensitive-data-safe-and-private',
|
||||
'category': 'android',
|
||||
'length' : 45,
|
||||
'updated' : 'Jun 28, 2017'
|
||||
},
|
||||
{
|
||||
'id' : '15427f4c1b7f3953234',
|
||||
'title' : 'Building a gRPC Service with Java',
|
||||
'slug' : 'building-a-grpc-service-with-java',
|
||||
'category': 'cloud',
|
||||
'length' : 30,
|
||||
'updated' : 'Jun 28, 2017'
|
||||
},
|
||||
{
|
||||
'id' : '1542d75d929a603125',
|
||||
'title' : 'Build a PWA Using Workbox',
|
||||
'slug' : 'build-a-pwa-using-workbox',
|
||||
'category': 'web',
|
||||
'length' : 120,
|
||||
'updated' : 'Jun 28, 2017'
|
||||
},
|
||||
{
|
||||
'id' : '1543ee3a5b43e0f9f45',
|
||||
'title' : 'Build an App for the Google Assistant with Firebase and Dialogflow',
|
||||
'slug' : 'build-an-app-for-the-google-assistant-with-firebase-and-dialogflow',
|
||||
'category': 'firebase',
|
||||
'length' : 30,
|
||||
'updated' : 'Jun 28, 2017'
|
||||
},
|
||||
{
|
||||
'id' : '1543cc4515df3146112',
|
||||
'title' : 'Cloud Functions for Firebase',
|
||||
'slug' : 'cloud-functions-for-firebase',
|
||||
'category': 'firebase',
|
||||
'length' : 45,
|
||||
'updated' : 'Jun 28, 2017'
|
||||
},
|
||||
{
|
||||
'id' : '154398a4770d7aaf9a2',
|
||||
'title' : 'Manage Your Pivotal Cloud Foundry App\'s Using Apigee Edge',
|
||||
'slug' : 'manage-your-pivotal-cloud-foundry-apps-using-apigee-Edge',
|
||||
'category': 'cloud',
|
||||
'length' : 90,
|
||||
'updated' : 'Jun 28, 2017'
|
||||
},
|
||||
{
|
||||
'id' : '15438351f87dcd68567',
|
||||
'title' : 'Building Beautiful UIs with Flutter',
|
||||
'your' : 'building-beautiful-uis-with-flutter',
|
||||
'category': 'web',
|
||||
'length' : 90,
|
||||
'updated' : 'Jun 28, 2017'
|
||||
},
|
||||
{
|
||||
'id' : '1544e43dcdae6ebf876',
|
||||
'title' : 'Cloud Firestore',
|
||||
'slug' : 'cloud-firestore',
|
||||
'category': 'firebase',
|
||||
'length' : 90,
|
||||
'updated' : 'Jun 28, 2017'
|
||||
},
|
||||
{
|
||||
'id' : '1541ca7af66da284177',
|
||||
'title' : 'Customize Network Topology with Subnetworks',
|
||||
'slug' : 'customize-network-topology-with-subnetworks',
|
||||
'category': 'web',
|
||||
'length' : 45,
|
||||
'updated' : 'Jun 28, 2017'
|
||||
},
|
||||
{
|
||||
'id' : '154297167e781781745',
|
||||
'title' : 'Looking at Campaign Finance with BigQuery',
|
||||
'slug' : 'looking-at-campaign-finance-with-bigquery',
|
||||
'category': 'cloud',
|
||||
'length' : 60,
|
||||
'updated' : 'Jun 28, 2017'
|
||||
},
|
||||
{
|
||||
'id' : '154537435d5b32bf11a',
|
||||
'title' : 'Firebase Android',
|
||||
'slug' : 'firebase-android',
|
||||
'category': 'android',
|
||||
'length' : 45,
|
||||
'updated' : 'Jun 28, 2017'
|
||||
},
|
||||
{
|
||||
'id' : '154204e45a59b168453',
|
||||
'title' : 'Simulating a Thread Network Using OpenThread',
|
||||
'slug' : 'simulating-a-thread-network-using-openthread',
|
||||
'category': 'web',
|
||||
'length' : 45,
|
||||
'updated' : 'Jun 28, 2017'
|
||||
},
|
||||
{
|
||||
'id' : '1541dd1e05dfc439216',
|
||||
'title' : 'Your First Progressive Web App',
|
||||
'slug' : 'your-first-progressive-web-app',
|
||||
'category': 'web',
|
||||
'length' : 30,
|
||||
'updated' : 'Jun 28, 2017'
|
||||
},
|
||||
{
|
||||
'id' : '1532dfc67e704e48515',
|
||||
'title' : 'Launch Cloud Datalab',
|
||||
'slug' : 'launch-cloud-datalab',
|
||||
'category': 'cloud',
|
||||
'length' : 60,
|
||||
'updated' : 'Jun 28, 2017'
|
||||
},
|
||||
{
|
||||
'id' : '1542e43dfaae6ebf226',
|
||||
'title' : 'Personalize Your iOS App with Firebase User Management',
|
||||
'slug' : 'personalize-your-ios-app-with-firebase-user-management',
|
||||
'category': 'firebase',
|
||||
'length' : 90,
|
||||
'updated' : 'Jun 28, 2017'
|
||||
}
|
||||
];
|
||||
|
||||
public static categories = [
|
||||
{
|
||||
'id' : 0,
|
||||
'value': 'web',
|
||||
'label': 'Web'
|
||||
},
|
||||
{
|
||||
'id' : 1,
|
||||
'value': 'firebase',
|
||||
'label': 'Firebase'
|
||||
},
|
||||
{
|
||||
'id' : 2,
|
||||
'value': 'cloud',
|
||||
'label': 'Cloud'
|
||||
},
|
||||
{
|
||||
'id' : 3,
|
||||
'value': 'android',
|
||||
'label': 'Android'
|
||||
}
|
||||
];
|
||||
|
||||
private static demoSteps = [
|
||||
{
|
||||
'title' : 'Introduction',
|
||||
'content': '<h1>Step 1 - Introduction</h1>' +
|
||||
'<br>' +
|
||||
'This is an example step of the course. You can put anything in here from example codes to videos.' +
|
||||
'<br><br>' +
|
||||
'To install the CLI you need to have installed <b>npm</b> which typically comes with <b>NodeJS</b>.' +
|
||||
'To install or upgrade the CLI run the following <b>npm</b> command:' +
|
||||
'<br><br>' +
|
||||
'<code>npm -g install @angular/cli</code>' +
|
||||
'<br><br>' +
|
||||
'To verify that the CLI has been installed correctly, open a console and run:' +
|
||||
'<br><br>' +
|
||||
'<code>ng version</code>' +
|
||||
'<br><br>' +
|
||||
'<h2>Install dependencies</h2>' +
|
||||
'<br>' +
|
||||
'To moderate the images we\'ll need a few Node.js packages:' +
|
||||
'<br><br>' +
|
||||
'<ul>' +
|
||||
'<li>' +
|
||||
'The Google Cloud Vision Client Library for Node.js: @google-cloud/vision to run the image through the Cloud Vision API to detect inappropriate images.' +
|
||||
'</li>' +
|
||||
'<br>' +
|
||||
'<li>' +
|
||||
'The Google Cloud Storage Client Library for Node.js: @google-cloud/storage to download and upload the images from Cloud Storage.' +
|
||||
'</li>' +
|
||||
'<br>' +
|
||||
'<li>' +
|
||||
'A Node.js library allowing us to run processes: child-process-promise to run ImageMagick since the ImageMagick command-line tool comes pre-installed on all Functions instances.' +
|
||||
'</li>' +
|
||||
'</ul>' +
|
||||
'<br>' +
|
||||
'To install these three packages into your Cloud Functions app, run the following npm install --save command. Make sure that you do this from the functions directory.' +
|
||||
'<br><br>' +
|
||||
'<code>npm install --save @google-cloud/vision @google-cloud/storage child-process-promise</code>' +
|
||||
'<br><br>' +
|
||||
'This will install the three packages locally and add them as declared dependencies in your package.js file.'
|
||||
},
|
||||
{
|
||||
'title' : 'Get the sample code',
|
||||
'content': '<h1>Step 2 - Get the sample code</h1>' +
|
||||
'<br>' +
|
||||
'This is an example step of the course. You can put anything in here from example codes to videos.' +
|
||||
'<br><br>' +
|
||||
'To install the CLI you need to have installed <b>npm</b> which typically comes with <b>NodeJS</b>.' +
|
||||
'To install or upgrade the CLI run the following <b>npm</b> command:' +
|
||||
'<br><br>' +
|
||||
'<code>npm -g install @angular/cli</code>' +
|
||||
'<br><br>' +
|
||||
'To verify that the CLI has been installed correctly, open a console and run:' +
|
||||
'<br><br>' +
|
||||
'<code>ng version</code>' +
|
||||
'<br><br>' +
|
||||
'<h2>Install dependencies</h2>' +
|
||||
'<br>' +
|
||||
'To moderate the images we\'ll need a few Node.js packages:' +
|
||||
'<br><br>' +
|
||||
'<ul>' +
|
||||
'<li>' +
|
||||
'The Google Cloud Vision Client Library for Node.js: @google-cloud/vision to run the image through the Cloud Vision API to detect inappropriate images.' +
|
||||
'</li>' +
|
||||
'<br>' +
|
||||
'<li>' +
|
||||
'The Google Cloud Storage Client Library for Node.js: @google-cloud/storage to download and upload the images from Cloud Storage.' +
|
||||
'</li>' +
|
||||
'<br>' +
|
||||
'<li>' +
|
||||
'A Node.js library allowing us to run processes: child-process-promise to run ImageMagick since the ImageMagick command-line tool comes pre-installed on all Functions instances.' +
|
||||
'</li>' +
|
||||
'</ul>' +
|
||||
'<br>' +
|
||||
'To install these three packages into your Cloud Functions app, run the following npm install --save command. Make sure that you do this from the functions directory.' +
|
||||
'<br><br>' +
|
||||
'<code>npm install --save @google-cloud/vision @google-cloud/storage child-process-promise</code>' +
|
||||
'<br><br>' +
|
||||
'This will install the three packages locally and add them as declared dependencies in your package.js file.'
|
||||
},
|
||||
{
|
||||
'title' : 'Create a Firebase project and Set up your app',
|
||||
'content': '<h1>Step 3 - Create a Firebase project and Set up your app</h1>' +
|
||||
'<br>' +
|
||||
'This is an example step of the course. You can put anything in here from example codes to videos.' +
|
||||
'<br><br>' +
|
||||
'To install the CLI you need to have installed <b>npm</b> which typically comes with <b>NodeJS</b>.' +
|
||||
'To install or upgrade the CLI run the following <b>npm</b> command:' +
|
||||
'<br><br>' +
|
||||
'<code>npm -g install @angular/cli</code>' +
|
||||
'<br><br>' +
|
||||
'To verify that the CLI has been installed correctly, open a console and run:' +
|
||||
'<br><br>' +
|
||||
'<code>ng version</code>' +
|
||||
'<br><br>' +
|
||||
'<h2>Install dependencies</h2>' +
|
||||
'<br>' +
|
||||
'To moderate the images we\'ll need a few Node.js packages:' +
|
||||
'<br><br>' +
|
||||
'<ul>' +
|
||||
'<li>' +
|
||||
'The Google Cloud Vision Client Library for Node.js: @google-cloud/vision to run the image through the Cloud Vision API to detect inappropriate images.' +
|
||||
'</li>' +
|
||||
'<br>' +
|
||||
'<li>' +
|
||||
'The Google Cloud Storage Client Library for Node.js: @google-cloud/storage to download and upload the images from Cloud Storage.' +
|
||||
'</li>' +
|
||||
'<br>' +
|
||||
'<li>' +
|
||||
'A Node.js library allowing us to run processes: child-process-promise to run ImageMagick since the ImageMagick command-line tool comes pre-installed on all Functions instances.' +
|
||||
'</li>' +
|
||||
'</ul>' +
|
||||
'<br>' +
|
||||
'To install these three packages into your Cloud Functions app, run the following npm install --save command. Make sure that you do this from the functions directory.' +
|
||||
'<br><br>' +
|
||||
'<code>npm install --save @google-cloud/vision @google-cloud/storage child-process-promise</code>' +
|
||||
'<br><br>' +
|
||||
'This will install the three packages locally and add them as declared dependencies in your package.js file.'
|
||||
},
|
||||
{
|
||||
'title' : 'Install the Firebase Command Line Interface',
|
||||
'content': '<h1>Step 4 - Install the Firebase Command Line Interface</h1>' +
|
||||
'<br>' +
|
||||
'This is an example step of the course. You can put anything in here from example codes to videos.' +
|
||||
'<br><br>' +
|
||||
'To install the CLI you need to have installed <b>npm</b> which typically comes with <b>NodeJS</b>.' +
|
||||
'To install or upgrade the CLI run the following <b>npm</b> command:' +
|
||||
'<br><br>' +
|
||||
'<code>npm -g install @angular/cli</code>' +
|
||||
'<br><br>' +
|
||||
'To verify that the CLI has been installed correctly, open a console and run:' +
|
||||
'<br><br>' +
|
||||
'<code>ng version</code>' +
|
||||
'<br><br>' +
|
||||
'<h2>Install dependencies</h2>' +
|
||||
'<br>' +
|
||||
'To moderate the images we\'ll need a few Node.js packages:' +
|
||||
'<br><br>' +
|
||||
'<ul>' +
|
||||
'<li>' +
|
||||
'The Google Cloud Vision Client Library for Node.js: @google-cloud/vision to run the image through the Cloud Vision API to detect inappropriate images.' +
|
||||
'</li>' +
|
||||
'<br>' +
|
||||
'<li>' +
|
||||
'The Google Cloud Storage Client Library for Node.js: @google-cloud/storage to download and upload the images from Cloud Storage.' +
|
||||
'</li>' +
|
||||
'<br>' +
|
||||
'<li>' +
|
||||
'A Node.js library allowing us to run processes: child-process-promise to run ImageMagick since the ImageMagick command-line tool comes pre-installed on all Functions instances.' +
|
||||
'</li>' +
|
||||
'</ul>' +
|
||||
'<br>' +
|
||||
'To install these three packages into your Cloud Functions app, run the following npm install --save command. Make sure that you do this from the functions directory.' +
|
||||
'<br><br>' +
|
||||
'<code>npm install --save @google-cloud/vision @google-cloud/storage child-process-promise</code>' +
|
||||
'<br><br>' +
|
||||
'This will install the three packages locally and add them as declared dependencies in your package.js file.'
|
||||
},
|
||||
{
|
||||
'title' : 'Deploy and run the web app',
|
||||
'content': '<h1>Step 5 - Deploy and run the web app</h1>' +
|
||||
'<br>' +
|
||||
'This is an example step of the course. You can put anything in here from example codes to videos.' +
|
||||
'<br><br>' +
|
||||
'To install the CLI you need to have installed <b>npm</b> which typically comes with <b>NodeJS</b>.' +
|
||||
'To install or upgrade the CLI run the following <b>npm</b> command:' +
|
||||
'<br><br>' +
|
||||
'<code>npm -g install @angular/cli</code>' +
|
||||
'<br><br>' +
|
||||
'To verify that the CLI has been installed correctly, open a console and run:' +
|
||||
'<br><br>' +
|
||||
'<code>ng version</code>' +
|
||||
'<br><br>' +
|
||||
'<h2>Install dependencies</h2>' +
|
||||
'<br>' +
|
||||
'To moderate the images we\'ll need a few Node.js packages:' +
|
||||
'<br><br>' +
|
||||
'<ul>' +
|
||||
'<li>' +
|
||||
'The Google Cloud Vision Client Library for Node.js: @google-cloud/vision to run the image through the Cloud Vision API to detect inappropriate images.' +
|
||||
'</li>' +
|
||||
'<br>' +
|
||||
'<li>' +
|
||||
'The Google Cloud Storage Client Library for Node.js: @google-cloud/storage to download and upload the images from Cloud Storage.' +
|
||||
'</li>' +
|
||||
'<br>' +
|
||||
'<li>' +
|
||||
'A Node.js library allowing us to run processes: child-process-promise to run ImageMagick since the ImageMagick command-line tool comes pre-installed on all Functions instances.' +
|
||||
'</li>' +
|
||||
'</ul>' +
|
||||
'<br>' +
|
||||
'To install these three packages into your Cloud Functions app, run the following npm install --save command. Make sure that you do this from the functions directory.' +
|
||||
'<br><br>' +
|
||||
'<code>npm install --save @google-cloud/vision @google-cloud/storage child-process-promise</code>' +
|
||||
'<br><br>' +
|
||||
'This will install the three packages locally and add them as declared dependencies in your package.js file.'
|
||||
},
|
||||
{
|
||||
'title' : 'The Functions Directory',
|
||||
'content': '<h1>Step 6 - The Functions Directory</h1>' +
|
||||
'<br>' +
|
||||
'This is an example step of the course. You can put anything in here from example codes to videos.' +
|
||||
'<br><br>' +
|
||||
'To install the CLI you need to have installed <b>npm</b> which typically comes with <b>NodeJS</b>.' +
|
||||
'To install or upgrade the CLI run the following <b>npm</b> command:' +
|
||||
'<br><br>' +
|
||||
'<code>npm -g install @angular/cli</code>' +
|
||||
'<br><br>' +
|
||||
'To verify that the CLI has been installed correctly, open a console and run:' +
|
||||
'<br><br>' +
|
||||
'<code>ng version</code>' +
|
||||
'<br><br>' +
|
||||
'<h2>Install dependencies</h2>' +
|
||||
'<br>' +
|
||||
'To moderate the images we\'ll need a few Node.js packages:' +
|
||||
'<br><br>' +
|
||||
'<ul>' +
|
||||
'<li>' +
|
||||
'The Google Cloud Vision Client Library for Node.js: @google-cloud/vision to run the image through the Cloud Vision API to detect inappropriate images.' +
|
||||
'</li>' +
|
||||
'<br>' +
|
||||
'<li>' +
|
||||
'The Google Cloud Storage Client Library for Node.js: @google-cloud/storage to download and upload the images from Cloud Storage.' +
|
||||
'</li>' +
|
||||
'<br>' +
|
||||
'<li>' +
|
||||
'A Node.js library allowing us to run processes: child-process-promise to run ImageMagick since the ImageMagick command-line tool comes pre-installed on all Functions instances.' +
|
||||
'</li>' +
|
||||
'</ul>' +
|
||||
'<br>' +
|
||||
'To install these three packages into your Cloud Functions app, run the following npm install --save command. Make sure that you do this from the functions directory.' +
|
||||
'<br><br>' +
|
||||
'<code>npm install --save @google-cloud/vision @google-cloud/storage child-process-promise</code>' +
|
||||
'<br><br>' +
|
||||
'This will install the three packages locally and add them as declared dependencies in your package.js file.'
|
||||
},
|
||||
{
|
||||
'title' : 'Import the Cloud Functions and Firebase Admin modules',
|
||||
'content': '<h1>Step 7 - Import the Cloud Functions and Firebase Admin modules</h1>' +
|
||||
'<br>' +
|
||||
'This is an example step of the course. You can put anything in here from example codes to videos.' +
|
||||
'<br><br>' +
|
||||
'To install the CLI you need to have installed <b>npm</b> which typically comes with <b>NodeJS</b>.' +
|
||||
'To install or upgrade the CLI run the following <b>npm</b> command:' +
|
||||
'<br><br>' +
|
||||
'<code>npm -g install @angular/cli</code>' +
|
||||
'<br><br>' +
|
||||
'To verify that the CLI has been installed correctly, open a console and run:' +
|
||||
'<br><br>' +
|
||||
'<code>ng version</code>' +
|
||||
'<br><br>' +
|
||||
'<h2>Install dependencies</h2>' +
|
||||
'<br>' +
|
||||
'To moderate the images we\'ll need a few Node.js packages:' +
|
||||
'<br><br>' +
|
||||
'<ul>' +
|
||||
'<li>' +
|
||||
'The Google Cloud Vision Client Library for Node.js: @google-cloud/vision to run the image through the Cloud Vision API to detect inappropriate images.' +
|
||||
'</li>' +
|
||||
'<br>' +
|
||||
'<li>' +
|
||||
'The Google Cloud Storage Client Library for Node.js: @google-cloud/storage to download and upload the images from Cloud Storage.' +
|
||||
'</li>' +
|
||||
'<br>' +
|
||||
'<li>' +
|
||||
'A Node.js library allowing us to run processes: child-process-promise to run ImageMagick since the ImageMagick command-line tool comes pre-installed on all Functions instances.' +
|
||||
'</li>' +
|
||||
'</ul>' +
|
||||
'<br>' +
|
||||
'To install these three packages into your Cloud Functions app, run the following npm install --save command. Make sure that you do this from the functions directory.' +
|
||||
'<br><br>' +
|
||||
'<code>npm install --save @google-cloud/vision @google-cloud/storage child-process-promise</code>' +
|
||||
'<br><br>' +
|
||||
'This will install the three packages locally and add them as declared dependencies in your package.js file.'
|
||||
},
|
||||
{
|
||||
'title' : 'Welcome New Users',
|
||||
'content': '<h1>Step 8 - Welcome New Users</h1>' +
|
||||
'<br>' +
|
||||
'This is an example step of the course. You can put anything in here from example codes to videos.' +
|
||||
'<br><br>' +
|
||||
'To install the CLI you need to have installed <b>npm</b> which typically comes with <b>NodeJS</b>.' +
|
||||
'To install or upgrade the CLI run the following <b>npm</b> command:' +
|
||||
'<br><br>' +
|
||||
'<code>npm -g install @angular/cli</code>' +
|
||||
'<br><br>' +
|
||||
'To verify that the CLI has been installed correctly, open a console and run:' +
|
||||
'<br><br>' +
|
||||
'<code>ng version</code>' +
|
||||
'<br><br>' +
|
||||
'<h2>Install dependencies</h2>' +
|
||||
'<br>' +
|
||||
'To moderate the images we\'ll need a few Node.js packages:' +
|
||||
'<br><br>' +
|
||||
'<ul>' +
|
||||
'<li>' +
|
||||
'The Google Cloud Vision Client Library for Node.js: @google-cloud/vision to run the image through the Cloud Vision API to detect inappropriate images.' +
|
||||
'</li>' +
|
||||
'<br>' +
|
||||
'<li>' +
|
||||
'The Google Cloud Storage Client Library for Node.js: @google-cloud/storage to download and upload the images from Cloud Storage.' +
|
||||
'</li>' +
|
||||
'<br>' +
|
||||
'<li>' +
|
||||
'A Node.js library allowing us to run processes: child-process-promise to run ImageMagick since the ImageMagick command-line tool comes pre-installed on all Functions instances.' +
|
||||
'</li>' +
|
||||
'</ul>' +
|
||||
'<br>' +
|
||||
'To install these three packages into your Cloud Functions app, run the following npm install --save command. Make sure that you do this from the functions directory.' +
|
||||
'<br><br>' +
|
||||
'<code>npm install --save @google-cloud/vision @google-cloud/storage child-process-promise</code>' +
|
||||
'<br><br>' +
|
||||
'This will install the three packages locally and add them as declared dependencies in your package.js file.'
|
||||
},
|
||||
{
|
||||
'title' : 'Images moderation',
|
||||
'content': '<h1>Step 9 - Images moderation</h1>' +
|
||||
'<br>' +
|
||||
'This is an example step of the course. You can put anything in here from example codes to videos.' +
|
||||
'<br><br>' +
|
||||
'To install the CLI you need to have installed <b>npm</b> which typically comes with <b>NodeJS</b>.' +
|
||||
'To install or upgrade the CLI run the following <b>npm</b> command:' +
|
||||
'<br><br>' +
|
||||
'<code>npm -g install @angular/cli</code>' +
|
||||
'<br><br>' +
|
||||
'To verify that the CLI has been installed correctly, open a console and run:' +
|
||||
'<br><br>' +
|
||||
'<code>ng version</code>' +
|
||||
'<br><br>' +
|
||||
'<h2>Install dependencies</h2>' +
|
||||
'<br>' +
|
||||
'To moderate the images we\'ll need a few Node.js packages:' +
|
||||
'<br><br>' +
|
||||
'<ul>' +
|
||||
'<li>' +
|
||||
'The Google Cloud Vision Client Library for Node.js: @google-cloud/vision to run the image through the Cloud Vision API to detect inappropriate images.' +
|
||||
'</li>' +
|
||||
'<br>' +
|
||||
'<li>' +
|
||||
'The Google Cloud Storage Client Library for Node.js: @google-cloud/storage to download and upload the images from Cloud Storage.' +
|
||||
'</li>' +
|
||||
'<br>' +
|
||||
'<li>' +
|
||||
'A Node.js library allowing us to run processes: child-process-promise to run ImageMagick since the ImageMagick command-line tool comes pre-installed on all Functions instances.' +
|
||||
'</li>' +
|
||||
'</ul>' +
|
||||
'<br>' +
|
||||
'To install these three packages into your Cloud Functions app, run the following npm install --save command. Make sure that you do this from the functions directory.' +
|
||||
'<br><br>' +
|
||||
'<code>npm install --save @google-cloud/vision @google-cloud/storage child-process-promise</code>' +
|
||||
'<br><br>' +
|
||||
'This will install the three packages locally and add them as declared dependencies in your package.js file.'
|
||||
},
|
||||
{
|
||||
'title' : 'New Message Notifications',
|
||||
'content': '<h1>Step 10 - New Message Notifications</h1>' +
|
||||
'<br>' +
|
||||
'This is an example step of the course. You can put anything in here from example codes to videos.' +
|
||||
'<br><br>' +
|
||||
'To install the CLI you need to have installed <b>npm</b> which typically comes with <b>NodeJS</b>.' +
|
||||
'To install or upgrade the CLI run the following <b>npm</b> command:' +
|
||||
'<br><br>' +
|
||||
'<code>npm -g install @angular/cli</code>' +
|
||||
'<br><br>' +
|
||||
'To verify that the CLI has been installed correctly, open a console and run:' +
|
||||
'<br><br>' +
|
||||
'<code>ng version</code>' +
|
||||
'<br><br>' +
|
||||
'<h2>Install dependencies</h2>' +
|
||||
'<br>' +
|
||||
'To moderate the images we\'ll need a few Node.js packages:' +
|
||||
'<br><br>' +
|
||||
'<ul>' +
|
||||
'<li>' +
|
||||
'The Google Cloud Vision Client Library for Node.js: @google-cloud/vision to run the image through the Cloud Vision API to detect inappropriate images.' +
|
||||
'</li>' +
|
||||
'<br>' +
|
||||
'<li>' +
|
||||
'The Google Cloud Storage Client Library for Node.js: @google-cloud/storage to download and upload the images from Cloud Storage.' +
|
||||
'</li>' +
|
||||
'<br>' +
|
||||
'<li>' +
|
||||
'A Node.js library allowing us to run processes: child-process-promise to run ImageMagick since the ImageMagick command-line tool comes pre-installed on all Functions instances.' +
|
||||
'</li>' +
|
||||
'</ul>' +
|
||||
'<br>' +
|
||||
'To install these three packages into your Cloud Functions app, run the following npm install --save command. Make sure that you do this from the functions directory.' +
|
||||
'<br><br>' +
|
||||
'<code>npm install --save @google-cloud/vision @google-cloud/storage child-process-promise</code>' +
|
||||
'<br><br>' +
|
||||
'This will install the three packages locally and add them as declared dependencies in your package.js file.'
|
||||
},
|
||||
{
|
||||
'title' : 'Congratulations!',
|
||||
'content': '<h1>Step 11 - Congratulations!</h1>' +
|
||||
'<br>' +
|
||||
'You\'ve built a full-fidelity, offline-capable progressive web app by leveraging the power of reusable Web Components and Firebase. Why bother with a native app when you know how to do all that?!'
|
||||
}
|
||||
];
|
||||
|
||||
public static course = [
|
||||
{
|
||||
'id' : '15459251a6d6b397565',
|
||||
'title' : 'Basics of Angular',
|
||||
'slug' : 'basics-of-angular',
|
||||
'description': 'Commits that need to be pushed lorem ipsum dolor sit amet, consectetur adipiscing elit.',
|
||||
'category' : 'web',
|
||||
'length' : 30,
|
||||
'totalSteps' : 11,
|
||||
'updated' : 'Jun 28, 2017',
|
||||
'steps' : AcademyFakeDb.demoSteps
|
||||
},
|
||||
{
|
||||
'id' : '154588a0864d2881124',
|
||||
'title' : 'Basics of TypeScript',
|
||||
'slug' : 'basics-of-typeScript',
|
||||
'description': 'Commits that need to be pushed lorem ipsum dolor sit amet, consectetur adipiscing elit.',
|
||||
'category' : 'web',
|
||||
'length' : 60,
|
||||
'totalSteps' : 11,
|
||||
'updated' : 'Nov 01, 2017',
|
||||
'steps' : AcademyFakeDb.demoSteps
|
||||
},
|
||||
{
|
||||
'id' : '15453ba60d3baa5daaf',
|
||||
'title' : 'Android N: Quick Settings',
|
||||
'slug' : 'android-n-quick-settings',
|
||||
'description': 'Commits that need to be pushed lorem ipsum dolor sit amet, consectetur adipiscing elit.',
|
||||
'category' : 'android',
|
||||
'length' : 120,
|
||||
'totalSteps' : 11,
|
||||
'updated' : 'Jun 28, 2017',
|
||||
'steps' : AcademyFakeDb.demoSteps
|
||||
},
|
||||
{
|
||||
'id' : '15453a06c08fb021776',
|
||||
'title' : 'Keep Sensitive Data Safe and Private',
|
||||
'slug' : 'keep-sensitive-data-safe-and-private',
|
||||
'description': 'Commits that need to be pushed lorem ipsum dolor sit amet, consectetur adipiscing elit.',
|
||||
'category' : 'android',
|
||||
'length' : 45,
|
||||
'totalSteps' : 11,
|
||||
'updated' : 'Jun 28, 2017',
|
||||
'steps' : AcademyFakeDb.demoSteps
|
||||
},
|
||||
{
|
||||
'id' : '15427f4c1b7f3953234',
|
||||
'title' : 'Building a gRPC Service with Java',
|
||||
'slug' : 'building-a-grpc-service-with-java',
|
||||
'description': 'Commits that need to be pushed lorem ipsum dolor sit amet, consectetur adipiscing elit.',
|
||||
'category' : 'cloud',
|
||||
'length' : 30,
|
||||
'totalSteps' : 11,
|
||||
'updated' : 'Jun 28, 2017',
|
||||
'steps' : AcademyFakeDb.demoSteps
|
||||
},
|
||||
{
|
||||
'id' : '1542d75d929a603125',
|
||||
'title' : 'Build a PWA Using Workbox',
|
||||
'slug' : 'build-a-pwa-using-workbox',
|
||||
'description': 'Commits that need to be pushed lorem ipsum dolor sit amet, consectetur adipiscing elit.',
|
||||
'category' : 'web',
|
||||
'length' : 120,
|
||||
'totalSteps' : 11,
|
||||
'updated' : 'Jun 28, 2017',
|
||||
'steps' : AcademyFakeDb.demoSteps
|
||||
},
|
||||
{
|
||||
'id' : '1543ee3a5b43e0f9f45',
|
||||
'title' : 'Build an App for the Google Assistant with Firebase and Dialogflow',
|
||||
'slug' : 'build-an-app-for-the-google-assistant-with-firebase-and-dialogflow',
|
||||
'description': 'Commits that need to be pushed lorem ipsum dolor sit amet, consectetur adipiscing elit.',
|
||||
'category' : 'firebase',
|
||||
'length' : 30,
|
||||
'totalSteps' : 11,
|
||||
'updated' : 'Jun 28, 2017',
|
||||
'steps' : AcademyFakeDb.demoSteps
|
||||
},
|
||||
{
|
||||
'id' : '1543cc4515df3146112',
|
||||
'title' : 'Cloud Functions for Firebase',
|
||||
'slug' : 'cloud-functions-for-firebase',
|
||||
'description': 'Commits that need to be pushed lorem ipsum dolor sit amet, consectetur adipiscing elit.',
|
||||
'category' : 'firebase',
|
||||
'length' : 45,
|
||||
'totalSteps' : 11,
|
||||
'updated' : 'Jun 28, 2017',
|
||||
'steps' : AcademyFakeDb.demoSteps
|
||||
},
|
||||
{
|
||||
'id' : '154398a4770d7aaf9a2',
|
||||
'title' : 'Manage Your Pivotal Cloud Foundry App\'s Using Apigee Edge',
|
||||
'slug' : 'manage-your-pivotal-cloud-foundry-apps-using-apigee-Edge',
|
||||
'description': 'Commits that need to be pushed lorem ipsum dolor sit amet, consectetur adipiscing elit.',
|
||||
'category' : 'cloud',
|
||||
'length' : 90,
|
||||
'totalSteps' : 11,
|
||||
'updated' : 'Jun 28, 2017',
|
||||
'steps' : AcademyFakeDb.demoSteps
|
||||
},
|
||||
{
|
||||
'id' : '15438351f87dcd68567',
|
||||
'title' : 'Building Beautiful UIs with Flutter',
|
||||
'your' : 'building-beautiful-uis-with-flutter',
|
||||
'description': 'Commits that need to be pushed lorem ipsum dolor sit amet, consectetur adipiscing elit.',
|
||||
'category' : 'web',
|
||||
'length' : 90,
|
||||
'totalSteps' : 11,
|
||||
'updated' : 'Jun 28, 2017',
|
||||
'steps' : AcademyFakeDb.demoSteps
|
||||
},
|
||||
{
|
||||
'id' : '1544e43dcdae6ebf876',
|
||||
'title' : 'Cloud Firestore',
|
||||
'slug' : 'cloud-firestore',
|
||||
'description': 'Commits that need to be pushed lorem ipsum dolor sit amet, consectetur adipiscing elit.',
|
||||
'category' : 'firebase',
|
||||
'length' : 90,
|
||||
'totalSteps' : 11,
|
||||
'updated' : 'Jun 28, 2017',
|
||||
'steps' : AcademyFakeDb.demoSteps
|
||||
},
|
||||
{
|
||||
'id' : '1541ca7af66da284177',
|
||||
'title' : 'Customize Network Topology with Subnetworks',
|
||||
'slug' : 'customize-network-topology-with-subnetworks',
|
||||
'description': 'Commits that need to be pushed lorem ipsum dolor sit amet, consectetur adipiscing elit.',
|
||||
'category' : 'web',
|
||||
'length' : 45,
|
||||
'totalSteps' : 11,
|
||||
'updated' : 'Jun 28, 2017',
|
||||
'steps' : AcademyFakeDb.demoSteps
|
||||
},
|
||||
{
|
||||
'id' : '154297167e781781745',
|
||||
'title' : 'Looking at Campaign Finance with BigQuery',
|
||||
'slug' : 'looking-at-campaign-finance-with-bigquery',
|
||||
'description': 'Commits that need to be pushed lorem ipsum dolor sit amet, consectetur adipiscing elit.',
|
||||
'category' : 'cloud',
|
||||
'length' : 60,
|
||||
'totalSteps' : 11,
|
||||
'updated' : 'Jun 28, 2017',
|
||||
'steps' : AcademyFakeDb.demoSteps
|
||||
},
|
||||
{
|
||||
'id' : '154537435d5b32bf11a',
|
||||
'title' : 'Firebase Android',
|
||||
'slug' : 'firebase-android',
|
||||
'description': 'Commits that need to be pushed lorem ipsum dolor sit amet, consectetur adipiscing elit.',
|
||||
'category' : 'android',
|
||||
'length' : 45,
|
||||
'totalSteps' : 11,
|
||||
'updated' : 'Jun 28, 2017',
|
||||
'steps' : AcademyFakeDb.demoSteps
|
||||
},
|
||||
{
|
||||
'id' : '154204e45a59b168453',
|
||||
'title' : 'Simulating a Thread Network Using OpenThread',
|
||||
'slug' : 'simulating-a-thread-network-using-openthread',
|
||||
'description': 'Commits that need to be pushed lorem ipsum dolor sit amet, consectetur adipiscing elit.',
|
||||
'category' : 'web',
|
||||
'length' : 45,
|
||||
'totalSteps' : 11,
|
||||
'updated' : 'Jun 28, 2017',
|
||||
'steps' : AcademyFakeDb.demoSteps
|
||||
},
|
||||
{
|
||||
'id' : '1541dd1e05dfc439216',
|
||||
'title' : 'Your First Progressive Web App',
|
||||
'slug' : 'your-first-progressive-web-app',
|
||||
'description': 'Commits that need to be pushed lorem ipsum dolor sit amet, consectetur adipiscing elit.',
|
||||
'category' : 'web',
|
||||
'length' : 30,
|
||||
'totalSteps' : 11,
|
||||
'updated' : 'Jun 28, 2017',
|
||||
'steps' : AcademyFakeDb.demoSteps
|
||||
},
|
||||
{
|
||||
'id' : '1532dfc67e704e48515',
|
||||
'title' : 'Launch Cloud Datalab',
|
||||
'slug' : 'launch-cloud-datalab',
|
||||
'description': 'Commits that need to be pushed lorem ipsum dolor sit amet, consectetur adipiscing elit.',
|
||||
'category' : 'cloud',
|
||||
'length' : 60,
|
||||
'totalSteps' : 11,
|
||||
'updated' : 'Jun 28, 2017',
|
||||
'steps' : AcademyFakeDb.demoSteps
|
||||
},
|
||||
{
|
||||
'id' : '1542e43dfaae6ebf226',
|
||||
'title' : 'Personalize Your iOS App with Firebase User Management',
|
||||
'slug' : 'personalize-your-ios-app-with-firebase-user-management',
|
||||
'description': 'Commits that need to be pushed lorem ipsum dolor sit amet, consectetur adipiscing elit.',
|
||||
'category' : 'firebase',
|
||||
'length' : 90,
|
||||
'totalSteps' : 11,
|
||||
'updated' : 'Jun 28, 2017',
|
||||
'steps' : AcademyFakeDb.demoSteps
|
||||
}
|
||||
];
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -16,6 +16,7 @@ import { ScrumboardFakeDb } from './scrumboard';
|
||||
import { FaqFakeDb } from './faq';
|
||||
import { KnowledgeBaseFakeDb } from './knowledge-base';
|
||||
import { ECommerceFakeDb } from './e-commerce';
|
||||
import { AcademyFakeDb } from './academy';
|
||||
|
||||
export class FuseFakeDbService implements InMemoryDbService
|
||||
{
|
||||
@@ -52,7 +53,10 @@ export class FuseFakeDbService implements InMemoryDbService
|
||||
'knowledge-base' : KnowledgeBaseFakeDb.data,
|
||||
'e-commerce-dashboard' : ECommerceFakeDb.dashboard,
|
||||
'e-commerce-products' : ECommerceFakeDb.products,
|
||||
'e-commerce-orders' : ECommerceFakeDb.orders
|
||||
'e-commerce-orders' : ECommerceFakeDb.orders,
|
||||
'academy-categories' : AcademyFakeDb.categories,
|
||||
'academy-courses' : AcademyFakeDb.courses,
|
||||
'academy-course' : AcademyFakeDb.course
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
47
src/app/main/content/apps/academy/academy.module.ts
Normal file
47
src/app/main/content/apps/academy/academy.module.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { SharedModule } from '../../../../core/modules/shared.module';
|
||||
import { RouterModule } from '@angular/router';
|
||||
|
||||
import { FuseAcademyCoursesComponent } from './courses/courses.component';
|
||||
import { FuseAcademyCourseComponent } from './course/course.component';
|
||||
import { AcademyCoursesService } from './courses.service';
|
||||
import { AcademyCourseService } from './course.service';
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path : 'courses',
|
||||
component: FuseAcademyCoursesComponent,
|
||||
resolve : {
|
||||
academy: AcademyCoursesService
|
||||
}
|
||||
},
|
||||
{
|
||||
path : 'courses/:courseId/:courseSlug',
|
||||
component: FuseAcademyCourseComponent,
|
||||
resolve : {
|
||||
academy: AcademyCourseService
|
||||
}
|
||||
},
|
||||
{
|
||||
path : '**',
|
||||
redirectTo: 'courses'
|
||||
}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports : [
|
||||
SharedModule,
|
||||
RouterModule.forChild(routes)
|
||||
],
|
||||
declarations: [
|
||||
FuseAcademyCoursesComponent,
|
||||
FuseAcademyCourseComponent
|
||||
],
|
||||
providers : [
|
||||
AcademyCoursesService,
|
||||
AcademyCourseService
|
||||
]
|
||||
})
|
||||
export class FuseAcademyModule
|
||||
{
|
||||
}
|
||||
49
src/app/main/content/apps/academy/course.service.ts
Normal file
49
src/app/main/content/apps/academy/course.service.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
|
||||
|
||||
@Injectable()
|
||||
export class AcademyCourseService implements Resolve<any>
|
||||
{
|
||||
onCourseChanged: BehaviorSubject<any> = new BehaviorSubject({});
|
||||
|
||||
constructor(private http: HttpClient)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* The Academy App Main Resolver
|
||||
*
|
||||
* @param {ActivatedRouteSnapshot} route
|
||||
* @param {RouterStateSnapshot} state
|
||||
* @returns {Observable<any> | Promise<any> | any}
|
||||
*/
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<any> | Promise<any> | any
|
||||
{
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
Promise.all([
|
||||
this.getCourse(route.params.courseId, route.params.courseSlug)
|
||||
]).then(
|
||||
() => {
|
||||
resolve();
|
||||
},
|
||||
reject
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
getCourse(courseId, courseSlug): Promise<any>
|
||||
{
|
||||
return new Promise((resolve, reject) => {
|
||||
this.http.get('api/academy-course/' + courseId + '/' + courseSlug)
|
||||
.subscribe((response: any) => {
|
||||
this.onCourseChanged.next(response);
|
||||
resolve(response);
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
100
src/app/main/content/apps/academy/course/course.component.html
Normal file
100
src/app/main/content/apps/academy/course/course.component.html
Normal file
@@ -0,0 +1,100 @@
|
||||
<div id="academy-course" class="page-layout simple left-sidenav">
|
||||
|
||||
<mat-sidenav-container>
|
||||
|
||||
<!-- SIDENAV -->
|
||||
<mat-sidenav class="sidenav" align="start" opened="true" mode="side"
|
||||
fuseMatSidenavHelper="academy-left-sidenav" mat-is-locked-open="gt-md">
|
||||
|
||||
<div class="sidenav-content" fusePerfectScrollbar>
|
||||
|
||||
<div class="steps">
|
||||
|
||||
<div class="step"
|
||||
*ngFor="let step of course.steps; let i = index; let last = last; let first = first"
|
||||
(click)="gotoStep(i)"
|
||||
[ngClass]="{'current': currentStep === i, 'completed': currentStep > i, 'last': last, 'first': first}">
|
||||
<div class="index">
|
||||
<span>{{i + 1}}</span>
|
||||
</div>
|
||||
<div class="title">{{step.title}}</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</mat-sidenav>
|
||||
<!-- / SIDENAV -->
|
||||
|
||||
<!-- CENTER -->
|
||||
<div class="center">
|
||||
|
||||
<!-- HEADER -->
|
||||
<div class="header mat-accent-bg p-24" fxLayout="row" fxLayoutAlign="start center">
|
||||
|
||||
<button mat-button class="mat-icon-button mr-16 sidenav-toggle"
|
||||
fuseMatSidenavToggler="academy-left-sidenav" fxHide.gt-md>
|
||||
<mat-icon>menu</mat-icon>
|
||||
</button>
|
||||
|
||||
<button mat-button class="mat-icon-button mr-16" [routerLink]="'/apps/academy/courses'">
|
||||
<mat-icon>arrow_back</mat-icon>
|
||||
</button>
|
||||
|
||||
<div>
|
||||
<h2>{{course.title}}</h2>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<!-- / HEADER -->
|
||||
|
||||
<!-- CONTENT -->
|
||||
<div id="course-content" class="content">
|
||||
|
||||
<ng-container *ngFor="let step of course.steps; let i = index;">
|
||||
|
||||
<div class="course-step" fusePerfectScrollbar
|
||||
*ngIf="currentStep === i"
|
||||
[@slideIn]="animationDirection">
|
||||
|
||||
<div id="course-step-content" class="course-step-content" [innerHTML]="step.content"></div>
|
||||
|
||||
</div>
|
||||
|
||||
</ng-container>
|
||||
|
||||
</div>
|
||||
<!-- / CONTENT -->
|
||||
|
||||
<div class="step-navigation">
|
||||
|
||||
<button mat-fab class="previous mat-accent white-fg"
|
||||
(click)="gotoPreviousStep()"
|
||||
[disabled]="currentStep === 0"
|
||||
[fxHide]="currentStep === 0">
|
||||
<mat-icon>chevron_left</mat-icon>
|
||||
</button>
|
||||
|
||||
<button mat-fab class="next mat-accent white-fg"
|
||||
(click)="gotoNextStep()"
|
||||
[disabled]="currentStep === course.totalSteps - 1"
|
||||
[fxHide]="currentStep === course.totalSteps - 1">
|
||||
<mat-icon>chevron_right</mat-icon>
|
||||
</button>
|
||||
|
||||
<button mat-fab class="done mat-green-600-bg"
|
||||
routerLink="/apps/academy/courses"
|
||||
[disabled]="currentStep !== course.totalSteps - 1"
|
||||
[fxShow]="currentStep === course.totalSteps - 1">
|
||||
<mat-icon>check</mat-icon>
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<!-- / CENTER -->
|
||||
|
||||
</mat-sidenav-container>
|
||||
|
||||
</div>
|
||||
202
src/app/main/content/apps/academy/course/course.component.scss
Normal file
202
src/app/main/content/apps/academy/course/course.component.scss
Normal file
@@ -0,0 +1,202 @@
|
||||
@import "src/app/core/scss/fuse";
|
||||
|
||||
#academy-course {
|
||||
|
||||
.mat-drawer-container {
|
||||
flex: 1 !important;
|
||||
|
||||
> .mat-drawer-content {
|
||||
flex: 1 !important;
|
||||
|
||||
@include media-breakpoint-up('lg') {
|
||||
z-index: 52;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sidenav {
|
||||
|
||||
.steps {
|
||||
padding: 16px 0;
|
||||
|
||||
.step {
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
padding: 16px;
|
||||
cursor: pointer;
|
||||
color: rgba(0, 0, 0, 0.54);
|
||||
|
||||
&.current {
|
||||
background: mat-color($mat-blue, 50);
|
||||
color: rgba(0, 0, 0, 0.87);
|
||||
|
||||
.index {
|
||||
|
||||
span {
|
||||
border: 2px solid mat-color($mat-blue, 500);
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
&.completed {
|
||||
color: rgba(0, 0, 0, 0.87);
|
||||
|
||||
.index {
|
||||
|
||||
span {
|
||||
border: 2px solid mat-color($mat-blue, 500);
|
||||
}
|
||||
|
||||
&:after {
|
||||
border-left-color: mat-color($mat-blue, 500);
|
||||
}
|
||||
}
|
||||
|
||||
+ .step {
|
||||
|
||||
.index {
|
||||
|
||||
&:before {
|
||||
border-left-color: mat-color($mat-blue, 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.first {
|
||||
|
||||
.index {
|
||||
|
||||
&:before {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.last {
|
||||
|
||||
.index {
|
||||
|
||||
&:after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.index {
|
||||
display: flex;
|
||||
margin-right: 12px;
|
||||
|
||||
span {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
width: 28px;
|
||||
min-width: 28px;
|
||||
max-width: 28px;
|
||||
height: 28px;
|
||||
background: white;
|
||||
border-radius: 100%;
|
||||
border: 2px solid mat-color($mat-grey, 500);
|
||||
font-weight: bold;
|
||||
font-size: 12px;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
&:before,
|
||||
&:after {
|
||||
position: absolute;
|
||||
display: block;
|
||||
content: ' ';
|
||||
border-left: 1px solid mat-color($mat-grey, 300);
|
||||
width: 1px;
|
||||
height: 50%;
|
||||
left: 29px;
|
||||
z-index: 8;
|
||||
}
|
||||
|
||||
&:before {
|
||||
top: 0;
|
||||
}
|
||||
|
||||
&:after {
|
||||
bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.center {
|
||||
position: relative;
|
||||
|
||||
.header {
|
||||
height: 72px;
|
||||
min-height: 72px;
|
||||
max-height: 72px;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
background: mat-color($mat-grey, 200);
|
||||
|
||||
.course-step {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
padding: 48px;
|
||||
|
||||
@media (max-width: 720px) {
|
||||
padding: 0 0 120px 0;
|
||||
}
|
||||
|
||||
.course-step-content {
|
||||
padding: 24px;
|
||||
max-width: 720px;
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
background: white;
|
||||
@include mat-elevation(4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.step-navigation {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
bottom: 32px;
|
||||
max-width: 944px;
|
||||
padding: 0 24px;
|
||||
width: 100%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
|
||||
.previous {
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.next,
|
||||
.done {
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
101
src/app/main/content/apps/academy/course/course.component.ts
Normal file
101
src/app/main/content/apps/academy/course/course.component.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
import { AfterViewInit, ChangeDetectorRef, Component, OnDestroy, OnInit, QueryList, ViewChildren, ViewEncapsulation } from '@angular/core';
|
||||
import { AcademyCourseService } from '../course.service';
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
import { FusePerfectScrollbarDirective } from '../../../../../core/directives/fuse-perfect-scrollbar/fuse-perfect-scrollbar.directive';
|
||||
import { fuseAnimations } from '../../../../../core/animations';
|
||||
|
||||
@Component({
|
||||
selector : 'fuse-academy-course',
|
||||
templateUrl : './course.component.html',
|
||||
styleUrls : ['./course.component.scss'],
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
animations : fuseAnimations
|
||||
})
|
||||
export class FuseAcademyCourseComponent implements OnInit, OnDestroy, AfterViewInit
|
||||
{
|
||||
course: any;
|
||||
courseSubscription: Subscription;
|
||||
currentStep = 0;
|
||||
courseStepContent;
|
||||
animationDirection: 'left' | 'right' | 'none' = 'none';
|
||||
@ViewChildren(FusePerfectScrollbarDirective) fuseScrollbarDirectives: QueryList<FusePerfectScrollbarDirective>;
|
||||
|
||||
constructor(
|
||||
private courseService: AcademyCourseService,
|
||||
private changeDetectorRef: ChangeDetectorRef
|
||||
)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
ngOnInit()
|
||||
{
|
||||
// Subscribe to courses
|
||||
this.courseSubscription =
|
||||
this.courseService.onCourseChanged
|
||||
.subscribe(course => {
|
||||
this.course = course;
|
||||
});
|
||||
}
|
||||
|
||||
ngAfterViewInit()
|
||||
{
|
||||
this.courseStepContent = this.fuseScrollbarDirectives.find((fuseScrollbarDirective) => {
|
||||
return fuseScrollbarDirective.element.nativeElement.id === 'course-step-content';
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy()
|
||||
{
|
||||
this.courseSubscription.unsubscribe();
|
||||
}
|
||||
|
||||
gotoStep(step)
|
||||
{
|
||||
// Decide the animation direction
|
||||
this.animationDirection = this.currentStep < step ? 'left' : 'right';
|
||||
|
||||
// Run change detection so the change
|
||||
// in the animation direction registered
|
||||
this.changeDetectorRef.detectChanges();
|
||||
|
||||
// Set the current step
|
||||
this.currentStep = step;
|
||||
}
|
||||
|
||||
gotoNextStep()
|
||||
{
|
||||
if ( this.currentStep === this.course.totalSteps - 1 )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the animation direction
|
||||
this.animationDirection = 'left';
|
||||
|
||||
// Run change detection so the change
|
||||
// in the animation direction registered
|
||||
this.changeDetectorRef.detectChanges();
|
||||
|
||||
// Increase the current step
|
||||
this.currentStep++;
|
||||
}
|
||||
|
||||
gotoPreviousStep()
|
||||
{
|
||||
if ( this.currentStep === 0 )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the animation direction
|
||||
this.animationDirection = 'right';
|
||||
|
||||
// Run change detection so the change
|
||||
// in the animation direction registered
|
||||
this.changeDetectorRef.detectChanges();
|
||||
|
||||
// Decrease the current step
|
||||
this.currentStep--;
|
||||
}
|
||||
}
|
||||
62
src/app/main/content/apps/academy/courses.service.ts
Normal file
62
src/app/main/content/apps/academy/courses.service.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
|
||||
|
||||
@Injectable()
|
||||
export class AcademyCoursesService implements Resolve<any>
|
||||
{
|
||||
onCategoriesChanged: BehaviorSubject<any> = new BehaviorSubject({});
|
||||
onCoursesChanged: BehaviorSubject<any> = new BehaviorSubject({});
|
||||
|
||||
constructor(private http: HttpClient)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* The Academy App Main Resolver
|
||||
*
|
||||
* @param {ActivatedRouteSnapshot} route
|
||||
* @param {RouterStateSnapshot} state
|
||||
* @returns {Observable<any> | Promise<any> | any}
|
||||
*/
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<any> | Promise<any> | any
|
||||
{
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
Promise.all([
|
||||
this.getCategories(),
|
||||
this.getCourses()
|
||||
]).then(
|
||||
() => {
|
||||
resolve();
|
||||
},
|
||||
reject
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
getCategories(): Promise<any>
|
||||
{
|
||||
return new Promise((resolve, reject) => {
|
||||
this.http.get('api/academy-categories')
|
||||
.subscribe((response: any) => {
|
||||
this.onCategoriesChanged.next(response);
|
||||
resolve(response);
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
|
||||
getCourses(): Promise<any>
|
||||
{
|
||||
return new Promise((resolve, reject) => {
|
||||
this.http.get('api/academy-courses')
|
||||
.subscribe((response: any) => {
|
||||
this.onCoursesChanged.next(response);
|
||||
resolve(response);
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
<div id="academy-courses" class="page-layout simple" fusePerfectScrollbar>
|
||||
|
||||
<!-- HEADER -->
|
||||
<div class="header mat-accent-bg p-16 p-sm-24" fxLayout="column" fxLayoutAlign="center center">
|
||||
|
||||
<div class="hero-text">
|
||||
<mat-icon class="hero-icon">school</mat-icon>
|
||||
<h1>WELCOME TO ACADEMY</h1>
|
||||
<h3>
|
||||
Our courses will step you through the process of building a small application, or adding a new feature
|
||||
to an existing application.
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<!-- / HEADER -->
|
||||
|
||||
<!-- CONTENT -->
|
||||
<div class="content p-24">
|
||||
|
||||
<div fxLayout="column" fxLayoutAlign="center">
|
||||
|
||||
<div class="filters" fxLayout="column" fxLayoutAlign="center center"
|
||||
fxLayout.gt-xs="row" fxLayoutAlign.gt-xs="space-between center">
|
||||
|
||||
<mat-form-field class="course-search">
|
||||
|
||||
<input matInput placeholder="Search for a course" [(ngModel)]="searchTerm"
|
||||
(input)="filterCoursesByTerm()">
|
||||
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="category-selector">
|
||||
|
||||
<mat-select placeholder="Select Category" [(ngModel)]="currentCategory"
|
||||
(change)="filterCoursesByCategory()">
|
||||
<mat-option [value]="'all'">
|
||||
All
|
||||
</mat-option>
|
||||
<mat-option *ngFor="let category of categories" [value]="category.value">
|
||||
{{ category.label }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
|
||||
</mat-form-field>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="courses" fxLayout="row" fxLayoutAlign="center" fxLayoutWrap>
|
||||
|
||||
<div class="course" *ngFor="let course of filteredCourses" fxFlex="100" fxFlex.gt-xs="50"
|
||||
fxFlex.gt-sm="33" [ngClass]="course.category">
|
||||
|
||||
<div class="course-content" fxLayout="column">
|
||||
|
||||
<div class="header" fxLayout="row" fxLayoutAlign="center center"
|
||||
[ngClass]="course.category + '-bg'">
|
||||
|
||||
<div class="category" fxFlex>
|
||||
{{course.category}}
|
||||
</div>
|
||||
|
||||
<div class="length" fxLayout="row" fxLayoutAlign="center center">
|
||||
<mat-icon class="length-icon s-20">access_time</mat-icon>
|
||||
<div class="min">{{course.length}} min</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="content" fxLayout="column" fxLayoutAlign="center center" fxFlex>
|
||||
<div class="h1">{{course.title}}</div>
|
||||
<div class="updated">Updated {{course.updated}}</div>
|
||||
</div>
|
||||
|
||||
<div class="footer" fxLayout="row" fxLayoutAlign="center center">
|
||||
<button mat-button color="accent"
|
||||
[routerLink]="'/apps/academy/courses/' + course.id + '/' + course.slug">
|
||||
START
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="no-courses" *ngIf="filteredCourses.length === 0">
|
||||
No courses found!
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<!-- / CONTENT -->
|
||||
|
||||
</div>
|
||||
188
src/app/main/content/apps/academy/courses/courses.component.scss
Normal file
188
src/app/main/content/apps/academy/courses/courses.component.scss
Normal file
@@ -0,0 +1,188 @@
|
||||
@import "src/app/core/scss/fuse";
|
||||
|
||||
#academy-courses {
|
||||
|
||||
.header {
|
||||
position: relative;
|
||||
flex: 1 0 auto;
|
||||
height: 280px;
|
||||
max-height: 280px;
|
||||
background: #1A237E;
|
||||
background: linear-gradient(to right, #0E2A3B 0%, #34306B 100%);
|
||||
text-align: center;
|
||||
|
||||
@include media-breakpoint('xs') {
|
||||
height: 240px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.hero-text {
|
||||
|
||||
.hero-icon {
|
||||
position: absolute;
|
||||
top: -64px;
|
||||
left: 0px;
|
||||
opacity: 0.04;
|
||||
font-size: 512px !important;
|
||||
width: 512px !important;
|
||||
height: 512px !important;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: white;
|
||||
font-size: 40px;
|
||||
font-weight: 300;
|
||||
letter-spacing: 0.01em;
|
||||
text-align: center;
|
||||
margin-top: 0;
|
||||
margin-bottom: 16px;
|
||||
|
||||
@include media-breakpoint-down('xs') {
|
||||
font-size: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
h3 {
|
||||
color: rgba(255, 255, 255, 0.75);
|
||||
max-width: 480px;
|
||||
text-align: center;
|
||||
font-weight: 300;
|
||||
letter-spacing: 0.03em;
|
||||
margin: 0;
|
||||
|
||||
@include media-breakpoint-down('xs') {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
|
||||
.category-selector {
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
.filters {
|
||||
width: 100%;
|
||||
max-width: 1040px;
|
||||
margin: 24px auto;
|
||||
padding: 0 20px;
|
||||
|
||||
@include media-breakpoint-down('xs') {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.course-search {
|
||||
width: 200px;
|
||||
|
||||
@include media-breakpoint-down('xs') {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
@include media-breakpoint-up('sm') {
|
||||
margin-right: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.courses {
|
||||
width: 100%;
|
||||
max-width: 1040px;
|
||||
margin: 0 auto;
|
||||
|
||||
.no-courses {
|
||||
font-size: 24px;
|
||||
margin: 24px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.course {
|
||||
padding: 16px;
|
||||
|
||||
&:hover {
|
||||
|
||||
.course-content {
|
||||
@include mat-elevation(8);
|
||||
}
|
||||
}
|
||||
|
||||
.course-content {
|
||||
background: white;
|
||||
min-height: 240px;
|
||||
transition: box-shadow 150ms ease-in-out;
|
||||
|
||||
@include mat-elevation(1);
|
||||
|
||||
.header {
|
||||
color: white;
|
||||
padding: 16px 24px;
|
||||
height: 64px !important;
|
||||
min-height: 64px !important;
|
||||
max-height: 64px !important;
|
||||
|
||||
&.web-bg {
|
||||
background: mat-color($mat-blue, 500);
|
||||
}
|
||||
|
||||
&.android-bg {
|
||||
background: mat-color($mat-green, 500);
|
||||
}
|
||||
|
||||
&.firebase-bg {
|
||||
background: mat-color($mat-amber, 800);
|
||||
}
|
||||
|
||||
&.cloud-bg {
|
||||
background: mat-color($mat-blue-grey, 500);
|
||||
}
|
||||
|
||||
.category {
|
||||
text-transform: capitalize;
|
||||
text-align: left;
|
||||
font-weight: 500;
|
||||
color: rgba(0, 0, 0, 0.54);
|
||||
}
|
||||
|
||||
.length {
|
||||
|
||||
.length-icon {
|
||||
margin-right: 8px;
|
||||
color: rgba(0, 0, 0, 0.54) !important;
|
||||
}
|
||||
|
||||
.min {
|
||||
font-size: 16px;
|
||||
color: rgba(0, 0, 0, 0.54);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 24px;
|
||||
|
||||
.h1 {
|
||||
font-size: 16px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.updated {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
margin-top: 4px;
|
||||
color: rgba(0, 0, 0, 0.37);
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
padding: 16px;
|
||||
height: 48px !important;
|
||||
min-height: 48px !important;
|
||||
max-height: 48px !important;
|
||||
box-shadow: inset 0 1px 0 0 rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
import { AcademyCoursesService } from '../courses.service';
|
||||
|
||||
@Component({
|
||||
selector : 'fuse-academy-courses',
|
||||
templateUrl: './courses.component.html',
|
||||
styleUrls : ['./courses.component.scss']
|
||||
})
|
||||
export class FuseAcademyCoursesComponent implements OnInit, OnDestroy
|
||||
{
|
||||
categories: any[];
|
||||
courses: any[];
|
||||
coursesFilteredByCategory: any[];
|
||||
filteredCourses: any[];
|
||||
|
||||
categoriesSubscription: Subscription;
|
||||
coursesSubscription: Subscription;
|
||||
|
||||
currentCategory = 'all';
|
||||
searchTerm = '';
|
||||
|
||||
constructor(
|
||||
private coursesService: AcademyCoursesService
|
||||
)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
ngOnInit()
|
||||
{
|
||||
// Subscribe to categories
|
||||
this.categoriesSubscription =
|
||||
this.coursesService.onCategoriesChanged
|
||||
.subscribe(categories => {
|
||||
this.categories = categories;
|
||||
});
|
||||
|
||||
// Subscribe to courses
|
||||
this.coursesSubscription =
|
||||
this.coursesService.onCoursesChanged
|
||||
.subscribe(courses => {
|
||||
this.filteredCourses = this.coursesFilteredByCategory = this.courses = courses;
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy()
|
||||
{
|
||||
this.categoriesSubscription.unsubscribe();
|
||||
this.coursesSubscription.unsubscribe();
|
||||
}
|
||||
|
||||
filterCoursesByCategory()
|
||||
{
|
||||
// Filter
|
||||
if ( this.currentCategory === 'all' )
|
||||
{
|
||||
this.coursesFilteredByCategory = this.courses;
|
||||
this.filteredCourses = this.courses;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.coursesFilteredByCategory = this.courses.filter((course) => {
|
||||
return course.category === this.currentCategory;
|
||||
});
|
||||
|
||||
this.filteredCourses = [...this.coursesFilteredByCategory];
|
||||
|
||||
}
|
||||
|
||||
// Re-filter by search term
|
||||
this.filterCoursesByTerm();
|
||||
}
|
||||
|
||||
filterCoursesByTerm()
|
||||
{
|
||||
const searchTerm = this.searchTerm.toLowerCase();
|
||||
|
||||
// Search
|
||||
if ( searchTerm === '' )
|
||||
{
|
||||
this.filteredCourses = this.coursesFilteredByCategory;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.filteredCourses = this.coursesFilteredByCategory.filter((course) => {
|
||||
return course.title.toLowerCase().includes(searchTerm);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -216,52 +216,52 @@
|
||||
}
|
||||
|
||||
&.Jan {
|
||||
background-image: url('/assets/images/backgrounds/january.jpg');
|
||||
background-position: 0 45%;
|
||||
background-image: url('/assets/images/backgrounds/winter.jpg');
|
||||
background-position: 0 85%;
|
||||
}
|
||||
&.Feb {
|
||||
background-image: url('/assets/images/backgrounds/february.jpg');
|
||||
background-position: 0 50%;
|
||||
background-image: url('/assets/images/backgrounds/winter.jpg');
|
||||
background-position: 0 85%;
|
||||
}
|
||||
&.Mar {
|
||||
background-image: url('/assets/images/backgrounds/march.jpg');
|
||||
background-position: 0 45%;
|
||||
background-image: url('/assets/images/backgrounds/spring.jpg');
|
||||
background-position: 0 40%;
|
||||
}
|
||||
&.Apr {
|
||||
background-image: url('/assets/images/backgrounds/april.jpg');
|
||||
background-position: 0 48%;
|
||||
background-image: url('/assets/images/backgrounds/spring.jpg');
|
||||
background-position: 0 40%;
|
||||
}
|
||||
&.May {
|
||||
background-image: url('/assets/images/backgrounds/may.jpg');
|
||||
background-position: 0 47%;
|
||||
background-image: url('/assets/images/backgrounds/spring.jpg');
|
||||
background-position: 0 40%;
|
||||
}
|
||||
&.Jun {
|
||||
background-image: url('/assets/images/backgrounds/june.jpg');
|
||||
background-position: 0 48%;
|
||||
background-image: url('/assets/images/backgrounds/summer.jpg');
|
||||
background-position: 0 80%;
|
||||
}
|
||||
&.Jul {
|
||||
background-image: url('/assets/images/backgrounds/july.jpg');
|
||||
background-position: 0 3%;
|
||||
background-image: url('/assets/images/backgrounds/summer.jpg');
|
||||
background-position: 0 80%;
|
||||
}
|
||||
&.Aug {
|
||||
background-image: url('/assets/images/backgrounds/august.jpg');
|
||||
background-position: 0 61%;
|
||||
background-image: url('/assets/images/backgrounds/summer.jpg');
|
||||
background-position: 0 80%;
|
||||
}
|
||||
&.Sep {
|
||||
background-image: url('/assets/images/backgrounds/september.jpg');
|
||||
background-position: 0 58%;
|
||||
background-image: url('/assets/images/backgrounds/autumn.jpg');
|
||||
background-position: 0 40%;
|
||||
}
|
||||
&.Oct {
|
||||
background-image: url('/assets/images/backgrounds/october.jpg');
|
||||
background-position: 0 50%;
|
||||
background-image: url('/assets/images/backgrounds/autumn.jpg');
|
||||
background-position: 0 40%;
|
||||
}
|
||||
&.Nov {
|
||||
background-image: url('/assets/images/backgrounds/november.jpg');
|
||||
background-position: 0 46%;
|
||||
background-image: url('/assets/images/backgrounds/autumn.jpg');
|
||||
background-position: 0 40%;
|
||||
}
|
||||
&.Dec {
|
||||
background-image: url('/assets/images/backgrounds/december.jpg');
|
||||
background-position: 0 43%;
|
||||
background-image: url('/assets/images/backgrounds/winter.jpg');
|
||||
background-position: 0 85%;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
@@ -299,7 +299,7 @@
|
||||
.add-event-button {
|
||||
position: absolute;
|
||||
right: 18px;
|
||||
bottom: -32px;
|
||||
bottom: -26px;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
|
||||
@@ -32,9 +32,11 @@
|
||||
</mat-slide-toggle>
|
||||
</div>
|
||||
|
||||
<div class="py-16" fxFlex="1 0 auto" fxLayout="row" formGroupName="color">
|
||||
<mat-form-field class="mr-24" fxFlex>
|
||||
<div class="py-16" fxFlex="1 0 auto" fxLayout="column" fxLayout.gt-xs="row" formGroupName="color">
|
||||
|
||||
<mat-form-field class="mr-sm-24" fxFlex>
|
||||
<input matInput
|
||||
class="primary-color-input"
|
||||
name="primary color"
|
||||
formControlName="primary"
|
||||
placeholder="Primary color"
|
||||
@@ -44,8 +46,10 @@
|
||||
[style.background]="event.color.primary"
|
||||
(colorPickerChange)="event.color.primary = $event; eventForm.patchValue({color:{primary:$event}})"/>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field fxFlex>
|
||||
<input matInput
|
||||
class="secondary-color-input"
|
||||
name="secondary color"
|
||||
formControlName="secondary"
|
||||
placeholder="Secondary color"
|
||||
@@ -55,11 +59,12 @@
|
||||
[style.background]="event.color.secondary"
|
||||
(colorPickerChange)="event.color.secondary = $event; eventForm.patchValue({color:{secondary:$event}})"/>
|
||||
</mat-form-field>
|
||||
|
||||
</div>
|
||||
|
||||
<div fxFlex="1 0 auto" fxLayout="row">
|
||||
<div fxFlex="1 0 auto" fxLayout="column" fxLayout.gt-xs="row">
|
||||
|
||||
<mat-form-field class="mr-24" fxFlex>
|
||||
<mat-form-field class="mr-sm-24" fxFlex>
|
||||
<input matInput [matDatepicker]="startDatePicker" placeholder="Start Date"
|
||||
name="start"
|
||||
formControlName="start">
|
||||
@@ -73,9 +78,9 @@
|
||||
|
||||
</div>
|
||||
|
||||
<div fxFlex="1 0 auto" fxLayout="row">
|
||||
<div fxFlex="1 0 auto" fxLayout="column" fxLayout.gt-xs="row">
|
||||
|
||||
<mat-form-field class="mr-24" fxFlex>
|
||||
<mat-form-field class="mr-sm-24" fxFlex>
|
||||
<input matInput [matDatepicker]="endDatePicker" placeholder="End Date"
|
||||
name="end"
|
||||
formControlName="end">
|
||||
@@ -98,12 +103,12 @@
|
||||
|
||||
<mat-form-field formGroupName="meta" class="w-100-p">
|
||||
|
||||
<textarea matInput
|
||||
formControlName="notes"
|
||||
placeholder="Notes"
|
||||
mat-maxlength="250"
|
||||
max-rows="4">
|
||||
</textarea>
|
||||
<textarea matInput
|
||||
formControlName="notes"
|
||||
placeholder="Notes"
|
||||
mat-maxlength="250"
|
||||
max-rows="4">
|
||||
</textarea>
|
||||
</mat-form-field>
|
||||
|
||||
</form>
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
@import "src/app/core/scss/fuse";
|
||||
|
||||
.event-form-dialog {
|
||||
|
||||
@include media-breakpoint('xs') {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@include media-breakpoint-up('xs') {
|
||||
width: 480px;
|
||||
}
|
||||
|
||||
.mat-dialog-container {
|
||||
padding: 0;
|
||||
width: 480px;
|
||||
}
|
||||
|
||||
.dialog-content-wrapper {
|
||||
@@ -10,4 +19,9 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.primary-color-input,
|
||||
.secondary-color-input {
|
||||
padding: 6px 8px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,15 +2,18 @@
|
||||
|
||||
.contact-form-dialog {
|
||||
|
||||
@include media-breakpoint('xs') {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@include media-breakpoint-up('xs') {
|
||||
width: 400px;
|
||||
}
|
||||
|
||||
.mat-dialog-container {
|
||||
padding: 0;
|
||||
width: 400px;
|
||||
overflow: hidden;
|
||||
|
||||
@include media-breakpoint('xs') {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.mat-toolbar {
|
||||
min-height: initial;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import { Subject } from 'rxjs/Subject';
|
||||
@Injectable()
|
||||
export class ContactsService implements Resolve<any>
|
||||
{
|
||||
onContactsChanged: BehaviorSubject<any> = new BehaviorSubject({});
|
||||
onContactsChanged: BehaviorSubject<any> = new BehaviorSubject([]);
|
||||
onSelectedContactsChanged: BehaviorSubject<any> = new BehaviorSubject([]);
|
||||
onUserDataChanged: BehaviorSubject<any> = new BehaviorSubject([]);
|
||||
onSearchTextChanged: Subject<any> = new Subject();
|
||||
|
||||
@@ -703,7 +703,7 @@
|
||||
|
||||
<div class="pl-16 pr-8 py-16" fxLayout="row" fxLayoutAlign="space-between center">
|
||||
|
||||
<div class="h3">{{dateNow | date: 'EEEE, h:m:ss'}}</div>
|
||||
<div class="h3">{{dateNow | date: 'EEEE, HH:mm:ss'}}</div>
|
||||
|
||||
<div>
|
||||
<button mat-icon-button [matMenuTriggerFor]="moreMenu" aria-label="more">
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
import { Component, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';
|
||||
import { EcommerceDashboardService } from './dashboard.service';
|
||||
import * as shape from 'd3-shape';
|
||||
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { DataSource } from '@angular/cdk/collections';
|
||||
import { fuseAnimations } from '../../../../../core/animations';
|
||||
|
||||
@Component({
|
||||
@@ -23,14 +19,10 @@ export class FuseEcommerceDashboardComponent implements OnInit, OnDestroy
|
||||
widget6: any = {};
|
||||
widget7: any = {};
|
||||
|
||||
dateNow = Date.now();
|
||||
|
||||
constructor(private projectsDashboardService: EcommerceDashboardService)
|
||||
{
|
||||
this.projects = this.projectsDashboardService.projects;
|
||||
|
||||
this.selectedProject = this.projects[0];
|
||||
|
||||
this.widgets = this.projectsDashboardService.widgets;
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Component, ElementRef, OnDestroy, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
|
||||
import { Component, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';
|
||||
import { EcommerceOrderService } from './order.service';
|
||||
import { fuseAnimations } from '../../../../../core/animations';
|
||||
import 'rxjs/add/operator/startWith';
|
||||
@@ -10,9 +10,6 @@ import 'rxjs/add/observable/fromEvent';
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
import { Order } from './order.model';
|
||||
import { FormBuilder, FormGroup } from '@angular/forms';
|
||||
import { FuseUtils } from '../../../../../core/fuseUtils';
|
||||
import { MatSnackBar } from '@angular/material';
|
||||
import { Location } from '@angular/common';
|
||||
import { orderStatuses } from './order-statuses';
|
||||
|
||||
@Component({
|
||||
@@ -32,8 +29,6 @@ export class FuseEcommerceOrderComponent implements OnInit, OnDestroy
|
||||
constructor(
|
||||
private orderService: EcommerceOrderService,
|
||||
private formBuilder: FormBuilder,
|
||||
public snackBar: MatSnackBar,
|
||||
private location: Location
|
||||
)
|
||||
{
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
.orders-table {
|
||||
flex: 1 1 auto;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, .12);
|
||||
overflow: auto;
|
||||
|
||||
.mat-header-row {
|
||||
min-height: 64px;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Component, ElementRef, OnDestroy, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
|
||||
import { Component, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';
|
||||
import { EcommerceProductService } from './product.service';
|
||||
import { fuseAnimations } from '../../../../../core/animations';
|
||||
import 'rxjs/add/operator/startWith';
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
.products-table {
|
||||
flex: 1 1 auto;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, .12);
|
||||
overflow: auto;
|
||||
|
||||
.mat-header-row {
|
||||
min-height: 64px;
|
||||
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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: ['']
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
14
src/app/main/content/apps/mail-ngrx/i18n/en.ts
Normal file
14
src/app/main/content/apps/mail-ngrx/i18n/en.ts
Normal 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'
|
||||
}
|
||||
}
|
||||
};
|
||||
14
src/app/main/content/apps/mail-ngrx/i18n/tr.ts
Normal file
14
src/app/main/content/apps/mail-ngrx/i18n/tr.ts
Normal 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'
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
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);
|
||||
this.markAsRead();
|
||||
}
|
||||
|
||||
markAsRead()
|
||||
{
|
||||
if ( this.mail && !this.mail.read )
|
||||
{
|
||||
this.mail.markRead();
|
||||
this.updateMail();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -0,0 +1,130 @@
|
||||
@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;
|
||||
background: #FAFAFA;
|
||||
|
||||
&.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%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
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;
|
||||
@HostBinding('class.unread') unread: 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.unread = !this.mail.read;
|
||||
|
||||
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()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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[];
|
||||
@Input() 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()
|
||||
{
|
||||
}
|
||||
}
|
||||
116
src/app/main/content/apps/mail-ngrx/mail.component.html
Normal file
116
src/app/main/content/apps/mail-ngrx/mail.component.html
Normal 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$ | async}">
|
||||
|
||||
<!-- 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$ | async" fxHide.gt-xs>
|
||||
<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" [currentMail]="currentMail$ | 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>
|
||||
81
src/app/main/content/apps/mail-ngrx/mail.component.scss
Normal file
81
src/app/main/content/apps/mail-ngrx/mail.component.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
139
src/app/main/content/apps/mail-ngrx/mail.component.ts
Normal file
139
src/app/main/content/apps/mail-ngrx/mail.component.ts
Normal file
@@ -0,0 +1,139 @@
|
||||
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';
|
||||
import { FuseConfigService } from '../../../../core/services/config.service';
|
||||
|
||||
@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 configService: FuseConfigService,
|
||||
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 = [];
|
||||
|
||||
this.configService.setSettings({
|
||||
routerAnimation: 'none'
|
||||
});
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
deSelectCurrentMail()
|
||||
{
|
||||
this.store.dispatch(new fromStore.SetCurrentMail(''));
|
||||
}
|
||||
|
||||
refresh()
|
||||
{
|
||||
this.cd.markForCheck();
|
||||
}
|
||||
|
||||
ngOnDestroy()
|
||||
{
|
||||
this.cd.detach();
|
||||
}
|
||||
}
|
||||
66
src/app/main/content/apps/mail-ngrx/mail.model.ts
Normal file
66
src/app/main/content/apps/mail-ngrx/mail.model.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
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;
|
||||
}
|
||||
|
||||
markRead()
|
||||
{
|
||||
this.read = true;
|
||||
}
|
||||
|
||||
markUnRead()
|
||||
{
|
||||
this.read = false;
|
||||
}
|
||||
}
|
||||
73
src/app/main/content/apps/mail-ngrx/mail.module.ts
Normal file
73
src/app/main/content/apps/mail-ngrx/mail.module.ts
Normal 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
|
||||
{
|
||||
}
|
||||
87
src/app/main/content/apps/mail-ngrx/mail.service.ts
Normal file
87
src/app/main/content/apps/mail-ngrx/mail.service.ts
Normal 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});
|
||||
}
|
||||
}
|
||||
@@ -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 -->
|
||||
@@ -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 {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -0,0 +1,4 @@
|
||||
export * from './mails.actions';
|
||||
export * from './folders.actions';
|
||||
export * from './filters.actions';
|
||||
export * from './labels.actions';
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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)))
|
||||
);
|
||||
}
|
||||
));
|
||||
}
|
||||
@@ -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)))
|
||||
);
|
||||
}
|
||||
));
|
||||
}
|
||||
16
src/app/main/content/apps/mail-ngrx/store/effects/index.ts
Normal file
16
src/app/main/content/apps/mail-ngrx/store/effects/index.ts
Normal 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';
|
||||
@@ -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)))
|
||||
);
|
||||
}
|
||||
));
|
||||
}
|
||||
@@ -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);
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './resolve.guard';
|
||||
@@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
4
src/app/main/content/apps/mail-ngrx/store/index.ts
Normal file
4
src/app/main/content/apps/mail-ngrx/store/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export * from './actions';
|
||||
export * from './reducers';
|
||||
export * from './selectors';
|
||||
export * from './effects';
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
34
src/app/main/content/apps/mail-ngrx/store/reducers/index.ts
Normal file
34
src/app/main/content/apps/mail-ngrx/store/reducers/index.ts
Normal 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';
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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])
|
||||
);
|
||||
@@ -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])
|
||||
);
|
||||
@@ -0,0 +1,4 @@
|
||||
export * from './mails.selectors';
|
||||
export * from './folders.selectors';
|
||||
export * from './filters.selectors';
|
||||
export * from './labels.selectors';
|
||||
@@ -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])
|
||||
);
|
||||
@@ -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
|
||||
);
|
||||
16
src/app/main/content/apps/mail-ngrx/store/store.module.ts
Normal file
16
src/app/main/content/apps/mail-ngrx/store/store.module.ts
Normal 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
|
||||
{
|
||||
}
|
||||
@@ -28,7 +28,12 @@
|
||||
type="email" required>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field>
|
||||
<div class="show-hide-extra-fields" (click)="toggleExtraToFields()">
|
||||
<span [fxShow]="!showExtraToFields">Show CC & BCC</span>
|
||||
<span [fxShow]="showExtraToFields">Hide CC & BCC</span>
|
||||
</div>
|
||||
|
||||
<mat-form-field [fxShow]="showExtraToFields">
|
||||
<input matInput
|
||||
name="cc"
|
||||
placeholder="Cc"
|
||||
@@ -36,7 +41,7 @@
|
||||
type="email">
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field>
|
||||
<mat-form-field [fxShow]="showExtraToFields">
|
||||
<input matInput
|
||||
name="bcc"
|
||||
placeholder="Bcc"
|
||||
@@ -51,11 +56,11 @@
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field>
|
||||
<textarea matInput name="message"
|
||||
placeholder="Message"
|
||||
formControlName="message"
|
||||
rows="6">
|
||||
</textarea>
|
||||
<textarea matInput name="message"
|
||||
placeholder="Message"
|
||||
formControlName="message"
|
||||
rows="6">
|
||||
</textarea>
|
||||
</mat-form-field>
|
||||
|
||||
<div class="attachment-list">
|
||||
|
||||
@@ -1,7 +1,17 @@
|
||||
@import "src/app/core/scss/fuse";
|
||||
|
||||
.mail-compose-dialog {
|
||||
|
||||
@include media-breakpoint('xs') {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@include media-breakpoint-up('sm') {
|
||||
width: 640px;
|
||||
}
|
||||
|
||||
.mat-dialog-container {
|
||||
padding: 0;
|
||||
width: 720px;
|
||||
|
||||
.compose-form {
|
||||
|
||||
@@ -9,6 +19,12 @@
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.show-hide-extra-fields {
|
||||
text-align: right;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.attachment-list {
|
||||
font-size: 13px;
|
||||
padding-top: 16px;
|
||||
|
||||
@@ -10,6 +10,7 @@ import { FormControl, FormGroup } from '@angular/forms';
|
||||
})
|
||||
export class FuseMailComposeDialogComponent implements OnInit
|
||||
{
|
||||
showExtraToFields = false;
|
||||
composeForm: FormGroup;
|
||||
|
||||
constructor(
|
||||
@@ -39,4 +40,8 @@ export class FuseMailComposeDialogComponent implements OnInit
|
||||
});
|
||||
}
|
||||
|
||||
toggleExtraToFields()
|
||||
{
|
||||
this.showExtraToFields = !this.showExtraToFields;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ export const locale = {
|
||||
'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'
|
||||
'SEARCH_PLACEHOLDER' : 'Search for an e-mail or contact'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -85,7 +85,7 @@
|
||||
</mat-menu>
|
||||
</div>
|
||||
|
||||
<div *ngIf="currentMail" fxHide.gt-lg>
|
||||
<div *ngIf="currentMail" fxHide.gt-xs>
|
||||
<button mat-icon-button (click)="deSelectCurrentMail()">
|
||||
<mat-icon>arrow_back</mat-icon>
|
||||
</button>
|
||||
|
||||
@@ -4,6 +4,8 @@ import { Subscription } from 'rxjs/Subscription';
|
||||
import { FormControl } from '@angular/forms';
|
||||
import { Mail } from './mail.model';
|
||||
import { FuseTranslationLoaderService } from '../../../../core/services/translation-loader.service';
|
||||
import 'rxjs/add/operator/debounceTime';
|
||||
import 'rxjs/add/operator/distinctUntilChanged';
|
||||
import { locale as english } from './i18n/en';
|
||||
import { locale as turkish } from './i18n/tr';
|
||||
|
||||
@@ -80,12 +82,12 @@ export class FuseMailComponent implements OnInit, OnDestroy
|
||||
}
|
||||
});
|
||||
|
||||
/*this.searchInput.valueChanges
|
||||
this.searchInput.valueChanges
|
||||
.debounceTime(300)
|
||||
.distinctUntilChanged()
|
||||
.subscribe(searchText => {
|
||||
this.mailService.onSearchTextChanged.next(searchText);
|
||||
});*/
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy()
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Observable } from 'rxjs/Observable';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Mail } from './mail.model';
|
||||
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
|
||||
import { FuseUtils } from 'app/core/fuseUtils';
|
||||
import { FuseUtils } from '../../../../core/fuseUtils';
|
||||
|
||||
@Injectable()
|
||||
export class MailService implements Resolve<any>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user