Skip to content

Commit

Permalink
Add trakt series actions, refactor actions (#1161)
Browse files Browse the repository at this point in the history
  • Loading branch information
harshithmohan authored Dec 21, 2024
1 parent 9c1f70c commit 5df7302
Show file tree
Hide file tree
Showing 14 changed files with 104 additions and 115 deletions.
1 change: 1 addition & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@
],
"paths": [
{ "name": "react-router", "importNames": ["useNavigate"], "message": "Please use @/hooks/useNavigateVoid instead." },
{ "name": "react-toastify", "importNames": ["toast"], "message": "Please use @/components/Toast instead." },
{ "name": "usehooks-ts", "importNames": ["useEventCallback"], "message": "Please use @/hooks/useEventCallback instead." }
]
}
Expand Down
2 changes: 1 addition & 1 deletion src/components/Collection/Series/EditSeriesTabs/Action.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,4 @@ const Action = (
</div>
);

export default Action;
export default React.memo(Action);
15 changes: 4 additions & 11 deletions src/components/Collection/Series/EditSeriesTabs/FileActionsTab.tsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,27 @@
import React from 'react';

import Action from '@/components/Collection/Series/EditSeriesTabs/Action';
import toast from '@/components/Toast';
import { useRehashSeriesFilesMutation, useRescanSeriesFilesMutation } from '@/core/react-query/series/mutations';

type Props = {
seriesId: number;
};

const FileActionsTab = ({ seriesId }: Props) => {
const { mutate: rehashSeriesFiles } = useRehashSeriesFilesMutation();
const { mutate: rescanSeriesFiles } = useRescanSeriesFilesMutation();
const { mutate: rehashSeriesFiles } = useRehashSeriesFilesMutation(seriesId);
const { mutate: rescanSeriesFiles } = useRescanSeriesFilesMutation(seriesId);

return (
<div className="flex h-[22rem] grow flex-col gap-y-4 overflow-y-auto">
<Action
name="Rescan Files"
description="Rescans every file associated with the series."
onClick={() =>
rescanSeriesFiles(seriesId, {
onSuccess: () => toast.success('Series files rescan queued!'),
})}
onClick={rescanSeriesFiles}
/>
<Action
name="Rehash Files"
description="Rehashes every file associated with the series."
onClick={() =>
rehashSeriesFiles(seriesId, {
onSuccess: () => toast.success('Series files rehash queued!'),
})}
onClick={rehashSeriesFiles}
/>
</div>
);
Expand Down
12 changes: 3 additions & 9 deletions src/components/Collection/Series/EditSeriesTabs/NameTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import cx from 'classnames';
import { useToggle } from 'usehooks-ts';

import Input from '@/components/Input/Input';
import toast from '@/components/Toast';
import { useOverrideSeriesTitleMutation } from '@/core/react-query/series/mutations';
import { useSeriesQuery } from '@/core/react-query/series/queries';

Expand All @@ -18,7 +17,7 @@ const NameTab = ({ seriesId }: Props) => {

const { data: seriesData, isError, isFetching, isSuccess } = useSeriesQuery(seriesId, { includeDataFrom: ['AniDB'] });

const { mutate: overrideTitle } = useOverrideSeriesTitleMutation();
const { mutate: overrideTitle } = useOverrideSeriesTitleMutation(seriesId);

useEffect(() => {
setName(seriesData?.Name ?? '');
Expand Down Expand Up @@ -50,12 +49,8 @@ const NameTab = ({ seriesId }: Props) => {
icon: mdiCheckUnderlineCircleOutline,
className: 'text-panel-text-primary',
onClick: () =>
overrideTitle({ seriesId: seriesData.IDs.ID, Title: name }, {
onSuccess: () => {
toast.success('Name updated successfully!');
toggleNameEditable();
},
onError: () => toast.error('Name could not be updated!'),
overrideTitle(name, {
onSuccess: () => toggleNameEditable(),
}),
tooltip: 'Save name',
},
Expand All @@ -66,7 +61,6 @@ const NameTab = ({ seriesId }: Props) => {
name,
nameEditable,
overrideTitle,
seriesData?.IDs.ID,
seriesData?.Name,
toggleNameEditable,
]);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,30 +1,36 @@
import React from 'react';

import Action from '@/components/Collection/Series/EditSeriesTabs/Action';
import toast from '@/components/Toast';
import {
useAutoSearchTmdbMatchMutation,
useRefreshSeriesAniDBInfoMutation,
useRefreshSeriesTMDBInfoMutation,
useRefreshSeriesTraktInfoMutation,
useSyncSeriesTraktMutation,
useUpdateSeriesTMDBImagesMutation,
} from '@/core/react-query/series/mutations';
import useEventCallback from '@/hooks/useEventCallback';

type Props = {
seriesId: number;
};

const UpdateActionsTab = ({ seriesId }: Props) => {
const { mutate: refreshAnidb } = useRefreshSeriesAniDBInfoMutation();
const { mutate: autoMatchTmdb } = useAutoSearchTmdbMatchMutation();
const { mutate: refreshTmdb } = useRefreshSeriesTMDBInfoMutation();
const { mutate: updateTmdbImages } = useUpdateSeriesTMDBImagesMutation();
const { mutate: refreshAnidb } = useRefreshSeriesAniDBInfoMutation(seriesId);
const { mutate: autoMatchTmdb } = useAutoSearchTmdbMatchMutation(seriesId);
const { mutate: refreshTmdb } = useRefreshSeriesTMDBInfoMutation(seriesId);
const { mutate: updateTmdbImagesMutation } = useUpdateSeriesTMDBImagesMutation(seriesId);
const { mutate: refreshTrakt } = useRefreshSeriesTraktInfoMutation(seriesId);
const { mutate: syncTrakt } = useSyncSeriesTraktMutation(seriesId);

const triggerAnidbRefresh = (force: boolean, cacheOnly: boolean) => {
refreshAnidb({ seriesId, force, cacheOnly }, {
onSuccess: () => toast.success('AniDB refresh queued!'),
});
refreshAnidb({ force, cacheOnly });
};

const updateTmdbImagesForce = useEventCallback(() => {
updateTmdbImagesMutation({ force: true });
});

return (
<div className="flex h-[22rem] grow flex-col gap-y-4 overflow-y-auto">
<Action
Expand All @@ -45,26 +51,27 @@ const UpdateActionsTab = ({ seriesId }: Props) => {
<Action
name="Auto-Search TMDB Match"
description="Automatically searches for a TMDB match."
onClick={() =>
autoMatchTmdb(seriesId, {
onSuccess: () => toast.success('TMDB refresh queued!'),
})}
onClick={autoMatchTmdb}
/>
<Action
name="Update TMDB Info"
description="Gets the latest series information from TMDB."
onClick={() =>
refreshTmdb(seriesId, {
onSuccess: () => toast.success('TMDB refresh queued!'),
})}
onClick={refreshTmdb}
/>
<Action
name="Update TMDB Images - Force"
description="Forces a complete redownload of images from TMDB."
onClick={() =>
updateTmdbImages({ seriesId, force: true }, {
onSuccess: () => toast.success('TMDB image download queued!'),
})}
onClick={updateTmdbImagesForce}
/>
<Action
name="Update Trakt Show Info"
description="Gets the latest show information from Trakt."
onClick={refreshTrakt}
/>
<Action
name="Sync Trakt Status"
description="Syncs episode status between Shoko and Trakt."
onClick={syncTrakt}
/>
</div>
);
Expand Down
8 changes: 2 additions & 6 deletions src/components/Collection/Series/SeriesRating.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { mdiStar, mdiStarOutline } from '@mdi/js';
import { Icon } from '@mdi/react';
import { toNumber } from 'lodash';

import toast from '@/components/Toast';
import { useVoteSeriesMutation } from '@/core/react-query/series/mutations';
import useEventCallback from '@/hooks/useEventCallback';

Expand Down Expand Up @@ -31,15 +30,12 @@ const StarIcon = React.memo(({ handleHover, handleVote, hovered, index }: StarIc
));

const SeriesRating = ({ ratingValue, seriesId }: Props) => {
const { mutate: voteSeries } = useVoteSeriesMutation();
const { mutate: voteSeries } = useVoteSeriesMutation(seriesId);

const [hoveredStar, setHoveredStar] = useState(ratingValue - 1);

const handleVote = useEventCallback((event: React.MouseEvent<HTMLDivElement>) => {
voteSeries({ seriesId, rating: toNumber(event.currentTarget.id) + 1 }, {
onSuccess: () => toast.success('Voted!'),
onError: () => toast.error('Failed to vote!'),
});
voteSeries(toNumber(event.currentTarget.id) + 1);
});

const handleClear = useEventCallback(() => {
Expand Down
7 changes: 2 additions & 5 deletions src/components/Collection/Tags/TagsSearchAndFilterPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import Icon from '@mdi/react';
import Checkbox from '@/components/Input/Checkbox';
import Input from '@/components/Input/Input';
import ShokoPanel from '@/components/Panels/ShokoPanel';
import toast from '@/components/Toast';
import { useRefreshSeriesAniDBInfoMutation } from '@/core/react-query/series/mutations';
import useEventCallback from '@/hooks/useEventCallback';

Expand All @@ -22,11 +21,9 @@ type Props = {
};
const TagsSearchAndFilterPanel = React.memo(
({ handleInputChange, search, seriesId, showSpoilers, sort, tagSourceFilter, toggleSort }: Props) => {
const { isPending: anidbRefreshPending, mutate: refreshAnidb } = useRefreshSeriesAniDBInfoMutation();
const { isPending: anidbRefreshPending, mutate: refreshAnidb } = useRefreshSeriesAniDBInfoMutation(seriesId);
const refreshAnidbCallback = useEventCallback(() => {
refreshAnidb({ seriesId, force: true }, {
onSuccess: () => toast.success('AniDB refresh queued!'),
});
refreshAnidb({ force: true });
});

const searchInput = useMemo(() => (
Expand Down
1 change: 1 addition & 0 deletions src/components/Toast.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type React from 'react';
// eslint-disable-next-line no-restricted-imports
import { toast } from 'react-toastify';
import type { ToastOptions } from 'react-toastify';
import { mdiAlertCircleOutline, mdiCheckboxMarkedCircleOutline, mdiInformationOutline } from '@mdi/js';
Expand Down
78 changes: 51 additions & 27 deletions src/core/react-query/series/mutations.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,30 @@
import { useMutation } from '@tanstack/react-query';

import toast from '@/components/Toast';
import { axios } from '@/core/axios';
import { invalidateQueries } from '@/core/react-query/queryClient';

import type {
ChangeSeriesImageRequestType,
DeleteSeriesRequestType,
RefreshAniDBSeriesRequestType,
RefreshSeriesAniDBInfoRequestType,
WatchSeriesEpisodesRequestType,
} from '@/core/react-query/series/types';
import type { ImageType } from '@/core/types/api/common';
import type { SeriesAniDBSearchResult } from '@/core/types/api/series';

export const useChangeSeriesImageMutation = () =>
export const useChangeSeriesImageMutation = (seriesId: number) =>
useMutation({
mutationFn: ({ image, seriesId }: ChangeSeriesImageRequestType) =>
mutationFn: (image: ImageType) =>
axios.put(
`Series/${seriesId}/Images/${image.Type}`,
{
ID: image.ID,
Source: image.Source,
},
),
onSuccess: (_, { seriesId }) => {
onSuccess: (_, image) => {
toast.success(`Series ${image.Type} image has been changed.`);
invalidateQueries(['series', seriesId, 'data']);
invalidateQueries(['series', seriesId, 'images']);
},
Expand All @@ -43,13 +45,14 @@ export const useGetSeriesAniDBMutation = () =>
mutationFn: (anidbId: number) => axios.get(`Series/AniDB/${anidbId}`),
});

export const useOverrideSeriesTitleMutation = () =>
export const useOverrideSeriesTitleMutation = (seriesId: number) =>
useMutation({
mutationFn: ({ seriesId, ...data }: { seriesId: number, Title: string }) =>
axios.post(`Series/${seriesId}/OverrideTitle`, data),
onSuccess: (_, { seriesId }) => {
mutationFn: (Title: string) => axios.post(`Series/${seriesId}/OverrideTitle`, { Title }),
onSuccess: (_) => {
toast.success('Name updated successfully!');
invalidateQueries(['series', seriesId, 'data']);
},
onError: () => toast.error('Name could not be updated!'),
});

export const useRefreshAniDBSeriesMutation = () =>
Expand All @@ -67,60 +70,81 @@ export const useRefreshAniDBSeriesMutation = () =>
},
});

export const useRefreshSeriesAniDBInfoMutation = () =>
export const useRefreshSeriesAniDBInfoMutation = (seriesId: number) =>
useMutation({
mutationFn: ({ seriesId, ...params }: RefreshSeriesAniDBInfoRequestType) =>
mutationFn: (params: RefreshSeriesAniDBInfoRequestType) =>
axios.post(`Series/${seriesId}/AniDB/Refresh`, null, { params }),
onSuccess: () => toast.success('AniDB refresh queued!'),
});

export const useRehashSeriesFilesMutation = () =>
export const useRehashSeriesFilesMutation = (seriesId: number) =>
useMutation({
mutationFn: (seriesId: number) => axios.post(`Series/${seriesId}/File/Rehash`),
mutationFn: () => axios.post(`Series/${seriesId}/File/Rehash`),
onSuccess: () => toast.success('Series files rehash queued!'),
});

export const useRescanSeriesFilesMutation = () =>
export const useRescanSeriesFilesMutation = (seriesId: number) =>
useMutation({
mutationFn: (seriesId: number) => axios.post(`Series/${seriesId}/File/Rescan`),
mutationFn: () => axios.post(`Series/${seriesId}/File/Rescan`),
onSuccess: () => toast.success('Series files rescan queued!'),
});

export const useVoteSeriesMutation = () =>
export const useVoteSeriesMutation = (seriesId: number) =>
useMutation({
mutationFn: ({ rating, seriesId }: { seriesId: number, rating: number }) =>
axios.post(`Series/${seriesId}/Vote`, { Value: rating, MaxValue: 10 }),
onSuccess: (_, { seriesId }) => {
mutationFn: (rating: number) => axios.post(`Series/${seriesId}/Vote`, { Value: rating, MaxValue: 10 }),
onSuccess: (_) => {
toast.success('Voted!');
invalidateQueries(['series', seriesId, 'data']);
},
onError: () => toast.error('Failed to vote!'),
});

export const useWatchSeriesEpisodesMutation = () =>
export const useWatchSeriesEpisodesMutation = (seriesId: number) =>
useMutation({
mutationFn: ({ seriesId, ...params }: WatchSeriesEpisodesRequestType) =>
mutationFn: (params: WatchSeriesEpisodesRequestType) =>
axios.post(`Series/${seriesId}/Episode/Watched`, null, { params }),
onSuccess: (_, { seriesId }) => {
onSuccess: (_, { value }) => {
toast.success(`Episodes marked as ${value ? 'watched' : 'unwatched'}!`);
invalidateQueries(['series', seriesId, 'data']);
invalidateQueries(['series', seriesId, 'episodes']);
},
onError: (_, { value }) => toast.error(`Failed to mark episodes as ${value ? 'watched' : 'unwatched'}!`),
});

export const useAutoSearchTmdbMatchMutation = () =>
export const useAutoSearchTmdbMatchMutation = (seriesId: number) =>
useMutation({
mutationFn: (seriesId: number) => axios.post(`Series/${seriesId}/TMDB/Action/AutoSearch`),
mutationFn: () => axios.post(`Series/${seriesId}/TMDB/Action/AutoSearch`),
onSuccess: () => toast.success('TMDB auto-search queued!'),
});

export const useRefreshSeriesTMDBInfoMutation = () =>
export const useRefreshSeriesTMDBInfoMutation = (seriesId: number) =>
useMutation({
mutationFn: (seriesId: number) =>
mutationFn: () =>
Promise.all([
axios.post(`Series/${seriesId}/TMDB/Show/Action/Refresh`, {}),
axios.post(`Series/${seriesId}/TMDB/Movie/Action/Refresh`, {}),
]),
onSuccess: () => toast.success('TMDB refresh queued!'),
});

export const useUpdateSeriesTMDBImagesMutation = () =>
export const useUpdateSeriesTMDBImagesMutation = (seriesId: number) =>
useMutation({
mutationFn: ({ force = false, seriesId }: { seriesId: number, force: boolean }) =>
mutationFn: ({ force = false }: { force?: boolean }) =>
Promise.all([
axios.post(`Series/${seriesId}/TMDB/Show/Action/DownloadImages`, { force }),
axios.post(`Series/${seriesId}/TMDB/Movie/Action/DownloadImages`, { force }),
]),
onSuccess: () => toast.success('TMDB image download queued!'),
});

export const useRefreshSeriesTraktInfoMutation = (seriesId: number) =>
useMutation({
mutationFn: () => axios.post(`Series/${seriesId}/Trakt/Refresh`),
onSuccess: () => toast.success('Trakt refresh queued!'),
});

export const useSyncSeriesTraktMutation = (seriesId: number) =>
useMutation({
mutationFn: () => axios.post(`Series/${seriesId}/Trakt/Sync`),
onSuccess: () => toast.success('Trakt sync queued!'),
});
Loading

0 comments on commit 5df7302

Please sign in to comment.