feat(web-ui): YouTube 6 서브탭 + Library 영상 파이프라인 트리거
This commit is contained in:
@@ -338,7 +338,7 @@ const TrackResult = ({ track, onDownload, onNew }) => {
|
|||||||
/* ─────────────────────────────────────────────
|
/* ─────────────────────────────────────────────
|
||||||
Library Card
|
Library Card
|
||||||
───────────────────────────────────────────── */
|
───────────────────────────────────────────── */
|
||||||
const LibraryCard = ({ track, onDelete, onPlay, isPlaying, onExtend, onVocalRemoval, onCoverArt, onWavConvert, onStemSplit, onSyncedLyrics, onVideoGenerate, onVideoProject, isGenerating }) => {
|
const LibraryCard = ({ track, onDelete, onPlay, isPlaying, onExtend, onVocalRemoval, onCoverArt, onWavConvert, onStemSplit, onSyncedLyrics, onVideoGenerate, onVideoProject, onVideoPipeline, isGenerating }) => {
|
||||||
const [menuOpen, setMenuOpen] = useState(false);
|
const [menuOpen, setMenuOpen] = useState(false);
|
||||||
const genre = GENRES.find((g) => g.id === track.genre);
|
const genre = GENRES.find((g) => g.id === track.genre);
|
||||||
const totalSec = track.duration_sec ?? null;
|
const totalSec = track.duration_sec ?? null;
|
||||||
@@ -429,6 +429,9 @@ const LibraryCard = ({ track, onDelete, onPlay, isPlaying, onExtend, onVocalRemo
|
|||||||
<button type="button" onClick={() => { onVideoProject(track); setMenuOpen(false); }}>
|
<button type="button" onClick={() => { onVideoProject(track); setMenuOpen(false); }}>
|
||||||
🎯 YouTube 프로젝트
|
🎯 YouTube 프로젝트
|
||||||
</button>
|
</button>
|
||||||
|
<button type="button" onClick={() => { onVideoPipeline(track); setMenuOpen(false); }}>
|
||||||
|
🎬 영상 파이프라인
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -439,6 +442,10 @@ const LibraryCard = ({ track, onDelete, onPlay, isPlaying, onExtend, onVocalRemo
|
|||||||
<a href={track.audio_url} download className="ms-btn ms-btn--ghost ms-btn--sm">
|
<a href={track.audio_url} download className="ms-btn ms-btn--ghost ms-btn--sm">
|
||||||
↓ Download
|
↓ Download
|
||||||
</a>
|
</a>
|
||||||
|
<button type="button" className="ms-btn ms-btn--ghost ms-btn--sm"
|
||||||
|
onClick={() => onVideoPipeline(track)}>
|
||||||
|
🎬 영상 파이프라인
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<p className="ms-lib-card__date">
|
<p className="ms-lib-card__date">
|
||||||
@@ -451,7 +458,7 @@ const LibraryCard = ({ track, onDelete, onPlay, isPlaying, onExtend, onVocalRemo
|
|||||||
/* ─────────────────────────────────────────────
|
/* ─────────────────────────────────────────────
|
||||||
Library Section
|
Library Section
|
||||||
───────────────────────────────────────────── */
|
───────────────────────────────────────────── */
|
||||||
const Library = ({ tracks, onDelete, onRefresh, onExtend, onVocalRemoval, onCoverArt, onWavConvert, onStemSplit, onSyncedLyrics, onVideoGenerate, onVideoProject, isGenerating, loading }) => {
|
const Library = ({ tracks, onDelete, onRefresh, onExtend, onVocalRemoval, onCoverArt, onWavConvert, onStemSplit, onSyncedLyrics, onVideoGenerate, onVideoProject, onVideoPipeline, isGenerating, loading }) => {
|
||||||
const [playingId, setPlayingId] = useState(null);
|
const [playingId, setPlayingId] = useState(null);
|
||||||
|
|
||||||
const handlePlay = (track) => {
|
const handlePlay = (track) => {
|
||||||
@@ -506,6 +513,7 @@ const Library = ({ tracks, onDelete, onRefresh, onExtend, onVocalRemoval, onCove
|
|||||||
onSyncedLyrics={onSyncedLyrics}
|
onSyncedLyrics={onSyncedLyrics}
|
||||||
onVideoGenerate={onVideoGenerate}
|
onVideoGenerate={onVideoGenerate}
|
||||||
onVideoProject={onVideoProject}
|
onVideoProject={onVideoProject}
|
||||||
|
onVideoPipeline={onVideoPipeline}
|
||||||
isGenerating={isGenerating}
|
isGenerating={isGenerating}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
@@ -521,6 +529,7 @@ export default function MusicStudio() {
|
|||||||
/* ── 탭 ── */
|
/* ── 탭 ── */
|
||||||
const [tab, setTab] = useState('create');
|
const [tab, setTab] = useState('create');
|
||||||
const [initialTrackId, setInitialTrackId] = useState(null);
|
const [initialTrackId, setInitialTrackId] = useState(null);
|
||||||
|
const [openPipelineFor, setOpenPipelineFor] = useState(null);
|
||||||
|
|
||||||
/* ── Provider 상태 ── */
|
/* ── Provider 상태 ── */
|
||||||
const [providers, setProviders] = useState([]);
|
const [providers, setProviders] = useState([]);
|
||||||
@@ -1070,6 +1079,11 @@ export default function MusicStudio() {
|
|||||||
setTab('youtube');
|
setTab('youtube');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleVideoPipeline = (track) => {
|
||||||
|
setOpenPipelineFor(track.id);
|
||||||
|
setTab('youtube');
|
||||||
|
};
|
||||||
|
|
||||||
const handleNewTrack = () => {
|
const handleNewTrack = () => {
|
||||||
setTrack(null);
|
setTrack(null);
|
||||||
setGenProgress(0);
|
setGenProgress(0);
|
||||||
@@ -1159,6 +1173,7 @@ export default function MusicStudio() {
|
|||||||
onSyncedLyrics={handleSyncedLyrics}
|
onSyncedLyrics={handleSyncedLyrics}
|
||||||
onVideoGenerate={handleVideoGenerate}
|
onVideoGenerate={handleVideoGenerate}
|
||||||
onVideoProject={handleVideoProject}
|
onVideoProject={handleVideoProject}
|
||||||
|
onVideoPipeline={handleVideoPipeline}
|
||||||
isGenerating={isGenerating}
|
isGenerating={isGenerating}
|
||||||
/>
|
/>
|
||||||
</PullToRefresh>
|
</PullToRefresh>
|
||||||
@@ -1193,6 +1208,7 @@ export default function MusicStudio() {
|
|||||||
library={library}
|
library={library}
|
||||||
initialTrackId={initialTrackId}
|
initialTrackId={initialTrackId}
|
||||||
onClearInitialTrack={() => setInitialTrackId(null)}
|
onClearInitialTrack={() => setInitialTrackId(null)}
|
||||||
|
openPipelineFor={openPipelineFor}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -3,48 +3,45 @@ import VideoProjectsTab from './VideoProjectsTab';
|
|||||||
import RevenueTab from './RevenueTab';
|
import RevenueTab from './RevenueTab';
|
||||||
import TrendsTab from './TrendsTab';
|
import TrendsTab from './TrendsTab';
|
||||||
import CompileTab from './CompileTab';
|
import CompileTab from './CompileTab';
|
||||||
|
import PipelineTab from './PipelineTab';
|
||||||
|
import SetupTab from './SetupTab';
|
||||||
|
|
||||||
export default function YoutubeTab({ library, initialTrackId, onClearInitialTrack }) {
|
export default function YoutubeTab({ library, initialTrackId, onClearInitialTrack, openPipelineFor }) {
|
||||||
const [subtab, setSubtab] = useState('video');
|
const [subtab, setSubtab] = useState('pipeline');
|
||||||
|
|
||||||
// initialTrackId가 들어오면 video 서브탭으로 전환
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (initialTrackId) setSubtab('video');
|
if (initialTrackId) setSubtab('video');
|
||||||
}, [initialTrackId]);
|
}, [initialTrackId]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (openPipelineFor) setSubtab('pipeline');
|
||||||
|
}, [openPipelineFor]);
|
||||||
|
|
||||||
|
const tabs = [
|
||||||
|
['pipeline', '🚀 진행'],
|
||||||
|
['video', '🎬 영상 제작'],
|
||||||
|
['compile', '🎵 컴파일'],
|
||||||
|
['trends', '📊 시장 트렌드'],
|
||||||
|
['revenue', '💰 수익 추적'],
|
||||||
|
['setup', '⚙️ 구성'],
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="yt-container">
|
<div className="yt-container">
|
||||||
<nav className="yt-subtabs">
|
<nav className="yt-subtabs">
|
||||||
<button
|
{tabs.map(([key, label]) => (
|
||||||
type="button"
|
<button
|
||||||
className={`yt-subtab ${subtab === 'video' ? 'is-active' : ''}`}
|
key={key}
|
||||||
onClick={() => setSubtab('video')}
|
type="button"
|
||||||
>
|
className={`yt-subtab ${subtab === key ? 'is-active' : ''}`}
|
||||||
🎬 영상 제작
|
onClick={() => setSubtab(key)}
|
||||||
</button>
|
>
|
||||||
<button
|
{label}
|
||||||
type="button"
|
</button>
|
||||||
className={`yt-subtab ${subtab === 'revenue' ? 'is-active' : ''}`}
|
))}
|
||||||
onClick={() => setSubtab('revenue')}
|
|
||||||
>
|
|
||||||
💰 수익 추적
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className={`yt-subtab ${subtab === 'trends' ? 'is-active' : ''}`}
|
|
||||||
onClick={() => setSubtab('trends')}
|
|
||||||
>
|
|
||||||
📊 시장 트렌드
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className={`yt-subtab ${subtab === 'compile' ? 'is-active' : ''}`}
|
|
||||||
onClick={() => setSubtab('compile')}
|
|
||||||
>
|
|
||||||
🎵 컴파일
|
|
||||||
</button>
|
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
|
{subtab === 'pipeline' && <PipelineTab library={library} initialTrackId={openPipelineFor} />}
|
||||||
{subtab === 'video' && (
|
{subtab === 'video' && (
|
||||||
<VideoProjectsTab
|
<VideoProjectsTab
|
||||||
library={library}
|
library={library}
|
||||||
@@ -52,9 +49,10 @@ export default function YoutubeTab({ library, initialTrackId, onClearInitialTrac
|
|||||||
onClearInitialTrack={onClearInitialTrack}
|
onClearInitialTrack={onClearInitialTrack}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{subtab === 'revenue' && <RevenueTab />}
|
|
||||||
{subtab === 'trends' && <TrendsTab />}
|
|
||||||
{subtab === 'compile' && <CompileTab library={library} />}
|
{subtab === 'compile' && <CompileTab library={library} />}
|
||||||
|
{subtab === 'trends' && <TrendsTab />}
|
||||||
|
{subtab === 'revenue' && <RevenueTab />}
|
||||||
|
{subtab === 'setup' && <SetupTab />}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user