From a1d62756b1dae47496aaa53007a9aa4c8fb0c769 Mon Sep 17 00:00:00 2001 From: Eduard Prigoana Date: Wed, 22 Oct 2025 18:03:28 +0300 Subject: [PATCH 1/4] fix --- assets/appicon.png | Bin 0 -> 7324 bytes index.html | 2 +- js/app.js | 57 ++++++++++++++++++++++++++++++++++++++------- manifest.json | 6 ++--- styles.css | 5 +++- 5 files changed, 56 insertions(+), 14 deletions(-) create mode 100644 assets/appicon.png diff --git a/assets/appicon.png b/assets/appicon.png new file mode 100644 index 0000000000000000000000000000000000000000..6b2cc5a8f1cb1ad9915665159501b4bafbd63ac8 GIT binary patch literal 7324 zcmeAS@N?(olHy`uVBq!ia0y~yU;#2&7&zE~RK2WrGmv5|4sv&5Sa(k5B}gjW(btiI zVPik{pF~z5pR>RtvY3H^?+6GpPSxg<1`0})xJHx&=ckpFCl;kL1SDqWmFW4ohA5co z8R}W)X6s4hbT1CXru2k8vWnY(WQUFq%V;uum9_x8@lE|Hf~4G;If6}vXklP4sg zhlka8qM@Ln;g_U69Qp^2R2zsGhO--9auD$GF$tCkWJ#ZsyuS26%J&@w3$knHvv>Xc zpMNQ9*Xy$-o{GXZ@8A0eGbH40jq05{ckagByRCf_>VV~Y-N~Dq(~mc^^B-&F7GK86 z@L{ud__`i-|9L*vGvxP!%ES-#1r;yuX?<1$<*nfystThX1ctz9g#tInNVdrOQhBLF)OCGLS z^WI|DXa3I*zj7R~R|O@l(VAqmUw-w~tXT}RzkGXpyYTa~vmgKb{9O3u z#l=jnoJ0Tq{j(^2b!A8O_jfzW-roB0>FMb&@(ID)wrx9h?%cTwU>#)A_*0N!hAtz+ zv((zgk8f^n-nf1Hb3XQ*8^6x|U+zEu*x`2mPUzp)XwVVty!w>POdhO$nEg1d?kCvRHCFf`ZVzj3Q zsyRm+5TH
- Current Track Cover + Current Track Cover
Select a song
diff --git a/js/app.js b/js/app.js index 678bf7b..4ae254d 100644 --- a/js/app.js +++ b/js/app.js @@ -396,6 +396,49 @@ document.addEventListener('DOMContentLoaded', async () => { window.loadHomeFeed = loadHomeFeed; + function positionContextMenu(menu, x, y, preferLeft = false) { + menu.style.display = 'block'; + menu.style.visibility = 'hidden'; + + const menuRect = menu.getBoundingClientRect(); + const viewportWidth = window.innerWidth; + const viewportHeight = window.innerHeight; + + let finalX = x; + let finalY = y; + + if (preferLeft || (x + menuRect.width > viewportWidth)) { + finalX = x - menuRect.width; + if (finalX < 0) { + finalX = Math.min(x, viewportWidth - menuRect.width - 10); + } + } + + if (finalX < 10) { + finalX = 10; + } + + if (finalX + menuRect.width > viewportWidth - 10) { + finalX = viewportWidth - menuRect.width - 10; + } + + if (y + menuRect.height > viewportHeight) { + finalY = Math.max(10, y - menuRect.height); + } + + if (finalY + menuRect.height > viewportHeight - 10) { + finalY = viewportHeight - menuRect.height - 10; + } + + if (finalY < 10) { + finalY = 10; + } + + menu.style.left = `${finalX}px`; + menu.style.top = `${finalY}px`; + menu.style.visibility = 'visible'; + } + function updateLastFMUI() { if (scrobbler.isAuthenticated()) { lastfmStatus.textContent = `Connected as ${scrobbler.username}`; @@ -808,11 +851,11 @@ document.addEventListener('DOMContentLoaded', async () => { function showQueueTrackMenu(e, trackIndex) { const menu = document.getElementById('queue-track-menu'); - menu.style.top = `${e.pageY}px`; - menu.style.left = `${e.pageX}px`; menu.classList.add('show'); menu.dataset.trackIndex = trackIndex; + positionContextMenu(menu, e.pageX, e.pageY, true); + document.addEventListener('click', hideQueueTrackMenu); } @@ -845,9 +888,7 @@ document.addEventListener('DOMContentLoaded', async () => { contextTrack = trackDataStore.get(trackItem); if (contextTrack) { const rect = menuBtn.getBoundingClientRect(); - contextMenu.style.top = `${rect.bottom + 5}px`; - contextMenu.style.left = `${rect.left}px`; - contextMenu.style.display = 'block'; + positionContextMenu(contextMenu, rect.left, rect.bottom + 5, true); } } return; @@ -877,9 +918,7 @@ document.addEventListener('DOMContentLoaded', async () => { contextTrack = trackDataStore.get(trackItem); if (contextTrack) { - contextMenu.style.top = `${e.pageY}px`; - contextMenu.style.left = `${e.pageX}px`; - contextMenu.style.display = 'block'; + positionContextMenu(contextMenu, e.pageX, e.pageY, true); } } }); @@ -1160,4 +1199,4 @@ document.addEventListener('DOMContentLoaded', async () => { e.preventDefault(); deferredPrompt = e; }); -}); +}); \ No newline at end of file diff --git a/manifest.json b/manifest.json index 0a211d4..5453afc 100644 --- a/manifest.json +++ b/manifest.json @@ -9,19 +9,19 @@ "orientation": "portrait-primary", "icons": [ { - "src": "/assets/192.png", + "src": "/assets/appicon.png", "sizes": "192x192", "type": "image/png", "purpose": "any" }, { - "src": "/assets/512.png", + "src": "/assets/appicon.png", "sizes": "512x512", "type": "image/png", "purpose": "any" }, { - "src": "/assets/512.png", + "src": "/assets/appicon.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" diff --git a/styles.css b/styles.css index 34f2ebd..a90be22 100644 --- a/styles.css +++ b/styles.css @@ -589,7 +589,10 @@ a { z-index: 10; } -.track-item:hover .track-menu-btn, +.track-item:hover .track-menu-btn { + opacity: 1; +} + @media (hover: none) { .track-menu-btn { opacity: 1; From 43a96d840748c5c3ec42ce8b644fedba00a7a536 Mon Sep 17 00:00:00 2001 From: Dazly Gonsalves Date: Wed, 22 Oct 2025 20:38:18 +0530 Subject: [PATCH 2/4] fix(player): display version alongside track title --- js/app.js | 3 ++- js/player.js | 6 ++++-- js/ui.js | 9 +++++---- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/js/app.js b/js/app.js index 678bf7b..2ff1c20 100644 --- a/js/app.js +++ b/js/app.js @@ -735,6 +735,7 @@ document.addEventListener('DOMContentLoaded', async () => { const html = currentQueue.map((track, index) => { const isPlaying = index === player.currentQueueIndex; + const trackTitle = track?.version ? `${track.title} (${track.version})` : track?.title; return `
@@ -748,7 +749,7 @@ document.addEventListener('DOMContentLoaded', async () => {
-
${track.title}
+
${trackTitle}
${track.artist?.name || 'Unknown'}
diff --git a/js/player.js b/js/player.js index d2210cb..b888be0 100644 --- a/js/player.js +++ b/js/player.js @@ -127,12 +127,14 @@ async playTrackFromQueue() { const track = currentQueue[this.currentQueueIndex]; this.currentTrack = track; + + const trackTitle = track?.version ? `${track.title} (${track.version})` : track?.title; document.querySelector('.now-playing-bar .cover').src = this.api.getCoverUrl(track.album?.cover, '1280'); - document.querySelector('.now-playing-bar .title').textContent = track.title; + document.querySelector('.now-playing-bar .title').textContent = trackTitle; document.querySelector('.now-playing-bar .artist').textContent = track.artist?.name || 'Unknown Artist'; - document.title = `${track.title} • ${track.artist?.name || 'Unknown'}`; + document.title = `${trackTitle} • ${track.artist?.name || 'Unknown'}`; this.updatePlayingTrackIndicator(); this.updateMediaSession(track); diff --git a/js/ui.js b/js/ui.js index c68ba5c..ccf23e1 100644 --- a/js/ui.js +++ b/js/ui.js @@ -25,7 +25,8 @@ export class UIRenderer { createTrackItemHTML(track, index, showCover = false) { const playIconSmall = ''; const trackNumberHTML = `
${showCover ? playIconSmall : index + 1}
`; - const explicitBadge = hasExplicitContent(track) ? this.createExplicitBadge() : ''; + const explicitBadge = !hasExplicitContent(track) ? this.createExplicitBadge() : ''; + const trackTitle = track?.version ? `${track.title} (${track.version})` : track?.title; return `
@@ -34,7 +35,7 @@ export class UIRenderer { ${showCover ? `Track Cover` : ''}
- ${track.title} + ${trackTitle} ${explicitBadge}
${track.artist?.name ?? 'Unknown Artist'}
@@ -114,7 +115,7 @@ export class UIRenderer { renderListWithTracks(container, tracks, showCover) { const fragment = document.createDocumentFragment(); const tempDiv = document.createElement('div'); - + tempDiv.innerHTML = tracks.map((track, i) => this.createTrackItemHTML(track, i, showCover) ).join(''); @@ -418,4 +419,4 @@ async renderHomePage() { } }); } -} \ No newline at end of file +} From ca941cd4fc6390a53f02cfa2f444d851ff1387a3 Mon Sep 17 00:00:00 2001 From: Eduard Prigoana Date: Wed, 22 Oct 2025 18:20:14 +0300 Subject: [PATCH 3/4] fix? --- js/app.js | 1 - styles.css | 13 +++++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/js/app.js b/js/app.js index 4ae254d..1cf990e 100644 --- a/js/app.js +++ b/js/app.js @@ -395,7 +395,6 @@ document.addEventListener('DOMContentLoaded', async () => { const lastfmToggleSetting = document.getElementById('lastfm-toggle-setting'); window.loadHomeFeed = loadHomeFeed; - function positionContextMenu(menu, x, y, preferLeft = false) { menu.style.display = 'block'; menu.style.visibility = 'hidden'; diff --git a/styles.css b/styles.css index a90be22..a10eeb7 100644 --- a/styles.css +++ b/styles.css @@ -117,6 +117,8 @@ html { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; + height: 100%; + overflow: hidden; } body { @@ -125,6 +127,9 @@ body { font-family: 'Inter', sans-serif; overflow: hidden; transition: background-color 0.3s ease, color 0.3s ease; + height: 100%; + position: fixed; + width: 100%; } img { @@ -142,6 +147,7 @@ a { .app-container { display: grid; height: 100vh; + height: 100dvh; grid-template: "sidebar main" 1fr "player player" auto / 280px 1fr; @@ -1008,7 +1014,7 @@ input:checked + .slider::before { #context-menu, .queue-track-menu { display: none; - position: absolute; + position: fixed; background-color: var(--card); border: 1px solid var(--border); border-radius: var(--radius); @@ -1557,6 +1563,8 @@ input:checked + .slider::before { "header" auto "main" 1fr "player" auto / 1fr; + height: 100vh; + height: 100dvh; } .main-content { @@ -1640,6 +1648,7 @@ input:checked + .slider::before { padding: var(--spacing-md); height: auto; } + .now-playing-bar .track-info { grid-area: track; @@ -1688,7 +1697,7 @@ input:checked + .slider::before { } #download-notifications { - bottom: 160px; + bottom: 10px; right: 10px; left: 10px; max-width: none; From e2b5217f3a3e3367c4549c4c3c250c291fd226ec Mon Sep 17 00:00:00 2001 From: Dazly Gonsalves Date: Wed, 22 Oct 2025 20:53:48 +0530 Subject: [PATCH 4/4] feat(player): display version beside track title --- js/app.js | 12 ++++++++---- js/player.js | 14 ++++++++------ js/ui.js | 4 ++-- js/utils.js | 9 +++++++-- 4 files changed, 25 insertions(+), 14 deletions(-) diff --git a/js/app.js b/js/app.js index 2ff1c20..bc95f36 100644 --- a/js/app.js +++ b/js/app.js @@ -7,7 +7,8 @@ import { REPEAT_MODE, SVG_PLAY, SVG_PAUSE, SVG_VOLUME, SVG_MUTE, formatTime, trackDataStore, buildTrackFilename, RATE_LIMIT_ERROR_MESSAGE, debounce, - sanitizeForFilename + sanitizeForFilename, + getTrackTitle } from './utils.js'; const downloadTasks = new Map(); @@ -38,13 +39,15 @@ function addDownloadTask(trackId, track, filename, api) { const taskEl = document.createElement('div'); taskEl.className = 'download-task'; taskEl.dataset.trackId = trackId; + + const trackTitle = getTrackTitle(track); taskEl.innerHTML = `
-
${track.title}
+
${trackTitle}
${track.artist?.name || 'Unknown'}
@@ -184,8 +187,9 @@ async function downloadAlbumAsZip(album, tracks, api, quality) { for (let i = 0; i < tracks.length; i++) { const track = tracks[i]; const filename = buildTrackFilename(track, quality); + const trackTitle = getTrackTitle(track); - updateBulkDownloadProgress(notification, i, tracks.length, track.title); + updateBulkDownloadProgress(notification, i, tracks.length, trackTitle); const blob = await downloadTrackBlob(track, quality, api); zip.file(`${folderName}/${filename}`, blob); @@ -735,7 +739,7 @@ document.addEventListener('DOMContentLoaded', async () => { const html = currentQueue.map((track, index) => { const isPlaying = index === player.currentQueueIndex; - const trackTitle = track?.version ? `${track.title} (${track.version})` : track?.title; + const trackTitle = getTrackTitle(track); return `
diff --git a/js/player.js b/js/player.js index b888be0..410ef1c 100644 --- a/js/player.js +++ b/js/player.js @@ -1,5 +1,5 @@ //player.js -import { REPEAT_MODE, formatTime } from './utils.js'; +import { REPEAT_MODE, formatTime, getTrackTitle } from './utils.js'; export class Player { constructor(audioElement, api, quality = 'LOSSLESS') { @@ -99,6 +99,7 @@ export class Player { for (const { track, index } of tracksToPreload) { if (this.preloadCache.has(track.id)) continue; + const trackTitle = getTrackTitle(track); try { const streamUrl = await this.api.getStreamUrl(track.id, this.quality); @@ -113,7 +114,7 @@ export class Player { } catch (error) { if (error.name !== 'AbortError') { - console.debug('Failed to get stream URL for preload:', track.title); + console.debug('Failed to get stream URL for preload:', trackTitle); } } } @@ -128,7 +129,7 @@ async playTrackFromQueue() { const track = currentQueue[this.currentQueueIndex]; this.currentTrack = track; - const trackTitle = track?.version ? `${track.title} (${track.version})` : track?.title; + const trackTitle = getTrackTitle(track); document.querySelector('.now-playing-bar .cover').src = this.api.getCoverUrl(track.album?.cover, '1280'); @@ -183,8 +184,8 @@ async playTrackFromQueue() { this.setupCrossfadeListener(); } catch (error) { - console.error(`Could not play track: ${track.title}`, error); - document.querySelector('.now-playing-bar .title').textContent = `Error: ${track.title}`; + console.error(`Could not play track: ${trackTitle}`, error); + document.querySelector('.now-playing-bar .title').textContent = `Error: ${trackTitle}`; document.querySelector('.now-playing-bar .artist').textContent = error.message || 'Could not load track'; } } @@ -412,6 +413,7 @@ async playTrackFromQueue() { const artwork = []; const sizes = ['1280']; const coverId = track.album?.cover; + const trackTitle = getTrackTitle(track); if (coverId) { sizes.forEach(size => { @@ -424,7 +426,7 @@ async playTrackFromQueue() { } navigator.mediaSession.metadata = new MediaMetadata({ - title: track.title || 'Unknown Title', + title: trackTitle, artist: track.artist?.name || 'Unknown Artist', album: track.album?.title || 'Unknown Album', artwork: artwork.length > 0 ? artwork : undefined diff --git a/js/ui.js b/js/ui.js index ccf23e1..80126d5 100644 --- a/js/ui.js +++ b/js/ui.js @@ -1,5 +1,5 @@ //ui.js -import { formatTime, createPlaceholder, trackDataStore, hasExplicitContent } from './utils.js'; +import { formatTime, createPlaceholder, trackDataStore, hasExplicitContent, getTrackTitle } from './utils.js'; import { recentActivityManager } from './storage.js'; export class UIRenderer { @@ -26,7 +26,7 @@ export class UIRenderer { const playIconSmall = ''; const trackNumberHTML = `
${showCover ? playIconSmall : index + 1}
`; const explicitBadge = !hasExplicitContent(track) ? this.createExplicitBadge() : ''; - const trackTitle = track?.version ? `${track.title} (${track.version})` : track?.title; + const trackTitle = getTrackTitle(track); return `
diff --git a/js/utils.js b/js/utils.js index 05754a2..460e19e 100644 --- a/js/utils.js +++ b/js/utils.js @@ -70,7 +70,7 @@ export const buildTrackFilename = (track, quality) => { const artistName = sanitizeForFilename(track.artist?.name); const albumTitle = sanitizeForFilename(track.album?.title); - const trackTitle = sanitizeForFilename(track.title); + const trackTitle = sanitizeForFilename(getTrackTitle(track)); return `${artistName} - ${albumTitle} - ${padded} ${trackTitle}.${extension}`; }; @@ -155,4 +155,9 @@ export const debounce = (func, wait) => { clearTimeout(timeout); timeout = setTimeout(later, wait); }; -}; \ No newline at end of file +}; + +export const getTrackTitle = (track, { fallback = 'Unknown Title' } = {}) => { + if (!track?.title) return fallback; + return track?.version ? `${track.title} (${track.version})` : track.title; +};