mirror of
https://github.com/billsonnn/nitro-react.git
synced 2024-11-23 14:40:50 +01:00
Merge pull request #6 from billsonnn/feature/mod-tools
Feature/mod tools
This commit is contained in:
commit
5a7f758cde
@ -2,6 +2,7 @@ import { IFurnitureData, NitroEvent, ObjectDataFactory, PetFigureData, PetRespec
|
|||||||
import { GetNitroInstance, GetRoomEngine, GetSessionDataManager, IsOwnerOfFurniture } from '../../../..';
|
import { GetNitroInstance, GetRoomEngine, GetSessionDataManager, IsOwnerOfFurniture } from '../../../..';
|
||||||
import { InventoryTradeRequestEvent, WiredSelectObjectEvent } from '../../../../../events';
|
import { InventoryTradeRequestEvent, WiredSelectObjectEvent } from '../../../../../events';
|
||||||
import { FriendsSendFriendRequestEvent } from '../../../../../events/friends/FriendsSendFriendRequestEvent';
|
import { FriendsSendFriendRequestEvent } from '../../../../../events/friends/FriendsSendFriendRequestEvent';
|
||||||
|
import { HelpReportUserEvent } from '../../../../../events/help/HelpReportUserEvent';
|
||||||
import { dispatchUiEvent } from '../../../../../hooks/events';
|
import { dispatchUiEvent } from '../../../../../hooks/events';
|
||||||
import { SendMessageHook } from '../../../../../hooks/messages';
|
import { SendMessageHook } from '../../../../../hooks/messages';
|
||||||
import { PetSupplementEnum } from '../../../../../views/room/widgets/avatar-info/common/PetSupplementEnum';
|
import { PetSupplementEnum } from '../../../../../views/room/widgets/avatar-info/common/PetSupplementEnum';
|
||||||
@ -164,6 +165,7 @@ export class RoomWidgetInfostandHandler extends RoomWidgetHandler
|
|||||||
case RoomWidgetUserActionMessage.REPORT:
|
case RoomWidgetUserActionMessage.REPORT:
|
||||||
return;
|
return;
|
||||||
case RoomWidgetUserActionMessage.REPORT_CFH_OTHER:
|
case RoomWidgetUserActionMessage.REPORT_CFH_OTHER:
|
||||||
|
dispatchUiEvent(new HelpReportUserEvent(userId));
|
||||||
return;
|
return;
|
||||||
case RoomWidgetUserActionMessage.AMBASSADOR_ALERT_USER:
|
case RoomWidgetUserActionMessage.AMBASSADOR_ALERT_USER:
|
||||||
this.container.roomSession.sendAmbassadorAlertMessage(userId);
|
this.container.roomSession.sendAmbassadorAlertMessage(userId);
|
||||||
|
BIN
src/assets/images/help/help_index.png
Normal file
BIN
src/assets/images/help/help_index.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.5 KiB |
9
src/events/chat-history/ChatHistoryEvent.ts
Normal file
9
src/events/chat-history/ChatHistoryEvent.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { NitroEvent } from '@nitrots/nitro-renderer';
|
||||||
|
|
||||||
|
export class ChatHistoryEvent extends NitroEvent
|
||||||
|
{
|
||||||
|
public static SHOW_CHAT_HISTORY: string = 'CHE_SHOW_CHAT_HISTORY';
|
||||||
|
public static HIDE_CHAT_HISTORY: string = 'CHE_HIDE_CHAT_HISTORY';
|
||||||
|
public static TOGGLE_CHAT_HISTORY: string = 'CHE_TOGGLE_CHAT_HISTORY';
|
||||||
|
public static CHAT_HISTORY_CHANGED: string = 'CHE_CHAT_HISTORY_CHANGED';
|
||||||
|
}
|
8
src/events/help/HelpEvent.ts
Normal file
8
src/events/help/HelpEvent.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { NitroEvent } from '@nitrots/nitro-renderer';
|
||||||
|
|
||||||
|
export class HelpEvent extends NitroEvent
|
||||||
|
{
|
||||||
|
public static SHOW_HELP_CENTER: string = 'HCE_SHOW_HELP_CENTER';
|
||||||
|
public static HIDE_HELP_CENTER: string = 'HCE_HIDE_HELP_CENTER';
|
||||||
|
public static TOGGLE_HELP_CENTER: string = 'HCE_TOGGLE_HELP_CENTER';
|
||||||
|
}
|
20
src/events/help/HelpReportUserEvent.ts
Normal file
20
src/events/help/HelpReportUserEvent.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { HelpEvent } from './HelpEvent';
|
||||||
|
|
||||||
|
export class HelpReportUserEvent extends HelpEvent
|
||||||
|
{
|
||||||
|
public static REPORT_USER: string = 'HCE_HELP_CENTER_REPORT_USER';
|
||||||
|
|
||||||
|
private _reportedUserId: number;
|
||||||
|
|
||||||
|
constructor(userId: number)
|
||||||
|
{
|
||||||
|
super(HelpReportUserEvent.REPORT_USER);
|
||||||
|
|
||||||
|
this._reportedUserId = userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get reportedUserId(): number
|
||||||
|
{
|
||||||
|
return this._reportedUserId;
|
||||||
|
}
|
||||||
|
}
|
@ -5,6 +5,8 @@ export class ModToolsEvent extends NitroEvent
|
|||||||
public static SHOW_MOD_TOOLS: string = 'MTE_SHOW_MOD_TOOLS';
|
public static SHOW_MOD_TOOLS: string = 'MTE_SHOW_MOD_TOOLS';
|
||||||
public static HIDE_MOD_TOOLS: string = 'MTE_HIDE_MOD_TOOLS';
|
public static HIDE_MOD_TOOLS: string = 'MTE_HIDE_MOD_TOOLS';
|
||||||
public static TOGGLE_MOD_TOOLS: string = 'MTE_TOGGLE_MOD_TOOLS';
|
public static TOGGLE_MOD_TOOLS: string = 'MTE_TOGGLE_MOD_TOOLS';
|
||||||
public static SELECT_USER: string = 'MTE_SELECT_USER';
|
|
||||||
public static OPEN_ROOM_INFO: string = 'MTE_OPEN_ROOM_INFO';
|
public static OPEN_ROOM_INFO: string = 'MTE_OPEN_ROOM_INFO';
|
||||||
|
public static OPEN_ROOM_CHATLOG: string = 'MTE_OPEN_ROOM_CHATLOG';
|
||||||
|
public static OPEN_USER_INFO: string = 'MTE_OPEN_USER_INFO';
|
||||||
|
public static OPEN_USER_CHATLOG: string = 'MTE_OPEN_USER_CHATLOG';
|
||||||
}
|
}
|
||||||
|
18
src/events/mod-tools/ModToolsOpenRoomChatlogEvent.ts
Normal file
18
src/events/mod-tools/ModToolsOpenRoomChatlogEvent.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { ModToolsEvent } from './ModToolsEvent';
|
||||||
|
|
||||||
|
export class ModToolsOpenRoomChatlogEvent extends ModToolsEvent
|
||||||
|
{
|
||||||
|
private _roomId: number;
|
||||||
|
|
||||||
|
constructor(roomId: number)
|
||||||
|
{
|
||||||
|
super(ModToolsEvent.OPEN_ROOM_CHATLOG);
|
||||||
|
|
||||||
|
this._roomId = roomId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get roomId(): number
|
||||||
|
{
|
||||||
|
return this._roomId;
|
||||||
|
}
|
||||||
|
}
|
18
src/events/mod-tools/ModToolsOpenUserChatlogEvent.ts
Normal file
18
src/events/mod-tools/ModToolsOpenUserChatlogEvent.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { ModToolsEvent } from './ModToolsEvent';
|
||||||
|
|
||||||
|
export class ModToolsOpenUserChatlogEvent extends ModToolsEvent
|
||||||
|
{
|
||||||
|
private _userId: number;
|
||||||
|
|
||||||
|
constructor(userId: number)
|
||||||
|
{
|
||||||
|
super(ModToolsEvent.OPEN_USER_CHATLOG);
|
||||||
|
|
||||||
|
this._userId = userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get userId(): number
|
||||||
|
{
|
||||||
|
return this._userId;
|
||||||
|
}
|
||||||
|
}
|
18
src/events/mod-tools/ModToolsOpenUserInfoEvent.ts
Normal file
18
src/events/mod-tools/ModToolsOpenUserInfoEvent.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { ModToolsEvent } from './ModToolsEvent';
|
||||||
|
|
||||||
|
export class ModToolsOpenUserInfoEvent extends ModToolsEvent
|
||||||
|
{
|
||||||
|
private _userId: number;
|
||||||
|
|
||||||
|
constructor(userId: number)
|
||||||
|
{
|
||||||
|
super(ModToolsEvent.OPEN_USER_INFO);
|
||||||
|
|
||||||
|
this._userId = userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get userId(): number
|
||||||
|
{
|
||||||
|
return this._userId;
|
||||||
|
}
|
||||||
|
}
|
@ -1,25 +0,0 @@
|
|||||||
import { ModToolsEvent } from './ModToolsEvent';
|
|
||||||
|
|
||||||
export class ModToolsSelectUserEvent extends ModToolsEvent
|
|
||||||
{
|
|
||||||
private _webID: number;
|
|
||||||
private _name: string;
|
|
||||||
|
|
||||||
constructor(webID: number, name: string)
|
|
||||||
{
|
|
||||||
super(ModToolsEvent.SELECT_USER);
|
|
||||||
|
|
||||||
this._webID = webID;
|
|
||||||
this._name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public get webID(): number
|
|
||||||
{
|
|
||||||
return this._webID;
|
|
||||||
}
|
|
||||||
|
|
||||||
public get name(): string
|
|
||||||
{
|
|
||||||
return this._name;
|
|
||||||
}
|
|
||||||
}
|
|
@ -4,6 +4,17 @@ $nitro-card-tabs-height: 33px;
|
|||||||
.nitro-card {
|
.nitro-card {
|
||||||
pointer-events: all;
|
pointer-events: all;
|
||||||
resize: both;
|
resize: both;
|
||||||
|
|
||||||
|
&.theme-dark {
|
||||||
|
padding: 2px;
|
||||||
|
background-color: #1C323F;
|
||||||
|
border: 2px solid rgba(255, 255, 255, 0.5);
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.theme-primary {
|
||||||
|
border: $border-width solid $border-color;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.nitro-card-responsive {
|
.nitro-card-responsive {
|
||||||
@ -15,10 +26,6 @@ $nitro-card-tabs-height: 33px;
|
|||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
.theme-primary {
|
|
||||||
border: $border-width solid $border-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
@include media-breakpoint-down(lg) {
|
@include media-breakpoint-down(lg) {
|
||||||
|
|
||||||
.draggable-window {
|
.draggable-window {
|
||||||
|
@ -3,6 +3,10 @@
|
|||||||
padding-top: $container-padding-x;
|
padding-top: $container-padding-x;
|
||||||
padding-bottom: $container-padding-x;
|
padding-bottom: $container-padding-x;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
|
||||||
|
&.theme-dark {
|
||||||
|
background-color: #1C323F !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@include media-breakpoint-down(lg) {
|
@include media-breakpoint-down(lg) {
|
||||||
|
@ -4,11 +4,11 @@ import { NitroCardContentViewProps } from './NitroCardContextView.types';
|
|||||||
|
|
||||||
export const NitroCardContentView: FC<NitroCardContentViewProps> = props =>
|
export const NitroCardContentView: FC<NitroCardContentViewProps> = props =>
|
||||||
{
|
{
|
||||||
const { children = null, className = '', ...rest } = props;
|
const { theme = 'primary', children = null, className = '', ...rest } = props;
|
||||||
const { simple = false } = useNitroCardContext();
|
const { simple = false } = useNitroCardContext();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={ `container-fluid bg-light content-area d-flex flex-column overflow-auto ${ (simple ? 'simple' : '') } ${ className || '' }` } { ...rest }>
|
<div className={ `container-fluid ${ theme === 'primary' ? 'bg-light' : ''} content-area d-flex flex-column overflow-auto theme-${theme} ${ (simple ? 'simple' : '') } ${ className || '' }` } { ...rest }>
|
||||||
{ children }
|
{ children }
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -2,4 +2,6 @@ import { DetailsHTMLAttributes } from 'react';
|
|||||||
|
|
||||||
|
|
||||||
export interface NitroCardContentViewProps extends DetailsHTMLAttributes<HTMLDivElement>
|
export interface NitroCardContentViewProps extends DetailsHTMLAttributes<HTMLDivElement>
|
||||||
{}
|
{
|
||||||
|
theme?: string;
|
||||||
|
}
|
||||||
|
@ -15,6 +15,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.theme-dark {
|
||||||
|
background-color: #3d5f6e !important;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
.bg-tertiary-split {
|
.bg-tertiary-split {
|
||||||
position: relative;
|
position: relative;
|
||||||
border-bottom: 2px solid darken($quaternary, 5);
|
border-bottom: 2px solid darken($quaternary, 5);
|
||||||
|
@ -4,7 +4,7 @@ import { NitroCardHeaderViewProps } from './NitroCardHeaderView.types';
|
|||||||
|
|
||||||
export const NitroCardHeaderView: FC<NitroCardHeaderViewProps> = props =>
|
export const NitroCardHeaderView: FC<NitroCardHeaderViewProps> = props =>
|
||||||
{
|
{
|
||||||
const { headerText = null, onCloseClick = null } = props;
|
const { headerText = null, onCloseClick = null, theme= 'primary' } = props;
|
||||||
const { simple = false } = useNitroCardContext();
|
const { simple = false } = useNitroCardContext();
|
||||||
|
|
||||||
const onMouseDown = useCallback((event: MouseEvent<HTMLDivElement>) =>
|
const onMouseDown = useCallback((event: MouseEvent<HTMLDivElement>) =>
|
||||||
@ -31,7 +31,7 @@ export const NitroCardHeaderView: FC<NitroCardHeaderViewProps> = props =>
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="drag-handler container-fluid bg-primary">
|
<div className="drag-handler container-fluid bg-primary">
|
||||||
<div className="row nitro-card-header overflow-hidden">
|
<div className={`row nitro-card-header overflow-hidden theme-${theme}`}>
|
||||||
<div className="d-flex justify-content-center align-items-center w-100 position-relative">
|
<div className="d-flex justify-content-center align-items-center w-100 position-relative">
|
||||||
<div className="h4 text-white text-shadow header-text">{ headerText }</div>
|
<div className="h4 text-white text-shadow header-text">{ headerText }</div>
|
||||||
<div className="position-absolute header-close" onMouseDownCapture={ onMouseDown } onClick={ onCloseClick }>
|
<div className="position-absolute header-close" onMouseDownCapture={ onMouseDown } onClick={ onCloseClick }>
|
||||||
|
@ -3,5 +3,6 @@ import { MouseEvent } from 'react';
|
|||||||
export interface NitroCardHeaderViewProps
|
export interface NitroCardHeaderViewProps
|
||||||
{
|
{
|
||||||
headerText: string;
|
headerText: string;
|
||||||
|
theme?: string;
|
||||||
onCloseClick: (event: MouseEvent) => void;
|
onCloseClick: (event: MouseEvent) => void;
|
||||||
}
|
}
|
||||||
|
@ -20,3 +20,5 @@
|
|||||||
@import './achievements/AchievementsView';
|
@import './achievements/AchievementsView';
|
||||||
@import './user-settings/UserSettingsView';
|
@import './user-settings/UserSettingsView';
|
||||||
@import './user-profile/UserProfileVew';
|
@import './user-profile/UserProfileVew';
|
||||||
|
@import './chat-history/ChatHistoryView';
|
||||||
|
@import './help/HelpView';
|
||||||
|
107
src/views/chat-history/ChatHistoryMessageHandler.tsx
Normal file
107
src/views/chat-history/ChatHistoryMessageHandler.tsx
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
import { RoomInfoEvent, RoomSessionChatEvent, RoomSessionEvent } from '@nitrots/nitro-renderer';
|
||||||
|
import { FC, useCallback, useState } from 'react';
|
||||||
|
import { GetRoomSession } from '../../api';
|
||||||
|
import { CreateMessageHook, useRoomSessionManagerEvent } from '../../hooks';
|
||||||
|
import { useChatHistoryContext } from './context/ChatHistoryContext';
|
||||||
|
import { ChatEntryType, CHAT_HISTORY_MAX, IChatEntry, IRoomHistoryEntry, ROOM_HISTORY_MAX } from './context/ChatHistoryContext.types';
|
||||||
|
import { currentDate } from './utils/Utilities';
|
||||||
|
|
||||||
|
export const ChatHistoryMessageHandler: FC<{}> = props =>
|
||||||
|
{
|
||||||
|
const { chatHistoryState = null, roomHistoryState = null } = useChatHistoryContext();
|
||||||
|
|
||||||
|
const [needsRoomInsert, setNeedsRoomInsert ] = useState(false);
|
||||||
|
|
||||||
|
const addChatEntry = useCallback((entry: IChatEntry) =>
|
||||||
|
{
|
||||||
|
entry.id = chatHistoryState.chats.length;
|
||||||
|
|
||||||
|
chatHistoryState.chats.push(entry);
|
||||||
|
|
||||||
|
//check for overflow
|
||||||
|
if(chatHistoryState.chats.length > CHAT_HISTORY_MAX)
|
||||||
|
{
|
||||||
|
chatHistoryState.chats.shift();
|
||||||
|
}
|
||||||
|
chatHistoryState.notify();
|
||||||
|
|
||||||
|
//dispatchUiEvent(new ChatHistoryEvent(ChatHistoryEvent.CHAT_HISTORY_CHANGED));
|
||||||
|
|
||||||
|
}, [chatHistoryState]);
|
||||||
|
|
||||||
|
const addRoomHistoryEntry = useCallback((entry: IRoomHistoryEntry) =>
|
||||||
|
{
|
||||||
|
roomHistoryState.roomHistory.push(entry);
|
||||||
|
|
||||||
|
// check for overflow
|
||||||
|
if(roomHistoryState.roomHistory.length > ROOM_HISTORY_MAX)
|
||||||
|
{
|
||||||
|
roomHistoryState.roomHistory.shift();
|
||||||
|
}
|
||||||
|
|
||||||
|
roomHistoryState.notify();
|
||||||
|
}, [roomHistoryState]);
|
||||||
|
|
||||||
|
const onChatEvent = useCallback((event: RoomSessionChatEvent) =>
|
||||||
|
{
|
||||||
|
const roomSession = GetRoomSession();
|
||||||
|
|
||||||
|
if(!roomSession) return;
|
||||||
|
|
||||||
|
const userData = roomSession.userDataManager.getUserDataByIndex(event.objectId);
|
||||||
|
|
||||||
|
if(!userData) return;
|
||||||
|
|
||||||
|
const timeString = currentDate();
|
||||||
|
|
||||||
|
const entry: IChatEntry = { id: -1, entityId: userData.webID, name: userData.name, look: userData.figure, entityType: userData.type, message: event.message, timestamp: timeString, type: ChatEntryType.TYPE_CHAT, roomId: roomSession.roomId };
|
||||||
|
|
||||||
|
addChatEntry(entry);
|
||||||
|
}, [addChatEntry]);
|
||||||
|
|
||||||
|
useRoomSessionManagerEvent(RoomSessionChatEvent.CHAT_EVENT, onChatEvent);
|
||||||
|
|
||||||
|
const onRoomSessionEvent = useCallback((event: RoomSessionEvent) =>
|
||||||
|
{
|
||||||
|
switch(event.type)
|
||||||
|
{
|
||||||
|
case RoomSessionEvent.STARTED:
|
||||||
|
setNeedsRoomInsert(true);
|
||||||
|
break;
|
||||||
|
case RoomSessionEvent.ENDED:
|
||||||
|
//dispatchUiEvent(new ChatHistoryEvent(ChatHistoryEvent.HIDE_CHAT_HISTORY));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useRoomSessionManagerEvent(RoomSessionEvent.ENDED, onRoomSessionEvent);
|
||||||
|
useRoomSessionManagerEvent(RoomSessionEvent.STARTED, onRoomSessionEvent);
|
||||||
|
|
||||||
|
const onRoomInfoEvent = useCallback((event: RoomInfoEvent) =>
|
||||||
|
{
|
||||||
|
const parser = event.getParser();
|
||||||
|
|
||||||
|
if(!parser) return;
|
||||||
|
|
||||||
|
const session = GetRoomSession();
|
||||||
|
|
||||||
|
if(!session || (session.roomId !== parser.data.roomId)) return;
|
||||||
|
|
||||||
|
if(needsRoomInsert)
|
||||||
|
{
|
||||||
|
const chatEntry: IChatEntry = { id: -1, entityId: parser.data.roomId, name: parser.data.roomName, timestamp: currentDate(), type: ChatEntryType.TYPE_ROOM_INFO, roomId: parser.data.roomId };
|
||||||
|
|
||||||
|
addChatEntry(chatEntry);
|
||||||
|
|
||||||
|
const roomEntry: IRoomHistoryEntry = { id: parser.data.roomId, name: parser.data.roomName };
|
||||||
|
|
||||||
|
addRoomHistoryEntry(roomEntry);
|
||||||
|
|
||||||
|
setNeedsRoomInsert(false);
|
||||||
|
}
|
||||||
|
}, [addChatEntry, addRoomHistoryEntry, needsRoomInsert]);
|
||||||
|
|
||||||
|
CreateMessageHook(RoomInfoEvent, onRoomInfoEvent);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
27
src/views/chat-history/ChatHistoryView.scss
Normal file
27
src/views/chat-history/ChatHistoryView.scss
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
.nitro-chat-history
|
||||||
|
{
|
||||||
|
width: 300px;
|
||||||
|
|
||||||
|
.chat-history-content
|
||||||
|
{
|
||||||
|
|
||||||
|
.chat-history-container
|
||||||
|
{
|
||||||
|
min-height: 200px;
|
||||||
|
|
||||||
|
.chat-history-list
|
||||||
|
{
|
||||||
|
.chathistory-entry
|
||||||
|
{
|
||||||
|
.light {
|
||||||
|
background-color: #121f27;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark {
|
||||||
|
background-color: #0d171d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
171
src/views/chat-history/ChatHistoryView.tsx
Normal file
171
src/views/chat-history/ChatHistoryView.tsx
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
|
import { AutoSizer, CellMeasurer, CellMeasurerCache, List, ListRowProps, ListRowRenderer, Size } from 'react-virtualized';
|
||||||
|
import { RenderedRows } from 'react-virtualized/dist/es/List';
|
||||||
|
import { ChatHistoryEvent } from '../../events/chat-history/ChatHistoryEvent';
|
||||||
|
import { useUiEvent } from '../../hooks';
|
||||||
|
import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../layout';
|
||||||
|
import { ChatHistoryMessageHandler } from './ChatHistoryMessageHandler';
|
||||||
|
import { ChatHistoryState } from './common/ChatHistoryState';
|
||||||
|
import { SetChatHistory } from './common/GetChatHistory';
|
||||||
|
import { RoomHistoryState } from './common/RoomHistoryState';
|
||||||
|
import { ChatHistoryContextProvider } from './context/ChatHistoryContext';
|
||||||
|
import { ChatEntryType } from './context/ChatHistoryContext.types';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const ChatHistoryView: FC<{}> = props =>
|
||||||
|
{
|
||||||
|
const [ isVisible, setIsVisible ] = useState(false);
|
||||||
|
const [ needsScroll, setNeedsScroll ] = useState(false);
|
||||||
|
const [ chatHistoryUpdateId, setChatHistoryUpdateId ] = useState(-1);
|
||||||
|
const [ roomHistoryUpdateId, setRoomHistoryUpdateId ] = useState(-1);
|
||||||
|
const [ chatHistoryState, setChatHistoryState ] = useState(new ChatHistoryState());
|
||||||
|
const [ roomHistoryState, setRoomHistoryState ] = useState(new RoomHistoryState());
|
||||||
|
const elementRef = useRef<List>(null);
|
||||||
|
|
||||||
|
useEffect(() =>
|
||||||
|
{
|
||||||
|
const chatState = new ChatHistoryState();
|
||||||
|
const roomState = new RoomHistoryState();
|
||||||
|
|
||||||
|
SetChatHistory(chatState);
|
||||||
|
|
||||||
|
chatState.notifier = () => setChatHistoryUpdateId(prevValue => (prevValue + 1));
|
||||||
|
roomState.notifier = () => setRoomHistoryUpdateId(prevValue => (prevValue + 1));
|
||||||
|
|
||||||
|
setChatHistoryState(chatState);
|
||||||
|
setRoomHistoryState(roomState);
|
||||||
|
|
||||||
|
return () => {chatState.notifier = null; roomState.notifier = null;};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onChatHistoryEvent = useCallback((event: ChatHistoryEvent) =>
|
||||||
|
{
|
||||||
|
switch(event.type)
|
||||||
|
{
|
||||||
|
case ChatHistoryEvent.SHOW_CHAT_HISTORY:
|
||||||
|
setIsVisible(true);
|
||||||
|
break;
|
||||||
|
case ChatHistoryEvent.HIDE_CHAT_HISTORY:
|
||||||
|
setIsVisible(false);
|
||||||
|
break;
|
||||||
|
case ChatHistoryEvent.TOGGLE_CHAT_HISTORY:
|
||||||
|
setIsVisible(!isVisible);
|
||||||
|
break;
|
||||||
|
case ChatHistoryEvent.CHAT_HISTORY_CHANGED:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}, [isVisible]);
|
||||||
|
|
||||||
|
useUiEvent(ChatHistoryEvent.HIDE_CHAT_HISTORY, onChatHistoryEvent);
|
||||||
|
useUiEvent(ChatHistoryEvent.SHOW_CHAT_HISTORY, onChatHistoryEvent);
|
||||||
|
useUiEvent(ChatHistoryEvent.TOGGLE_CHAT_HISTORY, onChatHistoryEvent);
|
||||||
|
useUiEvent(ChatHistoryEvent.CHAT_HISTORY_CHANGED, onChatHistoryEvent);
|
||||||
|
|
||||||
|
const cache = useMemo(() =>
|
||||||
|
{
|
||||||
|
return new CellMeasurerCache({
|
||||||
|
defaultHeight: 25,
|
||||||
|
fixedWidth: true,
|
||||||
|
//keyMapper: (index) => chatHistoryState.chats[index].id
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const RowRenderer: ListRowRenderer = (props: ListRowProps) =>
|
||||||
|
{
|
||||||
|
const item = chatHistoryState.chats[props.index];
|
||||||
|
|
||||||
|
const isDark = (props.index % 2 === 0);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CellMeasurer
|
||||||
|
cache={cache}
|
||||||
|
columnIndex={0}
|
||||||
|
key={props.key}
|
||||||
|
parent={props.parent}
|
||||||
|
rowIndex={props.index}
|
||||||
|
>
|
||||||
|
<div key={props.key} style={props.style} className="row chathistory-entry justify-content-start">
|
||||||
|
{(item.type === ChatEntryType.TYPE_CHAT) &&
|
||||||
|
<div className={`col d-flex flex-wrap text-break text-wrap ${isDark ? 'dark' : 'light'}`}>
|
||||||
|
<div className="p-1">{item.timestamp}</div>
|
||||||
|
<div className="p-1 fw-bold cursor-pointer" dangerouslySetInnerHTML={ { __html: (item.name + ':') }} />
|
||||||
|
<div className="p-1 text-break text-wrap">{item.message}</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
{(item.type === ChatEntryType.TYPE_ROOM_INFO) &&
|
||||||
|
<div className={`col d-flex flex-wrap text-break text-wrap ${isDark ? 'dark' : 'light'}`}>
|
||||||
|
<div className="p-1">{item.timestamp}</div>
|
||||||
|
<div className="p-1 fw-bold cursor-pointer">{item.name}</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</CellMeasurer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onResize = useCallback((info: Size) =>
|
||||||
|
{
|
||||||
|
cache.clearAll();
|
||||||
|
}, [cache]);
|
||||||
|
|
||||||
|
const onRowsRendered = useCallback((info: RenderedRows) =>
|
||||||
|
{
|
||||||
|
if(elementRef && elementRef.current && isVisible && needsScroll)
|
||||||
|
{
|
||||||
|
console.log('stop ' + info.stopIndex);
|
||||||
|
//if(chatHistoryState.chats.length > 0) elementRef.current.measureAllRows();
|
||||||
|
elementRef.current.scrollToRow(chatHistoryState.chats.length);
|
||||||
|
console.log('scroll')
|
||||||
|
setNeedsScroll(false);
|
||||||
|
}
|
||||||
|
}, [chatHistoryState.chats.length, isVisible, needsScroll]);
|
||||||
|
|
||||||
|
useEffect(() =>
|
||||||
|
{
|
||||||
|
|
||||||
|
if(elementRef && elementRef.current && isVisible)
|
||||||
|
{
|
||||||
|
//if(chatHistoryState.chats.length > 0) elementRef.current.measureAllRows();
|
||||||
|
elementRef.current.scrollToRow(chatHistoryState.chats.length);
|
||||||
|
}
|
||||||
|
//console.log(chatHistoryState.chats.length);
|
||||||
|
|
||||||
|
setNeedsScroll(true);
|
||||||
|
}, [chatHistoryState.chats, isVisible, chatHistoryUpdateId]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ChatHistoryContextProvider value={ { chatHistoryState, roomHistoryState } }>
|
||||||
|
<ChatHistoryMessageHandler />
|
||||||
|
{isVisible &&
|
||||||
|
<NitroCardView uniqueKey="chat-history" className="nitro-chat-history" simple={ false } theme={'dark'} >
|
||||||
|
<NitroCardHeaderView headerText={ 'Chat History' } onCloseClick={ event => setIsVisible(false) } theme={'dark'}/>
|
||||||
|
<NitroCardContentView className="chat-history-content" theme={'dark'}>
|
||||||
|
<div className="row w-100 h-100 chat-history-container">
|
||||||
|
<AutoSizer defaultWidth={300} defaultHeight={200} onResize={onResize}>
|
||||||
|
{({ height, width }) =>
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
<List
|
||||||
|
ref={elementRef}
|
||||||
|
width={width}
|
||||||
|
height={height}
|
||||||
|
rowCount={chatHistoryState.chats.length}
|
||||||
|
rowHeight={cache.rowHeight}
|
||||||
|
className={'chat-history-list'}
|
||||||
|
rowRenderer={RowRenderer}
|
||||||
|
onRowsRendered={onRowsRendered}
|
||||||
|
deferredMeasurementCache={cache}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</AutoSizer>
|
||||||
|
</div>
|
||||||
|
</NitroCardContentView>
|
||||||
|
</NitroCardView>
|
||||||
|
}
|
||||||
|
</ChatHistoryContextProvider>
|
||||||
|
);
|
||||||
|
}
|
32
src/views/chat-history/common/ChatHistoryState.ts
Normal file
32
src/views/chat-history/common/ChatHistoryState.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import { IChatEntry, IChatHistoryState } from '../context/ChatHistoryContext.types';
|
||||||
|
|
||||||
|
export class ChatHistoryState implements IChatHistoryState
|
||||||
|
{
|
||||||
|
private _chats: IChatEntry[];
|
||||||
|
private _notifier: () => void;
|
||||||
|
|
||||||
|
constructor()
|
||||||
|
{
|
||||||
|
this._chats = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public get chats(): IChatEntry[]
|
||||||
|
{
|
||||||
|
return this._chats;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get notifier(): () => void
|
||||||
|
{
|
||||||
|
return this._notifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
public set notifier(notifier: () => void)
|
||||||
|
{
|
||||||
|
this._notifier = notifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
notify(): void
|
||||||
|
{
|
||||||
|
if(this._notifier) this._notifier();
|
||||||
|
}
|
||||||
|
}
|
7
src/views/chat-history/common/GetChatHistory.ts
Normal file
7
src/views/chat-history/common/GetChatHistory.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { IChatHistoryState } from '../context/ChatHistoryContext.types';
|
||||||
|
|
||||||
|
let GLOBAL_CHATS: IChatHistoryState = null;
|
||||||
|
|
||||||
|
export const SetChatHistory = (chatHistory: IChatHistoryState) => (GLOBAL_CHATS = chatHistory);
|
||||||
|
|
||||||
|
export const GetChatHistory = () => GLOBAL_CHATS;
|
32
src/views/chat-history/common/RoomHistoryState.ts
Normal file
32
src/views/chat-history/common/RoomHistoryState.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import { IRoomHistoryEntry, IRoomHistoryState } from '../context/ChatHistoryContext.types';
|
||||||
|
|
||||||
|
export class RoomHistoryState implements IRoomHistoryState
|
||||||
|
{
|
||||||
|
private _roomHistory: IRoomHistoryEntry[];
|
||||||
|
private _notifier: () => void;
|
||||||
|
|
||||||
|
constructor()
|
||||||
|
{
|
||||||
|
this._roomHistory = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public get roomHistory(): IRoomHistoryEntry[]
|
||||||
|
{
|
||||||
|
return this._roomHistory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get notifier(): () => void
|
||||||
|
{
|
||||||
|
return this._notifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
public set notifier(notifier: () => void)
|
||||||
|
{
|
||||||
|
this._notifier = notifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
notify(): void
|
||||||
|
{
|
||||||
|
if(this._notifier) this._notifier();
|
||||||
|
}
|
||||||
|
}
|
14
src/views/chat-history/context/ChatHistoryContext.tsx
Normal file
14
src/views/chat-history/context/ChatHistoryContext.tsx
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { createContext, FC, useContext } from 'react';
|
||||||
|
import { ChatHistoryContextProps, IChatHistoryContext } from './ChatHistoryContext.types';
|
||||||
|
|
||||||
|
const ChatHistoryContext = createContext<IChatHistoryContext>({
|
||||||
|
chatHistoryState: null,
|
||||||
|
roomHistoryState: null
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ChatHistoryContextProvider: FC<ChatHistoryContextProps> = props =>
|
||||||
|
{
|
||||||
|
return <ChatHistoryContext.Provider value={ props.value }>{ props.children }</ChatHistoryContext.Provider>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useChatHistoryContext = () => useContext(ChatHistoryContext);
|
50
src/views/chat-history/context/ChatHistoryContext.types.ts
Normal file
50
src/views/chat-history/context/ChatHistoryContext.types.ts
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import { ProviderProps } from 'react';
|
||||||
|
|
||||||
|
export interface IChatHistoryContext
|
||||||
|
{
|
||||||
|
chatHistoryState: IChatHistoryState;
|
||||||
|
roomHistoryState: IRoomHistoryState;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IChatHistoryState {
|
||||||
|
chats: IChatEntry[];
|
||||||
|
notifier: () => void
|
||||||
|
notify(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IRoomHistoryState {
|
||||||
|
roomHistory: IRoomHistoryEntry[];
|
||||||
|
notifier: () => void
|
||||||
|
notify(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IChatEntry {
|
||||||
|
id: number;
|
||||||
|
entityId: number;
|
||||||
|
name: string;
|
||||||
|
look?: string;
|
||||||
|
message?: string;
|
||||||
|
entityType?: number;
|
||||||
|
roomId: number;
|
||||||
|
timestamp: string;
|
||||||
|
type: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IRoomHistoryEntry {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ChatEntryType
|
||||||
|
{
|
||||||
|
public static TYPE_CHAT = 1;
|
||||||
|
public static TYPE_ROOM_INFO = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CHAT_HISTORY_MAX = 1000;
|
||||||
|
export const ROOM_HISTORY_MAX = 10;
|
||||||
|
|
||||||
|
export interface ChatHistoryContextProps extends ProviderProps<IChatHistoryContext>
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
5
src/views/chat-history/utils/Utilities.ts
Normal file
5
src/views/chat-history/utils/Utilities.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export const currentDate = () =>
|
||||||
|
{
|
||||||
|
const currentTime = new Date();
|
||||||
|
return `${currentTime.getHours().toString().padStart(2, '0')}:${currentTime.getMinutes().toString().padStart(2, '0')}`;
|
||||||
|
}
|
@ -1,12 +1,10 @@
|
|||||||
import { CallForHelpResultMessageEvent, FurnitureListItemParser, PetData } from '@nitrots/nitro-renderer';
|
import { CallForHelpResultMessageEvent } from '@nitrots/nitro-renderer';
|
||||||
import { FC, useCallback } from 'react';
|
import { FC, useCallback } from 'react';
|
||||||
import { LocalizeText } from '../../api';
|
import { LocalizeText } from '../../api';
|
||||||
import { CreateMessageHook } from '../../hooks/messages/message-event';
|
import { CreateMessageHook } from '../../hooks/messages/message-event';
|
||||||
import { NotificationAlertType } from '../notification-center/common/NotificationAlertType';
|
import { NotificationAlertType } from '../notification-center/common/NotificationAlertType';
|
||||||
import { NotificationUtilities } from '../notification-center/common/NotificationUtilities';
|
import { NotificationUtilities } from '../notification-center/common/NotificationUtilities';
|
||||||
import { GetCloseReasonKey } from './common/GetCloseReasonKey';
|
import { GetCloseReasonKey } from './common/GetCloseReasonKey';
|
||||||
let furniMsgFragments: Map<number, FurnitureListItemParser>[] = null;
|
|
||||||
let petMsgFragments: Map<number, PetData>[] = null;
|
|
||||||
|
|
||||||
export const HelpMessageHandler: FC<{}> = props =>
|
export const HelpMessageHandler: FC<{}> = props =>
|
||||||
{
|
{
|
||||||
|
10
src/views/help/HelpView.scss
Normal file
10
src/views/help/HelpView.scss
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
.nitro-help {
|
||||||
|
height: 430px;
|
||||||
|
width: 300px;
|
||||||
|
|
||||||
|
.index-image {
|
||||||
|
background: url('../../assets/images/help/help_index.png');
|
||||||
|
width: 126px;
|
||||||
|
height: 105px;
|
||||||
|
}
|
||||||
|
}
|
@ -1,11 +1,79 @@
|
|||||||
import { FC } from 'react';
|
import { FC, useCallback, useState } from 'react';
|
||||||
|
import { LocalizeText } from '../../api';
|
||||||
|
import { HelpEvent } from '../../events/help/HelpEvent';
|
||||||
|
import { HelpReportUserEvent } from '../../events/help/HelpReportUserEvent';
|
||||||
|
import { useUiEvent } from '../../hooks';
|
||||||
|
import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../layout';
|
||||||
|
import { HelpContextProvider } from './context/HelpContext';
|
||||||
|
import { IHelpReportState, initialReportState } from './context/HelpContext.types';
|
||||||
import { HelpMessageHandler } from './HelpMessageHandler';
|
import { HelpMessageHandler } from './HelpMessageHandler';
|
||||||
|
import { DescribeReportView } from './views/DescribeReportView';
|
||||||
|
import { HelpIndexView } from './views/HelpIndexView';
|
||||||
|
import { SelectReportedChatsView } from './views/SelectReportedChatsView';
|
||||||
|
import { SelectReportedUserView } from './views/SelectReportedUserView';
|
||||||
|
import { SelectTopicView } from './views/SelectTopicView';
|
||||||
|
|
||||||
export const HelpView: FC<{}> = props =>
|
export const HelpView: FC<{}> = props =>
|
||||||
{
|
{
|
||||||
|
const [isVisible, setIsVisible] = useState(false);
|
||||||
|
const [ helpReportState, setHelpReportState ] = useState<IHelpReportState>(initialReportState);
|
||||||
|
|
||||||
|
const onHelpEvent = useCallback((event: HelpEvent) =>
|
||||||
|
{
|
||||||
|
setHelpReportState(initialReportState);
|
||||||
|
|
||||||
|
switch(event.type)
|
||||||
|
{
|
||||||
|
case HelpEvent.SHOW_HELP_CENTER:
|
||||||
|
setIsVisible(true);
|
||||||
|
break;
|
||||||
|
case HelpEvent.HIDE_HELP_CENTER:
|
||||||
|
setIsVisible(false);
|
||||||
|
break;
|
||||||
|
case HelpEvent.TOGGLE_HELP_CENTER:
|
||||||
|
setIsVisible(!isVisible);
|
||||||
|
break;
|
||||||
|
case HelpReportUserEvent.REPORT_USER:
|
||||||
|
const reportEvent = event as HelpReportUserEvent;
|
||||||
|
const reportState = Object.assign({}, helpReportState );
|
||||||
|
reportState.reportedUserId = reportEvent.reportedUserId;
|
||||||
|
reportState.currentStep = 2;
|
||||||
|
setHelpReportState(reportState);
|
||||||
|
setIsVisible(true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}, [helpReportState, isVisible]);
|
||||||
|
|
||||||
|
useUiEvent(HelpEvent.TOGGLE_HELP_CENTER, onHelpEvent);
|
||||||
|
useUiEvent(HelpEvent.SHOW_HELP_CENTER, onHelpEvent);
|
||||||
|
useUiEvent(HelpEvent.HIDE_HELP_CENTER, onHelpEvent);
|
||||||
|
useUiEvent(HelpReportUserEvent.REPORT_USER, onHelpEvent);
|
||||||
|
|
||||||
|
const CurrentStepView = useCallback(() =>
|
||||||
|
{
|
||||||
|
switch(helpReportState.currentStep)
|
||||||
|
{
|
||||||
|
case 0: return <HelpIndexView />
|
||||||
|
case 1: return <SelectReportedUserView />
|
||||||
|
case 2: return <SelectReportedChatsView />
|
||||||
|
case 3: return <SelectTopicView />
|
||||||
|
case 4: return <DescribeReportView />
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}, [helpReportState.currentStep]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<HelpContextProvider value={ { helpReportState, setHelpReportState } }>
|
||||||
<HelpMessageHandler />
|
<HelpMessageHandler />
|
||||||
</>
|
{isVisible &&
|
||||||
|
<NitroCardView className="nitro-help">
|
||||||
|
<NitroCardHeaderView headerText={LocalizeText('help.button.cfh')} onCloseClick={() => setIsVisible(false)} />
|
||||||
|
<NitroCardContentView className="text-black">
|
||||||
|
<CurrentStepView />
|
||||||
|
</NitroCardContentView>
|
||||||
|
</NitroCardView>
|
||||||
|
}
|
||||||
|
</HelpContextProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
5
src/views/help/common/IUser.ts
Normal file
5
src/views/help/common/IUser.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export interface IUser
|
||||||
|
{
|
||||||
|
id: number;
|
||||||
|
username: string;
|
||||||
|
}
|
14
src/views/help/context/HelpContext.tsx
Normal file
14
src/views/help/context/HelpContext.tsx
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { createContext, FC, useContext } from 'react';
|
||||||
|
import { HelpContextProps, IHelpContext } from './HelpContext.types';
|
||||||
|
|
||||||
|
const HelpContext = createContext<IHelpContext>({
|
||||||
|
helpReportState: null,
|
||||||
|
setHelpReportState: null
|
||||||
|
});
|
||||||
|
|
||||||
|
export const HelpContextProvider: FC<HelpContextProps> = props =>
|
||||||
|
{
|
||||||
|
return <HelpContext.Provider value={ props.value }>{ props.children }</HelpContext.Provider>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useHelpContext = () => useContext(HelpContext);
|
33
src/views/help/context/HelpContext.types.ts
Normal file
33
src/views/help/context/HelpContext.types.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import { ProviderProps } from 'react';
|
||||||
|
import { IChatEntry } from '../../chat-history/context/ChatHistoryContext.types';
|
||||||
|
|
||||||
|
export interface IHelpContext
|
||||||
|
{
|
||||||
|
helpReportState: IHelpReportState;
|
||||||
|
setHelpReportState: React.Dispatch<React.SetStateAction<IHelpReportState>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IHelpReportState {
|
||||||
|
reportedUserId: number;
|
||||||
|
reportedChats: IChatEntry[];
|
||||||
|
cfhCategory: number;
|
||||||
|
cfhTopic: number;
|
||||||
|
roomId: number;
|
||||||
|
message: string;
|
||||||
|
currentStep: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const initialReportState: IHelpReportState = {
|
||||||
|
reportedUserId: -1,
|
||||||
|
reportedChats: [],
|
||||||
|
cfhCategory: -1,
|
||||||
|
cfhTopic: -1,
|
||||||
|
roomId: -1,
|
||||||
|
message: '',
|
||||||
|
currentStep: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HelpContextProps extends ProviderProps<IHelpContext>
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
50
src/views/help/views/DescribeReportView.tsx
Normal file
50
src/views/help/views/DescribeReportView.tsx
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import { CallForHelpMessageComposer } from '@nitrots/nitro-renderer';
|
||||||
|
import { FC, useCallback, useState } from 'react';
|
||||||
|
import { LocalizeText } from '../../../api';
|
||||||
|
import { HelpEvent } from '../../../events/help/HelpEvent';
|
||||||
|
import { dispatchUiEvent, SendMessageHook } from '../../../hooks';
|
||||||
|
import { useHelpContext } from '../context/HelpContext';
|
||||||
|
|
||||||
|
export const DescribeReportView: FC<{}> = props =>
|
||||||
|
{
|
||||||
|
const { helpReportState = null, setHelpReportState = null } = useHelpContext();
|
||||||
|
const [message, setMessage] = useState('');
|
||||||
|
|
||||||
|
const submitReport = useCallback(() =>
|
||||||
|
{
|
||||||
|
if(message.length < 15) return;
|
||||||
|
|
||||||
|
const reportState = Object.assign({}, helpReportState);
|
||||||
|
|
||||||
|
reportState.message = message;
|
||||||
|
|
||||||
|
setHelpReportState(reportState);
|
||||||
|
|
||||||
|
const roomId = reportState.reportedChats[0].roomId;
|
||||||
|
const chats: (string | number )[] = [];
|
||||||
|
reportState.reportedChats.forEach(entry =>
|
||||||
|
{
|
||||||
|
chats.push(entry.entityId);
|
||||||
|
chats.push(entry.message);
|
||||||
|
});
|
||||||
|
|
||||||
|
SendMessageHook(new CallForHelpMessageComposer(message, reportState.cfhTopic, reportState.reportedUserId, roomId, chats));
|
||||||
|
|
||||||
|
dispatchUiEvent(new HelpEvent(HelpEvent.HIDE_HELP_CENTER));
|
||||||
|
}, [helpReportState, message, setHelpReportState]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="d-grid col-12 mx-auto justify-content-center">
|
||||||
|
<div className="col-12"><h3 className="fw-bold">{LocalizeText('help.emergency.chat_report.subtitle')}</h3></div>
|
||||||
|
<div className="text-wrap">{LocalizeText('help.cfh.input.text')}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="form-group mb-2">
|
||||||
|
<textarea className="form-control" value={message} onChange={event => setMessage(event.target.value)} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button className="btn btn-danger mt-2" type="button" disabled={message.length < 15} onClick={submitReport}>{LocalizeText('help.bully.submit')}</button>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
36
src/views/help/views/HelpIndexView.tsx
Normal file
36
src/views/help/views/HelpIndexView.tsx
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import { FC, useCallback } from 'react';
|
||||||
|
import { LocalizeText } from '../../../api';
|
||||||
|
import { useHelpContext } from '../context/HelpContext';
|
||||||
|
|
||||||
|
export const HelpIndexView: FC<{}> = props =>
|
||||||
|
{
|
||||||
|
const { helpReportState = null, setHelpReportState = null } = useHelpContext();
|
||||||
|
|
||||||
|
const onReportClick = useCallback(() =>
|
||||||
|
{
|
||||||
|
const reportState = Object.assign({}, helpReportState );
|
||||||
|
reportState.currentStep = 1;
|
||||||
|
setHelpReportState(reportState);
|
||||||
|
},[helpReportState, setHelpReportState]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="d-grid col-12 mx-auto justify-content-center">
|
||||||
|
<div className="col-12"><h3>{LocalizeText('help.main.frame.title')}</h3></div>
|
||||||
|
<div className="index-image align-self-center"></div>
|
||||||
|
<p className="text-center">{LocalizeText('help.main.self.description')}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="d-grid gap-2 col-8 mx-auto">
|
||||||
|
<button className="btn btn-primary" type="button" onClick={onReportClick}>{LocalizeText('help.main.bully.subtitle')}</button>
|
||||||
|
<button className="btn btn-primary" type="button">{LocalizeText('help.main.help.title')}</button>
|
||||||
|
<button className="btn btn-primary" type="button">{LocalizeText('help.main.self.tips.title')}</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="d-grid gap-2 col-8 mx-auto">
|
||||||
|
<button className="btn btn-link" type="button">{LocalizeText('help.main.my.sanction.status')}</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
88
src/views/help/views/SelectReportedChatsView.tsx
Normal file
88
src/views/help/views/SelectReportedChatsView.tsx
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
import { RoomObjectType } from '@nitrots/nitro-renderer';
|
||||||
|
import { FC, useCallback, useMemo, useState } from 'react';
|
||||||
|
import { LocalizeText } from '../../../api';
|
||||||
|
import { NitroCardGridItemView, NitroCardGridView } from '../../../layout';
|
||||||
|
import { GetChatHistory } from '../../chat-history/common/GetChatHistory';
|
||||||
|
import { ChatEntryType, IChatEntry } from '../../chat-history/context/ChatHistoryContext.types';
|
||||||
|
import { useHelpContext } from '../context/HelpContext';
|
||||||
|
|
||||||
|
export const SelectReportedChatsView: FC<{}> = props =>
|
||||||
|
{
|
||||||
|
const { helpReportState = null, setHelpReportState = null } = useHelpContext();
|
||||||
|
const [ selectedChats, setSelectedChats ] = useState<Map<number, IChatEntry>>(new Map());
|
||||||
|
|
||||||
|
const userChats = useMemo(() =>
|
||||||
|
{
|
||||||
|
return GetChatHistory().chats.filter(chat => (chat.type === ChatEntryType.TYPE_CHAT) && (chat.entityId === helpReportState.reportedUserId) && (chat.entityType === RoomObjectType.USER))
|
||||||
|
}, [helpReportState.reportedUserId]);
|
||||||
|
|
||||||
|
const selectChat = useCallback((chatEntry: IChatEntry) =>
|
||||||
|
{
|
||||||
|
const chats = new Map(selectedChats);
|
||||||
|
|
||||||
|
if(chats.has(chatEntry.id))
|
||||||
|
{
|
||||||
|
chats.delete(chatEntry.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
{
|
||||||
|
chats.set(chatEntry.id, chatEntry);
|
||||||
|
}
|
||||||
|
|
||||||
|
setSelectedChats(chats);
|
||||||
|
|
||||||
|
}, [selectedChats]);
|
||||||
|
|
||||||
|
const submitChats = useCallback(() =>
|
||||||
|
{
|
||||||
|
if(!selectedChats || selectedChats.size <= 0) return;
|
||||||
|
|
||||||
|
const reportState = Object.assign({}, helpReportState);
|
||||||
|
|
||||||
|
reportState.reportedChats = Array.from(selectedChats.values());
|
||||||
|
reportState.currentStep = 3;
|
||||||
|
setHelpReportState(reportState);
|
||||||
|
|
||||||
|
}, [helpReportState, selectedChats, setHelpReportState]);
|
||||||
|
|
||||||
|
const back = useCallback(() =>
|
||||||
|
{
|
||||||
|
const reportState = Object.assign({}, helpReportState);
|
||||||
|
reportState.currentStep = --reportState.currentStep;
|
||||||
|
setHelpReportState(reportState);
|
||||||
|
}, [helpReportState, setHelpReportState]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="d-grid col-12 mx-auto justify-content-center">
|
||||||
|
<div className="col-12"><h3 className="fw-bold">{LocalizeText('help.emergency.chat_report.subtitle')}</h3></div>
|
||||||
|
{ userChats.length > 0 &&
|
||||||
|
<div className="text-wrap">{LocalizeText('help.emergency.chat_report.description')}</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
{
|
||||||
|
(userChats.length === 0) && <div>{LocalizeText('help.cfh.error.no_user_data')}</div>
|
||||||
|
}
|
||||||
|
{ userChats.length > 0 &&
|
||||||
|
<>
|
||||||
|
<NitroCardGridView columns={1}>
|
||||||
|
{userChats.map((chat, index) =>
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
<NitroCardGridItemView key={chat.id} onClick={() => selectChat(chat)} itemActive={selectedChats.has(chat.id)}>
|
||||||
|
<span>{chat.message}</span>
|
||||||
|
</NitroCardGridItemView>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</NitroCardGridView>
|
||||||
|
|
||||||
|
<div className="d-flex gap-2 justify-content-between mt-auto">
|
||||||
|
<button className="btn btn-secondary mt-2" type="button" onClick={back}>{LocalizeText('generic.back')}</button>
|
||||||
|
<button className="btn btn-primary mt-2" type="button" disabled={selectedChats.size <= 0} onClick={submitChats}>{LocalizeText('help.emergency.main.submit.button')}</button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
88
src/views/help/views/SelectReportedUserView.tsx
Normal file
88
src/views/help/views/SelectReportedUserView.tsx
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
import { RoomObjectType } from '@nitrots/nitro-renderer';
|
||||||
|
import { FC, useCallback, useMemo, useState } from 'react';
|
||||||
|
import { GetSessionDataManager, LocalizeText } from '../../../api';
|
||||||
|
import { NitroCardGridItemView, NitroCardGridView } from '../../../layout';
|
||||||
|
import { GetChatHistory } from '../../chat-history/common/GetChatHistory';
|
||||||
|
import { ChatEntryType } from '../../chat-history/context/ChatHistoryContext.types';
|
||||||
|
import { IUser } from '../common/IUser';
|
||||||
|
import { useHelpContext } from '../context/HelpContext';
|
||||||
|
|
||||||
|
export const SelectReportedUserView: FC<{}> = props =>
|
||||||
|
{
|
||||||
|
const { helpReportState = null, setHelpReportState = null } = useHelpContext();
|
||||||
|
const [selectedUserId, setSelectedUserId] = useState(-1);
|
||||||
|
|
||||||
|
const availableUsers = useMemo(() =>
|
||||||
|
{
|
||||||
|
const users: Map<number, IUser> = new Map();
|
||||||
|
|
||||||
|
GetChatHistory().chats
|
||||||
|
.forEach(chat =>
|
||||||
|
{
|
||||||
|
if((chat.type === ChatEntryType.TYPE_CHAT) && (chat.entityType === RoomObjectType.USER) && (chat.entityId !== GetSessionDataManager().userId))
|
||||||
|
{
|
||||||
|
if(!users.has(chat.entityId))
|
||||||
|
{
|
||||||
|
users.set(chat.entityId, { id: chat.entityId, username: chat.name })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return Array.from(users.values());
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const submitUser = useCallback(() =>
|
||||||
|
{
|
||||||
|
if(selectedUserId <= 0) return;
|
||||||
|
|
||||||
|
const reportState = Object.assign({}, helpReportState);
|
||||||
|
reportState.reportedUserId = selectedUserId;
|
||||||
|
reportState.currentStep = 2;
|
||||||
|
setHelpReportState(reportState);
|
||||||
|
}, [helpReportState, selectedUserId, setHelpReportState]);
|
||||||
|
|
||||||
|
const selectUser = useCallback((userId: number) =>
|
||||||
|
{
|
||||||
|
if(selectedUserId === userId) setSelectedUserId(-1);
|
||||||
|
else setSelectedUserId(userId);
|
||||||
|
}, [selectedUserId]);
|
||||||
|
|
||||||
|
const back = useCallback(() =>
|
||||||
|
{
|
||||||
|
const reportState = Object.assign({}, helpReportState);
|
||||||
|
reportState.currentStep = --reportState.currentStep;
|
||||||
|
setHelpReportState(reportState);
|
||||||
|
}, [helpReportState, setHelpReportState]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="d-grid col-12 mx-auto justify-content-center">
|
||||||
|
<h3 className="fw-bold">{LocalizeText('help.emergency.main.step.two.title')}</h3>
|
||||||
|
<p>{(availableUsers.length > 0) ? LocalizeText('report.user.pick.user') : ''}</p>
|
||||||
|
</div>
|
||||||
|
{
|
||||||
|
(availableUsers.length <= 0) && <div>{LocalizeText('report.user.error.nolist')}</div>
|
||||||
|
}
|
||||||
|
{
|
||||||
|
(availableUsers.length > 0) &&
|
||||||
|
<>
|
||||||
|
<NitroCardGridView columns={1}>
|
||||||
|
{availableUsers.map((user, index) =>
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
<NitroCardGridItemView key={user.id} onClick={() => selectUser(user.id)} itemActive={(selectedUserId === user.id)}>
|
||||||
|
<span dangerouslySetInnerHTML={{ __html: (user.username) }} />
|
||||||
|
</NitroCardGridItemView>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</NitroCardGridView>
|
||||||
|
|
||||||
|
<div className="d-flex gap-2 justify-content-between mt-auto">
|
||||||
|
<button className="btn btn-secondary mt-2" type="button" onClick={back}>{LocalizeText('generic.back')}</button>
|
||||||
|
<button className="btn btn-primary mt-2" type="button" disabled={selectedUserId <= 0} onClick={submitUser}>{LocalizeText('help.emergency.main.submit.button')}</button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
64
src/views/help/views/SelectTopicView.tsx
Normal file
64
src/views/help/views/SelectTopicView.tsx
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import { FC, useCallback, useMemo, useState } from 'react';
|
||||||
|
import { LocalizeText } from '../../../api';
|
||||||
|
import { GetCfhCategories } from '../../mod-tools/common/GetCFHCategories';
|
||||||
|
import { useHelpContext } from '../context/HelpContext';
|
||||||
|
|
||||||
|
export const SelectTopicView: FC<{}> = props =>
|
||||||
|
{
|
||||||
|
const { helpReportState = null, setHelpReportState = null } = useHelpContext();
|
||||||
|
const [selectedCategory, setSelectedCategory] = useState(-1);
|
||||||
|
const [selectedTopic, setSelectedTopic] = useState(-1);
|
||||||
|
|
||||||
|
const cfhCategories = useMemo(() =>
|
||||||
|
{
|
||||||
|
return GetCfhCategories();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const submitTopic = useCallback(() =>
|
||||||
|
{
|
||||||
|
if(selectedCategory < 0) return;
|
||||||
|
if(selectedTopic < 0) return;
|
||||||
|
|
||||||
|
const reportState = Object.assign({}, helpReportState);
|
||||||
|
reportState.cfhCategory = selectedCategory;
|
||||||
|
reportState.cfhTopic = cfhCategories[selectedCategory].topics[selectedTopic].id;
|
||||||
|
reportState.currentStep = 4;
|
||||||
|
setHelpReportState(reportState);
|
||||||
|
}, [cfhCategories, helpReportState, selectedCategory, selectedTopic, setHelpReportState]);
|
||||||
|
|
||||||
|
const back = useCallback(() =>
|
||||||
|
{
|
||||||
|
const reportState = Object.assign({}, helpReportState);
|
||||||
|
reportState.currentStep = --reportState.currentStep;
|
||||||
|
setHelpReportState(reportState);
|
||||||
|
}, [helpReportState, setHelpReportState]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="d-grid col-12 mx-auto justify-content-center">
|
||||||
|
<div className="col-12"><h3 className="fw-bold">{LocalizeText('help.emergency.chat_report.subtitle')}</h3></div>
|
||||||
|
<div className="text-wrap">{LocalizeText('help.cfh.pick.topic')}</div>
|
||||||
|
</div>
|
||||||
|
<div className="d-grid gap-2 col-8 mx-auto">
|
||||||
|
{(selectedCategory < 0) &&
|
||||||
|
cfhCategories.map((category, index) =>
|
||||||
|
{
|
||||||
|
return <button key={index} className="btn btn-danger" type="button" onClick={() => setSelectedCategory(index)}>{LocalizeText(`help.cfh.reason.${category.name}`)}</button>
|
||||||
|
})
|
||||||
|
}
|
||||||
|
{(selectedCategory >= 0) &&
|
||||||
|
cfhCategories[selectedCategory].topics.map((topic, index) =>
|
||||||
|
{
|
||||||
|
return <button key={index} className="btn btn-danger" type="button" onClick={() => setSelectedTopic(index)}>{LocalizeText('help.cfh.topic.' + topic.id)}</button>
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="d-flex gap-2 justify-content-between mt-auto">
|
||||||
|
<button className="btn btn-secondary mt-2" type="button" onClick={back}>{LocalizeText('generic.back')}</button>
|
||||||
|
<button className="btn btn-primary mt-2" type="button" disabled={selectedTopic < 0} onClick={submitTopic}>{LocalizeText('help.emergency.main.submit.button')}</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
@ -7,6 +7,7 @@ import { AchievementsView } from '../achievements/AchievementsView';
|
|||||||
import { AvatarEditorView } from '../avatar-editor/AvatarEditorView';
|
import { AvatarEditorView } from '../avatar-editor/AvatarEditorView';
|
||||||
import { CameraWidgetView } from '../camera/CameraWidgetView';
|
import { CameraWidgetView } from '../camera/CameraWidgetView';
|
||||||
import { CatalogView } from '../catalog/CatalogView';
|
import { CatalogView } from '../catalog/CatalogView';
|
||||||
|
import { ChatHistoryView } from '../chat-history/ChatHistoryView';
|
||||||
import { FriendsView } from '../friends/FriendsView';
|
import { FriendsView } from '../friends/FriendsView';
|
||||||
import { GroupsView } from '../groups/GroupsView';
|
import { GroupsView } from '../groups/GroupsView';
|
||||||
import { HelpView } from '../help/HelpView';
|
import { HelpView } from '../help/HelpView';
|
||||||
@ -58,6 +59,7 @@ export const MainView: FC<MainViewProps> = props =>
|
|||||||
<ToolbarView isInRoom={ !landingViewVisible } />
|
<ToolbarView isInRoom={ !landingViewVisible } />
|
||||||
<ModToolsView />
|
<ModToolsView />
|
||||||
<RoomHostView />
|
<RoomHostView />
|
||||||
|
<ChatHistoryView />
|
||||||
<WiredView />
|
<WiredView />
|
||||||
<AvatarEditorView />
|
<AvatarEditorView />
|
||||||
<AchievementsView />
|
<AchievementsView />
|
||||||
|
267
src/views/mod-tools/ModToolsMessageHandler.tsx
Normal file
267
src/views/mod-tools/ModToolsMessageHandler.tsx
Normal file
@ -0,0 +1,267 @@
|
|||||||
|
import { CfhSanctionMessageEvent, CfhTopicsInitEvent, IssueDeletedMessageEvent, IssueInfoMessageEvent, IssuePickFailedMessageEvent, ModeratorActionResultMessageEvent, ModeratorInitMessageEvent, ModeratorToolPreferencesEvent, RoomEngineEvent } from '@nitrots/nitro-renderer';
|
||||||
|
import { FC, useCallback } from 'react';
|
||||||
|
import { NotificationAlertEvent } from '../../events';
|
||||||
|
import { ModToolsEvent } from '../../events/mod-tools/ModToolsEvent';
|
||||||
|
import { ModToolsOpenRoomChatlogEvent } from '../../events/mod-tools/ModToolsOpenRoomChatlogEvent';
|
||||||
|
import { ModToolsOpenRoomInfoEvent } from '../../events/mod-tools/ModToolsOpenRoomInfoEvent';
|
||||||
|
import { ModToolsOpenUserChatlogEvent } from '../../events/mod-tools/ModToolsOpenUserChatlogEvent';
|
||||||
|
import { ModToolsOpenUserInfoEvent } from '../../events/mod-tools/ModToolsOpenUserInfoEvent';
|
||||||
|
import { CreateMessageHook, dispatchUiEvent, useRoomEngineEvent, useUiEvent } from '../../hooks';
|
||||||
|
import { SetCfhCategories } from './common/GetCFHCategories';
|
||||||
|
import { useModToolsContext } from './context/ModToolsContext';
|
||||||
|
import { ModToolsActions } from './reducers/ModToolsReducer';
|
||||||
|
|
||||||
|
export const ModToolsMessageHandler: FC<{}> = props =>
|
||||||
|
{
|
||||||
|
const { modToolsState = null, dispatchModToolsState = null } = useModToolsContext();
|
||||||
|
const { openRooms = null, openRoomChatlogs = null, openUserChatlogs = null, openUserInfo = null, tickets= null } = modToolsState;
|
||||||
|
|
||||||
|
const onModeratorInitMessageEvent = useCallback((event: ModeratorInitMessageEvent) =>
|
||||||
|
{
|
||||||
|
const parser = event.getParser();
|
||||||
|
|
||||||
|
if(!parser) return;
|
||||||
|
|
||||||
|
const data = parser.data;
|
||||||
|
|
||||||
|
dispatchModToolsState({
|
||||||
|
type: ModToolsActions.SET_INIT_DATA,
|
||||||
|
payload: {
|
||||||
|
settings: data
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
dispatchModToolsState({
|
||||||
|
type: ModToolsActions.SET_TICKETS,
|
||||||
|
payload: {
|
||||||
|
tickets: data.issues
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(parser);
|
||||||
|
}, [dispatchModToolsState]);
|
||||||
|
|
||||||
|
const onIssueInfoMessageEvent = useCallback((event: IssueInfoMessageEvent) =>
|
||||||
|
{
|
||||||
|
const parser = event.getParser();
|
||||||
|
|
||||||
|
if(!parser) return;
|
||||||
|
|
||||||
|
const newTickets = tickets ? Array.from(tickets) : [];
|
||||||
|
const existingIndex = newTickets.findIndex( entry => entry.issueId === parser.issueData.issueId)
|
||||||
|
|
||||||
|
if(existingIndex > -1)
|
||||||
|
{
|
||||||
|
newTickets[existingIndex] = parser.issueData;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
newTickets.push(parser.issueData);
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatchModToolsState({
|
||||||
|
type: ModToolsActions.SET_TICKETS,
|
||||||
|
payload: {
|
||||||
|
tickets: newTickets
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
//todo: play ticket sound
|
||||||
|
//GetNitroInstance().events.dispatchEvent(new NitroSoundEvent(NitroSoundEvent.PLAY_SOUND, sound)
|
||||||
|
console.log(parser);
|
||||||
|
}, [dispatchModToolsState, tickets]);
|
||||||
|
|
||||||
|
const onModeratorToolPreferencesEvent = useCallback((event: ModeratorToolPreferencesEvent) =>
|
||||||
|
{
|
||||||
|
const parser = event.getParser();
|
||||||
|
|
||||||
|
if(!parser) return;
|
||||||
|
|
||||||
|
console.log(parser);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onIssuePickFailedMessageEvent = useCallback((event: IssuePickFailedMessageEvent) =>
|
||||||
|
{
|
||||||
|
const parser = event.getParser();
|
||||||
|
|
||||||
|
if(!parser) return;
|
||||||
|
|
||||||
|
// todo: let user know it failed
|
||||||
|
dispatchUiEvent(new NotificationAlertEvent(['Failed to pick issue'], null, null, null, 'Error', null));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onIssueDeletedMessageEvent = useCallback((event: IssueDeletedMessageEvent) =>
|
||||||
|
{
|
||||||
|
const parser = event.getParser();
|
||||||
|
|
||||||
|
if(!parser) return;
|
||||||
|
|
||||||
|
const newTickets = tickets ? Array.from(tickets) : [];
|
||||||
|
const existingIndex = newTickets.findIndex( entry => entry.issueId === parser.issueId);
|
||||||
|
|
||||||
|
if(existingIndex === -1) return;
|
||||||
|
|
||||||
|
newTickets.splice(existingIndex, 1);
|
||||||
|
|
||||||
|
dispatchModToolsState({
|
||||||
|
type: ModToolsActions.SET_TICKETS,
|
||||||
|
payload: {
|
||||||
|
tickets: newTickets
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, [dispatchModToolsState, tickets]);
|
||||||
|
|
||||||
|
const onModeratorActionResultMessageEvent = useCallback((event: ModeratorActionResultMessageEvent) =>
|
||||||
|
{
|
||||||
|
const parser = event.getParser();
|
||||||
|
|
||||||
|
if(!parser) return;
|
||||||
|
|
||||||
|
if(parser.success)
|
||||||
|
{
|
||||||
|
dispatchUiEvent(new NotificationAlertEvent(['Moderation action was successfull'], null, null, null, 'Success', null));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
dispatchUiEvent(new NotificationAlertEvent(['There was a problem applying that moderation action'], null, null, null, 'Error', null));
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onCfhTopicsInitEvent = useCallback((event: CfhTopicsInitEvent) =>
|
||||||
|
{
|
||||||
|
const parser = event.getParser();
|
||||||
|
|
||||||
|
if(!parser) return;
|
||||||
|
|
||||||
|
const categories = parser.callForHelpCategories;
|
||||||
|
|
||||||
|
dispatchModToolsState({
|
||||||
|
type: ModToolsActions.SET_CFH_CATEGORIES,
|
||||||
|
payload: {
|
||||||
|
cfhCategories: categories
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
SetCfhCategories(categories);
|
||||||
|
|
||||||
|
console.log(parser);
|
||||||
|
}, [dispatchModToolsState]);
|
||||||
|
|
||||||
|
const onCfhSanctionMessageEvent = useCallback((event: CfhSanctionMessageEvent) =>
|
||||||
|
{
|
||||||
|
const parser = event.getParser();
|
||||||
|
|
||||||
|
if(!parser) return;
|
||||||
|
|
||||||
|
console.log(parser);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
CreateMessageHook(ModeratorInitMessageEvent, onModeratorInitMessageEvent);
|
||||||
|
CreateMessageHook(IssueInfoMessageEvent, onIssueInfoMessageEvent);
|
||||||
|
CreateMessageHook(ModeratorToolPreferencesEvent, onModeratorToolPreferencesEvent);
|
||||||
|
CreateMessageHook(IssuePickFailedMessageEvent, onIssuePickFailedMessageEvent);
|
||||||
|
CreateMessageHook(IssueDeletedMessageEvent, onIssueDeletedMessageEvent);
|
||||||
|
CreateMessageHook(ModeratorActionResultMessageEvent, onModeratorActionResultMessageEvent);
|
||||||
|
CreateMessageHook(CfhTopicsInitEvent, onCfhTopicsInitEvent);
|
||||||
|
CreateMessageHook(CfhSanctionMessageEvent, onCfhSanctionMessageEvent);
|
||||||
|
|
||||||
|
const onRoomEngineEvent = useCallback((event: RoomEngineEvent) =>
|
||||||
|
{
|
||||||
|
switch(event.type)
|
||||||
|
{
|
||||||
|
case RoomEngineEvent.INITIALIZED:
|
||||||
|
dispatchModToolsState({
|
||||||
|
type: ModToolsActions.SET_CURRENT_ROOM_ID,
|
||||||
|
payload: {
|
||||||
|
currentRoomId: event.roomId
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
case RoomEngineEvent.DISPOSED:
|
||||||
|
dispatchModToolsState({
|
||||||
|
type: ModToolsActions.SET_CURRENT_ROOM_ID,
|
||||||
|
payload: {
|
||||||
|
currentRoomId: null
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}, [ dispatchModToolsState ]);
|
||||||
|
|
||||||
|
useRoomEngineEvent(RoomEngineEvent.INITIALIZED, onRoomEngineEvent);
|
||||||
|
useRoomEngineEvent(RoomEngineEvent.DISPOSED, onRoomEngineEvent);
|
||||||
|
|
||||||
|
const onModToolsEvent = useCallback((event: ModToolsEvent) =>
|
||||||
|
{
|
||||||
|
switch(event.type)
|
||||||
|
{
|
||||||
|
case ModToolsEvent.OPEN_ROOM_INFO: {
|
||||||
|
const castedEvent = (event as ModToolsOpenRoomInfoEvent);
|
||||||
|
|
||||||
|
if(openRooms && openRooms.includes(castedEvent.roomId)) return;
|
||||||
|
|
||||||
|
const rooms = openRooms || [];
|
||||||
|
|
||||||
|
dispatchModToolsState({
|
||||||
|
type: ModToolsActions.SET_OPEN_ROOMS,
|
||||||
|
payload: {
|
||||||
|
openRooms: [...rooms, castedEvent.roomId]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case ModToolsEvent.OPEN_ROOM_CHATLOG: {
|
||||||
|
const castedEvent = (event as ModToolsOpenRoomChatlogEvent);
|
||||||
|
|
||||||
|
if(openRoomChatlogs && openRoomChatlogs.includes(castedEvent.roomId)) return;
|
||||||
|
|
||||||
|
const chatlogs = openRoomChatlogs || [];
|
||||||
|
|
||||||
|
dispatchModToolsState({
|
||||||
|
type: ModToolsActions.SET_OPEN_ROOM_CHATLOGS,
|
||||||
|
payload: {
|
||||||
|
openRoomChatlogs: [...chatlogs, castedEvent.roomId]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case ModToolsEvent.OPEN_USER_INFO: {
|
||||||
|
const castedEvent = (event as ModToolsOpenUserInfoEvent);
|
||||||
|
|
||||||
|
if(openUserInfo && openUserInfo.includes(castedEvent.userId)) return;
|
||||||
|
|
||||||
|
const userInfo = openUserInfo || [];
|
||||||
|
|
||||||
|
dispatchModToolsState({
|
||||||
|
type: ModToolsActions.SET_OPEN_USERINFO,
|
||||||
|
payload: {
|
||||||
|
openUserInfo: [...userInfo, castedEvent.userId]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case ModToolsEvent.OPEN_USER_CHATLOG: {
|
||||||
|
const castedEvent = (event as ModToolsOpenUserChatlogEvent);
|
||||||
|
|
||||||
|
if(openUserChatlogs && openUserChatlogs.includes(castedEvent.userId)) return;
|
||||||
|
|
||||||
|
const userChatlog = openUserChatlogs || [];
|
||||||
|
|
||||||
|
dispatchModToolsState({
|
||||||
|
type: ModToolsActions.SET_OPEN_USER_CHATLOGS,
|
||||||
|
payload: {
|
||||||
|
openUserChatlogs: [...userChatlog, castedEvent.userId]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [openRooms, dispatchModToolsState, openRoomChatlogs, openUserInfo, openUserChatlogs]);
|
||||||
|
|
||||||
|
useUiEvent(ModToolsEvent.OPEN_ROOM_INFO, onModToolsEvent);
|
||||||
|
useUiEvent(ModToolsEvent.OPEN_ROOM_CHATLOG, onModToolsEvent);
|
||||||
|
useUiEvent(ModToolsEvent.OPEN_USER_INFO, onModToolsEvent);
|
||||||
|
useUiEvent(ModToolsEvent.OPEN_USER_CHATLOG, onModToolsEvent);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
@ -2,5 +2,8 @@
|
|||||||
width: 200px;
|
width: 200px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@import './views/chatlog/ModToolsChatlogView';
|
@import './views/room/room-tools/ModToolsRoomView';
|
||||||
@import './views/room/ModToolsRoomView';
|
@import './views/chatlog/ChatlogView';
|
||||||
|
@import './views/user/user-info/ModToolsUserView';
|
||||||
|
@import './views/user/user-room-visits/ModToolsUserRoomVisitsView';
|
||||||
|
@import './views/tickets/ModToolsTicketView';
|
||||||
|
@ -1,26 +1,30 @@
|
|||||||
import { RoomEngineEvent } from '@nitrots/nitro-renderer';
|
import { RoomEngineObjectEvent, RoomObjectCategory } from '@nitrots/nitro-renderer';
|
||||||
import { FC, useCallback, useEffect, useReducer, useState } from 'react';
|
import { FC, useCallback, useReducer, useState } from 'react';
|
||||||
|
import { GetRoomSession } from '../../api';
|
||||||
import { ModToolsEvent } from '../../events/mod-tools/ModToolsEvent';
|
import { ModToolsEvent } from '../../events/mod-tools/ModToolsEvent';
|
||||||
|
import { ModToolsOpenRoomChatlogEvent } from '../../events/mod-tools/ModToolsOpenRoomChatlogEvent';
|
||||||
import { ModToolsOpenRoomInfoEvent } from '../../events/mod-tools/ModToolsOpenRoomInfoEvent';
|
import { ModToolsOpenRoomInfoEvent } from '../../events/mod-tools/ModToolsOpenRoomInfoEvent';
|
||||||
import { ModToolsSelectUserEvent } from '../../events/mod-tools/ModToolsSelectUserEvent';
|
import { ModToolsOpenUserInfoEvent } from '../../events/mod-tools/ModToolsOpenUserInfoEvent';
|
||||||
import { useRoomEngineEvent } from '../../hooks/events';
|
import { useRoomEngineEvent } from '../../hooks/events';
|
||||||
import { dispatchUiEvent, useUiEvent } from '../../hooks/events/ui/ui-event';
|
import { dispatchUiEvent, useUiEvent } from '../../hooks/events/ui/ui-event';
|
||||||
import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../layout';
|
import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../layout';
|
||||||
import { ModToolsContextProvider } from './context/ModToolsContext';
|
import { ModToolsContextProvider } from './context/ModToolsContext';
|
||||||
|
import { ModToolsMessageHandler } from './ModToolsMessageHandler';
|
||||||
import { ModToolsViewProps } from './ModToolsView.types';
|
import { ModToolsViewProps } from './ModToolsView.types';
|
||||||
import { initialModTools, ModToolsActions, ModToolsReducer } from './reducers/ModToolsReducer';
|
import { initialModTools, ModToolsActions, ModToolsReducer } from './reducers/ModToolsReducer';
|
||||||
import { ModToolsRoomView } from './views/room/ModToolsRoomView';
|
import { ISelectedUser } from './utils/ISelectedUser';
|
||||||
|
import { ModToolsChatlogView } from './views/room/room-chatlog/ModToolsChatlogView';
|
||||||
|
import { ModToolsRoomView } from './views/room/room-tools/ModToolsRoomView';
|
||||||
import { ModToolsTicketsView } from './views/tickets/ModToolsTicketsView';
|
import { ModToolsTicketsView } from './views/tickets/ModToolsTicketsView';
|
||||||
import { ModToolsUserView } from './views/user/ModToolsUserView';
|
import { ModToolsUserChatlogView } from './views/user/user-chatlog/ModToolsUserChatlogView';
|
||||||
|
import { ModToolsUserView } from './views/user/user-info/ModToolsUserView';
|
||||||
|
|
||||||
export const ModToolsView: FC<ModToolsViewProps> = props =>
|
export const ModToolsView: FC<ModToolsViewProps> = props =>
|
||||||
{
|
{
|
||||||
const [ isVisible, setIsVisible ] = useState(false);
|
const [ isVisible, setIsVisible ] = useState(false);
|
||||||
const [ modToolsState, dispatchModToolsState ] = useReducer(ModToolsReducer, initialModTools);
|
const [ modToolsState, dispatchModToolsState ] = useReducer(ModToolsReducer, initialModTools);
|
||||||
const { currentRoomId = null, selectedUser = null, openRooms = null, openChatlogs = null } = modToolsState;
|
const { currentRoomId = null, openRooms = null, openRoomChatlogs = null, openUserChatlogs = null, openUserInfo = null } = modToolsState;
|
||||||
|
const [ selectedUser, setSelectedUser] = useState<ISelectedUser>(null);
|
||||||
const [ isRoomVisible, setIsRoomVisible ] = useState(false);
|
|
||||||
const [ isUserVisible, setIsUserVisible ] = useState(false);
|
|
||||||
const [ isTicketsVisible, setIsTicketsVisible ] = useState(false);
|
const [ isTicketsVisible, setIsTicketsVisible ] = useState(false);
|
||||||
|
|
||||||
const onModToolsEvent = useCallback((event: ModToolsEvent) =>
|
const onModToolsEvent = useCallback((event: ModToolsEvent) =>
|
||||||
@ -36,67 +40,29 @@ export const ModToolsView: FC<ModToolsViewProps> = props =>
|
|||||||
case ModToolsEvent.TOGGLE_MOD_TOOLS:
|
case ModToolsEvent.TOGGLE_MOD_TOOLS:
|
||||||
setIsVisible(value => !value);
|
setIsVisible(value => !value);
|
||||||
return;
|
return;
|
||||||
case ModToolsEvent.SELECT_USER: {
|
|
||||||
const castedEvent = (event as ModToolsSelectUserEvent);
|
|
||||||
|
|
||||||
dispatchModToolsState({
|
|
||||||
type: ModToolsActions.SET_SELECTED_USER,
|
|
||||||
payload: {
|
|
||||||
selectedUser: {
|
|
||||||
webID: castedEvent.webID,
|
|
||||||
name: castedEvent.name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
case ModToolsEvent.OPEN_ROOM_INFO: {
|
|
||||||
const castedEvent = (event as ModToolsOpenRoomInfoEvent);
|
|
||||||
|
|
||||||
if(openRooms && openRooms.includes(castedEvent.roomId)) return;
|
|
||||||
|
|
||||||
dispatchModToolsState({
|
|
||||||
type: ModToolsActions.SET_OPEN_ROOMS,
|
|
||||||
payload: {
|
|
||||||
openRooms: [...openRooms, castedEvent.roomId]
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}, [ dispatchModToolsState, setIsVisible, openRooms ]);
|
}, []);
|
||||||
|
|
||||||
useUiEvent(ModToolsEvent.SHOW_MOD_TOOLS, onModToolsEvent);
|
useUiEvent(ModToolsEvent.SHOW_MOD_TOOLS, onModToolsEvent);
|
||||||
useUiEvent(ModToolsEvent.HIDE_MOD_TOOLS, onModToolsEvent);
|
useUiEvent(ModToolsEvent.HIDE_MOD_TOOLS, onModToolsEvent);
|
||||||
useUiEvent(ModToolsEvent.TOGGLE_MOD_TOOLS, onModToolsEvent);
|
useUiEvent(ModToolsEvent.TOGGLE_MOD_TOOLS, onModToolsEvent);
|
||||||
useUiEvent(ModToolsEvent.SELECT_USER, onModToolsEvent);
|
|
||||||
useUiEvent(ModToolsEvent.OPEN_ROOM_INFO, onModToolsEvent);
|
const onRoomEngineObjectEvent = useCallback((event: RoomEngineObjectEvent) =>
|
||||||
|
|
||||||
const onRoomEngineEvent = useCallback((event: RoomEngineEvent) =>
|
|
||||||
{
|
{
|
||||||
switch(event.type)
|
if(event.category !== RoomObjectCategory.UNIT) return;
|
||||||
{
|
|
||||||
case RoomEngineEvent.INITIALIZED:
|
|
||||||
dispatchModToolsState({
|
|
||||||
type: ModToolsActions.SET_CURRENT_ROOM_ID,
|
|
||||||
payload: {
|
|
||||||
currentRoomId: event.roomId
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
case RoomEngineEvent.DISPOSED:
|
|
||||||
dispatchModToolsState({
|
|
||||||
type: ModToolsActions.SET_CURRENT_ROOM_ID,
|
|
||||||
payload: {
|
|
||||||
currentRoomId: null
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}, [ dispatchModToolsState ]);
|
|
||||||
|
|
||||||
useRoomEngineEvent(RoomEngineEvent.INITIALIZED, onRoomEngineEvent);
|
const roomSession = GetRoomSession();
|
||||||
useRoomEngineEvent(RoomEngineEvent.DISPOSED, onRoomEngineEvent);
|
|
||||||
|
if(!roomSession) return;
|
||||||
|
|
||||||
|
const userData = roomSession.userDataManager.getUserDataByIndex(event.objectId);
|
||||||
|
|
||||||
|
if(!userData) return;
|
||||||
|
|
||||||
|
setSelectedUser({ userId: userData.webID, username: userData.name });
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useRoomEngineEvent(RoomEngineObjectEvent.SELECTED, onRoomEngineObjectEvent);
|
||||||
|
|
||||||
const handleClick = useCallback((action: string, value?: string) =>
|
const handleClick = useCallback((action: string, value?: string) =>
|
||||||
{
|
{
|
||||||
@ -111,9 +77,7 @@ export const ModToolsView: FC<ModToolsViewProps> = props =>
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const itemIndex = openRooms.indexOf(currentRoomId);
|
if(openRooms.indexOf(currentRoomId) > -1)
|
||||||
|
|
||||||
if(itemIndex > -1)
|
|
||||||
{
|
{
|
||||||
handleClick('close_room', currentRoomId.toString());
|
handleClick('close_room', currentRoomId.toString());
|
||||||
}
|
}
|
||||||
@ -137,46 +101,124 @@ export const ModToolsView: FC<ModToolsViewProps> = props =>
|
|||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
case 'close_chatlog': {
|
case 'toggle_room_chatlog': {
|
||||||
const itemIndex = openChatlogs.indexOf(Number(value));
|
if(!openRoomChatlogs)
|
||||||
|
{
|
||||||
|
dispatchUiEvent(new ModToolsOpenRoomChatlogEvent(currentRoomId));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const clone = Array.from(openChatlogs);
|
if(openRoomChatlogs.indexOf(currentRoomId) > -1)
|
||||||
|
{
|
||||||
|
handleClick('close_room_chatlog', currentRoomId.toString());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
dispatchUiEvent(new ModToolsOpenRoomChatlogEvent(currentRoomId));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case 'close_room_chatlog': {
|
||||||
|
const itemIndex = openRoomChatlogs.indexOf(Number(value));
|
||||||
|
|
||||||
|
const clone = Array.from(openRoomChatlogs);
|
||||||
clone.splice(itemIndex, 1);
|
clone.splice(itemIndex, 1);
|
||||||
|
|
||||||
dispatchModToolsState({
|
dispatchModToolsState({
|
||||||
type: ModToolsActions.SET_OPEN_CHATLOGS,
|
type: ModToolsActions.SET_OPEN_ROOM_CHATLOGS,
|
||||||
payload: {
|
payload: {
|
||||||
openChatlogs: clone
|
openRoomChatlogs: clone
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case 'toggle_user_info': {
|
||||||
|
|
||||||
|
if(!selectedUser) return;
|
||||||
|
|
||||||
|
const userId = selectedUser.userId;
|
||||||
|
|
||||||
|
if(!openUserInfo)
|
||||||
|
{
|
||||||
|
dispatchUiEvent(new ModToolsOpenUserInfoEvent(userId));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(openUserInfo.indexOf(userId) > -1)
|
||||||
|
{
|
||||||
|
handleClick('close_user_info', userId.toString());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
dispatchUiEvent(new ModToolsOpenUserInfoEvent(userId));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case 'close_user_info': {
|
||||||
|
const itemIndex = openUserInfo.indexOf(Number(value));
|
||||||
|
|
||||||
|
const clone = Array.from(openUserInfo);
|
||||||
|
clone.splice(itemIndex, 1);
|
||||||
|
|
||||||
|
dispatchModToolsState({
|
||||||
|
type: ModToolsActions.SET_OPEN_USERINFO,
|
||||||
|
payload: {
|
||||||
|
openUserInfo: clone
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case 'close_user_chatlog': {
|
||||||
|
const itemIndex = openUserChatlogs.indexOf(Number(value));
|
||||||
|
|
||||||
|
const clone = Array.from(openUserChatlogs);
|
||||||
|
clone.splice(itemIndex, 1);
|
||||||
|
|
||||||
|
dispatchModToolsState({
|
||||||
|
type: ModToolsActions.SET_OPEN_USER_CHATLOGS,
|
||||||
|
payload: {
|
||||||
|
openUserChatlogs: clone
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [ dispatchModToolsState, openRooms, openChatlogs, currentRoomId ]);
|
}, [openRooms, currentRoomId, openRoomChatlogs, selectedUser, openUserInfo, openUserChatlogs]);
|
||||||
|
|
||||||
useEffect(() =>
|
|
||||||
{
|
|
||||||
if(!isVisible) return;
|
|
||||||
}, [ isVisible ]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ModToolsContextProvider value={ { modToolsState, dispatchModToolsState } }>
|
<ModToolsContextProvider value={ { modToolsState, dispatchModToolsState } }>
|
||||||
|
<ModToolsMessageHandler />
|
||||||
{ isVisible &&
|
{ isVisible &&
|
||||||
<NitroCardView uniqueKey="mod-tools" className="nitro-mod-tools" simple={ false }>
|
<NitroCardView uniqueKey="mod-tools" className="nitro-mod-tools" simple={ false }>
|
||||||
<NitroCardHeaderView headerText={ 'Mod Tools' } onCloseClick={ event => setIsVisible(false) } />
|
<NitroCardHeaderView headerText={ 'Mod Tools' } onCloseClick={ event => setIsVisible(false) } />
|
||||||
<NitroCardContentView className="text-black">
|
<NitroCardContentView className="text-black">
|
||||||
<button className="btn btn-primary btn-sm w-100 mb-2" onClick={ () => handleClick('toggle_room') } disabled={ !currentRoomId }><i className="fas fa-home"></i> Room Tool</button>
|
<button className="btn btn-primary btn-sm w-100 mb-2" onClick={ () => handleClick('toggle_room') } disabled={ !currentRoomId }><i className="fas fa-home"></i> Room Tool</button>
|
||||||
<button className="btn btn-primary btn-sm w-100 mb-2" onClick={ () => {} } disabled={ !currentRoomId }><i className="fas fa-comments"></i> Chatlog Tool</button>
|
<button className="btn btn-primary btn-sm w-100 mb-2" onClick={ () => handleClick('toggle_room_chatlog') } disabled={ !currentRoomId }><i className="fas fa-comments"></i> Chatlog Tool</button>
|
||||||
<button className="btn btn-primary btn-sm w-100 mb-2" onClick={ () => setIsUserVisible(value => !value) } disabled={ !selectedUser }><i className="fas fa-user"></i> User: { selectedUser ? selectedUser.name : '' }</button>
|
<button className="btn btn-primary btn-sm w-100 mb-2" onClick={ () => handleClick('toggle_user_info') } disabled={ !selectedUser }><i className="fas fa-user"></i> User: { selectedUser ? selectedUser.username : '' }</button>
|
||||||
<button className="btn btn-primary btn-sm w-100" onClick={ () => setIsTicketsVisible(value => !value) }><i className="fas fa-exclamation-circle"></i> Report Tool</button>
|
<button className="btn btn-primary btn-sm w-100" onClick={ () => setIsTicketsVisible(value => !value) }><i className="fas fa-exclamation-circle"></i> Report Tool</button>
|
||||||
</NitroCardContentView>
|
</NitroCardContentView>
|
||||||
</NitroCardView> }
|
</NitroCardView> }
|
||||||
{ openRooms && openRooms.map(roomId =>
|
{ openRooms && openRooms.map(roomId =>
|
||||||
{
|
{
|
||||||
return <ModToolsRoomView key={ roomId } roomId={ roomId } onCloseClick={ () => handleClick('close_room', roomId.toString()) } />;
|
return <ModToolsRoomView key={ roomId } roomId={ roomId } onCloseClick={ () => handleClick('close_room', roomId.toString()) } />;
|
||||||
}) }
|
})
|
||||||
|
}
|
||||||
|
{ openRoomChatlogs && openRoomChatlogs.map(roomId =>
|
||||||
|
{
|
||||||
|
return <ModToolsChatlogView key={ roomId } roomId={ roomId } onCloseClick={ () => handleClick('close_room_chatlog', roomId.toString()) } />;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
{ openUserInfo && openUserInfo.map(userId =>
|
||||||
|
{
|
||||||
|
return <ModToolsUserView key={userId} userId={userId} onCloseClick={ () => handleClick('close_user_info', userId.toString())}/>
|
||||||
|
})
|
||||||
|
}
|
||||||
|
{ openUserChatlogs && openUserChatlogs.map(userId =>
|
||||||
|
{
|
||||||
|
return <ModToolsUserChatlogView key={userId} userId={userId} onCloseClick={ () => handleClick('close_user_chatlog', userId.toString())}/>
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
{ isUserVisible && <ModToolsUserView /> }
|
|
||||||
{ isTicketsVisible && <ModToolsTicketsView onCloseClick={ () => setIsTicketsVisible(false) } /> }
|
{ isTicketsVisible && <ModToolsTicketsView onCloseClick={ () => setIsTicketsVisible(false) } /> }
|
||||||
</ModToolsContextProvider>
|
</ModToolsContextProvider>
|
||||||
);
|
);
|
||||||
|
7
src/views/mod-tools/common/GetCFHCategories.ts
Normal file
7
src/views/mod-tools/common/GetCFHCategories.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { CallForHelpCategoryData } from '@nitrots/nitro-renderer';
|
||||||
|
|
||||||
|
let cfhCategories: CallForHelpCategoryData[] = [];
|
||||||
|
|
||||||
|
export const SetCfhCategories = (categories: CallForHelpCategoryData[]) => (cfhCategories = categories);
|
||||||
|
|
||||||
|
export const GetCfhCategories = () => cfhCategories;
|
35
src/views/mod-tools/common/IssueCategoryNames.ts
Normal file
35
src/views/mod-tools/common/IssueCategoryNames.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
export const getSourceName = (categoryId: number): string =>
|
||||||
|
{
|
||||||
|
switch(categoryId)
|
||||||
|
{
|
||||||
|
case 1:
|
||||||
|
case 2:
|
||||||
|
return 'Normal';
|
||||||
|
case 3:
|
||||||
|
return 'Automatic';
|
||||||
|
case 4:
|
||||||
|
return 'Automatic IM';
|
||||||
|
case 5:
|
||||||
|
return 'Guide System';
|
||||||
|
case 6:
|
||||||
|
return 'IM';
|
||||||
|
case 7:
|
||||||
|
return 'Room';
|
||||||
|
case 8:
|
||||||
|
return 'Panic';
|
||||||
|
case 9:
|
||||||
|
return 'Guardian';
|
||||||
|
case 10:
|
||||||
|
return 'Automatic Helper';
|
||||||
|
case 11:
|
||||||
|
return 'Discussion';
|
||||||
|
case 12:
|
||||||
|
return 'Selfie';
|
||||||
|
case 14:
|
||||||
|
return 'Photo';
|
||||||
|
case 15:
|
||||||
|
return 'Ambassador';
|
||||||
|
default:
|
||||||
|
return 'Unknown';
|
||||||
|
}
|
||||||
|
}
|
@ -1,48 +1,65 @@
|
|||||||
|
import { CallForHelpCategoryData, IssueMessageData, ModeratorInitData } from '@nitrots/nitro-renderer';
|
||||||
import { Reducer } from 'react';
|
import { Reducer } from 'react';
|
||||||
|
|
||||||
export interface IModToolsState
|
export interface IModToolsState
|
||||||
{
|
{
|
||||||
selectedUser: {webID: number, name: string};
|
settings: ModeratorInitData;
|
||||||
currentRoomId: number;
|
currentRoomId: number;
|
||||||
openRooms: number[];
|
openRooms: number[];
|
||||||
openChatlogs: number[];
|
openRoomChatlogs: number[];
|
||||||
|
openUserInfo: number[];
|
||||||
|
openUserChatlogs: number[];
|
||||||
|
tickets: IssueMessageData[]
|
||||||
|
cfhCategories: CallForHelpCategoryData[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IModToolsAction
|
export interface IModToolsAction
|
||||||
{
|
{
|
||||||
type: string;
|
type: string;
|
||||||
payload: {
|
payload: {
|
||||||
selectedUser?: {webID: number, name: string};
|
settings?: ModeratorInitData;
|
||||||
currentRoomId?: number;
|
currentRoomId?: number;
|
||||||
openRooms?: number[];
|
openRooms?: number[];
|
||||||
openChatlogs?: number[];
|
openRoomChatlogs?: number[];
|
||||||
|
openUserInfo?: number[];
|
||||||
|
openUserChatlogs?: number[];
|
||||||
|
tickets?: IssueMessageData[];
|
||||||
|
cfhCategories?: CallForHelpCategoryData[];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ModToolsActions
|
export class ModToolsActions
|
||||||
{
|
{
|
||||||
public static SET_SELECTED_USER: string = 'MTA_SET_SELECTED_USER';
|
public static SET_INIT_DATA: string = 'MTA_SET_INIT_DATA';
|
||||||
public static SET_CURRENT_ROOM_ID: string = 'MTA_SET_CURRENT_ROOM_ID';
|
public static SET_CURRENT_ROOM_ID: string = 'MTA_SET_CURRENT_ROOM_ID';
|
||||||
public static SET_OPEN_ROOMS: string = 'MTA_SET_OPEN_ROOMS';
|
public static SET_OPEN_ROOMS: string = 'MTA_SET_OPEN_ROOMS';
|
||||||
public static SET_OPEN_CHATLOGS: string = 'MTA_SET_OPEN_CHATLOGS';
|
public static SET_OPEN_USERINFO: string = 'MTA_SET_OPEN_USERINFO';
|
||||||
|
public static SET_OPEN_ROOM_CHATLOGS: string = 'MTA_SET_OPEN_CHATLOGS';
|
||||||
|
public static SET_OPEN_USER_CHATLOGS: string = 'MTA_SET_OPEN_USER_CHATLOGS';
|
||||||
|
public static SET_TICKETS: string = 'MTA_SET_TICKETS';
|
||||||
|
public static SET_CFH_CATEGORIES: string = 'MTA_SET_CFH_CATEGORIES';
|
||||||
public static RESET_STATE: string = 'MTA_RESET_STATE';
|
public static RESET_STATE: string = 'MTA_RESET_STATE';
|
||||||
}
|
}
|
||||||
|
|
||||||
export const initialModTools: IModToolsState = {
|
export const initialModTools: IModToolsState = {
|
||||||
selectedUser: null,
|
settings: null,
|
||||||
currentRoomId: null,
|
currentRoomId: null,
|
||||||
openRooms: null,
|
openRooms: null,
|
||||||
openChatlogs: null
|
openRoomChatlogs: null,
|
||||||
|
openUserChatlogs: null,
|
||||||
|
openUserInfo: null,
|
||||||
|
tickets: null,
|
||||||
|
cfhCategories: null
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ModToolsReducer: Reducer<IModToolsState, IModToolsAction> = (state, action) =>
|
export const ModToolsReducer: Reducer<IModToolsState, IModToolsAction> = (state, action) =>
|
||||||
{
|
{
|
||||||
switch(action.type)
|
switch(action.type)
|
||||||
{
|
{
|
||||||
case ModToolsActions.SET_SELECTED_USER: {
|
case ModToolsActions.SET_INIT_DATA: {
|
||||||
const selectedUser = (action.payload.selectedUser || state.selectedUser || null);
|
const settings = (action.payload.settings || state.settings || null);
|
||||||
|
|
||||||
return { ...state, selectedUser };
|
return { ...state, settings };
|
||||||
}
|
}
|
||||||
case ModToolsActions.SET_CURRENT_ROOM_ID: {
|
case ModToolsActions.SET_CURRENT_ROOM_ID: {
|
||||||
const currentRoomId = (action.payload.currentRoomId || state.currentRoomId || null);
|
const currentRoomId = (action.payload.currentRoomId || state.currentRoomId || null);
|
||||||
@ -54,6 +71,31 @@ export const ModToolsReducer: Reducer<IModToolsState, IModToolsAction> = (state,
|
|||||||
|
|
||||||
return { ...state, openRooms };
|
return { ...state, openRooms };
|
||||||
}
|
}
|
||||||
|
case ModToolsActions.SET_OPEN_USERINFO: {
|
||||||
|
const openUserInfo = (action.payload.openUserInfo || state.openUserInfo || null);
|
||||||
|
|
||||||
|
return { ...state, openUserInfo };
|
||||||
|
}
|
||||||
|
case ModToolsActions.SET_OPEN_ROOM_CHATLOGS: {
|
||||||
|
const openRoomChatlogs = (action.payload.openRoomChatlogs || state.openRoomChatlogs || null);
|
||||||
|
|
||||||
|
return { ...state, openRoomChatlogs };
|
||||||
|
}
|
||||||
|
case ModToolsActions.SET_OPEN_USER_CHATLOGS: {
|
||||||
|
const openUserChatlogs = (action.payload.openUserChatlogs || state.openUserChatlogs || null);
|
||||||
|
|
||||||
|
return { ...state, openUserChatlogs };
|
||||||
|
}
|
||||||
|
case ModToolsActions.SET_TICKETS: {
|
||||||
|
const tickets = (action.payload.tickets || state.tickets || null);
|
||||||
|
|
||||||
|
return { ...state, tickets };
|
||||||
|
}
|
||||||
|
case ModToolsActions.SET_CFH_CATEGORIES: {
|
||||||
|
const cfhCategories = (action.payload.cfhCategories || state.cfhCategories || null);
|
||||||
|
|
||||||
|
return { ...state, cfhCategories };
|
||||||
|
}
|
||||||
case ModToolsActions.RESET_STATE: {
|
case ModToolsActions.RESET_STATE: {
|
||||||
return { ...initialModTools };
|
return { ...initialModTools };
|
||||||
}
|
}
|
||||||
|
4
src/views/mod-tools/utils/ISelectedUser.ts
Normal file
4
src/views/mod-tools/utils/ISelectedUser.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export interface ISelectedUser {
|
||||||
|
userId: number;
|
||||||
|
username: string;
|
||||||
|
}
|
6
src/views/mod-tools/utils/IUserInfo.ts
Normal file
6
src/views/mod-tools/utils/IUserInfo.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export interface IUserInfo
|
||||||
|
{
|
||||||
|
nameKey: string;
|
||||||
|
nameKeyFallback: string;
|
||||||
|
value: string;
|
||||||
|
}
|
49
src/views/mod-tools/utils/ModActionDefinition.ts
Normal file
49
src/views/mod-tools/utils/ModActionDefinition.ts
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
export class ModActionDefinition
|
||||||
|
{
|
||||||
|
public static ALERT:number = 1;
|
||||||
|
public static MUTE:number = 2;
|
||||||
|
public static BAN:number = 3;
|
||||||
|
public static KICK:number = 4;
|
||||||
|
public static TRADE_LOCK:number = 5;
|
||||||
|
public static MESSAGE:number = 6;
|
||||||
|
|
||||||
|
private readonly _actionId:number;
|
||||||
|
private readonly _name:string;
|
||||||
|
private readonly _actionType:number;
|
||||||
|
private readonly _sanctionTypeId:number;
|
||||||
|
private readonly _actionLengthHours:number;
|
||||||
|
|
||||||
|
constructor(actionId:number, actionName:string, actionType:number, sanctionTypeId:number, actionLengthHours:number)
|
||||||
|
{
|
||||||
|
this._actionId = actionId;
|
||||||
|
this._name = actionName;
|
||||||
|
this._actionType = actionType;
|
||||||
|
this._sanctionTypeId = sanctionTypeId;
|
||||||
|
this._actionLengthHours = actionLengthHours;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get actionId():number
|
||||||
|
{
|
||||||
|
return this._actionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get name():string
|
||||||
|
{
|
||||||
|
return this._name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get actionType():number
|
||||||
|
{
|
||||||
|
return this._actionType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get sanctionTypeId():number
|
||||||
|
{
|
||||||
|
return this._sanctionTypeId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get actionLengthHours():number
|
||||||
|
{
|
||||||
|
return this._actionLengthHours;
|
||||||
|
}
|
||||||
|
}
|
40
src/views/mod-tools/views/chatlog/ChatlogView.scss
Normal file
40
src/views/mod-tools/views/chatlog/ChatlogView.scss
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
.chatlog-messages {
|
||||||
|
color: $black;
|
||||||
|
min-width: 400px;
|
||||||
|
|
||||||
|
$username-col-width: 100px;
|
||||||
|
|
||||||
|
.username-label {
|
||||||
|
width: $username-col-width;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chatlog {
|
||||||
|
min-height: 200px;
|
||||||
|
|
||||||
|
.chatlog-container {
|
||||||
|
color: $black;
|
||||||
|
|
||||||
|
div.chatlog-entry {
|
||||||
|
border-bottom: 1px solid rgba(0, 0, 0, 0.2);
|
||||||
|
.username {
|
||||||
|
color: #1E7295;
|
||||||
|
text-decoration: underline;
|
||||||
|
width: $username-col-width;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.highlighted {
|
||||||
|
border: 1px solid $red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message {
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.room-info {
|
||||||
|
border-bottom: 1px solid rgba(0, 0, 0, 0.2);
|
||||||
|
background: rgba(0, 0, 0, .05);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
150
src/views/mod-tools/views/chatlog/ChatlogView.tsx
Normal file
150
src/views/mod-tools/views/chatlog/ChatlogView.tsx
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
import { ChatlineData, ChatRecordData, UserProfileComposer } from '@nitrots/nitro-renderer';
|
||||||
|
import { FC, useCallback } from 'react';
|
||||||
|
import { AutoSizer, CellMeasurer, CellMeasurerCache, List, ListRowProps, ListRowRenderer } from 'react-virtualized';
|
||||||
|
import { TryVisitRoom } from '../../../../api';
|
||||||
|
import { ModToolsOpenRoomInfoEvent } from '../../../../events/mod-tools/ModToolsOpenRoomInfoEvent';
|
||||||
|
import { dispatchUiEvent, SendMessageHook } from '../../../../hooks';
|
||||||
|
import { ChatlogViewProps } from './ChatlogView.types';
|
||||||
|
|
||||||
|
export const ChatlogView: FC<ChatlogViewProps> = props =>
|
||||||
|
{
|
||||||
|
const { records = null } = props;
|
||||||
|
|
||||||
|
const simpleRowRenderer: ListRowRenderer = (props: ListRowProps) =>
|
||||||
|
{
|
||||||
|
const item = records[0].chatlog[props.index];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CellMeasurer
|
||||||
|
cache={cache}
|
||||||
|
columnIndex={0}
|
||||||
|
key={props.key}
|
||||||
|
parent={props.parent}
|
||||||
|
rowIndex={props.index}
|
||||||
|
>
|
||||||
|
<div key={props.key} style={props.style} className={'row chatlog-entry justify-content-start ' + (item.hasHighlighting ? 'highlighted' : '')}>
|
||||||
|
<div className="col-auto text-center">{item.timestamp}</div>
|
||||||
|
<div className="col-sm-2 justify-content-start username"><span className="fw-bold cursor-pointer" onClick={() => SendMessageHook(new UserProfileComposer(item.userId))}>{item.userName}</span></div>
|
||||||
|
<div className="col justify-content-start h-100"><span className="text-break text-wrap h-100">{item.message}</span></div>
|
||||||
|
</div>
|
||||||
|
</CellMeasurer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const advancedRowRenderer: ListRowRenderer = (props: ListRowProps) =>
|
||||||
|
{
|
||||||
|
let chatlogEntry: ChatlineData;
|
||||||
|
let currentRecord: ChatRecordData;
|
||||||
|
let isRoomInfo = false;
|
||||||
|
|
||||||
|
let totalIndex = 0;
|
||||||
|
for(let i = 0; i < records.length; i++)
|
||||||
|
{
|
||||||
|
currentRecord = records[i];
|
||||||
|
|
||||||
|
totalIndex++; // row for room info
|
||||||
|
totalIndex = totalIndex + currentRecord.chatlog.length;
|
||||||
|
|
||||||
|
if(props.index > (totalIndex - 1))
|
||||||
|
{
|
||||||
|
continue; // it is not in current one
|
||||||
|
}
|
||||||
|
|
||||||
|
if( (props.index + 1) === (totalIndex - currentRecord.chatlog.length))
|
||||||
|
{
|
||||||
|
isRoomInfo = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const index = props.index - (totalIndex - currentRecord.chatlog.length);
|
||||||
|
chatlogEntry = currentRecord.chatlog[index];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CellMeasurer
|
||||||
|
cache={cache}
|
||||||
|
columnIndex={0}
|
||||||
|
key={props.key}
|
||||||
|
parent={props.parent}
|
||||||
|
rowIndex={props.index}
|
||||||
|
>
|
||||||
|
{isRoomInfo && <RoomInfo roomId={currentRecord.roomId} roomName={currentRecord.roomName} uniqueKey={props.key} style={props.style}/>}
|
||||||
|
{!isRoomInfo &&
|
||||||
|
<div key={props.key} style={props.style} className="row chatlog-entry justify-content-start">
|
||||||
|
<div className="col-auto text-center">{chatlogEntry.timestamp}</div>
|
||||||
|
<div className="col-sm-2 justify-content-start username"><span className="fw-bold cursor-pointer" onClick={() => SendMessageHook(new UserProfileComposer(chatlogEntry.userId))}>{chatlogEntry.userName}</span></div>
|
||||||
|
<div className="col justify-content-start h-100"><span className="text-break text-wrap h-100">{chatlogEntry.message}</span></div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
</CellMeasurer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const getNumRowsForAdvanced = useCallback(() =>
|
||||||
|
{
|
||||||
|
let count = 0;
|
||||||
|
|
||||||
|
for(let i = 0; i < records.length; i++)
|
||||||
|
{
|
||||||
|
count++; // add room info row
|
||||||
|
count = count + records[i].chatlog.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}, [records]);
|
||||||
|
|
||||||
|
const cache = new CellMeasurerCache({
|
||||||
|
defaultHeight: 25,
|
||||||
|
fixedWidth: true
|
||||||
|
});
|
||||||
|
|
||||||
|
const RoomInfo = useCallback(({ roomId, roomName, uniqueKey, style }) =>
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
<div key={uniqueKey} style={style} className="row justify-content-start gap-2 room-info">
|
||||||
|
<div className="col-7"><span className="fw-bold">Room: </span>{roomName}</div>
|
||||||
|
<button className="btn btn-sm btn-primary col-sm-auto" onClick={() => TryVisitRoom(roomId)}>Visit Room</button>
|
||||||
|
<button className="btn btn-sm btn-primary col-sm-auto" onClick={() => dispatchUiEvent(new ModToolsOpenRoomInfoEvent(roomId))}>Room Tools</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{
|
||||||
|
(records && records.length) &&
|
||||||
|
<>
|
||||||
|
{(records.length === 1) && <RoomInfo roomId={records[0].roomId} roomName={records[0].roomName} uniqueKey={records[0].roomId} style={{}} />}
|
||||||
|
<div className="chatlog-messages w-100 h-100 overflow-hidden">
|
||||||
|
<div className="row align-items-start w-100">
|
||||||
|
<div className="col-auto text-center fw-bold">Time</div>
|
||||||
|
<div className="col-sm-2 username-label fw-bold">User</div>
|
||||||
|
<div className="col fw-bold">Message</div>
|
||||||
|
</div>
|
||||||
|
<div className="row w-100 h-100 chatlog">
|
||||||
|
<AutoSizer defaultWidth={400} defaultHeight={200}>
|
||||||
|
{({ height, width }) =>
|
||||||
|
{
|
||||||
|
cache.clearAll();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<List
|
||||||
|
width={width}
|
||||||
|
height={height}
|
||||||
|
rowCount={records.length > 1 ? getNumRowsForAdvanced() : records[0].chatlog.length}
|
||||||
|
rowHeight={cache.rowHeight}
|
||||||
|
className={'chatlog-container'}
|
||||||
|
rowRenderer={records.length > 1 ? advancedRowRenderer : simpleRowRenderer}
|
||||||
|
deferredMeasurementCache={cache} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</AutoSizer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
6
src/views/mod-tools/views/chatlog/ChatlogView.types.ts
Normal file
6
src/views/mod-tools/views/chatlog/ChatlogView.types.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import { ChatRecordData } from '@nitrots/nitro-renderer';
|
||||||
|
|
||||||
|
export interface ChatlogViewProps
|
||||||
|
{
|
||||||
|
records: ChatRecordData[];
|
||||||
|
}
|
@ -1,26 +0,0 @@
|
|||||||
.nitro-mod-tools-chatlog {
|
|
||||||
width: 480px;
|
|
||||||
|
|
||||||
.chatlog-messages {
|
|
||||||
height: 300px;
|
|
||||||
max-height: 300px;
|
|
||||||
|
|
||||||
.table {
|
|
||||||
color: $black;
|
|
||||||
|
|
||||||
> :not(caption) > * > * {
|
|
||||||
box-shadow: none;
|
|
||||||
border-bottom: 1px solid rgba(0, 0, 0, .2);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.table-striped > tbody > tr:nth-of-type(odd) {
|
|
||||||
color: $black;
|
|
||||||
background: rgba(0, 0, 0, .05);
|
|
||||||
}
|
|
||||||
|
|
||||||
td {
|
|
||||||
padding: 0px 5px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,84 +0,0 @@
|
|||||||
import { ModtoolRequestRoomChatlogComposer, ModtoolRoomChatlogEvent, ModtoolRoomChatlogLine } from '@nitrots/nitro-renderer';
|
|
||||||
import { FC, useCallback, useEffect, useState } from 'react';
|
|
||||||
import { TryVisitRoom } from '../../../../api';
|
|
||||||
import { CreateMessageHook, SendMessageHook } from '../../../../hooks/messages';
|
|
||||||
import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../layout';
|
|
||||||
import { ModToolsChatlogViewProps } from './ModToolsChatlogView.types';
|
|
||||||
|
|
||||||
export const ModToolsChatlogView: FC<ModToolsChatlogViewProps> = props =>
|
|
||||||
{
|
|
||||||
const { roomId = null, onCloseClick = null } = props;
|
|
||||||
|
|
||||||
const [ roomName, setRoomName ] = useState(null);
|
|
||||||
const [ messages, setMessages ] = useState<ModtoolRoomChatlogLine[]>(null);
|
|
||||||
const [ loadedRoomId, setLoadedRoomId ] = useState(null);
|
|
||||||
|
|
||||||
const [ messagesRequested, setMessagesRequested ] = useState(false);
|
|
||||||
|
|
||||||
useEffect(() =>
|
|
||||||
{
|
|
||||||
if(messagesRequested) return;
|
|
||||||
|
|
||||||
SendMessageHook(new ModtoolRequestRoomChatlogComposer(roomId));
|
|
||||||
setMessagesRequested(true);
|
|
||||||
}, [ roomId, messagesRequested, setMessagesRequested ]);
|
|
||||||
|
|
||||||
const onModtoolRoomChatlogEvent = useCallback((event: ModtoolRoomChatlogEvent) =>
|
|
||||||
{
|
|
||||||
const parser = event.getParser();
|
|
||||||
|
|
||||||
setRoomName(parser.data.roomName);
|
|
||||||
setMessages(parser.data.chatlog);
|
|
||||||
setLoadedRoomId(parser.data.roomId);
|
|
||||||
}, [ setRoomName, setMessages ]);
|
|
||||||
|
|
||||||
CreateMessageHook(ModtoolRoomChatlogEvent, onModtoolRoomChatlogEvent);
|
|
||||||
|
|
||||||
const handleClick = useCallback((action: string, value?: string) =>
|
|
||||||
{
|
|
||||||
if(!action) return;
|
|
||||||
|
|
||||||
switch(action)
|
|
||||||
{
|
|
||||||
case 'close':
|
|
||||||
onCloseClick();
|
|
||||||
return;
|
|
||||||
case 'visit_room':
|
|
||||||
TryVisitRoom(loadedRoomId);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}, [ onCloseClick, loadedRoomId ]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<NitroCardView className="nitro-mod-tools-chatlog" simple={ true }>
|
|
||||||
<NitroCardHeaderView headerText={ 'Room Chatlog' + (roomName ? ': ' + roomName : '') } onCloseClick={ event => handleClick('close') } />
|
|
||||||
<NitroCardContentView className="text-black h-100">
|
|
||||||
<div className="w-100 d-flex justify-content-end">
|
|
||||||
<button className="btn btn-sm btn-primary me-2" onClick={ event => handleClick('visit_room') }>Visit Room</button>
|
|
||||||
<button className="btn btn-sm btn-primary">Room Tools</button>
|
|
||||||
</div>
|
|
||||||
<div className="chatlog-messages overflow-auto">
|
|
||||||
{ messages && <table className="table table-striped">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th className="text-center">Time</th>
|
|
||||||
<th className="text-center">User</th>
|
|
||||||
<th>Message</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{ messages.map((message, index) =>
|
|
||||||
{
|
|
||||||
return <tr key={ index }>
|
|
||||||
<td className="text-center">{ message.timestamp }</td>
|
|
||||||
<td className="text-center"><a href="#" className="fw-bold">{ message.userName }</a></td>
|
|
||||||
<td className="word-break">{ message.message }</td>
|
|
||||||
</tr>;
|
|
||||||
}) }
|
|
||||||
</tbody>
|
|
||||||
</table> }
|
|
||||||
</div>
|
|
||||||
</NitroCardContentView>
|
|
||||||
</NitroCardView>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
.nitro-mod-tools-room {
|
|
||||||
width: 240px;
|
|
||||||
}
|
|
@ -0,0 +1,40 @@
|
|||||||
|
import { ChatRecordData, GetRoomChatlogMessageComposer, RoomChatlogEvent } from '@nitrots/nitro-renderer';
|
||||||
|
import { FC, useCallback, useEffect, useState } from 'react';
|
||||||
|
import { CreateMessageHook, SendMessageHook } from '../../../../../hooks/messages';
|
||||||
|
import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../../layout';
|
||||||
|
import { ChatlogView } from '../../chatlog/ChatlogView';
|
||||||
|
import { ModToolsChatlogViewProps } from './ModToolsChatlogView.types';
|
||||||
|
|
||||||
|
export const ModToolsChatlogView: FC<ModToolsChatlogViewProps> = props =>
|
||||||
|
{
|
||||||
|
const { roomId = null, onCloseClick = null } = props;
|
||||||
|
|
||||||
|
const [roomChatlog, setRoomChatlog] = useState<ChatRecordData>(null);
|
||||||
|
|
||||||
|
useEffect(() =>
|
||||||
|
{
|
||||||
|
SendMessageHook(new GetRoomChatlogMessageComposer(roomId));
|
||||||
|
}, [roomId]);
|
||||||
|
|
||||||
|
const onModtoolRoomChatlogEvent = useCallback((event: RoomChatlogEvent) =>
|
||||||
|
{
|
||||||
|
const parser = event.getParser();
|
||||||
|
|
||||||
|
if(!parser || parser.data.roomId !== roomId) return;
|
||||||
|
|
||||||
|
setRoomChatlog(parser.data);
|
||||||
|
}, [roomId, setRoomChatlog]);
|
||||||
|
|
||||||
|
CreateMessageHook(RoomChatlogEvent, onModtoolRoomChatlogEvent);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<NitroCardView className="nitro-mod-tools-room-chatlog" simple={true}>
|
||||||
|
<NitroCardHeaderView headerText={'Room Chatlog' + (roomChatlog ? ': ' + roomChatlog.roomName : '')} onCloseClick={() => onCloseClick()} />
|
||||||
|
<NitroCardContentView className="text-black h-100">
|
||||||
|
{roomChatlog &&
|
||||||
|
<ChatlogView records={[roomChatlog]} />
|
||||||
|
}
|
||||||
|
</NitroCardContentView>
|
||||||
|
</NitroCardView>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
.nitro-mod-tools-room {
|
||||||
|
width: 240px;
|
||||||
|
|
||||||
|
.username {
|
||||||
|
color: #1E7295;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,10 @@
|
|||||||
import { ModtoolRequestRoomInfoComposer, ModtoolRoomInfoEvent } from '@nitrots/nitro-renderer';
|
import { GetModeratorRoomInfoMessageComposer, ModerateRoomMessageComposer, ModeratorActionMessageComposer, ModeratorRoomInfoEvent } from '@nitrots/nitro-renderer';
|
||||||
import { FC, useCallback, useEffect, useState } from 'react';
|
import { FC, useCallback, useEffect, useState } from 'react';
|
||||||
import { CreateMessageHook, SendMessageHook } from '../../../../hooks/messages';
|
import { TryVisitRoom } from '../../../../../api';
|
||||||
import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../layout';
|
import { ModToolsOpenRoomChatlogEvent } from '../../../../../events/mod-tools/ModToolsOpenRoomChatlogEvent';
|
||||||
|
import { dispatchUiEvent } from '../../../../../hooks';
|
||||||
|
import { CreateMessageHook, SendMessageHook } from '../../../../../hooks/messages';
|
||||||
|
import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../../layout';
|
||||||
import { ModToolsRoomViewProps } from './ModToolsRoomView.types';
|
import { ModToolsRoomViewProps } from './ModToolsRoomView.types';
|
||||||
|
|
||||||
export const ModToolsRoomView: FC<ModToolsRoomViewProps> = props =>
|
export const ModToolsRoomView: FC<ModToolsRoomViewProps> = props =>
|
||||||
@ -17,27 +20,35 @@ export const ModToolsRoomView: FC<ModToolsRoomViewProps> = props =>
|
|||||||
const [ ownerInRoom, setOwnerInRoom ] = useState(false);
|
const [ ownerInRoom, setOwnerInRoom ] = useState(false);
|
||||||
const [ usersInRoom, setUsersInRoom ] = useState(0);
|
const [ usersInRoom, setUsersInRoom ] = useState(0);
|
||||||
|
|
||||||
|
//form data
|
||||||
|
const [kickUsers, setKickUsers] = useState(false);
|
||||||
|
const [lockRoom, setLockRoom] = useState(false);
|
||||||
|
const [changeRoomName, setChangeRoomName] = useState(false);
|
||||||
|
const [message, setMessage] = useState('');
|
||||||
|
|
||||||
useEffect(() =>
|
useEffect(() =>
|
||||||
{
|
{
|
||||||
if(infoRequested) return;
|
if(infoRequested) return;
|
||||||
|
|
||||||
SendMessageHook(new ModtoolRequestRoomInfoComposer(roomId));
|
SendMessageHook(new GetModeratorRoomInfoMessageComposer(roomId));
|
||||||
setInfoRequested(true);
|
setInfoRequested(true);
|
||||||
}, [ roomId, infoRequested, setInfoRequested ]);
|
}, [ roomId, infoRequested, setInfoRequested ]);
|
||||||
|
|
||||||
const onModtoolRoomInfoEvent = useCallback((event: ModtoolRoomInfoEvent) =>
|
const onModtoolRoomInfoEvent = useCallback((event: ModeratorRoomInfoEvent) =>
|
||||||
{
|
{
|
||||||
const parser = event.getParser();
|
const parser = event.getParser();
|
||||||
|
|
||||||
setLoadedRoomId(parser.id);
|
if(!parser || parser.data.flatId !== roomId) return;
|
||||||
setName(parser.name);
|
|
||||||
setOwnerId(parser.ownerId);
|
|
||||||
setOwnerName(parser.ownerName);
|
|
||||||
setOwnerInRoom(parser.ownerInRoom);
|
|
||||||
setUsersInRoom(parser.playerAmount);
|
|
||||||
}, [ setLoadedRoomId, setName, setOwnerId, setOwnerName, setOwnerInRoom, setUsersInRoom ]);
|
|
||||||
|
|
||||||
CreateMessageHook(ModtoolRoomInfoEvent, onModtoolRoomInfoEvent);
|
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);
|
||||||
|
}, [ setLoadedRoomId, setName, setOwnerId, setOwnerName, setOwnerInRoom, setUsersInRoom, roomId ]);
|
||||||
|
|
||||||
|
CreateMessageHook(ModeratorRoomInfoEvent, onModtoolRoomInfoEvent);
|
||||||
|
|
||||||
const handleClick = useCallback((action: string, value?: string) =>
|
const handleClick = useCallback((action: string, value?: string) =>
|
||||||
{
|
{
|
||||||
@ -45,27 +56,33 @@ export const ModToolsRoomView: FC<ModToolsRoomViewProps> = props =>
|
|||||||
|
|
||||||
switch(action)
|
switch(action)
|
||||||
{
|
{
|
||||||
case 'close':
|
case 'alert_only':
|
||||||
onCloseClick();
|
if(message.trim().length === 0) return;
|
||||||
|
SendMessageHook(new ModeratorActionMessageComposer(ModeratorActionMessageComposer.ACTION_ALERT, message, ''));
|
||||||
|
return;
|
||||||
|
case 'send_message':
|
||||||
|
if(message.trim().length === 0) return;
|
||||||
|
SendMessageHook(new ModeratorActionMessageComposer(ModeratorActionMessageComposer.ACTION_MESSAGE, message, ''));
|
||||||
|
SendMessageHook(new ModerateRoomMessageComposer(roomId, lockRoom ? 1 : 0, changeRoomName ? 1 : 0, kickUsers ? 1 : 0))
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}, [ onCloseClick ]);
|
}, [changeRoomName, kickUsers, lockRoom, message, roomId]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NitroCardView className="nitro-mod-tools-room" simple={ true }>
|
<NitroCardView className="nitro-mod-tools-room" simple={ true }>
|
||||||
<NitroCardHeaderView headerText={ 'Room Info' + (name ? ': ' + name : '') } onCloseClick={ event => handleClick('close') } />
|
<NitroCardHeaderView headerText={ 'Room Info' + (name ? ': ' + name : '') } onCloseClick={ () => onCloseClick() } />
|
||||||
<NitroCardContentView className="text-black">
|
<NitroCardContentView className="text-black">
|
||||||
<div className="d-flex justify-content-between align-items-center mb-1">
|
<div className="d-flex justify-content-between align-items-center mb-1">
|
||||||
<div>
|
<div>
|
||||||
<b>Room Owner:</b> <a href="#" className="fw-bold">{ ownerName }</a>
|
<b>Room Owner:</b> <span className="username fw-bold cursor-pointer">{ ownerName }</span>
|
||||||
</div>
|
</div>
|
||||||
<button className="btn btn-sm btn-primary">Visit Room</button>
|
<button className="btn btn-sm btn-primary" onClick={() => TryVisitRoom(roomId)}>Visit Room</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="d-flex justify-content-between align-items-center mb-1">
|
<div className="d-flex justify-content-between align-items-center mb-1">
|
||||||
<div>
|
<div>
|
||||||
<b>Users in room:</b> { usersInRoom }
|
<b>Users in room:</b> { usersInRoom }
|
||||||
</div>
|
</div>
|
||||||
<button className="btn btn-sm btn-primary">Chatlog</button>
|
<button className="btn btn-sm btn-primary" onClick={() => dispatchUiEvent(new ModToolsOpenRoomChatlogEvent(roomId))}>Chatlog</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="d-flex justify-content-between align-items-center mb-2">
|
<div className="d-flex justify-content-between align-items-center mb-2">
|
||||||
<div>
|
<div>
|
||||||
@ -75,28 +92,28 @@ export const ModToolsRoomView: FC<ModToolsRoomViewProps> = props =>
|
|||||||
</div>
|
</div>
|
||||||
<div className="bg-muted rounded py-1 px-2 mb-2">
|
<div className="bg-muted rounded py-1 px-2 mb-2">
|
||||||
<div className="form-check">
|
<div className="form-check">
|
||||||
<input className="form-check-input" type="checkbox" id="kickUsers" />
|
<input className="form-check-input" type="checkbox" id="kickUsers" checked={ kickUsers } onChange={e => setKickUsers(e.target.checked)}/>
|
||||||
<label className="form-check-label" htmlFor="kickUsers">
|
<label className="form-check-label" htmlFor="kickUsers">
|
||||||
Kick users out of the room
|
Kick users out of the room
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div className="form-check">
|
<div className="form-check">
|
||||||
<input className="form-check-input" type="checkbox" id="lockRoom" />
|
<input className="form-check-input" type="checkbox" id="lockRoom" checked={ lockRoom } onChange={e => setLockRoom(e.target.checked)}/>
|
||||||
<label className="form-check-label" htmlFor="lockRoom">
|
<label className="form-check-label" htmlFor="lockRoom">
|
||||||
Change room lock to doorbell
|
Change room lock to doorbell
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div className="form-check">
|
<div className="form-check">
|
||||||
<input className="form-check-input" type="checkbox" id="lockRoom" />
|
<input className="form-check-input" type="checkbox" id="roomName" checked={ changeRoomName } onChange={e => setChangeRoomName(e.target.checked)}/>
|
||||||
<label className="form-check-label" htmlFor="lockRoom">
|
<label className="form-check-label" htmlFor="roomName">
|
||||||
Change room name to "Inappro- priate to Hotel Management"
|
Change room name to "Inappro- priate to Hotel Management"
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<textarea className="form-control mb-2" placeholder="Type a mandatory message to the users in this text box..."></textarea>
|
<textarea className="form-control mb-2" placeholder="Type a mandatory message to the users in this text box..." value={message} onChange={e => setMessage(e.target.value)}></textarea>
|
||||||
<div className="d-flex justify-content-between">
|
<div className="d-flex justify-content-between">
|
||||||
<button className="btn btn-danger w-100 me-2">Send Caution</button>
|
<button className="btn btn-danger w-100 me-2" onClick={() => handleClick('send_message')}>Send Caution</button>
|
||||||
<button className="btn btn-success w-100">Send Alert only</button>
|
<button className="btn btn-success w-100" onClick={() => handleClick('alert_only')}>Send Alert only</button>
|
||||||
</div>
|
</div>
|
||||||
</NitroCardContentView>
|
</NitroCardContentView>
|
||||||
</NitroCardView>
|
</NitroCardView>
|
11
src/views/mod-tools/views/tickets/ModToolsTicketView.scss
Normal file
11
src/views/mod-tools/views/tickets/ModToolsTicketView.scss
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
.nitro-mod-tools-tickets
|
||||||
|
{
|
||||||
|
width: 400px;
|
||||||
|
height: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nitro-mod-tools-handle-issue
|
||||||
|
{
|
||||||
|
width: 400px;
|
||||||
|
height: 300px;
|
||||||
|
}
|
@ -1,6 +1,13 @@
|
|||||||
import { FC, useState } from 'react';
|
import { IssueMessageData } from '@nitrots/nitro-renderer';
|
||||||
|
import { FC, useCallback, useMemo, useState } from 'react';
|
||||||
|
import { GetSessionDataManager } from '../../../../api';
|
||||||
import { NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView } from '../../../../layout';
|
import { NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView } from '../../../../layout';
|
||||||
|
import { useModToolsContext } from '../../context/ModToolsContext';
|
||||||
|
import { IssueInfoView } from './issue-info/IssueInfoView';
|
||||||
import { ModToolsTicketsViewProps } from './ModToolsTicketsView.types';
|
import { ModToolsTicketsViewProps } from './ModToolsTicketsView.types';
|
||||||
|
import { ModToolsMyIssuesTabView } from './my-issues/ModToolsMyIssuesTabView';
|
||||||
|
import { ModToolsOpenIssuesTabView } from './open-issues/ModToolsOpenIssuesTabView';
|
||||||
|
import { ModToolsPickedIssuesTabView } from './picked-issues/ModToolsPickedIssuesTabView';
|
||||||
|
|
||||||
const TABS: string[] = [
|
const TABS: string[] = [
|
||||||
'Open Issues',
|
'Open Issues',
|
||||||
@ -11,10 +18,70 @@ const TABS: string[] = [
|
|||||||
export const ModToolsTicketsView: FC<ModToolsTicketsViewProps> = props =>
|
export const ModToolsTicketsView: FC<ModToolsTicketsViewProps> = props =>
|
||||||
{
|
{
|
||||||
const { onCloseClick = null } = props;
|
const { onCloseClick = null } = props;
|
||||||
|
const { modToolsState = null } = useModToolsContext();
|
||||||
|
const { tickets= null } = modToolsState;
|
||||||
const [ currentTab, setCurrentTab ] = useState<number>(0);
|
const [ currentTab, setCurrentTab ] = useState<number>(0);
|
||||||
|
const [ issueInfoWindows, setIssueInfoWindows ] = useState<number[]>([]);
|
||||||
|
|
||||||
|
const openIssues = useMemo(() =>
|
||||||
|
{
|
||||||
|
if(!tickets) return [];
|
||||||
|
|
||||||
|
return tickets.filter(issue => issue.state === IssueMessageData.STATE_OPEN);
|
||||||
|
}, [tickets]);
|
||||||
|
|
||||||
|
const myIssues = useMemo(() =>
|
||||||
|
{
|
||||||
|
if(!tickets) return [];
|
||||||
|
|
||||||
|
return tickets.filter(issue => (issue.state === IssueMessageData.STATE_PICKED) && (issue.pickerUserId === GetSessionDataManager().userId));
|
||||||
|
}, [tickets]);
|
||||||
|
|
||||||
|
const pickedIssues = useMemo(() =>
|
||||||
|
{
|
||||||
|
if(!tickets) return [];
|
||||||
|
|
||||||
|
return tickets.filter(issue => issue.state === IssueMessageData.STATE_PICKED);
|
||||||
|
}, [tickets]);
|
||||||
|
|
||||||
|
const onIssueInfoClosed = useCallback((issueId: number) =>
|
||||||
|
{
|
||||||
|
const indexOfValue = issueInfoWindows.indexOf(issueId);
|
||||||
|
|
||||||
|
if(indexOfValue === -1) return;
|
||||||
|
|
||||||
|
const newValues = Array.from(issueInfoWindows);
|
||||||
|
newValues.splice(indexOfValue, 1);
|
||||||
|
setIssueInfoWindows(newValues);
|
||||||
|
}, [issueInfoWindows]);
|
||||||
|
|
||||||
|
const onIssueHandleClicked = useCallback((issueId: number) =>
|
||||||
|
{
|
||||||
|
if(issueInfoWindows.indexOf(issueId) === -1)
|
||||||
|
{
|
||||||
|
const newValues = Array.from(issueInfoWindows);
|
||||||
|
newValues.push(issueId);
|
||||||
|
setIssueInfoWindows(newValues);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
onIssueInfoClosed(issueId);
|
||||||
|
}
|
||||||
|
}, [issueInfoWindows, onIssueInfoClosed]);
|
||||||
|
|
||||||
|
const CurrentTabComponent = useCallback(() =>
|
||||||
|
{
|
||||||
|
switch(currentTab)
|
||||||
|
{
|
||||||
|
case 0: return <ModToolsOpenIssuesTabView openIssues={openIssues}/>;
|
||||||
|
case 1: return <ModToolsMyIssuesTabView myIssues={myIssues} onIssueHandleClick={onIssueHandleClicked}/>;
|
||||||
|
case 2: return <ModToolsPickedIssuesTabView pickedIssues={pickedIssues}/>;
|
||||||
|
default: return null;
|
||||||
|
}
|
||||||
|
}, [currentTab, myIssues, onIssueHandleClicked, openIssues, pickedIssues]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
<NitroCardView className="nitro-mod-tools-tickets" simple={ false }>
|
<NitroCardView className="nitro-mod-tools-tickets" simple={ false }>
|
||||||
<NitroCardHeaderView headerText={ 'Tickets' } onCloseClick={ onCloseClick } />
|
<NitroCardHeaderView headerText={ 'Tickets' } onCloseClick={ onCloseClick } />
|
||||||
<NitroCardContentView className="p-0 text-black">
|
<NitroCardContentView className="p-0 text-black">
|
||||||
@ -26,8 +93,14 @@ export const ModToolsTicketsView: FC<ModToolsTicketsViewProps> = props =>
|
|||||||
</NitroCardTabsItemView>);
|
</NitroCardTabsItemView>);
|
||||||
}) }
|
}) }
|
||||||
</NitroCardTabsView>
|
</NitroCardTabsView>
|
||||||
<div className="p-2"></div>
|
<div className="p-2">
|
||||||
|
<CurrentTabComponent />
|
||||||
|
</div>
|
||||||
</NitroCardContentView>
|
</NitroCardContentView>
|
||||||
</NitroCardView>
|
</NitroCardView>
|
||||||
|
{
|
||||||
|
issueInfoWindows && issueInfoWindows.map(issueId => <IssueInfoView key={issueId} issueId={issueId} onIssueInfoClosed={onIssueInfoClosed}/>)
|
||||||
|
}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,37 @@
|
|||||||
|
import { CfhChatlogData, CfhChatlogEvent, GetCfhChatlogMessageComposer } from '@nitrots/nitro-renderer';
|
||||||
|
import { FC, useCallback, useEffect, useState } from 'react';
|
||||||
|
import { CreateMessageHook, SendMessageHook } from '../../../../../hooks';
|
||||||
|
import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../../layout';
|
||||||
|
import { ChatlogView } from '../../chatlog/ChatlogView';
|
||||||
|
import { CfhChatlogViewProps } from './CfhChatlogView.types';
|
||||||
|
|
||||||
|
export const CfhChatlogView: FC<CfhChatlogViewProps> = props =>
|
||||||
|
{
|
||||||
|
const { onCloseClick = null, issueId = null } = props;
|
||||||
|
const [ chatlogData, setChatlogData ] = useState<CfhChatlogData>(null);
|
||||||
|
|
||||||
|
useEffect(() =>
|
||||||
|
{
|
||||||
|
SendMessageHook(new GetCfhChatlogMessageComposer(issueId));
|
||||||
|
}, [issueId]);
|
||||||
|
|
||||||
|
const onCfhChatlogEvent = useCallback((event: CfhChatlogEvent) =>
|
||||||
|
{
|
||||||
|
const parser = event.getParser();
|
||||||
|
|
||||||
|
if(!parser || parser.data.issueId !== issueId) return;
|
||||||
|
|
||||||
|
setChatlogData(parser.data);
|
||||||
|
}, [issueId]);
|
||||||
|
|
||||||
|
CreateMessageHook(CfhChatlogEvent, onCfhChatlogEvent);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<NitroCardView className="nitro-mod-tools-cfh-chatlog" simple={true}>
|
||||||
|
<NitroCardHeaderView headerText={'Issue Chatlog'} onCloseClick={onCloseClick} />
|
||||||
|
<NitroCardContentView className="text-black">
|
||||||
|
{ chatlogData && <ChatlogView records={[chatlogData.chatRecord]} />}
|
||||||
|
</NitroCardContentView>
|
||||||
|
</NitroCardView>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
export interface CfhChatlogViewProps
|
||||||
|
{
|
||||||
|
issueId: number;
|
||||||
|
onCloseClick(): void;
|
||||||
|
}
|
@ -0,0 +1,72 @@
|
|||||||
|
import { CloseIssuesMessageComposer, ReleaseIssuesMessageComposer } from '@nitrots/nitro-renderer';
|
||||||
|
import { FC, useCallback, useMemo, useState } from 'react';
|
||||||
|
import { LocalizeText } from '../../../../../api';
|
||||||
|
import { ModToolsOpenUserInfoEvent } from '../../../../../events/mod-tools/ModToolsOpenUserInfoEvent';
|
||||||
|
import { dispatchUiEvent, SendMessageHook } from '../../../../../hooks';
|
||||||
|
import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../../layout';
|
||||||
|
import { getSourceName } from '../../../common/IssueCategoryNames';
|
||||||
|
import { useModToolsContext } from '../../../context/ModToolsContext';
|
||||||
|
import { CfhChatlogView } from './CfhChatlogView';
|
||||||
|
import { IssueInfoViewProps } from './IssueInfoView.types';
|
||||||
|
|
||||||
|
export const IssueInfoView: FC<IssueInfoViewProps> = props =>
|
||||||
|
{
|
||||||
|
const { issueId = null, onIssueInfoClosed = null } = props;
|
||||||
|
const { modToolsState = null } = useModToolsContext();
|
||||||
|
const { tickets= null } = modToolsState;
|
||||||
|
const [ cfhChatlogOpen, setcfhChatlogOpen ] = useState(false);
|
||||||
|
|
||||||
|
const ticket = useMemo(() =>
|
||||||
|
{
|
||||||
|
return tickets.find( issue => issue.issueId === issueId);
|
||||||
|
}, [issueId, tickets]);
|
||||||
|
|
||||||
|
const onReleaseIssue = useCallback((issueId: number) =>
|
||||||
|
{
|
||||||
|
SendMessageHook(new ReleaseIssuesMessageComposer([issueId]));
|
||||||
|
onIssueInfoClosed(issueId);
|
||||||
|
}, [onIssueInfoClosed]);
|
||||||
|
|
||||||
|
const openUserInfo = useCallback((userId: number) =>
|
||||||
|
{
|
||||||
|
dispatchUiEvent(new ModToolsOpenUserInfoEvent(userId));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const closeIssue = useCallback((resolutionType: number) =>
|
||||||
|
{
|
||||||
|
SendMessageHook(new CloseIssuesMessageComposer([issueId], resolutionType));
|
||||||
|
onIssueInfoClosed(issueId)
|
||||||
|
}, [issueId, onIssueInfoClosed]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<NitroCardView className="nitro-mod-tools-handle-issue" simple={true}>
|
||||||
|
<NitroCardHeaderView headerText={'Resolving issue ' + issueId} onCloseClick={() => onIssueInfoClosed(issueId)} />
|
||||||
|
<NitroCardContentView className="text-black">
|
||||||
|
<div className="row">
|
||||||
|
<div className="col-8">
|
||||||
|
<h3>Issue Information</h3>
|
||||||
|
<div><span className="fw-bold">Source: </span>{getSourceName(ticket.categoryId)}</div>
|
||||||
|
<div><span className="fw-bold">Category: </span>{LocalizeText('help.cfh.topic.' + ticket.reportedCategoryId)}</div>
|
||||||
|
<div><span className="fw-bold">Description: </span>{ticket.message}</div>
|
||||||
|
<div><span className="fw-bold">Caller: </span><button className="btn btn-link fw-bold" onClick={() => openUserInfo(ticket.reporterUserId)}>{ticket.reporterUserName}</button></div>
|
||||||
|
<div><span className="fw-bold">Reported User: </span><button className="btn btn-link fw-bold" onClick={() => openUserInfo(ticket.reportedUserId)}>{ticket.reportedUserName}</button></div>
|
||||||
|
</div>
|
||||||
|
<div className="col-4">
|
||||||
|
<div className="d-grid gap-2 mb-4">
|
||||||
|
<button className="btn btn-secondary" onClick={() => setcfhChatlogOpen(!cfhChatlogOpen)}>Chatlog</button>
|
||||||
|
</div>
|
||||||
|
<div className="d-grid gap-2">
|
||||||
|
<button className="btn btn-primary" onClick={() => closeIssue(CloseIssuesMessageComposer.RESOLUTION_USELESS)}>Close as useless</button>
|
||||||
|
<button className="btn btn-danger" onClick={() => closeIssue(CloseIssuesMessageComposer.RESOLUTION_ABUSIVE)}>Close as abusive</button>
|
||||||
|
<button className="btn btn-success" onClick={() => closeIssue(CloseIssuesMessageComposer.RESOLUTION_RESOLVED)}>Close as resolved</button>
|
||||||
|
<button className="btn btn-secondary" onClick={() => onReleaseIssue(issueId)}>Release</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</NitroCardContentView>
|
||||||
|
</NitroCardView>
|
||||||
|
{ cfhChatlogOpen && <CfhChatlogView issueId={issueId} onCloseClick={() => setcfhChatlogOpen(false) }/>}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
export interface IssueInfoViewProps
|
||||||
|
{
|
||||||
|
issueId: number;
|
||||||
|
onIssueInfoClosed(issueId: number): void;
|
||||||
|
}
|
@ -0,0 +1,45 @@
|
|||||||
|
import { ReleaseIssuesMessageComposer } from '@nitrots/nitro-renderer';
|
||||||
|
import { FC, useCallback } from 'react';
|
||||||
|
import { SendMessageHook } from '../../../../../hooks';
|
||||||
|
import { ModToolsMyIssuesTabViewProps } from './ModToolsMyIssuesTabView.types';
|
||||||
|
|
||||||
|
export const ModToolsMyIssuesTabView: FC<ModToolsMyIssuesTabViewProps> = props =>
|
||||||
|
{
|
||||||
|
const { myIssues = null, onIssueHandleClick = null } = props;
|
||||||
|
|
||||||
|
|
||||||
|
const onReleaseIssue = useCallback((issueId: number) =>
|
||||||
|
{
|
||||||
|
SendMessageHook(new ReleaseIssuesMessageComposer([issueId]));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<table className="table text-black table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">Type</th>
|
||||||
|
<th scope="col">Room/Player</th>
|
||||||
|
<th scope="col">Opened</th>
|
||||||
|
<th scope="col"></th>
|
||||||
|
<th scope="col"></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{myIssues.map(issue =>
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
<tr className="text-black" key={issue.issueId}>
|
||||||
|
<td>{issue.categoryId}</td>
|
||||||
|
<td>{issue.reportedUserName}</td>
|
||||||
|
<td>{new Date(Date.now() - issue.issueAgeInMilliseconds).toLocaleTimeString()}</td>
|
||||||
|
<td><button className="btn btn-sm btn-primary" onClick={() => onIssueHandleClick(issue.issueId)}>Handle</button></td>
|
||||||
|
<td><button className="btn btn-sm btn-danger" onClick={() => onReleaseIssue(issue.issueId)}>Release</button></td>
|
||||||
|
</tr>)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
import { IssueMessageData } from '@nitrots/nitro-renderer';
|
||||||
|
|
||||||
|
export interface ModToolsMyIssuesTabViewProps
|
||||||
|
{
|
||||||
|
myIssues: IssueMessageData[];
|
||||||
|
onIssueHandleClick(issueId: number): void;
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
import { PickIssuesMessageComposer } from '@nitrots/nitro-renderer';
|
||||||
|
import { FC, useCallback } from 'react';
|
||||||
|
import { SendMessageHook } from '../../../../../hooks';
|
||||||
|
import { ModToolsOpenIssuesTabViewProps } from './ModToolsOpenIssuesTabView.types';
|
||||||
|
|
||||||
|
export const ModToolsOpenIssuesTabView: FC<ModToolsOpenIssuesTabViewProps> = props =>
|
||||||
|
{
|
||||||
|
const { openIssues = null } = props;
|
||||||
|
|
||||||
|
const onPickIssue = useCallback((issueId: number) =>
|
||||||
|
{
|
||||||
|
SendMessageHook(new PickIssuesMessageComposer([issueId], false, 0, 'pick issue button'));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<table className="table text-black table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">Type</th>
|
||||||
|
<th scope="col">Room/Player</th>
|
||||||
|
<th scope="col">Opened</th>
|
||||||
|
<th scope="col"></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{openIssues.map(issue =>
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
<tr className="text-black" key={issue.issueId}>
|
||||||
|
<td>{issue.categoryId}</td>
|
||||||
|
<td>{issue.reportedUserName}</td>
|
||||||
|
<td>{new Date(Date.now() - issue.issueAgeInMilliseconds).toLocaleTimeString()}</td>
|
||||||
|
<td><button className="btn btn-sm btn-success" onClick={() => onPickIssue(issue.issueId)}>Pick Issue</button></td>
|
||||||
|
</tr>)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
import { IssueMessageData } from '@nitrots/nitro-renderer';
|
||||||
|
|
||||||
|
export interface ModToolsOpenIssuesTabViewProps
|
||||||
|
{
|
||||||
|
openIssues: IssueMessageData[];
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
import { FC } from 'react';
|
||||||
|
import { ModToolsPickedIssuesTabViewProps } from './ModToolsPickedIssuesTabView.types';
|
||||||
|
|
||||||
|
export const ModToolsPickedIssuesTabView: FC<ModToolsPickedIssuesTabViewProps> = props =>
|
||||||
|
{
|
||||||
|
const { pickedIssues = null } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<table className="table text-black table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">Type</th>
|
||||||
|
<th scope="col">Room/Player</th>
|
||||||
|
<th scope="col">Opened</th>
|
||||||
|
<th scope="col">Picker</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{pickedIssues.map(issue =>
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
<tr className="text-black" key={issue.issueId}>
|
||||||
|
<td>{issue.categoryId}</td>
|
||||||
|
<td>{issue.reportedUserName}</td>
|
||||||
|
<td>{new Date(Date.now() - issue.issueAgeInMilliseconds).toLocaleTimeString()}</td>
|
||||||
|
<td>{issue.pickerUserName}</td>
|
||||||
|
</tr>)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
import { IssueMessageData } from '@nitrots/nitro-renderer';
|
||||||
|
|
||||||
|
export interface ModToolsPickedIssuesTabViewProps
|
||||||
|
{
|
||||||
|
pickedIssues: IssueMessageData[];
|
||||||
|
}
|
@ -1,15 +0,0 @@
|
|||||||
import { FC } from 'react';
|
|
||||||
import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../layout';
|
|
||||||
import { ModToolsUserViewProps } from './ModToolsUserView.types';
|
|
||||||
|
|
||||||
export const ModToolsUserView: FC<ModToolsUserViewProps> = props =>
|
|
||||||
{
|
|
||||||
return (
|
|
||||||
<NitroCardView className="nitro-mod-tools-user" simple={ true }>
|
|
||||||
<NitroCardHeaderView headerText={ 'User Info' } onCloseClick={ event => {} } />
|
|
||||||
<NitroCardContentView className="text-black">
|
|
||||||
|
|
||||||
</NitroCardContentView>
|
|
||||||
</NitroCardView>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,2 +0,0 @@
|
|||||||
export interface ModToolsUserViewProps
|
|
||||||
{}
|
|
@ -0,0 +1,41 @@
|
|||||||
|
import { ChatRecordData, GetUserChatlogMessageComposer, UserChatlogEvent } from '@nitrots/nitro-renderer';
|
||||||
|
import { FC, useCallback, useEffect, useState } from 'react';
|
||||||
|
import { CreateMessageHook, SendMessageHook } from '../../../../../hooks';
|
||||||
|
import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../../layout';
|
||||||
|
import { ChatlogView } from '../../chatlog/ChatlogView';
|
||||||
|
import { ModToolsUserChatlogViewProps } from './ModToolsUserChatlogView.types';
|
||||||
|
|
||||||
|
export const ModToolsUserChatlogView: FC<ModToolsUserChatlogViewProps> = props =>
|
||||||
|
{
|
||||||
|
const { userId = null, onCloseClick = null } = props;
|
||||||
|
const [userChatlog, setUserChatlog] = useState<ChatRecordData[]>(null);
|
||||||
|
const [username, setUsername] = useState<string>(null);
|
||||||
|
|
||||||
|
useEffect(() =>
|
||||||
|
{
|
||||||
|
SendMessageHook(new GetUserChatlogMessageComposer(userId));
|
||||||
|
}, [userId]);
|
||||||
|
|
||||||
|
const onModtoolUserChatlogEvent = useCallback((event: UserChatlogEvent) =>
|
||||||
|
{
|
||||||
|
const parser = event.getParser();
|
||||||
|
|
||||||
|
if(!parser || parser.data.userId !== userId) return;
|
||||||
|
|
||||||
|
setUsername(parser.data.username);
|
||||||
|
setUserChatlog(parser.data.roomChatlogs);
|
||||||
|
}, [setUsername, setUserChatlog, userId]);
|
||||||
|
|
||||||
|
CreateMessageHook(UserChatlogEvent, onModtoolUserChatlogEvent);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<NitroCardView className="nitro-mod-tools-user-chatlog" simple={true}>
|
||||||
|
<NitroCardHeaderView headerText={'User Chatlog' + (username ? ': ' + username : '')} onCloseClick={() => onCloseClick()} />
|
||||||
|
<NitroCardContentView className="text-black h-100">
|
||||||
|
{userChatlog &&
|
||||||
|
<ChatlogView records={userChatlog} />
|
||||||
|
}
|
||||||
|
</NitroCardContentView>
|
||||||
|
</NitroCardView>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
export interface ModToolsUserChatlogViewProps
|
||||||
|
{
|
||||||
|
userId: number;
|
||||||
|
onCloseClick: () => void;
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
.nitro-mod-tools-user {
|
||||||
|
width: 350px;
|
||||||
|
height: 370px;
|
||||||
|
|
||||||
|
.username {
|
||||||
|
color: #1E7295;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table {
|
||||||
|
color: $black;
|
||||||
|
|
||||||
|
> :not(caption) > * > * {
|
||||||
|
box-shadow: none;
|
||||||
|
border-bottom: 1px solid rgba(0, 0, 0, .2);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.table-striped > tbody > tr:nth-of-type(odd) {
|
||||||
|
color: $black;
|
||||||
|
background: rgba(0, 0, 0, .05);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
153
src/views/mod-tools/views/user/user-info/ModToolsUserView.tsx
Normal file
153
src/views/mod-tools/views/user/user-info/ModToolsUserView.tsx
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
import { FriendlyTime, GetModeratorUserInfoMessageComposer, ModeratorUserInfoData, ModeratorUserInfoEvent } from '@nitrots/nitro-renderer';
|
||||||
|
import { FC, useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
|
import { LocalizeText } from '../../../../../api';
|
||||||
|
import { ModToolsOpenUserChatlogEvent } from '../../../../../events/mod-tools/ModToolsOpenUserChatlogEvent';
|
||||||
|
import { CreateMessageHook, dispatchUiEvent, SendMessageHook } from '../../../../../hooks';
|
||||||
|
import { NitroCardContentView, NitroCardHeaderView, NitroCardView, NitroLayoutButton, NitroLayoutGrid, NitroLayoutGridColumn } from '../../../../../layout';
|
||||||
|
import { ModToolsUserModActionView } from '../user-mod-action/ModToolsUserModActionView';
|
||||||
|
import { ModToolsUserRoomVisitsView } from '../user-room-visits/ModToolsUserRoomVisitsView';
|
||||||
|
import { ModToolsSendUserMessageView } from '../user-sendmessage/ModToolsSendUserMessageView';
|
||||||
|
import { ModToolsUserViewProps } from './ModToolsUserView.types';
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
useEffect(() =>
|
||||||
|
{
|
||||||
|
SendMessageHook(new GetModeratorUserInfoMessageComposer(userId));
|
||||||
|
}, [ userId ]);
|
||||||
|
|
||||||
|
const onModtoolUserInfoEvent = useCallback((event: ModeratorUserInfoEvent) =>
|
||||||
|
{
|
||||||
|
const parser = event.getParser();
|
||||||
|
|
||||||
|
if(!parser || parser.data.userId !== userId) return;
|
||||||
|
|
||||||
|
setUserInfo(parser.data);
|
||||||
|
}, [setUserInfo, userId]);
|
||||||
|
|
||||||
|
CreateMessageHook(ModeratorUserInfoEvent, onModtoolUserInfoEvent);
|
||||||
|
|
||||||
|
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 ]);
|
||||||
|
|
||||||
|
if(!userInfo) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<NitroCardView className="nitro-mod-tools-user" simple={true}>
|
||||||
|
<NitroCardHeaderView headerText={ LocalizeText('modtools.userinfo.title', [ 'username' ], [ userInfo.userName ]) } onCloseClick={ () => onCloseClick() } />
|
||||||
|
<NitroCardContentView className="text-black">
|
||||||
|
<NitroLayoutGrid>
|
||||||
|
<NitroLayoutGridColumn 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>
|
||||||
|
</NitroLayoutGridColumn>
|
||||||
|
<NitroLayoutGridColumn size={ 4 }>
|
||||||
|
<NitroLayoutButton variant="primary" size="sm" onClick={ event => dispatchUiEvent(new ModToolsOpenUserChatlogEvent(userId)) }>
|
||||||
|
Room Chat
|
||||||
|
</NitroLayoutButton>
|
||||||
|
<NitroLayoutButton variant="primary" size="sm" onClick={ event => setSendMessageVisible(!sendMessageVisible) }>
|
||||||
|
Send Message
|
||||||
|
</NitroLayoutButton>
|
||||||
|
<NitroLayoutButton variant="primary" size="sm" onClick={ event => setRoomVisitsVisible(!roomVisitsVisible) }>
|
||||||
|
Room Visits
|
||||||
|
</NitroLayoutButton>
|
||||||
|
<NitroLayoutButton variant="primary" size="sm" onClick={ event => setModActionVisible(!modActionVisible) }>
|
||||||
|
Mod Action
|
||||||
|
</NitroLayoutButton>
|
||||||
|
</NitroLayoutGridColumn>
|
||||||
|
</NitroLayoutGrid>
|
||||||
|
</NitroCardContentView>
|
||||||
|
</NitroCardView>
|
||||||
|
{ sendMessageVisible &&
|
||||||
|
<ModToolsSendUserMessageView 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) } /> }
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
export interface ModToolsUserViewProps
|
||||||
|
{
|
||||||
|
userId: number;
|
||||||
|
onCloseClick: () => void;
|
||||||
|
}
|
@ -0,0 +1,201 @@
|
|||||||
|
import { CallForHelpTopicData, DefaultSanctionMessageComposer, ModAlertMessageComposer, ModBanMessageComposer, ModKickMessageComposer, ModMessageMessageComposer, ModMuteMessageComposer, ModTradingLockMessageComposer } from '@nitrots/nitro-renderer';
|
||||||
|
import { FC, useCallback, useMemo, useState } from 'react';
|
||||||
|
import { LocalizeText } from '../../../../../api';
|
||||||
|
import { NotificationAlertEvent } from '../../../../../events';
|
||||||
|
import { dispatchUiEvent, SendMessageHook } from '../../../../../hooks';
|
||||||
|
import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../../layout';
|
||||||
|
import { useModToolsContext } from '../../../context/ModToolsContext';
|
||||||
|
import { ModActionDefinition } from '../../../utils/ModActionDefinition';
|
||||||
|
import { ModToolsUserModActionViewProps } from './ModToolsUserModActionView.types';
|
||||||
|
|
||||||
|
const actions = [
|
||||||
|
new ModActionDefinition(1, 'Alert', ModActionDefinition.ALERT, 1, 0),
|
||||||
|
new ModActionDefinition(2, 'Mute 1h', ModActionDefinition.MUTE, 2, 0),
|
||||||
|
new ModActionDefinition(4, 'Ban 7 days', ModActionDefinition.BAN, 4, 0),
|
||||||
|
new ModActionDefinition(3, 'Ban 18h', ModActionDefinition.BAN, 3, 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 { modToolsState = null, dispatchModToolsState = null } = useModToolsContext();
|
||||||
|
const { cfhCategories = null, settings = null } = modToolsState;
|
||||||
|
const [ selectedTopic, setSelectedTopic ] = useState(-1);
|
||||||
|
const [ selectedAction, setSelectedAction ] = useState(-1);
|
||||||
|
const [ message, setMessage ] = useState<string>('');
|
||||||
|
|
||||||
|
const topics = useMemo(() =>
|
||||||
|
{
|
||||||
|
const values: CallForHelpTopicData[] = [];
|
||||||
|
|
||||||
|
if(!cfhCategories) return values;
|
||||||
|
|
||||||
|
for(let category of cfhCategories)
|
||||||
|
{
|
||||||
|
for(let topic of category.topics)
|
||||||
|
{
|
||||||
|
values.push(topic)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return values;
|
||||||
|
}, [cfhCategories]);
|
||||||
|
|
||||||
|
const sendDefaultSanction = useCallback(() =>
|
||||||
|
{
|
||||||
|
SendMessageHook(new DefaultSanctionMessageComposer(user.userId, selectedTopic, message));
|
||||||
|
onCloseClick();
|
||||||
|
}, [message, onCloseClick, selectedTopic, user.userId]);
|
||||||
|
|
||||||
|
const sendSanction = useCallback(() =>
|
||||||
|
{
|
||||||
|
if( (selectedTopic === -1) || (selectedAction === -1) )
|
||||||
|
{
|
||||||
|
dispatchUiEvent(new NotificationAlertEvent(['You must select a CFH topic and Sanction'], null, null, null, 'Error', null));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!settings || !settings.cfhPermission)
|
||||||
|
{
|
||||||
|
dispatchUiEvent(new NotificationAlertEvent(['You do not have permission to do this'], null, null, null, 'Error', null));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const category = topics[selectedTopic];
|
||||||
|
const sanction = actions[selectedAction];
|
||||||
|
|
||||||
|
if(!category)
|
||||||
|
{
|
||||||
|
dispatchUiEvent(new NotificationAlertEvent(['You must select a CFH topic'], null, null, null, 'Error', null));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!sanction)
|
||||||
|
{
|
||||||
|
dispatchUiEvent(new NotificationAlertEvent(['You must select a sanction'], null, null, null, 'Error', null));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const messageOrDefault = message.trim().length === 0 ? LocalizeText('help.cfh.topic.' + category.id) : message;
|
||||||
|
|
||||||
|
switch(sanction.actionType)
|
||||||
|
{
|
||||||
|
case ModActionDefinition.ALERT:
|
||||||
|
|
||||||
|
if(!settings.alertPermission)
|
||||||
|
{
|
||||||
|
dispatchUiEvent(new NotificationAlertEvent(['You have insufficient permissions.'], null, null, null, 'Error', null));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(message.trim().length === 0)
|
||||||
|
{
|
||||||
|
dispatchUiEvent(new NotificationAlertEvent(['Please write a message to user.'], null, null, null, 'Error', null));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SendMessageHook(new ModAlertMessageComposer(user.userId, message, category.id));
|
||||||
|
|
||||||
|
break;
|
||||||
|
case ModActionDefinition.MUTE:
|
||||||
|
SendMessageHook(new ModMuteMessageComposer(user.userId, messageOrDefault, category.id));
|
||||||
|
|
||||||
|
break;
|
||||||
|
case ModActionDefinition.BAN:
|
||||||
|
|
||||||
|
if(!settings.banPermission)
|
||||||
|
{
|
||||||
|
dispatchUiEvent(new NotificationAlertEvent(['You have insufficient permissions.'], null, null, null, 'Error', null));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SendMessageHook(new ModBanMessageComposer(user.userId, messageOrDefault, category.id, selectedAction, (sanction.actionId === 106)));
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ModActionDefinition.KICK:
|
||||||
|
|
||||||
|
if(!settings.kickPermission)
|
||||||
|
{
|
||||||
|
dispatchUiEvent(new NotificationAlertEvent(['You have insufficient permissions.'], null, null, null, 'Error', null));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SendMessageHook(new ModKickMessageComposer(user.userId, messageOrDefault, category.id));
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ModActionDefinition.TRADE_LOCK:
|
||||||
|
{
|
||||||
|
const numSeconds = sanction.actionLengthHours * 60;
|
||||||
|
SendMessageHook(new ModTradingLockMessageComposer(user.userId, messageOrDefault, numSeconds, category.id));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ModActionDefinition.MESSAGE:
|
||||||
|
|
||||||
|
if(message.trim().length === 0)
|
||||||
|
{
|
||||||
|
dispatchUiEvent(new NotificationAlertEvent(['Please write a message to user.'], null, null, null, 'Error', null));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SendMessageHook(new ModMessageMessageComposer(user.userId, message, category.id));
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
onCloseClick();
|
||||||
|
}, [message, onCloseClick, selectedAction, selectedTopic, settings, topics, user.userId]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<NitroCardView className="nitro-mod-tools-user-action" simple={true}>
|
||||||
|
<NitroCardHeaderView headerText={'Mod Action: ' + (user ? user.username : '')} onCloseClick={ () => onCloseClick() } />
|
||||||
|
<NitroCardContentView className="text-black">
|
||||||
|
{ user &&
|
||||||
|
<>
|
||||||
|
<div className="form-group mb-2">
|
||||||
|
<select className="form-control form-control-sm" value={selectedTopic} onChange={event => setSelectedTopic(parseInt(event.target.value))}>
|
||||||
|
<option value={-1}>CFH Topic:</option>
|
||||||
|
{ topics.map( (topic,index) =>
|
||||||
|
{
|
||||||
|
return (<option key={index} value={index}>{LocalizeText('help.cfh.topic.' + topic.id)}</option>)
|
||||||
|
})}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="form-group mb-2">
|
||||||
|
<select className="form-control form-control-sm" value={selectedAction} onChange={event => setSelectedAction(parseInt(event.target.value))}>
|
||||||
|
<option value={-1}>Sanction type:</option>
|
||||||
|
{ actions.map( (action, index) =>
|
||||||
|
{
|
||||||
|
return (<option key={index} value={index}>{ action.name }</option>)
|
||||||
|
})}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="form-group mb-2">
|
||||||
|
<label>Optional message type, overrides default</label>
|
||||||
|
<textarea className="form-control" value={message} onChange={event => setMessage(event.target.value)}/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="form-group mb-2">
|
||||||
|
<div className="d-flex justify-content-between">
|
||||||
|
<button type="button" className="btn btn-danger w-100 me-2" onClick={ () => sendSanction()}>Sanction</button>
|
||||||
|
<button className="btn btn-success w-100" onClick={ () => sendDefaultSanction()}>Default Sanction</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
</NitroCardContentView>
|
||||||
|
</NitroCardView>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
import { ISelectedUser } from '../../../utils/ISelectedUser';
|
||||||
|
|
||||||
|
export interface ModToolsUserModActionViewProps
|
||||||
|
{
|
||||||
|
user: ISelectedUser;
|
||||||
|
onCloseClick: () => void;
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
.nitro-mod-tools-user-visits {
|
||||||
|
min-width: 300px;
|
||||||
|
|
||||||
|
.user-visits {
|
||||||
|
min-height: 200px;
|
||||||
|
|
||||||
|
.roomvisits-container {
|
||||||
|
div.room-visit {
|
||||||
|
border-bottom: 1px solid rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,69 @@
|
|||||||
|
import { GetRoomVisitsMessageComposer, RoomVisitsData, RoomVisitsEvent } from '@nitrots/nitro-renderer';
|
||||||
|
import { FC, useCallback, useEffect, useState } from 'react';
|
||||||
|
import { AutoSizer, List, ListRowProps, ListRowRenderer } from 'react-virtualized';
|
||||||
|
import { TryVisitRoom } from '../../../../../api';
|
||||||
|
import { CreateMessageHook, SendMessageHook } from '../../../../../hooks';
|
||||||
|
import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../../layout';
|
||||||
|
import { ModToolsUserRoomVisitsViewProps } from './ModToolsUserRoomVisitsView.types';
|
||||||
|
|
||||||
|
export const ModToolsUserRoomVisitsView: FC<ModToolsUserRoomVisitsViewProps> = props =>
|
||||||
|
{
|
||||||
|
const { userId = null, onCloseClick = null } = props;
|
||||||
|
|
||||||
|
const [roomVisitData, setRoomVisitData] = useState<RoomVisitsData>(null);
|
||||||
|
|
||||||
|
useEffect(() =>
|
||||||
|
{
|
||||||
|
SendMessageHook(new GetRoomVisitsMessageComposer(userId));
|
||||||
|
}, [userId]);
|
||||||
|
|
||||||
|
const onModtoolReceivedRoomsUserEvent = useCallback((event: RoomVisitsEvent) =>
|
||||||
|
{
|
||||||
|
const parser = event.getParser();
|
||||||
|
|
||||||
|
if(!parser || parser.data.userId !== userId) return;
|
||||||
|
|
||||||
|
setRoomVisitData(parser.data);
|
||||||
|
}, [userId]);
|
||||||
|
|
||||||
|
CreateMessageHook(RoomVisitsEvent, onModtoolReceivedRoomsUserEvent);
|
||||||
|
|
||||||
|
const RowRenderer: ListRowRenderer = (props: ListRowProps) =>
|
||||||
|
{
|
||||||
|
const item = roomVisitData.rooms[props.index];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={props.style} key={props.key} className="row room-visit">
|
||||||
|
<div className="col-auto text-center">{item.enterHour.toString().padStart(2, '0')}:{item.enterMinute.toString().padStart(2, '0')}</div>
|
||||||
|
<div className="col-7"><span className="fw-bold">Room: </span>{item.roomName}</div>
|
||||||
|
<button className="btn btn-sm btn-link col-sm-auto fw-bold" onClick={() => TryVisitRoom(item.roomId)}>Visit Room</button>
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<NitroCardView className="nitro-mod-tools-user-visits" simple={true}>
|
||||||
|
<NitroCardHeaderView headerText={'User Visits'} onCloseClick={ () => onCloseClick() } />
|
||||||
|
<NitroCardContentView className="text-black">
|
||||||
|
{roomVisitData &&
|
||||||
|
<div className="row h-100 w-100 user-visits">
|
||||||
|
<AutoSizer defaultWidth={400} defaultHeight={200}>
|
||||||
|
{({ height, width }) =>
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
<List
|
||||||
|
width={width}
|
||||||
|
height={height}
|
||||||
|
rowCount={roomVisitData.rooms.length}
|
||||||
|
rowHeight={30}
|
||||||
|
className={'roomvisits-container'}
|
||||||
|
rowRenderer={RowRenderer}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</AutoSizer>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</NitroCardContentView>
|
||||||
|
</NitroCardView>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
|
||||||
|
export interface ModToolsUserRoomVisitsViewProps
|
||||||
|
{
|
||||||
|
userId: number;
|
||||||
|
onCloseClick: () => void;
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
import { ISelectedUser } from '../../../utils/ISelectedUser';
|
||||||
|
|
||||||
|
export interface ModToolsSendUserMessageViewProps
|
||||||
|
{
|
||||||
|
user: ISelectedUser;
|
||||||
|
onCloseClick: () => void;
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
import { ModMessageMessageComposer } from '@nitrots/nitro-renderer';
|
||||||
|
import { FC, useCallback, useState } from 'react';
|
||||||
|
import { NotificationAlertEvent } from '../../../../../events';
|
||||||
|
import { dispatchUiEvent, SendMessageHook } from '../../../../../hooks';
|
||||||
|
import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../../layout';
|
||||||
|
import { ModToolsSendUserMessageViewProps } from './ModToolsSendUserMessage.types';
|
||||||
|
|
||||||
|
export const ModToolsSendUserMessageView: FC<ModToolsSendUserMessageViewProps> = props =>
|
||||||
|
{
|
||||||
|
const { user = null, onCloseClick = null } = props;
|
||||||
|
const [message, setMessage] = useState('');
|
||||||
|
|
||||||
|
|
||||||
|
const sendMessage = useCallback(() =>
|
||||||
|
{
|
||||||
|
if(message.trim().length === 0)
|
||||||
|
{
|
||||||
|
dispatchUiEvent(new NotificationAlertEvent(['Please write a message to user.'], null, null, null, 'Error', null));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SendMessageHook(new ModMessageMessageComposer(user.userId, message, -999));
|
||||||
|
|
||||||
|
onCloseClick();
|
||||||
|
}, [message, onCloseClick, user.userId]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<NitroCardView className="nitro-mod-tools-user-message" simple={true}>
|
||||||
|
<NitroCardHeaderView headerText={'Send Message'} onCloseClick={ () => onCloseClick() } />
|
||||||
|
<NitroCardContentView className="text-black">
|
||||||
|
{user && <>
|
||||||
|
<div>Message To: {user.username}</div>
|
||||||
|
<div className="form-group mb-2">
|
||||||
|
<textarea className="form-control" value={message} onChange={e => setMessage(e.target.value)}></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="form-group mb-2">
|
||||||
|
<button type="button" className="btn btn-primary" onClick={ () => sendMessage()}>Send message</button>
|
||||||
|
</div>
|
||||||
|
</>}
|
||||||
|
</NitroCardContentView>
|
||||||
|
</NitroCardView>
|
||||||
|
);
|
||||||
|
}
|
@ -60,7 +60,7 @@ export const NotificationCenterMessageHandler: FC<INotificationCenterMessageHand
|
|||||||
{
|
{
|
||||||
const parser = event.getParser();
|
const parser = event.getParser();
|
||||||
|
|
||||||
NotificationUtilities.handleModeratorMessage(parser.message, parser.link);
|
NotificationUtilities.handleModeratorMessage(parser.message, parser.url);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
CreateMessageHook(ModeratorMessageEvent, onModeratorMessageEvent);
|
CreateMessageHook(ModeratorMessageEvent, onModeratorMessageEvent);
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { FriendlyTime, HabboClubLevelEnum, UserCurrencyComposer, UserSubscriptionComposer } from '@nitrots/nitro-renderer';
|
import { FriendlyTime, HabboClubLevelEnum, UserCurrencyComposer, UserSubscriptionComposer } from '@nitrots/nitro-renderer';
|
||||||
import { FC, useCallback, useEffect, useMemo, useState } from 'react';
|
import { FC, useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { GetConfiguration, LocalizeText } from '../../api';
|
import { GetConfiguration, LocalizeText } from '../../api';
|
||||||
|
import { HelpEvent } from '../../events/help/HelpEvent';
|
||||||
import { UserSettingsUIEvent } from '../../events/user-settings/UserSettingsUIEvent';
|
import { UserSettingsUIEvent } from '../../events/user-settings/UserSettingsUIEvent';
|
||||||
import { dispatchUiEvent } from '../../hooks';
|
import { dispatchUiEvent } from '../../hooks';
|
||||||
import { SendMessageHook } from '../../hooks/messages/message-event';
|
import { SendMessageHook } from '../../hooks/messages/message-event';
|
||||||
@ -24,6 +25,11 @@ export const PurseView: FC<{}> = props =>
|
|||||||
dispatchUiEvent(new UserSettingsUIEvent(UserSettingsUIEvent.TOGGLE_USER_SETTINGS));
|
dispatchUiEvent(new UserSettingsUIEvent(UserSettingsUIEvent.TOGGLE_USER_SETTINGS));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const handleHelpCenterClick = useCallback(() =>
|
||||||
|
{
|
||||||
|
dispatchUiEvent(new HelpEvent(HelpEvent.TOGGLE_HELP_CENTER));
|
||||||
|
}, []);
|
||||||
|
|
||||||
const displayedCurrencies = useMemo(() =>
|
const displayedCurrencies = useMemo(() =>
|
||||||
{
|
{
|
||||||
return GetConfiguration<number[]>('system.currency.types', []);
|
return GetConfiguration<number[]>('system.currency.types', []);
|
||||||
@ -140,7 +146,7 @@ export const PurseView: FC<{}> = props =>
|
|||||||
</div>
|
</div>
|
||||||
<div className="col-2 px-0">
|
<div className="col-2 px-0">
|
||||||
<div className="d-flex flex-column nitro-purse-buttons h-100 justify-content-center">
|
<div className="d-flex flex-column nitro-purse-buttons h-100 justify-content-center">
|
||||||
<div className="nitro-purse-button text-white h-100 text-center d-flex align-items-center justify-content-center cursor-pointer"><i className="icon icon-help"/></div>
|
<div className="nitro-purse-button text-white h-100 text-center d-flex align-items-center justify-content-center cursor-pointer" onClick={ handleHelpCenterClick }><i className="icon icon-help"/></div>
|
||||||
<div className="nitro-purse-button text-white h-100 text-center d-flex align-items-center justify-content-center cursor-pointer" onClick={ handleUserSettingsClick } ><i className="fas fa-cogs"/></div>
|
<div className="nitro-purse-button text-white h-100 text-center d-flex align-items-center justify-content-center cursor-pointer" onClick={ handleUserSettingsClick } ><i className="fas fa-cogs"/></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -10,6 +10,14 @@
|
|||||||
padding-right: 10px;
|
padding-right: 10px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
|
@include media-breakpoint-down(sm) {
|
||||||
|
display: flex;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 70px;
|
||||||
|
left: calc(100% / 3);
|
||||||
|
width: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
&:before {
|
&:before {
|
||||||
content: "";
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { NitroEvent, RoomEngineTriggerWidgetEvent, RoomObjectVariable } from '@nitrots/nitro-renderer';
|
import { NitroEvent, RoomEngineTriggerWidgetEvent, RoomObjectVariable } from '@nitrots/nitro-renderer';
|
||||||
import { FC, useCallback, useState } from 'react';
|
import { FC, useCallback, useState } from 'react';
|
||||||
import { GetRoomEngine, RoomWidgetRoomObjectUpdateEvent } from '../../../../../api';
|
import { GetRoomEngine, RoomWidgetUpdateRoomObjectEvent } from '../../../../../api';
|
||||||
import { CreateEventDispatcherHook } from '../../../../../hooks/events/event-dispatcher.base';
|
import { CreateEventDispatcherHook } from '../../../../../hooks/events/event-dispatcher.base';
|
||||||
import { useRoomEngineEvent } from '../../../../../hooks/events/nitro/room/room-engine-event';
|
import { useRoomEngineEvent } from '../../../../../hooks/events/nitro/room/room-engine-event';
|
||||||
import { NitroLayoutTrophyView } from '../../../../../layout';
|
import { NitroLayoutTrophyView } from '../../../../../layout';
|
||||||
@ -9,6 +9,7 @@ import { FurnitureTrophyData } from './FurnitureTrophyData';
|
|||||||
|
|
||||||
export const FurnitureTrophyView: FC<{}> = props =>
|
export const FurnitureTrophyView: FC<{}> = props =>
|
||||||
{
|
{
|
||||||
|
|
||||||
const { eventDispatcher = null, widgetHandler = null } = useRoomContext();
|
const { eventDispatcher = null, widgetHandler = null } = useRoomContext();
|
||||||
const [ trophyData, setTrophyData ] = useState<FurnitureTrophyData>(null);
|
const [ trophyData, setTrophyData ] = useState<FurnitureTrophyData>(null);
|
||||||
|
|
||||||
@ -39,8 +40,8 @@ export const FurnitureTrophyView: FC<{}> = props =>
|
|||||||
setTrophyData(new FurnitureTrophyData(widgetEvent.objectId, widgetEvent.category, color, ownerName, trophyDate, trophyText));
|
setTrophyData(new FurnitureTrophyData(widgetEvent.objectId, widgetEvent.category, color, ownerName, trophyDate, trophyText));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
case RoomWidgetRoomObjectUpdateEvent.FURNI_REMOVED: {
|
case RoomWidgetUpdateRoomObjectEvent.FURNI_REMOVED: {
|
||||||
const widgetEvent = (event as RoomWidgetRoomObjectUpdateEvent);
|
const widgetEvent = (event as RoomWidgetUpdateRoomObjectEvent);
|
||||||
|
|
||||||
setTrophyData(prevState =>
|
setTrophyData(prevState =>
|
||||||
{
|
{
|
||||||
@ -54,7 +55,7 @@ export const FurnitureTrophyView: FC<{}> = props =>
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useRoomEngineEvent(RoomEngineTriggerWidgetEvent.REQUEST_TROPHY, onNitroEvent);
|
useRoomEngineEvent(RoomEngineTriggerWidgetEvent.REQUEST_TROPHY, onNitroEvent);
|
||||||
CreateEventDispatcherHook(RoomWidgetRoomObjectUpdateEvent.FURNI_REMOVED, widgetHandler.eventDispatcher, onNitroEvent);
|
CreateEventDispatcherHook(RoomWidgetUpdateRoomObjectEvent.FURNI_REMOVED, widgetHandler.eventDispatcher, onNitroEvent);
|
||||||
|
|
||||||
const processAction = useCallback((type: string, value: string = null) =>
|
const processAction = useCallback((type: string, value: string = null) =>
|
||||||
{
|
{
|
||||||
|
@ -3,6 +3,7 @@ import classNames from 'classnames';
|
|||||||
import { FC, useCallback, useState } from 'react';
|
import { FC, useCallback, useState } from 'react';
|
||||||
import { LocalizeText, RoomWidgetZoomToggleMessage } from '../../../../api';
|
import { LocalizeText, RoomWidgetZoomToggleMessage } from '../../../../api';
|
||||||
import { NavigatorEvent } from '../../../../events';
|
import { NavigatorEvent } from '../../../../events';
|
||||||
|
import { ChatHistoryEvent } from '../../../../events/chat-history/ChatHistoryEvent';
|
||||||
import { dispatchUiEvent } from '../../../../hooks/events';
|
import { dispatchUiEvent } from '../../../../hooks/events';
|
||||||
import { SendMessageHook } from '../../../../hooks/messages';
|
import { SendMessageHook } from '../../../../hooks/messages';
|
||||||
import { useRoomContext } from '../../context/RoomContext';
|
import { useRoomContext } from '../../context/RoomContext';
|
||||||
@ -27,6 +28,8 @@ export const RoomToolsWidgetView: FC<{}> = props =>
|
|||||||
setIsZoomedIn(value => !value);
|
setIsZoomedIn(value => !value);
|
||||||
return;
|
return;
|
||||||
case 'chat_history':
|
case 'chat_history':
|
||||||
|
dispatchUiEvent(new ChatHistoryEvent(ChatHistoryEvent.TOGGLE_CHAT_HISTORY));
|
||||||
|
//setIsExpanded(false); close this ??
|
||||||
return;
|
return;
|
||||||
case 'like_room':
|
case 'like_room':
|
||||||
if(isLiked) return;
|
if(isLiked) return;
|
||||||
|
@ -14,6 +14,11 @@
|
|||||||
|
|
||||||
#toolbar-chat-input-container {
|
#toolbar-chat-input-container {
|
||||||
margin: 0 10px;
|
margin: 0 10px;
|
||||||
|
|
||||||
|
@include media-breakpoint-down(sm) {
|
||||||
|
width: 0px;
|
||||||
|
height: 0px
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.navigation-items {
|
.navigation-items {
|
||||||
|
@ -29,6 +29,7 @@ export const ToolbarView: FC<ToolbarViewProps> = props =>
|
|||||||
const [ unseenInventoryCount, setUnseenInventoryCount ] = useState(0);
|
const [ unseenInventoryCount, setUnseenInventoryCount ] = useState(0);
|
||||||
const [ unseenAchievementCount, setUnseenAchievementCount ] = useState(0);
|
const [ unseenAchievementCount, setUnseenAchievementCount ] = useState(0);
|
||||||
|
|
||||||
|
const isMod = GetSessionDataManager().isModerator;
|
||||||
const unseenFriendListCount = 0;
|
const unseenFriendListCount = 0;
|
||||||
|
|
||||||
const onUserInfoEvent = useCallback((event: UserInfoEvent) =>
|
const onUserInfoEvent = useCallback((event: UserInfoEvent) =>
|
||||||
@ -200,9 +201,10 @@ export const ToolbarView: FC<ToolbarViewProps> = props =>
|
|||||||
<div className="navigation-item" onClick={ event => handleToolbarItemClick(ToolbarViewItems.CAMERA_ITEM) }>
|
<div className="navigation-item" onClick={ event => handleToolbarItemClick(ToolbarViewItems.CAMERA_ITEM) }>
|
||||||
<i className="icon icon-camera"></i>
|
<i className="icon icon-camera"></i>
|
||||||
</div>) }
|
</div>) }
|
||||||
<div className="navigation-item" onClick={ event => handleToolbarItemClick(ToolbarViewItems.MOD_TOOLS_ITEM) }>
|
{ isMod && (
|
||||||
|
<div className="navigation-item" onClick={ event => handleToolbarItemClick(ToolbarViewItems.MOD_TOOLS_ITEM) }>
|
||||||
<i className="icon icon-modtools"></i>
|
<i className="icon icon-modtools"></i>
|
||||||
</div>
|
</div>) }
|
||||||
</div>
|
</div>
|
||||||
<div id="toolbar-chat-input-container" className="d-flex align-items-center" />
|
<div id="toolbar-chat-input-container" className="d-flex align-items-center" />
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
Reference in New Issue
Block a user