Telegram Web K with changes to work inside I2P https://web.telegram.i2p/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

201 lines
5.7 KiB

/*
* https://github.com/morethanwords/tweb
* Copyright (C) 2019-2021 Eduard Kuzmenko
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
import forEachReverse from "./array/forEachReverse";
import safeAssign from "./object/safeAssign";
export type ListLoaderOptions<T extends {}, P extends {}> = {
loadMore: ListLoader<T, P>['loadMore'],
loadCount?: ListLoader<T, P>['loadCount'],
loadWhenLeft?: ListLoader<T, P>['loadWhenLeft'],
processItem?: ListLoader<T, P>['processItem'],
onJump?: ListLoader<T, P>['onJump'],
onLoadedMore?: ListLoader<T, P>['onLoadedMore']
};
export type ListLoaderResult<T extends {}> = {count: number, items: any[]};
export default class ListLoader<T extends {}, P extends {}> {
public current: T;
public previous: T[] = [];
public next: T[] = [];
public count: number;
public reverse = false; // reverse means next = higher msgid
protected loadMore: (anchor: T, older: boolean, loadCount: number) => Promise<ListLoaderResult<T>>;
protected processItem: (item: P) => T;
protected loadCount = 50;
protected loadWhenLeft = 20;
public onJump: (item: T, older: boolean) => void;
public onLoadedMore: () => void;
protected loadedAllUp = false;
protected loadedAllDown = false;
protected loadPromiseUp: Promise<void>;
protected loadPromiseDown: Promise<void>;
constructor(options: ListLoaderOptions<T, P>) {
safeAssign(this, options);
}
public setTargets(previous: T[], next: T[], reverse: boolean) {
this.previous = previous;
this.next = next;
this.reverse = reverse;
}
public get index() {
return this.count !== undefined ? this.previous.length : -1;
}
/* public filter(callback: (item: T, idx: number, arr: T[]) => boolean) {
const filter = (item: T, idx: number, arr: T[]) => {
if(!callback(item, idx, arr)) {
arr.splice(idx, 1);
}
};
forEachReverse(this.previous, filter);
forEachReverse(this.next, filter);
} */
public reset(loadedAll = false) {
this.current = undefined;
this.previous = [];
this.next = [];
this.setLoaded(true, loadedAll);
this.setLoaded(false, loadedAll);
}
public go(length: number, dispatchJump = true) {
let items: T[], item: T;
if(length > 0) {
items = this.next.splice(0, length);
item = items.pop();
if(!item) {
return;
}
if(this.current !== undefined) items.unshift(this.current);
this.previous.push(...items);
} else {
items = this.previous.splice(Math.max(0, this.previous.length + length), -length);
item = items.shift();
if(!item) {
return;
}
if(this.current !== undefined) items.push(this.current);
this.next.unshift(...items);
}
if(this.next.length < this.loadWhenLeft) {
this.load(!this.reverse);
}
if(this.previous.length < this.loadWhenLeft) {
this.load(this.reverse);
}
this.current = item;
dispatchJump && this.onJump && this.onJump(item, length > 0);
return this.current;
}
protected unsetCurrent(toPrevious: boolean) {
if(toPrevious) this.previous.push(this.current);
else this.next.unshift(this.current);
this.current = undefined;
}
public goUnsafe(length: number, dispatchJump?: boolean) {
const leftLength = length > 0 ? Math.max(0, length - this.next.length) : Math.min(0, length + this.previous.length);
const item = this.go(length, leftLength ? false : dispatchJump);
/* if(length > 0 ? this.loadedAllUp : this.loadedAllDown) {
this.unsetCurrent(length > 0);
} */
return {
item: !leftLength ? item : undefined,
leftLength
};
}
protected setLoaded(down: boolean, value: boolean) {
const isChanged = (down ? this.loadedAllDown : this.loadedAllUp) !== value;
if(!isChanged) {
return false;
}
if(down) this.loadedAllDown = value;
else this.loadedAllUp = value;
if(!value) {
if(down) this.loadPromiseDown = null;
else this.loadPromiseUp = null;
}
return true;
}
// нет смысла делать проверку для reverse и loadMediaPromise
public load(older: boolean) {
if(older ? this.loadedAllDown : this.loadedAllUp) return Promise.resolve();
let promise = older ? this.loadPromiseDown : this.loadPromiseUp;
if(promise) return promise;
let anchor: T;
if(older) {
anchor = this.reverse ? this.previous[0] : this.next[this.next.length - 1];
} else {
anchor = this.reverse ? this.next[this.next.length - 1] : this.previous[0];
}
anchor ??= this.current;
promise = this.loadMore(anchor, older, this.loadCount).then(result => {
if((older ? this.loadPromiseDown : this.loadPromiseUp) !== promise) {
return;
}
if(result.items.length < this.loadCount) {
this.setLoaded(older, true);
}
if(this.count === undefined) {
this.count = result.count || result.items.length;
}
const method = older ? result.items.forEach.bind(result.items) : forEachReverse.bind(null, result.items);
method((item: any) => {
const processed = this.processItem ? this.processItem(item) : item;
if(!processed) return;
if(older) {
if(this.reverse) this.previous.unshift(processed);
else this.next.push(processed);
} else {
if(this.reverse) this.next.push(processed);
else this.previous.unshift(processed);
}
});
this.onLoadedMore && this.onLoadedMore();
}, () => {}).then(() => {
if(older) this.loadPromiseDown = null;
else this.loadPromiseUp = null;
});
if(older) this.loadPromiseDown = promise;
else this.loadPromiseUp = promise;
return promise;
}
}