ruffle/web/packages/extension/src/common.ts

148 lines
4.0 KiB
TypeScript
Raw Normal View History

2021-03-09 19:51:03 +00:00
import * as utils from "./utils";
2021-09-25 10:02:01 +00:00
import type { LogLevel } from "ruffle-core";
2021-03-09 19:51:03 +00:00
export interface Options {
ruffleEnable: boolean;
ignoreOptout: boolean;
warnOnUnsupportedContent: boolean;
2021-09-25 10:02:01 +00:00
logLevel: LogLevel;
2021-03-09 19:51:03 +00:00
}
2021-09-25 10:02:01 +00:00
interface OptionElement<T> {
readonly input: Element;
readonly label: HTMLLabelElement;
value: T;
}
class CheckboxOption implements OptionElement<boolean> {
private checkbox: HTMLInputElement;
readonly label: HTMLLabelElement;
constructor(checkbox: HTMLInputElement, label: HTMLLabelElement) {
this.checkbox = checkbox;
this.label = label;
}
get input() {
return this.checkbox;
}
get value() {
return this.checkbox.checked;
}
set value(value: boolean) {
this.checkbox.checked = value;
}
}
class SelectOption implements OptionElement<string> {
private select: HTMLSelectElement;
readonly label: HTMLLabelElement;
constructor(select: HTMLSelectElement, label: HTMLLabelElement) {
this.select = select;
this.label = label;
}
get input() {
return this.select;
}
get value() {
const index = this.select.selectedIndex;
const option = this.select.options[index];
return option.value;
}
set value(value: string) {
const options = Array.from(this.select.options);
const index = options.findIndex((option) => option.value === value);
this.select.selectedIndex = index;
}
}
function getElement(option: Element): OptionElement<unknown> {
const [label] = option.getElementsByTagName("label");
const [checkbox] = option.getElementsByTagName("input");
if (checkbox && checkbox.type === "checkbox") {
return new CheckboxOption(checkbox, label);
}
const [select] = option.getElementsByTagName("select");
if (select) {
return new SelectOption(select, label);
}
throw new Error("Unknown option element");
}
function findOptionElements() {
const camelize = (s: string) =>
s.replace(/[^a-z\d](.)/gi, (_, char) => char.toUpperCase());
2021-09-25 10:02:01 +00:00
const elements = new Map<keyof Options, OptionElement<unknown>>();
2021-03-09 19:51:03 +00:00
for (const option of document.getElementsByClassName("option")) {
2021-09-25 10:02:01 +00:00
const element = getElement(option);
const key = camelize(element.input.id) as keyof Options;
elements.set(key, element);
2021-03-09 19:51:03 +00:00
}
return elements;
}
2021-09-25 10:02:01 +00:00
export async function bindOptions(
onChange?: (options: Options) => void
): Promise<void> {
2021-09-25 10:02:01 +00:00
const elements = findOptionElements();
const options = await utils.getOptions(Array.from(elements.keys()));
2021-03-09 19:51:03 +00:00
2021-09-25 10:02:01 +00:00
for (const [key, element] of elements.entries()) {
// Bind initial value.
2021-09-25 10:02:01 +00:00
element.value = options[key];
// Prevent transition on load.
// Method from https://stackoverflow.com/questions/11131875.
2021-09-25 10:02:01 +00:00
element.label.classList.add("notransition");
element.label.offsetHeight; // Trigger a reflow, flushing the CSS changes.
element.label.classList.remove("notransition");
2021-03-09 19:51:03 +00:00
// Localize label.
2021-09-25 10:02:01 +00:00
const message = utils.i18n.getMessage(`settings_${element.input.id}`);
if (message) {
2021-09-25 10:02:01 +00:00
element.label.textContent = message;
}
// Listen for user input.
2021-09-25 10:02:01 +00:00
element.input.addEventListener("change", () => {
const value = element.value;
options[key] = value as never;
utils.storage.sync.set({ [key]: value });
});
2021-03-09 19:51:03 +00:00
}
// Listen for future changes.
utils.storage.onChanged.addListener((changes, namespace) => {
2021-03-09 19:51:03 +00:00
if (namespace !== "sync") {
return;
}
for (const [key, option] of Object.entries(changes)) {
const element = elements.get(key as keyof Options);
if (!element) {
2021-03-09 19:51:03 +00:00
continue;
}
2021-09-25 10:02:01 +00:00
element.value = option.newValue;
options[key as keyof Options] = option.newValue as never;
2021-03-09 19:51:03 +00:00
}
if (onChange) {
onChange(options);
}
});
if (onChange) {
onChange(options);
}
}