summaryrefslogtreecommitdiff
path: root/packages/client/src/components/MkPagination.vue
diff options
context:
space:
mode:
Diffstat (limited to 'packages/client/src/components/MkPagination.vue')
-rw-r--r--packages/client/src/components/MkPagination.vue317
1 files changed, 0 insertions, 317 deletions
diff --git a/packages/client/src/components/MkPagination.vue b/packages/client/src/components/MkPagination.vue
deleted file mode 100644
index 291409171a..0000000000
--- a/packages/client/src/components/MkPagination.vue
+++ /dev/null
@@ -1,317 +0,0 @@
-<template>
-<transition :name="$store.state.animation ? 'fade' : ''" mode="out-in">
- <MkLoading v-if="fetching"/>
-
- <MkError v-else-if="error" @retry="init()"/>
-
- <div v-else-if="empty" key="_empty_" class="empty">
- <slot name="empty">
- <div class="_fullinfo">
- <img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/>
- <div>{{ i18n.ts.nothing }}</div>
- </div>
- </slot>
- </div>
-
- <div v-else ref="rootEl">
- <div v-show="pagination.reversed && more" key="_more_" class="cxiknjgy _gap">
- <MkButton v-if="!moreFetching" class="button" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" primary @click="fetchMoreAhead">
- {{ i18n.ts.loadMore }}
- </MkButton>
- <MkLoading v-else class="loading"/>
- </div>
- <slot :items="items"></slot>
- <div v-show="!pagination.reversed && more" key="_more_" class="cxiknjgy _gap">
- <MkButton v-if="!moreFetching" v-appear="($store.state.enableInfiniteScroll && !disableAutoLoad) ? fetchMore : null" class="button" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" primary @click="fetchMore">
- {{ i18n.ts.loadMore }}
- </MkButton>
- <MkLoading v-else class="loading"/>
- </div>
- </div>
-</transition>
-</template>
-
-<script lang="ts" setup>
-import { computed, ComputedRef, isRef, markRaw, onActivated, onDeactivated, Ref, ref, watch } from 'vue';
-import * as misskey from 'misskey-js';
-import * as os from '@/os';
-import { onScrollTop, isTopVisible, getScrollPosition, getScrollContainer } from '@/scripts/scroll';
-import MkButton from '@/components/MkButton.vue';
-import { i18n } from '@/i18n';
-
-const SECOND_FETCH_LIMIT = 30;
-
-export type Paging<E extends keyof misskey.Endpoints = keyof misskey.Endpoints> = {
- endpoint: E;
- limit: number;
- params?: misskey.Endpoints[E]['req'] | ComputedRef<misskey.Endpoints[E]['req']>;
-
- /**
- * 検索APIのような、ページング不可なエンドポイントを利用する場合
- * (そのようなAPIをこの関数で使うのは若干矛盾してるけど)
- */
- noPaging?: boolean;
-
- /**
- * items 配列の中身を逆順にする(新しい方が最後)
- */
- reversed?: boolean;
-
- offsetMode?: boolean;
-};
-
-const props = withDefaults(defineProps<{
- pagination: Paging;
- disableAutoLoad?: boolean;
- displayLimit?: number;
-}>(), {
- displayLimit: 30,
-});
-
-const emit = defineEmits<{
- (ev: 'queue', count: number): void;
-}>();
-
-type Item = { id: string; [another: string]: unknown; };
-
-const rootEl = ref<HTMLElement>();
-const items = ref<Item[]>([]);
-const queue = ref<Item[]>([]);
-const offset = ref(0);
-const fetching = ref(true);
-const moreFetching = ref(false);
-const more = ref(false);
-const backed = ref(false); // 遡り中か否か
-const isBackTop = ref(false);
-const empty = computed(() => items.value.length === 0);
-const error = ref(false);
-
-const init = async (): Promise<void> => {
- queue.value = [];
- fetching.value = true;
- const params = props.pagination.params ? isRef(props.pagination.params) ? props.pagination.params.value : props.pagination.params : {};
- await os.api(props.pagination.endpoint, {
- ...params,
- limit: props.pagination.noPaging ? (props.pagination.limit || 10) : (props.pagination.limit || 10) + 1,
- }).then(res => {
- for (let i = 0; i < res.length; i++) {
- const item = res[i];
- if (props.pagination.reversed) {
- if (i === res.length - 2) item._shouldInsertAd_ = true;
- } else {
- if (i === 3) item._shouldInsertAd_ = true;
- }
- }
- if (!props.pagination.noPaging && (res.length > (props.pagination.limit || 10))) {
- res.pop();
- items.value = props.pagination.reversed ? [...res].reverse() : res;
- more.value = true;
- } else {
- items.value = props.pagination.reversed ? [...res].reverse() : res;
- more.value = false;
- }
- offset.value = res.length;
- error.value = false;
- fetching.value = false;
- }, err => {
- error.value = true;
- fetching.value = false;
- });
-};
-
-const reload = (): void => {
- items.value = [];
- init();
-};
-
-const fetchMore = async (): Promise<void> => {
- if (!more.value || fetching.value || moreFetching.value || items.value.length === 0) return;
- moreFetching.value = true;
- backed.value = true;
- const params = props.pagination.params ? isRef(props.pagination.params) ? props.pagination.params.value : props.pagination.params : {};
- await os.api(props.pagination.endpoint, {
- ...params,
- limit: SECOND_FETCH_LIMIT + 1,
- ...(props.pagination.offsetMode ? {
- offset: offset.value,
- } : props.pagination.reversed ? {
- sinceId: items.value[0].id,
- } : {
- untilId: items.value[items.value.length - 1].id,
- }),
- }).then(res => {
- for (let i = 0; i < res.length; i++) {
- const item = res[i];
- if (props.pagination.reversed) {
- if (i === res.length - 9) item._shouldInsertAd_ = true;
- } else {
- if (i === 10) item._shouldInsertAd_ = true;
- }
- }
- if (res.length > SECOND_FETCH_LIMIT) {
- res.pop();
- items.value = props.pagination.reversed ? [...res].reverse().concat(items.value) : items.value.concat(res);
- more.value = true;
- } else {
- items.value = props.pagination.reversed ? [...res].reverse().concat(items.value) : items.value.concat(res);
- more.value = false;
- }
- offset.value += res.length;
- moreFetching.value = false;
- }, err => {
- moreFetching.value = false;
- });
-};
-
-const fetchMoreAhead = async (): Promise<void> => {
- if (!more.value || fetching.value || moreFetching.value || items.value.length === 0) return;
- moreFetching.value = true;
- const params = props.pagination.params ? isRef(props.pagination.params) ? props.pagination.params.value : props.pagination.params : {};
- await os.api(props.pagination.endpoint, {
- ...params,
- limit: SECOND_FETCH_LIMIT + 1,
- ...(props.pagination.offsetMode ? {
- offset: offset.value,
- } : props.pagination.reversed ? {
- untilId: items.value[0].id,
- } : {
- sinceId: items.value[items.value.length - 1].id,
- }),
- }).then(res => {
- if (res.length > SECOND_FETCH_LIMIT) {
- res.pop();
- items.value = props.pagination.reversed ? [...res].reverse().concat(items.value) : items.value.concat(res);
- more.value = true;
- } else {
- items.value = props.pagination.reversed ? [...res].reverse().concat(items.value) : items.value.concat(res);
- more.value = false;
- }
- offset.value += res.length;
- moreFetching.value = false;
- }, err => {
- moreFetching.value = false;
- });
-};
-
-const prepend = (item: Item): void => {
- if (props.pagination.reversed) {
- if (rootEl.value) {
- const container = getScrollContainer(rootEl.value);
- if (container == null) {
- // TODO?
- } else {
- const pos = getScrollPosition(rootEl.value);
- const viewHeight = container.clientHeight;
- const height = container.scrollHeight;
- const isBottom = (pos + viewHeight > height - 32);
- if (isBottom) {
- // オーバーフローしたら古いアイテムは捨てる
- if (items.value.length >= props.displayLimit) {
- // このやり方だとVue 3.2以降アニメーションが動かなくなる
- //items.value = items.value.slice(-props.displayLimit);
- while (items.value.length >= props.displayLimit) {
- items.value.shift();
- }
- more.value = true;
- }
- }
- }
- }
- items.value.push(item);
- // TODO
- } else {
- // 初回表示時はunshiftだけでOK
- if (!rootEl.value) {
- items.value.unshift(item);
- return;
- }
-
- const isTop = isBackTop.value || (document.body.contains(rootEl.value) && isTopVisible(rootEl.value));
-
- if (isTop) {
- // Prepend the item
- items.value.unshift(item);
-
- // オーバーフローしたら古いアイテムは捨てる
- if (items.value.length >= props.displayLimit) {
- // このやり方だとVue 3.2以降アニメーションが動かなくなる
- //this.items = items.value.slice(0, props.displayLimit);
- while (items.value.length >= props.displayLimit) {
- items.value.pop();
- }
- more.value = true;
- }
- } else {
- queue.value.push(item);
- onScrollTop(rootEl.value, () => {
- for (const item of queue.value) {
- prepend(item);
- }
- queue.value = [];
- });
- }
- }
-};
-
-const append = (item: Item): void => {
- items.value.push(item);
-};
-
-const removeItem = (finder: (item: Item) => boolean) => {
- const i = items.value.findIndex(finder);
- items.value.splice(i, 1);
-};
-
-const updateItem = (id: Item['id'], replacer: (old: Item) => Item): void => {
- const i = items.value.findIndex(item => item.id === id);
- items.value[i] = replacer(items.value[i]);
-};
-
-if (props.pagination.params && isRef(props.pagination.params)) {
- watch(props.pagination.params, init, { deep: true });
-}
-
-watch(queue, (a, b) => {
- if (a.length === 0 && b.length === 0) return;
- emit('queue', queue.value.length);
-}, { deep: true });
-
-init();
-
-onActivated(() => {
- isBackTop.value = false;
-});
-
-onDeactivated(() => {
- isBackTop.value = window.scrollY === 0;
-});
-
-defineExpose({
- items,
- queue,
- backed,
- reload,
- prepend,
- append,
- removeItem,
- updateItem,
-});
-</script>
-
-<style lang="scss" scoped>
-.fade-enter-active,
-.fade-leave-active {
- transition: opacity 0.125s ease;
-}
-.fade-enter-from,
-.fade-leave-to {
- opacity: 0;
-}
-
-.cxiknjgy {
- > .button {
- margin-left: auto;
- margin-right: auto;
- }
-}
-</style>