Library: remove selected cards from the library
The bulk bar gets a Remove from library button that removes every copy of the selected cards. Decks keep them. Undo puts back the counts, locations and printing groups.
This commit is contained in:
@@ -238,7 +238,9 @@ installed app in place.
|
|||||||
row's thumbnail for a full-size preview that tracks the cursor. Rows show which
|
row's thumbnail for a full-size preview that tracks the cursor. Rows show which
|
||||||
decks a card is in and a warning (⚠) when it's assigned to more decks than you
|
decks a card is in and a warning (⚠) when it's assigned to more decks than you
|
||||||
own. Toggle **List / Tiles** (top-right) to switch between the table and a grid
|
own. Toggle **List / Tiles** (top-right) to switch between the table and a grid
|
||||||
of full card images; the choice is remembered.
|
of full card images; the choice is remembered. Select rows (or tiles) to act
|
||||||
|
on several at once, including **Remove from library**, which removes every
|
||||||
|
copy; decks keep the cards, and one undo covers the whole batch.
|
||||||
- **Card Search** — the same filters over *every* card, owned or not. Select
|
- **Card Search** — the same filters over *every* card, owned or not. Select
|
||||||
rows (or tiles) and bulk-add them to a deck; one undo covers the whole batch.
|
rows (or tiles) and bulk-add them to a deck; one undo covers the whole batch.
|
||||||
- Deck contents offer the same List / Tiles toggle and hover previews.
|
- Deck contents offer the same List / Tiles toggle and hover previews.
|
||||||
|
|||||||
@@ -522,4 +522,38 @@ mod tests {
|
|||||||
// Card not in the deck.
|
// Card not in the deck.
|
||||||
assert!(cat.set_deck_card_printing(d.id, "lightning-bolt", Some("print-lightning-bolt")).is_err());
|
assert!(cat.set_deck_card_printing(d.id, "lightning-bolt", Some("print-lightning-bolt")).is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// What the Library tab's "Remove from library" and its undo rely on.
|
||||||
|
#[test]
|
||||||
|
fn removing_a_card_keeps_its_decks_and_can_be_put_back() {
|
||||||
|
let fx = test_support::make();
|
||||||
|
let cat = &fx.catalog;
|
||||||
|
add_sol_ring_printings(cat);
|
||||||
|
let groups = |cat: &Catalog| -> Vec<(String, String, i64)> {
|
||||||
|
let copies = cat.list_library_copies("sol-ring").unwrap();
|
||||||
|
copies.into_iter().map(|c| (c.scryfall_id, c.finish, c.quantity)).collect()
|
||||||
|
};
|
||||||
|
cat.set_owned("sol-ring", 5).unwrap();
|
||||||
|
cat.set_location("sol-ring", "Binder 2").unwrap();
|
||||||
|
cat.set_library_copy("sol-ring", "sol-2x2", "foil", 1).unwrap();
|
||||||
|
cat.set_library_copy("sol-ring", "sol-c21", "nonfoil", 2).unwrap();
|
||||||
|
let d = cat.create_deck("Rocks", None).unwrap();
|
||||||
|
cat.set_deck_card(d.id, "sol-ring", 1, false, false).unwrap();
|
||||||
|
let before = groups(cat);
|
||||||
|
|
||||||
|
cat.set_owned("sol-ring", 0).unwrap();
|
||||||
|
assert!(groups(cat).is_empty(), "going to 0 drops the groups");
|
||||||
|
assert!(cat.list_library().unwrap().iter().all(|c| c.card.oracle_id != "sol-ring"));
|
||||||
|
assert_eq!(cat.deck_contents(d.id).unwrap().len(), 1, "the deck keeps it");
|
||||||
|
|
||||||
|
cat.set_owned("sol-ring", 5).unwrap();
|
||||||
|
cat.set_location("sol-ring", "Binder 2").unwrap();
|
||||||
|
for (scryfall_id, finish, quantity) in &before {
|
||||||
|
cat.set_library_copy("sol-ring", scryfall_id, finish, *quantity).unwrap();
|
||||||
|
}
|
||||||
|
assert_eq!(groups(cat), before);
|
||||||
|
let card = cat.list_library().unwrap().into_iter().find(|c| c.card.oracle_id == "sol-ring").unwrap();
|
||||||
|
assert_eq!(card.quantity_owned, 5);
|
||||||
|
assert_eq!(card.location, "Binder 2");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -268,6 +268,43 @@ export function LibraryBrowser({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove every copy of the selected cards from the library. Decks keep
|
||||||
|
* them, the same as decrementing a card to 0.
|
||||||
|
*
|
||||||
|
* Going to 0 also drops each card's printing groups, so those are read
|
||||||
|
* first for the "Undo" to put back. A group whose printing has left the card
|
||||||
|
* database can't be recreated; its copies come back unspecified.
|
||||||
|
*/
|
||||||
|
async function bulkRemoveFromLibrary() {
|
||||||
|
const targets = rows.filter((r) => selected.has(r.oracle_id) && r.quantity_owned > 0);
|
||||||
|
if (targets.length === 0) return;
|
||||||
|
setBulkBusy(true);
|
||||||
|
try {
|
||||||
|
const copies = await Promise.all(targets.map((r) => api.listLibraryCopies(r.oracle_id)));
|
||||||
|
await Promise.all(targets.map((r) => api.setOwned(r.oracle_id, 0)));
|
||||||
|
setRefreshKey((k) => k + 1);
|
||||||
|
onOfferUndo(
|
||||||
|
`Removed ${targets.length} card${targets.length === 1 ? "" : "s"} from your library.`,
|
||||||
|
async () => {
|
||||||
|
await Promise.all(
|
||||||
|
targets.map(async (r, i) => {
|
||||||
|
await api.setOwned(r.oracle_id, r.quantity_owned);
|
||||||
|
await api.setLocation(r.oracle_id, r.location);
|
||||||
|
// One at a time: a group's position sets which is trimmed first.
|
||||||
|
for (const c of copies[i].filter((c) => c.printing)) {
|
||||||
|
await api.setLibraryCopy(r.oracle_id, c.scryfall_id, c.finish, c.quantity);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
setRefreshKey((k) => k + 1);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
setBulkBusy(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function exportSelected() {
|
async function exportSelected() {
|
||||||
const targets = rows.filter((r) => selected.has(r.oracle_id)).map((r) => r.oracle_id);
|
const targets = rows.filter((r) => selected.has(r.oracle_id)).map((r) => r.oracle_id);
|
||||||
if (targets.length === 0) return;
|
if (targets.length === 0) return;
|
||||||
@@ -534,6 +571,14 @@ export function LibraryBrowser({
|
|||||||
>
|
>
|
||||||
Export selected
|
Export selected
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
className="danger"
|
||||||
|
onClick={bulkRemoveFromLibrary}
|
||||||
|
disabled={bulkBusy}
|
||||||
|
title="Remove every copy of the selected cards. Decks keep them."
|
||||||
|
>
|
||||||
|
Remove from library
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<span className="spacer" />
|
<span className="spacer" />
|
||||||
<button onClick={() => setSelected(new Set())} disabled={bulkBusy}>
|
<button onClick={() => setSelected(new Set())} disabled={bulkBusy}>
|
||||||
|
|||||||
Reference in New Issue
Block a user