Skip to content

Commit

Permalink
feat(ui): request card progress bar (sct#3123)
Browse files Browse the repository at this point in the history
  • Loading branch information
OwsleyJr authored and Mike Kao committed Jan 3, 2024
1 parent 5d106bd commit 0858f7f
Show file tree
Hide file tree
Showing 12 changed files with 283 additions and 33 deletions.
7 changes: 6 additions & 1 deletion server/api/servarr/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,12 @@ class ServarrBase<QueueItemAppendT> extends ExternalAPI {
public getQueue = async (): Promise<(QueueItem & QueueItemAppendT)[]> => {
try {
const response = await this.axios.get<QueueResponse<QueueItemAppendT>>(
`/queue`
`/queue`,
{
params: {
includeEpisode: true,
},
}
);

return response.data.records;
Expand Down
21 changes: 20 additions & 1 deletion server/api/servarr/sonarr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,21 @@ interface SonarrSeason {
percentOfEpisodes: number;
};
}
interface EpisodeResult {
seriesId: number;
episodeFileId: number;
seasonNumber: number;
episodeNumber: number;
title: string;
airDate: string;
airDateUtc: string;
overview: string;
hasFile: boolean;
monitored: boolean;
absoluteEpisodeNumber: number;
unverifiedSceneNumbering: boolean;
id: number;
}

export interface SonarrSeries {
title: string;
Expand Down Expand Up @@ -82,7 +97,11 @@ export interface LanguageProfile {
name: string;
}

class SonarrAPI extends ServarrBase<{ seriesId: number; episodeId: number }> {
class SonarrAPI extends ServarrBase<{
seriesId: number;
episodeId: number;
episode: EpisodeResult;
}> {
constructor({ url, apiKey }: { url: string; apiKey: string }) {
super({ url, apiKey, apiName: 'Sonarr', cacheName: 'sonarr' });
}
Expand Down
8 changes: 8 additions & 0 deletions server/lib/downloadtracker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ import { getSettings } from '@server/lib/settings';
import logger from '@server/logger';
import { uniqWith } from 'lodash';

interface EpisodeNumberResult {
seasonNumber: number;
episodeNumber: number;
absoluteEpisodeNumber: number;
id: number;
}
export interface DownloadingItem {
mediaType: MediaType;
externalId: number;
Expand All @@ -14,6 +20,7 @@ export interface DownloadingItem {
timeLeft: string;
estimatedCompletionTime: Date;
title: string;
episode?: EpisodeNumberResult;
}

class DownloadTracker {
Expand Down Expand Up @@ -164,6 +171,7 @@ class DownloadTracker {
status: item.status,
timeLeft: item.timeleft,
title: item.title,
episode: item.episode,
}));

if (queueItems.length > 0) {
Expand Down
28 changes: 27 additions & 1 deletion src/components/CollectionDetails/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import type { Collection } from '@server/models/Collection';
import { uniq } from 'lodash';
import Link from 'next/link';
import { useRouter } from 'next/router';
import { useState } from 'react';
import { useMemo, useState } from 'react';
import { defineMessages, useIntl } from 'react-intl';
import useSWR from 'swr';

Expand Down Expand Up @@ -51,6 +51,28 @@ const CollectionDetails = ({ collection }: CollectionDetailsProps) => {
const { data: genres } =
useSWR<{ id: number; name: string }[]>(`/api/v1/genres/movie`);

const [downloadStatus, downloadStatus4k] = useMemo(() => {
return [
data?.parts.flatMap((item) =>
item.mediaInfo?.downloadStatus ? item.mediaInfo?.downloadStatus : []
),
data?.parts.flatMap((item) =>
item.mediaInfo?.downloadStatus4k ? item.mediaInfo?.downloadStatus4k : []
),
];
}, [data?.parts]);

const [titles, titles4k] = useMemo(() => {
return [
data?.parts
.filter((media) => (media.mediaInfo?.downloadStatus ?? []).length > 0)
.map((title) => title.title),
data?.parts
.filter((media) => (media.mediaInfo?.downloadStatus4k ?? []).length > 0)
.map((title) => title.title),
];
}, [data?.parts]);

if (!data && !error) {
return <LoadingSpinner />;
}
Expand Down Expand Up @@ -205,6 +227,8 @@ const CollectionDetails = ({ collection }: CollectionDetailsProps) => {
<div className="media-status">
<StatusBadge
status={collectionStatus}
downloadItem={downloadStatus}
title={titles}
inProgress={data.parts.some(
(part) => (part.mediaInfo?.downloadStatus ?? []).length > 0
)}
Expand All @@ -218,6 +242,8 @@ const CollectionDetails = ({ collection }: CollectionDetailsProps) => {
) && (
<StatusBadge
status={collectionStatus4k}
downloadItem={downloadStatus4k}
title={titles4k}
is4k
inProgress={data.parts.some(
(part) =>
Expand Down
41 changes: 29 additions & 12 deletions src/components/Common/Tooltip/index.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
import React from 'react';
import ReactDOM from 'react-dom';
import type { Config } from 'react-popper-tooltip';
import { usePopperTooltip } from 'react-popper-tooltip';

type TooltipProps = {
content: React.ReactNode;
children: React.ReactElement;
tooltipConfig?: Partial<Config>;
className?: string;
};

const Tooltip = ({ children, content, tooltipConfig }: TooltipProps) => {
const Tooltip = ({
children,
content,
tooltipConfig,
className,
}: TooltipProps) => {
const { getTooltipProps, setTooltipRef, setTriggerRef, visible } =
usePopperTooltip({
followCursor: true,
Expand All @@ -17,20 +24,30 @@ const Tooltip = ({ children, content, tooltipConfig }: TooltipProps) => {
...tooltipConfig,
});

const tooltipStyle = [
'z-50 text-sm absolute font-normal bg-gray-800 px-2 py-1 rounded border border-gray-600 shadow text-gray-100',
];

if (className) {
tooltipStyle.push(className);
}

return (
<>
{React.cloneElement(children, { ref: setTriggerRef })}
{visible && content && (
<div
ref={setTooltipRef}
{...getTooltipProps({
className:
'z-50 text-sm font-normal bg-gray-800 px-2 py-1 rounded border border-gray-600 shadow text-gray-100',
})}
>
{content}
</div>
)}
{visible &&
content &&
ReactDOM.createPortal(
<div
ref={setTooltipRef}
{...getTooltipProps({
className: tooltipStyle.join(' '),
})}
>
{content}
</div>,
document.body
)}
</>
);
};
Expand Down
20 changes: 18 additions & 2 deletions src/components/DownloadBlock/index.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,39 @@
import Badge from '@app/components/Common/Badge';
import { Permission, useUser } from '@app/hooks/useUser';
import type { DownloadingItem } from '@server/lib/downloadtracker';
import { defineMessages, FormattedRelativeTime, useIntl } from 'react-intl';

const messages = defineMessages({
estimatedtime: 'Estimated {time}',
formattedTitle: '{title}: Season {seasonNumber} Episode {episodeNumber}',
});

interface DownloadBlockProps {
downloadItem: DownloadingItem;
is4k?: boolean;
title?: string;
}

const DownloadBlock = ({ downloadItem, is4k = false }: DownloadBlockProps) => {
const DownloadBlock = ({
downloadItem,
is4k = false,
title,
}: DownloadBlockProps) => {
const intl = useIntl();
const { hasPermission } = useUser();

return (
<div className="p-4">
<div className="mb-2 w-56 truncate text-sm sm:w-80 md:w-full">
{downloadItem.title}
{hasPermission(Permission.ADMIN)
? downloadItem.title
: downloadItem.episode
? intl.formatMessage(messages.formattedTitle, {
title,
seasonNumber: downloadItem?.episode?.seasonNumber,
episodeNumber: downloadItem?.episode?.episodeNumber,
})
: title}
</div>
<div className="relative mb-2 h-6 min-w-0 overflow-hidden rounded-full bg-gray-700">
<div
Expand Down
4 changes: 4 additions & 0 deletions src/components/MovieDetails/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,8 @@ const MovieDetails = ({ movie }: MovieDetailsProps) => {
<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"
Expand All @@ -324,6 +326,8 @@ const MovieDetails = ({ movie }: MovieDetailsProps) => {
) && (
<StatusBadge
status={data.mediaInfo?.status4k}
downloadItem={data.mediaInfo?.downloadStatus4k}
title={data.title}
is4k
inProgress={
(data.mediaInfo?.downloadStatus4k ?? []).length > 0
Expand Down
6 changes: 6 additions & 0 deletions src/components/RequestCard/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,12 @@ const RequestCard = ({ request, onTitleData }: RequestCardProps) => {
status={
requestData.media[requestData.is4k ? 'status4k' : 'status']
}
downloadItem={
(requestData.media?.downloadStatus4k ?? []).length > 0
? requestData.media?.downloadStatus4k
: requestData.media?.downloadStatus
}
title={isMovie(title) ? title.title : title.name}
inProgress={
(
requestData.media[
Expand Down
6 changes: 6 additions & 0 deletions src/components/RequestList/RequestItem/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,12 @@ const RequestItem = ({ request, revalidateList }: RequestItemProps) => {
status={
requestData.media[requestData.is4k ? 'status4k' : 'status']
}
downloadItem={
requestData.media?.downloadStatus4k
? requestData.media?.downloadStatus4k
: requestData.media?.downloadStatus
}
title={isMovie(title) ? title.title : title.name}
inProgress={
(
requestData.media[
Expand Down
Loading

0 comments on commit 0858f7f

Please sign in to comment.