perf(frontend): improve timeline performance
This commit is contained in:
parent
e8eb4e1965
commit
cf07e1e4b9
packages/frontend/src/components
|
@ -13,16 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #default="{ items: notes }">
|
<template #default="{ items: notes }">
|
||||||
<component
|
<div :class="[$style.root, { [$style.noGap]: noGap, '_gaps': !noGap, [$style.reverse]: pagination.reversed }]">
|
||||||
:is="prefer.s.animation ? TransitionGroup : 'div'"
|
|
||||||
:class="[$style.root, { [$style.noGap]: noGap, '_gaps': !noGap, [$style.reverse]: pagination.reversed }]"
|
|
||||||
: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 notes" :key="note.id">
|
<template v-for="(note, i) in notes" :key="note.id">
|
||||||
<div v-if="note._shouldInsertAd_" :class="[$style.noteWithAd, { '_gaps': !noGap }]" :data-scroll-anchor="note.id">
|
<div v-if="note._shouldInsertAd_" :class="[$style.noteWithAd, { '_gaps': !noGap }]" :data-scroll-anchor="note.id">
|
||||||
<MkNote :class="$style.note" :note="note" :withHardMute="true"/>
|
<MkNote :class="$style.note" :note="note" :withHardMute="true"/>
|
||||||
|
@ -32,19 +23,18 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
<MkNote v-else :class="$style.note" :note="note" :withHardMute="true" :data-scroll-anchor="note.id"/>
|
<MkNote v-else :class="$style.note" :note="note" :withHardMute="true" :data-scroll-anchor="note.id"/>
|
||||||
</template>
|
</template>
|
||||||
</component>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</MkPagination>
|
</MkPagination>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { useTemplateRef, TransitionGroup } from 'vue';
|
import { useTemplateRef } from 'vue';
|
||||||
import type { Paging } from '@/components/MkPagination.vue';
|
import type { Paging } from '@/components/MkPagination.vue';
|
||||||
import MkNote from '@/components/MkNote.vue';
|
import MkNote from '@/components/MkNote.vue';
|
||||||
import MkPagination from '@/components/MkPagination.vue';
|
import MkPagination from '@/components/MkPagination.vue';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { infoImageUrl } from '@/instance.js';
|
import { infoImageUrl } from '@/instance.js';
|
||||||
import { prefer } from '@/preferences.js';
|
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
pagination: Paging;
|
pagination: Paging;
|
||||||
|
@ -60,20 +50,6 @@ defineExpose({
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
.transition_x_move,
|
|
||||||
.transition_x_enterActive,
|
|
||||||
.transition_x_leaveActive {
|
|
||||||
transition: opacity 0.3s cubic-bezier(0,.5,.5,1), transform 0.3s cubic-bezier(0,.5,.5,1) !important;
|
|
||||||
}
|
|
||||||
.transition_x_enterFrom,
|
|
||||||
.transition_x_leaveTo {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateY(-50%);
|
|
||||||
}
|
|
||||||
.transition_x_leaveActive {
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
|
|
||||||
.reverse {
|
.reverse {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column-reverse;
|
flex-direction: column-reverse;
|
||||||
|
|
|
@ -5,29 +5,55 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<MkPullToRefresh ref="prComponent" :refresher="() => reloadTimeline()">
|
<MkPullToRefresh ref="prComponent" :refresher="() => reloadTimeline()">
|
||||||
<MkNotes
|
<MkPagination v-if="paginationQuery" ref="pagingComponent" :pagination="paginationQuery" @queue="emit('queue', $event)" @status="prComponent?.setDisabled($event)">
|
||||||
v-if="paginationQuery"
|
<template #empty>
|
||||||
ref="tlComponent"
|
<div class="_fullinfo">
|
||||||
:pagination="paginationQuery"
|
<img :src="infoImageUrl" draggable="false"/>
|
||||||
:noGap="!prefer.s.showGapBetweenNotesInTimeline"
|
<div>{{ i18n.ts.noNotes }}</div>
|
||||||
@queue="emit('queue', $event)"
|
</div>
|
||||||
@status="prComponent?.setDisabled($event)"
|
</template>
|
||||||
/>
|
|
||||||
|
<template #default="{ items: notes }">
|
||||||
|
<component
|
||||||
|
:is="prefer.s.animation ? TransitionGroup : 'div'"
|
||||||
|
:class="[$style.root, { [$style.noGap]: noGap, '_gaps': !noGap, [$style.reverse]: paginationQuery.reversed }]"
|
||||||
|
: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 notes" :key="note.id">
|
||||||
|
<div v-if="note._shouldInsertAd_" :class="[$style.noteWithAd, { '_gaps': !noGap }]" :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" :data-scroll-anchor="note.id"/>
|
||||||
|
</template>
|
||||||
|
</component>
|
||||||
|
</template>
|
||||||
|
</MkPagination>
|
||||||
</MkPullToRefresh>
|
</MkPullToRefresh>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, watch, onUnmounted, provide, useTemplateRef } from 'vue';
|
import { computed, watch, onUnmounted, provide, useTemplateRef, TransitionGroup } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import type { BasicTimelineType } from '@/timelines.js';
|
import type { BasicTimelineType } from '@/timelines.js';
|
||||||
import type { Paging } from '@/components/MkPagination.vue';
|
import type { Paging } from '@/components/MkPagination.vue';
|
||||||
import MkNotes from '@/components/MkNotes.vue';
|
|
||||||
import MkPullToRefresh from '@/components/MkPullToRefresh.vue';
|
import MkPullToRefresh from '@/components/MkPullToRefresh.vue';
|
||||||
import { useStream } from '@/stream.js';
|
import { useStream } from '@/stream.js';
|
||||||
import * as sound from '@/utility/sound.js';
|
import * as sound from '@/utility/sound.js';
|
||||||
import { $i } from '@/i.js';
|
import { $i } from '@/i.js';
|
||||||
import { instance } from '@/instance.js';
|
import { instance } from '@/instance.js';
|
||||||
import { prefer } from '@/preferences.js';
|
import { prefer } from '@/preferences.js';
|
||||||
|
import MkNote from '@/components/MkNote.vue';
|
||||||
|
import MkPagination from '@/components/MkPagination.vue';
|
||||||
|
import { i18n } from '@/i18n.js';
|
||||||
|
import { infoImageUrl } from '@/instance.js';
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
src: BasicTimelineType | 'mentions' | 'directs' | 'list' | 'antenna' | 'channel' | 'role';
|
src: BasicTimelineType | 'mentions' | 'directs' | 'list' | 'antenna' | 'channel' | 'role';
|
||||||
|
@ -68,12 +94,12 @@ type TimelineQueryType = {
|
||||||
};
|
};
|
||||||
|
|
||||||
const prComponent = useTemplateRef('prComponent');
|
const prComponent = useTemplateRef('prComponent');
|
||||||
const tlComponent = useTemplateRef('tlComponent');
|
const pagingComponent = useTemplateRef('pagingComponent');
|
||||||
|
|
||||||
let tlNotesCount = 0;
|
let tlNotesCount = 0;
|
||||||
|
|
||||||
function prepend(note) {
|
function prepend(note) {
|
||||||
if (tlComponent.value == null) return;
|
if (pagingComponent.value == null) return;
|
||||||
|
|
||||||
tlNotesCount++;
|
tlNotesCount++;
|
||||||
|
|
||||||
|
@ -81,7 +107,7 @@ function prepend(note) {
|
||||||
note._shouldInsertAd_ = true;
|
note._shouldInsertAd_ = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
tlComponent.value.pagingComponent?.prepend(note);
|
pagingComponent.value.prepend(note);
|
||||||
|
|
||||||
emit('note');
|
emit('note');
|
||||||
|
|
||||||
|
@ -93,6 +119,7 @@ function prepend(note) {
|
||||||
let connection: Misskey.ChannelConnection | null = null;
|
let connection: Misskey.ChannelConnection | null = null;
|
||||||
let connection2: Misskey.ChannelConnection | null = null;
|
let connection2: Misskey.ChannelConnection | null = null;
|
||||||
let paginationQuery: Paging | null = null;
|
let paginationQuery: Paging | null = null;
|
||||||
|
const noGap = !prefer.s.showGapBetweenNotesInTimeline;
|
||||||
|
|
||||||
const stream = useStream();
|
const stream = useStream();
|
||||||
|
|
||||||
|
@ -263,11 +290,11 @@ onUnmounted(() => {
|
||||||
|
|
||||||
function reloadTimeline() {
|
function reloadTimeline() {
|
||||||
return new Promise<void>((res) => {
|
return new Promise<void>((res) => {
|
||||||
if (tlComponent.value == null) return;
|
if (pagingComponent.value == null) return;
|
||||||
|
|
||||||
tlNotesCount = 0;
|
tlNotesCount = 0;
|
||||||
|
|
||||||
tlComponent.value.pagingComponent?.reload().then(() => {
|
pagingComponent.value.reload().then(() => {
|
||||||
res();
|
res();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -277,3 +304,56 @@ defineExpose({
|
||||||
reloadTimeline,
|
reloadTimeline,
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" module>
|
||||||
|
.transition_x_move,
|
||||||
|
.transition_x_enterActive,
|
||||||
|
.transition_x_leaveActive {
|
||||||
|
transition: opacity 0.3s cubic-bezier(0,.5,.5,1), transform 0.3s cubic-bezier(0,.5,.5,1) !important;
|
||||||
|
}
|
||||||
|
.transition_x_enterFrom,
|
||||||
|
.transition_x_leaveTo {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
}
|
||||||
|
.transition_x_leaveActive {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reverse {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column-reverse;
|
||||||
|
}
|
||||||
|
|
||||||
|
.root {
|
||||||
|
container-type: inline-size;
|
||||||
|
|
||||||
|
&.noGap {
|
||||||
|
background: var(--MI_THEME-panel);
|
||||||
|
|
||||||
|
.note {
|
||||||
|
border-bottom: solid 0.5px var(--MI_THEME-divider);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ad {
|
||||||
|
padding: 8px;
|
||||||
|
background-size: auto auto;
|
||||||
|
background-image: repeating-linear-gradient(45deg, transparent, transparent 8px, var(--MI_THEME-bg) 8px, var(--MI_THEME-bg) 14px);
|
||||||
|
border-bottom: solid 0.5px var(--MI_THEME-divider);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(.noGap) {
|
||||||
|
background: var(--MI_THEME-bg);
|
||||||
|
|
||||||
|
.note {
|
||||||
|
background: var(--MI_THEME-panel);
|
||||||
|
border-radius: var(--MI-radius);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ad:empty {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
Loading…
Reference in New Issue