remove old js lazylist
This commit is contained in:
parent
9557178ffb
commit
35fb84c275
1 changed files with 0 additions and 287 deletions
|
@ -1,287 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2021 The Matrix.org Foundation C.I.C.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import {el} from "./html";
|
|
||||||
import {mountView} from "./utils";
|
|
||||||
import {ListView} from "./ListView";
|
|
||||||
import {insertAt} from "./utils";
|
|
||||||
import {ItemRange, ScrollDirection} from "./ItemRange.js";
|
|
||||||
|
|
||||||
export class LazyListView extends ListView {
|
|
||||||
constructor({itemHeight, overflowMargin = 5, overflowItems = 20,...options}, childCreator) {
|
|
||||||
super(options, childCreator);
|
|
||||||
this._itemHeight = itemHeight;
|
|
||||||
this._overflowMargin = overflowMargin;
|
|
||||||
this._overflowItems = overflowItems;
|
|
||||||
}
|
|
||||||
|
|
||||||
_getVisibleRange() {
|
|
||||||
const length = this._list ? this._list.length : 0;
|
|
||||||
const scrollTop = this._parent.scrollTop;
|
|
||||||
const topCount = Math.min(Math.max(0, Math.floor(scrollTop / this._itemHeight)), length);
|
|
||||||
const itemsAfterTop = length - topCount;
|
|
||||||
const visibleItems = this._height !== 0 ? Math.ceil(this._height / this._itemHeight) : 0;
|
|
||||||
const renderCount = Math.min(visibleItems, itemsAfterTop);
|
|
||||||
const bottomCount = itemsAfterTop - renderCount;
|
|
||||||
return new ItemRange(topCount, renderCount, bottomCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
_renderIfNeeded() {
|
|
||||||
const range = this._getVisibleRange();
|
|
||||||
const intersectRange = range.expand(this._overflowMargin);
|
|
||||||
const renderRange = range.expand(this._overflowItems);
|
|
||||||
// only update render Range if the new range + overflowMargin isn't contained by the old anymore
|
|
||||||
if (!this._renderRange.contains(intersectRange)) {
|
|
||||||
console.log("new", renderRange);
|
|
||||||
console.log("current", this._renderRange);
|
|
||||||
console.log("diff", this._renderRange.diff(renderRange));
|
|
||||||
this._renderElementsInRange(renderRange);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async _initialRender() {
|
|
||||||
/*
|
|
||||||
Wait two frames for the return from mount() to be inserted into DOM.
|
|
||||||
This should be enough, but if this gives us trouble we can always use
|
|
||||||
MutationObserver.
|
|
||||||
*/
|
|
||||||
await new Promise(r => requestAnimationFrame(r));
|
|
||||||
await new Promise(r => requestAnimationFrame(r));
|
|
||||||
|
|
||||||
this._height = this._parent.clientHeight;
|
|
||||||
if (this._height === 0) { console.error("LazyListView could not calculate parent height."); }
|
|
||||||
const initialRange = this._getVisibleRange();
|
|
||||||
const initialRenderRange = initialRange.expand(this._overflowItems);
|
|
||||||
this._renderRange = new ItemRange(0, 0, initialRange.bottomCount + 1);
|
|
||||||
this._renderElementsInRange(initialRenderRange);
|
|
||||||
}
|
|
||||||
|
|
||||||
_itemsFromList({start, end}) {
|
|
||||||
const array = [];
|
|
||||||
let i = 0;
|
|
||||||
for (const item of this._list) {
|
|
||||||
if (i >= start && i <= end) {
|
|
||||||
array.push(item);
|
|
||||||
}
|
|
||||||
i = i + 1;
|
|
||||||
}
|
|
||||||
return array;
|
|
||||||
}
|
|
||||||
|
|
||||||
_itemAtIndex(idx) {
|
|
||||||
let i = 0;
|
|
||||||
for (const item of this._list) {
|
|
||||||
if (i === idx) {
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
i = i + 1;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
_adjustPadding(range) {
|
|
||||||
const { topCount, bottomCount } = range;
|
|
||||||
const paddingTop = topCount * this._itemHeight;
|
|
||||||
const paddingBottom = bottomCount * this._itemHeight;
|
|
||||||
this._root.style.paddingTop = `${paddingTop}px`;
|
|
||||||
this._root.style.paddingBottom = `${paddingBottom}px`;
|
|
||||||
}
|
|
||||||
|
|
||||||
_renderedFragment(items, childInstanceModifier) {
|
|
||||||
const fragment = document.createDocumentFragment();
|
|
||||||
for (const item of items) {
|
|
||||||
const view = this._childCreator(item);
|
|
||||||
childInstanceModifier(view);
|
|
||||||
fragment.appendChild(mountView(view, this._mountArgs));
|
|
||||||
}
|
|
||||||
return fragment;
|
|
||||||
}
|
|
||||||
|
|
||||||
_renderElementsInRange(range) {
|
|
||||||
const diff = this._renderRange.diff(range);
|
|
||||||
const renderedItems = this._itemsFromList(diff.toAdd);
|
|
||||||
this._adjustPadding(range);
|
|
||||||
const {start, end} = diff.toRemove;
|
|
||||||
const normalizedStart = this._renderRange.normalize(start);
|
|
||||||
this._childInstances.splice(normalizedStart, end - start + 1).forEach(child => this._removeChild(child));
|
|
||||||
|
|
||||||
if (diff.scrollDirection === ScrollDirection.downwards) {
|
|
||||||
const fragment = this._renderedFragment(renderedItems, view => this._childInstances.push(view));
|
|
||||||
this._root.appendChild(fragment);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
const fragment = this._renderedFragment(renderedItems, view => this._childInstances.unshift(view));
|
|
||||||
this._root.insertBefore(fragment, this._root.firstChild);
|
|
||||||
}
|
|
||||||
this._renderRange = range;
|
|
||||||
}
|
|
||||||
|
|
||||||
mount() {
|
|
||||||
const root = super.mount();
|
|
||||||
this._subscription = this._list.subscribe(this);
|
|
||||||
this._childInstances = [];
|
|
||||||
this._parent = el("div", {className: "LazyListParent"}, root);
|
|
||||||
/*
|
|
||||||
Hooking to scroll events can be expensive.
|
|
||||||
Do we need to do more (like event throttling)?
|
|
||||||
*/
|
|
||||||
this._parent.addEventListener("scroll", () => this._renderIfNeeded());
|
|
||||||
this._initialRender();
|
|
||||||
return this._parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
update(attributes) {
|
|
||||||
this._renderRange = null;
|
|
||||||
super.update(attributes);
|
|
||||||
this._childInstances = [];
|
|
||||||
this._initialRender();
|
|
||||||
}
|
|
||||||
|
|
||||||
loadList() {
|
|
||||||
// We don't render the entire list; so nothing to see here.
|
|
||||||
}
|
|
||||||
|
|
||||||
_removeChild(child) {
|
|
||||||
child.root().remove();
|
|
||||||
child.unmount();
|
|
||||||
}
|
|
||||||
|
|
||||||
onAdd(idx, value) {
|
|
||||||
const {topCount, renderCount, bottomCount} = this._renderRange;
|
|
||||||
if (this._renderRange.containsIndex(idx)) {
|
|
||||||
this.onBeforeListChanged();
|
|
||||||
const normalizedIdx = this._renderRange.normalize(idx);
|
|
||||||
if (bottomCount === 0) {
|
|
||||||
/*
|
|
||||||
If we're at the bottom of the list, we need to render the additional item
|
|
||||||
without removing another item from the list.
|
|
||||||
We can't increment topCount because the index topCount is not affected by the
|
|
||||||
add operation (and any modification will thus break ItemRange.normalize()).
|
|
||||||
We can't increment bottomCount because there's not enough items left to trigger
|
|
||||||
a further render.
|
|
||||||
*/
|
|
||||||
this._renderRange = new ItemRange(topCount, renderCount + 1, bottomCount);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Remove the last element, render the new element
|
|
||||||
this._removeChild(this._childInstances.pop());
|
|
||||||
this._renderRange = new ItemRange(topCount, renderCount, bottomCount + 1);
|
|
||||||
}
|
|
||||||
super.onAdd(normalizedIdx, value, true);
|
|
||||||
this.onListChanged();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this._renderRange = idx < topCount ? new ItemRange(topCount + 1, renderCount, bottomCount):
|
|
||||||
new ItemRange(topCount, renderCount, bottomCount + 1);
|
|
||||||
}
|
|
||||||
this._adjustPadding(this._renderRange);
|
|
||||||
}
|
|
||||||
|
|
||||||
onRemove(idx, value) {
|
|
||||||
const {topCount, renderCount, bottomCount} = this._renderRange;
|
|
||||||
if (this._renderRange.containsIndex(idx)) {
|
|
||||||
this.onBeforeListChanged();
|
|
||||||
const normalizedIdx = this._renderRange.normalize(idx);
|
|
||||||
super.onRemove(normalizedIdx, value, true);
|
|
||||||
if (bottomCount === 0) {
|
|
||||||
// See onAdd for explanation
|
|
||||||
this._renderRange = new ItemRange(topCount, renderCount - 1, bottomCount);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
const child = this._childCreator(this._itemAtIndex(this._renderRange.lastIndex));
|
|
||||||
this._childInstances.push(child);
|
|
||||||
this._root.appendChild(mountView(child, this._mountArgs));
|
|
||||||
this._renderRange = new ItemRange(topCount, renderCount, bottomCount - 1);
|
|
||||||
}
|
|
||||||
this.onListChanged();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this._renderRange = idx < topCount ? new ItemRange(topCount - 1, renderCount, bottomCount):
|
|
||||||
new ItemRange(topCount, renderCount, bottomCount - 1);
|
|
||||||
}
|
|
||||||
this._adjustPadding(this._renderRange);
|
|
||||||
}
|
|
||||||
|
|
||||||
onUpdate(idx, value, params) {
|
|
||||||
if (this._renderRange.containsIndex(idx)) {
|
|
||||||
const normalizedIdx = this._renderRange.normalize(idx);
|
|
||||||
super.onUpdate(normalizedIdx, value, params);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
recreateItem(idx, value) {
|
|
||||||
if (this._renderRange.containsIndex(idx)) {
|
|
||||||
const normalizedIdx = this._renderRange.normalize(idx);
|
|
||||||
super.recreateItem(normalizedIdx, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Render additional element from top or bottom to offset the outgoing element
|
|
||||||
*/
|
|
||||||
_renderExtraOnMove(fromIdx, toIdx) {
|
|
||||||
const {topCount, renderCount} = this._renderRange;
|
|
||||||
if (toIdx < fromIdx) {
|
|
||||||
// Element is moved up the list, so render element from top boundary
|
|
||||||
const index = topCount;
|
|
||||||
const child = this._childCreator(this._itemAtIndex(index));
|
|
||||||
this._childInstances.unshift(child);
|
|
||||||
this._root.insertBefore(mountView(child, this._mountArgs), this._root.firstChild);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Element is moved down the list, so render element from bottom boundary
|
|
||||||
const index = topCount + renderCount - 1;
|
|
||||||
const child = this._childCreator(this._itemAtIndex(index));
|
|
||||||
this._childInstances.push(child);
|
|
||||||
this._root.appendChild(mountView(child, this._mountArgs));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove an element from top or bottom to make space for the incoming element
|
|
||||||
*/
|
|
||||||
_removeElementOnMove(fromIdx, toIdx) {
|
|
||||||
// If element comes from the bottom, remove element at bottom and vice versa
|
|
||||||
const child = toIdx < fromIdx ? this._childInstances.pop() : this._childInstances.shift();
|
|
||||||
this._removeChild(child);
|
|
||||||
}
|
|
||||||
|
|
||||||
onMove(fromIdx, toIdx, value) {
|
|
||||||
const fromInRange = this._renderRange.containsIndex(fromIdx);
|
|
||||||
const toInRange = this._renderRange.containsIndex(toIdx);
|
|
||||||
const normalizedFromIdx = this._renderRange.normalize(fromIdx);
|
|
||||||
const normalizedToIdx = this._renderRange.normalize(toIdx);
|
|
||||||
if (fromInRange && toInRange) {
|
|
||||||
super.onMove(normalizedFromIdx, normalizedToIdx, value);
|
|
||||||
}
|
|
||||||
else if (fromInRange && !toInRange) {
|
|
||||||
this.onBeforeListChanged();
|
|
||||||
const [child] = this._childInstances.splice(normalizedFromIdx, 1);
|
|
||||||
this._removeChild(child);
|
|
||||||
this._renderExtraOnMove(fromIdx, toIdx);
|
|
||||||
this.onListChanged();
|
|
||||||
}
|
|
||||||
else if (!fromInRange && toInRange) {
|
|
||||||
this.onBeforeListChanged();
|
|
||||||
const child = this._childCreator(value);
|
|
||||||
this._removeElementOnMove(fromIdx, toIdx);
|
|
||||||
this._childInstances.splice(normalizedToIdx, 0, child);
|
|
||||||
insertAt(this._root, normalizedToIdx, mountView(child, this._mountArgs));
|
|
||||||
this.onListChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
Reference in a new issue