mirror of
https://github.com/duckietm/Nitro-V3.git
synced 2026-06-19 15:06:20 +00:00
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:
@@ -1,7 +1,7 @@
|
|||||||
import { GetSessionDataManager } from '@nitrots/nitro-renderer';
|
|
||||||
import { FC, useState } from 'react';
|
import { FC, useState } from 'react';
|
||||||
import { CalendarItemState, ICalendarItem, LocalizeText } from '../../api';
|
import { CalendarItemState, ICalendarItem, LocalizeText } from '../../api';
|
||||||
import { Button, Column, Grid, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../common';
|
import { Button, Column, Grid, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../common';
|
||||||
|
import { useIsModerator } from '../../hooks';
|
||||||
import { CalendarItemView } from './CalendarItemView';
|
import { CalendarItemView } from './CalendarItemView';
|
||||||
|
|
||||||
interface CalendarViewProps
|
interface CalendarViewProps
|
||||||
@@ -23,6 +23,7 @@ export const CalendarView: FC<CalendarViewProps> = props =>
|
|||||||
const { onClose = null, campaignName = null, currentDay = null, numDays = null, missedDays = null, openedDays = null, openPackage = null, receivedProducts = null } = props;
|
const { onClose = null, campaignName = null, currentDay = null, numDays = null, missedDays = null, openedDays = null, openPackage = null, receivedProducts = null } = props;
|
||||||
const [ selectedDay, setSelectedDay ] = useState(currentDay);
|
const [ selectedDay, setSelectedDay ] = useState(currentDay);
|
||||||
const [ index, setIndex ] = useState(Math.max(0, (selectedDay - 1)));
|
const [ index, setIndex ] = useState(Math.max(0, (selectedDay - 1)));
|
||||||
|
const isModerator = useIsModerator();
|
||||||
|
|
||||||
const getDayState = (day: number) =>
|
const getDayState = (day: number) =>
|
||||||
{
|
{
|
||||||
@@ -109,7 +110,7 @@ export const CalendarView: FC<CalendarViewProps> = props =>
|
|||||||
<Text>{ dayMessage(selectedDay) }</Text>
|
<Text>{ dayMessage(selectedDay) }</Text>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{ GetSessionDataManager().isModerator &&
|
{ isModerator &&
|
||||||
<Button variant="danger" onClick={ forceOpen }>Force open</Button> }
|
<Button variant="danger" onClick={ forceOpen }>Force open</Button> }
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { AddLinkEventTracker, GetSessionDataManager, ILinkEventTracker, RemoveLinkEventTracker } from '@nitrots/nitro-renderer';
|
import { AddLinkEventTracker, ILinkEventTracker, RemoveLinkEventTracker } from '@nitrots/nitro-renderer';
|
||||||
import { FC, useEffect } from 'react';
|
import { FC, useEffect } from 'react';
|
||||||
import { FaCog, FaEdit, FaEye, FaEyeSlash, FaPlus, FaTrash } from 'react-icons/fa';
|
import { FaCog, FaEdit, FaEye, FaEyeSlash, FaPlus, FaTrash } from 'react-icons/fa';
|
||||||
import { CatalogType, GetConfigurationValue, LocalizeText } from '../../api';
|
import { CatalogType, GetConfigurationValue, LocalizeText } from '../../api';
|
||||||
import { Column, Grid, NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView } from '../../common';
|
import { Column, Grid, NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView } from '../../common';
|
||||||
import { useCatalogActions, useCatalogData, useCatalogUiState } from '../../hooks';
|
import { useCatalogActions, useCatalogData, useCatalogUiState, useIsModerator } from '../../hooks';
|
||||||
import { CatalogAdminProvider, useCatalogAdmin } from './CatalogAdminContext';
|
import { CatalogAdminProvider, useCatalogAdmin } from './CatalogAdminContext';
|
||||||
import { CatalogAdminOfferEditView } from './views/admin/CatalogAdminOfferEditView';
|
import { CatalogAdminOfferEditView } from './views/admin/CatalogAdminOfferEditView';
|
||||||
import { CatalogAdminPageEditView } from './views/admin/CatalogAdminPageEditView';
|
import { CatalogAdminPageEditView } from './views/admin/CatalogAdminPageEditView';
|
||||||
@@ -28,7 +28,7 @@ const CatalogClassicViewInner: FC<{}> = () =>
|
|||||||
{});
|
{});
|
||||||
const loading = catalogAdmin?.loading ?? false;
|
const loading = catalogAdmin?.loading ?? false;
|
||||||
|
|
||||||
const isMod = GetSessionDataManager().isModerator;
|
const isMod = useIsModerator();
|
||||||
const buildersClubHeaderStyle = (currentType === CatalogType.BUILDER)
|
const buildersClubHeaderStyle = (currentType === CatalogType.BUILDER)
|
||||||
? { borderColor: '#d79d2e', borderBottomColor: '#000', background: 'linear-gradient(180deg, #d89f2d 0%, #c68515 100%)' }
|
? { borderColor: '#d79d2e', borderBottomColor: '#000', background: 'linear-gradient(180deg, #d89f2d 0%, #c68515 100%)' }
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { AddLinkEventTracker, GetSessionDataManager, ILinkEventTracker, RemoveLinkEventTracker } from '@nitrots/nitro-renderer';
|
import { AddLinkEventTracker, ILinkEventTracker, RemoveLinkEventTracker } from '@nitrots/nitro-renderer';
|
||||||
import { FC, useEffect, useState } from 'react';
|
import { FC, useEffect, useState } from 'react';
|
||||||
import { FaCog, FaEdit, FaEye, FaEyeSlash, FaHeart, FaPlus, FaStar, FaTrash } from 'react-icons/fa';
|
import { FaCog, FaEdit, FaEye, FaEyeSlash, FaHeart, FaPlus, FaStar, FaTrash } from 'react-icons/fa';
|
||||||
import { CatalogType, LocalizeText } from '../../api';
|
import { CatalogType, LocalizeText } from '../../api';
|
||||||
import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../common';
|
import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../common';
|
||||||
import { useCatalogActions, useCatalogData, useCatalogFavorites, useCatalogUiState } from '../../hooks';
|
import { useCatalogActions, useCatalogData, useCatalogFavorites, useCatalogUiState, useIsModerator } from '../../hooks';
|
||||||
import { CatalogAdminProvider, useCatalogAdmin } from './CatalogAdminContext';
|
import { CatalogAdminProvider, useCatalogAdmin } from './CatalogAdminContext';
|
||||||
import { CatalogAdminOfferEditView } from './views/admin/CatalogAdminOfferEditView';
|
import { CatalogAdminOfferEditView } from './views/admin/CatalogAdminOfferEditView';
|
||||||
import { CatalogAdminPageEditView } from './views/admin/CatalogAdminPageEditView';
|
import { CatalogAdminPageEditView } from './views/admin/CatalogAdminPageEditView';
|
||||||
@@ -32,7 +32,7 @@ const CatalogModernViewInner: FC<{}> = () =>
|
|||||||
const { favoriteOfferIds, favoritePageIds } = useCatalogFavorites();
|
const { favoriteOfferIds, favoritePageIds } = useCatalogFavorites();
|
||||||
const [ showFavorites, setShowFavorites ] = useState(false);
|
const [ showFavorites, setShowFavorites ] = useState(false);
|
||||||
|
|
||||||
const isMod = GetSessionDataManager().isModerator;
|
const isMod = useIsModerator();
|
||||||
const totalFavs = favoriteOfferIds.length + favoritePageIds.length;
|
const totalFavs = favoriteOfferIds.length + favoritePageIds.length;
|
||||||
const buildersClubHeaderStyle = (currentType === CatalogType.BUILDER)
|
const buildersClubHeaderStyle = (currentType === CatalogType.BUILDER)
|
||||||
? { borderColor: '#d79d2e', borderBottomColor: '#000', background: 'linear-gradient(180deg, #d89f2d 0%, #c68515 100%)' }
|
? { borderColor: '#d79d2e', borderBottomColor: '#000', background: 'linear-gradient(180deg, #d89f2d 0%, #c68515 100%)' }
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { FurniturePickupAllComposer, GetSessionDataManager } from '@nitrots/nitro-renderer';
|
import { FurniturePickupAllComposer } from '@nitrots/nitro-renderer';
|
||||||
import { FC, useEffect, useEffectEvent, useMemo, useState } from 'react';
|
import { FC, useEffect, useEffectEvent, useMemo, useState } from 'react';
|
||||||
import { LocalizeText, RoomObjectItem, SendMessageComposer, chooserSelectionVisualizer } from '../../../../api';
|
import { LocalizeText, RoomObjectItem, SendMessageComposer, chooserSelectionVisualizer } from '../../../../api';
|
||||||
import { Button, Flex, InfiniteScroll, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../../common';
|
import { Button, Flex, InfiniteScroll, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../../common';
|
||||||
|
import { useIsModerator } from '../../../../hooks';
|
||||||
import { NitroInput, classNames } from '../../../../layout';
|
import { NitroInput, classNames } from '../../../../layout';
|
||||||
|
|
||||||
const LIMIT_FURNI_PICKALL = 100;
|
const LIMIT_FURNI_PICKALL = 100;
|
||||||
@@ -23,7 +24,7 @@ export const ChooserWidgetView: FC<ChooserWidgetViewProps> = props =>
|
|||||||
const [ searchValue, setSearchValue ] = useState('');
|
const [ searchValue, setSearchValue ] = useState('');
|
||||||
const [ checkAll, setCheckAll ] = useState(false);
|
const [ checkAll, setCheckAll ] = useState(false);
|
||||||
const [ checkedIds, setCheckedIds ] = useState<number[]>([]);
|
const [ checkedIds, setCheckedIds ] = useState<number[]>([]);
|
||||||
const canSeeId = GetSessionDataManager().isModerator;
|
const canSeeId = useIsModerator();
|
||||||
|
|
||||||
const ownerNames = useMemo(() =>
|
const ownerNames = useMemo(() =>
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { CreateLinkEvent, Dispose, DropBounce, EaseOut, GetSessionDataManager, JumpBy, Motions, NitroToolbarAnimateIconEvent, PerkAllowancesMessageEvent, PerkEnum, Queue, Wait, YouTubeRoomSettingsEvent } from '@nitrots/nitro-renderer';
|
import { CreateLinkEvent, Dispose, DropBounce, EaseOut, JumpBy, Motions, NitroToolbarAnimateIconEvent, PerkAllowancesMessageEvent, PerkEnum, Queue, Wait, YouTubeRoomSettingsEvent } from '@nitrots/nitro-renderer';
|
||||||
import { AnimatePresence, motion, Variants } from 'framer-motion';
|
import { AnimatePresence, motion, Variants } from 'framer-motion';
|
||||||
import { FC, useEffect, useState } from 'react';
|
import { FC, useEffect, useState } from 'react';
|
||||||
import { GetConfigurationValue, MessengerIconState, OpenMessengerChat, setYoutubeRoomEnabled, VisitDesktop } from '../../api';
|
import { GetConfigurationValue, MessengerIconState, OpenMessengerChat, setYoutubeRoomEnabled, VisitDesktop } from '../../api';
|
||||||
import { Flex, LayoutAvatarImageView, LayoutItemCountView } from '../../common';
|
import { Flex, LayoutAvatarImageView, LayoutItemCountView } from '../../common';
|
||||||
import { useAchievements, useFriends, useInventoryUnseenTracker, useMessageEvent, useMessenger, useNitroEvent, useSessionInfo, useWiredTools } from '../../hooks';
|
import { useAchievements, useFriends, useInventoryUnseenTracker, useIsModerator, useMessageEvent, useMessenger, useNitroEvent, useSessionInfo, useWiredTools } from '../../hooks';
|
||||||
import { ToolbarItemView } from './ToolbarItemView';
|
import { ToolbarItemView } from './ToolbarItemView';
|
||||||
import { ToolbarMeView } from './ToolbarMeView';
|
import { ToolbarMeView } from './ToolbarMeView';
|
||||||
import { YouTubePlayerView } from './YouTubePlayerView';
|
import { YouTubePlayerView } from './YouTubePlayerView';
|
||||||
@@ -49,7 +49,7 @@ export const ToolbarView: FC<{ isInRoom: boolean }> = props =>
|
|||||||
const { requests = [] } = useFriends();
|
const { requests = [] } = useFriends();
|
||||||
const { iconState = MessengerIconState.HIDDEN } = useMessenger();
|
const { iconState = MessengerIconState.HIDDEN } = useMessenger();
|
||||||
const { openMonitor, showToolbarButton } = useWiredTools();
|
const { openMonitor, showToolbarButton } = useWiredTools();
|
||||||
const isMod = GetSessionDataManager().isModerator;
|
const isMod = useIsModerator();
|
||||||
const isVisible = (isToolbarOpen || !isInRoom);
|
const isVisible = (isToolbarOpen || !isInRoom);
|
||||||
const visibilityVariant = isVisible ? 'visible' : 'hidden';
|
const visibilityVariant = isVisible ? 'visible' : 'hidden';
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { ControlYoutubeDisplayPlaybackMessageComposer, YouTubeRoomBroadcastEvent, YouTubeRoomPlayComposer, YouTubeRoomSettingsEvent, YouTubeRoomWatchersEvent, YouTubeRoomWatchingComposer } from '@nitrots/nitro-renderer';
|
import { ControlYoutubeDisplayPlaybackMessageComposer, YouTubeRoomBroadcastEvent, YouTubeRoomPlayComposer, YouTubeRoomSettingsEvent, YouTubeRoomWatchersEvent, YouTubeRoomWatchingComposer } from '@nitrots/nitro-renderer';
|
||||||
import { FC, useEffect, useRef, useState } from 'react';
|
import { FC, useEffect, useRef, useState } from 'react';
|
||||||
import ReactPlayer from 'react-player/youtube';
|
import ReactPlayer from 'react-player/youtube';
|
||||||
import { GetRoomSession, getYoutubeRoomEnabled, GetSessionDataManager, LocalizeText, SendMessageComposer, YoutubeVideoPlaybackStateEnum } from '../../api';
|
import { GetRoomSession, getYoutubeRoomEnabled, LocalizeText, SendMessageComposer, YoutubeVideoPlaybackStateEnum } from '../../api';
|
||||||
import { NitroCardContentView, NitroCardHeaderView, NitroCardView, LayoutAvatarImageView } from '../../common';
|
import { NitroCardContentView, NitroCardHeaderView, NitroCardView, LayoutAvatarImageView } from '../../common';
|
||||||
import { useFurnitureYoutubeWidget, useMessageEvent } from '../../hooks';
|
import { useFurnitureYoutubeWidget, useIsModerator, useMessageEvent } from '../../hooks';
|
||||||
|
|
||||||
const CONTROL_COMMAND_PREVIOUS_VIDEO = 0;
|
const CONTROL_COMMAND_PREVIOUS_VIDEO = 0;
|
||||||
const CONTROL_COMMAND_NEXT_VIDEO = 1;
|
const CONTROL_COMMAND_NEXT_VIDEO = 1;
|
||||||
@@ -46,6 +46,9 @@ export const YouTubePlayerView: FC<{}> = () =>
|
|||||||
const [broadcastPlaylist, setBroadcastPlaylist] = useState<string[]>([]);
|
const [broadcastPlaylist, setBroadcastPlaylist] = useState<string[]>([]);
|
||||||
const [watcherIds, setWatcherIds] = useState<Set<number>>(new Set());
|
const [watcherIds, setWatcherIds] = useState<Set<number>>(new Set());
|
||||||
const [youtubeEnabled, setYoutubeEnabled] = useState(getYoutubeRoomEnabled());
|
const [youtubeEnabled, setYoutubeEnabled] = useState(getYoutubeRoomEnabled());
|
||||||
|
// Reactive — must sit above the `if (!isOpen) return null` below
|
||||||
|
// so the hook order stays stable across renders.
|
||||||
|
const isModerator = useIsModerator();
|
||||||
|
|
||||||
useMessageEvent<YouTubeRoomSettingsEvent>(YouTubeRoomSettingsEvent, event =>
|
useMessageEvent<YouTubeRoomSettingsEvent>(YouTubeRoomSettingsEvent, event =>
|
||||||
{
|
{
|
||||||
@@ -270,7 +273,7 @@ export const YouTubePlayerView: FC<{}> = () =>
|
|||||||
const isPlaying = currentVideoState === YoutubeVideoPlaybackStateEnum.PLAYING;
|
const isPlaying = currentVideoState === YoutubeVideoPlaybackStateEnum.PLAYING;
|
||||||
const isPaused = currentVideoState === YoutubeVideoPlaybackStateEnum.PAUSED;
|
const isPaused = currentVideoState === YoutubeVideoPlaybackStateEnum.PAUSED;
|
||||||
const roomSession = GetRoomSession();
|
const roomSession = GetRoomSession();
|
||||||
const isMyRoom = GetSessionDataManager().isModerator || (roomSession && roomSession.isRoomOwner);
|
const isMyRoom = isModerator || (roomSession && roomSession.isRoomOwner);
|
||||||
|
|
||||||
const QuickVolumeButton = ({
|
const QuickVolumeButton = ({
|
||||||
value,
|
value,
|
||||||
|
|||||||
@@ -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 { useMemo } from 'react';
|
||||||
import { useExternalSnapshot } from '../events/useExternalSnapshot';
|
import { useExternalSnapshot } from '../events/useExternalSnapshot';
|
||||||
|
|
||||||
@@ -123,6 +123,20 @@ export const useIsUserIgnored = (name: string): boolean =>
|
|||||||
return useMemo(() => list.includes(name), [ list, name ]);
|
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> =>
|
export const useGroupBadgesSnapshot = (): ReadonlyMap<number, string> =>
|
||||||
useExternalSnapshot(
|
useExternalSnapshot(
|
||||||
subscribeTo(NitroEventType.GROUP_BADGES_UPDATED),
|
subscribeTo(NitroEventType.GROUP_BADGES_UPDATED),
|
||||||
|
|||||||
Reference in New Issue
Block a user