feat: stream tags, home, and public timelines (#190)

zio/stable
Daniel Roe 2022-11-28 11:18:45 +00:00 committed by GitHub
parent d94bed686b
commit 5560fe66cc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 55 additions and 16 deletions

View File

@ -2,12 +2,13 @@
// @ts-expect-error missing types // @ts-expect-error missing types
import { DynamicScroller } from 'vue-virtual-scroller' import { DynamicScroller } from 'vue-virtual-scroller'
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css' import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
import type { Paginator } from 'masto' import type { Paginator, WsEvents } from 'masto'
const { paginator, keyProp = 'id', virtualScroller = false } = defineProps<{ const { paginator, stream, keyProp = 'id', virtualScroller = false } = defineProps<{
paginator: Paginator<any, any[]> paginator: Paginator<any, any[]>
keyProp?: string keyProp?: string
virtualScroller: boolean virtualScroller: boolean
stream?: WsEvents
}>() }>()
defineSlots<{ defineSlots<{
@ -15,14 +16,19 @@ defineSlots<{
item: any item: any
active?: boolean active?: boolean
} }
updater: {
number: number
update: () => void
}
loading: {} loading: {}
}>() }>()
const { items, state, endAnchor, error } = usePaginator(paginator) const { items, prevItems, update, state, endAnchor, error } = usePaginator(paginator, stream)
</script> </script>
<template> <template>
<div> <div>
<slot v-if="prevItems.length" name="updater" v-bind="{ number: prevItems.length, update }" />
<template v-if="virtualScroller"> <template v-if="virtualScroller">
<DynamicScroller <DynamicScroller
v-slot="{ item, active }" v-slot="{ item, active }"

View File

@ -1,15 +1,21 @@
<script setup lang="ts"> <script setup lang="ts">
// @ts-expect-error missing types // @ts-expect-error missing types
import { DynamicScrollerItem } from 'vue-virtual-scroller' import { DynamicScrollerItem } from 'vue-virtual-scroller'
import type { Paginator, Status } from 'masto' import type { Paginator, Status, WsEvents } from 'masto'
const { paginator } = defineProps<{ const { paginator, stream } = defineProps<{
paginator: Paginator<any, Status[]> paginator: Paginator<any, Status[]>
stream?: WsEvents
}>() }>()
</script> </script>
<template> <template>
<CommonPaginator :paginator="paginator" virtual-scroller> <CommonPaginator v-bind="{ paginator, stream }" virtual-scroller>
<template #updater="{ number, update }">
<button py-4 border="b base" flex="~ col" p-3 w-full text-primary font-bold @click="update">
Show {{ number }} new items
</button>
</template>
<template #default="{ item, active }"> <template #default="{ item, active }">
<DynamicScrollerItem :item="item" :active="active" tag="article"> <DynamicScrollerItem :item="item" :active="active" tag="article">
<StatusCard <StatusCard

View File

@ -1,11 +1,12 @@
import type { Paginator } from 'masto' import type { Paginator, WsEvents } from 'masto'
import { useDeactivated } from './lifecycle' import { useDeactivated } from './lifecycle'
import type { PaginatorState } from '~/types' import type { PaginatorState } from '~/types'
export function usePaginator<T>(paginator: Paginator<any, T[]>) { export function usePaginator<T>(paginator: Paginator<any, T[]>, stream?: WsEvents) {
const state = ref<PaginatorState>('idle') const state = ref<PaginatorState>('idle')
const items = ref<T[]>([]) const items = ref<T[]>([])
const newItems = ref<T[]>([]) const nextItems = ref<T[]>([])
const prevItems = ref<T[]>([])
const endAnchor = ref<HTMLDivElement>() const endAnchor = ref<HTMLDivElement>()
const bound = reactive(useElementBounding(endAnchor)) const bound = reactive(useElementBounding(endAnchor))
@ -13,6 +14,22 @@ export function usePaginator<T>(paginator: Paginator<any, T[]>) {
const error = ref<unknown | undefined>() const error = ref<unknown | undefined>()
const deactivated = useDeactivated() const deactivated = useDeactivated()
async function update() {
items.value.unshift(...prevItems.value)
prevItems.value = []
}
stream?.on('update', (status) => {
prevItems.value.unshift(status as any)
})
// TODO: update statuses
stream?.on('status.update', (status) => {
const index = items.value.findIndex((s: any) => s.id === status.id)
if (index >= 0)
items.value[index] = status as any
})
async function loadNext() { async function loadNext() {
if (state.value !== 'idle') if (state.value !== 'idle')
return return
@ -22,8 +39,8 @@ export function usePaginator<T>(paginator: Paginator<any, T[]>) {
const result = await paginator.next() const result = await paginator.next()
if (result.value?.length) { if (result.value?.length) {
newItems.value = result.value nextItems.value = result.value
items.value.push(...newItems.value) items.value.push(...nextItems.value)
state.value = 'idle' state.value = 'idle'
} }
else { else {
@ -59,7 +76,9 @@ export function usePaginator<T>(paginator: Paginator<any, T[]>) {
return { return {
items, items,
newItems, prevItems,
nextItems,
update,
state, state,
error, error,
endAnchor, endAnchor,

View File

@ -12,6 +12,8 @@ if (useRoute().path === '/signin/callback') {
} }
const paginator = useMasto().timelines.getHomeIterable() const paginator = useMasto().timelines.getHomeIterable()
const stream = await useMasto().stream.streamUser()
onBeforeUnmount(() => stream.disconnect())
const { t } = useI18n() const { t } = useI18n()
useHead({ useHead({
@ -26,7 +28,7 @@ useHead({
</template> </template>
<slot> <slot>
<PublishWidget draft-key="home" border="b base" /> <PublishWidget draft-key="home" border="b base" />
<TimelinePaginator :paginator="paginator" /> <TimelinePaginator v-bind="{ paginator, stream }" />
</slot> </slot>
</MainContent> </MainContent>
</template> </template>

View File

@ -1,5 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
const paginator = useMasto().timelines.getPublicIterable() const paginator = useMasto().timelines.getPublicIterable()
const stream = await useMasto().stream.streamPublicTimeline()
onBeforeUnmount(() => stream.disconnect())
useHead({ useHead({
title: 'Federated', title: 'Federated',
@ -13,7 +15,7 @@ useHead({
</template> </template>
<slot> <slot>
<TimelinePaginator :paginator="paginator" /> <TimelinePaginator v-bind="{ paginator, stream }" />
</slot> </slot>
</MainContent> </MainContent>
</template> </template>

View File

@ -1,5 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
const paginator = useMasto().timelines.getPublicIterable({ local: true }) const paginator = useMasto().timelines.getPublicIterable({ local: true })
const stream = await useMasto().stream.streamCommunityTimeline()
onBeforeUnmount(() => stream.disconnect())
useHead({ useHead({
title: 'Local', title: 'Local',
@ -13,7 +15,7 @@ useHead({
</template> </template>
<slot> <slot>
<TimelinePaginator :paginator="paginator" /> <TimelinePaginator v-bind="{ paginator, stream }" />
</slot> </slot>
</MainContent> </MainContent>
</template> </template>

View File

@ -3,6 +3,8 @@ const params = useRoute().params
const tag = $(computedEager(() => params.tag as string)) const tag = $(computedEager(() => params.tag as string))
const paginator = useMasto().timelines.getHashtagIterable(tag) const paginator = useMasto().timelines.getHashtagIterable(tag)
const stream = await useMasto().stream.streamTagTimeline(tag)
onBeforeUnmount(() => stream.disconnect())
useHead({ useHead({
title: `#${tag}`, title: `#${tag}`,
@ -16,7 +18,7 @@ useHead({
</template> </template>
<slot> <slot>
<TimelinePaginator :paginator="paginator" /> <TimelinePaginator v-bind="{ paginator, stream }" />
</slot> </slot>
</MainContent> </MainContent>
</template> </template>