diff --git a/backend/app/data/utils.py b/backend/app/data/utils.py index e1b3617..1cdea1b 100644 --- a/backend/app/data/utils.py +++ b/backend/app/data/utils.py @@ -29,7 +29,8 @@ PROFILE_FIELDS = [ "custom_formats", # Array of {name, score} objects "qualities", # Array of strings "upgrade_until", - "language" + "language", + "tweaks" ] # Category mappings diff --git a/frontend/src/components/profile/ProfileModal.jsx b/frontend/src/components/profile/ProfileModal.jsx index ec31ef6..203dd7d 100644 --- a/frontend/src/components/profile/ProfileModal.jsx +++ b/frontend/src/components/profile/ProfileModal.jsx @@ -8,8 +8,18 @@ import ProfileGeneralTab from './ProfileGeneralTab'; import ProfileScoringTab from './ProfileScoringTab'; import ProfileQualitiesTab from './ProfileQualitiesTab'; import ProfileLanguagesTab from './ProfileLangaugesTab'; +import ProfileTweaksTab from './ProfileTweaksTab'; import QUALITIES from '../../constants/qualities'; +const DEFAULT_TWEAKS = { + preferFreeleech: true, + allowLosslessAudio: true, + allowDVNoFallback: false, + allowBleedingEdgeCodecs: false, + allowPrereleases: false, + languageStrictness: 'disabled' +}; + function unsanitize(text) { if (!text) return ''; return text.replace(/\\:/g, ':').replace(/\\n/g, '\n'); @@ -61,11 +71,15 @@ function ProfileModal({ // Language state const [selectedLanguage, setSelectedLanguage] = useState('any'); + // Tweaks state + const [tweaks, setTweaks] = useState({}); + const tabs = [ {id: 'general', label: 'General'}, {id: 'scoring', label: 'Scoring'}, {id: 'qualities', label: 'Qualities'}, - {id: 'languages', label: 'Languages'} + {id: 'languages', label: 'Languages'}, + {id: 'tweaks', label: 'Tweaks'} ]; useEffect(() => { @@ -122,6 +136,12 @@ function ProfileModal({ }); setTagScores(initialTagScores); + // Tweaks + setTweaks({ + ...DEFAULT_TWEAKS, + ...(content.tweaks || {}) + }); + // Qualities setup - include all qualities, set enabled status const allQualitiesMap = {}; // Map of all qualities by id QUALITIES.forEach(quality => { @@ -149,7 +169,7 @@ function ProfileModal({ newSortedQualities.push({ id: q.id, name: q.name, - description: q.description, // Changed: removed || '' to preserve null/undefined + description: q.description, qualities: groupQualities, enabled: true }); @@ -266,6 +286,9 @@ function ProfileModal({ id: defaultQuality.id, name: defaultQuality.name }); + + // Initialize empty tweaks + setTweaks(DEFAULT_TWEAKS); } setLoading(false); @@ -346,7 +369,8 @@ function ProfileModal({ }) } : null, - language: selectedLanguage + language: selectedLanguage, + tweaks }; if (isCloning || !initialProfile) { @@ -385,7 +409,6 @@ function ProfileModal({ setError('An unexpected error occurred'); } }; - const handleDelete = async () => { if (!initialProfile) return; @@ -547,6 +570,12 @@ function ProfileModal({ onLanguageChange={setSelectedLanguage} /> )} + {activeTab === 'tweaks' && ( + + )} )} diff --git a/frontend/src/components/profile/ProfileTweaksTab.jsx b/frontend/src/components/profile/ProfileTweaksTab.jsx new file mode 100644 index 0000000..3a957a4 --- /dev/null +++ b/frontend/src/components/profile/ProfileTweaksTab.jsx @@ -0,0 +1,233 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import {InfoIcon, AlertTriangle} from 'lucide-react'; +import {LANGUAGES} from '@constants/languages'; + +const ProfileTweaksTab = ({tweaks, onTweaksChange}) => { + const handleTweakChange = key => { + onTweaksChange({ + ...tweaks, + [key]: !tweaks[key] + }); + }; + + const handleLanguageStrictness = value => { + onTweaksChange({ + ...tweaks, + languageStrictness: value + }); + }; + + return ( +
+
+
+ +

+ Tweaks are custom changes that can be toggled according + to your preference. These settings are profile-specific + and won't create merge conflicts when synchronizing with + remote repositories. Use tweaks to fine-tune your + profile's behavior without affecting the core + configuration. +

+
+ +
+ {/* Allow Dolby Vision without Fallback */} +
handleTweakChange('allowDVNoFallback')} + className={` + p-4 rounded-lg cursor-pointer select-none + border transition-colors duration-200 + ${ + tweaks.allowDVNoFallback + ? 'border-blue-200 dark:border-blue-800 bg-blue-50 dark:bg-blue-900/20' + : 'border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800' + } + hover:border-blue-500 dark:hover:border-blue-400 + `}> +
+

+ Allow Dolby Vision without Fallback +

+

+ Allow Dolby Vision releases that don't include + HDR10 fallback. These may display incorrectly on + non-Dolby Vision displays. +

+
+ +

+ Only enable if your display supports Dolby + Vision +

+
+
+
+ + {/* Allow Bleeding Edge Codecs */} +
+ handleTweakChange('allowBleedingEdgeCodecs') + } + className={` + p-4 rounded-lg cursor-pointer select-none + border transition-colors duration-200 + ${ + tweaks.allowBleedingEdgeCodecs + ? 'border-blue-200 dark:border-blue-800 bg-blue-50 dark:bg-blue-900/20' + : 'border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800' + } + hover:border-blue-500 dark:hover:border-blue-400 + `}> +
+

+ Allow Bleeding Edge Codecs +

+

+ Allow releases using newer codecs like AV1 and + H.266/VVC. These may offer better compression + but have limited hardware support. +

+
+
+ + {/* Allow Lossless Audio */} +
handleTweakChange('allowLosslessAudio')} + className={` + p-4 rounded-lg cursor-pointer select-none + border transition-colors duration-200 + ${ + tweaks.allowLosslessAudio ?? true + ? 'border-blue-200 dark:border-blue-800 bg-blue-50 dark:bg-blue-900/20' + : 'border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800' + } + hover:border-blue-500 dark:hover:border-blue-400 + `}> +
+

+ Allow Lossless Audio +

+

+ Allow high-quality lossless audio formats + including TrueHD + Atmos, DTS-HD MA, DTS-X, + FLAC, and PCM. +

+
+ +

+ May skip better quality releases if disabled +

+
+
+
+ + {/* Allow Prereleases */} +
handleTweakChange('allowPrereleases')} + className={` + p-4 rounded-lg cursor-pointer select-none + border transition-colors duration-200 + ${ + tweaks.allowPrereleases + ? 'border-blue-200 dark:border-blue-800 bg-blue-50 dark:bg-blue-900/20' + : 'border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800' + } + hover:border-blue-500 dark:hover:border-blue-400 + `}> +
+

+ Allow Prereleases +

+

+ Allow early releases like CAMs, Telecines, + Telesyncs, and Screeners. These are typically + available before official releases but at lower + quality. +

+
+
+ + {/* Prefer Freeleech */} +
handleTweakChange('preferFreeleech')} + className={` + p-4 rounded-lg cursor-pointer select-none + border transition-colors duration-200 + ${ + tweaks.preferFreeleech + ? 'border-blue-200 dark:border-blue-800 bg-blue-50 dark:bg-blue-900/20' + : 'border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800' + } + hover:border-blue-500 dark:hover:border-blue-400 + `}> +
+

+ Prefer Freeleech +

+

+ Prioritize releases tagged as freeleech when + choosing between different indexers' releases. +

+
+
+ + {/* Language Strictness */} +
+
+
+

+ Language Strictness +

+

+ Set strict language requirements for + releases. Select 'Disabled' to use normal + language preferences. +

+
+ +
+
+
+
+
+ ); +}; + +ProfileTweaksTab.propTypes = { + tweaks: PropTypes.object.isRequired, + onTweaksChange: PropTypes.func.isRequired +}; + +export default ProfileTweaksTab;