mirror of
https://github.com/duckietm/Nitro-V3.git
synced 2026-06-20 07:26:19 +00:00
🆙 Init V3
This commit is contained in:
@@ -0,0 +1,147 @@
|
||||
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 { Button, DraggableWindowPosition, NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../common';
|
||||
import { useModTools, useNitroEvent, useObjectSelectedEvent } 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 { 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);
|
||||
|
||||
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 ]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{ isVisible &&
|
||||
<NitroCardView className="nitro-mod-tools" theme="primary-slim" uniqueKey="mod-tools" windowPosition={ DraggableWindowPosition.TOP_LEFT } >
|
||||
<NitroCardHeaderView headerText={ 'Mod Tools' } onCloseClick={ event => setIsVisible(false) } />
|
||||
<NitroCardContentView className="text-black" gap={ 1 }>
|
||||
<Button className="relative" disabled={ (currentRoomId <= 0) } gap={ 1 } onClick={ event => CreateLinkEvent(`mod-tools/toggle-room-info/${ currentRoomId }`) }>
|
||||
<div className="nitro-icon icon-small-room absolute start-1" /> Room Tool
|
||||
</Button>
|
||||
<Button className="relative" disabled={ (currentRoomId <= 0) } gap={ 1 } innerRef={ elementRef } onClick={ event => CreateLinkEvent(`mod-tools/toggle-room-chatlog/${ currentRoomId }`) }>
|
||||
<div className="nitro-icon icon-chat-history absolute start-1" /> Chatlog Tool
|
||||
</Button>
|
||||
<Button className="relative" disabled={ !selectedUser } gap={ 1 } onClick={ () => CreateLinkEvent(`mod-tools/toggle-user-info/${ selectedUser.userId }`) }>
|
||||
<div className="nitro-icon icon-user absolute start-1" /> User: { selectedUser ? selectedUser.username : '' }
|
||||
</Button>
|
||||
<Button className="relative" gap={ 1 } onClick={ () => setIsTicketsVisible(prevValue => !prevValue) }>
|
||||
<div className="nitro-icon icon-tickets absolute start-1" /> Report Tool
|
||||
</Button>
|
||||
</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) } /> }
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
export interface ChatlogRecord
|
||||
{
|
||||
timestamp?: string;
|
||||
habboId?: number;
|
||||
username?: string;
|
||||
message?: string;
|
||||
hasHighlighting?: boolean;
|
||||
isRoomInfo?: boolean;
|
||||
roomId?: number;
|
||||
roomName?: string;
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
import { ChatRecordData, CreateLinkEvent } from '@nitrots/nitro-renderer';
|
||||
import { FC, useMemo } from 'react';
|
||||
import { TryVisitRoom } from '../../../../api';
|
||||
import { Button, Column, Flex, Grid, InfiniteScroll, Text } from '../../../../common';
|
||||
import { useModTools } from '../../../../hooks';
|
||||
import { ChatlogRecord } from './ChatlogRecord';
|
||||
|
||||
interface ChatlogViewProps
|
||||
{
|
||||
records: ChatRecordData[];
|
||||
}
|
||||
|
||||
export const ChatlogView: FC<ChatlogViewProps> = props =>
|
||||
{
|
||||
const { records = null } = props;
|
||||
const { openRoomInfo = null } = useModTools();
|
||||
|
||||
const allRecords = useMemo(() =>
|
||||
{
|
||||
const results: ChatlogRecord[] = [];
|
||||
|
||||
records.forEach(record =>
|
||||
{
|
||||
results.push({
|
||||
isRoomInfo: true,
|
||||
roomId: record.roomId,
|
||||
roomName: record.roomName
|
||||
});
|
||||
|
||||
record.chatlog.forEach(chatlog =>
|
||||
{
|
||||
results.push({
|
||||
timestamp: chatlog.timestamp,
|
||||
habboId: chatlog.userId,
|
||||
username: chatlog.userName,
|
||||
hasHighlighting: chatlog.hasHighlighting,
|
||||
message: chatlog.message,
|
||||
isRoomInfo: false
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return results;
|
||||
}, [ records ]);
|
||||
|
||||
const RoomInfo = (props: { roomId: number, roomName: string }) =>
|
||||
{
|
||||
return (
|
||||
<Flex alignItems="center" className="bg-muted rounded p-1" gap={ 2 } justifyContent="between">
|
||||
<div className="flex gap-1">
|
||||
<Text bold>Room name:</Text>
|
||||
<Text>{ props.roomName }</Text>
|
||||
</div>
|
||||
<div className="flex gap-1">
|
||||
<Button onClick={ event => TryVisitRoom(props.roomId) }>Visit Room</Button>
|
||||
<Button onClick={ event => openRoomInfo(props.roomId) }>Room Tools</Button>
|
||||
</div>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Column fit gap={ 0 } overflow="hidden">
|
||||
<Column gap={ 2 }>
|
||||
<Grid className="text-black font-bold border-bottom pb-1" gap={ 1 }>
|
||||
<div className="col-span-2">Time</div>
|
||||
<div className="col-span-3">User</div>
|
||||
<div className="col-span-7">Message</div>
|
||||
</Grid>
|
||||
</Column>
|
||||
{ (records && (records.length > 0)) &&
|
||||
<InfiniteScroll rowRender={ (row: ChatlogRecord) =>
|
||||
{
|
||||
return (
|
||||
<>
|
||||
{ row.isRoomInfo &&
|
||||
<RoomInfo roomId={ row.roomId } roomName={ row.roomName } /> }
|
||||
{ !row.isRoomInfo &&
|
||||
<Grid alignItems="center" className="log-entry py-1 border-bottom" fullHeight={ false } gap={ 1 }>
|
||||
<Text className="col-span-2">{ row.timestamp }</Text>
|
||||
<Text bold pointer underline className="col-span-3" onClick={ event => CreateLinkEvent(`mod-tools/open-user-info/${ row.habboId }`) }>{ row.username }</Text>
|
||||
<Text textBreak wrap className="col-span-7">{ row.message }</Text>
|
||||
</Grid> }
|
||||
</>
|
||||
);
|
||||
} } rows={ allRecords } /> }
|
||||
</Column>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,44 @@
|
||||
import { ChatRecordData, GetRoomChatlogMessageComposer, RoomChatlogEvent } from '@nitrots/nitro-renderer';
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
import { SendMessageComposer } from '../../../../api';
|
||||
import { DraggableWindowPosition, NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../common';
|
||||
import { useMessageEvent } from '../../../../hooks';
|
||||
import { ChatlogView } from '../chatlog/ChatlogView';
|
||||
|
||||
interface ModToolsChatlogViewProps
|
||||
{
|
||||
roomId: number;
|
||||
onCloseClick: () => void;
|
||||
}
|
||||
|
||||
export const ModToolsChatlogView: FC<ModToolsChatlogViewProps> = props =>
|
||||
{
|
||||
const { roomId = null, onCloseClick = null } = props;
|
||||
const [ roomChatlog, setRoomChatlog ] = useState<ChatRecordData>(null);
|
||||
|
||||
useMessageEvent<RoomChatlogEvent>(RoomChatlogEvent, event =>
|
||||
{
|
||||
const parser = event.getParser();
|
||||
|
||||
if(!parser || parser.data.roomId !== roomId) return;
|
||||
|
||||
setRoomChatlog(parser.data);
|
||||
});
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
SendMessageComposer(new GetRoomChatlogMessageComposer(roomId));
|
||||
}, [ roomId ]);
|
||||
|
||||
if(!roomChatlog) return null;
|
||||
|
||||
return (
|
||||
<NitroCardView className="nitro-mod-tools-chatlog" theme="primary-slim" windowPosition={ DraggableWindowPosition.TOP_LEFT }>
|
||||
<NitroCardHeaderView headerText={ `Room Chatlog ${ roomChatlog.roomName }` } onCloseClick={ onCloseClick } />
|
||||
<NitroCardContentView className="text-black" overflow="hidden">
|
||||
{ roomChatlog &&
|
||||
<ChatlogView records={ [ roomChatlog ] } /> }
|
||||
</NitroCardContentView>
|
||||
</NitroCardView>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,117 @@
|
||||
import { CreateLinkEvent, GetModeratorRoomInfoMessageComposer, ModerateRoomMessageComposer, ModeratorActionMessageComposer, ModeratorRoomInfoEvent } from '@nitrots/nitro-renderer';
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
import { SendMessageComposer, TryVisitRoom } from '../../../../api';
|
||||
import { Button, Column, DraggableWindowPosition, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../../common';
|
||||
import { useMessageEvent } from '../../../../hooks';
|
||||
|
||||
interface ModToolsRoomViewProps
|
||||
{
|
||||
roomId: number;
|
||||
onCloseClick: () => void;
|
||||
}
|
||||
|
||||
export const ModToolsRoomView: FC<ModToolsRoomViewProps> = props =>
|
||||
{
|
||||
const { roomId = null, onCloseClick = null } = props;
|
||||
const [ infoRequested, setInfoRequested ] = useState(false);
|
||||
const [ loadedRoomId, setLoadedRoomId ] = useState(null);
|
||||
const [ name, setName ] = useState(null);
|
||||
const [ ownerId, setOwnerId ] = useState(null);
|
||||
const [ ownerName, setOwnerName ] = useState(null);
|
||||
const [ ownerInRoom, setOwnerInRoom ] = useState(false);
|
||||
const [ usersInRoom, setUsersInRoom ] = useState(0);
|
||||
const [ kickUsers, setKickUsers ] = useState(false);
|
||||
const [ lockRoom, setLockRoom ] = useState(false);
|
||||
const [ changeRoomName, setChangeRoomName ] = useState(false);
|
||||
const [ message, setMessage ] = useState('');
|
||||
|
||||
const handleClick = (action: string, value?: string) =>
|
||||
{
|
||||
if(!action) return;
|
||||
|
||||
switch(action)
|
||||
{
|
||||
case 'alert_only':
|
||||
if(message.trim().length === 0) return;
|
||||
|
||||
SendMessageComposer(new ModeratorActionMessageComposer(ModeratorActionMessageComposer.ACTION_ALERT, message, ''));
|
||||
SendMessageComposer(new ModerateRoomMessageComposer(roomId, lockRoom ? 1 : 0, changeRoomName ? 1 : 0, kickUsers ? 1 : 0));
|
||||
return;
|
||||
case 'send_message':
|
||||
if(message.trim().length === 0) return;
|
||||
|
||||
SendMessageComposer(new ModeratorActionMessageComposer(ModeratorActionMessageComposer.ACTION_MESSAGE, message, ''));
|
||||
SendMessageComposer(new ModerateRoomMessageComposer(roomId, lockRoom ? 1 : 0, changeRoomName ? 1 : 0, kickUsers ? 1 : 0));
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
useMessageEvent<ModeratorRoomInfoEvent>(ModeratorRoomInfoEvent, event =>
|
||||
{
|
||||
const parser = event.getParser();
|
||||
|
||||
if(!parser || parser.data.flatId !== roomId) return;
|
||||
|
||||
setLoadedRoomId(parser.data.flatId);
|
||||
setName(parser.data.room.name);
|
||||
setOwnerId(parser.data.ownerId);
|
||||
setOwnerName(parser.data.ownerName);
|
||||
setOwnerInRoom(parser.data.ownerInRoom);
|
||||
setUsersInRoom(parser.data.userCount);
|
||||
});
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(infoRequested) return;
|
||||
|
||||
SendMessageComposer(new GetModeratorRoomInfoMessageComposer(roomId));
|
||||
setInfoRequested(true);
|
||||
}, [ roomId, infoRequested, setInfoRequested ]);
|
||||
|
||||
return (
|
||||
<NitroCardView className="nitro-mod-tools-room" theme="primary-slim" windowPosition={ DraggableWindowPosition.TOP_LEFT }>
|
||||
<NitroCardHeaderView headerText={ 'Room Info' + (name ? ': ' + name : '') } onCloseClick={ event => onCloseClick() } />
|
||||
<NitroCardContentView className="text-black">
|
||||
<div className="flex gap-2">
|
||||
<Column grow gap={ 1 } justifyContent="center">
|
||||
<div className="items-center gap-2">
|
||||
<Text bold align="end" className="col-span-7">Room Owner:</Text>
|
||||
<Text pointer truncate underline>{ ownerName }</Text>
|
||||
</div>
|
||||
<div className="items-center gap-2">
|
||||
<Text bold align="end" className="col-span-7">Users in room:</Text>
|
||||
<Text>{ usersInRoom }</Text>
|
||||
</div>
|
||||
<div className="items-center gap-2">
|
||||
<Text bold align="end" className="col-span-7">Owner in room:</Text>
|
||||
<Text>{ ownerInRoom ? 'Yes' : 'No' }</Text>
|
||||
</div>
|
||||
</Column>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Button onClick={ event => TryVisitRoom(roomId) }>Visit Room</Button>
|
||||
<Button onClick={ event => CreateLinkEvent(`mod-tools/open-room-chatlog/${ roomId }`) }>Chatlog</Button>
|
||||
</div>
|
||||
</div>
|
||||
<Column className="bg-muted rounded p-2" gap={ 1 }>
|
||||
<div className="flex items-center gap-1">
|
||||
<input checked={ kickUsers } className="form-check-input" type="checkbox" onChange={ event => setKickUsers(event.target.checked) } />
|
||||
<Text small>Kick everyone out</Text>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<input checked={ lockRoom } className="form-check-input" type="checkbox" onChange={ event => setLockRoom(event.target.checked) } />
|
||||
<Text small>Enable the doorbell</Text>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<input checked={ changeRoomName } className="form-check-input" type="checkbox" onChange={ event => setChangeRoomName(event.target.checked) } />
|
||||
<Text small>Change room name</Text>
|
||||
</div>
|
||||
</Column>
|
||||
<textarea className="min-h-[calc(1.5em+ .5rem+2px)] px-[.5rem] py-[.25rem] rounded-[.2rem]" placeholder="Type a mandatory message to the users in this text box..." value={ message } onChange={ event => setMessage(event.target.value) }></textarea>
|
||||
<div className="flex justify-between">
|
||||
<Button variant="danger" onClick={ event => handleClick('send_message') }>Send Caution</Button>
|
||||
<Button onClick={ event => handleClick('alert_only') }>Send Alert only</Button>
|
||||
</div>
|
||||
</NitroCardContentView>
|
||||
</NitroCardView>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,41 @@
|
||||
import { CfhChatlogData, CfhChatlogEvent, GetCfhChatlogMessageComposer } from '@nitrots/nitro-renderer';
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
import { SendMessageComposer } from '../../../../api';
|
||||
import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../common';
|
||||
import { useMessageEvent } from '../../../../hooks';
|
||||
import { ChatlogView } from '../chatlog/ChatlogView';
|
||||
|
||||
interface CfhChatlogViewProps
|
||||
{
|
||||
issueId: number;
|
||||
onCloseClick(): void;
|
||||
}
|
||||
|
||||
export const CfhChatlogView: FC<CfhChatlogViewProps> = props =>
|
||||
{
|
||||
const { onCloseClick = null, issueId = null } = props;
|
||||
const [ chatlogData, setChatlogData ] = useState<CfhChatlogData>(null);
|
||||
|
||||
useMessageEvent<CfhChatlogEvent>(CfhChatlogEvent, event =>
|
||||
{
|
||||
const parser = event.getParser();
|
||||
|
||||
if(!parser || parser.data.issueId !== issueId) return;
|
||||
|
||||
setChatlogData(parser.data);
|
||||
});
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
SendMessageComposer(new GetCfhChatlogMessageComposer(issueId));
|
||||
}, [ issueId ]);
|
||||
|
||||
return (
|
||||
<NitroCardView className="nitro-mod-tools-chatlog" theme="primary-slim">
|
||||
<NitroCardHeaderView headerText={ 'Issue Chatlog' } onCloseClick={ onCloseClick } />
|
||||
<NitroCardContentView className="text-black">
|
||||
{ chatlogData && <ChatlogView records={ [ chatlogData.chatRecord ] } /> }
|
||||
</NitroCardContentView>
|
||||
</NitroCardView>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,86 @@
|
||||
import { CloseIssuesMessageComposer, ReleaseIssuesMessageComposer } from '@nitrots/nitro-renderer';
|
||||
import { FC, useState } from 'react';
|
||||
import { GetIssueCategoryName, LocalizeText, SendMessageComposer } from '../../../../api';
|
||||
import { Button, Column, Grid, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../../common';
|
||||
import { useModTools } from '../../../../hooks';
|
||||
import { CfhChatlogView } from './CfhChatlogView';
|
||||
|
||||
interface IssueInfoViewProps
|
||||
{
|
||||
issueId: number;
|
||||
onIssueInfoClosed(issueId: number): void;
|
||||
}
|
||||
|
||||
export const ModToolsIssueInfoView: FC<IssueInfoViewProps> = props =>
|
||||
{
|
||||
const { issueId = null, onIssueInfoClosed = null } = props;
|
||||
const [ cfhChatlogOpen, setcfhChatlogOpen ] = useState(false);
|
||||
const { tickets = [], openUserInfo = null } = useModTools();
|
||||
const ticket = tickets.find(issue => (issue.issueId === issueId));
|
||||
|
||||
const releaseIssue = (issueId: number) =>
|
||||
{
|
||||
SendMessageComposer(new ReleaseIssuesMessageComposer([ issueId ]));
|
||||
|
||||
onIssueInfoClosed(issueId);
|
||||
};
|
||||
|
||||
const closeIssue = (resolutionType: number) =>
|
||||
{
|
||||
SendMessageComposer(new CloseIssuesMessageComposer([ issueId ], resolutionType));
|
||||
|
||||
onIssueInfoClosed(issueId);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<NitroCardView className="nitro-mod-tools-handle-issue" theme="primary-slim">
|
||||
<NitroCardHeaderView headerText={ 'Resolving issue ' + issueId } onCloseClick={ () => onIssueInfoClosed(issueId) } />
|
||||
<NitroCardContentView className="text-black">
|
||||
<Text fontSize={ 4 }>Issue Information</Text>
|
||||
<Grid overflow="auto">
|
||||
<Column size={ 8 }>
|
||||
<table className="table table-striped table-sm table-text-small text-black m-0">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Source</th>
|
||||
<td>{ GetIssueCategoryName(ticket.categoryId) }</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Category</th>
|
||||
<td className="text-break">{ LocalizeText('help.cfh.topic.' + ticket.reportedCategoryId) }</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Description</th>
|
||||
<td className="text-break">{ ticket.message }</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Caller</th>
|
||||
<td>
|
||||
<Text bold pointer underline onClick={ event => openUserInfo(ticket.reporterUserId) }>{ ticket.reporterUserName }</Text>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Reported User</th>
|
||||
<td>
|
||||
<Text bold pointer underline onClick={ event => openUserInfo(ticket.reportedUserId) }>{ ticket.reportedUserName }</Text>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</Column>
|
||||
<Column gap={ 1 } size={ 4 }>
|
||||
<Button variant="secondary" onClick={ () => setcfhChatlogOpen(!cfhChatlogOpen) }>Chatlog</Button>
|
||||
<Button onClick={ event => closeIssue(CloseIssuesMessageComposer.RESOLUTION_USELESS) }>Close as useless</Button>
|
||||
<Button variant="danger" onClick={ event => closeIssue(CloseIssuesMessageComposer.RESOLUTION_ABUSIVE) }>Close as abusive</Button>
|
||||
<Button variant="success" onClick={ event => closeIssue(CloseIssuesMessageComposer.RESOLUTION_RESOLVED) }>Close as resolved</Button>
|
||||
<Button variant="secondary" onClick={ event => releaseIssue(issueId) } >Release</Button>
|
||||
</Column>
|
||||
</Grid>
|
||||
</NitroCardContentView>
|
||||
</NitroCardView>
|
||||
{ cfhChatlogOpen &&
|
||||
<CfhChatlogView issueId={ issueId } onCloseClick={ () => setcfhChatlogOpen(false) }/> }
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,47 @@
|
||||
import { IssueMessageData, ReleaseIssuesMessageComposer } from '@nitrots/nitro-renderer';
|
||||
import { FC } from 'react';
|
||||
import { SendMessageComposer } from '../../../../api';
|
||||
import { Button, Column, Grid } from '../../../../common';
|
||||
|
||||
interface ModToolsMyIssuesTabViewProps
|
||||
{
|
||||
myIssues: IssueMessageData[];
|
||||
handleIssue: (issueId: number) => void;
|
||||
}
|
||||
|
||||
export const ModToolsMyIssuesTabView: FC<ModToolsMyIssuesTabViewProps> = props =>
|
||||
{
|
||||
const { myIssues = null, handleIssue = null } = props;
|
||||
|
||||
return (
|
||||
<Column gap={ 0 } overflow="hidden">
|
||||
<Column gap={ 2 }>
|
||||
<Grid className="text-black font-bold border-bottom pb-1" gap={ 1 }>
|
||||
<div className="col-span-2">Type</div>
|
||||
<div className="col-span-3">Room/Player</div>
|
||||
<div className="col-span-3">Opened</div>
|
||||
<div className="col-span-2"></div>
|
||||
<div className="col-span-2"></div>
|
||||
</Grid>
|
||||
</Column>
|
||||
<Column className="striped-children" gap={ 0 } overflow="auto">
|
||||
{ myIssues && (myIssues.length > 0) && myIssues.map(issue =>
|
||||
{
|
||||
return (
|
||||
<Grid key={ issue.issueId } alignItems="center" className="text-black py-1 border-bottom" gap={ 1 }>
|
||||
<div className="col-span-2">{ issue.categoryId }</div>
|
||||
<div className="col-span-3">{ issue.reportedUserName }</div>
|
||||
<div className="col-span-3">{ new Date(Date.now() - issue.issueAgeInMilliseconds).toLocaleTimeString() }</div>
|
||||
<div className="col-span-2">
|
||||
<Button variant="primary" onClick={ event => handleIssue(issue.issueId) }>Handle</Button>
|
||||
</div>
|
||||
<div className="col-span-2">
|
||||
<Button variant="danger" onClick={ event => SendMessageComposer(new ReleaseIssuesMessageComposer([ issue.issueId ])) }>Release</Button>
|
||||
</div>
|
||||
</Grid>
|
||||
);
|
||||
}) }
|
||||
</Column>
|
||||
</Column>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,42 @@
|
||||
import { IssueMessageData, PickIssuesMessageComposer } from '@nitrots/nitro-renderer';
|
||||
import { FC } from 'react';
|
||||
import { SendMessageComposer } from '../../../../api';
|
||||
import { Button, Column, Grid } from '../../../../common';
|
||||
|
||||
interface ModToolsOpenIssuesTabViewProps
|
||||
{
|
||||
openIssues: IssueMessageData[];
|
||||
}
|
||||
|
||||
export const ModToolsOpenIssuesTabView: FC<ModToolsOpenIssuesTabViewProps> = props =>
|
||||
{
|
||||
const { openIssues = null } = props;
|
||||
|
||||
return (
|
||||
<Column gap={ 0 } overflow="hidden">
|
||||
<Column gap={ 2 }>
|
||||
<Grid className="text-black font-bold border-bottom pb-1" gap={ 1 }>
|
||||
<div className="col-span-2">Type</div>
|
||||
<div className="col-span-3">Room/Player</div>
|
||||
<div className="col-span-4">Opened</div>
|
||||
<div className="col-span-3"></div>
|
||||
</Grid>
|
||||
</Column>
|
||||
<Column className="striped-children" gap={ 0 } overflow="auto">
|
||||
{ openIssues && (openIssues.length > 0) && openIssues.map(issue =>
|
||||
{
|
||||
return (
|
||||
<Grid key={ issue.issueId } alignItems="center" className="text-black py-1 border-bottom" gap={ 1 }>
|
||||
<div className="col-span-2">{ issue.categoryId }</div>
|
||||
<div className="col-span-3">{ issue.reportedUserName }</div>
|
||||
<div className="col-span-4">{ new Date(Date.now() - issue.issueAgeInMilliseconds).toLocaleTimeString() }</div>
|
||||
<div className="col-span-3">
|
||||
<Button variant="success" onClick={ event => SendMessageComposer(new PickIssuesMessageComposer([ issue.issueId ], false, 0, 'pick issue button')) }>Pick Issue</Button>
|
||||
</div>
|
||||
</Grid>
|
||||
);
|
||||
}) }
|
||||
</Column>
|
||||
</Column>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,39 @@
|
||||
import { IssueMessageData } from '@nitrots/nitro-renderer';
|
||||
import { FC } from 'react';
|
||||
import { Column, Grid } from '../../../../common';
|
||||
|
||||
interface ModToolsPickedIssuesTabViewProps
|
||||
{
|
||||
pickedIssues: IssueMessageData[];
|
||||
}
|
||||
|
||||
export const ModToolsPickedIssuesTabView: FC<ModToolsPickedIssuesTabViewProps> = props =>
|
||||
{
|
||||
const { pickedIssues = null } = props;
|
||||
|
||||
return (
|
||||
<Column gap={ 0 } overflow="hidden">
|
||||
<Column gap={ 2 }>
|
||||
<Grid className="text-black font-bold border-bottom pb-1" gap={ 1 }>
|
||||
<div className="col-span-2">Type</div>
|
||||
<div className="col-span-3">Room/Player</div>
|
||||
<div className="col-span-4">Opened</div>
|
||||
<div className="col-span-3">Picker</div>
|
||||
</Grid>
|
||||
</Column>
|
||||
<Column className="striped-children" gap={ 0 } overflow="auto">
|
||||
{ pickedIssues && (pickedIssues.length > 0) && pickedIssues.map(issue =>
|
||||
{
|
||||
return (
|
||||
<Grid key={ issue.issueId } alignItems="center" className="text-black py-1 border-bottom" gap={ 1 }>
|
||||
<div className="col-span-2">{ issue.categoryId }</div>
|
||||
<div className="col-span-3">{ issue.reportedUserName }</div>
|
||||
<div className="col-span-4">{ new Date(Date.now() - issue.issueAgeInMilliseconds).toLocaleTimeString() }</div>
|
||||
<div className="col-span-3">{ issue.pickerUserName }</div>
|
||||
</Grid>
|
||||
);
|
||||
}) }
|
||||
</Column>
|
||||
</Column>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,90 @@
|
||||
import { GetSessionDataManager, IssueMessageData } from '@nitrots/nitro-renderer';
|
||||
import { FC, useState } from 'react';
|
||||
import { NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView } from '../../../../common';
|
||||
import { useModTools } from '../../../../hooks';
|
||||
import { ModToolsIssueInfoView } from './ModToolsIssueInfoView';
|
||||
import { ModToolsMyIssuesTabView } from './ModToolsMyIssuesTabView';
|
||||
import { ModToolsOpenIssuesTabView } from './ModToolsOpenIssuesTabView';
|
||||
import { ModToolsPickedIssuesTabView } from './ModToolsPickedIssuesTabView';
|
||||
|
||||
interface ModToolsTicketsViewProps
|
||||
{
|
||||
onCloseClick: () => void;
|
||||
}
|
||||
|
||||
const TABS: string[] = [
|
||||
'Open Issues',
|
||||
'My Issues',
|
||||
'Picked Issues'
|
||||
];
|
||||
|
||||
export const ModToolsTicketsView: FC<ModToolsTicketsViewProps> = props =>
|
||||
{
|
||||
const { onCloseClick = null } = props;
|
||||
const [ currentTab, setCurrentTab ] = useState<number>(0);
|
||||
const [ issueInfoWindows, setIssueInfoWindows ] = useState<number[]>([]);
|
||||
const { tickets = [] } = useModTools();
|
||||
|
||||
const openIssues = tickets.filter(issue => issue.state === IssueMessageData.STATE_OPEN);
|
||||
const myIssues = tickets.filter(issue => (issue.state === IssueMessageData.STATE_PICKED) && (issue.pickerUserId === GetSessionDataManager().userId));
|
||||
const pickedIssues = tickets.filter(issue => issue.state === IssueMessageData.STATE_PICKED);
|
||||
|
||||
const closeIssue = (issueId: number) =>
|
||||
{
|
||||
setIssueInfoWindows(prevValue =>
|
||||
{
|
||||
const newValue = [ ...prevValue ];
|
||||
const existingIndex = newValue.indexOf(issueId);
|
||||
|
||||
if(existingIndex >= 0) newValue.splice(existingIndex, 1);
|
||||
|
||||
return newValue;
|
||||
});
|
||||
};
|
||||
|
||||
const handleIssue = (issueId: number) =>
|
||||
{
|
||||
setIssueInfoWindows(prevValue =>
|
||||
{
|
||||
const newValue = [ ...prevValue ];
|
||||
const existingIndex = newValue.indexOf(issueId);
|
||||
|
||||
if(existingIndex === -1) newValue.push(issueId);
|
||||
else newValue.splice(existingIndex, 1);
|
||||
|
||||
return newValue;
|
||||
});
|
||||
};
|
||||
|
||||
const CurrentTabComponent = () =>
|
||||
{
|
||||
switch(currentTab)
|
||||
{
|
||||
case 0: return <ModToolsOpenIssuesTabView openIssues={ openIssues }/>;
|
||||
case 1: return <ModToolsMyIssuesTabView handleIssue={ handleIssue } myIssues={ myIssues }/>;
|
||||
case 2: return <ModToolsPickedIssuesTabView pickedIssues={ pickedIssues }/>;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<NitroCardView className="nitro-mod-tools-tickets">
|
||||
<NitroCardHeaderView headerText={ 'Tickets' } onCloseClick={ onCloseClick } />
|
||||
<NitroCardTabsView>
|
||||
{ TABS.map((tab, index) =>
|
||||
{
|
||||
return (<NitroCardTabsItemView key={ index } isActive={ (currentTab === index) } onClick={ event => setCurrentTab(index) }>
|
||||
{ tab }
|
||||
</NitroCardTabsItemView>);
|
||||
}) }
|
||||
</NitroCardTabsView>
|
||||
<NitroCardContentView gap={ 1 }>
|
||||
<CurrentTabComponent />
|
||||
</NitroCardContentView>
|
||||
</NitroCardView>
|
||||
{ issueInfoWindows && (issueInfoWindows.length > 0) && issueInfoWindows.map(issueId => <ModToolsIssueInfoView key={ issueId } issueId={ issueId } onIssueInfoClosed={ closeIssue } />) }
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,44 @@
|
||||
import { ChatRecordData, GetUserChatlogMessageComposer, UserChatlogEvent } from '@nitrots/nitro-renderer';
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
import { SendMessageComposer } from '../../../../api';
|
||||
import { DraggableWindowPosition, NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../common';
|
||||
import { useMessageEvent } from '../../../../hooks';
|
||||
import { ChatlogView } from '../chatlog/ChatlogView';
|
||||
|
||||
interface ModToolsUserChatlogViewProps
|
||||
{
|
||||
userId: number;
|
||||
onCloseClick: () => void;
|
||||
}
|
||||
|
||||
export const ModToolsUserChatlogView: FC<ModToolsUserChatlogViewProps> = props =>
|
||||
{
|
||||
const { userId = null, onCloseClick = null } = props;
|
||||
const [ userChatlog, setUserChatlog ] = useState<ChatRecordData[]>(null);
|
||||
const [ username, setUsername ] = useState<string>(null);
|
||||
|
||||
useMessageEvent<UserChatlogEvent>(UserChatlogEvent, event =>
|
||||
{
|
||||
const parser = event.getParser();
|
||||
|
||||
if(!parser || parser.data.userId !== userId) return;
|
||||
|
||||
setUsername(parser.data.username);
|
||||
setUserChatlog(parser.data.roomChatlogs);
|
||||
});
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
SendMessageComposer(new GetUserChatlogMessageComposer(userId));
|
||||
}, [ userId ]);
|
||||
|
||||
return (
|
||||
<NitroCardView className="nitro-mod-tools-chatlog" theme="primary-slim" windowPosition={ DraggableWindowPosition.TOP_LEFT }>
|
||||
<NitroCardHeaderView headerText={ `User Chatlog: ${ username || '' }` } onCloseClick={ onCloseClick } />
|
||||
<NitroCardContentView className="text-black h-full">
|
||||
{ userChatlog &&
|
||||
<ChatlogView records={ userChatlog } /> }
|
||||
</NitroCardContentView>
|
||||
</NitroCardView>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,176 @@
|
||||
import { CallForHelpTopicData, DefaultSanctionMessageComposer, ModAlertMessageComposer, ModBanMessageComposer, ModKickMessageComposer, ModMessageMessageComposer, ModMuteMessageComposer, ModTradingLockMessageComposer } from '@nitrots/nitro-renderer';
|
||||
import { FC, useMemo, useState } from 'react';
|
||||
import { ISelectedUser, LocalizeText, ModActionDefinition, NotificationAlertType, SendMessageComposer } from '../../../../api';
|
||||
import { Button, DraggableWindowPosition, Flex, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../../common';
|
||||
import { useModTools, useNotification } from '../../../../hooks';
|
||||
|
||||
interface ModToolsUserModActionViewProps
|
||||
{
|
||||
user: ISelectedUser;
|
||||
onCloseClick: () => void;
|
||||
}
|
||||
|
||||
const MOD_ACTION_DEFINITIONS = [
|
||||
new ModActionDefinition(1, 'Alert', ModActionDefinition.ALERT, 1, 0),
|
||||
new ModActionDefinition(2, 'Mute 1h', ModActionDefinition.MUTE, 2, 0),
|
||||
new ModActionDefinition(3, 'Ban 18h', ModActionDefinition.BAN, 3, 0),
|
||||
new ModActionDefinition(4, 'Ban 7 days', ModActionDefinition.BAN, 4, 0),
|
||||
new ModActionDefinition(5, 'Ban 30 days (step 1)', ModActionDefinition.BAN, 5, 0),
|
||||
new ModActionDefinition(7, 'Ban 30 days (step 2)', ModActionDefinition.BAN, 7, 0),
|
||||
new ModActionDefinition(6, 'Ban 100 years', ModActionDefinition.BAN, 6, 0),
|
||||
new ModActionDefinition(106, 'Ban avatar-only 100 years', ModActionDefinition.BAN, 6, 0),
|
||||
new ModActionDefinition(101, 'Kick', ModActionDefinition.KICK, 0, 0),
|
||||
new ModActionDefinition(102, 'Lock trade 1 week', ModActionDefinition.TRADE_LOCK, 0, 168),
|
||||
new ModActionDefinition(104, 'Lock trade permanent', ModActionDefinition.TRADE_LOCK, 0, 876000),
|
||||
new ModActionDefinition(105, 'Message', ModActionDefinition.MESSAGE, 0, 0),
|
||||
];
|
||||
|
||||
export const ModToolsUserModActionView: FC<ModToolsUserModActionViewProps> = props =>
|
||||
{
|
||||
const { user = null, onCloseClick = null } = props;
|
||||
const [ selectedTopic, setSelectedTopic ] = useState(-1);
|
||||
const [ selectedAction, setSelectedAction ] = useState(-1);
|
||||
const [ message, setMessage ] = useState<string>('');
|
||||
const { cfhCategories = null, settings = null } = useModTools();
|
||||
const { simpleAlert = null } = useNotification();
|
||||
|
||||
const topics = useMemo(() =>
|
||||
{
|
||||
const values: CallForHelpTopicData[] = [];
|
||||
|
||||
if(cfhCategories && cfhCategories.length)
|
||||
{
|
||||
for(const category of cfhCategories)
|
||||
{
|
||||
for(const topic of category.topics) values.push(topic);
|
||||
}
|
||||
}
|
||||
|
||||
return values;
|
||||
}, [ cfhCategories ]);
|
||||
|
||||
const sendAlert = (message: string) => simpleAlert(message, NotificationAlertType.DEFAULT, null, null, 'Error');
|
||||
|
||||
const sendDefaultSanction = () =>
|
||||
{
|
||||
let errorMessage: string = null;
|
||||
|
||||
const category = topics[selectedTopic];
|
||||
|
||||
if(selectedTopic === -1) errorMessage = 'You must select a CFH topic';
|
||||
|
||||
if(errorMessage) return sendAlert(errorMessage);
|
||||
|
||||
const messageOrDefault = (message.trim().length === 0) ? LocalizeText(`help.cfh.topic.${ category.id }`) : message;
|
||||
|
||||
SendMessageComposer(new DefaultSanctionMessageComposer(user.userId, selectedTopic, messageOrDefault));
|
||||
|
||||
onCloseClick();
|
||||
};
|
||||
|
||||
const sendSanction = () =>
|
||||
{
|
||||
let errorMessage: string = null;
|
||||
|
||||
const category = topics[selectedTopic];
|
||||
const sanction = MOD_ACTION_DEFINITIONS[selectedAction];
|
||||
|
||||
if((selectedTopic === -1) || (selectedAction === -1)) errorMessage = 'You must select a CFH topic and Sanction';
|
||||
else if(!settings || !settings.cfhPermission) errorMessage = 'You do not have permission to do this';
|
||||
else if(!category) errorMessage = 'You must select a CFH topic';
|
||||
else if(!sanction) errorMessage = 'You must select a sanction';
|
||||
|
||||
if(errorMessage)
|
||||
{
|
||||
sendAlert(errorMessage);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const messageOrDefault = (message.trim().length === 0) ? LocalizeText(`help.cfh.topic.${ category.id }`) : message;
|
||||
|
||||
switch(sanction.actionType)
|
||||
{
|
||||
case ModActionDefinition.ALERT: {
|
||||
if(!settings.alertPermission)
|
||||
{
|
||||
sendAlert('You have insufficient permissions');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
SendMessageComposer(new ModAlertMessageComposer(user.userId, messageOrDefault, category.id));
|
||||
break;
|
||||
}
|
||||
case ModActionDefinition.MUTE:
|
||||
SendMessageComposer(new ModMuteMessageComposer(user.userId, messageOrDefault, category.id));
|
||||
break;
|
||||
case ModActionDefinition.BAN: {
|
||||
if(!settings.banPermission)
|
||||
{
|
||||
sendAlert('You have insufficient permissions');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
SendMessageComposer(new ModBanMessageComposer(user.userId, messageOrDefault, category.id, selectedAction, (sanction.actionId === 106)));
|
||||
break;
|
||||
}
|
||||
case ModActionDefinition.KICK: {
|
||||
if(!settings.kickPermission)
|
||||
{
|
||||
sendAlert('You have insufficient permissions');
|
||||
return;
|
||||
}
|
||||
|
||||
SendMessageComposer(new ModKickMessageComposer(user.userId, messageOrDefault, category.id));
|
||||
break;
|
||||
}
|
||||
case ModActionDefinition.TRADE_LOCK: {
|
||||
const numSeconds = (sanction.actionLengthHours * 60);
|
||||
|
||||
SendMessageComposer(new ModTradingLockMessageComposer(user.userId, messageOrDefault, numSeconds, category.id));
|
||||
break;
|
||||
}
|
||||
case ModActionDefinition.MESSAGE: {
|
||||
if(message.trim().length === 0)
|
||||
{
|
||||
sendAlert('Please write a message to user');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
SendMessageComposer(new ModMessageMessageComposer(user.userId, message, category.id));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
onCloseClick();
|
||||
};
|
||||
|
||||
if(!user) return null;
|
||||
|
||||
return (
|
||||
<NitroCardView className="nitro-mod-tools-user-action" theme="primary-slim" windowPosition={ DraggableWindowPosition.TOP_LEFT }>
|
||||
<NitroCardHeaderView headerText={ 'Mod Action: ' + (user ? user.username : '') } onCloseClick={ () => onCloseClick() } />
|
||||
<NitroCardContentView className="text-black">
|
||||
<select className="form-select form-select-sm" value={ selectedTopic } onChange={ event => setSelectedTopic(parseInt(event.target.value)) }>
|
||||
<option disabled value={ -1 }>CFH Topic</option>
|
||||
{ topics.map((topic, index) => <option key={ index } value={ index }>{ LocalizeText('help.cfh.topic.' + topic.id) }</option>) }
|
||||
</select>
|
||||
<select className="form-select form-select-sm" value={ selectedAction } onChange={ event => setSelectedAction(parseInt(event.target.value)) }>
|
||||
<option disabled value={ -1 }>Sanction Type</option>
|
||||
{ MOD_ACTION_DEFINITIONS.map((action, index) => <option key={ index } value={ index }>{ action.name }</option>) }
|
||||
</select>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text small>Optional message type, overrides default</Text>
|
||||
<textarea className="min-h-[calc(1.5em+ .5rem+2px)] px-[.5rem] py-[.25rem] rounded-[.2rem]" value={ message } onChange={ event => setMessage(event.target.value) } />
|
||||
</div>
|
||||
<Flex gap={ 1 } justifyContent="between">
|
||||
<Button variant="primary" onClick={ sendDefaultSanction }>Default Sanction</Button>
|
||||
<Button variant="success" onClick={ sendSanction }>Sanction</Button>
|
||||
</Flex>
|
||||
</NitroCardContentView>
|
||||
</NitroCardView>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,60 @@
|
||||
import { GetRoomVisitsMessageComposer, RoomVisitsData, RoomVisitsEvent } from '@nitrots/nitro-renderer';
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
import { SendMessageComposer, TryVisitRoom } from '../../../../api';
|
||||
import { Column, DraggableWindowPosition, Grid, InfiniteScroll, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../../common';
|
||||
import { useMessageEvent } from '../../../../hooks';
|
||||
|
||||
interface ModToolsUserRoomVisitsViewProps
|
||||
{
|
||||
userId: number;
|
||||
onCloseClick: () => void;
|
||||
}
|
||||
|
||||
export const ModToolsUserRoomVisitsView: FC<ModToolsUserRoomVisitsViewProps> = props =>
|
||||
{
|
||||
const { userId = null, onCloseClick = null } = props;
|
||||
const [ roomVisitData, setRoomVisitData ] = useState<RoomVisitsData>(null);
|
||||
|
||||
useMessageEvent<RoomVisitsEvent>(RoomVisitsEvent, event =>
|
||||
{
|
||||
const parser = event.getParser();
|
||||
|
||||
if(parser.data.userId !== userId) return;
|
||||
|
||||
setRoomVisitData(parser.data);
|
||||
});
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
SendMessageComposer(new GetRoomVisitsMessageComposer(userId));
|
||||
}, [ userId ]);
|
||||
|
||||
if(!userId) return null;
|
||||
|
||||
return (
|
||||
<NitroCardView className="nitro-mod-tools-user-visits" theme="primary-slim" windowPosition={ DraggableWindowPosition.TOP_LEFT }>
|
||||
<NitroCardHeaderView headerText={ 'User Visits' } onCloseClick={ onCloseClick } />
|
||||
<NitroCardContentView className="text-black" gap={ 1 }>
|
||||
<Column fullHeight gap={ 0 } overflow="hidden">
|
||||
<Column gap={ 2 }>
|
||||
<Grid className="text-black font-bold border-bottom pb-1" gap={ 1 }>
|
||||
<div className="col-span-2">Time</div>
|
||||
<div className="col-span-7">Room name</div>
|
||||
<div className="col-span-3">Visit</div>
|
||||
</Grid>
|
||||
</Column>
|
||||
<InfiniteScroll rowRender={ row =>
|
||||
{
|
||||
return (
|
||||
<Grid alignItems="center" className="text-black py-1 border-bottom" fullHeight={ false } gap={ 1 }>
|
||||
<Text className="col-span-2">{ row.enterHour.toString().padStart(2, '0') }: { row.enterMinute.toString().padStart(2, '0') }</Text>
|
||||
<Text className="col-span-7">{ row.roomName }</Text>
|
||||
<Text bold pointer underline className="col-span-3" variant="primary" onClick={ event => TryVisitRoom(row.roomId) }>Visit Room</Text>
|
||||
</Grid>
|
||||
);
|
||||
} } rows={ roomVisitData?.rooms ?? [] } />
|
||||
</Column>
|
||||
</NitroCardContentView>
|
||||
</NitroCardView>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,45 @@
|
||||
import { ModMessageMessageComposer } from '@nitrots/nitro-renderer';
|
||||
import { FC, useState } from 'react';
|
||||
import { ISelectedUser, SendMessageComposer } from '../../../../api';
|
||||
import { Button, DraggableWindowPosition, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../../common';
|
||||
import { useNotification } from '../../../../hooks';
|
||||
|
||||
interface ModToolsUserSendMessageViewProps
|
||||
{
|
||||
user: ISelectedUser;
|
||||
onCloseClick: () => void;
|
||||
}
|
||||
|
||||
export const ModToolsUserSendMessageView: FC<ModToolsUserSendMessageViewProps> = props =>
|
||||
{
|
||||
const { user = null, onCloseClick = null } = props;
|
||||
const [ message, setMessage ] = useState('');
|
||||
const { simpleAlert = null } = useNotification();
|
||||
|
||||
if(!user) return null;
|
||||
|
||||
const sendMessage = () =>
|
||||
{
|
||||
if(message.trim().length === 0)
|
||||
{
|
||||
simpleAlert('Please write a message to user.', null, null, null, 'Error', null);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
SendMessageComposer(new ModMessageMessageComposer(user.userId, message, -999));
|
||||
|
||||
onCloseClick();
|
||||
};
|
||||
|
||||
return (
|
||||
<NitroCardView className="nitro-mod-tools-user-message" theme="primary-slim" windowPosition={ DraggableWindowPosition.TOP_LEFT }>
|
||||
<NitroCardHeaderView headerText={ 'Send Message' } onCloseClick={ () => onCloseClick() } />
|
||||
<NitroCardContentView className="text-black">
|
||||
<Text>Message To: { user.username }</Text>
|
||||
<textarea className="min-h-[calc(1.5em+ .5rem+2px)] px-[.5rem] py-[.25rem] rounded-[.2rem]" value={ message } onChange={ event => setMessage(event.target.value) }></textarea>
|
||||
<Button fullWidth onClick={ sendMessage }>Send message</Button>
|
||||
</NitroCardContentView>
|
||||
</NitroCardView>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,156 @@
|
||||
import { CreateLinkEvent, GetModeratorUserInfoMessageComposer, ModeratorUserInfoData, ModeratorUserInfoEvent } from '@nitrots/nitro-renderer';
|
||||
import { FC, useEffect, useMemo, useState } from 'react';
|
||||
import { FriendlyTime, LocalizeText, SendMessageComposer } from '../../../../api';
|
||||
import { Button, Column, DraggableWindowPosition, Grid, NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../common';
|
||||
import { useMessageEvent } from '../../../../hooks';
|
||||
import { ModToolsUserModActionView } from './ModToolsUserModActionView';
|
||||
import { ModToolsUserRoomVisitsView } from './ModToolsUserRoomVisitsView';
|
||||
import { ModToolsUserSendMessageView } from './ModToolsUserSendMessageView';
|
||||
|
||||
interface ModToolsUserViewProps
|
||||
{
|
||||
userId: number;
|
||||
onCloseClick: () => void;
|
||||
}
|
||||
|
||||
export const ModToolsUserView: FC<ModToolsUserViewProps> = props =>
|
||||
{
|
||||
const { onCloseClick = null, userId = null } = props;
|
||||
const [ userInfo, setUserInfo ] = useState<ModeratorUserInfoData>(null);
|
||||
const [ sendMessageVisible, setSendMessageVisible ] = useState(false);
|
||||
const [ modActionVisible, setModActionVisible ] = useState(false);
|
||||
const [ roomVisitsVisible, setRoomVisitsVisible ] = useState(false);
|
||||
|
||||
const userProperties = useMemo(() =>
|
||||
{
|
||||
if(!userInfo) return null;
|
||||
|
||||
return [
|
||||
{
|
||||
localeKey: 'modtools.userinfo.userName',
|
||||
value: userInfo.userName,
|
||||
showOnline: true
|
||||
},
|
||||
{
|
||||
localeKey: 'modtools.userinfo.cfhCount',
|
||||
value: userInfo.cfhCount.toString()
|
||||
},
|
||||
{
|
||||
localeKey: 'modtools.userinfo.abusiveCfhCount',
|
||||
value: userInfo.abusiveCfhCount.toString()
|
||||
},
|
||||
{
|
||||
localeKey: 'modtools.userinfo.cautionCount',
|
||||
value: userInfo.cautionCount.toString()
|
||||
},
|
||||
{
|
||||
localeKey: 'modtools.userinfo.banCount',
|
||||
value: userInfo.banCount.toString()
|
||||
},
|
||||
{
|
||||
localeKey: 'modtools.userinfo.lastSanctionTime',
|
||||
value: userInfo.lastSanctionTime
|
||||
},
|
||||
{
|
||||
localeKey: 'modtools.userinfo.tradingLockCount',
|
||||
value: userInfo.tradingLockCount.toString()
|
||||
},
|
||||
{
|
||||
localeKey: 'modtools.userinfo.tradingExpiryDate',
|
||||
value: userInfo.tradingExpiryDate
|
||||
},
|
||||
{
|
||||
localeKey: 'modtools.userinfo.minutesSinceLastLogin',
|
||||
value: FriendlyTime.format(userInfo.minutesSinceLastLogin * 60, '.ago', 2)
|
||||
},
|
||||
{
|
||||
localeKey: 'modtools.userinfo.lastPurchaseDate',
|
||||
value: userInfo.lastPurchaseDate
|
||||
},
|
||||
{
|
||||
localeKey: 'modtools.userinfo.primaryEmailAddress',
|
||||
value: userInfo.primaryEmailAddress
|
||||
},
|
||||
{
|
||||
localeKey: 'modtools.userinfo.identityRelatedBanCount',
|
||||
value: userInfo.identityRelatedBanCount.toString()
|
||||
},
|
||||
{
|
||||
localeKey: 'modtools.userinfo.registrationAgeInMinutes',
|
||||
value: FriendlyTime.format(userInfo.registrationAgeInMinutes * 60, '.ago', 2)
|
||||
},
|
||||
{
|
||||
localeKey: 'modtools.userinfo.userClassification',
|
||||
value: userInfo.userClassification
|
||||
}
|
||||
];
|
||||
}, [ userInfo ]);
|
||||
|
||||
useMessageEvent<ModeratorUserInfoEvent>(ModeratorUserInfoEvent, event =>
|
||||
{
|
||||
const parser = event.getParser();
|
||||
|
||||
if(!parser || parser.data.userId !== userId) return;
|
||||
|
||||
setUserInfo(parser.data);
|
||||
});
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
SendMessageComposer(new GetModeratorUserInfoMessageComposer(userId));
|
||||
}, [ userId ]);
|
||||
|
||||
if(!userInfo) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<NitroCardView className="nitro-mod-tools-user" theme="primary-slim" windowPosition={ DraggableWindowPosition.TOP_LEFT }>
|
||||
<NitroCardHeaderView headerText={ LocalizeText('modtools.userinfo.title', [ 'username' ], [ userInfo.userName ]) } onCloseClick={ () => onCloseClick() } />
|
||||
<NitroCardContentView className="text-black">
|
||||
<Grid overflow="hidden">
|
||||
<Column overflow="auto" size={ 8 }>
|
||||
<table className="table table-striped table-sm table-text-small text-black m-0">
|
||||
<tbody>
|
||||
{ userProperties.map( (property, index) =>
|
||||
{
|
||||
|
||||
return (
|
||||
<tr key={ index }>
|
||||
<th scope="row">{ LocalizeText(property.localeKey) }</th>
|
||||
<td>
|
||||
{ property.value }
|
||||
{ property.showOnline &&
|
||||
<i className={ `icon icon-pf-${ userInfo.online ? 'online' : 'offline' } ms-2` } /> }
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}) }
|
||||
</tbody>
|
||||
</table>
|
||||
</Column>
|
||||
<Column gap={ 1 } size={ 4 }>
|
||||
<Button onClick={ event => CreateLinkEvent(`mod-tools/open-user-chatlog/${ userId }`) }>
|
||||
Room Chat
|
||||
</Button>
|
||||
<Button onClick={ event => setSendMessageVisible(!sendMessageVisible) }>
|
||||
Send Message
|
||||
</Button>
|
||||
<Button onClick={ event => setRoomVisitsVisible(!roomVisitsVisible) }>
|
||||
Room Visits
|
||||
</Button>
|
||||
<Button onClick={ event => setModActionVisible(!modActionVisible) }>
|
||||
Mod Action
|
||||
</Button>
|
||||
</Column>
|
||||
</Grid>
|
||||
</NitroCardContentView>
|
||||
</NitroCardView>
|
||||
{ sendMessageVisible &&
|
||||
<ModToolsUserSendMessageView user={ { userId: userId, username: userInfo.userName } } onCloseClick={ () => setSendMessageVisible(false) } /> }
|
||||
{ modActionVisible &&
|
||||
<ModToolsUserModActionView user={ { userId: userId, username: userInfo.userName } } onCloseClick={ () => setModActionVisible(false) } /> }
|
||||
{ roomVisitsVisible &&
|
||||
<ModToolsUserRoomVisitsView userId={ userId } onCloseClick={ () => setRoomVisitsVisible(false) } /> }
|
||||
</>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user