feat(settings): rework settings page; remove unused SkeletonLoader/StorageManagement
Settings page refactor plus supporting docs (requirements, ux-flows, traceability) and the frontend-domain-model spec with implementation-status banner. Removes SkeletonLoader and StorageManagement components (no remaining references).
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
<!-- TRACES: UR-023, UR-029 | DR-048, DR-077 -->
|
||||
<!-- TRACES: UR-023, UR-029, UR-057 | DR-048, DR-077, DR-086 -->
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import { commands } from "$lib/api/bindings";
|
||||
@@ -63,8 +63,6 @@
|
||||
let networkDetectionSupported = $state(false);
|
||||
|
||||
let loading = $state(true);
|
||||
let saving = $state(false);
|
||||
let saveMessage = $state("");
|
||||
|
||||
// Image cache state
|
||||
let cacheStats = $state<ImageCacheStats | null>(null);
|
||||
@@ -153,55 +151,99 @@
|
||||
return Math.min(100, (cacheStats.totalSizeBytes / cacheStats.limitBytes) * 100);
|
||||
}
|
||||
|
||||
async function saveSettings() {
|
||||
// Settings apply the moment the user changes a control — there is no Save
|
||||
// button. Each helper writes just the settings group it owns so a single
|
||||
// toggle doesn't re-push unrelated state.
|
||||
async function persistAudio() {
|
||||
try {
|
||||
saving = true;
|
||||
saveMessage = "";
|
||||
await Promise.all([
|
||||
commands.playerSetAudioSettings(settings),
|
||||
commands.playerSetVideoSettings(videoSettings),
|
||||
updateCacheConfig(cacheConfig),
|
||||
]);
|
||||
await commands.playerSetAudioSettings(settings);
|
||||
} catch (e) {
|
||||
console.error("Failed to save audio settings:", e);
|
||||
}
|
||||
}
|
||||
|
||||
async function persistVideo() {
|
||||
try {
|
||||
await commands.playerSetVideoSettings(videoSettings);
|
||||
} catch (e) {
|
||||
console.error("Failed to save video settings:", e);
|
||||
}
|
||||
}
|
||||
|
||||
async function persistCache() {
|
||||
try {
|
||||
await updateCacheConfig(cacheConfig);
|
||||
// Re-report the network so the backend re-evaluates the gate against the
|
||||
// just-saved wifi-only preference, releasing or holding the queue now
|
||||
// just-changed wifi-only preference, releasing or holding the queue now
|
||||
// rather than at the next network change.
|
||||
await reportNetworkState();
|
||||
saveMessage = "Settings saved successfully!";
|
||||
setTimeout(() => {
|
||||
saveMessage = "";
|
||||
}, 3000);
|
||||
} catch (e) {
|
||||
console.error("Failed to save settings:", e);
|
||||
saveMessage = "Failed to save settings";
|
||||
} finally {
|
||||
saving = false;
|
||||
console.error("Failed to save download settings:", e);
|
||||
}
|
||||
}
|
||||
|
||||
// Slider drags fire `input` on every tick; update the live display there but
|
||||
// only persist on `change` (pointer release) so we don't spam the backend.
|
||||
function handleCrossfadeInput(e: Event) {
|
||||
const target = e.target as HTMLInputElement;
|
||||
settings.crossfadeDuration = parseFloat(target.value);
|
||||
}
|
||||
|
||||
function handleCrossfadeChange(e: Event) {
|
||||
const target = e.target as HTMLInputElement;
|
||||
settings.crossfadeDuration = parseFloat(target.value);
|
||||
persistAudio();
|
||||
}
|
||||
|
||||
function handleGaplessToggle() {
|
||||
settings.gaplessPlayback = !settings.gaplessPlayback;
|
||||
persistAudio();
|
||||
}
|
||||
|
||||
function handleNormalizeToggle() {
|
||||
settings.normalizeVolume = !settings.normalizeVolume;
|
||||
persistAudio();
|
||||
}
|
||||
|
||||
function handleVolumeLevelChange(level: VolumeLevel) {
|
||||
settings.volumeLevel = level;
|
||||
persistAudio();
|
||||
}
|
||||
|
||||
function handleAutoPlayToggle() {
|
||||
videoSettings.autoPlayNextEpisode = !videoSettings.autoPlayNextEpisode;
|
||||
persistVideo();
|
||||
}
|
||||
|
||||
function handleCountdownInput(e: Event) {
|
||||
const target = e.target as HTMLInputElement;
|
||||
videoSettings.autoPlayCountdownSeconds = parseInt(target.value, 10);
|
||||
}
|
||||
|
||||
function handleCountdownChange(e: Event) {
|
||||
const target = e.target as HTMLInputElement;
|
||||
videoSettings.autoPlayCountdownSeconds = parseInt(target.value, 10);
|
||||
persistVideo();
|
||||
}
|
||||
|
||||
function handleEpisodeLimitChange(value: number) {
|
||||
videoSettings.autoPlayMaxEpisodes = value;
|
||||
persistVideo();
|
||||
}
|
||||
|
||||
function handleSmartCachingToggle() {
|
||||
cacheConfig.albumAffinityEnabled = !cacheConfig.albumAffinityEnabled;
|
||||
persistCache();
|
||||
}
|
||||
|
||||
function handleQueuePrecacheToggle() {
|
||||
cacheConfig.queuePrecacheEnabled = !cacheConfig.queuePrecacheEnabled;
|
||||
persistCache();
|
||||
}
|
||||
|
||||
function handleWifiOnlyToggle() {
|
||||
cacheConfig.wifiOnly = !cacheConfig.wifiOnly;
|
||||
persistCache();
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -279,7 +321,8 @@
|
||||
max="12"
|
||||
step="0.5"
|
||||
value={settings.crossfadeDuration}
|
||||
oninput={handleCrossfadeChange}
|
||||
oninput={handleCrossfadeInput}
|
||||
onchange={handleCrossfadeChange}
|
||||
class="w-full h-2 bg-gray-700 rounded-lg appearance-none cursor-pointer accent-[var(--color-jellyfin)]"
|
||||
/>
|
||||
<div class="flex justify-between text-xs text-gray-500 mt-2">
|
||||
@@ -425,7 +468,8 @@
|
||||
max="30"
|
||||
step="5"
|
||||
value={videoSettings.autoPlayCountdownSeconds}
|
||||
oninput={handleCountdownChange}
|
||||
oninput={handleCountdownInput}
|
||||
onchange={handleCountdownChange}
|
||||
class="w-full h-2 bg-gray-700 rounded-lg appearance-none cursor-pointer accent-[var(--color-jellyfin)]"
|
||||
/>
|
||||
<div class="flex justify-between text-xs text-gray-500 mt-2">
|
||||
@@ -445,7 +489,7 @@
|
||||
<div class="grid grid-cols-3 md:grid-cols-6 gap-2">
|
||||
{#each episodeLimitOptions as option}
|
||||
<button
|
||||
onclick={() => { videoSettings.autoPlayMaxEpisodes = option.value; }}
|
||||
onclick={() => handleEpisodeLimitChange(option.value)}
|
||||
class="py-3 px-3 rounded-lg transition-all text-sm
|
||||
{videoSettings.autoPlayMaxEpisodes === option.value
|
||||
? 'bg-[var(--color-jellyfin)] text-white'
|
||||
@@ -597,9 +641,7 @@
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
onclick={() =>
|
||||
(cacheConfig.albumAffinityEnabled =
|
||||
!cacheConfig.albumAffinityEnabled)}
|
||||
onclick={handleSmartCachingToggle}
|
||||
class="relative inline-flex h-8 w-14 items-center rounded-full transition-colors {cacheConfig.albumAffinityEnabled
|
||||
? 'bg-[var(--color-jellyfin)]'
|
||||
: 'bg-gray-600'}"
|
||||
@@ -626,9 +668,7 @@
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
onclick={() =>
|
||||
(cacheConfig.queuePrecacheEnabled =
|
||||
!cacheConfig.queuePrecacheEnabled)}
|
||||
onclick={handleQueuePrecacheToggle}
|
||||
class="relative inline-flex h-8 w-14 items-center rounded-full transition-colors {cacheConfig.queuePrecacheEnabled
|
||||
? 'bg-[var(--color-jellyfin)]'
|
||||
: 'bg-gray-600'}"
|
||||
@@ -660,7 +700,7 @@
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
onclick={() => (cacheConfig.wifiOnly = !cacheConfig.wifiOnly)}
|
||||
onclick={handleWifiOnlyToggle}
|
||||
class="relative inline-flex h-8 w-14 items-center rounded-full transition-colors {cacheConfig.wifiOnly
|
||||
? 'bg-[var(--color-jellyfin)]'
|
||||
: 'bg-gray-600'} {networkDetectionSupported
|
||||
@@ -680,28 +720,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Save Button -->
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="text-sm">
|
||||
{#if saveMessage}
|
||||
<span
|
||||
class="text-{saveMessage.includes('success')
|
||||
? 'green'
|
||||
: 'red'}-400"
|
||||
>
|
||||
{saveMessage}
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
<button
|
||||
onclick={saveSettings}
|
||||
disabled={saving}
|
||||
class="px-6 py-3 bg-[var(--color-jellyfin)] text-white rounded-lg font-semibold hover:opacity-90 transition-opacity disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
{saving ? "Saving..." : "Save Settings"}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Info Box -->
|
||||
<div class="bg-blue-900/20 border border-blue-800 rounded-lg p-4">
|
||||
<div class="flex gap-3">
|
||||
|
||||
Reference in New Issue
Block a user