feat(05-04): implement download/sync UI with 6-state machine visualization
- Add handleCancelDownload callback to cancel downloads via JamTrackCancelDownload - Add handleRetryDownload callback to retry failed downloads - Implement 6-state sync machine UI (checking, downloading, keying, error) - Show progress bar with percentage (0-100%) during download - Show step indicator (X of Y) when available - Display Cancel button during download state - Display Retry button on error state with error message - Hide UI for idle and synchronized states (normal playback) - Import setDownloadState from mediaSlice Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
845b44a319
commit
6a68d8b402
|
|
@ -4,7 +4,8 @@ import {
|
|||
loadJamTrack,
|
||||
checkJamTrackSync,
|
||||
closeJamTrack,
|
||||
setJamTrackState
|
||||
setJamTrackState,
|
||||
setDownloadState
|
||||
} from '../../store/features/mediaSlice';
|
||||
import { setOpenJamTrack, clearOpenJamTrack } from '../../store/features/sessionUISlice';
|
||||
import { setAvailableMixdowns, setActiveMixdown } from '../../store/features/activeSessionSlice';
|
||||
|
|
@ -281,6 +282,48 @@ const JKSessionJamTrackPlayer = ({
|
|||
}
|
||||
}, [isOperating, jamClient, jamTrack, jamTrackState, availableMixdowns, dispatch]);
|
||||
|
||||
// Download cancel handler
|
||||
const handleCancelDownload = useCallback(async () => {
|
||||
if (!jamClient || !fqIdRef.current) return;
|
||||
|
||||
try {
|
||||
await jamClient.JamTrackCancelDownload(fqIdRef.current);
|
||||
dispatch(setDownloadState({ state: 'idle', progress: 0 }));
|
||||
} catch (err) {
|
||||
console.error('[JamTrack] Cancel download error:', err);
|
||||
setError({ type: 'download', message: 'Failed to cancel download' });
|
||||
}
|
||||
}, [jamClient, dispatch]);
|
||||
|
||||
// Download retry handler
|
||||
const handleRetryDownload = useCallback(async () => {
|
||||
if (isOperating || !jamClient) return;
|
||||
|
||||
try {
|
||||
setIsOperating(true);
|
||||
setError(null);
|
||||
|
||||
// Clear error state
|
||||
dispatch(setDownloadState({ state: 'idle', error: null }));
|
||||
|
||||
// Retry load (will trigger download if not synced)
|
||||
await dispatch(loadJamTrack({
|
||||
jamTrack,
|
||||
mixdownId: selectedMixdownId,
|
||||
autoPlay: false,
|
||||
jamClient
|
||||
})).unwrap();
|
||||
|
||||
} catch (err) {
|
||||
console.error('[JamTrack] Retry download error:', err);
|
||||
setError({ type: 'download', message: 'Retry failed' });
|
||||
} finally {
|
||||
if (mountedRef.current) {
|
||||
setIsOperating(false);
|
||||
}
|
||||
}
|
||||
}, [isOperating, jamClient, jamTrack, selectedMixdownId, dispatch]);
|
||||
|
||||
// Helper: Format milliseconds to MM:SS
|
||||
const formatTime = (ms) => {
|
||||
if (!ms || isNaN(ms)) return '00:00';
|
||||
|
|
@ -359,9 +402,43 @@ const JKSessionJamTrackPlayer = ({
|
|||
</div>
|
||||
)}
|
||||
|
||||
{/* Download/Sync State Machine UI */}
|
||||
{downloadState.state !== 'idle' && downloadState.state !== 'synchronized' && (
|
||||
<div>
|
||||
<p>Download: {downloadState.state} ({downloadState.progress}%)</p>
|
||||
<div style={{ background: '#f0f8ff', padding: '15px', marginBottom: '10px', border: '1px solid #cce7ff' }}>
|
||||
<h4>
|
||||
{downloadState.state === 'checking' && 'Checking sync status...'}
|
||||
{downloadState.state === 'downloading' && 'Downloading JamTrack...'}
|
||||
{downloadState.state === 'keying' && 'Requesting decryption keys...'}
|
||||
{downloadState.state === 'error' && 'Download Failed'}
|
||||
</h4>
|
||||
|
||||
{downloadState.state === 'downloading' && (
|
||||
<div>
|
||||
<progress
|
||||
value={downloadState.progress}
|
||||
max="100"
|
||||
style={{ width: '100%', height: '20px' }}
|
||||
/>
|
||||
<p>{downloadState.progress}%</p>
|
||||
{downloadState.totalSteps > 0 && (
|
||||
<p>Step {downloadState.currentStep} of {downloadState.totalSteps}</p>
|
||||
)}
|
||||
<button onClick={handleCancelDownload}>Cancel</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{downloadState.state === 'keying' && (
|
||||
<div>
|
||||
<p>Finalizing download...</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{downloadState.state === 'error' && (
|
||||
<div style={{ color: '#d00' }}>
|
||||
<p>{downloadState.error?.message || 'Download failed'}</p>
|
||||
<button onClick={handleRetryDownload}>Retry</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue