Skip to content

Commit

Permalink
Merge pull request #105 from Team-Partage/feature/playlist
Browse files Browse the repository at this point in the history
플레이리스트 순서 변경 로직 수정
  • Loading branch information
Si-off authored Jul 28, 2024
2 parents df6543a + 18c8e2e commit 8bca12d
Show file tree
Hide file tree
Showing 7 changed files with 133 additions and 123 deletions.
8 changes: 7 additions & 1 deletion src/app/channel/_components/SocketConnector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,10 @@ const SocketConnector = ({ channelId }: Props) => {
setStore({ type: 'SET_LEAVE', payload: body.data });
break;
case 'PLAYLIST_ADD':
setStore({ type: 'SET_PLAYLIST', payload: JSON.parse(body.data) });
setStore({ type: 'ADD_PLAYLIST', payload: JSON.parse(body.data) });
break;
case 'PLAYLIST_MOVE':
setStore({ type: 'PLAYLIST_MOVE', payload: body.data });
break;
case 'PLAYLIST_REMOVE': {
const data = body.data as MessageType['PLAYLIST_REMOVE'];
Expand All @@ -70,6 +73,9 @@ const SocketConnector = ({ channelId }: Props) => {
setStore({ type: 'SET_VIDEO_TIME', payload: body.data });
break;
}
case 'VIDEO_PLAY':
setStore({ type: 'SET_VIDEO', payload: body.data });
break;
default:
break;
}
Expand Down
137 changes: 71 additions & 66 deletions src/app/channel/_components/playlist/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const Playlist = ({ channel, owner }: Props) => {

/** STORE */
const user_id = useUserStore((state) => state.user_id);
const playlist_no = useSocketStore((state) => state.video.playlist_no);
const { playlist, setStore } = useSocketStore(
useShallow((state) => ({ playlist: state.playlist, setStore: state.setSocketStore })),
);
Expand All @@ -41,8 +42,8 @@ const Playlist = ({ channel, owner }: Props) => {

useEffect(() => {
const fetch = async () => {
const res = await getPlaylist({ channelId: channel_id });
setStore({ type: 'SET_PLAYLIST', payload: res.playlists });
const res = await getPlaylist({ channelId: channel_id, pageSize: 50 });
setStore({ type: 'ADD_PLAYLIST', payload: res.playlists });
};

fetch();
Expand All @@ -51,12 +52,11 @@ const Playlist = ({ channel, owner }: Props) => {
/** 비디오 재생 */
const handlePlay = (index: number) => {
if (!isOwner) return;
// send('VIDEO_PLAY', { playlist_no: id, playing: true });
setStore({
type: 'SET_VIDEO',
payload: { playlist_no: playlist.data[index].playlist_no, url: playlist.data[index].url },
send('VIDEO_PLAY', {
playlist_no: playlist[index].playlist_no,
url: playlist[index].url,
playing: true,
});
setStore({ type: 'SET_PLAYLIST_CURSOR', payload: index });
};

/** 비디오 삭제 */
Expand All @@ -72,33 +72,34 @@ const Playlist = ({ channel, owner }: Props) => {
setUrl('');
};

/** 드래그앤드랍 */
const onDragEnd = ({ source, destination }: DropResult) => {
if (!isOwner || !destination) return;

// swap
const newPlaylist = playlist.data;
const temp = newPlaylist[source.index];
newPlaylist[source.index] = newPlaylist[destination.index];
newPlaylist[destination.index] = temp;

setStore({ type: 'SET_PLAYLIST', payload: newPlaylist });
if (source.index === playlist.cursor) {
setStore({ type: 'SET_PLAYLIST_CURSOR', payload: destination.index });
} else {
setStore({ type: 'SET_PLAYLIST_CURSOR', payload: source.index });
}
setStore({
type: 'PLAYLIST_MOVE',
payload: {
playlist_no: playlist[source.index].playlist_no,
sequence: destination.index,
},
});

send('PLAYLIST_MOVE', {
playlist_no: playlist[source.index].playlist_no,
sequence: destination.index,
});
};

return (
<section
className={`h-full py-5 desktop:order-1 desktop:px-8 ${isFold && 'desktop:px-[22px]'}`}
className={`flex flex-col desktop:order-1 desktop:max-h-screen desktop:max-w-[384px] desktop:px-8 ${isFold ? 'desktop:px-[22px]' : 'w-full'}`}
>
{/** 헤더 */}
<header className={`flex h-[4.1875rem] items-center justify-between shadow-xl `}>
<header className={`flex h-[67px] items-center justify-between shadow-xl desktop:h-[90px]`}>
<div className="flex items-center">
{/** 플레이리스트 펼치기 버튼 */}
<button
className={`${isFold && 'rounded desktop:p-[10px] desktop:hover:bg-transparent-white-10'}`}
className={`${isFold ? 'rounded desktop:p-[10px] desktop:hover:bg-transparent-white-10' : ''}`}
onClick={() => setIsFold(!isFold)}
disabled={!isFold}
>
Expand All @@ -124,57 +125,61 @@ const Playlist = ({ channel, owner }: Props) => {
<DragDropContext onDragEnd={onDragEnd}>
<Droppable droppableId="playlist">
{(provided) => (
<div {...provided.droppableProps} ref={provided.innerRef}>
<ol className={`flex flex-col ${isFold && 'hidden'}`}>
{playlist?.data.map((item, index) => (
<Draggable
key={item.playlist_no}
draggableId={item.playlist_no.toString()}
index={index}
isDragDisabled={!isOwner}
>
{(provided) => (
<li
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
onClick={(e) => {
e.preventDefault();
handlePlay(index);
}}
onMouseEnter={() => setHoveredItem(item.playlist_no)}
onMouseLeave={() => setHoveredItem(null)}
className={`relative h-[66px] rounded-lg border p-3 transition-colors desktop:w-[320px] ${isOwner && 'hover:border-main-skyblue hover:bg-main-skyblue/20'} ${playlist.cursor === index ? 'border-main-skyblue bg-main-skyblue/20' : 'border-transparent'}`}
>
<PlaylistCard {...item} />
{isOwner && hoveredItem === item.playlist_no && (
<div className="absolute right-2 top-1/2 flex -translate-y-1/2 gap-4">
<Button
size="icon"
className="size-5 p-0 tablet:size-6"
onClick={(e) => {
e.preventDefault();
handleDelete(item.playlist_no);
}}
>
<Trash2 className="text-main-skyblue" />
</Button>
</div>
)}
</li>
)}
</Draggable>
))}
</ol>
<ol
{...provided.droppableProps}
ref={provided.innerRef}
className={`h-[320px] overflow-y-auto no-scrollbar desktop:h-screen-playList ${isFold ? 'hidden' : ''}`}
>
{playlist?.map((item, index) => (
<Draggable
key={item.playlist_no}
draggableId={item.playlist_no.toString()}
index={index}
isDragDisabled={!isOwner}
>
{(provided) => (
<li
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
onClick={(e) => {
e.preventDefault();
handlePlay(index);
}}
onMouseEnter={() => setHoveredItem(item.playlist_no)}
onMouseLeave={() => setHoveredItem(null)}
className={`relative h-[66px] rounded-lg border p-3 transition-colors desktop:w-[320px] ${isOwner && 'hover:border-main-skyblue hover:bg-main-skyblue/20'} ${playlist_no === item.playlist_no ? 'border-main-skyblue bg-main-skyblue/20' : 'border-transparent'}`}
>
<PlaylistCard {...item} />
{isOwner && hoveredItem === item.playlist_no && (
<div className="absolute right-2 top-1/2 flex -translate-y-1/2 gap-4">
<Button
size="icon"
className="size-5 p-0 tablet:size-6"
onClick={(e) => {
e.preventDefault();
handleDelete(item.playlist_no);
}}
>
<Trash2 className="text-main-skyblue" />
</Button>
</div>
)}
</li>
)}
</Draggable>
))}
{provided.placeholder}
</div>
</ol>
)}
</Droppable>
</DragDropContext>

{/** URL INPUT */}
{isOwner && (
<div className={`${isFold && 'hidden'}`}>
<div
className={`pb-3 desktop:fixed desktop:bottom-0 desktop:w-[320px] ${isFold ? 'hidden' : ''}`}
>
<Input
value={url}
onChange={(e) => {
Expand Down
30 changes: 9 additions & 21 deletions src/app/channel/_hooks/usePlaylist.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,18 @@
import send from '@/services/websocket/send';
import { useSocketStore } from '@/stores/useSocketStore';

const usePlaylist = () => {
const setStore = useSocketStore((state) => state.setSocketStore);
const next = () => {
const { playlist, video } = useSocketStore.getState();

const next = (cursor?: number) => {
const { playlist } = useSocketStore.getState();
let nextIndex = playlist.findIndex((item) => item.playlist_no === video.playlist_no) + 1;
if (playlist.length <= nextIndex) {
nextIndex = 0;
}

const calc = () => {
let newCursor = 0;
if (cursor) {
newCursor = cursor;
} else {
newCursor = playlist.cursor + 1;
}
const { playlist_no, url } = playlist[nextIndex];

if (playlist.length <= newCursor) newCursor = 0;

return newCursor;
};

const nextCursor = calc();

const { playlist_no, url } = playlist.data[nextCursor];

setStore({ type: 'SET_VIDEO', payload: { playlist_no, url } });
setStore({ type: 'SET_PLAYLIST_CURSOR', payload: nextCursor });
send('VIDEO_PLAY', { playlist_no, url, playing: true });
};

return { next };
Expand Down
2 changes: 1 addition & 1 deletion src/components/modal/ShareChannelModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { DialogClose, DialogContent, DialogHeader, DialogTitle } from '../ui/dia
const ShareChannelModal = () => {
const params = useParams() as { channel_id: string };
const { playlist, video } = useSocketStore((state) => ({
playlist: state.playlist.data,
playlist: state.playlist,
video: state.video,
}));
const [channelTitle, setChannelTitle] = useState('');
Expand Down
12 changes: 12 additions & 0 deletions src/services/websocket/type.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { ChannelPermission } from '../channel/type';
import { Playlist } from '../playlist/type';

/** 채팅 입력 */
Expand Down Expand Up @@ -27,6 +28,7 @@ export type UserLeaveReq = {
/** 영상 재생/일시정지 */
export type VideoPlayReq = {
playlist_no: number;
url: string;
playing: boolean;
};

Expand Down Expand Up @@ -100,6 +102,7 @@ export type MessageType = {
/** 비디오 재생 및 일시정지 */
VIDEO_PLAY: {
playlist_no: number;
url: string;
playing: boolean;
};

Expand All @@ -111,6 +114,15 @@ export type MessageType = {

/** 재생시간 */
VIDEO_TIME: number;

/** 채널 권한 변경 */
CHANNEL_PERMISSION_CHANGE: { channel_permissions: ChannelPermission };

/** 채널 유저역할 변경 */
CHANNEL_USER_ROLE_CHANGE: {
user_id: string;
role_id: string;
};
};

export interface MessageBody<T extends keyof MessageType = never> {
Expand Down
Loading

0 comments on commit 8bca12d

Please sign in to comment.