Add external links on show/movie page

This commit is contained in:
Zoe Roux 2023-11-03 16:05:09 +01:00
parent a825912ad3
commit b995b86cee
8 changed files with 129 additions and 18 deletions

View File

@ -0,0 +1,37 @@
/*
* Kyoo - A portable and vast media library solution.
* Copyright (c) Kyoo.
*
* See AUTHORS.md and LICENSE file in the project root for full license information.
*
* Kyoo is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Kyoo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
*/
import { z } from "zod";
export const MetadataP = z.record(
z.object({
/*
* The ID of the resource on the external provider.
*/
dataId: z.string(),
/*
* The URL of the resource on the external provider.
*/
link: z.string().nullable(),
}),
);
export type Metadata = z.infer<typeof MetadataP>;

View File

@ -25,6 +25,7 @@ import { Genre } from "./genre";
import { StudioP } from "./studio";
import { Status } from "./show";
import { CollectionP } from "./collection";
import { MetadataP } from "./metadata";
export const MovieP = withImages(
ResourceP.extend({
@ -94,6 +95,10 @@ export const MovieP = withImages(
*/
hls: z.string().transform(imageFn),
}),
/**
* The link to metadata providers that this show has.
*/
externalId: MetadataP,
}),
"movies",
)

View File

@ -26,6 +26,7 @@ import { SeasonP } from "./season";
import { StudioP } from "./studio";
import { BaseEpisodeP } from "./episode.base";
import { CollectionP } from "./collection";
import { MetadataP } from "./metadata";
/**
* The enum containing show's status.
@ -99,6 +100,10 @@ export const ShowP = withImages(
* The first episode of this show
*/
firstEpisode: BaseEpisodeP.optional().nullable(),
/**
* The link to metadata providers that this show has.
*/
externalId: MetadataP,
}),
"shows",
)

View File

@ -18,35 +18,54 @@
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
*/
import { rem, Stylable, Theme, useYoshiki } from "yoshiki/native";
import { px, rem, Stylable, Theme, useYoshiki } from "yoshiki/native";
import { P } from "./text";
import { ts } from "./utils";
import { A } from "./links";
import { ComponentType } from "react";
export const Chip = ({
label,
export const Chip = <AsProps = { label: string },>({
color,
size = "medium",
outline = false,
as,
...props
}: { label: string; color?: string; size?: "small" | "medium" | "large" } & Stylable) => {
}: {
color?: string;
size?: "small" | "medium" | "large";
outline?: boolean;
as?: ComponentType<AsProps>;
} & AsProps) => {
const { css } = useYoshiki();
const sizeMult = size == "medium" ? 1 : size == "small" ? 0.75 : 1.25;
const As = as ?? (P as any);
// @ts-ignore backward compatibilty
if (!as && props.label) props.children = props.label;
return (
<P
<As
{...css(
{
pY: ts(1 * sizeMult),
pX: ts(1.5 * sizeMult),
borderRadius: ts(3),
fontSize: rem(0.8),
color: (theme: Theme) => theme.contrast,
bg: color ?? ((theme: Theme) => theme.accent),
},
[
{
pY: ts(1 * sizeMult),
pX: ts(1.5 * sizeMult),
borderRadius: ts(3),
fontSize: rem(0.8),
},
!outline && {
color: (theme: Theme) => theme.contrast,
bg: color ?? ((theme: Theme) => theme.accent),
},
outline && {
borderColor: color ?? ((theme: Theme) => theme.accent),
borderStyle: "solid",
borderWidth: px(1),
},
],
props,
)}
>
{label}
</P>
/>
);
};

View File

@ -86,7 +86,7 @@ export const Link = ({
target,
children,
...props
}: { href?: string; target?: string; replace?: boolean } & PressableProps) => {
}: { href?: string | null; target?: string; replace?: boolean } & PressableProps) => {
const linkProps = useLink({
href: href ?? "#",
replace,

View File

@ -332,7 +332,14 @@ const Description = ({
<P {...css({ textAlign: "justify" })}>{overview ?? t("show.noOverview")}</P>
)}
</Skeleton>
<View {...css({ flexWrap: "wrap", flexDirection: "row", marginTop: ts(0.5) })}>
<View
{...css({
flexWrap: "wrap",
flexDirection: "row",
alignItems: "center",
marginTop: ts(0.5),
})}
>
<P {...css({ marginRight: ts(0.5) })}>{t("show.tags")}:</P>
{(isLoading ? [...Array<string>(3)] : tags!).map((tag, i) => (
<Chip key={tag ?? i} label={tag} size="small" {...css({ m: ts(0.5) })} />
@ -373,6 +380,7 @@ export const Header = ({
type: "movie" | "show";
}) => {
const { css } = useYoshiki();
const { t } = useTranslation();
return (
<Fetch query={query}>
@ -407,6 +415,41 @@ export const Header = ({
tags={data?.tags}
{...css({ paddingTop: { xs: 0, md: ts(2) } })}
/>
<Container
{...css({
flexWrap: "wrap",
flexDirection: "row",
alignItems: "center",
marginTop: ts(0.5),
})}
>
<P {...css({ marginRight: ts(0.5), textAlign: "center" })}>{t("show.links")}:</P>
{(!isLoading
? Object.entries(data.externalId!).filter(([_, data]) => data.link)
: [...Array(3)].map((_, i) => [i, undefined] as const)
).map(([name, data]) => (
<Chip
key={name}
as={Link}
href={data?.link}
target="_blank"
size="small"
outline
{...css({
m: ts(0.5),
color: "inherit",
fover: {
self: {
color: (theme: Theme) => theme.contrast,
bg: (theme: Theme) => theme.accent,
},
},
})}
>
{data ? name : <Skeleton {...css({ width: rem(3) })} />}
</Chip>
))}
</Container>
{data?.collections && (
<Container {...css({ marginY: ts(2) })}>
{data.collections.map((x) => (

View File

@ -17,6 +17,7 @@
"episode-none": "There is no episodes in this season",
"episodeNoMetadata": "No metadata available",
"tags": "Tags",
"links": "Links",
"jumpToSeason": "Jump to season",
"partOf": "Part of the"
},

View File

@ -17,6 +17,7 @@
"episode-none": "Il n'y a pas d'épisodes dans cette saison",
"episodeNoMetadata": "Aucune metadonnée disponible",
"tags": "Tags",
"links": "Liens",
"jumpToSeason": "Aller sur une saison",
"partOf": "Fait parti de"
},