diff --git a/frontend/src/Settings/ImportLists/ImportLists/ImportList.css b/frontend/src/Settings/ImportLists/ImportLists/ImportList.css index 4796748f71..c811b765ab 100644 --- a/frontend/src/Settings/ImportLists/ImportLists/ImportList.css +++ b/frontend/src/Settings/ImportLists/ImportLists/ImportList.css @@ -4,6 +4,11 @@ width: 290px; } +.nameContainer { + display: flex; + justify-content: space-between; +} + .name { @add-mixin truncate; @@ -12,6 +17,12 @@ font-size: 24px; } +.cloneButton { + composes: button from '~Components/Link/IconButton.css'; + + height: 36px; +} + .enabled { display: flex; flex-wrap: wrap; diff --git a/frontend/src/Settings/ImportLists/ImportLists/ImportList.css.d.ts b/frontend/src/Settings/ImportLists/ImportLists/ImportList.css.d.ts index a0f1a4158f..c8fa34ffe0 100644 --- a/frontend/src/Settings/ImportLists/ImportLists/ImportList.css.d.ts +++ b/frontend/src/Settings/ImportLists/ImportLists/ImportList.css.d.ts @@ -1,9 +1,11 @@ // This file is automatically generated. // Please do not change this file! interface CssExports { + 'cloneButton': string; 'enabled': string; 'list': string; 'name': string; + 'nameContainer': string; } export const cssExports: CssExports; export default cssExports; diff --git a/frontend/src/Settings/ImportLists/ImportLists/ImportList.tsx b/frontend/src/Settings/ImportLists/ImportLists/ImportList.tsx index 1f2a3d7844..de8449dd61 100644 --- a/frontend/src/Settings/ImportLists/ImportLists/ImportList.tsx +++ b/frontend/src/Settings/ImportLists/ImportLists/ImportList.tsx @@ -2,9 +2,10 @@ import React, { useCallback, useState } from 'react'; import { useDispatch } from 'react-redux'; import Card from 'Components/Card'; import Label from 'Components/Label'; +import IconButton from 'Components/Link/IconButton'; import ConfirmModal from 'Components/Modal/ConfirmModal'; import TagList from 'Components/TagList'; -import { kinds } from 'Helpers/Props'; +import { icons, kinds } from 'Helpers/Props'; import { deleteImportList } from 'Store/Actions/settingsActions'; import useTags from 'Tags/useTags'; import formatShortTimeSpan from 'Utilities/Date/formatShortTimeSpan'; @@ -19,6 +20,7 @@ interface ImportListProps { enableAuto: boolean; tags: number[]; minRefreshInterval: string; + onCloneImportListPress: (id: number) => void; } function ImportList({ @@ -28,6 +30,7 @@ function ImportList({ enableAuto, tags, minRefreshInterval, + onCloneImportListPress, }: ImportListProps) { const dispatch = useDispatch(); const tagList = useTags(); @@ -59,13 +62,26 @@ function ImportList({ dispatch(deleteImportList({ id })); }, [id, dispatch]); + const handleCloneImportListPress = useCallback(() => { + onCloneImportListPress(id); + }, [id, onCloneImportListPress]); + return ( -
{name}
+
+
{name}
+ + +
{enabled ? ( diff --git a/frontend/src/Settings/ImportLists/ImportLists/ImportLists.tsx b/frontend/src/Settings/ImportLists/ImportLists/ImportLists.tsx index b43691ca18..28c577b114 100644 --- a/frontend/src/Settings/ImportLists/ImportLists/ImportLists.tsx +++ b/frontend/src/Settings/ImportLists/ImportLists/ImportLists.tsx @@ -7,7 +7,10 @@ import Icon from 'Components/Icon'; import PageSectionContent from 'Components/Page/PageSectionContent'; import { icons } from 'Helpers/Props'; import { fetchRootFolders } from 'Store/Actions/rootFolderActions'; -import { fetchImportLists } from 'Store/Actions/settingsActions'; +import { + cloneImportList, + fetchImportLists, +} from 'Store/Actions/settingsActions'; import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector'; import ImportListModel from 'typings/ImportList'; import sortByProp from 'Utilities/Array/sortByProp'; @@ -49,6 +52,14 @@ function ImportLists() { setIsEditImportListModalOpen(false); }, []); + const handleCloneImportListPress = useCallback( + (id: number) => { + dispatch(cloneImportList({ id })); + setIsEditImportListModalOpen(true); + }, + [dispatch] + ); + useEffect(() => { dispatch(fetchImportLists()); dispatch(fetchRootFolders()); @@ -64,7 +75,13 @@ function ImportLists() { >
{items.map((item) => { - return ; + return ( + + ); })} diff --git a/frontend/src/Store/Actions/Settings/importLists.js b/frontend/src/Store/Actions/Settings/importLists.js index aaee9fa22c..3a62a4d9d0 100644 --- a/frontend/src/Store/Actions/Settings/importLists.js +++ b/frontend/src/Store/Actions/Settings/importLists.js @@ -10,7 +10,10 @@ import createTestProviderHandler, { createCancelTestProviderHandler } from 'Stor import createSetProviderFieldValueReducer from 'Store/Actions/Creators/Reducers/createSetProviderFieldValueReducer'; import createSetSettingValueReducer from 'Store/Actions/Creators/Reducers/createSetSettingValueReducer'; import { createThunk } from 'Store/thunks'; +import getSectionState from 'Utilities/State/getSectionState'; import selectProviderSchema from 'Utilities/State/selectProviderSchema'; +import updateSectionState from 'Utilities/State/updateSectionState'; +import translate from 'Utilities/String/translate'; // // Variables @@ -31,9 +34,9 @@ export const DELETE_IMPORT_LIST = 'settings/importLists/deleteImportList'; export const TEST_IMPORT_LIST = 'settings/importLists/testImportList'; export const CANCEL_TEST_IMPORT_LIST = 'settings/importLists/cancelTestImportList'; export const TEST_ALL_IMPORT_LISTS = 'settings/importLists/testAllImportLists'; - -export const BULK_DELETE_IMPORT_LISTS = 'settings/importLists/bulkDeleteImportLists'; export const BULK_EDIT_IMPORT_LISTS = 'settings/importLists/bulkEditImportLists'; +export const BULK_DELETE_IMPORT_LISTS = 'settings/importLists/bulkDeleteImportLists'; +export const CLONE_IMPORT_LIST = 'settings/importLists/cloneImportList'; // // Action Creators @@ -48,9 +51,8 @@ export const deleteImportList = createThunk(DELETE_IMPORT_LIST); export const testImportList = createThunk(TEST_IMPORT_LIST); export const cancelTestImportList = createThunk(CANCEL_TEST_IMPORT_LIST); export const testAllImportLists = createThunk(TEST_ALL_IMPORT_LISTS); - -export const bulkDeleteImportLists = createThunk(BULK_DELETE_IMPORT_LISTS); export const bulkEditImportLists = createThunk(BULK_EDIT_IMPORT_LISTS); +export const bulkDeleteImportLists = createThunk(BULK_DELETE_IMPORT_LISTS); export const setImportListValue = createAction(SET_IMPORT_LIST_VALUE, (payload) => { return { @@ -66,6 +68,8 @@ export const setImportListFieldValue = createAction(SET_IMPORT_LIST_FIELD_VALUE, }; }); +export const cloneImportList = createAction(CLONE_IMPORT_LIST); + // // Details @@ -128,6 +132,37 @@ export default { return selectedSchema; }); + }, + + [CLONE_IMPORT_LIST]: (state, { payload }) => { + const id = payload.id; + const newState = getSectionState(state, section); + const item = newState.items.find((i) => i.id === id); + + const selectedSchema = { ...item }; + delete selectedSchema.id; + delete selectedSchema.name; + + // Use selectedSchema so `createProviderSettingsSelector` works properly + selectedSchema.fields = selectedSchema.fields.map((field) => { + const newField = { ...field }; + + if (newField.privacy === 'apiKey' || newField.privacy === 'password') { + newField.value = ''; + } + + return newField; + }); + + newState.selectedSchema = selectedSchema; + + const pendingChanges = { ...item, id: 0 }; + delete pendingChanges.id; + + pendingChanges.name = translate('DefaultNameCopiedImportList', { name: pendingChanges.name }); + newState.pendingChanges = pendingChanges; + + return updateSectionState(state, section, newState); } } diff --git a/src/NzbDrone.Core/Localization/Core/en.json b/src/NzbDrone.Core/Localization/Core/en.json index 7f581140bd..d9d28af40c 100644 --- a/src/NzbDrone.Core/Localization/Core/en.json +++ b/src/NzbDrone.Core/Localization/Core/en.json @@ -222,6 +222,7 @@ "CloneAutoTag": "Clone Auto Tag", "CloneCondition": "Clone Condition", "CloneCustomFormat": "Clone Custom Format", + "CloneImportList": "Clone Import List", "CloneIndexer": "Clone Indexer", "CloneProfile": "Clone Profile", "Close": "Close", @@ -311,6 +312,7 @@ "Default": "Default", "DefaultCase": "Default Case", "DefaultDelayProfileMovie": "This is the default profile. It applies to all movies that don't have an explicit profile.", + "DefaultNameCopiedImportList": "{name} - Copy", "DefaultNameCopiedProfile": "{name} - Copy", "DefaultNameCopiedSpecification": "{name} - Copy", "DefaultNotFoundMessage": "You must be lost, nothing to see here.",