import { Database, Search, Trash2 } from "lucide-react"; import { useCallback, useEffect, useMemo, useOptimistic, useState, } from "react"; import type { CachedSourceInfo } from "../adapters/bestiary-cache.js"; import * as bestiaryCache from "../adapters/bestiary-cache.js"; import { useBestiaryContext } from "../contexts/bestiary-context.js"; import { Button } from "./ui/button.js"; import { Input } from "./ui/input.js"; export function SourceManager() { const { refreshCache } = useBestiaryContext(); const [sources, setSources] = useState([]); const [filter, setFilter] = useState(""); const [optimisticSources, applyOptimistic] = useOptimistic( sources, ( state, action: { type: "remove"; sourceCode: string } | { type: "clear" }, ) => action.type === "clear" ? [] : state.filter((s) => s.sourceCode !== action.sourceCode), ); const loadSources = useCallback(async () => { const cached = await bestiaryCache.getCachedSources(); setSources(cached); }, []); useEffect(() => { void loadSources(); }, [loadSources]); const handleClearSource = async (sourceCode: string) => { applyOptimistic({ type: "remove", sourceCode }); await bestiaryCache.clearSource(sourceCode); await loadSources(); void refreshCache(); }; const handleClearAll = async () => { applyOptimistic({ type: "clear" }); await bestiaryCache.clearAll(); await loadSources(); void refreshCache(); }; const filteredSources = useMemo(() => { const term = filter.toLowerCase(); return term ? optimisticSources.filter((s) => s.displayName.toLowerCase().includes(term), ) : optimisticSources; }, [optimisticSources, filter]); if (optimisticSources.length === 0) { return (

No cached sources

); } return (
Cached Sources
setFilter(e.target.value)} className="pl-8" />
); }