Skip to content

Commit fe5b9a0

Browse files
committed
Maybe virtual rendering fixed, scroll not
1 parent f42ca7d commit fe5b9a0

File tree

1 file changed

+152
-102
lines changed

1 file changed

+152
-102
lines changed

app/javascript/controllers/wunderbaum_controller.js

Lines changed: 152 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export default class extends Controller {
2929
}
3030

3131
async connect() {
32+
window.wbController = this;
3233
try {
3334
const res = await fetch(this.urlValue, {
3435
headers: { Accept: "application/json" },
@@ -44,7 +45,6 @@ export default class extends Controller {
4445
checkbox: true,
4546
lazy: true,
4647
selectMode: "hier",
47-
grid: { debounce: 0 },
4848
columnsResizable: true,
4949
fixedCol: true,
5050
filter: {
@@ -178,27 +178,24 @@ export default class extends Controller {
178178
? this.contentdmCollectionOptions
179179
: this.userOptions;
180180

181-
if (!options) {
182-
util.setValueToElem(elem, value ?? "");
183-
break;
181+
if (!elem.dataset.bound) {
182+
const select = document.createElement("select")
183+
select.name = colId
184+
;(options || []).forEach(opt => {
185+
const o = document.createElement("option")
186+
o.value = String(opt.value)
187+
o.textContent = opt.label
188+
select.appendChild(o)
189+
})
190+
elem.textContent = ""
191+
elem.appendChild(select)
192+
elem.dataset.bound = "1"
193+
select.addEventListener("change", ev => {
194+
node.data[colId] = ev.target.value
195+
})
184196
}
185-
186-
if (!elem.dataset.rendered) {
187-
const select = document.createElement("select");
188-
select.name = colId;
189-
options.forEach((opt) => {
190-
const option = document.createElement("option");
191-
option.value = String(opt.value);
192-
option.textContent = opt.label;
193-
select.appendChild(option);
194-
});
195-
elem.replaceChildren(select);
196-
elem.dataset.rendered = "true";
197-
}
198-
199-
const select = elem.querySelector("select");
200-
if (select) select.value = value ?? "";
201-
break;
197+
elem.querySelector("select").value = value ?? ""
198+
break
202199
}
203200

204201
case "notes":
@@ -280,11 +277,62 @@ export default class extends Controller {
280277

281278
source
282279
});
280+
const t = this.tree;
281+
let lastTop = -1
282+
283+
if (t && typeof t.updateViewport === "function") {
284+
const orig = t.updateViewport
285+
t.updateViewport = function (force) {
286+
const el = this.listContainerElement
287+
if (!el) return
288+
const top = el.scrollTop
289+
if (!force && Math.abs(top - lastTop) < 8) return
290+
lastTop = top
291+
return orig.call(this, force)
292+
}
293+
}
294+
295+
requestAnimationFrame(() => {
296+
const list = this.tree?.listContainerElement
297+
if (list) {
298+
list.style.lineHeight = "24px"
299+
list.style.rowHeight = "24px"
300+
}
301+
this.tree.rowHeight = 24
302+
})
303+
304+
if (t && typeof t.updateViewport === "function") {
305+
const origUpdate = t.updateViewport;
306+
let ticking = false;
307+
308+
t.updateViewport = (force) => {
309+
if (ticking) return;
310+
ticking = true;
311+
requestAnimationFrame(() => {
312+
try {
313+
origUpdate.call(t, force);
314+
} catch (err) {
315+
console.error("updateViewport failed:", err);
316+
} finally {
317+
ticking = false;
318+
}
319+
});
320+
};
321+
}
322+
323+
this.$scrollContainer = [this.tree.listContainerElement];
324+
// this._setupScrollHandler();
283325

284326
if (!this._renderCount) this._renderCount = 0
285327
this._renderCount++
286328
if (this._renderCount % 50 === 0) console.log("renders:", this._renderCount)
287329

330+
if (!this._renderCount) this._renderCount = 0;
331+
this._renderCount++;
332+
if (this._renderCount % 10 === 0) {
333+
console.log("render", this._renderCount, this.tree.listContainerElement?.scrollTop);
334+
}
335+
288336
requestAnimationFrame(() => {
289337
const t = this.tree
290338
const listEl = t.listContainerElement
@@ -295,13 +343,24 @@ export default class extends Controller {
295343
const parentId = root?.key || "root"
296344
let nextPage = 2
297345

298-
listEl.addEventListener("scroll", async () => {
299-
if (listEl.scrollTop + listEl.clientHeight >= listEl.scrollHeight - 100) {
300-
if (nextPage) nextPage = await this._loadNextAssetPage(parentId, nextPage)
346+
let scrollTicking = false;
347+
348+
listEl.addEventListener("scroll", async () => {
349+
if (scrollTicking) return;
350+
scrollTicking = true;
351+
352+
requestAnimationFrame(async () => {
353+
const atBottom = listEl.scrollTop + listEl.clientHeight >= listEl.scrollHeight - 100;
354+
355+
if (atBottom && !this._pageLoading) {
356+
this._pageLoading = true;
357+
if (nextPage) nextPage = await this._loadNextAssetPage(parentId, nextPage);
358+
this._pageLoading = false;
301359
}
302-
if (t.updateViewport) t.updateViewport?.(true)
303-
else if (t._updateViewportThrottled) t._updateViewportThrottled?.(true)
304-
})
360+
console.log("scroll fired", listEl.scrollTop, listEl.scrollHeight, performance.now());
361+
scrollTicking = false;
362+
});
363+
});
305364
}
306365
})
307366

@@ -315,7 +374,7 @@ export default class extends Controller {
315374
this._setupFilterModeToggle();
316375
requestAnimationFrame(() => {
317376
this._tagHeaderCells()
318-
this._deferSelectHydration()
377+
// this._deferSelectHydration()
319378
// this._setupVirtualWindow()
320379
})
321380

@@ -728,20 +787,25 @@ _handleInputChange(e) {
728787

729788
if (remaining.length) {
730789
let i = 0
731-
const schedule = () => {
732-
if (i >= remaining.length) {
790+
const total = remaining.length
791+
const processNextChunk = () => {
792+
if (i >= total) {
733793
this.assetsLoadedFor.add(k)
734794
this.loadedAssets.add(k)
735-
this.tree?.updateViewport?.(true)
736795
return
737796
}
738-
const slice = remaining.slice(i, i + CHUNK_SIZE)
739-
i += CHUNK_SIZE
740-
node.addChildren(slice)
741-
this.tree?.updateViewport?.(true)
742-
requestIdleCallback(schedule, { timeout: 200 })
797+
const slice = remaining.slice(i, i + 50)
798+
i += 50
799+
node.addChildren(slice, { batch: true })
800+
if (!this._assetViewportTimer) {
801+
this._assetViewportTimer = setTimeout(() => {
802+
this._assetViewportTimer = null
803+
this.tree?.updateViewport?.(true)
804+
}, 200)
805+
}
806+
requestIdleCallback(processNextChunk, { timeout: 250 })
743807
}
744-
requestIdleCallback(schedule, { timeout: 200 })
808+
requestIdleCallback(processNextChunk, { timeout: 250 })
745809
} else {
746810
this.assetsLoadedFor.add(k)
747811
this.loadedAssets.add(k)
@@ -941,11 +1005,6 @@ _handleInputChange(e) {
9411005
if (batch.length) node.addChildren(batch)
9421006
if (queue.length) {
9431007
requestIdleCallback(processNext, { timeout: 200 })
944-
} else {
945-
requestAnimationFrame(() => {
946-
const t = node.tree
947-
if (t.updateViewport) t.updateViewport?.(true)
948-
})
9491008
}
9501009
}
9511010

@@ -1199,7 +1258,7 @@ async _processBgAdds() {
11991258
}
12001259
this._bgAddRunning = false
12011260
requestAnimationFrame(() => {
1202-
this.tree?.updateViewport?.(true)
1261+
//requestAnimationFrame(() => this.tree?.update?.("lazy"))
12031262
})
12041263
}
12051264

@@ -1507,12 +1566,6 @@ async _loadNextAssetPage(parentId, nextPage) {
15071566
node.lazy = false;
15081567
node.data.lazy = false;
15091568

1510-
requestAnimationFrame(() => {
1511-
const t = node.tree;
1512-
if (t?.updateViewport) t.updateViewport?.(true);
1513-
else if (t?._updateViewportThrottled) t._updateViewportThrottled?.(true);
1514-
});
1515-
15161569
return children;
15171570
} catch (err) {
15181571
console.error("Folder lazy load failed:", err);
@@ -1522,58 +1575,55 @@ async _loadNextAssetPage(parentId, nextPage) {
15221575
}
15231576
}
15241577

1525-
// _setupVirtualWindow() {
1526-
// const list = this.tree.listContainerElement
1527-
// if (!list) return
1528-
1529-
// const rowH = list.querySelector(".wb-row")?.offsetHeight || 24
1530-
// const buffer = 40
1531-
// let scheduled = false
1532-
// let virtualActive = true
1533-
1534-
// const renderWindow = () => {
1535-
// scheduled = false
1536-
// if (!virtualActive) return
1537-
1538-
// const top = list.scrollTop
1539-
// const bottom = top + list.clientHeight
1540-
// const startIdx = Math.max(0, Math.floor(top / rowH) - buffer)
1541-
// const endIdx = Math.ceil(bottom / rowH) + buffer
1542-
// let idx = 0
1543-
1544-
// const iterator = this.tree.visit((node) => {
1545-
// if (!node.parent?.expanded || node.data.folder) return true
1546-
// const el = node.tr || node.li
1547-
// if (!el) return
1548-
// const visible = idx >= startIdx && idx <= endIdx
1549-
// el.hidden = !visible
1550-
// idx++
1551-
// })
1552-
1553-
// // yield control after big trees
1554-
// if (idx > 500) requestIdleCallback(() => iterator, { timeout: 200 })
1555-
// }
1556-
1557-
// const schedule = () => {
1558-
// if (!scheduled) {
1559-
// scheduled = true
1560-
// requestAnimationFrame(renderWindow)
1561-
// }
1562-
// }
1563-
1564-
// list.addEventListener("scroll", schedule, { passive: true })
1565-
1566-
// document.addEventListener("focusin", e => {
1567-
// if (e.target?.tagName === "SELECT") virtualActive = false
1568-
// })
1569-
// document.addEventListener("focusout", e => {
1570-
// if (e.target?.tagName === "SELECT") {
1571-
// virtualActive = true
1572-
// schedule()
1573-
// }
1574-
// })
1575-
1576-
// renderWindow()
1577-
// }
1578+
_setupScrollHandler() {
1579+
console.log("_setupScrollHandler called, container:", this.$scrollContainer);
1580+
if (!this.$scrollContainer || !this.$scrollContainer[0]) {
1581+
console.log("container missing, will retry");
1582+
setTimeout(() => this._setupScrollHandler(), 50);
1583+
return;
1584+
}
1585+
1586+
const container = this.$scrollContainer[0];
1587+
console.log("container found:", container);
1588+
1589+
let isUpdating = false;
1590+
1591+
container.addEventListener("scroll", () => {
1592+
console.log("scroll event fired");
1593+
if (isUpdating) return;
1594+
isUpdating = true;
1595+
1596+
requestAnimationFrame(() => {
1597+
console.log("_setupVirtualWindow triggered");
1598+
// this._setupVirtualWindow();
1599+
isUpdating = false;
1600+
});
1601+
});
1602+
}
1603+
1604+
_setupVirtualWindow() {
1605+
if (this._inVirtualUpdate) return;
1606+
this._inVirtualUpdate = true;
1607+
1608+
if (!this.$scrollContainer || !this.$scrollContainer[0]) return;
1609+
1610+
const container = this.$scrollContainer[0];
1611+
const scrollTop = container.scrollTop;
1612+
const viewportHeight = container.clientHeight;
1613+
const rowHeight = this.rowHeight || 24;
1614+
1615+
const first = Math.floor(scrollTop / rowHeight);
1616+
const last = Math.ceil((scrollTop + viewportHeight) / rowHeight);
1617+
1618+
if (first === this._prevFirst && last === this._prevLast) return;
1619+
this._prevFirst = first;
1620+
this._prevLast = last;
1621+
1622+
const active = document.activeElement;
1623+
if (active && this.element.contains(active)) return;
1624+
1625+
this.tree?.update?.("scroll");
1626+
this._inVirtualUpdate = false;
1627+
}
15781628

15791629
}

0 commit comments

Comments
 (0)