diff --git a/front/packages/models/src/resources/metadata.ts b/front/packages/models/src/resources/metadata.ts new file mode 100644 index 00000000..e8c3ad2d --- /dev/null +++ b/front/packages/models/src/resources/metadata.ts @@ -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 . + */ + +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; diff --git a/front/packages/models/src/resources/movie.ts b/front/packages/models/src/resources/movie.ts index f6247d4f..7646a20b 100644 --- a/front/packages/models/src/resources/movie.ts +++ b/front/packages/models/src/resources/movie.ts @@ -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", ) diff --git a/front/packages/models/src/resources/show.ts b/front/packages/models/src/resources/show.ts index 0c77a35d..454fe4cb 100644 --- a/front/packages/models/src/resources/show.ts +++ b/front/packages/models/src/resources/show.ts @@ -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", ) diff --git a/front/packages/primitives/src/chip.tsx b/front/packages/primitives/src/chip.tsx index 2488dd00..2f1ddfbb 100644 --- a/front/packages/primitives/src/chip.tsx +++ b/front/packages/primitives/src/chip.tsx @@ -18,35 +18,54 @@ * along with Kyoo. If not, see . */ -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 = ({ 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) => { 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 ( -

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} -

+ /> ); }; diff --git a/front/packages/primitives/src/links.tsx b/front/packages/primitives/src/links.tsx index b7fcac8f..d97f5925 100644 --- a/front/packages/primitives/src/links.tsx +++ b/front/packages/primitives/src/links.tsx @@ -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, diff --git a/front/packages/ui/src/details/header.tsx b/front/packages/ui/src/details/header.tsx index 6539461a..cf4a2fa3 100644 --- a/front/packages/ui/src/details/header.tsx +++ b/front/packages/ui/src/details/header.tsx @@ -332,7 +332,14 @@ const Description = ({

{overview ?? t("show.noOverview")}

)} - +

{t("show.tags")}:

{(isLoading ? [...Array(3)] : tags!).map((tag, i) => ( @@ -373,6 +380,7 @@ export const Header = ({ type: "movie" | "show"; }) => { const { css } = useYoshiki(); + const { t } = useTranslation(); return ( @@ -407,6 +415,41 @@ export const Header = ({ tags={data?.tags} {...css({ paddingTop: { xs: 0, md: ts(2) } })} /> + +

{t("show.links")}:

+ {(!isLoading + ? Object.entries(data.externalId!).filter(([_, data]) => data.link) + : [...Array(3)].map((_, i) => [i, undefined] as const) + ).map(([name, data]) => ( + theme.contrast, + bg: (theme: Theme) => theme.accent, + }, + }, + })} + > + {data ? name : } + + ))} +
{data?.collections && ( {data.collections.map((x) => ( diff --git a/front/translations/en.json b/front/translations/en.json index 0f603c1e..56ab9553 100644 --- a/front/translations/en.json +++ b/front/translations/en.json @@ -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" }, diff --git a/front/translations/fr.json b/front/translations/fr.json index c7affdd0..841fe87b 100644 --- a/front/translations/fr.json +++ b/front/translations/fr.json @@ -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" },