scrobbling and skeleton changes
This commit is contained in:
parent
09a60753f1
commit
2522e0e5be
9 changed files with 224 additions and 90 deletions
172
index.html
172
index.html
|
|
@ -2396,41 +2396,40 @@
|
||||||
<div class="settings-group">
|
<div class="settings-group">
|
||||||
<div class="setting-item">
|
<div class="setting-item">
|
||||||
<div class="info">
|
<div class="info">
|
||||||
<span class="label">ListenBrainz Scrobbling</span>
|
<span class="label">Scrobble Threshold</span>
|
||||||
<span class="description"
|
<span class="description"
|
||||||
>Submit listens to ListenBrainz (requires User Token)</span
|
>Percentage of track to play before scrobbling (1-100%)</span
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<label class="toggle-switch">
|
<div style="display: flex; align-items: center; gap: 10px">
|
||||||
<input type="checkbox" id="listenbrainz-enabled-toggle" />
|
|
||||||
<span class="slider"></span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="setting-item" id="listenbrainz-token-setting" style="display: none">
|
|
||||||
<div class="info">
|
|
||||||
<span class="label">User Token</span>
|
|
||||||
<span class="description">Found on your ListenBrainz profile page</span>
|
|
||||||
</div>
|
|
||||||
<input
|
<input
|
||||||
type="password"
|
type="range"
|
||||||
id="listenbrainz-token-input"
|
id="scrobble-percentage-slider"
|
||||||
placeholder="Enter Token"
|
min="1"
|
||||||
class="template-input"
|
max="100"
|
||||||
style="width: 250px"
|
step="1"
|
||||||
|
value="75"
|
||||||
|
style="width: 100px"
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
<div class="setting-item" id="listenbrainz-custom-url-setting" style="display: none">
|
|
||||||
<div class="info">
|
|
||||||
<span class="label">Custom API URL (Optional)</span>
|
|
||||||
<span class="description">Leave empty to use official ListenBrainz server</span>
|
|
||||||
</div>
|
|
||||||
<input
|
<input
|
||||||
type="url"
|
type="number"
|
||||||
id="listenbrainz-custom-url-input"
|
id="scrobble-percentage-input"
|
||||||
placeholder="https://api.listenbrainz.org/1"
|
min="1"
|
||||||
class="template-input"
|
max="100"
|
||||||
style="width: 250px"
|
value="75"
|
||||||
|
style="
|
||||||
|
width: 50px;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
text-align: center;
|
||||||
|
padding: 4px;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: 4px;
|
||||||
|
background: var(--input-bg);
|
||||||
|
color: var(--text-color);
|
||||||
|
"
|
||||||
/>
|
/>
|
||||||
|
<span style="font-size: 0.9rem">%</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -2472,6 +2471,85 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="settings-group">
|
||||||
|
<div class="setting-item">
|
||||||
|
<div class="info">
|
||||||
|
<span class="label">Libre.fm Scrobbling</span>
|
||||||
|
<span class="description" id="librefm-status"
|
||||||
|
>Connect your Libre.fm account to scrobble tracks</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div id="librefm-controls">
|
||||||
|
<button id="librefm-connect-btn" class="btn-secondary">Connect Libre.fm</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="setting-item" id="librefm-toggle-setting" style="display: none">
|
||||||
|
<div class="info">
|
||||||
|
<span class="label">Enable Scrobbling</span>
|
||||||
|
<span class="description">Automatically scrobble played tracks</span>
|
||||||
|
</div>
|
||||||
|
<label class="toggle-switch">
|
||||||
|
<input type="checkbox" id="librefm-toggle" />
|
||||||
|
<span class="slider"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="setting-item" id="librefm-love-setting" style="display: none">
|
||||||
|
<div class="info">
|
||||||
|
<span class="label">Love on Like</span>
|
||||||
|
<span class="description"
|
||||||
|
>Automatically 'love' tracks on Libre.fm when you like them</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<label class="toggle-switch">
|
||||||
|
<input type="checkbox" id="librefm-love-toggle" />
|
||||||
|
<span class="slider"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="settings-group">
|
||||||
|
<div class="setting-item">
|
||||||
|
<div class="info">
|
||||||
|
<span class="label">ListenBrainz Scrobbling</span>
|
||||||
|
<span class="description"
|
||||||
|
>Submit listens to ListenBrainz (requires User Token)</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<label class="toggle-switch">
|
||||||
|
<input type="checkbox" id="listenbrainz-enabled-toggle" />
|
||||||
|
<span class="slider"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="setting-item" id="listenbrainz-token-setting" style="display: none">
|
||||||
|
<div class="info">
|
||||||
|
<span class="label">User Token</span>
|
||||||
|
<span class="description">Found on your ListenBrainz profile page</span>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
id="listenbrainz-token-input"
|
||||||
|
placeholder="Enter Token"
|
||||||
|
class="template-input"
|
||||||
|
style="width: 250px"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="setting-item" id="listenbrainz-custom-url-setting" style="display: none">
|
||||||
|
<div class="info">
|
||||||
|
<span class="label">Custom API URL (Optional)</span>
|
||||||
|
<span class="description">Leave empty to use official ListenBrainz server</span>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
type="url"
|
||||||
|
id="listenbrainz-custom-url-input"
|
||||||
|
placeholder="https://api.listenbrainz.org/1"
|
||||||
|
class="template-input"
|
||||||
|
style="width: 250px"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="settings-group">
|
<div class="settings-group">
|
||||||
<div class="setting-item">
|
<div class="setting-item">
|
||||||
<div class="info">
|
<div class="info">
|
||||||
|
|
@ -2510,44 +2588,6 @@
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="settings-group">
|
|
||||||
<div class="setting-item">
|
|
||||||
<div class="info">
|
|
||||||
<span class="label">Libre.fm Scrobbling</span>
|
|
||||||
<span class="description" id="librefm-status"
|
|
||||||
>Connect your Libre.fm account to scrobble tracks</span
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<div id="librefm-controls">
|
|
||||||
<button id="librefm-connect-btn" class="btn-secondary">Connect Libre.fm</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="setting-item" id="librefm-toggle-setting" style="display: none">
|
|
||||||
<div class="info">
|
|
||||||
<span class="label">Enable Scrobbling</span>
|
|
||||||
<span class="description">Automatically scrobble played tracks</span>
|
|
||||||
</div>
|
|
||||||
<label class="toggle-switch">
|
|
||||||
<input type="checkbox" id="librefm-toggle" />
|
|
||||||
<span class="slider"></span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="setting-item" id="librefm-love-setting" style="display: none">
|
|
||||||
<div class="info">
|
|
||||||
<span class="label">Love on Like</span>
|
|
||||||
<span class="description"
|
|
||||||
>Automatically 'love' tracks on Libre.fm when you like them</span
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<label class="toggle-switch">
|
|
||||||
<input type="checkbox" id="librefm-love-toggle" />
|
|
||||||
<span class="slider"></span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="settings-tab-content" id="settings-tab-audio">
|
<div class="settings-tab-content" id="settings-tab-audio">
|
||||||
|
|
|
||||||
12
js/lastfm.js
12
js/lastfm.js
|
|
@ -13,6 +13,7 @@ export class LastFMScrobbler {
|
||||||
this.scrobbleTimer = null;
|
this.scrobbleTimer = null;
|
||||||
this.scrobbleThreshold = 0;
|
this.scrobbleThreshold = 0;
|
||||||
this.hasScrobbled = false;
|
this.hasScrobbled = false;
|
||||||
|
this.isScrobbling = false;
|
||||||
|
|
||||||
this.loadSession();
|
this.loadSession();
|
||||||
}
|
}
|
||||||
|
|
@ -178,7 +179,11 @@ export class LastFMScrobbler {
|
||||||
if (!this.isAuthenticated()) return;
|
if (!this.isAuthenticated()) return;
|
||||||
|
|
||||||
this.currentTrack = track;
|
this.currentTrack = track;
|
||||||
|
// Only reset hasScrobbled if we're not currently in the middle of scrobbling
|
||||||
|
// to prevent race conditions that could cause double scrobbles
|
||||||
|
if (!this.isScrobbling) {
|
||||||
this.hasScrobbled = false;
|
this.hasScrobbled = false;
|
||||||
|
}
|
||||||
this.clearScrobbleTimer();
|
this.clearScrobbleTimer();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
@ -204,7 +209,8 @@ export class LastFMScrobbler {
|
||||||
|
|
||||||
console.log('Now playing updated:', scrobbleTitle);
|
console.log('Now playing updated:', scrobbleTitle);
|
||||||
|
|
||||||
this.scrobbleThreshold = Math.min(track.duration / 2, 240);
|
const scrobblePercentage = lastFMStorage.getScrobblePercentage() / 100;
|
||||||
|
this.scrobbleThreshold = Math.min(track.duration * scrobblePercentage, 240);
|
||||||
this.scheduleScrobble(this.scrobbleThreshold * 1000);
|
this.scheduleScrobble(this.scrobbleThreshold * 1000);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to update now playing:', error);
|
console.error('Failed to update now playing:', error);
|
||||||
|
|
@ -229,6 +235,8 @@ export class LastFMScrobbler {
|
||||||
async scrobbleCurrentTrack() {
|
async scrobbleCurrentTrack() {
|
||||||
if (!this.isAuthenticated() || !this.currentTrack || this.hasScrobbled) return;
|
if (!this.isAuthenticated() || !this.currentTrack || this.hasScrobbled) return;
|
||||||
|
|
||||||
|
this.isScrobbling = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const timestamp = Math.floor(Date.now() / 1000);
|
const timestamp = Math.floor(Date.now() / 1000);
|
||||||
const scrobbleTitle = this.currentTrack.cleanTitle || this.currentTrack.title;
|
const scrobbleTitle = this.currentTrack.cleanTitle || this.currentTrack.title;
|
||||||
|
|
@ -257,6 +265,8 @@ export class LastFMScrobbler {
|
||||||
console.log('Scrobbled:', this.currentTrack.cleanTitle || this.currentTrack.title);
|
console.log('Scrobbled:', this.currentTrack.cleanTitle || this.currentTrack.title);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to scrobble:', error);
|
console.error('Failed to scrobble:', error);
|
||||||
|
} finally {
|
||||||
|
this.isScrobbling = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { libreFmSettings } from './storage.js';
|
import { libreFmSettings, lastFMStorage } from './storage.js';
|
||||||
|
|
||||||
export class LibreFmScrobbler {
|
export class LibreFmScrobbler {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
|
@ -12,6 +12,7 @@ export class LibreFmScrobbler {
|
||||||
this.scrobbleTimer = null;
|
this.scrobbleTimer = null;
|
||||||
this.scrobbleThreshold = 0;
|
this.scrobbleThreshold = 0;
|
||||||
this.hasScrobbled = false;
|
this.hasScrobbled = false;
|
||||||
|
this.isScrobbling = false;
|
||||||
|
|
||||||
this.loadSession();
|
this.loadSession();
|
||||||
}
|
}
|
||||||
|
|
@ -174,7 +175,11 @@ export class LibreFmScrobbler {
|
||||||
if (!this.isAuthenticated()) return;
|
if (!this.isAuthenticated()) return;
|
||||||
|
|
||||||
this.currentTrack = track;
|
this.currentTrack = track;
|
||||||
|
// Only reset hasScrobbled if we're not currently in the middle of scrobbling
|
||||||
|
// to prevent race conditions that could cause double scrobbles
|
||||||
|
if (!this.isScrobbling) {
|
||||||
this.hasScrobbled = false;
|
this.hasScrobbled = false;
|
||||||
|
}
|
||||||
this.clearScrobbleTimer();
|
this.clearScrobbleTimer();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
@ -200,7 +205,8 @@ export class LibreFmScrobbler {
|
||||||
|
|
||||||
console.log('[Libre.fm] Now playing updated:', scrobbleTitle);
|
console.log('[Libre.fm] Now playing updated:', scrobbleTitle);
|
||||||
|
|
||||||
this.scrobbleThreshold = Math.min(track.duration / 2, 240);
|
const scrobblePercentage = lastFMStorage.getScrobblePercentage() / 100;
|
||||||
|
this.scrobbleThreshold = Math.min(track.duration * scrobblePercentage, 240);
|
||||||
this.scheduleScrobble(this.scrobbleThreshold * 1000);
|
this.scheduleScrobble(this.scrobbleThreshold * 1000);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[Libre.fm] Failed to update now playing:', error);
|
console.error('[Libre.fm] Failed to update now playing:', error);
|
||||||
|
|
@ -225,6 +231,8 @@ export class LibreFmScrobbler {
|
||||||
async scrobbleCurrentTrack() {
|
async scrobbleCurrentTrack() {
|
||||||
if (!this.isAuthenticated() || !this.currentTrack || this.hasScrobbled) return;
|
if (!this.isAuthenticated() || !this.currentTrack || this.hasScrobbled) return;
|
||||||
|
|
||||||
|
this.isScrobbling = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const timestamp = Math.floor(Date.now() / 1000);
|
const timestamp = Math.floor(Date.now() / 1000);
|
||||||
const scrobbleTitle = this.currentTrack.cleanTitle || this.currentTrack.title;
|
const scrobbleTitle = this.currentTrack.cleanTitle || this.currentTrack.title;
|
||||||
|
|
@ -253,6 +261,8 @@ export class LibreFmScrobbler {
|
||||||
console.log('[Libre.fm] Scrobbled:', this.currentTrack.cleanTitle || this.currentTrack.title);
|
console.log('[Libre.fm] Scrobbled:', this.currentTrack.cleanTitle || this.currentTrack.title);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[Libre.fm] Failed to scrobble:', error);
|
console.error('[Libre.fm] Failed to scrobble:', error);
|
||||||
|
} finally {
|
||||||
|
this.isScrobbling = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { listenBrainzSettings } from './storage.js';
|
import { listenBrainzSettings, lastFMStorage } from './storage.js';
|
||||||
|
|
||||||
export class ListenBrainzScrobbler {
|
export class ListenBrainzScrobbler {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
|
@ -7,6 +7,7 @@ export class ListenBrainzScrobbler {
|
||||||
this.scrobbleTimer = null;
|
this.scrobbleTimer = null;
|
||||||
this.scrobbleThreshold = 0;
|
this.scrobbleThreshold = 0;
|
||||||
this.hasScrobbled = false;
|
this.hasScrobbled = false;
|
||||||
|
this.isScrobbling = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
getApiUrl() {
|
getApiUrl() {
|
||||||
|
|
@ -120,12 +121,17 @@ export class ListenBrainzScrobbler {
|
||||||
if (!this.isEnabled()) return;
|
if (!this.isEnabled()) return;
|
||||||
|
|
||||||
this.currentTrack = track;
|
this.currentTrack = track;
|
||||||
|
// Only reset hasScrobbled if we're not currently in the middle of scrobbling
|
||||||
|
// to prevent race conditions that could cause double scrobbles
|
||||||
|
if (!this.isScrobbling) {
|
||||||
this.hasScrobbled = false;
|
this.hasScrobbled = false;
|
||||||
|
}
|
||||||
this.clearScrobbleTimer();
|
this.clearScrobbleTimer();
|
||||||
|
|
||||||
await this.submitListen('playing_now', track);
|
await this.submitListen('playing_now', track);
|
||||||
|
|
||||||
this.scrobbleThreshold = Math.min(track.duration / 2, 240);
|
const scrobblePercentage = lastFMStorage.getScrobblePercentage() / 100;
|
||||||
|
this.scrobbleThreshold = Math.min(track.duration * scrobblePercentage, 240);
|
||||||
this.scheduleScrobble(this.scrobbleThreshold * 1000);
|
this.scheduleScrobble(this.scrobbleThreshold * 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -146,9 +152,15 @@ export class ListenBrainzScrobbler {
|
||||||
async scrobbleCurrentTrack() {
|
async scrobbleCurrentTrack() {
|
||||||
if (!this.isEnabled() || !this.currentTrack || this.hasScrobbled) return;
|
if (!this.isEnabled() || !this.currentTrack || this.hasScrobbled) return;
|
||||||
|
|
||||||
|
this.isScrobbling = true;
|
||||||
|
|
||||||
|
try {
|
||||||
const timestamp = Math.floor(Date.now() / 1000);
|
const timestamp = Math.floor(Date.now() / 1000);
|
||||||
await this.submitListen('single', this.currentTrack, timestamp);
|
await this.submitListen('single', this.currentTrack, timestamp);
|
||||||
this.hasScrobbled = true;
|
this.hasScrobbled = true;
|
||||||
|
} finally {
|
||||||
|
this.isScrobbling = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onTrackChange(track) {
|
onTrackChange(track) {
|
||||||
|
|
|
||||||
15
js/maloja.js
15
js/maloja.js
|
|
@ -1,4 +1,5 @@
|
||||||
import { malojaSettings } from './storage.js';
|
import { malojaSettings } from './storage.js';
|
||||||
|
import { lastFMStorage } from './storage.js';
|
||||||
|
|
||||||
export class MalojaScrobbler {
|
export class MalojaScrobbler {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
|
@ -6,6 +7,7 @@ export class MalojaScrobbler {
|
||||||
this.scrobbleTimer = null;
|
this.scrobbleTimer = null;
|
||||||
this.scrobbleThreshold = 0;
|
this.scrobbleThreshold = 0;
|
||||||
this.hasScrobbled = false;
|
this.hasScrobbled = false;
|
||||||
|
this.isScrobbling = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
getApiUrl() {
|
getApiUrl() {
|
||||||
|
|
@ -115,14 +117,19 @@ export class MalojaScrobbler {
|
||||||
if (!this.isEnabled()) return;
|
if (!this.isEnabled()) return;
|
||||||
|
|
||||||
this.currentTrack = track;
|
this.currentTrack = track;
|
||||||
|
// Only reset hasScrobbled if we're not currently in the middle of scrobbling
|
||||||
|
// to prevent race conditions that could cause double scrobbles
|
||||||
|
if (!this.isScrobbling) {
|
||||||
this.hasScrobbled = false;
|
this.hasScrobbled = false;
|
||||||
|
}
|
||||||
this.clearScrobbleTimer();
|
this.clearScrobbleTimer();
|
||||||
|
|
||||||
// Maloja doesn't have a separate "now playing" endpoint like Last.fm
|
// Maloja doesn't have a separate "now playing" endpoint like Last.fm
|
||||||
// It just scrobbles when the track is actually played
|
// It just scrobbles when the track is actually played
|
||||||
// We'll set up the timer to scrobble after the threshold
|
// We'll set up the timer to scrobble after the threshold
|
||||||
|
|
||||||
this.scrobbleThreshold = Math.min(track.duration / 2, 240);
|
const scrobblePercentage = lastFMStorage.getScrobblePercentage() / 100;
|
||||||
|
this.scrobbleThreshold = Math.min(track.duration * scrobblePercentage, 240);
|
||||||
this.scheduleScrobble(this.scrobbleThreshold * 1000);
|
this.scheduleScrobble(this.scrobbleThreshold * 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -143,9 +150,15 @@ export class MalojaScrobbler {
|
||||||
async scrobbleCurrentTrack() {
|
async scrobbleCurrentTrack() {
|
||||||
if (!this.isEnabled() || !this.currentTrack || this.hasScrobbled) return;
|
if (!this.isEnabled() || !this.currentTrack || this.hasScrobbled) return;
|
||||||
|
|
||||||
|
this.isScrobbling = true;
|
||||||
|
|
||||||
|
try {
|
||||||
const timestamp = Math.floor(Date.now() / 1000);
|
const timestamp = Math.floor(Date.now() / 1000);
|
||||||
await this.submitScrobble(this.currentTrack, timestamp);
|
await this.submitScrobble(this.currentTrack, timestamp);
|
||||||
this.hasScrobbled = true;
|
this.hasScrobbled = true;
|
||||||
|
} finally {
|
||||||
|
this.isScrobbling = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onTrackChange(track) {
|
onTrackChange(track) {
|
||||||
|
|
|
||||||
|
|
@ -220,6 +220,40 @@ export function initializeSettings(scrobbler, player, api, ui) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// Global Scrobble Settings
|
||||||
|
// ========================================
|
||||||
|
const scrobblePercentageSlider = document.getElementById('scrobble-percentage-slider');
|
||||||
|
const scrobblePercentageInput = document.getElementById('scrobble-percentage-input');
|
||||||
|
|
||||||
|
if (scrobblePercentageSlider && scrobblePercentageInput) {
|
||||||
|
const percentage = lastFMStorage.getScrobblePercentage();
|
||||||
|
scrobblePercentageSlider.value = percentage;
|
||||||
|
scrobblePercentageInput.value = percentage;
|
||||||
|
|
||||||
|
scrobblePercentageSlider.addEventListener('input', (e) => {
|
||||||
|
const newPercentage = parseInt(e.target.value, 10);
|
||||||
|
scrobblePercentageInput.value = newPercentage;
|
||||||
|
lastFMStorage.setScrobblePercentage(newPercentage);
|
||||||
|
});
|
||||||
|
|
||||||
|
scrobblePercentageInput.addEventListener('change', (e) => {
|
||||||
|
let newPercentage = parseInt(e.target.value, 10);
|
||||||
|
newPercentage = Math.max(1, Math.min(100, newPercentage || 75));
|
||||||
|
scrobblePercentageSlider.value = newPercentage;
|
||||||
|
scrobblePercentageInput.value = newPercentage;
|
||||||
|
lastFMStorage.setScrobblePercentage(newPercentage);
|
||||||
|
});
|
||||||
|
|
||||||
|
scrobblePercentageInput.addEventListener('input', (e) => {
|
||||||
|
let newPercentage = parseInt(e.target.value, 10);
|
||||||
|
if (!isNaN(newPercentage) && newPercentage >= 1 && newPercentage <= 100) {
|
||||||
|
scrobblePercentageSlider.value = newPercentage;
|
||||||
|
lastFMStorage.setScrobblePercentage(newPercentage);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
// ListenBrainz Settings
|
// ListenBrainz Settings
|
||||||
// ========================================
|
// ========================================
|
||||||
|
|
|
||||||
|
|
@ -279,6 +279,10 @@ export const themeManager = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const lastFMStorage = {
|
export const lastFMStorage = {
|
||||||
|
STORAGE_KEY: 'lastfm-enabled',
|
||||||
|
LOVE_ON_LIKE_KEY: 'lastfm-love-on-like',
|
||||||
|
SCROBBLE_PERCENTAGE_KEY: 'lastfm-scrobble-percentage',
|
||||||
|
|
||||||
isEnabled() {
|
isEnabled() {
|
||||||
try {
|
try {
|
||||||
return localStorage.getItem(this.STORAGE_KEY) === 'true';
|
return localStorage.getItem(this.STORAGE_KEY) === 'true';
|
||||||
|
|
@ -302,6 +306,20 @@ export const lastFMStorage = {
|
||||||
setLoveOnLike(enabled) {
|
setLoveOnLike(enabled) {
|
||||||
localStorage.setItem(this.LOVE_ON_LIKE_KEY, enabled ? 'true' : 'false');
|
localStorage.setItem(this.LOVE_ON_LIKE_KEY, enabled ? 'true' : 'false');
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getScrobblePercentage() {
|
||||||
|
try {
|
||||||
|
const value = localStorage.getItem(this.SCROBBLE_PERCENTAGE_KEY);
|
||||||
|
return value ? parseInt(value, 10) : 75;
|
||||||
|
} catch {
|
||||||
|
return 75;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
setScrobblePercentage(percentage) {
|
||||||
|
const validPercentage = Math.max(1, Math.min(100, parseInt(percentage, 10) || 75));
|
||||||
|
localStorage.setItem(this.SCROBBLE_PERCENTAGE_KEY, validPercentage.toString());
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const nowPlayingSettings = {
|
export const nowPlayingSettings = {
|
||||||
|
|
|
||||||
4
js/ui.js
4
js/ui.js
|
|
@ -566,10 +566,10 @@ export class UIRenderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
createSkeletonCards(count = 6, isArtist = false) {
|
createSkeletonCards(count = 6, isArtist = false) {
|
||||||
return `<div class="card-grid">${Array(count)
|
return Array(count)
|
||||||
.fill(0)
|
.fill(0)
|
||||||
.map(() => this.createSkeletonCard(isArtist))
|
.map(() => this.createSkeletonCard(isArtist))
|
||||||
.join('')}</div>`;
|
.join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
setupSearchClearButton(inputElement, clearBtnSelector = '.search-clear-btn') {
|
setupSearchClearButton(inputElement, clearBtnSelector = '.search-clear-btn') {
|
||||||
|
|
|
||||||
3
todo.md
3
todo.md
|
|
@ -2,9 +2,6 @@
|
||||||
|
|
||||||
Sorted by ease of implementation (easiest to hardest):
|
Sorted by ease of implementation (easiest to hardest):
|
||||||
|
|
||||||
- [x] Album click navigation: Clicking on the album in the player bar navigates to the album page (default behavior - can be changed in settings)
|
|
||||||
- [ ] Fix button overlap: Next track and casting buttons overlap on some screen resolutions
|
|
||||||
- [ ] Reduce API calls: Minimize unnecessary API calls throughout the app
|
|
||||||
- [ ] Editor's Picks: Create a JSON file of curated album IDs with metadata for the home screen. Include an option to disable in settings to avoid extra API calls.
|
- [ ] Editor's Picks: Create a JSON file of curated album IDs with metadata for the home screen. Include an option to disable in settings to avoid extra API calls.
|
||||||
|
|
||||||
- [ ] Update notifications: Add ability to show the update popup in settings, with an option to automatically update (enabled by default)
|
- [ ] Update notifications: Add ability to show the update popup in settings, with an option to automatically update (enabled by default)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue