feat: remove 'none' trigger from sync strategies, add dirty tracking / unsaved changes to sync page

This commit is contained in:
Sam Chau
2026-01-15 15:39:55 +10:30
parent cabf1ff815
commit 55c5886125
8 changed files with 69 additions and 23 deletions

View File

@@ -1,7 +1,7 @@
import { db } from '../db.ts';
// Types
export type SyncTrigger = 'none' | 'manual' | 'on_pull' | 'on_change' | 'schedule';
export type SyncTrigger = 'manual' | 'on_pull' | 'on_change' | 'schedule';
export interface ProfileSelection {
databaseId: number;
@@ -75,7 +75,7 @@ export const arrSyncQueries = {
profileId: row.profile_id
})),
config: {
trigger: (configRow?.trigger as SyncTrigger) ?? 'none',
trigger: (configRow?.trigger as SyncTrigger) ?? 'manual',
cron: configRow?.cron ?? null
}
};
@@ -133,7 +133,7 @@ export const arrSyncQueries = {
profileId: row.profile_id
})),
config: {
trigger: (configRow?.trigger as SyncTrigger) ?? 'none',
trigger: (configRow?.trigger as SyncTrigger) ?? 'manual',
cron: configRow?.cron ?? null
}
};
@@ -184,7 +184,7 @@ export const arrSyncQueries = {
namingDatabaseId: row?.naming_database_id ?? null,
qualityDefinitionsDatabaseId: row?.quality_definitions_database_id ?? null,
mediaSettingsDatabaseId: row?.media_settings_database_id ?? null,
trigger: (row?.trigger as SyncTrigger) ?? 'none',
trigger: (row?.trigger as SyncTrigger) ?? 'manual',
cron: row?.cron ?? null
};
},

View File

@@ -240,22 +240,19 @@ export async function syncInstance(instanceId: number): Promise<ProcessSyncsResu
const mmConfig = arrSyncQueries.getMediaManagementSync(instanceId);
// Sync quality profiles if configured
if (qpConfig.config.trigger !== 'none' && qpConfig.selections.length > 0) {
if (qpConfig.selections.length > 0) {
const syncer = new QualityProfileSyncer(client, instanceId, instance.name, instance.type as SyncArrType);
result.qualityProfiles = await syncer.sync();
}
// Sync delay profiles if configured
if (dpConfig.config.trigger !== 'none' && dpConfig.selections.length > 0) {
if (dpConfig.selections.length > 0) {
const syncer = new DelayProfileSyncer(client, instanceId, instance.name);
result.delayProfiles = await syncer.sync();
}
// Sync media management if configured
if (
mmConfig.trigger !== 'none' &&
(mmConfig.namingDatabaseId || mmConfig.qualityDefinitionsDatabaseId || mmConfig.mediaSettingsDatabaseId)
) {
if (mmConfig.namingDatabaseId || mmConfig.qualityDefinitionsDatabaseId || mmConfig.mediaSettingsDatabaseId) {
const syncer = new MediaManagementSyncer(client, instanceId, instance.name, instance.type as ArrType);
result.mediaManagement = await syncer.sync();
}

View File

@@ -76,7 +76,7 @@ export const actions: Actions = {
try {
const selections: ProfileSelection[] = JSON.parse(selectionsJson || '[]');
const effectiveTrigger = trigger || 'none';
const effectiveTrigger = trigger || 'manual';
const effectiveCron = cron || null;
arrSyncQueries.saveQualityProfilesSync(id, selections, {
trigger: effectiveTrigger,
@@ -113,7 +113,7 @@ export const actions: Actions = {
try {
const selections: ProfileSelection[] = JSON.parse(selectionsJson || '[]');
const effectiveTrigger = trigger || 'none';
const effectiveTrigger = trigger || 'manual';
const effectiveCron = cron || null;
arrSyncQueries.saveDelayProfilesSync(id, selections, {
trigger: effectiveTrigger,
@@ -151,7 +151,7 @@ export const actions: Actions = {
const cron = formData.get('cron') as string | null;
try {
const effectiveTrigger = trigger || 'none';
const effectiveTrigger = trigger || 'manual';
const effectiveCron = cron || null;
arrSyncQueries.saveMediaManagementSync(id, {
namingDatabaseId: namingDatabaseId ? parseInt(namingDatabaseId, 10) : null,

View File

@@ -2,10 +2,12 @@
import type { PageData } from './$types';
import { Info } from 'lucide-svelte';
import InfoModal from '$ui/modal/InfoModal.svelte';
import DirtyModal from '$ui/modal/DirtyModal.svelte';
import QualityProfiles from './components/QualityProfiles.svelte';
import DelayProfiles from './components/DelayProfiles.svelte';
import MediaManagement from './components/MediaManagement.svelte';
import type { SyncTrigger } from '$db/queries/arrSync.ts';
import { initEdit, initCreate } from '$lib/client/stores/dirty';
export let data: PageData;
@@ -40,6 +42,19 @@
};
let mediaManagementTrigger: SyncTrigger = data.syncData.mediaManagement.trigger;
let mediaManagementCron: string = data.syncData.mediaManagement.cron || '0 * * * *';
// Track dirty state from each component
let qualityProfilesDirty = false;
let delayProfilesDirty = false;
let mediaManagementDirty = false;
// Sync combined dirty state to global dirty store for DirtyModal
$: anyDirty = qualityProfilesDirty || delayProfilesDirty || mediaManagementDirty;
$: if (anyDirty) {
initCreate({});
} else {
initEdit({});
}
</script>
<svelte:head>
@@ -72,18 +87,21 @@
bind:state={qualityProfileState}
bind:syncTrigger={qualityProfileTrigger}
bind:cronExpression={qualityProfileCron}
bind:isDirty={qualityProfilesDirty}
/>
<DelayProfiles
databases={data.databases}
bind:state={delayProfileState}
bind:syncTrigger={delayProfileTrigger}
bind:cronExpression={delayProfileCron}
bind:isDirty={delayProfilesDirty}
/>
<MediaManagement
databases={data.databases}
bind:state={mediaManagementState}
bind:syncTrigger={mediaManagementTrigger}
bind:cronExpression={mediaManagementCron}
bind:isDirty={mediaManagementDirty}
/>
</div>
@@ -153,3 +171,5 @@
</div>
</div>
</InfoModal>
<DirtyModal />

View File

@@ -13,12 +13,18 @@
export let databases: DatabaseWithProfiles[];
export let state: Record<number, Record<number, boolean>> = {};
export let syncTrigger: 'none' | 'manual' | 'on_pull' | 'on_change' | 'schedule' = 'none';
export let syncTrigger: 'manual' | 'on_pull' | 'on_change' | 'schedule' = 'manual';
export let cronExpression: string = '0 * * * *';
let saving = false;
let syncing = false;
// Track saved state for dirty detection
let savedState = JSON.stringify({ state, syncTrigger, cronExpression });
$: currentState = JSON.stringify({ state, syncTrigger, cronExpression });
export let isDirty = false;
$: isDirty = currentState !== savedState;
// Initialize state for all databases/profiles
$: {
for (const db of databases) {
@@ -60,6 +66,8 @@
if (response.ok) {
alertStore.add('success', 'Delay profiles sync config saved');
// Update saved state to current
savedState = JSON.stringify({ state, syncTrigger, cronExpression });
} else {
alertStore.add('error', 'Failed to save delay profiles sync config');
}
@@ -139,5 +147,5 @@
{/if}
</div>
<SyncFooter bind:syncTrigger bind:cronExpression {saving} {syncing} on:save={handleSave} on:sync={handleSync} />
<SyncFooter bind:syncTrigger bind:cronExpression {saving} {syncing} {isDirty} on:save={handleSave} on:sync={handleSync} />
</div>

View File

@@ -43,12 +43,18 @@
showMediaDropdown = false;
}
export let syncTrigger: 'none' | 'manual' | 'on_pull' | 'on_change' | 'schedule' = 'none';
export let syncTrigger: 'manual' | 'on_pull' | 'on_change' | 'schedule' = 'manual';
export let cronExpression: string = '0 * * * *';
let saving = false;
let syncing = false;
// Track saved state for dirty detection
let savedState = JSON.stringify({ state, syncTrigger, cronExpression });
$: currentState = JSON.stringify({ state, syncTrigger, cronExpression });
export let isDirty = false;
$: isDirty = currentState !== savedState;
async function handleSave() {
saving = true;
try {
@@ -66,6 +72,8 @@
if (response.ok) {
alertStore.add('success', 'Media management sync config saved');
// Update saved state to current
savedState = JSON.stringify({ state, syncTrigger, cronExpression });
} else {
alertStore.add('error', 'Failed to save media management sync config');
}
@@ -246,5 +254,5 @@
</div>
</div>
<SyncFooter bind:syncTrigger bind:cronExpression {saving} {syncing} on:save={handleSave} on:sync={handleSync} />
<SyncFooter bind:syncTrigger bind:cronExpression {saving} {syncing} {isDirty} on:save={handleSave} on:sync={handleSync} />
</div>

View File

@@ -13,12 +13,18 @@
export let databases: DatabaseWithProfiles[];
export let state: Record<number, Record<number, boolean>> = {};
export let syncTrigger: 'none' | 'manual' | 'on_pull' | 'on_change' | 'schedule' = 'none';
export let syncTrigger: 'manual' | 'on_pull' | 'on_change' | 'schedule' = 'manual';
export let cronExpression: string = '0 * * * *';
let saving = false;
let syncing = false;
// Track saved state for dirty detection
let savedState = JSON.stringify({ state, syncTrigger, cronExpression });
$: currentState = JSON.stringify({ state, syncTrigger, cronExpression });
export let isDirty = false;
$: isDirty = currentState !== savedState;
// Initialize state for all databases/profiles
$: {
for (const db of databases) {
@@ -60,6 +66,8 @@
if (response.ok) {
alertStore.add('success', 'Quality profiles sync config saved');
// Update saved state to current
savedState = JSON.stringify({ state, syncTrigger, cronExpression });
} else {
alertStore.add('error', 'Failed to save quality profiles sync config');
}
@@ -139,5 +147,5 @@
{/if}
</div>
<SyncFooter bind:syncTrigger bind:cronExpression {saving} {syncing} on:save={handleSave} on:sync={handleSync} />
<SyncFooter bind:syncTrigger bind:cronExpression {saving} {syncing} {isDirty} on:save={handleSave} on:sync={handleSync} />
</div>

View File

@@ -3,20 +3,24 @@
import { Check, RefreshCw, Save, Loader2 } from 'lucide-svelte';
import { createEventDispatcher } from 'svelte';
export let syncTrigger: 'none' | 'manual' | 'on_pull' | 'on_change' | 'schedule' = 'none';
export let syncTrigger: 'manual' | 'on_pull' | 'on_change' | 'schedule' = 'manual';
export let cronExpression: string = '0 * * * *';
export let saving: boolean = false;
export let syncing: boolean = false;
export let isDirty: boolean = false;
const dispatch = createEventDispatcher<{ save: void; sync: void }>();
const triggerOptions = [
{ value: 'none', label: 'None' },
{ value: 'manual', label: 'Manual' },
{ value: 'on_pull', label: 'On Pull' },
{ value: 'on_change', label: 'On Change' },
{ value: 'schedule', label: 'Schedule' }
] as const;
// Save disabled when not dirty, Sync disabled when dirty (unsaved changes)
$: saveDisabled = saving || !isDirty;
$: syncDisabled = syncing || isDirty;
</script>
<div class="border-t border-neutral-200 px-6 py-4 dark:border-neutral-800">
@@ -48,8 +52,9 @@
<div class="flex items-center gap-2">
<button
type="button"
disabled={syncing}
disabled={syncDisabled}
on:click={() => dispatch('sync')}
title={isDirty ? 'Save changes before syncing' : ''}
class="flex items-center gap-1.5 rounded-lg border border-neutral-300 bg-white px-3 py-1.5 text-sm font-medium text-neutral-700 transition-colors hover:bg-neutral-50 disabled:opacity-50 disabled:cursor-not-allowed dark:border-neutral-700 dark:bg-neutral-800 dark:text-neutral-300 dark:hover:bg-neutral-700"
>
{#if syncing}
@@ -61,7 +66,7 @@
</button>
<button
type="button"
disabled={saving}
disabled={saveDisabled}
on:click={() => dispatch('save')}
class="flex items-center gap-1.5 rounded-lg bg-accent-600 px-3 py-1.5 text-sm font-medium text-white transition-colors hover:bg-accent-700 disabled:opacity-50 disabled:cursor-not-allowed"
>