fix (frontend): federation settings glitch when the grid has more than one columns

fix: remove the overflow flag

fix: remove the overflow style

fix: modify the initial value of backed

revert change

feat: add padding to the pagination property

fix: update top calculation

fix: wrong implementation

fix: wrong implementation in federation page

feat: new implementation

fix: wrong implementation

fix: wrong implementation

# Conflicts:
#	packages/frontend/src/components/MkPagination.vue

fix: wrong execution timing

fix: execution timing

fix: wrong implementation

fix: wrong execute timing

fix: execution timing

fix: execution timing
This commit is contained in:
BackRunner 2023-10-22 19:42:45 +08:00
parent eb38f08e13
commit ac0f552a83
3 changed files with 124 additions and 25 deletions

View File

@ -46,7 +46,7 @@ SPDX-License-Identifier: AGPL-3.0-only
import { computed, ComputedRef, isRef, nextTick, onActivated, onBeforeUnmount, onDeactivated, onMounted, ref, watch } from 'vue';
import * as Misskey from 'misskey-js';
import * as os from '@/os.js';
import { onScrollTop, isTopVisible, getBodyScrollHeight, getScrollContainer, onScrollBottom, scrollToBottom, scroll, isBottomVisible } from '@/scripts/scroll.js';
import { onScrollTop, isTopVisible, getBodyScrollHeight, getScrollContainer, onScrollBottom, scrollToBottom, scroll, isBottomVisible, onScrollDownOnce, onScrollUpOnce } from '@/scripts/scroll.js';
import { useDocumentVisibility } from '@/scripts/use-document-visibility.js';
import MkButton from '@/components/MkButton.vue';
import { defaultStore } from '@/store.js';
@ -95,8 +95,11 @@ const props = withDefaults(defineProps<{
pagination: Paging;
disableAutoLoad?: boolean;
displayLimit?: number;
disableObserver?: boolean;
tolerance?: number;
}>(), {
displayLimit: 20,
tolerance: TOLERANCE,
});
const emit = defineEmits<{
@ -106,7 +109,7 @@ const emit = defineEmits<{
let rootEl = $shallowRef<HTMLElement>();
//
let backed = $ref(false);
let backed = $ref(true);
let scrollRemove = $ref<(() => void) | null>(null);
@ -151,11 +154,13 @@ const BACKGROUND_PAUSE_WAIT_SEC = 10;
//
// https://qiita.com/mkataigi/items/0154aefd2223ce23398e
let scrollObserver = $ref<IntersectionObserver>();
watch([() => props.pagination.reversed, $$(scrollableElement)], () => {
if (scrollObserver) scrollObserver.disconnect();
let scrollObserver = $ref<IntersectionObserver | null>(null);
const createScrollObserver = () => {
if (scrollObserver) {
scrollObserver.disconnect();
scrollObserver = null;
}
scrollObserver = new IntersectionObserver(entries => {
backed = entries[0].isIntersecting;
}, {
@ -163,6 +168,16 @@ watch([() => props.pagination.reversed, $$(scrollableElement)], () => {
rootMargin: props.pagination.reversed ? '-100% 0px 100% 0px' : '100% 0px -100% 0px',
threshold: 0.01,
});
};
let initialScrollCleaner: () => void | undefined;
watch([() => props.pagination.reversed, $$(scrollableElement)], () => {
if (props.disableObserver) {
initialScrollCleaner?.();
} else {
createScrollObserver();
}
}, { immediate: true });
watch($$(rootEl), () => {
@ -175,8 +190,7 @@ watch($$(rootEl), () => {
watch([$$(backed), $$(contentEl)], () => {
if (!backed) {
if (!contentEl) return;
scrollRemove = (props.pagination.reversed ? onScrollBottom : onScrollTop)(contentEl, executeQueue, TOLERANCE);
scrollRemove = (props.pagination.reversed ? onScrollBottom : onScrollTop)(contentEl, executeQueue, props.tolerance, false, !props.disableObserver);
} else {
if (scrollRemove) scrollRemove();
scrollRemove = null;
@ -201,9 +215,15 @@ async function init(): Promise<void> {
...params,
limit: props.pagination.limit ?? 10,
}).then(res => {
for (let i = 0; i < res.length; i++) {
for (let i = 0; i < res.length; i += 3) {
const item = res[i];
if (i === 3) item._shouldInsertAd_ = true;
if (!item) {
continue;
}
if (i === 3) {
item._shouldInsertAd_ = true;
break;
}
}
if (res.length === 0 || props.pagination.noPaging) {
@ -241,9 +261,9 @@ const fetchMore = async (): Promise<void> => {
untilId: Array.from(items.value.keys()).at(-1),
}),
}).then(res => {
for (let i = 0; i < res.length; i++) {
const item = res[i];
if (i === 10) item._shouldInsertAd_ = true;
for (let i = 0; i < res.length; i += 10) {
if (!res[i]) continue;
res[i]._shouldInsertAd_ = true;
}
const reverseConcat = _res => {
@ -388,9 +408,11 @@ const prepend = (item: MisskeyEntity): void => {
*/
function unshiftItems(newItems: MisskeyEntity[]) {
const length = newItems.length + items.value.size;
items.value = new Map([...arrayToEntries(newItems), ...items.value].slice(0, props.displayLimit));
const mapEntries = [...arrayToEntries(newItems), ...items.value].slice(0, props.displayLimit);
items.value = new Map(mapEntries);
if (length >= props.displayLimit) more.value = true;
offset.value = mapEntries.length;
}
/**
@ -399,9 +421,11 @@ function unshiftItems(newItems: MisskeyEntity[]) {
*/
function concatItems(oldItems: MisskeyEntity[]) {
const length = oldItems.length + items.value.size;
items.value = new Map([...items.value, ...arrayToEntries(oldItems)].slice(0, props.displayLimit));
const mapEntries = [...items.value, ...arrayToEntries(oldItems)].slice(0, props.displayLimit);
items.value = new Map(mapEntries);
if (length >= props.displayLimit) more.value = true;
offset.value = mapEntries.length;
}
function executeQueue() {
@ -447,11 +471,42 @@ function toBottom() {
scrollToBottom(contentEl!);
}
const createInitialScrollListener = () => {
if (!props.disableObserver || !contentEl) {
return;
}
setTimeout(() => {
initialScrollCleaner = (props.pagination.reversed ? onScrollUpOnce : onScrollDownOnce)(contentEl, () => {
backed = false;
nextTick(() => {
const cleaner = (props.pagination.reversed ? onScrollBottom : onScrollTop)(contentEl, () => {
setTimeout(() => {
backed = true;
createInitialScrollListener();
});
}, props.tolerance, true, false);
if (cleaner) initialScrollCleaner = cleaner;
});
});
});
}
watch($$(contentEl), () => {
if (props.disableObserver) {
createInitialScrollListener();
}
});
onMounted(() => {
inited.then(() => {
if (props.pagination.reversed) {
nextTick(() => {
setTimeout(toBottom, 800);
setTimeout(() => {
toBottom();
setTimeout(() => {
createInitialScrollListener();
});
}, 800);
// scrollToBottommoreFetching
// more = true
@ -459,6 +514,10 @@ onMounted(() => {
moreFetching.value = false;
}, 2000);
});
} else {
nextTick(() => {
createInitialScrollListener();
});
}
});
});

View File

@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template #header><XHeader :actions="headerActions"/></template>
<MkSpacer :contentMax="900">
<div class="_gaps">
<div>
<div ref="form">
<MkInput v-model="host" :debounce="true" class="">
<template #prefix><i class="ti ti-search"></i></template>
<template #label>{{ i18n.ts.host }}</template>
@ -42,8 +42,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkSelect>
</FormSplit>
</div>
<MkPagination v-slot="{items}" ref="instances" :key="host + state" :pagination="pagination">
<MkPagination v-slot="{items}" ref="instances" :key="host + state" :pagination="pagination" :tolerance="1" disable-observer>
<div :class="$style.instances">
<MkA v-for="instance in items" :key="instance.id" v-tooltip.mfm="`Status: ${getStatus(instance)}`" :class="$style.instance" :to="`/instance-info/${instance.host}`">
<MkInstanceCardMini :instance="instance"/>
@ -70,13 +69,14 @@ import { definePageMetadata } from '@/scripts/page-metadata.js';
let host = $ref('');
let state = $ref('federating');
let sort = $ref('+pubSub');
const pagination = {
endpoint: 'federation/instances' as const,
limit: 10,
limit: 30,
offsetMode: true,
params: computed(() => ({
sort: sort,
host: host !== '' ? host : null,
host: host || null,
...(
state === 'federating' ? { federating: true } :
state === 'subscribing' ? { subscribing: true } :

View File

@ -28,9 +28,9 @@ export function getScrollPosition(el: HTMLElement | null): number {
return container == null ? window.scrollY : container.scrollTop;
}
export function onScrollTop(el: HTMLElement, cb: () => unknown, tolerance = 1, once = false) {
export function onScrollTop(el: HTMLElement, cb: () => unknown, tolerance = 1, once = false, immediate = true) {
// とりあえず評価してみる
if (el.isConnected && isTopVisible(el)) {
if (el.isConnected && isTopVisible(el) && immediate) {
cb();
if (once) return null;
}
@ -50,11 +50,11 @@ export function onScrollTop(el: HTMLElement, cb: () => unknown, tolerance = 1, o
return removeListener;
}
export function onScrollBottom(el: HTMLElement, cb: () => unknown, tolerance = 1, once = false) {
export function onScrollBottom(el: HTMLElement, cb: () => unknown, tolerance = 1, once = false, immediate = true) {
const container = getScrollContainer(el);
// とりあえず評価してみる
if (el.isConnected && isBottomVisible(el, tolerance, container)) {
if (el.isConnected && isBottomVisible(el, tolerance, container) && immediate) {
cb();
if (once) return null;
}
@ -132,3 +132,43 @@ export function getBodyScrollHeight() {
document.body.clientHeight, document.documentElement.clientHeight,
);
}
export function onScrollDownOnce(el: HTMLElement, cb: () => unknown): () => void {
const containerEl = getScrollContainer(el);
const targetEl = containerEl ?? window;
let initialScrollTop = containerEl?.scrollTop || 0;
const listener = () => {
if ((containerEl?.scrollTop || 0) > initialScrollTop) {
cb();
targetEl.removeEventListener('scroll', listener);
}
};
targetEl.addEventListener('scroll', listener, { passive: true });
return () => {
targetEl.removeEventListener('scroll', listener);
};
}
export function onScrollUpOnce(el: HTMLElement, cb: () => unknown): () => void {
const containerEl = getScrollContainer(el);
const targetEl = containerEl ?? window;
let initialScrollTop = containerEl?.scrollTop || 0;
const listener = () => {
if ((containerEl?.scrollTop || 0) < initialScrollTop) {
cb();
targetEl.removeEventListener('scroll', listener);
}
};
targetEl.addEventListener('scroll', listener, { passive: true });
return () => {
targetEl.removeEventListener('scroll', listener);
};
}