diff --git a/crates/mtgc-core/src/filters.rs b/crates/mtgc-core/src/filters.rs index 8ee4038..3cf0a60 100644 --- a/crates/mtgc-core/src/filters.rs +++ b/crates/mtgc-core/src/filters.rs @@ -53,6 +53,9 @@ fn push_card_filters( if filter.exclude_universes_beyond { sql.push_str(" AND oc.universes_beyond = 0"); } + if filter.only_universes_beyond { + sql.push_str(" AND oc.universes_beyond = 1"); + } if filter.exclude_digital_only { sql.push_str(" AND oc.digital_only = 0"); } @@ -387,7 +390,7 @@ mod tests { } #[test] - fn excluding_universes_beyond_hides_flagged_cards_only_when_asked() { + fn universes_beyond_filters_hide_or_keep_only_flagged_cards() { let fx = owned_fixture(); // Stand in for what the ingest derives: a card that exists only as // Universes Beyond. @@ -410,6 +413,16 @@ mod tests { // Off unless asked for — this must not quietly change existing searches. let all = names(&fx.catalog.search_library(&LibraryFilter::default()).unwrap()); assert!(all.contains(&"Serra Angel".to_string())); + + let f = LibraryFilter { + only_universes_beyond: true, + ..Default::default() + }; + assert_eq!( + names(&fx.catalog.search_library(&f).unwrap()), + vec!["Serra Angel"], + "only the flagged card is kept" + ); } #[test] diff --git a/crates/mtgc-core/src/model.rs b/crates/mtgc-core/src/model.rs index 803c069..7568122 100644 --- a/crates/mtgc-core/src/model.rs +++ b/crates/mtgc-core/src/model.rs @@ -421,6 +421,10 @@ pub struct LibraryFilter { /// existed, the same way the other reference-schema additions degrade. #[serde(default)] pub exclude_universes_beyond: bool, + /// The opposite: only cards that exist only as Universes Beyond. Inert + /// against an older `reference.db` in the same way. + #[serde(default)] + pub only_universes_beyond: bool, /// Hide cards that exist only on Arena/MTGO (Alchemy rebalances and the /// like) and so can never be in a physical collection. #[serde(default)] diff --git a/src/api.ts b/src/api.ts index 4e9cf29..8a8292b 100644 --- a/src/api.ts +++ b/src/api.ts @@ -201,6 +201,8 @@ export interface LibraryFilter { * Warhammer 40,000, Marvel, …). Ordinary cards reprinted into a UB set are * kept. Needs card data downloaded after this shipped; inert otherwise. */ exclude_universes_beyond?: boolean; + /** The opposite: only cards that exist only as Universes Beyond. */ + only_universes_beyond?: boolean; /** Hide Arena/MTGO-only cards (Alchemy rebalances) — they can't be owned on * paper. Needs card data downloaded after this shipped; inert otherwise. */ exclude_digital_only?: boolean; diff --git a/src/components.tsx b/src/components.tsx index 8be4eff..e335234 100644 --- a/src/components.tsx +++ b/src/components.tsx @@ -2391,6 +2391,8 @@ const ICON_PACKAGE = icon( */ export type CardCriteria = { excludeUb: boolean; + /** Never set together with `excludeUb`: the toggle cycles off → hide → only. */ + onlyUb: boolean; excludeDigital: boolean; excludeFunny: boolean; priceMin: string; @@ -2410,6 +2412,7 @@ export type CardCriteria = { export const EMPTY_CRITERIA: CardCriteria = { excludeUb: false, + onlyUb: false, excludeDigital: false, excludeFunny: false, priceMin: "", @@ -2451,6 +2454,7 @@ const num = (s: string): number | null => (s.trim() === "" ? null : Number(s)); export function applyCardCriteria(c: CardCriteria): Partial { return { exclude_universes_beyond: c.excludeUb, + only_universes_beyond: c.onlyUb, exclude_digital_only: c.excludeDigital, exclude_funny: c.excludeFunny, price_min: num(c.priceMin), @@ -2474,10 +2478,12 @@ export function applyCardCriteria(c: CardCriteria): Partial { * The three "exclude" toggles deliberately don't count: they refine a result * set rather than select one, so ticking "hide Un-sets" on its own shouldn't * make Card Search pull every card in the database. Choosing a format, a - * power range, or "not owned" is a real query, and does. + * power range, "not owned" or "only Universes Beyond" is a real query, and + * does. */ export function criteriaActive(c: CardCriteria): boolean { return ( + c.onlyUb || c.canBeCommander || c.vanillaOnly || c.notOwned || @@ -2611,13 +2617,29 @@ export function CardCriteriaFields({
{/* Exclusions read as "no X" — tinted with the danger colour when active, so removing things looks different from selecting them. */} + {/* Three states: off → hide → only → off. "Only" looks like the + other "only" toggles rather than an exclusion. */} onChange({ excludeUb: !c.excludeUb })} + label={ + c.onlyUb + ? "Only Universes Beyond (click to show all)" + : c.excludeUb + ? "Hiding Universes Beyond (click for only them)" + : "Hide Universes Beyond" + } + hint="Cards that only exist as another property — Lord of the Rings, Warhammer 40,000, Marvel. Normal cards reprinted in those sets count as normal cards. Click again to show only these cards, and once more to show everything." + exclude={!c.onlyUb} + on={c.excludeUb || c.onlyUb} + onToggle={() => + onChange( + c.onlyUb + ? { excludeUb: false, onlyUb: false } + : c.excludeUb + ? { excludeUb: false, onlyUb: true } + : { excludeUb: true, onlyUb: false }, + ) + } />