From 1ad5be0ff666821c9d57b4f57b84ba6ced6a0fdf Mon Sep 17 00:00:00 2001 From: bbassie <17990055+bbassie@users.noreply.github.com> Date: Sat, 17 Oct 2026 19:12:12 +0000 Subject: [PATCH] Keep each player's drawer state separate An import, delete or release for one player that finished after another player was selected reloaded the drawer with the first player's data under the second player's name. The drawer body is now keyed by player, so a late completion can only update the view it was started from. Co-Authored-By: Claude Opus 5.5 --- plugins/player_sync/web/index.tsx | 40 ++++++++++++++++--------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/plugins/player_sync/web/index.tsx b/plugins/player_sync/web/index.tsx index 49535e6a..38f101fc 100644 --- a/plugins/player_sync/web/index.tsx +++ b/plugins/player_sync/web/index.tsx @@ -84,20 +84,30 @@ function InventoryTable(props: { name: string, items: ItemSummary[] }) { } function PlayerDrawer(props: { playerName?: string, onClose: () => void, onChange: () => void }) { + return + {/* Keyed so a request that finishes after switching players can only update the old player's view */} + {props.playerName !== undefined + && } + ; +} + +function PlayerDetails(props: { playerName: string, onChange: () => void }) { const control = useContext(ControlContext); const account = useAccount(); const instanceName = useInstanceName(); const [response, setResponse] = useState(); const { playerName } = props; - // Only the latest request may set the response, older ones may be for another player + // Only the latest request may set the response, an import or delete can finish after a reload const requestId = useRef(0); const reload = useCallback(() => { requestId.current += 1; let id = requestId.current; - if (playerName === undefined) { - return; - } control.send(new GetPlayerDataRequest(playerName)).then(result => { if (id === requestId.current) { setResponse(result); @@ -106,10 +116,7 @@ function PlayerDrawer(props: { playerName?: string, onClose: () => void, onChang notifyErrorHandler("Error loading player data") ); }, [control, playerName]); - useEffect(() => { - setResponse(undefined); - reload(); - }, [reload]); + useEffect(reload, [reload]); function changed() { reload(); @@ -124,9 +131,9 @@ function PlayerDrawer(props: { playerName?: string, onClose: () => void, onChang notify("Invalid JSON", "error", err.message); return; } - playerData.name = playerName!; + playerData.name = playerName; playerData.generation ??= 0; - let result = await control.send(new SetPlayerDataRequest(playerName!, playerData)); + let result = await control.send(new SetPlayerDataRequest(playerName, playerData)); notify(`Stored player data for ${playerName} as generation ${result.generation}`); changed(); } @@ -144,12 +151,7 @@ function PlayerDrawer(props: { playerName?: string, onClose: () => void, onChang const canModify = account.hasPermission("player_sync.inventory.modify"); const playerData = response?.playerData; const acquiredBy = response?.instanceId !== undefined ? instanceName(response.instanceId) : undefined; - return + return <> {response && {acquiredBy !== undefined && void, onChang } action={canModify && control.send(new ForceReleaseRequest(playerName!)).then(changed).catch( + onConfirm={() => control.send(new ForceReleaseRequest(playerName)).then(changed).catch( notifyErrorHandler("Error releasing lock") )} > @@ -195,7 +197,7 @@ function PlayerDrawer(props: { playerName?: string, onClose: () => void, onChang } okText="Delete" okButtonProps={{ danger: true }} - onConfirm={() => control.send(new DeletePlayerDataRequest(playerName!)).then(changed).catch( + onConfirm={() => control.send(new DeletePlayerDataRequest(playerName)).then(changed).catch( notifyErrorHandler("Error deleting player data") )} > @@ -207,7 +209,7 @@ function PlayerDrawer(props: { playerName?: string, onClose: () => void, onChang inventory => )} } - ; + ; } function InventoryPage() {