From 139341b0434b41e7c31af36baacd8d65566a6a0c Mon Sep 17 00:00:00 2001 From: TheCatLady <52870424+TheCatLady@users.noreply.github.com> Date: Sat, 20 Feb 2021 08:59:44 -0500 Subject: [PATCH] feat(ui): Add support for requesting collections in 4K (#968) --- src/components/CollectionDetails/index.tsx | 224 ++++++++++++++---- src/components/MovieDetails/index.tsx | 42 ++-- .../RequestList/RequestItem/index.tsx | 6 +- src/components/TitleCard/index.tsx | 63 ++--- src/components/TvDetails/index.tsx | 37 ++- src/i18n/locale/en.json | 3 + 6 files changed, 251 insertions(+), 124 deletions(-) diff --git a/src/components/CollectionDetails/index.tsx b/src/components/CollectionDetails/index.tsx index c958b9e0..5148a309 100644 --- a/src/components/CollectionDetails/index.tsx +++ b/src/components/CollectionDetails/index.tsx @@ -8,16 +8,17 @@ import { MediaStatus } from '../../../server/constants/media'; import type { MediaRequest } from '../../../server/entity/MediaRequest'; import type { Collection } from '../../../server/models/Collection'; import { LanguageContext } from '../../context/LanguageContext'; -import globalMessages from '../../i18n/globalMessages'; import Error from '../../pages/_error'; -import Badge from '../Common/Badge'; -import Button from '../Common/Button'; +import StatusBadge from '../StatusBadge'; +import ButtonWithDropdown from '../Common/ButtonWithDropdown'; import LoadingSpinner from '../Common/LoadingSpinner'; import Modal from '../Common/Modal'; import Slider from '../Slider'; import TitleCard from '../TitleCard'; import Transition from '../Transition'; import PageTitle from '../Common/PageTitle'; +import { useUser, Permission } from '../../hooks/useUser'; +import useSettings from '../../hooks/useSettings'; const messages = defineMessages({ overviewunavailable: 'Overview unavailable.', @@ -29,6 +30,10 @@ const messages = defineMessages({ requestcollection: 'Request Collection', requestswillbecreated: '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: '{title} successfully requested!', }); @@ -41,10 +46,14 @@ const CollectionDetails: React.FC = ({ }) => { const intl = useIntl(); const router = useRouter(); + const settings = useSettings(); const { addToast } = useToasts(); const { locale } = useContext(LanguageContext); + const { hasPermission } = useUser(); const [requestModal, setRequestModal] = useState(false); const [isRequesting, setRequesting] = useState(false); + const [is4k, setIs4k] = useState(false); + const { data, error, revalidate } = useSWR( `/api/v1/collection/${router.query.collectionId}?language=${locale}`, { @@ -61,8 +70,45 @@ const CollectionDetails: React.FC = ({ return ; } + 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( - (part) => !part.mediaInfo || part.mediaInfo.status === MediaStatus.UNKNOWN + (part) => + !part.mediaInfo || + part.mediaInfo[is4k ? 'status4k' : 'status'] === MediaStatus.UNKNOWN ); const requestBundle = async () => { @@ -73,6 +119,7 @@ const CollectionDetails: React.FC = ({ await axios.post('/api/v1/request', { mediaId: part.id, mediaType: 'movie', + is4k, }); }) ); @@ -123,12 +170,14 @@ const CollectionDetails: React.FC = ({ okText={ isRequesting ? intl.formatMessage(messages.requesting) - : intl.formatMessage(messages.request) + : intl.formatMessage(is4k ? messages.request4k : messages.request) } okDisabled={isRequesting} okButtonType="primary" onCancel={() => setRequestModal(false)} - title={intl.formatMessage(messages.requestcollection)} + title={intl.formatMessage( + is4k ? messages.requestcollection4k : messages.requestcollection + )} iconSvg={ = ({ } > -

{intl.formatMessage(messages.requestswillbecreated)}

+

+ {intl.formatMessage( + is4k + ? messages.requestswillbecreated4k + : messages.requestswillbecreated + )} +

    {data.parts .filter( (part) => !part.mediaInfo || - part.mediaInfo?.status === MediaStatus.UNKNOWN + part.mediaInfo[is4k ? 'status4k' : 'status'] === + MediaStatus.UNKNOWN ) .map((part) => (
  • {part.title}
  • @@ -160,64 +216,128 @@ const CollectionDetails: React.FC = ({
-
-
+
+
-
-
- {data.parts.every( - (part) => part.mediaInfo?.status === MediaStatus.AVAILABLE - ) && ( - - {intl.formatMessage(globalMessages.available)} - - )} - {!data.parts.every( - (part) => part.mediaInfo?.status === MediaStatus.AVAILABLE - ) && - data.parts.some( - (part) => part.mediaInfo?.status === MediaStatus.AVAILABLE +
+
+ + (part.mediaInfo?.downloadStatus ?? []).length > 0 + )} + /> + + {settings.currentSettings.movie4kEnabled && + hasPermission( + [Permission.REQUEST_4K, Permission.REQUEST_4K_MOVIE], + { + type: 'or', + } ) && ( - - {intl.formatMessage(globalMessages.partiallyavailable)} - + + + (part.mediaInfo?.downloadStatus4k ?? []).length > 0 + )} + /> + )}

{data.name}

- + {intl.formatMessage(messages.numberofmovies, { count: data.parts.length, })}
-
- {data.parts.some( - (part) => - !part.mediaInfo || part.mediaInfo?.status === MediaStatus.UNKNOWN - ) && ( - - )} +
+ {hasPermission(Permission.REQUEST) && + (collectionStatus !== MediaStatus.AVAILABLE || + (settings.currentSettings.movie4kEnabled && + hasPermission( + [Permission.REQUEST_4K, Permission.REQUEST_4K_MOVIE], + { type: 'or' } + ) && + collectionStatus4k !== MediaStatus.AVAILABLE)) && ( +
+ { + setRequestModal(true); + setIs4k(collectionStatus === MediaStatus.AVAILABLE); + }} + text={ + <> + + + + + {intl.formatMessage( + collectionStatus === MediaStatus.AVAILABLE + ? messages.requestcollection4k + : messages.requestcollection + )} + + + } + > + {settings.currentSettings.movie4kEnabled && + hasPermission( + [Permission.REQUEST_4K, Permission.REQUEST_4K_MOVIE], + { type: 'or' } + ) && + collectionStatus !== MediaStatus.AVAILABLE && + collectionStatus4k !== MediaStatus.AVAILABLE && ( + { + setRequestModal(true); + setIs4k(true); + }} + > + + + + + {intl.formatMessage(messages.requestcollection4k)} + + + )} + +
+ )}
diff --git a/src/components/MovieDetails/index.tsx b/src/components/MovieDetails/index.tsx index 0f35c6c4..58b74801 100644 --- a/src/components/MovieDetails/index.tsx +++ b/src/components/MovieDetails/index.tsx @@ -378,31 +378,31 @@ const MovieDetails: React.FC = ({ movie }) => {
- {data.mediaInfo && data.mediaInfo.status !== MediaStatus.UNKNOWN && ( - - 0} - plexUrl={data.mediaInfo?.plexUrl} - plexUrl4k={data.mediaInfo?.plexUrl4k} - /> - - )} - + 0} + status={data.mediaInfo?.status} + inProgress={(data.mediaInfo?.downloadStatus ?? []).length > 0} plexUrl={data.mediaInfo?.plexUrl} - plexUrl4k={ - data.mediaInfo?.plexUrl4k && - (hasPermission(Permission.REQUEST_4K) || - hasPermission(Permission.REQUEST_4K_MOVIE)) - ? data.mediaInfo.plexUrl4k - : undefined - } /> + {settings.currentSettings.movie4kEnabled && + hasPermission( + [Permission.REQUEST_4K, Permission.REQUEST_4K_MOVIE], + { + type: 'or', + } + ) && ( + + 0 + } + plexUrl4k={data.mediaInfo?.plexUrl4k} + /> + + )}

{data.title}{' '} diff --git a/src/components/RequestList/RequestItem/index.tsx b/src/components/RequestList/RequestItem/index.tsx index 28cdf2b6..59cdea66 100644 --- a/src/components/RequestList/RequestItem/index.tsx +++ b/src/components/RequestList/RequestItem/index.tsx @@ -190,7 +190,8 @@ const RequestItem: React.FC = ({

- {requestData.media.status === MediaStatus.UNKNOWN || + {requestData.media[requestData.is4k ? 'status4k' : 'status'] === + MediaStatus.UNKNOWN || requestData.status === MediaRequestStatus.DECLINED ? ( {requestData.status === MediaRequestStatus.DECLINED @@ -247,7 +248,8 @@ const RequestItem: React.FC = ({
- {requestData.media.status === MediaStatus.UNKNOWN && + {requestData.media[requestData.is4k ? 'status4k' : 'status'] === + MediaStatus.UNKNOWN && requestData.status !== MediaRequestStatus.DECLINED && hasPermission(Permission.MANAGE_REQUESTS) && ( - )} + + + + + {intl.formatMessage(globalMessages.request)} + + + )}
diff --git a/src/components/TvDetails/index.tsx b/src/components/TvDetails/index.tsx index 658a6863..dfbbc8b0 100644 --- a/src/components/TvDetails/index.tsx +++ b/src/components/TvDetails/index.tsx @@ -400,31 +400,28 @@ const TvDetails: React.FC = ({ tv }) => {
- {data.mediaInfo && data.mediaInfo.status !== MediaStatus.UNKNOWN && ( - - 0} - plexUrl={data.mediaInfo?.plexUrl} - plexUrl4k={data.mediaInfo?.plexUrl4k} - /> - - )} - + 0} plexUrl={data.mediaInfo?.plexUrl} - plexUrl4k={ - data.mediaInfo?.plexUrl4k && - (hasPermission(Permission.REQUEST_4K) || - hasPermission(Permission.REQUEST_4K_TV)) - ? data.mediaInfo.plexUrl4k - : undefined - } /> + {settings.currentSettings.series4kEnabled && + hasPermission([Permission.REQUEST_4K, Permission.REQUEST_4K_TV], { + type: 'or', + }) && ( + + 0 + } + plexUrl4k={data.mediaInfo?.plexUrl4k} + /> + + )}

{data.name}{' '} diff --git a/src/i18n/locale/en.json b/src/i18n/locale/en.json index 257365da..1dffab24 100644 --- a/src/i18n/locale/en.json +++ b/src/i18n/locale/en.json @@ -6,10 +6,13 @@ "components.CollectionDetails.overview": "Overview", "components.CollectionDetails.overviewunavailable": "Overview unavailable.", "components.CollectionDetails.request": "Request", + "components.CollectionDetails.request4k": "Request 4K", "components.CollectionDetails.requestSuccess": "{title} successfully requested!", "components.CollectionDetails.requestcollection": "Request Collection", + "components.CollectionDetails.requestcollection4k": "Request Collection in 4K", "components.CollectionDetails.requesting": "Requesting…", "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.Discover.discover": "Discover", "components.Discover.discovermovies": "Popular Movies",