fix(ui): show installing for downloaded games

The redesigned action hook marked every accepted install command as Checking
Peers. That is correct while the launcher is asking peers for file details, but
it is wrong for a game that is already downloaded and only needs local archive
installation.

Track the already-downloaded path separately and optimistically show Installing
until the backend install lifecycle event arrives. Peer-backed downloads keep the
existing Checking Peers state.

Test Plan:
- git diff --check

Refs: user redesign nitpick about install button state
This commit is contained in:
2026-05-19 20:49:22 +02:00
parent 50698f9a7d
commit 74d9266723
2 changed files with 22 additions and 4 deletions
@@ -12,9 +12,9 @@ export interface GameActions {
/** /**
* Thin wrappers over the backend `run_game` / `install_game` / `update_game` * Thin wrappers over the backend `run_game` / `install_game` / `update_game`
* / `uninstall_game` commands. For install + update we mark the game as * / `uninstall_game` commands. We mark peer-backed downloads as "checking
* "checking peers" up-front through the games hook so the UI doesn't have to * peers" and already-downloaded installs as "installing" up-front so the UI
* wait for the first backend event. * doesn't have to wait for the first backend event.
*/ */
export const useGameActions = (games: UseGamesResult): GameActions => { export const useGameActions = (games: UseGamesResult): GameActions => {
const play = useCallback(async (id: string) => { const play = useCallback(async (id: string) => {
@@ -28,7 +28,14 @@ export const useGameActions = (games: UseGamesResult): GameActions => {
const install = useCallback(async (id: string) => { const install = useCallback(async (id: string) => {
try { try {
const success = await invoke<boolean>('install_game', { id }); const success = await invoke<boolean>('install_game', { id });
if (success) games.markChecking(id); if (!success) return;
const game = games.games.find(item => item.id === id);
if (game?.downloaded && !game.installed) {
games.markInstalling(id);
} else {
games.markChecking(id);
}
} catch (err) { } catch (err) {
console.error('install_game failed:', err); console.error('install_game failed:', err);
} }
@@ -52,6 +52,7 @@ export interface UseGamesResult {
totalPeerCount: number; totalPeerCount: number;
requestGames: () => Promise<void>; requestGames: () => Promise<void>;
markChecking: (id: string) => void; markChecking: (id: string) => void;
markInstalling: (id: string) => void;
cancelChecking: (id: string) => void; cancelChecking: (id: string) => void;
} }
@@ -95,6 +96,15 @@ export const useGames = (rescanGameDir: () => void): UseGamesResult => {
}, CHECKING_PEERS_TIMEOUT_MS); }, CHECKING_PEERS_TIMEOUT_MS);
}, [cancelChecking]); }, [cancelChecking]);
const markInstalling = useCallback((id: string) => {
cancelChecking(id);
setGames(prev => prev.map(item =>
item.id === id
? applyPatch(item, { install_status: InstallStatus.Installing, clearStatus: true })
: item,
));
}, [cancelChecking]);
const requestGames = useCallback(async () => { const requestGames = useCallback(async () => {
try { try {
await invoke('request_games'); await invoke('request_games');
@@ -247,6 +257,7 @@ export const useGames = (rescanGameDir: () => void): UseGamesResult => {
totalPeerCount, totalPeerCount,
requestGames, requestGames,
markChecking, markChecking,
markInstalling,
cancelChecking, cancelChecking,
}; };
}; };