mirror of
https://github.com/Dictionarry-Hub/profilarr.git
synced 2026-01-22 10:51:02 +01:00
style: add better default sorting to custom format conditions
This commit is contained in:
@@ -36,10 +36,8 @@ export async function list(cache: PCDCache): Promise<CustomFormatTableRow[]> {
|
||||
// 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<CustomFormatTableRow[]> {
|
||||
}
|
||||
conditionsMap.get(condition.custom_format_name)!.push({
|
||||
name: condition.name,
|
||||
type: condition.type,
|
||||
required: condition.required === 1,
|
||||
negate: condition.negate === 1
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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<string, number> = 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<T extends { required: boolean; negate: boolean; type: string; name: string }>(
|
||||
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;
|
||||
|
||||
|
||||
@@ -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<string, KeyedCondition[]>
|
||||
);
|
||||
|
||||
// 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 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Existing conditions grouped by type -->
|
||||
<!-- Existing conditions sorted by status, type, then name -->
|
||||
{#if conditions.length === 0 && draftConditions.length === 0}
|
||||
<p class="text-sm text-neutral-500 dark:text-neutral-400">No conditions defined</p>
|
||||
{:else}
|
||||
{#each orderedTypes as group (group.value)}
|
||||
<div class="space-y-2">
|
||||
<!-- Group header -->
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-sm font-medium text-neutral-600 dark:text-neutral-400">
|
||||
{group.label}
|
||||
</span>
|
||||
<Badge variant="neutral" size="sm">{group.conditions.length}</Badge>
|
||||
</div>
|
||||
|
||||
<!-- Conditions -->
|
||||
{#each group.conditions as condition (condition._key)}
|
||||
<ConditionCard
|
||||
{condition}
|
||||
availablePatterns={data.availablePatterns}
|
||||
availableLanguages={data.availableLanguages}
|
||||
invalid={!isConditionValid(condition)}
|
||||
nameConflict={hasNameConflict(condition)}
|
||||
on:remove={() => handleRemove(condition._key)}
|
||||
on:change={(e) => handleConditionChange(e.detail, condition._key)}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
{/each}
|
||||
<div class="space-y-2">
|
||||
{#each sortedConditions as condition (condition._key)}
|
||||
<ConditionCard
|
||||
{condition}
|
||||
availablePatterns={data.availablePatterns}
|
||||
availableLanguages={data.availableLanguages}
|
||||
invalid={!isConditionValid(condition)}
|
||||
nameConflict={hasNameConflict(condition)}
|
||||
on:remove={() => handleRemove(condition._key)}
|
||||
on:change={(e) => handleConditionChange(e.detail, condition._key)}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -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
|
||||
? `<div class="flex flex-wrap gap-1">${row.conditions
|
||||
? `<div class="flex flex-wrap gap-1">${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';
|
||||
|
||||
Reference in New Issue
Block a user