diff options
| author | Paul Buetow <paul@buetow.org> | 2026-05-02 22:42:35 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-05-02 22:42:35 +0300 |
| commit | daccc2fd2c43545fe2a904cccb1f866e38817a10 (patch) | |
| tree | dca78b9da806adbf61d4a259c576f93027e57ea9 /web | |
| parent | e80e81f4a47a201074f12c6bcd7cf1591a6722e3 (diff) | |
add player minimize toggle
Diffstat (limited to 'web')
| -rw-r--r-- | web/css/player.css | 38 | ||||
| -rw-r--r-- | web/index.html | 5 | ||||
| -rw-r--r-- | web/js/app.js | 3 | ||||
| -rw-r--r-- | web/js/keyboard.js | 6 | ||||
| -rw-r--r-- | web/js/player.js | 31 |
5 files changed, 78 insertions, 5 deletions
diff --git a/web/css/player.css b/web/css/player.css index 45dc948..f660134 100644 --- a/web/css/player.css +++ b/web/css/player.css @@ -46,6 +46,44 @@ height: 2rem; font-size: 0.8rem; } +.player.minimized { + inset: auto auto 1rem 1rem; + width: min(20rem, calc(100vw - 2rem)); + border: 1px solid var(--border); + border-radius: var(--radius-sm); + box-shadow: 0 8px 32px rgba(0,0,0,0.4); + overflow: hidden; +} +.player.minimized .stage, +.player.minimized .controls { + display: none; +} +.player .player-restore { + display: none; +} +.player.minimized .player-restore { + display: flex; + align-items: center; + width: 100%; + min-height: 2.25rem; + padding: 0.45rem 0.65rem; + border: 0; + background: var(--player-bg); + color: var(--text-secondary); + cursor: pointer; + font: inherit; + font-size: 0.8rem; + text-align: left; +} +.player.minimized .player-restore:hover { + color: var(--text-primary); + background: var(--bg-surface-hover); +} +.player.minimized .player-restore span { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} .player.is-fullscreen { inset: 0; background: var(--player-stage-bg); diff --git a/web/index.html b/web/index.html index 7ea78b8..c22ce7f 100644 --- a/web/index.html +++ b/web/index.html @@ -87,6 +87,7 @@ </div> <div class="controls"> <button id="btn-toggle-stage" class="icon-btn" aria-label="Toggle stage (c)">⛶</button> + <button id="btn-minimize" class="icon-btn" aria-label="Minimize player (Shift+C)">▾</button> <button id="btn-play" class="icon-btn" aria-label="Play/Pause (Space/p)">▶</button> <div class="time" id="time-elapsed">0:00</div> <div id="progress-track" class="progress-track" tabindex="0" aria-label="Seek"> @@ -103,6 +104,9 @@ </div> <button id="btn-fullscreen" class="icon-btn" aria-label="Fullscreen (f)">⛶</button> </div> + <button id="btn-restore-player" class="player-restore" type="button" aria-label="Restore player"> + <span id="player-restore-title">Player minimized</span> + </button> </aside> <!-- Detached player placeholder bar --> @@ -130,6 +134,7 @@ <tr><td><kbd>Space</kbd> <kbd>p</kbd></td><td>Play / pause</td></tr> <tr><td><kbd>f</kbd></td><td>Toggle fullscreen</td></tr> <tr><td><kbd>c</kbd></td><td>Collapse / expand player stage</td></tr> + <tr><td><kbd>C</kbd></td><td>Minimize / restore player</td></tr> <tr><td><kbd>d</kbd></td><td>Detach / attach player popup</td></tr> <tr><td><kbd>D</kbd></td><td>Download selected media</td></tr> <tr><td><kbd>Esc</kbd></td><td>Exit fullscreen, deselect item</td></tr> diff --git a/web/js/app.js b/web/js/app.js index a0e8893..b2e79e9 100644 --- a/web/js/app.js +++ b/web/js/app.js @@ -1,7 +1,7 @@ import { API } from './api.js'; import { initKeyboard } from './keyboard.js'; import { initSelection, select, selectByElement, next, prev, currentIndex, currentElement, navUp, navDown, navLeft, navRight } from './selection.js'; -import { initPlayer, togglePlay, toggleFullscreen, toggleStage, toggleDetach, exitFullscreenIfNeeded, currentMediaId, selectAndPlay, playPrevious, playNext } from './player.js'; +import { initPlayer, togglePlay, toggleFullscreen, toggleStage, toggleMinimize, toggleDetach, exitFullscreenIfNeeded, currentMediaId, selectAndPlay, playPrevious, playNext } from './player.js'; import { initSearch, focusSearch, trigger as triggerSearch } from './search.js'; import { initShuffle, toggle as toggleShuffle, isOn as isShuffle } from './shuffle.js'; import { initThemes } from './themes.js'; @@ -109,6 +109,7 @@ async function initApp() { playPause: () => togglePlay(), fullscreen: () => toggleFullscreen(), toggleStage: () => toggleStage(), + toggleMinimize: () => toggleMinimize(), escape: () => { exitFullscreenIfNeeded(); const el = currentElement(); if (el) el.classList.remove('selected'); closeAllModals(); }, shuffle: () => { toggleShuffle(); loadMedia(); }, share: () => shareSelected(), diff --git a/web/js/keyboard.js b/web/js/keyboard.js index a293d3f..db0d4db 100644 --- a/web/js/keyboard.js +++ b/web/js/keyboard.js @@ -101,10 +101,8 @@ export function initKeyboard(handlers) { break; case 'Enter': handlers.enter?.(e); break; case 'p': handlers.playPause?.(e); break; - case 'c': - case 'C': - handlers.toggleStage?.(e); - break; + case 'c': handlers.toggleStage?.(e); break; + case 'C': handlers.toggleMinimize?.(e); break; case 'f': handlers.fullscreen?.(e); break; case 'Escape': handlers.escape?.(e); break; case 'Backspace': handlers.backspace?.(e); break; diff --git a/web/js/player.js b/web/js/player.js index 253548c..46e6246 100644 --- a/web/js/player.js +++ b/web/js/player.js @@ -21,6 +21,9 @@ const els = () => ({ btnMute: document.getElementById('btn-mute'), btnFs: document.getElementById('btn-fullscreen'), btnToggleStage: document.getElementById('btn-toggle-stage'), + btnMinimize: document.getElementById('btn-minimize'), + btnRestore: document.getElementById('btn-restore-player'), + restoreTitle: document.getElementById('player-restore-title'), bigPlay: document.getElementById('big-play'), coverArt: document.getElementById('cover-art'), track: document.getElementById('progress-track'), @@ -42,6 +45,8 @@ export function initPlayer() { e.btnMute?.addEventListener('click', toggleMute); e.btnFs?.addEventListener('click', toggleFullscreen); e.btnToggleStage?.addEventListener('click', toggleStage); + e.btnMinimize?.addEventListener('click', minimizePlayer); + e.btnRestore?.addEventListener('click', toggleMinimize); e.bigPlay?.addEventListener('click', togglePlay); e.bigPlay?.addEventListener('keydown', (ev) => { if (ev.key === 'Enter' || ev.key === ' ') { ev.preventDefault(); togglePlay(); } }); @@ -251,6 +256,7 @@ export function loadMediaDirect(media, streamUrl, thumbnailUrl, resumeFrom = 0) e.buffered && (e.buffered.style.background = 'transparent'); e.fill.style.width = '0%'; e.thumb.style.left = '0%'; + updateMinimizedTitle(); updateCollapsedSize(); } @@ -292,6 +298,7 @@ function loadMedia(media, resumeFrom = 0) { e.buffered && (e.buffered.style.background = 'transparent'); e.fill.style.width = '0%'; e.thumb.style.left = '0%'; + updateMinimizedTitle(); updateCollapsedSize(); } @@ -349,10 +356,34 @@ export function toggleFullscreen() { export function toggleStage() { const e = els(); + e.player?.classList.remove('minimized'); e.player?.classList.toggle('collapsed'); console.log('toggleStage: collapsed =', e.player?.classList.contains('collapsed')); } +export function toggleMinimize() { + const e = els(); + if (!e.player || !e.player.classList.contains('open')) return; + const willMinimize = !e.player.classList.contains('minimized'); + e.player.classList.toggle('minimized'); + if (willMinimize) exitFullscreenIfNeeded(); + updateMinimizedTitle(); +} + +function minimizePlayer() { + const e = els(); + if (!e.player || !e.player.classList.contains('open')) return; + e.player.classList.add('minimized'); + exitFullscreenIfNeeded(); + updateMinimizedTitle(); +} + +function updateMinimizedTitle() { + const e = els(); + if (!e.restoreTitle) return; + e.restoreTitle.textContent = currentMedia?.file_name ? `Restore: ${currentMedia.file_name}` : 'Restore player'; +} + export function exitFullscreenIfNeeded() { if (document.fullscreenElement) { document.exitFullscreen().catch(() => {}); |
