mirror of
https://github.com/billsonnn/nitro-react.git
synced 2024-11-23 14:40:50 +01:00
Messenger Chat
This commit is contained in:
parent
b5850c1995
commit
7a6fe87aed
@ -19,6 +19,10 @@ $nitro-card-tabs-height: 33px;
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
|
||||||
|
.theme-primary {
|
||||||
|
border: $border-width solid $border-color;
|
||||||
|
}
|
||||||
|
|
||||||
@include media-breakpoint-down(lg) {
|
@include media-breakpoint-down(lg) {
|
||||||
|
|
||||||
.draggable-window {
|
.draggable-window {
|
||||||
|
@ -11,7 +11,7 @@ export const NitroCardView: FC<NitroCardViewProps> = props =>
|
|||||||
<NitroCardContextProvider value={ { theme, simple } }>
|
<NitroCardContextProvider value={ { theme, simple } }>
|
||||||
<div className="nitro-card-responsive">
|
<div className="nitro-card-responsive">
|
||||||
<DraggableWindow { ...rest }>
|
<DraggableWindow { ...rest }>
|
||||||
<div className={ 'nitro-card d-flex flex-column rounded border shadow overflow-hidden ' + className }>
|
<div className={ `nitro-card d-flex flex-column rounded shadow overflow-hidden theme-${theme} ${className}` }>
|
||||||
{ children }
|
{ children }
|
||||||
</div>
|
</div>
|
||||||
</DraggableWindow>
|
</DraggableWindow>
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import { FriendListFragmentEvent, FriendListUpdateEvent, FriendRequestsEvent, GetFriendRequestsComposer, MessengerInitEvent, NewConsoleMessageEvent } from '@nitrots/nitro-renderer';
|
import { FriendListFragmentEvent, FriendListUpdateEvent, FriendRequestsEvent, GetFriendRequestsComposer, MessengerInitEvent, NewConsoleMessageEvent } from '@nitrots/nitro-renderer';
|
||||||
import { FC, useCallback } from 'react';
|
import { FC, useCallback } from 'react';
|
||||||
|
import { GetSessionDataManager } from '../../api';
|
||||||
import { CreateMessageHook, SendMessageHook } from '../../hooks/messages/message-event';
|
import { CreateMessageHook, SendMessageHook } from '../../hooks/messages/message-event';
|
||||||
|
import { MessengerChatMessage } from './common/MessengerChatMessage';
|
||||||
import { MessengerSettings } from './common/MessengerSettings';
|
import { MessengerSettings } from './common/MessengerSettings';
|
||||||
import { useFriendsContext } from './context/FriendsContext';
|
import { useFriendsContext } from './context/FriendsContext';
|
||||||
import { FriendsActions } from './reducers/FriendsReducer';
|
import { FriendsActions } from './reducers/FriendsReducer';
|
||||||
@ -68,17 +70,17 @@ export const FriendsMessageHandler: FC<{}> = props =>
|
|||||||
{
|
{
|
||||||
const parser = event.getParser();
|
const parser = event.getParser();
|
||||||
|
|
||||||
const activeChat = activeChats.find(c => c.friendId === parser.senderId);
|
let userId = parser.senderId;
|
||||||
|
|
||||||
if(activeChat)
|
if(userId === GetSessionDataManager().userId) userId = 0;
|
||||||
{
|
|
||||||
|
|
||||||
}
|
dispatchFriendsState({
|
||||||
else
|
type: FriendsActions.ADD_CHAT_MESSAGE,
|
||||||
{
|
payload: {
|
||||||
|
chatMessage: new MessengerChatMessage(MessengerChatMessage.MESSAGE, userId, parser.messageText, parser.secondsSinceSent, parser.extraData)
|
||||||
}
|
}
|
||||||
}, [ friendsState, dispatchFriendsState ]);
|
});
|
||||||
|
}, [ dispatchFriendsState ]);
|
||||||
|
|
||||||
CreateMessageHook(MessengerInitEvent, onMessengerInitEvent);
|
CreateMessageHook(MessengerInitEvent, onMessengerInitEvent);
|
||||||
CreateMessageHook(FriendListFragmentEvent, onFriendsFragmentEvent);
|
CreateMessageHook(FriendListFragmentEvent, onFriendsFragmentEvent);
|
||||||
|
@ -3,3 +3,4 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@import './views/friend-bar/FriendBarView';
|
@import './views/friend-bar/FriendBarView';
|
||||||
|
@import './views/messenger/FriendsMessengerView';
|
||||||
|
@ -95,7 +95,7 @@ export const FriendsView: FC<{}> = props =>
|
|||||||
return (
|
return (
|
||||||
<FriendsContextProvider value={ { friendsState, dispatchFriendsState } }>
|
<FriendsContextProvider value={ { friendsState, dispatchFriendsState } }>
|
||||||
<FriendsMessageHandler />
|
<FriendsMessageHandler />
|
||||||
{ isReady && createPortal(<FriendBarView />, document.getElementById('toolbar-friend-bar-container')) }
|
{ isReady && createPortal(<FriendBarView onlineFriends={ onlineFriends } />, document.getElementById('toolbar-friend-bar-container')) }
|
||||||
{ isListVisible && <FriendsListView onlineFriends={ onlineFriends } offlineFriends={ offlineFriends } friendRequests={ requests } onCloseClick={ () => setIsListVisible(false) } /> }
|
{ isListVisible && <FriendsListView onlineFriends={ onlineFriends } offlineFriends={ offlineFriends } friendRequests={ requests } onCloseClick={ () => setIsListVisible(false) } /> }
|
||||||
<FriendsMessengerView />
|
<FriendsMessengerView />
|
||||||
</FriendsContextProvider>
|
</FriendsContextProvider>
|
||||||
|
@ -1,20 +1,24 @@
|
|||||||
import { MessengerChatMessage } from './MessengerChatMessage';
|
import { MessengerChatMessage } from './MessengerChatMessage';
|
||||||
|
import { MessengerChatMessageGroup } from './MessengerChatMessageGroup';
|
||||||
export class MessengerChat
|
export class MessengerChat
|
||||||
{
|
{
|
||||||
private _friendId: number;
|
private _friendId: number;
|
||||||
private _isRead: boolean;
|
private _isRead: boolean;
|
||||||
private _messages: MessengerChatMessage[];
|
private _messageGroups: MessengerChatMessageGroup[];
|
||||||
|
|
||||||
constructor(friendId: number, isRead: boolean = true)
|
constructor(friendId: number, isRead: boolean = true)
|
||||||
{
|
{
|
||||||
this._friendId = friendId;
|
this._friendId = friendId;
|
||||||
this._isRead = isRead;
|
this._isRead = isRead;
|
||||||
this._messages = [];
|
this._messageGroups = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
public addMessage(type: number, senderId: number, message: string, sentAt: number, extraData?: string): void
|
public addMessage(message: MessengerChatMessage): void
|
||||||
{
|
{
|
||||||
this._messages.push(new MessengerChatMessage(type, senderId, message, sentAt, extraData));
|
if(!this.lastMessageGroup || this.lastMessageGroup.userId !== message.senderId) this._messageGroups.push(new MessengerChatMessageGroup(message.senderId));
|
||||||
|
|
||||||
|
this.lastMessageGroup.addMessage(message);
|
||||||
|
this._isRead = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public get friendId(): number
|
public get friendId(): number
|
||||||
@ -27,8 +31,13 @@ export class MessengerChat
|
|||||||
return this._isRead;
|
return this._isRead;
|
||||||
}
|
}
|
||||||
|
|
||||||
public get messages(): MessengerChatMessage[]
|
public get messageGroups(): MessengerChatMessageGroup[]
|
||||||
{
|
{
|
||||||
return this._messages;
|
return this._messageGroups;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get lastMessageGroup(): MessengerChatMessageGroup
|
||||||
|
{
|
||||||
|
return this._messageGroups[this._messageGroups.length - 1];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
export class MessengerChatMessage
|
export class MessengerChatMessage
|
||||||
{
|
{
|
||||||
|
public static MESSAGE: number = 0;
|
||||||
|
public static ROOM_INVITE: number = 1;
|
||||||
|
public static SYSTEM_NOTIFICATION: number = 2;
|
||||||
|
|
||||||
private _type: number;
|
private _type: number;
|
||||||
private _senderId: number;
|
private _senderId: number;
|
||||||
private _message: string;
|
private _message: string;
|
||||||
|
28
src/views/friends/common/MessengerChatMessageGroup.ts
Normal file
28
src/views/friends/common/MessengerChatMessageGroup.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { MessengerChatMessage } from './MessengerChatMessage';
|
||||||
|
|
||||||
|
export class MessengerChatMessageGroup
|
||||||
|
{
|
||||||
|
private _userId: number;
|
||||||
|
private _messages: MessengerChatMessage[];
|
||||||
|
|
||||||
|
constructor(userId: number)
|
||||||
|
{
|
||||||
|
this._userId = userId;
|
||||||
|
this._messages = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public addMessage(message: MessengerChatMessage): void
|
||||||
|
{
|
||||||
|
this._messages.push(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public get userId(): number
|
||||||
|
{
|
||||||
|
return this._userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get messages(): MessengerChatMessage[]
|
||||||
|
{
|
||||||
|
return this._messages;
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
import { FriendListUpdateParser, FriendParser, FriendRequestData } from '@nitrots/nitro-renderer';
|
import { FriendListUpdateParser, FriendParser, FriendRequestData } from '@nitrots/nitro-renderer';
|
||||||
import { Reducer } from 'react';
|
import { Reducer } from 'react';
|
||||||
import { MessengerChat } from '../common/MessengerChat';
|
import { MessengerChat } from '../common/MessengerChat';
|
||||||
|
import { MessengerChatMessage } from '../common/MessengerChatMessage';
|
||||||
import { MessengerFriend } from '../common/MessengerFriend';
|
import { MessengerFriend } from '../common/MessengerFriend';
|
||||||
import { MessengerRequest } from '../common/MessengerRequest';
|
import { MessengerRequest } from '../common/MessengerRequest';
|
||||||
import { MessengerSettings } from '../common/MessengerSettings';
|
import { MessengerSettings } from '../common/MessengerSettings';
|
||||||
@ -29,6 +30,8 @@ export interface IFriendsAction
|
|||||||
update?: FriendListUpdateParser;
|
update?: FriendListUpdateParser;
|
||||||
requests?: FriendRequestData[];
|
requests?: FriendRequestData[];
|
||||||
chats?: MessengerChat[];
|
chats?: MessengerChat[];
|
||||||
|
chatMessage?: MessengerChatMessage;
|
||||||
|
numberValue?: number;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,6 +43,7 @@ export class FriendsActions
|
|||||||
public static PROCESS_UPDATE: string = 'FA_PROCESS_UPDATE';
|
public static PROCESS_UPDATE: string = 'FA_PROCESS_UPDATE';
|
||||||
public static PROCESS_REQUESTS: string = 'FA_PROCESS_REQUESTS';
|
public static PROCESS_REQUESTS: string = 'FA_PROCESS_REQUESTS';
|
||||||
public static SET_ACTIVE_CHATS: string = 'FA_SET_ACTIVE_CHATS';
|
public static SET_ACTIVE_CHATS: string = 'FA_SET_ACTIVE_CHATS';
|
||||||
|
public static ADD_CHAT_MESSAGE: string = 'FA_ADD_CHAT_MESSAGE';
|
||||||
}
|
}
|
||||||
|
|
||||||
export const initialFriends: IFriendsState = {
|
export const initialFriends: IFriendsState = {
|
||||||
@ -143,6 +147,24 @@ export const FriendsReducer: Reducer<IFriendsState, IFriendsAction> = (state, ac
|
|||||||
|
|
||||||
return { ...state, activeChats };
|
return { ...state, activeChats };
|
||||||
}
|
}
|
||||||
|
case FriendsActions.ADD_CHAT_MESSAGE: {
|
||||||
|
const message = action.payload.chatMessage;
|
||||||
|
const toFriendId = action.payload.numberValue;
|
||||||
|
|
||||||
|
const activeChats = Array.from(state.activeChats);
|
||||||
|
|
||||||
|
let activeChatIndex = activeChats.findIndex(c => c.friendId === toFriendId ? toFriendId : message.senderId);
|
||||||
|
|
||||||
|
if(activeChatIndex === -1)
|
||||||
|
{
|
||||||
|
activeChats.push(new MessengerChat(message.senderId, false));
|
||||||
|
activeChatIndex = activeChats.length - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
activeChats[activeChatIndex].addMessage(message);
|
||||||
|
|
||||||
|
return { ...state, activeChats };
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { FollowFriendMessageComposer, MouseEventType, UserProfileComposer } from '@nitrots/nitro-renderer';
|
import { FollowFriendMessageComposer, MouseEventType, UserProfileComposer } from '@nitrots/nitro-renderer';
|
||||||
import { FC, useCallback, useEffect, useRef, useState } from 'react';
|
import { FC, useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import { LocalizeText } from '../../../../api';
|
import { LocalizeText, OpenMessengerChat } from '../../../../api';
|
||||||
import { SendMessageHook } from '../../../../hooks/messages';
|
import { SendMessageHook } from '../../../../hooks/messages';
|
||||||
import { AvatarImageView } from '../../../shared/avatar-image/AvatarImageView';
|
import { AvatarImageView } from '../../../shared/avatar-image/AvatarImageView';
|
||||||
import { FriendBarItemViewProps } from './FriendBarItemView.types';
|
import { FriendBarItemViewProps } from './FriendBarItemView.types';
|
||||||
@ -16,6 +16,13 @@ export const FriendBarItemView: FC<FriendBarItemViewProps> = props =>
|
|||||||
SendMessageHook(new FollowFriendMessageComposer(friend.id));
|
SendMessageHook(new FollowFriendMessageComposer(friend.id));
|
||||||
}, [ friend ]);
|
}, [ friend ]);
|
||||||
|
|
||||||
|
const openMessengerChat = useCallback(() =>
|
||||||
|
{
|
||||||
|
if(!friend) return;
|
||||||
|
|
||||||
|
OpenMessengerChat(friend.id);
|
||||||
|
}, [ friend ]);
|
||||||
|
|
||||||
const openProfile = useCallback(() =>
|
const openProfile = useCallback(() =>
|
||||||
{
|
{
|
||||||
SendMessageHook(new UserProfileComposer(friend.id));
|
SendMessageHook(new UserProfileComposer(friend.id));
|
||||||
@ -59,7 +66,7 @@ export const FriendBarItemView: FC<FriendBarItemViewProps> = props =>
|
|||||||
<div className="text-truncate">{ friend.name }</div>
|
<div className="text-truncate">{ friend.name }</div>
|
||||||
{ isVisible &&
|
{ isVisible &&
|
||||||
<div className="d-flex justify-content-between">
|
<div className="d-flex justify-content-between">
|
||||||
<i className="icon icon-fb-chat cursor-pointer" />
|
<i onClick={ openMessengerChat } className="icon icon-fb-chat cursor-pointer" />
|
||||||
{ friend.followingAllowed && <i onClick={ followFriend } className="icon icon-fb-visit cursor-pointer" /> }
|
{ friend.followingAllowed && <i onClick={ followFriend } className="icon icon-fb-visit cursor-pointer" /> }
|
||||||
<i onClick={ openProfile } className="icon icon-fb-profile cursor-pointer" />
|
<i onClick={ openProfile } className="icon icon-fb-profile cursor-pointer" />
|
||||||
</div> }
|
</div> }
|
||||||
|
@ -1,20 +1,14 @@
|
|||||||
import { FC, useMemo, useState } from 'react';
|
import { FC, useMemo, useState } from 'react';
|
||||||
import { useFriendsContext } from '../../context/FriendsContext';
|
|
||||||
import { FriendBarItemView } from '../friend-bar-item/FriendBarItemView';
|
import { FriendBarItemView } from '../friend-bar-item/FriendBarItemView';
|
||||||
import { FriendBarViewProps } from './FriendBarView.types';
|
import { FriendBarViewProps } from './FriendBarView.types';
|
||||||
|
|
||||||
export const FriendBarView: FC<FriendBarViewProps> = props =>
|
export const FriendBarView: FC<FriendBarViewProps> = props =>
|
||||||
{
|
{
|
||||||
const { friendsState = null } = useFriendsContext();
|
const { onlineFriends = null } = props;
|
||||||
const { friends = null } = friendsState;
|
|
||||||
const [ indexOffset, setIndexOffset ] = useState(0);
|
const [ indexOffset, setIndexOffset ] = useState(0);
|
||||||
const [ maxDisplayCount, setMaxDisplayCount ] = useState(3);
|
const [ maxDisplayCount, setMaxDisplayCount ] = useState(3);
|
||||||
|
|
||||||
const onlineFriends = useMemo(() =>
|
|
||||||
{
|
|
||||||
return friends.filter(friend => friend.online);
|
|
||||||
}, [ friends ]);
|
|
||||||
|
|
||||||
const canDecreaseIndex = useMemo(() =>
|
const canDecreaseIndex = useMemo(() =>
|
||||||
{
|
{
|
||||||
if(indexOffset === 0) return false;
|
if(indexOffset === 0) return false;
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
|
import { MessengerFriend } from './../../common/MessengerFriend';
|
||||||
export interface FriendBarViewProps
|
export interface FriendBarViewProps
|
||||||
{
|
{
|
||||||
|
onlineFriends: MessengerFriend[];
|
||||||
}
|
}
|
||||||
|
@ -1,71 +0,0 @@
|
|||||||
import { SetRelationshipStatusComposer } from '@nitrots/nitro-renderer';
|
|
||||||
import { FollowFriendMessageComposer } from '@nitrots/nitro-renderer/src/nitro/communication/messages/outgoing/friendlist/FollowFriendMessageComposer';
|
|
||||||
import { FC, useCallback, useState } from 'react';
|
|
||||||
import { LocalizeText, OpenMessengerChat } from '../../../../api';
|
|
||||||
import { SendMessageHook } from '../../../../hooks';
|
|
||||||
import { UserProfileIconView } from '../../../shared/user-profile-icon/UserProfileIconView';
|
|
||||||
import { MessengerFriend } from '../../common/MessengerFriend';
|
|
||||||
import { FriendsListItemViewProps } from './FriendsListItemView.types';
|
|
||||||
|
|
||||||
export const FriendsListItemView: FC<FriendsListItemViewProps> = props =>
|
|
||||||
{
|
|
||||||
const { friend = null } = props;
|
|
||||||
|
|
||||||
const [ isExpanded, setIsExpanded ] = useState<boolean>(false);
|
|
||||||
|
|
||||||
const followFriend = useCallback(() =>
|
|
||||||
{
|
|
||||||
if(!friend) return;
|
|
||||||
|
|
||||||
SendMessageHook(new FollowFriendMessageComposer(friend.id));
|
|
||||||
}, [ friend ]);
|
|
||||||
|
|
||||||
const openMessengerChat = useCallback(() =>
|
|
||||||
{
|
|
||||||
if(!friend) return;
|
|
||||||
|
|
||||||
OpenMessengerChat(friend.id);
|
|
||||||
}, [ friend ]);
|
|
||||||
|
|
||||||
const getCurrentRelationshipName = useCallback(() =>
|
|
||||||
{
|
|
||||||
if(!friend) return 'none';
|
|
||||||
|
|
||||||
switch(friend.relationshipStatus)
|
|
||||||
{
|
|
||||||
case MessengerFriend.RELATIONSHIP_HEART: return 'heart';
|
|
||||||
case MessengerFriend.RELATIONSHIP_SMILE: return 'smile';
|
|
||||||
case MessengerFriend.RELATIONSHIP_BOBBA: return 'bobba';
|
|
||||||
default: return 'none';
|
|
||||||
}
|
|
||||||
}, [ friend ]);
|
|
||||||
|
|
||||||
const updateRelationship = useCallback((type: number) =>
|
|
||||||
{
|
|
||||||
if(type !== friend.relationshipStatus) SendMessageHook(new SetRelationshipStatusComposer(friend.id, type));
|
|
||||||
|
|
||||||
setIsExpanded(false);
|
|
||||||
}, [ friend ]);
|
|
||||||
|
|
||||||
if(!friend) return null;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="px-2 py-1 d-flex gap-1 align-items-center">
|
|
||||||
<UserProfileIconView userId={ friend.id } />
|
|
||||||
<div>{ friend.name }</div>
|
|
||||||
<div className="ms-auto d-flex align-items-center gap-1">
|
|
||||||
{ !isExpanded && <>
|
|
||||||
{ friend.followingAllowed && <i className="icon icon-friendlist-follow cursor-pointer" onClick={ followFriend } title={ LocalizeText('friendlist.tip.follow') } /> }
|
|
||||||
{ friend.online && <i className="icon icon-friendlist-chat cursor-pointer" onClick={ openMessengerChat } title={ LocalizeText('friendlist.tip.im') } /> }
|
|
||||||
<i className={ 'icon cursor-pointer icon-relationship-' + getCurrentRelationshipName() } onClick={ () => setIsExpanded(true) } title={ LocalizeText('infostand.link.relationship') } />
|
|
||||||
</> }
|
|
||||||
{ isExpanded && <>
|
|
||||||
<i className="icon icon-relationship-heart cursor-pointer" onClick={ () => updateRelationship(MessengerFriend.RELATIONSHIP_HEART) } />
|
|
||||||
<i className="icon icon-relationship-smile cursor-pointer" onClick={ () => updateRelationship(MessengerFriend.RELATIONSHIP_SMILE) } />
|
|
||||||
<i className="icon icon-relationship-bobba cursor-pointer" onClick={ () => updateRelationship(MessengerFriend.RELATIONSHIP_BOBBA) } />
|
|
||||||
<i className="icon icon-relationship-none cursor-pointer" onClick={ () => updateRelationship(MessengerFriend.RELATIONSHIP_NONE) } />
|
|
||||||
</> }
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,6 +0,0 @@
|
|||||||
import { MessengerFriend } from '../../common/MessengerFriend';
|
|
||||||
|
|
||||||
export interface FriendsListItemViewProps
|
|
||||||
{
|
|
||||||
friend: MessengerFriend;
|
|
||||||
}
|
|
@ -1,6 +1,6 @@
|
|||||||
import { FollowFriendMessageComposer, SetRelationshipStatusComposer } from '@nitrots/nitro-renderer';
|
import { FollowFriendMessageComposer, SetRelationshipStatusComposer } from '@nitrots/nitro-renderer';
|
||||||
import { FC, useCallback, useState } from 'react';
|
import { FC, useCallback, useState } from 'react';
|
||||||
import { LocalizeText } from '../../../../api';
|
import { LocalizeText, OpenMessengerChat } from '../../../../api';
|
||||||
import { SendMessageHook } from '../../../../hooks';
|
import { SendMessageHook } from '../../../../hooks';
|
||||||
import { UserProfileIconView } from '../../../shared/user-profile-icon/UserProfileIconView';
|
import { UserProfileIconView } from '../../../shared/user-profile-icon/UserProfileIconView';
|
||||||
import { MessengerFriend } from '../../common/MessengerFriend';
|
import { MessengerFriend } from '../../common/MessengerFriend';
|
||||||
@ -19,6 +19,13 @@ export const FriendsGroupItemView: FC<FriendsGroupItemViewProps> = props =>
|
|||||||
SendMessageHook(new FollowFriendMessageComposer(friend.id));
|
SendMessageHook(new FollowFriendMessageComposer(friend.id));
|
||||||
}, [ friend ]);
|
}, [ friend ]);
|
||||||
|
|
||||||
|
const openMessengerChat = useCallback(() =>
|
||||||
|
{
|
||||||
|
if(!friend) return;
|
||||||
|
|
||||||
|
OpenMessengerChat(friend.id);
|
||||||
|
}, [ friend ]);
|
||||||
|
|
||||||
const getCurrentRelationshipName = useCallback(() =>
|
const getCurrentRelationshipName = useCallback(() =>
|
||||||
{
|
{
|
||||||
if(!friend) return 'none';
|
if(!friend) return 'none';
|
||||||
@ -48,7 +55,7 @@ export const FriendsGroupItemView: FC<FriendsGroupItemViewProps> = props =>
|
|||||||
<div className="ms-auto d-flex align-items-center gap-1">
|
<div className="ms-auto d-flex align-items-center gap-1">
|
||||||
{ !isExpanded && <>
|
{ !isExpanded && <>
|
||||||
{ friend.followingAllowed && <i onClick={ followFriend } className="icon icon-friendlist-follow cursor-pointer" title={ LocalizeText('friendlist.tip.follow') } /> }
|
{ friend.followingAllowed && <i onClick={ followFriend } className="icon icon-friendlist-follow cursor-pointer" title={ LocalizeText('friendlist.tip.follow') } /> }
|
||||||
{ friend.online && <i className="icon icon-friendlist-chat cursor-pointer" title={ LocalizeText('friendlist.tip.im') } /> }
|
{ friend.online && <i className="icon icon-friendlist-chat cursor-pointer" onClick={ openMessengerChat } title={ LocalizeText('friendlist.tip.im') } /> }
|
||||||
<i className={ 'icon cursor-pointer icon-relationship-' + getCurrentRelationshipName() } onClick={ () => setIsExpanded(true) } title={ LocalizeText('infostand.link.relationship') } />
|
<i className={ 'icon cursor-pointer icon-relationship-' + getCurrentRelationshipName() } onClick={ () => setIsExpanded(true) } title={ LocalizeText('infostand.link.relationship') } />
|
||||||
</> }
|
</> }
|
||||||
{ isExpanded && <>
|
{ isExpanded && <>
|
||||||
|
71
src/views/friends/views/messenger/FriendsMessengerView.scss
Normal file
71
src/views/friends/views/messenger/FriendsMessengerView.scss
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
.nitro-friends-messenger {
|
||||||
|
width: 300px;
|
||||||
|
|
||||||
|
.friend-head {
|
||||||
|
position: relative;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.avatar-image {
|
||||||
|
position: absolute;
|
||||||
|
margin-left: -27px;
|
||||||
|
margin-top: -27px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-title {
|
||||||
|
margin-top: -21px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-messages {
|
||||||
|
height: 200px;
|
||||||
|
min-height: 200px;
|
||||||
|
overflow-y: auto;
|
||||||
|
|
||||||
|
.message-avatar {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
|
||||||
|
.avatar-image {
|
||||||
|
position: absolute;
|
||||||
|
margin-left: -22px;
|
||||||
|
margin-top: -25px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.messages-group-left {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
position: absolute;
|
||||||
|
content: ' ';
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border-right: 8px solid rgba(var(--bs-light-rgb), var(--bs-bg-opacity)) !important;
|
||||||
|
border-top: 8px solid transparent;
|
||||||
|
border-bottom: 8px solid transparent;
|
||||||
|
top: 10px;
|
||||||
|
left: -8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.messages-group-right {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
position: absolute;
|
||||||
|
content: ' ';
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border-left: 8px solid rgba(var(--bs-light-rgb), var(--bs-bg-opacity)) !important;
|
||||||
|
border-top: 8px solid transparent;
|
||||||
|
border-bottom: 8px solid transparent;
|
||||||
|
top: 10px;
|
||||||
|
right: -8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,20 +1,25 @@
|
|||||||
import { ILinkEventTracker, NitroEvent } from '@nitrots/nitro-renderer';
|
import { FollowFriendMessageComposer, ILinkEventTracker, NitroEvent, SendMessageComposer, UserProfileComposer } from '@nitrots/nitro-renderer';
|
||||||
import { FC, useCallback, useEffect, useState } from 'react';
|
import { FC, KeyboardEvent, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { AddEventLinkTracker, LocalizeText, RemoveLinkEventTracker } from '../../../../api';
|
import { AddEventLinkTracker, GetSessionDataManager, LocalizeText, RemoveLinkEventTracker } from '../../../../api';
|
||||||
import { FriendsEvent } from '../../../../events/friends/FriendsEvent';
|
import { FriendsEvent } from '../../../../events/friends/FriendsEvent';
|
||||||
import { useUiEvent } from '../../../../hooks';
|
import { SendMessageHook, useUiEvent } from '../../../../hooks';
|
||||||
import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../layout';
|
import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../layout';
|
||||||
|
import { AvatarImageView } from '../../../shared/avatar-image/AvatarImageView';
|
||||||
import { MessengerChat } from '../../common/MessengerChat';
|
import { MessengerChat } from '../../common/MessengerChat';
|
||||||
|
import { MessengerChatMessage } from '../../common/MessengerChatMessage';
|
||||||
import { useFriendsContext } from '../../context/FriendsContext';
|
import { useFriendsContext } from '../../context/FriendsContext';
|
||||||
import { FriendsActions } from '../../reducers/FriendsReducer';
|
import { FriendsActions } from '../../reducers/FriendsReducer';
|
||||||
|
|
||||||
export const FriendsMessengerView: FC<{}> = props =>
|
export const FriendsMessengerView: FC<{}> = props =>
|
||||||
{
|
{
|
||||||
const { friendsState = null, dispatchFriendsState = null } = useFriendsContext();
|
const { friendsState = null, dispatchFriendsState = null } = useFriendsContext();
|
||||||
const { activeChats = [] } = friendsState;
|
const { activeChats = [], friends = [] } = friendsState;
|
||||||
|
|
||||||
const [ isVisible, setIsVisible ] = useState(false);
|
const [ isVisible, setIsVisible ] = useState(false);
|
||||||
const [ activeChatIndex, setActiveChatIndex ] = useState(0);
|
const [ selectedChatIndex, setSelectedChatIndex ] = useState(0);
|
||||||
|
const [ message, setMessage ] = useState('');
|
||||||
|
|
||||||
|
const messagesBox = useRef<HTMLDivElement>();
|
||||||
|
|
||||||
const onNitroEvent = useCallback((event: NitroEvent) =>
|
const onNitroEvent = useCallback((event: NitroEvent) =>
|
||||||
{
|
{
|
||||||
@ -35,11 +40,11 @@ export const FriendsMessengerView: FC<{}> = props =>
|
|||||||
const linkReceived = useCallback((url: string) =>
|
const linkReceived = useCallback((url: string) =>
|
||||||
{
|
{
|
||||||
const parts = url.split('/');
|
const parts = url.split('/');
|
||||||
console.log(parts);
|
|
||||||
if(parts.length < 3) return;
|
if(parts.length < 3) return;
|
||||||
|
|
||||||
const friendId = parseInt(parts[2]);
|
const friendId = parseInt(parts[2]);
|
||||||
console.log(friendId);
|
|
||||||
let existingChatIndex = activeChats.findIndex(c => c.friendId === friendId);
|
let existingChatIndex = activeChats.findIndex(c => c.friendId === friendId);
|
||||||
|
|
||||||
if(existingChatIndex === -1)
|
if(existingChatIndex === -1)
|
||||||
@ -57,10 +62,35 @@ export const FriendsMessengerView: FC<{}> = props =>
|
|||||||
existingChatIndex = clonedActiveChats.length - 1;
|
existingChatIndex = clonedActiveChats.length - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
setActiveChatIndex(existingChatIndex);
|
setSelectedChatIndex(existingChatIndex);
|
||||||
setIsVisible(true);
|
setIsVisible(true);
|
||||||
}, [ activeChats, dispatchFriendsState ]);
|
}, [ activeChats, dispatchFriendsState ]);
|
||||||
|
|
||||||
|
const getFriendFigure = useCallback((id: number) =>
|
||||||
|
{
|
||||||
|
const friend = friends.find(f => f.id === id);
|
||||||
|
|
||||||
|
if(!friend) return null;
|
||||||
|
|
||||||
|
return friend.figure;
|
||||||
|
}, [ friends ]);
|
||||||
|
|
||||||
|
const selectedChat = useMemo(() =>
|
||||||
|
{
|
||||||
|
return activeChats[selectedChatIndex];
|
||||||
|
}, [ activeChats, selectedChatIndex ]);
|
||||||
|
|
||||||
|
const selectedChatFriend = useMemo(() =>
|
||||||
|
{
|
||||||
|
if(!selectedChat) return null;
|
||||||
|
|
||||||
|
const friend = friends.find(f => f.id === selectedChat.friendId);
|
||||||
|
|
||||||
|
if(!friend) return null;
|
||||||
|
|
||||||
|
return friend;
|
||||||
|
}, [ friends, selectedChat ]);
|
||||||
|
|
||||||
useEffect(() =>
|
useEffect(() =>
|
||||||
{
|
{
|
||||||
const linkTracker: ILinkEventTracker = {
|
const linkTracker: ILinkEventTracker = {
|
||||||
@ -73,12 +103,99 @@ export const FriendsMessengerView: FC<{}> = props =>
|
|||||||
return () => RemoveLinkEventTracker(linkTracker);
|
return () => RemoveLinkEventTracker(linkTracker);
|
||||||
}, [ linkReceived ]);
|
}, [ linkReceived ]);
|
||||||
|
|
||||||
|
useEffect(() =>
|
||||||
|
{
|
||||||
|
if(!messagesBox || !messagesBox.current) return;
|
||||||
|
|
||||||
|
messagesBox.current.scrollTop = messagesBox.current.scrollHeight;
|
||||||
|
|
||||||
|
}, [ selectedChat ]);
|
||||||
|
|
||||||
|
const followFriend = useCallback(() =>
|
||||||
|
{
|
||||||
|
SendMessageHook(new FollowFriendMessageComposer(selectedChatFriend.id));
|
||||||
|
}, [ selectedChatFriend ]);
|
||||||
|
|
||||||
|
const openProfile = useCallback(() =>
|
||||||
|
{
|
||||||
|
SendMessageHook(new UserProfileComposer(selectedChatFriend.id));
|
||||||
|
}, [ selectedChatFriend ]);
|
||||||
|
|
||||||
|
const sendMessage = useCallback(() =>
|
||||||
|
{
|
||||||
|
if(message.length === 0) return;
|
||||||
|
|
||||||
|
SendMessageHook(new SendMessageComposer(selectedChat.friendId, message));
|
||||||
|
|
||||||
|
dispatchFriendsState({
|
||||||
|
type: FriendsActions.ADD_CHAT_MESSAGE,
|
||||||
|
payload: {
|
||||||
|
chatMessage: new MessengerChatMessage(MessengerChatMessage.MESSAGE, 0, message, (new Date().getMilliseconds())),
|
||||||
|
numberValue: selectedChat.friendId
|
||||||
|
}
|
||||||
|
});
|
||||||
|
setMessage('');
|
||||||
|
}, [ selectedChat, message, dispatchFriendsState ]);
|
||||||
|
|
||||||
|
const onKeyDown = useCallback((event: KeyboardEvent<HTMLInputElement>) =>
|
||||||
|
{
|
||||||
|
if(event.key !== 'Enter') return;
|
||||||
|
|
||||||
|
sendMessage();
|
||||||
|
}, [ sendMessage ]);
|
||||||
|
|
||||||
if(!isVisible) return null;
|
if(!isVisible) return null;
|
||||||
|
|
||||||
return (<NitroCardView className="nitro-friends-messenger" simple={ true }>
|
return (<NitroCardView className="nitro-friends-messenger" simple={ true }>
|
||||||
<NitroCardHeaderView headerText={ LocalizeText('friendlist.friends') } onCloseClick={ () => {} } />
|
<NitroCardHeaderView headerText={ LocalizeText('messenger.window.title', ['OPEN_CHAT_COUNT'], [activeChats.length.toString()]) } onCloseClick={ () => setIsVisible(false) } />
|
||||||
<NitroCardContentView className="p-0">
|
<NitroCardContentView>
|
||||||
|
<div className="d-flex gap-2 overflow-auto pb-1">
|
||||||
|
{ activeChats && activeChats.map((chat, index) =>
|
||||||
|
{
|
||||||
|
return <div key={ index } className="friend-head bg-muted rounded flex-shrink-0 cursor-pointer" onClick={ () => setSelectedChatIndex(index) }>
|
||||||
|
<AvatarImageView figure={ getFriendFigure(chat.friendId) } headOnly={true} direction={3} />
|
||||||
|
</div>;
|
||||||
|
}) }
|
||||||
|
</div>
|
||||||
|
<hr className="bg-dark mt-3 mb-2" />
|
||||||
|
{ selectedChat && selectedChatFriend && <>
|
||||||
|
<div className="text-black chat-title bg-light pe-2 position-absolute">{ LocalizeText('messenger.window.separator', ['FRIEND_NAME'], [ selectedChatFriend.name ]) }</div>
|
||||||
|
<div className="d-flex gap-2 mb-2">
|
||||||
|
<div className="btn-group">
|
||||||
|
<button className="btn btn-sm btn-primary" onClick={ followFriend }>
|
||||||
|
<i className="icon icon-friendlist-follow" />
|
||||||
|
</button>
|
||||||
|
<button className="btn btn-sm btn-primary" onClick={ openProfile }>
|
||||||
|
<i className="icon icon-user-profile" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<button className="btn btn-sm btn-danger">{ LocalizeText('messenger.window.button.report') }</button>
|
||||||
|
<button className="btn btn-sm btn-primary ms-auto"><i className="fas fa-times" /></button>
|
||||||
|
</div>
|
||||||
|
<div ref={ messagesBox } className="bg-muted p-2 rounded chat-messages mb-2 d-flex flex-column">
|
||||||
|
{ selectedChat.messageGroups.map((group, groupIndex) =>
|
||||||
|
{
|
||||||
|
return <div key={ groupIndex } className={ 'd-flex gap-2 w-100 justify-content-' + (group.userId === 0 ? 'end' : 'start') }>
|
||||||
|
{ group.userId !== 0 && <div className="message-avatar flex-shrink-0">
|
||||||
|
<AvatarImageView figure={ selectedChatFriend.figure } direction={ 2 } />
|
||||||
|
</div> }
|
||||||
|
<div className={ 'bg-light text-black border-radius mb-2 rounded py-1 px-2 messages-group-' + (group.userId === 0 ? 'right' : 'left') }>
|
||||||
|
{ group.messages.map((message, messageIndex) =>
|
||||||
|
{
|
||||||
|
return <div key={ messageIndex } className="text-break">{ message.message }</div>
|
||||||
|
}) }
|
||||||
|
</div>
|
||||||
|
{ group.userId === 0 && <div className="message-avatar flex-shrink-0">
|
||||||
|
<AvatarImageView figure={ GetSessionDataManager().figure } direction={ 4 } />
|
||||||
|
</div> }
|
||||||
|
</div>;
|
||||||
|
}) }
|
||||||
|
</div>
|
||||||
|
<div className="d-flex gap-2">
|
||||||
|
<input type="text" className="form-control form-control-sm" placeholder={ LocalizeText('messenger.window.input.default', ['FRIEND_NAME'], [ selectedChatFriend.name ]) } value={ message } onChange={ (e) => setMessage(e.target.value) } onKeyDown={ onKeyDown } />
|
||||||
|
<button className="btn btn-sm btn-success" onClick={ sendMessage }>{ LocalizeText('widgets.chatinput.say') }</button>
|
||||||
|
</div>
|
||||||
|
</> }
|
||||||
</NitroCardContentView>
|
</NitroCardContentView>
|
||||||
</NitroCardView>);
|
</NitroCardView>);
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user