Compare commits

...

59 Commits

Author SHA1 Message Date
Sercan Yemen
8b590408b0 Updated Angular & Angular Material
+ Increased the Fuse version
2018-01-11 12:57:38 +03:00
Sercan Yemen
18b2bdf5ab Mail-Ngrx app throws errors for certain rxjs operators
+ Various other small code fixes
2018-01-11 12:31:06 +03:00
Sercan Yemen
ba49621e79 Update perfect scrollbar on document click...
This isn't the most elegant solution but there is no other way
of knowing when the contents of the scrollable container changes.
Therefore, we update scrollbars on every document click.
2018-01-09 11:30:11 +03:00
Sercan Yemen
416f1997a9 Updated Angular Material, Angular and various other modules to latest versions 2018-01-08 16:39:44 +03:00
Sercan Yemen
fcfbedfd74 Increase the version number to 1.3.2 2018-01-08 16:02:45 +03:00
Sercan Yemen
35f3512e89 Added the [path] input to the fuse-highlight for loading source code externally
+ Removed highlight.js and angular2-markdown
+ Updated the Angular Material example viewer
2018-01-08 16:02:00 +03:00
Sercan Yemen
2288905cbd Merge branch 'master' of https://github.com/withinpixels/fuse2
+ fuse-hljs replaced with fuse-highlight
2018-01-08 15:29:04 +03:00
Sercan Yemen
5a40116c7b Merge branch 'master' of https://github.com/withinpixels/fuse2
+ fuse-hljs replaced with fuse-highlight
2018-01-08 15:28:50 +03:00
Sercan Yemen
b7c10a515c Added new cards 2018-01-08 12:40:39 +03:00
Sercan Yemen
65e637eeb9 Use [overlapTrigger] in toolbar menus 2018-01-08 12:40:25 +03:00
Sercan Yemen
b56088948c added --open to the "npm start" 2018-01-08 12:39:55 +03:00
mustafahlvc
0c5066e7d0 Lazy loading applied to group of demo modules. 2018-01-08 12:37:52 +03:00
Sercan Yemen
751497556a (Contacts App) Fixed: Selected filter is not preserved on route changes 2018-01-03 10:32:15 +03:00
Sercan Yemen
5f2372cc08 Updated Angular Calendar due to broken AoT 2018-01-02 12:29:32 +03:00
Sercan Yemen
6f315aa38e Small fixes to Auth pages 2018-01-02 12:00:46 +03:00
Sercan Yemen
0653b5f36b Academy app small tweaks and responsive fixes 2017-12-28 14:00:06 +03:00
Sercan Yemen
f13120bc01 Updated Academy example data 2017-12-28 11:53:02 +03:00
Sercan Yemen
a6c56518bc New: Academy (Courses) app 2017-12-28 10:49:29 +03:00
Sercan Yemen
ecae48f3d0 Updated Angular and Angular Material + various other components
+ Increased the Fuse version number
2017-12-28 10:49:04 +03:00
Sercan Yemen
c74751e0f4 Added new SlideIn Animation
+ Improvements on perfect scrollbar
2017-12-28 10:48:05 +03:00
Sercan Yemen
2b755fa669 Replaced depreciated Quick panel focus helpers 2017-12-28 10:47:19 +03:00
Sercan Yemen
ca0f46b414 Fix: Scroll doesn't propagate by default (PerfectScrollbar) 2017-12-26 10:48:58 +03:00
Sercan Yemen
528fa31df6 Fixed: (Firefox) Navbar logo doesn't show when navbar collapsed 2017-12-21 10:04:56 +03:00
Sercan Yemen
abfb2a6706 Fixed: Mail Compose dialog responsive issues
+ Added Show/Hide CC & BCC fields toggle button
2017-12-19 12:21:22 +03:00
Sercan Yemen
b1ab11393e Fix: (Ecommerce) Orders and Products tables not scrolling when custom scrollbars disabled 2017-12-19 10:27:33 +03:00
Sercan Yemen
786883eb10 Updated season images
+ Fixed e-commerce app images
+ Stop animations in mail-ngrx app
2017-12-18 12:24:47 +03:00
Sercan Yemen
e477f797d0 Updated Material Moment Adapter package 2017-12-14 16:06:07 +03:00
Sercan Yemen
fb196c3864 Angular & Angular Material versions updated 2017-12-14 15:51:09 +03:00
mustafahlvc
5cf44962fc (Mail) back arrow visibility updated for responsive 2017-12-14 12:27:08 +03:00
mustafahlvc
06b0c3775a (Mail-ngrx) missing deSelectCurrentFunction() added to mail.component,
back arrow visibility updated for responsive
2017-12-14 12:25:48 +03:00
mustafahlvc
26690990f0 (Mail-ngrx) currentMail fixes on html 2017-12-14 11:52:53 +03:00
mustafahlvc
377092d9ec (Mail-ngrx) unread, selected class added for mail list item 2017-12-14 11:10:35 +03:00
mustafahlvc
abede386c8 (Mail-ngrx) Ngrx version of Mail App added. 2017-12-12 22:31:50 +03:00
Sercan Yemen
242feaa169 Updated Angular to 5.0.1
+ Updated Angular Material to 5.0.0 stable
+ Install the ngx-dnd package from its original location
+ Updated various other packages
+ Increased the Fuse version
2017-12-08 10:34:39 +03:00
Sercan Yemen
7c2494a82c Fixed: Shortcut items duplicates on search in some cases 2017-12-08 10:31:39 +03:00
Sercan Yemen
5c2e717a40 Make sure the nav item has children before trying to get them
+ Added LICENSE file
+ Renamed the KnowledgeBase demo module
2017-12-08 09:34:30 +03:00
Sercan Yemen
6ae0a9760d Fix the missing backgrounds
+ Increased the shadow weight of the content on Auth, Coming Soon and Maintenance pages
2017-12-06 14:39:24 +03:00
Sercan Yemen
2a10f3e443 Moved the navigation.model.ts into its own folder
+ Added support for translations in nav items
+ Added badge support for collapsable nav items
+ Initialize the navigation from app.component rather then navigation.service
2017-12-06 14:10:48 +03:00
Sercan Yemen
db7a00440c Re-activated mail search
+ Small formatting in mail translation
2017-12-06 14:09:31 +03:00
Sercan Yemen
0e1c589399 Use absolute paths for importing fuse.scss 2017-12-06 11:40:43 +03:00
Sercan Yemen
2f419b1af5 Removed unused HttpModule import 2017-12-06 11:40:16 +03:00
Sercan Yemen
effd3cefcb Scrumboard and Contacts app dialogs responsive fixes 2017-12-06 11:35:58 +03:00
Sercan Yemen
21fd488a8e ngx-color-picker style adjustments
+ Calendar event form responsive fixes
2017-12-06 11:26:57 +03:00
Sercan Yemen
37a5c69269 Removed Google Calendar images as they are no longer available freely from Google 2017-12-06 10:34:40 +03:00
Sercan Yemen
9f440b1bf2 Some code inspection fixes 2017-11-30 15:55:00 +03:00
Sercan Yemen
a65f61cce4 Form Stepper examples
+ Simplified the mat-select style
+ Further simplified the mat-select in dashboard widgets
2017-11-30 15:37:32 +03:00
Sercan Yemen
0d8fe0be72 Trigger expand/collapse of the navigation on ngOnInit to update the active item 2017-11-30 10:37:35 +03:00
Sercan Yemen
2bbc90af64 Perfect scrollbar shouldn't be destroyed if it's not available
+ Print media css classes updated to hide the perfect scrollbars
2017-11-30 10:22:18 +03:00
Sercan Yemen
ad21d9fed5 Further improvements to Auth pages 2017-11-30 10:21:22 +03:00
Sercan Yemen
4c6ef29e20 Fixed: Behavior Subject must be array 2017-11-28 10:48:52 +03:00
Sercan Yemen
a74c5108fd Fixed: Couple small issues with Auth forms
Added custom validator for Password Matching to Register forms
2017-11-28 10:42:41 +03:00
Sercan Yemen
19fdbbdbcb Increased version number
Fixed: Sidenav helper causes issues on mobile media steps
2017-11-27 17:20:34 +03:00
Sercan Yemen
8bbabd7437 Corrected the version number 2017-11-27 14:31:57 +03:00
Sercan Yemen
5a9cd36282 Increased the Fuse version number
+ Updated Angular, Angular Material and Flex Layout
+ Updated various components for better Angular5 support
+ Fixed: Contacts app various issues
+ Fixed: Duplicate content in Profile page tabs
+ Fixed: Folded status of the Navbar shouldn't brake the layout if Horizontal Navbar is active
2017-11-27 14:28:08 +03:00
mustafahlvc
297bb95a2e Temporary aot build fix for @swimlane/ngx-charts 2017-11-18 01:43:08 +03:00
Sercan Yemen
2511a03b66 Increased the Fuse version number
+ Updated ngx-charts and ngx-datatables
+ added rxjs imports for the various search
2017-11-18 00:45:42 +03:00
mustafahlvc
76358f996e Angular material docs updated,
+ @angular/material-moment-adapter added to package.json
2017-11-17 18:35:44 +03:00
mustafahlvc
142fc982ca Angular updated to 5.0.2, angular-cli updated to 1.5.2,
+ angular-material-elements and fakeDb folder excluded from linting,
+ some lint errors solved.
2017-11-17 16:57:42 +03:00
Sercan Yemen
22d9279e3b Fix: Authentication (v2) pages not scrollable when custom scrollbars disabled 2017-11-14 09:35:03 +03:00
406 changed files with 12374 additions and 3967 deletions

View File

@@ -39,15 +39,27 @@
"lint": [ "lint": [
{ {
"project": "src/tsconfig.app.json", "project": "src/tsconfig.app.json",
"exclude": "**/node_modules/**" "exclude": [
"**/node_modules/**",
"**/src/app/fuse-fake-db/**/*",
"**/src/assets/angular-material-examples/**/*"
]
}, },
{ {
"project": "src/tsconfig.spec.json", "project": "src/tsconfig.spec.json",
"exclude": "**/node_modules/**" "exclude": [
"**/node_modules/**",
"**/src/app/fuse-fake-db/**/*",
"**/src/assets/angular-material-examples/**/*"
]
}, },
{ {
"project": "e2e/tsconfig.e2e.json", "project": "e2e/tsconfig.e2e.json",
"exclude": "**/node_modules/**" "exclude": [
"**/node_modules/**",
"**/src/app/fuse-fake-db/**/*",
"**/src/assets/angular-material-examples/**/*"
]
} }
], ],
"test": { "test": {

1
LICENSE Normal file
View File

@@ -0,0 +1 @@
https://themeforest.net/licenses/terms/regular

2339
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,74 +1,84 @@
{ {
"name": "fuse2", "name": "fuse2",
"version": "1.2.1", "version": "1.3.3",
"license": "", "license": "https://themeforest.net/licenses/terms/regular",
"scripts": { "scripts": {
"ng": "ng", "ng": "ng",
"start": "ng serve", "start": "ng serve --open",
"start-hmr": "ng serve --hmr -e=hmr -sm=false", "start-hmr": "ng serve --hmr -e=hmr -sm=false",
"start-hmr-sourcemaps": "ng serve --hmr -e=hmr", "start-hmr-sourcemaps": "ng serve --hmr -e=hmr",
"build": "node --max_old_space_size=6144 ./node_modules/@angular/cli/bin/ng build --dev", "build": "node --max_old_space_size=6144 ./node_modules/@angular/cli/bin/ng build --dev",
"build-stats": "node --max_old_space_size=6144 ./node_modules/@angular/cli/bin/ng build --dev --stats-json",
"build-prod": "node --max_old_space_size=6144 ./node_modules/@angular/cli/bin/ng build --prod", "build-prod": "node --max_old_space_size=6144 ./node_modules/@angular/cli/bin/ng build --prod",
"build-prod-stats": "node --max_old_space_size=6144 ./node_modules/@angular/cli/bin/ng build --prod --stats-json",
"test": "ng test", "test": "ng test",
"lint": "ng lint", "lint": "ng lint",
"e2e": "ng e2e" "e2e": "ng e2e",
"bundle-report": "webpack-bundle-analyzer dist/stats.json"
}, },
"private": true, "private": true,
"dependencies": { "dependencies": {
"@agm/core": "1.0.0-beta.2", "@agm/core": "1.0.0-beta.2",
"@angular/animations": "5.0.1", "@angular/animations": "5.2.0",
"@angular/cdk": "5.0.0-rc0", "@angular/cdk": "5.0.4",
"@angular/common": "5.0.1", "@angular/common": "5.2.0",
"@angular/compiler": "5.0.1", "@angular/compiler": "5.2.0",
"@angular/core": "5.0.1", "@angular/core": "5.2.0",
"@angular/flex-layout": "2.0.0-beta.10", "@angular/flex-layout": "2.0.0-beta.12",
"@angular/forms": "5.0.1", "@angular/forms": "5.2.0",
"@angular/http": "5.0.1", "@angular/http": "5.2.0",
"@angular/material": "5.0.0-rc0", "@angular/material": "5.0.4",
"@angular/platform-browser": "5.0.1", "@angular/material-moment-adapter": "5.0.4",
"@angular/platform-browser-dynamic": "5.0.1", "@angular/platform-browser": "5.2.0",
"@angular/router": "5.0.1", "@angular/platform-browser-dynamic": "5.2.0",
"@ngx-translate/core": "8.0.0", "@angular/router": "5.2.0",
"@swimlane/ngx-charts": "6.1.0", "@ngrx/effects": "4.1.1",
"@swimlane/ngx-datatable": "11.0.3", "@ngrx/router-store": "4.1.1",
"@withinpixels/ngx-dnd": "3.1.0", "@ngrx/store": "4.1.1",
"angular-calendar": "0.21.3", "@ngrx/store-devtools": "4.1.1",
"angular-in-memory-web-api": "0.5.1", "@ngx-translate/core": "9.0.2",
"angular2-markdown": "1.6.0", "@swimlane/ngx-charts": "7.0.1",
"@swimlane/ngx-datatable": "11.1.7",
"@swimlane/ngx-dnd": "3.1.0",
"@types/prismjs": "1.9.0",
"angular-calendar": "0.23.2",
"angular-in-memory-web-api": "0.5.2",
"classlist.js": "1.1.20150312", "classlist.js": "1.1.20150312",
"core-js": "2.5.1", "core-js": "2.5.3",
"d3": "4.11.0", "d3": "4.12.2",
"hammerjs": "2.0.8", "hammerjs": "2.0.8",
"highlight.js": "9.12.0",
"intl": "1.2.5", "intl": "1.2.5",
"moment": "2.19.2", "moment": "2.20.1",
"ngx-color-picker": "4.4.0", "ngrx-store-freeze": "0.2.0",
"ngx-cookie-service": "1.0.9", "ngx-color-picker": "5.3.0",
"perfect-scrollbar": "1.0.3", "ngx-cookie-service": "1.0.10",
"rxjs": "5.5.2", "perfect-scrollbar": "1.3.0",
"prismjs": "1.9.0",
"rxjs": "5.5.6",
"web-animations-js": "2.3.1", "web-animations-js": "2.3.1",
"zone.js": "0.8.18" "zone.js": "0.8.20"
}, },
"devDependencies": { "devDependencies": {
"@angular/cli": "1.5.0", "@angular/cli": "1.6.3",
"@angular/compiler-cli": "5.0.1", "@angular/compiler-cli": "5.2.0",
"@angular/language-service": "5.0.1", "@angular/language-service": "5.2.0",
"@angularclass/hmr": "2.1.3", "@angularclass/hmr": "2.1.3",
"@types/jasmine": "2.5.54", "@types/jasmine": "2.5.54",
"@types/jasminewd2": "2.0.3", "@types/jasminewd2": "2.0.3",
"@types/node": "6.0.90", "@types/node": "6.0.96",
"codelyzer": "3.2.2", "codelyzer": "4.0.2",
"jasmine-core": "2.6.4", "jasmine-core": "2.6.4",
"jasmine-spec-reporter": "4.1.1", "jasmine-spec-reporter": "4.1.1",
"karma": "1.7.1", "karma": "1.7.1",
"karma-chrome-launcher": "2.1.1", "karma-chrome-launcher": "2.1.1",
"karma-cli": "1.0.1", "karma-cli": "1.0.1",
"karma-coverage-istanbul-reporter": "1.3.0", "karma-coverage-istanbul-reporter": "1.3.3",
"karma-jasmine": "1.1.0", "karma-jasmine": "1.1.1",
"karma-jasmine-html-reporter": "0.2.2", "karma-jasmine-html-reporter": "0.2.2",
"protractor": "5.1.2", "protractor": "5.1.2",
"ts-node": "3.2.2", "ts-node": "3.2.2",
"tslint": "5.7.0", "tslint": "5.7.0",
"typescript": "2.4.2" "typescript": "2.4.2",
"webpack-bundle-analyzer": "2.9.2"
} }
} }

View File

@@ -1,6 +1,12 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { FuseSplashScreenService } from './core/services/splash-screen.service'; import { FuseSplashScreenService } from './core/services/splash-screen.service';
import { TranslateService } from '@ngx-translate/core'; 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({ @Component({
selector : 'fuse-root', selector : 'fuse-root',
@@ -10,8 +16,10 @@ import { TranslateService } from '@ngx-translate/core';
export class AppComponent export class AppComponent
{ {
constructor( constructor(
private fuseNavigationService: FuseNavigationService,
private fuseSplashScreen: FuseSplashScreenService, private fuseSplashScreen: FuseSplashScreenService,
private translate: TranslateService private translate: TranslateService,
private translationLoader: FuseTranslationLoaderService
) )
{ {
// Add languages // Add languages
@@ -22,5 +30,11 @@ export class AppComponent
// Use a language // Use a language
this.translate.use('en'); this.translate.use('en');
// Set the navigation model
this.fuseNavigationService.setNavigationModel(new FuseNavigationModel());
// Set the navigation translations
this.translationLoader.loadTranslations(navigationEnglish, navigationTurkish);
} }
} }

View File

@@ -1,6 +1,5 @@
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser'; import { BrowserModule } from '@angular/platform-browser';
import { HttpModule } from '@angular/http';
import { HttpClientModule } from '@angular/common/http'; import { HttpClientModule } from '@angular/common/http';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { RouterModule, Routes } from '@angular/router'; import { RouterModule, Routes } from '@angular/router';
@@ -8,53 +7,38 @@ import { InMemoryWebApiModule } from 'angular-in-memory-web-api';
import 'hammerjs'; import 'hammerjs';
import { SharedModule } from './core/modules/shared.module'; import { SharedModule } from './core/modules/shared.module';
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';
import { ProjectModule } from './main/content/apps/dashboards/project/project.module';
import { FuseFakeDbService } from './fuse-fake-db/fuse-fake-db.service'; import { FuseFakeDbService } from './fuse-fake-db/fuse-fake-db.service';
import { FuseMainModule } from './main/main.module'; import { FuseMainModule } from './main/main.module';
import { PagesModule } from './main/content/pages/pages.module';
import { UIModule } from './main/content/ui/ui.module';
import { ComponentsModule } from './main/content/components/components.module';
import { FuseSplashScreenService } from './core/services/splash-screen.service'; import { FuseSplashScreenService } from './core/services/splash-screen.service';
import { FuseConfigService } from './core/services/config.service'; import { FuseConfigService } from './core/services/config.service';
import { FuseNavigationService } from './core/components/navigation/navigation.service'; import { FuseNavigationService } from './core/components/navigation/navigation.service';
import { ComponentsThirdPartyModule } from './main/content/components-third-party/components-third-party.module';
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 { TranslateModule } from '@ngx-translate/core';
import { AppStoreModule } from './store/store.module';
const appRoutes: Routes = [ const appRoutes: Routes = [
{ {
path : 'apps/mail', path : 'apps',
loadChildren: './main/content/apps/mail/mail.module#FuseMailModule' loadChildren: './main/content/apps/apps.module#FuseAppsModule'
}, },
{ {
path : 'apps/chat', path : 'pages',
loadChildren: './main/content/apps/chat/chat.module#FuseChatModule' loadChildren: './main/content/pages/pages.module#FusePagesModule'
}, },
{ {
path : 'apps/calendar', path : 'ui',
loadChildren: './main/content/apps/calendar/calendar.module#FuseCalendarModule' loadChildren: './main/content/ui/ui.module#FuseUIModule'
}, },
{ {
path : 'apps/e-commerce', path : 'services',
loadChildren: './main/content/apps/e-commerce/e-commerce.module#FuseEcommerceModule' loadChildren: './main/content/services/services.module#FuseServicesModule'
}, },
{ {
path : 'apps/todo', path : 'components',
loadChildren: './main/content/apps/todo/todo.module#FuseTodoModule' loadChildren: './main/content/components/components.module#FuseComponentsModule'
}, },
{ {
path : 'apps/file-manager', path : 'components-third-party',
loadChildren: './main/content/apps/file-manager/file-manager.module#FuseFileManagerModule' loadChildren: './main/content/components-third-party/components-third-party.module#FuseComponentsThirdPartyModule'
},
{
path : 'apps/contacts',
loadChildren: './main/content/apps/contacts/contacts.module#FuseContactsModule'
},
{
path : 'apps/scrumboard',
loadChildren: './main/content/apps/scrumboard/scrumboard.module#FuseScrumboardModule'
}, },
{ {
path : '**', path : '**',
@@ -68,29 +52,17 @@ const appRoutes: Routes = [
], ],
imports : [ imports : [
BrowserModule, BrowserModule,
HttpModule,
HttpClientModule, HttpClientModule,
BrowserAnimationsModule, BrowserAnimationsModule,
RouterModule.forRoot(appRoutes), RouterModule.forRoot(appRoutes),
SharedModule, SharedModule,
MarkdownModule.forRoot(),
TranslateModule.forRoot(), TranslateModule.forRoot(),
InMemoryWebApiModule.forRoot(FuseFakeDbService, { InMemoryWebApiModule.forRoot(FuseFakeDbService, {
delay : 0, delay : 0,
passThruUnknownUrl: true passThruUnknownUrl: true
}), }),
AppStoreModule,
FuseMainModule, FuseMainModule
ProjectModule,
PagesModule,
UIModule,
ServicesModule,
ComponentsModule,
FuseAngularMaterialModule,
ComponentsThirdPartyModule
], ],
providers : [ providers : [
FuseSplashScreenService, FuseSplashScreenService,

View File

@@ -76,6 +76,53 @@ export const fuseAnimations = [
transition('0 => 1', animate('300ms ease-in')) 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', [ trigger('slideInLeft', [
state('void', style({ state('void', style({
transform: 'translateX(-100%)', transform: 'translateX(-100%)',

View File

@@ -0,0 +1,6 @@
:host {
display: block;
padding: 8px;
background: #263238;
cursor: text;
}

View File

@@ -0,0 +1,102 @@
import { Component, ContentChild, ElementRef, Input, OnInit } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import * as Prism from 'prismjs/prism';
import './prism-languages';
@Component({
selector : 'fuse-highlight',
template : ' ',
styleUrls: ['./highlight.component.scss']
})
export class FuseHighlightComponent implements OnInit
{
@ContentChild('source') source: ElementRef;
@Input('lang') lang: string;
@Input('path') path: string;
constructor(
private elementRef: ElementRef,
private http: HttpClient
)
{
}
ngOnInit()
{
// If there is no language defined, return...
if ( !this.lang )
{
return;
}
// If the path is defined...
if ( this.path )
{
// Get the source
this.http.get(this.path, {responseType: 'text'}).subscribe((response) => {
// Highlight it
this.highlight(response);
});
}
// If the path is not defined and the source element exists...
if ( !this.path && this.source )
{
// Highlight it
this.highlight(this.source.nativeElement.value);
}
}
highlight(sourceCode)
{
// Split the source into lines
const sourceLines = sourceCode.split('\n');
// Remove the first and the last line of the source
// code if they are blank lines. This way, the html
// can be formatted properly while using fuse-highlight
// component
if ( !sourceLines[0].trim() )
{
sourceLines.shift();
}
if ( !sourceLines[sourceLines.length - 1].trim() )
{
sourceLines.pop();
}
// Find the first non-whitespace char index in
// the first line of the source code
const indexOfFirstChar = sourceLines[0].search(/\S|$/);
// Generate the trimmed source
let source = '';
// Iterate through all the lines
sourceLines.forEach((line, index) => {
// Trim the beginning white space depending on the index
// and concat the source code
source = source + line.substr(indexOfFirstChar, line.length);
// If it's not the last line...
if ( index !== sourceLines.length - 1 )
{
// Add a line break at the end
source = source + '\n';
}
});
// Generate the highlighted code
const highlightedCode = Prism.highlight(source, Prism.languages[this.lang]);
// Replace the innerHTML of the component with the highlighted code
this.elementRef.nativeElement.innerHTML =
'<pre><code class="highlight language-' + this.lang + '">' + highlightedCode + '</code></pre>';
}
}

View File

@@ -0,0 +1,16 @@
import 'prismjs/prism';
import 'prismjs/components/prism-c';
import 'prismjs/components/prism-cpp';
import 'prismjs/components/prism-csharp';
import 'prismjs/components/prism-css';
import 'prismjs/components/prism-diff';
import 'prismjs/components/prism-markup';
import 'prismjs/components/prism-java';
import 'prismjs/components/prism-javascript';
import 'prismjs/components/prism-json';
import 'prismjs/components/prism-perl';
import 'prismjs/components/prism-php';
import 'prismjs/components/prism-python';
import 'prismjs/components/prism-sass';
import 'prismjs/components/prism-scss';
import 'prismjs/components/prism-typescript';

View File

@@ -1,3 +0,0 @@
:host {
}

View File

@@ -1,58 +0,0 @@
import { Component, ContentChild, ElementRef, Input, OnInit } from '@angular/core';
import * as hljs from 'highlight.js';
@Component({
selector : 'fuse-hljs',
template : ' ',
styleUrls: ['./hljs.component.scss']
})
export class FuseHljsComponent implements OnInit
{
hljs: any;
@ContentChild('source') source: ElementRef;
@Input('lang') lang: string;
constructor(
private elementRef: ElementRef
)
{
this.hljs = hljs;
}
ngOnInit()
{
const originalSource = this.source.nativeElement.value;
if ( !originalSource || !this.lang )
{
return;
}
// Split the source into lines
const sourceLines = originalSource.split('\n');
// Find the first non-whitespace char index in
// the first line of the source code
const indexOfFirstChar = sourceLines[0].search(/\S|$/);
// Generate the trimmed source
let source = '';
// Iterate through all the lines and trim the
// beginning white space depending on the index
sourceLines.forEach((line, index) => {
source = source + line.substr(indexOfFirstChar, line.length);
if ( index !== sourceLines.length - 1 )
{
source = source + '\n';
}
});
this.elementRef.nativeElement.innerHTML =
`<pre><code class="highlight">` + this.hljs.highlight(this.lang, source).value + `</code></pre>`;
}
}

View File

@@ -1,6 +1,10 @@
<a class="nav-link" matRipple> <a class="nav-link" matRipple>
<mat-icon class="nav-link-icon" *ngIf="item.icon">{{item.icon}}</mat-icon> <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> <mat-icon class="collapse-arrow">keyboard_arrow_right</mat-icon>
</a> </a>

View File

@@ -1,8 +1,8 @@
<a class="nav-link" *ngIf="item.url" [routerLink]="[item.url]" routerLinkActive="active" <a class="nav-link" *ngIf="item.url" [routerLink]="[item.url]" routerLinkActive="active"
[routerLinkActiveOptions]="{exact: item.exactMatch || false}" matRipple> [routerLinkActiveOptions]="{exact: item.exactMatch || false}" matRipple>
<mat-icon class="nav-link-icon" *ngIf="item.icon">{{item.icon}}</mat-icon> <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" <span class="nav-link-badge" *ngIf="item.badge" [translate]="item.badge.translate"
[ngStyle]="{'background-color': item.badge.bg,'color': item.badge.fg}"> [ngStyle]="{'background-color': item.badge.bg,'color': item.badge.fg}">
{{item.badge.title}} {{item.badge.title}}
</span> </span>
@@ -10,8 +10,8 @@
<span class="nav-link" *ngIf="item.function" (click)="item.function()" matRipple> <span class="nav-link" *ngIf="item.function" (click)="item.function()" matRipple>
<mat-icon class="nav-link-icon" *ngIf="item.icon">{{item.icon}}</mat-icon> <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" <span class="nav-link-badge" *ngIf="item.badge" [translate]="item.badge.translate"
[ngStyle]="{'background-color': item.badge.bg,'color': item.badge.fg}"> [ngStyle]="{'background-color': item.badge.bg,'color': item.badge.fg}">
{{item.badge.title}} {{item.badge.title}}
</span> </span>

View File

@@ -0,0 +1,5 @@
export interface FuseNavigationModelInterface
{
model: any[];
}

View File

@@ -1,6 +1,6 @@
import { EventEmitter, Injectable } from '@angular/core'; import { EventEmitter, Injectable } from '@angular/core';
import { NavigationModel } from '../../../navigation.model';
import { BehaviorSubject } from 'rxjs/BehaviorSubject'; import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { FuseNavigationModelInterface } from './navigation.model';
@Injectable() @Injectable()
export class FuseNavigationService export class FuseNavigationService
@@ -8,13 +8,11 @@ export class FuseNavigationService
onNavCollapseToggle = new EventEmitter<any>(); onNavCollapseToggle = new EventEmitter<any>();
onNavCollapseToggled = new EventEmitter<any>(); onNavCollapseToggled = new EventEmitter<any>();
onNavigationModelChange: BehaviorSubject<any> = new BehaviorSubject({}); onNavigationModelChange: BehaviorSubject<any> = new BehaviorSubject({});
navigationModel: NavigationModel; navigationModel: FuseNavigationModelInterface;
flatNavigation: any[] = []; flatNavigation: any[] = [];
constructor() constructor()
{ {
this.navigationModel = new NavigationModel();
this.onNavigationModelChange.next(this.navigationModel.model);
} }
/** /**
@@ -155,8 +153,15 @@ export class FuseNavigationService
*/ */
getFlatNavigation(navigationItems?) getFlatNavigation(navigationItems?)
{ {
// If navigation items not provided,
// that means we are running the function
// for the first time...
if ( !navigationItems ) if ( !navigationItems )
{ {
// Reset the flat navigation
this.flatNavigation = [];
// Get the entire navigation model
navigationItems = this.navigationModel.model; navigationItems = this.navigationModel.model;
} }
@@ -181,7 +186,10 @@ export class FuseNavigationService
if ( navItem.type === 'collapse' || navItem.type === 'group' ) if ( navItem.type === 'collapse' || navItem.type === 'group' )
{ {
this.getFlatNavigation(navItem.children); if ( navItem.children )
{
this.getFlatNavigation(navItem.children);
}
} }
} }

View File

@@ -1,8 +1,13 @@
<a class="nav-link" matRipple (click)="toggleOpen($event)"> <a class="nav-link" matRipple (click)="toggleOpen($event)">
<mat-icon class="nav-link-icon" *ngIf="item.icon">{{item.icon}}</mat-icon> <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> <mat-icon class="collapse-arrow">keyboard_arrow_right</mat-icon>
</a> </a>
<div class="children" [@slideInOut]="isOpen"> <div class="children" [@slideInOut]="isOpen">
<ng-container *ngFor="let item of item.children"> <ng-container *ngFor="let item of item.children">
<fuse-nav-vertical-item *ngIf="item.type=='item'" [item]="item"></fuse-nav-vertical-item> <fuse-nav-vertical-item *ngIf="item.type=='item'" [item]="item"></fuse-nav-vertical-item>

View File

@@ -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 * Toggle collapse
* *
@@ -108,6 +122,7 @@ export class FuseNavVerticalCollapseComponent implements OnInit
{ {
return; return;
} }
this.isOpen = false; this.isOpen = false;
this.navigationService.onNavCollapseToggle.emit(); this.navigationService.onNavCollapseToggle.emit();
} }
@@ -175,8 +190,4 @@ export class FuseNavVerticalCollapseComponent implements OnInit
return false; return false;
} }
ngOnInit()
{
}
} }

View File

@@ -1,6 +1,7 @@
<div class="group-title"> <div class="group-title">
<span class="hint-text">{{ item.title }}</span> <span class="hint-text" [translate]="item.translate">{{ item.title }}</span>
</div> </div>
<div class="group-items"> <div class="group-items">
<ng-container *ngFor="let item of item.children"> <ng-container *ngFor="let item of item.children">
<fuse-nav-vertical-group *ngIf="item.type=='group'" [item]="item"></fuse-nav-vertical-group> <fuse-nav-vertical-group *ngIf="item.type=='group'" [item]="item"></fuse-nav-vertical-group>

View File

@@ -1,8 +1,8 @@
<a class="nav-link" *ngIf="item.url" [routerLink]="[item.url]" routerLinkActive="active" <a class="nav-link" *ngIf="item.url" [routerLink]="[item.url]" routerLinkActive="active"
[routerLinkActiveOptions]="{exact: item.exactMatch || false}" matRipple> [routerLinkActiveOptions]="{exact: item.exactMatch || false}" matRipple>
<mat-icon class="nav-link-icon" *ngIf="item.icon">{{item.icon}}</mat-icon> <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" <span class="nav-link-badge" *ngIf="item.badge" [translate]="item.badge.translate"
[ngStyle]="{'background-color': item.badge.bg,'color': item.badge.fg}"> [ngStyle]="{'background-color': item.badge.bg,'color': item.badge.fg}">
{{item.badge.title}} {{item.badge.title}}
</span> </span>
@@ -10,8 +10,8 @@
<span class="nav-link" *ngIf="item.function" (click)="item.function()" matRipple> <span class="nav-link" *ngIf="item.function" (click)="item.function()" matRipple>
<mat-icon class="nav-link-icon" *ngIf="item.icon">{{item.icon}}</mat-icon> <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" <span class="nav-link-badge" *ngIf="item.badge" [translate]="item.badge.translate"
[ngStyle]="{'background-color': item.badge.bg,'color': item.badge.fg}"> [ngStyle]="{'background-color': item.badge.bg,'color': item.badge.fg}">
{{item.badge.title}} {{item.badge.title}}
</span> </span>

View File

@@ -62,4 +62,23 @@ fuse-widget {
transform: rotateY(360deg); 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;
}
}
}
} }

View File

@@ -27,7 +27,9 @@ export class FuseWidgetComponent implements OnInit, AfterContentInit
setTimeout(() => { setTimeout(() => {
this.toggleButtons.forEach(flipButton => { 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(); this.toggle();
}); });
}); });

View File

@@ -13,7 +13,6 @@ export class FuseMatSidenavHelperDirective implements OnInit, OnDestroy
matchMediaSubscription: Subscription; matchMediaSubscription: Subscription;
@HostBinding('class.mat-is-locked-open') isLockedOpen = true; @HostBinding('class.mat-is-locked-open') isLockedOpen = true;
@HostBinding('class.mat-stop-transition') stopTransition = true;
@Input('fuseMatSidenavHelper') id: string; @Input('fuseMatSidenavHelper') id: string;
@Input('mat-is-locked-open') matIsLockedOpenBreakpoint: string; @Input('mat-is-locked-open') matIsLockedOpenBreakpoint: string;
@@ -33,45 +32,31 @@ export class FuseMatSidenavHelperDirective implements OnInit, OnDestroy
if ( this.observableMedia.isActive(this.matIsLockedOpenBreakpoint) ) if ( this.observableMedia.isActive(this.matIsLockedOpenBreakpoint) )
{ {
setTimeout(() => { this.isLockedOpen = true;
this.isLockedOpen = true; this.matSidenav.mode = 'side';
this.matSidenav.mode = 'side'; this.matSidenav.toggle(true);
this.matSidenav.open();
});
this.stopTransition = false;
} }
else else
{ {
setTimeout(() => { this.isLockedOpen = false;
this.isLockedOpen = false; this.matSidenav.mode = 'over';
this.matSidenav.mode = 'over'; this.matSidenav.toggle(false);
this.matSidenav.close();
});
setTimeout(() => {
this.stopTransition = false;
}, 3000);
} }
this.matchMediaSubscription = this.fuseMatchMedia.onMediaChange.subscribe(() => { this.matchMediaSubscription = this.fuseMatchMedia.onMediaChange.subscribe(() => {
if ( this.observableMedia.isActive(this.matIsLockedOpenBreakpoint) ) if ( this.observableMedia.isActive(this.matIsLockedOpenBreakpoint) )
{ {
setTimeout(() => { this.isLockedOpen = true;
this.isLockedOpen = true; this.matSidenav.mode = 'side';
this.matSidenav.mode = 'side'; this.matSidenav.toggle(true);
this.matSidenav.open();
});
} }
else else
{ {
setTimeout(() => { this.isLockedOpen = false;
this.isLockedOpen = false; this.matSidenav.mode = 'over';
this.matSidenav.mode = 'over'; this.matSidenav.toggle(false);
this.matSidenav.close();
});
} }
}); });
} }
ngOnDestroy() ngOnDestroy()

View File

@@ -1,22 +1,22 @@
import { AfterViewInit, Directive, ElementRef, OnDestroy, OnInit } from '@angular/core'; import { AfterViewInit, Directive, ElementRef, HostListener, OnDestroy } from '@angular/core';
import PerfectScrollbar from 'perfect-scrollbar';
import { FuseConfigService } from '../../services/config.service'; import { FuseConfigService } from '../../services/config.service';
import { Subscription } from 'rxjs/Subscription'; import { Subscription } from 'rxjs/Subscription';
import { Platform } from '@angular/cdk/platform'; import { Platform } from '@angular/cdk/platform';
import PerfectScrollbar from 'perfect-scrollbar';
@Directive({ @Directive({
selector: '[fusePerfectScrollbar]' selector: '[fusePerfectScrollbar]'
}) })
export class FusePerfectScrollbarDirective implements OnInit, AfterViewInit, OnDestroy export class FusePerfectScrollbarDirective implements AfterViewInit, OnDestroy
{ {
onSettingsChanged: Subscription; onSettingsChanged: Subscription;
isDisableCustomScrollbars = false; isDisableCustomScrollbars = false;
isMobile = false; isMobile = false;
isInitialized = true; isInitialized = true;
ps; ps: PerfectScrollbar;
constructor( constructor(
private element: ElementRef, public element: ElementRef,
private fuseConfig: FuseConfigService, private fuseConfig: FuseConfigService,
private platform: Platform private platform: Platform
) )
@@ -35,11 +35,6 @@ export class FusePerfectScrollbarDirective implements OnInit, AfterViewInit, OnD
} }
} }
ngOnInit()
{
}
ngAfterViewInit() ngAfterViewInit()
{ {
if ( this.isMobile || this.isDisableCustomScrollbars ) if ( this.isMobile || this.isDisableCustomScrollbars )
@@ -49,12 +44,14 @@ export class FusePerfectScrollbarDirective implements OnInit, AfterViewInit, OnD
} }
// Initialize the perfect-scrollbar // Initialize the perfect-scrollbar
this.ps = new PerfectScrollbar(this.element.nativeElement); this.ps = new PerfectScrollbar(this.element.nativeElement, {
wheelPropagation: true
});
} }
ngOnDestroy() ngOnDestroy()
{ {
if ( !this.isInitialized ) if ( !this.isInitialized || !this.ps )
{ {
return; return;
} }
@@ -65,6 +62,21 @@ export class FusePerfectScrollbarDirective implements OnInit, AfterViewInit, OnD
this.ps.destroy(); this.ps.destroy();
} }
@HostListener('document:click', ['$event'])
documentClick(event: Event): void
{
if ( !this.isInitialized || !this.ps )
{
return;
}
// Update the scrollbar on document click..
// This isn't the most elegant solution but there is no other way
// of knowing when the contents of the scrollable container changes.
// Therefore, we update scrollbars on every document click.
this.ps.update();
}
update() update()
{ {
if ( !this.isInitialized ) if ( !this.isInitialized )

View File

@@ -681,6 +681,7 @@ const matColors = {
} }
}; };
// tslint:disable-next-line
const matPresetColors = [ const matPresetColors = [
'#ffebee', '#ffcdd2', '#ef9a9a', '#e57373', '#ef5350', '#f44336', '#e53935', '#d32f2f', '#c62828', '#b71c1c', '#ff8a80', '#ff5252', '#ff1744', '#d50000', '#fce4ec', '#f8bbd0', '#f48fb1', '#f06292', '#ec407a', '#e91e63', '#d81b60', '#c2185b', '#ad1457', '#880e4f', '#ff80ab', '#ff4081', '#f50057', '#c51162', '#f3e5f5', '#e1bee7', '#ce93d8', '#ba68c8', '#ab47bc', '#9c27b0', '#8e24aa', '#7b1fa2', '#6a1b9a', '#4a148c', '#ea80fc', '#e040fb', '#d500f9', '#aa00ff', '#ede7f6', '#d1c4e9', '#b39ddb', '#9575cd', '#7e57c2', '#673ab7', '#5e35b1', '#512da8', '#4527a0', '#311b92', '#b388ff', '#7c4dff', '#651fff', '#6200ea', '#e8eaf6', '#c5cae9', '#9fa8da', '#7986cb', '#5c6bc0', '#3f51b5', '#3949ab', '#303f9f', '#283593', '#1a237e', '#8c9eff', '#536dfe', '#3d5afe', '#304ffe', '#e3f2fd', '#bbdefb', '#90caf9', '#64b5f6', '#42a5f5', '#2196f3', '#1e88e5', '#1976d2', '#1565c0', '#0d47a1', '#82b1ff', '#448aff', '#2979ff', '#2962ff', '#e1f5fe', '#b3e5fc', '#81d4fa', '#4fc3f7', '#29b6f6', '#03a9f4', '#039be5', '#0288d1', '#0277bd', '#01579b', '#80d8ff', '#40c4ff', '#00b0ff', '#0091ea', '#e0f7fa', '#b2ebf2', '#80deea', '#4dd0e1', '#26c6da', '#00bcd4', '#00acc1', '#0097a7', '#00838f', '#006064', '#84ffff', '#18ffff', '#00e5ff', '#00b8d4', '#e0f2f1', '#b2dfdb', '#80cbc4', '#4db6ac', '#26a69a', '#009688', '#00897b', '#00796b', '#00695c', '#004d40', '#a7ffeb', '#64ffda', '#1de9b6', '#00bfa5', '#e8f5e9', '#c8e6c9', '#a5d6a7', '#81c784', '#66bb6a', '#4caf50', '#43a047', '#388e3c', '#2e7d32', '#1b5e20', '#b9f6ca', '#69f0ae', '#00e676', '#00c853', '#f1f8e9', '#dcedc8', '#c5e1a5', '#aed581', '#9ccc65', '#8bc34a', '#7cb342', '#689f38', '#558b2f', '#33691e', '#ccff90', '#b2ff59', '#76ff03', '#64dd17', '#f9fbe7', '#f0f4c3', '#e6ee9c', '#dce775', '#d4e157', '#cddc39', '#c0ca33', '#afb42b', '#9e9d24', '#827717', '#f4ff81', '#eeff41', '#c6ff00', '#aeea00', '#fffde7', '#fff9c4', '#fff59d', '#fff176', '#ffee58', '#ffeb3b', '#fdd835', '#fbc02d', '#f9a825', '#f57f17', '#ffff8d', '#ffff00', '#ffea00', '#ffd600', '#fff8e1', '#ffecb3', '#ffe082', '#ffd54f', '#ffca28', '#ffc107', '#ffb300', '#ffa000', '#ff8f00', '#ff6f00', '#ffe57f', '#ffd740', '#ffc400', '#ffab00', '#fff3e0', '#ffe0b2', '#ffcc80', '#ffb74d', '#ffa726', '#ff9800', '#fb8c00', '#f57c00', '#ef6c00', '#e65100', '#ffd180', '#ffab40', '#ff9100', '#ff6d00', '#fbe9e7', '#ffccbc', '#ffab91', '#ff8a65', '#ff7043', '#ff5722', '#f4511e', '#e64a19', '#d84315', '#bf360c', '#ff9e80', '#ff6e40', '#ff3d00', '#dd2c00', '#efebe9', '#d7ccc8', '#bcaaa4', '#a1887f', '#8d6e63', '#795548', '#6d4c41', '#5d4037', '#4e342e', '#3e2723', '#d7ccc8', '#bcaaa4', '#8d6e63', '#5d4037', '#fafafa', '#f5f5f5', '#eeeeee', '#e0e0e0', '#bdbdbd', '#9e9e9e', '#757575', '#616161', '#424242', '#212121', '#ffffff', '#eeeeee', '#bdbdbd', '#616161', '#eceff1', '#cfd8dc', '#b0bec5', '#90a4ae', '#78909c', '#607d8b', '#546e7a', '#455a64', '#37474f', '#263238', '#cfd8dc', '#b0bec5', '#78909c', '#455a64' '#ffebee', '#ffcdd2', '#ef9a9a', '#e57373', '#ef5350', '#f44336', '#e53935', '#d32f2f', '#c62828', '#b71c1c', '#ff8a80', '#ff5252', '#ff1744', '#d50000', '#fce4ec', '#f8bbd0', '#f48fb1', '#f06292', '#ec407a', '#e91e63', '#d81b60', '#c2185b', '#ad1457', '#880e4f', '#ff80ab', '#ff4081', '#f50057', '#c51162', '#f3e5f5', '#e1bee7', '#ce93d8', '#ba68c8', '#ab47bc', '#9c27b0', '#8e24aa', '#7b1fa2', '#6a1b9a', '#4a148c', '#ea80fc', '#e040fb', '#d500f9', '#aa00ff', '#ede7f6', '#d1c4e9', '#b39ddb', '#9575cd', '#7e57c2', '#673ab7', '#5e35b1', '#512da8', '#4527a0', '#311b92', '#b388ff', '#7c4dff', '#651fff', '#6200ea', '#e8eaf6', '#c5cae9', '#9fa8da', '#7986cb', '#5c6bc0', '#3f51b5', '#3949ab', '#303f9f', '#283593', '#1a237e', '#8c9eff', '#536dfe', '#3d5afe', '#304ffe', '#e3f2fd', '#bbdefb', '#90caf9', '#64b5f6', '#42a5f5', '#2196f3', '#1e88e5', '#1976d2', '#1565c0', '#0d47a1', '#82b1ff', '#448aff', '#2979ff', '#2962ff', '#e1f5fe', '#b3e5fc', '#81d4fa', '#4fc3f7', '#29b6f6', '#03a9f4', '#039be5', '#0288d1', '#0277bd', '#01579b', '#80d8ff', '#40c4ff', '#00b0ff', '#0091ea', '#e0f7fa', '#b2ebf2', '#80deea', '#4dd0e1', '#26c6da', '#00bcd4', '#00acc1', '#0097a7', '#00838f', '#006064', '#84ffff', '#18ffff', '#00e5ff', '#00b8d4', '#e0f2f1', '#b2dfdb', '#80cbc4', '#4db6ac', '#26a69a', '#009688', '#00897b', '#00796b', '#00695c', '#004d40', '#a7ffeb', '#64ffda', '#1de9b6', '#00bfa5', '#e8f5e9', '#c8e6c9', '#a5d6a7', '#81c784', '#66bb6a', '#4caf50', '#43a047', '#388e3c', '#2e7d32', '#1b5e20', '#b9f6ca', '#69f0ae', '#00e676', '#00c853', '#f1f8e9', '#dcedc8', '#c5e1a5', '#aed581', '#9ccc65', '#8bc34a', '#7cb342', '#689f38', '#558b2f', '#33691e', '#ccff90', '#b2ff59', '#76ff03', '#64dd17', '#f9fbe7', '#f0f4c3', '#e6ee9c', '#dce775', '#d4e157', '#cddc39', '#c0ca33', '#afb42b', '#9e9d24', '#827717', '#f4ff81', '#eeff41', '#c6ff00', '#aeea00', '#fffde7', '#fff9c4', '#fff59d', '#fff176', '#ffee58', '#ffeb3b', '#fdd835', '#fbc02d', '#f9a825', '#f57f17', '#ffff8d', '#ffff00', '#ffea00', '#ffd600', '#fff8e1', '#ffecb3', '#ffe082', '#ffd54f', '#ffca28', '#ffc107', '#ffb300', '#ffa000', '#ff8f00', '#ff6f00', '#ffe57f', '#ffd740', '#ffc400', '#ffab00', '#fff3e0', '#ffe0b2', '#ffcc80', '#ffb74d', '#ffa726', '#ff9800', '#fb8c00', '#f57c00', '#ef6c00', '#e65100', '#ffd180', '#ffab40', '#ff9100', '#ff6d00', '#fbe9e7', '#ffccbc', '#ffab91', '#ff8a65', '#ff7043', '#ff5722', '#f4511e', '#e64a19', '#d84315', '#bf360c', '#ff9e80', '#ff6e40', '#ff3d00', '#dd2c00', '#efebe9', '#d7ccc8', '#bcaaa4', '#a1887f', '#8d6e63', '#795548', '#6d4c41', '#5d4037', '#4e342e', '#3e2723', '#d7ccc8', '#bcaaa4', '#8d6e63', '#5d4037', '#fafafa', '#f5f5f5', '#eeeeee', '#e0e0e0', '#bdbdbd', '#9e9e9e', '#757575', '#616161', '#424242', '#212121', '#ffffff', '#eeeeee', '#bdbdbd', '#616161', '#eceff1', '#cfd8dc', '#b0bec5', '#90a4ae', '#78909c', '#607d8b', '#546e7a', '#455a64', '#37474f', '#263238', '#cfd8dc', '#b0bec5', '#78909c', '#455a64'
]; ];

View File

@@ -5,7 +5,7 @@ import { CommonModule } from '@angular/common';
import { MaterialModule } from './material.module'; import { MaterialModule } from './material.module';
import { FlexLayoutModule } from '@angular/flex-layout'; import { FlexLayoutModule } from '@angular/flex-layout';
import { ColorPickerModule } from 'ngx-color-picker'; 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 { NgxDatatableModule } from '@swimlane/ngx-datatable';
import { FuseMatSidenavHelperDirective, FuseMatSidenavTogglerDirective } from '../directives/fuse-mat-sidenav-helper/fuse-mat-sidenav-helper.directive'; import { FuseMatSidenavHelperDirective, FuseMatSidenavTogglerDirective } from '../directives/fuse-mat-sidenav-helper/fuse-mat-sidenav-helper.directive';
@@ -15,13 +15,12 @@ import { FuseConfirmDialogComponent } from '../components/confirm-dialog/confirm
import { FuseCountdownComponent } from '../components/countdown/countdown.component'; import { FuseCountdownComponent } from '../components/countdown/countdown.component';
import { FuseMatchMedia } from '../services/match-media.service'; import { FuseMatchMedia } from '../services/match-media.service';
import { FuseNavbarVerticalService } from '../../main/navbar/vertical/navbar-vertical.service'; import { FuseNavbarVerticalService } from '../../main/navbar/vertical/navbar-vertical.service';
import { FuseHljsComponent } from '../components/hljs/hljs.component'; import { FuseHighlightComponent } from '../components/highlight/highlight.component';
import { FusePerfectScrollbarDirective } from '../directives/fuse-perfect-scrollbar/fuse-perfect-scrollbar.directive'; import { FusePerfectScrollbarDirective } from '../directives/fuse-perfect-scrollbar/fuse-perfect-scrollbar.directive';
import { FuseIfOnDomDirective } from '../directives/fuse-if-on-dom/fuse-if-on-dom.directive'; import { FuseIfOnDomDirective } from '../directives/fuse-if-on-dom/fuse-if-on-dom.directive';
import { FuseMaterialColorPickerComponent } from '../components/material-color-picker/material-color-picker.component'; import { FuseMaterialColorPickerComponent } from '../components/material-color-picker/material-color-picker.component';
import { FuseTranslationLoaderService } from '../services/translation-loader.service'; import { FuseTranslationLoaderService } from '../services/translation-loader.service';
import { CookieService } from 'ngx-cookie-service'; import { CookieService } from 'ngx-cookie-service';
import { MarkdownModule } from 'angular2-markdown';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
@NgModule({ @NgModule({
@@ -30,7 +29,7 @@ import { TranslateModule } from '@ngx-translate/core';
FuseMatSidenavTogglerDirective, FuseMatSidenavTogglerDirective,
FuseConfirmDialogComponent, FuseConfirmDialogComponent,
FuseCountdownComponent, FuseCountdownComponent,
FuseHljsComponent, FuseHighlightComponent,
FuseIfOnDomDirective, FuseIfOnDomDirective,
FusePerfectScrollbarDirective, FusePerfectScrollbarDirective,
FuseMaterialColorPickerComponent FuseMaterialColorPickerComponent
@@ -44,8 +43,7 @@ import { TranslateModule } from '@ngx-translate/core';
ReactiveFormsModule, ReactiveFormsModule,
ColorPickerModule, ColorPickerModule,
NgxDnDModule, NgxDnDModule,
NgxDatatableModule, NgxDatatableModule
MarkdownModule
], ],
exports : [ exports : [
FlexLayoutModule, FlexLayoutModule,
@@ -56,7 +54,7 @@ import { TranslateModule } from '@ngx-translate/core';
FuseMatSidenavTogglerDirective, FuseMatSidenavTogglerDirective,
FusePipesModule, FusePipesModule,
FuseCountdownComponent, FuseCountdownComponent,
FuseHljsComponent, FuseHighlightComponent,
FusePerfectScrollbarDirective, FusePerfectScrollbarDirective,
ReactiveFormsModule, ReactiveFormsModule,
ColorPickerModule, ColorPickerModule,
@@ -64,7 +62,6 @@ import { TranslateModule } from '@ngx-translate/core';
NgxDatatableModule, NgxDatatableModule,
FuseIfOnDomDirective, FuseIfOnDomDirective,
FuseMaterialColorPickerComponent, FuseMaterialColorPickerComponent,
MarkdownModule,
TranslateModule TranslateModule
], ],
entryComponents: [ entryComponents: [

View File

@@ -6,42 +6,6 @@
} }
} }
// Fix: "Sidenav opening with animations for the first time"
mat-sidenav-container {
mat-sidenav {
&[mat-is-locked-open].mat-stop-transition {
transition: none !important;
transform: translate3d(0, 0, 0) !important;
opacity: 0;
~ .mat-sidenav-content,
~ .mat-drawer-content {
transition: none !important;
}
}
&.mat-sidenav-opened {
&.mat-drawer-side {
~ .mat-sidenav-content,
~ .mat-drawer-content {
transition: none !important;
transform: translate3d(0, 0, 0) !important;
}
}
}
&.mat-drawer-end {
}
}
.mat-drawer-content {
}
}
// Fix: "Inconsistent font sizes across elements" // Fix: "Inconsistent font sizes across elements"
.mat-input-wrapper { .mat-input-wrapper {
font-size: 16px; font-size: 16px;
@@ -56,11 +20,59 @@ mat-sidenav-container {
} }
.mat-pseudo-checkbox-checked:after { .mat-pseudo-checkbox-checked:after {
width: 14px; width: 14px !important;
height: 7px; height: 7px !important;
} }
// Fix: "Input underlines has wrong color opacity value" // Fix: "Input underlines has wrong color opacity value"
.mat-form-field-underline { .mat-form-field-underline {
background-color: rgba(0, 0, 0, 0.12); 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;
}

View File

@@ -9,145 +9,47 @@
min-width: 0; min-width: 0;
} }
.card-rich-media { // Buttons
position: relative; .mat-button {
min-width: 0 !important;
padding: 0 8px !important;
}
.card-title { // Button Toggle Group
position: absolute; .mat-button-toggle-group,
right: 16px; .mat-button-toggle-standalone {
bottom: 16px; box-shadow: none !important;
left: 16px; }
font-size: 20px;
color: white; // Tabs
} .mat-tab-labels {
} justify-content: center;
}
.card-media-header {
display: flex; .mat-tab-label {
padding: 16px; min-width: 0 !important;
align-items: center; }
&.medium { // Divider
align-items: flex-start; .card-divider {
border-top: 1px solid rgba(0, 0, 0, 0.12);
.card-rich-media { margin: 16px;
width: 120px;
height: 120px; &.light {
} border-top-color: rgba(255, 255, 255, 0.12);
} }
&.large { &.full-width {
align-items: flex-start; margin: 0;
.card-rich-media {
width: 160px;
height: 160px;
}
}
.card-primary-title {
padding: 0 16px 0 0;
flex: 1;
}
.card-rich-media {
width: 80px;
height: 80px;
}
+ div {
padding-top: 0;
}
}
.card-avatar-header {
display: flex;
padding: 16px;
align-items: center;
.card-avatar {
width: 40px;
height: 40px;
border-radius: 100%;
margin-right: 16px;
}
.card-avatar-title {
.card-title {
font-size: 14px;
font-weight: bold;
}
.card-subtitle {
font-size: 13px;
font-weight: bold;
}
}
}
.card-primary-title {
padding: 16px;
.card-title {
font-size: 24px;
}
.card-subtitle {
font-size: 14px;
}
+ div {
padding-top: 0;
}
}
.card-supporting-text {
padding: 16px;
font-size: 14px;
line-height: 1.75;
+ div {
padding-top: 0;
}
}
.card-actions {
display: flex;
padding: 8px;
&.icon-buttons {
padding: 0 8px;
}
&.align-center {
justify-content: center;
}
&.align-right {
justify-content: flex-end;
}
.mat-button {
min-width: 0 !important;
padding: 0 8px !important;
}
.card-expander {
margin-left: auto;
}
+ div {
padding-top: 0;
} }
} }
// Expand Area
.card-expand-area { .card-expand-area {
overflow: hidden; overflow: hidden;
.card-expanded-supporting-text { .card-expanded-content {
padding: 8px 16px 16px 16px; padding: 8px 16px 16px 16px;
font-size: 14px;
line-height: 1.75; line-height: 1.75;
} }
} }

View File

@@ -1,9 +1,59 @@
// ######################
// POSITION HELPERS
// ######################
@each $breakpoint in map-keys($grid-breakpoints) {
@include media-breakpoint-up($breakpoint) {
$infix: breakpoint-infix($breakpoint, $grid-breakpoints);
.position#{$infix}-relative {
position: relative;
}
.position#{$infix}-absolute {
position: absolute;
}
.position#{$infix}-static {
position: static;
}
}
}
// ####################################
// ABSOLUTE POSITION ALIGNMENT HELPERS
// ####################################
@each $breakpoint in map-keys($grid-breakpoints) {
@include media-breakpoint-up($breakpoint) {
$infix: breakpoint-infix($breakpoint, $grid-breakpoints);
.align#{$infix}-top {
top: 0;
}
.align#{$infix}-right {
right: 0;
}
.align#{$infix}-bottom {
bottom: 0;
}
.align#{$infix}-left {
left: 0;
}
}
}
// ###################### // ######################
// SIZE HELPERS // SIZE HELPERS
// ###################### // ######################
@each $prop, $abbrev in (height: h, width: w) { @each $prop, $abbrev in (height: h, width: w) {
@for $index from 0 through 128 { @for $index from 0 through 180 {
$size: $index * 4; $size: $index * 4;
$length: #{$size}px; $length: #{$size}px;
@@ -28,7 +78,6 @@
// ###################### // ######################
// SPACING HELPERS // SPACING HELPERS
// ###################### // ######################
@each $breakpoint in map-keys($grid-breakpoints) { @each $breakpoint in map-keys($grid-breakpoints) {
@include media-breakpoint-up($breakpoint) { @include media-breakpoint-up($breakpoint) {
@@ -83,47 +132,45 @@
} }
@if ($abbrev == m) { @if ($abbrev == m) {
@for $index from 0 through 64 {
$size: $index * 4;
$length: #{$size}px;
// Some special margin utils for flex alignments // Some special margin utils for flex alignments
.m#{$infix}-auto { .m#{$infix}-auto {
margin: auto !important; margin: auto !important;
} }
.mt#{$infix}-auto { .mt#{$infix}-auto {
margin-top: auto !important; margin-top: auto !important;
} }
.mr#{$infix}-auto { .mr#{$infix}-auto {
margin-right: auto !important; margin-right: auto !important;
} }
.mb#{$infix}-auto { .mb#{$infix}-auto {
margin-bottom: auto !important; margin-bottom: auto !important;
} }
.ml#{$infix}-auto { .ml#{$infix}-auto {
margin-left: auto !important; margin-left: auto !important;
} }
.mx#{$infix}-auto { .mx#{$infix}-auto {
margin-right: auto !important; margin-right: auto !important;
margin-left: auto !important; margin-left: auto !important;
} }
.my#{$infix}-auto { .my#{$infix}-auto {
margin-top: auto !important; margin-top: auto !important;
margin-bottom: auto !important; margin-bottom: auto !important;
}
} }
} }
} }
} }
} }
// Border helpers // ######################
// BORDER HELPERS
// ######################
$border-style: 1px solid rgba(0, 0, 0, 0.12); $border-style: 1px solid rgba(0, 0, 0, 0.12);
.border, .border,
@@ -162,3 +209,10 @@ $border-style: 1px solid rgba(0, 0, 0, 0.12);
border-top: $border-style; border-top: $border-style;
border-bottom: $border-style; border-bottom: $border-style;
} }
// ######################
// BORDER RADIUS HELPERS
// ######################
.border-radius-100 {
border-radius: 100%;
}

View File

@@ -411,7 +411,6 @@ table {
color: rgba(0, 0, 0, 0.54); color: rgba(0, 0, 0, 0.54);
border-bottom: 1px solid rgba(0, 0, 0, 0.12); border-bottom: 1px solid rgba(0, 0, 0, 0.12);
white-space: nowrap; white-space: nowrap;
min-width: 120px;
&:first-child { &:first-child {
padding-left: 24px; padding-left: 24px;

View File

@@ -47,14 +47,20 @@
} }
.nav-link-badge { .nav-link-badge {
display: flex;
align-items: center;
min-width: 20px; min-width: 20px;
height: 20px; height: 20px;
line-height: 20px;
padding: 0 7px; padding: 0 7px;
font-size: 11px; font-size: 11px;
font-weight: 500; font-weight: 500;
border-radius: 20px; border-radius: 20px;
transition: opacity 0.2s ease-in-out 0.1s; transition: opacity 0.2s ease-in-out 0.1s;
margin-left: 8px;
+ .collapse-arrow {
margin-left: 8px;
}
} }
&:hover { &:hover {

View File

@@ -42,8 +42,8 @@
fuse-footer, fuse-footer,
fuse-quick-panel, fuse-quick-panel,
fuse-theme-options, fuse-theme-options,
.ps > .ps__scrollbar-x-rail, .ps > .ps__rail-x,
.ps > .ps__scrollbar-y-rail { .ps > .ps__rail-y {
display: none !important; display: none !important;
} }

View File

@@ -229,6 +229,22 @@ strong {
} }
} }
.line-height-1 {
line-height: 1;
}
.line-height-1\.25 {
line-height: 1.25;
}
.line-height-1\.50 {
line-height: 1.5;
}
.line-height-1\.75 {
line-height: 1.75;
}
// Boxed text // Boxed text
.text-boxed { .text-boxed {
border-radius: 2px; border-radius: 2px;

View File

@@ -1,124 +0,0 @@
/*
github.com style (c) Vasily Polovnyov <vast@whiteants.net>
*/
hljs,
[hljs] {
display: block;
overflow-x: auto;
// padding: 0.5em;
color: #333;
background: #F8F8F8;
-webkit-text-size-adjust: none;
}
.hljs-comment,
.diff .hljs-header {
color: #998;
font-style: italic;
}
.hljs-keyword,
.css .rule .hljs-keyword,
.hljs-winutils,
.nginx .hljs-title,
.hljs-subst,
.hljs-request,
.hljs-status {
color: #333;
font-weight: bold;
}
.hljs-number,
.hljs-hexcolor,
.ruby .hljs-constant {
color: #008080;
}
.hljs-string,
.hljs-tag .hljs-value,
.hljs-doctag,
.tex .hljs-formula {
color: #D14;
}
.hljs-title,
.hljs-id,
.scss .hljs-preprocessor {
color: #900;
font-weight: bold;
}
.hljs-list .hljs-keyword,
.hljs-subst {
font-weight: normal;
}
.hljs-class .hljs-title,
.hljs-type,
.vhdl .hljs-literal,
.tex .hljs-command {
color: #458;
font-weight: bold;
}
.hljs-tag,
.hljs-tag .hljs-title,
.hljs-rule .hljs-property,
.django .hljs-tag .hljs-keyword {
color: #000080;
font-weight: normal;
}
.hljs-attribute,
.hljs-variable,
.lisp .hljs-body,
.hljs-name {
color: #008080;
}
.hljs-regexp {
color: #009926;
}
.hljs-symbol,
.ruby .hljs-symbol .hljs-string,
.lisp .hljs-keyword,
.clojure .hljs-keyword,
.scheme .hljs-keyword,
.tex .hljs-special,
.hljs-prompt {
color: #990073;
}
.hljs-built_in {
color: #0086B3;
}
.hljs-preprocessor,
.hljs-pragma,
.hljs-pi,
.hljs-doctype,
.hljs-shebang,
.hljs-cdata {
color: #999;
font-weight: bold;
}
.hljs-deletion {
background: #FDD;
}
.hljs-addition {
background: #DFD;
}
.diff .hljs-change {
background: #0086B3;
}
.hljs-chunk {
color: #AAA;
}

View File

@@ -3,20 +3,21 @@
.color-picker { .color-picker {
height: auto !important; height: auto !important;
border: none !important; border: none !important;
@include mat-elevation(4); @include mat-elevation(4);
.preset-area { .preset-area {
//padding: 4px 15px; padding: 0 0 16px 16px !important;
padding: 0 0 12px 12px !important;
height: 140px; height: 140px;
overflow-y: auto; overflow-y: auto;
overflow-x: hidden; overflow-x: hidden;
> hr { > hr {
display: none; display: none;
} }
.preset-label { .preset-label {
display: none; display: none;
} }
.preset-color { .preset-color {
@@ -25,7 +26,8 @@
margin: 0 !important; margin: 0 !important;
border: none !important; border: none !important;
border-radius: 0 !important; border-radius: 0 !important;
&:nth-child(14n+3) {
&:nth-child(14n+1) {
clear: both; clear: both;
} }
} }

View File

@@ -1,4 +1,3 @@
@import "highlight";
@import "prism"; @import "prism";
@import "perfect-scrollbar"; @import "perfect-scrollbar";
@import "ngx-datatable"; @import "ngx-datatable";

View File

@@ -2,7 +2,7 @@
$base00: #263238; $base00: #263238;
$base01: #2C393F; $base01: #2C393F;
$base02: #37474F; $base02: #62727A;
$base03: #707880; $base03: #707880;
$base04: #C9CCD3; $base04: #C9CCD3;
$base05: #CDD3DE; $base05: #CDD3DE;

View File

@@ -34,7 +34,7 @@ export class FuseConfigService
footer : 'mat-fuse-dark-900-bg' footer : 'mat-fuse-dark-900-bg'
}, },
customScrollbars: true, customScrollbars: true,
routerAnimation : 'fadeIn' // fadeIn, slideUp, slideDown, slideRight, slideLeft routerAnimation : 'fadeIn' // fadeIn, slideUp, slideDown, slideRight, slideLeft, none
}; };
/** /**

View 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

View File

@@ -16,6 +16,7 @@ import { ScrumboardFakeDb } from './scrumboard';
import { FaqFakeDb } from './faq'; import { FaqFakeDb } from './faq';
import { KnowledgeBaseFakeDb } from './knowledge-base'; import { KnowledgeBaseFakeDb } from './knowledge-base';
import { ECommerceFakeDb } from './e-commerce'; import { ECommerceFakeDb } from './e-commerce';
import { AcademyFakeDb } from './academy';
export class FuseFakeDbService implements InMemoryDbService export class FuseFakeDbService implements InMemoryDbService
{ {
@@ -52,7 +53,10 @@ export class FuseFakeDbService implements InMemoryDbService
'knowledge-base' : KnowledgeBaseFakeDb.data, 'knowledge-base' : KnowledgeBaseFakeDb.data,
'e-commerce-dashboard' : ECommerceFakeDb.dashboard, 'e-commerce-dashboard' : ECommerceFakeDb.dashboard,
'e-commerce-products' : ECommerceFakeDb.products, '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
}; };
} }
} }

View 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
{
}

View 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);
});
}
}

View 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>

View 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;
}
}
}
}

View 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--;
}
}

View 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);
});
}
}

View File

@@ -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>

View 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);
}
}
}
}
}
}

View File

@@ -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);
});
}
}
}

View File

@@ -0,0 +1,63 @@
import { NgModule } from '@angular/core';
import { SharedModule } from '../../../core/modules/shared.module';
import { RouterModule } from '@angular/router';
import { FuseAngularMaterialModule } from '../components/angular-material/angular-material.module';
const routes = [
{
path : 'dashboards/project',
loadChildren: './dashboards/project/project.module#FuseProjectDashboardModule'
},
{
path : 'mail',
loadChildren: './mail/mail.module#FuseMailModule'
},
{
path : 'mail-ngrx',
loadChildren: './mail-ngrx/mail.module#FuseMailNgrxModule'
},
{
path : 'chat',
loadChildren: './chat/chat.module#FuseChatModule'
},
{
path : 'calendar',
loadChildren: './calendar/calendar.module#FuseCalendarModule'
},
{
path : 'e-commerce',
loadChildren: './e-commerce/e-commerce.module#FuseEcommerceModule'
},
{
path : 'academy',
loadChildren: './academy/academy.module#FuseAcademyModule'
},
{
path : 'todo',
loadChildren: './todo/todo.module#FuseTodoModule'
},
{
path : 'file-manager',
loadChildren: './file-manager/file-manager.module#FuseFileManagerModule'
},
{
path : 'contacts',
loadChildren: './contacts/contacts.module#FuseContactsModule'
},
{
path : 'scrumboard',
loadChildren: './scrumboard/scrumboard.module#FuseScrumboardModule'
}
];
@NgModule({
imports : [
SharedModule,
RouterModule.forChild(routes),
FuseAngularMaterialModule
],
declarations: []
})
export class FuseAppsModule
{
}

View File

@@ -216,52 +216,52 @@
} }
&.Jan { &.Jan {
background-image: url('/assets/images/backgrounds/january.jpg'); background-image: url('/assets/images/backgrounds/winter.jpg');
background-position: 0 45%; background-position: 0 85%;
} }
&.Feb { &.Feb {
background-image: url('/assets/images/backgrounds/february.jpg'); background-image: url('/assets/images/backgrounds/winter.jpg');
background-position: 0 50%; background-position: 0 85%;
} }
&.Mar { &.Mar {
background-image: url('/assets/images/backgrounds/march.jpg'); background-image: url('/assets/images/backgrounds/spring.jpg');
background-position: 0 45%; background-position: 0 40%;
} }
&.Apr { &.Apr {
background-image: url('/assets/images/backgrounds/april.jpg'); background-image: url('/assets/images/backgrounds/spring.jpg');
background-position: 0 48%; background-position: 0 40%;
} }
&.May { &.May {
background-image: url('/assets/images/backgrounds/may.jpg'); background-image: url('/assets/images/backgrounds/spring.jpg');
background-position: 0 47%; background-position: 0 40%;
} }
&.Jun { &.Jun {
background-image: url('/assets/images/backgrounds/june.jpg'); background-image: url('/assets/images/backgrounds/summer.jpg');
background-position: 0 48%; background-position: 0 80%;
} }
&.Jul { &.Jul {
background-image: url('/assets/images/backgrounds/july.jpg'); background-image: url('/assets/images/backgrounds/summer.jpg');
background-position: 0 3%; background-position: 0 80%;
} }
&.Aug { &.Aug {
background-image: url('/assets/images/backgrounds/august.jpg'); background-image: url('/assets/images/backgrounds/summer.jpg');
background-position: 0 61%; background-position: 0 80%;
} }
&.Sep { &.Sep {
background-image: url('/assets/images/backgrounds/september.jpg'); background-image: url('/assets/images/backgrounds/autumn.jpg');
background-position: 0 58%; background-position: 0 40%;
} }
&.Oct { &.Oct {
background-image: url('/assets/images/backgrounds/october.jpg'); background-image: url('/assets/images/backgrounds/autumn.jpg');
background-position: 0 50%; background-position: 0 40%;
} }
&.Nov { &.Nov {
background-image: url('/assets/images/backgrounds/november.jpg'); background-image: url('/assets/images/backgrounds/autumn.jpg');
background-position: 0 46%; background-position: 0 40%;
} }
&.Dec { &.Dec {
background-image: url('/assets/images/backgrounds/december.jpg'); background-image: url('/assets/images/backgrounds/winter.jpg');
background-position: 0 43%; background-position: 0 85%;
} }
.header-content { .header-content {
@@ -299,7 +299,7 @@
.add-event-button { .add-event-button {
position: absolute; position: absolute;
right: 18px; right: 18px;
bottom: -32px; bottom: -26px;
z-index: 10; z-index: 10;
} }

View File

@@ -25,21 +25,13 @@ import { fuseAnimations } from '../../../../core/animations';
export class FuseCalendarComponent implements OnInit export class FuseCalendarComponent implements OnInit
{ {
view: string; view: string;
viewDate: Date; viewDate: Date;
events: CalendarEvent[]; events: CalendarEvent[];
public actions: CalendarEventAction[]; public actions: CalendarEventAction[];
activeDayIsOpen: boolean; activeDayIsOpen: boolean;
refresh: Subject<any> = new Subject(); refresh: Subject<any> = new Subject();
dialogRef: any; dialogRef: any;
confirmDialogRef: MatDialogRef<FuseConfirmDialogComponent>; confirmDialogRef: MatDialogRef<FuseConfirmDialogComponent>;
selectedDay: any; selectedDay: any;
constructor( constructor(

View File

@@ -32,9 +32,11 @@
</mat-slide-toggle> </mat-slide-toggle>
</div> </div>
<div class="py-16" fxFlex="1 0 auto" fxLayout="row" formGroupName="color"> <div class="py-16" fxFlex="1 0 auto" fxLayout="column" fxLayout.gt-xs="row" formGroupName="color">
<mat-form-field class="mr-24" fxFlex>
<mat-form-field class="mr-sm-24" fxFlex>
<input matInput <input matInput
class="primary-color-input"
name="primary color" name="primary color"
formControlName="primary" formControlName="primary"
placeholder="Primary color" placeholder="Primary color"
@@ -44,8 +46,10 @@
[style.background]="event.color.primary" [style.background]="event.color.primary"
(colorPickerChange)="event.color.primary = $event; eventForm.patchValue({color:{primary:$event}})"/> (colorPickerChange)="event.color.primary = $event; eventForm.patchValue({color:{primary:$event}})"/>
</mat-form-field> </mat-form-field>
<mat-form-field fxFlex> <mat-form-field fxFlex>
<input matInput <input matInput
class="secondary-color-input"
name="secondary color" name="secondary color"
formControlName="secondary" formControlName="secondary"
placeholder="Secondary color" placeholder="Secondary color"
@@ -55,11 +59,12 @@
[style.background]="event.color.secondary" [style.background]="event.color.secondary"
(colorPickerChange)="event.color.secondary = $event; eventForm.patchValue({color:{secondary:$event}})"/> (colorPickerChange)="event.color.secondary = $event; eventForm.patchValue({color:{secondary:$event}})"/>
</mat-form-field> </mat-form-field>
</div> </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" <input matInput [matDatepicker]="startDatePicker" placeholder="Start Date"
name="start" name="start"
formControlName="start"> formControlName="start">
@@ -73,9 +78,9 @@
</div> </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" <input matInput [matDatepicker]="endDatePicker" placeholder="End Date"
name="end" name="end"
formControlName="end"> formControlName="end">
@@ -98,12 +103,12 @@
<mat-form-field formGroupName="meta" class="w-100-p"> <mat-form-field formGroupName="meta" class="w-100-p">
<textarea matInput <textarea matInput
formControlName="notes" formControlName="notes"
placeholder="Notes" placeholder="Notes"
mat-maxlength="250" mat-maxlength="250"
max-rows="4"> max-rows="4">
</textarea> </textarea>
</mat-form-field> </mat-form-field>
</form> </form>

View File

@@ -1,8 +1,17 @@
@import "src/app/core/scss/fuse";
.event-form-dialog { .event-form-dialog {
@include media-breakpoint('xs') {
width: 100%;
}
@include media-breakpoint-up('xs') {
width: 480px;
}
.mat-dialog-container { .mat-dialog-container {
padding: 0; padding: 0;
width: 480px;
} }
.dialog-content-wrapper { .dialog-content-wrapper {
@@ -10,4 +19,9 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
.primary-color-input,
.secondary-color-input {
padding: 6px 8px;
}
} }

View File

@@ -13,7 +13,7 @@
<mat-sidenav-container> <mat-sidenav-container>
<!-- LEFT SIDENAV --> <!-- LEFT SIDENAV -->
<mat-sidenav class="sidenav mat-sidenav-opened" align="start" opened="true" mode="side" <mat-sidenav class="sidenav" align="start" opened="true" mode="side"
fuseMatSidenavHelper="chat-left-sidenav" mat-is-locked-open="gt-md"> fuseMatSidenavHelper="chat-left-sidenav" mat-is-locked-open="gt-md">
<fuse-chat-left-sidenav></fuse-chat-left-sidenav> <fuse-chat-left-sidenav></fuse-chat-left-sidenav>
</mat-sidenav> </mat-sidenav>
@@ -26,7 +26,7 @@
<!-- / CONTENT --> <!-- / CONTENT -->
<!-- RIGHT SIDENAV --> <!-- RIGHT SIDENAV -->
<mat-sidenav class="sidenav mat-sidenav-opened" align="end" opened="false" mode="over" <mat-sidenav class="sidenav" align="end" opened="false" mode="over"
fuseMatSidenavHelper="chat-right-sidenav"> fuseMatSidenavHelper="chat-right-sidenav">
<fuse-chat-right-sidenav></fuse-chat-right-sidenav> <fuse-chat-right-sidenav></fuse-chat-right-sidenav>
</mat-sidenav> </mat-sidenav>

View File

@@ -1,6 +1,7 @@
import { Component, OnDestroy, OnInit } from '@angular/core'; import { Component, OnDestroy, OnInit } from '@angular/core';
import { ChatService } from '../../../chat.service'; import { ChatService } from '../../../chat.service';
import { FormControl, FormGroup } from '@angular/forms'; import { FormControl, FormGroup } from '@angular/forms';
import 'rxjs/add/operator/distinctUntilChanged';
@Component({ @Component({
selector : 'fuse-chat-user-sidenav', selector : 'fuse-chat-user-sidenav',

View File

@@ -1,8 +1,17 @@
@import "src/app/core/scss/fuse";
.contact-form-dialog { .contact-form-dialog {
@include media-breakpoint('xs') {
width: 100%;
}
@include media-breakpoint-up('xs') {
width: 400px;
}
.mat-dialog-container { .mat-dialog-container {
padding: 0; padding: 0;
width: 400px;
overflow: hidden; overflow: hidden;
.mat-toolbar { .mat-toolbar {

View File

@@ -44,12 +44,9 @@ fuse-contacts-contact-list {
} }
#add-contact-button { #add-contact-button {
position: fixed; position: absolute;
bottom: 12px; bottom: 12px;
right: 12px; right: 12px;
padding: 0; padding: 0;
z-index: 99;
@include media-breakpoint-down('xs') {
top: 12px;
}
} }

View File

@@ -55,6 +55,11 @@ export class FuseContactsContactListComponent implements OnInit, OnDestroy
this.contactsService.onSelectedContactsChanged.subscribe(selectedContacts => { this.contactsService.onSelectedContactsChanged.subscribe(selectedContacts => {
for ( const id in this.checkboxes ) for ( const id in this.checkboxes )
{ {
if ( !this.checkboxes.hasOwnProperty(id) )
{
continue;
}
this.checkboxes[id] = selectedContacts.includes(id); this.checkboxes[id] = selectedContacts.includes(id);
} }
this.selectedContacts = selectedContacts; this.selectedContacts = selectedContacts;

View File

@@ -42,7 +42,7 @@
<mat-sidenav-container> <mat-sidenav-container>
<!-- SIDENAV --> <!-- SIDENAV -->
<mat-sidenav class="sidenav mat-sidenav-opened" align="start" opened="true" mode="side" <mat-sidenav class="sidenav" align="start" opened="true" mode="side"
fuseMatSidenavHelper="contacts-main-sidenav" mat-is-locked-open="gt-sm"> fuseMatSidenavHelper="contacts-main-sidenav" mat-is-locked-open="gt-sm">
<fuse-contacts-main-sidenav *fuseIfOnDom [@animate]="{value:'*'}"></fuse-contacts-main-sidenav> <fuse-contacts-main-sidenav *fuseIfOnDom [@animate]="{value:'*'}"></fuse-contacts-main-sidenav>

View File

@@ -4,11 +4,3 @@
overflow: hidden; overflow: hidden;
} }
} }
#add-contact-button {
position: absolute;
bottom: 12px;
right: 12px;
padding: 0;
z-index: 99;
}

View File

@@ -1,9 +1,11 @@
import { Component, OnInit, ViewEncapsulation } from '@angular/core'; import { Component, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';
import { ContactsService } from './contacts.service'; import { ContactsService } from './contacts.service';
import { fuseAnimations } from '../../../../core/animations'; import { fuseAnimations } from '../../../../core/animations';
import { FormControl, FormGroup } from '@angular/forms'; import { FormControl, FormGroup } from '@angular/forms';
import { FuseContactsContactFormDialogComponent } from './contact-form/contact-form.component'; import { FuseContactsContactFormDialogComponent } from './contact-form/contact-form.component';
import { MatDialog } from '@angular/material'; import { MatDialog } from '@angular/material';
import 'rxjs/add/operator/distinctUntilChanged';
import { Subscription } from 'rxjs/Subscription';
@Component({ @Component({
selector : 'fuse-contacts', selector : 'fuse-contacts',
@@ -12,11 +14,12 @@ import { MatDialog } from '@angular/material';
encapsulation: ViewEncapsulation.None, encapsulation: ViewEncapsulation.None,
animations : fuseAnimations animations : fuseAnimations
}) })
export class FuseContactsComponent implements OnInit export class FuseContactsComponent implements OnInit, OnDestroy
{ {
hasSelectedContacts: boolean; hasSelectedContacts: boolean;
searchInput: FormControl; searchInput: FormControl;
dialogRef: any; dialogRef: any;
onSelectedContactsChangedSubscription: Subscription;
constructor( constructor(
private contactsService: ContactsService, private contactsService: ContactsService,
@@ -50,11 +53,11 @@ export class FuseContactsComponent implements OnInit
ngOnInit() ngOnInit()
{ {
this.onSelectedContactsChangedSubscription =
this.contactsService.onSelectedContactsChanged this.contactsService.onSelectedContactsChanged
.subscribe(selectedContacts => { .subscribe(selectedContacts => {
this.hasSelectedContacts = selectedContacts.length > 0; this.hasSelectedContacts = selectedContacts.length > 0;
}); });
this.searchInput.valueChanges this.searchInput.valueChanges
.debounceTime(300) .debounceTime(300)
@@ -64,4 +67,8 @@ export class FuseContactsComponent implements OnInit
}); });
} }
ngOnDestroy()
{
this.onSelectedContactsChangedSubscription.unsubscribe();
}
} }

View File

@@ -10,7 +10,7 @@ import { Subject } from 'rxjs/Subject';
@Injectable() @Injectable()
export class ContactsService implements Resolve<any> export class ContactsService implements Resolve<any>
{ {
onContactsChanged: BehaviorSubject<any> = new BehaviorSubject({}); onContactsChanged: BehaviorSubject<any> = new BehaviorSubject([]);
onSelectedContactsChanged: BehaviorSubject<any> = new BehaviorSubject([]); onSelectedContactsChanged: BehaviorSubject<any> = new BehaviorSubject([]);
onUserDataChanged: BehaviorSubject<any> = new BehaviorSubject([]); onUserDataChanged: BehaviorSubject<any> = new BehaviorSubject([]);
onSearchTextChanged: Subject<any> = new Subject(); onSearchTextChanged: Subject<any> = new Subject();

View File

@@ -1,26 +1,25 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnDestroy } from '@angular/core';
import { ContactsService } from '../../contacts.service'; import { ContactsService } from '../../contacts.service';
import { Subscription } from 'rxjs/Subscription';
@Component({ @Component({
selector : 'fuse-contacts-main-sidenav', selector : 'fuse-contacts-main-sidenav',
templateUrl: './main.component.html', templateUrl: './main.component.html',
styleUrls : ['./main.component.scss'] styleUrls : ['./main.component.scss']
}) })
export class FuseContactsMainSidenavComponent implements OnInit export class FuseContactsMainSidenavComponent implements OnDestroy
{ {
user: any; user: any;
filterBy: string; filterBy: string;
onUserDataChangedSubscription: Subscription;
constructor(private contactsService: ContactsService) constructor(private contactsService: ContactsService)
{ {
this.filterBy = 'all'; this.filterBy = this.contactsService.filterBy || 'all';
this.contactsService.onUserDataChanged.subscribe(user => { this.onUserDataChangedSubscription =
this.user = user; this.contactsService.onUserDataChanged.subscribe(user => {
}); this.user = user;
} });
ngOnInit()
{
} }
changeFilter(filter) changeFilter(filter)
@@ -28,4 +27,9 @@ export class FuseContactsMainSidenavComponent implements OnInit
this.filterBy = filter; this.filterBy = filter;
this.contactsService.onFilterChanged.next(this.filterBy); this.contactsService.onFilterChanged.next(this.filterBy);
} }
ngOnDestroy()
{
this.onUserDataChangedSubscription.unsubscribe();
}
} }

View File

@@ -688,7 +688,7 @@
<!-- / CENTER --> <!-- / CENTER -->
<!-- SIDENAV --> <!-- SIDENAV -->
<mat-sidenav class="sidenav mat-sidenav-opened" align="end" mode="side" opened="true" fuseMatSidenavHelper="dashboards-right-sidenav" mat-is-locked-open="gt-md"> <mat-sidenav class="sidenav" align="end" mode="side" opened="true" fuseMatSidenavHelper="dashboards-right-sidenav" mat-is-locked-open="gt-md">
<div class="sidenav-content" fusePerfectScrollbar> <div class="sidenav-content" fusePerfectScrollbar>
@@ -703,7 +703,7 @@
<div class="pl-16 pr-8 py-16" fxLayout="row" fxLayoutAlign="space-between center"> <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> <div>
<button mat-icon-button [matMenuTriggerFor]="moreMenu" aria-label="more"> <button mat-icon-button [matMenuTriggerFor]="moreMenu" aria-label="more">

View File

@@ -8,7 +8,7 @@ import { NgxChartsModule } from '@swimlane/ngx-charts';
const routes: Routes = [ const routes: Routes = [
{ {
path : 'apps/dashboards/project', path : '**',
component: FuseProjectComponent, component: FuseProjectComponent,
resolve : { resolve : {
data: ProjectsDashboardService data: ProjectsDashboardService
@@ -30,7 +30,7 @@ const routes: Routes = [
ProjectsDashboardService ProjectsDashboardService
] ]
}) })
export class ProjectModule export class FuseProjectDashboardModule
{ {
} }

View File

@@ -1,9 +1,5 @@
import { Component, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core'; import { Component, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';
import { EcommerceDashboardService } from './dashboard.service'; 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'; import { fuseAnimations } from '../../../../../core/animations';
@Component({ @Component({
@@ -23,14 +19,10 @@ export class FuseEcommerceDashboardComponent implements OnInit, OnDestroy
widget6: any = {}; widget6: any = {};
widget7: any = {}; widget7: any = {};
dateNow = Date.now();
constructor(private projectsDashboardService: EcommerceDashboardService) constructor(private projectsDashboardService: EcommerceDashboardService)
{ {
this.projects = this.projectsDashboardService.projects; this.projects = this.projectsDashboardService.projects;
this.selectedProject = this.projects[0]; this.selectedProject = this.projects[0];
this.widgets = this.projectsDashboardService.widgets; this.widgets = this.projectsDashboardService.widgets;
/** /**

View File

@@ -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 { EcommerceOrderService } from './order.service';
import { fuseAnimations } from '../../../../../core/animations'; import { fuseAnimations } from '../../../../../core/animations';
import 'rxjs/add/operator/startWith'; import 'rxjs/add/operator/startWith';
@@ -10,9 +10,6 @@ import 'rxjs/add/observable/fromEvent';
import { Subscription } from 'rxjs/Subscription'; import { Subscription } from 'rxjs/Subscription';
import { Order } from './order.model'; import { Order } from './order.model';
import { FormBuilder, FormGroup } from '@angular/forms'; 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'; import { orderStatuses } from './order-statuses';
@Component({ @Component({
@@ -32,8 +29,6 @@ export class FuseEcommerceOrderComponent implements OnInit, OnDestroy
constructor( constructor(
private orderService: EcommerceOrderService, private orderService: EcommerceOrderService,
private formBuilder: FormBuilder, private formBuilder: FormBuilder,
public snackBar: MatSnackBar,
private location: Location
) )
{ {

View File

@@ -17,6 +17,7 @@
.orders-table { .orders-table {
flex: 1 1 auto; flex: 1 1 auto;
border-bottom: 1px solid rgba(0, 0, 0, .12); border-bottom: 1px solid rgba(0, 0, 0, .12);
overflow: auto;
.mat-header-row { .mat-header-row {
min-height: 64px; min-height: 64px;

View File

@@ -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 { EcommerceProductService } from './product.service';
import { fuseAnimations } from '../../../../../core/animations'; import { fuseAnimations } from '../../../../../core/animations';
import 'rxjs/add/operator/startWith'; import 'rxjs/add/operator/startWith';

View File

@@ -24,6 +24,7 @@
.products-table { .products-table {
flex: 1 1 auto; flex: 1 1 auto;
border-bottom: 1px solid rgba(0, 0, 0, .12); border-bottom: 1px solid rgba(0, 0, 0, .12);
overflow: auto;
.mat-header-row { .mat-header-row {
min-height: 64px; min-height: 64px;

View File

@@ -69,7 +69,7 @@
<!-- / CENTER --> <!-- / CENTER -->
<!-- SIDENAV --> <!-- SIDENAV -->
<mat-sidenav class="sidenav right-sidenav mat-sidenav-opened" align="end" opened="true" mode="side" <mat-sidenav class="sidenav right-sidenav " align="end" opened="true" mode="side"
fuseMatSidenavHelper="file-manager-right-sidenav" mat-is-locked-open="gt-md"> fuseMatSidenavHelper="file-manager-right-sidenav" mat-is-locked-open="gt-md">
<fuse-file-manager-details-sidenav></fuse-file-manager-details-sidenav> <fuse-file-manager-details-sidenav></fuse-file-manager-details-sidenav>
</mat-sidenav> </mat-sidenav>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,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()
{
}
}

View File

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

View File

@@ -0,0 +1,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%;
}
}
}
}
}
}

View File

@@ -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()
{
}
}

View File

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

View File

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

View File

@@ -0,0 +1,56 @@
import { ChangeDetectionStrategy, Component, Input, OnDestroy, OnInit } from '@angular/core';
import { Mail } from '../mail.model';
import { ActivatedRoute, Router } from '@angular/router';
import { MailNgrxService } from '../mail.service';
@Component({
selector : 'fuse-mail-list',
templateUrl : './mail-list.component.html',
styleUrls : ['./mail-list.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class FuseMailNgrxListComponent implements OnInit, OnDestroy
{
@Input() mails: Mail[];
@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()
{
}
}

View File

@@ -0,0 +1,116 @@
<div id="mail" class="page-layout carded left-sidenav" fusePerfectScrollbar>
<!-- TOP BACKGROUND -->
<div class="top-bg mat-accent-bg"></div>
<!-- / TOP BACKGROUND -->
<mat-sidenav-container>
<!-- SIDENAV -->
<mat-sidenav class="sidenav mat-sidenav-opened" align="start" mode="side" opened="true"
fuseMatSidenavHelper="carded-left-sidenav" mat-is-locked-open="gt-md">
<fuse-mail-main-sidenav></fuse-mail-main-sidenav>
</mat-sidenav>
<!-- / SIDENAV -->
<!-- CENTER -->
<div class="center">
<!-- CONTENT HEADER -->
<div class="header" fxLayout="row" fxLayoutAlign="start center">
<div class="search-wrapper" fxFlex fxLayout="row" fxLayoutAlign="start center">
<button mat-button class="mat-icon-button sidenav-toggle"
fuseMatSidenavToggler="carded-left-sidenav"
fxHide.gt-md aria-label="Toggle Sidenav">
<mat-icon>menu</mat-icon>
</button>
<div class="search mat-white-bg" flex fxLayout="row" fxLayoutAlign="start center">
<mat-icon>search</mat-icon>
<input [formControl]="searchInput" [placeholder]="'MAIL.SEARCH_PLACEHOLDER' | translate" fxFlex>
</div>
</div>
</div>
<!-- / CONTENT HEADER -->
<!-- CONTENT CARD -->
<div class="content-card mat-white-bg" [ngClass]="{'current-mail-selected':currentMail$ | 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>

View File

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

View File

@@ -0,0 +1,141 @@
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 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged';
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();
}
}

View 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;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

Some files were not shown because too many files have changed in this diff Show More