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:
Nuwan 2026-01-15 01:00:59 +05:30
parent 845b44a319
commit 6a68d8b402
1 changed files with 80 additions and 3 deletions

View File

@ -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>
)}