Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/components/CollectionDetails/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -500,6 +500,7 @@ const CollectionDetails = ({ collection }: CollectionDetailsProps) => {
isAddedToWatchlist={title.mediaInfo?.watchlists?.length ?? 0}
image={title.posterPath}
status={title.mediaInfo?.status}
status4k={title.mediaInfo?.status4k}
summary={title.overview}
title={title.title}
userScore={title.voteAverage}
Expand Down
8 changes: 8 additions & 0 deletions src/components/Common/ListView/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ const ListView = ({
}
image={title.posterPath}
status={title.mediaInfo?.status}
status4k={title.mediaInfo?.status4k}
summary={title.overview}
title={title.title}
userScore={title.voteAverage}
Expand All @@ -95,6 +96,9 @@ const ListView = ({
inProgress={
(title.mediaInfo?.downloadStatus ?? []).length > 0
}
inProgress4k={
(title.mediaInfo?.downloadStatus4k ?? []).length > 0
}
canExpand
/>
);
Expand All @@ -109,6 +113,7 @@ const ListView = ({
}
image={title.posterPath}
status={title.mediaInfo?.status}
status4k={title.mediaInfo?.status4k}
summary={title.overview}
title={title.name}
userScore={title.voteAverage}
Expand All @@ -117,6 +122,9 @@ const ListView = ({
inProgress={
(title.mediaInfo?.downloadStatus ?? []).length > 0
}
inProgress4k={
(title.mediaInfo?.downloadStatus4k ?? []).length > 0
}
canExpand
/>
);
Expand Down
140 changes: 101 additions & 39 deletions src/components/Common/StatusBadgeMini/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,68 +15,130 @@ interface StatusBadgeMiniProps {
inProgress?: boolean;
// Should the badge shrink on mobile to a smaller size? (TitleCard)
shrink?: boolean;
ringStatus4k?: MediaStatus;
}

const StatusBadgeMini = ({
status,
is4k = false,
inProgress = false,
shrink = false,
}: StatusBadgeMiniProps) => {
const badgeStyle = [
`rounded-full shadow-md ${
shrink ? 'w-4 sm:w-5 border p-0' : 'w-5 ring-1 p-0.5'
}`,
];
interface StatusStyle {
badge: string;
fill: string;
ring?: string;
}

let indicatorIcon: React.ReactNode;
export const statusStyle: Record<MediaStatus, StatusStyle> = {
[MediaStatus.UNKNOWN]: {
badge: 'bg-gray-900/80 border-gray-300 ring-gray-300 text-white',
fill: 'bg-gray-600',
},
[MediaStatus.PENDING]: {
badge: 'bg-yellow-500/80 border-yellow-400 ring-yellow-400 text-yellow-100',
fill: 'bg-yellow-500',
ring: 'ring-yellow-500',
},
[MediaStatus.PROCESSING]: {
badge: 'bg-indigo-500/80 border-indigo-400 ring-indigo-400 text-indigo-100',
fill: 'bg-indigo-500',
ring: 'ring-indigo-500',
},
[MediaStatus.PARTIALLY_AVAILABLE]: {
badge: 'bg-green-500/80 border-green-400 ring-green-400 text-green-100',
fill: 'bg-[conic-gradient(theme(colors.green.500)_0_50%,theme(colors.gray.700)_0)]',
},
[MediaStatus.AVAILABLE]: {
badge: 'bg-green-500/80 border-green-400 ring-green-400 text-green-100',
fill: 'bg-green-500',
ring: 'ring-green-500',
},
[MediaStatus.BLOCKLISTED]: {
badge: 'bg-red-500/80 border-white ring-white text-white',
fill: 'bg-red-500',
ring: 'ring-red-500',
},
[MediaStatus.DELETED]: {
badge: 'bg-red-500/80 border-red-400 ring-red-400 text-red-100',
fill: 'bg-red-500',
ring: 'ring-red-500',
},
};

const statusIcon = (status: MediaStatus): React.ReactNode => {
switch (status) {
case MediaStatus.PROCESSING:
badgeStyle.push(
'bg-indigo-500/80 border-indigo-400 ring-indigo-400 text-indigo-100'
);
indicatorIcon = <ClockIcon />;
break;
return <ClockIcon />;
case MediaStatus.AVAILABLE:
badgeStyle.push(
'bg-green-500/80 border-green-400 ring-green-400 text-green-100'
);
indicatorIcon = <CheckCircleIcon />;
break;
return <CheckCircleIcon />;
case MediaStatus.PENDING:
badgeStyle.push(
'bg-yellow-500/80 border-yellow-400 ring-yellow-400 text-yellow-100'
);
indicatorIcon = <BellIcon />;
break;
return <BellIcon />;
case MediaStatus.BLOCKLISTED:
badgeStyle.push('bg-red-500/80 border-white ring-white text-white');
indicatorIcon = <EyeSlashIcon />;
break;
return <EyeSlashIcon />;
case MediaStatus.PARTIALLY_AVAILABLE:
badgeStyle.push(
'bg-green-500/80 border-green-400 ring-green-400 text-green-100'
);
indicatorIcon = <MinusSmallIcon />;
break;
return <MinusSmallIcon />;
case MediaStatus.DELETED:
badgeStyle.push('bg-red-500/80 border-red-400 ring-red-400 text-red-100');
indicatorIcon = <TrashIcon />;
break;
return <TrashIcon />;
default:
return null;
}
};

const StatusBadgeMini = ({
status,
is4k = false,
inProgress = false,
shrink = false,
ringStatus4k,
}: StatusBadgeMiniProps) => {
const style4k =
ringStatus4k && ringStatus4k !== MediaStatus.UNKNOWN
? statusStyle[ringStatus4k]
: undefined;
const size = shrink ? 'w-4 sm:w-5' : 'w-5';
const hasHdState = status !== MediaStatus.UNKNOWN;

const badgeStyle = [
`aspect-square rounded-full ${style4k ? '' : 'shadow-md'} ${size} ${
shrink ? `border ${hasHdState ? 'p-0' : 'p-0.5'}` : 'ring-1 p-0.5'
}`,
statusStyle[status].badge,
];

let indicatorIcon = statusIcon(
hasHdState ? status : (ringStatus4k as MediaStatus)
);

if (inProgress) {
indicatorIcon = <Spinner />;
}

let badge = <div className={badgeStyle.join(' ')}>{indicatorIcon}</div>;

if (style4k) {
badge = style4k.ring ? (
<div
className={`inline-flex rounded-full shadow-md ring-2 ring-offset-2 ring-offset-gray-900 ${style4k.ring}`}
>
{badge}
</div>
) : (
<div className="relative inline-flex">
<span
className={`absolute rounded-full shadow-md ${style4k.fill}`}
style={{ inset: '-4px' }}
/>
<span
className="absolute rounded-full bg-gray-900"
style={{ inset: '-2px' }}
/>
<span className="relative inline-flex">{badge}</span>
</div>
);
}

return (
<div
className={`relative inline-flex whitespace-nowrap rounded-full border-gray-700 text-xs font-semibold leading-5 ring-gray-700 ${
shrink ? '' : 'ring-1'
}`}
>
<div className={badgeStyle.join(' ')}>{indicatorIcon}</div>
{badge}
{is4k && <span className="pl-1 pr-2 text-gray-200">4K</span>}
</div>
);
Expand Down
8 changes: 8 additions & 0 deletions src/components/MediaSlider/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -127,12 +127,16 @@ const MediaSlider = ({
isAddedToWatchlist={title.mediaInfo?.watchlists?.length ?? 0}
image={title.posterPath}
status={title.mediaInfo?.status}
status4k={title.mediaInfo?.status4k}
summary={title.overview}
title={title.title}
userScore={title.voteAverage}
year={title.releaseDate}
mediaType={title.mediaType}
inProgress={(title.mediaInfo?.downloadStatus ?? []).length > 0}
inProgress4k={
(title.mediaInfo?.downloadStatus4k ?? []).length > 0
}
/>
);
case 'tv':
Expand All @@ -143,12 +147,16 @@ const MediaSlider = ({
isAddedToWatchlist={title.mediaInfo?.watchlists?.length ?? 0}
image={title.posterPath}
status={title.mediaInfo?.status}
status4k={title.mediaInfo?.status4k}
summary={title.overview}
title={title.name}
userScore={title.voteAverage}
year={title.firstAirDate}
mediaType={title.mediaType}
inProgress={(title.mediaInfo?.downloadStatus ?? []).length > 0}
inProgress4k={
(title.mediaInfo?.downloadStatus4k ?? []).length > 0
}
/>
);
case 'person':
Expand Down
2 changes: 2 additions & 0 deletions src/components/PersonDetails/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ const PersonDetails = () => {
summary={media.overview}
mediaType={media.mediaType as 'movie' | 'tv'}
status={media.mediaInfo?.status}
status4k={media.mediaInfo?.status4k}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
canExpand
/>
{media.character && (
Expand Down Expand Up @@ -224,6 +225,7 @@ const PersonDetails = () => {
summary={media.overview}
mediaType={media.mediaType as 'movie' | 'tv'}
status={media.mediaInfo?.status}
status4k={media.mediaInfo?.status4k}
canExpand
/>
{media.job && (
Expand Down
6 changes: 2 additions & 4 deletions src/components/RequestButton/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@ import { mutate } from 'swr';
const messages = defineMessages('components.RequestButton', {
viewrequest: 'View Request',
viewrequest4k: 'View 4K Request',
requestmore: 'Request More',
requestmore4k: 'Request More in 4K',
approverequest: 'Approve Request',
approverequest4k: 'Approve 4K Request',
declinerequest: 'Decline Request',
Expand Down Expand Up @@ -303,7 +301,7 @@ const RequestButton = ({
) {
buttons.push({
id: 'request-more',
text: intl.formatMessage(messages.requestmore),
text: intl.formatMessage(globalMessages.requestmore),
action: () => {
setEditRequest(false);
setShowRequestModal(true);
Expand Down Expand Up @@ -351,7 +349,7 @@ const RequestButton = ({
) {
buttons.push({
id: 'request-more-4k',
text: intl.formatMessage(messages.requestmore4k),
text: intl.formatMessage(globalMessages.requestmore4k),
action: () => {
setEditRequest(false);
setShowRequest4kModal(true);
Expand Down
67 changes: 67 additions & 0 deletions src/components/TitleCard/AvailabilityPopover.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { statusStyle } from '@app/components/Common/StatusBadgeMini';
import globalMessages from '@app/i18n/globalMessages';
import defineMessages from '@app/utils/defineMessages';
import { MediaStatus } from '@server/constants/media';
import { useIntl } from 'react-intl';

interface AvailabilityPopoverProps {
status?: MediaStatus;
status4k?: MediaStatus;
show4k?: boolean;
inProgress?: boolean;
inProgress4k?: boolean;
}

const messages = defineMessages('components.TitleCard', {
status4k: '4K {status}',
});

const statusMessage = {
[MediaStatus.AVAILABLE]: globalMessages.available,
[MediaStatus.PARTIALLY_AVAILABLE]: globalMessages.partiallyavailable,
[MediaStatus.PROCESSING]: globalMessages.requested,
[MediaStatus.PENDING]: globalMessages.pending,
[MediaStatus.BLOCKLISTED]: globalMessages.blocklisted,
[MediaStatus.DELETED]: globalMessages.deleted,
[MediaStatus.UNKNOWN]: globalMessages.notrequested,
};

const AvailabilityPopover = ({
status,
status4k,
show4k = false,
inProgress = false,
inProgress4k = false,
}: AvailabilityPopoverProps) => {
const intl = useIntl();

const row = (is4k: boolean, rowStatus: MediaStatus, downloading: boolean) => {
const label = intl.formatMessage(
downloading && rowStatus === MediaStatus.PROCESSING
? globalMessages.processing
: statusMessage[rowStatus]
);

return (
<div className="flex items-center gap-2">
<span
className={`h-2 w-2 flex-none rounded-full ${statusStyle[rowStatus].fill}`}
/>
<span className="font-semibold">
{is4k
? intl.formatMessage(messages.status4k, { status: label })
: label}
</span>
</div>
);
};

return (
<div className="flex flex-col gap-2">
{row(false, status ?? MediaStatus.UNKNOWN, inProgress)}
{show4k && row(true, status4k ?? MediaStatus.UNKNOWN, inProgress4k)}
</div>
);
};

export default AvailabilityPopover;
Loading
Loading