speed up rendering w/ virtualization
This commit is contained in:
@@ -45,6 +45,9 @@ class OrgTodoListView extends ItemView {
|
|||||||
this.filterText = "";
|
this.filterText = "";
|
||||||
this.activeTags = new Set();
|
this.activeTags = new Set();
|
||||||
this.showDone = false;
|
this.showDone = false;
|
||||||
|
this.ROW_H = 28; // fixed row height in px — MUST match CSS
|
||||||
|
this.BUFFER = 5; // extra rows above/below viewport, hides scroll seams
|
||||||
|
this.filtered = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
getViewType() {
|
getViewType() {
|
||||||
@@ -210,9 +213,8 @@ class OrgTodoListView extends ItemView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderList() {
|
renderList() {
|
||||||
this.listEl.empty();
|
// 1. Filter (cheap — pure in-memory).
|
||||||
|
this.filtered = this.tasks.filter((t) => {
|
||||||
const filtered = this.tasks.filter((t) => {
|
|
||||||
if (!this.showDone && t.done) return false;
|
if (!this.showDone && t.done) return false;
|
||||||
if (this.activeTags.size > 0) {
|
if (this.activeTags.size > 0) {
|
||||||
for (const tag of this.activeTags) if (!t.tags.includes(tag)) return false;
|
for (const tag of this.activeTags) if (!t.tags.includes(tag)) return false;
|
||||||
@@ -224,13 +226,45 @@ class OrgTodoListView extends ItemView {
|
|||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (filtered.length === 0) {
|
this.listEl.empty();
|
||||||
|
|
||||||
|
if (this.filtered.length === 0) {
|
||||||
this.listEl.createEl("div", { text: "No matching tasks.", cls: "org-todo-empty" });
|
this.listEl.createEl("div", { text: "No matching tasks.", cls: "org-todo-empty" });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const t of filtered) {
|
// 2. A tall spacer establishes the full scrollable height...
|
||||||
const row = this.listEl.createDiv({ cls: "org-todo-row" });
|
this.spacerEl = this.listEl.createDiv({ cls: "org-todo-spacer" });
|
||||||
|
this.spacerEl.style.height = this.filtered.length * this.ROW_H + "px";
|
||||||
|
|
||||||
|
// 3. ...and an absolutely-positioned layer holds the handful of visible rows.
|
||||||
|
this.rowsEl = this.spacerEl.createDiv({ cls: "org-todo-rows" });
|
||||||
|
|
||||||
|
// Repaint visible rows on scroll. Registered once per renderList; the old
|
||||||
|
// listEl (and its listener) is discarded by empty() above.
|
||||||
|
this.listEl.onscroll = () => this.renderVisible();
|
||||||
|
|
||||||
|
this.renderVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Paints only the rows currently in the viewport, recycling on scroll.
|
||||||
|
renderVisible() {
|
||||||
|
if (!this.filtered || this.filtered.length === 0) return;
|
||||||
|
|
||||||
|
const scrollTop = this.listEl.scrollTop;
|
||||||
|
const viewportH = this.listEl.clientHeight;
|
||||||
|
|
||||||
|
const first = Math.max(0, Math.floor(scrollTop / this.ROW_H) - this.BUFFER);
|
||||||
|
const visibleCount = Math.ceil(viewportH / this.ROW_H) + this.BUFFER * 2;
|
||||||
|
const last = Math.min(this.filtered.length, first + visibleCount);
|
||||||
|
|
||||||
|
// Offset the rows layer so the painted slice sits at the right scroll position.
|
||||||
|
this.rowsEl.style.transform = `translateY(${first * this.ROW_H}px)`;
|
||||||
|
this.rowsEl.empty();
|
||||||
|
|
||||||
|
for (let i = first; i < last; i++) {
|
||||||
|
const t = this.filtered[i];
|
||||||
|
const row = this.rowsEl.createDiv({ cls: "org-todo-row" });
|
||||||
|
|
||||||
const box = row.createEl("input", { type: "checkbox" });
|
const box = row.createEl("input", { type: "checkbox" });
|
||||||
box.checked = t.done;
|
box.checked = t.done;
|
||||||
|
|||||||
+27
-2
@@ -31,13 +31,38 @@
|
|||||||
background: var(--interactive-accent);
|
background: var(--interactive-accent);
|
||||||
color: var(--text-on-accent);
|
color: var(--text-on-accent);
|
||||||
}
|
}
|
||||||
|
.org-todo-list {
|
||||||
|
position: relative;
|
||||||
|
overflow-y: auto;
|
||||||
|
height: 100%; /* the scroll viewport — must have a bounded height */
|
||||||
|
}
|
||||||
|
.org-todo-spacer {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.org-todo-rows {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
.org-todo-row {
|
.org-todo-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: baseline;
|
align-items: center; /* center, not baseline — fixed height now */
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
padding: 3px 0;
|
height: 28px; /* MUST equal ROW_H */
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 0;
|
||||||
border-bottom: 1px solid var(--background-modifier-border-hover);
|
border-bottom: 1px solid var(--background-modifier-border-hover);
|
||||||
}
|
}
|
||||||
|
.org-todo-text {
|
||||||
|
cursor: pointer;
|
||||||
|
flex: 1;
|
||||||
|
white-space: nowrap; /* truncation: no wrap... */
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis; /* ...show ellipsis instead */
|
||||||
|
min-width: 0; /* lets flex item actually shrink to ellipsis */
|
||||||
|
}
|
||||||
.org-todo-text {
|
.org-todo-text {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
|||||||
Reference in New Issue
Block a user