Skip to content

Commit

Permalink
feat: add chat room
Browse files Browse the repository at this point in the history
  • Loading branch information
bbb169 committed Oct 22, 2023
1 parent 514ee40 commit 9c43947
Show file tree
Hide file tree
Showing 11 changed files with 225 additions and 10 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"antd": "^5.9.2",
"crypto-js": "^4.1.1",
"intro.js": "^7.2.0",
"rc-virtual-list": "^3.11.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.16.0",
Expand Down
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

97 changes: 97 additions & 0 deletions src/component/chatRoom/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { useChatMessageStore } from '@/pages/playRoom/store/chatMessage.js';
import { customScrollBar } from '@/styles/scrollbar.js';
import { emitSocket } from '@/utils/api.js';
import { infoContext } from '@/utils/infoContext.js';
import { SendOutlined } from '@ant-design/icons';
import { css } from '@emotion/react';
import { Button, Input, Tooltip } from 'antd';
import React, { useContext, useEffect, useRef, useState } from 'react';
import PlayerAvatar from '../playerAvatar/index.js';

const chatContainerStyle = css`
width: 50vw;
margin: 0 auto;
${customScrollBar()}
`;

const scrollMessageBox = css`
width: 100%;
max-height: 30vh;
min-height: 200px;
overflow-y: auto;
overflow-x: hidden;
margin: 0 auto;
background-color: rgb(246,248,250);
${customScrollBar()}
`;

const chatBubbleBoxStyle = (isUser = false) => css`
width: 100%;
color: #fff;
padding: 4px 8px;
border-radius: 10px;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: ${isUser ? 'flex-end;' : 'flex-start;'}
`;

const chatBubbleStyle = (isUser = false) => css`
background-color: ${isUser ? '#69c0ff' : '#87e8de'};
color: #fff;
padding: 4px 8px;
border-radius: 10px;
margin: 5px;
max-width: 70%;
`;

export default function ChatRoom (): React.JSX.Element {
const listRef = useRef<HTMLDivElement>(null);
const [message, setMessage] = useState('');
const { chatMessage: { info: messages } } = useChatMessageStore();
const { player } = useContext(infoContext);

useEffect(() => {
listRef.current?.scrollTo({ top: listRef.current.scrollHeight });
}, [messages]);

const handleSendMessage = () => {
if (message.trim() === '') return;
emitSocket('sendMessage', { player, msg: message });
setMessage('');
};

return (
<div css={chatContainerStyle}>
<div
ref={listRef}
css={scrollMessageBox}
>
{
messages.map(({ player: curPlayer, msg, key }) => {
const isMe = curPlayer.name === player?.name;

return <div css={chatBubbleBoxStyle(isMe)} key={key}>
{!isMe && <Tooltip title={curPlayer.name}><PlayerAvatar player={curPlayer}/></Tooltip>}
<span css={chatBubbleStyle(isMe)}>
{msg}
</span>
{isMe && <Tooltip title={curPlayer.name}><PlayerAvatar player={curPlayer}/></Tooltip>}
</div>;
})
}
</div>
<Input
css={css`
width: 100%
`}
value={message}
onChange={(e) => setMessage(e.target.value)}
onPressEnter={handleSendMessage}
suffix={
<Button type="primary" shape='circle' onClick={handleSendMessage} icon={<SendOutlined />} />
}
/>
</div>
);
}
21 changes: 21 additions & 0 deletions src/component/playerAvatar/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { PlayerInfoType } from '@/pages/playRoom/type.js';
import { css } from '@emotion/react';
import { Avatar, AvatarProps } from 'antd';

const ColorList = ['#f56a00', '#7265e6', '#ffbf00', '#00a2ae'];

export default function PlayerAvatar ({ player, importCss, ...restProps } : { player: PlayerInfoType; importCss?: string } & AvatarProps) {
return <>
<Avatar
css={css`
background-color: ${ColorList[(player.position % 4)]};
text-align: center;
${importCss}
`}
{
...restProps
}>
{player.name[0].toLocaleUpperCase()}
</Avatar>
</>;
}
2 changes: 2 additions & 0 deletions src/pages/playRoom/component/customFloatButton/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import introJs from 'intro.js';
import 'intro.js/introjs.css';
import { getFloatCss } from '../../styles/playRoom.js';
import AudioButton from '../audioButton/index.js';
import TalkRommButton from '../talkRoomButton/index.js';

const Introduct = introJs as unknown as () => { start: () => void };

Expand Down Expand Up @@ -41,5 +42,6 @@ export function CustomFloatButton () {
/>
</Popconfirm>
<AudioButton />
<TalkRommButton/>
</>;
}
14 changes: 4 additions & 10 deletions src/pages/playRoom/component/playerBox/index.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import PlayerAvatar from '@/component/playerAvatar/index.js';
import { boxHoverShadow } from '@/styles/animation.js';
import { css } from '@emotion/react';
import { Avatar, Tooltip } from 'antd';
import { Tooltip } from 'antd';
import { usePlayerAudioInfo } from '../../store/playerInfo.js';
import { PlayerInfoType } from '../../type.js';
import { ripplePlayerBoxCss, UsersBoxFlexCss } from './style.js';

const ColorList = ['#f56a00', '#7265e6', '#ffbf00', '#00a2ae'];

export default function PlayerBox ({ player } : { player: PlayerInfoType }) {
const { playingPlayer } = usePlayerAudioInfo((state) => state);
const isBarking = playingPlayer[0].has(player.name);
Expand All @@ -28,14 +26,10 @@ export default function PlayerBox ({ player } : { player: PlayerInfoType }) {
return <>
<div css={UsersBoxFlexCss(player.status.includes('fold') || player.status.includes('disconnect'))} key={player.position}>
<Tooltip title={playerInfo} placement='bottom'>
<Avatar size={size} css={css`
background-color: ${ColorList[(player.position % 4)]};
text-align: center;
<PlayerAvatar player={player} importCss={`
${boxHoverShadow}
${isBarking ? ripplePlayerBoxCss : ''}
`}>
{player.name[0].toLocaleUpperCase()}
</Avatar>
`} size={size}/>
</Tooltip>
</div>
</>;
Expand Down
37 changes: 37 additions & 0 deletions src/pages/playRoom/component/talkRoomButton/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Badge, Button, Popconfirm } from 'antd';
import { CommentOutlined } from '@ant-design/icons';
import 'intro.js/introjs.css';
import { getFloatCss } from '../../styles/playRoom.js';
import ChatRoom from '@/component/chatRoom/index.js';
import { useChatMessageStore } from '../../store/chatMessage.js';
import { useState } from 'react';

export default function TalkRommButton () {
const { chatMessage: { unReadNum }, readMsg } = useChatMessageStore();
const [open, setOpen] = useState(false);

return <>
<Popconfirm
title="聊天室"
description={<ChatRoom/>}
okText="关闭"
showCancel={false}
placement='bottomLeft'
icon={<></>}
open={open}
onOpenChange={() => {
setOpen(pre => !pre);
readMsg();
}}
>
<Badge css={getFloatCss(true, 1)} count={open ? 0 : unReadNum}>
<Button
icon={<CommentOutlined />}
type='primary'
shape='circle'
data-intro="此处为聊天室"
/>
</Badge>
</Popconfirm>
</>;
}
18 changes: 18 additions & 0 deletions src/pages/playRoom/hooks/useChatMessage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { useEffect } from 'react';
import { Socket } from 'socket.io-client';
import { useChatMessageStore } from '../store/chatMessage.js';
import { ChatMessageType } from '../type.js';

/** set room infomation */
export default function useChatMessage (socket: Socket | void) {
const { addMessage } = useChatMessageStore();

useEffect(() => {
if (socket) {
socket.on('receiveMessage', (msg: Omit<ChatMessageType, 'key'>) => {
console.log(msg);
addMessage(msg);
});
}
}, [socket]);
}
2 changes: 2 additions & 0 deletions src/pages/playRoom/hooks/useInfosFromSocket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ import { useEffect, useState } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import { io, Socket } from 'socket.io-client';
import { GptPredicateRes, PlayerInfoType, RoomInfo, VictoryInfo } from '../type.js';
import useChatMessage from './useChatMessage.js';
import usePlayersInfo from './usePlayersInfo.js';
import useRoom from './useRoom.js';

export default function useInfosFromSocket (): [PlayerInfoType[], PlayerInfoType | undefined, RoomInfo | undefined, [PlayerInfoType, VictoryInfo][], Socket | void] {
const [socket, setSocket] = useState<Socket | void>();
const [room] = useRoom(socket);
const [otherPlayers, myPlayer, victoryPlayers] = usePlayersInfo(socket);
useChatMessage(socket);
const { roomId, userName } = useParams();
const navigate = useNavigate();

Expand Down
34 changes: 34 additions & 0 deletions src/pages/playRoom/store/chatMessage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { create } from 'zustand';
import { ChatMessageType } from '../type.js';

interface ChatMessageStore {
chatMessage: {
info: ChatMessageType[];
unReadNum: number;
},
addMessage: (msg: Omit<ChatMessageType, 'key'>) => void;
readMsg: () => void;
}

export const useChatMessageStore = create<ChatMessageStore>((set) => ({
chatMessage: {
info: [],
unReadNum: 0,
},
addMessage: (msg) => set(({ chatMessage }) => {
return ({
chatMessage: {
info: [...chatMessage.info, { ...msg, key: chatMessage.info.length }],
unReadNum: chatMessage.unReadNum + 1,
},
});
}),
readMsg:() => set(({ chatMessage }) => {
return ({
chatMessage: {
...chatMessage,
unReadNum: 0,
},
});
}),
}));
6 changes: 6 additions & 0 deletions src/pages/playRoom/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,10 @@ export interface GptPredicateRes {
winRate: number;
publcHighestCards: CardType[];
userHighestCards: CardType[];
}

export interface ChatMessageType {
key: string | number,
player: PlayerInfoType,
msg: string
}

0 comments on commit 9c43947

Please sign in to comment.