Refresh the whole UI when the library document changes
Picking a library left the path bar showing the previous one until the next
launch: it reads status.library_path from get_status, but sync_set_document
returns a SyncStatus, and nothing re-fetched the former.
The less visible half was that every view kept rendering the old library's
rows. Switching documents replaces every card, deck and internal id beneath
them, so they're now keyed on the document path and remounted rather than left
reconciling state belonging to a library that is no longer open — which matters
most for Decks, whose persisted "last selected deck" id means nothing in a
different library.
Both Open and New Library now go through one adoptDocument(), which refreshes
status, library stats and the views together, and reports what was opened
("Opened …/decks-v1.json — 366 cards, 7 decks") so a successful switch isn't
silent.
The backend was already correct: it had loaded the picked document all along.
This commit is contained in:
46
src/App.tsx
46
src/App.tsx
@@ -452,6 +452,32 @@ export default function App() {
|
||||
}, [tab, refreshSync]);
|
||||
|
||||
|
||||
/**
|
||||
* Point the app at `path` and bring the whole UI into line with it.
|
||||
*
|
||||
* Switching documents changes the entire library underneath every view, so
|
||||
* refreshing the sync panel alone isn't enough: the path bar reads
|
||||
* `status.library_path`, the sidebar reads `libStats`, and each tab is
|
||||
* holding rows fetched from the *previous* library. Returns a short " — N
|
||||
* cards, M decks" suffix so the caller can confirm what actually opened.
|
||||
*/
|
||||
async function adoptDocument(path: string): Promise<string> {
|
||||
setSync(await api.syncSetDocument(path));
|
||||
// The path bar comes from get_status, which nothing else re-fetches.
|
||||
setStatus(await api.getStatus());
|
||||
setDeckRefreshSignal((v) => v + 1);
|
||||
if (!readyRef.current) return "";
|
||||
try {
|
||||
const stats = await api.getLibraryStats();
|
||||
setLibStats(stats);
|
||||
return ` — ${stats.unique_cards} card${stats.unique_cards === 1 ? "" : "s"}, ${
|
||||
stats.deck_count
|
||||
} deck${stats.deck_count === 1 ? "" : "s"}`;
|
||||
} catch {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/** Choose where the library lives. Android grants a folder through the system
|
||||
* picker (SAF has no path to hand back); desktop picks the JSON file itself. */
|
||||
async function pickLibrary() {
|
||||
@@ -474,7 +500,8 @@ export default function App() {
|
||||
});
|
||||
if (typeof path !== "string") return;
|
||||
setSyncBusy(true);
|
||||
setSync(await api.syncSetDocument(path));
|
||||
const opened = await adoptDocument(path);
|
||||
setSyncTest({ ok: true, message: `Opened ${path}${opened}.` });
|
||||
} catch (err) {
|
||||
setSyncTest({ ok: false, message: String(err) });
|
||||
} finally {
|
||||
@@ -495,8 +522,8 @@ export default function App() {
|
||||
setSyncBusy(true);
|
||||
// Pointing at a file that doesn't exist yet moves the current library
|
||||
// there rather than starting empty — see sync_set_document.
|
||||
setSync(await api.syncSetDocument(path));
|
||||
setSyncTest({ ok: true, message: `Your library is now kept at ${path}.` });
|
||||
const moved = await adoptDocument(path);
|
||||
setSyncTest({ ok: true, message: `Your library is now kept at ${path}${moved}.` });
|
||||
} catch (err) {
|
||||
setSyncTest({ ok: false, message: String(err) });
|
||||
} finally {
|
||||
@@ -795,12 +822,17 @@ export default function App() {
|
||||
</div>
|
||||
) : tab === "add" ? (
|
||||
<CardEntry />
|
||||
) : tab === "library" ? (
|
||||
<LibraryBrowser onOfferUndo={offerUndo} />
|
||||
) : /* Keyed on the library document: switching to a different one
|
||||
replaces every card, deck and id these views are holding, so
|
||||
they're remounted rather than left to reconcile state that
|
||||
belongs to a library that's no longer open. Stable across
|
||||
ordinary status refreshes, so it costs nothing otherwise. */
|
||||
tab === "library" ? (
|
||||
<LibraryBrowser key={status?.library_path} onOfferUndo={offerUndo} />
|
||||
) : tab === "search" ? (
|
||||
<CardSearch onOfferUndo={offerUndo} />
|
||||
<CardSearch key={status?.library_path} onOfferUndo={offerUndo} />
|
||||
) : tab === "decks" ? (
|
||||
<DeckView refreshSignal={deckRefreshSignal} />
|
||||
<DeckView key={status?.library_path} refreshSignal={deckRefreshSignal} />
|
||||
) : (
|
||||
// The per-install status page: card data, library sync, and the
|
||||
// collection summary. On mobile it's the only home for the
|
||||
|
||||
Reference in New Issue
Block a user