import { useEffect, useState } from "react"; import { api, Deck, LibraryCard, LibraryFilter } from "../api"; import { CardThumb, CardTile, ColorPips, ColorToggles, GroupBySelect, GroupMode, LocationField, ViewToggle, groupCards, usePersistedState, useViewMode, } from "../components"; /** Browse & filter the owned library by name and card criteria. */ export function LibraryBrowser() { const [text, setText] = useState(""); const [commander, setCommander] = useState(0); const [colorsInclude, setColorsInclude] = useState(0); const [cmcMin, setCmcMin] = useState(""); const [cmcMax, setCmcMax] = useState(""); const [type, setType] = useState(""); const [subtype, setSubtype] = useState(""); const [keyword, setKeyword] = useState(""); const [inDeck, setInDeck] = useState(""); const [decks, setDecks] = useState([]); const [rows, setRows] = useState([]); const [view, setView] = useViewMode("library-view"); const [groupBy, setGroupBy] = usePersistedState("library-group", "none"); const [refreshKey, setRefreshKey] = useState(0); useEffect(() => { api.listDecks().then(setDecks); }, []); useEffect(() => { const filter: LibraryFilter = { text: text.trim() || null, commander_identity: commander || null, colors_include: colorsInclude || null, cmc_min: cmcMin === "" ? null : Number(cmcMin), cmc_max: cmcMax === "" ? null : Number(cmcMax), types: splitCsv(type), subtypes: splitCsv(subtype), keywords: splitCsv(keyword), in_deck: inDeck === "" ? null : Number(inDeck), }; const t = setTimeout(() => { api.searchLibrary(filter).then(setRows).catch(() => setRows([])); }, 120); return () => clearTimeout(t); }, [text, commander, colorsInclude, cmcMin, cmcMax, type, subtype, keyword, inDeck, refreshKey]); const deckOrLocation = (c: LibraryCard) => c.decks.length === 0 ? ( setRefreshKey((k) => k + 1)} /> ) : ( c.decks.map((d) => ( {d} )) ); function libTable(cards: LibraryCard[]) { return ( {cards.map((c) => ( ))}
Colors Name Type MV Owned Decks / Location
{c.name} {c.type_line} {c.mana_value} c.quantity_owned ? "over-assigned" : ""}> {c.quantity_owned} {c.quantity_assigned > c.quantity_owned && ( {" "} ⚠ {c.quantity_assigned} assigned )} {deckOrLocation(c)}
); } function libTiles(cards: LibraryCard[]) { return (
{cards.map((c) => ( c.quantity_owned ? "warn" : ""}`} title="Copies owned" > ×{c.quantity_owned} {c.quantity_assigned > c.quantity_owned ? " ⚠" : ""} } footer={deckOrLocation(c)} /> ))}
); } const groups = groupCards(rows, groupBy); return (

Library {rows.length}

setText(e.target.value)} placeholder="e.g. bolt" />
setCmcMin(e.target.value)} /> – setCmcMax(e.target.value)} />
setType(e.target.value)} placeholder="Creature, Legendary" />
setSubtype(e.target.value)} placeholder="Elf" />
setKeyword(e.target.value)} placeholder="Flying" />
{rows.length === 0 ? (

No matching cards owned. Add some in the Add Cards tab, or loosen the filters.

) : ( groups.map((g) => (
{g.label && (

{g.label} {g.cards.length}

)} {view === "list" ? libTable(g.cards) : libTiles(g.cards)}
)) )}
); } function splitCsv(s: string): string[] { return s .split(",") .map((x) => x.trim()) .filter(Boolean); }