import { Component, Inject, OnDestroy, OnInit, Renderer2, ViewEncapsulation } from '@angular/core'; import { DOCUMENT } from '@angular/common'; import { ActivatedRoute, NavigationEnd, Router } from '@angular/router'; import { combineLatest, filter, map, Subject, takeUntil } from 'rxjs'; import { FuseConfigService } from '@fuse/services/config'; import { FuseMediaWatcherService } from '@fuse/services/media-watcher'; import { FUSE_VERSION } from '@fuse/version'; import { Layout } from 'app/layout/layout.types'; import { AppConfig } from 'app/core/config/app.config'; @Component({ selector : 'layout', templateUrl : './layout.component.html', styleUrls : ['./layout.component.scss'], encapsulation: ViewEncapsulation.None }) export class LayoutComponent implements OnInit, OnDestroy { config: AppConfig; layout: Layout; scheme: 'dark' | 'light'; theme: string; private _unsubscribeAll: Subject = new Subject(); /** * Constructor */ constructor( private _activatedRoute: ActivatedRoute, @Inject(DOCUMENT) private _document: any, private _renderer2: Renderer2, private _router: Router, private _fuseConfigService: FuseConfigService, private _fuseMediaWatcherService: FuseMediaWatcherService ) { } // ----------------------------------------------------------------------------------------------------- // @ Lifecycle hooks // ----------------------------------------------------------------------------------------------------- /** * On init */ ngOnInit(): void { // Set the theme and scheme based on the configuration combineLatest([ this._fuseConfigService.config$, this._fuseMediaWatcherService.onMediaQueryChange$(['(prefers-color-scheme: dark)', '(prefers-color-scheme: light)']) ]).pipe( takeUntil(this._unsubscribeAll), map(([config, mql]) => { const options = { scheme: config.scheme, theme : config.theme }; // If the scheme is set to 'auto'... if ( config.scheme === 'auto' ) { // Decide the scheme using the media query options.scheme = mql.breakpoints['(prefers-color-scheme: dark)'] ? 'dark' : 'light'; } return options; }) ).subscribe((options) => { // Store the options this.scheme = options.scheme; this.theme = options.theme; // Update the scheme and theme this._updateScheme(); this._updateTheme(); }); // Subscribe to config changes this._fuseConfigService.config$ .pipe(takeUntil(this._unsubscribeAll)) .subscribe((config: AppConfig) => { // Store the config this.config = config; // Update the layout this._updateLayout(); }); // Subscribe to NavigationEnd event this._router.events.pipe( filter(event => event instanceof NavigationEnd), takeUntil(this._unsubscribeAll) ).subscribe(() => { // Update the layout this._updateLayout(); }); // Set the app version this._renderer2.setAttribute(this._document.querySelector('[ng-version]'), 'fuse-version', FUSE_VERSION); } /** * On destroy */ ngOnDestroy(): void { // Unsubscribe from all subscriptions this._unsubscribeAll.next(null); this._unsubscribeAll.complete(); } // ----------------------------------------------------------------------------------------------------- // @ Private methods // ----------------------------------------------------------------------------------------------------- /** * Update the selected layout */ private _updateLayout(): void { // Get the current activated route let route = this._activatedRoute; while ( route.firstChild ) { route = route.firstChild; } // 1. Set the layout from the config this.layout = this.config.layout; // 2. Get the query parameter from the current route and // set the layout and save the layout to the config const layoutFromQueryParam = (route.snapshot.queryParamMap.get('layout') as Layout); if ( layoutFromQueryParam ) { this.layout = layoutFromQueryParam; if ( this.config ) { this.config.layout = layoutFromQueryParam; } } // 3. Iterate through the paths and change the layout as we find // a config for it. // // The reason we do this is that there might be empty grouping // paths or componentless routes along the path. Because of that, // we cannot just assume that the layout configuration will be // in the last path's config or in the first path's config. // // So, we get all the paths that matched starting from root all // the way to the current activated route, walk through them one // by one and change the layout as we find the layout config. This // way, layout configuration can live anywhere within the path and // we won't miss it. // // Also, this will allow overriding the layout in any time so we // can have different layouts for different routes. const paths = route.pathFromRoot; paths.forEach((path) => { // Check if there is a 'layout' data if ( path.routeConfig && path.routeConfig.data && path.routeConfig.data.layout ) { // Set the layout this.layout = path.routeConfig.data.layout; } }); } /** * Update the selected scheme * * @private */ private _updateScheme(): void { // Remove class names for all schemes this._document.body.classList.remove('light', 'dark'); // Add class name for the currently selected scheme this._document.body.classList.add(this.scheme); } /** * Update the selected theme * * @private */ private _updateTheme(): void { // Find the class name for the previously selected theme and remove it this._document.body.classList.forEach((className: string) => { if ( className.startsWith('theme-') ) { this._document.body.classList.remove(className, className.split('-')[1]); } }); // Add class name for the currently selected theme this._document.body.classList.add(`theme-${this.theme}`); } }