feat(05-04): implement mixdown picker UI with hierarchy sorting
- Add handleMixdownChange callback to switch between mixdowns - Stop and restart playback when changing mixdown (if playing) - Add mixdown dropdown selector with sorted display - Sort order: master → custom mixes → stems (alphabetical within types) - Visual indicators: 🎵 master, 🎨 custom, 🎸 stem - Disable picker during operations and loading - Update selectedMixdownId local state and activeMixdown Redux state Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
7014107152
commit
845b44a319
|
|
@ -241,6 +241,46 @@ const JKSessionJamTrackPlayer = ({
|
|||
}
|
||||
}, [isOperating, jamClient, jamTrackState.isPaused, dispatch]);
|
||||
|
||||
// Mixdown change handler
|
||||
const handleMixdownChange = useCallback(async (mixdownId) => {
|
||||
if (isOperating || !jamClient || !fqIdRef.current) return;
|
||||
|
||||
try {
|
||||
setIsOperating(true);
|
||||
setError(null);
|
||||
|
||||
// Find the new mixdown
|
||||
const mixdown = availableMixdowns.find(m => m.id === mixdownId);
|
||||
if (!mixdown) {
|
||||
throw new Error('Mixdown not found');
|
||||
}
|
||||
|
||||
// Update local state
|
||||
setSelectedMixdownId(mixdownId);
|
||||
dispatch(setActiveMixdown(mixdown));
|
||||
|
||||
// If currently playing, stop and restart with new mixdown
|
||||
if (jamTrackState.isPlaying || jamTrackState.isPaused) {
|
||||
await jamClient.JamTrackStop(fqIdRef.current);
|
||||
|
||||
await dispatch(loadJamTrack({
|
||||
jamTrack,
|
||||
mixdownId,
|
||||
autoPlay: true,
|
||||
jamClient
|
||||
})).unwrap();
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
console.error('[JamTrack] Mixdown change error:', err);
|
||||
setError({ type: 'playback', message: 'Failed to change mixdown' });
|
||||
} finally {
|
||||
if (mountedRef.current) {
|
||||
setIsOperating(false);
|
||||
}
|
||||
}
|
||||
}, [isOperating, jamClient, jamTrack, jamTrackState, availableMixdowns, dispatch]);
|
||||
|
||||
// Helper: Format milliseconds to MM:SS
|
||||
const formatTime = (ms) => {
|
||||
if (!ms || isNaN(ms)) return '00:00';
|
||||
|
|
@ -354,6 +394,37 @@ const JKSessionJamTrackPlayer = ({
|
|||
{formatTime(jamTrackState.currentPositionMs)} / {formatTime(jamTrackState.durationMs)}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{availableMixdowns.length > 0 && (
|
||||
<div style={{ marginTop: '10px' }}>
|
||||
<label>Mixdown:</label>
|
||||
<select
|
||||
value={selectedMixdownId || ''}
|
||||
onChange={(e) => handleMixdownChange(parseInt(e.target.value, 10))}
|
||||
disabled={isOperating || isLoadingSync}
|
||||
>
|
||||
{availableMixdowns
|
||||
.slice()
|
||||
.sort((a, b) => {
|
||||
// Sort order: master first, then custom mixes, then stems
|
||||
if (a.type === 'master') return -1;
|
||||
if (b.type === 'master') return 1;
|
||||
if (a.type === 'custom' || a.type === 'custom-mix') return -1;
|
||||
if (b.type === 'custom' || b.type === 'custom-mix') return 1;
|
||||
return a.name.localeCompare(b.name);
|
||||
})
|
||||
.map(mixdown => (
|
||||
<option key={mixdown.id} value={mixdown.id}>
|
||||
{mixdown.type === 'master' && '🎵 '}
|
||||
{(mixdown.type === 'custom' || mixdown.type === 'custom-mix') && '🎨 '}
|
||||
{mixdown.type === 'stem' && '🎸 '}
|
||||
{mixdown.name}
|
||||
</option>
|
||||
))
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue