feat: Refactor settings handling in full layout and header components for improved theme management
This commit is contained in:
@@ -1,17 +1,17 @@
|
|||||||
<span [dir]="options.dir!">
|
<span [dir]="settings.getOptions().dir">
|
||||||
<mat-sidenav-container class="mainWrapper" autosize autoFocus [ngClass]="{
|
<mat-sidenav-container class="mainWrapper" autosize autoFocus [ngClass]="{
|
||||||
'sidebarNav-mini':
|
'sidebarNav-mini':
|
||||||
options.sidenavCollapsed && options.navPos !== 'top' && !resView,
|
settings.getOptions().sidenavCollapsed && settings.getOptions().navPos !== 'top' && !resView,
|
||||||
'sidebarNav-horizontal': options.horizontal,
|
'sidebarNav-horizontal': settings.getOptions().horizontal,
|
||||||
cardBorder: options.cardBorder
|
cardBorder: settings.getOptions().cardBorder
|
||||||
}">
|
}">
|
||||||
<!-- ============================================================== -->
|
<!-- ============================================================== -->
|
||||||
<!-- Vertical Sidebar -->
|
<!-- Vertical Sidebar -->
|
||||||
<!-- ============================================================== -->
|
<!-- ============================================================== -->
|
||||||
@if (!options.horizontal) {
|
@if (!settings.getOptions().horizontal) {
|
||||||
<mat-sidenav #leftsidenav [mode]="isOver ? 'over' : 'side'" [opened]="
|
<mat-sidenav #leftsidenav [mode]="isOver ? 'over' : 'side'" [opened]="
|
||||||
options.navPos === 'side' &&
|
settings.getOptions().navPos === 'side' &&
|
||||||
options.sidenavOpened &&
|
settings.getOptions().sidenavOpened &&
|
||||||
!isOver &&
|
!isOver &&
|
||||||
!resView
|
!resView
|
||||||
" (openedChange)="onSidenavOpenedChange($event)" (closedStart)="onSidenavClosedStart()" class="sidebarNav">
|
" (openedChange)="onSidenavOpenedChange($event)" (closedStart)="onSidenavClosedStart()" class="sidebarNav">
|
||||||
@@ -47,7 +47,7 @@
|
|||||||
<!-- horizontal Sidebar -->
|
<!-- horizontal Sidebar -->
|
||||||
<!-- ============================================================== -->
|
<!-- ============================================================== -->
|
||||||
@if (resView) {
|
@if (resView) {
|
||||||
<mat-sidenav #leftsidenav [mode]="'over'" [opened]="options.sidenavOpened && !isTablet"
|
<mat-sidenav #leftsidenav [mode]="'over'" [opened]="settings.getOptions().sidenavOpened && !isTablet"
|
||||||
(openedChange)="onSidenavOpenedChange($event)" (closedStart)="onSidenavClosedStart()" class="sidebarNav">
|
(openedChange)="onSidenavOpenedChange($event)" (closedStart)="onSidenavClosedStart()" class="sidebarNav">
|
||||||
<app-sidebar> </app-sidebar>
|
<app-sidebar> </app-sidebar>
|
||||||
<ng-scrollbar class="position-relative" style="height: 100%">
|
<ng-scrollbar class="position-relative" style="height: 100%">
|
||||||
@@ -82,19 +82,19 @@
|
|||||||
<!-- ============================================================== -->
|
<!-- ============================================================== -->
|
||||||
<!-- VerticalHeader -->
|
<!-- VerticalHeader -->
|
||||||
<!-- ============================================================== -->
|
<!-- ============================================================== -->
|
||||||
@if (!options.horizontal) {
|
@if (!settings.getOptions().horizontal) {
|
||||||
<app-header [showToggle]="!isOver" (toggleCollapsed)="toggleCollapsed()" (toggleMobileNav)="sidenav.toggle()"
|
<app-header [showToggle]="!isOver" (toggleCollapsed)="toggleCollapsed()" (toggleMobileNav)="sidenav.toggle()"
|
||||||
(toggleMobileFilterNav)="toggleFilterNav()" (optionsChange)="receiveOptions($event)"></app-header>
|
(toggleMobileFilterNav)="toggleFilterNav()" (optionsChange)="receiveOptions($event)"></app-header>
|
||||||
} @else {
|
} @else {
|
||||||
<!-- horizontal header -->
|
<!-- horizontal header -->
|
||||||
<app-horizontal-header (toggleMobileNav)="sidenav.toggle()" (toggleMobileFilterNav)="toggleFilterNav()"
|
<app-horizontal-header (toggleMobileNav)="sidenav.toggle()" (toggleMobileFilterNav)="toggleFilterNav()"
|
||||||
(optionsChange)="receiveOptions($event)"></app-horizontal-header>
|
></app-horizontal-header>
|
||||||
} @if(options.horizontal) {
|
} @if(settings.getOptions().horizontal) {
|
||||||
<app-horizontal-sidebar></app-horizontal-sidebar>
|
<app-horizontal-sidebar></app-horizontal-sidebar>
|
||||||
}
|
}
|
||||||
|
|
||||||
<main class="pageWrapper" [ngClass]="{
|
<main class="pageWrapper" [ngClass]="{
|
||||||
maxWidth: options.boxed
|
maxWidth: settings.getOptions().boxed
|
||||||
}">
|
}">
|
||||||
<app-breadcrumb></app-breadcrumb>
|
<app-breadcrumb></app-breadcrumb>
|
||||||
<!-- ============================================================== -->
|
<!-- ============================================================== -->
|
||||||
@@ -115,7 +115,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<div class="d-flex justify-content-between align-items-center">
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
<div class="branding">
|
<div class="branding">
|
||||||
@if(options.theme === 'light') {
|
@if(settings.getOptions().theme === 'light') {
|
||||||
<a href="/">
|
<a href="/">
|
||||||
<img src="./assets/images/logos/dark-logo.svg" class="align-middle m-2" alt="logo" />
|
<img src="./assets/images/logos/dark-logo.svg" class="align-middle m-2" alt="logo" />
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { BreakpointObserver, MediaMatcher } from '@angular/cdk/layout';
|
import { BreakpointObserver, MediaMatcher } from '@angular/cdk/layout';
|
||||||
import { ChangeDetectorRef, Component, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
|
import { ChangeDetectorRef, Component, effect, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
|
||||||
import { Subscription } from 'rxjs';
|
import { Subscription } from 'rxjs';
|
||||||
import { MatSidenav, MatSidenavContent } from '@angular/material/sidenav';
|
import { MatSidenav, MatSidenavContent } from '@angular/material/sidenav';
|
||||||
import { CoreService } from '../../services/core.service';
|
import { CoreService } from '../../services/core.service';
|
||||||
@@ -71,7 +71,7 @@ export class FullComponent implements OnInit {
|
|||||||
resView = false;
|
resView = false;
|
||||||
@ViewChild('content', { static: true }) content!: MatSidenavContent;
|
@ViewChild('content', { static: true }) content!: MatSidenavContent;
|
||||||
//get options from service
|
//get options from service
|
||||||
options: AppSettings;
|
// options: AppSettings;
|
||||||
private layoutChangesSubscription = Subscription.EMPTY;
|
private layoutChangesSubscription = Subscription.EMPTY;
|
||||||
private isMobileScreen = false;
|
private isMobileScreen = false;
|
||||||
private isContentWidthFixed = true;
|
private isContentWidthFixed = true;
|
||||||
@@ -190,29 +190,33 @@ export class FullComponent implements OnInit {
|
|||||||
];
|
];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private settings: CoreService,
|
protected settings: CoreService,
|
||||||
private mediaMatcher: MediaMatcher,
|
private mediaMatcher: MediaMatcher,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private breakpointObserver: BreakpointObserver,
|
private breakpointObserver: BreakpointObserver,
|
||||||
private navService: NavService, private cdr: ChangeDetectorRef
|
private navService: NavService,
|
||||||
|
private cdr: ChangeDetectorRef
|
||||||
) {
|
) {
|
||||||
this.htmlElement = document.querySelector('html')!;
|
this.htmlElement = document.querySelector('html')!;
|
||||||
this.options = this.settings.getOptions();
|
// this.options = this.settings.getOptions();
|
||||||
this.layoutChangesSubscription = this.breakpointObserver
|
this.layoutChangesSubscription = this.breakpointObserver
|
||||||
.observe([MOBILE_VIEW, TABLET_VIEW, MONITOR_VIEW, BELOWMONITOR])
|
.observe([MOBILE_VIEW, TABLET_VIEW, MONITOR_VIEW, BELOWMONITOR])
|
||||||
.subscribe((state) => {
|
.subscribe((state) => {
|
||||||
// SidenavOpened must be reset true when layout changes
|
// SidenavOpened must be reset true when layout changes
|
||||||
this.options.sidenavOpened = true;
|
this.settings.setOptions({ sidenavOpened: true });
|
||||||
|
// this.options.sidenavOpened = true;
|
||||||
this.isMobileScreen = state.breakpoints[BELOWMONITOR];
|
this.isMobileScreen = state.breakpoints[BELOWMONITOR];
|
||||||
if (this.options.sidenavCollapsed == false) {
|
if (this.settings.getOptions().sidenavCollapsed == false) {
|
||||||
this.options.sidenavCollapsed = state.breakpoints[TABLET_VIEW];
|
this.settings.setOptions({ sidenavCollapsed: state.breakpoints[TABLET_VIEW] });
|
||||||
}
|
}
|
||||||
this.isContentWidthFixed = state.breakpoints[MONITOR_VIEW];
|
this.isContentWidthFixed = state.breakpoints[MONITOR_VIEW];
|
||||||
this.resView = state.breakpoints[BELOWMONITOR];
|
this.resView = state.breakpoints[BELOWMONITOR];
|
||||||
});
|
});
|
||||||
|
|
||||||
// Initialize project theme with options
|
effect(() => {
|
||||||
this.receiveOptions(this.options);
|
const options = this.settings.getOptionsSignal()();
|
||||||
|
this.receiveOptions(options);
|
||||||
|
});
|
||||||
|
|
||||||
// This is for scroll to top
|
// This is for scroll to top
|
||||||
this.router.events
|
this.router.events
|
||||||
@@ -230,7 +234,7 @@ export class FullComponent implements OnInit {
|
|||||||
this.cdr.detectChanges(); // Ensures Angular updates the view
|
this.cdr.detectChanges(); // Ensures Angular updates the view
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {}
|
ngOnInit(): void { }
|
||||||
|
|
||||||
ngOnDestroy() {
|
ngOnDestroy() {
|
||||||
this.layoutChangesSubscription.unsubscribe();
|
this.layoutChangesSubscription.unsubscribe();
|
||||||
@@ -238,12 +242,12 @@ export class FullComponent implements OnInit {
|
|||||||
|
|
||||||
toggleCollapsed() {
|
toggleCollapsed() {
|
||||||
this.isContentWidthFixed = false;
|
this.isContentWidthFixed = false;
|
||||||
this.options.sidenavCollapsed = !this.options.sidenavCollapsed;
|
this.settings.setOptions({ sidenavCollapsed: !this.settings.getOptions().sidenavCollapsed });
|
||||||
this.resetCollapsedState();
|
this.resetCollapsedState();
|
||||||
}
|
}
|
||||||
|
|
||||||
resetCollapsedState(timer = 400) {
|
resetCollapsedState(timer = 400) {
|
||||||
setTimeout(() => this.settings.setOptions(this.options), timer);
|
setTimeout(() => this.settings.setOptions(this.settings.getOptions()), timer);
|
||||||
}
|
}
|
||||||
|
|
||||||
onSidenavClosedStart() {
|
onSidenavClosedStart() {
|
||||||
@@ -252,8 +256,7 @@ export class FullComponent implements OnInit {
|
|||||||
|
|
||||||
onSidenavOpenedChange(isOpened: boolean) {
|
onSidenavOpenedChange(isOpened: boolean) {
|
||||||
this.isCollapsedWidthFixed = !this.isOver;
|
this.isCollapsedWidthFixed = !this.isOver;
|
||||||
this.options.sidenavOpened = isOpened;
|
this.settings.setOptions({ sidenavOpened: isOpened });
|
||||||
this.settings.setOptions(this.options);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
receiveOptions(options: AppSettings): void {
|
receiveOptions(options: AppSettings): void {
|
||||||
|
|||||||
@@ -101,7 +101,7 @@
|
|||||||
}
|
}
|
||||||
</mat-menu>
|
</mat-menu>
|
||||||
|
|
||||||
@if(options.theme=='light'){
|
@if(settings.getOptions().theme=='light'){
|
||||||
<button mat-icon-button aria-label="lightdark" class="d-flex justify-content-center" (click)="setlightDark('dark')">
|
<button mat-icon-button aria-label="lightdark" class="d-flex justify-content-center" (click)="setlightDark('dark')">
|
||||||
<i-tabler class="d-flex icon-22" [name]="'moon'"></i-tabler>
|
<i-tabler class="d-flex icon-22" [name]="'moon'"></i-tabler>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import { TablerIconsModule } from 'angular-tabler-icons';
|
|||||||
import { MaterialModule } from '../../../../material.module';
|
import { MaterialModule } from '../../../../material.module';
|
||||||
import { BrandingComponent } from '../../vertical/sidebar/branding.component';
|
import { BrandingComponent } from '../../vertical/sidebar/branding.component';
|
||||||
import { FormsModule } from '@angular/forms';
|
import { FormsModule } from '@angular/forms';
|
||||||
import { AppSettings } from '../../../../config';
|
|
||||||
import { AuthenticationService } from '../../../../services/authentication.service';
|
import { AuthenticationService } from '../../../../services/authentication.service';
|
||||||
|
|
||||||
interface notifications {
|
interface notifications {
|
||||||
@@ -74,19 +73,14 @@ export class AppHorizontalHeaderComponent {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@Output() optionsChange = new EventEmitter<AppSettings>();
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private settings: CoreService,
|
protected settings: CoreService,
|
||||||
private vsidenav: CoreService,
|
private vsidenav: CoreService,
|
||||||
public dialog: MatDialog,
|
public dialog: MatDialog,
|
||||||
private translate: TranslateService,
|
private translate: TranslateService,
|
||||||
public auth: AuthenticationService
|
public auth: AuthenticationService
|
||||||
) {
|
) { }
|
||||||
// translate.setDefaultLang('en');
|
|
||||||
this.options = this.settings.getOptions();
|
|
||||||
}
|
|
||||||
options: AppSettings;
|
|
||||||
|
|
||||||
openDialog() {
|
openDialog() {
|
||||||
const dialogRef = this.dialog.open(AppHorizontalSearchDialogComponent);
|
const dialogRef = this.dialog.open(AppHorizontalSearchDialogComponent);
|
||||||
@@ -96,13 +90,8 @@ export class AppHorizontalHeaderComponent {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private emitOptions() {
|
|
||||||
this.optionsChange.emit(this.options);
|
|
||||||
}
|
|
||||||
|
|
||||||
setlightDark(theme: string) {
|
setlightDark(theme: string) {
|
||||||
this.options.theme = theme;
|
this.settings.setOptions({ theme: theme });
|
||||||
this.emitOptions();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
changeLanguage(lang: any): void {
|
changeLanguage(lang: any): void {
|
||||||
|
|||||||
@@ -5,13 +5,57 @@ import { AppSettings, defaults } from '../config';
|
|||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
export class CoreService {
|
export class CoreService {
|
||||||
private optionsSignal = signal<AppSettings>(defaults);
|
// detect user's preferred color scheme (safe for SSR)
|
||||||
|
private static detectTheme(): 'dark' | 'light' {
|
||||||
|
// detect theme if set manually
|
||||||
|
const theme = localStorage.getItem('theme') as 'dark' | 'light' | null;
|
||||||
|
if (theme)
|
||||||
|
return theme;
|
||||||
|
else
|
||||||
|
try {
|
||||||
|
if (typeof window !== 'undefined' && window.matchMedia) {
|
||||||
|
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// ignore and fall through
|
||||||
|
}
|
||||||
|
return defaults.theme === 'dark' ? 'dark' : 'light';
|
||||||
|
}
|
||||||
|
|
||||||
|
// merge detected theme into initial defaults
|
||||||
|
private initialOptions = { ...defaults, theme: CoreService.detectTheme() as string } as AppSettings;
|
||||||
|
|
||||||
|
private optionsSignal = signal<AppSettings>(this.initialOptions);
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
// listen for changes to the OS/browser color-scheme and update settings
|
||||||
|
if (typeof window !== 'undefined' && window.matchMedia) {
|
||||||
|
const mq = window.matchMedia('(prefers-color-scheme: dark)');
|
||||||
|
const handler = (e: MediaQueryListEvent) => {
|
||||||
|
this.setOptions({ theme: e.matches ? 'dark' : 'light' });
|
||||||
|
};
|
||||||
|
if (typeof mq.addEventListener === 'function') {
|
||||||
|
mq.addEventListener('change', handler);
|
||||||
|
} else if (typeof (mq as any).addListener === 'function') {
|
||||||
|
// fallback for older browsers
|
||||||
|
(mq as any).addListener(handler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
getOptions() {
|
getOptions() {
|
||||||
return this.optionsSignal();
|
return this.optionsSignal();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getOptionsSignal() {
|
||||||
|
return this.optionsSignal;
|
||||||
|
}
|
||||||
|
|
||||||
setOptions(options: Partial<AppSettings>) {
|
setOptions(options: Partial<AppSettings>) {
|
||||||
|
// if theme is specified, persist to localStorage
|
||||||
|
if (options.theme !== null && options.theme !== undefined)
|
||||||
|
localStorage.setItem('theme', options.theme);
|
||||||
|
|
||||||
this.optionsSignal.update((current) => ({
|
this.optionsSignal.update((current) => ({
|
||||||
...current,
|
...current,
|
||||||
...options,
|
...options,
|
||||||
|
|||||||
Reference in New Issue
Block a user