diff --git a/src/lib/client/ui/form/Autocomplete.svelte b/src/lib/client/ui/form/Autocomplete.svelte new file mode 100644 index 0000000..4117aaa --- /dev/null +++ b/src/lib/client/ui/form/Autocomplete.svelte @@ -0,0 +1,177 @@ + + +
+ +
+ + {#each selected as item (item.value)} +
+ {item.label} + +
+ {/each} + + + {#if selected.length < max} + + {/if} +
+ + + {#if isOpen && filteredOptions.length > 0} +
+ {#each filteredOptions as option, index (option.value)} + + {/each} +
+ {/if} + + + {#if isOpen && inputValue && filteredOptions.length === 0} +
+ No matches found +
+ {/if} +
diff --git a/src/lib/client/ui/form/Select.svelte b/src/lib/client/ui/form/Select.svelte new file mode 100644 index 0000000..6a17808 --- /dev/null +++ b/src/lib/client/ui/form/Select.svelte @@ -0,0 +1,117 @@ + + +
+ + + + + {#if isOpen} +
+ {#each options as option, index (option.value)} + + {/each} +
+ {/if} +
diff --git a/src/lib/server/pcd/queries/customFormats/index.ts b/src/lib/server/pcd/queries/customFormats/index.ts index 479c6c4..80e3e2e 100644 --- a/src/lib/server/pcd/queries/customFormats/index.ts +++ b/src/lib/server/pcd/queries/customFormats/index.ts @@ -8,6 +8,7 @@ export type { CreateTestInput, CreateTestOptions } from './testCreate.ts'; export type { UpdateTestInput, UpdateTestOptions } from './testUpdate.ts'; export type { DeleteTestOptions } from './testDelete.ts'; export type { ConditionData } from './conditions.ts'; +export type { ConditionListItem } from './listConditions.ts'; export type { ConditionResult, EvaluationResult, ParsedInfo } from './evaluator.ts'; // Export query functions (reads) @@ -15,6 +16,7 @@ export { list } from './list.ts'; export { general } from './general.ts'; export { getById, listTests, getTestById } from './tests.ts'; export { getConditionsForEvaluation } from './conditions.ts'; +export { listConditions } from './listConditions.ts'; export { evaluateCustomFormat, getParsedInfo } from './evaluator.ts'; // Export mutation functions (writes via PCD operations) diff --git a/src/lib/server/pcd/queries/customFormats/listConditions.ts b/src/lib/server/pcd/queries/customFormats/listConditions.ts new file mode 100644 index 0000000..9190473 --- /dev/null +++ b/src/lib/server/pcd/queries/customFormats/listConditions.ts @@ -0,0 +1,39 @@ +/** + * Custom format condition list query + */ + +import type { PCDCache } from '../../cache.ts'; + +/** Condition item for list display */ +export interface ConditionListItem { + id: number; + name: string; + type: string; + negate: boolean; + required: boolean; +} + +/** + * Get all conditions for a custom format (basic info for list display) + */ +export async function listConditions( + cache: PCDCache, + formatId: number +): Promise { + const db = cache.kb; + + const conditions = await db + .selectFrom('custom_format_conditions') + .select(['id', 'name', 'type', 'negate', 'required']) + .where('custom_format_id', '=', formatId) + .orderBy('id') + .execute(); + + return conditions.map((c) => ({ + id: c.id, + name: c.name, + type: c.type, + negate: c.negate === 1, + required: c.required === 1 + })); +} diff --git a/src/lib/shared/conditionTypes.ts b/src/lib/shared/conditionTypes.ts new file mode 100644 index 0000000..c0f7426 --- /dev/null +++ b/src/lib/shared/conditionTypes.ts @@ -0,0 +1,84 @@ +/** + * Condition types and their valid values for custom formats + */ + +// Arr type for filtering +export type ArrType = 'all' | 'radarr' | 'sonarr'; + +// Condition type definitions +export const CONDITION_TYPES = [ + { 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; + +// Pattern-based types (use regex patterns as values) +export const PATTERN_TYPES = ['release_title', 'release_group', 'edition'] as const; + +export type ConditionType = (typeof CONDITION_TYPES)[number]['value']; + +// Source values +export const SOURCE_VALUES = [ + { value: 'unknown', label: 'Unknown', arrType: 'all' as ArrType }, + { value: 'television', label: 'Television', arrType: 'all' as ArrType }, + { value: 'television_raw', label: 'Television Raw', arrType: 'sonarr' as ArrType }, + { value: 'webdl', label: 'WEB-DL', arrType: 'all' as ArrType }, + { value: 'webrip', label: 'WEBRip', arrType: 'all' as ArrType }, + { value: 'dvd', label: 'DVD', arrType: 'all' as ArrType }, + { value: 'bluray', label: 'Bluray', arrType: 'all' as ArrType }, + { value: 'bluray_raw', label: 'Bluray Raw', arrType: 'sonarr' as ArrType }, + { value: 'cam', label: 'CAM', arrType: 'all' as ArrType }, + { value: 'telesync', label: 'Telesync', arrType: 'all' as ArrType }, + { value: 'telecine', label: 'Telecine', arrType: 'all' as ArrType }, + { value: 'workprint', label: 'Workprint', arrType: 'all' as ArrType } +] as const; + +// Resolution values +export const RESOLUTION_VALUES = [ + { value: '360p', label: '360p', arrType: 'all' as ArrType }, + { value: '480p', label: '480p', arrType: 'all' as ArrType }, + { value: '540p', label: '540p', arrType: 'all' as ArrType }, + { value: '576p', label: '576p', arrType: 'all' as ArrType }, + { value: '720p', label: '720p', arrType: 'all' as ArrType }, + { value: '1080p', label: '1080p', arrType: 'all' as ArrType }, + { value: '2160p', label: '2160p', arrType: 'all' as ArrType } +] as const; + +// Quality modifier values (Radarr only) +export const QUALITY_MODIFIER_VALUES = [ + { value: 'none', label: 'None', arrType: 'radarr' as ArrType }, + { value: 'regional', label: 'Regional', arrType: 'radarr' as ArrType }, + { value: 'screener', label: 'Screener', arrType: 'radarr' as ArrType }, + { value: 'rawhd', label: 'RawHD', arrType: 'radarr' as ArrType }, + { value: 'brdisk', label: 'BRDISK', arrType: 'radarr' as ArrType }, + { value: 'remux', label: 'REMUX', arrType: 'radarr' as ArrType } +] as const; + +// Release type values (Sonarr only) +export const RELEASE_TYPE_VALUES = [ + { value: 'single_episode', label: 'Single Episode', arrType: 'sonarr' as ArrType }, + { value: 'multi_episode', label: 'Multi Episode', arrType: 'sonarr' as ArrType }, + { value: 'season_pack', label: 'Season Pack', arrType: 'sonarr' as ArrType } +] as const; + +// Indexer flag values +export const INDEXER_FLAG_VALUES = [ + { value: 'freeleech', label: 'Freeleech', arrType: 'all' as ArrType }, + { value: 'halfleech', label: 'Halfleech', arrType: 'all' as ArrType }, + { value: 'double_upload', label: 'Double Upload', arrType: 'all' as ArrType }, + { value: 'internal', label: 'Internal', arrType: 'all' as ArrType }, + { value: 'scene', label: 'Scene', arrType: 'all' as ArrType }, + { value: 'freeleech_75', label: 'Freeleech 75%', arrType: 'all' as ArrType }, + { value: 'freeleech_25', label: 'Freeleech 25%', arrType: 'all' as ArrType }, + { value: 'nuked', label: 'Nuked', arrType: 'all' as ArrType }, + { value: 'ptp_golden', label: 'PTP Golden', arrType: 'radarr' as ArrType }, + { value: 'ptp_approved', label: 'PTP Approved', arrType: 'radarr' as ArrType } +] as const; diff --git a/src/routes/custom-formats/[databaseId]/[id]/+layout.svelte b/src/routes/custom-formats/[databaseId]/[id]/+layout.svelte index 2c9a4a3..7f9231f 100644 --- a/src/routes/custom-formats/[databaseId]/[id]/+layout.svelte +++ b/src/routes/custom-formats/[databaseId]/[id]/+layout.svelte @@ -1,7 +1,7 @@ + + + {data.format.name} - Conditions - Profilarr + + +
+ +
+
+

Conditions

+

+ Define the conditions that must be met for this custom format to match a release. +

+
+ +
+ + + {#if draftConditions.length > 0} +
+
+ Drafts + {draftConditions.length} +
+ + {#each draftConditions as draft (draft.id)} + confirmDraft(draft)} + on:discard={() => discardDraft(draft.id)} + /> + {/each} +
+ {/if} + + + {#if data.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.id)} + handleRemove(condition.id)} + /> + {/each} +
+ {/each} + {/if} +
diff --git a/src/routes/custom-formats/[databaseId]/[id]/conditions/components/ConditionCard.svelte b/src/routes/custom-formats/[databaseId]/[id]/conditions/components/ConditionCard.svelte new file mode 100644 index 0000000..206e4b6 --- /dev/null +++ b/src/routes/custom-formats/[databaseId]/[id]/conditions/components/ConditionCard.svelte @@ -0,0 +1,327 @@ + + +
+ +
+ +
+ + +
+ + - + + GB +
+ {:else if condition.type === 'year'} +
+ + - + +
+ {:else} + + +
+ + +
+ + - + + GB +
+ {:else if condition.type === 'year'} +
+ + - + +
+ {:else} + +