Improve settings page

This commit is contained in:
Andras Schmelczer 2023-05-27 13:35:41 +01:00
commit cd9aff0362
No known key found for this signature in database
GPG key ID: FC8F2C3D3D1A718C
4 changed files with 89 additions and 26 deletions

View file

@ -0,0 +1,44 @@
export class CollapsiblePanelAnimator {
private isOpen = false;
public onOpen: () => unknown = () => {};
public onClose: () => unknown = () => {};
public constructor(
infoButton: HTMLButtonElement,
private readonly infoPage: HTMLElement
) {
infoButton.addEventListener('click', this.toggle.bind(this));
window.addEventListener('click', (event) => {
if ([infoButton, this.infoPage].includes(event.target as HTMLElement)) {
return;
}
if (this.infoPage.contains(event.target as Node)) {
return;
}
this.close();
});
}
public open() {
this.isOpen = true;
this.infoPage.classList.remove('hidden');
this.onOpen();
}
public close() {
this.isOpen = false;
this.infoPage.classList.add('hidden');
this.onClose();
}
public toggle() {
if (this.isOpen) {
this.close();
} else {
this.open();
}
}
}

View file

@ -1,10 +0,0 @@
export class InfoPageHandler {
public constructor(
private readonly infoButton: HTMLButtonElement,
private readonly infoPage: HTMLElement
) {
infoButton.addEventListener('click', () => {
infoPage.classList.toggle('hidden');
});
}
}

View file

@ -1,9 +1,8 @@
export const persist = <T extends Record<string, number>>(wrapee: T): T => { export const persist = <T extends Record<string, number>>(wrapee: T): T => {
const initialState = { ...wrapee };
const keys = Object.keys(wrapee); const keys = Object.keys(wrapee);
const keysToShortKeys = Object.fromEntries( const keysToShortKeys = Object.fromEntries(
keys.map((key, i) => [key, String.fromCharCode(65 + i)]) keys.map((key, i) => [key, String.fromCharCode(97 + i)])
); );
const params = new URLSearchParams(window.location.search); const params = new URLSearchParams(window.location.search);
@ -25,11 +24,7 @@ export const persist = <T extends Record<string, number>>(wrapee: T): T => {
set: (target, key: string, value: number) => { set: (target, key: string, value: number) => {
const params = new URLSearchParams(window.location.search); const params = new URLSearchParams(window.location.search);
if (initialState[key] === value) {
params.delete(keysToShortKeys[key]);
} else {
params.set(keysToShortKeys[key], value.toString()); params.set(keysToShortKeys[key], value.toString());
}
(target as any)[key] = value; (target as any)[key] = value;

View file

@ -1,11 +1,19 @@
import { formatNumber } from './format-number'; import { formatNumber } from './format-number';
export enum ValueScaling {
Linear,
Quadratic,
Logarithmic,
}
export interface SliderConfiguration { export interface SliderConfiguration {
min: number; min: number;
max: number; max: number;
unit?: string; unit?: string;
step?: number; step?: number;
onChangeCallback?: (value: number) => unknown; onChangeCallback?: (value: number) => unknown;
scaling: ValueScaling;
rounding: (value: number) => number;
} }
export class SettingsSlider<T extends Record<string, number>> { export class SettingsSlider<T extends Record<string, number>> {
@ -17,6 +25,8 @@ export class SettingsSlider<T extends Record<string, number>> {
private readonly config: SliderConfiguration = { private readonly config: SliderConfiguration = {
min: 0, min: 0,
max: 1, max: 1,
scaling: ValueScaling.Linear,
rounding: (value) => value,
}; };
public constructor( public constructor(
@ -24,7 +34,7 @@ export class SettingsSlider<T extends Record<string, number>> {
private readonly settingName: keyof T & string, private readonly settingName: keyof T & string,
config: Partial<SliderConfiguration> = {} config: Partial<SliderConfiguration> = {}
) { ) {
this.slider = SettingsSlider.createSlider(this.settings[this.settingName]); this.slider = SettingsSlider.createSlider();
this.valueDisplay = SettingsSlider.createValueDisplay(); this.valueDisplay = SettingsSlider.createValueDisplay();
this.sliderWrapper = SettingsSlider.createSliderWrapper( this.sliderWrapper = SettingsSlider.createSliderWrapper(
this.settingName, this.settingName,
@ -37,12 +47,9 @@ export class SettingsSlider<T extends Record<string, number>> {
this.updateConfig(config); this.updateConfig(config);
} }
private static createSlider(initialValue: any) { private static createSlider() {
const input = document.createElement('input'); const input = document.createElement('input');
input.type = 'range'; input.type = 'range';
input.value = initialValue.toString();
return input; return input;
} }
@ -75,7 +82,9 @@ export class SettingsSlider<T extends Record<string, number>> {
} }
private onChange() { private onChange() {
this.settings[this.settingName] = Number(this.slider.value) as any; this.settings[this.settingName] = this.config.rounding(
this.inverseScaling(Number(this.slider.value))
) as any;
this.config.onChangeCallback?.(this.settings[this.settingName]); this.config.onChangeCallback?.(this.settings[this.settingName]);
this.valueDisplay.innerText = formatNumber( this.valueDisplay.innerText = formatNumber(
this.settings[this.settingName], this.settings[this.settingName],
@ -88,11 +97,14 @@ export class SettingsSlider<T extends Record<string, number>> {
if (this.config.step === undefined) { if (this.config.step === undefined) {
this.config.step = this.config.step =
(this.config.max - this.config.min) / SettingsSlider.DEFAULT_STEP_COUNT; this.scaling(this.config.max - this.scaling(this.config.min)) /
SettingsSlider.DEFAULT_STEP_COUNT;
} }
this.slider.min = this.config.min.toString(); this.slider.value = this.scaling(this.settings[this.settingName]).toString();
this.slider.max = this.config.max.toString(); this.slider.min = this.scaling(this.config.min).toString();
this.slider.max = this.scaling(this.config.max).toString();
this.slider.step = this.config.step.toString(); this.slider.step = this.config.step.toString();
this.onChange(); this.onChange();
@ -101,4 +113,26 @@ export class SettingsSlider<T extends Record<string, number>> {
public get element(): HTMLElement { public get element(): HTMLElement {
return this.sliderWrapper; return this.sliderWrapper;
} }
private get scaling(): (value: number) => number {
switch (this.config.scaling) {
case ValueScaling.Linear:
return (value) => value;
case ValueScaling.Quadratic:
return (value) => Math.sqrt(value);
case ValueScaling.Logarithmic:
return (value) => Math.log10(value);
}
}
private get inverseScaling(): (value: number) => number {
switch (this.config.scaling) {
case ValueScaling.Linear:
return (value) => value;
case ValueScaling.Quadratic:
return (value) => Math.pow(value, 2);
case ValueScaling.Logarithmic:
return (value) => Math.pow(10, value);
}
}
} }