diff --git a/CHANGELOG.md b/CHANGELOG.md index 3625865..d31e3b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,6 +41,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- **Exile-aware nation filter (web UI).** Filtering the collection by nation now + includes that nation's exile cards by default — cards of another nation that + can be played in its decks (for example the Polish *T-34 76 PL* under Soviet), + matching the game client. A new **exiles** toggle (on by default) restricts + the view to a nation's own cards. The toggle is inert when no nation is + selected. - **Web UI Sync flow**: a **Sync** button in the page header opens a confirmation modal, runs the same fetch + diff the CLI does (with a spinner during the request), and shows a categorized preview (new / diff --git a/CONTEXT.md b/CONTEXT.md index 4bebb0b..f6d9e81 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -56,8 +56,12 @@ the game, it does not police it. **Exile**: A cross-faction link on a card: the card belongs to one faction but may be played in decks of another faction (its exile faction), reflecting the -game's exile-forces mechanic. Deck import falls back to the exile link when -a card is not found under its own faction. +game's exile-forces mechanic. The card keeps its own faction (a Polish +Exile card is still Poland); the link only grants a second faction it can +be used by. Deck import falls back to the exile link when a card is not +found under its own faction. Collection filtering by faction includes a +faction's Exile cards by default (as the game client does), toggleable in +the web UI. **Diff**: The comparison of card content between the local database and a fresh API diff --git a/README.md b/README.md index 9daa38c..b279975 100644 --- a/README.md +++ b/README.md @@ -183,6 +183,13 @@ browsing, sorting, filters, card details, quantity editing, and full **Sync** and **Export** flows so the browser is the only surface needed to manage a collection. +Filtering by nation includes that nation's **exile** cards by default — cards +that belong to another nation but can be played in its decks (for example the +Polish *T-34 76 PL* under Soviet), the way the game client shows them. Uncheck +the **exiles** toggle to restrict the view to a nation's own cards. The toggle +has no effect when no nation is selected, since every card is already listed +under its own nation. + Click **Edit** to enable quantity changes. Quantity writes are server-side validated by rarity caps: diff --git a/kardscm/locales/de.toml b/kardscm/locales/de.toml index 53e4e90..cd59be2 100644 --- a/kardscm/locales/de.toml +++ b/kardscm/locales/de.toml @@ -93,6 +93,7 @@ page_title = "Kards Kollektion" search_placeholder = "nach Name suchen…" toggle_spawnable = "Erstellbare" toggle_reserved = "Reservierte" +toggle_exiles = "exiles" toggle_owned = "Nur im Besitz" col_cost = "Kosten" card_id_label = "Karten-ID" diff --git a/kardscm/locales/en.toml b/kardscm/locales/en.toml index 9624cb4..5644e92 100644 --- a/kardscm/locales/en.toml +++ b/kardscm/locales/en.toml @@ -101,6 +101,7 @@ page_title = "kardscm collection" search_placeholder = "search by name…" toggle_spawnable = "spawnable" toggle_reserved = "reserved" +toggle_exiles = "exiles" toggle_owned = "only owned" filter_abilities = "Abilities" filter_extra_abilities = "Extra abilities" diff --git a/kardscm/locales/es.toml b/kardscm/locales/es.toml index 9752d21..275a493 100644 --- a/kardscm/locales/es.toml +++ b/kardscm/locales/es.toml @@ -92,6 +92,7 @@ page_title = "colección kardscm" search_placeholder = "buscar por nombre…" toggle_spawnable = "invocable" toggle_reserved = "reservado" +toggle_exiles = "exiles" toggle_owned = "solo en posesión" col_cost = "Costo" card_id_label = "ID de carta" diff --git a/kardscm/locales/fr.toml b/kardscm/locales/fr.toml index 735c48c..604dc71 100644 --- a/kardscm/locales/fr.toml +++ b/kardscm/locales/fr.toml @@ -92,6 +92,7 @@ page_title = "kardscm collection" search_placeholder = "search by name…" toggle_spawnable = "spawnable" toggle_reserved = "reserved" +toggle_exiles = "exiles" toggle_owned = "only owned" col_cost = "Cost" card_id_label = "cardId" diff --git a/kardscm/locales/it.toml b/kardscm/locales/it.toml index 0186f00..ecc6e36 100644 --- a/kardscm/locales/it.toml +++ b/kardscm/locales/it.toml @@ -92,6 +92,7 @@ page_title = "collezione kardscm" search_placeholder = "cerca per nome…" toggle_spawnable = "generabile" toggle_reserved = "riservato" +toggle_exiles = "exiles" toggle_owned = "solo possedute" col_cost = "Costo" card_id_label = "ID carta" diff --git a/kardscm/locales/ja.toml b/kardscm/locales/ja.toml index 92b2b45..812ff7d 100644 --- a/kardscm/locales/ja.toml +++ b/kardscm/locales/ja.toml @@ -93,6 +93,7 @@ page_title = "kardscm collection" search_placeholder = "search by name…" toggle_spawnable = "spawnable" toggle_reserved = "reserved" +toggle_exiles = "exiles" toggle_owned = "only owned" col_cost = "Cost" card_id_label = "cardId" diff --git a/kardscm/locales/ko.toml b/kardscm/locales/ko.toml index 4d4ad38..2938197 100644 --- a/kardscm/locales/ko.toml +++ b/kardscm/locales/ko.toml @@ -92,6 +92,7 @@ page_title = "kardscm collection" search_placeholder = "search by name…" toggle_spawnable = "spawnable" toggle_reserved = "reserved" +toggle_exiles = "exiles" toggle_owned = "only owned" col_cost = "Cost" card_id_label = "cardId" diff --git a/kardscm/locales/pl.toml b/kardscm/locales/pl.toml index 00425db..d78daa7 100644 --- a/kardscm/locales/pl.toml +++ b/kardscm/locales/pl.toml @@ -92,6 +92,7 @@ page_title = "kolekcja kardscm" search_placeholder = "szukaj po nazwie…" toggle_spawnable = "do wystawienia" toggle_reserved = "zarezerwowane" +toggle_exiles = "exiles" toggle_owned = "tylko posiadane" col_cost = "Koszt" card_id_label = "cardId" diff --git a/kardscm/locales/pt.toml b/kardscm/locales/pt.toml index 5aad38a..5c09eb5 100644 --- a/kardscm/locales/pt.toml +++ b/kardscm/locales/pt.toml @@ -92,6 +92,7 @@ page_title = "coleção kardscm" search_placeholder = "buscar por nome…" toggle_spawnable = "gerável" toggle_reserved = "reservado" +toggle_exiles = "exiles" toggle_owned = "apenas possuídas" col_cost = "Custo" card_id_label = "ID da carta" diff --git a/kardscm/locales/ru.toml b/kardscm/locales/ru.toml index 12e8794..0299904 100644 --- a/kardscm/locales/ru.toml +++ b/kardscm/locales/ru.toml @@ -92,6 +92,7 @@ page_title = "Коллекция kardscm" search_placeholder = "поиск по названию…" toggle_spawnable = "спаунятся" toggle_reserved = "в резерве" +toggle_exiles = "в изгнании" toggle_owned = "только мои" filter_abilities = "Способности" filter_extra_abilities = "Доп-способности" diff --git a/kardscm/locales/zh-Hant.toml b/kardscm/locales/zh-Hant.toml index 3f7b11f..d435f14 100644 --- a/kardscm/locales/zh-Hant.toml +++ b/kardscm/locales/zh-Hant.toml @@ -92,6 +92,7 @@ page_title = "kardscm collection" search_placeholder = "search by name…" toggle_spawnable = "spawnable" toggle_reserved = "reserved" +toggle_exiles = "exiles" toggle_owned = "only owned" col_cost = "Cost" card_id_label = "cardId" diff --git a/kardscm/locales/zh.toml b/kardscm/locales/zh.toml index 94bedb3..f8c4fc5 100644 --- a/kardscm/locales/zh.toml +++ b/kardscm/locales/zh.toml @@ -92,6 +92,7 @@ page_title = "kardscm collection" search_placeholder = "search by name…" toggle_spawnable = "spawnable" toggle_reserved = "reserved" +toggle_exiles = "exiles" toggle_owned = "only owned" col_cost = "Cost" card_id_label = "cardId" diff --git a/kardscm/web/deps.py b/kardscm/web/deps.py index dd407d0..722bf25 100644 --- a/kardscm/web/deps.py +++ b/kardscm/web/deps.py @@ -40,9 +40,16 @@ def card_filters_dep( q: str = Query(default=""), spawnable: bool = Query(default=False), reserved: bool = Query(default=False), + exiles: list[str] = Query(default=[]), owned: bool = Query(default=False), ) -> CardFilters: - """FastAPI dependency that builds CardFilters from query string params.""" + """FastAPI dependency that builds CardFilters from query string params. + + `exiles` arrives as a list because the exile toggle defaults to ON: the + template pairs a hidden ``exiles=false`` with the checkbox's ``exiles=true`` + so an unchecked box still submits a value. Absent entirely (a bare page + load with no form) means "use the default", which is ON. + """ return CardFilters( factions=factions, types=types, @@ -54,6 +61,7 @@ def card_filters_dep( text_query=q.strip(), include_spawnable=spawnable, include_reserved=reserved, + include_exiles=("true" in exiles) if exiles else True, owned_only=owned, ) diff --git a/kardscm/web/queries.py b/kardscm/web/queries.py index a6977b1..eec60ff 100644 --- a/kardscm/web/queries.py +++ b/kardscm/web/queries.py @@ -50,6 +50,7 @@ class CardFilters: text_query: str = "" include_spawnable: bool = False include_reserved: bool = False + include_exiles: bool = True owned_only: bool = False @@ -70,8 +71,17 @@ def _build_where(filters: CardFilters, locale_key: str) -> tuple[list[str], list if filters.factions: placeholders = ",".join("?" for _ in filters.factions) - where.append(f"faction IN ({placeholders})") - params.extend(filters.factions) + if filters.include_exiles: + # An exile card keeps its own faction but may be played by its + # exile faction, so a nation view surfaces it too — matching the + # game client. Without a nation filter this branch is moot: every + # card is already listed under its own faction. + where.append(f"(faction IN ({placeholders}) OR exile IN ({placeholders}))") + params.extend(filters.factions) + params.extend(filters.factions) + else: + where.append(f"faction IN ({placeholders})") + params.extend(filters.factions) if filters.types: placeholders = ",".join("?" for _ in filters.types) diff --git a/kardscm/web/templates/_filters.html b/kardscm/web/templates/_filters.html index e7c887d..efe5f55 100644 --- a/kardscm/web/templates/_filters.html +++ b/kardscm/web/templates/_filters.html @@ -104,6 +104,13 @@ {% if filters.include_reserved %}checked{% endif %}> {{ ui.toggle_reserved }} +