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";
|
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 { api, Deck, LibraryCard, LibraryFilter } from "../api";
|
|
|
|
|
|
import {
|
|
|
|
|
|
CardThumb,
|
|
|
|
|
|
CardTile,
|
|
|
|
|
|
ColorPips,
|
|
|
|
|
|
ColorToggles,
|
|
|
|
|
|
GroupBySelect,
|
|
|
|
|
|
GroupMode,
|
|
|
|
|
|
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,
|
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,
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
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
|
|
|
|
/** Browse & filter the owned library by name and card criteria. */
|
|
|
|
|
|
export function LibraryBrowser() {
|
|
|
|
|
|
const [text, setText] = useState("");
|
|
|
|
|
|
const [commander, setCommander] = useState(0);
|
|
|
|
|
|
const [colorsInclude, setColorsInclude] = useState(0);
|
|
|
|
|
|
const [cmcMin, setCmcMin] = useState("");
|
|
|
|
|
|
const [cmcMax, setCmcMax] = useState("");
|
|
|
|
|
|
const [type, setType] = useState("");
|
|
|
|
|
|
const [subtype, setSubtype] = useState("");
|
|
|
|
|
|
const [keyword, setKeyword] = useState("");
|
|
|
|
|
|
const [inDeck, setInDeck] = useState<number | "">("");
|
2026-07-07 21:35:09 -05:00
|
|
|
|
const [location, setLocation] = useState("");
|
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");
|
|
|
|
|
|
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 | "">("");
|
|
|
|
|
|
const [bulkDelta, setBulkDelta] = useState(1);
|
|
|
|
|
|
const [locationDialogOpen, setLocationDialogOpen] = useState(false);
|
|
|
|
|
|
const [bulkBusy, setBulkBusy] = useState(false);
|
|
|
|
|
|
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);
|
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
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),
|
|
|
|
|
|
types: splitCsv(type),
|
|
|
|
|
|
subtypes: splitCsv(subtype),
|
|
|
|
|
|
keywords: splitCsv(keyword),
|
|
|
|
|
|
in_deck: inDeck === "" ? null : Number(inDeck),
|
2026-07-07 21:35:09 -05:00
|
|
|
|
location: location.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,
|
|
|
|
|
|
type,
|
|
|
|
|
|
subtype,
|
|
|
|
|
|
keyword,
|
|
|
|
|
|
inDeck,
|
|
|
|
|
|
location,
|
|
|
|
|
|
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
|
|
|
|
|
|
* bulk "fix the land count" tool.
|
|
|
|
|
|
*/
|
|
|
|
|
|
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));
|
|
|
|
|
|
await Promise.all(
|
|
|
|
|
|
targets.map((r) =>
|
|
|
|
|
|
api.setDeckCard(deck.id, r.oracle_id, mode === "all" ? r.quantity_owned : 1, false),
|
|
|
|
|
|
),
|
|
|
|
|
|
);
|
|
|
|
|
|
setRefreshKey((k) => k + 1);
|
|
|
|
|
|
} 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);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
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-07-08 08:20:09 -05:00
|
|
|
|
<td>
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="checkbox"
|
|
|
|
|
|
checked={selected.has(c.oracle_id)}
|
|
|
|
|
|
onChange={() => toggleOne(c.oracle_id)}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</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
|
|
|
|
<td>
|
|
|
|
|
|
<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>
|
|
|
|
|
|
<td>{c.mana_value}</td>
|
|
|
|
|
|
<td 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>
|
|
|
|
|
|
<td>{deckOrLocation(c)}</td>
|
|
|
|
|
|
</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>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const groups = groupCards(rows, groupBy);
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<div className="row" style={{ justifyContent: "space-between", marginBottom: 12 }}>
|
2026-07-08 08:20:09 -05:00
|
|
|
|
<div className="row" style={{ gap: 14 }}>
|
|
|
|
|
|
<label className="row" style={{ gap: 6, cursor: "pointer" }} title="Select all filtered cards">
|
|
|
|
|
|
<input
|
|
|
|
|
|
ref={selectAllRef}
|
|
|
|
|
|
type="checkbox"
|
|
|
|
|
|
checked={rows.length > 0 && selected.size === rows.length}
|
|
|
|
|
|
onChange={toggleSelectAll}
|
|
|
|
|
|
disabled={rows.length === 0}
|
|
|
|
|
|
/>
|
|
|
|
|
|
<span className="muted" style={{ fontSize: 12 }}>
|
|
|
|
|
|
Select all
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</label>
|
|
|
|
|
|
<h2 style={{ margin: 0 }}>
|
|
|
|
|
|
Library <span className="badge">{rows.length}</span>
|
|
|
|
|
|
</h2>
|
|
|
|
|
|
</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 className="row" style={{ gap: 16 }}>
|
|
|
|
|
|
<GroupBySelect value={groupBy} onChange={setGroupBy} />
|
|
|
|
|
|
<ViewToggle mode={view} onChange={setView} />
|
|
|
|
|
|
</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>
|
|
|
|
|
|
</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>
|
|
|
|
|
|
</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)}
|
|
|
|
|
|
/>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
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="filters">
|
|
|
|
|
|
<div className="field">
|
|
|
|
|
|
<label>Name contains</label>
|
|
|
|
|
|
<input value={text} onChange={(e) => setText(e.target.value)} placeholder="e.g. bolt" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<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>
|
|
|
|
|
|
<input value={type} onChange={(e) => setType(e.target.value)} placeholder="Creature, Legendary" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="field">
|
|
|
|
|
|
<label>Subtype(s)</label>
|
|
|
|
|
|
<input value={subtype} onChange={(e) => setSubtype(e.target.value)} placeholder="Elf" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="field">
|
|
|
|
|
|
<label>Keyword(s)</label>
|
|
|
|
|
|
<input value={keyword} onChange={(e) => setKeyword(e.target.value)} placeholder="Flying" />
|
|
|
|
|
|
</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>
|
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 && (
|
|
|
|
|
|
<h3>
|
|
|
|
|
|
{g.label} <span className="badge">{g.cards.length}</span>
|
|
|
|
|
|
</h3>
|
|
|
|
|
|
)}
|
|
|
|
|
|
{view === "list" ? libTable(g.cards) : libTiles(g.cards)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
))
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function splitCsv(s: string): string[] {
|
|
|
|
|
|
return s
|
|
|
|
|
|
.split(",")
|
|
|
|
|
|
.map((x) => x.trim())
|
|
|
|
|
|
.filter(Boolean);
|
|
|
|
|
|
}
|