import { useState, useEffect } from "react"; import { graphql, readInlineData } from "react-relay"; import { Link } from "react-router-dom"; import { AlertCircle } from "lucide-react"; import { validate, type LexiconDoc } from "@slices/lexicon"; import type { LexiconTree_lexicon$key } from "../__generated__/LexiconTree_lexicon.graphql.ts"; export const LexiconFragment = graphql` fragment LexiconTree_lexicon on NetworkSlicesLexicon @inline { uri nsid description definitions createdAt updatedAt } `; type LexiconData = { uri: string; nsid: string; description: string | null | undefined; definitions: string; createdAt: string; updatedAt: string | null | undefined; }; interface LexiconTreeProps { lexicons: ReadonlyArray; handle: string; sliceRkey: string; } export function LexiconTree({ lexicons, handle, sliceRkey }: LexiconTreeProps) { // Read fragment data for each lexicon const lexiconData = lexicons.map((lexicon) => readInlineData(LexiconFragment, lexicon) ); // Track which lexicons are invalid const [invalidLexicons, setInvalidLexicons] = useState>(new Set()); // Validate all lexicons together useEffect(() => { const validateLexicons = async () => { try { // Convert lexicons to validator format const lexiconsForValidation: LexiconDoc[] = []; for (const lex of lexiconData) { try { lexiconsForValidation.push({ id: lex.nsid, defs: JSON.parse(lex.definitions), lexicon: 1, description: lex.description || undefined, }); } catch { // If we can't parse the definitions, mark as invalid } } // Validate all lexicons const validationResult = await validate(lexiconsForValidation); if (validationResult !== null) { // validationResult is a map of lexicon ID to array of error strings setInvalidLexicons(new Set(Object.keys(validationResult))); } else { setInvalidLexicons(new Set()); } } catch (error) { console.error("Failed to validate lexicons:", error); } }; validateLexicons(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [lexicons]); return (
{lexiconData.map((lexicon, index) => { const rkey = lexicon.uri.split("/").pop(); const parts = lexicon.nsid.split("."); const authority = parts.length >= 2 ? `${parts[0]}.${parts[1]}` : parts[0]; const rest = parts.length >= 2 ? parts.slice(2).join(".") : ""; // Check if this is a record type lexicon let isRecordType = false; try { const definitions = JSON.parse(lexicon.definitions); isRecordType = definitions.main?.type === "record"; } catch (e) { console.error("Failed to parse lexicon definitions:", lexicon.nsid, e); } // Check if this is the first item or if the authority changed from previous item const prevLexicon = index > 0 ? lexiconData[index - 1] : null; const prevParts = prevLexicon?.nsid.split("."); const prevAuthority = prevParts && prevParts.length >= 2 ? `${prevParts[0]}.${prevParts[1]}` : prevParts?.[0]; const showBreak = index > 0 && authority !== prevAuthority; // Check if this lexicon is invalid const isInvalid = invalidLexicons.has(lexicon.nsid); // Split the rest into middle and last part if it's a record type let middle = rest; let lastPart = ""; if (isRecordType && rest) { const restParts = rest.split("."); if (restParts.length > 1) { lastPart = restParts[restParts.length - 1]; middle = restParts.slice(0, -1).join("."); } else { lastPart = rest; middle = ""; } } return (
{showBreak &&
} {isInvalid && ( )} {authority} {isRecordType ? ( <> {middle && .{middle}} {lastPart && ( <> . {lastPart} )} ) : ( rest && .{rest} )} {lexicon.description && ( {lexicon.description} )}
); })}
); }