Add a "not owned" quick filter and horizontal tile view to Card Search

"Not owned" moves into the shared Quick filters icon-toggle row (hidden on
the Library tab, where it'd always be a no-op since that view is owned-only
already) instead of a standalone button.

Tiles view gains a "Horizontal" toggle that rotates actually-horizontal
cards (Battles, Planes, Phenomena) into their natural landscape orientation
via a container-query-sized wrapper, spanning two grid columns so the
rotated card isn't squeezed into a single portrait column's width and left
too small to read.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-22 19:14:27 -05:00
parent e3725cee55
commit df2cbb372a
7 changed files with 149 additions and 12 deletions

View File

@@ -254,6 +254,10 @@ impl Catalog {
let mut vals: Vec<Value> = Vec::new();
push_card_filters(&mut sql, &mut vals, filter, self.has_universes_beyond, self.has_vanilla);
if filter.not_owned {
sql.push_str(" AND COALESCE(lc.quantity_owned, 0) = 0");
}
sql.push_str(" ORDER BY oc.edhrec_rank IS NULL, oc.edhrec_rank, oc.name COLLATE NOCASE LIMIT ?");
vals.push(Value::Integer(limit as i64));
@@ -765,6 +769,31 @@ mod tests {
assert_eq!(bolt.quantity_owned, 0);
}
#[test]
fn search_reference_not_owned_excludes_owned_cards() {
let fx = test_support::make();
let cat = &fx.catalog;
cat.add_owned("sol-ring", 1).unwrap();
let unowned = cat.search_reference(&LibraryFilter {
not_owned: true,
..Default::default()
}, 50).unwrap();
assert!(!unowned.iter().any(|c| c.card.oracle_id == "sol-ring"));
// The other four fixture cards are all unowned and still show up.
assert_eq!(unowned.len(), 4);
// A card set to 0 owned (never actually acquired, or reset back to 0)
// counts as not-owned too, not just "no library_cards row at all".
cat.add_owned("lightning-bolt", 1).unwrap();
cat.set_owned("lightning-bolt", 0).unwrap();
let unowned = cat.search_reference(&LibraryFilter {
not_owned: true,
..Default::default()
}, 50).unwrap();
assert!(unowned.iter().any(|c| c.card.oracle_id == "lightning-bolt"));
}
#[test]
fn search_reference_respects_limit() {
let fx = test_support::make();

View File

@@ -341,6 +341,12 @@ pub struct LibraryFilter {
pub power_max: Option<f64>,
pub toughness_min: Option<f64>,
pub toughness_max: Option<f64>,
/// Only cards with zero owned copies. Meaningless against
/// [`Catalog::search_library`] (which already only returns owned cards),
/// so it's only honored by [`Catalog::search_reference`] — the Card
/// Search tab's "Not owned" quick filter.
#[serde(default)]
pub not_owned: bool,
}
#[cfg(test)]