feat(hooks): useIsModerator() + migrate 6 component reads

Adds a reactive `useIsModerator()` derived from
`useUserDataSnapshot().securityLevel >= SecurityLevel.MODERATOR`
(mirrors the renderer-side getter at SessionDataManager.ts:684), and
migrates the six React component-body reads of
`GetSessionDataManager().isModerator`:

- ToolbarView (mod-only chat-input button)
- CatalogClassicView, CatalogModernView (admin toggles in catalog
  header)
- ChooserWidgetView (room-object id column visibility)
- YouTubePlayerView (room-control affordance — hook moved above the
  `if (!isOpen) return null` early return so the hook order stays
  stable when the player opens/closes)
- CalendarView (mod-only "open all" affordance)

UX impact: any future promote/demote that flips
SESSION_DATA_UPDATED now re-renders the mod-only UI live, instead of
requiring an F5. Imperative call sites
(AvatarInfoUtilities.populate*, CanManipulateFurniture,
RoomChatHandler) still read the manager directly — they run at click
time, not in a React render, so reactivity has no upside there.

Five of the six call sites are top-level component-body reads (no
early-return interaction). YouTubePlayerView has an
`if (!isOpen) return null` below the hook list, so the hook had to
move ABOVE it; same shape as the recent CatalogPurchaseWidgetView and
CatalogItemGridWidgetView fixes.

Verification: yarn typecheck clean, yarn lint:hooks clean, yarn test
209/209.
This commit is contained in:
simoleo89
2026-05-19 18:07:17 +02:00
parent 3459400ed7
commit 532cb28ca7
7 changed files with 36 additions and 17 deletions
+15 -1
View File
@@ -1,4 +1,4 @@
import { GetEventDispatcher, GetRoomSessionManager, GetSessionDataManager, GetSoundManager, IRoomSessionSnapshot, IRoomUserData, ISoundVolumesSnapshot, IUserDataSnapshot, NitroEventType } from '@nitrots/nitro-renderer';
import { GetEventDispatcher, GetRoomSessionManager, GetSessionDataManager, GetSoundManager, IRoomSessionSnapshot, IRoomUserData, ISoundVolumesSnapshot, IUserDataSnapshot, NitroEventType, SecurityLevel } from '@nitrots/nitro-renderer';
import { useMemo } from 'react';
import { useExternalSnapshot } from '../events/useExternalSnapshot';
@@ -123,6 +123,20 @@ export const useIsUserIgnored = (name: string): boolean =>
return useMemo(() => list.includes(name), [ list, name ]);
};
/**
* Reactive equivalent of `GetSessionDataManager().isModerator`. Derives
* from `useUserDataSnapshot().securityLevel` so any future
* promote/demote that flips the SESSION_DATA_UPDATED event re-renders
* the consumer without an F5. Mirrors the renderer-side getter at
* `SessionDataManager.ts:684` (`securityLevel >= SecurityLevel.MODERATOR`).
*/
export const useIsModerator = (): boolean =>
{
const userData = useUserDataSnapshot();
return userData.securityLevel >= SecurityLevel.MODERATOR;
};
export const useGroupBadgesSnapshot = (): ReadonlyMap<number, string> =>
useExternalSnapshot(
subscribeTo(NitroEventType.GROUP_BADGES_UPDATED),