feat(frontend): open media management slideover on status badge click (#2407)

* feat(frontend): open media management slideover on status badge click

* fix(frontend): use Link component for in-app badge links

* fix: check for query param value of '1'

* fix: correct query param check

* fix: available badges should still link to Plex
This commit is contained in:
TheCatLady
2022-01-19 22:40:10 -05:00
committed by GitHub
parent 114366fa4b
commit 1f5785d6c5
7 changed files with 76 additions and 69 deletions

View File

@@ -1,22 +1,23 @@
import Link from 'next/link';
import React from 'react'; import React from 'react';
interface BadgeProps { interface BadgeProps {
badgeType?: 'default' | 'primary' | 'danger' | 'warning' | 'success'; badgeType?: 'default' | 'primary' | 'danger' | 'warning' | 'success';
className?: string; className?: string;
url?: string; href?: string;
} }
const Badge: React.FC<BadgeProps> = ({ const Badge: React.FC<BadgeProps> = ({
badgeType = 'default', badgeType = 'default',
className, className,
url, href,
children, children,
}) => { }) => {
const badgeStyle = [ const badgeStyle = [
'px-2 inline-flex text-xs leading-5 font-semibold rounded-full whitespace-nowrap', 'px-2 inline-flex text-xs leading-5 font-semibold rounded-full whitespace-nowrap',
]; ];
if (url) { if (href) {
badgeStyle.push('transition cursor-pointer !no-underline'); badgeStyle.push('transition cursor-pointer !no-underline');
} else { } else {
badgeStyle.push('cursor-default'); badgeStyle.push('cursor-default');
@@ -25,25 +26,25 @@ const Badge: React.FC<BadgeProps> = ({
switch (badgeType) { switch (badgeType) {
case 'danger': case 'danger':
badgeStyle.push('bg-red-600 !text-red-100'); badgeStyle.push('bg-red-600 !text-red-100');
if (url) { if (href) {
badgeStyle.push('hover:bg-red-500'); badgeStyle.push('hover:bg-red-500');
} }
break; break;
case 'warning': case 'warning':
badgeStyle.push('bg-yellow-500 !text-yellow-100'); badgeStyle.push('bg-yellow-500 !text-yellow-100');
if (url) { if (href) {
badgeStyle.push('hover:bg-yellow-400'); badgeStyle.push('hover:bg-yellow-400');
} }
break; break;
case 'success': case 'success':
badgeStyle.push('bg-green-500 !text-green-100'); badgeStyle.push('bg-green-500 !text-green-100');
if (url) { if (href) {
badgeStyle.push('hover:bg-green-400'); badgeStyle.push('hover:bg-green-400');
} }
break; break;
default: default:
badgeStyle.push('bg-indigo-500 !text-indigo-100'); badgeStyle.push('bg-indigo-500 !text-indigo-100');
if (url) { if (href) {
badgeStyle.push('hover:bg-indigo-400'); badgeStyle.push('hover:bg-indigo-400');
} }
} }
@@ -52,10 +53,10 @@ const Badge: React.FC<BadgeProps> = ({
badgeStyle.push(className); badgeStyle.push(className);
} }
if (url) { if (href?.includes('://')) {
return ( return (
<a <a
href={url} href={href}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className={badgeStyle.join(' ')} className={badgeStyle.join(' ')}
@@ -63,6 +64,12 @@ const Badge: React.FC<BadgeProps> = ({
{children} {children}
</a> </a>
); );
} else if (href) {
return (
<Link href={href}>
<a className={badgeStyle.join(' ')}>{children}</a>
</Link>
);
} else { } else {
return <span className={badgeStyle.join(' ')}>{children}</span>; return <span className={badgeStyle.join(' ')}>{children}</span>;
} }

View File

@@ -183,11 +183,11 @@ const IssueItem: React.FC<IssueItemProps> = ({ issue }) => {
{intl.formatMessage(messages.issuestatus)} {intl.formatMessage(messages.issuestatus)}
</span> </span>
{issue.status === IssueStatus.OPEN ? ( {issue.status === IssueStatus.OPEN ? (
<Badge badgeType="warning"> <Badge badgeType="warning" href={`/issues/${issue.id}`}>
{intl.formatMessage(globalMessages.open)} {intl.formatMessage(globalMessages.open)}
</Badge> </Badge>
) : ( ) : (
<Badge badgeType="success"> <Badge badgeType="success" href={`/issues/${issue.id}`}>
{intl.formatMessage(globalMessages.resolved)} {intl.formatMessage(globalMessages.resolved)}
</Badge> </Badge>
)} )}

View File

@@ -85,7 +85,9 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
const router = useRouter(); const router = useRouter();
const intl = useIntl(); const intl = useIntl();
const { locale } = useLocale(); const { locale } = useLocale();
const [showManager, setShowManager] = useState(false); const [showManager, setShowManager] = useState(
router.query.manage == '1' ? true : false
);
const minStudios = 3; const minStudios = 3;
const [showMoreStudios, setShowMoreStudios] = useState(false); const [showMoreStudios, setShowMoreStudios] = useState(false);
const [showIssueModal, setShowIssueModal] = useState(false); const [showIssueModal, setShowIssueModal] = useState(false);
@@ -276,12 +278,9 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
<StatusBadge <StatusBadge
status={data.mediaInfo?.status} status={data.mediaInfo?.status}
inProgress={(data.mediaInfo?.downloadStatus ?? []).length > 0} inProgress={(data.mediaInfo?.downloadStatus ?? []).length > 0}
tmdbId={data.mediaInfo?.tmdbId}
mediaType="movie"
plexUrl={data.mediaInfo?.plexUrl} plexUrl={data.mediaInfo?.plexUrl}
serviceUrl={
hasPermission(Permission.ADMIN)
? data.mediaInfo?.serviceUrl
: undefined
}
/> />
{settings.currentSettings.movie4kEnabled && {settings.currentSettings.movie4kEnabled &&
hasPermission( hasPermission(
@@ -296,12 +295,9 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
inProgress={ inProgress={
(data.mediaInfo?.downloadStatus4k ?? []).length > 0 (data.mediaInfo?.downloadStatus4k ?? []).length > 0
} }
tmdbId={data.mediaInfo?.tmdbId}
mediaType="movie"
plexUrl={data.mediaInfo?.plexUrl4k} plexUrl={data.mediaInfo?.plexUrl4k}
serviceUrl={
hasPermission(Permission.ADMIN)
? data.mediaInfo?.serviceUrl4k
: undefined
}
/> />
)} )}
</div> </div>

View File

@@ -271,13 +271,17 @@ const RequestCard: React.FC<RequestCardProps> = ({ request, onTitleData }) => {
<span className="hidden mr-2 font-bold sm:block"> <span className="hidden mr-2 font-bold sm:block">
{intl.formatMessage(globalMessages.status)} {intl.formatMessage(globalMessages.status)}
</span> </span>
{requestData.media[requestData.is4k ? 'status4k' : 'status'] === {requestData.status === MediaRequestStatus.DECLINED ? (
MediaStatus.UNKNOWN ||
requestData.status === MediaRequestStatus.DECLINED ? (
<Badge badgeType="danger"> <Badge badgeType="danger">
{requestData.status === MediaRequestStatus.DECLINED {intl.formatMessage(globalMessages.declined)}
? intl.formatMessage(globalMessages.declined) </Badge>
: intl.formatMessage(globalMessages.failed)} ) : requestData.media[requestData.is4k ? 'status4k' : 'status'] ===
MediaStatus.UNKNOWN ? (
<Badge
badgeType="danger"
href={`/${requestData.type}/${requestData.media.tmdbId}?manage=1`}
>
{intl.formatMessage(globalMessages.failed)}
</Badge> </Badge>
) : ( ) : (
<StatusBadge <StatusBadge
@@ -292,17 +296,10 @@ const RequestCard: React.FC<RequestCardProps> = ({ request, onTitleData }) => {
).length > 0 ).length > 0
} }
is4k={requestData.is4k} is4k={requestData.is4k}
tmdbId={requestData.media.tmdbId}
mediaType={requestData.type}
plexUrl={ plexUrl={
requestData.is4k requestData.media[requestData.is4k ? 'plexUrl4k' : 'plexUrl']
? requestData.media.plexUrl4k
: requestData.media.plexUrl
}
serviceUrl={
hasPermission(Permission.ADMIN)
? requestData.is4k
? requestData.media.serviceUrl4k
: requestData.media.serviceUrl
: undefined
} }
/> />
)} )}

View File

@@ -272,13 +272,18 @@ const RequestItem: React.FC<RequestItemProps> = ({
<span className="card-field-name"> <span className="card-field-name">
{intl.formatMessage(globalMessages.status)} {intl.formatMessage(globalMessages.status)}
</span> </span>
{requestData.media[requestData.is4k ? 'status4k' : 'status'] === {requestData.status === MediaRequestStatus.DECLINED ? (
MediaStatus.UNKNOWN ||
requestData.status === MediaRequestStatus.DECLINED ? (
<Badge badgeType="danger"> <Badge badgeType="danger">
{requestData.status === MediaRequestStatus.DECLINED {intl.formatMessage(globalMessages.declined)}
? intl.formatMessage(globalMessages.declined) </Badge>
: intl.formatMessage(globalMessages.failed)} ) : requestData.media[
requestData.is4k ? 'status4k' : 'status'
] === MediaStatus.UNKNOWN ? (
<Badge
badgeType="danger"
href={`/${requestData.type}/${requestData.media.tmdbId}?manage=1`}
>
{intl.formatMessage(globalMessages.failed)}
</Badge> </Badge>
) : ( ) : (
<StatusBadge <StatusBadge
@@ -293,17 +298,12 @@ const RequestItem: React.FC<RequestItemProps> = ({
).length > 0 ).length > 0
} }
is4k={requestData.is4k} is4k={requestData.is4k}
tmdbId={requestData.media.tmdbId}
mediaType={requestData.type}
plexUrl={ plexUrl={
requestData.is4k requestData.media[
? requestData.media.plexUrl4k requestData.is4k ? 'plexUrl4k' : 'plexUrl'
: requestData.media.plexUrl ]
}
serviceUrl={
hasPermission(Permission.ADMIN)
? requestData.is4k
? requestData.media.serviceUrl4k
: requestData.media.serviceUrl
: undefined
} }
/> />
)} )}

View File

@@ -2,6 +2,7 @@ import React from 'react';
import { defineMessages, useIntl } from 'react-intl'; import { defineMessages, useIntl } from 'react-intl';
import { MediaStatus } from '../../../server/constants/media'; import { MediaStatus } from '../../../server/constants/media';
import Spinner from '../../assets/spinner.svg'; import Spinner from '../../assets/spinner.svg';
import { Permission, useUser } from '../../hooks/useUser';
import globalMessages from '../../i18n/globalMessages'; import globalMessages from '../../i18n/globalMessages';
import Badge from '../Common/Badge'; import Badge from '../Common/Badge';
@@ -16,6 +17,8 @@ interface StatusBadgeProps {
inProgress?: boolean; inProgress?: boolean;
plexUrl?: string; plexUrl?: string;
serviceUrl?: string; serviceUrl?: string;
tmdbId?: number;
mediaType?: 'movie' | 'tv';
} }
const StatusBadge: React.FC<StatusBadgeProps> = ({ const StatusBadge: React.FC<StatusBadgeProps> = ({
@@ -24,13 +27,21 @@ const StatusBadge: React.FC<StatusBadgeProps> = ({
inProgress = false, inProgress = false,
plexUrl, plexUrl,
serviceUrl, serviceUrl,
tmdbId,
mediaType,
}) => { }) => {
const intl = useIntl(); const intl = useIntl();
const { hasPermission } = useUser();
const manageLink =
tmdbId && mediaType && hasPermission(Permission.MANAGE_REQUESTS)
? `/${mediaType}/${tmdbId}?manage=1`
: undefined;
switch (status) { switch (status) {
case MediaStatus.AVAILABLE: case MediaStatus.AVAILABLE:
return ( return (
<Badge badgeType="success" url={plexUrl}> <Badge badgeType="success" href={plexUrl}>
<div className="flex items-center"> <div className="flex items-center">
<span> <span>
{intl.formatMessage(is4k ? messages.status4k : messages.status, { {intl.formatMessage(is4k ? messages.status4k : messages.status, {
@@ -44,7 +55,7 @@ const StatusBadge: React.FC<StatusBadgeProps> = ({
case MediaStatus.PARTIALLY_AVAILABLE: case MediaStatus.PARTIALLY_AVAILABLE:
return ( return (
<Badge badgeType="success" url={plexUrl}> <Badge badgeType="success" href={plexUrl}>
<div className="flex items-center"> <div className="flex items-center">
<span> <span>
{intl.formatMessage(is4k ? messages.status4k : messages.status, { {intl.formatMessage(is4k ? messages.status4k : messages.status, {
@@ -58,7 +69,7 @@ const StatusBadge: React.FC<StatusBadgeProps> = ({
case MediaStatus.PROCESSING: case MediaStatus.PROCESSING:
return ( return (
<Badge badgeType="primary" url={serviceUrl}> <Badge badgeType="primary" href={manageLink ?? serviceUrl}>
<div className="flex items-center"> <div className="flex items-center">
<span> <span>
{intl.formatMessage(is4k ? messages.status4k : messages.status, { {intl.formatMessage(is4k ? messages.status4k : messages.status, {
@@ -74,7 +85,7 @@ const StatusBadge: React.FC<StatusBadgeProps> = ({
case MediaStatus.PENDING: case MediaStatus.PENDING:
return ( return (
<Badge badgeType="warning"> <Badge badgeType="warning" href={manageLink}>
{intl.formatMessage(is4k ? messages.status4k : messages.status, { {intl.formatMessage(is4k ? messages.status4k : messages.status, {
status: intl.formatMessage(globalMessages.pending), status: intl.formatMessage(globalMessages.pending),
})} })}

View File

@@ -80,7 +80,9 @@ const TvDetails: React.FC<TvDetailsProps> = ({ tv }) => {
const intl = useIntl(); const intl = useIntl();
const { locale } = useLocale(); const { locale } = useLocale();
const [showRequestModal, setShowRequestModal] = useState(false); const [showRequestModal, setShowRequestModal] = useState(false);
const [showManager, setShowManager] = useState(false); const [showManager, setShowManager] = useState(
router.query.manage == '1' ? true : false
);
const [showIssueModal, setShowIssueModal] = useState(false); const [showIssueModal, setShowIssueModal] = useState(false);
const { const {
@@ -278,12 +280,9 @@ const TvDetails: React.FC<TvDetailsProps> = ({ tv }) => {
<StatusBadge <StatusBadge
status={data.mediaInfo?.status} status={data.mediaInfo?.status}
inProgress={(data.mediaInfo?.downloadStatus ?? []).length > 0} inProgress={(data.mediaInfo?.downloadStatus ?? []).length > 0}
tmdbId={data.mediaInfo?.tmdbId}
mediaType="tv"
plexUrl={data.mediaInfo?.plexUrl} plexUrl={data.mediaInfo?.plexUrl}
serviceUrl={
hasPermission(Permission.ADMIN)
? data.mediaInfo?.serviceUrl
: undefined
}
/> />
{settings.currentSettings.series4kEnabled && {settings.currentSettings.series4kEnabled &&
hasPermission([Permission.REQUEST_4K, Permission.REQUEST_4K_TV], { hasPermission([Permission.REQUEST_4K, Permission.REQUEST_4K_TV], {
@@ -295,12 +294,9 @@ const TvDetails: React.FC<TvDetailsProps> = ({ tv }) => {
inProgress={ inProgress={
(data.mediaInfo?.downloadStatus4k ?? []).length > 0 (data.mediaInfo?.downloadStatus4k ?? []).length > 0
} }
tmdbId={data.mediaInfo?.tmdbId}
mediaType="tv"
plexUrl={data.mediaInfo?.plexUrl4k} plexUrl={data.mediaInfo?.plexUrl4k}
serviceUrl={
hasPermission(Permission.ADMIN)
? data.mediaInfo?.serviceUrl4k
: undefined
}
/> />
)} )}
</div> </div>