refactor(useChatWidget,useAvatarInfoWidget): reactive ownUserId + typed avatar-click-control

Two small modernization wins on the previously skip-motivated god-hooks.
Neither hook lends itself to the data/actions split, but both had
concrete imperative-style residue worth tidying:

== useChatWidget

Replace `const ownUserId = GetSessionDataManager()?.userId || -1;` with
`useUserDataSnapshot().userId`. The previous read happened at hook mount
and stayed pinned to whatever userId the manager held at that point —
a session change (re-login without page reload) would silently corrupt
the outgoing-translation owner check below. With the snapshot hook,
the value updates reactively via SESSION_DATA_UPDATED and the
useNitroEvent re-registration picks up the fresh ownUserId for every
incoming chat event.

== useAvatarInfoWidget

Two tidy points:

- CLICK_USER_DEBOUNCE_MS (the 120ms window during which a directional
  click suppresses the context menu) lifted from inside the hook body
  to a module-level const. It's never going to change at runtime and
  doesn't depend on hook state — keeping it inside meant it was
  redeclared on every render.

- The `(globalThis as any).__nitroAvatarClickControl` read replaced by
  a typed `getAvatarClickControl()` helper backed by a proper
  `NitroAvatarClickControl` interface. Same runtime behaviour; type
  channel no longer goes through `any`, and the symbol is documented
  in one place above the hook.

Public APIs of both hooks unchanged. Suite: 207/207.
This commit is contained in:
simoleo89
2026-05-18 21:48:17 +02:00
parent 19b48513d8
commit 05ff7df7d2
2 changed files with 21 additions and 4 deletions
+14 -2
View File
@@ -8,9 +8,21 @@ import { useObjectDeselectedEvent, useObjectRollOutEvent, useObjectRollOverEvent
import { useRoom } from '../useRoom'; import { useRoom } from '../useRoom';
import { applyFavouriteGroupUpdate, applyUserBadgesUpdate, applyUserFigureUpdate } from './avatarInfo.reducers'; import { applyFavouriteGroupUpdate, applyUserBadgesUpdate, applyUserFigureUpdate } from './avatarInfo.reducers';
// Time window after a directional / move-state user click during
// which the context menu must NOT open. Set on `globalThis.__nitroAvatarClickControl`
// by the click-routing code in the room engine.
const CLICK_USER_DEBOUNCE_MS = 120;
interface NitroAvatarClickControl
{
suppressMenuUntil: number;
}
const getAvatarClickControl = (): NitroAvatarClickControl | null =>
(globalThis as unknown as { __nitroAvatarClickControl?: NitroAvatarClickControl }).__nitroAvatarClickControl ?? null;
const useAvatarInfoWidgetState = () => const useAvatarInfoWidgetState = () =>
{ {
const CLICK_USER_DEBOUNCE_MS = 120;
const [ avatarInfo, setAvatarInfo ] = useState<IAvatarInfo>(null); const [ avatarInfo, setAvatarInfo ] = useState<IAvatarInfo>(null);
const [ activeNameBubble, setActiveNameBubble ] = useState<AvatarInfoName>(null); const [ activeNameBubble, setActiveNameBubble ] = useState<AvatarInfoName>(null);
const [ nameBubbles, setNameBubbles ] = useState<AvatarInfoName[]>([]); const [ nameBubbles, setNameBubbles ] = useState<AvatarInfoName[]>([]);
@@ -33,7 +45,7 @@ const useAvatarInfoWidgetState = () =>
const isAvatarMenuBlocked = () => const isAvatarMenuBlocked = () =>
{ {
const control = (globalThis as any).__nitroAvatarClickControl; const control = getAvatarClickControl();
return !!control && (control.suppressMenuUntil > Date.now()); return !!control && (control.suppressMenuUntil > Date.now());
}; };
+7 -2
View File
@@ -1,7 +1,8 @@
import { GetGuestRoomResultEvent, GetRoomEngine, GetSessionDataManager, PetFigureData, RoomChatSettings, RoomChatSettingsEvent, RoomDragEvent, RoomObjectCategory, RoomObjectType, RoomObjectVariable, RoomSessionChatEvent, RoomUserData, SystemChatStyleEnum } from '@nitrots/nitro-renderer'; import { GetGuestRoomResultEvent, GetRoomEngine, PetFigureData, RoomChatSettings, RoomChatSettingsEvent, RoomDragEvent, RoomObjectCategory, RoomObjectType, RoomObjectVariable, RoomSessionChatEvent, RoomUserData, SystemChatStyleEnum } from '@nitrots/nitro-renderer';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { ChatBubbleMessage, ChatBubbleUtilities, ChatEntryType, ChatHistoryCurrentDate, GetConfigurationValue, GetRoomObjectScreenLocation, IRoomChatSettings, LocalizeText, PlaySound, RoomChatFormatter } from '../../../api'; import { ChatBubbleMessage, ChatBubbleUtilities, ChatEntryType, ChatHistoryCurrentDate, GetConfigurationValue, GetRoomObjectScreenLocation, IRoomChatSettings, LocalizeText, PlaySound, RoomChatFormatter } from '../../../api';
import { useMessageEvent, useNitroEvent } from '../../events'; import { useMessageEvent, useNitroEvent } from '../../events';
import { useUserDataSnapshot } from '../../session/useSessionSnapshots';
import { useTranslation } from '../../translation'; import { useTranslation } from '../../translation';
import { useRoom } from '../useRoom'; import { useRoom } from '../useRoom';
import { useChatHistory } from './../../chat-history'; import { useChatHistory } from './../../chat-history';
@@ -22,7 +23,11 @@ const useChatWidgetState = () =>
const { addChatEntry, updateChatEntry } = useChatHistory(); const { addChatEntry, updateChatEntry } = useChatHistory();
const { settings, translateIncoming, consumeOutgoingTranslation } = useTranslation(); const { settings, translateIncoming, consumeOutgoingTranslation } = useTranslation();
const isDisposed = useRef(false); const isDisposed = useRef(false);
const ownUserId = (GetSessionDataManager()?.userId || -1); // Reactive: a session change (re-login without reload) immediately
// updates the outgoing-translation owner check below. Old code read
// GetSessionDataManager()?.userId at hook mount and would have stayed
// stuck on the previous session's id.
const ownUserId = (useUserDataSnapshot().userId || -1);
const applyTranslationToBubble = useCallback((chatMessage: ChatBubbleMessage, originalText: string, translatedText: string, detectedLanguage: string, targetLanguage: string) => const applyTranslationToBubble = useCallback((chatMessage: ChatBubbleMessage, originalText: string, translatedText: string, detectedLanguage: string, targetLanguage: string) =>
{ {