mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-06-04 22:24:14 -04:00
wip front blurhash
This commit is contained in:
parent
30d52c6061
commit
36f4bbc7e7
@ -24,10 +24,10 @@ services:
|
|||||||
dockerfile: Dockerfile.dev
|
dockerfile: Dockerfile.dev
|
||||||
volumes:
|
volumes:
|
||||||
- ./front:/app
|
- ./front:/app
|
||||||
- /app/.yarn
|
# - /app/.yarn
|
||||||
- /app/node_modules
|
# - /app/node_modules
|
||||||
- /app/apps/web/.next/
|
# - /app/apps/web/.next/
|
||||||
- /app/apps/mobile/.expo/
|
# - /app/apps/mobile/.expo/
|
||||||
ports:
|
ports:
|
||||||
- "3000:3000"
|
- "3000:3000"
|
||||||
- "19000:19000"
|
- "19000:19000"
|
||||||
|
@ -26,6 +26,7 @@
|
|||||||
"expo-constants": "~14.2.1",
|
"expo-constants": "~14.2.1",
|
||||||
"expo-dev-client": "~2.2.1",
|
"expo-dev-client": "~2.2.1",
|
||||||
"expo-font": "~11.1.1",
|
"expo-font": "~11.1.1",
|
||||||
|
"expo-image": "~1.0.0",
|
||||||
"expo-linear-gradient": "~12.1.2",
|
"expo-linear-gradient": "~12.1.2",
|
||||||
"expo-linking": "~4.0.1",
|
"expo-linking": "~4.0.1",
|
||||||
"expo-localization": "~14.1.1",
|
"expo-localization": "~14.1.1",
|
||||||
|
@ -17,7 +17,9 @@
|
|||||||
"@material-symbols/svg-400": "^0.5.0",
|
"@material-symbols/svg-400": "^0.5.0",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.0.4",
|
"@radix-ui/react-dropdown-menu": "^2.0.4",
|
||||||
"@tanstack/react-query": "^4.26.1",
|
"@tanstack/react-query": "^4.26.1",
|
||||||
|
"expo-image": "^1.3.2",
|
||||||
"expo-linear-gradient": "^12.1.2",
|
"expo-linear-gradient": "^12.1.2",
|
||||||
|
"expo-modules-core": "^1.5.9",
|
||||||
"hls.js": "^1.3.4",
|
"hls.js": "^1.3.4",
|
||||||
"i18next": "^22.4.11",
|
"i18next": "^22.4.11",
|
||||||
"jotai": "^2.0.3",
|
"jotai": "^2.0.3",
|
||||||
|
@ -124,7 +124,7 @@ export const queryFn = async <Data,>(
|
|||||||
const parsed = await type.safeParseAsync(data);
|
const parsed = await type.safeParseAsync(data);
|
||||||
if (!parsed.success) {
|
if (!parsed.success) {
|
||||||
console.log("Parse error: ", parsed.error);
|
console.log("Parse error: ", parsed.error);
|
||||||
throw { errors: parsed.error.errors.map((x) => x.message) } as KyooErrors;
|
throw { errors: ["Invalid response from kyoo. Possible version mismatch between the server and the application."] } as KyooErrors;
|
||||||
}
|
}
|
||||||
return parsed.data;
|
return parsed.data;
|
||||||
};
|
};
|
||||||
|
@ -23,44 +23,51 @@ import { zdate } from "../utils";
|
|||||||
import { ImagesP } from "../traits";
|
import { ImagesP } from "../traits";
|
||||||
import { ResourceP } from "../traits/resource";
|
import { ResourceP } from "../traits/resource";
|
||||||
|
|
||||||
export const EpisodeP = z.preprocess(
|
const BaseEpisodeP = ResourceP.merge(ImagesP).extend({
|
||||||
(x: any) => {
|
/**
|
||||||
if (!x) return x;
|
* The season in witch this episode is in.
|
||||||
x.name = x.title;
|
*/
|
||||||
return x;
|
seasonNumber: z.number().nullable(),
|
||||||
},
|
|
||||||
ResourceP.merge(ImagesP).extend({
|
|
||||||
/**
|
|
||||||
* The season in witch this episode is in.
|
|
||||||
*/
|
|
||||||
seasonNumber: z.number().nullable(),
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The number of this episode in it's season.
|
* The number of this episode in it's season.
|
||||||
*/
|
*/
|
||||||
episodeNumber: z.number().nullable(),
|
episodeNumber: z.number().nullable(),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The absolute number of this episode. It's an episode number that is not reset to 1 after a new season.
|
* The absolute number of this episode. It's an episode number that is not reset to 1 after a new
|
||||||
*/
|
* season.
|
||||||
absoluteNumber: z.number().nullable(),
|
*/
|
||||||
|
absoluteNumber: z.number().nullable(),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The title of this episode.
|
* The title of this episode.
|
||||||
*/
|
*/
|
||||||
name: z.string().nullable(),
|
name: z.string().nullable(),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The overview of this episode.
|
* The overview of this episode.
|
||||||
*/
|
*/
|
||||||
overview: z.string().nullable(),
|
overview: z.string().nullable(),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The release date of this episode. It can be null if unknown.
|
* The release date of this episode. It can be null if unknown.
|
||||||
*/
|
*/
|
||||||
releaseDate: zdate().nullable(),
|
releaseDate: zdate().nullable(),
|
||||||
}),
|
});
|
||||||
);
|
|
||||||
|
export const EpisodeP = BaseEpisodeP.extend({
|
||||||
|
/**
|
||||||
|
* The episode that come before this one if you follow usual watch orders. If this is the first
|
||||||
|
* episode or this is a movie, it will be null.
|
||||||
|
*/
|
||||||
|
previousEpisode: BaseEpisodeP.nullable().optional(),
|
||||||
|
/**
|
||||||
|
* The episode that come after this one if you follow usual watch orders. If this is the last
|
||||||
|
* aired episode or this is a movie, it will be null.
|
||||||
|
*/
|
||||||
|
nextEpisode: BaseEpisodeP.nullable().optional(),
|
||||||
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A class to represent a single show's episode.
|
* A class to represent a single show's episode.
|
||||||
|
@ -18,17 +18,23 @@
|
|||||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { z } from "zod";
|
export enum Genre {
|
||||||
import { ResourceP } from "../traits/resource";
|
Action = "Action",
|
||||||
|
Adventure = "Adventure",
|
||||||
export const GenreP = ResourceP.extend({
|
Animation = "Animation",
|
||||||
/**
|
Comedy = "Comedy",
|
||||||
* The name of this genre.
|
Crime = "Crime",
|
||||||
*/
|
Documentary = "Documentary",
|
||||||
name: z.string(),
|
Drama = "Drama",
|
||||||
});
|
Family = "Family",
|
||||||
|
Fantasy = "Fantasy",
|
||||||
/**
|
History = "History",
|
||||||
* A genre that allow one to specify categories for shows.
|
Horror = "Horror",
|
||||||
*/
|
Music = "Music",
|
||||||
export type Genre = z.infer<typeof GenreP>;
|
Mystery = "Mystery",
|
||||||
|
Romance = "Romance",
|
||||||
|
ScienceFiction = "ScienceFiction",
|
||||||
|
Thriller = "Thriller",
|
||||||
|
War = "War",
|
||||||
|
Western = "Western",
|
||||||
|
}
|
||||||
|
@ -18,7 +18,6 @@
|
|||||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export * from "./library";
|
|
||||||
export * from "./library-item";
|
export * from "./library-item";
|
||||||
export * from "./show";
|
export * from "./show";
|
||||||
export * from "./movie";
|
export * from "./movie";
|
||||||
|
@ -26,32 +26,26 @@ import { ShowP } from "./show";
|
|||||||
/**
|
/**
|
||||||
* The type of item, ether a show, a movie or a collection.
|
* The type of item, ether a show, a movie or a collection.
|
||||||
*/
|
*/
|
||||||
export enum ItemType {
|
export enum ItemKind {
|
||||||
Show = 0,
|
Show = "Show",
|
||||||
Movie = 1,
|
Movie = "Movie",
|
||||||
Collection = 2,
|
Collection = "Collection",
|
||||||
}
|
}
|
||||||
|
|
||||||
export const LibraryItemP = z.preprocess(
|
export const LibraryItemP = z.union([
|
||||||
(x: any) => {
|
/*
|
||||||
if (!x.aliases) x.aliases = [];
|
* Either a Show
|
||||||
return x;
|
*/
|
||||||
},
|
ShowP.and(z.object({ kind: z.literal(ItemKind.Show) })),
|
||||||
z.union([
|
/*
|
||||||
/*
|
* Or a Movie
|
||||||
* Either a Show
|
*/
|
||||||
*/
|
MovieP.and(z.object({ kind: z.literal(ItemKind.Movie) })),
|
||||||
ShowP.and(z.object({ type: z.literal(ItemType.Show) })),
|
/*
|
||||||
/*
|
* Or a Collection
|
||||||
* Or a Movie
|
*/
|
||||||
*/
|
CollectionP.and(z.object({ kind: z.literal(ItemKind.Collection) })),
|
||||||
MovieP.and(z.object({ type: z.literal(ItemType.Movie) })),
|
]);
|
||||||
/*
|
|
||||||
* Or a Collection
|
|
||||||
*/
|
|
||||||
CollectionP.and(z.object({ type: z.literal(ItemType.Collection) })),
|
|
||||||
]),
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An item that can be contained by a Library (so a Show, a Movie or a Collection).
|
* An item that can be contained by a Library (so a Show, a Movie or a Collection).
|
||||||
|
@ -1,39 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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";
|
|
||||||
import { ResourceP } from "../traits/resource";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The library that will contain Shows, Collections...
|
|
||||||
*/
|
|
||||||
export const LibraryP = ResourceP.extend({
|
|
||||||
/**
|
|
||||||
* The name of this library.
|
|
||||||
*/
|
|
||||||
name: z.string(),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The list of paths that this library is responsible for. This is mainly used by the Scan task.
|
|
||||||
*/
|
|
||||||
paths: z.array(z.string()),
|
|
||||||
});
|
|
||||||
|
|
||||||
export type Library = z.infer<typeof LibraryP>;
|
|
@ -21,58 +21,53 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { zdate } from "../utils";
|
import { zdate } from "../utils";
|
||||||
import { ImagesP, ResourceP } from "../traits";
|
import { ImagesP, ResourceP } from "../traits";
|
||||||
import { GenreP } from "./genre";
|
import { Genre } from "./genre";
|
||||||
import { StudioP } from "./studio";
|
import { StudioP } from "./studio";
|
||||||
|
import { Status } from "./show";
|
||||||
|
|
||||||
/**
|
export const MovieP = ResourceP.merge(ImagesP).extend({
|
||||||
* The enum containing movie's status.
|
/**
|
||||||
*/
|
* The title of this movie.
|
||||||
export enum MovieStatus {
|
*/
|
||||||
Unknown = 0,
|
name: z.string(),
|
||||||
Finished = 1,
|
/**
|
||||||
Planned = 3,
|
* A catchphrase for this show.
|
||||||
}
|
*/
|
||||||
|
tagline: z.string().nullable(),
|
||||||
export const MovieP = z.preprocess(
|
/**
|
||||||
(x: any) => {
|
* The list of alternative titles of this movie.
|
||||||
// Waiting for the API to be updaded
|
*/
|
||||||
x.name = x.title;
|
aliases: z.array(z.string()),
|
||||||
if (x.aliases === null) x.aliases = [];
|
/**
|
||||||
x.airDate = x.startAir;
|
* The summary of this movie.
|
||||||
x.trailer = x.images["3"];
|
*/
|
||||||
return x;
|
overview: z.string().nullable(),
|
||||||
},
|
/**
|
||||||
ResourceP.merge(ImagesP).extend({
|
* A list of tags that match this movie.
|
||||||
/**
|
*/
|
||||||
* The title of this movie.
|
tags: z.array(z.string()),
|
||||||
*/
|
/**
|
||||||
name: z.string(),
|
/**
|
||||||
/**
|
* Is this movie not aired yet or finished?
|
||||||
* The list of alternative titles of this movie.
|
*/
|
||||||
*/
|
status: z.nativeEnum(Status),
|
||||||
aliases: z.array(z.string()),
|
/**
|
||||||
/**
|
* The date this movie aired. It can also be null if this is unknown.
|
||||||
* The summary of this movie.
|
*/
|
||||||
*/
|
airDate: zdate().nullable(),
|
||||||
overview: z.string().nullable(),
|
/**
|
||||||
/**
|
* A youtube url for the trailer.
|
||||||
* Is this movie not aired yet or finished?
|
*/
|
||||||
*/
|
trailer: z.string().optional().nullable(),
|
||||||
status: z.nativeEnum(MovieStatus),
|
/**
|
||||||
/**
|
* The list of genres (themes) this movie has.
|
||||||
* The date this movie aired. It can also be null if this is unknown.
|
*/
|
||||||
*/
|
genres: z.array(z.nativeEnum(Genre)),
|
||||||
airDate: zdate().nullable(),
|
/**
|
||||||
/**
|
* The studio that made this movie.
|
||||||
* The list of genres (themes) this movie has.
|
*/
|
||||||
*/
|
studio: StudioP.optional().nullable(),
|
||||||
genres: z.array(GenreP).optional(),
|
});
|
||||||
/**
|
|
||||||
* The studio that made this movie.
|
|
||||||
*/
|
|
||||||
studio: StudioP.optional().nullable(),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A Movie type
|
* A Movie type
|
||||||
|
@ -23,37 +23,28 @@ import { zdate } from "../utils";
|
|||||||
import { ImagesP } from "../traits";
|
import { ImagesP } from "../traits";
|
||||||
import { ResourceP } from "../traits/resource";
|
import { ResourceP } from "../traits/resource";
|
||||||
|
|
||||||
export const SeasonP = z.preprocess(
|
export const SeasonP = ResourceP.merge(ImagesP).extend({
|
||||||
(x: any) => {
|
/**
|
||||||
x.name = x.title;
|
* The name of this season.
|
||||||
return x;
|
*/
|
||||||
},
|
name: z.string(),
|
||||||
ResourceP.merge(ImagesP).extend({
|
/**
|
||||||
/**
|
* The number of this season. This can be set to 0 to indicate specials.
|
||||||
* The name of this season.
|
*/
|
||||||
*/
|
seasonNumber: z.number(),
|
||||||
name: z.string(),
|
/**
|
||||||
/**
|
* A quick overview of this season.
|
||||||
* The number of this season. This can be set to 0 to indicate specials.
|
*/
|
||||||
*/
|
overview: z.string().nullable(),
|
||||||
seasonNumber: z.number(),
|
/**
|
||||||
|
* The starting air date of this season.
|
||||||
/**
|
*/
|
||||||
* A quick overview of this season.
|
startDate: zdate().nullable(),
|
||||||
*/
|
/**
|
||||||
overview: z.string().nullable(),
|
* The ending date of this season.
|
||||||
|
*/
|
||||||
/**
|
endDate: zdate().nullable(),
|
||||||
* The starting air date of this season.
|
});
|
||||||
*/
|
|
||||||
startDate: zdate().nullable(),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The ending date of this season.
|
|
||||||
*/
|
|
||||||
endDate: zdate().nullable(),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A season of a Show.
|
* A season of a Show.
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { zdate } from "../utils";
|
import { zdate } from "../utils";
|
||||||
import { ImagesP, ResourceP } from "../traits";
|
import { ImagesP, ResourceP } from "../traits";
|
||||||
import { GenreP } from "./genre";
|
import { Genre } from "./genre";
|
||||||
import { SeasonP } from "./season";
|
import { SeasonP } from "./season";
|
||||||
import { StudioP } from "./studio";
|
import { StudioP } from "./studio";
|
||||||
|
|
||||||
@ -29,60 +29,62 @@ import { StudioP } from "./studio";
|
|||||||
* The enum containing show's status.
|
* The enum containing show's status.
|
||||||
*/
|
*/
|
||||||
export enum Status {
|
export enum Status {
|
||||||
Unknown = 0,
|
Unknown = "Unknown",
|
||||||
Finished = 1,
|
Finished = "Finished",
|
||||||
Airing = 2,
|
Airing = "Airing",
|
||||||
Planned = 3,
|
Planned = "Planned",
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ShowP = z.preprocess(
|
export const ShowP = ResourceP.merge(ImagesP).extend({
|
||||||
(x: any) => {
|
/**
|
||||||
if (!x) return x;
|
* The title of this show.
|
||||||
// Waiting for the API to be updaded
|
*/
|
||||||
x.name = x.title;
|
name: z.string(),
|
||||||
if (x.aliases === null) x.aliases = [];
|
/**
|
||||||
x.trailer = x.images["3"];
|
* A catchphrase for this show.
|
||||||
return x;
|
*/
|
||||||
},
|
tagline: z.string().nullable(),
|
||||||
ResourceP.merge(ImagesP).extend({
|
/**
|
||||||
/**
|
* The list of alternative titles of this show.
|
||||||
* The title of this show.
|
*/
|
||||||
*/
|
aliases: z.array(z.string()),
|
||||||
name: z.string(),
|
/**
|
||||||
/**
|
* The summary of this show.
|
||||||
* The list of alternative titles of this show.
|
*/
|
||||||
*/
|
overview: z.string().nullable(),
|
||||||
aliases: z.array(z.string()),
|
/**
|
||||||
/**
|
* A list of tags that match this movie.
|
||||||
* The summary of this show.
|
*/
|
||||||
*/
|
tags: z.array(z.string()),
|
||||||
overview: z.string().nullable(),
|
/**
|
||||||
/**
|
* Is this show airing, not aired yet or finished?
|
||||||
* Is this show airing, not aired yet or finished?
|
*/
|
||||||
*/
|
status: z.nativeEnum(Status),
|
||||||
status: z.nativeEnum(Status),
|
/**
|
||||||
/**
|
* The date this show started airing. It can be null if this is unknown.
|
||||||
* The date this show started airing. It can be null if this is unknown.
|
*/
|
||||||
*/
|
startAir: zdate().nullable(),
|
||||||
startAir: zdate().nullable(),
|
/**
|
||||||
/**
|
* The date this show finished airing. It can also be null if this is unknown.
|
||||||
* The date this show finished airing. It can also be null if this is unknown.
|
*/
|
||||||
*/
|
endAir: zdate().nullable(),
|
||||||
endAir: zdate().nullable(),
|
/**
|
||||||
/**
|
* The list of genres (themes) this show has.
|
||||||
* The list of genres (themes) this show has.
|
*/
|
||||||
*/
|
genres: z.array(z.nativeEnum(Genre)),
|
||||||
genres: z.array(GenreP).optional(),
|
/**
|
||||||
/**
|
* A youtube url for the trailer.
|
||||||
* The studio that made this show.
|
*/
|
||||||
*/
|
trailer: z.string().optional().nullable(),
|
||||||
studio: StudioP.optional().nullable(),
|
/**
|
||||||
/**
|
* The studio that made this show.
|
||||||
* The list of seasons of this show.
|
*/
|
||||||
*/
|
studio: StudioP.optional().nullable(),
|
||||||
seasons: z.array(SeasonP).optional(),
|
/**
|
||||||
}),
|
* The list of seasons of this show.
|
||||||
);
|
*/
|
||||||
|
seasons: z.array(SeasonP).optional(),
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A tv serie or an anime.
|
* A tv serie or an anime.
|
||||||
|
@ -176,16 +176,6 @@ const WatchEpisodeP = WatchMovieP.and(
|
|||||||
* new season.
|
* new season.
|
||||||
*/
|
*/
|
||||||
absoluteNumber: z.number().nullable(),
|
absoluteNumber: z.number().nullable(),
|
||||||
/**
|
|
||||||
* The episode that come before this one if you follow usual watch orders. If this is the first
|
|
||||||
* episode or this is a movie, it will be null.
|
|
||||||
*/
|
|
||||||
previousEpisode: EpisodeP.nullable(),
|
|
||||||
/**
|
|
||||||
* The episode that come after this one if you follow usual watch orders. If this is the last
|
|
||||||
* aired episode or this is a movie, it will be null.
|
|
||||||
*/
|
|
||||||
nextEpisode: EpisodeP.nullable(),
|
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -27,37 +27,35 @@ export const imageFn = (url: string) =>
|
|||||||
? `/api${url}`
|
? `/api${url}`
|
||||||
: kyooApiUrl + url;
|
: kyooApiUrl + url;
|
||||||
|
|
||||||
|
const Img = z.object({
|
||||||
|
source: z.string(),
|
||||||
|
blurhash: z.string()
|
||||||
|
});
|
||||||
|
|
||||||
export const ImagesP = z.object({
|
export const ImagesP = z.object({
|
||||||
/**
|
/**
|
||||||
* An url to the poster of this resource. If this resource does not have an image, the link will
|
* 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
|
* 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.
|
* resource, this field won't be present.
|
||||||
*/
|
*/
|
||||||
poster: z.string().transform(imageFn).optional().nullable(),
|
poster: Img.nullable(),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An url to the thumbnail of this resource. If this resource does not have an image, the link
|
* 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
|
* 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.
|
* specific resource, this field won't be present.
|
||||||
*/
|
*/
|
||||||
thumbnail: z.string().transform(imageFn).optional().nullable(),
|
thumbnail: Img.nullable(),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An url to the logo of this resource. If this resource does not have an image, the link will be
|
* 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
|
* 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.
|
* resource, this field won't be present.
|
||||||
*/
|
*/
|
||||||
logo: z.string().transform(imageFn).optional().nullable(),
|
logo: 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.
|
|
||||||
*/
|
|
||||||
trailer: z.string().optional().nullable(),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base traits for items that has image resources.
|
* Base traits for items that has image resources.
|
||||||
*/
|
*/
|
||||||
export type Images = z.infer<typeof ImagesP>;
|
export type KyooImage = z.infer<typeof Img>;
|
||||||
|
@ -34,6 +34,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@expo/html-elements": "^0.4.1",
|
"@expo/html-elements": "^0.4.1",
|
||||||
"@tanstack/react-query": "^4.26.1",
|
"@tanstack/react-query": "^4.26.1",
|
||||||
|
"expo-image": "^1.3.2",
|
||||||
"solito": "^3.0.0"
|
"solito": "^3.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,10 +18,10 @@
|
|||||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { KyooImage } from "@kyoo/models";
|
||||||
import { ComponentType, ReactNode, useState } from "react";
|
import { ComponentType, ReactNode, useState } from "react";
|
||||||
import {
|
import {
|
||||||
View,
|
View,
|
||||||
Image as Img,
|
|
||||||
ImageSourcePropType,
|
ImageSourcePropType,
|
||||||
ImageStyle,
|
ImageStyle,
|
||||||
Platform,
|
Platform,
|
||||||
@ -29,6 +29,7 @@ import {
|
|||||||
ViewProps,
|
ViewProps,
|
||||||
ViewStyle,
|
ViewStyle,
|
||||||
} from "react-native";
|
} from "react-native";
|
||||||
|
import {Image as Img} from "expo-image"
|
||||||
import { percent, useYoshiki } from "yoshiki/native";
|
import { percent, useYoshiki } from "yoshiki/native";
|
||||||
import { YoshikiStyle } from "yoshiki/dist/type";
|
import { YoshikiStyle } from "yoshiki/dist/type";
|
||||||
import { Skeleton } from "./skeleton";
|
import { Skeleton } from "./skeleton";
|
||||||
@ -44,14 +45,14 @@ type YoshikiEnhanced<Style> = Style extends any
|
|||||||
type WithLoading<T> = (T & { isLoading?: boolean }) | (Partial<T> & { isLoading: true });
|
type WithLoading<T> = (T & { isLoading?: boolean }) | (Partial<T> & { isLoading: true });
|
||||||
|
|
||||||
type Props = WithLoading<{
|
type Props = WithLoading<{
|
||||||
src?: string | ImageSourcePropType | null;
|
src?: KyooImage | null;
|
||||||
alt?: string;
|
alt?: string;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
type ImageLayout = YoshikiEnhanced<
|
type ImageLayout = YoshikiEnhanced<
|
||||||
| { width: ViewStyle["width"]; height: ViewStyle["height"] }
|
| { width: ImageStyle["width"]; height: ImageStyle["height"] }
|
||||||
| { width: ViewStyle["width"]; aspectRatio: ViewStyle["aspectRatio"] }
|
| { width: ImageStyle["width"]; aspectRatio: ImageStyle["aspectRatio"] }
|
||||||
| { height: ViewStyle["height"]; aspectRatio: ViewStyle["aspectRatio"] }
|
| { height: ImageStyle["height"]; aspectRatio: ImageStyle["aspectRatio"] }
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export const Image = ({
|
export const Image = ({
|
||||||
@ -62,49 +63,67 @@ export const Image = ({
|
|||||||
...props
|
...props
|
||||||
}: Props & { style?: ViewStyle } & { layout: ImageLayout }) => {
|
}: Props & { style?: ViewStyle } & { layout: ImageLayout }) => {
|
||||||
const { css } = useYoshiki();
|
const { css } = useYoshiki();
|
||||||
const [state, setState] = useState<"loading" | "errored" | "finished">(
|
console.log(src);
|
||||||
src ? "loading" : "errored",
|
|
||||||
);
|
|
||||||
|
|
||||||
// This could be done with a key but this makes the API easier to use.
|
|
||||||
// This unsures that the state is resetted when the source change (useful for recycler lists.)
|
|
||||||
const [oldSource, setOldSource] = useState(src);
|
|
||||||
if (oldSource !== src) {
|
|
||||||
setState("loading");
|
|
||||||
setOldSource(src);
|
|
||||||
}
|
|
||||||
|
|
||||||
const border = { borderRadius: 6 } satisfies ViewStyle;
|
|
||||||
|
|
||||||
if (forcedLoading) return <Skeleton variant="custom" {...css([layout, border], props)} />;
|
|
||||||
if (!src || state === "errored")
|
|
||||||
return <View {...css([{ bg: (theme) => theme.overlay0 }, layout, border], props)} />;
|
|
||||||
|
|
||||||
const nativeProps = Platform.select<Partial<ImageProps>>({
|
|
||||||
web: {
|
|
||||||
defaultSource: typeof src === "string" ? { uri: src } : Array.isArray(src) ? src[0] : src,
|
|
||||||
},
|
|
||||||
default: {},
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Skeleton variant="custom" show={state === "loading"} {...css([layout, border], props)}>
|
<Img
|
||||||
<Img
|
source={src?.source}
|
||||||
source={typeof src === "string" ? { uri: src } : src}
|
placeholder={src?.blurhash}
|
||||||
accessibilityLabel={alt}
|
accessibilityLabel={alt}
|
||||||
onLoad={() => setState("finished")}
|
{...css([
|
||||||
onError={() => setState("errored")}
|
layout,
|
||||||
{...nativeProps}
|
// {
|
||||||
{...css([
|
// // width: percent(100),
|
||||||
{
|
// // height: percent(100),
|
||||||
width: percent(100),
|
// // resizeMode: "cover",
|
||||||
height: percent(100),
|
// borderRadius: 6
|
||||||
resizeMode: "cover",
|
// },
|
||||||
},
|
]) as ImageStyle}
|
||||||
])}
|
/>
|
||||||
/>
|
|
||||||
</Skeleton>
|
|
||||||
);
|
);
|
||||||
|
// const [state, setState] = useState<"loading" | "errored" | "finished">(
|
||||||
|
// src ? "loading" : "errored",
|
||||||
|
// );
|
||||||
|
//
|
||||||
|
// // This could be done with a key but this makes the API easier to use.
|
||||||
|
// // This unsures that the state is resetted when the source change (useful for recycler lists.)
|
||||||
|
// const [oldSource, setOldSource] = useState(src);
|
||||||
|
// if (oldSource !== src) {
|
||||||
|
// setState("loading");
|
||||||
|
// setOldSource(src);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// const border = { borderRadius: 6 } satisfies ViewStyle;
|
||||||
|
//
|
||||||
|
// if (forcedLoading) return <Skeleton variant="custom" {...css([layout, border], props)} />;
|
||||||
|
// if (!src || state === "errored")
|
||||||
|
// return <View {...css([{ bg: (theme) => theme.overlay0 }, layout, border], props)} />;
|
||||||
|
//
|
||||||
|
// const nativeProps = Platform.select<Partial<ImageProps>>({
|
||||||
|
// web: {
|
||||||
|
// defaultSource: typeof src === "string" ? { uri: src } : Array.isArray(src) ? src[0] : src,
|
||||||
|
// },
|
||||||
|
// default: {},
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
// return (
|
||||||
|
// <Skeleton variant="custom" show={state === "loading"} {...css([layout, border], props)}>
|
||||||
|
// <Img
|
||||||
|
// source={typeof src === "string" ? { uri: src } : src}
|
||||||
|
// accessibilityLabel={alt}
|
||||||
|
// onLoad={() => setState("finished")}
|
||||||
|
// onError={() => setState("errored")}
|
||||||
|
// {...nativeProps}
|
||||||
|
// {...css([
|
||||||
|
// {
|
||||||
|
// width: percent(100),
|
||||||
|
// height: percent(100),
|
||||||
|
// resizeMode: "cover",
|
||||||
|
// },
|
||||||
|
// ])}
|
||||||
|
// />
|
||||||
|
// </Skeleton>
|
||||||
|
// );
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Poster = ({
|
export const Poster = ({
|
||||||
|
@ -5,7 +5,8 @@
|
|||||||
"packageManager": "yarn@3.2.4",
|
"packageManager": "yarn@3.2.4",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@kyoo/models": "workspace:^",
|
"@kyoo/models": "workspace:^",
|
||||||
"@kyoo/primitives": "workspace:^"
|
"@kyoo/primitives": "workspace:^",
|
||||||
|
"expo-image": "^1.3.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@shopify/flash-list": "^1.4.1",
|
"@shopify/flash-list": "^1.4.1",
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { KyooImage } from "@kyoo/models";
|
||||||
import { Link, Skeleton, Poster, ts, focusReset, P, SubP } from "@kyoo/primitives";
|
import { Link, Skeleton, Poster, ts, focusReset, P, SubP } from "@kyoo/primitives";
|
||||||
import { Platform } from "react-native";
|
import { Platform } from "react-native";
|
||||||
import { percent, px, Stylable, useYoshiki } from "yoshiki/native";
|
import { percent, px, Stylable, useYoshiki } from "yoshiki/native";
|
||||||
@ -34,7 +35,7 @@ export const ItemGrid = ({
|
|||||||
href: string;
|
href: string;
|
||||||
name: string;
|
name: string;
|
||||||
subtitle?: string;
|
subtitle?: string;
|
||||||
poster?: string | null;
|
poster?: KyooImage | null;
|
||||||
}> &
|
}> &
|
||||||
Stylable<"text">) => {
|
Stylable<"text">) => {
|
||||||
const { css } = useYoshiki("grid");
|
const { css } = useYoshiki("grid");
|
||||||
|
@ -23,7 +23,7 @@ import {
|
|||||||
QueryPage,
|
QueryPage,
|
||||||
LibraryItem,
|
LibraryItem,
|
||||||
LibraryItemP,
|
LibraryItemP,
|
||||||
ItemType,
|
ItemKind,
|
||||||
getDisplayDate,
|
getDisplayDate,
|
||||||
} from "@kyoo/models";
|
} from "@kyoo/models";
|
||||||
import { ComponentProps, useState } from "react";
|
import { ComponentProps, useState } from "react";
|
||||||
@ -44,14 +44,14 @@ export const itemMap = (
|
|||||||
if (item.isLoading) return item;
|
if (item.isLoading) return item;
|
||||||
|
|
||||||
let href;
|
let href;
|
||||||
if (item?.type === ItemType.Movie) href = `/movie/${item.slug}`;
|
if (item?.kind === ItemKind.Movie) href = `/movie/${item.slug}`;
|
||||||
else if (item?.type === ItemType.Show) href = `/show/${item.slug}`;
|
else if (item?.kind === ItemKind.Show) href = `/show/${item.slug}`;
|
||||||
else href = `/collection/${item.slug}`;
|
else href = `/collection/${item.slug}`;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isLoading: item.isLoading,
|
isLoading: item.isLoading,
|
||||||
name: item.name,
|
name: item.name,
|
||||||
subtitle: item.type !== ItemType.Collection ? getDisplayDate(item) : undefined,
|
subtitle: item.kind !== ItemKind.Collection ? getDisplayDate(item) : undefined,
|
||||||
href,
|
href,
|
||||||
poster: item.poster,
|
poster: item.poster,
|
||||||
thumbnail: item.thumbnail,
|
thumbnail: item.thumbnail,
|
||||||
@ -67,10 +67,9 @@ const query = (
|
|||||||
path: slug ? ["library", slug, "items"] : ["items"],
|
path: slug ? ["library", slug, "items"] : ["items"],
|
||||||
infinite: true,
|
infinite: true,
|
||||||
params: {
|
params: {
|
||||||
// The API still uses title isntead of name
|
|
||||||
sortBy: sortKey
|
sortBy: sortKey
|
||||||
? `${sortKey === SortBy.Name ? "title" : sortKey}:${sortOrd ?? "asc"}`
|
? `${sortKey}:${sortOrd ?? "asc"}`
|
||||||
: "title:asc",
|
: "namek:asc",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { KyooImage } from "@kyoo/models";
|
||||||
import { Link, P, Skeleton, ts, ImageBackground, Poster, Heading } from "@kyoo/primitives";
|
import { Link, P, Skeleton, ts, ImageBackground, Poster, Heading } from "@kyoo/primitives";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { View } from "react-native";
|
import { View } from "react-native";
|
||||||
@ -35,8 +36,8 @@ export const ItemList = ({
|
|||||||
href: string;
|
href: string;
|
||||||
name: string;
|
name: string;
|
||||||
subtitle?: string;
|
subtitle?: string;
|
||||||
poster?: string | null;
|
poster?: KyooImage | null;
|
||||||
thumbnail?: string | null;
|
thumbnail?: KyooImage | null;
|
||||||
}>) => {
|
}>) => {
|
||||||
const { css } = useYoshiki();
|
const { css } = useYoshiki();
|
||||||
const [isHovered, setHovered] = useState(0);
|
const [isHovered, setHovered] = useState(0);
|
||||||
|
@ -2344,6 +2344,7 @@ __metadata:
|
|||||||
"@gorhom/portal": ^1.0.14
|
"@gorhom/portal": ^1.0.14
|
||||||
"@tanstack/react-query": ^4.26.1
|
"@tanstack/react-query": ^4.26.1
|
||||||
"@types/react": ^18.0.28
|
"@types/react": ^18.0.28
|
||||||
|
expo-image: ^1.3.2
|
||||||
solito: ^3.0.0
|
solito: ^3.0.0
|
||||||
typescript: ^4.9.5
|
typescript: ^4.9.5
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -2375,6 +2376,7 @@ __metadata:
|
|||||||
"@kyoo/primitives": "workspace:^"
|
"@kyoo/primitives": "workspace:^"
|
||||||
"@shopify/flash-list": ^1.4.1
|
"@shopify/flash-list": ^1.4.1
|
||||||
"@types/react": ^18.0.28
|
"@types/react": ^18.0.28
|
||||||
|
expo-image: ^1.3.2
|
||||||
react-native-uuid: ^2.0.1
|
react-native-uuid: ^2.0.1
|
||||||
typescript: ^4.9.5
|
typescript: ^4.9.5
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -6830,6 +6832,24 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"expo-image@npm:^1.3.2":
|
||||||
|
version: 1.4.1
|
||||||
|
resolution: "expo-image@npm:1.4.1"
|
||||||
|
peerDependencies:
|
||||||
|
expo: "*"
|
||||||
|
checksum: 8eda22cba8ec12f647532b5562913c33adb2e206a4cea426e2d7325c34ab7b3fcb2d1effbf4e0b5df685dfd156e435d4429873a1ceb966e73885944cb6634005
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"expo-image@npm:~1.0.0":
|
||||||
|
version: 1.0.1
|
||||||
|
resolution: "expo-image@npm:1.0.1"
|
||||||
|
peerDependencies:
|
||||||
|
expo: "*"
|
||||||
|
checksum: 4370c442df51dda577e2e1a5daf67267d9336fd89afb2b8ee6b0f74b4fdffd9a3b71a5f5580583310d1a71610eabba9ec40d5cf007a01ac6d8a6200ac8ee6a21
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"expo-json-utils@npm:~0.5.0":
|
"expo-json-utils@npm:~0.5.0":
|
||||||
version: 0.5.1
|
version: 0.5.1
|
||||||
resolution: "expo-json-utils@npm:0.5.1"
|
resolution: "expo-json-utils@npm:0.5.1"
|
||||||
@ -6913,6 +6933,16 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"expo-modules-core@npm:^1.5.9":
|
||||||
|
version: 1.6.0
|
||||||
|
resolution: "expo-modules-core@npm:1.6.0"
|
||||||
|
dependencies:
|
||||||
|
compare-versions: ^3.4.0
|
||||||
|
invariant: ^2.2.4
|
||||||
|
checksum: 7e3d697cdeb7e1246216f9f4024f16d1a2966805b8cb46fd938cec1feec5bffb5aa166f0b25a6edb5bdf881ca318e142957d25ab4fb48420d8f060fc3742f4a4
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"expo-navigation-bar@npm:~2.1.1":
|
"expo-navigation-bar@npm:~2.1.1":
|
||||||
version: 2.1.1
|
version: 2.1.1
|
||||||
resolution: "expo-navigation-bar@npm:2.1.1"
|
resolution: "expo-navigation-bar@npm:2.1.1"
|
||||||
@ -10386,6 +10416,7 @@ __metadata:
|
|||||||
expo-constants: ~14.2.1
|
expo-constants: ~14.2.1
|
||||||
expo-dev-client: ~2.2.1
|
expo-dev-client: ~2.2.1
|
||||||
expo-font: ~11.1.1
|
expo-font: ~11.1.1
|
||||||
|
expo-image: ~1.0.0
|
||||||
expo-linear-gradient: ~12.1.2
|
expo-linear-gradient: ~12.1.2
|
||||||
expo-linking: ~4.0.1
|
expo-linking: ~4.0.1
|
||||||
expo-localization: ~14.1.1
|
expo-localization: ~14.1.1
|
||||||
@ -14194,7 +14225,9 @@ __metadata:
|
|||||||
copy-webpack-plugin: ^11.0.0
|
copy-webpack-plugin: ^11.0.0
|
||||||
eslint: ^8.36.0
|
eslint: ^8.36.0
|
||||||
eslint-config-next: 13.2.4
|
eslint-config-next: 13.2.4
|
||||||
|
expo-image: ^1.3.2
|
||||||
expo-linear-gradient: ^12.1.2
|
expo-linear-gradient: ^12.1.2
|
||||||
|
expo-modules-core: ^1.5.9
|
||||||
hls.js: ^1.3.4
|
hls.js: ^1.3.4
|
||||||
i18next: ^22.4.11
|
i18next: ^22.4.11
|
||||||
jotai: ^2.0.3
|
jotai: ^2.0.3
|
||||||
|
Loading…
x
Reference in New Issue
Block a user