Compare commits

..

74 Commits

Author SHA1 Message Date
Sercan Yemen
43b85ca3b6 Reverted the ngx-dnd back to 3.1.0
+ Added missing debounceTime imports to prevent errors
2018-02-05 17:04:22 +03:00
Sercan Yemen
99696cabf7 Finished up the Analytics Dashboard
+ Updated the Angular Material version along with couple other libraries
+ Increased the version number of the Fuse
2018-02-05 15:08:18 +03:00
Sercan Yemen
f246fab1f4 Make sure the splash screen element exists before adding an event to it 2018-02-05 11:29:30 +03:00
Sercan Yemen
b0101a1f8c Fix the wrong api url 2018-02-05 11:28:24 +03:00
Sercan Yemen
8f5e947c28 Added a new dashboard (Analytics) 2018-02-01 15:30:44 +03:00
Sercan Yemen
dfd430712d Fixed some naming inconsistencies in Project dashboard
+ Added fake db for Analytics Dashboard
2018-02-01 15:30:30 +03:00
Sercan Yemen
8431c19133 Added Chart.js
+ Some improvements on Helper classes and Fuse Card
2018-02-01 15:28:40 +03:00
Sercan Yemen
6ee6934e0d Fixed a small typo in multi-language component page 2018-01-30 15:18:50 +03:00
Sercan Yemen
3253fbfaf4 Updated readme with slack workspace invitation 2018-01-29 11:24:47 +03:00
mustafahlvc
985a8dd3a9 ngrx updated to v5.0.0 2018-01-23 13:37:58 +03:00
Sercan Yemen
0c99c075f3 Updated Angular CLI and couple other packages to fix the devkit issue
+ Increase the Fuse version number
2018-01-23 12:47:31 +03:00
Sercan Yemen
c79db27092 Updated package-lock.json file 2018-01-18 13:37:57 +03:00
Sercan Yemen
a92cb8b7b6 Quick panel width is too wide for smaller screens
+ (Academy app) Course page doesn't scroll on mobile
2018-01-18 13:18:09 +03:00
Sercan Yemen
bb3d6d4839 Updated Angular, Angular Material and various other packages
+ Increased the Fuse version
+ Replaced datatable icons
+ Removed broken css imports
2018-01-18 13:13:40 +03:00
Sercan Yemen
9c06622efb Small fixes 2018-01-12 10:19:10 +03:00
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
424 changed files with 18805 additions and 4889 deletions

View File

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

1
LICENSE Normal file
View File

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

View File

@@ -2,6 +2,12 @@
Material Design Admin Template with Angular 5+ and Angular Material 2
## The Community
Share your ideas, discuss Fuse and help each other.
[Click here](https://join.slack.com/t/fuse-theme/shared_invite/enQtMzA1NDE4NzYwMjcyLWQxODE4ODM1MWU4MWJjMDhlMGEwYzM4Mzg3N2E4YzVlZThhZjNlOTQ1Y2Q5OTBlNDNkMzJhZDIwMjgxOWQ3YWU) to join our Slack workspace.
## Development server
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.

7782
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,74 +1,85 @@
{
"name": "fuse2",
"version": "1.2.1",
"license": "",
"version": "1.3.6",
"license": "https://themeforest.net/licenses/terms/regular",
"scripts": {
"ng": "ng",
"start": "ng serve",
"start": "ng serve --open",
"start-hmr": "ng serve --hmr -e=hmr -sm=false",
"start-hmr-sourcemaps": "ng serve --hmr -e=hmr",
"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-stats": "node --max_old_space_size=6144 ./node_modules/@angular/cli/bin/ng build --prod --stats-json",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e"
"e2e": "ng e2e",
"bundle-report": "webpack-bundle-analyzer dist/stats.json"
},
"private": true,
"dependencies": {
"@agm/core": "1.0.0-beta.2",
"@angular/animations": "5.0.1",
"@angular/cdk": "5.0.0-rc0",
"@angular/common": "5.0.1",
"@angular/compiler": "5.0.1",
"@angular/core": "5.0.1",
"@angular/flex-layout": "2.0.0-beta.10",
"@angular/forms": "5.0.1",
"@angular/http": "5.0.1",
"@angular/material": "5.0.0-rc0",
"@angular/platform-browser": "5.0.1",
"@angular/platform-browser-dynamic": "5.0.1",
"@angular/router": "5.0.1",
"@ngx-translate/core": "8.0.0",
"@swimlane/ngx-charts": "6.1.0",
"@swimlane/ngx-datatable": "11.0.3",
"@withinpixels/ngx-dnd": "3.1.0",
"angular-calendar": "0.21.3",
"angular-in-memory-web-api": "0.5.1",
"angular2-markdown": "1.6.0",
"@angular/animations": "5.2.3",
"@angular/cdk": "5.1.1",
"@angular/common": "5.2.3",
"@angular/compiler": "5.2.3",
"@angular/core": "5.2.3",
"@angular/flex-layout": "2.0.0-beta.12",
"@angular/forms": "5.2.3",
"@angular/http": "5.2.3",
"@angular/material": "5.1.1",
"@angular/material-moment-adapter": "5.1.1",
"@angular/platform-browser": "5.2.3",
"@angular/platform-browser-dynamic": "5.2.3",
"@angular/router": "5.2.3",
"@ngrx/effects": "5.0.0",
"@ngrx/router-store": "5.0.0",
"@ngrx/store": "5.0.0",
"@ngrx/store-devtools": "5.0.0",
"@ngx-translate/core": "9.1.1",
"@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.3",
"angular-in-memory-web-api": "0.5.3",
"chart.js": "2.7.1",
"classlist.js": "1.1.20150312",
"core-js": "2.5.1",
"d3": "4.11.0",
"core-js": "2.5.3",
"d3": "4.13.0",
"hammerjs": "2.0.8",
"highlight.js": "9.12.0",
"intl": "1.2.5",
"moment": "2.19.2",
"ngx-color-picker": "4.4.0",
"ngx-cookie-service": "1.0.9",
"perfect-scrollbar": "1.0.3",
"rxjs": "5.5.2",
"moment": "2.20.1",
"ng2-charts": "1.6.0",
"ngrx-store-freeze": "0.2.1",
"ngx-color-picker": "5.3.1",
"ngx-cookie-service": "1.0.10",
"perfect-scrollbar": "1.3.0",
"prismjs": "1.11.0",
"rxjs": "5.5.6",
"web-animations-js": "2.3.1",
"zone.js": "0.8.18"
"zone.js": "0.8.20"
},
"devDependencies": {
"@angular/cli": "1.5.0",
"@angular/compiler-cli": "5.0.1",
"@angular/language-service": "5.0.1",
"@angular/cli": "1.6.7",
"@angular/compiler-cli": "5.2.3",
"@angular/language-service": "5.2.3",
"@angularclass/hmr": "2.1.3",
"@types/jasmine": "2.5.54",
"@types/jasmine": "2.8.6",
"@types/jasminewd2": "2.0.3",
"@types/node": "6.0.90",
"codelyzer": "3.2.2",
"jasmine-core": "2.6.4",
"jasmine-spec-reporter": "4.1.1",
"karma": "1.7.1",
"karma-chrome-launcher": "2.1.1",
"karma-cli": "1.0.1",
"karma-coverage-istanbul-reporter": "1.3.0",
"karma-jasmine": "1.1.0",
"@types/node": "6.0.96",
"codelyzer": "4.1.0",
"jasmine-core": "2.8.0",
"jasmine-spec-reporter": "4.2.1",
"karma": "2.0.0",
"karma-chrome-launcher": "2.2.0",
"karma-coverage-istanbul-reporter": "1.4.1",
"karma-jasmine": "1.1.1",
"karma-jasmine-html-reporter": "0.2.2",
"protractor": "5.1.2",
"ts-node": "3.2.2",
"tslint": "5.7.0",
"typescript": "2.4.2"
"ts-node": "4.1.0",
"tslint": "5.9.1",
"typescript": "2.6.2",
"webpack-bundle-analyzer": "2.10.0"
}
}

View File

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

View File

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

View File

@@ -76,6 +76,53 @@ export const fuseAnimations = [
transition('0 => 1', animate('300ms ease-in'))
]),
trigger('slideIn', [
transition('void => left', [
style({
transform: 'translateX(100%)'
}),
animate('300ms ease-in',
style({
transform: 'translateX(0)'
})
)
]
),
transition('left => void', [
style({
transform: 'translateX(0)'
}),
animate('300ms ease-in',
style({
transform: 'translateX(-100%)'
})
)
]
),
transition('void => right', [
style({
transform: 'translateX(-100%)'
}),
animate('300ms ease-in',
style({
transform: 'translateX(0)'
})
)
]
),
transition('right => void', [
style({
transform: 'translateX(0)'
}),
animate('300ms ease-in',
style({
transform: 'translateX(100%)'
})
)
]
),
]),
trigger('slideInLeft', [
state('void', style({
transform: 'translateX(-100%)',

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>
<mat-icon class="nav-link-icon" *ngIf="item.icon">{{item.icon}}</mat-icon>
<span class="nav-link-title">{{item.title}}</span>
<span class="nav-link-title" [translate]="item.translate">{{item.title}}</span>
<span class="nav-link-badge" *ngIf="item.badge" [translate]="item.badge.translate"
[ngStyle]="{'background-color': item.badge.bg,'color': item.badge.fg}">
{{item.badge.title}}
</span>
<mat-icon class="collapse-arrow">keyboard_arrow_right</mat-icon>
</a>

View File

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

View File

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

View File

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

View File

@@ -1,8 +1,13 @@
<a class="nav-link" matRipple (click)="toggleOpen($event)">
<mat-icon class="nav-link-icon" *ngIf="item.icon">{{item.icon}}</mat-icon>
<span class="nav-link-title">{{item.title}}</span>
<span class="nav-link-title" [translate]="item.translate">{{item.title}}</span>
<span class="nav-link-badge" *ngIf="item.badge" [translate]="item.badge.translate"
[ngStyle]="{'background-color': item.badge.bg,'color': item.badge.fg}">
{{item.badge.title}}
</span>
<mat-icon class="collapse-arrow">keyboard_arrow_right</mat-icon>
</a>
<div class="children" [@slideInOut]="isOpen">
<ng-container *ngFor="let item of item.children">
<fuse-nav-vertical-item *ngIf="item.type=='item'" [item]="item"></fuse-nav-vertical-item>

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

View File

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

View File

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

View File

@@ -62,4 +62,23 @@ fuse-widget {
transform: rotateY(360deg);
}
}
.mat-form-field {
&.mat-form-field-type-mat-select {
.mat-input-wrapper {
padding: 16px 0;
.mat-input-infix {
border: none;
padding: 0;
}
}
.mat-input-underline {
display: none;
}
}
}
}

View File

@@ -27,7 +27,9 @@ export class FuseWidgetComponent implements OnInit, AfterContentInit
setTimeout(() => {
this.toggleButtons.forEach(flipButton => {
this.renderer.listen(flipButton.el.nativeElement, 'click', () => {
this.renderer.listen(flipButton.el.nativeElement, 'click', (event) => {
event.preventDefault();
event.stopPropagation();
this.toggle();
});
});

View File

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

View File

@@ -1,22 +1,22 @@
import { AfterViewInit, Directive, ElementRef, OnDestroy, OnInit } from '@angular/core';
import PerfectScrollbar from 'perfect-scrollbar';
import { AfterViewInit, Directive, ElementRef, HostListener, OnDestroy } from '@angular/core';
import { FuseConfigService } from '../../services/config.service';
import { Subscription } from 'rxjs/Subscription';
import { Platform } from '@angular/cdk/platform';
import PerfectScrollbar from 'perfect-scrollbar';
@Directive({
selector: '[fusePerfectScrollbar]'
})
export class FusePerfectScrollbarDirective implements OnInit, AfterViewInit, OnDestroy
export class FusePerfectScrollbarDirective implements AfterViewInit, OnDestroy
{
onSettingsChanged: Subscription;
isDisableCustomScrollbars = false;
isMobile = false;
isInitialized = true;
ps;
ps: PerfectScrollbar;
constructor(
private element: ElementRef,
public element: ElementRef,
private fuseConfig: FuseConfigService,
private platform: Platform
)
@@ -35,11 +35,6 @@ export class FusePerfectScrollbarDirective implements OnInit, AfterViewInit, OnD
}
}
ngOnInit()
{
}
ngAfterViewInit()
{
if ( this.isMobile || this.isDisableCustomScrollbars )
@@ -49,12 +44,14 @@ export class FusePerfectScrollbarDirective implements OnInit, AfterViewInit, OnD
}
// Initialize the perfect-scrollbar
this.ps = new PerfectScrollbar(this.element.nativeElement);
this.ps = new PerfectScrollbar(this.element.nativeElement, {
wheelPropagation: true
});
}
ngOnDestroy()
{
if ( !this.isInitialized )
if ( !this.isInitialized || !this.ps )
{
return;
}
@@ -65,6 +62,21 @@ export class FusePerfectScrollbarDirective implements OnInit, AfterViewInit, OnD
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()
{
if ( !this.isInitialized )

View File

@@ -681,6 +681,7 @@ const matColors = {
}
};
// tslint:disable-next-line
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'
];

View File

@@ -5,7 +5,7 @@ import { CommonModule } from '@angular/common';
import { MaterialModule } from './material.module';
import { FlexLayoutModule } from '@angular/flex-layout';
import { ColorPickerModule } from 'ngx-color-picker';
import { NgxDnDModule } from '@withinpixels/ngx-dnd';
import { NgxDnDModule } from '@swimlane/ngx-dnd';
import { NgxDatatableModule } from '@swimlane/ngx-datatable';
import { FuseMatSidenavHelperDirective, FuseMatSidenavTogglerDirective } from '../directives/fuse-mat-sidenav-helper/fuse-mat-sidenav-helper.directive';
@@ -15,14 +15,14 @@ import { FuseConfirmDialogComponent } from '../components/confirm-dialog/confirm
import { FuseCountdownComponent } from '../components/countdown/countdown.component';
import { FuseMatchMedia } from '../services/match-media.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 { FuseIfOnDomDirective } from '../directives/fuse-if-on-dom/fuse-if-on-dom.directive';
import { FuseMaterialColorPickerComponent } from '../components/material-color-picker/material-color-picker.component';
import { FuseTranslationLoaderService } from '../services/translation-loader.service';
import { CookieService } from 'ngx-cookie-service';
import { MarkdownModule } from 'angular2-markdown';
import { TranslateModule } from '@ngx-translate/core';
import { ChartsModule } from 'ng2-charts';
@NgModule({
declarations : [
@@ -30,7 +30,7 @@ import { TranslateModule } from '@ngx-translate/core';
FuseMatSidenavTogglerDirective,
FuseConfirmDialogComponent,
FuseCountdownComponent,
FuseHljsComponent,
FuseHighlightComponent,
FuseIfOnDomDirective,
FusePerfectScrollbarDirective,
FuseMaterialColorPickerComponent
@@ -45,7 +45,7 @@ import { TranslateModule } from '@ngx-translate/core';
ColorPickerModule,
NgxDnDModule,
NgxDatatableModule,
MarkdownModule
ChartsModule
],
exports : [
FlexLayoutModule,
@@ -56,7 +56,7 @@ import { TranslateModule } from '@ngx-translate/core';
FuseMatSidenavTogglerDirective,
FusePipesModule,
FuseCountdownComponent,
FuseHljsComponent,
FuseHighlightComponent,
FusePerfectScrollbarDirective,
ReactiveFormsModule,
ColorPickerModule,
@@ -64,8 +64,8 @@ import { TranslateModule } from '@ngx-translate/core';
NgxDatatableModule,
FuseIfOnDomDirective,
FuseMaterialColorPickerComponent,
MarkdownModule,
TranslateModule
TranslateModule,
ChartsModule
],
entryComponents: [
FuseConfirmDialogComponent

View File

@@ -1,9 +1,9 @@
// ngx-datatable
@import '~@swimlane/ngx-datatable/release/index.css';
@import '~@swimlane/ngx-datatable/release/themes/material.css';
@import '~@swimlane/ngx-datatable/release/assets/icons.css';
@import '~@swimlane/ngx-datatable/release/themes/material';
// Perfect scrollbar
@import '~perfect-scrollbar/css/perfect-scrollbar.css';
@import '~perfect-scrollbar/css/perfect-scrollbar';
// Fuse
@import "fuse";

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"
.mat-input-wrapper {
font-size: 16px;
@@ -56,11 +20,59 @@ mat-sidenav-container {
}
.mat-pseudo-checkbox-checked:after {
width: 14px;
height: 7px;
width: 14px !important;
height: 7px !important;
}
// Fix: "Input underlines has wrong color opacity value"
.mat-form-field-underline {
background-color: rgba(0, 0, 0, 0.12);
}
// Fix: "Some idiots using table-cell and inline-table in mat-select"
.mat-form-field {
&.mat-form-field-type-mat-select {
.mat-input-infix {
display: inline-flex;
width: auto;
.mat-select-trigger {
display: inline-flex;
align-items: center;
width: 100%;
.mat-select-value {
display: flex;
max-width: none;
margin-right: 8px;
}
.mat-select-arrow-wrapper {
display: inline-flex;
}
}
}
}
}
// Fix: "Stepper icons are broken due to Fuse's icon helpers"
mat-horizontal-stepper,
mat-vertical-stepper {
mat-step-header {
mat-icon {
height: 16px !important;
width: 16px !important;
min-width: 0 !important;
min-height: 0 !important;
color: rgba(255, 255, 255, 0.87) !important;
}
}
}
mat-vertical-stepper {
padding: 16px 0;
}

View File

@@ -9,145 +9,52 @@
min-width: 0;
}
.card-rich-media {
position: relative;
&.auto-width {
min-width: 0;
max-width: none;
}
.card-title {
position: absolute;
right: 16px;
bottom: 16px;
left: 16px;
font-size: 20px;
color: white;
}
}
.card-media-header {
display: flex;
padding: 16px;
align-items: center;
&.medium {
align-items: flex-start;
.card-rich-media {
width: 120px;
height: 120px;
}
}
&.large {
align-items: flex-start;
.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;
// Buttons
.mat-button {
min-width: 0 !important;
padding: 0 8px !important;
}
// Button Toggle Group
.mat-button-toggle-group,
.mat-button-toggle-standalone {
box-shadow: none !important;
}
// Tabs
.mat-tab-labels {
justify-content: center;
}
.mat-tab-label {
min-width: 0 !important;
}
// Divider
.card-divider {
border-top: 1px solid rgba(0, 0, 0, 0.12);
margin: 16px;
&.light {
border-top-color: rgba(255, 255, 255, 0.12);
}
&.full-width {
margin: 0;
}
}
// Expand Area
.card-expand-area {
overflow: hidden;
.card-expanded-supporting-text {
.card-expanded-content {
padding: 8px 16px 16px 16px;
font-size: 14px;
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
// ######################
@each $prop, $abbrev in (height: h, width: w) {
@for $index from 0 through 128 {
@for $index from 0 through 180 {
$size: $index * 4;
$length: #{$size}px;
@@ -28,7 +78,6 @@
// ######################
// SPACING HELPERS
// ######################
@each $breakpoint in map-keys($grid-breakpoints) {
@include media-breakpoint-up($breakpoint) {
@@ -83,47 +132,45 @@
}
@if ($abbrev == m) {
@for $index from 0 through 64 {
$size: $index * 4;
$length: #{$size}px;
// Some special margin utils for flex alignments
.m#{$infix}-auto {
margin: auto !important;
}
// Some special margin utils for flex alignments
.m#{$infix}-auto {
margin: auto !important;
}
.mt#{$infix}-auto {
margin-top: auto !important;
}
.mt#{$infix}-auto {
margin-top: auto !important;
}
.mr#{$infix}-auto {
margin-right: auto !important;
}
.mr#{$infix}-auto {
margin-right: auto !important;
}
.mb#{$infix}-auto {
margin-bottom: auto !important;
}
.mb#{$infix}-auto {
margin-bottom: auto !important;
}
.ml#{$infix}-auto {
margin-left: auto !important;
}
.ml#{$infix}-auto {
margin-left: auto !important;
}
.mx#{$infix}-auto {
margin-right: auto !important;
margin-left: auto !important;
}
.mx#{$infix}-auto {
margin-right: auto !important;
margin-left: auto !important;
}
.my#{$infix}-auto {
margin-top: auto !important;
margin-bottom: auto !important;
}
.my#{$infix}-auto {
margin-top: auto !important;
margin-bottom: auto !important;
}
}
}
}
}
// Border helpers
// ######################
// BORDER HELPERS
// ######################
$border-style: 1px solid rgba(0, 0, 0, 0.12);
.border,
@@ -162,3 +209,37 @@ $border-style: 1px solid rgba(0, 0, 0, 0.12);
border-top: $border-style;
border-bottom: $border-style;
}
// ######################
// BORDER RADIUS HELPERS
// ######################
.border-radius-100 {
border-radius: 100%;
}
.border-radius-2 {
border-radius: 2px;
}
.border-radius-4 {
border-radius: 4px;
}
.border-radius-8 {
border-radius: 8px;
}
.border-radius-16 {
border-radius: 16px;
}
// ######################
// CURSOR HELPERS
// ######################
.cursor-pointer {
cursor: pointer;
}
.cursor-default {
cursor: default;
}

View File

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

View File

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

View File

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

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

View File

@@ -98,3 +98,68 @@
}
}
}
[class*="datatable-icon-"] {
font-family: 'Material Icons';
font-weight: normal;
font-style: normal;
font-size: 24px;
line-height: 1;
letter-spacing: normal;
text-transform: none;
display: inline-block;
white-space: nowrap;
word-wrap: normal;
direction: ltr;
-webkit-font-feature-settings: 'liga';
-webkit-font-smoothing: antialiased;
}
.datatable-icon-filter:before {
content: "filter_list";
}
.datatable-icon-collapse:before {
content: "unfold_less";
}
.datatable-icon-expand:before {
content: "unfold_more";
}
.datatable-icon-close:before {
content: "close";
}
.datatable-icon-up:before {
content: "keyboard_arrow_up";
}
.datatable-icon-down:before {
content: "keyboard_arrow_down";
}
.datatable-icon-sort:before {
content: "sort";
}
.datatable-icon-done:before {
content: "done";
}
.datatable-icon-done-all:before {
content: "done_all";
}
.datatable-icon-search:before {
content: "search";
}
.datatable-icon-pin:before {
content: "lock";
}
.datatable-icon-add:before {
content: "add";
}
.datatable-icon-left:before {
content: "chevron_left";
}
.datatable-icon-right:before {
content: "chevron_right";
}
.datatable-icon-skip:before {
content: "skip_next";
}
.datatable-icon-prev:before {
content: "skip_previous";
}

View File

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

View File

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

View File

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

View File

@@ -15,18 +15,27 @@ export class FuseSplashScreenService
private router: Router
)
{
// Get the splash screen element
this.splashScreenEl = this.document.body.querySelector('#fuse-splash-screen');
const hideOnLoad = this.router.events.subscribe((event) => {
if ( event instanceof NavigationEnd )
{
setTimeout(() => {
this.hide();
hideOnLoad.unsubscribe();
}, 0);
// If the splash screen element exists...
if ( this.splashScreenEl )
{
// Hide it on the first NavigationEnd event
const hideOnLoad = this.router.events.subscribe((event) => {
if ( event instanceof NavigationEnd )
{
setTimeout(() => {
this.hide();
// Unsubscribe from this event so it
// won't get triggered again
hideOnLoad.unsubscribe();
}, 0);
}
}
}
);
);
}
}
show()

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

View File

@@ -0,0 +1,602 @@
export class AnalyticsDashboardDb
{
public static widgets = {
widget1: {
chartType: 'line',
datasets : {
'2015': [
{
label: 'Sales',
data : [1.9, 3, 3.4, 2.2, 2.9, 3.9, 2.5, 3.8, 4.1, 3.8, 3.2, 2.9],
fill : 'start'
}
],
'2016': [
{
label: 'Sales',
data : [2.2, 2.9, 3.9, 2.5, 3.8, 3.2, 2.9, 1.9, 3, 3.4, 4.1, 3.8],
fill : 'start'
}
],
'2017': [
{
label: 'Sales',
data : [3.9, 2.5, 3.8, 4.1, 1.9, 3, 3.8, 3.2, 2.9, 3.4, 2.2, 2.9],
fill : 'start'
}
]
},
labels : ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC'],
colors : [
{
borderColor : '#42a5f5',
backgroundColor : '#42a5f5',
pointBackgroundColor : '#1e88e5',
pointHoverBackgroundColor: '#1e88e5',
pointBorderColor : '#ffffff',
pointHoverBorderColor : '#ffffff'
}
],
options : {
spanGaps : false,
legend : {
display: false
},
maintainAspectRatio: false,
layout : {
padding: {
top : 32,
left : 32,
right: 32
}
},
elements : {
point: {
radius : 4,
borderWidth : 2,
hoverRadius : 4,
hoverBorderWidth: 2
},
line : {
tension: 0
}
},
scales : {
xAxes: [
{
gridLines: {
display : false,
drawBorder : false,
tickMarkLength: 18
},
ticks : {
fontColor: '#ffffff'
}
}
],
yAxes: [
{
display: false,
ticks : {
min : 1.5,
max : 5,
stepSize: 0.5
}
}
]
},
plugins : {
filler : {
propagate: false
},
xLabelsOnTop: {
active: true
}
}
}
},
widget2: {
conversion: {
value : 492,
ofTarget: 13
},
chartType : 'bar',
datasets : [
{
label: 'Conversion',
data : [221, 428, 492, 471, 413, 344, 294]
}
],
labels : ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'],
colors : [
{
borderColor : '#42a5f5',
backgroundColor: '#42a5f5'
}
],
options : {
spanGaps : false,
legend : {
display: false
},
maintainAspectRatio: false,
layout : {
padding: {
top : 24,
left : 16,
right : 16,
bottom: 16
}
},
scales : {
xAxes: [
{
display: false
}
],
yAxes: [
{
display: false,
ticks : {
min: 100,
max: 500
}
}
]
}
}
},
widget3: {
impressions: {
value : '87k',
ofTarget: 12
},
chartType : 'line',
datasets : [
{
label: 'Impression',
data : [67000, 54000, 82000, 57000, 72000, 57000, 87000, 72000, 89000, 98700, 112000, 136000, 110000, 149000, 98000],
fill : false
}
],
labels : ['Jan 1', 'Jan 2', 'Jan 3', 'Jan 4', 'Jan 5', 'Jan 6', 'Jan 7', 'Jan 8', 'Jan 9', 'Jan 10', 'Jan 11', 'Jan 12', 'Jan 13', 'Jan 14', 'Jan 15'],
colors : [
{
borderColor: '#5c84f1'
}
],
options : {
spanGaps : false,
legend : {
display: false
},
maintainAspectRatio: false,
elements : {
point: {
radius : 2,
borderWidth : 1,
hoverRadius : 2,
hoverBorderWidth: 1
},
line : {
tension: 0
}
},
layout : {
padding: {
top : 24,
left : 16,
right : 16,
bottom: 16
}
},
scales : {
xAxes: [
{
display: false
}
],
yAxes: [
{
display: false,
ticks : {
// min: 100,
// max: 500
}
}
]
}
}
},
widget4: {
visits : {
value : 882,
ofTarget: -9
},
chartType: 'bar',
datasets : [
{
label: 'Visits',
data : [432, 428, 327, 363, 456, 267, 231]
}
],
labels : ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'],
colors : [
{
borderColor : '#f44336',
backgroundColor: '#f44336'
}
],
options : {
spanGaps : false,
legend : {
display: false
},
maintainAspectRatio: false,
layout : {
padding: {
top : 24,
left : 16,
right : 16,
bottom: 16
}
},
scales : {
xAxes: [
{
display: false
}
],
yAxes: [
{
display: false,
ticks : {
min: 150,
max: 500
}
}
]
}
}
},
widget5: {
chartType: 'line',
datasets : {
'yesterday': [
{
label: 'Visitors',
data : [190, 300, 340, 220, 290, 390, 250, 380, 410, 380, 320, 290],
fill : 'start'
},
{
label: 'Page views',
data : [2200, 2900, 3900, 2500, 3800, 3200, 2900, 1900, 3000, 3400, 4100, 3800],
fill : 'start'
}
],
'today' : [
{
label: 'Visitors',
data : [410, 380, 320, 290, 190, 390, 250, 380, 300, 340, 220, 290],
fill : 'start'
},
{
label: 'Page Views',
data : [3000, 3400, 4100, 3800, 2200, 3200, 2900, 1900, 2900, 3900, 2500, 3800],
fill : 'start'
}
]
},
labels : ['12am', '2am', '4am', '6am', '8am', '10am', '12pm', '2pm', '4pm', '6pm', '8pm', '10pm'],
colors : [
{
borderColor : '#3949ab',
backgroundColor : '#3949ab',
pointBackgroundColor : '#3949ab',
pointHoverBackgroundColor: '#3949ab',
pointBorderColor : '#ffffff',
pointHoverBorderColor : '#ffffff'
},
{
borderColor : 'rgba(30, 136, 229, 0.87)',
backgroundColor : 'rgba(30, 136, 229, 0.87)',
pointBackgroundColor : 'rgba(30, 136, 229, 0.87)',
pointHoverBackgroundColor: 'rgba(30, 136, 229, 0.87)',
pointBorderColor : '#ffffff',
pointHoverBorderColor : '#ffffff'
}
],
options : {
spanGaps : false,
legend : {
display: false
},
maintainAspectRatio: false,
tooltips : {
position : 'nearest',
mode : 'index',
intersect: false
},
layout : {
padding: {
left : 24,
right: 32
}
},
elements : {
point: {
radius : 4,
borderWidth : 2,
hoverRadius : 4,
hoverBorderWidth: 2
}
},
scales : {
xAxes: [
{
gridLines: {
display: false
},
ticks : {
fontColor: 'rgba(0,0,0,0.54)'
}
}
],
yAxes: [
{
gridLines: {
tickMarkLength: 16
},
ticks : {
stepSize: 1000
}
}
]
},
plugins : {
filler: {
propagate: false
}
}
}
},
widget6: {
markers: [
{
lat : 52,
lng : -73,
label: '120'
},
{
lat : 37,
lng : -104,
label: '498'
},
{
lat : 21,
lng : -7,
label: '443'
},
{
lat : 55,
lng : 75,
label: '332'
},
{
lat : 51,
lng : 7,
label: '50'
},
{
lat : 31,
lng : 12,
label: '221'
},
{
lat : 45,
lng : 44,
label: '455'
},
{
lat : -26,
lng : 134,
label: '231'
},
{
lat : -9,
lng : -60,
label: '67'
},
{
lat : 33,
lng : 104,
label: '665'
}
],
styles : [
{
'featureType': 'administrative',
'elementType': 'labels.text.fill',
'stylers' : [
{
'color': '#444444'
}
]
},
{
'featureType': 'landscape',
'elementType': 'all',
'stylers' : [
{
'color': '#f2f2f2'
}
]
},
{
'featureType': 'poi',
'elementType': 'all',
'stylers' : [
{
'visibility': 'off'
}
]
},
{
'featureType': 'road',
'elementType': 'all',
'stylers' : [
{
'saturation': -100
},
{
'lightness': 45
}
]
},
{
'featureType': 'road.highway',
'elementType': 'all',
'stylers' : [
{
'visibility': 'simplified'
}
]
},
{
'featureType': 'road.arterial',
'elementType': 'labels.icon',
'stylers' : [
{
'visibility': 'off'
}
]
},
{
'featureType': 'transit',
'elementType': 'all',
'stylers' : [
{
'visibility': 'off'
}
]
},
{
'featureType': 'water',
'elementType': 'all',
'stylers' : [
{
'color': '#039be5'
},
{
'visibility': 'on'
}
]
}
]
},
widget7: {
scheme : {
domain: ['#4867d2', '#5c84f1', '#89a9f4']
},
devices: [
{
name : 'Desktop',
value : 92.8,
change: -0.6
},
{
name : 'Mobile',
value : 6.1,
change: 0.7
},
{
name : 'Tablet',
value : 1.1,
change: 0.1
}
]
},
widget8: {
scheme : {
domain: ['#5c84f1']
},
today : '12,540',
change : {
value : 321,
percentage: 2.05
},
data : [
{
name : 'Sales',
series: [
{
name : 'Jan 1',
value: 540
},
{
name : 'Jan 2',
value: 539
},
{
name : 'Jan 3',
value: 538
},
{
name : 'Jan 4',
value: 539
},
{
name : 'Jan 5',
value: 540
},
{
name : 'Jan 6',
value: 539
},
{
name : 'Jan 7',
value: 540
}
]
}
],
dataMin: 538,
dataMax: 541
},
widget9: {
rows: [
{
title : 'Holiday Travel',
clicks : 3621,
conversion: 90
},
{
title : 'Get Away Deals',
clicks : 703,
conversion: 7
},
{
title : 'Airfare',
clicks : 532,
conversion: 0
},
{
title : 'Vacation',
clicks : 201,
conversion: 8
},
{
title : 'Hotels',
clicks : 94,
conversion: 4
}
]
}
};
}

View File

@@ -1,4 +1,4 @@
export class ProjectsDashboardDb
export class ProjectDashboardDb
{
public static projects = [
{

File diff suppressed because it is too large Load Diff

View File

@@ -1,58 +1,97 @@
import { InMemoryDbService } from 'angular-in-memory-web-api';
import { ProjectDashboardDb } from './dashboard-project';
import { AnalyticsDashboardDb } from './dashboard-analytics';
import { CalendarFakeDb } from './calendar';
import { ECommerceFakeDb } from './e-commerce';
import { AcademyFakeDb } from './academy';
import { MailFakeDb } from './mail';
import { ChatFakeDb } from './chat';
import { CalendarFakeDb } from './calendar';
import { TodoFakeDb } from './todo';
import { ProfileFakeDb } from './profile';
import { ContactsFakeDb } from './contacts';
import { InvoiceFakeDb } from './invoice';
import { FileManagerFakeDb } from './file-manager';
import { SearchFakeDb } from './search';
import { QuickPanelFakeDb } from './quick-panel';
import { IconsFakeDb } from './icons';
import { ProjectsDashboardDb } from './projects-dashboard';
import { ContactsFakeDb } from './contacts';
import { TodoFakeDb } from './todo';
import { ScrumboardFakeDb } from './scrumboard';
import { InvoiceFakeDb } from './invoice';
import { ProfileFakeDb } from './profile';
import { SearchFakeDb } from './search';
import { FaqFakeDb } from './faq';
import { KnowledgeBaseFakeDb } from './knowledge-base';
import { ECommerceFakeDb } from './e-commerce';
import { IconsFakeDb } from './icons';
import { QuickPanelFakeDb } from './quick-panel';
export class FuseFakeDbService implements InMemoryDbService
{
createDb()
{
return {
'mail-mails' : MailFakeDb.mails,
'mail-folders' : MailFakeDb.folders,
'mail-filters' : MailFakeDb.filters,
'mail-labels' : MailFakeDb.labels,
'chat-contacts' : ChatFakeDb.contacts,
'chat-chats' : ChatFakeDb.chats,
'chat-user' : ChatFakeDb.user,
'calendar' : CalendarFakeDb.data,
'todo-todos' : TodoFakeDb.todos,
'todo-filters' : TodoFakeDb.filters,
'todo-tags' : TodoFakeDb.tags,
'profile-timeline' : ProfileFakeDb.timeline,
'profile-photos-videos' : ProfileFakeDb.photosVideos,
'profile-about' : ProfileFakeDb.about,
'contacts-contacts' : ContactsFakeDb.contacts,
'contacts-user' : ContactsFakeDb.user,
'invoice' : InvoiceFakeDb.invoice,
'file-manager' : FileManagerFakeDb.files,
'search-classic' : SearchFakeDb.classic,
'search-table' : SearchFakeDb.table,
'quick-panel-notes' : QuickPanelFakeDb.notes,
'quick-panel-events' : QuickPanelFakeDb.events,
'icons' : IconsFakeDb.icons,
'projects-dashboard-projects': ProjectsDashboardDb.projects,
'projects-dashboard-widgets' : ProjectsDashboardDb.widgets,
'scrumboard-boards' : ScrumboardFakeDb.boards,
'faq' : FaqFakeDb.data,
'knowledge-base' : KnowledgeBaseFakeDb.data,
'e-commerce-dashboard' : ECommerceFakeDb.dashboard,
'e-commerce-products' : ECommerceFakeDb.products,
'e-commerce-orders' : ECommerceFakeDb.orders
// Dashboards
'project-dashboard-projects' : ProjectDashboardDb.projects,
'project-dashboard-widgets' : ProjectDashboardDb.widgets,
'analytics-dashboard-widgets': AnalyticsDashboardDb.widgets,
// Calendar
'calendar': CalendarFakeDb.data,
// E-Commerce
'e-commerce-dashboard': ECommerceFakeDb.dashboard,
'e-commerce-products' : ECommerceFakeDb.products,
'e-commerce-orders' : ECommerceFakeDb.orders,
// Academy
'academy-categories': AcademyFakeDb.categories,
'academy-courses' : AcademyFakeDb.courses,
'academy-course' : AcademyFakeDb.course,
// Mail
'mail-mails' : MailFakeDb.mails,
'mail-folders': MailFakeDb.folders,
'mail-filters': MailFakeDb.filters,
'mail-labels' : MailFakeDb.labels,
// Chat
'chat-contacts': ChatFakeDb.contacts,
'chat-chats' : ChatFakeDb.chats,
'chat-user' : ChatFakeDb.user,
// File Manager
'file-manager': FileManagerFakeDb.files,
// Contacts
'contacts-contacts': ContactsFakeDb.contacts,
'contacts-user' : ContactsFakeDb.user,
// Todo
'todo-todos' : TodoFakeDb.todos,
'todo-filters': TodoFakeDb.filters,
'todo-tags' : TodoFakeDb.tags,
// Scrumboard
'scrumboard-boards': ScrumboardFakeDb.boards,
// Invoice
'invoice': InvoiceFakeDb.invoice,
// Profile
'profile-timeline' : ProfileFakeDb.timeline,
'profile-photos-videos': ProfileFakeDb.photosVideos,
'profile-about' : ProfileFakeDb.about,
// Search
'search-classic': SearchFakeDb.classic,
'search-table' : SearchFakeDb.table,
// FAQ
'faq': FaqFakeDb.data,
// Knowledge base
'knowledge-base': KnowledgeBaseFakeDb.data,
// Icons
'icons': IconsFakeDb.icons,
// Quick Panel
'quick-panel-notes' : QuickPanelFakeDb.notes,
'quick-panel-events': QuickPanelFakeDb.events
};
}
}

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

View File

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

View File

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

View File

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

View File

@@ -13,7 +13,7 @@
<mat-sidenav-container>
<!-- 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">
<fuse-chat-left-sidenav></fuse-chat-left-sidenav>
</mat-sidenav>
@@ -26,7 +26,7 @@
<!-- / CONTENT -->
<!-- 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">
<fuse-chat-right-sidenav></fuse-chat-right-sidenav>
</mat-sidenav>

View File

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

View File

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

View File

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

View File

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

View File

@@ -42,7 +42,7 @@
<mat-sidenav-container>
<!-- 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">
<fuse-contacts-main-sidenav *fuseIfOnDom [@animate]="{value:'*'}"></fuse-contacts-main-sidenav>

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,510 @@
<div id="dashboard-analytics" class="page-layout blank grey-100-bg" fusePerfectScrollbar>
<div class="main-widget">
<div class="position-relative p-24 mat-blue-600-bg"
fxLayout="row" fxLayoutAlign="space-between center">
<div fxLayout="column" fxLayoutAlign="start start">
<span class="h2">Visitors</span>
<span class="h5 secondary-text">Unique visitors by month</span>
</div>
<div fxLayout="row" fxLayoutAlign="start center">
<div class="py-8 px-12 border-radius-2 line-height-1 mr-8 cursor-pointer"
(click)="widget1SelectedYear = '2015'"
[ngClass]="{'blue-300-bg': widget1SelectedYear === '2015'}">
2015
</div>
<div class="py-8 px-12 border-radius-2 line-height-1 mr-8 cursor-pointer"
(click)="widget1SelectedYear = '2016'"
[ngClass]="{'blue-300-bg': widget1SelectedYear === '2016'}">
2016
</div>
<div class="py-8 px-12 border-radius-2 line-height-1 cursor-pointer"
(click)="widget1SelectedYear = '2017'"
[ngClass]="{'blue-300-bg': widget1SelectedYear === '2017'}">
2017
</div>
</div>
</div>
<div class="position-relative h-256 pb-16 mat-blue-600-bg">
<canvas baseChart
[datasets]="widgets.widget1.datasets[widget1SelectedYear]"
[labels]="widgets.widget1.labels"
[colors]="widgets.widget1.colors"
[options]="widgets.widget1.options"
[chartType]="widgets.widget1.chartType">
</canvas>
</div>
</div>
<div class="content">
<div class="left mr-32">
<div class="pb-24 font-size-18 font-weight-300">
How are your active users trending over time?
</div>
<div fxLayout="column" fxLayoutAlign="start"
fxLayout.gt-sm="row" fxLayoutAlign.gt-sm="start start">
<!-- Widget 2 -->
<div class="widget" fxFlex="100" fxFlex.gt-sm="33">
<div class="fuse-card auto-width mb-32" [ngClass.gt-sm]="'mb-0 mr-32'">
<div class="p-16 pb-0" fxLayout="row" fxLayoutAlign="start end" fxLayoutWrap>
<div class="pr-16">
<div class="h3 secondary-text">Conversion</div>
<div class="font-size-54 font-weight-300 line-height-1 mt-8">
{{widgets.widget2.conversion.value}}
</div>
</div>
<div class="py-4 font-size-16" fxLayout="row" fxLayoutAlign="start center">
<div fxFlex="row" fxLayoutAlign="start center" class="green-fg"
*ngIf="widgets.widget2.conversion.ofTarget > 0">
<mat-icon class="green-fg mr-4">trending_up</mat-icon>
<span>{{widgets.widget2.conversion.ofTarget}}%</span>
</div>
<div fxFlex="row" fxLayoutAlign="start center" class="red-fg"
*ngIf="widgets.widget2.conversion.ofTarget < 0">
<mat-icon class="red-fg mr-4">trending_down</mat-icon>
<span>{{widgets.widget2.conversion.ofTarget}}%</span>
</div>
<div class="ml-4 text-nowrap">of target</div>
</div>
</div>
<div class="h-96 w-100-p">
<canvas baseChart
[datasets]="widgets.widget2.datasets"
[labels]="widgets.widget2.labels"
[colors]="widgets.widget2.colors"
[options]="widgets.widget2.options"
[chartType]="widgets.widget2.chartType">
</canvas>
</div>
</div>
</div>
<!-- / Widget 2 -->
<!-- Widget 3 -->
<div class="widget" fxFlex="100" fxFlex.gt-sm="34">
<div class="fuse-card auto-width mb-32" [ngClass.gt-sm]="'mb-0 mr-32'">
<div class="p-16 pb-0" fxLayout="row" fxLayoutAlign="start end" fxLayoutWrap>
<div class="pr-16">
<div class="h3 secondary-text">Impressions</div>
<div class="font-size-54 font-weight-300 line-height-1 mt-8">
{{widgets.widget3.impressions.value}}
</div>
</div>
<div class="py-4 font-size-16" fxLayout="row" fxLayoutAlign="start center">
<div fxFlex="row" fxLayoutAlign="start center" class="green-fg"
*ngIf="widgets.widget3.impressions.ofTarget > 0">
<mat-icon class="green-fg mr-4">trending_up</mat-icon>
<span>{{widgets.widget3.impressions.ofTarget}}%</span>
</div>
<div fxFlex="row" fxLayoutAlign="start center" class="red-fg"
*ngIf="widgets.widget3.impressions.ofTarget < 0">
<mat-icon class="red-fg mr-4">trending_down</mat-icon>
<span>{{widgets.widget3.impressions.ofTarget}}%</span>
</div>
<div class="ml-4 text-nowrap">of target</div>
</div>
</div>
<div class="h-96 w-100-p">
<canvas baseChart
[datasets]="widgets.widget3.datasets"
[labels]="widgets.widget3.labels"
[colors]="widgets.widget3.colors"
[options]="widgets.widget3.options"
[chartType]="widgets.widget3.chartType">
</canvas>
</div>
</div>
</div>
<!-- / Widget 3 -->
<!-- Widget 4 -->
<div class="widget" fxFlex="100" fxFlex.gt-sm="33">
<div class="fuse-card auto-width">
<div class="p-16 pb-0" fxLayout="row" fxLayoutAlign="start end" fxLayoutWrap>
<div class="pr-16">
<div class="h3 secondary-text">Visits</div>
<div class="font-size-54 font-weight-300 line-height-1 mt-8">
{{widgets.widget4.visits.value}}
</div>
</div>
<div class="py-4 font-size-16" fxLayout="row" fxLayoutAlign="start center">
<div fxFlex="row" fxLayoutAlign="start center" class="green-fg"
*ngIf="widgets.widget4.visits.ofTarget > 0">
<mat-icon class="green-fg mr-4">trending_up</mat-icon>
<span>{{widgets.widget4.visits.ofTarget}}%</span>
</div>
<div fxFlex="row" fxLayoutAlign="start center" class="red-fg"
*ngIf="widgets.widget4.visits.ofTarget < 0">
<mat-icon class="red-fg mr-4">trending_down</mat-icon>
<span>{{widgets.widget4.visits.ofTarget}}%</span>
</div>
<div class="ml-4 text-nowrap">of target</div>
</div>
</div>
<div class="h-96 w-100-p">
<canvas baseChart
[datasets]="widgets.widget4.datasets"
[labels]="widgets.widget4.labels"
[colors]="widgets.widget4.colors"
[options]="widgets.widget4.options"
[chartType]="widgets.widget4.chartType">
</canvas>
</div>
</div>
</div>
<!-- / Widget 4 -->
</div>
<!-- Widget 5 -->
<div class="pt-48 pb-24 font-size-18 font-weight-300">
How many pages your users visit?
</div>
<div class="fuse-card auto-width">
<div class="position-relative p-24"
fxLayout="row" fxLayoutAlign="space-between center">
<div fxLayout="column" fxLayoutAlign="start start">
<span class="h2">Visitors & Page views</span>
</div>
<div fxLayout="row" fxLayoutAlign="start center">
<div class="py-8 px-12 border-radius-2 line-height-1 mr-8 cursor-pointer"
(click)="widget5SelectedDay = 'yesterday'"
[ngClass]="{'grey-300-bg': widget5SelectedDay === 'yesterday'}">
Yesterday
</div>
<div class="py-8 px-12 border-radius-2 line-height-1 mr-8 cursor-pointer"
(click)="widget5SelectedDay = 'today'"
[ngClass]="{'grey-300-bg': widget5SelectedDay === 'today'}">
Today
</div>
</div>
</div>
<div class="position-relative h-368 pb-16">
<canvas baseChart
[datasets]="widgets.widget5.datasets[widget5SelectedDay]"
[labels]="widgets.widget5.labels"
[colors]="widgets.widget5.colors"
[options]="widgets.widget5.options"
[chartType]="widgets.widget5.chartType">
</canvas>
</div>
</div>
<!-- / Widget 5 -->
<!-- Widget 6 -->
<div class="pt-48 pb-24 font-size-18 font-weight-300">
Where are your users?
</div>
<div class="fuse-card auto-width p-16">
<agm-map class="h-640 w-100-p"
[minZoom]="2"
[maxZoom]="2"
[fullscreenControl]="false"
[rotateControl]="false"
[zoomControl]="false"
[scaleControl]="false"
[streetViewControl]="false"
[scrollwheel]="false"
[styles]="widgets.widget6.styles">
<agm-marker
*ngFor="let marker of widgets.widget6.markers"
[latitude]="marker.lat"
[longitude]="marker.lng">
<agm-info-window>
<strong>{{marker.label}}</strong>
</agm-info-window>
</agm-marker>
</agm-map>
</div>
<!-- / Widget 6 -->
</div>
<div class="right">
<div fxLayout="row" fxLayoutWrap
fxLayout.gt-md="column">
<!-- Widget 7 -->
<div class="mb-48" [ngClass.lt-lg]="'mr-32'">
<div class="pb-24 font-size-18 font-weight-300">
What are your top devices?
</div>
<div class="fuse-card">
<div class="p-16">
<div class="h1 font-weight-300">Sessions by device</div>
</div>
<div class="h-200">
<ngx-charts-pie-chart
*fuseIfOnDom
[scheme]="widgets.widget7.scheme"
[results]="widgets.widget7.devices"
[doughnut]="true">
</ngx-charts-pie-chart>
</div>
<div class="p-16" fxLayout="row" fxLayoutAlign="center center">
<div class="px-16" fxLayout="column" fxLayoutAlign="start center"
*ngFor="let device of widgets.widget7.devices">
<div class="h4 secondary-text">{{device.name}}</div>
<div class="h2 font-weight-300 py-8">{{device.value}}%</div>
<div fxLayout="row" fxLayoutAlign="center center">
<mat-icon class="s-18 pr-4 red-fg"
*ngIf="device.change < 0">
arrow_downward
</mat-icon>
<mat-icon class="s-18 pr-4 green-fg"
*ngIf="device.change > 0">
arrow_upward
</mat-icon>
<div class="h5 red-fg"
[ngClass]="{'red-fg': device.change < 0, 'green-fg': device.change > 0}">
{{device.change}}%
</div>
</div>
</div>
</div>
<div class="card-divider mb-0"></div>
<div class="px-16" fxLayout="row" fxLayoutAlign="space-between center">
<mat-form-field>
<mat-select class="simplified" value="7days">
<mat-option value="today">Today</mat-option>
<mat-option value="yesterday">Yesterday</mat-option>
<mat-option value="7days">Last 7 days</mat-option>
<mat-option value="28days">Last 28 days</mat-option>
<mat-option value="90days">Last 90 days</mat-option>
</mat-select>
</mat-form-field>
<button mat-button color="accent">OVERVIEW</button>
</div>
</div>
</div>
<!-- / Widget 7 -->
<!-- Widget 8 -->
<div class="mb-48" [ngClass.lt-lg]="'mr-32'">
<div class="pb-24 font-size-18 font-weight-300">
How are your sales?
</div>
<div class="fuse-card">
<div class="mat-light-blue-600-bg">
<div class="p-16" fxLayout="row" fxLayoutAlign="space-between center">
<div class="pr-16">
<div class="h1 font-weight-300">Sales</div>
<div class="h5 secondary-text">Lifetime sum of your sales</div>
</div>
<div>
<button mat-icon-button [matMenuTriggerFor]="card19Menu" aria-label="more">
<mat-icon>more_vert</mat-icon>
</button>
<mat-menu #card19Menu="matMenu">
<button mat-menu-item>
<mat-icon>trending_up</mat-icon>
<span>Trend</span>
</button>
<button mat-menu-item>
<mat-icon>history</mat-icon>
<span>History</span>
</button>
<button mat-menu-item>
<mat-icon>notifications_off</mat-icon>
<span>Disable alerts</span>
</button>
</mat-menu>
</div>
</div>
<div class="p-16 pt-8" fxLayout="row" fxLayoutAlign="space-between end">
<div class="font-size-48 font-weight-300 line-height-1">{{widgets.widget8.today}}</div>
<div fxLayout="row" fxLayoutAlign="start center">
<mat-icon *ngIf="widgets.widget8.change.value > 0">trending_up</mat-icon>
<mat-icon *ngIf="widgets.widget8.change.value < 0">trending_down</mat-icon>
<div class="ml-8">{{widgets.widget8.change.value}}
({{widgets.widget8.change.percentage}}%)
</div>
</div>
</div>
</div>
<mat-tab-group backgroundColor="accent">
<mat-tab label="1DAY">
<div class="h-200 my-16">
<ngx-charts-line-chart
*fuseIfOnDom
[scheme]="widgets.widget8.scheme"
[results]="widgets.widget8.data"
[xAxis]="false"
[yAxis]="true"
[yScaleMin]="widgets.widget8.dataMin"
[yScaleMax]="widgets.widget8.dataMax">
</ngx-charts-line-chart>
</div>
</mat-tab>
<mat-tab label="1WEEK">
<div class="h-200 my-16">
<ngx-charts-line-chart
*fuseIfOnDom
[scheme]="widgets.widget8.scheme"
[results]="widgets.widget8.data"
[xAxis]="false"
[yAxis]="true"
[yScaleMin]="widgets.widget8.dataMin"
[yScaleMax]="widgets.widget8.dataMax">
</ngx-charts-line-chart>
</div>
</mat-tab>
<mat-tab label="1MONTH">
<div class="h-200 my-16">
<ngx-charts-line-chart
*fuseIfOnDom
[scheme]="widgets.widget8.scheme"
[results]="widgets.widget8.data"
[xAxis]="false"
[yAxis]="true"
[yScaleMin]="widgets.widget8.dataMin"
[yScaleMax]="widgets.widget8.dataMax">
</ngx-charts-line-chart>
</div>
</mat-tab>
</mat-tab-group>
</div>
</div>
<!-- / Widget 8 -->
<!-- Widget 9 -->
<div class="mb-48" [ngClass.lt-lg]="'mr-32'">
<div class="pb-24 font-size-18 font-weight-300" [ngClass.lt-lg]="'pt-0'">
What are your top campaigns?
</div>
<div class="fuse-card">
<div class="p-16" fxLayout="row" fxLayoutAlign="space-between center">
<div class="h1 pr-16">Top campaigns</div>
<div>
<button mat-icon-button [matMenuTriggerFor]="card20Menu" aria-label="more">
<mat-icon>more_vert</mat-icon>
</button>
<mat-menu #card20Menu="matMenu">
<button fxLayout="row" fxLayoutAlign="start center" mat-menu-item>
<mat-icon color="accent">check_box</mat-icon>
<span>Show Clicks</span>
</button>
<button fxLayout="row" fxLayoutAlign="start center" mat-menu-item>
<mat-icon color="accent">check_box</mat-icon>
<span>Show Conversion</span>
</button>
<button fxLayout="row" fxLayoutAlign="start center" mat-menu-item>
<mat-icon>check_box_outline_blank</mat-icon>
<span>Show CPC</span>
</button>
</mat-menu>
</div>
</div>
<table class="simple clickable">
<thead>
<tr>
<th></th>
<th class="text-right">Clicks</th>
<th class="text-right">Conv</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let row of widgets.widget9.rows">
<td>{{row.title}}</td>
<td class="text-right">{{row.clicks}}</td>
<td class="text-right">{{row.conversion}}</td>
</tr>
</tbody>
</table>
<div class="card-divider full-width"></div>
<div class="p-8 pt-16" fxLayout="row" fxLayoutAlign="start center">
<button mat-button color="accent">GO TO CAMPAIGNS</button>
</div>
</div>
</div>
<!-- / widget 9 -->
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,49 @@
@import "src/app/core/scss/fuse";
#dashboard-analytics {
.main-widget {
display: flex;
flex-direction: column;
}
.content {
display: flex;
flex: 1 1 auto;
padding: 32px;
min-width: 0;
@include media-breakpoint-down('md') {
flex-direction: column;
}
.left {
display: flex;
flex-direction: column;
flex: 1 1 auto;
min-width: 0;
.widget {
flex: 1 1 auto;
min-width: 0;
}
}
.right {
display: flex;
flex: 0 0 auto;
width: 320px;
min-width: 320px;
max-width: 320px;
@include media-breakpoint-down('md') {
flex: 1 0 100%;
margin-top: 32px;
width: 100%;
min-width: 0;
max-width: none;
}
}
}
}

View File

@@ -0,0 +1,91 @@
import { Component, ViewEncapsulation } from '@angular/core';
import { AnalyticsDashboardService } from './analytics.service';
import { fuseAnimations } from '../../../../../core/animations';
@Component({
selector : 'fuse-analytics-dashboard',
templateUrl : './analytics.component.html',
styleUrls : ['./analytics.component.scss'],
encapsulation: ViewEncapsulation.None,
animations : fuseAnimations
})
export class FuseAnalyticsDashboardComponent
{
widgets: any;
widget1SelectedYear = '2016';
widget5SelectedDay = 'today';
constructor(
private analyticsDashboardService: AnalyticsDashboardService
)
{
// Get the widgets from the service
this.widgets = this.analyticsDashboardService.widgets;
// Register the custom chart.js plugin
this.registerCustomChartJSPlugin();
}
/**
* Register a custom plugin
*/
registerCustomChartJSPlugin()
{
(<any>window).Chart.plugins.register({
afterDatasetsDraw: function (chart, easing) {
// Only activate the plugin if it's made available
// in the options
if (
!chart.options.plugins.xLabelsOnTop ||
(chart.options.plugins.xLabelsOnTop && chart.options.plugins.xLabelsOnTop.active === false)
)
{
return;
}
// To only draw at the end of animation, check for easing === 1
const ctx = chart.ctx;
chart.data.datasets.forEach(function (dataset, i) {
const meta = chart.getDatasetMeta(i);
if ( !meta.hidden )
{
meta.data.forEach(function (element, index) {
// Draw the text in black, with the specified font
ctx.fillStyle = 'rgba(255, 255, 255, 0.7)';
const fontSize = 13;
const fontStyle = 'normal';
const fontFamily = 'Roboto, Helvetica Neue, Arial';
ctx.font = (<any>window).Chart.helpers.fontString(fontSize, fontStyle, fontFamily);
// Just naively convert to string for now
const dataString = dataset.data[index].toString() + 'k';
// Make sure alignment settings are correct
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
const padding = 15;
const startY = 24;
const position = element.tooltipPosition();
ctx.fillText(dataString, position.x, startY);
ctx.save();
ctx.beginPath();
ctx.setLineDash([5, 3]);
ctx.moveTo(position.x, startY + padding);
ctx.lineTo(position.x, position.y - padding);
ctx.strokeStyle = 'rgba(255,255,255,0.12)';
ctx.stroke();
ctx.restore();
});
}
});
}
});
}
}

View File

@@ -0,0 +1,41 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { SharedModule } from '../../../../../core/modules/shared.module';
import { NgxChartsModule } from '@swimlane/ngx-charts';
import { AgmCoreModule } from '@agm/core';
import { FuseAnalyticsDashboardComponent } from './analytics.component';
import { AnalyticsDashboardService } from './analytics.service';
import { FuseWidgetModule } from '../../../../../core/components/widget/widget.module';
const routes: Routes = [
{
path : '**',
component: FuseAnalyticsDashboardComponent,
resolve : {
data: AnalyticsDashboardService
}
}
];
@NgModule({
imports : [
SharedModule,
RouterModule.forChild(routes),
FuseWidgetModule,
NgxChartsModule,
AgmCoreModule.forRoot({
apiKey: 'AIzaSyD81ecsCj4yYpcXSLFcYU97PvRsE_X8Bx8'
})
],
declarations: [
FuseAnalyticsDashboardComponent
],
providers : [
AnalyticsDashboardService
]
})
export class FuseAnalyticsDashboardModule
{
}

View File

@@ -0,0 +1,48 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs/Observable';
import { HttpClient } from '@angular/common/http';
@Injectable()
export class AnalyticsDashboardService implements Resolve<any>
{
widgets: any[];
constructor(
private http: HttpClient
)
{
}
/**
* Resolve
* @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.getWidgets()
]).then(
() => {
resolve();
},
reject
);
});
}
getWidgets(): Promise<any>
{
return new Promise((resolve, reject) => {
this.http.get('api/analytics-dashboard-widgets')
.subscribe((response: any) => {
this.widgets = response;
resolve(response);
}, reject);
});
}
}

View File

@@ -10,7 +10,9 @@
<div fxLayout="row" fxLayoutAlign="space-between start">
<span class="mat-display-1 mb-0 welcome-message" *fuseIfOnDom [@animate]="{value:'*',params:{x:'50px'}}">Welcome back, John!</span>
<span class="mat-display-1 mb-0 welcome-message" *fuseIfOnDom
[@animate]="{value:'*',params:{x:'50px'}}">Welcome back, John!
</span>
<button mat-icon-button fuseMatSidenavToggler="dashboards-right-sidenav" fxHide.gt-md>
<mat-icon>menu</mat-icon>
@@ -21,7 +23,8 @@
<div class="selected-project">{{selectedProject.name}}</div>
<button mat-icon-button class="project-selector" [matMenuTriggerFor]="projectsMenu" aria-label="Select project">
<button mat-icon-button class="project-selector" [matMenuTriggerFor]="projectsMenu"
aria-label="Select project">
<mat-icon>more_horiz</mat-icon>
</button>
@@ -41,16 +44,20 @@
<mat-tab label="Home">
<div class="widget-group p-12" fxLayout="row" fxFlex="100" fxLayoutWrap *fuseIfOnDom [@animateStagger]="{value:'50'}">
<div class="widget-group grey-100-bg p-12" fxLayout="row" fxFlex="100" fxLayoutWrap *fuseIfOnDom
[@animateStagger]="{value:'50'}">
<!-- WIDGET 1 -->
<fuse-widget [@animate]="{value:'*',params:{y:'100%'}}" class="widget" fxLayout="column" fxFlex="100" fxFlex.gt-xs="50" fxFlex.gt-md="25">
<fuse-widget [@animate]="{value:'*',params:{y:'100%'}}" class="widget" fxLayout="column"
fxFlex="100" fxFlex.gt-xs="50" fxFlex.gt-md="25">
<!-- Front -->
<div class="fuse-widget-front mat-white-bg mat-elevation-z2">
<div class="pl-16 pr-8 py-16 h-52" fxLayout="row" fxLayoutAlign="space-between center">
<div class="pl-16 pr-8 py-16 h-52" fxLayout="row"
fxLayoutAlign="space-between center">
<mat-form-field>
<mat-select class="simplified font-size-16" [(ngModel)]="widgets.widget1.currentRange"
<mat-select class="simplified font-size-16"
[(ngModel)]="widgets.widget1.currentRange"
aria-label="Change range">
<mat-option *ngFor="let range of widgets.widget1.ranges | keys"
[value]="range.key">
@@ -67,12 +74,17 @@
<div class="light-blue-fg font-size-72 line-height-72">
{{widgets.widget1.data.count[widgets.widget1.currentRange]}}
</div>
<div class="h3 secondary-text font-weight-500">{{widgets.widget1.data.label}}</div>
<div class="h3 secondary-text font-weight-500">{{widgets.widget1.data.label}}
</div>
</div>
<div class="p-16 grey-50-bg border-top" fxLayout="row" fxLayoutAlign="start center">
<span class="h4 secondary-text text-truncate">{{widgets.widget1.data.extra.label}}:</span>
<span class="h4 ml-8">{{widgets.widget1.data.extra.count[widgets.widget1.currentRange]}}</span>
<span class="h4 secondary-text text-truncate">
{{widgets.widget1.data.extra.label}}:
</span>
<span class="h4 ml-8">
{{widgets.widget1.data.extra.count[widgets.widget1.currentRange]}}
</span>
</div>
</div>
<!-- / Front -->
@@ -94,14 +106,17 @@
<!-- / WIDGET 1 -->
<!-- WIDGET 2 -->
<fuse-widget [@animate]="{value:'*',params:{y:'100%'}}" class="widget" fxLayout="column" fxFlex="100" fxFlex.gt-xs="50" fxFlex.gt-md="25">
<fuse-widget [@animate]="{value:'*',params:{y:'100%'}}" class="widget" fxLayout="column"
fxFlex="100" fxFlex.gt-xs="50" fxFlex.gt-md="25">
<!-- Front -->
<div class="fuse-widget-front mat-white-bg mat-elevation-z2">
<div class="pl-16 pr-8 py-16 h-52" fxLayout="row" fxLayoutAlign="space-between center">
<div class="pl-16 pr-8 py-16 h-52" fxLayout="row"
fxLayoutAlign="space-between center">
<div class="h3">{{widgets.widget2.title}}</div>
<button mat-icon-button fuseWidgetToggle class="fuse-widget-flip-button" aria-label="more">
<button mat-icon-button fuseWidgetToggle class="fuse-widget-flip-button"
aria-label="more">
<mat-icon>more_vert</mat-icon>
</button>
</div>
@@ -110,11 +125,14 @@
<div class="red-fg font-size-72 line-height-72">
{{widgets.widget2.data.count}}
</div>
<div class="h3 secondary-text font-weight-500">{{widgets.widget2.data.label}}</div>
<div class="h3 secondary-text font-weight-500">{{widgets.widget2.data.label}}
</div>
</div>
<div class="p-16 grey-50-bg border-top" fxLayout="row" fxLayoutAlign="start center">
<span class="h4 secondary-text text-truncate">{{widgets.widget2.data.extra.label}}:</span>
<span class="h4 secondary-text text-truncate">
{{widgets.widget2.data.extra.label}}:
</span>
<span class="h4 ml-8">{{widgets.widget2.data.extra.count}}</span>
</div>
</div>
@@ -137,14 +155,17 @@
<!-- / WIDGET 2 -->
<!-- WIDGET 3 -->
<fuse-widget [@animate]="{value:'*',params:{y:'100%'}}" class="widget" fxLayout="column" fxFlex="100" fxFlex.gt-xs="50" fxFlex.gt-md="25">
<fuse-widget [@animate]="{value:'*',params:{y:'100%'}}" class="widget" fxLayout="column"
fxFlex="100" fxFlex.gt-xs="50" fxFlex.gt-md="25">
<!-- Front -->
<div class="fuse-widget-front mat-white-bg mat-elevation-z2">
<div class="pl-16 pr-8 py-16 h-52" fxLayout="row" fxLayoutAlign="space-between center">
<div class="pl-16 pr-8 py-16 h-52" fxLayout="row"
fxLayoutAlign="space-between center">
<div class="h3">{{widgets.widget3.title}}</div>
<button mat-icon-button fuseWidgetToggle class="fuse-widget-flip-button" aria-label="more">
<button mat-icon-button fuseWidgetToggle class="fuse-widget-flip-button"
aria-label="more">
<mat-icon>more_vert</mat-icon>
</button>
</div>
@@ -153,11 +174,14 @@
<div class="orange-fg font-size-72 line-height-72">
{{widgets.widget3.data.count}}
</div>
<div class="h3 secondary-text font-weight-500">{{widgets.widget3.data.label}}</div>
<div class="h3 secondary-text font-weight-500">{{widgets.widget3.data.label}}
</div>
</div>
<div class="p-16 grey-50-bg border-top" fxLayout="row" fxLayoutAlign="start center">
<span class="h4 secondary-text text-truncate">{{widgets.widget3.data.extra.label}}:</span>
<span class="h4 secondary-text text-truncate">
{{widgets.widget3.data.extra.label}}:
</span>
<span class="h4 ml-8">{{widgets.widget3.data.extra.count}}</span>
</div>
</div>
@@ -180,26 +204,33 @@
<!-- / WIDGET 3 -->
<!-- WIDGET 4 -->
<fuse-widget [@animate]="{value:'*',params:{y:'100%'}}" class="widget" fxLayout="column" fxFlex="100" fxFlex.gt-xs="50" fxFlex.gt-md="25">
<fuse-widget [@animate]="{value:'*',params:{y:'100%'}}" class="widget" fxLayout="column"
fxFlex="100" fxFlex.gt-xs="50" fxFlex.gt-md="25">
<!-- Front -->
<div class="fuse-widget-front mat-white-bg mat-elevation-z2">
<div class="pl-16 pr-8 py-16 h-52" fxLayout="row" fxLayoutAlign="space-between center">
<div class="pl-16 pr-8 py-16 h-52" fxLayout="row"
fxLayoutAlign="space-between center">
<div class="h3">{{widgets.widget4.title}}</div>
<button mat-icon-button fuseWidgetToggle class="fuse-widget-flip-button" aria-label="more">
<button mat-icon-button fuseWidgetToggle class="fuse-widget-flip-button"
aria-label="more">
<mat-icon>more_vert</mat-icon>
</button>
</div>
<div class="pt-8 pb-32" fxLayout="column" fxLayoutAlign="center center">
<div class="blue-grey-fg font-size-72 line-height-72">{{widgets.widget4.data.count}}
<div class="blue-grey-fg font-size-72 line-height-72">
{{widgets.widget4.data.count}}
</div>
<div class="h3 secondary-text font-weight-500">{{widgets.widget4.data.label}}
</div>
<div class="h3 secondary-text font-weight-500">{{widgets.widget4.data.label}}</div>
</div>
<div class="p-16 grey-50-bg border-top" fxLayout="row" fxLayoutAlign="start center">
<span class="h4 secondary-text text-truncate">{{widgets.widget4.data.extra.label}}:</span>
<span class="h4 secondary-text text-truncate">
{{widgets.widget4.data.extra.label}}:
</span>
<span class="h4 ml-8">{{widgets.widget4.data.extra.count}}</span>
</div>
</div>
@@ -221,18 +252,19 @@
</fuse-widget>
<!-- / WIDGET 4 -->
<!-- WIDGET 5 -->
<fuse-widget [@animate]="{value:'*',params:{y:'100%'}}" class="widget" fxLayout="row" fxFlex="100">
<fuse-widget [@animate]="{value:'*',params:{y:'100%'}}" fxLayout="row" fxFlex="100"
class="widget widget5">
<!-- Front -->
<div class="fuse-widget-front mat-white-bg mat-elevation-z2">
<div class="px-16 border-bottom" fxLayout="row" fxLayoutAlign="space-between center" fxLayoutWrap>
<div class="px-16 border-bottom" fxLayout="row" fxLayoutAlign="space-between center"
fxLayoutWrap>
<div fxFlex class="py-8 h3">{{widgets.widget5.title}}</div>
<div fxFlex class="py-16 h3">{{widgets.widget5.title}}</div>
<div fxFlex="0 1 auto" class="py-8" fxLayout="row">
<div fxFlex="0 1 auto" class="py-16" fxLayout="row">
<button mat-button class="px-16"
*ngFor="let range of widgets.widget5.ranges | keys"
(click)="widget5.currentRange = range.key"
@@ -242,8 +274,9 @@
</div>
</div>
<div fxLayout="row" fxLayoutAlign="start end" fxLayoutWrap>
<div class="h-420 mb-16" fxLayout="row" fxFlex="100" fxFlex.gt-sm="50">
<div fxLayout="row" fxLayoutAlign="start start" fxLayoutWrap>
<div class="h-420 my-16" fxLayout="row" fxFlex="100" fxFlex.gt-sm="50">
<ngx-charts-bar-vertical-stacked
*fuseIfOnDom
[scheme]="widget5.scheme"
@@ -260,10 +293,12 @@
</ngx-charts-bar-vertical-stacked>
</div>
<div class="mb-16" fxFlex="100" fxFlex.gt-sm="50" fxLayoutAlign="row" fxLayoutWrap>
<div class="my-16" fxFlex="100" fxFlex.gt-sm="50" fxLayoutAlign="row"
fxLayoutWrap>
<div fxLayout="column" fxFlex="100" fxFlex.gt-sm="50" fxLayoutAlign="center"
*ngFor="let widget of widgets.widget5.supporting | keys">
*ngFor="let widget of widgets.widget5.supporting | keys"
class="mb-24">
<div class="px-24">
<div class="h4 secondary-text">{{widget.value.label}}</div>
@@ -297,17 +332,19 @@
</fuse-widget>
<!-- / WIDGET 5 -->
<!-- WIDGET 6 -->
<fuse-widget [@animate]="{value:'*',params:{y:'100%'}}" class="widget" fxLayout="column" fxFlex="100" fxFlex.gt-sm="50">
<fuse-widget [@animate]="{value:'*',params:{y:'100%'}}" class="widget" fxLayout="column"
fxFlex="100" fxFlex.gt-sm="50">
<!-- Front -->
<div class="fuse-widget-front mat-white-bg mat-elevation-z2">
<div class="px-16 border-bottom" fxLayout="row" fxLayoutAlign="space-between center">
<div class="px-16 py-8 border-bottom" fxLayout="row"
fxLayoutAlign="space-between center">
<div class="h3">{{widgets.widget6.title}}</div>
<mat-form-field>
<mat-select class="simplified" [(ngModel)]="widget6.currentRange" aria-label="Change range">
<mat-select class="simplified" [(ngModel)]="widget6.currentRange"
aria-label="Change range">
<mat-option *ngFor="let range of widgets.widget6.ranges | keys"
[value]="range.key">
{{range.value}}
@@ -330,14 +367,21 @@
</ngx-charts-pie-chart>
</div>
<div class="py-8 mh-16 border-top" fxLayout="row" fxLayoutAlign="start center" fxLayoutWrap>
<div class="py-8 border-right" fxLayout="column" fxLayoutAlign="center center" fxFlex="100" fxFlex.gt-sm="50">
<span class="mat-display-1 mb-0">{{widgets.widget6.footerLeft.count[widget6.currentRange]}}</span>
<div class="py-8 mh-16 border-top" fxLayout="row" fxLayoutAlign="start center"
fxLayoutWrap>
<div class="py-8 border-right" fxLayout="column" fxLayoutAlign="center center"
fxFlex="100" fxFlex.gt-sm="50">
<span class="mat-display-1 mb-0">
{{widgets.widget6.footerLeft.count[widget6.currentRange]}}
</span>
<span class="h4">{{widgets.widget6.footerLeft.title}}</span>
</div>
<div class="py-8" fxLayout="column" fxLayoutAlign="center center" fxFlex="100" fxFlex.gt-sm="50">
<span class="mat-display-1 mb-0">{{widgets.widget6.footerRight.count[widget6.currentRange]}}</span>
<div class="py-8" fxLayout="column" fxLayoutAlign="center center" fxFlex="100"
fxFlex.gt-sm="50">
<span class="mat-display-1 mb-0">
{{widgets.widget6.footerRight.count[widget6.currentRange]}}
</span>
<span class="h4">{{widgets.widget6.footerRight.title}}</span>
</div>
</div>
@@ -349,12 +393,14 @@
<!-- / WIDGET 6 -->
<!-- WIDGET 7 -->
<fuse-widget [@animate]="{value:'*',params:{y:'100%'}}" class="widget" fxLayout="column" fxFlex="100" fxFlex.gt-sm="50">
<fuse-widget [@animate]="{value:'*',params:{y:'100%'}}" class="widget" fxLayout="column"
fxFlex="100" fxFlex.gt-sm="50">
<!-- Front -->
<div class="fuse-widget-front mat-white-bg mat-elevation-z2">
<div class="px-16 border-bottom" fxLayout="row" fxLayoutAlign="space-between center">
<div class="px-16 py-8 border-bottom" fxLayout="row"
fxLayoutAlign="space-between center">
<div class="h3">{{widgets.widget7.title}}</div>
<mat-form-field>
<mat-select class="simplified" [(ngModel)]="widget7.currentRange"
@@ -391,20 +437,21 @@
</div>
<!-- / WIDGET GROUP -->
</mat-tab>
<mat-tab label="Budget Summary">
<!-- WIDGET GROUP -->
<div class="widget-group" fxLayout="row" fxFlex="100" fxLayoutWrap *fuseIfOnDom [@animateStagger]="{value:'50'}">
<div class="widget-group grey-100-bg" fxLayout="row" fxFlex="100" fxLayoutWrap *fuseIfOnDom
[@animateStagger]="{value:'50'}">
<!-- WIDGET 8 -->
<fuse-widget [@animate]="{value:'*',params:{y:'100%'}}" class="widget" fxLayout="column" fxFlex="100" fxFlex.gt-sm="50">
<fuse-widget [@animate]="{value:'*',params:{y:'100%'}}" class="widget" fxLayout="column"
fxFlex="100" fxFlex.gt-sm="50">
<!-- Front -->
<div class="fuse-widget-front mat-white-bg mat-elevation-z2">
<div class="h3 p-16">
<div class="h3 px-16 py-24">
{{widgets.widget8.title}}
</div>
@@ -431,11 +478,13 @@
<!-- / WIDGET 8 -->
<!-- WIDGET 9 -->
<fuse-widget [@animate]="{value:'*',params:{y:'100%'}}" class="widget" fxLayout="column" fxFlex="100" fxFlex.gt-sm="50">
<fuse-widget [@animate]="{value:'*',params:{y:'100%'}}" class="widget" fxLayout="column"
fxFlex="100" fxFlex.gt-sm="50">
<!-- Front -->
<div class="fuse-widget-front mat-white-bg mat-elevation-z2">
<div class="px-16 border-bottom" fxLayout="row" fxLayoutAlign="space-between center">
<div class="px-16 py-12 border-bottom" fxLayout="row"
fxLayoutAlign="space-between center">
<div class="h3">{{widgets.widget9.title}}</div>
<mat-form-field>
<mat-select [(ngModel)]="widget9.currentRange" aria-label="Change range">
@@ -447,7 +496,7 @@
</mat-form-field>
</div>
<div class="p-16" fxLayout="column" fxLayoutAlign="center" fxLayout.gt-xs="row"
<div class="px-16 py-24" fxLayout="column" fxLayoutAlign="center" fxLayout.gt-xs="row"
fxLayoutAlign.gt-xs="space-between end">
<div fxFlex="0 1 auto">
<div class="h4 secondary-text">{{widgets.widget9.weeklySpent.title}}</div>
@@ -474,7 +523,7 @@
</div>
</div>
<div class="p-16" fxLayout="column" fxLayoutAlign="center" fxLayout.gt-xs="row"
<div class="px-16 py-24" fxLayout="column" fxLayoutAlign="center" fxLayout.gt-xs="row"
fxLayoutAlign.gt-xs="space-between end">
<div fxFlex="0 1 auto">
<div class="h4 secondary-text">{{widgets.widget9.totalSpent.title}}</div>
@@ -501,7 +550,7 @@
</div>
</div>
<div class="p-16" fxLayout="column" fxLayoutAlign="center" fxLayout.gt-xs="row"
<div class="px-16 py-24" fxLayout="column" fxLayoutAlign="center" fxLayout.gt-xs="row"
fxLayoutAlign.gt-xs="space-between end">
<div fxFlex="0 1 auto">
<div class="h4 secondary-text">{{widgets.widget9.remaining.title}}</div>
@@ -528,7 +577,7 @@
</div>
</div>
<div class="p-16 border-top">
<div class="px-16 py-24 border-top">
<div class="h4 secondary-text">{{widgets.widget9.totalBudget.title}}</div>
<div class="pt-8 mat-display-1 m-0 font-weight-300">
<span class="secondary-text">$</span>
@@ -543,7 +592,8 @@
<!-- / WIDGET 9 -->
<!-- WIDGET 10 -->
<fuse-widget [@animate]="{value:'*',params:{y:'100%'}}" class="widget" fxLayout="row" fxFlex="100">
<fuse-widget [@animate]="{value:'*',params:{y:'100%'}}" class="widget" fxLayout="row"
fxFlex="100">
<!-- Front -->
<div class="fuse-widget-front mat-white-bg mat-elevation-z2">
@@ -569,7 +619,8 @@
<span class="p-4" [ngClass]="cell.classes">
{{cell.value}}
</span>
<mat-icon *ngIf="cell.icon" class="s-16">{{cell.icon}}</mat-icon>
<mat-icon *ngIf="cell.icon" class="s-16">{{cell.icon}}
</mat-icon>
</td>
</tr>
</tbody>
@@ -590,17 +641,21 @@
<mat-tab label="Team Members">
<!-- WIDGET GROUP -->
<div class="widget-group" fxLayout="row" fxFlex="100" fxLayoutWrap *fuseIfOnDom [@animateStagger]="{value:'50'}">
<div class="widget-group grey-100-bg" fxLayout="row" fxFlex="100" fxLayoutWrap *fuseIfOnDom
[@animateStagger]="{value:'50'}">
<!-- WIDGET 11 -->
<fuse-widget [@animate]="{value:'*',params:{y:'100%'}}" class="widget" fxLayout="row" fxFlex="100">
<fuse-widget [@animate]="{value:'*',params:{y:'100%'}}" class="widget" fxLayout="row"
fxFlex="100">
<!-- Front -->
<div class="fuse-widget-front mat-white-bg mat-elevation-z2">
<div class="p-24 mb-8 border-bottom" fxLayout="row" fxLayoutAlign="space-between center">
<div class="p-24 mb-8 border-bottom" fxLayout="row"
fxLayoutAlign="space-between center">
<div class="h2">{{widgets.widget11.title}}</div>
<div class="text-boxed red-bg white-fg m-0">{{widgets.widget11.table.rows.length}}
<div class="text-boxed red-bg white-fg m-0">
{{widgets.widget11.table.rows.length}}
members
</div>
</div>
@@ -620,13 +675,15 @@
<ng-container cdkColumnDef="name">
<mat-header-cell *cdkHeaderCellDef>Name</mat-header-cell>
<mat-cell *cdkCellDef="let contact">
<p class="text-truncate font-weight-600">{{contact.name}} {{contact.lastName}}</p>
<p class="text-truncate font-weight-600">{{contact.name}}
{{contact.lastName}}</p>
</mat-cell>
</ng-container>
<!-- Position Column -->
<ng-container cdkColumnDef="position">
<mat-header-cell *cdkHeaderCellDef fxHide fxShow.gt-sm>Position</mat-header-cell>
<mat-header-cell *cdkHeaderCellDef fxHide fxShow.gt-sm>Position
</mat-header-cell>
<mat-cell *cdkCellDef="let contact" fxHide fxShow.gt-sm>
<p class="position text-truncate">
{{contact.position}}
@@ -636,7 +693,8 @@
<!-- Office Column -->
<ng-container cdkColumnDef="office">
<mat-header-cell *cdkHeaderCellDef fxHide fxShow.gt-md>Office</mat-header-cell>
<mat-header-cell *cdkHeaderCellDef fxHide fxShow.gt-md>Office
</mat-header-cell>
<mat-cell *cdkCellDef="let contact" fxHide fxShow.gt-md>
<p class="office text-truncate">
{{contact.office}}
@@ -644,10 +702,10 @@
</mat-cell>
</ng-container>
<!-- Email Column -->
<ng-container cdkColumnDef="email">
<mat-header-cell *cdkHeaderCellDef fxHide fxShow.gt-sm>Email</mat-header-cell>
<mat-header-cell *cdkHeaderCellDef fxHide fxShow.gt-sm>Email
</mat-header-cell>
<mat-cell *cdkCellDef="let contact" fxHide fxShow.gt-sm>
<p class="email text-truncate">
{{contact.email}}
@@ -657,7 +715,8 @@
<!-- Phone Column -->
<ng-container cdkColumnDef="phone">
<mat-header-cell *cdkHeaderCellDef fxHide fxShow.gt-md>Phone</mat-header-cell>
<mat-header-cell *cdkHeaderCellDef fxHide fxShow.gt-md>Phone
</mat-header-cell>
<mat-cell *cdkCellDef="let contact" fxHide fxShow.gt-md>
<p class="phone text-truncate">
{{contact.phone}}
@@ -665,7 +724,8 @@
</mat-cell>
</ng-container>
<mat-header-row *cdkHeaderRowDef="widgets.widget11.table.columns"></mat-header-row>
<mat-header-row
*cdkHeaderRowDef="widgets.widget11.table.columns"></mat-header-row>
<mat-row *cdkRowDef="let contact; columns: widgets.widget11.table.columns;">
</mat-row>
</mat-table>
@@ -688,7 +748,8 @@
<!-- / CENTER -->
<!-- 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>
@@ -703,7 +764,7 @@
<div class="pl-16 pr-8 py-16" fxLayout="row" fxLayoutAlign="space-between center">
<div class="h3">{{dateNow | date: 'EEEE, h:m:ss'}}</div>
<div class="h3">{{dateNow | date: 'EEEE, HH:mm:ss'}}</div>
<div>
<button mat-icon-button [matMenuTriggerFor]="moreMenu" aria-label="more">
@@ -768,7 +829,8 @@
<div class="p-16 pb-32" fxLayout="column" fxLayoutAlign="center center">
<div fxLayout="row" fxLayoutAlign="center center">
<mat-icon fontSet="meteocons" [fontIcon]="widgets.weatherWidget.locations[widgets.weatherWidget.currentLocation].icon"
<mat-icon fontSet="meteocons"
[fontIcon]="widgets.weatherWidget.locations[widgets.weatherWidget.currentLocation].icon"
class="icon s-40 mr-16"></mat-icon>
<span class="mat-display-2 m-0 font-weight-300 secondary-text">
{{widgets.weatherWidget.locations[widgets.weatherWidget.currentLocation].temp[widgets.weatherWidget.tempUnit]}}
@@ -789,12 +851,16 @@
<div fxLayout="row" fxLayoutAlign="start center">
<mat-icon fontSet="meteocons" fontIcon="icon-compass" class="mr-8 s-16"></mat-icon>
<span>{{widgets.weatherWidget.locations[widgets.weatherWidget.currentLocation].windDirection}}</span>
<span>
{{widgets.weatherWidget.locations[widgets.weatherWidget.currentLocation].windDirection}}
</span>
</div>
<div fxLayout="row" fxLayoutAlign="start center">
<mat-icon fontSet="meteocons" fontIcon="icon-rainy" class="mr-8 s-16"></mat-icon>
<span>{{widgets.weatherWidget.locations[widgets.weatherWidget.currentLocation].rainProbability}}</span>
<span>
{{widgets.weatherWidget.locations[widgets.weatherWidget.currentLocation].rainProbability}}
</span>
</div>
</div>
@@ -806,7 +872,9 @@
<mat-icon fontSet="meteocons" [fontIcon]="day.icon" class="mr-16"></mat-icon>
<span class="h2">{{day.temp[widgets.weatherWidget.tempUnit]}}</span>
<span class="h2 font-weight-300 secondary-text text-super">&deg;</span>
<span class="h2 font-weight-300 secondary-text">{{widgets.weatherWidget.tempUnit}}</span>
<span class="h2 font-weight-300 secondary-text">
{{widgets.weatherWidget.tempUnit}}
</span>
</div>
</div>
</div>

View File

@@ -56,5 +56,15 @@
min-width: 250px !important;
max-width: 250px !important;
}
.widget {
&.widget5 {
.gridline-path.gridline-path-horizontal {
display: none;
}
}
}
}
}

View File

@@ -1,19 +1,20 @@
import { Component, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';
import { ProjectsDashboardService } from './projects.service';
import * as shape from 'd3-shape';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { Observable } from 'rxjs/Observable';
import { DataSource } from '@angular/cdk/collections';
import { ProjectDashboardService } from './project.service';
import { fuseAnimations } from '../../../../../core/animations';
@Component({
selector : 'fuse-project',
selector : 'fuse-project-dashboard',
templateUrl : './project.component.html',
styleUrls : ['./project.component.scss'],
encapsulation: ViewEncapsulation.None,
animations : fuseAnimations
})
export class FuseProjectComponent implements OnInit, OnDestroy
export class FuseProjectDashboardComponent implements OnInit, OnDestroy
{
projects: any[];
selectedProject: any;
@@ -28,13 +29,11 @@ export class FuseProjectComponent implements OnInit, OnDestroy
dateNow = Date.now();
constructor(private projectsDashboardService: ProjectsDashboardService)
constructor(private projectDashboardService: ProjectDashboardService)
{
this.projects = this.projectsDashboardService.projects;
this.projects = this.projectDashboardService.projects;
this.selectedProject = this.projects[0];
this.widgets = this.projectsDashboardService.widgets;
this.widgets = this.projectDashboardService.widgets;
/**
* Widget 5

View File

@@ -1,17 +1,18 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { FuseProjectComponent } from './project.component';
import { SharedModule } from '../../../../../core/modules/shared.module';
import { ProjectsDashboardService } from './projects.service';
import { FuseProjectDashboardComponent } from './project.component';
import { ProjectDashboardService } from './project.service';
import { FuseWidgetModule } from '../../../../../core/components/widget/widget.module';
import { NgxChartsModule } from '@swimlane/ngx-charts';
const routes: Routes = [
{
path : 'apps/dashboards/project',
component: FuseProjectComponent,
path : '**',
component: FuseProjectDashboardComponent,
resolve : {
data: ProjectsDashboardService
data: ProjectDashboardService
}
}
];
@@ -24,13 +25,13 @@ const routes: Routes = [
NgxChartsModule
],
declarations: [
FuseProjectComponent
FuseProjectDashboardComponent
],
providers : [
ProjectsDashboardService
ProjectDashboardService
]
})
export class ProjectModule
export class FuseProjectDashboardModule
{
}

View File

@@ -4,7 +4,7 @@ import { Observable } from 'rxjs/Observable';
import { HttpClient } from '@angular/common/http';
@Injectable()
export class ProjectsDashboardService implements Resolve<any>
export class ProjectDashboardService implements Resolve<any>
{
projects: any[];
widgets: any[];
@@ -41,7 +41,7 @@ export class ProjectsDashboardService implements Resolve<any>
getProjects(): Promise<any>
{
return new Promise((resolve, reject) => {
this.http.get('api/projects-dashboard-projects')
this.http.get('api/project-dashboard-projects')
.subscribe((response: any) => {
this.projects = response;
resolve(response);
@@ -52,7 +52,7 @@ export class ProjectsDashboardService implements Resolve<any>
getWidgets(): Promise<any>
{
return new Promise((resolve, reject) => {
this.http.get('api/projects-dashboard-widgets')
this.http.get('api/project-dashboard-widgets')
.subscribe((response: any) => {
this.widgets = response;
resolve(response);

View File

@@ -1,9 +1,5 @@
import { Component, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';
import { EcommerceDashboardService } from './dashboard.service';
import * as shape from 'd3-shape';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { Observable } from 'rxjs/Observable';
import { DataSource } from '@angular/cdk/collections';
import { fuseAnimations } from '../../../../../core/animations';
@Component({
@@ -23,14 +19,10 @@ export class FuseEcommerceDashboardComponent implements OnInit, OnDestroy
widget6: any = {};
widget7: any = {};
dateNow = Date.now();
constructor(private projectsDashboardService: EcommerceDashboardService)
{
this.projects = this.projectsDashboardService.projects;
this.selectedProject = this.projects[0];
this.widgets = this.projectsDashboardService.widgets;
/**

View File

@@ -41,7 +41,7 @@ export class EcommerceDashboardService implements Resolve<any>
getProjects(): Promise<any>
{
return new Promise((resolve, reject) => {
this.http.get('api/projects-dashboard-projects')
this.http.get('api/project-dashboard-projects')
.subscribe((response: any) => {
this.projects = response;
resolve(response);

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

View File

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

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

View File

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

View File

@@ -69,7 +69,7 @@
<!-- / CENTER -->
<!-- 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">
<fuse-file-manager-details-sidenav></fuse-file-manager-details-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>

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