import { useState } from "react"; import { graphql, useMutation, ConnectionHandler } from "react-relay"; import { isValidNsid } from "@slices/lexicon"; import { Dialog } from "./Dialog.tsx"; import { FormControl } from "./FormControl.tsx"; import { Textarea } from "./Textarea.tsx"; import { Button } from "./Button.tsx"; import { PublishedLexiconsList } from "./PublishedLexiconsList.tsx"; import type { CreateLexiconDialogMutation } from "../__generated__/CreateLexiconDialogMutation.graphql.ts"; import "../components/LexiconTree.tsx"; // Import for fragment interface CreateLexiconDialogProps { open: boolean; onClose: () => void; sliceUri: string; existingNsids: string[]; } type SourceType = 'published' | 'new' | null; export function CreateLexiconDialog({ open, onClose, sliceUri, existingNsids, }: CreateLexiconDialogProps) { const [step, setStep] = useState<1 | 2>(1); const [sourceType, setSourceType] = useState(null); const [lexiconJson, setLexiconJson] = useState(""); const [error, setError] = useState(""); const [isValidating, setIsValidating] = useState(false); const [commitMutation, isMutationInFlight] = useMutation( graphql` mutation CreateLexiconDialogMutation( $input: NetworkSlicesLexiconInput! ) { createNetworkSlicesLexicon(input: $input) { id uri cid nsid description definitions createdAt updatedAt excludedFromSync slice ...LexiconTree_lexicon } } ` ); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); e.stopPropagation(); setError(""); setIsValidating(true); try { if (!lexiconJson.trim()) { setError("Lexicon JSON is required"); return; } let lexiconData; try { lexiconData = JSON.parse(lexiconJson); } catch (parseError) { setError( `Failed to parse lexicon JSON: ${ parseError instanceof Error ? parseError.message : String(parseError) }` ); return; } // Basic validation for data integrity const defs = lexiconData.defs || lexiconData.definitions; const id = lexiconData.id || lexiconData.nsid; if (!id) { setError("Lexicon must have an 'id' field"); return; } if (!defs) { setError("Lexicon must have a 'defs' field"); return; } // Validate NSID format (needs at least 3 segments like "com.example.collection") if (!(await isValidNsid(id))) { setError(`Invalid lexicon ID: "${id}". Must be a valid NSID with at least 3 segments (e.g., "com.example.post")`); return; } // Check for duplicate NSID if (existingNsids.includes(id)) { setError(`A lexicon with NSID "${id}" already exists in this slice. Please use a different NSID.`); return; } const nsid = id; const definitionsString = JSON.stringify(defs); commitMutation({ variables: { input: { nsid, description: lexiconData.description || "", definitions: definitionsString, slice: sliceUri, createdAt: new Date().toISOString(), excludedFromSync: false, }, }, onCompleted: () => { setLexiconJson(""); setError(""); onClose(); }, onError: (err) => { setError(err.message || "Failed to create lexicon"); }, updater: (store) => { const newLexicon = store.getRootField("createNetworkSlicesLexicon"); if (!newLexicon) return; // Extract the rkey from the slice URI (e.g., "at://did/collection/rkey" -> "rkey") const sliceRkey = sliceUri.split("/").pop(); if (!sliceRkey) return; // Use ConnectionHandler to get the connection const root = store.getRoot(); const connection = ConnectionHandler.getConnection( root, "SliceOverview_networkSlicesLexicons", { where: { slice: { contains: sliceRkey } } } ); if (connection) { // Create and insert a new edge const newEdge = ConnectionHandler.createEdge( store, connection, newLexicon, "NetworkSlicesLexiconEdge" ); ConnectionHandler.insertEdgeAfter(connection, newEdge); } }, }); } finally { setIsValidating(false); } }; const handleClose = () => { if (isValidating) { return; // Prevent closing while validation is in progress } setStep(1); setSourceType(null); setLexiconJson(""); setError(""); setIsValidating(false); onClose(); }; const handleSourceSelect = (type: SourceType) => { setSourceType(type); setStep(2); setError(""); }; const handleBack = () => { setStep(1); setSourceType(null); setLexiconJson(""); setError(""); }; return ( {error && (
{error}
)} {step === 1 ? (

Choose how you'd like to add a lexicon:

) : sourceType === 'new' ? (