nup/web/config.ts

127 lines
3.3 KiB
TypeScript

// Copyright 2015 Daniel Erat.
// All rights reserved.
// Names to pass to Config.get() or Config.set().
export enum Pref {
THEME = 'theme',
FULLSCREEN_MODE = 'fullscreenMode',
GAIN_TYPE = 'gainType',
PRE_AMP = 'preAmp',
BASS_FREQ = 'bassFreq',
BASS_GAIN = 'bassGain',
}
// Values for Pref.THEME.
export enum Theme {
AUTO = 0,
LIGHT = 1,
DARK = 2,
}
// Values for Pref.FULLSCREEN_MODE.
export enum FullscreenMode {
SCREEN = 0,
WINDOW = 1,
}
// Values for Pref.GAIN_TYPE.
export enum GainType {
ALBUM = 0,
TRACK = 1,
NONE = 2,
AUTO = 3,
}
// localStorage key; exported for tests.
export const ConfigKey = 'config';
const FLOAT_NAMES = new Set([Pref.PRE_AMP, Pref.BASS_FREQ, Pref.BASS_GAIN]);
const INT_NAMES = new Set([Pref.THEME, Pref.FULLSCREEN_MODE, Pref.GAIN_TYPE]);
// Config provides persistent storage for preferences.
export class Config {
#callbacks: ConfigCallback[] = [];
#values: Record<Pref, any> = {
[Pref.THEME]: Theme.AUTO,
[Pref.FULLSCREEN_MODE]: FullscreenMode.SCREEN,
[Pref.GAIN_TYPE]: GainType.AUTO,
[Pref.PRE_AMP]: 0,
[Pref.BASS_FREQ]: 100,
[Pref.BASS_GAIN]: 0,
};
constructor() {
this.#load();
}
// Adds a function that will be invoked whenever a preference changes.
//
// |cb| will be invoked with two arguments: a string containing the pref's
// name (see constants above) and an appropriately-typed second argument
// containing the pref's value.
addCallback(cb: ConfigCallback) {
this.#callbacks.push(cb);
}
// Gets the value of the preference identified by |name|. An error is thrown
// if an invalid name is supplied.
get(name: Pref): any {
if (this.#values.hasOwnProperty(name)) return this.#values[name];
throw new Error(`Unknown pref "${name}"`);
}
// Sets |name| to |value|. An error is thrown if an invalid name is supplied
// or the value is of an inappropriate type.
set(name: Pref, value: any) {
let parsed = 0;
if (FLOAT_NAMES.has(name)) {
parsed = parseFloat(value);
if (isNaN(parsed)) throw new Error(`Non-float "${name}" "${value}"`);
} else if (INT_NAMES.has(name)) {
parsed = parseInt(value);
if (isNaN(parsed)) throw new Error(`Non-int "${name}" "${value}"`);
} else {
throw new Error(`Unknown pref "${name}"`);
}
this.#values[name] = parsed;
this.#callbacks.forEach((cb) => cb(name, parsed));
}
// Loads and validates prefs from local storage.
#load() {
const json = localStorage.getItem(ConfigKey);
if (!json) return;
let loaded = {};
try {
loaded = JSON.parse(json);
} catch (e) {
console.error(`Ignoring bad config ${JSON.stringify(json)}: ${e}`);
return;
}
Object.entries(loaded).forEach(([name, value]) => {
try {
this.set(name as Pref, value);
} catch (e) {
console.error(`Skipping bad pref "${name}": ${e}`);
}
});
}
// Saves all prefs to local storage.
save() {
localStorage.setItem(ConfigKey, JSON.stringify(this.#values));
}
}
type ConfigCallback = (name: Pref, value: number) => void;
let defaultConfig: Config | null = null;
// Returns a default singleton Config instance.
export function getConfig(): Config {
if (!defaultConfig) defaultConfig = new Config();
return defaultConfig;
}