Add a durable, acknowledged Local network sharing switch with fail-closed hydration, serialized mutation, and redacted ephemeral-identity diagnostics. Keep local Call-to-Play state available while gating every network action on the effective sharing generation. Render revisioned verification, invalid-source retry, and sticky source exhaustion states. Preserve opaque attempt IDs through progress delivery so out-of-order webview events cannot attach stale bytes to a successor transfer, and keep terminal exhaustion visible after the last source departs. Own listeners, native invokes, persistence, dialogs, and companion-window creation through webview close. Late creation is settled and cleaned before the parent realm is destroyed. Test Plan: - `just frontend-test` -- passed (91/91) - `just build` -- passed with TypeScript, Vite, and release Tauri compilation - `just test` -- passed on the completed stack (708 workspace tests) - `just clippy` -- passed on the completed stack - `git diff --cached --check` -- passed
44 lines
1.6 KiB
TypeScript
44 lines
1.6 KiB
TypeScript
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
import { invoke } from '@tauri-apps/api/core';
|
|
|
|
import {
|
|
createThumbnailRequestKey,
|
|
startOwnedThumbnailGeneration,
|
|
ThumbnailRequestGeneration,
|
|
thumbnailIdsFromRequestKey,
|
|
} from '../lib/thumbnailRequests';
|
|
import { windowAsyncScope } from '../lib/asyncOwnership';
|
|
|
|
/**
|
|
* Per-id cache for cover thumbnails belonging to the supplied game ids.
|
|
* Returns `null` until the value is known; returns the empty string when the
|
|
* backend has nothing for the id, so callers can use the placeholder cover.
|
|
*/
|
|
export const useThumbnails = (ids: readonly string[]) => {
|
|
const [thumbnails, setThumbnails] = useState<Map<string, string>>(new Map());
|
|
const thumbnailsRef = useRef(thumbnails);
|
|
const requestKey = createThumbnailRequestKey(ids);
|
|
|
|
useEffect(() => {
|
|
const generation = new ThumbnailRequestGeneration(
|
|
id => invoke<string>('get_game_thumbnail', { gameId: id }),
|
|
(id, url) => {
|
|
const next = new Map(thumbnailsRef.current).set(id, url);
|
|
thumbnailsRef.current = next;
|
|
setThumbnails(next);
|
|
},
|
|
);
|
|
const missingIds = thumbnailIdsFromRequestKey(requestKey)
|
|
.filter(id => !thumbnailsRef.current.has(id));
|
|
|
|
return startOwnedThumbnailGeneration(windowAsyncScope, generation, missingIds);
|
|
}, [requestKey]);
|
|
|
|
const get = useCallback((id: string): string | null => {
|
|
if (thumbnails.has(id)) return thumbnails.get(id) ?? '';
|
|
return null;
|
|
}, [thumbnails]);
|
|
|
|
return { get };
|
|
};
|