From 33d35dcac82f448d4277a3820364750f45c15ec6 Mon Sep 17 00:00:00 2001 From: DanConwayDev Date: Mon, 8 Apr 2024 09:39:28 +0100 Subject: [PATCH] refactor: fetch user profile only when needed store hexpubkey in Repo and Proposal data object instead of full profile this prevents unnecessary state updates for those objects the trade-off is including business logic (getting profile data) within UI components this is mitigated by allowing hexpubkey or UserObject to be passed to UI components so that UI component tests can be written without having to worry about business logic this new approach can be abstracted to other object types --- .../proposals/ProposalHeader.svelte | 29 +++++++++-- src/lib/components/repo/type.ts | 18 +++++-- src/lib/components/repo/vectors.ts | 48 ++++++++++++------- src/lib/components/users/UserHeader.svelte | 28 ++++++++--- src/lib/components/users/type.ts | 8 ++-- src/lib/components/users/vectors.ts | 22 ++++----- src/lib/stores/Issue.ts | 28 +---------- src/lib/stores/Proposals.ts | 32 ++----------- src/lib/stores/repos.ts | 44 ++--------------- src/lib/stores/users.ts | 13 ++--- 10 files changed, 126 insertions(+), 144 deletions(-) diff --git a/src/lib/components/proposals/ProposalHeader.svelte b/src/lib/components/proposals/ProposalHeader.svelte index e1e115f..8691410 100644 --- a/src/lib/components/proposals/ProposalHeader.svelte +++ b/src/lib/components/proposals/ProposalHeader.svelte @@ -5,10 +5,16 @@ import dayjs from 'dayjs' import relativeTime from 'dayjs/plugin/relativeTime' import { summary_defaults } from './type' - import { getName } from '../users/type' + import { + defaults as user_defaults, + getName, + type UserObject, + } from '../users/type' import Container from '../Container.svelte' import Status from './Status.svelte' - import { logged_in_user } from '$lib/stores/users' + import { ensureUser, logged_in_user } from '$lib/stores/users' + import type { Unsubscriber } from 'svelte/store' + import { onDestroy } from 'svelte' dayjs.extend(relativeTime) export let type: 'proposal' | 'issue' = 'proposal' @@ -27,8 +33,23 @@ let short_title: string let created_at_ago: string let author_name = '' + let author_object: UserObject = { + ...user_defaults, + } + let unsubscriber: Unsubscriber + $: { + if (typeof author === 'string') { + if (unsubscriber) unsubscriber() + unsubscriber = ensureUser(author).subscribe((u) => { + author_object = { ...u } + }) + } else author_object = author + } + onDestroy(() => { + if (unsubscriber) unsubscriber() + }) $: { - author_name = getName(author) + author_name = getName(author_object) } $: { if (title.length > 70) short_title = title.slice(0, 65) + '...' @@ -67,7 +88,7 @@ opened {created_at_ago}
- {#if author.loading} + {#if author_object.loading}
{:else} {author_name} diff --git a/src/lib/components/repo/type.ts b/src/lib/components/repo/type.ts index 78c879e..cd955a6 100644 --- a/src/lib/components/repo/type.ts +++ b/src/lib/components/repo/type.ts @@ -1,6 +1,10 @@ -import { defaults as user_defaults, type User } from '../users/type' +import { + defaults as user_defaults, + type User, + type UserObject, +} from '../users/type' -export interface RepoEvent { +export interface RepoEventBase { event_id: string naddr: string identifier: string @@ -10,12 +14,20 @@ export interface RepoEvent { clone: string[] web: string[] tags: string[] - maintainers: User[] + maintainers: string | User[] relays: string[] referenced_by: string[] created_at: number loading: boolean } +export interface RepoEvent extends RepoEventBase { + maintainers: string[] +} + +export interface RepoEventWithMaintainersMetadata extends RepoEventBase { + maintainers: UserObject[] +} + export const event_defaults: RepoEvent = { event_id: '', naddr: '', diff --git a/src/lib/components/repo/vectors.ts b/src/lib/components/repo/vectors.ts index 9b33d82..d21115c 100644 --- a/src/lib/components/repo/vectors.ts +++ b/src/lib/components/repo/vectors.ts @@ -1,5 +1,5 @@ import { UserVectors, withName } from '../users/vectors' -import type { RepoEvent, RepoSummary } from './type' +import type { RepoEventWithMaintainersMetadata, RepoSummary } from './type' export const RepoSummaryCardArgsVectors = { Short: { @@ -33,7 +33,7 @@ export const RepoSummaryCardArgsVectors = { ], } as RepoSummary, } -const base: RepoEvent = { +const base: RepoEventWithMaintainersMetadata = { identifier: '9ee507fc4357d7ee16a5d8901bedcd103f23c17d', unique_commit: '9ee507fc4357d7ee16a5d8901bedcd103f23c17d', name: 'Short Name', @@ -55,24 +55,31 @@ const base: RepoEvent = { } export const RepoDetailsArgsVectors = { - Short: { ...base } as RepoEvent, + Short: { ...base } as RepoEventWithMaintainersMetadata, Long: { ...base, name: 'Long Name that goes on and on and on and on and on and on and on and on and on', description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis quis nisl eget turpis congue molestie. Nulla vitae purus nec augue accumsan facilisis sed sed ligula. Vestibulum sed risus lacinia risus lacinia molestie. Ut lorem quam, consequat eget tempus in, rhoncus vel nunc. Duis efficitur a leo vel sodales. Nam id fermentum lacus. Etiam nec placerat velit. Praesent ac consectetur est. Aenean iaculis commodo enim.\n Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis quis nisl eget turpis congue molestie.', - } as RepoEvent, + } as RepoEventWithMaintainersMetadata, LongNoSpaces: { ...base, name: 'LongNameLongNameLongNameLongNameLongNameLongNameLongNameLongName', description: 'LoremipsumLoremipsumLoremipsumLoremipsumLoremipsumLoremipsumLoremipsumLoremipsumLoremipsumLoremipsumLoremipsumLoremipsumLoremipsumLoremipsumLoremipsumLoremipsumLoremipsumLoremipsumLoremipsumLoremipsumLoremipsumLoremipsumLoremipsumLoremipsumLoremipsum', - } as RepoEvent, - NoNameOrDescription: { ...base, name: '', description: '' } as RepoEvent, - NoDescription: { ...base, description: '' } as RepoEvent, - NoTags: { ...base, tags: [] } as RepoEvent, - NoGitServer: { ...base, clone: [''] } as RepoEvent, - NoWeb: { ...base, web: [] } as RepoEvent, + } as RepoEventWithMaintainersMetadata, + NoNameOrDescription: { + ...base, + name: '', + description: '', + } as RepoEventWithMaintainersMetadata, + NoDescription: { + ...base, + description: '', + } as RepoEventWithMaintainersMetadata, + NoTags: { ...base, tags: [] } as RepoEventWithMaintainersMetadata, + NoGitServer: { ...base, clone: [''] } as RepoEventWithMaintainersMetadata, + NoWeb: { ...base, web: [] } as RepoEventWithMaintainersMetadata, MaintainersOneProfileNotLoaded: { ...base, maintainers: [ @@ -80,7 +87,7 @@ export const RepoDetailsArgsVectors = { { ...UserVectors.loading }, { ...base.maintainers[2] }, ], - } as RepoEvent, + } as RepoEventWithMaintainersMetadata, MaintainersOneProfileDisplayNameWithoutName: { ...base, maintainers: [ @@ -88,7 +95,7 @@ export const RepoDetailsArgsVectors = { { ...UserVectors.display_name_only }, { ...base.maintainers[2] }, ], - } as RepoEvent, + } as RepoEventWithMaintainersMetadata, MaintainersOneProfileNameAndDisplayNamePresent: { ...base, maintainers: [ @@ -96,7 +103,7 @@ export const RepoDetailsArgsVectors = { { ...UserVectors.display_name_and_name }, { ...base.maintainers[2] }, ], - } as RepoEvent, + } as RepoEventWithMaintainersMetadata, MaintainersOneProfileNoNameOrDisplayNameBeingPresent: { ...base, maintainers: [ @@ -104,8 +111,15 @@ export const RepoDetailsArgsVectors = { { ...UserVectors.no_profile }, { ...base.maintainers[2] }, ], - } as RepoEvent, - NoMaintainers: { ...base, maintainers: [] } as RepoEvent, - NoRelays: { ...base, relays: [] } as RepoEvent, - NoMaintainersOrRelays: { ...base, maintainers: [], relays: [] } as RepoEvent, + } as RepoEventWithMaintainersMetadata, + NoMaintainers: { + ...base, + maintainers: [], + } as RepoEventWithMaintainersMetadata, + NoRelays: { ...base, relays: [] } as RepoEventWithMaintainersMetadata, + NoMaintainersOrRelays: { + ...base, + maintainers: [], + relays: [], + } as RepoEventWithMaintainersMetadata, } diff --git a/src/lib/components/users/UserHeader.svelte b/src/lib/components/users/UserHeader.svelte index 1ec537c..eb8b9e4 100644 --- a/src/lib/components/users/UserHeader.svelte +++ b/src/lib/components/users/UserHeader.svelte @@ -1,10 +1,11 @@
diff --git a/src/lib/components/users/type.ts b/src/lib/components/users/type.ts index 7da72d3..e609b76 100644 --- a/src/lib/components/users/type.ts +++ b/src/lib/components/users/type.ts @@ -1,19 +1,21 @@ import type { NDKUserProfile } from '@nostr-dev-kit/ndk' -export interface User { +export interface UserObject { loading: boolean hexpubkey: string npub: string profile?: NDKUserProfile } -export const defaults: User = { +export const defaults: UserObject = { loading: true, hexpubkey: '', npub: '', } -export function getName(user: User, truncate_above = 25): string { +export type User = UserObject | string + +export function getName(user: UserObject, truncate_above = 25): string { return truncate( user.profile ? user.profile.name diff --git a/src/lib/components/users/vectors.ts b/src/lib/components/users/vectors.ts index cbbb0fe..0b10c78 100644 --- a/src/lib/components/users/vectors.ts +++ b/src/lib/components/users/vectors.ts @@ -1,7 +1,7 @@ -import type { User } from './type' +import type { UserObject } from './type' // nsec1rg53qfv09az39dlw6j64ange3cx8sh5p8np29qcxtythplvplktsv93tnr -const base: User = { +const base: UserObject = { hexpubkey: '3eb45c6f15752d796fa5465d0530a5a5feb79fb6f08c0a4176be9d73cc28c40d', npub: 'npub18669cmc4w5khjma9gews2v995hlt08ak7zxq5stkh6wh8npgcsxslt2xjn', loading: false, @@ -10,30 +10,30 @@ const base: User = { const image = '../test-profile-image.jpg' export const UserVectors = { - loading: { ...base, loading: true } as User, - default: { ...base, profile: { name: 'DanConwayDev', image } } as User, + loading: { ...base, loading: true } as UserObject, + default: { ...base, profile: { name: 'DanConwayDev', image } } as UserObject, display_name_only: { ...base, profile: { displayName: 'DanConwayDev', image }, - } as User, + } as UserObject, display_name_and_name: { ...base, profile: { name: 'Dan', displayName: 'DanConwayDev', image }, - } as User, - no_image: { ...base, profile: { name: 'DanConwayDev' } } as User, - no_profile: { ...base } as User, + } as UserObject, + no_image: { ...base, profile: { name: 'DanConwayDev' } } as UserObject, + no_profile: { ...base } as UserObject, long_name: { ...base, profile: { name: 'Really Really Long Long Name', image }, - } as User, + } as UserObject, } -export function withName(base: User, name: string): User { +export function withName(base: UserObject, name: string): UserObject { return { ...base, profile: { ...base.profile, name, }, - } as User + } as UserObject } diff --git a/src/lib/stores/Issue.ts b/src/lib/stores/Issue.ts index 765b059..7c00325 100644 --- a/src/lib/stores/Issue.ts +++ b/src/lib/stores/Issue.ts @@ -1,8 +1,6 @@ import { NDKRelaySet, type NDKEvent, NDKSubscription } from '@nostr-dev-kit/ndk' -import { writable, type Unsubscriber, type Writable } from 'svelte/store' +import { writable, type Writable } from 'svelte/store' import { base_relays, ndk } from './ndk' -import type { User } from '$lib/components/users/type' -import { ensureUser } from './users' import { type IssueFull, full_defaults } from '$lib/components/issues/type' import { proposal_status_kinds, proposal_status_open } from '$lib/kinds' import { awaitSelectedRepoCollection } from './repo' @@ -20,7 +18,6 @@ export const selected_issue_full: Writable = writable({ // eslint-disable-next-line @typescript-eslint/no-unused-vars let selected_issue_repo_id: string = '' let selected_issue_id: string = '' -let issue_summary_author_unsubsriber: Unsubscriber | undefined export const selected_issue_replies: Writable = writable([]) @@ -59,8 +56,6 @@ export const ensureIssueFull = (repo_identifier: string, issue_id: string) => { }, loading: true, }) - if (issue_summary_author_unsubsriber) issue_summary_author_unsubsriber() - issue_summary_author_unsubsriber = undefined new Promise(async (r) => { const repo_collection = await awaitSelectedRepoCollection(repo_identifier) @@ -98,30 +93,11 @@ export const ensureIssueFull = (repo_identifier: string, issue_id: string) => { descritpion: extractIssueDescription(event.content), created_at: event.created_at, comments: 0, - author: { - hexpubkey: event.pubkey, - loading: true, - npub: '', - }, + author: event.pubkey, loading: false, }, } }) - - issue_summary_author_unsubsriber = ensureUser(event.pubkey).subscribe( - (u: User) => { - selected_issue_full.update((full) => { - return { - ...full, - summary: { - ...full.summary, - author: - event.pubkey == u.hexpubkey ? u : full.summary.author, - }, - } - }) - } - ) } } catch {} }) diff --git a/src/lib/stores/Proposals.ts b/src/lib/stores/Proposals.ts index 358c2c9..86aade7 100644 --- a/src/lib/stores/Proposals.ts +++ b/src/lib/stores/Proposals.ts @@ -4,11 +4,9 @@ import { NDKSubscription, type NDKFilter, } from '@nostr-dev-kit/ndk' -import { writable, type Unsubscriber, type Writable } from 'svelte/store' +import { writable, type Writable } from 'svelte/store' import { base_relays, ndk } from './ndk' import { summary_defaults } from '$lib/components/proposals/type' -import type { User } from '$lib/components/users/type' -import { ensureUser } from './users' import type { ProposalSummaries } from '$lib/components/proposals/type' import { awaitSelectedRepoCollection } from './repo' import { @@ -29,8 +27,6 @@ export const proposal_summaries: Writable = writable({ let selected_repo_id: string | undefined = '' -let authors_unsubscribers: Unsubscriber[] = [] - let sub: NDKSubscription export const ensureProposalSummaries = async (repo_id: string | undefined) => { @@ -43,8 +39,6 @@ export const ensureProposalSummaries = async (repo_id: string | undefined) => { if (sub) sub.stop() if (sub_statuses) sub_statuses.stop() - authors_unsubscribers.forEach((u) => u()) - authors_unsubscribers = [] selected_repo_id = repo_id @@ -83,7 +77,7 @@ export const ensureProposalSummaries = async (repo_id: string | undefined) => { filter = { kinds: [patch_kind], '#a': repo.maintainers.map( - (m) => `${repo_kind}:${m.hexpubkey}:${repo.identifier}` + (m) => `${repo_kind}:${m}:${repo.identifier}` ), limit: 100, } @@ -91,7 +85,7 @@ export const ensureProposalSummaries = async (repo_id: string | undefined) => { filter = { kinds: [patch_kind], '#a': repo.maintainers.map( - (m) => `${repo_kind}:${m.hexpubkey}:${repo.identifier}` + (m) => `${repo_kind}:${m}:${repo.identifier}` ), '#t': ['root'], limit: 100, @@ -139,11 +133,7 @@ export const ensureProposalSummaries = async (repo_id: string | undefined) => { descritpion: event.tagValue('description') || '', created_at: event.created_at, comments: 0, - author: { - hexpubkey: event.pubkey, - loading: true, - npub: '', - }, + author: event.pubkey, loading: false, }, ], @@ -174,20 +164,6 @@ export const ensureProposalSummaries = async (repo_id: string | undefined) => { } } } - - authors_unsubscribers.push( - ensureUser(event.pubkey).subscribe((u: User) => { - proposal_summaries.update((proposals) => { - return { - ...proposals, - summaries: proposals.summaries.map((o) => ({ - ...o, - author: event.pubkey === o.author.hexpubkey ? u : o.author, - })), - } - }) - }) - ) } catch {} }) sub.on('eose', () => { diff --git a/src/lib/stores/repos.ts b/src/lib/stores/repos.ts index f78dd44..8989fa9 100644 --- a/src/lib/stores/repos.ts +++ b/src/lib/stores/repos.ts @@ -8,8 +8,6 @@ import { NDKRelaySet, type NDKFilter, NDKEvent } from '@nostr-dev-kit/ndk' import { get, writable, type Writable } from 'svelte/store' import { base_relays, ndk } from './ndk' import { repo_kind } from '$lib/kinds' -import type { User } from '$lib/components/users/type' -import { ensureUser } from './users' import { selectRepoFromCollection } from '$lib/components/repo/utils' import { selected_repo_collection } from './repo' @@ -99,7 +97,7 @@ export const ensureRepoCollection = ( const ref_sub = ndk.subscribe( { '#a': [ - `${repo_kind}:${repo_event.maintainers[0].hexpubkey}:${repo_event.identifier}`, + `${repo_kind}:${repo_event.maintainers[0]}:${repo_event.identifier}`, ], limit: 10, }, @@ -166,35 +164,11 @@ export const ensureRepoCollection = ( } }) }) - - // load maintainers - we will subscribe later to prevent too many updates - repo_event.maintainers.forEach((m) => ensureUser(m.hexpubkey)) } }) sub.on('eose', () => { // still awaiting reference_by at this point repos[unique_commit_or_identifier].update((repo_collection) => { - // subscribe to maintainers - const hexpubkeys = repo_collection.events.flatMap((repo_event) => - repo_event.maintainers.map((m) => m.hexpubkey) - ) - hexpubkeys.forEach((hexpubkey) => { - ensureUser(hexpubkey).subscribe((u) => { - repos[unique_commit_or_identifier].update((repo_collection) => ({ - ...repo_collection, - events: [ - ...repo_collection.events.map((repo_event) => ({ - ...repo_event, - maintainers: [ - ...repo_event.maintainers.map((m) => ({ - ...(m.hexpubkey === u.hexpubkey ? u : m), - })), - ], - })), - ], - })) - }) - }) return { ...repo_collection, loading: false, @@ -217,21 +191,11 @@ export const ensureRepoCollection = ( export const eventToRepoEvent = (event: NDKEvent): RepoEvent | undefined => { if (event.kind !== repo_kind) return undefined - const maintainers = [ - { - hexpubkey: event.pubkey, - loading: true, - npub: '', - } as User, - ] + const maintainers = [event.pubkey] event.getMatchingTags('maintainers').forEach((t: string[]) => { t.forEach((v, i) => { - if (i > 0 && v !== maintainers[0].hexpubkey) { - maintainers.push({ - hexpubkey: v, - loading: true, - npub: '', - } as User) + if (i > 0 && v !== maintainers[0]) { + maintainers.push(v) } }) }) diff --git a/src/lib/stores/users.ts b/src/lib/stores/users.ts index 7388ffe..e64e8a4 100644 --- a/src/lib/stores/users.ts +++ b/src/lib/stores/users.ts @@ -1,18 +1,18 @@ import { defaults as user_defaults, - type User, + type UserObject, } from '$lib/components/users/type' import { NDKNip07Signer, NDKRelayList } from '@nostr-dev-kit/ndk' import { get, writable, type Unsubscriber, type Writable } from 'svelte/store' import { ndk } from './ndk' -export const users: { [hexpubkey: string]: Writable } = {} +export const users: { [hexpubkey: string]: Writable } = {} -export const ensureUser = (hexpubkey: string): Writable => { +export const ensureUser = (hexpubkey: string): Writable => { if (!users[hexpubkey]) { const u = ndk.getUser({ hexpubkey }) - const base: User = { + const base: UserObject = { loading: false, hexpubkey, npub: u.npub, @@ -44,7 +44,7 @@ export const ensureUser = (hexpubkey: string): Writable => { return users[hexpubkey] } -export const returnUser = async (hexpubkey: string): Promise => { +export const returnUser = async (hexpubkey: string): Promise => { return new Promise((r) => { const unsubscriber = ensureUser(hexpubkey).subscribe((u) => { if (!u.loading) { @@ -81,7 +81,8 @@ export const checkForNip07Plugin = () => { const signer = new NDKNip07Signer(2000) -export const logged_in_user: Writable = writable(undefined) +export const logged_in_user: Writable = + writable(undefined) export const login = async (): Promise => { return new Promise(async (res, rej) => {