PLAYER: reworked player panel and fulscreen
Build and Publish / Build and Publish Docker Image (push) Successful in 3m1s
Build and Publish / Build and Publish Docker Image (push) Successful in 3m1s
This commit is contained in:
Generated
+1
-1
@@ -1418,7 +1418,7 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
|
||||
|
||||
[[package]]
|
||||
name = "furumusic"
|
||||
version = "0.2.7"
|
||||
version = "0.2.8"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
|
||||
@@ -170,10 +170,14 @@ document.addEventListener('alpine:init', () => {
|
||||
playerDragOffset: 0,
|
||||
playerCloseOffset: 0,
|
||||
_playerDragStartY: 0,
|
||||
_playerDragStartX: 0,
|
||||
_playerDragTracking: false,
|
||||
_playerDragMode: null,
|
||||
_playerDragPointerId: null,
|
||||
_playerDragElement: null,
|
||||
_playerDragMove: null,
|
||||
_playerDragEnd: null,
|
||||
_playerSuppressClickUntil: 0,
|
||||
toggleLibrary() {
|
||||
this.libraryOpen = !this.libraryOpen;
|
||||
if (this.libraryOpen) Alpine.store('user').menuOpen = false;
|
||||
@@ -202,31 +206,37 @@ document.addEventListener('alpine:init', () => {
|
||||
playerDragStyle() {
|
||||
return `--mobile-player-drag:${this.playerDragOffset}px; --mobile-player-close-drag:${this.playerCloseOffset}px;`;
|
||||
},
|
||||
handlePlayerClick(event) {
|
||||
if (Date.now() <= this._playerSuppressClickUntil) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this._playerSuppressClickUntil = 0;
|
||||
}
|
||||
},
|
||||
startPlayerDrag(event, force = false) {
|
||||
if (!this.isMobilePlayer() || !Alpine.store('player').currentTrack) return;
|
||||
if (event.button && event.button !== 0) return;
|
||||
const target = event.target;
|
||||
const isInteractive = target.closest('button, input, select, textarea, a, .volume-slider, .progress-bar, .device-popover, .mobile-expanded-queue');
|
||||
const isDragBlocked = target.closest('input, select, textarea, .volume-slider, .device-popover, .mobile-expanded-queue');
|
||||
if (!force) {
|
||||
if (this.playerExpanded) {
|
||||
const isCloseHandle = target.closest('.player-now-playing');
|
||||
const scroller = event.currentTarget?.classList?.contains('player-bar') ? event.currentTarget : null;
|
||||
if (!isCloseHandle || isInteractive || (scroller && scroller.scrollTop > 4)) return;
|
||||
} else if (isInteractive) {
|
||||
if (!isCloseHandle || isDragBlocked || (scroller && scroller.scrollTop > 4)) return;
|
||||
} else if (isDragBlocked) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
event.preventDefault();
|
||||
if (this.playerDragging) this.endPlayerDrag({ type: 'pointercancel' });
|
||||
this.playerDragging = true;
|
||||
if (this._playerDragTracking) this.endPlayerDrag({ type: 'pointercancel' });
|
||||
this.playerDragging = false;
|
||||
this._playerDragTracking = true;
|
||||
this._playerDragMode = this.playerExpanded ? 'close' : 'open';
|
||||
this._playerDragStartY = event.clientY;
|
||||
this._playerDragStartX = event.clientX;
|
||||
this._playerDragPointerId = event.pointerId;
|
||||
this._playerDragElement = event.currentTarget;
|
||||
this.playerDragOffset = 0;
|
||||
this.playerCloseOffset = 0;
|
||||
try {
|
||||
this._playerDragElement?.setPointerCapture?.(event.pointerId);
|
||||
} catch (_) {}
|
||||
this._playerDragMove = e => this.movePlayerDrag(e);
|
||||
this._playerDragEnd = e => this.endPlayerDrag(e);
|
||||
window.addEventListener('pointermove', this._playerDragMove, { passive: false });
|
||||
@@ -234,11 +244,22 @@ document.addEventListener('alpine:init', () => {
|
||||
window.addEventListener('pointercancel', this._playerDragEnd, { passive: false });
|
||||
},
|
||||
movePlayerDrag(event) {
|
||||
if (!this.playerDragging) return;
|
||||
if (!this._playerDragTracking) return;
|
||||
const delta = this._playerDragStartY - event.clientY;
|
||||
if (Math.abs(delta) > 6) event.preventDefault();
|
||||
if (this.playerExpanded) {
|
||||
this.playerCloseOffset = Math.max(0, Math.min(180, -delta));
|
||||
const absDelta = Math.abs(delta);
|
||||
if (!this.playerDragging) {
|
||||
const horizontalDelta = Math.abs(event.clientX - this._playerDragStartX);
|
||||
const wantsOpen = this._playerDragMode === 'open' && delta > 0;
|
||||
const wantsClose = this._playerDragMode === 'close' && delta < 0;
|
||||
if (absDelta < 8 || absDelta < horizontalDelta * 1.15 || (!wantsOpen && !wantsClose)) return;
|
||||
this.playerDragging = true;
|
||||
try {
|
||||
this._playerDragElement?.setPointerCapture?.(this._playerDragPointerId);
|
||||
} catch (_) {}
|
||||
}
|
||||
event.preventDefault();
|
||||
if (this._playerDragMode === 'close') {
|
||||
this.playerCloseOffset = Math.max(0, Math.min(window.innerHeight, -delta));
|
||||
} else {
|
||||
const max = Math.max(0, window.innerHeight - 132);
|
||||
this.playerDragOffset = Math.max(0, Math.min(max, delta));
|
||||
@@ -246,15 +267,17 @@ document.addEventListener('alpine:init', () => {
|
||||
},
|
||||
endPlayerDrag(event) {
|
||||
const openThreshold = Math.min(180, Math.max(90, window.innerHeight * 0.18));
|
||||
const closeThreshold = 110;
|
||||
const closeThreshold = Math.min(110, Math.max(64, window.innerHeight * 0.1));
|
||||
const wasCancelled = event?.type === 'pointercancel';
|
||||
if (this.playerExpanded) {
|
||||
const wasDragging = this.playerDragging;
|
||||
if (wasDragging) this._playerSuppressClickUntil = Date.now() + 450;
|
||||
if (this._playerDragMode === 'close') {
|
||||
if (!wasCancelled && this.playerCloseOffset > closeThreshold) this.closePlayerFullscreen();
|
||||
else {
|
||||
this.playerCloseOffset = 0;
|
||||
this.playerDragging = false;
|
||||
}
|
||||
} else if (!wasCancelled && this.playerDragOffset > openThreshold) {
|
||||
} else if (this._playerDragMode === 'open' && !wasCancelled && this.playerDragOffset > openThreshold) {
|
||||
this.openPlayerFullscreen();
|
||||
} else {
|
||||
this.playerDragOffset = 0;
|
||||
@@ -270,6 +293,8 @@ document.addEventListener('alpine:init', () => {
|
||||
window.removeEventListener('pointerup', this._playerDragEnd);
|
||||
window.removeEventListener('pointercancel', this._playerDragEnd);
|
||||
}
|
||||
this._playerDragTracking = false;
|
||||
this._playerDragMode = null;
|
||||
this._playerDragPointerId = null;
|
||||
this._playerDragElement = null;
|
||||
this._playerDragMove = null;
|
||||
|
||||
@@ -946,6 +946,7 @@
|
||||
<div class="player-bar"
|
||||
:class="{ 'mobile-expanded': $store.mobile.playerExpanded, 'mobile-dragging': $store.mobile.playerDragging }"
|
||||
:style="$store.mobile.playerDragStyle()"
|
||||
@click.capture="$store.mobile.handlePlayerClick($event)"
|
||||
@pointerdown="$store.mobile.startPlayerDrag($event)">
|
||||
<button class="mobile-player-collapse-btn" type="button" @click.stop="$store.mobile.closePlayerFullscreen()" title="{{ t.player_close }}" aria-label="{{ t.player_close }}">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4">
|
||||
@@ -956,8 +957,7 @@
|
||||
<template x-if="$store.player.currentTrack">
|
||||
<div style="display:flex;align-items:center;gap:12px;overflow:hidden">
|
||||
<div class="player-cover"
|
||||
@click.stop="$store.mobile.openPlayerFullscreen()"
|
||||
@pointerdown.stop="$store.mobile.startPlayerDrag($event, true)">
|
||||
@click.stop="$store.mobile.openPlayerFullscreen()">
|
||||
<template x-if="$store.player.currentTrack.cover_url">
|
||||
<img :src="$store.player.currentTrack.cover_url" :alt="$store.player.currentTrack.title">
|
||||
</template>
|
||||
@@ -1030,6 +1030,8 @@
|
||||
<div class="progress-bar-thumb"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="player-progress-strip-times"
|
||||
x-text="'-' + formatTime(Math.max(0, $store.player.duration - $store.player.currentTime)) + ' / ' + formatTime($store.player.duration)"></div>
|
||||
<span class="player-time" x-text="formatTime($store.player.duration)"></span>
|
||||
</div>
|
||||
<div class="player-version-chip">v{{ t.app_version() }}</div>
|
||||
|
||||
@@ -1261,6 +1261,10 @@ button.user-stat:hover {
|
||||
|
||||
.player-time { font-size: 11px; color: var(--text-subdued); min-width: 40px; text-align: center; }
|
||||
|
||||
.player-progress-strip-times {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
flex: 1;
|
||||
height: 4px;
|
||||
@@ -3642,14 +3646,13 @@ button.user-stat:hover {
|
||||
.player-bar {
|
||||
position: relative;
|
||||
grid-template-columns: auto minmax(0, 1fr);
|
||||
grid-template-rows: 62px 58px 24px;
|
||||
grid-template-rows: 62px 58px;
|
||||
grid-template-areas:
|
||||
"now now"
|
||||
"buttons actions"
|
||||
"timeline timeline";
|
||||
"buttons actions";
|
||||
gap: 4px 10px;
|
||||
align-items: center;
|
||||
padding: 7px 12px calc(9px + var(--safe-bottom));
|
||||
padding: 34px 12px calc(9px + var(--safe-bottom));
|
||||
touch-action: none;
|
||||
user-select: none;
|
||||
}
|
||||
@@ -3742,13 +3745,61 @@ button.user-stat:hover {
|
||||
}
|
||||
|
||||
.player-timeline {
|
||||
grid-area: timeline;
|
||||
max-width: none;
|
||||
gap: 5px;
|
||||
align-self: center;
|
||||
padding-right: 58px;
|
||||
}
|
||||
|
||||
.player-bar:not(.mobile-expanded) .player-timeline {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 21px;
|
||||
gap: 0;
|
||||
padding-right: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.player-bar:not(.mobile-expanded) .player-time {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.player-bar:not(.mobile-expanded) .progress-bar,
|
||||
.player-bar:not(.mobile-expanded) .progress-bar:hover {
|
||||
width: 100%;
|
||||
height: 21px;
|
||||
border-radius: 0;
|
||||
background: rgba(29, 185, 84, 0.18);
|
||||
}
|
||||
|
||||
.player-bar:not(.mobile-expanded) .progress-bar-fill {
|
||||
border-radius: 0;
|
||||
background: var(--accent);
|
||||
}
|
||||
|
||||
.player-bar:not(.mobile-expanded) .progress-bar-thumb {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.player-bar:not(.mobile-expanded) .player-progress-strip-times {
|
||||
position: absolute;
|
||||
top: 22px;
|
||||
right: 10px;
|
||||
height: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0;
|
||||
color: var(--text-subdued);
|
||||
font-size: 9px;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
pointer-events: none;
|
||||
text-shadow: none;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.player-version-chip {
|
||||
display: block;
|
||||
position: absolute;
|
||||
@@ -4603,7 +4654,7 @@ button.user-stat:hover {
|
||||
gap: 8px;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
grid-template-rows: 60px 58px 24px;
|
||||
grid-template-rows: 60px 58px;
|
||||
}
|
||||
|
||||
.player-track-title { font-size: 12px; }
|
||||
|
||||
Reference in New Issue
Block a user