diff --git a/angular.json b/angular.json index d0942847..1955cca0 100644 --- a/angular.json +++ b/angular.json @@ -49,6 +49,10 @@ "vendorChunk": false, "buildOptimizer": true }, + "ec": { + "sourceMap": true, + "extractCss": true + }, "hmr": { "fileReplacements": [ { @@ -71,6 +75,9 @@ "hmr": { "hmr": true, "browserTarget": "fuse:build:hmr" + }, + "ec": { + "browserTarget": "fuse:build:ec" } } }, diff --git a/package-lock.json b/package-lock.json index 52ff6dfe..f3df7b0c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "fuse", - "version": "6.1.2", + "version": "6.2.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -129,9 +129,9 @@ } }, "@angular/cdk": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-6.3.1.tgz", - "integrity": "sha512-TEf1pstDmoLk4C0H45CxpdkBLunNYRQS1pCyWf7hWOHC0M0m130CYZ+rMt4uVDLe4NxiFUk3XwHITB3AuSlCOQ==", + "version": "6.3.3", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-6.3.3.tgz", + "integrity": "sha512-cfUwvnGGByZy/poA75/vsELBq68eDUYJe3qi0WvSbtFzbQlsxeqaBiaxVYqmHJkPlSF5nsUhg5KvDowED3a4sA==", "requires": { "tslib": "^1.7.1" } @@ -373,17 +373,26 @@ "dev": true }, "@angular/material": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/@angular/material/-/material-6.3.1.tgz", - "integrity": "sha512-2M5be3Re1EqTl+i5KuS54P/s+i2AQYlv6swR8ukwXR2arwXy8ewtuwiPjUQulpuEaUkKTQ3f6WndXAgP40Ou7A==", + "version": "6.3.3", + "resolved": "https://registry.npmjs.org/@angular/material/-/material-6.3.3.tgz", + "integrity": "sha512-3qTZ8+pjc8P1D+TLr9ETGfFyMYO+BAQlFiVs3oV8rw5y0Wzkz6G1JHfKQ2oOd8/npXP6rJQldssUM4IBbSOxIQ==", "requires": { + "parse5": "^5.0.0", "tslib": "^1.7.1" + }, + "dependencies": { + "parse5": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.0.0.tgz", + "integrity": "sha512-0ywuiUOnpWWeil5grH2rxjyTJoeQVwyBuO2si6QIU9dWtj2npjuyK1HaY1RbLnVfDhEbhyAPNUBKRK0Xj2xE0w==", + "optional": true + } } }, "@angular/material-moment-adapter": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/@angular/material-moment-adapter/-/material-moment-adapter-6.3.1.tgz", - "integrity": "sha512-rttEepHc/eVEUEErz8eg6063KC79vVN6/rATuoabfgtIqtoSEuft18SkwCt7gQz6lWtuJA1oFc3HIXhN8gxKBw==", + "version": "6.3.3", + "resolved": "https://registry.npmjs.org/@angular/material-moment-adapter/-/material-moment-adapter-6.3.3.tgz", + "integrity": "sha512-YFi1iED8gcHhCwkWGMkyJtHX/tWAsQWnRNrh1AwbWl4JW1739vPAM/OimdIijeGAZ5dVVZX4A4EscCBb74b4dA==", "requires": { "tslib": "^1.7.1" } @@ -7168,9 +7177,9 @@ } }, "ngx-color-picker": { - "version": "6.3.3", - "resolved": "https://registry.npmjs.org/ngx-color-picker/-/ngx-color-picker-6.3.3.tgz", - "integrity": "sha512-0uoxt+3BtanTTbQj9elpsZe/6a/BWAl8jdl0m6OyrCXlCiO3q4zGZjWee4905RF0NXGxQvDWT27coqmDeXGsvw==" + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/ngx-color-picker/-/ngx-color-picker-6.4.0.tgz", + "integrity": "sha512-u4UeeSdZoaGmHJCSlFQGe0xhOLB63rms35IjI7HhHz9MS4VeCQa4UiFMK9Dgjg1FuGt2nEyj/4Fs6tpQfgXEXQ==" }, "ngx-cookie-service": { "version": "1.0.10", diff --git a/package.json b/package.json index a96a888e..afe1f78f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fuse", - "version": "6.1.2", + "version": "6.2.0", "license": "https://themeforest.net/licenses/terms/regular", "scripts": { "ng": "ng", @@ -20,15 +20,15 @@ "dependencies": { "@agm/core": "1.0.0-beta.3", "@angular/animations": "6.0.7", - "@angular/cdk": "6.3.1", + "@angular/cdk": "6.3.3", "@angular/common": "6.0.7", "@angular/compiler": "6.0.7", "@angular/core": "6.0.7", "@angular/flex-layout": "6.0.0-beta.16", "@angular/forms": "6.0.7", "@angular/http": "6.0.7", - "@angular/material": "6.3.1", - "@angular/material-moment-adapter": "6.3.1", + "@angular/material": "6.3.3", + "@angular/material-moment-adapter": "6.3.3", "@angular/platform-browser": "6.0.7", "@angular/platform-browser-dynamic": "6.0.7", "@angular/router": "6.0.7", @@ -52,7 +52,7 @@ "moment": "2.22.2", "ng2-charts": "1.6.0", "ngrx-store-freeze": "0.2.4", - "ngx-color-picker": "6.3.3", + "ngx-color-picker": "6.4.0", "ngx-cookie-service": "1.0.10", "perfect-scrollbar": "1.4.0", "prismjs": "1.15.0", diff --git a/src/@fuse/components/sidebar/sidebar.component.scss b/src/@fuse/components/sidebar/sidebar.component.scss index 0005a506..63a7775a 100644 --- a/src/@fuse/components/sidebar/sidebar.component.scss +++ b/src/@fuse/components/sidebar/sidebar.component.scss @@ -37,12 +37,6 @@ fuse-sidebar { position: absolute !important; top: 0; bottom: 0; - - &:not(.unfolded) { - width: 64px; - min-width: 64px; - max-width: 64px; - } } &.animations-enabled { diff --git a/src/@fuse/components/sidebar/sidebar.component.ts b/src/@fuse/components/sidebar/sidebar.component.ts index a4695d4c..67b5efe2 100644 --- a/src/@fuse/components/sidebar/sidebar.component.ts +++ b/src/@fuse/components/sidebar/sidebar.component.ts @@ -2,7 +2,7 @@ import { ChangeDetectorRef, Component, ElementRef, EventEmitter, HostBinding, Ho import { animate, AnimationBuilder, AnimationPlayer, style } from '@angular/animations'; import { ObservableMedia } from '@angular/flex-layout'; import { Subject } from 'rxjs'; -import { takeUntil } from 'rxjs/internal/operators'; +import { takeUntil } from 'rxjs/operators'; import { FuseSidebarService } from './sidebar.service'; import { FuseMatchMediaService } from '@fuse/services/match-media.service'; @@ -40,6 +40,14 @@ export class FuseSidebarComponent implements OnInit, OnDestroy @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; @@ -92,6 +100,8 @@ export class FuseSidebarComponent implements OnInit, OnDestroy ) { // Set the defaults + this.foldedAutoTriggerOnHover = true; + this.foldedWidth = 64; this.foldedChanged = new EventEmitter(); this.openedChanged = new EventEmitter(); this.opened = false; @@ -108,7 +118,11 @@ export class FuseSidebarComponent implements OnInit, OnDestroy // @ Accessors // ----------------------------------------------------------------------------------------------------- - // Folded + /** + * Folded + * + * @param {boolean} value + */ @Input() set folded(value: boolean) { @@ -121,23 +135,23 @@ export class FuseSidebarComponent implements OnInit, OnDestroy return; } - // Programmatically add/remove margin to the element + // Programmatically add/remove padding to the element // that comes after or before based on the position let sibling, styleRule; - const styleValue = '64px'; + const styleValue = this.foldedWidth + 'px'; // Get the sibling and set the style rule if ( this.position === 'left' ) { sibling = this._elementRef.nativeElement.nextElementSibling; - styleRule = 'margin-left'; + styleRule = 'padding-left'; } else { sibling = this._elementRef.nativeElement.previousElementSibling; - styleRule = 'margin-right'; + styleRule = 'padding-right'; } // If there is no sibling, return... @@ -152,6 +166,11 @@ export class FuseSidebarComponent implements OnInit, OnDestroy // 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, RendererStyleFlags2.Important + RendererStyleFlags2.DashCase); this._renderer.addClass(this._elementRef.nativeElement, 'folded'); @@ -162,6 +181,11 @@ export class FuseSidebarComponent implements OnInit, OnDestroy // 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'); @@ -375,23 +399,23 @@ export class FuseSidebarComponent implements OnInit, OnDestroy return; } - // Programmatically add/remove margin to the element + // Programmatically add/remove padding to the element // that comes after or before based on the position let sibling, styleRule; - const styleValue = '64px'; + const styleValue = this.foldedWidth + 'px'; // Get the sibling and set the style rule if ( this.position === 'left' ) { sibling = this._elementRef.nativeElement.nextElementSibling; - styleRule = 'margin-left'; + styleRule = 'padding-left'; } else { sibling = this._elementRef.nativeElement.previousElementSibling; - styleRule = 'margin-right'; + styleRule = 'padding-right'; } // If there is no sibling, return... @@ -403,6 +427,11 @@ export class FuseSidebarComponent implements OnInit, OnDestroy // 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, RendererStyleFlags2.Important + RendererStyleFlags2.DashCase); this._renderer.addClass(this._elementRef.nativeElement, 'folded'); @@ -633,20 +662,13 @@ export class FuseSidebarComponent implements OnInit, OnDestroy @HostListener('mouseenter') onMouseEnter(): void { - // Only work if the sidebar is folded - if ( !this.folded ) + // Only work if the auto trigger is enabled + if ( !this.foldedAutoTriggerOnHover ) { return; } - // Enable the animations - this._enableAnimations(); - - // Unfold the sidebar temporarily - this.unfolded = true; - - // Mark for check - this._changeDetectorRef.markForCheck(); + this.unfoldTemporarily(); } /** @@ -655,20 +677,13 @@ export class FuseSidebarComponent implements OnInit, OnDestroy @HostListener('mouseleave') onMouseLeave(): void { - // Only work if the sidebar is folded - if ( !this.folded ) + // Only work if the auto trigger is enabled + if ( !this.foldedAutoTriggerOnHover ) { return; } - // Enable the animations - this._enableAnimations(); - - // Fold the sidebar back - this.unfolded = false; - - // Mark for check - this._changeDetectorRef.markForCheck(); + this.foldTemporarily(); } /** @@ -727,4 +742,58 @@ export class FuseSidebarComponent implements OnInit, OnDestroy 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(); + } } diff --git a/src/@fuse/components/theme-options/theme-options.component.html b/src/@fuse/components/theme-options/theme-options.component.html index c6a6cc75..00a10741 100644 --- a/src/@fuse/components/theme-options/theme-options.component.html +++ b/src/@fuse/components/theme-options/theme-options.component.html @@ -73,6 +73,17 @@ Right +

Variant:

+ + Style 1 + Style 2 + + +

Color:

+ + + @@ -91,6 +102,11 @@ Below Fixed +

Color:

+ + + @@ -109,6 +125,11 @@ Below Fixed +

Color:

+ + + @@ -146,6 +167,17 @@ Right +

Variant:

+ + Style 1 + Style 2 + + +

Color:

+ + + @@ -164,6 +196,11 @@ Below +

Color:

+ + + @@ -182,6 +219,11 @@ Below +

Color:

+ + + @@ -219,6 +261,17 @@ Right +

Variant:

+ + Style 1 + Style 2 + + +

Color:

+ + + @@ -236,6 +289,11 @@ Above Fixed +

Color:

+ + + @@ -253,6 +311,11 @@ Above Fixed +

Color:

+ + + @@ -285,6 +348,17 @@ Top +

Variant (Vertical):

+ + Style 1 + Style 2 + + +

Color:

+ + + @@ -302,6 +376,11 @@ Below +

Color:

+ + + @@ -319,6 +398,11 @@ Above Static +

Color:

+ + + @@ -338,35 +422,6 @@ - -
- -

Colors

- -
- -
-

Toolbar Color

- -
- -
-

Navbar Color

- -
- -
-

Footer Color

- -
- -
- -
- diff --git a/src/@fuse/components/theme-options/theme-options.component.scss b/src/@fuse/components/theme-options/theme-options.component.scss index 3024ff25..8199e635 100644 --- a/src/@fuse/components/theme-options/theme-options.component.scss +++ b/src/@fuse/components/theme-options/theme-options.component.scss @@ -73,11 +73,6 @@ } } } - - .colors { - display: block !important; - width: 100%; - } } } } diff --git a/src/@fuse/components/theme-options/theme-options.component.ts b/src/@fuse/components/theme-options/theme-options.component.ts index ebfbcac5..2243e1ea 100644 --- a/src/@fuse/components/theme-options/theme-options.component.ts +++ b/src/@fuse/components/theme-options/theme-options.component.ts @@ -65,20 +65,21 @@ export class FuseThemeOptionsComponent implements OnInit, OnDestroy style : new FormControl(), width : new FormControl(), navbar : this._formBuilder.group({ + background: new FormControl(), + folded : new FormControl(), hidden : new FormControl(), position : new FormControl(), - folded : new FormControl(), - background: new FormControl() + variant : new FormControl() }), toolbar: this._formBuilder.group({ + background: new FormControl(), hidden : new FormControl(), - position : new FormControl(), - background: new FormControl() + position : new FormControl() }), footer : this._formBuilder.group({ + background: new FormControl(), hidden : new FormControl(), - position : new FormControl(), - background: new FormControl() + position : new FormControl() }) }), customScrollbars: new FormControl() @@ -174,20 +175,21 @@ export class FuseThemeOptionsComponent implements OnInit, OnDestroy layout: { width : 'fullwidth', navbar : { + background: 'mat-fuse-dark-700-bg', + folded : false, hidden : false, position : 'left', - folded : false, - background: 'mat-fuse-dark-700-bg' + variant : 'vertical-style-1' }, toolbar: { + background: 'mat-white-500-bg', hidden : false, - position : 'below-static', - background: 'mat-white-500-bg' + position : 'below-static' }, footer : { + background: 'mat-fuse-dark-900-bg', hidden : false, - position : 'below-static', - background: 'mat-fuse-dark-900-bg' + position : 'below-static' } } }); @@ -202,20 +204,21 @@ export class FuseThemeOptionsComponent implements OnInit, OnDestroy layout: { width : 'fullwidth', navbar : { + background: 'mat-fuse-dark-700-bg', + folded : false, hidden : false, position : 'left', - folded : false, - background: 'mat-fuse-dark-700-bg' + variant : 'vertical-style-1' }, toolbar: { + background: 'mat-white-500-bg', hidden : false, - position : 'below', - background: 'mat-white-500-bg' + position : 'below' }, footer : { + background: 'mat-fuse-dark-900-bg', hidden : false, - position : 'below', - background: 'mat-fuse-dark-900-bg' + position : 'below' } } }); @@ -230,20 +233,21 @@ export class FuseThemeOptionsComponent implements OnInit, OnDestroy layout: { width : 'fullwidth', navbar : { + background: 'mat-fuse-dark-700-bg', + folded : false, hidden : false, position : 'left', - folded : false, - background: 'mat-fuse-dark-700-bg' + layout : 'vertical-style-1' }, toolbar: { + background: 'mat-white-500-bg', hidden : false, - position : 'above-static', - background: 'mat-white-500-bg' + position : 'above-static' }, footer : { + background: 'mat-fuse-dark-900-bg', hidden : false, - position : 'above-static', - background: 'mat-fuse-dark-900-bg' + position : 'above-static' } } }); @@ -258,20 +262,21 @@ export class FuseThemeOptionsComponent implements OnInit, OnDestroy layout: { width : 'fullwidth', navbar : { + background: 'mat-fuse-dark-700-bg', + folded : false, hidden : false, position : 'top', - folded : false, - background: 'mat-fuse-dark-700-bg' + variant : 'vertical-style-1' }, toolbar: { + background: 'mat-white-500-bg', hidden : false, - position : 'above', - background: 'mat-white-500-bg' + position : 'above' }, footer : { + background: 'mat-fuse-dark-900-bg', hidden : false, - position : 'above-fixed', - background: 'mat-fuse-dark-900-bg' + position : 'above-fixed' } } }); diff --git a/src/@fuse/scss/partials/_colors.scss b/src/@fuse/scss/partials/_colors.scss index 7b98456d..cc864cde 100644 --- a/src/@fuse/scss/partials/_colors.scss +++ b/src/@fuse/scss/partials/_colors.scss @@ -14,7 +14,7 @@ i { } // Material colors map -$matColorsMap: ( +$matPalettes: ( primary: $primary, accent: $accent, warn: $warn, @@ -43,75 +43,91 @@ $matColorsMap: ( ); // Material color hues list -$matColorHues: 50, 100, 200, 300, 400, 500, 600, 700, 800, 900, A100, A200, A400, A700; +$matHues: 50, 100, 200, 300, 400, 500, 600, 700, 800, 900, A100, A200, A400, A700; // Text color levels generator mixin -@mixin generateTextColorLevels($baseTextColor) { +@mixin generateTextColorLevels($classes, $contrast) { - // If the base text color is black... - @if (rgba(black, 1) == rgba($baseTextColor, 1)) { + // If the contrast is dark... + @if ($contrast == 'dark') { - i, - .icon { - color: rgba(0, 0, 0, 0.54); - } + // Put down the color classes + #{$classes} { - &.secondary-text, - .secondary-text { - color: rgba(0, 0, 0, 0.54) !important; - } + i, + .icon { + color: rgba(0, 0, 0, 0.54); + } - &.hint-text, - .hint-text, - &.disabled-text, - .disabled-text { - color: rgba(0, 0, 0, 0.38) !important; - } + &.secondary-text, + .secondary-text { + color: rgba(0, 0, 0, 0.54) !important; + } - &.divider, - .divider { - color: rgba(0, 0, 0, 0.12) !important; - } + &.hint-text, + .hint-text, + &.disabled-text, + .disabled-text { + color: rgba(0, 0, 0, 0.38) !important; + } - .mat-ripple-element { - background: rgba(0, 0, 0, 0.1); + &.divider, + .divider { + color: rgba(0, 0, 0, 0.12) !important; + } + + .mat-ripple-element { + background: rgba(0, 0, 0, 0.1); + } + + .adaptive-border-color { + border-color: rgba(0, 0, 0, 0.12); + } } } // If the base text color is white... @else { - i, - .icon { - color: rgba(255, 255, 255, 1); - } + // Put down the color classes + #{$classes} { - &.secondary-text, - .secondary-text { - color: rgba(255, 255, 255, 0.70) !important; - } + i, + .icon { + color: rgba(255, 255, 255, 1); + } - &.hint-text, - .hint-text, - &.disabled-text, - .disabled-text { - color: rgba(255, 255, 255, 0.50) !important; - } + &.secondary-text, + .secondary-text { + color: rgba(255, 255, 255, 0.70) !important; + } - &.divider, - .divider { - color: rgba(255, 255, 255, 0.12) !important; - } + &.hint-text, + .hint-text, + &.disabled-text, + .disabled-text { + color: rgba(255, 255, 255, 0.50) !important; + } - .mat-ripple-element { - background: rgba(255, 255, 255, 0.1); + &.divider, + .divider { + color: rgba(255, 255, 255, 0.12) !important; + } + + .mat-ripple-element { + background: rgba(255, 255, 255, 0.1); + } + + .adaptive-border-color { + border-color: rgba(255, 255, 255, 0.12); + } } } } -@mixin generateMaterialElementColors($contrastColor) { +@mixin generateMaterialElementColors($classes, $contrast) { - // If the contrast color is white... + // If the contrast color is light... $fuseForeground: ( base: white, text: white, @@ -119,8 +135,8 @@ $matColorHues: 50, 100, 200, 300, 400, 500, 600, 700, 800, 900, A100, A200, A400 divider: rgba(white, 0.12), ); - // If the contrast color is black... - @if (rgba(black, 1) == rgba($contrastColor, 1)) { + // If the contrast color is dark... + @if ($contrast == 'dark') { $fuseForeground: ( base: black, @@ -129,43 +145,47 @@ $matColorHues: 50, 100, 200, 300, 400, 500, 600, 700, 800, 900, A100, A200, A400 ); } - // Native Input - input[type="text"] { - color: map_get($fuseForeground, base); - } + // Put down the color classes + #{$classes} { - // Input - .mat-form-field-label { - color: map_get($fuseForeground, hint-text); - } + // Native Input + input[type="text"] { + color: map_get($fuseForeground, base); + } - .mat-form-field-underline { - background-color: map_get($fuseForeground, divider); - } + // Input + .mat-form-field-label { + color: map_get($fuseForeground, hint-text); + } - // Select - .mat-select-trigger, - .mat-select-arrow { - color: map_get($fuseForeground, hint-text); - } + .mat-form-field-underline { + background-color: map_get($fuseForeground, divider); + } - .mat-select-underline { - background-color: map_get($fuseForeground, divider); - } + // Select + .mat-select-trigger, + .mat-select-arrow { + color: map_get($fuseForeground, hint-text); + } - .mat-select-disabled .mat-select-value, - .mat-select-arrow, - .mat-select-trigger { - color: map_get($fuseForeground, hint-text); - } + .mat-select-underline { + background-color: map_get($fuseForeground, divider); + } - .mat-select-content, - .mat-select-panel-done-animating { - background: map_get($background, card); - } + .mat-select-disabled .mat-select-value, + .mat-select-arrow, + .mat-select-trigger { + color: map_get($fuseForeground, hint-text); + } - .mat-select-value { - color: map_get($fuseForeground, text); + .mat-select-content, + .mat-select-panel-done-animating { + background: map_get($background, card); + } + + .mat-select-value { + color: map_get($fuseForeground, text); + } } } @@ -180,14 +200,6 @@ $matColorHues: 50, 100, 200, 300, 400, 500, 600, 700, 800, 900, A100, A200, A400 background-color: $color !important; color: $contrastColor !important; - // Generate text color levels - // based on current contrast color - @include generateTextColorLevels($contrastColor); - - // Generate material element colors - // based on current contrast color - @include generateMaterialElementColors($contrastColor); - &[disabled] { background-color: rgba($color, .12) !important; color: rgba($contrastColor, .26) !important; @@ -196,14 +208,6 @@ $matColorHues: 50, 100, 200, 300, 400, 500, 600, 700, 800, 900, A100, A200, A400 .#{$colorName}#{$hue}-fg { color: $color !important; - - // Generate text color levels - // based on current contrast color - @include generateTextColorLevels($color); - - // Generate material element colors - // based on current contrast color - @include generateMaterialElementColors($color); } .#{$colorName}#{$hue}-border { @@ -229,49 +233,126 @@ $matColorHues: 50, 100, 200, 300, 400, 500, 600, 700, 800, 900, A100, A200, A400 @mixin generateFuseColorClasses($primary, $accent, $warn) { - $colorMap: ( + $palettes: ( primary: $primary, accent: $accent, warn: $warn ); + // Define contrast lists + $light-contrasting-classes: (); + $dark-contrasting-classes: (); + // Generate the color classes... - @each $name, $map in $colorMap { + @each $paletteName, $palette in $palettes { - @each $hue in $matColorHues { + // Get the contrasts map + $contrasts: map-get($palette, 'contrast'); - $color: map-get($map, $hue); - $contrastColor: map-get(map-get($map, 'contrast'), $hue); + @each $hue in $matHues { - @if ($color != null and $contrastColor != null) { + // Get the color and the contrasting color + $color: map-get($palette, $hue); + $contrast: map-get($contrasts, $hue); - @include generateColorClasses($name, $color, $contrastColor, '-#{$hue}'); + @if ($color != null and $contrast != null) { + + // Generate color classes + @include generateColorClasses($paletteName, $color, $contrast, '-#{$hue}'); + + // If the contrast color is dark + @if (rgba(black, 1) == rgba($contrast, 1)) { + $dark-contrasting-classes: append($dark-contrasting-classes, unquote('.mat-#{$paletteName}-#{$hue}-bg'), 'comma'); + } + // if the contrast color is light + @else { + $light-contrasting-classes: append($light-contrasting-classes, unquote('.mat-#{$paletteName}-#{$hue}-bg'), 'comma'); + } // Run the generator one more time for default values (500) @if ($hue == 500) { - @include generateColorClasses($name, $color, $contrastColor, ''); + + // Generate color classes + @include generateColorClasses($paletteName, $color, $contrast, ''); + + // Add color to the correct list depending on the contrasting color + + // If the contrast color is dark + @if (rgba(black, 1) == rgba($contrast, 1)) { + $dark-contrasting-classes: append($dark-contrasting-classes, unquote('.mat-#{$paletteName}-bg'), 'comma'); + } + // if the contrast color is light + @else { + $light-contrasting-classes: append($light-contrasting-classes, unquote('.mat-#{$paletteName}-bg'), 'comma'); + } + } + } + } + } + + // Generate contrasting colors + @include generateTextColorLevels($dark-contrasting-classes, 'dark'); + @include generateTextColorLevels($light-contrasting-classes, 'light'); + @include generateMaterialElementColors($dark-contrasting-classes, 'dark'); + @include generateMaterialElementColors($light-contrasting-classes, 'light'); +} + +// Generate the color classes... + +// Define contrast lists +$light-contrasting-classes: (); +$dark-contrasting-classes: (); + +@each $paletteName, $palette in $matPalettes { + + // Get the contrasts map + $contrasts: map-get($palette, 'contrast'); + + @each $hue in $matHues { + + // Get the color and the contrasting color + $color: map-get($palette, $hue); + $contrast: map-get($contrasts, $hue); + + @if ($color != null and $contrast != null) { + + // Generate color classes + @include generateColorClasses($paletteName, $color, $contrast, '-#{$hue}'); + + // Add color to the correct list depending on the contrasting color + + // If the contrast color is dark + @if (rgba(black, 1) == rgba($contrast, 1)) { + $dark-contrasting-classes: append($dark-contrasting-classes, unquote('.mat-#{$paletteName}-#{$hue}-bg'), 'comma'); + } + // if the contrast color is light + @else { + $light-contrasting-classes: append($light-contrasting-classes, unquote('.mat-#{$paletteName}-#{$hue}-bg'), 'comma'); + } + + // Run the generator one more time for default values (500) + @if ($hue == 500) { + + // Generate color classes + @include generateColorClasses($paletteName, $color, $contrast, ''); + + // Add color to the correct list depending on the contrasting color + + // If the contrast color is dark + @if (rgba(black, 1) == rgba($contrast, 1)) { + $dark-contrasting-classes: append($dark-contrasting-classes, unquote('.mat-#{$paletteName}-bg'), 'comma'); + } + // if the contrast color is light + @else { + $light-contrasting-classes: append($light-contrasting-classes, unquote('.mat-#{$paletteName}-bg'), 'comma'); } } } } } -// Generate the color classes... -@each $colorName, $colorMap in $matColorsMap { - - @each $hue in $matColorHues { - - $color: map-get($colorMap, $hue); - $contrastColor: map-get(map-get($colorMap, 'contrast'), $hue); - - @if ($color != null and $contrastColor != null) { - - @include generateColorClasses($colorName, $color, $contrastColor, '-#{$hue}'); - - // Run the generator one more time for default values (500) - @if ($hue == 500) { - @include generateColorClasses($colorName, $color, $contrastColor, ''); - } - } - } -} +// Generate contrasting colors +@include generateTextColorLevels($dark-contrasting-classes, 'dark'); +@include generateTextColorLevels($light-contrasting-classes, 'light'); +@include generateMaterialElementColors($dark-contrasting-classes, 'dark'); +@include generateMaterialElementColors($light-contrasting-classes, 'light'); diff --git a/src/@fuse/scss/partials/_navigation.scss b/src/@fuse/scss/partials/_navigation.scss index df7b1be2..f9fb868d 100644 --- a/src/@fuse/scss/partials/_navigation.scss +++ b/src/@fuse/scss/partials/_navigation.scss @@ -219,4 +219,26 @@ } } } + + // Material style + &.material { + + .nav-subheader { + border-top: 1px solid rgba(0, 0, 0, 0.12); + + &:first-child { + border-top: none; + } + } + + .nav-item { + + .nav-link { + height: 40px; + padding: 0 16px; + margin: 4px 8px; + border-radius: 4px; + } + } + } } diff --git a/src/@fuse/types/fuse-config.ts b/src/@fuse/types/fuse-config.ts index b0d511e2..6ad44ba4 100644 --- a/src/@fuse/types/fuse-config.ts +++ b/src/@fuse/types/fuse-config.ts @@ -4,20 +4,21 @@ export interface FuseConfig style: string, width: 'fullwidth' | 'boxed', navbar: { + background: string, hidden: boolean, folded: boolean, position: 'left' | 'right' | 'top', - background: string + variant: string }, toolbar: { + background: string, hidden: boolean, - position: 'above' | 'above-static' | 'above-fixed' | 'below' | 'below-static' | 'below-fixed', - background: string + position: 'above' | 'above-static' | 'above-fixed' | 'below' | 'below-static' | 'below-fixed' } footer: { + background: string, hidden: boolean, - position: 'above' | 'above-static' | 'above-fixed' | 'below' | 'below-static' | 'below-fixed', - background: string + position: 'above' | 'above-static' | 'above-fixed' | 'below' | 'below-static' | 'below-fixed' } }; customScrollbars: boolean; diff --git a/src/app/app.component.scss b/src/app/app.component.scss index 42a78d3d..e04a412f 100644 --- a/src/app/app.component.scss +++ b/src/app/app.component.scss @@ -1,3 +1,5 @@ +@import "src/@fuse/scss/fuse"; + :host { position: relative; display: flex; @@ -9,7 +11,7 @@ .theme-options-button { position: absolute; top: 160px; - right: 0; + right: 70px; width: 48px; height: 48px; line-height: 48px; @@ -18,9 +20,13 @@ border-radius: 0; margin: 0; pointer-events: auto; - opacity: .75; + opacity: .90; z-index: 998; + @include media-breakpoint-down('md') { + right: 0; + } + mat-icon { animation: rotating 3s linear infinite; } diff --git a/src/app/fake-db/chat-panel.ts b/src/app/fake-db/chat-panel.ts new file mode 100644 index 00000000..ec738bef --- /dev/null +++ b/src/app/fake-db/chat-panel.ts @@ -0,0 +1,343 @@ +export class ChatPanelFakeDb +{ + public static contacts = [ + { + 'id' : '5725a680b3249760ea21de52', + 'name' : 'Alice Freeman', + 'avatar': 'assets/images/avatars/alice.jpg', + 'status': 'online', + 'mood' : 'Lorem ipsum dolor sit amet, consectetur adipiscing elit...', + 'unread': '2' + }, + { + 'id' : '5725a680606588342058356d', + 'name' : 'Arnold', + 'avatar': 'assets/images/avatars/Arnold.jpg', + 'status': 'do-not-disturb', + 'mood' : 'Lorem ipsum dolor sit amet, consectetur adipiscing elit...' + }, + { + 'id' : '5725a68009e20d0a9e9acf2a', + 'name' : 'Barrera', + 'avatar': 'assets/images/avatars/Barrera.jpg', + 'status': 'do-not-disturb', + 'mood' : 'Lorem ipsum dolor sit amet, consectetur adipiscing elit...' + }, + { + 'id' : '5725a6809fdd915739187ed5', + 'name' : 'Blair', + 'avatar': 'assets/images/avatars/Blair.jpg', + 'status': 'offline', + 'mood' : 'Lorem ipsum dolor sit amet, consectetur adipiscing elit...', + 'unread': '3' + }, + { + 'id' : '5725a68007920cf75051da64', + 'name' : 'Boyle', + 'avatar': 'assets/images/avatars/Boyle.jpg', + 'status': 'offline', + 'mood' : 'Lorem ipsum dolor sit amet, consectetur adipiscing elit...', + 'unread': '1' + }, + { + 'id' : '5725a68031fdbb1db2c1af47', + 'name' : 'Christy', + 'avatar': 'assets/images/avatars/Christy.jpg', + 'status': 'offline', + 'mood' : 'Lorem ipsum dolor sit amet, consectetur adipiscing elit...' + }, + { + 'id' : '5725a680bc670af746c435e2', + 'name' : 'Copeland', + 'avatar': 'assets/images/avatars/Copeland.jpg', + 'status': 'online', + 'mood' : 'Lorem ipsum dolor sit amet, consectetur adipiscing elit...' + }, + { + 'id' : '5725a680e7eb988a58ddf303', + 'name' : 'Estes', + 'avatar': 'assets/images/avatars/Estes.jpg', + 'status': 'away', + 'mood' : 'Lorem ipsum dolor sit amet, consectetur adipiscing elit...' + }, + { + 'id' : '5725a680dcb077889f758961', + 'name' : 'Harper', + 'avatar': 'assets/images/avatars/Harper.jpg', + 'status': 'offline', + 'mood' : 'Lorem ipsum dolor sit amet, consectetur adipiscing elit...' + }, + { + 'id' : '5725a6806acf030f9341e925', + 'name' : 'Helen', + 'avatar': 'assets/images/avatars/Helen.jpg', + 'status': 'away', + 'mood' : 'Lorem ipsum dolor sit amet, consectetur adipiscing elit...' + }, + { + 'id' : '5725a680ae1ae9a3c960d487', + 'name' : 'Henderson', + 'avatar': 'assets/images/avatars/Henderson.jpg', + 'status': 'offline', + 'mood' : 'Lorem ipsum dolor sit amet, consectetur adipiscing elit...' + }, + { + 'id' : '5725a680b8d240c011dd224b', + 'name' : 'Josefina', + 'avatar': 'assets/images/avatars/Josefina.jpg', + 'status': 'online', + 'mood' : 'Lorem ipsum dolor sit amet, consectetur adipiscing elit...' + }, + { + 'id' : '5725a68034cb3968e1f79eac', + 'name' : 'Katina', + 'avatar': 'assets/images/avatars/Katina.jpg', + 'status': 'away', + 'mood' : 'Lorem ipsum dolor sit amet, consectetur adipiscing elit...' + }, + { + 'id' : '5725a6801146cce777df2a08', + 'name' : 'Lily', + 'avatar': 'assets/images/avatars/Lily.jpg', + 'status': 'do-not-disturb', + 'mood' : 'Lorem ipsum dolor sit amet, consectetur adipiscing elit...', + 'unread': '10' + }, + { + 'id' : '5725a6808a178bfd034d6ecf', + 'name' : 'Mai', + 'avatar': 'assets/images/avatars/Mai.jpg', + 'status': 'away', + 'mood' : 'Lorem ipsum dolor sit amet, consectetur adipiscing elit...' + }, + { + 'id' : '5725a680653c265f5c79b5a9', + 'name' : 'Nancy', + 'avatar': 'assets/images/avatars/Nancy.jpg', + 'status': 'do-not-disturb', + 'mood' : 'Lorem ipsum dolor sit amet, consectetur adipiscing elit...' + }, + { + 'id' : '5725a680bbcec3cc32a8488a', + 'name' : 'Nora', + 'avatar': 'assets/images/avatars/Nora.jpg', + 'status': 'do-not-disturb', + 'mood' : 'Lorem ipsum dolor sit amet, consectetur adipiscing elit...', + 'unread': '7' + }, + { + 'id' : '5725a6803d87f1b77e17b62b', + 'name' : 'Odessa', + 'avatar': 'assets/images/avatars/Odessa.jpg', + 'status': 'away', + 'mood' : 'Lorem ipsum dolor sit amet, consectetur adipiscing elit...', + 'unread': '1' + }, + { + 'id' : '5725a680e87cb319bd9bd673', + 'name' : 'Reyna', + 'avatar': 'assets/images/avatars/Reyna.jpg', + 'status': 'offline', + 'mood' : 'Lorem ipsum dolor sit amet, consectetur adipiscing elit...' + }, + { + 'id' : '5725a6802d10e277a0f35775', + 'name' : 'Shauna', + 'avatar': 'assets/images/avatars/Shauna.jpg', + 'status': 'online', + 'mood' : 'Lorem ipsum dolor sit amet, consectetur adipiscing elit...' + }, + { + 'id' : '5725a680aef1e5cf26dd3d1f', + 'name' : 'Shepard', + 'avatar': 'assets/images/avatars/Shepard.jpg', + 'status': 'online', + 'mood' : 'Lorem ipsum dolor sit amet, consectetur adipiscing elit...' + }, + { + 'id' : '5725a680cd7efa56a45aea5d', + 'name' : 'Tillman', + 'avatar': 'assets/images/avatars/Tillman.jpg', + 'status': 'do-not-disturb', + 'mood' : '', + 'unread': '99+' + }, + { + 'id' : '5725a680fb65c91a82cb35e2', + 'name' : 'Trevino', + 'avatar': 'assets/images/avatars/Trevino.jpg', + 'status': 'away', + 'mood' : 'Lorem ipsum dolor sit amet, consectetur adipiscing elit...' + }, + { + 'id' : '5725a68018c663044be49cbf', + 'name' : 'Tyson', + 'avatar': 'assets/images/avatars/Tyson.jpg', + 'status': 'do-not-disturb', + 'mood' : 'Lorem ipsum dolor sit amet, consectetur adipiscing elit...' + }, + { + 'id' : '5725a6809413bf8a0a5272b1', + 'name' : 'Velazquez', + 'avatar': 'assets/images/avatars/Velazquez.jpg', + 'status': 'online', + 'mood' : 'Lorem ipsum dolor sit amet, consectetur adipiscing elit...' + } + ]; + + public static chats = [ + { + 'id' : '1725a680b3249760ea21de52', + 'dialog': [ + { + 'who' : '5725a680b3249760ea21de52', + 'message': 'Quickly come to the meeting room 1B, we have a big server issue', + 'time' : '2017-03-22T08:54:28.299Z' + }, + { + 'who' : '5725a6802d10e277a0f35724', + 'message': 'I’m having breakfast right now, can’t you wait for 10 minutes?', + 'time' : '2017-03-22T08:55:28.299Z' + }, + { + 'who' : '5725a6802d10e277a0f35724', + 'message': 'I’m having breakfast right now, can’t you wait for 10 minutes?', + 'time' : '2017-03-22T08:55:28.299Z' + }, + { + 'who' : '5725a680b3249760ea21de52', + 'message': 'We are losing money! Quick!', + 'time' : '2017-03-22T09:00:28.299Z' + }, + { + 'who' : '5725a6802d10e277a0f35724', + 'message': 'It’s not my money, you know. I will eat my breakfast and then I will come to the meeting room.', + 'time' : '2017-03-22T09:02:28.299Z' + }, + { + 'who' : '5725a680b3249760ea21de52', + 'message': 'You are the worst!', + 'time' : '2017-03-22T09:05:28.299Z' + }, + { + 'who' : '5725a680b3249760ea21de52', + 'message': 'We are losing money! Quick!', + 'time' : '2017-03-22T09:15:28.299Z' + }, + { + 'who' : '5725a680b3249760ea21de52', + 'message': 'You are the worst!', + 'time' : '2017-03-22T09:05:28.299Z' + }, + { + 'who' : '5725a680b3249760ea21de52', + 'message': 'We are losing money! Quick!', + 'time' : '2017-03-22T09:15:28.299Z' + }, + { + 'who' : '5725a6802d10e277a0f35724', + 'message': 'It’s not my money, you know. I will eat my breakfast and then I will come to the meeting room.', + 'time' : '2017-03-22T09:20:28.299Z' + }, + { + 'who' : '5725a680b3249760ea21de52', + 'message': 'You are the worst!', + 'time' : '2017-03-22T09:22:28.299Z' + }, + { + 'who' : '5725a680b3249760ea21de52', + 'message': 'We are losing money! Quick!', + 'time' : '2017-03-22T09:25:28.299Z' + }, + { + 'who' : '5725a6802d10e277a0f35724', + 'message': 'It’s not my money, you know. I will eat my breakfast and then I will come to the meeting room.', + 'time' : '2017-03-22T09:27:28.299Z' + }, + { + 'who' : '5725a680b3249760ea21de52', + 'message': 'You are the worst!', + 'time' : '2017-03-22T09:33:28.299Z' + }, + { + 'who' : '5725a680b3249760ea21de52', + 'message': 'You are the worst!', + 'time' : '2017-03-22T09:33:28.299Z' + }, + { + 'who' : '5725a680b3249760ea21de52', + 'message': 'We are losing money! Quick!', + 'time' : '2017-03-22T09:35:28.299Z' + }, + { + 'who' : '5725a6802d10e277a0f35724', + 'message': 'It’s not my money, you know. I will eat my breakfast and then I will come to the meeting room.', + 'time' : '2017-03-22T09:45:28.299Z' + }, + { + 'who' : '5725a680b3249760ea21de52', + 'message': 'You are the worst!', + 'time' : '2017-03-22T10:00:28.299Z' + } + ] + }, + { + 'id' : '2725a680b8d240c011dd2243', + 'dialog': [ + { + 'who' : '5725a680606588342058356d', + 'message': 'Quickly come to the meeting room 1B, we have a big server issue', + 'time' : '2017-04-22T01:00:00.299Z' + }, + { + 'who' : '5725a6802d10e277a0f35724', + 'message': 'I’m having breakfast right now, can’t you wait for 10 minutes?', + 'time' : '2017-04-22T01:05:00.299Z' + }, + { + 'who' : '5725a680606588342058356d', + 'message': 'We are losing money! Quick!', + 'time' : '2017-04-22T01:10:00.299Z' + } + ] + }, + { + 'id' : '3725a6809413bf8a0a5272b4', + 'dialog': [ + { + 'who' : '5725a68009e20d0a9e9acf2a', + 'message': 'Quickly come to the meeting room 1B, we have a big server issue', + 'time' : '2017-04-22T02:10:00.299Z' + } + ] + } + ]; + + public static user = [ + { + 'id' : '5725a6802d10e277a0f35724', + 'name' : 'John Doe', + 'avatar' : 'assets/images/avatars/profile.jpg', + 'status' : 'online', + 'mood' : '', + 'chatList': [ + { + 'chatId' : '1725a680b3249760ea21de52', + 'contactId' : '5725a680b3249760ea21de52', + 'lastMessageTime': '2017-06-12T02:10:18.931Z' + }, + { + 'chatId' : '2725a680b8d240c011dd2243', + 'contactId' : '5725a680606588342058356d', + 'lastMessageTime': '2017-02-18T10:30:18.931Z' + }, + { + 'chatId' : '3725a6809413bf8a0a5272b4', + 'contactId' : '5725a68009e20d0a9e9acf2a', + 'lastMessageTime': '2017-03-18T12:30:18.931Z' + } + ] + } + ]; + +} diff --git a/src/app/fuse-config/index.ts b/src/app/fuse-config/index.ts index 4b9e9adc..aac07997 100644 --- a/src/app/fuse-config/index.ts +++ b/src/app/fuse-config/index.ts @@ -10,23 +10,24 @@ import { FuseConfig } from '@fuse/types'; export const fuseConfig: FuseConfig = { layout : { - style : 'vertical-layout-1', - width : 'fullwidth', - navbar : { + style : 'vertical-layout-1', + width : 'fullwidth', + navbar : { + background: 'mat-fuse-dark-700-bg', + folded : false, hidden : false, position : 'left', - folded : false, - background: 'mat-fuse-dark-700-bg' + variant : 'vertical-style-1' }, - toolbar : { + toolbar: { + background: 'mat-white-500-bg', hidden : false, - position : 'below-static', - background: 'mat-white-500-bg' + position : 'below-static' }, - footer : { + footer : { + background: 'mat-fuse-dark-900-bg', hidden : false, - position : 'below-static', - background: 'mat-fuse-dark-900-bg' + position : 'below-fixed' } }, customScrollbars: true diff --git a/src/app/layout/components/chat-panel/chat-panel.component.html b/src/app/layout/components/chat-panel/chat-panel.component.html new file mode 100644 index 00000000..585f2a8c --- /dev/null +++ b/src/app/layout/components/chat-panel/chat-panel.component.html @@ -0,0 +1,119 @@ +
+ + + +
+ chat +

Team Chat

+
+ +
+ + + +
+ +

{{selectedContact.name}}

+
+ +
+ + + + + +
+ +
+ + + + + + + +
{{contact.unread}}
+
+ +
+ +
+ + + +
+ +
+ + + +
+ + + +
+
{{message.message}}
+
{{message.time | date:'short'}}
+
+ +
+ +
+ + + +
+ chat +
+ +
+ Start a conversation by typing your message below. +
+
+ +
+ +
+ +
+ + + + + + + +
+ +
+ +
+ + +
\ No newline at end of file diff --git a/src/app/layout/components/chat-panel/chat-panel.component.scss b/src/app/layout/components/chat-panel/chat-panel.component.scss new file mode 100644 index 00000000..93b0d321 --- /dev/null +++ b/src/app/layout/components/chat-panel/chat-panel.component.scss @@ -0,0 +1,374 @@ +@import "src/@fuse/scss/fuse"; + +chat-panel { + display: flex; + flex-direction: column; + flex: 1 1 auto; + width: 360px; + min-width: 360px; + max-width: 360px; + z-index: 99; + overflow: hidden; + + .header { + height: 64px; + max-height: 64px; + min-height: 64px; + + .title { + + mat-icon { + margin-left: 4px; + } + + h3 { + max-width: 120px; + transition: opacity 300ms ease-in-out; + } + } + } + + #contacts-list { + padding: 8px 0; + overflow: auto; + width: 72px; + min-width: 72px; + max-width: 72px; + + // Perfect scrollbar + .ps__rail-y { + width: 3px !important; + + .ps__thumb-y { + width: 3px !important; + } + } + + .mat-list-item { + cursor: pointer; + position: relative; + + &.active { + background-color: mat-color(mat-palette($mat-grey, 300)); + @include mat-elevation(2); + } + + &.offline { + + .mat-list-item-content { + + img { + filter: grayscale(100%); + opacity: 0.7; + } + + h3 { + opacity: 0.7; + } + } + } + + .mat-list-item-content { + + .unread-count { + position: absolute; + min-width: 18px; + height: 18px; + top: 4px; + left: 10px; + border-radius: 9px; + padding: 0 5px; + font-size: 11px; + text-align: center; + display: flex; + align-items: center; + justify-content: center; + background-color: mat-color(mat-palette($mat-indigo)); + color: white; + box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.35); + } + + .status-icon { + position: absolute; + width: 12px; + height: 12px; + bottom: 3px; + left: 44px; + border: 2px solid white; + border-radius: 50%; + + &.online { + background-color: #4CAF50; + } + + &.do-not-disturb { + background-color: #F44336; + } + + &.away { + background-color: #FFC107; + } + + &.offline { + background-color: #646464; + } + } + } + } + } + + #chat { + background-color: mat-color(mat-palette($mat-grey, 300)); + + .messages { + position: relative; + overflow: auto; + padding: 16px 0 40px 40px; + + .message-row { + position: relative; + display: flex; + flex-direction: column; + align-items: flex-start; + justify-content: flex-end; + padding: 0 16px 4px 16px; + + .avatar { + position: absolute; + left: -32px; + margin: 0; + } + + .bubble { + position: relative; + display: flex; + align-items: center; + justify-content: center; + padding: 12px; + + .message { + white-space: pre-wrap; + line-height: 1.2; + } + + .time { + position: absolute; + display: none; + width: 100%; + font-size: 11px; + margin-top: 8px; + top: 100%; + color: $black-87-opacity; + white-space: nowrap; + } + } + + &.contact { + + .bubble { + background-color: mat-color(mat-palette($mat-indigo)); + color: white; + + border-top-left-radius: 5px; + border-bottom-left-radius: 5px; + + border-top-right-radius: 20px; + border-bottom-right-radius: 20px; + + .time { + margin-left: 12px; + } + } + + &.first-of-group { + + .bubble { + border-top-left-radius: 20px; + } + } + + &.last-of-group { + + .bubble { + border-bottom-left-radius: 20px; + } + } + } + + &.me { + padding-left: 40px; + + .avatar { + order: 2; + margin: 0 0 0 16px; + } + + .bubble { + margin-left: auto; + background-color: #FFFFFF; + color: $black-87-opacity; + + border-top-left-radius: 20px; + border-bottom-left-radius: 20px; + + border-top-right-radius: 5px; + border-bottom-right-radius: 5px; + + .time { + justify-content: flex-end; + right: 0; + margin-right: 12px; + } + } + + &.first-of-group { + + .bubble { + border-top-right-radius: 20px; + } + } + + &.last-of-group { + + .bubble { + border-bottom-right-radius: 20px; + } + } + } + + &.contact + .me, + &.me + .contact { + padding-top: 20px; + margin-top: 20px; + } + + &.first-of-group { + + .bubble { + border-top-left-radius: 20px; + padding-top: 13px; + } + } + + &.last-of-group { + + .bubble { + border-bottom-left-radius: 20px; + padding-bottom: 13px; + + .time { + display: flex; + } + } + } + } + + .no-messages-icon { + position: absolute; + top: 50%; + right: 0; + left: 0; + padding: 0 24px; + margin-top: -64px; + text-align: center; + + mat-icon { + color: rgba(0, 0, 0, 0.06); + } + } + + .no-messages { + position: absolute; + right: 0; + bottom: 0; + left: 0; + padding: 0 16px 24px 16px; + text-align: center; + } + } + + .reply-form { + position: relative; + + .message-text { + padding: 16px 8px; + + .mat-form-field-wrapper { + padding: 0; + + .mat-form-field-flex { + padding: 0; + + .mat-form-field-infix { + padding: 0; + border: none; + background: white; + border-radius: 20px; + @include mat-elevation(2); + + textarea { + overflow: hidden; + margin: 16px 48px 16px 16px; + width: calc(100% - 64px); + padding: 0; + } + } + } + + .mat-form-field-underline { + display: none !important; + } + } + } + + .send-message-button { + position: absolute; + right: 16px; + bottom: 21px; + } + } + } +} + +fuse-sidebar { + + &.chat-panel { + width: 360px; + min-width: 360px; + max-width: 360px; + + // Folded + &.folded { + + chat-panel { + + .header { + + .title { + + h3 { + opacity: 0; + } + } + } + } + + // Folded unfolded + &.unfolded { + + chat-panel { + + .header { + + .title { + + h3 { + opacity: 1; + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/src/app/layout/components/chat-panel/chat-panel.component.ts b/src/app/layout/components/chat-panel/chat-panel.component.ts new file mode 100644 index 00000000..cf7c04f9 --- /dev/null +++ b/src/app/layout/components/chat-panel/chat-panel.component.ts @@ -0,0 +1,269 @@ +import { AfterViewInit, Component, ElementRef, OnDestroy, OnInit, QueryList, ViewChild, ViewChildren, ViewEncapsulation } from '@angular/core'; +import { NgForm } from '@angular/forms'; +import { HttpClient } from '@angular/common/http'; + +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; + +import { FuseSidebarService } from '@fuse/components/sidebar/sidebar.service'; +import { FusePerfectScrollbarDirective } from '@fuse/directives/fuse-perfect-scrollbar/fuse-perfect-scrollbar.directive'; +import { ChatPanelService } from 'app/layout/components/chat-panel/chat-panel.service'; + +@Component({ + selector : 'chat-panel', + templateUrl : './chat-panel.component.html', + styleUrls : ['./chat-panel.component.scss'], + encapsulation: ViewEncapsulation.None +}) +export class ChatPanelComponent implements OnInit, AfterViewInit, OnDestroy +{ + contacts: any[]; + chat: any; + selectedContact: any; + sidebarFolded: boolean; + user: any; + + @ViewChild('replyForm') + set replyForm(content: NgForm) + { + this._replyForm = content; + } + + @ViewChild('replyInput') + set replyInput(content: ElementRef) + { + this._replyInput = content; + } + + @ViewChildren(FusePerfectScrollbarDirective) + private _fusePerfectScrollbarDirectives: QueryList; + + // Private + private _chatViewScrollbar: FusePerfectScrollbarDirective; + private _replyForm: NgForm; + private _replyInput: ElementRef; + private _unsubscribeAll: Subject; + + /** + * Constructor + * + * @param {ChatPanelService} _chatPanelService + * @param {HttpClient} _httpClient + * @param {FuseSidebarService} _fuseSidebarService + */ + constructor( + private _chatPanelService: ChatPanelService, + private _httpClient: HttpClient, + private _fuseSidebarService: FuseSidebarService + ) + { + // Set the defaults + this.selectedContact = null; + this.sidebarFolded = true; + + // Set the private defaults + this._unsubscribeAll = new Subject(); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Lifecycle hooks + // ----------------------------------------------------------------------------------------------------- + + /** + * On init + */ + ngOnInit(): void + { + // Load the contacts + this._chatPanelService.loadContacts().then(() => { + + this.contacts = this._chatPanelService.contacts; + this.user = this._chatPanelService.user; + }); + + // Subscribe to the foldedChanged observable + this._fuseSidebarService.getSidebar('chatPanel').foldedChanged + .pipe(takeUntil(this._unsubscribeAll)) + .subscribe((folded) => { + this.sidebarFolded = folded; + }); + } + + /** + * After view init + */ + ngAfterViewInit(): void + { + this._chatViewScrollbar = this._fusePerfectScrollbarDirectives.find((directive) => { + return directive.elementRef.nativeElement.id === 'messages'; + }); + } + + /** + * On destroy + */ + ngOnDestroy(): void + { + // Unsubscribe from all subscriptions + this._unsubscribeAll.next(); + this._unsubscribeAll.complete(); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Private methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Prepare the chat for the replies + */ + private _prepareChatForReplies(): void + { + setTimeout(() => { + + // Reset the reply form + this._replyForm.reset(); + + // Focus to the reply input + this._replyInput.nativeElement.focus(); + + // Scroll to the bottom of the messages list + if ( this._chatViewScrollbar ) + { + this._chatViewScrollbar.update(); + + setTimeout(() => { + this._chatViewScrollbar.scrollToBottom(0); + }); + } + }); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Toggle sidebar folded status + */ + toggleSidebarFolded(): void + { + this._fuseSidebarService.getSidebar('chatPanel').toggleFold(); + } + + /** + * Fold the temporarily unfolded sidebar back + */ + foldSidebarTemporarily(): void + { + this._fuseSidebarService.getSidebar('chatPanel').foldTemporarily(); + } + + /** + * Unfold the sidebar temporarily + */ + unfoldSidebarTemporarily(): void + { + this._fuseSidebarService.getSidebar('chatPanel').unfoldTemporarily(); + } + + /** + * Toggle sidebar opened status + */ + toggleSidebarOpen(): void + { + this._fuseSidebarService.getSidebar('chatPanel').toggleOpen(); + } + + /** + * Decide whether to show or not the contact's avatar in the message row + * + * @param message + * @param i + * @returns {boolean} + */ + shouldShowContactAvatar(message, i): boolean + { + return ( + message.who === this.selectedContact.id && + ((this.chat.dialog[i + 1] && this.chat.dialog[i + 1].who !== this.selectedContact.id) || !this.chat.dialog[i + 1]) + ); + } + + /** + * Check if the given message is the first message of a group + * + * @param message + * @param i + * @returns {boolean} + */ + isFirstMessageOfGroup(message, i): boolean + { + return (i === 0 || this.chat.dialog[i - 1] && this.chat.dialog[i - 1].who !== message.who); + } + + /** + * Check if the given message is the last message of a group + * + * @param message + * @param i + * @returns {boolean} + */ + isLastMessageOfGroup(message, i): boolean + { + return (i === this.chat.dialog.length - 1 || this.chat.dialog[i + 1] && this.chat.dialog[i + 1].who !== message.who); + } + + /** + * Go to chat with the contact + * + * @param contact + */ + goToChat(contact): void + { + // Unfold the sidebar temporarily + this.unfoldSidebarTemporarily(); + + // Set the selected contact + this.selectedContact = contact; + + // Load the chat + this._chatPanelService.getChat(contact.id).then((chat) => { + + // Set the chat + this.chat = chat; + + // Prepare the chat for the replies + this._prepareChatForReplies(); + }); + } + + /** + * Reply + */ + reply(event): void + { + event.preventDefault(); + + if ( !this._replyForm.form.value.message ) + { + return; + } + + // Message + const message = { + who : this.user.id, + message: this._replyForm.form.value.message, + time : new Date().toISOString() + }; + + // Add the message to the chat + this.chat.dialog.push(message); + + // Update the server + this._chatPanelService.updateChat(this.chat.id, this.chat.dialog).then(response => { + + // Prepare the chat for the replies + this._prepareChatForReplies(); + }); + } +} diff --git a/src/app/layout/components/chat-panel/chat-panel.module.ts b/src/app/layout/components/chat-panel/chat-panel.module.ts new file mode 100644 index 00000000..5d9eb29b --- /dev/null +++ b/src/app/layout/components/chat-panel/chat-panel.module.ts @@ -0,0 +1,34 @@ +import { NgModule } from '@angular/core'; +import { MatButtonModule, MatFormFieldModule, MatIconModule, MatInputModule, MatListModule, MatRippleModule, MatTabsModule, MatTooltipModule } from '@angular/material'; + +import { FuseSharedModule } from '@fuse/shared.module'; + +import { ChatPanelComponent } from 'app/layout/components/chat-panel/chat-panel.component'; +import { ChatPanelService } from 'app/layout/components/chat-panel/chat-panel.service'; + +@NgModule({ + declarations: [ + ChatPanelComponent + ], + providers : [ + ChatPanelService + ], + imports : [ + MatButtonModule, + MatFormFieldModule, + MatIconModule, + MatInputModule, + MatListModule, + MatTabsModule, + MatTooltipModule, + MatRippleModule, + + FuseSharedModule + ], + exports : [ + ChatPanelComponent + ] +}) +export class ChatPanelModule +{ +} diff --git a/src/app/layout/components/chat-panel/chat-panel.service.ts b/src/app/layout/components/chat-panel/chat-panel.service.ts new file mode 100644 index 00000000..8aa1b952 --- /dev/null +++ b/src/app/layout/components/chat-panel/chat-panel.service.ts @@ -0,0 +1,182 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; + +import { FuseUtils } from '@fuse/utils'; + +@Injectable() +export class ChatPanelService +{ + contacts: any[]; + chats: any[]; + user: any; + + /** + * Constructor + * + * @param {HttpClient} _httpClient + */ + constructor( + private _httpClient: HttpClient + ) + { + } + + /** + * Loader + * + * @returns {Promise | any} + */ + loadContacts(): Promise | any + { + return new Promise((resolve, reject) => { + Promise.all([ + this.getContacts(), + this.getUser() + ]).then( + ([contacts, user]) => { + this.contacts = contacts; + this.user = user; + resolve(); + }, + reject + ); + }); + } + + /** + * Get chat + * + * @param contactId + * @returns {Promise} + */ + getChat(contactId): Promise + { + const chatItem = this.user.chatList.find((item) => { + return item.contactId === contactId; + }); + + // Get the chat + return new Promise((resolve, reject) => { + + // If there is a chat with this user, return that. + if ( chatItem ) + { + this._httpClient.get('api/chat-panel-chats/' + chatItem.chatId) + .subscribe((chat) => { + + // Resolve the promise + resolve(chat); + + }, reject); + } + // If there is no chat with this user, create one... + else + { + this.createNewChat(contactId).then(() => { + + // and then recall the getChat method + this.getChat(contactId).then((chat) => { + resolve(chat); + }); + }); + } + }); + } + + /** + * Create new chat + * + * @param contactId + * @returns {Promise} + */ + createNewChat(contactId): Promise + { + return new Promise((resolve, reject) => { + + // Generate a new id + const chatId = FuseUtils.generateGUID(); + + // Prepare the chat object + const chat = { + id : chatId, + dialog: [] + }; + + // Prepare the chat list entry + const chatListItem = { + chatId : chatId, + contactId : contactId, + lastMessageTime: '2017-02-18T10:30:18.931Z' + }; + + // Add new chat list item to the user's chat list + this.user.chatList.push(chatListItem); + + // Post the created chat to the server + this._httpClient.post('api/chat-panel-chats', {...chat}) + .subscribe(() => { + + // Post the updated user data to the server + this._httpClient.post('api/chat-panel-user/' + this.user.id, this.user) + .subscribe(() => { + + // Resolve the promise + resolve(); + }); + }, reject); + }); + } + + /** + * Update the chat + * + * @param chatId + * @param dialog + * @returns {Promise} + */ + updateChat(chatId, dialog): Promise + { + return new Promise((resolve, reject) => { + + const newData = { + id : chatId, + dialog: dialog + }; + + this._httpClient.post('api/chat-panel-chats/' + chatId, newData) + .subscribe(updatedChat => { + resolve(updatedChat); + }, reject); + }); + } + + /** + * Get contacts + * + * @returns {Promise} + */ + getContacts(): Promise + { + return new Promise((resolve, reject) => { + this._httpClient.get('api/chat-panel-contacts') + .subscribe((response: any) => { + resolve(response); + }, reject); + }); + } + + /** + * Get user + * + * @returns {Promise} + */ + getUser(): Promise + { + return new Promise((resolve, reject) => { + this._httpClient.get('api/chat-panel-user') + .subscribe((response: any) => { + resolve(response[0]); + }, reject); + }); + } +} diff --git a/src/app/layout/components/footer/footer.component.html b/src/app/layout/components/footer/footer.component.html index 9c23c155..3d6e909e 100644 --- a/src/app/layout/components/footer/footer.component.html +++ b/src/app/layout/components/footer/footer.component.html @@ -2,7 +2,7 @@ - diff --git a/src/app/layout/components/navbar/horizontal/style-1/style-1.component.html b/src/app/layout/components/navbar/horizontal/style-1/style-1.component.html new file mode 100644 index 00000000..5e6bb93d --- /dev/null +++ b/src/app/layout/components/navbar/horizontal/style-1/style-1.component.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/app/layout/components/navbar/horizontal/style-1/style-1.component.scss b/src/app/layout/components/navbar/horizontal/style-1/style-1.component.scss new file mode 100644 index 00000000..9a5630e2 --- /dev/null +++ b/src/app/layout/components/navbar/horizontal/style-1/style-1.component.scss @@ -0,0 +1,3 @@ +navbar-horizontal-style-1 { + +} diff --git a/src/app/layout/components/navbar/horizontal/style-1/style-1.component.ts b/src/app/layout/components/navbar/horizontal/style-1/style-1.component.ts new file mode 100644 index 00000000..df2ecf03 --- /dev/null +++ b/src/app/layout/components/navbar/horizontal/style-1/style-1.component.ts @@ -0,0 +1,65 @@ +import { Component, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core'; +import { Subject } from 'rxjs'; +import { filter, takeUntil } from 'rxjs/operators'; + +import { FuseNavigationService } from '@fuse/components/navigation/navigation.service'; +import { FuseSidebarService } from '@fuse/components/sidebar/sidebar.service'; + +@Component({ + selector : 'navbar-horizontal-style-1', + templateUrl : './style-1.component.html', + styleUrls : ['./style-1.component.scss'], + encapsulation: ViewEncapsulation.None +}) +export class NavbarHorizontalStyle1Component implements OnInit, OnDestroy +{ + navigation: any; + + // Private + private _unsubscribeAll: Subject; + + /** + * Constructor + * + * @param {FuseNavigationService} _fuseNavigationService + * @param {FuseSidebarService} _fuseSidebarService + */ + constructor( + private _fuseNavigationService: FuseNavigationService, + private _fuseSidebarService: FuseSidebarService + ) + { + // Set the private defaults + this._unsubscribeAll = new Subject(); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Lifecycle hooks + // ----------------------------------------------------------------------------------------------------- + + /** + * On init + */ + ngOnInit(): void + { + // Get current navigation + this._fuseNavigationService.onNavigationChanged + .pipe( + filter(value => value !== null), + takeUntil(this._unsubscribeAll) + ) + .subscribe(() => { + this.navigation = this._fuseNavigationService.getCurrentNavigation(); + }); + } + + /** + * On destroy + */ + ngOnDestroy(): void + { + // Unsubscribe from all subscriptions + this._unsubscribeAll.next(); + this._unsubscribeAll.complete(); + } +} diff --git a/src/app/layout/components/navbar/horizontal/style-1/style-1.module.ts b/src/app/layout/components/navbar/horizontal/style-1/style-1.module.ts new file mode 100644 index 00000000..9f32f8a1 --- /dev/null +++ b/src/app/layout/components/navbar/horizontal/style-1/style-1.module.ts @@ -0,0 +1,26 @@ +import { NgModule } from '@angular/core'; +import { MatButtonModule, MatIconModule } from '@angular/material'; + +import { FuseNavigationModule } from '@fuse/components'; +import { FuseSharedModule } from '@fuse/shared.module'; + +import { NavbarHorizontalStyle1Component } from 'app/layout/components/navbar/horizontal/style-1/style-1.component'; + +@NgModule({ + declarations: [ + NavbarHorizontalStyle1Component + ], + imports : [ + MatButtonModule, + MatIconModule, + + FuseSharedModule, + FuseNavigationModule + ], + exports : [ + NavbarHorizontalStyle1Component + ] +}) +export class NavbarHorizontalStyle1Module +{ +} diff --git a/src/app/layout/components/navbar/navbar.component.html b/src/app/layout/components/navbar/navbar.component.html index 115b1902..158a127c 100644 --- a/src/app/layout/components/navbar/navbar.component.html +++ b/src/app/layout/components/navbar/navbar.component.html @@ -1,39 +1,11 @@ - - - - + + - + + + - - - \ No newline at end of file + + + diff --git a/src/app/layout/components/navbar/navbar.component.scss b/src/app/layout/components/navbar/navbar.component.scss index fc111989..e7408888 100644 --- a/src/app/layout/components/navbar/navbar.component.scss +++ b/src/app/layout/components/navbar/navbar.component.scss @@ -1,81 +1,5 @@ -@import "src/@fuse/scss/fuse"; - -fuse-sidebar { - - &.folded:not(.unfolded) { - - .navbar-vertical { - - .navbar-header { - padding: 0 13px; - - .logo { - - .logo-text { - opacity: 0; - transition: opacity 200ms ease; - } - } - } - } - } -} - navbar { - - &:not(.top-navbar) { - height: 100%; - overflow: hidden; - } - - .navbar-vertical { - display: flex; - flex-direction: column; - width: 100%; - height: 100%; - - .navbar-header { - display: flex; - align-items: center; - justify-content: space-between; - height: 64px; - min-height: 64px; - padding: 0 16px 0 24px; - transition: padding 200ms ease; - background-color: rgba(255, 255, 255, .05); - @include mat-elevation(1); - - .logo { - display: flex; - align-items: center; - - .logo-icon { - width: 38px; - height: 38px; - } - - .logo-text { - margin-left: 8px; - font-size: 20px; - font-weight: 300; - letter-spacing: 0.4px; - } - } - } - - .navbar-content { - flex: 1 1 auto; - overflow-y: auto; - } - } - - &.right-navbar { - - .toggle-sidebar-opened { - - mat-icon { - transform: rotate(180deg); - } - } - } + display: flex; + flex-direction: column; + width: 100%; } diff --git a/src/app/layout/components/navbar/navbar.component.ts b/src/app/layout/components/navbar/navbar.component.ts index e4424f8a..c9544269 100644 --- a/src/app/layout/components/navbar/navbar.component.ts +++ b/src/app/layout/components/navbar/navbar.component.ts @@ -1,11 +1,4 @@ -import { Component, Input, OnDestroy, OnInit, ViewChild, ViewEncapsulation } from '@angular/core'; -import { NavigationEnd, Router } from '@angular/router'; -import { Subject } from 'rxjs'; -import { filter, take, takeUntil } from 'rxjs/operators'; - -import { FuseNavigationService } from '@fuse/components/navigation/navigation.service'; -import { FusePerfectScrollbarDirective } from '@fuse/directives/fuse-perfect-scrollbar/fuse-perfect-scrollbar.directive'; -import { FuseSidebarService } from '@fuse/components/sidebar/sidebar.service'; +import { Component, Input, ViewEncapsulation } from '@angular/core'; @Component({ selector : 'navbar', @@ -13,148 +6,18 @@ import { FuseSidebarService } from '@fuse/components/sidebar/sidebar.service'; styleUrls : ['./navbar.component.scss'], encapsulation: ViewEncapsulation.None }) -export class NavbarComponent implements OnInit, OnDestroy +export class NavbarComponent { - // Layout + // Variant @Input() - layout; - - fusePerfectScrollbarUpdateTimeout: any; - navigation: any; - - // Private - private _fusePerfectScrollbar: FusePerfectScrollbarDirective; - private _unsubscribeAll: Subject; + variant; /** * Constructor - * - * @param {FuseNavigationService} _fuseNavigationService - * @param {FuseSidebarService} _fuseSidebarService - * @param {Router} _router */ - constructor( - private _fuseNavigationService: FuseNavigationService, - private _fuseSidebarService: FuseSidebarService, - private _router: Router - ) + constructor() { // Set the defaults - this.layout = 'vertical'; - - // Set the private defaults - this._unsubscribeAll = new Subject(); - } - - // ----------------------------------------------------------------------------------------------------- - // @ Accessors - // ----------------------------------------------------------------------------------------------------- - - // Directive - @ViewChild(FusePerfectScrollbarDirective) - set directive(theDirective: FusePerfectScrollbarDirective) - { - if ( !theDirective ) - { - return; - } - - this._fusePerfectScrollbar = theDirective; - - // Update the scrollbar on collapsable item toggle - this._fuseNavigationService.onItemCollapseToggled - .pipe(takeUntil(this._unsubscribeAll)) - .subscribe(() => { - this.fusePerfectScrollbarUpdateTimeout = setTimeout(() => { - this._fusePerfectScrollbar.update(); - }, 310); - }); - - // Scroll to the active item position - this._router.events - .pipe( - filter((event) => event instanceof NavigationEnd), - take(1) - ) - .subscribe(() => { - setTimeout(() => { - const activeNavItem: any = document.querySelector('navbar .nav-link.active'); - - if ( activeNavItem ) - { - const activeItemOffsetTop = activeNavItem.offsetTop, - activeItemOffsetParentTop = activeNavItem.offsetParent.offsetTop, - scrollDistance = activeItemOffsetTop - activeItemOffsetParentTop - (48 * 3); - - this._fusePerfectScrollbar.scrollToTop(scrollDistance); - } - }); - } - ); - } - - // ----------------------------------------------------------------------------------------------------- - // @ Lifecycle hooks - // ----------------------------------------------------------------------------------------------------- - - /** - * On init - */ - ngOnInit(): void - { - this._router.events - .pipe( - filter((event) => event instanceof NavigationEnd), - takeUntil(this._unsubscribeAll) - ) - .subscribe(() => { - if ( this._fuseSidebarService.getSidebar('navbar') ) - { - this._fuseSidebarService.getSidebar('navbar').close(); - } - } - ); - - // Get current navigation - this._fuseNavigationService.onNavigationChanged - .pipe(filter(value => value !== null)) - .subscribe(() => { - this.navigation = this._fuseNavigationService.getCurrentNavigation(); - }); - } - - /** - * On destroy - */ - ngOnDestroy(): void - { - if ( this.fusePerfectScrollbarUpdateTimeout ) - { - clearTimeout(this.fusePerfectScrollbarUpdateTimeout); - } - - // Unsubscribe from all subscriptions - this._unsubscribeAll.next(); - this._unsubscribeAll.complete(); - } - - // ----------------------------------------------------------------------------------------------------- - // @ Public methods - // ----------------------------------------------------------------------------------------------------- - - /** - * Toggle sidebar opened status - */ - toggleSidebarOpened(): void - { - this._fuseSidebarService.getSidebar('navbar').toggleOpen(); - } - - /** - * Toggle sidebar folded status - */ - toggleSidebarFolded(): void - { - this._fuseSidebarService.getSidebar('navbar').toggleFold(); + this.variant = 'vertical-style-1'; } } diff --git a/src/app/layout/components/navbar/navbar.module.ts b/src/app/layout/components/navbar/navbar.module.ts index acb91738..67005f0f 100644 --- a/src/app/layout/components/navbar/navbar.module.ts +++ b/src/app/layout/components/navbar/navbar.module.ts @@ -1,21 +1,22 @@ import { NgModule } from '@angular/core'; -import { MatButtonModule, MatIconModule } from '@angular/material'; -import { FuseNavigationModule } from '@fuse/components'; import { FuseSharedModule } from '@fuse/shared.module'; import { NavbarComponent } from 'app/layout/components/navbar/navbar.component'; +import { NavbarHorizontalStyle1Module } from 'app/layout/components/navbar/horizontal/style-1/style-1.module'; +import { NavbarVerticalStyle1Module } from 'app/layout/components/navbar/vertical/style-1/style-1.module'; +import { NavbarVerticalStyle2Module } from 'app/layout/components/navbar/vertical/style-2/style-2.module'; @NgModule({ declarations: [ NavbarComponent ], imports : [ - MatButtonModule, - MatIconModule, - FuseSharedModule, - FuseNavigationModule + + NavbarHorizontalStyle1Module, + NavbarVerticalStyle1Module, + NavbarVerticalStyle2Module ], exports : [ NavbarComponent diff --git a/src/app/layout/components/navbar/vertical/style-1/style-1.component.html b/src/app/layout/components/navbar/vertical/style-1/style-1.component.html new file mode 100644 index 00000000..a8299367 --- /dev/null +++ b/src/app/layout/components/navbar/vertical/style-1/style-1.component.html @@ -0,0 +1,41 @@ + + + + diff --git a/src/app/layout/components/navbar/vertical/style-1/style-1.component.scss b/src/app/layout/components/navbar/vertical/style-1/style-1.component.scss new file mode 100644 index 00000000..4099e475 --- /dev/null +++ b/src/app/layout/components/navbar/vertical/style-1/style-1.component.scss @@ -0,0 +1,159 @@ +@import "src/@fuse/scss/fuse"; + +fuse-sidebar { + overflow: hidden; + + &.folded:not(.unfolded) { + + navbar { + + navbar-vertical-style-1 { + + .navbar-top { + padding: 12px 0; + justify-content: center; + + .buttons { + display: none; + } + + .logo { + + .logo-icon { + width: 32px; + height: 32px; + } + + .logo-text { + display: none; + } + } + } + + .navbar-scroll-container { + + .user { + padding: 12px 0; + + .avatar-container { + position: relative; + top: auto; + padding: 0; + + .avatar { + width: 40px; + height: 40px; + } + } + + .username, + .email { + display: none; + } + } + + .navbar-content { + margin-top: 0; + } + } + } + } + } +} + +navbar { + + navbar-vertical-style-1 { + display: flex; + flex-direction: column; + width: 100%; + height: 100%; + + .navbar-top { + display: flex; + flex-direction: row; + flex: 1 0 auto; + align-items: center; + justify-content: space-between; + min-height: 64px; + height: 64px; + padding: 12px 12px 12px 20px; + + @include media-breakpoint('xs') { + min-height: 56px; + height: 56px; + } + + .logo { + display: flex; + align-items: center; + + .logo-icon { + width: 24px; + height: 24px; + } + + .logo-text { + margin-left: 12px; + font-size: 16px; + font-weight: 300; + letter-spacing: 0.4px; + line-height: normal; + } + } + + .buttons { + display: flex; + align-items: center; + } + } + + .navbar-scroll-container { + overflow-y: auto; + + background: linear-gradient(rgba(0, 0, 0, 0) 30%, rgba(0, 0, 0, 0) 30%), + linear-gradient(rgba(0, 0, 0, 0.25) 0, rgba(0, 0, 0, 0) 40%); + + background-repeat: no-repeat; + background-size: 100% 40px, 100% 10px; + background-attachment: local, scroll; + + .user { + position: relative; + display: flex; + align-items: center; + justify-content: flex-start; + width: 100%; + padding: 24px 0 64px 0; + + .avatar-container { + position: absolute; + top: 92px; + border-radius: 50%; + padding: 8px; + + .avatar { + width: 72px; + height: 72px; + margin: 0; + } + } + } + + .navbar-content { + flex: 1 1 auto; + margin-top: 32px; + } + } + } + + &.right-navbar { + + .toggle-sidebar-opened { + + mat-icon { + transform: rotate(180deg); + } + } + } +} \ No newline at end of file diff --git a/src/app/layout/components/navbar/vertical/style-1/style-1.component.ts b/src/app/layout/components/navbar/vertical/style-1/style-1.component.ts new file mode 100644 index 00000000..acdc3238 --- /dev/null +++ b/src/app/layout/components/navbar/vertical/style-1/style-1.component.ts @@ -0,0 +1,167 @@ +import { Component, OnDestroy, OnInit, ViewChild, ViewEncapsulation } from '@angular/core'; +import { NavigationEnd, Router } from '@angular/router'; +import { Subject } from 'rxjs'; +import { filter, take, takeUntil } from 'rxjs/operators'; + +import { FuseConfigService } from '@fuse/services/config.service'; +import { FuseNavigationService } from '@fuse/components/navigation/navigation.service'; +import { FusePerfectScrollbarDirective } from '@fuse/directives/fuse-perfect-scrollbar/fuse-perfect-scrollbar.directive'; +import { FuseSidebarService } from '@fuse/components/sidebar/sidebar.service'; + +@Component({ + selector : 'navbar-vertical-style-1', + templateUrl : './style-1.component.html', + styleUrls : ['./style-1.component.scss'], + encapsulation: ViewEncapsulation.None +}) +export class NavbarVerticalStyle1Component implements OnInit, OnDestroy +{ + fuseConfig: any; + fusePerfectScrollbarUpdateTimeout: any; + navigation: any; + + // Private + private _fusePerfectScrollbar: FusePerfectScrollbarDirective; + private _unsubscribeAll: Subject; + + /** + * Constructor + * + * @param {FuseConfigService} _fuseConfigService + * @param {FuseNavigationService} _fuseNavigationService + * @param {FuseSidebarService} _fuseSidebarService + * @param {Router} _router + */ + constructor( + private _fuseConfigService: FuseConfigService, + private _fuseNavigationService: FuseNavigationService, + private _fuseSidebarService: FuseSidebarService, + private _router: Router + ) + { + // Set the private defaults + this._unsubscribeAll = new Subject(); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Accessors + // ----------------------------------------------------------------------------------------------------- + + // Directive + @ViewChild(FusePerfectScrollbarDirective) + set directive(theDirective: FusePerfectScrollbarDirective) + { + if ( !theDirective ) + { + return; + } + + this._fusePerfectScrollbar = theDirective; + + // Update the scrollbar on collapsable item toggle + this._fuseNavigationService.onItemCollapseToggled + .pipe(takeUntil(this._unsubscribeAll)) + .subscribe(() => { + this.fusePerfectScrollbarUpdateTimeout = setTimeout(() => { + this._fusePerfectScrollbar.update(); + }, 310); + }); + + // Scroll to the active item position + this._router.events + .pipe( + filter((event) => event instanceof NavigationEnd), + take(1) + ) + .subscribe(() => { + setTimeout(() => { + const activeNavItem: any = document.querySelector('navbar .nav-link.active'); + + if ( activeNavItem ) + { + const activeItemOffsetTop = activeNavItem.offsetTop, + activeItemOffsetParentTop = activeNavItem.offsetParent.offsetTop, + scrollDistance = activeItemOffsetTop - activeItemOffsetParentTop - (48 * 3) - 168; + + this._fusePerfectScrollbar.scrollToTop(scrollDistance); + } + }); + } + ); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Lifecycle hooks + // ----------------------------------------------------------------------------------------------------- + + /** + * On init + */ + ngOnInit(): void + { + this._router.events + .pipe( + filter((event) => event instanceof NavigationEnd), + takeUntil(this._unsubscribeAll) + ) + .subscribe(() => { + if ( this._fuseSidebarService.getSidebar('navbar') ) + { + this._fuseSidebarService.getSidebar('navbar').close(); + } + } + ); + + // Subscribe to the config changes + this._fuseConfigService.config + .pipe(takeUntil(this._unsubscribeAll)) + .subscribe((config) => { + this.fuseConfig = config; + }); + + // Get current navigation + this._fuseNavigationService.onNavigationChanged + .pipe( + filter(value => value !== null), + takeUntil(this._unsubscribeAll) + ) + .subscribe(() => { + this.navigation = this._fuseNavigationService.getCurrentNavigation(); + }); + } + + /** + * On destroy + */ + ngOnDestroy(): void + { + if ( this.fusePerfectScrollbarUpdateTimeout ) + { + clearTimeout(this.fusePerfectScrollbarUpdateTimeout); + } + + // Unsubscribe from all subscriptions + this._unsubscribeAll.next(); + this._unsubscribeAll.complete(); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Toggle sidebar opened status + */ + toggleSidebarOpened(): void + { + this._fuseSidebarService.getSidebar('navbar').toggleOpen(); + } + + /** + * Toggle sidebar folded status + */ + toggleSidebarFolded(): void + { + this._fuseSidebarService.getSidebar('navbar').toggleFold(); + } +} diff --git a/src/app/layout/components/navbar/vertical/style-1/style-1.module.ts b/src/app/layout/components/navbar/vertical/style-1/style-1.module.ts new file mode 100644 index 00000000..f452e93f --- /dev/null +++ b/src/app/layout/components/navbar/vertical/style-1/style-1.module.ts @@ -0,0 +1,26 @@ +import { NgModule } from '@angular/core'; +import { MatButtonModule, MatIconModule } from '@angular/material'; + +import { FuseNavigationModule } from '@fuse/components'; +import { FuseSharedModule } from '@fuse/shared.module'; + +import { NavbarVerticalStyle1Component } from 'app/layout/components/navbar/vertical/style-1/style-1.component'; + +@NgModule({ + declarations: [ + NavbarVerticalStyle1Component + ], + imports : [ + MatButtonModule, + MatIconModule, + + FuseSharedModule, + FuseNavigationModule + ], + exports : [ + NavbarVerticalStyle1Component + ] +}) +export class NavbarVerticalStyle1Module +{ +} diff --git a/src/app/layout/components/navbar/vertical/style-2/style-2.component.html b/src/app/layout/components/navbar/vertical/style-2/style-2.component.html new file mode 100644 index 00000000..601617f3 --- /dev/null +++ b/src/app/layout/components/navbar/vertical/style-2/style-2.component.html @@ -0,0 +1,23 @@ + + + \ No newline at end of file diff --git a/src/app/layout/components/navbar/vertical/style-2/style-2.component.scss b/src/app/layout/components/navbar/vertical/style-2/style-2.component.scss new file mode 100644 index 00000000..d150731e --- /dev/null +++ b/src/app/layout/components/navbar/vertical/style-2/style-2.component.scss @@ -0,0 +1,81 @@ +@import "src/@fuse/scss/fuse"; + +fuse-sidebar { + overflow: hidden; + + &.folded:not(.unfolded) { + + navbar { + + navbar-vertical-style-2 { + + .navbar-header { + padding: 0 13px; + + .logo { + + .logo-text { + opacity: 0; + transition: opacity 200ms ease; + } + } + } + } + } + } +} + +navbar { + + navbar-vertical-style-2 { + display: flex; + flex-direction: column; + width: 100%; + height: 100%; + + .navbar-header { + display: flex; + align-items: center; + justify-content: space-between; + height: 64px; + min-height: 64px; + padding: 0 16px 0 24px; + transition: padding 200ms ease; + background-color: rgba(255, 255, 255, .05); + @include mat-elevation(1); + + .logo { + display: flex; + align-items: center; + + .logo-icon { + width: 38px; + height: 38px; + } + + .logo-text { + margin-left: 8px; + font-size: 20px; + font-weight: 300; + letter-spacing: 0.4px; + } + } + } + + .navbar-content { + flex: 1 1 auto; + overflow-y: auto; + } + + } + + &.right-navbar { + + .toggle-sidebar-opened { + + mat-icon { + transform: rotate(180deg); + } + } + } +} diff --git a/src/app/layout/components/navbar/vertical/style-2/style-2.component.ts b/src/app/layout/components/navbar/vertical/style-2/style-2.component.ts new file mode 100644 index 00000000..29e4f440 --- /dev/null +++ b/src/app/layout/components/navbar/vertical/style-2/style-2.component.ts @@ -0,0 +1,156 @@ +import { Component, OnDestroy, OnInit, ViewChild, ViewEncapsulation } from '@angular/core'; +import { NavigationEnd, Router } from '@angular/router'; +import { Subject } from 'rxjs'; +import { filter, take, takeUntil } from 'rxjs/operators'; + +import { FuseNavigationService } from '@fuse/components/navigation/navigation.service'; +import { FusePerfectScrollbarDirective } from '@fuse/directives/fuse-perfect-scrollbar/fuse-perfect-scrollbar.directive'; +import { FuseSidebarService } from '@fuse/components/sidebar/sidebar.service'; + +@Component({ + selector : 'navbar-vertical-style-2', + templateUrl : './style-2.component.html', + styleUrls : ['./style-2.component.scss'], + encapsulation: ViewEncapsulation.None +}) +export class NavbarVerticalStyle2Component implements OnInit, OnDestroy +{ + fusePerfectScrollbarUpdateTimeout: any; + navigation: any; + + // Private + private _fusePerfectScrollbar: FusePerfectScrollbarDirective; + private _unsubscribeAll: Subject; + + /** + * Constructor + * + * @param {FuseNavigationService} _fuseNavigationService + * @param {FuseSidebarService} _fuseSidebarService + * @param {Router} _router + */ + constructor( + private _fuseNavigationService: FuseNavigationService, + private _fuseSidebarService: FuseSidebarService, + private _router: Router + ) + { + // Set the private defaults + this._unsubscribeAll = new Subject(); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Accessors + // ----------------------------------------------------------------------------------------------------- + + // Directive + @ViewChild(FusePerfectScrollbarDirective) + set directive(theDirective: FusePerfectScrollbarDirective) + { + if ( !theDirective ) + { + return; + } + + this._fusePerfectScrollbar = theDirective; + + // Update the scrollbar on collapsable item toggle + this._fuseNavigationService.onItemCollapseToggled + .pipe(takeUntil(this._unsubscribeAll)) + .subscribe(() => { + this.fusePerfectScrollbarUpdateTimeout = setTimeout(() => { + this._fusePerfectScrollbar.update(); + }, 310); + }); + + // Scroll to the active item position + this._router.events + .pipe( + filter((event) => event instanceof NavigationEnd), + take(1) + ) + .subscribe(() => { + setTimeout(() => { + const activeNavItem: any = document.querySelector('navbar .nav-link.active'); + + if ( activeNavItem ) + { + const activeItemOffsetTop = activeNavItem.offsetTop, + activeItemOffsetParentTop = activeNavItem.offsetParent.offsetTop, + scrollDistance = activeItemOffsetTop - activeItemOffsetParentTop - (48 * 3); + + this._fusePerfectScrollbar.scrollToTop(scrollDistance); + } + }); + } + ); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Lifecycle hooks + // ----------------------------------------------------------------------------------------------------- + + /** + * On init + */ + ngOnInit(): void + { + this._router.events + .pipe( + filter((event) => event instanceof NavigationEnd), + takeUntil(this._unsubscribeAll) + ) + .subscribe(() => { + if ( this._fuseSidebarService.getSidebar('navbar') ) + { + this._fuseSidebarService.getSidebar('navbar').close(); + } + } + ); + + // Get current navigation + this._fuseNavigationService.onNavigationChanged + .pipe( + filter(value => value !== null), + takeUntil(this._unsubscribeAll) + ) + .subscribe(() => { + this.navigation = this._fuseNavigationService.getCurrentNavigation(); + }); + } + + /** + * On destroy + */ + ngOnDestroy(): void + { + if ( this.fusePerfectScrollbarUpdateTimeout ) + { + clearTimeout(this.fusePerfectScrollbarUpdateTimeout); + } + + // Unsubscribe from all subscriptions + this._unsubscribeAll.next(); + this._unsubscribeAll.complete(); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Toggle sidebar opened status + */ + toggleSidebarOpened(): void + { + this._fuseSidebarService.getSidebar('navbar').toggleOpen(); + } + + /** + * Toggle sidebar folded status + */ + toggleSidebarFolded(): void + { + this._fuseSidebarService.getSidebar('navbar').toggleFold(); + } +} diff --git a/src/app/layout/components/navbar/vertical/style-2/style-2.module.ts b/src/app/layout/components/navbar/vertical/style-2/style-2.module.ts new file mode 100644 index 00000000..4968641b --- /dev/null +++ b/src/app/layout/components/navbar/vertical/style-2/style-2.module.ts @@ -0,0 +1,26 @@ +import { NgModule } from '@angular/core'; +import { MatButtonModule, MatIconModule } from '@angular/material'; + +import { FuseNavigationModule } from '@fuse/components'; +import { FuseSharedModule } from '@fuse/shared.module'; + +import { NavbarVerticalStyle2Component } from 'app/layout/components/navbar/vertical/style-2/style-2.component'; + +@NgModule({ + declarations: [ + NavbarVerticalStyle2Component + ], + imports : [ + MatButtonModule, + MatIconModule, + + FuseSharedModule, + FuseNavigationModule + ], + exports : [ + NavbarVerticalStyle2Component + ] +}) +export class NavbarVerticalStyle2Module +{ +} diff --git a/src/app/layout/components/toolbar/toolbar.component.html b/src/app/layout/components/toolbar/toolbar.component.html index 14d2e6f5..60d0e50f 100644 --- a/src/app/layout/components/toolbar/toolbar.component.html +++ b/src/app/layout/components/toolbar/toolbar.component.html @@ -30,8 +30,8 @@ @@ -83,6 +83,15 @@
+ + +
+