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