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>
This commit is contained in:
65
src/App.tsx
65
src/App.tsx
@@ -9,11 +9,11 @@ import { DeckView } from "./views/DeckView";
|
||||
|
||||
type Tab = "add" | "library" | "decks";
|
||||
|
||||
const svg = (children: ReactNode) => (
|
||||
const svg = (children: ReactNode, size = 20) => (
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
width="20"
|
||||
height="20"
|
||||
width={size}
|
||||
height={size}
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
@@ -24,6 +24,27 @@ const svg = (children: ReactNode) => (
|
||||
</svg>
|
||||
);
|
||||
|
||||
const ICON_OPEN_LIBRARY = svg(
|
||||
<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z" />,
|
||||
16,
|
||||
);
|
||||
const ICON_NEW_LIBRARY = svg(
|
||||
<>
|
||||
<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z" />
|
||||
<line x1="12" y1="11" x2="12" y2="17" />
|
||||
<line x1="9" y1="14" x2="15" y2="14" />
|
||||
</>,
|
||||
16,
|
||||
);
|
||||
const ICON_DOWNLOAD = svg(
|
||||
<>
|
||||
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
|
||||
<polyline points="7 10 12 15 17 10" />
|
||||
<line x1="12" y1="15" x2="12" y2="3" />
|
||||
</>,
|
||||
16,
|
||||
);
|
||||
|
||||
const NAV: { id: Tab; label: string; icon: ReactNode }[] = [
|
||||
{ id: "add", label: "Add Cards", icon: svg(<path d="M12 5v14M5 12h14" />) },
|
||||
{
|
||||
@@ -124,14 +145,25 @@ export default function App() {
|
||||
await api.cancelUpdateCardData();
|
||||
}
|
||||
|
||||
async function switchLibrary(path: string) {
|
||||
try {
|
||||
setStatus(await api.setLibraryPath(path));
|
||||
} catch (err) {
|
||||
setProgress({
|
||||
stage: "error",
|
||||
message: `Couldn't open that library: ${String(err)}`,
|
||||
overall_percent: 0,
|
||||
stages: lastProgress.current?.stages ?? [],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function openLibrary() {
|
||||
const path = await open({
|
||||
title: "Open an existing library.db",
|
||||
filters: [{ name: "SQLite", extensions: ["db", "sqlite"] }],
|
||||
});
|
||||
if (typeof path === "string") {
|
||||
setStatus(await api.setLibraryPath(path));
|
||||
}
|
||||
if (typeof path === "string") await switchLibrary(path);
|
||||
}
|
||||
|
||||
async function newLibrary() {
|
||||
@@ -140,9 +172,7 @@ export default function App() {
|
||||
defaultPath: "library.db",
|
||||
filters: [{ name: "SQLite", extensions: ["db", "sqlite"] }],
|
||||
});
|
||||
if (typeof path === "string") {
|
||||
setStatus(await api.setLibraryPath(path));
|
||||
}
|
||||
if (typeof path === "string") await switchLibrary(path);
|
||||
}
|
||||
|
||||
const ready = status?.reference_ready ?? false;
|
||||
@@ -177,19 +207,19 @@ export default function App() {
|
||||
|
||||
<div className="main">
|
||||
<header className="topbar">
|
||||
{status && (
|
||||
<span className="path" title={status.library_path}>
|
||||
{status.library_path}
|
||||
</span>
|
||||
)}
|
||||
<span className="spacer" />
|
||||
<span className="path" title={status?.library_path ?? ""}>
|
||||
{status?.library_path ?? ""}
|
||||
</span>
|
||||
<button onClick={openLibrary} disabled={busy}>
|
||||
Open Library…
|
||||
{ICON_OPEN_LIBRARY}
|
||||
Open Library
|
||||
</button>
|
||||
<button onClick={newLibrary} disabled={busy}>
|
||||
New Library…
|
||||
{ICON_NEW_LIBRARY}
|
||||
New Library
|
||||
</button>
|
||||
<button onClick={updateCardData} disabled={busy} className="primary">
|
||||
{ICON_DOWNLOAD}
|
||||
{ready ? "Update card data" : "Download card data"}
|
||||
</button>
|
||||
</header>
|
||||
@@ -249,6 +279,7 @@ export default function App() {
|
||||
be synced.
|
||||
</p>
|
||||
<button className="primary big" onClick={updateCardData} disabled={busy}>
|
||||
{ICON_DOWNLOAD}
|
||||
{busy ? "Downloading…" : "Download card data"}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user