import { useState } from "react"; import { graphql, useLazyLoadQuery } from "react-relay"; import { FormControl } from "./FormControl.tsx"; import { Input } from "./Input.tsx"; import { Button } from "./Button.tsx"; import { LexiconDependencyConfirmationDialog } from "./LexiconDependencyConfirmationDialog.tsx"; import { resolveDependencies } from "../utils/lexiconDependencies.ts"; import type { PublishedLexiconsListQuery } from "../__generated__/PublishedLexiconsListQuery.graphql.ts"; interface PublishedLexicon { uri: string; nsid: string; description?: string; defs: unknown; fullData: unknown; } interface LexiconWithData { nsid: string; data: unknown; } interface PublishedLexiconsListProps { existingNsids: string[]; onSelect: (lexicons: LexiconWithData[]) => void; onBack: () => void; onCancel: () => void; } const PUBLISHED_LEXICONS_SLICE_URI = "at://did:plc:dzmqinfp7efnofbqg5npjmth/network.slices.slice/3m3fsrppc3p2h"; export function PublishedLexiconsList({ existingNsids, onSelect, onBack, onCancel, }: PublishedLexiconsListProps) { const [searchQuery, setSearchQuery] = useState(""); const [showDepsDialog, setShowDepsDialog] = useState(false); const [selectedLexicon, setSelectedLexicon] = useState(null); const [resolvedDeps, setResolvedDeps] = useState([]); const data = useLazyLoadQuery( graphql` query PublishedLexiconsListQuery( $sliceUri: String! $where: SliceRecordsWhereInput ) { sliceRecords(sliceUri: $sliceUri, first: 1000, where: $where) { edges { node { uri collection value } } } } `, { sliceUri: PUBLISHED_LEXICONS_SLICE_URI, where: { collection: { eq: "com.atproto.lexicon.schema" }, }, }, { fetchPolicy: "store-and-network", } ); // Parse and filter published lexicons const publishedLexicons = data.sliceRecords.edges .map((edge) => { try { const lexiconData = JSON.parse(edge.node.value); const nsid = lexiconData.id || lexiconData.nsid; const defs = lexiconData.defs || lexiconData.definitions; if (!nsid || !defs) return null; return { uri: edge.node.uri, nsid, description: lexiconData.description, defs, fullData: lexiconData, } as PublishedLexicon; } catch { return null; } }) .filter((lex): lex is PublishedLexicon => lex !== null); // Filter by search query const filteredLexicons = publishedLexicons.filter((lex) => { if (!searchQuery) return true; const query = searchQuery.toLowerCase(); return ( lex.nsid.toLowerCase().includes(query) || lex.description?.toLowerCase().includes(query) ); }); // Check if lexicon already exists in slice const isAlreadyAdded = (nsid: string) => existingNsids.includes(nsid); // Handle lexicon selection with dependency resolution const handleLexiconClick = (lexicon: PublishedLexicon) => { if (isAlreadyAdded(lexicon.nsid)) return; // Convert to LexiconWithData format const mainLexicon: LexiconWithData = { nsid: lexicon.nsid, data: lexicon.fullData, }; // Convert all published lexicons to LexiconWithData format const allLexicons: LexiconWithData[] = publishedLexicons.map(lex => ({ nsid: lex.nsid, data: lex.fullData, })); // Resolve dependencies const dependencies = resolveDependencies(mainLexicon, allLexicons, existingNsids); // If there are dependencies, show confirmation dialog if (dependencies.length > 0) { setSelectedLexicon(mainLexicon); setResolvedDeps(dependencies); setShowDepsDialog(true); } else { // No dependencies, add directly onSelect([mainLexicon]); } }; // Handle confirmation dialog confirmation const handleConfirmDeps = () => { if (selectedLexicon) { onSelect([selectedLexicon, ...resolvedDeps]); } setShowDepsDialog(false); setSelectedLexicon(null); setResolvedDeps([]); }; // Handle confirmation dialog cancellation const handleCancelDeps = () => { setShowDepsDialog(false); setSelectedLexicon(null); setResolvedDeps([]); }; return (
setSearchQuery(e.target.value)} placeholder="Filter by NSID or description..." />
{filteredLexicons.length === 0 ? (
{searchQuery ? "No lexicons match your search" : "No published lexicons found"}
) : ( filteredLexicons.map((lexicon) => { const alreadyAdded = isAlreadyAdded(lexicon.nsid); 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 defs = lexicon.defs as Record | undefined; isRecordType = defs?.main?.type === "record"; } catch { // ignore } // 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 ( ); }) )}
{/* Dependency confirmation dialog */} dep.nsid)} onConfirm={handleConfirmDeps} onCancel={handleCancelDeps} />
); }