From 6a68d8b402717a8933b0db393c69c12aa552a281 Mon Sep 17 00:00:00 2001 From: Nuwan Date: Thu, 15 Jan 2026 01:00:59 +0530 Subject: [PATCH] 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 --- .../client/JKSessionJamTrackPlayer.js | 83 ++++++++++++++++++- 1 file changed, 80 insertions(+), 3 deletions(-) diff --git a/jam-ui/src/components/client/JKSessionJamTrackPlayer.js b/jam-ui/src/components/client/JKSessionJamTrackPlayer.js index 20edd8a11..b9edf5d2b 100644 --- a/jam-ui/src/components/client/JKSessionJamTrackPlayer.js +++ b/jam-ui/src/components/client/JKSessionJamTrackPlayer.js @@ -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 = ({ )} +{/* Download/Sync State Machine UI */} {downloadState.state !== 'idle' && downloadState.state !== 'synchronized' && ( -
-

Download: {downloadState.state} ({downloadState.progress}%)

+
+

+ {downloadState.state === 'checking' && 'Checking sync status...'} + {downloadState.state === 'downloading' && 'Downloading JamTrack...'} + {downloadState.state === 'keying' && 'Requesting decryption keys...'} + {downloadState.state === 'error' && 'Download Failed'} +

+ + {downloadState.state === 'downloading' && ( +
+ +

{downloadState.progress}%

+ {downloadState.totalSteps > 0 && ( +

Step {downloadState.currentStep} of {downloadState.totalSteps}

+ )} + +
+ )} + + {downloadState.state === 'keying' && ( +
+

Finalizing download...

+
+ )} + + {downloadState.state === 'error' && ( +
+

{downloadState.error?.message || 'Download failed'}

+ +
+ )}
)}