Skip to content

Commit

Permalink
refactored core/browser services (#213)
Browse files Browse the repository at this point in the history
* added favicon ref enum

* moved services into `core/browser`

* missed a service rename

* converted `FaviconService` to use `Renderer2`

* converted `ColorsService` to use `Renderer2`

* adds sizes any to favicon.svg
  • Loading branch information
kubikowski committed Aug 3, 2021
1 parent 1d4e748 commit f97406d
Show file tree
Hide file tree
Showing 14 changed files with 97 additions and 54 deletions.
14 changes: 7 additions & 7 deletions src/app/app.component.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { ChangeDetectionStrategy, Component, ViewChild } from '@angular/core';
import { MatSidenav } from '@angular/material/sidenav';
import { AnimationFrameService } from 'src/app/core/browser/animation-frame.service';
import { FaviconService } from 'src/app/core/browser/favicon.service';
import { ScreenDetectorService } from 'src/app/core/browser/screen-detector.service';
import { TitleService } from 'src/app/core/browser/title.service';
import { ColorsService } from 'src/app/core/colors/services/colors.service';
import { TitleService } from 'src/app/core/routing/title.service';
import { FpsService } from 'src/app/core/screen/fps.service';
import { ScreenDetectorService } from 'src/app/core/screen/screen-detector.service';
import { SvgIconService } from 'src/app/core/svg/svg-icon.service';
import { NavigationService } from 'src/app/features/navigation/services/navigation.service';

Expand All @@ -17,13 +17,13 @@ import { NavigationService } from 'src/app/features/navigation/services/navigati
export class AppComponent {

constructor(
private readonly navigationService: NavigationService,
private readonly titleService: TitleService,
private readonly colorsService: ColorsService,
private readonly animationFrameService: AnimationFrameService,
private readonly faviconService: FaviconService,
private readonly fpsService: FpsService,
private readonly screenDetectorService: ScreenDetectorService,
private readonly titleService: TitleService,
private readonly colorsService: ColorsService,
private readonly svgIconService: SvgIconService,
private readonly navigationService: NavigationService,
) { }

@ViewChild('sidenav', { static: true })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Observed } from 'src/app/core/decorators/observed.decorator';
import { SubSink } from 'subsink';

@Injectable({ providedIn: 'root' })
export class FpsService implements OnDestroy {
export class AnimationFrameService implements OnDestroy {
private readonly subscriptions = new SubSink();

private static enabled = true;
Expand All @@ -22,21 +22,21 @@ export class FpsService implements OnDestroy {

this.subscriptions.sink = this.timeStamp$
.pipe(
scan((acc, timestamp) => [ ...acc.slice(acc.length <= FpsService.frameAverage ? 0 : 1), timestamp ], []),
scan((acc, timestamp) => [ ...acc.slice(acc.length <= AnimationFrameService.frameAverage ? 0 : 1), timestamp ], []),
map(timestamps => timestamps[timestamps.length - 1] - timestamps[0]),
map(msBetweenTimestamps => 1000 * FpsService.frameAverage / msBetweenTimestamps),
map(msBetweenTimestamps => 1000 * AnimationFrameService.frameAverage / msBetweenTimestamps),
).subscribe(fps => this.fps = fps);
}

ngOnDestroy(): void {
this.subscriptions.unsubscribe();
FpsService.enabled = false;
AnimationFrameService.enabled = false;
}

private onAnimationFrame(time: number): void {
this.timeStamp = time;

if (FpsService.enabled) {
if (AnimationFrameService.enabled) {
requestAnimationFrame(this.onAnimationFrame.bind(this));
}
}
Expand Down
56 changes: 38 additions & 18 deletions src/app/core/browser/favicon.service.ts
Original file line number Diff line number Diff line change
@@ -1,39 +1,59 @@
import { DOCUMENT } from '@angular/common';
import { Inject, Injectable } from '@angular/core';
import { environment } from 'src/environments/environment';
import { Inject, Injectable, Renderer2, RendererFactory2 } from '@angular/core';
import { FaviconRef } from 'src/app/core/svg/favicon-ref.enum';

@Injectable({ providedIn: 'root' })
export class FaviconService {
private readonly head: HTMLHeadElement;
private readonly renderer: Renderer2;

constructor(
@Inject(DOCUMENT) private readonly document: HTMLDocument,
private readonly rendererFactory: RendererFactory2,
) {
this.initializeFavicon();
this.head = this.document.head;
this.renderer = this.rendererFactory.createRenderer(this.head, null);

this.replaceFavicon(FaviconRef.getDefault());
}

private initializeFavicon(): void {
const favicon = this.document.getElementById('favicon');
private replaceFavicon(faviconRef: FaviconRef): void {
const favicon = this.svgFavicon;

if (favicon?.getAttribute('href') !== faviconRef) {
this.removeFavicon(favicon);
this.addFavicon(faviconRef);
}
}

if (favicon.getAttribute('href') !== environment.iconRef) {
this.removeFavicon();
this.addFavicon();
private removeFavicon(favicon: HTMLLinkElement): void {
if (typeof favicon !== 'undefined') {
this.renderer.removeChild(this.head, favicon);
}
}

private removeFavicon(): void {
const favicon = this.document.getElementById('favicon');
private addFavicon(faviconRef: FaviconRef): void {
const favicon = this.renderer.createElement('link');

favicon.parentNode.removeChild(favicon);
this.renderer.setAttribute(favicon, 'rel', 'icon');
this.renderer.setAttribute(favicon, 'type', 'image/svg+xml');
this.renderer.setAttribute(favicon, 'sizes', 'any');
this.renderer.setAttribute(favicon, 'href', faviconRef);

this.renderer.insertBefore(this.head, favicon, this.appleTouchIcon);
}

private addFavicon(): void {
const favicon = this.document.createElement('link');
private get svgFavicon(): HTMLLinkElement {
return Array.from(this.favicons)
.find(favicon => favicon.getAttribute('type') === 'image/svg+xml');
}

favicon.setAttribute('id', 'favicon');
favicon.setAttribute('rel', 'icon');
favicon.setAttribute('type', 'image/svg+xml');
favicon.setAttribute('href', environment.iconRef);
private get appleTouchIcon(): HTMLLinkElement {
return Array.from(this.favicons)
.find(favicon => favicon.getAttribute('type') === 'apple-touch-icon');
}

this.document.head.appendChild(favicon);
private get favicons(): NodeListOf<HTMLLinkElement> {
return this.head.querySelectorAll('link[rel="icon"]');
}
}
File renamed without changes.
41 changes: 26 additions & 15 deletions src/app/core/colors/services/colors.service.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Injectable, OnDestroy } from '@angular/core';
import { DOCUMENT } from '@angular/common';
import { Inject, Injectable, OnDestroy, Renderer2, RendererFactory2 } from '@angular/core';
import { Observable } from 'rxjs';
import { BaseColorPalette } from 'src/app/core/colors/models/color-palettes/base-color-palette.model';
import { ColorPalette } from 'src/app/core/colors/models/color-palettes/color-palette.model';
Expand All @@ -12,20 +13,30 @@ import { SubSink } from 'subsink';
@Injectable({ providedIn: 'root' })
export class ColorsService implements OnDestroy {
private readonly subscriptions = new SubSink();
private readonly renderer: Renderer2;

private readonly body: HTMLElement;
private readonly element: HTMLElement;

@Observed() public theme: ColorTheme;
@Observed() public palette: ColorPalette;

public readonly theme$: Observable<ColorTheme>;
public readonly palette$: Observable<ColorPalette>;

constructor() {
constructor(
@Inject(DOCUMENT) private readonly document: HTMLDocument,
private readonly rendererFactory: RendererFactory2,
) {
this.body = this.document.body;
this.element = this.document.documentElement;
this.renderer = this.rendererFactory.createRenderer(this.body, null);

this.theme = ColorsService.localStorageTheme;
this.palette = ColorsService.localStoragePalette;

this.subscriptions.sink = this.theme$
.subscribe(theme => this.setTheme(theme));

this.subscriptions.sink = this.palette$
.subscribe(palette => this.setPalette(palette));
}
Expand All @@ -37,16 +48,16 @@ export class ColorsService implements OnDestroy {
// region setters
private setTheme(theme: ColorTheme): void {
ColorsService.localStorageTheme = theme;
ColorsService.documentBodyThemeClass = theme;
ColorsService.cssThemeVariables = theme;
this.documentBodyThemeClass = theme;
this.cssThemeVariables = theme;

ColorsService.cssPaletteVariables = this.computedPalette;
this.cssPaletteVariables = this.computedPalette;
}

private setPalette(palette: ColorPalette): void {
ColorsService.localStoragePalette = palette;

ColorsService.cssPaletteVariables = this.computedPalette;
this.cssPaletteVariables = this.computedPalette;
}

private get computedPalette(): ColorPalette {
Expand All @@ -68,20 +79,20 @@ export class ColorsService implements OnDestroy {
localStorage.setItem('theme', theme.themeName);
}

private static set documentBodyThemeClass(theme: ColorTheme) {
const themeNames = ColorThemes.map(colorTheme => colorTheme.themeName);
private set documentBodyThemeClass(theme: ColorTheme) {
ColorThemes.forEach(colorTheme =>
this.renderer.removeClass(this.body, colorTheme.themeName));

document.body.classList.remove(...themeNames);
document.body.classList.add(theme.themeName);
this.renderer.addClass(this.body, theme.themeName);
}

private static set cssThemeVariables(theme: ColorTheme) {
private set cssThemeVariables(theme: ColorTheme) {
const cssThemeEntries = Object.entries(BaseColorTheme.CssThemeVariables);

cssThemeEntries.forEach(([ themeKey, cssVariableName ]) => {
const cssVariableValue = theme[themeKey];

document.documentElement.style.setProperty(cssVariableName, cssVariableValue);
this.renderer.setStyle(this.element, cssVariableName, cssVariableValue, 2);
});
}
// endregion theme handling
Expand All @@ -98,13 +109,13 @@ export class ColorsService implements OnDestroy {
localStorage.setItem('palette', palette.paletteName);
}

private static set cssPaletteVariables(palette: ColorPalette) {
private set cssPaletteVariables(palette: ColorPalette) {
const cssPaletteEntries = Object.entries(BaseColorPalette.CssPaletteVariables);

cssPaletteEntries.forEach(([ paletteKey, cssVariableName ]) => {
const cssVariableValue = palette[paletteKey];

document.documentElement.style.setProperty(cssVariableName, cssVariableValue);
this.renderer.setStyle(this.element, cssVariableName, cssVariableValue, 2);
});
}
// endregion palette handling
Expand Down
14 changes: 14 additions & 0 deletions src/app/core/svg/favicon-ref.enum.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { environment } from 'src/environments/environment';

export enum FaviconRef {
DEV_DEFAULT = 'assets/icon/inanity.dev.svg',
PROD_DEFAULT = 'assets/icon/inanity.svg',
}

export namespace FaviconRef {
export function getDefault(): FaviconRef {
return environment.production
? FaviconRef.PROD_DEFAULT
: FaviconRef.DEV_DEFAULT;
}
}
6 changes: 3 additions & 3 deletions src/app/features/background/background.component.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { AfterViewInit, ChangeDetectionStrategy, Component, ElementRef, ViewChild } from '@angular/core';
import { Observable } from 'rxjs';
import { distinctUntilChanged, map } from 'rxjs/operators';
import { FpsService } from 'src/app/core/screen/fps.service';
import { AnimationFrameService } from 'src/app/core/browser/animation-frame.service';
import { BackgroundCanvasService } from 'src/app/features/background/services/background-canvas.service';

@Component({
Expand All @@ -19,9 +19,9 @@ export class BackgroundComponent implements AfterViewInit {

constructor(
private readonly backgroundCanvasService: BackgroundCanvasService,
private readonly fpsService: FpsService,
private readonly animationFrameService: AnimationFrameService,
) {
this.fps$ = this.fpsService.fps$
this.fps$ = this.animationFrameService.fps$
.pipe(map(fps => Math.floor(fps)), distinctUntilChanged());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Injectable } from '@angular/core';
import { animationFrameScheduler, combineLatest, interval } from 'rxjs';
import { debounceTime, filter, map, tap } from 'rxjs/operators';
import { ColorsService } from 'src/app/core/colors/services/colors.service';
import { ScreenDetectorService } from 'src/app/core/screen/screen-detector.service';
import { ScreenDetectorService } from 'src/app/core/browser/screen-detector.service';
import { CanvasElement } from 'src/app/features/background/models/canvas-element.model';
import { Circle } from 'src/app/features/background/models/circle.model';
import { CanvasService } from 'src/app/features/background/services/canvas.service';
Expand Down
2 changes: 1 addition & 1 deletion src/app/features/background/services/canvas.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Injectable, OnDestroy } from '@angular/core';
import { combineLatest, Observable } from 'rxjs';
import { map, tap } from 'rxjs/operators';
import { Observed } from 'src/app/core/decorators/observed.decorator';
import { ScreenDetectorService } from 'src/app/core/screen/screen-detector.service';
import { ScreenDetectorService } from 'src/app/core/browser/screen-detector.service';
import { CanvasElement } from 'src/app/features/background/models/canvas-element.model';
import { SubSink } from 'subsink';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Injectable, OnDestroy } from '@angular/core';
import { combineLatest, Observable } from 'rxjs';
import { distinctUntilChanged, map } from 'rxjs/operators';
import { Observed } from 'src/app/core/decorators/observed.decorator';
import { ScreenDetectorService } from 'src/app/core/screen/screen-detector.service';
import { ScreenDetectorService } from 'src/app/core/browser/screen-detector.service';
import { SubSink } from 'subsink';

@Injectable()
Expand Down
1 change: 0 additions & 1 deletion src/environments/environment.prod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,4 @@ import packageInfo from 'package.json';
export const environment = {
production: true,
version: packageInfo.version,
iconRef: 'assets/icon/inanity.svg',
};
1 change: 0 additions & 1 deletion src/environments/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import packageInfo from 'package.json';
export const environment = {
production: false,
version: packageInfo.version,
iconRef: 'assets/icon/inanity.dev.svg',
};

/*
Expand Down
2 changes: 1 addition & 1 deletion src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#036ea6">

<link rel="icon" type="image/svg+xml" sizes="any" href="assets/icon/inanity.svg" id="favicon">
<link rel="icon" type="image/svg+xml" sizes="any" href="assets/icon/inanity.svg">
<link rel="icon" type="apple-touch-icon" sizes="180x180" href="assets/icon/inanity-180.png">
<link rel="manifest" href=".webmanifest">

Expand Down

0 comments on commit f97406d

Please sign in to comment.