diff --git a/src/components/Collection/Group/EditGroupModal.tsx b/src/components/Collection/Group/EditGroupModal.tsx index d0fd87333..fda84db7a 100644 --- a/src/components/Collection/Group/EditGroupModal.tsx +++ b/src/components/Collection/Group/EditGroupModal.tsx @@ -3,6 +3,7 @@ import { useDispatch, useSelector } from 'react-redux'; import cx from 'classnames'; import { map } from 'lodash'; +import FileActionsTab from '@/components/Collection/Group/EditGroupTabs/FileActionsTab'; import NameTab from '@/components/Collection/Group/EditGroupTabs/NameTab'; import SeriesTab from '@/components/Collection/Group/EditGroupTabs/SeriesTab'; import ModalPanel from '@/components/Panels/ModalPanel'; @@ -14,6 +15,7 @@ import type { RootState } from '@/core/store'; const tabs = { name: 'Name', series: 'Series', + file_actions: 'File Actions', }; const renderTab = (activeTab: string, groupId: number) => { @@ -24,6 +26,8 @@ const renderTab = (activeTab: string, groupId: number) => { switch (activeTab) { case 'series': return ; + case 'file_actions': + return ; case 'name': default: return ; diff --git a/src/components/Collection/Group/EditGroupTabs/Action.tsx b/src/components/Collection/Group/EditGroupTabs/Action.tsx new file mode 100644 index 000000000..caa31629b --- /dev/null +++ b/src/components/Collection/Group/EditGroupTabs/Action.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import cx from 'classnames'; + +const Action = ( + { description, name, onClick, scroll }: { name: string, description: string, scroll?: boolean, onClick: () => void }, +) => ( +
+
+
+
{name}
+
+
{description}
+
+
+); + +export default React.memo(Action); diff --git a/src/components/Collection/Group/EditGroupTabs/FileActionsTab.tsx b/src/components/Collection/Group/EditGroupTabs/FileActionsTab.tsx new file mode 100644 index 000000000..63b41836c --- /dev/null +++ b/src/components/Collection/Group/EditGroupTabs/FileActionsTab.tsx @@ -0,0 +1,29 @@ +import React from 'react'; + +import Action from '@/components/Collection/Group/EditGroupTabs/Action'; +import toast from '@/components/Toast'; +import { useRelocateGroupFilesMutation } from '@/core/react-query/group/mutations'; +import useIsFeatureSupported, { FeatureType } from '@/hooks/useIsFeatureSupported'; + +type Props = { + groupId: number; +}; + +const FileActionsTab = ({ groupId }: Props) => { + const { mutate: relocateGroupFiles } = useRelocateGroupFilesMutation(groupId); + const showUnsupportedToast = () => { + toast.error(`This feature require server version >= ${FeatureType.RelocateSeriesFiles}`); + }; + + return ( +
+ +
+ ); +}; + +export default FileActionsTab; diff --git a/src/components/Collection/Series/EditSeriesTabs/FileActionsTab.tsx b/src/components/Collection/Series/EditSeriesTabs/FileActionsTab.tsx index 97367e003..56bb354ed 100644 --- a/src/components/Collection/Series/EditSeriesTabs/FileActionsTab.tsx +++ b/src/components/Collection/Series/EditSeriesTabs/FileActionsTab.tsx @@ -1,7 +1,13 @@ import React from 'react'; import Action from '@/components/Collection/Series/EditSeriesTabs/Action'; -import { useRehashSeriesFilesMutation, useRescanSeriesFilesMutation } from '@/core/react-query/series/mutations'; +import toast from '@/components/Toast'; +import { + useRehashSeriesFilesMutation, + useRelocateSeriesFilesMutation, + useRescanSeriesFilesMutation, +} from '@/core/react-query/series/mutations'; +import useIsFeatureSupported, { FeatureType } from '@/hooks/useIsFeatureSupported'; type Props = { seriesId: number; @@ -10,6 +16,10 @@ type Props = { const FileActionsTab = ({ seriesId }: Props) => { const { mutate: rehashSeriesFiles } = useRehashSeriesFilesMutation(seriesId); const { mutate: rescanSeriesFiles } = useRescanSeriesFilesMutation(seriesId); + const { mutate: relocateSeriesFiles } = useRelocateSeriesFilesMutation(seriesId); + const showUnsupportedToast = () => { + toast.error(`This feature require server version >= ${FeatureType.RelocateSeriesFiles}`); + }; return (
@@ -23,6 +33,11 @@ const FileActionsTab = ({ seriesId }: Props) => { description="Rehashes every file associated with the series." onClick={rehashSeriesFiles} /> +
); }; diff --git a/src/core/react-query/group/mutations.ts b/src/core/react-query/group/mutations.ts index af12b42d1..1a861c245 100644 --- a/src/core/react-query/group/mutations.ts +++ b/src/core/react-query/group/mutations.ts @@ -5,6 +5,7 @@ import { axios } from '@/core/axios'; import { invalidateQueries } from '@/core/react-query/queryClient'; import type { MoveSeriesGroupRequestType, PatchGroupRequestType } from '@/core/react-query/group/types'; +import type { SeriesType } from '@/core/types/api/series'; // TODO: FIX INVALIDATIONS @@ -57,3 +58,19 @@ export const useMoveGroupMutation = () => toast.success('Moved series into new group!'); }, }); + +export const useRelocateGroupFilesMutation = (groupId: number) => + useMutation({ + mutationFn: async () => { + const targetSeries = await axios.get(`Group/${groupId}/Series`, { + params: { recursive: true }, + }); + + return Promise.all( + targetSeries.map( + series => axios.post(`Series/${series.IDs.ID}/File/Relocate`), + ), + ); + }, + onSuccess: () => toast.success('Group files renamed/moved!'), + }); diff --git a/src/core/react-query/series/mutations.ts b/src/core/react-query/series/mutations.ts index a5e051197..5ac822a2d 100644 --- a/src/core/react-query/series/mutations.ts +++ b/src/core/react-query/series/mutations.ts @@ -148,3 +148,9 @@ export const useSyncSeriesTraktMutation = (seriesId: number) => mutationFn: () => axios.post(`Series/${seriesId}/Trakt/Sync`), onSuccess: () => toast.success('Trakt sync queued!'), }); + +export const useRelocateSeriesFilesMutation = (seriesId: number) => + useMutation({ + mutationFn: () => axios.post(`Series/${seriesId}/File/Relocate`), + onSuccess: () => toast.success('Series files renamed/moved!'), + }); diff --git a/src/hooks/useIsFeatureSupported.ts b/src/hooks/useIsFeatureSupported.ts index f1be7ca01..188877949 100644 --- a/src/hooks/useIsFeatureSupported.ts +++ b/src/hooks/useIsFeatureSupported.ts @@ -8,6 +8,7 @@ import type { VersionType } from '@/core/types/api/init'; export enum FeatureType { Placeholder = '5.0.0.0', // This is as a placeholder so the string conversion for `parseServerVersion` works and also serves as an example + RelocateSeriesFiles = '5.1.0.35', } const useIsFeatureSupported = (feature: FeatureType) => {