From b04b4000b84a8a3ea8bb7b784fbbf9ff08451c6e Mon Sep 17 00:00:00 2001 From: Qstick Date: Sun, 23 Jan 2022 23:42:41 -0600 Subject: [PATCH] New: Custom Formats Co-Authored-By: ta264 --- .../src/Activity/Blocklist/BlocklistRow.js | 13 + .../History/Details/HistoryDetails.js | 24 +- frontend/src/Activity/History/HistoryRow.css | 2 +- frontend/src/Activity/History/HistoryRow.js | 19 +- frontend/src/Activity/Queue/QueueRow.js | 13 + frontend/src/App/AppRoutes.js | 6 + .../src/Components/Form/FormInputGroup.js | 4 + frontend/src/Components/Form/TextArea.css | 19 + frontend/src/Components/Form/TextArea.js | 172 ++++++ .../src/Components/Link/ClipboardButton.js | 16 +- .../Components/Page/Sidebar/PageSidebar.js | 4 + frontend/src/Episode/EpisodeFormats.js | 33 ++ .../src/Episode/History/EpisodeHistory.js | 11 + .../src/Episode/History/EpisodeHistoryRow.js | 27 + frontend/src/Helpers/Props/icons.js | 2 + frontend/src/Helpers/Props/inputTypes.js | 2 + .../InteractiveSearch/InteractiveSearch.js | 4 +- .../InteractiveSearchRow.css | 2 +- .../InteractiveSearch/InteractiveSearchRow.js | 30 +- frontend/src/Series/Details/EpisodeRow.js | 16 +- .../src/Series/Details/EpisodeRowConnector.js | 1 + .../History/SeriesHistoryModalContent.js | 11 + .../src/Series/History/SeriesHistoryRow.js | 27 + .../CustomFormatSettingsConnector.js | 32 ++ .../CustomFormats/CustomFormat.css | 38 ++ .../CustomFormats/CustomFormat.js | 173 ++++++ .../CustomFormats/CustomFormats.css | 21 + .../CustomFormats/CustomFormats.js | 115 ++++ .../CustomFormats/CustomFormatsConnector.js | 63 +++ .../CustomFormats/EditCustomFormatModal.js | 61 ++ .../EditCustomFormatModalConnector.js | 43 ++ .../EditCustomFormatModalContent.css | 27 + .../EditCustomFormatModalContent.js | 256 +++++++++ .../EditCustomFormatModalContentConnector.js | 102 ++++ .../CustomFormats/ExportCustomFormatModal.js | 61 ++ .../ExportCustomFormatModalContent.css | 5 + .../ExportCustomFormatModalContent.js | 84 +++ ...ExportCustomFormatModalContentConnector.js | 83 +++ .../CustomFormats/ImportCustomFormatModal.js | 61 ++ .../ImportCustomFormatModalContent.css | 5 + .../ImportCustomFormatModalContent.js | 151 +++++ ...ImportCustomFormatModalContentConnector.js | 145 +++++ .../Specifications/AddSpecificationItem.css | 44 ++ .../Specifications/AddSpecificationItem.js | 110 ++++ .../Specifications/AddSpecificationModal.js | 25 + .../AddSpecificationModalContent.css | 5 + .../AddSpecificationModalContent.js | 101 ++++ .../AddSpecificationModalContentConnector.js | 70 +++ .../AddSpecificationPresetMenuItem.js | 49 ++ .../Specifications/EditSpecificationModal.js | 27 + .../EditSpecificationModalConnector.js | 50 ++ .../EditSpecificationModalContent.css | 5 + .../EditSpecificationModalContent.js | 160 ++++++ .../EditSpecificationModalContentConnector.js | 78 +++ .../Specifications/Specification.css | 38 ++ .../Specifications/Specification.js | 139 +++++ .../MediaManagement/MediaManagement.js | 4 +- .../MediaManagement/Naming/NamingModal.js | 2 +- .../EditQualityProfileModalContent.css | 17 +- .../Quality/EditQualityProfileModalContent.js | 66 ++- ...EditQualityProfileModalContentConnector.js | 48 +- .../Quality/QualityProfileFormatItem.css | 45 ++ .../Quality/QualityProfileFormatItem.js | 68 +++ .../Quality/QualityProfileFormatItems.css | 31 + .../Quality/QualityProfileFormatItems.js | 159 ++++++ .../Release/EditReleaseProfileModalContent.js | 35 +- ...EditReleaseProfileModalContentConnector.js | 1 - .../Profiles/Release/ReleaseProfile.js | 25 - frontend/src/Settings/Settings.js | 11 + .../Settings/customFormatSpecifications.js | 193 +++++++ .../Store/Actions/Settings/customFormats.js | 108 ++++ .../src/Store/Actions/blocklistActions.js | 6 + frontend/src/Store/Actions/episodeActions.js | 5 + frontend/src/Store/Actions/historyActions.js | 12 +- frontend/src/Store/Actions/queueActions.js | 6 + frontend/src/Store/Actions/settingsActions.js | 10 + frontend/src/Utilities/State/getNextId.js | 5 + .../src/Utilities/State/getProviderState.js | 17 +- .../CustomFormats/CustomFormatsFixture.cs | 34 ++ .../171_add_custom_formatsFixture.cs | 535 ++++++++++++++++++ ...matAllowedByProfileSpecificationFixture.cs | 116 ++++ .../CutoffSpecificationFixture.cs | 454 ++++++++------- .../PrioritizeDownloadDecisionFixture.cs | 28 +- .../QueueSpecificationFixture.cs | 53 +- .../RssSync/DelaySpecificationFixture.cs | 3 +- .../RssSync/HistorySpecificationFixture.cs | 16 +- .../UpgradeDiskSpecificationFixture.cs | 26 +- .../UpgradeSpecificationFixture.cs | 24 +- .../MoveEpisodeFileFixture.cs | 4 +- ...isodeFilePreferredWordCalculatorFixture.cs | 152 ----- .../UpgradeSpecificationFixture.cs | 65 ++- .../FileNameBuilderTests/CleanTitleFixture.cs | 7 +- .../CleanTitleYearFixture.cs | 5 + .../EpisodeTitleCollapseFixture.cs | 7 +- .../FileNameBuilderFixture.cs | 5 + .../MultiEpisodeFixture.cs | 5 + .../MultiEpisodeTitleFixture.cs | 5 + .../OriginalTitleFixture.cs | 5 + .../PreferredWordsFixture.cs | 110 ---- .../ReplaceCharacterFixure.cs | 7 +- .../ReservedDeviceNameFixture.cs | 5 + .../FileNameBuilderTests/TitleTheFixture.cs | 5 + .../TitleTheYearFixture.cs | 5 + .../FileNameBuilderTests/TitleYearFixture.cs | 5 + .../TruncatedEpisodeTitlesFixture.cs | 5 + .../Profiles/QualityProfileServiceFixture.cs | 5 + .../PreferredWordService/CalculateFixture.cs | 94 --- .../GetMatchingPreferredWordsFixture.cs | 140 ----- .../Annotations}/SelectOption.cs | 2 +- .../Annotations/SelectOptionsConverter.cs | 9 + .../CustomFormats/CustomFormat.cs | 71 +++ .../CustomFormatCalculationService.cs | 148 +++++ .../CustomFormats/CustomFormatRepository.cs | 17 + .../CustomFormats/CustomFormatService.cs | 77 +++ .../Events/CustomFormatAddedEvent.cs | 14 + .../Events/CustomFormatDeletedEvent.cs | 14 + .../SpecificationMatchesGroup.cs | 13 + .../CustomFormatSpecificationBase.cs | 34 ++ .../ICustomFormatSpecification.cs | 17 + .../Specifications/RegexSpecificationBase.cs | 32 ++ .../ReleaseGroupSpecification.cs | 16 + .../ReleaseTitleSpecification.cs | 20 + .../Specifications/ResolutionSpecification.cs | 20 + .../Specifications/SizeSpecification.cs | 26 + .../Specifications/SourceSpecification.cs | 20 + .../Converters/CustomFormatIntConverter.cs | 20 + .../CustomFormatSpecificationConverter.cs | 74 +++ .../Migration/171_add_custom_formats.cs | 266 +++++++++ src/NzbDrone.Core/Datastore/TableMapping.cs | 6 + .../DownloadDecisionComparer.cs | 6 +- .../DecisionEngine/DownloadDecisionMaker.cs | 9 +- ...stomFormatAllowedByProfileSpecification.cs | 25 + .../Specifications/CutoffSpecification.cs | 14 +- .../Specifications/QueueSpecification.cs | 19 +- .../RssSync/HistorySpecification.cs | 26 +- .../Specifications/UpgradableSpecification.cs | 30 +- .../UpgradeDiskSpecification.cs | 15 +- .../AggregatePreferredWordScore.cs | 22 - .../History/DownloadHistoryService.cs | 2 +- .../TrackedDownloadService.cs | 10 + src/NzbDrone.Core/History/HistoryService.cs | 10 +- .../EpisodeFilePreferredWordCalculator.cs | 91 --- .../AggregatePreferredWordScore.cs | 37 -- .../Specifications/UpgradeSpecification.cs | 50 +- .../Organizer/FileNameBuilder.cs | 66 +-- .../Organizer/FileNameSampleService.cs | 37 +- .../Parser/Model/LocalEpisode.cs | 1 - .../Parser/Model/ParsedEpisodeInfo.cs | 5 + .../Parser/Model/RemoteEpisode.cs | 5 +- .../Profiles/ProfileFormatItem.cs | 14 + .../Profiles/Qualities/QualityProfile.cs | 14 + .../Qualities/QualityProfileRepository.cs | 30 +- .../Qualities/QualityProfileService.cs | 58 +- .../Releases/PreferredWordMatchResults.cs | 29 - .../Profiles/Releases/PreferredWordService.cs | 119 ---- .../Profiles/Releases/ReleaseProfile.cs | 4 - .../Releases/ReleaseProfileService.cs | 8 - .../Blocklist/BlocklistController.cs | 8 +- .../Blocklist/BlocklistResource.cs | 8 +- .../CustomFormats/CustomFormatController.cs | 143 +++++ .../CustomFormats/CustomFormatResource.cs | 58 ++ .../CustomFormatSpecificationSchema.cs | 36 ++ .../EpisodeFiles/EpisodeFileController.cs | 30 +- .../EpisodeFiles/EpisodeFileResource.cs | 3 + .../History/HistoryController.cs | 6 +- src/Sonarr.Api.V3/History/HistoryResource.cs | 6 +- src/Sonarr.Api.V3/Indexers/ReleaseResource.cs | 7 +- .../Quality/QualityProfileController.cs | 21 +- .../Quality/QualityProfileResource.cs | 39 +- .../Release/ReleaseProfileController.cs | 9 +- .../Release/ReleaseProfileResource.cs | 7 - src/Sonarr.Api.V3/Queue/QueueResource.cs | 3 + src/Sonarr.Http/ClientSchema/Field.cs | 1 + 173 files changed, 6400 insertions(+), 1346 deletions(-) create mode 100644 frontend/src/Components/Form/TextArea.css create mode 100644 frontend/src/Components/Form/TextArea.js create mode 100644 frontend/src/Episode/EpisodeFormats.js create mode 100644 frontend/src/Settings/CustomFormats/CustomFormatSettingsConnector.js create mode 100644 frontend/src/Settings/CustomFormats/CustomFormats/CustomFormat.css create mode 100644 frontend/src/Settings/CustomFormats/CustomFormats/CustomFormat.js create mode 100644 frontend/src/Settings/CustomFormats/CustomFormats/CustomFormats.css create mode 100644 frontend/src/Settings/CustomFormats/CustomFormats/CustomFormats.js create mode 100644 frontend/src/Settings/CustomFormats/CustomFormats/CustomFormatsConnector.js create mode 100644 frontend/src/Settings/CustomFormats/CustomFormats/EditCustomFormatModal.js create mode 100644 frontend/src/Settings/CustomFormats/CustomFormats/EditCustomFormatModalConnector.js create mode 100644 frontend/src/Settings/CustomFormats/CustomFormats/EditCustomFormatModalContent.css create mode 100644 frontend/src/Settings/CustomFormats/CustomFormats/EditCustomFormatModalContent.js create mode 100644 frontend/src/Settings/CustomFormats/CustomFormats/EditCustomFormatModalContentConnector.js create mode 100644 frontend/src/Settings/CustomFormats/CustomFormats/ExportCustomFormatModal.js create mode 100644 frontend/src/Settings/CustomFormats/CustomFormats/ExportCustomFormatModalContent.css create mode 100644 frontend/src/Settings/CustomFormats/CustomFormats/ExportCustomFormatModalContent.js create mode 100644 frontend/src/Settings/CustomFormats/CustomFormats/ExportCustomFormatModalContentConnector.js create mode 100644 frontend/src/Settings/CustomFormats/CustomFormats/ImportCustomFormatModal.js create mode 100644 frontend/src/Settings/CustomFormats/CustomFormats/ImportCustomFormatModalContent.css create mode 100644 frontend/src/Settings/CustomFormats/CustomFormats/ImportCustomFormatModalContent.js create mode 100644 frontend/src/Settings/CustomFormats/CustomFormats/ImportCustomFormatModalContentConnector.js create mode 100644 frontend/src/Settings/CustomFormats/CustomFormats/Specifications/AddSpecificationItem.css create mode 100644 frontend/src/Settings/CustomFormats/CustomFormats/Specifications/AddSpecificationItem.js create mode 100644 frontend/src/Settings/CustomFormats/CustomFormats/Specifications/AddSpecificationModal.js create mode 100644 frontend/src/Settings/CustomFormats/CustomFormats/Specifications/AddSpecificationModalContent.css create mode 100644 frontend/src/Settings/CustomFormats/CustomFormats/Specifications/AddSpecificationModalContent.js create mode 100644 frontend/src/Settings/CustomFormats/CustomFormats/Specifications/AddSpecificationModalContentConnector.js create mode 100644 frontend/src/Settings/CustomFormats/CustomFormats/Specifications/AddSpecificationPresetMenuItem.js create mode 100644 frontend/src/Settings/CustomFormats/CustomFormats/Specifications/EditSpecificationModal.js create mode 100644 frontend/src/Settings/CustomFormats/CustomFormats/Specifications/EditSpecificationModalConnector.js create mode 100644 frontend/src/Settings/CustomFormats/CustomFormats/Specifications/EditSpecificationModalContent.css create mode 100644 frontend/src/Settings/CustomFormats/CustomFormats/Specifications/EditSpecificationModalContent.js create mode 100644 frontend/src/Settings/CustomFormats/CustomFormats/Specifications/EditSpecificationModalContentConnector.js create mode 100644 frontend/src/Settings/CustomFormats/CustomFormats/Specifications/Specification.css create mode 100644 frontend/src/Settings/CustomFormats/CustomFormats/Specifications/Specification.js create mode 100644 frontend/src/Settings/Profiles/Quality/QualityProfileFormatItem.css create mode 100644 frontend/src/Settings/Profiles/Quality/QualityProfileFormatItem.js create mode 100644 frontend/src/Settings/Profiles/Quality/QualityProfileFormatItems.css create mode 100644 frontend/src/Settings/Profiles/Quality/QualityProfileFormatItems.js create mode 100644 frontend/src/Store/Actions/Settings/customFormatSpecifications.js create mode 100644 frontend/src/Store/Actions/Settings/customFormats.js create mode 100644 frontend/src/Utilities/State/getNextId.js create mode 100644 src/NzbDrone.Core.Test/CustomFormats/CustomFormatsFixture.cs create mode 100644 src/NzbDrone.Core.Test/Datastore/Migration/171_add_custom_formatsFixture.cs create mode 100644 src/NzbDrone.Core.Test/DecisionEngineTests/CustomFormatAllowedByProfileSpecificationFixture.cs delete mode 100644 src/NzbDrone.Core.Test/MediaFiles/EpisodeFilePreferredWordCalculatorFixture.cs delete mode 100644 src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/PreferredWordsFixture.cs delete mode 100644 src/NzbDrone.Core.Test/Profiles/Releases/PreferredWordService/CalculateFixture.cs delete mode 100644 src/NzbDrone.Core.Test/Profiles/Releases/PreferredWordService/GetMatchingPreferredWordsFixture.cs rename src/{Sonarr.Http/ClientSchema => NzbDrone.Core/Annotations}/SelectOption.cs (84%) create mode 100644 src/NzbDrone.Core/Annotations/SelectOptionsConverter.cs create mode 100644 src/NzbDrone.Core/CustomFormats/CustomFormat.cs create mode 100644 src/NzbDrone.Core/CustomFormats/CustomFormatCalculationService.cs create mode 100644 src/NzbDrone.Core/CustomFormats/CustomFormatRepository.cs create mode 100644 src/NzbDrone.Core/CustomFormats/CustomFormatService.cs create mode 100644 src/NzbDrone.Core/CustomFormats/Events/CustomFormatAddedEvent.cs create mode 100644 src/NzbDrone.Core/CustomFormats/Events/CustomFormatDeletedEvent.cs create mode 100644 src/NzbDrone.Core/CustomFormats/SpecificationMatchesGroup.cs create mode 100644 src/NzbDrone.Core/CustomFormats/Specifications/CustomFormatSpecificationBase.cs create mode 100644 src/NzbDrone.Core/CustomFormats/Specifications/ICustomFormatSpecification.cs create mode 100644 src/NzbDrone.Core/CustomFormats/Specifications/RegexSpecificationBase.cs create mode 100644 src/NzbDrone.Core/CustomFormats/Specifications/ReleaseGroupSpecification.cs create mode 100644 src/NzbDrone.Core/CustomFormats/Specifications/ReleaseTitleSpecification.cs create mode 100644 src/NzbDrone.Core/CustomFormats/Specifications/ResolutionSpecification.cs create mode 100644 src/NzbDrone.Core/CustomFormats/Specifications/SizeSpecification.cs create mode 100644 src/NzbDrone.Core/CustomFormats/Specifications/SourceSpecification.cs create mode 100644 src/NzbDrone.Core/Datastore/Converters/CustomFormatIntConverter.cs create mode 100644 src/NzbDrone.Core/Datastore/Converters/CustomFormatSpecificationConverter.cs create mode 100644 src/NzbDrone.Core/Datastore/Migration/171_add_custom_formats.cs create mode 100644 src/NzbDrone.Core/DecisionEngine/Specifications/CustomFormatAllowedByProfileSpecification.cs delete mode 100644 src/NzbDrone.Core/Download/Aggregation/Aggregators/AggregatePreferredWordScore.cs delete mode 100644 src/NzbDrone.Core/MediaFiles/EpisodeFilePreferredWordCalculator.cs delete mode 100644 src/NzbDrone.Core/MediaFiles/EpisodeImport/Aggregation/Aggregators/AggregatePreferredWordScore.cs create mode 100644 src/NzbDrone.Core/Profiles/ProfileFormatItem.cs delete mode 100644 src/NzbDrone.Core/Profiles/Releases/PreferredWordMatchResults.cs delete mode 100644 src/NzbDrone.Core/Profiles/Releases/PreferredWordService.cs create mode 100644 src/Sonarr.Api.V3/CustomFormats/CustomFormatController.cs create mode 100644 src/Sonarr.Api.V3/CustomFormats/CustomFormatResource.cs create mode 100644 src/Sonarr.Api.V3/CustomFormats/CustomFormatSpecificationSchema.cs diff --git a/frontend/src/Activity/Blocklist/BlocklistRow.js b/frontend/src/Activity/Blocklist/BlocklistRow.js index cea880fd4..571ba5d8d 100644 --- a/frontend/src/Activity/Blocklist/BlocklistRow.js +++ b/frontend/src/Activity/Blocklist/BlocklistRow.js @@ -5,6 +5,7 @@ import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellCo import TableRowCell from 'Components/Table/Cells/TableRowCell'; import TableSelectCell from 'Components/Table/Cells/TableSelectCell'; import TableRow from 'Components/Table/TableRow'; +import EpisodeFormats from 'Episode/EpisodeFormats'; import EpisodeLanguage from 'Episode/EpisodeLanguage'; import EpisodeQuality from 'Episode/EpisodeQuality'; import { icons, kinds } from 'Helpers/Props'; @@ -46,6 +47,7 @@ class BlocklistRow extends Component { sourceTitle, language, quality, + customFormats, date, protocol, indexer, @@ -120,6 +122,16 @@ class BlocklistRow extends Component { ); } + if (name === 'customFormats') { + return ( + + + + ); + } + if (name === 'date') { return ( : null } @@ -163,7 +163,7 @@ function HistoryDetails(props) { if (eventType === 'downloadFolderImported') { const { - preferredWordScore, + customFormatScore, droppedPath, importedPath } = data; @@ -197,10 +197,10 @@ function HistoryDetails(props) { } { - preferredWordScore && preferredWordScore !== '0' ? + customFormatScore && customFormatScore !== '0' ? : null } @@ -211,7 +211,7 @@ function HistoryDetails(props) { if (eventType === 'episodeFileDeleted') { const { reason, - preferredWordScore + customFormatScore } = data; let reasonMessage = ''; @@ -243,10 +243,10 @@ function HistoryDetails(props) { /> { - preferredWordScore && preferredWordScore !== '0' ? + customFormatScore && customFormatScore !== '0' ? : null } diff --git a/frontend/src/Activity/History/HistoryRow.css b/frontend/src/Activity/History/HistoryRow.css index 07a392d71..039804b63 100644 --- a/frontend/src/Activity/History/HistoryRow.css +++ b/frontend/src/Activity/History/HistoryRow.css @@ -10,7 +10,7 @@ width: 80px; } -.preferredWordScore { +.customFormatScore { composes: cell from '~Components/Table/Cells/TableRowCell.css'; width: 55px; diff --git a/frontend/src/Activity/History/HistoryRow.js b/frontend/src/Activity/History/HistoryRow.js index db16aec05..8ad1c07f7 100644 --- a/frontend/src/Activity/History/HistoryRow.js +++ b/frontend/src/Activity/History/HistoryRow.js @@ -5,6 +5,7 @@ import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellCo import TableRowCell from 'Components/Table/Cells/TableRowCell'; import TableRow from 'Components/Table/TableRow'; import episodeEntities from 'Episode/episodeEntities'; +import EpisodeFormats from 'Episode/EpisodeFormats'; import EpisodeLanguage from 'Episode/EpisodeLanguage'; import EpisodeQuality from 'Episode/EpisodeQuality'; import EpisodeTitleLink from 'Episode/EpisodeTitleLink'; @@ -61,6 +62,7 @@ class HistoryRow extends Component { language, languageCutoffNotMet, quality, + customFormats, qualityCutoffNotMet, eventType, sourceTitle, @@ -164,6 +166,16 @@ class HistoryRow extends Component { ); } + if (name === 'customFormats') { + return ( + + + + ); + } + if (name === 'date') { return ( - {formatPreferredWordScore(data.preferredWordScore)} + {formatPreferredWordScore(data.customFormatScore)} ); } @@ -269,6 +281,7 @@ HistoryRow.propTypes = { language: PropTypes.object.isRequired, languageCutoffNotMet: PropTypes.bool.isRequired, quality: PropTypes.object.isRequired, + customFormats: PropTypes.arrayOf(PropTypes.object), qualityCutoffNotMet: PropTypes.bool.isRequired, eventType: PropTypes.string.isRequired, sourceTitle: PropTypes.string.isRequired, diff --git a/frontend/src/Activity/Queue/QueueRow.js b/frontend/src/Activity/Queue/QueueRow.js index b42a669b3..67b8b9b3c 100644 --- a/frontend/src/Activity/Queue/QueueRow.js +++ b/frontend/src/Activity/Queue/QueueRow.js @@ -8,6 +8,7 @@ import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellCo import TableRowCell from 'Components/Table/Cells/TableRowCell'; import TableSelectCell from 'Components/Table/Cells/TableSelectCell'; import TableRow from 'Components/Table/TableRow'; +import EpisodeFormats from 'Episode/EpisodeFormats'; import EpisodeLanguage from 'Episode/EpisodeLanguage'; import EpisodeQuality from 'Episode/EpisodeQuality'; import EpisodeTitleLink from 'Episode/EpisodeTitleLink'; @@ -89,6 +90,7 @@ class QueueRow extends Component { episode, language, quality, + customFormats, protocol, indexer, outputPath, @@ -247,6 +249,16 @@ class QueueRow extends Component { ); } + if (name === 'customFormats') { + return ( + + + + ); + } + if (name === 'protocol') { return ( @@ -400,6 +412,7 @@ QueueRow.propTypes = { episode: PropTypes.object, language: PropTypes.object.isRequired, quality: PropTypes.object.isRequired, + customFormats: PropTypes.arrayOf(PropTypes.object), protocol: PropTypes.string.isRequired, indexer: PropTypes.string, outputPath: PropTypes.string, diff --git a/frontend/src/App/AppRoutes.js b/frontend/src/App/AppRoutes.js index ebee670e9..004d49381 100644 --- a/frontend/src/App/AppRoutes.js +++ b/frontend/src/App/AppRoutes.js @@ -13,6 +13,7 @@ import SeasonPassConnector from 'SeasonPass/SeasonPassConnector'; import SeriesDetailsPageConnector from 'Series/Details/SeriesDetailsPageConnector'; import SeriesEditorConnector from 'Series/Editor/SeriesEditorConnector'; import SeriesIndexConnector from 'Series/Index/SeriesIndexConnector'; +import CustomFormatSettingsConnector from 'Settings/CustomFormats/CustomFormatSettingsConnector'; import DownloadClientSettingsConnector from 'Settings/DownloadClients/DownloadClientSettingsConnector'; import GeneralSettingsConnector from 'Settings/General/GeneralSettingsConnector'; import ImportListSettingsConnector from 'Settings/ImportLists/ImportListSettingsConnector'; @@ -161,6 +162,11 @@ function AppRoutes(props) { component={QualityConnector} /> + + { + this._input = ref; + }; + + selectionChange() { + if (this._selectionTimeout) { + this._selectionTimeout = clearTimeout(this._selectionTimeout); + } + + this._selectionTimeout = setTimeout(() => { + const selectionStart = this._input.selectionStart; + const selectionEnd = this._input.selectionEnd; + + const selectionChanged = ( + this._selectionStart !== selectionStart || + this._selectionEnd !== selectionEnd + ); + + this._selectionStart = selectionStart; + this._selectionEnd = selectionEnd; + + if (this.props.onSelectionChange && selectionChanged) { + this.props.onSelectionChange(selectionStart, selectionEnd); + } + }, 10); + } + + // + // Listeners + + onChange = (event) => { + const { + name, + onChange + } = this.props; + + const payload = { + name, + value: event.target.value + }; + + onChange(payload); + }; + + onFocus = (event) => { + if (this.props.onFocus) { + this.props.onFocus(event); + } + + this.selectionChange(); + }; + + onKeyUp = () => { + this.selectionChange(); + }; + + onMouseDown = () => { + this._isMouseTarget = true; + }; + + onMouseUp = () => { + this.selectionChange(); + }; + + onDocumentMouseUp = () => { + if (this._isMouseTarget) { + this.selectionChange(); + } + + this._isMouseTarget = false; + }; + + // + // Render + + render() { + const { + className, + readOnly, + autoFocus, + placeholder, + name, + value, + hasError, + hasWarning, + onBlur + } = this.props; + + return ( +