IMP: fullscreen lyrics, various css fixes
This commit is contained in:
parent
b828322a72
commit
801fa264cd
4 changed files with 63 additions and 236 deletions
|
|
@ -65,8 +65,6 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="fullscreen-lyrics-container" id="fullscreen-lyrics-container" style="display: none;">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
188
js/lyrics.js
188
js/lyrics.js
|
|
@ -193,125 +193,16 @@ export async function openLyricsPanel(track, audioPlayer, lyricsManager) {
|
|||
};
|
||||
|
||||
const renderContent = async (container) => {
|
||||
container.innerHTML = '<div class="lyrics-loading">Loading lyrics...</div>';
|
||||
// Clean up any previous sync (though sidePanelManager might handle cleanup, we ensure it here)
|
||||
clearLyricsPanelSync(audioPlayer, sidePanelManager.panel);
|
||||
|
||||
try {
|
||||
await manager.ensureComponentLoaded();
|
||||
|
||||
const title = track.title;
|
||||
const artist = getTrackArtists(track);
|
||||
const album = track.album?.title;
|
||||
const durationMs = track.duration ? Math.round(track.duration * 1000) : undefined;
|
||||
const isrc = track.isrc || '';
|
||||
|
||||
container.innerHTML = '';
|
||||
const amLyrics = document.createElement('am-lyrics');
|
||||
amLyrics.setAttribute('song-title', title);
|
||||
amLyrics.setAttribute('song-artist', artist);
|
||||
if (album) amLyrics.setAttribute('song-album', album);
|
||||
if (durationMs) amLyrics.setAttribute('song-duration', durationMs);
|
||||
amLyrics.setAttribute('query', `${title} ${artist}`.trim());
|
||||
if (isrc) amLyrics.setAttribute('isrc', isrc);
|
||||
|
||||
amLyrics.setAttribute('highlight-color', '#93c5fd');
|
||||
amLyrics.setAttribute('hover-background-color', 'rgba(59, 130, 246, 0.14)');
|
||||
amLyrics.setAttribute('autoscroll', '');
|
||||
amLyrics.setAttribute('interpolate', '');
|
||||
amLyrics.style.height = '100%';
|
||||
amLyrics.style.width = '100%';
|
||||
|
||||
container.appendChild(amLyrics);
|
||||
manager.amLyricsElement = amLyrics;
|
||||
|
||||
// Clean up any previous sync
|
||||
clearLyricsPanelSync(audioPlayer, sidePanelManager.panel);
|
||||
setupLyricsSync(track, audioPlayer, sidePanelManager.panel, manager, amLyrics);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Failed to load lyrics:', error);
|
||||
container.innerHTML = '<div class="lyrics-error">Failed to load lyrics! :(</div>';
|
||||
}
|
||||
await renderLyricsComponent(container, track, audioPlayer, manager);
|
||||
};
|
||||
|
||||
sidePanelManager.open('lyrics', 'Lyrics', renderControls, renderContent);
|
||||
}
|
||||
|
||||
function setupLyricsSync(track, audioPlayer, panel, lyricsManager, amLyrics) {
|
||||
let baseTimeMs = 0;
|
||||
let lastTimestamp = performance.now();
|
||||
|
||||
const updateTime = () => {
|
||||
const currentMs = audioPlayer.currentTime * 1000;
|
||||
baseTimeMs = currentMs;
|
||||
lastTimestamp = performance.now();
|
||||
amLyrics.currentTime = currentMs;
|
||||
};
|
||||
|
||||
const tick = () => {
|
||||
if (!audioPlayer.paused) {
|
||||
const now = performance.now();
|
||||
const elapsed = now - lastTimestamp;
|
||||
const nextMs = baseTimeMs + elapsed;
|
||||
amLyrics.currentTime = nextMs;
|
||||
lyricsManager.animationFrameId = requestAnimationFrame(tick);
|
||||
}
|
||||
};
|
||||
|
||||
const onPlay = () => {
|
||||
baseTimeMs = audioPlayer.currentTime * 1000;
|
||||
lastTimestamp = performance.now();
|
||||
tick();
|
||||
};
|
||||
|
||||
const onPause = () => {
|
||||
if (lyricsManager.animationFrameId) {
|
||||
cancelAnimationFrame(lyricsManager.animationFrameId);
|
||||
}
|
||||
};
|
||||
|
||||
audioPlayer.addEventListener('timeupdate', updateTime);
|
||||
audioPlayer.addEventListener('play', onPlay);
|
||||
audioPlayer.addEventListener('pause', onPause);
|
||||
audioPlayer.addEventListener('seeked', updateTime);
|
||||
|
||||
// Store handlers for removal
|
||||
panel.lyricsUpdateHandler = updateTime;
|
||||
panel.lyricsPlayHandler = onPlay;
|
||||
panel.lyricsPauseHandler = onPause;
|
||||
panel.lyricsSeekHandler = updateTime;
|
||||
|
||||
amLyrics.addEventListener('line-click', (e) => {
|
||||
if (e.detail && e.detail.timestamp) {
|
||||
audioPlayer.currentTime = e.detail.timestamp / 1000;
|
||||
audioPlayer.play();
|
||||
}
|
||||
});
|
||||
|
||||
if (!audioPlayer.paused) {
|
||||
tick();
|
||||
}
|
||||
|
||||
panel.lyricsCleanup = () => {
|
||||
if (lyricsManager.animationFrameId) {
|
||||
cancelAnimationFrame(lyricsManager.animationFrameId);
|
||||
}
|
||||
audioPlayer.removeEventListener('timeupdate', updateTime);
|
||||
audioPlayer.removeEventListener('play', onPlay);
|
||||
audioPlayer.removeEventListener('pause', onPause);
|
||||
audioPlayer.removeEventListener('seeked', updateTime);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export function clearLyricsPanelSync(audioPlayer, panel) {
|
||||
if (panel && panel.lyricsCleanup) {
|
||||
panel.lyricsCleanup();
|
||||
panel.lyricsCleanup = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export async function renderLyricsInFullscreen(track, audioPlayer, lyricsManager, container) {
|
||||
async function renderLyricsComponent(container, track, audioPlayer, lyricsManager) {
|
||||
container.innerHTML = '<div class="lyrics-loading">Loading lyrics...</div>';
|
||||
|
||||
try {
|
||||
|
|
@ -340,21 +231,25 @@ export async function renderLyricsInFullscreen(track, audioPlayer, lyricsManager
|
|||
amLyrics.style.width = '100%';
|
||||
|
||||
container.appendChild(amLyrics);
|
||||
|
||||
setupFullscreenLyricsSync(track, audioPlayer, container, lyricsManager, amLyrics);
|
||||
|
||||
|
||||
const cleanup = setupSync(track, audioPlayer, amLyrics);
|
||||
|
||||
// Attach cleanup to container for easy access
|
||||
container.lyricsCleanup = cleanup;
|
||||
|
||||
return amLyrics;
|
||||
} catch (error) {
|
||||
console.error('Failed to load lyrics in fullscreen:', error);
|
||||
console.error('Failed to load lyrics:', error);
|
||||
container.innerHTML = '<div class="lyrics-error">Failed to load lyrics</div>';
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function setupFullscreenLyricsSync(track, audioPlayer, container, lyricsManager, amLyrics) {
|
||||
function setupSync(track, audioPlayer, amLyrics) {
|
||||
let baseTimeMs = 0;
|
||||
let lastTimestamp = performance.now();
|
||||
|
||||
let animationFrameId = null;
|
||||
|
||||
const updateTime = () => {
|
||||
const currentMs = audioPlayer.currentTime * 1000;
|
||||
baseTimeMs = currentMs;
|
||||
|
|
@ -368,7 +263,7 @@ function setupFullscreenLyricsSync(track, audioPlayer, container, lyricsManager,
|
|||
const elapsed = now - lastTimestamp;
|
||||
const nextMs = baseTimeMs + elapsed;
|
||||
amLyrics.currentTime = nextMs;
|
||||
lyricsManager.fullscreenAnimationFrameId = requestAnimationFrame(tick);
|
||||
animationFrameId = requestAnimationFrame(tick);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -379,9 +274,16 @@ function setupFullscreenLyricsSync(track, audioPlayer, container, lyricsManager,
|
|||
};
|
||||
|
||||
const onPause = () => {
|
||||
if (lyricsManager.fullscreenAnimationFrameId) {
|
||||
cancelAnimationFrame(lyricsManager.fullscreenAnimationFrameId);
|
||||
lyricsManager.fullscreenAnimationFrameId = null;
|
||||
if (animationFrameId) {
|
||||
cancelAnimationFrame(animationFrameId);
|
||||
animationFrameId = null;
|
||||
}
|
||||
};
|
||||
|
||||
const onLineClick = (e) => {
|
||||
if (e.detail && e.detail.timestamp) {
|
||||
audioPlayer.currentTime = e.detail.timestamp / 1000;
|
||||
audioPlayer.play();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -389,33 +291,26 @@ function setupFullscreenLyricsSync(track, audioPlayer, container, lyricsManager,
|
|||
audioPlayer.addEventListener('play', onPlay);
|
||||
audioPlayer.addEventListener('pause', onPause);
|
||||
audioPlayer.addEventListener('seeked', updateTime);
|
||||
|
||||
container.lyricsUpdateHandler = updateTime;
|
||||
container.lyricsPlayHandler = onPlay;
|
||||
container.lyricsPauseHandler = onPause;
|
||||
container.lyricsSeekHandler = updateTime;
|
||||
|
||||
container.lyricsCleanup = () => {
|
||||
if (lyricsManager.fullscreenAnimationFrameId) {
|
||||
cancelAnimationFrame(lyricsManager.fullscreenAnimationFrameId);
|
||||
lyricsManager.fullscreenAnimationFrameId = null;
|
||||
amLyrics.addEventListener('line-click', onLineClick);
|
||||
|
||||
if (!audioPlayer.paused) {
|
||||
tick();
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (animationFrameId) {
|
||||
cancelAnimationFrame(animationFrameId);
|
||||
}
|
||||
audioPlayer.removeEventListener('timeupdate', updateTime);
|
||||
audioPlayer.removeEventListener('play', onPlay);
|
||||
audioPlayer.removeEventListener('pause', onPause);
|
||||
audioPlayer.removeEventListener('seeked', updateTime);
|
||||
amLyrics.removeEventListener('line-click', onLineClick);
|
||||
};
|
||||
}
|
||||
|
||||
amLyrics.addEventListener('line-click', (e) => {
|
||||
if (e.detail && e.detail.timestamp) {
|
||||
audioPlayer.currentTime = e.detail.timestamp / 1000;
|
||||
audioPlayer.play();
|
||||
}
|
||||
});
|
||||
|
||||
if (!audioPlayer.paused) {
|
||||
tick();
|
||||
}
|
||||
export async function renderLyricsInFullscreen(track, audioPlayer, lyricsManager, container) {
|
||||
return renderLyricsComponent(container, track, audioPlayer, lyricsManager);
|
||||
}
|
||||
|
||||
export function clearFullscreenLyricsSync(container) {
|
||||
|
|
@ -424,3 +319,10 @@ export function clearFullscreenLyricsSync(container) {
|
|||
container.lyricsCleanup = null;
|
||||
}
|
||||
}
|
||||
|
||||
export function clearLyricsPanelSync(audioPlayer, panel) {
|
||||
if (panel && panel.lyricsCleanup) {
|
||||
panel.lyricsCleanup();
|
||||
panel.lyricsCleanup = null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
32
js/ui.js
32
js/ui.js
|
|
@ -1,6 +1,6 @@
|
|||
//js/ui.js
|
||||
import { SVG_PLAY, SVG_DOWNLOAD, SVG_MENU, SVG_HEART, formatTime, createPlaceholder, trackDataStore, hasExplicitContent, getTrackArtists, getTrackTitle, calculateTotalDuration, formatDuration } from './utils.js';
|
||||
import { renderLyricsInFullscreen, clearFullscreenLyricsSync } from './lyrics.js';
|
||||
import { openLyricsPanel } from './lyrics.js';
|
||||
import { recentActivityManager, backgroundSettings, trackListSettings } from './storage.js';
|
||||
import { db } from './db.js';
|
||||
|
||||
|
|
@ -491,25 +491,11 @@ async showFullscreenCover(track, nextTrack, lyricsManager, audioPlayer) {
|
|||
|
||||
if (lyricsManager && audioPlayer) {
|
||||
lyricsToggleBtn.style.display = 'flex';
|
||||
lyricsContainer.style.display = 'none';
|
||||
lyricsContainer.classList.remove('active');
|
||||
lyricsToggleBtn.classList.remove('active');
|
||||
|
||||
const toggleLyrics = async () => {
|
||||
const isActive = lyricsContainer.classList.contains('active');
|
||||
if (isActive) {
|
||||
lyricsContainer.classList.remove('active');
|
||||
lyricsToggleBtn.classList.remove('active');
|
||||
setTimeout(() => {
|
||||
lyricsContainer.style.display = 'none';
|
||||
clearFullscreenLyricsSync(lyricsContainer);
|
||||
}, 300);
|
||||
} else {
|
||||
lyricsContainer.style.display = 'block';
|
||||
setTimeout(() => lyricsContainer.classList.add('active'), 10);
|
||||
lyricsToggleBtn.classList.add('active');
|
||||
await renderLyricsInFullscreen(track, audioPlayer, lyricsManager, lyricsContainer);
|
||||
}
|
||||
const toggleLyrics = () => {
|
||||
openLyricsPanel(track, audioPlayer, lyricsManager);
|
||||
lyricsToggleBtn.classList.toggle('active');
|
||||
};
|
||||
|
||||
const newToggleBtn = lyricsToggleBtn.cloneNode(true);
|
||||
|
|
@ -524,13 +510,6 @@ async showFullscreenCover(track, nextTrack, lyricsManager, audioPlayer) {
|
|||
|
||||
closeFullscreenCover() {
|
||||
const overlay = document.getElementById('fullscreen-cover-overlay');
|
||||
const lyricsContainer = document.getElementById('fullscreen-lyrics-container');
|
||||
clearFullscreenLyricsSync(lyricsContainer);
|
||||
|
||||
lyricsContainer.style.display = 'none';
|
||||
lyricsContainer.classList.remove('active');
|
||||
lyricsContainer.innderHTML = '';
|
||||
|
||||
overlay.style.display = 'none';
|
||||
}
|
||||
|
||||
|
|
@ -1198,7 +1177,8 @@ async showFullscreenCover(track, nextTrack, lyricsManager, audioPlayer) {
|
|||
|
||||
// Set play button action
|
||||
playBtn.onclick = () => {
|
||||
player.playTracks(tracks, 0);
|
||||
this.player.setQueue(tracks, 0);
|
||||
this.player.playTrackFromQueue();
|
||||
};
|
||||
|
||||
document.title = `${displayTitle} - Monochrome`;
|
||||
|
|
|
|||
77
styles.css
77
styles.css
|
|
@ -1,5 +1,6 @@
|
|||
|
||||
:root {
|
||||
color-scheme: light dark;
|
||||
--spacing-xs: 0.4rem;
|
||||
--spacing-sm: 0.5rem;
|
||||
--spacing-md: 1rem;
|
||||
|
|
@ -16,6 +17,7 @@
|
|||
}
|
||||
|
||||
:root[data-theme="monochrome"] {
|
||||
color-scheme: dark;
|
||||
--background: #000;
|
||||
--foreground: #fafafa;
|
||||
--card: #111;
|
||||
|
|
@ -37,6 +39,7 @@
|
|||
}
|
||||
|
||||
:root[data-theme="dark"] {
|
||||
color-scheme: dark;
|
||||
--background: #0a0a0a;
|
||||
--foreground: #ededed;
|
||||
--card: #1a1a1a;
|
||||
|
|
@ -57,6 +60,7 @@
|
|||
}
|
||||
|
||||
:root[data-theme="ocean"] {
|
||||
color-scheme: dark;
|
||||
--background: #0c1821;
|
||||
--foreground: #e0f4ff;
|
||||
--card: #1b2838;
|
||||
|
|
@ -77,6 +81,7 @@
|
|||
}
|
||||
|
||||
:root[data-theme="purple"] {
|
||||
color-scheme: dark;
|
||||
--background: #0f0514;
|
||||
--foreground: #f3e8ff;
|
||||
--card: #1e0a2e;
|
||||
|
|
@ -97,6 +102,7 @@
|
|||
}
|
||||
|
||||
:root[data-theme="forest"] {
|
||||
color-scheme: dark;
|
||||
--background: #0a1409;
|
||||
--foreground: #e8f5e9;
|
||||
--card: #1a2e1a;
|
||||
|
|
@ -117,6 +123,7 @@
|
|||
}
|
||||
|
||||
:root[data-theme="mocha"] {
|
||||
color-scheme: dark;
|
||||
--background: #1e1e2e;
|
||||
--foreground: #cdd6f4;
|
||||
--card: #313244;
|
||||
|
|
@ -137,6 +144,7 @@
|
|||
}
|
||||
|
||||
:root[data-theme="machiatto"] {
|
||||
color-scheme: dark;
|
||||
--background: #24273a;
|
||||
--foreground: #cad3f5;
|
||||
--card: #363a4f;
|
||||
|
|
@ -157,6 +165,7 @@
|
|||
}
|
||||
|
||||
:root[data-theme="frappe"] {
|
||||
color-scheme: dark;
|
||||
--background: #303446;
|
||||
--foreground:#c6d0f5;
|
||||
--card: #414559;
|
||||
|
|
@ -177,6 +186,7 @@
|
|||
}
|
||||
|
||||
:root[data-theme="latte"] {
|
||||
color-scheme: light;
|
||||
--background: #eff1f5;
|
||||
--foreground: #4c4f69;
|
||||
--card: #ccd0da;
|
||||
|
|
@ -197,6 +207,7 @@
|
|||
}
|
||||
|
||||
:root[data-theme="light"] {
|
||||
color-scheme: light;
|
||||
--background: #ffffff;
|
||||
--foreground: #000000;
|
||||
--card: #f4f4f5;
|
||||
|
|
@ -2893,7 +2904,7 @@ input:checked + .slider::before {
|
|||
max-width: 100vw;
|
||||
background: var(--card);
|
||||
border-left: 1px solid var(--border);
|
||||
z-index: 2000;
|
||||
z-index: 2050;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
transform: translateX(100%);
|
||||
|
|
@ -3266,54 +3277,6 @@ img:not([src]), img[src=''] {
|
|||
transition: flex 0.3s ease;
|
||||
}
|
||||
|
||||
.fullscreen-lyrics-container {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
width: 0;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.85);
|
||||
backdrop-filter: blur(20px);
|
||||
border-left: 1px solid rgba(255, 255, 255, 0.1);
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
transition: width 0.3s ease;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.fullscreen-lyrics-container.active {
|
||||
width: 400px;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
|
||||
.fullscreen-lyrics-container > * {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
padding: 2rem;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.fullscreen-lyrics-container::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
.fullscreen-lyrics-container::-webkit-scrollbar-track {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.fullscreen-lyrics-container::-webkit-scrollbar-thumb {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.fullscreen-lyrics-container::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
|
||||
.fullscreen-lyrics-toggle {
|
||||
position: absolute;
|
||||
top: 1rem;
|
||||
|
|
@ -3345,22 +3308,6 @@ img:not([src]), img[src=''] {
|
|||
flex-direction: column;
|
||||
}
|
||||
|
||||
.fullscreen-lyrics-container {
|
||||
top: auto;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 0;
|
||||
border-left: none;
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
||||
transition: height 0.3s ease;
|
||||
}
|
||||
|
||||
.fullscreen-lyrics-container.active {
|
||||
width: 50vh;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.fullscreen-lyrics-toggle {
|
||||
right: 3.5rem;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue