Merge branch 'dev' into @update/groups

This commit is contained in:
Bill 2022-02-23 01:59:21 -05:00
commit 0824cafb86
32 changed files with 960 additions and 59 deletions

View File

@ -28,7 +28,7 @@
"system.animation.fps": 24, "system.animation.fps": 24,
"system.limits.fps": false, "system.limits.fps": false,
"system.dispatcher.log": false, "system.dispatcher.log": false,
"system.packet.log": false, "system.packet.log": true,
"system.pong.manually": true, "system.pong.manually": true,
"system.pong.interval.ms": 20000, "system.pong.interval.ms": 20000,
"room.color.skip.transition": true, "room.color.skip.transition": true,

View File

@ -5,7 +5,9 @@
"thumbnails.url": "https://camera.com/thumbnail/%thumbnail%.png", "thumbnails.url": "https://camera.com/thumbnail/%thumbnail%.png",
"url.prefix": "https://website.com", "url.prefix": "https://website.com",
"floorplan.tile.url": "${asset.url}/floorplan-editor/tiles.json", "floorplan.tile.url": "${asset.url}/floorplan-editor/tiles.json",
"habbopages.url": "https://website.com/habbopages/", "habbopages.url": "${url.prefix}/",
"group.homepage.url": "${url.prefix}/groups/%groupid%/id",
"guide.help.alpha.groupid": 0,
"chat.viewer.height.percentage": 0.40, "chat.viewer.height.percentage": 0.40,
"widget.dimmer.colorwheel": false, "widget.dimmer.colorwheel": false,
"avatar.wardrobe.max.slots": 10, "avatar.wardrobe.max.slots": 10,

Binary file not shown.

After

Width:  |  Height:  |  Size: 537 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 B

View File

@ -1 +1 @@
export type ColorVariantType = 'primary' | 'success' | 'danger' | 'secondary' | 'link' | 'black' | 'white' | 'dark' | 'warning' | 'muted'; export type ColorVariantType = 'primary' | 'success' | 'danger' | 'secondary' | 'link' | 'black' | 'white' | 'dark' | 'warning' | 'muted' | 'light';

View File

@ -0,0 +1,81 @@
.nitro-guide-tool {
width: 250px;
.duty-switch {
width: 38px;
height: 21px;
cursor: pointer;
background-image: url(../../assets/images/guide-tool/guide_tool_duty_switch.png);
&.off {
background-position: 0px -22px;
}
}
.info-icon {
width: 23px;
height: 24px;
background-image: url(../../assets/images/guide-tool/guide_tool_info_icon.png);
}
.chat-messages {
.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;
}
}
}
.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,343 @@
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 { AddEventLinkTracker, GetConfiguration, GetSessionDataManager, LocalizeText, RemoveLinkEventTracker } from '../../api';
import { GuideToolEvent, NotificationAlertEvent } from '../../events';
import { CreateMessageHook, dispatchUiEvent, SendMessageHook, useUiEvent } from '../../hooks';
import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../layout';
import { GuideSessionState } from './common/GuideSessionState';
import { GuideToolMessage } from './common/GuideToolMessage';
import { GuideToolMessageGroup } from './common/GuideToolMessageGroup';
import { GuideToolAcceptView } from './views/GuideToolAcceptView';
import { GuideToolMenuView } from './views/GuideToolMenuView';
import { GuideToolOngoingView } from './views/GuideToolOngoingView';
import { GuideToolUserCreateRequestView } from './views/GuideToolUserCreateRequestView';
import { GuideToolUserFeedbackView } from './views/GuideToolUserFeedbackView';
import { GuideToolUserPendingView } from './views/GuideToolUserPendingView';
import { GuideToolUserThanksView } from './views/GuideToolUserThanksView';
export const GuideToolView: FC<{}> = props =>
{
const [ isVisible, setIsVisible ] = useState<boolean>(false);
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 [ isOnDuty, setIsOnDuty ] = useState<boolean>(false);
const [ isHandlingBullyReports, setIsHandlingBullyReports ] = useState<boolean>(false);
const [ isHandlingGuideRequests, setIsHandlingGuideRequests ] = useState<boolean>(false);
const [ isHandlingHelpRequests, setIsHandlingHelpRequests ] = useState<boolean>(false);
const [ helpersOnDuty, setHelpersOnDuty ] = useState<number>(0);
const [ guidesOnDuty, setGuidesOnDuty ] = useState<number>(0);
const [ guardiansOnDuty, setGuardiansOnDuty ] = useState<number>(0);
const [ userRequest, setUserRequest ] = useState<string>('');
const [ helpRequestDescription, setHelpRequestDescription ] = useState<string>(null);
const [ helpRequestAverageTime, setHelpRequestAverageTime ] = useState<number>(0);
const [ ongoingUserId, setOngoingUserId ] = useState<number>(0);
const [ ongoingUsername, setOngoingUsername ] = 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) =>
{
switch(event.type)
{
case GuideToolEvent.SHOW_GUIDE_TOOL:
setIsVisible(true);
return;
case GuideToolEvent.HIDE_GUIDE_TOOL:
setIsVisible(false);
return;
case GuideToolEvent.TOGGLE_GUIDE_TOOL:
setIsVisible(value => !value);
return;
case GuideToolEvent.CREATE_HELP_REQUEST:
updateSessionState(GuideSessionState.USER_CREATE);
return;
}
}, [ updateSessionState ]);
useUiEvent(GuideToolEvent.SHOW_GUIDE_TOOL, onGuideToolEvent);
useUiEvent(GuideToolEvent.HIDE_GUIDE_TOOL, onGuideToolEvent);
useUiEvent(GuideToolEvent.TOGGLE_GUIDE_TOOL, onGuideToolEvent);
useUiEvent(GuideToolEvent.CREATE_HELP_REQUEST, onGuideToolEvent);
const onPerkAllowancesMessageEvent = useCallback((event: PerkAllowancesMessageEvent) =>
{
const parser = event.getParser();
if(!parser.isAllowed(PerkEnum.USE_GUIDE_TOOL) && isOnDuty)
{
setIsOnDuty(false);
SendMessageHook(new GuideSessionOnDutyUpdateMessageComposer(false, false, false, false));
}
}, [ isOnDuty, setIsOnDuty ]);
CreateMessageHook(PerkAllowancesMessageEvent, onPerkAllowancesMessageEvent);
const onGuideOnDutyStatusMessageEvent = useCallback((event: GuideOnDutyStatusMessageEvent) =>
{
const parser = event.getParser();
setIsOnDuty(parser.onDuty);
setGuidesOnDuty(parser.guidesOnDuty);
setHelpersOnDuty(parser.helpersOnDuty);
setGuardiansOnDuty(parser.guardiansOnDuty);
}, [ setIsOnDuty, setHelpersOnDuty, setGuidesOnDuty, setGuardiansOnDuty ]);
CreateMessageHook(GuideOnDutyStatusMessageEvent, onGuideOnDutyStatusMessageEvent);
const onGuideSessionAttachedMessageEvent = useCallback((event: GuideSessionAttachedMessageEvent) =>
{
const parser = event.getParser();
setHelpRequestDescription(parser.helpRequestDescription);
setHelpRequestAverageTime(parser.roleSpecificWaitTime);
if(parser.asGuide && isOnDuty) updateSessionState(GuideSessionState.GUIDE_ACCEPT);
if(!parser.asGuide) updateSessionState(GuideSessionState.USER_PENDING);
}, [ isOnDuty, updateSessionState ]);
CreateMessageHook(GuideSessionAttachedMessageEvent, onGuideSessionAttachedMessageEvent);
const onGuideSessionStartedMessageEvent = useCallback((event: GuideSessionStartedMessageEvent) =>
{
const parser = event.getParser();
if(isOnDuty)
{
setOngoingUserId(parser.requesterUserId);
setOngoingUsername(parser.requesterName);
setOngoingFigure(parser.requesterFigure);
updateSessionState(GuideSessionState.GUIDE_ONGOING, parser.requesterName);
}
else
{
setOngoingUserId(parser.guideUserId);
setOngoingUsername(parser.guideName);
setOngoingFigure(parser.guideFigure);
updateSessionState(GuideSessionState.USER_ONGOING, parser.guideName);
}
}, [ isOnDuty, updateSessionState ]);
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 parts = url.split('/');
if(parts.length < 2) return;
switch(parts[1])
{
case 'tour':
//Create Tour Request
return;
}
}, []);
useEffect(() =>
{
const linkTracker: ILinkEventTracker = {
linkReceived,
eventUrlPrefix: 'help/'
};
AddEventLinkTracker(linkTracker);
return () => RemoveLinkEventTracker(linkTracker);
}, [ linkReceived ]);
const processAction = useCallback((action: string) =>
{
switch(action)
{
case 'close':
setIsVisible(false);
setUserRequest('');
setSessionState(GuideSessionState.GUIDE_TOOL_MENU);
return;
case 'toggle_duty':
if(!isHandlingBullyReports && !isHandlingGuideRequests && !isHandlingHelpRequests)
{
dispatchUiEvent(new NotificationAlertEvent([LocalizeText('guide.help.guide.tool.noqueueselected.message')], null, null, null, LocalizeText('guide.help.guide.tool.noqueueselected.caption'), null));
return;
}
setIsOnDuty(v =>
{
SendMessageHook(new GuideSessionOnDutyUpdateMessageComposer(!v, v ? false : isHandlingGuideRequests, v ? false : isHandlingHelpRequests, v ? false : isHandlingBullyReports));
return !v;
});
return;
case 'forum_link':
const url: string = GetConfiguration<string>('group.homepage.url', '').replace('%groupid%', GetConfiguration<string>('guide.help.alpha.groupid', '0'));
window.open(url);
return;
}
}, [isHandlingBullyReports, isHandlingGuideRequests, isHandlingHelpRequests]);
if(!isVisible) return null;
return (
<NitroCardView className="nitro-guide-tool" simple>
<NitroCardHeaderView headerText={ headerText } onCloseClick={ event => processAction('close') } noCloseButton={ noCloseButton } />
<NitroCardContentView className="text-black">
{ (sessionState === GuideSessionState.GUIDE_TOOL_MENU) &&
<GuideToolMenuView isOnDuty={ isOnDuty } isHandlingGuideRequests={ isHandlingGuideRequests } setIsHandlingGuideRequests={ setIsHandlingGuideRequests } isHandlingHelpRequests={ isHandlingHelpRequests } setIsHandlingHelpRequests={ setIsHandlingHelpRequests } isHandlingBullyReports={ isHandlingBullyReports } setIsHandlingBullyReports={ setIsHandlingBullyReports } guidesOnDuty={ guidesOnDuty } helpersOnDuty={ helpersOnDuty } guardiansOnDuty={ guardiansOnDuty } processAction={ processAction } /> }
{ (sessionState === GuideSessionState.GUIDE_ACCEPT) &&
<GuideToolAcceptView helpRequestDescription={ helpRequestDescription } helpRequestAverageTime={ helpRequestAverageTime } /> }
{ [ GuideSessionState.GUIDE_ONGOING, GuideSessionState.USER_ONGOING ].includes(sessionState) &&
<GuideToolOngoingView isGuide={ isOnDuty } 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 /> }
</NitroCardContentView>
</NitroCardView>
);
};

View File

@ -0,0 +1,21 @@
export class GuideSessionState
{
public static readonly NONE: string = 'NONE';
public static readonly ERROR: string = 'ERROR';
public static readonly REJECTED: string = 'REJECTED';
public static readonly USER_CREATE: string = 'USER_CREATE';
public static readonly USER_PENDING: string = 'USER_PENDING';
public static readonly USER_ONGOING: string = 'USER_ONGOING';
public static readonly USER_FEEDBACK: string = 'USER_FEEDBACK';
public static readonly USER_THANKS: string = 'USER_THANKS';
public static readonly USER_GUIDE_DISCONNECTED: string = 'USER_GUIDE_DISCONNECTED';
public static readonly GUIDE_TOOL_MENU: string = 'GUIDE_TOOL_MENU';
public static readonly GUIDE_ACCEPT: string = 'GUIDE_ACCEPT';
public static readonly GUIDE_ONGOING: string = 'GUIDE_ONGOING';
public static readonly GUIDE_CLOSED: string = 'GUIDE_CLOSED';
public static readonly GUARDIAN_CHAT_REVIEW_ACCEPT: string = 'GUARDIAN_CHAT_REVIEW_ACCEPT';
public static readonly GUARDIAN_CHAT_REVIEW_WAIT_FOR_VOTERS: string = 'GUARDIAN_CHAT_REVIEW_WAIT_FOR_VOTERS';
public static readonly GUARDIAN_CHAT_REVIEW_VOTE: string = 'GUARDIAN_CHAT_REVIEW_VOTE';
public static readonly GUARDIAN_CHAT_REVIEW_WAIT_FOR_RESULTS: string = 'GUARDIAN_CHAT_REVIEW_WAIT_FOR_RESULTS';
public static readonly GUARDIAN_CHAT_REVIEW_RESULTS: string = 'GUARDIAN_CHAT_REVIEW_RESULTS';
}

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

@ -0,0 +1,36 @@
import { GuideSessionGuideDecidesMessageComposer } from '@nitrots/nitro-renderer';
import { FC } from 'react';
import { LocalizeText } from '../../../api';
import { Button, Column, Text } from '../../../common';
import { SendMessageHook } from '../../../hooks';
interface GuideToolAcceptViewProps
{
helpRequestDescription: string;
helpRequestAverageTime: number;
}
export const GuideToolAcceptView: FC<GuideToolAcceptViewProps> = props =>
{
const { helpRequestDescription = null, helpRequestAverageTime = 0 } = props;
const answerRequest = (response: boolean) => SendMessageHook(new GuideSessionGuideDecidesMessageComposer(response));
return (
<Column>
<Column gap={ 0 } className="bg-muted p-2 rounded">
<Text bold>{ LocalizeText('guide.help.request.guide.accept.request.title') }</Text>
<Text variant="muted">{ LocalizeText('guide.help.request.type.1') }</Text>
<Text wrap textBreak>{ helpRequestDescription }</Text>
</Column>
<Column gap={ 1 }>
<Button variant="success" onClick={ event => answerRequest(true) }>
{ LocalizeText('guide.help.request.guide.accept.accept.button') }
</Button>
<Button variant="danger" onClick={ event => answerRequest(false) }>
{ LocalizeText('guide.help.request.guide.accept.skip.link') }
</Button>
</Column>
</Column>
);
};

View File

@ -0,0 +1,76 @@
import { FC } from 'react';
import { LocalizeText } from '../../../api';
import { Base, Button, Column, Flex, Text } from '../../../common';
interface GuideToolMenuViewProps
{
isOnDuty: boolean;
isHandlingGuideRequests: boolean;
setIsHandlingGuideRequests: (value: boolean) => void;
isHandlingHelpRequests: boolean;
setIsHandlingHelpRequests: (value: boolean) => void;
isHandlingBullyReports: boolean;
setIsHandlingBullyReports: (value: boolean) => void;
guidesOnDuty: number;
helpersOnDuty: number;
guardiansOnDuty: number;
processAction: (action: string) => void;
}
export const GuideToolMenuView: FC<GuideToolMenuViewProps> = props =>
{
const {
isOnDuty = false,
isHandlingGuideRequests = false,
setIsHandlingGuideRequests = null,
isHandlingHelpRequests = false,
setIsHandlingHelpRequests = null,
isHandlingBullyReports = false,
setIsHandlingBullyReports = null,
guidesOnDuty = 0,
helpersOnDuty = 0,
guardiansOnDuty = 0,
processAction = null
} = props;
return (
<Column>
<Flex alignItems="center" gap={ 2 } className="bg-muted p-2 rounded">
<Base className={ 'duty-switch' + (isOnDuty ? '' : ' off') } onClick={ event => processAction('toggle_duty') } />
<Column gap={ 0 }>
<Text bold>{ LocalizeText('guide.help.guide.tool.yourstatus') }</Text>
<Text>{ LocalizeText(`guide.help.guide.tool.duty.${ (isOnDuty ? 'on' : 'off') }`) }</Text>
</Column>
</Flex>
<Column gap={ 1 }>
<Text bold>{ LocalizeText('guide.help.guide.tool.tickettypeselection.caption') }</Text>
<Flex alignItems="center" gap={ 1 }>
<input className="form-check-input" disabled={ isOnDuty } type="checkbox" checked={ isHandlingGuideRequests } onChange={ event => setIsHandlingGuideRequests(event.target.checked) } />
<Text>{ LocalizeText('guide.help.guide.tool.tickettypeselection.guiderequests') }</Text>
</Flex>
<Flex alignItems="center" gap={ 1 }>
<input className="form-check-input" disabled={ isOnDuty } type="checkbox" checked={ isHandlingHelpRequests } onChange={ event => setIsHandlingHelpRequests(event.target.checked) } />
<Text>{ LocalizeText('guide.help.guide.tool.tickettypeselection.onlyhelprequests') }</Text>
</Flex>
<Flex alignItems="center" gap={ 1 }>
<input className="form-check-input" disabled={ isOnDuty } type="checkbox" checked={ isHandlingBullyReports } onChange={ event => setIsHandlingBullyReports(event.target.checked) } />
<Text>{ LocalizeText('guide.help.guide.tool.tickettypeselection.bullyreports') }</Text>
</Flex>
</Column>
<hr className="bg-dark m-0" />
<Flex center gap={ 2 }>
<Base className="info-icon" />
<Column gap={ 1 }>
<Base dangerouslySetInnerHTML={ { __html: LocalizeText('guide.help.guide.tool.guidesonduty', [ 'amount' ], [ guidesOnDuty.toString() ]) } } />
<Base dangerouslySetInnerHTML={ { __html: LocalizeText('guide.help.guide.tool.helpersonduty', [ 'amount' ], [ helpersOnDuty.toString() ]) } } />
<Base dangerouslySetInnerHTML={ { __html: LocalizeText('guide.help.guide.tool.guardiansonduty', [ 'amount' ], [ guardiansOnDuty.toString() ]) } } />
</Column>
</Flex>
<hr className="bg-dark m-0" />
<Flex justifyContent="between" gap={ 2 }>
<Button onClick={ event => processAction('forum_link') }>{ LocalizeText('guide.help.guide.tool.forum.link') }</Button>
<Button disabled>{ LocalizeText('guide.help.guide.tool.skill.link') }</Button>
</Flex>
</Column>
);
}

View File

@ -0,0 +1,127 @@
import { GuideSessionGetRequesterRoomMessageComposer, GuideSessionInviteRequesterMessageComposer, GuideSessionRequesterRoomMessageEvent, GuideSessionResolvedMessageComposer } from '@nitrots/nitro-renderer';
import { GuideSessionMessageMessageComposer } from '@nitrots/nitro-renderer/src';
import { FC, KeyboardEvent, useCallback, useState } from 'react';
import { GetSessionDataManager, LocalizeText, TryVisitRoom } from '../../../api';
import { Base, Button, ButtonGroup, Column, Flex, Text } from '../../../common';
import { CreateMessageHook, SendMessageHook } from '../../../hooks';
import { NitroLayoutBase } from '../../../layout/base';
import { AvatarImageView } from '../../../views/shared/avatar-image/AvatarImageView';
import { GuideToolMessageGroup } from '../common/GuideToolMessageGroup';
interface GuideToolOngoingViewProps
{
isGuide: boolean;
userId: number;
userName: string;
userFigure: string;
isTyping: boolean;
messageGroups: GuideToolMessageGroup[];
}
export const GuideToolOngoingView: FC<GuideToolOngoingViewProps> = 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 ]);
const isOwnChat = useCallback((userId: number) =>
{
return userId === GetSessionDataManager().userId;
}, []);
return (
<Column fullHeight>
<Flex alignItems="center" justifyContent="between" gap={ 1 } className="bg-muted p-2 rounded">
{ isGuide &&
<ButtonGroup>
<Button onClick={ visit }>{ LocalizeText('guide.help.request.guide.ongoing.visit.button') }</Button>
<Button disabled onClick={ invite }>{ LocalizeText('guide.help.request.guide.ongoing.invite.button') }</Button>
</ButtonGroup> }
{ !isGuide &&
<Column gap={ 0 }>
<Text bold>{ userName }</Text>
<Text>{ LocalizeText('guide.help.request.user.ongoing.guide.desc') }</Text>
</Column> }
<Button variant="danger" disabled>{ LocalizeText('guide.help.common.report.link') }</Button>
</Flex>
<Column fullHeight overflow="hidden" gap={ 1 } className="bg-muted rounded chat-messages p-2">
<Column overflow="auto">
{ messageGroups.map((group, index) =>
{
return (
<Flex fullWidth justifyContent={ isOwnChat(group.userId) ? 'end' : 'start' } gap={ 2 }>
<Base shrink className="message-avatar">
{ (!isOwnChat(group.userId)) &&
<AvatarImageView figure={ userFigure } direction={ 2 } /> }
</Base>
<Base className={ 'bg-light text-black border-radius mb-2 rounded py-1 px-2 messages-group-' + (isOwnChat(group.userId) ? 'right' : 'left') }>
<Text bold>
{ (isOwnChat(group.userId)) && GetSessionDataManager().userName }
{ (!isOwnChat(group.userId)) && userName }
</Text>
{ group.messages.map((chat, index) => <Base key={ index } className="text-break">{ chat.message }</Base>) }
</Base>
{ (isOwnChat(group.userId)) &&
<NitroLayoutBase className="message-avatar flex-shrink-0">
<AvatarImageView figure={ GetSessionDataManager().figure } direction={ 4 } />
</NitroLayoutBase> }
</Flex>
);
}) }
</Column>
</Column>
<Column gap={ 1 }>
<Flex gap={ 1 }>
<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 } />
<Button variant="success" onClick={ sendMessage }>
{ LocalizeText('widgets.chatinput.say') }
</Button>
</Flex>
{ isTyping &&
<Text variant="muted">{ LocalizeText('guide.help.common.typing') }</Text> }
</Column>
<Button fullWidth variant="success" onClick={ resolve }>
{ LocalizeText('guide.help.request.' + (isGuide ? 'guide' : 'user') + '.ongoing.close.link') }
</Button>
</Column>
);
};

View File

@ -0,0 +1,33 @@
import { GuideSessionCreateMessageComposer } from '@nitrots/nitro-renderer';
import { FC, useState } from 'react';
import { LocalizeText } from '../../../api';
import { Button, Column, Text } from '../../../common';
import { SendMessageHook } from '../../../hooks';
interface GuideToolUserCreateRequestViewProps
{
userRequest: string;
setUserRequest: (value: string) => void;
}
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 = () =>
{
setIsPending(true);
SendMessageHook(new GuideSessionCreateMessageComposer(1, userRequest));
}
return (
<Column>
<Text>{ LocalizeText('guide.help.request.user.create.help') }</Text>
<textarea className="request-message" maxLength={ 140 } value={ userRequest } onChange={ event => setUserRequest(event.target.value) } placeholder={ LocalizeText('guide.help.request.user.create.input.help') }></textarea>
<Button fullWidth variant="success" disabled={ (userRequest.length < MIN_REQUEST_LENGTH) || isPending } onClick={ sendRequest }>{ LocalizeText('guide.help.request.user.create.input.button') }</Button>
</Column>
);
};

View File

@ -0,0 +1,44 @@
import { GuideSessionFeedbackMessageComposer } from '@nitrots/nitro-renderer';
import { FC, useCallback } from 'react';
import { LocalizeText } from '../../../api';
import { Button, Column, Flex, Text } from '../../../common';
import { SendMessageHook } from '../../../hooks';
interface GuideToolUserFeedbackViewProps
{
userName: string;
}
export const GuideToolUserFeedbackView: FC<GuideToolUserFeedbackViewProps> = props =>
{
const { userName = null } = props;
const giveFeedback = useCallback((recommend: boolean) =>
{
SendMessageHook(new GuideSessionFeedbackMessageComposer(recommend));
}, []);
return (
<Column>
<Flex justifyContent="between" gap={ 1 } className="bg-muted p-2 rounded">
<Column gap={ 0 }>
<Text bold>{ userName }</Text>
<Text>{ LocalizeText('guide.help.request.user.feedback.guide.desc') }</Text>
</Column>
<Button variant="danger" disabled>{ LocalizeText('guide.help.common.report.link') }</Button>
</Flex>
<Column gap={ 1 }>
<Text bold>{ LocalizeText('guide.help.request.user.feedback.closed.title') }</Text>
<Text>{ LocalizeText('guide.help.request.user.feedback.closed.desc') }</Text>
</Column>
<hr className="bg-dark m-0 mt-auto" />
<Column>
<Text center bold>{ LocalizeText('guide.help.request.user.feedback.question') }</Text>
<Flex gap={ 1 }>
<Button fullWidth variant="success" onClick={ event => giveFeedback(true) }>{ LocalizeText('guide.help.request.user.feedback.positive.button') }</Button>
<Button fullWidth variant="danger" onClick={ event => giveFeedback(false) }>{ LocalizeText('guide.help.request.user.feedback.negative.button') }</Button>
</Flex>
</Column>
</Column>
);
};

View File

@ -0,0 +1,34 @@
import { GuideSessionRequesterCancelsMessageComposer } from '@nitrots/nitro-renderer';
import { FC } from 'react';
import { LocalizeText } from '../../../api';
import { Button, Column, Text } from '../../../common';
import { SendMessageHook } from '../../../hooks';
interface GuideToolUserPendingViewProps
{
helpRequestDescription: string;
helpRequestAverageTime: number;
}
export const GuideToolUserPendingView: FC<GuideToolUserPendingViewProps> = props =>
{
const { helpRequestDescription = null, helpRequestAverageTime = 0 } = props;
const cancelRequest = () => SendMessageHook(new GuideSessionRequesterCancelsMessageComposer());
return (
<Column>
<Column gap={ 0 } className="bg-muted rounded p-2">
<Text bold>{ LocalizeText('guide.help.request.guide.accept.request.title') }</Text>
<Text variant="muted">{ LocalizeText('guide.help.request.type.1') }</Text>
<Text wrap textBreak>{ helpRequestDescription }</Text>
</Column>
<Column gap={ 1 }>
<Text bold>{ LocalizeText('guide.help.request.user.pending.info.title') }</Text>
<Text>{ LocalizeText('guide.help.request.user.pending.info.message') }</Text>
<Text>{ LocalizeText('guide.help.request.user.pending.info.waiting', [ 'waitingtime' ], [ helpRequestAverageTime.toString() ]) }</Text>
</Column>
<Button variant="danger" onClick={ cancelRequest }>{ LocalizeText('guide.help.request.user.pending.cancel.button') }</Button>
</Column>
);
};

View File

@ -0,0 +1,13 @@
import { FC } from 'react';
import { LocalizeText } from '../../../api';
import { Column, Text } from '../../../common';
export const GuideToolUserThanksView: FC<{}> = props =>
{
return (
<Column gap={ 1 }>
<Text bold>{ LocalizeText('guide.help.request.user.thanks.info.title') }</Text>
<Text>{ LocalizeText('guide.help.request.user.thanks.info.desc') }</Text>
</Column>
);
};

View File

@ -4,7 +4,8 @@ import { LocalizeText } from '../../../api';
import { Button } from '../../../common/Button'; import { Button } from '../../../common/Button';
import { Column } from '../../../common/Column'; import { Column } from '../../../common/Column';
import { Text } from '../../../common/Text'; import { Text } from '../../../common/Text';
import { SendMessageHook } from '../../../hooks'; import { GuideToolEvent } from '../../../events/guide-tool/GuideToolEvent';
import { dispatchUiEvent, SendMessageHook } from '../../../hooks';
import { useHelpContext } from '../HelpContext'; import { useHelpContext } from '../HelpContext';
export const HelpIndexView: FC<{}> = props => export const HelpIndexView: FC<{}> = props =>
@ -23,6 +24,11 @@ export const HelpIndexView: FC<{}> = props =>
SendMessageHook(new GetCfhStatusMessageComposer(false)); SendMessageHook(new GetCfhStatusMessageComposer(false));
}, []); }, []);
const onNewHelpRequestClick = useCallback(() =>
{
dispatchUiEvent(new GuideToolEvent(GuideToolEvent.CREATE_HELP_REQUEST));
}, []);
return ( return (
<> <>
<Column gap={ 1 }> <Column gap={ 1 }>
@ -31,7 +37,7 @@ export const HelpIndexView: FC<{}> = props =>
</Column> </Column>
<Column gap={ 1 }> <Column gap={ 1 }>
<Button onClick={ onReportClick }>{ LocalizeText('help.main.bully.subtitle') }</Button> <Button onClick={ onReportClick }>{ LocalizeText('help.main.bully.subtitle') }</Button>
<Button disabled={ true }>{ LocalizeText('help.main.help.title') }</Button> <Button onClick={ onNewHelpRequestClick }>{ LocalizeText('help.main.help.title') }</Button>
<Button disabled={ true }>{ LocalizeText('help.main.self.tips.title') }</Button> <Button disabled={ true }>{ LocalizeText('help.main.self.tips.title') }</Button>
<Button variant="link" className="text-black" onClick={ onRequestMySanctionStatusClick }>{ LocalizeText('help.main.my.sanction.status') }</Button> <Button variant="link" className="text-black" onClick={ onRequestMySanctionStatusClick }>{ LocalizeText('help.main.my.sanction.status') }</Button>
</Column> </Column>

View File

@ -4,6 +4,7 @@
@import './catalog/CatalogView'; @import './catalog/CatalogView';
@import './chat-history/ChatHistoryView'; @import './chat-history/ChatHistoryView';
@import './groups/GroupView'; @import './groups/GroupView';
@import './guide-tool/GuideToolView';
@import './help/HelpView'; @import './help/HelpView';
@import './inventory/InventoryView'; @import './inventory/InventoryView';
@import './loading/LoadingView'; @import './loading/LoadingView';

View File

@ -16,6 +16,7 @@ import { CameraWidgetView } from '../camera/CameraWidgetView';
import { CatalogView } from '../catalog/CatalogView'; import { CatalogView } from '../catalog/CatalogView';
import { ChatHistoryView } from '../chat-history/ChatHistoryView'; import { ChatHistoryView } from '../chat-history/ChatHistoryView';
import { GroupsView } from '../groups/GroupsView'; import { GroupsView } from '../groups/GroupsView';
import { GuideToolView } from '../guide-tool/GuideToolView';
import { HelpView } from '../help/HelpView'; import { HelpView } from '../help/HelpView';
import { InventoryView } from '../inventory/InventoryView'; import { InventoryView } from '../inventory/InventoryView';
import { ModToolsView } from '../mod-tools/ModToolsView'; import { ModToolsView } from '../mod-tools/ModToolsView';
@ -116,6 +117,7 @@ export const MainView: FC<{}> = props =>
<HelpView /> <HelpView />
<FloorplanEditorView /> <FloorplanEditorView />
<NitropediaView /> <NitropediaView />
<GuideToolView />
<HcCenterView /> <HcCenterView />
<CampaignView /> <CampaignView />
</Base> </Base>

View File

@ -65,57 +65,59 @@ export const NavigatorRoomCreatorView: FC<{}> = props =>
return ( return (
<Column overflow="hidden"> <Column overflow="hidden">
<Grid fullHeight={ false }> <Grid overflow="hidden">
<Column size={ 6 } gap={ 1 }> <Column size={ 6 } gap={ 1 } overflow="auto">
<Text>{ LocalizeText('navigator.createroom.roomnameinfo') }</Text> <Column gap={ 1 }>
<input type="text" className="form-control form-control-sm" onChange={ event => setName(event.target.value) } /> <Text>{ LocalizeText('navigator.createroom.roomnameinfo') }</Text>
<input type="text" className="form-control form-control-sm" onChange={ event => setName(event.target.value) } />
</Column>
<Column gap={ 1 }>
<Text>{ LocalizeText('navigator.category') }</Text>
<select className="form-select form-select-sm" onChange={ event => setCategory(Number(event.target.value)) }>
{ categories && categories.map(category =>
{
return <option key={ category.id } value={ category.id }>{ LocalizeText(category.name) }</option>
}) }
</select>
</Column>
<Column gap={ 1 }>
<Text>{ LocalizeText('navigator.maxvisitors') }</Text>
<select className="form-select form-select-sm" onChange={ event => setVisitorsCount(Number(event.target.value)) }>
{ maxVisitorsList && maxVisitorsList.map(value =>
{
return <option key={ value } value={ value }>{ value }</option>
}) }
</select>
</Column>
<Column gap={ 1 }>
<Text>{ LocalizeText('navigator.tradesettings') }</Text>
<select className="form-select form-select-sm" onChange={ event => setTradesSetting(Number(event.target.value)) }>
<option value="0">{ LocalizeText('navigator.roomsettings.trade_not_allowed') }</option>
<option value="1">{ LocalizeText('navigator.roomsettings.trade_not_with_Controller') }</option>
<option value="2">{ LocalizeText('navigator.roomsettings.trade_allowed') }</option>
</select>
</Column>
<Column gap={ 1 }>
<Text>{ LocalizeText('navigator.createroom.roomdescinfo') }</Text>
<input type="text" className="form-control form-control-sm" onChange={ event => setDescription(event.target.value) } />
</Column>
</Column> </Column>
<Column size={ 6 } gap={ 1 }> <Column size={ 6 } gap={ 1 } overflow="auto">
<Text>{ LocalizeText('navigator.category') }</Text> {
<select className="form-select form-select-sm" onChange={ event => setCategory(Number(event.target.value)) }> RoomModels.map(model =>
{ categories && categories.map(category =>
{ {
return <option key={ category.id } value={ category.id }>{ LocalizeText(category.name) }</option> return (<LayoutGridItem fullHeight key={ model.name } onClick={ () => selectModel(model) } itemActive={ (selectedModelName === model.name) } overflow="unset" gap={ 0 } className="p-1" disabled={ (GetSessionDataManager().clubLevel < model.clubLevel) }>
}) } <Flex fullHeight center overflow="hidden">
</select> <img alt="" src={ getRoomModelImage(model.name) } />
</Column> </Flex>
<Column size={ 6 } gap={ 1 }> <Text bold>{ model.tileSize } { LocalizeText('navigator.createroom.tilesize') }</Text>
<Text>{ LocalizeText('navigator.maxvisitors') }</Text> { model.clubLevel > HabboClubLevelEnum.NO_CLUB && <CurrencyIcon position="absolute" className="top-1 end-1" type="hc" /> }
<select className="form-select form-select-sm" onChange={ event => setVisitorsCount(Number(event.target.value)) }> </LayoutGridItem>);
{ maxVisitorsList && maxVisitorsList.map(value => })
{ }
return <option key={ value } value={ value }>{ value }</option>
}) }
</select>
</Column>
<Column size={ 6 } gap={ 1 }>
<Text>{ LocalizeText('navigator.tradesettings') }</Text>
<select className="form-select form-select-sm" onChange={ event => setTradesSetting(Number(event.target.value)) }>
<option value="0">{ LocalizeText('navigator.roomsettings.trade_not_allowed') }</option>
<option value="1">{ LocalizeText('navigator.roomsettings.trade_not_with_Controller') }</option>
<option value="2">{ LocalizeText('navigator.roomsettings.trade_allowed') }</option>
</select>
</Column> </Column>
</Grid> </Grid>
<Column gap={ 1 }> <Button fullWidth variant="success" onClick={ createRoom } disabled={ (!name || (name.length < 3)) }>{ LocalizeText('navigator.createroom.create') }</Button>
<Text>{ LocalizeText('navigator.createroom.roomdescinfo') }</Text>
<input type="text" className="form-control form-control-sm" onChange={ event => setDescription(event.target.value) } />
</Column>
<Flex overflow="auto" gap={ 1 }>
{
RoomModels.map(model =>
{
return (<LayoutGridItem fullHeight key={ model.name } onClick={ () => selectModel(model) } itemActive={ (selectedModelName === model.name) } overflow="unset" gap={ 0 } className="p-1" disabled={ (GetSessionDataManager().clubLevel < model.clubLevel) }>
<Flex fullHeight center overflow="hidden">
<img alt="" src={ getRoomModelImage(model.name) } />
</Flex>
<Text bold>{ model.tileSize } { LocalizeText('navigator.createroom.tilesize') }</Text>
{ model.clubLevel > HabboClubLevelEnum.NO_CLUB && <CurrencyIcon position="absolute" className="top-1 end-1" type="hc" /> }
</LayoutGridItem>);
})
}
</Flex>
<Button variant="success" onClick={ createRoom } disabled={ (!name || (name.length < 3)) }>{ LocalizeText('navigator.createroom.create') }</Button>
</Column> </Column>
); );
} }

View File

@ -7,13 +7,14 @@ import { ToolbarViewItems } from './common/ToolbarViewItems';
export interface ToolbarMeViewProps export interface ToolbarMeViewProps
{ {
useGuideTool: boolean;
unseenAchievementCount: number; unseenAchievementCount: number;
handleToolbarItemClick: (item: string) => void; handleToolbarItemClick: (item: string) => void;
} }
export const ToolbarMeView: FC<ToolbarMeViewProps> = props => export const ToolbarMeView: FC<ToolbarMeViewProps> = props =>
{ {
const { unseenAchievementCount = 0, handleToolbarItemClick = null } = props; const { useGuideTool = false, unseenAchievementCount = 0, handleToolbarItemClick = null } = props;
useEffect(() => useEffect(() =>
{ {
@ -26,6 +27,8 @@ export const ToolbarMeView: FC<ToolbarMeViewProps> = props =>
return ( return (
<Flex alignItems="center" className="nitro-toolbar-me p-2" gap={ 2 }> <Flex alignItems="center" className="nitro-toolbar-me p-2" gap={ 2 }>
{ useGuideTool &&
<Base pointer className="navigation-item icon icon-me-helper-tool" onClick={ () => handleToolbarItemClick(ToolbarViewItems.GUIDE_TOOL_ITEM) } /> }
<Base pointer className="navigation-item icon icon-me-achievements" onClick={ () => handleToolbarItemClick(ToolbarViewItems.ACHIEVEMENTS_ITEM) }> <Base pointer className="navigation-item icon icon-me-achievements" onClick={ () => handleToolbarItemClick(ToolbarViewItems.ACHIEVEMENTS_ITEM) }>
{ (unseenAchievementCount > 0) && { (unseenAchievementCount > 0) &&
<ItemCountView count={ unseenAchievementCount } /> } <ItemCountView count={ unseenAchievementCount } /> }

View File

@ -1,8 +1,8 @@
import { Dispose, DropBounce, EaseOut, FigureUpdateEvent, JumpBy, Motions, NitroToolbarAnimateIconEvent, Queue, UserInfoDataParser, UserInfoEvent, Wait } from '@nitrots/nitro-renderer'; import { Dispose, DropBounce, EaseOut, FigureUpdateEvent, JumpBy, Motions, NitroToolbarAnimateIconEvent, PerkAllowancesMessageEvent, PerkEnum, Queue, UserInfoDataParser, UserInfoEvent, Wait } from '@nitrots/nitro-renderer';
import { FC, useCallback, useState } from 'react'; import { FC, useCallback, useState } from 'react';
import { CreateLinkEvent, GetRoomSession, GetRoomSessionManager, GetSessionDataManager, GetUserProfile, GoToDesktop, OpenMessengerChat } from '../../api'; import { CreateLinkEvent, GetRoomSession, GetRoomSessionManager, GetSessionDataManager, GetUserProfile, GoToDesktop, OpenMessengerChat } from '../../api';
import { Base, Flex } from '../../common'; import { Base, Flex } from '../../common';
import { AvatarEditorEvent, FriendsEvent, FriendsMessengerIconEvent, FriendsRequestCountEvent, InventoryEvent, NavigatorEvent, RoomWidgetCameraEvent } from '../../events'; import { AvatarEditorEvent, FriendsEvent, FriendsMessengerIconEvent, FriendsRequestCountEvent, GuideToolEvent, InventoryEvent, NavigatorEvent, RoomWidgetCameraEvent } from '../../events';
import { AchievementsUIEvent, AchievementsUIUnseenCountEvent } from '../../events/achievements'; import { AchievementsUIEvent, AchievementsUIUnseenCountEvent } from '../../events/achievements';
import { UnseenItemTrackerUpdateEvent } from '../../events/inventory/UnseenItemTrackerUpdateEvent'; import { UnseenItemTrackerUpdateEvent } from '../../events/inventory/UnseenItemTrackerUpdateEvent';
import { ModToolsEvent } from '../../events/mod-tools/ModToolsEvent'; import { ModToolsEvent } from '../../events/mod-tools/ModToolsEvent';
@ -32,6 +32,7 @@ export const ToolbarView: FC<ToolbarViewProps> = props =>
const [ userInfo, setUserInfo ] = useState<UserInfoDataParser>(null); const [ userInfo, setUserInfo ] = useState<UserInfoDataParser>(null);
const [ userFigure, setUserFigure ] = useState<string>(null); const [ userFigure, setUserFigure ] = useState<string>(null);
const [ isMeExpanded, setMeExpanded ] = useState(false); const [ isMeExpanded, setMeExpanded ] = useState(false);
const [ useGuideTool, setUseGuideTool ] = useState(false);
const [ chatIconType, setChatIconType ] = useState(CHAT_ICON_HIDDEN); const [ chatIconType, setChatIconType ] = useState(CHAT_ICON_HIDDEN);
const [ unseenInventoryCount, setUnseenInventoryCount ] = useState(0); const [ unseenInventoryCount, setUnseenInventoryCount ] = useState(0);
const [ unseenAchievementCount, setUnseenAchievementCount ] = useState(0); const [ unseenAchievementCount, setUnseenAchievementCount ] = useState(0);
@ -60,6 +61,15 @@ export const ToolbarView: FC<ToolbarViewProps> = props =>
CreateMessageHook(FigureUpdateEvent, onUserFigureEvent); CreateMessageHook(FigureUpdateEvent, onUserFigureEvent);
const onPerkAllowancesMessageEvent = useCallback((event: PerkAllowancesMessageEvent) =>
{
const parser = event.getParser();
setUseGuideTool(parser.isAllowed(PerkEnum.USE_GUIDE_TOOL));
}, [ setUseGuideTool ]);
CreateMessageHook(PerkAllowancesMessageEvent, onPerkAllowancesMessageEvent);
const onFriendsMessengerIconEvent = useCallback((event: FriendsMessengerIconEvent) => const onFriendsMessengerIconEvent = useCallback((event: FriendsMessengerIconEvent) =>
{ {
setChatIconType(event.iconType); setChatIconType(event.iconType);
@ -167,6 +177,10 @@ export const ToolbarView: FC<ToolbarViewProps> = props =>
dispatchUiEvent(new UserSettingsUIEvent(UserSettingsUIEvent.TOGGLE_USER_SETTINGS)); dispatchUiEvent(new UserSettingsUIEvent(UserSettingsUIEvent.TOGGLE_USER_SETTINGS));
setMeExpanded(false); setMeExpanded(false);
return; return;
case ToolbarViewItems.GUIDE_TOOL_ITEM:
dispatchUiEvent(new GuideToolEvent(GuideToolEvent.TOGGLE_GUIDE_TOOL));
setMeExpanded(false);
return;
case ToolbarViewItems.FRIEND_CHAT_ITEM: case ToolbarViewItems.FRIEND_CHAT_ITEM:
OpenMessengerChat(); OpenMessengerChat();
return; return;
@ -184,7 +198,7 @@ export const ToolbarView: FC<ToolbarViewProps> = props =>
return ( return (
<> <>
<TransitionAnimation type={ TransitionAnimationTypes.FADE_IN } inProp={ isMeExpanded } timeout={ 300 }> <TransitionAnimation type={ TransitionAnimationTypes.FADE_IN } inProp={ isMeExpanded } timeout={ 300 }>
<ToolbarMeView unseenAchievementCount={ unseenAchievementCount } handleToolbarItemClick={ handleToolbarItemClick } /> <ToolbarMeView useGuideTool={ useGuideTool } unseenAchievementCount={ unseenAchievementCount } handleToolbarItemClick={ handleToolbarItemClick } />
</TransitionAnimation> </TransitionAnimation>
<Flex alignItems="center" justifyContent="between" gap={ 2 } className="nitro-toolbar py-1 px-3"> <Flex alignItems="center" justifyContent="between" gap={ 2 } className="nitro-toolbar py-1 px-3">
<Flex gap={ 2 } alignItems="center"> <Flex gap={ 2 } alignItems="center">

View File

@ -11,4 +11,5 @@ export class ToolbarViewItems
public static ACHIEVEMENTS_ITEM: string = 'TVI_ACHIEVEMENTS_ITEM'; public static ACHIEVEMENTS_ITEM: string = 'TVI_ACHIEVEMENTS_ITEM';
public static PROFILE_ITEM: string = 'TVI_PROFILE_ITEM'; public static PROFILE_ITEM: string = 'TVI_PROFILE_ITEM';
public static SETTINGS_ITEM: string = 'TVI_SETTINGS_ITEM'; public static SETTINGS_ITEM: string = 'TVI_SETTINGS_ITEM';
public static GUIDE_TOOL_ITEM: string = 'TVI_GUIDE_TOOL_ITEM';
} }

View File

@ -0,0 +1,10 @@
import { NitroEvent } from '@nitrots/nitro-renderer';
export class GuideToolEvent extends NitroEvent
{
public static readonly SHOW_GUIDE_TOOL: string = 'GTE_SHOW_GUIDE_TOOL';
public static readonly HIDE_GUIDE_TOOL: string = 'GTE_HIDE_GUIDE_TOOL';
public static readonly TOGGLE_GUIDE_TOOL: string = 'GTE_TOGGLE_GUIDE_TOOL';
public static readonly CREATE_HELP_REQUEST: string = 'GTE_CREATE_HELP_REQUEST';
public static readonly CREATE_BULLY_REQUEST: string = 'GTE_CREATE_BULLY_REQUEST';
}

View File

@ -0,0 +1 @@
export * from './GuideToolEvent';

View File

@ -2,6 +2,7 @@ export * from './avatar-editor';
export * from './camera'; export * from './camera';
export * from './catalog'; export * from './catalog';
export * from './friends'; export * from './friends';
export * from './guide-tool';
export * from './inventory'; export * from './inventory';
export * from './navigator'; export * from './navigator';
export * from './notification-center'; export * from './notification-center';

View File

@ -5,6 +5,7 @@ import ReactDOM from 'react-dom';
import { App } from './App'; import { App } from './App';
import './index.scss'; import './index.scss';
//@ts-ignore
library.add(fas); library.add(fas);
ReactDOM.render( ReactDOM.render(

View File

@ -5,7 +5,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, noCloseButton = false, onCloseClick = null } = props;
const { theme = 'primary', simple = false } = useNitroCardContext(); const { theme = 'primary', simple = false } = useNitroCardContext();
const onMouseDown = useCallback((event: MouseEvent<HTMLDivElement>) => const onMouseDown = useCallback((event: MouseEvent<HTMLDivElement>) =>

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

@ -20,7 +20,6 @@ export const NotificationCenterView: FC<NotificationCenterViewProps> = props =>
const onNotificationAlertEvent = useCallback((event: NotificationAlertEvent) => const onNotificationAlertEvent = useCallback((event: NotificationAlertEvent) =>
{ {
console.log(event);
const alertItem = new NotificationAlertItem(event.messages, event.alertType, event.clickUrl, event.clickUrlText, event.title, event.imageUrl); const alertItem = new NotificationAlertItem(event.messages, event.alertType, event.clickUrl, event.clickUrlText, event.title, event.imageUrl);
setAlerts(prevValue => [ alertItem, ...prevValue ]); setAlerts(prevValue => [ alertItem, ...prevValue ]);
@ -30,7 +29,6 @@ export const NotificationCenterView: FC<NotificationCenterViewProps> = props =>
const onNotificationBubbleEvent = useCallback((event: NotificationBubbleEvent) => const onNotificationBubbleEvent = useCallback((event: NotificationBubbleEvent) =>
{ {
console.log(event);
const notificationItem = new NotificationBubbleItem(event.message, event.notificationType, event.imageUrl, event.linkUrl); const notificationItem = new NotificationBubbleItem(event.message, event.notificationType, event.imageUrl, event.linkUrl);
setBubbleAlerts(prevValue => [ notificationItem, ...prevValue ]); setBubbleAlerts(prevValue => [ notificationItem, ...prevValue ]);

View File

@ -80,6 +80,7 @@
} }
.fas, .fas,
.svg-inline--fa,
.nitro-currency-icon { .nitro-currency-icon {
position: absolute; position: absolute;
right: 8px; right: 8px;