Project is initialized

This commit is contained in:
병준 박 2019-07-25 22:18:34 +09:00
commit 9e8ea6c188
316 changed files with 39147 additions and 0 deletions

13
.editorconfig Normal file
View File

@ -0,0 +1,13 @@
# Editor configuration, see https://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
max_line_length = off
trim_trailing_whitespace = false

46
.gitignore vendored Normal file
View File

@ -0,0 +1,46 @@
# See http://help.github.com/ignore-files/ for more about ignoring files.
# compiled output
/dist
/tmp
/out-tsc
# Only exists if Bazel was run
/bazel-out
# dependencies
/node_modules
# profiling files
chrome-profiler-events.json
speed-measure-plugin.json
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history/*
# misc
/.sass-cache
/connect.lock
/coverage
/libpeerconnection.log
npm-debug.log
yarn-error.log
testem.log
/typings
# System Files
.DS_Store
Thumbs.db

13
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,13 @@
{
"editor.tabSize": 2,
"editor.insertSpaces": true,
"editor.formatOnSave": true,
"editor.formatOnPaste": true,
"editor.autoClosingBrackets": "languageDefined",
"editor.trimAutoWhitespace": true,
"files.trimTrailingWhitespace": true,
"files.trimFinalNewlines": true,
"git.ignoreLimitWarning": true,
"prettier.singleQuote": true,
"debug.node.autoAttach": "on"
}

27
README.md Normal file
View File

@ -0,0 +1,27 @@
# Backend
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 8.1.2.
## 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.
## Code scaffolding
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
## Build
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build.
## Running unit tests
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
## Running end-to-end tests
Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).
## Further help
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).

132
angular.json Normal file
View File

@ -0,0 +1,132 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"backend": {
"projectType": "application",
"schematics": {
"@schematics/angular:component": {
"style": "scss"
}
},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist/backend",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.app.json",
"aot": false,
"assets": ["src/favicon.ico", "src/assets"],
"styles": ["src/styles.scss"],
"scripts": []
},
"configurations": {
"production": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"extractCss": true,
"namedChunks": false,
"aot": true,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
"budgets": [
{
"type": "initial",
"maximumWarning": "2mb",
"maximumError": "5mb"
}
]
},
"hmr": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.hmr.ts"
}
]
},
"ec": {
"sourceMap": true,
"extractCss": true
}
}
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"browserTarget": "backend:build"
},
"configurations": {
"production": {
"browserTarget": "backend:build:production"
},
"hmr": {
"hmr": true,
"browserTarget": "backend:build:hmr"
},
"ec": {
"browserTarget": "backend:build:ec"
}
}
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "backend:build"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "src/test.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.spec.json",
"karmaConfig": "karma.conf.js",
"assets": ["src/favicon.ico", "src/assets"],
"styles": ["src/styles.scss"],
"scripts": []
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"tsconfig.app.json",
"tsconfig.spec.json",
"e2e/tsconfig.json"
],
"exclude": ["**/node_modules/**"]
}
},
"e2e": {
"builder": "@angular-devkit/build-angular:protractor",
"options": {
"protractorConfig": "e2e/protractor.conf.js",
"devServerTarget": "backend:serve"
},
"configurations": {
"production": {
"devServerTarget": "backend:serve:production"
}
}
}
}
}
},
"defaultProject": "backend"
}

12
browserslist Normal file
View File

@ -0,0 +1,12 @@
# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
# For additional information regarding the format and rule options, please see:
# https://github.com/browserslist/browserslist#queries
# You can see what browsers were selected by your queries by running:
# npx browserslist
> 0.5%
last 2 versions
Firefox ESR
not dead
not IE 9-11 # For IE 9-11 support, remove 'not'.

32
e2e/protractor.conf.js Normal file
View File

@ -0,0 +1,32 @@
// @ts-check
// Protractor configuration file, see link for more information
// https://github.com/angular/protractor/blob/master/lib/config.ts
const { SpecReporter } = require('jasmine-spec-reporter');
/**
* @type { import("protractor").Config }
*/
exports.config = {
allScriptsTimeout: 11000,
specs: [
'./src/**/*.e2e-spec.ts'
],
capabilities: {
'browserName': 'chrome'
},
directConnect: true,
baseUrl: 'http://localhost:4200/',
framework: 'jasmine',
jasmineNodeOpts: {
showColors: true,
defaultTimeoutInterval: 30000,
print: function() {}
},
onPrepare() {
require('ts-node').register({
project: require('path').join(__dirname, './tsconfig.json')
});
jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
}
};

23
e2e/src/app.e2e-spec.ts Normal file
View File

@ -0,0 +1,23 @@
import { AppPage } from './app.po';
import { browser, logging } from 'protractor';
describe('workspace-project App', () => {
let page: AppPage;
beforeEach(() => {
page = new AppPage();
});
it('should display welcome message', () => {
page.navigateTo();
expect(page.getTitleText()).toEqual('Welcome to backend!');
});
afterEach(async () => {
// Assert that there are no errors emitted from the browser
const logs = await browser.manage().logs().get(logging.Type.BROWSER);
expect(logs).not.toContain(jasmine.objectContaining({
level: logging.Level.SEVERE,
} as logging.Entry));
});
});

11
e2e/src/app.po.ts Normal file
View File

@ -0,0 +1,11 @@
import { browser, by, element } from 'protractor';
export class AppPage {
navigateTo() {
return browser.get(browser.baseUrl) as Promise<any>;
}
getTitleText() {
return element(by.css('app-root h1')).getText() as Promise<string>;
}
}

13
e2e/tsconfig.json Normal file
View File

@ -0,0 +1,13 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/e2e",
"module": "commonjs",
"target": "es5",
"types": [
"jasmine",
"jasminewd2",
"node"
]
}
}

32
karma.conf.js Normal file
View File

@ -0,0 +1,32 @@
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage-istanbul-reporter'),
require('@angular-devkit/build-angular/plugins/karma')
],
client: {
clearContext: false // leave Jasmine Spec Runner output visible in browser
},
coverageIstanbulReporter: {
dir: require('path').join(__dirname, './coverage/backend'),
reports: ['html', 'lcovonly', 'text-summary'],
fixWebpackSourcePaths: true
},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false,
restartOnFileChange: true
});
};

12472
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

74
package.json Normal file
View File

@ -0,0 +1,74 @@
{
"name": "backend",
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve --open",
"start:hmr": "ng serve --configuration hmr --source-map=false --hmr-warning=false",
"start:hmr:sourcemaps": "ng serve --configuration hmr --source-map=true --hmr-warning=false",
"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"
},
"private": true,
"dependencies": {
"@angular/animations": "~8.1.2",
"@angular/cdk": "^8.1.1",
"@angular/common": "~8.1.2",
"@angular/compiler": "~8.1.2",
"@angular/core": "~8.1.2",
"@angular/flex-layout": "^8.0.0-beta.26",
"@angular/forms": "~8.1.2",
"@angular/material": "^8.1.1",
"@angular/material-moment-adapter": "^8.1.1",
"@angular/platform-browser": "~8.1.2",
"@angular/platform-browser-dynamic": "~8.1.2",
"@angular/router": "~8.1.2",
"@ngrx/effects": "^8.1.0",
"@ngrx/router-store": "^8.1.0",
"@ngrx/store": "^8.1.0",
"@ngx-translate/core": "^11.0.1",
"@swimlane/ngx-datatable": "^15.0.2",
"d3": "^5.9.7",
"hammerjs": "^2.0.8",
"lodash": "^4.17.15",
"moment": "^2.24.0",
"ngx-cookie-service": "^2.2.0",
"perfect-scrollbar": "^1.4.0",
"prismjs": "^1.17.1",
"rxjs": "^6.5.2",
"tslib": "^1.9.0",
"web-animations-js": "^2.3.2",
"zone.js": "~0.9.1"
},
"devDependencies": {
"@angular-devkit/build-angular": "~0.801.2",
"@angular/cli": "~8.1.2",
"@angular/compiler-cli": "~8.1.2",
"@angular/language-service": "~8.1.2",
"@angularclass/hmr": "^2.1.3",
"@ngrx/store-devtools": "^8.1.0",
"@types/lodash": "^4.14.136",
"@types/node": "~8.9.4",
"@types/jasmine": "~3.3.8",
"@types/jasminewd2": "~2.0.3",
"@types/prismjs": "^1.16.0",
"codelyzer": "^5.0.0",
"jasmine-core": "~3.4.0",
"jasmine-spec-reporter": "~4.2.1",
"karma": "~4.1.0",
"karma-chrome-launcher": "~2.2.0",
"karma-coverage-istanbul-reporter": "~2.0.1",
"karma-jasmine": "~2.0.1",
"karma-jasmine-html-reporter": "^1.4.0",
"ngrx-store-freeze": "^0.2.4",
"protractor": "~5.4.0",
"ts-node": "~7.0.0",
"tslint": "~5.15.0",
"typescript": "~3.4.3"
}
}

View File

@ -0,0 +1,552 @@
import {
sequence,
trigger,
animate,
style,
group,
query,
transition,
animateChild,
state,
animation,
useAnimation,
stagger
} from '@angular/animations';
const customAnimation = animation(
[
style({
opacity: '{{opacity}}',
transform: 'scale({{scale}}) translate3d({{x}}, {{y}}, {{z}})'
}),
animate('{{duration}} {{delay}} cubic-bezier(0.0, 0.0, 0.2, 1)', style('*'))
],
{
params: {
duration: '200ms',
delay: '0ms',
opacity: '0',
scale: '1',
x: '0',
y: '0',
z: '0'
}
}
);
export const fuseAnimations = [
trigger('animate', [
transition('void => *', [useAnimation(customAnimation)])
]),
trigger('animateStagger', [
state('50', style('*')),
state('100', style('*')),
state('200', style('*')),
transition(
'void => 50',
query('@*', [stagger('50ms', [animateChild()])], { optional: true })
),
transition(
'void => 100',
query('@*', [stagger('100ms', [animateChild()])], { optional: true })
),
transition(
'void => 200',
query('@*', [stagger('200ms', [animateChild()])], { optional: true })
)
]),
trigger('fadeInOut', [
state(
'0, void',
style({
opacity: 0
})
),
state(
'1, *',
style({
opacity: 1
})
),
transition('1 => 0', animate('300ms ease-out')),
transition('0 => 1', animate('300ms ease-in')),
transition('void <=> *', animate('300ms ease-in'))
]),
trigger('slideInOut', [
state(
'0',
style({
height: '0px'
})
),
state(
'1',
style({
height: '*'
})
),
transition('1 => 0', animate('300ms ease-out')),
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%)'
})
),
state(
'*',
style({
transform: 'translateX(0)'
})
),
transition('void => *', animate('300ms')),
transition('* => void', animate('300ms'))
]),
trigger('slideInRight', [
state(
'void',
style({
transform: 'translateX(100%)'
})
),
state(
'*',
style({
transform: 'translateX(0)'
})
),
transition('void => *', animate('300ms')),
transition('* => void', animate('300ms'))
]),
trigger('slideInTop', [
state(
'void',
style({
transform: 'translateY(-100%)'
})
),
state(
'*',
style({
transform: 'translateY(0)'
})
),
transition('void => *', animate('300ms')),
transition('* => void', animate('300ms'))
]),
trigger('slideInBottom', [
state(
'void',
style({
transform: 'translateY(100%)'
})
),
state(
'*',
style({
transform: 'translateY(0)'
})
),
transition('void => *', animate('300ms')),
transition('* => void', animate('300ms'))
]),
trigger('expandCollapse', [
state(
'void',
style({
height: '0px'
})
),
state(
'*',
style({
height: '*'
})
),
transition('void => *', animate('300ms ease-out')),
transition('* => void', animate('300ms ease-in'))
]),
// -----------------------------------------------------------------------------------------------------
// @ Router animations
// -----------------------------------------------------------------------------------------------------
trigger('routerTransitionLeft', [
transition('* => *', [
query(
'content > :enter, content > :leave',
[
style({
position: 'absolute',
top: 0,
bottom: 0,
left: 0,
right: 0
})
],
{ optional: true }
),
query(
'content > :enter',
[
style({
transform: 'translateX(100%)',
opacity: 0
})
],
{ optional: true }
),
sequence([
group([
query(
'content > :leave',
[
style({
transform: 'translateX(0)',
opacity: 1
}),
animate(
'600ms cubic-bezier(0.0, 0.0, 0.2, 1)',
style({
transform: 'translateX(-100%)',
opacity: 0
})
)
],
{ optional: true }
),
query(
'content > :enter',
[
style({ transform: 'translateX(100%)' }),
animate(
'600ms cubic-bezier(0.0, 0.0, 0.2, 1)',
style({
transform: 'translateX(0%)',
opacity: 1
})
)
],
{ optional: true }
)
]),
query('content > :leave', animateChild(), { optional: true }),
query('content > :enter', animateChild(), { optional: true })
])
])
]),
trigger('routerTransitionRight', [
transition('* => *', [
query(
'content > :enter, content > :leave',
[
style({
position: 'absolute',
top: 0,
bottom: 0,
left: 0,
right: 0
})
],
{ optional: true }
),
query(
'content > :enter',
[
style({
transform: 'translateX(-100%)',
opacity: 0
})
],
{ optional: true }
),
sequence([
group([
query(
'content > :leave',
[
style({
transform: 'translateX(0)',
opacity: 1
}),
animate(
'600ms cubic-bezier(0.0, 0.0, 0.2, 1)',
style({
transform: 'translateX(100%)',
opacity: 0
})
)
],
{ optional: true }
),
query(
'content > :enter',
[
style({ transform: 'translateX(-100%)' }),
animate(
'600ms cubic-bezier(0.0, 0.0, 0.2, 1)',
style({
transform: 'translateX(0%)',
opacity: 1
})
)
],
{ optional: true }
)
]),
query('content > :leave', animateChild(), { optional: true }),
query('content > :enter', animateChild(), { optional: true })
])
])
]),
trigger('routerTransitionUp', [
transition('* => *', [
query(
'content > :enter, content > :leave',
[
style({
position: 'absolute',
top: 0,
bottom: 0,
left: 0,
right: 0
})
],
{ optional: true }
),
query(
'content > :enter',
[
style({
transform: 'translateY(100%)',
opacity: 0
})
],
{ optional: true }
),
group([
query(
'content > :leave',
[
style({
transform: 'translateY(0)',
opacity: 1
}),
animate(
'600ms cubic-bezier(0.0, 0.0, 0.2, 1)',
style({
transform: 'translateY(-100%)',
opacity: 0
})
)
],
{ optional: true }
),
query(
'content > :enter',
[
style({ transform: 'translateY(100%)' }),
animate(
'600ms cubic-bezier(0.0, 0.0, 0.2, 1)',
style({
transform: 'translateY(0%)',
opacity: 1
})
)
],
{ optional: true }
)
]),
query('content > :leave', animateChild(), { optional: true }),
query('content > :enter', animateChild(), { optional: true })
])
]),
trigger('routerTransitionDown', [
transition('* => *', [
query(
'content > :enter, content > :leave',
[
style({
position: 'absolute',
top: 0,
bottom: 0,
left: 0,
right: 0
})
],
{ optional: true }
),
query(
'content > :enter',
[
style({
transform: 'translateY(-100%)',
opacity: 0
})
],
{ optional: true }
),
sequence([
group([
query(
'content > :leave',
[
style({
transform: 'translateY(0)',
opacity: 1
}),
animate(
'600ms cubic-bezier(0.0, 0.0, 0.2, 1)',
style({
transform: 'translateY(100%)',
opacity: 0
})
)
],
{ optional: true }
),
query(
'content > :enter',
[
style({ transform: 'translateY(-100%)' }),
animate(
'600ms cubic-bezier(0.0, 0.0, 0.2, 1)',
style({
transform: 'translateY(0%)',
opacity: 1
})
)
],
{ optional: true }
)
]),
query('content > :leave', animateChild(), { optional: true }),
query('content > :enter', animateChild(), { optional: true })
])
])
]),
trigger('routerTransitionFade', [
transition(
'* => *',
group([
query(
'content > :enter, content > :leave ',
[
style({
position: 'absolute',
top: 0,
bottom: 0,
left: 0,
right: 0
})
],
{ optional: true }
),
query(
'content > :enter',
[
style({
opacity: 0
})
],
{ optional: true }
),
query(
'content > :leave',
[
style({
opacity: 1
}),
animate(
'300ms cubic-bezier(0.0, 0.0, 0.2, 1)',
style({
opacity: 0
})
)
],
{ optional: true }
),
query(
'content > :enter',
[
style({
opacity: 0
}),
animate(
'300ms cubic-bezier(0.0, 0.0, 0.2, 1)',
style({
opacity: 1
})
)
],
{ optional: true }
),
query('content > :enter', animateChild(), { optional: true }),
query('content > :leave', animateChild(), { optional: true })
])
)
])
];

View File

@ -0,0 +1,12 @@
<h1 matDialogTitle>Confirm</h1>
<div mat-dialog-content>{{ confirmMessage }}</div>
<div mat-dialog-actions class="pt-24">
<button
mat-raised-button
class="mat-accent mr-16"
(click)="dialogRef.close(true)"
>
Confirm
</button>
<button mat-button (click)="dialogRef.close(false)">Cancel</button>
</div>

View File

@ -0,0 +1,18 @@
import { Component } from '@angular/core';
import { MatDialogRef } from '@angular/material/dialog';
@Component({
selector: 'fuse-confirm-dialog',
templateUrl: './confirm-dialog.component.html',
styleUrls: ['./confirm-dialog.component.scss']
})
export class FuseConfirmDialogComponent {
public confirmMessage: string;
/**
* Constructor
*
* @param {MatDialogRef<FuseConfirmDialogComponent>} dialogRef
*/
constructor(public dialogRef: MatDialogRef<FuseConfirmDialogComponent>) {}
}

View File

@ -0,0 +1,12 @@
import { NgModule } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatDialogModule } from '@angular/material/dialog';
import { FuseConfirmDialogComponent } from 'src/@fuse/components/confirm-dialog/confirm-dialog.component';
@NgModule({
declarations: [FuseConfirmDialogComponent],
imports: [MatDialogModule, MatButtonModule],
entryComponents: [FuseConfirmDialogComponent]
})
export class FuseConfirmDialogModule {}

View File

@ -0,0 +1,37 @@
<div class="fuse-countdown">
<div class="time days">
<div class="value">
{{ countdown.days }}
</div>
<div class="title">
days
</div>
</div>
<div class="time hours">
<div class="value">
{{ countdown.hours }}
</div>
<div class="title">
hours
</div>
</div>
<div class="time minutes">
<div class="value">
{{ countdown.minutes }}
</div>
<div class="title">
minutes
</div>
</div>
<div class="time seconds">
<div class="value">
{{ countdown.seconds }}
</div>
<div class="title">
seconds
</div>
</div>
</div>

View File

@ -0,0 +1,30 @@
fuse-countdown {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
.fuse-countdown {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
text-align: center;
.time {
display: flex;
flex-direction: column;
padding: 0 12px;
.value {
font-size: 34px;
line-height: 34px;
padding-bottom: 8px;
}
.title {
color: rgba(0, 0, 0, 0.54);
}
}
}
}

View File

@ -0,0 +1,107 @@
import {
Component,
Input,
OnDestroy,
OnInit,
ViewEncapsulation
} from '@angular/core';
import { interval, Subject } from 'rxjs';
import { map, takeUntil } from 'rxjs/operators';
import * as moment from 'moment';
@Component({
selector: 'fuse-countdown',
templateUrl: './countdown.component.html',
styleUrls: ['./countdown.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class FuseCountdownComponent implements OnInit, OnDestroy {
// Event date
@Input('eventDate')
eventDate;
countdown: any;
// Private
private _unsubscribeAll: Subject<any>;
/**
* Constructor
*/
constructor() {
// Set the defaults
this.countdown = {
days: '',
hours: '',
minutes: '',
seconds: ''
};
// Set the private defaults
this._unsubscribeAll = new Subject();
}
// -----------------------------------------------------------------------------------------------------
// @ Lifecycle hooks
// -----------------------------------------------------------------------------------------------------
/**
* On init
*/
ngOnInit(): void {
const currDate = moment();
const eventDate = moment(this.eventDate);
// Get the difference in between the current date and event date in seconds
let diff = eventDate.diff(currDate, 'seconds');
// Calculate the remaining time for the first time so there will be no
// delay on the countdown
this.countdown = this._secondsToRemaining(diff);
// Create a subscribable interval
const countDown = interval(1000).pipe(
map(value => {
return (diff = diff - 1);
}),
map(value => {
return this._secondsToRemaining(value);
})
);
// Subscribe to the countdown interval
countDown.pipe(takeUntil(this._unsubscribeAll)).subscribe(value => {
this.countdown = value;
});
}
/**
* On destroy
*/
ngOnDestroy(): void {
// Unsubscribe from all subscriptions
this._unsubscribeAll.next();
this._unsubscribeAll.complete();
}
// -----------------------------------------------------------------------------------------------------
// @ Private methods
// -----------------------------------------------------------------------------------------------------
/**
* Converts given seconds to a remaining time
*
* @param seconds
* @private
*/
private _secondsToRemaining(seconds): any {
const timeLeft = moment.duration(seconds, 'seconds');
return {
days: timeLeft.asDays().toFixed(0),
hours: timeLeft.hours(),
minutes: timeLeft.minutes(),
seconds: timeLeft.seconds()
};
}
}

View File

@ -0,0 +1,9 @@
import { NgModule } from '@angular/core';
import { FuseCountdownComponent } from 'src/@fuse/components/countdown/countdown.component';
@NgModule({
declarations: [FuseCountdownComponent],
exports: [FuseCountdownComponent]
})
export class FuseCountdownModule {}

View File

@ -0,0 +1,13 @@
@mixin fuse-countdown-theme($theme) {
$foreground: map-get($theme, foreground);
fuse-countdown {
.fuse-countdown {
.time {
.title {
color: map-get($foreground, secondary-text);
}
}
}
}
}

View File

@ -0,0 +1,107 @@
<!-- DEMO CONTENT -->
<div class="demo-content line-height-1.75">
<h1 class="m-0">Early Sunrise in Winter</h1>
<h4 class="mt-0 secondary-text">Demo Content</h4>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse tortor
nibh, convallis sed purus nec, auctor venenatis nisl. Suspendisse potenti.
Nullam sagittis nulla in diam finibus, sed pharetra velit vestibulum.
Suspendisse euismod in urna eu posuere.
</p>
<blockquote class="my-24">
<p>
Nunc vel lacinia lorem. Nullam tincidunt sed purus eu placerat. Donec id
dictum erat. Etiam enim ex, dapibus et tortor id, posuere pretium est.
Maecenas fringilla ipsum vitae neque elementum, at eleifend ante
sollicitudin. Donec viverra augue dolor, a venenatis tellus consectetur
sit amet.
</p>
<footer>
John Doe
</footer>
</blockquote>
<p>
Ut ornare sit amet velit vel congue. Ut nec tristique eros. Lorem ipsum
dolor sit amet, consectetur
<b>adipiscing elit</b>. Vivamus sed lorem quis nibh porta iaculis.
Vestibulum ut eleifend ante, at semper mi. Nam imperdiet est nisi, quis
hendrerit tellus convallis et. Morbi in luctus neque. Curabitur elementum ut
est et gravida. In hac habitasse platea dictumst. In et placerat eros, eu
tempor turpis. Curabitur ac felis finibus, elementum lectus vitae, venenatis
est. Integer mollis nisl a eros scelerisque varius. Etiam venenatis lectus
vel erat condimentum tristique vel vel mi. Nulla id euismod mi, et mollis
tellus.
</p>
<p>
Orci varius natoque penatibus et magnis dis parturient montes, nascetur
ridiculus mus. Class aptent taciti sociosqu ad litora torquent per conubia
nostra, per inceptos himenaeos. Curabitur vitae sagittis odio. Suspendisse
ullamcorper nunc non pellentesque laoreet. Curabitur eu tortor id quam
pretium mattis. Proin ut quam velit.
</p>
<img
class="mt-24 w-100-p"
src="assets/images/demo-content/morain-lake.jpg"
style="max-width: 640px"
/>
<p class="mt-8 mb-24 secondary-text">
<em
>Nullam tincidunt sed purus eu placerat. Donec id dictum erat. Etiam enim
ex, dapibus et tortor id.</em
>
</p>
<p>
Quisque sit amet risus enim. Aliquam sit amet interdum justo, at ultricies
sapien. Suspendisse et semper urna, in gravida eros. Quisque id nibh
iaculis, euismod urna sed, egestas nisi. Donec eros metus, congue a
imperdiet feugiat, sagittis nec ipsum. Quisque dapibus mollis felis non
tristique.
</p>
<p>
Ut auctor, metus sed dapibus tempus, urna diam auctor odio, in malesuada
odio risus vitae nisi. Etiam blandit ante urna, vitae placerat massa mollis
in. Duis nec urna ac purus semper dictum ut eget justo. Aenean non sagittis
augue. Sed venenatis rhoncus enim eget ornare.
<a href="#">Donec viverra sed felis at venenatis.</a>
Mauris aliquam fringilla nulla, sit amet congue felis dignissim at.
</p>
<ul>
<li>Orci varius</li>
<li>Magnis dis</li>
<li>Conubia nostra</li>
<li>Semper urna</li>
<li>Donec viverra</li>
</ul>
<p>
Quisque accumsan augue tempor ante mollis, sed placerat diam porttitor.
Vestibulum dignissim sem vel velit eleifend, non pellentesque quam
convallis. Pellentesque est dolor, dignissim ac tortor tristique, hendrerit
iaculis metus. Praesent pulvinar quam eu leo consectetur faucibus.
Vestibulum purus diam, gravida sagittis feugiat sit amet, tincidunt in
ligula. Sed semper vestibulum magna. Lorem ipsum dolor sit amet, consectetur
adipiscing elit. Suspendisse tortor nibh, convallis sed purus nec, auctor
venenatis nisl. Suspendisse potenti.
</p>
<p>
Nullam sagittis nulla in diam finibus, sed pharetra velit vestibulum.
Suspendisse euismod in urna eu posuere. Etiam blandit nunc arcu, et
consectetur orci blandit a. Aliquam condimentum pharetra quam at ultricies.
Nunc vel lacinia lorem. Nullam tincidunt sed purus eu placerat. Donec id
dictum erat. Etiam enim ex, dapibus et tortor id, posuere pretium est.
Maecenas fringilla ipsum vitae neque elementum, at eleifend ante
sollicitudin. Donec viverra augue dolor, a venenatis tellus consectetur sit
amet.
</p>
</div>
<!-- / DEMO CONTENT -->

View File

@ -0,0 +1,13 @@
import { Component } from '@angular/core';
@Component({
selector: 'fuse-demo-content',
templateUrl: './demo-content.component.html',
styleUrls: ['./demo-content.component.scss']
})
export class FuseDemoContentComponent {
/**
* Constructor
*/
constructor() {}
}

View File

@ -0,0 +1,99 @@
<div class="demo-sidebar">
<mat-list>
<h3 matSubheader>Sidebar Demo</h3>
<mat-list-item>
<span>Sidebar Item 1</span>
</mat-list-item>
<mat-divider></mat-divider>
<mat-list-item>
<span>Sidebar Item 2</span>
</mat-list-item>
<mat-divider></mat-divider>
<mat-list-item>
<span>Sidebar Item 3</span>
</mat-list-item>
<mat-divider></mat-divider>
<mat-list-item>
<span>Sidebar Item 4</span>
</mat-list-item>
<mat-divider></mat-divider>
<mat-list-item>
<span>Sidebar Item 5</span>
</mat-list-item>
<mat-divider></mat-divider>
<mat-list-item>
<span>Sidebar Item 6</span>
</mat-list-item>
<mat-divider></mat-divider>
<mat-list-item>
<span>Sidebar Item 7</span>
</mat-list-item>
<mat-divider></mat-divider>
<mat-list-item>
<span>Sidebar Item 8</span>
</mat-list-item>
<mat-divider></mat-divider>
<mat-list-item>
<span>Sidebar Item 9</span>
</mat-list-item>
<mat-divider></mat-divider>
<mat-list-item>
<span>Sidebar Item 10</span>
</mat-list-item>
<mat-divider></mat-divider>
<mat-list-item>
<span>Sidebar Item 11</span>
</mat-list-item>
<mat-divider></mat-divider>
<mat-list-item>
<span>Sidebar Item 12</span>
</mat-list-item>
<mat-divider></mat-divider>
<mat-list-item>
<span>Sidebar Item 13</span>
</mat-list-item>
<mat-divider></mat-divider>
<mat-list-item>
<span>Sidebar Item 14</span>
</mat-list-item>
<mat-divider></mat-divider>
<mat-list-item>
<span>Sidebar Item 15</span>
</mat-list-item>
<mat-divider></mat-divider>
<mat-list-item>
<span>Sidebar Item 16</span>
</mat-list-item>
</mat-list>
</div>

View File

@ -0,0 +1,13 @@
import { Component } from '@angular/core';
@Component({
selector: 'fuse-demo-sidebar',
templateUrl: './demo-sidebar.component.html',
styleUrls: ['./demo-sidebar.component.scss']
})
export class FuseDemoSidebarComponent {
/**
* Constructor
*/
constructor() {}
}

View File

@ -0,0 +1,15 @@
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { MatDividerModule } from '@angular/material/divider';
import { MatListModule } from '@angular/material/list';
import { FuseDemoContentComponent } from './demo-content/demo-content.component';
import { FuseDemoSidebarComponent } from './demo-sidebar/demo-sidebar.component';
@NgModule({
declarations: [FuseDemoContentComponent, FuseDemoSidebarComponent],
imports: [RouterModule, MatDividerModule, MatListModule],
exports: [FuseDemoContentComponent, FuseDemoSidebarComponent]
})
export class FuseDemoModule {}

View File

@ -0,0 +1,9 @@
:host {
display: block;
width: 100%;
padding: 8px;
background: #263238;
cursor: text;
overflow: auto;
-webkit-overflow-scrolling: touch;
}

View File

@ -0,0 +1,147 @@
import {
Component,
ContentChild,
ElementRef,
Input,
OnDestroy,
OnInit
} from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import * as Prism from 'prismjs/prism';
import 'src/@fuse/components/highlight/prism-languages';
@Component({
selector: 'fuse-highlight',
template: '',
styleUrls: ['./highlight.component.scss']
})
export class FuseHighlightComponent implements OnInit, OnDestroy {
// Source
@ContentChild('source', { static: true })
source: ElementRef;
// Lang
@Input('lang')
lang: string;
// Path
@Input('path')
path: string;
// Private
private _unsubscribeAll: Subject<any>;
/**
* Constructor
*
* @param {ElementRef} _elementRef
* @param {HttpClient} _httpClient
*/
constructor(
private _elementRef: ElementRef,
private _httpClient: HttpClient
) {
// Set the private defaults
this._unsubscribeAll = new Subject();
}
// -----------------------------------------------------------------------------------------------------
// @ Lifecycle hooks
// -----------------------------------------------------------------------------------------------------
/**
* On init
*/
ngOnInit(): void {
// If there is no language defined, return...
if (!this.lang) {
return;
}
// If the path is defined...
if (this.path) {
// Get the source
this._httpClient
.get(this.path, { responseType: 'text' })
.pipe(takeUntil(this._unsubscribeAll))
.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);
}
}
/**
* On destroy
*/
ngOnDestroy(): void {
// Unsubscribe from all subscriptions
this._unsubscribeAll.next();
this._unsubscribeAll.complete();
}
// -----------------------------------------------------------------------------------------------------
// @ Public methods
// -----------------------------------------------------------------------------------------------------
/**
* Highlight the given source code
*
* @param sourceCode
*/
highlight(sourceCode): void {
// 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,9 @@
import { NgModule } from '@angular/core';
import { FuseHighlightComponent } from 'src/@fuse/components/highlight/highlight.component';
@NgModule({
declarations: [FuseHighlightComponent],
exports: [FuseHighlightComponent]
})
export class FuseHighlightModule {}

View File

@ -0,0 +1,18 @@
import 'prismjs/prism';
import 'prismjs/components/prism-bash';
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-markup-templating';
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

@ -0,0 +1,12 @@
export * from './confirm-dialog/confirm-dialog.module';
export * from './countdown/countdown.module';
export * from './demo/demo.module';
export * from './highlight/highlight.module';
export * from './material-color-picker/material-color-picker.module';
export * from './navigation/navigation.module';
export * from './progress-bar/progress-bar.module';
export * from './search-bar/search-bar.module';
export * from './shortcuts/shortcuts.module';
export * from './sidebar/sidebar.module';
export * from './theme-options/theme-options.module';
export * from './widget/widget.module';

View File

@ -0,0 +1,93 @@
<button
mat-icon-button
class="mat-elevation-z1"
[matMenuTriggerFor]="colorMenu"
(menuOpened)="onMenuOpen()"
[ngClass]="selectedPalette + '-' + selectedHue"
>
<mat-icon>palette</mat-icon>
</button>
<mat-menu
#colorMenu="matMenu"
class="fuse-material-color-picker-menu mat-elevation-z8"
>
<header
[ngClass]="selectedColor?.class || 'accent'"
class="mat-elevation-z4"
fxLayout="row"
fxLayoutAlign="space-between center"
>
<button
mat-icon-button
class="secondary-text"
[style.visibility]="view === 'hues' ? 'visible' : 'hidden'"
(click)="goToPalettesView($event)"
aria-label="Palette"
>
<mat-icon class="s-20">arrow_back</mat-icon>
</button>
<span *ngIf="selectedColor?.palette">
{{ selectedColor.palette }} {{ selectedColor.hue }}
</span>
<span *ngIf="!selectedColor?.palette">
Select a Color
</span>
<button
mat-icon-button
class="remove-color-button secondary-text"
(click)="removeColor($event)"
aria-label="Remove color"
matTooltip="Remove color"
>
<mat-icon class="s-20">delete</mat-icon>
</button>
</header>
<div [ngSwitch]="view" class="views">
<div class="view" *ngSwitchCase="'palettes'">
<div
fxLayout="row wrap"
fxLayoutAlign="start start"
class="colors"
fusePerfectScrollbar
>
<div
class="color"
fxLayout="row"
fxLayoutAlign="center center"
*ngFor="let color of colors | keys"
[ngClass]="color.key"
[class.selected]="selectedPalette === color.key"
(click)="selectPalette($event, color.key)"
></div>
</div>
</div>
<div class="view" *ngSwitchCase="'hues'">
<div
fxLayout="row wrap"
fxLayoutAlign="start start"
class="colors"
fusePerfectScrollbar
>
<div
class="color"
fxLayout="row"
fxLayoutAlign="center center"
*ngFor="let hue of hues"
[fxHide]="
(selectedPalette === 'fuse-white' && hue !== '500') ||
(selectedPalette === 'fuse-black' && hue !== '500')
"
[ngClass]="selectedPalette + '-' + hue"
[class.selected]="selectedHue === hue"
(click)="selectHue($event, hue)"
></div>
</div>
</div>
</div>
</mat-menu>

View File

@ -0,0 +1,39 @@
.fuse-material-color-picker-menu {
width: 245px;
.mat-menu-content {
padding: 0;
.views {
display: flex;
flex-direction: column;
min-height: 165px;
.view {
overflow: hidden;
.colors {
padding: 1px 0 0 0;
margin-left: -1px;
.color {
width: 40px;
height: 40px;
margin: 0 0 1px 1px;
border-radius: 0;
cursor: pointer;
transition: border-radius 0.4s cubic-bezier(0.25, 0.8, 0.25, 1);
&:hover {
border-radius: 20%;
}
&.selected {
border-radius: 50% !important;
}
}
}
}
}
}
}

View File

@ -0,0 +1,272 @@
import {
Component,
EventEmitter,
forwardRef,
Input,
Output,
ViewEncapsulation
} from '@angular/core';
import { fuseAnimations } from 'src/@fuse/animations';
import { MatColors } from 'src/@fuse/mat-colors';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
export const FUSE_MATERIAL_COLOR_PICKER_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => FuseMaterialColorPickerComponent),
multi: true
};
@Component({
selector: 'fuse-material-color-picker',
templateUrl: './material-color-picker.component.html',
styleUrls: ['./material-color-picker.component.scss'],
animations: fuseAnimations,
encapsulation: ViewEncapsulation.None,
providers: [FUSE_MATERIAL_COLOR_PICKER_VALUE_ACCESSOR]
})
export class FuseMaterialColorPickerComponent implements ControlValueAccessor {
colors: any;
hues: string[];
view: string;
selectedColor: any;
selectedPalette: string;
selectedHue: string;
// Color changed
@Output()
colorChanged: EventEmitter<any>;
// Private
private _color: string;
private _modelChange: (value: any) => void;
private _modelTouched: (value: any) => void;
/**
* Constructor
*/
constructor() {
// Set the defaults
this.colorChanged = new EventEmitter();
this.colors = MatColors.all;
this.hues = [
'50',
'100',
'200',
'300',
'400',
'500',
'600',
'700',
'800',
'900',
'A100',
'A200',
'A400',
'A700'
];
this.selectedHue = '500';
this.view = 'palettes';
// Set the private defaults
this._color = '';
this._modelChange = () => {};
this._modelTouched = () => {};
}
// -----------------------------------------------------------------------------------------------------
// @ Accessors
// -----------------------------------------------------------------------------------------------------
/**
* Selected class
*
* @param value
*/
@Input()
set color(value) {
if (!value || value === '' || this._color === value) {
return;
}
// Split the color value (red-400, blue-500, fuse-navy-700 etc.)
const colorParts = value.split('-');
// Take the very last part as the selected hue value
this.selectedHue = colorParts[colorParts.length - 1];
// Remove the last part
colorParts.pop();
// Rejoin the remaining parts as the selected palette name
this.selectedPalette = colorParts.join('-');
// Store the color value
this._color = value;
}
get color(): string {
return this._color;
}
// -----------------------------------------------------------------------------------------------------
// @ Control Value Accessor implementation
// -----------------------------------------------------------------------------------------------------
/**
* Register on change function
*
* @param fn
*/
registerOnChange(fn: any): void {
this._modelChange = fn;
}
/**
* Register on touched function
*
* @param fn
*/
registerOnTouched(fn: any): void {
this._modelTouched = fn;
}
/**
* Write value to the view from model
*
* @param color
*/
writeValue(color: any): void {
// Return if null
if (!color) {
return;
}
// Set the color
this.color = color;
// Update the selected color
this.updateSelectedColor();
}
// -----------------------------------------------------------------------------------------------------
// @ Public methods
// -----------------------------------------------------------------------------------------------------
/**
* Select palette
*
* @param event
* @param palette
*/
selectPalette(event, palette): void {
// Stop propagation
event.stopPropagation();
// Go to 'hues' view
this.view = 'hues';
// Update the selected palette
this.selectedPalette = palette;
// Update the selected color
this.updateSelectedColor();
}
/**
* Select hue
*
* @param event
* @param hue
*/
selectHue(event, hue): void {
// Stop propagation
event.stopPropagation();
// Update the selected huse
this.selectedHue = hue;
// Update the selected color
this.updateSelectedColor();
}
/**
* Remove color
*
* @param event
*/
removeColor(event): void {
// Stop propagation
event.stopPropagation();
// Return to the 'palettes' view
this.view = 'palettes';
// Clear the selected palette and hue
this.selectedPalette = '';
this.selectedHue = '';
// Update the selected color
this.updateSelectedColor();
}
/**
* Update selected color
*/
updateSelectedColor(): void {
if (
this.selectedColor &&
this.selectedColor.palette === this.selectedPalette &&
this.selectedColor.hue === this.selectedHue
) {
return;
}
// Set the selected color object
this.selectedColor = {
palette: this.selectedPalette,
hue: this.selectedHue,
class: this.selectedPalette + '-' + this.selectedHue,
bg:
this.selectedPalette === ''
? ''
: MatColors.getColor(this.selectedPalette)[this.selectedHue],
fg:
this.selectedPalette === ''
? ''
: MatColors.getColor(this.selectedPalette).contrast[this.selectedHue]
};
// Emit the color changed event
this.colorChanged.emit(this.selectedColor);
// Mark the model as touched
this._modelTouched(this.selectedColor.class);
// Update the model
this._modelChange(this.selectedColor.class);
}
/**
* Go to palettes view
*
* @param event
*/
goToPalettesView(event): void {
// Stop propagation
event.stopPropagation();
this.view = 'palettes';
}
/**
* On menu open
*/
onMenuOpen(): void {
if (this.selectedPalette === '') {
this.view = 'palettes';
} else {
this.view = 'hues';
}
}
}

View File

@ -0,0 +1,29 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FlexLayoutModule } from '@angular/flex-layout';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { MatMenuModule } from '@angular/material/menu';
import { MatTooltipModule } from '@angular/material/tooltip';
import { FusePipesModule } from 'src/@fuse/pipes/pipes.module';
import { FuseMaterialColorPickerComponent } from 'src/@fuse/components/material-color-picker/material-color-picker.component';
@NgModule({
declarations: [FuseMaterialColorPickerComponent],
imports: [
CommonModule,
FlexLayoutModule,
MatButtonModule,
MatIconModule,
MatMenuModule,
MatTooltipModule,
FusePipesModule
],
exports: [FuseMaterialColorPickerComponent]
})
export class FuseMaterialColorPickerModule {}

View File

@ -0,0 +1,11 @@
@mixin fuse-material-color-picker-theme($theme) {
$background: map-get($theme, background);
.fuse-material-color-picker-menu {
.mat-menu-content {
.views {
background: #303030;
}
}
}
}

View File

@ -0,0 +1,104 @@
<ng-container *ngIf="!item.hidden">
<!-- normal collapse -->
<a
class="nav-link"
[ngClass]="item.classes"
*ngIf="!item.url && !item.function"
>
<ng-container *ngTemplateOutlet="itemContent"></ng-container>
</a>
<!-- item.url -->
<a
class="nav-link"
[ngClass]="item.classes"
*ngIf="item.url && !item.externalUrl && !item.function"
[routerLink]="[item.url]"
[routerLinkActive]="['active', 'accent']"
[routerLinkActiveOptions]="{ exact: item.exactMatch || false }"
[target]="item.openInNewTab ? '_blank' : '_self'"
>
<ng-container *ngTemplateOutlet="itemContent"></ng-container>
</a>
<!-- item.externalUrl -->
<a
class="nav-link"
[ngClass]="item.classes"
*ngIf="item.url && item.externalUrl && !item.function"
[href]="item.url"
[target]="item.openInNewTab ? '_blank' : '_self'"
>
<ng-container *ngTemplateOutlet="itemContent"></ng-container>
</a>
<!-- item.function -->
<span
class="nav-link"
[ngClass]="item.classes"
*ngIf="!item.url && item.function"
(click)="item.function()"
>
<ng-container *ngTemplateOutlet="itemContent"></ng-container>
</span>
<!-- item.url && item.function -->
<a
class="nav-link"
[ngClass]="item.classes"
*ngIf="item.url && !item.externalUrl && item.function"
(click)="item.function()"
[routerLink]="[item.url]"
[routerLinkActive]="['active', 'accent']"
[routerLinkActiveOptions]="{ exact: item.exactMatch || false }"
>
<ng-container *ngTemplateOutlet="itemContent"></ng-container>
</a>
<!-- item.externalUrl && item.function -->
<a
class="nav-link"
[ngClass]="item.classes"
*ngIf="item.url && item.externalUrl && item.function"
(click)="item.function()"
[href]="item.url"
[target]="item.openInNewTab ? '_blank' : '_self'"
>
<ng-container *ngTemplateOutlet="itemContent"></ng-container>
</a>
<ng-template #itemContent>
<mat-icon class="nav-link-icon" *ngIf="item.icon">{{ item.icon }}</mat-icon>
<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="collapsable-arrow">keyboard_arrow_right</mat-icon>
</ng-template>
<div class="children" [ngClass]="{ open: isOpen }">
<div class="{{ fuseConfig.layout.navbar.primaryBackground }}">
<ng-container *ngFor="let item of item.children">
<fuse-nav-horizontal-item
*ngIf="item.type == 'item'"
[item]="item"
></fuse-nav-horizontal-item>
<fuse-nav-horizontal-collapsable
*ngIf="item.type == 'collapsable'"
[item]="item"
></fuse-nav-horizontal-collapsable>
<fuse-nav-horizontal-collapsable
*ngIf="item.type == 'group'"
[item]="item"
></fuse-nav-horizontal-collapsable>
</ng-container>
</div>
</div>
</ng-container>

View File

@ -0,0 +1,84 @@
import {
Component,
HostBinding,
HostListener,
Input,
OnDestroy,
OnInit
} from '@angular/core';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { fuseAnimations } from 'src/@fuse/animations';
import { FuseConfigService } from 'src/@fuse/services/config.service';
@Component({
selector: 'fuse-nav-horizontal-collapsable',
templateUrl: './collapsable.component.html',
styleUrls: ['./collapsable.component.scss'],
animations: fuseAnimations
})
export class FuseNavHorizontalCollapsableComponent
implements OnInit, OnDestroy {
fuseConfig: any;
isOpen = false;
@HostBinding('class')
classes = 'nav-collapsable nav-item';
@Input()
item: any;
// Private
private _unsubscribeAll: Subject<any>;
constructor(private _fuseConfigService: FuseConfigService) {
// Set the private defaults
this._unsubscribeAll = new Subject();
}
// -----------------------------------------------------------------------------------------------------
// @ Lifecycle hooks
// -----------------------------------------------------------------------------------------------------
/**
* On init
*/
ngOnInit(): void {
// Subscribe to config changes
this._fuseConfigService.config
.pipe(takeUntil(this._unsubscribeAll))
.subscribe(config => {
this.fuseConfig = config;
});
}
/**
* On destroy
*/
ngOnDestroy(): void {
// Unsubscribe from all subscriptions
this._unsubscribeAll.next();
this._unsubscribeAll.complete();
}
// -----------------------------------------------------------------------------------------------------
// @ Public methods
// -----------------------------------------------------------------------------------------------------
/**
* Open
*/
@HostListener('mouseenter')
open(): void {
this.isOpen = true;
}
/**
* Close
*/
@HostListener('mouseleave')
close(): void {
this.isOpen = false;
}
}

View File

@ -0,0 +1,76 @@
<ng-container *ngIf="!item.hidden">
<!-- item.url -->
<a
class="nav-link"
[ngClass]="item.classes"
*ngIf="item.url && !item.externalUrl && !item.function"
[routerLink]="[item.url]"
[routerLinkActive]="['active', 'accent']"
[routerLinkActiveOptions]="{ exact: item.exactMatch || false }"
[target]="item.openInNewTab ? '_blank' : '_self'"
>
<ng-container *ngTemplateOutlet="itemContent"></ng-container>
</a>
<!-- item.externalUrl -->
<a
class="nav-link"
[ngClass]="item.classes"
*ngIf="item.url && item.externalUrl && !item.function"
[href]="item.url"
[target]="item.openInNewTab ? '_blank' : '_self'"
>
<ng-container *ngTemplateOutlet="itemContent"></ng-container>
</a>
<!-- item.function -->
<span
class="nav-link"
[ngClass]="item.classes"
*ngIf="!item.url && item.function"
(click)="item.function()"
>
<ng-container *ngTemplateOutlet="itemContent"></ng-container>
</span>
<!-- item.url && item.function -->
<a
class="nav-link"
[ngClass]="item.classes"
*ngIf="item.url && !item.externalUrl && item.function"
(click)="item.function()"
[routerLink]="[item.url]"
[routerLinkActive]="['active', 'accent']"
[routerLinkActiveOptions]="{ exact: item.exactMatch || false }"
[target]="item.openInNewTab ? '_blank' : '_self'"
>
<ng-container *ngTemplateOutlet="itemContent"></ng-container>
</a>
<!-- item.externalUrl && item.function -->
<a
class="nav-link"
[ngClass]="item.classes"
*ngIf="item.url && item.externalUrl && item.function"
(click)="item.function()"
[href]="item.url"
[target]="item.openInNewTab ? '_blank' : '_self'"
>
<ng-container *ngTemplateOutlet="itemContent"></ng-container>
</a>
<ng-template #itemContent>
<mat-icon class="nav-link-icon" *ngIf="item.icon">{{ item.icon }}</mat-icon>
<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>
</ng-template>
</ng-container>

View File

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

View File

@ -0,0 +1,19 @@
import { Component, HostBinding, Input } from '@angular/core';
@Component({
selector: 'fuse-nav-horizontal-item',
templateUrl: './item.component.html',
styleUrls: ['./item.component.scss']
})
export class FuseNavHorizontalItemComponent {
@HostBinding('class')
classes = 'nav-item';
@Input()
item: any;
/**
* Constructor
*/
constructor() {}
}

View File

@ -0,0 +1,45 @@
<div
class="nav"
[ngClass]="{
horizontal: layout === 'horizontal',
vertical: layout === 'vertical'
}"
>
<!-- Vertical Navigation Layout -->
<ng-container *ngIf="layout === 'vertical'">
<ng-container *ngFor="let item of navigation">
<fuse-nav-vertical-group
*ngIf="item.type == 'group'"
[item]="item"
></fuse-nav-vertical-group>
<fuse-nav-vertical-collapsable
*ngIf="item.type == 'collapsable'"
[item]="item"
></fuse-nav-vertical-collapsable>
<fuse-nav-vertical-item
*ngIf="item.type == 'item'"
[item]="item"
></fuse-nav-vertical-item>
</ng-container>
</ng-container>
<!-- / Vertical Navigation Layout -->
<!-- Horizontal Navigation Layout -->
<ng-container *ngIf="layout === 'horizontal'">
<ng-container *ngFor="let item of navigation">
<fuse-nav-horizontal-collapsable
*ngIf="item.type == 'group'"
[item]="item"
></fuse-nav-horizontal-collapsable>
<fuse-nav-horizontal-collapsable
*ngIf="item.type == 'collapsable'"
[item]="item"
></fuse-nav-horizontal-collapsable>
<fuse-nav-horizontal-item
*ngIf="item.type == 'item'"
[item]="item"
></fuse-nav-horizontal-item>
</ng-container>
</ng-container>
<!-- / Horizontal Navigation Layout -->
</div>

View File

@ -0,0 +1,12 @@
@import 'src/@fuse/scss/fuse';
fuse-navigation {
display: flex;
flex: 1 0 auto;
> .nav {
margin: 0;
padding: 0;
width: 100%;
}
}

View File

@ -0,0 +1,79 @@
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
Input,
OnInit,
ViewEncapsulation
} from '@angular/core';
import { merge, Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { FuseNavigationService } from 'src/@fuse/components/navigation/navigation.service';
@Component({
selector: 'fuse-navigation',
templateUrl: './navigation.component.html',
styleUrls: ['./navigation.component.scss'],
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class FuseNavigationComponent implements OnInit {
@Input()
layout = 'vertical';
@Input()
navigation: any;
// Private
private _unsubscribeAll: Subject<any>;
/**
*
* @param {ChangeDetectorRef} _changeDetectorRef
* @param {FuseNavigationService} _fuseNavigationService
*/
constructor(
private _changeDetectorRef: ChangeDetectorRef,
private _fuseNavigationService: FuseNavigationService
) {
// Set the private defaults
this._unsubscribeAll = new Subject();
}
// -----------------------------------------------------------------------------------------------------
// @ Lifecycle hooks
// -----------------------------------------------------------------------------------------------------
/**
* On init
*/
ngOnInit(): void {
// Load the navigation either from the input or from the service
this.navigation =
this.navigation || this._fuseNavigationService.getCurrentNavigation();
// Subscribe to the current navigation changes
this._fuseNavigationService.onNavigationChanged
.pipe(takeUntil(this._unsubscribeAll))
.subscribe(() => {
// Load the navigation
this.navigation = this._fuseNavigationService.getCurrentNavigation();
// Mark for check
this._changeDetectorRef.markForCheck();
});
// Subscribe to navigation item
merge(
this._fuseNavigationService.onNavigationItemAdded,
this._fuseNavigationService.onNavigationItemUpdated,
this._fuseNavigationService.onNavigationItemRemoved
)
.pipe(takeUntil(this._unsubscribeAll))
.subscribe(() => {
// Mark for check
this._changeDetectorRef.markForCheck();
});
}
}

View File

@ -0,0 +1,36 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';
import { MatRippleModule } from '@angular/material/core';
import { MatIconModule } from '@angular/material/icon';
import { TranslateModule } from '@ngx-translate/core';
import { FuseNavigationComponent } from './navigation.component';
import { FuseNavVerticalItemComponent } from './vertical/item/item.component';
import { FuseNavVerticalCollapsableComponent } from './vertical/collapsable/collapsable.component';
import { FuseNavVerticalGroupComponent } from './vertical/group/group.component';
import { FuseNavHorizontalItemComponent } from './horizontal/item/item.component';
import { FuseNavHorizontalCollapsableComponent } from './horizontal/collapsable/collapsable.component';
@NgModule({
imports: [
CommonModule,
RouterModule,
MatIconModule,
MatRippleModule,
TranslateModule.forChild()
],
exports: [FuseNavigationComponent],
declarations: [
FuseNavigationComponent,
FuseNavVerticalGroupComponent,
FuseNavVerticalItemComponent,
FuseNavVerticalCollapsableComponent,
FuseNavHorizontalItemComponent,
FuseNavHorizontalCollapsableComponent
]
})
export class FuseNavigationModule {}

View File

@ -0,0 +1,392 @@
import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable, Subject } from 'rxjs';
import * as _ from 'lodash';
import { FuseNavigationItem } from 'src/@fuse/types';
@Injectable({
providedIn: 'root'
})
export class FuseNavigationService {
onItemCollapsed: Subject<any>;
onItemCollapseToggled: Subject<any>;
// Private
private _onNavigationChanged: BehaviorSubject<any>;
private _onNavigationRegistered: BehaviorSubject<any>;
private _onNavigationUnregistered: BehaviorSubject<any>;
private _onNavigationItemAdded: BehaviorSubject<any>;
private _onNavigationItemUpdated: BehaviorSubject<any>;
private _onNavigationItemRemoved: BehaviorSubject<any>;
private _currentNavigationKey: string;
private _registry: { [key: string]: any } = {};
/**
* Constructor
*/
constructor() {
// Set the defaults
this.onItemCollapsed = new Subject();
this.onItemCollapseToggled = new Subject();
// Set the private defaults
this._currentNavigationKey = null;
this._onNavigationChanged = new BehaviorSubject(null);
this._onNavigationRegistered = new BehaviorSubject(null);
this._onNavigationUnregistered = new BehaviorSubject(null);
this._onNavigationItemAdded = new BehaviorSubject(null);
this._onNavigationItemUpdated = new BehaviorSubject(null);
this._onNavigationItemRemoved = new BehaviorSubject(null);
}
// -----------------------------------------------------------------------------------------------------
// @ Accessors
// -----------------------------------------------------------------------------------------------------
/**
* Get onNavigationChanged
*
* @returns {Observable<any>}
*/
get onNavigationChanged(): Observable<any> {
return this._onNavigationChanged.asObservable();
}
/**
* Get onNavigationRegistered
*
* @returns {Observable<any>}
*/
get onNavigationRegistered(): Observable<any> {
return this._onNavigationRegistered.asObservable();
}
/**
* Get onNavigationUnregistered
*
* @returns {Observable<any>}
*/
get onNavigationUnregistered(): Observable<any> {
return this._onNavigationUnregistered.asObservable();
}
/**
* Get onNavigationItemAdded
*
* @returns {Observable<any>}
*/
get onNavigationItemAdded(): Observable<any> {
return this._onNavigationItemAdded.asObservable();
}
/**
* Get onNavigationItemUpdated
*
* @returns {Observable<any>}
*/
get onNavigationItemUpdated(): Observable<any> {
return this._onNavigationItemUpdated.asObservable();
}
/**
* Get onNavigationItemRemoved
*
* @returns {Observable<any>}
*/
get onNavigationItemRemoved(): Observable<any> {
return this._onNavigationItemRemoved.asObservable();
}
// -----------------------------------------------------------------------------------------------------
// @ Public methods
// -----------------------------------------------------------------------------------------------------
/**
* Register the given navigation
* with the given key
*
* @param key
* @param navigation
*/
register(key, navigation): void {
// Check if the key already being used
if (this._registry[key]) {
console.error(
`The navigation with the key '${key}' already exists. Either unregister it first or use a unique key.`
);
return;
}
// Add to the registry
this._registry[key] = navigation;
// Notify the subject
this._onNavigationRegistered.next([key, navigation]);
}
/**
* Unregister the navigation from the registry
* @param key
*/
unregister(key): void {
// Check if the navigation exists
if (!this._registry[key]) {
console.warn(
`The navigation with the key '${key}' doesn't exist in the registry.`
);
}
// Unregister the sidebar
delete this._registry[key];
// Notify the subject
this._onNavigationUnregistered.next(key);
}
/**
* Get navigation from registry by key
*
* @param key
* @returns {any}
*/
getNavigation(key): any {
// Check if the navigation exists
if (!this._registry[key]) {
console.warn(
`The navigation with the key '${key}' doesn't exist in the registry.`
);
return;
}
// Return the sidebar
return this._registry[key];
}
/**
* Get flattened navigation array
*
* @param navigation
* @param flatNavigation
* @returns {any[]}
*/
getFlatNavigation(
navigation,
flatNavigation: FuseNavigationItem[] = []
): any {
for (const item of navigation) {
if (item.type === 'item') {
flatNavigation.push(item);
continue;
}
if (item.type === 'collapsable' || item.type === 'group') {
if (item.children) {
this.getFlatNavigation(item.children, flatNavigation);
}
}
}
return flatNavigation;
}
/**
* Get the current navigation
*
* @returns {any}
*/
getCurrentNavigation(): any {
if (!this._currentNavigationKey) {
console.warn(`The current navigation is not set.`);
return;
}
return this.getNavigation(this._currentNavigationKey);
}
/**
* Set the navigation with the key
* as the current navigation
*
* @param key
*/
setCurrentNavigation(key): void {
// Check if the sidebar exists
if (!this._registry[key]) {
console.warn(
`The navigation with the key '${key}' doesn't exist in the registry.`
);
return;
}
// Set the current navigation key
this._currentNavigationKey = key;
// Notify the subject
this._onNavigationChanged.next(key);
}
/**
* Get navigation item by id from the
* current navigation
*
* @param id
* @param {any} navigation
* @returns {any | boolean}
*/
getNavigationItem(id, navigation = null): any | boolean {
if (!navigation) {
navigation = this.getCurrentNavigation();
}
for (const item of navigation) {
if (item.id === id) {
return item;
}
if (item.children) {
const childItem = this.getNavigationItem(id, item.children);
if (childItem) {
return childItem;
}
}
}
return false;
}
/**
* Get the parent of the navigation item
* with the id
*
* @param id
* @param {any} navigation
* @param parent
*/
getNavigationItemParent(id, navigation = null, parent = null): any {
if (!navigation) {
navigation = this.getCurrentNavigation();
parent = navigation;
}
for (const item of navigation) {
if (item.id === id) {
return parent;
}
if (item.children) {
const childItem = this.getNavigationItemParent(id, item.children, item);
if (childItem) {
return childItem;
}
}
}
return false;
}
/**
* Add a navigation item to the specified location
*
* @param item
* @param id
*/
addNavigationItem(item, id): void {
// Get the current navigation
const navigation: any[] = this.getCurrentNavigation();
// Add to the end of the navigation
if (id === 'end') {
navigation.push(item);
// Trigger the observable
this._onNavigationItemAdded.next(true);
return;
}
// Add to the start of the navigation
if (id === 'start') {
navigation.unshift(item);
// Trigger the observable
this._onNavigationItemAdded.next(true);
return;
}
// Add it to a specific location
const parent: any = this.getNavigationItem(id);
if (parent) {
// Check if parent has a children entry,
// and add it if it doesn't
if (!parent.children) {
parent.children = [];
}
// Add the item
parent.children.push(item);
}
// Trigger the observable
this._onNavigationItemAdded.next(true);
}
/**
* Update navigation item with the given id
*
* @param id
* @param properties
*/
updateNavigationItem(id, properties): void {
// Get the navigation item
const navigationItem = this.getNavigationItem(id);
// If there is no navigation with the give id, return
if (!navigationItem) {
return;
}
// Merge the navigation properties
_.merge(navigationItem, properties);
// Trigger the observable
this._onNavigationItemUpdated.next(true);
}
/**
* Remove navigation item with the given id
*
* @param id
*/
removeNavigationItem(id): void {
const item = this.getNavigationItem(id);
// Return, if there is not such an item
if (!item) {
return;
}
// Get the parent of the item
let parent = this.getNavigationItemParent(id);
// This check is required because of the first level
// of the navigation, since the first level is not
// inside the 'children' array
parent = parent.children || parent;
// Remove the item
parent.splice(parent.indexOf(item), 1);
// Trigger the observable
this._onNavigationItemRemoved.next(true);
}
}

View File

@ -0,0 +1,105 @@
<ng-container *ngIf="!item.hidden">
<!-- normal collapsable -->
<a
class="nav-link"
[ngClass]="item.classes"
*ngIf="!item.url && !item.function"
(click)="toggleOpen($event)"
>
<ng-container *ngTemplateOutlet="itemContent"></ng-container>
</a>
<!-- item.url -->
<a
class="nav-link"
[ngClass]="item.classes"
*ngIf="item.url && !item.externalUrl && !item.function"
(click)="toggleOpen($event)"
[routerLink]="[item.url]"
[routerLinkActive]="['active', 'accent']"
[routerLinkActiveOptions]="{ exact: item.exactMatch || false }"
[target]="item.openInNewTab ? '_blank' : '_self'"
>
<ng-container *ngTemplateOutlet="itemContent"></ng-container>
</a>
<!-- item.externalUrl -->
<a
class="nav-link"
[ngClass]="item.classes"
*ngIf="item.url && item.externalUrl && !item.function"
(click)="toggleOpen($event)"
[href]="item.url"
[target]="item.openInNewTab ? '_blank' : '_self'"
>
<ng-container *ngTemplateOutlet="itemContent"></ng-container>
</a>
<!-- item.function -->
<span
class="nav-link"
[ngClass]="item.classes"
*ngIf="!item.url && item.function"
(click)="toggleOpen($event); item.function()"
>
<ng-container *ngTemplateOutlet="itemContent"></ng-container>
</span>
<!-- item.url && item.function -->
<a
class="nav-link"
[ngClass]="item.classes"
*ngIf="item.url && !item.externalUrl && item.function"
(click)="toggleOpen($event); item.function()"
[routerLink]="[item.url]"
[routerLinkActive]="['active', 'accent']"
[routerLinkActiveOptions]="{ exact: item.exactMatch || false }"
>
<ng-container *ngTemplateOutlet="itemContent"></ng-container>
</a>
<!-- item.externalUrl && item.function -->
<a
class="nav-link"
[ngClass]="item.classes"
*ngIf="item.url && item.externalUrl && item.function"
(click)="toggleOpen($event); item.function()"
[href]="item.url"
[target]="item.openInNewTab ? '_blank' : '_self'"
>
<ng-container *ngTemplateOutlet="itemContent"></ng-container>
</a>
<ng-template #itemContent>
<mat-icon class="nav-link-icon" *ngIf="item.icon">{{ item.icon }}</mat-icon>
<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="collapsable-arrow">keyboard_arrow_right</mat-icon>
</ng-template>
<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>
<fuse-nav-vertical-collapsable
*ngIf="item.type == 'collapsable'"
[item]="item"
></fuse-nav-vertical-collapsable>
<fuse-nav-vertical-group
*ngIf="item.type == 'group'"
[item]="item"
></fuse-nav-vertical-group>
</ng-container>
</div>
</ng-container>

View File

@ -0,0 +1,35 @@
:host {
.folded:not(.unfolded) & {
.nav-link {
> span {
opacity: 0;
transition: opacity 200ms ease;
}
}
&.open {
.children {
display: none !important;
}
}
}
.nav-link {
.collapsable-arrow {
transition: transform 0.3s ease-in-out, opacity 0.25s ease-in-out 0.1s;
transform: rotate(0);
}
}
> .children {
overflow: hidden;
}
&.open {
> .nav-link {
.collapsable-arrow {
transform: rotate(90deg);
}
}
}
}

View File

@ -0,0 +1,240 @@
import {
ChangeDetectorRef,
Component,
HostBinding,
Input,
OnDestroy,
OnInit
} from '@angular/core';
import { NavigationEnd, Router } from '@angular/router';
import { merge, Subject } from 'rxjs';
import { filter, takeUntil } from 'rxjs/operators';
import { FuseNavigationItem } from 'src/@fuse/types';
import { fuseAnimations } from 'src/@fuse/animations';
import { FuseNavigationService } from 'src/@fuse/components/navigation/navigation.service';
@Component({
selector: 'fuse-nav-vertical-collapsable',
templateUrl: './collapsable.component.html',
styleUrls: ['./collapsable.component.scss'],
animations: fuseAnimations
})
export class FuseNavVerticalCollapsableComponent implements OnInit, OnDestroy {
@Input()
item: FuseNavigationItem;
@HostBinding('class')
classes = 'nav-collapsable nav-item';
@HostBinding('class.open')
public isOpen = false;
// Private
private _unsubscribeAll: Subject<any>;
/**
* Constructor
*
* @param {ChangeDetectorRef} _changeDetectorRef
* @param {FuseNavigationService} _fuseNavigationService
* @param {Router} _router
*/
constructor(
private _changeDetectorRef: ChangeDetectorRef,
private _fuseNavigationService: FuseNavigationService,
private _router: Router
) {
// Set the private defaults
this._unsubscribeAll = new Subject();
}
// -----------------------------------------------------------------------------------------------------
// @ Lifecycle hooks
// -----------------------------------------------------------------------------------------------------
/**
* On init
*/
ngOnInit(): void {
// Listen for router events
this._router.events
.pipe(
filter(event => event instanceof NavigationEnd),
takeUntil(this._unsubscribeAll)
)
.subscribe((event: NavigationEnd) => {
// Check if the url can be found in
// one of the children of this item
if (this.isUrlInChildren(this.item, event.urlAfterRedirects)) {
this.expand();
} else {
this.collapse();
}
});
// Listen for collapsing of any navigation item
this._fuseNavigationService.onItemCollapsed
.pipe(takeUntil(this._unsubscribeAll))
.subscribe(clickedItem => {
if (clickedItem && clickedItem.children) {
// Check if the clicked item is one
// of the children of this item
if (this.isChildrenOf(this.item, clickedItem)) {
return;
}
// Check if the url can be found in
// one of the children of this item
if (this.isUrlInChildren(this.item, this._router.url)) {
return;
}
// If the clicked item is not this item, collapse...
if (this.item !== clickedItem) {
this.collapse();
}
}
});
// 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();
}
// Subscribe to navigation item
merge(
this._fuseNavigationService.onNavigationItemAdded,
this._fuseNavigationService.onNavigationItemUpdated,
this._fuseNavigationService.onNavigationItemRemoved
)
.pipe(takeUntil(this._unsubscribeAll))
.subscribe(() => {
// Mark for check
this._changeDetectorRef.markForCheck();
});
}
/**
* On destroy
*/
ngOnDestroy(): void {
// Unsubscribe from all subscriptions
this._unsubscribeAll.next();
this._unsubscribeAll.complete();
}
// -----------------------------------------------------------------------------------------------------
// @ Public methods
// -----------------------------------------------------------------------------------------------------
/**
* Toggle collapse
*
* @param ev
*/
toggleOpen(ev): void {
ev.preventDefault();
this.isOpen = !this.isOpen;
// Navigation collapse toggled...
this._fuseNavigationService.onItemCollapsed.next(this.item);
this._fuseNavigationService.onItemCollapseToggled.next();
}
/**
* Expand the collapsable navigation
*/
expand(): void {
if (this.isOpen) {
return;
}
this.isOpen = true;
// Mark for check
this._changeDetectorRef.markForCheck();
this._fuseNavigationService.onItemCollapseToggled.next();
}
/**
* Collapse the collapsable navigation
*/
collapse(): void {
if (!this.isOpen) {
return;
}
this.isOpen = false;
// Mark for check
this._changeDetectorRef.markForCheck();
this._fuseNavigationService.onItemCollapseToggled.next();
}
/**
* Check if the given parent has the
* given item in one of its children
*
* @param parent
* @param item
* @returns {boolean}
*/
isChildrenOf(parent, item): boolean {
const children = parent.children;
if (!children) {
return false;
}
if (children.indexOf(item) > -1) {
return true;
}
for (const child of children) {
if (child.children) {
if (this.isChildrenOf(child, item)) {
return true;
}
}
}
return false;
}
/**
* Check if the given url can be found
* in one of the given parent's children
*
* @param parent
* @param url
* @returns {boolean}
*/
isUrlInChildren(parent, url): boolean {
const children = parent.children;
if (!children) {
return false;
}
for (const child of children) {
if (child.children) {
if (this.isUrlInChildren(child, url)) {
return true;
}
}
if (child.url === url || url.includes(child.url)) {
return true;
}
}
return false;
}
}

View File

@ -0,0 +1,22 @@
<ng-container *ngIf="!item.hidden">
<div class="group-title" [ngClass]="item.classes">
<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>
<fuse-nav-vertical-collapsable
*ngIf="item.type == 'collapsable'"
[item]="item"
></fuse-nav-vertical-collapsable>
<fuse-nav-vertical-item
*ngIf="item.type == 'item'"
[item]="item"
></fuse-nav-vertical-item>
</ng-container>
</div>
</ng-container>

View File

@ -0,0 +1,21 @@
:host {
.folded:not(.unfolded) & {
> .group-title {
align-items: center;
> span {
opacity: 0;
transition: opacity 200ms ease;
}
&:before {
content: '';
display: block;
position: absolute;
min-width: 1.6rem;
border-top: 2px solid;
opacity: 0.2;
}
}
}
}

View File

@ -0,0 +1,76 @@
import {
ChangeDetectorRef,
Component,
HostBinding,
Input,
OnDestroy,
OnInit
} from '@angular/core';
import { merge, Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { FuseNavigationItem } from 'src/@fuse/types';
import { FuseNavigationService } from 'src/@fuse/components/navigation/navigation.service';
@Component({
selector: 'fuse-nav-vertical-group',
templateUrl: './group.component.html',
styleUrls: ['./group.component.scss']
})
export class FuseNavVerticalGroupComponent implements OnInit, OnDestroy {
@HostBinding('class')
classes = 'nav-group nav-item';
@Input()
item: FuseNavigationItem;
// Private
private _unsubscribeAll: Subject<any>;
/**
* Constructor
*/
/**
*
* @param {ChangeDetectorRef} _changeDetectorRef
* @param {FuseNavigationService} _fuseNavigationService
*/
constructor(
private _changeDetectorRef: ChangeDetectorRef,
private _fuseNavigationService: FuseNavigationService
) {
// Set the private defaults
this._unsubscribeAll = new Subject();
}
// -----------------------------------------------------------------------------------------------------
// @ Lifecycle hooks
// -----------------------------------------------------------------------------------------------------
/**
* On init
*/
ngOnInit(): void {
// Subscribe to navigation item
merge(
this._fuseNavigationService.onNavigationItemAdded,
this._fuseNavigationService.onNavigationItemUpdated,
this._fuseNavigationService.onNavigationItemRemoved
)
.pipe(takeUntil(this._unsubscribeAll))
.subscribe(() => {
// Mark for check
this._changeDetectorRef.markForCheck();
});
}
/**
* On destroy
*/
ngOnDestroy(): void {
// Unsubscribe from all subscriptions
this._unsubscribeAll.next();
this._unsubscribeAll.complete();
}
}

View File

@ -0,0 +1,76 @@
<ng-container *ngIf="!item.hidden">
<!-- item.url -->
<a
class="nav-link"
[ngClass]="item.classes"
*ngIf="item.url && !item.externalUrl && !item.function"
[routerLink]="[item.url]"
[routerLinkActive]="['active', 'accent']"
[routerLinkActiveOptions]="{ exact: item.exactMatch || false }"
[target]="item.openInNewTab ? '_blank' : '_self'"
>
<ng-container *ngTemplateOutlet="itemContent"></ng-container>
</a>
<!-- item.externalUrl -->
<a
class="nav-link"
[ngClass]="item.classes"
*ngIf="item.url && item.externalUrl && !item.function"
[href]="item.url"
[target]="item.openInNewTab ? '_blank' : '_self'"
>
<ng-container *ngTemplateOutlet="itemContent"></ng-container>
</a>
<!-- item.function -->
<span
class="nav-link"
[ngClass]="item.classes"
*ngIf="!item.url && item.function"
(click)="item.function()"
>
<ng-container *ngTemplateOutlet="itemContent"></ng-container>
</span>
<!-- item.url && item.function -->
<a
class="nav-link"
[ngClass]="item.classes"
*ngIf="item.url && !item.externalUrl && item.function"
(click)="item.function()"
[routerLink]="[item.url]"
[routerLinkActive]="['active', 'accent']"
[routerLinkActiveOptions]="{ exact: item.exactMatch || false }"
[target]="item.openInNewTab ? '_blank' : '_self'"
>
<ng-container *ngTemplateOutlet="itemContent"></ng-container>
</a>
<!-- item.externalUrl && item.function -->
<a
class="nav-link"
[ngClass]="item.classes"
*ngIf="item.url && item.externalUrl && item.function"
(click)="item.function()"
[href]="item.url"
[target]="item.openInNewTab ? '_blank' : '_self'"
>
<ng-container *ngTemplateOutlet="itemContent"></ng-container>
</a>
<ng-template #itemContent>
<mat-icon class="nav-link-icon" *ngIf="item.icon">{{ item.icon }}</mat-icon>
<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>
</ng-template>
</ng-container>

View File

@ -0,0 +1,11 @@
:host {
.folded:not(.unfolded) & {
.nav-link {
> .nav-link-title,
> .nav-link-badge {
opacity: 0;
transition: opacity 200ms ease;
}
}
}
}

View File

@ -0,0 +1,76 @@
import {
ChangeDetectorRef,
Component,
HostBinding,
Input,
OnDestroy,
OnInit
} from '@angular/core';
import { merge, Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { FuseNavigationItem } from 'src/@fuse/types';
import { FuseNavigationService } from 'src/@fuse/components/navigation/navigation.service';
@Component({
selector: 'fuse-nav-vertical-item',
templateUrl: './item.component.html',
styleUrls: ['./item.component.scss']
})
export class FuseNavVerticalItemComponent implements OnInit, OnDestroy {
@HostBinding('class')
classes = 'nav-item';
@Input()
item: FuseNavigationItem;
// Private
private _unsubscribeAll: Subject<any>;
/**
* Constructor
*/
/**
*
* @param {ChangeDetectorRef} _changeDetectorRef
* @param {FuseNavigationService} _fuseNavigationService
*/
constructor(
private _changeDetectorRef: ChangeDetectorRef,
private _fuseNavigationService: FuseNavigationService
) {
// Set the private defaults
this._unsubscribeAll = new Subject();
}
// -----------------------------------------------------------------------------------------------------
// @ Lifecycle hooks
// -----------------------------------------------------------------------------------------------------
/**
* On init
*/
ngOnInit(): void {
// Subscribe to navigation item
merge(
this._fuseNavigationService.onNavigationItemAdded,
this._fuseNavigationService.onNavigationItemUpdated,
this._fuseNavigationService.onNavigationItemRemoved
)
.pipe(takeUntil(this._unsubscribeAll))
.subscribe(() => {
// Mark for check
this._changeDetectorRef.markForCheck();
});
}
/**
* On destroy
*/
ngOnDestroy(): void {
// Unsubscribe from all subscriptions
this._unsubscribeAll.next();
this._unsubscribeAll.complete();
}
}

View File

@ -0,0 +1,8 @@
<ng-container *ngIf="visible">
<mat-progress-bar
color="accent"
[bufferValue]="bufferValue"
[mode]="mode"
[value]="value"
></mat-progress-bar>
</ng-container>

View File

@ -0,0 +1,16 @@
@import 'src/@fuse/scss/fuse';
fuse-progress-bar {
position: absolute;
top: 0;
left: 0;
right: 0;
width: 100%;
z-index: 99998;
mat-progress-bar {
.mat-progress-bar-buffer {
background-color: #c5c6cb !important;
}
}
}

View File

@ -0,0 +1,85 @@
import { Component, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { FuseProgressBarService } from 'src/@fuse/components/progress-bar/progress-bar.service';
@Component({
selector: 'fuse-progress-bar',
templateUrl: './progress-bar.component.html',
styleUrls: ['./progress-bar.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class FuseProgressBarComponent implements OnInit, OnDestroy {
bufferValue: number;
mode: 'determinate' | 'indeterminate' | 'buffer' | 'query';
value: number;
visible: boolean;
// Private
private _unsubscribeAll: Subject<any>;
/**
* Constructor
*
* @param {FuseProgressBarService} _fuseProgressBarService
*/
constructor(private _fuseProgressBarService: FuseProgressBarService) {
// Set the defaults
// Set the private defaults
this._unsubscribeAll = new Subject();
}
// -----------------------------------------------------------------------------------------------------
// @ Lifecycle hooks
// -----------------------------------------------------------------------------------------------------
/**
* On init
*/
ngOnInit(): void {
// Subscribe to the progress bar service properties
// Buffer value
this._fuseProgressBarService.bufferValue
.pipe(takeUntil(this._unsubscribeAll))
.subscribe(bufferValue => {
this.bufferValue = bufferValue;
});
// Mode
this._fuseProgressBarService.mode
.pipe(takeUntil(this._unsubscribeAll))
.subscribe(mode => {
this.mode = mode;
});
// Value
this._fuseProgressBarService.value
.pipe(takeUntil(this._unsubscribeAll))
.subscribe(value => {
this.value = value;
});
// Visible
this._fuseProgressBarService.visible
.pipe(takeUntil(this._unsubscribeAll))
.subscribe(visible => {
this.visible = visible;
});
}
/**
* On destroy
*/
ngOnDestroy(): void {
// Unsubscribe from all subscriptions
this._unsubscribeAll.next();
this._unsubscribeAll.complete();
}
// -----------------------------------------------------------------------------------------------------
// @ Public methods
// -----------------------------------------------------------------------------------------------------
}

View File

@ -0,0 +1,23 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { MatProgressBarModule } from '@angular/material/progress-bar';
import { FuseProgressBarComponent } from './progress-bar.component';
@NgModule({
declarations: [FuseProgressBarComponent],
imports: [
CommonModule,
RouterModule,
MatButtonModule,
MatIconModule,
MatProgressBarModule
],
exports: [FuseProgressBarComponent]
})
export class FuseProgressBarModule {}

View File

@ -0,0 +1,130 @@
import { Injectable } from '@angular/core';
import {
NavigationCancel,
NavigationEnd,
NavigationError,
NavigationStart,
Router
} from '@angular/router';
import { BehaviorSubject, Observable } from 'rxjs';
import { filter } from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class FuseProgressBarService {
// Private
private _bufferValue: BehaviorSubject<number>;
private _mode: BehaviorSubject<string>;
private _value: BehaviorSubject<number>;
private _visible: BehaviorSubject<boolean>;
/**
* Constructor
*
* @param {Router} _router
*/
constructor(private _router: Router) {
// Initialize the service
this._init();
}
// -----------------------------------------------------------------------------------------------------
// @ Accessors
// -----------------------------------------------------------------------------------------------------
/**
* Buffer value
*/
get bufferValue(): Observable<any> {
return this._bufferValue.asObservable();
}
setBufferValue(value: number): void {
this._bufferValue.next(value);
}
/**
* Mode
*/
get mode(): Observable<any> {
return this._mode.asObservable();
}
setMode(value: 'determinate' | 'indeterminate' | 'buffer' | 'query'): void {
this._mode.next(value);
}
/**
* Value
*/
get value(): Observable<any> {
return this._value.asObservable();
}
setValue(value: number): void {
this._value.next(value);
}
/**
* Visible
*/
get visible(): Observable<any> {
return this._visible.asObservable();
}
// -----------------------------------------------------------------------------------------------------
// @ Private methods
// -----------------------------------------------------------------------------------------------------
/**
* Initialize
*
* @private
*/
private _init(): void {
// Initialize the behavior subjects
this._bufferValue = new BehaviorSubject(0);
this._mode = new BehaviorSubject('indeterminate');
this._value = new BehaviorSubject(0);
this._visible = new BehaviorSubject(false);
// Subscribe to the router events to show/hide the loading bar
this._router.events
.pipe(filter(event => event instanceof NavigationStart))
.subscribe(() => {
this.show();
});
this._router.events
.pipe(
filter(
event =>
event instanceof NavigationEnd ||
event instanceof NavigationError ||
event instanceof NavigationCancel
)
)
.subscribe(() => {
this.hide();
});
}
// -----------------------------------------------------------------------------------------------------
// @ Public methods
// -----------------------------------------------------------------------------------------------------
/**
* Show the progress bar
*/
show(): void {
this._visible.next(true);
}
/**
* Hide the progress bar
*/
hide(): void {
this._visible.next(false);
}
}

View File

@ -0,0 +1,33 @@
<div class="fuse-search-bar" [ngClass]="{ expanded: !collapsed }">
<div class="fuse-search-bar-content">
<label for="fuse-search-bar-input">
<button
mat-icon-button
class="fuse-search-bar-expander"
aria-label="Expand Search Bar"
(click)="expand()"
*ngIf="collapsed"
>
<mat-icon class="s-24 secondary-text">search</mat-icon>
</button>
</label>
<input
id="fuse-search-bar-input"
class="ml-24"
type="text"
placeholder="Search"
(input)="search($event)"
fxFlex
/>
<button
mat-icon-button
class="fuse-search-bar-collapser"
(click)="collapse()"
aria-label="Collapse Search Bar"
>
<mat-icon class="s-24 secondary-text">close</mat-icon>
</button>
</div>
</div>

View File

@ -0,0 +1,88 @@
@import 'src/@fuse/scss/fuse';
:host {
.fuse-search-bar {
display: flex;
flex: 0 1 auto;
min-width: 64px;
height: 64px;
font-size: 13px;
@include media-breakpoint('xs') {
height: 56px;
}
.fuse-search-bar-content {
display: flex;
flex: 1 1 auto;
align-items: center;
justify-content: flex-start;
.fuse-search-bar-expander,
.fuse-search-bar-collapser {
cursor: pointer;
padding: 0 20px;
margin: 0;
width: 64px !important;
height: 64px !important;
line-height: 64px !important;
@include media-breakpoint('xs') {
height: 56px !important;
line-height: 56px !important;
}
}
.fuse-search-bar-loader {
width: 64px !important;
height: 64px !important;
line-height: 64px !important;
@include media-breakpoint('xs') {
height: 56px !important;
line-height: 56px !important;
}
}
.fuse-search-bar-collapser {
display: none;
}
#fuse-search-bar-input {
display: none;
flex: 1 0 auto;
min-height: 64px;
font-size: 16px;
background-color: transparent;
color: currentColor;
}
}
&.expanded {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 10;
.fuse-search-bar-content {
#fuse-search-bar-input {
display: flex;
}
.fuse-search-bar-collapser {
display: flex;
}
}
}
}
body {
&.fuse-search-bar-expanded {
#toolbar {
z-index: 999 !important;
}
}
}
}

View File

@ -0,0 +1,93 @@
import {
Component,
EventEmitter,
OnDestroy,
OnInit,
Output
} from '@angular/core';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { FuseConfigService } from 'src/@fuse/services/config.service';
@Component({
selector: 'fuse-search-bar',
templateUrl: './search-bar.component.html',
styleUrls: ['./search-bar.component.scss']
})
export class FuseSearchBarComponent implements OnInit, OnDestroy {
collapsed: boolean;
fuseConfig: any;
@Output()
input: EventEmitter<any>;
// Private
private _unsubscribeAll: Subject<any>;
/**
* Constructor
*
* @param {FuseConfigService} _fuseConfigService
*/
constructor(private _fuseConfigService: FuseConfigService) {
// Set the defaults
this.input = new EventEmitter();
this.collapsed = true;
// Set the private defaults
this._unsubscribeAll = new Subject();
}
// -----------------------------------------------------------------------------------------------------
// @ Lifecycle hooks
// -----------------------------------------------------------------------------------------------------
/**
* On init
*/
ngOnInit(): void {
// Subscribe to config changes
this._fuseConfigService.config
.pipe(takeUntil(this._unsubscribeAll))
.subscribe(config => {
this.fuseConfig = config;
});
}
/**
* On destroy
*/
ngOnDestroy(): void {
// Unsubscribe from all subscriptions
this._unsubscribeAll.next();
this._unsubscribeAll.complete();
}
// -----------------------------------------------------------------------------------------------------
// @ Public methods
// -----------------------------------------------------------------------------------------------------
/**
* Collapse
*/
collapse(): void {
this.collapsed = true;
}
/**
* Expand
*/
expand(): void {
this.collapsed = false;
}
/**
* Search
*
* @param event
*/
search(event): void {
this.input.emit(event.target.value);
}
}

View File

@ -0,0 +1,15 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { FuseSearchBarComponent } from './search-bar.component';
@NgModule({
declarations: [FuseSearchBarComponent],
imports: [CommonModule, RouterModule, MatButtonModule, MatIconModule],
exports: [FuseSearchBarComponent]
})
export class FuseSearchBarModule {}

View File

@ -0,0 +1,9 @@
@mixin fuse-search-bar-theme($theme) {
$background: map-get($theme, background);
.fuse-search-bar {
&.expanded {
background-color: map-get($background, background);
}
}
}

View File

@ -0,0 +1,113 @@
<div id="fuse-shortcuts" #shortcuts>
<div class="shortcuts-mobile-toggle" *ngIf="!mobileShortcutsPanelActive" fxLayout="row" fxLayoutAlign="start center"
fxHide fxShow.lt-md>
<button mat-icon-button (click)="showMobileShortcutsPanel()">
<mat-icon class="amber-600-fg">star</mat-icon>
</button>
</div>
<div class="shortcuts" fxLayout="row" fxHide fxShow.gt-sm>
<div fxLayout="row" fxLayoutAlign="space-between center" fxFlex="0 1 auto">
<div fxLayout="row" fxLayoutAlign="start center">
<div class="w-40 h-40 p-4" fxLayout="row" fxLayoutAlign="center center"
*ngFor="let shortcutItem of shortcutItems">
<a mat-icon-button matTooltip="{{shortcutItem.title}}" [routerLink]="shortcutItem.url">
<mat-icon class="secondary-text" *ngIf="shortcutItem.icon">{{shortcutItem.icon}}</mat-icon>
<span *ngIf="!shortcutItem.icon" class="h2 secondary-text text-bold">
{{shortcutItem.title.substr(0, 1).toUpperCase()}}
</span>
</a>
</div>
<button mat-icon-button [matMenuTriggerFor]="addMenu" matTooltip="Click to add/remove shortcut"
(menuOpened)="onMenuOpen()">
<mat-icon class="amber-600-fg">star</mat-icon>
</button>
</div>
<div class="shortcuts-mobile-close" fxLayout="row" fxLayoutAlign="start center" fxHide fxShow.lt-md>
<button mat-icon-button (click)="hideMobileShortcutsPanel()">
<mat-icon>close</mat-icon>
</button>
</div>
</div>
<mat-menu #addMenu="matMenu" class="w-240">
<mat-form-field class="px-16 w-100-p" (click)="$event.stopPropagation()" floatLabel="never">
<input #searchInput matInput placeholder="Search for an app or a page" (input)="search($event)">
</mat-form-field>
<mat-divider></mat-divider>
<mat-nav-list *ngIf="!searching" style="max-height: 312px; overflow: auto" fusePerfectScrollbar>
<mat-list-item *ngFor="let shortcutItem of shortcutItems"
(click)="toggleShortcut($event, shortcutItem)">
<div class="w-100-p" fxLayout="row" fxLayoutAlign="start center">
<mat-icon mat-list-icon class="mr-8 secondary-text" *ngIf="shortcutItem.icon">
{{shortcutItem.icon}}
</mat-icon>
<span class="h2 w-32 h-32 p-4 mr-8 secondary-text text-bold" fxLayout="row"
fxLayoutAlign="center center" *ngIf="!shortcutItem.icon">
{{shortcutItem.title.substr(0, 1).toUpperCase()}}
</span>
<p matLine fxFlex>{{shortcutItem.title}}</p>
<mat-icon class="ml-8 amber-fg">star</mat-icon>
</div>
</mat-list-item>
<mat-list-item *ngIf="shortcutItems.length === 0">
<p>
<small>No shortcuts yet!</small>
</p>
</mat-list-item>
</mat-nav-list>
<mat-nav-list *ngIf="searching" style="max-height: 312px; overflow: auto" fusePerfectScrollbar>
<mat-list-item *ngFor="let navigationItem of filteredNavigationItems"
(click)="toggleShortcut($event, navigationItem)">
<div class="w-100-p" fxLayout="row" fxLayoutAlign="start center">
<mat-icon mat-list-icon class="mr-8 secondary-text" *ngIf="navigationItem.icon">
{{navigationItem.icon}}
</mat-icon>
<span class="h2 w-32 h-32 p-4 mr-8 secondary-text text-bold" fxLayout="row"
fxLayoutAlign="center center" *ngIf="!navigationItem.icon">
{{navigationItem.title.substr(0, 1).toUpperCase()}}
</span>
<p matLine fxFlex>{{navigationItem.title}}</p>
<mat-icon class="ml-8 amber-fg" *ngIf="isInShortcuts(navigationItem)">star</mat-icon>
</div>
</mat-list-item>
</mat-nav-list>
</mat-menu>
</div>
</div>

View File

@ -0,0 +1,30 @@
@import 'src/@fuse/scss/fuse';
:host {
@include media-breakpoint('lt-md') {
#fuse-shortcuts {
&.show-mobile-panel {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 99;
padding: 0 8px;
.shortcuts {
display: flex !important;
flex: 1;
height: 100%;
> div {
flex: 1 1 auto !important;
}
}
}
}
}
}

View File

@ -0,0 +1,239 @@
import {
AfterViewInit,
Component,
ElementRef,
Input,
OnDestroy,
OnInit,
Renderer2,
ViewChild
} from '@angular/core';
import { MediaObserver } from '@angular/flex-layout';
import { CookieService } from 'ngx-cookie-service';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { FuseMatchMediaService } from 'src/@fuse/services/match-media.service';
import { FuseNavigationService } from 'src/@fuse/components/navigation/navigation.service';
@Component({
selector: 'fuse-shortcuts',
templateUrl: './shortcuts.component.html',
styleUrls: ['./shortcuts.component.scss']
})
export class FuseShortcutsComponent
implements OnInit, AfterViewInit, OnDestroy {
shortcutItems: any[];
navigationItems: any[];
filteredNavigationItems: any[];
searching: boolean;
mobileShortcutsPanelActive: boolean;
@Input()
navigation: any;
@ViewChild('searchInput', { static: false })
searchInputField;
@ViewChild('shortcuts', { static: false })
shortcutsEl: ElementRef;
// Private
private _unsubscribeAll: Subject<any>;
/**
* Constructor
*
* @param {CookieService} _cookieService
* @param {FuseMatchMediaService} _fuseMatchMediaService
* @param {FuseNavigationService} _fuseNavigationService
* @param {MediaObserver} _mediaObserver
* @param {Renderer2} _renderer
*/
constructor(
private _cookieService: CookieService,
private _fuseMatchMediaService: FuseMatchMediaService,
private _fuseNavigationService: FuseNavigationService,
private _mediaObserver: MediaObserver,
private _renderer: Renderer2
) {
// Set the defaults
this.shortcutItems = [];
this.searching = false;
this.mobileShortcutsPanelActive = false;
// Set the private defaults
this._unsubscribeAll = new Subject();
}
// -----------------------------------------------------------------------------------------------------
// @ Lifecycle hooks
// -----------------------------------------------------------------------------------------------------
/**
* On init
*/
ngOnInit(): void {
// Get the navigation items and flatten them
this.filteredNavigationItems = this.navigationItems = this._fuseNavigationService.getFlatNavigation(
this.navigation
);
if (this._cookieService.check('FUSE2.shortcuts')) {
this.shortcutItems = JSON.parse(
this._cookieService.get('FUSE2.shortcuts')
);
} else {
// User's shortcut items
this.shortcutItems = [
{
title: 'Calendar',
type: 'item',
icon: 'today',
url: '/apps/calendar'
},
{
title: 'Mail',
type: 'item',
icon: 'email',
url: '/apps/mail'
},
{
title: 'Contacts',
type: 'item',
icon: 'account_box',
url: '/apps/contacts'
},
{
title: 'To-Do',
type: 'item',
icon: 'check_box',
url: '/apps/todo'
}
];
}
}
ngAfterViewInit(): void {
// Subscribe to media changes
this._fuseMatchMediaService.onMediaChange
.pipe(takeUntil(this._unsubscribeAll))
.subscribe(() => {
if (this._mediaObserver.isActive('gt-sm')) {
this.hideMobileShortcutsPanel();
}
});
}
/**
* On destroy
*/
ngOnDestroy(): void {
// Unsubscribe from all subscriptions
this._unsubscribeAll.next();
this._unsubscribeAll.complete();
}
// -----------------------------------------------------------------------------------------------------
// @ Public methods
// -----------------------------------------------------------------------------------------------------
/**
* Search
*
* @param event
*/
search(event): void {
const value = event.target.value.toLowerCase();
if (value === '') {
this.searching = false;
this.filteredNavigationItems = this.navigationItems;
return;
}
this.searching = true;
this.filteredNavigationItems = this.navigationItems.filter(
navigationItem => {
return navigationItem.title.toLowerCase().includes(value);
}
);
}
/**
* Toggle shortcut
*
* @param event
* @param itemToToggle
*/
toggleShortcut(event, itemToToggle): void {
event.stopPropagation();
for (let i = 0; i < this.shortcutItems.length; i++) {
if (this.shortcutItems[i].url === itemToToggle.url) {
this.shortcutItems.splice(i, 1);
// Save to the cookies
this._cookieService.set(
'FUSE2.shortcuts',
JSON.stringify(this.shortcutItems)
);
return;
}
}
this.shortcutItems.push(itemToToggle);
// Save to the cookies
this._cookieService.set(
'FUSE2.shortcuts',
JSON.stringify(this.shortcutItems)
);
}
/**
* Is in shortcuts?
*
* @param navigationItem
* @returns {any}
*/
isInShortcuts(navigationItem): any {
return this.shortcutItems.find(item => {
return item.url === navigationItem.url;
});
}
/**
* On menu open
*/
onMenuOpen(): void {
setTimeout(() => {
this.searchInputField.nativeElement.focus();
});
}
/**
* Show mobile shortcuts
*/
showMobileShortcutsPanel(): void {
this.mobileShortcutsPanelActive = true;
this._renderer.addClass(
this.shortcutsEl.nativeElement,
'show-mobile-panel'
);
}
/**
* Hide mobile shortcuts
*/
hideMobileShortcutsPanel(): void {
this.mobileShortcutsPanelActive = false;
this._renderer.removeClass(
this.shortcutsEl.nativeElement,
'show-mobile-panel'
);
}
}

View File

@ -0,0 +1,37 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';
import { FlexLayoutModule } from '@angular/flex-layout';
import { MatButtonModule } from '@angular/material/button';
import { MatDividerModule } from '@angular/material/divider';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatListModule } from '@angular/material/list';
import { MatMenuModule } from '@angular/material/menu';
import { MatTooltipModule } from '@angular/material/tooltip';
import { CookieService } from 'ngx-cookie-service';
import { FuseShortcutsComponent } from './shortcuts.component';
@NgModule({
declarations: [FuseShortcutsComponent],
imports: [
CommonModule,
RouterModule,
FlexLayoutModule,
MatButtonModule,
MatDividerModule,
MatFormFieldModule,
MatIconModule,
MatInputModule,
MatMenuModule,
MatListModule,
MatTooltipModule
],
exports: [FuseShortcutsComponent],
providers: [CookieService]
})
export class FuseShortcutsModule {}

View File

@ -0,0 +1,9 @@
@mixin fuse-shortcuts-theme($theme) {
$background: map-get($theme, background);
#fuse-shortcuts {
&.show-mobile-panel {
background-color: map-get($background, background);
}
}
}

View File

@ -0,0 +1 @@
<ng-content></ng-content>

View File

@ -0,0 +1,65 @@
@import 'src/@fuse/scss/fuse';
fuse-sidebar {
display: flex;
flex-direction: column;
flex: 1 0 auto;
position: absolute;
top: 0;
bottom: 0;
overflow-x: hidden;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
width: 280px;
min-width: 280px;
max-width: 280px;
z-index: 1000;
box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.35);
@include media-breakpoint('xs') {
min-width: 0 !important;
max-width: 80vw !important;
width: 80vw !important;
}
&.left-positioned {
left: 0;
transform: translateX(-100%);
}
&.right-positioned {
right: 0;
transform: translateX(100%);
}
&.open {
transform: translateX(0);
}
&.locked-open {
position: relative !important;
transform: translateX(0) !important;
}
&.folded {
position: absolute !important;
top: 0;
bottom: 0;
}
&.animations-enabled {
transition-property: transform, width, min-width, max-width;
transition-duration: 150ms;
transition-timing-function: ease-in-out;
}
}
.fuse-sidebar-overlay {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
z-index: 999;
opacity: 0;
}

View File

@ -0,0 +1,803 @@
import {
ChangeDetectorRef,
Component,
ElementRef,
EventEmitter,
HostBinding,
HostListener,
Input,
OnDestroy,
OnInit,
Output,
Renderer2,
ViewEncapsulation
} from '@angular/core';
import {
animate,
AnimationBuilder,
AnimationPlayer,
style
} from '@angular/animations';
import { MediaObserver } from '@angular/flex-layout';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { FuseSidebarService } from './sidebar.service';
import { FuseMatchMediaService } from 'src/@fuse/services/match-media.service';
import { FuseConfigService } from 'src/@fuse/services/config.service';
@Component({
selector: 'fuse-sidebar',
templateUrl: './sidebar.component.html',
styleUrls: ['./sidebar.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class FuseSidebarComponent implements OnInit, OnDestroy {
// Name
@Input()
name: string;
// Key
@Input()
key: string;
// Position
@Input()
position: 'left' | 'right';
// Open
@HostBinding('class.open')
opened: boolean;
// Locked Open
@Input()
lockedOpen: string;
// isLockedOpen
@HostBinding('class.locked-open')
isLockedOpen: boolean;
// Folded width
@Input()
foldedWidth: number;
// Folded auto trigger on hover
@Input()
foldedAutoTriggerOnHover: boolean;
// Folded unfolded
@HostBinding('class.unfolded')
unfolded: boolean;
// Invisible overlay
@Input()
invisibleOverlay: boolean;
// Folded changed
@Output()
foldedChanged: EventEmitter<boolean>;
// Opened changed
@Output()
openedChanged: EventEmitter<boolean>;
// Private
private _folded: boolean;
private _fuseConfig: any;
private _wasActive: boolean;
private _wasFolded: boolean;
private _backdrop: HTMLElement | null = null;
private _player: AnimationPlayer;
private _unsubscribeAll: Subject<any>;
@HostBinding('class.animations-enabled')
private _animationsEnabled: boolean;
/**
* Constructor
*
* @param {AnimationBuilder} _animationBuilder
* @param {ChangeDetectorRef} _changeDetectorRef
* @param {ElementRef} _elementRef
* @param {FuseConfigService} _fuseConfigService
* @param {FuseMatchMediaService} _fuseMatchMediaService
* @param {FuseSidebarService} _fuseSidebarService
* @param {MediaObserver} _mediaObserver
* @param {Renderer2} _renderer
*/
constructor(
private _animationBuilder: AnimationBuilder,
private _changeDetectorRef: ChangeDetectorRef,
private _elementRef: ElementRef,
private _fuseConfigService: FuseConfigService,
private _fuseMatchMediaService: FuseMatchMediaService,
private _fuseSidebarService: FuseSidebarService,
private _mediaObserver: MediaObserver,
private _renderer: Renderer2
) {
// Set the defaults
this.foldedAutoTriggerOnHover = true;
this.foldedWidth = 64;
this.foldedChanged = new EventEmitter();
this.openedChanged = new EventEmitter();
this.opened = false;
this.position = 'left';
this.invisibleOverlay = false;
// Set the private defaults
this._animationsEnabled = false;
this._folded = false;
this._unsubscribeAll = new Subject();
}
// -----------------------------------------------------------------------------------------------------
// @ Accessors
// -----------------------------------------------------------------------------------------------------
/**
* Folded
*
* @param {boolean} value
*/
@Input()
set folded(value: boolean) {
// Set the folded
this._folded = value;
// Return if the sidebar is closed
if (!this.opened) {
return;
}
// Programmatically add/remove padding to the element
// that comes after or before based on the position
let sibling, styleRule;
const styleValue = this.foldedWidth + 'px';
// Get the sibling and set the style rule
if (this.position === 'left') {
sibling = this._elementRef.nativeElement.nextElementSibling;
styleRule = 'padding-left';
} else {
sibling = this._elementRef.nativeElement.previousElementSibling;
styleRule = 'padding-right';
}
// If there is no sibling, return...
if (!sibling) {
return;
}
// If folded...
if (value) {
// Fold the sidebar
this.fold();
// Set the folded width
this._renderer.setStyle(
this._elementRef.nativeElement,
'width',
styleValue
);
this._renderer.setStyle(
this._elementRef.nativeElement,
'min-width',
styleValue
);
this._renderer.setStyle(
this._elementRef.nativeElement,
'max-width',
styleValue
);
// Set the style and class
this._renderer.setStyle(sibling, styleRule, styleValue);
this._renderer.addClass(this._elementRef.nativeElement, 'folded');
}
// If unfolded...
else {
// Unfold the sidebar
this.unfold();
// Remove the folded width
this._renderer.removeStyle(this._elementRef.nativeElement, 'width');
this._renderer.removeStyle(this._elementRef.nativeElement, 'min-width');
this._renderer.removeStyle(this._elementRef.nativeElement, 'max-width');
// Remove the style and class
this._renderer.removeStyle(sibling, styleRule);
this._renderer.removeClass(this._elementRef.nativeElement, 'folded');
}
// Emit the 'foldedChanged' event
this.foldedChanged.emit(this.folded);
}
get folded(): boolean {
return this._folded;
}
// -----------------------------------------------------------------------------------------------------
// @ Lifecycle hooks
// -----------------------------------------------------------------------------------------------------
/**
* On init
*/
ngOnInit(): void {
// Subscribe to config changes
this._fuseConfigService.config
.pipe(takeUntil(this._unsubscribeAll))
.subscribe(config => {
this._fuseConfig = config;
});
// Register the sidebar
this._fuseSidebarService.register(this.name, this);
// Setup visibility
this._setupVisibility();
// Setup position
this._setupPosition();
// Setup lockedOpen
this._setupLockedOpen();
// Setup folded
this._setupFolded();
}
/**
* On destroy
*/
ngOnDestroy(): void {
// If the sidebar is folded, unfold it to revert modifications
if (this.folded) {
this.unfold();
}
// Unregister the sidebar
this._fuseSidebarService.unregister(this.name);
// Unsubscribe from all subscriptions
this._unsubscribeAll.next();
this._unsubscribeAll.complete();
}
// -----------------------------------------------------------------------------------------------------
// @ Private methods
// -----------------------------------------------------------------------------------------------------
/**
* Setup the visibility of the sidebar
*
* @private
*/
private _setupVisibility(): void {
// Remove the existing box-shadow
this._renderer.setStyle(
this._elementRef.nativeElement,
'box-shadow',
'none'
);
// Make the sidebar invisible
this._renderer.setStyle(
this._elementRef.nativeElement,
'visibility',
'hidden'
);
}
/**
* Setup the sidebar position
*
* @private
*/
private _setupPosition(): void {
// Add the correct class name to the sidebar
// element depending on the position attribute
if (this.position === 'right') {
this._renderer.addClass(
this._elementRef.nativeElement,
'right-positioned'
);
} else {
this._renderer.addClass(
this._elementRef.nativeElement,
'left-positioned'
);
}
}
/**
* Setup the lockedOpen handler
*
* @private
*/
private _setupLockedOpen(): void {
// Return if the lockedOpen wasn't set
if (!this.lockedOpen) {
// Return
return;
}
// Set the wasActive for the first time
this._wasActive = false;
// Set the wasFolded
this._wasFolded = this.folded;
// Show the sidebar
this._showSidebar();
// Act on every media change
this._fuseMatchMediaService.onMediaChange
.pipe(takeUntil(this._unsubscribeAll))
.subscribe(() => {
// Get the active status
const isActive = this._mediaObserver.isActive(this.lockedOpen);
// If the both status are the same, don't act
if (this._wasActive === isActive) {
return;
}
// Activate the lockedOpen
if (isActive) {
// Set the lockedOpen status
this.isLockedOpen = true;
// Show the sidebar
this._showSidebar();
// Force the the opened status to true
this.opened = true;
// Emit the 'openedChanged' event
this.openedChanged.emit(this.opened);
// If the sidebar was folded, forcefully fold it again
if (this._wasFolded) {
// Enable the animations
this._enableAnimations();
// Fold
this.folded = true;
// Mark for check
this._changeDetectorRef.markForCheck();
}
// Hide the backdrop if any exists
this._hideBackdrop();
}
// De-Activate the lockedOpen
else {
// Set the lockedOpen status
this.isLockedOpen = false;
// Unfold the sidebar in case if it was folded
this.unfold();
// Force the the opened status to close
this.opened = false;
// Emit the 'openedChanged' event
this.openedChanged.emit(this.opened);
// Hide the sidebar
this._hideSidebar();
}
// Store the new active status
this._wasActive = isActive;
});
}
/**
* Setup the initial folded status
*
* @private
*/
private _setupFolded(): void {
// Return, if sidebar is not folded
if (!this.folded) {
return;
}
// Return if the sidebar is closed
if (!this.opened) {
return;
}
// Programmatically add/remove padding to the element
// that comes after or before based on the position
let sibling, styleRule;
const styleValue = this.foldedWidth + 'px';
// Get the sibling and set the style rule
if (this.position === 'left') {
sibling = this._elementRef.nativeElement.nextElementSibling;
styleRule = 'padding-left';
} else {
sibling = this._elementRef.nativeElement.previousElementSibling;
styleRule = 'padding-right';
}
// If there is no sibling, return...
if (!sibling) {
return;
}
// Fold the sidebar
this.fold();
// Set the folded width
this._renderer.setStyle(
this._elementRef.nativeElement,
'width',
styleValue
);
this._renderer.setStyle(
this._elementRef.nativeElement,
'min-width',
styleValue
);
this._renderer.setStyle(
this._elementRef.nativeElement,
'max-width',
styleValue
);
// Set the style and class
this._renderer.setStyle(sibling, styleRule, styleValue);
this._renderer.addClass(this._elementRef.nativeElement, 'folded');
}
/**
* Show the backdrop
*
* @private
*/
private _showBackdrop(): void {
// Create the backdrop element
this._backdrop = this._renderer.createElement('div');
// Add a class to the backdrop element
this._backdrop.classList.add('fuse-sidebar-overlay');
// Add a class depending on the invisibleOverlay option
if (this.invisibleOverlay) {
this._backdrop.classList.add('fuse-sidebar-overlay-invisible');
}
// Append the backdrop to the parent of the sidebar
this._renderer.appendChild(
this._elementRef.nativeElement.parentElement,
this._backdrop
);
// Create the enter animation and attach it to the player
this._player = this._animationBuilder
.build([animate('300ms ease', style({ opacity: 1 }))])
.create(this._backdrop);
// Play the animation
this._player.play();
// Add an event listener to the overlay
this._backdrop.addEventListener('click', () => {
this.close();
});
// Mark for check
this._changeDetectorRef.markForCheck();
}
/**
* Hide the backdrop
*
* @private
*/
private _hideBackdrop(): void {
if (!this._backdrop) {
return;
}
// Create the leave animation and attach it to the player
this._player = this._animationBuilder
.build([animate('300ms ease', style({ opacity: 0 }))])
.create(this._backdrop);
// Play the animation
this._player.play();
// Once the animation is done...
this._player.onDone(() => {
// If the backdrop still exists...
if (this._backdrop) {
// Remove the backdrop
this._backdrop.parentNode.removeChild(this._backdrop);
this._backdrop = null;
}
});
// Mark for check
this._changeDetectorRef.markForCheck();
}
/**
* Change some properties of the sidebar
* and make it visible
*
* @private
*/
private _showSidebar(): void {
// Remove the box-shadow style
this._renderer.removeStyle(this._elementRef.nativeElement, 'box-shadow');
// Make the sidebar invisible
this._renderer.removeStyle(this._elementRef.nativeElement, 'visibility');
// Mark for check
this._changeDetectorRef.markForCheck();
}
/**
* Change some properties of the sidebar
* and make it invisible
*
* @private
*/
private _hideSidebar(delay = true): void {
const delayAmount = delay ? 300 : 0;
// Add a delay so close animation can play
setTimeout(() => {
// Remove the box-shadow
this._renderer.setStyle(
this._elementRef.nativeElement,
'box-shadow',
'none'
);
// Make the sidebar invisible
this._renderer.setStyle(
this._elementRef.nativeElement,
'visibility',
'hidden'
);
}, delayAmount);
// Mark for check
this._changeDetectorRef.markForCheck();
}
/**
* Enable the animations
*
* @private
*/
private _enableAnimations(): void {
// Return if animations already enabled
if (this._animationsEnabled) {
return;
}
// Enable the animations
this._animationsEnabled = true;
// Mark for check
this._changeDetectorRef.markForCheck();
}
// -----------------------------------------------------------------------------------------------------
// @ Public methods
// -----------------------------------------------------------------------------------------------------
/**
* Open the sidebar
*/
open(): void {
if (this.opened || this.isLockedOpen) {
return;
}
// Enable the animations
this._enableAnimations();
// Show the sidebar
this._showSidebar();
// Show the backdrop
this._showBackdrop();
// Set the opened status
this.opened = true;
// Emit the 'openedChanged' event
this.openedChanged.emit(this.opened);
// Mark for check
this._changeDetectorRef.markForCheck();
}
/**
* Close the sidebar
*/
close(): void {
if (!this.opened || this.isLockedOpen) {
return;
}
// Enable the animations
this._enableAnimations();
// Hide the backdrop
this._hideBackdrop();
// Set the opened status
this.opened = false;
// Emit the 'openedChanged' event
this.openedChanged.emit(this.opened);
// Hide the sidebar
this._hideSidebar();
// Mark for check
this._changeDetectorRef.markForCheck();
}
/**
* Toggle open/close the sidebar
*/
toggleOpen(): void {
if (this.opened) {
this.close();
} else {
this.open();
}
}
/**
* Mouseenter
*/
@HostListener('mouseenter')
onMouseEnter(): void {
// Only work if the auto trigger is enabled
if (!this.foldedAutoTriggerOnHover) {
return;
}
this.unfoldTemporarily();
}
/**
* Mouseleave
*/
@HostListener('mouseleave')
onMouseLeave(): void {
// Only work if the auto trigger is enabled
if (!this.foldedAutoTriggerOnHover) {
return;
}
this.foldTemporarily();
}
/**
* Fold the sidebar permanently
*/
fold(): void {
// Only work if the sidebar is not folded
if (this.folded) {
return;
}
// Enable the animations
this._enableAnimations();
// Fold
this.folded = true;
// Mark for check
this._changeDetectorRef.markForCheck();
}
/**
* Unfold the sidebar permanently
*/
unfold(): void {
// Only work if the sidebar is folded
if (!this.folded) {
return;
}
// Enable the animations
this._enableAnimations();
// Unfold
this.folded = false;
// Mark for check
this._changeDetectorRef.markForCheck();
}
/**
* Toggle the sidebar fold/unfold permanently
*/
toggleFold(): void {
if (this.folded) {
this.unfold();
} else {
this.fold();
}
}
/**
* Fold the temporarily unfolded sidebar back
*/
foldTemporarily(): void {
// Only work if the sidebar is folded
if (!this.folded) {
return;
}
// Enable the animations
this._enableAnimations();
// Fold the sidebar back
this.unfolded = false;
// Set the folded width
const styleValue = this.foldedWidth + 'px';
this._renderer.setStyle(
this._elementRef.nativeElement,
'width',
styleValue
);
this._renderer.setStyle(
this._elementRef.nativeElement,
'min-width',
styleValue
);
this._renderer.setStyle(
this._elementRef.nativeElement,
'max-width',
styleValue
);
// Mark for check
this._changeDetectorRef.markForCheck();
}
/**
* Unfold the sidebar temporarily
*/
unfoldTemporarily(): void {
// Only work if the sidebar is folded
if (!this.folded) {
return;
}
// Enable the animations
this._enableAnimations();
// Unfold the sidebar temporarily
this.unfolded = true;
// Remove the folded width
this._renderer.removeStyle(this._elementRef.nativeElement, 'width');
this._renderer.removeStyle(this._elementRef.nativeElement, 'min-width');
this._renderer.removeStyle(this._elementRef.nativeElement, 'max-width');
// Mark for check
this._changeDetectorRef.markForCheck();
}
}

View File

@ -0,0 +1,9 @@
import { NgModule } from '@angular/core';
import { FuseSidebarComponent } from './sidebar.component';
@NgModule({
declarations: [FuseSidebarComponent],
exports: [FuseSidebarComponent]
})
export class FuseSidebarModule {}

View File

@ -0,0 +1,73 @@
import { Injectable } from '@angular/core';
import { FuseSidebarComponent } from './sidebar.component';
@Injectable({
providedIn: 'root'
})
export class FuseSidebarService {
// Private
private _registry: { [key: string]: FuseSidebarComponent } = {};
/**
* Constructor
*/
constructor() {}
/**
* Add the sidebar to the registry
*
* @param key
* @param sidebar
*/
register(key, sidebar): void {
// Check if the key already being used
if (this._registry[key]) {
console.error(
`The sidebar with the key '${key}' already exists. Either unregister it first or use a unique key.`
);
return;
}
// Add to the registry
this._registry[key] = sidebar;
}
/**
* Remove the sidebar from the registry
*
* @param key
*/
unregister(key): void {
// Check if the sidebar exists
if (!this._registry[key]) {
console.warn(
`The sidebar with the key '${key}' doesn't exist in the registry.`
);
}
// Unregister the sidebar
delete this._registry[key];
}
/**
* Return the sidebar with the given key
*
* @param key
* @returns {FuseSidebarComponent}
*/
getSidebar(key): FuseSidebarComponent {
// Check if the sidebar exists
if (!this._registry[key]) {
console.warn(
`The sidebar with the key '${key}' doesn't exist in the registry.`
);
return;
}
// Return the sidebar
return this._registry[key];
}
}

View File

@ -0,0 +1,15 @@
@mixin fuse-sidebar-theme($theme) {
$background: map-get($theme, background);
fuse-sidebar {
background: map-get($background, background);
}
.fuse-sidebar-overlay {
background-color: rgba(0, 0, 0, 0.6);
&.fuse-sidebar-overlay-invisible {
background-color: transparent;
}
}
}

View File

@ -0,0 +1,545 @@
<div class="theme-options-panel" fusePerfectScrollbar>
<div class="header">
<span class="title">Theme Options</span>
<button mat-icon-button class="close-button" (click)="toggleSidebarOpen('themeOptionsPanel')">
<mat-icon>close</mat-icon>
</button>
</div>
<form [formGroup]="form">
<!-- COLOR THEME -->
<div class="group">
<h2>Color themes</h2>
<mat-radio-group fxLayout="column" fxLayoutAlign="start start" formControlName="colorTheme">
<mat-radio-button class="mb-12" value="theme-default">Default Light</mat-radio-button>
<mat-radio-button class="mb-12" value="theme-yellow-light">Yellow Light</mat-radio-button>
<mat-radio-button class="mb-12" value="theme-blue-gray-dark">Blue-Gray Dark</mat-radio-button>
<mat-radio-button class="mb-12" value="theme-pink-dark">Pink Dark</mat-radio-button>
</mat-radio-group>
</div>
<!-- LAYOUT STYLES -->
<div class="group" formGroupName="layout">
<h2>Layout Styles</h2>
<mat-radio-group fxLayout="column" fxLayoutAlign="start start" formControlName="style">
<mat-radio-button class="mb-12" value="vertical-layout-1">
Vertical Layout #1
</mat-radio-button>
<mat-radio-button class="mb-12" value="vertical-layout-2">
Vertical Layout #2
</mat-radio-button>
<mat-radio-button class="mb-12" value="vertical-layout-3">
Vertical Layout #3
</mat-radio-button>
<mat-radio-button class="mb-12" value="horizontal-layout-1">
Horizontal Layout #1
</mat-radio-button>
</mat-radio-group>
<!-- DIFFERENT FORMS BASED ON LAYOUT STYLES -->
<ng-container [ngSwitch]="fuseConfig.layout.style">
<!-- VERTICAL LAYOUT #1 -->
<ng-container *ngSwitchCase="'vertical-layout-1'">
<!-- LAYOUT WIDTH -->
<div class="group mt-32">
<h2>Layout Width</h2>
<mat-radio-group fxLayout="column" fxLayoutAlign="start start" formControlName="width">
<mat-radio-button class="mb-12" value="fullwidth">Fullwidth</mat-radio-button>
<mat-radio-button class="mb-12" value="boxed">Boxed</mat-radio-button>
</mat-radio-group>
</div>
<!-- NAVBAR -->
<div class="group" formGroupName="navbar">
<h2>Navbar</h2>
<mat-slide-toggle formControlName="hidden">
Hide
</mat-slide-toggle>
<mat-slide-toggle class="mt-24" formControlName="folded">
Folded
</mat-slide-toggle>
<h3 class="mt-24">Position:</h3>
<mat-radio-group fxLayout="column" fxLayoutAlign="start start" formControlName="position">
<mat-radio-button class="mb-16" value="left">Left</mat-radio-button>
<mat-radio-button class="mb-16" value="right">Right</mat-radio-button>
</mat-radio-group>
<h3 class="mt-8">Variant:</h3>
<mat-radio-group fxLayout="column" fxLayoutAlign="start start" formControlName="variant">
<mat-radio-button class="mb-16" value="vertical-style-1">Style 1</mat-radio-button>
<mat-radio-button class="mb-16" value="vertical-style-2">Style 2</mat-radio-button>
</mat-radio-group>
<h3 class="mt-16 mb-8">Primary background:</h3>
<fuse-material-color-picker class="mb-16" formControlName="primaryBackground">
</fuse-material-color-picker>
<h3 class="mt-16 mb-8">Secondary background:</h3>
<fuse-material-color-picker class="mb-16" formControlName="secondaryBackground">
</fuse-material-color-picker>
</div>
<!-- TOOLBAR -->
<div class="group" formGroupName="toolbar">
<h2>Toolbar</h2>
<mat-slide-toggle formControlName="hidden">
Hide
</mat-slide-toggle>
<h3 class="mt-24">Position:</h3>
<mat-radio-group fxLayout="column" fxLayoutAlign="start start" formControlName="position">
<mat-radio-button class="mb-12" value="above">Above</mat-radio-button>
<mat-radio-button class="mb-12" value="below-static">Below Static</mat-radio-button>
<mat-radio-button class="mb-12" value="below-fixed">Below Fixed</mat-radio-button>
</mat-radio-group>
<mat-checkbox class="mt-24" formControlName="customBackgroundColor">
Use custom background color
</mat-checkbox>
<h3 class="mt-24 mb-8">Background color:</h3>
<fuse-material-color-picker class="mb-16" formControlName="background">
</fuse-material-color-picker>
</div>
<!-- FOOTER -->
<div class="group" formGroupName="footer">
<h2>Footer</h2>
<mat-slide-toggle formControlName="hidden">
Hide
</mat-slide-toggle>
<h3 class="mt-24">Position:</h3>
<mat-radio-group fxLayout="column" fxLayoutAlign="start start" formControlName="position">
<mat-radio-button class="mb-12" value="above">Above</mat-radio-button>
<mat-radio-button class="mb-12" value="below-static">Below Static</mat-radio-button>
<mat-radio-button class="mb-12" value="below-fixed">Below Fixed</mat-radio-button>
</mat-radio-group>
<mat-checkbox class="mt-24" formControlName="customBackgroundColor">
Use custom background color
</mat-checkbox>
<h3 class="mt-24 mb-8">Color:</h3>
<fuse-material-color-picker class="mb-16" formControlName="background">
</fuse-material-color-picker>
</div>
<!-- SIDE PANEL -->
<div class="group" formGroupName="sidepanel">
<h2>Side Panel</h2>
<mat-slide-toggle formControlName="hidden">
Hide
</mat-slide-toggle>
<h3 class="mt-24">Position:</h3>
<mat-radio-group fxLayout="column" fxLayoutAlign="start start" formControlName="position">
<mat-radio-button class="mb-12" value="left">Left</mat-radio-button>
<mat-radio-button class="mb-12" value="right">Right</mat-radio-button>
</mat-radio-group>
</div>
</ng-container>
<!-- VERTICAL LAYOUT #2 -->
<ng-container *ngSwitchCase="'vertical-layout-2'">
<!-- LAYOUT WIDTH -->
<div class="group mt-32">
<h2>Layout Width</h2>
<mat-radio-group fxLayout="column" fxLayoutAlign="start start" formControlName="width">
<mat-radio-button class="mb-12" value="fullwidth">Fullwidth</mat-radio-button>
<mat-radio-button class="mb-12" value="boxed">Boxed</mat-radio-button>
</mat-radio-group>
</div>
<!-- NAVBAR -->
<div class="group" formGroupName="navbar">
<h2>Navbar</h2>
<mat-slide-toggle formControlName="hidden">
Hide
</mat-slide-toggle>
<mat-slide-toggle class="mt-24" formControlName="folded">
Folded
</mat-slide-toggle>
<h3 class="mt-24">Position:</h3>
<mat-radio-group fxLayout="column" fxLayoutAlign="start start" formControlName="position">
<mat-radio-button class="mb-16" value="left">Left</mat-radio-button>
<mat-radio-button class="mb-16" value="right">Right</mat-radio-button>
</mat-radio-group>
<h3 class="mt-8">Variant:</h3>
<mat-radio-group fxLayout="column" fxLayoutAlign="start start" formControlName="variant">
<mat-radio-button class="mb-16" value="vertical-style-1">Style 1</mat-radio-button>
<mat-radio-button class="mb-16" value="vertical-style-2">Style 2</mat-radio-button>
</mat-radio-group>
<h3 class="mt-16 mb-8">Primary background:</h3>
<fuse-material-color-picker class="mb-16" formControlName="primaryBackground">
</fuse-material-color-picker>
<h3 class="mt-16 mb-8">Secondary background:</h3>
<fuse-material-color-picker class="mb-16" formControlName="secondaryBackground">
</fuse-material-color-picker>
</div>
<!-- TOOLBAR -->
<div class="group" formGroupName="toolbar">
<h2>Toolbar</h2>
<mat-slide-toggle formControlName="hidden">
Hide
</mat-slide-toggle>
<h3 class="mt-24">Position:</h3>
<mat-radio-group fxLayout="column" fxLayoutAlign="start start" formControlName="position">
<mat-radio-button class="mb-12" value="above-static">Above Static</mat-radio-button>
<mat-radio-button class="mb-12" value="above-fixed">Above Fixed</mat-radio-button>
<mat-radio-button class="mb-12" value="below">Below</mat-radio-button>
</mat-radio-group>
<mat-checkbox class="mt-24" formControlName="customBackgroundColor">
Use custom background color
</mat-checkbox>
<h3 class="mt-24 mb-8">Background color:</h3>
<fuse-material-color-picker class="mb-16" formControlName="background">
</fuse-material-color-picker>
</div>
<!-- FOOTER -->
<div class="group" formGroupName="footer">
<h2>Footer</h2>
<mat-slide-toggle formControlName="hidden">
Hide
</mat-slide-toggle>
<h3 class="mt-24">Position:</h3>
<mat-radio-group fxLayout="column" fxLayoutAlign="start start" formControlName="position">
<mat-radio-button class="mb-12" value="above-static">Above Static</mat-radio-button>
<mat-radio-button class="mb-12" value="above-fixed">Above Fixed</mat-radio-button>
<mat-radio-button class="mb-12" value="below">Below</mat-radio-button>
</mat-radio-group>
<mat-checkbox class="mt-24" formControlName="customBackgroundColor">
Use custom background color
</mat-checkbox>
<h3 class="mt-24 mb-8">Background color:</h3>
<fuse-material-color-picker class="mb-16" formControlName="background">
</fuse-material-color-picker>
</div>
<!-- SIDE PANEL -->
<div class="group" formGroupName="sidepanel">
<h2>Side Panel</h2>
<mat-slide-toggle formControlName="hidden">
Hide
</mat-slide-toggle>
<h3 class="mt-24">Position:</h3>
<mat-radio-group fxLayout="column" fxLayoutAlign="start start" formControlName="position">
<mat-radio-button class="mb-12" value="left">Left</mat-radio-button>
<mat-radio-button class="mb-12" value="right">Right</mat-radio-button>
</mat-radio-group>
</div>
</ng-container>
<!-- VERTICAL LAYOUT #3 -->
<ng-container *ngSwitchCase="'vertical-layout-3'">
<!-- LAYOUT WIDTH -->
<div class="group mt-32">
<h2>Layout Width</h2>
<mat-radio-group fxLayout="column" fxLayoutAlign="start start" formControlName="width">
<mat-radio-button class="mb-12" value="fullwidth">Fullwidth</mat-radio-button>
<mat-radio-button class="mb-12" value="boxed">Boxed</mat-radio-button>
</mat-radio-group>
</div>
<!-- NAVBAR -->
<div class="group" formGroupName="navbar">
<h2>Navbar</h2>
<mat-slide-toggle formControlName="hidden">
Hide
</mat-slide-toggle>
<mat-slide-toggle class="mt-24" formControlName="folded">
Folded
</mat-slide-toggle>
<h3 class="mt-24">Position:</h3>
<mat-radio-group fxLayout="column" fxLayoutAlign="start start" formControlName="position">
<mat-radio-button class="mb-16" value="left">Left</mat-radio-button>
<mat-radio-button class="mb-16" value="right">Right</mat-radio-button>
</mat-radio-group>
<h3 class="mt-8">Variant:</h3>
<mat-radio-group fxLayout="column" fxLayoutAlign="start start" formControlName="variant">
<mat-radio-button class="mb-16" value="vertical-style-1">Style 1</mat-radio-button>
<mat-radio-button class="mb-16" value="vertical-style-2">Style 2</mat-radio-button>
</mat-radio-group>
<h3 class="mt-16 mb-8">Primary background:</h3>
<fuse-material-color-picker class="mb-16" formControlName="primaryBackground">
</fuse-material-color-picker>
<h3 class="mt-16 mb-8">Secondary background:</h3>
<fuse-material-color-picker class="mb-16" formControlName="secondaryBackground">
</fuse-material-color-picker>
</div>
<!-- TOOLBAR -->
<div class="group" formGroupName="toolbar">
<h2>Toolbar</h2>
<mat-slide-toggle formControlName="hidden">
Hide
</mat-slide-toggle>
<h3 class="mt-24">Position:</h3>
<mat-radio-group fxLayout="column" fxLayoutAlign="start start" formControlName="position">
<mat-radio-button class="mb-12" value="above-static">Above Static</mat-radio-button>
<mat-radio-button class="mb-12" value="above-fixed">Above Fixed</mat-radio-button>
</mat-radio-group>
<mat-checkbox class="mt-24" formControlName="customBackgroundColor">
Use custom background color
</mat-checkbox>
<h3 class="mt-24 mb-8">Background color:</h3>
<fuse-material-color-picker class="mb-16" formControlName="background">
</fuse-material-color-picker>
</div>
<!-- FOOTER -->
<div class="group" formGroupName="footer">
<h2>Footer</h2>
<mat-slide-toggle formControlName="hidden">
Hide
</mat-slide-toggle>
<h3 class="mt-24">Position:</h3>
<mat-radio-group fxLayout="column" fxLayoutAlign="start start" formControlName="position">
<mat-radio-button class="mb-12" value="above-static">Above Static</mat-radio-button>
<mat-radio-button class="mb-12" value="above-fixed">Above Fixed</mat-radio-button>
</mat-radio-group>
<mat-checkbox class="mt-24" formControlName="customBackgroundColor">
Use custom background color
</mat-checkbox>
<h3 class="mt-24 mb-8">Background color:</h3>
<fuse-material-color-picker class="mb-16" formControlName="background">
</fuse-material-color-picker>
</div>
<!-- SIDE PANEL -->
<div class="group" formGroupName="sidepanel">
<h2>Side Panel</h2>
<mat-slide-toggle formControlName="hidden">
Hide
</mat-slide-toggle>
<h3 class="mt-24">Position:</h3>
<mat-radio-group fxLayout="column" fxLayoutAlign="start start" formControlName="position">
<mat-radio-button class="mb-12" value="left">Left</mat-radio-button>
<mat-radio-button class="mb-12" value="right">Right</mat-radio-button>
</mat-radio-group>
</div>
</ng-container>
<!-- HORIZONTAL LAYOUT #1 -->
<ng-container *ngSwitchCase="'horizontal-layout-1'">
<!-- LAYOUT WIDTH -->
<div class="group mt-32">
<h2>Layout Width</h2>
<mat-radio-group fxLayout="column" fxLayoutAlign="start start" formControlName="width">
<mat-radio-button class="mb-12" value="fullwidth">Fullwidth</mat-radio-button>
<mat-radio-button class="mb-12" value="boxed">Boxed</mat-radio-button>
</mat-radio-group>
</div>
<!-- NAVBAR -->
<div class="group" formGroupName="navbar">
<h2>Navbar</h2>
<mat-slide-toggle formControlName="hidden">
Hide
</mat-slide-toggle>
<h3 class="mt-24">Position:</h3>
<mat-radio-group fxLayout="column" fxLayoutAlign="start start" formControlName="position">
<mat-radio-button class="mb-16" value="top">Top</mat-radio-button>
</mat-radio-group>
<h3 class="mt-8">Variant (Vertical):</h3>
<mat-radio-group fxLayout="column" fxLayoutAlign="start start" formControlName="variant">
<mat-radio-button class="mb-16" value="vertical-style-1">Style 1</mat-radio-button>
<mat-radio-button class="mb-16" value="vertical-style-2">Style 2</mat-radio-button>
</mat-radio-group>
<h3 class="mt-16 mb-8">Primary background:</h3>
<fuse-material-color-picker class="mb-16" formControlName="primaryBackground">
</fuse-material-color-picker>
<h3 class="mt-16 mb-8">Secondary background (Vertical):</h3>
<fuse-material-color-picker class="mb-16" formControlName="secondaryBackground">
</fuse-material-color-picker>
</div>
<!-- TOOLBAR -->
<div class="group" formGroupName="toolbar">
<h2>Toolbar</h2>
<mat-slide-toggle formControlName="hidden">
Hide
</mat-slide-toggle>
<h3 class="mt-24">Position:</h3>
<mat-radio-group fxLayout="column" fxLayoutAlign="start start" formControlName="position">
<mat-radio-button class="mb-12" value="above">Above</mat-radio-button>
<mat-radio-button class="mb-12" value="below">Below</mat-radio-button>
</mat-radio-group>
<mat-checkbox class="mt-24" formControlName="customBackgroundColor">
Use custom background color
</mat-checkbox>
<h3 class="mt-24 mb-8">Background color:</h3>
<fuse-material-color-picker class="mb-16" formControlName="background">
</fuse-material-color-picker>
</div>
<!-- FOOTER -->
<div class="group" formGroupName="footer">
<h2>Footer</h2>
<mat-slide-toggle formControlName="hidden">
Hide
</mat-slide-toggle>
<h3 class="mt-24">Position:</h3>
<mat-radio-group fxLayout="column" fxLayoutAlign="start start" formControlName="position">
<mat-radio-button class="mb-12" value="above-fixed">Above Fixed</mat-radio-button>
<mat-radio-button class="mb-12" value="above-static">Above Static</mat-radio-button>
</mat-radio-group>
<mat-checkbox class="mt-24" formControlName="customBackgroundColor">
Use custom background color
</mat-checkbox>
<h3 class="mt-24 mb-8">Background color:</h3>
<fuse-material-color-picker class="mb-16" formControlName="background">
</fuse-material-color-picker>
</div>
<!-- SIDE PANEL -->
<div class="group" formGroupName="sidepanel">
<h2>Side Panel</h2>
<mat-slide-toggle formControlName="hidden">
Hide
</mat-slide-toggle>
<h3 class="mt-24">Position:</h3>
<mat-radio-group fxLayout="column" fxLayoutAlign="start start" formControlName="position">
<mat-radio-button class="mb-12" value="left">Left</mat-radio-button>
<mat-radio-button class="mb-12" value="right">Right</mat-radio-button>
</mat-radio-group>
</div>
</ng-container>
</ng-container>
</div>
<!-- CUSTOM SCROLLBARS -->
<div class="group">
<h2>Custom scrollbars</h2>
<mat-slide-toggle class="mb-12" formControlName="customScrollbars">
Enable custom scrollbars
</mat-slide-toggle>
</div>
</form>
</div>

View File

@ -0,0 +1,75 @@
@import 'src/@fuse/scss/fuse';
@keyframes rotating {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
fuse-theme-options {
display: flex;
overflow: hidden;
.theme-options-panel {
display: flex;
flex-direction: column;
flex: 1 0 auto;
padding: 40px 24px 24px 24px;
overflow: auto;
-webkit-overflow-scrolling: touch;
.header {
display: flex;
flex: 0 1 auto;
margin-bottom: 32px;
align-items: center;
justify-content: space-between;
.title {
font-size: 20px;
font-weight: 600;
padding-left: 4px;
}
}
form {
display: flex;
flex: 1 1 auto;
flex-direction: column;
.group {
display: flex;
flex: 1 0 auto;
flex-direction: column;
position: relative;
border-radius: 2px;
padding: 28px 16px 8px 16px;
margin: 16px 0;
h2 {
position: absolute;
top: -11px;
left: 8px;
margin: 0;
padding: 0 8px;
font-size: 16px;
font-weight: 600;
}
h3 {
font-size: 14px;
font-weight: 600;
margin: 24px 0 16px 0;
padding: 0;
&:first-of-type {
margin-top: 0;
}
}
}
}
}
}

View File

@ -0,0 +1,336 @@
import {
Component,
HostBinding,
Inject,
OnDestroy,
OnInit,
Renderer2,
ViewEncapsulation
} from '@angular/core';
import { FormBuilder, FormControl, FormGroup } from '@angular/forms';
import { DOCUMENT } from '@angular/common';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { fuseAnimations } from 'src/@fuse/animations';
import { FuseConfigService } from 'src/@fuse/services/config.service';
import { FuseNavigationService } from 'src/@fuse/components/navigation/navigation.service';
import { FuseSidebarService } from 'src/@fuse/components/sidebar/sidebar.service';
@Component({
selector: 'fuse-theme-options',
templateUrl: './theme-options.component.html',
styleUrls: ['./theme-options.component.scss'],
encapsulation: ViewEncapsulation.None,
animations: fuseAnimations
})
export class FuseThemeOptionsComponent implements OnInit, OnDestroy {
fuseConfig: any;
form: FormGroup;
@HostBinding('class.bar-closed')
barClosed: boolean;
// Private
private _unsubscribeAll: Subject<any>;
/**
* Constructor
*
* @param {DOCUMENT} document
* @param {FormBuilder} _formBuilder
* @param {FuseConfigService} _fuseConfigService
* @param {FuseNavigationService} _fuseNavigationService
* @param {FuseSidebarService} _fuseSidebarService
* @param {Renderer2} _renderer
*/
constructor(
@Inject(DOCUMENT) private document: any,
private _formBuilder: FormBuilder,
private _fuseConfigService: FuseConfigService,
private _fuseNavigationService: FuseNavigationService,
private _fuseSidebarService: FuseSidebarService,
private _renderer: Renderer2
) {
// Set the defaults
this.barClosed = true;
// Set the private defaults
this._unsubscribeAll = new Subject();
}
// -----------------------------------------------------------------------------------------------------
// @ Lifecycle hooks
// -----------------------------------------------------------------------------------------------------
/**
* On init
*/
ngOnInit(): void {
// Build the config form
// noinspection TypeScriptValidateTypes
this.form = this._formBuilder.group({
colorTheme: new FormControl(),
customScrollbars: new FormControl(),
layout: this._formBuilder.group({
style: new FormControl(),
width: new FormControl(),
navbar: this._formBuilder.group({
primaryBackground: new FormControl(),
secondaryBackground: new FormControl(),
folded: new FormControl(),
hidden: new FormControl(),
position: new FormControl(),
variant: new FormControl()
}),
toolbar: this._formBuilder.group({
background: new FormControl(),
customBackgroundColor: new FormControl(),
hidden: new FormControl(),
position: new FormControl()
}),
footer: this._formBuilder.group({
background: new FormControl(),
customBackgroundColor: new FormControl(),
hidden: new FormControl(),
position: new FormControl()
}),
sidepanel: this._formBuilder.group({
hidden: new FormControl(),
position: new FormControl()
})
})
});
// Subscribe to the config changes
this._fuseConfigService.config
.pipe(takeUntil(this._unsubscribeAll))
.subscribe(config => {
// Update the stored config
this.fuseConfig = config;
// Set the config form values without emitting an event
// so that we don't end up with an infinite loop
this.form.setValue(config, { emitEvent: false });
});
// Subscribe to the specific form value changes (layout.style)
this.form
.get('layout.style')
.valueChanges.pipe(takeUntil(this._unsubscribeAll))
.subscribe(value => {
// Reset the form values based on the
// selected layout style
this._resetFormValues(value);
});
// Subscribe to the form value changes
this.form.valueChanges
.pipe(takeUntil(this._unsubscribeAll))
.subscribe(config => {
// Update the config
this._fuseConfigService.config = config;
});
// Add customize nav item that opens the bar programmatically
const customFunctionNavItem = {
id: 'custom-function',
title: 'Custom Function',
type: 'group',
icon: 'settings',
children: [
{
id: 'customize',
title: 'Customize',
type: 'item',
icon: 'settings',
function: () => {
this.toggleSidebarOpen('themeOptionsPanel');
}
}
]
};
this._fuseNavigationService.addNavigationItem(customFunctionNavItem, 'end');
}
/**
* On destroy
*/
ngOnDestroy(): void {
// Unsubscribe from all subscriptions
this._unsubscribeAll.next();
this._unsubscribeAll.complete();
// Remove the custom function menu
this._fuseNavigationService.removeNavigationItem('custom-function');
}
// -----------------------------------------------------------------------------------------------------
// @ Private methods
// -----------------------------------------------------------------------------------------------------
/**
* Reset the form values based on the
* selected layout style
*
* @param value
* @private
*/
private _resetFormValues(value): void {
switch (value) {
// Vertical Layout #1
case 'vertical-layout-1': {
this.form.patchValue({
layout: {
width: 'fullwidth',
navbar: {
primaryBackground: 'fuse-navy-700',
secondaryBackground: 'fuse-navy-900',
folded: false,
hidden: false,
position: 'left',
variant: 'vertical-style-1'
},
toolbar: {
background: 'fuse-white-500',
customBackgroundColor: false,
hidden: false,
position: 'below-static'
},
footer: {
background: 'fuse-navy-900',
customBackgroundColor: true,
hidden: false,
position: 'below-static'
},
sidepanel: {
hidden: false,
position: 'right'
}
}
});
break;
}
// Vertical Layout #2
case 'vertical-layout-2': {
this.form.patchValue({
layout: {
width: 'fullwidth',
navbar: {
primaryBackground: 'fuse-navy-700',
secondaryBackground: 'fuse-navy-900',
folded: false,
hidden: false,
position: 'left',
variant: 'vertical-style-1'
},
toolbar: {
background: 'fuse-white-500',
customBackgroundColor: false,
hidden: false,
position: 'below'
},
footer: {
background: 'fuse-navy-900',
customBackgroundColor: true,
hidden: false,
position: 'below'
},
sidepanel: {
hidden: false,
position: 'right'
}
}
});
break;
}
// Vertical Layout #3
case 'vertical-layout-3': {
this.form.patchValue({
layout: {
width: 'fullwidth',
navbar: {
primaryBackground: 'fuse-navy-700',
secondaryBackground: 'fuse-navy-900',
folded: false,
hidden: false,
position: 'left',
layout: 'vertical-style-1'
},
toolbar: {
background: 'fuse-white-500',
customBackgroundColor: false,
hidden: false,
position: 'above-static'
},
footer: {
background: 'fuse-navy-900',
customBackgroundColor: true,
hidden: false,
position: 'above-static'
},
sidepanel: {
hidden: false,
position: 'right'
}
}
});
break;
}
// Horizontal Layout #1
case 'horizontal-layout-1': {
this.form.patchValue({
layout: {
width: 'fullwidth',
navbar: {
primaryBackground: 'fuse-navy-700',
secondaryBackground: 'fuse-navy-900',
folded: false,
hidden: false,
position: 'top',
variant: 'vertical-style-1'
},
toolbar: {
background: 'fuse-white-500',
customBackgroundColor: false,
hidden: false,
position: 'above'
},
footer: {
background: 'fuse-navy-900',
customBackgroundColor: true,
hidden: false,
position: 'above-fixed'
},
sidepanel: {
hidden: false,
position: 'right'
}
}
});
break;
}
}
}
// -----------------------------------------------------------------------------------------------------
// @ Public methods
// -----------------------------------------------------------------------------------------------------
/**
* Toggle sidebar open
*
* @param key
*/
toggleSidebarOpen(key): void {
this._fuseSidebarService.getSidebar(key).toggleOpen();
}
}

View File

@ -0,0 +1,46 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { FlexLayoutModule } from '@angular/flex-layout';
import { MatButtonModule } from '@angular/material/button';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatOptionModule } from '@angular/material/core';
import { MatDividerModule } from '@angular/material/divider';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatRadioModule } from '@angular/material/radio';
import { MatSelectModule } from '@angular/material/select';
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { FuseDirectivesModule } from 'src/@fuse/directives/directives';
import { FuseMaterialColorPickerModule } from 'src/@fuse/components/material-color-picker/material-color-picker.module';
import { FuseSidebarModule } from 'src/@fuse/components/sidebar/sidebar.module';
import { FuseThemeOptionsComponent } from 'src/@fuse/components/theme-options/theme-options.component';
@NgModule({
declarations: [FuseThemeOptionsComponent],
imports: [
CommonModule,
FormsModule,
ReactiveFormsModule,
FlexLayoutModule,
MatButtonModule,
MatCheckboxModule,
MatDividerModule,
MatFormFieldModule,
MatIconModule,
MatOptionModule,
MatRadioModule,
MatSelectModule,
MatSlideToggleModule,
FuseDirectivesModule,
FuseMaterialColorPickerModule,
FuseSidebarModule
],
exports: [FuseThemeOptionsComponent]
})
export class FuseThemeOptionsModule {}

View File

@ -0,0 +1,23 @@
@mixin fuse-theme-options-theme($theme) {
$background: map-get($theme, background);
$foreground: map-get($theme, foreground);
fuse-theme-options {
.theme-options-panel {
form {
.group {
border: 1px solid map-get($foreground, divider);
h2 {
background: map-get($background, background);
color: map-get($foreground, secondary-text);
}
h3 {
color: map-get($foreground, secondary-text);
}
}
}
}
}
}

View File

@ -0,0 +1,13 @@
import { Directive, ElementRef } from '@angular/core';
@Directive({
selector: '[fuseWidgetToggle]'
})
export class FuseWidgetToggleDirective {
/**
* Constructor
*
* @param {ElementRef} elementRef
*/
constructor(public elementRef: ElementRef) {}
}

View File

@ -0,0 +1 @@
<ng-content></ng-content>

View File

@ -0,0 +1,87 @@
fuse-widget {
display: block;
position: relative;
perspective: 3000px;
padding: 12px;
> div {
position: relative;
transform-style: preserve-3d;
transition: transform 1s;
}
> .fuse-widget-front {
display: flex;
flex-direction: column;
flex: 1 1 auto;
position: relative;
overflow: hidden;
visibility: visible;
width: 100%;
opacity: 1;
z-index: 10;
border-radius: 8px;
transition: transform 0.5s ease-out 0s, visibility 0s ease-in 0.2s,
opacity 0s ease-in 0.2s;
transform: rotateY(0deg);
backface-visibility: hidden;
border: 1px solid;
}
> .fuse-widget-back {
display: block;
position: absolute;
top: 12px;
right: 12px;
bottom: 12px;
left: 12px;
overflow: hidden;
visibility: hidden;
opacity: 0;
z-index: 10;
border-radius: 8px;
transition: transform 0.5s ease-out 0s, visibility 0s ease-in 0.2s,
opacity 0s ease-in 0.2s;
transform: rotateY(180deg);
backface-visibility: hidden;
border: 1px solid;
[fuseWidgetToggle] {
position: absolute;
top: 0;
right: 0;
}
}
&.flipped {
> .fuse-widget-front {
visibility: hidden;
opacity: 0;
transform: rotateY(180deg);
}
> .fuse-widget-back {
display: block;
visibility: visible;
opacity: 1;
transform: rotateY(360deg);
}
}
.mat-form-field {
&.mat-form-field-type-mat-select {
.mat-form-field-wrapper {
padding: 16px 0;
.mat-form-field-infix {
border: none;
padding: 0;
}
}
.mat-form-field-underline {
display: none;
}
}
}
}

View File

@ -0,0 +1,68 @@
import {
AfterContentInit,
Component,
ContentChildren,
ElementRef,
HostBinding,
QueryList,
Renderer2,
ViewEncapsulation
} from '@angular/core';
import { FuseWidgetToggleDirective } from './widget-toggle.directive';
@Component({
selector: 'fuse-widget',
templateUrl: './widget.component.html',
styleUrls: ['./widget.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class FuseWidgetComponent implements AfterContentInit {
@HostBinding('class.flipped')
flipped = false;
@ContentChildren(FuseWidgetToggleDirective, { descendants: true })
toggleButtons: QueryList<FuseWidgetToggleDirective>;
/**
* Constructor
*
* @param {ElementRef} _elementRef
* @param {Renderer2} _renderer
*/
constructor(private _elementRef: ElementRef, private _renderer: Renderer2) {}
// -----------------------------------------------------------------------------------------------------
// @ Lifecycle hooks
// -----------------------------------------------------------------------------------------------------
/**
* After content init
*/
ngAfterContentInit(): void {
// Listen for the flip button click
setTimeout(() => {
this.toggleButtons.forEach(flipButton => {
this._renderer.listen(
flipButton.elementRef.nativeElement,
'click',
event => {
event.preventDefault();
event.stopPropagation();
this.toggle();
}
);
});
});
}
// -----------------------------------------------------------------------------------------------------
// @ Public methods
// -----------------------------------------------------------------------------------------------------
/**
* Toggle the flipped status
*/
toggle(): void {
this.flipped = !this.flipped;
}
}

View File

@ -0,0 +1,10 @@
import { NgModule } from '@angular/core';
import { FuseWidgetComponent } from './widget.component';
import { FuseWidgetToggleDirective } from './widget-toggle.directive';
@NgModule({
declarations: [FuseWidgetComponent, FuseWidgetToggleDirective],
exports: [FuseWidgetComponent, FuseWidgetToggleDirective]
})
export class FuseWidgetModule {}

View File

@ -0,0 +1,12 @@
@mixin fuse-widget-theme($theme) {
$background: map-get($theme, background);
$foreground: map-get($theme, foreground);
fuse-widget {
> .fuse-widget-front,
> .fuse-widget-back {
background: map-get($background, card);
border-color: map-get($foreground, divider);
}
}
}

View File

@ -0,0 +1,28 @@
import { NgModule } from '@angular/core';
import { FuseIfOnDomDirective } from 'src/@fuse/directives/fuse-if-on-dom/fuse-if-on-dom.directive';
import { FuseInnerScrollDirective } from 'src/@fuse/directives/fuse-inner-scroll/fuse-inner-scroll.directive';
import { FusePerfectScrollbarDirective } from 'src/@fuse/directives/fuse-perfect-scrollbar/fuse-perfect-scrollbar.directive';
import {
FuseMatSidenavHelperDirective,
FuseMatSidenavTogglerDirective
} from 'src/@fuse/directives/fuse-mat-sidenav/fuse-mat-sidenav.directive';
@NgModule({
declarations: [
FuseIfOnDomDirective,
FuseInnerScrollDirective,
FuseMatSidenavHelperDirective,
FuseMatSidenavTogglerDirective,
FusePerfectScrollbarDirective
],
imports: [],
exports: [
FuseIfOnDomDirective,
FuseInnerScrollDirective,
FuseMatSidenavHelperDirective,
FuseMatSidenavTogglerDirective,
FusePerfectScrollbarDirective
]
})
export class FuseDirectivesModule {}

View File

@ -0,0 +1,55 @@
import {
AfterContentChecked,
Directive,
ElementRef,
TemplateRef,
ViewContainerRef
} from '@angular/core';
@Directive({
selector: '[fuseIfOnDom]'
})
export class FuseIfOnDomDirective implements AfterContentChecked {
isCreated: boolean;
/**
* Constructor
*
* @param {ElementRef} _elementRef
* @param {TemplateRef<any>} _templateRef
* @param {ViewContainerRef} _viewContainerRef
*/
constructor(
private _elementRef: ElementRef,
private _templateRef: TemplateRef<any>,
private _viewContainerRef: ViewContainerRef
) {
// Set the defaults
this.isCreated = false;
}
// -----------------------------------------------------------------------------------------------------
// @ Lifecycle hooks
// -----------------------------------------------------------------------------------------------------
/**
* After content checked
*/
ngAfterContentChecked(): void {
if (
document.body.contains(this._elementRef.nativeElement) &&
!this.isCreated
) {
setTimeout(() => {
this._viewContainerRef.createEmbeddedView(this._templateRef);
}, 300);
this.isCreated = true;
} else if (
this.isCreated &&
!document.body.contains(this._elementRef.nativeElement)
) {
this._viewContainerRef.clear();
this.isCreated = false;
}
}
}

View File

@ -0,0 +1,108 @@
import {
Directive,
ElementRef,
OnDestroy,
OnInit,
Renderer2
} from '@angular/core';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { FuseMatchMediaService } from 'src/@fuse/services/match-media.service';
@Directive({
selector: '.inner-scroll'
})
export class FuseInnerScrollDirective implements OnInit, OnDestroy {
// Private
private _parent: any;
private _grandParent: any;
private _unsubscribeAll: Subject<any>;
/**
* Constructor
*
* @param {ElementRef} _elementRef
* @param {FuseMatchMediaService} _fuseMediaMatchService
* @param {Renderer2} _renderer
*/
constructor(
private _elementRef: ElementRef,
private _fuseMediaMatchService: FuseMatchMediaService,
private _renderer: Renderer2
) {
// Set the private defaults
this._unsubscribeAll = new Subject();
}
// -----------------------------------------------------------------------------------------------------
// @ Lifecycle hooks
// -----------------------------------------------------------------------------------------------------
/**
* On init
*/
ngOnInit(): void {
// Get the parent
this._parent = this._renderer.parentNode(this._elementRef.nativeElement);
// Return, if there is no parent
if (!this._parent) {
return;
}
// Get the grand parent
this._grandParent = this._renderer.parentNode(this._parent);
// Register to the media query changes
this._fuseMediaMatchService.onMediaChange
.pipe(takeUntil(this._unsubscribeAll))
.subscribe(alias => {
if (alias === 'xs') {
this._removeClass();
} else {
this._addClass();
}
});
}
/**
* On destroy
*/
ngOnDestroy(): void {
// Return, if there is no parent
if (!this._parent) {
return;
}
// Remove the class
this._removeClass();
// Unsubscribe from all subscriptions
this._unsubscribeAll.next();
this._unsubscribeAll.complete();
}
// -----------------------------------------------------------------------------------------------------
// @ Private methods
// -----------------------------------------------------------------------------------------------------
/**
* Add the class name
*
* @private
*/
private _addClass(): void {
// Add the inner-scroll class
this._renderer.addClass(this._grandParent, 'inner-scroll');
}
/**
* Remove the class name
* @private
*/
private _removeClass(): void {
// Remove the inner-scroll class
this._renderer.removeClass(this._grandParent, 'inner-scroll');
}
}

View File

@ -0,0 +1,138 @@
import {
Directive,
Input,
OnInit,
HostListener,
OnDestroy,
HostBinding
} from '@angular/core';
import { MatSidenav } from '@angular/material/sidenav';
import { MediaObserver } from '@angular/flex-layout';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { FuseMatchMediaService } from 'src/@fuse/services/match-media.service';
import { FuseMatSidenavHelperService } from 'src/@fuse/directives/fuse-mat-sidenav/fuse-mat-sidenav.service';
@Directive({
selector: '[fuseMatSidenavHelper]'
})
export class FuseMatSidenavHelperDirective implements OnInit, OnDestroy {
@HostBinding('class.mat-is-locked-open')
isLockedOpen: boolean;
@Input()
fuseMatSidenavHelper: string;
@Input()
matIsLockedOpen: string;
// Private
private _unsubscribeAll: Subject<any>;
/**
* Constructor
*
* @param {FuseMatchMediaService} _fuseMatchMediaService
* @param {FuseMatSidenavHelperService} _fuseMatSidenavHelperService
* @param {MatSidenav} _matSidenav
* @param {MediaObserver} _mediaObserver
*/
constructor(
private _fuseMatchMediaService: FuseMatchMediaService,
private _fuseMatSidenavHelperService: FuseMatSidenavHelperService,
private _matSidenav: MatSidenav,
private _mediaObserver: MediaObserver
) {
// Set the defaults
this.isLockedOpen = true;
// Set the private defaults
this._unsubscribeAll = new Subject();
}
// -----------------------------------------------------------------------------------------------------
// @ Lifecycle hooks
// -----------------------------------------------------------------------------------------------------
/**
* On init
*/
ngOnInit(): void {
// Register the sidenav to the service
this._fuseMatSidenavHelperService.setSidenav(
this.fuseMatSidenavHelper,
this._matSidenav
);
if (
this.matIsLockedOpen &&
this._mediaObserver.isActive(this.matIsLockedOpen)
) {
this.isLockedOpen = true;
this._matSidenav.mode = 'side';
this._matSidenav.toggle(true);
} else {
this.isLockedOpen = false;
this._matSidenav.mode = 'over';
this._matSidenav.toggle(false);
}
this._fuseMatchMediaService.onMediaChange
.pipe(takeUntil(this._unsubscribeAll))
.subscribe(() => {
if (
this.matIsLockedOpen &&
this._mediaObserver.isActive(this.matIsLockedOpen)
) {
this.isLockedOpen = true;
this._matSidenav.mode = 'side';
this._matSidenav.toggle(true);
} else {
this.isLockedOpen = false;
this._matSidenav.mode = 'over';
this._matSidenav.toggle(false);
}
});
}
/**
* On destroy
*/
ngOnDestroy(): void {
// Unsubscribe from all subscriptions
this._unsubscribeAll.next();
this._unsubscribeAll.complete();
}
}
@Directive({
selector: '[fuseMatSidenavToggler]'
})
export class FuseMatSidenavTogglerDirective {
@Input()
fuseMatSidenavToggler: string;
/**
* Constructor
*
* @param {FuseMatSidenavHelperService} _fuseMatSidenavHelperService
*/
constructor(
private _fuseMatSidenavHelperService: FuseMatSidenavHelperService
) {}
// -----------------------------------------------------------------------------------------------------
// @ Public methods
// -----------------------------------------------------------------------------------------------------
/**
* On click
*/
@HostListener('click')
onClick(): void {
this._fuseMatSidenavHelperService
.getSidenav(this.fuseMatSidenavToggler)
.toggle();
}
}

View File

@ -0,0 +1,40 @@
import { Injectable } from '@angular/core';
import { MatSidenav } from '@angular/material/sidenav';
@Injectable({
providedIn: 'root'
})
export class FuseMatSidenavHelperService {
sidenavInstances: MatSidenav[];
/**
* Constructor
*/
constructor() {
this.sidenavInstances = [];
}
// -----------------------------------------------------------------------------------------------------
// @ Accessors
// -----------------------------------------------------------------------------------------------------
/**
* Set sidenav
*
* @param id
* @param instance
*/
setSidenav(id, instance): void {
this.sidenavInstances[id] = instance;
}
/**
* Get sidenav
*
* @param id
* @returns {any}
*/
getSidenav(id): any {
return this.sidenavInstances[id];
}
}

View File

@ -0,0 +1,508 @@
import {
AfterViewInit,
Directive,
ElementRef,
HostListener,
Input,
OnDestroy,
OnInit
} from '@angular/core';
import { NavigationEnd, Router } from '@angular/router';
import { Platform } from '@angular/cdk/platform';
import { fromEvent, Subject } from 'rxjs';
import { debounceTime, filter, takeUntil } from 'rxjs/operators';
import PerfectScrollbar from 'perfect-scrollbar';
import * as _ from 'lodash';
import {
FusePerfectScrollbarGeometry,
FusePerfectScrollbarPosition
} from 'src/@fuse/directives/fuse-perfect-scrollbar/fuse-perfect-scrollbar.interfaces';
import { FuseConfigService } from 'src/@fuse/services/config.service';
@Directive({
selector: '[fusePerfectScrollbar]'
})
export class FusePerfectScrollbarDirective
implements OnInit, AfterViewInit, OnDestroy {
isInitialized: boolean;
isMobile: boolean;
ps: PerfectScrollbar | any;
// Private
private _animation: number | null;
private _enabled: boolean | '';
private _debouncedUpdate: any;
private _options: any;
private _unsubscribeAll: Subject<any>;
/**
* Constructor
*
* @param {ElementRef} elementRef
* @param {FuseConfigService} _fuseConfigService
* @param {Platform} _platform
* @param {Router} _router
*/
constructor(
public elementRef: ElementRef,
private _fuseConfigService: FuseConfigService,
private _platform: Platform,
private _router: Router
) {
// Set the defaults
this.isInitialized = false;
this.isMobile = false;
// Set the private defaults
this._animation = null;
this._enabled = false;
this._debouncedUpdate = _.debounce(this.update, 150);
this._options = {
updateOnRouteChange: false
};
this._unsubscribeAll = new Subject();
}
// -----------------------------------------------------------------------------------------------------
// @ Accessors
// -----------------------------------------------------------------------------------------------------
/**
* Perfect Scrollbar options
*
* @param value
*/
@Input()
set fusePerfectScrollbarOptions(value) {
// Merge the options
this._options = _.merge({}, this._options, value);
// Destroy and re-init the PerfectScrollbar to update its options
setTimeout(() => {
this._destroy();
});
setTimeout(() => {
this._init();
});
}
get fusePerfectScrollbarOptions(): any {
// Return the options
return this._options;
}
/**
* Is enabled
*
* @param {boolean | ""} value
*/
@Input('fusePerfectScrollbar')
set enabled(value: boolean | '') {
// If nothing is provided with the directive (empty string),
// we will take that as a true
if (value === '') {
value = true;
}
// Return, if both values are the same
if (this.enabled === value) {
return;
}
// Store the value
this._enabled = value;
// If enabled...
if (this.enabled) {
// Init the directive
this._init();
} else {
// Otherwise destroy it
this._destroy();
}
}
get enabled(): boolean | '' {
// Return the enabled status
return this._enabled;
}
// -----------------------------------------------------------------------------------------------------
// @ Lifecycle hooks
// -----------------------------------------------------------------------------------------------------
/**
* On init
*/
ngOnInit(): void {
// Subscribe to window resize event
fromEvent(window, 'resize')
.pipe(
takeUntil(this._unsubscribeAll),
debounceTime(150)
)
.subscribe(() => {
// Update the PerfectScrollbar
this.update();
});
}
/**
* After view init
*/
ngAfterViewInit(): void {
// Check if scrollbars enabled or not from the main config
this._fuseConfigService.config
.pipe(takeUntil(this._unsubscribeAll))
.subscribe(settings => {
this.enabled = settings.customScrollbars;
});
// Scroll to the top on every route change
if (this.fusePerfectScrollbarOptions.updateOnRouteChange) {
this._router.events
.pipe(
takeUntil(this._unsubscribeAll),
filter(event => event instanceof NavigationEnd)
)
.subscribe(() => {
setTimeout(() => {
this.scrollToTop();
this.update();
}, 0);
});
}
}
/**
* On destroy
*/
ngOnDestroy(): void {
this._destroy();
// Unsubscribe from all subscriptions
this._unsubscribeAll.next();
this._unsubscribeAll.complete();
}
// -----------------------------------------------------------------------------------------------------
// @ Private methods
// -----------------------------------------------------------------------------------------------------
/**
* Initialize
*
* @private
*/
_init(): void {
// Return, if already initialized
if (this.isInitialized) {
return;
}
// Check if is mobile
if (this._platform.ANDROID || this._platform.IOS) {
this.isMobile = true;
}
// Return if it's mobile
if (this.isMobile) {
// Return...
return;
}
// Set as initialized
this.isInitialized = true;
// Initialize the perfect-scrollbar
this.ps = new PerfectScrollbar(this.elementRef.nativeElement, {
...this.fusePerfectScrollbarOptions
});
// Unbind 'keydown' events of PerfectScrollbar since it causes an extremely
// high CPU usage on Angular Material inputs.
// Loop through all the event elements of this PerfectScrollbar instance
this.ps.event.eventElements.forEach(eventElement => {
// If we hit to the element with a 'keydown' event...
if (typeof eventElement.handlers['keydown'] !== 'undefined') {
// Unbind it
eventElement.element.removeEventListener(
'keydown',
eventElement.handlers['keydown'][0]
);
}
});
}
/**
* Destroy
*
* @private
*/
_destroy(): void {
if (!this.isInitialized || !this.ps) {
return;
}
// Destroy the perfect-scrollbar
this.ps.destroy();
// Clean up
this.ps = null;
this.isInitialized = false;
}
/**
* Update scrollbars on window resize
*
* @private
*/
@HostListener('window:resize')
_updateOnResize(): void {
this._debouncedUpdate();
}
// -----------------------------------------------------------------------------------------------------
// @ Public methods
// -----------------------------------------------------------------------------------------------------
/**
* Document click
*
* @param {Event} event
*/
@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 the scrollbar
*/
update(): void {
if (!this.isInitialized) {
return;
}
// Update the perfect-scrollbar
this.ps.update();
}
/**
* Destroy the scrollbar
*/
destroy(): void {
this.ngOnDestroy();
}
/**
* Returns the geometry of the scrollable element
*
* @param prefix
*/
geometry(prefix: string = 'scroll'): FusePerfectScrollbarGeometry {
return new FusePerfectScrollbarGeometry(
this.elementRef.nativeElement[prefix + 'Left'],
this.elementRef.nativeElement[prefix + 'Top'],
this.elementRef.nativeElement[prefix + 'Width'],
this.elementRef.nativeElement[prefix + 'Height']
);
}
/**
* Returns the position of the scrollable element
*
* @param absolute
*/
position(absolute: boolean = false): FusePerfectScrollbarPosition {
if (!absolute && this.ps) {
return new FusePerfectScrollbarPosition(
this.ps.reach.x || 0,
this.ps.reach.y || 0
);
} else {
return new FusePerfectScrollbarPosition(
this.elementRef.nativeElement.scrollLeft,
this.elementRef.nativeElement.scrollTop
);
}
}
/**
* Scroll to
*
* @param x
* @param y
* @param speed
*/
scrollTo(x: number, y?: number, speed?: number): void {
if (y == null && speed == null) {
this.animateScrolling('scrollTop', x, speed);
} else {
if (x != null) {
this.animateScrolling('scrollLeft', x, speed);
}
if (y != null) {
this.animateScrolling('scrollTop', y, speed);
}
}
}
/**
* Scroll to X
*
* @param {number} x
* @param {number} speed
*/
scrollToX(x: number, speed?: number): void {
this.animateScrolling('scrollLeft', x, speed);
}
/**
* Scroll to Y
*
* @param {number} y
* @param {number} speed
*/
scrollToY(y: number, speed?: number): void {
this.animateScrolling('scrollTop', y, speed);
}
/**
* Scroll to top
*
* @param {number} offset
* @param {number} speed
*/
scrollToTop(offset?: number, speed?: number): void {
this.animateScrolling('scrollTop', offset || 0, speed);
}
/**
* Scroll to left
*
* @param {number} offset
* @param {number} speed
*/
scrollToLeft(offset?: number, speed?: number): void {
this.animateScrolling('scrollLeft', offset || 0, speed);
}
/**
* Scroll to right
*
* @param {number} offset
* @param {number} speed
*/
scrollToRight(offset?: number, speed?: number): void {
const left =
this.elementRef.nativeElement.scrollWidth -
this.elementRef.nativeElement.clientWidth;
this.animateScrolling('scrollLeft', left - (offset || 0), speed);
}
/**
* Scroll to bottom
*
* @param {number} offset
* @param {number} speed
*/
scrollToBottom(offset?: number, speed?: number): void {
const top =
this.elementRef.nativeElement.scrollHeight -
this.elementRef.nativeElement.clientHeight;
this.animateScrolling('scrollTop', top - (offset || 0), speed);
}
/**
* Scroll to element
*
* @param qs
* @param offset
* @param speed
*/
scrollToElement(qs: string, offset?: number, speed?: number): void {
const element = this.elementRef.nativeElement.querySelector(qs);
if (!element) {
return;
}
const elementPos = element.getBoundingClientRect();
const scrollerPos = this.elementRef.nativeElement.getBoundingClientRect();
if (this.elementRef.nativeElement.classList.contains('ps--active-x')) {
const currentPos = this.elementRef.nativeElement['scrollLeft'];
const position = elementPos.left - scrollerPos.left + currentPos;
this.animateScrolling('scrollLeft', position + (offset || 0), speed);
}
if (this.elementRef.nativeElement.classList.contains('ps--active-y')) {
const currentPos = this.elementRef.nativeElement['scrollTop'];
const position = elementPos.top - scrollerPos.top + currentPos;
this.animateScrolling('scrollTop', position + (offset || 0), speed);
}
}
/**
* Animate scrolling
*
* @param target
* @param value
* @param speed
*/
animateScrolling(target: string, value: number, speed?: number): void {
if (this._animation) {
window.cancelAnimationFrame(this._animation);
this._animation = null;
}
if (!speed || typeof window === 'undefined') {
this.elementRef.nativeElement[target] = value;
} else if (value !== this.elementRef.nativeElement[target]) {
let newValue = 0;
let scrollCount = 0;
let oldTimestamp = performance.now();
let oldValue = this.elementRef.nativeElement[target];
const cosParameter = (oldValue - value) / 2;
const step = (newTimestamp: number) => {
scrollCount += Math.PI / (speed / (newTimestamp - oldTimestamp));
newValue = Math.round(
value + cosParameter + cosParameter * Math.cos(scrollCount)
);
// Only continue animation if scroll position has not changed
if (this.elementRef.nativeElement[target] === oldValue) {
if (scrollCount >= Math.PI) {
this.animateScrolling(target, value, 0);
} else {
this.elementRef.nativeElement[target] = newValue;
// On a zoomed out page the resulting offset may differ
oldValue = this.elementRef.nativeElement[target];
oldTimestamp = newTimestamp;
this._animation = window.requestAnimationFrame(step);
}
}
};
window.requestAnimationFrame(step);
}
}
}

View File

@ -0,0 +1,24 @@
export class FusePerfectScrollbarGeometry {
public x: number;
public y: number;
public w: number;
public h: number;
constructor(x: number, y: number, w: number, h: number) {
this.x = x;
this.y = y;
this.w = w;
this.h = h;
}
}
export class FusePerfectScrollbarPosition {
public x: number | 'start' | 'end';
public y: number | 'start' | 'end';
constructor(x: number | 'start' | 'end', y: number | 'start' | 'end') {
this.x = x;
this.y = y;
}
}

31
src/@fuse/fuse.module.ts Normal file
View File

@ -0,0 +1,31 @@
import {
ModuleWithProviders,
NgModule,
Optional,
SkipSelf
} from '@angular/core';
import { FUSE_CONFIG } from 'src/@fuse/services/config.service';
@NgModule()
export class FuseModule {
constructor(@Optional() @SkipSelf() parentModule: FuseModule) {
if (parentModule) {
throw new Error(
'FuseModule is already loaded. Import it in the AppModule only!'
);
}
}
static forRoot(config): ModuleWithProviders {
return {
ngModule: FuseModule,
providers: [
{
provide: FUSE_CONFIG,
useValue: config
}
]
};
}
}

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