forked from mystiq/hydrogen-web
Move ItemRange to separate file
Signed-off-by: RMidhunSuresh <rmidhunsuresh@gmail.com>
This commit is contained in:
parent
aee135a6cd
commit
5d54285640
2 changed files with 117 additions and 101 deletions
116
src/platform/web/ui/general/ItemRange.js
Normal file
116
src/platform/web/ui/general/ItemRange.js
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
/*
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export class ScrollDirection {
|
||||||
|
static get downwards() { return 1; }
|
||||||
|
static get upwards() { return -1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ItemRange {
|
||||||
|
constructor(topCount, renderCount, bottomCount) {
|
||||||
|
this.topCount = topCount;
|
||||||
|
this.renderCount = renderCount;
|
||||||
|
this.bottomCount = bottomCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
contains(range) {
|
||||||
|
// don't contain empty ranges
|
||||||
|
// as it will prevent clearing the list
|
||||||
|
// once it is scrolled far enough out of view
|
||||||
|
if (!range.renderCount && this.renderCount) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return range.topCount >= this.topCount &&
|
||||||
|
(range.topCount + range.renderCount) <= (this.topCount + this.renderCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
containsIndex(idx) {
|
||||||
|
return idx >= this.topCount && idx < this.lastIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
expand(amount) {
|
||||||
|
// don't expand ranges that won't render anything
|
||||||
|
if (this.renderCount === 0) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
const topGrow = Math.min(amount, this.topCount);
|
||||||
|
const bottomGrow = Math.min(amount, this.bottomCount);
|
||||||
|
return new ItemRange(
|
||||||
|
this.topCount - topGrow,
|
||||||
|
this.renderCount + topGrow + bottomGrow,
|
||||||
|
this.bottomCount - bottomGrow,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
get lastIndex() {
|
||||||
|
return this.topCount + this.renderCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
totalSize() {
|
||||||
|
return this.topCount + this.renderCount + this.bottomCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
normalize(idx) {
|
||||||
|
/*
|
||||||
|
map index from list to index in rendered range
|
||||||
|
eg: if the index range of this._list is [0, 200] and we have rendered
|
||||||
|
elements in range [50, 60] then index 50 in list must map to index 0
|
||||||
|
in DOM tree/childInstance array.
|
||||||
|
*/
|
||||||
|
return idx - this.topCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
scrollDirectionTo(range) {
|
||||||
|
return range.bottomCount < this.bottomCount ? ScrollDirection.downwards : ScrollDirection.upwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if this range intersects with another range
|
||||||
|
* @param {ItemRange} range The range to check against
|
||||||
|
* @param {ScrollDirection} scrollDirection
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
intersects(range) {
|
||||||
|
return !!Math.max(0, Math.min(this.lastIndex, range.lastIndex) - Math.max(this.topCount, range.topCount));
|
||||||
|
}
|
||||||
|
|
||||||
|
diff(range) {
|
||||||
|
/**
|
||||||
|
* Range-1
|
||||||
|
* |----------------------|
|
||||||
|
* Range-2
|
||||||
|
* |---------------------|
|
||||||
|
* <-------><------------><------->
|
||||||
|
* bisect-1 intersection bisect-2
|
||||||
|
*/
|
||||||
|
const scrollDirection = this.scrollDirectionTo(range);
|
||||||
|
if (!this.intersects(range)) {
|
||||||
|
// There is no intersection between the ranges; which can happen if you scroll really fast
|
||||||
|
// In this case, we need to do full render of the new range
|
||||||
|
const toRemove = { start: this.topCount, end: this.lastIndex };
|
||||||
|
const toAdd = { start: range.topCount, end: range.lastIndex };
|
||||||
|
return {toRemove, toAdd, scrollDirection};
|
||||||
|
}
|
||||||
|
const bisection1 = {start: Math.min(this.topCount, range.topCount), end: Math.max(this.topCount, range.topCount) - 1};
|
||||||
|
const bisection2 = {start: Math.min(this.lastIndex, range.lastIndex), end: Math.max(this.lastIndex, range.lastIndex)};
|
||||||
|
// When scrolling down, bisection1 needs to be removed and bisection2 needs to be added
|
||||||
|
// When scrolling up, vice versa
|
||||||
|
const toRemove = scrollDirection === ScrollDirection.downwards ? bisection1 : bisection2;
|
||||||
|
const toAdd = scrollDirection === ScrollDirection.downwards ? bisection2 : bisection1;
|
||||||
|
return {toAdd, toRemove, scrollDirection};
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,107 +18,7 @@ import {el} from "./html";
|
||||||
import {mountView} from "./utils";
|
import {mountView} from "./utils";
|
||||||
import {ListView} from "./ListView";
|
import {ListView} from "./ListView";
|
||||||
import {insertAt} from "./utils";
|
import {insertAt} from "./utils";
|
||||||
|
import {ItemRange, ScrollDirection} from "./ItemRange.js";
|
||||||
class ScrollDirection {
|
|
||||||
static get downwards() { return 1; }
|
|
||||||
static get upwards() { return -1; }
|
|
||||||
}
|
|
||||||
|
|
||||||
class ItemRange {
|
|
||||||
constructor(topCount, renderCount, bottomCount) {
|
|
||||||
this.topCount = topCount;
|
|
||||||
this.renderCount = renderCount;
|
|
||||||
this.bottomCount = bottomCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
contains(range) {
|
|
||||||
// don't contain empty ranges
|
|
||||||
// as it will prevent clearing the list
|
|
||||||
// once it is scrolled far enough out of view
|
|
||||||
if (!range.renderCount && this.renderCount) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return range.topCount >= this.topCount &&
|
|
||||||
(range.topCount + range.renderCount) <= (this.topCount + this.renderCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
containsIndex(idx) {
|
|
||||||
return idx >= this.topCount && idx < this.lastIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
expand(amount) {
|
|
||||||
// don't expand ranges that won't render anything
|
|
||||||
if (this.renderCount === 0) {
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
const topGrow = Math.min(amount, this.topCount);
|
|
||||||
const bottomGrow = Math.min(amount, this.bottomCount);
|
|
||||||
return new ItemRange(
|
|
||||||
this.topCount - topGrow,
|
|
||||||
this.renderCount + topGrow + bottomGrow,
|
|
||||||
this.bottomCount - bottomGrow,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
get lastIndex() {
|
|
||||||
return this.topCount + this.renderCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
totalSize() {
|
|
||||||
return this.topCount + this.renderCount + this.bottomCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
normalize(idx) {
|
|
||||||
/*
|
|
||||||
map index from list to index in rendered range
|
|
||||||
eg: if the index range of this._list is [0, 200] and we have rendered
|
|
||||||
elements in range [50, 60] then index 50 in list must map to index 0
|
|
||||||
in DOM tree/childInstance array.
|
|
||||||
*/
|
|
||||||
return idx - this.topCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
scrollDirectionTo(range) {
|
|
||||||
return range.bottomCount < this.bottomCount ? ScrollDirection.downwards : ScrollDirection.upwards;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if this range intersects with another range
|
|
||||||
* @param {ItemRange} range The range to check against
|
|
||||||
* @param {ScrollDirection} scrollDirection
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
intersects(range) {
|
|
||||||
return !!Math.max(0, Math.min(this.lastIndex, range.lastIndex) - Math.max(this.topCount, range.topCount));
|
|
||||||
}
|
|
||||||
|
|
||||||
diff(range) {
|
|
||||||
/**
|
|
||||||
* Range-1
|
|
||||||
* |----------------------|
|
|
||||||
* Range-2
|
|
||||||
* |---------------------|
|
|
||||||
* <-------><------------><------->
|
|
||||||
* bisect-1 intersection bisect-2
|
|
||||||
*/
|
|
||||||
const scrollDirection = this.scrollDirectionTo(range);
|
|
||||||
if (!this.intersects(range)) {
|
|
||||||
// There is no intersection between the ranges; which can happen if you scroll really fast
|
|
||||||
// In this case, we need to do full render of the new range
|
|
||||||
const toRemove = { start: this.topCount, end: this.lastIndex };
|
|
||||||
const toAdd = { start: range.topCount, end: range.lastIndex };
|
|
||||||
return {toRemove, toAdd, scrollDirection};
|
|
||||||
}
|
|
||||||
const bisection1 = {start: Math.min(this.topCount, range.topCount), end: Math.max(this.topCount, range.topCount) - 1};
|
|
||||||
const bisection2 = {start: Math.min(this.lastIndex, range.lastIndex), end: Math.max(this.lastIndex, range.lastIndex)};
|
|
||||||
// When scrolling down, bisection1 needs to be removed and bisection2 needs to be added
|
|
||||||
// When scrolling up, vice versa
|
|
||||||
const toRemove = scrollDirection === ScrollDirection.downwards ? bisection1 : bisection2;
|
|
||||||
const toAdd = scrollDirection === ScrollDirection.downwards ? bisection2 : bisection1;
|
|
||||||
return {toAdd, toRemove, scrollDirection};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class LazyListView extends ListView {
|
export class LazyListView extends ListView {
|
||||||
constructor({itemHeight, overflowMargin = 5, overflowItems = 20,...options}, childCreator) {
|
constructor({itemHeight, overflowMargin = 5, overflowItems = 20,...options}, childCreator) {
|
||||||
|
|
Loading…
Reference in a new issue