Fix player UI: sports Highlights pane, scrubber knob, volume knob, description + save-to-playlist

Player/UI fixes:
- Sports (match) Highlights pane: move the toggle click-handler script out of
  the always-false @if(false ...) legacy block so the "Highlights" button opens,
  and emit videoId/isOwner with @json instead of {{ json_encode() }} (escaped
  quotes crashed the data/tab/CRUD block once route keys became hashid strings).
- Progress scrubber knob (video + audio players): center on the track line with
  translate(-50%, -50%); drop the margin-top hack that floated it above the line.
- Music player volume: give the slider wrap height so overflow:hidden no longer
  crops the knob; add a subtle thumb shadow for visibility.
- Music language menu: cap height to available space above the header, sticky
  header, internal scroll; keep controls from auto-hiding while the menu is open.
- Description box: re-check "Show more" overflow at every settle point (fonts,
  images, rAF, tab reopen) so SPA autonext keeps the expand button.

Also bundles pending work: save-to-playlist button component, playlist/channel
views, PlaylistController + NasSyncService, and component-usage tracker updates.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
ghassanandClaude Opus 4.8 committed 2026-07-28 22:12:58 +03:00
1 parent 80948efff7
commit 6b43a7060a
14 files changed
+236 -136

No files matched your search

@@ -0,0 +1,30 @@
@props([
'video',
'tag' => 'button', // 'button' or 'a'
])
{{-- Canonical "Save to playlist" trigger. Opens the single
<x-add-to-playlist-modal/> (included once in layouts/app.blade.php) via
openAddToPlaylistModal(numericId). Always passes $video->id, never the
route key — the backend validates against videos.id. --}}
@php
$onclick = "openAddToPlaylistModal({$video->id})";
@endphp
@if($tag === 'a')
<a href="javascript:void(0)" {{ $attributes }} onclick="{{ $onclick }}">
@if($slot->isEmpty())
<i class="bi bi-bookmark"></i> Save to playlist
@else
{{ $slot }}
@endif
</a>
@else
<button type="button" {{ $attributes }} onclick="{{ $onclick }}">
@if($slot->isEmpty())
<i class="bi bi-bookmark"></i> <span>Save to playlist</span>
@else
{{ $slot }}
@endif
</button>
@endif
@@ -177,10 +177,10 @@
@endif
<!-- Save to Playlist Button -->
<button class="action-btn desktop-action" onclick="openAddToPlaylistModal({{ $video->id }})">
<x-save-to-playlist-button :video="$video" class="action-btn desktop-action">
<i class="bi bi-bookmark"></i>
<span>Save</span>
</button>
</x-save-to-playlist-button>
@php
$dlAccess = $video->download_access ?? 'disabled';
@@ -280,9 +280,9 @@
</button>
@endif
<button class="dropdown-item" onclick="openAddToPlaylistModal({{ $video->id }})">
<i class="bi bi-bookmark"></i> Save
</button>
<x-save-to-playlist-button :video="$video" class="dropdown-item">
<i class="bi bi-bookmark"></i> <span>Save</span>
</x-save-to-playlist-button>
@if($showDl)
@if($isAudioDl)
<a class="dropdown-item" href="#"
@@ -128,9 +128,9 @@ $sizeClasses = match($size) {
</a>
</li>
<li>
<a class="dropdown-item" href="javascript:void(0)" onclick="openPlaylistModal('{{ $video->getRouteKey() }}')">
<x-save-to-playlist-button :video="$video" tag="a" class="dropdown-item">
<i class="bi bi-bookmark"></i> Save to playlist
</a>
</x-save-to-playlist-button>
</li>
@if($video->allow_download)
<li>
@@ -957,39 +957,6 @@ function saveToWatchLater(videoId) {
.catch(error => console.error('Error:', error));
}
// Global function to open playlist modal
function openPlaylistModal(videoId) {
// Set the current video ID for the modal as global variable
window.currentVideoIdForModal = videoId;
// Close any open dropdown menus first
const activeDropdowns = document.querySelectorAll('.dropdown-menu.show');
activeDropdowns.forEach(function(dropdown) {
dropdown.classList.remove('show');
});
// Also close Bootstrap dropdowns by clicking the toggle
const dropdownToggles = document.querySelectorAll('.dropdown-toggle[aria-expanded="true"]');
dropdownToggles.forEach(function(toggle) {
toggle.click();
});
// Try to open the add to playlist modal
if (typeof openAddToPlaylistModal === 'function') {
openAddToPlaylistModal(videoId);
} else {
// Modal might not be loaded, try to find and show it directly
const modal = document.getElementById('addToPlaylistModal');
if (modal) {
modal.style.display = 'flex';
modal.style.opacity = '1';
} else {
// Fallback - redirect to login
window.location.href = '{{ route("login") }}?redirect=' + encodeURIComponent(window.location.href);
}
}
}
// Global function to add to queue
function addToQueue(videoId) {
showToast('Queue feature coming soon!', 'info');
@@ -397,7 +397,6 @@
.ytp-scrubber-container {
position: absolute;
top: 50%;
transform: translateY(-50%);
width: 0;
pointer-events: none;
}
@@ -406,12 +405,13 @@
height: 13px;
border-radius: 50%;
background: #f00;
transform: translate(-50%, 0) scale(0);
/* Center the knob on the track: -50% X sits it on the play head, -50% Y keeps it
vertically centered on the bar's mid-line (container top:50%) at any bar height. */
transform: translate(-50%, -50%) scale(0);
transition: transform .1s;
margin-top: -4px;
}
.ytp-progress-bar-container:hover .ytp-scrubber-button,
.ytp-progress-bar.dragging .ytp-scrubber-button { transform: translate(-50%, 0) scale(1); }
.ytp-progress-bar.dragging .ytp-scrubber-button { transform: translate(-50%, -50%) scale(1); }
.ytp-hover-time {
position: absolute;