Initial commit: MTG personal library catalog (Tauri v2 + React/TS)
Full working app with:
- Rust core (mtgc-core): Scryfall ingester, nucleo fuzzy search, SQLite two-DB
architecture (reference.db + library.db via ATTACH), criteria filters, deck ops
- Tauri v2 shell exposing the core as typed IPC commands
- React/TS frontend: card entry, library browser, deck editor
- Features: fuzzy card search, color/CMC/type/commander-identity filters,
tile/list view toggle, hover card previews, deck format + commander support,
per-card location field, grouping by type/subtype/CMC/color, localStorage
persistence for active tab/deck/grouping, Discord-inspired dark UI (#C8553D accent)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-07 19:37:56 -05:00
|
|
|
//! App state: the open [`Catalog`] and fuzzy [`NameIndex`], plus the db paths.
|
|
|
|
|
|
|
|
|
|
use std::path::PathBuf;
|
|
|
|
|
|
|
|
|
|
use mtgc_core::{Catalog, NameIndex};
|
|
|
|
|
|
|
|
|
|
/// Managed application state, guarded by a `Mutex` in Tauri's state store.
|
|
|
|
|
pub struct AppState {
|
|
|
|
|
pub library_path: PathBuf,
|
|
|
|
|
pub reference_path: PathBuf,
|
|
|
|
|
pub catalog: Option<Catalog>,
|
|
|
|
|
pub index: Option<NameIndex>,
|
2026-07-07 20:22:31 -05:00
|
|
|
/// Set while `update_card_data` is running; `cancel_update_card_data`
|
|
|
|
|
/// flips it to abort the in-progress ingest.
|
|
|
|
|
pub ingest_cancel: Option<mtgc_core::ingest::CancelToken>,
|
Initial commit: MTG personal library catalog (Tauri v2 + React/TS)
Full working app with:
- Rust core (mtgc-core): Scryfall ingester, nucleo fuzzy search, SQLite two-DB
architecture (reference.db + library.db via ATTACH), criteria filters, deck ops
- Tauri v2 shell exposing the core as typed IPC commands
- React/TS frontend: card entry, library browser, deck editor
- Features: fuzzy card search, color/CMC/type/commander-identity filters,
tile/list view toggle, hover card previews, deck format + commander support,
per-card location field, grouping by type/subtype/CMC/color, localStorage
persistence for active tab/deck/grouping, Discord-inspired dark UI (#C8553D accent)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-07 19:37:56 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl AppState {
|
|
|
|
|
/// Initialize with default paths under the app data dir and open the catalog
|
|
|
|
|
/// if a reference db already exists.
|
|
|
|
|
pub fn new(data_dir: PathBuf) -> Self {
|
|
|
|
|
let mut s = Self {
|
|
|
|
|
library_path: data_dir.join("library.db"),
|
|
|
|
|
reference_path: data_dir.join("reference.db"),
|
|
|
|
|
catalog: None,
|
|
|
|
|
index: None,
|
2026-07-07 20:22:31 -05:00
|
|
|
ingest_cancel: None,
|
Initial commit: MTG personal library catalog (Tauri v2 + React/TS)
Full working app with:
- Rust core (mtgc-core): Scryfall ingester, nucleo fuzzy search, SQLite two-DB
architecture (reference.db + library.db via ATTACH), criteria filters, deck ops
- Tauri v2 shell exposing the core as typed IPC commands
- React/TS frontend: card entry, library browser, deck editor
- Features: fuzzy card search, color/CMC/type/commander-identity filters,
tile/list view toggle, hover card previews, deck format + commander support,
per-card location field, grouping by type/subtype/CMC/color, localStorage
persistence for active tab/deck/grouping, Discord-inspired dark UI (#C8553D accent)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-07 19:37:56 -05:00
|
|
|
};
|
|
|
|
|
// Best-effort open; failures just leave the catalog unloaded.
|
|
|
|
|
let _ = s.reopen();
|
|
|
|
|
s
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// (Re)open the catalog + rebuild the name index. Only opens when a reference
|
|
|
|
|
/// db exists — attaching a non-existent file would create an empty (tableless)
|
|
|
|
|
/// db and make every query fail.
|
|
|
|
|
pub fn reopen(&mut self) -> Result<(), String> {
|
|
|
|
|
if self.reference_path.exists() {
|
|
|
|
|
let catalog = Catalog::open(&self.library_path, &self.reference_path)
|
|
|
|
|
.map_err(|e| e.to_string())?;
|
|
|
|
|
let index = catalog.build_name_index().map_err(|e| e.to_string())?;
|
|
|
|
|
self.index = Some(index);
|
|
|
|
|
self.catalog = Some(catalog);
|
|
|
|
|
} else {
|
|
|
|
|
self.catalog = None;
|
|
|
|
|
self.index = None;
|
|
|
|
|
}
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn catalog(&self) -> Result<&Catalog, String> {
|
|
|
|
|
self.catalog
|
|
|
|
|
.as_ref()
|
|
|
|
|
.ok_or_else(|| "Card data not loaded yet — click 'Download card data'.".to_string())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn index(&self) -> Result<&NameIndex, String> {
|
|
|
|
|
self.index
|
|
|
|
|
.as_ref()
|
|
|
|
|
.ok_or_else(|| "Card data not loaded yet — click 'Download card data'.".to_string())
|
|
|
|
|
}
|
|
|
|
|
}
|