diff options
| author | syuilo <Syuilotan@yahoo.co.jp> | 2021-11-12 02:02:25 +0900 |
|---|---|---|
| committer | syuilo <Syuilotan@yahoo.co.jp> | 2021-11-12 02:02:25 +0900 |
| commit | 0e4a111f81cceed275d9bec2695f6e401fb654d8 (patch) | |
| tree | 40874799472fa07416f17b50a398ac33b7771905 /packages/client/src/scripts/paging.ts | |
| parent | update deps (diff) | |
| download | misskey-0e4a111f81cceed275d9bec2695f6e401fb654d8.tar.gz misskey-0e4a111f81cceed275d9bec2695f6e401fb654d8.tar.bz2 misskey-0e4a111f81cceed275d9bec2695f6e401fb654d8.zip | |
refactoring
Resolve #7779
Diffstat (limited to 'packages/client/src/scripts/paging.ts')
| -rw-r--r-- | packages/client/src/scripts/paging.ts | 246 |
1 files changed, 246 insertions, 0 deletions
diff --git a/packages/client/src/scripts/paging.ts b/packages/client/src/scripts/paging.ts new file mode 100644 index 0000000000..ef63ecc450 --- /dev/null +++ b/packages/client/src/scripts/paging.ts @@ -0,0 +1,246 @@ +import { markRaw } from 'vue'; +import * as os from '@/os'; +import { onScrollTop, isTopVisible, getScrollPosition, getScrollContainer } from './scroll'; + +const SECOND_FETCH_LIMIT = 30; + +// reversed: items 配列の中身を逆順にする(新しい方が最後) + +export default (opts) => ({ + emits: ['queue'], + + data() { + return { + items: [], + queue: [], + offset: 0, + fetching: true, + moreFetching: false, + inited: false, + more: false, + backed: false, // 遡り中か否か + isBackTop: false, + }; + }, + + computed: { + empty(): boolean { + return this.items.length === 0 && !this.fetching && this.inited; + }, + + error(): boolean { + return !this.fetching && !this.inited; + }, + }, + + watch: { + pagination: { + handler() { + this.init(); + }, + deep: true + }, + + queue: { + handler(a, b) { + if (a.length === 0 && b.length === 0) return; + this.$emit('queue', this.queue.length); + }, + deep: true + } + }, + + created() { + opts.displayLimit = opts.displayLimit || 30; + this.init(); + }, + + activated() { + this.isBackTop = false; + }, + + deactivated() { + this.isBackTop = window.scrollY === 0; + }, + + methods: { + reload() { + this.items = []; + this.init(); + }, + + replaceItem(finder, data) { + const i = this.items.findIndex(finder); + this.items[i] = data; + }, + + removeItem(finder) { + const i = this.items.findIndex(finder); + this.items.splice(i, 1); + }, + + async init() { + this.queue = []; + this.fetching = true; + if (opts.before) opts.before(this); + let params = typeof this.pagination.params === 'function' ? this.pagination.params(true) : this.pagination.params; + if (params && params.then) params = await params; + if (params === null) return; + const endpoint = typeof this.pagination.endpoint === 'function' ? this.pagination.endpoint() : this.pagination.endpoint; + await os.api(endpoint, { + ...params, + limit: this.pagination.noPaging ? (this.pagination.limit || 10) : (this.pagination.limit || 10) + 1, + }).then(items => { + for (let i = 0; i < items.length; i++) { + const item = items[i]; + markRaw(item); + if (this.pagination.reversed) { + if (i === items.length - 2) item._shouldInsertAd_ = true; + } else { + if (i === 3) item._shouldInsertAd_ = true; + } + } + if (!this.pagination.noPaging && (items.length > (this.pagination.limit || 10))) { + items.pop(); + this.items = this.pagination.reversed ? [...items].reverse() : items; + this.more = true; + } else { + this.items = this.pagination.reversed ? [...items].reverse() : items; + this.more = false; + } + this.offset = items.length; + this.inited = true; + this.fetching = false; + if (opts.after) opts.after(this, null); + }, e => { + this.fetching = false; + if (opts.after) opts.after(this, e); + }); + }, + + async fetchMore() { + if (!this.more || this.fetching || this.moreFetching || this.items.length === 0) return; + this.moreFetching = true; + this.backed = true; + let params = typeof this.pagination.params === 'function' ? this.pagination.params(false) : this.pagination.params; + if (params && params.then) params = await params; + const endpoint = typeof this.pagination.endpoint === 'function' ? this.pagination.endpoint() : this.pagination.endpoint; + await os.api(endpoint, { + ...params, + limit: SECOND_FETCH_LIMIT + 1, + ...(this.pagination.offsetMode ? { + offset: this.offset, + } : { + untilId: this.pagination.reversed ? this.items[0].id : this.items[this.items.length - 1].id, + }), + }).then(items => { + for (let i = 0; i < items.length; i++) { + const item = items[i]; + markRaw(item); + if (this.pagination.reversed) { + if (i === items.length - 9) item._shouldInsertAd_ = true; + } else { + if (i === 10) item._shouldInsertAd_ = true; + } + } + if (items.length > SECOND_FETCH_LIMIT) { + items.pop(); + this.items = this.pagination.reversed ? [...items].reverse().concat(this.items) : this.items.concat(items); + this.more = true; + } else { + this.items = this.pagination.reversed ? [...items].reverse().concat(this.items) : this.items.concat(items); + this.more = false; + } + this.offset += items.length; + this.moreFetching = false; + }, e => { + this.moreFetching = false; + }); + }, + + async fetchMoreFeature() { + if (!this.more || this.fetching || this.moreFetching || this.items.length === 0) return; + this.moreFetching = true; + let params = typeof this.pagination.params === 'function' ? this.pagination.params(false) : this.pagination.params; + if (params && params.then) params = await params; + const endpoint = typeof this.pagination.endpoint === 'function' ? this.pagination.endpoint() : this.pagination.endpoint; + await os.api(endpoint, { + ...params, + limit: SECOND_FETCH_LIMIT + 1, + ...(this.pagination.offsetMode ? { + offset: this.offset, + } : { + sinceId: this.pagination.reversed ? this.items[0].id : this.items[this.items.length - 1].id, + }), + }).then(items => { + for (const item of items) { + markRaw(item); + } + if (items.length > SECOND_FETCH_LIMIT) { + items.pop(); + this.items = this.pagination.reversed ? [...items].reverse().concat(this.items) : this.items.concat(items); + this.more = true; + } else { + this.items = this.pagination.reversed ? [...items].reverse().concat(this.items) : this.items.concat(items); + this.more = false; + } + this.offset += items.length; + this.moreFetching = false; + }, e => { + this.moreFetching = false; + }); + }, + + prepend(item) { + if (this.pagination.reversed) { + const container = getScrollContainer(this.$el); + const pos = getScrollPosition(this.$el); + const viewHeight = container.clientHeight; + const height = container.scrollHeight; + const isBottom = (pos + viewHeight > height - 32); + if (isBottom) { + // オーバーフローしたら古いアイテムは捨てる + if (this.items.length >= opts.displayLimit) { + // このやり方だとVue 3.2以降アニメーションが動かなくなる + //this.items = this.items.slice(-opts.displayLimit); + while (this.items.length >= opts.displayLimit) { + this.items.shift(); + } + this.more = true; + } + } + this.items.push(item); + // TODO + } else { + const isTop = this.isBackTop || (document.body.contains(this.$el) && isTopVisible(this.$el)); + + if (isTop) { + // Prepend the item + this.items.unshift(item); + + // オーバーフローしたら古いアイテムは捨てる + if (this.items.length >= opts.displayLimit) { + // このやり方だとVue 3.2以降アニメーションが動かなくなる + //this.items = this.items.slice(0, opts.displayLimit); + while (this.items.length >= opts.displayLimit) { + this.items.pop(); + } + this.more = true; + } + } else { + this.queue.push(item); + onScrollTop(this.$el, () => { + for (const item of this.queue) { + this.prepend(item); + } + this.queue = []; + }); + } + } + }, + + append(item) { + this.items.push(item); + }, + } +}); |