diff --git a/jam-ui/src/components/client/JKSessionJamTrackPlayer.css b/jam-ui/src/components/client/JKSessionJamTrackPlayer.css new file mode 100644 index 000000000..b146b4ece --- /dev/null +++ b/jam-ui/src/components/client/JKSessionJamTrackPlayer.css @@ -0,0 +1,332 @@ +.jamtrack-player-window { + background: #f5f5f7; + border-radius: 8px; + box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3); + width: 420px; + min-height: 200px; + display: flex; + flex-direction: column; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif; +} + +/* Window Chrome */ +.jamtrack-player-chrome { + display: flex; + align-items: center; + padding: 12px 16px; + background: linear-gradient(180deg, #ffffff 0%, #f5f5f7 100%); + border-bottom: 1px solid #d1d1d6; + border-radius: 8px 8px 0 0; +} + +.jamtrack-player-traffic-lights { + display: flex; + gap: 8px; + margin-right: 12px; +} + +.traffic-light { + width: 12px; + height: 12px; + border-radius: 50%; + border: 0.5px solid rgba(0, 0, 0, 0.1); +} + +.traffic-light.red { + background: #ff5f56; +} + +.traffic-light.yellow { + background: #ffbd2e; +} + +.traffic-light.green { + background: #27c93f; +} + +.jamtrack-player-title { + flex: 1; + text-align: center; + font-size: 14px; + font-weight: 500; + color: #1d1d1f; +} + +/* Content Area */ +.jamtrack-player-content { + padding: 20px 24px; + background: #ffffff; + flex: 1; +} + +/* Playback Controls */ +.jamtrack-controls { + display: flex; + align-items: center; + gap: 12px; + margin-bottom: 20px; +} + +.jamtrack-btn { + border: none; + cursor: pointer; + transition: all 0.2s ease; + display: flex; + align-items: center; + justify-content: center; +} + +.jamtrack-btn:disabled { + opacity: 0.4; + cursor: not-allowed; +} + +.jamtrack-btn-play { + width: 36px; + height: 36px; + border-radius: 50%; + background: #007aff; + color: white; +} + +.jamtrack-btn-play:hover:not(:disabled) { + background: #0051d5; + transform: scale(1.05); +} + +.jamtrack-btn-stop { + width: 28px; + height: 28px; + border-radius: 4px; + background: #007aff; + color: white; +} + +.jamtrack-btn-stop:hover:not(:disabled) { + background: #0051d5; +} + +/* Time Display */ +.jamtrack-time-display { + display: flex; + align-items: center; + gap: 10px; + flex: 1; +} + +.jamtrack-time { + font-size: 12px; + color: #6e6e73; + font-variant-numeric: tabular-nums; + min-width: 35px; +} + +/* Scrubber */ +.jamtrack-scrubber { + flex: 1; + position: relative; + height: 6px; + background: #e5e5ea; + border-radius: 3px; + overflow: hidden; + cursor: pointer; +} + +.jamtrack-scrubber:hover .jamtrack-scrubber-thumb { + transform: scale(1.2); +} + +.jamtrack-scrubber-progress { + position: absolute; + left: 0; + top: 0; + height: 100%; + background: #007aff; + border-radius: 3px 0 0 3px; + transition: width 0.1s linear; +} + +.jamtrack-scrubber-thumb { + position: absolute; + top: 50%; + transform: translateY(-50%); + width: 12px; + height: 12px; + background: #007aff; + border: 2px solid white; + border-radius: 50%; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2); + transition: transform 0.2s ease; +} + +.jamtrack-scrubber input[type="range"] { + position: absolute; + width: 100%; + height: 100%; + opacity: 0; + cursor: pointer; + z-index: 10; +} + +/* Mix Selector */ +.jamtrack-mix-selector { + margin-top: 20px; + display: flex; + align-items: center; + gap: 8px; +} + +.jamtrack-mix-label { + font-size: 13px; + color: #6e6e73; + font-weight: 500; +} + +.jamtrack-mix-dropdown { + flex: 1; + padding: 6px 12px; + font-size: 13px; + border: 1px solid #d1d1d6; + border-radius: 6px; + background: white; + color: #1d1d1f; + cursor: pointer; + outline: none; +} + +.jamtrack-mix-dropdown:hover { + border-color: #007aff; +} + +.jamtrack-mix-dropdown:focus { + border-color: #007aff; + box-shadow: 0 0 0 3px rgba(0, 122, 255, 0.1); +} + +.jamtrack-custom-mix-link { + margin-top: 8px; + text-align: right; +} + +.jamtrack-custom-mix-link a { + font-size: 12px; + color: #007aff; + text-decoration: none; + cursor: pointer; +} + +.jamtrack-custom-mix-link a:hover { + text-decoration: underline; +} + +/* Close Button */ +.jamtrack-close-btn { + margin-top: 20px; + padding: 8px 24px; + font-size: 13px; + font-weight: 500; + color: #007aff; + background: transparent; + border: 1px solid #d1d1d6; + border-radius: 6px; + cursor: pointer; + transition: all 0.2s ease; + align-self: center; +} + +.jamtrack-close-btn:hover { + background: #f5f5f7; + border-color: #007aff; +} + +/* Download/Sync State Banner */ +.jamtrack-state-banner { + background: #f0f8ff; + padding: 12px 16px; + margin-bottom: 16px; + border: 1px solid #cce7ff; + border-radius: 6px; +} + +.jamtrack-state-banner h4 { + margin: 0 0 8px 0; + font-size: 13px; + font-weight: 600; + color: #1d1d1f; +} + +.jamtrack-state-banner p { + margin: 4px 0; + font-size: 12px; + color: #6e6e73; +} + +.jamtrack-state-progress { + width: 100%; + height: 6px; + margin-top: 8px; + border-radius: 3px; +} + +/* Error Banner */ +.jamtrack-error-banner { + background: #fff5f5; + padding: 12px 16px; + margin-bottom: 16px; + border: 1px solid #ffcccc; + border-radius: 6px; +} + +.jamtrack-error-banner.warning { + background: #fffbf0; + border-color: #ffe4a3; +} + +.jamtrack-error-banner strong { + display: block; + margin-bottom: 4px; + font-size: 12px; + font-weight: 600; + color: #c41e3a; +} + +.jamtrack-error-banner.warning strong { + color: #a05a00; +} + +.jamtrack-error-actions { + margin-top: 8px; + display: flex; + gap: 8px; +} + +.jamtrack-error-btn { + padding: 4px 12px; + font-size: 12px; + border: 1px solid #d1d1d6; + border-radius: 4px; + background: white; + cursor: pointer; + transition: all 0.2s ease; +} + +.jamtrack-error-btn:hover { + background: #f5f5f7; +} + +/* Loading Spinner */ +.jamtrack-spinner { + display: inline-block; + width: 16px; + height: 16px; + border: 2px solid #e5e5ea; + border-top-color: #007aff; + border-radius: 50%; + animation: jamtrack-spin 0.8s linear infinite; +} + +@keyframes jamtrack-spin { + to { + transform: rotate(360deg); + } +} diff --git a/jam-ui/src/components/client/JKSessionJamTrackPlayer.js b/jam-ui/src/components/client/JKSessionJamTrackPlayer.js index 1c9aa7509..e5d75d7ed 100644 --- a/jam-ui/src/components/client/JKSessionJamTrackPlayer.js +++ b/jam-ui/src/components/client/JKSessionJamTrackPlayer.js @@ -10,6 +10,7 @@ import { import { setOpenJamTrack, clearOpenJamTrack } from '../../store/features/sessionUISlice'; import { setAvailableMixdowns, setActiveMixdown } from '../../store/features/activeSessionSlice'; import { useJamServerContext } from '../../context/JamServerContext'; +import './JKSessionJamTrackPlayer.css'; // Error types for comprehensive error handling const ERROR_TYPES = { @@ -475,148 +476,190 @@ const JKSessionJamTrackPlayer = ({ if (!isOpen && !isPopup) return null; return ( -
- {error && ( -
- {error.type.toUpperCase()} ERROR: {error.message} -
- - {(error.type === ERROR_TYPES.NETWORK || error.type === ERROR_TYPES.DOWNLOAD || error.type === ERROR_TYPES.FILE) && ( - +
+ {/* Window Chrome */} +
+
+
+
+
+
+
+ JamTrack: {jamTrack?.name || 'Loading...'} +
+
+ + {/* Content Area */} +
+ {/* Error Banner */} + {error && ( +
+ {error.type.toUpperCase()} ERROR: +
{error.message}
+
+ + {(error.type === ERROR_TYPES.NETWORK || error.type === ERROR_TYPES.DOWNLOAD || error.type === ERROR_TYPES.FILE) && ( + + )} +
+
+ )} + + {/* Download/Sync State Banner */} + {downloadState.state !== 'idle' && downloadState.state !== 'synchronized' && ( +
+

+ {downloadState.state === 'checking' && 'Checking sync status...'} + {downloadState.state === 'packaging' && 'Your JamTrack is currently being created in the JamKazam server'} + {downloadState.state === 'downloading' && 'Downloading JamTrack...'} + {downloadState.state === 'keying' && 'Requesting decryption keys...'} + {downloadState.state === 'error' && 'Download Failed'} +

+ + {downloadState.state === 'packaging' && ( + <> +

+ {downloadState.signing_state === 'SIGNED' && 'Package ready, starting download...'} + {downloadState.signing_state !== 'SIGNED' && downloadState.signing_state && `Status: ${downloadState.signing_state}`} + {!downloadState.signing_state && 'Preparing your JamTrack...'} +

+ {downloadState.packaging_steps > 0 && ( +

Step {downloadState.current_packaging_step} of {downloadState.packaging_steps}

+ )} +
+ + )} + + {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'}

+ + )}
-
- )} + )} -{/* Download/Sync State Machine UI */} - {downloadState.state !== 'idle' && downloadState.state !== 'synchronized' && ( -
-

- {downloadState.state === 'checking' && 'Checking sync status...'} - {downloadState.state === 'packaging' && 'Your JamTrack is currently being created in the JamKazam server'} - {downloadState.state === 'downloading' && 'Downloading JamTrack...'} - {downloadState.state === 'keying' && 'Requesting decryption keys...'} - {downloadState.state === 'error' && 'Download Failed'} -

- - {downloadState.state === 'packaging' && ( -
-

- {downloadState.signing_state === 'SIGNED' && 'Package ready, starting download...'} - {downloadState.signing_state !== 'SIGNED' && downloadState.signing_state && `Status: ${downloadState.signing_state}`} - {!downloadState.signing_state && 'Preparing your JamTrack...'} -

- {downloadState.packaging_steps > 0 && ( -

- Step {downloadState.current_packaging_step} of {downloadState.packaging_steps} -

- )} -
-
- Packaging... -
-
-
- )} - - {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'}

- -
- )} -
- )} - -
- - - -
- -
- handleSeek(parseInt(e.target.value, 10))} - disabled={isOperating || !jamTrackState.durationMs} - style={{ width: '300px' }} - /> -
- -
-

- {formattedPosition} / {formattedDuration} -

-
- - {availableMixdowns.length > 0 && ( -
- - + {jamTrackState.isPlaying && !jamTrackState.isPaused ? ( + + + + + ) : ( + + + + )} + + + + + {/* Time Display and Scrubber */} +
+ {formattedPosition} +
+
+
+
+ handleSeek(parseInt(e.target.value, 10))} + disabled={isOperating || !jamTrackState.durationMs} + /> +
+ {formattedDuration} +
- )} + + {/* Mix Selector */} + {availableMixdowns.length > 0 && ( + <> +
+ Mix: + +
+
+ console.log('TODO: Open custom mix creator')}> + create custom mix + +
+ + )} + + {/* Close Button */} + +
); };