Add friend request bubble

This commit is contained in:
Bill 2021-11-19 12:31:43 -05:00
parent 128919af62
commit b51ef0bbcc
12 changed files with 213 additions and 32 deletions

View File

@ -3,7 +3,7 @@ import { DeclineFriendMessageComposer } from '@nitrots/nitro-renderer/src';
import { FC, useCallback, useEffect, useMemo, useState } from 'react'; import { FC, useCallback, useEffect, useMemo, useState } from 'react';
import { createPortal } from 'react-dom'; import { createPortal } from 'react-dom';
import { GetRoomSession } from '../../api'; import { GetRoomSession } from '../../api';
import { FriendEnteredRoomEvent, FriendListContentEvent, FriendsEvent, FriendsRequestCountEvent } from '../../events'; import { FriendEnteredRoomEvent, FriendListContentEvent, FriendRequestEvent, FriendsAcceptFriendRequestEvent, FriendsDeclineFriendRequestEvent, FriendsEvent, FriendsRequestCountEvent } from '../../events';
import { FriendsSendFriendRequestEvent } from '../../events/friends/FriendsSendFriendRequestEvent'; import { FriendsSendFriendRequestEvent } from '../../events/friends/FriendsSendFriendRequestEvent';
import { CreateMessageHook, useRoomEngineEvent } from '../../hooks'; import { CreateMessageHook, useRoomEngineEvent } from '../../hooks';
import { dispatchUiEvent, useUiEvent } from '../../hooks/events/ui/ui-event'; import { dispatchUiEvent, useUiEvent } from '../../hooks/events/ui/ui-event';
@ -71,18 +71,17 @@ export const FriendsView: FC<{}> = props =>
{ {
setRequests(prevValue => setRequests(prevValue =>
{ {
const newRequests: MessengerRequest[] = []; const newRequests: MessengerRequest[] = [ ...prevValue ];
for(const request of prevValue) const index = newRequests.findIndex(request => (request.requesterUserId === userId));
if(index >= 0)
{ {
if(request.requesterUserId !== userId) SendMessageHook(new AcceptFriendMessageComposer(newRequests[index].id));
{
newRequests.push(request); dispatchUiEvent(new FriendRequestEvent(FriendRequestEvent.ACCEPTED, userId));
}
else newRequests.splice(index, 1);
{
SendMessageHook(new AcceptFriendMessageComposer(request.id));
}
} }
return newRequests; return newRequests;
@ -97,22 +96,23 @@ export const FriendsView: FC<{}> = props =>
{ {
SendMessageHook(new DeclineFriendMessageComposer(true)); SendMessageHook(new DeclineFriendMessageComposer(true));
for(const request of prevValue) dispatchUiEvent(new FriendRequestEvent(FriendRequestEvent.DECLINED, request.requesterUserId));
return []; return [];
} }
else else
{ {
const newRequests: MessengerRequest[] = []; const newRequests: MessengerRequest[] = [ ...prevValue ];
for(const request of prevValue) const index = newRequests.findIndex(request => (request.requesterUserId === userId));
if(index >= 0)
{ {
if(request.requesterUserId !== userId) SendMessageHook(new DeclineFriendMessageComposer(false, newRequests[index].id));
{
newRequests.push(request); dispatchUiEvent(new FriendRequestEvent(FriendRequestEvent.DECLINED, userId));
}
else newRequests.splice(index, 1);
{
SendMessageHook(new DeclineFriendMessageComposer(false, request.id));
}
} }
return newRequests; return newRequests;
@ -237,10 +237,15 @@ export const FriendsView: FC<{}> = props =>
{ {
const newRequests = [ ...prevValue ]; const newRequests = [ ...prevValue ];
const newRequest = new MessengerRequest(); const index = newRequests.findIndex(existing => (existing.requesterUserId === request.requesterUserId));
newRequest.populate(request);
newRequests.push(newRequest); if(index === -1)
{
const newRequest = new MessengerRequest();
newRequest.populate(request);
newRequests.push(newRequest);
}
return newRequests; return newRequests;
}); });
@ -273,6 +278,20 @@ export const FriendsView: FC<{}> = props =>
useUiEvent(FriendsSendFriendRequestEvent.SEND_FRIEND_REQUEST, onFriendsEvent); useUiEvent(FriendsSendFriendRequestEvent.SEND_FRIEND_REQUEST, onFriendsEvent);
useUiEvent(FriendsEvent.REQUEST_FRIEND_LIST, onFriendsEvent); useUiEvent(FriendsEvent.REQUEST_FRIEND_LIST, onFriendsEvent);
const onFriendsAcceptFriendRequestEvent = useCallback((event: FriendsAcceptFriendRequestEvent) =>
{
acceptFriend(event.requestId);
}, [ acceptFriend ]);
useUiEvent(FriendsAcceptFriendRequestEvent.ACCEPT_FRIEND_REQUEST, onFriendsAcceptFriendRequestEvent);
const onFriendsDeclineFriendRequestEvent = useCallback((event: FriendsDeclineFriendRequestEvent) =>
{
declineFriend(event.requestId);
}, [ declineFriend ]);
useUiEvent(FriendsDeclineFriendRequestEvent.DECLINE_FRIEND_REQUEST, onFriendsDeclineFriendRequestEvent);
const onRoomEngineObjectEvent = useCallback((event: RoomEngineObjectEvent) => const onRoomEngineObjectEvent = useCallback((event: RoomEngineObjectEvent) =>
{ {
const roomSession = GetRoomSession(); const roomSession = GetRoomSession();

View File

@ -14,9 +14,9 @@ export const FriendsRequestView: FC<FriendsRequestViewProps> = props =>
return ( return (
<NitroCardAccordionSetView headerText={ LocalizeText('friendlist.tab.friendrequests') + ` (${ requests.length })` } isExpanded={ true }> <NitroCardAccordionSetView headerText={ LocalizeText('friendlist.tab.friendrequests') + ` (${ requests.length })` } isExpanded={ true }>
{ requests.map(request => { requests.map((request, index) =>
{ {
return <FriendsRequestItemView key={ request.requesterUserId } request={ request } /> return <FriendsRequestItemView key={ index } request={ request } />
}) } }) }
<NitroLayoutFlex className="justify-content-center p-1"> <NitroLayoutFlex className="justify-content-center p-1">
<NitroLayoutButton size="sm" onClick={ event => declineFriend(-1, true) }> <NitroLayoutButton size="sm" onClick={ event => declineFriend(-1, true) }>

View File

@ -1,11 +1,12 @@
@import './avatar-info/AvatarInfoWidgetView'; @import './avatar-info/AvatarInfoWidgetView';
@import './chat/ChatWidgetView'; @import './chat/ChatWidgetView';
@import './chat-input/ChatInputView'; @import './chat-input/ChatInputView';
@import './choosers/ChooserWidgetView';
@import './context-menu/ContextMenu'; @import './context-menu/ContextMenu';
@import './doorbell/DoorbellWidgetView'; @import './doorbell/DoorbellWidgetView';
@import './friend-request/FriendRequestDialogView';
@import './furniture/FurnitureWidgets'; @import './furniture/FurnitureWidgets';
@import './infostand/InfoStandWidgetView'; @import './infostand/InfoStandWidgetView';
@import './object-location/ObjectLocationView'; @import './object-location/ObjectLocationView';
@import './room-tools/RoomToolsWidgetView'; @import './room-tools/RoomToolsWidgetView';
@import './choosers/ChooserWidgetView';
@import './word-quiz/WordQuizWidgetView'; @import './word-quiz/WordQuizWidgetView';

View File

@ -1,7 +1,8 @@
import { RoomEngineEvent, RoomEngineObjectEvent, RoomEngineRoomAdEvent, RoomEngineTriggerWidgetEvent, RoomEngineUseProductEvent, RoomId, RoomObjectCategory, RoomObjectOperationType, RoomObjectVariable, RoomSessionChatEvent, RoomSessionDanceEvent, RoomSessionDimmerPresetsEvent, RoomSessionDoorbellEvent, RoomSessionErrorMessageEvent, RoomSessionEvent, RoomSessionFriendRequestEvent, RoomSessionPetInfoUpdateEvent, RoomSessionPollEvent, RoomSessionPresentEvent, RoomSessionUserBadgesEvent, RoomSessionWordQuizEvent, RoomZoomEvent } from '@nitrots/nitro-renderer'; import { RoomEngineEvent, RoomEngineObjectEvent, RoomEngineRoomAdEvent, RoomEngineTriggerWidgetEvent, RoomEngineUseProductEvent, RoomId, RoomObjectCategory, RoomObjectOperationType, RoomObjectVariable, RoomSessionChatEvent, RoomSessionDanceEvent, RoomSessionDimmerPresetsEvent, RoomSessionDoorbellEvent, RoomSessionErrorMessageEvent, RoomSessionEvent, RoomSessionFriendRequestEvent, RoomSessionPetInfoUpdateEvent, RoomSessionPollEvent, RoomSessionPresentEvent, RoomSessionUserBadgesEvent, RoomSessionWordQuizEvent, RoomZoomEvent } from '@nitrots/nitro-renderer';
import { FC, useCallback } from 'react'; import { FC, useCallback } from 'react';
import { CanManipulateFurniture, GetRoomEngine, GetSessionDataManager, IsFurnitureSelectionDisabled, LocalizeText, ProcessRoomObjectOperation, RoomWidgetFurniToWidgetMessage, RoomWidgetUpdateRoomEngineEvent, RoomWidgetUpdateRoomObjectEvent } from '../../../api'; import { CanManipulateFurniture, GetRoomEngine, GetSessionDataManager, IsFurnitureSelectionDisabled, LocalizeText, ProcessRoomObjectOperation, RoomWidgetFurniToWidgetMessage, RoomWidgetUpdateRoomEngineEvent, RoomWidgetUpdateRoomObjectEvent } from '../../../api';
import { useRoomEngineEvent, useRoomSessionManagerEvent } from '../../../hooks/events'; import { FriendRequestEvent } from '../../../events';
import { useRoomEngineEvent, useRoomSessionManagerEvent, useUiEvent } from '../../../hooks';
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 { useRoomContext } from '../context/RoomContext'; import { useRoomContext } from '../context/RoomContext';
@ -11,6 +12,7 @@ import { ChatWidgetView } from './chat/ChatWidgetView';
import { FurniChooserWidgetView } from './choosers/FurniChooserWidgetView'; import { FurniChooserWidgetView } from './choosers/FurniChooserWidgetView';
import { UserChooserWidgetView } from './choosers/UserChooserWidgetView'; import { UserChooserWidgetView } from './choosers/UserChooserWidgetView';
import { DoorbellWidgetView } from './doorbell/DoorbellWidgetView'; import { DoorbellWidgetView } from './doorbell/DoorbellWidgetView';
import { FriendRequestWidgetView } from './friend-request/FriendRequestWidgetView';
import { FurnitureWidgetsView } from './furniture/FurnitureWidgetsView'; import { FurnitureWidgetsView } from './furniture/FurnitureWidgetsView';
import { InfoStandWidgetView } from './infostand/InfoStandWidgetView'; import { InfoStandWidgetView } from './infostand/InfoStandWidgetView';
import { RoomThumbnailWidgetView } from './room-thumbnail/RoomThumbnailWidgetView'; import { RoomThumbnailWidgetView } from './room-thumbnail/RoomThumbnailWidgetView';
@ -275,6 +277,8 @@ export const RoomWidgetsView: FC<{}> = props =>
useRoomSessionManagerEvent(RoomSessionPollEvent.OFFER, onRoomSessionEvent); useRoomSessionManagerEvent(RoomSessionPollEvent.OFFER, onRoomSessionEvent);
useRoomSessionManagerEvent(RoomSessionPollEvent.ERROR, onRoomSessionEvent); useRoomSessionManagerEvent(RoomSessionPollEvent.ERROR, onRoomSessionEvent);
useRoomSessionManagerEvent(RoomSessionPollEvent.CONTENT, onRoomSessionEvent); useRoomSessionManagerEvent(RoomSessionPollEvent.CONTENT, onRoomSessionEvent);
useUiEvent(FriendRequestEvent.ACCEPTED, onRoomSessionEvent);
useUiEvent(FriendRequestEvent.DECLINED, onRoomSessionEvent);
const onRoomSessionErrorMessageEvent = useCallback((event: RoomSessionErrorMessageEvent) => const onRoomSessionErrorMessageEvent = useCallback((event: RoomSessionErrorMessageEvent) =>
{ {
@ -357,6 +361,7 @@ export const RoomWidgetsView: FC<{}> = props =>
<FurniChooserWidgetView /> <FurniChooserWidgetView />
<UserChooserWidgetView /> <UserChooserWidgetView />
<WordQuizWidgetView /> <WordQuizWidgetView />
<FriendRequestWidgetView />
</> </>
); );
} }

View File

@ -0,0 +1,4 @@
.nitro-friend-request-dialog {
width: unset;
max-width: 200px;
}

View File

@ -0,0 +1,47 @@
import { FC, useCallback } from 'react';
import { LocalizeText, RoomWidgetFriendRequestMessage } from '../../../../api';
import { NitroLayoutButton, NitroLayoutFlex, NitroLayoutFlexColumn } from '../../../../layout';
import { NitroLayoutBase } from '../../../../layout/base';
import { useRoomContext } from '../../context/RoomContext';
import { UserLocationView } from '../user-location/UserLocationView';
import { FriendRequestDialogViewProps } from './FriendRequestDialogView.types';
export const FriendRequestDialogView: FC<FriendRequestDialogViewProps> = props =>
{
const { requestId = -1, userId = -1, userName = null, close = null } = props;
const { widgetHandler = null } = useRoomContext();
const accept = useCallback(() =>
{
widgetHandler.processWidgetMessage(new RoomWidgetFriendRequestMessage(RoomWidgetFriendRequestMessage.ACCEPT, requestId));
close();
}, [ requestId, widgetHandler, close ]);
const decline = useCallback(() =>
{
widgetHandler.processWidgetMessage(new RoomWidgetFriendRequestMessage(RoomWidgetFriendRequestMessage.ACCEPT, requestId));
close();
}, [ requestId, widgetHandler, close ]);
return (
<UserLocationView userId={ userId }>
<NitroLayoutBase className="nitro-friend-request-dialog nitro-context-menu p-2">
<NitroLayoutFlexColumn>
<NitroLayoutBase className="h6">
{ LocalizeText('widget.friendrequest.from', [ 'username' ], [ userName ]) }
</NitroLayoutBase>
<NitroLayoutFlex className="justify-content-end align-items-center" gap={ 2 }>
<NitroLayoutButton variant="danger" size="sm" onClick={ decline }>
{ LocalizeText('widget.friendrequest.decline') }
</NitroLayoutButton>
<NitroLayoutButton variant="success" size="sm" onClick={ accept }>
{ LocalizeText('widget.friendrequest.accept') }
</NitroLayoutButton>
</NitroLayoutFlex>
</NitroLayoutFlexColumn>
</NitroLayoutBase>
</UserLocationView>
);
}

View File

@ -0,0 +1,7 @@
export interface FriendRequestDialogViewProps
{
requestId: number;
userId: number;
userName: string;
close: () => void;
}

View File

@ -0,0 +1,70 @@
import { FC, useCallback, useState } from 'react';
import { RoomWidgetUpdateFriendRequestEvent } from '../../../../api';
import { CreateEventDispatcherHook } from '../../../../hooks';
import { useRoomContext } from '../../context/RoomContext';
import { FriendRequestDialogView } from './FriendRequestDialogView';
export const FriendRequestWidgetView: FC<{}> = props =>
{
const [ friendRequests, setFriendRequests ] = useState<{ requestId: number, userId: number, userName: string }[]>([]);
const { eventDispatcher = null } = useRoomContext();
const showFriendRequest = useCallback((requestId: number, userId: number, userName: string) =>
{
const index = friendRequests.findIndex(value => (value.userId === userId));
if(index >= 0) return;
setFriendRequests(prevValue =>
{
const newValue = [ ...prevValue ];
newValue.push({ requestId, userId, userName });
return newValue;
});
}, [ friendRequests ]);
const hideFriendRequest = useCallback((requestId: number) =>
{
const index = friendRequests.findIndex(value => (value.requestId === requestId));
if(index === -1) return;
setFriendRequests(prevValue =>
{
const newValue = [ ...prevValue ];
newValue.splice(index, 1);
return newValue;
})
}, [ friendRequests ]);
const onRoomWidgetUpdateFriendRequestEvent = useCallback((event: RoomWidgetUpdateFriendRequestEvent) =>
{
switch(event.type)
{
case RoomWidgetUpdateFriendRequestEvent.SHOW_FRIEND_REQUEST:
showFriendRequest(event.requestId, event.userId, event.userName);
return;
case RoomWidgetUpdateFriendRequestEvent.HIDE_FRIEND_REQUEST:
hideFriendRequest(event.requestId);
return;
}
}, [ showFriendRequest, hideFriendRequest ]);
CreateEventDispatcherHook(RoomWidgetUpdateFriendRequestEvent.SHOW_FRIEND_REQUEST, eventDispatcher, onRoomWidgetUpdateFriendRequestEvent);
CreateEventDispatcherHook(RoomWidgetUpdateFriendRequestEvent.HIDE_FRIEND_REQUEST, eventDispatcher, onRoomWidgetUpdateFriendRequestEvent);
if(!friendRequests.length) return null;
return (
<>
{ friendRequests.map((request, index) =>
{
return <FriendRequestDialogView key={ index } { ...request } close={ () => hideFriendRequest(request.userId) } />
}) }
</>
);
}

View File

@ -1,10 +1,11 @@
import { FC, useCallback, useEffect, useRef, useState } from 'react'; import { FC, useCallback, useEffect, useRef, useState } from 'react';
import { GetNitroInstance, GetRoomEngine, GetRoomSession } from '../../../../api'; import { GetNitroInstance, GetRoomEngine, GetRoomSession } from '../../../../api';
import { NitroLayoutBase } from '../../../../layout/base';
import { ObjectLocationViewProps } from './ObjectLocationView.types'; import { ObjectLocationViewProps } from './ObjectLocationView.types';
export const ObjectLocationView: FC<ObjectLocationViewProps> = props => export const ObjectLocationView: FC<ObjectLocationViewProps> = props =>
{ {
const { objectId = -1, category = -1, noFollow = false, children = null } = props; const { objectId = -1, category = -1, noFollow = false, children = null, ...rest } = props;
const [ pos, setPos ] = useState<{ x: number, y: number }>({ x: -1, y: -1 }); const [ pos, setPos ] = useState<{ x: number, y: number }>({ x: -1, y: -1 });
const elementRef = useRef<HTMLDivElement>(); const elementRef = useRef<HTMLDivElement>();
@ -50,8 +51,8 @@ export const ObjectLocationView: FC<ObjectLocationViewProps> = props =>
}, [ updatePosition, noFollow ]); }, [ updatePosition, noFollow ]);
return ( return (
<div ref={ elementRef } className={ 'object-location position-absolute ' + ( (pos.x + (elementRef.current ? elementRef.current.offsetWidth : 0 ) )> -1 ? 'visible' : 'invisible') } style={ { left: pos.x, top: pos.y } }> <NitroLayoutBase innerRef={ elementRef } className={ 'object-location position-absolute ' + ( (pos.x + (elementRef.current ? elementRef.current.offsetWidth : 0 ) )> -1 ? 'visible' : 'invisible') } style={ { left: pos.x, top: pos.y } } { ...rest }>
{ children } { children }
</div> </NitroLayoutBase>
); );
} }

View File

@ -1,4 +1,6 @@
export interface ObjectLocationViewProps import { NitroLayoutBaseProps } from '../../../../layout/base';
export interface ObjectLocationViewProps extends NitroLayoutBaseProps
{ {
objectId: number; objectId: number;
category: number; category: number;

View File

@ -0,0 +1,19 @@
import { RoomObjectCategory } from '@nitrots/nitro-renderer';
import { FC } from 'react';
import { useRoomContext } from '../../context/RoomContext';
import { ObjectLocationView } from '../object-location/ObjectLocationView';
import { UserLocationViewProps } from './UserLocationView.types';
export const UserLocationView: FC<UserLocationViewProps> = props =>
{
const { userId = -1, ...rest } = props;
const { roomSession = null } = useRoomContext();
if((userId === -1) || !roomSession) return null;
const userData = roomSession.userDataManager.getUserData(userId);
if(!userData) return null;
return <ObjectLocationView objectId={ userData.roomIndex } category={ RoomObjectCategory.UNIT } { ...rest } />;
}

View File

@ -0,0 +1,6 @@
import { NitroLayoutBaseProps } from '../../../../layout/base';
export interface UserLocationViewProps extends NitroLayoutBaseProps
{
userId: number;
}