import { useState, useMemo } from "react"; import { useNavigate } from "react-router-dom"; import { pluginRegistry } from "../core/plugins/plugin-registry"; import type { UnknownPlimiPlugin } from "../core/plugins/plugin-types"; import { useTheme } from "../app/useTheme"; import { PlimiSearch, CategoryChips, ToolTile, SectionHeader } from "../components/directory/DirectoryComponents"; const PLIMI_CATEGORIES = [ { id: "all", label: "All tools" }, { id: "developer", label: "Developer" }, { id: "image", label: "Image" }, { id: "text", label: "Text" }, { id: "pdf", label: "PDF" }, { id: "crypto", label: "Crypto" }, { id: "privacy", label: "Privacy" }, ]; export function ToolsPage() { const navigate = useNavigate(); const { dark } = useTheme(); const [q, setQ] = useState(""); const [cat, setCat] = useState("all"); const [focusedIndex, setFocusedIndex] = useState(0); const filtered = useMemo(() => { return pluginRegistry.filter( (p) => (cat === "all" || p.manifest.category === cat) && (p.manifest.name.toLowerCase().includes(q.toLowerCase()) || p.manifest.description.toLowerCase().includes(q.toLowerCase())) ); }, [q, cat]); const safeFocusedIndex = filtered.length === 0 ? -1 : Math.min(focusedIndex, filtered.length - 1); const counts = useMemo(() => { const out: Record = { all: 0 }; PLIMI_CATEGORIES.forEach((c) => { out[c.id] = 0; }); pluginRegistry.forEach((p) => { const matchSearch = p.manifest.name.toLowerCase().includes(q.toLowerCase()) || p.manifest.description.toLowerCase().includes(q.toLowerCase()); if (!matchSearch) return; out.all += 1; if (out[p.manifest.category] !== undefined) { out[p.manifest.category] += 1; } else { out[p.manifest.category] = 1; } }); return out; }, [q]); const grouped = useMemo(() => { if (cat !== 'all' || q) return null; const map: Record = {}; filtered.forEach((t) => { (map[t.manifest.category] = map[t.manifest.category] || []).push(t); }); return PLIMI_CATEGORIES.filter((c) => c.id !== 'all' && map[c.id]?.length).map((c) => ({ cat: c, tools: map[c.id] })); }, [filtered, cat, q]); const handleOpenTool = (plugin: UnknownPlimiPlugin) => { navigate(`/tools/${plugin.manifest.id}`); }; return (
your digital pencil case

Small tools.
Big trust.

{pluginRegistry.length} utilities for files, text and code — running entirely in your browser. No upload. No account. No server.

{ setQ(nextQuery); setFocusedIndex(0); }} count={filtered.length} total={pluginRegistry.length} onArrow={(dir) => { if (filtered.length === 0) return; setFocusedIndex((prev) => { const next = prev + dir; if (next < 0) return filtered.length - 1; if (next >= filtered.length) return 0; return next; }); }} onEnter={() => { if (safeFocusedIndex >= 0 && safeFocusedIndex < filtered.length) { handleOpenTool(filtered[safeFocusedIndex]); } }} />
{ setCat(nextCategory); setFocusedIndex(0); }} counts={counts} categories={PLIMI_CATEGORIES} /> {grouped ? (
{grouped.map(({ cat: c, tools }) => (
{tools.map((t) => { const flatIdx = filtered.indexOf(t); return ( ); })}
))}
) : filtered.length === 0 ? (
No tools found matching "{q}".
) : (
{filtered.map((t, flatIdx) => ( ))}
)}
); }