morethanwords
3 years ago
17 changed files with 580 additions and 453 deletions
@ -1,53 +1,149 @@ |
|||||||
/* |
/* |
||||||
* https://github.com/morethanwords/tweb
|
* https://github.com/morethanwords/tweb
|
||||||
* Copyright (C) 2019-2021 Eduard Kuzmenko |
* Copyright (C) 2019-2021 Eduard Kuzmenko |
||||||
* https://github.com/morethanwords/tweb/blob/master/LICENSE
|
* https://github.com/morethanwords/tweb/blob/master/LICENSE
|
||||||
*/ |
*/ |
||||||
|
|
||||||
import Scrollable from "../components/scrollable"; |
import { forEachReverse } from "./array"; |
||||||
import { safeAssign } from "./object"; |
import { safeAssign } from "./object"; |
||||||
|
|
||||||
export default class ScrollableLoader { |
export type ListLoaderOptions<T extends {}> = { |
||||||
public loading = false; |
loadMore: ListLoader<T>['loadMore'], |
||||||
private scrollable: Scrollable; |
loadCount?: ListLoader<T>['loadCount'], |
||||||
private getPromise: () => Promise<any>; |
loadWhenLeft?: ListLoader<T>['loadWhenLeft'], |
||||||
private promise: Promise<any>; |
processItem?: ListLoader<T>['processItem'], |
||||||
private loaded = false; |
onJump?: ListLoader<T>['onJump'], |
||||||
|
onLoadedMore?: ListLoader<T>['onLoadedMore'] |
||||||
constructor(options: { |
}; |
||||||
scrollable: ScrollableLoader['scrollable'], |
|
||||||
getPromise: ScrollableLoader['getPromise'] |
export type ListLoaderResult<T extends {}> = {count: number, items: any[]}; |
||||||
}) { |
export default class ListLoader<T extends {}> { |
||||||
safeAssign(this, options); |
public current: T; |
||||||
|
public previous: T[] = []; |
||||||
options.scrollable.onScrolledBottom = () => { |
public next: T[] = []; |
||||||
this.load(); |
public count: number; |
||||||
}; |
public reverse = false; // reverse means next = higher msgid
|
||||||
} |
|
||||||
|
protected loadMore: (anchor: T, older: boolean, loadCount: number) => Promise<ListLoaderResult<T>>; |
||||||
public load() { |
protected processItem: (item: any) => T; |
||||||
if(this.loaded) { |
protected loadCount = 50; |
||||||
return Promise.resolve(); |
protected loadWhenLeft = 20; |
||||||
} |
|
||||||
|
public onJump: (item: T, older: boolean) => void; |
||||||
if(this.loading) { |
public onLoadedMore: () => void; |
||||||
return this.promise; |
|
||||||
} |
protected loadedAllUp = false; |
||||||
|
protected loadedAllDown = false; |
||||||
this.loading = true; |
protected loadPromiseUp: Promise<void>; |
||||||
this.promise = this.getPromise().then(done => { |
protected loadPromiseDown: Promise<void>; |
||||||
this.loading = false; |
|
||||||
this.promise = undefined; |
constructor(options: ListLoaderOptions<T>) { |
||||||
|
safeAssign(this, options); |
||||||
if(done) { |
} |
||||||
this.loaded = true; |
|
||||||
this.scrollable.onScrolledBottom = null; |
public setTargets(previous: T[], next: T[], reverse: boolean) { |
||||||
} else { |
this.previous = previous; |
||||||
this.scrollable.checkForTriggers(); |
this.next = next; |
||||||
} |
this.reverse = reverse; |
||||||
}, () => { |
} |
||||||
this.promise = undefined; |
|
||||||
this.loading = false; |
public get index() { |
||||||
}); |
return this.count !== undefined ? this.previous.length : -1; |
||||||
} |
} |
||||||
} |
|
||||||
|
public reset() { |
||||||
|
this.current = undefined; |
||||||
|
this.previous = []; |
||||||
|
this.next = []; |
||||||
|
this.loadedAllUp = this.loadedAllDown = false; |
||||||
|
this.loadPromiseUp = this.loadPromiseDown = null; |
||||||
|
} |
||||||
|
|
||||||
|
public go(length: number) { |
||||||
|
let items: T[], item: T; |
||||||
|
if(length > 0) { |
||||||
|
items = this.next.splice(0, length); |
||||||
|
item = items.pop(); |
||||||
|
if(!item) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
this.previous.push(this.current, ...items); |
||||||
|
} else { |
||||||
|
items = this.previous.splice(this.previous.length + length, -length); |
||||||
|
item = items.shift(); |
||||||
|
if(!item) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
this.next.unshift(...items, this.current); |
||||||
|
} |
||||||
|
|
||||||
|
if(this.next.length < this.loadWhenLeft) { |
||||||
|
this.load(!this.reverse); |
||||||
|
} |
||||||
|
|
||||||
|
if(this.previous.length < this.loadWhenLeft) { |
||||||
|
this.load(this.reverse); |
||||||
|
} |
||||||
|
|
||||||
|
this.current = item; |
||||||
|
this.onJump && this.onJump(item, length > 0); |
||||||
|
} |
||||||
|
|
||||||
|
// нет смысла делать проверку для reverse и loadMediaPromise
|
||||||
|
public load(older: boolean) { |
||||||
|
if(older && this.loadedAllDown) return Promise.resolve(); |
||||||
|
else if(!older && this.loadedAllUp) return Promise.resolve(); |
||||||
|
|
||||||
|
if(older && this.loadPromiseDown) return this.loadPromiseDown; |
||||||
|
else if(!older && this.loadPromiseUp) return this.loadPromiseUp; |
||||||
|
|
||||||
|
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]; |
||||||
|
} |
||||||
|
|
||||||
|
const promise = this.loadMore(anchor, older, this.loadCount).then(result => { |
||||||
|
if((older && this.loadPromiseDown !== promise) || (!older && this.loadPromiseUp !== promise)) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
if(result.items.length < this.loadCount) { |
||||||
|
if(older) this.loadedAllDown = true; |
||||||
|
else this.loadedAllUp = 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; |
||||||
|
} |
||||||
|
} |
||||||
|
@ -0,0 +1,53 @@ |
|||||||
|
/* |
||||||
|
* https://github.com/morethanwords/tweb
|
||||||
|
* Copyright (C) 2019-2021 Eduard Kuzmenko |
||||||
|
* https://github.com/morethanwords/tweb/blob/master/LICENSE
|
||||||
|
*/ |
||||||
|
|
||||||
|
import Scrollable from "../components/scrollable"; |
||||||
|
import { safeAssign } from "./object"; |
||||||
|
|
||||||
|
export default class ScrollableLoader { |
||||||
|
public loading = false; |
||||||
|
private scrollable: Scrollable; |
||||||
|
private getPromise: () => Promise<any>; |
||||||
|
private promise: Promise<any>; |
||||||
|
private loaded = false; |
||||||
|
|
||||||
|
constructor(options: { |
||||||
|
scrollable: ScrollableLoader['scrollable'], |
||||||
|
getPromise: ScrollableLoader['getPromise'] |
||||||
|
}) { |
||||||
|
safeAssign(this, options); |
||||||
|
|
||||||
|
options.scrollable.onScrolledBottom = () => { |
||||||
|
this.load(); |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
public load() { |
||||||
|
if(this.loaded) { |
||||||
|
return Promise.resolve(); |
||||||
|
} |
||||||
|
|
||||||
|
if(this.loading) { |
||||||
|
return this.promise; |
||||||
|
} |
||||||
|
|
||||||
|
this.loading = true; |
||||||
|
this.promise = this.getPromise().then(done => { |
||||||
|
this.loading = false; |
||||||
|
this.promise = undefined; |
||||||
|
|
||||||
|
if(done) { |
||||||
|
this.loaded = true; |
||||||
|
this.scrollable.onScrolledBottom = null; |
||||||
|
} else { |
||||||
|
this.scrollable.checkForTriggers(); |
||||||
|
} |
||||||
|
}, () => { |
||||||
|
this.promise = undefined; |
||||||
|
this.loading = false; |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue