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:
parent
bb926461a1
commit
34facdc5c2
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue