Skip to content

Commit

Permalink
Add tmdb show settings modal (#1183)
Browse files Browse the repository at this point in the history
  • Loading branch information
revam authored Feb 11, 2025
1 parent 9038c28 commit b35912a
Show file tree
Hide file tree
Showing 7 changed files with 288 additions and 4 deletions.
26 changes: 24 additions & 2 deletions src/components/Collection/SeriesMetadata.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import React, { useMemo } from 'react';
import { mdiCloseCircleOutline, mdiOpenInNew, mdiPencilCircleOutline, mdiPlusCircleOutline } from '@mdi/js';
import {
mdiCloseCircleOutline,
mdiCogOutline,
mdiOpenInNew,
mdiPencilCircleOutline,
mdiPlusCircleOutline,
} from '@mdi/js';
import { Icon } from '@mdi/react';

import Button from '@/components/Input/Button';
Expand All @@ -10,12 +16,13 @@ import useNavigateVoid from '@/hooks/useNavigateVoid';

type Props = {
id?: number;
openTmdbShowSettings?: (showId: number) => void;
seriesId: number;
site: 'AniDB' | 'TMDB' | 'TraktTv';
type?: 'Movie' | 'Show';
};

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

Expand All @@ -34,13 +41,23 @@ const MetadataLink = ({ id, seriesId, site, type }: Props) => {
}, [id, site, type]);

const canAddLink = useMemo(() => site === 'TMDB', [site]);
const canOpenSettings = useMemo(() => site === 'TMDB' && type === 'Show' && openTmdbShowSettings != null, [
site,
type,
openTmdbShowSettings,
]);
const canEditLink = useMemo(() => site === 'TMDB', [site]);
const canRemoveLink = useMemo(() => site === 'TMDB', [site]);

const addLink = useEventCallback(() => {
navigate('../tmdb-linking');
});

const openSettings = useEventCallback(() => {
if (!id || !type || !canOpenSettings) return;
openTmdbShowSettings!(id);
});

const editLink = useEventCallback(() => {
if (!id || !type) return;
navigate(`../tmdb-linking?type=${type}&id=${id}`);
Expand Down Expand Up @@ -88,6 +105,11 @@ const MetadataLink = ({ id, seriesId, site, type }: Props) => {
{id
? (
<>
{canOpenSettings && (
<Button onClick={openSettings} tooltip="Open Settings">
<Icon className="text-panel-icon-action" path={mdiCogOutline} size={1} />
</Button>
)}
{canEditLink && (
<Button onClick={editLink} tooltip="Edit Link">
<Icon className="text-panel-icon-action" path={mdiPencilCircleOutline} size={1} />
Expand Down
152 changes: 152 additions & 0 deletions src/components/Dialogs/TmdbShowSettingsModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import React, { useEffect, useLayoutEffect, useMemo, useState } from 'react';
import cx from 'classnames';

import Button from '@/components/Input/Button';
import ModalPanel from '@/components/Panels/ModalPanel';
import toast from '@/components/Toast';
import { invalidateQueries } from '@/core/react-query/queryClient';
import { useSetPreferredTmdbShowOrderingMutation } from '@/core/react-query/tmdb/mutations';
import { useTmdbShowOrderingQuery } from '@/core/react-query/tmdb/queries';
import { AlternateOrderingTypeEnum } from '@/core/react-query/tmdb/types';
import useEventCallback from '@/hooks/useEventCallback';

export type Props = {
onClose: () => void;
show: boolean;
showId: number;
};

function orderingToDescription(ordering: AlternateOrderingTypeEnum | undefined): string {
switch (ordering) {
case AlternateOrderingTypeEnum.Unknown:
return ' (Unknown)';
case AlternateOrderingTypeEnum.OriginalAirDate:
return ' (Original Air Date)';
case AlternateOrderingTypeEnum.Absolute:
return ' (Absolute)';
case AlternateOrderingTypeEnum.DVD:
return ' (DVD)';
case AlternateOrderingTypeEnum.Digital:
return ' (Digital)';
case AlternateOrderingTypeEnum.StoryArc:
return ' (Story Arc)';
case AlternateOrderingTypeEnum.Production:
return ' (Production)';
case AlternateOrderingTypeEnum.TV:
return ' (TV)';
default:
return '';
}
}

function TmdbShowSettingsModal({ onClose, show, showId }: Props) {
const orderingQuery = useTmdbShowOrderingQuery(showId, show && showId > 0);
const { mutate: setOrdering, status: setOrderingStatus, submittedAt } = useSetPreferredTmdbShowOrderingMutation(
showId,
);
const { data: orderingList, fetchStatus } = orderingQuery;
const [selectedOrderingId, setSelectedOrderingId] = useState<string>('');
const selectedOrdering = useMemo(() => orderingList.find(ordering => ordering.OrderingID === selectedOrderingId), [
orderingList,
selectedOrderingId,
]);

const lockedControls = fetchStatus === 'fetching' && submittedAt - Date.now() > 100;
const canSave = !lockedControls && setOrderingStatus !== 'pending' && selectedOrdering != null
&& !selectedOrdering.InUse;

const handleClose = useEventCallback(() => {
setSelectedOrderingId('');
invalidateQueries(['series', 'tmdb', 'show']);
invalidateQueries(['series', 'tmdb', 'episode', 'bulk']);
onClose();
});

const handleOrderingClick = useEventCallback((event: React.MouseEvent<HTMLElement>) => {
const { orderingId } = event.currentTarget.dataset;
if (!orderingId) return;

const ordering = orderingList.find(ord => ord.OrderingID === orderingId);
if (!ordering || ordering.InUse) return;

if (selectedOrderingId === orderingId) {
setSelectedOrderingId('');
} else {
setSelectedOrderingId(orderingId);
}
});

const handleSave = useEventCallback(() => {
if (!selectedOrderingId) return;
setOrdering(selectedOrderingId, {
onSuccess: () => {
toast.success('Ordering has been updated!');
},
onError: () => {
toast.error('Failed to update ordering!');
},
});
});

useEffect(() => {
if (selectedOrdering?.InUse ?? false) {
setSelectedOrderingId('');
}
}, [selectedOrdering]);

useLayoutEffect(() => {
if (show) {
orderingQuery.refetch({ cancelRefetch: true }).catch(console.error);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [show, showId]);

return (
<ModalPanel
show={show}
onRequestClose={handleClose}
header="TMDb Show Settings"
size="sm"
overlayClassName="!z-[90]"
>
<div className="flex grow flex-col gap-y-2">
<div className="mb-2 flex justify-between font-semibold">
Ordering
</div>
<div className="flex h-[10.5rem] flex-col overflow-y-auto rounded-md border border-panel-border bg-panel-background-alt px-4 py-2 contain-strict">
{orderingList.map(ordering => (
<div
key={ordering.OrderingID}
data-ordering-id={ordering.OrderingID}
onClick={handleOrderingClick}
className={cx(
'flex flex-row justify-between transition-colors',
lockedControls && 'opacity-65',
!lockedControls && !ordering.InUse && 'cursor-pointer',
!lockedControls && ordering.InUse
&& (!selectedOrdering || selectedOrdering.OrderingID !== ordering.OrderingID)
&& 'text-panel-text-primary',
selectedOrdering?.OrderingID === ordering.OrderingID && 'text-panel-text-important',
)}
>
<span>
{`${ordering.OrderingName}${orderingToDescription(ordering.OrderingType)}`}
</span>
<span className="w-10 text-center">
{`${ordering.EpisodeCount}${
ordering.HiddenEpisodeCount > 0 ? `(+${ordering.HiddenEpisodeCount})` : ''
}`}
</span>
</div>
))}
</div>
</div>
<div className="flex justify-end gap-x-3 font-semibold">
<Button onClick={handleClose} buttonType="secondary" className="px-6 py-2">Close</Button>
<Button onClick={handleSave} buttonType="primary" disabled={!canSave} className="px-6 py-2">Save</Button>
</div>
</ModalPanel>
);
}

export default TmdbShowSettingsModal;
9 changes: 9 additions & 0 deletions src/core/react-query/tmdb/mutations.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useMutation } from '@tanstack/react-query';

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

import type {
TmdbAddAutoXrefsRequestType,
Expand Down Expand Up @@ -40,3 +41,11 @@ export const useDeleteTmdbLinkMutation = (seriesId: number, linkType: 'Movie' |
useMutation({
mutationFn: (data: TmdbDeleteLinkRequestType) => axios.delete(`Series/${seriesId}/TMDB/${linkType}`, { data }),
});

export const useSetPreferredTmdbShowOrderingMutation = (showId: number) =>
useMutation({
mutationKey: ['series', 'tmdb', 'show', showId, 'ordering', 'set-preferred'],
mutationFn: (alternateOrderingId: string) =>
axios.post(`/TMDB/Show/${showId}/Ordering/SetPreferred`, { alternateOrderingId }),
onSuccess: () => invalidateQueries(['series', 'tmdb', 'show', showId, 'ordering']),
});
9 changes: 9 additions & 0 deletions src/core/react-query/tmdb/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type {
TmdbBulkRequestType,
TmdbSearchRequestType,
TmdbShowEpisodesRequestType,
TmdbShowOrderingInformationType,
} from '@/core/react-query/tmdb/types';
import type { ListResultType } from '@/core/types/api';
import type {
Expand Down Expand Up @@ -142,3 +143,11 @@ export const useTmdbBulkMoviesOnlineQuery = (data: TmdbBulkRequestType, enabled
queryFn: () => axios.post('Tmdb/Movie/Online/Bulk', data),
enabled: enabled && data.IDs.length > 0,
});

export const useTmdbShowOrderingQuery = (showId: number, enabled = true) =>
useQuery<TmdbShowOrderingInformationType[]>({
queryKey: ['series', 'tmdb', 'show', showId, 'ordering'],
queryFn: () => (showId > 0 ? axios.get(`TMDB/Show/${showId}/Ordering`) : Promise.resolve([])),
initialData: () => [],
enabled,
});
59 changes: 59 additions & 0 deletions src/core/react-query/tmdb/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,62 @@ export type TmdbEditEpisodeXrefsRequestType = {
export type TmdbShowEpisodesRequestType = {
search?: string;
} & PaginationType;

export type TmdbShowOrderingInformationType = {
/**
* The ordering ID.
*/
OrderingID: string;

/**
* The alternate ordering type. Will not be set if the main ordering is
* used.
*/
OrderingType?: AlternateOrderingTypeEnum;

/**
* English name of the ordering scheme.
*/
OrderingName: string;

/**
* The number of episodes in the ordering scheme.
*/
EpisodeCount: number;

/**
* The number of hidden episodes in the ordering scheme.
*/
HiddenEpisodeCount: number;

/**
* The number of seasons in the ordering scheme.
*/
SeasonCount: number;

/**
* Indicates the current ordering is the default ordering for the show.
*/
IsDefault: boolean;

/**
* Indicates the current ordering is the preferred ordering for the show.
*/
IsPreferred: boolean;

/**
* Indicates the current ordering is in use for the show.
*/
InUse: boolean;
};

export const enum AlternateOrderingTypeEnum {
Unknown = 'Unknown',
OriginalAirDate = 'OriginalAirDate',
Absolute = 'Absolute',
DVD = 'DVD',
Digital = 'Digital',
StoryArc = 'StoryArc',
Production = 'Production',
TV = 'TV',
}
11 changes: 11 additions & 0 deletions src/pages/collection/series/SeriesOverview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { flatMap, get, map, round } from 'lodash';
import CharacterImage from '@/components/CharacterImage';
import EpisodeSummary from '@/components/Collection/Episode/EpisodeSummary';
import SeriesMetadata from '@/components/Collection/SeriesMetadata';
import TmdbShowSettingsModal from '@/components/Dialogs/TmdbShowSettingsModal';
import MultiStateButton from '@/components/Input/MultiStateButton';
import ShokoPanel from '@/components/Panels/ShokoPanel';
import SeriesPoster from '@/components/SeriesPoster';
Expand All @@ -29,6 +30,10 @@ const MetadataLinks = ['AniDB', 'TMDB', 'TraktTv'] as const;
const SeriesOverview = () => {
const { series } = useOutletContext<SeriesContextType>();

const [selectedTmdbShowId, setSelectedTmdbShowId] = useState<number>(-1);
const showTmdbSettingsModal = selectedTmdbShowId > 0;
const handleTmdbSettingsClose = useEventCallback(() => setSelectedTmdbShowId(-1));

const nextUpEpisodeQuery = useSeriesNextUpQuery(series.IDs.ID, {
includeDataFrom: ['AniDB'],
includeMissing: false,
Expand Down Expand Up @@ -95,6 +100,7 @@ const SeriesOverview = () => {
id={id}
seriesId={series.IDs.ID}
type={type}
openTmdbShowSettings={setSelectedTmdbShowId}
/>
))),
/* Show row to add new TMDB links */
Expand Down Expand Up @@ -230,6 +236,11 @@ const SeriesOverview = () => {
</div>
))}
</div>
<TmdbShowSettingsModal
show={showTmdbSettingsModal}
onClose={handleTmdbSettingsClose}
showId={selectedTmdbShowId}
/>
</ShokoPanel>
</>
);
Expand Down
Loading

0 comments on commit b35912a

Please sign in to comment.