Room invite + friend deletion

This commit is contained in:
MyNameIsBatman 2022-02-13 19:38:20 -03:00
parent 9666a4103a
commit 77819a7e2b
12 changed files with 218 additions and 46 deletions

View File

@ -92,5 +92,13 @@
}
}
.nitro-friends-room-invite {
width: $friends-list-width;
}
.nitro-friends-remove-confirmation {
width: $friends-list-width;
}
@import "./views/friend-bar/FriendBarView";
@import "./views/messenger/FriendsMessengerView";

View File

@ -1,4 +1,5 @@
import { FollowFriendMessageComposer, SetRelationshipStatusComposer } from '@nitrots/nitro-renderer';
import classNames from 'classnames';
import { FC, useCallback, useState } from 'react';
import { LocalizeText, OpenMessengerChat } from '../../../../api';
import { SendMessageHook } from '../../../../hooks';
@ -9,7 +10,7 @@ import { FriendsGroupItemViewProps } from './FriendsGroupItemView.types';
export const FriendsGroupItemView: FC<FriendsGroupItemViewProps> = props =>
{
const { friend = null, selected = false, children = null, ...rest } = props;
const { friend = null, selected = false, selectFriend = null, children = null, ...rest } = props;
const [ isExpanded, setIsExpanded ] = useState<boolean>(false);
@ -20,8 +21,10 @@ export const FriendsGroupItemView: FC<FriendsGroupItemViewProps> = props =>
SendMessageHook(new FollowFriendMessageComposer(friend.id));
}, [ friend ]);
const openMessengerChat = useCallback(() =>
const openMessengerChat = useCallback((e) =>
{
e.stopPropagation();
if(!friend) return;
OpenMessengerChat(friend.id);
@ -40,8 +43,16 @@ export const FriendsGroupItemView: FC<FriendsGroupItemViewProps> = props =>
}
}, [ friend ]);
const updateRelationship = useCallback((type: number) =>
const initUpdateRelationship = useCallback((e) =>
{
e.stopPropagation();
setIsExpanded(true);
}, []);
const updateRelationship = useCallback((e, type: number) =>
{
e.stopPropagation();
if(type !== friend.relationshipStatus) SendMessageHook(new SetRelationshipStatusComposer(friend.id, type));
setIsExpanded(false);
@ -50,8 +61,10 @@ export const FriendsGroupItemView: FC<FriendsGroupItemViewProps> = props =>
if(!friend) return null;
return (
<NitroLayoutFlex className="px-2 py-1 align-items-center" gap={ 1 } { ...rest }>
<NitroLayoutFlex className={ 'px-2 py-1 align-items-center' + classNames({ ' bg-primary text-white': selected }) } gap={ 1 } { ...rest } onClick={ selectFriend }>
<div onClick={ (e) => e.stopPropagation() }>
<UserProfileIconView userId={ friend.id } />
</div>
<div>{ friend.name }</div>
<NitroLayoutFlex className="ms-auto align-items-center" gap={ 1 }>
{ !isExpanded &&
@ -60,14 +73,14 @@ export const FriendsGroupItemView: FC<FriendsGroupItemViewProps> = props =>
<NitroLayoutBase onClick={ followFriend } className="nitro-friends-spritesheet icon-follow cursor-pointer" title={ LocalizeText('friendlist.tip.follow') } /> }
{ friend.online &&
<NitroLayoutBase className="nitro-friends-spritesheet icon-chat cursor-pointer" onClick={ openMessengerChat } title={ LocalizeText('friendlist.tip.im') } /> }
<NitroLayoutBase className={ `nitro-friends-spritesheet icon-${ getCurrentRelationshipName() } cursor-pointer` }onClick={ event => setIsExpanded(true) } title={ LocalizeText('infostand.link.relationship') } />
<NitroLayoutBase className={ `nitro-friends-spritesheet icon-${ getCurrentRelationshipName() } cursor-pointer` } onClick={ initUpdateRelationship } title={ LocalizeText('infostand.link.relationship') } />
</> }
{ isExpanded &&
<>
<NitroLayoutBase className="nitro-friends-spritesheet icon-heart cursor-pointer" onClick={ () => updateRelationship(MessengerFriend.RELATIONSHIP_HEART) } />
<NitroLayoutBase className="nitro-friends-spritesheet icon-smile cursor-pointer" onClick={ () => updateRelationship(MessengerFriend.RELATIONSHIP_SMILE) } />
<NitroLayoutBase className="nitro-friends-spritesheet icon-bobba cursor-pointer" onClick={ () => updateRelationship(MessengerFriend.RELATIONSHIP_BOBBA) } />
<NitroLayoutBase className="nitro-friends-spritesheet icon-none cursor-pointer" onClick={ () => updateRelationship(MessengerFriend.RELATIONSHIP_NONE) } />
<NitroLayoutBase className="nitro-friends-spritesheet icon-heart cursor-pointer" onClick={ (e) => updateRelationship(e, MessengerFriend.RELATIONSHIP_HEART) } />
<NitroLayoutBase className="nitro-friends-spritesheet icon-smile cursor-pointer" onClick={ (e) => updateRelationship(e, MessengerFriend.RELATIONSHIP_SMILE) } />
<NitroLayoutBase className="nitro-friends-spritesheet icon-bobba cursor-pointer" onClick={ (e) => updateRelationship(e, MessengerFriend.RELATIONSHIP_BOBBA) } />
<NitroLayoutBase className="nitro-friends-spritesheet icon-none cursor-pointer" onClick={ (e) => updateRelationship(e, MessengerFriend.RELATIONSHIP_NONE) } />
</> }
</NitroLayoutFlex>
{ children }

View File

@ -5,4 +5,5 @@ export interface FriendsGroupItemViewProps extends NitroLayoutFlexProps
{
friend: MessengerFriend;
selected?: boolean;
selectFriend: () => void;
}

View File

@ -4,15 +4,15 @@ import { FriendsGroupViewProps } from './FriendsGroupView.types';
export const FriendsGroupView: FC<FriendsGroupViewProps> = props =>
{
const { list = null } = props;
const { list = null, selectedFriendsIds = null, selectFriend = null } = props;
if(!list) return null;
return (
<>
{ list.map((item, index) =>
{ selectedFriendsIds && list && list.map((item, index) =>
{
return <FriendsGroupItemView key={ index } friend={ item } />;
return <FriendsGroupItemView key={ index } friend={ item } selected={ selectedFriendsIds.includes(item.id) } selectFriend={ () => selectFriend(item.id) } />;
}) }
</>
);

View File

@ -3,4 +3,6 @@ import { MessengerFriend } from '../../common/MessengerFriend';
export interface FriendsGroupViewProps
{
list: MessengerFriend[];
selectedFriendsIds: number[];
selectFriend: (userId: number) => void;
}

View File

@ -1,9 +1,13 @@
import { FC, useEffect, useState } from 'react';
import { RemoveFriendComposer, SendRoomInviteComposer } from '@nitrots/nitro-renderer';
import { FC, useCallback, useMemo, useState } from 'react';
import { LocalizeText } from '../../../../api';
import { SendMessageHook } from '../../../../hooks';
import { NitroCardAccordionSetView, NitroCardAccordionView, NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView } from '../../../../layout';
import { MessengerFriend } from '../../common/MessengerFriend';
import { FriendsGroupView } from '../friends-group/FriendsGroupView';
import { FriendsRemoveConfirmationView } from '../friends-remove-confirmation/FriendsRemoveConfirmationView';
import { FriendsRequestView } from '../friends-request/FriendsRequestView';
import { FriendsRoomInviteView } from '../friends-room-invite/FriendsRoomInviteView';
import { FriendsSearchView } from '../friends-search/FriendsSearchView';
import { FriendsListViewProps } from './FriendsListView.types';
@ -13,15 +17,71 @@ const MODE_SEARCH: number = 1;
export const FriendsListView: FC<FriendsListViewProps> = props =>
{
const { onlineFriends = [], offlineFriends = [], friendRequests = [], onCloseClick = null } = props;
const [ selectedFriends, setSelectedFriends ] = useState<MessengerFriend[]>([]);
const [ selectedFriendsIds, setSelectedFriendsIds ] = useState<number[]>([]);
const [ mode, setMode ] = useState<number>(0);
useEffect(() =>
const [ showRoomInvite, setShowRoomInvite ] = useState<boolean>(false);
const [ showRemoveFriendsConfirmation, setShowRemoveFriendsConfirmation ] = useState<boolean>(false);
const removeFriendsText = useMemo(() =>
{
setSelectedFriends([]);
}, [ onlineFriends, offlineFriends ]);
if(!selectedFriendsIds || !selectedFriendsIds.length) return '';
const userNames: string[] = [];
for(const userId of selectedFriendsIds)
{
let existingFriend: MessengerFriend = onlineFriends.find(f => f.id === userId);
if(!existingFriend) existingFriend = offlineFriends.find(f => f.id === userId);
if(!existingFriend) continue;
userNames.push(existingFriend.name);
}
return LocalizeText('friendlist.removefriendconfirm.userlist', ['user_names'], [userNames.join(', ')]);
}, [offlineFriends, onlineFriends, selectedFriendsIds]);
const selectFriend = useCallback((userId: number) =>
{
if(userId < 0) return;
const existingUserIdIndex: number = selectedFriendsIds.indexOf(userId);
if(existingUserIdIndex > -1)
{
const clonedFriend = [...selectedFriendsIds];
clonedFriend.splice(existingUserIdIndex, 1)
setSelectedFriendsIds([...clonedFriend]);
}
else
{
setSelectedFriendsIds([...selectedFriendsIds, userId]);
}
}, [ selectedFriendsIds, setSelectedFriendsIds ]);
const sendRoomInvite = useCallback((message: string) =>
{
if(selectedFriendsIds.length === 0 || !message || message.length === 0) return;
SendMessageHook(new SendRoomInviteComposer(message, ...selectedFriendsIds));
setShowRoomInvite(false);
}, [ selectedFriendsIds, setShowRoomInvite ]);
const removeSelectedFriends = useCallback(() =>
{
if(selectedFriendsIds.length === 0) return;
SendMessageHook(new RemoveFriendComposer(...selectedFriendsIds));
setSelectedFriendsIds([]);
setShowRemoveFriendsConfirmation(false);
}, [ selectedFriendsIds ]);
return (
<>
<NitroCardView className="nitro-friends" uniqueKey="nitro-friends">
<NitroCardHeaderView headerText={ LocalizeText('friendlist.friends') } onCloseClick={ onCloseClick } />
<NitroCardTabsView>
@ -34,18 +94,30 @@ export const FriendsListView: FC<FriendsListViewProps> = props =>
</NitroCardTabsView>
<NitroCardContentView className="p-0 text-black">
{ (mode === MODE_FRIENDS) &&
<NitroCardAccordionView>
<>
<NitroCardAccordionView className="overflow-y-auto">
<NitroCardAccordionSetView headerText={ LocalizeText('friendlist.friends') + ` (${onlineFriends.length})` } isExpanded={ true }>
<FriendsGroupView list={ onlineFriends } />
<FriendsGroupView list={ onlineFriends } selectedFriendsIds={ selectedFriendsIds } selectFriend={ selectFriend } />
</NitroCardAccordionSetView>
<NitroCardAccordionSetView headerText={ LocalizeText('friendlist.friends.offlinecaption') + ` (${offlineFriends.length})` }>
<FriendsGroupView list={ offlineFriends } />
<FriendsGroupView list={ offlineFriends } selectedFriendsIds={ selectedFriendsIds } selectFriend={ selectFriend } />
</NitroCardAccordionSetView>
<FriendsRequestView requests={ friendRequests } />
</NitroCardAccordionView> }
</NitroCardAccordionView>
{ selectedFriendsIds && selectedFriendsIds.length > 0 && <div className="d-flex gap-2 p-2">
<button className="btn btn-primary w-100" onClick={ () => setShowRoomInvite(true) }>Invite</button>
<button className="btn btn-danger w-100" onClick={ () => setShowRemoveFriendsConfirmation(true) }>Delete</button>
</div> }
</>
}
{ (mode === MODE_SEARCH) &&
<FriendsSearchView /> }
</NitroCardContentView>
</NitroCardView>
{ showRoomInvite &&
<FriendsRoomInviteView selectedFriendsIds={ selectedFriendsIds } onCloseClick={ () => setShowRoomInvite(false) } sendRoomInvite={ sendRoomInvite } /> }
{ showRemoveFriendsConfirmation &&
<FriendsRemoveConfirmationView selectedFriendsIds={ selectedFriendsIds } removeFriendsText={ removeFriendsText } onCloseClick={ () => setShowRemoveFriendsConfirmation(false) } removeSelectedFriends={ removeSelectedFriends } /> }
</>
);
};

View File

@ -0,0 +1,22 @@
import { FC } from 'react';
import { LocalizeText } from '../../../../api';
import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../layout';
import { FriendsRemoveConfirmationViewProps } from './FriendsRemoveConfirmationView.types';
export const FriendsRemoveConfirmationView: FC<FriendsRemoveConfirmationViewProps> = props =>
{
const { selectedFriendsIds = null, removeFriendsText = null, removeSelectedFriends = null, onCloseClick = null } = props;
return (
<NitroCardView className="nitro-friends-remove-confirmation" uniqueKey="nitro-friends-remove-confirmation" simple={ true }>
<NitroCardHeaderView headerText={ LocalizeText('friendlist.removefriendconfirm.title') } onCloseClick={ onCloseClick } />
<NitroCardContentView className="text-black d-flex flex-column gap-3">
<div>{ removeFriendsText }</div>
<div className="d-flex gap-2">
<button className="btn btn-danger w-100" disabled={ selectedFriendsIds.length === 0 } onClick={ removeSelectedFriends }>{ LocalizeText('generic.ok') }</button>
<button className="btn btn-primary w-100" onClick={ onCloseClick }>{ LocalizeText('generic.cancel') }</button>
</div>
</NitroCardContentView>
</NitroCardView>
);
};

View File

@ -0,0 +1,7 @@
export interface FriendsRemoveConfirmationViewProps
{
selectedFriendsIds: number[];
removeFriendsText: string;
removeSelectedFriends: () => void;
onCloseClick: () => void;
}

View File

@ -0,0 +1,26 @@
import { FC, useState } from 'react';
import { LocalizeText } from '../../../../api';
import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../layout';
import { FriendsRoomInviteViewProps } from './FriendsRoomInviteView.types';
export const FriendsRoomInviteView: FC<FriendsRoomInviteViewProps> = props =>
{
const { selectedFriendsIds = null, onCloseClick = null, sendRoomInvite = null } = props;
const [ roomInviteMessage, setRoomInviteMessage ] = useState<string>('');
return (
<NitroCardView className="nitro-friends-room-invite" uniqueKey="nitro-friends-room-invite" simple={ true }>
<NitroCardHeaderView headerText={ LocalizeText('friendlist.invite.title') } onCloseClick={ onCloseClick } />
<NitroCardContentView className="text-black d-flex flex-column gap-2">
{ LocalizeText('friendlist.invite.summary', ['count'], [selectedFriendsIds.length.toString()]) }
<textarea className="form-control" value={roomInviteMessage} onChange={e => setRoomInviteMessage(e.target.value)}></textarea>
<div className="bg-muted rounded text-center p-2">{ LocalizeText('friendlist.invite.note') }</div>
<div className="d-flex gap-2">
<button className="btn btn-success w-100" disabled={ roomInviteMessage.length === 0 || selectedFriendsIds.length === 0 } onClick={ () => sendRoomInvite(roomInviteMessage) }>{ LocalizeText('friendlist.invite.send') }</button>
<button className="btn btn-primary w-100" onClick={ onCloseClick }>{ LocalizeText('generic.cancel') }</button>
</div>
</NitroCardContentView>
</NitroCardView>
);
};

View File

@ -0,0 +1,6 @@
export interface FriendsRoomInviteViewProps
{
selectedFriendsIds: number[];
onCloseClick: () => void;
sendRoomInvite: (message: string) => void;
}

View File

@ -1,4 +1,4 @@
import { FollowFriendMessageComposer, ILinkEventTracker, NewConsoleMessageEvent, SendMessageComposer } from '@nitrots/nitro-renderer';
import { FollowFriendMessageComposer, ILinkEventTracker, NewConsoleMessageEvent, RoomInviteEvent, SendMessageComposer } from '@nitrots/nitro-renderer';
import { FC, KeyboardEvent, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { AddEventLinkTracker, GetSessionDataManager, GetUserProfile, LocalizeText, RemoveLinkEventTracker } from '../../../../api';
import { MESSENGER_MESSAGE_RECEIVED, MESSENGER_NEW_THREAD, PlaySound } from '../../../../api/utils/PlaySound';
@ -105,6 +105,21 @@ export const FriendsMessengerView: FC<{}> = props =>
CreateMessageHook(NewConsoleMessageEvent, onNewConsoleMessageEvent);
const onRoomInviteEvent = useCallback((event: RoomInviteEvent) =>
{
const parser = event.getParser();
const [threadIndex, thread] = getMessageThreadWithIndex(parser.senderId);
if((threadIndex === -1) || !thread) return;
thread.addMessage(parser.senderId, parser.messageText, 0, null, MessengerThreadChat.ROOM_INVITE);
setMessageThreads(prevValue => [...prevValue]);
}, [getMessageThreadWithIndex]);
CreateMessageHook(RoomInviteEvent, onRoomInviteEvent);
const sendMessage = useCallback(() =>
{
if(!messageText || !messageText.length) return;

View File

@ -119,7 +119,7 @@ export const RoomToolsWidgetView: FC<{}> = props =>
<div className="h4 text-muted m-0">{ roomOwner }</div>
</div>
{ roomTags && roomTags.length > 0 && <div className="d-flex gap-2">
{ roomTags.map(tag =>
{ roomTags.map((tag: string) =>
{
return <div className="rounded bg-primary text-white p-1 text-sm">#{ tag }</div>
}) }