feat: global relationships batching (#24)

This commit is contained in:
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">
import type { Account } from 'masto'
const { account, following } = defineProps<{
const { account } = defineProps<{
account: Account
following?: boolean
}>()
const masto = await useMasto()
let isFollowing = $ref<boolean | undefined>(following)
watch($$(following), () => {
isFollowing = following
})
const relationship = $(useRelationship(account))
function unfollow() {
masto.accounts.unfollow(account.id)
isFollowing = false
relationship!.following = false
}
function follow() {
masto.accounts.follow(account.id)
isFollowing = true
relationship!.following = true
}
</script>
@ -27,8 +23,8 @@ function follow() {
<div flex justify-between>
<AccountInfo :account="account" p3 />
<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-else-if="isFollowing === false" color-gray hover:color-purple hover:cursor-pointer i-ri:user-follow-fill @click="follow" />
<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="relationship?.following === false" color-gray hover:color-purple hover:cursor-pointer i-ri:user-follow-fill @click="follow" />
</div>
</div>
</template>

View file

@ -5,22 +5,14 @@ const { account } = defineProps<{
account: Account
}>()
let isFollowing = $ref<boolean | undefined>()
let isFollowedBy = $ref<boolean | undefined>()
const relationship = $(useRelationship(account))
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() {
isFollowing = !isFollowing
relationship!.following = !relationship!.following
masto ??= await useMasto()
await masto.accounts[isFollowing ? 'follow' : 'unfollow'](account.id)
await masto.accounts[relationship!.following ? 'follow' : 'unfollow'](account.id)
}
const createdAt = $computed(() => {
@ -50,9 +42,9 @@ const createdAt = $computed(() => {
</NuxtLink>
</div>
<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">
{{ isFollowing ? 'Unfollow' : isFollowedBy ? 'Follow back' : 'Follow' }}
{{ relationship?.following ? 'Unfollow' : relationship?.followedBy ? 'Follow back' : 'Follow' }}
</div>
</button>
<!-- <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<{
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>
<template>
<CommonPaginator
:paginator="paginator"
border="t border"
@items="onNewItems"
>
<template #default="{ item }">
<AccountCard
:account="item"
:following="metadataMap[item.id]?.following"
border="b border" py-1
/>
</template>

View file

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