reorganize settings menu, add playback speed reset and instances tab

This commit is contained in:
Julien Maille 2026-03-08 18:34:27 +01:00
parent b9bbbb6820
commit 473d63c899
4 changed files with 227 additions and 113 deletions

View file

@ -3428,6 +3428,7 @@
<button class="settings-tab" data-tab="scrobbling">Scrobbling</button>
<button class="settings-tab" data-tab="audio">Audio</button>
<button class="settings-tab" data-tab="downloads">Downloads</button>
<button class="settings-tab" data-tab="instances">Instances</button>
<button class="settings-tab" data-tab="system">System</button>
</div>
<div class="settings-tab-content active" id="settings-tab-appearance">
@ -3665,6 +3666,9 @@
<span class="slider"></span>
</label>
</div>
</div>
<div class="settings-group">
<div class="setting-item">
<div class="info">
<span class="label">Full-screen Visualizer</span>
@ -3868,6 +3872,29 @@
</div>
</div>
<div class="settings-group">
<div class="setting-item">
<div class="info">
<span class="label">Compact Artists</span>
<span class="description">Show artist cards in a compact, horizontal layout</span>
</div>
<label class="toggle-switch">
<input type="checkbox" id="compact-artist-toggle" />
<span class="slider"></span>
</label>
</div>
<div class="setting-item">
<div class="info">
<span class="label">Compact Albums</span>
<span class="description">Show album cards in a compact, horizontal layout</span>
</div>
<label class="toggle-switch">
<input type="checkbox" id="compact-album-toggle" />
<span class="slider"></span>
</label>
</div>
</div>
<div class="settings-group" id="sidebar-order-settings-group">
<div class="sidebar-settings-section sidebar-settings-main">
<span class="sidebar-settings-section-label">TOP SECTION</span>
@ -4502,43 +4529,6 @@
<option value="LOW">AAC 96kbps</option>
</select>
</div>
<div class="setting-item">
<div class="info">
<span class="label">Download Quality</span>
<span class="description">Quality for track downloads</span>
</div>
<select id="download-quality-setting">
<option value="HI_RES_LOSSLESS">Hi-Res FLAC (24-bit)</option>
<option value="LOSSLESS">FLAC (Lossless)</option>
<option value="MP3_320">MP3 320kbps</option>
<option value="HIGH">AAC 320kbps</option>
<option value="LOW">AAC 96kbps</option>
</select>
</div>
<div class="setting-item">
<div class="info">
<span class="label">Lossless Container</span>
<span class="description">Container format for lossless downloads</span>
</div>
<select id="lossless-container-setting">
<option value="flac">FLAC</option>
<option value="alac">Apple Lossless</option>
<option value="nochange">Don't change</option>
</select>
</div>
<div class="setting-item">
<div class="info">
<span class="label">Cover Art Size</span>
<span class="description">Size for downloaded/embedded cover art</span>
</div>
<input
type="text"
id="cover-art-size-setting"
class="template-input"
style="width: 120px; text-align: right"
placeholder="1280x1280"
/>
</div>
<div class="setting-item">
<div class="info">
<span class="label">Show Quality Badges</span>
@ -4571,6 +4561,9 @@
<span class="slider"></span>
</label>
</div>
</div>
<div class="settings-group">
<div class="setting-item">
<div class="info">
<span class="label">ReplayGain Mode</span>
@ -4624,7 +4617,7 @@
<span class="label">Playback Speed</span>
<span class="description">Adjust playback speed (0.01x - 100x)</span>
</div>
<div style="display: flex; align-items: center; gap: 12px">
<div class="playback-speed-control">
<input
type="range"
id="playback-speed-slider"
@ -4632,7 +4625,7 @@
max="4.0"
step="0.01"
value="1.0"
style="width: 150px"
class="playback-speed-slider"
/>
<input
type="number"
@ -4641,18 +4634,12 @@
max="100"
step="0.01"
value="1.0"
style="
width: 80px;
text-align: center;
font-family: var(--font-family);
padding: 4px 8px;
border: 1px solid var(--border-color);
border-radius: 4px;
background: var(--bg-secondary);
color: var(--text-primary);
"
class="playback-speed-number-input"
/>
<span style="font-family: var(--font-family)">x</span>
<span class="playback-speed-unit">x</span>
<button id="playback-speed-reset" class="btn-secondary" title="Reset to default">
Reset
</button>
</div>
</div>
@ -4924,32 +4911,10 @@
</div>
</div>
</div>
<div class="settings-group">
<div class="info">
<span class="label">Compact Artists</span>
<span class="description">Show artist cards in a compact, horizontal layout</span>
</div>
<label class="toggle-switch">
<input type="checkbox" id="compact-artist-toggle" />
<span class="slider"></span>
</label>
</div>
<div class="setting-item">
<div class="info">
<span class="label">Compact Albums</span>
<span class="description">Show album cards in a compact, horizontal layout</span>
</div>
<label class="toggle-switch">
<input type="checkbox" id="compact-album-toggle" />
<span class="slider"></span>
</label>
</div>
</div>
</div>
</div>
<div class="settings-tab-content" id="settings-tab-downloads">
<div class="settings-list">
<div class="settings-tab-content" id="settings-tab-downloads">
<div class="settings-list">
<div class="settings-group">
<div class="setting-item">
<div class="info">
@ -4985,6 +4950,43 @@
<span class="slider"></span>
</label>
</div>
<div class="setting-item">
<div class="info">
<span class="label">Download Quality</span>
<span class="description">Quality for track downloads</span>
</div>
<select id="download-quality-setting">
<option value="HI_RES_LOSSLESS">Hi-Res FLAC (24-bit)</option>
<option value="LOSSLESS">FLAC (Lossless)</option>
<option value="MP3_320">MP3 320kbps</option>
<option value="HIGH">AAC 320kbps</option>
<option value="LOW">AAC 96kbps</option>
</select>
</div>
<div class="setting-item">
<div class="info">
<span class="label">Lossless Container</span>
<span class="description">Container format for lossless downloads</span>
</div>
<select id="lossless-container-setting">
<option value="flac">FLAC</option>
<option value="alac">Apple Lossless</option>
<option value="nochange">Don't change</option>
</select>
</div>
<div class="setting-item">
<div class="info">
<span class="label">Cover Art Size</span>
<span class="description">Size for downloaded/embedded cover art</span>
</div>
<input
type="text"
id="cover-art-size-setting"
class="template-input"
style="width: 120px; text-align: right"
placeholder="1280x1280"
/>
</div>
<div class="setting-item">
<div class="info">
<span class="label">Filename Template</span>
@ -5015,6 +5017,9 @@
placeholder="{albumTitle} - {albumArtist} - monochrome.tf"
/>
</div>
</div>
<div class="settings-group">
<div class="setting-item">
<div class="info">
<span class="label">Generate M3U</span>
@ -5067,6 +5072,9 @@
<span class="slider"></span>
</label>
</div>
</div>
<div class="settings-group">
<div class="setting-item">
<div class="info">
<span class="label">Relative Paths</span>
@ -5092,6 +5100,36 @@
</div>
</div>
</div>
<div class="settings-tab-content" id="settings-tab-instances">
<div class="settings-list">
<div class="settings-group">
<div class="setting-item">
<div class="info">
<span class="label">ADVANCED: Custom Database/Auth</span>
<span class="description">Configure custom PocketBase and Firebase instances</span>
</div>
<button id="custom-db-btn" class="btn-secondary">Configure</button>
</div>
</div>
<div class="settings-group">
<div id="api-instance-manager">
<div class="setting-item">
<div class="info">
<span class="label">API Instances</span>
<span class="description">Manage and prioritize API instances.</span>
</div>
<button id="refresh-speed-test-btn" class="btn-secondary">
Refresh Instance List
</button>
</div>
<ul id="api-instance-list"></ul>
</div>
</div>
</div>
</div>
<div class="settings-tab-content" id="settings-tab-system">
<div class="settings-list">
<div class="settings-group">
@ -5140,6 +5178,9 @@
<span class="slider"></span>
</label>
</div>
</div>
<div class="settings-group">
<div class="setting-item">
<div class="info">
<span class="label">Reset Local Data</span>
@ -5160,6 +5201,9 @@
Clear Cloud Data
</button>
</div>
</div>
<div class="settings-group">
<div class="setting-item">
<div class="info">
<span class="label">Backup & Restore</span>
@ -5187,27 +5231,10 @@
/>
</div>
</div>
<div class="setting-item">
<div class="info">
<span class="label">ADVANCED: Custom Database/Auth</span>
<span class="description">Configure custom PocketBase and Firebase instances</span>
</div>
<button id="custom-db-btn" class="btn-secondary">Configure</button>
</div>
<div id="api-instance-manager">
<div class="setting-item" style="padding-bottom: 1rem; border: none">
<div class="info">
<span class="label">API Instances</span>
<span class="description">Manage and prioritize API instances.</span>
</div>
<button id="refresh-speed-test-btn" class="btn-secondary">
Refresh Instance List
</button>
</div>
<ul id="api-instance-list"></ul>
</div>
</div>
<div class="setting-item" style="padding-bottom: 1rem; border-top: 1px solid var(--border)">
<div class="settings-group">
<div class="setting-item">
<div class="info">
<span class="label">Blocked Content</span>
<span class="description"

View file

@ -901,33 +901,56 @@ export function initializeSettings(scrobbler, player, api, ui) {
// ========================================
const playbackSpeedSlider = document.getElementById('playback-speed-slider');
const playbackSpeedInput = document.getElementById('playback-speed-input');
const playbackSpeedReset = document.getElementById('playback-speed-reset');
if (playbackSpeedSlider && playbackSpeedInput) {
const currentSpeed = audioEffectsSettings.getSpeed();
// Clamp slider to its range (0.25-4), but show actual value in input
playbackSpeedSlider.value = Math.max(0.25, Math.min(4.0, currentSpeed));
playbackSpeedInput.value = currentSpeed;
// Slider only controls 0.25-4 range
playbackSpeedSlider.addEventListener('input', (e) => {
const speed = parseFloat(e.target.value) || 1.0;
playbackSpeedInput.value = speed;
player.setPlaybackSpeed(speed);
});
// Input allows full 0.01-100 range
const handleInputChange = () => {
const speed = parseFloat(playbackSpeedInput.value) || 1.0;
const validSpeed = Math.max(0.01, Math.min(100, speed));
// Helper function to update both controls
const updatePlaybackSpeedControls = (speed) => {
const validSpeed = Math.max(0.01, Math.min(100, parseFloat(speed) || 1.0));
playbackSpeedInput.value = validSpeed;
// Only update slider if value is within slider range
if (validSpeed >= 0.25 && validSpeed <= 4.0) {
playbackSpeedSlider.value = validSpeed;
}
player.setPlaybackSpeed(validSpeed);
return validSpeed;
};
playbackSpeedInput.addEventListener('change', handleInputChange);
playbackSpeedInput.addEventListener('blur', handleInputChange);
// Initialize with current value
const currentSpeed = audioEffectsSettings.getSpeed();
updatePlaybackSpeedControls(currentSpeed);
playbackSpeedSlider.addEventListener('input', (e) => {
const speed = parseFloat(e.target.value);
playbackSpeedInput.value = speed;
audioEffectsSettings.setSpeed(speed);
player.setPlaybackSpeed(speed);
});
playbackSpeedInput.addEventListener('input', (e) => {
const speed = parseFloat(e.target.value);
if (!isNaN(speed) && speed >= 0.01 && speed <= 100) {
if (speed >= 0.25 && speed <= 4.0) {
playbackSpeedSlider.value = speed;
}
audioEffectsSettings.setSpeed(speed);
player.setPlaybackSpeed(speed);
}
});
playbackSpeedInput.addEventListener('change', (e) => {
const speed = parseFloat(e.target.value);
const validSpeed = updatePlaybackSpeedControls(speed);
audioEffectsSettings.setSpeed(validSpeed);
player.setPlaybackSpeed(validSpeed);
});
if (playbackSpeedReset) {
playbackSpeedReset.addEventListener('click', () => {
const defaultSpeed = audioEffectsSettings.resetSpeed();
updatePlaybackSpeedControls(defaultSpeed);
player.setPlaybackSpeed(defaultSpeed);
});
}
}
// ========================================

View file

@ -1352,6 +1352,11 @@ export const audioEffectsSettings = {
localStorage.setItem(this.SPEED_KEY, validSpeed.toString());
},
resetSpeed() {
this.setSpeed(1.0);
return 1.0;
},
// Preserve pitch when changing speed (default true)
isPreservePitchEnabled() {
try {

View file

@ -2617,6 +2617,65 @@ input[type='search']::-webkit-search-cancel-button {
width: 100px;
}
.playback-speed-control {
display: flex;
align-items: center;
gap: var(--spacing-md);
}
.playback-speed-slider {
appearance: none;
width: 150px;
height: 6px;
background: var(--border);
border-radius: var(--radius-full);
cursor: pointer;
outline: none;
}
.playback-speed-slider::-webkit-slider-thumb {
appearance: none;
width: 16px;
height: 16px;
background: var(--primary);
border-radius: 50%;
cursor: pointer;
transition: transform 0.1s ease;
}
.playback-speed-slider::-webkit-slider-thumb:hover {
transform: scale(1.2);
}
.playback-speed-number-input {
width: 80px;
padding: 0.25rem 0.5rem;
border: 1px solid var(--border);
border-radius: var(--radius-sm);
background: var(--input);
color: var(--foreground);
text-align: center;
font-size: 0.9rem;
}
.playback-speed-unit {
font-size: 0.9rem;
color: var(--muted-foreground);
min-width: 1rem;
}
.playback-speed-number-input:focus {
outline: none;
border-color: var(--primary);
}
/* Hide arrows/spinners for number input */
.playback-speed-number-input::-webkit-outer-spin-button,
.playback-speed-number-input::-webkit-inner-spin-button {
appearance: none;
margin: 0;
}
.template-input {
width: 100%;
max-width: 400px;