diff --git a/src/views/friends/FriendsView.scss b/src/views/friends/FriendsView.scss index 6bfa3033..5ac057b6 100644 --- a/src/views/friends/FriendsView.scss +++ b/src/views/friends/FriendsView.scss @@ -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"; diff --git a/src/views/friends/views/friends-group-item/FriendsGroupItemView.tsx b/src/views/friends/views/friends-group-item/FriendsGroupItemView.tsx index 5e9d745e..888c0c57 100644 --- a/src/views/friends/views/friends-group-item/FriendsGroupItemView.tsx +++ b/src/views/friends/views/friends-group-item/FriendsGroupItemView.tsx @@ -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 = 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(false); @@ -20,8 +21,10 @@ export const FriendsGroupItemView: FC = 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 = 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 = props => if(!friend) return null; return ( - - + +
e.stopPropagation() }> + +
{ friend.name }
{ !isExpanded && @@ -60,14 +73,14 @@ export const FriendsGroupItemView: FC = props => } { friend.online && } - setIsExpanded(true) } title={ LocalizeText('infostand.link.relationship') } /> + } { isExpanded && <> - updateRelationship(MessengerFriend.RELATIONSHIP_HEART) } /> - updateRelationship(MessengerFriend.RELATIONSHIP_SMILE) } /> - updateRelationship(MessengerFriend.RELATIONSHIP_BOBBA) } /> - updateRelationship(MessengerFriend.RELATIONSHIP_NONE) } /> + updateRelationship(e, MessengerFriend.RELATIONSHIP_HEART) } /> + updateRelationship(e, MessengerFriend.RELATIONSHIP_SMILE) } /> + updateRelationship(e, MessengerFriend.RELATIONSHIP_BOBBA) } /> + updateRelationship(e, MessengerFriend.RELATIONSHIP_NONE) } /> } { children } diff --git a/src/views/friends/views/friends-group-item/FriendsGroupItemView.types.ts b/src/views/friends/views/friends-group-item/FriendsGroupItemView.types.ts index ed8ccd86..392d57d7 100644 --- a/src/views/friends/views/friends-group-item/FriendsGroupItemView.types.ts +++ b/src/views/friends/views/friends-group-item/FriendsGroupItemView.types.ts @@ -5,4 +5,5 @@ export interface FriendsGroupItemViewProps extends NitroLayoutFlexProps { friend: MessengerFriend; selected?: boolean; + selectFriend: () => void; } diff --git a/src/views/friends/views/friends-group/FriendsGroupView.tsx b/src/views/friends/views/friends-group/FriendsGroupView.tsx index 55b82fca..e1cad75e 100644 --- a/src/views/friends/views/friends-group/FriendsGroupView.tsx +++ b/src/views/friends/views/friends-group/FriendsGroupView.tsx @@ -4,15 +4,15 @@ import { FriendsGroupViewProps } from './FriendsGroupView.types'; export const FriendsGroupView: FC = 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 ; + return selectFriend(item.id) } />; }) } ); diff --git a/src/views/friends/views/friends-group/FriendsGroupView.types.ts b/src/views/friends/views/friends-group/FriendsGroupView.types.ts index 5ce24421..3400723c 100644 --- a/src/views/friends/views/friends-group/FriendsGroupView.types.ts +++ b/src/views/friends/views/friends-group/FriendsGroupView.types.ts @@ -3,4 +3,6 @@ import { MessengerFriend } from '../../common/MessengerFriend'; export interface FriendsGroupViewProps { list: MessengerFriend[]; + selectedFriendsIds: number[]; + selectFriend: (userId: number) => void; } diff --git a/src/views/friends/views/friends-list/FriendsListView.tsx b/src/views/friends/views/friends-list/FriendsListView.tsx index 7ceaa48d..66f2cb2e 100644 --- a/src/views/friends/views/friends-list/FriendsListView.tsx +++ b/src/views/friends/views/friends-list/FriendsListView.tsx @@ -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,39 +17,107 @@ const MODE_SEARCH: number = 1; export const FriendsListView: FC = props => { const { onlineFriends = [], offlineFriends = [], friendRequests = [], onCloseClick = null } = props; - const [ selectedFriends, setSelectedFriends ] = useState([]); - const [ mode, setMode ] = useState(0); - useEffect(() => + const [ selectedFriendsIds, setSelectedFriendsIds ] = useState([]); + const [ mode, setMode ] = useState(0); + + const [ showRoomInvite, setShowRoomInvite ] = useState(false); + const [ showRemoveFriendsConfirmation, setShowRemoveFriendsConfirmation ] = useState(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 ( - - - - setMode(MODE_FRIENDS) }> - { LocalizeText('friendlist.friends') } - - setMode(MODE_SEARCH) }> - { LocalizeText('generic.search') } - - - - { (mode === MODE_FRIENDS) && - - - - - - - - - } - { (mode === MODE_SEARCH) && - } - - + <> + + + + setMode(MODE_FRIENDS) }> + { LocalizeText('friendlist.friends') } + + setMode(MODE_SEARCH) }> + { LocalizeText('generic.search') } + + + + { (mode === MODE_FRIENDS) && + <> + + + + + + + + + + { selectedFriendsIds && selectedFriendsIds.length > 0 &&
+ + +
} + + } + { (mode === MODE_SEARCH) && + } +
+
+ { showRoomInvite && + setShowRoomInvite(false) } sendRoomInvite={ sendRoomInvite } /> } + { showRemoveFriendsConfirmation && + setShowRemoveFriendsConfirmation(false) } removeSelectedFriends={ removeSelectedFriends } /> } + ); }; diff --git a/src/views/friends/views/friends-remove-confirmation/FriendsRemoveConfirmationView.tsx b/src/views/friends/views/friends-remove-confirmation/FriendsRemoveConfirmationView.tsx new file mode 100644 index 00000000..cc3327a8 --- /dev/null +++ b/src/views/friends/views/friends-remove-confirmation/FriendsRemoveConfirmationView.tsx @@ -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 = props => +{ + const { selectedFriendsIds = null, removeFriendsText = null, removeSelectedFriends = null, onCloseClick = null } = props; + + return ( + + + +
{ removeFriendsText }
+
+ + +
+
+
+ ); +}; diff --git a/src/views/friends/views/friends-remove-confirmation/FriendsRemoveConfirmationView.types.ts b/src/views/friends/views/friends-remove-confirmation/FriendsRemoveConfirmationView.types.ts new file mode 100644 index 00000000..eeff1c9c --- /dev/null +++ b/src/views/friends/views/friends-remove-confirmation/FriendsRemoveConfirmationView.types.ts @@ -0,0 +1,7 @@ +export interface FriendsRemoveConfirmationViewProps +{ + selectedFriendsIds: number[]; + removeFriendsText: string; + removeSelectedFriends: () => void; + onCloseClick: () => void; +} diff --git a/src/views/friends/views/friends-room-invite/FriendsRoomInviteView.tsx b/src/views/friends/views/friends-room-invite/FriendsRoomInviteView.tsx new file mode 100644 index 00000000..3c4c9d26 --- /dev/null +++ b/src/views/friends/views/friends-room-invite/FriendsRoomInviteView.tsx @@ -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 = props => +{ + const { selectedFriendsIds = null, onCloseClick = null, sendRoomInvite = null } = props; + + const [ roomInviteMessage, setRoomInviteMessage ] = useState(''); + + return ( + + + + { LocalizeText('friendlist.invite.summary', ['count'], [selectedFriendsIds.length.toString()]) } + +
{ LocalizeText('friendlist.invite.note') }
+
+ + +
+
+
+ ); +}; diff --git a/src/views/friends/views/friends-room-invite/FriendsRoomInviteView.types.ts b/src/views/friends/views/friends-room-invite/FriendsRoomInviteView.types.ts new file mode 100644 index 00000000..1fc54ff3 --- /dev/null +++ b/src/views/friends/views/friends-room-invite/FriendsRoomInviteView.types.ts @@ -0,0 +1,6 @@ +export interface FriendsRoomInviteViewProps +{ + selectedFriendsIds: number[]; + onCloseClick: () => void; + sendRoomInvite: (message: string) => void; +} diff --git a/src/views/friends/views/messenger/FriendsMessengerView.tsx b/src/views/friends/views/messenger/FriendsMessengerView.tsx index 5544e2b1..919f3bd4 100644 --- a/src/views/friends/views/messenger/FriendsMessengerView.tsx +++ b/src/views/friends/views/messenger/FriendsMessengerView.tsx @@ -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; diff --git a/src/views/room/widgets/room-tools/RoomToolsWidgetView.tsx b/src/views/room/widgets/room-tools/RoomToolsWidgetView.tsx index b4674424..f383a6af 100644 --- a/src/views/room/widgets/room-tools/RoomToolsWidgetView.tsx +++ b/src/views/room/widgets/room-tools/RoomToolsWidgetView.tsx @@ -119,7 +119,7 @@ export const RoomToolsWidgetView: FC<{}> = props =>
{ roomOwner }
{ roomTags && roomTags.length > 0 &&
- { roomTags.map(tag => + { roomTags.map((tag: string) => { return
#{ tag }
}) }