diff --git a/src/App.tsx b/src/App.tsx index 5c7e8f0..ed1d514 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -29,7 +29,7 @@ export function App() { const [zone, setZone] = useState(emptyZone); const [typeFilter, setTypeFilter] = useState("ALL"); const [selectedPath, setSelectedPath] = useState([]); - const { parseErrors, importedName, importFile, exportZone, reset } = useZoneFile(); + const { parseErrors, importedName, isResolving, importFile, importFromDns, exportZone, reset } = useZoneFile(); const dns = useDnsCheck(); const issues = useMemo(() => validateZone(zone), [zone]); @@ -72,6 +72,15 @@ export function App() { } }; + const handleImportFromDns = async (resolverId: string, domain: string) => { + dns.reset(); + const next = await importFromDns(resolverId, domain); + if (next) { + setZone(next); + setSelectedPath([]); + } + }; + const handleNew = () => { setZone(emptyZone()); reset(); @@ -127,7 +136,9 @@ export function App() { exportZone(zone)} onNew={handleNew} onAddRecord={handleAddRecord} diff --git a/src/components/ImportMenu.tsx b/src/components/ImportMenu.tsx new file mode 100644 index 0000000..719a675 --- /dev/null +++ b/src/components/ImportMenu.tsx @@ -0,0 +1,128 @@ +import { useEffect, useRef, useState, type FormEvent } from "react"; +import { DEFAULT_RESOLVERS } from "../dns/resolvers"; + +interface ImportMenuProps { + onImportFile: (file: File) => void; + onImportFromDns: (resolverId: string, domain: string) => void; + isResolving: boolean; +} + +export function ImportMenu({ onImportFile, onImportFromDns, isResolving }: ImportMenuProps) { + const [open, setOpen] = useState(false); + const [resolverId, setResolverId] = useState(null); + const [domain, setDomain] = useState(""); + const fileInput = useRef(null); + const containerRef = useRef(null); + + useEffect(() => { + if (!open && resolverId === null) return; + const handler = (e: MouseEvent) => { + if (containerRef.current && !containerRef.current.contains(e.target as Node)) { + setOpen(false); + setResolverId(null); + } + }; + const keyHandler = (e: KeyboardEvent) => { + if (e.key === "Escape") { + setOpen(false); + setResolverId(null); + } + }; + window.addEventListener("mousedown", handler); + window.addEventListener("keydown", keyHandler); + return () => { + window.removeEventListener("mousedown", handler); + window.removeEventListener("keydown", keyHandler); + }; + }, [open, resolverId]); + + const handlePickResolver = (id: string) => { + setOpen(false); + setResolverId(id); + setDomain(""); + }; + + const handleSubmit = (e: FormEvent) => { + e.preventDefault(); + const trimmed = domain.trim(); + if (!trimmed || !resolverId) return; + onImportFromDns(resolverId, trimmed); + setResolverId(null); + setDomain(""); + }; + + const activeResolver = resolverId + ? DEFAULT_RESOLVERS.find((r) => r.id === resolverId) + : null; + + return ( +
+ + { + const f = e.target.files?.[0]; + if (f) onImportFile(f); + e.target.value = ""; + setOpen(false); + }} + /> + + {open && ( +
+ +
+
From public DNS
+ {DEFAULT_RESOLVERS.map((r) => ( + + ))} +
+ )} + + {activeResolver && ( +
+ Resolve from {activeResolver.name}: + setDomain(e.target.value)} + placeholder="example.com" + /> + + +
+ )} +
+ ); +} diff --git a/src/components/Topbar.tsx b/src/components/Topbar.tsx index c56b973..0d80527 100644 --- a/src/components/Topbar.tsx +++ b/src/components/Topbar.tsx @@ -1,11 +1,13 @@ -import { useRef } from "react"; import { GitHubLink } from "./GitHubLink"; +import { ImportMenu } from "./ImportMenu"; import { Logo } from "./Logo"; interface TopbarProps { canExport: boolean; isChecking: boolean; + isResolving: boolean; onImport: (file: File) => void; + onImportFromDns: (resolverId: string, domain: string) => void; onExport: () => void; onNew: () => void; onAddRecord: () => void; @@ -16,15 +18,15 @@ interface TopbarProps { export function Topbar({ canExport, isChecking, + isResolving, onImport, + onImportFromDns, onExport, onNew, onAddRecord, onCheck, onCancelCheck, }: TopbarProps) { - const fileInput = useRef(null); - return (
@@ -32,17 +34,10 @@ export function Topbar({

Codabind

- - { - const f = e.target.files?.[0]; - if (f) onImport(f); - e.target.value = ""; - }} +