mirror of
https://github.com/richard-loafle/fuse-angular.git
synced 2025-12-24 10:57:06 +00:00
Compare commits
48 Commits
v13.0.0
...
v13.0.2-st
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2d2db97416 | ||
|
|
466bf50de4 | ||
|
|
5daa2260e6 | ||
|
|
95bc7dc4db | ||
|
|
af984fcba1 | ||
|
|
cfca19dc68 | ||
|
|
a0c20f8d59 | ||
|
|
97d3662417 | ||
|
|
df9f2256cd | ||
|
|
6bcd04b799 | ||
|
|
3c0e326113 | ||
|
|
c7158377f7 | ||
|
|
105575d40e | ||
|
|
f470313d72 | ||
|
|
60e9c65505 | ||
|
|
3b727fe859 | ||
|
|
4694bb401d | ||
|
|
ee86dc6245 | ||
|
|
3d09241c64 | ||
|
|
e499c06884 | ||
|
|
eca618c95b | ||
|
|
8524df013a | ||
|
|
d897a244c8 | ||
|
|
e4df408abe | ||
|
|
fd859a8663 | ||
|
|
59960af7a5 | ||
|
|
74c4dc2ad8 | ||
|
|
f76f38b812 | ||
|
|
5c40e99518 | ||
|
|
c1ca701e92 | ||
|
|
e00dda98bc | ||
|
|
9edc62f703 | ||
|
|
f9c8e16778 | ||
|
|
4a30e3482c | ||
|
|
837f444cc9 | ||
|
|
d0876eb80c | ||
|
|
d146a92c79 | ||
|
|
27b6858b76 | ||
|
|
fcfba4c9e4 | ||
|
|
40894e0aa3 | ||
|
|
8dcf21cb1a | ||
|
|
d917f03883 | ||
|
|
0f2ddbda83 | ||
|
|
fa0d74504b | ||
|
|
ad2b19a07a | ||
|
|
4bf11591a2 | ||
|
|
f45a605b4e | ||
|
|
c150a8902c |
@@ -16,8 +16,7 @@
|
||||
],
|
||||
"parserOptions": {
|
||||
"project": [
|
||||
"tsconfig.json",
|
||||
"e2e/tsconfig.json"
|
||||
"tsconfig.json"
|
||||
],
|
||||
"createDefaultProgram": true
|
||||
},
|
||||
@@ -44,6 +43,7 @@
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/dot-notation": "off",
|
||||
"@typescript-eslint/explicit-function-return-type": "error",
|
||||
"@typescript-eslint/explicit-member-accessibility": [
|
||||
"off",
|
||||
{
|
||||
|
||||
3
CREDITS
3
CREDITS
@@ -2,6 +2,9 @@
|
||||
// @ 3rd party credits
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
// Flags
|
||||
https://github.com/Yummygum/flagpack-core
|
||||
|
||||
// Icons
|
||||
Material - https://material.io/tools/icons
|
||||
Feather - https://feathericons.com/
|
||||
|
||||
544
package-lock.json
generated
544
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
21
package.json
21
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@fuse/demo",
|
||||
"version": "13.0.0",
|
||||
"version": "13.0.2",
|
||||
"license": "https://themeforest.net/licenses/standard",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
@@ -31,13 +31,14 @@
|
||||
"@fullcalendar/moment": "4.4.2",
|
||||
"@fullcalendar/rrule": "4.4.2",
|
||||
"@fullcalendar/timegrid": "4.4.2",
|
||||
"apexcharts": "3.26.2",
|
||||
"@ngneat/transloco": "2.20.1",
|
||||
"apexcharts": "3.26.3",
|
||||
"crypto-js": "3.3.0",
|
||||
"highlight.js": "10.7.2",
|
||||
"lodash-es": "4.17.21",
|
||||
"moment": "2.29.1",
|
||||
"ng-apexcharts": "1.5.9",
|
||||
"ngx-markdown": "11.1.3",
|
||||
"ng-apexcharts": "1.5.10",
|
||||
"ngx-markdown": "12.0.0",
|
||||
"ngx-quill": "14.0.0",
|
||||
"perfect-scrollbar": "1.5.1",
|
||||
"quill": "1.3.7",
|
||||
@@ -67,13 +68,13 @@
|
||||
"@types/lodash": "4.14.169",
|
||||
"@types/lodash-es": "4.17.4",
|
||||
"@types/node": "12.20.13",
|
||||
"@typescript-eslint/eslint-plugin": "4.23.0",
|
||||
"@typescript-eslint/parser": "4.23.0",
|
||||
"@typescript-eslint/eslint-plugin": "4.24.0",
|
||||
"@typescript-eslint/parser": "4.24.0",
|
||||
"autoprefixer": "10.2.5",
|
||||
"chroma-js": "2.1.1",
|
||||
"chroma-js": "2.1.2",
|
||||
"eslint": "7.26.0",
|
||||
"eslint-plugin-import": "2.23.0",
|
||||
"eslint-plugin-jsdoc": "34.2.2",
|
||||
"eslint-plugin-import": "2.23.2",
|
||||
"eslint-plugin-jsdoc": "34.8.2",
|
||||
"eslint-plugin-prefer-arrow": "1.2.3",
|
||||
"jasmine-core": "3.7.1",
|
||||
"jasmine-spec-reporter": "5.0.2",
|
||||
@@ -83,7 +84,7 @@
|
||||
"karma-jasmine": "4.0.1",
|
||||
"karma-jasmine-html-reporter": "1.6.0",
|
||||
"lodash": "4.17.21",
|
||||
"postcss": "8.2.15",
|
||||
"postcss": "8.3.0",
|
||||
"protractor": "7.0.0",
|
||||
"tailwindcss": "2.1.2",
|
||||
"typescript": "4.2.4"
|
||||
|
||||
@@ -1 +1 @@
|
||||
export * from './public-api';
|
||||
export * from '@fuse/animations/public-api';
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { expandCollapse } from './expand-collapse';
|
||||
import { fadeIn, fadeInBottom, fadeInLeft, fadeInRight, fadeInTop, fadeOut, fadeOutBottom, fadeOutLeft, fadeOutRight, fadeOutTop } from './fade';
|
||||
import { shake } from './shake';
|
||||
import { slideInBottom, slideInLeft, slideInRight, slideInTop, slideOutBottom, slideOutLeft, slideOutRight, slideOutTop } from './slide';
|
||||
import { zoomIn, zoomOut } from './zoom';
|
||||
import { expandCollapse } from '@fuse/animations/expand-collapse';
|
||||
import { fadeIn, fadeInBottom, fadeInLeft, fadeInRight, fadeInTop, fadeOut, fadeOutBottom, fadeOutLeft, fadeOutRight, fadeOutTop } from '@fuse/animations/fade';
|
||||
import { shake } from '@fuse/animations/shake';
|
||||
import { slideInBottom, slideInLeft, slideInRight, slideInTop, slideOutBottom, slideOutLeft, slideOutRight, slideOutTop } from '@fuse/animations/slide';
|
||||
import { zoomIn, zoomOut } from '@fuse/animations/zoom';
|
||||
|
||||
export const fuseAnimations = [
|
||||
expandCollapse,
|
||||
|
||||
@@ -61,9 +61,9 @@ export class FuseDateRangeComponent implements ControlValueAccessor, OnInit, OnD
|
||||
private _viewContainerRef: ViewContainerRef
|
||||
)
|
||||
{
|
||||
this._onChange = () => {
|
||||
this._onChange = (): void => {
|
||||
};
|
||||
this._onTouched = () => {
|
||||
this._onTouched = (): void => {
|
||||
};
|
||||
this.dateFormat = 'DD/MM/YYYY';
|
||||
this.timeFormat = '12';
|
||||
@@ -361,7 +361,7 @@ export class FuseDateRangeComponent implements ControlValueAccessor, OnInit, OnD
|
||||
this._unsubscribeAll.complete();
|
||||
|
||||
// @ TODO: Workaround until "angular/issues/20007" resolved
|
||||
this.writeValue = () => {
|
||||
this.writeValue = (): void => {
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
export * from '@fuse/components/card/public-api';
|
||||
export * from '@fuse/components/masonry/public-api';
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
[ngClass]="{'fuse-horizontal-navigation-item-active-forced': item.active}"
|
||||
[routerLink]="[item.link]"
|
||||
[routerLinkActive]="'fuse-horizontal-navigation-item-active'"
|
||||
[routerLinkActiveOptions]="{exact: item.exactMatch || false}">
|
||||
[routerLinkActiveOptions]="isActiveMatchOptions">
|
||||
<ng-container *ngTemplateOutlet="itemTemplate"></ng-container>
|
||||
</div>
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
[ngClass]="{'fuse-horizontal-navigation-item-active-forced': item.active}"
|
||||
[routerLink]="[item.link]"
|
||||
[routerLinkActive]="'fuse-horizontal-navigation-item-active'"
|
||||
[routerLinkActiveOptions]="{exact: item.exactMatch || false}"
|
||||
[routerLinkActiveOptions]="isActiveMatchOptions"
|
||||
(click)="item.function(item)">
|
||||
<ng-container *ngTemplateOutlet="itemTemplate"></ng-container>
|
||||
</div>
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnDestroy, OnInit } from '@angular/core';
|
||||
import { IsActiveMatchOptions } from '@angular/router';
|
||||
import { Subject } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
import { FuseHorizontalNavigationComponent } from '@fuse/components/navigation/horizontal/horizontal.component';
|
||||
import { FuseNavigationService } from '@fuse/components/navigation/navigation.service';
|
||||
import { FuseNavigationItem } from '@fuse/components/navigation/navigation.types';
|
||||
import { FuseUtilsService } from '@fuse/services/utils/utils.service';
|
||||
|
||||
@Component({
|
||||
selector : 'fuse-horizontal-navigation-basic-item',
|
||||
@@ -16,6 +18,7 @@ export class FuseHorizontalNavigationBasicItemComponent implements OnInit, OnDes
|
||||
@Input() item: FuseNavigationItem;
|
||||
@Input() name: string;
|
||||
|
||||
isActiveMatchOptions: IsActiveMatchOptions;
|
||||
private _fuseHorizontalNavigationComponent: FuseHorizontalNavigationComponent;
|
||||
private _unsubscribeAll: Subject<any> = new Subject<any>();
|
||||
|
||||
@@ -24,9 +27,15 @@ export class FuseHorizontalNavigationBasicItemComponent implements OnInit, OnDes
|
||||
*/
|
||||
constructor(
|
||||
private _changeDetectorRef: ChangeDetectorRef,
|
||||
private _fuseNavigationService: FuseNavigationService
|
||||
private _fuseNavigationService: FuseNavigationService,
|
||||
private _fuseUtilsService: FuseUtilsService
|
||||
)
|
||||
{
|
||||
// Set the equivalent of {exact: false} as default for active match options.
|
||||
// We are not assigning the item.isActiveMatchOptions directly to the
|
||||
// [routerLinkActiveOptions] because if it's "undefined" initially, the router
|
||||
// will throw an error and stop working.
|
||||
this.isActiveMatchOptions = this._fuseUtilsService.subsetMatchOptions;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
@@ -38,9 +47,20 @@ export class FuseHorizontalNavigationBasicItemComponent implements OnInit, OnDes
|
||||
*/
|
||||
ngOnInit(): void
|
||||
{
|
||||
// Set the "isActiveMatchOptions" either from item's
|
||||
// "isActiveMatchOptions" or the equivalent form of
|
||||
// item's "exactMatch" option
|
||||
this.isActiveMatchOptions =
|
||||
this.item.isActiveMatchOptions ?? this.item.exactMatch
|
||||
? this._fuseUtilsService.exactMatchOptions
|
||||
: this._fuseUtilsService.subsetMatchOptions;
|
||||
|
||||
// Get the parent navigation component
|
||||
this._fuseHorizontalNavigationComponent = this._fuseNavigationService.getComponent(this.name);
|
||||
|
||||
// Mark for check
|
||||
this._changeDetectorRef.markForCheck();
|
||||
|
||||
// Subscribe to onRefreshed on the navigation component
|
||||
this._fuseHorizontalNavigationComponent.onRefreshed.pipe(
|
||||
takeUntil(this._unsubscribeAll)
|
||||
|
||||
@@ -46,7 +46,7 @@ export class FuseNavigationService
|
||||
*
|
||||
* @param name
|
||||
*/
|
||||
getComponent(name: string): any
|
||||
getComponent<T>(name: string): T
|
||||
{
|
||||
return this._componentRegistry.get(name);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { IsActiveMatchOptions } from '@angular/router';
|
||||
|
||||
export interface FuseNavigationItem
|
||||
{
|
||||
id?: string;
|
||||
@@ -16,6 +18,7 @@ export interface FuseNavigationItem
|
||||
link?: string;
|
||||
externalLink?: boolean;
|
||||
exactMatch?: boolean;
|
||||
isActiveMatchOptions?: IsActiveMatchOptions;
|
||||
function?: (item: FuseNavigationItem) => void;
|
||||
classes?: {
|
||||
title?: string;
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
[ngClass]="{'fuse-vertical-navigation-item-active-forced': item.active}"
|
||||
[routerLink]="[item.link]"
|
||||
[routerLinkActive]="'fuse-vertical-navigation-item-active'"
|
||||
[routerLinkActiveOptions]="{exact: item.exactMatch || false}">
|
||||
[routerLinkActiveOptions]="isActiveMatchOptions">
|
||||
<ng-container *ngTemplateOutlet="itemTemplate"></ng-container>
|
||||
</a>
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
[ngClass]="{'fuse-vertical-navigation-item-active-forced': item.active}"
|
||||
[routerLink]="[item.link]"
|
||||
[routerLinkActive]="'fuse-vertical-navigation-item-active'"
|
||||
[routerLinkActiveOptions]="{exact: item.exactMatch || false}"
|
||||
[routerLinkActiveOptions]="isActiveMatchOptions"
|
||||
(click)="item.function(item)">
|
||||
<ng-container *ngTemplateOutlet="itemTemplate"></ng-container>
|
||||
</a>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnDestroy, OnInit } from '@angular/core';
|
||||
import { IsActiveMatchOptions } from '@angular/router';
|
||||
import { Subject } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
import { FuseVerticalNavigationComponent } from '@fuse/components/navigation/vertical/vertical.component';
|
||||
@@ -17,6 +18,7 @@ export class FuseVerticalNavigationBasicItemComponent implements OnInit, OnDestr
|
||||
@Input() item: FuseNavigationItem;
|
||||
@Input() name: string;
|
||||
|
||||
isActiveMatchOptions: IsActiveMatchOptions;
|
||||
private _fuseVerticalNavigationComponent: FuseVerticalNavigationComponent;
|
||||
private _unsubscribeAll: Subject<any> = new Subject<any>();
|
||||
|
||||
@@ -29,6 +31,11 @@ export class FuseVerticalNavigationBasicItemComponent implements OnInit, OnDestr
|
||||
private _fuseUtilsService: FuseUtilsService
|
||||
)
|
||||
{
|
||||
// Set the equivalent of {exact: false} as default for active match options.
|
||||
// We are not assigning the item.isActiveMatchOptions directly to the
|
||||
// [routerLinkActiveOptions] because if it's "undefined" initially, the router
|
||||
// will throw an error and stop working.
|
||||
this.isActiveMatchOptions = this._fuseUtilsService.subsetMatchOptions;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
@@ -40,9 +47,20 @@ export class FuseVerticalNavigationBasicItemComponent implements OnInit, OnDestr
|
||||
*/
|
||||
ngOnInit(): void
|
||||
{
|
||||
// Set the "isActiveMatchOptions" either from item's
|
||||
// "isActiveMatchOptions" or the equivalent form of
|
||||
// item's "exactMatch" option
|
||||
this.isActiveMatchOptions =
|
||||
this.item.isActiveMatchOptions ?? this.item.exactMatch
|
||||
? this._fuseUtilsService.exactMatchOptions
|
||||
: this._fuseUtilsService.subsetMatchOptions;
|
||||
|
||||
// Get the parent navigation component
|
||||
this._fuseVerticalNavigationComponent = this._fuseNavigationService.getComponent(this.name);
|
||||
|
||||
// Mark for check
|
||||
this._changeDetectorRef.markForCheck();
|
||||
|
||||
// Subscribe to onRefreshed on the navigation component
|
||||
this._fuseVerticalNavigationComponent.onRefreshed.pipe(
|
||||
takeUntil(this._unsubscribeAll)
|
||||
|
||||
@@ -73,10 +73,10 @@ export class FuseVerticalNavigationComponent implements OnChanges, OnInit, After
|
||||
private _fuseUtilsService: FuseUtilsService
|
||||
)
|
||||
{
|
||||
this._handleAsideOverlayClick = () => {
|
||||
this._handleAsideOverlayClick = (): void => {
|
||||
this.closeAside();
|
||||
};
|
||||
this._handleOverlayClick = () => {
|
||||
this._handleOverlayClick = (): void => {
|
||||
this.close();
|
||||
};
|
||||
}
|
||||
|
||||
@@ -389,7 +389,7 @@ export class FuseScrollbarDirective implements OnChanges, OnInit, OnDestroy
|
||||
|
||||
const cosParameter = (oldValue - value) / 2;
|
||||
|
||||
const step = (newTimestamp: number) => {
|
||||
const step = (newTimestamp: number): void => {
|
||||
scrollCount += Math.PI / (speed / (newTimestamp - oldTimestamp));
|
||||
newValue = Math.round(value + cosParameter + cosParameter * Math.cos(scrollCount));
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ export class FuseMockApiInterceptor implements HttpInterceptor
|
||||
delay(handler.delay ?? this._defaultDelay ?? 0),
|
||||
switchMap((response) => {
|
||||
|
||||
// If there is no response mock-api,
|
||||
// If there is no response data,
|
||||
// throw an error response
|
||||
if ( !response )
|
||||
{
|
||||
@@ -64,7 +64,7 @@ export class FuseMockApiInterceptor implements HttpInterceptor
|
||||
return throwError(response);
|
||||
}
|
||||
|
||||
// Parse the response mock-api
|
||||
// Parse the response data
|
||||
const data = {
|
||||
status: response[0],
|
||||
body : response[1]
|
||||
|
||||
@@ -29,7 +29,7 @@ export class FuseMockApiModule
|
||||
{
|
||||
provide : APP_INITIALIZER,
|
||||
deps : [...mockApiServices],
|
||||
useFactory: () => () => null,
|
||||
useFactory: () => (): any => null,
|
||||
multi : true
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1 +1 @@
|
||||
export * from '@fuse/services/media-watcher/public-api';
|
||||
export * from '@fuse/services/tailwind/public-api';
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
export * from '@fuse/services/media-watcher/media-watcher.module';
|
||||
export * from '@fuse/services/media-watcher/media-watcher.service';
|
||||
export * from '@fuse/services/tailwind/tailwind.module';
|
||||
export * from '@fuse/services/tailwind/tailwind.service';
|
||||
|
||||
@@ -1 +1 @@
|
||||
export * from '@fuse/services/config/public-api';
|
||||
export * from '@fuse/services/utils/public-api';
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { IsActiveMatchOptions } from '@angular/router';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
@@ -12,6 +13,36 @@ export class FuseUtilsService
|
||||
{
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Accessors
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Get the equivalent "IsActiveMatchOptions" options for "exact = true".
|
||||
*/
|
||||
get exactMatchOptions(): IsActiveMatchOptions
|
||||
{
|
||||
return {
|
||||
paths : 'exact',
|
||||
fragment : 'ignored',
|
||||
matrixParams: 'ignored',
|
||||
queryParams : 'exact'
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the equivalent "IsActiveMatchOptions" options for "exact = false".
|
||||
*/
|
||||
get subsetMatchOptions(): IsActiveMatchOptions
|
||||
{
|
||||
return {
|
||||
paths : 'subset',
|
||||
fragment : 'ignored',
|
||||
matrixParams: 'ignored',
|
||||
queryParams : 'subset'
|
||||
};
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Public methods
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
@@ -178,11 +178,7 @@
|
||||
|
||||
/* Add hover and focus style on all buttons */
|
||||
.mat-button-focus-overlay {
|
||||
@apply bg-gray-400 bg-opacity-20 #{'!important'};
|
||||
|
||||
.dark & {
|
||||
background-color: rgba(0, 0, 0, 0.05) !important;
|
||||
}
|
||||
@apply bg-gray-400 bg-opacity-20 dark:bg-black dark:bg-opacity-5 #{'!important'};
|
||||
}
|
||||
|
||||
/* On palette colored buttons, use a darker color */
|
||||
@@ -253,11 +249,7 @@
|
||||
|
||||
/* Add hover and focus styles */
|
||||
.mat-button-focus-overlay {
|
||||
@apply bg-gray-400 bg-opacity-20 #{'!important'};
|
||||
|
||||
.dark & {
|
||||
background-color: rgba(0, 0, 0, 0.05) !important;
|
||||
}
|
||||
@apply bg-gray-400 bg-opacity-20 dark:bg-black dark:bg-opacity-5 #{'!important'};
|
||||
}
|
||||
|
||||
/* On primary colored buttons, use the primary color as focus overlay */
|
||||
@@ -330,19 +322,11 @@
|
||||
|
||||
/* Border color */
|
||||
&:not(.mat-button-disabled) {
|
||||
@apply border-gray-300 #{'!important'};
|
||||
|
||||
.dark & {
|
||||
@apply border-gray-500 #{'!important'};
|
||||
}
|
||||
@apply border-gray-300 dark:border-gray-500 #{'!important'};
|
||||
}
|
||||
|
||||
&.mat-button-disabled {
|
||||
@apply border-gray-200 #{'!important'};
|
||||
|
||||
.dark & {
|
||||
@apply border-gray-600 #{'!important'};
|
||||
}
|
||||
@apply border-gray-200 dark:border-gray-600 #{'!important'};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -542,13 +526,7 @@
|
||||
border-radius: 6px;
|
||||
padding: 0 16px;
|
||||
border-width: 1px;
|
||||
background-color: white;
|
||||
@apply border-gray-300 shadow-sm #{'!important'};
|
||||
|
||||
.dark & {
|
||||
background-color: rgba(0, 0, 0, 0.05) !important;
|
||||
@apply border-gray-500 #{'!important'};
|
||||
}
|
||||
@apply shadow-sm bg-white border-gray-300 dark:bg-black dark:bg-opacity-5 dark:border-gray-500 #{'!important'};
|
||||
|
||||
.mat-form-field-prefix {
|
||||
|
||||
@@ -633,12 +611,28 @@
|
||||
@apply icon-size-6;
|
||||
}
|
||||
|
||||
/* Make mat-select usable as */
|
||||
/* prefix and suffix */
|
||||
/* Make mat-select usable as prefix and suffix */
|
||||
.mat-select {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&:focus {
|
||||
|
||||
.mat-select-trigger {
|
||||
|
||||
.mat-select-value {
|
||||
@apply text-primary #{'!important'};
|
||||
}
|
||||
|
||||
.mat-select-arrow-wrapper {
|
||||
|
||||
.mat-select-arrow {
|
||||
border-top-color: var(--fuse-primary) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mat-select-trigger {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -663,6 +657,7 @@
|
||||
|
||||
.mat-select-arrow {
|
||||
min-height: 0;
|
||||
@apply text-gray-500 dark:text-gray-400 #{'!important'};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1032,11 +1027,7 @@
|
||||
|
||||
.mat-form-field-prefix,
|
||||
.mat-form-field-suffix {
|
||||
@apply border-gray-300 bg-default #{'!important'};
|
||||
|
||||
.dark & {
|
||||
@apply border-gray-500 #{'!important'};
|
||||
}
|
||||
@apply bg-default border-gray-300 dark:border-gray-500 #{'!important'};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
export * from './public-api';
|
||||
export * from '@fuse/validators/public-api';
|
||||
|
||||
@@ -1 +1 @@
|
||||
export * from './validators';
|
||||
export * from '@fuse/validators/validators';
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
import { Version } from '@fuse/version/version';
|
||||
|
||||
export const FUSE_VERSION = new Version('13.0.0').full;
|
||||
export const FUSE_VERSION = new Version('13.0.2').full;
|
||||
|
||||
@@ -27,18 +27,18 @@ const routerConfig: ExtraOptions = {
|
||||
BrowserAnimationsModule,
|
||||
RouterModule.forRoot(appRoutes, routerConfig),
|
||||
|
||||
// Fuse & Fuse Mock API
|
||||
// Fuse, FuseConfig & FuseMockAPI
|
||||
FuseModule,
|
||||
FuseConfigModule.forRoot(appConfig),
|
||||
FuseMockApiModule.forRoot(mockApiServices),
|
||||
|
||||
// Core
|
||||
// Core module of your application
|
||||
CoreModule,
|
||||
|
||||
// Layout
|
||||
// Layout module of your application
|
||||
LayoutModule,
|
||||
|
||||
// 3rd party modules
|
||||
// 3rd party modules that require global configuration via forRoot
|
||||
MarkdownModule.forRoot({})
|
||||
],
|
||||
bootstrap : [
|
||||
|
||||
@@ -5,18 +5,18 @@ import { LayoutComponent } from 'app/layout/layout.component';
|
||||
import { InitialDataResolver } from 'app/app.resolvers';
|
||||
|
||||
// @formatter:off
|
||||
/* eslint-disable max-len */
|
||||
// tslint:disable:max-line-length
|
||||
export const appRoutes: Route[] = [
|
||||
|
||||
// Redirect empty path to '/dashboards/project'
|
||||
{path: '', pathMatch : 'full', redirectTo: 'dashboards/project'},
|
||||
// Redirect empty path to '/example'
|
||||
{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'
|
||||
// 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.
|
||||
{path: 'signed-in-redirect', pathMatch : 'full', redirectTo: 'dashboards/project'},
|
||||
{path: 'signed-in-redirect', pathMatch : 'full', redirectTo: 'example'},
|
||||
|
||||
// Auth routes for guests
|
||||
{
|
||||
@@ -73,125 +73,7 @@ export const appRoutes: Route[] = [
|
||||
initialData: InitialDataResolver,
|
||||
},
|
||||
children : [
|
||||
|
||||
// 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)},
|
||||
]},
|
||||
|
||||
// Apps
|
||||
{path: 'apps', children: [
|
||||
{path: 'academy', loadChildren: () => import('app/modules/admin/apps/academy/academy.module').then(m => m.AcademyModule)},
|
||||
{path: 'calendar', loadChildren: () => import('app/modules/admin/apps/calendar/calendar.module').then(m => m.CalendarModule)},
|
||||
{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: 'tasks', loadChildren: () => import('app/modules/admin/apps/tasks/tasks.module').then(m => m.TasksModule)},
|
||||
]},
|
||||
|
||||
// Pages
|
||||
{path: 'pages', children: [
|
||||
|
||||
// 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: [
|
||||
|
||||
// Angular Material
|
||||
{path: 'angular-material', loadChildren: () => import('app/modules/admin/ui/angular-material/angular-material.module').then(m => m.AngularMaterialModule)},
|
||||
|
||||
// TailwindCSS
|
||||
{path: 'tailwindcss', loadChildren: () => import('app/modules/admin/ui/tailwindcss/tailwindcss.module').then(m => m.TailwindCSSModule)},
|
||||
|
||||
// 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)},
|
||||
|
||||
// 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)},
|
||||
|
||||
// Core features
|
||||
{path: 'core-features', loadChildren: () => import('app/modules/admin/docs/core-features/core-features.module').then(m => m.CoreFeaturesModule)},
|
||||
|
||||
// Other components
|
||||
{path: 'other-components', loadChildren: () => import('app/modules/admin/docs/other-components/other-components.module').then(m => m.OtherComponentsModule)},
|
||||
]},
|
||||
|
||||
// 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'}
|
||||
{path: 'example', loadChildren: () => import('app/modules/admin/example/example.module').then(m => m.ExampleModule)},
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
21
src/app/core/auth/auth.module.ts
Normal file
21
src/app/core/auth/auth.module.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http';
|
||||
import { AuthService } from 'app/core/auth/auth.service';
|
||||
import { AuthInterceptor } from 'app/core/auth/auth.interceptor';
|
||||
|
||||
@NgModule({
|
||||
imports : [
|
||||
HttpClientModule
|
||||
],
|
||||
providers: [
|
||||
AuthService,
|
||||
{
|
||||
provide : HTTP_INTERCEPTORS,
|
||||
useClass: AuthInterceptor,
|
||||
multi : true
|
||||
}
|
||||
]
|
||||
})
|
||||
export class AuthModule
|
||||
{
|
||||
}
|
||||
@@ -1,21 +1,13 @@
|
||||
import { NgModule, Optional, SkipSelf } from '@angular/core';
|
||||
import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http';
|
||||
import { DomSanitizer } from '@angular/platform-browser';
|
||||
import { MatIconRegistry } from '@angular/material/icon';
|
||||
import { AuthService } from 'app/core/auth/auth.service';
|
||||
import { AuthInterceptor } from 'app/core/auth/auth.interceptor';
|
||||
import { AuthModule } from 'app/core/auth/auth.module';
|
||||
import { IconsModule } from 'app/core/icons/icons.module';
|
||||
import { TranslocoCoreModule } from 'app/core/transloco/transloco.module';
|
||||
|
||||
@NgModule({
|
||||
imports : [
|
||||
HttpClientModule
|
||||
],
|
||||
providers: [
|
||||
AuthService,
|
||||
{
|
||||
provide : HTTP_INTERCEPTORS,
|
||||
useClass: AuthInterceptor,
|
||||
multi : true
|
||||
}
|
||||
imports: [
|
||||
AuthModule,
|
||||
IconsModule,
|
||||
TranslocoCoreModule
|
||||
]
|
||||
})
|
||||
export class CoreModule
|
||||
@@ -24,8 +16,6 @@ export class CoreModule
|
||||
* Constructor
|
||||
*/
|
||||
constructor(
|
||||
private _domSanitizer: DomSanitizer,
|
||||
private _matIconRegistry: MatIconRegistry,
|
||||
@Optional() @SkipSelf() parentModule?: CoreModule
|
||||
)
|
||||
{
|
||||
@@ -34,14 +24,5 @@ export class CoreModule
|
||||
{
|
||||
throw new Error('CoreModule has already been loaded. Import this module in the AppModule only.');
|
||||
}
|
||||
|
||||
// Register icon sets
|
||||
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_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('heroicons_outline', this._domSanitizer.bypassSecurityTrustResourceUrl('assets/icons/heroicons-outline.svg'));
|
||||
this._matIconRegistry.addSvgIconSetInNamespace('heroicons_solid', this._domSanitizer.bypassSecurityTrustResourceUrl('assets/icons/heroicons-solid.svg'));
|
||||
}
|
||||
}
|
||||
|
||||
25
src/app/core/icons/icons.module.ts
Normal file
25
src/app/core/icons/icons.module.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { DomSanitizer } from '@angular/platform-browser';
|
||||
import { MatIconRegistry } from '@angular/material/icon';
|
||||
|
||||
@NgModule()
|
||||
export class IconsModule
|
||||
{
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
constructor(
|
||||
private _domSanitizer: DomSanitizer,
|
||||
private _matIconRegistry: MatIconRegistry
|
||||
)
|
||||
{
|
||||
// Register icon sets
|
||||
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_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('heroicons_outline', this._domSanitizer.bypassSecurityTrustResourceUrl('assets/icons/heroicons-outline.svg'));
|
||||
this._matIconRegistry.addSvgIconSetInNamespace('heroicons_solid', this._domSanitizer.bypassSecurityTrustResourceUrl('assets/icons/heroicons-solid.svg'));
|
||||
}
|
||||
}
|
||||
32
src/app/core/transloco/transloco.http-loader.ts
Normal file
32
src/app/core/transloco/transloco.http-loader.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Observable } from 'rxjs';
|
||||
import { Translation, TranslocoLoader } from '@ngneat/transloco';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class TranslocoHttpLoader implements TranslocoLoader
|
||||
{
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
constructor(
|
||||
private _httpClient: HttpClient)
|
||||
{
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Public methods
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Get translation
|
||||
*
|
||||
* @param lang
|
||||
*/
|
||||
getTranslation(lang: string): Observable<Translation>
|
||||
{
|
||||
return this._httpClient.get<Translation>(`/assets/i18n/${lang}.json`);
|
||||
}
|
||||
}
|
||||
50
src/app/core/transloco/transloco.module.ts
Normal file
50
src/app/core/transloco/transloco.module.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { Translation, TRANSLOCO_CONFIG, TRANSLOCO_LOADER, translocoConfig, TranslocoModule, TranslocoService } from '@ngneat/transloco';
|
||||
import { APP_INITIALIZER, NgModule } from '@angular/core';
|
||||
import { environment } from 'environments/environment';
|
||||
import { TranslocoHttpLoader } from 'app/core/transloco/transloco.http-loader';
|
||||
|
||||
@NgModule({
|
||||
exports : [
|
||||
TranslocoModule
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
// Provide the default Transloco configuration
|
||||
provide : TRANSLOCO_CONFIG,
|
||||
useValue: translocoConfig({
|
||||
availableLangs : [
|
||||
{
|
||||
id : 'en',
|
||||
label: 'English'
|
||||
},
|
||||
{
|
||||
id : 'tr',
|
||||
label: 'Turkish'
|
||||
}
|
||||
],
|
||||
defaultLang : 'en',
|
||||
reRenderOnLangChange: true,
|
||||
prodMode : environment.production
|
||||
})
|
||||
},
|
||||
{
|
||||
// Provide the default Transloco loader
|
||||
provide : TRANSLOCO_LOADER,
|
||||
useClass: TranslocoHttpLoader
|
||||
},
|
||||
{
|
||||
// Preload the default language before the app starts to prevent empty/jumping content
|
||||
provide : APP_INITIALIZER,
|
||||
deps : [TranslocoService],
|
||||
useFactory: (translocoService: TranslocoService): any => (): Promise<Translation> => {
|
||||
const defaultLang = translocoService.getDefaultLang();
|
||||
translocoService.setActiveLang(defaultLang);
|
||||
return translocoService.load(defaultLang).toPromise();
|
||||
},
|
||||
multi : true
|
||||
}
|
||||
]
|
||||
})
|
||||
export class TranslocoCoreModule
|
||||
{
|
||||
}
|
||||
35
src/app/layout/common/language/language.component.html
Normal file
35
src/app/layout/common/language/language.component.html
Normal file
@@ -0,0 +1,35 @@
|
||||
<!-- Button -->
|
||||
<button
|
||||
mat-icon-button
|
||||
[matMenuTriggerFor]="languages">
|
||||
<ng-container *ngTemplateOutlet="flagImage; context: {$implicit: activeLang}"></ng-container>
|
||||
</button>
|
||||
|
||||
<!-- Language menu -->
|
||||
<mat-menu
|
||||
[xPosition]="'before'"
|
||||
#languages="matMenu">
|
||||
<ng-container *ngFor="let lang of availableLangs; trackBy: trackByFn">
|
||||
<button
|
||||
mat-menu-item
|
||||
(click)="setActiveLang(lang.id)">
|
||||
<span class="flex items-center">
|
||||
<ng-container *ngTemplateOutlet="flagImage; context: {$implicit: lang.id}"></ng-container>
|
||||
<span class="ml-3">{{lang.label}}</span>
|
||||
</span>
|
||||
</button>
|
||||
</ng-container>
|
||||
</mat-menu>
|
||||
|
||||
<!-- Flag image template -->
|
||||
<ng-template
|
||||
let-lang
|
||||
#flagImage>
|
||||
<span class="relative w-6 shadow rounded-sm overflow-hidden">
|
||||
<span class="absolute inset-0 ring-1 ring-inset ring-black ring-opacity-10"></span>
|
||||
<img
|
||||
class="w-full"
|
||||
[src]="'assets/images/flags/' + flagCodes[lang].toUpperCase() + '.svg'"
|
||||
[alt]="'Flag image for ' + lang">
|
||||
</span>
|
||||
</ng-template>
|
||||
153
src/app/layout/common/language/language.component.ts
Normal file
153
src/app/layout/common/language/language.component.ts
Normal file
@@ -0,0 +1,153 @@
|
||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';
|
||||
import { take } from 'rxjs/operators';
|
||||
import { AvailableLangs, TranslocoService } from '@ngneat/transloco';
|
||||
import { FuseNavigationService, FuseVerticalNavigationComponent } from '@fuse/components/navigation';
|
||||
|
||||
@Component({
|
||||
selector : 'language',
|
||||
templateUrl : './language.component.html',
|
||||
encapsulation : ViewEncapsulation.None,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
exportAs : 'language'
|
||||
})
|
||||
export class LanguageComponent implements OnInit, OnDestroy
|
||||
{
|
||||
availableLangs: AvailableLangs;
|
||||
activeLang: string;
|
||||
flagCodes: any;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
constructor(
|
||||
private _changeDetectorRef: ChangeDetectorRef,
|
||||
private _fuseNavigationService: FuseNavigationService,
|
||||
private _translocoService: TranslocoService
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Lifecycle hooks
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* On init
|
||||
*/
|
||||
ngOnInit(): void
|
||||
{
|
||||
// Get the available languages from transloco
|
||||
this.availableLangs = this._translocoService.getAvailableLangs();
|
||||
|
||||
// Subscribe to language changes
|
||||
this._translocoService.langChanges$.subscribe((activeLang) => {
|
||||
|
||||
// Get the active lang
|
||||
this.activeLang = activeLang;
|
||||
|
||||
// Update the navigation
|
||||
this._updateNavigation(activeLang);
|
||||
});
|
||||
|
||||
// Set the country iso codes for languages for flags
|
||||
this.flagCodes = {
|
||||
'en': 'us',
|
||||
'tr': 'tr'
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* On destroy
|
||||
*/
|
||||
ngOnDestroy(): void
|
||||
{
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Public methods
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Set the active lang
|
||||
*
|
||||
* @param lang
|
||||
*/
|
||||
setActiveLang(lang: string): void
|
||||
{
|
||||
// Set the active lang
|
||||
this._translocoService.setActiveLang(lang);
|
||||
}
|
||||
|
||||
/**
|
||||
* Track by function for ngFor loops
|
||||
*
|
||||
* @param index
|
||||
* @param item
|
||||
*/
|
||||
trackByFn(index: number, item: any): any
|
||||
{
|
||||
return item.id || index;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Private methods
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Update the navigation
|
||||
*
|
||||
* @param lang
|
||||
* @private
|
||||
*/
|
||||
private _updateNavigation(lang: string): void
|
||||
{
|
||||
// For the demonstration purposes, we will only update the Dashboard names
|
||||
// from the navigation but you can do a full swap and change the entire
|
||||
// navigation data.
|
||||
//
|
||||
// You can import the data from a file or request it from your backend,
|
||||
// it's up to you.
|
||||
|
||||
// Get the component -> navigation data -> item
|
||||
const navComponent = this._fuseNavigationService.getComponent<FuseVerticalNavigationComponent>('mainNavigation');
|
||||
|
||||
// Return if the navigation component does not exist
|
||||
if ( !navComponent )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get the flat navigation data
|
||||
const navigation = navComponent.navigation;
|
||||
|
||||
// Get the Project dashboard item and update its title
|
||||
const projectDashboardItem = this._fuseNavigationService.getItem('dashboards.project', navigation);
|
||||
if ( projectDashboardItem )
|
||||
{
|
||||
this._translocoService.selectTranslate('Project').pipe(take(1))
|
||||
.subscribe((translation) => {
|
||||
|
||||
// Set the title
|
||||
projectDashboardItem.title = translation;
|
||||
|
||||
// Refresh the navigation component
|
||||
navComponent.refresh();
|
||||
});
|
||||
}
|
||||
|
||||
// Get the Analytics dashboard item and update its title
|
||||
const analyticsDashboardItem = this._fuseNavigationService.getItem('dashboards.analytics', navigation);
|
||||
if ( analyticsDashboardItem )
|
||||
{
|
||||
this._translocoService.selectTranslate('Analytics').pipe(take(1))
|
||||
.subscribe((translation) => {
|
||||
|
||||
// Set the title
|
||||
analyticsDashboardItem.title = translation;
|
||||
|
||||
// Refresh the navigation component
|
||||
navComponent.refresh();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
24
src/app/layout/common/language/language.module.ts
Normal file
24
src/app/layout/common/language/language.module.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatMenuModule } from '@angular/material/menu';
|
||||
import { LanguageComponent } from 'app/layout/common/language/language.component';
|
||||
import { SharedModule } from 'app/shared/shared.module';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
LanguageComponent
|
||||
],
|
||||
imports : [
|
||||
MatButtonModule,
|
||||
MatIconModule,
|
||||
MatMenuModule,
|
||||
SharedModule
|
||||
],
|
||||
exports : [
|
||||
LanguageComponent
|
||||
]
|
||||
})
|
||||
export class LanguageModule
|
||||
{
|
||||
}
|
||||
@@ -46,7 +46,7 @@
|
||||
<!-- Content -->
|
||||
<div class="relative flex flex-col flex-auto sm:max-h-120 divide-y overflow-y-auto bg-card">
|
||||
<!-- Messages -->
|
||||
<ng-container *ngFor="let message of messages">
|
||||
<ng-container *ngFor="let message of messages; trackBy: trackByFn">
|
||||
<div
|
||||
class="flex group hover:bg-gray-50 dark:hover:bg-black dark:hover:bg-opacity-5"
|
||||
[ngClass]="{'unread': !message.read}">
|
||||
|
||||
@@ -155,6 +155,17 @@ export class MessagesComponent implements OnInit, OnChanges, OnDestroy
|
||||
this._messagesService.delete(message.id).subscribe();
|
||||
}
|
||||
|
||||
/**
|
||||
* Track by function for ngFor loops
|
||||
*
|
||||
* @param index
|
||||
* @param item
|
||||
*/
|
||||
trackByFn(index: number, item: any): any
|
||||
{
|
||||
return item.id || index;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Private methods
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
<!-- Content -->
|
||||
<div class="relative flex flex-col flex-auto sm:max-h-120 divide-y overflow-y-auto bg-card">
|
||||
<!-- Notifications -->
|
||||
<ng-container *ngFor="let notification of notifications">
|
||||
<ng-container *ngFor="let notification of notifications; trackBy: trackByFn">
|
||||
<div
|
||||
class="flex group hover:bg-gray-50 dark:hover:bg-black dark:hover:bg-opacity-5"
|
||||
[ngClass]="{'unread': !notification.read}">
|
||||
|
||||
@@ -155,6 +155,17 @@ export class NotificationsComponent implements OnChanges, OnInit, OnDestroy
|
||||
this._notificationsService.delete(notification.id).subscribe();
|
||||
}
|
||||
|
||||
/**
|
||||
* Track by function for ngFor loops
|
||||
*
|
||||
* @param index
|
||||
* @param item
|
||||
*/
|
||||
trackByFn(index: number, item: any): any
|
||||
{
|
||||
return item.id || index;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Private methods
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
*ngIf="results && !results.length">
|
||||
No results found!
|
||||
</mat-option>
|
||||
<ng-container *ngFor="let result of results">
|
||||
<ng-container *ngFor="let result of results; trackBy: trackByFn">
|
||||
<mat-option
|
||||
class="group relative h-14 px-6 py-0 sm:px-8 text-md"
|
||||
[routerLink]="result.link">
|
||||
@@ -73,7 +73,7 @@
|
||||
*ngIf="results && !results.length">
|
||||
No results found!
|
||||
</mat-option>
|
||||
<ng-container *ngFor="let result of results">
|
||||
<ng-container *ngFor="let result of results; trackBy: trackByFn">
|
||||
<mat-option
|
||||
class="group relative h-14 px-5 py-0 text-md"
|
||||
[routerLink]="result.link">
|
||||
|
||||
@@ -200,4 +200,15 @@ export class SearchComponent implements OnChanges, OnInit, OnDestroy
|
||||
// Close the search
|
||||
this.opened = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Track by function for ngFor loops
|
||||
*
|
||||
* @param index
|
||||
* @param item
|
||||
*/
|
||||
trackByFn(index: number, item: any): any
|
||||
{
|
||||
return item.id || index;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { Overlay } from '@angular/cdk/overlay';
|
||||
import { BlockScrollStrategy, Overlay } from '@angular/cdk/overlay';
|
||||
import { MAT_AUTOCOMPLETE_SCROLL_STRATEGY, MatAutocompleteModule } from '@angular/material/autocomplete';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
@@ -28,7 +28,7 @@ import { SearchComponent } from 'app/layout/common/search/search.component';
|
||||
providers : [
|
||||
{
|
||||
provide : MAT_AUTOCOMPLETE_SCROLL_STRATEGY,
|
||||
useFactory: (overlay: Overlay) => () => overlay.scrollStrategies.block(),
|
||||
useFactory: (overlay: Overlay) => (): BlockScrollStrategy => overlay.scrollStrategies.block(),
|
||||
deps : [Overlay]
|
||||
}
|
||||
]
|
||||
|
||||
@@ -94,7 +94,7 @@
|
||||
<!-- Shortcuts -->
|
||||
<div class="grid grid-cols-2 grid-flow-row">
|
||||
<!-- Shortcut -->
|
||||
<ng-container *ngFor="let shortcut of shortcuts">
|
||||
<ng-container *ngFor="let shortcut of shortcuts; trackBy: trackByFn">
|
||||
<div class="relative group flex flex-col overflow-hidden bg-card border-r border-b even:border-r-0 hover:bg-gray-50 dark:hover:bg-black dark:hover:bg-opacity-5">
|
||||
<ng-container *ngIf="mode === 'modify'">
|
||||
<div
|
||||
|
||||
@@ -176,7 +176,7 @@ export class ShortcutsComponent implements OnChanges, OnInit, OnDestroy
|
||||
*/
|
||||
save(): void
|
||||
{
|
||||
// Get the mock-api from the form
|
||||
// Get the data from the form
|
||||
const shortcut = this.shortcutForm.value;
|
||||
|
||||
// If there is an id, update it...
|
||||
@@ -199,7 +199,7 @@ export class ShortcutsComponent implements OnChanges, OnInit, OnDestroy
|
||||
*/
|
||||
delete(): void
|
||||
{
|
||||
// Get the mock-api from the form
|
||||
// Get the data from the form
|
||||
const shortcut = this.shortcutForm.value;
|
||||
|
||||
// Delete
|
||||
@@ -209,6 +209,17 @@ export class ShortcutsComponent implements OnChanges, OnInit, OnDestroy
|
||||
this.mode = 'modify';
|
||||
}
|
||||
|
||||
/**
|
||||
* Track by function for ngFor loops
|
||||
*
|
||||
* @param index
|
||||
* @param item
|
||||
*/
|
||||
trackByFn(index: number, item: any): any
|
||||
{
|
||||
return item.id || index;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Private methods
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
@@ -217,7 +217,7 @@ export class LayoutComponent implements OnInit, OnDestroy
|
||||
const paths = route.pathFromRoot;
|
||||
paths.forEach((path) => {
|
||||
|
||||
// Check if there is a 'layout' mock-api
|
||||
// Check if there is a 'layout' data
|
||||
if ( path.routeConfig && path.routeConfig.data && path.routeConfig.data.layout )
|
||||
{
|
||||
// Set the layout
|
||||
|
||||
@@ -63,6 +63,7 @@
|
||||
</ng-container>
|
||||
<!-- Components -->
|
||||
<div class="flex items-center pl-2 ml-auto space-x-2">
|
||||
<language></language>
|
||||
<fuse-fullscreen></fuse-fullscreen>
|
||||
<search [appearance]="'bar'"></search>
|
||||
<shortcuts [shortcuts]="data.shortcuts"></shortcuts>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { ActivatedRoute, Data, Router } from '@angular/router';
|
||||
import { Subject } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
import { FuseMediaWatcherService } from '@fuse/services/media-watcher';
|
||||
import { FuseNavigationService } from '@fuse/components/navigation';
|
||||
import { FuseNavigationService, FuseVerticalNavigationComponent } from '@fuse/components/navigation';
|
||||
import { InitialData } from 'app/app.types';
|
||||
|
||||
@Component({
|
||||
@@ -50,7 +50,7 @@ export class CenteredLayoutComponent implements OnInit, OnDestroy
|
||||
*/
|
||||
ngOnInit(): void
|
||||
{
|
||||
// Subscribe to the resolved route mock-api
|
||||
// Subscribe to the resolved route data
|
||||
this._activatedRoute.data.subscribe((data: Data) => {
|
||||
this.data = data.initialData;
|
||||
});
|
||||
@@ -87,7 +87,7 @@ export class CenteredLayoutComponent implements OnInit, OnDestroy
|
||||
toggleNavigation(name: string): void
|
||||
{
|
||||
// Get the navigation
|
||||
const navigation = this._fuseNavigationService.getComponent(name);
|
||||
const navigation = this._fuseNavigationService.getComponent<FuseVerticalNavigationComponent>(name);
|
||||
|
||||
if ( navigation )
|
||||
{
|
||||
|
||||
@@ -7,6 +7,7 @@ import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatMenuModule } from '@angular/material/menu';
|
||||
import { FuseFullscreenModule } from '@fuse/components/fullscreen';
|
||||
import { FuseNavigationModule } from '@fuse/components/navigation';
|
||||
import { LanguageModule } from 'app/layout/common/language/language.module';
|
||||
import { MessagesModule } from 'app/layout/common/messages/messages.module';
|
||||
import { NotificationsModule } from 'app/layout/common/notifications/notifications.module';
|
||||
import { SearchModule } from 'app/layout/common/search/search.module';
|
||||
@@ -28,6 +29,7 @@ import { CenteredLayoutComponent } from 'app/layout/layouts/horizontal/centered/
|
||||
MatMenuModule,
|
||||
FuseFullscreenModule,
|
||||
FuseNavigationModule,
|
||||
LanguageModule,
|
||||
MessagesModule,
|
||||
NotificationsModule,
|
||||
SearchModule,
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
</ng-container>
|
||||
<!-- Components -->
|
||||
<div class="flex items-center pl-2 ml-auto space-x-2">
|
||||
<language></language>
|
||||
<fuse-fullscreen></fuse-fullscreen>
|
||||
<search [appearance]="'bar'"></search>
|
||||
<shortcuts [shortcuts]="data.shortcuts"></shortcuts>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { ActivatedRoute, Data, Router } from '@angular/router';
|
||||
import { Subject } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
import { FuseMediaWatcherService } from '@fuse/services/media-watcher';
|
||||
import { FuseNavigationService } from '@fuse/components/navigation';
|
||||
import { FuseNavigationService, FuseVerticalNavigationComponent } from '@fuse/components/navigation';
|
||||
import { InitialData } from 'app/app.types';
|
||||
|
||||
@Component({
|
||||
@@ -50,7 +50,7 @@ export class EnterpriseLayoutComponent implements OnInit, OnDestroy
|
||||
*/
|
||||
ngOnInit(): void
|
||||
{
|
||||
// Subscribe to the resolved route mock-api
|
||||
// Subscribe to the resolved route data
|
||||
this._activatedRoute.data.subscribe((data: Data) => {
|
||||
this.data = data.initialData;
|
||||
});
|
||||
@@ -87,7 +87,7 @@ export class EnterpriseLayoutComponent implements OnInit, OnDestroy
|
||||
toggleNavigation(name: string): void
|
||||
{
|
||||
// Get the navigation
|
||||
const navigation = this._fuseNavigationService.getComponent(name);
|
||||
const navigation = this._fuseNavigationService.getComponent<FuseVerticalNavigationComponent>(name);
|
||||
|
||||
if ( navigation )
|
||||
{
|
||||
|
||||
@@ -7,6 +7,7 @@ import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatMenuModule } from '@angular/material/menu';
|
||||
import { FuseFullscreenModule } from '@fuse/components/fullscreen';
|
||||
import { FuseNavigationModule } from '@fuse/components/navigation';
|
||||
import { LanguageModule } from 'app/layout/common/language/language.module';
|
||||
import { MessagesModule } from 'app/layout/common/messages/messages.module';
|
||||
import { NotificationsModule } from 'app/layout/common/notifications/notifications.module';
|
||||
import { SearchModule } from 'app/layout/common/search/search.module';
|
||||
@@ -28,6 +29,7 @@ import { EnterpriseLayoutComponent } from 'app/layout/layouts/horizontal/enterpr
|
||||
MatMenuModule,
|
||||
FuseFullscreenModule,
|
||||
FuseNavigationModule,
|
||||
LanguageModule,
|
||||
MessagesModule,
|
||||
NotificationsModule,
|
||||
SearchModule,
|
||||
|
||||
@@ -52,6 +52,7 @@
|
||||
</ng-container>
|
||||
<!-- Components -->
|
||||
<div class="flex items-center pl-2 ml-auto space-x-2">
|
||||
<language></language>
|
||||
<fuse-fullscreen></fuse-fullscreen>
|
||||
<search [appearance]="'bar'"></search>
|
||||
<shortcuts [shortcuts]="data.shortcuts"></shortcuts>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { ActivatedRoute, Data, Router } from '@angular/router';
|
||||
import { Subject } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
import { FuseMediaWatcherService } from '@fuse/services/media-watcher';
|
||||
import { FuseNavigationService } from '@fuse/components/navigation';
|
||||
import { FuseNavigationService, FuseVerticalNavigationComponent } from '@fuse/components/navigation';
|
||||
import { InitialData } from 'app/app.types';
|
||||
|
||||
@Component({
|
||||
@@ -50,7 +50,7 @@ export class MaterialLayoutComponent implements OnInit, OnDestroy
|
||||
*/
|
||||
ngOnInit(): void
|
||||
{
|
||||
// Subscribe to the resolved route mock-api
|
||||
// Subscribe to the resolved route data
|
||||
this._activatedRoute.data.subscribe((data: Data) => {
|
||||
this.data = data.initialData;
|
||||
});
|
||||
@@ -87,7 +87,7 @@ export class MaterialLayoutComponent implements OnInit, OnDestroy
|
||||
toggleNavigation(name: string): void
|
||||
{
|
||||
// Get the navigation
|
||||
const navigation = this._fuseNavigationService.getComponent(name);
|
||||
const navigation = this._fuseNavigationService.getComponent<FuseVerticalNavigationComponent>(name);
|
||||
|
||||
if ( navigation )
|
||||
{
|
||||
|
||||
@@ -7,6 +7,7 @@ import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatMenuModule } from '@angular/material/menu';
|
||||
import { FuseFullscreenModule } from '@fuse/components/fullscreen';
|
||||
import { FuseNavigationModule } from '@fuse/components/navigation';
|
||||
import { LanguageModule } from 'app/layout/common/language/language.module';
|
||||
import { MessagesModule } from 'app/layout/common/messages/messages.module';
|
||||
import { NotificationsModule } from 'app/layout/common/notifications/notifications.module';
|
||||
import { SearchModule } from 'app/layout/common/search/search.module';
|
||||
@@ -28,6 +29,7 @@ import { MaterialLayoutComponent } from 'app/layout/layouts/horizontal/material/
|
||||
MatMenuModule,
|
||||
FuseFullscreenModule,
|
||||
FuseNavigationModule,
|
||||
LanguageModule,
|
||||
MessagesModule,
|
||||
NotificationsModule,
|
||||
SearchModule,
|
||||
|
||||
@@ -55,6 +55,7 @@
|
||||
</ng-container>
|
||||
<!-- Components -->
|
||||
<div class="flex items-center pl-2 ml-auto space-x-2">
|
||||
<language></language>
|
||||
<fuse-fullscreen></fuse-fullscreen>
|
||||
<search [appearance]="'bar'"></search>
|
||||
<shortcuts [shortcuts]="data.shortcuts"></shortcuts>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { ActivatedRoute, Data, Router } from '@angular/router';
|
||||
import { Subject } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
import { FuseMediaWatcherService } from '@fuse/services/media-watcher';
|
||||
import { FuseNavigationService } from '@fuse/components/navigation';
|
||||
import { FuseNavigationService, FuseVerticalNavigationComponent } from '@fuse/components/navigation';
|
||||
import { InitialData } from 'app/app.types';
|
||||
|
||||
@Component({
|
||||
@@ -50,7 +50,7 @@ export class ModernLayoutComponent implements OnInit, OnDestroy
|
||||
*/
|
||||
ngOnInit(): void
|
||||
{
|
||||
// Subscribe to the resolved route mock-api
|
||||
// Subscribe to the resolved route data
|
||||
this._activatedRoute.data.subscribe((data: Data) => {
|
||||
this.data = data.initialData;
|
||||
});
|
||||
@@ -87,7 +87,7 @@ export class ModernLayoutComponent implements OnInit, OnDestroy
|
||||
toggleNavigation(name: string): void
|
||||
{
|
||||
// Get the navigation
|
||||
const navigation = this._fuseNavigationService.getComponent(name);
|
||||
const navigation = this._fuseNavigationService.getComponent<FuseVerticalNavigationComponent>(name);
|
||||
|
||||
if ( navigation )
|
||||
{
|
||||
|
||||
@@ -7,6 +7,7 @@ import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatMenuModule } from '@angular/material/menu';
|
||||
import { FuseFullscreenModule } from '@fuse/components/fullscreen';
|
||||
import { FuseNavigationModule } from '@fuse/components/navigation';
|
||||
import { LanguageModule } from 'app/layout/common/language/language.module';
|
||||
import { MessagesModule } from 'app/layout/common/messages/messages.module';
|
||||
import { NotificationsModule } from 'app/layout/common/notifications/notifications.module';
|
||||
import { SearchModule } from 'app/layout/common/search/search.module';
|
||||
@@ -28,6 +29,7 @@ import { ModernLayoutComponent } from 'app/layout/layouts/horizontal/modern/mode
|
||||
MatMenuModule,
|
||||
FuseFullscreenModule,
|
||||
FuseNavigationModule,
|
||||
LanguageModule,
|
||||
MessagesModule,
|
||||
NotificationsModule,
|
||||
SearchModule,
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
</button>
|
||||
<!-- Components -->
|
||||
<div class="flex items-center pl-2 ml-auto space-x-2">
|
||||
<language></language>
|
||||
<fuse-fullscreen></fuse-fullscreen>
|
||||
<search [appearance]="'bar'"></search>
|
||||
<shortcuts [shortcuts]="data.shortcuts"></shortcuts>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { ActivatedRoute, Data, Router } from '@angular/router';
|
||||
import { Subject } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
import { FuseMediaWatcherService } from '@fuse/services/media-watcher';
|
||||
import { FuseNavigationService } from '@fuse/components/navigation';
|
||||
import { FuseNavigationService, FuseVerticalNavigationComponent } from '@fuse/components/navigation';
|
||||
import { InitialData } from 'app/app.types';
|
||||
|
||||
@Component({
|
||||
@@ -50,7 +50,7 @@ export class ClassicLayoutComponent implements OnInit, OnDestroy
|
||||
*/
|
||||
ngOnInit(): void
|
||||
{
|
||||
// Subscribe to the resolved route mock-api
|
||||
// Subscribe to the resolved route data
|
||||
this._activatedRoute.data.subscribe((data: Data) => {
|
||||
this.data = data.initialData;
|
||||
});
|
||||
@@ -87,7 +87,7 @@ export class ClassicLayoutComponent implements OnInit, OnDestroy
|
||||
toggleNavigation(name: string): void
|
||||
{
|
||||
// Get the navigation
|
||||
const navigation = this._fuseNavigationService.getComponent(name);
|
||||
const navigation = this._fuseNavigationService.getComponent<FuseVerticalNavigationComponent>(name);
|
||||
|
||||
if ( navigation )
|
||||
{
|
||||
|
||||
@@ -7,6 +7,7 @@ import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatMenuModule } from '@angular/material/menu';
|
||||
import { FuseFullscreenModule } from '@fuse/components/fullscreen';
|
||||
import { FuseNavigationModule } from '@fuse/components/navigation';
|
||||
import { LanguageModule } from 'app/layout/common/language/language.module';
|
||||
import { MessagesModule } from 'app/layout/common/messages/messages.module';
|
||||
import { NotificationsModule } from 'app/layout/common/notifications/notifications.module';
|
||||
import { SearchModule } from 'app/layout/common/search/search.module';
|
||||
@@ -28,6 +29,7 @@ import { ClassicLayoutComponent } from 'app/layout/layouts/vertical/classic/clas
|
||||
MatMenuModule,
|
||||
FuseFullscreenModule,
|
||||
FuseNavigationModule,
|
||||
LanguageModule,
|
||||
MessagesModule,
|
||||
NotificationsModule,
|
||||
SearchModule,
|
||||
|
||||
@@ -66,6 +66,7 @@
|
||||
</button>
|
||||
<!-- Components -->
|
||||
<div class="flex items-center pl-2 ml-auto space-x-2">
|
||||
<language></language>
|
||||
<fuse-fullscreen></fuse-fullscreen>
|
||||
<search [appearance]="'bar'"></search>
|
||||
<shortcuts [shortcuts]="data.shortcuts"></shortcuts>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { ActivatedRoute, Data, Router } from '@angular/router';
|
||||
import { Subject } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
import { FuseMediaWatcherService } from '@fuse/services/media-watcher';
|
||||
import { FuseNavigationService } from '@fuse/components/navigation';
|
||||
import { FuseNavigationService, FuseVerticalNavigationComponent } from '@fuse/components/navigation';
|
||||
import { InitialData } from 'app/app.types';
|
||||
|
||||
@Component({
|
||||
@@ -50,7 +50,7 @@ export class ClassyLayoutComponent implements OnInit, OnDestroy
|
||||
*/
|
||||
ngOnInit(): void
|
||||
{
|
||||
// Subscribe to the resolved route mock-api
|
||||
// Subscribe to the resolved route data
|
||||
this._activatedRoute.data.subscribe((data: Data) => {
|
||||
this.data = data.initialData;
|
||||
});
|
||||
@@ -87,7 +87,7 @@ export class ClassyLayoutComponent implements OnInit, OnDestroy
|
||||
toggleNavigation(name: string): void
|
||||
{
|
||||
// Get the navigation
|
||||
const navigation = this._fuseNavigationService.getComponent(name);
|
||||
const navigation = this._fuseNavigationService.getComponent<FuseVerticalNavigationComponent>(name);
|
||||
|
||||
if ( navigation )
|
||||
{
|
||||
|
||||
@@ -7,6 +7,7 @@ import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatMenuModule } from '@angular/material/menu';
|
||||
import { FuseNavigationModule } from '@fuse/components/navigation';
|
||||
import { FuseFullscreenModule } from '@fuse/components/fullscreen/fullscreen.module';
|
||||
import { LanguageModule } from 'app/layout/common/language/language.module';
|
||||
import { MessagesModule } from 'app/layout/common/messages/messages.module';
|
||||
import { NotificationsModule } from 'app/layout/common/notifications/notifications.module';
|
||||
import { SearchModule } from 'app/layout/common/search/search.module';
|
||||
@@ -28,6 +29,7 @@ import { ClassyLayoutComponent } from 'app/layout/layouts/vertical/classy/classy
|
||||
MatMenuModule,
|
||||
FuseFullscreenModule,
|
||||
FuseNavigationModule,
|
||||
LanguageModule,
|
||||
MessagesModule,
|
||||
NotificationsModule,
|
||||
SearchModule,
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
</button>
|
||||
<!-- Components -->
|
||||
<div class="flex items-center pl-2 ml-auto space-x-2">
|
||||
<language></language>
|
||||
<fuse-fullscreen></fuse-fullscreen>
|
||||
<search [appearance]="'bar'"></search>
|
||||
<shortcuts [shortcuts]="data.shortcuts"></shortcuts>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { ActivatedRoute, Data, Router } from '@angular/router';
|
||||
import { Subject } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
import { FuseMediaWatcherService } from '@fuse/services/media-watcher';
|
||||
import { FuseNavigationService } from '@fuse/components/navigation';
|
||||
import { FuseNavigationService, FuseVerticalNavigationComponent } from '@fuse/components/navigation';
|
||||
import { InitialData } from 'app/app.types';
|
||||
|
||||
@Component({
|
||||
@@ -50,7 +50,7 @@ export class CompactLayoutComponent implements OnInit, OnDestroy
|
||||
*/
|
||||
ngOnInit(): void
|
||||
{
|
||||
// Subscribe to the resolved route mock-api
|
||||
// Subscribe to the resolved route data
|
||||
this._activatedRoute.data.subscribe((data: Data) => {
|
||||
this.data = data.initialData;
|
||||
});
|
||||
@@ -87,7 +87,7 @@ export class CompactLayoutComponent implements OnInit, OnDestroy
|
||||
toggleNavigation(name: string): void
|
||||
{
|
||||
// Get the navigation
|
||||
const navigation = this._fuseNavigationService.getComponent(name);
|
||||
const navigation = this._fuseNavigationService.getComponent<FuseVerticalNavigationComponent>(name);
|
||||
|
||||
if ( navigation )
|
||||
{
|
||||
|
||||
@@ -7,6 +7,7 @@ import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatMenuModule } from '@angular/material/menu';
|
||||
import { FuseFullscreenModule } from '@fuse/components/fullscreen';
|
||||
import { FuseNavigationModule } from '@fuse/components/navigation';
|
||||
import { LanguageModule } from 'app/layout/common/language/language.module';
|
||||
import { MessagesModule } from 'app/layout/common/messages/messages.module';
|
||||
import { NotificationsModule } from 'app/layout/common/notifications/notifications.module';
|
||||
import { SearchModule } from 'app/layout/common/search/search.module';
|
||||
@@ -28,6 +29,7 @@ import { CompactLayoutComponent } from 'app/layout/layouts/vertical/compact/comp
|
||||
MatMenuModule,
|
||||
FuseFullscreenModule,
|
||||
FuseNavigationModule,
|
||||
LanguageModule,
|
||||
MessagesModule,
|
||||
NotificationsModule,
|
||||
SearchModule,
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
</div>
|
||||
<!-- Components -->
|
||||
<div class="flex items-center pl-2 ml-auto space-x-2">
|
||||
<language></language>
|
||||
<fuse-fullscreen></fuse-fullscreen>
|
||||
<search [appearance]="'bar'"></search>
|
||||
<shortcuts [shortcuts]="data.shortcuts"></shortcuts>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { ActivatedRoute, Data, Router } from '@angular/router';
|
||||
import { Subject } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
import { FuseMediaWatcherService } from '@fuse/services/media-watcher';
|
||||
import { FuseNavigationService } from '@fuse/components/navigation';
|
||||
import { FuseNavigationService, FuseVerticalNavigationComponent } from '@fuse/components/navigation';
|
||||
import { InitialData } from 'app/app.types';
|
||||
|
||||
@Component({
|
||||
@@ -51,7 +51,7 @@ export class DenseLayoutComponent implements OnInit, OnDestroy
|
||||
*/
|
||||
ngOnInit(): void
|
||||
{
|
||||
// Subscribe to the resolved route mock-api
|
||||
// Subscribe to the resolved route data
|
||||
this._activatedRoute.data.subscribe((data: Data) => {
|
||||
this.data = data.initialData;
|
||||
});
|
||||
@@ -88,7 +88,7 @@ export class DenseLayoutComponent implements OnInit, OnDestroy
|
||||
toggleNavigation(name: string): void
|
||||
{
|
||||
// Get the navigation
|
||||
const navigation = this._fuseNavigationService.getComponent(name);
|
||||
const navigation = this._fuseNavigationService.getComponent<FuseVerticalNavigationComponent>(name);
|
||||
|
||||
if ( navigation )
|
||||
{
|
||||
|
||||
@@ -7,6 +7,7 @@ import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatMenuModule } from '@angular/material/menu';
|
||||
import { FuseFullscreenModule } from '@fuse/components/fullscreen';
|
||||
import { FuseNavigationModule } from '@fuse/components/navigation';
|
||||
import { LanguageModule } from 'app/layout/common/language/language.module';
|
||||
import { MessagesModule } from 'app/layout/common/messages/messages.module';
|
||||
import { NotificationsModule } from 'app/layout/common/notifications/notifications.module';
|
||||
import { SearchModule } from 'app/layout/common/search/search.module';
|
||||
@@ -28,6 +29,7 @@ import { DenseLayoutComponent } from 'app/layout/layouts/vertical/dense/dense.co
|
||||
MatMenuModule,
|
||||
FuseFullscreenModule,
|
||||
FuseNavigationModule,
|
||||
LanguageModule,
|
||||
MessagesModule,
|
||||
NotificationsModule,
|
||||
SearchModule,
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
</button>
|
||||
<!-- Components -->
|
||||
<div class="flex items-center pl-2 ml-auto space-x-2">
|
||||
<language></language>
|
||||
<fuse-fullscreen></fuse-fullscreen>
|
||||
<search [appearance]="'bar'"></search>
|
||||
<shortcuts [shortcuts]="data.shortcuts"></shortcuts>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { ActivatedRoute, Data, Router } from '@angular/router';
|
||||
import { Subject } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
import { FuseMediaWatcherService } from '@fuse/services/media-watcher';
|
||||
import { FuseNavigationService } from '@fuse/components/navigation';
|
||||
import { FuseNavigationService, FuseVerticalNavigationComponent } from '@fuse/components/navigation';
|
||||
import { InitialData } from 'app/app.types';
|
||||
|
||||
@Component({
|
||||
@@ -50,7 +50,7 @@ export class FuturisticLayoutComponent implements OnInit, OnDestroy
|
||||
*/
|
||||
ngOnInit(): void
|
||||
{
|
||||
// Subscribe to the resolved route mock-api
|
||||
// Subscribe to the resolved route data
|
||||
this._activatedRoute.data.subscribe((data: Data) => {
|
||||
this.data = data.initialData;
|
||||
});
|
||||
@@ -87,7 +87,7 @@ export class FuturisticLayoutComponent implements OnInit, OnDestroy
|
||||
toggleNavigation(name: string): void
|
||||
{
|
||||
// Get the navigation
|
||||
const navigation = this._fuseNavigationService.getComponent(name);
|
||||
const navigation = this._fuseNavigationService.getComponent<FuseVerticalNavigationComponent>(name);
|
||||
|
||||
if ( navigation )
|
||||
{
|
||||
|
||||
@@ -7,6 +7,7 @@ import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatMenuModule } from '@angular/material/menu';
|
||||
import { FuseFullscreenModule } from '@fuse/components/fullscreen';
|
||||
import { FuseNavigationModule } from '@fuse/components/navigation';
|
||||
import { LanguageModule } from 'app/layout/common/language/language.module';
|
||||
import { MessagesModule } from 'app/layout/common/messages/messages.module';
|
||||
import { NotificationsModule } from 'app/layout/common/notifications/notifications.module';
|
||||
import { SearchModule } from 'app/layout/common/search/search.module';
|
||||
@@ -28,6 +29,7 @@ import { FuturisticLayoutComponent } from 'app/layout/layouts/vertical/futuristi
|
||||
MatMenuModule,
|
||||
FuseFullscreenModule,
|
||||
FuseNavigationModule,
|
||||
LanguageModule,
|
||||
MessagesModule,
|
||||
NotificationsModule,
|
||||
SearchModule,
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
</button>
|
||||
<!-- Components -->
|
||||
<div class="flex items-center pl-2 ml-auto space-x-2">
|
||||
<language></language>
|
||||
<fuse-fullscreen></fuse-fullscreen>
|
||||
<search [appearance]="'bar'"></search>
|
||||
<shortcuts [shortcuts]="data.shortcuts"></shortcuts>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { ActivatedRoute, Data, Router } from '@angular/router';
|
||||
import { Subject } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
import { FuseMediaWatcherService } from '@fuse/services/media-watcher';
|
||||
import { FuseNavigationService } from '@fuse/components/navigation';
|
||||
import { FuseNavigationService, FuseVerticalNavigationComponent } from '@fuse/components/navigation';
|
||||
import { InitialData } from 'app/app.types';
|
||||
|
||||
@Component({
|
||||
@@ -50,7 +50,7 @@ export class ThinLayoutComponent implements OnInit, OnDestroy
|
||||
*/
|
||||
ngOnInit(): void
|
||||
{
|
||||
// Subscribe to the resolved route mock-api
|
||||
// Subscribe to the resolved route data
|
||||
this._activatedRoute.data.subscribe((data: Data) => {
|
||||
this.data = data.initialData;
|
||||
});
|
||||
@@ -87,7 +87,7 @@ export class ThinLayoutComponent implements OnInit, OnDestroy
|
||||
toggleNavigation(name: string): void
|
||||
{
|
||||
// Get the navigation
|
||||
const navigation = this._fuseNavigationService.getComponent(name);
|
||||
const navigation = this._fuseNavigationService.getComponent<FuseVerticalNavigationComponent>(name);
|
||||
|
||||
if ( navigation )
|
||||
{
|
||||
|
||||
@@ -7,6 +7,7 @@ import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatMenuModule } from '@angular/material/menu';
|
||||
import { FuseFullscreenModule } from '@fuse/components/fullscreen';
|
||||
import { FuseNavigationModule } from '@fuse/components/navigation';
|
||||
import { LanguageModule } from 'app/layout/common/language/language.module';
|
||||
import { MessagesModule } from 'app/layout/common/messages/messages.module';
|
||||
import { NotificationsModule } from 'app/layout/common/notifications/notifications.module';
|
||||
import { SearchModule } from 'app/layout/common/search/search.module';
|
||||
@@ -28,6 +29,7 @@ import { ThinLayoutComponent } from 'app/layout/layouts/vertical/thin/thin.compo
|
||||
MatMenuModule,
|
||||
FuseFullscreenModule,
|
||||
FuseNavigationModule,
|
||||
LanguageModule,
|
||||
MessagesModule,
|
||||
NotificationsModule,
|
||||
SearchModule,
|
||||
|
||||
@@ -295,12 +295,12 @@ export class ContactsMockApi
|
||||
const reader = new FileReader();
|
||||
|
||||
// Resolve the promise on success
|
||||
reader.onload = () => {
|
||||
reader.onload = (): void => {
|
||||
resolve(reader.result);
|
||||
};
|
||||
|
||||
// Reject the promise on error
|
||||
reader.onerror = (e) => {
|
||||
reader.onerror = (e): void => {
|
||||
reject(e);
|
||||
};
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ export class ECommerceInventoryMockApi
|
||||
// @ Products - GET
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
this._fuseMockApiService
|
||||
.onGet('api/apps/ecommerce/inventory/products', 625)
|
||||
.onGet('api/apps/ecommerce/inventory/products', 300)
|
||||
.reply(({request}) => {
|
||||
|
||||
// Get available queries
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
/* eslint-disable */
|
||||
import * as moment from 'moment';
|
||||
import { Message } from 'app/layout/common/messages/messages.types';
|
||||
|
||||
export const messages: Message[] = [
|
||||
export const messages = [
|
||||
{
|
||||
id : '832276cc-c5e9-4fcc-8e23-d38e2e267bc9',
|
||||
image : 'assets/images/avatars/male-01.jpg',
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,7 @@
|
||||
/* eslint-disable */
|
||||
import * as moment from 'moment';
|
||||
import { Notification } from 'app/layout/common/notifications/notifications.types';
|
||||
|
||||
export const notifications: Notification[] = [
|
||||
export const notifications = [
|
||||
{
|
||||
id : '493190c9-5b61-4912-afe5-78c21f1044d7',
|
||||
icon : 'heroicons_outline:star',
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
/* eslint-disable */
|
||||
import { Shortcut } from 'app/layout/common/shortcuts/shortcuts.types';
|
||||
|
||||
export const shortcuts: Shortcut[] = [
|
||||
export const shortcuts = [
|
||||
{
|
||||
id : 'a1ae91d3-e2cb-459b-9be9-a184694f548b',
|
||||
label : 'Changelog',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/* eslint-disable */
|
||||
export const user: any = {
|
||||
export const user = {
|
||||
id : 'cfaad35d-07a3-4447-a6c3-d8c3d54fd5df',
|
||||
name : 'Brian Hughes',
|
||||
email : 'hughes.brian@company.com',
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { AcademyMockApi } from 'app/mock-api/apps/academy/api';
|
||||
import { ActivitiesMockApi } from 'app/mock-api/pages/activities/api';
|
||||
import { AnalyticsMockApi } from 'app/mock-api/dashboards/analytics/api';
|
||||
import { AuthMockApi } from 'app/mock-api/common/auth/api';
|
||||
import { CalendarMockApi } from 'app/mock-api/apps/calendar/api';
|
||||
@@ -21,6 +22,7 @@ import { UserMockApi } from 'app/mock-api/common/user/api';
|
||||
|
||||
export const mockApiServices = [
|
||||
AcademyMockApi,
|
||||
ActivitiesMockApi,
|
||||
AnalyticsMockApi,
|
||||
AuthMockApi,
|
||||
CalendarMockApi,
|
||||
|
||||
38
src/app/mock-api/pages/activities/api.ts
Normal file
38
src/app/mock-api/pages/activities/api.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { FuseMockApiService } from '@fuse/lib/mock-api';
|
||||
import { activities as activitiesData } from 'app/mock-api/pages/activities/data';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class ActivitiesMockApi
|
||||
{
|
||||
private _activities: any = activitiesData;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
constructor(private _fuseMockApiService: FuseMockApiService)
|
||||
{
|
||||
// Register Mock API handlers
|
||||
this.registerHandlers();
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Public methods
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Register Mock API handlers
|
||||
*/
|
||||
registerHandlers(): void
|
||||
{
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Activities - GET
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
this._fuseMockApiService
|
||||
.onGet('api/pages/activities')
|
||||
.reply(() => [200, cloneDeep(this._activities)]);
|
||||
}
|
||||
}
|
||||
88
src/app/mock-api/pages/activities/data.ts
Normal file
88
src/app/mock-api/pages/activities/data.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
/* eslint-disable */
|
||||
import * as moment from 'moment';
|
||||
|
||||
export const activities = [
|
||||
{
|
||||
id : '493190c9-5b61-4912-afe5-78c21f1044d7',
|
||||
icon : 'heroicons_solid:star',
|
||||
description : 'Your submission has been accepted',
|
||||
date : moment().subtract(25, 'minutes').toISOString(), // 25 minutes ago
|
||||
extraContent: `<div class="font-bold">Congratulations for your acceptance!</div><br>
|
||||
<div>Hi Brian,<br>Your submission has been accepted and you are ready to move into the next phase. Once you are ready, reach out to me and we will ...</div>`
|
||||
},
|
||||
{
|
||||
id : '6e3e97e5-effc-4fb7-b730-52a151f0b641',
|
||||
image : 'assets/images/avatars/male-04.jpg',
|
||||
description : '<strong>Leo Gill</strong> added you to <strong>Top Secret Project</strong> group and assigned you as a <strong>Project Manager</strong>',
|
||||
date : moment().subtract(50, 'minutes').toISOString(), // 50 minutes ago
|
||||
linkedContent: 'Top Secret Project',
|
||||
link : '/dashboards/project',
|
||||
useRouter : true
|
||||
},
|
||||
{
|
||||
id : 'b91ccb58-b06c-413b-b389-87010e03a120',
|
||||
icon : 'heroicons_solid:mail',
|
||||
description : 'You have 15 unread mails across 3 mailboxes',
|
||||
date : moment().subtract(3, 'hours').toISOString(), // 3 hours ago
|
||||
linkedContent: 'Mailbox',
|
||||
link : '/apps/mailbox',
|
||||
useRouter : true
|
||||
},
|
||||
{
|
||||
id : '541416c9-84a7-408a-8d74-27a43c38d797',
|
||||
icon : 'heroicons_solid:refresh',
|
||||
description : 'Your <strong>Docker container</strong> is ready to publish',
|
||||
date : moment().subtract(5, 'hours').toISOString(), // 5 hours ago
|
||||
linkedContent: 'Download the container',
|
||||
link : '.',
|
||||
useRouter : true
|
||||
},
|
||||
{
|
||||
id : 'ef7b95a7-8e8b-4616-9619-130d9533add9',
|
||||
image : 'assets/images/avatars/male-06.jpg',
|
||||
description : '<strong>Roger Murray</strong> accepted your friend request',
|
||||
date : moment().subtract(7, 'hours').toISOString(), // 7 hours ago
|
||||
extraContent: `You have <span class="font-semibold">8</span> mutual friends.`
|
||||
},
|
||||
{
|
||||
id : 'eb8aa470-635e-461d-88e1-23d9ea2a5665',
|
||||
image : 'assets/images/avatars/female-04.jpg',
|
||||
description: '<strong>Sophie Stone</strong> sent you a direct message',
|
||||
date : moment().subtract(9, 'hours').toISOString() // 9 hours ago
|
||||
},
|
||||
{
|
||||
id : 'b85c2338-cc98-4140-bbf8-c226ce4e395e',
|
||||
icon : 'heroicons_solid:mail',
|
||||
description : 'You have 3 new mails',
|
||||
date : moment().subtract(1, 'day').toISOString(), // 1 day ago
|
||||
extraContent : `<ol class="list-decimal list-inside space-y-2">
|
||||
<li class="font-medium">Please review and sign the attached agreement</li>
|
||||
<li class="font-medium">Delivery address confirmation</li>
|
||||
<li class="font-medium">Previous clients and their invoices</li>
|
||||
</ol>`,
|
||||
linkedContent: 'Mailbox',
|
||||
link : '/apps/mailbox',
|
||||
useRouter : true
|
||||
},
|
||||
{
|
||||
id : 'fd0f01b4-f3de-4333-add5-cd86850279f8',
|
||||
image : 'assets/images/avatars/female-02.jpg',
|
||||
description : '<strong>Tina Harris</strong> started a chat with you',
|
||||
date : moment().subtract(1, 'day').toISOString(), // 1 day ago,
|
||||
linkedContent: 'Go to Chat (Tina Harris)',
|
||||
link : '/apps/chat/5636c0ba-fa47-42ca-9160-27340583041e',
|
||||
useRouter : true
|
||||
},
|
||||
{
|
||||
id : '8f8e1bf9-4661-4939-9e43-390957b60f42',
|
||||
icon : 'heroicons_solid:star',
|
||||
description: 'Your submission has been accepted and you are ready to sign-up for the final assigment which will be ready in 2 days',
|
||||
date : moment().subtract(3, 'days').toISOString() // 3 days ago
|
||||
},
|
||||
{
|
||||
id : '30af917b-7a6a-45d1-822f-9e7ad7f8bf69',
|
||||
icon : 'heroicons_solid:refresh',
|
||||
description: 'Your Vagrant container is ready to download',
|
||||
date : moment().subtract(4, 'day').toISOString() // 4 days ago
|
||||
}
|
||||
];
|
||||
@@ -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,110 +0,0 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot } from '@angular/router';
|
||||
import { Observable, throwError } from 'rxjs';
|
||||
import { catchError } from 'rxjs/operators';
|
||||
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,105 +0,0 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { BehaviorSubject, Observable, of, throwError } from 'rxjs';
|
||||
import { map, switchMap, tap } from 'rxjs/operators';
|
||||
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,204 +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 } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
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();
|
||||
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,157 +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 } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
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();
|
||||
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,386 +0,0 @@
|
||||
<div class="absolute inset-0 flex flex-col min-w-0 overflow-hidden dark:bg-gray-900">
|
||||
|
||||
<mat-drawer-container class="flex-auto h-full bg-transparent">
|
||||
|
||||
<!-- Drawer -->
|
||||
<mat-drawer
|
||||
class="w-60 dark:bg-gray-900"
|
||||
[autoFocus]="false"
|
||||
[mode]="drawerMode"
|
||||
[opened]="drawerOpened"
|
||||
#drawer>
|
||||
<calendar-sidebar (calendarUpdated)="onCalendarUpdated($event)"></calendar-sidebar>
|
||||
</mat-drawer>
|
||||
|
||||
<mat-drawer-content class="flex">
|
||||
|
||||
<!-- Main -->
|
||||
<div class="flex flex-col flex-auto">
|
||||
|
||||
<!-- Header -->
|
||||
<div class="flex flex-0 flex-wrap items-center p-4 border-b bg-card">
|
||||
|
||||
<button
|
||||
mat-icon-button
|
||||
(click)="toggleDrawer()">
|
||||
<mat-icon [svgIcon]="'heroicons_outline:menu'"></mat-icon>
|
||||
</button>
|
||||
|
||||
<div class="ml-4 text-2xl font-semibold tracking-tight whitespace-nowrap">
|
||||
{{viewTitle}}
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="ml-5"
|
||||
mat-icon-button
|
||||
(click)="previous()">
|
||||
<mat-icon
|
||||
class="icon-size-5"
|
||||
[svgIcon]="'heroicons_solid:chevron-left'"></mat-icon>
|
||||
</button>
|
||||
|
||||
<button
|
||||
mat-icon-button
|
||||
(click)="next()">
|
||||
<mat-icon
|
||||
class="icon-size-5"
|
||||
[svgIcon]="'heroicons_solid:chevron-right'"></mat-icon>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="hidden md:inline-flex"
|
||||
mat-icon-button
|
||||
(click)="today()">
|
||||
<mat-icon [svgIcon]="'heroicons_outline:calendar'"></mat-icon>
|
||||
</button>
|
||||
|
||||
<div class="hidden md:block ml-auto">
|
||||
<mat-form-field class="fuse-mat-dense fuse-mat-no-subscript w-30 ml-2">
|
||||
<mat-select
|
||||
(selectionChange)="changeView(viewChanger.value)"
|
||||
[value]="view"
|
||||
#viewChanger="matSelect">
|
||||
<mat-option [value]="'dayGridMonth'">Month</mat-option>
|
||||
<mat-option [value]="'timeGridWeek'">Week</mat-option>
|
||||
<mat-option [value]="'timeGridDay'">Day</mat-option>
|
||||
<mat-option [value]="'listYear'">Schedule</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<!-- Mobile menu -->
|
||||
<div class="md:hidden ml-auto">
|
||||
<button
|
||||
class=""
|
||||
[matMenuTriggerFor]="actionsMenu"
|
||||
mat-icon-button>
|
||||
<mat-icon [svgIcon]="'heroicons_outline:dots-vertical'"></mat-icon>
|
||||
|
||||
<mat-menu #actionsMenu="matMenu">
|
||||
<button
|
||||
mat-menu-item
|
||||
(click)="today()">
|
||||
<mat-icon [svgIcon]="'heroicons_outline:calendar'"></mat-icon>
|
||||
<span>Go to today</span>
|
||||
</button>
|
||||
<button
|
||||
[matMenuTriggerFor]="actionsViewsMenu"
|
||||
mat-menu-item>
|
||||
<mat-icon [svgIcon]="'heroicons_outline:view-grid'"></mat-icon>
|
||||
<span>View</span>
|
||||
</button>
|
||||
</mat-menu>
|
||||
|
||||
<mat-menu #actionsViewsMenu="matMenu">
|
||||
<button
|
||||
mat-menu-item
|
||||
[disabled]="view === 'dayGridMonth'"
|
||||
(click)="changeView('dayGridMonth')">
|
||||
<span>Month</span>
|
||||
</button>
|
||||
<button
|
||||
mat-menu-item
|
||||
[disabled]="view === 'timeGridWeek'"
|
||||
(click)="changeView('timeGridWeek')">
|
||||
<span>Week</span>
|
||||
</button>
|
||||
<button
|
||||
mat-menu-item
|
||||
[disabled]="view === 'timeGridDay'"
|
||||
(click)="changeView('timeGridDay')">
|
||||
<span>Day</span>
|
||||
</button>
|
||||
<button
|
||||
mat-menu-item
|
||||
[disabled]="view === 'listYear'"
|
||||
(click)="changeView('listYear')">
|
||||
<span>Schedule</span>
|
||||
</button>
|
||||
</mat-menu>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- FullCalendar -->
|
||||
<div class="flex flex-col flex-auto">
|
||||
<full-calendar
|
||||
[defaultView]="view"
|
||||
[events]="events"
|
||||
[firstDay]="settings.startWeekOn"
|
||||
[handleWindowResize]="false"
|
||||
[header]="false"
|
||||
[height]="'parent'"
|
||||
[plugins]="calendarPlugins"
|
||||
[views]="views"
|
||||
(dateClick)="onDateClick($event)"
|
||||
(eventClick)="onEventClick($event)"
|
||||
(eventRender)="onEventRender($event)"
|
||||
#fullCalendar></full-calendar>
|
||||
</div>
|
||||
|
||||
<!-- Event panel -->
|
||||
<ng-template #eventPanel>
|
||||
|
||||
<!-- Preview mode -->
|
||||
<ng-container *ngIf="panelMode === 'view'">
|
||||
<div class="flex-auto p-8">
|
||||
<!-- Info -->
|
||||
<div class="flex">
|
||||
<mat-icon [svgIcon]="'heroicons_outline:information-circle'"></mat-icon>
|
||||
<div class="flex flex-auto justify-between ml-6">
|
||||
<!-- Info -->
|
||||
<div>
|
||||
<div class="text-3xl font-semibold tracking-tight leading-none">{{event.title || '(No title)'}}</div>
|
||||
<div class="mt-0.5 text-secondary">{{event.start | date:'EEEE, MMMM d'}}</div>
|
||||
<div class="text-secondary">{{recurrenceStatus}}</div>
|
||||
</div>
|
||||
<!-- Actions -->
|
||||
<div class="flex -mt-2 -mr-2 ml-10">
|
||||
|
||||
<!-- Non-recurring event -->
|
||||
<ng-container *ngIf="!event.recurrence">
|
||||
<!-- Edit -->
|
||||
<button
|
||||
mat-icon-button
|
||||
(click)="changeEventPanelMode('edit', 'single')">
|
||||
<mat-icon [svgIcon]="'heroicons_outline:pencil-alt'"></mat-icon>
|
||||
</button>
|
||||
<!-- Delete -->
|
||||
<button
|
||||
mat-icon-button
|
||||
(click)="deleteEvent(event)">
|
||||
<mat-icon [svgIcon]="'heroicons_outline:trash'"></mat-icon>
|
||||
</button>
|
||||
</ng-container>
|
||||
|
||||
<!-- Recurring event -->
|
||||
<ng-container *ngIf="event.recurrence">
|
||||
<!-- Edit -->
|
||||
<button
|
||||
mat-icon-button
|
||||
[matMenuTriggerFor]="editMenu">
|
||||
<mat-icon [svgIcon]="'heroicons_outline:pencil-alt'"></mat-icon>
|
||||
</button>
|
||||
<mat-menu #editMenu="matMenu">
|
||||
<button
|
||||
mat-menu-item
|
||||
(click)="changeEventPanelMode('edit', 'single')">
|
||||
This event
|
||||
</button>
|
||||
<button
|
||||
mat-menu-item
|
||||
*ngIf="!event.isFirstInstance"
|
||||
(click)="changeEventPanelMode('edit', 'future')">
|
||||
This and following events
|
||||
</button>
|
||||
<button
|
||||
mat-menu-item
|
||||
(click)="changeEventPanelMode('edit', 'all')">
|
||||
All events
|
||||
</button>
|
||||
</mat-menu>
|
||||
<!-- Delete -->
|
||||
<button
|
||||
mat-icon-button
|
||||
[matMenuTriggerFor]="deleteMenu">
|
||||
<mat-icon [svgIcon]="'heroicons_outline:trash'"></mat-icon>
|
||||
</button>
|
||||
<mat-menu #deleteMenu="matMenu">
|
||||
<button
|
||||
mat-menu-item
|
||||
(click)="deleteEvent(event, 'single')">
|
||||
This event
|
||||
</button>
|
||||
<button
|
||||
mat-menu-item
|
||||
*ngIf="!event.isFirstInstance"
|
||||
(click)="deleteEvent(event, 'future')">
|
||||
This and following events
|
||||
</button>
|
||||
<button
|
||||
mat-menu-item
|
||||
(click)="deleteEvent(event, 'all')">
|
||||
All events
|
||||
</button>
|
||||
</mat-menu>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Description -->
|
||||
<div
|
||||
class="flex mt-6"
|
||||
*ngIf="event.description">
|
||||
<mat-icon [svgIcon]="'heroicons_outline:menu-alt-2'"></mat-icon>
|
||||
<div class="flex-auto ml-6">{{event.description}}</div>
|
||||
</div>
|
||||
|
||||
<!-- Calendar -->
|
||||
<div class="flex mt-6">
|
||||
<mat-icon [svgIcon]="'heroicons_outline:calendar'"></mat-icon>
|
||||
<div class="flex flex-auto items-center ml-6">
|
||||
<div
|
||||
class="w-2 h-2 rounded-full"
|
||||
[ngClass]="getCalendar(event.calendarId).color"></div>
|
||||
<div class="ml-3 leading-none">{{getCalendar(event.calendarId).title}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<!-- Add / Edit mode -->
|
||||
<ng-container *ngIf="panelMode === 'add' || panelMode === 'edit'">
|
||||
<form
|
||||
class="flex flex-col w-full p-6 pt-8 sm:pt-10 sm:pr-8"
|
||||
[formGroup]="eventForm">
|
||||
|
||||
<!-- Title -->
|
||||
<div class="flex items-center">
|
||||
<mat-icon
|
||||
class="hidden sm:inline-flex mr-6"
|
||||
[svgIcon]="'heroicons_outline:pencil-alt'"></mat-icon>
|
||||
<mat-form-field class="fuse-mat-no-subscript flex-auto">
|
||||
<input
|
||||
matInput
|
||||
[formControlName]="'title'"
|
||||
[placeholder]="'Event title'">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<!-- Dates -->
|
||||
<div class="flex items-start mt-6">
|
||||
<mat-icon
|
||||
class="hidden sm:inline-flex mt-3 mr-6"
|
||||
[svgIcon]="'heroicons_outline:calendar'"></mat-icon>
|
||||
<div class="flex-auto">
|
||||
<fuse-date-range
|
||||
[formControlName]="'range'"
|
||||
[dateFormat]="settings.dateFormat"
|
||||
[timeRange]="!eventForm.get('allDay').value"
|
||||
[timeFormat]="settings.timeFormat"></fuse-date-range>
|
||||
<mat-checkbox
|
||||
class="mt-4"
|
||||
[color]="'primary'"
|
||||
[formControlName]="'allDay'">
|
||||
All day
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Recurrence -->
|
||||
<div
|
||||
class="flex items-center mt-6"
|
||||
*ngIf="!event.recurrence || eventEditMode !== 'single'">
|
||||
<mat-icon
|
||||
class="hidden sm:inline-flex mr-6 transform -scale-x-1"
|
||||
[svgIcon]="'heroicons_outline:refresh'"></mat-icon>
|
||||
<div
|
||||
class="flex flex-auto items-center h-12 px-4 rounded-md border cursor-pointer shadow-sm border-gray-300 dark:bg-black dark:bg-opacity-5 dark:border-gray-500"
|
||||
(click)="openRecurrenceDialog()">
|
||||
<div class="flex-auto">
|
||||
{{recurrenceStatus || 'Does not repeat'}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Calendar -->
|
||||
<div class="flex items-center mt-6">
|
||||
<mat-icon
|
||||
class="hidden sm:inline-flex mr-6"
|
||||
[svgIcon]="'heroicons_outline:tag'"></mat-icon>
|
||||
<mat-form-field class="fuse-mat-no-subscript flex-auto">
|
||||
<mat-select
|
||||
[formControlName]="'calendarId'"
|
||||
(change)="$event.stopImmediatePropagation()">
|
||||
<mat-select-trigger class="inline-flex items-center leading-none">
|
||||
<span
|
||||
class="w-3 h-3 rounded-full"
|
||||
[ngClass]="getCalendar(eventForm.get('calendarId').value)?.color"></span>
|
||||
<span class="ml-3">{{getCalendar(eventForm.get('calendarId').value)?.title}}</span>
|
||||
</mat-select-trigger>
|
||||
<ng-container *ngFor="let calendar of calendars">
|
||||
<mat-option [value]="calendar.id">
|
||||
<div class="inline-flex items-center">
|
||||
<span
|
||||
class="w-3 h-3 rounded-full"
|
||||
[ngClass]="calendar.color"></span>
|
||||
<span class="ml-3">{{calendar.title}}</span>
|
||||
</div>
|
||||
</mat-option>
|
||||
</ng-container>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<!-- Description -->
|
||||
<div class="flex items-start mt-6">
|
||||
<mat-icon
|
||||
class="hidden sm:inline-flex mr-6 mt-3"
|
||||
[svgIcon]="'heroicons_outline:menu-alt-2'"></mat-icon>
|
||||
<mat-form-field class="fuse-mat-textarea fuse-mat-no-subscript flex-auto">
|
||||
<textarea
|
||||
matInput
|
||||
cdkTextareaAutosize
|
||||
[cdkAutosizeMinRows]="1"
|
||||
[formControlName]="'description'"
|
||||
[placeholder]="'Event description'">
|
||||
</textarea>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="ml-auto mt-6">
|
||||
<button
|
||||
class="add"
|
||||
*ngIf="panelMode === 'add'"
|
||||
mat-flat-button
|
||||
type="button"
|
||||
[color]="'primary'"
|
||||
(click)="addEvent()">
|
||||
Add
|
||||
</button>
|
||||
<button
|
||||
class="save"
|
||||
*ngIf="panelMode === 'edit'"
|
||||
mat-flat-button
|
||||
type="button"
|
||||
[color]="'primary'"
|
||||
(click)="updateEvent()">
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</ng-container>
|
||||
|
||||
</ng-template>
|
||||
|
||||
</div>
|
||||
|
||||
</mat-drawer-content>
|
||||
|
||||
</mat-drawer-container>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,121 +0,0 @@
|
||||
calendar {
|
||||
|
||||
/* Tweak: FullCalendar CSS only height to improve resize performance */
|
||||
/* With this tweak, we can disable "handleWindowResize" option of FullCalendar */
|
||||
/* which disables the height calculations on window resize and increases the */
|
||||
/* overall performance. */
|
||||
/* This tweak only affects the Calendar app's FullCalendar. */
|
||||
full-calendar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1 0 auto;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
.fc-view-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1 0 auto;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
.fc-view {
|
||||
|
||||
/* Day grid - Month view */
|
||||
/* Time grid - Week view */
|
||||
/* Time grid - Day view */
|
||||
&.fc-dayGridMonth-view,
|
||||
&.fc-timeGridWeek-view,
|
||||
&.fc-timeGridDay-view {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1 0 auto;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
> table {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1 0 auto;
|
||||
height: 100%;
|
||||
|
||||
> thead {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
> tbody {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
overflow: hidden;
|
||||
|
||||
> tr {
|
||||
display: flex;
|
||||
|
||||
> td {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.fc-scroller {
|
||||
flex: 1 1 auto;
|
||||
overflow: hidden scroll !important;
|
||||
height: auto !important;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Day grid - Month view */
|
||||
&.fc-dayGridMonth-view {
|
||||
|
||||
> table {
|
||||
|
||||
> tbody {
|
||||
|
||||
> tr {
|
||||
|
||||
> td {
|
||||
|
||||
.fc-scroller {
|
||||
display: flex;
|
||||
|
||||
> .fc-day-grid {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 580px;
|
||||
|
||||
> .fc-row {
|
||||
flex: 1 0 0;
|
||||
height: auto !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* List - Year view */
|
||||
&.fc-listYear-view {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
.fc-scroller {
|
||||
width: 100%;
|
||||
height: 100% !important;
|
||||
overflow: hidden !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Event panel */
|
||||
.calendar-event-panel {
|
||||
border-radius: 8px;
|
||||
@apply shadow-2xl bg-card;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user