2026-07-20 00:21:22 -05:00
|
|
|
|
import { open, save } from "@tauri-apps/plugin-dialog";
|
Card preview modal, library quantity steppers, and UI polish
- Fix keyboard-nav selection highlight (search results, deck list): the
Discord-styling rewrite dropped the --panel-2 CSS variable it relied
on, so ↑/↓ moved selection but rendered no visible highlight.
- Add click-to-preview: clicking a card thumbnail/tile now opens a
larger centered modal (list and tile views), with a flip button for
double-faced cards' back face. Hover preview is unchanged and clears
when a click-preview opens.
- Add a QtyStepper component (rounded [− N +] pill, with an `overlay`
variant for tile corners) and use it consistently for owned-copy
counts on Add Cards, and the Library list and tile views.
- Add matching +/- steppers to the Library views; decrementing to 0
dims the row for a 5s grace period (still fully interactive) before
it drops out, so an accidental removal can be undone with +.
- Topbar: fill remaining width with the library path (no more premature
truncation), add icons to Open/New Library and Download/Update card
data, and drop the trailing "…" from the library buttons.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-07 20:55:18 -05:00
|
|
|
|
import { useEffect, useRef, useState } from "react";
|
2026-07-20 00:21:22 -05:00
|
|
|
|
import { api, Deck, LibraryCard, LibraryFacets, LibraryFilter, LibraryImportResult } from "../api";
|
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
|
|
|
|
import {
|
|
|
|
|
|
CardThumb,
|
|
|
|
|
|
CardTile,
|
|
|
|
|
|
ColorPips,
|
|
|
|
|
|
ColorToggles,
|
|
|
|
|
|
GroupBySelect,
|
|
|
|
|
|
GroupMode,
|
2026-07-20 00:21:22 -05:00
|
|
|
|
InfoDialog,
|
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
|
|
|
|
LocationField,
|
2026-07-08 08:20:09 -05:00
|
|
|
|
PromptDialog,
|
Card preview modal, library quantity steppers, and UI polish
- Fix keyboard-nav selection highlight (search results, deck list): the
Discord-styling rewrite dropped the --panel-2 CSS variable it relied
on, so ↑/↓ moved selection but rendered no visible highlight.
- Add click-to-preview: clicking a card thumbnail/tile now opens a
larger centered modal (list and tile views), with a flip button for
double-faced cards' back face. Hover preview is unchanged and clears
when a click-preview opens.
- Add a QtyStepper component (rounded [− N +] pill, with an `overlay`
variant for tile corners) and use it consistently for owned-copy
counts on Add Cards, and the Library list and tile views.
- Add matching +/- steppers to the Library views; decrementing to 0
dims the row for a 5s grace period (still fully interactive) before
it drops out, so an accidental removal can be undone with +.
- Topbar: fill remaining width with the library path (no more premature
truncation), add icons to Open/New Library and Download/Update card
data, and drop the trailing "…" from the library buttons.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-07 20:55:18 -05:00
|
|
|
|
QtyStepper,
|
2026-07-22 20:55:17 -05:00
|
|
|
|
SortBySelect,
|
|
|
|
|
|
SortDir,
|
|
|
|
|
|
SortMode,
|
2026-07-08 23:54:47 -05:00
|
|
|
|
TagInput,
|
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
|
|
|
|
ViewToggle,
|
|
|
|
|
|
groupCards,
|
2026-07-22 20:55:17 -05:00
|
|
|
|
sortCards,
|
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
|
|
|
|
usePersistedState,
|
|
|
|
|
|
useViewMode,
|
|
|
|
|
|
} from "../components";
|
|
|
|
|
|
|
Card preview modal, library quantity steppers, and UI polish
- Fix keyboard-nav selection highlight (search results, deck list): the
Discord-styling rewrite dropped the --panel-2 CSS variable it relied
on, so ↑/↓ moved selection but rendered no visible highlight.
- Add click-to-preview: clicking a card thumbnail/tile now opens a
larger centered modal (list and tile views), with a flip button for
double-faced cards' back face. Hover preview is unchanged and clears
when a click-preview opens.
- Add a QtyStepper component (rounded [− N +] pill, with an `overlay`
variant for tile corners) and use it consistently for owned-copy
counts on Add Cards, and the Library list and tile views.
- Add matching +/- steppers to the Library views; decrementing to 0
dims the row for a 5s grace period (still fully interactive) before
it drops out, so an accidental removal can be undone with +.
- Topbar: fill remaining width with the library path (no more premature
truncation), add icons to Open/New Library and Download/Update card
data, and drop the trailing "…" from the library buttons.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-07 20:55:18 -05:00
|
|
|
|
// How long a card that's been decremented to 0 lingers (dimmed, still
|
|
|
|
|
|
// adjustable) before actually dropping out of the library view — gives the
|
|
|
|
|
|
// user a window to undo an accidental removal by clicking + again.
|
|
|
|
|
|
const REMOVAL_GRACE_MS = 5000;
|
|
|
|
|
|
|
2026-07-08 23:54:47 -05:00
|
|
|
|
const NO_FACETS: LibraryFacets = { types: [], subtypes: [], keywords: [] };
|
|
|
|
|
|
|
2026-07-21 21:04:36 -05:00
|
|
|
|
/**
|
|
|
|
|
|
* Browse & filter the owned library by name and card criteria.
|
|
|
|
|
|
*
|
|
|
|
|
|
* `onOfferUndo` is threaded in from `App` (rather than owning an `UndoBar`
|
|
|
|
|
|
* here) so the resulting "Undo" is also reachable from the command palette,
|
|
|
|
|
|
* which is mounted at the app level and outlives this view across tab
|
|
|
|
|
|
* switches.
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function LibraryBrowser({
|
|
|
|
|
|
onOfferUndo,
|
|
|
|
|
|
}: {
|
|
|
|
|
|
onOfferUndo: (message: string, run: () => void | Promise<void>) => void;
|
|
|
|
|
|
}) {
|
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
|
|
|
|
const [text, setText] = useState("");
|
|
|
|
|
|
const [commander, setCommander] = useState(0);
|
|
|
|
|
|
const [colorsInclude, setColorsInclude] = useState(0);
|
|
|
|
|
|
const [cmcMin, setCmcMin] = useState("");
|
|
|
|
|
|
const [cmcMax, setCmcMax] = useState("");
|
2026-07-08 23:54:47 -05:00
|
|
|
|
const [types, setTypes] = useState<string[]>([]);
|
|
|
|
|
|
const [subtypes, setSubtypes] = useState<string[]>([]);
|
|
|
|
|
|
const [keywords, setKeywords] = useState<string[]>([]);
|
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
|
|
|
|
const [inDeck, setInDeck] = useState<number | "">("");
|
2026-07-07 21:35:09 -05:00
|
|
|
|
const [location, setLocation] = useState("");
|
2026-07-08 23:54:47 -05:00
|
|
|
|
const [oracleText, setOracleText] = useState("");
|
2026-08-02 16:10:25 -05:00
|
|
|
|
// Collapsed by default (relevant on narrow/mobile screens only — see
|
|
|
|
|
|
// .filters-more's CSS, which is always expanded above that breakpoint
|
|
|
|
|
|
// regardless of this).
|
|
|
|
|
|
const [filtersExpanded, setFiltersExpanded] = useState(false);
|
2026-07-08 23:54:47 -05:00
|
|
|
|
const [facets, setFacets] = useState<LibraryFacets>(NO_FACETS);
|
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
|
|
|
|
|
|
|
|
|
|
const [decks, setDecks] = useState<Deck[]>([]);
|
|
|
|
|
|
const [rows, setRows] = useState<LibraryCard[]>([]);
|
|
|
|
|
|
const [view, setView] = useViewMode("library-view");
|
|
|
|
|
|
const [groupBy, setGroupBy] = usePersistedState<GroupMode>("library-group", "none");
|
2026-07-22 20:55:17 -05:00
|
|
|
|
const [sortBy, setSortBy] = usePersistedState<SortMode>("library-sort", "name");
|
|
|
|
|
|
const [sortDir, setSortDir] = usePersistedState<SortDir>("library-sort-dir", "asc");
|
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
|
|
|
|
const [refreshKey, setRefreshKey] = useState(0);
|
Card preview modal, library quantity steppers, and UI polish
- Fix keyboard-nav selection highlight (search results, deck list): the
Discord-styling rewrite dropped the --panel-2 CSS variable it relied
on, so ↑/↓ moved selection but rendered no visible highlight.
- Add click-to-preview: clicking a card thumbnail/tile now opens a
larger centered modal (list and tile views), with a flip button for
double-faced cards' back face. Hover preview is unchanged and clears
when a click-preview opens.
- Add a QtyStepper component (rounded [− N +] pill, with an `overlay`
variant for tile corners) and use it consistently for owned-copy
counts on Add Cards, and the Library list and tile views.
- Add matching +/- steppers to the Library views; decrementing to 0
dims the row for a 5s grace period (still fully interactive) before
it drops out, so an accidental removal can be undone with +.
- Topbar: fill remaining width with the library path (no more premature
truncation), add icons to Open/New Library and Download/Update card
data, and drop the trailing "…" from the library buttons.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-07 20:55:18 -05:00
|
|
|
|
const [pendingRemoval, setPendingRemoval] = useState<Set<string>>(new Set());
|
|
|
|
|
|
const removalTimers = useRef<Map<string, ReturnType<typeof setTimeout>>>(new Map());
|
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
|
|
|
|
|
2026-07-08 08:20:09 -05:00
|
|
|
|
const [selected, setSelected] = useState<Set<string>>(new Set());
|
|
|
|
|
|
const [bulkDeckId, setBulkDeckId] = useState<number | "">("");
|
2026-07-20 00:21:22 -05:00
|
|
|
|
const [bulkToMaybeboard, setBulkToMaybeboard] = useState(false);
|
2026-07-08 08:20:09 -05:00
|
|
|
|
const [bulkDelta, setBulkDelta] = useState(1);
|
|
|
|
|
|
const [locationDialogOpen, setLocationDialogOpen] = useState(false);
|
|
|
|
|
|
const [bulkBusy, setBulkBusy] = useState(false);
|
2026-07-20 00:21:22 -05:00
|
|
|
|
const [importResult, setImportResult] = useState<LibraryImportResult | null>(null);
|
2026-07-08 08:20:09 -05:00
|
|
|
|
const selectAllRef = useRef<HTMLInputElement>(null);
|
|
|
|
|
|
|
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
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
api.listDecks().then(setDecks);
|
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
2026-07-08 23:54:47 -05:00
|
|
|
|
// Refetch whenever refreshKey bumps too, so newly-owned cards' types
|
|
|
|
|
|
// become suggestible without restarting the app.
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
api.getLibraryFacets().then(setFacets).catch(() => setFacets(NO_FACETS));
|
|
|
|
|
|
}, [refreshKey]);
|
|
|
|
|
|
|
Card preview modal, library quantity steppers, and UI polish
- Fix keyboard-nav selection highlight (search results, deck list): the
Discord-styling rewrite dropped the --panel-2 CSS variable it relied
on, so ↑/↓ moved selection but rendered no visible highlight.
- Add click-to-preview: clicking a card thumbnail/tile now opens a
larger centered modal (list and tile views), with a flip button for
double-faced cards' back face. Hover preview is unchanged and clears
when a click-preview opens.
- Add a QtyStepper component (rounded [− N +] pill, with an `overlay`
variant for tile corners) and use it consistently for owned-copy
counts on Add Cards, and the Library list and tile views.
- Add matching +/- steppers to the Library views; decrementing to 0
dims the row for a 5s grace period (still fully interactive) before
it drops out, so an accidental removal can be undone with +.
- Topbar: fill remaining width with the library path (no more premature
truncation), add icons to Open/New Library and Download/Update card
data, and drop the trailing "…" from the library buttons.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-07 20:55:18 -05:00
|
|
|
|
// Cancel any in-flight removal timers on unmount so they don't fire against
|
|
|
|
|
|
// an unmounted component.
|
|
|
|
|
|
useEffect(() => () => removalTimers.current.forEach(clearTimeout), []);
|
|
|
|
|
|
|
2026-07-08 08:20:09 -05:00
|
|
|
|
// Keep selection in sync with what's actually visible — drop ids that fall
|
|
|
|
|
|
// out of the filtered set (filter changed, or a card's qty hit 0 and its
|
|
|
|
|
|
// grace period expired).
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
setSelected((s) => {
|
|
|
|
|
|
const visible = new Set(rows.map((r) => r.oracle_id));
|
|
|
|
|
|
const next = new Set([...s].filter((id) => visible.has(id)));
|
|
|
|
|
|
return next.size === s.size ? s : next;
|
|
|
|
|
|
});
|
|
|
|
|
|
}, [rows]);
|
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
if (selectAllRef.current) {
|
|
|
|
|
|
selectAllRef.current.indeterminate = selected.size > 0 && selected.size < rows.length;
|
|
|
|
|
|
}
|
|
|
|
|
|
}, [selected, rows]);
|
|
|
|
|
|
|
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
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
const filter: LibraryFilter = {
|
|
|
|
|
|
text: text.trim() || null,
|
|
|
|
|
|
commander_identity: commander || null,
|
|
|
|
|
|
colors_include: colorsInclude || null,
|
|
|
|
|
|
cmc_min: cmcMin === "" ? null : Number(cmcMin),
|
|
|
|
|
|
cmc_max: cmcMax === "" ? null : Number(cmcMax),
|
2026-07-08 23:54:47 -05:00
|
|
|
|
types,
|
|
|
|
|
|
subtypes,
|
|
|
|
|
|
keywords,
|
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
|
|
|
|
in_deck: inDeck === "" ? null : Number(inDeck),
|
2026-07-07 21:35:09 -05:00
|
|
|
|
location: location.trim() || null,
|
2026-07-08 23:54:47 -05:00
|
|
|
|
oracle_text: oracleText.trim() || null,
|
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
|
|
|
|
};
|
|
|
|
|
|
const t = setTimeout(() => {
|
|
|
|
|
|
api.searchLibrary(filter).then(setRows).catch(() => setRows([]));
|
|
|
|
|
|
}, 120);
|
|
|
|
|
|
return () => clearTimeout(t);
|
2026-07-07 21:35:09 -05:00
|
|
|
|
}, [
|
|
|
|
|
|
text,
|
|
|
|
|
|
commander,
|
|
|
|
|
|
colorsInclude,
|
|
|
|
|
|
cmcMin,
|
|
|
|
|
|
cmcMax,
|
2026-07-08 23:54:47 -05:00
|
|
|
|
types,
|
|
|
|
|
|
subtypes,
|
|
|
|
|
|
keywords,
|
2026-07-07 21:35:09 -05:00
|
|
|
|
inDeck,
|
|
|
|
|
|
location,
|
2026-07-08 23:54:47 -05:00
|
|
|
|
oracleText,
|
2026-07-07 21:35:09 -05:00
|
|
|
|
refreshKey,
|
|
|
|
|
|
]);
|
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
|
|
|
|
|
Card preview modal, library quantity steppers, and UI polish
- Fix keyboard-nav selection highlight (search results, deck list): the
Discord-styling rewrite dropped the --panel-2 CSS variable it relied
on, so ↑/↓ moved selection but rendered no visible highlight.
- Add click-to-preview: clicking a card thumbnail/tile now opens a
larger centered modal (list and tile views), with a flip button for
double-faced cards' back face. Hover preview is unchanged and clears
when a click-preview opens.
- Add a QtyStepper component (rounded [− N +] pill, with an `overlay`
variant for tile corners) and use it consistently for owned-copy
counts on Add Cards, and the Library list and tile views.
- Add matching +/- steppers to the Library views; decrementing to 0
dims the row for a 5s grace period (still fully interactive) before
it drops out, so an accidental removal can be undone with +.
- Topbar: fill remaining width with the library path (no more premature
truncation), add icons to Open/New Library and Download/Update card
data, and drop the trailing "…" from the library buttons.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-07 20:55:18 -05:00
|
|
|
|
async function adjust(oracleId: string, delta: number) {
|
|
|
|
|
|
const qty = await api.addOwned(oracleId, delta);
|
|
|
|
|
|
setRows((rs) => rs.map((c) => (c.oracle_id === oracleId ? { ...c, quantity_owned: qty } : c)));
|
|
|
|
|
|
|
|
|
|
|
|
// Re-adding during the grace period below cancels the pending removal.
|
|
|
|
|
|
const timer = removalTimers.current.get(oracleId);
|
|
|
|
|
|
if (timer) {
|
|
|
|
|
|
clearTimeout(timer);
|
|
|
|
|
|
removalTimers.current.delete(oracleId);
|
|
|
|
|
|
setPendingRemoval((p) => {
|
|
|
|
|
|
const next = new Set(p);
|
|
|
|
|
|
next.delete(oracleId);
|
|
|
|
|
|
return next;
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (qty <= 0) {
|
|
|
|
|
|
// search_library only returns owned (>0) cards — rather than dropping
|
|
|
|
|
|
// the row immediately, dim it and give the user a window to undo a
|
|
|
|
|
|
// mistaken decrement (via +) before it actually disappears.
|
|
|
|
|
|
setPendingRemoval((p) => new Set(p).add(oracleId));
|
|
|
|
|
|
removalTimers.current.set(
|
|
|
|
|
|
oracleId,
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
setRows((rs) => rs.filter((c) => c.oracle_id !== oracleId));
|
|
|
|
|
|
setPendingRemoval((p) => {
|
|
|
|
|
|
const next = new Set(p);
|
|
|
|
|
|
next.delete(oracleId);
|
|
|
|
|
|
return next;
|
|
|
|
|
|
});
|
|
|
|
|
|
removalTimers.current.delete(oracleId);
|
|
|
|
|
|
}, REMOVAL_GRACE_MS),
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-08 08:20:09 -05:00
|
|
|
|
function toggleOne(oracleId: string) {
|
|
|
|
|
|
setSelected((s) => {
|
|
|
|
|
|
const next = new Set(s);
|
|
|
|
|
|
if (next.has(oracleId)) next.delete(oracleId);
|
|
|
|
|
|
else next.add(oracleId);
|
|
|
|
|
|
return next;
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function toggleSelectAll() {
|
|
|
|
|
|
setSelected((s) => (s.size === rows.length ? new Set() : new Set(rows.map((r) => r.oracle_id))));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Set the in-deck quantity of every selected card to either 1 or the full
|
|
|
|
|
|
* owned count (e.g. select a handful of basic lands, "Add All" to put
|
|
|
|
|
|
* every copy you own into the deck at once) — overwrites the quantity for
|
|
|
|
|
|
* cards already in the deck rather than skipping them, so it doubles as a
|
2026-07-20 00:21:22 -05:00
|
|
|
|
* bulk "fix the land count" tool. `bulkToMaybeboard` sends them to the
|
|
|
|
|
|
* deck's maybeboard instead of the mainboard.
|
2026-07-21 21:04:36 -05:00
|
|
|
|
*
|
|
|
|
|
|
* Since this overwrites rather than skips, it can clobber an existing
|
|
|
|
|
|
* in-deck quantity/board — so it snapshots each target's prior deck_cards
|
|
|
|
|
|
* row (or absence of one) first and offers an "Undo" that restores exactly
|
|
|
|
|
|
* that prior state (deleting the row again for cards that weren't in the
|
|
|
|
|
|
* deck before).
|
2026-07-08 08:20:09 -05:00
|
|
|
|
*/
|
|
|
|
|
|
async function bulkAddToDeck(mode: "one" | "all") {
|
|
|
|
|
|
if (bulkDeckId === "") return;
|
|
|
|
|
|
const deck = decks.find((d) => d.id === bulkDeckId);
|
|
|
|
|
|
if (!deck) return;
|
|
|
|
|
|
setBulkBusy(true);
|
|
|
|
|
|
try {
|
|
|
|
|
|
const targets = rows.filter((r) => selected.has(r.oracle_id));
|
2026-07-21 21:04:36 -05:00
|
|
|
|
const before = await api.deckContents(deck.id);
|
|
|
|
|
|
const prevByOracle = new Map(before.map((e) => [e.oracle_id, e]));
|
2026-07-08 08:20:09 -05:00
|
|
|
|
await Promise.all(
|
|
|
|
|
|
targets.map((r) =>
|
2026-07-20 00:21:22 -05:00
|
|
|
|
api.setDeckCard(
|
|
|
|
|
|
deck.id,
|
|
|
|
|
|
r.oracle_id,
|
|
|
|
|
|
mode === "all" ? r.quantity_owned : 1,
|
|
|
|
|
|
false,
|
|
|
|
|
|
bulkToMaybeboard,
|
|
|
|
|
|
),
|
2026-07-08 08:20:09 -05:00
|
|
|
|
),
|
|
|
|
|
|
);
|
|
|
|
|
|
setRefreshKey((k) => k + 1);
|
2026-07-21 21:04:36 -05:00
|
|
|
|
onOfferUndo(
|
|
|
|
|
|
`Added ${targets.length} card${targets.length === 1 ? "" : "s"} to ${deck.name}.`,
|
|
|
|
|
|
async () => {
|
|
|
|
|
|
await Promise.all(
|
|
|
|
|
|
targets.map((r) => {
|
|
|
|
|
|
const prev = prevByOracle.get(r.oracle_id);
|
|
|
|
|
|
return api.setDeckCard(
|
|
|
|
|
|
deck.id,
|
|
|
|
|
|
r.oracle_id,
|
|
|
|
|
|
prev?.quantity ?? 0,
|
|
|
|
|
|
prev?.is_commander ?? false,
|
|
|
|
|
|
prev?.is_maybeboard ?? false,
|
|
|
|
|
|
);
|
|
|
|
|
|
}),
|
|
|
|
|
|
);
|
|
|
|
|
|
setRefreshKey((k) => k + 1);
|
|
|
|
|
|
},
|
|
|
|
|
|
);
|
2026-07-08 08:20:09 -05:00
|
|
|
|
} finally {
|
|
|
|
|
|
setBulkBusy(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function bulkSetLocation(value: string) {
|
|
|
|
|
|
setBulkBusy(true);
|
|
|
|
|
|
try {
|
|
|
|
|
|
await Promise.all([...selected].map((id) => api.setLocation(id, value)));
|
|
|
|
|
|
setLocationDialogOpen(false);
|
|
|
|
|
|
setRefreshKey((k) => k + 1);
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
setBulkBusy(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function bulkAdjust(delta: number) {
|
|
|
|
|
|
setBulkBusy(true);
|
|
|
|
|
|
try {
|
|
|
|
|
|
await Promise.all([...selected].map((id) => adjust(id, delta)));
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
setBulkBusy(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-20 00:21:22 -05:00
|
|
|
|
async function exportSelected() {
|
|
|
|
|
|
const targets = rows.filter((r) => selected.has(r.oracle_id)).map((r) => r.oracle_id);
|
|
|
|
|
|
if (targets.length === 0) return;
|
|
|
|
|
|
const path = await save({
|
|
|
|
|
|
title: "Export library cards",
|
|
|
|
|
|
defaultPath: "library-export.txt",
|
|
|
|
|
|
filters: [{ name: "Decklist", extensions: ["txt"] }],
|
|
|
|
|
|
});
|
|
|
|
|
|
if (typeof path === "string") await api.exportLibraryCards(targets, path);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function pickImportFile() {
|
|
|
|
|
|
const path = await open({
|
|
|
|
|
|
title: "Import library cards",
|
|
|
|
|
|
filters: [{ name: "Decklist", extensions: ["txt"] }],
|
|
|
|
|
|
});
|
|
|
|
|
|
if (typeof path !== "string") return;
|
|
|
|
|
|
const result = await api.importLibraryCards(path);
|
|
|
|
|
|
setImportResult(result);
|
|
|
|
|
|
setRefreshKey((k) => k + 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
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
|
|
|
|
const deckOrLocation = (c: LibraryCard) =>
|
|
|
|
|
|
c.decks.length === 0 ? (
|
|
|
|
|
|
<LocationField
|
|
|
|
|
|
oracleId={c.oracle_id}
|
|
|
|
|
|
location={c.location}
|
|
|
|
|
|
onSaved={() => setRefreshKey((k) => k + 1)}
|
|
|
|
|
|
/>
|
|
|
|
|
|
) : (
|
|
|
|
|
|
c.decks.map((d) => (
|
|
|
|
|
|
<span key={d} className="deck-chip">
|
|
|
|
|
|
{d}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
))
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
function libTable(cards: LibraryCard[]) {
|
|
|
|
|
|
return (
|
|
|
|
|
|
<table>
|
|
|
|
|
|
<thead>
|
|
|
|
|
|
<tr>
|
2026-07-08 08:20:09 -05:00
|
|
|
|
<th></th>
|
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
|
|
|
|
<th></th>
|
|
|
|
|
|
<th>Colors</th>
|
|
|
|
|
|
<th>Name</th>
|
|
|
|
|
|
<th>Type</th>
|
|
|
|
|
|
<th>MV</th>
|
|
|
|
|
|
<th>Owned</th>
|
|
|
|
|
|
<th>Decks / Location</th>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
</thead>
|
|
|
|
|
|
<tbody>
|
|
|
|
|
|
{cards.map((c) => (
|
|
|
|
|
|
<tr key={c.oracle_id}>
|
2026-08-02 16:10:25 -05:00
|
|
|
|
<td className="cell-check">
|
2026-07-08 08:20:09 -05:00
|
|
|
|
<input
|
|
|
|
|
|
type="checkbox"
|
|
|
|
|
|
checked={selected.has(c.oracle_id)}
|
|
|
|
|
|
onChange={() => toggleOne(c.oracle_id)}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</td>
|
2026-08-02 16:10:25 -05:00
|
|
|
|
<td className="cell-thumb">
|
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
|
|
|
|
<CardThumb card={c} />
|
|
|
|
|
|
</td>
|
|
|
|
|
|
<td>
|
|
|
|
|
|
<ColorPips mask={c.color_identity} />
|
|
|
|
|
|
</td>
|
|
|
|
|
|
<td className="card-name">{c.name}</td>
|
|
|
|
|
|
<td className="card-type">{c.type_line}</td>
|
2026-08-02 16:10:25 -05:00
|
|
|
|
<td data-label="MV">{c.mana_value}</td>
|
|
|
|
|
|
<td
|
|
|
|
|
|
data-label="Owned"
|
|
|
|
|
|
className={c.quantity_assigned > c.quantity_owned ? "over-assigned" : ""}
|
|
|
|
|
|
>
|
Card preview modal, library quantity steppers, and UI polish
- Fix keyboard-nav selection highlight (search results, deck list): the
Discord-styling rewrite dropped the --panel-2 CSS variable it relied
on, so ↑/↓ moved selection but rendered no visible highlight.
- Add click-to-preview: clicking a card thumbnail/tile now opens a
larger centered modal (list and tile views), with a flip button for
double-faced cards' back face. Hover preview is unchanged and clears
when a click-preview opens.
- Add a QtyStepper component (rounded [− N +] pill, with an `overlay`
variant for tile corners) and use it consistently for owned-copy
counts on Add Cards, and the Library list and tile views.
- Add matching +/- steppers to the Library views; decrementing to 0
dims the row for a 5s grace period (still fully interactive) before
it drops out, so an accidental removal can be undone with +.
- Topbar: fill remaining width with the library path (no more premature
truncation), add icons to Open/New Library and Download/Update card
data, and drop the trailing "…" from the library buttons.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-07 20:55:18 -05:00
|
|
|
|
<span className="row" style={{ gap: 8 }}>
|
|
|
|
|
|
<QtyStepper
|
|
|
|
|
|
value={c.quantity_owned}
|
|
|
|
|
|
onDecrement={() => adjust(c.oracle_id, -1)}
|
|
|
|
|
|
onIncrement={() => adjust(c.oracle_id, 1)}
|
|
|
|
|
|
warn={c.quantity_assigned > c.quantity_owned}
|
|
|
|
|
|
pending={pendingRemoval.has(c.oracle_id)}
|
|
|
|
|
|
/>
|
|
|
|
|
|
{c.quantity_assigned > c.quantity_owned && (
|
|
|
|
|
|
<span title="Assigned to more decks than you own">
|
|
|
|
|
|
⚠ {c.quantity_assigned} assigned
|
|
|
|
|
|
</span>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</span>
|
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
|
|
|
|
</td>
|
2026-08-02 16:10:25 -05:00
|
|
|
|
<td data-label="Decks / Location">{deckOrLocation(c)}</td>
|
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
|
|
|
|
</tr>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</tbody>
|
|
|
|
|
|
</table>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function libTiles(cards: LibraryCard[]) {
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div className="tile-grid">
|
|
|
|
|
|
{cards.map((c) => (
|
|
|
|
|
|
<CardTile
|
|
|
|
|
|
key={c.oracle_id}
|
|
|
|
|
|
card={c}
|
2026-07-08 08:20:09 -05:00
|
|
|
|
selected={selected.has(c.oracle_id)}
|
|
|
|
|
|
cornerLeft={
|
|
|
|
|
|
<span className="tile-select">
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="checkbox"
|
|
|
|
|
|
checked={selected.has(c.oracle_id)}
|
|
|
|
|
|
onChange={() => toggleOne(c.oracle_id)}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</span>
|
|
|
|
|
|
}
|
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
|
|
|
|
corner={
|
Card preview modal, library quantity steppers, and UI polish
- Fix keyboard-nav selection highlight (search results, deck list): the
Discord-styling rewrite dropped the --panel-2 CSS variable it relied
on, so ↑/↓ moved selection but rendered no visible highlight.
- Add click-to-preview: clicking a card thumbnail/tile now opens a
larger centered modal (list and tile views), with a flip button for
double-faced cards' back face. Hover preview is unchanged and clears
when a click-preview opens.
- Add a QtyStepper component (rounded [− N +] pill, with an `overlay`
variant for tile corners) and use it consistently for owned-copy
counts on Add Cards, and the Library list and tile views.
- Add matching +/- steppers to the Library views; decrementing to 0
dims the row for a 5s grace period (still fully interactive) before
it drops out, so an accidental removal can be undone with +.
- Topbar: fill remaining width with the library path (no more premature
truncation), add icons to Open/New Library and Download/Update card
data, and drop the trailing "…" from the library buttons.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-07 20:55:18 -05:00
|
|
|
|
<QtyStepper
|
|
|
|
|
|
overlay
|
|
|
|
|
|
value={c.quantity_owned}
|
|
|
|
|
|
onDecrement={() => adjust(c.oracle_id, -1)}
|
|
|
|
|
|
onIncrement={() => adjust(c.oracle_id, 1)}
|
|
|
|
|
|
warn={c.quantity_assigned > c.quantity_owned}
|
|
|
|
|
|
pending={pendingRemoval.has(c.oracle_id)}
|
|
|
|
|
|
/>
|
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
|
|
|
|
}
|
|
|
|
|
|
footer={deckOrLocation(c)}
|
|
|
|
|
|
/>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-22 20:55:17 -05:00
|
|
|
|
const groups = groupCards(sortCards(rows, sortBy, sortDir), groupBy);
|
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
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div>
|
Cohesive styling pass: design tokens, shared primitives, responsive groundwork
The UI had a good terracotta-on-dark system that wasn't applied
consistently — magic-number spacing, ad-hoc page headers, ~6 copied
panel surfaces, ~8 drifting label variants, and scattered accent use.
- Tokens: warm the neutral ramp, add spacing/radius/type/motion scales,
--accent-quiet + panel highlight. Terracotta hue unchanged.
- Primitives: .panel, .section-label, .page-header/.toolbar — collapse
the copied surfaces and label variants onto shared classes.
- Unify each filter-heavy page's display controls (group/sort/view,
select-all, count) into a workspace toolbar inside the filter panel,
so they read as one surface instead of floating above it.
- Labels: sentence-case, higher-contrast form labels; uppercase section
markers bumped faint -> muted.
- Accent hierarchy: reserve full terracotta for primary actions/active
state; demote decorative labels and the group-header underline.
- Responsive groundwork: reflow classes for the deck two-pane and nav
rail (stacks / icon-collapse at breakpoints), touch-target sizing.
Desktop unchanged in kind.
2026-07-22 23:44:21 -05:00
|
|
|
|
<div className="page-header">
|
|
|
|
|
|
<h2 className="page-header-title">
|
|
|
|
|
|
Library <span className="badge">{rows.length}</span>
|
|
|
|
|
|
</h2>
|
|
|
|
|
|
<div className="page-header-actions">
|
2026-07-20 00:21:22 -05:00
|
|
|
|
<button onClick={pickImportFile}>Import cards…</button>
|
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
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-07-08 08:20:09 -05:00
|
|
|
|
{selected.size > 0 && (
|
|
|
|
|
|
<div className="bulk-bar">
|
|
|
|
|
|
<span className="bulk-bar-count">{selected.size} selected</span>
|
|
|
|
|
|
<div className="bulk-bar-actions">
|
|
|
|
|
|
<span className="row" style={{ gap: 6 }}>
|
|
|
|
|
|
<select
|
|
|
|
|
|
value={bulkDeckId}
|
|
|
|
|
|
onChange={(e) => setBulkDeckId(e.target.value === "" ? "" : Number(e.target.value))}
|
|
|
|
|
|
disabled={bulkBusy}
|
|
|
|
|
|
>
|
|
|
|
|
|
<option value="">Add to deck…</option>
|
|
|
|
|
|
{decks.map((d) => (
|
|
|
|
|
|
<option key={d.id} value={d.id}>
|
|
|
|
|
|
{d.name}
|
|
|
|
|
|
</option>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</select>
|
|
|
|
|
|
<button
|
|
|
|
|
|
onClick={() => bulkAddToDeck("one")}
|
|
|
|
|
|
disabled={bulkBusy || bulkDeckId === ""}
|
|
|
|
|
|
title="Set each selected card to 1 copy in the deck"
|
|
|
|
|
|
>
|
|
|
|
|
|
Add 1
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button
|
|
|
|
|
|
onClick={() => bulkAddToDeck("all")}
|
|
|
|
|
|
disabled={bulkBusy || bulkDeckId === ""}
|
|
|
|
|
|
title="Set each selected card to every copy you own"
|
|
|
|
|
|
>
|
|
|
|
|
|
Add All
|
|
|
|
|
|
</button>
|
2026-07-20 00:21:22 -05:00
|
|
|
|
<label className="row" style={{ gap: 4, cursor: "pointer" }} title="Send to the maybeboard instead of the mainboard">
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="checkbox"
|
|
|
|
|
|
checked={bulkToMaybeboard}
|
|
|
|
|
|
onChange={(e) => setBulkToMaybeboard(e.target.checked)}
|
|
|
|
|
|
disabled={bulkBusy}
|
|
|
|
|
|
/>
|
|
|
|
|
|
<span className="muted" style={{ fontSize: 12 }}>
|
|
|
|
|
|
Maybeboard
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</label>
|
2026-07-08 08:20:09 -05:00
|
|
|
|
</span>
|
|
|
|
|
|
<button onClick={() => setLocationDialogOpen(true)} disabled={bulkBusy}>
|
|
|
|
|
|
Set location…
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<span className="row" style={{ gap: 4 }}>
|
|
|
|
|
|
<button
|
|
|
|
|
|
onClick={() => bulkAdjust(-bulkDelta)}
|
|
|
|
|
|
disabled={bulkBusy}
|
|
|
|
|
|
title="Remove copies from each selected card"
|
|
|
|
|
|
>
|
|
|
|
|
|
−
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<input
|
|
|
|
|
|
className="bulk-bar-delta"
|
|
|
|
|
|
type="number"
|
|
|
|
|
|
min={1}
|
|
|
|
|
|
value={bulkDelta}
|
|
|
|
|
|
onChange={(e) => setBulkDelta(Math.max(1, Number(e.target.value) || 1))}
|
|
|
|
|
|
/>
|
|
|
|
|
|
<button
|
|
|
|
|
|
onClick={() => bulkAdjust(bulkDelta)}
|
|
|
|
|
|
disabled={bulkBusy}
|
|
|
|
|
|
title="Add copies to each selected card"
|
|
|
|
|
|
>
|
|
|
|
|
|
+
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</span>
|
2026-07-20 00:21:22 -05:00
|
|
|
|
<button
|
|
|
|
|
|
onClick={exportSelected}
|
|
|
|
|
|
disabled={bulkBusy}
|
|
|
|
|
|
title="Export the selected cards to a decklist file"
|
|
|
|
|
|
>
|
|
|
|
|
|
Export selected
|
|
|
|
|
|
</button>
|
2026-07-08 08:20:09 -05:00
|
|
|
|
</div>
|
|
|
|
|
|
<span className="spacer" />
|
|
|
|
|
|
<button onClick={() => setSelected(new Set())} disabled={bulkBusy}>
|
|
|
|
|
|
Clear
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{locationDialogOpen && (
|
|
|
|
|
|
<PromptDialog
|
|
|
|
|
|
title="Set location"
|
|
|
|
|
|
label={`Location for ${selected.size} card${selected.size === 1 ? "" : "s"}`}
|
|
|
|
|
|
defaultValue=""
|
|
|
|
|
|
confirmLabel="Apply"
|
|
|
|
|
|
onConfirm={bulkSetLocation}
|
|
|
|
|
|
onCancel={() => setLocationDialogOpen(false)}
|
|
|
|
|
|
/>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
2026-07-20 00:21:22 -05:00
|
|
|
|
{importResult && (
|
|
|
|
|
|
<InfoDialog
|
|
|
|
|
|
title="Cards imported"
|
|
|
|
|
|
message={
|
|
|
|
|
|
importResult.unresolved.length > 0
|
|
|
|
|
|
? `Added ${importResult.added.length} card${importResult.added.length === 1 ? "" : "s"}. ${importResult.unresolved.length} name${importResult.unresolved.length === 1 ? "" : "s"} couldn't be matched:`
|
|
|
|
|
|
: `Added ${importResult.added.length} card${importResult.added.length === 1 ? "" : "s"} to your library.`
|
|
|
|
|
|
}
|
|
|
|
|
|
items={importResult.unresolved}
|
|
|
|
|
|
onClose={() => setImportResult(null)}
|
|
|
|
|
|
/>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
Cohesive styling pass: design tokens, shared primitives, responsive groundwork
The UI had a good terracotta-on-dark system that wasn't applied
consistently — magic-number spacing, ad-hoc page headers, ~6 copied
panel surfaces, ~8 drifting label variants, and scattered accent use.
- Tokens: warm the neutral ramp, add spacing/radius/type/motion scales,
--accent-quiet + panel highlight. Terracotta hue unchanged.
- Primitives: .panel, .section-label, .page-header/.toolbar — collapse
the copied surfaces and label variants onto shared classes.
- Unify each filter-heavy page's display controls (group/sort/view,
select-all, count) into a workspace toolbar inside the filter panel,
so they read as one surface instead of floating above it.
- Labels: sentence-case, higher-contrast form labels; uppercase section
markers bumped faint -> muted.
- Accent hierarchy: reserve full terracotta for primary actions/active
state; demote decorative labels and the group-header underline.
- Responsive groundwork: reflow classes for the deck two-pane and nav
rail (stacks / icon-collapse at breakpoints), touch-target sizing.
Desktop unchanged in kind.
2026-07-22 23:44:21 -05:00
|
|
|
|
<div className="panel workspace">
|
|
|
|
|
|
<div className="filters">
|
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
|
|
|
|
<div className="field">
|
|
|
|
|
|
<label>Name contains</label>
|
|
|
|
|
|
<input value={text} onChange={(e) => setText(e.target.value)} placeholder="e.g. bolt" />
|
|
|
|
|
|
</div>
|
2026-08-02 16:10:25 -05:00
|
|
|
|
<div className={`filters-more ${filtersExpanded ? "expanded" : ""}`}>
|
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
|
|
|
|
<div className="field">
|
|
|
|
|
|
<label>Playable in commander (color identity ⊆)</label>
|
|
|
|
|
|
<ColorToggles value={commander} onChange={setCommander} />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="field">
|
|
|
|
|
|
<label>Includes colors</label>
|
|
|
|
|
|
<ColorToggles value={colorsInclude} onChange={setColorsInclude} />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="field">
|
|
|
|
|
|
<label>Mana value</label>
|
|
|
|
|
|
<div className="row">
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="number"
|
|
|
|
|
|
style={{ width: 70 }}
|
|
|
|
|
|
placeholder="min"
|
|
|
|
|
|
value={cmcMin}
|
|
|
|
|
|
onChange={(e) => setCmcMin(e.target.value)}
|
|
|
|
|
|
/>
|
|
|
|
|
|
<span className="muted">–</span>
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="number"
|
|
|
|
|
|
style={{ width: 70 }}
|
|
|
|
|
|
placeholder="max"
|
|
|
|
|
|
value={cmcMax}
|
|
|
|
|
|
onChange={(e) => setCmcMax(e.target.value)}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="field">
|
|
|
|
|
|
<label>Type(s)</label>
|
2026-07-08 23:54:47 -05:00
|
|
|
|
<TagInput values={types} onChange={setTypes} suggestions={facets.types} placeholder="Creature…" />
|
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
|
|
|
|
</div>
|
|
|
|
|
|
<div className="field">
|
|
|
|
|
|
<label>Subtype(s)</label>
|
2026-07-08 23:54:47 -05:00
|
|
|
|
<TagInput
|
|
|
|
|
|
values={subtypes}
|
|
|
|
|
|
onChange={setSubtypes}
|
|
|
|
|
|
suggestions={facets.subtypes}
|
|
|
|
|
|
placeholder="Elf…"
|
|
|
|
|
|
/>
|
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
|
|
|
|
</div>
|
|
|
|
|
|
<div className="field">
|
|
|
|
|
|
<label>Keyword(s)</label>
|
2026-07-08 23:54:47 -05:00
|
|
|
|
<TagInput
|
|
|
|
|
|
values={keywords}
|
|
|
|
|
|
onChange={setKeywords}
|
|
|
|
|
|
suggestions={facets.keywords}
|
|
|
|
|
|
placeholder="Flying…"
|
|
|
|
|
|
/>
|
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
|
|
|
|
</div>
|
|
|
|
|
|
<div className="field">
|
|
|
|
|
|
<label>In deck</label>
|
|
|
|
|
|
<select value={inDeck} onChange={(e) => setInDeck(e.target.value === "" ? "" : Number(e.target.value))}>
|
|
|
|
|
|
<option value="">Any</option>
|
|
|
|
|
|
{decks.map((d) => (
|
|
|
|
|
|
<option key={d.id} value={d.id}>
|
|
|
|
|
|
{d.name}
|
|
|
|
|
|
</option>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</select>
|
|
|
|
|
|
</div>
|
2026-07-07 21:35:09 -05:00
|
|
|
|
<div className="field">
|
|
|
|
|
|
<label>Location</label>
|
|
|
|
|
|
<input
|
|
|
|
|
|
value={location}
|
|
|
|
|
|
onChange={(e) => setLocation(e.target.value)}
|
|
|
|
|
|
placeholder="e.g. Box 1"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
2026-07-08 23:54:47 -05:00
|
|
|
|
<div className="field">
|
|
|
|
|
|
<label>Oracle text</label>
|
|
|
|
|
|
<input
|
|
|
|
|
|
value={oracleText}
|
|
|
|
|
|
onChange={(e) => setOracleText(e.target.value)}
|
|
|
|
|
|
placeholder="e.g. landfall"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
Cohesive styling pass: design tokens, shared primitives, responsive groundwork
The UI had a good terracotta-on-dark system that wasn't applied
consistently — magic-number spacing, ad-hoc page headers, ~6 copied
panel surfaces, ~8 drifting label variants, and scattered accent use.
- Tokens: warm the neutral ramp, add spacing/radius/type/motion scales,
--accent-quiet + panel highlight. Terracotta hue unchanged.
- Primitives: .panel, .section-label, .page-header/.toolbar — collapse
the copied surfaces and label variants onto shared classes.
- Unify each filter-heavy page's display controls (group/sort/view,
select-all, count) into a workspace toolbar inside the filter panel,
so they read as one surface instead of floating above it.
- Labels: sentence-case, higher-contrast form labels; uppercase section
markers bumped faint -> muted.
- Accent hierarchy: reserve full terracotta for primary actions/active
state; demote decorative labels and the group-header underline.
- Responsive groundwork: reflow classes for the deck two-pane and nav
rail (stacks / icon-collapse at breakpoints), touch-target sizing.
Desktop unchanged in kind.
2026-07-22 23:44:21 -05:00
|
|
|
|
</div>
|
2026-08-02 16:10:25 -05:00
|
|
|
|
</div>
|
|
|
|
|
|
<button
|
|
|
|
|
|
className="filters-toggle"
|
|
|
|
|
|
onClick={() => setFiltersExpanded((v) => !v)}
|
|
|
|
|
|
>
|
|
|
|
|
|
{filtersExpanded ? "▴ Fewer filters" : "▾ More filters"}
|
|
|
|
|
|
</button>
|
Cohesive styling pass: design tokens, shared primitives, responsive groundwork
The UI had a good terracotta-on-dark system that wasn't applied
consistently — magic-number spacing, ad-hoc page headers, ~6 copied
panel surfaces, ~8 drifting label variants, and scattered accent use.
- Tokens: warm the neutral ramp, add spacing/radius/type/motion scales,
--accent-quiet + panel highlight. Terracotta hue unchanged.
- Primitives: .panel, .section-label, .page-header/.toolbar — collapse
the copied surfaces and label variants onto shared classes.
- Unify each filter-heavy page's display controls (group/sort/view,
select-all, count) into a workspace toolbar inside the filter panel,
so they read as one surface instead of floating above it.
- Labels: sentence-case, higher-contrast form labels; uppercase section
markers bumped faint -> muted.
- Accent hierarchy: reserve full terracotta for primary actions/active
state; demote decorative labels and the group-header underline.
- Responsive groundwork: reflow classes for the deck two-pane and nav
rail (stacks / icon-collapse at breakpoints), touch-target sizing.
Desktop unchanged in kind.
2026-07-22 23:44:21 -05:00
|
|
|
|
|
|
|
|
|
|
<div className="workspace-bar">
|
|
|
|
|
|
<label className="workspace-bar-check" title="Select all filtered cards">
|
|
|
|
|
|
<input
|
|
|
|
|
|
ref={selectAllRef}
|
|
|
|
|
|
type="checkbox"
|
|
|
|
|
|
checked={rows.length > 0 && selected.size === rows.length}
|
|
|
|
|
|
onChange={toggleSelectAll}
|
|
|
|
|
|
disabled={rows.length === 0}
|
|
|
|
|
|
/>
|
|
|
|
|
|
Select all
|
|
|
|
|
|
</label>
|
|
|
|
|
|
<span className="workspace-bar-count">
|
|
|
|
|
|
{rows.length} card{rows.length === 1 ? "" : "s"}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<span className="spacer" />
|
|
|
|
|
|
<GroupBySelect value={groupBy} onChange={setGroupBy} />
|
|
|
|
|
|
<SortBySelect
|
|
|
|
|
|
value={sortBy}
|
|
|
|
|
|
dir={sortDir}
|
|
|
|
|
|
onChangeValue={setSortBy}
|
|
|
|
|
|
onToggleDir={() => setSortDir(sortDir === "asc" ? "desc" : "asc")}
|
|
|
|
|
|
/>
|
|
|
|
|
|
<ViewToggle mode={view} onChange={setView} />
|
|
|
|
|
|
</div>
|
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
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{rows.length === 0 ? (
|
|
|
|
|
|
<p className="hint">
|
|
|
|
|
|
No matching cards owned. Add some in the Add Cards tab, or loosen the filters.
|
|
|
|
|
|
</p>
|
|
|
|
|
|
) : (
|
|
|
|
|
|
groups.map((g) => (
|
|
|
|
|
|
<div key={g.label || "all"} style={{ marginBottom: g.label ? 20 : 0 }}>
|
|
|
|
|
|
{g.label && (
|
2026-07-20 00:21:22 -05:00
|
|
|
|
<h3 className="group-header">
|
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
|
|
|
|
{g.label} <span className="badge">{g.cards.length}</span>
|
|
|
|
|
|
</h3>
|
|
|
|
|
|
)}
|
|
|
|
|
|
{view === "list" ? libTable(g.cards) : libTiles(g.cards)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
))
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|