feat(youtube-tab): MusicStudio YouTube 탭 연결 + CSS + Library 버튼

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-01 15:08:15 +09:00
parent ed95f6678f
commit 08981a292a
2 changed files with 486 additions and 2 deletions

View File

@@ -27,6 +27,7 @@ import LyricsTab from './components/LyricsTab';
import StemModal from './components/StemModal';
import SyncedLyricsPlayer from './components/SyncedLyricsPlayer';
import RemixTab from './components/RemixTab';
import YoutubeTab from './components/YoutubeTab';
/* ─────────────────────────────────────────────
데이터 상수
@@ -337,7 +338,7 @@ const TrackResult = ({ track, onDownload, onNew }) => {
/* ─────────────────────────────────────────────
Library Card
───────────────────────────────────────────── */
const LibraryCard = ({ track, onDelete, onPlay, isPlaying, onExtend, onVocalRemoval, onCoverArt, onWavConvert, onStemSplit, onSyncedLyrics, onVideoGenerate, isGenerating }) => {
const LibraryCard = ({ track, onDelete, onPlay, isPlaying, onExtend, onVocalRemoval, onCoverArt, onWavConvert, onStemSplit, onSyncedLyrics, onVideoGenerate, onVideoProject, isGenerating }) => {
const [menuOpen, setMenuOpen] = useState(false);
const genre = GENRES.find((g) => g.id === track.genre);
const totalSec = track.duration_sec ?? null;
@@ -425,6 +426,9 @@ const LibraryCard = ({ track, onDelete, onPlay, isPlaying, onExtend, onVocalRemo
disabled={isGenerating || !track.lyrics}>📝 Synced Lyrics</button>
<button type="button" onClick={() => { onVideoGenerate(track); setMenuOpen(false); }}
disabled={isGenerating}>🎬 Music Video</button>
<button type="button" onClick={() => { onVideoProject(track); setMenuOpen(false); }}>
🎯 YouTube 프로젝트
</button>
</div>
)}
</div>
@@ -447,7 +451,7 @@ const LibraryCard = ({ track, onDelete, onPlay, isPlaying, onExtend, onVocalRemo
/* ─────────────────────────────────────────────
Library Section
───────────────────────────────────────────── */
const Library = ({ tracks, onDelete, onRefresh, onExtend, onVocalRemoval, onCoverArt, onWavConvert, onStemSplit, onSyncedLyrics, onVideoGenerate, isGenerating, loading }) => {
const Library = ({ tracks, onDelete, onRefresh, onExtend, onVocalRemoval, onCoverArt, onWavConvert, onStemSplit, onSyncedLyrics, onVideoGenerate, onVideoProject, isGenerating, loading }) => {
const [playingId, setPlayingId] = useState(null);
const handlePlay = (track) => {
@@ -501,6 +505,7 @@ const Library = ({ tracks, onDelete, onRefresh, onExtend, onVocalRemoval, onCove
onStemSplit={onStemSplit}
onSyncedLyrics={onSyncedLyrics}
onVideoGenerate={onVideoGenerate}
onVideoProject={onVideoProject}
isGenerating={isGenerating}
/>
))}
@@ -515,6 +520,7 @@ const Library = ({ tracks, onDelete, onRefresh, onExtend, onVocalRemoval, onCove
export default function MusicStudio() {
/* ── 탭 ── */
const [tab, setTab] = useState('create');
const [initialTrackId, setInitialTrackId] = useState(null);
/* ── Provider 상태 ── */
const [providers, setProviders] = useState([]);
@@ -1058,6 +1064,11 @@ export default function MusicStudio() {
}
};
const handleVideoProject = (track) => {
setInitialTrackId(track.id);
setTab('youtube');
};
const handleNewTrack = () => {
setTrack(null);
setGenProgress(0);
@@ -1121,6 +1132,13 @@ export default function MusicStudio() {
>
<span className="ms-tab__icon">🔄</span> Remix
</button>
<button
type="button"
className={`ms-tab ms-tab--youtube ${tab === 'youtube' ? 'is-active' : ''}`}
onClick={() => setTab('youtube')}
>
<span className="ms-tab__icon">🎯</span> YouTube
</button>
</nav>
{/* ═══ LIBRARY TAB ═══ */}
@@ -1138,6 +1156,7 @@ export default function MusicStudio() {
onStemSplit={handleStemSplit}
onSyncedLyrics={handleSyncedLyrics}
onVideoGenerate={handleVideoGenerate}
onVideoProject={handleVideoProject}
isGenerating={isGenerating}
/>
</PullToRefresh>
@@ -1166,6 +1185,15 @@ export default function MusicStudio() {
/>
)}
{/* ═══ YOUTUBE TAB ═══ */}
{tab === 'youtube' && (
<YoutubeTab
library={library}
initialTrackId={initialTrackId}
onClearInitialTrack={() => setInitialTrackId(null)}
/>
)}
{/* ═══ CREATE TAB ═══ */}
{tab === 'create' && (
<div className="ms-layout">