mirror of
https://github.com/ajnart/homarr.git
synced 2025-11-10 15:35:55 +01:00
⚡ Improve MediaDisplay overseerr
This commit is contained in:
@@ -1,26 +1,18 @@
|
|||||||
import {
|
import { Badge, Button, Group, Image, Stack, Text, Title } from '@mantine/core';
|
||||||
Image,
|
import { IconDownload, IconExternalLink, IconPlayerPlay } from '@tabler/icons';
|
||||||
Group,
|
import { useState } from 'react';
|
||||||
Title,
|
|
||||||
Badge,
|
|
||||||
Text,
|
|
||||||
ActionIcon,
|
|
||||||
Anchor,
|
|
||||||
ScrollArea,
|
|
||||||
createStyles,
|
|
||||||
Stack,
|
|
||||||
Button,
|
|
||||||
} from '@mantine/core';
|
|
||||||
import { useMediaQuery } from '@mantine/hooks';
|
|
||||||
import { IconLink } from '@tabler/icons';
|
|
||||||
import { useConfig } from '../../tools/state';
|
import { useConfig } from '../../tools/state';
|
||||||
import { serviceItem } from '../../tools/types';
|
import { serviceItem } from '../../tools/types';
|
||||||
|
import { RequestModal } from '../overseerr/RequestModal';
|
||||||
|
import { Result } from '../overseerr/SearchResult';
|
||||||
|
|
||||||
export interface IMedia {
|
export interface IMedia {
|
||||||
overview: string;
|
overview: string;
|
||||||
imdbId?: any;
|
imdbId?: any;
|
||||||
artist?: string;
|
artist?: string;
|
||||||
title: string;
|
title?: string;
|
||||||
|
type: 'movie' | 'tvshow' | 'book' | 'music' | 'overseer';
|
||||||
|
episodetitle?: string;
|
||||||
voteAverage?: string;
|
voteAverage?: string;
|
||||||
poster?: string;
|
poster?: string;
|
||||||
genres: string[];
|
genres: string[];
|
||||||
@@ -30,83 +22,24 @@ export interface IMedia {
|
|||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
const useStyles = createStyles((theme) => ({
|
export function OverseerrMediaDisplay(props: any) {
|
||||||
overview: {
|
const { media }: { media: Result } = props;
|
||||||
[theme.fn.largerThan('sm')]: {
|
|
||||||
width: 400,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
export function MediaDisplay(props: { media: IMedia }) {
|
|
||||||
const { media }: { media: IMedia } = props;
|
|
||||||
const { classes, cx } = useStyles();
|
|
||||||
const phone = useMediaQuery('(min-width: 800px)');
|
|
||||||
return (
|
return (
|
||||||
<Group {...props} position="apart">
|
<MediaDisplay
|
||||||
<Text>
|
media={{
|
||||||
{media.poster && (
|
...media,
|
||||||
<Image
|
genres: [],
|
||||||
width={phone ? 250 : 100}
|
overview: media.overview ?? '',
|
||||||
height={phone ? 400 : 160}
|
title: media.title ?? media.name ?? media.originalName ?? undefined,
|
||||||
style={{
|
poster: `https://image.tmdb.org/t/p/w600_and_h900_bestv2/${media.posterPath}`,
|
||||||
float: 'right',
|
seasonNumber: media.mediaInfo?.seasons.length ?? undefined,
|
||||||
}}
|
episodetitle: media.title ?? undefined,
|
||||||
radius="md"
|
plexUrl: media.mediaInfo?.plexUrl ?? undefined,
|
||||||
fit="cover"
|
voteAverage: media.voteAverage?.toString() ?? undefined,
|
||||||
src={media.poster}
|
overseerrResult: media,
|
||||||
alt={media.title}
|
type: 'overseer',
|
||||||
/>
|
}}
|
||||||
)}
|
/>
|
||||||
<Stack style={{ minWidth: phone ? 450 : '65vw' }}>
|
|
||||||
<Group noWrap mr="sm" className={classes.overview}>
|
|
||||||
<Title order={3}>{media.title}</Title>
|
|
||||||
{media.artist && <Text color="gray">New release from {media.artist}</Text>}
|
|
||||||
{(media.episodeNumber || media.seasonNumber) && (
|
|
||||||
<Text color="gray">
|
|
||||||
Season {media.seasonNumber}{' '}
|
|
||||||
{media.episodeNumber && `episode ${media.episodeNumber}`}
|
|
||||||
</Text>
|
|
||||||
)}
|
|
||||||
</Group>
|
|
||||||
{media.voteAverage && (
|
|
||||||
<Button
|
|
||||||
radius="md"
|
|
||||||
variant="light"
|
|
||||||
color={media.plexUrl ? 'green' : 'cyan'}
|
|
||||||
size="md"
|
|
||||||
onClick={
|
|
||||||
media.plexUrl
|
|
||||||
? () => window.open(media.plexUrl)
|
|
||||||
: () => {
|
|
||||||
// TODO: implement overseerr media requests
|
|
||||||
console.log(media);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{media.plexUrl ? 'Available on Plex' : 'Request'}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
{media.imdbId && (
|
|
||||||
<Anchor href={`https://www.imdb.com/title/${media.imdbId}`} target="_blank">
|
|
||||||
<ActionIcon>
|
|
||||||
<IconLink />
|
|
||||||
</ActionIcon>
|
|
||||||
</Anchor>
|
|
||||||
)}
|
|
||||||
</Stack>
|
|
||||||
<Stack>
|
|
||||||
<ScrollArea style={{ height: 280, maxWidth: 700 }}>{media.overview}</ScrollArea>
|
|
||||||
<Group align="center" position="center" spacing="xs">
|
|
||||||
{media.genres.slice(-5).map((genre: string, i: number) => (
|
|
||||||
<Badge size="sm" key={i}>
|
|
||||||
{genre}
|
|
||||||
</Badge>
|
|
||||||
))}
|
|
||||||
</Group>
|
|
||||||
</Stack>
|
|
||||||
</Text>
|
|
||||||
</Group>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,11 +60,14 @@ export function ReadarrMediaDisplay(props: any) {
|
|||||||
return (
|
return (
|
||||||
<MediaDisplay
|
<MediaDisplay
|
||||||
media={{
|
media={{
|
||||||
|
...media,
|
||||||
title: media.title,
|
title: media.title,
|
||||||
poster: fullLink,
|
poster: fullLink,
|
||||||
artist: media.author.authorName,
|
artist: media.authorTitle,
|
||||||
overview: media.overview,
|
overview: `new book release by ${media.authorTitle}`,
|
||||||
genres: media.genres,
|
genres: media.genres ?? [],
|
||||||
|
voteAverage: media.ratings.value.toString() ?? undefined,
|
||||||
|
type: 'book',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -154,6 +90,7 @@ export function LidarrMediaDisplay(props: any) {
|
|||||||
return (
|
return (
|
||||||
<MediaDisplay
|
<MediaDisplay
|
||||||
media={{
|
media={{
|
||||||
|
type: 'music',
|
||||||
title: media.title,
|
title: media.title,
|
||||||
poster: fullLink,
|
poster: fullLink,
|
||||||
artist: media.artist.artistName,
|
artist: media.artist.artistName,
|
||||||
@@ -167,16 +104,17 @@ export function LidarrMediaDisplay(props: any) {
|
|||||||
export function RadarrMediaDisplay(props: any) {
|
export function RadarrMediaDisplay(props: any) {
|
||||||
const { media }: { media: any } = props;
|
const { media }: { media: any } = props;
|
||||||
// Find a poster CoverType
|
// Find a poster CoverType
|
||||||
const poster = media.images.find((image: any) => image.coverType === 'poster');
|
|
||||||
// Return a movie poster containting the title and the description
|
|
||||||
return (
|
return (
|
||||||
<MediaDisplay
|
<MediaDisplay
|
||||||
media={{
|
media={{
|
||||||
imdbId: media.imdbId,
|
...media,
|
||||||
title: media.title,
|
title: media.title ?? media.originalTitle,
|
||||||
overview: media.overview,
|
overview: media.overview ?? '',
|
||||||
poster: poster.url,
|
genres: media.genres ?? [],
|
||||||
genres: media.genres,
|
poster: media.images.find((image: any) => image.coverType === 'poster')?.url ?? undefined,
|
||||||
|
voteAverage: media.ratings.tmdb.value.toString() ?? undefined,
|
||||||
|
imdbId: media.imdbId ?? undefined,
|
||||||
|
type: 'movie',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -190,14 +128,106 @@ export function SonarrMediaDisplay(props: any) {
|
|||||||
return (
|
return (
|
||||||
<MediaDisplay
|
<MediaDisplay
|
||||||
media={{
|
media={{
|
||||||
imdbId: media.series.imdbId,
|
...media,
|
||||||
title: media.series.title,
|
genres: media.series.genres ?? [],
|
||||||
overview: media.series.overview,
|
overview: media.overview ?? media.series.overview ?? '',
|
||||||
poster: poster.url,
|
title: media.series.title ?? undefined,
|
||||||
genres: media.series.genres,
|
poster: poster ? poster.url : undefined,
|
||||||
seasonNumber: media.seasonNumber,
|
episodeNumber: media.episodeNumber ?? undefined,
|
||||||
episodeNumber: media.episodeNumber,
|
seasonNumber: media.seasonNumber ?? undefined,
|
||||||
|
episodetitle: media.title ?? undefined,
|
||||||
|
imdbId: media.series.imdbId ?? undefined,
|
||||||
|
voteAverage: media.series.ratings.value.toString() ?? undefined,
|
||||||
|
type: 'tvshow',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function MediaDisplay({ media }: { media: IMedia }) {
|
||||||
|
const [opened, setOpened] = useState(false);
|
||||||
|
return (
|
||||||
|
<Group mr="xs" align="stretch" noWrap style={{ maxHeight: 200 }}>
|
||||||
|
<Image src={media.poster} height={200} width={150} radius="md" fit="cover" />
|
||||||
|
<Stack justify="space-around">
|
||||||
|
<Stack spacing="sm">
|
||||||
|
<Text lineClamp={2}>
|
||||||
|
<Title order={5}>{media.title}</Title>
|
||||||
|
</Text>
|
||||||
|
<Group spacing="xs">
|
||||||
|
{media.type === 'tvshow' && (
|
||||||
|
<Badge variant="dot" size="xs" radius="md" color="blue">
|
||||||
|
s{media.seasonNumber}e{media.episodeNumber} - {media.episodetitle}
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
{media.type === 'music' && (
|
||||||
|
<Badge variant="dot" size="xs" radius="md" color="green">
|
||||||
|
{media.artist}
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
{media.type === 'movie' && (
|
||||||
|
<Badge variant="dot" size="xs" radius="md" color="orange">
|
||||||
|
Radarr
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
{media.type === 'book' && (
|
||||||
|
<Badge variant="dot" size="xs" radius="md" color="red">
|
||||||
|
Readarr
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
{media.genres.slice(0, 2).map((genre) => (
|
||||||
|
<Badge size="xs" radius="md" key={genre}>
|
||||||
|
{genre}
|
||||||
|
</Badge>
|
||||||
|
))}
|
||||||
|
</Group>
|
||||||
|
<Text color="dimmed" size="xs" lineClamp={4}>
|
||||||
|
{media.overview}
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
|
<Group grow>
|
||||||
|
{media.plexUrl && (
|
||||||
|
<Button
|
||||||
|
component="a"
|
||||||
|
target="_blank"
|
||||||
|
variant="outline"
|
||||||
|
href={media.plexUrl}
|
||||||
|
size="sm"
|
||||||
|
rightIcon={<IconPlayerPlay size={15} />}
|
||||||
|
>
|
||||||
|
Play
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{media.imdbId && (
|
||||||
|
<Button
|
||||||
|
component="a"
|
||||||
|
target="_blank"
|
||||||
|
href={`https://www.imdb.com/title/${media.imdbId}`}
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
rightIcon={<IconExternalLink size={15} />}
|
||||||
|
>
|
||||||
|
IMDb
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{media.type === 'overseer' && (
|
||||||
|
<>
|
||||||
|
<RequestModal
|
||||||
|
base={media.overseerrResult as Result}
|
||||||
|
opened={opened}
|
||||||
|
setOpened={setOpened}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
onClick={() => setOpened(true)}
|
||||||
|
size="sm"
|
||||||
|
rightIcon={<IconDownload size={15} />}
|
||||||
|
>
|
||||||
|
Request
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Group>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
import { MediaDisplay } from '../common';
|
|
||||||
import { Result } from './SearchResult';
|
|
||||||
|
|
||||||
export default function OverseerrMediaDisplay(props: any) {
|
|
||||||
const { media }: { media: Result } = props;
|
|
||||||
return (
|
|
||||||
<MediaDisplay
|
|
||||||
media={{
|
|
||||||
title: media.name ?? media.originalTitle ?? media.title ?? '',
|
|
||||||
poster: `https://image.tmdb.org/t/p/w600_and_h900_bestv2/${media.posterPath}`,
|
|
||||||
genres: [`score: ${media.voteAverage}/10`],
|
|
||||||
seasonNumber: media.mediaInfo?.seasons.length,
|
|
||||||
plexUrl: media.mediaInfo?.plexUrl,
|
|
||||||
imdbId: media.mediaInfo?.imdbId,
|
|
||||||
overview: media.overview,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { IconEyeglass } from '@tabler/icons';
|
import { IconEyeglass } from '@tabler/icons';
|
||||||
|
import { OverseerrMediaDisplay } from '../common';
|
||||||
import { IModule } from '../ModuleTypes';
|
import { IModule } from '../ModuleTypes';
|
||||||
import OverseerrMediaDisplay from './OverseerrMediaDisplay';
|
|
||||||
|
|
||||||
export const OverseerrModule: IModule = {
|
export const OverseerrModule: IModule = {
|
||||||
title: 'Overseerr',
|
title: 'Overseerr',
|
||||||
|
|||||||
Reference in New Issue
Block a user