style: auto-fix linting issues
This commit is contained in:
parent
82301e8a44
commit
bae0d0a170
19 changed files with 118 additions and 205 deletions
|
|
@ -19,7 +19,7 @@ export default [
|
||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
'no-unused-vars': ['warn', { argsIgnorePattern: '^_' }],
|
'no-unused-vars': ['warn', { argsIgnorePattern: '^_' }],
|
||||||
'no-console': ['warn', { allow: ['warn', 'error'] }],
|
'no-console': ['warn', { allow: ['log', 'warn', 'error'] }],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -152,7 +152,7 @@ export function initializeFirebaseSettingsUI() {
|
||||||
customFirebaseConfigContainer.classList.add('visible');
|
customFirebaseConfigContainer.classList.add('visible');
|
||||||
toggleFirebaseConfigBtn.textContent = 'Hide Custom Configuration';
|
toggleFirebaseConfigBtn.textContent = 'Hide Custom Configuration';
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch {
|
||||||
firebaseConfigInput.value = currentConfig;
|
firebaseConfigInput.value = currentConfig;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -180,7 +180,7 @@ export function initializeFirebaseSettingsUI() {
|
||||||
prompt('Copy this link:', link);
|
prompt('Copy this link:', link);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch {
|
||||||
alert('Invalid configuration found.');
|
alert('Invalid configuration found.');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import PocketBase from 'pocketbase';
|
||||||
import { db } from '../db.js';
|
import { db } from '../db.js';
|
||||||
import { authManager } from './auth.js';
|
import { authManager } from './auth.js';
|
||||||
|
|
||||||
|
|
@ -292,7 +293,9 @@ const syncManager = {
|
||||||
if (typeof extraData === 'string') {
|
if (typeof extraData === 'string') {
|
||||||
try {
|
try {
|
||||||
extraData = JSON.parse(extraData);
|
extraData = JSON.parse(extraData);
|
||||||
} catch (e) {}
|
} catch {
|
||||||
|
// Ignore
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!rawCover && extraData && typeof extraData === 'object') {
|
if (!rawCover && extraData && typeof extraData === 'object') {
|
||||||
|
|
|
||||||
18
js/app.js
18
js/app.js
|
|
@ -10,13 +10,7 @@ import {
|
||||||
import { UIRenderer } from './ui.js';
|
import { UIRenderer } from './ui.js';
|
||||||
import { Player } from './player.js';
|
import { Player } from './player.js';
|
||||||
import { LastFMScrobbler } from './lastfm.js';
|
import { LastFMScrobbler } from './lastfm.js';
|
||||||
import {
|
import { LyricsManager, openLyricsPanel, clearLyricsPanelSync } from './lyrics.js';
|
||||||
LyricsManager,
|
|
||||||
openLyricsPanel,
|
|
||||||
clearLyricsPanelSync,
|
|
||||||
renderLyricsInFullscreen,
|
|
||||||
clearFullscreenLyricsSync,
|
|
||||||
} from './lyrics.js';
|
|
||||||
import { createRouter, updateTabTitle } from './router.js';
|
import { createRouter, updateTabTitle } 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';
|
||||||
|
|
@ -176,7 +170,7 @@ function showOfflineNotification() {
|
||||||
document.body.appendChild(notification);
|
document.body.appendChild(notification);
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
notification.style.animation = 'slideOut 0.3s ease forwards';
|
notification.style.animation = 'slide-out 0.3s ease forwards';
|
||||||
setTimeout(() => notification.remove(), 300);
|
setTimeout(() => notification.remove(), 300);
|
||||||
}, 5000);
|
}, 5000);
|
||||||
}
|
}
|
||||||
|
|
@ -184,7 +178,7 @@ function showOfflineNotification() {
|
||||||
function hideOfflineNotification() {
|
function hideOfflineNotification() {
|
||||||
const notification = document.querySelector('.offline-notification');
|
const notification = document.querySelector('.offline-notification');
|
||||||
if (notification) {
|
if (notification) {
|
||||||
notification.style.animation = 'slideOut 0.3s ease forwards';
|
notification.style.animation = 'slide-out 0.3s ease forwards';
|
||||||
setTimeout(() => notification.remove(), 300);
|
setTimeout(() => notification.remove(), 300);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -398,7 +392,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
if (!userPlaylist) {
|
if (!userPlaylist) {
|
||||||
try {
|
try {
|
||||||
userPlaylist = await syncManager.getPublicPlaylist(playlistId);
|
userPlaylist = await syncManager.getPublicPlaylist(playlistId);
|
||||||
} catch (e) {
|
} catch {
|
||||||
// Not a public playlist
|
// Not a public playlist
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -461,7 +455,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
await syncManager.unpublishPlaylist(playlist.id);
|
await syncManager.unpublishPlaylist(playlist.id);
|
||||||
} catch (e) {
|
} catch {
|
||||||
// Ignore error if it wasn't public
|
// Ignore error if it wasn't public
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1292,7 +1286,7 @@ async function parseCSV(csvText, api, onProgress) {
|
||||||
const cleanTitle = (t) =>
|
const cleanTitle = (t) =>
|
||||||
t
|
t
|
||||||
.split(' - ')[0]
|
.split(' - ')[0]
|
||||||
.replace(/\s*[\(\[]feat\.?.*?[\)\]]/i, '')
|
.replace(/\s*[([]feat\.?.*?[)\]]/i, '')
|
||||||
.trim();
|
.trim();
|
||||||
const cleanedTitle = cleanTitle(trackTitle);
|
const cleanedTitle = cleanTitle(trackTitle);
|
||||||
const isTitleCleaned = cleanedTitle !== trackTitle;
|
const isTitleCleaned = cleanedTitle !== trackTitle;
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ export class APICache {
|
||||||
return cached.data;
|
return cached.data;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.debug('IndexedDB read error:', error);
|
console.log('IndexedDB read error:', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -83,7 +83,7 @@ export class APICache {
|
||||||
try {
|
try {
|
||||||
await this.setInIndexedDB(entry);
|
await this.setInIndexedDB(entry);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.debug('IndexedDB write error:', error);
|
console.log('IndexedDB write error:', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -163,7 +163,7 @@ export class APICache {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.debug('Failed to clear expired IndexedDB entries:', error);
|
console.log('Failed to clear expired IndexedDB entries:', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
4
js/db.js
4
js/db.js
|
|
@ -136,7 +136,7 @@ export class MusicDatabase {
|
||||||
try {
|
try {
|
||||||
const result = await this.performTransaction(storeName, 'readonly', (store) => store.get(id));
|
const result = await this.performTransaction(storeName, 'readonly', (store) => store.get(id));
|
||||||
return !!result;
|
return !!result;
|
||||||
} catch (e) {
|
} catch {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -316,12 +316,10 @@ export class MusicDatabase {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const transaction = db.transaction(storeName, 'readwrite');
|
const transaction = db.transaction(storeName, 'readwrite');
|
||||||
const store = transaction.objectStore(storeName);
|
const store = transaction.objectStore(storeName);
|
||||||
let hasChanges = false;
|
|
||||||
|
|
||||||
// force clear on first sync
|
// force clear on first sync
|
||||||
console.log(`Clearing ${storeName} to Make Sure Everythings Good`);
|
console.log(`Clearing ${storeName} to Make Sure Everythings Good`);
|
||||||
store.clear();
|
store.clear();
|
||||||
hasChanges = true;
|
|
||||||
|
|
||||||
itemsArray.forEach((item) => {
|
itemsArray.forEach((item) => {
|
||||||
if (item.id && typeof item.id === 'string' && !isNaN(item.id)) {
|
if (item.id && typeof item.id === 'string' && !isNaN(item.id)) {
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,7 @@ export function showNotification(message) {
|
||||||
|
|
||||||
// Auto remove
|
// Auto remove
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
notifEl.style.animation = 'slideOut 0.3s ease';
|
notifEl.style.animation = 'slide-out 0.3s ease forwards';
|
||||||
setTimeout(() => notifEl.remove(), 300);
|
setTimeout(() => notifEl.remove(), 300);
|
||||||
}, 1500);
|
}, 1500);
|
||||||
}
|
}
|
||||||
|
|
@ -162,7 +162,7 @@ function removeDownloadTask(trackId) {
|
||||||
if (!task) return;
|
if (!task) return;
|
||||||
|
|
||||||
const { taskEl } = task;
|
const { taskEl } = task;
|
||||||
taskEl.style.animation = 'slideOut 0.3s ease';
|
taskEl.style.animation = 'slide-out 0.3s ease forwards';
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
taskEl.remove();
|
taskEl.remove();
|
||||||
|
|
@ -179,7 +179,7 @@ function removeBulkDownloadTask(notifEl) {
|
||||||
const task = bulkDownloadTasks.get(notifEl);
|
const task = bulkDownloadTasks.get(notifEl);
|
||||||
if (!task) return;
|
if (!task) return;
|
||||||
|
|
||||||
notifEl.style.animation = 'slideOut 0.3s ease';
|
notifEl.style.animation = 'slide-out 0.3s ease forwards';
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
notifEl.remove();
|
notifEl.remove();
|
||||||
|
|
@ -225,6 +225,7 @@ async function downloadTrackBlob(track, quality, api, lyricsManager = null, sign
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle DASH streams (blob URLs)
|
// Handle DASH streams (blob URLs)
|
||||||
|
let blob;
|
||||||
if (streamUrl.startsWith('blob:')) {
|
if (streamUrl.startsWith('blob:')) {
|
||||||
try {
|
try {
|
||||||
const downloader = new DashDownloader();
|
const downloader = new DashDownloader();
|
||||||
|
|
@ -266,7 +267,7 @@ async function generateAndDownloadZip(zip, filename, notification, progressTotal
|
||||||
compression: 'STORE',
|
compression: 'STORE',
|
||||||
streamFiles: true,
|
streamFiles: true,
|
||||||
})
|
})
|
||||||
.on('data', (chunk, metadata) => {
|
.on('data', (chunk) => {
|
||||||
writable.write(chunk);
|
writable.write(chunk);
|
||||||
})
|
})
|
||||||
.on('error', (err) => {
|
.on('error', (err) => {
|
||||||
|
|
@ -364,7 +365,7 @@ async function downloadTracksToZip(
|
||||||
zip.file(`${folderName}/${lrcFilename}`, lrcContent);
|
zip.file(`${folderName}/${lrcFilename}`, lrcContent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch {
|
||||||
console.log('Could not add lyrics for:', trackTitle);
|
console.log('Could not add lyrics for:', trackTitle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -518,7 +519,7 @@ export async function downloadDiscography(artist, selectedReleases, api, quality
|
||||||
zip.file(`${fullFolderPath}/${lrcFilename}`, lrcContent);
|
zip.file(`${fullFolderPath}/${lrcFilename}`, lrcContent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch {
|
||||||
// Silent fail for lyrics in bulk
|
// Silent fail for lyrics in bulk
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -547,7 +548,7 @@ export async function downloadDiscography(artist, selectedReleases, api, quality
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function createBulkDownloadNotification(type, name, totalItems) {
|
function createBulkDownloadNotification(type, name, _totalItems) {
|
||||||
const container = createDownloadNotification();
|
const container = createDownloadNotification();
|
||||||
|
|
||||||
const notifEl = document.createElement('div');
|
const notifEl = document.createElement('div');
|
||||||
|
|
@ -608,7 +609,7 @@ function completeBulkDownload(notifEl, success = true, message = null) {
|
||||||
statusEl.style.color = '#10b981';
|
statusEl.style.color = '#10b981';
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
notifEl.style.animation = 'slideOut 0.3s ease';
|
notifEl.style.animation = 'slide-out 0.3s ease forwards';
|
||||||
setTimeout(() => notifEl.remove(), 300);
|
setTimeout(() => notifEl.remove(), 300);
|
||||||
}, 3000);
|
}, 3000);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -617,7 +618,7 @@ function completeBulkDownload(notifEl, success = true, message = null) {
|
||||||
statusEl.style.color = '#ef4444';
|
statusEl.style.color = '#ef4444';
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
notifEl.style.animation = 'slideOut 0.3s ease';
|
notifEl.style.animation = 'slide-out 0.3s ease forwards';
|
||||||
setTimeout(() => notifEl.remove(), 300);
|
setTimeout(() => notifEl.remove(), 300);
|
||||||
}, 5000);
|
}, 5000);
|
||||||
}
|
}
|
||||||
|
|
@ -660,7 +661,7 @@ export async function downloadTrackWithMetadata(track, quality, api, lyricsManag
|
||||||
ongoingDownloads.add(downloadKey);
|
ongoingDownloads.add(downloadKey);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { taskEl } = addDownloadTask(track.id, enrichedTrack, filename, api, controller);
|
addDownloadTask(track.id, enrichedTrack, filename, api, controller);
|
||||||
|
|
||||||
await api.downloadTrack(track.id, quality, filename, {
|
await api.downloadTrack(track.id, quality, filename, {
|
||||||
signal: controller.signal,
|
signal: controller.signal,
|
||||||
|
|
@ -678,7 +679,7 @@ export async function downloadTrackWithMetadata(track, quality, api, lyricsManag
|
||||||
if (lyricsData) {
|
if (lyricsData) {
|
||||||
lyricsManager.downloadLRC(lyricsData, track);
|
lyricsManager.downloadLRC(lyricsData, track);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch {
|
||||||
console.log('Could not download lyrics for track');
|
console.log('Could not download lyrics for track');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
22
js/events.js
22
js/events.js
|
|
@ -1,25 +1,13 @@
|
||||||
//js/events.js
|
//js/events.js
|
||||||
import {
|
import { SVG_PLAY, SVG_PAUSE, SVG_VOLUME, SVG_MUTE, REPEAT_MODE, trackDataStore, formatTime } from './utils.js';
|
||||||
SVG_PLAY,
|
|
||||||
SVG_PAUSE,
|
|
||||||
SVG_VOLUME,
|
|
||||||
SVG_MUTE,
|
|
||||||
REPEAT_MODE,
|
|
||||||
trackDataStore,
|
|
||||||
RATE_LIMIT_ERROR_MESSAGE,
|
|
||||||
buildTrackFilename,
|
|
||||||
getTrackTitle,
|
|
||||||
formatTime,
|
|
||||||
} from './utils.js';
|
|
||||||
import { lastFMStorage, waveformSettings } from './storage.js';
|
import { lastFMStorage, waveformSettings } from './storage.js';
|
||||||
import { showNotification, downloadTrackWithMetadata } from './downloads.js';
|
import { showNotification, downloadTrackWithMetadata } from './downloads.js';
|
||||||
import { lyricsSettings, downloadQualitySettings } from './storage.js';
|
import { downloadQualitySettings } from './storage.js';
|
||||||
import { updateTabTitle } from './router.js';
|
import { updateTabTitle } from './router.js';
|
||||||
import { db } from './db.js';
|
import { db } from './db.js';
|
||||||
import { syncManager } from './accounts/pocketbase.js';
|
import { syncManager } from './accounts/pocketbase.js';
|
||||||
import { waveformGenerator } from './waveform.js';
|
import { waveformGenerator } from './waveform.js';
|
||||||
|
|
||||||
let currentWaveformPeaks = null;
|
|
||||||
let currentTrackIdForWaveform = null;
|
let currentTrackIdForWaveform = null;
|
||||||
|
|
||||||
export function initializePlayerEvents(player, audioPlayer, scrobbler, ui) {
|
export function initializePlayerEvents(player, audioPlayer, scrobbler, ui) {
|
||||||
|
|
@ -396,7 +384,7 @@ function initializeSmoothSliders(audioPlayer, player) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
document.addEventListener('mouseup', (e) => {
|
document.addEventListener('mouseup', () => {
|
||||||
if (isSeeking) {
|
if (isSeeking) {
|
||||||
// Commit the seek
|
// Commit the seek
|
||||||
if (!isNaN(audioPlayer.duration)) {
|
if (!isNaN(audioPlayer.duration)) {
|
||||||
|
|
@ -412,7 +400,7 @@ function initializeSmoothSliders(audioPlayer, player) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
document.addEventListener('touchend', (e) => {
|
document.addEventListener('touchend', () => {
|
||||||
if (isSeeking) {
|
if (isSeeking) {
|
||||||
if (!isNaN(audioPlayer.duration)) {
|
if (!isNaN(audioPlayer.duration)) {
|
||||||
audioPlayer.currentTime = lastSeekPosition * audioPlayer.duration;
|
audioPlayer.currentTime = lastSeekPosition * audioPlayer.duration;
|
||||||
|
|
@ -562,7 +550,7 @@ export async function handleTrackAction(
|
||||||
if (!playlist) {
|
if (!playlist) {
|
||||||
try {
|
try {
|
||||||
playlist = await syncManager.getPublicPlaylist(item.id);
|
playlist = await syncManager.getPublicPlaylist(item.id);
|
||||||
} catch (e) {
|
} catch {
|
||||||
// Ignore
|
// Ignore
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
//js/lastfm.js
|
//js/lastfm.js
|
||||||
import { delay, getTrackArtists } from './utils.js';
|
|
||||||
|
|
||||||
export class LastFMScrobbler {
|
export class LastFMScrobbler {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
|
@ -66,7 +65,7 @@ export class LastFMScrobbler {
|
||||||
try {
|
try {
|
||||||
const { default: md5 } = await import('https://cdn.jsdelivr.net/npm/md5@2.3.0/+esm');
|
const { default: md5 } = await import('https://cdn.jsdelivr.net/npm/md5@2.3.0/+esm');
|
||||||
return md5(signatureString);
|
return md5(signatureString);
|
||||||
} catch (e) {
|
} catch {
|
||||||
console.error('MD5 library not available');
|
console.error('MD5 library not available');
|
||||||
throw new Error('MD5 library required for Last.fm');
|
throw new Error('MD5 library required for Last.fm');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
//js/lyrics.js
|
//js/lyrics.js
|
||||||
import { getTrackTitle, getTrackArtists, buildTrackFilename, SVG_DOWNLOAD, SVG_CLOSE } from './utils.js';
|
import { getTrackTitle, getTrackArtists, buildTrackFilename, SVG_CLOSE } from './utils.js';
|
||||||
import { sidePanelManager } from './side-panel.js';
|
import { sidePanelManager } from './side-panel.js';
|
||||||
|
|
||||||
// Dictionary path for kuromoji
|
// Dictionary path for kuromoji
|
||||||
// Using CDN - the kuroshiro-analyzer loaded from unpkg will use this as base for fetching dict files
|
// Using CDN - the kuroshiro-analyzer loaded from unpkg will use this as base for fetching dict files
|
||||||
const KUROMOJI_DICT_PATH = 'https://cdn.jsdelivr.net/npm/kuromoji@0.1.2/dict/';
|
|
||||||
|
|
||||||
export class LyricsManager {
|
export class LyricsManager {
|
||||||
constructor(api) {
|
constructor(api) {
|
||||||
|
|
@ -190,7 +189,7 @@ export class LyricsManager {
|
||||||
getRomajiMode() {
|
getRomajiMode() {
|
||||||
try {
|
try {
|
||||||
return localStorage.getItem('lyricsRomajiMode') === 'true';
|
return localStorage.getItem('lyricsRomajiMode') === 'true';
|
||||||
} catch (e) {
|
} catch {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -549,7 +548,7 @@ export async function openLyricsPanel(track, audioPlayer, lyricsManager) {
|
||||||
romajiBtn.addEventListener('click', async () => {
|
romajiBtn.addEventListener('click', async () => {
|
||||||
const amLyrics = sidePanelManager.panel.querySelector('am-lyrics');
|
const amLyrics = sidePanelManager.panel.querySelector('am-lyrics');
|
||||||
if (amLyrics) {
|
if (amLyrics) {
|
||||||
const newMode = await manager.toggleRomajiMode(amLyrics);
|
await manager.toggleRomajiMode(amLyrics);
|
||||||
updateRomajiBtn();
|
updateRomajiBtn();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -243,7 +243,6 @@ async function readMp3Metadata(file, metadata) {
|
||||||
|
|
||||||
if (file.size > 128) {
|
if (file.size > 128) {
|
||||||
const tailBuffer = await file.slice(file.size - 128).arrayBuffer();
|
const tailBuffer = await file.slice(file.size - 128).arrayBuffer();
|
||||||
const tailView = new DataView(tailBuffer);
|
|
||||||
const tag = new TextDecoder().decode(new Uint8Array(tailBuffer, 0, 3));
|
const tag = new TextDecoder().decode(new Uint8Array(tailBuffer, 0, 3));
|
||||||
if (tag === 'TAG') {
|
if (tag === 'TAG') {
|
||||||
const title = new TextDecoder()
|
const title = new TextDecoder()
|
||||||
|
|
@ -288,33 +287,6 @@ function readID3Text(view) {
|
||||||
return decoder.decode(buffer).replace(/\0/g, '');
|
return decoder.decode(buffer).replace(/\0/g, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
function readID3Picture(view) {
|
|
||||||
let offset = 1;
|
|
||||||
const encoding = view.getUint8(0);
|
|
||||||
|
|
||||||
let mimeEnd = offset;
|
|
||||||
while (view.getUint8(mimeEnd) !== 0) mimeEnd++;
|
|
||||||
const mime = new TextDecoder('iso-8859-1').decode(
|
|
||||||
view.buffer.slice(view.byteOffset + offset, view.byteOffset + mimeEnd)
|
|
||||||
);
|
|
||||||
offset = mimeEnd + 1;
|
|
||||||
|
|
||||||
const picType = view.getUint8(offset);
|
|
||||||
offset++;
|
|
||||||
|
|
||||||
let descEnd = offset;
|
|
||||||
while (
|
|
||||||
view.getUint8(descEnd) !== 0 ||
|
|
||||||
(encoding === 1 || encoding === 2 ? view.getUint8(descEnd + 1) !== 0 : false)
|
|
||||||
)
|
|
||||||
descEnd++;
|
|
||||||
offset = descEnd + (encoding === 1 || encoding === 2 ? 2 : 1);
|
|
||||||
|
|
||||||
const imgData = view.buffer.slice(view.byteOffset + offset, view.byteOffset + view.byteLength);
|
|
||||||
const blob = new Blob([imgData], { type: mime });
|
|
||||||
return URL.createObjectURL(blob);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds Vorbis comment metadata to FLAC files
|
* Adds Vorbis comment metadata to FLAC files
|
||||||
*/
|
*/
|
||||||
|
|
@ -438,7 +410,7 @@ function createVorbisCommentBlock(track) {
|
||||||
if (!isNaN(year)) {
|
if (!isNaN(year)) {
|
||||||
comments.push(['DATE', String(year)]);
|
comments.push(['DATE', String(year)]);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch {
|
||||||
// Invalid date, skip
|
// Invalid date, skip
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -787,7 +759,7 @@ function createMp4MetadataAtoms(track) {
|
||||||
if (!isNaN(year)) {
|
if (!isNaN(year)) {
|
||||||
tags['©day'] = String(year);
|
tags['©day'] = String(year);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch {
|
||||||
// Invalid date, skip
|
// Invalid date, skip
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -862,12 +834,6 @@ function rebuildMp4WithMetadata(dataView, atoms, metadataAtoms) {
|
||||||
|
|
||||||
// Write preserved children of moov
|
// Write preserved children of moov
|
||||||
for (const child of filteredMoovChildren) {
|
for (const child of filteredMoovChildren) {
|
||||||
const childStart = moovAtom.offset + 8 + child.offset; // child.offset is relative to moov body start in our parseMp4Atoms helper usage?
|
|
||||||
// Wait, parseMp4Atoms returns absolute offsets usually?
|
|
||||||
// Let's verify parseMp4Atoms usage.
|
|
||||||
// When we passed a slice DataView, the offsets returned by parseMp4Atoms
|
|
||||||
// are relative to the start of that DataView (which is moov body start).
|
|
||||||
|
|
||||||
const absoluteChildStart = moovAtom.offset + 8 + child.offset;
|
const absoluteChildStart = moovAtom.offset + 8 + child.offset;
|
||||||
newFile.set(originalArray.subarray(absoluteChildStart, absoluteChildStart + child.size), offset);
|
newFile.set(originalArray.subarray(absoluteChildStart, absoluteChildStart + child.size), offset);
|
||||||
offset += child.size;
|
offset += child.size;
|
||||||
|
|
|
||||||
|
|
@ -618,7 +618,7 @@ export class Player {
|
||||||
position: Math.min(this.audio.currentTime, duration),
|
position: Math.min(this.audio.currentTime, duration),
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.debug('Failed to update Media Session position:', error);
|
console.log('Failed to update Media Session position:', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,7 @@ export function initializeSettings(scrobbler, player, api, ui) {
|
||||||
authButtonsContainer.style.display = 'flex';
|
authButtonsContainer.style.display = 'flex';
|
||||||
emailInput.value = '';
|
emailInput.value = '';
|
||||||
passwordInput.value = '';
|
passwordInput.value = '';
|
||||||
} catch (e) {
|
} catch {
|
||||||
// Error handled in authManager
|
// Error handled in authManager
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -78,7 +78,7 @@ export function initializeSettings(scrobbler, player, api, ui) {
|
||||||
authButtonsContainer.style.display = 'flex';
|
authButtonsContainer.style.display = 'flex';
|
||||||
emailInput.value = '';
|
emailInput.value = '';
|
||||||
passwordInput.value = '';
|
passwordInput.value = '';
|
||||||
} catch (e) {
|
} catch {
|
||||||
// Error handled in authManager
|
// Error handled in authManager
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -165,7 +165,7 @@ export function initializeSettings(scrobbler, player, api, ui) {
|
||||||
lastfmToggle.checked = true;
|
lastfmToggle.checked = true;
|
||||||
alert(`Successfully connected to Last.fm as ${result.username}!`);
|
alert(`Successfully connected to Last.fm as ${result.username}!`);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch {
|
||||||
// Still waiting
|
// Still waiting
|
||||||
}
|
}
|
||||||
}, 2000);
|
}, 2000);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
//js/smooth-scrolling.js
|
//js/smooth-scrolling.js
|
||||||
|
/* global Lenis */
|
||||||
import { smoothScrollingSettings } from './storage.js';
|
import { smoothScrollingSettings } from './storage.js';
|
||||||
|
|
||||||
let lenis = null;
|
let lenis = null;
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ export const apiSettings = {
|
||||||
if (isSimpleArray) {
|
if (isSimpleArray) {
|
||||||
groupedInstances.api = [...data.api];
|
groupedInstances.api = [...data.api];
|
||||||
} else {
|
} else {
|
||||||
for (const [provider, config] of Object.entries(data.api)) {
|
for (const [, config] of Object.entries(data.api)) {
|
||||||
if (config.cors === false && Array.isArray(config.urls)) {
|
if (config.cors === false && Array.isArray(config.urls)) {
|
||||||
groupedInstances.api.push(...config.urls);
|
groupedInstances.api.push(...config.urls);
|
||||||
}
|
}
|
||||||
|
|
@ -122,7 +122,7 @@ export const apiSettings = {
|
||||||
}
|
}
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
} catch (e) {
|
} catch {
|
||||||
return { speeds: {}, timestamp: Date.now() };
|
return { speeds: {}, timestamp: Date.now() };
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -141,7 +141,7 @@ export const apiSettings = {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
localStorage.setItem(this.SPEED_TEST_CACHE_KEY, JSON.stringify(currentCache));
|
localStorage.setItem(this.SPEED_TEST_CACHE_KEY, JSON.stringify(currentCache));
|
||||||
} catch (e) {
|
} catch {
|
||||||
console.warn('[SpeedTest] Failed to cache results');
|
console.warn('[SpeedTest] Failed to cache results');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -253,7 +253,7 @@ export const recentActivityManager = {
|
||||||
if (!parsed.playlists) parsed.playlists = [];
|
if (!parsed.playlists) parsed.playlists = [];
|
||||||
if (!parsed.mixes) parsed.mixes = [];
|
if (!parsed.mixes) parsed.mixes = [];
|
||||||
return parsed;
|
return parsed;
|
||||||
} catch (e) {
|
} catch {
|
||||||
return { artists: [], albums: [], playlists: [], mixes: [] };
|
return { artists: [], albums: [], playlists: [], mixes: [] };
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -311,7 +311,7 @@ export const themeManager = {
|
||||||
getTheme() {
|
getTheme() {
|
||||||
try {
|
try {
|
||||||
return localStorage.getItem(this.STORAGE_KEY) || 'system';
|
return localStorage.getItem(this.STORAGE_KEY) || 'system';
|
||||||
} catch (e) {
|
} catch {
|
||||||
return 'system';
|
return 'system';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -343,7 +343,7 @@ export const themeManager = {
|
||||||
try {
|
try {
|
||||||
const stored = localStorage.getItem(this.CUSTOM_THEME_KEY);
|
const stored = localStorage.getItem(this.CUSTOM_THEME_KEY);
|
||||||
return stored ? JSON.parse(stored) : null;
|
return stored ? JSON.parse(stored) : null;
|
||||||
} catch (e) {
|
} catch {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -363,13 +363,10 @@ export const themeManager = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const lastFMStorage = {
|
export const lastFMStorage = {
|
||||||
STORAGE_KEY: 'lastfm-enabled',
|
|
||||||
LOVE_ON_LIKE_KEY: 'lastfm-love-on-like',
|
|
||||||
|
|
||||||
isEnabled() {
|
isEnabled() {
|
||||||
try {
|
try {
|
||||||
return localStorage.getItem(this.STORAGE_KEY) === 'true';
|
return localStorage.getItem(this.STORAGE_KEY) === 'true';
|
||||||
} catch (e) {
|
} catch {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -381,7 +378,7 @@ export const lastFMStorage = {
|
||||||
shouldLoveOnLike() {
|
shouldLoveOnLike() {
|
||||||
try {
|
try {
|
||||||
return localStorage.getItem(this.LOVE_ON_LIKE_KEY) === 'true';
|
return localStorage.getItem(this.LOVE_ON_LIKE_KEY) === 'true';
|
||||||
} catch (e) {
|
} catch {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -397,7 +394,7 @@ export const nowPlayingSettings = {
|
||||||
getMode() {
|
getMode() {
|
||||||
try {
|
try {
|
||||||
return localStorage.getItem(this.STORAGE_KEY) || 'cover';
|
return localStorage.getItem(this.STORAGE_KEY) || 'cover';
|
||||||
} catch (e) {
|
} catch {
|
||||||
return 'cover';
|
return 'cover';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -413,7 +410,7 @@ export const lyricsSettings = {
|
||||||
shouldDownloadLyrics() {
|
shouldDownloadLyrics() {
|
||||||
try {
|
try {
|
||||||
return localStorage.getItem(this.DOWNLOAD_WITH_TRACKS) === 'true';
|
return localStorage.getItem(this.DOWNLOAD_WITH_TRACKS) === 'true';
|
||||||
} catch (e) {
|
} catch {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -430,7 +427,7 @@ export const backgroundSettings = {
|
||||||
try {
|
try {
|
||||||
// Default to true if not set
|
// Default to true if not set
|
||||||
return localStorage.getItem(this.STORAGE_KEY) !== 'false';
|
return localStorage.getItem(this.STORAGE_KEY) !== 'false';
|
||||||
} catch (e) {
|
} catch {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -448,7 +445,7 @@ export const trackListSettings = {
|
||||||
const mode = localStorage.getItem(this.STORAGE_KEY) || 'dropdown';
|
const mode = localStorage.getItem(this.STORAGE_KEY) || 'dropdown';
|
||||||
document.documentElement.setAttribute('data-track-actions-mode', mode);
|
document.documentElement.setAttribute('data-track-actions-mode', mode);
|
||||||
return mode;
|
return mode;
|
||||||
} catch (e) {
|
} catch {
|
||||||
return 'dropdown';
|
return 'dropdown';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -467,7 +464,7 @@ export const cardSettings = {
|
||||||
try {
|
try {
|
||||||
const val = localStorage.getItem(this.COMPACT_ARTIST_KEY);
|
const val = localStorage.getItem(this.COMPACT_ARTIST_KEY);
|
||||||
return val === null ? true : val === 'true';
|
return val === null ? true : val === 'true';
|
||||||
} catch (e) {
|
} catch {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -479,7 +476,7 @@ export const cardSettings = {
|
||||||
isCompactAlbum() {
|
isCompactAlbum() {
|
||||||
try {
|
try {
|
||||||
return localStorage.getItem(this.COMPACT_ALBUM_KEY) === 'true';
|
return localStorage.getItem(this.COMPACT_ALBUM_KEY) === 'true';
|
||||||
} catch (e) {
|
} catch {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -512,7 +509,7 @@ export const downloadQualitySettings = {
|
||||||
getQuality() {
|
getQuality() {
|
||||||
try {
|
try {
|
||||||
return localStorage.getItem(this.STORAGE_KEY) || 'LOSSLESS';
|
return localStorage.getItem(this.STORAGE_KEY) || 'LOSSLESS';
|
||||||
} catch (e) {
|
} catch {
|
||||||
return 'LOSSLESS';
|
return 'LOSSLESS';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -527,7 +524,7 @@ export const waveformSettings = {
|
||||||
isEnabled() {
|
isEnabled() {
|
||||||
try {
|
try {
|
||||||
return localStorage.getItem(this.STORAGE_KEY) === 'true';
|
return localStorage.getItem(this.STORAGE_KEY) === 'true';
|
||||||
} catch (e) {
|
} catch {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -543,7 +540,7 @@ export const smoothScrollingSettings = {
|
||||||
isEnabled() {
|
isEnabled() {
|
||||||
try {
|
try {
|
||||||
return localStorage.getItem(this.STORAGE_KEY) === 'true';
|
return localStorage.getItem(this.STORAGE_KEY) === 'true';
|
||||||
} catch (e) {
|
} catch {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -560,7 +557,7 @@ export const queueManager = {
|
||||||
try {
|
try {
|
||||||
const data = localStorage.getItem(this.STORAGE_KEY);
|
const data = localStorage.getItem(this.STORAGE_KEY);
|
||||||
return data ? JSON.parse(data) : null;
|
return data ? JSON.parse(data) : null;
|
||||||
} catch (e) {
|
} catch {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ import {
|
||||||
SVG_HEART,
|
SVG_HEART,
|
||||||
SVG_DOWNLOAD,
|
SVG_DOWNLOAD,
|
||||||
formatTime,
|
formatTime,
|
||||||
trackDataStore,
|
|
||||||
getTrackTitle,
|
getTrackTitle,
|
||||||
getTrackArtists,
|
getTrackArtists,
|
||||||
escapeHtml,
|
escapeHtml,
|
||||||
|
|
@ -158,7 +157,7 @@ export function initializeUIInteractions(player, api) {
|
||||||
try {
|
try {
|
||||||
let addedCount = 0;
|
let addedCount = 0;
|
||||||
for (const track of currentQueue) {
|
for (const track of currentQueue) {
|
||||||
const playlist = await db.addTrackToPlaylist(playlistId, track);
|
await db.addTrackToPlaylist(playlistId, track);
|
||||||
addedCount++;
|
addedCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -302,7 +301,6 @@ export function initializeUIInteractions(player, api) {
|
||||||
trackMixItem.style.display = hasMix ? 'block' : 'none';
|
trackMixItem.style.display = hasMix ? 'block' : 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
const rect = item.getBoundingClientRect();
|
|
||||||
const menuWidth = 150;
|
const menuWidth = 150;
|
||||||
const menuHeight = 200;
|
const menuHeight = 200;
|
||||||
|
|
||||||
|
|
@ -325,7 +323,7 @@ export function initializeUIInteractions(player, api) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
item.addEventListener('dragstart', (e) => {
|
item.addEventListener('dragstart', () => {
|
||||||
draggedQueueIndex = index;
|
draggedQueueIndex = index;
|
||||||
item.style.opacity = '0.5';
|
item.style.opacity = '0.5';
|
||||||
});
|
});
|
||||||
|
|
|
||||||
7
js/ui.js
7
js/ui.js
|
|
@ -15,8 +15,9 @@ import {
|
||||||
escapeHtml,
|
escapeHtml,
|
||||||
} from './utils.js';
|
} from './utils.js';
|
||||||
import { openLyricsPanel } from './lyrics.js';
|
import { openLyricsPanel } from './lyrics.js';
|
||||||
import { recentActivityManager, backgroundSettings, trackListSettings, cardSettings } from './storage.js';
|
import { recentActivityManager, backgroundSettings, cardSettings } from './storage.js';
|
||||||
import { db } from './db.js';
|
import { db } from './db.js';
|
||||||
|
import { showNotification } from './downloads.js';
|
||||||
import { getVibrantColorFromImage } from './vibrant-color.js';
|
import { getVibrantColorFromImage } from './vibrant-color.js';
|
||||||
import { syncManager } from './accounts/pocketbase.js';
|
import { syncManager } from './accounts/pocketbase.js';
|
||||||
|
|
||||||
|
|
@ -68,7 +69,7 @@ export class UIRenderer {
|
||||||
this.vibrantColorCache.set(url, null);
|
this.vibrantColorCache.set(url, null);
|
||||||
this.resetVibrantColor();
|
this.resetVibrantColor();
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch {
|
||||||
this.vibrantColorCache.set(url, null);
|
this.vibrantColorCache.set(url, null);
|
||||||
this.resetVibrantColor();
|
this.resetVibrantColor();
|
||||||
}
|
}
|
||||||
|
|
@ -161,7 +162,6 @@ export class UIRenderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
createTrackItemHTML(track, index, showCover = false, hasMultipleDiscs = false) {
|
createTrackItemHTML(track, index, showCover = false, hasMultipleDiscs = false) {
|
||||||
const playIconSmall = SVG_PLAY;
|
|
||||||
const trackImageHTML = showCover
|
const trackImageHTML = showCover
|
||||||
? `<img src="${this.api.getCoverUrl(track.album?.cover)}" alt="Track Cover" class="track-item-cover" loading="lazy">`
|
? `<img src="${this.api.getCoverUrl(track.album?.cover)}" alt="Track Cover" class="track-item-cover" loading="lazy">`
|
||||||
: '';
|
: '';
|
||||||
|
|
@ -625,7 +625,6 @@ export class UIRenderer {
|
||||||
const title = document.getElementById('fullscreen-track-title');
|
const title = document.getElementById('fullscreen-track-title');
|
||||||
const artist = document.getElementById('fullscreen-track-artist');
|
const artist = document.getElementById('fullscreen-track-artist');
|
||||||
const nextTrackEl = document.getElementById('fullscreen-next-track');
|
const nextTrackEl = document.getElementById('fullscreen-next-track');
|
||||||
const lyricsContainer = document.getElementById('fullscreen-lyrics-container');
|
|
||||||
const lyricsToggleBtn = document.getElementById('toggle-fullscreen-lyrics-btn');
|
const lyricsToggleBtn = document.getElementById('toggle-fullscreen-lyrics-btn');
|
||||||
|
|
||||||
const coverUrl = this.api.getCoverUrl(track.album?.cover, '1280');
|
const coverUrl = this.api.getCoverUrl(track.album?.cover, '1280');
|
||||||
|
|
|
||||||
|
|
@ -288,7 +288,7 @@ export async function getCoverBlob(api, coverId) {
|
||||||
return blob;
|
return blob;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch {
|
||||||
// Network error (CORS rejection not handled by SW), try proxy
|
// Network error (CORS rejection not handled by SW), try proxy
|
||||||
const url = api.getCoverUrl(coverId, '1280');
|
const url = api.getCoverUrl(coverId, '1280');
|
||||||
const blob = await fetchWithProxy(url);
|
const blob = await fetchWithProxy(url);
|
||||||
|
|
|
||||||
128
styles.css
128
styles.css
|
|
@ -1,3 +1,4 @@
|
||||||
|
/* stylelint-disable no-descending-specificity */
|
||||||
:root {
|
:root {
|
||||||
color-scheme: light dark;
|
color-scheme: light dark;
|
||||||
|
|
||||||
|
|
@ -14,6 +15,8 @@
|
||||||
--shadow-lg: 0 10px 30px rgb(0, 0, 0, 0.5);
|
--shadow-lg: 0 10px 30px rgb(0, 0, 0, 0.5);
|
||||||
--shadow-xl: 0 20px 60px rgb(0, 0, 0, 0.8);
|
--shadow-xl: 0 20px 60px rgb(0, 0, 0, 0.8);
|
||||||
--cover-filter: blur(50px) brightness(0.4);
|
--cover-filter: blur(50px) brightness(0.4);
|
||||||
|
--player-bar-height-desktop: 90px;
|
||||||
|
--player-bar-height-mobile: 130px;
|
||||||
}
|
}
|
||||||
|
|
||||||
:root[data-theme='monochrome'] {
|
:root[data-theme='monochrome'] {
|
||||||
|
|
@ -408,6 +411,17 @@ kbd {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.search-bar svg {
|
||||||
|
position: absolute;
|
||||||
|
left: 0.75rem;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
color: var(--muted-foreground);
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
.sidebar-nav .nav-item a:hover {
|
.sidebar-nav .nav-item a:hover {
|
||||||
background-color: var(--secondary);
|
background-color: var(--secondary);
|
||||||
color: var(--foreground);
|
color: var(--foreground);
|
||||||
|
|
@ -489,19 +503,11 @@ kbd {
|
||||||
|
|
||||||
.search-bar {
|
.search-bar {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 80%;
|
||||||
max-width: 400px;
|
max-width: 100%;
|
||||||
}
|
margin-bottom: var(--spacing-xl);
|
||||||
|
display: flex;
|
||||||
.search-bar svg {
|
align-items: center;
|
||||||
position: absolute;
|
|
||||||
left: 0.75rem;
|
|
||||||
top: 50%;
|
|
||||||
transform: translateY(-50%);
|
|
||||||
color: var(--muted-foreground);
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-bar input {
|
.search-bar input {
|
||||||
|
|
@ -534,10 +540,10 @@ body.has-page-background .track-item:hover {
|
||||||
|
|
||||||
.page.active {
|
.page.active {
|
||||||
display: block;
|
display: block;
|
||||||
animation: fadeIn 0.25s cubic-bezier(0.4, 0, 0.2, 1);
|
animation: fade-in 0.25s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes fadeIn {
|
@keyframes fade-in {
|
||||||
from {
|
from {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transform: translateY(4px);
|
transform: translateY(4px);
|
||||||
|
|
@ -549,7 +555,7 @@ body.has-page-background .track-item:hover {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes scaleIn {
|
@keyframes scale-in {
|
||||||
from {
|
from {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transform: scale(0.95);
|
transform: scale(0.95);
|
||||||
|
|
@ -561,7 +567,7 @@ body.has-page-background .track-item:hover {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes slideIn {
|
@keyframes slide-in {
|
||||||
from {
|
from {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transform: translateX(100%);
|
transform: translateX(100%);
|
||||||
|
|
@ -573,7 +579,7 @@ body.has-page-background .track-item:hover {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes slideOut {
|
@keyframes slide-out {
|
||||||
from {
|
from {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
transform: translateX(0);
|
transform: translateX(0);
|
||||||
|
|
@ -1000,9 +1006,11 @@ body.has-page-background .track-item:hover {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
.track-item:hover .track-menu-btn {
|
.track-item:hover .track-menu-btn {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
padding: 0.5rem;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.track-menu-btn:hover {
|
.track-menu-btn:hover {
|
||||||
background-color: rgb(var(--highlight-rgb), 0.2);
|
background-color: rgb(var(--highlight-rgb), 0.2);
|
||||||
|
|
@ -1087,7 +1095,7 @@ body.has-page-background .track-item:hover {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
word-break: break-word;
|
overflow-wrap: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail-header-info .title.long-title {
|
.detail-header-info .title.long-title {
|
||||||
|
|
@ -1547,9 +1555,10 @@ input:checked + .slider::before {
|
||||||
|
|
||||||
.volume-controls {
|
.volume-controls {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: center !important;
|
||||||
align-items: center;
|
align-items: flex-end !important;
|
||||||
gap: 0.75rem;
|
gap: 0.5rem !important;
|
||||||
|
flex-direction: column !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.volume-controls button {
|
.volume-controls button {
|
||||||
|
|
@ -1721,15 +1730,13 @@ input:checked + .slider::before {
|
||||||
|
|
||||||
.fullscreen-cover-content {
|
.fullscreen-cover-content {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
||||||
/* Remove fixed padding to allow flex centering to work within the overlay's padded box */
|
|
||||||
padding: 1rem;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
|
padding: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
#close-fullscreen-cover-btn {
|
#close-fullscreen-cover-btn {
|
||||||
|
|
@ -1781,7 +1788,7 @@ input:checked + .slider::before {
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
margin-bottom: 0.5rem;
|
margin-bottom: 0.5rem;
|
||||||
color: var(--foreground);
|
color: var(--foreground);
|
||||||
word-break: break-word;
|
overflow-wrap: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
#fullscreen-track-artist {
|
#fullscreen-track-artist {
|
||||||
|
|
@ -1825,7 +1832,7 @@ input:checked + .slider::before {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
box-shadow: var(--shadow-xl);
|
box-shadow: var(--shadow-xl);
|
||||||
animation: scaleIn 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
animation: scale-in 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
#queue-modal-header {
|
#queue-modal-header {
|
||||||
|
|
@ -2317,7 +2324,7 @@ input:checked + .slider::before {
|
||||||
border-radius: var(--radius);
|
border-radius: var(--radius);
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
box-shadow: var(--shadow-lg);
|
box-shadow: var(--shadow-lg);
|
||||||
animation: slideIn 0.3s ease;
|
animation: slide-in 0.3s ease;
|
||||||
min-width: 300px;
|
min-width: 300px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2419,7 +2426,7 @@ input:checked + .slider::before {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
max-width: 350px;
|
max-width: 350px;
|
||||||
animation: slideIn 0.3s ease;
|
animation: slide-in 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.offline-notification svg {
|
.offline-notification svg {
|
||||||
|
|
@ -2511,11 +2518,6 @@ input:checked + .slider::before {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Side Panels (Lyrics & Queue) */
|
/* Side Panels (Lyrics & Queue) */
|
||||||
:root {
|
|
||||||
--player-bar-height-desktop: 90px;
|
|
||||||
--player-bar-height-mobile: 130px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.side-panel {
|
.side-panel {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
right: 0;
|
right: 0;
|
||||||
|
|
@ -2756,12 +2758,6 @@ input:checked + .slider::before {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Updated Volume Controls Layout */
|
/* Updated Volume Controls Layout */
|
||||||
.volume-controls {
|
|
||||||
flex-direction: column !important;
|
|
||||||
align-items: flex-end !important;
|
|
||||||
gap: 0.5rem !important;
|
|
||||||
justify-content: center !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.player-actions-row {
|
.player-actions-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
@ -2864,21 +2860,6 @@ img[src=''] {
|
||||||
padding: 1rem 0;
|
padding: 1rem 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-bar {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
width: 80%;
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fullscreen-cover-content {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fullscreen-main-view {
|
.fullscreen-main-view {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
@ -2945,7 +2926,7 @@ img[src=''] {
|
||||||
max-width: 400px;
|
max-width: 400px;
|
||||||
width: 90%;
|
width: 90%;
|
||||||
box-shadow: var(--shadow-xl);
|
box-shadow: var(--shadow-xl);
|
||||||
animation: scaleIn 0.2s ease;
|
animation: scale-in 0.2s ease;
|
||||||
max-height: 90vh;
|
max-height: 90vh;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
@ -3013,7 +2994,7 @@ img[src=''] {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes scaleIn {
|
@keyframes scale-in {
|
||||||
from {
|
from {
|
||||||
transform: scale(0.95);
|
transform: scale(0.95);
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
|
@ -3027,13 +3008,13 @@ img[src=''] {
|
||||||
|
|
||||||
#playlist-modal {
|
#playlist-modal {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
animation-name: fadeInOpacity;
|
animation-name: fade-in-opacity;
|
||||||
animation-iteration-count: 1;
|
animation-iteration-count: 1;
|
||||||
animation-timing-function: ease-in;
|
animation-timing-function: ease-in;
|
||||||
animation-duration: 0.1s;
|
animation-duration: 0.1s;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes fadeInOpacity {
|
@keyframes fade-in-opacity {
|
||||||
0% {
|
0% {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
@ -3056,7 +3037,7 @@ img[src=''] {
|
||||||
z-index: 10001;
|
z-index: 10001;
|
||||||
max-width: 400px;
|
max-width: 400px;
|
||||||
min-width: 350px;
|
min-width: 350px;
|
||||||
animation: slideIn 0.3s ease;
|
animation: slide-in 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.csv-import-progress .progress-header {
|
.csv-import-progress .progress-header {
|
||||||
|
|
@ -3349,6 +3330,9 @@ img[src=''] {
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.player-controls .progress-container {
|
.player-controls .progress-container {
|
||||||
order: -1;
|
order: -1;
|
||||||
|
max-width: none;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
gap: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
#fullscreen-cover-overlay {
|
#fullscreen-cover-overlay {
|
||||||
|
|
@ -3556,12 +3540,6 @@ img[src=''] {
|
||||||
height: 32px;
|
height: 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.player-controls .progress-container {
|
|
||||||
max-width: none;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.desktop-only {
|
.desktop-only {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
@ -3632,11 +3610,6 @@ img[src=''] {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.track-menu-btn {
|
|
||||||
padding: 0.5rem;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.track-menu-btn svg {
|
.track-menu-btn svg {
|
||||||
width: 18px;
|
width: 18px;
|
||||||
height: 18px;
|
height: 18px;
|
||||||
|
|
@ -3776,10 +3749,6 @@ img[src=''] {
|
||||||
.mobile-only {
|
.mobile-only {
|
||||||
display: flex !important;
|
display: flex !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.desktop-only {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 480px) {
|
@media (max-width: 480px) {
|
||||||
|
|
@ -3922,6 +3891,7 @@ img[src=''] {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* stylelint-disable-next-line media-feature-name-value-no-unknown */
|
||||||
@media (display-mode: window-controls-overlay) {
|
@media (display-mode: window-controls-overlay) {
|
||||||
.app-container {
|
.app-container {
|
||||||
margin-top: env(titlebar-area-height, 0);
|
margin-top: env(titlebar-area-height, 0);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue