fix: enhance unsaved changes tracking and improve state management in scoring components

This commit is contained in:
Sam Chau
2025-11-09 05:26:42 +11:00
parent a0fc3e7ece
commit 53a74a98e8
3 changed files with 81 additions and 36 deletions

View File

@@ -1,10 +1,13 @@
<script lang="ts">
import { ChevronDown, Info } from 'lucide-svelte';
import InfoModal from '$ui/modal/InfoModal.svelte';
import UnsavedChangesModal from '$ui/modal/UnsavedChangesModal.svelte';
import { useUnsavedChanges } from '$lib/client/utils/unsavedChanges.svelte';
import type { PageData } from './$types';
export let data: PageData;
const unsavedChanges = useUnsavedChanges();
let showInfoModal = false;
const typeOptions: Array<{ value: 'simple' | 'must' | 'only' | 'not'; label: string }> = [
@@ -30,6 +33,14 @@
$: isValidLanguage = searchQuery === '' || selectedLanguageId !== null;
$: showValidationError = searchQuery !== '' && !isValidLanguage;
// Mark as dirty when language settings change
$: if (
selectedType !== (data.languages[0]?.type || 'simple') ||
selectedLanguageId !== (data.languages[0]?.id || null)
) {
unsavedChanges.markDirty();
}
function selectType(type: 'must' | 'only' | 'not' | 'simple') {
selectedType = type;
showTypeDropdown = false;
@@ -74,6 +85,8 @@
<title>Languages - Profilarr</title>
</svelte:head>
<UnsavedChangesModal />
<div class="mt-6 space-y-3">
<div class="flex items-start justify-between">
<div>

View File

@@ -24,6 +24,19 @@
let showInfoModal = false;
let showOptionsInfoModal = false;
// State variables
let minimumScore = 0;
let upgradeUntilScore = 0;
let upgradeScoreIncrement = 0;
let customFormatScores: Record<number, Record<string, number | null>> = {};
let customFormatEnabled: Record<number, Record<string, boolean>> = {};
let scoringData: any = null;
// Track initial values
let initialMinimumScore = 0;
let initialUpgradeUntilScore = 0;
let initialUpgradeScoreIncrement = 0;
type SortKey = 'name' | 'radarr' | 'sonarr';
type SortDirection = 'asc' | 'desc';
@@ -293,34 +306,51 @@
return arrTypeColors[arrType] || '#3b82f6'; // default to blue
}
// Initialize state based on scoring data
function initializeState(scoring: any) {
const minimumScore = scoring.minimum_custom_format_score;
const upgradeUntilScore = scoring.upgrade_until_score;
const upgradeScoreIncrement = scoring.upgrade_score_increment;
// Reactive state - initialize from data
$: if (scoringData) {
minimumScore = scoringData.minimum_custom_format_score;
upgradeUntilScore = scoringData.upgrade_until_score;
upgradeScoreIncrement = scoringData.upgrade_score_increment;
// Custom format scores - create a reactive map
const customFormatScores: Record<number, Record<string, number | null>> = {};
const customFormatEnabled: Record<number, Record<string, boolean>> = {};
// Save initial values
initialMinimumScore = scoringData.minimum_custom_format_score;
initialUpgradeUntilScore = scoringData.upgrade_until_score;
initialUpgradeScoreIncrement = scoringData.upgrade_score_increment;
// Initialize scores and enabled state from data
scoring.customFormats.forEach((cf: any) => {
customFormatScores[cf.id] = { ...cf.scores };
customFormatEnabled[cf.id] = {};
scoring.arrTypes.forEach((arrType: string) => {
customFormatEnabled[cf.id][arrType] = cf.scores[arrType] !== null;
const newScores: Record<number, Record<string, number | null>> = {};
const newEnabled: Record<number, Record<string, boolean>> = {};
scoringData.customFormats.forEach((cf: any) => {
newScores[cf.id] = { ...cf.scores };
newEnabled[cf.id] = {};
scoringData.arrTypes.forEach((arrType: string) => {
newEnabled[cf.id][arrType] = cf.scores[arrType] !== null;
});
});
return {
minimumScore,
upgradeUntilScore,
upgradeScoreIncrement,
customFormatScores,
customFormatEnabled
};
customFormatScores = newScores;
customFormatEnabled = newEnabled;
}
// Mark as dirty when values change
$: if (
minimumScore !== initialMinimumScore ||
upgradeUntilScore !== initialUpgradeUntilScore ||
upgradeScoreIncrement !== initialUpgradeScoreIncrement
) {
unsavedChanges.markDirty();
}
// Create state object for convenience
$: state = {
minimumScore,
upgradeUntilScore,
upgradeScoreIncrement,
customFormatScores,
customFormatEnabled
};
function toggleSort(key: SortKey, defaultDirection: SortDirection = 'asc') {
if (sortState?.key === key) {
// Toggle direction
@@ -453,7 +483,7 @@
</div>
</div>
{:then scoring}
{@const state = initializeState(scoring)}
{@const _ = (scoringData = scoring, null)}
{@const searchQuery = ($searchStore.query ?? '').trim().toLowerCase()}
{@const filteredCustomFormats = scoring.customFormats.filter((format) => {
// Filter by search
@@ -491,7 +521,7 @@
<p class="text-xs text-neutral-600 dark:text-neutral-400">
Minimum custom format score required to download
</p>
<NumberInput name="minimumScore" bind:value={state.minimumScore} step={1} font="mono" />
<NumberInput name="minimumScore" bind:value={minimumScore} step={1} font="mono" />
</div>
<div class="space-y-2">
@@ -506,7 +536,7 @@
</p>
<NumberInput
name="upgradeUntilScore"
bind:value={state.upgradeUntilScore}
bind:value={upgradeUntilScore}
step={1}
font="mono"
/>
@@ -524,7 +554,7 @@
</p>
<NumberInput
name="upgradeScoreIncrement"
bind:value={state.upgradeScoreIncrement}
bind:value={upgradeScoreIncrement}
step={1}
font="mono"
/>

View File

@@ -72,31 +72,33 @@
<td class="px-6 py-4">
<div class="flex items-center justify-center gap-2">
<IconCheckbox
checked={state.customFormatEnabled[format.id][arrType]}
checked={state.customFormatEnabled[format.id]?.[arrType] ?? false}
icon={Check}
color={getArrTypeColor(arrType)}
shape="circle"
on:click={() => {
const isEnabled = state.customFormatEnabled[format.id][arrType];
const isEnabled = state.customFormatEnabled[format.id]?.[arrType] ?? false;
if (isEnabled) {
state.customFormatScores[format.id][arrType] = null;
} else {
if (state.customFormatScores[format.id][arrType] === null) {
if (state.customFormatScores[format.id]?.[arrType] === null) {
state.customFormatScores[format.id][arrType] = 0;
}
}
state.customFormatEnabled[format.id][arrType] = !isEnabled;
}}
/>
<div class="w-48">
<NumberInput
name="score-{format.id}-{arrType}"
bind:value={state.customFormatScores[format.id][arrType]}
step={1}
disabled={!state.customFormatEnabled[format.id][arrType]}
font="mono"
/>
</div>
{#if state.customFormatScores[format.id]}
<div class="w-48">
<NumberInput
name="score-{format.id}-{arrType}"
bind:value={state.customFormatScores[format.id][arrType]}
step={1}
disabled={!state.customFormatEnabled[format.id]?.[arrType]}
font="mono"
/>
</div>
{/if}
</div>
</td>
{/each}