Skip to content

Commit 8744c12

Browse files
committed
ui: consolidate Timeline controls into compact single toolbar
- Merge detection toggle, action buttons (Snapshot/Download/Protect/Delete), and speed controls into one compact row below the video - Replace large bordered SpeedControls panel with inline small pill buttons - Shrink play button from 40px to 28px circle - Reduce time display font size and margins - Use emoji icons for action buttons to save space (📷 ↓ 🛡 🗑) - Reduce all inter-element margins (mb-2 → mb-1, etc) - Net result: ~3 fewer rows between video player and timeline bar
1 parent f819ba8 commit 8744c12

3 files changed

Lines changed: 73 additions & 98 deletions

File tree

web/js/components/preact/timeline/SpeedControls.jsx

Lines changed: 15 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -44,30 +44,21 @@ export function SpeedControls() {
4444
};
4545

4646
return (
47-
<div className="mt-2 mb-4 p-2 border border-primary rounded-lg bg-card text-card-foreground shadow-sm">
48-
<div className="flex flex-col items-center">
49-
<div className="text-sm font-semibold mb-2 text-foreground">Playback Speed</div>
50-
51-
<div className="flex flex-wrap justify-center gap-1">
52-
{speeds.map(speed => (
53-
<button
54-
key={`speed-${speed}`}
55-
className={`speed-btn px-2 py-1 text-sm rounded-full ${speed === currentSpeed
56-
? 'bg-primary text-primary-foreground'
57-
: 'bg-secondary text-secondary-foreground hover:bg-secondary/80'}
58-
font-medium transition-all focus:outline-none focus:ring-1 focus:ring-primary focus:ring-opacity-50`}
59-
data-speed={speed}
60-
onClick={() => setPlaybackSpeed(speed)}
61-
>
62-
{speed === 1.0 ? '1× (Normal)' : `${speed}×`}
63-
</button>
64-
))}
65-
</div>
66-
67-
<div className="mt-1 text-xs font-medium text-primary">
68-
Current: {currentSpeed}× {currentSpeed === 1.0 ? '(Normal)' : ''}
69-
</div>
70-
</div>
47+
<div className="flex items-center gap-0.5">
48+
<span className="text-[10px] text-muted-foreground mr-0.5">Speed</span>
49+
{speeds.map(speed => (
50+
<button
51+
key={`speed-${speed}`}
52+
className={`px-1.5 py-0.5 text-[11px] rounded ${speed === currentSpeed
53+
? 'bg-primary text-primary-foreground'
54+
: 'bg-secondary text-secondary-foreground hover:bg-secondary/80'}
55+
font-medium transition-all focus:outline-none`}
56+
data-speed={speed}
57+
onClick={() => setPlaybackSpeed(speed)}
58+
>
59+
{speed}×
60+
</button>
61+
))}
7162
</div>
7263
);
7364
}

web/js/components/preact/timeline/TimelineControls.jsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -389,15 +389,15 @@ export function TimelineControls() {
389389
};
390390

391391
return (
392-
<div className="timeline-controls flex justify-between items-center mb-2">
393-
<div className="flex items-center">
392+
<div className="timeline-controls flex justify-between items-center mb-1">
393+
<div className="flex items-center gap-1.5">
394394
<button
395395
id="play-button"
396-
className="w-10 h-10 rounded-full btn-success flex items-center justify-center focus:outline-none focus:ring-1 focus:ring-green-500 focus:ring-offset-1 transition-colors shadow-sm mr-2"
396+
className="w-7 h-7 rounded-full btn-success flex items-center justify-center focus:outline-none transition-colors shadow-sm"
397397
onClick={togglePlayback}
398398
title={isPlaying ? 'Pause' : 'Play from current position'}
399399
>
400-
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
400+
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
401401
{isPlaying ? (
402402
<>
403403
<rect x="6" y="6" width="4" height="12" rx="1" fill="white" />
@@ -408,12 +408,12 @@ export function TimelineControls() {
408408
)}
409409
</svg>
410410
</button>
411-
<span className="text-xs text-muted-foreground">Play from current position</span>
411+
<span className="text-[11px] text-muted-foreground">Play from cursor</span>
412412
</div>
413413

414414
{/* Current time display */}
415415
<div id="time-display"
416-
className="timeline-time-display bg-secondary text-foreground px-2 py-0.5 rounded font-mono text-sm tabular-nums border border-border">
416+
className="timeline-time-display bg-secondary text-foreground px-2 py-0.5 rounded font-mono text-xs tabular-nums border border-border">
417417
00:00:00
418418
</div>
419419

web/js/components/preact/timeline/TimelinePlayer.jsx

Lines changed: 52 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -679,7 +679,7 @@ export function TimelinePlayer() {
679679

680680
return (
681681
<>
682-
<div className="timeline-player-container mb-2" id="video-player">
682+
<div className="timeline-player-container mb-1" id="video-player">
683683
<div ref={videoContainerRef} className="relative w-full bg-black rounded-lg shadow-md" style={{ aspectRatio: '16/9' }}>
684684
<video
685685
ref={videoRef}
@@ -715,81 +715,65 @@ export function TimelinePlayer() {
715715
</div>
716716
</div>
717717

718-
{/* Detection overlay toggle and playback speed controls */}
719-
<div className="flex items-center justify-between mb-2">
720-
<div className="flex items-center gap-2">
718+
{/* Compact toolbar: detections toggle | action buttons | speed */}
719+
<div className="flex items-center flex-wrap gap-x-3 gap-y-1 mb-1">
720+
{/* Detection toggle */}
721+
<label className="flex items-center gap-1.5 cursor-pointer">
721722
<input
722723
type="checkbox"
723724
id="timeline-detection-overlay"
724-
className="w-4 h-4 accent-primary"
725+
className="w-3.5 h-3.5 accent-primary"
725726
checked={detectionOverlayEnabled}
726727
onChange={(e) => setDetectionOverlayEnabled(e.target.checked)}
727728
/>
728-
<label htmlFor="timeline-detection-overlay" className="text-xs font-medium text-foreground">
729-
Show Detections {detections.length > 0 ? `(${detections.length})` : ''}
730-
</label>
731-
</div>
732-
<SpeedControls />
733-
</div>
729+
<span className="text-[11px] text-foreground">
730+
Detections{detections.length > 0 ? ` (${detections.length})` : ''}
731+
</span>
732+
</label>
733+
734+
{/* Action buttons — only when a segment is selected */}
735+
{currentSegmentId && (
736+
<div className="flex items-center gap-1">
737+
<button
738+
className="px-2 py-1 bg-secondary text-secondary-foreground rounded hover:bg-secondary/80 transition-colors flex items-center text-[11px]"
739+
onClick={handleSnapshot}
740+
title="Take Snapshot"
741+
>
742+
📷 Snapshot
743+
</button>
744+
<a
745+
className="px-2 py-1 bg-primary text-primary-foreground rounded hover:bg-primary/90 transition-colors flex items-center text-[11px]"
746+
href={`/api/recordings/download/${currentSegmentId}`}
747+
download
748+
>
749+
↓ Download
750+
</a>
751+
<button
752+
className={`px-2 py-1 rounded transition-colors flex items-center text-[11px] ${
753+
isProtected
754+
? 'bg-yellow-500 text-white hover:bg-yellow-600'
755+
: 'bg-secondary text-secondary-foreground hover:bg-secondary/80'
756+
}`}
757+
onClick={handleToggleProtection}
758+
title={isProtected ? 'Unprotect Recording' : 'Protect Recording'}
759+
>
760+
🛡 {isProtected ? 'Protected' : 'Protect'}
761+
</button>
762+
<button
763+
className="px-2 py-1 bg-red-600 text-white rounded hover:bg-red-700 transition-colors flex items-center text-[11px]"
764+
onClick={() => setShowDeleteConfirm(true)}
765+
title="Delete Recording"
766+
>
767+
🗑 Delete
768+
</button>
769+
</div>
770+
)}
734771

735-
{/* Recording action buttons */}
736-
{currentSegmentId && (
737-
<div className="flex flex-wrap gap-2 mb-2">
738-
{/* Snapshot */}
739-
<button
740-
className="px-3 py-1.5 bg-secondary text-secondary-foreground rounded hover:bg-secondary/80 transition-colors flex items-center text-xs"
741-
onClick={handleSnapshot}
742-
title="Take Snapshot"
743-
>
744-
<svg xmlns="http://www.w3.org/2000/svg" className="h-3.5 w-3.5 mr-1.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
745-
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M3 9a2 2 0 012-2h.93a2 2 0 001.664-.89l.812-1.22A2 2 0 0110.07 4h3.86a2 2 0 011.664.89l.812 1.22A2 2 0 0018.07 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V9z" />
746-
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M15 13a3 3 0 11-6 0 3 3 0 016 0z" />
747-
</svg>
748-
Snapshot
749-
</button>
750-
{/* Download */}
751-
<a
752-
className="px-3 py-1.5 bg-primary text-primary-foreground rounded hover:bg-primary/90 transition-colors flex items-center text-xs"
753-
href={`/api/recordings/download/${currentSegmentId}`}
754-
download
755-
>
756-
<svg xmlns="http://www.w3.org/2000/svg" className="h-3.5 w-3.5 mr-1.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
757-
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
758-
</svg>
759-
Download
760-
</a>
761-
{/* Protect */}
762-
<button
763-
className={`px-3 py-1.5 rounded transition-colors flex items-center text-xs ${
764-
isProtected
765-
? 'bg-yellow-500 text-white hover:bg-yellow-600'
766-
: 'bg-secondary text-secondary-foreground hover:bg-secondary/80'
767-
}`}
768-
onClick={handleToggleProtection}
769-
title={isProtected ? 'Unprotect Recording' : 'Protect Recording'}
770-
>
771-
<svg xmlns="http://www.w3.org/2000/svg" className="h-3.5 w-3.5 mr-1.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
772-
{isProtected ? (
773-
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" />
774-
) : (
775-
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M8 11V7a4 4 0 118 0m-4 8v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2z" />
776-
)}
777-
</svg>
778-
{isProtected ? 'Protected' : 'Protect'}
779-
</button>
780-
{/* Delete */}
781-
<button
782-
className="px-3 py-1.5 bg-red-600 text-white rounded hover:bg-red-700 transition-colors flex items-center text-xs"
783-
onClick={() => setShowDeleteConfirm(true)}
784-
title="Delete Recording"
785-
>
786-
<svg xmlns="http://www.w3.org/2000/svg" className="h-3.5 w-3.5 mr-1.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
787-
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
788-
</svg>
789-
Delete
790-
</button>
772+
{/* Speed controls — pushed right */}
773+
<div className="ml-auto">
774+
<SpeedControls />
791775
</div>
792-
)}
776+
</div>
793777

794778
{/* Delete confirmation */}
795779
<ConfirmDialog

0 commit comments

Comments
 (0)