mirror of
https://github.com/Dictionarry-Hub/profilarr.git
synced 2026-01-22 10:51:02 +01:00
fix: various dirty tracking bug fixes, unused variables
This commit is contained in:
@@ -1,16 +1,27 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
|
||||
export let value: string = '';
|
||||
export let placeholder: string = '';
|
||||
export let type: 'text' | 'number' | 'email' | 'password' = 'text';
|
||||
export let disabled: boolean = false;
|
||||
export let width: string = 'w-28';
|
||||
|
||||
const dispatch = createEventDispatcher<{ input: string }>();
|
||||
|
||||
function handleInput(e: Event) {
|
||||
const target = e.target as HTMLInputElement;
|
||||
value = target.value;
|
||||
dispatch('input', value);
|
||||
}
|
||||
</script>
|
||||
|
||||
<input
|
||||
{type}
|
||||
bind:value
|
||||
{value}
|
||||
{placeholder}
|
||||
{disabled}
|
||||
on:input={handleInput}
|
||||
class="{width} rounded-lg border border-neutral-300 bg-white px-2.5 py-1.5 text-sm text-neutral-900 placeholder:text-neutral-400 focus:outline-none dark:border-neutral-700 dark:bg-neutral-800 dark:text-neutral-100 dark:placeholder:text-neutral-500 {disabled
|
||||
? 'cursor-not-allowed opacity-50'
|
||||
: ''}"
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
|
||||
export let checked: boolean = false;
|
||||
export let color: 'accent' | 'amber' | 'green' | 'red' = 'accent';
|
||||
export let disabled: boolean = false;
|
||||
export let label: string = 'Toggle';
|
||||
|
||||
const dispatch = createEventDispatcher<{ change: boolean }>();
|
||||
|
||||
const colors = {
|
||||
accent: 'bg-accent-500',
|
||||
amber: 'bg-amber-500',
|
||||
@@ -12,6 +16,12 @@
|
||||
};
|
||||
|
||||
$: colorClass = colors[color];
|
||||
|
||||
function handleClick() {
|
||||
if (disabled) return;
|
||||
checked = !checked;
|
||||
dispatch('change', checked);
|
||||
}
|
||||
</script>
|
||||
|
||||
<button
|
||||
@@ -20,7 +30,7 @@
|
||||
aria-checked={checked}
|
||||
aria-label={label}
|
||||
{disabled}
|
||||
on:click={() => !disabled && (checked = !checked)}
|
||||
on:click={handleClick}
|
||||
class="relative h-5 w-12 flex-shrink-0 cursor-pointer rounded-full p-[3px] transition-colors duration-300 ease-out
|
||||
{checked ? colorClass : 'bg-neutral-300 dark:bg-neutral-600'}
|
||||
{disabled ? 'cursor-not-allowed opacity-50' : ''}"
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { enhance } from '$app/forms';
|
||||
import { onMount } from 'svelte';
|
||||
import { alertStore } from '$lib/client/alerts/store';
|
||||
import { isDirty, initEdit, initCreate, update, clear } from '$lib/client/stores/dirty';
|
||||
import { isDirty, initEdit, update, current, clear } from '$lib/client/stores/dirty';
|
||||
import { Info, Save, Play, Settings, History } from 'lucide-svelte';
|
||||
import RenameSettings from './components/RenameSettings.svelte';
|
||||
import RenameRunHistory from './components/RenameRunHistory.svelte';
|
||||
@@ -15,41 +15,34 @@
|
||||
export let data: PageData;
|
||||
export let form: ActionData;
|
||||
|
||||
// Initialize from existing settings or defaults
|
||||
let enabled = data.settings?.enabled ?? false;
|
||||
let dryRun = data.settings?.dryRun ?? true;
|
||||
let renameFolders = data.settings?.renameFolders ?? false;
|
||||
let ignoreTag = data.settings?.ignoreTag ?? '';
|
||||
let schedule = String(data.settings?.schedule ?? 1440);
|
||||
let summaryNotifications = data.settings?.summaryNotifications ?? true;
|
||||
// Initialize dirty tracking on mount (same pattern as sync page)
|
||||
onMount(() => {
|
||||
const initialFormData = {
|
||||
enabled: data.settings?.enabled ?? false,
|
||||
dryRun: data.settings?.dryRun ?? true,
|
||||
renameFolders: data.settings?.renameFolders ?? false,
|
||||
ignoreTag: data.settings?.ignoreTag ?? '',
|
||||
schedule: String(data.settings?.schedule ?? 1440),
|
||||
summaryNotifications: data.settings?.summaryNotifications ?? true
|
||||
};
|
||||
// Always use initEdit - isDirty should be false until user makes changes
|
||||
initEdit(initialFormData);
|
||||
return () => clear();
|
||||
});
|
||||
|
||||
// Track if settings exist (determines save vs edit)
|
||||
$: isNewConfig = !data.settings;
|
||||
|
||||
let showInfoModal = false;
|
||||
let saving = false;
|
||||
let running = false;
|
||||
|
||||
// Initialize dirty tracking
|
||||
onMount(() => {
|
||||
const formData = { enabled, dryRun, renameFolders, ignoreTag, schedule, summaryNotifications };
|
||||
if (isNewConfig) {
|
||||
initCreate(formData);
|
||||
} else {
|
||||
initEdit(formData);
|
||||
}
|
||||
return () => clear();
|
||||
});
|
||||
$: enabled = ($current.enabled ?? false) as boolean;
|
||||
$: dryRun = ($current.dryRun ?? true) as boolean;
|
||||
$: renameFolders = ($current.renameFolders ?? false) as boolean;
|
||||
$: ignoreTag = ($current.ignoreTag ?? '') as string;
|
||||
$: schedule = ($current.schedule ?? '1440') as string;
|
||||
$: summaryNotifications = ($current.summaryNotifications ?? true) as boolean;
|
||||
|
||||
// Update dirty store when fields change
|
||||
$: update('enabled', enabled);
|
||||
$: update('dryRun', dryRun);
|
||||
$: update('renameFolders', renameFolders);
|
||||
$: update('ignoreTag', ignoreTag);
|
||||
$: update('schedule', schedule);
|
||||
$: update('summaryNotifications', summaryNotifications);
|
||||
|
||||
// Handle form response - use a processed flag to avoid re-running on field changes
|
||||
let lastFormId: unknown = null;
|
||||
$: if (form && form !== lastFormId) {
|
||||
lastFormId = form;
|
||||
@@ -124,13 +117,19 @@
|
||||
Settings
|
||||
</h2>
|
||||
<RenameSettings
|
||||
bind:enabled
|
||||
bind:dryRun
|
||||
bind:renameFolders
|
||||
bind:ignoreTag
|
||||
bind:schedule
|
||||
bind:summaryNotifications
|
||||
{enabled}
|
||||
{dryRun}
|
||||
{renameFolders}
|
||||
{ignoreTag}
|
||||
{schedule}
|
||||
{summaryNotifications}
|
||||
lastRunAt={data.settings?.lastRunAt ?? null}
|
||||
onEnabledChange={(v) => update('enabled', v)}
|
||||
onDryRunChange={(v) => update('dryRun', v)}
|
||||
onRenameFoldersChange={(v) => update('renameFolders', v)}
|
||||
onIgnoreTagChange={(v) => update('ignoreTag', v)}
|
||||
onScheduleChange={(v) => update('schedule', v)}
|
||||
onSummaryNotificationsChange={(v) => update('summaryNotifications', v)}
|
||||
/>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@@ -17,6 +17,13 @@
|
||||
export let summaryNotifications: boolean = true;
|
||||
export let lastRunAt: string | null = null;
|
||||
|
||||
export let onEnabledChange: ((value: boolean) => void) | undefined = undefined;
|
||||
export let onDryRunChange: ((value: boolean) => void) | undefined = undefined;
|
||||
export let onRenameFoldersChange: ((value: boolean) => void) | undefined = undefined;
|
||||
export let onIgnoreTagChange: ((value: string) => void) | undefined = undefined;
|
||||
export let onScheduleChange: ((value: string) => void) | undefined = undefined;
|
||||
export let onSummaryNotificationsChange: ((value: boolean) => void) | undefined = undefined;
|
||||
|
||||
let scheduleOpen = false;
|
||||
|
||||
const scheduleOptions = [
|
||||
@@ -102,7 +109,7 @@
|
||||
<div class="flex flex-wrap items-center gap-x-6 gap-y-3">
|
||||
<!-- Enabled Toggle -->
|
||||
<label class="flex cursor-pointer items-center gap-2">
|
||||
<Toggle bind:checked={enabled} />
|
||||
<Toggle checked={enabled} on:change={(e) => onEnabledChange?.(e.detail)} />
|
||||
<div>
|
||||
<span class="text-sm font-medium text-neutral-700 dark:text-neutral-300">Enabled</span>
|
||||
<p class="text-xs text-neutral-500 dark:text-neutral-400">Run on schedule</p>
|
||||
@@ -111,7 +118,7 @@
|
||||
|
||||
<!-- Dry Run Toggle -->
|
||||
<label class="flex cursor-pointer items-center gap-2">
|
||||
<Toggle bind:checked={dryRun} color="amber" />
|
||||
<Toggle checked={dryRun} color="amber" on:change={(e) => onDryRunChange?.(e.detail)} />
|
||||
<div>
|
||||
<span class="text-sm font-medium text-neutral-700 dark:text-neutral-300">Dry Run</span>
|
||||
<p class="text-xs text-neutral-500 dark:text-neutral-400">Preview only</p>
|
||||
@@ -120,7 +127,7 @@
|
||||
|
||||
<!-- Rename Folders Toggle -->
|
||||
<label class="flex cursor-pointer items-center gap-2">
|
||||
<Toggle bind:checked={renameFolders} />
|
||||
<Toggle checked={renameFolders} on:change={(e) => onRenameFoldersChange?.(e.detail)} />
|
||||
<div>
|
||||
<span class="text-sm font-medium text-neutral-700 dark:text-neutral-300">Folders</span>
|
||||
<p class="text-xs text-neutral-500 dark:text-neutral-400">Also rename folders</p>
|
||||
@@ -129,7 +136,7 @@
|
||||
|
||||
<!-- Summary Notifications Toggle -->
|
||||
<label class="flex cursor-pointer items-center gap-2">
|
||||
<Toggle bind:checked={summaryNotifications} />
|
||||
<Toggle checked={summaryNotifications} on:change={(e) => onSummaryNotificationsChange?.(e.detail)} />
|
||||
<div>
|
||||
<span class="text-sm font-medium text-neutral-700 dark:text-neutral-300">Summary</span>
|
||||
<p class="text-xs text-neutral-500 dark:text-neutral-400">Compact notifications</p>
|
||||
@@ -156,7 +163,7 @@
|
||||
label={option.label}
|
||||
selected={schedule === option.value}
|
||||
on:click={() => {
|
||||
schedule = option.value;
|
||||
onScheduleChange?.(option.value);
|
||||
scheduleOpen = false;
|
||||
}}
|
||||
/>
|
||||
@@ -169,7 +176,7 @@
|
||||
<!-- Ignore Tag -->
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-sm text-neutral-500 dark:text-neutral-400">Ignore Tag:</span>
|
||||
<Input bind:value={ignoreTag} placeholder="no-rename" />
|
||||
<Input value={ignoreTag} placeholder="no-rename" on:input={(e) => onIgnoreTagChange?.(e.detail)} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import { enhance } from '$app/forms';
|
||||
import { onMount } from 'svelte';
|
||||
import { alertStore } from '$lib/client/alerts/store';
|
||||
import { isDirty, initEdit, initCreate, update, clear } from '$lib/client/stores/dirty';
|
||||
import { isDirty, initEdit, update, current, clear } from '$lib/client/stores/dirty';
|
||||
import { Info, Save, Play, RotateCcw, Settings, SlidersHorizontal, History } from 'lucide-svelte';
|
||||
import CoreSettings from './components/CoreSettings.svelte';
|
||||
import FilterSettings from './components/FilterSettings.svelte';
|
||||
@@ -17,12 +17,19 @@
|
||||
export let data: PageData;
|
||||
export let form: ActionData;
|
||||
|
||||
// Initialize from existing config or defaults
|
||||
let enabled = data.config?.enabled ?? false;
|
||||
let dryRun = data.config?.dryRun ?? true;
|
||||
let schedule = String(data.config?.schedule ?? 360);
|
||||
let filterMode: FilterMode = data.config?.filterMode ?? 'round_robin';
|
||||
let filters: FilterConfig[] = data.config?.filters ?? [];
|
||||
// Initialize dirty tracking on mount (same pattern as sync page)
|
||||
onMount(() => {
|
||||
const initialFormData = {
|
||||
enabled: data.config?.enabled ?? false,
|
||||
dryRun: data.config?.dryRun ?? true,
|
||||
schedule: String(data.config?.schedule ?? 360),
|
||||
filterMode: (data.config?.filterMode ?? 'round_robin') as FilterMode,
|
||||
filters: JSON.stringify(data.config?.filters ?? [])
|
||||
};
|
||||
// Always use initEdit - isDirty should be false until user makes changes
|
||||
initEdit(initialFormData);
|
||||
return () => clear();
|
||||
});
|
||||
|
||||
// Track if config exists
|
||||
$: isNewConfig = !data.config;
|
||||
@@ -32,23 +39,12 @@
|
||||
let running = false;
|
||||
let clearing = false;
|
||||
|
||||
// Initialize dirty tracking
|
||||
onMount(() => {
|
||||
const formData = { enabled, dryRun, schedule, filterMode, filters: JSON.stringify(filters) };
|
||||
if (data.config) {
|
||||
initEdit(formData);
|
||||
} else {
|
||||
initCreate(formData);
|
||||
}
|
||||
return () => clear();
|
||||
});
|
||||
|
||||
// Update dirty store when fields change
|
||||
$: update('enabled', enabled);
|
||||
$: update('dryRun', dryRun);
|
||||
$: update('schedule', schedule);
|
||||
$: update('filterMode', filterMode);
|
||||
$: update('filters', JSON.stringify(filters));
|
||||
// Read current values from dirty store (same pattern as working pages)
|
||||
$: enabled = ($current.enabled ?? false) as boolean;
|
||||
$: dryRun = ($current.dryRun ?? true) as boolean;
|
||||
$: schedule = ($current.schedule ?? '360') as string;
|
||||
$: filterMode = ($current.filterMode ?? 'round_robin') as FilterMode;
|
||||
$: filters = JSON.parse(($current.filters ?? '[]') as string) as FilterConfig[];
|
||||
|
||||
// Handle form response - use a processed flag to avoid re-running on field changes
|
||||
let lastFormId: unknown = null;
|
||||
@@ -137,11 +133,15 @@
|
||||
Settings
|
||||
</h2>
|
||||
<CoreSettings
|
||||
bind:enabled
|
||||
bind:dryRun
|
||||
bind:schedule
|
||||
bind:filterMode
|
||||
{enabled}
|
||||
{dryRun}
|
||||
{schedule}
|
||||
{filterMode}
|
||||
lastRunAt={data.config?.lastRunAt ?? null}
|
||||
onEnabledChange={(v) => update('enabled', v)}
|
||||
onDryRunChange={(v) => update('dryRun', v)}
|
||||
onScheduleChange={(v) => update('schedule', v)}
|
||||
onFilterModeChange={(v) => update('filterMode', v)}
|
||||
/>
|
||||
</section>
|
||||
|
||||
@@ -152,7 +152,10 @@
|
||||
<SlidersHorizontal size={18} class="text-neutral-500 dark:text-neutral-400" />
|
||||
Filters
|
||||
</h2>
|
||||
<FilterSettings bind:filters />
|
||||
<FilterSettings
|
||||
{filters}
|
||||
onFiltersChange={(v) => update('filters', JSON.stringify(v))}
|
||||
/>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -15,6 +15,11 @@
|
||||
export let filterMode: FilterMode = 'round_robin';
|
||||
export let lastRunAt: string | null = null;
|
||||
|
||||
export let onEnabledChange: ((value: boolean) => void) | undefined = undefined;
|
||||
export let onDryRunChange: ((value: boolean) => void) | undefined = undefined;
|
||||
export let onScheduleChange: ((value: string) => void) | undefined = undefined;
|
||||
export let onFilterModeChange: ((value: FilterMode) => void) | undefined = undefined;
|
||||
|
||||
let scheduleOpen = false;
|
||||
let modeOpen = false;
|
||||
|
||||
@@ -101,7 +106,10 @@
|
||||
<div class="flex flex-wrap items-center gap-x-6 gap-y-3">
|
||||
<!-- Enabled Toggle -->
|
||||
<label class="flex cursor-pointer items-center gap-2">
|
||||
<Toggle bind:checked={enabled} />
|
||||
<Toggle
|
||||
checked={enabled}
|
||||
on:change={(e) => onEnabledChange?.(e.detail)}
|
||||
/>
|
||||
<div>
|
||||
<span class="text-sm font-medium text-neutral-700 dark:text-neutral-300">Enabled</span>
|
||||
<p class="text-xs text-neutral-500 dark:text-neutral-400">Run on schedule</p>
|
||||
@@ -110,7 +118,11 @@
|
||||
|
||||
<!-- Dry Run Toggle -->
|
||||
<label class="flex cursor-pointer items-center gap-2">
|
||||
<Toggle bind:checked={dryRun} color="amber" />
|
||||
<Toggle
|
||||
checked={dryRun}
|
||||
color="amber"
|
||||
on:change={(e) => onDryRunChange?.(e.detail)}
|
||||
/>
|
||||
<div>
|
||||
<span class="text-sm font-medium text-neutral-700 dark:text-neutral-300">Dry Run</span>
|
||||
<p class="text-xs text-neutral-500 dark:text-neutral-400">Preview only</p>
|
||||
@@ -137,7 +149,7 @@
|
||||
label={option.label}
|
||||
selected={schedule === option.value}
|
||||
on:click={() => {
|
||||
schedule = option.value;
|
||||
onScheduleChange?.(option.value);
|
||||
scheduleOpen = false;
|
||||
}}
|
||||
/>
|
||||
@@ -164,7 +176,7 @@
|
||||
label={mode.label}
|
||||
selected={filterMode === mode.id}
|
||||
on:click={() => {
|
||||
filterMode = mode.id;
|
||||
onFilterModeChange?.(mode.id);
|
||||
modeOpen = false;
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -29,6 +29,11 @@
|
||||
const debouncedQuery = searchStore.debouncedQuery;
|
||||
|
||||
export let filters: FilterConfig[] = [];
|
||||
export let onFiltersChange: ((filters: FilterConfig[]) => void) | undefined = undefined;
|
||||
|
||||
function notifyChange() {
|
||||
onFiltersChange?.(filters);
|
||||
}
|
||||
|
||||
let showInfoModal = false;
|
||||
|
||||
@@ -62,6 +67,7 @@
|
||||
expandedIds.delete(filterToDelete.id);
|
||||
expandedIds = expandedIds;
|
||||
alertStore.add('success', `Deleted "${filterToDelete.name}"`);
|
||||
notifyChange();
|
||||
}
|
||||
deleteModalOpen = false;
|
||||
filterToDelete = null;
|
||||
@@ -77,6 +83,7 @@
|
||||
filters = [...filters, newFilter];
|
||||
expandedIds.add(newFilter.id);
|
||||
expandedIds = expandedIds;
|
||||
notifyChange();
|
||||
}
|
||||
|
||||
function startEditing(filter: FilterConfig) {
|
||||
@@ -90,6 +97,7 @@
|
||||
if (filter) {
|
||||
filter.name = editingName;
|
||||
filters = filters;
|
||||
notifyChange();
|
||||
}
|
||||
}
|
||||
editingId = null;
|
||||
@@ -98,6 +106,7 @@
|
||||
|
||||
function handleChange() {
|
||||
filters = filters;
|
||||
notifyChange();
|
||||
}
|
||||
|
||||
function toggleEnabled(id: string) {
|
||||
@@ -105,6 +114,7 @@
|
||||
if (filter) {
|
||||
filter.enabled = !filter.enabled;
|
||||
filters = filters;
|
||||
notifyChange();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,6 +129,7 @@
|
||||
filters = [...filters, duplicate];
|
||||
expandedIds.add(duplicate.id);
|
||||
expandedIds = expandedIds;
|
||||
notifyChange();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,6 +172,7 @@
|
||||
filter.enabled = imported.enabled ?? filter.enabled;
|
||||
|
||||
filters = filters;
|
||||
notifyChange();
|
||||
alertStore.add('success', `Pasted settings into "${filter.name}"`);
|
||||
} catch {
|
||||
alertStore.add('error', 'Failed to paste from clipboard');
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
}>();
|
||||
|
||||
export let condition: ConditionData;
|
||||
export let arrType: ArrType = 'all';
|
||||
export let invalid = false;
|
||||
export let nameConflict = false;
|
||||
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
}>();
|
||||
|
||||
export let condition: ConditionData;
|
||||
export let arrType: ArrType = 'all';
|
||||
|
||||
// Available patterns and languages from database (passed in)
|
||||
export let availablePatterns: { id: number; name: string; pattern: string }[] = [];
|
||||
|
||||
Reference in New Issue
Block a user