feat: global relationships batching (#24)

zio/stable
patak 2022-11-22 14:03:36 +01:00 committed by GitHub
parent 5dc79df091
commit ac156034d1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 47 additions and 46 deletions

View File

@ -1,25 +1,21 @@
<script setup lang="ts"> <script setup lang="ts">
import type { Account } from 'masto' import type { Account } from 'masto'
const { account, following } = defineProps<{ const { account } = defineProps<{
account: Account account: Account
following?: boolean
}>() }>()
const masto = await useMasto() const masto = await useMasto()
let isFollowing = $ref<boolean | undefined>(following) const relationship = $(useRelationship(account))
watch($$(following), () => {
isFollowing = following
})
function unfollow() { function unfollow() {
masto.accounts.unfollow(account.id) masto.accounts.unfollow(account.id)
isFollowing = false relationship!.following = false
} }
function follow() { function follow() {
masto.accounts.follow(account.id) masto.accounts.follow(account.id)
isFollowing = true relationship!.following = true
} }
</script> </script>
@ -27,8 +23,8 @@ function follow() {
<div flex justify-between> <div flex justify-between>
<AccountInfo :account="account" p3 /> <AccountInfo :account="account" p3 />
<div h-full p5> <div h-full p5>
<div v-if="isFollowing === true" color-purple hover:color-gray hover:cursor-pointer i-ri:user-unfollow-fill @click="unfollow" /> <div v-if="relationship?.following === true" color-purple hover:color-gray hover:cursor-pointer i-ri:user-unfollow-fill @click="unfollow" />
<div v-else-if="isFollowing === false" color-gray hover:color-purple hover:cursor-pointer i-ri:user-follow-fill @click="follow" /> <div v-else-if="relationship?.following === false" color-gray hover:color-purple hover:cursor-pointer i-ri:user-follow-fill @click="follow" />
</div> </div>
</div> </div>
</template> </template>

View File

@ -5,22 +5,14 @@ const { account } = defineProps<{
account: Account account: Account
}>() }>()
let isFollowing = $ref<boolean | undefined>() const relationship = $(useRelationship(account))
let isFollowedBy = $ref<boolean | undefined>()
let masto: MastoClient let masto: MastoClient
onMounted(async () => {
masto ??= await useMasto()
const relationship = (await masto.accounts.fetchRelationships([account.id]))[0]
isFollowing = relationship.following
isFollowedBy = relationship.followedBy
})
async function toggleFollow() { async function toggleFollow() {
isFollowing = !isFollowing relationship!.following = !relationship!.following
masto ??= await useMasto() masto ??= await useMasto()
await masto.accounts[isFollowing ? 'follow' : 'unfollow'](account.id) await masto.accounts[relationship!.following ? 'follow' : 'unfollow'](account.id)
} }
const createdAt = $computed(() => { const createdAt = $computed(() => {
@ -50,9 +42,9 @@ const createdAt = $computed(() => {
</NuxtLink> </NuxtLink>
</div> </div>
<div flex gap-2> <div flex gap-2>
<button flex gap-1 items-center w-full rounded op75 hover="op100 text-white b-purple" group @click="toggleFollow"> <button v-if="relationship" flex gap-1 items-center w-full rounded op75 hover="op100 text-white b-purple" group @click="toggleFollow">
<div rounded w-30 p2 group-hover="bg-rose/10"> <div rounded w-30 p2 group-hover="bg-rose/10">
{{ isFollowing ? 'Unfollow' : isFollowedBy ? 'Follow back' : 'Follow' }} {{ relationship?.following ? 'Unfollow' : relationship?.followedBy ? 'Follow back' : 'Follow' }}
</div> </div>
</button> </button>
<!-- <button flex gap-1 items-center w-full rounded op75 hover="op100 text-purple" group> <!-- <button flex gap-1 items-center w-full rounded op75 hover="op100 text-purple" group>

View File

@ -4,30 +4,16 @@ import type { Account, Paginator } from 'masto'
const { paginator } = defineProps<{ const { paginator } = defineProps<{
paginator: Paginator<any, Account[]> paginator: Paginator<any, Account[]>
}>() }>()
const masto = await useMasto()
const metadataMap = $ref<{ [key: string]: { following?: boolean } }>({})
async function onNewItems(items: Account[]) {
for (const item of items)
metadataMap[item.id] = { following: undefined }
const relationships = await masto.accounts.fetchRelationships(items.map(item => item.id))
for (const rel of relationships)
metadataMap[rel.id].following = rel.following
}
</script> </script>
<template> <template>
<CommonPaginator <CommonPaginator
:paginator="paginator" :paginator="paginator"
border="t border" border="t border"
@items="onNewItems"
> >
<template #default="{ item }"> <template #default="{ item }">
<AccountCard <AccountCard
:account="item" :account="item"
:following="metadataMap[item.id]?.following"
border="b border" py-1 border="b border" py-1
/> />
</template> </template>

View File

@ -1,5 +1,4 @@
<script setup lang="ts"> <script setup lang="ts">
import type { Ref } from 'vue'
import type { Paginator } from 'masto' import type { Paginator } from 'masto'
const { paginator, keyProp = 'id' } = defineProps<{ const { paginator, keyProp = 'id' } = defineProps<{
@ -7,13 +6,7 @@ const { paginator, keyProp = 'id' } = defineProps<{
keyProp?: string keyProp?: string
}>() }>()
const emit = defineEmits(['items']) const { items, state, endAnchor, error } = usePaginator(paginator)
const { items, newItems, state, endAnchor, error } = usePaginator(paginator)
watch(newItems, () => {
emit('items', newItems.value)
})
</script> </script>
<template> <template>

View File

@ -1,5 +1,39 @@
import type { Account } from 'masto' import type { Ref } from 'vue'
import type { Account, MastoClient, Relationship } from 'masto'
export function getDisplayName(account: Account) { export function getDisplayName(account: Account) {
return account.displayName || account.username return account.displayName || account.username
} }
// Batch requests for relationships when used in the UI
// We don't want to hold to old values, so every time a Relationship is needed it
// is requested again from the server to show the latest state
const requestedRelationships = new Map<string, Ref<Relationship | undefined> >()
let timeoutHandle: NodeJS.Timeout | undefined
export function useRelationship(account: Account): Ref<Relationship | undefined> {
let relationship = requestedRelationships.get(account.id)
if (relationship)
return relationship
relationship = ref<Relationship | undefined>()
requestedRelationships.set(account.id, relationship)
if (timeoutHandle)
clearTimeout(timeoutHandle)
timeoutHandle = setTimeout(() => {
timeoutHandle = undefined
fetchRelationships()
}, 100)
return relationship
}
async function fetchRelationships() {
const masto = await useMasto()
const requested = Array.from(requestedRelationships.entries())
requestedRelationships.clear()
const relationships = await masto.accounts.fetchRelationships(requested.map(([id]) => id))
for (let i = 0; i < requested.length; i++)
requested[i][1].value = relationships[i]
}