diff --git a/src/views/CardEntry.tsx b/src/views/CardEntry.tsx index faa00e6..938f0d0 100644 --- a/src/views/CardEntry.tsx +++ b/src/views/CardEntry.tsx @@ -1,5 +1,5 @@ import { useEffect, useRef, useState } from "react"; -import { api, CardRef, libraryKey } from "../api"; +import { api, CardRef, Deck, DeckEntry, DECK_STATUSES, isPhysical, libraryKey } from "../api"; import { CardSearchBox, QtyStepper } from "../components"; /** Rapid card entry: fuzzy search-as-you-type, ↑/↓ + Enter (or click) adds one copy. */ @@ -7,17 +7,42 @@ export function CardEntry() { const [query, setQuery] = useState(""); const [results, setResults] = useState([]); const [recent, setRecent] = useState< - { oracleId: string; name: string; qty: number; location: string }[] + { oracleId: string; name: string; qty: number; location: string; deck: string }[] >([]); // Persisted so a "box" location sticks while entering a stack of cards. // Each library has its own boxes. const [location, setLocation] = useState(() => localStorage.getItem(libraryKey("add-location")) || ""); + // Likewise a deck being filled from a pile of new cards. Retired decks + // aren't offered: they're kept for reference, not added to. + const [decks, setDecks] = useState([]); + const [deckId, setDeckId] = useState(() => { + const saved = Number(localStorage.getItem(libraryKey("add-deck"))); + return saved > 0 ? saved : ""; + }); const searchInputRef = useRef(null); useEffect(() => { localStorage.setItem(libraryKey("add-location"), location); }, [location]); + useEffect(() => { + localStorage.setItem(libraryKey("add-deck"), String(deckId)); + }, [deckId]); + + useEffect(() => { + api.listDecks().then((all) => { + const open = all.filter((d) => d.status !== "retired"); + setDecks(open); + // The remembered deck may since have been deleted or retired. + setDeckId((id) => (open.some((d) => d.id === id) ? id : "")); + }); + }, []); + + const deck = decks.find((d) => d.id === deckId) ?? null; + // A building/built deck holds its cards, so new copies go straight into it + // rather than into a box. + const intoDeck = deck !== null && isPhysical(deck.status); + // Typing anywhere on this tab — without having clicked into another field // first — jumps focus to the search box and keeps the keystroke that // triggered it, instead of it going nowhere. @@ -62,16 +87,40 @@ export function CardEntry() { }, [query]); async function add(card: CardRef) { + // Read the deck's slot before the new copy changes what it has spare. + const entry = deck ? (await api.deckContents(deck.id)).find((e) => e.oracle_id === card.oracle_id) : undefined; const qty = await api.addOwned(card.oracle_id, 1); - const loc = location.trim(); + const loc = intoDeck ? "" : location.trim(); if (loc) await api.setLocation(card.oracle_id, loc); + if (deck) await sendToDeck(deck, card.oracle_id, entry); setRecent((r) => - [{ oracleId: card.oracle_id, name: card.name, qty, location: loc }, ...r].slice(0, 12), + [{ oracleId: card.oracle_id, name: card.name, qty, location: loc, deck: deck?.name ?? "" }, ...r].slice(0, 12), ); setQuery(""); setResults([]); } + /** Put a just-acquired copy in `deck`. It fills a slot the deck is short + * on when there is one, otherwise the deck gets one more copy. Short means + * not yet pulled for a building/built deck, which also counts the copy as + * pulled since it went straight in instead of into a box; for an idea deck + * it means not owned or claimed by other decks. A maybeboarded card moves + * to the main board. */ + async function sendToDeck(deck: Deck, oracleId: string, entry: DeckEntry | undefined) { + const physical = isPhysical(deck.status); + let quantity = 1; + let pulled = 0; + if (entry && entry.is_maybeboard) { + quantity = entry.quantity; + } else if (entry) { + const short = physical ? entry.quantity_pulled < entry.quantity : entry.quantity_spare < entry.quantity; + quantity = short ? entry.quantity : entry.quantity + 1; + pulled = entry.quantity_pulled; + } + await api.setDeckCard(deck.id, oracleId, quantity, entry?.is_commander ?? false, false); + if (physical) await api.setDeckCardPulled(deck.id, oracleId, pulled + 1); + } + async function adjust(oracleId: string, delta: number) { const qty = await api.addOwned(oracleId, delta); setRecent((r) => r.map((e) => (e.oracleId === oracleId ? { ...e, qty } : e))); @@ -83,13 +132,27 @@ export function CardEntry() {

Add Cards

-
- - setLocation(e.target.value)} - /> +
+
+ + setLocation(e.target.value)} + /> +
+
+ + +
(
  • {r.name} + {r.deck && → {r.deck}} {r.location && {r.location}}