diff --git a/front/src/models/entry.ts b/front/src/models/entry.ts new file mode 100644 index 00000000..25ddca9e --- /dev/null +++ b/front/src/models/entry.ts @@ -0,0 +1,7 @@ +import z from "zod"; + +export const Entry = z.object({ + id: z.string(), + slug: z.string(), +}); +export type Entry = z.infer; diff --git a/front/src/models/index.ts b/front/src/models/index.ts index 61d55ba6..60822ce5 100644 --- a/front/src/models/index.ts +++ b/front/src/models/index.ts @@ -1,4 +1,4 @@ -export * from "./page"; +export * from "./utils/page"; export * from "./kyoo-error"; export * from "./resources"; export * from "./traits"; diff --git a/front/src/models/resources/genre.ts b/front/src/models/resources/genre.ts deleted file mode 100644 index a1435860..00000000 --- a/front/src/models/resources/genre.ts +++ /dev/null @@ -1,26 +0,0 @@ -export enum Genre { - Action = "Action", - Adventure = "Adventure", - Animation = "Animation", - Comedy = "Comedy", - Crime = "Crime", - Documentary = "Documentary", - Drama = "Drama", - Family = "Family", - Fantasy = "Fantasy", - History = "History", - Horror = "Horror", - Music = "Music", - Mystery = "Mystery", - Romance = "Romance", - ScienceFiction = "ScienceFiction", - Thriller = "Thriller", - War = "War", - Western = "Western", - Kids = "Kids", - News = "News", - Reality = "Reality", - Soap = "Soap", - Talk = "Talk", - Politics = "Politics", -} diff --git a/front/src/models/resources/index.ts b/front/src/models/resources/index.ts deleted file mode 100644 index 9b813dba..00000000 --- a/front/src/models/resources/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -export * from "./account"; -export * from "./library-item"; -export * from "./news"; -export * from "./show"; -export * from "./movie"; -export * from "./collection"; -export * from "./genre"; -export * from "./person"; -export * from "./studio"; -export * from "./episode"; -export * from "./season"; -export * from "./watch-info"; -export * from "./watch-status"; -export * from "./watchlist"; -export * from "./user"; -export * from "./server-info"; diff --git a/front/src/models/resources/metadata.ts b/front/src/models/resources/metadata.ts deleted file mode 100644 index 593de0d9..00000000 --- a/front/src/models/resources/metadata.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { z } from "zod"; - -export const MetadataP = z.preprocess( - (x) => - typeof x === "object" && x ? Object.fromEntries(Object.entries(x).filter(([_, v]) => v)) : x, - 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/src/models/resources/show.ts b/front/src/models/resources/show.ts deleted file mode 100644 index 5d5d68ea..00000000 --- a/front/src/models/resources/show.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { z } from "zod"; -import { ImagesP, ResourceP } from "../traits"; -import { zdate } from "../utils"; -import { BaseEpisodeP } from "./episode.base"; -import { Genre } from "./genre"; -import { MetadataP } from "./metadata"; -import { StudioP } from "./studio"; -import { ShowWatchStatusP } from "./watch-status"; - -/** - * The enum containing show's status. - */ -export enum Status { - Unknown = "Unknown", - Finished = "Finished", - Airing = "Airing", - Planned = "Planned", -} - -export const ShowP = ResourceP("show") - .merge(ImagesP) - .extend({ - /** - * The title of this show. - */ - name: z.string(), - /** - * A catchphrase for this show. - */ - tagline: z.string().nullable(), - /** - * The list of alternative titles of this show. - */ - aliases: z.array(z.string()), - /** - * The summary of this show. - */ - overview: z.string().nullable(), - /** - * A list of tags that match this movie. - */ - tags: z.array(z.string()), - /** - * Is this show airing, not aired yet or finished? - */ - status: z.nativeEnum(Status), - /** - * How well this item is rated? (from 0 to 100). - */ - rating: z.number().int().gte(0).lte(100), - /** - * The date this show started airing. It can be null if this is unknown. - */ - startAir: zdate().nullable(), - /** - * The date this show finished airing. It can also be null if this is unknown. - */ - endAir: zdate().nullable(), - /** - * The list of genres (themes) this show has. - */ - genres: z.array(z.nativeEnum(Genre)), - /** - * A youtube url for the trailer. - */ - trailer: z.string().optional().nullable(), - /** - * The studio that made this show. - */ - studio: StudioP.optional().nullable(), - /** - * The first episode of this show - */ - firstEpisode: BaseEpisodeP.optional().nullable(), - /** - * The link to metadata providers that this show has. - */ - externalId: MetadataP, - /** - * Metadata of what an user as started/planned to watch. - */ - watchStatus: ShowWatchStatusP.nullable().optional(), - /** - * The number of episodes in this show. - */ - episodesCount: z.number().int().gte(0).optional(), - }) - .transform((x) => { - if (!x.thumbnail && x.poster) { - x.thumbnail = { ...x.poster }; - if (x.thumbnail) { - x.thumbnail.low = x.thumbnail.high; - x.thumbnail.medium = x.thumbnail.high; - } - } - return x; - }) - .transform((x) => ({ - href: `/show/${x.slug}`, - playHref: x.firstEpisode ? `/watch/${x.firstEpisode.slug}` : null, - ...x, - })); - -/** - * A tv serie or an anime. - */ -export type Show = z.infer; diff --git a/front/src/models/resources/studio.ts b/front/src/models/resources/studio.ts deleted file mode 100644 index 2d25e7d5..00000000 --- a/front/src/models/resources/studio.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { z } from "zod"; -import { ResourceP } from "../traits"; - -export const StudioP = ResourceP("studio").extend({ - /** - * The name of this studio. - */ - name: z.string(), -}); - -/** - * A studio that make shows. - */ -export type Studio = z.infer; diff --git a/front/src/models/resources/watch-status.ts b/front/src/models/resources/watch-status.ts deleted file mode 100644 index 3b23cd80..00000000 --- a/front/src/models/resources/watch-status.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { z } from "zod"; -import { zdate } from "../utils"; -import { BaseEpisodeP } from "./episode.base"; - -export enum WatchStatusV { - Completed = "Completed", - Watching = "Watching", - Droped = "Droped", - Planned = "Planned", -} - -export const WatchStatusP = z.object({ - /** - * The date this item was added to the watchlist (watched or plan to watch by the user). - */ - addedDate: zdate(), - /** - * The date at which this item was played. - */ - playedDate: zdate().nullable(), - /** - * Has the user started watching, is it planned? - */ - status: z.nativeEnum(WatchStatusV), - /** - * Where the player has stopped watching the episode (in seconds). - * Null if the status is not Watching or if the next episode is not started. - */ - watchedTime: z.number().int().gte(0).nullable(), - /** - * Where the player has stopped watching the episode (in percentage between 0 and 100). - * Null if the status is not Watching or if the next episode is not started. - */ - watchedPercent: z.number().int().gte(0).lte(100).nullable(), -}); -export type WatchStatus = z.infer; - -export const ShowWatchStatusP = WatchStatusP.and( - z.object({ - /** - * The number of episodes the user has not seen. - */ - unseenEpisodesCount: z.number().int().gte(0), - /** - * The next episode to watch - */ - nextEpisode: BaseEpisodeP.nullable(), - }), -); -export type ShowWatchStatus = z.infer; diff --git a/front/src/models/serie.ts b/front/src/models/serie.ts new file mode 100644 index 00000000..3cffee78 --- /dev/null +++ b/front/src/models/serie.ts @@ -0,0 +1,62 @@ +import { z } from "zod"; +import { Entry } from "./entry"; +import { Studio } from "./studio"; +import { Genre } from "./utils/genre"; +import { Image } from "./utils/images"; +import { Metadata } from "./utils/metadata"; +import { zdate } from "./utils/utils"; + +export const Serie = z + .object({ + id: z.string(), + slug: z.string(), + name: z.string(), + original: z.object({ + name: z.string(), + latinName: z.string().nullable(), + language: z.string(), + }), + tagline: z.string().nullable(), + aliases: z.array(z.string()), + tags: z.array(z.string()), + description: z.string().nullable(), + status: z.enum(["unknown", "finished", "airing", "planned"]), + rating: z.number().int().gte(0).lte(100).nullable(), + startAir: zdate().nullable(), + endAir: zdate().nullable(), + genres: z.array(Genre), + runtime: z.number().nullable(), + externalId: Metadata, + + entriesCount: z.number().int(), + availableCount: z.number().int(), + + poster: Image.nullable(), + thumbnail: Image.nullable(), + banner: Image.nullable(), + logo: Image.nullable(), + trailerUrl: z.string().optional().nullable(), + + createdAt: zdate(), + updatedAt: zdate(), + + studios: z.array(Studio).optional(), + firstEntry: Entry.optional().nullable(), + nextEntry: Entry.optional().nullable(), + watchStatus: z + .object({ + status: z.enum(["completed", "watching", "rewatching", "dropped", "planned"]), + score: z.number().int().gte(0).lte(100).nullable(), + startedAt: zdate().nullable(), + completedAt: zdate().nullable(), + seenCount: z.number().int().gte(0), + }) + .nullable(), + }) + .transform((x) => ({ + ...x, + href: `/serie/${x.slug}`, + playHref: x.firstEntry ? `/watch/${x.firstEntry.slug}` : null, + })); + +export type Serie = z.infer; diff --git a/front/src/models/studio.ts b/front/src/models/studio.ts new file mode 100644 index 00000000..5e372d98 --- /dev/null +++ b/front/src/models/studio.ts @@ -0,0 +1,15 @@ +import { z } from "zod"; +import { Image } from "./utils/images"; +import { Metadata } from "./utils/metadata"; +import { zdate } from "./utils/utils"; + +export const Studio = z.object({ + id: z.string(), + slug: z.string(), + name: z.string(), + logo: Image.nullable(), + externalId: Metadata, + createdAt: zdate(), + updatedAt: zdate(), +}); +export type Studio = z.infer; diff --git a/front/src/models/traits/images.ts b/front/src/models/traits/images.ts deleted file mode 100644 index f2a29380..00000000 --- a/front/src/models/traits/images.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { z } from "zod"; - -export const Img = z.object({ - source: z.string(), - blurhash: z.string(), - low: z.string(), - medium: z.string(), - high: z.string(), -}); - -export const ImagesP = z.object({ - /** - * An url to the poster of this resource. If this resource does not have an image, the link will - * be null. If the kyoo's instance is not capable of handling this kind of image for the specific - * resource, this field won't be present. - */ - poster: Img.nullable(), - - /** - * An url to the thumbnail of this resource. If this resource does not have an image, the link - * will be null. If the kyoo's instance is not capable of handling this kind of image for the - * specific resource, this field won't be present. - */ - thumbnail: Img.nullable(), - - /** - * An url to the logo of this resource. If this resource does not have an image, the link will be - * null. If the kyoo's instance is not capable of handling this kind of image for the specific - * resource, this field won't be present. - */ - logo: Img.nullable(), -}); - -/** - * Base traits for items that has image resources. - */ -export type KyooImage = z.infer; diff --git a/front/src/models/traits/index.ts b/front/src/models/traits/index.ts deleted file mode 100644 index 3c73c1fe..00000000 --- a/front/src/models/traits/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./resource"; -export * from "./images"; diff --git a/front/src/models/traits/resource.ts b/front/src/models/traits/resource.ts deleted file mode 100644 index 005a3000..00000000 --- a/front/src/models/traits/resource.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { z } from "zod"; - -export const ResourceP = (kind: T) => - z.object({ - /** - * A unique ID for this type of resource. This can't be changed and duplicates are not allowed. - */ - id: z.string(), - - /** - * A human-readable identifier that can be used instead of an ID. A slug must be unique for a type - * of resource but it can be changed. - */ - slug: z.string(), - - /** - * The type of resource - */ - kind: z.literal(kind), - }); - -/** - * The base trait used to represent identifiable resources. - */ -export type Resource = z.infer>; diff --git a/front/src/models/utils/genre.ts b/front/src/models/utils/genre.ts new file mode 100644 index 00000000..2f0c5fdd --- /dev/null +++ b/front/src/models/utils/genre.ts @@ -0,0 +1,27 @@ +import z from "zod"; + +export const Genre = z.enum([ + "action", + "adventure", + "animation", + "comedy", + "crime", + "documentary", + "drama", + "family", + "fantasy", + "history", + "horror", + "music", + "mystery", + "romance", + "science-fiction", + "thriller", + "war", + "western", + "kids", + "reality", + "politics", + "soap", + "talk", +]); diff --git a/front/src/models/utils/images.ts b/front/src/models/utils/images.ts new file mode 100644 index 00000000..bc66ed81 --- /dev/null +++ b/front/src/models/utils/images.ts @@ -0,0 +1,16 @@ +import { z } from "zod"; + +export const Image = z + .object({ + id: z.string(), + source: z.string(), + blurhash: z.string(), + }) + .transform((x) => ({ + ...x, + low: `/images/${x.id}?quality=low`, + medium: `/images/${x.id}?quality=medium`, + high: `/images/${x.id}?quality=high`, + })); + +export type Image = z.infer; diff --git a/front/src/models/utils/metadata.ts b/front/src/models/utils/metadata.ts new file mode 100644 index 00000000..1b443411 --- /dev/null +++ b/front/src/models/utils/metadata.ts @@ -0,0 +1,9 @@ +import { z } from "zod"; + +export const Metadata = z.record( + z.object({ + dataId: z.string(), + link: z.string().nullable(), + }), +); +export type Metadata = z.infer; diff --git a/front/src/models/page.ts b/front/src/models/utils/page.ts similarity index 55% rename from front/src/models/page.ts rename to front/src/models/utils/page.ts index 1b12868a..46699ce2 100644 --- a/front/src/models/page.ts +++ b/front/src/models/utils/page.ts @@ -1,38 +1,10 @@ import { z } from "zod"; -/** - * A page of resource that contains information about the pagination of resources. - */ export interface Page { - /** - * The link of the current page. - * - * @format uri - */ this: string; - - /** - * The link of the first page. - * - * @format uri - */ first: string; - - /** - * The link of the next page. - * - * @format uri - */ next: string | null; - - /** - * The number of items in the current page. - */ count: number; - - /** - * The list of items in the page. - */ items: T[]; } diff --git a/front/src/models/utils.ts b/front/src/models/utils/utils.ts similarity index 100% rename from front/src/models/utils.ts rename to front/src/models/utils/utils.ts diff --git a/front/src/ui/browse/list.tsx b/front/src/ui/browse/list.tsx deleted file mode 100644 index 7619d65a..00000000 --- a/front/src/ui/browse/list.tsx +++ /dev/null @@ -1,176 +0,0 @@ -import type { KyooImage, WatchStatusV } from "@kyoo/models"; -import { - GradientImageBackground, - Heading, - Link, - P, - Poster, - PosterBackground, - Skeleton, - imageBorderRadius, - important, - ts, -} from "@kyoo/primitives"; -import { useState } from "react"; -import { Platform, View } from "react-native"; -import { percent, px, rem, useYoshiki } from "yoshiki/native"; -import { ItemContext } from "../../packages/ui/src/components/context-menus"; -import type { Layout } from "../fetch"; -import { ItemWatchStatus } from "../ui/browse/grid"; - -export const ItemList = ({ - href, - slug, - type, - name, - subtitle, - thumbnail, - poster, - watchStatus, - unseenEpisodesCount, - ...props -}: { - href: string; - slug: string; - type: "movie" | "show" | "collection"; - name: string; - subtitle: string | null; - poster: KyooImage | null; - thumbnail: KyooImage | null; - watchStatus: WatchStatusV | null; - unseenEpisodesCount: number | null; -}) => { - const { css } = useYoshiki("line"); - const [moreOpened, setMoreOpened] = useState(false); - - return ( - setMoreOpened(true)} - {...css( - { - alignItems: "center", - justifyContent: "space-evenly", - flexDirection: "row", - height: ItemList.layout.size, - borderRadius: px(imageBorderRadius), - overflow: "hidden", - marginX: ItemList.layout.gap, - child: { - more: { - opacity: 0, - }, - }, - fover: { - title: { - textDecorationLine: "underline", - }, - more: { - opacity: 100, - }, - }, - }, - props, - )} - > - - - - {name} - - {type !== "collection" && ( - setMoreOpened(v)} - {...css([ - { - // I dont know why marginLeft gets overwritten by the margin: px(2) so we important - marginLeft: important(ts(2)), - bg: (theme) => theme.darkOverlay, - }, - "more", - Platform.OS === "web" && moreOpened && { opacity: important(100) }, - ])} - /> - )} - - {subtitle && ( -

- {subtitle} -

- )} -
- - - -
- ); -}; - -ItemList.Loader = (props: object) => { - const { css } = useYoshiki(); - - return ( - theme.dark.background, - marginX: ItemList.layout.gap, - }, - props, - )} - > - - - - - - - ); -}; - -ItemList.layout = { numColumns: 1, size: 300, layout: "vertical", gap: ts(2) } satisfies Layout;