import { useEffect, useState, useCallback } from "react"; import { api, CardRef, Deck, DeckEntry } from "../api"; import { CardSearchBox, CardThumb, CardTile, ColorPips, GroupBySelect, GroupMode, ViewToggle, groupCards, usePersistedState, useViewMode, } from "../components"; const FORMATS = [ "", "Commander", "Brawl", "Standard", "Pioneer", "Modern", "Legacy", "Vintage", "Pauper", "Other", ]; const COMMANDER_FORMATS = ["Commander", "Brawl"]; /** * Deck editor. A deck's cards are split into two groups — "In your library" * (owned) and "Not owned" — each with its own search box, and each groupable by * card attribute. Deck-level format + commander metadata sit at the top. */ export function DeckView() { const [decks, setDecks] = useState([]); const [selected, setSelected] = usePersistedState("deck-selected", null); const [newName, setNewName] = useState(""); const [contents, setContents] = useState([]); const [view, setView] = useViewMode("deck-view"); const [groupBy, setGroupBy] = usePersistedState("deck-group", "none"); const [libQuery, setLibQuery] = useState(""); const [libResults, setLibResults] = useState([]); const [anyQuery, setAnyQuery] = useState(""); const [anyResults, setAnyResults] = useState([]); const [cmdQuery, setCmdQuery] = useState(""); const [cmdResults, setCmdResults] = useState([]); const deck = decks.find((d) => d.id === selected) ?? null; const loadDecks = useCallback(async () => { const d = await api.listDecks(); setDecks(d); // Keep the persisted selection if it still exists, else fall back to the first. if (d.length && (selected === null || !d.some((x) => x.id === selected))) { setSelected(d[0].id); } }, [selected, setSelected]); useEffect(() => { loadDecks(); }, [loadDecks]); const loadContents = useCallback(async () => { if (selected === null) { setContents([]); return; } setContents(await api.deckContents(selected)); }, [selected]); useEffect(() => { loadContents(); }, [loadContents]); useEffect(() => { const q = libQuery.trim(); if (!q) return setLibResults([]); const t = setTimeout(() => api.searchLibrary({ text: q }).then(setLibResults), 100); return () => clearTimeout(t); }, [libQuery]); useEffect(() => { const q = anyQuery.trim(); if (!q) return setAnyResults([]); const t = setTimeout(() => api.searchCards(q, 12).then(setAnyResults), 80); return () => clearTimeout(t); }, [anyQuery]); useEffect(() => { const q = cmdQuery.trim(); if (!q) return setCmdResults([]); const t = setTimeout(() => api.searchCards(q, 12).then(setCmdResults), 80); return () => clearTimeout(t); }, [cmdQuery]); async function createDeck() { if (!newName.trim()) return; const d = await api.createDeck(newName.trim(), null); setNewName(""); await loadDecks(); setSelected(d.id); } async function addToDeck(card: CardRef) { if (selected === null) return; const existing = contents.find((e) => e.oracle_id === card.oracle_id); const qty = existing ? existing.quantity + 1 : 1; await api.setDeckCard(selected, card.oracle_id, qty, false); setLibQuery(""); setLibResults([]); setAnyQuery(""); setAnyResults([]); await loadContents(); } async function setQty(oracleId: string, qty: number) { if (selected === null) return; await api.setDeckCard(selected, oracleId, qty, false); await loadContents(); } async function removeDeck(id: number) { await api.deleteDeck(id); if (selected === id) setSelected(null); await loadDecks(); } async function changeFormat(format: string) { if (selected === null) return; await api.setDeckFormat(selected, format); await loadDecks(); } async function setCommander(card: CardRef) { if (selected === null) return; // Ensure the commander is in the deck, then flag it. if (!contents.find((e) => e.oracle_id === card.oracle_id)) { await api.setDeckCard(selected, card.oracle_id, 1, true); } await api.setDeckCommander(selected, card.oracle_id); setCmdQuery(""); setCmdResults([]); await loadContents(); await loadDecks(); } async function clearCommander() { if (selected === null) return; await api.setDeckCommander(selected, null); await loadContents(); await loadDecks(); } const owned = contents.filter((e) => e.quantity_owned > 0); const unowned = contents.filter((e) => e.quantity_owned === 0); const commanderName = deck?.commander_oracle_id && (contents.find((e) => e.oracle_id === deck.commander_oracle_id)?.name ?? deck.commander_oracle_id); function deckTiles(entries: DeckEntry[]) { return (
{entries.map((e) => ( setQty(e.oracle_id, 0)} > ✕ } footer={ {e.is_commander ? "★ " : ""}in deck ×{e.quantity} } /> ))}
); } function deckTable(entries: DeckEntry[]) { return ( {entries.map((e) => ( ))}
Colors Name Type In deck Owned
{e.is_commander && ★ } {e.name} {e.type_line} setQty(e.oracle_id, Number(ev.target.value))} /> {e.quantity_owned}
); } function cardsGroup(entries: DeckEntry[]) { if (entries.length === 0) return

None yet.

; return groupCards(entries, groupBy).map((g) => (
{g.label && (

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

)} {view === "tiles" ? deckTiles(g.cards) : deckTable(g.cards)}
)); } return (

Decks

setNewName(e.target.value)} onKeyDown={(e) => e.key === "Enter" && createDeck()} />
    {decks.map((d) => (
  • setSelected(d.id)} > {d.name} {d.format && {d.format}}
  • ))} {decks.length === 0 && (
  • No decks yet.
  • )}
{selected === null || !deck ? (

Select or create a deck.

) : ( <>

{deck.name}

{/* Deck metadata: format + (for Commander/Brawl) the commander card. */}
{COMMANDER_FORMATS.includes(deck.format) && (
Commander: {commanderName || "none"} {commanderName && ( )}
)}

In your library {owned.length}

{cardsGroup(owned)}

Not owned {unowned.length}

{cardsGroup(unowned)}
)}
); }