pull/3636/merge
Brandon Cohen 2 weeks ago committed by GitHub
commit a1b45a3205
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -26,6 +26,8 @@ export interface PublicSettingsResponse {
applicationUrl: string;
hideAvailable: boolean;
localLogin: boolean;
movieEnabled: boolean;
seriesEnabled: boolean;
movie4kEnabled: boolean;
series4kEnabled: boolean;
region: string;

@ -115,6 +115,8 @@ interface FullPublicSettings extends PublicSettings {
applicationUrl: string;
hideAvailable: boolean;
localLogin: boolean;
movieEnabled: boolean;
seriesEnabled: boolean;
movie4kEnabled: boolean;
series4kEnabled: boolean;
region: string;
@ -493,6 +495,12 @@ class Settings {
applicationUrl: this.data.main.applicationUrl,
hideAvailable: this.data.main.hideAvailable,
localLogin: this.data.main.localLogin,
movieEnabled: this.data.radarr.some(
(radarr) => !radarr.is4k && radarr.isDefault
),
seriesEnabled: this.data.sonarr.some(
(sonarr) => !sonarr.is4k && sonarr.isDefault
),
movie4kEnabled: this.data.radarr.some(
(radarr) => radarr.is4k && radarr.isDefault
),

@ -129,6 +129,7 @@ const CollectionDetails = ({ collection }: CollectionDetailsProps) => {
}
const hasRequestable =
settings.currentSettings.movieEnabled &&
hasPermission([Permission.REQUEST, Permission.REQUEST_MOVIE], {
type: 'or',
}) &&
@ -237,14 +238,19 @@ const CollectionDetails = ({ collection }: CollectionDetailsProps) => {
</div>
<div className="media-title">
<div className="media-status">
<StatusBadge
status={collectionStatus}
downloadItem={downloadStatus}
title={titles}
inProgress={data.parts.some(
(part) => (part.mediaInfo?.downloadStatus ?? []).length > 0
{settings.currentSettings.movieEnabled &&
hasPermission([Permission.REQUEST, Permission.REQUEST_MOVIE], {
type: 'or',
}) && (
<StatusBadge
status={collectionStatus}
downloadItem={downloadStatus}
title={titles}
inProgress={data.parts.some(
(part) => (part.mediaInfo?.downloadStatus ?? []).length > 0
)}
/>
)}
/>
{settings.currentSettings.movie4kEnabled &&
hasPermission(
[Permission.REQUEST_4K, Permission.REQUEST_4K_MOVIE],
@ -340,6 +346,7 @@ const CollectionDetails = ({ collection }: CollectionDetailsProps) => {
id={title.id}
image={title.posterPath}
status={title.mediaInfo?.status}
status4k={title.mediaInfo?.status4k}
summary={title.overview}
title={title.title}
userScore={title.voteAverage}

@ -61,6 +61,7 @@ const ListView = ({
id={title.id}
image={title.posterPath}
status={title.mediaInfo?.status}
status4k={title.mediaInfo?.status4k}
summary={title.overview}
title={title.title}
userScore={title.voteAverage}
@ -79,6 +80,7 @@ const ListView = ({
id={title.id}
image={title.posterPath}
status={title.mediaInfo?.status}
status4k={title.mediaInfo?.status4k}
summary={title.overview}
title={title.name}
userScore={title.voteAverage}

@ -72,8 +72,12 @@ const CreateIssueModal = ({
const availableSeasons = (data?.mediaInfo?.seasons ?? [])
.filter(
(season) =>
season.status === MediaStatus.AVAILABLE ||
season.status === MediaStatus.PARTIALLY_AVAILABLE ||
(settings.currentSettings.seriesEnabled &&
hasPermission([Permission.REQUEST_4K, Permission.REQUEST_4K_TV], {
type: 'or',
}) &&
(season.status === MediaStatus.AVAILABLE ||
season.status === MediaStatus.PARTIALLY_AVAILABLE)) ||
(settings.currentSettings.series4kEnabled &&
hasPermission([Permission.REQUEST_4K, Permission.REQUEST_4K_TV], {
type: 'or',

@ -468,22 +468,23 @@ const ManageSlideOver = ({
{intl.formatMessage(messages.manageModalAdvanced)}
</h3>
<div className="space-y-2">
{data?.mediaInfo.status !== MediaStatus.AVAILABLE && (
<Button
onClick={() => markAvailable()}
className="w-full"
buttonType="success"
>
<CheckCircleIcon />
<span>
{intl.formatMessage(
mediaType === 'movie'
? messages.markavailable
: messages.markallseasonsavailable
)}
</span>
</Button>
)}
{data?.mediaInfo.status !== MediaStatus.AVAILABLE &&
settings.currentSettings.seriesEnabled && (
<Button
onClick={() => markAvailable()}
className="w-full"
buttonType="success"
>
<CheckCircleIcon />
<span>
{intl.formatMessage(
mediaType === 'movie'
? messages.markavailable
: messages.markallseasonsavailable
)}
</span>
</Button>
)}
{data?.mediaInfo.status4k !== MediaStatus.AVAILABLE &&
settings.currentSettings.series4kEnabled && (
<Button

@ -98,6 +98,7 @@ const MediaSlider = ({
id={title.id}
image={title.posterPath}
status={title.mediaInfo?.status}
status4k={title.mediaInfo?.status4k}
summary={title.overview}
title={title.title}
userScore={title.voteAverage}
@ -112,6 +113,7 @@ const MediaSlider = ({
id={title.id}
image={title.posterPath}
status={title.mediaInfo?.status}
status4k={title.mediaInfo?.status4k}
summary={title.overview}
title={title.name}
userScore={title.voteAverage}

@ -154,6 +154,7 @@ const MovieDetails = ({ movie }: MovieDetailsProps) => {
const mediaLinks: PlayButtonLink[] = [];
if (
settings.currentSettings.movieEnabled &&
plexUrl &&
hasPermission([Permission.REQUEST, Permission.REQUEST_MOVIE], {
type: 'or',
@ -314,16 +315,28 @@ const MovieDetails = ({ movie }: MovieDetailsProps) => {
</div>
<div className="media-title">
<div className="media-status">
<StatusBadge
status={data.mediaInfo?.status}
downloadItem={data.mediaInfo?.downloadStatus}
title={data.title}
inProgress={(data.mediaInfo?.downloadStatus ?? []).length > 0}
tmdbId={data.mediaInfo?.tmdbId}
mediaType="movie"
plexUrl={plexUrl}
serviceUrl={data.mediaInfo?.serviceUrl}
/>
{settings.currentSettings.movieEnabled &&
hasPermission(
[
Permission.MANAGE_REQUESTS,
Permission.REQUEST,
Permission.REQUEST_MOVIE,
],
{
type: 'or',
}
) && (
<StatusBadge
status={data.mediaInfo?.status}
downloadItem={data.mediaInfo?.downloadStatus}
title={data.title}
inProgress={(data.mediaInfo?.downloadStatus ?? []).length > 0}
tmdbId={data.mediaInfo?.tmdbId}
mediaType="movie"
plexUrl={plexUrl}
serviceUrl={data.mediaInfo?.serviceUrl}
/>
)}
{settings.currentSettings.movie4kEnabled &&
hasPermission(
[
@ -379,7 +392,8 @@ const MovieDetails = ({ movie }: MovieDetailsProps) => {
tmdbId={data.id}
onUpdate={() => revalidate()}
/>
{(data.mediaInfo?.status === MediaStatus.AVAILABLE ||
{((settings.currentSettings.movieEnabled &&
data.mediaInfo?.status === MediaStatus.AVAILABLE) ||
(settings.currentSettings.movie4kEnabled &&
hasPermission(
[Permission.REQUEST_4K, Permission.REQUEST_4K_MOVIE],

@ -159,17 +159,19 @@ const NotificationTypeSelector = ({
{ type: 'or' }
) ||
// Cannot submit non-4K movie requests OR has Auto-Approve perms for non-4K movies
((!hasPermission([Permission.REQUEST, Permission.REQUEST_MOVIE], {
type: 'or',
}) ||
((!settings.currentSettings.movieEnabled ||
!hasPermission([Permission.REQUEST, Permission.REQUEST_MOVIE], {
type: 'or',
}) ||
hasPermission(
[Permission.AUTO_APPROVE, Permission.AUTO_APPROVE_MOVIE],
{ type: 'or' }
)) &&
// Cannot submit non-4K series requests OR has Auto-Approve perms for non-4K series
(!hasPermission([Permission.REQUEST, Permission.REQUEST_TV], {
type: 'or',
}) ||
(!settings.currentSettings.seriesEnabled ||
!hasPermission([Permission.REQUEST, Permission.REQUEST_TV], {
type: 'or',
}) ||
hasPermission(
[Permission.AUTO_APPROVE, Permission.AUTO_APPROVE_TV],
{ type: 'or' }

@ -145,6 +145,7 @@ const PersonDetails = () => {
summary={media.overview}
mediaType={media.mediaType as 'movie' | 'tv'}
status={media.mediaInfo?.status}
status4k={media.mediaInfo?.status4k}
canExpand
/>
{media.character && (
@ -185,6 +186,7 @@ const PersonDetails = () => {
summary={media.overview}
mediaType={media.mediaType as 'movie' | 'tv'}
status={media.mediaInfo?.status}
status4k={media.mediaInfo?.status}
canExpand
/>
{media.job && (

@ -273,7 +273,9 @@ const RequestButton = ({
: Permission.REQUEST_TV,
],
{ type: 'or' }
)
) &&
((settings.currentSettings.movieEnabled && mediaType === 'movie') ||
(settings.currentSettings.seriesEnabled && mediaType === 'tv'))
) {
buttons.push({
id: 'request',
@ -292,7 +294,8 @@ const RequestButton = ({
}) &&
media &&
media.status !== MediaStatus.AVAILABLE &&
!isShowComplete
!isShowComplete &&
settings.currentSettings.seriesEnabled
) {
buttons.push({
id: 'request-more',

@ -73,7 +73,10 @@ const StatusBadge = ({
type: 'or',
}
) &&
(!is4k ||
((!is4k &&
(mediaType === 'movie'
? settings.currentSettings.movieEnabled
: settings.currentSettings.seriesEnabled)) ||
(mediaType === 'movie'
? settings.currentSettings.movie4kEnabled
: settings.currentSettings.series4kEnabled))

@ -59,6 +59,7 @@ const TmdbTitleCard = ({
id={title.id}
image={title.posterPath}
status={title.mediaInfo?.status}
status4k={title.mediaInfo?.status4k}
summary={title.overview}
title={title.title}
userScore={title.voteAverage}
@ -71,6 +72,7 @@ const TmdbTitleCard = ({
id={title.id}
image={title.posterPath}
status={title.mediaInfo?.status}
status4k={title.mediaInfo?.status4k}
summary={title.overview}
title={title.name}
userScore={title.voteAverage}

@ -6,6 +6,7 @@ import RequestModal from '@app/components/RequestModal';
import ErrorCard from '@app/components/TitleCard/ErrorCard';
import Placeholder from '@app/components/TitleCard/Placeholder';
import { useIsTouch } from '@app/hooks/useIsTouch';
import useSettings from '@app/hooks/useSettings';
import { Permission, useUser } from '@app/hooks/useUser';
import globalMessages from '@app/i18n/globalMessages';
import { withProperties } from '@app/utils/typeHelpers';
@ -26,6 +27,7 @@ interface TitleCardProps {
userScore?: number;
mediaType: MediaType;
status?: MediaStatus;
status4k?: MediaStatus;
canExpand?: boolean;
inProgress?: boolean;
}
@ -37,6 +39,7 @@ const TitleCard = ({
year,
title,
status,
status4k,
mediaType,
inProgress = false,
canExpand = false,
@ -46,8 +49,11 @@ const TitleCard = ({
const { hasPermission } = useUser();
const [isUpdating, setIsUpdating] = useState(false);
const [currentStatus, setCurrentStatus] = useState(status);
const [currentStatus4k, setCurrentStatus4k] = useState(status4k);
const [showDetail, setShowDetail] = useState(false);
const [showRequestModal, setShowRequestModal] = useState(false);
const [showRequestModal4k, setShowRequestModal4k] = useState(false);
const settings = useSettings();
// Just to get the year from the date
if (year) {
@ -56,7 +62,8 @@ const TitleCard = ({
useEffect(() => {
setCurrentStatus(status);
}, [status]);
setCurrentStatus4k(status4k);
}, [status, status4k]);
const requestComplete = useCallback((newStatus: MediaStatus) => {
setCurrentStatus(newStatus);
@ -70,15 +77,44 @@ const TitleCard = ({
const closeModal = useCallback(() => setShowRequestModal(false), []);
const showRequestButton = hasPermission(
[
Permission.REQUEST,
mediaType === 'movie' || mediaType === 'collection'
? Permission.REQUEST_MOVIE
: Permission.REQUEST_TV,
],
{ type: 'or' }
);
const requestComplete4k = useCallback((newStatus: MediaStatus) => {
setCurrentStatus4k(newStatus);
setShowRequestModal4k(false);
}, []);
const closeModal4k = useCallback(() => setShowRequestModal4k(false), []);
const showRequestButton =
hasPermission(
[
Permission.REQUEST,
mediaType === 'movie' || mediaType === 'collection'
? Permission.REQUEST_MOVIE
: Permission.REQUEST_TV,
],
{ type: 'or' }
) &&
((settings.currentSettings.movieEnabled && mediaType === 'movie') ||
(settings.currentSettings.seriesEnabled && mediaType === 'tv'));
const showRequestButton4k =
hasPermission(
[
Permission.REQUEST_4K,
mediaType === 'movie' || mediaType === 'collection'
? Permission.REQUEST_4K_MOVIE
: Permission.REQUEST_4K_TV,
],
{ type: 'or' }
) &&
((settings.currentSettings.movie4kEnabled && mediaType === 'movie') ||
(settings.currentSettings.series4kEnabled && mediaType === 'tv'));
const isRequestable =
(showRequestButton &&
(!currentStatus || currentStatus === MediaStatus.UNKNOWN)) ||
(showRequestButton4k &&
(!currentStatus4k || currentStatus4k === MediaStatus.UNKNOWN));
return (
<div
@ -99,6 +135,21 @@ const TitleCard = ({
onUpdating={requestUpdating}
onCancel={closeModal}
/>
<RequestModal
tmdbId={id}
show={showRequestModal4k}
type={
mediaType === 'movie'
? 'movie'
: mediaType === 'collection'
? 'collection'
: 'tv'
}
onComplete={requestComplete4k}
onUpdating={requestUpdating}
onCancel={closeModal4k}
is4k
/>
<div
className={`relative transform-gpu cursor-default overflow-hidden rounded-xl bg-gray-800 bg-cover outline-none ring-1 transition duration-300 ${
showDetail
@ -151,15 +202,24 @@ const TitleCard = ({
: intl.formatMessage(globalMessages.tvshow)}
</div>
</div>
{currentStatus && currentStatus !== MediaStatus.UNKNOWN && (
<div className="pointer-events-none z-40 flex items-center">
<div className="pointer-events-none z-40 flex items-center">
{currentStatus && currentStatus !== MediaStatus.UNKNOWN ? (
<StatusBadgeMini
status={currentStatus}
inProgress={inProgress}
shrink
/>
</div>
)}
) : (
currentStatus4k &&
currentStatus4k !== MediaStatus.UNKNOWN && (
<StatusBadgeMini
status={currentStatus4k}
inProgress={inProgress}
shrink
/>
)
)}
</div>
</div>
<Transition
as={Fragment}
@ -178,7 +238,9 @@ const TitleCard = ({
<Transition
as={Fragment}
show={!image || showDetail || showRequestModal}
show={
!image || showDetail || showRequestModal || showRequestModal4k
}
enter="transition-opacity"
enterFrom="opacity-0"
enterTo="opacity-100"
@ -206,10 +268,7 @@ const TitleCard = ({
<div className="flex h-full w-full items-end">
<div
className={`px-2 text-white ${
!showRequestButton ||
(currentStatus && currentStatus !== MediaStatus.UNKNOWN)
? 'pb-2'
: 'pb-11'
!isRequestable ? 'pb-2' : 'pb-11'
}`}
>
{year && (
@ -232,12 +291,7 @@ const TitleCard = ({
<div
className="whitespace-normal text-xs"
style={{
WebkitLineClamp:
!showRequestButton ||
(currentStatus &&
currentStatus !== MediaStatus.UNKNOWN)
? 5
: 3,
WebkitLineClamp: !isRequestable ? 5 : 3,
display: '-webkit-box',
overflow: 'hidden',
WebkitBoxOrient: 'vertical',
@ -253,20 +307,39 @@ const TitleCard = ({
<div className="absolute bottom-0 left-0 right-0 flex justify-between px-2 py-2">
{showRequestButton &&
(!currentStatus || currentStatus === MediaStatus.UNKNOWN) && (
(!currentStatus || currentStatus === MediaStatus.UNKNOWN) ? (
<Button
buttonType="primary"
buttonSize="sm"
onClick={(e) => {
e.preventDefault();
setShowRequestModal(true);
}}
className="h-7 w-full"
>
<ArrowDownTrayIcon />
<span>{intl.formatMessage(globalMessages.request)}</span>
</Button>
) : (
showRequestButton4k &&
(!currentStatus4k ||
currentStatus4k === MediaStatus.UNKNOWN) && (
<Button
buttonType="primary"
buttonSize="sm"
onClick={(e) => {
e.preventDefault();
setShowRequestModal(true);
setShowRequestModal4k(true);
}}
className="h-7 w-full"
>
<ArrowDownTrayIcon />
<span>{intl.formatMessage(globalMessages.request)}</span>
<span>
{intl.formatMessage(globalMessages.request4k)}
</span>
</Button>
)}
)
)}
</div>
</div>
</Transition>

@ -149,7 +149,13 @@ const TvDetails = ({ tv }: TvDetailsProps) => {
const mediaLinks: PlayButtonLink[] = [];
if (plexUrl) {
if (
settings.currentSettings.seriesEnabled &&
plexUrl &&
hasPermission([Permission.REQUEST, Permission.REQUEST_TV], {
type: 'or',
})
) {
mediaLinks.push({
text: intl.formatMessage(messages.playonplex),
url: plexUrl,
@ -337,16 +343,28 @@ const TvDetails = ({ tv }: TvDetailsProps) => {
</div>
<div className="media-title">
<div className="media-status">
<StatusBadge
status={data.mediaInfo?.status}
downloadItem={data.mediaInfo?.downloadStatus}
title={data.name}
inProgress={(data.mediaInfo?.downloadStatus ?? []).length > 0}
tmdbId={data.mediaInfo?.tmdbId}
mediaType="tv"
plexUrl={plexUrl}
serviceUrl={data.mediaInfo?.serviceUrl}
/>
{settings.currentSettings.seriesEnabled &&
hasPermission(
[
Permission.MANAGE_REQUESTS,
Permission.REQUEST,
Permission.REQUEST_TV,
],
{
type: 'or',
}
) && (
<StatusBadge
status={data.mediaInfo?.status}
downloadItem={data.mediaInfo?.downloadStatus}
title={data.name}
inProgress={(data.mediaInfo?.downloadStatus ?? []).length > 0}
tmdbId={data.mediaInfo?.tmdbId}
mediaType="tv"
plexUrl={plexUrl}
serviceUrl={data.mediaInfo?.serviceUrl}
/>
)}
{settings.currentSettings.series4kEnabled &&
hasPermission(
[
@ -404,8 +422,12 @@ const TvDetails = ({ tv }: TvDetailsProps) => {
isShowComplete={isComplete}
is4kShowComplete={is4kComplete}
/>
{(data.mediaInfo?.status === MediaStatus.AVAILABLE ||
data.mediaInfo?.status === MediaStatus.PARTIALLY_AVAILABLE ||
{((settings.currentSettings.seriesEnabled &&
hasPermission([Permission.REQUEST, Permission.REQUEST_TV], {
type: 'or',
}) &&
(data.mediaInfo?.status === MediaStatus.AVAILABLE ||
data?.mediaInfo?.status === MediaStatus.PARTIALLY_AVAILABLE)) ||
(settings.currentSettings.series4kEnabled &&
hasPermission([Permission.REQUEST_4K, Permission.REQUEST_4K_TV], {
type: 'or',
@ -524,6 +546,18 @@ const TvDetails = ({ tv }: TvDetailsProps) => {
.reverse()
.filter((season) => season.seasonNumber !== 0)
.map((season) => {
const showNon4k =
settings.currentSettings.seriesEnabled &&
hasPermission(
[
Permission.MANAGE_REQUESTS,
Permission.REQUEST,
Permission.REQUEST_TV,
],
{
type: 'or',
}
);
const show4k =
settings.currentSettings.series4kEnabled &&
hasPermission(
@ -588,65 +622,75 @@ const TvDetails = ({ tv }: TvDetailsProps) => {
</div>
{((!mSeason &&
request?.status === MediaRequestStatus.APPROVED) ||
mSeason?.status === MediaStatus.PROCESSING) && (
<>
<div className="hidden md:flex">
<Badge badgeType="primary">
{intl.formatMessage(globalMessages.requested)}
</Badge>
</div>
<div className="flex md:hidden">
<StatusBadgeMini
status={MediaStatus.PROCESSING}
/>
</div>
</>
)}
mSeason?.status === MediaStatus.PROCESSING) &&
showNon4k && (
<>
<div className="hidden md:flex">
<Badge badgeType="primary">
{intl.formatMessage(
globalMessages.requested
)}
</Badge>
</div>
<div className="flex md:hidden">
<StatusBadgeMini
status={MediaStatus.PROCESSING}
/>
</div>
</>
)}
{((!mSeason &&
request?.status === MediaRequestStatus.PENDING) ||
mSeason?.status === MediaStatus.PENDING) && (
<>
<div className="hidden md:flex">
<Badge badgeType="warning">
{intl.formatMessage(globalMessages.pending)}
</Badge>
</div>
<div className="flex md:hidden">
<StatusBadgeMini status={MediaStatus.PENDING} />
</div>
</>
)}
mSeason?.status === MediaStatus.PENDING) &&
showNon4k && (
<>
<div className="hidden md:flex">
<Badge badgeType="warning">
{intl.formatMessage(globalMessages.pending)}
</Badge>
</div>
<div className="flex md:hidden">
<StatusBadgeMini
status={MediaStatus.PENDING}
/>
</div>
</>
)}
{mSeason?.status ===
MediaStatus.PARTIALLY_AVAILABLE && (
<>
<div className="hidden md:flex">
<Badge badgeType="success">
{intl.formatMessage(
globalMessages.partiallyavailable
)}
</Badge>
</div>
<div className="flex md:hidden">
<StatusBadgeMini
status={MediaStatus.PARTIALLY_AVAILABLE}
/>
</div>
</>
)}
{mSeason?.status === MediaStatus.AVAILABLE && (
<>
<div className="hidden md:flex">
<Badge badgeType="success">
{intl.formatMessage(globalMessages.available)}
</Badge>
</div>
<div className="flex md:hidden">
<StatusBadgeMini
status={MediaStatus.AVAILABLE}
/>
</div>
</>
)}
MediaStatus.PARTIALLY_AVAILABLE &&
showNon4k && (
<>
<div className="hidden md:flex">
<Badge badgeType="success">
{intl.formatMessage(
globalMessages.partiallyavailable
)}
</Badge>
</div>
<div className="flex md:hidden">
<StatusBadgeMini
status={MediaStatus.PARTIALLY_AVAILABLE}
/>
</div>
</>
)}
{mSeason?.status === MediaStatus.AVAILABLE &&
showNon4k && (
<>
<div className="hidden md:flex">
<Badge badgeType="success">
{intl.formatMessage(
globalMessages.available
)}
</Badge>
</div>
<div className="flex md:hidden">
<StatusBadgeMini
status={MediaStatus.AVAILABLE}
/>
</div>
</>
)}
{((!mSeason4k &&
request4k?.status ===
MediaRequestStatus.APPROVED) ||

@ -13,6 +13,8 @@ const defaultSettings = {
applicationUrl: '',
hideAvailable: false,
localLogin: true,
movieEnabled: false,
seriesEnabled: false,
movie4kEnabled: false,
series4kEnabled: false,
region: '',

@ -186,6 +186,8 @@ CoreApp.getInitialProps = async (initialProps) => {
applicationTitle: '',
applicationUrl: '',
hideAvailable: false,
movieEnabled: false,
seriesEnabled: false,
movie4kEnabled: false,
series4kEnabled: false,
localLogin: true,

Loading…
Cancel
Save