import React from "react"; import type { TangledRepoRecord } from "../types/tangled"; import { useAtProto } from "../providers/AtProtoProvider"; import { useBacklinks } from "../hooks/useBacklinks"; import { useRepoLanguages } from "../hooks/useRepoLanguages"; export interface TangledRepoRendererProps { record: TangledRepoRecord; error?: Error; loading: boolean; did: string; rkey: string; canonicalUrl?: string; showStarCount?: boolean; branch?: string; languages?: string[]; } export const TangledRepoRenderer: React.FC = ({ record, error, loading, did, rkey, canonicalUrl, showStarCount = true, branch, languages, }) => { const { tangledBaseUrl, constellationBaseUrl } = useAtProto(); // Construct the AT-URI for this repo record const atUri = `at://${did}/sh.tangled.repo/${rkey}`; // Fetch star backlinks const { count: starCount, loading: starsLoading, error: starsError, } = useBacklinks({ subject: atUri, source: "sh.tangled.feed.star:subject", limit: 100, constellationBaseUrl, enabled: showStarCount, }); // Extract knot server from record.knot (e.g., "knot.gaze.systems") const knotUrl = record?.knot ? record.knot.startsWith("http://") || record.knot.startsWith("https://") ? new URL(record.knot).hostname : record.knot : undefined; // Fetch language data from knot server only if languages not provided const { data: languagesData, loading: _languagesLoading, error: _languagesError, } = useRepoLanguages({ knot: knotUrl, did, repoName: record?.name, branch, enabled: !languages && !!knotUrl && !!record?.name, }); // Convert provided language names to the format expected by the renderer const providedLanguagesData = languages ? { languages: languages.map((name) => ({ name, percentage: 0, size: 0, })), ref: branch || "main", totalFiles: 0, totalSize: 0, } : undefined; // Use provided languages or fetched languages const finalLanguagesData = providedLanguagesData ?? languagesData; if (error) return (
Failed to load repository.
); if (loading && !record) return
Loading…
; // Construct the canonical URL: tangled.org/[did]/[repo-name] const viewUrl = canonicalUrl ?? `${tangledBaseUrl}/${did}/${encodeURIComponent(record.name)}`; const tangledIcon = ( ); return (
{/* Header with title and icons */}
{record.name}
{tangledIcon} {record.source && ( )}
{/* Description */} {record.description && (
{record.description}
)} {/* Languages and Stars */}
{/* Languages */} {finalLanguagesData && finalLanguagesData.languages.length > 0 && (() => { const topLanguages = finalLanguagesData.languages .filter((lang) => lang.name && (lang.percentage > 0 || finalLanguagesData.languages.every(l => l.percentage === 0))) .sort((a, b) => b.percentage - a.percentage) .slice(0, 2); return topLanguages.length > 0 ? (
{topLanguages.map((lang) => ( {lang.name} ))}
) : null; })()} {/* Right side: Stars and View on Tangled link */}
{/* Stars */} {showStarCount && (
{starsLoading ? ( ... ) : starsError ? ( — ) : ( {starCount} )}
)} {/* View on Tangled link */} View on Tangled
); }; const base: Record = { container: { fontFamily: "system-ui, sans-serif", borderRadius: 6, overflow: "hidden", transition: "background-color 180ms ease, border-color 180ms ease, color 180ms ease, box-shadow 180ms ease", width: "100%", }, header: { padding: "16px", display: "flex", flexDirection: "column", }, headerTop: { display: "flex", justifyContent: "space-between", alignItems: "flex-start", gap: 12, }, headerRight: { display: "flex", alignItems: "center", gap: 8, }, repoName: { fontFamily: 'SFMono-Regular, ui-monospace, Menlo, Monaco, "Courier New", monospace', fontSize: 18, fontWeight: 600, wordBreak: "break-word", margin: 0, }, iconLink: { display: "flex", alignItems: "center", textDecoration: "none", opacity: 0.7, transition: "opacity 150ms ease", }, description: { padding: "0 16px 16px 16px", fontSize: 14, lineHeight: 1.5, }, languageSection: { padding: "0 16px 16px 16px", display: "flex", justifyContent: "space-between", alignItems: "center", gap: 12, flexWrap: "wrap", }, languageTags: { display: "flex", gap: 8, flexWrap: "wrap", }, languageTag: { fontSize: 12, fontWeight: 500, padding: "4px 10px", background: `var(--atproto-color-bg)`, borderRadius: 12, border: "1px solid var(--atproto-color-border)", }, rightSection: { display: "flex", alignItems: "center", gap: 12, }, starCountContainer: { display: "flex", alignItems: "center", gap: 4, fontSize: 13, }, starCount: { fontSize: 13, fontWeight: 500, }, viewLink: { fontSize: 13, fontWeight: 500, textDecoration: "none", }, }; export default TangledRepoRenderer;