From 77c1a42da18414112f955771dbbadb8b305e08b9 Mon Sep 17 00:00:00 2001 From: Bogdan Date: Tue, 11 Jul 2023 00:42:51 +0300 Subject: [PATCH] New: Bulk Manage Applications, Download Clients Co-authored-by: Qstick --- frontend/src/AlbumStudio/AlbumStudioFooter.js | 3 +- frontend/src/App/ModelBase.ts | 5 + frontend/src/App/State/AppSectionState.ts | 48 +++ frontend/src/App/State/AppState.ts | 41 +++ frontend/src/App/State/SettingsAppState.ts | 40 +++ frontend/src/App/State/TagsAppState.ts | 12 + frontend/src/Artist/Details/ArtistDetails.js | 5 +- .../src/Artist/Editor/ArtistEditorFooter.js | 2 +- frontend/src/Components/Alert.js | 8 +- .../Filter/Builder/FilterBuilderRow.js | 2 +- .../Builder/FilterBuilderRowValueTag.css | 2 + .../Builder/FilterBuilderRowValueTag.js | 11 +- .../src/Components/Form/FormInputGroup.js | 15 +- frontend/src/Components/Form/FormLabel.js | 26 +- .../MetadataProfileSelectInputConnector.js | 3 +- .../Form/MonitorAlbumsSelectInput.js | 3 +- .../Form/MonitorNewItemsSelectInput.js | 3 +- frontend/src/Components/Form/NumberInput.js | 2 +- .../QualityProfileSelectInputConnector.js | 3 +- .../Components/Form/SeriesTypeSelectInput.js | 3 +- frontend/src/Components/Form/TagInput.js | 22 +- .../src/Components/Form/TagInputConnector.js | 1 + frontend/src/Components/Form/TagInputInput.js | 8 +- frontend/src/Components/Form/TagInputTag.js | 8 +- frontend/src/Components/Label.js | 1 + frontend/src/Components/Link/IconButton.js | 2 + frontend/src/Components/Link/Link.js | 110 ------- frontend/src/Components/Link/Link.tsx | 96 ++++++ frontend/src/Components/Link/SpinnerButton.js | 1 + .../src/Components/Page/Header/PageHeader.js | 5 +- frontend/src/Components/Table/Column.ts | 12 + frontend/src/Components/Table/Table.js | 2 + frontend/src/Helpers/Hooks/usePrevious.tsx | 11 + frontend/src/Helpers/Hooks/useSelectState.tsx | 113 +++++++ frontend/src/Helpers/Props/SortDirection.ts | 6 + frontend/src/Helpers/Props/icons.js | 2 + .../DownloadClients/DownloadClientSettings.js | 26 +- .../Edit/ManageDownloadClientsEditModal.tsx | 28 ++ .../ManageDownloadClientsEditModalContent.css | 16 + ...geDownloadClientsEditModalContent.css.d.ts | 8 + .../ManageDownloadClientsEditModalContent.tsx | 180 +++++++++++ .../Manage/ManageDownloadClientsModal.tsx | 20 ++ .../ManageDownloadClientsModalContent.css | 16 + ...ManageDownloadClientsModalContent.css.d.ts | 9 + .../ManageDownloadClientsModalContent.tsx | 252 +++++++++++++++ .../Manage/ManageDownloadClientsModalRow.css | 11 + .../ManageDownloadClientsModalRow.css.d.ts | 13 + .../Manage/ManageDownloadClientsModalRow.tsx | 82 +++++ .../ImportLists/ImportListSettings.js | 25 +- .../Edit/ManageImportListsEditModal.tsx | 26 ++ .../ManageImportListsEditModalContent.css | 16 + ...ManageImportListsEditModalContent.css.d.ts | 8 + .../ManageImportListsEditModalContent.tsx | 158 ++++++++++ .../Manage/ManageImportListsModal.tsx | 20 ++ .../Manage/ManageImportListsModalContent.css | 16 + .../ManageImportListsModalContent.css.d.ts | 9 + .../Manage/ManageImportListsModalContent.tsx | 291 +++++++++++++++++ .../Manage/ManageImportListsModalRow.css | 10 + .../Manage/ManageImportListsModalRow.css.d.ts | 12 + .../Manage/ManageImportListsModalRow.tsx | 84 +++++ .../ImportLists/Manage/Tags/TagsModal.tsx | 22 ++ .../Manage/Tags/TagsModalContent.css | 12 + .../Manage/Tags/TagsModalContent.css.d.ts | 9 + .../Manage/Tags/TagsModalContent.tsx | 183 +++++++++++ .../src/Settings/Indexers/IndexerSettings.js | 26 +- .../Manage/Edit/ManageIndexersEditModal.tsx | 26 ++ .../Edit/ManageIndexersEditModalContent.css | 16 + .../ManageIndexersEditModalContent.css.d.ts | 8 + .../Edit/ManageIndexersEditModalContent.tsx | 178 +++++++++++ .../Indexers/Manage/ManageIndexersModal.tsx | 20 ++ .../Manage/ManageIndexersModalContent.css | 16 + .../ManageIndexersModalContent.css.d.ts | 9 + .../Manage/ManageIndexersModalContent.tsx | 295 ++++++++++++++++++ .../Manage/ManageIndexersModalRow.css | 11 + .../Manage/ManageIndexersModalRow.css.d.ts | 13 + .../Manage/ManageIndexersModalRow.tsx | 100 ++++++ .../Indexers/Manage/Tags/TagsModal.tsx | 22 ++ .../Indexers/Manage/Tags/TagsModalContent.css | 12 + .../Manage/Tags/TagsModalContent.css.d.ts | 9 + .../Indexers/Manage/Tags/TagsModalContent.tsx | 183 +++++++++++ .../Creators/createBulkEditItemHandler.js | 54 ++++ .../Creators/createBulkRemoveItemHandler.js | 48 +++ .../Store/Actions/Settings/downloadClients.js | 12 +- .../src/Store/Actions/Settings/importLists.js | 12 +- .../src/Store/Actions/Settings/indexers.js | 12 +- .../Selectors/createQualityProfileSelector.js | 11 + frontend/src/Utilities/String/translate.js | 2 +- frontend/src/typings/DownloadClient.ts | 28 ++ frontend/src/typings/ImportList.ts | 27 ++ frontend/src/typings/Indexer.ts | 28 ++ frontend/src/typings/Notification.ts | 24 ++ frontend/src/typings/UiSettings.ts | 6 + frontend/src/typings/props.ts | 5 + frontend/typings/Globals.d.ts | 10 + .../DownloadClientBulkResource.cs | 34 ++ .../DownloadClientController.cs | 7 +- .../ImportLists/ImportListBulkResource.cs | 32 ++ .../ImportLists/ImportListController.cs | 7 +- .../Indexers/IndexerBulkResource.cs | 34 ++ .../Indexers/IndexerController.cs | 7 +- .../Metadata/MetadataBulkResource.cs | 12 + .../Metadata/MetadataController.cs | 21 +- .../Notifications/NotificationBulkResource.cs | 12 + .../Notifications/NotificationController.cs | 21 +- src/Lidarr.Api.V1/ProviderBulkResource.cs | 39 +++ src/Lidarr.Api.V1/ProviderControllerBase.cs | 61 +++- src/NzbDrone.Core/Localization/Core/en.json | 38 ++- .../ThingiProvider/IProviderFactory.cs | 3 + .../ThingiProvider/ProviderFactory.cs | 27 ++ 109 files changed, 3588 insertions(+), 189 deletions(-) create mode 100644 frontend/src/App/ModelBase.ts create mode 100644 frontend/src/App/State/AppSectionState.ts create mode 100644 frontend/src/App/State/AppState.ts create mode 100644 frontend/src/App/State/SettingsAppState.ts create mode 100644 frontend/src/App/State/TagsAppState.ts delete mode 100644 frontend/src/Components/Link/Link.js create mode 100644 frontend/src/Components/Link/Link.tsx create mode 100644 frontend/src/Components/Table/Column.ts create mode 100644 frontend/src/Helpers/Hooks/usePrevious.tsx create mode 100644 frontend/src/Helpers/Hooks/useSelectState.tsx create mode 100644 frontend/src/Helpers/Props/SortDirection.ts create mode 100644 frontend/src/Settings/DownloadClients/DownloadClients/Manage/Edit/ManageDownloadClientsEditModal.tsx create mode 100644 frontend/src/Settings/DownloadClients/DownloadClients/Manage/Edit/ManageDownloadClientsEditModalContent.css create mode 100644 frontend/src/Settings/DownloadClients/DownloadClients/Manage/Edit/ManageDownloadClientsEditModalContent.css.d.ts create mode 100644 frontend/src/Settings/DownloadClients/DownloadClients/Manage/Edit/ManageDownloadClientsEditModalContent.tsx create mode 100644 frontend/src/Settings/DownloadClients/DownloadClients/Manage/ManageDownloadClientsModal.tsx create mode 100644 frontend/src/Settings/DownloadClients/DownloadClients/Manage/ManageDownloadClientsModalContent.css create mode 100644 frontend/src/Settings/DownloadClients/DownloadClients/Manage/ManageDownloadClientsModalContent.css.d.ts create mode 100644 frontend/src/Settings/DownloadClients/DownloadClients/Manage/ManageDownloadClientsModalContent.tsx create mode 100644 frontend/src/Settings/DownloadClients/DownloadClients/Manage/ManageDownloadClientsModalRow.css create mode 100644 frontend/src/Settings/DownloadClients/DownloadClients/Manage/ManageDownloadClientsModalRow.css.d.ts create mode 100644 frontend/src/Settings/DownloadClients/DownloadClients/Manage/ManageDownloadClientsModalRow.tsx create mode 100644 frontend/src/Settings/ImportLists/ImportLists/Manage/Edit/ManageImportListsEditModal.tsx create mode 100644 frontend/src/Settings/ImportLists/ImportLists/Manage/Edit/ManageImportListsEditModalContent.css create mode 100644 frontend/src/Settings/ImportLists/ImportLists/Manage/Edit/ManageImportListsEditModalContent.css.d.ts create mode 100644 frontend/src/Settings/ImportLists/ImportLists/Manage/Edit/ManageImportListsEditModalContent.tsx create mode 100644 frontend/src/Settings/ImportLists/ImportLists/Manage/ManageImportListsModal.tsx create mode 100644 frontend/src/Settings/ImportLists/ImportLists/Manage/ManageImportListsModalContent.css create mode 100644 frontend/src/Settings/ImportLists/ImportLists/Manage/ManageImportListsModalContent.css.d.ts create mode 100644 frontend/src/Settings/ImportLists/ImportLists/Manage/ManageImportListsModalContent.tsx create mode 100644 frontend/src/Settings/ImportLists/ImportLists/Manage/ManageImportListsModalRow.css create mode 100644 frontend/src/Settings/ImportLists/ImportLists/Manage/ManageImportListsModalRow.css.d.ts create mode 100644 frontend/src/Settings/ImportLists/ImportLists/Manage/ManageImportListsModalRow.tsx create mode 100644 frontend/src/Settings/ImportLists/ImportLists/Manage/Tags/TagsModal.tsx create mode 100644 frontend/src/Settings/ImportLists/ImportLists/Manage/Tags/TagsModalContent.css create mode 100644 frontend/src/Settings/ImportLists/ImportLists/Manage/Tags/TagsModalContent.css.d.ts create mode 100644 frontend/src/Settings/ImportLists/ImportLists/Manage/Tags/TagsModalContent.tsx create mode 100644 frontend/src/Settings/Indexers/Indexers/Manage/Edit/ManageIndexersEditModal.tsx create mode 100644 frontend/src/Settings/Indexers/Indexers/Manage/Edit/ManageIndexersEditModalContent.css create mode 100644 frontend/src/Settings/Indexers/Indexers/Manage/Edit/ManageIndexersEditModalContent.css.d.ts create mode 100644 frontend/src/Settings/Indexers/Indexers/Manage/Edit/ManageIndexersEditModalContent.tsx create mode 100644 frontend/src/Settings/Indexers/Indexers/Manage/ManageIndexersModal.tsx create mode 100644 frontend/src/Settings/Indexers/Indexers/Manage/ManageIndexersModalContent.css create mode 100644 frontend/src/Settings/Indexers/Indexers/Manage/ManageIndexersModalContent.css.d.ts create mode 100644 frontend/src/Settings/Indexers/Indexers/Manage/ManageIndexersModalContent.tsx create mode 100644 frontend/src/Settings/Indexers/Indexers/Manage/ManageIndexersModalRow.css create mode 100644 frontend/src/Settings/Indexers/Indexers/Manage/ManageIndexersModalRow.css.d.ts create mode 100644 frontend/src/Settings/Indexers/Indexers/Manage/ManageIndexersModalRow.tsx create mode 100644 frontend/src/Settings/Indexers/Indexers/Manage/Tags/TagsModal.tsx create mode 100644 frontend/src/Settings/Indexers/Indexers/Manage/Tags/TagsModalContent.css create mode 100644 frontend/src/Settings/Indexers/Indexers/Manage/Tags/TagsModalContent.css.d.ts create mode 100644 frontend/src/Settings/Indexers/Indexers/Manage/Tags/TagsModalContent.tsx create mode 100644 frontend/src/Store/Actions/Creators/createBulkEditItemHandler.js create mode 100644 frontend/src/Store/Actions/Creators/createBulkRemoveItemHandler.js create mode 100644 frontend/src/typings/DownloadClient.ts create mode 100644 frontend/src/typings/ImportList.ts create mode 100644 frontend/src/typings/Indexer.ts create mode 100644 frontend/src/typings/Notification.ts create mode 100644 frontend/src/typings/UiSettings.ts create mode 100644 frontend/src/typings/props.ts create mode 100644 src/Lidarr.Api.V1/DownloadClient/DownloadClientBulkResource.cs create mode 100644 src/Lidarr.Api.V1/ImportLists/ImportListBulkResource.cs create mode 100644 src/Lidarr.Api.V1/Indexers/IndexerBulkResource.cs create mode 100644 src/Lidarr.Api.V1/Metadata/MetadataBulkResource.cs create mode 100644 src/Lidarr.Api.V1/Notifications/NotificationBulkResource.cs create mode 100644 src/Lidarr.Api.V1/ProviderBulkResource.cs diff --git a/frontend/src/AlbumStudio/AlbumStudioFooter.js b/frontend/src/AlbumStudio/AlbumStudioFooter.js index 0d649e635..1e56b9cf4 100644 --- a/frontend/src/AlbumStudio/AlbumStudioFooter.js +++ b/frontend/src/AlbumStudio/AlbumStudioFooter.js @@ -6,6 +6,7 @@ import SelectInput from 'Components/Form/SelectInput'; import SpinnerButton from 'Components/Link/SpinnerButton'; import PageContentFooter from 'Components/Page/PageContentFooter'; import { kinds } from 'Helpers/Props'; +import translate from 'Utilities/String/translate'; import styles from './AlbumStudioFooter.css'; const NO_CHANGE = 'noChange'; @@ -87,7 +88,7 @@ class AlbumStudioFooter extends Component { } = this.state; const monitoredOptions = [ - { key: NO_CHANGE, value: 'No Change', disabled: true }, + { key: NO_CHANGE, value: translate('NoChange'), disabled: true }, { key: 'monitored', value: 'Monitored' }, { key: 'unmonitored', value: 'Unmonitored' } ]; diff --git a/frontend/src/App/ModelBase.ts b/frontend/src/App/ModelBase.ts new file mode 100644 index 000000000..187b12fb2 --- /dev/null +++ b/frontend/src/App/ModelBase.ts @@ -0,0 +1,5 @@ +interface ModelBase { + id: number; +} + +export default ModelBase; diff --git a/frontend/src/App/State/AppSectionState.ts b/frontend/src/App/State/AppSectionState.ts new file mode 100644 index 000000000..d511963fc --- /dev/null +++ b/frontend/src/App/State/AppSectionState.ts @@ -0,0 +1,48 @@ +import SortDirection from 'Helpers/Props/SortDirection'; + +export interface Error { + responseJSON: { + message: string; + }; +} + +export interface AppSectionDeleteState { + isDeleting: boolean; + deleteError: Error; +} + +export interface AppSectionSaveState { + isSaving: boolean; + saveError: Error; +} + +export interface PagedAppSectionState { + pageSize: number; +} + +export interface AppSectionSchemaState { + isSchemaFetching: boolean; + isSchemaPopulated: boolean; + schemaError: Error; + schema: { + items: T[]; + }; +} + +export interface AppSectionItemState { + isFetching: boolean; + isPopulated: boolean; + error: Error; + item: T; +} + +interface AppSectionState { + isFetching: boolean; + isPopulated: boolean; + error: Error; + items: T[]; + sortKey: string; + sortDirection: SortDirection; +} + +export default AppSectionState; diff --git a/frontend/src/App/State/AppState.ts b/frontend/src/App/State/AppState.ts new file mode 100644 index 000000000..8c8b99fba --- /dev/null +++ b/frontend/src/App/State/AppState.ts @@ -0,0 +1,41 @@ +import SettingsAppState from './SettingsAppState'; +import TagsAppState from './TagsAppState'; + +interface FilterBuilderPropOption { + id: string; + name: string; +} + +export interface FilterBuilderProp { + name: string; + label: string; + type: string; + valueType?: string; + optionsSelector?: (items: T[]) => FilterBuilderPropOption[]; +} + +export interface PropertyFilter { + key: string; + value: boolean | string | number | string[] | number[]; + type: string; +} + +export interface Filter { + key: string; + label: string; + filers: PropertyFilter[]; +} + +export interface CustomFilter { + id: number; + type: string; + label: string; + filers: PropertyFilter[]; +} + +interface AppState { + settings: SettingsAppState; + tags: TagsAppState; +} + +export default AppState; diff --git a/frontend/src/App/State/SettingsAppState.ts b/frontend/src/App/State/SettingsAppState.ts new file mode 100644 index 000000000..4c0680956 --- /dev/null +++ b/frontend/src/App/State/SettingsAppState.ts @@ -0,0 +1,40 @@ +import AppSectionState, { + AppSectionDeleteState, + AppSectionSaveState, +} from 'App/State/AppSectionState'; +import DownloadClient from 'typings/DownloadClient'; +import ImportList from 'typings/ImportList'; +import Indexer from 'typings/Indexer'; +import Notification from 'typings/Notification'; +import { UiSettings } from 'typings/UiSettings'; + +export interface DownloadClientAppState + extends AppSectionState, + AppSectionDeleteState, + AppSectionSaveState {} + +export interface ImportListAppState + extends AppSectionState, + AppSectionDeleteState, + AppSectionSaveState {} + +export interface IndexerAppState + extends AppSectionState, + AppSectionDeleteState, + AppSectionSaveState {} + +export interface NotificationAppState + extends AppSectionState, + AppSectionDeleteState {} + +export type UiSettingsAppState = AppSectionState; + +interface SettingsAppState { + downloadClients: DownloadClientAppState; + importLists: ImportListAppState; + indexers: IndexerAppState; + notifications: NotificationAppState; + uiSettings: UiSettingsAppState; +} + +export default SettingsAppState; diff --git a/frontend/src/App/State/TagsAppState.ts b/frontend/src/App/State/TagsAppState.ts new file mode 100644 index 000000000..d1f1d5a2f --- /dev/null +++ b/frontend/src/App/State/TagsAppState.ts @@ -0,0 +1,12 @@ +import ModelBase from 'App/ModelBase'; +import AppSectionState, { + AppSectionDeleteState, +} from 'App/State/AppSectionState'; + +export interface Tag extends ModelBase { + label: string; +} + +interface TagsAppState extends AppSectionState, AppSectionDeleteState {} + +export default TagsAppState; diff --git a/frontend/src/Artist/Details/ArtistDetails.js b/frontend/src/Artist/Details/ArtistDetails.js index b6f5179ac..7ff777f1e 100644 --- a/frontend/src/Artist/Details/ArtistDetails.js +++ b/frontend/src/Artist/Details/ArtistDetails.js @@ -415,10 +415,7 @@ class ArtistDetails extends Component { name={icons.ARROW_UP} size={30} title={translate('GoToArtistListing')} - to={{ - pathname: '/', - state: { restoreScrollPosition: true } - }} + to={'/'} /> a.value.localeCompare(b.value)); const ValueComponent = getRowValueConnector(selectedFilterBuilderProp); diff --git a/frontend/src/Components/Filter/Builder/FilterBuilderRowValueTag.css b/frontend/src/Components/Filter/Builder/FilterBuilderRowValueTag.css index f77146a06..807f383dd 100644 --- a/frontend/src/Components/Filter/Builder/FilterBuilderRowValueTag.css +++ b/frontend/src/Components/Filter/Builder/FilterBuilderRowValueTag.css @@ -1,4 +1,6 @@ .tag { + display: flex; + &.isLastTag { .or { display: none; diff --git a/frontend/src/Components/Filter/Builder/FilterBuilderRowValueTag.js b/frontend/src/Components/Filter/Builder/FilterBuilderRowValueTag.js index 4408c87b3..6b5846594 100644 --- a/frontend/src/Components/Filter/Builder/FilterBuilderRowValueTag.js +++ b/frontend/src/Components/Filter/Builder/FilterBuilderRowValueTag.js @@ -6,7 +6,7 @@ import styles from './FilterBuilderRowValueTag.css'; function FilterBuilderRowValueTag(props) { return ( - { - !props.isLastTag && - + props.isLastTag ? + null : +
or - +
} -
+ ); } diff --git a/frontend/src/Components/Form/FormInputGroup.js b/frontend/src/Components/Form/FormInputGroup.js index ade8be204..7e6b0c7c9 100644 --- a/frontend/src/Components/Form/FormInputGroup.js +++ b/frontend/src/Components/Form/FormInputGroup.js @@ -1,7 +1,7 @@ import PropTypes from 'prop-types'; import React from 'react'; import Link from 'Components/Link/Link'; -import { inputTypes } from 'Helpers/Props'; +import { inputTypes, kinds } from 'Helpers/Props'; import translate from 'Utilities/String/translate'; import AlbumReleaseSelectInputConnector from './AlbumReleaseSelectInputConnector'; import AutoCompleteInput from './AutoCompleteInput'; @@ -273,16 +273,27 @@ FormInputGroup.propTypes = { className: PropTypes.string.isRequired, containerClassName: PropTypes.string.isRequired, inputClassName: PropTypes.string, + name: PropTypes.string.isRequired, + value: PropTypes.any, + values: PropTypes.arrayOf(PropTypes.any), type: PropTypes.string.isRequired, + kind: PropTypes.oneOf(kinds.all), + min: PropTypes.number, + max: PropTypes.number, unit: PropTypes.string, buttons: PropTypes.oneOfType([PropTypes.node, PropTypes.arrayOf(PropTypes.node)]), helpText: PropTypes.string, helpTexts: PropTypes.arrayOf(PropTypes.string), helpTextWarning: PropTypes.string, helpLink: PropTypes.string, + autoFocus: PropTypes.bool, + includeNoChange: PropTypes.bool, + includeNoChangeDisabled: PropTypes.bool, + selectedValueOptions: PropTypes.object, pending: PropTypes.bool, errors: PropTypes.arrayOf(PropTypes.object), - warnings: PropTypes.arrayOf(PropTypes.object) + warnings: PropTypes.arrayOf(PropTypes.object), + onChange: PropTypes.func.isRequired }; FormInputGroup.defaultProps = { diff --git a/frontend/src/Components/Form/FormLabel.js b/frontend/src/Components/Form/FormLabel.js index d419039b3..d4a4bcffc 100644 --- a/frontend/src/Components/Form/FormLabel.js +++ b/frontend/src/Components/Form/FormLabel.js @@ -4,16 +4,18 @@ import React from 'react'; import { sizes } from 'Helpers/Props'; import styles from './FormLabel.css'; -function FormLabel({ - children, - className, - errorClassName, - size, - name, - hasError, - isAdvanced, - ...otherProps -}) { +function FormLabel(props) { + const { + children, + className, + errorClassName, + size, + name, + hasError, + isAdvanced, + ...otherProps + } = props; + return (