diff --git a/src/components/repo/RepoIssues.tsx b/src/components/repo/RepoIssues.tsx index 6546eda..61a8ba9 100644 --- a/src/components/repo/RepoIssues.tsx +++ b/src/components/repo/RepoIssues.tsx @@ -1,13 +1,12 @@ import { parseResourceUri, type Did, type Handle } from '@atcute/lexicons' +import { isDid } from '@atcute/lexicons/syntax' import { IconCircleDot, IconMessageCircle } from '@tabler/icons-react' import { Link } from 'react-router-dom' -import { useRecordAuthors } from '../../hooks/useRecordAuthors' +import { useVisibleActor } from '../../hooks/useVisibleActor' import { useRepoIssues } from '../../hooks/useRepoIssues' import { getRecordRkey, type Issue } from '../../lib/tangled/repo' import { SurfaceCard } from '../shared/SurfaceCard' -const EMPTY_ISSUES: readonly Issue[] = [] - type RepoIssuesProps = { repoOwnerHandle: Handle repoDid: Did @@ -16,7 +15,6 @@ type RepoIssuesProps = { export function RepoIssues({ repoOwnerHandle, repoDid, repoKey }: RepoIssuesProps) { const { issues, error } = useRepoIssues(repoDid) - const { authors, error: authorsError } = useRecordAuthors(issues?.items ?? EMPTY_ISSUES) if (error) { return

Could not load issues: {error.message}

@@ -30,40 +28,43 @@ export function RepoIssues({ repoOwnerHandle, repoDid, repoKey }: RepoIssuesProp return

No issues found.

} - const authorResolutionMessage = - authorsError === null ? null : 'Some issues may not be available until their authors resolve.' - return ( - <> - {authorResolutionMessage &&

{authorResolutionMessage}

} - - +type IssueListItemProps = { issue: Issue; repoKey: string; repoOwnerHandle: Handle } + +function IssueListItem({ issue, repoKey, repoOwnerHandle }: IssueListItemProps) { + const authorIdentifier = parseResourceUri(issue.uri).repo + const { actor, elementRef } = useVisibleActor(isDid(authorIdentifier) ? authorIdentifier : null) + const issueUrl = actor + ? `/${repoOwnerHandle}/${repoKey}/issues/${actor.miniDoc.handle}/${getRecordRkey(issue.uri)}` + : undefined + + return ( +
  • + {issueUrl === undefined ? ( + + ) : ( + + + + )} +
  • ) } diff --git a/src/components/repo/RepoPulls.tsx b/src/components/repo/RepoPulls.tsx index f84cadc..202e855 100644 --- a/src/components/repo/RepoPulls.tsx +++ b/src/components/repo/RepoPulls.tsx @@ -1,13 +1,12 @@ import { parseResourceUri, type Did, type Handle } from '@atcute/lexicons' +import { isDid } from '@atcute/lexicons/syntax' import { IconGitPullRequest, IconMessageCircle } from '@tabler/icons-react' import { Link } from 'react-router-dom' -import { useRecordAuthors } from '../../hooks/useRecordAuthors' +import { useVisibleActor } from '../../hooks/useVisibleActor' import { useRepoPulls } from '../../hooks/useRepoPulls' import { getRecordRkey, type Pull } from '../../lib/tangled/repo' import { SurfaceCard } from '../shared/SurfaceCard' -const EMPTY_PULLS: readonly Pull[] = [] - type RepoPullsProps = { repoOwnerHandle: Handle repoDid: Did @@ -16,7 +15,6 @@ type RepoPullsProps = { export function RepoPulls({ repoOwnerHandle, repoDid, repoKey }: RepoPullsProps) { const { pulls, error } = useRepoPulls(repoDid) - const { authors, error: authorsError } = useRecordAuthors(pulls?.items ?? EMPTY_PULLS) if (error) { return

    Could not load pulls: {error.message}

    @@ -30,47 +28,49 @@ export function RepoPulls({ repoOwnerHandle, repoDid, repoKey }: RepoPullsProps) return

    No pulls found.

    } - const authorResolutionMessage = - authorsError === null - ? null - : 'Some pull requests may not be available until their authors resolve.' - return ( - <> - {authorResolutionMessage &&

    {authorResolutionMessage}

    } - - +type PullListItemProps = { pull: Pull; repoKey: string; repoOwnerHandle: Handle } + +function PullListItem({ pull, repoKey, repoOwnerHandle }: PullListItemProps) { + const authorIdentifier = parseResourceUri(pull.uri).repo + const { actor, elementRef } = useVisibleActor(isDid(authorIdentifier) ? authorIdentifier : null) + const pullUrl = actor + ? `/${repoOwnerHandle}/${repoKey}/pulls/${actor.miniDoc.handle}/${getRecordRkey(pull.uri)}` + : undefined + const stateClassName = + pull.state === 'open' + ? 'text-ctp-teal' + : pull.state === 'merged' + ? 'text-ctp-mauve' + : 'text-ctp-overlay-1' + + return ( +
  • + {pullUrl === undefined ? ( + + ) : ( + + + + )} +
  • ) } diff --git a/src/components/vouch/VouchList.tsx b/src/components/vouch/VouchList.tsx index 4783239..33854d6 100644 --- a/src/components/vouch/VouchList.tsx +++ b/src/components/vouch/VouchList.tsx @@ -1,5 +1,4 @@ import type { VouchRecord } from '../../lib/tangled/graph' -import { useResolvedVouches } from '../../hooks/useResolvedVouches' import { VouchListItem } from './VouchListItem' type VouchListProps = { @@ -7,20 +6,10 @@ type VouchListProps = { } export function VouchList({ vouches }: VouchListProps) { - const { resolvedVouches, isLoading } = useResolvedVouches(vouches) - - if (isLoading) { - return

    Loading vouches...

    - } - - if (resolvedVouches.length === 0) { - return

    Vouches could not be resolved.

    - } - return (
    - {resolvedVouches.map(({ vouch, author }) => ( - + {vouches.map((vouch) => ( + ))}
    ) diff --git a/src/components/vouch/VouchListItem.tsx b/src/components/vouch/VouchListItem.tsx index 4d845d8..ebb81f0 100644 --- a/src/components/vouch/VouchListItem.tsx +++ b/src/components/vouch/VouchListItem.tsx @@ -1,17 +1,20 @@ import { IconThumbDown, IconThumbUp } from '@tabler/icons-react' +import { parseResourceUri } from '@atcute/lexicons' +import { isDid } from '@atcute/lexicons/syntax' import { Link } from 'react-router-dom' -import type { ResolvedActor } from '../../lib/actor' +import { useVisibleActor } from '../../hooks/useVisibleActor' import { ProfileAvatar } from '../profile/ProfileAvatar' import { SurfaceCard } from '../shared/SurfaceCard' import type { VouchRecord } from '../../lib/tangled/graph' type VouchListItemProps = { vouchRecord: VouchRecord - author: ResolvedActor } -export function VouchListItem({ vouchRecord, author }: VouchListItemProps) { +export function VouchListItem({ vouchRecord }: VouchListItemProps) { const { value } = vouchRecord + const authorIdentifier = parseResourceUri(vouchRecord.uri).repo + const { actor, elementRef } = useVisibleActor(isDid(authorIdentifier) ? authorIdentifier : null) const isDenouncement = value.kind === 'denounce' const actionLabel = isDenouncement ? 'Denounces' : 'Vouches' const actionColor = isDenouncement ? 'text-ctp-red' : 'text-ctp-green' @@ -19,43 +22,55 @@ export function VouchListItem({ vouchRecord, author }: VouchListItemProps) { const ActionIcon = isDenouncement ? IconThumbDown : IconThumbUp return ( - -
    - - +
    + +
    + {actor ? ( + + - - {author.miniDoc.handle} - - + + {actor.miniDoc.handle} + + + ) : ( + + + {authorIdentifier} + + )} - - + + - -
    + +
    - {value.reason && ( -

    - {value.reason} -

    - )} -
    + {value.reason && ( +

    + {value.reason} +

    + )} + + ) } diff --git a/src/hooks/useRecordAuthors.ts b/src/hooks/useRecordAuthors.ts deleted file mode 100644 index 7732c94..0000000 --- a/src/hooks/useRecordAuthors.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { parseResourceUri, type Handle, type ResourceUri } from '@atcute/lexicons' -import { useEffect, useMemo, useState } from 'react' -import { resolveMiniDoc } from '../lib/actor' - -type RecordWithUri = { - uri: ResourceUri -} - -type AuthorsState = { - authors: Map - error: Error | null - key: string -} - -export function useRecordAuthors(records: readonly RecordWithUri[]) { - const authorDids = useMemo( - () => [...new Set(records.map(({ uri }) => parseResourceUri(uri).repo))], - [records], - ) - const authorsKey = [...authorDids].sort().join(',') - const [state, setState] = useState(null) - - useEffect(() => { - let isCancelled = false - - void Promise.allSettled( - authorDids.map(async (did) => [did, (await resolveMiniDoc(did)).handle] as const), - ).then((results) => { - if (isCancelled) return - - const authors = new Map() - const failedResolutions = results.filter((result) => result.status === 'rejected') - for (const result of results) { - if (result.status === 'fulfilled') authors.set(...result.value) - } - - setState({ - authors, - error: - failedResolutions.length > 0 - ? new Error('Some record authors could not be resolved') - : null, - key: authorsKey, - }) - }) - - return () => { - isCancelled = true - } - }, [authorDids, authorsKey]) - - const isCurrentList = state?.key === authorsKey - return { - authors: isCurrentList ? (state.authors ?? null) : null, - error: isCurrentList ? (state.error ?? null) : null, - } -} diff --git a/src/hooks/useResolvedVouches.ts b/src/hooks/useResolvedVouches.ts deleted file mode 100644 index c0a1e87..0000000 --- a/src/hooks/useResolvedVouches.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { parseResourceUri, type Did } from '@atcute/lexicons' -import { isDid } from '@atcute/lexicons/syntax' -import { useEffect, useState } from 'react' -import { resolveActor, type ResolvedActor } from '../lib/actor' -import type { VouchRecord } from '../lib/tangled/graph' - -export type ResolvedVouch = { author: ResolvedActor; vouch: VouchRecord } - -export function useResolvedVouches(vouches: VouchRecord[]) { - const [resolvedVouches, setResolvedVouches] = useState([]) - const [isLoading, setIsLoading] = useState(true) - - useEffect(() => { - let isCancelled = false - - async function loadVouches() { - setIsLoading(true) - const authorDids = [ - ...new Set(vouches.map(getVouchAuthorDid).filter((did): did is Did => did !== null)), - ] - const results = await Promise.allSettled( - authorDids.map(async (did): Promise<[Did, ResolvedActor]> => [ - did, - await resolveActor(did), - ]), - ) - - if (isCancelled) return - - const authors = new Map( - results.flatMap((result) => (result.status === 'fulfilled' ? [result.value] : [])), - ) - setResolvedVouches( - vouches.flatMap((vouch) => { - const authorDid = getVouchAuthorDid(vouch) - const author = authorDid === null ? undefined : authors.get(authorDid) - return author === undefined ? [] : [{ vouch, author }] - }), - ) - setIsLoading(false) - } - - void loadVouches() - return () => { - isCancelled = true - } - }, [vouches]) - - return { resolvedVouches, isLoading } -} - -function getVouchAuthorDid(vouch: VouchRecord): Did | null { - const { repo } = parseResourceUri(vouch.uri) - return isDid(repo) ? repo : null -}