mirror of
https://github.com/AnimeThemes/animethemes-web.git
synced 2026-07-11 01:24:31 +02:00
feat: Added fullscreen mode (#179)
* Added theme entry menu to video player. * Added overlay to video player. * Improved video player styles for mobile. * Fixed button focus ring showing on click. * Fixed z-index issue with multi cover image. * Fixed global search. * Fixed various watch list issues.
This commit is contained in:
Generated
+123
-6
@@ -19,6 +19,7 @@
|
||||
"@radix-ui/react-dropdown-menu": "^2.0.4",
|
||||
"@radix-ui/react-select": "^1.2.0",
|
||||
"@radix-ui/react-switch": "^1.0.1",
|
||||
"ahooks": "^3.7.6",
|
||||
"axios": "^1.2.2",
|
||||
"common-tags": "^1.8.0",
|
||||
"framer-motion": "^6.5.1",
|
||||
@@ -1009,9 +1010,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/runtime": {
|
||||
"version": "7.20.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.6.tgz",
|
||||
"integrity": "sha512-Q+8MqP7TiHMWzSfwiJwXCjyf4GYA4Dgw3emg/7xmwsdLJOZUp+nMqcOwOzzYheuM1rhDu8FSj2l0aoMygEuXuA==",
|
||||
"version": "7.21.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.0.tgz",
|
||||
"integrity": "sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==",
|
||||
"dependencies": {
|
||||
"regenerator-runtime": "^0.13.11"
|
||||
},
|
||||
@@ -3522,6 +3523,11 @@
|
||||
"hoist-non-react-statics": "^3.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/js-cookie": {
|
||||
"version": "2.2.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-2.2.7.tgz",
|
||||
"integrity": "sha512-aLkWa0C0vO5b4Sr798E26QgOkss68Un0bLjs7u9qxzPT5CG+8DuNTffWES58YzJs3hrVAOs1wonycqEBqNJubA=="
|
||||
},
|
||||
"node_modules/@types/js-yaml": {
|
||||
"version": "4.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.5.tgz",
|
||||
@@ -3979,6 +3985,34 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/ahooks": {
|
||||
"version": "3.7.6",
|
||||
"resolved": "https://registry.npmjs.org/ahooks/-/ahooks-3.7.6.tgz",
|
||||
"integrity": "sha512-p+2j4H/BI9vqXR0fZI7S/q6fUPxPklQnHqvU7zAVBljMFNSFeYRWB2iHHbjpXGOwUTOBYCh2OuvIHyJYj6Lpag==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.21.0",
|
||||
"@types/js-cookie": "^2.x.x",
|
||||
"ahooks-v3-count": "^1.0.0",
|
||||
"dayjs": "^1.9.1",
|
||||
"intersection-observer": "^0.12.0",
|
||||
"js-cookie": "^2.x.x",
|
||||
"lodash": "^4.17.21",
|
||||
"resize-observer-polyfill": "^1.5.1",
|
||||
"screenfull": "^5.0.0",
|
||||
"tslib": "^2.4.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ahooks-v3-count": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ahooks-v3-count/-/ahooks-v3-count-1.0.0.tgz",
|
||||
"integrity": "sha512-V7uUvAwnimu6eh/PED4mCDjE7tokeZQLKlxg9lCTMPhN+NjsSbtdacByVlR1oluXQzD3MOw55wylDmQo4+S9ZQ=="
|
||||
},
|
||||
"node_modules/ajv": {
|
||||
"version": "6.12.6",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||
@@ -5013,6 +5047,11 @@
|
||||
"integrity": "sha512-8YnDaaf7N3k/q5HnTJVuzSyLETjoZjVmHc4AeKAzOvKHEFQKcn64OKBfzHYtE9zGjctNM7V9I0MfnUVLpi7M5g==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/dayjs": {
|
||||
"version": "1.11.7",
|
||||
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.7.tgz",
|
||||
"integrity": "sha512-+Yw9U6YO5TQohxLcIkrXBeY73WP3ejHWVvx8XCk3gxvQDCTEmS48ZrSZCKciI7Bhl/uCMyxYtE9UqRILmFphkQ=="
|
||||
},
|
||||
"node_modules/debounce": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz",
|
||||
@@ -7060,6 +7099,11 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/intersection-observer": {
|
||||
"version": "0.12.2",
|
||||
"resolved": "https://registry.npmjs.org/intersection-observer/-/intersection-observer-0.12.2.tgz",
|
||||
"integrity": "sha512-7m1vEcPCxXYI8HqnL8CKI6siDyD+eIWSwgB3DZA+ZTogxk9I4CDnj4wilt9x/+/QbHI4YG5YZNmC6458/e9Ktg=="
|
||||
},
|
||||
"node_modules/invariant": {
|
||||
"version": "2.2.4",
|
||||
"resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
|
||||
@@ -7512,6 +7556,11 @@
|
||||
"url": "https://github.com/sponsors/panva"
|
||||
}
|
||||
},
|
||||
"node_modules/js-cookie": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-2.2.1.tgz",
|
||||
"integrity": "sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ=="
|
||||
},
|
||||
"node_modules/js-sha3": {
|
||||
"version": "0.8.0",
|
||||
"resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz",
|
||||
@@ -10106,6 +10155,11 @@
|
||||
"integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/resize-observer-polyfill": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz",
|
||||
"integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg=="
|
||||
},
|
||||
"node_modules/resolve": {
|
||||
"version": "1.22.2",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz",
|
||||
@@ -10296,6 +10350,17 @@
|
||||
"loose-envify": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/screenfull": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/screenfull/-/screenfull-5.2.0.tgz",
|
||||
"integrity": "sha512-9BakfsO2aUQN2K9Fdbj87RJIEZ82Q9IGim7FqM5OsebfoFC6ZHXgDq/KvniuLTPdeM8wY2o6Dj3WQ7KeQCj3cA==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/scuid": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/scuid/-/scuid-1.1.0.tgz",
|
||||
@@ -12529,9 +12594,9 @@
|
||||
}
|
||||
},
|
||||
"@babel/runtime": {
|
||||
"version": "7.20.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.6.tgz",
|
||||
"integrity": "sha512-Q+8MqP7TiHMWzSfwiJwXCjyf4GYA4Dgw3emg/7xmwsdLJOZUp+nMqcOwOzzYheuM1rhDu8FSj2l0aoMygEuXuA==",
|
||||
"version": "7.21.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.0.tgz",
|
||||
"integrity": "sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==",
|
||||
"requires": {
|
||||
"regenerator-runtime": "^0.13.11"
|
||||
}
|
||||
@@ -14539,6 +14604,11 @@
|
||||
"hoist-non-react-statics": "^3.3.0"
|
||||
}
|
||||
},
|
||||
"@types/js-cookie": {
|
||||
"version": "2.2.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-2.2.7.tgz",
|
||||
"integrity": "sha512-aLkWa0C0vO5b4Sr798E26QgOkss68Un0bLjs7u9qxzPT5CG+8DuNTffWES58YzJs3hrVAOs1wonycqEBqNJubA=="
|
||||
},
|
||||
"@types/js-yaml": {
|
||||
"version": "4.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.5.tgz",
|
||||
@@ -14875,6 +14945,28 @@
|
||||
"indent-string": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"ahooks": {
|
||||
"version": "3.7.6",
|
||||
"resolved": "https://registry.npmjs.org/ahooks/-/ahooks-3.7.6.tgz",
|
||||
"integrity": "sha512-p+2j4H/BI9vqXR0fZI7S/q6fUPxPklQnHqvU7zAVBljMFNSFeYRWB2iHHbjpXGOwUTOBYCh2OuvIHyJYj6Lpag==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.21.0",
|
||||
"@types/js-cookie": "^2.x.x",
|
||||
"ahooks-v3-count": "^1.0.0",
|
||||
"dayjs": "^1.9.1",
|
||||
"intersection-observer": "^0.12.0",
|
||||
"js-cookie": "^2.x.x",
|
||||
"lodash": "^4.17.21",
|
||||
"resize-observer-polyfill": "^1.5.1",
|
||||
"screenfull": "^5.0.0",
|
||||
"tslib": "^2.4.1"
|
||||
}
|
||||
},
|
||||
"ahooks-v3-count": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ahooks-v3-count/-/ahooks-v3-count-1.0.0.tgz",
|
||||
"integrity": "sha512-V7uUvAwnimu6eh/PED4mCDjE7tokeZQLKlxg9lCTMPhN+NjsSbtdacByVlR1oluXQzD3MOw55wylDmQo4+S9ZQ=="
|
||||
},
|
||||
"ajv": {
|
||||
"version": "6.12.6",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||
@@ -15632,6 +15724,11 @@
|
||||
"integrity": "sha512-8YnDaaf7N3k/q5HnTJVuzSyLETjoZjVmHc4AeKAzOvKHEFQKcn64OKBfzHYtE9zGjctNM7V9I0MfnUVLpi7M5g==",
|
||||
"dev": true
|
||||
},
|
||||
"dayjs": {
|
||||
"version": "1.11.7",
|
||||
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.7.tgz",
|
||||
"integrity": "sha512-+Yw9U6YO5TQohxLcIkrXBeY73WP3ejHWVvx8XCk3gxvQDCTEmS48ZrSZCKciI7Bhl/uCMyxYtE9UqRILmFphkQ=="
|
||||
},
|
||||
"debounce": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz",
|
||||
@@ -17166,6 +17263,11 @@
|
||||
"side-channel": "^1.0.4"
|
||||
}
|
||||
},
|
||||
"intersection-observer": {
|
||||
"version": "0.12.2",
|
||||
"resolved": "https://registry.npmjs.org/intersection-observer/-/intersection-observer-0.12.2.tgz",
|
||||
"integrity": "sha512-7m1vEcPCxXYI8HqnL8CKI6siDyD+eIWSwgB3DZA+ZTogxk9I4CDnj4wilt9x/+/QbHI4YG5YZNmC6458/e9Ktg=="
|
||||
},
|
||||
"invariant": {
|
||||
"version": "2.2.4",
|
||||
"resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
|
||||
@@ -17466,6 +17568,11 @@
|
||||
"integrity": "sha512-MSJQC5vXco5Br38mzaQKiq9mwt7lwj2eXpgpRyQYNHYt2lq1PjkWa7DLXX0WVcQLE9HhMh3jPiufS7fhJf+CLQ==",
|
||||
"dev": true
|
||||
},
|
||||
"js-cookie": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-2.2.1.tgz",
|
||||
"integrity": "sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ=="
|
||||
},
|
||||
"js-sha3": {
|
||||
"version": "0.8.0",
|
||||
"resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz",
|
||||
@@ -19235,6 +19342,11 @@
|
||||
"integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
|
||||
"dev": true
|
||||
},
|
||||
"resize-observer-polyfill": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz",
|
||||
"integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg=="
|
||||
},
|
||||
"resolve": {
|
||||
"version": "1.22.2",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz",
|
||||
@@ -19362,6 +19474,11 @@
|
||||
"loose-envify": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"screenfull": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/screenfull/-/screenfull-5.2.0.tgz",
|
||||
"integrity": "sha512-9BakfsO2aUQN2K9Fdbj87RJIEZ82Q9IGim7FqM5OsebfoFC6ZHXgDq/KvniuLTPdeM8wY2o6Dj3WQ7KeQCj3cA=="
|
||||
},
|
||||
"scuid": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/scuid/-/scuid-1.1.0.tgz",
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
"@radix-ui/react-dropdown-menu": "^2.0.4",
|
||||
"@radix-ui/react-select": "^1.2.0",
|
||||
"@radix-ui/react-switch": "^1.0.1",
|
||||
"ahooks": "^3.7.6",
|
||||
"axios": "^1.2.2",
|
||||
"common-tags": "^1.8.0",
|
||||
"framer-motion": "^6.5.1",
|
||||
|
||||
@@ -76,7 +76,7 @@ const BaseButton = styled.button<{ $isCircle: boolean }>`
|
||||
margin: -8px 8px -8px -16px;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
&:focus:focus-visible {
|
||||
box-shadow: 0 0 0 2px var(--focus-ring-color);
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -5,6 +5,7 @@ import theme from "theme";
|
||||
import useMediaQuery from "hooks/useMediaQuery";
|
||||
import type { IconDefinition } from "@fortawesome/fontawesome-svg-core";
|
||||
import type { ComponentPropsWithoutRef, ReactNode } from "react";
|
||||
import { forwardRef } from "react";
|
||||
|
||||
const StyledButton = styled(Button)`
|
||||
gap: 8px;
|
||||
@@ -24,21 +25,23 @@ interface IconTextButtonProps extends ComponentPropsWithoutRef<typeof StyledButt
|
||||
collapsible?: boolean
|
||||
}
|
||||
|
||||
export function IconTextButton({ icon, children, collapsible = false, ...props }: IconTextButtonProps) {
|
||||
const isMobile = useMediaQuery(`(max-width: ${theme.breakpoints.mobileMax})`);
|
||||
const isCollapsed = collapsible && isMobile;
|
||||
export const IconTextButton = forwardRef(
|
||||
function IconTextButton({ icon, children, collapsible = false, ...props }: IconTextButtonProps, ref) {
|
||||
const isMobile = useMediaQuery(`(max-width: ${theme.breakpoints.mobileMax})`);
|
||||
const isCollapsed = collapsible && isMobile;
|
||||
|
||||
return (
|
||||
<StyledButton variant="silent" isCircle={isCollapsed} {...props}>
|
||||
{isIconDefinition(icon) ? (
|
||||
<Icon icon={icon} color="text-disabled"/>
|
||||
) : icon}
|
||||
{children ? (
|
||||
<StyledText collapsible={collapsible}>{children}</StyledText>
|
||||
) : null}
|
||||
</StyledButton>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<StyledButton ref={ref} variant="silent" isCircle={isCollapsed} {...props}>
|
||||
{isIconDefinition(icon) ? (
|
||||
<Icon icon={icon} color="text-disabled"/>
|
||||
) : icon}
|
||||
{children ? (
|
||||
<StyledText collapsible={collapsible}>{children}</StyledText>
|
||||
) : null}
|
||||
</StyledButton>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
function isIconDefinition(icon: IconDefinition | ReactNode): icon is IconDefinition {
|
||||
return !!icon && typeof icon === "object" && "icon" in icon;
|
||||
|
||||
@@ -32,6 +32,7 @@ const StyledCoverContainer = styled.div`
|
||||
height: 100%;
|
||||
border-radius: 0.5rem;
|
||||
overflow: hidden;
|
||||
isolation: isolate;
|
||||
`;
|
||||
const StyledCoverItemContainer = styled.div<{ $itemCount: number }>`
|
||||
position: absolute;
|
||||
|
||||
@@ -7,9 +7,10 @@ import * as RadixMenu from "@radix-ui/react-dropdown-menu";
|
||||
|
||||
const StyledMenuContent = styled(RadixMenu.Content)`
|
||||
width: max-content;
|
||||
max-height: var(--radix-dropdown-menu-content-available-height);
|
||||
padding: 8px 0;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
overflow: auto;
|
||||
|
||||
background-color: ${theme.colors["solid"]};
|
||||
box-shadow: 0 0 0 2px ${theme.colors["text-primary"]}, ${theme.shadows.high};
|
||||
@@ -48,4 +49,26 @@ export const MenuItem = styled(RadixMenu.Item)`
|
||||
color: ${theme.colors["text"]};
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&[data-disabled] {
|
||||
opacity: 0.5;
|
||||
cursor: revert;
|
||||
}
|
||||
`;
|
||||
|
||||
export const MenuLabel = styled(RadixMenu.Label)`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
width: 100%;
|
||||
padding: 8px 16px;
|
||||
color: ${theme.colors["text-muted"]};
|
||||
font-size: 0.8rem;
|
||||
`;
|
||||
|
||||
export const MenuSeparator = styled(RadixMenu.Separator)`
|
||||
height: 2px;
|
||||
background-color: ${theme.colors["text-primary"]};
|
||||
margin: 8px 0;
|
||||
`;
|
||||
|
||||
@@ -21,6 +21,10 @@ export const StyledNavigation = styled(Solid).attrs({ as: "nav" })<{ $floating:
|
||||
background-color: transparent;
|
||||
box-shadow: none;
|
||||
`}
|
||||
|
||||
[data-fullscreen] & {
|
||||
display: none;
|
||||
}
|
||||
`;
|
||||
|
||||
export const StyledNavigationContainer = styled(Container)`
|
||||
|
||||
@@ -9,7 +9,7 @@ interface EpisodeTagProps {
|
||||
|
||||
export function EpisodeTag({ entry }: EpisodeTagProps) {
|
||||
return (
|
||||
<Tag icon={faFilm}>
|
||||
<Tag icon={faFilm} title={entry.episodes ? `Used in episode ${entry.episodes}` : "Used in all episodes"}>
|
||||
{entry.episodes || "—"}
|
||||
</Tag>
|
||||
);
|
||||
|
||||
@@ -13,7 +13,7 @@ const StyledTag = styled.span`
|
||||
gap: 4px;
|
||||
|
||||
& ${Icon} {
|
||||
transform: translateY(0.2rem);
|
||||
transform: translateY(0.2em);
|
||||
}
|
||||
`;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import styled from "styled-components";
|
||||
import theme from "theme";
|
||||
import type { RefObject } from "react";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { useContext, useEffect, useRef } from "react";
|
||||
import { VideoPlayerContext } from "components/video-player-2/VideoPlayer2";
|
||||
|
||||
const StyledPlayerProgress = styled.div`
|
||||
position: absolute;
|
||||
@@ -11,6 +11,7 @@ const StyledPlayerProgress = styled.div`
|
||||
padding: 16px 0;
|
||||
|
||||
cursor: pointer;
|
||||
touch-action: none;
|
||||
transform: translateY(-50%);
|
||||
`;
|
||||
|
||||
@@ -69,12 +70,18 @@ const StyledPlayerProgressBarHover = styled.div`
|
||||
}
|
||||
`;
|
||||
|
||||
interface ProgressBarProps {
|
||||
playerRef: RefObject<HTMLVideoElement | HTMLAudioElement>;
|
||||
progressRef: RefObject<HTMLDivElement>;
|
||||
}
|
||||
export function ProgressBar() {
|
||||
const context = useContext(VideoPlayerContext);
|
||||
|
||||
if (!context) {
|
||||
throw new Error("ProgressBar needs to be inside VideoPlayer!");
|
||||
}
|
||||
|
||||
const {
|
||||
playerRef,
|
||||
progressRef,
|
||||
} = context;
|
||||
|
||||
export function ProgressBar({ playerRef, progressRef }: ProgressBarProps) {
|
||||
const progressHoverRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const nextProgressRef = useRef(0);
|
||||
@@ -101,18 +108,18 @@ export function ProgressBar({ playerRef, progressRef }: ProgressBarProps) {
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener("mouseup", onMouseUp);
|
||||
window.addEventListener("mousemove", onMouseMove);
|
||||
window.addEventListener("pointerup", onMouseUp);
|
||||
window.addEventListener("pointermove", onMouseMove);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("mouseup", onMouseUp);
|
||||
window.removeEventListener("mousemove", onMouseMove);
|
||||
window.removeEventListener("pointerup", onMouseUp);
|
||||
window.removeEventListener("pointermove", onMouseMove);
|
||||
};
|
||||
}, [playerRef, progressRef]);
|
||||
|
||||
return (
|
||||
<StyledPlayerProgress
|
||||
onMouseDown={(event) => {
|
||||
onPointerDown={(event) => {
|
||||
const nextProgress = event.clientX / event.currentTarget.getBoundingClientRect().width;
|
||||
isDraggingRef.current = true;
|
||||
nextProgressRef.current = nextProgress;
|
||||
@@ -121,7 +128,7 @@ export function ProgressBar({ playerRef, progressRef }: ProgressBarProps) {
|
||||
progressRef.current.style.width = `${nextProgress * 100}%`;
|
||||
}
|
||||
}}
|
||||
onMouseMove={(event) => {
|
||||
onPointerMove={(event) => {
|
||||
const nextProgress = event.clientX / event.currentTarget.getBoundingClientRect().width;
|
||||
if (progressHoverRef.current) {
|
||||
progressHoverRef.current.innerText = playerRef.current ? formatTime(nextProgress * playerRef.current.duration) : "";
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import styled from "styled-components";
|
||||
import theme from "theme";
|
||||
import { Switcher } from "components/switcher";
|
||||
import { Solid } from "components/box";
|
||||
import { IconTextButton } from "components/button";
|
||||
import { m } from "framer-motion";
|
||||
|
||||
export const StyledPlayer = styled.div`
|
||||
@@ -15,6 +13,10 @@ export const StyledPlayer = styled.div`
|
||||
&:not([data-background]) {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
&[data-relaxed] {
|
||||
cursor: none;
|
||||
}
|
||||
`;
|
||||
|
||||
export const StyledPlayerContent = styled.div`
|
||||
@@ -39,16 +41,29 @@ export const StyledPlayerContent = styled.div`
|
||||
inset: 70px 16px 104px 16px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
[data-fullscreen] & {
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: auto;
|
||||
padding: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
export const StyledPlaybackArea = styled(m.div)`
|
||||
align-self: stretch;
|
||||
justify-self: stretch;
|
||||
|
||||
padding: 16px 0;
|
||||
position: relative;
|
||||
|
||||
margin: 32px 0;
|
||||
|
||||
@media (max-width: ${theme.breakpoints.tabletMax}) {
|
||||
aspect-ratio: 16 / 9;
|
||||
margin: 0 -16px;
|
||||
|
||||
[data-background] & {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
[data-background] & {
|
||||
@@ -57,44 +72,44 @@ export const StyledPlaybackArea = styled(m.div)`
|
||||
left: 0;
|
||||
width: 300px;
|
||||
aspect-ratio: 16 / 9;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
[data-fullscreen] & {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
export const StyledVideoBackground = styled.div`
|
||||
position: relative;
|
||||
|
||||
display: flex;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
`;
|
||||
|
||||
export const StyledVideo = styled.video`
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
margin: auto;
|
||||
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
aspect-ratio: 16 / 9;
|
||||
`;
|
||||
|
||||
export const StyledVideo = styled.video`
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
outline: none;
|
||||
|
||||
@media (max-width: ${theme.breakpoints.tabletMax}) {
|
||||
position: static;
|
||||
}
|
||||
`;
|
||||
|
||||
export const StyledAudioBackground = styled.div`
|
||||
position: relative;
|
||||
|
||||
display: flex;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
margin: auto;
|
||||
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
`;
|
||||
|
||||
export const StyledAudio = styled.audio`
|
||||
@@ -102,12 +117,15 @@ export const StyledAudio = styled.audio`
|
||||
`;
|
||||
|
||||
export const StyledAudioCover = styled.img`
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
margin: auto;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
max-height: 100%;
|
||||
pointer-events: none;
|
||||
outline: none;
|
||||
object-fit: contain;
|
||||
|
||||
[data-background] & {
|
||||
pointer-events: none;
|
||||
}
|
||||
`;
|
||||
|
||||
export const StyledAside = styled.aside`
|
||||
@@ -119,9 +137,10 @@ export const StyledAside = styled.aside`
|
||||
|
||||
@media (max-width: ${theme.breakpoints.tabletMax}) {
|
||||
padding-top: 0;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
[data-background] & {
|
||||
[data-background] &, [data-fullscreen] & {
|
||||
display: none;
|
||||
}
|
||||
`;
|
||||
@@ -138,47 +157,3 @@ export const StyledScrollArea = styled.div`
|
||||
padding-bottom: 16px;
|
||||
overflow: auto;
|
||||
`;
|
||||
|
||||
export const StyledPlayerBar = styled(Solid)`
|
||||
position: relative;
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto 1fr;
|
||||
grid-gap: 32px;
|
||||
align-items: center;
|
||||
|
||||
padding: 16px 32px;
|
||||
|
||||
@media (max-width: ${theme.breakpoints.tabletMax}) {
|
||||
grid-template-columns: 1fr auto;
|
||||
grid-gap: 16px;
|
||||
|
||||
padding: 16px;
|
||||
}
|
||||
`;
|
||||
|
||||
export const StyledPlayerBarControls = styled.div`
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
|
||||
@media (max-width: ${theme.breakpoints.tabletMax}) {
|
||||
& > :first-child, & > :last-child {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const StyledPlayerBarControl = styled(IconTextButton)`
|
||||
font-size: 2rem;
|
||||
`;
|
||||
|
||||
export const StyledPlayerBarActions = styled.div`
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
|
||||
margin-left: auto;
|
||||
|
||||
@media (max-width: ${theme.breakpoints.tabletMax}) {
|
||||
display: none;
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { ReactNode, SyntheticEvent } from "react";
|
||||
import { useCallback, useContext, useEffect, useRef, useState } from "react";
|
||||
import type { PointerEvent, ReactNode, RefObject, SyntheticEvent } from "react";
|
||||
import { createContext, useCallback, useContext, useEffect, useRef, useState } from "react";
|
||||
import {
|
||||
StyledAside,
|
||||
StyledAudio,
|
||||
@@ -7,10 +7,6 @@ import {
|
||||
StyledAudioCover,
|
||||
StyledPlaybackArea,
|
||||
StyledPlayer,
|
||||
StyledPlayerBar,
|
||||
StyledPlayerBarActions,
|
||||
StyledPlayerBarControl,
|
||||
StyledPlayerBarControls,
|
||||
StyledPlayerContent,
|
||||
StyledVideo,
|
||||
StyledVideoBackground
|
||||
@@ -22,52 +18,59 @@ import useSetting from "hooks/useSetting";
|
||||
import { AudioMode, GlobalVolume } from "utils/settings";
|
||||
import { AUDIO_URL, VIDEO_URL } from "utils/config";
|
||||
import extractImages from "utils/extractImages";
|
||||
import { Text } from "components/text";
|
||||
import { Column } from "components/box";
|
||||
import { Switcher } from "components/switcher";
|
||||
import { SwitcherOption } from "components/switcher/Switcher";
|
||||
import { Button, IconTextButton } from "components/button";
|
||||
import { Icon } from "components/icon";
|
||||
import { faBackwardStep, faForwardStep, faPause, faPlay, faShare, faXmark } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { Performances, SongTitle } from "components/utils";
|
||||
import Link from "next/link";
|
||||
import { PlaylistTrackAddDialog } from "components/dialog/PlaylistTrackAddDialog";
|
||||
import { Menu, MenuContent, MenuItem, MenuTrigger } from "components/menu/Menu";
|
||||
import { Toast } from "components/toast";
|
||||
import { useToasts } from "context/toastContext";
|
||||
import useWatchHistory from "hooks/useWatchHistory";
|
||||
import { ProgressBar } from "components/video-player-2/ProgressBar";
|
||||
import type { VideoSummaryCardVideoFragment } from "generated/graphql";
|
||||
import { ConditionalWrapper } from "components/utils/ConditionalWrapper";
|
||||
import { VideoPlayerBar } from "components/video-player-2/VideoPlayerBar";
|
||||
import useMouseRelax from "hooks/useMouseRelax";
|
||||
|
||||
interface VideoPlayerProps {
|
||||
interface VideoPlayerContextValue {
|
||||
video: VideoSummaryCardVideoFragment;
|
||||
background: boolean;
|
||||
playerRef: RefObject<HTMLVideoElement | HTMLAudioElement | null>;
|
||||
progressRef: RefObject<HTMLDivElement>;
|
||||
previousVideoPath: string | null;
|
||||
playPreviousTrack(navigate: boolean): void;
|
||||
nextVideoPath: string | null;
|
||||
playNextTrack(navigate: boolean): void;
|
||||
isPlaying: boolean;
|
||||
togglePlay(): void;
|
||||
videoUrl: string;
|
||||
audioUrl: string;
|
||||
updateAudioMode(audioMode: string): void;
|
||||
}
|
||||
|
||||
export const VideoPlayerContext = createContext<VideoPlayerContextValue | null>(null);
|
||||
|
||||
type VideoPlayerProps = {
|
||||
video: VideoSummaryCardVideoFragment;
|
||||
background: boolean;
|
||||
children?: ReactNode;
|
||||
overlay?: ReactNode;
|
||||
}
|
||||
|
||||
export function VideoPlayer2({ video, background, children, ...props }: VideoPlayerProps) {
|
||||
export function VideoPlayer2({ video, background, children, overlay, ...props }: VideoPlayerProps) {
|
||||
const entry = video.entries[0];
|
||||
const theme = entry.theme!;
|
||||
const anime = theme.anime!;
|
||||
|
||||
const videoPath = `/anime/${anime.slug}/${createVideoSlug(theme, entry, video)}`;
|
||||
const theme = entry.theme;
|
||||
const anime = theme.anime;
|
||||
|
||||
const videoUrl = `${VIDEO_URL}/${video.basename}`;
|
||||
const audioUrl = `${AUDIO_URL}/${video.audio.basename}`;
|
||||
|
||||
const [isPlaying, setPlaying] = useState(false);
|
||||
const [aspectRatio, setAspectRatio] = useState(16 / 9);
|
||||
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const playerRef = useRef<HTMLVideoElement | HTMLAudioElement | null>(null);
|
||||
const progressRef = useRef<HTMLDivElement>(null);
|
||||
const currentTimeBeforeModeSwitch = useRef<number | null>(null);
|
||||
|
||||
const { watchList, currentWatchListItem, setCurrentWatchListItem } = useContext(PlayerContext);
|
||||
const router = useRouter();
|
||||
const [globalVolume, setGlobalVolume] = useSetting(GlobalVolume);
|
||||
const [canRenderPlayer, setCanRenderPlayer] = useState(false);
|
||||
const { smallCover, largeCover } = extractImages(anime);
|
||||
const [audioMode, setAudioMode] = useSetting(AudioMode, { storageSync: false });
|
||||
const { dispatchToast } = useToasts();
|
||||
const { addToHistory } = useWatchHistory();
|
||||
const isRelaxed = useMouseRelax();
|
||||
|
||||
const previousVideo = getWatchListVideo(-1);
|
||||
const previousEntry = previousVideo?.entries[0];
|
||||
@@ -105,8 +108,6 @@ export function VideoPlayer2({ video, background, children, ...props }: VideoPla
|
||||
}
|
||||
}, [nextVideo, nextVideoPath, router, setCurrentWatchListItem]);
|
||||
|
||||
useEffect(() => setCanRenderPlayer(true), []);
|
||||
|
||||
useEffect(() => {
|
||||
if (playerRef.current) {
|
||||
playerRef.current.volume = globalVolume;
|
||||
@@ -158,6 +159,9 @@ export function VideoPlayer2({ video, background, children, ...props }: VideoPla
|
||||
playerRef.current = player;
|
||||
if (playerRef.current) {
|
||||
setPlaying(!playerRef.current.paused);
|
||||
if ("videoWidth" in playerRef.current) {
|
||||
setAspectRatio(playerRef.current.videoWidth / playerRef.current.videoHeight);
|
||||
}
|
||||
playerRef.current.volume = globalVolume;
|
||||
if (currentTimeBeforeModeSwitch.current) {
|
||||
playerRef.current.currentTime = currentTimeBeforeModeSwitch.current;
|
||||
@@ -166,6 +170,12 @@ export function VideoPlayer2({ video, background, children, ...props }: VideoPla
|
||||
}
|
||||
}
|
||||
|
||||
function onPlayerClick(event: PointerEvent) {
|
||||
if (!background && event.nativeEvent.pointerType === "mouse") {
|
||||
togglePlay();
|
||||
}
|
||||
}
|
||||
|
||||
function togglePlay() {
|
||||
if (isPlaying) {
|
||||
playerRef.current?.pause();
|
||||
@@ -182,11 +192,6 @@ export function VideoPlayer2({ video, background, children, ...props }: VideoPla
|
||||
}
|
||||
}
|
||||
|
||||
function saveToClipboard(url: string) {
|
||||
navigator.clipboard.writeText(url)
|
||||
.then(() => dispatchToast("clipboard", <Toast>Copied to clipboard!</Toast>));
|
||||
}
|
||||
|
||||
function updateAudioMode(audioMode: string) {
|
||||
currentTimeBeforeModeSwitch.current = playerRef.current?.currentTime ?? null;
|
||||
setAudioMode(audioMode);
|
||||
@@ -215,8 +220,27 @@ export function VideoPlayer2({ video, background, children, ...props }: VideoPla
|
||||
const constraintRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
return (
|
||||
<>
|
||||
<StyledPlayer data-background={background || undefined} {...props}>
|
||||
<VideoPlayerContext.Provider value={{
|
||||
video,
|
||||
background,
|
||||
playerRef,
|
||||
progressRef,
|
||||
previousVideoPath,
|
||||
playPreviousTrack,
|
||||
nextVideoPath,
|
||||
playNextTrack,
|
||||
isPlaying,
|
||||
togglePlay,
|
||||
videoUrl,
|
||||
audioUrl,
|
||||
updateAudioMode,
|
||||
}}>
|
||||
<StyledPlayer
|
||||
ref={containerRef}
|
||||
data-background={background || undefined}
|
||||
data-relaxed={isRelaxed || undefined}
|
||||
{...props}
|
||||
>
|
||||
<StyledPlayerContent ref={constraintRef}>
|
||||
<StyledPlaybackArea
|
||||
layout
|
||||
@@ -229,7 +253,7 @@ export function VideoPlayer2({ video, background, children, ...props }: VideoPla
|
||||
>
|
||||
{audioMode === AudioMode.ENABLED ? (
|
||||
<StyledAudioBackground>
|
||||
<StyledAudioCover src={largeCover} onClick={background ? undefined : togglePlay}/>
|
||||
<StyledAudioCover src={largeCover} onPointerDown={onPlayerClick}/>
|
||||
<StyledAudio
|
||||
ref={onPlayerMount}
|
||||
src={audioUrl}
|
||||
@@ -243,9 +267,10 @@ export function VideoPlayer2({ video, background, children, ...props }: VideoPla
|
||||
onTimeUpdate={updateProgress}
|
||||
onVolumeChange={(event: SyntheticEvent<HTMLAudioElement>) => setGlobalVolume(event.currentTarget.volume)}
|
||||
/>
|
||||
{overlay}
|
||||
</StyledAudioBackground>
|
||||
) : (
|
||||
<StyledVideoBackground>
|
||||
<StyledVideoBackground style={{ aspectRatio }}>
|
||||
<StyledVideo
|
||||
ref={onPlayerMount}
|
||||
src={videoUrl}
|
||||
@@ -256,10 +281,16 @@ export function VideoPlayer2({ video, background, children, ...props }: VideoPla
|
||||
setPlaying(false);
|
||||
playNextTrack(true);
|
||||
}}
|
||||
onClick={background ? undefined : togglePlay}
|
||||
onPointerDown={onPlayerClick}
|
||||
onTimeUpdate={updateProgress}
|
||||
onVolumeChange={(event: SyntheticEvent<HTMLVideoElement>) => setGlobalVolume(event.currentTarget.volume)}
|
||||
onLoadedMetadata={(event) => {
|
||||
console.log(event.currentTarget.videoWidth);
|
||||
console.log(event.currentTarget.videoHeight);
|
||||
setAspectRatio(event.currentTarget.videoWidth / event.currentTarget.videoHeight);
|
||||
}}
|
||||
/>
|
||||
{overlay}
|
||||
</StyledVideoBackground>
|
||||
)}
|
||||
</StyledPlaybackArea>
|
||||
@@ -267,114 +298,8 @@ export function VideoPlayer2({ video, background, children, ...props }: VideoPla
|
||||
{children}
|
||||
</StyledAside>
|
||||
</StyledPlayerContent>
|
||||
<StyledPlayerBar>
|
||||
<Column style={{ "--gap": "8px" }}>
|
||||
<Text color="text-muted" maxLines={1}>
|
||||
<SongTitle song={theme.song} href={videoPath} />
|
||||
<Text variant="small"> - </Text>
|
||||
<Text weight={600}>{theme.type}{theme.sequence || null}{theme.group && ` (${theme.group})`}</Text>
|
||||
<Text variant="small"> from </Text>
|
||||
<Link href={`/anime/${anime.slug}`} passHref legacyBehavior>
|
||||
<Text as="a" link>{anime.name}</Text>
|
||||
</Link>
|
||||
</Text>
|
||||
{!!theme.song?.performances?.length && (
|
||||
<Text variant="small" color="text-muted">
|
||||
<Text>Performed</Text>
|
||||
<Performances song={theme.song} maxPerformances={3} />
|
||||
</Text>
|
||||
)}
|
||||
</Column>
|
||||
<StyledPlayerBarControls>
|
||||
{previousVideoPath ? (
|
||||
<ConditionalWrapper
|
||||
condition={!background}
|
||||
wrap={(children) => <Link href={previousVideoPath}>{children}</Link>}
|
||||
>
|
||||
<StyledPlayerBarControl
|
||||
icon={faBackwardStep}
|
||||
isCircle
|
||||
onClick={() => playPreviousTrack(!background)}
|
||||
/>
|
||||
</ConditionalWrapper>
|
||||
) : (
|
||||
<StyledPlayerBarControl
|
||||
icon={faBackwardStep}
|
||||
isCircle
|
||||
disabled
|
||||
/>
|
||||
)}
|
||||
<StyledPlayerBarControl
|
||||
icon={isPlaying ? faPause : <Icon icon={faPlay} color="text-disabled" style={{ transform: "translateX(2px)" }} />}
|
||||
isCircle
|
||||
onClick={togglePlay}
|
||||
/>
|
||||
{nextVideoPath ? (
|
||||
<ConditionalWrapper
|
||||
condition={!background}
|
||||
wrap={(children) => <Link href={nextVideoPath}>{children}</Link>}
|
||||
>
|
||||
<StyledPlayerBarControl
|
||||
icon={faForwardStep}
|
||||
isCircle
|
||||
onClick={() => playNextTrack(!background)}
|
||||
/>
|
||||
</ConditionalWrapper>
|
||||
) : (
|
||||
<StyledPlayerBarControl
|
||||
icon={faForwardStep}
|
||||
isCircle
|
||||
disabled
|
||||
/>
|
||||
)}
|
||||
</StyledPlayerBarControls>
|
||||
<StyledPlayerBarActions>
|
||||
<PlaylistTrackAddDialog
|
||||
video={{
|
||||
// Flip the structure on it's head,
|
||||
// because we need video as the root object here.
|
||||
...video,
|
||||
entries: [{
|
||||
...entry,
|
||||
theme,
|
||||
}],
|
||||
}}
|
||||
/>
|
||||
<Menu modal={false}>
|
||||
<MenuTrigger asChild>
|
||||
<Button style={{ "--gap": "8px" }}>
|
||||
<Icon icon={faShare}/>
|
||||
<Text>Share</Text>
|
||||
</Button>
|
||||
</MenuTrigger>
|
||||
<MenuContent>
|
||||
<MenuItem onSelect={() => saveToClipboard(location.href)}>Copy URL to this Page</MenuItem>
|
||||
{audioMode === AudioMode.ENABLED ? (
|
||||
<MenuItem onSelect={() => saveToClipboard(audioUrl)}>Copy URL to Embeddable Audio</MenuItem>
|
||||
) : (
|
||||
<MenuItem onSelect={() => saveToClipboard(videoUrl)}>Copy URL to Embeddable Video</MenuItem>
|
||||
)}
|
||||
</MenuContent>
|
||||
</Menu>
|
||||
{canRenderPlayer ? (
|
||||
<Switcher selectedItem={audioMode} onChange={updateAudioMode}>
|
||||
<SwitcherOption value={AudioMode.DISABLED}>Video</SwitcherOption>
|
||||
<SwitcherOption value={AudioMode.ENABLED}>Audio</SwitcherOption>
|
||||
</Switcher>
|
||||
) : null}
|
||||
<IconTextButton
|
||||
icon={faXmark}
|
||||
isCircle
|
||||
disabled={!background}
|
||||
onClick={() => setCurrentWatchListItem(null)}
|
||||
/>
|
||||
</StyledPlayerBarActions>
|
||||
<ProgressBar
|
||||
playerRef={playerRef}
|
||||
progressRef={progressRef}
|
||||
/>
|
||||
</StyledPlayerBar>
|
||||
<VideoPlayerBar />
|
||||
</StyledPlayer>
|
||||
</>
|
||||
</VideoPlayerContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,216 @@
|
||||
import { Column, Solid } from "components/box";
|
||||
import { Text } from "components/text";
|
||||
import { Performances, SongTitle } from "components/utils";
|
||||
import Link from "next/link";
|
||||
import { ConditionalWrapper } from "components/utils/ConditionalWrapper";
|
||||
import { faBackwardStep, faForwardStep, faPause, faPlay, faShare, faXmark } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { Icon } from "components/icon";
|
||||
import { PlaylistTrackAddDialog } from "components/dialog/PlaylistTrackAddDialog";
|
||||
import { Menu, MenuContent, MenuItem, MenuTrigger } from "components/menu/Menu";
|
||||
import { Button, IconTextButton } from "components/button";
|
||||
import { AudioMode } from "utils/settings";
|
||||
import { ProgressBar } from "components/video-player-2/ProgressBar";
|
||||
import styled from "styled-components";
|
||||
import theme from "theme";
|
||||
import { Toast } from "components/toast";
|
||||
import { useToasts } from "context/toastContext";
|
||||
import createVideoSlug from "utils/createVideoSlug";
|
||||
import { useContext } from "react";
|
||||
import { VideoPlayerContext } from "components/video-player-2/VideoPlayer2";
|
||||
import useSetting from "hooks/useSetting";
|
||||
import PlayerContext from "context/playerContext";
|
||||
|
||||
const StyledPlayerBar = styled(Solid)`
|
||||
position: relative;
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto 1fr;
|
||||
grid-gap: 32px;
|
||||
align-items: center;
|
||||
|
||||
padding: 16px 32px;
|
||||
|
||||
transition: opacity 100ms;
|
||||
|
||||
@media (max-width: ${theme.breakpoints.tabletMax}) {
|
||||
grid-template-columns: 1fr auto;
|
||||
grid-gap: 16px;
|
||||
|
||||
padding: 8px 16px;
|
||||
}
|
||||
|
||||
[data-fullscreen] & {
|
||||
position: fixed;
|
||||
inset: auto 0 0 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
backdrop-filter: brightness(0.25);
|
||||
}
|
||||
|
||||
[data-fullscreen] [data-relaxed] & {
|
||||
opacity: 0;
|
||||
transition: opacity 500ms;
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledPlayerBarControls = styled.div`
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
|
||||
@media (max-width: ${theme.breakpoints.tabletMax}) {
|
||||
& > :first-child, & > :last-child {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledPlayerBarControl = styled(IconTextButton)`
|
||||
font-size: 2rem;
|
||||
`;
|
||||
|
||||
const StyledPlayerBarActions = styled.div`
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
|
||||
margin-left: auto;
|
||||
|
||||
@media (max-width: ${theme.breakpoints.tabletMax}) {
|
||||
display: none;
|
||||
}
|
||||
`;
|
||||
|
||||
export function VideoPlayerBar() {
|
||||
const context = useContext(VideoPlayerContext);
|
||||
|
||||
if (!context) {
|
||||
throw new Error("VideoPlayerBar needs to be inside VideoPlayer!");
|
||||
}
|
||||
|
||||
const {
|
||||
video,
|
||||
background,
|
||||
previousVideoPath,
|
||||
playPreviousTrack,
|
||||
nextVideoPath,
|
||||
playNextTrack,
|
||||
isPlaying,
|
||||
togglePlay,
|
||||
videoUrl,
|
||||
audioUrl,
|
||||
} = context;
|
||||
|
||||
const entry = video.entries[0];
|
||||
const theme = entry.theme;
|
||||
const anime = theme.anime;
|
||||
|
||||
const videoPath = `/anime/${anime.slug}/${createVideoSlug(theme, entry, video)}`;
|
||||
|
||||
const { dispatchToast } = useToasts();
|
||||
const [audioMode] = useSetting(AudioMode, { storageSync: false });
|
||||
const { setCurrentWatchListItem } = useContext(PlayerContext);
|
||||
|
||||
function saveToClipboard(url: string) {
|
||||
navigator.clipboard.writeText(url)
|
||||
.then(() => dispatchToast("clipboard", <Toast>Copied to clipboard!</Toast>));
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledPlayerBar>
|
||||
<Column style={{ "--gap": "8px" }}>
|
||||
<Text color="text-muted" maxLines={1}>
|
||||
<SongTitle song={theme.song} href={videoPath} />
|
||||
<Text variant="small"> - </Text>
|
||||
<Text weight={600}>{theme.type}{theme.sequence || null}{theme.group && ` (${theme.group})`}</Text>
|
||||
<Text variant="small"> from </Text>
|
||||
<Link href={`/anime/${anime.slug}`} passHref legacyBehavior>
|
||||
<Text as="a" link>{anime.name}</Text>
|
||||
</Link>
|
||||
</Text>
|
||||
{!!theme.song?.performances?.length && (
|
||||
<Text variant="small" color="text-muted">
|
||||
<Text>Performed</Text>
|
||||
<Performances song={theme.song} maxPerformances={3} />
|
||||
</Text>
|
||||
)}
|
||||
</Column>
|
||||
<StyledPlayerBarControls>
|
||||
{previousVideoPath ? (
|
||||
<ConditionalWrapper
|
||||
condition={!background}
|
||||
wrap={(children) => <Link href={previousVideoPath}>{children}</Link>}
|
||||
>
|
||||
<StyledPlayerBarControl
|
||||
icon={faBackwardStep}
|
||||
isCircle
|
||||
onClick={() => playPreviousTrack(!background)}
|
||||
/>
|
||||
</ConditionalWrapper>
|
||||
) : (
|
||||
<StyledPlayerBarControl
|
||||
icon={faBackwardStep}
|
||||
isCircle
|
||||
disabled
|
||||
/>
|
||||
)}
|
||||
<StyledPlayerBarControl
|
||||
icon={isPlaying ? faPause : <Icon icon={faPlay} color="text-disabled" style={{ transform: "translateX(2px)" }} />}
|
||||
isCircle
|
||||
onClick={togglePlay}
|
||||
/>
|
||||
{nextVideoPath ? (
|
||||
<ConditionalWrapper
|
||||
condition={!background}
|
||||
wrap={(children) => <Link href={nextVideoPath}>{children}</Link>}
|
||||
>
|
||||
<StyledPlayerBarControl
|
||||
icon={faForwardStep}
|
||||
isCircle
|
||||
onClick={() => playNextTrack(!background)}
|
||||
/>
|
||||
</ConditionalWrapper>
|
||||
) : (
|
||||
<StyledPlayerBarControl
|
||||
icon={faForwardStep}
|
||||
isCircle
|
||||
disabled
|
||||
/>
|
||||
)}
|
||||
</StyledPlayerBarControls>
|
||||
<StyledPlayerBarActions>
|
||||
<PlaylistTrackAddDialog
|
||||
video={{
|
||||
// Flip the structure on it's head,
|
||||
// because we need video as the root object here.
|
||||
...video,
|
||||
entries: [{
|
||||
...entry,
|
||||
theme,
|
||||
}],
|
||||
}}
|
||||
/>
|
||||
<Menu modal={false}>
|
||||
<MenuTrigger asChild>
|
||||
<Button style={{ "--gap": "8px" }}>
|
||||
<Icon icon={faShare}/>
|
||||
<Text>Share</Text>
|
||||
</Button>
|
||||
</MenuTrigger>
|
||||
<MenuContent>
|
||||
<MenuItem onSelect={() => saveToClipboard(location.href)}>Copy URL to this Page</MenuItem>
|
||||
{audioMode === AudioMode.ENABLED ? (
|
||||
<MenuItem onSelect={() => saveToClipboard(audioUrl)}>Copy URL to Embeddable Audio</MenuItem>
|
||||
) : (
|
||||
<MenuItem onSelect={() => saveToClipboard(videoUrl)}>Copy URL to Embeddable Video</MenuItem>
|
||||
)}
|
||||
</MenuContent>
|
||||
</Menu>
|
||||
<IconTextButton
|
||||
icon={faXmark}
|
||||
isCircle
|
||||
disabled={!background}
|
||||
onClick={() => setCurrentWatchListItem(null)}
|
||||
/>
|
||||
</StyledPlayerBarActions>
|
||||
<ProgressBar />
|
||||
</StyledPlayerBar>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
import { Row } from "components/box";
|
||||
import { IconTextButton } from "components/button";
|
||||
import { faCheck, faCog, faCompress, faExpand } from "@fortawesome/pro-solid-svg-icons";
|
||||
import styled from "styled-components";
|
||||
import { Fragment, useContext } from "react";
|
||||
import FullscreenContext from "context/fullscreenContext";
|
||||
import { Menu, MenuContent, MenuItem, MenuLabel, MenuSeparator, MenuTrigger } from "components/menu/Menu";
|
||||
import { Text } from "components/text";
|
||||
import { ThemeEntryTags } from "components/tag/ThemeEntryTags";
|
||||
import Link from "next/link";
|
||||
import createVideoSlug from "utils/createVideoSlug";
|
||||
import { Icon } from "components/icon";
|
||||
import { VideoTags } from "components/tag/VideoTags";
|
||||
import type { VideoPageProps } from "pages/anime/[animeSlug]/[videoSlug]";
|
||||
import PlayerContext from "context/playerContext";
|
||||
import { VideoPlayerContext } from "components/video-player-2/VideoPlayer2";
|
||||
import useSetting from "hooks/useSetting";
|
||||
import { AudioMode } from "utils/settings";
|
||||
import { StyledPlaybackArea, StyledPlayer } from "components/video-player-2/VideoPlayer2.style";
|
||||
|
||||
const StyledOverlay = styled.div`
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
padding: 16px;
|
||||
|
||||
opacity: 0;
|
||||
transition: opacity 500ms;
|
||||
|
||||
${StyledPlayer}:not([data-relaxed]) ${StyledPlaybackArea}:hover &, &:hover {
|
||||
opacity: 1;
|
||||
transition: opacity 100ms;
|
||||
}
|
||||
`;
|
||||
const StyledOverlayButton = styled(IconTextButton)`
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
backdrop-filter: blur(5px);
|
||||
`;
|
||||
|
||||
export function VideoPlayerOverlay({ anime, themeIndex, entryIndex, videoIndex }: VideoPageProps) {
|
||||
const theme = anime.themes[themeIndex];
|
||||
const entry = theme.entries[entryIndex];
|
||||
const video = entry.videos[videoIndex];
|
||||
|
||||
const { watchList, setWatchList, currentWatchListItem } = useContext(PlayerContext);
|
||||
const { isFullscreen, toggleFullscreen, } = useContext(FullscreenContext);
|
||||
const context = useContext(VideoPlayerContext);
|
||||
const [audioMode] = useSetting(AudioMode, { storageSync: false });
|
||||
|
||||
if (!context) {
|
||||
throw new Error("VideoPlayerOverlay needs to be inside VideoPlayer!");
|
||||
}
|
||||
|
||||
const {
|
||||
updateAudioMode,
|
||||
} = context;
|
||||
|
||||
const otherEntries = theme.entries.map((otherEntry) => {
|
||||
const videos = otherEntry.videos;
|
||||
|
||||
if (!videos.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
...otherEntry,
|
||||
videos
|
||||
};
|
||||
}).filter((otherEntry) => !!otherEntry);
|
||||
|
||||
return (
|
||||
<StyledOverlay>
|
||||
<Row style={{ "--gap": "16px" }}>
|
||||
<Menu modal={false}>
|
||||
<MenuTrigger asChild>
|
||||
<StyledOverlayButton icon={faCog} isCircle />
|
||||
</MenuTrigger>
|
||||
<MenuContent collisionPadding={16}>
|
||||
<MenuLabel>
|
||||
<Text variant="small">Media type</Text>
|
||||
</MenuLabel>
|
||||
<MenuItem onSelect={() => updateAudioMode(AudioMode.DISABLED)}>
|
||||
<Icon icon={faCheck} color={audioMode === AudioMode.DISABLED ? "text-primary" : "transparent"} />
|
||||
Video
|
||||
</MenuItem>
|
||||
<MenuItem onSelect={() => updateAudioMode(AudioMode.ENABLED)}>
|
||||
<Icon icon={faCheck} color={audioMode === AudioMode.ENABLED ? "text-primary" : "transparent"} />
|
||||
Audio
|
||||
</MenuItem>
|
||||
<MenuSeparator />
|
||||
{otherEntries.map((otherEntry) => otherEntry ? (
|
||||
<Fragment key={otherEntry.version ?? 1}>
|
||||
<MenuLabel>
|
||||
<Row style={{ "--gap": "8px", "--align-items": "baseline" }}>
|
||||
<Text variant="small">Version {otherEntry.version || 1}</Text>
|
||||
<ThemeEntryTags entry={otherEntry}/>
|
||||
</Row>
|
||||
</MenuLabel>
|
||||
{otherEntry.videos.map((otherVideo) => (
|
||||
<MenuItem key={otherVideo.basename} asChild onSelect={() => {
|
||||
const currentItemIndex = watchList.findIndex((item) => item.watchListId === currentWatchListItem?.watchListId);
|
||||
const currentItem = watchList[currentItemIndex];
|
||||
|
||||
const newItem = {
|
||||
watchListId: currentItem.watchListId,
|
||||
...otherVideo,
|
||||
entries: [
|
||||
{
|
||||
...otherEntry,
|
||||
theme,
|
||||
},
|
||||
],
|
||||
};
|
||||
const newWatchList = [...watchList];
|
||||
|
||||
newWatchList[currentItemIndex] = newItem;
|
||||
|
||||
setWatchList(newWatchList);
|
||||
}}>
|
||||
<Link href={`/anime/${anime.slug}/${createVideoSlug(theme, otherEntry, otherVideo)}`}>
|
||||
<Icon icon={faCheck} color={otherVideo.basename === video.basename ? "text-primary" : "transparent"} />
|
||||
<VideoTags video={otherVideo} hideTextOnMobile />
|
||||
</Link>
|
||||
</MenuItem>
|
||||
))}
|
||||
</Fragment>
|
||||
) : null)}
|
||||
</MenuContent>
|
||||
</Menu>
|
||||
<StyledOverlayButton icon={isFullscreen ? faCompress : faExpand} isCircle onClick={toggleFullscreen} />
|
||||
</Row>
|
||||
</StyledOverlay>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { createContext } from "react";
|
||||
|
||||
interface FullscreenContextValue {
|
||||
isFullscreen: boolean;
|
||||
toggleFullscreen(): void;
|
||||
}
|
||||
|
||||
const FullscreenContext = createContext<FullscreenContextValue>({
|
||||
isFullscreen: false,
|
||||
toggleFullscreen() {
|
||||
// Do nothing.
|
||||
}
|
||||
});
|
||||
|
||||
export default FullscreenContext;
|
||||
+48
-48
@@ -130,7 +130,7 @@ export type Entry = {
|
||||
id: Scalars['Int'];
|
||||
nsfw: Scalars['Boolean'];
|
||||
spoiler: Scalars['Boolean'];
|
||||
theme: Maybe<Theme>;
|
||||
theme: Theme;
|
||||
version: Maybe<Scalars['Int']>;
|
||||
videos: Array<Video>;
|
||||
};
|
||||
@@ -451,7 +451,7 @@ export type Synonym = {
|
||||
};
|
||||
|
||||
export type Theme = {
|
||||
anime: Maybe<Anime>;
|
||||
anime: Anime;
|
||||
entries: Array<Entry>;
|
||||
group: Maybe<Scalars['String']>;
|
||||
id: Scalars['Int'];
|
||||
@@ -555,7 +555,7 @@ export type Year = {
|
||||
value: Scalars['Int'];
|
||||
};
|
||||
|
||||
export type BracketThemeSummaryCardConstestantFragment = { name: string, source: string, image: string, theme: { slug: string, type: string, sequence: number | null, group: string | null, id: number, anime: { slug: string, name: string, images: Array<{ link: string, facet: string | null }> } | null, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }> } | null, entries: Array<{ version: number | null, videos: Array<{ id: number, basename: string, tags: string, audio: { basename: string } }> }> } | null };
|
||||
export type BracketThemeSummaryCardConstestantFragment = { name: string, source: string, image: string, theme: { slug: string, type: string, sequence: number | null, group: string | null, id: number, anime: { slug: string, name: string, images: Array<{ link: string, facet: string | null }> }, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }> } | null, entries: Array<{ version: number | null, videos: Array<{ id: number, basename: string, tags: string, audio: { basename: string } }> }> } | null };
|
||||
|
||||
export type VideoButtonAnimeFragment = { slug: string };
|
||||
|
||||
@@ -567,7 +567,7 @@ export type VideoButtonVideoFragment = { id: number, tags: string, resolution: n
|
||||
|
||||
export type AnimeSummaryCardAnimeFragment = { slug: string, name: string, year: number | null, season: string | null, themes: Array<{ group: string | null }>, images: Array<{ link: string, facet: string | null }> };
|
||||
|
||||
export type AnimeSummaryCardAnimeExpandableFragment = { themes: Array<{ group: string | null, type: string, sequence: number | null, slug: string, anime: { slug: string } | null, entries: Array<{ version: number | null, episodes: string | null, spoiler: boolean, nsfw: boolean, videos: Array<{ tags: string, resolution: number | null, nc: boolean, subbed: boolean, lyrics: boolean, uncen: boolean, source: VideoSource | null, overlap: VideoOverlap }> }>, song: { title: string | null } | null }> };
|
||||
export type AnimeSummaryCardAnimeExpandableFragment = { themes: Array<{ group: string | null, type: string, sequence: number | null, slug: string, anime: { slug: string }, entries: Array<{ version: number | null, episodes: string | null, spoiler: boolean, nsfw: boolean, videos: Array<{ tags: string, resolution: number | null, nc: boolean, subbed: boolean, lyrics: boolean, uncen: boolean, source: VideoSource | null, overlap: VideoOverlap }> }>, song: { title: string | null } | null }> };
|
||||
|
||||
export type ArtistSummaryCardArtistFragment = { slug: string, name: string, images: Array<{ link: string, facet: string | null }> };
|
||||
|
||||
@@ -575,26 +575,26 @@ export type PlaylistSummaryCardPlaylistFragment = { id: string, name: string, vi
|
||||
|
||||
export type PlaylistSummaryCardShowOwnerFragment = { user: { name: string } };
|
||||
|
||||
export type ThemeDetailCardThemeFragment = { slug: string, type: string, sequence: number | null, group: string | null, id: number, anime: { slug: string, name: string, images: Array<{ link: string, facet: string | null }> } | null, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }> } | null, entries: Array<{ version: number | null, episodes: string | null, spoiler: boolean, nsfw: boolean, videos: Array<{ filename: string, tags: string, id: number, basename: string, resolution: number | null, nc: boolean, subbed: boolean, lyrics: boolean, uncen: boolean, source: VideoSource | null, overlap: VideoOverlap, audio: { basename: string } }> }> };
|
||||
export type ThemeDetailCardThemeFragment = { slug: string, type: string, sequence: number | null, group: string | null, id: number, anime: { slug: string, name: string, images: Array<{ link: string, facet: string | null }> }, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }> } | null, entries: Array<{ version: number | null, episodes: string | null, spoiler: boolean, nsfw: boolean, videos: Array<{ filename: string, tags: string, id: number, basename: string, resolution: number | null, nc: boolean, subbed: boolean, lyrics: boolean, uncen: boolean, source: VideoSource | null, overlap: VideoOverlap, audio: { basename: string } }> }> };
|
||||
|
||||
export type ThemeSummaryCardThemeFragment = { slug: string, type: string, sequence: number | null, group: string | null, id: number, anime: { slug: string, name: string, images: Array<{ link: string, facet: string | null }> } | null, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }> } | null, entries: Array<{ version: number | null, videos: Array<{ id: number, basename: string, tags: string, audio: { basename: string } }> }> };
|
||||
export type ThemeSummaryCardThemeFragment = { slug: string, type: string, sequence: number | null, group: string | null, id: number, anime: { slug: string, name: string, images: Array<{ link: string, facet: string | null }> }, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }> } | null, entries: Array<{ version: number | null, videos: Array<{ id: number, basename: string, tags: string, audio: { basename: string } }> }> };
|
||||
|
||||
export type ThemeSummaryCardArtistFragment = { slug: string };
|
||||
|
||||
export type ThemeSummaryCardThemeExpandableFragment = { type: string, sequence: number | null, slug: string, anime: { slug: string } | null, entries: Array<{ version: number | null, episodes: string | null, spoiler: boolean, nsfw: boolean, videos: Array<{ tags: string, resolution: number | null, nc: boolean, subbed: boolean, lyrics: boolean, uncen: boolean, source: VideoSource | null, overlap: VideoOverlap }> }>, song: { title: string | null } | null };
|
||||
export type ThemeSummaryCardThemeExpandableFragment = { type: string, sequence: number | null, slug: string, anime: { slug: string }, entries: Array<{ version: number | null, episodes: string | null, spoiler: boolean, nsfw: boolean, videos: Array<{ tags: string, resolution: number | null, nc: boolean, subbed: boolean, lyrics: boolean, uncen: boolean, source: VideoSource | null, overlap: VideoOverlap }> }>, song: { title: string | null } | null };
|
||||
|
||||
export type ThemeSummaryCardQueryVariables = Exact<{
|
||||
themeId: Scalars['Int'];
|
||||
}>;
|
||||
|
||||
|
||||
export type ThemeSummaryCardQuery = { theme: { slug: string, type: string, sequence: number | null, group: string | null, id: number, anime: { year: number | null, season: string | null, slug: string, name: string, images: Array<{ link: string, facet: string | null }> } | null, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }> } | null, entries: Array<{ version: number | null, videos: Array<{ id: number, basename: string, tags: string, audio: { basename: string } }> }> } | null };
|
||||
export type ThemeSummaryCardQuery = { theme: { slug: string, type: string, sequence: number | null, group: string | null, id: number, anime: { year: number | null, season: string | null, slug: string, name: string, images: Array<{ link: string, facet: string | null }> }, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }> } | null, entries: Array<{ version: number | null, videos: Array<{ id: number, basename: string, tags: string, audio: { basename: string } }> }> } | null };
|
||||
|
||||
export type VideoSummaryCardVideoFragment = { id: number, basename: string, tags: string, entries: Array<{ version: number | null, theme: { id: number, slug: string, type: string, sequence: number | null, group: string | null, anime: { slug: string, name: string, images: Array<{ link: string, facet: string | null }> } | null, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }> } | null } | null }>, audio: { basename: string } };
|
||||
export type VideoSummaryCardVideoFragment = { id: number, basename: string, tags: string, entries: Array<{ version: number | null, theme: { id: number, slug: string, type: string, sequence: number | null, group: string | null, anime: { slug: string, name: string, images: Array<{ link: string, facet: string | null }> }, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }> } | null } }>, audio: { basename: string } };
|
||||
|
||||
export type PlaylistRemoveDialogPlaylistFragment = { id: string, name: string, visibility: PlaylistVisibility, tracks_count: number };
|
||||
|
||||
export type PlaylistTrackAddDialogVideoFragment = { id: number, basename: string, tags: string, entries: Array<{ version: number | null, theme: { id: number, slug: string, type: string, sequence: number | null, group: string | null, anime: { slug: string, name: string, images: Array<{ link: string, facet: string | null }> } | null, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }> } | null } | null }>, audio: { basename: string } };
|
||||
export type PlaylistTrackAddDialogVideoFragment = { id: number, basename: string, tags: string, entries: Array<{ version: number | null, theme: { id: number, slug: string, type: string, sequence: number | null, group: string | null, anime: { slug: string, name: string, images: Array<{ link: string, facet: string | null }> }, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }> } | null } }>, audio: { basename: string } };
|
||||
|
||||
export type PlaylistTrackAddFormPlaylistQueryVariables = Exact<{
|
||||
filterVideoId: Scalars['Int'];
|
||||
@@ -605,11 +605,11 @@ export type PlaylistTrackAddFormPlaylistQuery = { me: { playlistAll: Array<{ id:
|
||||
|
||||
export type PlaylistTrackRemoveDialogPlaylistFragment = { id: string, name: string };
|
||||
|
||||
export type PlaylistTrackRemoveDialogVideoFragment = { id: number, basename: string, tags: string, entries: Array<{ version: number | null, theme: { id: number, slug: string, type: string, sequence: number | null, group: string | null, anime: { slug: string, name: string, images: Array<{ link: string, facet: string | null }> } | null, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }> } | null } | null }>, audio: { basename: string } };
|
||||
export type PlaylistTrackRemoveDialogVideoFragment = { id: number, basename: string, tags: string, entries: Array<{ version: number | null, theme: { id: number, slug: string, type: string, sequence: number | null, group: string | null, anime: { slug: string, name: string, images: Array<{ link: string, facet: string | null }> }, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }> } | null } }>, audio: { basename: string } };
|
||||
|
||||
export type FeaturedThemeThemeFragment = { slug: string, type: string, sequence: number | null, group: string | null, id: number, anime: { slug: string, name: string, images: Array<{ link: string, facet: string | null }> } | null, entries: Array<{ version: number | null, videos: Array<{ basename: string, id: number, tags: string, audio: { basename: string } }> }>, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }> } | null };
|
||||
export type FeaturedThemeThemeFragment = { slug: string, type: string, sequence: number | null, group: string | null, id: number, anime: { slug: string, name: string, images: Array<{ link: string, facet: string | null }> }, entries: Array<{ version: number | null, videos: Array<{ basename: string, id: number, tags: string, audio: { basename: string } }> }>, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }> } | null };
|
||||
|
||||
export type AnimeThemeFilterThemeFragment = { type: string, group: string | null, slug: string, sequence: number | null, id: number, anime: { slug: string, name: string, images: Array<{ link: string, facet: string | null }> } | null, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }> } | null, entries: Array<{ version: number | null, episodes: string | null, spoiler: boolean, nsfw: boolean, videos: Array<{ filename: string, tags: string, id: number, basename: string, resolution: number | null, nc: boolean, subbed: boolean, lyrics: boolean, uncen: boolean, source: VideoSource | null, overlap: VideoOverlap, audio: { basename: string } }> }> };
|
||||
export type AnimeThemeFilterThemeFragment = { type: string, group: string | null, slug: string, sequence: number | null, id: number, anime: { slug: string, name: string, images: Array<{ link: string, facet: string | null }> }, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }> } | null, entries: Array<{ version: number | null, episodes: string | null, spoiler: boolean, nsfw: boolean, videos: Array<{ filename: string, tags: string, id: number, basename: string, resolution: number | null, nc: boolean, subbed: boolean, lyrics: boolean, uncen: boolean, source: VideoSource | null, overlap: VideoOverlap, audio: { basename: string } }> }> };
|
||||
|
||||
type CoverImageResourceWithImages_Anime_Fragment = { images: Array<{ link: string, facet: string | null }> };
|
||||
|
||||
@@ -631,14 +631,14 @@ export type ProfileImageUserFragment = { name: string, email: string };
|
||||
|
||||
export type StudioCoverImageStudioFragment = { images: Array<{ link: string, facet: string | null }>, anime: Array<{ name: string, images: Array<{ link: string, facet: string | null }> }> };
|
||||
|
||||
export type ThemeMenuThemeFragment = { id: number, slug: string, type: string, sequence: number | null, group: string | null, anime: { slug: string, name: string, images: Array<{ link: string, facet: string | null }> } | null, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }> } | null, entries: Array<{ version: number | null, videos: Array<{ id: number, basename: string, tags: string, audio: { basename: string } }> }> };
|
||||
export type ThemeMenuThemeFragment = { id: number, slug: string, type: string, sequence: number | null, group: string | null, anime: { slug: string, name: string, images: Array<{ link: string, facet: string | null }> }, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }> } | null, entries: Array<{ version: number | null, videos: Array<{ id: number, basename: string, tags: string, audio: { basename: string } }> }> };
|
||||
|
||||
export type SearchAnimeQueryVariables = Exact<{
|
||||
args: SearchArgs;
|
||||
}>;
|
||||
|
||||
|
||||
export type SearchAnimeQuery = { searchAnime: { nextPage: number | null, data: Array<{ slug: string, name: string, year: number | null, season: string | null, themes: Array<{ group: string | null, type: string, sequence: number | null, slug: string, anime: { slug: string } | null, entries: Array<{ version: number | null, episodes: string | null, spoiler: boolean, nsfw: boolean, videos: Array<{ tags: string, resolution: number | null, nc: boolean, subbed: boolean, lyrics: boolean, uncen: boolean, source: VideoSource | null, overlap: VideoOverlap }> }>, song: { title: string | null } | null }>, images: Array<{ link: string, facet: string | null }> }> } };
|
||||
export type SearchAnimeQuery = { searchAnime: { nextPage: number | null, data: Array<{ slug: string, name: string, year: number | null, season: string | null, themes: Array<{ group: string | null, type: string, sequence: number | null, slug: string, anime: { slug: string }, entries: Array<{ version: number | null, episodes: string | null, spoiler: boolean, nsfw: boolean, videos: Array<{ tags: string, resolution: number | null, nc: boolean, subbed: boolean, lyrics: boolean, uncen: boolean, source: VideoSource | null, overlap: VideoOverlap }> }>, song: { title: string | null } | null }>, images: Array<{ link: string, facet: string | null }> }> } };
|
||||
|
||||
export type SearchArtistQueryVariables = Exact<{
|
||||
args: SearchArgs;
|
||||
@@ -652,7 +652,7 @@ export type SearchGlobalQueryVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type SearchGlobalQuery = { search: { anime: Array<{ slug: string, name: string, year: number | null, season: string | null, themes: Array<{ group: string | null, type: string, sequence: number | null, slug: string, anime: { slug: string } | null, entries: Array<{ version: number | null, episodes: string | null, spoiler: boolean, nsfw: boolean, videos: Array<{ tags: string, resolution: number | null, nc: boolean, subbed: boolean, lyrics: boolean, uncen: boolean, source: VideoSource | null, overlap: VideoOverlap }> }>, song: { title: string | null } | null }>, images: Array<{ link: string, facet: string | null }> }>, themes: Array<{ slug: string, type: string, sequence: number | null, group: string | null, id: number, anime: { slug: string, name: string, images: Array<{ link: string, facet: string | null }> } | null, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }> } | null, entries: Array<{ version: number | null, episodes: string | null, spoiler: boolean, nsfw: boolean, videos: Array<{ id: number, basename: string, tags: string, resolution: number | null, nc: boolean, subbed: boolean, lyrics: boolean, uncen: boolean, source: VideoSource | null, overlap: VideoOverlap, audio: { basename: string } }> }> }>, artists: Array<{ slug: string, name: string, images: Array<{ link: string, facet: string | null }> }>, series: Array<{ slug: string, name: string }>, studios: Array<{ slug: string, name: string }>, playlists: Array<{ id: string, name: string, visibility: PlaylistVisibility, tracks_count: number, user: { name: string } }> } };
|
||||
export type SearchGlobalQuery = { search: { anime: Array<{ slug: string, name: string, year: number | null, season: string | null, themes: Array<{ group: string | null, type: string, sequence: number | null, slug: string, anime: { slug: string }, entries: Array<{ version: number | null, episodes: string | null, spoiler: boolean, nsfw: boolean, videos: Array<{ tags: string, resolution: number | null, nc: boolean, subbed: boolean, lyrics: boolean, uncen: boolean, source: VideoSource | null, overlap: VideoOverlap }> }>, song: { title: string | null } | null }>, images: Array<{ link: string, facet: string | null }> }>, themes: Array<{ slug: string, type: string, sequence: number | null, group: string | null, id: number, anime: { slug: string, name: string, images: Array<{ link: string, facet: string | null }> }, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }> } | null, entries: Array<{ version: number | null, episodes: string | null, spoiler: boolean, nsfw: boolean, videos: Array<{ id: number, basename: string, tags: string, resolution: number | null, nc: boolean, subbed: boolean, lyrics: boolean, uncen: boolean, source: VideoSource | null, overlap: VideoOverlap, audio: { basename: string } }> }> }>, artists: Array<{ slug: string, name: string, images: Array<{ link: string, facet: string | null }> }>, series: Array<{ slug: string, name: string }>, studios: Array<{ slug: string, name: string }>, playlists: Array<{ id: string, name: string, visibility: PlaylistVisibility, tracks_count: number, user: { name: string } }> } };
|
||||
|
||||
export type SearchPlaylistQueryVariables = Exact<{
|
||||
args: SearchArgs;
|
||||
@@ -680,9 +680,9 @@ export type SearchThemeQueryVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type SearchThemeQuery = { searchTheme: { nextPage: number | null, data: Array<{ slug: string, type: string, sequence: number | null, group: string | null, id: number, anime: { slug: string, name: string, images: Array<{ link: string, facet: string | null }> } | null, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }> } | null, entries: Array<{ version: number | null, episodes: string | null, spoiler: boolean, nsfw: boolean, videos: Array<{ id: number, basename: string, tags: string, resolution: number | null, nc: boolean, subbed: boolean, lyrics: boolean, uncen: boolean, source: VideoSource | null, overlap: VideoOverlap, audio: { basename: string } }> }> }> } };
|
||||
export type SearchThemeQuery = { searchTheme: { nextPage: number | null, data: Array<{ slug: string, type: string, sequence: number | null, group: string | null, id: number, anime: { slug: string, name: string, images: Array<{ link: string, facet: string | null }> }, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }> } | null, entries: Array<{ version: number | null, episodes: string | null, spoiler: boolean, nsfw: boolean, videos: Array<{ id: number, basename: string, tags: string, resolution: number | null, nc: boolean, subbed: boolean, lyrics: boolean, uncen: boolean, source: VideoSource | null, overlap: VideoOverlap, audio: { basename: string } }> }> }> } };
|
||||
|
||||
export type ThemeTableThemeFragment = { type: string, sequence: number | null, slug: string, anime: { slug: string } | null, entries: Array<{ version: number | null, episodes: string | null, spoiler: boolean, nsfw: boolean, videos: Array<{ tags: string, resolution: number | null, nc: boolean, subbed: boolean, lyrics: boolean, uncen: boolean, source: VideoSource | null, overlap: VideoOverlap }> }>, song: { title: string | null } | null };
|
||||
export type ThemeTableThemeFragment = { type: string, sequence: number | null, slug: string, anime: { slug: string }, entries: Array<{ version: number | null, episodes: string | null, spoiler: boolean, nsfw: boolean, videos: Array<{ tags: string, resolution: number | null, nc: boolean, subbed: boolean, lyrics: boolean, uncen: boolean, source: VideoSource | null, overlap: VideoOverlap }> }>, song: { title: string | null } | null };
|
||||
|
||||
export type ContentWarningTagsEntryFragment = { spoiler: boolean, nsfw: boolean };
|
||||
|
||||
@@ -696,11 +696,11 @@ export type PlaylistRemoveToastPlaylistFragment = { id: string, name: string };
|
||||
|
||||
export type PlaylistTrackAddToastPlaylistFragment = { id: string, name: string };
|
||||
|
||||
export type PlaylistTrackAddToastVideoFragment = { entries: Array<{ theme: { song: { title: string | null } | null } | null }> };
|
||||
export type PlaylistTrackAddToastVideoFragment = { entries: Array<{ theme: { song: { title: string | null } | null } }> };
|
||||
|
||||
export type PlaylistTrackRemoveToastPlaylistFragment = { id: string, name: string };
|
||||
|
||||
export type PlaylistTrackRemoveToastVideoFragment = { entries: Array<{ theme: { song: { title: string | null } | null } | null }> };
|
||||
export type PlaylistTrackRemoveToastVideoFragment = { entries: Array<{ theme: { song: { title: string | null } | null } }> };
|
||||
|
||||
export type PerformancesSongFragment = { performances: Array<{ as: string | null, artist: { slug: string, name: string } }> };
|
||||
|
||||
@@ -730,7 +730,7 @@ export type CheckAuthQuery = { me: { user: { id: number, name: string, email: st
|
||||
export type RandomThemeQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type RandomThemeQuery = { searchTheme: { data: Array<{ slug: string, type: string, sequence: number | null, group: string | null, id: number, entries: Array<{ version: number | null, episodes: string | null, spoiler: boolean, nsfw: boolean, videos: Array<{ basename: string, id: number, tags: string, resolution: number | null, nc: boolean, subbed: boolean, lyrics: boolean, uncen: boolean, source: VideoSource | null, overlap: VideoOverlap, audio: { basename: string } }> }>, anime: { slug: string, name: string, images: Array<{ link: string, facet: string | null }> } | null, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }> } | null }> } };
|
||||
export type RandomThemeQuery = { searchTheme: { data: Array<{ slug: string, type: string, sequence: number | null, group: string | null, id: number, entries: Array<{ version: number | null, episodes: string | null, spoiler: boolean, nsfw: boolean, videos: Array<{ basename: string, id: number, tags: string, resolution: number | null, nc: boolean, subbed: boolean, lyrics: boolean, uncen: boolean, source: VideoSource | null, overlap: VideoOverlap, audio: { basename: string } }> }>, anime: { slug: string, name: string, images: Array<{ link: string, facet: string | null }> }, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }> } | null }> } };
|
||||
|
||||
export type DumpIndexPageQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
@@ -758,33 +758,33 @@ export type TransparencyPageAllQueryVariables = Exact<{ [key: string]: never; }>
|
||||
|
||||
export type TransparencyPageAllQuery = { balanceAll: Array<{ id: number, date: string, service: string, frequency: string, usage: number, month_to_date_balance: number }>, transactionAll: Array<{ id: number, date: string, service: string, description: string, amount: number }> };
|
||||
|
||||
export type VideoPageAnimeFragment = { name: string, slug: string, year: number | null, season: string | null, themes: Array<{ id: number, slug: string, type: string, sequence: number | null, group: string | null, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string, images: Array<{ link: string, facet: string | null }> } }> } | null, entries: Array<{ episodes: string | null, nsfw: boolean, spoiler: boolean, version: number | null, videos: Array<{ id: number, basename: string, filename: string, lyrics: boolean, nc: boolean, overlap: VideoOverlap, resolution: number | null, source: VideoSource | null, subbed: boolean, uncen: boolean, tags: string, entries: Array<{ theme: { slug: string, type: string, sequence: number | null, group: string | null, id: number, anime: { slug: string, name: string, images: Array<{ link: string, facet: string | null }> } | null, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }> } | null, entries: Array<{ version: number | null, videos: Array<{ id: number, basename: string, tags: string, audio: { basename: string } }> }> } | null }>, audio: { basename: string }, script: { link: string } | null }> }>, anime: { slug: string, name: string, images: Array<{ link: string, facet: string | null }> } | null }>, images: Array<{ facet: string | null, link: string }>, series: Array<{ slug: string, name: string }>, studios: Array<{ slug: string, name: string }> };
|
||||
export type VideoPageAnimeFragment = { name: string, slug: string, year: number | null, season: string | null, themes: Array<{ id: number, slug: string, type: string, sequence: number | null, group: string | null, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string, images: Array<{ link: string, facet: string | null }> } }> } | null, entries: Array<{ episodes: string | null, nsfw: boolean, spoiler: boolean, version: number | null, videos: Array<{ id: number, basename: string, filename: string, lyrics: boolean, nc: boolean, overlap: VideoOverlap, resolution: number | null, source: VideoSource | null, subbed: boolean, uncen: boolean, tags: string, entries: Array<{ theme: { slug: string, type: string, sequence: number | null, group: string | null, id: number, anime: { slug: string, name: string, images: Array<{ link: string, facet: string | null }> }, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }> } | null, entries: Array<{ version: number | null, videos: Array<{ id: number, basename: string, tags: string, audio: { basename: string } }> }> } }>, audio: { basename: string }, script: { link: string } | null }> }>, anime: { slug: string, name: string, images: Array<{ link: string, facet: string | null }> } }>, images: Array<{ facet: string | null, link: string }>, series: Array<{ slug: string, name: string }>, studios: Array<{ slug: string, name: string }> };
|
||||
|
||||
export type VideoPageQueryVariables = Exact<{
|
||||
animeSlug: Scalars['String'];
|
||||
}>;
|
||||
|
||||
|
||||
export type VideoPageQuery = { anime: { name: string, slug: string, year: number | null, season: string | null, themes: Array<{ id: number, slug: string, type: string, sequence: number | null, group: string | null, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string, images: Array<{ link: string, facet: string | null }> } }> } | null, entries: Array<{ episodes: string | null, nsfw: boolean, spoiler: boolean, version: number | null, videos: Array<{ id: number, basename: string, filename: string, lyrics: boolean, nc: boolean, overlap: VideoOverlap, resolution: number | null, source: VideoSource | null, subbed: boolean, uncen: boolean, tags: string, entries: Array<{ theme: { slug: string, type: string, sequence: number | null, group: string | null, id: number, anime: { slug: string, name: string, images: Array<{ link: string, facet: string | null }> } | null, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }> } | null, entries: Array<{ version: number | null, videos: Array<{ id: number, basename: string, tags: string, audio: { basename: string } }> }> } | null }>, audio: { basename: string }, script: { link: string } | null }> }>, anime: { slug: string, name: string, images: Array<{ link: string, facet: string | null }> } | null }>, images: Array<{ facet: string | null, link: string }>, series: Array<{ slug: string, name: string }>, studios: Array<{ slug: string, name: string }> } | null };
|
||||
export type VideoPageQuery = { anime: { name: string, slug: string, year: number | null, season: string | null, themes: Array<{ id: number, slug: string, type: string, sequence: number | null, group: string | null, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string, images: Array<{ link: string, facet: string | null }> } }> } | null, entries: Array<{ episodes: string | null, nsfw: boolean, spoiler: boolean, version: number | null, videos: Array<{ id: number, basename: string, filename: string, lyrics: boolean, nc: boolean, overlap: VideoOverlap, resolution: number | null, source: VideoSource | null, subbed: boolean, uncen: boolean, tags: string, entries: Array<{ theme: { slug: string, type: string, sequence: number | null, group: string | null, id: number, anime: { slug: string, name: string, images: Array<{ link: string, facet: string | null }> }, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }> } | null, entries: Array<{ version: number | null, videos: Array<{ id: number, basename: string, tags: string, audio: { basename: string } }> }> } }>, audio: { basename: string }, script: { link: string } | null }> }>, anime: { slug: string, name: string, images: Array<{ link: string, facet: string | null }> } }>, images: Array<{ facet: string | null, link: string }>, series: Array<{ slug: string, name: string }>, studios: Array<{ slug: string, name: string }> } | null };
|
||||
|
||||
export type VideoPageAllQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type VideoPageAllQuery = { animeAll: Array<{ name: string, slug: string, year: number | null, season: string | null, themes: Array<{ id: number, slug: string, type: string, sequence: number | null, group: string | null, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string, images: Array<{ link: string, facet: string | null }> } }> } | null, entries: Array<{ episodes: string | null, nsfw: boolean, spoiler: boolean, version: number | null, videos: Array<{ id: number, basename: string, filename: string, lyrics: boolean, nc: boolean, overlap: VideoOverlap, resolution: number | null, source: VideoSource | null, subbed: boolean, uncen: boolean, tags: string, entries: Array<{ theme: { slug: string, type: string, sequence: number | null, group: string | null, id: number, anime: { slug: string, name: string, images: Array<{ link: string, facet: string | null }> } | null, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }> } | null, entries: Array<{ version: number | null, videos: Array<{ id: number, basename: string, tags: string, audio: { basename: string } }> }> } | null }>, audio: { basename: string }, script: { link: string } | null }> }>, anime: { slug: string, name: string, images: Array<{ link: string, facet: string | null }> } | null }>, images: Array<{ facet: string | null, link: string }>, series: Array<{ slug: string, name: string }>, studios: Array<{ slug: string, name: string }> }> };
|
||||
export type VideoPageAllQuery = { animeAll: Array<{ name: string, slug: string, year: number | null, season: string | null, themes: Array<{ id: number, slug: string, type: string, sequence: number | null, group: string | null, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string, images: Array<{ link: string, facet: string | null }> } }> } | null, entries: Array<{ episodes: string | null, nsfw: boolean, spoiler: boolean, version: number | null, videos: Array<{ id: number, basename: string, filename: string, lyrics: boolean, nc: boolean, overlap: VideoOverlap, resolution: number | null, source: VideoSource | null, subbed: boolean, uncen: boolean, tags: string, entries: Array<{ theme: { slug: string, type: string, sequence: number | null, group: string | null, id: number, anime: { slug: string, name: string, images: Array<{ link: string, facet: string | null }> }, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }> } | null, entries: Array<{ version: number | null, videos: Array<{ id: number, basename: string, tags: string, audio: { basename: string } }> }> } }>, audio: { basename: string }, script: { link: string } | null }> }>, anime: { slug: string, name: string, images: Array<{ link: string, facet: string | null }> } }>, images: Array<{ facet: string | null, link: string }>, series: Array<{ slug: string, name: string }>, studios: Array<{ slug: string, name: string }> }> };
|
||||
|
||||
export type AnimeDetailPageAnimeFragment = { slug: string, name: string, season: string | null, year: number | null, synopsis: string | null, synonyms: Array<{ text: string | null }>, series: Array<{ slug: string, name: string }>, studios: Array<{ slug: string, name: string }>, resources: Array<{ site: string | null, link: string | null, as: string | null }>, themes: Array<{ slug: string, type: string, sequence: number | null, group: string | null, id: number, anime: { slug: string, name: string, images: Array<{ link: string, facet: string | null }> } | null, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }> } | null, entries: Array<{ version: number | null, episodes: string | null, spoiler: boolean, nsfw: boolean, videos: Array<{ filename: string, tags: string, id: number, basename: string, resolution: number | null, nc: boolean, subbed: boolean, lyrics: boolean, uncen: boolean, source: VideoSource | null, overlap: VideoOverlap, audio: { basename: string } }> }> }>, images: Array<{ link: string, facet: string | null }> };
|
||||
export type AnimeDetailPageAnimeFragment = { slug: string, name: string, season: string | null, year: number | null, synopsis: string | null, synonyms: Array<{ text: string | null }>, series: Array<{ slug: string, name: string }>, studios: Array<{ slug: string, name: string }>, resources: Array<{ site: string | null, link: string | null, as: string | null }>, themes: Array<{ slug: string, type: string, sequence: number | null, group: string | null, id: number, anime: { slug: string, name: string, images: Array<{ link: string, facet: string | null }> }, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }> } | null, entries: Array<{ version: number | null, episodes: string | null, spoiler: boolean, nsfw: boolean, videos: Array<{ filename: string, tags: string, id: number, basename: string, resolution: number | null, nc: boolean, subbed: boolean, lyrics: boolean, uncen: boolean, source: VideoSource | null, overlap: VideoOverlap, audio: { basename: string } }> }> }>, images: Array<{ link: string, facet: string | null }> };
|
||||
|
||||
export type AnimeDetailPageQueryVariables = Exact<{
|
||||
animeSlug: Scalars['String'];
|
||||
}>;
|
||||
|
||||
|
||||
export type AnimeDetailPageQuery = { anime: { slug: string, name: string, season: string | null, year: number | null, synopsis: string | null, synonyms: Array<{ text: string | null }>, series: Array<{ slug: string, name: string }>, studios: Array<{ slug: string, name: string }>, resources: Array<{ site: string | null, link: string | null, as: string | null }>, themes: Array<{ slug: string, type: string, sequence: number | null, group: string | null, id: number, anime: { slug: string, name: string, images: Array<{ link: string, facet: string | null }> } | null, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }> } | null, entries: Array<{ version: number | null, episodes: string | null, spoiler: boolean, nsfw: boolean, videos: Array<{ filename: string, tags: string, id: number, basename: string, resolution: number | null, nc: boolean, subbed: boolean, lyrics: boolean, uncen: boolean, source: VideoSource | null, overlap: VideoOverlap, audio: { basename: string } }> }> }>, images: Array<{ link: string, facet: string | null }> } | null };
|
||||
export type AnimeDetailPageQuery = { anime: { slug: string, name: string, season: string | null, year: number | null, synopsis: string | null, synonyms: Array<{ text: string | null }>, series: Array<{ slug: string, name: string }>, studios: Array<{ slug: string, name: string }>, resources: Array<{ site: string | null, link: string | null, as: string | null }>, themes: Array<{ slug: string, type: string, sequence: number | null, group: string | null, id: number, anime: { slug: string, name: string, images: Array<{ link: string, facet: string | null }> }, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }> } | null, entries: Array<{ version: number | null, episodes: string | null, spoiler: boolean, nsfw: boolean, videos: Array<{ filename: string, tags: string, id: number, basename: string, resolution: number | null, nc: boolean, subbed: boolean, lyrics: boolean, uncen: boolean, source: VideoSource | null, overlap: VideoOverlap, audio: { basename: string } }> }> }>, images: Array<{ link: string, facet: string | null }> } | null };
|
||||
|
||||
export type AnimeDetailPageAllQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type AnimeDetailPageAllQuery = { animeAll: Array<{ slug: string, name: string, season: string | null, year: number | null, synopsis: string | null, synonyms: Array<{ text: string | null }>, series: Array<{ slug: string, name: string }>, studios: Array<{ slug: string, name: string }>, resources: Array<{ site: string | null, link: string | null, as: string | null }>, themes: Array<{ slug: string, type: string, sequence: number | null, group: string | null, id: number, anime: { slug: string, name: string, images: Array<{ link: string, facet: string | null }> } | null, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }> } | null, entries: Array<{ version: number | null, episodes: string | null, spoiler: boolean, nsfw: boolean, videos: Array<{ filename: string, tags: string, id: number, basename: string, resolution: number | null, nc: boolean, subbed: boolean, lyrics: boolean, uncen: boolean, source: VideoSource | null, overlap: VideoOverlap, audio: { basename: string } }> }> }>, images: Array<{ link: string, facet: string | null }> }> };
|
||||
export type AnimeDetailPageAllQuery = { animeAll: Array<{ slug: string, name: string, season: string | null, year: number | null, synopsis: string | null, synonyms: Array<{ text: string | null }>, series: Array<{ slug: string, name: string }>, studios: Array<{ slug: string, name: string }>, resources: Array<{ site: string | null, link: string | null, as: string | null }>, themes: Array<{ slug: string, type: string, sequence: number | null, group: string | null, id: number, anime: { slug: string, name: string, images: Array<{ link: string, facet: string | null }> }, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }> } | null, entries: Array<{ version: number | null, episodes: string | null, spoiler: boolean, nsfw: boolean, videos: Array<{ filename: string, tags: string, id: number, basename: string, resolution: number | null, nc: boolean, subbed: boolean, lyrics: boolean, uncen: boolean, source: VideoSource | null, overlap: VideoOverlap, audio: { basename: string } }> }> }>, images: Array<{ link: string, facet: string | null }> }> };
|
||||
|
||||
export type AnimeIndexPageQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
@@ -798,35 +798,35 @@ export type RevalidateAnimeQueryVariables = Exact<{
|
||||
|
||||
export type RevalidateAnimeQuery = { anime: { year: number | null, season: string | null, series: Array<{ slug: string }>, studios: Array<{ slug: string }>, themes: Array<{ slug: string, song: { performances: Array<{ artist: { slug: string } }> } | null, entries: Array<{ version: number | null, videos: Array<{ tags: string }> }> }> } | null };
|
||||
|
||||
export type ArtistDetailPageArtistFragment = { slug: string, name: string, performances: Array<{ as: string | null, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }>, themes: Array<{ id: number, slug: string, group: string | null, type: string, sequence: number | null, anime: { slug: string, year: number | null, season: string | null, name: string, images: Array<{ link: string, facet: string | null }> } | null, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }> } | null, entries: Array<{ version: number | null, episodes: string | null, spoiler: boolean, nsfw: boolean, videos: Array<{ id: number, basename: string, tags: string, resolution: number | null, nc: boolean, subbed: boolean, lyrics: boolean, uncen: boolean, source: VideoSource | null, overlap: VideoOverlap, audio: { basename: string } }> }> }> } }>, members: Array<{ as: string | null, member: { slug: string, name: string } }>, groups: Array<{ as: string | null, group: { slug: string, name: string } }>, resources: Array<{ link: string | null, site: string | null, as: string | null }>, images: Array<{ facet: string | null, link: string }> };
|
||||
export type ArtistDetailPageArtistFragment = { slug: string, name: string, performances: Array<{ as: string | null, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }>, themes: Array<{ id: number, slug: string, group: string | null, type: string, sequence: number | null, anime: { slug: string, year: number | null, season: string | null, name: string, images: Array<{ link: string, facet: string | null }> }, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }> } | null, entries: Array<{ version: number | null, episodes: string | null, spoiler: boolean, nsfw: boolean, videos: Array<{ id: number, basename: string, tags: string, resolution: number | null, nc: boolean, subbed: boolean, lyrics: boolean, uncen: boolean, source: VideoSource | null, overlap: VideoOverlap, audio: { basename: string } }> }> }> } }>, members: Array<{ as: string | null, member: { slug: string, name: string } }>, groups: Array<{ as: string | null, group: { slug: string, name: string } }>, resources: Array<{ link: string | null, site: string | null, as: string | null }>, images: Array<{ facet: string | null, link: string }> };
|
||||
|
||||
export type ArtistDetailPageQueryVariables = Exact<{
|
||||
artistSlug: Scalars['String'];
|
||||
}>;
|
||||
|
||||
|
||||
export type ArtistDetailPageQuery = { artist: { slug: string, name: string, performances: Array<{ as: string | null, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }>, themes: Array<{ id: number, slug: string, group: string | null, type: string, sequence: number | null, anime: { slug: string, year: number | null, season: string | null, name: string, images: Array<{ link: string, facet: string | null }> } | null, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }> } | null, entries: Array<{ version: number | null, episodes: string | null, spoiler: boolean, nsfw: boolean, videos: Array<{ id: number, basename: string, tags: string, resolution: number | null, nc: boolean, subbed: boolean, lyrics: boolean, uncen: boolean, source: VideoSource | null, overlap: VideoOverlap, audio: { basename: string } }> }> }> } }>, members: Array<{ as: string | null, member: { slug: string, name: string } }>, groups: Array<{ as: string | null, group: { slug: string, name: string } }>, resources: Array<{ link: string | null, site: string | null, as: string | null }>, images: Array<{ facet: string | null, link: string }> } | null };
|
||||
export type ArtistDetailPageQuery = { artist: { slug: string, name: string, performances: Array<{ as: string | null, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }>, themes: Array<{ id: number, slug: string, group: string | null, type: string, sequence: number | null, anime: { slug: string, year: number | null, season: string | null, name: string, images: Array<{ link: string, facet: string | null }> }, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }> } | null, entries: Array<{ version: number | null, episodes: string | null, spoiler: boolean, nsfw: boolean, videos: Array<{ id: number, basename: string, tags: string, resolution: number | null, nc: boolean, subbed: boolean, lyrics: boolean, uncen: boolean, source: VideoSource | null, overlap: VideoOverlap, audio: { basename: string } }> }> }> } }>, members: Array<{ as: string | null, member: { slug: string, name: string } }>, groups: Array<{ as: string | null, group: { slug: string, name: string } }>, resources: Array<{ link: string | null, site: string | null, as: string | null }>, images: Array<{ facet: string | null, link: string }> } | null };
|
||||
|
||||
export type ArtistDetailPageAllQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type ArtistDetailPageAllQuery = { artistAll: Array<{ slug: string, name: string, performances: Array<{ as: string | null, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }>, themes: Array<{ id: number, slug: string, group: string | null, type: string, sequence: number | null, anime: { slug: string, year: number | null, season: string | null, name: string, images: Array<{ link: string, facet: string | null }> } | null, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }> } | null, entries: Array<{ version: number | null, episodes: string | null, spoiler: boolean, nsfw: boolean, videos: Array<{ id: number, basename: string, tags: string, resolution: number | null, nc: boolean, subbed: boolean, lyrics: boolean, uncen: boolean, source: VideoSource | null, overlap: VideoOverlap, audio: { basename: string } }> }> }> } }>, members: Array<{ as: string | null, member: { slug: string, name: string } }>, groups: Array<{ as: string | null, group: { slug: string, name: string } }>, resources: Array<{ link: string | null, site: string | null, as: string | null }>, images: Array<{ facet: string | null, link: string }> }> };
|
||||
export type ArtistDetailPageAllQuery = { artistAll: Array<{ slug: string, name: string, performances: Array<{ as: string | null, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }>, themes: Array<{ id: number, slug: string, group: string | null, type: string, sequence: number | null, anime: { slug: string, year: number | null, season: string | null, name: string, images: Array<{ link: string, facet: string | null }> }, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }> } | null, entries: Array<{ version: number | null, episodes: string | null, spoiler: boolean, nsfw: boolean, videos: Array<{ id: number, basename: string, tags: string, resolution: number | null, nc: boolean, subbed: boolean, lyrics: boolean, uncen: boolean, source: VideoSource | null, overlap: VideoOverlap, audio: { basename: string } }> }> }> } }>, members: Array<{ as: string | null, member: { slug: string, name: string } }>, groups: Array<{ as: string | null, group: { slug: string, name: string } }>, resources: Array<{ link: string | null, site: string | null, as: string | null }>, images: Array<{ facet: string | null, link: string }> }> };
|
||||
|
||||
export type ArtistIndexPageQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type ArtistIndexPageQuery = { artistAll: Array<{ slug: string, name: string }> };
|
||||
|
||||
export type CharacterFragmentFragment = { id: number, seed: number, name: string, source: string, image: string, theme: { slug: string, type: string, sequence: number | null, group: string | null, id: number, anime: { slug: string, name: string, images: Array<{ link: string, facet: string | null }> } | null, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }> } | null, entries: Array<{ version: number | null, videos: Array<{ id: number, basename: string, tags: string, audio: { basename: string } }> }> } | null };
|
||||
export type CharacterFragmentFragment = { id: number, seed: number, name: string, source: string, image: string, theme: { slug: string, type: string, sequence: number | null, group: string | null, id: number, anime: { slug: string, name: string, images: Array<{ link: string, facet: string | null }> }, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }> } | null, entries: Array<{ version: number | null, videos: Array<{ id: number, basename: string, tags: string, audio: { basename: string } }> }> } | null };
|
||||
|
||||
export type RoundFragmentFragment = { tier: number, name: string | null, pairings: Array<{ order: number, group: number, votesA: number | null, votesB: number | null, characterA: { id: number, seed: number, name: string, source: string, image: string, theme: { slug: string, type: string, sequence: number | null, group: string | null, id: number, anime: { slug: string, name: string, images: Array<{ link: string, facet: string | null }> } | null, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }> } | null, entries: Array<{ version: number | null, videos: Array<{ id: number, basename: string, tags: string, audio: { basename: string } }> }> } | null }, characterB: { id: number, seed: number, name: string, source: string, image: string, theme: { slug: string, type: string, sequence: number | null, group: string | null, id: number, anime: { slug: string, name: string, images: Array<{ link: string, facet: string | null }> } | null, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }> } | null, entries: Array<{ version: number | null, videos: Array<{ id: number, basename: string, tags: string, audio: { basename: string } }> }> } | null } }> };
|
||||
export type RoundFragmentFragment = { tier: number, name: string | null, pairings: Array<{ order: number, group: number, votesA: number | null, votesB: number | null, characterA: { id: number, seed: number, name: string, source: string, image: string, theme: { slug: string, type: string, sequence: number | null, group: string | null, id: number, anime: { slug: string, name: string, images: Array<{ link: string, facet: string | null }> }, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }> } | null, entries: Array<{ version: number | null, videos: Array<{ id: number, basename: string, tags: string, audio: { basename: string } }> }> } | null }, characterB: { id: number, seed: number, name: string, source: string, image: string, theme: { slug: string, type: string, sequence: number | null, group: string | null, id: number, anime: { slug: string, name: string, images: Array<{ link: string, facet: string | null }> }, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }> } | null, entries: Array<{ version: number | null, videos: Array<{ id: number, basename: string, tags: string, audio: { basename: string } }> }> } | null } }> };
|
||||
|
||||
export type BracketPageQueryVariables = Exact<{
|
||||
bracketSlug: Scalars['String'];
|
||||
}>;
|
||||
|
||||
|
||||
export type BracketPageQuery = { bracket: { name: string, currentGroup: number | null, currentRound: { tier: number, name: string | null, pairings: Array<{ order: number, group: number, votesA: number | null, votesB: number | null, characterA: { id: number, seed: number, name: string, source: string, image: string, theme: { slug: string, type: string, sequence: number | null, group: string | null, id: number, anime: { slug: string, name: string, images: Array<{ link: string, facet: string | null }> } | null, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }> } | null, entries: Array<{ version: number | null, videos: Array<{ id: number, basename: string, tags: string, audio: { basename: string } }> }> } | null }, characterB: { id: number, seed: number, name: string, source: string, image: string, theme: { slug: string, type: string, sequence: number | null, group: string | null, id: number, anime: { slug: string, name: string, images: Array<{ link: string, facet: string | null }> } | null, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }> } | null, entries: Array<{ version: number | null, videos: Array<{ id: number, basename: string, tags: string, audio: { basename: string } }> }> } | null } }> } | null, rounds: Array<{ tier: number, name: string | null, pairings: Array<{ order: number, group: number, votesA: number | null, votesB: number | null, characterA: { id: number, seed: number, name: string, source: string, image: string, theme: { slug: string, type: string, sequence: number | null, group: string | null, id: number, anime: { slug: string, name: string, images: Array<{ link: string, facet: string | null }> } | null, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }> } | null, entries: Array<{ version: number | null, videos: Array<{ id: number, basename: string, tags: string, audio: { basename: string } }> }> } | null }, characterB: { id: number, seed: number, name: string, source: string, image: string, theme: { slug: string, type: string, sequence: number | null, group: string | null, id: number, anime: { slug: string, name: string, images: Array<{ link: string, facet: string | null }> } | null, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }> } | null, entries: Array<{ version: number | null, videos: Array<{ id: number, basename: string, tags: string, audio: { basename: string } }> }> } | null } }> }> } | null };
|
||||
export type BracketPageQuery = { bracket: { name: string, currentGroup: number | null, currentRound: { tier: number, name: string | null, pairings: Array<{ order: number, group: number, votesA: number | null, votesB: number | null, characterA: { id: number, seed: number, name: string, source: string, image: string, theme: { slug: string, type: string, sequence: number | null, group: string | null, id: number, anime: { slug: string, name: string, images: Array<{ link: string, facet: string | null }> }, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }> } | null, entries: Array<{ version: number | null, videos: Array<{ id: number, basename: string, tags: string, audio: { basename: string } }> }> } | null }, characterB: { id: number, seed: number, name: string, source: string, image: string, theme: { slug: string, type: string, sequence: number | null, group: string | null, id: number, anime: { slug: string, name: string, images: Array<{ link: string, facet: string | null }> }, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }> } | null, entries: Array<{ version: number | null, videos: Array<{ id: number, basename: string, tags: string, audio: { basename: string } }> }> } | null } }> } | null, rounds: Array<{ tier: number, name: string | null, pairings: Array<{ order: number, group: number, votesA: number | null, votesB: number | null, characterA: { id: number, seed: number, name: string, source: string, image: string, theme: { slug: string, type: string, sequence: number | null, group: string | null, id: number, anime: { slug: string, name: string, images: Array<{ link: string, facet: string | null }> }, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }> } | null, entries: Array<{ version: number | null, videos: Array<{ id: number, basename: string, tags: string, audio: { basename: string } }> }> } | null }, characterB: { id: number, seed: number, name: string, source: string, image: string, theme: { slug: string, type: string, sequence: number | null, group: string | null, id: number, anime: { slug: string, name: string, images: Array<{ link: string, facet: string | null }> }, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }> } | null, entries: Array<{ version: number | null, videos: Array<{ id: number, basename: string, tags: string, audio: { basename: string } }> }> } | null } }> }> } | null };
|
||||
|
||||
export type BracketPageAllQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
@@ -838,7 +838,7 @@ export type AwardPageThemeQueryVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type AwardPageThemeQuery = { theme: { id: number, slug: string, anime: { slug: string, name: string, images: Array<{ facet: string | null, link: string }> } | null, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }> } | null, entries: Array<{ version: number | null, videos: Array<{ basename: string, tags: string }> }> } | null };
|
||||
export type AwardPageThemeQuery = { theme: { id: number, slug: string, anime: { slug: string, name: string, images: Array<{ facet: string | null, link: string }> }, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }> } | null, entries: Array<{ version: number | null, videos: Array<{ basename: string, tags: string }> }> } | null };
|
||||
|
||||
export type EventPageQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
@@ -848,31 +848,31 @@ export type EventPageQuery = { bracketAll: Array<{ slug: string, name: string }>
|
||||
export type HomePageRecentlyAddedQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type HomePageRecentlyAddedQuery = { videoAll: Array<{ id: number, basename: string, tags: string, entries: Array<{ version: number | null, theme: { id: number, slug: string, type: string, sequence: number | null, group: string | null, anime: { slug: string, name: string, images: Array<{ link: string, facet: string | null }> } | null, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }> } | null } | null }>, audio: { basename: string } }> };
|
||||
export type HomePageRecentlyAddedQuery = { videoAll: Array<{ id: number, basename: string, tags: string, entries: Array<{ version: number | null, theme: { id: number, slug: string, type: string, sequence: number | null, group: string | null, anime: { slug: string, name: string, images: Array<{ link: string, facet: string | null }> }, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }> } | null } }>, audio: { basename: string } }> };
|
||||
|
||||
export type HomePageMostViewedQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type HomePageMostViewedQuery = { videoAll: Array<{ id: number, basename: string, tags: string, entries: Array<{ version: number | null, theme: { id: number, slug: string, type: string, sequence: number | null, group: string | null, anime: { slug: string, name: string, images: Array<{ link: string, facet: string | null }> } | null, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }> } | null } | null }>, audio: { basename: string } }> };
|
||||
export type HomePageMostViewedQuery = { videoAll: Array<{ id: number, basename: string, tags: string, entries: Array<{ version: number | null, theme: { id: number, slug: string, type: string, sequence: number | null, group: string | null, anime: { slug: string, name: string, images: Array<{ link: string, facet: string | null }> }, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }> } | null } }>, audio: { basename: string } }> };
|
||||
|
||||
export type HomePageQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type HomePageQuery = { featuredTheme: { theme: { slug: string, type: string, sequence: number | null, group: string | null, id: number, anime: { slug: string, name: string, images: Array<{ link: string, facet: string | null }> } | null, entries: Array<{ version: number | null, videos: Array<{ basename: string, id: number, tags: string, audio: { basename: string } }> }>, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }> } | null } } | null, announcementAll: Array<{ content: string }> };
|
||||
export type HomePageQuery = { featuredTheme: { theme: { slug: string, type: string, sequence: number | null, group: string | null, id: number, anime: { slug: string, name: string, images: Array<{ link: string, facet: string | null }> }, entries: Array<{ version: number | null, videos: Array<{ basename: string, id: number, tags: string, audio: { basename: string } }> }>, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }> } | null } } | null, announcementAll: Array<{ content: string }> };
|
||||
|
||||
export type PlaylistDetailPagePlaylistQueryVariables = Exact<{
|
||||
playlistId: Scalars['String'];
|
||||
}>;
|
||||
|
||||
|
||||
export type PlaylistDetailPagePlaylistQuery = { playlist: { id: string, name: string, visibility: PlaylistVisibility, tracks_count: number, forward: Array<{ id: string, video: { id: number, basename: string, tags: string, entries: Array<{ version: number | null, theme: { id: number, slug: string, type: string, sequence: number | null, group: string | null, anime: { year: number | null, season: string | null, slug: string, name: string, images: Array<{ link: string, facet: string | null }> } | null, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }> } | null } | null }>, audio: { basename: string } } }>, user: { name: string } } | null };
|
||||
export type PlaylistDetailPagePlaylistQuery = { playlist: { id: string, name: string, visibility: PlaylistVisibility, tracks_count: number, forward: Array<{ id: string, video: { id: number, basename: string, tags: string, entries: Array<{ version: number | null, theme: { id: number, slug: string, type: string, sequence: number | null, group: string | null, anime: { year: number | null, season: string | null, slug: string, name: string, images: Array<{ link: string, facet: string | null }> }, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }> } | null } }>, audio: { basename: string } } }>, user: { name: string } } | null };
|
||||
|
||||
export type PlaylistDetailPageMeQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type PlaylistDetailPageMeQuery = { me: { user: { name: string } | null } };
|
||||
|
||||
export type PlaylistDetailPagePlaylistFragment = { id: string, name: string, visibility: PlaylistVisibility, tracks_count: number, forward: Array<{ id: string, video: { id: number, basename: string, tags: string, entries: Array<{ version: number | null, theme: { id: number, slug: string, type: string, sequence: number | null, group: string | null, anime: { year: number | null, season: string | null, slug: string, name: string, images: Array<{ link: string, facet: string | null }> } | null, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }> } | null } | null }>, audio: { basename: string } } }>, user: { name: string } };
|
||||
export type PlaylistDetailPagePlaylistFragment = { id: string, name: string, visibility: PlaylistVisibility, tracks_count: number, forward: Array<{ id: string, video: { id: number, basename: string, tags: string, entries: Array<{ version: number | null, theme: { id: number, slug: string, type: string, sequence: number | null, group: string | null, anime: { year: number | null, season: string | null, slug: string, name: string, images: Array<{ link: string, facet: string | null }> }, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }> } | null } }>, audio: { basename: string } } }>, user: { name: string } };
|
||||
|
||||
type PlaylistDetailPageUser_UserAuth_Fragment = { name: string };
|
||||
|
||||
@@ -885,7 +885,7 @@ export type PlaylistDetailPageQueryVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type PlaylistDetailPageQuery = { playlist: { id: string, name: string, visibility: PlaylistVisibility, tracks_count: number, forward: Array<{ id: string, video: { id: number, basename: string, tags: string, entries: Array<{ version: number | null, theme: { id: number, slug: string, type: string, sequence: number | null, group: string | null, anime: { year: number | null, season: string | null, slug: string, name: string, images: Array<{ link: string, facet: string | null }> } | null, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }> } | null } | null }>, audio: { basename: string } } }>, user: { name: string } } | null, me: { user: { name: string } | null } };
|
||||
export type PlaylistDetailPageQuery = { playlist: { id: string, name: string, visibility: PlaylistVisibility, tracks_count: number, forward: Array<{ id: string, video: { id: number, basename: string, tags: string, entries: Array<{ version: number | null, theme: { id: number, slug: string, type: string, sequence: number | null, group: string | null, anime: { year: number | null, season: string | null, slug: string, name: string, images: Array<{ link: string, facet: string | null }> }, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }> } | null } }>, audio: { basename: string } } }>, user: { name: string } } | null, me: { user: { name: string } | null } };
|
||||
|
||||
export type GalleryPageQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
@@ -906,38 +906,38 @@ export type ProfilePageQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
export type ProfilePageQuery = { me: { user: { name: string, email: string, email_verified_at: string | null, created_at: string, roles: Array<{ name: string, color: string | null, priority: number | null, default: boolean }> } | null, playlistAll: Array<{ id: string, name: string, visibility: PlaylistVisibility, tracks_count: number }> | null } };
|
||||
|
||||
export type SeriesDetailPageSeriesFragment = { slug: string, name: string, anime: Array<{ name: string, slug: string, year: number | null, season: string | null, themes: Array<{ slug: string, type: string, sequence: number | null, group: string | null, entries: Array<{ version: number | null, episodes: string | null, spoiler: boolean, nsfw: boolean, videos: Array<{ tags: string, resolution: number | null, nc: boolean, subbed: boolean, lyrics: boolean, uncen: boolean, source: VideoSource | null, overlap: VideoOverlap }> }>, anime: { slug: string } | null, song: { title: string | null } | null }>, images: Array<{ facet: string | null, link: string }> }> };
|
||||
export type SeriesDetailPageSeriesFragment = { slug: string, name: string, anime: Array<{ name: string, slug: string, year: number | null, season: string | null, themes: Array<{ slug: string, type: string, sequence: number | null, group: string | null, entries: Array<{ version: number | null, episodes: string | null, spoiler: boolean, nsfw: boolean, videos: Array<{ tags: string, resolution: number | null, nc: boolean, subbed: boolean, lyrics: boolean, uncen: boolean, source: VideoSource | null, overlap: VideoOverlap }> }>, anime: { slug: string }, song: { title: string | null } | null }>, images: Array<{ facet: string | null, link: string }> }> };
|
||||
|
||||
export type SeriesDetailPageQueryVariables = Exact<{
|
||||
seriesSlug: Scalars['String'];
|
||||
}>;
|
||||
|
||||
|
||||
export type SeriesDetailPageQuery = { series: { slug: string, name: string, anime: Array<{ name: string, slug: string, year: number | null, season: string | null, themes: Array<{ slug: string, type: string, sequence: number | null, group: string | null, entries: Array<{ version: number | null, episodes: string | null, spoiler: boolean, nsfw: boolean, videos: Array<{ tags: string, resolution: number | null, nc: boolean, subbed: boolean, lyrics: boolean, uncen: boolean, source: VideoSource | null, overlap: VideoOverlap }> }>, anime: { slug: string } | null, song: { title: string | null } | null }>, images: Array<{ facet: string | null, link: string }> }> } | null };
|
||||
export type SeriesDetailPageQuery = { series: { slug: string, name: string, anime: Array<{ name: string, slug: string, year: number | null, season: string | null, themes: Array<{ slug: string, type: string, sequence: number | null, group: string | null, entries: Array<{ version: number | null, episodes: string | null, spoiler: boolean, nsfw: boolean, videos: Array<{ tags: string, resolution: number | null, nc: boolean, subbed: boolean, lyrics: boolean, uncen: boolean, source: VideoSource | null, overlap: VideoOverlap }> }>, anime: { slug: string }, song: { title: string | null } | null }>, images: Array<{ facet: string | null, link: string }> }> } | null };
|
||||
|
||||
export type SeriesDetailPageAllQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type SeriesDetailPageAllQuery = { seriesAll: Array<{ slug: string, name: string, anime: Array<{ name: string, slug: string, year: number | null, season: string | null, themes: Array<{ slug: string, type: string, sequence: number | null, group: string | null, entries: Array<{ version: number | null, episodes: string | null, spoiler: boolean, nsfw: boolean, videos: Array<{ tags: string, resolution: number | null, nc: boolean, subbed: boolean, lyrics: boolean, uncen: boolean, source: VideoSource | null, overlap: VideoOverlap }> }>, anime: { slug: string } | null, song: { title: string | null } | null }>, images: Array<{ facet: string | null, link: string }> }> }> };
|
||||
export type SeriesDetailPageAllQuery = { seriesAll: Array<{ slug: string, name: string, anime: Array<{ name: string, slug: string, year: number | null, season: string | null, themes: Array<{ slug: string, type: string, sequence: number | null, group: string | null, entries: Array<{ version: number | null, episodes: string | null, spoiler: boolean, nsfw: boolean, videos: Array<{ tags: string, resolution: number | null, nc: boolean, subbed: boolean, lyrics: boolean, uncen: boolean, source: VideoSource | null, overlap: VideoOverlap }> }>, anime: { slug: string }, song: { title: string | null } | null }>, images: Array<{ facet: string | null, link: string }> }> }> };
|
||||
|
||||
export type SeriesIndexPageQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type SeriesIndexPageQuery = { seriesAll: Array<{ slug: string, name: string }> };
|
||||
|
||||
export type StudioDetailPageStudioFragment = { slug: string, name: string, anime: Array<{ name: string, slug: string, year: number | null, season: string | null, themes: Array<{ slug: string, type: string, sequence: number | null, group: string | null, entries: Array<{ version: number | null, episodes: string | null, spoiler: boolean, nsfw: boolean, videos: Array<{ tags: string, resolution: number | null, nc: boolean, subbed: boolean, lyrics: boolean, uncen: boolean, source: VideoSource | null, overlap: VideoOverlap }> }>, anime: { slug: string } | null, song: { title: string | null } | null }>, images: Array<{ facet: string | null, link: string }> }>, resources: Array<{ link: string | null, site: string | null, as: string | null }>, images: Array<{ link: string, facet: string | null }> };
|
||||
export type StudioDetailPageStudioFragment = { slug: string, name: string, anime: Array<{ name: string, slug: string, year: number | null, season: string | null, themes: Array<{ slug: string, type: string, sequence: number | null, group: string | null, entries: Array<{ version: number | null, episodes: string | null, spoiler: boolean, nsfw: boolean, videos: Array<{ tags: string, resolution: number | null, nc: boolean, subbed: boolean, lyrics: boolean, uncen: boolean, source: VideoSource | null, overlap: VideoOverlap }> }>, anime: { slug: string }, song: { title: string | null } | null }>, images: Array<{ facet: string | null, link: string }> }>, resources: Array<{ link: string | null, site: string | null, as: string | null }>, images: Array<{ link: string, facet: string | null }> };
|
||||
|
||||
export type StudioDetailPageQueryVariables = Exact<{
|
||||
studioSlug: Scalars['String'];
|
||||
}>;
|
||||
|
||||
|
||||
export type StudioDetailPageQuery = { studio: { slug: string, name: string, anime: Array<{ name: string, slug: string, year: number | null, season: string | null, themes: Array<{ slug: string, type: string, sequence: number | null, group: string | null, entries: Array<{ version: number | null, episodes: string | null, spoiler: boolean, nsfw: boolean, videos: Array<{ tags: string, resolution: number | null, nc: boolean, subbed: boolean, lyrics: boolean, uncen: boolean, source: VideoSource | null, overlap: VideoOverlap }> }>, anime: { slug: string } | null, song: { title: string | null } | null }>, images: Array<{ facet: string | null, link: string }> }>, resources: Array<{ link: string | null, site: string | null, as: string | null }>, images: Array<{ link: string, facet: string | null }> } | null };
|
||||
export type StudioDetailPageQuery = { studio: { slug: string, name: string, anime: Array<{ name: string, slug: string, year: number | null, season: string | null, themes: Array<{ slug: string, type: string, sequence: number | null, group: string | null, entries: Array<{ version: number | null, episodes: string | null, spoiler: boolean, nsfw: boolean, videos: Array<{ tags: string, resolution: number | null, nc: boolean, subbed: boolean, lyrics: boolean, uncen: boolean, source: VideoSource | null, overlap: VideoOverlap }> }>, anime: { slug: string }, song: { title: string | null } | null }>, images: Array<{ facet: string | null, link: string }> }>, resources: Array<{ link: string | null, site: string | null, as: string | null }>, images: Array<{ link: string, facet: string | null }> } | null };
|
||||
|
||||
export type StudioDetailPageAllQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type StudioDetailPageAllQuery = { studioAll: Array<{ slug: string, name: string, anime: Array<{ name: string, slug: string, year: number | null, season: string | null, themes: Array<{ slug: string, type: string, sequence: number | null, group: string | null, entries: Array<{ version: number | null, episodes: string | null, spoiler: boolean, nsfw: boolean, videos: Array<{ tags: string, resolution: number | null, nc: boolean, subbed: boolean, lyrics: boolean, uncen: boolean, source: VideoSource | null, overlap: VideoOverlap }> }>, anime: { slug: string } | null, song: { title: string | null } | null }>, images: Array<{ facet: string | null, link: string }> }>, resources: Array<{ link: string | null, site: string | null, as: string | null }>, images: Array<{ link: string, facet: string | null }> }> };
|
||||
export type StudioDetailPageAllQuery = { studioAll: Array<{ slug: string, name: string, anime: Array<{ name: string, slug: string, year: number | null, season: string | null, themes: Array<{ slug: string, type: string, sequence: number | null, group: string | null, entries: Array<{ version: number | null, episodes: string | null, spoiler: boolean, nsfw: boolean, videos: Array<{ tags: string, resolution: number | null, nc: boolean, subbed: boolean, lyrics: boolean, uncen: boolean, source: VideoSource | null, overlap: VideoOverlap }> }>, anime: { slug: string }, song: { title: string | null } | null }>, images: Array<{ facet: string | null, link: string }> }>, resources: Array<{ link: string | null, site: string | null, as: string | null }>, images: Array<{ link: string, facet: string | null }> }> };
|
||||
|
||||
export type StudioIndexPageQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
@@ -967,7 +967,7 @@ export type SeasonDetailPageQueryVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type SeasonDetailPageQuery = { year: { value: number, seasons: Array<{ value: string }> } | null, season: { value: string, anime: Array<{ slug: string, name: string, year: number | null, season: string | null, themes: Array<{ group: string | null, type: string, sequence: number | null, slug: string, anime: { slug: string } | null, entries: Array<{ version: number | null, episodes: string | null, spoiler: boolean, nsfw: boolean, videos: Array<{ tags: string, resolution: number | null, nc: boolean, subbed: boolean, lyrics: boolean, uncen: boolean, source: VideoSource | null, overlap: VideoOverlap }> }>, song: { title: string | null } | null }>, images: Array<{ link: string, facet: string | null }> }> } | null, yearAll: Array<{ value: number }> };
|
||||
export type SeasonDetailPageQuery = { year: { value: number, seasons: Array<{ value: string }> } | null, season: { value: string, anime: Array<{ slug: string, name: string, year: number | null, season: string | null, themes: Array<{ group: string | null, type: string, sequence: number | null, slug: string, anime: { slug: string }, entries: Array<{ version: number | null, episodes: string | null, spoiler: boolean, nsfw: boolean, videos: Array<{ tags: string, resolution: number | null, nc: boolean, subbed: boolean, lyrics: boolean, uncen: boolean, source: VideoSource | null, overlap: VideoOverlap }> }>, song: { title: string | null } | null }>, images: Array<{ link: string, facet: string | null }> }> } | null, yearAll: Array<{ value: number }> };
|
||||
|
||||
export type SeasonDetailPageAllQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
@@ -979,7 +979,7 @@ export type YearDetailPageQueryVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type YearDetailPageQuery = { year: { value: number, seasons: Array<{ value: string, anime: Array<{ slug: string, name: string, year: number | null, season: string | null, themes: Array<{ group: string | null, type: string, sequence: number | null, slug: string, anime: { slug: string } | null, entries: Array<{ version: number | null, episodes: string | null, spoiler: boolean, nsfw: boolean, videos: Array<{ tags: string, resolution: number | null, nc: boolean, subbed: boolean, lyrics: boolean, uncen: boolean, source: VideoSource | null, overlap: VideoOverlap }> }>, song: { title: string | null } | null }>, images: Array<{ link: string, facet: string | null }> }> }> } | null, yearAll: Array<{ value: number }> };
|
||||
export type YearDetailPageQuery = { year: { value: number, seasons: Array<{ value: string, anime: Array<{ slug: string, name: string, year: number | null, season: string | null, themes: Array<{ group: string | null, type: string, sequence: number | null, slug: string, anime: { slug: string }, entries: Array<{ version: number | null, episodes: string | null, spoiler: boolean, nsfw: boolean, videos: Array<{ tags: string, resolution: number | null, nc: boolean, subbed: boolean, lyrics: boolean, uncen: boolean, source: VideoSource | null, overlap: VideoOverlap }> }>, song: { title: string | null } | null }>, images: Array<{ link: string, facet: string | null }> }> }> } | null, yearAll: Array<{ value: number }> };
|
||||
|
||||
export type YearDetailPageAllQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export default function useMouseRelax() {
|
||||
const [isRelaxed, setRelaxed] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
let timeoutId: number;
|
||||
|
||||
function onMouseMove() {
|
||||
setRelaxed(false);
|
||||
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
timeoutId = window.setTimeout(() => setRelaxed(true), 2500);
|
||||
}
|
||||
|
||||
addEventListener("pointermove", onMouseMove);
|
||||
addEventListener("pointerdown", onMouseMove);
|
||||
|
||||
return () => {
|
||||
removeEventListener("pointermove", onMouseMove);
|
||||
removeEventListener("pointerdown", onMouseMove);
|
||||
clearTimeout(timeoutId);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return isRelaxed;
|
||||
}
|
||||
@@ -106,7 +106,7 @@ export const searchResolvers = {
|
||||
searchParams.append("fields[anime]", "name,slug,year,season");
|
||||
searchParams.append("fields[animetheme]", "type,sequence,slug,group,id");
|
||||
searchParams.append("fields[animethemeentry]", "version,episodes,spoiler,nsfw");
|
||||
searchParams.append("fields[video]", "id,tags,resolution,nc,subbed,lyrics,uncen,source,overlap");
|
||||
searchParams.append("fields[video]", "id,tags,resolution,nc,subbed,lyrics,uncen,source,overlap,basename");
|
||||
searchParams.append("fields[image]", "facet,link");
|
||||
searchParams.append("fields[song]", "title");
|
||||
searchParams.append("fields[artist]", "name,slug");
|
||||
|
||||
@@ -78,7 +78,7 @@ const typeDefs = `
|
||||
sequence: Int
|
||||
group: String
|
||||
song: Song
|
||||
anime: Anime
|
||||
anime: Anime!
|
||||
entries: [Entry!]!
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ const typeDefs = `
|
||||
episodes: String
|
||||
nsfw: Boolean!
|
||||
spoiler: Boolean!
|
||||
theme: Theme
|
||||
theme: Theme!
|
||||
videos: [Video!]!
|
||||
}
|
||||
|
||||
|
||||
+32
-5
@@ -31,6 +31,9 @@ import { LazyMotion } from "framer-motion";
|
||||
import { VideoPlayer2 } from "components/video-player-2/VideoPlayer2";
|
||||
import { PageRevalidation } from "components/utils/PageRevalidation";
|
||||
import type { VideoPageProps } from "pages/anime/[animeSlug]/[videoSlug]";
|
||||
import FullscreenContext from "context/fullscreenContext";
|
||||
import { VideoPlayerOverlay } from "components/video-player-2/VideoPlayerOverlay";
|
||||
import { useFullscreen } from "ahooks";
|
||||
|
||||
import "@fortawesome/fontawesome-svg-core/styles.css";
|
||||
import "styles/prism.scss";
|
||||
@@ -86,16 +89,35 @@ export default function MyApp({ Component, pageProps }: AppProps) {
|
||||
return [];
|
||||
});
|
||||
const [watchListFactory, setWatchListFactory] = useState<(() => Promise<WatchListItem[]>) | null>(null);
|
||||
const [currentWatchListItem, setCurrentWatchListItem] = useState<WatchListItem | null>(() => {
|
||||
const [currentWatchListItemId, setCurrentWatchListItemId] = useState<number | null>(() => {
|
||||
if (watchList.length) {
|
||||
return watchList[0];
|
||||
return watchList[0].watchListId;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
const currentWatchListItem = watchList.find((item) => item.watchListId === currentWatchListItemId) ?? null;
|
||||
|
||||
const currentBasename = getBasename(pageProps);
|
||||
const [previousBasename, setPreviousBasename] = useState<string | null>(() => getBasename(pageProps));
|
||||
|
||||
const [isFullscreen, { toggleFullscreen }] = useFullscreen(
|
||||
() => document.documentElement,
|
||||
{
|
||||
onEnter() {
|
||||
document.documentElement.dataset.fullscreen = "true";
|
||||
},
|
||||
onExit() {
|
||||
delete document.documentElement.dataset.fullscreen;
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isVideoPage && isFullscreen) {
|
||||
toggleFullscreen();
|
||||
}
|
||||
}, [isFullscreen, isVideoPage, toggleFullscreen]);
|
||||
|
||||
useEffect(() => {
|
||||
const hotkeyListener = (event: KeyboardEvent) => {
|
||||
if (!pageProps.isSearch && ((event.key === "s" && event.ctrlKey) || event.key === "/")) {
|
||||
@@ -136,7 +158,7 @@ export default function MyApp({ Component, pageProps }: AppProps) {
|
||||
|
||||
setWatchList(watchList);
|
||||
setWatchListFactory(null);
|
||||
setCurrentWatchListItem(watchList[0]);
|
||||
setCurrentWatchListItemId(watchList[0].watchListId);
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -145,6 +167,7 @@ export default function MyApp({ Component, pageProps }: AppProps) {
|
||||
return (
|
||||
<MultiContextProvider providers={[
|
||||
stackContext(ThemeProvider, { theme }),
|
||||
stackContext(FullscreenContext.Provider, { value: { isFullscreen, toggleFullscreen } }),
|
||||
stackContext(ColorThemeContext.Provider, { value: { colorTheme, setColorTheme } }),
|
||||
stackContext(PlayerContext.Provider, { value: {
|
||||
currentVideo: null,
|
||||
@@ -156,7 +179,7 @@ export default function MyApp({ Component, pageProps }: AppProps) {
|
||||
setWatchListFactory: (factory) => setWatchListFactory(() => factory),
|
||||
currentWatchListItem,
|
||||
setCurrentWatchListItem: (watchListItem) => {
|
||||
setCurrentWatchListItem(watchListItem);
|
||||
setCurrentWatchListItemId(watchListItem?.watchListId ?? null);
|
||||
if (watchListFactory && watchList.findIndex((item) => item.watchListId === watchListItem?.watchListId) === watchList.length - 1) {
|
||||
watchListFactory().then((nextWatchList) => {
|
||||
setWatchList([
|
||||
@@ -230,7 +253,11 @@ export default function MyApp({ Component, pageProps }: AppProps) {
|
||||
</>
|
||||
) : null}
|
||||
{currentWatchListItem && (
|
||||
<VideoPlayer2 video={currentWatchListItem} background={!isVideoPage}>
|
||||
<VideoPlayer2
|
||||
video={currentWatchListItem}
|
||||
background={!isVideoPage}
|
||||
overlay={isVideoPage ? <VideoPlayerOverlay {...pageProps} /> : null}
|
||||
>
|
||||
{isVideoPage ? (
|
||||
<Component {...pageProps}/>
|
||||
) : null}
|
||||
|
||||
@@ -16,8 +16,7 @@ import { SwitcherOption } from "components/switcher/Switcher";
|
||||
import { Column, Row } from "components/box";
|
||||
import { VideoSummaryCard } from "components/card/VideoSummaryCard";
|
||||
import { Text } from "components/text";
|
||||
import { ThemeEntryTags } from "components/tag/ThemeEntryTags";
|
||||
import { VideoButton } from "components/button";
|
||||
import { IconTextButton } from "components/button";
|
||||
import { useContext, useState } from "react";
|
||||
import PlayerContext from "context/playerContext";
|
||||
import { DeveloperMode } from "utils/settings";
|
||||
@@ -26,6 +25,7 @@ import useSetting from "hooks/useSetting";
|
||||
import { SEO } from "components/seo";
|
||||
import extractImages from "utils/extractImages";
|
||||
import { VIDEO_URL } from "utils/config";
|
||||
import { faChevronDown, faChevronUp } from "@fortawesome/pro-solid-svg-icons";
|
||||
|
||||
export interface VideoPageProps extends SharedPageProps, RequiredNonNullable<VideoPageQuery> {
|
||||
themeIndex: number
|
||||
@@ -52,27 +52,16 @@ export default function VideoPage({ anime, themeIndex, entryIndex, videoIndex, l
|
||||
const [selectedTab, setSelectedTab] = useState<"watch-list" | "info" | "related">(() => {
|
||||
return watchList.length > 1 ? "watch-list" : "info";
|
||||
});
|
||||
const [showMoreRelatedThemes, setShowMoreRelatedThemes] = useState(false);
|
||||
|
||||
const relatedThemes = anime.themes
|
||||
.filter((relatedTheme) => relatedTheme.slug !== theme.slug);
|
||||
.filter((relatedTheme) => relatedTheme.slug !== theme.slug)
|
||||
.slice(0, showMoreRelatedThemes ? undefined : 4);
|
||||
|
||||
const usedAlsoAs = video.entries
|
||||
.map((entry) => entry.theme)
|
||||
.filter((otherTheme) => otherTheme?.anime && otherTheme.anime.slug !== anime.slug);
|
||||
|
||||
const otherEntries = theme.entries.map(otherEntry => {
|
||||
const videos = otherEntry.videos.filter((otherVideo) => otherVideo.filename !== video.filename);
|
||||
|
||||
if (!videos.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
...otherEntry,
|
||||
videos
|
||||
};
|
||||
}).filter((otherEntry) => !!otherEntry);
|
||||
|
||||
const pageTitle = entry.version
|
||||
? `${songTitle} (${anime.name} ${theme.slug} v${entry.version})`
|
||||
: `${songTitle} (${anime.name} ${theme.slug})`;
|
||||
@@ -190,34 +179,16 @@ export default function VideoPage({ anime, themeIndex, entryIndex, videoIndex, l
|
||||
{relatedThemes.map((theme) => (
|
||||
<ThemeSummaryCard key={theme.slug} theme={{ ...theme, anime }}/>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
{!!otherEntries.length && (
|
||||
<>
|
||||
<Text variant="h2">Other versions</Text>
|
||||
<Column style={{ "--gap": "32px" }}>
|
||||
{otherEntries.map((otherEntry) => otherEntry ? (
|
||||
<Column style={{ "--gap": "16px" }} key={otherEntry.version ?? 1}>
|
||||
<Text color="text-muted">
|
||||
<Row style={{ "--gap": "8px", "--align-items": "baseline" }}>
|
||||
<Text variant="small">Version {otherEntry.version || 1}</Text>
|
||||
<ThemeEntryTags entry={otherEntry}/>
|
||||
</Row>
|
||||
</Text>
|
||||
<Row $wrap style={{ "--gap": "16px" }}>
|
||||
{otherEntry.videos.map((video, index) => (
|
||||
<VideoButton
|
||||
key={index}
|
||||
anime={anime}
|
||||
theme={theme}
|
||||
entry={otherEntry}
|
||||
video={video}
|
||||
/>
|
||||
))}
|
||||
</Row>
|
||||
</Column>
|
||||
) : null)}
|
||||
</Column>
|
||||
{anime.themes.length > 4 ? (
|
||||
<Row style={{ "--justify-content": "center" }}>
|
||||
<IconTextButton
|
||||
icon={showMoreRelatedThemes ? faChevronUp : faChevronDown}
|
||||
variant="silent"
|
||||
isCircle
|
||||
onClick={() => setShowMoreRelatedThemes(!showMoreRelatedThemes)}
|
||||
/>
|
||||
</Row>
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
</Column>
|
||||
|
||||
@@ -39,7 +39,11 @@ export default createGlobalStyle`
|
||||
`}
|
||||
}
|
||||
|
||||
html, body, #__next {
|
||||
html {
|
||||
height: 100dvh;
|
||||
}
|
||||
|
||||
body, #__next {
|
||||
// Every container in the page hierarchy needs to have the
|
||||
// maximum height of the viewport. Using 100vh on innermost container
|
||||
// would break certain layouts on mobile devices.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
interface BaseColors {
|
||||
"transparent": string
|
||||
"white": string
|
||||
"gray-100": string
|
||||
"gray-200": string
|
||||
@@ -52,6 +53,7 @@ export interface Shadows {
|
||||
}
|
||||
|
||||
const baseColors: BaseColors = {
|
||||
"transparent": "#00000000",
|
||||
"white": "#ffffff",
|
||||
"gray-100": "#f5f2fa",
|
||||
"gray-200": "#e6e1f0",
|
||||
|
||||
Reference in New Issue
Block a user