Info page: show the app version, two-column layout on wide screens
An About panel shows the installed version (from Tauri at runtime, since release builds stamp it at build time; dev builds are labelled as such). On wide screens the page splits into panels in two columns — card database, library stats and libraries on the left; sync and about on the right — while narrow screens keep the single-column order. The sync panel is now headed "Sync" and the Info page's summary "Library stats". Closes #22
This commit is contained in:
102
src/App.tsx
102
src/App.tsx
@@ -1,4 +1,5 @@
|
||||
import { useEffect, useRef, useState, useCallback, ReactNode } from "react";
|
||||
import { getVersion } from "@tauri-apps/api/app";
|
||||
import { listen } from "@tauri-apps/api/event";
|
||||
import { open } from "@tauri-apps/plugin-dialog";
|
||||
import {
|
||||
@@ -168,10 +169,12 @@ function LibrarySummary({
|
||||
ready,
|
||||
libStats,
|
||||
status,
|
||||
title = "Your library",
|
||||
}: {
|
||||
ready: boolean;
|
||||
libStats: LibraryStats | null;
|
||||
status: Status | null;
|
||||
title?: string;
|
||||
}) {
|
||||
if (!ready) {
|
||||
return (
|
||||
@@ -185,7 +188,7 @@ function LibrarySummary({
|
||||
<div className="lib-stats">
|
||||
<div className="lib-stats-head">
|
||||
<span className="status-dot on" />
|
||||
<span className="lib-stats-title">Your library</span>
|
||||
<span className="lib-stats-title">{title}</span>
|
||||
</div>
|
||||
{!libStats ? (
|
||||
<div className="lib-stats-loading">Loading…</div>
|
||||
@@ -242,6 +245,12 @@ export default function App() {
|
||||
const [status, setStatus] = useState<Status | null>(null);
|
||||
const [libStats, setLibStats] = useState<LibraryStats | null>(null);
|
||||
const [tab, setTab] = usePersistedState<Tab>("active-tab", "add");
|
||||
// The installed build's version, for the Info page. Release builds stamp
|
||||
// it at build time, so it comes from Tauri rather than package.json.
|
||||
const [appVersion, setAppVersion] = useState<string | null>(null);
|
||||
useEffect(() => {
|
||||
getVersion().then(setAppVersion).catch(() => setAppVersion(null));
|
||||
}, []);
|
||||
// A deck a chip elsewhere asked to open; DeckView picks it up and clears it.
|
||||
const [openDeckId, setOpenDeckId] = useState<number | null>(null);
|
||||
// Deck chips only carry the deck's name, which is unique per library.
|
||||
@@ -798,41 +807,64 @@ export default function App() {
|
||||
// rail it normally sits in is replaced by the bottom nav);
|
||||
// on desktop those also appear in the topbar and rail, but sync
|
||||
// lives only here.
|
||||
<div className="panel info-panel">
|
||||
<h2 style={{ margin: 0 }}>Card database</h2>
|
||||
<div className="row" style={{ gap: "var(--space-3)", flexWrap: "wrap" }}>
|
||||
<button className="primary" onClick={updateCardData} disabled={busy}>
|
||||
{ICON_DOWNLOAD}
|
||||
{busy ? "Downloading…" : "Update card data"}
|
||||
</button>
|
||||
<button onClick={importReferenceDb} disabled={busy}>
|
||||
{ICON_OPEN_LIBRARY}
|
||||
Import card data…
|
||||
</button>
|
||||
// Two columns on wide screens — card data, library stats and the
|
||||
// libraries on the left; sync and the version on the right. On
|
||||
// narrow ones the columns dissolve into one list in the order
|
||||
// set by `.info-*` (see styles.css).
|
||||
<div className="info-grid">
|
||||
<div className="info-col">
|
||||
<section className="panel info-section info-db">
|
||||
<h2 style={{ margin: 0 }}>Card database</h2>
|
||||
<div className="row" style={{ gap: "var(--space-3)", flexWrap: "wrap" }}>
|
||||
<button className="primary" onClick={updateCardData} disabled={busy}>
|
||||
{ICON_DOWNLOAD}
|
||||
{busy ? "Downloading…" : "Update card data"}
|
||||
</button>
|
||||
<button onClick={importReferenceDb} disabled={busy}>
|
||||
{ICON_OPEN_LIBRARY}
|
||||
Import card data…
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
<section className="panel info-section info-summary">
|
||||
<LibrarySummary ready={ready} libStats={libStats} status={status} title="Library stats" />
|
||||
</section>
|
||||
<section className="panel info-section info-libraries">
|
||||
<LibrariesPanel
|
||||
libraries={libraries}
|
||||
signedIn={sync?.signed_in ?? false}
|
||||
onChanged={librariesChanged}
|
||||
/>
|
||||
</section>
|
||||
</div>
|
||||
<div className="info-col">
|
||||
<section className="panel info-section info-sync">
|
||||
{sync === null ? (
|
||||
<>
|
||||
<h2 style={{ margin: 0 }}>Sync</h2>
|
||||
<div className="hint">Checking…</div>
|
||||
</>
|
||||
) : (
|
||||
<SyncPanel
|
||||
sync={sync}
|
||||
setSync={setSync}
|
||||
syncing={syncing}
|
||||
runSync={runSync}
|
||||
onLibraryReplaced={refreshEverything}
|
||||
/>
|
||||
)}
|
||||
</section>
|
||||
<section className="panel info-section info-about">
|
||||
<h2 style={{ margin: 0 }}>About</h2>
|
||||
<div className="info-about-row">
|
||||
<span className="muted">mtgc version</span>
|
||||
<span className="info-version">
|
||||
{appVersion ?? "unknown"}
|
||||
{import.meta.env.DEV && <span className="muted"> · development build</span>}
|
||||
</span>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<hr className="deck-section-divider" style={{ margin: 0 }} />
|
||||
<LibrariesPanel
|
||||
libraries={libraries}
|
||||
signedIn={sync?.signed_in ?? false}
|
||||
onChanged={librariesChanged}
|
||||
/>
|
||||
<hr className="deck-section-divider" style={{ margin: 0 }} />
|
||||
{sync === null ? (
|
||||
<>
|
||||
<h2 style={{ margin: 0 }}>Your library</h2>
|
||||
<div className="hint">Checking…</div>
|
||||
</>
|
||||
) : (
|
||||
<SyncPanel
|
||||
sync={sync}
|
||||
setSync={setSync}
|
||||
syncing={syncing}
|
||||
runSync={runSync}
|
||||
onLibraryReplaced={refreshEverything}
|
||||
/>
|
||||
)}
|
||||
<hr className="deck-section-divider" style={{ margin: 0 }} />
|
||||
<LibrarySummary ready={ready} libStats={libStats} status={status} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -208,7 +208,7 @@ export function SyncPanel({
|
||||
|
||||
return (
|
||||
<>
|
||||
<h2 style={{ margin: 0 }}>Your library</h2>
|
||||
<h2 style={{ margin: 0 }}>Sync</h2>
|
||||
|
||||
{!sync.signed_in ? (
|
||||
<>
|
||||
|
||||
@@ -805,11 +805,51 @@ input[type="checkbox"] {
|
||||
|
||||
/* Mobile's Info tab — card-database management, which lives in the topbar
|
||||
instead on desktop (see INFO_TAB in App.tsx). */
|
||||
.info-panel {
|
||||
/* Info page: a column of panels on narrow screens, two columns on wide ones.
|
||||
Below the breakpoint the two `.info-col` wrappers dissolve (display:
|
||||
contents) so `order` can interleave their panels into the reading order
|
||||
the single column always had: card data, libraries, sync, summary, about. */
|
||||
.info-grid {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-4);
|
||||
}
|
||||
.info-col {
|
||||
display: contents;
|
||||
}
|
||||
.info-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-3);
|
||||
max-width: 480px;
|
||||
min-width: 0;
|
||||
}
|
||||
.info-db { order: 1; }
|
||||
.info-libraries { order: 2; }
|
||||
.info-sync { order: 3; }
|
||||
.info-summary { order: 4; }
|
||||
.info-about { order: 5; }
|
||||
@media (min-width: 900px) {
|
||||
.info-grid {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
||||
align-items: start;
|
||||
max-width: 1200px;
|
||||
}
|
||||
.info-col {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-4);
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
.info-about-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
.info-version {
|
||||
font-variant-numeric: tabular-nums;
|
||||
font-weight: 600;
|
||||
}
|
||||
/* Top-bar sync state; opens the Info tab. Quiet when all is well, so it
|
||||
only draws the eye when something needs attention. */
|
||||
|
||||
1
src/vite-env.d.ts
vendored
Normal file
1
src/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
||||
Reference in New Issue
Block a user