Skip to content

Commit

Permalink
Merge pull request #105 from 42Blank/feat/pong-socketio-multi
Browse files Browse the repository at this point in the history
Pong: Socket 연결 및 게임 목록 기능 구현
  • Loading branch information
devleomk1 authored Feb 18, 2023
2 parents 8a8ff2e + e4f9520 commit 867ce7c
Show file tree
Hide file tree
Showing 18 changed files with 374 additions and 64 deletions.
1 change: 1 addition & 0 deletions frontend/src/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export { useHandleSocket } from './useHandleSocket';
export { useGetUser } from './useGetUser';
export { useGetCurrentChatRoom } from './useGetCurrentChatRoom';
export { useGetCurrentGameRoom } from './useGetCurrentGameRoom';
export { useLogout } from './useLogout';
14 changes: 14 additions & 0 deletions frontend/src/hooks/useGetCurrentGameRoom.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { useRecoilValue } from 'recoil';
import { useLocation } from 'react-router-dom';

import { gameRoomListState } from 'store';

export function useGetCurrentGameRoom() {
const uuid = useLocation().pathname.split('/')[2];
const gameRoomList = useRecoilValue(gameRoomListState);

const foundGameRoom = gameRoomList.find(gameRoom => gameRoom.id === uuid);
if (!foundGameRoom) throw Error('게임방을 찾을 수 없습니다!');

return foundGameRoom;
}
72 changes: 61 additions & 11 deletions frontend/src/hooks/useHandleSocket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,24 @@ import { io, Socket } from 'socket.io-client';
import { useEffect } from 'react';
import { useRecoilState, useRecoilValue, useResetRecoilState } from 'recoil';

import { joinChatRoomState, leaveChatRoomState, newChatRoomState, newMessageState, updateChatRoomState } from 'store';
import { ChatDataType, ChatRoomInfoType } from 'types/chat';
import {
joinChatRoomState,
leaveChatRoomState,
newChatRoomState,
newMessageState,
updateChatRoomState,
joinGameRoomState,
newGameRoomState,
leaveGameRoomState,
} from 'store';
import { useSetSocketHandler } from './useSetSocketHandler';

export const sockets: {
chatSocket: Socket | null;
gameSocket: Socket | null;
} = {
chatSocket: null,
gameSocket: null,
};

function createSocket(
Expand All @@ -18,9 +28,6 @@ function createSocket(
connectHandler: () => void;
exceptionHandler: (data: Error) => void;
disconnectHandler: (reason: string) => void;
getCurrentChatHandler: (data: ChatDataType) => void;
getAllChatRoomHandler: (data: ChatRoomInfoType[]) => void;
joinChatRoomHandler: (id: string) => void;
},
) {
const socket = io(`${process.env.REACT_APP_SERVER as string}/${namespace}`, {
Expand All @@ -31,9 +38,6 @@ function createSocket(
socket.on('connect', handler.connectHandler);
socket.on('disconnect', handler.disconnectHandler);
socket.on('exception', handler.exceptionHandler);
socket.on('chat_message', handler.getCurrentChatHandler);
socket.on('update_chat_room', handler.getAllChatRoomHandler);
socket.on('join_room', handler.joinChatRoomHandler);
}

return socket;
Expand All @@ -50,13 +54,24 @@ export function useHandleSocket() {
const updateChatRoom = useRecoilValue(updateChatRoomState);
const resetUpdateChatRoom = useResetRecoilState(updateChatRoomState);

/* Game Room */
const newGameRoom = useRecoilValue(newGameRoomState);
const resetNewGameRoom = useResetRecoilState(newGameRoomState);
const joinGameRoom = useRecoilValue(joinGameRoomState);
const resetJoinGameRoom = useResetRecoilState(joinGameRoomState);
const leaveGameRoom = useRecoilValue(leaveGameRoomState);
const resetLeaveGameRoom = useResetRecoilState(leaveGameRoomState);

const {
connectHandler,
exceptionHandler,
disconnectHandler,
getCurrentChatHandler,
getAllChatRoomHandler,
joinChatRoomHandler,
// Game
joinGameRoomHandler,
getAllGameRoomHandler,
} = useSetSocketHandler();

useEffect(() => {
Expand Down Expand Up @@ -103,16 +118,51 @@ export function useHandleSocket() {
resetUpdateChatRoom();
}, [updateChatRoom]);

/* ----------------- Game Room List ----------------- */
useEffect(() => {
if (newGameRoom.created === false) return;
if (sockets.gameSocket === null) return;

sockets.gameSocket.emit('create_room', newGameRoom);
resetNewGameRoom();
}, [newGameRoom]);

useEffect(() => {
if (leaveGameRoom.id.length === 0) return;
if (sockets.gameSocket === null) return;

sockets.gameSocket.emit('leave_room', leaveGameRoom);
resetLeaveGameRoom();
}, [leaveGameRoom]);

useEffect(() => {
if (joinGameRoom.id.length === 0) return;
if (sockets.gameSocket === null) return;

sockets.gameSocket.emit('join_room', joinGameRoom);
resetJoinGameRoom();
}, [joinGameRoom]);

/* ----------------- Game ----------------- */
useEffect(() => {
if (!sockets.chatSocket) {
sockets.chatSocket = createSocket('chat', {
connectHandler,
exceptionHandler,
disconnectHandler,
getCurrentChatHandler,
getAllChatRoomHandler,
joinChatRoomHandler,
});
sockets.chatSocket.on('chat_message', getCurrentChatHandler);
sockets.chatSocket.on('update_chat_room', getAllChatRoomHandler);
sockets.chatSocket.on('join_room', joinChatRoomHandler);
}
if (!sockets.gameSocket) {
sockets.gameSocket = createSocket('game', {
connectHandler,
exceptionHandler,
disconnectHandler,
});
sockets.gameSocket.on('join_room', joinGameRoomHandler);
sockets.gameSocket.on('update_game_room', getAllGameRoomHandler);
}
}, []);
}
21 changes: 20 additions & 1 deletion frontend/src/hooks/useSetSocketHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@ import { useSetRecoilState } from 'recoil';
import { useNavigate } from 'react-router-dom';

import { ChatDataType, ChatRoomInfoType } from 'types/chat';
import { chatRoomListState, currentChatDataState } from 'store';
import { chatRoomListState, currentChatDataState, gameRoomListState } from 'store';
import { ROUTE } from 'common/constants';
import { currentGamePongState } from 'store/currentGamePongState';
import { GameRoomInfoType } from 'types/game';

export function useSetSocketHandler() {
const setCurrentChatData = useSetRecoilState(currentChatDataState);
const setCurrentGamePong = useSetRecoilState(currentGamePongState);
const setChatRoomList = useSetRecoilState(chatRoomListState);
const setGameRoomList = useSetRecoilState(gameRoomListState);
const nav = useNavigate();

function connectHandler() {}
Expand All @@ -26,12 +30,27 @@ export function useSetSocketHandler() {
function joinChatRoomHandler(id: string) {
nav(`${ROUTE.CHAT}/${id}`);
}

/* Game */
function gamePongHandler(data: ChatDataType) {
setCurrentGamePong(prev => [data, ...prev]);
}
function getAllGameRoomHandler(data: GameRoomInfoType[]) {
setGameRoomList(data);
}
function joinGameRoomHandler(id: string) {
nav(`${ROUTE.GAME}/${id}`);
}

return {
connectHandler,
exceptionHandler,
disconnectHandler,
getCurrentChatHandler,
getAllChatRoomHandler,
joinChatRoomHandler,
gamePongHandler,
getAllGameRoomHandler,
joinGameRoomHandler,
};
}
26 changes: 19 additions & 7 deletions frontend/src/pages/GameListPage/GameRoomElement.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { Link } from 'react-router-dom';
import { useSetRecoilState } from 'recoil';
import { joinGameRoomState } from 'store';
import { GameRoomInfoType } from 'types/game';

import {
gameRoomElementStyle,
Expand All @@ -8,11 +10,21 @@ import {
gameRoomVsSpanStyle,
} from './GameRoomElement.styles';

export const GameRoomElement = () => {
interface Props {
gameRoomInfo: GameRoomInfoType;
}

export const GameRoomElement = ({ gameRoomInfo }: Props) => {
const { id: roomID, host /* challenger */ } = gameRoomInfo;
const setJoinGameRoom = useSetRecoilState(joinGameRoomState);

function handleClickJoinButton() {
setJoinGameRoom({ id: roomID });
}

return (
<Link to="./123" className={gameRoomLinkStyle}>
<button type="button" onClick={handleClickJoinButton} className={gameRoomLinkStyle}>
<div className={gameRoomElementStyle}>
<h3>초보만</h3>
<div className={gameRoomVsSectionStyle}>
<div className={gameRoomUserWrapperStyle}>
<img
Expand All @@ -21,7 +33,7 @@ export const GameRoomElement = () => {
height={70}
alt="profile1"
/>
<span>ycha</span>
<span>{host.user.nickname}</span>
</div>
<span className={gameRoomVsSpanStyle}>vs</span>
<div className={gameRoomUserWrapperStyle}>
Expand All @@ -31,10 +43,10 @@ export const GameRoomElement = () => {
height={70}
alt="profile1"
/>
<span>jiychoi</span>
<span>도전자</span>
</div>
</div>
</div>
</Link>
</button>
);
};
38 changes: 38 additions & 0 deletions frontend/src/pages/GameListPage/NewGameModalBody.styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { css } from '@emotion/css';

export const newGameFormStyle = css`
flex: 1;
display: flex;
flex-direction: column;
`;

export const newGameInnerDivStyle = css`
flex: 1;
`;

export const formSectionDivStyle = css`
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
padding: 10px 20px;
width: calc(100% - 40px);
`;

export const formSectionButtonWrapper = css`
width: 100%;
height: 50px;
display: flex;
justify-content: space-around;
align-items: center;
border-top: 1px solid black; // TODO: 색상 상수화
& button {
width: 50%;
height: 100%;
}
& button:first-child {
border-right: 1px solid black; // TODO: 색상 상수화
}
`;
47 changes: 47 additions & 0 deletions frontend/src/pages/GameListPage/NewGameModalBody.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { FormEvent } from 'react';
import { useSetRecoilState } from 'recoil';
import { newGameRoomState } from 'store';

import {
formSectionButtonWrapper,
formSectionDivStyle,
newGameFormStyle,
newGameInnerDivStyle,
} from './NewGameModalBody.styles';

interface Props {
onClickClose: () => void;
}

export const NewGameModalBody = ({ onClickClose }: Props) => {
const setNewGameRoom = useSetRecoilState(newGameRoomState);
// const nameRef = useRef<HTMLInputElement>(null);

function handleOnClick(e: FormEvent) {
// roomTitle 필요없어서 처내야함.
e.preventDefault();
setNewGameRoom({
created: true,
});
onClickClose();
}

return (
<div className={newGameFormStyle}>
<div className={newGameInnerDivStyle}>
<div className={formSectionDivStyle}>
<label htmlFor="new-chat-name">이름따위설정할수없다</label>
{/* <input id="new-chat-name" ref={nameRef} type="text" placeholder="최대 20자" required /> */}
</div>
</div>
<div className={formSectionButtonWrapper}>
<button type="button" onClick={onClickClose}>
닫기
</button>
<button type="button" onClick={handleOnClick}>
생성
</button>
</div>
</div>
);
};
12 changes: 9 additions & 3 deletions frontend/src/pages/GameListPage/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { useState } from 'react';
import { useRecoilValue } from 'recoil';

import { PlusIcon } from 'assets';
import { Modal } from 'common';
import { gameRoomListState } from 'store';
import { GameRoomElement } from './GameRoomElement';

import {
Expand All @@ -10,9 +12,11 @@ import {
newGameModalHeaderStyle,
newGameModalWrapperStyle,
} from './GameListPage.styles';
import { NewGameModalBody } from './NewGameModalBody';

export const GameListPage = () => {
const [isModalShown, setIsModalShown] = useState(false);
const gameRoomList = useRecoilValue(gameRoomListState);

function handleClickButton() {
setIsModalShown(true);
Expand All @@ -21,18 +25,20 @@ export const GameListPage = () => {
function handleClickClose() {
setIsModalShown(false);
}

return (
<main className={gameListWrapperStyle}>
<GameRoomElement />
{gameRoomList.map((data, index) => (
<GameRoomElement key={`game-room-${index}`} gameRoomInfo={data} />
))}
<button type="button" onClick={handleClickButton} className={gameRoomIconStyle}>
<PlusIcon />
</button>
{isModalShown && (
<Modal onClickClose={handleClickClose} className={newGameModalWrapperStyle}>
<header className={newGameModalHeaderStyle}>
<h4> 게임</h4>
<h4>🐦 게임 만들기</h4>
</header>
<NewGameModalBody onClickClose={handleClickClose} />
</Modal>
)}
</main>
Expand Down
Loading

1 comment on commit 867ce7c

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deploy completed!

Please sign in to comment.