mirror of
https://github.com/sct/overseerr.git
synced 2025-09-17 17:24:35 +02:00
feat(ui): Add support for requesting collections in 4K (#968)
This commit is contained in:
@@ -8,16 +8,17 @@ import { MediaStatus } from '../../../server/constants/media';
|
|||||||
import type { MediaRequest } from '../../../server/entity/MediaRequest';
|
import type { MediaRequest } from '../../../server/entity/MediaRequest';
|
||||||
import type { Collection } from '../../../server/models/Collection';
|
import type { Collection } from '../../../server/models/Collection';
|
||||||
import { LanguageContext } from '../../context/LanguageContext';
|
import { LanguageContext } from '../../context/LanguageContext';
|
||||||
import globalMessages from '../../i18n/globalMessages';
|
|
||||||
import Error from '../../pages/_error';
|
import Error from '../../pages/_error';
|
||||||
import Badge from '../Common/Badge';
|
import StatusBadge from '../StatusBadge';
|
||||||
import Button from '../Common/Button';
|
import ButtonWithDropdown from '../Common/ButtonWithDropdown';
|
||||||
import LoadingSpinner from '../Common/LoadingSpinner';
|
import LoadingSpinner from '../Common/LoadingSpinner';
|
||||||
import Modal from '../Common/Modal';
|
import Modal from '../Common/Modal';
|
||||||
import Slider from '../Slider';
|
import Slider from '../Slider';
|
||||||
import TitleCard from '../TitleCard';
|
import TitleCard from '../TitleCard';
|
||||||
import Transition from '../Transition';
|
import Transition from '../Transition';
|
||||||
import PageTitle from '../Common/PageTitle';
|
import PageTitle from '../Common/PageTitle';
|
||||||
|
import { useUser, Permission } from '../../hooks/useUser';
|
||||||
|
import useSettings from '../../hooks/useSettings';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
overviewunavailable: 'Overview unavailable.',
|
overviewunavailable: 'Overview unavailable.',
|
||||||
@@ -29,6 +30,10 @@ const messages = defineMessages({
|
|||||||
requestcollection: 'Request Collection',
|
requestcollection: 'Request Collection',
|
||||||
requestswillbecreated:
|
requestswillbecreated:
|
||||||
'The following titles will have requests created for them:',
|
'The following titles will have requests created for them:',
|
||||||
|
request4k: 'Request 4K',
|
||||||
|
requestcollection4k: 'Request Collection in 4K',
|
||||||
|
requestswillbecreated4k:
|
||||||
|
'The following titles will have 4K requests created for them:',
|
||||||
requestSuccess: '<strong>{title}</strong> successfully requested!',
|
requestSuccess: '<strong>{title}</strong> successfully requested!',
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -41,10 +46,14 @@ const CollectionDetails: React.FC<CollectionDetailsProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const settings = useSettings();
|
||||||
const { addToast } = useToasts();
|
const { addToast } = useToasts();
|
||||||
const { locale } = useContext(LanguageContext);
|
const { locale } = useContext(LanguageContext);
|
||||||
|
const { hasPermission } = useUser();
|
||||||
const [requestModal, setRequestModal] = useState(false);
|
const [requestModal, setRequestModal] = useState(false);
|
||||||
const [isRequesting, setRequesting] = useState(false);
|
const [isRequesting, setRequesting] = useState(false);
|
||||||
|
const [is4k, setIs4k] = useState(false);
|
||||||
|
|
||||||
const { data, error, revalidate } = useSWR<Collection>(
|
const { data, error, revalidate } = useSWR<Collection>(
|
||||||
`/api/v1/collection/${router.query.collectionId}?language=${locale}`,
|
`/api/v1/collection/${router.query.collectionId}?language=${locale}`,
|
||||||
{
|
{
|
||||||
@@ -61,8 +70,45 @@ const CollectionDetails: React.FC<CollectionDetailsProps> = ({
|
|||||||
return <Error statusCode={404} />;
|
return <Error statusCode={404} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let collectionStatus = MediaStatus.UNKNOWN;
|
||||||
|
let collectionStatus4k = MediaStatus.UNKNOWN;
|
||||||
|
|
||||||
|
if (
|
||||||
|
data.parts.every(
|
||||||
|
(part) =>
|
||||||
|
part.mediaInfo && part.mediaInfo.status === MediaStatus.AVAILABLE
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
collectionStatus = MediaStatus.AVAILABLE;
|
||||||
|
} else if (
|
||||||
|
data.parts.some(
|
||||||
|
(part) =>
|
||||||
|
part.mediaInfo && part.mediaInfo.status === MediaStatus.AVAILABLE
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
collectionStatus = MediaStatus.PARTIALLY_AVAILABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
data.parts.every(
|
||||||
|
(part) =>
|
||||||
|
part.mediaInfo && part.mediaInfo.status4k === MediaStatus.AVAILABLE
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
collectionStatus4k = MediaStatus.AVAILABLE;
|
||||||
|
} else if (
|
||||||
|
data.parts.some(
|
||||||
|
(part) =>
|
||||||
|
part.mediaInfo && part.mediaInfo.status4k === MediaStatus.AVAILABLE
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
collectionStatus4k = MediaStatus.PARTIALLY_AVAILABLE;
|
||||||
|
}
|
||||||
|
|
||||||
const requestableParts = data.parts.filter(
|
const requestableParts = data.parts.filter(
|
||||||
(part) => !part.mediaInfo || part.mediaInfo.status === MediaStatus.UNKNOWN
|
(part) =>
|
||||||
|
!part.mediaInfo ||
|
||||||
|
part.mediaInfo[is4k ? 'status4k' : 'status'] === MediaStatus.UNKNOWN
|
||||||
);
|
);
|
||||||
|
|
||||||
const requestBundle = async () => {
|
const requestBundle = async () => {
|
||||||
@@ -73,6 +119,7 @@ const CollectionDetails: React.FC<CollectionDetailsProps> = ({
|
|||||||
await axios.post<MediaRequest>('/api/v1/request', {
|
await axios.post<MediaRequest>('/api/v1/request', {
|
||||||
mediaId: part.id,
|
mediaId: part.id,
|
||||||
mediaType: 'movie',
|
mediaType: 'movie',
|
||||||
|
is4k,
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@@ -123,12 +170,14 @@ const CollectionDetails: React.FC<CollectionDetailsProps> = ({
|
|||||||
okText={
|
okText={
|
||||||
isRequesting
|
isRequesting
|
||||||
? intl.formatMessage(messages.requesting)
|
? intl.formatMessage(messages.requesting)
|
||||||
: intl.formatMessage(messages.request)
|
: intl.formatMessage(is4k ? messages.request4k : messages.request)
|
||||||
}
|
}
|
||||||
okDisabled={isRequesting}
|
okDisabled={isRequesting}
|
||||||
okButtonType="primary"
|
okButtonType="primary"
|
||||||
onCancel={() => setRequestModal(false)}
|
onCancel={() => setRequestModal(false)}
|
||||||
title={intl.formatMessage(messages.requestcollection)}
|
title={intl.formatMessage(
|
||||||
|
is4k ? messages.requestcollection4k : messages.requestcollection
|
||||||
|
)}
|
||||||
iconSvg={
|
iconSvg={
|
||||||
<svg
|
<svg
|
||||||
className="w-6 h-6"
|
className="w-6 h-6"
|
||||||
@@ -146,13 +195,20 @@ const CollectionDetails: React.FC<CollectionDetailsProps> = ({
|
|||||||
</svg>
|
</svg>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<p>{intl.formatMessage(messages.requestswillbecreated)}</p>
|
<p>
|
||||||
|
{intl.formatMessage(
|
||||||
|
is4k
|
||||||
|
? messages.requestswillbecreated4k
|
||||||
|
: messages.requestswillbecreated
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
<ul className="py-4 pl-8 list-disc">
|
<ul className="py-4 pl-8 list-disc">
|
||||||
{data.parts
|
{data.parts
|
||||||
.filter(
|
.filter(
|
||||||
(part) =>
|
(part) =>
|
||||||
!part.mediaInfo ||
|
!part.mediaInfo ||
|
||||||
part.mediaInfo?.status === MediaStatus.UNKNOWN
|
part.mediaInfo[is4k ? 'status4k' : 'status'] ===
|
||||||
|
MediaStatus.UNKNOWN
|
||||||
)
|
)
|
||||||
.map((part) => (
|
.map((part) => (
|
||||||
<li key={`request-part-${part.id}`}>{part.title}</li>
|
<li key={`request-part-${part.id}`}>{part.title}</li>
|
||||||
@@ -160,64 +216,128 @@ const CollectionDetails: React.FC<CollectionDetailsProps> = ({
|
|||||||
</ul>
|
</ul>
|
||||||
</Modal>
|
</Modal>
|
||||||
</Transition>
|
</Transition>
|
||||||
<div className="flex flex-col items-center pt-4 md:flex-row md:items-end">
|
<div className="flex flex-col items-center pt-4 lg:flex-row lg:items-end">
|
||||||
<div className="flex-shrink-0 md:mr-4">
|
<div className="lg:mr-4">
|
||||||
<img
|
<img
|
||||||
src={`//image.tmdb.org/t/p/w600_and_h900_bestv2${data.posterPath}`}
|
src={`//image.tmdb.org/t/p/w600_and_h900_bestv2${data.posterPath}`}
|
||||||
alt=""
|
alt=""
|
||||||
className="w-32 rounded shadow md:rounded-lg md:shadow-2xl md:w-52"
|
className="w-32 rounded shadow md:rounded-lg md:shadow-2xl md:w-44 lg:w-52"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col mt-4 text-center text-white md:mr-4 md:mt-0 md:text-left">
|
<div className="flex flex-col flex-1 mt-4 text-center text-white lg:mr-4 lg:mt-0 lg:text-left">
|
||||||
<div className="mb-2">
|
<div className="mb-2 space-x-2">
|
||||||
{data.parts.every(
|
<span className="ml-2 lg:ml-0">
|
||||||
(part) => part.mediaInfo?.status === MediaStatus.AVAILABLE
|
<StatusBadge
|
||||||
) && (
|
status={collectionStatus}
|
||||||
<Badge badgeType="success">
|
inProgress={data.parts.some(
|
||||||
{intl.formatMessage(globalMessages.available)}
|
(part) => (part.mediaInfo?.downloadStatus ?? []).length > 0
|
||||||
</Badge>
|
)}
|
||||||
)}
|
/>
|
||||||
{!data.parts.every(
|
</span>
|
||||||
(part) => part.mediaInfo?.status === MediaStatus.AVAILABLE
|
{settings.currentSettings.movie4kEnabled &&
|
||||||
) &&
|
hasPermission(
|
||||||
data.parts.some(
|
[Permission.REQUEST_4K, Permission.REQUEST_4K_MOVIE],
|
||||||
(part) => part.mediaInfo?.status === MediaStatus.AVAILABLE
|
{
|
||||||
|
type: 'or',
|
||||||
|
}
|
||||||
) && (
|
) && (
|
||||||
<Badge badgeType="success">
|
<span>
|
||||||
{intl.formatMessage(globalMessages.partiallyavailable)}
|
<StatusBadge
|
||||||
</Badge>
|
status={collectionStatus4k}
|
||||||
|
is4k
|
||||||
|
inProgress={data.parts.some(
|
||||||
|
(part) =>
|
||||||
|
(part.mediaInfo?.downloadStatus4k ?? []).length > 0
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<h1 className="text-2xl md:text-4xl">{data.name}</h1>
|
<h1 className="text-2xl md:text-4xl">{data.name}</h1>
|
||||||
<span className="mt-1 text-xs md:text-base md:mt-0">
|
<span className="mt-1 text-xs lg:text-base lg:mt-0">
|
||||||
{intl.formatMessage(messages.numberofmovies, {
|
{intl.formatMessage(messages.numberofmovies, {
|
||||||
count: data.parts.length,
|
count: data.parts.length,
|
||||||
})}
|
})}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-end flex-1 mt-4 md:mt-0">
|
<div className="relative z-10 flex flex-wrap justify-center flex-shrink-0 mt-4 sm:justify-end sm:flex-nowrap lg:mt-0">
|
||||||
{data.parts.some(
|
{hasPermission(Permission.REQUEST) &&
|
||||||
(part) =>
|
(collectionStatus !== MediaStatus.AVAILABLE ||
|
||||||
!part.mediaInfo || part.mediaInfo?.status === MediaStatus.UNKNOWN
|
(settings.currentSettings.movie4kEnabled &&
|
||||||
) && (
|
hasPermission(
|
||||||
<Button buttonType="primary" onClick={() => setRequestModal(true)}>
|
[Permission.REQUEST_4K, Permission.REQUEST_4K_MOVIE],
|
||||||
<svg
|
{ type: 'or' }
|
||||||
className="w-4 mr-1"
|
) &&
|
||||||
fill="none"
|
collectionStatus4k !== MediaStatus.AVAILABLE)) && (
|
||||||
stroke="currentColor"
|
<div className="mb-3 sm:mb-0">
|
||||||
viewBox="0 0 24 24"
|
<ButtonWithDropdown
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
buttonType="primary"
|
||||||
>
|
onClick={() => {
|
||||||
<path
|
setRequestModal(true);
|
||||||
strokeLinecap="round"
|
setIs4k(collectionStatus === MediaStatus.AVAILABLE);
|
||||||
strokeLinejoin="round"
|
}}
|
||||||
strokeWidth={2}
|
text={
|
||||||
d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"
|
<>
|
||||||
/>
|
<svg
|
||||||
</svg>
|
className="w-4 mr-1"
|
||||||
{intl.formatMessage(messages.requestcollection)}
|
fill="none"
|
||||||
</Button>
|
stroke="currentColor"
|
||||||
)}
|
viewBox="0 0 24 24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={2}
|
||||||
|
d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span>
|
||||||
|
{intl.formatMessage(
|
||||||
|
collectionStatus === MediaStatus.AVAILABLE
|
||||||
|
? messages.requestcollection4k
|
||||||
|
: messages.requestcollection
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{settings.currentSettings.movie4kEnabled &&
|
||||||
|
hasPermission(
|
||||||
|
[Permission.REQUEST_4K, Permission.REQUEST_4K_MOVIE],
|
||||||
|
{ type: 'or' }
|
||||||
|
) &&
|
||||||
|
collectionStatus !== MediaStatus.AVAILABLE &&
|
||||||
|
collectionStatus4k !== MediaStatus.AVAILABLE && (
|
||||||
|
<ButtonWithDropdown.Item
|
||||||
|
buttonType="primary"
|
||||||
|
onClick={() => {
|
||||||
|
setRequestModal(true);
|
||||||
|
setIs4k(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
className="w-4 mr-1"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={2}
|
||||||
|
d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span>
|
||||||
|
{intl.formatMessage(messages.requestcollection4k)}
|
||||||
|
</span>
|
||||||
|
</ButtonWithDropdown.Item>
|
||||||
|
)}
|
||||||
|
</ButtonWithDropdown>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col pt-8 pb-4 text-white md:flex-row">
|
<div className="flex flex-col pt-8 pb-4 text-white md:flex-row">
|
||||||
|
@@ -378,31 +378,31 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col flex-1 mt-4 text-center text-white lg:mr-4 lg:mt-0 lg:text-left">
|
<div className="flex flex-col flex-1 mt-4 text-center text-white lg:mr-4 lg:mt-0 lg:text-left">
|
||||||
<div className="mb-2 space-x-2">
|
<div className="mb-2 space-x-2">
|
||||||
{data.mediaInfo && data.mediaInfo.status !== MediaStatus.UNKNOWN && (
|
<span className="ml-2 lg:ml-0">
|
||||||
<span className="ml-2 lg:ml-0">
|
|
||||||
<StatusBadge
|
|
||||||
status={data.mediaInfo?.status}
|
|
||||||
inProgress={(data.mediaInfo.downloadStatus ?? []).length > 0}
|
|
||||||
plexUrl={data.mediaInfo?.plexUrl}
|
|
||||||
plexUrl4k={data.mediaInfo?.plexUrl4k}
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
<span>
|
|
||||||
<StatusBadge
|
<StatusBadge
|
||||||
status={data.mediaInfo?.status4k}
|
status={data.mediaInfo?.status}
|
||||||
is4k
|
inProgress={(data.mediaInfo?.downloadStatus ?? []).length > 0}
|
||||||
inProgress={(data.mediaInfo?.downloadStatus4k ?? []).length > 0}
|
|
||||||
plexUrl={data.mediaInfo?.plexUrl}
|
plexUrl={data.mediaInfo?.plexUrl}
|
||||||
plexUrl4k={
|
|
||||||
data.mediaInfo?.plexUrl4k &&
|
|
||||||
(hasPermission(Permission.REQUEST_4K) ||
|
|
||||||
hasPermission(Permission.REQUEST_4K_MOVIE))
|
|
||||||
? data.mediaInfo.plexUrl4k
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
|
{settings.currentSettings.movie4kEnabled &&
|
||||||
|
hasPermission(
|
||||||
|
[Permission.REQUEST_4K, Permission.REQUEST_4K_MOVIE],
|
||||||
|
{
|
||||||
|
type: 'or',
|
||||||
|
}
|
||||||
|
) && (
|
||||||
|
<span>
|
||||||
|
<StatusBadge
|
||||||
|
status={data.mediaInfo?.status4k}
|
||||||
|
is4k
|
||||||
|
inProgress={
|
||||||
|
(data.mediaInfo?.downloadStatus4k ?? []).length > 0
|
||||||
|
}
|
||||||
|
plexUrl4k={data.mediaInfo?.plexUrl4k}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<h1 className="text-2xl lg:text-4xl">
|
<h1 className="text-2xl lg:text-4xl">
|
||||||
{data.title}{' '}
|
{data.title}{' '}
|
||||||
|
@@ -190,7 +190,8 @@ const RequestItem: React.FC<RequestItemProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
</Table.TD>
|
</Table.TD>
|
||||||
<Table.TD>
|
<Table.TD>
|
||||||
{requestData.media.status === MediaStatus.UNKNOWN ||
|
{requestData.media[requestData.is4k ? 'status4k' : 'status'] ===
|
||||||
|
MediaStatus.UNKNOWN ||
|
||||||
requestData.status === MediaRequestStatus.DECLINED ? (
|
requestData.status === MediaRequestStatus.DECLINED ? (
|
||||||
<Badge badgeType="danger">
|
<Badge badgeType="danger">
|
||||||
{requestData.status === MediaRequestStatus.DECLINED
|
{requestData.status === MediaRequestStatus.DECLINED
|
||||||
@@ -247,7 +248,8 @@ const RequestItem: React.FC<RequestItemProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
</Table.TD>
|
</Table.TD>
|
||||||
<Table.TD alignText="right">
|
<Table.TD alignText="right">
|
||||||
{requestData.media.status === MediaStatus.UNKNOWN &&
|
{requestData.media[requestData.is4k ? 'status4k' : 'status'] ===
|
||||||
|
MediaStatus.UNKNOWN &&
|
||||||
requestData.status !== MediaRequestStatus.DECLINED &&
|
requestData.status !== MediaRequestStatus.DECLINED &&
|
||||||
hasPermission(Permission.MANAGE_REQUESTS) && (
|
hasPermission(Permission.MANAGE_REQUESTS) && (
|
||||||
<Button
|
<Button
|
||||||
|
@@ -10,6 +10,7 @@ import { defineMessages, useIntl } from 'react-intl';
|
|||||||
import { useIsTouch } from '../../hooks/useIsTouch';
|
import { useIsTouch } from '../../hooks/useIsTouch';
|
||||||
import globalMessages from '../../i18n/globalMessages';
|
import globalMessages from '../../i18n/globalMessages';
|
||||||
import Spinner from '../../assets/spinner.svg';
|
import Spinner from '../../assets/spinner.svg';
|
||||||
|
import { useUser, Permission } from '../../hooks/useUser';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
movie: 'Movie',
|
movie: 'Movie',
|
||||||
@@ -42,6 +43,7 @@ const TitleCard: React.FC<TitleCardProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const isTouch = useIsTouch();
|
const isTouch = useIsTouch();
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
const { hasPermission } = useUser();
|
||||||
const [isUpdating, setIsUpdating] = useState(false);
|
const [isUpdating, setIsUpdating] = useState(false);
|
||||||
const [currentStatus, setCurrentStatus] = useState(status);
|
const [currentStatus, setCurrentStatus] = useState(status);
|
||||||
const [showDetail, setShowDetail] = useState(false);
|
const [showDetail, setShowDetail] = useState(false);
|
||||||
@@ -204,7 +206,8 @@ const TitleCard: React.FC<TitleCardProps> = ({
|
|||||||
<div className="flex items-end w-full h-full">
|
<div className="flex items-end w-full h-full">
|
||||||
<div
|
<div
|
||||||
className={`px-2 text-white ${
|
className={`px-2 text-white ${
|
||||||
currentStatus && currentStatus !== MediaStatus.UNKNOWN
|
!hasPermission(Permission.REQUEST) ||
|
||||||
|
(currentStatus && currentStatus !== MediaStatus.UNKNOWN)
|
||||||
? 'pb-2'
|
? 'pb-2'
|
||||||
: 'pb-11'
|
: 'pb-11'
|
||||||
}`}
|
}`}
|
||||||
@@ -218,8 +221,9 @@ const TitleCard: React.FC<TitleCardProps> = ({
|
|||||||
className="text-xs whitespace-normal"
|
className="text-xs whitespace-normal"
|
||||||
style={{
|
style={{
|
||||||
WebkitLineClamp:
|
WebkitLineClamp:
|
||||||
currentStatus &&
|
!hasPermission(Permission.REQUEST) ||
|
||||||
currentStatus !== MediaStatus.UNKNOWN
|
(currentStatus &&
|
||||||
|
currentStatus !== MediaStatus.UNKNOWN)
|
||||||
? 5
|
? 5
|
||||||
: 3,
|
: 3,
|
||||||
display: '-webkit-box',
|
display: '-webkit-box',
|
||||||
@@ -235,33 +239,34 @@ const TitleCard: React.FC<TitleCardProps> = ({
|
|||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<div className="absolute bottom-0 left-0 right-0 flex justify-between px-2 py-2">
|
<div className="absolute bottom-0 left-0 right-0 flex justify-between px-2 py-2">
|
||||||
{(!currentStatus || currentStatus === MediaStatus.UNKNOWN) && (
|
{hasPermission(Permission.REQUEST) &&
|
||||||
<button
|
(!currentStatus || currentStatus === MediaStatus.UNKNOWN) && (
|
||||||
onClick={(e) => {
|
<button
|
||||||
e.preventDefault();
|
onClick={(e) => {
|
||||||
setShowRequestModal(true);
|
e.preventDefault();
|
||||||
}}
|
setShowRequestModal(true);
|
||||||
className="flex items-center justify-center w-full text-white transition duration-150 ease-in-out bg-indigo-600 rounded-md h-7 hover:bg-indigo-500 focus:border-indigo-700 focus:ring-indigo active:bg-indigo-700"
|
}}
|
||||||
>
|
className="flex items-center justify-center w-full text-white transition duration-150 ease-in-out bg-indigo-600 rounded-md h-7 hover:bg-indigo-500 focus:border-indigo-700 focus:ring-indigo active:bg-indigo-700"
|
||||||
<svg
|
|
||||||
className="w-4 mr-1"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
>
|
>
|
||||||
<path
|
<svg
|
||||||
strokeLinecap="round"
|
className="w-4 mr-1"
|
||||||
strokeLinejoin="round"
|
fill="none"
|
||||||
strokeWidth={2}
|
stroke="currentColor"
|
||||||
d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"
|
viewBox="0 0 24 24"
|
||||||
/>
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
</svg>
|
>
|
||||||
<span className="text-xs">
|
<path
|
||||||
{intl.formatMessage(globalMessages.request)}
|
strokeLinecap="round"
|
||||||
</span>
|
strokeLinejoin="round"
|
||||||
</button>
|
strokeWidth={2}
|
||||||
)}
|
d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span className="text-xs">
|
||||||
|
{intl.formatMessage(globalMessages.request)}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Transition>
|
</Transition>
|
||||||
|
@@ -400,31 +400,28 @@ const TvDetails: React.FC<TvDetailsProps> = ({ tv }) => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col flex-1 mt-4 text-center text-white lg:mr-4 lg:mt-0 lg:text-left">
|
<div className="flex flex-col flex-1 mt-4 text-center text-white lg:mr-4 lg:mt-0 lg:text-left">
|
||||||
<div className="mb-2 space-x-2">
|
<div className="mb-2 space-x-2">
|
||||||
{data.mediaInfo && data.mediaInfo.status !== MediaStatus.UNKNOWN && (
|
<span className="ml-2 lg:ml-0">
|
||||||
<span className="ml-2 lg:ml-0">
|
|
||||||
<StatusBadge
|
|
||||||
status={data.mediaInfo?.status}
|
|
||||||
inProgress={(data.mediaInfo.downloadStatus ?? []).length > 0}
|
|
||||||
plexUrl={data.mediaInfo?.plexUrl}
|
|
||||||
plexUrl4k={data.mediaInfo?.plexUrl4k}
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
<span>
|
|
||||||
<StatusBadge
|
<StatusBadge
|
||||||
status={data.mediaInfo?.status4k}
|
status={data.mediaInfo?.status}
|
||||||
is4k
|
|
||||||
inProgress={(data.mediaInfo?.downloadStatus ?? []).length > 0}
|
inProgress={(data.mediaInfo?.downloadStatus ?? []).length > 0}
|
||||||
plexUrl={data.mediaInfo?.plexUrl}
|
plexUrl={data.mediaInfo?.plexUrl}
|
||||||
plexUrl4k={
|
|
||||||
data.mediaInfo?.plexUrl4k &&
|
|
||||||
(hasPermission(Permission.REQUEST_4K) ||
|
|
||||||
hasPermission(Permission.REQUEST_4K_TV))
|
|
||||||
? data.mediaInfo.plexUrl4k
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
|
{settings.currentSettings.series4kEnabled &&
|
||||||
|
hasPermission([Permission.REQUEST_4K, Permission.REQUEST_4K_TV], {
|
||||||
|
type: 'or',
|
||||||
|
}) && (
|
||||||
|
<span>
|
||||||
|
<StatusBadge
|
||||||
|
status={data.mediaInfo?.status4k}
|
||||||
|
is4k
|
||||||
|
inProgress={
|
||||||
|
(data.mediaInfo?.downloadStatus ?? []).length > 0
|
||||||
|
}
|
||||||
|
plexUrl4k={data.mediaInfo?.plexUrl4k}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<h1 className="text-2xl lg:text-4xl">
|
<h1 className="text-2xl lg:text-4xl">
|
||||||
{data.name}{' '}
|
{data.name}{' '}
|
||||||
|
@@ -6,10 +6,13 @@
|
|||||||
"components.CollectionDetails.overview": "Overview",
|
"components.CollectionDetails.overview": "Overview",
|
||||||
"components.CollectionDetails.overviewunavailable": "Overview unavailable.",
|
"components.CollectionDetails.overviewunavailable": "Overview unavailable.",
|
||||||
"components.CollectionDetails.request": "Request",
|
"components.CollectionDetails.request": "Request",
|
||||||
|
"components.CollectionDetails.request4k": "Request 4K",
|
||||||
"components.CollectionDetails.requestSuccess": "<strong>{title}</strong> successfully requested!",
|
"components.CollectionDetails.requestSuccess": "<strong>{title}</strong> successfully requested!",
|
||||||
"components.CollectionDetails.requestcollection": "Request Collection",
|
"components.CollectionDetails.requestcollection": "Request Collection",
|
||||||
|
"components.CollectionDetails.requestcollection4k": "Request Collection in 4K",
|
||||||
"components.CollectionDetails.requesting": "Requesting…",
|
"components.CollectionDetails.requesting": "Requesting…",
|
||||||
"components.CollectionDetails.requestswillbecreated": "The following titles will have requests created for them:",
|
"components.CollectionDetails.requestswillbecreated": "The following titles will have requests created for them:",
|
||||||
|
"components.CollectionDetails.requestswillbecreated4k": "The following titles will have 4K requests created for them:",
|
||||||
"components.Common.ListView.noresults": "No results.",
|
"components.Common.ListView.noresults": "No results.",
|
||||||
"components.Discover.discover": "Discover",
|
"components.Discover.discover": "Discover",
|
||||||
"components.Discover.discovermovies": "Popular Movies",
|
"components.Discover.discovermovies": "Popular Movies",
|
||||||
|
Reference in New Issue
Block a user