diff --git a/frontend/src/app/json-editor/page.tsx b/frontend/src/app/json-editor/page.tsx index fbaf7d544..64ee461f5 100644 --- a/frontend/src/app/json-editor/page.tsx +++ b/frontend/src/app/json-editor/page.tsx @@ -32,6 +32,11 @@ import Note from "./_components/note"; import { githubGist, nord } from "react-syntax-highlighter/dist/esm/styles/hljs"; import SyntaxHighlighter from "react-syntax-highlighter"; import { ScriptItem } from "../scripts/_components/script-item"; +import { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"; +import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog"; +import { search } from "@/components/command-menu"; +import { basePath } from "@/config/site-config"; +import Image from "next/image"; import { useTheme } from "next-themes"; const initialScript: Script = { @@ -65,8 +70,35 @@ export default function JSONGenerator() { const [isValid, setIsValid] = useState(false); const [categories, setCategories] = useState([]); const [currentTab, setCurrentTab] = useState<"json" | "preview">("json"); + const [selectedCategory, setSelectedCategory] = useState(""); + const [searchQuery, setSearchQuery] = useState(""); + const [isImportDialogOpen, setIsImportDialogOpen] = useState(false); const [zodErrors, setZodErrors] = useState(null); + const selectedCategoryObj = useMemo( + () => categories.find(cat => cat.id.toString() === selectedCategory), + [categories, selectedCategory] + ); + + const allScripts = useMemo( + () => categories.flatMap(cat => cat.scripts || []), + [categories] + ); + + const scripts = useMemo(() => { + const query = searchQuery.trim() + + if (query) { + return search(allScripts, query) + } + + if (selectedCategoryObj) { + return selectedCategoryObj.scripts || [] + } + + return [] + }, [allScripts, selectedCategoryObj, searchQuery]); + useEffect(() => { fetchCategories() .then(setCategories) @@ -120,6 +152,53 @@ export default function JSONGenerator() { if (isValid) toast.success("Copied metadata to clipboard"); }, [script]); + const importScript = (script: Script) => { + try { + const result = ScriptSchema.safeParse(script); + if (!result.success) { + setIsValid(false); + setZodErrors(result.error); + toast.error("Imported JSON is invalid according to the schema."); + return; + } + + setScript(result.data); + setIsValid(true); + setZodErrors(null); + toast.success("Imported JSON successfully"); + } catch (error) { + toast.error("Failed to read or parse the JSON file."); + } + + } + + const handleFileImport = useCallback(() => { + const input = document.createElement("input"); + input.type = "file"; + input.accept = "application/json"; + + input.onchange = (e: Event) => { + const target = e.target as HTMLInputElement; + const file = target.files?.[0]; + if (!file) return; + + const reader = new FileReader(); + reader.onload = (event) => { + try { + const content = event.target?.result as string; + const parsed = JSON.parse(content); + importScript(parsed); + toast.success("Imported JSON successfully"); + } catch (error) { + toast.error("Failed to read the JSON file."); + } + }; + reader.readAsText(file); + }; + + input.click(); + }, [setScript]); + const handleDownload = useCallback(() => { if (isValid === false) { toast.error("Cannot download invalid JSON"); @@ -177,7 +256,94 @@ export default function JSONGenerator() { return (
-

JSON Generator

+
+

JSON Generator

+ + + + + + + Import local JSON file + + + e.preventDefault()}> + Import existing script + + + + + Import existing script + + Select one of the puplished scripts to import its metadata. + + + +
+
+ + setSearchQuery(e.target.value)} + /> + {!selectedCategory && !searchQuery ? ( +

+ Select a category or search for a script +

+ ) : scripts.length === 0 ? ( +

+ No scripts found +

+ ) : ( +
+ {scripts.map(script => ( +
{ + importScript(script); + setIsImportDialogOpen(false); + }} + > + {script.name} +

{script.name}

+
+ ))} +
+ )} +
+
+
+
+
+
+
+
diff --git a/frontend/src/components/command-menu.tsx b/frontend/src/components/command-menu.tsx index 081cade24..fcfb1a174 100644 --- a/frontend/src/components/command-menu.tsx +++ b/frontend/src/components/command-menu.tsx @@ -23,7 +23,7 @@ import { Button } from "./ui/button"; import { Badge } from "./ui/badge"; import Link from "next/link"; -function search(scripts: Script[], query: string): Script[] { +export function search(scripts: Script[], query: string): Script[] { const queryLower = query.toLowerCase().trim(); const searchWords = queryLower.split(/\s+/).filter(Boolean);