diff --git a/web/packages/extension/assets/css/common.css b/web/packages/extension/assets/css/common.css index 3c856f85fce9..a1ca713deda8 100644 --- a/web/packages/extension/assets/css/common.css +++ b/web/packages/extension/assets/css/common.css @@ -47,13 +47,15 @@ body { margin-top: 24px; } -.option input { +.option input, +.option select { position: absolute; + right: 0; +} + +.option.checkbox input { width: 40px; height: 20px; - top: 0; - bottom: 0; - right: 0; margin: auto; cursor: pointer; z-index: 1; @@ -65,8 +67,8 @@ body { padding-right: 40px; } -.option label::before, -.option label::after { +.option.checkbox label::before, +.option.checkbox label::after { content: ""; position: absolute; border-radius: 10px; @@ -76,25 +78,25 @@ body { transition: background 0.2s, right 0.2s; } -.option label::before { +.option.checkbox label::before { height: 20px; width: 40px; right: 0; background: gray; } -.option label::after { +.option.checkbox label::after { height: 18px; width: 18px; right: 21px; background: silver; } -.option input:checked + label::before { +.option.checkbox input:checked + label::before { background: var(--ruffle-dark-orange); } -.option input:checked + label::after { +.option.checkbox input:checked + label::after { background: var(--ruffle-orange); right: 1px; } diff --git a/web/packages/extension/assets/options.html b/web/packages/extension/assets/options.html index 42fe81775f1a..3067244ac671 100644 --- a/web/packages/extension/assets/options.html +++ b/web/packages/extension/assets/options.html @@ -19,18 +19,26 @@
-
+
-
+
-
+
+
+ + +
diff --git a/web/packages/extension/assets/popup.html b/web/packages/extension/assets/popup.html index 0bcdbaa4b319..d6b8b882f195 100644 --- a/web/packages/extension/assets/popup.html +++ b/web/packages/extension/assets/popup.html @@ -23,11 +23,11 @@ Ruffle is running.
-
+
-
+
diff --git a/web/packages/extension/src/common.ts b/web/packages/extension/src/common.ts index 24fbb9cc5c56..43ebbffd6a7c 100644 --- a/web/packages/extension/src/common.ts +++ b/web/packages/extension/src/common.ts @@ -1,57 +1,122 @@ import * as utils from "./utils"; +import type { LogLevel } from "ruffle-core"; export interface Options { ruffleEnable: boolean; ignoreOptout: boolean; warnOnUnsupportedContent: boolean; + logLevel: LogLevel; } -function getBooleanElements() { +interface OptionElement { + readonly input: Element; + readonly label: HTMLLabelElement; + value: T; +} + +class CheckboxOption implements OptionElement { + 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 { + 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 { + 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()); - const elements = new Map< - keyof Options, - { option: Element; checkbox: HTMLInputElement; label: HTMLLabelElement } - >(); + const elements = new Map>(); for (const option of document.getElementsByClassName("option")) { - const [checkbox] = option.getElementsByTagName("input"); - if (checkbox.type !== "checkbox") { - continue; - } - const [label] = option.getElementsByTagName("label"); - const key = camelize(checkbox.id) as keyof Options; - elements.set(key, { option, checkbox, label }); + const element = getElement(option); + const key = camelize(element.input.id) as keyof Options; + elements.set(key, element); } return elements; } -export async function bindBooleanOptions( +export async function bindOptions( onChange?: (options: Options) => void ): Promise { - const elements = getBooleanElements(); + const elements = findOptionElements(); const options = await utils.getOptions(Array.from(elements.keys())); - for (const [key, { checkbox, label }] of elements.entries()) { + for (const [key, element] of elements.entries()) { // Bind initial value. - checkbox.checked = options[key]; + element.value = options[key]; // Prevent transition on load. // Method from https://stackoverflow.com/questions/11131875. - label.classList.add("notransition"); - label.offsetHeight; // Trigger a reflow, flushing the CSS changes. - label.classList.remove("notransition"); + element.label.classList.add("notransition"); + element.label.offsetHeight; // Trigger a reflow, flushing the CSS changes. + element.label.classList.remove("notransition"); // Localize label. - const message = utils.i18n.getMessage(`settings_${checkbox.id}`); + const message = utils.i18n.getMessage(`settings_${element.input.id}`); if (message) { - label.textContent = message; + element.label.textContent = message; } // Listen for user input. - checkbox.addEventListener("change", () => { - const value = checkbox.checked; - options[key] = value; + element.input.addEventListener("change", () => { + const value = element.value; + options[key] = value as never; utils.storage.sync.set({ [key]: value }); }); } @@ -67,8 +132,8 @@ export async function bindBooleanOptions( if (!element) { continue; } - element.checkbox.checked = option.newValue; - options[key as keyof Options] = option.newValue; + element.value = option.newValue; + options[key as keyof Options] = option.newValue as never; } if (onChange) { diff --git a/web/packages/extension/src/content.ts b/web/packages/extension/src/content.ts index cf7e44b43a52..830c5b47caec 100644 --- a/web/packages/extension/src/content.ts +++ b/web/packages/extension/src/content.ts @@ -110,6 +110,7 @@ function isXMLDocument(): boolean { "ruffleEnable", "ignoreOptout", "warnOnUnsupportedContent", + "logLevel", ]); const pageOptout = checkPageOptout(); const shouldLoad = @@ -175,6 +176,7 @@ function isXMLDocument(): boolean { type: "load", config: { warnOnUnsupportedContent: options.warnOnUnsupportedContent, + logLevel: options.logLevel, }, }); })(); diff --git a/web/packages/extension/src/options.ts b/web/packages/extension/src/options.ts index 31ba1a3a108b..81d657d11722 100644 --- a/web/packages/extension/src/options.ts +++ b/web/packages/extension/src/options.ts @@ -1,7 +1,7 @@ import * as utils from "./utils"; -import { bindBooleanOptions } from "./common"; +import { bindOptions } from "./common"; window.addEventListener("DOMContentLoaded", () => { document.title = utils.i18n.getMessage("settings_page"); - bindBooleanOptions(); + bindOptions(); }); diff --git a/web/packages/extension/src/player.ts b/web/packages/extension/src/player.ts index 26ad81bd983c..97380c2bc1f6 100644 --- a/web/packages/extension/src/player.ts +++ b/web/packages/extension/src/player.ts @@ -1,5 +1,5 @@ import * as utils from "./utils"; -import { PublicAPI, SourceAPI, Letterbox, LogLevel } from "ruffle-core"; +import { PublicAPI, SourceAPI, Letterbox } from "ruffle-core"; const api = PublicAPI.negotiate( window.RufflePlayer!, @@ -30,8 +30,7 @@ window.addEventListener("DOMContentLoaded", async () => { const config = { letterbox: Letterbox.On, - logLevel: LogLevel.Warn, - ...(await utils.getOptions(["warnOnUnsupportedContent"])), + ...(await utils.getOptions(["warnOnUnsupportedContent", "logLevel"])), }; player.load({ url: swfUrl, base: swfUrl, ...config }); }); diff --git a/web/packages/extension/src/popup.ts b/web/packages/extension/src/popup.ts index f35ed0f43a87..10133b668c66 100644 --- a/web/packages/extension/src/popup.ts +++ b/web/packages/extension/src/popup.ts @@ -1,5 +1,5 @@ import * as utils from "./utils"; -import { Options, bindBooleanOptions } from "./common"; +import { Options, bindOptions } from "./common"; let activeTab: chrome.tabs.Tab | browser.tabs.Tab; let savedOptions: Options; @@ -114,7 +114,7 @@ function displayTabStatus() { } window.addEventListener("DOMContentLoaded", () => { - bindBooleanOptions((options) => { + bindOptions((options) => { savedOptions = options; optionsChanged(); }); diff --git a/web/packages/extension/src/utils.ts b/web/packages/extension/src/utils.ts index 2b49bef4a0ce..636c32c30068 100644 --- a/web/packages/extension/src/utils.ts +++ b/web/packages/extension/src/utils.ts @@ -1,9 +1,11 @@ import { Options } from "./common"; +import { LogLevel } from "ruffle-core"; const DEFAULT_OPTIONS: Options = { ruffleEnable: true, ignoreOptout: false, warnOnUnsupportedContent: true, + logLevel: LogLevel.Error, }; export let i18n: {