Skip to content

Commit 0c7d662

Browse files
committed
[UX] Further rendering and tab-switching performance improvement for very large site lists.
1 parent cd265fc commit 0c7d662

4 files changed

Lines changed: 95 additions & 48 deletions

File tree

src/ui/options.js

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ document.querySelector("#version").textContent = _("Version",
207207
UI.updateSettings({policy, contextStore});
208208
}
209209

210-
presetsUI.render([""]);
210+
await presetsUI.render([""]);
211211
window.setTimeout(() => {
212212
let def = parent.querySelector('input.preset[value="DEFAULT"]');
213213
def.checked = true;
@@ -337,17 +337,18 @@ document.querySelector("#version").textContent = _("Version",
337337
validate();
338338
newSiteInput.addEventListener("input", validate);
339339

340-
newSiteForm.addEventListener("submit", e => {
340+
newSiteForm.addEventListener("submit", async (e) => {
341341
e.preventDefault();
342342
e.stopPropagation();
343343
let site = newSiteInput.value.trim();
344344
let valid = Sites.isValid(site);
345345
if (valid && canAdd(site)) {
346346
currentPolicy.set(site, currentPolicy.TRUSTED);
347347
UI.updateSettings({policy, contextStore});
348-
newSiteInput.value = "";
349-
sitesUI.render(currentPolicy.sites);
350-
sitesUI.hilite(site);
348+
const rendering = sitesUI.render(currentPolicy.sites);
349+
validate(); // apply filter and disable button
350+
await rendering;
351+
sitesUI.hilite(site); // display and focus
351352
sitesUI.onChange();
352353
}
353354
}, true);

src/ui/popup.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -548,7 +548,7 @@ addEventListener("unload", e => {
548548
});
549549
}
550550

551-
sitesUI.render(sites);
551+
await sitesUI.render(sites);
552552
window.scrollTo(0, 0);
553553
window.setTimeout(() => sitesUI.focus(), 10);
554554
}

src/ui/ui.css

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,6 @@ span.preset {
354354
font-size: 1em;
355355
vertical-align: baseline;
356356
}
357-
358357
.presets label.preset {
359358
letter-spacing: -0.06em;
360359
width: 0;
@@ -393,7 +392,9 @@ span.preset {
393392
.site.filtered, site.filtered {
394393
display: none;
395394
}
396-
395+
.site.overflown {
396+
display: none;
397+
}
397398
.site-controls {
398399
display: flex;
399400
flex-direction: row;

src/ui/ui.js

Lines changed: 85 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -253,10 +253,10 @@ var UI = (() => {
253253

254254
hilite(el) {
255255
el.classList.add("hilite");
256+
el.scrollIntoView();
256257
window.setTimeout(() => {
257258
el.classList.remove("hilite");
258259
el.classList.add("hilite-end");
259-
el.scrollIntoView();
260260
window.setTimeout(() => {
261261
el.classList.remove("hilite-end");
262262
}, 1000)
@@ -934,49 +934,94 @@ var UI = (() => {
934934
}
935935

936936
render(sites = this.sites, sorter = this.sorter) {
937-
if (sites) this._populate(sites, sorter);
938-
requestAnimationFrame(() => {
939-
const { parentNode } = this;
940-
parentNode.innerHTML = "";
941-
if (!sites) return;
942-
parentNode.appendChild(this.fragment);
943-
const root = parentNode.querySelector(".sites");
944-
if (!root.wiredBy) {
945-
root.addEventListener("keydown", (e) => this._keyNavHandler(e), true);
946-
root.addEventListener(
947-
"keyup",
948-
(e) => {
949-
// we use a keyup listener to open the customizer from other presets
950-
// because space repetition may lead to unintendedly "click" on the
951-
// first cap checkbox once focused from keydown
952-
switch (e.code) {
953-
case "Space": {
954-
let focused = document.activeElement;
955-
if (focused.matches(".site .preset")) {
956-
focused
957-
.closest(".site")
958-
.querySelector(".preset[value='CUSTOM']")
959-
.click();
960-
e.preventDefault();
961-
}
937+
if (sites) {
938+
this._populate(sites, sorter);
939+
}
940+
return new Promise(resolve => {
941+
requestAnimationFrame(() => {
942+
const { parentNode } = this;
943+
parentNode.innerHTML = "";
944+
if (!sites) {
945+
resolve();
946+
return;
947+
}
948+
949+
const PAGE_SIZE = 50;
950+
const unfiltered = this.list.querySelectorAll(".site:not(.filtered)");
951+
if (unfiltered.length > PAGE_SIZE) {
952+
let timeout;
953+
let overflowIdx;
954+
const resetOverflow = () => {
955+
clearTimeout(timeout);
956+
overflowIdx = PAGE_SIZE;
957+
for (let j = unfiltered.length; j-- > overflowIdx;) {
958+
unfiltered[j].classList.add("overflown");
959+
}
960+
};
961+
resetOverflow();
962+
963+
const updateOverflow = () => {
964+
clearTimeout(timeout);
965+
requestAnimationFrame(() => {
966+
const end = Math.min(unfiltered.length, overflowIdx + PAGE_SIZE);
967+
for (let j = overflowIdx; j < end; j++) {
968+
unfiltered[j].classList.remove("overflown");
969+
}
970+
if (end <= unfiltered.length) {
971+
overflowIdx += PAGE_SIZE;
972+
timeout = setTimeout(updateOverflow, 10);
962973
}
974+
});
975+
};
976+
timeout = setTimeout(updateOverflow, 20);
977+
978+
(this.resizeObserver || new ResizeObserver(entries => {
979+
if (entries[0].contentRect.height == 0) {
980+
resetOverflow();
981+
} else {
982+
updateOverflow();
963983
}
964-
},
965-
true
966-
);
967-
root.addEventListener("click", this, true);
968-
root.addEventListener("change", this, true);
969-
root.wiredBy = this;
970-
971-
if (!UA.mobile) {
972-
return;
984+
})).observe(this.list);
973985
}
974-
const siteWidth = root.querySelector(".site")?.offsetWidth + 12;
975-
const viewportWidth = document.documentElement.clientWidth;
976-
if (viewportWidth < siteWidth) {
977-
document.documentElement.style.zoom = viewportWidth / siteWidth;
986+
parentNode.appendChild(this.fragment);
987+
resolve();
988+
const root = parentNode.querySelector(".sites");
989+
if (!root.wiredBy) {
990+
root.addEventListener("keydown", (e) => this._keyNavHandler(e), true);
991+
root.addEventListener(
992+
"keyup",
993+
(e) => {
994+
// we use a keyup listener to open the customizer from other presets
995+
// because space repetition may lead to unintendedly "click" on the
996+
// first cap checkbox once focused from keydown
997+
switch (e.code) {
998+
case "Space": {
999+
let focused = document.activeElement;
1000+
if (focused.matches(".site .preset")) {
1001+
focused
1002+
.closest(".site")
1003+
.querySelector(".preset[value='CUSTOM']")
1004+
.click();
1005+
e.preventDefault();
1006+
}
1007+
}
1008+
}
1009+
},
1010+
true
1011+
);
1012+
root.addEventListener("click", this, true);
1013+
root.addEventListener("change", this, true);
1014+
root.wiredBy = this;
1015+
if (!UA.mobile) {
1016+
return;
1017+
}
1018+
const siteWidth = root.querySelector(".site")?.offsetWidth + 12;
1019+
const viewportWidth = document.documentElement.clientWidth;
1020+
if (viewportWidth < siteWidth) {
1021+
document.documentElement.style.zoom = viewportWidth / siteWidth;
1022+
}
9781023
}
979-
}
1024+
});
9801025
});
9811026
}
9821027

0 commit comments

Comments
 (0)