feat(members): throttle merged profile derivation in SpaceMembers to reduce reactive invalidation

This commit is contained in:
2026-04-29 07:11:39 +05:30
parent dee017b12b
commit 0c8a2dc140
5 changed files with 85 additions and 11 deletions
+18 -3
View File
@@ -3,6 +3,7 @@
import {removeUndefined} from "@welshman/lib"
import {displayPubkey} from "@welshman/util"
import {deriveHandleForPubkey, displayHandle, deriveProfileDisplay} from "@welshman/app"
import {readable, type Readable} from "svelte/store"
import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte"
import ProfileCircle from "@app/components/ProfileCircle.svelte"
@@ -18,13 +19,27 @@
showPubkey?: boolean
avatarSize?: number
inert?: boolean
profileDisplayValue?: string
handleValue?: unknown
}
const {pubkey, url, showPubkey, inert, avatarSize = 10}: Props = $props()
const {
pubkey,
url,
showPubkey,
inert,
avatarSize = 10,
profileDisplayValue,
handleValue,
}: Props = $props()
const relays = removeUndefined([url])
const profileDisplay = deriveProfileDisplay(pubkey, relays)
const handle = deriveHandleForPubkey(pubkey)
const profileDisplay: Readable<string> =
profileDisplayValue !== undefined
? readable(profileDisplayValue)
: deriveProfileDisplay(pubkey, relays)
const handle: Readable<any> =
handleValue !== undefined ? readable(handleValue) : deriveHandleForPubkey(pubkey)
const openProfile = () => {
pushModal(ProfileDetail, {pubkey, url})
+56 -2
View File
@@ -1,6 +1,13 @@
<script lang="ts">
import {ManagementMethod} from "@welshman/util"
import {manageRelay, displayProfileByPubkey} from "@welshman/app"
import {
manageRelay,
displayProfileByPubkey,
deriveProfileDisplay,
deriveHandleForPubkey,
} from "@welshman/app"
import {merged, throttled} from "@welshman/store"
import {removeUndefined} from "@welshman/lib"
import MenuDots from "@assets/icons/menu-dots.svg?dataurl"
import UserMinus from "@assets/icons/user-minus.svg?dataurl"
import MinusCircle from "@assets/icons/minus-circle.svg?dataurl"
@@ -96,6 +103,49 @@
})
let menuPubkey = $state<string | undefined>()
let profileDisplayMap = $state<Map<string, string>>(new Map())
let handleMap = $state<Map<string, unknown>>(new Map())
let unsubscribeDisplay = $state<() => void | undefined>()
let unsubscribeHandle = $state<() => void | undefined>()
$effect(() => {
unsubscribeDisplay?.()
unsubscribeHandle?.()
unsubscribeDisplay = undefined
unsubscribeHandle = undefined
if ($members && $members.length > 0) {
const relays = removeUndefined([url])
const displayStores = $members.map(pubkey => deriveProfileDisplay(pubkey, relays))
const handleStores = $members.map(pubkey => deriveHandleForPubkey(pubkey))
const mergedDisplay = throttled(300, merged(displayStores))
const mergedHandle = throttled(300, merged(handleStores))
unsubscribeDisplay = mergedDisplay.subscribe(values => {
const m = new Map<string, string>()
for (let i = 0; i < $members.length; i++) m.set($members[i], values[i])
profileDisplayMap = m
})
unsubscribeHandle = mergedHandle.subscribe(values => {
const m = new Map<string, unknown>()
for (let i = 0; i < $members.length; i++) m.set($members[i], values[i])
handleMap = m
})
} else {
profileDisplayMap = new Map()
handleMap = new Map()
}
return () => {
unsubscribeDisplay?.()
unsubscribeHandle?.()
unsubscribeDisplay = undefined
unsubscribeHandle = undefined
}
})
</script>
<Modal>
@@ -126,7 +176,11 @@
<div class="card2 card2-sm bg-alt relative">
<div class="flex items-center justify-between gap-2">
<div class="min-w-0 flex-1">
<Profile {pubkey} {url} />
<Profile
{pubkey}
{url}
profileDisplayValue={profileDisplayMap.get(pubkey)}
handleValue={handleMap.get(pubkey)} />
</div>
{#if canBan || canUnallow}
<div class="relative">
+5 -3
View File
@@ -1,6 +1,6 @@
<script lang="ts">
import {derived} from "svelte/store"
import {displayRelayUrl, EVENT_TIME, ZAP_GOAL, THREAD, CLASSIFIED, POLL} from "@welshman/util"
import {EVENT_TIME, ZAP_GOAL, THREAD, CLASSIFIED, POLL} from "@welshman/util"
import {deriveRelay, deriveRelayDisplay, createSearch, pubkey} from "@welshman/app"
import {fly} from "@lib/transition"
import Magnifier from "@assets/icons/magnifier.svg?dataurl"
@@ -154,7 +154,10 @@
</div>
{#if $notificationSettings.push && !$shouldNotify}
<Icon icon={BellOff} size={3} class="opacity-50" />
View Members ({$members.length})
{/if}
</strong>
<Icon icon={AltArrowDown} />
</div>
</Button>
{#if showMenu}
<Popover hideOnClick onClose={toggleMenu}>
@@ -178,7 +181,6 @@
<Icon icon={UserRounded} />
{#if $members === undefined}
View Members
View Members
{:else}
View Members ({$members.length})
{/if}
+5 -2
View File
@@ -972,6 +972,9 @@ export enum MembershipStatus {
Granted,
}
const eventTargetsUser = (event: TrustedEvent, pubkey: string) =>
event.pubkey === pubkey || getPubkeyTagValues(event.tags).includes(pubkey)
export const deriveUserIsSpaceAdmin = memoize((url?: string) => {
const store = writable(false)
@@ -1014,7 +1017,7 @@ export const deriveUserSpaceMembershipStatus = (url: string) => {
} else {
// No member list available - replay the user's add/remove history.
for (const event of sortBy(e => e.created_at, $userAddRemoveEvents)) {
if (event.pubkey !== $pubkey) {
if (!eventTargetsUser(event, $pubkey!)) {
continue
}
@@ -1079,7 +1082,7 @@ export const deriveUserRoomMembershipStatus = (url: string, h: string) => {
} else {
// No member list available - replay the user's add/remove history.
for (const event of sortEventsAsc($userAddRemoveEvents)) {
if (event.pubkey !== $pubkey) {
if (!eventTargetsUser(event, $pubkey!)) {
continue
}
+1 -1
View File
@@ -1,6 +1,6 @@
<script lang="ts">
import Server from "@assets/icons/server.svg?dataurl"
import CloudCheck from "@assets/icons/cloud-check.svg?dataurl"
import CheckCircle from "@assets/icons/check-circle.svg?dataurl"
import ArrowRight from "@assets/icons/arrow-right.svg?dataurl"
import Link from "@lib/components/Link.svelte"