mirror of
https://github.com/Dictionarry-Hub/profilarr.git
synced 2026-01-22 10:51:02 +01:00
fix: enhance unsaved changes tracking and improve state management in scoring components
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
/>
|
||||
|
||||
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user