Skip to content

Commit 2506b4f

Browse files
fehmerMiodec
andauthored
impr(settings): settings group performance (@fehmer, @Miodec) (monkeytypegame#6509)
Co-authored-by: Miodec <[email protected]>
1 parent ac33789 commit 2506b4f

File tree

3 files changed

+171
-136
lines changed

3 files changed

+171
-136
lines changed

frontend/src/ts/elements/settings/settings-group.ts

Lines changed: 88 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ export default class SettingsGroup<T extends ConfigValue> {
1313
public mode: Mode;
1414
public setCallback?: () => void;
1515
public updateCallback?: () => void;
16+
private elements: Element[];
17+
1618
constructor(
1719
configName: string,
1820
configFunction: (param: T, nosave?: boolean) => boolean,
@@ -27,32 +29,48 @@ export default class SettingsGroup<T extends ConfigValue> {
2729
this.setCallback = setCallback;
2830
this.updateCallback = updateCallback;
2931

30-
this.updateUI();
31-
3232
if (this.mode === "select") {
33-
const selectElement = document.querySelector(
33+
const el = document.querySelector(
3434
`.pageSettings .section[data-config-name=${this.configName}] select`
3535
);
36-
selectElement?.addEventListener("change", (e) => {
36+
37+
if (el === null) {
38+
throw new Error(`Failed to find select element for ${configName}`);
39+
}
40+
41+
if (el.hasAttribute("multiple")) {
42+
throw new Error(
43+
"multi-select dropdowns not supported. Config: " + this.configName
44+
);
45+
}
46+
47+
el.addEventListener("change", (e) => {
3748
const target = $(e.target as HTMLSelectElement);
3849
if (target.hasClass("disabled") || target.hasClass("no-auto-handle")) {
3950
return;
4051
}
52+
4153
this.setValue(target.val() as T);
4254
});
55+
56+
this.elements = [el];
4357
} else if (this.mode === "button") {
44-
$(".pageSettings").on(
45-
"click",
46-
`.section[data-config-name='${this.configName}'] button`,
47-
(e) => {
48-
const target = $(e.currentTarget);
58+
const els = document.querySelectorAll(`
59+
.pageSettings .section[data-config-name=${this.configName}] button`);
60+
61+
if (els.length === 0) {
62+
throw new Error(`Failed to find a button element for ${configName}`);
63+
}
64+
65+
for (const button of els) {
66+
button.addEventListener("click", (e) => {
4967
if (
50-
target.hasClass("disabled") ||
51-
target.hasClass("no-auto-handle")
68+
button.classList.contains("disabled") ||
69+
button.classList.contains("no-auto-handle")
5270
) {
5371
return;
5472
}
55-
const value = target.attr(`data-config-value`);
73+
const value = button.getAttribute("data-config-value");
5674
if (value === undefined || value === "") {
5775
console.error(
5876
`Failed to handle settings button click for ${configName}: data-${configName} is missing or empty.`
@@ -67,82 +85,105 @@ export default class SettingsGroup<T extends ConfigValue> {
6785
if (typed === "true") typed = true as T;
6886
if (typed === "false") typed = false as T;
6987
this.setValue(typed);
70-
}
71-
);
88+
});
89+
}
90+
91+
this.elements = Array.from(els);
7292
} else if (this.mode === "range") {
73-
const rangeElement = document.querySelector(
93+
const el = document.querySelector(
7494
`.pageSettings .section[data-config-name=${this.configName}] input[type=range]`
7595
);
7696

77-
if (!rangeElement) {
78-
Notifications.add(`Failed to find range element for ${configName}`, -1);
79-
return;
97+
if (el === null) {
98+
throw new Error(`Failed to find range element for ${configName}`);
8099
}
81100

82101
const debounced = debounce<(val: T) => void>(250, (val) => {
83102
this.setValue(val);
84103
});
85104

86-
rangeElement.addEventListener("input", (e) => {
87-
const target = $(e.target as HTMLInputElement);
88-
if (target.hasClass("disabled") || target.hasClass("no-auto-handle")) {
105+
el.addEventListener("input", (e) => {
106+
if (
107+
el.classList.contains("disabled") ||
108+
el.classList.contains("no-auto-handle")
109+
) {
89110
return;
90111
}
91-
const val = parseFloat(target.val() as string) as unknown as T;
112+
const val = parseFloat((el as HTMLInputElement).value) as unknown as T;
92113
this.updateUI(val);
93114
debounced(val);
94115
});
116+
117+
this.elements = [el];
118+
} else {
119+
this.elements = [];
95120
}
121+
122+
if (this.elements.length === 0 || this.elements === undefined) {
123+
throw new Error(
124+
`Failed to find elements for ${configName} with mode ${mode}`
125+
);
126+
}
127+
128+
this.updateUI();
96129
}
97130

98131
setValue(value: T): void {
132+
if (Config[this.configName as keyof typeof Config] === value) {
133+
return;
134+
}
99135
this.configFunction(value);
100136
this.updateUI();
101137
if (this.setCallback) this.setCallback();
102138
}
103139

104140
updateUI(valueOverride?: T): void {
105-
this.configValue =
141+
const newValue =
106142
valueOverride ?? (Config[this.configName as keyof typeof Config] as T);
107-
$(
108-
`.pageSettings .section[data-config-name='${this.configName}'] button`
109-
).removeClass("active");
110-
if (this.mode === "select") {
111-
const select = document.querySelector<HTMLSelectElement>(
112-
`.pageSettings .section[data-config-name='${this.configName}'] select`
113-
);
114143

115-
if (select === null) {
144+
if (this.mode === "select") {
145+
const select = this.elements?.[0] as HTMLSelectElement | null | undefined;
146+
if (!select) {
116147
return;
117148
}
118149

119-
select.value = this.configValue as string;
120-
121150
//@ts-expect-error this is fine, slimselect adds slim to the element
122151
const ss = select.slim as SlimSelect | undefined;
123-
ss?.store.setSelectedBy("value", [this.configValue as string]);
124-
ss?.render.renderValues();
125-
ss?.render.renderOptions(ss.store.getData());
152+
if (ss !== undefined) {
153+
const currentSelected = ss.getSelected()[0] ?? null;
154+
if (newValue !== currentSelected) {
155+
ss.setSelected(newValue as string);
156+
}
157+
} else {
158+
if (select.value !== newValue) select.value = newValue as string;
159+
}
126160
} else if (this.mode === "button") {
127-
$(
128-
// this cant be an object?
129-
// eslint-disable-next-line @typescript-eslint/no-base-to-string
130-
`.pageSettings .section[data-config-name='${this.configName}'] button[data-config-value='${this.configValue}']`
131-
).addClass("active");
161+
for (const button of this.elements) {
162+
let value = button.getAttribute("data-config-value");
163+
164+
let typed = value as T;
165+
if (typed === "true") typed = true as T;
166+
if (typed === "false") typed = false as T;
167+
168+
if (typed !== newValue) {
169+
button.classList.remove("active");
170+
} else {
171+
button.classList.add("active");
172+
}
173+
}
132174
} else if (this.mode === "range") {
133-
const range = document.querySelector<HTMLInputElement>(
134-
`.pageSettings .section[data-config-name='${this.configName}'] input[type=range]`
135-
);
175+
const range = this.elements?.[0] as HTMLInputElement | null | undefined;
176+
136177
const rangeValue = document.querySelector(
137178
`.pageSettings .section[data-config-name='${this.configName}'] .value`
138179
);
139180

140-
if (range === null || rangeValue === null) {
181+
if (range === undefined || range === null || rangeValue === null) {
141182
return;
142183
}
143184

144-
range.value = this.configValue as unknown as string;
145-
rangeValue.textContent = `${(this.configValue as number).toFixed(1)}`;
185+
range.value = newValue as unknown as string;
186+
rangeValue.textContent = `${(newValue as number).toFixed(1)}`;
146187
}
147188
if (this.updateCallback) this.updateCallback();
148189
}

0 commit comments

Comments
 (0)