Add a Card Search tab and fix horizontal-card (Battle/Plane) preview orientation

Card Search browses the whole downloaded card universe (not just owned
cards), reusing the Library tab's filter/group/sort/list/tiles UI; results
show ownership and offer quick "own" and "add to deck" actions.

Battles, Planes, and Phenomena store their Scryfall image portrait-shaped
with the content itself rotated, so hover/click previews now detect this
(a new `horizontal` flag threaded through ingest and every card query, with
graceful fallback for a reference.db built before the column existed) and
rotate the image back into place. Single-faced and non-DFC cases are
verified fixed; a double-faced Battle's front face still needs another pass.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-22 20:55:17 -05:00
parent 3c625a9830
commit 2b361c4a85
19 changed files with 1076 additions and 102 deletions

View File

@@ -311,7 +311,7 @@ impl Catalog {
let mut sql = format!(
"SELECT oc.oracle_id, oc.name, oc.mana_value, oc.colors, oc.color_identity, oc.type_line,
{image}, {back}, oc.produced_mana, oc.produces_colorless, oc.legalities, {price},
oc.themes, oc.edhrec_rank, COALESCE(lc.quantity_owned, 0)
oc.themes, oc.edhrec_rank, COALESCE(lc.quantity_owned, 0), {horizontal}
FROM {REF_DB}.oracle_cards oc
LEFT JOIN library_cards lc ON lc.oracle_id = oc.oracle_id
WHERE oc.oracle_id NOT IN (SELECT oracle_id FROM deck_cards WHERE deck_id = ?)
@@ -320,6 +320,7 @@ impl Catalog {
image = crate::db::image_subquery("image_uri"),
back = crate::db::image_subquery("back_image_uri"),
price = crate::db::price_subquery(),
horizontal = self.horizontal_col(),
);
let mut vals: Vec<Value> = vec![
Value::Integer(deck_id),
@@ -372,6 +373,7 @@ impl Catalog {
produces_colorless: r.get::<_, i64>(9)? != 0,
legalities: Legalities(r.get::<_, i64>(10)?),
price_usd: r.get::<_, Option<f64>>(11)?.unwrap_or_default(),
horizontal: r.get::<_, i64>(15)? != 0,
},
themes: r.get(12)?,
edhrec_rank: r.get(13)?,