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 { useEffect, useRef, useState, useCallback, ReactNode } from "react";
|
||||||
|
import { getVersion } from "@tauri-apps/api/app";
|
||||||
import { listen } from "@tauri-apps/api/event";
|
import { listen } from "@tauri-apps/api/event";
|
||||||
import { open } from "@tauri-apps/plugin-dialog";
|
import { open } from "@tauri-apps/plugin-dialog";
|
||||||
import {
|
import {
|
||||||
@@ -168,10 +169,12 @@ function LibrarySummary({
|
|||||||
ready,
|
ready,
|
||||||
libStats,
|
libStats,
|
||||||
status,
|
status,
|
||||||
|
title = "Your library",
|
||||||
}: {
|
}: {
|
||||||
ready: boolean;
|
ready: boolean;
|
||||||
libStats: LibraryStats | null;
|
libStats: LibraryStats | null;
|
||||||
status: Status | null;
|
status: Status | null;
|
||||||
|
title?: string;
|
||||||
}) {
|
}) {
|
||||||
if (!ready) {
|
if (!ready) {
|
||||||
return (
|
return (
|
||||||
@@ -185,7 +188,7 @@ function LibrarySummary({
|
|||||||
<div className="lib-stats">
|
<div className="lib-stats">
|
||||||
<div className="lib-stats-head">
|
<div className="lib-stats-head">
|
||||||
<span className="status-dot on" />
|
<span className="status-dot on" />
|
||||||
<span className="lib-stats-title">Your library</span>
|
<span className="lib-stats-title">{title}</span>
|
||||||
</div>
|
</div>
|
||||||
{!libStats ? (
|
{!libStats ? (
|
||||||
<div className="lib-stats-loading">Loading…</div>
|
<div className="lib-stats-loading">Loading…</div>
|
||||||
@@ -242,6 +245,12 @@ export default function App() {
|
|||||||
const [status, setStatus] = useState<Status | null>(null);
|
const [status, setStatus] = useState<Status | null>(null);
|
||||||
const [libStats, setLibStats] = useState<LibraryStats | null>(null);
|
const [libStats, setLibStats] = useState<LibraryStats | null>(null);
|
||||||
const [tab, setTab] = usePersistedState<Tab>("active-tab", "add");
|
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.
|
// A deck a chip elsewhere asked to open; DeckView picks it up and clears it.
|
||||||
const [openDeckId, setOpenDeckId] = useState<number | null>(null);
|
const [openDeckId, setOpenDeckId] = useState<number | null>(null);
|
||||||
// Deck chips only carry the deck's name, which is unique per library.
|
// 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);
|
// rail it normally sits in is replaced by the bottom nav);
|
||||||
// on desktop those also appear in the topbar and rail, but sync
|
// on desktop those also appear in the topbar and rail, but sync
|
||||||
// lives only here.
|
// lives only here.
|
||||||
<div className="panel info-panel">
|
// Two columns on wide screens — card data, library stats and the
|
||||||
<h2 style={{ margin: 0 }}>Card database</h2>
|
// libraries on the left; sync and the version on the right. On
|
||||||
<div className="row" style={{ gap: "var(--space-3)", flexWrap: "wrap" }}>
|
// narrow ones the columns dissolve into one list in the order
|
||||||
<button className="primary" onClick={updateCardData} disabled={busy}>
|
// set by `.info-*` (see styles.css).
|
||||||
{ICON_DOWNLOAD}
|
<div className="info-grid">
|
||||||
{busy ? "Downloading…" : "Update card data"}
|
<div className="info-col">
|
||||||
</button>
|
<section className="panel info-section info-db">
|
||||||
<button onClick={importReferenceDb} disabled={busy}>
|
<h2 style={{ margin: 0 }}>Card database</h2>
|
||||||
{ICON_OPEN_LIBRARY}
|
<div className="row" style={{ gap: "var(--space-3)", flexWrap: "wrap" }}>
|
||||||
Import card data…
|
<button className="primary" onClick={updateCardData} disabled={busy}>
|
||||||
</button>
|
{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>
|
</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>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -208,7 +208,7 @@ export function SyncPanel({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<h2 style={{ margin: 0 }}>Your library</h2>
|
<h2 style={{ margin: 0 }}>Sync</h2>
|
||||||
|
|
||||||
{!sync.signed_in ? (
|
{!sync.signed_in ? (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -805,11 +805,51 @@ input[type="checkbox"] {
|
|||||||
|
|
||||||
/* Mobile's Info tab — card-database management, which lives in the topbar
|
/* Mobile's Info tab — card-database management, which lives in the topbar
|
||||||
instead on desktop (see INFO_TAB in App.tsx). */
|
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;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: var(--space-3);
|
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
|
/* Top-bar sync state; opens the Info tab. Quiet when all is well, so it
|
||||||
only draws the eye when something needs attention. */
|
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