group of bug fixes

This commit is contained in:
pj committed 2026-08-24 09:17:26 +05:30
1 parent cfc93fc97d
commit bcd5eb9ebc
30 files changed
+973 -238

No files matched your search

+49 -7
View File
@@ -7,6 +7,7 @@ import {
gdriveDisconnect,
gdriveRestore,
gdriveStatus,
gdriveSync,
} from "../backup";
import { isDesktop } from "../ipc";
import { useBook } from "./useBook";
@@ -18,6 +19,7 @@ interface BackupState {
email: string | null;
lastBackup: number | null;
pending: boolean;
needsReauth: boolean;
phase: Phase;
error: string | null;
authUrl: string | null;
@@ -33,6 +35,7 @@ interface BackupState {
copyAuthUrl: () => Promise<void>;
disconnect: () => Promise<void>;
backup: (silent?: boolean) => Promise<void>;
sync: (silent?: boolean) => Promise<void>;
restore: () => Promise<void>;
restoreFromDrive: () => Promise<void>;
openSettings: () => void;
@@ -43,11 +46,31 @@ function notify(message: string) {
useBook.getState().setNotice(message);
}
const RECONNECT = /Connect again/;
function describe(error: unknown, fallback: string): string {
const message = String(error).replace(/^Error:\s*/, "");
return RECONNECT.test(message) ? message : `${fallback}: ${message}`;
}
function plural(n: number, word: string): string {
return `${n} ${word}${n === 1 ? "" : "s"}`;
}
function syncMessage(uploaded: number, downloaded: number): string {
if (!uploaded && !downloaded) return "Everything is already in sync";
const parts = [];
if (downloaded) parts.push(`brought back ${plural(downloaded, "file")}`);
if (uploaded) parts.push(`backed up ${plural(uploaded, "file")}`);
return `Synced with Google Drive: ${parts.join(", ")}`;
}
export const useBackup = create<BackupState>((set, get) => ({
connected: false,
email: null,
lastBackup: null,
pending: false,
needsReauth: false,
phase: "idle",
error: null,
authUrl: null,
@@ -60,6 +83,7 @@ export const useBackup = create<BackupState>((set, get) => ({
email: status.email,
lastBackup: status.lastBackup,
pending: status.pending,
needsReauth: status.needsReauth,
}),
refresh: async () => {
if (!isDesktop) return;
@@ -75,7 +99,7 @@ export const useBackup = create<BackupState>((set, get) => ({
.then((url) => set({ authUrl: url }))
.catch((e) => {
set({ phase: "error", error: String(e), resolveConnect: null });
notify(`Could not connect: ${e}`);
notify(describe(e, "Could not connect"));
resolve(false);
});
}),
@@ -91,11 +115,13 @@ export const useBackup = create<BackupState>((set, get) => ({
await get().refresh();
set({ phase: "idle", authUrl: null, error: null, resolveConnect: null });
notify("Connected to Google Drive");
} else {
set({ phase: "error", authUrl: null, error: error ?? "authorization failed", resolveConnect: null });
notify(`Could not connect: ${error ?? "authorization failed"}`);
await get().sync();
resolve?.(true);
return;
}
resolve?.(ok);
set({ phase: "error", authUrl: null, error: error ?? "authorization failed", resolveConnect: null });
notify(describe(error ?? "authorization failed", "Could not connect"));
resolve?.(false);
},
openAuthUrl: () => {
const url = get().authUrl;
@@ -132,7 +158,22 @@ export const useBackup = create<BackupState>((set, get) => ({
if (!silent) notify(outcome.uploaded > 0 ? "Backed up to Google Drive" : "Nothing new to back up");
} catch (e) {
set({ phase: "error", error: String(e) });
if (!silent) notify(`Backup failed: ${e}`);
await get().refresh();
if (!silent || RECONNECT.test(String(e))) notify(describe(e, "Backup failed"));
}
},
sync: async (silent = false) => {
if (!get().connected || get().phase === "working") return;
set({ phase: "working", error: null });
try {
const outcome = await gdriveSync();
get().apply(outcome);
set((s) => ({ phase: "idle", restoreNonce: outcome.downloaded > 0 ? s.restoreNonce + 1 : s.restoreNonce }));
if (!silent) notify(syncMessage(outcome.uploaded, outcome.downloaded));
} catch (e) {
set({ phase: "error", error: String(e) });
await get().refresh();
if (!silent || RECONNECT.test(String(e))) notify(describe(e, "Sync failed"));
}
},
restore: async () => {
@@ -144,7 +185,8 @@ export const useBackup = create<BackupState>((set, get) => ({
notify(result.restored > 0 ? `Restored ${result.restored} file${result.restored === 1 ? "" : "s"}` : "No backups found in Google Drive");
} catch (e) {
set({ phase: "error", error: String(e) });
notify(`Restore failed: ${e}`);
await get().refresh();
notify(describe(e, "Restore failed"));
}
},
restoreFromDrive: async () => {
+4 -1
View File
@@ -14,7 +14,7 @@ import {
normalizeBook,
} from "../model/book";
import { loadActiveChapter, saveActiveChapter } from "../editor/positions";
import { saveBook } from "../library";
import { rememberLastBook, saveBook } from "../library";
import { applyBookFonts, resetBookFonts } from "../book-style";
export const COVER_ID = "__cover__";
@@ -70,10 +70,12 @@ export const useBook = create<BookState>((set, get) => ({
const valid = saved === COVER_ID || normalized.chapters.some((c) => c.id === saved);
const activeChapterId = valid ? (saved as string) : normalized.chapters[0]?.id ?? "";
applyBookFonts(normalized.settings.fonts);
rememberLastBook(normalized.id);
set({ book: normalized, activeChapterId, dirty: false, pendingTitleFocus: null });
},
closeBook: () => {
flushOutgoing(get().book, get().dirty);
rememberLastBook(null);
resetBookFonts();
set({ book: null, activeChapterId: "", dirty: false, pendingTitleFocus: null });
},
@@ -163,6 +165,7 @@ export const useBook = create<BookState>((set, get) => ({
const index = s.book.chapters.findIndex((c) => c.id === id);
if (index === -1) return {};
const chapters = s.book.chapters.filter((c) => c.id !== id);
if (!chapters.length) chapters.push(createChapter(""));
const activeChapterId =
s.activeChapterId === id ? (chapters[index] ?? chapters[index - 1])?.id ?? COVER_ID : s.activeChapterId;
return { dirty: true, activeChapterId, book: { ...s.book, chapters } };