mirror of
https://github.com/duckietm/Nitro-V3.git
synced 2026-06-19 23:16:21 +00:00
0ad284fa9c
The Context strip at the top of the launcher showed which room the mod is currently observing — green pill + door icon when in a room, zinc strip when not. In practice it's noise: the Room Tool / Chatlog Tool buttons right under it already gate on the same in-room state (disabled when not in a room) and carry their own tooltip explaining that. The strip duplicated the signal without adding actionable info. Remove the section, the now-unused FaDoorOpen / FaDoorClosed imports, and the matching `modtools.window.section.context` / `modtools.window.context.room` locale keys (from both the runtime UITexts.json and the versioned UITexts.example template).
223 lines
14 KiB
TypeScript
223 lines
14 KiB
TypeScript
import { AddLinkEventTracker, CreateLinkEvent, ILinkEventTracker, RemoveLinkEventTracker, RoomEngineEvent, RoomId, RoomObjectCategory, RoomObjectType } from '@nitrots/nitro-renderer';
|
|
import { FC, useEffect, useMemo, useRef, useState } from 'react';
|
|
import { FaTimes, FaUserSlash } from 'react-icons/fa';
|
|
import { GetRoomSession, ISelectedUser, LocalizeText } from '../../api';
|
|
import { Button, DraggableWindowPosition, NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../common';
|
|
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';
|
|
import { ModToolsUserChatlogView } from './views/user/ModToolsUserChatlogView';
|
|
import { ModToolsUserView } from './views/user/ModToolsUserView';
|
|
|
|
export const ModToolsView: FC<{}> = props =>
|
|
{
|
|
const [ isVisible, setIsVisible ] = useState(false);
|
|
const [ currentRoomId, setCurrentRoomId ] = useState<number>(-1);
|
|
const [ selectedUser, setSelectedUser ] = useState<ISelectedUser>(null);
|
|
const [ isTicketsVisible, setIsTicketsVisible ] = useState(false);
|
|
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,
|
|
RoomEngineEvent.DISPOSED
|
|
], event =>
|
|
{
|
|
if(RoomId.isRoomPreviewerId(event.roomId)) return;
|
|
|
|
switch(event.type)
|
|
{
|
|
case RoomEngineEvent.INITIALIZED:
|
|
setCurrentRoomId(event.roomId);
|
|
return;
|
|
case RoomEngineEvent.DISPOSED:
|
|
setCurrentRoomId(-1);
|
|
return;
|
|
}
|
|
});
|
|
|
|
useObjectSelectedEvent(event =>
|
|
{
|
|
if(event.category !== RoomObjectCategory.UNIT) return;
|
|
|
|
const roomSession = GetRoomSession();
|
|
|
|
if(!roomSession) return;
|
|
|
|
const userData = roomSession.userDataManager.getUserDataByIndex(event.id);
|
|
|
|
if(!userData || userData.type !== RoomObjectType.USER) return;
|
|
|
|
setSelectedUser({ userId: userData.webID, username: userData.name });
|
|
});
|
|
|
|
useEffect(() =>
|
|
{
|
|
const linkTracker: ILinkEventTracker = {
|
|
linkReceived: (url: string) =>
|
|
{
|
|
const parts = url.split('/');
|
|
|
|
if(parts.length < 2) return;
|
|
|
|
switch(parts[1])
|
|
{
|
|
case 'show':
|
|
setIsVisible(true);
|
|
return;
|
|
case 'hide':
|
|
setIsVisible(false);
|
|
return;
|
|
case 'toggle':
|
|
setIsVisible(prevValue => !prevValue);
|
|
return;
|
|
case 'open-room-info':
|
|
openRoomInfo(Number(parts[2]));
|
|
return;
|
|
case 'close-room-info':
|
|
closeRoomInfo(Number(parts[2]));
|
|
return;
|
|
case 'toggle-room-info':
|
|
toggleRoomInfo(Number(parts[2]));
|
|
return;
|
|
case 'open-room-chatlog':
|
|
openRoomChatlog(Number(parts[2]));
|
|
return;
|
|
case 'close-room-chatlog':
|
|
closeRoomChatlog(Number(parts[2]));
|
|
return;
|
|
case 'toggle-room-chatlog':
|
|
toggleRoomChatlog(Number(parts[2]));
|
|
return;
|
|
case 'open-user-info':
|
|
openUserInfo(Number(parts[2]));
|
|
return;
|
|
case 'close-user-info':
|
|
closeUserInfo(Number(parts[2]));
|
|
return;
|
|
case 'toggle-user-info':
|
|
toggleUserInfo(Number(parts[2]));
|
|
return;
|
|
case 'open-user-chatlog':
|
|
openUserChatlog(Number(parts[2]));
|
|
return;
|
|
case 'close-user-chatlog':
|
|
closeUserChatlog(Number(parts[2]));
|
|
return;
|
|
case 'toggle-user-chatlog':
|
|
toggleUserChatlog(Number(parts[2]));
|
|
return;
|
|
}
|
|
},
|
|
eventUrlPrefix: 'mod-tools/'
|
|
};
|
|
|
|
AddLinkEventTracker(linkTracker);
|
|
|
|
return () => RemoveLinkEventTracker(linkTracker);
|
|
}, [ openRoomInfo, closeRoomInfo, toggleRoomInfo, openRoomChatlog, closeRoomChatlog, toggleRoomChatlog, openUserInfo, closeUserInfo, toggleUserInfo, openUserChatlog, closeUserChatlog, toggleUserChatlog ]);
|
|
|
|
const isInRoom = currentRoomId > 0;
|
|
const isRoomInfoOpen = isInRoom && openRooms.includes(currentRoomId);
|
|
const isRoomChatlogOpen = isInRoom && openRoomChatlogs.includes(currentRoomId);
|
|
const isUserInfoOpen = selectedUser && openUserInfos.includes(selectedUser.userId);
|
|
const noRoomHint = LocalizeText('modtools.window.no.room');
|
|
|
|
return (
|
|
<>
|
|
{ isVisible &&
|
|
<NitroCardView className="nitro-mod-tools min-w-[240px] max-w-[260px]" theme="primary-slim" uniqueKey="mod-tools" windowPosition={ DraggableWindowPosition.TOP_LEFT } >
|
|
<NitroCardHeaderView headerText={ LocalizeText('modtools.window.title') } onCloseClick={ event => setIsVisible(false) } />
|
|
<NitroCardContentView className="text-black" gap={ 2 }>
|
|
{/* Room tools */}
|
|
<div className="flex flex-col gap-1.5">
|
|
<div className="text-[.6rem] uppercase tracking-wide opacity-60 font-semibold pl-1">{ LocalizeText('modtools.window.section.room') }</div>
|
|
<Button active={ isRoomInfoOpen } disabled={ !isInRoom } gap={ 2 } justifyContent="start" title={ !isInRoom ? noRoomHint : undefined } onClick={ () => CreateLinkEvent(`mod-tools/toggle-room-info/${ currentRoomId }`) }>
|
|
<div className="nitro-icon icon-small-room shrink-0" />
|
|
<span className="grow text-start">{ LocalizeText('modtools.window.tools.room') }</span>
|
|
</Button>
|
|
<Button active={ isRoomChatlogOpen } disabled={ !isInRoom } gap={ 2 } innerRef={ elementRef } justifyContent="start" title={ !isInRoom ? noRoomHint : undefined } onClick={ () => CreateLinkEvent(`mod-tools/toggle-room-chatlog/${ currentRoomId }`) }>
|
|
<div className="nitro-icon icon-chat-history shrink-0" />
|
|
<span className="grow text-start">{ LocalizeText('modtools.window.tools.chatlog') }</span>
|
|
</Button>
|
|
</div>
|
|
|
|
{/* Selected user */}
|
|
<div className="flex flex-col gap-1.5">
|
|
<div className="text-[.6rem] uppercase tracking-wide opacity-60 font-semibold pl-1">{ LocalizeText('modtools.window.section.user') }</div>
|
|
{ selectedUser
|
|
? (
|
|
<div className={ `flex flex-col gap-1.5 rounded p-1.5 border ${ isSelectedUserPresent ? 'bg-gradient-to-r from-emerald-50 to-transparent border-emerald-100' : 'bg-gradient-to-r from-zinc-50 to-transparent border-zinc-200' }` }>
|
|
<div className="flex items-center gap-1.5">
|
|
<span className={ `inline-block w-2 h-2 rounded-full shrink-0 ${ isSelectedUserPresent ? 'bg-emerald-500' : 'bg-zinc-400' }` }
|
|
title={ isSelectedUserPresent ? LocalizeText('modtools.window.user.in_room') : LocalizeText('modtools.window.user.left_room') }
|
|
aria-label={ isSelectedUserPresent ? LocalizeText('modtools.userinfo.presence.in_room') : LocalizeText('modtools.window.user.left_room') } />
|
|
<span className="truncate grow text-start text-sm font-semibold leading-tight">{ selectedUser.username }</span>
|
|
<button
|
|
className="inline-flex items-center justify-center w-5 h-5 rounded text-zinc-500 hover:text-rose-600 hover:bg-rose-100 shrink-0 transition-colors"
|
|
onClick={ event =>
|
|
{
|
|
event.stopPropagation();
|
|
setSelectedUser(null);
|
|
} }
|
|
title={ LocalizeText('modtools.window.user.clear') }>
|
|
<FaTimes size={ 10 } />
|
|
</button>
|
|
</div>
|
|
<Button active={ !!isUserInfoOpen } gap={ 2 } justifyContent="start" onClick={ () => CreateLinkEvent(`mod-tools/toggle-user-info/${ selectedUser.userId }`) }>
|
|
<div className="nitro-icon icon-user shrink-0" />
|
|
<span className="grow text-start">{ LocalizeText('modtools.window.user.open_info') }</span>
|
|
</Button>
|
|
</div>
|
|
)
|
|
: (
|
|
<div className="flex items-center gap-2 rounded p-2 border border-dashed border-zinc-300 bg-zinc-50/50 opacity-70">
|
|
<FaUserSlash className="text-zinc-400 shrink-0" size={ 14 } />
|
|
<span className="text-xs italic">{ LocalizeText('modtools.window.select.user') }</span>
|
|
</div>
|
|
)
|
|
}
|
|
</div>
|
|
|
|
{/* Reports */}
|
|
<div className="flex flex-col gap-1.5">
|
|
<div className="text-[.6rem] uppercase tracking-wide opacity-60 font-semibold pl-1">{ LocalizeText('modtools.window.section.reports') }</div>
|
|
<Button active={ isTicketsVisible } gap={ 2 } justifyContent="start" onClick={ () => setIsTicketsVisible(prevValue => !prevValue) }>
|
|
<div className="nitro-icon icon-tickets shrink-0" />
|
|
<span className="grow text-start">{ LocalizeText('modtools.window.tools.report') }</span>
|
|
{ (openTicketsCount > 0) &&
|
|
<span
|
|
className="inline-flex items-center justify-center min-w-[1.25rem] h-5 px-1.5 rounded-full bg-rose-500 text-white text-xs font-semibold shrink-0 [box-shadow:0_0_0_2px_rgba(244,63,94,.25)]"
|
|
title={ LocalizeText(openTicketsCount === 1 ? 'modtools.window.tickets.open' : 'modtools.window.tickets.open.many', [ 'count' ], [ openTicketsCount.toString() ]) }>
|
|
{ openTicketsCount > 99 ? '99+' : openTicketsCount }
|
|
</span> }
|
|
</Button>
|
|
</div>
|
|
</NitroCardContentView>
|
|
</NitroCardView> }
|
|
{ (openRooms.length > 0) && openRooms.map(roomId => <ModToolsRoomView key={ roomId } roomId={ roomId } onCloseClick={ () => CreateLinkEvent(`mod-tools/close-room-info/${ roomId }`) } />) }
|
|
{ (openRoomChatlogs.length > 0) && openRoomChatlogs.map(roomId => <ModToolsChatlogView key={ roomId } roomId={ roomId } onCloseClick={ () => CreateLinkEvent(`mod-tools/close-room-chatlog/${ roomId }`) } />) }
|
|
{ (openUserInfos.length > 0) && openUserInfos.map(userId => <ModToolsUserView key={ userId } userId={ userId } onCloseClick={ () => CreateLinkEvent(`mod-tools/close-user-info/${ userId }`) } />) }
|
|
{ (openUserChatlogs.length > 0) && openUserChatlogs.map(userId => <ModToolsUserChatlogView key={ userId } userId={ userId } onCloseClick={ () => CreateLinkEvent(`mod-tools/close-user-chatlog/${ userId }`) } />) }
|
|
{ isTicketsVisible && <ModToolsTicketsView onCloseClick={ () => setIsTicketsVisible(false) } /> }
|
|
</>
|
|
);
|
|
};
|