This commit is contained in:
MyNameIsBatman 2021-12-04 14:43:06 -03:00
parent 78cd946335
commit 765ad79099
20 changed files with 526 additions and 61 deletions

View File

@ -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, theme = 'primary' } = props; const { headerText = null, onCloseClick = null, theme = 'primary', noCloseButton = false } = props;
const { simple = false } = useNitroCardContext(); const { simple = false } = useNitroCardContext();
const onMouseDown = useCallback((event: MouseEvent<HTMLDivElement>) => const onMouseDown = useCallback((event: MouseEvent<HTMLDivElement>) =>
@ -20,9 +20,9 @@ export const NitroCardHeaderView: FC<NitroCardHeaderViewProps> = props =>
<div className="row nitro-card-header overflow-hidden"> <div className="row nitro-card-header overflow-hidden">
<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="h5 text-shadow header-text">{ headerText }</div> <div className="h5 text-shadow header-text">{ headerText }</div>
<div className="position-absolute header-close" onMouseDownCapture={ onMouseDown } onClick={ onCloseClick }> { !noCloseButton && <div className="position-absolute header-close" onMouseDownCapture={ onMouseDown } onClick={ onCloseClick }>
<i className="fas fa-times" /> <i className="fas fa-times" />
</div> </div> }
</div> </div>
</div> </div>
</div> </div>

View File

@ -4,5 +4,6 @@ export interface NitroCardHeaderViewProps
{ {
headerText: string; headerText: string;
theme?: string; theme?: string;
noCloseButton?: boolean;
onCloseClick: (event: MouseEvent) => void; onCloseClick: (event: MouseEvent) => void;
} }

View File

@ -24,4 +24,8 @@
height: 24px; height: 24px;
background-image: url(../../assets/images/guide-tool/guide_tool_info_icon.png); background-image: url(../../assets/images/guide-tool/guide_tool_info_icon.png);
} }
@import './views/user-create-request/GuideToolUserCreateRequestView';
@import './views/ongoing/GuideToolOngoingView';
} }

View File

@ -1,19 +1,24 @@
import { GuideOnDutyStatusMessageEvent, GuideSessionAttachedMessageEvent, GuideSessionOnDutyUpdateMessageComposer, GuideSessionStartedMessageEvent, ILinkEventTracker, PerkAllowancesMessageEvent, PerkEnum } from '@nitrots/nitro-renderer'; import { GuideOnDutyStatusMessageEvent, GuideSessionAttachedMessageEvent, GuideSessionDetachedMessageEvent, GuideSessionEndedMessageEvent, GuideSessionInvitedToGuideRoomMessageEvent, GuideSessionMessageMessageEvent, GuideSessionOnDutyUpdateMessageComposer, GuideSessionPartnerIsTypingMessageEvent, GuideSessionStartedMessageEvent, ILinkEventTracker, PerkAllowancesMessageEvent, PerkEnum } from '@nitrots/nitro-renderer';
import { FC, useCallback, useEffect, useState } from 'react'; import { FC, useCallback, useEffect, useState } from 'react';
import { AddEventLinkTracker, GetConfiguration, LocalizeText, RemoveLinkEventTracker } from '../../api'; import { AddEventLinkTracker, GetConfiguration, GetSessionDataManager, LocalizeText, RemoveLinkEventTracker } from '../../api';
import { GuideToolEvent, NotificationAlertEvent } from '../../events'; import { GuideToolEvent, NotificationAlertEvent } from '../../events';
import { CreateMessageHook, dispatchUiEvent, SendMessageHook, useUiEvent } from '../../hooks'; import { CreateMessageHook, dispatchUiEvent, SendMessageHook, useUiEvent } from '../../hooks';
import { NitroCardHeaderView, NitroCardView } from '../../layout'; import { NitroCardHeaderView, NitroCardView } from '../../layout';
import { GuideSessionState } from './common'; import { GuideSessionState, GuideToolMessageGroup } from './common';
import { GuideToolMessage } from './common/GuideToolMessage';
import { GuideToolAcceptView } from './views/guide-accept/GuideToolAcceptView'; import { GuideToolAcceptView } from './views/guide-accept/GuideToolAcceptView';
import { GuideToolMenuView } from './views/guide-tool-menu/GuideToolMenuView'; import { GuideToolMenuView } from './views/guide-tool-menu/GuideToolMenuView';
import { GuideToolOngoingView } from './views/ongoing/GuideToolOngoingView'; import { GuideToolOngoingView } from './views/ongoing/GuideToolOngoingView';
import { GuildToolUserCreateRequestView } from './views/user-create-request/GuildToolUserCreateRequestView'; import { GuideToolUserCreateRequestView } from './views/user-create-request/GuideToolUserCreateRequestView';
import { GuideToolUserFeedbackView } from './views/user-feedback/GuideToolUserFeedbackView';
import { GuideToolUserPendingView } from './views/user-pending/GuideToolUserPendingView';
import { GuideToolUserThanksView } from './views/user-thanks/GuideToolUserThanksView';
export const GuideToolView: FC<{}> = props => export const GuideToolView: FC<{}> = props =>
{ {
const [ isVisible, setIsVisible ] = useState<boolean>(false); const [ isVisible, setIsVisible ] = useState<boolean>(false);
const [ headerText, setHeaderText ] = useState<string>(LocalizeText('guide.help.guide.tool.title')); const [ headerText, setHeaderText ] = useState<string>(LocalizeText('guide.help.guide.tool.title'));
const [ noCloseButton, setNoCloseButton ] = useState<boolean>(false);
const [ sessionState, setSessionState ] = useState<string>(GuideSessionState.GUIDE_TOOL_MENU); const [ sessionState, setSessionState ] = useState<string>(GuideSessionState.GUIDE_TOOL_MENU);
const [ isOnDuty, setIsOnDuty ] = useState<boolean>(false); const [ isOnDuty, setIsOnDuty ] = useState<boolean>(false);
@ -25,12 +30,58 @@ export const GuideToolView: FC<{}> = props =>
const [ guidesOnDuty, setGuidesOnDuty ] = useState<number>(0); const [ guidesOnDuty, setGuidesOnDuty ] = useState<number>(0);
const [ guardiansOnDuty, setGuardiansOnDuty ] = useState<number>(0); const [ guardiansOnDuty, setGuardiansOnDuty ] = useState<number>(0);
const [ userRequest, setUserRequest ] = useState<string>('');
const [ helpRequestDescription, setHelpRequestDescription ] = useState<string>(null); const [ helpRequestDescription, setHelpRequestDescription ] = useState<string>(null);
const [ helpRequestCountdown, setHelpRequestCountdown ] = useState<number>(0); const [ helpRequestAverageTime, setHelpRequestAverageTime ] = useState<number>(0);
const [ ongoingUserId, setOngoingUserId ] = useState<number>(0); const [ ongoingUserId, setOngoingUserId ] = useState<number>(0);
const [ ongoingUsername, setOngoingUsername ] = useState<string>(null); const [ ongoingUsername, setOngoingUsername ] = useState<string>(null);
const [ ongoingFigure, setOngoingFigure ] = useState<string>(null); const [ ongoingFigure, setOngoingFigure ] = useState<string>(null);
const [ ongoingIsTyping, setOngoingIsTyping ] = useState<boolean>(false);
const [ ongoingMessageGroups, setOngoingMessageGroups ] = useState<GuideToolMessageGroup[]>([]);
const updateSessionState = useCallback((newState: string, replacement?: string) =>
{
switch(newState)
{
case GuideSessionState.GUIDE_TOOL_MENU:
setHeaderText(LocalizeText('guide.help.guide.tool.title'));
setNoCloseButton(false);
break;
case GuideSessionState.GUIDE_ACCEPT:
setHeaderText(LocalizeText('guide.help.request.guide.accept.title'));
setNoCloseButton(true);
break;
case GuideSessionState.GUIDE_ONGOING:
setHeaderText(LocalizeText('guide.help.request.guide.ongoing.title', ['name'], [replacement]));
setNoCloseButton(true);
break;
case GuideSessionState.USER_CREATE:
setHeaderText(LocalizeText('guide.help.request.user.create.title'));
setNoCloseButton(false);
break;
case GuideSessionState.USER_PENDING:
setHeaderText(LocalizeText('guide.help.request.user.pending.title'));
setNoCloseButton(true);
break;
case GuideSessionState.USER_ONGOING:
setHeaderText(LocalizeText('guide.help.request.user.ongoing.title', ['name'], [replacement]));
setNoCloseButton(true);
break;
case GuideSessionState.USER_FEEDBACK:
setHeaderText(LocalizeText('guide.help.request.user.feedback.title'));
setNoCloseButton(true);
break;
case GuideSessionState.USER_THANKS:
setHeaderText(LocalizeText('guide.help.request.user.thanks.title'));
setNoCloseButton(false);
break;
}
setSessionState(newState);
setIsVisible(true);
}, []);
const onGuideToolEvent = useCallback((event: GuideToolEvent) => const onGuideToolEvent = useCallback((event: GuideToolEvent) =>
{ {
@ -46,12 +97,10 @@ export const GuideToolView: FC<{}> = props =>
setIsVisible(value => !value); setIsVisible(value => !value);
return; return;
case GuideToolEvent.CREATE_HELP_REQUEST: case GuideToolEvent.CREATE_HELP_REQUEST:
setSessionState(GuideSessionState.USER_CREATE); updateSessionState(GuideSessionState.USER_CREATE);
setHeaderText(LocalizeText('guide.help.request.user.create.title'));
setIsVisible(true);
return; return;
} }
}, []); }, [ updateSessionState ]);
useUiEvent(GuideToolEvent.SHOW_GUIDE_TOOL, onGuideToolEvent); useUiEvent(GuideToolEvent.SHOW_GUIDE_TOOL, onGuideToolEvent);
useUiEvent(GuideToolEvent.HIDE_GUIDE_TOOL, onGuideToolEvent); useUiEvent(GuideToolEvent.HIDE_GUIDE_TOOL, onGuideToolEvent);
@ -87,24 +136,14 @@ export const GuideToolView: FC<{}> = props =>
{ {
const parser = event.getParser(); const parser = event.getParser();
if(parser.asGuide)
{
if(!isOnDuty) return;
setSessionState(GuideSessionState.GUIDE_ACCEPT);
setHeaderText(LocalizeText('guide.help.request.guide.accept.title'));
setHelpRequestDescription(parser.helpRequestDescription); setHelpRequestDescription(parser.helpRequestDescription);
setHelpRequestCountdown(parser.roleSpecificWaitTime); setHelpRequestAverageTime(parser.roleSpecificWaitTime);
}
else
{
setSessionState(GuideSessionState.USER_PENDING);
setHeaderText(LocalizeText('guide.help.request.user.pending.title'));
setHelpRequestDescription(parser.helpRequestDescription);
}
setIsVisible(true); if(parser.asGuide && isOnDuty) updateSessionState(GuideSessionState.GUIDE_ACCEPT);
}, [ isOnDuty ]);
if(!parser.asGuide) updateSessionState(GuideSessionState.USER_PENDING);
}, [ isOnDuty, updateSessionState ]);
CreateMessageHook(GuideSessionAttachedMessageEvent, onGuideSessionAttachedMessageEvent); CreateMessageHook(GuideSessionAttachedMessageEvent, onGuideSessionAttachedMessageEvent);
@ -114,26 +153,113 @@ export const GuideToolView: FC<{}> = props =>
if(isOnDuty) if(isOnDuty)
{ {
setSessionState(GuideSessionState.GUIDE_ONGOING);
setHeaderText(LocalizeText('guide.help.request.guide.ongoing.title', ['name'], [parser.requesterName]));
setOngoingUserId(parser.requesterUserId); setOngoingUserId(parser.requesterUserId);
setOngoingUsername(parser.requesterName); setOngoingUsername(parser.requesterName);
setOngoingFigure(parser.requesterFigure); setOngoingFigure(parser.requesterFigure);
updateSessionState(GuideSessionState.GUIDE_ONGOING, parser.requesterName);
} }
else else
{ {
setSessionState(GuideSessionState.USER_ONGOING);
setHeaderText(LocalizeText('guide.help.request.user.ongoing.title', ['name'], [parser.guideName]));
setOngoingUserId(parser.guideUserId); setOngoingUserId(parser.guideUserId);
setOngoingUsername(parser.guideName); setOngoingUsername(parser.guideName);
setOngoingFigure(parser.guideFigure); setOngoingFigure(parser.guideFigure);
updateSessionState(GuideSessionState.USER_ONGOING, parser.guideName);
} }
}, [ isOnDuty, updateSessionState ]);
setIsVisible(true);
}, [ isOnDuty ]);
CreateMessageHook(GuideSessionStartedMessageEvent, onGuideSessionStartedMessageEvent); CreateMessageHook(GuideSessionStartedMessageEvent, onGuideSessionStartedMessageEvent);
const onGuideSessionPartnerIsTypingMessageEvent = useCallback((event: GuideSessionPartnerIsTypingMessageEvent) =>
{
const parser = event.getParser();
setOngoingIsTyping(parser.isTyping);
}, []);
CreateMessageHook(GuideSessionPartnerIsTypingMessageEvent, onGuideSessionPartnerIsTypingMessageEvent);
const onGuideSessionMessageMessageEvent = useCallback((event: GuideSessionMessageMessageEvent) =>
{
const parser = event.getParser();
const messageGroups = [...ongoingMessageGroups];
let lastGroup = messageGroups[messageGroups.length - 1];
if(!lastGroup || lastGroup.userId !== parser.senderId)
{
lastGroup = new GuideToolMessageGroup(parser.senderId);
messageGroups.push(lastGroup);
}
lastGroup.addChat(new GuideToolMessage(parser.chatMessage));
setOngoingMessageGroups(messageGroups);
}, [ ongoingMessageGroups ]);
CreateMessageHook(GuideSessionMessageMessageEvent, onGuideSessionMessageMessageEvent);
const onGuideSessionInvitedToGuideRoomMessageEvent = useCallback((event: GuideSessionInvitedToGuideRoomMessageEvent) =>
{
const parser = event.getParser();
const messageGroups = [...ongoingMessageGroups];
let lastGroup = messageGroups[messageGroups.length - 1];
const guideId = (isOnDuty ? GetSessionDataManager().userId : ongoingUserId);
if(!lastGroup || lastGroup.userId !== guideId)
{
lastGroup = new GuideToolMessageGroup(guideId);
messageGroups.push(lastGroup);
}
lastGroup.addChat(new GuideToolMessage(parser.roomName, parser.roomId));
setOngoingMessageGroups(messageGroups);
}, [isOnDuty, ongoingMessageGroups, ongoingUserId]);
CreateMessageHook(GuideSessionInvitedToGuideRoomMessageEvent, onGuideSessionInvitedToGuideRoomMessageEvent);
const onGuideSessionEndedMessageEvent = useCallback((event: GuideSessionEndedMessageEvent) =>
{
if(isOnDuty)
{
setOngoingUserId(0);
setOngoingUsername(null);
setOngoingFigure(null);
setOngoingIsTyping(false);
setOngoingMessageGroups([]);
updateSessionState(GuideSessionState.GUIDE_TOOL_MENU);
}
else
{
updateSessionState(GuideSessionState.USER_FEEDBACK);
}
}, [ isOnDuty, updateSessionState ]);
CreateMessageHook(GuideSessionEndedMessageEvent, onGuideSessionEndedMessageEvent);
const onGuideSessionDetachedMessageEvent = useCallback((event: GuideSessionDetachedMessageEvent) =>
{
setOngoingUserId(0);
setOngoingUsername(null);
setOngoingFigure(null);
setOngoingIsTyping(false);
setOngoingMessageGroups([]);
if(isOnDuty)
{
updateSessionState(GuideSessionState.GUIDE_TOOL_MENU);
}
else
{
updateSessionState(GuideSessionState.USER_THANKS);
}
}, [ isOnDuty, updateSessionState ]);
CreateMessageHook(GuideSessionDetachedMessageEvent, onGuideSessionDetachedMessageEvent);
const linkReceived = useCallback((url: string) => const linkReceived = useCallback((url: string) =>
{ {
const parts = url.split('/'); const parts = url.split('/');
@ -166,6 +292,8 @@ export const GuideToolView: FC<{}> = props =>
{ {
case 'close': case 'close':
setIsVisible(false); setIsVisible(false);
setUserRequest('');
setSessionState(GuideSessionState.GUIDE_TOOL_MENU);
return; return;
case 'toggle_duty': case 'toggle_duty':
if(!isHandlingBullyReports && !isHandlingGuideRequests && !isHandlingHelpRequests) if(!isHandlingBullyReports && !isHandlingGuideRequests && !isHandlingHelpRequests)
@ -192,7 +320,8 @@ export const GuideToolView: FC<{}> = props =>
return ( return (
<NitroCardView className="nitro-guide-tool" simple={ true }> <NitroCardView className="nitro-guide-tool" simple={ true }>
<NitroCardHeaderView headerText={ headerText } onCloseClick={ () => processAction('close') } /> <NitroCardHeaderView headerText={ headerText } onCloseClick={ () => processAction('close') } noCloseButton={ noCloseButton } />
{ sessionState === GuideSessionState.GUIDE_TOOL_MENU && { sessionState === GuideSessionState.GUIDE_TOOL_MENU &&
<GuideToolMenuView isOnDuty={ isOnDuty } <GuideToolMenuView isOnDuty={ isOnDuty }
isHandlingGuideRequests={ isHandlingGuideRequests } isHandlingGuideRequests={ isHandlingGuideRequests }
@ -206,11 +335,30 @@ export const GuideToolView: FC<{}> = props =>
guardiansOnDuty={ guardiansOnDuty } guardiansOnDuty={ guardiansOnDuty }
processAction={ processAction } processAction={ processAction }
/> } /> }
{ sessionState === GuideSessionState.GUIDE_ACCEPT && { sessionState === GuideSessionState.GUIDE_ACCEPT &&
<GuideToolAcceptView helpRequestDescription={ helpRequestDescription } helpRequestCountdown={ helpRequestCountdown } processAction={ processAction } /> } <GuideToolAcceptView helpRequestDescription={ helpRequestDescription } helpRequestAverageTime={ helpRequestAverageTime } /> }
{ [ GuideSessionState.GUIDE_ONGOING, GuideSessionState.USER_ONGOING ].includes(sessionState) && { [ GuideSessionState.GUIDE_ONGOING, GuideSessionState.USER_ONGOING ].includes(sessionState) &&
<GuideToolOngoingView isGuide={ isOnDuty } userId={ ongoingUserId } userName={ ongoingUsername } userFigure={ ongoingFigure } /> } <GuideToolOngoingView isGuide={ isOnDuty }
{ sessionState === GuideSessionState.USER_CREATE && <GuildToolUserCreateRequestView /> } userId={ ongoingUserId }
userName={ ongoingUsername }
userFigure={ ongoingFigure }
isTyping={ ongoingIsTyping }
messageGroups={ ongoingMessageGroups }
/> }
{ sessionState === GuideSessionState.USER_CREATE &&
<GuideToolUserCreateRequestView userRequest={ userRequest } setUserRequest={ setUserRequest } /> }
{ sessionState === GuideSessionState.USER_PENDING &&
<GuideToolUserPendingView helpRequestDescription={ helpRequestDescription } helpRequestAverageTime={ helpRequestAverageTime } /> }
{ sessionState === GuideSessionState.USER_FEEDBACK &&
<GuideToolUserFeedbackView userName={ ongoingUsername } /> }
{ sessionState === GuideSessionState.USER_THANKS &&
<GuideToolUserThanksView /> }
</NitroCardView> </NitroCardView>
); );
}; };

View File

@ -0,0 +1,21 @@
export class GuideToolMessage
{
private _message: string;
private _roomId: number;
constructor(message: string, roomId?: number)
{
this._message = message;
this._roomId = roomId;
}
public get message(): string
{
return this._message;
}
public get roomId(): number
{
return this._roomId;
}
}

View File

@ -0,0 +1,28 @@
import { GuideToolMessage } from './GuideToolMessage';
export class GuideToolMessageGroup
{
private _userId: number;
private _messages: GuideToolMessage[];
constructor(userId: number)
{
this._userId = userId;
this._messages = [];
}
public addChat(message: GuideToolMessage): void
{
this._messages.push(message);
}
public get userId(): number
{
return this._userId;
}
public get messages(): GuideToolMessage[]
{
return this._messages;
}
}

View File

@ -1 +1,2 @@
export * from './GuideSessionState'; export * from './GuideSessionState';
export * from './GuideToolMessageGroup';

View File

@ -7,7 +7,7 @@ import { GuideToolAcceptViewProps } from './GuideToolAcceptView.types';
export const GuideToolAcceptView: FC<GuideToolAcceptViewProps> = props => export const GuideToolAcceptView: FC<GuideToolAcceptViewProps> = props =>
{ {
const { helpRequestDescription = null, helpRequestCountdown = 0, processAction = null } = props; const { helpRequestDescription = null, helpRequestAverageTime = 0 } = props;
const answerRequest = useCallback((response: boolean) => const answerRequest = useCallback((response: boolean) =>
{ {

View File

@ -1,6 +1,5 @@
export interface GuideToolAcceptViewProps export interface GuideToolAcceptViewProps
{ {
helpRequestDescription: string; helpRequestDescription: string;
helpRequestCountdown: number; helpRequestAverageTime: number;
processAction: (action: string) => void;
} }

View File

@ -0,0 +1,50 @@
.chat-messages {
height: 200px;
min-height: 200px;
overflow-y: auto;
.message-avatar {
position: relative;
overflow: hidden;
width: 50px;
height: 50px;
.avatar-image {
position: absolute;
margin-left: -22px;
margin-top: -25px;
}
}
.messages-group-left {
position: relative;
&:before {
position: absolute;
content: ' ';
width: 0;
height: 0;
border-right: 8px solid rgba(var(--bs-light-rgb), var(--bs-bg-opacity)) !important;
border-top: 8px solid transparent;
border-bottom: 8px solid transparent;
top: 10px;
left: -8px;
}
}
.messages-group-right {
position: relative;
&:before {
position: absolute;
content: ' ';
width: 0;
height: 0;
border-left: 8px solid rgba(var(--bs-light-rgb), var(--bs-bg-opacity)) !important;
border-top: 8px solid transparent;
border-bottom: 8px solid transparent;
top: 10px;
right: -8px;
}
}
}

View File

@ -1,27 +1,111 @@
import { FC } from 'react'; import { GuideSessionGetRequesterRoomMessageComposer, GuideSessionInviteRequesterMessageComposer, GuideSessionRequesterRoomMessageEvent, GuideSessionResolvedMessageComposer } from '@nitrots/nitro-renderer';
import { LocalizeText } from '../../../../api'; import { GuideSessionMessageMessageComposer } from '@nitrots/nitro-renderer/src';
import { NitroCardContentView } from '../../../../layout'; import { FC, KeyboardEvent, useCallback, useState } from 'react';
import { GetSessionDataManager, LocalizeText, TryVisitRoom } from '../../../../api';
import { CreateMessageHook, SendMessageHook } from '../../../../hooks';
import { NitroCardContentView, NitroLayoutButton, NitroLayoutFlex } from '../../../../layout';
import { NitroLayoutBase } from '../../../../layout/base';
import { AvatarImageView } from '../../../shared/avatar-image/AvatarImageView';
import { GuideToolOngoingViewProps } from './GuideToolOngoingView.types'; import { GuideToolOngoingViewProps } from './GuideToolOngoingView.types';
export const GuideToolOngoingView: FC<GuideToolOngoingViewProps> = props => export const GuideToolOngoingView: FC<GuideToolOngoingViewProps> = props =>
{ {
const { isGuide = false, userId = 0, userName = null, userFigure = null } = props; const { isGuide = false, userId = 0, userName = null, userFigure = null, isTyping = false, messageGroups = [] } = props;
const [ messageText, setMessageText ] = useState<string>('');
const visit = useCallback(() =>
{
SendMessageHook(new GuideSessionGetRequesterRoomMessageComposer());
}, []);
const invite = useCallback(() =>
{
SendMessageHook(new GuideSessionInviteRequesterMessageComposer());
}, []);
const resolve = useCallback(() =>
{
SendMessageHook(new GuideSessionResolvedMessageComposer());
}, []);
const onGuideSessionRequesterRoomMessageEvent = useCallback((event: GuideSessionRequesterRoomMessageEvent) =>
{
const parser = event.getParser();
TryVisitRoom(parser.requesterRoomId);
}, []);
CreateMessageHook(GuideSessionRequesterRoomMessageEvent, onGuideSessionRequesterRoomMessageEvent);
const sendMessage = useCallback(() =>
{
if(!messageText || !messageText.length) return;
SendMessageHook(new GuideSessionMessageMessageComposer(messageText));
setMessageText('');
}, [ messageText ]);
const onKeyDown = useCallback((event: KeyboardEvent<HTMLInputElement>) =>
{
if(event.key !== 'Enter') return;
sendMessage();
}, [ sendMessage ]);
return ( return (
<NitroCardContentView className="text-black flex flex-column gap-2"> <NitroCardContentView className="p-0">
<div className="d-flex gap-2 align-items-center"> <div className="d-flex gap-2 align-items-center bg-secondary p-2 text-white">
{ isGuide && <button className="btn btn-primary btn-sm">{ LocalizeText('guide.help.request.guide.ongoing.visit.button') }</button> } { isGuide && <div className="btn-group">
{ isGuide && <button className="btn btn-primary btn-sm">{ LocalizeText('guide.help.request.guide.ongoing.invite.button') }</button> } <button className="btn btn-light btn-sm" onClick={ visit }>{ LocalizeText('guide.help.request.guide.ongoing.visit.button') }</button>
<button className="btn btn-light btn-sm" onClick={ invite }>{ LocalizeText('guide.help.request.guide.ongoing.invite.button') }</button>
</div> }
{ !isGuide && <div> { !isGuide && <div>
<div className="fw-bold">{ userName }</div> <div className="fw-bold">{ userName }</div>
<div>{ LocalizeText('guide.help.request.user.ongoing.guide.desc') }</div> <div>{ LocalizeText('guide.help.request.user.ongoing.guide.desc') }</div>
</div> } </div> }
<div className="ms-auto text-decoration-underline cursor-pointer text-nowrap">{ LocalizeText('guide.help.common.report.link') }</div> <div className="ms-auto text-decoration-underline cursor-pointer text-nowrap">{ LocalizeText('guide.help.common.report.link') }</div>
</div> </div>
<div className="p-2 d-flex flex-column gap-1">
<div className="text-black d-flex flex-column gap-2 p-2 chat-messages bg-muted rounded">
{ messageGroups.map((group, index) =>
{
return (
<NitroLayoutFlex className={ 'w-100 justify-content-' + (group.userId === 0 ? 'end' : 'start') } gap={ 2 }>
{ (group.userId === userId) &&
<NitroLayoutBase className="message-avatar flex-shrink-0">
<AvatarImageView figure={ userFigure } direction={ 2 } />
</NitroLayoutBase> }
<NitroLayoutBase className={ 'bg-light text-black border-radius mb-2 rounded py-1 px-2 messages-group-' + (group.userId !== userId ? 'right' : 'left') }>
{ group.messages.map((message, index) =>
{
return (
<NitroLayoutBase key={ index } className="text-break">
{ message.roomId > 0 ? LocalizeText('guide.help.request.user.ongoing.visit.guide.request.message', ['name', 'roomname'], [userName, message.message]) : message.message }
</NitroLayoutBase>
);
}) }
</NitroLayoutBase>
{ (group.userId !== userId) &&
<NitroLayoutBase className="message-avatar flex-shrink-0">
<AvatarImageView figure={ GetSessionDataManager().figure } direction={ 4 } />
</NitroLayoutBase> }
</NitroLayoutFlex>
);
}) }
</div>
{ isTyping && <div className="text-muted">{ LocalizeText('guide.help.common.typing') }</div> }
<NitroLayoutFlex gap={ 2 }>
<input type="text" className="form-control form-control-sm" placeholder={ LocalizeText('guide.help.request.guide.ongoing.input.empty', [ 'name' ], [ userName ]) } value={ messageText } onChange={ event => setMessageText(event.target.value) } onKeyDown={ onKeyDown } />
<NitroLayoutButton variant="success" size="sm" onClick={ sendMessage }>
{ LocalizeText('widgets.chatinput.say') }
</NitroLayoutButton>
</NitroLayoutFlex>
</div>
<div className="d-flex flex-column gap-2 p-2 pt-0">
<hr className="bg-dark m-0" /> <hr className="bg-dark m-0" />
<div>chat</div> <div className="btn btn-success" onClick={ resolve }>{ LocalizeText('guide.help.request.' + (isGuide ? 'guide' : 'user') + '.ongoing.close.link') }</div>
<hr className="bg-dark m-0" /> </div>
<div className="btn btn-success btn-sm">{ LocalizeText('guide.help.request.' + (isGuide ? 'guide' : 'user') + '.ongoing.close.link') }</div>
</NitroCardContentView> </NitroCardContentView>
); );
}; };

View File

@ -1,7 +1,11 @@
import { GuideToolMessageGroup } from '../../common';
export interface GuideToolOngoingViewProps export interface GuideToolOngoingViewProps
{ {
isGuide: boolean; isGuide: boolean;
userId: number; userId: number;
userName: string; userName: string;
userFigure: string; userFigure: string;
isTyping: boolean;
messageGroups: GuideToolMessageGroup[];
} }

View File

@ -0,0 +1,12 @@
.request-message {
width: 100%;
min-width: 100%;
max-width: 100%;
height: 90px;
min-height: 90px;
max-height: 90px;
border: none;
resize: none;
outline: none;
line-height: 17px;
}

View File

@ -0,0 +1,29 @@
import { GuideSessionCreateMessageComposer } from '@nitrots/nitro-renderer';
import { FC, useCallback, useState } from 'react';
import { LocalizeText } from '../../../../api';
import { SendMessageHook } from '../../../../hooks';
import { NitroCardContentView } from '../../../../layout';
import { GuideToolUserCreateRequestViewProps } from './GuideTooluserCreateRequestView.types';
const MIN_REQUEST_LENGTH: number = 15;
export const GuideToolUserCreateRequestView: FC<GuideToolUserCreateRequestViewProps> = props =>
{
const { userRequest = '', setUserRequest = null } = props;
const [ isPending, setIsPending ] = useState<boolean>(false);
const sendRequest = useCallback(() =>
{
setIsPending(true);
SendMessageHook(new GuideSessionCreateMessageComposer(1, userRequest));
}, [ userRequest ]);
return (
<NitroCardContentView className="text-black flex flex-column gap-2">
<div>{ LocalizeText('guide.help.request.user.create.help') }</div>
<textarea className="request-message" maxLength={ 140 } value={ userRequest } onChange={ (e) => setUserRequest(e.target.value) } placeholder={ LocalizeText('guide.help.request.user.create.input.help') }></textarea>
<button className="btn btn-success" disabled={ userRequest.length < MIN_REQUEST_LENGTH || isPending } onClick={ sendRequest }>{ LocalizeText('guide.help.request.user.create.input.button') }</button>
</NitroCardContentView>
);
};

View File

@ -0,0 +1,5 @@
export interface GuideToolUserCreateRequestViewProps
{
userRequest: string;
setUserRequest: (value: string) => void;
}

View File

@ -0,0 +1,40 @@
import { GuideSessionFeedbackMessageComposer } from '@nitrots/nitro-renderer';
import { FC, useCallback } from 'react';
import { LocalizeText } from '../../../../api';
import { SendMessageHook } from '../../../../hooks';
import { NitroCardContentView } from '../../../../layout';
import { GuideToolUserFeedbackViewProps } from './GuideToolUserFeedbackView.types';
export const GuideToolUserFeedbackView: FC<GuideToolUserFeedbackViewProps> = props =>
{
const { userName = null } = props;
const giveFeedback = useCallback((recommend: boolean) =>
{
SendMessageHook(new GuideSessionFeedbackMessageComposer(recommend));
}, []);
return (
<NitroCardContentView className="p-0">
<div className="d-flex gap-2 align-items-center bg-secondary p-2 text-white">
<div>
<div className="fw-bold">{ userName }</div>
<div>{ LocalizeText('guide.help.request.user.feedback.guide.desc') }</div>
</div>
<div className="ms-auto text-decoration-underline cursor-pointer text-nowrap">{ LocalizeText('guide.help.common.report.link') }</div>
</div>
<div className="text-black d-flex flex-column gap-2 p-2 h-100">
<div>
<div className="fw-bold">{ LocalizeText('guide.help.request.user.feedback.closed.title') }</div>
<div>{ LocalizeText('guide.help.request.user.feedback.closed.desc') }</div>
</div>
<hr className="bg-dark m-0 mt-auto" />
<div className="fw-bold text-center">{ LocalizeText('guide.help.request.user.feedback.question') }</div>
<div className="d-flex gap-2">
<div className="btn btn-success w-100" onClick={ () => giveFeedback(true) }>{ LocalizeText('guide.help.request.user.feedback.positive.button') }</div>
<div className="btn btn-danger w-100" onClick={ () => giveFeedback(false) }>{ LocalizeText('guide.help.request.user.feedback.negative.button') }</div>
</div>
</div>
</NitroCardContentView>
);
};

View File

@ -0,0 +1,4 @@
export interface GuideToolUserFeedbackViewProps
{
userName: string;
}

View File

@ -0,0 +1,32 @@
import { GuideSessionRequesterCancelsMessageComposer } from '@nitrots/nitro-renderer';
import { FC, useCallback } from 'react';
import { LocalizeText } from '../../../../api';
import { SendMessageHook } from '../../../../hooks';
import { NitroCardContentView } from '../../../../layout';
import { GuideToolUserPendingViewProps } from './GuideToolUserPendingView.types';
export const GuideToolUserPendingView: FC<GuideToolUserPendingViewProps> = props =>
{
const { helpRequestDescription = null, helpRequestAverageTime = 0 } = props;
const cancelRequest = useCallback(() =>
{
SendMessageHook(new GuideSessionRequesterCancelsMessageComposer());
}, []);
return (
<NitroCardContentView className="text-black flex flex-column gap-2">
<div className="duty-status py-2 px-3">
<div className="fw-bold">{ LocalizeText('guide.help.request.guide.accept.request.title') }</div>
<div className="text-muted">{ LocalizeText('guide.help.request.type.1') }</div>
<div className="text-wrap text-break">{ helpRequestDescription }</div>
</div>
<div>
<div className="fw-bold">{ LocalizeText('guide.help.request.user.pending.info.title') }</div>
<div>{ LocalizeText('guide.help.request.user.pending.info.message') }</div>
<div>{ LocalizeText('guide.help.request.user.pending.info.waiting', ['waitingtime'], [helpRequestAverageTime.toString()]) }</div>
</div>
<button className="btn btn-danger mt-auto" onClick={ cancelRequest }>{ LocalizeText('guide.help.request.user.pending.cancel.button') }</button>
</NitroCardContentView>
);
};

View File

@ -0,0 +1,5 @@
export interface GuideToolUserPendingViewProps
{
helpRequestDescription: string;
helpRequestAverageTime: number;
}

View File

@ -2,14 +2,12 @@ import { FC } from 'react';
import { LocalizeText } from '../../../../api'; import { LocalizeText } from '../../../../api';
import { NitroCardContentView } from '../../../../layout'; import { NitroCardContentView } from '../../../../layout';
export const GuildToolUserCreateRequestView: FC<{}> = props => export const GuideToolUserThanksView: FC<{}> = props =>
{ {
return ( return (
<NitroCardContentView className="text-black flex flex-column gap-2"> <NitroCardContentView className="text-black flex flex-column gap-2">
<div className="duty-status p-2 text-center"> <div className="fw-bold">{ LocalizeText('guide.help.request.user.thanks.info.title') }</div>
{ LocalizeText('guide.help.request.user.create.help') } <div>{ LocalizeText('guide.help.request.user.thanks.info.desc') }</div>
</div>
</NitroCardContentView> </NitroCardContentView>
); );
}; };