mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-07-09 03:04:20 -04:00
Add show details page
This commit is contained in:
parent
dfe6fa7cda
commit
9b80b340e3
9
front/locales/en/browse.json
Normal file
9
front/locales/en/browse.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"show": {
|
||||
"play": "Play",
|
||||
"trailer": "Play Trailer",
|
||||
"studio": "Studio",
|
||||
"genre": "Genres",
|
||||
"genre-none": "No genres"
|
||||
}
|
||||
}
|
9
front/locales/fr/browse.json
Normal file
9
front/locales/fr/browse.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"show": {
|
||||
"play": "Lecture",
|
||||
"trailer": "Jouer le trailer",
|
||||
"studio": "Studio",
|
||||
"genre": "Genres",
|
||||
"genre-none": "Aucun genres"
|
||||
}
|
||||
}
|
@ -24,16 +24,6 @@
|
||||
const nextConfig = {
|
||||
reactStrictMode: true,
|
||||
swcMinify: true,
|
||||
experimental: {
|
||||
swcPlugins: [
|
||||
[
|
||||
"next-superjson-plugin",
|
||||
{
|
||||
excluded: [],
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
env: {
|
||||
KYOO_URL: process.env.KYOO_URL ?? "http://localhost:5000",
|
||||
},
|
||||
|
@ -26,11 +26,11 @@
|
||||
"@mui/icons-material": "^5.8.4",
|
||||
"@mui/material": "^5.8.7",
|
||||
"next": "12.2.2",
|
||||
"next-superjson-plugin": "^0.3.0",
|
||||
"next-translate": "^1.5.0",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-query": "^4.0.0-beta.23",
|
||||
"superjson": "^1.9.1",
|
||||
"zod": "^3.18.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
33
front/src/components/container.tsx
Normal file
33
front/src/components/container.tsx
Normal file
@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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 { styled, experimental_sx as sx } from "@mui/system";
|
||||
|
||||
export const Container = styled("div")(sx({
|
||||
display: "flex",
|
||||
pl: "15px",
|
||||
pr: "15px",
|
||||
mx: "auto",
|
||||
width: {
|
||||
sm: "540px",
|
||||
md: "880px",
|
||||
lg: "1170px",
|
||||
},
|
||||
}));
|
@ -64,7 +64,7 @@ export const KyooTitle = (props: { sx: SxProps<Theme> }) => {
|
||||
mr: 2,
|
||||
fontFamily: "monospace",
|
||||
fontWeight: 700,
|
||||
color: "white",
|
||||
color: "white"
|
||||
}}
|
||||
>
|
||||
Kyoo
|
||||
@ -84,7 +84,7 @@ export const Navbar = (barProps: AppBarProps) => {
|
||||
const { data, error, isSuccess, isError } = useFetch(NavbarQuery);
|
||||
|
||||
return (
|
||||
<AppBar position="sticky">
|
||||
<AppBar position="sticky" {...barProps}>
|
||||
<Toolbar>
|
||||
<IconButton
|
||||
size="large"
|
||||
@ -118,7 +118,7 @@ export const Navbar = (barProps: AppBarProps) => {
|
||||
</Box>
|
||||
<Tooltip title={t("navbar.login")}>
|
||||
<IconButton sx={{ p: 0 }} href="/auth/login">
|
||||
<Avatar alt={t("navbar.login")} />
|
||||
<Avatar alt={t("navbar.login")}/>
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</Toolbar>
|
||||
|
@ -19,15 +19,11 @@
|
||||
*/
|
||||
|
||||
import { Box, Skeleton, styled } from "@mui/material";
|
||||
import {
|
||||
SyntheticEvent,
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import { SyntheticEvent, useEffect, useLayoutEffect, useRef, useState } from "react";
|
||||
import { ComponentsOverrides, ComponentsProps, ComponentsVariants } from "@mui/material";
|
||||
import { withThemeProps } from "~/utils/with-theme";
|
||||
import type { Property } from "csstype";
|
||||
import { ResponsiveStyleValue } from "@mui/system";
|
||||
|
||||
type ImageOptions = {
|
||||
radius?: string;
|
||||
@ -35,14 +31,17 @@ type ImageOptions = {
|
||||
};
|
||||
|
||||
type ImageProps = {
|
||||
img?: string;
|
||||
img?: string | null;
|
||||
alt: string;
|
||||
} & ImageOptions;
|
||||
|
||||
type ImagePropsWithLoading =
|
||||
| (ImageProps & { loading?: false })
|
||||
| (ImageProps & { loading?: boolean })
|
||||
| (Partial<ImageProps> & { loading: true });
|
||||
|
||||
type Width = ResponsiveStyleValue<Property.Width<(string & {}) | 0>>;
|
||||
type Height = ResponsiveStyleValue<Property.Height<(string & {}) | 0>>;
|
||||
|
||||
const _Image = ({
|
||||
img,
|
||||
alt,
|
||||
@ -55,8 +54,8 @@ const _Image = ({
|
||||
...others
|
||||
}: ImagePropsWithLoading &
|
||||
(
|
||||
| { aspectRatio?: string; width: string | number; height: string | number }
|
||||
| { aspectRatio: string; width?: string | number; height?: string | number }
|
||||
| { aspectRatio?: string; width: Width; height: Height }
|
||||
| { aspectRatio: string; width?: Width; height?: Height }
|
||||
)) => {
|
||||
const [showLoading, setLoading] = useState<boolean>(loading);
|
||||
const imgRef = useRef<HTMLImageElement>(null);
|
||||
@ -101,7 +100,9 @@ const _Image = ({
|
||||
export const Image = styled(_Image)({});
|
||||
|
||||
// eslint-disable-next-line jsx-a11y/alt-text
|
||||
const _Poster = (props: ImagePropsWithLoading) => <_Image aspectRatio="2 / 3" {...props} />;
|
||||
const _Poster = (
|
||||
props: ImagePropsWithLoading & { width?: Width; height?: Height },
|
||||
) => <_Image aspectRatio="2 / 3" {...props} />;
|
||||
|
||||
declare module "@mui/material/styles" {
|
||||
interface ComponentsPropsList {
|
||||
|
@ -1,3 +0,0 @@
|
||||
body {
|
||||
margin: 0 !important;
|
||||
}
|
@ -43,7 +43,7 @@ export interface Page<T> {
|
||||
*
|
||||
* @format uri
|
||||
*/
|
||||
next?: string;
|
||||
next: string | null;
|
||||
|
||||
/**
|
||||
* The number of items in the current page.
|
||||
@ -60,7 +60,7 @@ export const Paged = <Item>(item: z.ZodType<Item>): z.ZodSchema<Page<Item>> =>
|
||||
z.object({
|
||||
this: z.string(),
|
||||
first: z.string(),
|
||||
next: z.string().optional(),
|
||||
next: z.string().nullable(),
|
||||
count: z.number(),
|
||||
items: z.array(item),
|
||||
});
|
||||
|
@ -22,6 +22,7 @@ import { z } from "zod";
|
||||
import { zdate } from "~/utils/zod";
|
||||
import { ImagesP, ResourceP } from "../traits";
|
||||
import { GenreP } from "./genre";
|
||||
import { StudioP } from "./studio";
|
||||
|
||||
/**
|
||||
* The enum containing show's status.
|
||||
@ -58,7 +59,7 @@ export const ShowP = z.preprocess(
|
||||
/**
|
||||
* The date this show started airing. It can be null if this is unknown.
|
||||
*/
|
||||
startAir: zdate().optional(),
|
||||
startAir: zdate().nullable(),
|
||||
/**
|
||||
* The date this show finished airing. It can also be null if this is unknown.
|
||||
*/
|
||||
@ -67,6 +68,10 @@ export const ShowP = z.preprocess(
|
||||
* The list of genres (themes) this show has.
|
||||
*/
|
||||
genres: z.array(GenreP).optional(),
|
||||
/**
|
||||
* The studio that made this show.
|
||||
*/
|
||||
studio: StudioP.optional(),
|
||||
}),
|
||||
);
|
||||
|
||||
|
34
front/src/models/resources/studio.ts
Normal file
34
front/src/models/resources/studio.ts
Normal file
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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";
|
||||
|
||||
export const StudioP = ResourceP.extend({
|
||||
/**
|
||||
* The name of this studio.
|
||||
*/
|
||||
name: z.string(),
|
||||
});
|
||||
|
||||
/**
|
||||
* A studio that make shows.
|
||||
*/
|
||||
export type Studio = z.infer<typeof StudioP>;
|
@ -28,33 +28,31 @@ export const ImagesP = z.object({
|
||||
* 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: z.string().transform(imageFn).optional(),
|
||||
poster: z.string().transform(imageFn).optional().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: z.string().transform(imageFn).optional(),
|
||||
thumbnail: z.string().transform(imageFn).optional().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: z.string().transform(imageFn).optional(),
|
||||
logo: z.string().transform(imageFn).optional().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().transform(imageFn).optional(),
|
||||
trailer: z.string().transform(imageFn).optional().nullable(),
|
||||
});
|
||||
|
||||
/**
|
||||
* Base traits for items that has image resources.
|
||||
*/
|
||||
export type Images = z.infer<typeof ImagesP>;
|
||||
|
||||
export const imageList = ["poster", "thumbnail", "logo", "trailer"];
|
||||
|
26
front/src/models/utils.ts
Normal file
26
front/src/models/utils.ts
Normal file
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
export const getDisplayDate = (startAir: Date, endAir?: Date | null) => {
|
||||
if (!endAir || startAir.getFullYear() === endAir.getFullYear()) {
|
||||
return startAir.getFullYear();
|
||||
}
|
||||
return startAir.getFullYear() + (endAir ? ` - ${endAir.getFullYear()}` : "");
|
||||
};
|
@ -18,7 +18,7 @@
|
||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { useState } from "react";
|
||||
import React, { useState } from "react";
|
||||
import appWithI18n from "next-translate/appWithI18n";
|
||||
import { ThemeProvider } from "@mui/material";
|
||||
import NextApp, { AppContext } from "next/app";
|
||||
@ -27,13 +27,18 @@ import { Hydrate, QueryClientProvider } from "react-query";
|
||||
import { createQueryClient, fetchQuery, QueryIdentifier, QueryPage } from "~/utils/query";
|
||||
import { defaultTheme } from "~/utils/themes/default-theme";
|
||||
import { Navbar, NavbarQuery } from "~/components/navbar";
|
||||
import "../global.css";
|
||||
import { Box } from "@mui/system";
|
||||
import superjson from "superjson";
|
||||
|
||||
// Simply silence a SSR warning (see https://github.com/facebook/react/issues/14927 for more details)
|
||||
if (typeof window === "undefined") {
|
||||
React.useLayoutEffect = React.useEffect;
|
||||
}
|
||||
|
||||
const AppWithNavbar = ({ children }: { children: JSX.Element }) => {
|
||||
return (
|
||||
<>
|
||||
<Navbar/>
|
||||
{/* <Navbar /> */}
|
||||
{/* TODO: add an option to disable the navbar in the component */}
|
||||
<Box>{children}</Box>
|
||||
</>
|
||||
@ -42,19 +47,27 @@ const AppWithNavbar = ({ children }: { children: JSX.Element }) => {
|
||||
|
||||
const App = ({ Component, pageProps }: AppProps) => {
|
||||
const [queryClient] = useState(() => createQueryClient());
|
||||
const { queryState, ...props } = pageProps;
|
||||
const { queryState, ...props } = superjson.deserialize<any>(pageProps ?? {});
|
||||
|
||||
// TODO: tranform date string to date instances in the queryState
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Hydrate state={queryState}>
|
||||
<ThemeProvider theme={defaultTheme}>
|
||||
<AppWithNavbar>
|
||||
<Component {...props} />
|
||||
</AppWithNavbar>
|
||||
</ThemeProvider>
|
||||
</Hydrate>
|
||||
</QueryClientProvider>
|
||||
<>
|
||||
<style jsx global>{`
|
||||
body {
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
`}</style>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Hydrate state={queryState}>
|
||||
<ThemeProvider theme={defaultTheme}>
|
||||
<AppWithNavbar>
|
||||
<Component {...props} />
|
||||
</AppWithNavbar>
|
||||
</ThemeProvider>
|
||||
</Hydrate>
|
||||
</QueryClientProvider>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@ -67,7 +80,7 @@ App.getInitialProps = async (ctx: AppContext) => {
|
||||
urls.push(NavbarQuery);
|
||||
appProps.pageProps.queryState = await fetchQuery(urls);
|
||||
|
||||
return appProps;
|
||||
return { pageProps: superjson.serialize(appProps.pageProps) };
|
||||
};
|
||||
|
||||
// The as any is needed since appWithI18n as wrong type hints
|
||||
@ -77,7 +90,7 @@ export default appWithI18n(App as any, {
|
||||
defaultLocale: "en",
|
||||
loader: false,
|
||||
pages: {
|
||||
"*": ["common"],
|
||||
"*": ["common", "browse"],
|
||||
},
|
||||
loadLocaleFrom: (locale, namespace) =>
|
||||
import(`../../locales/${locale}/${namespace}`).then((m) => m.default),
|
||||
|
@ -18,20 +18,228 @@
|
||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Box, Typography } from "@mui/material";
|
||||
import { LocalMovies, PlayArrow } from "@mui/icons-material";
|
||||
import {
|
||||
alpha,
|
||||
Box,
|
||||
Divider,
|
||||
Fab,
|
||||
IconButton,
|
||||
Skeleton,
|
||||
SxProps,
|
||||
Tooltip,
|
||||
Typography,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import useTranslation from "next-translate/useTranslation";
|
||||
import Head from "next/head";
|
||||
import { Navbar } from "~/components/navbar";
|
||||
import { Image, Poster } from "~/components/poster";
|
||||
import { Show, ShowP } from "~/models";
|
||||
import { QueryIdentifier, QueryPage, useFetch } from "~/utils/query";
|
||||
import { getDisplayDate } from "~/models/utils";
|
||||
import { useScroll } from "~/utils/hooks/use-scroll";
|
||||
import { withRoute } from "~/utils/router";
|
||||
import { Container } from "~/components/container";
|
||||
import { makeTitle } from "~/utils/utils";
|
||||
import { Link } from "~/utils/link";
|
||||
import { Studio } from "~/models/resources/studio";
|
||||
|
||||
const StudioText = ({
|
||||
studio,
|
||||
loading = false,
|
||||
sx,
|
||||
}: {
|
||||
studio?: Studio;
|
||||
loading?: boolean;
|
||||
sx?: SxProps;
|
||||
}) => {
|
||||
const { t } = useTranslation("browse");
|
||||
|
||||
if (!loading && !studio) return null;
|
||||
return (
|
||||
<Typography sx={sx}>
|
||||
{t("show.studio")}:{" "}
|
||||
{loading ? (
|
||||
<Skeleton width="5rem" sx={{ display: "inline-flex" }} />
|
||||
) : (
|
||||
<Link href={`/studio/${studio!.slug}`}>{studio!.name}</Link>
|
||||
)}
|
||||
</Typography>
|
||||
);
|
||||
};
|
||||
|
||||
const ShowHeader = ({ data }: { data?: Show }) => {
|
||||
/* const scroll = useScroll(); */
|
||||
const { t } = useTranslation("browse");
|
||||
console.log(data);
|
||||
// TODO: tweek the navbar color with the theme.
|
||||
|
||||
const ShowHeader = (data: Show) => {
|
||||
return (
|
||||
<>
|
||||
<Image img={data.thumbnail} alt="" height="60vh" width="100%" sx={{ positon: "relative" }} />
|
||||
<Poster img={data.poster} alt={`${data.name}`} />
|
||||
<Typography variant="h1" component="h1">
|
||||
{data.name}
|
||||
</Typography>
|
||||
{/* TODO: Add a shadow on navbar items */}
|
||||
{/* TODO: Put the navbar outside of the scrollbox */}
|
||||
<Navbar
|
||||
position="fixed"
|
||||
sx={{ backgroundColor: `rgba(0, 0, 0, ${0 /*0.4 + scroll / 1000*/})` }}
|
||||
/>
|
||||
<Image
|
||||
img={data?.thumbnail}
|
||||
alt=""
|
||||
loading={!data}
|
||||
width="100%"
|
||||
height={{ xs: "40vh", sm: "60vh", lg: "70vh" }}
|
||||
sx={{
|
||||
minHeight: { xs: "350px", sm: "400px", lg: "550px" },
|
||||
position: "relative",
|
||||
"&::after": {
|
||||
content: '""',
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
left: 0,
|
||||
background: "linear-gradient(to bottom, rgba(0, 0, 0, 0) 50%, rgba(0, 0, 0, 0.6) 100%)",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
<Container
|
||||
sx={{
|
||||
position: "relative",
|
||||
marginTop: { xs: "-30%", sm: "-25%", md: "-15rem", lg: "-21rem", xl: "-23rem" },
|
||||
display: "flex",
|
||||
flexDirection: { xs: "column", sm: "row" },
|
||||
alignItems: { xs: "center", sm: "unset" },
|
||||
textAlign: { xs: "center", sm: "unset" },
|
||||
}}
|
||||
>
|
||||
<Poster
|
||||
img={data?.poster}
|
||||
alt={data?.name ?? ""}
|
||||
loading={!data}
|
||||
width={{ xs: "50%", md: "25%" }}
|
||||
sx={{ maxWidth: { xs: "175px", sm: "unset" }, flexShrink: 0 }}
|
||||
/>
|
||||
<Box sx={{ alignSelf: { xs: "center", sm: "end", md: "center" }, pl: { sm: "2.5rem" } }}>
|
||||
<Typography
|
||||
variant="h3"
|
||||
component="h1"
|
||||
sx={{
|
||||
color: { md: "white" },
|
||||
fontWeight: { md: 900 },
|
||||
mb: ".5rem",
|
||||
}}
|
||||
>
|
||||
{data?.name ?? <Skeleton width="15rem" />}
|
||||
</Typography>
|
||||
{(!data || data.startAir) && (
|
||||
<Typography variant="h5" sx={{ color: { md: "white" }, fontWeight: 300, mb: ".5rem" }}>
|
||||
{data != undefined ? (
|
||||
getDisplayDate(data.startAir!, data.endAir)
|
||||
) : (
|
||||
<Skeleton width="5rem" sx={{ mx: { xs: "auto", sm: "unset" } }} />
|
||||
)}
|
||||
</Typography>
|
||||
)}
|
||||
<Box sx={{ "& > *": { m: ".3rem !important" } }}>
|
||||
<Tooltip title={t("show.play")}>
|
||||
<Fab color="primary" size="small" aria-label={t("show.play")}>
|
||||
<PlayArrow />
|
||||
</Fab>
|
||||
</Tooltip>
|
||||
<Tooltip title={t("show.trailer")} aria-label={t("show.trailer")}>
|
||||
<IconButton>
|
||||
<LocalMovies sx={{ color: { md: "white" } }} />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: { xs: "none", md: "flex" },
|
||||
position: "absolute",
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
width: "25%",
|
||||
flexDirection: "column",
|
||||
alignSelf: "end",
|
||||
pr: "15px",
|
||||
}}
|
||||
>
|
||||
{data?.logo && (
|
||||
<Image
|
||||
img={data.logo}
|
||||
alt=""
|
||||
width="100%"
|
||||
height="100px"
|
||||
sx={{ display: { xs: "none", lg: "unset" } }}
|
||||
/>
|
||||
)}
|
||||
<StudioText loading={!data} studio={data?.studio} sx={{ mt: "auto", mb: 3 }} />
|
||||
</Box>
|
||||
</Container>
|
||||
|
||||
<Container sx={{ display: { xs: "block", sm: "none" }, pt: 3 }}>
|
||||
<StudioText loading={!data} studio={data?.studio} sx={{ mb: 1 }} />
|
||||
<Typography sx={{ mb: 1 }}>
|
||||
{t("show.genre")}
|
||||
{": "}
|
||||
{!data ? (
|
||||
<Skeleton width="10rem" sx={{ display: "inline-flex" }} />
|
||||
) : data?.genres ? (
|
||||
data.genres.map((genre, i) => [
|
||||
i > 0 && ", ",
|
||||
<Link key={genre.id} href={`/genres/${genre.slug}`}>
|
||||
{genre.name}
|
||||
</Link>,
|
||||
])
|
||||
) : (
|
||||
t("show.genre-none")
|
||||
)}
|
||||
</Typography>
|
||||
</Container>
|
||||
|
||||
<Container sx={{ pt: 2 }}>
|
||||
<Typography align="justify" sx={{ flexBasis: 0, flexGrow: 1, pt: { sm: 2 } }}>
|
||||
{data?.overview ?? [...Array(4)].map((_, i) => <Skeleton key={i} />)}
|
||||
</Typography>
|
||||
<Divider
|
||||
orientation="vertical"
|
||||
variant="middle"
|
||||
flexItem
|
||||
sx={{ mx: 2, display: { xs: "none", sm: "block" } }}
|
||||
/>
|
||||
<Box sx={{ flexBasis: "25%", display: { xs: "none", sm: "block" } }}>
|
||||
<StudioText
|
||||
loading={!data}
|
||||
studio={data?.studio}
|
||||
sx={{ display: { xs: "none", sm: "block", md: "none" }, pb: 2 }}
|
||||
/>
|
||||
|
||||
<Typography variant="h4" component="h2">
|
||||
{t("show.genre")}
|
||||
</Typography>
|
||||
{!data || data.genres ? (
|
||||
<ul>
|
||||
{(data ? data.genres! : [...Array(3)]).map((genre, i) => (
|
||||
<li key={genre?.id ?? i}>
|
||||
<Typography>
|
||||
{genre ? (
|
||||
<Link href={`/genres/${genre.slug}`}>{genre.name}</Link>
|
||||
) : (
|
||||
<Skeleton />
|
||||
)}
|
||||
</Typography>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
) : (
|
||||
<Typography>{t("show.genre-none")}</Typography>
|
||||
)}
|
||||
</Box>
|
||||
</Container>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@ -40,19 +248,22 @@ const query = (slug: string): QueryIdentifier<Show> => ({
|
||||
parser: ShowP,
|
||||
path: ["shows", slug],
|
||||
params: {
|
||||
fields: ["genres"],
|
||||
fields: ["genres", "studio"],
|
||||
},
|
||||
});
|
||||
|
||||
const ShowDetails: QueryPage<{ slug: string }> = ({ slug }) => {
|
||||
const { data, error } = useFetch(query(slug));
|
||||
console.log("error", data);
|
||||
|
||||
if (!data) return <p>oups</p>;
|
||||
if (error) return <p>oups</p>;
|
||||
|
||||
return (
|
||||
<>
|
||||
<ShowHeader {...data} />
|
||||
<Head>
|
||||
<title>{makeTitle(data?.name)}</title>
|
||||
<meta name="description" content={data?.overview} />
|
||||
</Head>
|
||||
<ShowHeader data={data} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
1
front/src/styled-jsx.d.ts
vendored
Normal file
1
front/src/styled-jsx.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
/// <reference types="styled-jsx" />
|
35
front/src/utils/hooks/use-scroll.ts
Normal file
35
front/src/utils/hooks/use-scroll.ts
Normal file
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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 { useState, useEffect } from "react";
|
||||
|
||||
export const useScroll = () => {
|
||||
const [scroll, setScroll] = useState(0);
|
||||
|
||||
const scrollHandler = () => {
|
||||
setScroll(window.scrollY);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener("scroll", scrollHandler);
|
||||
return () => window.removeEventListener("scroll", scrollHandler);
|
||||
}, []);
|
||||
return scroll;
|
||||
};
|
@ -18,7 +18,7 @@
|
||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React, { forwardRef, Ref } from "react";
|
||||
import { forwardRef, Ref } from "react";
|
||||
import NLink, { LinkProps as NLinkProps} from "next/link";
|
||||
import { Button as MButton, ButtonProps, Link as MLink, LinkProps as MLinkProps} from "@mui/material";
|
||||
|
||||
|
@ -27,7 +27,7 @@ import {
|
||||
useQuery,
|
||||
} from "react-query";
|
||||
import { z } from "zod";
|
||||
import { imageList, KyooErrors, Page } from "~/models";
|
||||
import { KyooErrors, Page } from "~/models";
|
||||
import { Paged } from "~/models/page";
|
||||
|
||||
const queryFn = async <Data>(
|
||||
|
@ -18,14 +18,26 @@
|
||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { createTheme } from "@mui/material";
|
||||
import { createTheme, responsiveFontSizes } from "@mui/material";
|
||||
|
||||
export const defaultTheme = createTheme({
|
||||
components: {
|
||||
MuiSkeleton: {
|
||||
defaultProps: {
|
||||
animation: "wave",
|
||||
export const defaultTheme = responsiveFontSizes(
|
||||
createTheme({
|
||||
components: {
|
||||
MuiSkeleton: {
|
||||
defaultProps: {
|
||||
animation: "wave",
|
||||
},
|
||||
},
|
||||
Poster: {
|
||||
defaultProps: {
|
||||
radius: "1%",
|
||||
},
|
||||
},
|
||||
MuiLink: {
|
||||
defaultProps: {
|
||||
underline: "hover",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
23
front/src/utils/utils.ts
Normal file
23
front/src/utils/utils.ts
Normal file
@ -0,0 +1,23 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
export const makeTitle = (title?: string) => {
|
||||
return title ? `${title} - Kyoo` : "Kyoo";
|
||||
};
|
@ -24,7 +24,7 @@ export const zdate = () => {
|
||||
return z.preprocess((arg) => {
|
||||
if (arg instanceof Date) return arg;
|
||||
|
||||
if (typeof arg === "string" && /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z/.test(arg)) {
|
||||
if (typeof arg === "string" && /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z?/.test(arg)) {
|
||||
return new Date(arg);
|
||||
}
|
||||
|
||||
|
@ -767,6 +767,13 @@ convert-source-map@^1.5.0:
|
||||
dependencies:
|
||||
safe-buffer "~5.1.1"
|
||||
|
||||
copy-anything@^3.0.2:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/copy-anything/-/copy-anything-3.0.2.tgz#7189171ff5e1893b2287e8bf574b8cd448ed50b1"
|
||||
integrity sha512-CzATjGXzUQ0EvuvgOCI6A4BGOo2bcVx8B+eC2nF862iv9fopnPQwlrbACakNCHRIJbCSBj+J/9JeDf60k64MkA==
|
||||
dependencies:
|
||||
is-what "^4.1.6"
|
||||
|
||||
core-js-pure@^3.20.2:
|
||||
version "3.23.4"
|
||||
resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.23.4.tgz#aba5c7fb297063444f6bf93afb0362151679a012"
|
||||
@ -1385,7 +1392,7 @@ has@^1.0.3:
|
||||
dependencies:
|
||||
function-bind "^1.1.1"
|
||||
|
||||
hoist-non-react-statics@^3.3.1, hoist-non-react-statics@^3.3.2:
|
||||
hoist-non-react-statics@^3.3.1:
|
||||
version "3.3.2"
|
||||
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
|
||||
integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
|
||||
@ -1536,6 +1543,11 @@ is-weakref@^1.0.2:
|
||||
dependencies:
|
||||
call-bind "^1.0.2"
|
||||
|
||||
is-what@^4.1.6:
|
||||
version "4.1.7"
|
||||
resolved "https://registry.yarnpkg.com/is-what/-/is-what-4.1.7.tgz#c41dc1d2d2d6a9285c624c2505f61849c8b1f9cc"
|
||||
integrity sha512-DBVOQNiPKnGMxRMLIYSwERAS5MVY1B7xYiGnpgctsOFvVDz9f9PFXXxMcTOHuoqYp4NK9qFYQaIC1NRRxLMpBQ==
|
||||
|
||||
isexe@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
|
||||
@ -1938,13 +1950,6 @@ natural-compare@^1.4.0:
|
||||
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
|
||||
integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==
|
||||
|
||||
next-superjson-plugin@^0.3.0:
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/next-superjson-plugin/-/next-superjson-plugin-0.3.0.tgz#81145f275c1e555be68867c104cc21113f96c675"
|
||||
integrity sha512-M0Soj1P2t9peCyzNndEJiS48O2m88X9UGsCXDy8WHyGwWw1S7eCOEg9MiMqR+X1GD5C3hsdtHKMsKqrZOzr+SQ==
|
||||
dependencies:
|
||||
hoist-non-react-statics "^3.3.2"
|
||||
|
||||
next-translate@^1.5.0:
|
||||
version "1.5.0"
|
||||
resolved "https://registry.yarnpkg.com/next-translate/-/next-translate-1.5.0.tgz#b1e5c4a8e55e31b3ed1b9428529f27c289c6b7bc"
|
||||
@ -2416,6 +2421,13 @@ stylis@4.0.13:
|
||||
resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.0.13.tgz#f5db332e376d13cc84ecfe5dace9a2a51d954c91"
|
||||
integrity sha512-xGPXiFVl4YED9Jh7Euv2V220mriG9u4B2TA6Ybjc1catrstKD2PpIdU3U0RKpkVBC2EhmL/F0sPCr9vrFTNRag==
|
||||
|
||||
superjson@^1.9.1:
|
||||
version "1.9.1"
|
||||
resolved "https://registry.yarnpkg.com/superjson/-/superjson-1.9.1.tgz#e23bd2e8cf0f4ade131d6d769754cac7eaa8ab34"
|
||||
integrity sha512-oT3HA2nPKlU1+5taFgz/HDy+GEaY+CWEbLzaRJVD4gZ7zMVVC4GDNFdgvAZt6/VuIk6D2R7RtPAiCHwmdzlMmg==
|
||||
dependencies:
|
||||
copy-anything "^3.0.2"
|
||||
|
||||
supports-color@^5.3.0:
|
||||
version "5.5.0"
|
||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
|
||||
|
Loading…
x
Reference in New Issue
Block a user