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}