From ebced3e5b61610baeb5a6e1af4bdbb1a0668416a Mon Sep 17 00:00:00 2001 From: Sam Chau Date: Wed, 21 Jan 2026 10:00:56 +1030 Subject: [PATCH] style: add better default sorting to custom format conditions --- .../server/pcd/queries/customFormats/list.ts | 5 +- .../server/pcd/queries/customFormats/types.ts | 1 + src/lib/shared/conditionTypes.ts | 42 +++++++++++-- .../[databaseId]/[id]/conditions/+page.svelte | 60 ++++++------------- .../[databaseId]/views/TableView.svelte | 7 ++- 5 files changed, 62 insertions(+), 53 deletions(-) diff --git a/src/lib/server/pcd/queries/customFormats/list.ts b/src/lib/server/pcd/queries/customFormats/list.ts index 9c4f712..84cb4b4 100644 --- a/src/lib/server/pcd/queries/customFormats/list.ts +++ b/src/lib/server/pcd/queries/customFormats/list.ts @@ -36,10 +36,8 @@ export async function list(cache: PCDCache): Promise { // 3. Get all conditions for all custom formats const allConditions = await db .selectFrom('custom_format_conditions') - .select(['custom_format_name', 'name', 'required', 'negate']) + .select(['custom_format_name', 'name', 'type', 'required', 'negate']) .where('custom_format_name', 'in', formatNames) - .orderBy('custom_format_name') - .orderBy('name') .execute(); // 4. Get test counts for all custom formats @@ -77,6 +75,7 @@ export async function list(cache: PCDCache): Promise { } conditionsMap.get(condition.custom_format_name)!.push({ name: condition.name, + type: condition.type, required: condition.required === 1, negate: condition.negate === 1 }); diff --git a/src/lib/server/pcd/queries/customFormats/types.ts b/src/lib/server/pcd/queries/customFormats/types.ts index 4172c32..6cf27ff 100644 --- a/src/lib/server/pcd/queries/customFormats/types.ts +++ b/src/lib/server/pcd/queries/customFormats/types.ts @@ -7,6 +7,7 @@ import type { Tag } from '../../types.ts'; /** Condition reference for display */ export interface ConditionRef { name: string; + type: string; required: boolean; negate: boolean; } diff --git a/src/lib/shared/conditionTypes.ts b/src/lib/shared/conditionTypes.ts index 6584b6f..f6e7dd1 100644 --- a/src/lib/shared/conditionTypes.ts +++ b/src/lib/shared/conditionTypes.ts @@ -5,21 +5,55 @@ // Arr type for filtering export type ArrType = 'all' | 'radarr' | 'sonarr'; -// Condition type definitions +// Condition type definitions (ordered for display sorting) export const CONDITION_TYPES = [ + { value: 'resolution', label: 'Resolution', arrType: 'all' as ArrType }, + { value: 'source', label: 'Source', arrType: 'all' as ArrType }, + { value: 'quality_modifier', label: 'Quality Modifier', arrType: 'radarr' as ArrType }, { value: 'release_title', label: 'Release Title', arrType: 'all' as ArrType }, { value: 'release_group', label: 'Release Group', arrType: 'all' as ArrType }, { value: 'edition', label: 'Edition', arrType: 'radarr' as ArrType }, { value: 'language', label: 'Language', arrType: 'all' as ArrType }, - { value: 'source', label: 'Source', arrType: 'all' as ArrType }, - { value: 'resolution', label: 'Resolution', arrType: 'all' as ArrType }, - { value: 'quality_modifier', label: 'Quality Modifier', arrType: 'radarr' as ArrType }, { value: 'release_type', label: 'Release Type', arrType: 'sonarr' as ArrType }, { value: 'indexer_flag', label: 'Indexer Flag', arrType: 'all' as ArrType }, { value: 'size', label: 'Size', arrType: 'all' as ArrType }, { value: 'year', label: 'Year', arrType: 'all' as ArrType } ] as const; +// Type order index for sorting +const TYPE_ORDER: Map = new Map(CONDITION_TYPES.map((t, i) => [t.value, i])); + +/** + * Get the status priority for sorting: required=0, negated=1, optional=2 + */ +function getStatusPriority(required: boolean, negate: boolean): number { + if (required && !negate) return 0; // Required + if (negate) return 1; // Negated (includes required+negate) + return 2; // Optional +} + +/** + * Sort conditions by: required/negated/optional, then type order, then alphabetical + */ +export function sortConditions( + conditions: T[] +): T[] { + return [...conditions].sort((a, b) => { + // Primary: status (required -> negated -> optional) + const statusA = getStatusPriority(a.required, a.negate); + const statusB = getStatusPriority(b.required, b.negate); + if (statusA !== statusB) return statusA - statusB; + + // Secondary: type order + const typeA = TYPE_ORDER.get(a.type) ?? 999; + const typeB = TYPE_ORDER.get(b.type) ?? 999; + if (typeA !== typeB) return typeA - typeB; + + // Tertiary: alphabetical by name + return a.name.localeCompare(b.name); + }); +} + // Pattern-based types (use regex patterns as values) export const PATTERN_TYPES = ['release_title', 'release_group', 'edition'] as const; diff --git a/src/routes/custom-formats/[databaseId]/[id]/conditions/+page.svelte b/src/routes/custom-formats/[databaseId]/[id]/conditions/+page.svelte index cf32ef5..0454752 100644 --- a/src/routes/custom-formats/[databaseId]/[id]/conditions/+page.svelte +++ b/src/routes/custom-formats/[databaseId]/[id]/conditions/+page.svelte @@ -9,7 +9,7 @@ import StickyCard from '$ui/card/StickyCard.svelte'; import SaveTargetModal from '$ui/modal/SaveTargetModal.svelte'; import { alertStore } from '$alerts/store'; - import { CONDITION_TYPES } from '$lib/shared/conditionTypes'; + import { sortConditions } from '$lib/shared/conditionTypes'; import { current, isDirty, initEdit, update } from '$lib/client/stores/dirty'; import type { PageData } from './$types'; import type { ConditionData } from '$pcd/queries/customFormats/index'; @@ -104,23 +104,8 @@ } } - // Group conditions by type - $: groupedConditions = conditions.reduce( - (acc, condition) => { - const type = condition.type; - if (!acc[type]) acc[type] = []; - acc[type].push(condition); - return acc; - }, - {} as Record - ); - - // Get ordered types (only those that have conditions) - $: orderedTypes = CONDITION_TYPES.filter((t) => groupedConditions[t.value]).map((t) => ({ - value: t.value, - label: t.label, - conditions: groupedConditions[t.value] - })); + // Sort conditions by status (required -> negated -> optional), then type, then alphabetical + $: sortedConditions = sortConditions(conditions); function handleRemove(key: string) { update( @@ -277,34 +262,23 @@ {/if} - + {#if conditions.length === 0 && draftConditions.length === 0}

No conditions defined

{:else} - {#each orderedTypes as group (group.value)} -
- -
- - {group.label} - - {group.conditions.length} -
- - - {#each group.conditions as condition (condition._key)} - handleRemove(condition._key)} - on:change={(e) => handleConditionChange(e.detail, condition._key)} - /> - {/each} -
- {/each} +
+ {#each sortedConditions as condition (condition._key)} + handleRemove(condition._key)} + on:change={(e) => handleConditionChange(e.detail, condition._key)} + /> + {/each} +
{/if} diff --git a/src/routes/custom-formats/[databaseId]/views/TableView.svelte b/src/routes/custom-formats/[databaseId]/views/TableView.svelte index 08c7c17..52c60dc 100644 --- a/src/routes/custom-formats/[databaseId]/views/TableView.svelte +++ b/src/routes/custom-formats/[databaseId]/views/TableView.svelte @@ -6,6 +6,7 @@ import { marked } from 'marked'; import { goto } from '$app/navigation'; import { page } from '$app/stores'; + import { sortConditions } from '$lib/shared/conditionTypes'; export let formats: CustomFormatTableRow[]; @@ -80,11 +81,11 @@ cell: (row: CustomFormatTableRow) => ({ html: row.conditions.length > 0 - ? `
${row.conditions + ? `
${sortConditions(row.conditions) .map((c) => { // Color based on required/negate: // required + negate = red (must NOT match) - // required + !negate = accent (must match) + // required + !negate = green (must match) // !required + negate = amber (optional negative) // !required + !negate = neutral (optional) let colorClass: string; @@ -92,7 +93,7 @@ colorClass = 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200'; } else if (c.required) { colorClass = - 'bg-accent-100 text-accent-800 dark:bg-accent-900 dark:text-accent-200'; + 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200'; } else if (c.negate) { colorClass = 'bg-amber-100 text-amber-800 dark:bg-amber-900 dark:text-amber-200';