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