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]="{
|
||||
'sidebarNav-mini':
|
||||
options.sidenavCollapsed && options.navPos !== 'top' && !resView,
|
||||
'sidebarNav-horizontal': options.horizontal,
|
||||
cardBorder: options.cardBorder
|
||||
settings.getOptions().sidenavCollapsed && settings.getOptions().navPos !== 'top' && !resView,
|
||||
'sidebarNav-horizontal': settings.getOptions().horizontal,
|
||||
cardBorder: settings.getOptions().cardBorder
|
||||
}">
|
||||
<!-- ============================================================== -->
|
||||
<!-- Vertical Sidebar -->
|
||||
<!-- ============================================================== -->
|
||||
@if (!options.horizontal) {
|
||||
@if (!settings.getOptions().horizontal) {
|
||||
<mat-sidenav #leftsidenav [mode]="isOver ? 'over' : 'side'" [opened]="
|
||||
options.navPos === 'side' &&
|
||||
options.sidenavOpened &&
|
||||
settings.getOptions().navPos === 'side' &&
|
||||
settings.getOptions().sidenavOpened &&
|
||||
!isOver &&
|
||||
!resView
|
||||
" (openedChange)="onSidenavOpenedChange($event)" (closedStart)="onSidenavClosedStart()" class="sidebarNav">
|
||||
@@ -47,7 +47,7 @@
|
||||
<!-- horizontal Sidebar -->
|
||||
<!-- ============================================================== -->
|
||||
@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">
|
||||
<app-sidebar> </app-sidebar>
|
||||
<ng-scrollbar class="position-relative" style="height: 100%">
|
||||
@@ -82,19 +82,19 @@
|
||||
<!-- ============================================================== -->
|
||||
<!-- VerticalHeader -->
|
||||
<!-- ============================================================== -->
|
||||
@if (!options.horizontal) {
|
||||
@if (!settings.getOptions().horizontal) {
|
||||
<app-header [showToggle]="!isOver" (toggleCollapsed)="toggleCollapsed()" (toggleMobileNav)="sidenav.toggle()"
|
||||
(toggleMobileFilterNav)="toggleFilterNav()" (optionsChange)="receiveOptions($event)"></app-header>
|
||||
} @else {
|
||||
<!-- horizontal header -->
|
||||
<app-horizontal-header (toggleMobileNav)="sidenav.toggle()" (toggleMobileFilterNav)="toggleFilterNav()"
|
||||
(optionsChange)="receiveOptions($event)"></app-horizontal-header>
|
||||
} @if(options.horizontal) {
|
||||
></app-horizontal-header>
|
||||
} @if(settings.getOptions().horizontal) {
|
||||
<app-horizontal-sidebar></app-horizontal-sidebar>
|
||||
}
|
||||
|
||||
<main class="pageWrapper" [ngClass]="{
|
||||
maxWidth: options.boxed
|
||||
maxWidth: settings.getOptions().boxed
|
||||
}">
|
||||
<app-breadcrumb></app-breadcrumb>
|
||||
<!-- ============================================================== -->
|
||||
@@ -115,7 +115,7 @@
|
||||
<div>
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div class="branding">
|
||||
@if(options.theme === 'light') {
|
||||
@if(settings.getOptions().theme === 'light') {
|
||||
<a href="/">
|
||||
<img src="./assets/images/logos/dark-logo.svg" class="align-middle m-2" alt="logo" />
|
||||
</a>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
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 { MatSidenav, MatSidenavContent } from '@angular/material/sidenav';
|
||||
import { CoreService } from '../../services/core.service';
|
||||
@@ -42,36 +42,36 @@ interface quicklinks {
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-full',
|
||||
imports: [
|
||||
RouterModule,
|
||||
AppNavItemComponent,
|
||||
MaterialModule,
|
||||
CommonModule,
|
||||
SidebarComponent,
|
||||
NgScrollbarModule,
|
||||
TablerIconsModule,
|
||||
HeaderComponent,
|
||||
AppHorizontalHeaderComponent,
|
||||
AppHorizontalSidebarComponent,
|
||||
AppBreadcrumbComponent,
|
||||
CustomizerComponent,
|
||||
],
|
||||
templateUrl: './full.component.html',
|
||||
|
||||
encapsulation: ViewEncapsulation.None
|
||||
selector: 'app-full',
|
||||
imports: [
|
||||
RouterModule,
|
||||
AppNavItemComponent,
|
||||
MaterialModule,
|
||||
CommonModule,
|
||||
SidebarComponent,
|
||||
NgScrollbarModule,
|
||||
TablerIconsModule,
|
||||
HeaderComponent,
|
||||
AppHorizontalHeaderComponent,
|
||||
AppHorizontalSidebarComponent,
|
||||
AppBreadcrumbComponent,
|
||||
CustomizerComponent,
|
||||
],
|
||||
templateUrl: './full.component.html',
|
||||
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class FullComponent implements OnInit {
|
||||
navItems = navItems;
|
||||
|
||||
|
||||
|
||||
|
||||
@ViewChild('leftsidenav')
|
||||
public sidenav: MatSidenav;
|
||||
resView = false;
|
||||
@ViewChild('content', { static: true }) content!: MatSidenavContent;
|
||||
//get options from service
|
||||
options: AppSettings;
|
||||
// options: AppSettings;
|
||||
private layoutChangesSubscription = Subscription.EMPTY;
|
||||
private isMobileScreen = false;
|
||||
private isContentWidthFixed = true;
|
||||
@@ -190,30 +190,34 @@ export class FullComponent implements OnInit {
|
||||
];
|
||||
|
||||
constructor(
|
||||
private settings: CoreService,
|
||||
protected settings: CoreService,
|
||||
private mediaMatcher: MediaMatcher,
|
||||
private router: Router,
|
||||
private breakpointObserver: BreakpointObserver,
|
||||
private navService: NavService, private cdr: ChangeDetectorRef
|
||||
private navService: NavService,
|
||||
private cdr: ChangeDetectorRef
|
||||
) {
|
||||
this.htmlElement = document.querySelector('html')!;
|
||||
this.options = this.settings.getOptions();
|
||||
// this.options = this.settings.getOptions();
|
||||
this.layoutChangesSubscription = this.breakpointObserver
|
||||
.observe([MOBILE_VIEW, TABLET_VIEW, MONITOR_VIEW, BELOWMONITOR])
|
||||
.subscribe((state) => {
|
||||
// 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];
|
||||
if (this.options.sidenavCollapsed == false) {
|
||||
this.options.sidenavCollapsed = state.breakpoints[TABLET_VIEW];
|
||||
if (this.settings.getOptions().sidenavCollapsed == false) {
|
||||
this.settings.setOptions({ sidenavCollapsed: state.breakpoints[TABLET_VIEW] });
|
||||
}
|
||||
this.isContentWidthFixed = state.breakpoints[MONITOR_VIEW];
|
||||
this.resView = state.breakpoints[BELOWMONITOR];
|
||||
});
|
||||
|
||||
// Initialize project theme with options
|
||||
this.receiveOptions(this.options);
|
||||
|
||||
effect(() => {
|
||||
const options = this.settings.getOptionsSignal()();
|
||||
this.receiveOptions(options);
|
||||
});
|
||||
|
||||
// This is for scroll to top
|
||||
this.router.events
|
||||
.pipe(filter((event) => event instanceof NavigationEnd))
|
||||
@@ -230,7 +234,7 @@ export class FullComponent implements OnInit {
|
||||
this.cdr.detectChanges(); // Ensures Angular updates the view
|
||||
}
|
||||
|
||||
ngOnInit(): void {}
|
||||
ngOnInit(): void { }
|
||||
|
||||
ngOnDestroy() {
|
||||
this.layoutChangesSubscription.unsubscribe();
|
||||
@@ -238,12 +242,12 @@ export class FullComponent implements OnInit {
|
||||
|
||||
toggleCollapsed() {
|
||||
this.isContentWidthFixed = false;
|
||||
this.options.sidenavCollapsed = !this.options.sidenavCollapsed;
|
||||
this.settings.setOptions({ sidenavCollapsed: !this.settings.getOptions().sidenavCollapsed });
|
||||
this.resetCollapsedState();
|
||||
}
|
||||
|
||||
resetCollapsedState(timer = 400) {
|
||||
setTimeout(() => this.settings.setOptions(this.options), timer);
|
||||
setTimeout(() => this.settings.setOptions(this.settings.getOptions()), timer);
|
||||
}
|
||||
|
||||
onSidenavClosedStart() {
|
||||
@@ -252,8 +256,7 @@ export class FullComponent implements OnInit {
|
||||
|
||||
onSidenavOpenedChange(isOpened: boolean) {
|
||||
this.isCollapsedWidthFixed = !this.isOver;
|
||||
this.options.sidenavOpened = isOpened;
|
||||
this.settings.setOptions(this.options);
|
||||
this.settings.setOptions({ sidenavOpened: isOpened });
|
||||
}
|
||||
|
||||
receiveOptions(options: AppSettings): void {
|
||||
|
||||
@@ -101,7 +101,7 @@
|
||||
}
|
||||
</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')">
|
||||
<i-tabler class="d-flex icon-22" [name]="'moon'"></i-tabler>
|
||||
</button>
|
||||
|
||||
@@ -8,7 +8,6 @@ import { TablerIconsModule } from 'angular-tabler-icons';
|
||||
import { MaterialModule } from '../../../../material.module';
|
||||
import { BrandingComponent } from '../../vertical/sidebar/branding.component';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { AppSettings } from '../../../../config';
|
||||
import { AuthenticationService } from '../../../../services/authentication.service';
|
||||
|
||||
interface notifications {
|
||||
@@ -74,19 +73,14 @@ export class AppHorizontalHeaderComponent {
|
||||
},
|
||||
];
|
||||
|
||||
@Output() optionsChange = new EventEmitter<AppSettings>();
|
||||
|
||||
constructor(
|
||||
private settings: CoreService,
|
||||
protected settings: CoreService,
|
||||
private vsidenav: CoreService,
|
||||
public dialog: MatDialog,
|
||||
private translate: TranslateService,
|
||||
public auth: AuthenticationService
|
||||
) {
|
||||
// translate.setDefaultLang('en');
|
||||
this.options = this.settings.getOptions();
|
||||
}
|
||||
options: AppSettings;
|
||||
) { }
|
||||
|
||||
|
||||
openDialog() {
|
||||
const dialogRef = this.dialog.open(AppHorizontalSearchDialogComponent);
|
||||
@@ -96,13 +90,8 @@ export class AppHorizontalHeaderComponent {
|
||||
});
|
||||
}
|
||||
|
||||
private emitOptions() {
|
||||
this.optionsChange.emit(this.options);
|
||||
}
|
||||
|
||||
setlightDark(theme: string) {
|
||||
this.options.theme = theme;
|
||||
this.emitOptions();
|
||||
this.settings.setOptions({ theme: theme });
|
||||
}
|
||||
|
||||
changeLanguage(lang: any): void {
|
||||
|
||||
@@ -5,13 +5,57 @@ import { AppSettings, defaults } from '../config';
|
||||
providedIn: 'root',
|
||||
})
|
||||
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() {
|
||||
return this.optionsSignal();
|
||||
}
|
||||
|
||||
getOptionsSignal() {
|
||||
return this.optionsSignal;
|
||||
}
|
||||
|
||||
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) => ({
|
||||
...current,
|
||||
...options,
|
||||
|
||||
Reference in New Issue
Block a user