Skip to content

Commit

Permalink
fix: update settings and series pages (#953)
Browse files Browse the repository at this point in the history
* fix: update settings and series pages
Updated the settings and series pages after the TMDB PR.

The settings now have overhauled language settings, though the source order selector aren't implemented. Also, all TMDB settings (except the user api key override) are exposed.

On the series page the linked TMDB entity will indicate if it's a movie or show link, and the external link for both should work.

* Fix revam's PR

* Fix metadata link

* Fix dashboard queries

* Fix delete tmdb link hook name

---------

Co-authored-by: Harshith Mohan <[email protected]>
  • Loading branch information
revam and harshithmohan authored Aug 6, 2024
1 parent ceec889 commit 8e597ea
Show file tree
Hide file tree
Showing 14 changed files with 770 additions and 204 deletions.
99 changes: 56 additions & 43 deletions src/components/Collection/SeriesMetadata.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,80 +3,93 @@ import { mdiCloseCircleOutline, mdiOpenInNew, mdiPencilCircleOutline, mdiPlusCir
import { Icon } from '@mdi/react';

import Button from '@/components/Input/Button';
import { useDeleteSeriesTvdbLinkMutation } from '@/core/react-query/series/mutations';
import { useDeleteSeriesTvdbLinkMutation, useDeleteTmdbLinkMutation } from '@/core/react-query/series/mutations';
import useEventCallback from '@/hooks/useEventCallback';

const MetadataLink = ({ id, seriesId, site }: { id: number | number[], seriesId: number, site: string }) => {
const linkId = Array.isArray(id) ? id[0] : id;
type Props = {
id?: number;
seriesId: number;
site: 'AniDB' | 'TMDB' | 'TvDB' | 'TraktTv';
type?: 'Movie' | 'Show';
};

const MetadataLink = ({ id, seriesId, site, type }: Props) => {
const { mutate: deleteTmdbLink } = useDeleteTmdbLinkMutation(type ?? 'Movie');
const { mutate: deleteTvdbLink } = useDeleteSeriesTvdbLinkMutation();

const siteLink = useMemo(() => {
if (!id) return '#';
switch (site) {
case 'AniDB':
return `https://anidb.net/anime/${linkId}`;
return `https://anidb.net/anime/${id}`;
case 'TMDB':
return `https://www.themoviedb.org/movie/${linkId}`;
return `https://www.themoviedb.org/${type === 'Show' ? 'tv' : 'movie'}/${id}`;
case 'TvDB':
return `https://thetvdb.com/?tab=series&id=${linkId}`;
return `https://thetvdb.com/?tab=series&id=${id}`;
case 'TraktTv':
// TODO: Figure how to get trakt series link using ID
return '#';
default:
return '#';
}
}, [linkId, site]);
}, [id, site, type]);

const canDisable = site === 'TvDB';
const canRemoveLink = useMemo(() => site === 'TvDB' || site === 'TMDB', [site]);

const disableMetadata = useEventCallback(() => {
const removeLink = useEventCallback(() => {
if (!id) return;
switch (site) {
case 'TvDB':
deleteTvdbLink(seriesId);
break;
case 'TMDB':
deleteTmdbLink(seriesId);
break;
default:
break;
}
});

return (
<div key={site} className="flex justify-between">
<div className="flex gap-x-4">
<div className={`metadata-link-icon ${site}`} />
{linkId
? (
<a
href={siteLink}
className="flex gap-x-2 font-semibold text-panel-text-primary"
rel="noopener noreferrer"
target="_blank"
>
{site}
<Icon className="text-panel-icon-action" path={mdiOpenInNew} size={1} />
</a>
)
: 'Series Not Linked'}
</div>
{site !== 'AniDB' && (
<div className="flex gap-x-2">
{linkId
<div className="w-full rounded-lg border border-panel-border bg-panel-background px-4 py-3">
<div className="flex justify-between">
<div className="flex gap-x-4">
<div className={`metadata-link-icon ${site}`} />
{id
? (
<>
<Button disabled>
<Icon className="text-panel-icon-action" path={mdiPencilCircleOutline} size={1} />
</Button>
<Button disabled={!canDisable} onClick={disableMetadata} tooltip="Remove link">
<Icon className="text-panel-icon-danger" path={mdiCloseCircleOutline} size={1} />
</Button>
</>
<a
href={siteLink}
className="flex gap-x-2 font-semibold text-panel-text-primary"
rel="noopener noreferrer"
target="_blank"
>
{`${site} (${type ? type[0].toLowerCase() : ''}${id})`}
<Icon className="text-panel-icon-action" path={mdiOpenInNew} size={1} />
</a>
)
: (
<Button disabled>
<Icon className="text-panel-icon-action" path={mdiPlusCircleOutline} size={1} />
</Button>
)}
: 'Series Not Linked'}
</div>
)}
{site !== 'AniDB' && (
<div className="flex gap-x-2">
{id
? (
<>
<Button disabled>
<Icon className="text-panel-icon-action" path={mdiPencilCircleOutline} size={1} />
</Button>
<Button disabled={!canRemoveLink} onClick={removeLink} tooltip="Remove link">
<Icon className="text-panel-icon-danger" path={mdiCloseCircleOutline} size={1} />
</Button>
</>
)
: (
<Button disabled>
<Icon className="text-panel-icon-action" path={mdiPlusCircleOutline} size={1} />
</Button>
)}
</div>
)}
</div>
</div>
);
};
Expand Down
29 changes: 23 additions & 6 deletions src/components/Dialogs/LanguagesModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,30 +10,47 @@ import { useSettingsQuery } from '@/core/react-query/settings/queries';
import useEventCallback from '@/hooks/useEventCallback';

type Props = {
type: 'Series' | 'Episode' | null;
type: 'Series' | 'Episode' | 'Description' | null;
onClose: () => void;
};

function LanguagesModal({ onClose, type }: Props) {
const settings = useSettingsQuery().data;
const LanguagePreference = useMemo(
() => (type === 'Episode'
? settings.EpisodeLanguagePreference
: settings.LanguagePreference),
() => {
switch (type) {
case 'Episode':
return settings.Language.EpisodeTitleLanguageOrder;
case 'Description':
return settings.Language.DescriptionLanguageOrder;
default:
return settings.Language.SeriesTitleLanguageOrder;
}
},
[type, settings],
);
const { mutate: patchSettings } = usePatchSettingsMutation();

const [languages, setLanguages] = useState([] as string[]);

const handleSave = useEventCallback(() => {
let preferenceType = 'SeriesTitleLanguageOrder';
if (type === 'Episode') {
preferenceType = 'EpisodeTitleLanguageOrder';
} else if (type === 'Description') {
preferenceType = 'DescriptionLanguageOrder';
}

patchSettings({
newSettings: {
...settings,
[type === 'Episode' ? 'EpisodeLanguagePreference' : 'LanguagePreference']: languages,
Language: {
...settings.Language,
[preferenceType]: languages,
},
},
}, {
onSuccess: () => onClose(),
onSuccess: onClose,
});
});

Expand Down
14 changes: 10 additions & 4 deletions src/core/react-query/dashboard/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ import { useQuery } from '@tanstack/react-query';

import { axios } from '@/core/axios';
import { transformSeriesSummary } from '@/core/react-query/dashboard/helpers';
import { transformListResultSimplified } from '@/core/react-query/helpers';

import type {
DashboardCalendarRequestType,
DashboardContinueWatchingRequestType,
DashboardNextUpRequestType,
} from '@/core/react-query/dashboard/types';
import type { DashboardRequestType } from '@/core/react-query/types';
import type { ListResultType } from '@/core/types/api';
import type {
DashboardEpisodeDetailsType,
DashboardSeriesSummaryType,
Expand All @@ -23,27 +25,31 @@ export const useDashboardCalendarQuery = (params: DashboardCalendarRequestType)
});

export const useDashboardContinueWatchingQuery = (params: DashboardContinueWatchingRequestType) =>
useQuery<DashboardEpisodeDetailsType[]>({
useQuery<ListResultType<DashboardEpisodeDetailsType>, unknown, DashboardEpisodeDetailsType[]>({
queryKey: ['dashboard', 'continue-watching', params],
queryFn: () => axios.get('Dashboard/ContinueWatchingEpisodes', { params }),
select: transformListResultSimplified,
});

export const useDashboardNextUpQuery = (params: DashboardNextUpRequestType) =>
useQuery<DashboardEpisodeDetailsType[]>({
useQuery<ListResultType<DashboardEpisodeDetailsType>, unknown, DashboardEpisodeDetailsType[]>({
queryKey: ['dashboard', 'next-up', params],
queryFn: () => axios.get('Dashboard/NextUpEpisodes', { params }),
select: transformListResultSimplified,
});

export const useDashboardRecentlyAddedEpisodesQuery = (params: DashboardRequestType) =>
useQuery<DashboardEpisodeDetailsType[]>({
useQuery<ListResultType<DashboardEpisodeDetailsType>, unknown, DashboardEpisodeDetailsType[]>({
queryKey: ['dashboard', 'recently-added-episodes', params],
queryFn: () => axios.get('Dashboard/RecentlyAddedEpisodes', { params }),
select: transformListResultSimplified,
});

export const useDashboardRecentlyAddedSeriesQuery = (params: DashboardRequestType) =>
useQuery<SeriesType[]>({
useQuery<ListResultType<SeriesType>, unknown, SeriesType[]>({
queryKey: ['dashboard', 'recently-added-series', params],
queryFn: () => axios.get('Dashboard/RecentlyAddedSeries', { params }),
select: transformListResultSimplified,
});

export const useDashboardSeriesSummaryQuery = () =>
Expand Down
6 changes: 6 additions & 0 deletions src/core/react-query/series/mutations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ export const useDeleteSeriesTvdbLinkMutation = () =>
onSuccess: (_, seriesId) => invalidateQueries(['series', seriesId, 'episodes']),
});

export const useDeleteTmdbLinkMutation = (linkType: 'Movie' | 'Show') =>
useMutation({
mutationFn: (seriesId: number) => axios.delete(`Series/${seriesId}/TMDB/${linkType}`),
onSuccess: (_, seriesId) => invalidateQueries(['series', seriesId]),
});

// This is actually a query but we had to declare it as mutation to use it properly as lazy query.
export const useGetSeriesAniDBMutation = () =>
useMutation<SeriesAniDBSearchResult, unknown, number>({
Expand Down
36 changes: 28 additions & 8 deletions src/core/react-query/settings/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import semver from 'semver';

import { webuiSettingsPatches } from '@/core/patches';
import { LanguageSource } from '@/core/types/api/settings';
import { uiVersion } from '@/core/util';

import type { SettingsServerType, SettingsType, WebUISettingsType } from '@/core/types/api/settings';
Expand Down Expand Up @@ -351,11 +352,33 @@ export const initialSettings: SettingsType = {
UpdateFrequency: 1,
Language: 'en',
},
MovieDb: {
AutoFanart: false,
AutoFanartAmount: 0,
AutoPosters: false,
AutoPostersAmount: 0,
TMDB: {
AutoLink: false,
AutoLinkRestricted: false,
AutoDownloadCrewAndCast: false,
AutoDownloadCollections: false,
AutoDownloadAlternateOrdering: false,
AutoDownloadBackdrops: true,
MaxAutoBackdrops: 10,
AutoDownloadPosters: true,
MaxAutoPosters: 10,
AutoDownloadLogos: true,
MaxAutoLogos: 10,
AutoDownloadThumbnails: true,
MaxAutoThumbnails: 10,
AutoDownloadStaffImages: true,
MaxAutoStaffImages: 10,
AutoDownloadStudioImages: true,
UserApiKey: null,
},
Language: {
UseSynonyms: false,
SeriesTitleLanguageOrder: [],
SeriesTitleSourceOrder: [LanguageSource.AniDB, LanguageSource.TMDB],
EpisodeTitleLanguageOrder: ['en'],
EpisodeTitleSourceOrder: [LanguageSource.TMDB, LanguageSource.TvDB, LanguageSource.AniDB],
DescriptionLanguageOrder: ['en'],
DescriptionSourceOrder: [LanguageSource.TMDB, LanguageSource.TvDB, LanguageSource.AniDB],
},
TraktTv: {
Enabled: false,
Expand All @@ -381,9 +404,6 @@ export const initialSettings: SettingsType = {
AutoGroupSeries: false,
AutoGroupSeriesUseScoreAlgorithm: false,
AutoGroupSeriesRelationExclusions: [],
LanguageUseSynonyms: false,
LanguagePreference: ['x-jat', 'en'],
EpisodeLanguagePreference: ['en'],
Import: {
AutomaticallyDeleteDuplicatesOnImport: false,
MoveOnImport: false,
Expand Down
6 changes: 4 additions & 2 deletions src/core/types/api/series.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,12 @@ export type SeriesIDsType = {
TopLevelGroup: number;
AniDB: number;
TvDB: number[];
TMDB: number[];
MAL: number[];
TraktTv: number[];
AniList: number[];
TMDB: {
Movie: number[];
Show: number[];
};
};

export type SeriesAniDBType = {
Expand Down
Loading

0 comments on commit 8e597ea

Please sign in to comment.