From 164ec1acf1eb012bddb77ec6de9c52263d50173c Mon Sep 17 00:00:00 2001 From: juwon5272 Date: Mon, 24 Feb 2025 21:53:32 +0900 Subject: [PATCH 1/6] =?UTF-8?q?[FE]=20BUG:=20roomId=20=EC=95=88=20?= =?UTF-8?q?=EB=B0=9B=EC=95=84=EC=98=A4=EB=8A=94=20=EB=AC=B8=EC=A0=9C=20?= =?UTF-8?q?=ED=95=B4=EA=B2=B0=20#245?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/frontend/src/components/YoutubePlayer/index.tsx | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/frontend/src/components/YoutubePlayer/index.tsx b/src/frontend/src/components/YoutubePlayer/index.tsx index da1c7678..0a707009 100644 --- a/src/frontend/src/components/YoutubePlayer/index.tsx +++ b/src/frontend/src/components/YoutubePlayer/index.tsx @@ -6,10 +6,9 @@ import { useWebSocketStore } from '@/stores/useWebSocketStore'; export const YouTubePlayer = () => { const { videoQueue } = useVideoStore(); - const { currentRoom } = useCurrentRoomStore(); + const { roomId } = useCurrentRoomStore.getState(); const { client, subTopic } = useWebSocketStore(); const pubTopic = useWebSocketStore.getState().pubTopic; - const roomId = currentRoom?.roomDetails?.roomInfo?.[0]?.roomId; const playerRef = useRef(null); const lastSentStateRef = useRef<'playing' | 'paused' | null>(null); @@ -46,7 +45,7 @@ export const YouTubePlayer = () => { height: '100%', width: '100%', videoId: id, - playerVars: { autoplay: 1, controls: 1, start: startTime }, + playerVars: { autoplay: 0, controls: 1, start: startTime }, events: { onStateChange: handleVideoStateChange, onReady: handlePlayerReady, @@ -63,10 +62,13 @@ export const YouTubePlayer = () => { // 유튜브 영상의 재생, 멈춤, 끝남 상태에 따라 동작 const broadcastPlayerState = (state: 'playing' | 'paused', time: number) => { + const roomId = useCurrentRoomStore.getState().roomId; + if (!client || !roomId || !pubTopic) { console.warn('⚠ WebSocket 준비 안됨'); return; } + lastSentStateRef.current = state; const message = JSON.stringify({ roomId, playTime: time, playerState: state }); pubTopic(`/app/play-time`, message); @@ -143,8 +145,7 @@ export const YouTubePlayer = () => { // 영상 변경 시 플레이어 로드 useEffect(() => { if (!videoQueue.length) return; - - const newCurrentVideo = videoQueue[0]; + const newCurrentVideo = useVideoStore.getState().videoQueue[0]; // 현재 상태와 비교 if ( From 355027d22f003c6b9d78e0f7e66c301153c2afe0 Mon Sep 17 00:00:00 2001 From: juwon5272 Date: Mon, 24 Feb 2025 21:54:36 +0900 Subject: [PATCH 2/6] =?UTF-8?q?[FE]=20BUG:=20=EB=B0=A9=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=EC=8B=9C=20=EC=B4=88=EA=B8=B0=20=EC=9E=AC=EC=83=9D?= =?UTF-8?q?=EC=98=81=EC=83=81=20=EC=95=88=20=EB=B0=94=EB=80=8C=EB=8A=94=20?= =?UTF-8?q?=EB=AC=B8=EC=A0=9C=20=ED=95=B4=EA=B2=B0=20#245?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/frontend/src/components/YoutubePlayer/index.tsx | 3 +++ src/frontend/src/stores/useVideoStore.ts | 3 +++ 2 files changed, 6 insertions(+) diff --git a/src/frontend/src/components/YoutubePlayer/index.tsx b/src/frontend/src/components/YoutubePlayer/index.tsx index 0a707009..4ce698af 100644 --- a/src/frontend/src/components/YoutubePlayer/index.tsx +++ b/src/frontend/src/components/YoutubePlayer/index.tsx @@ -28,6 +28,9 @@ export const YouTubePlayer = () => { script.src = 'https://www.youtube.com/iframe_api'; document.body.appendChild(script); } + return () => { + useVideoStore.getState().clearVideoQueue(); + }; }, []); // 유튜브 플레이어 로드 diff --git a/src/frontend/src/stores/useVideoStore.ts b/src/frontend/src/stores/useVideoStore.ts index 9aa4dfe8..a9245d2d 100644 --- a/src/frontend/src/stores/useVideoStore.ts +++ b/src/frontend/src/stores/useVideoStore.ts @@ -19,6 +19,7 @@ interface IVideoStore { setCurrentIndex: (index: number) => void; setCurrentVideo: (index: number) => void; moveToNextVideo: () => void; + clearVideoQueue: () => void; } export const useVideoStore = create(set => ({ @@ -88,4 +89,6 @@ export const useVideoStore = create(set => ({ currentIndex: 0, }; }), + + clearVideoQueue: () => set(() => ({ videoQueue: [] })), })); From 54658ee3c70579281b1d92173a82ffe9c372196b Mon Sep 17 00:00:00 2001 From: juwon5272 Date: Tue, 25 Feb 2025 01:28:49 +0900 Subject: [PATCH 3/6] =?UTF-8?q?[FE]=20REFACTOR:=20TopNavBar=EC=97=90=20?= =?UTF-8?q?=EC=9E=88=EB=8D=98=20=EC=9B=B9=EC=86=8C=EC=BC=93=20=EA=B5=AC?= =?UTF-8?q?=EB=8F=85=20=ED=8E=98=EC=9D=B4=EC=A7=80=EB=A1=9C=20=EC=9D=B4?= =?UTF-8?q?=EB=8F=99=20#245?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/TopNavBar/index.tsx | 77 +------------------ src/frontend/src/pages/RoomPage/index.tsx | 70 ++++++++++++++++- 2 files changed, 71 insertions(+), 76 deletions(-) diff --git a/src/frontend/src/components/TopNavBar/index.tsx b/src/frontend/src/components/TopNavBar/index.tsx index 9f64d2b7..04b9c6c4 100644 --- a/src/frontend/src/components/TopNavBar/index.tsx +++ b/src/frontend/src/components/TopNavBar/index.tsx @@ -1,6 +1,6 @@ import { useEffect, useState } from 'react'; import { Link, useNavigate } from 'react-router-dom'; -import axios from 'axios'; +// import axios from 'axios'; import AddCircleIcon from '@/assets/img/AddCircle.svg'; import BellIcon from '@/assets/img/Bell.svg'; import { @@ -27,12 +27,6 @@ import { friendApi } from '@/api/endpoints/friend/friend.api'; import { useFriendStore } from '@/stores/useFriendStore'; import { useNotificationStore } from '@/stores/useNotificationStore'; -import { useCurrentRoomStore } from '@/stores/useCurrentRoomStore'; -import { useVideoStore } from '@/stores/useVideoStore'; - -const API_KEY = import.meta.env.VITE_YOUTUBE_API_KEY as string; - - export const TopNavBar = () => { const navigate = useNavigate(); const { user, fetchMyProfile, clearProfile } = useUserStore(); @@ -44,10 +38,7 @@ export const TopNavBar = () => { resetNotificationCount, fetchNotifications, } = useNotificationStore(); - const { connect, subscribeRoomPlaylistUpdate } = useWebSocketStore(); - const { currentRoom } = useCurrentRoomStore(); - const { setVideoQueue } = useVideoStore(); - const roomId = currentRoom?.roomDetails?.roomInfo?.[0]?.roomId; + const { connect } = useWebSocketStore(); const accessToken = useAuthStore(state => state.accessToken); const [isRoomCreateModalOpen, setIsRoomCreateModalOpen] = useState(false); @@ -80,70 +71,6 @@ export const TopNavBar = () => { initializeUser(); }, [accessToken, clearProfile, fetchMyProfile, fetchMyRooms, connect]); - // YouTube API를 이용하여 영상 정보(제목 & 유튜버) 가져오기 - const fetchVideoDetails = async (videoIds: string[]) => { - if (!videoIds.length) return {}; - - try { - const { data } = await axios.get('https://www.googleapis.com/youtube/v3/videos', { - params: { - part: 'snippet', - id: videoIds.join(','), - key: API_KEY, - hl: 'ko', - }, - }); - - const videoDetailsMap: Record = {}; - data.items.forEach( - (item: { id: string; snippet: { title: string; channelTitle: string } }) => { - videoDetailsMap[item.id] = { - title: item.snippet.title, - youtuber: item.snippet.channelTitle, - }; - }, - ); - - return videoDetailsMap; - } catch (error) { - console.error('YouTube API 호출 실패:', error); - return {}; - } - }; - - // 방의 플레이리스트 업데이트 구독 - useEffect(() => { - if (!roomId) return; - - subscribeRoomPlaylistUpdate(roomId, async data => { - console.log('플레이리스트 업데이트 수신:', data); - - if (data?.playlist && Array.isArray(data.playlist)) { - const sortedPlaylist = data.playlist.sort((a, b) => a.order - b.order); - - const videoIds = sortedPlaylist - .map(item => { - return item.url.split('v=')[1]?.split('&')[0] || ''; - }) - .filter(id => id); - - const videoDetailsMap = await fetchVideoDetails(videoIds); - - const updatedQueue = sortedPlaylist.map(item => { - const videoId = item.url.split('v=')[1]?.split('&')[0] || ''; - return { - id: videoId, - start: parseInt(item.url.split('t=')[1] || '0', 10), - thumbnail: `https://img.youtube.com/vi/${videoId}/0.jpg`, - title: item.title || videoDetailsMap[videoId]?.title || '제목 없음', - youtuber: item.youtuber || videoDetailsMap[videoId]?.youtuber || '유튜버 정보 없음', - }; - }); - setVideoQueue(updatedQueue); - } - }); - }, [roomId, subscribeRoomPlaylistUpdate, setVideoQueue]); - const clickCreateRoom = (e: React.MouseEvent) => { e.stopPropagation(); if (!user) { diff --git a/src/frontend/src/pages/RoomPage/index.tsx b/src/frontend/src/pages/RoomPage/index.tsx index 37d5f0eb..c61d9a92 100644 --- a/src/frontend/src/pages/RoomPage/index.tsx +++ b/src/frontend/src/pages/RoomPage/index.tsx @@ -1,3 +1,4 @@ +import axios from 'axios'; import { Sidebar } from '@/components/Sidebar'; import { YouTubePlayer } from '@/components/YoutubePlayer'; import { RoomDetail } from '@/components/RoomDetail'; @@ -9,12 +10,15 @@ import { useSearchParams } from 'react-router-dom'; import { useCurrentRoom } from '@/hooks/queries/useCurrentRoom'; import { useVideoStore } from '@/stores/useVideoStore'; import { getVideoQueueFromPlaylist } from '@/utils/playlistUtils'; - +import { useWebSocketStore } from '@/stores/useWebSocketStore'; export const RoomPage = () => { const [searchParams] = useSearchParams(); const roomCode = searchParams.get('code'); const { data: room } = useCurrentRoom(roomCode); console.log('RoomPage:', room); + const { setVideoQueue } = useVideoStore(); + const { subscribeRoomPlaylistUpdate } = useWebSocketStore(); + const { roomId } = useCurrentRoomStore(); useEffect(() => { if (room) { @@ -34,6 +38,70 @@ export const RoomPage = () => { }; }, [room]); + // YouTube API를 이용하여 영상 정보(제목 & 유튜버) 가져오기 + const fetchVideoDetails = async (videoIds: string[]) => { + if (!videoIds.length) return {}; + + try { + const { data } = await axios.get('https://www.googleapis.com/youtube/v3/videos', { + params: { + part: 'snippet', + id: videoIds.join(','), + key: import.meta.env.VITE_YOUTUBE_API_KEY, + hl: 'ko', + }, + }); + + const videoDetailsMap: Record = {}; + data.items.forEach( + (item: { id: string; snippet: { title: string; channelTitle: string } }) => { + videoDetailsMap[item.id] = { + title: item.snippet.title, + youtuber: item.snippet.channelTitle, + }; + }, + ); + + return videoDetailsMap; + } catch (error) { + console.error('YouTube API 호출 실패:', error); + return {}; + } + }; + + // 방의 플레이리스트 업데이트 구독 + useEffect(() => { + if (!roomId) return; + + subscribeRoomPlaylistUpdate(roomId, async data => { + console.log('플레이리스트 업데이트 수신:', data); + + if (data?.playlist && Array.isArray(data.playlist)) { + const sortedPlaylist = data.playlist.sort((a, b) => a.order - b.order); + + const videoIds = sortedPlaylist + .map(item => { + return item.url.split('v=')[1]?.split('&')[0] || ''; + }) + .filter(id => id); + + const videoDetailsMap = await fetchVideoDetails(videoIds); + + const updatedQueue = sortedPlaylist.map(item => { + const videoId = item.url.split('v=')[1]?.split('&')[0] || ''; + return { + id: videoId, + start: parseInt(item.url.split('t=')[1] || '0', 10), + thumbnail: `https://img.youtube.com/vi/${videoId}/0.jpg`, + title: item.title || videoDetailsMap[videoId]?.title || '제목 없음', + youtuber: item.youtuber || videoDetailsMap[videoId]?.youtuber || '유튜버 정보 없음', + }; + }); + setVideoQueue(updatedQueue); + } + }); + }, [roomId, subscribeRoomPlaylistUpdate, setVideoQueue]); + return ( <> From 2b5fd05f717ed5a9e78ac425f9f2d3bf03b209ff Mon Sep 17 00:00:00 2001 From: juwon5272 Date: Tue, 25 Feb 2025 01:34:16 +0900 Subject: [PATCH 4/6] =?UTF-8?q?[FE]=20FEAT:=20Role=EC=9D=B4=20Member?= =?UTF-8?q?=EC=9D=BC=20=EA=B2=BD=EC=9A=B0=20=EC=98=81=EC=83=81=20=EB=B3=B4?= =?UTF-8?q?=EA=B8=B0=EB=A7=8C=20=EA=B0=80=EB=8A=A5=ED=95=98=EA=B2=8C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20#245?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/YoutubePlayer/index.tsx | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/frontend/src/components/YoutubePlayer/index.tsx b/src/frontend/src/components/YoutubePlayer/index.tsx index 4ce698af..c87bf653 100644 --- a/src/frontend/src/components/YoutubePlayer/index.tsx +++ b/src/frontend/src/components/YoutubePlayer/index.tsx @@ -3,12 +3,15 @@ import styled from 'styled-components'; import { useVideoStore } from '@/stores/useVideoStore'; import { useCurrentRoomStore } from '@/stores/useCurrentRoomStore'; import { useWebSocketStore } from '@/stores/useWebSocketStore'; +import { UserRole } from '@/types/enums/UserRole'; export const YouTubePlayer = () => { const { videoQueue } = useVideoStore(); const { roomId } = useCurrentRoomStore.getState(); const { client, subTopic } = useWebSocketStore(); const pubTopic = useWebSocketStore.getState().pubTopic; + const myRole = useCurrentRoomStore.getState().currentRoom?.myRole; + const isWatchOnly = myRole === UserRole.MEMBER; const playerRef = useRef(null); const lastSentStateRef = useRef<'playing' | 'paused' | null>(null); @@ -48,7 +51,12 @@ export const YouTubePlayer = () => { height: '100%', width: '100%', videoId: id, - playerVars: { autoplay: 0, controls: 1, start: startTime }, + playerVars: { + autoplay: 1, + controls: isWatchOnly ? 0 : 1, + disablekb: 1, + start: startTime, + }, events: { onStateChange: handleVideoStateChange, onReady: handlePlayerReady, @@ -65,6 +73,7 @@ export const YouTubePlayer = () => { // 유튜브 영상의 재생, 멈춤, 끝남 상태에 따라 동작 const broadcastPlayerState = (state: 'playing' | 'paused', time: number) => { + if (myRole === 2) return; const roomId = useCurrentRoomStore.getState().roomId; if (!client || !roomId || !pubTopic) { @@ -79,6 +88,8 @@ export const YouTubePlayer = () => { // 내부 이벤트로 인한 상태 변화 감지 const handleVideoStateChange = (event: YT.OnStateChangeEvent) => { + if (isWatchOnly) return; + if (isRemoteUpdateRef.current) { isRemoteUpdateRef.current = false; return; @@ -114,7 +125,6 @@ export const YouTubePlayer = () => { const applySyncState = ({ playTime, playerState }: { playTime: number; playerState: string }) => { if (!playerRef.current) return; playerRef.current.seekTo(playTime, true); - if (playerState === 'playing') { playerRef.current.playVideo(); } else if (playerState === 'paused') { @@ -148,7 +158,8 @@ export const YouTubePlayer = () => { // 영상 변경 시 플레이어 로드 useEffect(() => { if (!videoQueue.length) return; - const newCurrentVideo = useVideoStore.getState().videoQueue[0]; + + const newCurrentVideo = videoQueue[0]; // 현재 상태와 비교 if ( @@ -170,6 +181,7 @@ export const YouTubePlayer = () => { return ( + {/* */}
@@ -193,3 +205,7 @@ const VideoWrapper = styled.div` align-items: center; justify-content: center; `; + +// const VideoDiv = styled.div<{ $isWatchOnly: boolean }>` +// ${({ $isWatchOnly }) => $isWatchOnly && 'pointer-events: none;'} +// `; From b37e9a43a97bd3b697e9ba1ef8216130d87e982d Mon Sep 17 00:00:00 2001 From: juwon5272 Date: Tue, 25 Feb 2025 01:34:43 +0900 Subject: [PATCH 5/6] =?UTF-8?q?[FE]=20FEAT:=20Role=EC=9D=B4=20Member?= =?UTF-8?q?=EC=9D=BC=20=EA=B2=BD=EC=9A=B0=20=ED=94=8C=EB=A0=88=EC=9D=B4?= =?UTF-8?q?=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EB=B3=B4=EA=B8=B0=EB=A7=8C=20?= =?UTF-8?q?=EA=B0=80=EB=8A=A5=ED=95=98=EA=B2=8C=20=EC=88=98=EC=A0=95=20#24?= =?UTF-8?q?5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sidebar/Playlist/PlaylistItem/index.tsx | 26 +++++----- .../src/components/Sidebar/Playlist/index.tsx | 50 +++++++++++++------ 2 files changed, 47 insertions(+), 29 deletions(-) diff --git a/src/frontend/src/components/Sidebar/Playlist/PlaylistItem/index.tsx b/src/frontend/src/components/Sidebar/Playlist/PlaylistItem/index.tsx index f2e79b11..269cf0e2 100644 --- a/src/frontend/src/components/Sidebar/Playlist/PlaylistItem/index.tsx +++ b/src/frontend/src/components/Sidebar/Playlist/PlaylistItem/index.tsx @@ -25,25 +25,25 @@ interface IPlaylistItem { active: boolean; isDragging?: boolean; isPreview?: boolean; - onClick: (index: number) => void; - onMoveUp: (index: number) => void; - onMoveDown: (index: number) => void; - onRemove: (index: number) => void; - onDragStart: (index: number) => void; - onDragOver: (e: React.DragEvent) => void; + onClick?: (index: number) => void; + onMoveUp?: (index: number) => void; + onMoveDown?: (index: number) => void; + onRemove?: (index: number) => void; + onDragStart?: (index: number) => void; + onDragOver?: (e: React.DragEvent) => void; onDragEnd?: () => void; - onDrop: (index: number) => void; + onDrop?: (index: number) => void; } export const PlaylistItem = (props: IPlaylistItem) => { return ( props.onDragStart(props.index)} + onDragStart={() => props.onDragStart?.(props.index)} onDragOver={props.onDragOver} onDragEnd={props.onDragEnd} - onDrop={() => props.onDrop(props.index)} - onClick={() => props.onClick(props.index)} + onDrop={() => props.onDrop?.(props.index)} + onClick={() => props.onClick?.(props.index)} $active={props.active} $isDragging={props.isDragging} $isPreview={props.isPreview} @@ -64,7 +64,7 @@ export const PlaylistItem = (props: IPlaylistItem) => { { e.stopPropagation(); - props.onMoveUp(props.index); + props.onMoveUp?.(props.index); }} color={ButtonColor.DARKGRAY} borderradius="100px" @@ -75,7 +75,7 @@ export const PlaylistItem = (props: IPlaylistItem) => { { e.stopPropagation(); - props.onMoveDown(props.index); + props.onMoveDown?.(props.index); }} color={ButtonColor.DARKGRAY} borderradius="100px" @@ -87,7 +87,7 @@ export const PlaylistItem = (props: IPlaylistItem) => { { e.stopPropagation(); - props.onRemove(props.index); + props.onRemove?.(props.index); }} color={ButtonColor.DARKGRAY} borderradius="100px" diff --git a/src/frontend/src/components/Sidebar/Playlist/index.tsx b/src/frontend/src/components/Sidebar/Playlist/index.tsx index 9196e4a3..cbd2aa79 100644 --- a/src/frontend/src/components/Sidebar/Playlist/index.tsx +++ b/src/frontend/src/components/Sidebar/Playlist/index.tsx @@ -22,6 +22,7 @@ import DefaultThumbnail from '@/assets/img/DefaultThumbnail.svg'; import { useWebSocketStore } from '@/stores/useWebSocketStore'; import { useUserStore } from '@/stores/useUserStore'; import { useCurrentRoomStore } from '@/stores/useCurrentRoomStore'; +import { UserRole } from '@/types/enums/UserRole'; const API_KEY = import.meta.env.VITE_YOUTUBE_API_KEY as string; import { roomApi } from '@/api/endpoints/room/room.api'; @@ -57,6 +58,9 @@ export const Playlist = () => { const { currentRoom } = useCurrentRoomStore(); const roomId = currentRoom?.roomDetails?.roomInfo?.[0]?.roomId; + const myRole = currentRoom?.myRole; + const isWatchOnly = myRole === UserRole.MEMBER; + const { subscribeRoomPlaylistUpdate } = useWebSocketStore.getState(); // 드래그 상태를 useRef로 관리하고, 강제 업데이트를 위해 forceUpdate 함수를 사용 @@ -302,22 +306,36 @@ export const Playlist = () => { video={video} index={index} active={index === currentIndex} - isDragging={index === draggedIndexRef.current} - isPreview={draggedIndexRef.current !== null && index === dragOverIndexRef.current} - onClick={() => handleSetCurrentVideo(index)} - onMoveUp={() => { - moveVideoUp(index); - updatePlaylistOnServer(); - }} - onMoveDown={() => { - moveVideoDown(index); - updatePlaylistOnServer(); - }} - onRemove={() => handleRemoveVideo(index)} - onDragStart={() => handleDragStart(index)} - onDragOver={e => handleDragOver(e, index)} - onDragEnd={handleDragEnd} - onDrop={() => handleDrop(index)} + // isDragging={index === draggedIndexRef.current} + // isPreview={draggedIndexRef.current !== null && index === dragOverIndexRef.current} + // onClick={() => handleSetCurrentVideo(index)} + // onMoveUp={() => { + // moveVideoUp(index); + // updatePlaylistOnServer(); + // }} + // onMoveDown={() => { + // moveVideoDown(index); + // updatePlaylistOnServer(); + // }} + // onRemove={() => handleRemoveVideo(index)} + // onDragStart={() => handleDragStart(index)} + // onDragOver={e => handleDragOver(e, index)} + // onDragEnd={handleDragEnd} + // onDrop={() => handleDrop(index)} + isDragging={!isWatchOnly && index === draggedIndexRef.current} + isPreview={ + !isWatchOnly && + draggedIndexRef.current !== null && + index === dragOverIndexRef.current + } + onClick={!isWatchOnly ? () => handleSetCurrentVideo(index) : undefined} + onMoveUp={!isWatchOnly ? () => moveVideoUp(index) : undefined} + onMoveDown={!isWatchOnly ? () => moveVideoDown(index) : undefined} + onRemove={!isWatchOnly ? () => handleRemoveVideo(index) : undefined} + onDragStart={!isWatchOnly ? () => handleDragStart(index) : undefined} + onDragOver={!isWatchOnly ? e => handleDragOver(e, index) : undefined} + onDragEnd={!isWatchOnly ? handleDragEnd : undefined} + onDrop={!isWatchOnly ? () => handleDrop(index) : undefined} /> ) : null, )} From d14a04b7229cc1130056101f5b2542105bf499ec Mon Sep 17 00:00:00 2001 From: juwon5272 Date: Tue, 25 Feb 2025 09:35:03 +0900 Subject: [PATCH 6/6] =?UTF-8?q?[FE]=20FEAT:=20Role=EC=9D=B4=20Member?= =?UTF-8?q?=EC=9D=BC=20=EA=B2=BD=EC=9A=B0=20=ED=94=8C=EB=A0=88=EC=9D=B4?= =?UTF-8?q?=EB=A6=AC=EC=8A=A4=ED=8A=B8=EC=97=90=EC=84=9C=20=EC=95=84?= =?UTF-8?q?=EC=98=88=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=EA=B0=80=20=EC=95=88?= =?UTF-8?q?=EB=90=98=EA=B2=8C=20=EC=88=98=EC=A0=95=20#245?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sidebar/Playlist/PlaylistItem/index.tsx | 77 ++++++++++--------- .../src/components/Sidebar/Playlist/index.tsx | 36 +++------ 2 files changed, 52 insertions(+), 61 deletions(-) diff --git a/src/frontend/src/components/Sidebar/Playlist/PlaylistItem/index.tsx b/src/frontend/src/components/Sidebar/Playlist/PlaylistItem/index.tsx index 269cf0e2..c5606a95 100644 --- a/src/frontend/src/components/Sidebar/Playlist/PlaylistItem/index.tsx +++ b/src/frontend/src/components/Sidebar/Playlist/PlaylistItem/index.tsx @@ -25,20 +25,21 @@ interface IPlaylistItem { active: boolean; isDragging?: boolean; isPreview?: boolean; + draggable: boolean; onClick?: (index: number) => void; - onMoveUp?: (index: number) => void; - onMoveDown?: (index: number) => void; - onRemove?: (index: number) => void; - onDragStart?: (index: number) => void; - onDragOver?: (e: React.DragEvent) => void; - onDragEnd?: () => void; - onDrop?: (index: number) => void; + onMoveUp: (index: number) => void; + onMoveDown: (index: number) => void; + onRemove: (index: number) => void; + onDragStart: (index: number) => void; + onDragOver: (e: React.DragEvent) => void; + onDragEnd: () => void; + onDrop: (index: number) => void; } export const PlaylistItem = (props: IPlaylistItem) => { return ( props.onDragStart?.(props.index)} onDragOver={props.onDragOver} onDragEnd={props.onDragEnd} @@ -59,44 +60,46 @@ export const PlaylistItem = (props: IPlaylistItem) => { {props.video.title || '제목 없음'} {props.video.youtuber || '유튜버 정보 없음'} - -
+ {props.draggable && ( + +
+ { + e.stopPropagation(); + props.onMoveUp?.(props.index); + }} + color={ButtonColor.DARKGRAY} + borderradius="100px" + padding="5px" + > + Move Up + + { + e.stopPropagation(); + props.onMoveDown?.(props.index); + }} + color={ButtonColor.DARKGRAY} + borderradius="100px" + padding="5px" + > + Move Down + +
{ e.stopPropagation(); - props.onMoveUp?.(props.index); + props.onRemove?.(props.index); }} color={ButtonColor.DARKGRAY} borderradius="100px" padding="5px" + height="24px" > - Move Up + Remove - { - e.stopPropagation(); - props.onMoveDown?.(props.index); - }} - color={ButtonColor.DARKGRAY} - borderradius="100px" - padding="5px" - > - Move Down - -
- { - e.stopPropagation(); - props.onRemove?.(props.index); - }} - color={ButtonColor.DARKGRAY} - borderradius="100px" - padding="5px" - height="24px" - > - Remove - -
+ + )}
); }; diff --git a/src/frontend/src/components/Sidebar/Playlist/index.tsx b/src/frontend/src/components/Sidebar/Playlist/index.tsx index cbd2aa79..76ffab82 100644 --- a/src/frontend/src/components/Sidebar/Playlist/index.tsx +++ b/src/frontend/src/components/Sidebar/Playlist/index.tsx @@ -306,22 +306,7 @@ export const Playlist = () => { video={video} index={index} active={index === currentIndex} - // isDragging={index === draggedIndexRef.current} - // isPreview={draggedIndexRef.current !== null && index === dragOverIndexRef.current} - // onClick={() => handleSetCurrentVideo(index)} - // onMoveUp={() => { - // moveVideoUp(index); - // updatePlaylistOnServer(); - // }} - // onMoveDown={() => { - // moveVideoDown(index); - // updatePlaylistOnServer(); - // }} - // onRemove={() => handleRemoveVideo(index)} - // onDragStart={() => handleDragStart(index)} - // onDragOver={e => handleDragOver(e, index)} - // onDragEnd={handleDragEnd} - // onDrop={() => handleDrop(index)} + draggable={!isWatchOnly} isDragging={!isWatchOnly && index === draggedIndexRef.current} isPreview={ !isWatchOnly && @@ -329,13 +314,13 @@ export const Playlist = () => { index === dragOverIndexRef.current } onClick={!isWatchOnly ? () => handleSetCurrentVideo(index) : undefined} - onMoveUp={!isWatchOnly ? () => moveVideoUp(index) : undefined} - onMoveDown={!isWatchOnly ? () => moveVideoDown(index) : undefined} - onRemove={!isWatchOnly ? () => handleRemoveVideo(index) : undefined} - onDragStart={!isWatchOnly ? () => handleDragStart(index) : undefined} - onDragOver={!isWatchOnly ? e => handleDragOver(e, index) : undefined} - onDragEnd={!isWatchOnly ? handleDragEnd : undefined} - onDrop={!isWatchOnly ? () => handleDrop(index) : undefined} + onMoveUp={() => moveVideoUp(index)} + onMoveDown={() => moveVideoDown(index)} + onRemove={() => handleRemoveVideo(index)} + onDragStart={() => handleDragStart(index)} + onDragOver={e => handleDragOver(e, index)} + onDragEnd={handleDragEnd} + onDrop={() => handleDrop(index)} /> ) : null, )} @@ -367,9 +352,12 @@ export const Playlist = () => { setInputUrl(e.target.value)} + disabled={isWatchOnly} />