A React component library for rendering common AT Protocol records for applications such as Bluesky and Leaflet.
1import { useState, useEffect } from "react"; 2import type { RepoLanguagesResponse } from "../types/tangled"; 3 4export interface UseRepoLanguagesOptions { 5 /** The knot server URL (e.g., "knot.gaze.systems") */ 6 knot?: string; 7 /** DID of the repository owner */ 8 did?: string; 9 /** Repository name */ 10 repoName?: string; 11 /** Branch to query (defaults to trying "main", then "master") */ 12 branch?: string; 13 /** Whether to enable the query */ 14 enabled?: boolean; 15} 16 17export interface UseRepoLanguagesResult { 18 /** Language data from the knot server */ 19 data?: RepoLanguagesResponse; 20 /** Loading state */ 21 loading: boolean; 22 /** Error state */ 23 error?: Error; 24} 25 26/** 27 * Hook to fetch repository language information from a Tangled knot server. 28 * If no branch supplied, tries "main" first, then falls back to "master". 29 */ 30export function useRepoLanguages({ 31 knot, 32 did, 33 repoName, 34 branch, 35 enabled = true, 36}: UseRepoLanguagesOptions): UseRepoLanguagesResult { 37 const [data, setData] = useState<RepoLanguagesResponse | undefined>(); 38 const [loading, setLoading] = useState(false); 39 const [error, setError] = useState<Error | undefined>(); 40 41 useEffect(() => { 42 if (!enabled || !knot || !did || !repoName) { 43 return; 44 } 45 46 let cancelled = false; 47 48 const fetchLanguages = async (ref: string): Promise<boolean> => { 49 try { 50 const url = `https://${knot}/xrpc/sh.tangled.repo.languages?repo=${encodeURIComponent(`${did}/${repoName}`)}&ref=${encodeURIComponent(ref)}`; 51 const response = await fetch(url); 52 53 if (!response.ok) { 54 return false; 55 } 56 57 const result = await response.json(); 58 if (!cancelled) { 59 setData(result); 60 setError(undefined); 61 } 62 return true; 63 } catch (err) { 64 return false; 65 } 66 }; 67 68 const fetchWithFallback = async () => { 69 setLoading(true); 70 setError(undefined); 71 72 if (branch) { 73 const success = await fetchLanguages(branch); 74 if (!cancelled) { 75 if (!success) { 76 setError(new Error(`Failed to fetch languages for branch: ${branch}`)); 77 } 78 setLoading(false); 79 } 80 } else { 81 // Try "main" first, then "master" 82 let success = await fetchLanguages("main"); 83 if (!success && !cancelled) { 84 success = await fetchLanguages("master"); 85 } 86 87 if (!cancelled) { 88 if (!success) { 89 setError(new Error("Failed to fetch languages for main or master branch")); 90 } 91 setLoading(false); 92 } 93 } 94 }; 95 96 fetchWithFallback(); 97 98 return () => { 99 cancelled = true; 100 }; 101 }, [knot, did, repoName, branch, enabled]); 102 103 return { data, loading, error }; 104}