extension: Use `Map` instead of `Record` to store option elements

This has few advantages:

* `Map` is more performant, and its keys cannot clash with builtin
JavaScript properties (e.g. `toString`).
* TypeScript has better type information about `map.keys()`, whereas
`Object.keys()` always return `string[]`.

Also, move `camelize` inside `getBooleanElements`, as it's only used
there, and unify the 2 `for` loops in `bindBooleanOptions` (iterating
`options` is wrong because it might contain options that doesn't exist
in the page).
This commit is contained in:
relrelb 2021-09-24 09:25:03 +03:00 committed by relrelb
parent bb926461a1
commit 34facdc5c2
1 changed files with 24 additions and 26 deletions

View File

@ -1,27 +1,26 @@
import * as utils from "./utils"; import * as utils from "./utils";
function camelize(s: string) {
return s.replace(/[^a-z\d](.)/gi, (_, char) => char.toUpperCase());
}
export interface Options { export interface Options {
ruffleEnable: boolean; ruffleEnable: boolean;
ignoreOptout: boolean; ignoreOptout: boolean;
} }
function getBooleanElements() { function getBooleanElements() {
const elements: Record< const camelize = (s: string) =>
string, s.replace(/[^a-z\d](.)/gi, (_, char) => char.toUpperCase());
const elements = new Map<
keyof Options,
{ option: Element; checkbox: HTMLInputElement; label: HTMLLabelElement } { option: Element; checkbox: HTMLInputElement; label: HTMLLabelElement }
> = {}; >();
for (const option of document.getElementsByClassName("option")) { for (const option of document.getElementsByClassName("option")) {
const [checkbox] = option.getElementsByTagName("input"); const [checkbox] = option.getElementsByTagName("input");
if (checkbox.type !== "checkbox") { if (checkbox.type !== "checkbox") {
continue; continue;
} }
const [label] = option.getElementsByTagName("label"); const [label] = option.getElementsByTagName("label");
const key = camelize(checkbox.id); const key = camelize(checkbox.id) as keyof Options;
elements[key] = { option, checkbox, label }; elements.set(key, { option, checkbox, label });
} }
return elements; return elements;
} }
@ -30,28 +29,26 @@ export async function bindBooleanOptions(
onChange?: (options: Options) => void onChange?: (options: Options) => void
): Promise<void> { ): Promise<void> {
const elements = getBooleanElements(); const elements = getBooleanElements();
const options = await utils.getOptions(Array.from(elements.keys()));
// Bind initial values. for (const [key, { checkbox, label }] of elements.entries()) {
const options = await utils.getOptions(Object.keys(elements)); // Bind initial value.
for (const [key, value] of Object.entries(options)) { checkbox.checked = options[key];
elements[key].checkbox.checked = value;
}
for (const [key, { checkbox, label }] of Object.entries(elements)) {
// TODO: click/change/input?
checkbox.addEventListener("click", () => {
const value = checkbox.checked;
options[key as keyof Options] = value;
utils.storage.sync.set({ [key]: value });
});
label.textContent = utils.i18n.getMessage(`settings_${checkbox.id}`);
// Prevent transition on load. // Prevent transition on load.
// Method from https://stackoverflow.com/questions/11131875. // Method from https://stackoverflow.com/questions/11131875.
label.classList.add("notransition"); label.classList.add("notransition");
label.offsetHeight; // Trigger a reflow, flushing the CSS changes. label.offsetHeight; // Trigger a reflow, flushing the CSS changes.
label.classList.remove("notransition"); label.classList.remove("notransition");
// TODO: click/change/input?
checkbox.addEventListener("click", () => {
const value = checkbox.checked;
options[key] = value;
utils.storage.sync.set({ [key]: value });
});
label.textContent = utils.i18n.getMessage(`settings_${checkbox.id}`);
} }
// Listen for future changes. // Listen for future changes.
@ -61,10 +58,11 @@ export async function bindBooleanOptions(
} }
for (const [key, option] of Object.entries(changes)) { for (const [key, option] of Object.entries(changes)) {
if (!elements[key]) { const element = elements.get(key as keyof Options);
if (!element) {
continue; continue;
} }
elements[key].checkbox.checked = option.newValue; element.checkbox.checked = option.newValue;
options[key as keyof Options] = option.newValue; options[key as keyof Options] = option.newValue;
} }