Decks tab: a deck list page, with the builder on its own screen

The deck list is no longer a sidebar pinned beside the builder; on every
screen size the Decks tab opens to a deck list page (DeckList), and picking
a deck opens its builder full-width with an "All decks" back button. The
list shows each deck as an art tile (or a compact row, toggleable) with its
colors, size against its format, owned %, cost and legality, and can be
searched, filtered by format and sorted (recently opened, name, format,
size, cost, owned %). Rename/duplicate/export/delete live in each deck's
menu and beside the builder's title; New deck now asks for a format too,
defaulting to the last one used.

A deck's cover art is the commander, else its priciest nonland, unless a
card is chosen with "Use as cover" in the card preview. The cover is stored
in decks.cover_oracle_id (migration 0007) and synced as DeckDoc.cover; a
document carrying one is version 3, so the sync server must be updated
before clients publish covers. The list's stats come from one
all_deck_contents query, summarised with the builder's own analysis code,
now in deckAnalysis.ts.

Closes #9
Closes #11
Closes #12
This commit is contained in:
2026-10-25 15:04:41 -05:00
parent fae1aa2b05
commit d286d3420c
15 changed files with 2113 additions and 970 deletions

View File

@@ -2,6 +2,7 @@
//!
//! JS passes camelCase argument keys; Tauri maps them to these snake_case params.
use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::Mutex;
@@ -232,6 +233,13 @@ pub fn deck_contents(db: Db, deck_id: i64) -> Res<Vec<DeckEntry>> {
s.catalog()?.deck_contents(deck_id).map_err(|e| e.to_string())
}
/// Every deck's contents keyed by deck id, for the deck list's summaries.
#[tauri::command]
pub fn all_deck_contents(db: Db) -> Res<HashMap<i64, Vec<DeckEntry>>> {
let s = db.lock().unwrap();
s.catalog()?.all_deck_contents().map_err(|e| e.to_string())
}
#[tauri::command]
pub fn suggest_cards(
db: Db,
@@ -321,6 +329,14 @@ pub fn set_deck_format(db: Db, deck_id: i64, format: String) -> Res<()> {
.map_err(|e| e.to_string())
}
#[tauri::command]
pub fn set_deck_cover(db: Db, deck_id: i64, oracle_id: Option<String>) -> Res<()> {
let s = db.lock().unwrap();
s.catalog()?
.set_deck_cover(deck_id, oracle_id.as_deref())
.map_err(|e| e.to_string())
}
#[tauri::command]
pub fn set_deck_commander(db: Db, deck_id: i64, oracle_id: Option<String>) -> Res<()> {
let s = db.lock().unwrap();

View File

@@ -107,11 +107,13 @@ pub fn run() {
commands::rename_deck,
commands::duplicate_deck,
commands::set_deck_format,
commands::set_deck_cover,
commands::set_deck_commander,
commands::set_deck_commanders,
commands::search_partners,
commands::set_deck_card,
commands::deck_contents,
commands::all_deck_contents,
commands::suggest_cards,
commands::card_decks,
commands::list_printings,