File

src/app/core/site-settings/site-settings.service.ts

Description

Access to site settings stored in the database, like styling, site name and logo.

Extends

LatestEntityLoader

Index

Properties
Methods

Constructor

constructor(title: Title, fileService: FileService, schemaService: EntitySchemaService, enumService: ConfigurableEnumService, entityMapper: EntityMapperService)
Parameters :
Name Type Optional
title Title No
fileService FileService No
schemaService EntitySchemaService No
enumService ConfigurableEnumService No
entityMapper EntityMapperService No

Methods

getPropertyObservable
getPropertyObservable(property: P)
Type parameters :
  • P
Parameters :
Name Type Optional
property P No
Returns : Observable<>
Async loadOnce
loadOnce()
Inherited from LatestEntityLoader

Do an initial load of the entity to be available through the entityUpdated property (without watching for continuous updates).

Returns : Promise<T | undefined>
Async startLoading
startLoading()
Inherited from LatestEntityLoader

Initialize the loader to make the entity available and emit continuous updates through the entityUpdated property

Returns : unknown

Properties

Readonly DEFAULT_FAVICON
Type : string
Default value : "favicon.ico"
defaultLanguage
Default value : this.getPropertyObservable("defaultLanguage")
displayLanguageSelect
Default value : this.getPropertyObservable("displayLanguageSelect")
Readonly SITE_SETTINGS_LOCAL_STORAGE_KEY
Default value : Entity.createPrefixedId( SiteSettings.ENTITY_TYPE, SiteSettings.ENTITY_ID, )
siteName
Default value : this.getPropertyObservable("siteName")
siteSettings
Default value : this.entityUpdated.pipe(shareReplay(1))
entityUpdated
Default value : new Subject<T>()
Inherited from LatestEntityLoader

subscribe to this and execute any actions required when the entity changes

import { Injectable } from "@angular/core";
import { SiteSettings } from "./site-settings";
import { delay, firstValueFrom, Observable, skipWhile } from "rxjs";
import { distinctUntilChanged, map, shareReplay } from "rxjs/operators";
import { Title } from "@angular/platform-browser";
import { FileService } from "../../features/file/file.service";
import materialColours from "@aytek/material-color-picker";
import { EntityMapperService } from "../entity/entity-mapper/entity-mapper.service";
import { LatestEntityLoader } from "../entity/latest-entity-loader";
import { Logging } from "../logging/logging.service";
import { Entity } from "../entity/model/entity";
import { EntitySchemaService } from "../entity/schema/entity-schema.service";
import { availableLocales } from "../language/languages";
import { ConfigurableEnumService } from "../basic-datatypes/configurable-enum/configurable-enum.service";

/**
 * Access to site settings stored in the database, like styling, site name and logo.
 */
@Injectable({
  providedIn: "root",
})
export class SiteSettingsService extends LatestEntityLoader<SiteSettings> {
  readonly DEFAULT_FAVICON = "favicon.ico";
  readonly SITE_SETTINGS_LOCAL_STORAGE_KEY = Entity.createPrefixedId(
    SiteSettings.ENTITY_TYPE,
    SiteSettings.ENTITY_ID,
  );

  siteSettings = this.entityUpdated.pipe(shareReplay(1));

  siteName = this.getPropertyObservable("siteName");
  defaultLanguage = this.getPropertyObservable("defaultLanguage");
  displayLanguageSelect = this.getPropertyObservable("displayLanguageSelect");

  constructor(
    private title: Title,
    private fileService: FileService,
    private schemaService: EntitySchemaService,
    private enumService: ConfigurableEnumService,
    entityMapper: EntityMapperService,
  ) {
    super(SiteSettings, SiteSettings.ENTITY_ID, entityMapper);

    this.initAvailableLocales();

    this.siteName.subscribe((name) => this.title.setTitle(name));
    this.subscribeFontChanges();
    this.subscribeFaviconChanges();
    this.subscribeColorChanges("primary");
    this.subscribeColorChanges("secondary");
    this.subscribeColorChanges("error");

    this.initFromLocalStorage();
    this.cacheInLocalStorage();

    super.startLoading();
  }

  /**
   * Making locales enum available at runtime
   * so that UI can show dropdown options
   * @private
   */
  private initAvailableLocales() {
    this.enumService["cacheEnum"](availableLocales);
  }

  /**
   * Do an initial loading of settings from localStorage, if available.
   * @private
   */
  private initFromLocalStorage() {
    let localStorageSettings: SiteSettings;

    try {
      const stored = localStorage.getItem(this.SITE_SETTINGS_LOCAL_STORAGE_KEY);
      if (stored) {
        localStorageSettings = this.schemaService.loadDataIntoEntity(
          new SiteSettings(),
          JSON.parse(stored),
        );
      }
    } catch (e) {
      Logging.debug(
        "SiteSettingsService: could not parse settings from localStorage: " + e,
      );
    }

    if (localStorageSettings) {
      this.entityUpdated.next(localStorageSettings);
    }
  }

  /**
   * Store the latest SiteSettings in localStorage to be available before login also.
   * @private
   */
  private cacheInLocalStorage() {
    this.entityUpdated.subscribe((settings) => {
      const dbFormat =
        this.schemaService.transformEntityToDatabaseFormat(settings);
      localStorage.setItem(
        this.SITE_SETTINGS_LOCAL_STORAGE_KEY,
        JSON.stringify(dbFormat),
      );
    });
  }

  private subscribeFontChanges() {
    this.getPropertyObservable("font").subscribe((font) =>
      document.documentElement.style.setProperty("--font-family", font),
    );
  }

  private subscribeFaviconChanges() {
    this.getPropertyObservable("favicon")
      .pipe(delay(0))
      .subscribe(async (icon) => {
        const favIcon: HTMLLinkElement = document.querySelector("#appIcon");
        if (icon) {
          const entity = await firstValueFrom(this.siteSettings);
          const imgUrl = await firstValueFrom(
            this.fileService.loadFile(entity, "favicon"),
          );
          favIcon.href = Object.values(imgUrl)[0];
        } else {
          favIcon.href = this.DEFAULT_FAVICON;
        }
      });
  }

  private subscribeColorChanges(property: "primary" | "secondary" | "error") {
    this.getPropertyObservable(property).subscribe((color) => {
      if (color) {
        const palette = materialColours(color);
        palette["A100"] = palette["200"];
        palette["A200"] = palette["300"];
        palette["A400"] = palette["500"];
        palette["A700"] = palette["800"];
        Object.entries(palette).forEach(([key, value]) =>
          document.documentElement.style.setProperty(
            `--${property}-${key}`,
            `#${value}`,
          ),
        );
      }
    });
  }

  getPropertyObservable<P extends keyof SiteSettings>(
    property: P,
  ): Observable<SiteSettings[P]> {
    return this.siteSettings.pipe(
      skipWhile((v) => !v[property]),
      map((s) => s[property]),
      distinctUntilChanged(),
    );
  }
}

results matching ""

    No results matching ""