better-xcloud/src/utils/setting-element.ts
2024-12-07 16:48:58 +07:00

189 lines
6.9 KiB
TypeScript
Executable File

import type { PreferenceSetting } from "@/types/preferences";
import { CE, escapeCssSelector } from "@utils/html";
import type { PrefKey } from "@/enums/pref-keys";
import type { BaseSettingsStore } from "./settings-storages/base-settings-storage";
import { type BaseSettingDefinition, type MultipleOptionsParams, type NumberStepperParams } from "@/types/setting-definition";
import { BxEvent } from "./bx-event";
import { BxNumberStepper } from "@/web-components/bx-number-stepper";
export enum SettingElementType {
OPTIONS = 'options',
MULTIPLE_OPTIONS = 'multiple-options',
NUMBER_STEPPER = 'number-stepper',
CHECKBOX = 'checkbox',
}
interface BxBaseSettingElement {
setValue: (value: any) => void,
}
export interface BxHtmlSettingElement extends HTMLElement, BxBaseSettingElement {};
export interface BxSelectSettingElement extends HTMLSelectElement, BxBaseSettingElement {}
export class SettingElement {
private static renderOptions(key: string, setting: PreferenceSetting, currentValue: any, onChange: any): BxSelectSettingElement {
const $control = CE<BxSelectSettingElement>('select', {
// title: setting.label,
tabindex: 0,
});
let $parent: HTMLElement;
if (setting.optionsGroup) {
$parent = CE('optgroup', {
label: setting.optionsGroup,
});
$control.appendChild($parent);
} else {
$parent = $control;
}
for (let value in setting.options) {
const label = setting.options[value];
const $option = CE<HTMLOptionElement>('option', { value }, label);
$parent.appendChild($option);
}
$control.value = currentValue;
onChange && $control.addEventListener('input', e => {
const target = e.target as HTMLSelectElement;
const value = (setting.type && setting.type === 'number') ? parseInt(target.value) : target.value;
!(e as any).ignoreOnChange && onChange(e, value);
});
// Custom method
$control.setValue = (value: any) => {
$control.value = value;
};
return $control;
}
private static renderMultipleOptions(key: string, setting: PreferenceSetting, currentValue: any, onChange: any, params: MultipleOptionsParams={}): BxSelectSettingElement {
const $control = CE<BxSelectSettingElement>('select', {
// title: setting.label,
multiple: true,
tabindex: 0,
});
const totalOptions = Object.keys(setting.multipleOptions!).length;
const size = params.size ? Math.min(params.size, totalOptions) : totalOptions;
$control.setAttribute('size', size.toString());
for (const value in setting.multipleOptions) {
const label = setting.multipleOptions[value];
const $option = CE<HTMLOptionElement>('option', { value }, label) as HTMLOptionElement;
$option.selected = currentValue.indexOf(value) > -1;
$option.addEventListener('mousedown', function(e) {
e.preventDefault();
const target = e.target as HTMLOptionElement;
target.selected = !target.selected;
const $parent = target.parentElement!;
$parent.focus();
BxEvent.dispatch($parent, 'input');
});
$control.appendChild($option);
}
$control.addEventListener('mousedown', function(e) {
const self = this;
const orgScrollTop = self.scrollTop;
window.setTimeout(() => (self.scrollTop = orgScrollTop), 0);
});
$control.addEventListener('mousemove', e => e.preventDefault());
onChange && $control.addEventListener('input', (e: Event) => {
const target = e.target as HTMLSelectElement
const values = Array.from(target.selectedOptions).map(i => i.value);
!(e as any).ignoreOnChange && onChange(e, values);
});
return $control;
}
private static renderCheckbox(key: string, setting: PreferenceSetting, currentValue: any, onChange: any) {
const $control = CE('input', { type: 'checkbox', tabindex: 0 }) as HTMLInputElement;
$control.checked = currentValue;
onChange && $control.addEventListener('input', e => {
!(e as any).ignoreOnChange && onChange(e, (e.target as HTMLInputElement).checked);
});
($control as any).setValue = (value: boolean) => {
$control.checked = !!value;
};
return $control;
}
private static renderNumberStepper(key: string, setting: PreferenceSetting, value: any, onChange: any, options: NumberStepperParams={}) {
const $control = BxNumberStepper.create(key, value, setting.min!, setting.max!, options, onChange);
return $control;
}
private static readonly METHOD_MAP = {
[SettingElementType.OPTIONS]: SettingElement.renderOptions,
[SettingElementType.MULTIPLE_OPTIONS]: SettingElement.renderMultipleOptions,
[SettingElementType.NUMBER_STEPPER]: SettingElement.renderNumberStepper,
[SettingElementType.CHECKBOX]: SettingElement.renderCheckbox,
};
static render(type: SettingElementType, key: string, setting: BaseSettingDefinition, currentValue: any, onChange: any, options: any) {
const method = SettingElement.METHOD_MAP[type];
// @ts-ignore
const $control = method(...Array.from(arguments).slice(1)) as HTMLElement;
if (type !== SettingElementType.NUMBER_STEPPER) {
$control.id = `bx_setting_${escapeCssSelector(key)}`;
}
// Add "name" property to "select" elements
if (type === SettingElementType.OPTIONS || type === SettingElementType.MULTIPLE_OPTIONS) {
($control as HTMLSelectElement).name = $control.id;
}
return $control;
}
static fromPref(key: PrefKey, storage: BaseSettingsStore, onChange: any, overrideParams={}) {
const definition = storage.getDefinition(key);
let currentValue = storage.getSetting(key);
let type;
if ('options' in definition) {
type = SettingElementType.OPTIONS;
} else if ('multipleOptions' in definition) {
type = SettingElementType.MULTIPLE_OPTIONS;
} else if (typeof definition.default === 'number') {
type = SettingElementType.NUMBER_STEPPER;
} else {
type = SettingElementType.CHECKBOX;
}
let params: any = {};
if ('params' in definition) {
params = Object.assign(overrideParams, definition.params || {});
}
if (params.disabled) {
currentValue = definition.default;
}
const $control = SettingElement.render(type!, key as string, definition, currentValue, (e: any, value: any) => {
storage.setSetting(key, value);
onChange && onChange(e, value);
}, params);
return $control;
}
}