"use client"; import { useEffect, useRef, useState } from "react"; import { ChevronDown, ChevronUp, GripVertical, ListX, Play, Shuffle, Trash2 } from "lucide-react"; import { Equalizer, Thumb } from "@/components/ui"; import { cleanArtist, formatTime, type PlayerState } from "@/hooks/usePlayer"; import type { QueueState } from "@/hooks/useQueue"; interface QueueListProps { player: PlayerState; queue: QueueState; /** page = 서버 페이지의 트랙 목록, panel = 재생 화면의 "다음 트랙" 탭 */ variant: "page" | "panel"; } /** * 유튜브 뮤직 "다음 트랙" 목록. * 맨 위에 지금 재생 중인 곡(이퀄라이저 표시), 그 아래 대기열. * - 곡 누르기: 그 곡부터 재생(앞 곡들은 대기열에서 빠진다) * - 마우스: 왼쪽 손잡이로 드래그해 순서 변경, hover 시 삭제 버튼 * - 터치 화면: hover 가 없으니 ▲▼·삭제 버튼을 항상 보여준다 */ export default function QueueList({ player, queue, variant }: QueueListProps) { const { track: nowPlaying, isPaused, autoplay, toggleAutoplay } = player; // items 는 대기열 앞부분(최대 200곡)만, total·totalMs 는 대기열 전체 기준 const { queue: items, total, totalMs, moveTo, moveBy, playFrom, remove, shuffle, clear } = queue; // "모두 지우기"는 되돌릴 수 없어서 한 번 더 눌러야 실행한다(3초 안에). const [confirmClear, setConfirmClear] = useState(false); useEffect(() => { if (!confirmClear) return; const t = setTimeout(() => setConfirmClear(false), 3000); return () => clearTimeout(t); }, [confirmClear]); const totalMin = Math.round(totalMs / 60000); const hiddenCount = Math.max(total - items.length, 0); const [draggingIndex, setDraggingIndex] = useState(null); const [dragOverIndex, setDragOverIndex] = useState(null); const dragItem = useRef(null); const dragOverItem = useRef(null); const handleSort = async () => { const fromIndex = dragItem.current; const toIndex = dragOverItem.current; setDraggingIndex(null); setDragOverIndex(null); dragItem.current = null; dragOverItem.current = null; if (fromIndex === null || toIndex === null) return; await moveTo(fromIndex, toIndex); }; const isPage = variant === "page"; const thumbSize = "h-10 w-10"; return (
{/* 머리말: 곡 수·총 길이 + 섞기 / 모두 지우기 */}
{isPage &&

재생 목록

}

{total > 0 ? `다음 트랙 ${total.toLocaleString()}곡${totalMin > 0 ? ` · 약 ${totalMin >= 60 ? `${Math.floor(totalMin / 60).toLocaleString()}시간 ${totalMin % 60}분` : `${totalMin}분`}` : ""}` : "다음 트랙 없음"}

{/* 자동재생(유튜브 뮤직 "다음 트랙" 위의 스위치와 같은 자리) */}
자동재생 대기열이 끝나면 비슷한 곡을 이어서 틀어요
{/* 지금 재생 중 */} {nowPlaying && (
{nowPlaying.info?.title || "제목 없음"} {cleanArtist(nowPlaying.info?.author) || "알 수 없는 아티스트"} {formatTime(nowPlaying.info?.length)}
)} {items.length === 0 ? (

대기 중인 노래가 없습니다.

검색해서 나온 곡을 누르면 여기에 쌓입니다.

) : ( <> {nowPlaying && (

다음 트랙

)}
    {items.map((item, index) => { const isDropTarget = dragOverIndex === index && draggingIndex !== index; const title = item.info?.title || "제목 없음"; return (
  • { dragItem.current = index; setTimeout(() => setDraggingIndex(index), 0); }} onDragEnter={() => { dragOverItem.current = index; setDragOverIndex(index); }} onDragEnd={handleSort} onDragOver={(e) => e.preventDefault()} className={`group relative flex items-center gap-1 rounded-md px-1 py-1 transition-colors hover:bg-white/10 ${draggingIndex === index ? "opacity-40" : ""} ${isDropTarget ? "shadow-[inset_0_2px_0_0_#fff]" : ""}`} > {/* 드래그 손잡이(마우스 전용) */}
  • ); })} {/* 맨 아래로 드래그해 옮기기 위한 드롭존(마우스 전용) */} {draggingIndex !== null && (
  • { dragOverItem.current = items.length; setDragOverIndex(items.length); }} onDragOver={(e) => e.preventDefault()} className="flex h-10 items-start" > {dragOverIndex === items.length && }
  • )}
{hiddenCount > 0 && (

외 {hiddenCount.toLocaleString()}곡 · 앞의 곡이 재생되면 차례대로 보여요

)} )}
); }