diff --git a/src/components.tsx b/src/components.tsx index 054621d..1ba0f14 100644 --- a/src/components.tsx +++ b/src/components.tsx @@ -98,6 +98,15 @@ const ICON_UPLOAD = icon( , ); +// A box (as on the "Not owned" filter) with a plus: add to the library. +const ICON_LIBRARY_ADD = icon( + <> + + + + + , +); /** * A small round icon-only button, used consistently for lightweight actions @@ -146,6 +155,7 @@ export const Icons = { cards: ICON_CARDS, download: ICON_DOWNLOAD, upload: ICON_UPLOAD, + libraryAdd: ICON_LIBRARY_ADD, }; // --- Dialogs ------------------------------------------------------------ diff --git a/src/views/DeckView.tsx b/src/views/DeckView.tsx index e4a107a..7096425 100644 --- a/src/views/DeckView.tsx +++ b/src/views/DeckView.tsx @@ -55,6 +55,14 @@ function useMediaQuery(query: string) { } /** Tooltip/label + icon for the per-card mainboard/maybeboard toggle. */ +/** Copies of a deck card you'd need to acquire to cover the deck. */ +const shortBy = (e: DeckEntry) => Math.max(0, e.quantity - e.quantity_owned); + +function libraryAddLabel(e: DeckEntry): string { + const n = shortBy(e); + return n === 1 ? "Add to library" : `Add ${n} copies to library`; +} + function boardToggleProps(e: DeckEntry) { return e.is_maybeboard ? { icon: Icons.arrowLeftCircle, label: "Move to deck" } @@ -763,6 +771,23 @@ export function DeckView({ refreshSignal }: { refreshSignal?: number }) { } } + /** Record cards as owned: as many copies as the deck uses beyond those + * already owned, so an acquired card moves to "In your library". */ + async function addToLibrary(entries: DeckEntry[]) { + await Promise.all(entries.filter((e) => shortBy(e) > 0).map((e) => api.addOwned(e.oracle_id, shortBy(e)))); + await loadContents(); + } + + async function bulkAddToLibrary() { + setBulkBusy(true); + try { + await addToLibrary(contents.filter((e) => selectedCards.has(e.oracle_id))); + setSelectedCards(new Set()); + } finally { + setBulkBusy(false); + } + } + async function confirmDelete() { if (dialog?.kind !== "delete") return; await api.deleteDeck(dialog.deck.id); @@ -904,7 +929,17 @@ export function DeckView({ refreshSignal }: { refreshSignal?: number }) { /> } cornerBottomLeft={ - setMaybeboard(e, !e.is_maybeboard)} /> + + setMaybeboard(e, !e.is_maybeboard)} /> + {e.quantity_owned === 0 && ( + addToLibrary([e])} + /> + )} + } footer={e.is_commander ? ★ commander : undefined} /> @@ -960,7 +995,12 @@ export function DeckView({ refreshSignal }: { refreshSignal?: number }) { {e.quantity_owned} - setMaybeboard(e, !e.is_maybeboard)} /> + + setMaybeboard(e, !e.is_maybeboard)} /> + {e.quantity_owned === 0 && ( + addToLibrary([e])} /> + )} + ))} @@ -1264,6 +1304,13 @@ export function DeckView({ refreshSignal }: { refreshSignal?: number }) { +