mirror of
https://github.com/priyanshujain/margin.git
synced 2026-10-02 19:17:03 +00:00
feat(ui): surface save and export errors as toasts; add webkit backdrop-filter
This commit is contained in:
1 parent
c4b69f3641
commit
cb506af374
2 files changed
+57
-17
No files matched your search
+38
-17
@@ -16,6 +16,7 @@ function App() {
|
|||||||
const [editor, setEditor] = useState<TiptapEditor | null>(null);
|
const [editor, setEditor] = useState<TiptapEditor | null>(null);
|
||||||
const [settingsOpen, setSettingsOpen] = useState(false);
|
const [settingsOpen, setSettingsOpen] = useState(false);
|
||||||
const [exportOpen, setExportOpen] = useState(false);
|
const [exportOpen, setExportOpen] = useState(false);
|
||||||
|
const [notice, setNotice] = useState<string | null>(null);
|
||||||
const title = useBook((s) => s.book.metadata.title);
|
const title = useBook((s) => s.book.metadata.title);
|
||||||
const author = useBook((s) => s.book.metadata.author);
|
const author = useBook((s) => s.book.metadata.author);
|
||||||
const chapters = useBook((s) => s.book.chapters);
|
const chapters = useBook((s) => s.book.chapters);
|
||||||
@@ -30,18 +31,36 @@ function App() {
|
|||||||
const chapter = chapters[idx];
|
const chapter = chapters[idx];
|
||||||
|
|
||||||
const handleSave = useCallback(async () => {
|
const handleSave = useCallback(async () => {
|
||||||
const { book, path } = useBook.getState();
|
try {
|
||||||
const target = path ?? (await chooseSavePath(book));
|
const { book, path } = useBook.getState();
|
||||||
if (!target) return;
|
const target = path ?? (await chooseSavePath(book));
|
||||||
await writeBook(book, target);
|
if (!target) return;
|
||||||
markSaved(target);
|
await writeBook(book, target);
|
||||||
|
markSaved(target);
|
||||||
|
setNotice("Saved");
|
||||||
|
} catch (e) {
|
||||||
|
setNotice(`Save failed: ${e}`);
|
||||||
|
}
|
||||||
}, [markSaved]);
|
}, [markSaved]);
|
||||||
|
|
||||||
const handleOpen = useCallback(async () => {
|
const handleOpen = useCallback(async () => {
|
||||||
const result = await openBook();
|
try {
|
||||||
if (result) replaceBook(result.book, result.path);
|
const result = await openBook();
|
||||||
|
if (result) replaceBook(result.book, result.path);
|
||||||
|
} catch (e) {
|
||||||
|
setNotice(`Open failed: ${e}`);
|
||||||
|
}
|
||||||
}, [replaceBook]);
|
}, [replaceBook]);
|
||||||
|
|
||||||
|
const runExport = async (label: string, fn: (book: Book) => Promise<void>) => {
|
||||||
|
setExportOpen(false);
|
||||||
|
try {
|
||||||
|
await fn(useBook.getState().book);
|
||||||
|
} catch (e) {
|
||||||
|
setNotice(`${label} export failed: ${e}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const onKey = (e: KeyboardEvent) => {
|
const onKey = (e: KeyboardEvent) => {
|
||||||
const mod = e.metaKey || e.ctrlKey;
|
const mod = e.metaKey || e.ctrlKey;
|
||||||
@@ -58,14 +77,11 @@ function App() {
|
|||||||
return () => window.removeEventListener("keydown", onKey);
|
return () => window.removeEventListener("keydown", onKey);
|
||||||
}, [handleSave, handleOpen]);
|
}, [handleSave, handleOpen]);
|
||||||
|
|
||||||
const runExport = async (fn: (book: Book) => Promise<void>) => {
|
useEffect(() => {
|
||||||
setExportOpen(false);
|
if (!notice) return;
|
||||||
try {
|
const timer = setTimeout(() => setNotice(null), 4000);
|
||||||
await fn(useBook.getState().book);
|
return () => clearTimeout(timer);
|
||||||
} catch (e) {
|
}, [notice]);
|
||||||
console.error(e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="app">
|
<div className="app">
|
||||||
@@ -90,8 +106,8 @@ function App() {
|
|||||||
<>
|
<>
|
||||||
<div className="menu-backdrop" onClick={() => setExportOpen(false)} />
|
<div className="menu-backdrop" onClick={() => setExportOpen(false)} />
|
||||||
<div className="menu">
|
<div className="menu">
|
||||||
<button onClick={() => runExport(exportPdf)}>Export PDF…</button>
|
<button onClick={() => runExport("PDF", exportPdf)}>Export PDF…</button>
|
||||||
<button onClick={() => runExport(exportEpub)}>Export EPUB…</button>
|
<button onClick={() => runExport("EPUB", exportEpub)}>Export EPUB…</button>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@@ -129,6 +145,11 @@ function App() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{settingsOpen && <Settings onClose={() => setSettingsOpen(false)} />}
|
{settingsOpen && <Settings onClose={() => setSettingsOpen(false)} />}
|
||||||
|
{notice && (
|
||||||
|
<div className="toast" onClick={() => setNotice(null)}>
|
||||||
|
{notice}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -472,6 +472,7 @@ button {
|
|||||||
gap: 1px;
|
gap: 1px;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
background: rgba(252, 251, 247, 0.86);
|
background: rgba(252, 251, 247, 0.86);
|
||||||
|
-webkit-backdrop-filter: blur(10px);
|
||||||
backdrop-filter: blur(10px);
|
backdrop-filter: blur(10px);
|
||||||
border: 1px solid var(--line);
|
border: 1px solid var(--line);
|
||||||
border-radius: 999px;
|
border-radius: 999px;
|
||||||
@@ -627,6 +628,7 @@ button {
|
|||||||
inset: 0;
|
inset: 0;
|
||||||
z-index: 20;
|
z-index: 20;
|
||||||
background: rgba(35, 32, 27, 0.28);
|
background: rgba(35, 32, 27, 0.28);
|
||||||
|
-webkit-backdrop-filter: blur(2px);
|
||||||
backdrop-filter: blur(2px);
|
backdrop-filter: blur(2px);
|
||||||
display: grid;
|
display: grid;
|
||||||
place-items: center;
|
place-items: center;
|
||||||
@@ -771,3 +773,20 @@ button {
|
|||||||
background: var(--accent-wash);
|
background: var(--accent-wash);
|
||||||
color: var(--ink);
|
color: var(--ink);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.toast {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 26px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
z-index: 40;
|
||||||
|
max-width: 460px;
|
||||||
|
padding: 11px 16px;
|
||||||
|
border-radius: var(--r-md);
|
||||||
|
background: var(--ink);
|
||||||
|
color: var(--paper);
|
||||||
|
font-family: var(--font-ui);
|
||||||
|
font-size: var(--t-3);
|
||||||
|
box-shadow: var(--shadow-pop);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
Reference in new issue
Block a user