mirror of
https://github.com/duckietm/Nitro-V3.git
synced 2026-06-20 15:36:18 +00:00
WIP preserve local changes before duckie merge
This commit is contained in:
@@ -13,13 +13,19 @@ interface FriendsRemoveConfirmationViewProps
|
||||
export const FriendsRemoveConfirmationView: FC<FriendsRemoveConfirmationViewProps> = props =>
|
||||
{
|
||||
const { selectedFriendsIds = null, removeFriendsText = null, removeSelectedFriends = null, onCloseClick = null } = props;
|
||||
const separatorIndex = removeFriendsText.indexOf(':');
|
||||
const removeFriendsLeadText = (separatorIndex >= 0) ? removeFriendsText.substring(0, separatorIndex + 1) : removeFriendsText;
|
||||
const removeFriendsNamesText = (separatorIndex >= 0) ? removeFriendsText.substring(separatorIndex + 1).trimStart() : '';
|
||||
|
||||
return (
|
||||
<NitroCardView className="nitro-friends-remove-confirmation" theme="primary-slim">
|
||||
<NitroCardView className="nitro-friends-remove-confirmation" theme="primary-slim" isResizable={ false } style={ { width: 270, height: 225, minWidth: 270, minHeight: 225, maxWidth: 270, maxHeight: 225 } }>
|
||||
<NitroCardHeaderView headerText={ LocalizeText('friendlist.removefriendconfirm.title') } onCloseClick={ onCloseClick } />
|
||||
<NitroCardContentView className="text-black">
|
||||
<div>{ removeFriendsText }</div>
|
||||
<div className="flex gap-1">
|
||||
<NitroCardContentView className="nitro-friends-remove-confirmation-content text-black">
|
||||
<div className="nitro-friends-remove-confirmation-text">
|
||||
<div>{ removeFriendsLeadText }</div>
|
||||
{ removeFriendsNamesText.length > 0 && <div className="nitro-friends-remove-confirmation-names">{ removeFriendsNamesText }</div> }
|
||||
</div>
|
||||
<div className="nitro-friends-remove-confirmation-actions">
|
||||
<Button fullWidth disabled={ (selectedFriendsIds.length === 0) } variant="danger" onClick={ removeSelectedFriends }>{ LocalizeText('generic.ok') }</Button>
|
||||
<Button fullWidth onClick={ onCloseClick }>{ LocalizeText('generic.cancel') }</Button>
|
||||
</div>
|
||||
|
||||
@@ -15,13 +15,13 @@ export const FriendsRoomInviteView: FC<FriendsRoomInviteViewProps> = props =>
|
||||
const [ roomInviteMessage, setRoomInviteMessage ] = useState<string>('');
|
||||
|
||||
return (
|
||||
<NitroCardView className="nitro-friends-room-invite" theme="primary-slim" uniqueKey="nitro-friends-room-invite">
|
||||
<NitroCardView className="nitro-friends-room-invite" theme="primary-slim" uniqueKey="nitro-friends-room-invite" isResizable={ false } style={ { width: 270, height: 225, minWidth: 270, minHeight: 225, maxWidth: 270, maxHeight: 225 } }>
|
||||
<NitroCardHeaderView headerText={ LocalizeText('friendlist.invite.title') } onCloseClick={ onCloseClick } />
|
||||
<NitroCardContentView className="text-black">
|
||||
{ LocalizeText('friendlist.invite.summary', [ 'count' ], [ selectedFriendsIds.length.toString() ]) }
|
||||
<textarea className="min-h-[calc(1.5em+ .5rem+2px)] px-[.5rem] py-[.25rem] rounded-[.2rem]" maxLength={ 255 } value={ roomInviteMessage } onChange={ event => setRoomInviteMessage(event.target.value) }></textarea>
|
||||
<Text center className="bg-muted rounded p-1">{ LocalizeText('friendlist.invite.note') }</Text>
|
||||
<div className="flex gap-1">
|
||||
<NitroCardContentView className="nitro-friends-room-invite-content text-black" gap={ 2 }>
|
||||
<Text className="nitro-friends-room-invite-summary">{ LocalizeText('friendlist.invite.summary', [ 'count' ], [ selectedFriendsIds.length.toString() ]) }</Text>
|
||||
<textarea className="nitro-friends-room-invite-textarea" maxLength={ 255 } value={ roomInviteMessage } onChange={ event => setRoomInviteMessage(event.target.value) }></textarea>
|
||||
<Text center className="nitro-friends-room-invite-note">{ LocalizeText('friendlist.invite.note') }</Text>
|
||||
<div className="nitro-friends-room-invite-actions">
|
||||
<Button fullWidth disabled={ ((roomInviteMessage.length === 0) || (selectedFriendsIds.length === 0)) } variant="success" onClick={ () => sendRoomInvite(roomInviteMessage) }>{ LocalizeText('friendlist.invite.send') }</Button>
|
||||
<Button fullWidth onClick={ onCloseClick }>{ LocalizeText('generic.cancel') }</Button>
|
||||
</div>
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { HabboSearchComposer, HabboSearchResultData, HabboSearchResultEvent } from '@nitrots/nitro-renderer';
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
import { LocalizeText, OpenMessengerChat, SendMessageComposer } from '../../../../api';
|
||||
import { Column, NitroCardAccordionItemView, NitroCardAccordionSetView, NitroCardAccordionSetViewProps, Text, UserProfileIconView } from '../../../../common';
|
||||
import { Column, LayoutAvatarImageView, NitroCardAccordionItemView, NitroCardAccordionSetView, NitroCardAccordionSetViewProps, Text, UserProfileIconView } from '../../../../common';
|
||||
import { useFriends, useMessageEvent } from '../../../../hooks';
|
||||
import { resolveAvatarFigure } from './resolveAvatarFigure';
|
||||
import { resolveAvatarGender } from './resolveAvatarGender';
|
||||
|
||||
interface FriendsSearchViewProps extends NitroCardAccordionSetViewProps
|
||||
{
|
||||
@@ -17,6 +19,22 @@ export const FriendsSearchView: FC<FriendsSearchViewProps> = props =>
|
||||
const [ otherResults, setOtherResults ] = useState<HabboSearchResultData[]>(null);
|
||||
const { canRequestFriend = null, requestFriend = null } = useFriends();
|
||||
|
||||
const getSearchResultFigure = (result: HabboSearchResultData) =>
|
||||
{
|
||||
if(!result) return null;
|
||||
|
||||
const typedResult = (result as HabboSearchResultData & { figureString?: string; avatarFigure?: string; figure?: string; avatarFigureString?: string });
|
||||
|
||||
return typedResult.figureString || typedResult.avatarFigure || typedResult.figure || typedResult.avatarFigureString || null;
|
||||
};
|
||||
|
||||
const getSearchResultGender = (result: HabboSearchResultData) =>
|
||||
{
|
||||
const typedResult = (result as HabboSearchResultData & { gender?: string | number; avatarGender?: string | number });
|
||||
|
||||
return resolveAvatarGender(typedResult.avatarGender ?? typedResult.gender);
|
||||
};
|
||||
|
||||
useMessageEvent<HabboSearchResultEvent>(HabboSearchResultEvent, event =>
|
||||
{
|
||||
const parser = event.getParser();
|
||||
@@ -55,10 +73,15 @@ export const FriendsSearchView: FC<FriendsSearchViewProps> = props =>
|
||||
{ friendResults.map(result =>
|
||||
{
|
||||
return (
|
||||
<NitroCardAccordionItemView key={ result.avatarId } className="px-2 py-1" justifyContent="between">
|
||||
<div className="flex items-center gap-1">
|
||||
<UserProfileIconView userId={ result.avatarId } />
|
||||
<div>{ result.avatarName }</div>
|
||||
<NitroCardAccordionItemView key={ result.avatarId } className="friends-list-item px-2 py-1" justifyContent="between">
|
||||
<div className="friends-list-user">
|
||||
<div className="friends-list-avatar">
|
||||
<LayoutAvatarImageView figure={ resolveAvatarFigure(getSearchResultFigure(result), getSearchResultGender(result)) } gender={ getSearchResultGender(result) } headOnly={ true } direction={ 2 } />
|
||||
</div>
|
||||
<div>
|
||||
<UserProfileIconView userId={ result.avatarId } />
|
||||
</div>
|
||||
<div className="friends-list-name">{ result.avatarName }</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
{ result.isAvatarOnline &&
|
||||
@@ -82,10 +105,15 @@ export const FriendsSearchView: FC<FriendsSearchViewProps> = props =>
|
||||
{ otherResults.map(result =>
|
||||
{
|
||||
return (
|
||||
<NitroCardAccordionItemView key={ result.avatarId } className="px-2 py-1" justifyContent="between">
|
||||
<div className="flex items-center gap-1">
|
||||
<UserProfileIconView userId={ result.avatarId } />
|
||||
<div>{ result.avatarName }</div>
|
||||
<NitroCardAccordionItemView key={ result.avatarId } className="friends-list-item px-2 py-1" justifyContent="between">
|
||||
<div className="friends-list-user">
|
||||
<div className="friends-list-avatar">
|
||||
<LayoutAvatarImageView figure={ resolveAvatarFigure(getSearchResultFigure(result), getSearchResultGender(result)) } gender={ getSearchResultGender(result) } headOnly={ true } direction={ 2 } />
|
||||
</div>
|
||||
<div>
|
||||
<UserProfileIconView userId={ result.avatarId } />
|
||||
</div>
|
||||
<div className="friends-list-name">{ result.avatarName }</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
{ canRequestFriend(result.avatarId) &&
|
||||
|
||||
@@ -34,7 +34,7 @@ export const FriendsListView: FC<{}> = props =>
|
||||
userNames.push(existingFriend.name);
|
||||
}
|
||||
|
||||
return LocalizeText('friendlist.removefriendconfirm.userlist', [ 'user_names' ], [ userNames.join(', ') ]);
|
||||
return LocalizeText('friendlist.removefriendconfirm.userlist', [ 'user_names' ], [ userNames.join('\n') ]);
|
||||
}, [ offlineFriends, onlineFriends, selectedFriendsIds ]);
|
||||
|
||||
const selectFriend = useCallback((userId: number) =>
|
||||
@@ -60,6 +60,27 @@ export const FriendsListView: FC<{}> = props =>
|
||||
});
|
||||
}, [ setSelectedFriendsIds ]);
|
||||
|
||||
const toggleSelectFriends = useCallback((friendIds: number[]) =>
|
||||
{
|
||||
if(!friendIds.length) return;
|
||||
|
||||
setSelectedFriendsIds(prevValue =>
|
||||
{
|
||||
const allSelected = friendIds.every(friendId => (prevValue.indexOf(friendId) >= 0));
|
||||
|
||||
if(allSelected) return prevValue.filter(friendId => (friendIds.indexOf(friendId) === -1));
|
||||
|
||||
const nextValue = [ ...prevValue ];
|
||||
|
||||
for(const friendId of friendIds)
|
||||
{
|
||||
if(nextValue.indexOf(friendId) === -1) nextValue.push(friendId);
|
||||
}
|
||||
|
||||
return nextValue;
|
||||
});
|
||||
}, []);
|
||||
|
||||
const sendRoomInvite = (message: string) =>
|
||||
{
|
||||
if(!selectedFriendsIds.length || !message || !message.length || (message.length > 255)) return;
|
||||
@@ -125,10 +146,24 @@ export const FriendsListView: FC<{}> = props =>
|
||||
<NitroCardHeaderView headerText={ LocalizeText('friendlist.friends') } onCloseClick={ event => setIsVisible(false) } />
|
||||
<NitroCardContentView className="text-black p-0" gap={ 1 } overflow="hidden">
|
||||
<NitroCardAccordionView fullHeight overflow="hidden">
|
||||
<NitroCardAccordionSetView headerText={ LocalizeText('friendlist.friends') + ` (${ onlineFriends.length })` } isExpanded={ true }>
|
||||
<NitroCardAccordionSetView className="friends-list-section" headerText={ LocalizeText('friendlist.friends') + ` (${ onlineFriends.length })` } isExpanded={ true }>
|
||||
<Flex className="friends-list-toolbar px-2 py-1" justifyContent="end">
|
||||
<span className="friends-list-toolbar-link" onClick={ event => { event.stopPropagation(); toggleSelectFriends(onlineFriends.map(friend => friend.id)); } }>
|
||||
{ onlineFriends.length && onlineFriends.every(friend => (selectedFriendsIds.indexOf(friend.id) >= 0))
|
||||
? LocalizeText('friendlist.unselect_all')
|
||||
: LocalizeText('friendlist.select_all') }
|
||||
</span>
|
||||
</Flex>
|
||||
<FriendsListGroupView list={ onlineFriends } selectedFriendsIds={ selectedFriendsIds } selectFriend={ selectFriend } />
|
||||
</NitroCardAccordionSetView>
|
||||
<NitroCardAccordionSetView headerText={ LocalizeText('friendlist.friends.offlinecaption') + ` (${ offlineFriends.length })` }>
|
||||
<Flex className="friends-list-toolbar px-2 py-1" justifyContent="end">
|
||||
<span className="friends-list-toolbar-link" onClick={ event => { event.stopPropagation(); toggleSelectFriends(offlineFriends.map(friend => friend.id)); } }>
|
||||
{ offlineFriends.length && offlineFriends.every(friend => (selectedFriendsIds.indexOf(friend.id) >= 0))
|
||||
? LocalizeText('friendlist.unselect_all')
|
||||
: LocalizeText('friendlist.select_all') }
|
||||
</span>
|
||||
</Flex>
|
||||
<FriendsListGroupView list={ offlineFriends } selectedFriendsIds={ selectedFriendsIds } selectFriend={ selectFriend } />
|
||||
</NitroCardAccordionSetView>
|
||||
<FriendsListRequestView headerText={ LocalizeText('friendlist.tab.friendrequests') + ` (${ requests.length })` } isExpanded={ true } />
|
||||
|
||||
+10
-5
@@ -1,7 +1,9 @@
|
||||
import { FC, MouseEvent, useState } from 'react';
|
||||
import { LocalizeText, MessengerFriend, OpenMessengerChat } from '../../../../../api';
|
||||
import { NitroCardAccordionItemView, UserProfileIconView } from '../../../../../common';
|
||||
import { LayoutAvatarImageView, NitroCardAccordionItemView, UserProfileIconView } from '../../../../../common';
|
||||
import { useFriends } from '../../../../../hooks';
|
||||
import { resolveAvatarFigure } from '../resolveAvatarFigure';
|
||||
import { resolveAvatarGender } from '../resolveAvatarGender';
|
||||
|
||||
export const FriendsListGroupItemView: FC<{ friend: MessengerFriend, selected: boolean, selectFriend: (userId: number) => void }> = props =>
|
||||
{
|
||||
@@ -55,14 +57,17 @@ export const FriendsListGroupItemView: FC<{ friend: MessengerFriend, selected: b
|
||||
if(!friend) return null;
|
||||
|
||||
return (
|
||||
<NitroCardAccordionItemView className={ `px-2 py-1 ${ selected && 'bg-primary text-white' }` } justifyContent="between" onClick={ event => selectFriend(friend.id) }>
|
||||
<div className="flex items-center gap-1">
|
||||
<NitroCardAccordionItemView className={ `friends-list-item ${ selected ? 'selected' : '' }` } justifyContent="between" onClick={ event => selectFriend(friend.id) }>
|
||||
<div className="friends-list-user">
|
||||
<div className="friends-list-avatar">
|
||||
<LayoutAvatarImageView figure={ resolveAvatarFigure(friend.figure, friend.gender) } gender={ resolveAvatarGender(friend.gender) } headOnly={ true } direction={ 2 } />
|
||||
</div>
|
||||
<div onClick={ event => event.stopPropagation() }>
|
||||
<UserProfileIconView userId={ friend.id } />
|
||||
</div>
|
||||
<div>{ friend.name }</div>
|
||||
<div className="friends-list-name">{ friend.name }</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<div className="friends-list-actions">
|
||||
{ !isRelationshipOpen &&
|
||||
<>
|
||||
{ friend.online &&
|
||||
|
||||
+19
-8
@@ -1,7 +1,9 @@
|
||||
import { FC } from 'react';
|
||||
import { MessengerRequest } from '../../../../../api';
|
||||
import { NitroCardAccordionItemView, UserProfileIconView } from '../../../../../common';
|
||||
import { LocalizeText, MessengerRequest } from '../../../../../api';
|
||||
import { Button, LayoutAvatarImageView, NitroCardAccordionItemView, UserProfileIconView } from '../../../../../common';
|
||||
import { useFriends } from '../../../../../hooks';
|
||||
import { resolveAvatarFigure } from '../resolveAvatarFigure';
|
||||
import { resolveAvatarGender } from '../resolveAvatarGender';
|
||||
|
||||
export const FriendsListRequestItemView: FC<{ request: MessengerRequest }> = props =>
|
||||
{
|
||||
@@ -11,14 +13,23 @@ export const FriendsListRequestItemView: FC<{ request: MessengerRequest }> = pro
|
||||
if(!request) return null;
|
||||
|
||||
return (
|
||||
<NitroCardAccordionItemView className="px-2 py-1" justifyContent="between">
|
||||
<div className="flex items-center gap-1">
|
||||
<UserProfileIconView userId={ request.id } />
|
||||
<div>{ request.name }</div>
|
||||
<NitroCardAccordionItemView className="friends-list-item px-2 py-1" justifyContent="between">
|
||||
<div className="friends-list-user">
|
||||
<div className="friends-list-avatar">
|
||||
<LayoutAvatarImageView figure={ resolveAvatarFigure(request.figureString) } gender={ resolveAvatarGender(undefined) } headOnly={ true } direction={ 2 } />
|
||||
</div>
|
||||
<div>
|
||||
<UserProfileIconView userId={ request.requesterUserId } />
|
||||
</div>
|
||||
<div className="friends-list-name">{ request.name }</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<div className="nitro-friends-spritesheet icon-accept cursor-pointer" onClick={ event => requestResponse(request.id, true) } />
|
||||
<div className="nitro-friends-spritesheet icon-deny cursor-pointer" onClick={ event => requestResponse(request.id, false) } />
|
||||
<Button size="sm" onClick={ event => requestResponse(request.id, true) }>
|
||||
{ LocalizeText('friendlist.request_accept') }
|
||||
</Button>
|
||||
<Button size="sm" variant="danger" onClick={ event => requestResponse(request.id, false) }>
|
||||
{ LocalizeText('friendlist.request_decline') }
|
||||
</Button>
|
||||
</div>
|
||||
</NitroCardAccordionItemView>
|
||||
);
|
||||
|
||||
+5
-2
@@ -17,8 +17,11 @@ export const FriendsListRequestView: FC<NitroCardAccordionSetViewProps> = props
|
||||
<Column gap={ 0 }>
|
||||
{ requests.map((request, index) => <FriendsListRequestItemView key={ index } request={ request } />) }
|
||||
</Column>
|
||||
<div className="flex justify-center px-2 py-1">
|
||||
<Button onClick={ event => requestResponse(-1, false) }>
|
||||
<div className="flex justify-center gap-2 px-2 py-1">
|
||||
<Button onClick={ event => requests.forEach(request => requestResponse(request.id, true)) }>
|
||||
{ LocalizeText('friendlist.requests.acceptall') }
|
||||
</Button>
|
||||
<Button variant="danger" onClick={ event => requestResponse(-1, false) }>
|
||||
{ LocalizeText('friendlist.requests.dismissall') }
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import { resolveAvatarGender } from './resolveAvatarGender';
|
||||
|
||||
const DEFAULT_AVATAR_FIGURES: Record<string, string> = {
|
||||
M: 'hd-180-1.ch-210-66.lg-270-82.sh-290-80',
|
||||
F: 'hd-600-1.ch-630-66.lg-695-82.sh-725-80'
|
||||
};
|
||||
|
||||
export const resolveAvatarFigure = (figure: string | null | undefined, gender?: string | number | null) =>
|
||||
{
|
||||
const normalizedFigure = (figure || '').trim();
|
||||
|
||||
if(normalizedFigure.length && normalizedFigure.includes('hd-')) return normalizedFigure;
|
||||
|
||||
return DEFAULT_AVATAR_FIGURES[resolveAvatarGender(gender)] || DEFAULT_AVATAR_FIGURES.M;
|
||||
};
|
||||
@@ -0,0 +1,20 @@
|
||||
export const resolveAvatarGender = (value: string | number | null | undefined) =>
|
||||
{
|
||||
if(typeof value === 'string')
|
||||
{
|
||||
const normalized = value.trim().toUpperCase();
|
||||
|
||||
if(normalized === 'F') return 'F';
|
||||
if(normalized === 'M') return 'M';
|
||||
if(normalized === 'FEMALE') return 'F';
|
||||
if(normalized === 'MALE') return 'M';
|
||||
}
|
||||
|
||||
if(typeof value === 'number')
|
||||
{
|
||||
if(value === 2) return 'F';
|
||||
if(value === 1) return 'M';
|
||||
}
|
||||
|
||||
return 'M';
|
||||
};
|
||||
@@ -2,9 +2,8 @@ import { AddLinkEventTracker, FollowFriendMessageComposer, GetSessionDataManager
|
||||
import { FC, KeyboardEvent, useEffect, useRef, useState } from 'react';
|
||||
import { FaTimes } from 'react-icons/fa';
|
||||
import { GetUserProfile, LocalizeText, ReportType, SendMessageComposer } from '../../../../api';
|
||||
import { Button, Column, Flex, Grid, LayoutAvatarImageView, LayoutGridItem, LayoutItemCountView, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../../common';
|
||||
import { useHelp, useMessenger } from '../../../../hooks';
|
||||
import { NitroInput } from '../../../../layout';
|
||||
import { DraggableWindowPosition, LayoutAvatarImageView, NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../common';
|
||||
import { useHelp, useMessenger, useTranslation } from '../../../../hooks';
|
||||
import { FriendsMessengerThreadView } from './messenger-thread/FriendsMessengerThreadView';
|
||||
|
||||
export const FriendsMessengerView: FC<{}> = props =>
|
||||
@@ -14,15 +13,35 @@ export const FriendsMessengerView: FC<{}> = props =>
|
||||
const [ messageText, setMessageText ] = useState('');
|
||||
const { visibleThreads = [], activeThread = null, getMessageThread = null, sendMessage = null, setActiveThreadId = null, closeThread = null } = useMessenger();
|
||||
const { report = null } = useHelp();
|
||||
const { settings, translateOutgoing } = useTranslation();
|
||||
const messagesBox = useRef<HTMLDivElement>();
|
||||
|
||||
const followFriend = () => (activeThread && activeThread.participant && SendMessageComposer(new FollowFriendMessageComposer(activeThread.participant.id)));
|
||||
const openProfile = () => (activeThread && activeThread.participant && GetUserProfile(activeThread.participant.id));
|
||||
|
||||
const send = () =>
|
||||
const send = async () =>
|
||||
{
|
||||
if(!activeThread || !messageText.length) return;
|
||||
|
||||
const trimmedText = messageText.trimStart();
|
||||
const shouldTranslateOutgoing = settings.enabled && !!trimmedText.length && (trimmedText.charAt(0) !== ':');
|
||||
|
||||
if(!shouldTranslateOutgoing)
|
||||
{
|
||||
sendMessage(activeThread, GetSessionDataManager().userId, messageText);
|
||||
setMessageText('');
|
||||
return;
|
||||
}
|
||||
|
||||
const translation = await translateOutgoing(messageText);
|
||||
|
||||
if(translation && translation.translatedText?.length && (translation.translatedText.length <= 255))
|
||||
{
|
||||
sendMessage(activeThread, GetSessionDataManager().userId, translation.translatedText, 0, null, undefined, translation);
|
||||
setMessageText('');
|
||||
return;
|
||||
}
|
||||
|
||||
sendMessage(activeThread, GetSessionDataManager().userId, messageText);
|
||||
|
||||
setMessageText('');
|
||||
@@ -32,7 +51,7 @@ export const FriendsMessengerView: FC<{}> = props =>
|
||||
{
|
||||
if(event.key !== 'Enter') return;
|
||||
|
||||
send();
|
||||
void send();
|
||||
};
|
||||
|
||||
useEffect(() =>
|
||||
@@ -107,71 +126,60 @@ export const FriendsMessengerView: FC<{}> = props =>
|
||||
if(!isVisible) return null;
|
||||
|
||||
return (
|
||||
<NitroCardView className="nitro-friends-messenger w-[800px] h-[720px]" theme="primary-slim" uniqueKey="nitro-friends-messenger">
|
||||
<NitroCardView className="messenger-card" theme="primary-slim" uniqueKey={ null } windowPosition={ DraggableWindowPosition.TOP_CENTER } offsetTop={ 8 } isResizable={ false }>
|
||||
<NitroCardHeaderView headerText={ LocalizeText('messenger.window.title', [ 'OPEN_CHAT_COUNT' ], [ visibleThreads.length.toString() ]) } onCloseClick={ event => setIsVisible(false) } />
|
||||
<NitroCardContentView>
|
||||
<Grid overflow="hidden">
|
||||
<Column overflow="hidden" size={ 4 }>
|
||||
<Text bold>{ LocalizeText('toolbar.icon.label.messenger') }</Text>
|
||||
<Column fit overflow="auto">
|
||||
<Column>
|
||||
{ visibleThreads && (visibleThreads.length > 0) && visibleThreads.map(thread =>
|
||||
{
|
||||
return (
|
||||
<LayoutGridItem key={ thread.threadId } itemActive={ (activeThread === thread) } onClick={ event => setActiveThreadId(thread.threadId) } className="py-1 px-2">
|
||||
{ thread.unread && <LayoutItemCountView className="text-black" count={ thread.unreadCount } /> }
|
||||
<Flex fullWidth gap={ 1 } style={{ minHeight: '50px' }}>
|
||||
<LayoutAvatarImageView
|
||||
figure={ thread.participant.id > 0 ? thread.participant.figure : thread.participant.figure === 'ADM' ? 'ha-3409-1413-70.lg-285-89.ch-3032-1334-109.sh-3016-110.hd-185-1359.ca-3225-110-62.wa-3264-62-62.fa-1206-90.hr-3322-1403' : thread.participant.figure }
|
||||
headOnly={ true }
|
||||
direction={ thread.participant.id > 0 ? 2 : 3 }
|
||||
style={{ width: '50px', height: '80px', backgroundPosition: 'center 45%', flexShrink: 0, alignSelf: 'flex-end' }}
|
||||
/>
|
||||
<Text truncate grow className="self-center">{ thread.participant.name }</Text>
|
||||
</Flex>
|
||||
</LayoutGridItem>
|
||||
);
|
||||
}) }
|
||||
</Column>
|
||||
</Column>
|
||||
</Column>
|
||||
<Column overflow="hidden" size={ 8 }>
|
||||
{ activeThread &&
|
||||
<>
|
||||
<Text bold center>{ LocalizeText('messenger.window.separator', [ 'FRIEND_NAME' ], [ activeThread.participant.name ]) }</Text>
|
||||
<Flex alignItems="center" gap={ 1 } justifyContent="between">
|
||||
<NitroCardContentView className="text-black p-0" gap={ 0 } overflow="hidden">
|
||||
<div className="messenger-card-body">
|
||||
<div className="messenger-avatar-bar">
|
||||
{ visibleThreads && (visibleThreads.length > 0) && visibleThreads.map(thread =>
|
||||
{
|
||||
return (
|
||||
<button key={ thread.threadId } className={ 'messenger-avatar-tab' + ((activeThread === thread) ? ' active' : '') + (thread.unread ? ' unread' : '') } onClick={ event => setActiveThreadId(thread.threadId) }>
|
||||
<LayoutAvatarImageView
|
||||
figure={ thread.participant.id > 0 ? thread.participant.figure : thread.participant.figure === 'ADM' ? 'ha-3409-1413-70.lg-285-89.ch-3032-1334-109.sh-3016-110.hd-185-1359.ca-3225-110-62.wa-3264-62-62.fa-1206-90.hr-3322-1403' : thread.participant.figure }
|
||||
headOnly={ true }
|
||||
direction={ thread.participant.id > 0 ? 2 : 3 }
|
||||
/>
|
||||
</button>
|
||||
);
|
||||
}) }
|
||||
</div>
|
||||
|
||||
{ activeThread &&
|
||||
<>
|
||||
<div className="messenger-thread-header">
|
||||
<span className="messenger-thread-name">{ LocalizeText('messenger.window.separator', [ 'FRIEND_NAME' ], [ activeThread.participant.name ]) }</span>
|
||||
<div className="messenger-actions">
|
||||
{ (activeThread.participant.id > 0) &&
|
||||
<div className="flex gap-1">
|
||||
<div className="relative inline-flex align-middle">
|
||||
<Button onClick={ followFriend }>
|
||||
<div className="nitro-friends-spritesheet icon-follow" />
|
||||
</Button>
|
||||
<Button onClick={ openProfile }>
|
||||
<div className="nitro-friends-spritesheet icon-profile-sm" />
|
||||
</Button>
|
||||
</div>
|
||||
<Button variant="danger" onClick={ () => report(ReportType.IM, { reportedUserId: activeThread.participant.id }) }>
|
||||
<>
|
||||
<button className="messenger-btn icon-btn" onClick={ followFriend }>
|
||||
<div className="nitro-friends-spritesheet icon-follow" />
|
||||
</button>
|
||||
<button className="messenger-btn icon-btn" onClick={ openProfile }>
|
||||
<div className="nitro-friends-spritesheet icon-profile-sm" />
|
||||
</button>
|
||||
<button className="messenger-btn danger" onClick={ () => report(ReportType.IM, { reportedUserId: activeThread.participant.id }) }>
|
||||
{ LocalizeText('messenger.window.button.report') }
|
||||
</Button>
|
||||
</div> }
|
||||
<Button onClick={ event => closeThread(activeThread.threadId) }>
|
||||
<FaTimes className="fa-icon" />
|
||||
</Button>
|
||||
</Flex>
|
||||
<Column fit className="bg-muted p-2 rounded chat-messages">
|
||||
<Column innerRef={ messagesBox } overflow="auto">
|
||||
<FriendsMessengerThreadView thread={ activeThread } />
|
||||
</Column>
|
||||
</Column>
|
||||
<div className="flex gap-1">
|
||||
<NitroInput maxLength={ 255 } placeholder={ LocalizeText('messenger.window.input.default', [ 'FRIEND_NAME' ], [ activeThread.participant.name ]) } type="text" value={ messageText } onChange={ event => setMessageText(event.target.value) } onKeyDown={ onKeyDown } />
|
||||
<Button variant="success" onClick={ send }>
|
||||
{ LocalizeText('widgets.chatinput.say') }
|
||||
</Button>
|
||||
</button>
|
||||
</> }
|
||||
<button className="messenger-btn close-btn" onClick={ event => closeThread(activeThread.threadId) }>
|
||||
<FaTimes />
|
||||
</button>
|
||||
</div>
|
||||
</> }
|
||||
</Column>
|
||||
</Grid>
|
||||
</div>
|
||||
|
||||
<div ref={ messagesBox } className="chat-messages">
|
||||
<FriendsMessengerThreadView thread={ activeThread } />
|
||||
</div>
|
||||
|
||||
<div className="messenger-input-row">
|
||||
<input maxLength={ 255 } placeholder={ LocalizeText('messenger.window.input.default', [ 'FRIEND_NAME' ], [ activeThread.participant.name ]) } type="text" value={ messageText } onChange={ event => setMessageText(event.target.value) } onKeyDown={ onKeyDown } />
|
||||
<button className="messenger-btn send" onClick={ () => void send() }>
|
||||
{ LocalizeText('widgets.chatinput.say') }
|
||||
</button>
|
||||
</div>
|
||||
</> }
|
||||
</div>
|
||||
</NitroCardContentView>
|
||||
</NitroCardView>
|
||||
);
|
||||
|
||||
+32
-13
@@ -28,14 +28,11 @@ export const FriendsMessengerThreadGroup: FC<{ thread: MessengerThread, group: M
|
||||
<>
|
||||
{ group.chats.map((chat, index) =>
|
||||
{
|
||||
if(chat.type === MessengerThreadChat.SECURITY_NOTIFICATION) return null;
|
||||
|
||||
return (
|
||||
<Flex key={ index } fullWidth gap={ 2 } justifyContent="start">
|
||||
<Base className="w-full text-break">
|
||||
{ (chat.type === MessengerThreadChat.SECURITY_NOTIFICATION) &&
|
||||
<Flex alignItems="center" className="bg-light rounded mb-2 px-2 py-1 small text-muted" gap={ 2 }>
|
||||
<Base className="nitro-friends-spritesheet icon-warning shrink-0" />
|
||||
<Base>{ chat.message }</Base>
|
||||
</Flex> }
|
||||
{ (chat.type === MessengerThreadChat.ROOM_INVITE) &&
|
||||
<Flex alignItems="center" className="bg-light rounded mb-2 px-2 py-1 small text-black" gap={ 2 }>
|
||||
<Base className="messenger-notification-icon shrink-0" />
|
||||
@@ -50,24 +47,46 @@ export const FriendsMessengerThreadGroup: FC<{ thread: MessengerThread, group: M
|
||||
}
|
||||
|
||||
return (
|
||||
<Flex fullWidth gap={ 2 } justifyContent={ isOwnChat ? 'end' : 'start' }>
|
||||
<Flex fullWidth gap={ 2 } justifyContent={ isOwnChat ? 'end' : 'start' } className={ 'messenger-message-row ' + (isOwnChat ? 'own' : '') }>
|
||||
<Base shrink className="message-avatar">
|
||||
{ ((group.type === MessengerGroupType.PRIVATE_CHAT) && !isOwnChat) &&
|
||||
<LayoutAvatarImageView direction={ 2 } figure={ thread.participant.figure } /> }
|
||||
<LayoutAvatarImageView direction={ 2 } figure={ thread.participant.figure } headOnly={ true } /> }
|
||||
{ (groupChatData && !isOwnChat) &&
|
||||
<LayoutAvatarImageView direction={ 2 } figure={ groupChatData.figure } /> }
|
||||
<LayoutAvatarImageView direction={ 2 } figure={ groupChatData.figure } headOnly={ true } /> }
|
||||
</Base>
|
||||
<Base className={ 'bg-light text-black border-radius mb-2 rounded py-1 px-2 messages-group-' + (isOwnChat ? 'right' : 'left') }>
|
||||
<Base className="font-bold">
|
||||
<Base className="small text-muted">{ group.chats[0].date.toLocaleTimeString() }</Base>
|
||||
<Base className="messenger-message-body">
|
||||
<Base className={ 'messenger-message-name ' + (isOwnChat ? 'text-end' : '') }>
|
||||
{ isOwnChat && GetSessionDataManager().userName }
|
||||
{ !isOwnChat && (groupChatData ? groupChatData.username : thread.participant.name) }
|
||||
:
|
||||
</Base>
|
||||
{ group.chats.map((chat, index) => <Base key={ index } className="text-break">{ chat.message }</Base>) }
|
||||
<Base className={ 'messenger-message-bubble messages-group-' + (isOwnChat ? 'right' : 'left') }>
|
||||
{ group.chats.map((chat, index) =>
|
||||
{
|
||||
if(!chat.showTranslation)
|
||||
{
|
||||
return <Base key={ index } className="text-break">{ chat.message }</Base>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Base key={ index } className="messenger-translation-block">
|
||||
<Base className="messenger-translation-row">
|
||||
<span className="messenger-translation-label">original:</span>
|
||||
<span className="text-break">{ chat.originalMessage || chat.message }</span>
|
||||
</Base>
|
||||
<Base className="messenger-translation-row">
|
||||
<span className="messenger-translation-label">translate:</span>
|
||||
<span className="text-break">{ chat.translatedMessage || chat.message }</span>
|
||||
</Base>
|
||||
</Base>
|
||||
);
|
||||
}) }
|
||||
</Base>
|
||||
<Base className="messenger-message-time">{ group.chats[0].date.toLocaleTimeString() }</Base>
|
||||
</Base>
|
||||
{ isOwnChat &&
|
||||
<Base shrink className="message-avatar">
|
||||
<LayoutAvatarImageView direction={ 4 } figure={ GetSessionDataManager().figure } />
|
||||
<LayoutAvatarImageView direction={ 4 } figure={ GetSessionDataManager().figure } headOnly={ true } />
|
||||
</Base> }
|
||||
</Flex>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user