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 = {
|
const nextConfig = {
|
||||||
reactStrictMode: true,
|
reactStrictMode: true,
|
||||||
swcMinify: true,
|
swcMinify: true,
|
||||||
experimental: {
|
|
||||||
swcPlugins: [
|
|
||||||
[
|
|
||||||
"next-superjson-plugin",
|
|
||||||
{
|
|
||||||
excluded: [],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
],
|
|
||||||
},
|
|
||||||
env: {
|
env: {
|
||||||
KYOO_URL: process.env.KYOO_URL ?? "http://localhost:5000",
|
KYOO_URL: process.env.KYOO_URL ?? "http://localhost:5000",
|
||||||
},
|
},
|
||||||
|
@ -26,11 +26,11 @@
|
|||||||
"@mui/icons-material": "^5.8.4",
|
"@mui/icons-material": "^5.8.4",
|
||||||
"@mui/material": "^5.8.7",
|
"@mui/material": "^5.8.7",
|
||||||
"next": "12.2.2",
|
"next": "12.2.2",
|
||||||
"next-superjson-plugin": "^0.3.0",
|
|
||||||
"next-translate": "^1.5.0",
|
"next-translate": "^1.5.0",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"react-query": "^4.0.0-beta.23",
|
"react-query": "^4.0.0-beta.23",
|
||||||
|
"superjson": "^1.9.1",
|
||||||
"zod": "^3.18.0"
|
"zod": "^3.18.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"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,
|
mr: 2,
|
||||||
fontFamily: "monospace",
|
fontFamily: "monospace",
|
||||||
fontWeight: 700,
|
fontWeight: 700,
|
||||||
color: "white",
|
color: "white"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Kyoo
|
Kyoo
|
||||||
@ -84,7 +84,7 @@ export const Navbar = (barProps: AppBarProps) => {
|
|||||||
const { data, error, isSuccess, isError } = useFetch(NavbarQuery);
|
const { data, error, isSuccess, isError } = useFetch(NavbarQuery);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppBar position="sticky">
|
<AppBar position="sticky" {...barProps}>
|
||||||
<Toolbar>
|
<Toolbar>
|
||||||
<IconButton
|
<IconButton
|
||||||
size="large"
|
size="large"
|
||||||
|
@ -19,15 +19,11 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Box, Skeleton, styled } from "@mui/material";
|
import { Box, Skeleton, styled } from "@mui/material";
|
||||||
import {
|
import { SyntheticEvent, useEffect, useLayoutEffect, useRef, useState } from "react";
|
||||||
SyntheticEvent,
|
|
||||||
useEffect,
|
|
||||||
useLayoutEffect,
|
|
||||||
useRef,
|
|
||||||
useState,
|
|
||||||
} from "react";
|
|
||||||
import { ComponentsOverrides, ComponentsProps, ComponentsVariants } from "@mui/material";
|
import { ComponentsOverrides, ComponentsProps, ComponentsVariants } from "@mui/material";
|
||||||
import { withThemeProps } from "~/utils/with-theme";
|
import { withThemeProps } from "~/utils/with-theme";
|
||||||
|
import type { Property } from "csstype";
|
||||||
|
import { ResponsiveStyleValue } from "@mui/system";
|
||||||
|
|
||||||
type ImageOptions = {
|
type ImageOptions = {
|
||||||
radius?: string;
|
radius?: string;
|
||||||
@ -35,14 +31,17 @@ type ImageOptions = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
type ImageProps = {
|
type ImageProps = {
|
||||||
img?: string;
|
img?: string | null;
|
||||||
alt: string;
|
alt: string;
|
||||||
} & ImageOptions;
|
} & ImageOptions;
|
||||||
|
|
||||||
type ImagePropsWithLoading =
|
type ImagePropsWithLoading =
|
||||||
| (ImageProps & { loading?: false })
|
| (ImageProps & { loading?: boolean })
|
||||||
| (Partial<ImageProps> & { loading: true });
|
| (Partial<ImageProps> & { loading: true });
|
||||||
|
|
||||||
|
type Width = ResponsiveStyleValue<Property.Width<(string & {}) | 0>>;
|
||||||
|
type Height = ResponsiveStyleValue<Property.Height<(string & {}) | 0>>;
|
||||||
|
|
||||||
const _Image = ({
|
const _Image = ({
|
||||||
img,
|
img,
|
||||||
alt,
|
alt,
|
||||||
@ -55,8 +54,8 @@ const _Image = ({
|
|||||||
...others
|
...others
|
||||||
}: ImagePropsWithLoading &
|
}: ImagePropsWithLoading &
|
||||||
(
|
(
|
||||||
| { aspectRatio?: string; width: string | number; height: string | number }
|
| { aspectRatio?: string; width: Width; height: Height }
|
||||||
| { aspectRatio: string; width?: string | number; height?: string | number }
|
| { aspectRatio: string; width?: Width; height?: Height }
|
||||||
)) => {
|
)) => {
|
||||||
const [showLoading, setLoading] = useState<boolean>(loading);
|
const [showLoading, setLoading] = useState<boolean>(loading);
|
||||||
const imgRef = useRef<HTMLImageElement>(null);
|
const imgRef = useRef<HTMLImageElement>(null);
|
||||||
@ -101,7 +100,9 @@ const _Image = ({
|
|||||||
export const Image = styled(_Image)({});
|
export const Image = styled(_Image)({});
|
||||||
|
|
||||||
// eslint-disable-next-line jsx-a11y/alt-text
|
// 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" {
|
declare module "@mui/material/styles" {
|
||||||
interface ComponentsPropsList {
|
interface ComponentsPropsList {
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
body {
|
|
||||||
margin: 0 !important;
|
|
||||||
}
|
|
@ -43,7 +43,7 @@ export interface Page<T> {
|
|||||||
*
|
*
|
||||||
* @format uri
|
* @format uri
|
||||||
*/
|
*/
|
||||||
next?: string;
|
next: string | null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The number of items in the current page.
|
* 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({
|
z.object({
|
||||||
this: z.string(),
|
this: z.string(),
|
||||||
first: z.string(),
|
first: z.string(),
|
||||||
next: z.string().optional(),
|
next: z.string().nullable(),
|
||||||
count: z.number(),
|
count: z.number(),
|
||||||
items: z.array(item),
|
items: z.array(item),
|
||||||
});
|
});
|
||||||
|
@ -22,6 +22,7 @@ import { z } from "zod";
|
|||||||
import { zdate } from "~/utils/zod";
|
import { zdate } from "~/utils/zod";
|
||||||
import { ImagesP, ResourceP } from "../traits";
|
import { ImagesP, ResourceP } from "../traits";
|
||||||
import { GenreP } from "./genre";
|
import { GenreP } from "./genre";
|
||||||
|
import { StudioP } from "./studio";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The enum containing show's status.
|
* 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.
|
* 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.
|
* 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.
|
* The list of genres (themes) this show has.
|
||||||
*/
|
*/
|
||||||
genres: z.array(GenreP).optional(),
|
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
|
* 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(),
|
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
|
* 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(),
|
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
|
* 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(),
|
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
|
* 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.
|
||||||
*/
|
*/
|
||||||
trailer: z.string().transform(imageFn).optional(),
|
trailer: z.string().transform(imageFn).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 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/>.
|
* 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 appWithI18n from "next-translate/appWithI18n";
|
||||||
import { ThemeProvider } from "@mui/material";
|
import { ThemeProvider } from "@mui/material";
|
||||||
import NextApp, { AppContext } from "next/app";
|
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 { createQueryClient, fetchQuery, QueryIdentifier, QueryPage } from "~/utils/query";
|
||||||
import { defaultTheme } from "~/utils/themes/default-theme";
|
import { defaultTheme } from "~/utils/themes/default-theme";
|
||||||
import { Navbar, NavbarQuery } from "~/components/navbar";
|
import { Navbar, NavbarQuery } from "~/components/navbar";
|
||||||
import "../global.css";
|
|
||||||
import { Box } from "@mui/system";
|
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 }) => {
|
const AppWithNavbar = ({ children }: { children: JSX.Element }) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Navbar/>
|
{/* <Navbar /> */}
|
||||||
{/* TODO: add an option to disable the navbar in the component */}
|
{/* TODO: add an option to disable the navbar in the component */}
|
||||||
<Box>{children}</Box>
|
<Box>{children}</Box>
|
||||||
</>
|
</>
|
||||||
@ -42,10 +47,17 @@ const AppWithNavbar = ({ children }: { children: JSX.Element }) => {
|
|||||||
|
|
||||||
const App = ({ Component, pageProps }: AppProps) => {
|
const App = ({ Component, pageProps }: AppProps) => {
|
||||||
const [queryClient] = useState(() => createQueryClient());
|
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
|
// TODO: tranform date string to date instances in the queryState
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
|
<style jsx global>{`
|
||||||
|
body {
|
||||||
|
margin: 0px;
|
||||||
|
padding: 0px;
|
||||||
|
}
|
||||||
|
`}</style>
|
||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
<Hydrate state={queryState}>
|
<Hydrate state={queryState}>
|
||||||
<ThemeProvider theme={defaultTheme}>
|
<ThemeProvider theme={defaultTheme}>
|
||||||
@ -55,6 +67,7 @@ const App = ({ Component, pageProps }: AppProps) => {
|
|||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</Hydrate>
|
</Hydrate>
|
||||||
</QueryClientProvider>
|
</QueryClientProvider>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -67,7 +80,7 @@ App.getInitialProps = async (ctx: AppContext) => {
|
|||||||
urls.push(NavbarQuery);
|
urls.push(NavbarQuery);
|
||||||
appProps.pageProps.queryState = await fetchQuery(urls);
|
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
|
// The as any is needed since appWithI18n as wrong type hints
|
||||||
@ -77,7 +90,7 @@ export default appWithI18n(App as any, {
|
|||||||
defaultLocale: "en",
|
defaultLocale: "en",
|
||||||
loader: false,
|
loader: false,
|
||||||
pages: {
|
pages: {
|
||||||
"*": ["common"],
|
"*": ["common", "browse"],
|
||||||
},
|
},
|
||||||
loadLocaleFrom: (locale, namespace) =>
|
loadLocaleFrom: (locale, namespace) =>
|
||||||
import(`../../locales/${locale}/${namespace}`).then((m) => m.default),
|
import(`../../locales/${locale}/${namespace}`).then((m) => m.default),
|
||||||
|
@ -18,20 +18,228 @@
|
|||||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
* 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 { Image, Poster } from "~/components/poster";
|
||||||
import { Show, ShowP } from "~/models";
|
import { Show, ShowP } from "~/models";
|
||||||
import { QueryIdentifier, QueryPage, useFetch } from "~/utils/query";
|
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 { 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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<Image img={data.thumbnail} alt="" height="60vh" width="100%" sx={{ positon: "relative" }} />
|
{/* TODO: Add a shadow on navbar items */}
|
||||||
<Poster img={data.poster} alt={`${data.name}`} />
|
{/* TODO: Put the navbar outside of the scrollbox */}
|
||||||
<Typography variant="h1" component="h1">
|
<Navbar
|
||||||
{data.name}
|
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>
|
</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,
|
parser: ShowP,
|
||||||
path: ["shows", slug],
|
path: ["shows", slug],
|
||||||
params: {
|
params: {
|
||||||
fields: ["genres"],
|
fields: ["genres", "studio"],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const ShowDetails: QueryPage<{ slug: string }> = ({ slug }) => {
|
const ShowDetails: QueryPage<{ slug: string }> = ({ slug }) => {
|
||||||
const { data, error } = useFetch(query(slug));
|
const { data, error } = useFetch(query(slug));
|
||||||
console.log("error", data);
|
|
||||||
|
|
||||||
if (!data) return <p>oups</p>;
|
if (error) return <p>oups</p>;
|
||||||
|
|
||||||
return (
|
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/>.
|
* 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 NLink, { LinkProps as NLinkProps} from "next/link";
|
||||||
import { Button as MButton, ButtonProps, Link as MLink, LinkProps as MLinkProps} from "@mui/material";
|
import { Button as MButton, ButtonProps, Link as MLink, LinkProps as MLinkProps} from "@mui/material";
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ import {
|
|||||||
useQuery,
|
useQuery,
|
||||||
} from "react-query";
|
} from "react-query";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { imageList, KyooErrors, Page } from "~/models";
|
import { KyooErrors, Page } from "~/models";
|
||||||
import { Paged } from "~/models/page";
|
import { Paged } from "~/models/page";
|
||||||
|
|
||||||
const queryFn = async <Data>(
|
const queryFn = async <Data>(
|
||||||
|
@ -18,14 +18,26 @@
|
|||||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
* 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({
|
export const defaultTheme = responsiveFontSizes(
|
||||||
|
createTheme({
|
||||||
components: {
|
components: {
|
||||||
MuiSkeleton: {
|
MuiSkeleton: {
|
||||||
defaultProps: {
|
defaultProps: {
|
||||||
animation: "wave",
|
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) => {
|
return z.preprocess((arg) => {
|
||||||
if (arg instanceof Date) return 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);
|
return new Date(arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -767,6 +767,13 @@ convert-source-map@^1.5.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
safe-buffer "~5.1.1"
|
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:
|
core-js-pure@^3.20.2:
|
||||||
version "3.23.4"
|
version "3.23.4"
|
||||||
resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.23.4.tgz#aba5c7fb297063444f6bf93afb0362151679a012"
|
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:
|
dependencies:
|
||||||
function-bind "^1.1.1"
|
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"
|
version "3.3.2"
|
||||||
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
|
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
|
||||||
integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
|
integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
|
||||||
@ -1536,6 +1543,11 @@ is-weakref@^1.0.2:
|
|||||||
dependencies:
|
dependencies:
|
||||||
call-bind "^1.0.2"
|
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:
|
isexe@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
|
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"
|
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
|
||||||
integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==
|
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:
|
next-translate@^1.5.0:
|
||||||
version "1.5.0"
|
version "1.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/next-translate/-/next-translate-1.5.0.tgz#b1e5c4a8e55e31b3ed1b9428529f27c289c6b7bc"
|
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"
|
resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.0.13.tgz#f5db332e376d13cc84ecfe5dace9a2a51d954c91"
|
||||||
integrity sha512-xGPXiFVl4YED9Jh7Euv2V220mriG9u4B2TA6Ybjc1catrstKD2PpIdU3U0RKpkVBC2EhmL/F0sPCr9vrFTNRag==
|
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:
|
supports-color@^5.3.0:
|
||||||
version "5.5.0"
|
version "5.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
|
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user