diff options
| -rw-r--r-- | web/css/player.css | 11 | ||||
| -rw-r--r-- | web/detach.html | 1 | ||||
| -rw-r--r-- | web/index.html | 2 | ||||
| -rw-r--r-- | web/js/app.js | 3 | ||||
| -rw-r--r-- | web/js/keyboard.js | 1 | ||||
| -rw-r--r-- | web/js/player.js | 27 |
6 files changed, 43 insertions, 2 deletions
diff --git a/web/css/player.css b/web/css/player.css index 0f468f9..7a8b561 100644 --- a/web/css/player.css +++ b/web/css/player.css @@ -128,6 +128,17 @@ max-height: 100%; } +.player.crop-mode video { + object-fit: fill; +} + +.crop-indicator { + font-size: 0.75rem; + color: var(--text-secondary); + padding: 0 0.25rem; + user-select: none; +} + /* Cover art for audio playback */ .player .cover-art { width: 100%; diff --git a/web/detach.html b/web/detach.html index b83ef68..8d6af63 100644 --- a/web/detach.html +++ b/web/detach.html @@ -37,6 +37,7 @@ <input id="volume-slider" type="range" min="0" max="1" step="0.05" value="1" class="volume-slider" aria-label="Volume"> </div> <button id="btn-fullscreen" class="icon-btn" aria-label="Fullscreen">⛺</button> + <span id="crop-indicator" class="crop-indicator hidden" title="Crop mode active (c)">✂️</span> </div> </aside> </body> diff --git a/web/index.html b/web/index.html index f3e37a1..4b90f92 100644 --- a/web/index.html +++ b/web/index.html @@ -90,6 +90,7 @@ <input id="volume-slider" type="range" min="0" max="1" step="0.05" value="1" class="volume-slider" aria-label="Volume"> </div> <button id="btn-fullscreen" class="icon-btn" aria-label="Fullscreen (f)">⛶</button> + <span id="crop-indicator" class="crop-indicator hidden" title="Crop mode active (c)">✂️</span> </div> <button id="btn-restore-player" class="player-restore" type="button" aria-label="Restore player"> <span id="player-restore-title">Player minimized</span> @@ -133,6 +134,7 @@ <div class="help-row"><span><kbd>S</kbd></span><span>Share selected media</span></div> <div class="help-row"><span><kbd>+</kbd> <kbd>−</kbd></span><span>Zoom in / out (image viewer)</span></div> <div class="help-row"><span><kbd>Shift+S</kbd></span><span>Toggle slideshow (images)</span></div> + <div class="help-row"><span><kbd>c</kbd></span><span>Toggle crop mode (fullscreen)</span></div> <div class="help-row"><span><kbd>L</kbd></span><span>My Shares</span></div> <div class="help-row"><span><kbd>?</kbd></span><span>Show / hide help</span></div> </div> diff --git a/web/js/app.js b/web/js/app.js index 9c769c9..13cc824 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, clearSelection, select, selectByElement, next, prev, currentIndex, currentElement, navUp, navDown, navLeft, navRight } from './selection.js'; -import { initPlayer, togglePlay, toggleFullscreen, toggleMinimize, toggleDetach, exitFullscreenIfNeeded, currentMediaId, currentMediaInfo, hasLoadedMedia, isPlaybackActive, seekRelative, selectAndPlay, zoomIn as playerZoomIn, zoomOut as playerZoomOut, toggleSlideshow as playerToggleSlideshow, isImageMode as playerIsImageMode } from './player.js'; +import { initPlayer, togglePlay, toggleFullscreen, toggleMinimize, toggleDetach, exitFullscreenIfNeeded, toggleCrop, currentMediaId, currentMediaInfo, hasLoadedMedia, isPlaybackActive, seekRelative, selectAndPlay, zoomIn as playerZoomIn, zoomOut as playerZoomOut, toggleSlideshow as playerToggleSlideshow, isImageMode as playerIsImageMode } from './player.js'; import { initSearch, focusSearch, trigger as triggerSearch, parseQuery } from './search.js'; import { initShuffle, toggle as toggleShuffle, isOn as isShuffle } from './shuffle.js'; import { initThemes } from './themes.js'; @@ -134,6 +134,7 @@ async function initApp() { mediaInfo: () => toggleMediaInfo(), fullscreen: () => toggleFullscreen(), toggleMinimize: () => toggleMinimize(), + toggleCrop: () => toggleCrop(), escape: () => { exitFullscreenIfNeeded(); closeLightbox(); 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 951e573..dc3eb4e 100644 --- a/web/js/keyboard.js +++ b/web/js/keyboard.js @@ -214,6 +214,7 @@ export function initKeyboard(handlers) { break; case 'C': handlers.toggleMinimize?.(e); break; case 'f': handlers.fullscreen?.(e); break; + case 'c': handlers.toggleCrop?.(e); break; case 'Escape': handlers.escape?.(e); break; case 'Backspace': handlers.backspace?.(e); break; case 'r': handlers.shuffle?.(e); break; diff --git a/web/js/player.js b/web/js/player.js index 6da8954..fea4a19 100644 --- a/web/js/player.js +++ b/web/js/player.js @@ -21,6 +21,7 @@ let isPanning = false; let panStart = { x: 0, y: 0 }; let slideshowTimer = null; let slideshowPausedUntil = 0; +let cropMode = false; const els = () => ({ video: document.getElementById('media-video'), @@ -47,6 +48,7 @@ const els = () => ({ volume: document.getElementById('volume-slider'), timeElapsed: document.getElementById('time-elapsed'), timeTotal: document.getElementById('time-total'), + cropIndicator: document.getElementById('crop-indicator'), }); export function initPlayer(options = {}) { @@ -134,6 +136,16 @@ export function initPlayer(options = {}) { e.video.addEventListener('loadedmetadata', updateFloatingSize); + document.addEventListener('fullscreenchange', () => { + const p = e.player; + if (!p) return; + if (!document.fullscreenElement) { + p.classList.remove('crop-mode'); + cropMode = false; + if (e.cropIndicator) e.cropIndicator.classList.add('hidden'); + } + }); + function setupVideoDebug(m) { const events = ['loadstart','loadeddata','loadedmetadata','canplay','canplaythrough','playing','waiting','stalled','suspend','error','abort','emptied','ended']; events.forEach(event => { @@ -521,10 +533,23 @@ function updateMinimizedTitle() { export function exitFullscreenIfNeeded() { if (document.fullscreenElement) { document.exitFullscreen().catch(() => {}); - els().player?.classList.remove('is-fullscreen'); + const p = els().player; + if (p) p.classList.remove('is-fullscreen', 'crop-mode'); + cropMode = false; + const ind = els().cropIndicator; + if (ind) ind.classList.add('hidden'); } } +export function toggleCrop() { + const e = els(); + if (!e.player) return; + if (!document.fullscreenElement) return; + cropMode = !cropMode; + e.player.classList.toggle('crop-mode', cropMode); + if (e.cropIndicator) e.cropIndicator.classList.toggle('hidden', !cropMode); +} + function startProgressTimer() { stopProgressTimer(); if (!reportProgress) return; |
