feat: season/episode list on series details (#2967)
This commit is contained in:
@@ -18,7 +18,7 @@ const clickFirstTitleCardInSlider = (sliderTitle: string): void => {
|
|||||||
|
|
||||||
describe('Discover', () => {
|
describe('Discover', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.login(Cypress.env('ADMIN_EMAIL'), Cypress.env('ADMIN_PASSWORD'));
|
cy.loginAsAdmin();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('loads a trending item', () => {
|
it('loads a trending item', () => {
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
describe('Login Page', () => {
|
describe('Login Page', () => {
|
||||||
it('succesfully logs in as an admin', () => {
|
it('succesfully logs in as an admin', () => {
|
||||||
cy.login(Cypress.env('ADMIN_EMAIL'), Cypress.env('ADMIN_PASSWORD'));
|
cy.loginAsAdmin();
|
||||||
cy.visit('/');
|
cy.visit('/');
|
||||||
cy.contains('Trending');
|
cy.contains('Trending');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('succesfully logs in as a local user', () => {
|
it('succesfully logs in as a local user', () => {
|
||||||
cy.login(Cypress.env('USER_EMAIL'), Cypress.env('USER_PASSWORD'));
|
cy.loginAsUser();
|
||||||
cy.visit('/');
|
cy.visit('/');
|
||||||
cy.contains('Trending');
|
cy.contains('Trending');
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
describe('Movie Details', () => {
|
describe('Movie Details', () => {
|
||||||
it('loads a movie page', () => {
|
it('loads a movie page', () => {
|
||||||
cy.login(Cypress.env('ADMIN_EMAIL'), Cypress.env('ADMIN_PASSWORD'));
|
cy.loginAsAdmin();
|
||||||
// Try to load minions: rise of gru
|
// Try to load minions: rise of gru
|
||||||
cy.visit('/movie/438148');
|
cy.visit('/movie/438148');
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
describe('General Settings', () => {
|
describe('General Settings', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.login(Cypress.env('ADMIN_EMAIL'), Cypress.env('ADMIN_PASSWORD'));
|
cy.loginAsAdmin();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('opens the settings page from the home page', () => {
|
it('opens the settings page from the home page', () => {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
describe('TV Details', () => {
|
describe('TV Details', () => {
|
||||||
it('loads a movie page', () => {
|
it('loads a tv details page', () => {
|
||||||
cy.login(Cypress.env('ADMIN_EMAIL'), Cypress.env('ADMIN_PASSWORD'));
|
cy.loginAsAdmin();
|
||||||
// Try to load stranger things
|
// Try to load stranger things
|
||||||
cy.visit('/tv/66732');
|
cy.visit('/tv/66732');
|
||||||
|
|
||||||
@@ -9,4 +9,20 @@ describe('TV Details', () => {
|
|||||||
'Stranger Things (2016)'
|
'Stranger Things (2016)'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('shows seasons and expands episodes', () => {
|
||||||
|
cy.loginAsAdmin();
|
||||||
|
|
||||||
|
// Try to load stranger things
|
||||||
|
cy.visit('/tv/66732');
|
||||||
|
|
||||||
|
// intercept request for season info
|
||||||
|
cy.intercept('/api/v1/tv/66732/season/4').as('season4');
|
||||||
|
|
||||||
|
cy.contains('Season 4').should('be.visible').scrollIntoView().click();
|
||||||
|
|
||||||
|
cy.wait('@season4');
|
||||||
|
|
||||||
|
cy.contains('Chapter Nine').should('be.visible');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ const visitUserEditPage = (email: string): void => {
|
|||||||
|
|
||||||
describe('Auto Request Settings', () => {
|
describe('Auto Request Settings', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.login(Cypress.env('ADMIN_EMAIL'), Cypress.env('ADMIN_PASSWORD'));
|
cy.loginAsAdmin();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not see watchlist sync settings on an account without permissions', () => {
|
it('should not see watchlist sync settings on an account without permissions', () => {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
describe('User Profile', () => {
|
describe('User Profile', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.login(Cypress.env('ADMIN_EMAIL'), Cypress.env('ADMIN_PASSWORD'));
|
cy.loginAsAdmin();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('opens user profile page from the home page', () => {
|
it('opens user profile page from the home page', () => {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ const testUser = {
|
|||||||
|
|
||||||
describe('User List', () => {
|
describe('User List', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.login(Cypress.env('ADMIN_EMAIL'), Cypress.env('ADMIN_PASSWORD'));
|
cy.loginAsAdmin();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('opens the user list from the home page', () => {
|
it('opens the user list from the home page', () => {
|
||||||
|
|||||||
@@ -24,3 +24,11 @@ Cypress.Commands.add('login', (email, password) => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Cypress.Commands.add('loginAsAdmin', () => {
|
||||||
|
cy.login(Cypress.env('ADMIN_EMAIL'), Cypress.env('ADMIN_PASSWORD'));
|
||||||
|
});
|
||||||
|
|
||||||
|
Cypress.Commands.add('loginAsUser', () => {
|
||||||
|
cy.login(Cypress.env('USER_EMAIL'), Cypress.env('USER_PASSWORD'));
|
||||||
|
});
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ declare global {
|
|||||||
namespace Cypress {
|
namespace Cypress {
|
||||||
interface Chainable {
|
interface Chainable {
|
||||||
login(email?: string, password?: string): Chainable<Element>;
|
login(email?: string, password?: string): Chainable<Element>;
|
||||||
|
loginAsAdmin(): Chainable<Element>;
|
||||||
|
loginAsUser(): Chainable<Element>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
"@formatjs/intl-displaynames": "6.0.3",
|
"@formatjs/intl-displaynames": "6.0.3",
|
||||||
"@formatjs/intl-locale": "3.0.3",
|
"@formatjs/intl-locale": "3.0.3",
|
||||||
"@formatjs/intl-pluralrules": "5.0.3",
|
"@formatjs/intl-pluralrules": "5.0.3",
|
||||||
|
"@formatjs/intl-utils": "^3.8.4",
|
||||||
"@headlessui/react": "1.6.6",
|
"@headlessui/react": "1.6.6",
|
||||||
"@heroicons/react": "1.0.6",
|
"@heroicons/react": "1.0.6",
|
||||||
"@supercharge/request-ip": "1.2.0",
|
"@supercharge/request-ip": "1.2.0",
|
||||||
|
|||||||
62
src/components/AirDateBadge/index.tsx
Normal file
62
src/components/AirDateBadge/index.tsx
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import Badge from '@app/components/Common/Badge';
|
||||||
|
import { defineMessages, FormattedRelativeTime, useIntl } from 'react-intl';
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
airedrelative: 'Aired {relativeTime}',
|
||||||
|
airsrelative: 'Airs {relativeTime}',
|
||||||
|
});
|
||||||
|
|
||||||
|
type AirDateBadgeProps = {
|
||||||
|
airDate: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const AirDateBadge = ({ airDate }: AirDateBadgeProps) => {
|
||||||
|
const WEEK = 1000 * 60 * 60 * 24 * 8;
|
||||||
|
const intl = useIntl();
|
||||||
|
const dAirDate = new Date(airDate);
|
||||||
|
const nowDate = new Date();
|
||||||
|
const alreadyAired = dAirDate.getTime() < nowDate.getTime();
|
||||||
|
|
||||||
|
const compareWeek = new Date(
|
||||||
|
alreadyAired ? Date.now() - WEEK : Date.now() + WEEK
|
||||||
|
);
|
||||||
|
|
||||||
|
let showRelative = false;
|
||||||
|
|
||||||
|
if (
|
||||||
|
(alreadyAired && dAirDate.getTime() > compareWeek.getTime()) ||
|
||||||
|
(!alreadyAired && dAirDate.getTime() < compareWeek.getTime())
|
||||||
|
) {
|
||||||
|
showRelative = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Badge badgeType="light">
|
||||||
|
{intl.formatDate(dAirDate, {
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'long',
|
||||||
|
day: 'numeric',
|
||||||
|
})}
|
||||||
|
</Badge>
|
||||||
|
{showRelative && (
|
||||||
|
<Badge badgeType="light">
|
||||||
|
{intl.formatMessage(
|
||||||
|
alreadyAired ? messages.airedrelative : messages.airsrelative,
|
||||||
|
{
|
||||||
|
relativeTime: (
|
||||||
|
<FormattedRelativeTime
|
||||||
|
value={(dAirDate.getTime() - Date.now()) / 1000}
|
||||||
|
numeric="auto"
|
||||||
|
updateIntervalInSeconds={1}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AirDateBadge;
|
||||||
@@ -1,7 +1,14 @@
|
|||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
|
|
||||||
interface BadgeProps {
|
interface BadgeProps {
|
||||||
badgeType?: 'default' | 'primary' | 'danger' | 'warning' | 'success';
|
badgeType?:
|
||||||
|
| 'default'
|
||||||
|
| 'primary'
|
||||||
|
| 'danger'
|
||||||
|
| 'warning'
|
||||||
|
| 'success'
|
||||||
|
| 'dark'
|
||||||
|
| 'light';
|
||||||
className?: string;
|
className?: string;
|
||||||
href?: string;
|
href?: string;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
@@ -42,6 +49,18 @@ const Badge = ({
|
|||||||
badgeStyle.push('hover:bg-green-400');
|
badgeStyle.push('hover:bg-green-400');
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case 'dark':
|
||||||
|
badgeStyle.push('bg-gray-900 !text-gray-400');
|
||||||
|
if (href) {
|
||||||
|
badgeStyle.push('hover:bg-gray-800');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'light':
|
||||||
|
badgeStyle.push('bg-gray-700 !text-gray-300');
|
||||||
|
if (href) {
|
||||||
|
badgeStyle.push('hover:bg-gray-600');
|
||||||
|
}
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
badgeStyle.push('bg-indigo-500 !text-indigo-100');
|
badgeStyle.push('bg-indigo-500 !text-indigo-100');
|
||||||
if (href) {
|
if (href) {
|
||||||
|
|||||||
62
src/components/TvDetails/Season/index.tsx
Normal file
62
src/components/TvDetails/Season/index.tsx
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import AirDateBadge from '@app/components/AirDateBadge';
|
||||||
|
import LoadingSpinner from '@app/components/Common/LoadingSpinner';
|
||||||
|
import type { SeasonWithEpisodes } from '@server/models/Tv';
|
||||||
|
import { defineMessages, useIntl } from 'react-intl';
|
||||||
|
import useSWR from 'swr';
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
somethingwentwrong: 'Something went wrong while retrieving season data.',
|
||||||
|
});
|
||||||
|
|
||||||
|
type SeasonProps = {
|
||||||
|
seasonNumber: number;
|
||||||
|
tvId: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const Season = ({ seasonNumber, tvId }: SeasonProps) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
const { data, error } = useSWR<SeasonWithEpisodes>(
|
||||||
|
`/api/v1/tv/${tvId}/season/${seasonNumber}`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!data && !error) {
|
||||||
|
return <LoadingSpinner />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
return <div>{intl.formatMessage(messages.somethingwentwrong)}</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col justify-center divide-y divide-gray-700">
|
||||||
|
{data.episodes
|
||||||
|
.slice()
|
||||||
|
.reverse()
|
||||||
|
.map((episode) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="flex flex-col space-y-4 py-4 xl:flex-row xl:space-y-4 xl:space-x-4"
|
||||||
|
key={`season-${seasonNumber}-episode-${episode.episodeNumber}`}
|
||||||
|
>
|
||||||
|
<div className="flex-1">
|
||||||
|
<div className="flex flex-col space-y-2 xl:flex-row xl:items-center xl:space-y-0 xl:space-x-2">
|
||||||
|
<h3 className="text-lg">{episode.name}</h3>
|
||||||
|
<AirDateBadge airDate={episode.airDate} />
|
||||||
|
</div>
|
||||||
|
{episode.overview && <p>{episode.overview}</p>}
|
||||||
|
</div>
|
||||||
|
{episode.stillPath && (
|
||||||
|
<img
|
||||||
|
className="h-auto w-full rounded-lg xl:h-32 xl:w-auto"
|
||||||
|
src={`https://image.tmdb.org/t/p/original/${episode.stillPath}`}
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Season;
|
||||||
@@ -3,6 +3,7 @@ import RTAudRotten from '@app/assets/rt_aud_rotten.svg';
|
|||||||
import RTFresh from '@app/assets/rt_fresh.svg';
|
import RTFresh from '@app/assets/rt_fresh.svg';
|
||||||
import RTRotten from '@app/assets/rt_rotten.svg';
|
import RTRotten from '@app/assets/rt_rotten.svg';
|
||||||
import TmdbLogo from '@app/assets/tmdb_logo.svg';
|
import TmdbLogo from '@app/assets/tmdb_logo.svg';
|
||||||
|
import Badge from '@app/components/Common/Badge';
|
||||||
import Button from '@app/components/Common/Button';
|
import Button from '@app/components/Common/Button';
|
||||||
import CachedImage from '@app/components/Common/CachedImage';
|
import CachedImage from '@app/components/Common/CachedImage';
|
||||||
import LoadingSpinner from '@app/components/Common/LoadingSpinner';
|
import LoadingSpinner from '@app/components/Common/LoadingSpinner';
|
||||||
@@ -19,12 +20,14 @@ import RequestButton from '@app/components/RequestButton';
|
|||||||
import RequestModal from '@app/components/RequestModal';
|
import RequestModal from '@app/components/RequestModal';
|
||||||
import Slider from '@app/components/Slider';
|
import Slider from '@app/components/Slider';
|
||||||
import StatusBadge from '@app/components/StatusBadge';
|
import StatusBadge from '@app/components/StatusBadge';
|
||||||
|
import Season from '@app/components/TvDetails/Season';
|
||||||
import useLocale from '@app/hooks/useLocale';
|
import useLocale from '@app/hooks/useLocale';
|
||||||
import useSettings from '@app/hooks/useSettings';
|
import useSettings from '@app/hooks/useSettings';
|
||||||
import { Permission, useUser } from '@app/hooks/useUser';
|
import { Permission, useUser } from '@app/hooks/useUser';
|
||||||
import globalMessages from '@app/i18n/globalMessages';
|
import globalMessages from '@app/i18n/globalMessages';
|
||||||
import Error from '@app/pages/_error';
|
import Error from '@app/pages/_error';
|
||||||
import { sortCrewPriority } from '@app/utils/creditHelpers';
|
import { sortCrewPriority } from '@app/utils/creditHelpers';
|
||||||
|
import { Disclosure, Transition } from '@headlessui/react';
|
||||||
import {
|
import {
|
||||||
ArrowCircleRightIcon,
|
ArrowCircleRightIcon,
|
||||||
CogIcon,
|
CogIcon,
|
||||||
@@ -32,10 +35,11 @@ import {
|
|||||||
FilmIcon,
|
FilmIcon,
|
||||||
PlayIcon,
|
PlayIcon,
|
||||||
} from '@heroicons/react/outline';
|
} from '@heroicons/react/outline';
|
||||||
|
import { ChevronUpIcon } from '@heroicons/react/solid';
|
||||||
import type { RTRating } from '@server/api/rottentomatoes';
|
import type { RTRating } from '@server/api/rottentomatoes';
|
||||||
import { ANIME_KEYWORD_ID } from '@server/api/themoviedb/constants';
|
import { ANIME_KEYWORD_ID } from '@server/api/themoviedb/constants';
|
||||||
import { IssueStatus } from '@server/constants/issue';
|
import { IssueStatus } from '@server/constants/issue';
|
||||||
import { MediaStatus } from '@server/constants/media';
|
import { MediaRequestStatus, MediaStatus } from '@server/constants/media';
|
||||||
import type { Crew } from '@server/models/common';
|
import type { Crew } from '@server/models/common';
|
||||||
import type { TvDetails as TvDetailsType } from '@server/models/Tv';
|
import type { TvDetails as TvDetailsType } from '@server/models/Tv';
|
||||||
import { hasFlag } from 'country-flag-icons';
|
import { hasFlag } from 'country-flag-icons';
|
||||||
@@ -71,6 +75,10 @@ const messages = defineMessages({
|
|||||||
'Production {countryCount, plural, one {Country} other {Countries}}',
|
'Production {countryCount, plural, one {Country} other {Countries}}',
|
||||||
reportissue: 'Report an Issue',
|
reportissue: 'Report an Issue',
|
||||||
manageseries: 'Manage Series',
|
manageseries: 'Manage Series',
|
||||||
|
seasonstitle: 'Seasons',
|
||||||
|
episodeCount: '{episodeCount, plural, one {# Episode} other {# Episodes}}',
|
||||||
|
seasonnumber: 'Season {seasonNumber}',
|
||||||
|
status4k: '4K {status}',
|
||||||
});
|
});
|
||||||
|
|
||||||
interface TvDetailsProps {
|
interface TvDetailsProps {
|
||||||
@@ -476,6 +484,174 @@ const TvDetails = ({ tv }: TvDetailsProps) => {
|
|||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
<h2 className="py-4">{intl.formatMessage(messages.seasonstitle)}</h2>
|
||||||
|
<div className="flex w-full flex-col space-y-2">
|
||||||
|
{data.seasons
|
||||||
|
.slice()
|
||||||
|
.reverse()
|
||||||
|
.filter((season) => season.seasonNumber !== 0)
|
||||||
|
.map((season) => {
|
||||||
|
const show4k =
|
||||||
|
settings.currentSettings.series4kEnabled &&
|
||||||
|
hasPermission(
|
||||||
|
[
|
||||||
|
Permission.MANAGE_REQUESTS,
|
||||||
|
Permission.REQUEST_4K,
|
||||||
|
Permission.REQUEST_4K_TV,
|
||||||
|
],
|
||||||
|
{
|
||||||
|
type: 'or',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const mSeason = (data.mediaInfo?.seasons ?? []).find(
|
||||||
|
(s) =>
|
||||||
|
season.seasonNumber === s.seasonNumber &&
|
||||||
|
s.status !== MediaStatus.UNKNOWN
|
||||||
|
);
|
||||||
|
const mSeason4k = (data.mediaInfo?.seasons ?? []).find(
|
||||||
|
(s) =>
|
||||||
|
season.seasonNumber === s.seasonNumber &&
|
||||||
|
s.status4k !== MediaStatus.UNKNOWN
|
||||||
|
);
|
||||||
|
const request = (data.mediaInfo?.requests ?? []).find(
|
||||||
|
(r) =>
|
||||||
|
!!r.seasons.find(
|
||||||
|
(s) => s.seasonNumber === season.seasonNumber
|
||||||
|
) && !r.is4k
|
||||||
|
);
|
||||||
|
const request4k = (data.mediaInfo?.requests ?? []).find(
|
||||||
|
(r) =>
|
||||||
|
!!r.seasons.find(
|
||||||
|
(s) => s.seasonNumber === season.seasonNumber
|
||||||
|
) && r.is4k
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Disclosure key={`season-discoslure-${season.seasonNumber}`}>
|
||||||
|
{({ open }) => (
|
||||||
|
<>
|
||||||
|
<Disclosure.Button
|
||||||
|
className={`mt-2 flex w-full items-center justify-between border-gray-700 bg-gray-800 px-4 py-2 text-gray-200 ${
|
||||||
|
open
|
||||||
|
? 'rounded-t-md border-t border-l border-r'
|
||||||
|
: 'rounded-md border'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div className="flex flex-1 items-center space-x-2 text-lg">
|
||||||
|
<span>
|
||||||
|
{intl.formatMessage(messages.seasonnumber, {
|
||||||
|
seasonNumber: season.seasonNumber,
|
||||||
|
})}
|
||||||
|
</span>
|
||||||
|
<Badge badgeType="dark">
|
||||||
|
{intl.formatMessage(messages.episodeCount, {
|
||||||
|
episodeCount: season.episodeCount,
|
||||||
|
})}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
{((!mSeason &&
|
||||||
|
request?.status === MediaRequestStatus.APPROVED) ||
|
||||||
|
mSeason?.status === MediaStatus.PROCESSING) && (
|
||||||
|
<Badge badgeType="primary">
|
||||||
|
{intl.formatMessage(globalMessages.requested)}
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
{((!mSeason &&
|
||||||
|
request?.status === MediaRequestStatus.PENDING) ||
|
||||||
|
mSeason?.status === MediaStatus.PENDING) && (
|
||||||
|
<Badge badgeType="warning">
|
||||||
|
{intl.formatMessage(globalMessages.pending)}
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
{mSeason?.status ===
|
||||||
|
MediaStatus.PARTIALLY_AVAILABLE && (
|
||||||
|
<Badge badgeType="success">
|
||||||
|
{intl.formatMessage(
|
||||||
|
globalMessages.partiallyavailable
|
||||||
|
)}
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
{mSeason?.status === MediaStatus.AVAILABLE && (
|
||||||
|
<Badge badgeType="success">
|
||||||
|
{intl.formatMessage(globalMessages.available)}
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
{((!mSeason4k &&
|
||||||
|
request4k?.status ===
|
||||||
|
MediaRequestStatus.APPROVED) ||
|
||||||
|
mSeason4k?.status4k === MediaStatus.PROCESSING) &&
|
||||||
|
show4k && (
|
||||||
|
<Badge badgeType="primary">
|
||||||
|
{intl.formatMessage(messages.status4k, {
|
||||||
|
status: intl.formatMessage(
|
||||||
|
globalMessages.requested
|
||||||
|
),
|
||||||
|
})}
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
{((!mSeason4k &&
|
||||||
|
request4k?.status === MediaRequestStatus.PENDING) ||
|
||||||
|
mSeason?.status4k === MediaStatus.PENDING) &&
|
||||||
|
show4k && (
|
||||||
|
<Badge badgeType="warning">
|
||||||
|
{intl.formatMessage(messages.status4k, {
|
||||||
|
status: intl.formatMessage(
|
||||||
|
globalMessages.pending
|
||||||
|
),
|
||||||
|
})}
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
{mSeason4k?.status4k ===
|
||||||
|
MediaStatus.PARTIALLY_AVAILABLE &&
|
||||||
|
show4k && (
|
||||||
|
<Badge badgeType="success">
|
||||||
|
{intl.formatMessage(messages.status4k, {
|
||||||
|
status: intl.formatMessage(
|
||||||
|
globalMessages.partiallyavailable
|
||||||
|
),
|
||||||
|
})}
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
{mSeason4k?.status4k === MediaStatus.AVAILABLE &&
|
||||||
|
show4k && (
|
||||||
|
<Badge badgeType="success">
|
||||||
|
{intl.formatMessage(messages.status4k, {
|
||||||
|
status: intl.formatMessage(
|
||||||
|
globalMessages.available
|
||||||
|
),
|
||||||
|
})}
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
<ChevronUpIcon
|
||||||
|
className={`${
|
||||||
|
open ? 'rotate-180 transform' : ''
|
||||||
|
} h-5 w-5 text-gray-500`}
|
||||||
|
/>
|
||||||
|
</Disclosure.Button>
|
||||||
|
<Transition
|
||||||
|
show={open}
|
||||||
|
enter="transition duration-100 ease-out"
|
||||||
|
enterFrom="transform opacity-0"
|
||||||
|
enterTo="transform opacity-100"
|
||||||
|
leave="transition duration-75 ease-out"
|
||||||
|
leaveFrom="transform opacity-100"
|
||||||
|
leaveTo="transform opacity-0"
|
||||||
|
// Not sure why this transition is adding a margin without this here
|
||||||
|
style={{ margin: '0px' }}
|
||||||
|
>
|
||||||
|
<Disclosure.Panel className="w-full rounded-b-md border-b border-l border-r border-gray-700 px-4 pb-2">
|
||||||
|
<Season
|
||||||
|
tvId={data.id}
|
||||||
|
seasonNumber={season.seasonNumber}
|
||||||
|
/>
|
||||||
|
</Disclosure.Panel>
|
||||||
|
</Transition>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Disclosure>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="media-overview-right">
|
<div className="media-overview-right">
|
||||||
<div className="media-facts">
|
<div className="media-facts">
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
{
|
{
|
||||||
|
"components.AirDateBadge.airedrelative": "Aired {relativeTime}",
|
||||||
|
"components.AirDateBadge.airsrelative": "Airs {relativeTime}",
|
||||||
"components.AppDataWarning.dockerVolumeMissingDescription": "The <code>{appDataPath}</code> volume mount was not configured properly. All data will be cleared when the container is stopped or restarted.",
|
"components.AppDataWarning.dockerVolumeMissingDescription": "The <code>{appDataPath}</code> volume mount was not configured properly. All data will be cleared when the container is stopped or restarted.",
|
||||||
"components.CollectionDetails.numberofmovies": "{count} Movies",
|
"components.CollectionDetails.numberofmovies": "{count} Movies",
|
||||||
"components.CollectionDetails.overview": "Overview",
|
"components.CollectionDetails.overview": "Overview",
|
||||||
@@ -858,10 +860,12 @@
|
|||||||
"components.TitleCard.mediaerror": "{mediaType} Not Found",
|
"components.TitleCard.mediaerror": "{mediaType} Not Found",
|
||||||
"components.TitleCard.tmdbid": "TMDB ID",
|
"components.TitleCard.tmdbid": "TMDB ID",
|
||||||
"components.TitleCard.tvdbid": "TheTVDB ID",
|
"components.TitleCard.tvdbid": "TheTVDB ID",
|
||||||
|
"components.TvDetails.Season.somethingwentwrong": "Something went wrong while retrieving season data.",
|
||||||
"components.TvDetails.TvCast.fullseriescast": "Full Series Cast",
|
"components.TvDetails.TvCast.fullseriescast": "Full Series Cast",
|
||||||
"components.TvDetails.TvCrew.fullseriescrew": "Full Series Crew",
|
"components.TvDetails.TvCrew.fullseriescrew": "Full Series Crew",
|
||||||
"components.TvDetails.anime": "Anime",
|
"components.TvDetails.anime": "Anime",
|
||||||
"components.TvDetails.cast": "Cast",
|
"components.TvDetails.cast": "Cast",
|
||||||
|
"components.TvDetails.episodeCount": "{episodeCount, plural, one {# Episode} other {# Episodes}}",
|
||||||
"components.TvDetails.episodeRuntime": "Episode Runtime",
|
"components.TvDetails.episodeRuntime": "Episode Runtime",
|
||||||
"components.TvDetails.episodeRuntimeMinutes": "{runtime} minutes",
|
"components.TvDetails.episodeRuntimeMinutes": "{runtime} minutes",
|
||||||
"components.TvDetails.firstAirDate": "First Air Date",
|
"components.TvDetails.firstAirDate": "First Air Date",
|
||||||
@@ -877,9 +881,12 @@
|
|||||||
"components.TvDetails.productioncountries": "Production {countryCount, plural, one {Country} other {Countries}}",
|
"components.TvDetails.productioncountries": "Production {countryCount, plural, one {Country} other {Countries}}",
|
||||||
"components.TvDetails.recommendations": "Recommendations",
|
"components.TvDetails.recommendations": "Recommendations",
|
||||||
"components.TvDetails.reportissue": "Report an Issue",
|
"components.TvDetails.reportissue": "Report an Issue",
|
||||||
|
"components.TvDetails.seasonnumber": "Season {seasonNumber}",
|
||||||
"components.TvDetails.seasons": "{seasonCount, plural, one {# Season} other {# Seasons}}",
|
"components.TvDetails.seasons": "{seasonCount, plural, one {# Season} other {# Seasons}}",
|
||||||
|
"components.TvDetails.seasonstitle": "Seasons",
|
||||||
"components.TvDetails.showtype": "Series Type",
|
"components.TvDetails.showtype": "Series Type",
|
||||||
"components.TvDetails.similar": "Similar Series",
|
"components.TvDetails.similar": "Similar Series",
|
||||||
|
"components.TvDetails.status4k": "4K {status}",
|
||||||
"components.TvDetails.streamingproviders": "Currently Streaming On",
|
"components.TvDetails.streamingproviders": "Currently Streaming On",
|
||||||
"components.TvDetails.viewfullcrew": "View Full Crew",
|
"components.TvDetails.viewfullcrew": "View Full Crew",
|
||||||
"components.TvDetails.watchtrailer": "Watch Trailer",
|
"components.TvDetails.watchtrailer": "Watch Trailer",
|
||||||
|
|||||||
12
yarn.lock
12
yarn.lock
@@ -1750,6 +1750,13 @@
|
|||||||
"@formatjs/intl-localematcher" "0.2.28"
|
"@formatjs/intl-localematcher" "0.2.28"
|
||||||
tslib "2.4.0"
|
tslib "2.4.0"
|
||||||
|
|
||||||
|
"@formatjs/intl-utils@^3.8.4":
|
||||||
|
version "3.8.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/@formatjs/intl-utils/-/intl-utils-3.8.4.tgz#291baac91001db428fc3275c515a3e40fbe95945"
|
||||||
|
integrity sha512-j5C6NyfKevIxsfLK8KwO1C0vvP7k1+h4A9cFpc+cr6mEwCc1sPkr17dzh0Ke6k9U5pQccAQoXdcNBl3IYa4+ZQ==
|
||||||
|
dependencies:
|
||||||
|
emojis-list "^3.0.0"
|
||||||
|
|
||||||
"@formatjs/intl@2.3.1":
|
"@formatjs/intl@2.3.1":
|
||||||
version "2.3.1"
|
version "2.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/@formatjs/intl/-/intl-2.3.1.tgz#eccd6d03e4db18c256181f235b1d0a7f7aaebf5a"
|
resolved "https://registry.yarnpkg.com/@formatjs/intl/-/intl-2.3.1.tgz#eccd6d03e4db18c256181f235b1d0a7f7aaebf5a"
|
||||||
@@ -5435,6 +5442,11 @@ emoji-regex@^9.2.2:
|
|||||||
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72"
|
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72"
|
||||||
integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==
|
integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==
|
||||||
|
|
||||||
|
emojis-list@^3.0.0:
|
||||||
|
version "3.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78"
|
||||||
|
integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==
|
||||||
|
|
||||||
enabled@2.0.x:
|
enabled@2.0.x:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/enabled/-/enabled-2.0.0.tgz#f9dd92ec2d6f4bbc0d5d1e64e21d61cd4665e7c2"
|
resolved "https://registry.yarnpkg.com/enabled/-/enabled-2.0.0.tgz#f9dd92ec2d6f4bbc0d5d1e64e21d61cd4665e7c2"
|
||||||
|
|||||||
Reference in New Issue
Block a user