mirror of
https://github.com/misskey-dev/misskey.git
synced 2026-07-25 12:05:19 +02:00
revert(frontend): "タイムラインのリアルタイム更新のアニメーションでTransitionGroupを使用しないように" (#17787)
Revert "perf(frontend): タイムラインのリアルタイム更新のアニメーションでTransitionGroupを使用しないように (#17708)"
This reverts commit 152e0fc48c.
This commit is contained in:
@@ -29,7 +29,6 @@
|
||||
- Fix: 幅が狭い画面で動画の再生が困難な問題を修正
|
||||
- Fix: 一部の画像のみセンシティブなとき、ビューワー内で画像を切り替えるとセンシティブな画像がそのまま表示される問題を修正
|
||||
- Fix: 一部の画像をビューワーで読み込んだ際に正しく表示されない問題を修正
|
||||
- Enhance: タイムラインの読み込みパフォーマンスを改善
|
||||
- Enhance: タブがバックグラウンドの間は必要ない定期更新処理を停止するように
|
||||
- Fix: 「画像を新しいタブで開く」が機能しなくなっていた問題を修正
|
||||
- Fix: デバイスタイプをスマートフォンに固定している状態で画面幅が広いとき、画面左上のアイコンが表示されない問題を修正
|
||||
|
||||
@@ -19,15 +19,18 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<div :class="$style.newBg2"></div>
|
||||
<button class="_button" :class="$style.newButton" @click="releaseQueue()"><i class="ti ti-circle-arrow-up"></i> {{ i18n.ts.newNote }}</button>
|
||||
</div>
|
||||
<div :class="$style.notes">
|
||||
<component
|
||||
:is="prefer.s.animation ? MkStreamingTimelineItem : 'div'"
|
||||
v-for="(note, i) in paginator.items.value"
|
||||
v-bind="prefer.s.animation ? { animatingIn: note._shouldAnimateIn_, animatingOut: note._shouldAnimateOut_ } : {}"
|
||||
:key="note.id"
|
||||
:data-scroll-anchor="note.id"
|
||||
>
|
||||
<div v-if="i > 0 && isSeparatorNeeded(paginator.items.value[i -1].createdAt, note.createdAt) && paginator.items.value[i -1]._shouldAnimateOut_ !== true">
|
||||
<component
|
||||
:is="prefer.s.animation ? TransitionGroup : 'div'"
|
||||
:class="$style.notes"
|
||||
:enterActiveClass="$style.transition_x_enterActive"
|
||||
:leaveActiveClass="$style.transition_x_leaveActive"
|
||||
:enterFromClass="$style.transition_x_enterFrom"
|
||||
:leaveToClass="$style.transition_x_leaveTo"
|
||||
:moveClass="$style.transition_x_move"
|
||||
tag="div"
|
||||
>
|
||||
<template v-for="(note, i) in paginator.items.value" :key="note.id">
|
||||
<div v-if="i > 0 && isSeparatorNeeded(paginator.items.value[i -1].createdAt, note.createdAt)" :data-scroll-anchor="note.id">
|
||||
<div :class="$style.date">
|
||||
<span><i class="ti ti-chevron-up"></i> {{ getSeparatorInfo(paginator.items.value[i -1].createdAt, note.createdAt)?.prevText }}</span>
|
||||
<span style="height: 1em; width: 1px; background: var(--MI_THEME-divider);"></span>
|
||||
@@ -35,15 +38,15 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</div>
|
||||
<MkNote :class="$style.note" :note="note" :withHardMute="true"/>
|
||||
</div>
|
||||
<div v-else-if="note._shouldInsertAd_">
|
||||
<div v-else-if="note._shouldInsertAd_" :data-scroll-anchor="note.id">
|
||||
<MkNote :class="$style.note" :note="note" :withHardMute="true"/>
|
||||
<div :class="$style.ad">
|
||||
<MkAd :preferForms="['horizontal', 'horizontal-big']"/>
|
||||
</div>
|
||||
</div>
|
||||
<MkNote v-else :class="$style.note" :note="note" :withHardMute="true"/>
|
||||
</component>
|
||||
</div>
|
||||
<MkNote v-else :class="$style.note" :note="note" :withHardMute="true" :data-scroll-anchor="note.id"/>
|
||||
</template>
|
||||
</component>
|
||||
<button v-show="paginator.canFetchOlder.value" key="_more_" v-appear="prefer.s.enableInfiniteScroll ? paginator.fetchOlder : null" :disabled="paginator.fetchingOlder.value" class="_button" :class="$style.more" @click="paginator.fetchOlder">
|
||||
<div v-if="!paginator.fetchingOlder.value">{{ i18n.ts.loadMore }}</div>
|
||||
<MkLoading v-else :inline="true"/>
|
||||
@@ -53,7 +56,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, watch, onUnmounted, provide, useTemplateRef, onMounted, markRaw } from 'vue';
|
||||
import { computed, watch, onUnmounted, provide, useTemplateRef, TransitionGroup, onMounted, shallowRef, ref, markRaw } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import { useInterval } from '@@/js/use-interval.js';
|
||||
import { useDocumentVisibility } from '@@/js/use-document-visibility.js';
|
||||
@@ -69,10 +72,10 @@ import { instance } from '@/instance.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
import { store } from '@/store.js';
|
||||
import MkNote from '@/components/MkNote.vue';
|
||||
import MkStreamingTimelineItem, { ITEM_REMOVAL_MS } from '@/components/MkStreamingTimelineItem.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { DI } from '@/di.js';
|
||||
import { useGlobalEvent } from '@/events.js';
|
||||
import { globalEvents, useGlobalEvent } from '@/events.js';
|
||||
import { isSeparatorNeeded, getSeparatorInfo } from '@/utility/timeline-date-separate.js';
|
||||
import { Paginator } from '@/utility/paginator.js';
|
||||
|
||||
@@ -101,8 +104,6 @@ provide('inTimeline', true);
|
||||
provide('tl_withSensitive', computed(() => props.withSensitive));
|
||||
provide(DI.inChannel, computed(() => props.src === 'channel' ? props.channel ?? null : null));
|
||||
|
||||
const itemRemovalDelay = prefer.s.animation ? ITEM_REMOVAL_MS : false;
|
||||
|
||||
let paginator: IPaginator<Misskey.entities.Note>;
|
||||
|
||||
if (props.src === 'antenna') {
|
||||
@@ -111,7 +112,6 @@ if (props.src === 'antenna') {
|
||||
antennaId: props.antenna!,
|
||||
})),
|
||||
useShallowRef: true,
|
||||
itemRemovalDelay,
|
||||
}));
|
||||
} else if (props.src === 'home') {
|
||||
paginator = markRaw(new Paginator('notes/timeline', {
|
||||
@@ -120,7 +120,6 @@ if (props.src === 'antenna') {
|
||||
withFiles: props.onlyFiles ? true : undefined,
|
||||
})),
|
||||
useShallowRef: true,
|
||||
itemRemovalDelay,
|
||||
}));
|
||||
} else if (props.src === 'local') {
|
||||
paginator = markRaw(new Paginator('notes/local-timeline', {
|
||||
@@ -130,7 +129,6 @@ if (props.src === 'antenna') {
|
||||
withFiles: props.onlyFiles ? true : undefined,
|
||||
})),
|
||||
useShallowRef: true,
|
||||
itemRemovalDelay,
|
||||
}));
|
||||
} else if (props.src === 'social') {
|
||||
paginator = markRaw(new Paginator('notes/hybrid-timeline', {
|
||||
@@ -140,7 +138,6 @@ if (props.src === 'antenna') {
|
||||
withFiles: props.onlyFiles ? true : undefined,
|
||||
})),
|
||||
useShallowRef: true,
|
||||
itemRemovalDelay,
|
||||
}));
|
||||
} else if (props.src === 'global') {
|
||||
paginator = markRaw(new Paginator('notes/global-timeline', {
|
||||
@@ -149,12 +146,10 @@ if (props.src === 'antenna') {
|
||||
withFiles: props.onlyFiles ? true : undefined,
|
||||
})),
|
||||
useShallowRef: true,
|
||||
itemRemovalDelay,
|
||||
}));
|
||||
} else if (props.src === 'mentions') {
|
||||
paginator = markRaw(new Paginator('notes/mentions', {
|
||||
useShallowRef: true,
|
||||
itemRemovalDelay,
|
||||
}));
|
||||
} else if (props.src === 'directs') {
|
||||
paginator = markRaw(new Paginator('notes/mentions', {
|
||||
@@ -162,7 +157,6 @@ if (props.src === 'antenna') {
|
||||
visibility: 'specified',
|
||||
},
|
||||
useShallowRef: true,
|
||||
itemRemovalDelay,
|
||||
}));
|
||||
} else if (props.src === 'list') {
|
||||
paginator = markRaw(new Paginator('notes/user-list-timeline', {
|
||||
@@ -172,7 +166,6 @@ if (props.src === 'antenna') {
|
||||
listId: props.list!,
|
||||
})),
|
||||
useShallowRef: true,
|
||||
itemRemovalDelay,
|
||||
}));
|
||||
} else if (props.src === 'channel') {
|
||||
paginator = markRaw(new Paginator('channels/timeline', {
|
||||
@@ -180,7 +173,6 @@ if (props.src === 'antenna') {
|
||||
channelId: props.channel!,
|
||||
})),
|
||||
useShallowRef: true,
|
||||
itemRemovalDelay,
|
||||
}));
|
||||
} else if (props.src === 'role') {
|
||||
paginator = markRaw(new Paginator('roles/notes', {
|
||||
@@ -188,7 +180,6 @@ if (props.src === 'antenna') {
|
||||
roleId: props.role!,
|
||||
})),
|
||||
useShallowRef: true,
|
||||
itemRemovalDelay,
|
||||
}));
|
||||
} else {
|
||||
throw new Error('Unrecognized timeline type: ' + props.src);
|
||||
@@ -437,16 +428,45 @@ defineExpose({
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
.transition_x_move {
|
||||
transition: transform 0.7s cubic-bezier(0.23, 1, 0.32, 1);
|
||||
}
|
||||
|
||||
.transition_x_enterActive {
|
||||
transition: transform 0.7s cubic-bezier(0.23, 1, 0.32, 1), opacity 0.7s cubic-bezier(0.23, 1, 0.32, 1);
|
||||
|
||||
&.note,
|
||||
.note {
|
||||
/* Skip Note Rendering有効時、TransitionGroupでnoteを追加するときに一瞬がくっとなる問題を抑制する */
|
||||
content-visibility: visible !important;
|
||||
}
|
||||
}
|
||||
|
||||
.transition_x_leaveActive {
|
||||
transition: height 0.2s cubic-bezier(0,.5,.5,1), opacity 0.2s cubic-bezier(0,.5,.5,1);
|
||||
}
|
||||
|
||||
.transition_x_enterFrom {
|
||||
opacity: 0;
|
||||
transform: translateY(max(-64px, -100%));
|
||||
}
|
||||
|
||||
@supports (interpolate-size: allow-keywords) {
|
||||
.transition_x_leaveTo {
|
||||
interpolate-size: allow-keywords; // heightのtransitionを動作させるために必要
|
||||
height: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.transition_x_leaveTo {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.notes {
|
||||
container-type: inline-size;
|
||||
background: var(--MI_THEME-panel);
|
||||
}
|
||||
|
||||
.date,
|
||||
.note {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.note:not(:empty) {
|
||||
border-bottom: solid 0.5px var(--MI_THEME-divider);
|
||||
}
|
||||
|
||||
@@ -14,15 +14,16 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</div>
|
||||
|
||||
<div v-else ref="rootEl">
|
||||
<div :class="$style.notifications">
|
||||
<component
|
||||
:is="prefer.s.animation ? MkStreamingTimelineItem : 'div'"
|
||||
v-for="(notification, i) in paginator.items.value"
|
||||
v-bind="prefer.s.animation ? { animatingIn: notification._shouldAnimateIn_, animatingOut: notification._shouldAnimateOut_ } : {}"
|
||||
:key="notification.id"
|
||||
:data-scroll-anchor="notification.id"
|
||||
:class="$style.item"
|
||||
>
|
||||
<component
|
||||
:is="prefer.s.animation ? TransitionGroup : 'div'" :class="[$style.notifications]"
|
||||
:enterActiveClass="$style.transition_x_enterActive"
|
||||
:leaveActiveClass="$style.transition_x_leaveActive"
|
||||
:enterFromClass="$style.transition_x_enterFrom"
|
||||
:leaveToClass="$style.transition_x_leaveTo"
|
||||
:moveClass="$style.transition_x_move"
|
||||
tag="div"
|
||||
>
|
||||
<div v-for="(notification, i) in paginator.items.value" :key="notification.id" :data-scroll-anchor="notification.id" :class="$style.item">
|
||||
<div v-if="i > 0 && isSeparatorNeeded(paginator.items.value[i -1].createdAt, notification.createdAt)" :class="$style.date">
|
||||
<span><i class="ti ti-chevron-up"></i> {{ getSeparatorInfo(paginator.items.value[i -1].createdAt, notification.createdAt)?.prevText }}</span>
|
||||
<span style="height: 1em; width: 1px; background: var(--MI_THEME-divider);"></span>
|
||||
@@ -30,8 +31,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</div>
|
||||
<MkNote v-if="['reply', 'quote', 'mention'].includes(notification.type) && 'note' in notification" :class="$style.content" :note="notification.note" :withHardMute="true"/>
|
||||
<XNotification v-else :class="$style.content" :notification="notification" :withTime="true" :full="true"/>
|
||||
</component>
|
||||
</div>
|
||||
</div>
|
||||
</component>
|
||||
<button v-show="paginator.canFetchOlder.value" key="_more_" v-appear="prefer.s.enableInfiniteScroll ? paginator.fetchOlder : null" :disabled="paginator.fetchingOlder.value" class="_button" :class="$style.more" @click="paginator.fetchOlder">
|
||||
<div v-if="!paginator.fetchingOlder.value">{{ i18n.ts.loadMore }}</div>
|
||||
<MkLoading v-else/>
|
||||
@@ -41,7 +42,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onUnmounted, onMounted, computed, useTemplateRef, markRaw, watch } from 'vue';
|
||||
import { onUnmounted, onMounted, computed, useTemplateRef, TransitionGroup, markRaw, watch } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import { notificationTypes } from 'misskey-js';
|
||||
import { useInterval } from '@@/js/use-interval.js';
|
||||
@@ -52,7 +53,6 @@ import MkNote from '@/components/MkNote.vue';
|
||||
import { useStream } from '@/stream.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import MkPullToRefresh from '@/components/MkPullToRefresh.vue';
|
||||
import MkStreamingTimelineItem, { ITEM_REMOVAL_MS } from '@/components/MkStreamingTimelineItem.vue';
|
||||
import { prefer } from '@/preferences.js';
|
||||
import { store } from '@/store.js';
|
||||
import { isSeparatorNeeded, getSeparatorInfo } from '@/utility/timeline-date-separate.js';
|
||||
@@ -64,20 +64,16 @@ const props = defineProps<{
|
||||
|
||||
const rootEl = useTemplateRef('rootEl');
|
||||
|
||||
const itemRemovalDelay = prefer.s.animation ? ITEM_REMOVAL_MS : false;
|
||||
|
||||
const paginator = prefer.s.useGroupedNotifications ? markRaw(new Paginator('i/notifications-grouped', {
|
||||
limit: 20,
|
||||
computedParams: computed(() => ({
|
||||
excludeTypes: props.excludeTypes ?? undefined,
|
||||
})),
|
||||
itemRemovalDelay,
|
||||
})) : markRaw(new Paginator('i/notifications', {
|
||||
limit: 20,
|
||||
computedParams: computed(() => ({
|
||||
excludeTypes: props.excludeTypes ?? undefined,
|
||||
})),
|
||||
itemRemovalDelay,
|
||||
}));
|
||||
|
||||
const MIN_POLLING_INTERVAL = 1000 * 10;
|
||||
@@ -193,8 +189,38 @@ defineExpose({
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
.item {
|
||||
border-bottom: solid 0.5px var(--MI_THEME-divider);
|
||||
.transition_x_move {
|
||||
transition: transform 0.7s cubic-bezier(0.23, 1, 0.32, 1);
|
||||
}
|
||||
|
||||
.transition_x_enterActive {
|
||||
transition: transform 0.7s cubic-bezier(0.23, 1, 0.32, 1), opacity 0.7s cubic-bezier(0.23, 1, 0.32, 1);
|
||||
|
||||
&.content,
|
||||
.content {
|
||||
/* Skip Note Rendering有効時、TransitionGroupで通知を追加するときに一瞬がくっとなる問題を抑制する */
|
||||
content-visibility: visible !important;
|
||||
}
|
||||
}
|
||||
|
||||
.transition_x_leaveActive {
|
||||
transition: height 0.2s cubic-bezier(0,.5,.5,1), opacity 0.2s cubic-bezier(0,.5,.5,1);
|
||||
}
|
||||
|
||||
.transition_x_enterFrom {
|
||||
opacity: 0;
|
||||
transform: translateY(max(-64px, -100%));
|
||||
}
|
||||
|
||||
@supports (interpolate-size: allow-keywords) {
|
||||
.transition_x_enterFrom {
|
||||
interpolate-size: allow-keywords; // heightのtransitionを動作させるために必要
|
||||
height: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.transition_x_leaveTo {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.notifications {
|
||||
@@ -202,6 +228,10 @@ defineExpose({
|
||||
background: var(--MI_THEME-panel);
|
||||
}
|
||||
|
||||
.item {
|
||||
border-bottom: solid 0.5px var(--MI_THEME-divider);
|
||||
}
|
||||
|
||||
.date {
|
||||
display: flex;
|
||||
font-size: 85%;
|
||||
|
||||
@@ -1,127 +0,0 @@
|
||||
<!--
|
||||
SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div
|
||||
ref="rootEl"
|
||||
:class="[$style.root, {
|
||||
[$style.enter]: animatingIn && animating && !animatingOut,
|
||||
[$style.leave]: animatingOut,
|
||||
[$style.animating]: animating,
|
||||
}]"
|
||||
@animationend.self="onAnimationEnd"
|
||||
@animationcancel.self="onAnimationEnd"
|
||||
>
|
||||
<div ref="innerEl">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export const ITEM_REMOVAL_MS = 200;
|
||||
|
||||
const supportsInterpolateSize = CSS.supports('interpolate-size: allow-keywords');
|
||||
let resizeObserver: ResizeObserver | null = null;
|
||||
|
||||
if (!supportsInterpolateSize) {
|
||||
resizeObserver = new ResizeObserver((entries) => {
|
||||
for (const entry of entries) {
|
||||
const target = entry.target as HTMLElement;
|
||||
const root = target.parentElement;
|
||||
if (root != null) {
|
||||
root.style.setProperty('--child-height', `${entry.contentRect.height}px`);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useTemplateRef, onMounted, onBeforeUnmount, ref, watch } from 'vue';
|
||||
|
||||
const props = defineProps<{
|
||||
animatingIn?: boolean;
|
||||
animatingOut?: boolean;
|
||||
}>();
|
||||
|
||||
const rootEl = useTemplateRef('rootEl');
|
||||
const innerEl = useTemplateRef('innerEl');
|
||||
|
||||
const animating = ref(false);
|
||||
|
||||
watch([() => props.animatingIn, () => props.animatingOut], ([animatingIn, animatingOut]) => {
|
||||
if (animatingIn === true || animatingOut === true) animating.value = true;
|
||||
}, { immediate: true });
|
||||
|
||||
function onAnimationEnd() {
|
||||
// 削除アニメーション中(enterから差し替わった場合を含む)は、要素が消えるまで再生中扱いのままにする
|
||||
if (props.animatingOut === true) return;
|
||||
animating.value = false;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (resizeObserver != null && rootEl.value != null && innerEl.value != null) {
|
||||
resizeObserver.observe(innerEl.value);
|
||||
rootEl.value.style.setProperty('--child-height', `${innerEl.value.getBoundingClientRect().height}px`);
|
||||
}
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (resizeObserver != null && innerEl.value != null) {
|
||||
resizeObserver.unobserve(innerEl.value);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style module lang="scss">
|
||||
.animating {
|
||||
overflow: clip;
|
||||
}
|
||||
|
||||
.inner {
|
||||
display: flow-root;
|
||||
}
|
||||
|
||||
.enter {
|
||||
animation: enterAnim 0.7s cubic-bezier(0.23, 1, 0.32, 1) both;
|
||||
}
|
||||
|
||||
.leave {
|
||||
animation: leaveAnim 0.2s cubic-bezier(0,.5,.5,1) both;
|
||||
}
|
||||
|
||||
@supports (interpolate-size: allow-keywords) {
|
||||
.root {
|
||||
interpolate-size: allow-keywords;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes enterAnim {
|
||||
from {
|
||||
height: 0;
|
||||
opacity: 0;
|
||||
transform: translateY(max(-64px, -100%));
|
||||
}
|
||||
|
||||
to {
|
||||
height: var(--child-height, auto);
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes leaveAnim {
|
||||
from {
|
||||
height: var(--child-height, auto);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
to {
|
||||
height: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -17,8 +17,6 @@ export type MisskeyEntity = {
|
||||
id: string;
|
||||
createdAt: string;
|
||||
_shouldInsertAd_?: boolean;
|
||||
_shouldAnimateIn_?: boolean;
|
||||
_shouldAnimateOut_?: boolean;
|
||||
};
|
||||
|
||||
type AbsEndpointType = {
|
||||
@@ -109,8 +107,6 @@ export class Paginator<
|
||||
private canFetchDetection: 'safe' | 'limit' | null = null;
|
||||
private aheadQueue: T[] = [];
|
||||
private useShallowRef: SRef;
|
||||
private itemRemovalDelay: number | false;
|
||||
private removalTimers = new Map<string, number>();
|
||||
|
||||
// 配列内の要素をどのような順序で並べるか
|
||||
// newest: 新しいものが先頭 (default)
|
||||
@@ -142,9 +138,6 @@ export class Paginator<
|
||||
|
||||
useShallowRef?: SRef;
|
||||
|
||||
// アイテム削除時にアニメーションを待つ時間 (ms)
|
||||
itemRemovalDelay?: number | false;
|
||||
|
||||
canSearch?: boolean;
|
||||
searchParamName?: keyof E['req'];
|
||||
}) {
|
||||
@@ -167,7 +160,6 @@ export class Paginator<
|
||||
this.noPaging = props.noPaging ?? false;
|
||||
this.offsetMode = props.offsetMode ?? false;
|
||||
this.canSearch = props.canSearch ?? false;
|
||||
this.itemRemovalDelay = props.itemRemovalDelay ?? false;
|
||||
this.searchParamName = props.searchParamName ?? 'search';
|
||||
|
||||
this.getNewestId = this.getNewestId.bind(this);
|
||||
@@ -199,7 +191,6 @@ export class Paginator<
|
||||
}
|
||||
|
||||
public async init(): Promise<void> {
|
||||
this.clearRemovalTimers();
|
||||
this.items.value = [];
|
||||
this.aheadQueue = [];
|
||||
this.queuedAheadItemsCount.value = 0;
|
||||
@@ -393,8 +384,6 @@ export class Paginator<
|
||||
|
||||
public prepend(item: T): void {
|
||||
if (this.items.value.some(x => x.id === item.id)) return;
|
||||
item._shouldAnimateIn_ = true;
|
||||
item._shouldAnimateOut_ = false;
|
||||
this.items.value.unshift(item);
|
||||
this.trim(false);
|
||||
if (this.useShallowRef) triggerRef(this.items);
|
||||
@@ -410,10 +399,6 @@ export class Paginator<
|
||||
|
||||
public releaseQueue(): void {
|
||||
if (this.aheadQueue.length === 0) return; // これやらないと余計なre-renderが走る
|
||||
for (const item of this.aheadQueue) {
|
||||
item._shouldAnimateIn_ = false; // 一気に入るときは挿入アニメーションさせない
|
||||
item._shouldAnimateOut_ = false;
|
||||
}
|
||||
this.unshiftItems(this.aheadQueue);
|
||||
this.aheadQueue = [];
|
||||
this.queuedAheadItemsCount.value = 0;
|
||||
@@ -422,43 +407,13 @@ export class Paginator<
|
||||
public removeItem(id: string): void {
|
||||
// TODO: queueからも消す
|
||||
|
||||
if (this.itemRemovalDelay === false) {
|
||||
const index = this.items.value.findIndex(x => x.id === id);
|
||||
if (index !== -1) {
|
||||
this.items.value.splice(index, 1);
|
||||
if (this.useShallowRef) triggerRef(this.items);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const index = this.items.value.findIndex(x => x.id === id);
|
||||
if (index !== -1) {
|
||||
this.items.value.splice(index, 1);
|
||||
if (this.useShallowRef) triggerRef(this.items);
|
||||
|
||||
const item = this.items.value[index]!;
|
||||
item._shouldAnimateOut_ = true;
|
||||
if (this.useShallowRef) triggerRef(this.items);
|
||||
|
||||
if (this.removalTimers.has(id)) return;
|
||||
|
||||
this.removalTimers.set(id, window.setTimeout(() => {
|
||||
this.removalTimers.delete(id);
|
||||
const currentIndex = this.items.value.findIndex(x => x.id === id);
|
||||
if (currentIndex !== -1) {
|
||||
this.items.value.splice(currentIndex, 1);
|
||||
if (this.useShallowRef) triggerRef(this.items);
|
||||
}
|
||||
}, this.itemRemovalDelay + 20)); // アニメーション終了からやや余裕をもたせる
|
||||
}
|
||||
}
|
||||
|
||||
private clearRemovalTimers(): void {
|
||||
for (const timer of this.removalTimers.values()) {
|
||||
window.clearTimeout(timer);
|
||||
}
|
||||
this.removalTimers.clear();
|
||||
}
|
||||
|
||||
public updateItem(id: string, updater: (item: T) => T): void {
|
||||
// TODO: queueのも更新
|
||||
|
||||
|
||||
Reference in New Issue
Block a user