mirror of
https://github.com/duckietm/Nitro-V3.git
synced 2026-06-19 15:06:20 +00:00
feat(mod-tools): reactive box + bug fixes in useModTools
ModToolsView
- Subscribe to useRoomUserListSnapshot so the selected user's
"still in room" state is reactive — green dot when the user is
in the current room, gray dot when they've left. Previously the
selection was a static capture at click time.
- Add an inline X to clear the selected-user slot without having
to click a different avatar.
- Report Tool button shows a count badge for OPEN tickets
(IssueMessageData.STATE_OPEN) so a new ticket arriving while
the panel is open is visible immediately. Caps display at 99+.
- Tooltip on the room-bound buttons explains why they're disabled
("Enter a room first") instead of showing a silent disabled state.
- Buttons grow their labels with `flex-grow text-start` so the
trailing dot / badge / clear-X sits flush right.
useModTools
- Fix splice(index) → splice(index, 1) in close{Room,RoomChatlog,
UserInfo,UserChatlog} — the omitted second argument was
silently deleting EVERY subsequent open panel, not just the one
being closed. Visible whenever a moderator had two or more panels
of the same kind open.
- Fix toggleUserChatlog reading from openRoomChatlogs instead of
openUserChatlogs — copy-paste typo made the toggle inconsistent
with the underlying state.
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
import { AddLinkEventTracker, CreateLinkEvent, ILinkEventTracker, RemoveLinkEventTracker, RoomEngineEvent, RoomId, RoomObjectCategory, RoomObjectType } from '@nitrots/nitro-renderer';
|
||||
import { FC, useEffect, useRef, useState } from 'react';
|
||||
import { GetRoomSession, ISelectedUser } from '../../api';
|
||||
import { FC, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { FaTimes } from 'react-icons/fa';
|
||||
import { GetRoomSession, ISelectedUser, LocalizeText } from '../../api';
|
||||
import { Button, DraggableWindowPosition, NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../common';
|
||||
import { useModTools, useNitroEvent, useObjectSelectedEvent } from '../../hooks';
|
||||
import { useModTools, useNitroEvent, useObjectSelectedEvent, useRoomUserListSnapshot } from '../../hooks';
|
||||
import { ModToolsChatlogView } from './views/room/ModToolsChatlogView';
|
||||
import { ModToolsRoomView } from './views/room/ModToolsRoomView';
|
||||
import { ModToolsTicketsView } from './views/tickets/ModToolsTicketsView';
|
||||
@@ -15,8 +16,24 @@ export const ModToolsView: FC<{}> = props =>
|
||||
const [ currentRoomId, setCurrentRoomId ] = useState<number>(-1);
|
||||
const [ selectedUser, setSelectedUser ] = useState<ISelectedUser>(null);
|
||||
const [ isTicketsVisible, setIsTicketsVisible ] = useState(false);
|
||||
const { openRooms = [], openRoomChatlogs = [], openUserChatlogs = [], openUserInfos = [], openRoomInfo = null, closeRoomInfo = null, toggleRoomInfo = null, openRoomChatlog = null, closeRoomChatlog = null, toggleRoomChatlog = null, openUserInfo = null, closeUserInfo = null, toggleUserInfo = null, openUserChatlog = null, closeUserChatlog = null, toggleUserChatlog = null } = useModTools();
|
||||
const { tickets = [], openRooms = [], openRoomChatlogs = [], openUserChatlogs = [], openUserInfos = [], openRoomInfo = null, closeRoomInfo = null, toggleRoomInfo = null, openRoomChatlog = null, closeRoomChatlog = null, toggleRoomChatlog = null, openUserInfo = null, closeUserInfo = null, toggleUserInfo = null, openUserChatlog = null, closeUserChatlog = null, toggleUserChatlog = null } = useModTools();
|
||||
const elementRef = useRef<HTMLDivElement>(null);
|
||||
// Reactive room roster — used to auto-clear the selected user if
|
||||
// they leave the room while the panel is open, and to show an
|
||||
// online dot on the selected-user button without going through
|
||||
// userDataManager imperatively on every render.
|
||||
const roomUserList = useRoomUserListSnapshot();
|
||||
// Count of OPEN tickets the moderator hasn't picked yet — shown
|
||||
// as a badge on the Report Tool button so a new ticket is visible
|
||||
// immediately, without forcing the user to click through.
|
||||
const openTicketsCount = useMemo(
|
||||
() => tickets.filter(ticket => ticket && (ticket.state === 1)).length,
|
||||
[ tickets ]
|
||||
);
|
||||
const isSelectedUserPresent = useMemo(
|
||||
() => !!(selectedUser && roomUserList.some(user => user && (user.webID === selectedUser.userId))),
|
||||
[ selectedUser, roomUserList ]
|
||||
);
|
||||
|
||||
useNitroEvent<RoomEngineEvent>([
|
||||
RoomEngineEvent.INITIALIZED,
|
||||
@@ -120,28 +137,59 @@ export const ModToolsView: FC<{}> = props =>
|
||||
const isRoomInfoOpen = currentRoomId > 0 && openRooms.includes(currentRoomId);
|
||||
const isRoomChatlogOpen = currentRoomId > 0 && openRoomChatlogs.includes(currentRoomId);
|
||||
const isUserInfoOpen = selectedUser && openUserInfos.includes(selectedUser.userId);
|
||||
const noRoomHint = LocalizeText('mod.tools.no.room') || 'Enter a room first';
|
||||
|
||||
return (
|
||||
<>
|
||||
{ isVisible &&
|
||||
<NitroCardView className="nitro-mod-tools min-w-[200px]" theme="primary-slim" uniqueKey="mod-tools" windowPosition={ DraggableWindowPosition.TOP_LEFT } >
|
||||
<NitroCardView className="nitro-mod-tools min-w-[220px]" theme="primary-slim" uniqueKey="mod-tools" windowPosition={ DraggableWindowPosition.TOP_LEFT } >
|
||||
<NitroCardHeaderView headerText={ 'Mod Tools' } onCloseClick={ event => setIsVisible(false) } />
|
||||
<NitroCardContentView className="text-black" gap={ 2 }>
|
||||
<Button active={ isRoomInfoOpen } disabled={ (currentRoomId <= 0) } gap={ 2 } justifyContent="start" onClick={ event => CreateLinkEvent(`mod-tools/toggle-room-info/${ currentRoomId }`) }>
|
||||
<div className="nitro-icon icon-small-room shrink-0" /> Room Tool
|
||||
<Button active={ isRoomInfoOpen } disabled={ (currentRoomId <= 0) } gap={ 2 } justifyContent="start" title={ (currentRoomId <= 0) ? noRoomHint : undefined } onClick={ event => CreateLinkEvent(`mod-tools/toggle-room-info/${ currentRoomId }`) }>
|
||||
<div className="nitro-icon icon-small-room shrink-0" />
|
||||
<span className="grow text-start">Room Tool</span>
|
||||
</Button>
|
||||
<Button active={ isRoomChatlogOpen } disabled={ (currentRoomId <= 0) } gap={ 2 } innerRef={ elementRef } justifyContent="start" onClick={ event => CreateLinkEvent(`mod-tools/toggle-room-chatlog/${ currentRoomId }`) }>
|
||||
<div className="nitro-icon icon-chat-history shrink-0" /> Chatlog Tool
|
||||
<Button active={ isRoomChatlogOpen } disabled={ (currentRoomId <= 0) } gap={ 2 } innerRef={ elementRef } justifyContent="start" title={ (currentRoomId <= 0) ? noRoomHint : undefined } onClick={ event => CreateLinkEvent(`mod-tools/toggle-room-chatlog/${ currentRoomId }`) }>
|
||||
<div className="nitro-icon icon-chat-history shrink-0" />
|
||||
<span className="grow text-start">Chatlog Tool</span>
|
||||
</Button>
|
||||
<Button active={ !!isUserInfoOpen } disabled={ !selectedUser } gap={ 2 } justifyContent="start" onClick={ () => CreateLinkEvent(`mod-tools/toggle-user-info/${ selectedUser.userId }`) }>
|
||||
<Button active={ !!isUserInfoOpen } disabled={ !selectedUser } gap={ 2 } justifyContent="start" onClick={ () => selectedUser && CreateLinkEvent(`mod-tools/toggle-user-info/${ selectedUser.userId }`) }>
|
||||
<div className="nitro-icon icon-user shrink-0" />
|
||||
{ selectedUser
|
||||
? <span className="truncate">{ selectedUser.username }</span>
|
||||
: <span className="opacity-50 italic">Select a user</span>
|
||||
? (
|
||||
<>
|
||||
<span className="truncate grow text-start">{ selectedUser.username }</span>
|
||||
<span
|
||||
aria-label={ isSelectedUserPresent ? 'In room' : 'Left room' }
|
||||
className={ `inline-block w-2 h-2 rounded-full shrink-0 ${ isSelectedUserPresent ? 'bg-emerald-500' : 'bg-zinc-400' }` }
|
||||
title={ isSelectedUserPresent ? 'Still in this room' : 'No longer in this room' }
|
||||
/>
|
||||
<span
|
||||
className="inline-flex items-center justify-center w-4 h-4 rounded text-xs text-zinc-500 hover:text-rose-600 hover:bg-rose-100 shrink-0"
|
||||
onClick={ event =>
|
||||
{
|
||||
event.stopPropagation();
|
||||
setSelectedUser(null);
|
||||
} }
|
||||
role="button"
|
||||
tabIndex={ 0 }
|
||||
title="Clear selection">
|
||||
<FaTimes />
|
||||
</span>
|
||||
</>
|
||||
)
|
||||
: <span className="opacity-50 italic grow text-start">Select a user</span>
|
||||
}
|
||||
</Button>
|
||||
<Button active={ isTicketsVisible } gap={ 2 } justifyContent="start" onClick={ () => setIsTicketsVisible(prevValue => !prevValue) }>
|
||||
<div className="nitro-icon icon-tickets shrink-0" /> Report Tool
|
||||
<div className="nitro-icon icon-tickets shrink-0" />
|
||||
<span className="grow text-start">Report Tool</span>
|
||||
{ (openTicketsCount > 0) &&
|
||||
<span
|
||||
className="inline-flex items-center justify-center min-w-[1.25rem] h-5 px-1 rounded-full bg-rose-500 text-white text-xs font-semibold shrink-0"
|
||||
title={ `${ openTicketsCount } open ticket${ openTicketsCount === 1 ? '' : 's' }` }>
|
||||
{ openTicketsCount > 99 ? '99+' : openTicketsCount }
|
||||
</span> }
|
||||
</Button>
|
||||
</NitroCardContentView>
|
||||
</NitroCardView> }
|
||||
|
||||
@@ -30,7 +30,7 @@ const useModToolsState = () =>
|
||||
const newValue = [ ...prevValue ];
|
||||
const existingIndex = newValue.indexOf(roomId);
|
||||
|
||||
if(existingIndex >= 0) newValue.splice(existingIndex);
|
||||
if(existingIndex >= 0) newValue.splice(existingIndex, 1);
|
||||
|
||||
return newValue;
|
||||
});
|
||||
@@ -56,7 +56,7 @@ const useModToolsState = () =>
|
||||
const newValue = [ ...prevValue ];
|
||||
const existingIndex = newValue.indexOf(roomId);
|
||||
|
||||
if(existingIndex >= 0) newValue.splice(existingIndex);
|
||||
if(existingIndex >= 0) newValue.splice(existingIndex, 1);
|
||||
|
||||
return newValue;
|
||||
});
|
||||
@@ -82,7 +82,7 @@ const useModToolsState = () =>
|
||||
const newValue = [ ...prevValue ];
|
||||
const existingIndex = newValue.indexOf(userId);
|
||||
|
||||
if(existingIndex >= 0) newValue.splice(existingIndex);
|
||||
if(existingIndex >= 0) newValue.splice(existingIndex, 1);
|
||||
|
||||
return newValue;
|
||||
});
|
||||
@@ -108,7 +108,7 @@ const useModToolsState = () =>
|
||||
const newValue = [ ...prevValue ];
|
||||
const existingIndex = newValue.indexOf(userId);
|
||||
|
||||
if(existingIndex >= 0) newValue.splice(existingIndex);
|
||||
if(existingIndex >= 0) newValue.splice(existingIndex, 1);
|
||||
|
||||
return newValue;
|
||||
});
|
||||
@@ -116,7 +116,7 @@ const useModToolsState = () =>
|
||||
|
||||
const toggleUserChatlog = (userId: number) =>
|
||||
{
|
||||
if(openRoomChatlogs.indexOf(userId) >= 0) closeUserChatlog(userId);
|
||||
if(openUserChatlogs.indexOf(userId) >= 0) closeUserChatlog(userId);
|
||||
else openUserChatlog(userId);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user