fix: abort old loads, stale sync state, warn before unsaved edits are lost

This commit is contained in:
lobo committed 2026-09-30 07:51:09 +02:00
1 parent cbafa3d540
commit 9ddc50f819
28 files changed
+753 -121

No files matched your search

+16 -25
View File
@@ -20,7 +20,8 @@ import GameCover from '../components/GameCover.jsx';
import LibraryTabs from '../components/LibraryTabs.jsx';
import GameStatusBadge from '../components/GameStatusBadge.jsx';
import PageContainer from '../components/PageContainer.jsx';
import {apiFetch} from '../utils/api.js';
import {apiFetch, request} from '../utils/api.js';
import useAbortPrevious from '../hooks/useAbortPrevious.js';
import {platformLabel} from '../utils/platforms.js';
const PAGE_SIZES = [20, 40, 80];
@@ -65,33 +66,22 @@ export default function GameLibrary() {
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState(null);
const nextSignal = useAbortPrevious();
const load = useCallback(async () => {
const signal = nextSignal();
setIsLoading(true);
setError(null);
try {
const url = `${import.meta.env.VITE_CORE_API_URL}/api/library/games?${libraryQuery({page, size, sort, status: listStatus, q, maxMinutes: listShortOnly ? SHORT_GAME_MINUTES : null, hidden: hiddenOnly})}`;
const response = await apiFetch(url, {
headers: {
'Content-Type': 'application/json',
},
});
if (response.status === 401) return;
if (!response.ok) {
setError('LIBRARY.ERROR_LOAD');
return;
}
const json = await response.json();
setData(json);
} catch (err) {
console.error('Library load error:', err);
setError('INFO.ERROR.NETWORK_FAILURE');
} finally {
setIsLoading(false);
const url = `${import.meta.env.VITE_CORE_API_URL}/api/library/games?${libraryQuery({page, size, sort, status: listStatus, q, maxMinutes: listShortOnly ? SHORT_GAME_MINUTES : null, hidden: hiddenOnly})}`;
const {ok, status, data: json} = await request(url, {signal});
if (signal.aborted) return;
setIsLoading(false);
if (status === 401) return;
if (!ok || !json) {
setError(status === 0 ? 'INFO.ERROR.NETWORK_FAILURE' : 'LIBRARY.ERROR_LOAD');
return;
}
}, [page, size, sort, listStatus, q, listShortOnly, hiddenOnly]);
setData(json);
}, [nextSignal, page, size, sort, listStatus, q, listShortOnly, hiddenOnly]);
useEffect(() => {
load();
@@ -156,7 +146,7 @@ export default function GameLibrary() {
updateParams({page: target});
};
const {trigger: triggerSync, busy: syncBusy} = useForceSync(load);
const {trigger: triggerSync, busy: syncBusy} = useForceSync(sync.watch);
const syncing = syncBusy || sync.isRunning;
const ready = !isLoading && !error;
@@ -174,6 +164,7 @@ export default function GameLibrary() {
status={sync.status}
lastSyncedAt={sync.lastSyncedAt}
isRunning={sync.isRunning}
stale={sync.stale}
/>
<Button variant="outline" size="sm" onClick={triggerSync} disabled={syncing}>
<RefreshCw className={syncing ? 'animate-spin' : ''}/>