"use client"; import { type ChangeEvent, type DragEvent, useCallback, useEffect, useMemo, useRef, useState, } from "react"; import { useSearchParams } from "next/navigation"; import { loadIconsManifest } from "@/lib/icons-manifest"; import { ArrowUpRight, Braces, Check, Copy, Download, Minimize2, Moon, Sun, Trash2, Upload, } from "lucide-react"; import { Button } from "@/components/ui/button"; import { cn } from "@/lib/utils"; import { computeSvgMetrics, formatBytes, formatSvg, isLikelySvg, minifySvg, sanitizeSvgForRender, } from "@/lib/svg-utils"; import { DeepLinkActions } from "@/components/viewer/deep-link-actions"; import { QuickImport } from "@/components/viewer/quick-import"; type PreviewBg = "checker" | "light" | "dark"; export function SvgViewer() { const searchParams = useSearchParams(); const fromSlug = searchParams.get("from"); const [source, setSource] = useState(""); const [title, setTitle] = useState("thesvg export"); const [previewBg, setPreviewBg] = useState("checker"); const [copied, setCopied] = useState(false); const [dragOver, setDragOver] = useState(false); const [error, setError] = useState(null); const [autoloading, setAutoloading] = useState(null); const fileInputRef = useRef(null); const textareaRef = useRef(null); const gutterRef = useRef(null); // One-shot guard so Clear (which empties `source`) doesn't cause the // auto-load effect to re-fire and silently re-fill the editor. const didAutoLoad = useRef(false); // Auto-load icon from ?from=slug query param (set when arriving from // /icon/[slug]'s "Inspect in SVG Viewer" link or the editor menu). useEffect(() => { if (!fromSlug || didAutoLoad.current) return; didAutoLoad.current = true; let cancelled = false; setAutoloading(fromSlug); (async () => { try { const manifest = await loadIconsManifest(); const icon = manifest.find((m) => m.slug === fromSlug); if (!icon) { if (!cancelled) { setAutoloading(null); setError(`No icon with slug "${fromSlug}" in the library.`); } return; } const res = await fetch(icon.variants.default); if (!res.ok) throw new Error(`Fetch failed: ${res.status}`); const text = await res.text(); if (cancelled) return; setSource(text); setTitle(icon.title); setError(null); } catch { if (!cancelled) setError("Could not auto-load that icon."); } finally { if (!cancelled) setAutoloading(null); } })(); return () => { cancelled = true; }; }, [fromSlug]); const metrics = useMemo(() => computeSvgMetrics(source), [source]); const hasContent = source.trim().length > 0; const isValid = hasContent && metrics !== null; // The textarea content is fully user-controlled (paste, drop, import). // Sanitize before innerHTML to prevent inline event-handler /