Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FE] feat: 방 입장 전 세팅 화면 구성 #48

Merged
merged 29 commits into from
Nov 16, 2023
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
959066f
feat: [FE] 비디오 컴포넌트 공통 컴포넌트로 분리
d0422 Nov 14, 2023
46b94d0
feat: [FE] Button컴포넌트 합성 컴포넌트로 변경
d0422 Nov 14, 2023
e6004c9
chore: [FE] Button컴포넌트 합성 적용
d0422 Nov 14, 2023
9f24d89
feat: [FE] user의 Stream을 관리하는 useMedia 커스텀 훅 기능 구현
d0422 Nov 14, 2023
ea2295a
feat: [FE] media목록을 보여주고 media를 결정하는 MediaSelector 구현
d0422 Nov 14, 2023
870cbb4
feat: [FE] Video와 MediaSelector를 보여주는 SettingVideo컴포넌트 구현
d0422 Nov 14, 2023
72b8bae
feat: [FE] 방 입장 전 Setting을 할 수 있는 Setting컴포넌트 기능 구현
d0422 Nov 14, 2023
45a2d39
feat: [FE] useMedia muted, offVideo 함수 추가
d0422 Nov 14, 2023
948541c
feat: [FE] 기본 stream track을 selected 된 상태로 렌더링하는 기능 구현
d0422 Nov 14, 2023
bbe8eed
chore: [FE] video audio ControlButton을 위한 svg 파일 추가
d0422 Nov 14, 2023
7095939
fix: [FE] stream이 useEffect로 변경되어 micOff와 videoOff가 제대로 동작하지 않던 문제 함수 삭제
d0422 Nov 14, 2023
f463c6e
feat: [FE] audio, video Control Button추가
d0422 Nov 14, 2023
779d843
chore: [FE] useMedia 반환타입 명시
d0422 Nov 15, 2023
6cef3ad
feat: [FE] Video 음성 추가 및 조건부 muted처리
d0422 Nov 15, 2023
bb7f2d4
chore: [FE] mediaObject props로 내려주기 적용
d0422 Nov 15, 2023
9b5e3d4
feat: [FE] settings에서 참여하기 전까지 socket연결을 하지 않는 기능 추가
d0422 Nov 15, 2023
4432dab
config: [FE] lint default Props관련 룰 해제
d0422 Nov 15, 2023
dee14e0
chore: [FE] zustand 설치
d0422 Nov 15, 2023
23ffee9
feat: [FE] 전역상태 useSpeaker 생성
d0422 Nov 15, 2023
050c501
feat: [FE] useMedia에서 반환하는 setSpeaker 삭제
d0422 Nov 15, 2023
461cc9d
feat: [FE] 전역 setSpeaker 적용
d0422 Nov 15, 2023
3f96b15
feat: [FE] 변경된 speaker를 적용시키는 부분 구현
d0422 Nov 15, 2023
0ef75c0
refactor: [FE] import.meta.env를 유지하되, test코드에서 모킹하여 사용할 수 있도록 상수형태로 e…
d0422 Nov 15, 2023
b5d4d86
fix: [FE] videoRef.current에 setSinkId가 없는 경우 호출하지 않도록 처리
d0422 Nov 15, 2023
64afd46
test: [FE] Room 조건부 렌더링 테스트 추가
d0422 Nov 15, 2023
4c7bf93
test: [FE] useInput 커스텀훅 기능 테스트
d0422 Nov 15, 2023
1bfcf38
test: [FE] useMedia 기능 테스트
d0422 Nov 15, 2023
ac410b7
refactor: [FE] 코드리뷰 반영
d0422 Nov 15, 2023
2590b22
chore: [FE] 불필요한 파일 삭제
d0422 Nov 15, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions frontEnd/.eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,6 @@ module.exports = {
'no-param-reassign': 'off',
'react/no-array-index-key': 'off',
'react/no-danger': 'off',
'react/require-default-props': 'off',
},
};
46 changes: 41 additions & 5 deletions frontEnd/package-lock.json

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

3 changes: 2 additions & 1 deletion frontEnd/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
"react-router-dom": "^6.18.0",
"socket.io-client": "^4.7.2",
"uuid": "^9.0.1",
"yjs": "^13.6.8"
"yjs": "^13.6.8",
"zustand": "^4.4.6"
},
"devDependencies": {
"@testing-library/jest-dom": "^6.1.4",
Expand Down
30 changes: 30 additions & 0 deletions frontEnd/src/assets/micOff.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
28 changes: 28 additions & 0 deletions frontEnd/src/assets/micOn.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 changes: 18 additions & 0 deletions frontEnd/src/assets/videoOff.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 16 additions & 0 deletions frontEnd/src/assets/videoOn.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
23 changes: 22 additions & 1 deletion frontEnd/src/components/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@ type ButtonProps = {
onClick: () => void;
fontSize: string;
};
function Button({ children }: { children: ReactNode }) {
return <div>{children}</div>;
}

export default function Button({ children, onClick, fontSize }: ButtonProps) {
function Default({ children, onClick, fontSize }: ButtonProps) {
return (
<button
type="button"
Expand All @@ -18,3 +21,21 @@ export default function Button({ children, onClick, fontSize }: ButtonProps) {
</button>
);
}

function Full({ children, onClick, fontSize }: ButtonProps) {
return (
<button
type="button"
className="px-[1.6vw] py-[14px] bg-mainColor text-white rounded-[15px] font-Pretendard w-full"
onClick={onClick}
style={{ fontSize }}
>
{children}
</button>
);
}

export default Object.assign(Button, {
Default,
Full,
});
32 changes: 32 additions & 0 deletions frontEnd/src/components/MediaSelector.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
export default function MediaSelector({
stream,
optionsData,
setFunc,
}: {
stream: MediaStream;
optionsData: MediaDeviceInfo[];
setFunc: React.Dispatch<React.SetStateAction<string>>;
}) {
const onChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
setFunc(e.target.value);
};
return (
<select className="w-[33%] font-Pretendard text-xl" onChange={onChange}>
{optionsData?.map((device) => {
const isSelected = stream.getTracks().find((track) => track.label === device.label);
if (isSelected)
return (
<option key={device.label} value={device.deviceId} selected className="font-Pretendard">
{device.label}
</option>
);

return (
<option key={device.label} value={device.deviceId} className="font-Pretendard">
{device.label}
</option>
);
})}
</select>
);
}
90 changes: 90 additions & 0 deletions frontEnd/src/components/SettingVideo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { ReactNode, useState } from 'react';
import { MediaObject } from '@/hooks/useMedia';
import MediaSelector from './MediaSelector';
import Video from './Video';
import micOnSVG from '@/assets/micOn.svg';
import micOffSVG from '@/assets/micOff.svg';
import videoOffSVG from '@/assets/videoOff.svg';
import videoOnSVG from '@/assets/videoOn.svg';
import useSpeaker from '@/stores/useSpeaker';

function ControlButton({ onClick, style, children }: { onClick: () => void; style: Record<string, string>; children: ReactNode }) {
return (
<button
type="button"
onClick={onClick}
className="w-[5vw] p-[1vw] hover:opacity-50 border-solid border-[1px] rounded-[50%] border-white"
style={style}
>
{children}
</button>
);
}

export default function SettingVideo({ mediaObject }: { mediaObject: MediaObject }) {
const { stream, camera, mic, speaker } = mediaObject;
const { list: cameraList, setCamera } = camera;
const { list: micList, setMic } = mic;
const { list: speakerList } = speaker;
const setSpeaker = useSpeaker((state) => state.setSpeaker);
const [micOn, setMicOn] = useState(true);

const [videoOn, setVideoOn] = useState(true);

const offVideo = () => {
stream?.getVideoTracks().forEach((track) => {
track.enabled = !track.enabled;
});
};

const muteMic = () => {
stream?.getAudioTracks().forEach((track) => {
track.enabled = !track.enabled;
});
};
const handleMicClick = () => {
setMicOn((prev) => !prev);
muteMic();
};
const handleVideoClick = () => {
setVideoOn((prev) => !prev);
offVideo();
};
return (
stream && (
<div className="flex flex-col gap-[20px] ">
<div className="relative">
<Video stream={stream} muted />
<div className="absolute flex items-center justify-center w-full gap-[1vw] bottom-[10px]">
<ControlButton onClick={handleMicClick} style={{ backgroundColor: micOn ? 'transparent' : '#ea4335', transition: 'all 0.5s' }}>
<img src={micOn ? micOnSVG : micOffSVG} alt="micButton" />
</ControlButton>
<ControlButton
onClick={handleVideoClick}
style={{ backgroundColor: videoOn ? 'transparent' : '#ea4335', transition: 'all 0.5s' }}
>
<img src={videoOn ? videoOnSVG : videoOffSVG} alt="videoButton" />
</ControlButton>
</div>
</div>
<div className="flex gap-[10px]">
{[
[cameraList, setCamera],
[micList, setMic],
[speakerList, setSpeaker],
].map(
Copy link
Collaborator

Choose a reason for hiding this comment

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

어떤 것에 대한 map인지 한 번에 알아볼 수 있도록 변수로 분리하는 것도 좋을 것 같아요.

현재로서는 어떤 역할을 하는지 한 번에 확인하기 어려운 것 같아요.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

이런식으로 개선해보았습니다

  const selector = [
    { list: camera.list, setFunc: camera.setCamera },
    { list: mic.list, setFunc: mic.setMic },
    { list: speaker.list, setFunc: setSpeaker },
  ];
...
{selector.map(
            ({ list, setFunc }, i) =>
              list && (
                <MediaSelector
                  key={i}
                  stream={stream}
                  optionsData={list as MediaDeviceInfo[]}
                  setFunc={setFunc as React.Dispatch<React.SetStateAction<string>>}
                />
              ),
          )}

Copy link
Collaborator

Choose a reason for hiding this comment

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

좋습니다!

([deviceList, setFunc], i) =>
deviceList && (
<MediaSelector
key={i}
stream={stream}
optionsData={deviceList as MediaDeviceInfo[]}
setFunc={setFunc as React.Dispatch<React.SetStateAction<string>>}
/>
),
)}
</div>
</div>
)
);
}
Loading