style: auto-fix linting issues

This commit is contained in:
SamidyFR 2026-01-24 12:13:44 +00:00 committed by github-actions[bot]
parent b7bc90f4f1
commit 4ede3b2664
23 changed files with 413 additions and 273 deletions

View file

@ -67,7 +67,9 @@ class ServerAPI {
export async function onRequest(context) { export async function onRequest(context) {
const { request, params, env } = context; const { request, params, env } = context;
const userAgent = request.headers.get('User-Agent') || ''; const userAgent = request.headers.get('User-Agent') || '';
const isBot = /discordbot|twitterbot|facebookexternalhit|bingbot|googlebot|slurp|whatsapp|pinterest|slackbot/i.test(userAgent); const isBot = /discordbot|twitterbot|facebookexternalhit|bingbot|googlebot|slurp|whatsapp|pinterest|slackbot/i.test(
userAgent
);
const albumId = params.id; const albumId = params.id;
if (isBot && albumId) { if (isBot && albumId) {
@ -84,7 +86,9 @@ export async function onRequest(context) {
const trackCount = album.numberOfTracks || tracks.length; const trackCount = album.numberOfTracks || tracks.length;
const description = `Album by ${artist}${year}${trackCount} Tracks\nListen on Monochrome`; const description = `Album by ${artist}${year}${trackCount} Tracks\nListen on Monochrome`;
const imageUrl = album.cover ? api.getCoverUrl(album.cover, '1280') : 'https://monochrome.samidy.com/assets/appicon.png'; const imageUrl = album.cover
? api.getCoverUrl(album.cover, '1280')
: 'https://monochrome.samidy.com/assets/appicon.png';
const pageUrl = new URL(request.url).href; const pageUrl = new URL(request.url).href;
const metaHtml = ` const metaHtml = `

View file

@ -67,7 +67,9 @@ class ServerAPI {
export async function onRequest(context) { export async function onRequest(context) {
const { request, params, env } = context; const { request, params, env } = context;
const userAgent = request.headers.get('User-Agent') || ''; const userAgent = request.headers.get('User-Agent') || '';
const isBot = /discordbot|twitterbot|facebookexternalhit|bingbot|googlebot|slurp|whatsapp|pinterest|slackbot/i.test(userAgent); const isBot = /discordbot|twitterbot|facebookexternalhit|bingbot|googlebot|slurp|whatsapp|pinterest|slackbot/i.test(
userAgent
);
const artistId = params.id; const artistId = params.id;
if (isBot && artistId) { if (isBot && artistId) {
@ -79,7 +81,9 @@ export async function onRequest(context) {
if (artist && (artist.name || artist.title)) { if (artist && (artist.name || artist.title)) {
const name = artist.name || artist.title; const name = artist.name || artist.title;
const description = `Listen to ${name} on Monochrome`; const description = `Listen to ${name} on Monochrome`;
const imageUrl = artist.picture ? api.getArtistPictureUrl(artist.picture, '750') : 'https://monochrome.samidy.com/assets/appicon.png'; const imageUrl = artist.picture
? api.getArtistPictureUrl(artist.picture, '750')
: 'https://monochrome.samidy.com/assets/appicon.png';
const pageUrl = new URL(request.url).href; const pageUrl = new URL(request.url).href;
const metaHtml = ` const metaHtml = `

View file

@ -68,7 +68,9 @@ class ServerAPI {
export async function onRequest(context) { export async function onRequest(context) {
const { request, params, env } = context; const { request, params, env } = context;
const userAgent = request.headers.get('User-Agent') || ''; const userAgent = request.headers.get('User-Agent') || '';
const isBot = /discordbot|twitterbot|facebookexternalhit|bingbot|googlebot|slurp|whatsapp|pinterest|slackbot/i.test(userAgent); const isBot = /discordbot|twitterbot|facebookexternalhit|bingbot|googlebot|slurp|whatsapp|pinterest|slackbot/i.test(
userAgent
);
const playlistId = params.id; const playlistId = params.id;
if (isBot && playlistId) { if (isBot && playlistId) {
@ -82,7 +84,9 @@ export async function onRequest(context) {
const trackCount = playlist.numberOfTracks; const trackCount = playlist.numberOfTracks;
const description = `Playlist • ${trackCount} Tracks\nListen on Monochrome`; const description = `Playlist • ${trackCount} Tracks\nListen on Monochrome`;
const imageId = playlist.squareImage || playlist.image; const imageId = playlist.squareImage || playlist.image;
const imageUrl = imageId ? api.getCoverUrl(imageId, '1080') : 'https://monochrome.samidy.com/assets/appicon.png'; const imageUrl = imageId
? api.getCoverUrl(imageId, '1080')
: 'https://monochrome.samidy.com/assets/appicon.png';
const pageUrl = new URL(request.url).href; const pageUrl = new URL(request.url).href;
const metaHtml = ` const metaHtml = `

View file

@ -64,7 +64,7 @@ class ServerAPI {
const json = await response.json(); const json = await response.json();
const data = json.data || json; const data = json.data || json;
const items = Array.isArray(data) ? data : [data]; const items = Array.isArray(data) ? data : [data];
const found = items.find(i => (i.id == id) || (i.item && i.item.id == id)); const found = items.find((i) => i.id == id || (i.item && i.item.id == id));
if (found) { if (found) {
return found.item || found; return found.item || found;
} }
@ -87,7 +87,9 @@ class ServerAPI {
export async function onRequest(context) { export async function onRequest(context) {
const { request, params, env } = context; const { request, params, env } = context;
const userAgent = request.headers.get('User-Agent') || ''; const userAgent = request.headers.get('User-Agent') || '';
const isBot = /discordbot|twitterbot|facebookexternalhit|bingbot|googlebot|slurp|whatsapp|pinterest|slackbot/i.test(userAgent); const isBot = /discordbot|twitterbot|facebookexternalhit|bingbot|googlebot|slurp|whatsapp|pinterest|slackbot/i.test(
userAgent
);
const trackId = params.id; const trackId = params.id;
if (isBot && trackId) { if (isBot && trackId) {
@ -112,12 +114,14 @@ export async function onRequest(context) {
} }
} }
// this prob wont work im js winging it // this prob wont work im js winging it
const audioMeta = audioUrl ? ` const audioMeta = audioUrl
? `
<meta property="og:audio" content="${audioUrl}"> <meta property="og:audio" content="${audioUrl}">
<meta property="og:audio:type" content="audio/mp4"> <meta property="og:audio:type" content="audio/mp4">
<meta property="og:video" content="${audioUrl}"> <meta property="og:video" content="${audioUrl}">
<meta property="og:video:type" content="audio/mp4"> <meta property="og:video:type" content="audio/mp4">
` : ''; `
: '';
const metaHtml = ` const metaHtml = `
<!DOCTYPE html> <!DOCTYPE html>

View file

@ -1,12 +1,13 @@
// functions/userplaylist/[id].js // functions/userplaylist/[id].js
// note that, since this NEEDS a playlist to yknow, be public, this only works for PUBLIC playlists (and you will need an account) // note that, since this NEEDS a playlist to yknow, be public, this only works for PUBLIC playlists (and you will need an account)
export async function onRequest(context) { export async function onRequest(context) {
const { request, params, env } = context; const { request, params, env } = context;
const userAgent = request.headers.get('User-Agent') || ''; const userAgent = request.headers.get('User-Agent') || '';
const isBot = /discordbot|twitterbot|facebookexternalhit|bingbot|googlebot|slurp|whatsapp|pinterest|slackbot/i.test(userAgent); const isBot = /discordbot|twitterbot|facebookexternalhit|bingbot|googlebot|slurp|whatsapp|pinterest|slackbot/i.test(
userAgent
);
const playlistId = params.id; const playlistId = params.id;
if (isBot && playlistId) { if (isBot && playlistId) {
@ -30,7 +31,11 @@ export async function onRequest(context) {
const title = playlist.name || playlist.title || 'User Playlist'; const title = playlist.name || playlist.title || 'User Playlist';
let tracks = []; let tracks = [];
try { try {
tracks = Array.isArray(playlist.tracks) ? playlist.tracks : (playlist.tracks ? JSON.parse(playlist.tracks) : []); tracks = Array.isArray(playlist.tracks)
? playlist.tracks
: playlist.tracks
? JSON.parse(playlist.tracks)
: [];
} catch (e) { } catch (e) {
console.error('Failed to parse tracks JSON', e); console.error('Failed to parse tracks JSON', e);
} }
@ -45,7 +50,12 @@ export async function onRequest(context) {
} else { } else {
imageUrl = `https://monodb.samidy.com/api/files/${playlist.collectionId}/${playlist.id}/${playlist.cover}`; imageUrl = `https://monodb.samidy.com/api/files/${playlist.collectionId}/${playlist.id}/${playlist.cover}`;
} }
} else if (tracks.length > 0 && typeof tracks[0] === 'object' && tracks[0].album && tracks[0].album.cover) { } else if (
tracks.length > 0 &&
typeof tracks[0] === 'object' &&
tracks[0].album &&
tracks[0].album.cover
) {
const cover = tracks[0].album.cover; const cover = tracks[0].album.cover;
imageUrl = `https://resources.tidal.com/images/${cover.replace(/-/g, '/')}/1280x1280.jpg`; imageUrl = `https://resources.tidal.com/images/${cover.replace(/-/g, '/')}/1280x1280.jpg`;
} }

View file

@ -846,7 +846,7 @@ export class LosslessAPI {
let track; let track;
const items = Array.isArray(data) ? data : [data]; const items = Array.isArray(data) ? data : [data];
const found = items.find(i => (i.id == id) || (i.item && i.item.id == id)); const found = items.find((i) => i.id == id || (i.item && i.item.id == id));
if (found) { if (found) {
track = this.prepareTrack(found.item || found); track = this.prepareTrack(found.item || found);

View file

@ -15,7 +15,13 @@ import { createRouter, updateTabTitle, navigate } from './router.js';
import { initializeSettings } from './settings.js'; import { initializeSettings } from './settings.js';
import { initializePlayerEvents, initializeTrackInteractions, handleTrackAction } from './events.js'; import { initializePlayerEvents, initializeTrackInteractions, handleTrackAction } from './events.js';
import { initializeUIInteractions } from './ui-interactions.js'; import { initializeUIInteractions } from './ui-interactions.js';
import { downloadAlbumAsZip, downloadDiscography, downloadPlaylistAsZip, downloadLikedTracks, showNotification } from './downloads.js'; import {
downloadAlbumAsZip,
downloadDiscography,
downloadPlaylistAsZip,
downloadLikedTracks,
showNotification,
} from './downloads.js';
import { debounce, SVG_PLAY } from './utils.js'; import { debounce, SVG_PLAY } from './utils.js';
import { sidePanelManager } from './side-panel.js'; import { sidePanelManager } from './side-panel.js';
import { db } from './db.js'; import { db } from './db.js';
@ -192,7 +198,8 @@ document.addEventListener('DOMContentLoaded', async () => {
// i love ios and macos!!!! webkit fucking SUCKS BULLSHIT sorry ios/macos heads yall getting lossless only // i love ios and macos!!!! webkit fucking SUCKS BULLSHIT sorry ios/macos heads yall getting lossless only
const ua = navigator.userAgent.toLowerCase(); const ua = navigator.userAgent.toLowerCase();
const isIOS = /iphone|ipad|ipod/.test(ua) || (ua.includes('mac') && navigator.maxTouchPoints > 1); const isIOS = /iphone|ipad|ipod/.test(ua) || (ua.includes('mac') && navigator.maxTouchPoints > 1);
const isSafari = ua.includes('safari') && !ua.includes('chrome') && !ua.includes('crios') && !ua.includes('android'); const isSafari =
ua.includes('safari') && !ua.includes('chrome') && !ua.includes('crios') && !ua.includes('android');
if (isIOS || isSafari) { if (isIOS || isSafari) {
const qualitySelect = document.getElementById('streaming-quality-setting'); const qualitySelect = document.getElementById('streaming-quality-setting');
@ -919,11 +926,15 @@ document.addEventListener('DOMContentLoaded', async () => {
return; return;
} }
list.innerHTML = playlists.map(p => ` list.innerHTML = playlists
.map(
(p) => `
<div class="modal-option" data-id="${p.id}"> <div class="modal-option" data-id="${p.id}">
<span>${p.name}</span> <span>${p.name}</span>
</div> </div>
`).join(''); `
)
.join('');
const closeModal = () => { const closeModal = () => {
modal.classList.remove('active'); modal.classList.remove('active');
@ -1207,7 +1218,6 @@ document.addEventListener('DOMContentLoaded', async () => {
updateTabTitle(player); updateTabTitle(player);
}; };
await handleRouteChange(); await handleRouteChange();
window.addEventListener('popstate', handleRouteChange); window.addEventListener('popstate', handleRouteChange);
@ -1215,7 +1225,12 @@ document.addEventListener('DOMContentLoaded', async () => {
document.body.addEventListener('click', (e) => { document.body.addEventListener('click', (e) => {
const link = e.target.closest('a'); const link = e.target.closest('a');
if (link && link.origin === window.location.origin && link.target !== '_blank' && !link.hasAttribute('download')) { if (
link &&
link.origin === window.location.origin &&
link.target !== '_blank' &&
!link.hasAttribute('download')
) {
e.preventDefault(); e.preventDefault();
navigate(link.pathname); navigate(link.pathname);
} }

View file

@ -77,7 +77,7 @@ export class DashDownloader {
adaptationSets.sort((a, b) => { adaptationSets.sort((a, b) => {
const getMaxBandwidth = (set) => { const getMaxBandwidth = (set) => {
const reps = Array.from(set.querySelectorAll('Representation')); const reps = Array.from(set.querySelectorAll('Representation'));
return reps.length ? Math.max(...reps.map(r => parseInt(r.getAttribute('bandwidth') || '0', 10))) : 0; return reps.length ? Math.max(...reps.map((r) => parseInt(r.getAttribute('bandwidth') || '0', 10))) : 0;
}; };
return getMaxBandwidth(b) - getMaxBandwidth(a); return getMaxBandwidth(b) - getMaxBandwidth(a);
}); });

View file

@ -545,7 +545,7 @@ export class MusicDatabase {
cover: cover, cover: cover,
playlists: [], playlists: [],
createdAt: Date.now(), createdAt: Date.now(),
updatedAt: Date.now() updatedAt: Date.now(),
}; };
await this.performTransaction('user_folders', 'readwrite', (store) => store.put(folder)); await this.performTransaction('user_folders', 'readwrite', (store) => store.put(folder));
return folder; return folder;

View file

@ -545,7 +545,16 @@ function createBulkDownloadNotification(type, name, _totalItems) {
notifEl.dataset.bulkType = type; notifEl.dataset.bulkType = type;
notifEl.dataset.bulkName = name; notifEl.dataset.bulkName = name;
const typeLabel = type === 'album' ? 'Album' : type === 'playlist' ? 'Playlist' : type === 'liked' ? 'Liked Tracks' : type === 'queue' ? 'Queue' : 'Discography'; const typeLabel =
type === 'album'
? 'Album'
: type === 'playlist'
? 'Playlist'
: type === 'liked'
? 'Liked Tracks'
: type === 'queue'
? 'Queue'
: 'Discography';
notifEl.innerHTML = ` notifEl.innerHTML = `
<div style="display: flex; align-items: start; gap: 0.75rem;"> <div style="display: flex; align-items: start; gap: 0.75rem;">

View file

@ -170,7 +170,10 @@ export function initializePlayerEvents(player, audioPlayer, scrobbler, ui) {
const progressBar = document.getElementById('progress-bar'); const progressBar = document.getElementById('progress-bar');
const playerControls = document.querySelector('.player-controls'); const playerControls = document.querySelector('.player-controls');
const isTracker = player.currentTrack && (player.currentTrack.isTracker || (player.currentTrack.id && String(player.currentTrack.id).startsWith('tracker-'))); const isTracker =
player.currentTrack &&
(player.currentTrack.isTracker ||
(player.currentTrack.id && String(player.currentTrack.id).startsWith('tracker-')));
if (!waveformSettings.isEnabled() || !player.currentTrack || isTracker) { if (!waveformSettings.isEnabled() || !player.currentTrack || isTracker) {
if (progressBar) { if (progressBar) {

View file

@ -12,7 +12,7 @@ class GeniusManager {
} }
getToken() { getToken() {
return "QmS9OvsS-7ifRBKx_ochIPQU7oejIS9Eo_z5iWHmCPyhwLVQID3pYTHJmJTa6z8z"; // idgaf anymore im js hardcoding this lmaooo return 'QmS9OvsS-7ifRBKx_ochIPQU7oejIS9Eo_z5iWHmCPyhwLVQID3pYTHJmJTa6z8z'; // idgaf anymore im js hardcoding this lmaooo
} }
async searchTrack(title, artist) { async searchTrack(title, artist) {
@ -22,7 +22,7 @@ class GeniusManager {
const url = `https://api.genius.com/search?q=${query}`; const url = `https://api.genius.com/search?q=${query}`;
const token = this.getToken(); const token = this.getToken();
const response = await fetch(`https://corsproxy.io/?${encodeURIComponent(url)}`, { const response = await fetch(`https://corsproxy.io/?${encodeURIComponent(url)}`, {
headers: { 'Authorization': `Bearer ${token}` } headers: { Authorization: `Bearer ${token}` },
}); });
if (!response.ok) throw new Error('Failed to search Genius'); if (!response.ok) throw new Error('Failed to search Genius');
@ -30,10 +30,10 @@ class GeniusManager {
const data = await response.json(); const data = await response.json();
if (data.response.hits.length === 0) return null; if (data.response.hits.length === 0) return null;
const normalize = str => str.toLowerCase().replace(/[^\p{L}\p{N}]/gu, ''); const normalize = (str) => str.toLowerCase().replace(/[^\p{L}\p{N}]/gu, '');
const targetArtist = normalize(artist); const targetArtist = normalize(artist);
const hit = data.response.hits.find(h => { const hit = data.response.hits.find((h) => {
const hitArtist = normalize(h.result.primary_artist.name); const hitArtist = normalize(h.result.primary_artist.name);
return hitArtist.includes(targetArtist) || targetArtist.includes(hitArtist); return hitArtist.includes(targetArtist) || targetArtist.includes(hitArtist);
}); });
@ -45,7 +45,7 @@ class GeniusManager {
const token = this.getToken(); const token = this.getToken();
const url = `https://api.genius.com/referents?song_id=${songId}&text_format=plain&per_page=50`; const url = `https://api.genius.com/referents?song_id=${songId}&text_format=plain&per_page=50`;
const response = await fetch(`https://corsproxy.io/?${encodeURIComponent(url)}`, { const response = await fetch(`https://corsproxy.io/?${encodeURIComponent(url)}`, {
headers: { 'Authorization': `Bearer ${token}` } headers: { Authorization: `Bearer ${token}` },
}); });
if (!response.ok) throw new Error('Failed to fetch annotations'); if (!response.ok) throw new Error('Failed to fetch annotations');
@ -83,13 +83,18 @@ class GeniusManager {
findAnnotations(lineText, referents) { findAnnotations(lineText, referents) {
if (!referents || !lineText) return []; if (!referents || !lineText) return [];
const normalize = str => str.toLowerCase().replace(/[^\p{L}\p{N}\s]/gu, '').replace(/\s+/g, ' ').trim(); const normalize = (str) =>
str
.toLowerCase()
.replace(/[^\p{L}\p{N}\s]/gu, '')
.replace(/\s+/g, ' ')
.trim();
const normLine = normalize(lineText); const normLine = normalize(lineText);
const getWordSet = (str) => new Set(str.split(' ').filter(w => w.length > 0)); const getWordSet = (str) => new Set(str.split(' ').filter((w) => w.length > 0));
const lineWords = getWordSet(normLine); const lineWords = getWordSet(normLine);
return referents.filter(ref => { return referents.filter((ref) => {
const normFragment = normalize(ref.fragment); const normFragment = normalize(ref.fragment);
if (normLine.includes(normFragment) || normFragment.includes(normLine)) return true; if (normLine.includes(normFragment) || normFragment.includes(normLine)) return true;
@ -98,9 +103,11 @@ class GeniusManager {
if (fragmentWords.size === 0 || lineWords.size === 0) return false; if (fragmentWords.size === 0 || lineWords.size === 0) return false;
let matchCount = 0; let matchCount = 0;
fragmentWords.forEach(w => { if (lineWords.has(w)) matchCount++; }); fragmentWords.forEach((w) => {
if (lineWords.has(w)) matchCount++;
});
return (matchCount / Math.min(fragmentWords.size, lineWords.size)) > 0.6; return matchCount / Math.min(fragmentWords.size, lineWords.size) > 0.6;
}); });
} }
} }
@ -454,19 +461,20 @@ export class LyricsManager {
this.romajiObserver = new MutationObserver((mutations) => { this.romajiObserver = new MutationObserver((mutations) => {
// Check if any relevant mutation occurred // Check if any relevant mutation occurred
const hasRelevantChange = mutations.some((mutation) => { const hasRelevantChange = mutations.some((mutation) => {
if (mutation.type === 'childList') { if (mutation.type === 'childList') {
let relevant = false; let relevant = false;
if (mutation.addedNodes.length > 0) { if (mutation.addedNodes.length > 0) {
for (const node of mutation.addedNodes) { for (const node of mutation.addedNodes) {
if (node.nodeType === Node.ELEMENT_NODE && node.classList.contains('genius-indicator')) continue; if (node.nodeType === Node.ELEMENT_NODE && node.classList.contains('genius-indicator'))
continue;
relevant = true; relevant = true;
break; break;
} }
} }
if (!relevant && mutation.removedNodes.length > 0) { if (!relevant && mutation.removedNodes.length > 0) {
for (const node of mutation.removedNodes) { for (const node of mutation.removedNodes) {
if (node.nodeType === Node.ELEMENT_NODE && node.classList.contains('genius-indicator')) continue; if (node.nodeType === Node.ELEMENT_NODE && node.classList.contains('genius-indicator'))
continue;
relevant = true; relevant = true;
break; break;
} }
@ -628,34 +636,38 @@ export class LyricsManager {
if (lineElements.length === 0) return; if (lineElements.length === 0) return;
lineElements.forEach((el) => {
lineElements.forEach(el => {
el.classList.remove('genius-annotated', 'genius-multi-start', 'genius-multi-end', 'genius-multi-mid'); el.classList.remove('genius-annotated', 'genius-multi-start', 'genius-multi-end', 'genius-multi-mid');
delete el.__geniusAnnotations; delete el.__geniusAnnotations;
}); });
const normalize = (str) =>
str
.toLowerCase()
.replace(/[^\p{L}\p{N}\s]/gu, '')
.replace(/\s+/g, ' ')
.trim();
const normalize = str => str.toLowerCase().replace(/[^\p{L}\p{N}\s]/gu, '').replace(/\s+/g, ' ').trim(); referents.forEach((ref) => {
referents.forEach(ref => {
const fragment = normalize(ref.fragment); const fragment = normalize(ref.fragment);
if (!fragment) return; if (!fragment) return;
for (let i = 0; i < lineElements.length; i++) { for (let i = 0; i < lineElements.length; i++) {
let combinedText = ""; let combinedText = '';
let currentLines = []; let currentLines = [];
for (let j = i; j < lineElements.length; j++) { for (let j = i; j < lineElements.length; j++) {
const line = lineElements[j]; const line = lineElements[j];
const lineClone = line.cloneNode(true); const lineClone = line.cloneNode(true);
lineClone.querySelectorAll('.time, .timestamp, [class*="time"], .genius-indicator').forEach(n => n.remove()); lineClone
const text = normalize(lineClone.textContent || ""); .querySelectorAll('.time, .timestamp, [class*="time"], .genius-indicator')
.forEach((n) => n.remove());
const text = normalize(lineClone.textContent || '');
if (!text) continue; if (!text) continue;
if (currentLines.length > 0) combinedText += " "; if (currentLines.length > 0) combinedText += ' ';
combinedText += text; combinedText += text;
currentLines.push(line); currentLines.push(line);
@ -664,7 +676,7 @@ export class LyricsManager {
el.classList.add('genius-annotated'); el.classList.add('genius-annotated');
if (!el.__geniusAnnotations) el.__geniusAnnotations = []; if (!el.__geniusAnnotations) el.__geniusAnnotations = [];
if (!el.__geniusAnnotations.some(a => a.id === ref.id)) { if (!el.__geniusAnnotations.some((a) => a.id === ref.id)) {
el.__geniusAnnotations.push(ref); el.__geniusAnnotations.push(ref);
} }
@ -686,7 +698,6 @@ export class LyricsManager {
break; break;
} }
if (combinedText.length > fragment.length + 50) break; if (combinedText.length > fragment.length + 50) break;
} }
} }
@ -760,13 +771,16 @@ export function openLyricsPanel(track, audioPlayer, lyricsManager, forceOpen = f
geniusBtn.innerHTML = enabled ? SVG_GENIUS_ACTIVE : SVG_GENIUS_INACTIVE; geniusBtn.innerHTML = enabled ? SVG_GENIUS_ACTIVE : SVG_GENIUS_INACTIVE;
if (enabled) { if (enabled) {
try { try {
geniusBtn.style.opacity = '0.5'; geniusBtn.style.opacity = '0.5';
await manager.geniusManager.getDataForTrack(track); await manager.geniusManager.getDataForTrack(track);
manager.currentGeniusData = manager.geniusManager.cache.get(track.id); manager.currentGeniusData = manager.geniusManager.cache.get(track.id);
const amLyrics = sidePanelManager.panel.querySelector('am-lyrics'); const amLyrics = sidePanelManager.panel.querySelector('am-lyrics');
if (amLyrics) manager.applyGeniusAnnotations(amLyrics, manager.geniusManager.cache.get(track.id)?.referents); if (amLyrics)
manager.applyGeniusAnnotations(
amLyrics,
manager.geniusManager.cache.get(track.id)?.referents
);
} catch (e) { } catch (e) {
alert(e.message); alert(e.message);
manager.isGeniusMode = false; manager.isGeniusMode = false;
@ -776,13 +790,17 @@ export function openLyricsPanel(track, audioPlayer, lyricsManager, forceOpen = f
geniusBtn.style.opacity = '1'; geniusBtn.style.opacity = '1';
} }
} else { } else {
const amLyrics = sidePanelManager.panel.querySelector('am-lyrics'); const amLyrics = sidePanelManager.panel.querySelector('am-lyrics');
if (amLyrics) { if (amLyrics) {
const root = amLyrics.shadowRoot || amLyrics; const root = amLyrics.shadowRoot || amLyrics;
const lineElements = Array.from(root.querySelectorAll('.genius-annotated')); const lineElements = Array.from(root.querySelectorAll('.genius-annotated'));
lineElements.forEach(el => { lineElements.forEach((el) => {
el.classList.remove('genius-annotated', 'genius-multi-start', 'genius-multi-end', 'genius-multi-mid'); el.classList.remove(
'genius-annotated',
'genius-multi-start',
'genius-multi-end',
'genius-multi-mid'
);
delete el.__geniusAnnotations; delete el.__geniusAnnotations;
}); });
} }
@ -848,8 +866,9 @@ async function renderLyricsComponent(container, track, audioPlayer, lyricsManage
await lyricsManager.loadKuroshiro(); await lyricsManager.loadKuroshiro();
} }
lyricsManager
lyricsManager.fetchLyrics(track.id, track).then(async () => { .fetchLyrics(track.id, track)
.then(async () => {
if (lyricsManager.isGeniusMode) { if (lyricsManager.isGeniusMode) {
try { try {
const data = await lyricsManager.geniusManager.getDataForTrack(track); const data = await lyricsManager.geniusManager.getDataForTrack(track);
@ -857,9 +876,12 @@ async function renderLyricsComponent(container, track, audioPlayer, lyricsManage
lyricsManager.currentGeniusData = data; lyricsManager.currentGeniusData = data;
lyricsManager.applyGeniusAnnotations(amLyrics, data.referents); lyricsManager.applyGeniusAnnotations(amLyrics, data.referents);
} }
} catch (e) { console.warn('Genius auto-load failed', e); } } catch (e) {
console.warn('Genius auto-load failed', e);
} }
}).catch(e => console.warn('Background lyrics fetch failed', e)); }
})
.catch((e) => console.warn('Background lyrics fetch failed', e));
// Wait for lyrics to appear, then do an immediate conversion // Wait for lyrics to appear, then do an immediate conversion
const waitForLyrics = () => { const waitForLyrics = () => {
@ -955,20 +977,21 @@ function setupSync(track, audioPlayer, amLyrics, lyricsManager) {
const onLineClick = (e) => { const onLineClick = (e) => {
if (e.detail && e.detail.timestamp !== undefined) { if (e.detail && e.detail.timestamp !== undefined) {
const manager = lyricsManager || sidePanelManager.panel.lyricsManager; const manager = lyricsManager || sidePanelManager.panel.lyricsManager;
if (manager && manager.isGeniusMode) { if (manager && manager.isGeniusMode) {
const timestampSeconds = e.detail.timestamp / 1000; const timestampSeconds = e.detail.timestamp / 1000;
const lyricsData = manager.lyricsCache.get(track.id); const lyricsData = manager.lyricsCache.get(track.id);
if (lyricsData && lyricsData.subtitles) { if (lyricsData && lyricsData.subtitles) {
const parsed = manager.parseSyncedLyrics(lyricsData.subtitles); const parsed = manager.parseSyncedLyrics(lyricsData.subtitles);
const line = parsed.find(l => Math.abs(l.time - timestampSeconds) < 1.0); const line = parsed.find((l) => Math.abs(l.time - timestampSeconds) < 1.0);
if (line && line.text && manager.currentGeniusData) { if (line && line.text && manager.currentGeniusData) {
const annotations = manager.geniusManager.findAnnotations(line.text, manager.currentGeniusData.referents); const annotations = manager.geniusManager.findAnnotations(
line.text,
manager.currentGeniusData.referents
);
showGeniusAnnotations(annotations, line.text); showGeniusAnnotations(annotations, line.text);
} }
} }
@ -1003,7 +1026,6 @@ function setupSync(track, audioPlayer, amLyrics, lyricsManager) {
} }
function showGeniusAnnotations(annotations, lineText) { function showGeniusAnnotations(annotations, lineText) {
const existing = document.querySelector('.genius-annotation-modal'); const existing = document.querySelector('.genius-annotation-modal');
if (existing) existing.remove(); if (existing) existing.remove();
@ -1026,7 +1048,7 @@ function showGeniusAnnotations(annotations, lineText) {
</div> </div>
`; `;
} else { } else {
annotations.forEach(ann => { annotations.forEach((ann) => {
const body = ann.annotations[0].body.plain; const body = ann.annotations[0].body.plain;
contentHtml += ` contentHtml += `
<div class="annotation-item"> <div class="annotation-item">
@ -1043,7 +1065,9 @@ function showGeniusAnnotations(annotations, lineText) {
modal.querySelector('.close-genius').addEventListener('click', () => modal.remove()); modal.querySelector('.close-genius').addEventListener('click', () => modal.remove());
modal.addEventListener('click', (e) => { if(e.target === modal) modal.remove(); }); modal.addEventListener('click', (e) => {
if (e.target === modal) modal.remove();
});
} }
export async function renderLyricsInFullscreen(track, audioPlayer, lyricsManager, container) { export async function renderLyricsInFullscreen(track, audioPlayer, lyricsManager, container) {

View file

@ -22,7 +22,8 @@ export async function addMetadataToAudio(audioBlob, track, api, quality) {
const buffer = await audioBlob.slice(0, 4).arrayBuffer(); const buffer = await audioBlob.slice(0, 4).arrayBuffer();
const view = new DataView(buffer); const view = new DataView(buffer);
const isFlac = view.byteLength >= 4 && const isFlac =
view.byteLength >= 4 &&
view.getUint8(0) === 0x66 && // f view.getUint8(0) === 0x66 && // f
view.getUint8(1) === 0x4c && // L view.getUint8(1) === 0x4c && // L
view.getUint8(2) === 0x61 && // a view.getUint8(2) === 0x61 && // a
@ -37,7 +38,6 @@ export async function addMetadataToAudio(audioBlob, track, api, quality) {
if (mime === 'audio/mp4') { if (mime === 'audio/mp4') {
return await addM4aMetadata(audioBlob, track, api); return await addM4aMetadata(audioBlob, track, api);
} }
} }
/** /**

View file

@ -319,7 +319,10 @@ export class Player {
} }
streamUrl = track.audioUrl; streamUrl = track.audioUrl;
if ((!streamUrl || (typeof streamUrl === 'string' && streamUrl.startsWith('blob:'))) && track.remoteUrl) { if (
(!streamUrl || (typeof streamUrl === 'string' && streamUrl.startsWith('blob:'))) &&
track.remoteUrl
) {
streamUrl = track.remoteUrl; streamUrl = track.remoteUrl;
} }

View file

@ -11,7 +11,6 @@ export function navigate(path) {
export function createRouter(ui) { export function createRouter(ui) {
const router = async () => { const router = async () => {
if (window.location.hash && window.location.hash.length > 1) { if (window.location.hash && window.location.hash.length > 1) {
const hash = window.location.hash.substring(1); const hash = window.location.hash.substring(1);
if (hash.includes('/')) { if (hash.includes('/')) {

View file

@ -10,14 +10,20 @@ async function loadArtistsData() {
const response = await fetch('./artists.ndjson'); const response = await fetch('./artists.ndjson');
if (!response.ok) throw new Error('Network response was not ok'); if (!response.ok) throw new Error('Network response was not ok');
const text = await response.text(); const text = await response.text();
artistsData = text.trim().split('\n') artistsData = text
.filter(line => line.trim()) .trim()
.map(line => { .split('\n')
try { return JSON.parse(line); } catch (e) { return null; } .filter((line) => line.trim())
}) .map((line) => {
.filter(item => item !== null); try {
return JSON.parse(line);
} catch (e) { } catch (e) {
console.error("Failed to load Artists LIst:", e); return null;
}
})
.filter((item) => item !== null);
} catch (e) {
console.error('Failed to load Artists LIst:', e);
} }
} }
@ -29,11 +35,13 @@ function getSheetId(url) {
async function fetchTrackerData(sheetId) { async function fetchTrackerData(sheetId) {
try { try {
const response = await fetch(`https://corsproxy.io/?${encodeURIComponent(`https://tracker.israeli.ovh/get/${sheetId}`)}`); const response = await fetch(
`https://corsproxy.io/?${encodeURIComponent(`https://tracker.israeli.ovh/get/${sheetId}`)}`
);
if (!response.ok) return null; if (!response.ok) return null;
return await response.json(); return await response.json();
} catch (e) { } catch (e) {
console.error("Failed to fetch tracker data", e); console.error('Failed to fetch tracker data', e);
return null; return null;
} }
} }
@ -110,7 +118,7 @@ function renderTracker(trackerData, container, artistName) {
if (!trackerData.eras) return; if (!trackerData.eras) return;
Object.values(trackerData.eras).forEach(era => { Object.values(trackerData.eras).forEach((era) => {
const card = document.createElement('div'); const card = document.createElement('div');
card.className = 'card'; card.className = 'card';
card.style.cursor = 'pointer'; card.style.cursor = 'pointer';
@ -154,7 +162,6 @@ function showEraSongs(era, artistName) {
const overlay = modal.querySelector('.modal-overlay'); const overlay = modal.querySelector('.modal-overlay');
const closeBtn = document.getElementById('close-tracker-modal'); const closeBtn = document.getElementById('close-tracker-modal');
const img = document.getElementById('tracker-header-image'); const img = document.getElementById('tracker-header-image');
const title = document.getElementById('tracker-header-title'); const title = document.getElementById('tracker-header-title');
const meta = document.getElementById('tracker-header-meta'); const meta = document.getElementById('tracker-header-meta');
@ -178,7 +185,7 @@ function showEraSongs(era, artistName) {
{ label: 'Special', emoji: '✨' }, { label: 'Special', emoji: '✨' },
{ label: 'Grails', emoji: '🏆' }, { label: 'Grails', emoji: '🏆' },
{ label: 'Wanted', emoji: '🥇' }, { label: 'Wanted', emoji: '🥇' },
{ label: 'Worst Of', emoji: '🗑️' } { label: 'Worst Of', emoji: '🗑️' },
]; ];
let activeFilter = ''; let activeFilter = '';
@ -186,7 +193,7 @@ function showEraSongs(era, artistName) {
const applyFilter = () => { const applyFilter = () => {
const items = trackList.querySelectorAll('.track-item'); const items = trackList.querySelectorAll('.track-item');
items.forEach(item => { items.forEach((item) => {
const titleEl = item.querySelector('.title'); const titleEl = item.querySelector('.title');
if (titleEl) { if (titleEl) {
const title = titleEl.textContent.trim(); const title = titleEl.textContent.trim();
@ -199,11 +206,11 @@ function showEraSongs(era, artistName) {
}); });
const categories = trackList.querySelectorAll('h4'); const categories = trackList.querySelectorAll('h4');
categories.forEach(cat => { categories.forEach((cat) => {
let next = cat.nextElementSibling; let next = cat.nextElementSibling;
let hasVisibleItems = false; let hasVisibleItems = false;
while(next && next.tagName !== 'H4') { while (next && next.tagName !== 'H4') {
if (next.classList.contains('track-item') && next.style.display !== 'none') { if (next.classList.contains('track-item') && next.style.display !== 'none') {
hasVisibleItems = true; hasVisibleItems = true;
break; break;
@ -215,7 +222,7 @@ function showEraSongs(era, artistName) {
}); });
}; };
filters.forEach(filter => { filters.forEach((filter) => {
const btn = document.createElement('button'); const btn = document.createElement('button');
btn.className = 'btn-secondary'; btn.className = 'btn-secondary';
btn.textContent = filter.emoji ? `${filter.emoji} ${filter.label}` : filter.label; btn.textContent = filter.emoji ? `${filter.emoji} ${filter.label}` : filter.label;
@ -229,7 +236,7 @@ function showEraSongs(era, artistName) {
} }
btn.onclick = () => { btn.onclick = () => {
Array.from(filterContainer.children).forEach(b => { Array.from(filterContainer.children).forEach((b) => {
b.style.backgroundColor = ''; b.style.backgroundColor = '';
b.style.color = ''; b.style.color = '';
}); });
@ -260,7 +267,7 @@ function showEraSongs(era, artistName) {
const isValidUrl = (u) => u && typeof u === 'string' && u.trim().length > 0; const isValidUrl = (u) => u && typeof u === 'string' && u.trim().length > 0;
songs.forEach(song => { songs.forEach((song) => {
const trackItem = document.createElement('div'); const trackItem = document.createElement('div');
trackItem.className = 'track-item'; trackItem.className = 'track-item';
@ -287,27 +294,40 @@ function showEraSongs(era, artistName) {
e.preventDefault(); e.preventDefault();
const contextMenu = document.getElementById('context-menu'); const contextMenu = document.getElementById('context-menu');
if (contextMenu) { if (contextMenu) {
const rawUrl = (isValidUrl(song.url) ? song.url : null) || (song.urls ? song.urls.find(isValidUrl) : null); const rawUrl =
(isValidUrl(song.url) ? song.url : null) || (song.urls ? song.urls.find(isValidUrl) : null);
const directUrl = getDirectUrl(rawUrl); const directUrl = getDirectUrl(rawUrl);
const track = { const track = {
id: `tracker-${song.name}`, id: `tracker-${song.name}`,
title: song.name, title: song.name,
artist: { name: artistName || document.getElementById('artist-detail-name')?.textContent || 'Unknown Artist' }, artist: {
artists: [{ name: artistName || document.getElementById('artist-detail-name')?.textContent || 'Unknown Artist' }], name:
artistName ||
document.getElementById('artist-detail-name')?.textContent ||
'Unknown Artist',
},
artists: [
{
name:
artistName ||
document.getElementById('artist-detail-name')?.textContent ||
'Unknown Artist',
},
],
album: { album: {
title: era.name, title: era.name,
cover: era.image cover: era.image,
}, },
duration: parseDuration(song.track_length), duration: parseDuration(song.track_length),
isTracker: true, isTracker: true,
audioUrl: directUrl, audioUrl: directUrl,
remoteUrl: directUrl remoteUrl: directUrl,
}; };
contextMenu._contextTrack = track; contextMenu._contextTrack = track;
['go-to-album', 'go-to-artist', 'toggle-like', 'download', 'track-mix'].forEach(action => { ['go-to-album', 'go-to-artist', 'toggle-like', 'download', 'track-mix'].forEach((action) => {
const item = contextMenu.querySelector(`[data-action="${action}"]`); const item = contextMenu.querySelector(`[data-action="${action}"]`);
if (item) item.style.display = 'none'; if (item) item.style.display = 'none';
}); });
@ -326,7 +346,7 @@ function showEraSongs(era, artistName) {
if (hasValidUrl) { if (hasValidUrl) {
trackItem.onclick = async () => { trackItem.onclick = async () => {
if (song.track_length === '-') { if (song.track_length === '-') {
const targetUrl = (song.urls && song.urls.length > 0) ? song.urls[0] : song.url; const targetUrl = song.urls && song.urls.length > 0 ? song.urls[0] : song.url;
if (targetUrl) window.open(targetUrl, '_blank'); if (targetUrl) window.open(targetUrl, '_blank');
return; return;
} }
@ -337,7 +357,8 @@ function showEraSongs(era, artistName) {
trackItem.classList.add('loading'); trackItem.classList.add('loading');
const trackNumEl = trackItem.querySelector('.track-number'); const trackNumEl = trackItem.querySelector('.track-number');
const originalNum = trackNumEl.textContent; const originalNum = trackNumEl.textContent;
trackNumEl.innerHTML = '<svg class="animate-spin" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"></circle></svg>'; trackNumEl.innerHTML =
'<svg class="animate-spin" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"></circle></svg>';
let urlsToTry = []; let urlsToTry = [];
if (isValidUrl(song.url)) { if (isValidUrl(song.url)) {
@ -373,9 +394,11 @@ function showEraSongs(era, artistName) {
if (response.ok) { if (response.ok) {
const contentType = response.headers.get('content-type') || ''; const contentType = response.headers.get('content-type') || '';
if (contentType.includes('audio/') || if (
contentType.includes('audio/') ||
contentType.includes('mpeg') || contentType.includes('mpeg') ||
contentType.includes('octet-stream')) { contentType.includes('octet-stream')
) {
const arrayBuffer = await response.arrayBuffer(); const arrayBuffer = await response.arrayBuffer();
if (arrayBuffer.byteLength > 1000) { if (arrayBuffer.byteLength > 1000) {
const blob = new Blob([arrayBuffer], { type: 'audio/mpeg' }); const blob = new Blob([arrayBuffer], { type: 'audio/mpeg' });
@ -396,7 +419,9 @@ function showEraSongs(era, artistName) {
trackNumEl.textContent = originalNum; trackNumEl.textContent = originalNum;
if (!audioUrl) { if (!audioUrl) {
alert(`Unable to load this track! :( The source may be unavailable.\n\nTried ${urlsToTry.length} URL(s)`); alert(
`Unable to load this track! :( The source may be unavailable.\n\nTried ${urlsToTry.length} URL(s)`
);
return; return;
} }
@ -404,16 +429,31 @@ function showEraSongs(era, artistName) {
const track = { const track = {
id: `tracker-${song.name}`, id: `tracker-${song.name}`,
title: song.name, title: song.name,
artist: { name: artistName || document.getElementById('artist-detail-name')?.textContent || 'Unknown Artist' }, artist: {
artists: [{ name: artistName || document.getElementById('artist-detail-name')?.textContent || 'Unknown Artist' }], name:
artistName ||
document.getElementById('artist-detail-name')?.textContent ||
'Unknown Artist',
},
artists: [
{
name:
artistName ||
document.getElementById('artist-detail-name')?.textContent ||
'Unknown Artist',
},
],
album: { album: {
title: era.name, title: era.name,
cover: era.image cover: era.image,
}, },
duration: parseDuration(song.track_length), duration: parseDuration(song.track_length),
isTracker: true, isTracker: true,
audioUrl: audioUrl, audioUrl: audioUrl,
remoteUrl: successfulUrl || (urlsToTry.length > 0 ? getDirectUrl(urlsToTry[0]) : null) || getDirectUrl(song.url) remoteUrl:
successfulUrl ||
(urlsToTry.length > 0 ? getDirectUrl(urlsToTry[0]) : null) ||
getDirectUrl(song.url),
}; };
globalPlayer.setQueue([track], 0); globalPlayer.setQueue([track], 0);
@ -465,7 +505,7 @@ export async function initTracker(player, ui) {
trackerSection.innerHTML = ''; trackerSection.innerHTML = '';
trackerSection.style.display = 'none'; trackerSection.style.display = 'none';
const artistEntry = artistsData.find(a => a.name.toLowerCase() === artistName.toLowerCase()); const artistEntry = artistsData.find((a) => a.name.toLowerCase() === artistName.toLowerCase());
if (artistEntry && artistEntry.url) { if (artistEntry && artistEntry.url) {
const sheetId = getSheetId(artistEntry.url); const sheetId = getSheetId(artistEntry.url);

View file

@ -824,9 +824,11 @@ export class UIRenderer {
lastPausedState = isPaused; lastPausedState = isPaused;
if (isPaused) { if (isPaused) {
playBtn.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="5 3 19 12 5 21 5 3"></polygon></svg>'; playBtn.innerHTML =
'<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="5 3 19 12 5 21 5 3"></polygon></svg>';
} else { } else {
playBtn.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="6" y="4" width="4" height="16"></rect><rect x="14" y="4" width="4" height="16"></rect></svg>'; playBtn.innerHTML =
'<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="6" y="4" width="4" height="16"></rect><rect x="14" y="4" width="4" height="16"></rect></svg>';
} }
}; };
@ -849,9 +851,11 @@ export class UIRenderer {
const mode = this.player.toggleRepeat(); const mode = this.player.toggleRepeat();
repeatBtn.classList.toggle('active', mode !== 0); repeatBtn.classList.toggle('active', mode !== 0);
if (mode === 2) { if (mode === 2) {
repeatBtn.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m17 2 4 4-4 4"/><path d="M3 11v-1a4 4 0 0 1 4-4h14"/><path d="m7 22-4-4 4-4"/><path d="M21 13v1a4 4 0 0 1-4 4H3"/><path d="M11 10h1v4"/></svg>'; repeatBtn.innerHTML =
'<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m17 2 4 4-4 4"/><path d="M3 11v-1a4 4 0 0 1 4-4h14"/><path d="m7 22-4-4 4-4"/><path d="M21 13v1a4 4 0 0 1-4 4H3"/><path d="M11 10h1v4"/></svg>';
} else { } else {
repeatBtn.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m17 2 4 4-4 4"/><path d="M3 11v-1a4 4 0 0 1 4-4h14"/><path d="m7 22-4-4 4-4"/><path d="M21 13v1a4 4 0 0 1-4 4H3"/></svg>'; repeatBtn.innerHTML =
'<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m17 2 4 4-4 4"/><path d="M3 11v-1a4 4 0 0 1 4-4h14"/><path d="m7 22-4-4 4-4"/><path d="M21 13v1a4 4 0 0 1-4 4H3"/></svg>';
} }
}; };
@ -883,7 +887,8 @@ export class UIRenderer {
const mode = this.player.repeatMode; const mode = this.player.repeatMode;
repeatBtn.classList.toggle('active', mode !== 0); repeatBtn.classList.toggle('active', mode !== 0);
if (mode === 2) { if (mode === 2) {
repeatBtn.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m17 2 4 4-4 4"/><path d="M3 11v-1a4 4 0 0 1 4-4h14"/><path d="m7 22-4-4 4-4"/><path d="M21 13v1a4 4 0 0 1-4 4H3"/><path d="M11 10h1v4"/></svg>'; repeatBtn.innerHTML =
'<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m17 2 4 4-4 4"/><path d="M3 11v-1a4 4 0 0 1 4-4h14"/><path d="m7 22-4-4 4-4"/><path d="M21 13v1a4 4 0 0 1-4 4H3"/><path d="M11 10h1v4"/></svg>';
} }
const update = () => { const update = () => {
@ -913,7 +918,10 @@ export class UIRenderer {
}); });
document.querySelectorAll('.sidebar-nav a').forEach((link) => { document.querySelectorAll('.sidebar-nav a').forEach((link) => {
link.classList.toggle('active', link.pathname === `/${pageId}` || (pageId === 'home' && link.pathname === '/')); link.classList.toggle(
'active',
link.pathname === `/${pageId}` || (pageId === 'home' && link.pathname === '/')
);
}); });
document.querySelector('.main-content').scrollTop = 0; document.querySelector('.main-content').scrollTop = 0;
@ -1015,7 +1023,7 @@ export class UIRenderer {
if (tracks.length > 1) { if (tracks.length > 1) {
albumSection.style.display = 'block'; albumSection.style.display = 'block';
const otherTracks = tracks.filter(t => t.id !== track.id); const otherTracks = tracks.filter((t) => t.id !== track.id);
this.renderListWithTracks(albumTracksContainer, otherTracks, false); this.renderListWithTracks(albumTracksContainer, otherTracks, false);
} else { } else {
albumSection.style.display = 'none'; albumSection.style.display = 'none';
@ -1121,7 +1129,7 @@ export class UIRenderer {
const folders = await db.getFolders(); const folders = await db.getFolders();
if (foldersContainer) { if (foldersContainer) {
foldersContainer.innerHTML = folders.map(f => this.createFolderCardHTML(f)).join(''); foldersContainer.innerHTML = folders.map((f) => this.createFolderCardHTML(f)).join('');
foldersContainer.style.display = folders.length ? 'grid' : 'none'; foldersContainer.style.display = folders.length ? 'grid' : 'none';
} }
@ -1990,7 +1998,9 @@ export class UIRenderer {
} else if (sortType === 'added-oldest') { } else if (sortType === 'added-oldest') {
currentTracks = [...originalTracks].sort((a, b) => (a.addedAt || 0) - (b.addedAt || 0)); currentTracks = [...originalTracks].sort((a, b) => (a.addedAt || 0) - (b.addedAt || 0));
} else if (sortType === 'title') { } else if (sortType === 'title') {
currentTracks = [...originalTracks].sort((a, b) => (a.title || '').localeCompare(b.title || '')); currentTracks = [...originalTracks].sort((a, b) =>
(a.title || '').localeCompare(b.title || '')
);
} else if (sortType === 'artist') { } else if (sortType === 'artist') {
currentTracks = [...originalTracks].sort((a, b) => { currentTracks = [...originalTracks].sort((a, b) => {
const artistA = a.artist?.name || a.artists?.[0]?.name || ''; const artistA = a.artist?.name || a.artists?.[0]?.name || '';
@ -2015,7 +2025,13 @@ export class UIRenderer {
} }
// Render Actions (Shuffle, Edit, Delete, Share, Sort) // Render Actions (Shuffle, Edit, Delete, Share, Sort)
this.updatePlaylistHeaderActions(playlistData, !!ownedPlaylist, tracks, false, !!ownedPlaylist ? applySort : null); this.updatePlaylistHeaderActions(
playlistData,
!!ownedPlaylist,
tracks,
false,
ownedPlaylist ? applySort : null
);
playBtn.onclick = () => { playBtn.onclick = () => {
this.player.setQueue(currentTracks, 0); this.player.setQueue(currentTracks, 0);
@ -2137,7 +2153,9 @@ export class UIRenderer {
if (!folder) throw new Error('Folder not found'); if (!folder) throw new Error('Folder not found');
imageEl.src = folder.cover || '/assets/folder.png'; imageEl.src = folder.cover || '/assets/folder.png';
imageEl.onerror = () => { imageEl.src = '/assets/folder.png'; }; imageEl.onerror = () => {
imageEl.src = '/assets/folder.png';
};
imageEl.style.backgroundColor = ''; imageEl.style.backgroundColor = '';
titleEl.textContent = folder.name; titleEl.textContent = folder.name;
@ -2467,7 +2485,13 @@ export class UIRenderer {
const actionsDiv = document.getElementById('page-playlist').querySelector('.detail-header-actions'); const actionsDiv = document.getElementById('page-playlist').querySelector('.detail-header-actions');
// Cleanup existing dynamic buttons // Cleanup existing dynamic buttons
['shuffle-playlist-btn', 'edit-playlist-btn', 'delete-playlist-btn', 'share-playlist-btn', 'sort-playlist-btn'].forEach((id) => { [
'shuffle-playlist-btn',
'edit-playlist-btn',
'delete-playlist-btn',
'share-playlist-btn',
'sort-playlist-btn',
].forEach((id) => {
const btn = actionsDiv.querySelector(`#${id}`); const btn = actionsDiv.querySelector(`#${id}`);
if (btn) btn.remove(); if (btn) btn.remove();
}); });
@ -2776,7 +2800,8 @@ export class UIRenderer {
document.body.classList.add('sidebar-collapsed'); document.body.classList.add('sidebar-collapsed');
const toggleBtn = document.getElementById('sidebar-toggle'); const toggleBtn = document.getElementById('sidebar-toggle');
if (toggleBtn) { if (toggleBtn) {
toggleBtn.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m9 18 6-6-6-6"/></svg>'; toggleBtn.innerHTML =
'<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m9 18 6-6-6-6"/></svg>';
} }
const imageEl = document.getElementById('track-detail-image'); const imageEl = document.getElementById('track-detail-image');
@ -2893,7 +2918,7 @@ export class UIRenderer {
const tracks = albumData.tracks; const tracks = albumData.tracks;
if (tracks.length > 1) { if (tracks.length > 1) {
albumSection.style.display = 'block'; albumSection.style.display = 'block';
const otherTracks = tracks.filter(t => t.id != track.id); const otherTracks = tracks.filter((t) => t.id != track.id);
this.renderListWithTracks(albumTracksContainer, otherTracks, false, false, true); this.renderListWithTracks(albumTracksContainer, otherTracks, false, false, true);
} }
} catch (err) { } catch (err) {
@ -2901,14 +2926,17 @@ export class UIRenderer {
} }
} }
this.api.getRecommendedTracksForPlaylist([track], 5).then(similarTracks => { this.api
.getRecommendedTracksForPlaylist([track], 5)
.then((similarTracks) => {
if (similarTracks.length > 0) { if (similarTracks.length > 0) {
this.renderListWithTracks(similarTracksContainer, similarTracks, true); this.renderListWithTracks(similarTracksContainer, similarTracks, true);
similarSection.style.display = 'block'; similarSection.style.display = 'block';
} else { } else {
similarSection.style.display = 'none'; similarSection.style.display = 'none';
} }
}).catch(() => similarSection.style.display = 'none'); })
.catch(() => (similarSection.style.display = 'none'));
document.title = `${displayTitle} - ${artistName}`; document.title = `${displayTitle} - ${artistName}`;
} catch (e) { } catch (e) {

View file

@ -285,10 +285,14 @@ function resizeImageBlob(blob, size) {
ctx.imageSmoothingEnabled = true; ctx.imageSmoothingEnabled = true;
ctx.imageSmoothingQuality = 'high'; ctx.imageSmoothingQuality = 'high';
ctx.drawImage(img, 0, 0, size, size); ctx.drawImage(img, 0, 0, size, size);
canvas.toBlob((resizedBlob) => { canvas.toBlob(
(resizedBlob) => {
if (resizedBlob) resolve(resizedBlob); if (resizedBlob) resolve(resizedBlob);
else reject(new Error('Canvas toBlob failed')); else reject(new Error('Canvas toBlob failed'));
}, blob.type || 'image/jpeg', 0.9); },
blob.type || 'image/jpeg',
0.9
);
}; };
img.onerror = (e) => { img.onerror = (e) => {
URL.revokeObjectURL(url); URL.revokeObjectURL(url);
@ -320,7 +324,7 @@ export async function getCoverBlob(api, coverId) {
const supportedSizes = [80, 160, 320, 640, 1280]; const supportedSizes = [80, 160, 320, 640, 1280];
let fetchSize = 1280; let fetchSize = 1280;
const bestSize = supportedSizes.find(s => s >= requestedSize); const bestSize = supportedSizes.find((s) => s >= requestedSize);
if (bestSize) { if (bestSize) {
fetchSize = bestSize; fetchSize = bestSize;
} }

View file

@ -33,7 +33,7 @@ export class Visualizer {
this.source.connect(this.analyser); this.source.connect(this.analyser);
this.analyser.connect(this.audioContext.destination); this.analyser.connect(this.audioContext.destination);
} catch (e) { } catch (e) {
console.warn("Visualizer init failed (likely CORS or already connected):", e); console.warn('Visualizer init failed (likely CORS or already connected):', e);
} }
} }
@ -94,14 +94,11 @@ export class Visualizer {
for (let i = 0; i < 4; i++) bassSum += dataArray[i]; for (let i = 0; i < 4; i++) bassSum += dataArray[i];
const bass = bassSum / 4 / 255; const bass = bassSum / 4 / 255;
const intensity = bass * bass; const intensity = bass * bass;
this.energyAverage = this.energyAverage * 0.99 + intensity * 0.01; this.energyAverage = this.energyAverage * 0.99 + intensity * 0.01;
this.upbeatSmoother = this.upbeatSmoother * 0.92 + intensity * 0.08; this.upbeatSmoother = this.upbeatSmoother * 0.92 + intensity * 0.08;
if (visualizerSettings.isSmartIntensityEnabled()) { if (visualizerSettings.isSmartIntensityEnabled()) {
let target = 0.1; let target = 0.1;
if (this.energyAverage > 0.4) { if (this.energyAverage > 0.4) {
@ -113,18 +110,14 @@ export class Visualizer {
sensitivity = target; sensitivity = target;
} }
let threshold = 0.5; let threshold = 0.5;
if (this.energyAverage < 0.3) { if (this.energyAverage < 0.3) {
threshold = 0.5 + (0.3 - this.energyAverage) * 2; threshold = 0.5 + (0.3 - this.energyAverage) * 2;
} }
const now = Date.now(); const now = Date.now();
if (intensity > threshold) { if (intensity > threshold) {
if (intensity > this.lastIntensity + 0.05 && now - this.lastBeatTime > 50) { if (intensity > this.lastIntensity + 0.05 && now - this.lastBeatTime > 50) {
this.kick = 1.0; this.kick = 1.0;
this.lastBeatTime = now; this.lastBeatTime = now;
@ -145,7 +138,6 @@ export class Visualizer {
} }
this.lastIntensity = intensity; this.lastIntensity = intensity;
let shakeX = 0; let shakeX = 0;
let shakeY = 0; let shakeY = 0;
if (this.kick > 0.1) { if (this.kick > 0.1) {
@ -154,8 +146,8 @@ export class Visualizer {
shakeY = (Math.random() - 0.5) * shakeAmt; shakeY = (Math.random() - 0.5) * shakeAmt;
} }
const primaryColor = getComputedStyle(document.documentElement).getPropertyValue('--primary').trim() || '#ffffff'; const primaryColor =
getComputedStyle(document.documentElement).getPropertyValue('--primary').trim() || '#ffffff';
const particleCount = 180; const particleCount = 180;
if (this.particles.length !== particleCount) { if (this.particles.length !== particleCount) {
@ -167,7 +159,7 @@ export class Visualizer {
vx: (Math.random() - 0.5) * 2, vx: (Math.random() - 0.5) * 2,
vy: (Math.random() - 0.5) * 2, vy: (Math.random() - 0.5) * 2,
size: Math.random() * 3 + 1, size: Math.random() * 3 + 1,
baseSize: Math.random() * 3 + 1 baseSize: Math.random() * 3 + 1,
}); });
} }
} }
@ -181,8 +173,7 @@ export class Visualizer {
for (let i = 0; i < this.particles.length; i++) { for (let i = 0; i < this.particles.length; i++) {
let p = this.particles[i]; let p = this.particles[i];
const speedMult = 1 + intensity * 2 + this.kick * 8 * sensitivity;
const speedMult = 1 + (intensity * 2) + this.kick * 8 * sensitivity;
p.x += p.vx * speedMult; p.x += p.vx * speedMult;
p.y += p.vy * speedMult; p.y += p.vy * speedMult;
@ -196,9 +187,8 @@ export class Visualizer {
if (p.y < 0) p.y = h; if (p.y < 0) p.y = h;
if (p.y > h) p.y = 0; if (p.y > h) p.y = 0;
const size = p.baseSize * (1 + intensity * 0.5 + this.kick * 0.8 * sensitivity); const size = p.baseSize * (1 + intensity * 0.5 + this.kick * 0.8 * sensitivity);
ctx.globalAlpha = 0.4 + (intensity * 0.2) + this.kick * 0.15 * sensitivity; ctx.globalAlpha = 0.4 + intensity * 0.2 + this.kick * 0.15 * sensitivity;
ctx.beginPath(); ctx.beginPath();
ctx.arc(p.x, p.y, size, 0, Math.PI * 2); ctx.arc(p.x, p.y, size, 0, Math.PI * 2);
ctx.fill(); ctx.fill();
@ -207,7 +197,7 @@ export class Visualizer {
const p2 = this.particles[j]; const p2 = this.particles[j];
const dx = p.x - p2.x; const dx = p.x - p2.x;
const dy = p.y - p2.y; const dy = p.y - p2.y;
const distSq = dx*dx + dy*dy; const distSq = dx * dx + dy * dy;
const maxDist = 150 + intensity * 50 + this.kick * 50 * sensitivity; const maxDist = 150 + intensity * 50 + this.kick * 50 * sensitivity;
const maxDistSq = maxDist * maxDist; const maxDistSq = maxDist * maxDist;

View file

@ -2076,7 +2076,7 @@ input:checked + .slider::before {
.fullscreen-progress-container .progress-bar { .fullscreen-progress-container .progress-bar {
flex: 1; flex: 1;
height: 6px; height: 6px;
background: rgba(255, 255, 255, 0.2); background: rgb(255, 255, 255, 0.2);
border-radius: 3px; border-radius: 3px;
cursor: pointer; cursor: pointer;
position: relative; position: relative;
@ -2110,7 +2110,7 @@ input:checked + .slider::before {
} }
.fullscreen-buttons button:hover { .fullscreen-buttons button:hover {
background: rgba(255, 255, 255, 0.1); background: rgb(255, 255, 255, 0.1);
transform: scale(1.1); transform: scale(1.1);
} }
@ -3193,7 +3193,7 @@ img[src=''] {
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
padding: 6rem 2rem 2rem 2rem; padding: 6rem 2rem 2rem;
transition: flex 0.3s ease; transition: flex 0.3s ease;
} }
@ -4348,7 +4348,6 @@ body:has(#fullscreen-cover-overlay:not([style*='display: none'])) .now-playing-b
text-overflow: ellipsis; text-overflow: ellipsis;
} }
/* Genius i love genius brah!! */ /* Genius i love genius brah!! */
.genius-annotation-modal { .genius-annotation-modal {
position: fixed; position: fixed;
@ -4357,7 +4356,7 @@ body:has(#fullscreen-cover-overlay:not([style*='display: none'])) .now-playing-b
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
background: rgba(0, 0, 0, 0.6); background: rgb(0, 0, 0, 0.6);
backdrop-filter: blur(4px); backdrop-filter: blur(4px);
animation: fade-in 0.2s ease; animation: fade-in 0.2s ease;
} }
@ -4424,14 +4423,14 @@ body:has(#fullscreen-cover-overlay:not([style*='display: none'])) .now-playing-b
} }
.genius-annotated { .genius-annotated {
background-color: rgba(255, 255, 100, 0.1); background-color: rgb(255, 255, 100, 0.1);
cursor: pointer; cursor: pointer;
border-radius: 4px; border-radius: 4px;
transition: background-color 0.2s; transition: background-color 0.2s;
} }
.genius-annotated:hover { .genius-annotated:hover {
background-color: rgba(255, 255, 100, 0.2); background-color: rgb(255, 255, 100, 0.2);
} }
.genius-multi-start { .genius-multi-start {
@ -4491,7 +4490,7 @@ body.sidebar-collapsed #sidebar-toggle {
#page-track .detail-header-image { #page-track .detail-header-image {
width: 350px; width: 350px;
height: 350px; height: 350px;
box-shadow: 0 30px 60px rgba(0, 0, 0, 0.5); box-shadow: 0 30px 60px rgb(0, 0, 0, 0.5);
} }
#page-track .detail-header-info { #page-track .detail-header-info {
@ -4526,12 +4525,12 @@ body.sidebar-collapsed #sidebar-toggle {
width: 250px; width: 250px;
height: 250px; height: 250px;
} }
#page-track .detail-header-info .title { #page-track .detail-header-info .title {
font-size: 2rem; font-size: 2rem;
} }
} }
.tracker-modal .track-item.loading { .tracker-modal .track-item.loading {
opacity: 0.7; opacity: 0.7;
pointer-events: none; pointer-events: none;