mirror of
https://github.com/TautvydasDerzinskas/Thingport.git
synced 2026-10-21 22:22:24 -05:00
fix: ignore stale card preview saves and render checks
Queue card-preview saves so the last pick sticks, drop failures from superseded picks or a previous account, and ignore a render check that returns after its ModelRender is gone.
This commit is contained in:
1 parent
ddfe33a4bd
commit
e76666e291
2 files changed
+23
-5
No files matched your search
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useState, type ReactNode } from "react";
|
||||
import { useEffect, useRef, useState, type ReactNode } from "react";
|
||||
import { keyframes } from "@emotion/react";
|
||||
import Box from "@mui/material/Box";
|
||||
import Skeleton from "@mui/material/Skeleton";
|
||||
@@ -34,6 +34,14 @@ export default function ModelRender({ url, alt, onUnavailable, placeholder }: Pr
|
||||
const [attempt, setAttempt] = useState(0);
|
||||
const [waiting, setWaiting] = useState(false);
|
||||
const [loaded, setLoaded] = useState(false);
|
||||
// A check still out when the model changes or the card goes must not report on the next one.
|
||||
const mounted = useRef(true);
|
||||
useEffect(() => {
|
||||
mounted.current = true;
|
||||
return () => {
|
||||
mounted.current = false;
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (attempt > RETRY_DELAYS_MS.length) onUnavailable();
|
||||
@@ -89,6 +97,7 @@ export default function ModelRender({ url, alt, onUnavailable, placeholder }: Pr
|
||||
.then((res) => res.json() as Promise<{ code?: string }>)
|
||||
.catch(() => ({ code: undefined }))
|
||||
.then(({ code }) => {
|
||||
if (!mounted.current) return;
|
||||
if (code && code !== "PREVIEW_GENERATING") return onUnavailable();
|
||||
setAttempt((count) => count + 1);
|
||||
setWaiting(true);
|
||||
|
||||
@@ -7,6 +7,9 @@ let cached: CardPreview | undefined;
|
||||
let inFlight: Promise<CardPreview> | null = null;
|
||||
// Bumped on sign-in, so an answer still on its way for the previous account is dropped.
|
||||
let account = 0;
|
||||
// Saves go one at a time, so the last pick is the one that sticks.
|
||||
let saving: Promise<unknown> = Promise.resolve();
|
||||
let latestSave = 0;
|
||||
const listeners = new Set<(value: CardPreview) => void>();
|
||||
|
||||
function load(): Promise<CardPreview> {
|
||||
@@ -35,10 +38,11 @@ export function resetCardPreview() {
|
||||
account++;
|
||||
cached = undefined;
|
||||
inFlight = null;
|
||||
saving = Promise.resolve();
|
||||
}
|
||||
|
||||
/** Null until the saved choice is known, so cards don't flash the wrong picture first. The setter shows the switch
|
||||
* straight away, puts it back if the save fails, and rejects with the save's error. */
|
||||
* straight away, puts it back if the latest save fails, and rejects with that save's error. */
|
||||
export function useCardPreview(): [CardPreview | null, (preview: CardPreview) => Promise<void>] {
|
||||
const [value, setValue] = useState<CardPreview | null>(cached ?? null);
|
||||
|
||||
@@ -56,12 +60,17 @@ export function useCardPreview(): [CardPreview | null, (preview: CardPreview) =>
|
||||
|
||||
const update = async (preview: CardPreview) => {
|
||||
const previous = cached ?? "images";
|
||||
const save = ++latestSave;
|
||||
const forAccount = account;
|
||||
publish(preview);
|
||||
const request = saving.then(() => (forAccount === account ? settingsApi.updateCardPreview(preview) : undefined));
|
||||
saving = request.catch(() => undefined);
|
||||
try {
|
||||
await settingsApi.updateCardPreview(preview);
|
||||
await request;
|
||||
} catch (err) {
|
||||
// Only if nothing newer was picked meanwhile.
|
||||
if (cached === preview) publish(previous);
|
||||
// A newer pick, or another account, has taken over; this failure is no longer theirs to hear about.
|
||||
if (save !== latestSave || forAccount !== account) return;
|
||||
publish(previous);
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
Reference in new issue
Block a user