mirror of
https://github.com/richard-loafle/fuse-angular.git
synced 2025-12-23 22:17:05 +00:00
Compare commits
104 Commits
v14.0.0
...
v15.0.0-st
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8e513ccf1e | ||
|
|
a6399c0cf4 | ||
|
|
22d91673ae | ||
|
|
3e303a041a | ||
|
|
714bc49c4e | ||
|
|
3cc4f31238 | ||
|
|
c5aa094230 | ||
|
|
cbd35d57f8 | ||
|
|
11d5dcdb9d | ||
|
|
66e5511402 | ||
|
|
20a03c3689 | ||
|
|
d033470851 | ||
|
|
c374c2c6cf | ||
|
|
30d75c9bd7 | ||
|
|
54cd21f496 | ||
|
|
57d87fa1c4 | ||
|
|
cd8c6ece0f | ||
|
|
1bf4c48cdc | ||
|
|
ea9efc3dc2 | ||
|
|
878a6bf191 | ||
|
|
099e745a36 | ||
|
|
150ddc64d7 | ||
|
|
a6d64b1747 | ||
|
|
6eff4a1898 | ||
|
|
e3630218b5 | ||
|
|
154095da0f | ||
|
|
a32970b7c2 | ||
|
|
2771d3b1ae | ||
|
|
9b44793b64 | ||
|
|
9521257b4d | ||
|
|
5378a6b6ef | ||
|
|
c41e48df7f | ||
|
|
b87173b056 | ||
|
|
6aaa355a48 | ||
|
|
b96dd041d9 | ||
|
|
9786c6baf5 | ||
|
|
f5cc14939c | ||
|
|
8fd434600b | ||
|
|
4c82f6749b | ||
|
|
b0830148a3 | ||
|
|
cdc54ab05e | ||
|
|
e6ad547d27 | ||
|
|
4430754020 | ||
|
|
9d93a2b060 | ||
|
|
afda4b35c9 | ||
|
|
3b88638dee | ||
|
|
b96182c848 | ||
|
|
443a103d6f | ||
|
|
42241b279d | ||
|
|
e5eaea5013 | ||
|
|
6e85b9ff2a | ||
|
|
38a4c392fe | ||
|
|
6e593b1f1e | ||
|
|
57a3f6ae99 | ||
|
|
700a117654 | ||
|
|
5337c95782 | ||
|
|
964c4b23a9 | ||
|
|
d9dee128de | ||
|
|
a8337ce617 | ||
|
|
3ceda2cf3f | ||
|
|
805b50707e | ||
|
|
b84fb68a47 | ||
|
|
3d6714008a | ||
|
|
ea954a75de | ||
|
|
4b37f2304a | ||
|
|
82244fd40e | ||
|
|
c5433cb917 | ||
|
|
cf5f13d78e | ||
|
|
ef85907d1b | ||
|
|
cafc3188cf | ||
|
|
6381362326 | ||
|
|
e1942f46fd | ||
|
|
ce656430c8 | ||
|
|
c93b1ce232 | ||
|
|
b874b37db2 | ||
|
|
4287361a09 | ||
|
|
4f19eb6171 | ||
|
|
cd13f9d9a4 | ||
|
|
db8c52f093 | ||
|
|
2189232273 | ||
|
|
36b89e75db | ||
|
|
8c2ff122b2 | ||
|
|
b2cb512b0e | ||
|
|
21652570c2 | ||
|
|
90891eb201 | ||
|
|
6c7201b77a | ||
|
|
90f869e7b9 | ||
|
|
2d2db97416 | ||
|
|
5daa2260e6 | ||
|
|
af984fcba1 | ||
|
|
97d3662417 | ||
|
|
d897a244c8 | ||
|
|
d146a92c79 | ||
|
|
27b6858b76 | ||
|
|
fcfba4c9e4 | ||
|
|
40894e0aa3 | ||
|
|
8dcf21cb1a | ||
|
|
d917f03883 | ||
|
|
0f2ddbda83 | ||
|
|
fa0d74504b | ||
|
|
ad2b19a07a | ||
|
|
4bf11591a2 | ||
|
|
f45a605b4e | ||
|
|
c150a8902c |
@@ -1,6 +1,11 @@
|
|||||||
{
|
{
|
||||||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||||
"version": 1,
|
"version": 1,
|
||||||
|
"cli": {
|
||||||
|
"schematicCollections": [
|
||||||
|
"@angular-eslint/schematics"
|
||||||
|
]
|
||||||
|
},
|
||||||
"newProjectRoot": "projects",
|
"newProjectRoot": "projects",
|
||||||
"projects": {
|
"projects": {
|
||||||
"fuse": {
|
"fuse": {
|
||||||
@@ -136,9 +141,5 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"defaultProject": "fuse",
|
|
||||||
"cli": {
|
|
||||||
"defaultCollection": "@angular-eslint/schematics"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
16271
package-lock.json
generated
16271
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
107
package.json
107
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@fuse/demo",
|
"name": "fuse-angular",
|
||||||
"version": "14.0.0",
|
"version": "15.0.0",
|
||||||
"description": "Fuse - Angular Admin Template and Starter Project",
|
"description": "Fuse - Angular Admin Template and Starter Project",
|
||||||
"author": "https://themeforest.net/user/srcn",
|
"author": "https://themeforest.net/user/srcn",
|
||||||
"license": "https://themeforest.net/licenses/standard",
|
"license": "https://themeforest.net/licenses/standard",
|
||||||
@@ -14,68 +14,67 @@
|
|||||||
"lint": "ng lint"
|
"lint": "ng lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular/animations": "13.0.1",
|
"@angular/animations": "14.0.0",
|
||||||
"@angular/cdk": "13.0.0",
|
"@angular/cdk": "14.0.0",
|
||||||
"@angular/common": "13.0.1",
|
"@angular/common": "14.0.0",
|
||||||
"@angular/compiler": "13.0.1",
|
"@angular/compiler": "14.0.0",
|
||||||
"@angular/core": "13.0.1",
|
"@angular/core": "14.0.0",
|
||||||
"@angular/forms": "13.0.1",
|
"@angular/forms": "14.0.0",
|
||||||
"@angular/material": "13.0.0",
|
"@angular/material": "14.0.0",
|
||||||
"@angular/material-moment-adapter": "13.0.0",
|
"@angular/material-moment-adapter": "14.0.0",
|
||||||
"@angular/platform-browser": "13.0.1",
|
"@angular/platform-browser": "14.0.0",
|
||||||
"@angular/platform-browser-dynamic": "13.0.1",
|
"@angular/platform-browser-dynamic": "14.0.0",
|
||||||
"@angular/router": "13.0.1",
|
"@angular/router": "14.0.0",
|
||||||
"@ngneat/transloco": "3.1.0",
|
"@ngneat/transloco": "4.0.0",
|
||||||
"apexcharts": "3.29.0",
|
"apexcharts": "3.35.3",
|
||||||
"crypto-js": "3.3.0",
|
"crypto-js": "3.3.0",
|
||||||
"highlight.js": "11.3.1",
|
"highlight.js": "11.5.1",
|
||||||
"lodash-es": "4.17.21",
|
"lodash-es": "4.17.21",
|
||||||
"moment": "2.29.1",
|
"moment": "2.29.3",
|
||||||
"ng-apexcharts": "1.5.12",
|
"ng-apexcharts": "1.7.1",
|
||||||
"ngx-markdown": "13.0.0",
|
"ngx-markdown": "13.1.0",
|
||||||
"ngx-quill": "16.0.1",
|
"ngx-quill": "17.0.0",
|
||||||
"perfect-scrollbar": "1.5.3",
|
"perfect-scrollbar": "1.5.5",
|
||||||
"quill": "1.3.7",
|
"quill": "1.3.7",
|
||||||
"rxjs": "7.4.0",
|
"rxjs": "7.5.5",
|
||||||
"tslib": "2.3.1",
|
"tslib": "2.4.0",
|
||||||
"zone.js": "0.11.4"
|
"zone.js": "0.11.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@angular-devkit/build-angular": "13.0.2",
|
"@angular-devkit/build-angular": "14.0.0",
|
||||||
"@angular-eslint/builder": "12.6.1",
|
"@angular-eslint/builder": "13.2.1",
|
||||||
"@angular-eslint/eslint-plugin": "12.6.1",
|
"@angular-eslint/eslint-plugin": "13.2.1",
|
||||||
"@angular-eslint/eslint-plugin-template": "12.6.1",
|
"@angular-eslint/eslint-plugin-template": "13.2.1",
|
||||||
"@angular-eslint/schematics": "12.6.1",
|
"@angular-eslint/schematics": "13.2.1",
|
||||||
"@angular-eslint/template-parser": "12.6.1",
|
"@angular-eslint/template-parser": "13.2.1",
|
||||||
"@angular/cli": "13.0.2",
|
"@angular/cli": "14.0.0",
|
||||||
"@angular/compiler-cli": "13.0.1",
|
"@angular/compiler-cli": "14.0.0",
|
||||||
"@tailwindcss/aspect-ratio": "0.3.0",
|
"@tailwindcss/aspect-ratio": "0.4.0",
|
||||||
"@tailwindcss/line-clamp": "0.2.2",
|
"@tailwindcss/line-clamp": "0.4.0",
|
||||||
"@tailwindcss/typography": "0.4.1",
|
"@tailwindcss/typography": "0.5.2",
|
||||||
"@types/chroma-js": "2.1.3",
|
"@types/chroma-js": "2.1.3",
|
||||||
"@types/crypto-js": "3.1.47",
|
"@types/crypto-js": "3.1.47",
|
||||||
"@types/highlight.js": "10.1.0",
|
"@types/highlight.js": "10.1.0",
|
||||||
"@types/jasmine": "3.10.2",
|
"@types/jasmine": "4.0.3",
|
||||||
"@types/lodash": "4.14.176",
|
"@types/lodash": "4.14.182",
|
||||||
"@types/lodash-es": "4.17.5",
|
"@types/lodash-es": "4.17.6",
|
||||||
"@types/node": "12.20.37",
|
"@typescript-eslint/eslint-plugin": "5.27.0",
|
||||||
"@typescript-eslint/eslint-plugin": "5.3.1",
|
"@typescript-eslint/parser": "5.27.0",
|
||||||
"@typescript-eslint/parser": "5.3.1",
|
"autoprefixer": "10.4.7",
|
||||||
"autoprefixer": "10.4.0",
|
"chroma-js": "2.4.2",
|
||||||
"chroma-js": "2.1.2",
|
"eslint": "8.16.0",
|
||||||
"eslint": "7.32.0",
|
"eslint-plugin-import": "2.26.0",
|
||||||
"eslint-plugin-import": "2.25.3",
|
"eslint-plugin-jsdoc": "39.3.2",
|
||||||
"eslint-plugin-jsdoc": "37.0.3",
|
|
||||||
"eslint-plugin-prefer-arrow": "1.2.3",
|
"eslint-plugin-prefer-arrow": "1.2.3",
|
||||||
"jasmine-core": "3.10.1",
|
"jasmine-core": "4.1.1",
|
||||||
"karma": "6.3.8",
|
"karma": "6.3.20",
|
||||||
"karma-chrome-launcher": "3.1.0",
|
"karma-chrome-launcher": "3.1.1",
|
||||||
"karma-coverage": "2.0.3",
|
"karma-coverage": "2.2.0",
|
||||||
"karma-jasmine": "4.0.1",
|
"karma-jasmine": "5.0.1",
|
||||||
"karma-jasmine-html-reporter": "1.7.0",
|
"karma-jasmine-html-reporter": "1.7.0",
|
||||||
"lodash": "4.17.21",
|
"lodash": "4.17.21",
|
||||||
"postcss": "8.3.11",
|
"postcss": "8.4.14",
|
||||||
"tailwindcss": "2.2.19",
|
"tailwindcss": "3.0.24",
|
||||||
"typescript": "4.4.4"
|
"typescript": "4.7.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ export class FuseAlertComponent implements OnChanges, OnInit, OnDestroy
|
|||||||
*/
|
*/
|
||||||
@HostBinding('class') get classList(): any
|
@HostBinding('class') get classList(): any
|
||||||
{
|
{
|
||||||
|
/* eslint-disable @typescript-eslint/naming-convention */
|
||||||
return {
|
return {
|
||||||
'fuse-alert-appearance-border' : this.appearance === 'border',
|
'fuse-alert-appearance-border' : this.appearance === 'border',
|
||||||
'fuse-alert-appearance-fill' : this.appearance === 'fill',
|
'fuse-alert-appearance-fill' : this.appearance === 'fill',
|
||||||
@@ -70,6 +71,7 @@ export class FuseAlertComponent implements OnChanges, OnInit, OnDestroy
|
|||||||
'fuse-alert-type-warning' : this.type === 'warning',
|
'fuse-alert-type-warning' : this.type === 'warning',
|
||||||
'fuse-alert-type-error' : this.type === 'error'
|
'fuse-alert-type-error' : this.type === 'error'
|
||||||
};
|
};
|
||||||
|
/* eslint-enable @typescript-eslint/naming-convention */
|
||||||
}
|
}
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -38,12 +38,14 @@ export class FuseCardComponent implements OnChanges
|
|||||||
*/
|
*/
|
||||||
@HostBinding('class') get classList(): any
|
@HostBinding('class') get classList(): any
|
||||||
{
|
{
|
||||||
|
/* eslint-disable @typescript-eslint/naming-convention */
|
||||||
return {
|
return {
|
||||||
'fuse-card-expanded' : this.expanded,
|
'fuse-card-expanded' : this.expanded,
|
||||||
'fuse-card-face-back' : this.flippable && this.face === 'back',
|
'fuse-card-face-back' : this.flippable && this.face === 'back',
|
||||||
'fuse-card-face-front': this.flippable && this.face === 'front',
|
'fuse-card-face-front': this.flippable && this.face === 'front',
|
||||||
'fuse-card-flippable' : this.flippable
|
'fuse-card-flippable' : this.flippable
|
||||||
};
|
};
|
||||||
|
/* eslint-enable @typescript-eslint/naming-convention */
|
||||||
}
|
}
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
/* Variables */
|
/* Variables */
|
||||||
$fuse-drawer-width: 320;
|
:root {
|
||||||
|
--fuse-drawer-width: 320px;
|
||||||
|
}
|
||||||
|
|
||||||
fuse-drawer {
|
fuse-drawer {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
width: #{$fuse-drawer-width}px;
|
width: var(--fuse-drawer-width);
|
||||||
min-width: #{$fuse-drawer-width}px;
|
min-width: var(--fuse-drawer-width);
|
||||||
max-width: #{$fuse-drawer-width}px;
|
max-width: var(--fuse-drawer-width);
|
||||||
z-index: 300;
|
z-index: 300;
|
||||||
box-shadow: 0 2px 8px 0 rgba(0, 0, 0, .35);
|
box-shadow: 0 2px 8px 0 rgba(0, 0, 0, .35);
|
||||||
@apply bg-card;
|
@apply bg-card;
|
||||||
@@ -43,7 +45,7 @@ fuse-drawer {
|
|||||||
|
|
||||||
/* Side mode */
|
/* Side mode */
|
||||||
&.fuse-drawer-mode-side {
|
&.fuse-drawer-mode-side {
|
||||||
margin-left: -#{$fuse-drawer-width}px;
|
margin-left: calc(var(--fuse-drawer-width) * -1);
|
||||||
|
|
||||||
&.fuse-drawer-opened {
|
&.fuse-drawer-opened {
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
@@ -71,7 +73,7 @@ fuse-drawer {
|
|||||||
|
|
||||||
/* Side mode */
|
/* Side mode */
|
||||||
&.fuse-drawer-mode-side {
|
&.fuse-drawer-mode-side {
|
||||||
margin-right: -#{$fuse-drawer-width}px;
|
margin-right: calc(var(--fuse-drawer-width) * -1);
|
||||||
|
|
||||||
&.fuse-drawer-opened {
|
&.fuse-drawer-opened {
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ export class FuseDrawerComponent implements OnChanges, OnInit, OnDestroy
|
|||||||
@Output() readonly positionChanged: EventEmitter<FuseDrawerPosition> = new EventEmitter<FuseDrawerPosition>();
|
@Output() readonly positionChanged: EventEmitter<FuseDrawerPosition> = new EventEmitter<FuseDrawerPosition>();
|
||||||
|
|
||||||
private _animationsEnabled: boolean = false;
|
private _animationsEnabled: boolean = false;
|
||||||
|
private readonly _handleOverlayClick: any;
|
||||||
private _hovered: boolean = false;
|
private _hovered: boolean = false;
|
||||||
private _overlay: HTMLElement;
|
private _overlay: HTMLElement;
|
||||||
private _player: AnimationPlayer;
|
private _player: AnimationPlayer;
|
||||||
@@ -47,6 +48,9 @@ export class FuseDrawerComponent implements OnChanges, OnInit, OnDestroy
|
|||||||
private _fuseUtilsService: FuseUtilsService
|
private _fuseUtilsService: FuseUtilsService
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
|
this._handleOverlayClick = (): void => {
|
||||||
|
this.close();
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------------------------------
|
||||||
@@ -58,6 +62,7 @@ export class FuseDrawerComponent implements OnChanges, OnInit, OnDestroy
|
|||||||
*/
|
*/
|
||||||
@HostBinding('class') get classList(): any
|
@HostBinding('class') get classList(): any
|
||||||
{
|
{
|
||||||
|
/* eslint-disable @typescript-eslint/naming-convention */
|
||||||
return {
|
return {
|
||||||
'fuse-drawer-animations-enabled' : this._animationsEnabled,
|
'fuse-drawer-animations-enabled' : this._animationsEnabled,
|
||||||
'fuse-drawer-fixed' : this.fixed,
|
'fuse-drawer-fixed' : this.fixed,
|
||||||
@@ -66,6 +71,7 @@ export class FuseDrawerComponent implements OnChanges, OnInit, OnDestroy
|
|||||||
'fuse-drawer-opened' : this.opened,
|
'fuse-drawer-opened' : this.opened,
|
||||||
[`fuse-drawer-position-${this.position}`]: true
|
[`fuse-drawer-position-${this.position}`]: true
|
||||||
};
|
};
|
||||||
|
/* eslint-enable @typescript-eslint/naming-convention */
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -318,12 +324,6 @@ export class FuseDrawerComponent implements OnChanges, OnInit, OnDestroy
|
|||||||
// Create the backdrop element
|
// Create the backdrop element
|
||||||
this._overlay = this._renderer2.createElement('div');
|
this._overlay = this._renderer2.createElement('div');
|
||||||
|
|
||||||
// Return if overlay couldn't be create for some reason
|
|
||||||
if ( !this._overlay )
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add a class to the backdrop element
|
// Add a class to the backdrop element
|
||||||
this._overlay.classList.add('fuse-drawer-overlay');
|
this._overlay.classList.add('fuse-drawer-overlay');
|
||||||
|
|
||||||
@@ -342,27 +342,17 @@ export class FuseDrawerComponent implements OnChanges, OnInit, OnDestroy
|
|||||||
// Append the backdrop to the parent of the drawer
|
// Append the backdrop to the parent of the drawer
|
||||||
this._renderer2.appendChild(this._elementRef.nativeElement.parentElement, this._overlay);
|
this._renderer2.appendChild(this._elementRef.nativeElement.parentElement, this._overlay);
|
||||||
|
|
||||||
// Create the enter animation and attach it to the player
|
// Create enter animation and attach it to the player
|
||||||
this._player = this._animationBuilder.build([
|
this._player = this._animationBuilder.build([
|
||||||
style({opacity: 0}),
|
style({opacity: 0}),
|
||||||
animate('300ms cubic-bezier(0.25, 0.8, 0.25, 1)', style({opacity: 1}))
|
animate('300ms cubic-bezier(0.25, 0.8, 0.25, 1)', style({opacity: 1}))
|
||||||
]).create(this._overlay);
|
]).create(this._overlay);
|
||||||
|
|
||||||
// Once the animation is done...
|
|
||||||
this._player.onDone(() => {
|
|
||||||
|
|
||||||
// Destroy the player
|
|
||||||
this._player.destroy();
|
|
||||||
this._player = null;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Play the animation
|
// Play the animation
|
||||||
this._player.play();
|
this._player.play();
|
||||||
|
|
||||||
// Add an event listener to the overlay
|
// Add an event listener to the overlay
|
||||||
this._overlay.addEventListener('click', () => {
|
this._overlay.addEventListener('click', this._handleOverlayClick);
|
||||||
this.close();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -388,14 +378,13 @@ export class FuseDrawerComponent implements OnChanges, OnInit, OnDestroy
|
|||||||
// Once the animation is done...
|
// Once the animation is done...
|
||||||
this._player.onDone(() => {
|
this._player.onDone(() => {
|
||||||
|
|
||||||
// Destroy the player
|
// If the overlay still exists...
|
||||||
this._player.destroy();
|
|
||||||
this._player = null;
|
|
||||||
|
|
||||||
// If the backdrop still exists...
|
|
||||||
if ( this._overlay )
|
if ( this._overlay )
|
||||||
{
|
{
|
||||||
// Remove the backdrop
|
// Remove the event listener
|
||||||
|
this._overlay.removeEventListener('click', this._handleOverlayClick);
|
||||||
|
|
||||||
|
// Remove the overlay
|
||||||
this._overlay.parentNode.removeChild(this._overlay);
|
this._overlay.parentNode.removeChild(this._overlay);
|
||||||
this._overlay = null;
|
this._overlay = null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import { FuseMediaWatcherService } from '@fuse/services/media-watcher';
|
|||||||
@Component({
|
@Component({
|
||||||
selector : 'fuse-masonry',
|
selector : 'fuse-masonry',
|
||||||
templateUrl : './masonry.component.html',
|
templateUrl : './masonry.component.html',
|
||||||
styleUrls : ['./masonry.component.scss'],
|
|
||||||
encapsulation: ViewEncapsulation.None,
|
encapsulation: ViewEncapsulation.None,
|
||||||
animations : fuseAnimations,
|
animations : fuseAnimations,
|
||||||
exportAs : 'fuseMasonry'
|
exportAs : 'fuseMasonry'
|
||||||
|
|||||||
@@ -10,6 +10,10 @@
|
|||||||
class="fuse-horizontal-navigation-item"
|
class="fuse-horizontal-navigation-item"
|
||||||
[ngClass]="{'fuse-horizontal-navigation-item-active-forced': item.active}"
|
[ngClass]="{'fuse-horizontal-navigation-item-active-forced': item.active}"
|
||||||
[routerLink]="[item.link]"
|
[routerLink]="[item.link]"
|
||||||
|
[fragment]="item.fragment ?? null"
|
||||||
|
[preserveFragment]="item.preserveFragment ?? false"
|
||||||
|
[queryParams]="item.queryParams ?? null"
|
||||||
|
[queryParamsHandling]="item.queryParamsHandling ?? null"
|
||||||
[routerLinkActive]="'fuse-horizontal-navigation-item-active'"
|
[routerLinkActive]="'fuse-horizontal-navigation-item-active'"
|
||||||
[routerLinkActiveOptions]="isActiveMatchOptions"
|
[routerLinkActiveOptions]="isActiveMatchOptions"
|
||||||
[matTooltip]="item.tooltip || ''">
|
[matTooltip]="item.tooltip || ''">
|
||||||
@@ -45,6 +49,10 @@
|
|||||||
class="fuse-horizontal-navigation-item"
|
class="fuse-horizontal-navigation-item"
|
||||||
[ngClass]="{'fuse-horizontal-navigation-item-active-forced': item.active}"
|
[ngClass]="{'fuse-horizontal-navigation-item-active-forced': item.active}"
|
||||||
[routerLink]="[item.link]"
|
[routerLink]="[item.link]"
|
||||||
|
[fragment]="item.fragment ?? null"
|
||||||
|
[preserveFragment]="item.preserveFragment ?? false"
|
||||||
|
[queryParams]="item.queryParams ?? null"
|
||||||
|
[queryParamsHandling]="item.queryParamsHandling ?? null"
|
||||||
[routerLinkActive]="'fuse-horizontal-navigation-item-active'"
|
[routerLinkActive]="'fuse-horizontal-navigation-item-active'"
|
||||||
[routerLinkActiveOptions]="isActiveMatchOptions"
|
[routerLinkActiveOptions]="isActiveMatchOptions"
|
||||||
[matTooltip]="item.tooltip || ''"
|
[matTooltip]="item.tooltip || ''"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { IsActiveMatchOptions } from '@angular/router';
|
import { IsActiveMatchOptions, Params, QueryParamsHandling } from '@angular/router';
|
||||||
|
|
||||||
export interface FuseNavigationItem
|
export interface FuseNavigationItem
|
||||||
{
|
{
|
||||||
@@ -17,6 +17,10 @@ export interface FuseNavigationItem
|
|||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
tooltip?: string;
|
tooltip?: string;
|
||||||
link?: string;
|
link?: string;
|
||||||
|
fragment?: string;
|
||||||
|
preserveFragment?: boolean;
|
||||||
|
queryParams?: Params | null;
|
||||||
|
queryParamsHandling?: QueryParamsHandling | null;
|
||||||
externalLink?: boolean;
|
externalLink?: boolean;
|
||||||
target?:
|
target?:
|
||||||
| '_blank'
|
| '_blank'
|
||||||
|
|||||||
@@ -10,6 +10,10 @@
|
|||||||
class="fuse-vertical-navigation-item"
|
class="fuse-vertical-navigation-item"
|
||||||
[ngClass]="{'fuse-vertical-navigation-item-active-forced': item.active}"
|
[ngClass]="{'fuse-vertical-navigation-item-active-forced': item.active}"
|
||||||
[routerLink]="[item.link]"
|
[routerLink]="[item.link]"
|
||||||
|
[fragment]="item.fragment ?? null"
|
||||||
|
[preserveFragment]="item.preserveFragment ?? false"
|
||||||
|
[queryParams]="item.queryParams ?? null"
|
||||||
|
[queryParamsHandling]="item.queryParamsHandling ?? null"
|
||||||
[routerLinkActive]="'fuse-vertical-navigation-item-active'"
|
[routerLinkActive]="'fuse-vertical-navigation-item-active'"
|
||||||
[routerLinkActiveOptions]="isActiveMatchOptions"
|
[routerLinkActiveOptions]="isActiveMatchOptions"
|
||||||
[matTooltip]="item.tooltip || ''">
|
[matTooltip]="item.tooltip || ''">
|
||||||
@@ -45,6 +49,10 @@
|
|||||||
class="fuse-vertical-navigation-item"
|
class="fuse-vertical-navigation-item"
|
||||||
[ngClass]="{'fuse-vertical-navigation-item-active-forced': item.active}"
|
[ngClass]="{'fuse-vertical-navigation-item-active-forced': item.active}"
|
||||||
[routerLink]="[item.link]"
|
[routerLink]="[item.link]"
|
||||||
|
[fragment]="item.fragment ?? null"
|
||||||
|
[preserveFragment]="item.preserveFragment ?? false"
|
||||||
|
[queryParams]="item.queryParams ?? null"
|
||||||
|
[queryParamsHandling]="item.queryParamsHandling ?? null"
|
||||||
[routerLinkActive]="'fuse-vertical-navigation-item-active'"
|
[routerLinkActive]="'fuse-vertical-navigation-item-active'"
|
||||||
[routerLinkActiveOptions]="isActiveMatchOptions"
|
[routerLinkActiveOptions]="isActiveMatchOptions"
|
||||||
[matTooltip]="item.tooltip || ''"
|
[matTooltip]="item.tooltip || ''"
|
||||||
|
|||||||
@@ -48,10 +48,12 @@ export class FuseVerticalNavigationCollapsableItemComponent implements OnInit, O
|
|||||||
*/
|
*/
|
||||||
@HostBinding('class') get classList(): any
|
@HostBinding('class') get classList(): any
|
||||||
{
|
{
|
||||||
|
/* eslint-disable @typescript-eslint/naming-convention */
|
||||||
return {
|
return {
|
||||||
'fuse-vertical-navigation-item-collapsed': this.isCollapsed,
|
'fuse-vertical-navigation-item-collapsed': this.isCollapsed,
|
||||||
'fuse-vertical-navigation-item-expanded' : this.isExpanded
|
'fuse-vertical-navigation-item-expanded' : this.isExpanded
|
||||||
};
|
};
|
||||||
|
/* eslint-enable @typescript-eslint/naming-convention */
|
||||||
}
|
}
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -1,20 +1,22 @@
|
|||||||
/* Variables */
|
/* Variables */
|
||||||
$fuse-vertical-navigation-compact-width: 112px;
|
:root {
|
||||||
|
--fuse-vertical-navigation-compact-width: 112px;
|
||||||
|
}
|
||||||
|
|
||||||
fuse-vertical-navigation {
|
fuse-vertical-navigation {
|
||||||
|
|
||||||
/* Compact appearance overrides */
|
/* Compact appearance overrides */
|
||||||
&.fuse-vertical-navigation-appearance-compact {
|
&.fuse-vertical-navigation-appearance-compact {
|
||||||
width: $fuse-vertical-navigation-compact-width;
|
width: var(--fuse-vertical-navigation-compact-width);
|
||||||
min-width: $fuse-vertical-navigation-compact-width;
|
min-width: var(--fuse-vertical-navigation-compact-width);
|
||||||
max-width: $fuse-vertical-navigation-compact-width;
|
max-width: var(--fuse-vertical-navigation-compact-width);
|
||||||
|
|
||||||
/* Left positioned */
|
/* Left positioned */
|
||||||
&.fuse-vertical-navigation-position-left {
|
&.fuse-vertical-navigation-position-left {
|
||||||
|
|
||||||
/* Side mode */
|
/* Side mode */
|
||||||
&.fuse-vertical-navigation-mode-side {
|
&.fuse-vertical-navigation-mode-side {
|
||||||
margin-left: -$fuse-vertical-navigation-compact-width;
|
margin-left: calc(var(--fuse-vertical-navigation-compact-width) * -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Opened */
|
/* Opened */
|
||||||
@@ -28,7 +30,7 @@ fuse-vertical-navigation {
|
|||||||
|
|
||||||
/* Side mode */
|
/* Side mode */
|
||||||
&.fuse-vertical-navigation-mode-side {
|
&.fuse-vertical-navigation-mode-side {
|
||||||
margin-right: -$fuse-vertical-navigation-compact-width;
|
margin-right: calc(var(--fuse-vertical-navigation-compact-width) * -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Opened */
|
/* Opened */
|
||||||
@@ -39,7 +41,7 @@ fuse-vertical-navigation {
|
|||||||
/* Aside wrapper */
|
/* Aside wrapper */
|
||||||
.fuse-vertical-navigation-aside-wrapper {
|
.fuse-vertical-navigation-aside-wrapper {
|
||||||
left: auto;
|
left: auto;
|
||||||
right: $fuse-vertical-navigation-compact-width;
|
right: var(--fuse-vertical-navigation-compact-width);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,7 +106,7 @@ fuse-vertical-navigation {
|
|||||||
|
|
||||||
/* Aside wrapper */
|
/* Aside wrapper */
|
||||||
.fuse-vertical-navigation-aside-wrapper {
|
.fuse-vertical-navigation-aside-wrapper {
|
||||||
left: $fuse-vertical-navigation-compact-width;
|
left: var(--fuse-vertical-navigation-compact-width);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
/* Variables */
|
/* Variables */
|
||||||
$fuse-vertical-navigation-width: 280px;
|
:root {
|
||||||
|
--fuse-vertical-navigation-width: 280px;
|
||||||
|
}
|
||||||
|
|
||||||
fuse-vertical-navigation {
|
fuse-vertical-navigation {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
@@ -7,9 +9,9 @@ fuse-vertical-navigation {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
flex: 1 0 auto;
|
flex: 1 0 auto;
|
||||||
top: 0;
|
top: 0;
|
||||||
width: $fuse-vertical-navigation-width;
|
width: var(--fuse-vertical-navigation-width);
|
||||||
min-width: $fuse-vertical-navigation-width;
|
min-width: var(--fuse-vertical-navigation-width);
|
||||||
max-width: $fuse-vertical-navigation-width;
|
max-width: var(--fuse-vertical-navigation-width);
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
max-height: 100vh;
|
max-height: 100vh;
|
||||||
@@ -45,7 +47,7 @@ fuse-vertical-navigation {
|
|||||||
|
|
||||||
/* Side mode */
|
/* Side mode */
|
||||||
&.fuse-vertical-navigation-mode-side {
|
&.fuse-vertical-navigation-mode-side {
|
||||||
margin-left: -$fuse-vertical-navigation-width;
|
margin-left: calc(#{var(--fuse-vertical-navigation-width)} * -1);
|
||||||
|
|
||||||
&.fuse-vertical-navigation-opened {
|
&.fuse-vertical-navigation-opened {
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
@@ -73,7 +75,7 @@ fuse-vertical-navigation {
|
|||||||
|
|
||||||
/* Side mode */
|
/* Side mode */
|
||||||
&.fuse-vertical-navigation-mode-side {
|
&.fuse-vertical-navigation-mode-side {
|
||||||
margin-right: -$fuse-vertical-navigation-width;
|
margin-right: calc(var(--fuse-vertical-navigation-width) * -1);
|
||||||
|
|
||||||
&.fuse-vertical-navigation-opened {
|
&.fuse-vertical-navigation-opened {
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
@@ -170,8 +172,8 @@ fuse-vertical-navigation {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
top: 0;
|
top: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: $fuse-vertical-navigation-width;
|
left: var(--fuse-vertical-navigation-width);
|
||||||
width: $fuse-vertical-navigation-width;
|
width: var(--fuse-vertical-navigation-width);
|
||||||
height: 100%;
|
height: 100%;
|
||||||
z-index: 5;
|
z-index: 5;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
@@ -196,7 +198,7 @@ fuse-vertical-navigation {
|
|||||||
|
|
||||||
.fuse-vertical-navigation-aside-wrapper {
|
.fuse-vertical-navigation-aside-wrapper {
|
||||||
left: auto;
|
left: auto;
|
||||||
right: $fuse-vertical-navigation-width;
|
right: var(--fuse-vertical-navigation-width);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -335,7 +337,10 @@ fuse-vertical-navigation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
> .fuse-vertical-navigation-item-children {
|
> .fuse-vertical-navigation-item-children {
|
||||||
margin-top: 6px;
|
|
||||||
|
> *:first-child {
|
||||||
|
margin-top: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
> *:last-child {
|
> *:last-child {
|
||||||
padding-bottom: 6px;
|
padding-bottom: 6px;
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
/* Variables */
|
/* Variables */
|
||||||
$fuse-vertical-navigation-width: 280px;
|
:root {
|
||||||
$fuse-vertical-navigation-dense-width: 80px;
|
--fuse-vertical-navigation-width: 280px;
|
||||||
|
--fuse-vertical-navigation-dense-width: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
fuse-vertical-navigation {
|
fuse-vertical-navigation {
|
||||||
|
|
||||||
@@ -8,16 +10,16 @@ fuse-vertical-navigation {
|
|||||||
&.fuse-vertical-navigation-appearance-dense {
|
&.fuse-vertical-navigation-appearance-dense {
|
||||||
|
|
||||||
&:not(.fuse-vertical-navigation-mode-over) {
|
&:not(.fuse-vertical-navigation-mode-over) {
|
||||||
width: $fuse-vertical-navigation-dense-width;
|
width: var(--fuse-vertical-navigation-dense-width);
|
||||||
min-width: $fuse-vertical-navigation-dense-width;
|
min-width: var(--fuse-vertical-navigation-dense-width);
|
||||||
max-width: $fuse-vertical-navigation-dense-width;
|
max-width: var(--fuse-vertical-navigation-dense-width);
|
||||||
|
|
||||||
/* Left positioned */
|
/* Left positioned */
|
||||||
&.fuse-vertical-navigation-position-left {
|
&.fuse-vertical-navigation-position-left {
|
||||||
|
|
||||||
/* Side mode */
|
/* Side mode */
|
||||||
&.fuse-vertical-navigation-mode-side {
|
&.fuse-vertical-navigation-mode-side {
|
||||||
margin-left: -$fuse-vertical-navigation-dense-width;
|
margin-left: calc(var(--fuse-vertical-navigation-dense-width) * -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Opened */
|
/* Opened */
|
||||||
@@ -31,7 +33,7 @@ fuse-vertical-navigation {
|
|||||||
|
|
||||||
/* Side mode */
|
/* Side mode */
|
||||||
&.fuse-vertical-navigation-mode-side {
|
&.fuse-vertical-navigation-mode-side {
|
||||||
margin-right: -$fuse-vertical-navigation-dense-width;
|
margin-right: calc(var(--fuse-vertical-navigation-dense-width) * -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Opened */
|
/* Opened */
|
||||||
@@ -42,14 +44,14 @@ fuse-vertical-navigation {
|
|||||||
/* Aside wrapper */
|
/* Aside wrapper */
|
||||||
.fuse-vertical-navigation-aside-wrapper {
|
.fuse-vertical-navigation-aside-wrapper {
|
||||||
left: auto;
|
left: auto;
|
||||||
right: $fuse-vertical-navigation-dense-width;
|
right: var(--fuse-vertical-navigation-dense-width);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.fuse-vertical-navigation-hover {
|
&.fuse-vertical-navigation-hover {
|
||||||
|
|
||||||
.fuse-vertical-navigation-aside-wrapper {
|
.fuse-vertical-navigation-aside-wrapper {
|
||||||
left: auto;
|
left: auto;
|
||||||
right: $fuse-vertical-navigation-width;
|
right: var(--fuse-vertical-navigation-width);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -69,9 +71,9 @@ fuse-vertical-navigation {
|
|||||||
.fuse-vertical-navigation-item-wrapper {
|
.fuse-vertical-navigation-item-wrapper {
|
||||||
|
|
||||||
.fuse-vertical-navigation-item {
|
.fuse-vertical-navigation-item {
|
||||||
width: $fuse-vertical-navigation-dense-width - 24px;
|
width: calc(var(--fuse-vertical-navigation-dense-width) - 24px);
|
||||||
min-width: $fuse-vertical-navigation-dense-width - 24px;
|
min-width: calc(var(--fuse-vertical-navigation-dense-width) - 24px);
|
||||||
max-width: $fuse-vertical-navigation-dense-width - 24px;
|
max-width: calc(var(--fuse-vertical-navigation-dense-width) - 24px);
|
||||||
|
|
||||||
.fuse-vertical-navigation-item-arrow,
|
.fuse-vertical-navigation-item-arrow,
|
||||||
.fuse-vertical-navigation-item-badge,
|
.fuse-vertical-navigation-item-badge,
|
||||||
@@ -142,23 +144,23 @@ fuse-vertical-navigation {
|
|||||||
|
|
||||||
/* Aside wrapper */
|
/* Aside wrapper */
|
||||||
.fuse-vertical-navigation-aside-wrapper {
|
.fuse-vertical-navigation-aside-wrapper {
|
||||||
left: $fuse-vertical-navigation-dense-width;
|
left: var(--fuse-vertical-navigation-dense-width);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Hover */
|
/* Hover */
|
||||||
&.fuse-vertical-navigation-hover {
|
&.fuse-vertical-navigation-hover {
|
||||||
|
|
||||||
.fuse-vertical-navigation-wrapper {
|
.fuse-vertical-navigation-wrapper {
|
||||||
width: $fuse-vertical-navigation-width;
|
width: var(--fuse-vertical-navigation-width);
|
||||||
|
|
||||||
.fuse-vertical-navigation-content {
|
.fuse-vertical-navigation-content {
|
||||||
|
|
||||||
.fuse-vertical-navigation-item-wrapper {
|
.fuse-vertical-navigation-item-wrapper {
|
||||||
|
|
||||||
.fuse-vertical-navigation-item {
|
.fuse-vertical-navigation-item {
|
||||||
width: $fuse-vertical-navigation-width - 24px;
|
width: calc(var(--fuse-vertical-navigation-width) - 24px);
|
||||||
min-width: $fuse-vertical-navigation-width - 24px;
|
min-width: calc(var(--fuse-vertical-navigation-width) - 24px);
|
||||||
max-width: $fuse-vertical-navigation-width - 24px;
|
max-width: calc(var(--fuse-vertical-navigation-width) - 24px);
|
||||||
|
|
||||||
.fuse-vertical-navigation-item-arrow,
|
.fuse-vertical-navigation-item-arrow,
|
||||||
.fuse-vertical-navigation-item-badge,
|
.fuse-vertical-navigation-item-badge,
|
||||||
@@ -173,7 +175,7 @@ fuse-vertical-navigation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.fuse-vertical-navigation-aside-wrapper {
|
.fuse-vertical-navigation-aside-wrapper {
|
||||||
left: $fuse-vertical-navigation-width;
|
left: var(--fuse-vertical-navigation-width);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,21 @@
|
|||||||
/* Variables */
|
/* Variables */
|
||||||
$fuse-vertical-navigation-thin-width: 80px;
|
:root {
|
||||||
|
--fuse-vertical-navigation-thin-width: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
fuse-vertical-navigation {
|
fuse-vertical-navigation {
|
||||||
|
|
||||||
/* Thin appearance overrides */
|
/* Thin appearance overrides */
|
||||||
&.fuse-vertical-navigation-appearance-thin {
|
&.fuse-vertical-navigation-appearance-thin {
|
||||||
width: $fuse-vertical-navigation-thin-width;
|
width: var(--fuse-vertical-navigation-thin-width);
|
||||||
min-width: $fuse-vertical-navigation-thin-width;
|
min-width: var(--fuse-vertical-navigation-thin-width);
|
||||||
max-width: $fuse-vertical-navigation-thin-width;
|
max-width: var(--fuse-vertical-navigation-thin-width);
|
||||||
|
|
||||||
/* Left positioned */
|
/* Left positioned */
|
||||||
&.fuse-vertical-navigation-position-left {
|
&.fuse-vertical-navigation-position-left {
|
||||||
|
|
||||||
&.fuse-vertical-navigation-mode-side {
|
&.fuse-vertical-navigation-mode-side {
|
||||||
margin-left: -$fuse-vertical-navigation-thin-width;
|
margin-left: calc(var(--fuse-vertical-navigation-thin-width) * -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.fuse-vertical-navigation-opened {
|
&.fuse-vertical-navigation-opened {
|
||||||
@@ -25,7 +27,7 @@ fuse-vertical-navigation {
|
|||||||
&.fuse-vertical-navigation-position-right {
|
&.fuse-vertical-navigation-position-right {
|
||||||
|
|
||||||
&.fuse-vertical-navigation-mode-side {
|
&.fuse-vertical-navigation-mode-side {
|
||||||
margin-right: -$fuse-vertical-navigation-thin-width;
|
margin-right: calc(var(--fuse-vertical-navigation-thin-width) * -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.fuse-vertical-navigation-opened {
|
&.fuse-vertical-navigation-opened {
|
||||||
@@ -34,7 +36,7 @@ fuse-vertical-navigation {
|
|||||||
|
|
||||||
.fuse-vertical-navigation-aside-wrapper {
|
.fuse-vertical-navigation-aside-wrapper {
|
||||||
left: auto;
|
left: auto;
|
||||||
right: $fuse-vertical-navigation-thin-width;
|
right: var(--fuse-vertical-navigation-thin-width);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,7 +93,7 @@ fuse-vertical-navigation {
|
|||||||
|
|
||||||
/* Aside wrapper */
|
/* Aside wrapper */
|
||||||
.fuse-vertical-navigation-aside-wrapper {
|
.fuse-vertical-navigation-aside-wrapper {
|
||||||
left: $fuse-vertical-navigation-thin-width;
|
left: var(--fuse-vertical-navigation-thin-width);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@import 'styles/appearances/default';
|
@use 'styles/appearances/default';
|
||||||
@import 'styles/appearances/compact';
|
@use 'styles/appearances/compact';
|
||||||
@import 'styles/appearances/dense';
|
@use 'styles/appearances/dense';
|
||||||
@import 'styles/appearances/thin';
|
@use 'styles/appearances/thin';
|
||||||
|
|||||||
@@ -89,6 +89,7 @@ export class FuseVerticalNavigationComponent implements OnChanges, OnInit, After
|
|||||||
*/
|
*/
|
||||||
@HostBinding('class') get classList(): any
|
@HostBinding('class') get classList(): any
|
||||||
{
|
{
|
||||||
|
/* eslint-disable @typescript-eslint/naming-convention */
|
||||||
return {
|
return {
|
||||||
'fuse-vertical-navigation-animations-enabled' : this._animationsEnabled,
|
'fuse-vertical-navigation-animations-enabled' : this._animationsEnabled,
|
||||||
[`fuse-vertical-navigation-appearance-${this.appearance}`]: true,
|
[`fuse-vertical-navigation-appearance-${this.appearance}`]: true,
|
||||||
@@ -100,6 +101,7 @@ export class FuseVerticalNavigationComponent implements OnChanges, OnInit, After
|
|||||||
'fuse-vertical-navigation-position-left' : this.position === 'left',
|
'fuse-vertical-navigation-position-left' : this.position === 'left',
|
||||||
'fuse-vertical-navigation-position-right' : this.position === 'right'
|
'fuse-vertical-navigation-position-right' : this.position === 'right'
|
||||||
};
|
};
|
||||||
|
/* eslint-enable @typescript-eslint/naming-convention */
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import { MAT_FORM_FIELD_DEFAULT_OPTIONS } from '@angular/material/form-field';
|
|||||||
import { FuseConfirmationModule } from '@fuse/services/confirmation';
|
import { FuseConfirmationModule } from '@fuse/services/confirmation';
|
||||||
import { FuseLoadingModule } from '@fuse/services/loading';
|
import { FuseLoadingModule } from '@fuse/services/loading';
|
||||||
import { FuseMediaWatcherModule } from '@fuse/services/media-watcher/media-watcher.module';
|
import { FuseMediaWatcherModule } from '@fuse/services/media-watcher/media-watcher.module';
|
||||||
|
import { FusePlatformModule } from '@fuse/services/platform/platform.module';
|
||||||
import { FuseSplashScreenModule } from '@fuse/services/splash-screen/splash-screen.module';
|
import { FuseSplashScreenModule } from '@fuse/services/splash-screen/splash-screen.module';
|
||||||
import { FuseTailwindConfigModule } from '@fuse/services/tailwind/tailwind.module';
|
|
||||||
import { FuseUtilsModule } from '@fuse/services/utils/utils.module';
|
import { FuseUtilsModule } from '@fuse/services/utils/utils.module';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
@@ -13,8 +13,8 @@ import { FuseUtilsModule } from '@fuse/services/utils/utils.module';
|
|||||||
FuseConfirmationModule,
|
FuseConfirmationModule,
|
||||||
FuseLoadingModule,
|
FuseLoadingModule,
|
||||||
FuseMediaWatcherModule,
|
FuseMediaWatcherModule,
|
||||||
|
FusePlatformModule,
|
||||||
FuseSplashScreenModule,
|
FuseSplashScreenModule,
|
||||||
FuseTailwindConfigModule,
|
|
||||||
FuseUtilsModule
|
FuseUtilsModule
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ export class FuseConfigService
|
|||||||
this._config.next(config);
|
this._config.next(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/member-ordering
|
||||||
get config$(): Observable<any>
|
get config$(): Observable<any>
|
||||||
{
|
{
|
||||||
return this._config.asObservable();
|
return this._config.asObservable();
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
import { Component, Inject, OnInit, ViewEncapsulation } from '@angular/core';
|
import { Component, Inject, ViewEncapsulation } from '@angular/core';
|
||||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
import { MAT_DIALOG_DATA } from '@angular/material/dialog';
|
||||||
import { FuseConfirmationConfig } from '@fuse/services/confirmation/confirmation.types';
|
import { FuseConfirmationConfig } from '@fuse/services/confirmation/confirmation.types';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector : 'fuse-confirmation-dialog',
|
selector : 'fuse-confirmation-dialog',
|
||||||
templateUrl : './dialog.component.html',
|
templateUrl : './dialog.component.html',
|
||||||
styles : [
|
styles : [
|
||||||
/* language=SCSS */
|
|
||||||
`
|
`
|
||||||
.fuse-confirmation-dialog-panel {
|
.fuse-confirmation-dialog-panel {
|
||||||
@screen md {
|
@screen md {
|
||||||
@@ -21,32 +20,13 @@ import { FuseConfirmationConfig } from '@fuse/services/confirmation/confirmation
|
|||||||
],
|
],
|
||||||
encapsulation: ViewEncapsulation.None
|
encapsulation: ViewEncapsulation.None
|
||||||
})
|
})
|
||||||
export class FuseConfirmationDialogComponent implements OnInit
|
export class FuseConfirmationDialogComponent
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
*/
|
*/
|
||||||
constructor(
|
constructor(@Inject(MAT_DIALOG_DATA) public data: FuseConfirmationConfig)
|
||||||
@Inject(MAT_DIALOG_DATA) public data: FuseConfirmationConfig,
|
|
||||||
public matDialogRef: MatDialogRef<FuseConfirmationDialogComponent>
|
|
||||||
)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------------------------------
|
|
||||||
// @ Lifecycle hooks
|
|
||||||
// -----------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* On init
|
|
||||||
*/
|
|
||||||
ngOnInit(): void
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------------------------------
|
|
||||||
// @ Public methods
|
|
||||||
// -----------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { BreakpointObserver, BreakpointState } from '@angular/cdk/layout';
|
import { BreakpointObserver, BreakpointState } from '@angular/cdk/layout';
|
||||||
import { map, Observable, ReplaySubject, switchMap } from 'rxjs';
|
import { map, Observable, ReplaySubject, switchMap } from 'rxjs';
|
||||||
import { FuseTailwindService } from '@fuse/services/tailwind/tailwind.service';
|
import { fromPairs } from 'lodash-es';
|
||||||
|
import { FuseConfigService } from '@fuse/services/config';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class FuseMediaWatcherService
|
export class FuseMediaWatcherService
|
||||||
@@ -13,11 +14,12 @@ export class FuseMediaWatcherService
|
|||||||
*/
|
*/
|
||||||
constructor(
|
constructor(
|
||||||
private _breakpointObserver: BreakpointObserver,
|
private _breakpointObserver: BreakpointObserver,
|
||||||
private _fuseTailwindConfigService: FuseTailwindService
|
private _fuseConfigService: FuseConfigService
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
this._fuseTailwindConfigService.tailwindConfig$.pipe(
|
this._fuseConfigService.config$.pipe(
|
||||||
switchMap(config => this._breakpointObserver.observe(Object.values(config.breakpoints)).pipe(
|
map(config => fromPairs(Object.entries(config.screens).map(([alias, screen]) => ([alias, `(min-width: ${screen})`])))),
|
||||||
|
switchMap(screens => this._breakpointObserver.observe(Object.values(screens)).pipe(
|
||||||
map((state) => {
|
map((state) => {
|
||||||
|
|
||||||
// Prepare the observable values and set their defaults
|
// Prepare the observable values and set their defaults
|
||||||
@@ -29,7 +31,7 @@ export class FuseMediaWatcherService
|
|||||||
for ( const [query] of matchingBreakpoints )
|
for ( const [query] of matchingBreakpoints )
|
||||||
{
|
{
|
||||||
// Find the alias of the matching query
|
// Find the alias of the matching query
|
||||||
const matchingAlias = Object.entries(config.breakpoints).find(([alias, q]) => q === query)[0];
|
const matchingAlias = Object.entries(screens).find(([alias, q]) => q === query)[0];
|
||||||
|
|
||||||
// Add the matching query to the observable values
|
// Add the matching query to the observable values
|
||||||
if ( matchingAlias )
|
if ( matchingAlias )
|
||||||
|
|||||||
1
src/@fuse/services/platform/index.ts
Normal file
1
src/@fuse/services/platform/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from '@fuse/services/platform/public-api';
|
||||||
17
src/@fuse/services/platform/platform.module.ts
Normal file
17
src/@fuse/services/platform/platform.module.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { FusePlatformService } from '@fuse/services/platform/platform.service';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
providers: [
|
||||||
|
FusePlatformService
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class FusePlatformModule
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*/
|
||||||
|
constructor(private _fusePlatformService: FusePlatformService)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
59
src/@fuse/services/platform/platform.service.ts
Normal file
59
src/@fuse/services/platform/platform.service.ts
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { Platform } from '@angular/cdk/platform';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class FusePlatformService
|
||||||
|
{
|
||||||
|
osName = 'os-unknown';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*/
|
||||||
|
constructor(private _platform: Platform)
|
||||||
|
{
|
||||||
|
// If the platform is not a browser, return immediately
|
||||||
|
if ( !this._platform.isBrowser )
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Windows
|
||||||
|
if ( navigator.userAgent.includes('Win') )
|
||||||
|
{
|
||||||
|
this.osName = 'os-windows';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mac OS
|
||||||
|
if ( navigator.userAgent.includes('Mac') )
|
||||||
|
{
|
||||||
|
this.osName = 'os-mac';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unix
|
||||||
|
if ( navigator.userAgent.includes('X11') )
|
||||||
|
{
|
||||||
|
this.osName = 'os-unix';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Linux
|
||||||
|
if ( navigator.userAgent.includes('Linux') )
|
||||||
|
{
|
||||||
|
this.osName = 'os-linux';
|
||||||
|
}
|
||||||
|
|
||||||
|
// iOS
|
||||||
|
if ( this._platform.IOS )
|
||||||
|
{
|
||||||
|
this.osName = 'os-ios';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Android
|
||||||
|
if ( this._platform.ANDROID )
|
||||||
|
{
|
||||||
|
this.osName = 'os-android';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
1
src/@fuse/services/platform/public-api.ts
Normal file
1
src/@fuse/services/platform/public-api.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from '@fuse/services/platform/platform.service';
|
||||||
@@ -1 +0,0 @@
|
|||||||
export * from '@fuse/services/tailwind/public-api';
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
export * from '@fuse/services/tailwind/tailwind.module';
|
|
||||||
export * from '@fuse/services/tailwind/tailwind.service';
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
import { NgModule } from '@angular/core';
|
|
||||||
import { FuseTailwindService } from '@fuse/services/tailwind/tailwind.service';
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
providers: [
|
|
||||||
FuseTailwindService
|
|
||||||
]
|
|
||||||
})
|
|
||||||
export class FuseTailwindConfigModule
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Constructor
|
|
||||||
*/
|
|
||||||
constructor(private _fuseTailwindConfigService: FuseTailwindService)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
import { Injectable } from '@angular/core';
|
|
||||||
import { Observable, ReplaySubject } from 'rxjs';
|
|
||||||
import { fromPairs, map } from 'lodash-es';
|
|
||||||
import * as extractedTailwindConfigStyle from '@fuse/styles/core/tailwind-config.scss';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class FuseTailwindService
|
|
||||||
{
|
|
||||||
private _tailwindConfig: ReplaySubject<any> = new ReplaySubject<any>(1);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor
|
|
||||||
*/
|
|
||||||
constructor()
|
|
||||||
{
|
|
||||||
// Prepare the config object
|
|
||||||
const config: any = {};
|
|
||||||
|
|
||||||
// Extract the style from the class
|
|
||||||
const regexpForClass = /\.fuse-tailwind-extracted-config\s\{([\s\S]*)\}/g;
|
|
||||||
const style = regexpForClass.exec(extractedTailwindConfigStyle.default)[1].trim();
|
|
||||||
|
|
||||||
// Extract the rules
|
|
||||||
const regexp = /(--[\s\S]*?):'([\s\S]*?)';/g;
|
|
||||||
let rules = regexp.exec(style);
|
|
||||||
|
|
||||||
// Add to the config
|
|
||||||
while ( rules !== null )
|
|
||||||
{
|
|
||||||
const configGroup = /--([\s\S]*?)-/g.exec(rules[1])[1];
|
|
||||||
if ( !config[configGroup] )
|
|
||||||
{
|
|
||||||
config[configGroup] = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
config[configGroup][rules[1].replace(/(--[\s\S]*?-)/g, '')] = rules[2];
|
|
||||||
rules = regexp.exec(style);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse the themes objects
|
|
||||||
config.themes = fromPairs(map(config.themes, (value, key) => [key, JSON.parse(value)]));
|
|
||||||
|
|
||||||
// Execute the observable with the config
|
|
||||||
this._tailwindConfig.next(config);
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------------------------------
|
|
||||||
// @ Accessors
|
|
||||||
// -----------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Getter for _tailwindConfig
|
|
||||||
*/
|
|
||||||
get tailwindConfig$(): Observable<any>
|
|
||||||
{
|
|
||||||
return this._tailwindConfig.asObservable();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
/* ----------------------------------------------------------------------------------------------------- */
|
|
||||||
/* @ Any configuration we need from Tailwind's config file will be extracted here so we can import this
|
|
||||||
/* @ file from "config.ts" to access the extracted configuration from TypeScript
|
|
||||||
/* ----------------------------------------------------------------------------------------------------- */
|
|
||||||
@variants fuse-tailwind-extracted-config {
|
|
||||||
}
|
|
||||||
@@ -1,12 +1,9 @@
|
|||||||
/* 1. Core */
|
/* 1. Components */
|
||||||
@import 'core/tailwind-config';
|
@use 'components/example-viewer';
|
||||||
|
@use 'components/input';
|
||||||
|
|
||||||
/* 2. Components */
|
/* 2. Overrides */
|
||||||
@import 'components/example-viewer';
|
@use 'overrides/angular-material';
|
||||||
@import 'components/input';
|
@use 'overrides/highlightjs';
|
||||||
|
@use 'overrides/perfect-scrollbar';
|
||||||
/* 3. Overrides */
|
@use 'overrides/quill';
|
||||||
@import 'overrides/angular-material';
|
|
||||||
@import 'overrides/highlightjs';
|
|
||||||
@import 'overrides/perfect-scrollbar';
|
|
||||||
@import 'overrides/quill';
|
|
||||||
|
|||||||
@@ -80,7 +80,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.ql-container {
|
.ql-container {
|
||||||
overflow: hidden;
|
overflow: auto;
|
||||||
|
min-height: 160px;
|
||||||
|
max-height: 400px;
|
||||||
border-radius: 0 0 6px 6px;
|
border-radius: 0 0 6px 6px;
|
||||||
@apply border-gray-300 border-opacity-100 shadow-sm #{'!important'};
|
@apply border-gray-300 border-opacity-100 shadow-sm #{'!important'};
|
||||||
|
|
||||||
@@ -89,17 +91,52 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.ql-editor {
|
.ql-editor {
|
||||||
min-height: 160px;
|
|
||||||
max-height: 160px;
|
|
||||||
height: 160px;
|
|
||||||
@apply bg-card;
|
@apply bg-card;
|
||||||
|
|
||||||
.dark & {
|
.dark & {
|
||||||
background-color: rgba(0, 0, 0, 0.05);
|
//background-color: rgba(0, 0, 0, 0.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.ql-blank::before {
|
&.ql-blank::before {
|
||||||
@apply text-hint;
|
@apply text-hint;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ql-tooltip {
|
||||||
|
@apply px-3 py-1 shadow-sm rounded-md bg-gray-100 border-gray-300;
|
||||||
|
|
||||||
|
.dark & {
|
||||||
|
@apply shadow-lg bg-gray-700 border-gray-700 #{'!important'};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Label
|
||||||
|
&:before {
|
||||||
|
@apply text-secondary;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ql-action,
|
||||||
|
.ql-remove {
|
||||||
|
@apply text-primary border-gray-300;
|
||||||
|
|
||||||
|
.dark & {
|
||||||
|
@apply text-primary-400 border-gray-300;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ql-action:after {
|
||||||
|
@apply border-r border-r-gray-300 #{'!important'};
|
||||||
|
|
||||||
|
.dark & {
|
||||||
|
@apply border-r-gray-500 #{'!important'};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
@apply rounded-sm text-default bg-white border-gray-300 #{'!important'};
|
||||||
|
|
||||||
|
.dark & {
|
||||||
|
@apply bg-gray-700 border-gray-500 #{'!important'};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,6 +76,46 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Style scrollbars on platforms other than MacOS and iOS */
|
||||||
|
@media only screen and (min-width: 960px) {
|
||||||
|
|
||||||
|
body:not(.os-mac) {
|
||||||
|
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
background-color: rgba(0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar:hover {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
background-color: rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
border: 2px solid transparent;
|
||||||
|
border-radius: 20px;
|
||||||
|
box-shadow: inset 0 0 0 20px rgba(0, 0, 0, 0.24);
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb:active {
|
||||||
|
border-radius: 20px;
|
||||||
|
box-shadow: inset 0 0 0 20px rgba(0, 0, 0, 0.37);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.dark {
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
box-shadow: inset 0 0 0 20px rgba(255, 255, 255, 0.24);
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb:active {
|
||||||
|
box-shadow: inset 0 0 0 20px rgba(255, 255, 255, 0.37);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[disabled] * {
|
[disabled] * {
|
||||||
@apply text-disabled #{'!important'};
|
@apply text-disabled #{'!important'};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,38 +72,38 @@ body .light {
|
|||||||
is-dark: map.get(map.get($base-light-theme, color), is-dark),
|
is-dark: map.get(map.get($base-light-theme, color), is-dark),
|
||||||
foreground: (
|
foreground: (
|
||||||
base: #000000,
|
base: #000000,
|
||||||
divider: #E2E8F0, /* blueGray.200 */
|
divider: #E2E8F0, /* slate.200 */
|
||||||
dividers: #E2E8F0, /* blueGray.200 */
|
dividers: #E2E8F0, /* slate.200 */
|
||||||
disabled: #94A3B8, /* blueGray.400 */
|
disabled: #94A3B8, /* slate.400 */
|
||||||
disabled-button: #94A3B8, /* blueGray.400 */
|
disabled-button: #94A3B8, /* slate.400 */
|
||||||
disabled-text: #94A3B8, /* blueGray.400 */
|
disabled-text: #94A3B8, /* slate.400 */
|
||||||
elevation: #000000,
|
elevation: #000000,
|
||||||
hint-text: #94A3B8, /* blueGray.400 */
|
hint-text: #94A3B8, /* slate.400 */
|
||||||
secondary-text: #64748B, /* blueGray.500 */
|
secondary-text: #64748B, /* slate.500 */
|
||||||
icon: #64748B, /* blueGray.500 */
|
icon: #64748B, /* slate.500 */
|
||||||
icons: #64748B, /* blueGray.500 */
|
icons: #64748B, /* slate.500 */
|
||||||
mat-icon: #64748B, /* blueGray.500 */
|
mat-icon: #64748B, /* slate.500 */
|
||||||
text: #1E293B, /* blueGray.800 */
|
text: #1E293B, /* slate.800 */
|
||||||
slider-min: #1E293B, /* blueGray.800 */
|
slider-min: #1E293B, /* slate.800 */
|
||||||
slider-off: #CBD5E1, /* blueGray.300 */
|
slider-off: #CBD5E1, /* slate.300 */
|
||||||
slider-off-active: #94A3B8 /* blueGray.400 */
|
slider-off-active: #94A3B8 /* slate.400 */
|
||||||
),
|
),
|
||||||
background: (
|
background: (
|
||||||
status-bar: #CBD5E1, /* blueGray.300 */
|
status-bar: #CBD5E1, /* slate.300 */
|
||||||
app-bar: #FFFFFF,
|
app-bar: #FFFFFF,
|
||||||
background: #F1F5F9, /* blueGray.100 */
|
background: #F1F5F9, /* slate.100 */
|
||||||
hover: rgba(148, 163, 184, 0.12), /* blueGray.400 + opacity */
|
hover: rgba(148, 163, 184, 0.12), /* slate.400 + opacity */
|
||||||
card: #FFFFFF,
|
card: #FFFFFF,
|
||||||
dialog: #FFFFFF,
|
dialog: #FFFFFF,
|
||||||
disabled-button: rgba(148, 163, 184, 0.38), /* blueGray.400 + opacity */
|
disabled-button: rgba(148, 163, 184, 0.38), /* slate.400 + opacity */
|
||||||
raised-button: #FFFFFF,
|
raised-button: #FFFFFF,
|
||||||
focused-button: #64748B, /* blueGray.500 */
|
focused-button: #64748B, /* slate.500 */
|
||||||
selected-button: #E2E8F0, /* blueGray.200 */
|
selected-button: #E2E8F0, /* slate.200 */
|
||||||
selected-disabled-button: #E2E8F0, /* blueGray.200 */
|
selected-disabled-button: #E2E8F0, /* slate.200 */
|
||||||
disabled-button-toggle: #CBD5E1, /* blueGray.300 */
|
disabled-button-toggle: #CBD5E1, /* slate.300 */
|
||||||
unselected-chip: #E2E8F0, /* blueGray.200 */
|
unselected-chip: #E2E8F0, /* slate.200 */
|
||||||
disabled-list-option: #CBD5E1, /* blueGray.300 */
|
disabled-list-option: #CBD5E1, /* slate.300 */
|
||||||
tooltip: #1E293B /* blueGray.800 */
|
tooltip: #1E293B /* slate.800 */
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@@ -126,38 +126,38 @@ body .dark {
|
|||||||
is-dark: map.get(map.get($base-dark-theme, color), is-dark),
|
is-dark: map.get(map.get($base-dark-theme, color), is-dark),
|
||||||
foreground: (
|
foreground: (
|
||||||
base: #FFFFFF,
|
base: #FFFFFF,
|
||||||
divider: rgba(241, 245, 249, 0.12), /* blueGray.100 + opacity */
|
divider: rgba(241, 245, 249, 0.12), /* slate.100 + opacity */
|
||||||
dividers: rgba(241, 245, 249, 0.12), /* blueGray.100 + opacity */
|
dividers: rgba(241, 245, 249, 0.12), /* slate.100 + opacity */
|
||||||
disabled: #475569, /* blueGray.600 */
|
disabled: #475569, /* slate.600 */
|
||||||
disabled-button: #1E293B, /* blueGray.800 */
|
disabled-button: #1E293B, /* slate.800 */
|
||||||
disabled-text: #475569, /* blueGray.600 */
|
disabled-text: #475569, /* slate.600 */
|
||||||
elevation: #000000,
|
elevation: #000000,
|
||||||
hint-text: #64748B, /* blueGray.500 */
|
hint-text: #64748B, /* slate.500 */
|
||||||
secondary-text: #94A3B8, /* blueGray.400 */
|
secondary-text: #94A3B8, /* slate.400 */
|
||||||
icon: #F1F5F9, /* blueGray.100 */
|
icon: #F1F5F9, /* slate.100 */
|
||||||
icons: #F1F5F9, /* blueGray.100 */
|
icons: #F1F5F9, /* slate.100 */
|
||||||
mat-icon: #94A3B8, /* blueGray.400 */
|
mat-icon: #94A3B8, /* slate.400 */
|
||||||
text: #FFFFFF,
|
text: #FFFFFF,
|
||||||
slider-min: #FFFFFF,
|
slider-min: #FFFFFF,
|
||||||
slider-off: #64748B, /* blueGray.500 */
|
slider-off: #64748B, /* slate.500 */
|
||||||
slider-off-active: #94A3B8 /* blueGray.400 */
|
slider-off-active: #94A3B8 /* slate.400 */
|
||||||
),
|
),
|
||||||
background: (
|
background: (
|
||||||
status-bar: #0F172A, /* blueGray.900 */
|
status-bar: #0F172A, /* slate.900 */
|
||||||
app-bar: #0F172A, /* blueGray.900 */
|
app-bar: #0F172A, /* slate.900 */
|
||||||
background: #0F172A, /* blueGray.900 */
|
background: #0F172A, /* slate.900 */
|
||||||
hover: rgba(255, 255, 255, 0.05),
|
hover: rgba(255, 255, 255, 0.05),
|
||||||
card: #1E293B, /* blueGray.800 */
|
card: #1E293B, /* slate.800 */
|
||||||
dialog: #1E293B, /* blueGray.800 */
|
dialog: #1E293B, /* slate.800 */
|
||||||
disabled-button: rgba(15, 23, 42, 0.38), /* blueGray.900 + opacity */
|
disabled-button: rgba(15, 23, 42, 0.38), /* slate.900 + opacity */
|
||||||
raised-button: #0F172A, /* blueGray.900 */
|
raised-button: #0F172A, /* slate.900 */
|
||||||
focused-button: #E2E8F0, /* blueGray.200 */
|
focused-button: #E2E8F0, /* slate.200 */
|
||||||
selected-button: rgba(255, 255, 255, 0.05),
|
selected-button: rgba(255, 255, 255, 0.05),
|
||||||
selected-disabled-button: #1E293B, /* blueGray.800 */
|
selected-disabled-button: #1E293B, /* slate.800 */
|
||||||
disabled-button-toggle: #0F172A, /* blueGray.900 */
|
disabled-button-toggle: #0F172A, /* slate.900 */
|
||||||
unselected-chip: #475569, /* blueGray.600 */
|
unselected-chip: #475569, /* slate.600 */
|
||||||
disabled-list-option: #E2E8F0, /* blueGray.200 */
|
disabled-list-option: #E2E8F0, /* slate.200 */
|
||||||
tooltip: #64748B /* blueGray.500 */
|
tooltip: #64748B /* slate.500 */
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,38 +0,0 @@
|
|||||||
const plugin = require('tailwindcss/plugin');
|
|
||||||
const buildMediaQuery = require('tailwindcss/lib/util/buildMediaQuery').default;
|
|
||||||
|
|
||||||
const extractConfig = plugin(({
|
|
||||||
addVariant,
|
|
||||||
theme
|
|
||||||
}) =>
|
|
||||||
{
|
|
||||||
addVariant('fuse-tailwind-extracted-config', ({container}) =>
|
|
||||||
{
|
|
||||||
// Prepare the extracted config variable
|
|
||||||
let extractedConfig = '';
|
|
||||||
|
|
||||||
// Breakpoints
|
|
||||||
Object.entries(theme('screens')).forEach(([key, value]) =>
|
|
||||||
{
|
|
||||||
extractedConfig = `${extractedConfig} --breakpoints-${key}:'${buildMediaQuery(value)}';`;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Themes
|
|
||||||
(theme('fuse.themes')).forEach((value) =>
|
|
||||||
{
|
|
||||||
Object.entries(value).forEach(([key, value]) =>
|
|
||||||
{
|
|
||||||
extractedConfig = `${extractedConfig} --themes-${key}:'${JSON.stringify(value)}';`;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Append the extracted config
|
|
||||||
container.append(`
|
|
||||||
.fuse-tailwind-extracted-config {
|
|
||||||
${extractedConfig}
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = extractConfig;
|
|
||||||
@@ -1,17 +1,14 @@
|
|||||||
const plugin = require('tailwindcss/plugin');
|
const plugin = require('tailwindcss/plugin');
|
||||||
|
|
||||||
const iconSize = plugin(({
|
module.exports = plugin(
|
||||||
addUtilities,
|
({
|
||||||
theme,
|
matchUtilities,
|
||||||
e,
|
theme
|
||||||
variants
|
|
||||||
}) =>
|
}) =>
|
||||||
{
|
{
|
||||||
const values = theme('iconSize');
|
matchUtilities(
|
||||||
|
{
|
||||||
addUtilities(
|
'icon-size': (value) => ({
|
||||||
Object.entries(values).map(([key, value]) => ({
|
|
||||||
[`.${e(`icon-size-${key}`)}`]: {
|
|
||||||
width : value,
|
width : value,
|
||||||
height : value,
|
height : value,
|
||||||
minWidth : value,
|
minWidth : value,
|
||||||
@@ -22,13 +19,14 @@ const iconSize = plugin(({
|
|||||||
width : value,
|
width : value,
|
||||||
height: value
|
height: value
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
})),
|
},
|
||||||
variants('iconSize')
|
{
|
||||||
);
|
values: theme('iconSize')
|
||||||
|
});
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
theme : {
|
theme: {
|
||||||
iconSize: {
|
iconSize: {
|
||||||
3 : '0.75rem',
|
3 : '0.75rem',
|
||||||
3.5: '0.875rem',
|
3.5: '0.875rem',
|
||||||
@@ -47,10 +45,6 @@ const iconSize = plugin(({
|
|||||||
22 : '5.5rem',
|
22 : '5.5rem',
|
||||||
24 : '6rem'
|
24 : '6rem'
|
||||||
}
|
}
|
||||||
},
|
|
||||||
variants: {
|
|
||||||
iconSize: ['responsive']
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
);
|
||||||
module.exports = iconSize;
|
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ const theming = plugin.withOptions((options) => ({
|
|||||||
// @ Map variable colors
|
// @ Map variable colors
|
||||||
// -----------------------------------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------------------------------
|
||||||
const mapVariableColors = _.fromPairs(_.map(options.themes, (theme, themeName) => [
|
const mapVariableColors = _.fromPairs(_.map(options.themes, (theme, themeName) => [
|
||||||
themeName === 'default' ? 'body' : `body.theme-${e(themeName)}`,
|
themeName === 'default' ? 'body, .theme-default' : `.theme-${e(themeName)}`,
|
||||||
_.fromPairs(_.flatten(_.map(flattenColorPalette(_.fromPairs(_.flatten(_.map(normalizeTheme(theme), (palette, paletteName) => [
|
_.fromPairs(_.flatten(_.map(flattenColorPalette(_.fromPairs(_.flatten(_.map(normalizeTheme(theme), (palette, paletteName) => [
|
||||||
[
|
[
|
||||||
e(paletteName),
|
e(paletteName),
|
||||||
@@ -185,47 +185,46 @@ const theming = plugin.withOptions((options) => ({
|
|||||||
light: {
|
light: {
|
||||||
'bg-app-bar' : '#FFFFFF',
|
'bg-app-bar' : '#FFFFFF',
|
||||||
'bg-card' : '#FFFFFF',
|
'bg-card' : '#FFFFFF',
|
||||||
'bg-default' : colors.blueGray[100],
|
'bg-default' : colors.slate[100],
|
||||||
'bg-dialog' : '#FFFFFF',
|
'bg-dialog' : '#FFFFFF',
|
||||||
'bg-hover' : chroma(colors.blueGray[400]).alpha(0.12).css(),
|
'bg-hover' : chroma(colors.slate[400]).alpha(0.12).css(),
|
||||||
'bg-status-bar': colors.blueGray[300]
|
'bg-status-bar': colors.slate[300]
|
||||||
},
|
},
|
||||||
dark : {
|
dark : {
|
||||||
'bg-app-bar' : colors.blueGray[900],
|
'bg-app-bar' : colors.slate[900],
|
||||||
'bg-card' : colors.blueGray[800],
|
'bg-card' : colors.slate[800],
|
||||||
'bg-default' : colors.blueGray[900],
|
'bg-default' : colors.slate[900],
|
||||||
'bg-dialog' : colors.blueGray[800],
|
'bg-dialog' : colors.slate[800],
|
||||||
'bg-hover' : 'rgba(255, 255, 255, 0.05)',
|
'bg-hover' : 'rgba(255, 255, 255, 0.05)',
|
||||||
'bg-status-bar': colors.blueGray[900]
|
'bg-status-bar': colors.slate[900]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
foreground: {
|
foreground: {
|
||||||
light: {
|
light: {
|
||||||
'text-default' : colors.blueGray[800],
|
'text-default' : colors.slate[800],
|
||||||
'text-secondary': colors.blueGray[500],
|
'text-secondary': colors.slate[500],
|
||||||
'text-hint' : colors.blueGray[400],
|
'text-hint' : colors.slate[400],
|
||||||
'text-disabled' : colors.blueGray[400],
|
'text-disabled' : colors.slate[400],
|
||||||
'border' : colors.blueGray[200],
|
'border' : colors.slate[200],
|
||||||
'divider' : colors.blueGray[200],
|
'divider' : colors.slate[200],
|
||||||
'icon' : colors.blueGray[500],
|
'icon' : colors.slate[500],
|
||||||
'mat-icon' : colors.blueGray[500]
|
'mat-icon' : colors.slate[500]
|
||||||
},
|
},
|
||||||
dark : {
|
dark : {
|
||||||
'text-default' : '#FFFFFF',
|
'text-default' : '#FFFFFF',
|
||||||
'text-secondary': colors.blueGray[400],
|
'text-secondary': colors.slate[400],
|
||||||
'text-hint' : colors.blueGray[500],
|
'text-hint' : colors.slate[500],
|
||||||
'text-disabled' : colors.blueGray[600],
|
'text-disabled' : colors.slate[600],
|
||||||
'border' : chroma(colors.blueGray[100]).alpha(0.12).css(),
|
'border' : chroma(colors.slate[100]).alpha(0.12).css(),
|
||||||
'divider' : chroma(colors.blueGray[100]).alpha(0.12).css(),
|
'divider' : chroma(colors.slate[100]).alpha(0.12).css(),
|
||||||
'icon' : colors.blueGray[400],
|
'icon' : colors.slate[400],
|
||||||
'mat-icon' : colors.blueGray[400]
|
'mat-icon' : colors.slate[400]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
themes : generateThemesObject(options.themes)
|
themes : generateThemesObject(options.themes)
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
variants: {}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
const plugin = require('tailwindcss/plugin');
|
const plugin = require('tailwindcss/plugin');
|
||||||
|
|
||||||
const utilities = plugin(({
|
module.exports = plugin(({
|
||||||
addComponents
|
addComponents
|
||||||
}) =>
|
}) =>
|
||||||
{
|
{
|
||||||
@@ -54,9 +54,6 @@ const utilities = plugin(({
|
|||||||
'--tw-ring-opacity': '1 !important',
|
'--tw-ring-opacity': '1 !important',
|
||||||
'--tw-ring-color' : 'rgba(var(--fuse-bg-card-rgb), var(--tw-ring-opacity)) !important'
|
'--tw-ring-color' : 'rgba(var(--fuse-bg-card-rgb), var(--tw-ring-opacity)) !important'
|
||||||
}
|
}
|
||||||
},
|
|
||||||
{
|
|
||||||
variants: ['dark', 'responsive', 'group-hover', 'hover']
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -65,11 +62,6 @@ const utilities = plugin(({
|
|||||||
'.bg-hover': {
|
'.bg-hover': {
|
||||||
backgroundColor: 'var(--fuse-bg-hover) !important'
|
backgroundColor: 'var(--fuse-bg-hover) !important'
|
||||||
}
|
}
|
||||||
},
|
|
||||||
{
|
|
||||||
variants: ['dark', 'group-hover', 'hover']
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = utilities;
|
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
import { Version } from '@fuse/version/version';
|
import { Version } from '@fuse/version/version';
|
||||||
|
|
||||||
export const FUSE_VERSION = new Version('14.0.0').full;
|
export const FUSE_VERSION = new Version('15.0.0').full;
|
||||||
|
|||||||
@@ -3,4 +3,4 @@
|
|||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,15 +9,15 @@ import { InitialDataResolver } from 'app/app.resolvers';
|
|||||||
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
||||||
export const appRoutes: Route[] = [
|
export const appRoutes: Route[] = [
|
||||||
|
|
||||||
// Redirect empty path to '/dashboards/project'
|
// Redirect empty path to '/example'
|
||||||
{path: '', pathMatch : 'full', redirectTo: 'dashboards/project'},
|
{path: '', pathMatch : 'full', redirectTo: 'example'},
|
||||||
|
|
||||||
// Redirect signed in user to the '/dashboards/project'
|
// Redirect signed in user to the '/example'
|
||||||
//
|
//
|
||||||
// After the user signs in, the sign in page will redirect the user to the 'signed-in-redirect'
|
// After the user signs in, the sign in page will redirect the user to the 'signed-in-redirect'
|
||||||
// path. Below is another redirection for that path to redirect the user to the desired
|
// path. Below is another redirection for that path to redirect the user to the desired
|
||||||
// location. This is a small convenience to keep all main routes together here on this file.
|
// location. This is a small convenience to keep all main routes together here on this file.
|
||||||
{path: 'signed-in-redirect', pathMatch : 'full', redirectTo: 'dashboards/project'},
|
{path: 'signed-in-redirect', pathMatch : 'full', redirectTo: 'example'},
|
||||||
|
|
||||||
// Auth routes for guests
|
// Auth routes for guests
|
||||||
{
|
{
|
||||||
@@ -74,136 +74,7 @@ export const appRoutes: Route[] = [
|
|||||||
initialData: InitialDataResolver,
|
initialData: InitialDataResolver,
|
||||||
},
|
},
|
||||||
children : [
|
children : [
|
||||||
|
{path: 'example', loadChildren: () => import('app/modules/admin/example/example.module').then(m => m.ExampleModule)},
|
||||||
// Dashboards
|
|
||||||
{path: 'dashboards', children: [
|
|
||||||
{path: 'project', loadChildren: () => import('app/modules/admin/dashboards/project/project.module').then(m => m.ProjectModule)},
|
|
||||||
{path: 'analytics', loadChildren: () => import('app/modules/admin/dashboards/analytics/analytics.module').then(m => m.AnalyticsModule)},
|
|
||||||
{path: 'finance', loadChildren: () => import('app/modules/admin/dashboards/finance/finance.module').then(m => m.FinanceModule)},
|
|
||||||
{path: 'crypto', loadChildren: () => import('app/modules/admin/dashboards/crypto/crypto.module').then(m => m.CryptoModule)},
|
|
||||||
]},
|
|
||||||
|
|
||||||
// Apps
|
|
||||||
{path: 'apps', children: [
|
|
||||||
{path: 'academy', loadChildren: () => import('app/modules/admin/apps/academy/academy.module').then(m => m.AcademyModule)},
|
|
||||||
{path: 'chat', loadChildren: () => import('app/modules/admin/apps/chat/chat.module').then(m => m.ChatModule)},
|
|
||||||
{path: 'contacts', loadChildren: () => import('app/modules/admin/apps/contacts/contacts.module').then(m => m.ContactsModule)},
|
|
||||||
{path: 'ecommerce', loadChildren: () => import('app/modules/admin/apps/ecommerce/ecommerce.module').then(m => m.ECommerceModule)},
|
|
||||||
{path: 'file-manager', loadChildren: () => import('app/modules/admin/apps/file-manager/file-manager.module').then(m => m.FileManagerModule)},
|
|
||||||
{path: 'help-center', loadChildren: () => import('app/modules/admin/apps/help-center/help-center.module').then(m => m.HelpCenterModule)},
|
|
||||||
{path: 'mailbox', loadChildren: () => import('app/modules/admin/apps/mailbox/mailbox.module').then(m => m.MailboxModule)},
|
|
||||||
{path: 'notes', loadChildren: () => import('app/modules/admin/apps/notes/notes.module').then(m => m.NotesModule)},
|
|
||||||
{path: 'scrumboard', loadChildren: () => import('app/modules/admin/apps/scrumboard/scrumboard.module').then(m => m.ScrumboardModule)},
|
|
||||||
{path: 'tasks', loadChildren: () => import('app/modules/admin/apps/tasks/tasks.module').then(m => m.TasksModule)},
|
|
||||||
]},
|
|
||||||
|
|
||||||
// Pages
|
|
||||||
{path: 'pages', children: [
|
|
||||||
|
|
||||||
// Activities
|
|
||||||
{path: 'activities', loadChildren: () => import('app/modules/admin/pages/activities/activities.module').then(m => m.ActivitiesModule)},
|
|
||||||
|
|
||||||
// Authentication
|
|
||||||
{path: 'authentication', loadChildren: () => import('app/modules/admin/pages/authentication/authentication.module').then(m => m.AuthenticationModule)},
|
|
||||||
|
|
||||||
// Coming Soon
|
|
||||||
{path: 'coming-soon', loadChildren: () => import('app/modules/admin/pages/coming-soon/coming-soon.module').then(m => m.ComingSoonModule)},
|
|
||||||
|
|
||||||
// Error
|
|
||||||
{path: 'error', children: [
|
|
||||||
{path: '404', loadChildren: () => import('app/modules/admin/pages/error/error-404/error-404.module').then(m => m.Error404Module)},
|
|
||||||
{path: '500', loadChildren: () => import('app/modules/admin/pages/error/error-500/error-500.module').then(m => m.Error500Module)}
|
|
||||||
]},
|
|
||||||
|
|
||||||
// Invoice
|
|
||||||
{path: 'invoice', children: [
|
|
||||||
{path: 'printable', children: [
|
|
||||||
{path: 'compact', loadChildren: () => import('app/modules/admin/pages/invoice/printable/compact/compact.module').then(m => m.CompactModule)},
|
|
||||||
{path: 'modern', loadChildren: () => import('app/modules/admin/pages/invoice/printable/modern/modern.module').then(m => m.ModernModule)}
|
|
||||||
]}
|
|
||||||
]},
|
|
||||||
|
|
||||||
// Maintenance
|
|
||||||
{path: 'maintenance', loadChildren: () => import('app/modules/admin/pages/maintenance/maintenance.module').then(m => m.MaintenanceModule)},
|
|
||||||
|
|
||||||
// Pricing
|
|
||||||
{path: 'pricing', children: [
|
|
||||||
{path: 'modern', loadChildren: () => import('app/modules/admin/pages/pricing/modern/modern.module').then(m => m.PricingModernModule)},
|
|
||||||
{path: 'simple', loadChildren: () => import('app/modules/admin/pages/pricing/simple/simple.module').then(m => m.PricingSimpleModule)},
|
|
||||||
{path: 'single', loadChildren: () => import('app/modules/admin/pages/pricing/single/single.module').then(m => m.PricingSingleModule)},
|
|
||||||
{path: 'table', loadChildren: () => import('app/modules/admin/pages/pricing/table/table.module').then(m => m.PricingTableModule)}
|
|
||||||
]},
|
|
||||||
|
|
||||||
// Profile
|
|
||||||
{path: 'profile', loadChildren: () => import('app/modules/admin/pages/profile/profile.module').then(m => m.ProfileModule)},
|
|
||||||
|
|
||||||
// Settings
|
|
||||||
{path: 'settings', loadChildren: () => import('app/modules/admin/pages/settings/settings.module').then(m => m.SettingsModule)},
|
|
||||||
]},
|
|
||||||
|
|
||||||
// User Interface
|
|
||||||
{path: 'ui', children: [
|
|
||||||
|
|
||||||
// Material Components
|
|
||||||
{path: 'material-components', loadChildren: () => import('app/modules/admin/ui/material-components/material-components.module').then(m => m.MaterialComponentsModule)},
|
|
||||||
|
|
||||||
// Fuse Components
|
|
||||||
{path: 'fuse-components', loadChildren: () => import('app/modules/admin/ui/fuse-components/fuse-components.module').then(m => m.FuseComponentsModule)},
|
|
||||||
|
|
||||||
// Other Components
|
|
||||||
{path: 'other-components', loadChildren: () => import('app/modules/admin/ui/other-components/other-components.module').then(m => m.OtherComponentsModule)},
|
|
||||||
|
|
||||||
// TailwindCSS
|
|
||||||
{path: 'tailwindcss', loadChildren: () => import('app/modules/admin/ui/tailwindcss/tailwindcss.module').then(m => m.TailwindCSSModule)},
|
|
||||||
|
|
||||||
// Advanced Search
|
|
||||||
{path: 'advanced-search', loadChildren: () => import('app/modules/admin/ui/advanced-search/advanced-search.module').then(m => m.AdvancedSearchModule)},
|
|
||||||
|
|
||||||
// Animations
|
|
||||||
{path: 'animations', loadChildren: () => import('app/modules/admin/ui/animations/animations.module').then(m => m.AnimationsModule)},
|
|
||||||
|
|
||||||
// Cards
|
|
||||||
{path: 'cards', loadChildren: () => import('app/modules/admin/ui/cards/cards.module').then(m => m.CardsModule)},
|
|
||||||
|
|
||||||
// Colors
|
|
||||||
{path: 'colors', loadChildren: () => import('app/modules/admin/ui/colors/colors.module').then(m => m.ColorsModule)},
|
|
||||||
|
|
||||||
// Confirmation Dialog
|
|
||||||
{path: 'confirmation-dialog', loadChildren: () => import('app/modules/admin/ui/confirmation-dialog/confirmation-dialog.module').then(m => m.ConfirmationDialogModule)},
|
|
||||||
|
|
||||||
// Datatable
|
|
||||||
{path: 'datatable', loadChildren: () => import('app/modules/admin/ui/datatable/datatable.module').then(m => m.DatatableModule)},
|
|
||||||
|
|
||||||
// Forms
|
|
||||||
{path: 'forms', children: [
|
|
||||||
{path: 'fields', loadChildren: () => import('app/modules/admin/ui/forms/fields/fields.module').then(m => m.FormsFieldsModule)},
|
|
||||||
{path: 'layouts', loadChildren: () => import('app/modules/admin/ui/forms/layouts/layouts.module').then(m => m.FormsLayoutsModule)},
|
|
||||||
{path: 'wizards', loadChildren: () => import('app/modules/admin/ui/forms/wizards/wizards.module').then(m => m.FormsWizardsModule)}
|
|
||||||
]},
|
|
||||||
|
|
||||||
// Icons
|
|
||||||
{path: 'icons', loadChildren: () => import('app/modules/admin/ui/icons/icons.module').then(m => m.IconsModule)},
|
|
||||||
|
|
||||||
// Page Layouts
|
|
||||||
{path: 'page-layouts', loadChildren: () => import('app/modules/admin/ui/page-layouts/page-layouts.module').then(m => m.PageLayoutsModule)},
|
|
||||||
|
|
||||||
// Typography
|
|
||||||
{path: 'typography', loadChildren: () => import('app/modules/admin/ui/typography/typography.module').then(m => m.TypographyModule)}
|
|
||||||
]},
|
|
||||||
|
|
||||||
// Documentation
|
|
||||||
{path: 'docs', children: [
|
|
||||||
|
|
||||||
// Changelog
|
|
||||||
{path: 'changelog', loadChildren: () => import('app/modules/admin/docs/changelog/changelog.module').then(m => m.ChangelogModule)},
|
|
||||||
|
|
||||||
// Guides
|
|
||||||
{path: 'guides', loadChildren: () => import('app/modules/admin/docs/guides/guides.module').then(m => m.GuidesModule)}
|
|
||||||
]},
|
|
||||||
|
|
||||||
// 404 & Catch all
|
|
||||||
{path: '404-not-found', pathMatch: 'full', loadChildren: () => import('app/modules/admin/pages/error/error-404/error-404.module').then(m => m.Error404Module)},
|
|
||||||
{path: '**', redirectTo: '404-not-found'}
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -96,8 +96,8 @@ export class AuthService
|
|||||||
*/
|
*/
|
||||||
signInUsingToken(): Observable<any>
|
signInUsingToken(): Observable<any>
|
||||||
{
|
{
|
||||||
// Renew token
|
// Sign in using the token
|
||||||
return this._httpClient.post('api/auth/refresh-access-token', {
|
return this._httpClient.post('api/auth/sign-in-with-token', {
|
||||||
accessToken: this.accessToken
|
accessToken: this.accessToken
|
||||||
}).pipe(
|
}).pipe(
|
||||||
catchError(() =>
|
catchError(() =>
|
||||||
@@ -107,8 +107,17 @@ export class AuthService
|
|||||||
),
|
),
|
||||||
switchMap((response: any) => {
|
switchMap((response: any) => {
|
||||||
|
|
||||||
// Store the access token in the local storage
|
// Replace the access token with the new one if it's available on
|
||||||
this.accessToken = response.accessToken;
|
// the response object.
|
||||||
|
//
|
||||||
|
// This is an added optional step for better security. Once you sign
|
||||||
|
// in using the token, you should generate a new one on the server
|
||||||
|
// side and attach it to the response object. Then the following
|
||||||
|
// piece of code can replace the token with the refreshed one.
|
||||||
|
if ( response.accessToken )
|
||||||
|
{
|
||||||
|
this.accessToken = response.accessToken;
|
||||||
|
}
|
||||||
|
|
||||||
// Set the authenticated flag to true
|
// Set the authenticated flag to true
|
||||||
this._authenticated = true;
|
this._authenticated = true;
|
||||||
|
|||||||
@@ -2,7 +2,9 @@ import { Layout } from 'app/layout/layout.types';
|
|||||||
|
|
||||||
// Types
|
// Types
|
||||||
export type Scheme = 'auto' | 'dark' | 'light';
|
export type Scheme = 'auto' | 'dark' | 'light';
|
||||||
export type Theme = 'default' | string;
|
export type Screens = { [key: string]: string };
|
||||||
|
export type Theme = 'theme-default' | string;
|
||||||
|
export type Themes = { id: string; name: string }[];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AppConfig interface. Update this interface to strictly type your config
|
* AppConfig interface. Update this interface to strictly type your config
|
||||||
@@ -12,7 +14,9 @@ export interface AppConfig
|
|||||||
{
|
{
|
||||||
layout: Layout;
|
layout: Layout;
|
||||||
scheme: Scheme;
|
scheme: Scheme;
|
||||||
|
screens: Screens;
|
||||||
theme: Theme;
|
theme: Theme;
|
||||||
|
themes: Themes;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -22,9 +26,46 @@ export interface AppConfig
|
|||||||
* If you need to store global configuration for your app, you can use this
|
* If you need to store global configuration for your app, you can use this
|
||||||
* object to set the defaults. To access, update and reset the config, use
|
* object to set the defaults. To access, update and reset the config, use
|
||||||
* FuseConfigService and its methods.
|
* FuseConfigService and its methods.
|
||||||
|
*
|
||||||
|
* "Screens" are carried over to the BreakpointObserver for accessing them within
|
||||||
|
* components, and they are required.
|
||||||
|
*
|
||||||
|
* "Themes" are required for Tailwind to generate themes.
|
||||||
*/
|
*/
|
||||||
export const appConfig: AppConfig = {
|
export const appConfig: AppConfig = {
|
||||||
layout: 'classy',
|
layout : 'classy',
|
||||||
scheme: 'light',
|
scheme : 'light',
|
||||||
theme : 'default'
|
screens: {
|
||||||
|
sm: '600px',
|
||||||
|
md: '960px',
|
||||||
|
lg: '1280px',
|
||||||
|
xl: '1440px'
|
||||||
|
},
|
||||||
|
theme : 'theme-default',
|
||||||
|
themes : [
|
||||||
|
{
|
||||||
|
id : 'theme-default',
|
||||||
|
name: 'Default'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id : 'theme-brand',
|
||||||
|
name: 'Brand'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id : 'theme-teal',
|
||||||
|
name: 'Teal'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id : 'theme-rose',
|
||||||
|
name: 'Rose'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id : 'theme-purple',
|
||||||
|
name: 'Purple'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id : 'theme-amber',
|
||||||
|
name: 'Amber'
|
||||||
|
}
|
||||||
|
]
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ export class IconsModule
|
|||||||
this._matIconRegistry.addSvgIconSet(this._domSanitizer.bypassSecurityTrustResourceUrl('assets/icons/material-twotone.svg'));
|
this._matIconRegistry.addSvgIconSet(this._domSanitizer.bypassSecurityTrustResourceUrl('assets/icons/material-twotone.svg'));
|
||||||
this._matIconRegistry.addSvgIconSetInNamespace('mat_outline', this._domSanitizer.bypassSecurityTrustResourceUrl('assets/icons/material-outline.svg'));
|
this._matIconRegistry.addSvgIconSetInNamespace('mat_outline', this._domSanitizer.bypassSecurityTrustResourceUrl('assets/icons/material-outline.svg'));
|
||||||
this._matIconRegistry.addSvgIconSetInNamespace('mat_solid', this._domSanitizer.bypassSecurityTrustResourceUrl('assets/icons/material-solid.svg'));
|
this._matIconRegistry.addSvgIconSetInNamespace('mat_solid', this._domSanitizer.bypassSecurityTrustResourceUrl('assets/icons/material-solid.svg'));
|
||||||
this._matIconRegistry.addSvgIconSetInNamespace('iconsmind', this._domSanitizer.bypassSecurityTrustResourceUrl('assets/icons/iconsmind.svg'));
|
|
||||||
this._matIconRegistry.addSvgIconSetInNamespace('feather', this._domSanitizer.bypassSecurityTrustResourceUrl('assets/icons/feather.svg'));
|
this._matIconRegistry.addSvgIconSetInNamespace('feather', this._domSanitizer.bypassSecurityTrustResourceUrl('assets/icons/feather.svg'));
|
||||||
this._matIconRegistry.addSvgIconSetInNamespace('heroicons_outline', this._domSanitizer.bypassSecurityTrustResourceUrl('assets/icons/heroicons-outline.svg'));
|
this._matIconRegistry.addSvgIconSetInNamespace('heroicons_outline', this._domSanitizer.bypassSecurityTrustResourceUrl('assets/icons/heroicons-outline.svg'));
|
||||||
this._matIconRegistry.addSvgIconSetInNamespace('heroicons_solid', this._domSanitizer.bypassSecurityTrustResourceUrl('assets/icons/heroicons-solid.svg'));
|
this._matIconRegistry.addSvgIconSetInNamespace('heroicons_solid', this._domSanitizer.bypassSecurityTrustResourceUrl('assets/icons/heroicons-solid.svg'));
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
#messagesOrigin>
|
#messagesOrigin>
|
||||||
<ng-container *ngIf="unreadCount > 0">
|
<ng-container *ngIf="unreadCount > 0">
|
||||||
<span class="absolute top-0 right-0 left-0 flex items-center justify-center h-3">
|
<span class="absolute top-0 right-0 left-0 flex items-center justify-center h-3">
|
||||||
<span class="flex items-center justify-center flex-shrink-0 min-w-4 h-4 px-1 ml-4 mt-2.5 rounded-full bg-indigo-600 text-indigo-50 text-xs font-medium">
|
<span class="flex items-center justify-center shrink-0 min-w-4 h-4 px-1 ml-4 mt-2.5 rounded-full bg-indigo-600 text-indigo-50 text-xs font-medium">
|
||||||
{{unreadCount}}
|
{{unreadCount}}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
<div class="fixed inset-0 sm:static sm:inset-auto flex flex-col sm:min-w-90 sm:w-90 sm:rounded-2xl overflow-hidden shadow-lg">
|
<div class="fixed inset-0 sm:static sm:inset-auto flex flex-col sm:min-w-90 sm:w-90 sm:rounded-2xl overflow-hidden shadow-lg">
|
||||||
|
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<div class="flex flex-shrink-0 items-center py-4 pr-4 pl-6 bg-primary text-on-primary">
|
<div class="flex shrink-0 items-center py-4 pr-4 pl-6 bg-primary text-on-primary">
|
||||||
<div class="sm:hidden -ml-1 mr-3">
|
<div class="sm:hidden -ml-1 mr-3">
|
||||||
<button
|
<button
|
||||||
mat-icon-button
|
mat-icon-button
|
||||||
@@ -108,7 +108,7 @@
|
|||||||
<ng-template #messageContent>
|
<ng-template #messageContent>
|
||||||
<!-- Icon -->
|
<!-- Icon -->
|
||||||
<ng-container *ngIf="message.icon && !message.image">
|
<ng-container *ngIf="message.icon && !message.image">
|
||||||
<div class="flex flex-shrink-0 items-center justify-center w-8 h-8 mr-4 rounded-full bg-gray-100 dark:bg-gray-700">
|
<div class="flex shrink-0 items-center justify-center w-8 h-8 mr-4 rounded-full bg-gray-100 dark:bg-gray-700">
|
||||||
<mat-icon
|
<mat-icon
|
||||||
class="icon-size-5"
|
class="icon-size-5"
|
||||||
[svgIcon]="message.icon">
|
[svgIcon]="message.icon">
|
||||||
@@ -118,7 +118,7 @@
|
|||||||
<!-- Image -->
|
<!-- Image -->
|
||||||
<ng-container *ngIf="message.image">
|
<ng-container *ngIf="message.image">
|
||||||
<img
|
<img
|
||||||
class="flex-shrink-0 w-8 h-8 mr-4 rounded-full overflow-hidden object-cover object-center"
|
class="shrink-0 w-8 h-8 mr-4 rounded-full overflow-hidden object-cover object-center"
|
||||||
[src]="message.image"
|
[src]="message.image"
|
||||||
[alt]="'Message image'">
|
[alt]="'Message image'">
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
#notificationsOrigin>
|
#notificationsOrigin>
|
||||||
<ng-container *ngIf="unreadCount > 0">
|
<ng-container *ngIf="unreadCount > 0">
|
||||||
<span class="absolute top-0 right-0 left-0 flex items-center justify-center h-3">
|
<span class="absolute top-0 right-0 left-0 flex items-center justify-center h-3">
|
||||||
<span class="flex items-center justify-center flex-shrink-0 min-w-4 h-4 px-1 ml-4 mt-2.5 rounded-full bg-teal-600 text-indigo-50 text-xs font-medium">
|
<span class="flex items-center justify-center shrink-0 min-w-4 h-4 px-1 ml-4 mt-2.5 rounded-full bg-teal-600 text-indigo-50 text-xs font-medium">
|
||||||
{{unreadCount}}
|
{{unreadCount}}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
<div class="fixed inset-0 sm:static sm:inset-auto flex flex-col sm:min-w-90 sm:w-90 sm:rounded-2xl overflow-hidden shadow-lg">
|
<div class="fixed inset-0 sm:static sm:inset-auto flex flex-col sm:min-w-90 sm:w-90 sm:rounded-2xl overflow-hidden shadow-lg">
|
||||||
|
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<div class="flex flex-shrink-0 items-center py-4 pr-4 pl-6 bg-primary text-on-primary">
|
<div class="flex shrink-0 items-center py-4 pr-4 pl-6 bg-primary text-on-primary">
|
||||||
<div class="sm:hidden -ml-1 mr-3">
|
<div class="sm:hidden -ml-1 mr-3">
|
||||||
<button
|
<button
|
||||||
mat-icon-button
|
mat-icon-button
|
||||||
@@ -109,7 +109,7 @@
|
|||||||
<ng-template #notificationContent>
|
<ng-template #notificationContent>
|
||||||
<!-- Icon -->
|
<!-- Icon -->
|
||||||
<ng-container *ngIf="notification.icon && !notification.image">
|
<ng-container *ngIf="notification.icon && !notification.image">
|
||||||
<div class="flex flex-shrink-0 items-center justify-center w-8 h-8 mr-4 rounded-full bg-gray-100 dark:bg-gray-700">
|
<div class="flex shrink-0 items-center justify-center w-8 h-8 mr-4 rounded-full bg-gray-100 dark:bg-gray-700">
|
||||||
<mat-icon
|
<mat-icon
|
||||||
class="icon-size-5"
|
class="icon-size-5"
|
||||||
[svgIcon]="notification.icon">
|
[svgIcon]="notification.icon">
|
||||||
@@ -119,7 +119,7 @@
|
|||||||
<!-- Image -->
|
<!-- Image -->
|
||||||
<ng-container *ngIf="notification.image">
|
<ng-container *ngIf="notification.image">
|
||||||
<img
|
<img
|
||||||
class="flex-shrink-0 w-8 h-8 mr-4 rounded-full overflow-hidden object-cover object-center"
|
class="shrink-0 w-8 h-8 mr-4 rounded-full overflow-hidden object-cover object-center"
|
||||||
[src]="notification.image"
|
[src]="notification.image"
|
||||||
[alt]="'Notification image'">
|
[alt]="'Notification image'">
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<div class="fixed lg:sticky top-0 bottom-0 lg:left-full w-full sm:w-96 lg:w-16 lg:h-screen lg:shadow">
|
<div class="fixed lg:sticky top-0 bottom-0 lg:left-full w-full sm:w-96 lg:w-16 lg:h-screen lg:shadow">
|
||||||
<div
|
<div
|
||||||
class="flex flex-col w-full sm:w-96 h-full transform transition-transform duration-400 ease-drawer bg-card"
|
class="flex flex-col w-full sm:w-96 h-full transition-transform duration-400 ease-drawer bg-card"
|
||||||
[ngClass]="{'-translate-x-full sm:-translate-x-96 lg:-translate-x-80 shadow': opened, 'translate-x-0': !opened}">
|
[ngClass]="{'-translate-x-full sm:-translate-x-96 lg:-translate-x-80 shadow': opened, 'translate-x-0': !opened}">
|
||||||
|
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
@@ -93,7 +93,7 @@
|
|||||||
<div class="flex flex-col flex-auto border-l overflow-hidden bg-gray-50 dark:bg-transparent">
|
<div class="flex flex-col flex-auto border-l overflow-hidden bg-gray-50 dark:bg-transparent">
|
||||||
<ng-container *ngIf="chat; else selectChatOrStartNew">
|
<ng-container *ngIf="chat; else selectChatOrStartNew">
|
||||||
<div class="flex flex-col-reverse overflow-y-auto overscroll-y-contain">
|
<div class="flex flex-col-reverse overflow-y-auto overscroll-y-contain">
|
||||||
<div class="flex flex-col flex-auto flex-shrink p-6">
|
<div class="flex flex-col flex-auto shrink p-6">
|
||||||
<ng-container *ngFor="let message of chat.messages; let i = index; let first = first; let last = last; trackBy: trackByFn">
|
<ng-container *ngFor="let message of chat.messages; let i = index; let first = first; let last = last; trackBy: trackByFn">
|
||||||
<!-- Start of the day -->
|
<!-- Start of the day -->
|
||||||
<ng-container *ngIf="first || (chat.messages[i - 1].createdAt | date:'d') !== (message.createdAt | date:'d')">
|
<ng-container *ngIf="first || (chat.messages[i - 1].createdAt | date:'d') !== (message.createdAt | date:'d')">
|
||||||
@@ -108,20 +108,20 @@
|
|||||||
<div
|
<div
|
||||||
class="flex flex-col"
|
class="flex flex-col"
|
||||||
[ngClass]="{'items-end': message.isMine,
|
[ngClass]="{'items-end': message.isMine,
|
||||||
'items-start': !message.isMine,
|
'items-start': !message.isMine,
|
||||||
'mt-0.5': i > 0 && chat.messages[i - 1].isMine === message.isMine,
|
'mt-0.5': i > 0 && chat.messages[i - 1].isMine === message.isMine,
|
||||||
'mt-3': i > 0 && chat.messages[i - 1].isMine !== message.isMine}">
|
'mt-3': i > 0 && chat.messages[i - 1].isMine !== message.isMine}">
|
||||||
<!-- Bubble -->
|
<!-- Bubble -->
|
||||||
<div
|
<div
|
||||||
class="relative max-w-3/4 px-3 py-2 rounded-lg"
|
class="relative max-w-3/4 px-3 py-2 rounded-lg"
|
||||||
[ngClass]="{'bg-blue-500 text-blue-50': message.isMine,
|
[ngClass]="{'bg-blue-500 text-blue-50': message.isMine,
|
||||||
'bg-gray-500 text-gray-50': !message.isMine}">
|
'bg-gray-500 text-gray-50': !message.isMine}">
|
||||||
<!-- Speech bubble tail -->
|
<!-- Speech bubble tail -->
|
||||||
<ng-container *ngIf="last || chat.messages[i + 1].isMine !== message.isMine">
|
<ng-container *ngIf="last || chat.messages[i + 1].isMine !== message.isMine">
|
||||||
<div
|
<div
|
||||||
class="absolute bottom-0 w-3 transform"
|
class="absolute bottom-0 w-3"
|
||||||
[ngClass]="{'text-blue-500 -right-1 -mr-px mb-px': message.isMine,
|
[ngClass]="{'text-blue-500 -right-1 -mr-px mb-px': message.isMine,
|
||||||
'text-gray-500 -left-1 -ml-px mb-px -scale-x-1': !message.isMine}">
|
'text-gray-500 -left-1 -ml-px mb-px -scale-x-1': !message.isMine}">
|
||||||
<ng-container *ngTemplateOutlet="speechBubbleExtension"></ng-container>
|
<ng-container *ngTemplateOutlet="speechBubbleExtension"></ng-container>
|
||||||
</div>
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
@@ -140,7 +140,7 @@
|
|||||||
<div
|
<div
|
||||||
class="my-0.5 text-sm font-medium text-secondary"
|
class="my-0.5 text-sm font-medium text-secondary"
|
||||||
[ngClass]="{'mr-3': message.isMine,
|
[ngClass]="{'mr-3': message.isMine,
|
||||||
'ml-3': !message.isMine}">
|
'ml-3': !message.isMine}">
|
||||||
{{message.createdAt | date:'HH:mm'}}
|
{{message.createdAt | date:'HH:mm'}}
|
||||||
</div>
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
@@ -163,7 +163,7 @@
|
|||||||
<button
|
<button
|
||||||
mat-icon-button>
|
mat-icon-button>
|
||||||
<mat-icon
|
<mat-icon
|
||||||
class="transform rotate-90"
|
class="rotate-90"
|
||||||
[svgIcon]="'heroicons_outline:paper-airplane'"></mat-icon>
|
[svgIcon]="'heroicons_outline:paper-airplane'"></mat-icon>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -178,8 +178,8 @@
|
|||||||
<ng-template #selectChatOrStartNew>
|
<ng-template #selectChatOrStartNew>
|
||||||
<div class="flex flex-col flex-auto items-center justify-center w-full h-full p-4">
|
<div class="flex flex-col flex-auto items-center justify-center w-full h-full p-4">
|
||||||
<mat-icon
|
<mat-icon
|
||||||
class="icon-size-20"
|
class="icon-size-24"
|
||||||
[svgIcon]="'iconsmind:speach_bubble'"></mat-icon>
|
[svgIcon]="'heroicons_outline:chat'"></mat-icon>
|
||||||
<div class="mt-4 text-xl text-center font-medium tracking-tight text-secondary">Select a conversation</div>
|
<div class="mt-4 text-xl text-center font-medium tracking-tight text-secondary">Select a conversation</div>
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<mat-icon [svgIcon]="'heroicons_outline:search'"></mat-icon>
|
<mat-icon [svgIcon]="'heroicons_outline:search'"></mat-icon>
|
||||||
</button>
|
</button>
|
||||||
<div
|
<div
|
||||||
class="absolute inset-0 flex items-center flex-shrink-0 z-99 bg-card"
|
class="absolute inset-0 flex items-center shrink-0 z-99 bg-card"
|
||||||
*ngIf="opened"
|
*ngIf="opened"
|
||||||
@slideInTop
|
@slideInTop
|
||||||
@slideOutTop>
|
@slideOutTop>
|
||||||
@@ -18,11 +18,12 @@
|
|||||||
class="w-full h-full px-16 sm:px-18"
|
class="w-full h-full px-16 sm:px-18"
|
||||||
[formControl]="searchControl"
|
[formControl]="searchControl"
|
||||||
[matAutocomplete]="matAutocomplete"
|
[matAutocomplete]="matAutocomplete"
|
||||||
[placeholder]="'Search for a page or a contact'"
|
[placeholder]="'Search...'"
|
||||||
(keydown)="onKeydown($event)"
|
(keydown)="onKeydown($event)"
|
||||||
#barSearchInput>
|
#barSearchInput>
|
||||||
<mat-autocomplete
|
<mat-autocomplete
|
||||||
class="max-h-128 sm:px-2 border-t rounded-b shadow-md"
|
class="max-h-128 sm:px-2 border-t rounded-b shadow-md"
|
||||||
|
[autoSelectActiveOption]="true"
|
||||||
[disableRipple]="true"
|
[disableRipple]="true"
|
||||||
#matAutocomplete="matAutocomplete">
|
#matAutocomplete="matAutocomplete">
|
||||||
<mat-option
|
<mat-option
|
||||||
@@ -37,7 +38,8 @@
|
|||||||
<ng-container *ngFor="let result of resultSet.results; trackBy: trackByFn">
|
<ng-container *ngFor="let result of resultSet.results; trackBy: trackByFn">
|
||||||
<mat-option
|
<mat-option
|
||||||
class="group relative mb-1 py-0 px-6 text-md rounded-md hover:bg-gray-100 dark:hover:bg-hover"
|
class="group relative mb-1 py-0 px-6 text-md rounded-md hover:bg-gray-100 dark:hover:bg-hover"
|
||||||
[routerLink]="result.link">
|
[routerLink]="result.link"
|
||||||
|
[value]="result.value">
|
||||||
<!-- Contacts -->
|
<!-- Contacts -->
|
||||||
<ng-container *ngIf="resultSet.id === 'contacts'">
|
<ng-container *ngIf="resultSet.id === 'contacts'">
|
||||||
<ng-container *ngTemplateOutlet="contactResult; context: {$implicit: result}"></ng-container>
|
<ng-container *ngTemplateOutlet="contactResult; context: {$implicit: result}"></ng-container>
|
||||||
@@ -55,7 +57,7 @@
|
|||||||
</ng-container>
|
</ng-container>
|
||||||
</mat-autocomplete>
|
</mat-autocomplete>
|
||||||
<button
|
<button
|
||||||
class="absolute top-1/2 right-5 sm:right-7 flex-shrink-0 w-10 h-10 -mt-5"
|
class="absolute top-1/2 right-5 sm:right-7 shrink-0 w-10 h-10 -mt-5"
|
||||||
mat-icon-button
|
mat-icon-button
|
||||||
(click)="close()">
|
(click)="close()">
|
||||||
<mat-icon [svgIcon]="'heroicons_outline:x'"></mat-icon>
|
<mat-icon [svgIcon]="'heroicons_outline:x'"></mat-icon>
|
||||||
@@ -74,11 +76,12 @@
|
|||||||
matInput
|
matInput
|
||||||
[formControl]="searchControl"
|
[formControl]="searchControl"
|
||||||
[matAutocomplete]="matAutocomplete"
|
[matAutocomplete]="matAutocomplete"
|
||||||
[placeholder]="'Search for a page or a contact'"
|
[placeholder]="'Search...'"
|
||||||
(keydown)="onKeydown($event)">
|
(keydown)="onKeydown($event)">
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<mat-autocomplete
|
<mat-autocomplete
|
||||||
class="max-h-128 mt-1 rounded"
|
class="max-h-128 mt-1 rounded"
|
||||||
|
[autoSelectActiveOption]="true"
|
||||||
[disableRipple]="true"
|
[disableRipple]="true"
|
||||||
#matAutocomplete="matAutocomplete">
|
#matAutocomplete="matAutocomplete">
|
||||||
<mat-option
|
<mat-option
|
||||||
@@ -93,7 +96,8 @@
|
|||||||
<ng-container *ngFor="let result of resultSet.results; trackBy: trackByFn">
|
<ng-container *ngFor="let result of resultSet.results; trackBy: trackByFn">
|
||||||
<mat-option
|
<mat-option
|
||||||
class="group relative mb-1 py-0 px-6 text-md rounded-md hover:bg-gray-100 dark:hover:bg-hover"
|
class="group relative mb-1 py-0 px-6 text-md rounded-md hover:bg-gray-100 dark:hover:bg-hover"
|
||||||
[routerLink]="result.link">
|
[routerLink]="result.link"
|
||||||
|
[value]="result.value">
|
||||||
<!-- Contacts -->
|
<!-- Contacts -->
|
||||||
<ng-container *ngIf="resultSet.id === 'contacts'">
|
<ng-container *ngIf="resultSet.id === 'contacts'">
|
||||||
<ng-container *ngTemplateOutlet="contactResult; context: {$implicit: result}"></ng-container>
|
<ng-container *ngTemplateOutlet="contactResult; context: {$implicit: result}"></ng-container>
|
||||||
@@ -118,7 +122,7 @@
|
|||||||
#contactResult
|
#contactResult
|
||||||
let-result>
|
let-result>
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<div class="flex flex-shrink-0 items-center justify-center w-8 h-8 rounded-full overflow-hidden bg-primary-100 dark:bg-primary-800">
|
<div class="flex shrink-0 items-center justify-center w-8 h-8 rounded-full overflow-hidden bg-primary-100 dark:bg-primary-800">
|
||||||
<img
|
<img
|
||||||
*ngIf="result.avatar"
|
*ngIf="result.avatar"
|
||||||
[src]="result.avatar">
|
[src]="result.avatar">
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Component, ElementRef, EventEmitter, HostBinding, Input, OnChanges, OnDestroy, OnInit, Output, Renderer2, SimpleChanges, ViewChild, ViewEncapsulation } from '@angular/core';
|
import { Component, ElementRef, EventEmitter, HostBinding, Input, OnChanges, OnDestroy, OnInit, Output, Renderer2, SimpleChanges, ViewChild, ViewEncapsulation } from '@angular/core';
|
||||||
import { FormControl } from '@angular/forms';
|
import { UntypedFormControl } from '@angular/forms';
|
||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { MatAutocomplete } from '@angular/material/autocomplete';
|
||||||
import { debounceTime, filter, map, Subject, takeUntil } from 'rxjs';
|
import { debounceTime, filter, map, Subject, takeUntil } from 'rxjs';
|
||||||
import { fuseAnimations } from '@fuse/animations/public-api';
|
import { fuseAnimations } from '@fuse/animations/public-api';
|
||||||
|
|
||||||
@@ -20,7 +21,8 @@ export class SearchComponent implements OnChanges, OnInit, OnDestroy
|
|||||||
|
|
||||||
opened: boolean = false;
|
opened: boolean = false;
|
||||||
resultSets: any[];
|
resultSets: any[];
|
||||||
searchControl: FormControl = new FormControl();
|
searchControl: UntypedFormControl = new UntypedFormControl();
|
||||||
|
private _matAutocomplete: MatAutocomplete;
|
||||||
private _unsubscribeAll: Subject<any> = new Subject<any>();
|
private _unsubscribeAll: Subject<any> = new Subject<any>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -59,7 +61,7 @@ export class SearchComponent implements OnChanges, OnInit, OnDestroy
|
|||||||
set barSearchInput(value: ElementRef)
|
set barSearchInput(value: ElementRef)
|
||||||
{
|
{
|
||||||
// If the value exists, it means that the search input
|
// If the value exists, it means that the search input
|
||||||
// is now in the DOM and we can focus on the input..
|
// is now in the DOM, and we can focus on the input..
|
||||||
if ( value )
|
if ( value )
|
||||||
{
|
{
|
||||||
// Give Angular time to complete the change detection cycle
|
// Give Angular time to complete the change detection cycle
|
||||||
@@ -71,6 +73,17 @@ export class SearchComponent implements OnChanges, OnInit, OnDestroy
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setter for mat-autocomplete element reference
|
||||||
|
*
|
||||||
|
* @param value
|
||||||
|
*/
|
||||||
|
@ViewChild('matAutocomplete')
|
||||||
|
set matAutocomplete(value: MatAutocomplete)
|
||||||
|
{
|
||||||
|
this._matAutocomplete = value;
|
||||||
|
}
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------------------------------
|
||||||
// @ Lifecycle hooks
|
// @ Lifecycle hooks
|
||||||
// -----------------------------------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------------------------------
|
||||||
@@ -152,14 +165,12 @@ export class SearchComponent implements OnChanges, OnInit, OnDestroy
|
|||||||
*/
|
*/
|
||||||
onKeydown(event: KeyboardEvent): void
|
onKeydown(event: KeyboardEvent): void
|
||||||
{
|
{
|
||||||
// Listen for escape to close the search
|
// Escape
|
||||||
// if the appearance is 'bar'
|
if ( event.code === 'Escape' )
|
||||||
if ( this.appearance === 'bar' )
|
|
||||||
{
|
{
|
||||||
// Escape
|
// If the appearance is 'bar' and the mat-autocomplete is not open, close the search
|
||||||
if ( event.code === 'Escape' )
|
if ( this.appearance === 'bar' && !this._matAutocomplete.isOpen )
|
||||||
{
|
{
|
||||||
// Close the search
|
|
||||||
this.close();
|
this.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<div
|
<div
|
||||||
class="fixed flex items-center justify-center right-0 w-10 h-10 shadow-lg rounded-l-lg z-90 cursor-pointer bg-red-600 bg-opacity-90 print:hidden"
|
class="settings-cog fixed flex items-center justify-center right-0 w-10 h-10 shadow-lg rounded-l-lg z-90 cursor-pointer bg-red-600 bg-opacity-90 print:hidden"
|
||||||
[class.lg:right-0]="config.layout === 'centered' || config.layout === 'material'"
|
[class.lg:right-0]="config.layout === 'centered' || config.layout === 'material'"
|
||||||
[class.lg:right-16]="config.layout !== 'centered' && config.layout !== 'material'"
|
[class.lg:right-16]="config.layout !== 'centered' && config.layout !== 'material'"
|
||||||
style="top: 275px"
|
style="top: 275px"
|
||||||
@@ -38,18 +38,19 @@
|
|||||||
<!-- Theme -->
|
<!-- Theme -->
|
||||||
<div class="text-md font-semibold text-secondary">THEME</div>
|
<div class="text-md font-semibold text-secondary">THEME</div>
|
||||||
<div class="grid grid-cols-2 sm:grid-cols-3 gap-3 mt-6">
|
<div class="grid grid-cols-2 sm:grid-cols-3 gap-3 mt-6">
|
||||||
<ng-container *ngFor="let theme of themes">
|
<ng-container *ngFor="let theme of config.themes">
|
||||||
<div
|
<div
|
||||||
class="flex items-center justify-center px-4 py-3 rounded-full cursor-pointer ring-inset ring-primary bg-hover"
|
class="flex items-center justify-center px-4 py-3 rounded-full cursor-pointer ring-inset ring-primary bg-hover"
|
||||||
[class.ring-2]="config.theme === theme[0]"
|
[class.ring-2]="config.theme === theme.id"
|
||||||
(click)="setTheme(theme[0])">
|
[ngClass]="theme.id"
|
||||||
|
(click)="setTheme(theme.id)">
|
||||||
<div
|
<div
|
||||||
class="flex-0 w-3 h-3 rounded-full"
|
class="flex-0 w-3 h-3 rounded-full bg-primary"
|
||||||
[style.background-color]="theme[1].primary"></div>
|
></div>
|
||||||
<div
|
<div
|
||||||
class="ml-2.5 font-medium leading-5 truncate"
|
class="ml-2.5 font-medium leading-5 truncate"
|
||||||
[class.text-secondary]="config.theme !== theme[0]">
|
[class.text-secondary]="config.theme !== theme.id">
|
||||||
{{theme[0] | titlecase}}
|
{{theme.name}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|||||||
@@ -2,8 +2,7 @@ import { Component, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';
|
|||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { Subject, takeUntil } from 'rxjs';
|
import { Subject, takeUntil } from 'rxjs';
|
||||||
import { FuseConfigService } from '@fuse/services/config';
|
import { FuseConfigService } from '@fuse/services/config';
|
||||||
import { FuseTailwindService } from '@fuse/services/tailwind';
|
import { AppConfig, Scheme, Theme, Themes } from 'app/core/config/app.config';
|
||||||
import { AppConfig, Scheme, Theme } from 'app/core/config/app.config';
|
|
||||||
import { Layout } from 'app/layout/layout.types';
|
import { Layout } from 'app/layout/layout.types';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@@ -17,6 +16,13 @@ import { Layout } from 'app/layout/layout.types';
|
|||||||
flex: none;
|
flex: none;
|
||||||
width: auto;
|
width: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (screen and min-width: 1280px) {
|
||||||
|
|
||||||
|
empty-layout + settings .settings-cog {
|
||||||
|
right: 0 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
`
|
`
|
||||||
],
|
],
|
||||||
encapsulation: ViewEncapsulation.None
|
encapsulation: ViewEncapsulation.None
|
||||||
@@ -27,7 +33,7 @@ export class SettingsComponent implements OnInit, OnDestroy
|
|||||||
layout: Layout;
|
layout: Layout;
|
||||||
scheme: 'dark' | 'light';
|
scheme: 'dark' | 'light';
|
||||||
theme: string;
|
theme: string;
|
||||||
themes: [string, any][] = [];
|
themes: Themes;
|
||||||
private _unsubscribeAll: Subject<any> = new Subject<any>();
|
private _unsubscribeAll: Subject<any> = new Subject<any>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -35,8 +41,7 @@ export class SettingsComponent implements OnInit, OnDestroy
|
|||||||
*/
|
*/
|
||||||
constructor(
|
constructor(
|
||||||
private _router: Router,
|
private _router: Router,
|
||||||
private _fuseConfigService: FuseConfigService,
|
private _fuseConfigService: FuseConfigService
|
||||||
private _fuseTailwindService: FuseTailwindService
|
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@@ -50,13 +55,6 @@ export class SettingsComponent implements OnInit, OnDestroy
|
|||||||
*/
|
*/
|
||||||
ngOnInit(): void
|
ngOnInit(): void
|
||||||
{
|
{
|
||||||
// Get the themes
|
|
||||||
this._fuseTailwindService.tailwindConfig$
|
|
||||||
.pipe(takeUntil(this._unsubscribeAll))
|
|
||||||
.subscribe((config) => {
|
|
||||||
this.themes = Object.entries(config.themes);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Subscribe to config changes
|
// Subscribe to config changes
|
||||||
this._fuseConfigService.config$
|
this._fuseConfigService.config$
|
||||||
.pipe(takeUntil(this._unsubscribeAll))
|
.pipe(takeUntil(this._unsubscribeAll))
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
<div class="fixed inset-0 sm:static sm:inset-auto flex flex-col sm:min-w-90 sm:w-90 sm:rounded-2xl overflow-hidden shadow-lg">
|
<div class="fixed inset-0 sm:static sm:inset-auto flex flex-col sm:min-w-90 sm:w-90 sm:rounded-2xl overflow-hidden shadow-lg">
|
||||||
|
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<div class="flex flex-shrink-0 items-center py-4 pr-4 pl-6 bg-primary text-on-primary">
|
<div class="flex shrink-0 items-center py-4 pr-4 pl-6 bg-primary text-on-primary">
|
||||||
<div class="sm:hidden -ml-1 mr-3">
|
<div class="sm:hidden -ml-1 mr-3">
|
||||||
<button
|
<button
|
||||||
mat-icon-button
|
mat-icon-button
|
||||||
@@ -128,7 +128,7 @@
|
|||||||
</a>
|
</a>
|
||||||
<!-- Link content template -->
|
<!-- Link content template -->
|
||||||
<ng-template #linkContent>
|
<ng-template #linkContent>
|
||||||
<div class="relative flex flex-shrink-0 items-center justify-center w-12 h-12 mb-3 rounded-full bg-gray-100 dark:bg-gray-700">
|
<div class="relative flex shrink-0 items-center justify-center w-12 h-12 mb-3 rounded-full bg-gray-100 dark:bg-gray-700">
|
||||||
<mat-icon
|
<mat-icon
|
||||||
class="absolute opacity-0 group-hover:opacity-100 z-20 icon-size-5"
|
class="absolute opacity-0 group-hover:opacity-100 z-20 icon-size-5"
|
||||||
*ngIf="mode === 'modify'"
|
*ngIf="mode === 'modify'"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit, TemplateRef, ViewChild, ViewContainerRef, ViewEncapsulation } from '@angular/core';
|
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit, TemplateRef, ViewChild, ViewContainerRef, ViewEncapsulation } from '@angular/core';
|
||||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
|
||||||
import { Overlay, OverlayRef } from '@angular/cdk/overlay';
|
import { Overlay, OverlayRef } from '@angular/cdk/overlay';
|
||||||
import { TemplatePortal } from '@angular/cdk/portal';
|
import { TemplatePortal } from '@angular/cdk/portal';
|
||||||
import { MatButton } from '@angular/material/button';
|
import { MatButton } from '@angular/material/button';
|
||||||
@@ -20,7 +20,7 @@ export class ShortcutsComponent implements OnInit, OnDestroy
|
|||||||
@ViewChild('shortcutsPanel') private _shortcutsPanel: TemplateRef<any>;
|
@ViewChild('shortcutsPanel') private _shortcutsPanel: TemplateRef<any>;
|
||||||
|
|
||||||
mode: 'view' | 'modify' | 'add' | 'edit' = 'view';
|
mode: 'view' | 'modify' | 'add' | 'edit' = 'view';
|
||||||
shortcutForm: FormGroup;
|
shortcutForm: UntypedFormGroup;
|
||||||
shortcuts: Shortcut[];
|
shortcuts: Shortcut[];
|
||||||
private _overlayRef: OverlayRef;
|
private _overlayRef: OverlayRef;
|
||||||
private _unsubscribeAll: Subject<any> = new Subject<any>();
|
private _unsubscribeAll: Subject<any> = new Subject<any>();
|
||||||
@@ -30,7 +30,7 @@ export class ShortcutsComponent implements OnInit, OnDestroy
|
|||||||
*/
|
*/
|
||||||
constructor(
|
constructor(
|
||||||
private _changeDetectorRef: ChangeDetectorRef,
|
private _changeDetectorRef: ChangeDetectorRef,
|
||||||
private _formBuilder: FormBuilder,
|
private _formBuilder: UntypedFormBuilder,
|
||||||
private _shortcutsService: ShortcutsService,
|
private _shortcutsService: ShortcutsService,
|
||||||
private _overlay: Overlay,
|
private _overlay: Overlay,
|
||||||
private _viewContainerRef: ViewContainerRef
|
private _viewContainerRef: ViewContainerRef
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { ActivatedRoute, NavigationEnd, Router } from '@angular/router';
|
|||||||
import { combineLatest, filter, map, Subject, takeUntil } from 'rxjs';
|
import { combineLatest, filter, map, Subject, takeUntil } from 'rxjs';
|
||||||
import { FuseConfigService } from '@fuse/services/config';
|
import { FuseConfigService } from '@fuse/services/config';
|
||||||
import { FuseMediaWatcherService } from '@fuse/services/media-watcher';
|
import { FuseMediaWatcherService } from '@fuse/services/media-watcher';
|
||||||
|
import { FusePlatformService } from '@fuse/services/platform';
|
||||||
import { FUSE_VERSION } from '@fuse/version';
|
import { FUSE_VERSION } from '@fuse/version';
|
||||||
import { Layout } from 'app/layout/layout.types';
|
import { Layout } from 'app/layout/layout.types';
|
||||||
import { AppConfig } from 'app/core/config/app.config';
|
import { AppConfig } from 'app/core/config/app.config';
|
||||||
@@ -31,7 +32,8 @@ export class LayoutComponent implements OnInit, OnDestroy
|
|||||||
private _renderer2: Renderer2,
|
private _renderer2: Renderer2,
|
||||||
private _router: Router,
|
private _router: Router,
|
||||||
private _fuseConfigService: FuseConfigService,
|
private _fuseConfigService: FuseConfigService,
|
||||||
private _fuseMediaWatcherService: FuseMediaWatcherService
|
private _fuseMediaWatcherService: FuseMediaWatcherService,
|
||||||
|
private _fusePlatformService: FusePlatformService
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@@ -102,6 +104,9 @@ export class LayoutComponent implements OnInit, OnDestroy
|
|||||||
|
|
||||||
// Set the app version
|
// Set the app version
|
||||||
this._renderer2.setAttribute(this._document.querySelector('[ng-version]'), 'fuse-version', FUSE_VERSION);
|
this._renderer2.setAttribute(this._document.querySelector('[ng-version]'), 'fuse-version', FUSE_VERSION);
|
||||||
|
|
||||||
|
// Set the OS name
|
||||||
|
this._renderer2.addClass(this._document.body, this._fusePlatformService.osName);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -203,6 +208,6 @@ export class LayoutComponent implements OnInit, OnDestroy
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Add class name for the currently selected theme
|
// Add class name for the currently selected theme
|
||||||
this._document.body.classList.add(`theme-${this.theme}`);
|
this._document.body.classList.add(this.theme);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,10 +37,10 @@
|
|||||||
[svgIcon]="'heroicons_solid:user-circle'"></mat-icon>
|
[svgIcon]="'heroicons_solid:user-circle'"></mat-icon>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col items-center justify-center w-full mt-6">
|
<div class="flex flex-col items-center justify-center w-full mt-6">
|
||||||
<div class="w-full whitespace-nowrap overflow-ellipsis overflow-hidden text-center leading-normal font-medium">
|
<div class="w-full whitespace-nowrap text-ellipsis overflow-hidden text-center leading-normal font-medium">
|
||||||
{{user.name}}
|
{{user.name}}
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full mt-0.5 whitespace-nowrap overflow-ellipsis overflow-hidden text-center text-md leading-normal font-medium text-secondary">
|
<div class="w-full mt-0.5 whitespace-nowrap text-ellipsis overflow-hidden text-center text-md leading-normal font-medium text-secondary">
|
||||||
{{user.email}}
|
{{user.email}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -66,6 +66,9 @@ export class DenseLayoutComponent implements OnInit, OnDestroy
|
|||||||
|
|
||||||
// Check if the screen is small
|
// Check if the screen is small
|
||||||
this.isScreenSmall = !matchingAliases.includes('md');
|
this.isScreenSmall = !matchingAliases.includes('md');
|
||||||
|
|
||||||
|
// Change the navigation appearance
|
||||||
|
this.navigationAppearance = this.isScreenSmall ? 'default' : 'dense';
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,10 +23,10 @@
|
|||||||
<div class="flex items-center w-full px-6 py-8 border-t">
|
<div class="flex items-center w-full px-6 py-8 border-t">
|
||||||
<user></user>
|
<user></user>
|
||||||
<div class="flex flex-col w-full ml-4 overflow-hidden">
|
<div class="flex flex-col w-full ml-4 overflow-hidden">
|
||||||
<div class="w-full whitespace-nowrap overflow-ellipsis overflow-hidden leading-normal text-current opacity-80">
|
<div class="w-full whitespace-nowrap text-ellipsis overflow-hidden leading-normal text-current opacity-80">
|
||||||
{{user.name}}
|
{{user.name}}
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full mt-0.5 whitespace-nowrap text-sm overflow-ellipsis overflow-hidden leading-normal text-current opacity-50">
|
<div class="w-full mt-0.5 whitespace-nowrap text-sm text-ellipsis overflow-hidden leading-normal text-current opacity-50">
|
||||||
brian.hughes@company.com
|
brian.hughes@company.com
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -38,8 +38,8 @@ export class FileManagerMockApi
|
|||||||
// Clone the items
|
// Clone the items
|
||||||
let items = cloneDeep(this._items);
|
let items = cloneDeep(this._items);
|
||||||
|
|
||||||
// See if a folder id exist
|
// See if the folder id exist
|
||||||
const folderId = request.params.get('folderId') ?? null;
|
const folderId = request.params.get('folderId') === 'null' ? null : request.params.get('folderId');
|
||||||
|
|
||||||
// Filter the items by folder id. If folder id is null,
|
// Filter the items by folder id. If folder id is null,
|
||||||
// that means we want to root items which have folder id
|
// that means we want to root items which have folder id
|
||||||
|
|||||||
@@ -87,10 +87,10 @@ export class AuthMockApi
|
|||||||
});
|
});
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------------------------------
|
||||||
// @ Verify and refresh the access token - POST
|
// @ Sign in using the access token - POST
|
||||||
// -----------------------------------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------------------------------
|
||||||
this._fuseMockApiService
|
this._fuseMockApiService
|
||||||
.onPost('api/auth/refresh-access-token')
|
.onPost('api/auth/sign-in-with-token')
|
||||||
.reply(({request}) => {
|
.reply(({request}) => {
|
||||||
|
|
||||||
// Get the access token
|
// Get the access token
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -79,6 +79,9 @@ export class SearchMockApi
|
|||||||
|
|
||||||
// Add a link
|
// Add a link
|
||||||
result.link = '/apps/contacts/' + result.id;
|
result.link = '/apps/contacts/' + result.id;
|
||||||
|
|
||||||
|
// Add the name as the value
|
||||||
|
result.value = result.name;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add to the results
|
// Add to the results
|
||||||
@@ -95,6 +98,8 @@ export class SearchMockApi
|
|||||||
// Normalize the results
|
// Normalize the results
|
||||||
pagesResults.forEach((result: any) => {
|
pagesResults.forEach((result: any) => {
|
||||||
|
|
||||||
|
// Add the page title as the value
|
||||||
|
result.value = result.title;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add to the results
|
// Add to the results
|
||||||
@@ -113,6 +118,9 @@ export class SearchMockApi
|
|||||||
|
|
||||||
// Add a link
|
// Add a link
|
||||||
result.link = '/apps/tasks/' + result.id;
|
result.link = '/apps/tasks/' + result.id;
|
||||||
|
|
||||||
|
// Add the title as the value
|
||||||
|
result.value = result.title;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add to the results
|
// Add to the results
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { cloneDeep } from 'lodash-es';
|
import { cloneDeep } from 'lodash-es';
|
||||||
import { FuseMockApiService } from '@fuse/lib/mock-api';
|
import { FuseMockApiService } from '@fuse/lib/mock-api';
|
||||||
import { feather, heroicons, iconsmind, material } from 'app/mock-api/ui/icons/data';
|
import { feather, heroicons, material } from 'app/mock-api/ui/icons/data';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
@@ -10,7 +10,6 @@ export class IconsMockApi
|
|||||||
{
|
{
|
||||||
private readonly _feather: any = feather;
|
private readonly _feather: any = feather;
|
||||||
private readonly _heroicons: any = heroicons;
|
private readonly _heroicons: any = heroicons;
|
||||||
private readonly _iconsmind: any = iconsmind;
|
|
||||||
private readonly _material: any = material;
|
private readonly _material: any = material;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -41,7 +40,7 @@ export class IconsMockApi
|
|||||||
{
|
{
|
||||||
namespace: 'feather',
|
namespace: 'feather',
|
||||||
name : 'Feather',
|
name : 'Feather',
|
||||||
grid : 6,
|
grid : 'icon-size-6',
|
||||||
list : cloneDeep(this._feather)
|
list : cloneDeep(this._feather)
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
@@ -56,7 +55,7 @@ export class IconsMockApi
|
|||||||
{
|
{
|
||||||
namespace: 'heroicons_outline',
|
namespace: 'heroicons_outline',
|
||||||
name : 'Heroicons Outline',
|
name : 'Heroicons Outline',
|
||||||
grid : 6,
|
grid : 'icon-size-6',
|
||||||
list : cloneDeep(this._heroicons)
|
list : cloneDeep(this._heroicons)
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
@@ -71,26 +70,11 @@ export class IconsMockApi
|
|||||||
{
|
{
|
||||||
namespace: 'heroicons_solid',
|
namespace: 'heroicons_solid',
|
||||||
name : 'Heroicons Solid',
|
name : 'Heroicons Solid',
|
||||||
grid : 5,
|
grid : 'icon-size-5',
|
||||||
list : cloneDeep(this._heroicons)
|
list : cloneDeep(this._heroicons)
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------------------------------
|
|
||||||
// @ Iconsmind icons - GET
|
|
||||||
// -----------------------------------------------------------------------------------------------------
|
|
||||||
this._fuseMockApiService
|
|
||||||
.onGet('api/ui/icons/iconsmind')
|
|
||||||
.reply(() => [
|
|
||||||
200,
|
|
||||||
{
|
|
||||||
namespace: 'iconsmind',
|
|
||||||
name : 'Iconsmind',
|
|
||||||
grid : 10,
|
|
||||||
list : cloneDeep(this._iconsmind)
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------------------------------
|
||||||
// @ Material solid icons - GET
|
// @ Material solid icons - GET
|
||||||
// -----------------------------------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------------------------------
|
||||||
@@ -101,7 +85,7 @@ export class IconsMockApi
|
|||||||
{
|
{
|
||||||
namespace: 'mat_solid',
|
namespace: 'mat_solid',
|
||||||
name : 'Material Solid',
|
name : 'Material Solid',
|
||||||
grid : 6,
|
grid : 'icon-size-6',
|
||||||
list : cloneDeep(this._material)
|
list : cloneDeep(this._material)
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
@@ -116,7 +100,7 @@ export class IconsMockApi
|
|||||||
{
|
{
|
||||||
namespace: 'mat_outline',
|
namespace: 'mat_outline',
|
||||||
name : 'Material Outline',
|
name : 'Material Outline',
|
||||||
grid : 6,
|
grid : 'icon-size-6',
|
||||||
list : cloneDeep(this._material)
|
list : cloneDeep(this._material)
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
@@ -131,7 +115,7 @@ export class IconsMockApi
|
|||||||
{
|
{
|
||||||
namespace: '',
|
namespace: '',
|
||||||
name : 'Material Twotone',
|
name : 'Material Twotone',
|
||||||
grid : 6,
|
grid : 'icon-size-6',
|
||||||
list : cloneDeep(this._material)
|
list : cloneDeep(this._material)
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1 +0,0 @@
|
|||||||
<router-outlet></router-outlet>
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
import { ChangeDetectionStrategy, Component, ViewEncapsulation } from '@angular/core';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector : 'academy',
|
|
||||||
templateUrl : './academy.component.html',
|
|
||||||
encapsulation : ViewEncapsulation.None,
|
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush
|
|
||||||
})
|
|
||||||
export class AcademyComponent
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Constructor
|
|
||||||
*/
|
|
||||||
constructor()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
import { NgModule } from '@angular/core';
|
|
||||||
import { RouterModule } from '@angular/router';
|
|
||||||
import { MatButtonModule } from '@angular/material/button';
|
|
||||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
|
||||||
import { MatIconModule } from '@angular/material/icon';
|
|
||||||
import { MatInputModule } from '@angular/material/input';
|
|
||||||
import { MatProgressBarModule } from '@angular/material/progress-bar';
|
|
||||||
import { MatSelectModule } from '@angular/material/select';
|
|
||||||
import { MatSidenavModule } from '@angular/material/sidenav';
|
|
||||||
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
|
|
||||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
|
||||||
import { FuseFindByKeyPipeModule } from '@fuse/pipes/find-by-key';
|
|
||||||
import { SharedModule } from 'app/shared/shared.module';
|
|
||||||
import { academyRoutes } from 'app/modules/admin/apps/academy/academy.routing';
|
|
||||||
import { AcademyComponent } from 'app/modules/admin/apps/academy/academy.component';
|
|
||||||
import { AcademyDetailsComponent } from 'app/modules/admin/apps/academy/details/details.component';
|
|
||||||
import { AcademyListComponent } from 'app/modules/admin/apps/academy/list/list.component';
|
|
||||||
import { MatTabsModule } from '@angular/material/tabs';
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
declarations: [
|
|
||||||
AcademyComponent,
|
|
||||||
AcademyDetailsComponent,
|
|
||||||
AcademyListComponent
|
|
||||||
],
|
|
||||||
imports : [
|
|
||||||
RouterModule.forChild(academyRoutes),
|
|
||||||
MatButtonModule,
|
|
||||||
MatFormFieldModule,
|
|
||||||
MatIconModule,
|
|
||||||
MatInputModule,
|
|
||||||
MatProgressBarModule,
|
|
||||||
MatSelectModule,
|
|
||||||
MatSidenavModule,
|
|
||||||
MatSlideToggleModule,
|
|
||||||
MatTooltipModule,
|
|
||||||
FuseFindByKeyPipeModule,
|
|
||||||
SharedModule,
|
|
||||||
MatTabsModule
|
|
||||||
]
|
|
||||||
})
|
|
||||||
export class AcademyModule
|
|
||||||
{
|
|
||||||
}
|
|
||||||
@@ -1,109 +0,0 @@
|
|||||||
import { Injectable } from '@angular/core';
|
|
||||||
import { ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot } from '@angular/router';
|
|
||||||
import { catchError, Observable, throwError } from 'rxjs';
|
|
||||||
import { Category, Course } from 'app/modules/admin/apps/academy/academy.types';
|
|
||||||
import { AcademyService } from 'app/modules/admin/apps/academy/academy.service';
|
|
||||||
|
|
||||||
@Injectable({
|
|
||||||
providedIn: 'root'
|
|
||||||
})
|
|
||||||
export class AcademyCategoriesResolver implements Resolve<any>
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Constructor
|
|
||||||
*/
|
|
||||||
constructor(private _academyService: AcademyService)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------------------------------
|
|
||||||
// @ Public methods
|
|
||||||
// -----------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolver
|
|
||||||
*
|
|
||||||
* @param route
|
|
||||||
* @param state
|
|
||||||
*/
|
|
||||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<Category[]>
|
|
||||||
{
|
|
||||||
return this._academyService.getCategories();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Injectable({
|
|
||||||
providedIn: 'root'
|
|
||||||
})
|
|
||||||
export class AcademyCoursesResolver implements Resolve<any>
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Constructor
|
|
||||||
*/
|
|
||||||
constructor(private _academyService: AcademyService)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------------------------------
|
|
||||||
// @ Public methods
|
|
||||||
// -----------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolver
|
|
||||||
*
|
|
||||||
* @param route
|
|
||||||
* @param state
|
|
||||||
*/
|
|
||||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<Course[]>
|
|
||||||
{
|
|
||||||
return this._academyService.getCourses();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Injectable({
|
|
||||||
providedIn: 'root'
|
|
||||||
})
|
|
||||||
export class AcademyCourseResolver implements Resolve<any>
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Constructor
|
|
||||||
*/
|
|
||||||
constructor(
|
|
||||||
private _router: Router,
|
|
||||||
private _academyService: AcademyService
|
|
||||||
)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------------------------------
|
|
||||||
// @ Public methods
|
|
||||||
// -----------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolver
|
|
||||||
*
|
|
||||||
* @param route
|
|
||||||
* @param state
|
|
||||||
*/
|
|
||||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<Course>
|
|
||||||
{
|
|
||||||
return this._academyService.getCourseById(route.paramMap.get('id'))
|
|
||||||
.pipe(
|
|
||||||
// Error here means the requested task is not available
|
|
||||||
catchError((error) => {
|
|
||||||
|
|
||||||
// Log the error
|
|
||||||
console.error(error);
|
|
||||||
|
|
||||||
// Get the parent url
|
|
||||||
const parentUrl = state.url.split('/').slice(0, -1).join('/');
|
|
||||||
|
|
||||||
// Navigate to there
|
|
||||||
this._router.navigateByUrl(parentUrl);
|
|
||||||
|
|
||||||
// Throw an error
|
|
||||||
return throwError(error);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
import { Route } from '@angular/router';
|
|
||||||
import { AcademyComponent } from 'app/modules/admin/apps/academy/academy.component';
|
|
||||||
import { AcademyListComponent } from 'app/modules/admin/apps/academy/list/list.component';
|
|
||||||
import { AcademyDetailsComponent } from 'app/modules/admin/apps/academy/details/details.component';
|
|
||||||
import { AcademyCategoriesResolver, AcademyCourseResolver, AcademyCoursesResolver } from 'app/modules/admin/apps/academy/academy.resolvers';
|
|
||||||
|
|
||||||
export const academyRoutes: Route[] = [
|
|
||||||
{
|
|
||||||
path : '',
|
|
||||||
component: AcademyComponent,
|
|
||||||
resolve : {
|
|
||||||
categories: AcademyCategoriesResolver
|
|
||||||
},
|
|
||||||
children : [
|
|
||||||
{
|
|
||||||
path : '',
|
|
||||||
pathMatch: 'full',
|
|
||||||
component: AcademyListComponent,
|
|
||||||
resolve : {
|
|
||||||
courses: AcademyCoursesResolver
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path : ':id',
|
|
||||||
component: AcademyDetailsComponent,
|
|
||||||
resolve : {
|
|
||||||
course: AcademyCourseResolver
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
];
|
|
||||||
@@ -1,104 +0,0 @@
|
|||||||
import { Injectable } from '@angular/core';
|
|
||||||
import { HttpClient } from '@angular/common/http';
|
|
||||||
import { BehaviorSubject, map, Observable, of, switchMap, tap, throwError } from 'rxjs';
|
|
||||||
import { Category, Course } from 'app/modules/admin/apps/academy/academy.types';
|
|
||||||
|
|
||||||
@Injectable({
|
|
||||||
providedIn: 'root'
|
|
||||||
})
|
|
||||||
export class AcademyService
|
|
||||||
{
|
|
||||||
// Private
|
|
||||||
private _categories: BehaviorSubject<Category[] | null> = new BehaviorSubject(null);
|
|
||||||
private _course: BehaviorSubject<Course | null> = new BehaviorSubject(null);
|
|
||||||
private _courses: BehaviorSubject<Course[] | null> = new BehaviorSubject(null);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor
|
|
||||||
*/
|
|
||||||
constructor(private _httpClient: HttpClient)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------------------------------
|
|
||||||
// @ Accessors
|
|
||||||
// -----------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Getter for categories
|
|
||||||
*/
|
|
||||||
get categories$(): Observable<Category[]>
|
|
||||||
{
|
|
||||||
return this._categories.asObservable();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Getter for courses
|
|
||||||
*/
|
|
||||||
get courses$(): Observable<Course[]>
|
|
||||||
{
|
|
||||||
return this._courses.asObservable();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Getter for course
|
|
||||||
*/
|
|
||||||
get course$(): Observable<Course>
|
|
||||||
{
|
|
||||||
return this._course.asObservable();
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------------------------------
|
|
||||||
// @ Public methods
|
|
||||||
// -----------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get categories
|
|
||||||
*/
|
|
||||||
getCategories(): Observable<Category[]>
|
|
||||||
{
|
|
||||||
return this._httpClient.get<Category[]>('api/apps/academy/categories').pipe(
|
|
||||||
tap((response: any) => {
|
|
||||||
this._categories.next(response);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get courses
|
|
||||||
*/
|
|
||||||
getCourses(): Observable<Course[]>
|
|
||||||
{
|
|
||||||
return this._httpClient.get<Course[]>('api/apps/academy/courses').pipe(
|
|
||||||
tap((response: any) => {
|
|
||||||
this._courses.next(response);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get course by id
|
|
||||||
*/
|
|
||||||
getCourseById(id: string): Observable<Course>
|
|
||||||
{
|
|
||||||
return this._httpClient.get<Course>('api/apps/academy/courses/course', {params: {id}}).pipe(
|
|
||||||
map((course) => {
|
|
||||||
|
|
||||||
// Update the course
|
|
||||||
this._course.next(course);
|
|
||||||
|
|
||||||
// Return the course
|
|
||||||
return course;
|
|
||||||
}),
|
|
||||||
switchMap((course) => {
|
|
||||||
|
|
||||||
if ( !course )
|
|
||||||
{
|
|
||||||
return throwError('Could not found course with id of ' + id + '!');
|
|
||||||
}
|
|
||||||
|
|
||||||
return of(course);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
export interface Category
|
|
||||||
{
|
|
||||||
id?: string;
|
|
||||||
title?: string;
|
|
||||||
slug?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Course
|
|
||||||
{
|
|
||||||
id?: string;
|
|
||||||
title?: string;
|
|
||||||
slug?: string;
|
|
||||||
description?: string;
|
|
||||||
category?: string;
|
|
||||||
duration?: number;
|
|
||||||
steps?: {
|
|
||||||
order?: number;
|
|
||||||
title?: string;
|
|
||||||
subtitle?: string;
|
|
||||||
content?: string;
|
|
||||||
}[];
|
|
||||||
totalSteps?: number;
|
|
||||||
updatedAt?: number;
|
|
||||||
featured?: boolean;
|
|
||||||
progress?: {
|
|
||||||
currentStep?: number;
|
|
||||||
completed?: number;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,201 +0,0 @@
|
|||||||
<div class="absolute inset-0 flex flex-col min-w-0 overflow-hidden">
|
|
||||||
|
|
||||||
<mat-drawer-container class="flex-auto h-full">
|
|
||||||
|
|
||||||
<!-- Drawer -->
|
|
||||||
<mat-drawer
|
|
||||||
class="w-90 dark:bg-gray-900"
|
|
||||||
[autoFocus]="false"
|
|
||||||
[mode]="drawerMode"
|
|
||||||
[opened]="drawerOpened"
|
|
||||||
#matDrawer>
|
|
||||||
<div class="flex flex-col items-start p-8 border-b">
|
|
||||||
<!-- Back to courses -->
|
|
||||||
<a
|
|
||||||
class="inline-flex items-center leading-6 text-primary hover:underline"
|
|
||||||
[routerLink]="['..']">
|
|
||||||
<span class="inline-flex items-center">
|
|
||||||
<mat-icon
|
|
||||||
class="icon-size-5 text-current"
|
|
||||||
[svgIcon]="'heroicons_solid:arrow-sm-left'"></mat-icon>
|
|
||||||
<span class="ml-1.5 font-medium leading-5">Back to courses</span>
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
<!-- Course category -->
|
|
||||||
<ng-container *ngIf="(course.category | fuseFindByKey:'slug':categories) as category">
|
|
||||||
<div
|
|
||||||
class="mt-7 py-0.5 px-3 rounded-full text-sm font-semibold"
|
|
||||||
[ngClass]="{'text-blue-800 bg-blue-100 dark:text-blue-50 dark:bg-blue-500': category.slug === 'web',
|
|
||||||
'text-green-800 bg-green-100 dark:text-green-50 dark:bg-green-500': category.slug === 'android',
|
|
||||||
'text-pink-800 bg-pink-100 dark:text-pink-50 dark:bg-pink-500': category.slug === 'cloud',
|
|
||||||
'text-amber-800 bg-amber-100 dark:text-amber-50 dark:bg-amber-500': category.slug === 'firebase'}">
|
|
||||||
{{category.title}}
|
|
||||||
</div>
|
|
||||||
</ng-container>
|
|
||||||
<!-- Course title & description -->
|
|
||||||
<div class="mt-3 text-2xl font-semibold">{{course.title}}</div>
|
|
||||||
<div class="text-secondary">{{course.description}}</div>
|
|
||||||
<!-- Course time -->
|
|
||||||
<div class="mt-6 flex items-center leading-5 text-md text-secondary">
|
|
||||||
<mat-icon
|
|
||||||
class="icon-size-5 text-hint"
|
|
||||||
[svgIcon]="'heroicons_solid:clock'"></mat-icon>
|
|
||||||
<div class="ml-1.5">{{course.duration}} minutes</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Steps -->
|
|
||||||
<div class="py-2 px-8">
|
|
||||||
<ol>
|
|
||||||
<ng-container *ngFor="let step of course.steps; let last = last; trackBy: trackByFn">
|
|
||||||
<li
|
|
||||||
class="relative group py-6"
|
|
||||||
[class.current-step]="step.order === currentStep">
|
|
||||||
<ng-container *ngIf="!last">
|
|
||||||
<div
|
|
||||||
class="absolute top-6 left-4 w-0.5 h-full -ml-px"
|
|
||||||
[ngClass]="{'bg-primary': step.order < currentStep,
|
|
||||||
'bg-gray-300 dark:bg-gray-600': step.order >= currentStep}"></div>
|
|
||||||
</ng-container>
|
|
||||||
<div
|
|
||||||
class="relative flex items-start cursor-pointer"
|
|
||||||
(click)="goToStep(step.order)">
|
|
||||||
<div
|
|
||||||
class="flex flex-0 items-center justify-center w-8 h-8 rounded-full ring-2 ring-inset ring-transparent bg-card dark:bg-default"
|
|
||||||
[ngClass]="{'bg-primary dark:bg-primary text-on-primary group-hover:bg-primary-800': step.order < currentStep,
|
|
||||||
'ring-primary': step.order === currentStep,
|
|
||||||
'ring-gray-300 dark:ring-gray-600 group-hover:ring-gray-400': step.order > currentStep}">
|
|
||||||
<!-- Check icon, show if the step is completed -->
|
|
||||||
<ng-container *ngIf="step.order < currentStep">
|
|
||||||
<mat-icon
|
|
||||||
class="icon-size-5 text-current"
|
|
||||||
[svgIcon]="'heroicons_solid:check'"></mat-icon>
|
|
||||||
</ng-container>
|
|
||||||
<!-- Step order, show if the step is the current step -->
|
|
||||||
<ng-container *ngIf="step.order === currentStep">
|
|
||||||
<div class="text-md font-semibold text-primary dark:text-primary-500">{{step.order + 1}}</div>
|
|
||||||
</ng-container>
|
|
||||||
<!-- Step order, show if the step is not completed -->
|
|
||||||
<ng-container *ngIf="step.order > currentStep">
|
|
||||||
<div class="text-md font-semibold text-hint group-hover:text-secondary">{{step.order + 1}}</div>
|
|
||||||
</ng-container>
|
|
||||||
</div>
|
|
||||||
<div class="ml-4">
|
|
||||||
<div class="font-medium leading-4">{{step.title}}</div>
|
|
||||||
<div class="mt-1.5 text-md leading-4 text-secondary">{{step.subtitle}}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ng-container>
|
|
||||||
</ol>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</mat-drawer>
|
|
||||||
|
|
||||||
<!-- Drawer content -->
|
|
||||||
<mat-drawer-content class="flex flex-col overflow-hidden">
|
|
||||||
|
|
||||||
<!-- Header -->
|
|
||||||
<div class="lg:hidden flex flex-0 items-center py-2 pl-4 pr-6 sm:py-4 md:pl-6 md:pr-8 border-b lg:border-b-0 bg-card dark:bg-transparent">
|
|
||||||
<!-- Title & Actions -->
|
|
||||||
<button
|
|
||||||
mat-icon-button
|
|
||||||
[routerLink]="['..']">
|
|
||||||
<mat-icon [svgIcon]="'heroicons_outline:arrow-sm-left'"></mat-icon>
|
|
||||||
</button>
|
|
||||||
<h2 class="ml-2.5 text-md sm:text-xl font-medium tracking-tight truncate">
|
|
||||||
{{course.title}}
|
|
||||||
</h2>
|
|
||||||
</div>
|
|
||||||
<mat-progress-bar
|
|
||||||
class="hidden lg:block flex-0 h-0.5 w-full"
|
|
||||||
[value]="100 * (currentStep + 1) / course.totalSteps"></mat-progress-bar>
|
|
||||||
|
|
||||||
<!-- Main -->
|
|
||||||
<div
|
|
||||||
class="flex-auto overflow-y-auto"
|
|
||||||
cdkScrollable>
|
|
||||||
|
|
||||||
<!-- Steps -->
|
|
||||||
<mat-tab-group
|
|
||||||
class="fuse-mat-no-header"
|
|
||||||
[animationDuration]="'200'"
|
|
||||||
#courseSteps>
|
|
||||||
<ng-container *ngFor="let step of course.steps; trackBy: trackByFn">
|
|
||||||
<mat-tab>
|
|
||||||
<ng-template matTabContent>
|
|
||||||
<div
|
|
||||||
class="prose prose-sm max-w-3xl mx-auto sm:my-2 lg:mt-4 p-6 sm:p-10 sm:py-12 rounded-2xl shadow overflow-hidden bg-card"
|
|
||||||
[innerHTML]="step.content"></div>
|
|
||||||
</ng-template>
|
|
||||||
</mat-tab>
|
|
||||||
</ng-container>
|
|
||||||
</mat-tab-group>
|
|
||||||
|
|
||||||
<!-- Navigation - Desktop -->
|
|
||||||
<div class="z-10 sticky hidden lg:flex bottom-4 p-4">
|
|
||||||
<div class="flex items-center justify-center mx-auto p-2 rounded-full shadow-lg bg-primary">
|
|
||||||
<button
|
|
||||||
class="flex-0"
|
|
||||||
mat-flat-button
|
|
||||||
[color]="'primary'"
|
|
||||||
(click)="goToPreviousStep()">
|
|
||||||
<mat-icon
|
|
||||||
class="mr-2"
|
|
||||||
[svgIcon]="'heroicons_outline:arrow-narrow-left'"></mat-icon>
|
|
||||||
<span class="mr-1">Prev</span>
|
|
||||||
</button>
|
|
||||||
<div class="flex items-center justify-center mx-2.5 font-medium leading-5 text-on-primary">
|
|
||||||
<span>{{currentStep + 1}}</span>
|
|
||||||
<span class="mx-0.5 text-hint">/</span>
|
|
||||||
<span>{{course.totalSteps}}</span>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
class="flex-0"
|
|
||||||
mat-flat-button
|
|
||||||
[color]="'primary'"
|
|
||||||
(click)="goToNextStep()">
|
|
||||||
<span class="ml-1">Next</span>
|
|
||||||
<mat-icon
|
|
||||||
class="ml-2"
|
|
||||||
[svgIcon]="'heroicons_outline:arrow-narrow-right'"></mat-icon>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Progress & Navigation - Mobile -->
|
|
||||||
<div class="lg:hidden flex items-center p-4 border-t bg-card">
|
|
||||||
<button
|
|
||||||
mat-icon-button
|
|
||||||
(click)="matDrawer.toggle()">
|
|
||||||
<mat-icon [svgIcon]="'heroicons_outline:view-list'"></mat-icon>
|
|
||||||
</button>
|
|
||||||
<div class="flex items-center justify-center ml-1 lg:ml-2 font-medium leading-5">
|
|
||||||
<span>{{currentStep + 1}}</span>
|
|
||||||
<span class="mx-0.5 text-hint">/</span>
|
|
||||||
<span>{{course.totalSteps}}</span>
|
|
||||||
</div>
|
|
||||||
<mat-progress-bar
|
|
||||||
class="flex-auto ml-6 rounded-full"
|
|
||||||
[value]="100 * (currentStep + 1) / course.totalSteps"></mat-progress-bar>
|
|
||||||
<button
|
|
||||||
class="ml-4"
|
|
||||||
mat-icon-button
|
|
||||||
(click)="goToPreviousStep()">
|
|
||||||
<mat-icon [svgIcon]="'heroicons_outline:arrow-narrow-left'"></mat-icon>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="ml-0.5"
|
|
||||||
mat-icon-button
|
|
||||||
(click)="goToNextStep()">
|
|
||||||
<mat-icon [svgIcon]="'heroicons_outline:arrow-narrow-right'"></mat-icon>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</mat-drawer-content>
|
|
||||||
|
|
||||||
</mat-drawer-container>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
@@ -1,203 +0,0 @@
|
|||||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, Inject, OnDestroy, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
|
|
||||||
import { DOCUMENT } from '@angular/common';
|
|
||||||
import { MatTabGroup } from '@angular/material/tabs';
|
|
||||||
import { Subject, takeUntil } from 'rxjs';
|
|
||||||
import { FuseMediaWatcherService } from '@fuse/services/media-watcher';
|
|
||||||
import { Category, Course } from 'app/modules/admin/apps/academy/academy.types';
|
|
||||||
import { AcademyService } from 'app/modules/admin/apps/academy/academy.service';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector : 'academy-details',
|
|
||||||
templateUrl : './details.component.html',
|
|
||||||
encapsulation : ViewEncapsulation.None,
|
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush
|
|
||||||
})
|
|
||||||
export class AcademyDetailsComponent implements OnInit, OnDestroy
|
|
||||||
{
|
|
||||||
@ViewChild('courseSteps', {static: true}) courseSteps: MatTabGroup;
|
|
||||||
categories: Category[];
|
|
||||||
course: Course;
|
|
||||||
currentStep: number = 0;
|
|
||||||
drawerMode: 'over' | 'side' = 'side';
|
|
||||||
drawerOpened: boolean = true;
|
|
||||||
private _unsubscribeAll: Subject<any> = new Subject<any>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor
|
|
||||||
*/
|
|
||||||
constructor(
|
|
||||||
@Inject(DOCUMENT) private _document: Document,
|
|
||||||
private _academyService: AcademyService,
|
|
||||||
private _changeDetectorRef: ChangeDetectorRef,
|
|
||||||
private _elementRef: ElementRef,
|
|
||||||
private _fuseMediaWatcherService: FuseMediaWatcherService
|
|
||||||
)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------------------------------
|
|
||||||
// @ Lifecycle hooks
|
|
||||||
// -----------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* On init
|
|
||||||
*/
|
|
||||||
ngOnInit(): void
|
|
||||||
{
|
|
||||||
// Get the categories
|
|
||||||
this._academyService.categories$
|
|
||||||
.pipe(takeUntil(this._unsubscribeAll))
|
|
||||||
.subscribe((categories: Category[]) => {
|
|
||||||
|
|
||||||
// Get the categories
|
|
||||||
this.categories = categories;
|
|
||||||
|
|
||||||
// Mark for check
|
|
||||||
this._changeDetectorRef.markForCheck();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Get the course
|
|
||||||
this._academyService.course$
|
|
||||||
.pipe(takeUntil(this._unsubscribeAll))
|
|
||||||
.subscribe((course: Course) => {
|
|
||||||
|
|
||||||
// Get the course
|
|
||||||
this.course = course;
|
|
||||||
|
|
||||||
// Go to step
|
|
||||||
this.goToStep(course.progress.currentStep);
|
|
||||||
|
|
||||||
// Mark for check
|
|
||||||
this._changeDetectorRef.markForCheck();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Subscribe to media changes
|
|
||||||
this._fuseMediaWatcherService.onMediaChange$
|
|
||||||
.pipe(takeUntil(this._unsubscribeAll))
|
|
||||||
.subscribe(({matchingAliases}) => {
|
|
||||||
|
|
||||||
// Set the drawerMode and drawerOpened
|
|
||||||
if ( matchingAliases.includes('lg') )
|
|
||||||
{
|
|
||||||
this.drawerMode = 'side';
|
|
||||||
this.drawerOpened = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
this.drawerMode = 'over';
|
|
||||||
this.drawerOpened = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mark for check
|
|
||||||
this._changeDetectorRef.markForCheck();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* On destroy
|
|
||||||
*/
|
|
||||||
ngOnDestroy(): void
|
|
||||||
{
|
|
||||||
// Unsubscribe from all subscriptions
|
|
||||||
this._unsubscribeAll.next(null);
|
|
||||||
this._unsubscribeAll.complete();
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------------------------------
|
|
||||||
// @ Public methods
|
|
||||||
// -----------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Go to given step
|
|
||||||
*
|
|
||||||
* @param step
|
|
||||||
*/
|
|
||||||
goToStep(step: number): void
|
|
||||||
{
|
|
||||||
// Set the current step
|
|
||||||
this.currentStep = step;
|
|
||||||
|
|
||||||
// Go to the step
|
|
||||||
this.courseSteps.selectedIndex = this.currentStep;
|
|
||||||
|
|
||||||
// Mark for check
|
|
||||||
this._changeDetectorRef.markForCheck();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Go to previous step
|
|
||||||
*/
|
|
||||||
goToPreviousStep(): void
|
|
||||||
{
|
|
||||||
// Return if we already on the first step
|
|
||||||
if ( this.currentStep === 0 )
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Go to step
|
|
||||||
this.goToStep(this.currentStep - 1);
|
|
||||||
|
|
||||||
// Scroll the current step selector from sidenav into view
|
|
||||||
this._scrollCurrentStepElementIntoView();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Go to next step
|
|
||||||
*/
|
|
||||||
goToNextStep(): void
|
|
||||||
{
|
|
||||||
// Return if we already on the last step
|
|
||||||
if ( this.currentStep === this.course.totalSteps - 1 )
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Go to step
|
|
||||||
this.goToStep(this.currentStep + 1);
|
|
||||||
|
|
||||||
// Scroll the current step selector from sidenav into view
|
|
||||||
this._scrollCurrentStepElementIntoView();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Track by function for ngFor loops
|
|
||||||
*
|
|
||||||
* @param index
|
|
||||||
* @param item
|
|
||||||
*/
|
|
||||||
trackByFn(index: number, item: any): any
|
|
||||||
{
|
|
||||||
return item.id || index;
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------------------------------
|
|
||||||
// @ Private methods
|
|
||||||
// -----------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Scrolls the current step element from
|
|
||||||
* sidenav into the view. This only happens when
|
|
||||||
* previous/next buttons pressed as we don't want
|
|
||||||
* to change the scroll position of the sidebar
|
|
||||||
* when the user actually clicks around the sidebar.
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
private _scrollCurrentStepElementIntoView(): void
|
|
||||||
{
|
|
||||||
// Wrap everything into setTimeout so we can make sure that the 'current-step' class points to correct element
|
|
||||||
setTimeout(() => {
|
|
||||||
|
|
||||||
// Get the current step element and scroll it into view
|
|
||||||
const currentStepElement = this._document.getElementsByClassName('current-step')[0];
|
|
||||||
if ( currentStepElement )
|
|
||||||
{
|
|
||||||
currentStepElement.scrollIntoView({
|
|
||||||
behavior: 'smooth',
|
|
||||||
block : 'start'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,196 +0,0 @@
|
|||||||
<div
|
|
||||||
class="absolute inset-0 flex flex-col min-w-0 overflow-y-auto"
|
|
||||||
cdkScrollable>
|
|
||||||
|
|
||||||
<!-- Header -->
|
|
||||||
<div class="relative flex-0 py-8 px-4 sm:p-16 overflow-hidden bg-gray-800 dark">
|
|
||||||
<!-- Background - @formatter:off -->
|
|
||||||
<!-- Rings -->
|
|
||||||
<svg class="absolute inset-0 pointer-events-none"
|
|
||||||
viewBox="0 0 960 540" width="100%" height="100%" preserveAspectRatio="xMidYMax slice" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<g class="text-gray-700 opacity-25" fill="none" stroke="currentColor" stroke-width="100">
|
|
||||||
<circle r="234" cx="196" cy="23"></circle>
|
|
||||||
<circle r="234" cx="790" cy="491"></circle>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
<!-- @formatter:on -->
|
|
||||||
<div class="z-10 relative flex flex-col items-center">
|
|
||||||
<h2 class="text-xl font-semibold">FUSE ACADEMY</h2>
|
|
||||||
<div class="mt-1 text-4xl sm:text-7xl font-extrabold tracking-tight leading-tight text-center">
|
|
||||||
What do you want to learn today?
|
|
||||||
</div>
|
|
||||||
<div class="max-w-2xl mt-6 sm:text-2xl text-center tracking-tight text-secondary">
|
|
||||||
Our courses will step you through the process of a building small applications, or adding new features to existing applications.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Main -->
|
|
||||||
<div class="flex flex-auto p-6 sm:p-10">
|
|
||||||
|
|
||||||
<div class="flex flex-col flex-auto w-full max-w-xs sm:max-w-5xl mx-auto">
|
|
||||||
<!-- Filters -->
|
|
||||||
<div class="flex flex-col sm:flex-row items-center justify-between w-full max-w-xs sm:max-w-none">
|
|
||||||
<mat-form-field class="fuse-mat-no-subscript w-full sm:w-36">
|
|
||||||
<mat-select
|
|
||||||
[value]="'all'"
|
|
||||||
(selectionChange)="filterByCategory($event)">
|
|
||||||
<mat-option [value]="'all'">All</mat-option>
|
|
||||||
<ng-container *ngFor="let category of categories; trackBy: trackByFn">
|
|
||||||
<mat-option [value]="category.slug">{{category.title}}</mat-option>
|
|
||||||
</ng-container>
|
|
||||||
</mat-select>
|
|
||||||
</mat-form-field>
|
|
||||||
<mat-form-field
|
|
||||||
class="fuse-mat-no-subscript w-full sm:w-72 mt-4 sm:mt-0 sm:ml-4"
|
|
||||||
[floatLabel]="'always'">
|
|
||||||
<mat-icon
|
|
||||||
matPrefix
|
|
||||||
class="icon-size-5"
|
|
||||||
[svgIcon]="'heroicons_solid:search'"></mat-icon>
|
|
||||||
<input
|
|
||||||
(input)="filterByQuery(query.value)"
|
|
||||||
placeholder="Search by title or description"
|
|
||||||
matInput
|
|
||||||
#query>
|
|
||||||
</mat-form-field>
|
|
||||||
<mat-slide-toggle
|
|
||||||
class="mt-8 sm:mt-0 sm:ml-auto"
|
|
||||||
[color]="'primary'"
|
|
||||||
(change)="toggleCompleted($event)">
|
|
||||||
Hide completed
|
|
||||||
</mat-slide-toggle>
|
|
||||||
</div>
|
|
||||||
<!-- Courses -->
|
|
||||||
<ng-container *ngIf="this.filteredCourses.length; else noCourses">
|
|
||||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-8 mt-8 sm:mt-10">
|
|
||||||
<ng-container *ngFor="let course of filteredCourses; trackBy: trackByFn">
|
|
||||||
<!-- Course -->
|
|
||||||
<div class="flex flex-col h-96 shadow rounded-2xl overflow-hidden bg-card">
|
|
||||||
<div class="flex flex-col p-6">
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<!-- Course category -->
|
|
||||||
<ng-container *ngIf="(course.category | fuseFindByKey:'slug':categories) as category">
|
|
||||||
<div
|
|
||||||
class="py-0.5 px-3 rounded-full text-sm font-semibold"
|
|
||||||
[ngClass]="{'text-blue-800 bg-blue-100 dark:text-blue-50 dark:bg-blue-500': category.slug === 'web',
|
|
||||||
'text-green-800 bg-green-100 dark:text-green-50 dark:bg-green-500': category.slug === 'android',
|
|
||||||
'text-pink-800 bg-pink-100 dark:text-pink-50 dark:bg-pink-500': category.slug === 'cloud',
|
|
||||||
'text-amber-800 bg-amber-100 dark:text-amber-50 dark:bg-amber-500': category.slug === 'firebase'}">
|
|
||||||
{{category.title}}
|
|
||||||
</div>
|
|
||||||
</ng-container>
|
|
||||||
<!-- Completed at least once -->
|
|
||||||
<div class="flex items-center">
|
|
||||||
<ng-container *ngIf="course.progress.completed > 0">
|
|
||||||
<mat-icon
|
|
||||||
class="icon-size-5 text-green-600"
|
|
||||||
[svgIcon]="'heroicons_solid:badge-check'"
|
|
||||||
[matTooltip]="'You completed this course at least once'"></mat-icon>
|
|
||||||
</ng-container>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- Course title & description -->
|
|
||||||
<div class="mt-4 text-lg font-medium">{{course.title}}</div>
|
|
||||||
<div class="mt-0.5 line-clamp-2 text-secondary">{{course.description}}</div>
|
|
||||||
<div class="w-12 h-1 my-6 border-t-2"></div>
|
|
||||||
<!-- Course time -->
|
|
||||||
<div class="flex items-center leading-5 text-md text-secondary">
|
|
||||||
<mat-icon
|
|
||||||
class="icon-size-5 text-hint"
|
|
||||||
[svgIcon]="'heroicons_solid:clock'"></mat-icon>
|
|
||||||
<div class="ml-1.5">{{course.duration}} minutes</div>
|
|
||||||
</div>
|
|
||||||
<!-- Course completion -->
|
|
||||||
<div class="flex items-center mt-2 leading-5 text-md text-secondary">
|
|
||||||
<mat-icon
|
|
||||||
class="icon-size-5 text-hint"
|
|
||||||
[svgIcon]="'heroicons_solid:academic-cap'"></mat-icon>
|
|
||||||
<ng-container *ngIf="course.progress.completed === 0">
|
|
||||||
<div class="ml-1.5">Never completed</div>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container *ngIf="course.progress.completed > 0">
|
|
||||||
<div class="ml-1.5">
|
|
||||||
<span>Completed</span>
|
|
||||||
<span class="ml-1">
|
|
||||||
<!-- Once -->
|
|
||||||
<ng-container *ngIf="course.progress.completed === 1">once</ng-container>
|
|
||||||
<!-- Twice -->
|
|
||||||
<ng-container *ngIf="course.progress.completed === 2">twice</ng-container>
|
|
||||||
<!-- Others -->
|
|
||||||
<ng-container *ngIf="course.progress.completed > 2">{{course.progress.completed}}
|
|
||||||
{{course.progress.completed | i18nPlural: {
|
|
||||||
'=0' : 'time',
|
|
||||||
'=1' : 'time',
|
|
||||||
'other': 'times'
|
|
||||||
} }}
|
|
||||||
</ng-container>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</ng-container>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- Footer -->
|
|
||||||
<div class="flex flex-col w-full mt-auto">
|
|
||||||
<!-- Course progress -->
|
|
||||||
<div class="relative h-0.5">
|
|
||||||
<div
|
|
||||||
class="z-10 absolute inset-x-0 h-6 -mt-3"
|
|
||||||
[matTooltip]="course.progress.currentStep / course.totalSteps | percent"
|
|
||||||
[matTooltipPosition]="'above'"
|
|
||||||
[matTooltipClass]="'-mb-0.5'"></div>
|
|
||||||
<mat-progress-bar
|
|
||||||
class="h-0.5"
|
|
||||||
[value]="(100 * course.progress.currentStep) / course.totalSteps"></mat-progress-bar>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Course launch button -->
|
|
||||||
<div class="px-6 py-4 text-right bg-gray-50 dark:bg-transparent">
|
|
||||||
<button
|
|
||||||
mat-stroked-button
|
|
||||||
[routerLink]="[course.id]">
|
|
||||||
<span class="inline-flex items-center">
|
|
||||||
|
|
||||||
<!-- Not started -->
|
|
||||||
<ng-container *ngIf="course.progress.currentStep === 0">
|
|
||||||
<!-- Never completed -->
|
|
||||||
<ng-container *ngIf="course.progress.completed === 0">
|
|
||||||
<span>Start</span>
|
|
||||||
</ng-container>
|
|
||||||
<!-- Completed before -->
|
|
||||||
<ng-container *ngIf="course.progress.completed > 0">
|
|
||||||
<span>Start again</span>
|
|
||||||
</ng-container>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<!-- Started -->
|
|
||||||
<ng-container *ngIf="course.progress.currentStep > 0">
|
|
||||||
<span>Continue</span>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<mat-icon
|
|
||||||
class="ml-1.5 icon-size-5"
|
|
||||||
[svgIcon]="'heroicons_solid:arrow-sm-right'"></mat-icon>
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</ng-container>
|
|
||||||
</div>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<!-- No courses -->
|
|
||||||
<ng-template #noCourses>
|
|
||||||
<div class="flex flex-auto flex-col items-center justify-center bg-gray-100 dark:bg-transparent">
|
|
||||||
<mat-icon
|
|
||||||
class="icon-size-20"
|
|
||||||
[svgIcon]="'iconsmind:file_search'"></mat-icon>
|
|
||||||
<div class="mt-6 text-2xl font-semibold tracking-tight text-secondary">No courses found!</div>
|
|
||||||
</div>
|
|
||||||
</ng-template>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
@@ -1,156 +0,0 @@
|
|||||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';
|
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
|
||||||
import { MatSelectChange } from '@angular/material/select';
|
|
||||||
import { MatSlideToggleChange } from '@angular/material/slide-toggle';
|
|
||||||
import { BehaviorSubject, combineLatest, Subject, takeUntil } from 'rxjs';
|
|
||||||
import { AcademyService } from 'app/modules/admin/apps/academy/academy.service';
|
|
||||||
import { Category, Course } from 'app/modules/admin/apps/academy/academy.types';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector : 'academy-list',
|
|
||||||
templateUrl : './list.component.html',
|
|
||||||
encapsulation : ViewEncapsulation.None,
|
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush
|
|
||||||
})
|
|
||||||
export class AcademyListComponent implements OnInit, OnDestroy
|
|
||||||
{
|
|
||||||
categories: Category[];
|
|
||||||
courses: Course[];
|
|
||||||
filteredCourses: Course[];
|
|
||||||
filters: {
|
|
||||||
categorySlug$: BehaviorSubject<string>;
|
|
||||||
query$: BehaviorSubject<string>;
|
|
||||||
hideCompleted$: BehaviorSubject<boolean>;
|
|
||||||
} = {
|
|
||||||
categorySlug$ : new BehaviorSubject('all'),
|
|
||||||
query$ : new BehaviorSubject(''),
|
|
||||||
hideCompleted$: new BehaviorSubject(false)
|
|
||||||
};
|
|
||||||
|
|
||||||
private _unsubscribeAll: Subject<any> = new Subject<any>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor
|
|
||||||
*/
|
|
||||||
constructor(
|
|
||||||
private _activatedRoute: ActivatedRoute,
|
|
||||||
private _changeDetectorRef: ChangeDetectorRef,
|
|
||||||
private _router: Router,
|
|
||||||
private _academyService: AcademyService
|
|
||||||
)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------------------------------
|
|
||||||
// @ Lifecycle hooks
|
|
||||||
// -----------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* On init
|
|
||||||
*/
|
|
||||||
ngOnInit(): void
|
|
||||||
{
|
|
||||||
// Get the categories
|
|
||||||
this._academyService.categories$
|
|
||||||
.pipe(takeUntil(this._unsubscribeAll))
|
|
||||||
.subscribe((categories: Category[]) => {
|
|
||||||
this.categories = categories;
|
|
||||||
|
|
||||||
// Mark for check
|
|
||||||
this._changeDetectorRef.markForCheck();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Get the courses
|
|
||||||
this._academyService.courses$
|
|
||||||
.pipe(takeUntil(this._unsubscribeAll))
|
|
||||||
.subscribe((courses: Course[]) => {
|
|
||||||
this.courses = this.filteredCourses = courses;
|
|
||||||
|
|
||||||
// Mark for check
|
|
||||||
this._changeDetectorRef.markForCheck();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Filter the courses
|
|
||||||
combineLatest([this.filters.categorySlug$, this.filters.query$, this.filters.hideCompleted$])
|
|
||||||
.subscribe(([categorySlug, query, hideCompleted]) => {
|
|
||||||
|
|
||||||
// Reset the filtered courses
|
|
||||||
this.filteredCourses = this.courses;
|
|
||||||
|
|
||||||
// Filter by category
|
|
||||||
if ( categorySlug !== 'all' )
|
|
||||||
{
|
|
||||||
this.filteredCourses = this.filteredCourses.filter(course => course.category === categorySlug);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter by search query
|
|
||||||
if ( query !== '' )
|
|
||||||
{
|
|
||||||
this.filteredCourses = this.filteredCourses.filter(course => course.title.toLowerCase().includes(query.toLowerCase())
|
|
||||||
|| course.description.toLowerCase().includes(query.toLowerCase())
|
|
||||||
|| course.category.toLowerCase().includes(query.toLowerCase()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter by completed
|
|
||||||
if ( hideCompleted )
|
|
||||||
{
|
|
||||||
this.filteredCourses = this.filteredCourses.filter(course => course.progress.completed === 0);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* On destroy
|
|
||||||
*/
|
|
||||||
ngOnDestroy(): void
|
|
||||||
{
|
|
||||||
// Unsubscribe from all subscriptions
|
|
||||||
this._unsubscribeAll.next(null);
|
|
||||||
this._unsubscribeAll.complete();
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------------------------------
|
|
||||||
// @ Public methods
|
|
||||||
// -----------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Filter by search query
|
|
||||||
*
|
|
||||||
* @param query
|
|
||||||
*/
|
|
||||||
filterByQuery(query: string): void
|
|
||||||
{
|
|
||||||
this.filters.query$.next(query);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Filter by category
|
|
||||||
*
|
|
||||||
* @param change
|
|
||||||
*/
|
|
||||||
filterByCategory(change: MatSelectChange): void
|
|
||||||
{
|
|
||||||
this.filters.categorySlug$.next(change.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show/hide completed courses
|
|
||||||
*
|
|
||||||
* @param change
|
|
||||||
*/
|
|
||||||
toggleCompleted(change: MatSlideToggleChange): void
|
|
||||||
{
|
|
||||||
this.filters.hideCompleted$.next(change.checked);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Track by function for ngFor loops
|
|
||||||
*
|
|
||||||
* @param index
|
|
||||||
* @param item
|
|
||||||
*/
|
|
||||||
trackByFn(index: number, item: any): any
|
|
||||||
{
|
|
||||||
return item.id || index;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
<div class="absolute inset-0 flex flex-col min-w-0 overflow-hidden">
|
|
||||||
|
|
||||||
<!-- Main -->
|
|
||||||
<div class="flex flex-auto overflow-hidden">
|
|
||||||
<router-outlet></router-outlet>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
import { ChangeDetectionStrategy, Component, ViewEncapsulation } from '@angular/core';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector : 'chat',
|
|
||||||
templateUrl : './chat.component.html',
|
|
||||||
encapsulation : ViewEncapsulation.None,
|
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush
|
|
||||||
})
|
|
||||||
export class ChatComponent
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Constructor
|
|
||||||
*/
|
|
||||||
constructor()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
import { NgModule } from '@angular/core';
|
|
||||||
import { RouterModule } from '@angular/router';
|
|
||||||
import { MatButtonModule } from '@angular/material/button';
|
|
||||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
|
||||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
|
||||||
import { MatIconModule } from '@angular/material/icon';
|
|
||||||
import { MatInputModule } from '@angular/material/input';
|
|
||||||
import { MatMenuModule } from '@angular/material/menu';
|
|
||||||
import { MatSidenavModule } from '@angular/material/sidenav';
|
|
||||||
import { SharedModule } from 'app/shared/shared.module';
|
|
||||||
import { chatRoutes } from 'app/modules/admin/apps/chat/chat.routing';
|
|
||||||
import { ChatComponent } from 'app/modules/admin/apps/chat/chat.component';
|
|
||||||
import { ChatsComponent } from 'app/modules/admin/apps/chat/chats/chats.component';
|
|
||||||
import { ContactInfoComponent } from 'app/modules/admin/apps/chat/contact-info/contact-info.component';
|
|
||||||
import { ConversationComponent } from 'app/modules/admin/apps/chat/conversation/conversation.component';
|
|
||||||
import { NewChatComponent } from 'app/modules/admin/apps/chat/new-chat/new-chat.component';
|
|
||||||
import { ProfileComponent } from 'app/modules/admin/apps/chat/profile/profile.component';
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
declarations: [
|
|
||||||
ChatComponent,
|
|
||||||
ChatsComponent,
|
|
||||||
ContactInfoComponent,
|
|
||||||
ConversationComponent,
|
|
||||||
NewChatComponent,
|
|
||||||
ProfileComponent
|
|
||||||
],
|
|
||||||
imports : [
|
|
||||||
RouterModule.forChild(chatRoutes),
|
|
||||||
MatButtonModule,
|
|
||||||
MatCheckboxModule,
|
|
||||||
MatFormFieldModule,
|
|
||||||
MatIconModule,
|
|
||||||
MatInputModule,
|
|
||||||
MatMenuModule,
|
|
||||||
MatSidenavModule,
|
|
||||||
SharedModule
|
|
||||||
]
|
|
||||||
})
|
|
||||||
export class ChatModule
|
|
||||||
{
|
|
||||||
}
|
|
||||||
@@ -1,146 +0,0 @@
|
|||||||
import { Injectable } from '@angular/core';
|
|
||||||
import { ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot } from '@angular/router';
|
|
||||||
import { catchError, Observable, throwError } from 'rxjs';
|
|
||||||
import { ChatService } from 'app/modules/admin/apps/chat/chat.service';
|
|
||||||
import { Chat, Contact, Profile } from 'app/modules/admin/apps/chat/chat.types';
|
|
||||||
|
|
||||||
@Injectable({
|
|
||||||
providedIn: 'root'
|
|
||||||
})
|
|
||||||
export class ChatChatsResolver implements Resolve<any>
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Constructor
|
|
||||||
*/
|
|
||||||
constructor(
|
|
||||||
private _chatService: ChatService,
|
|
||||||
private _router: Router
|
|
||||||
)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------------------------------
|
|
||||||
// @ Public methods
|
|
||||||
// -----------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolver
|
|
||||||
*
|
|
||||||
* @param route
|
|
||||||
* @param state
|
|
||||||
*/
|
|
||||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<Chat[]> | any
|
|
||||||
{
|
|
||||||
return this._chatService.getChats();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Injectable({
|
|
||||||
providedIn: 'root'
|
|
||||||
})
|
|
||||||
export class ChatChatResolver implements Resolve<any>
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Constructor
|
|
||||||
*/
|
|
||||||
constructor(
|
|
||||||
private _chatService: ChatService,
|
|
||||||
private _router: Router
|
|
||||||
)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------------------------------
|
|
||||||
// @ Public methods
|
|
||||||
// -----------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolver
|
|
||||||
*
|
|
||||||
* @param route
|
|
||||||
* @param state
|
|
||||||
*/
|
|
||||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<Chat>
|
|
||||||
{
|
|
||||||
return this._chatService.getChatById(route.paramMap.get('id'))
|
|
||||||
.pipe(
|
|
||||||
// Error here means the requested chat is not available
|
|
||||||
catchError((error) => {
|
|
||||||
|
|
||||||
// Log the error
|
|
||||||
console.error(error);
|
|
||||||
|
|
||||||
// Get the parent url
|
|
||||||
const parentUrl = state.url.split('/').slice(0, -1).join('/');
|
|
||||||
|
|
||||||
// Navigate to there
|
|
||||||
this._router.navigateByUrl(parentUrl);
|
|
||||||
|
|
||||||
// Throw an error
|
|
||||||
return throwError(error);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Injectable({
|
|
||||||
providedIn: 'root'
|
|
||||||
})
|
|
||||||
export class ChatContactsResolver implements Resolve<any>
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Constructor
|
|
||||||
*/
|
|
||||||
constructor(
|
|
||||||
private _chatService: ChatService,
|
|
||||||
private _router: Router
|
|
||||||
)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------------------------------
|
|
||||||
// @ Public methods
|
|
||||||
// -----------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolver
|
|
||||||
*
|
|
||||||
* @param route
|
|
||||||
* @param state
|
|
||||||
*/
|
|
||||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<Contact[]> | any
|
|
||||||
{
|
|
||||||
return this._chatService.getContacts();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Injectable({
|
|
||||||
providedIn: 'root'
|
|
||||||
})
|
|
||||||
export class ChatProfileResolver implements Resolve<any>
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Constructor
|
|
||||||
*/
|
|
||||||
constructor(
|
|
||||||
private _chatService: ChatService,
|
|
||||||
private _router: Router
|
|
||||||
)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------------------------------
|
|
||||||
// @ Public methods
|
|
||||||
// -----------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolver
|
|
||||||
*
|
|
||||||
* @param route
|
|
||||||
* @param state
|
|
||||||
*/
|
|
||||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<Profile> | any
|
|
||||||
{
|
|
||||||
return this._chatService.getProfile();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
import { Route } from '@angular/router';
|
|
||||||
import { ChatChatResolver, ChatChatsResolver, ChatContactsResolver, ChatProfileResolver } from 'app/modules/admin/apps/chat/chat.resolvers';
|
|
||||||
import { ChatComponent } from 'app/modules/admin/apps/chat/chat.component';
|
|
||||||
import { ChatsComponent } from 'app/modules/admin/apps/chat/chats/chats.component';
|
|
||||||
import { ConversationComponent } from 'app/modules/admin/apps/chat/conversation/conversation.component';
|
|
||||||
|
|
||||||
export const chatRoutes: Route[] = [
|
|
||||||
{
|
|
||||||
path : '',
|
|
||||||
component: ChatComponent,
|
|
||||||
resolve : {
|
|
||||||
chats : ChatChatsResolver,
|
|
||||||
contacts: ChatContactsResolver,
|
|
||||||
profile : ChatProfileResolver
|
|
||||||
},
|
|
||||||
children : [
|
|
||||||
{
|
|
||||||
path : '',
|
|
||||||
component: ChatsComponent,
|
|
||||||
children : [
|
|
||||||
{
|
|
||||||
path : '',
|
|
||||||
component: ConversationComponent,
|
|
||||||
children : [
|
|
||||||
{
|
|
||||||
path : ':id',
|
|
||||||
resolve: {
|
|
||||||
conversation: ChatChatResolver
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
];
|
|
||||||
@@ -1,201 +0,0 @@
|
|||||||
import { Injectable } from '@angular/core';
|
|
||||||
import { HttpClient } from '@angular/common/http';
|
|
||||||
import { BehaviorSubject, filter, map, Observable, of, switchMap, take, tap, throwError } from 'rxjs';
|
|
||||||
import { Chat, Contact, Profile } from 'app/modules/admin/apps/chat/chat.types';
|
|
||||||
|
|
||||||
@Injectable({
|
|
||||||
providedIn: 'root'
|
|
||||||
})
|
|
||||||
export class ChatService
|
|
||||||
{
|
|
||||||
private _chat: BehaviorSubject<Chat> = new BehaviorSubject(null);
|
|
||||||
private _chats: BehaviorSubject<Chat[]> = new BehaviorSubject(null);
|
|
||||||
private _contact: BehaviorSubject<Contact> = new BehaviorSubject(null);
|
|
||||||
private _contacts: BehaviorSubject<Contact[]> = new BehaviorSubject(null);
|
|
||||||
private _profile: BehaviorSubject<Profile> = new BehaviorSubject(null);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor
|
|
||||||
*/
|
|
||||||
constructor(private _httpClient: HttpClient)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------------------------------
|
|
||||||
// @ Accessors
|
|
||||||
// -----------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Getter for chat
|
|
||||||
*/
|
|
||||||
get chat$(): Observable<Chat>
|
|
||||||
{
|
|
||||||
return this._chat.asObservable();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Getter for chats
|
|
||||||
*/
|
|
||||||
get chats$(): Observable<Chat[]>
|
|
||||||
{
|
|
||||||
return this._chats.asObservable();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Getter for contact
|
|
||||||
*/
|
|
||||||
get contact$(): Observable<Contact>
|
|
||||||
{
|
|
||||||
return this._contact.asObservable();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Getter for contacts
|
|
||||||
*/
|
|
||||||
get contacts$(): Observable<Contact[]>
|
|
||||||
{
|
|
||||||
return this._contacts.asObservable();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Getter for profile
|
|
||||||
*/
|
|
||||||
get profile$(): Observable<Profile>
|
|
||||||
{
|
|
||||||
return this._profile.asObservable();
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------------------------------
|
|
||||||
// @ Public methods
|
|
||||||
// -----------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get chats
|
|
||||||
*/
|
|
||||||
getChats(): Observable<any>
|
|
||||||
{
|
|
||||||
return this._httpClient.get<Chat[]>('api/apps/chat/chats').pipe(
|
|
||||||
tap((response: Chat[]) => {
|
|
||||||
this._chats.next(response);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get contact
|
|
||||||
*
|
|
||||||
* @param id
|
|
||||||
*/
|
|
||||||
getContact(id: string): Observable<any>
|
|
||||||
{
|
|
||||||
return this._httpClient.get<Contact>('api/apps/chat/contacts', {params: {id}}).pipe(
|
|
||||||
tap((response: Contact) => {
|
|
||||||
this._contact.next(response);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get contacts
|
|
||||||
*/
|
|
||||||
getContacts(): Observable<any>
|
|
||||||
{
|
|
||||||
return this._httpClient.get<Contact[]>('api/apps/chat/contacts').pipe(
|
|
||||||
tap((response: Contact[]) => {
|
|
||||||
this._contacts.next(response);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get profile
|
|
||||||
*/
|
|
||||||
getProfile(): Observable<any>
|
|
||||||
{
|
|
||||||
return this._httpClient.get<Profile>('api/apps/chat/profile').pipe(
|
|
||||||
tap((response: Profile) => {
|
|
||||||
this._profile.next(response);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get chat
|
|
||||||
*
|
|
||||||
* @param id
|
|
||||||
*/
|
|
||||||
getChatById(id: string): Observable<any>
|
|
||||||
{
|
|
||||||
return this._httpClient.get<Chat>('api/apps/chat/chat', {params: {id}}).pipe(
|
|
||||||
map((chat) => {
|
|
||||||
|
|
||||||
// Update the chat
|
|
||||||
this._chat.next(chat);
|
|
||||||
|
|
||||||
// Return the chat
|
|
||||||
return chat;
|
|
||||||
}),
|
|
||||||
switchMap((chat) => {
|
|
||||||
|
|
||||||
if ( !chat )
|
|
||||||
{
|
|
||||||
return throwError('Could not found chat with id of ' + id + '!');
|
|
||||||
}
|
|
||||||
|
|
||||||
return of(chat);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update chat
|
|
||||||
*
|
|
||||||
* @param id
|
|
||||||
* @param chat
|
|
||||||
*/
|
|
||||||
updateChat(id: string, chat: Chat): Observable<Chat>
|
|
||||||
{
|
|
||||||
return this.chats$.pipe(
|
|
||||||
take(1),
|
|
||||||
switchMap(chats => this._httpClient.patch<Chat>('api/apps/chat/chat', {
|
|
||||||
id,
|
|
||||||
chat
|
|
||||||
}).pipe(
|
|
||||||
map((updatedChat) => {
|
|
||||||
|
|
||||||
// Find the index of the updated chat
|
|
||||||
const index = chats.findIndex(item => item.id === id);
|
|
||||||
|
|
||||||
// Update the chat
|
|
||||||
chats[index] = updatedChat;
|
|
||||||
|
|
||||||
// Update the chats
|
|
||||||
this._chats.next(chats);
|
|
||||||
|
|
||||||
// Return the updated contact
|
|
||||||
return updatedChat;
|
|
||||||
}),
|
|
||||||
switchMap(updatedChat => this.chat$.pipe(
|
|
||||||
take(1),
|
|
||||||
filter(item => item && item.id === id),
|
|
||||||
tap(() => {
|
|
||||||
|
|
||||||
// Update the chat if it's selected
|
|
||||||
this._chat.next(updatedChat);
|
|
||||||
|
|
||||||
// Return the updated chat
|
|
||||||
return updatedChat;
|
|
||||||
})
|
|
||||||
))
|
|
||||||
))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reset the selected chat
|
|
||||||
*/
|
|
||||||
resetChat(): void
|
|
||||||
{
|
|
||||||
this._chat.next(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
export interface Profile
|
|
||||||
{
|
|
||||||
id?: string;
|
|
||||||
name?: string;
|
|
||||||
email?: string;
|
|
||||||
avatar?: string;
|
|
||||||
about?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Contact
|
|
||||||
{
|
|
||||||
id?: string;
|
|
||||||
avatar?: string;
|
|
||||||
name?: string;
|
|
||||||
about?: string;
|
|
||||||
details?: {
|
|
||||||
emails?: {
|
|
||||||
email?: string;
|
|
||||||
label?: string;
|
|
||||||
}[];
|
|
||||||
phoneNumbers?: {
|
|
||||||
country?: string;
|
|
||||||
phoneNumber?: string;
|
|
||||||
label?: string;
|
|
||||||
}[];
|
|
||||||
title?: string;
|
|
||||||
company?: string;
|
|
||||||
birthday?: string;
|
|
||||||
address?: string;
|
|
||||||
};
|
|
||||||
attachments?: {
|
|
||||||
media?: any[];
|
|
||||||
docs?: any[];
|
|
||||||
links?: any[];
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Chat
|
|
||||||
{
|
|
||||||
id?: string;
|
|
||||||
contactId?: string;
|
|
||||||
contact?: Contact;
|
|
||||||
unreadCount?: number;
|
|
||||||
muted?: boolean;
|
|
||||||
lastMessage?: string;
|
|
||||||
lastMessageAt?: string;
|
|
||||||
messages?: {
|
|
||||||
id?: string;
|
|
||||||
chatId?: string;
|
|
||||||
contactId?: string;
|
|
||||||
isMine?: boolean;
|
|
||||||
value?: string;
|
|
||||||
createdAt?: string;
|
|
||||||
}[];
|
|
||||||
}
|
|
||||||
@@ -1,191 +0,0 @@
|
|||||||
<div class="relative flex flex-auto w-full bg-card dark:bg-transparent">
|
|
||||||
|
|
||||||
<mat-drawer-container
|
|
||||||
class="flex-auto h-full"
|
|
||||||
[hasBackdrop]="false">
|
|
||||||
|
|
||||||
<!-- Drawer -->
|
|
||||||
<mat-drawer
|
|
||||||
class="w-full sm:w-100 lg:border-r lg:shadow-none dark:bg-gray-900"
|
|
||||||
[autoFocus]="false"
|
|
||||||
[(opened)]="drawerOpened"
|
|
||||||
#drawer>
|
|
||||||
|
|
||||||
<!-- New chat -->
|
|
||||||
<ng-container *ngIf="drawerComponent === 'new-chat'">
|
|
||||||
<chat-new-chat [drawer]="drawer"></chat-new-chat>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<!-- Profile -->
|
|
||||||
<ng-container *ngIf="drawerComponent === 'profile'">
|
|
||||||
<chat-profile [drawer]="drawer"></chat-profile>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
</mat-drawer>
|
|
||||||
|
|
||||||
<!-- Drawer content -->
|
|
||||||
<mat-drawer-content class="flex overflow-hidden">
|
|
||||||
|
|
||||||
<!-- Chats list -->
|
|
||||||
<ng-container *ngIf="chats && chats.length > 0; else noChats">
|
|
||||||
<div class="relative flex flex-auto flex-col w-full min-w-0 lg:min-w-100 lg:max-w-100 bg-card dark:bg-transparent">
|
|
||||||
|
|
||||||
<!-- Header -->
|
|
||||||
<div class="flex flex-col flex-0 py-4 px-8 border-b bg-gray-50 dark:bg-transparent">
|
|
||||||
<div class="flex items-center">
|
|
||||||
<div
|
|
||||||
class="flex items-center mr-1 cursor-pointer"
|
|
||||||
(click)="openProfile()">
|
|
||||||
<div class="w-10 h-10">
|
|
||||||
<ng-container *ngIf="profile.avatar">
|
|
||||||
<img
|
|
||||||
class="object-cover w-full h-full rounded-full object-cover"
|
|
||||||
[src]="profile.avatar"
|
|
||||||
alt="Profile avatar"/>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container *ngIf="!profile.avatar">
|
|
||||||
<div class="flex items-center justify-center w-full h-full rounded-full text-lg uppercase bg-gray-200 text-gray-600 dark:bg-gray-700 dark:text-gray-200">
|
|
||||||
{{profile.name.charAt(0)}}
|
|
||||||
</div>
|
|
||||||
</ng-container>
|
|
||||||
</div>
|
|
||||||
<div class="ml-4 font-medium truncate">{{profile.name}}</div>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
class="ml-auto"
|
|
||||||
mat-icon-button
|
|
||||||
(click)="openNewChat()">
|
|
||||||
<mat-icon [svgIcon]="'heroicons_outline:plus-circle'"></mat-icon>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="ml-1 -mr-4"
|
|
||||||
mat-icon-button
|
|
||||||
[matMenuTriggerFor]="chatsHeaderMenu">
|
|
||||||
<mat-icon [svgIcon]="'heroicons_outline:dots-vertical'"></mat-icon>
|
|
||||||
<mat-menu #chatsHeaderMenu>
|
|
||||||
<button mat-menu-item>
|
|
||||||
<mat-icon [svgIcon]="'heroicons_outline:user-group'"></mat-icon>
|
|
||||||
New group
|
|
||||||
</button>
|
|
||||||
<button mat-menu-item>
|
|
||||||
<mat-icon [svgIcon]="'heroicons_outline:chat-alt-2'"></mat-icon>
|
|
||||||
Create a room
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
mat-menu-item
|
|
||||||
(click)="openProfile()">
|
|
||||||
<mat-icon [svgIcon]="'heroicons_outline:user-circle'"></mat-icon>
|
|
||||||
Profile
|
|
||||||
</button>
|
|
||||||
<button mat-menu-item>
|
|
||||||
<mat-icon [svgIcon]="'heroicons_outline:archive'"></mat-icon>
|
|
||||||
Archived
|
|
||||||
</button>
|
|
||||||
<button mat-menu-item>
|
|
||||||
<mat-icon [svgIcon]="'heroicons_outline:star'"></mat-icon>
|
|
||||||
Starred
|
|
||||||
</button>
|
|
||||||
<button mat-menu-item>
|
|
||||||
<mat-icon [svgIcon]="'heroicons_outline:cog'"></mat-icon>
|
|
||||||
Settings
|
|
||||||
</button>
|
|
||||||
</mat-menu>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<!-- Search -->
|
|
||||||
<div class="mt-4">
|
|
||||||
<mat-form-field
|
|
||||||
class="fuse-mat-no-subscript fuse-mat-rounded fuse-mat-dense w-full"
|
|
||||||
[floatLabel]="'always'">
|
|
||||||
<mat-icon
|
|
||||||
matPrefix
|
|
||||||
class="icon-size-5"
|
|
||||||
[svgIcon]="'heroicons_solid:search'"></mat-icon>
|
|
||||||
<input
|
|
||||||
matInput
|
|
||||||
[autocomplete]="'off'"
|
|
||||||
[placeholder]="'Search or start new chat'"
|
|
||||||
(input)="filterChats(searchField.value)"
|
|
||||||
#searchField>
|
|
||||||
</mat-form-field>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Chats -->
|
|
||||||
<div class="flex-auto overflow-y-auto">
|
|
||||||
<ng-container *ngIf="filteredChats.length > 0; else noChats">
|
|
||||||
<ng-container *ngFor="let chat of filteredChats; trackBy: trackByFn">
|
|
||||||
<div
|
|
||||||
class="z-20 flex items-center py-5 px-8 cursor-pointer border-b"
|
|
||||||
[ngClass]="{'hover:bg-gray-100 dark:hover:bg-hover': !selectedChat || selectedChat.id !== chat.id,
|
|
||||||
'bg-primary-50 dark:bg-hover': selectedChat && selectedChat.id === chat.id}"
|
|
||||||
[routerLink]="[chat.id]">
|
|
||||||
<div class="relative flex flex-0 items-center justify-center w-10 h-10">
|
|
||||||
<ng-container *ngIf="chat.unreadCount > 0">
|
|
||||||
<div
|
|
||||||
class="absolute bottom-0 right-0 flex-0 w-2 h-2 -ml-0.5 rounded-full ring-2 ring-bg-card dark:ring-gray-900 bg-primary dark:bg-primary-500 text-on-primary"
|
|
||||||
[class.ring-primary-50]="selectedChat && selectedChat.id === chat.id"></div>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container *ngIf="chat.contact.avatar">
|
|
||||||
<img
|
|
||||||
class="w-full h-full rounded-full object-cover"
|
|
||||||
[src]="chat.contact.avatar"
|
|
||||||
alt="Contact avatar"/>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container *ngIf="!chat.contact.avatar">
|
|
||||||
<div class="flex items-center justify-center w-full h-full rounded-full text-lg uppercase bg-gray-200 text-gray-600 dark:bg-gray-700 dark:text-gray-200">
|
|
||||||
{{chat.contact.name.charAt(0)}}
|
|
||||||
</div>
|
|
||||||
</ng-container>
|
|
||||||
</div>
|
|
||||||
<div class="min-w-0 ml-4">
|
|
||||||
<div class="font-medium leading-5 truncate">{{chat.contact.name}}</div>
|
|
||||||
<div
|
|
||||||
class="leading-5 truncate text-secondary"
|
|
||||||
[class.text-primary]="chat.unreadCount > 0"
|
|
||||||
[class.dark:text-primary-500]="chat.unreadCount > 0">
|
|
||||||
{{chat.lastMessage}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col items-end self-start ml-auto pl-2">
|
|
||||||
<div class="text-sm leading-5 text-secondary">{{chat.lastMessageAt}}</div>
|
|
||||||
<ng-container *ngIf="chat.muted">
|
|
||||||
<mat-icon
|
|
||||||
class="icon-size-5 text-hint"
|
|
||||||
[svgIcon]="'heroicons_solid:volume-off'"></mat-icon>
|
|
||||||
</ng-container>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</ng-container>
|
|
||||||
</ng-container>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<!-- No chats template -->
|
|
||||||
<ng-template #noChats>
|
|
||||||
<div class="flex flex-auto flex-col items-center justify-center h-full">
|
|
||||||
<mat-icon
|
|
||||||
class="icon-size-24"
|
|
||||||
[svgIcon]="'iconsmind:speach_bubble'"></mat-icon>
|
|
||||||
<div class="mt-4 text-2xl font-semibold tracking-tight text-secondary">No chats</div>
|
|
||||||
</div>
|
|
||||||
</ng-template>
|
|
||||||
|
|
||||||
<!-- Conversation -->
|
|
||||||
<ng-container *ngIf="chats && chats.length > 0">
|
|
||||||
<div
|
|
||||||
class="flex-auto border-l"
|
|
||||||
[ngClass]="{'z-20 absolute inset-0 lg:static lg:inset-auto flex': selectedChat && selectedChat.id,
|
|
||||||
'hidden lg:flex': !selectedChat || !selectedChat.id}">
|
|
||||||
<router-outlet></router-outlet>
|
|
||||||
</div>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
</mat-drawer-content>
|
|
||||||
|
|
||||||
</mat-drawer-container>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
@@ -1,137 +0,0 @@
|
|||||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';
|
|
||||||
import { Subject, takeUntil } from 'rxjs';
|
|
||||||
import { Chat, Profile } from 'app/modules/admin/apps/chat/chat.types';
|
|
||||||
import { ChatService } from 'app/modules/admin/apps/chat/chat.service';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector : 'chat-chats',
|
|
||||||
templateUrl : './chats.component.html',
|
|
||||||
encapsulation : ViewEncapsulation.None,
|
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush
|
|
||||||
})
|
|
||||||
export class ChatsComponent implements OnInit, OnDestroy
|
|
||||||
{
|
|
||||||
chats: Chat[];
|
|
||||||
drawerComponent: 'profile' | 'new-chat';
|
|
||||||
drawerOpened: boolean = false;
|
|
||||||
filteredChats: Chat[];
|
|
||||||
profile: Profile;
|
|
||||||
selectedChat: Chat;
|
|
||||||
private _unsubscribeAll: Subject<any> = new Subject<any>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor
|
|
||||||
*/
|
|
||||||
constructor(
|
|
||||||
private _chatService: ChatService,
|
|
||||||
private _changeDetectorRef: ChangeDetectorRef
|
|
||||||
)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------------------------------
|
|
||||||
// @ Lifecycle hooks
|
|
||||||
// -----------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* On init
|
|
||||||
*/
|
|
||||||
ngOnInit(): void
|
|
||||||
{
|
|
||||||
// Chats
|
|
||||||
this._chatService.chats$
|
|
||||||
.pipe(takeUntil(this._unsubscribeAll))
|
|
||||||
.subscribe((chats: Chat[]) => {
|
|
||||||
this.chats = this.filteredChats = chats;
|
|
||||||
|
|
||||||
// Mark for check
|
|
||||||
this._changeDetectorRef.markForCheck();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Profile
|
|
||||||
this._chatService.profile$
|
|
||||||
.pipe(takeUntil(this._unsubscribeAll))
|
|
||||||
.subscribe((profile: Profile) => {
|
|
||||||
this.profile = profile;
|
|
||||||
|
|
||||||
// Mark for check
|
|
||||||
this._changeDetectorRef.markForCheck();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Selected chat
|
|
||||||
this._chatService.chat$
|
|
||||||
.pipe(takeUntil(this._unsubscribeAll))
|
|
||||||
.subscribe((chat: Chat) => {
|
|
||||||
this.selectedChat = chat;
|
|
||||||
|
|
||||||
// Mark for check
|
|
||||||
this._changeDetectorRef.markForCheck();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* On destroy
|
|
||||||
*/
|
|
||||||
ngOnDestroy(): void
|
|
||||||
{
|
|
||||||
// Unsubscribe from all subscriptions
|
|
||||||
this._unsubscribeAll.next(null);
|
|
||||||
this._unsubscribeAll.complete();
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------------------------------
|
|
||||||
// @ Public methods
|
|
||||||
// -----------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Filter the chats
|
|
||||||
*
|
|
||||||
* @param query
|
|
||||||
*/
|
|
||||||
filterChats(query: string): void
|
|
||||||
{
|
|
||||||
// Reset the filter
|
|
||||||
if ( !query )
|
|
||||||
{
|
|
||||||
this.filteredChats = this.chats;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.filteredChats = this.chats.filter(chat => chat.contact.name.toLowerCase().includes(query.toLowerCase()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Open the new chat sidebar
|
|
||||||
*/
|
|
||||||
openNewChat(): void
|
|
||||||
{
|
|
||||||
this.drawerComponent = 'new-chat';
|
|
||||||
this.drawerOpened = true;
|
|
||||||
|
|
||||||
// Mark for check
|
|
||||||
this._changeDetectorRef.markForCheck();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Open the profile sidebar
|
|
||||||
*/
|
|
||||||
openProfile(): void
|
|
||||||
{
|
|
||||||
this.drawerComponent = 'profile';
|
|
||||||
this.drawerOpened = true;
|
|
||||||
|
|
||||||
// Mark for check
|
|
||||||
this._changeDetectorRef.markForCheck();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Track by function for ngFor loops
|
|
||||||
*
|
|
||||||
* @param index
|
|
||||||
* @param item
|
|
||||||
*/
|
|
||||||
trackByFn(index: number, item: any): any
|
|
||||||
{
|
|
||||||
return item.id || index;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
<div class="flex flex-col flex-auto h-full bg-card dark:bg-default">
|
|
||||||
|
|
||||||
<!-- Header -->
|
|
||||||
<div class="flex flex-0 items-center h-18 px-4 border-b bg-gray-50 dark:bg-transparent">
|
|
||||||
<button
|
|
||||||
mat-icon-button
|
|
||||||
(click)="drawer.close()">
|
|
||||||
<mat-icon [svgIcon]="'heroicons_outline:x'"></mat-icon>
|
|
||||||
</button>
|
|
||||||
<div class="ml-2 text-lg font-medium">Contact info</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="overflow-y-auto">
|
|
||||||
<!-- Contact avatar & info -->
|
|
||||||
<div class="flex flex-col items-center mt-8">
|
|
||||||
<div class="w-40 h-40 rounded-full">
|
|
||||||
<ng-container *ngIf="chat.contact.avatar">
|
|
||||||
<img
|
|
||||||
class="w-full h-full rounded-full object-cover"
|
|
||||||
[src]="chat.contact.avatar"
|
|
||||||
[alt]="'Contact avatar'">
|
|
||||||
</ng-container>
|
|
||||||
<ng-container *ngIf="!chat.contact.avatar">
|
|
||||||
<div class="flex items-center justify-center w-full h-full rounded-full text-8xl font-semibold uppercase bg-gray-200 text-gray-600 dark:bg-gray-700 dark:text-gray-200">
|
|
||||||
{{chat.contact.name.charAt(0)}}
|
|
||||||
</div>
|
|
||||||
</ng-container>
|
|
||||||
</div>
|
|
||||||
<div class="mt-4 text-lg font-medium">{{chat.contact.name}}</div>
|
|
||||||
<div class="mt-0.5 text-md text-secondary">{{chat.contact.about}}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="py-10 px-7">
|
|
||||||
<!-- Media -->
|
|
||||||
<div class="text-lg font-medium">Media</div>
|
|
||||||
<div class="grid grid-cols-4 gap-1 mt-4">
|
|
||||||
<ng-container *ngFor="let media of chat.contact.attachments.media">
|
|
||||||
<img
|
|
||||||
class="h-20 rounded object-cover"
|
|
||||||
[src]="media"/>
|
|
||||||
</ng-container>
|
|
||||||
</div>
|
|
||||||
<!-- Details -->
|
|
||||||
<div class="mt-10 space-y-4">
|
|
||||||
<div class="text-lg font-medium mb-3">Details</div>
|
|
||||||
<ng-container *ngIf="chat.contact.details.emails.length">
|
|
||||||
<div>
|
|
||||||
<div class="font-medium text-secondary">Email</div>
|
|
||||||
<div class="">{{chat.contact.details.emails[0].email}}</div>
|
|
||||||
</div>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container *ngIf="chat.contact.details.phoneNumbers.length">
|
|
||||||
<div>
|
|
||||||
<div class="font-medium text-secondary">Phone number</div>
|
|
||||||
<div class="">{{chat.contact.details.phoneNumbers[0].phoneNumber}}</div>
|
|
||||||
</div>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container *ngIf="chat.contact.details.title">
|
|
||||||
<div>
|
|
||||||
<div class="font-medium text-secondary">Title</div>
|
|
||||||
<div class="">{{chat.contact.details.title}}</div>
|
|
||||||
</div>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container *ngIf="chat.contact.details.company">
|
|
||||||
<div>
|
|
||||||
<div class="font-medium text-secondary">Company</div>
|
|
||||||
<div class="">{{chat.contact.details.company}}</div>
|
|
||||||
</div>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container *ngIf="chat.contact.details.birthday">
|
|
||||||
<div>
|
|
||||||
<div class="font-medium text-secondary">Birthday</div>
|
|
||||||
<div class="">{{chat.contact.details.birthday}}</div>
|
|
||||||
</div>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container *ngIf="chat.contact.details.address">
|
|
||||||
<div>
|
|
||||||
<div class="font-medium text-secondary">Address</div>
|
|
||||||
<div class="">{{chat.contact.details.address}}</div>
|
|
||||||
</div>
|
|
||||||
</ng-container>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
import { ChangeDetectionStrategy, Component, Input, ViewEncapsulation } from '@angular/core';
|
|
||||||
import { MatDrawer } from '@angular/material/sidenav';
|
|
||||||
import { Chat } from 'app/modules/admin/apps/chat/chat.types';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector : 'chat-contact-info',
|
|
||||||
templateUrl : './contact-info.component.html',
|
|
||||||
encapsulation : ViewEncapsulation.None,
|
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush
|
|
||||||
})
|
|
||||||
export class ContactInfoComponent
|
|
||||||
{
|
|
||||||
@Input() chat: Chat;
|
|
||||||
@Input() drawer: MatDrawer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor
|
|
||||||
*/
|
|
||||||
constructor()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,215 +0,0 @@
|
|||||||
<div class="flex flex-col flex-auto overflow-y-auto lg:overflow-hidden bg-card dark:bg-default">
|
|
||||||
|
|
||||||
<ng-container *ngIf="chat; else selectChatOrStartNew">
|
|
||||||
|
|
||||||
<mat-drawer-container
|
|
||||||
class="flex-auto h-full"
|
|
||||||
[hasBackdrop]="false">
|
|
||||||
|
|
||||||
<!-- Drawer -->
|
|
||||||
<mat-drawer
|
|
||||||
class="w-full sm:w-100 lg:border-l lg:shadow-none dark:bg-gray-900"
|
|
||||||
[autoFocus]="false"
|
|
||||||
[mode]="drawerMode"
|
|
||||||
[position]="'end'"
|
|
||||||
[(opened)]="drawerOpened"
|
|
||||||
#drawer>
|
|
||||||
|
|
||||||
<!-- Contact info -->
|
|
||||||
<chat-contact-info
|
|
||||||
[drawer]="drawer"
|
|
||||||
[chat]="chat"></chat-contact-info>
|
|
||||||
</mat-drawer>
|
|
||||||
|
|
||||||
<!-- Drawer content -->
|
|
||||||
<mat-drawer-content class="flex flex-col overflow-hidden">
|
|
||||||
|
|
||||||
<!-- Header -->
|
|
||||||
<div class="flex flex-0 items-center h-18 px-4 md:px-6 border-b bg-gray-50 dark:bg-transparent">
|
|
||||||
|
|
||||||
<!-- Back button -->
|
|
||||||
<a
|
|
||||||
class="lg:hidden md:-ml-2"
|
|
||||||
mat-icon-button
|
|
||||||
[routerLink]="['./']"
|
|
||||||
(click)="resetChat()">
|
|
||||||
<mat-icon [svgIcon]="'heroicons_outline:arrow-narrow-left'"></mat-icon>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<!-- Contact info -->
|
|
||||||
<div
|
|
||||||
class="flex items-center ml-2 lg:ml-0 mr-2 cursor-pointer"
|
|
||||||
(click)="openContactInfo()">
|
|
||||||
<div class="relative flex flex-0 items-center justify-center w-10 h-10">
|
|
||||||
<ng-container *ngIf="chat.contact.avatar">
|
|
||||||
<img
|
|
||||||
class="w-full h-full rounded-full object-cover"
|
|
||||||
[src]="chat.contact.avatar"
|
|
||||||
alt="Contact avatar"/>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container *ngIf="!chat.contact.avatar">
|
|
||||||
<div class="flex items-center justify-center w-full h-full rounded-full text-lg uppercase bg-gray-200 text-gray-600 dark:bg-gray-700 dark:text-gray-200">
|
|
||||||
{{chat.contact.name.charAt(0)}}
|
|
||||||
</div>
|
|
||||||
</ng-container>
|
|
||||||
</div>
|
|
||||||
<div class="ml-4 text-lg font-medium leading-5 truncate">{{chat.contact.name}}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button
|
|
||||||
class="ml-auto"
|
|
||||||
mat-icon-button
|
|
||||||
[matMenuTriggerFor]="conversationHeaderMenu">
|
|
||||||
<mat-icon [svgIcon]="'heroicons_outline:dots-vertical'"></mat-icon>
|
|
||||||
<mat-menu #conversationHeaderMenu>
|
|
||||||
<button
|
|
||||||
mat-menu-item
|
|
||||||
(click)="openContactInfo()">
|
|
||||||
<mat-icon [svgIcon]="'heroicons_outline:user-circle'"></mat-icon>
|
|
||||||
Contact info
|
|
||||||
</button>
|
|
||||||
<button mat-menu-item>
|
|
||||||
<mat-icon [svgIcon]="'heroicons_outline:check-circle'"></mat-icon>
|
|
||||||
Select messages
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
mat-menu-item
|
|
||||||
(click)="toggleMuteNotifications()">
|
|
||||||
<ng-container *ngIf="!chat.muted">
|
|
||||||
<mat-icon [svgIcon]="'heroicons_outline:volume-off'"></mat-icon>
|
|
||||||
Mute notifications
|
|
||||||
</ng-container>
|
|
||||||
<ng-container *ngIf="chat.muted">
|
|
||||||
<mat-icon [svgIcon]="'heroicons_outline:volume-up'"></mat-icon>
|
|
||||||
Unmute notifications
|
|
||||||
</ng-container>
|
|
||||||
</button>
|
|
||||||
<button mat-menu-item>
|
|
||||||
<mat-icon [svgIcon]="'heroicons_outline:backspace'"></mat-icon>
|
|
||||||
Clear messages
|
|
||||||
</button>
|
|
||||||
<button mat-menu-item>
|
|
||||||
<mat-icon [svgIcon]="'heroicons_outline:trash'"></mat-icon>
|
|
||||||
Delete chat
|
|
||||||
</button>
|
|
||||||
</mat-menu>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Conversation -->
|
|
||||||
<div class="flex overflow-y-auto flex-col-reverse">
|
|
||||||
<div class="flex flex-col flex-auto flex-shrink p-6 bg-card dark:bg-transparent">
|
|
||||||
<ng-container *ngFor="let message of chat.messages; let i = index; let first = first; let last = last; trackBy: trackByFn">
|
|
||||||
<!-- Start of the day -->
|
|
||||||
<ng-container *ngIf="first || (chat.messages[i - 1].createdAt | date:'d') !== (message.createdAt | date:'d')">
|
|
||||||
<div class="flex items-center justify-center my-3 -mx-6">
|
|
||||||
<div class="flex-auto border-b"></div>
|
|
||||||
<div class="flex-0 mx-4 text-sm font-medium leading-5 text-secondary">
|
|
||||||
{{message.createdAt | date: 'longDate'}}
|
|
||||||
</div>
|
|
||||||
<div class="flex-auto border-b"></div>
|
|
||||||
</div>
|
|
||||||
</ng-container>
|
|
||||||
<div
|
|
||||||
class="flex flex-col"
|
|
||||||
[ngClass]="{'items-end': message.isMine,
|
|
||||||
'items-start': !message.isMine,
|
|
||||||
'mt-0.5': i > 0 && chat.messages[i - 1].isMine === message.isMine,
|
|
||||||
'mt-3': i > 0 && chat.messages[i - 1].isMine !== message.isMine}">
|
|
||||||
<!-- Bubble -->
|
|
||||||
<div
|
|
||||||
class="relative max-w-3/4 px-3 py-2 rounded-lg"
|
|
||||||
[ngClass]="{'bg-blue-500 text-blue-50': message.isMine,
|
|
||||||
'bg-gray-500 text-gray-50': !message.isMine}">
|
|
||||||
<!-- Speech bubble tail -->
|
|
||||||
<ng-container *ngIf="last || chat.messages[i + 1].isMine !== message.isMine">
|
|
||||||
<div
|
|
||||||
class="absolute bottom-0 w-3 transform"
|
|
||||||
[ngClass]="{'text-blue-500 -right-1 -mr-px mb-px': message.isMine,
|
|
||||||
'text-gray-500 -left-1 -ml-px mb-px -scale-x-1': !message.isMine}">
|
|
||||||
<ng-container *ngTemplateOutlet="speechBubbleExtension"></ng-container>
|
|
||||||
</div>
|
|
||||||
</ng-container>
|
|
||||||
<!-- Message -->
|
|
||||||
<div
|
|
||||||
class="min-w-4 leading-5"
|
|
||||||
[innerHTML]="message.value">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- Time -->
|
|
||||||
<ng-container
|
|
||||||
*ngIf="first
|
|
||||||
|| last
|
|
||||||
|| chat.messages[i + 1].isMine !== message.isMine
|
|
||||||
|| chat.messages[i + 1].createdAt !== message.createdAt">
|
|
||||||
<div
|
|
||||||
class="my-0.5 text-sm font-medium text-secondary"
|
|
||||||
[ngClass]="{'mr-3': message.isMine,
|
|
||||||
'ml-3': !message.isMine}">
|
|
||||||
{{message.createdAt | date:'HH:mm'}}
|
|
||||||
</div>
|
|
||||||
</ng-container>
|
|
||||||
</div>
|
|
||||||
</ng-container>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Message field -->
|
|
||||||
<div class="flex items-end p-4 border-t bg-gray-50 dark:bg-transparent">
|
|
||||||
<div class="flex items-center h-11 my-px">
|
|
||||||
<button mat-icon-button>
|
|
||||||
<mat-icon [svgIcon]="'heroicons_outline:emoji-happy'"></mat-icon>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="ml-0.5"
|
|
||||||
mat-icon-button>
|
|
||||||
<mat-icon [svgIcon]="'heroicons_outline:paper-clip'"></mat-icon>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<mat-form-field class="fuse-mat-dense fuse-mat-no-subscript fuse-mat-rounded fuse-mat-bold w-full ml-4">
|
|
||||||
<textarea
|
|
||||||
class="min-h-5 my-0 resize-none"
|
|
||||||
style="margin: 11px 0 !important; padding: 0 !important;"
|
|
||||||
[rows]="1"
|
|
||||||
matInput
|
|
||||||
#messageInput></textarea>
|
|
||||||
</mat-form-field>
|
|
||||||
<div class="flex items-center h-11 my-px ml-4">
|
|
||||||
<button
|
|
||||||
mat-icon-button>
|
|
||||||
<mat-icon
|
|
||||||
class="transform rotate-90"
|
|
||||||
[svgIcon]="'heroicons_outline:paper-airplane'"></mat-icon>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</mat-drawer-content>
|
|
||||||
|
|
||||||
</mat-drawer-container>
|
|
||||||
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<!-- Select chat or start new template -->
|
|
||||||
<ng-template #selectChatOrStartNew>
|
|
||||||
<div class="flex flex-col flex-auto items-center justify-center bg-gray-100 dark:bg-transparent">
|
|
||||||
<mat-icon
|
|
||||||
class="icon-size-24"
|
|
||||||
[svgIcon]="'iconsmind:speach_bubble'"></mat-icon>
|
|
||||||
<div class="mt-4 text-2xl font-semibold tracking-tight text-secondary">Select a conversation or start a new chat</div>
|
|
||||||
</div>
|
|
||||||
</ng-template>
|
|
||||||
|
|
||||||
<!-- Speech bubble tail SVG -->
|
|
||||||
<!-- @formatter:off -->
|
|
||||||
<ng-template #speechBubbleExtension>
|
|
||||||
<svg width="100%" height="100%" viewBox="0 0 66 66" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
|
||||||
<path d="M1.01522827,0.516204834 C-8.83532715,54.3062744 61.7609863,70.5215302 64.8009949,64.3061218 C68.8074951,54.8859711 30.1663208,52.9997559 37.5036011,0.516204834 L1.01522827,0.516204834 Z" fill="currentColor" fill-rule="nonzero"></path>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
</ng-template>
|
|
||||||
<!-- @formatter:on -->
|
|
||||||
|
|
||||||
</div>
|
|
||||||
@@ -1,167 +0,0 @@
|
|||||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, HostListener, NgZone, OnDestroy, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
|
|
||||||
import { Subject, takeUntil } from 'rxjs';
|
|
||||||
import { FuseMediaWatcherService } from '@fuse/services/media-watcher';
|
|
||||||
import { Chat } from 'app/modules/admin/apps/chat/chat.types';
|
|
||||||
import { ChatService } from 'app/modules/admin/apps/chat/chat.service';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector : 'chat-conversation',
|
|
||||||
templateUrl : './conversation.component.html',
|
|
||||||
encapsulation : ViewEncapsulation.None,
|
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush
|
|
||||||
})
|
|
||||||
export class ConversationComponent implements OnInit, OnDestroy
|
|
||||||
{
|
|
||||||
@ViewChild('messageInput') messageInput: ElementRef;
|
|
||||||
chat: Chat;
|
|
||||||
drawerMode: 'over' | 'side' = 'side';
|
|
||||||
drawerOpened: boolean = false;
|
|
||||||
private _unsubscribeAll: Subject<any> = new Subject<any>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor
|
|
||||||
*/
|
|
||||||
constructor(
|
|
||||||
private _changeDetectorRef: ChangeDetectorRef,
|
|
||||||
private _chatService: ChatService,
|
|
||||||
private _fuseMediaWatcherService: FuseMediaWatcherService,
|
|
||||||
private _ngZone: NgZone
|
|
||||||
)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------------------------------
|
|
||||||
// @ Decorated methods
|
|
||||||
// -----------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resize on 'input' and 'ngModelChange' events
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
@HostListener('input')
|
|
||||||
@HostListener('ngModelChange')
|
|
||||||
private _resizeMessageInput(): void
|
|
||||||
{
|
|
||||||
// This doesn't need to trigger Angular's change detection by itself
|
|
||||||
this._ngZone.runOutsideAngular(() => {
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
|
|
||||||
// Set the height to 'auto' so we can correctly read the scrollHeight
|
|
||||||
this.messageInput.nativeElement.style.height = 'auto';
|
|
||||||
|
|
||||||
// Detect the changes so the height is applied
|
|
||||||
this._changeDetectorRef.detectChanges();
|
|
||||||
|
|
||||||
// Get the scrollHeight and subtract the vertical padding
|
|
||||||
this.messageInput.nativeElement.style.height = `${this.messageInput.nativeElement.scrollHeight}px`;
|
|
||||||
|
|
||||||
// Detect the changes one more time to apply the final height
|
|
||||||
this._changeDetectorRef.detectChanges();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------------------------------
|
|
||||||
// @ Lifecycle hooks
|
|
||||||
// -----------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* On init
|
|
||||||
*/
|
|
||||||
ngOnInit(): void
|
|
||||||
{
|
|
||||||
// Chat
|
|
||||||
this._chatService.chat$
|
|
||||||
.pipe(takeUntil(this._unsubscribeAll))
|
|
||||||
.subscribe((chat: Chat) => {
|
|
||||||
this.chat = chat;
|
|
||||||
|
|
||||||
// Mark for check
|
|
||||||
this._changeDetectorRef.markForCheck();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Subscribe to media changes
|
|
||||||
this._fuseMediaWatcherService.onMediaChange$
|
|
||||||
.pipe(takeUntil(this._unsubscribeAll))
|
|
||||||
.subscribe(({matchingAliases}) => {
|
|
||||||
|
|
||||||
// Set the drawerMode if the given breakpoint is active
|
|
||||||
if ( matchingAliases.includes('lg') )
|
|
||||||
{
|
|
||||||
this.drawerMode = 'side';
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
this.drawerMode = 'over';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mark for check
|
|
||||||
this._changeDetectorRef.markForCheck();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* On destroy
|
|
||||||
*/
|
|
||||||
ngOnDestroy(): void
|
|
||||||
{
|
|
||||||
// Unsubscribe from all subscriptions
|
|
||||||
this._unsubscribeAll.next(null);
|
|
||||||
this._unsubscribeAll.complete();
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------------------------------
|
|
||||||
// @ Public methods
|
|
||||||
// -----------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Open the contact info
|
|
||||||
*/
|
|
||||||
openContactInfo(): void
|
|
||||||
{
|
|
||||||
// Open the drawer
|
|
||||||
this.drawerOpened = true;
|
|
||||||
|
|
||||||
// Mark for check
|
|
||||||
this._changeDetectorRef.markForCheck();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reset the chat
|
|
||||||
*/
|
|
||||||
resetChat(): void
|
|
||||||
{
|
|
||||||
this._chatService.resetChat();
|
|
||||||
|
|
||||||
// Close the contact info in case it's opened
|
|
||||||
this.drawerOpened = false;
|
|
||||||
|
|
||||||
// Mark for check
|
|
||||||
this._changeDetectorRef.markForCheck();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Toggle mute notifications
|
|
||||||
*/
|
|
||||||
toggleMuteNotifications(): void
|
|
||||||
{
|
|
||||||
// Toggle the muted
|
|
||||||
this.chat.muted = !this.chat.muted;
|
|
||||||
|
|
||||||
// Update the chat on the server
|
|
||||||
this._chatService.updateChat(this.chat.id, this.chat).subscribe();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Track by function for ngFor loops
|
|
||||||
*
|
|
||||||
* @param index
|
|
||||||
* @param item
|
|
||||||
*/
|
|
||||||
trackByFn(index: number, item: any): any
|
|
||||||
{
|
|
||||||
return item.id || index;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
<div class="flex flex-col flex-auto h-full overflow-hidden bg-card dark:bg-default">
|
|
||||||
|
|
||||||
<!-- Header -->
|
|
||||||
<div class="flex flex-0 items-center h-18 -mb-px px-6 bg-gray-50 dark:bg-transparent">
|
|
||||||
<button
|
|
||||||
mat-icon-button
|
|
||||||
(click)="drawer.close()">
|
|
||||||
<mat-icon [svgIcon]="'heroicons_outline:arrow-narrow-left'"></mat-icon>
|
|
||||||
</button>
|
|
||||||
<div class="ml-2 text-2xl font-semibold">New chat</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="relative overflow-y-auto">
|
|
||||||
<ng-container *ngIf="contacts.length; else noContacts">
|
|
||||||
<ng-container *ngFor="let contact of contacts; let i = index; trackBy: trackByFn">
|
|
||||||
<!-- Group -->
|
|
||||||
<ng-container *ngIf="i === 0 || contact.name.charAt(0) !== contacts[i - 1].name.charAt(0)">
|
|
||||||
<div class="z-10 sticky top-0 -mt-px px-6 py-1 md:px-8 border-t border-b font-medium uppercase text-secondary bg-gray-100 dark:bg-gray-900">
|
|
||||||
{{contact.name.charAt(0)}}
|
|
||||||
</div>
|
|
||||||
</ng-container>
|
|
||||||
<!-- Contact -->
|
|
||||||
<div class="z-20 flex items-center px-6 py-4 md:px-8 cursor-pointer border-b hover:bg-gray-100 dark:hover:bg-hover">
|
|
||||||
<div class="flex flex-0 items-center justify-center w-10 h-10 rounded-full overflow-hidden">
|
|
||||||
<ng-container *ngIf="contact.avatar">
|
|
||||||
<img
|
|
||||||
class="object-cover w-full h-full"
|
|
||||||
[src]="contact.avatar"
|
|
||||||
alt="Contact avatar"/>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container *ngIf="!contact.avatar">
|
|
||||||
<div class="flex items-center justify-center w-full h-full rounded-full text-lg uppercase bg-gray-200 text-gray-600 dark:bg-gray-700 dark:text-gray-200">
|
|
||||||
{{contact.name.charAt(0)}}
|
|
||||||
</div>
|
|
||||||
</ng-container>
|
|
||||||
</div>
|
|
||||||
<div class="min-w-0 ml-4">
|
|
||||||
<div class="font-medium leading-5 truncate">{{contact.name}}</div>
|
|
||||||
<div class="leading-5 truncate text-secondary">{{contact.about}}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</ng-container>
|
|
||||||
</ng-container>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- No contacts -->
|
|
||||||
<ng-template #noContacts>
|
|
||||||
<div class="p-8 sm:p-16 border-t text-4xl font-semibold tracking-tight text-center">There are no contacts!</div>
|
|
||||||
</ng-template>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
import { ChangeDetectionStrategy, Component, Input, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';
|
|
||||||
import { MatDrawer } from '@angular/material/sidenav';
|
|
||||||
import { Subject, takeUntil } from 'rxjs';
|
|
||||||
import { Contact } from 'app/modules/admin/apps/chat/chat.types';
|
|
||||||
import { ChatService } from 'app/modules/admin/apps/chat/chat.service';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector : 'chat-new-chat',
|
|
||||||
templateUrl : './new-chat.component.html',
|
|
||||||
encapsulation : ViewEncapsulation.None,
|
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush
|
|
||||||
})
|
|
||||||
export class NewChatComponent implements OnInit, OnDestroy
|
|
||||||
{
|
|
||||||
@Input() drawer: MatDrawer;
|
|
||||||
contacts: Contact[] = [];
|
|
||||||
private _unsubscribeAll: Subject<any> = new Subject<any>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor
|
|
||||||
*/
|
|
||||||
constructor(private _chatService: ChatService)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------------------------------
|
|
||||||
// @ Lifecycle hooks
|
|
||||||
// -----------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* On init
|
|
||||||
*/
|
|
||||||
ngOnInit(): void
|
|
||||||
{
|
|
||||||
// Contacts
|
|
||||||
this._chatService.contacts$
|
|
||||||
.pipe(takeUntil(this._unsubscribeAll))
|
|
||||||
.subscribe((contacts: Contact[]) => {
|
|
||||||
this.contacts = contacts;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* On destroy
|
|
||||||
*/
|
|
||||||
ngOnDestroy(): void
|
|
||||||
{
|
|
||||||
// Unsubscribe from all subscriptions
|
|
||||||
this._unsubscribeAll.next(null);
|
|
||||||
this._unsubscribeAll.complete();
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------------------------------
|
|
||||||
// @ Public methods
|
|
||||||
// -----------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Track by function for ngFor loops
|
|
||||||
*
|
|
||||||
* @param index
|
|
||||||
* @param item
|
|
||||||
*/
|
|
||||||
trackByFn(index: number, item: any): any
|
|
||||||
{
|
|
||||||
return item.id || index;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
<div class="flex flex-col flex-auto overflow-y-auto bg-card dark:bg-default">
|
|
||||||
|
|
||||||
<!-- Header -->
|
|
||||||
<div class="flex flex-0 items-center h-18 px-6 border-b bg-gray-50 dark:bg-transparent">
|
|
||||||
<button
|
|
||||||
mat-icon-button
|
|
||||||
(click)="drawer.close()">
|
|
||||||
<mat-icon [svgIcon]="'heroicons_outline:arrow-narrow-left'"></mat-icon>
|
|
||||||
</button>
|
|
||||||
<div class="ml-2 text-2xl font-semibold">Profile</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="px-6">
|
|
||||||
<!-- Profile photo -->
|
|
||||||
<div class="group relative flex flex-0 mt-8 mx-auto w-40 h-40 rounded-full">
|
|
||||||
<div class="hidden group-hover:flex absolute inset-0 flex-col items-center justify-center backdrop-filter backdrop-blur bg-opacity-80 rounded-full cursor-pointer bg-gray-800">
|
|
||||||
<mat-icon
|
|
||||||
class="text-white"
|
|
||||||
[svgIcon]="'heroicons_outline:camera'"></mat-icon>
|
|
||||||
<div class="mt-2 mx-6 font-medium text-center text-white">Change Profile Photo</div>
|
|
||||||
</div>
|
|
||||||
<ng-container *ngIf="profile.avatar">
|
|
||||||
<img
|
|
||||||
class="w-full h-full rounded-full object-cover"
|
|
||||||
[src]="profile.avatar"
|
|
||||||
[alt]="'Profile avatar'">
|
|
||||||
</ng-container>
|
|
||||||
<ng-container *ngIf="!profile.avatar">
|
|
||||||
<div class="flex items-center justify-center w-full h-full rounded-full text-8xl font-semibold uppercase bg-gray-200 text-gray-600 dark:bg-gray-700 dark:text-gray-200">
|
|
||||||
{{profile.name.charAt(0)}}
|
|
||||||
</div>
|
|
||||||
</ng-container>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Profile info -->
|
|
||||||
<div class="flex flex-col mt-8 mx-2">
|
|
||||||
<mat-form-field>
|
|
||||||
<mat-label>Name</mat-label>
|
|
||||||
<mat-icon
|
|
||||||
class="icon-size-5"
|
|
||||||
matPrefix
|
|
||||||
[svgIcon]="'heroicons_solid:user-circle'"></mat-icon>
|
|
||||||
<input
|
|
||||||
matInput
|
|
||||||
[ngModel]="profile.name">
|
|
||||||
</mat-form-field>
|
|
||||||
<mat-form-field>
|
|
||||||
<mat-label>Email</mat-label>
|
|
||||||
<mat-icon
|
|
||||||
class="icon-size-5"
|
|
||||||
matPrefix
|
|
||||||
[svgIcon]="'heroicons_solid:mail'"></mat-icon>
|
|
||||||
<input
|
|
||||||
matInput
|
|
||||||
[ngModel]="profile.email">
|
|
||||||
</mat-form-field>
|
|
||||||
<mat-form-field>
|
|
||||||
<mat-label>About</mat-label>
|
|
||||||
<mat-icon
|
|
||||||
class="icon-size-5"
|
|
||||||
matPrefix
|
|
||||||
[svgIcon]="'heroicons_solid:identification'"></mat-icon>
|
|
||||||
<input
|
|
||||||
matInput
|
|
||||||
[ngModel]="profile.about">
|
|
||||||
</mat-form-field>
|
|
||||||
<div class="flex items-center justify-end mt-4">
|
|
||||||
<button
|
|
||||||
(click)="drawer.close()"
|
|
||||||
mat-button>Cancel
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="ml-2"
|
|
||||||
mat-flat-button
|
|
||||||
[color]="'primary'">Save
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
import { ChangeDetectionStrategy, Component, Input, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';
|
|
||||||
import { MatDrawer } from '@angular/material/sidenav';
|
|
||||||
import { Subject, takeUntil } from 'rxjs';
|
|
||||||
import { Profile } from 'app/modules/admin/apps/chat/chat.types';
|
|
||||||
import { ChatService } from 'app/modules/admin/apps/chat/chat.service';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector : 'chat-profile',
|
|
||||||
templateUrl : './profile.component.html',
|
|
||||||
encapsulation : ViewEncapsulation.None,
|
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush
|
|
||||||
})
|
|
||||||
export class ProfileComponent implements OnInit, OnDestroy
|
|
||||||
{
|
|
||||||
@Input() drawer: MatDrawer;
|
|
||||||
profile: Profile;
|
|
||||||
private _unsubscribeAll: Subject<any> = new Subject<any>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor
|
|
||||||
*/
|
|
||||||
constructor(private _chatService: ChatService)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------------------------------
|
|
||||||
// @ Lifecycle hooks
|
|
||||||
// -----------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* On init
|
|
||||||
*/
|
|
||||||
ngOnInit(): void
|
|
||||||
{
|
|
||||||
// Profile
|
|
||||||
this._chatService.profile$
|
|
||||||
.pipe(takeUntil(this._unsubscribeAll))
|
|
||||||
.subscribe((profile: Profile) => {
|
|
||||||
this.profile = profile;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* On destroy
|
|
||||||
*/
|
|
||||||
ngOnDestroy(): void
|
|
||||||
{
|
|
||||||
// Unsubscribe from all subscriptions
|
|
||||||
this._unsubscribeAll.next(null);
|
|
||||||
this._unsubscribeAll.complete();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
<router-outlet></router-outlet>
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
import { ChangeDetectionStrategy, Component, ViewEncapsulation } from '@angular/core';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector : 'contacts',
|
|
||||||
templateUrl : './contacts.component.html',
|
|
||||||
encapsulation : ViewEncapsulation.None,
|
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush
|
|
||||||
})
|
|
||||||
export class ContactsComponent
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Constructor
|
|
||||||
*/
|
|
||||||
constructor()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
import { Injectable } from '@angular/core';
|
|
||||||
import { ActivatedRouteSnapshot, CanDeactivate, RouterStateSnapshot, UrlTree } from '@angular/router';
|
|
||||||
import { Observable } from 'rxjs';
|
|
||||||
import { ContactsDetailsComponent } from 'app/modules/admin/apps/contacts/details/details.component';
|
|
||||||
|
|
||||||
@Injectable({
|
|
||||||
providedIn: 'root'
|
|
||||||
})
|
|
||||||
export class CanDeactivateContactsDetails implements CanDeactivate<ContactsDetailsComponent>
|
|
||||||
{
|
|
||||||
canDeactivate(
|
|
||||||
component: ContactsDetailsComponent,
|
|
||||||
currentRoute: ActivatedRouteSnapshot,
|
|
||||||
currentState: RouterStateSnapshot,
|
|
||||||
nextState: RouterStateSnapshot
|
|
||||||
): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree
|
|
||||||
{
|
|
||||||
// Get the next route
|
|
||||||
let nextRoute: ActivatedRouteSnapshot = nextState.root;
|
|
||||||
while ( nextRoute.firstChild )
|
|
||||||
{
|
|
||||||
nextRoute = nextRoute.firstChild;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the next state doesn't contain '/contacts'
|
|
||||||
// it means we are navigating away from the
|
|
||||||
// contacts app
|
|
||||||
if ( !nextState.url.includes('/contacts') )
|
|
||||||
{
|
|
||||||
// Let it navigate
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we are navigating to another contact...
|
|
||||||
if ( nextRoute.paramMap.get('id') )
|
|
||||||
{
|
|
||||||
// Just navigate
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// Otherwise...
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Close the drawer first, and then navigate
|
|
||||||
return component.closeDrawer().then(() => true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
import { NgModule } from '@angular/core';
|
|
||||||
import { RouterModule } from '@angular/router';
|
|
||||||
import { MatButtonModule } from '@angular/material/button';
|
|
||||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
|
||||||
import { MAT_DATE_FORMATS, MatRippleModule } from '@angular/material/core';
|
|
||||||
import { MatDatepickerModule } from '@angular/material/datepicker';
|
|
||||||
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 { MatMenuModule } from '@angular/material/menu';
|
|
||||||
import { MatMomentDateModule } from '@angular/material-moment-adapter';
|
|
||||||
import { MatProgressBarModule } from '@angular/material/progress-bar';
|
|
||||||
import { MatRadioModule } from '@angular/material/radio';
|
|
||||||
import { MatSelectModule } from '@angular/material/select';
|
|
||||||
import { MatSidenavModule } from '@angular/material/sidenav';
|
|
||||||
import { MatTableModule } from '@angular/material/table';
|
|
||||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
|
||||||
import * as moment from 'moment';
|
|
||||||
import { FuseFindByKeyPipeModule } from '@fuse/pipes/find-by-key';
|
|
||||||
import { SharedModule } from 'app/shared/shared.module';
|
|
||||||
import { contactsRoutes } from 'app/modules/admin/apps/contacts/contacts.routing';
|
|
||||||
import { ContactsComponent } from 'app/modules/admin/apps/contacts/contacts.component';
|
|
||||||
import { ContactsDetailsComponent } from 'app/modules/admin/apps/contacts/details/details.component';
|
|
||||||
import { ContactsListComponent } from 'app/modules/admin/apps/contacts/list/list.component';
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
declarations: [
|
|
||||||
ContactsComponent,
|
|
||||||
ContactsListComponent,
|
|
||||||
ContactsDetailsComponent
|
|
||||||
],
|
|
||||||
imports : [
|
|
||||||
RouterModule.forChild(contactsRoutes),
|
|
||||||
MatButtonModule,
|
|
||||||
MatCheckboxModule,
|
|
||||||
MatDatepickerModule,
|
|
||||||
MatDividerModule,
|
|
||||||
MatFormFieldModule,
|
|
||||||
MatIconModule,
|
|
||||||
MatInputModule,
|
|
||||||
MatMenuModule,
|
|
||||||
MatMomentDateModule,
|
|
||||||
MatProgressBarModule,
|
|
||||||
MatRadioModule,
|
|
||||||
MatRippleModule,
|
|
||||||
MatSelectModule,
|
|
||||||
MatSidenavModule,
|
|
||||||
MatTableModule,
|
|
||||||
MatTooltipModule,
|
|
||||||
FuseFindByKeyPipeModule,
|
|
||||||
SharedModule
|
|
||||||
],
|
|
||||||
providers : [
|
|
||||||
{
|
|
||||||
provide : MAT_DATE_FORMATS,
|
|
||||||
useValue: {
|
|
||||||
parse : {
|
|
||||||
dateInput: moment.ISO_8601
|
|
||||||
},
|
|
||||||
display: {
|
|
||||||
dateInput : 'LL',
|
|
||||||
monthYearLabel : 'MMM YYYY',
|
|
||||||
dateA11yLabel : 'LL',
|
|
||||||
monthYearA11yLabel: 'MMMM YYYY'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
})
|
|
||||||
export class ContactsModule
|
|
||||||
{
|
|
||||||
}
|
|
||||||
@@ -1,137 +0,0 @@
|
|||||||
import { Injectable } from '@angular/core';
|
|
||||||
import { ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot } from '@angular/router';
|
|
||||||
import { catchError, Observable, throwError } from 'rxjs';
|
|
||||||
import { ContactsService } from 'app/modules/admin/apps/contacts/contacts.service';
|
|
||||||
import { Contact, Country, Tag } from 'app/modules/admin/apps/contacts/contacts.types';
|
|
||||||
|
|
||||||
@Injectable({
|
|
||||||
providedIn: 'root'
|
|
||||||
})
|
|
||||||
export class ContactsResolver implements Resolve<any>
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Constructor
|
|
||||||
*/
|
|
||||||
constructor(private _contactsService: ContactsService)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------------------------------
|
|
||||||
// @ Public methods
|
|
||||||
// -----------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolver
|
|
||||||
*
|
|
||||||
* @param route
|
|
||||||
* @param state
|
|
||||||
*/
|
|
||||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<Contact[]>
|
|
||||||
{
|
|
||||||
return this._contactsService.getContacts();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Injectable({
|
|
||||||
providedIn: 'root'
|
|
||||||
})
|
|
||||||
export class ContactsContactResolver implements Resolve<any>
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Constructor
|
|
||||||
*/
|
|
||||||
constructor(
|
|
||||||
private _contactsService: ContactsService,
|
|
||||||
private _router: Router
|
|
||||||
)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------------------------------
|
|
||||||
// @ Public methods
|
|
||||||
// -----------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolver
|
|
||||||
*
|
|
||||||
* @param route
|
|
||||||
* @param state
|
|
||||||
*/
|
|
||||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<Contact>
|
|
||||||
{
|
|
||||||
return this._contactsService.getContactById(route.paramMap.get('id'))
|
|
||||||
.pipe(
|
|
||||||
// Error here means the requested contact is not available
|
|
||||||
catchError((error) => {
|
|
||||||
|
|
||||||
// Log the error
|
|
||||||
console.error(error);
|
|
||||||
|
|
||||||
// Get the parent url
|
|
||||||
const parentUrl = state.url.split('/').slice(0, -1).join('/');
|
|
||||||
|
|
||||||
// Navigate to there
|
|
||||||
this._router.navigateByUrl(parentUrl);
|
|
||||||
|
|
||||||
// Throw an error
|
|
||||||
return throwError(error);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Injectable({
|
|
||||||
providedIn: 'root'
|
|
||||||
})
|
|
||||||
export class ContactsCountriesResolver implements Resolve<any>
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Constructor
|
|
||||||
*/
|
|
||||||
constructor(private _contactsService: ContactsService)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------------------------------
|
|
||||||
// @ Public methods
|
|
||||||
// -----------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolver
|
|
||||||
*
|
|
||||||
* @param route
|
|
||||||
* @param state
|
|
||||||
*/
|
|
||||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<Country[]>
|
|
||||||
{
|
|
||||||
return this._contactsService.getCountries();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Injectable({
|
|
||||||
providedIn: 'root'
|
|
||||||
})
|
|
||||||
export class ContactsTagsResolver implements Resolve<any>
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Constructor
|
|
||||||
*/
|
|
||||||
constructor(private _contactsService: ContactsService)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------------------------------
|
|
||||||
// @ Public methods
|
|
||||||
// -----------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolver
|
|
||||||
*
|
|
||||||
* @param route
|
|
||||||
* @param state
|
|
||||||
*/
|
|
||||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<Tag[]>
|
|
||||||
{
|
|
||||||
return this._contactsService.getTags();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
import { Route } from '@angular/router';
|
|
||||||
import { CanDeactivateContactsDetails } from 'app/modules/admin/apps/contacts/contacts.guards';
|
|
||||||
import { ContactsContactResolver, ContactsCountriesResolver, ContactsResolver, ContactsTagsResolver } from 'app/modules/admin/apps/contacts/contacts.resolvers';
|
|
||||||
import { ContactsComponent } from 'app/modules/admin/apps/contacts/contacts.component';
|
|
||||||
import { ContactsListComponent } from 'app/modules/admin/apps/contacts/list/list.component';
|
|
||||||
import { ContactsDetailsComponent } from 'app/modules/admin/apps/contacts/details/details.component';
|
|
||||||
|
|
||||||
export const contactsRoutes: Route[] = [
|
|
||||||
{
|
|
||||||
path : '',
|
|
||||||
component: ContactsComponent,
|
|
||||||
resolve : {
|
|
||||||
tags: ContactsTagsResolver
|
|
||||||
},
|
|
||||||
children : [
|
|
||||||
{
|
|
||||||
path : '',
|
|
||||||
component: ContactsListComponent,
|
|
||||||
resolve : {
|
|
||||||
tasks : ContactsResolver,
|
|
||||||
countries: ContactsCountriesResolver
|
|
||||||
},
|
|
||||||
children : [
|
|
||||||
{
|
|
||||||
path : ':id',
|
|
||||||
component : ContactsDetailsComponent,
|
|
||||||
resolve : {
|
|
||||||
task : ContactsContactResolver,
|
|
||||||
countries: ContactsCountriesResolver
|
|
||||||
},
|
|
||||||
canDeactivate: [CanDeactivateContactsDetails]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
];
|
|
||||||
@@ -1,389 +0,0 @@
|
|||||||
import { Injectable } from '@angular/core';
|
|
||||||
import { HttpClient } from '@angular/common/http';
|
|
||||||
import { BehaviorSubject, filter, map, Observable, of, switchMap, take, tap, throwError } from 'rxjs';
|
|
||||||
import { Contact, Country, Tag } from 'app/modules/admin/apps/contacts/contacts.types';
|
|
||||||
|
|
||||||
@Injectable({
|
|
||||||
providedIn: 'root'
|
|
||||||
})
|
|
||||||
export class ContactsService
|
|
||||||
{
|
|
||||||
// Private
|
|
||||||
private _contact: BehaviorSubject<Contact | null> = new BehaviorSubject(null);
|
|
||||||
private _contacts: BehaviorSubject<Contact[] | null> = new BehaviorSubject(null);
|
|
||||||
private _countries: BehaviorSubject<Country[] | null> = new BehaviorSubject(null);
|
|
||||||
private _tags: BehaviorSubject<Tag[] | null> = new BehaviorSubject(null);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor
|
|
||||||
*/
|
|
||||||
constructor(private _httpClient: HttpClient)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------------------------------
|
|
||||||
// @ Accessors
|
|
||||||
// -----------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Getter for contact
|
|
||||||
*/
|
|
||||||
get contact$(): Observable<Contact>
|
|
||||||
{
|
|
||||||
return this._contact.asObservable();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Getter for contacts
|
|
||||||
*/
|
|
||||||
get contacts$(): Observable<Contact[]>
|
|
||||||
{
|
|
||||||
return this._contacts.asObservable();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Getter for countries
|
|
||||||
*/
|
|
||||||
get countries$(): Observable<Country[]>
|
|
||||||
{
|
|
||||||
return this._countries.asObservable();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Getter for tags
|
|
||||||
*/
|
|
||||||
get tags$(): Observable<Tag[]>
|
|
||||||
{
|
|
||||||
return this._tags.asObservable();
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------------------------------
|
|
||||||
// @ Public methods
|
|
||||||
// -----------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get contacts
|
|
||||||
*/
|
|
||||||
getContacts(): Observable<Contact[]>
|
|
||||||
{
|
|
||||||
return this._httpClient.get<Contact[]>('api/apps/contacts/all').pipe(
|
|
||||||
tap((contacts) => {
|
|
||||||
this._contacts.next(contacts);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Search contacts with given query
|
|
||||||
*
|
|
||||||
* @param query
|
|
||||||
*/
|
|
||||||
searchContacts(query: string): Observable<Contact[]>
|
|
||||||
{
|
|
||||||
return this._httpClient.get<Contact[]>('api/apps/contacts/search', {
|
|
||||||
params: {query}
|
|
||||||
}).pipe(
|
|
||||||
tap((contacts) => {
|
|
||||||
this._contacts.next(contacts);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get contact by id
|
|
||||||
*/
|
|
||||||
getContactById(id: string): Observable<Contact>
|
|
||||||
{
|
|
||||||
return this._contacts.pipe(
|
|
||||||
take(1),
|
|
||||||
map((contacts) => {
|
|
||||||
|
|
||||||
// Find the contact
|
|
||||||
const contact = contacts.find(item => item.id === id) || null;
|
|
||||||
|
|
||||||
// Update the contact
|
|
||||||
this._contact.next(contact);
|
|
||||||
|
|
||||||
// Return the contact
|
|
||||||
return contact;
|
|
||||||
}),
|
|
||||||
switchMap((contact) => {
|
|
||||||
|
|
||||||
if ( !contact )
|
|
||||||
{
|
|
||||||
return throwError('Could not found contact with id of ' + id + '!');
|
|
||||||
}
|
|
||||||
|
|
||||||
return of(contact);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create contact
|
|
||||||
*/
|
|
||||||
createContact(): Observable<Contact>
|
|
||||||
{
|
|
||||||
return this.contacts$.pipe(
|
|
||||||
take(1),
|
|
||||||
switchMap(contacts => this._httpClient.post<Contact>('api/apps/contacts/contact', {}).pipe(
|
|
||||||
map((newContact) => {
|
|
||||||
|
|
||||||
// Update the contacts with the new contact
|
|
||||||
this._contacts.next([newContact, ...contacts]);
|
|
||||||
|
|
||||||
// Return the new contact
|
|
||||||
return newContact;
|
|
||||||
})
|
|
||||||
))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update contact
|
|
||||||
*
|
|
||||||
* @param id
|
|
||||||
* @param contact
|
|
||||||
*/
|
|
||||||
updateContact(id: string, contact: Contact): Observable<Contact>
|
|
||||||
{
|
|
||||||
return this.contacts$.pipe(
|
|
||||||
take(1),
|
|
||||||
switchMap(contacts => this._httpClient.patch<Contact>('api/apps/contacts/contact', {
|
|
||||||
id,
|
|
||||||
contact
|
|
||||||
}).pipe(
|
|
||||||
map((updatedContact) => {
|
|
||||||
|
|
||||||
// Find the index of the updated contact
|
|
||||||
const index = contacts.findIndex(item => item.id === id);
|
|
||||||
|
|
||||||
// Update the contact
|
|
||||||
contacts[index] = updatedContact;
|
|
||||||
|
|
||||||
// Update the contacts
|
|
||||||
this._contacts.next(contacts);
|
|
||||||
|
|
||||||
// Return the updated contact
|
|
||||||
return updatedContact;
|
|
||||||
}),
|
|
||||||
switchMap(updatedContact => this.contact$.pipe(
|
|
||||||
take(1),
|
|
||||||
filter(item => item && item.id === id),
|
|
||||||
tap(() => {
|
|
||||||
|
|
||||||
// Update the contact if it's selected
|
|
||||||
this._contact.next(updatedContact);
|
|
||||||
|
|
||||||
// Return the updated contact
|
|
||||||
return updatedContact;
|
|
||||||
})
|
|
||||||
))
|
|
||||||
))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete the contact
|
|
||||||
*
|
|
||||||
* @param id
|
|
||||||
*/
|
|
||||||
deleteContact(id: string): Observable<boolean>
|
|
||||||
{
|
|
||||||
return this.contacts$.pipe(
|
|
||||||
take(1),
|
|
||||||
switchMap(contacts => this._httpClient.delete('api/apps/contacts/contact', {params: {id}}).pipe(
|
|
||||||
map((isDeleted: boolean) => {
|
|
||||||
|
|
||||||
// Find the index of the deleted contact
|
|
||||||
const index = contacts.findIndex(item => item.id === id);
|
|
||||||
|
|
||||||
// Delete the contact
|
|
||||||
contacts.splice(index, 1);
|
|
||||||
|
|
||||||
// Update the contacts
|
|
||||||
this._contacts.next(contacts);
|
|
||||||
|
|
||||||
// Return the deleted status
|
|
||||||
return isDeleted;
|
|
||||||
})
|
|
||||||
))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get countries
|
|
||||||
*/
|
|
||||||
getCountries(): Observable<Country[]>
|
|
||||||
{
|
|
||||||
return this._httpClient.get<Country[]>('api/apps/contacts/countries').pipe(
|
|
||||||
tap((countries) => {
|
|
||||||
this._countries.next(countries);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get tags
|
|
||||||
*/
|
|
||||||
getTags(): Observable<Tag[]>
|
|
||||||
{
|
|
||||||
return this._httpClient.get<Tag[]>('api/apps/contacts/tags').pipe(
|
|
||||||
tap((tags) => {
|
|
||||||
this._tags.next(tags);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create tag
|
|
||||||
*
|
|
||||||
* @param tag
|
|
||||||
*/
|
|
||||||
createTag(tag: Tag): Observable<Tag>
|
|
||||||
{
|
|
||||||
return this.tags$.pipe(
|
|
||||||
take(1),
|
|
||||||
switchMap(tags => this._httpClient.post<Tag>('api/apps/contacts/tag', {tag}).pipe(
|
|
||||||
map((newTag) => {
|
|
||||||
|
|
||||||
// Update the tags with the new tag
|
|
||||||
this._tags.next([...tags, newTag]);
|
|
||||||
|
|
||||||
// Return new tag from observable
|
|
||||||
return newTag;
|
|
||||||
})
|
|
||||||
))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the tag
|
|
||||||
*
|
|
||||||
* @param id
|
|
||||||
* @param tag
|
|
||||||
*/
|
|
||||||
updateTag(id: string, tag: Tag): Observable<Tag>
|
|
||||||
{
|
|
||||||
return this.tags$.pipe(
|
|
||||||
take(1),
|
|
||||||
switchMap(tags => this._httpClient.patch<Tag>('api/apps/contacts/tag', {
|
|
||||||
id,
|
|
||||||
tag
|
|
||||||
}).pipe(
|
|
||||||
map((updatedTag) => {
|
|
||||||
|
|
||||||
// Find the index of the updated tag
|
|
||||||
const index = tags.findIndex(item => item.id === id);
|
|
||||||
|
|
||||||
// Update the tag
|
|
||||||
tags[index] = updatedTag;
|
|
||||||
|
|
||||||
// Update the tags
|
|
||||||
this._tags.next(tags);
|
|
||||||
|
|
||||||
// Return the updated tag
|
|
||||||
return updatedTag;
|
|
||||||
})
|
|
||||||
))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete the tag
|
|
||||||
*
|
|
||||||
* @param id
|
|
||||||
*/
|
|
||||||
deleteTag(id: string): Observable<boolean>
|
|
||||||
{
|
|
||||||
return this.tags$.pipe(
|
|
||||||
take(1),
|
|
||||||
switchMap(tags => this._httpClient.delete('api/apps/contacts/tag', {params: {id}}).pipe(
|
|
||||||
map((isDeleted: boolean) => {
|
|
||||||
|
|
||||||
// Find the index of the deleted tag
|
|
||||||
const index = tags.findIndex(item => item.id === id);
|
|
||||||
|
|
||||||
// Delete the tag
|
|
||||||
tags.splice(index, 1);
|
|
||||||
|
|
||||||
// Update the tags
|
|
||||||
this._tags.next(tags);
|
|
||||||
|
|
||||||
// Return the deleted status
|
|
||||||
return isDeleted;
|
|
||||||
}),
|
|
||||||
filter(isDeleted => isDeleted),
|
|
||||||
switchMap(isDeleted => this.contacts$.pipe(
|
|
||||||
take(1),
|
|
||||||
map((contacts) => {
|
|
||||||
|
|
||||||
// Iterate through the contacts
|
|
||||||
contacts.forEach((contact) => {
|
|
||||||
|
|
||||||
const tagIndex = contact.tags.findIndex(tag => tag === id);
|
|
||||||
|
|
||||||
// If the contact has the tag, remove it
|
|
||||||
if ( tagIndex > -1 )
|
|
||||||
{
|
|
||||||
contact.tags.splice(tagIndex, 1);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Return the deleted status
|
|
||||||
return isDeleted;
|
|
||||||
})
|
|
||||||
))
|
|
||||||
))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the avatar of the given contact
|
|
||||||
*
|
|
||||||
* @param id
|
|
||||||
* @param avatar
|
|
||||||
*/
|
|
||||||
uploadAvatar(id: string, avatar: File): Observable<Contact>
|
|
||||||
{
|
|
||||||
return this.contacts$.pipe(
|
|
||||||
take(1),
|
|
||||||
switchMap(contacts => this._httpClient.post<Contact>('api/apps/contacts/avatar', {
|
|
||||||
id,
|
|
||||||
avatar
|
|
||||||
}, {
|
|
||||||
headers: {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
||||||
'Content-Type': avatar.type
|
|
||||||
}
|
|
||||||
}).pipe(
|
|
||||||
map((updatedContact) => {
|
|
||||||
|
|
||||||
// Find the index of the updated contact
|
|
||||||
const index = contacts.findIndex(item => item.id === id);
|
|
||||||
|
|
||||||
// Update the contact
|
|
||||||
contacts[index] = updatedContact;
|
|
||||||
|
|
||||||
// Update the contacts
|
|
||||||
this._contacts.next(contacts);
|
|
||||||
|
|
||||||
// Return the updated contact
|
|
||||||
return updatedContact;
|
|
||||||
}),
|
|
||||||
switchMap(updatedContact => this.contact$.pipe(
|
|
||||||
take(1),
|
|
||||||
filter(item => item && item.id === id),
|
|
||||||
tap(() => {
|
|
||||||
|
|
||||||
// Update the contact if it's selected
|
|
||||||
this._contact.next(updatedContact);
|
|
||||||
|
|
||||||
// Return the updated contact
|
|
||||||
return updatedContact;
|
|
||||||
})
|
|
||||||
))
|
|
||||||
))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user