import { Component, OnInit, OnDestroy, Inject, Renderer2, RendererFactory2 } from '@angular/core'; import { DOCUMENT } from '@angular/common'; import { MediaMatcher } from '@angular/cdk/layout'; import { LocalStorage } from 'ngx-webstorage'; import { BehaviorSubject, Subscription, Subject } from 'rxjs'; import { Router, RouterEvent, NavigationStart, NavigationEnd, NavigationCancel, NavigationError } from '@angular/router'; import { HttpClient, HttpHeaders } from '@angular/common/http'; import { map, filter } from 'rxjs/operators'; type ColorScheme = 'dark' | 'light'; export interface DomainResponse { domain: string; } @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.sass'] }) export class AppComponent implements OnInit, OnDestroy { private renderer: Renderer2; colorSchemeMatcher: MediaQueryList; title = 'spm-frontend'; public activeColorScheme$: BehaviorSubject = new BehaviorSubject('dark'); @LocalStorage('prefers-color-scheme') public colorSchemeOverride?: ColorScheme; colorSchemeSubscription?: Subscription; router: Router; public routerLoading$: Subject = new Subject(); routerEventSubscription?: Subscription; http: HttpClient; apiDomain$: Subject = new Subject(); constructor( rendererFactory: RendererFactory2, mediaMatcher: MediaMatcher, @Inject(DOCUMENT) private document: Document, router: Router, http: HttpClient, ) { this.renderer = rendererFactory.createRenderer(null, null); this.colorSchemeMatcher = mediaMatcher.matchMedia('(prefers-color-scheme: dark)'); this.document = document; this.colorSchemeListener = this.colorSchemeListener.bind(this); this.colorSchemeMatcher.addEventListener('change', this.colorSchemeListener); this.activeColorScheme$.next(this.getColorScheme()); this.router = router; this.http = http; } private colorSchemeListener(event: { matches: boolean; }) { this.activeColorScheme$.next(this.getColorScheme(event)); } private getColorScheme(event?: { matches: boolean; }): ColorScheme { if (this.colorSchemeOverride) { return this.colorSchemeOverride; } else if (event) { return event.matches ? 'dark' : 'light'; } else { return this.colorSchemeMatcher.matches ? 'dark' : 'light'; } } updateColorScheme(scheme: ColorScheme) { this.colorSchemeOverride = scheme; this.activeColorScheme$.next(scheme); } ngOnInit() { this.colorSchemeSubscription = this.activeColorScheme$.subscribe(scheme => { this.renderer.setAttribute(this.document.body, 'data-color-scheme', scheme); }); this.routerEventSubscription = this.router.events.pipe( filter(event => event instanceof NavigationStart || event instanceof NavigationEnd || event instanceof NavigationCancel || event instanceof NavigationError ), map(event => event instanceof NavigationStart) ).subscribe(this.routerLoading$); this.http.get('/domain', { headers: new HttpHeaders({ 'Accept': 'application/json' }) }).pipe(map((resp: DomainResponse) => resp.domain)).subscribe(this.apiDomain$); } ngOnDestroy() { this.colorSchemeMatcher.removeEventListener('change', this.colorSchemeListener); this.colorSchemeSubscription?.unsubscribe(); this.routerEventSubscription?.unsubscribe(); } }