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
|
|
|
|
2021-04-23 18:14:54 +00:00
|
|
|
export interface Options {
|
2021-04-23 18:29:38 +00:00
|
|
|
ruffleEnable: boolean;
|
|
|
|
ignoreOptout: boolean;
|
2021-09-21 13:42:02 +00:00
|
|
|
warnOnUnsupportedContent: boolean;
|
2021-09-25 10:02:01 +00:00
|
|
|
logLevel: LogLevel;
|
2021-11-10 16:06:20 +00:00
|
|
|
showSwfDownload: boolean;
|
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> {
|
2023-02-20 14:20:50 +00:00
|
|
|
constructor(
|
|
|
|
private readonly checkbox: HTMLInputElement,
|
|
|
|
readonly label: HTMLLabelElement
|
|
|
|
) {}
|
2021-09-25 10:02:01 +00:00
|
|
|
|
|
|
|
get input() {
|
|
|
|
return this.checkbox;
|
|
|
|
}
|
|
|
|
|
|
|
|
get value() {
|
|
|
|
return this.checkbox.checked;
|
|
|
|
}
|
|
|
|
|
|
|
|
set value(value: boolean) {
|
|
|
|
this.checkbox.checked = value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class SelectOption implements OptionElement<string> {
|
2023-02-20 14:20:50 +00:00
|
|
|
constructor(
|
|
|
|
private readonly select: HTMLSelectElement,
|
|
|
|
readonly label: HTMLLabelElement
|
|
|
|
) {}
|
2021-09-25 10:02:01 +00:00
|
|
|
|
|
|
|
get input() {
|
|
|
|
return this.select;
|
|
|
|
}
|
|
|
|
|
|
|
|
get value() {
|
|
|
|
const index = this.select.selectedIndex;
|
2023-02-20 14:20:50 +00:00
|
|
|
const option = this.select.options[index]!;
|
2021-09-25 10:02:01 +00:00
|
|
|
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> {
|
2023-02-20 14:20:50 +00:00
|
|
|
const label = option.getElementsByTagName("label")[0]!;
|
2021-09-25 10:02:01 +00:00
|
|
|
|
|
|
|
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() {
|
2021-09-24 06:25:03 +00:00
|
|
|
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(
|
2021-04-23 18:29:38 +00:00
|
|
|
onChange?: (options: Options) => void
|
|
|
|
): Promise<void> {
|
2021-09-25 10:02:01 +00:00
|
|
|
const elements = findOptionElements();
|
2021-10-01 06:41:53 +00:00
|
|
|
const options = await utils.getOptions();
|
2021-03-09 19:51:03 +00:00
|
|
|
|
2021-09-25 10:02:01 +00:00
|
|
|
for (const [key, element] of elements.entries()) {
|
2021-09-24 06:25:03 +00:00
|
|
|
// Bind initial value.
|
2021-09-25 10:02:01 +00:00
|
|
|
element.value = options[key];
|
2021-09-24 06:25:03 +00:00
|
|
|
|
|
|
|
// 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
|
|
|
|
2021-09-24 06:38:37 +00:00
|
|
|
// Localize label.
|
2021-09-25 10:02:01 +00:00
|
|
|
const message = utils.i18n.getMessage(`settings_${element.input.id}`);
|
2021-09-24 06:38:37 +00:00
|
|
|
if (message) {
|
2021-09-25 10:02:01 +00:00
|
|
|
element.label.textContent = message;
|
2021-09-24 06:38:37 +00:00
|
|
|
}
|
2021-09-24 06:44:17 +00:00
|
|
|
|
|
|
|
// 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;
|
2021-09-24 06:44:17 +00:00
|
|
|
utils.storage.sync.set({ [key]: value });
|
|
|
|
});
|
2021-03-09 19:51:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Listen for future changes.
|
2021-04-23 18:29:38 +00:00
|
|
|
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)) {
|
2021-09-24 06:25:03 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|