fix: various dirty tracking bug fixes, unused variables

This commit is contained in:
Sam Chau
2026-01-19 18:18:22 +10:30
parent c476775bc2
commit fc56a67b28
9 changed files with 129 additions and 77 deletions

View File

@@ -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'
: ''}"

View File

@@ -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' : ''}"

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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;
}}
/>

View File

@@ -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');

View File

@@ -22,7 +22,6 @@
}>();
export let condition: ConditionData;
export let arrType: ArrType = 'all';
export let invalid = false;
export let nameConflict = false;

View File

@@ -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 }[] = [];