diff --git a/src/components/repo.tsx b/src/components/repo.tsx index 6b33d9b..b974d38 100644 --- a/src/components/repo.tsx +++ b/src/components/repo.tsx @@ -29,6 +29,11 @@ import { marked } from 'marked'; import { A } from '@solidjs/router'; import { For, Show, createMemo, createSignal, onCleanup, onMount, type Component, type JSX } from 'solid-js'; import type { RepoContext, TreeEntry } from '../lib/api/repos'; +import { createQuery } from '@tanstack/solid-query'; +import { getRepo, getRepoLanguages } from '../lib/api/repos'; +import { getRepoStarCount } from '../lib/api/stars'; +import { getRepoIssueCount } from '../lib/api/issues'; +import { getRepoPullCount } from '../lib/api/pulls'; import { formatRelativeTime, languageColor } from '../lib/repo-utils'; import { getTangledAppviewService } from '../lib/settings'; import { Avatar, SkeletonBlock, buttonStyles, cardStyles } from './common'; @@ -1620,29 +1625,90 @@ export interface RepoCardProps { compact?: boolean; href?: string; noBorder?: boolean; + showOwner?: boolean; } export const RepoCard: Component = (props) => { const linkHref = () => props.href || `/${props.owner}/${props.name}`; - const langColor = () => props.language ? languageColor(props.language) : undefined; + + // Define queries for statistics + const repoContextQuery = createQuery(() => ({ + queryKey: ['repo-context', props.owner, props.name], + queryFn: () => getRepo(props.owner, props.name), + staleTime: 300_000, + })); + + const repoContext = () => repoContextQuery.data; + + const starCountQuery = createQuery(() => ({ + queryKey: ['repo-stars-count', repoContext()?.repoDid], + queryFn: () => getRepoStarCount(repoContext()!), + enabled: !!repoContext(), + staleTime: 300_000, + })); + + const issueCountQuery = createQuery(() => ({ + queryKey: ['repo-issues-count', repoContext()?.repoDid], + queryFn: () => getRepoIssueCount(repoContext()!), + enabled: !!repoContext(), + staleTime: 300_000, + })); + + const pullCountQuery = createQuery(() => ({ + queryKey: ['repo-pulls-count', repoContext()?.repoDid], + queryFn: () => getRepoPullCount(repoContext()!), + enabled: !!repoContext(), + staleTime: 300_000, + })); + + const languagesQuery = createQuery(() => ({ + queryKey: ['repo-languages', repoContext()?.repoDid], + queryFn: () => getRepoLanguages(repoContext()!, 'HEAD'), + enabled: !!repoContext(), + staleTime: 300_000, + })); + + const resolvedLanguage = () => { + if (props.language !== undefined) return props.language; + const list = languagesQuery.data?.languages; + if (!list || list.length === 0) return undefined; + const sorted = [...list].sort((a, b) => b.percentage - a.percentage); + return sorted[0].name; + }; + + const langColor = () => { + const lang = resolvedLanguage(); + return lang ? languageColor(lang) : undefined; + }; + + const stars = () => props.stars !== undefined ? props.stars : starCountQuery.data; + const openIssues = () => props.openIssues !== undefined ? props.openIssues : issueCountQuery.data; + const openPRs = () => props.openPRs !== undefined ? props.openPRs : pullCountQuery.data; + const forks = () => props.forks; + + const isMetricsLoading = () => { + const langLoading = props.language === undefined && (languagesQuery.isLoading || repoContextQuery.isLoading); + const starsLoading = props.stars === undefined && (starCountQuery.isLoading || repoContextQuery.isLoading); + const issuesLoading = props.openIssues === undefined && (issueCountQuery.isLoading || repoContextQuery.isLoading); + const prsLoading = props.openPRs === undefined && (pullCountQuery.isLoading || repoContextQuery.isLoading); + return langLoading || starsLoading || issuesLoading || prsLoading; + }; return (
- -
+
{props.description}
- -
- -
+ + + + + +
+ } + > +
+ +
- {props.language} + {resolvedLanguage()}
- 0}> -
+ 0}> +
- {props.stars} + {stars()}
- 0}> -
+ 0}> +
- {props.forks} + {forks()}
- 0}> -
+ 0}> +
- {props.openIssues} + {openIssues()}
- 0}> -
+ 0}> +
- {props.openPRs} + {openPRs()}
diff --git a/src/index.css b/src/index.css index 0b1fed2..6baad17 100644 --- a/src/index.css +++ b/src/index.css @@ -3470,3 +3470,9 @@ details[open] > .untangled-language-bar { color: rgb(244 114 182); } } + +.untangled-vouch-label { + display: grid; + grid-template-columns: auto 1fr auto; +} + diff --git a/src/layout.tsx b/src/layout.tsx index c1f61ec..8ea7aa2 100644 --- a/src/layout.tsx +++ b/src/layout.tsx @@ -1,5 +1,5 @@ import clsx from 'clsx'; -import { Bell, LogOut, RotateCcw, Save, Search, Settings } from 'lucide-solid'; +import { Bell, LogOut, RotateCcw, Save, Search, Settings, UserRound } from 'lucide-solid'; import { A, useLocation, useNavigate } from '@solidjs/router'; import { createQuery, useQueryClient } from '@tanstack/solid-query'; import { For, Show, createEffect, createSignal, onCleanup, onMount, type Component, type JSX } from 'solid-js'; @@ -229,6 +229,21 @@ const UserProfileMenu: Component = () => {
{viewerQuery.data?.handle ?? auth.currentDid()}
{auth.currentDid()}
+ + {(handle) => ( + { + const details = e.currentTarget.closest('details'); + if (details) details.open = false; + }} + > + + profile + + )} + + } + > + +
+ + + + +
+ + +
+ + +
+
{ + e.preventDefault(); + await props.onVouchSubmit(kind(), kind() === 'none' ? undefined : reason()); + popoverRef?.hidePopover(); + }} + class="flex flex-col gap-6 group" + > +
+

Vouch for {userIdent()}

+

+ + Vouching builds a web-of-trust across Tangled. Vouch for users you + have had positive interactions with.{' '} + + Read more + + + } + > + {(record) => ( + <> + You {record().value.kind === 'vouch' ? 'vouched' : 'denounced'} {userIdent()}{' '} + {formatRelativeTime(record().value.createdAt)}. + You can change your decision below. + + )} + +

+
+ +
+ setKind('vouch')} + class="peer/vouch hidden appearance-none" + /> + setKind('denounce')} + class="peer/denounce hidden appearance-none" + /> + setKind('none')} + class="peer/none hidden appearance-none" + /> + + + + + +