commit
5de174d63e
|
@ -50,6 +50,7 @@ module.exports = {
|
||||||
"max-classes-per-file": ["error", 1],
|
"max-classes-per-file": ["error", 1],
|
||||||
// "max-depth": ["warn", 4],
|
// "max-depth": ["warn", 4],
|
||||||
// "max-lines": "warn",
|
// "max-lines": "warn",
|
||||||
|
"max-len": ["warn", { code: 100 }],
|
||||||
// "max-lines-per-function": "warn",
|
// "max-lines-per-function": "warn",
|
||||||
// "max-nested-callbacks": "warn",
|
// "max-nested-callbacks": "warn",
|
||||||
// "max-params": "warn",
|
// "max-params": "warn",
|
||||||
|
|
21
README.md
21
README.md
|
@ -25,6 +25,7 @@
|
||||||
#### [Front](https://github.com/Accords-Library/accords-library.com) (this repository)
|
#### [Front](https://github.com/Accords-Library/accords-library.com) (this repository)
|
||||||
|
|
||||||
- Language: [TypeScript](https://www.typescriptlang.org/)
|
- Language: [TypeScript](https://www.typescriptlang.org/)
|
||||||
|
- Framework: [Next.js](https://nextjs.org/) (React)
|
||||||
- Queries: [GraphQL Code Generator](https://www.graphql-code-generator.com/)
|
- Queries: [GraphQL Code Generator](https://www.graphql-code-generator.com/)
|
||||||
- Fetch the GraphQL schema from the GraphQL back-end endpoint
|
- Fetch the GraphQL schema from the GraphQL back-end endpoint
|
||||||
- Read the operations and fragments stored as graphql files in the `src/graphql` folder
|
- Read the operations and fragments stored as graphql files in the `src/graphql` folder
|
||||||
|
@ -33,28 +34,34 @@
|
||||||
- Support for Arbitrary React Components and Component Props!
|
- Support for Arbitrary React Components and Component Props!
|
||||||
- Autogenerated multi-level table of content and anchor links for the different headers
|
- Autogenerated multi-level table of content and anchor links for the different headers
|
||||||
- Styling: [Tailwind CSS](https://tailwindcss.com/)
|
- Styling: [Tailwind CSS](https://tailwindcss.com/)
|
||||||
- Good typographic defaults using [Tailwind/Typography](https://tailwindcss.com/docs/typography-plugin)
|
|
||||||
- Beside the theme declaration no CSS outside of Tailwind CSS
|
|
||||||
- Manually added support for scrollbar styling
|
- Manually added support for scrollbar styling
|
||||||
- Support for [Material Icons](https://fonts.google.com/icons)
|
- Support for [Material Icons](https://fonts.google.com/icons)
|
||||||
- Support for light and dark mode with a manual switch and system's selected theme by default
|
|
||||||
- Support for creating any arbitrary theming mode by swapping CSS variables
|
- Support for creating any arbitrary theming mode by swapping CSS variables
|
||||||
- Support for many screen sizes and resolutions
|
- Support for many screen sizes and resolutions
|
||||||
- Framework: [Next.js](https://nextjs.org/) (React)
|
|
||||||
- Multilanguage support
|
|
||||||
- State Management: [React Context](https://reactjs.org/docs/context.html)
|
- State Management: [React Context](https://reactjs.org/docs/context.html)
|
||||||
- Persistent app state using LocalStorage
|
- Persistent app state using LocalStorage
|
||||||
|
- Accessibility
|
||||||
|
- Gestures using [react-swipeable](https://www.npmjs.com/package/react-swipeable)
|
||||||
|
- Keyboard hotkeys using [react-hot-keys](https://www.npmjs.com/package/react-hot-keys)
|
||||||
|
- Support for light and dark mode with a manual switch and system's selected theme by default
|
||||||
|
- Fonts can be swaped to [OpenDyslexic](https://www.npmjs.com/package/@fontsource/opendyslexic)
|
||||||
|
- Multilingual
|
||||||
|
- By default, use the browser's language as the main language
|
||||||
|
- Fallback languages are used for content which are not available in the main language
|
||||||
|
- Main and fallback languages can be ordered manually by the user
|
||||||
|
- At the content level, the user can know which language is available
|
||||||
|
- Furthermore, the user can temporary select another language then the one that was automatically selected
|
||||||
- SSG + ISR (Static Site Generation + Incremental Static Regeneration):
|
- SSG + ISR (Static Site Generation + Incremental Static Regeneration):
|
||||||
- The website is built before running in production
|
- The website is built before running in production
|
||||||
- Performances are great, and possibility to deploy the app using a CDN
|
- Performances are great, and possibility to deploy the app using a CDN
|
||||||
- On-Demand ISR to continuously update the website when new content is added or existing content is modified/deleted.
|
- On-Demand ISR to continuously update the website when new content is added or existing content is modified/deleted
|
||||||
- SEO
|
- SEO
|
||||||
- Good defaults for the metadate and OpenGraph properties
|
- Good defaults for the metadate and OpenGraph properties
|
||||||
- Each page can provide the thumbnail, title, description to be used
|
- Each page can provide the thumbnail, title, description to be used
|
||||||
- Automatic generation of the sitemap using [next-sitemap](https://www.npmjs.com/package/next-sitemap)
|
- Automatic generation of the sitemap using [next-sitemap](https://www.npmjs.com/package/next-sitemap)
|
||||||
- Data quality testing
|
- Data quality testing
|
||||||
- Data from the CMS is subject to a battery of tests (about 20 warning types and 40 error types) at build time
|
- Data from the CMS is subject to a battery of tests (about 20 warning types and 40 error types) at build time
|
||||||
- Each warning/error comes with a front-end link to the incriminating element, as well as a link to the CMS to fix it.
|
- Each warning/error comes with a front-end link to the incriminating element, as well as a link to the CMS to fix it
|
||||||
- Check for completeness, conformity, and integrity
|
- Check for completeness, conformity, and integrity
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
42
package.json
42
package.json
|
@ -16,39 +16,39 @@
|
||||||
"@fontsource/material-icons": "^4.5.4",
|
"@fontsource/material-icons": "^4.5.4",
|
||||||
"@fontsource/material-icons-rounded": "^4.5.4",
|
"@fontsource/material-icons-rounded": "^4.5.4",
|
||||||
"@fontsource/opendyslexic": "^4.5.4",
|
"@fontsource/opendyslexic": "^4.5.4",
|
||||||
"@fontsource/vollkorn": "^4.5.6",
|
"@fontsource/vollkorn": "^4.5.9",
|
||||||
"@fontsource/zen-maru-gothic": "^4.5.8",
|
"@fontsource/zen-maru-gothic": "^4.5.11",
|
||||||
"@tippyjs/react": "^4.2.6",
|
"@tippyjs/react": "^4.2.6",
|
||||||
"autoprefixer": "^10.4.5",
|
"autoprefixer": "^10.4.7",
|
||||||
"graphql-request": "^4.2.0",
|
"graphql-request": "^4.2.0",
|
||||||
"markdown-to-jsx": "^7.1.7",
|
"markdown-to-jsx": "^7.1.7",
|
||||||
"next": "^12.1.2",
|
"next": "^12.1.6",
|
||||||
"nodemailer": "^6.7.3",
|
"nodemailer": "^6.7.5",
|
||||||
"react": "17.0.2",
|
"react": "18.1.0",
|
||||||
"react-dom": "17.0.2",
|
"react-dom": "18.1.0",
|
||||||
"react-image-lightbox": "^5.1.4",
|
"react-hot-keys": "^2.7.2",
|
||||||
"react-swipeable": "^6.2.1",
|
"react-swipeable": "^7.0.0",
|
||||||
"turndown": "^7.1.1"
|
"turndown": "^7.1.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@graphql-codegen/cli": "^2.6.2",
|
"@graphql-codegen/cli": "^2.6.2",
|
||||||
"@graphql-codegen/typescript": "2.4.8",
|
"@graphql-codegen/typescript": "2.4.11",
|
||||||
"@graphql-codegen/typescript-graphql-request": "^4.4.5",
|
"@graphql-codegen/typescript-graphql-request": "^4.4.8",
|
||||||
"@graphql-codegen/typescript-operations": "^2.3.5",
|
"@graphql-codegen/typescript-operations": "^2.4.0",
|
||||||
"@types/node": "17.0.25",
|
"@types/node": "17.0.33",
|
||||||
"@types/nodemailer": "^6.4.4",
|
"@types/nodemailer": "^6.4.4",
|
||||||
"@types/react": "17.0.43",
|
"@types/react": "18.0.9",
|
||||||
"@types/react-dom": "^17.0.14",
|
"@types/react-dom": "^18.0.4",
|
||||||
"@types/turndown": "^5.0.1",
|
"@types/turndown": "^5.0.1",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.20.0",
|
"@typescript-eslint/eslint-plugin": "^5.23.0",
|
||||||
"@typescript-eslint/parser": "^5.20.0",
|
"@typescript-eslint/parser": "^5.23.0",
|
||||||
"eslint": "^8.14.0",
|
"eslint": "^8.15.0",
|
||||||
"eslint-config-next": "12.1.5",
|
"eslint-config-next": "12.1.6",
|
||||||
"graphql": "^14.7.0",
|
"graphql": "^16.5.0",
|
||||||
"next-sitemap": "^2.5.20",
|
"next-sitemap": "^2.5.20",
|
||||||
"prettier": "^2.6.2",
|
"prettier": "^2.6.2",
|
||||||
"prettier-plugin-organize-imports": "^2.3.4",
|
"prettier-plugin-organize-imports": "^2.3.4",
|
||||||
"tailwindcss": "^3.0.24",
|
"tailwindcss": "^3.0.24",
|
||||||
"typescript": "^4.6.3"
|
"typescript": "^4.6.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,23 +1,19 @@
|
||||||
import Button from "components/Inputs/Button";
|
import { Button } from "components/Inputs/Button";
|
||||||
import { useAppLayout } from "contexts/AppLayoutContext";
|
import { useAppLayout } from "contexts/AppLayoutContext";
|
||||||
import { UploadImageFragment } from "graphql/generated";
|
import { UploadImageFragment } from "graphql/generated";
|
||||||
|
import { AppStaticProps } from "graphql/getAppStaticProps";
|
||||||
|
import { prettyLanguage, prettySlug } from "helpers/formatters";
|
||||||
|
import { getOgImage, ImageQuality, OgImage } from "helpers/img";
|
||||||
|
import { Immutable } from "helpers/types";
|
||||||
import { useMediaMobile } from "hooks/useMediaQuery";
|
import { useMediaMobile } from "hooks/useMediaQuery";
|
||||||
import Head from "next/head";
|
import Head from "next/head";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { AppStaticProps } from "queries/getAppStaticProps";
|
|
||||||
import {
|
|
||||||
getOgImage,
|
|
||||||
OgImage,
|
|
||||||
prettyLanguage,
|
|
||||||
prettySlug,
|
|
||||||
} from "queries/helpers";
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useSwipeable } from "react-swipeable";
|
import { useSwipeable } from "react-swipeable";
|
||||||
import { ImageQuality } from "./Img";
|
import { OrderableList } from "./Inputs/OrderableList";
|
||||||
import OrderableList from "./Inputs/OrderableList";
|
import { Select } from "./Inputs/Select";
|
||||||
import Select from "./Inputs/Select";
|
import { MainPanel } from "./Panels/MainPanel";
|
||||||
import MainPanel from "./Panels/MainPanel";
|
import { Popup } from "./Popup";
|
||||||
import Popup from "./Popup";
|
|
||||||
|
|
||||||
interface Props extends AppStaticProps {
|
interface Props extends AppStaticProps {
|
||||||
subPanel?: React.ReactNode;
|
subPanel?: React.ReactNode;
|
||||||
|
@ -29,8 +25,19 @@ interface Props extends AppStaticProps {
|
||||||
description?: string;
|
description?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function AppLayout(props: Props): JSX.Element {
|
export function AppLayout(props: Immutable<Props>): JSX.Element {
|
||||||
const { langui, currencies, languages, subPanel, contentPanel } = props;
|
const {
|
||||||
|
langui,
|
||||||
|
currencies,
|
||||||
|
languages,
|
||||||
|
subPanel,
|
||||||
|
contentPanel,
|
||||||
|
thumbnail,
|
||||||
|
title,
|
||||||
|
navTitle,
|
||||||
|
description,
|
||||||
|
subPanelIcon,
|
||||||
|
} = props;
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const isMobile = useMediaMobile();
|
const isMobile = useMediaMobile();
|
||||||
const appLayout = useAppLayout();
|
const appLayout = useAppLayout();
|
||||||
|
@ -39,19 +46,23 @@ export default function AppLayout(props: Props): JSX.Element {
|
||||||
|
|
||||||
const handlers = useSwipeable({
|
const handlers = useSwipeable({
|
||||||
onSwipedLeft: (SwipeEventData) => {
|
onSwipedLeft: (SwipeEventData) => {
|
||||||
if (SwipeEventData.velocity < sensibilitySwipe) return;
|
if (appLayout.menuGestures) {
|
||||||
if (appLayout.mainPanelOpen) {
|
if (SwipeEventData.velocity < sensibilitySwipe) return;
|
||||||
appLayout.setMainPanelOpen(false);
|
if (appLayout.mainPanelOpen) {
|
||||||
} else if (subPanel && contentPanel) {
|
appLayout.setMainPanelOpen(false);
|
||||||
appLayout.setSubPanelOpen(true);
|
} else if (subPanel && contentPanel) {
|
||||||
|
appLayout.setSubPanelOpen(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onSwipedRight: (SwipeEventData) => {
|
onSwipedRight: (SwipeEventData) => {
|
||||||
if (SwipeEventData.velocity < sensibilitySwipe) return;
|
if (appLayout.menuGestures) {
|
||||||
if (appLayout.subPanelOpen) {
|
if (SwipeEventData.velocity < sensibilitySwipe) return;
|
||||||
appLayout.setSubPanelOpen(false);
|
if (appLayout.subPanelOpen) {
|
||||||
} else {
|
appLayout.setSubPanelOpen(false);
|
||||||
appLayout.setMainPanelOpen(true);
|
} else {
|
||||||
|
appLayout.setMainPanelOpen(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -59,8 +70,8 @@ export default function AppLayout(props: Props): JSX.Element {
|
||||||
const turnSubIntoContent = subPanel && !contentPanel;
|
const turnSubIntoContent = subPanel && !contentPanel;
|
||||||
|
|
||||||
const titlePrefix = "Accord’s Library";
|
const titlePrefix = "Accord’s Library";
|
||||||
const metaImage: OgImage = props.thumbnail
|
const metaImage: OgImage = thumbnail
|
||||||
? getOgImage(ImageQuality.Og, props.thumbnail)
|
? getOgImage(ImageQuality.Og, thumbnail)
|
||||||
: {
|
: {
|
||||||
image: "/default_og.jpg",
|
image: "/default_og.jpg",
|
||||||
width: 1200,
|
width: 1200,
|
||||||
|
@ -68,9 +79,9 @@ export default function AppLayout(props: Props): JSX.Element {
|
||||||
alt: "Accord's Library Logo",
|
alt: "Accord's Library Logo",
|
||||||
};
|
};
|
||||||
const ogTitle =
|
const ogTitle =
|
||||||
props.title ?? props.navTitle ?? prettySlug(router.asPath.split("/").pop());
|
title ?? navTitle ?? prettySlug(router.asPath.split("/").pop());
|
||||||
|
|
||||||
const metaDescription = props.description ?? langui.default_description ?? "";
|
const metaDescription = description ?? langui.default_description ?? "";
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.getElementsByTagName("html")[0].style.fontSize = `${
|
document.getElementsByTagName("html")[0].style.fontSize = `${
|
||||||
|
@ -115,7 +126,7 @@ export default function AppLayout(props: Props): JSX.Element {
|
||||||
}, [currencySelect]);
|
}, [currencySelect]);
|
||||||
|
|
||||||
let gridCol = "";
|
let gridCol = "";
|
||||||
if (props.subPanel) {
|
if (subPanel) {
|
||||||
if (appLayout.mainPanelReduced) {
|
if (appLayout.mainPanelReduced) {
|
||||||
gridCol = "grid-cols-[6rem_20rem_1fr]";
|
gridCol = "grid-cols-[6rem_20rem_1fr]";
|
||||||
} else {
|
} else {
|
||||||
|
@ -140,7 +151,9 @@ export default function AppLayout(props: Props): JSX.Element {
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
{...handlers}
|
{...handlers}
|
||||||
className={`fixed inset-0 touch-pan-y p-0 m-0 bg-light text-black grid [grid-template-areas:'main_sub_content'] ${gridCol} mobile:grid-cols-[1fr] mobile:grid-rows-[1fr_5rem] mobile:[grid-template-areas:'content''navbar']`}
|
className={`fixed inset-0 touch-pan-y p-0 m-0 bg-light text-black grid
|
||||||
|
[grid-template-areas:'main_sub_content'] ${gridCol} mobile:grid-cols-[1fr]
|
||||||
|
mobile:grid-rows-[1fr_5rem] mobile:[grid-template-areas:'content''navbar']`}
|
||||||
>
|
>
|
||||||
<Head>
|
<Head>
|
||||||
<title>{`${titlePrefix} - ${ogTitle}`}</title>
|
<title>{`${titlePrefix} - ${ogTitle}`}</title>
|
||||||
|
@ -172,7 +185,8 @@ export default function AppLayout(props: Props): JSX.Element {
|
||||||
|
|
||||||
{/* Background when navbar is opened */}
|
{/* Background when navbar is opened */}
|
||||||
<div
|
<div
|
||||||
className={`[grid-area:content] mobile:z-10 absolute inset-0 transition-[backdrop-filter] duration-500 ${
|
className={`[grid-area:content] mobile:z-10 absolute
|
||||||
|
inset-0 transition-[backdrop-filter] duration-500 ${
|
||||||
(appLayout.mainPanelOpen || appLayout.subPanelOpen) && isMobile
|
(appLayout.mainPanelOpen || appLayout.subPanelOpen) && isMobile
|
||||||
? "[backdrop-filter:blur(2px)]"
|
? "[backdrop-filter:blur(2px)]"
|
||||||
: "pointer-events-none touch-none "
|
: "pointer-events-none touch-none "
|
||||||
|
@ -201,7 +215,10 @@ export default function AppLayout(props: Props): JSX.Element {
|
||||||
contentPanel
|
contentPanel
|
||||||
) : (
|
) : (
|
||||||
<div className="grid place-content-center h-full">
|
<div className="grid place-content-center h-full">
|
||||||
<div className="text-dark border-dark border-2 border-dotted rounded-2xl p-8 grid grid-flow-col place-items-center gap-9 opacity-40">
|
<div
|
||||||
|
className="text-dark border-dark border-2 border-dotted rounded-2xl
|
||||||
|
p-8 grid grid-flow-col place-items-center gap-9 opacity-40"
|
||||||
|
>
|
||||||
<p className="text-4xl">❮</p>
|
<p className="text-4xl">❮</p>
|
||||||
<p className="text-2xl w-64">{langui.select_option_sidebar}</p>
|
<p className="text-2xl w-64">{langui.select_option_sidebar}</p>
|
||||||
</div>
|
</div>
|
||||||
|
@ -212,7 +229,10 @@ export default function AppLayout(props: Props): JSX.Element {
|
||||||
{/* Sub panel */}
|
{/* Sub panel */}
|
||||||
{subPanel && (
|
{subPanel && (
|
||||||
<div
|
<div
|
||||||
className={`[grid-area:sub] mobile:[grid-area:content] mobile:z-10 mobile:w-[90%] mobile:justify-self-end border-r-[1px] mobile:border-r-0 mobile:border-l-[1px] border-black border-dotted overflow-y-scroll webkit-scrollbar:w-0 [scrollbar-width:none] transition-transform duration-300 bg-light texture-paper-dots
|
className={`[grid-area:sub] mobile:[grid-area:content] mobile:z-10 mobile:w-[90%]
|
||||||
|
mobile:justify-self-end border-r-[1px] mobile:border-r-0 mobile:border-l-[1px]
|
||||||
|
border-black border-dotted overflow-y-scroll webkit-scrollbar:w-0
|
||||||
|
[scrollbar-width:none] transition-transform duration-300 bg-light texture-paper-dots
|
||||||
${
|
${
|
||||||
turnSubIntoContent
|
turnSubIntoContent
|
||||||
? "mobile:border-l-0 mobile:w-full"
|
? "mobile:border-l-0 mobile:w-full"
|
||||||
|
@ -225,14 +245,21 @@ export default function AppLayout(props: Props): JSX.Element {
|
||||||
|
|
||||||
{/* Main panel */}
|
{/* Main panel */}
|
||||||
<div
|
<div
|
||||||
className={`[grid-area:main] mobile:[grid-area:content] mobile:z-10 mobile:w-[90%] mobile:justify-self-start border-r-[1px] border-black border-dotted overflow-y-scroll webkit-scrollbar:w-0 [scrollbar-width:none] transition-transform duration-300 bg-light texture-paper-dots
|
className={`[grid-area:main] mobile:[grid-area:content] mobile:z-10 mobile:w-[90%]
|
||||||
${appLayout.mainPanelOpen ? "" : "mobile:-translate-x-full"}`}
|
mobile:justify-self-start border-r-[1px] border-black border-dotted overflow-y-scroll
|
||||||
|
webkit-scrollbar:w-0 [scrollbar-width:none] transition-transform duration-300 bg-light
|
||||||
|
texture-paper-dots ${
|
||||||
|
appLayout.mainPanelOpen ? "" : "mobile:-translate-x-full"
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
<MainPanel langui={langui} />
|
<MainPanel langui={langui} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Navbar */}
|
{/* Navbar */}
|
||||||
<div className="[grid-area:navbar] border-t-[1px] border-black border-dotted grid grid-cols-[5rem_1fr_5rem] place-items-center desktop:hidden bg-light texture-paper-dots">
|
<div
|
||||||
|
className="[grid-area:navbar] border-t-[1px] border-black border-dotted grid
|
||||||
|
grid-cols-[5rem_1fr_5rem] place-items-center desktop:hidden bg-light texture-paper-dots"
|
||||||
|
>
|
||||||
<span
|
<span
|
||||||
className="material-icons mt-[.1em] cursor-pointer"
|
className="material-icons mt-[.1em] cursor-pointer"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
@ -261,8 +288,8 @@ export default function AppLayout(props: Props): JSX.Element {
|
||||||
{subPanel && !turnSubIntoContent
|
{subPanel && !turnSubIntoContent
|
||||||
? appLayout.subPanelOpen
|
? appLayout.subPanelOpen
|
||||||
? "close"
|
? "close"
|
||||||
: props.subPanelIcon
|
: subPanelIcon
|
||||||
? props.subPanelIcon
|
? subPanelIcon
|
||||||
: "tune"
|
: "tune"
|
||||||
: ""}
|
: ""}
|
||||||
</span>
|
</span>
|
||||||
|
@ -274,7 +301,10 @@ export default function AppLayout(props: Props): JSX.Element {
|
||||||
>
|
>
|
||||||
<h2 className="text-2xl">{langui.settings}</h2>
|
<h2 className="text-2xl">{langui.settings}</h2>
|
||||||
|
|
||||||
<div className="mt-4 grid gap-16 justify-items-center text-center desktop:grid-cols-[auto_auto]">
|
<div
|
||||||
|
className="mt-4 grid gap-16 justify-items-center
|
||||||
|
text-center desktop:grid-cols-[auto_auto]"
|
||||||
|
>
|
||||||
{router.locales && (
|
{router.locales && (
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-xl">{langui.languages}</h3>
|
<h3 className="text-xl">{langui.languages}</h3>
|
||||||
|
@ -295,6 +325,12 @@ export default function AppLayout(props: Props): JSX.Element {
|
||||||
])
|
])
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
insertLabels={
|
||||||
|
new Map([
|
||||||
|
[0, langui.primary_language],
|
||||||
|
[1, langui.secondary_language],
|
||||||
|
])
|
||||||
|
}
|
||||||
onChange={(items) => {
|
onChange={(items) => {
|
||||||
const preferredLanguages = [...items].map(
|
const preferredLanguages = [...items].map(
|
||||||
([code]) => code
|
([code]) => code
|
||||||
|
@ -437,6 +473,7 @@ export default function AppLayout(props: Props): JSX.Element {
|
||||||
(event.target as HTMLInputElement).value
|
(event.target as HTMLInputElement).value
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
value={appLayout.playerName}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,12 +1,16 @@
|
||||||
|
import { Immutable } from "helpers/types";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
className?: string;
|
className?: string;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Chip(props: Props): JSX.Element {
|
export function Chip(props: Immutable<Props>): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`grid place-content-center place-items-center text-xs pb-[0.14rem] whitespace-nowrap px-1.5 border-[1px] rounded-full opacity-70 transition-[color,_opacity,_border-color] hover:opacity-100 ${props.className}`}
|
className={`grid place-content-center place-items-center text-xs pb-[0.14rem]
|
||||||
|
whitespace-nowrap px-1.5 border-[1px] rounded-full opacity-70
|
||||||
|
transition-[color,_opacity,_border-color] hover:opacity-100 ${props.className}`}
|
||||||
>
|
>
|
||||||
{props.children}
|
{props.children}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
|
import { Immutable } from "helpers/types";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function HorizontalLine(props: Props): JSX.Element {
|
export function HorizontalLine(props: Immutable<Props>): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`h-0 w-full my-8 border-t-[3px] border-dotted border-black ${props.className}`}
|
className={`h-0 w-full my-8 border-t-[3px] border-dotted border-black ${props.className}`}
|
||||||
|
|
|
@ -1,106 +1,43 @@
|
||||||
import { UploadImageFragment } from "graphql/generated";
|
import { UploadImageFragment } from "graphql/generated";
|
||||||
import Image, { ImageProps } from "next/image";
|
import { getAssetURL, getImgSizesByQuality, ImageQuality } from "helpers/img";
|
||||||
|
import { Immutable } from "helpers/types";
|
||||||
|
import { ImageProps } from "next/image";
|
||||||
|
import { MouseEventHandler } from "react";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
className?: string;
|
className?: string;
|
||||||
image?: UploadImageFragment | string;
|
image?: UploadImageFragment | string;
|
||||||
quality?: ImageQuality;
|
quality?: ImageQuality;
|
||||||
alt?: ImageProps["alt"];
|
alt?: ImageProps["alt"];
|
||||||
layout?: ImageProps["layout"];
|
onClick?: MouseEventHandler<HTMLImageElement>;
|
||||||
objectFit?: ImageProps["objectFit"];
|
|
||||||
priority?: ImageProps["priority"];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Img(props: Props): JSX.Element {
|
export function Img(props: Immutable<Props>): JSX.Element {
|
||||||
if (typeof props.image === "string") {
|
const {
|
||||||
|
className,
|
||||||
|
image,
|
||||||
|
quality = ImageQuality.Small,
|
||||||
|
alt,
|
||||||
|
onClick,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
if (typeof image === "string") {
|
||||||
|
return (
|
||||||
|
<img className={className} src={image} alt={alt ?? ""} loading="lazy" />
|
||||||
|
);
|
||||||
|
} else if (image?.width && image.height) {
|
||||||
|
const imgSize = getImgSizesByQuality(image.width, image.height, quality);
|
||||||
return (
|
return (
|
||||||
<img
|
<img
|
||||||
className={props.className}
|
className={className}
|
||||||
src={props.image}
|
src={getAssetURL(image.url, quality)}
|
||||||
alt={props.alt ?? ""}
|
alt={alt ?? image.alternativeText ?? ""}
|
||||||
/>
|
width={imgSize.width}
|
||||||
);
|
height={imgSize.height}
|
||||||
} else if (props.image?.width && props.image.height) {
|
loading="lazy"
|
||||||
const imgSize = getImgSizesByQuality(
|
onClick={onClick}
|
||||||
props.image.width,
|
|
||||||
props.image.height,
|
|
||||||
props.quality ?? ImageQuality.Small
|
|
||||||
);
|
|
||||||
return (
|
|
||||||
<Image
|
|
||||||
className={props.className}
|
|
||||||
src={getAssetURL(
|
|
||||||
props.image.url,
|
|
||||||
props.quality ? props.quality : ImageQuality.Small
|
|
||||||
)}
|
|
||||||
alt={props.alt ?? props.image.alternativeText ?? ""}
|
|
||||||
width={props.layout === "fill" ? undefined : imgSize.width}
|
|
||||||
height={props.layout === "fill" ? undefined : imgSize.height}
|
|
||||||
layout={props.layout}
|
|
||||||
objectFit={props.objectFit}
|
|
||||||
priority={props.priority}
|
|
||||||
unoptimized
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ImageQuality {
|
|
||||||
Small = "small",
|
|
||||||
Medium = "medium",
|
|
||||||
Large = "large",
|
|
||||||
Og = "og",
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getAssetFilename(path: string): string {
|
|
||||||
let result = path.split("/");
|
|
||||||
result = result[result.length - 1].split(".");
|
|
||||||
result = result
|
|
||||||
.splice(0, result.length - 1)
|
|
||||||
.join(".")
|
|
||||||
.split("_");
|
|
||||||
return result[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getAssetURL(url: string, quality: ImageQuality): string {
|
|
||||||
let newUrl = url;
|
|
||||||
newUrl = newUrl.replace(/^\/uploads/u, `/${quality}`);
|
|
||||||
newUrl = newUrl.replace(/.jpg$/u, ".webp");
|
|
||||||
newUrl = newUrl.replace(/.jpeg$/u, ".webp");
|
|
||||||
newUrl = newUrl.replace(/.png$/u, ".webp");
|
|
||||||
if (quality === ImageQuality.Og) newUrl = newUrl.replace(/.webp$/u, ".jpg");
|
|
||||||
return process.env.NEXT_PUBLIC_URL_IMG + newUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getImgSizesByMaxSize(
|
|
||||||
width: number,
|
|
||||||
height: number,
|
|
||||||
maxSize: number
|
|
||||||
): { width: number; height: number } {
|
|
||||||
if (width > height) {
|
|
||||||
if (width < maxSize) return { width: width, height: height };
|
|
||||||
return { width: maxSize, height: (height / width) * maxSize };
|
|
||||||
}
|
|
||||||
if (height < maxSize) return { width: width, height: height };
|
|
||||||
return { width: (width / height) * maxSize, height: maxSize };
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getImgSizesByQuality(
|
|
||||||
width: number,
|
|
||||||
height: number,
|
|
||||||
quality: ImageQuality
|
|
||||||
): { width: number; height: number } {
|
|
||||||
switch (quality) {
|
|
||||||
case ImageQuality.Og:
|
|
||||||
return getImgSizesByMaxSize(width, height, 512);
|
|
||||||
case ImageQuality.Small:
|
|
||||||
return getImgSizesByMaxSize(width, height, 512);
|
|
||||||
case ImageQuality.Medium:
|
|
||||||
return getImgSizesByMaxSize(width, height, 1024);
|
|
||||||
case ImageQuality.Large:
|
|
||||||
return getImgSizesByMaxSize(width, height, 2048);
|
|
||||||
default:
|
|
||||||
return { width: 0, height: 0 };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { Immutable } from "helpers/types";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { MouseEventHandler } from "react";
|
import { MouseEventHandler } from "react";
|
||||||
|
|
||||||
|
@ -14,7 +15,7 @@ interface Props {
|
||||||
badgeNumber?: number;
|
badgeNumber?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Button(props: Props): JSX.Element {
|
export function Button(props: Immutable<Props>): JSX.Element {
|
||||||
const {
|
const {
|
||||||
draggable,
|
draggable,
|
||||||
id,
|
id,
|
||||||
|
@ -39,11 +40,15 @@ export default function Button(props: Props): JSX.Element {
|
||||||
transition-all select-none hover:[--opacityBadge:0] --opacityBadge:100 ${className} ${
|
transition-all select-none hover:[--opacityBadge:0] --opacityBadge:100 ${className} ${
|
||||||
active
|
active
|
||||||
? "text-light bg-black drop-shadow-black-lg !border-black cursor-not-allowed"
|
? "text-light bg-black drop-shadow-black-lg !border-black cursor-not-allowed"
|
||||||
: "cursor-pointer hover:text-light hover:bg-dark hover:drop-shadow-shade-lg active:bg-black active:text-light active:drop-shadow-black-lg active:border-black"
|
: `cursor-pointer hover:text-light hover:bg-dark hover:drop-shadow-shade-lg
|
||||||
|
active:bg-black active:text-light active:drop-shadow-black-lg active:border-black`
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{badgeNumber && (
|
{badgeNumber && (
|
||||||
<div className="opacity-[var(--opacityBadge)] transition-opacity grid place-items-center absolute -top-3 -right-2 bg-dark w-8 h-8 text-light font-bold rounded-full">
|
<div
|
||||||
|
className="opacity-[var(--opacityBadge)] transition-opacity grid place-items-center
|
||||||
|
absolute -top-3 -right-2 bg-dark w-8 h-8 text-light font-bold rounded-full"
|
||||||
|
>
|
||||||
{badgeNumber}
|
{badgeNumber}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import { AppStaticProps } from "queries/getAppStaticProps";
|
import { AppStaticProps } from "graphql/getAppStaticProps";
|
||||||
import { prettyLanguage } from "queries/helpers";
|
import { prettyLanguage } from "helpers/formatters";
|
||||||
|
import { Immutable } from "helpers/types";
|
||||||
import { Dispatch, SetStateAction } from "react";
|
import { Dispatch, SetStateAction } from "react";
|
||||||
import ToolTip from "../ToolTip";
|
import { ToolTip } from "../ToolTip";
|
||||||
import Button from "./Button";
|
import { Button } from "./Button";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
className?: string;
|
className?: string;
|
||||||
|
@ -12,7 +13,7 @@ interface Props {
|
||||||
setLocalesIndex: Dispatch<SetStateAction<number | undefined>>;
|
setLocalesIndex: Dispatch<SetStateAction<number | undefined>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function LanguageSwitcher(props: Props): JSX.Element {
|
export function LanguageSwitcher(props: Immutable<Props>): JSX.Element {
|
||||||
const { locales, className, localesIndex, setLocalesIndex } = props;
|
const { locales, className, localesIndex, setLocalesIndex } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
import { arrayMove } from "queries/helpers";
|
import { arrayMove } from "helpers/others";
|
||||||
|
import { Immutable } from "helpers/types";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
className?: string;
|
className?: string;
|
||||||
items: Map<string, string>;
|
items: Map<string, string>;
|
||||||
|
insertLabels?: Map<number, string | null | undefined>;
|
||||||
onChange?: (items: Map<string, string>) => void;
|
onChange?: (items: Map<string, string>) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function OrderableList(props: Props): JSX.Element {
|
export function OrderableList(props: Immutable<Props>): JSX.Element {
|
||||||
const [items, setItems] = useState<Map<string, string>>(props.items);
|
const [items, setItems] = useState<Map<string, string>>(props.items);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -24,12 +26,8 @@ export default function OrderableList(props: Props): JSX.Element {
|
||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
{[...items].map(([key, value], index) => (
|
{[...items].map(([key, value], index) => (
|
||||||
<>
|
<>
|
||||||
{index === 0 ? (
|
{props.insertLabels?.get(index) && (
|
||||||
<p>Primary language</p>
|
<p>{props.insertLabels.get(index)}</p>
|
||||||
) : index === 1 ? (
|
|
||||||
<p>Secondary languages</p>
|
|
||||||
) : (
|
|
||||||
""
|
|
||||||
)}
|
)}
|
||||||
<div
|
<div
|
||||||
onDragStart={(event) => {
|
onDragStart={(event) => {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
|
import { Immutable } from "helpers/types";
|
||||||
import { Dispatch, SetStateAction } from "react";
|
import { Dispatch, SetStateAction } from "react";
|
||||||
import Button from "./Button";
|
import { Button } from "./Button";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
className?: string;
|
className?: string;
|
||||||
|
@ -8,7 +9,7 @@ interface Props {
|
||||||
setPage: Dispatch<SetStateAction<number>>;
|
setPage: Dispatch<SetStateAction<number>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function PageSelector(props: Props): JSX.Element {
|
export function PageSelector(props: Immutable<Props>): JSX.Element {
|
||||||
const { page, setPage, maxPage } = props;
|
const { page, setPage, maxPage } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { Immutable } from "helpers/types";
|
||||||
import { Dispatch, SetStateAction, useState } from "react";
|
import { Dispatch, SetStateAction, useState } from "react";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
@ -9,7 +10,7 @@ interface Props {
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Select(props: Props): JSX.Element {
|
export function Select(props: Immutable<Props>): JSX.Element {
|
||||||
const [opened, setOpened] = useState(false);
|
const [opened, setOpened] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -19,7 +20,9 @@ export default function Select(props: Props): JSX.Element {
|
||||||
} ${props.className}`}
|
} ${props.className}`}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={`outline outline-mid outline-2 outline-offset-[-2px] hover:outline-[transparent] bg-light rounded-[1em] p-1 grid grid-flow-col grid-cols-[1fr_auto_auto] place-items-center cursor-pointer hover:bg-mid transition-all ${
|
className={`outline outline-mid outline-2 outline-offset-[-2px] hover:outline-[transparent]
|
||||||
|
bg-light rounded-[1em] p-1 grid grid-flow-col grid-cols-[1fr_auto_auto] place-items-center
|
||||||
|
cursor-pointer hover:bg-mid transition-all ${
|
||||||
opened && "outline-[transparent] rounded-b-none"
|
opened && "outline-[transparent] rounded-b-none"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
|
@ -47,7 +50,8 @@ export default function Select(props: Props): JSX.Element {
|
||||||
<>
|
<>
|
||||||
{index !== props.state && (
|
{index !== props.state && (
|
||||||
<div
|
<div
|
||||||
className="bg-light hover:bg-mid transition-colors cursor-pointer p-1 last-of-type:rounded-b-[1em]"
|
className="bg-light hover:bg-mid transition-colors
|
||||||
|
cursor-pointer p-1 last-of-type:rounded-b-[1em]"
|
||||||
key={index}
|
key={index}
|
||||||
id={option}
|
id={option}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { Immutable } from "helpers/types";
|
||||||
import { Dispatch, SetStateAction } from "react";
|
import { Dispatch, SetStateAction } from "react";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
@ -6,18 +7,20 @@ interface Props {
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Switch(props: Props): JSX.Element {
|
export function Switch(props: Immutable<Props>): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`h-6 w-12 rounded-full border-2 border-mid grid transition-colors relative cursor-pointer ${
|
className={`h-6 w-12 rounded-full border-2 border-mid grid
|
||||||
props.className
|
transition-colors relative cursor-pointer ${props.className} ${
|
||||||
} ${props.state ? "bg-mid" : "bg-light"}`}
|
props.state ? "bg-mid" : "bg-light"
|
||||||
|
}`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
props.setState(!props.state);
|
props.setState(!props.state);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={`bg-dark aspect-square rounded-full absolute top-0 bottom-0 left-0 transition-transform ${
|
className={`bg-dark aspect-square rounded-full absolute
|
||||||
|
top-0 bottom-0 left-0 transition-transform ${
|
||||||
props.state && "translate-x-[115%]"
|
props.state && "translate-x-[115%]"
|
||||||
}`}
|
}`}
|
||||||
></div>
|
></div>
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
|
import { Immutable } from "helpers/types";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
className?: string;
|
className?: string;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
id?: string;
|
id?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function InsetBox(props: Props): JSX.Element {
|
export function InsetBox(props: Immutable<Props>): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
id={props.id}
|
id={props.id}
|
||||||
|
|
|
@ -1,26 +1,24 @@
|
||||||
import Chip from "components/Chip";
|
import { Chip } from "components/Chip";
|
||||||
import Button from "components/Inputs/Button";
|
import { Button } from "components/Inputs/Button";
|
||||||
import { GetLibraryItemQuery } from "graphql/generated";
|
import { GetLibraryItemQuery } from "graphql/generated";
|
||||||
import { AppStaticProps } from "queries/getAppStaticProps";
|
import { AppStaticProps } from "graphql/getAppStaticProps";
|
||||||
import { prettyinlineTitle, prettySlug } from "queries/helpers";
|
import { prettyinlineTitle, prettySlug } from "helpers/formatters";
|
||||||
|
import { Immutable } from "helpers/types";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
content: Exclude<
|
content: NonNullable<
|
||||||
Exclude<
|
NonNullable<
|
||||||
Exclude<
|
NonNullable<
|
||||||
GetLibraryItemQuery["libraryItems"],
|
GetLibraryItemQuery["libraryItems"]
|
||||||
null | undefined
|
>["data"][number]["attributes"]
|
||||||
>["data"][number]["attributes"],
|
>["contents"]
|
||||||
null | undefined
|
|
||||||
>["contents"],
|
|
||||||
null | undefined
|
|
||||||
>["data"][number];
|
>["data"][number];
|
||||||
parentSlug: string;
|
parentSlug: string;
|
||||||
langui: AppStaticProps["langui"];
|
langui: AppStaticProps["langui"];
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ContentLine(props: Props): JSX.Element {
|
export function ContentLine(props: Immutable<Props>): JSX.Element {
|
||||||
const { content, langui, parentSlug } = props;
|
const { content, langui, parentSlug } = props;
|
||||||
|
|
||||||
const [opened, setOpened] = useState(false);
|
const [opened, setOpened] = useState(false);
|
||||||
|
@ -32,15 +30,19 @@ export default function ContentLine(props: Props): JSX.Element {
|
||||||
opened && "bg-mid shadow-inner-sm shadow-shade h-auto py-3 my-2"
|
opened && "bg-mid shadow-inner-sm shadow-shade h-auto py-3 my-2"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div className="grid gap-4 place-items-center grid-cols-[auto_auto_1fr_auto_12ch] thin:grid-cols-[auto_auto_1fr_auto]">
|
<div
|
||||||
|
className="grid gap-4 place-items-center
|
||||||
|
grid-cols-[auto_auto_1fr_auto_12ch] thin:grid-cols-[auto_auto_1fr_auto]"
|
||||||
|
>
|
||||||
<a>
|
<a>
|
||||||
<h3 className="cursor-pointer" onClick={() => setOpened(!opened)}>
|
<h3 className="cursor-pointer" onClick={() => setOpened(!opened)}>
|
||||||
{content.attributes.content?.data?.attributes?.titles?.[0]
|
{content.attributes.content?.data?.attributes?.translations?.[0]
|
||||||
? prettyinlineTitle(
|
? prettyinlineTitle(
|
||||||
content.attributes.content.data.attributes.titles[0]
|
content.attributes.content.data.attributes.translations[0]
|
||||||
?.pre_title,
|
?.pre_title,
|
||||||
content.attributes.content.data.attributes.titles[0]?.title,
|
content.attributes.content.data.attributes.translations[0]
|
||||||
content.attributes.content.data.attributes.titles[0]
|
?.title,
|
||||||
|
content.attributes.content.data.attributes.translations[0]
|
||||||
?.subtitle
|
?.subtitle
|
||||||
)
|
)
|
||||||
: prettySlug(content.attributes.slug, props.parentSlug)}
|
: prettySlug(content.attributes.slug, props.parentSlug)}
|
||||||
|
|
|
@ -1,76 +1,55 @@
|
||||||
import Chip from "components/Chip";
|
import { Chip } from "components/Chip";
|
||||||
import Img, {
|
import { Img } from "components/Img";
|
||||||
getAssetFilename,
|
import { Button } from "components/Inputs/Button";
|
||||||
getAssetURL,
|
import { RecorderChip } from "components/RecorderChip";
|
||||||
ImageQuality,
|
import { ToolTip } from "components/ToolTip";
|
||||||
} from "components/Img";
|
|
||||||
import Button from "components/Inputs/Button";
|
|
||||||
import RecorderChip from "components/RecorderChip";
|
|
||||||
import ToolTip from "components/ToolTip";
|
|
||||||
import { GetLibraryItemScansQuery } from "graphql/generated";
|
import { GetLibraryItemScansQuery } from "graphql/generated";
|
||||||
import useSmartLanguage from "hooks/useSmartLanguage";
|
import { AppStaticProps } from "graphql/getAppStaticProps";
|
||||||
import { AppStaticProps } from "queries/getAppStaticProps";
|
import { getAssetFilename, getAssetURL, ImageQuality } from "helpers/img";
|
||||||
import { getStatusDescription, isInteger } from "queries/helpers";
|
import { isInteger } from "helpers/numbers";
|
||||||
import { Dispatch, SetStateAction } from "react";
|
import { getStatusDescription } from "helpers/others";
|
||||||
|
import { Immutable } from "helpers/types";
|
||||||
|
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
setLightboxOpen: Dispatch<SetStateAction<boolean>>;
|
openLightBox: (images: string[], index?: number) => void;
|
||||||
setLightboxImages: Dispatch<SetStateAction<string[]>>;
|
scanSet: NonNullable<
|
||||||
setLightboxIndex: Dispatch<SetStateAction<number>>;
|
NonNullable<
|
||||||
scanSet: Exclude<
|
NonNullable<
|
||||||
Exclude<
|
NonNullable<
|
||||||
Exclude<
|
NonNullable<
|
||||||
Exclude<
|
GetLibraryItemScansQuery["libraryItems"]
|
||||||
Exclude<
|
>["data"][number]["attributes"]
|
||||||
GetLibraryItemScansQuery["libraryItems"],
|
>["contents"]
|
||||||
null | undefined
|
>["data"][number]["attributes"]
|
||||||
>["data"][number]["attributes"],
|
>["scan_set"]
|
||||||
null | undefined
|
|
||||||
>["contents"],
|
|
||||||
null | undefined
|
|
||||||
>["data"][number]["attributes"],
|
|
||||||
null | undefined
|
|
||||||
>["scan_set"],
|
|
||||||
null | undefined
|
|
||||||
>;
|
>;
|
||||||
slug: string;
|
slug: string;
|
||||||
title: string;
|
title: string;
|
||||||
languages: AppStaticProps["languages"];
|
languages: AppStaticProps["languages"];
|
||||||
langui: AppStaticProps["langui"];
|
langui: AppStaticProps["langui"];
|
||||||
content: Exclude<
|
content: NonNullable<
|
||||||
Exclude<
|
NonNullable<
|
||||||
Exclude<
|
NonNullable<
|
||||||
Exclude<
|
NonNullable<
|
||||||
GetLibraryItemScansQuery["libraryItems"],
|
GetLibraryItemScansQuery["libraryItems"]
|
||||||
null | undefined
|
>["data"][number]["attributes"]
|
||||||
>["data"][number]["attributes"],
|
>["contents"]
|
||||||
null | undefined
|
>["data"][number]["attributes"]
|
||||||
>["contents"],
|
|
||||||
null | undefined
|
|
||||||
>["data"][number]["attributes"],
|
|
||||||
null | undefined
|
|
||||||
>["content"];
|
>["content"];
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ScanSet(props: Props): JSX.Element {
|
export function ScanSet(props: Immutable<Props>): JSX.Element {
|
||||||
const {
|
const { openLightBox, scanSet, slug, title, languages, langui, content } =
|
||||||
setLightboxOpen,
|
props;
|
||||||
setLightboxImages,
|
|
||||||
setLightboxIndex,
|
|
||||||
scanSet,
|
|
||||||
slug,
|
|
||||||
title,
|
|
||||||
languages,
|
|
||||||
langui,
|
|
||||||
content,
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
const [selectedScan, LanguageSwitcher] = useSmartLanguage({
|
const [selectedScan, LanguageSwitcher] = useSmartLanguage({
|
||||||
items: scanSet,
|
items: scanSet,
|
||||||
languages: languages,
|
languages: languages,
|
||||||
languageExtractor: (item) => item?.language?.data?.attributes?.code,
|
languageExtractor: (item) => item.language?.data?.attributes?.code,
|
||||||
transform: (item) => {
|
transform: (item) => {
|
||||||
item?.pages?.data.sort((a, b) => {
|
const newItem = { ...item } as NonNullable<Props["scanSet"][number]>;
|
||||||
|
newItem.pages?.data.sort((a, b) => {
|
||||||
if (a.attributes?.url && b.attributes?.url) {
|
if (a.attributes?.url && b.attributes?.url) {
|
||||||
let aName = getAssetFilename(a.attributes.url);
|
let aName = getAssetFilename(a.attributes.url);
|
||||||
let bName = getAssetFilename(b.attributes.url);
|
let bName = getAssetFilename(b.attributes.url);
|
||||||
|
@ -93,7 +72,7 @@ export default function ScanSet(props: Props): JSX.Element {
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
});
|
});
|
||||||
return item;
|
return newItem;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -101,7 +80,10 @@ export default function ScanSet(props: Props): JSX.Element {
|
||||||
<>
|
<>
|
||||||
{selectedScan && (
|
{selectedScan && (
|
||||||
<div>
|
<div>
|
||||||
<div className="flex flex-row flex-wrap place-items-center gap-6 text-base pt-10 first-of-type:pt-0">
|
<div
|
||||||
|
className="flex flex-row flex-wrap place-items-center
|
||||||
|
gap-6 text-base pt-10 first-of-type:pt-0"
|
||||||
|
>
|
||||||
<h2 id={slug} className="text-2xl">
|
<h2 id={slug} className="text-2xl">
|
||||||
{title}
|
{title}
|
||||||
</h2>
|
</h2>
|
||||||
|
@ -198,11 +180,16 @@ export default function ScanSet(props: Props): JSX.Element {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid gap-8 items-end mobile:grid-cols-2 desktop:grid-cols-[repeat(auto-fill,_minmax(10rem,1fr))] pb-12 border-b-[3px] border-dotted last-of-type:border-0">
|
<div
|
||||||
|
className="grid gap-8 items-end mobile:grid-cols-2
|
||||||
|
desktop:grid-cols-[repeat(auto-fill,_minmax(10rem,1fr))]
|
||||||
|
pb-12 border-b-[3px] border-dotted last-of-type:border-0"
|
||||||
|
>
|
||||||
{selectedScan.pages?.data.map((page, index) => (
|
{selectedScan.pages?.data.map((page, index) => (
|
||||||
<div
|
<div
|
||||||
key={page.id}
|
key={page.id}
|
||||||
className="drop-shadow-shade-lg hover:scale-[1.02] cursor-pointer transition-transform"
|
className="drop-shadow-shade-lg hover:scale-[1.02]
|
||||||
|
cursor-pointer transition-transform"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const images: string[] = [];
|
const images: string[] = [];
|
||||||
selectedScan.pages?.data.map((image) => {
|
selectedScan.pages?.data.map((image) => {
|
||||||
|
@ -211,9 +198,7 @@ export default function ScanSet(props: Props): JSX.Element {
|
||||||
getAssetURL(image.attributes.url, ImageQuality.Large)
|
getAssetURL(image.attributes.url, ImageQuality.Large)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
setLightboxOpen(true);
|
openLightBox(images, index);
|
||||||
setLightboxImages(images);
|
|
||||||
setLightboxIndex(index);
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{page.attributes && (
|
{page.attributes && (
|
||||||
|
|
|
@ -1,48 +1,37 @@
|
||||||
import Chip from "components/Chip";
|
import { Chip } from "components/Chip";
|
||||||
import Img, { getAssetURL, ImageQuality } from "components/Img";
|
import { Img } from "components/Img";
|
||||||
import RecorderChip from "components/RecorderChip";
|
import { RecorderChip } from "components/RecorderChip";
|
||||||
import ToolTip from "components/ToolTip";
|
import { ToolTip } from "components/ToolTip";
|
||||||
import {
|
import {
|
||||||
GetLibraryItemScansQuery,
|
GetLibraryItemScansQuery,
|
||||||
UploadImageFragment,
|
UploadImageFragment,
|
||||||
} from "graphql/generated";
|
} from "graphql/generated";
|
||||||
import useSmartLanguage from "hooks/useSmartLanguage";
|
import { AppStaticProps } from "graphql/getAppStaticProps";
|
||||||
import { AppStaticProps } from "queries/getAppStaticProps";
|
import { getAssetURL, ImageQuality } from "helpers/img";
|
||||||
import { getStatusDescription } from "queries/helpers";
|
import { getStatusDescription } from "helpers/others";
|
||||||
import { Dispatch, SetStateAction } from "react";
|
import { Immutable } from "helpers/types";
|
||||||
|
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
setLightboxOpen: Dispatch<SetStateAction<boolean>>;
|
openLightBox: (images: string[], index?: number) => void;
|
||||||
setLightboxImages: Dispatch<SetStateAction<string[]>>;
|
images: NonNullable<
|
||||||
setLightboxIndex: Dispatch<SetStateAction<number>>;
|
NonNullable<
|
||||||
images: Exclude<
|
NonNullable<
|
||||||
Exclude<
|
GetLibraryItemScansQuery["libraryItems"]
|
||||||
Exclude<
|
>["data"][number]["attributes"]
|
||||||
GetLibraryItemScansQuery["libraryItems"],
|
>["images"]
|
||||||
null | undefined
|
|
||||||
>["data"][number]["attributes"],
|
|
||||||
null | undefined
|
|
||||||
>["images"],
|
|
||||||
null | undefined
|
|
||||||
>;
|
>;
|
||||||
languages: AppStaticProps["languages"];
|
languages: AppStaticProps["languages"];
|
||||||
langui: AppStaticProps["langui"];
|
langui: AppStaticProps["langui"];
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ScanSetCover(props: Props): JSX.Element {
|
export function ScanSetCover(props: Immutable<Props>): JSX.Element {
|
||||||
const {
|
const { openLightBox, images, languages, langui } = props;
|
||||||
setLightboxOpen,
|
|
||||||
setLightboxImages,
|
|
||||||
setLightboxIndex,
|
|
||||||
images,
|
|
||||||
languages,
|
|
||||||
langui,
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
const [selectedScan, LanguageSwitcher] = useSmartLanguage({
|
const [selectedScan, LanguageSwitcher] = useSmartLanguage({
|
||||||
items: images,
|
items: images,
|
||||||
languages: languages,
|
languages: languages,
|
||||||
languageExtractor: (item) => item?.language?.data?.attributes?.code,
|
languageExtractor: (item) => item.language?.data?.attributes?.code,
|
||||||
});
|
});
|
||||||
|
|
||||||
const coverImages: UploadImageFragment[] = [];
|
const coverImages: UploadImageFragment[] = [];
|
||||||
|
@ -64,7 +53,10 @@ export default function ScanSetCover(props: Props): JSX.Element {
|
||||||
<>
|
<>
|
||||||
{selectedScan && (
|
{selectedScan && (
|
||||||
<div>
|
<div>
|
||||||
<div className="flex flex-row flex-wrap place-items-center gap-6 text-base pt-10 first-of-type:pt-0">
|
<div
|
||||||
|
className="flex flex-row flex-wrap place-items-center
|
||||||
|
gap-6 text-base pt-10 first-of-type:pt-0"
|
||||||
|
>
|
||||||
<h2 id={"cover"} className="text-2xl">
|
<h2 id={"cover"} className="text-2xl">
|
||||||
{"Cover"}
|
{"Cover"}
|
||||||
</h2>
|
</h2>
|
||||||
|
@ -149,20 +141,23 @@ export default function ScanSetCover(props: Props): JSX.Element {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid gap-8 items-end mobile:grid-cols-2 desktop:grid-cols-[repeat(auto-fill,_minmax(10rem,1fr))] pb-12 border-b-[3px] border-dotted last-of-type:border-0">
|
<div
|
||||||
|
className="grid gap-8 items-end mobile:grid-cols-2
|
||||||
|
desktop:grid-cols-[repeat(auto-fill,_minmax(10rem,1fr))]
|
||||||
|
pb-12 border-b-[3px] border-dotted last-of-type:border-0"
|
||||||
|
>
|
||||||
{coverImages.map((image, index) => (
|
{coverImages.map((image, index) => (
|
||||||
<div
|
<div
|
||||||
key={image.url}
|
key={image.url}
|
||||||
className="drop-shadow-shade-lg hover:scale-[1.02] cursor-pointer transition-transform"
|
className="drop-shadow-shade-lg hover:scale-[1.02]
|
||||||
|
cursor-pointer transition-transform"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const imgs: string[] = [];
|
const imgs: string[] = [];
|
||||||
coverImages.map((img) => {
|
coverImages.map((img) => {
|
||||||
if (img.url)
|
if (img.url)
|
||||||
imgs.push(getAssetURL(img.url, ImageQuality.Large));
|
imgs.push(getAssetURL(img.url, ImageQuality.Large));
|
||||||
});
|
});
|
||||||
setLightboxOpen(true);
|
openLightBox(imgs, index);
|
||||||
setLightboxImages(imgs);
|
|
||||||
setLightboxIndex(index);
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Img image={image} quality={ImageQuality.Small} />
|
<Img image={image} quality={ImageQuality.Small} />
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
import { useMediaMobile } from "hooks/useMediaQuery";
|
import { Immutable } from "helpers/types";
|
||||||
import { Dispatch, SetStateAction } from "react";
|
import { Dispatch, SetStateAction } from "react";
|
||||||
import Lightbox from "react-image-lightbox";
|
import Hotkeys from "react-hot-keys";
|
||||||
|
import { useSwipeable } from "react-swipeable";
|
||||||
|
import { Img } from "./Img";
|
||||||
|
import { Button } from "./Inputs/Button";
|
||||||
|
import { Popup } from "./Popup";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
setState:
|
setState:
|
||||||
|
@ -12,27 +16,75 @@ interface Props {
|
||||||
setIndex: Dispatch<SetStateAction<number>>;
|
setIndex: Dispatch<SetStateAction<number>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function LightBox(props: Props): JSX.Element {
|
export function LightBox(props: Immutable<Props>): JSX.Element {
|
||||||
const { state, setState, images, index, setIndex } = props;
|
const { state, setState, images, index, setIndex } = props;
|
||||||
const mobile = useMediaMobile();
|
|
||||||
|
function handlePrevious() {
|
||||||
|
if (index > 0) setIndex(index - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleNext() {
|
||||||
|
if (index < images.length - 1) setIndex(index + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const sensibilitySwipe = 0.5;
|
||||||
|
|
||||||
|
const handlers = useSwipeable({
|
||||||
|
onSwipedLeft: (SwipeEventData) => {
|
||||||
|
if (SwipeEventData.velocity < sensibilitySwipe) return;
|
||||||
|
handleNext();
|
||||||
|
},
|
||||||
|
onSwipedRight: (SwipeEventData) => {
|
||||||
|
if (SwipeEventData.velocity < sensibilitySwipe) return;
|
||||||
|
handlePrevious();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{state && (
|
{state && (
|
||||||
<Lightbox
|
<Hotkeys
|
||||||
reactModalProps={{
|
keyName="left,right"
|
||||||
parentSelector: () => document.getElementById("MyAppLayout"),
|
allowRepeat
|
||||||
|
onKeyDown={(keyName) => {
|
||||||
|
if (keyName === "left") {
|
||||||
|
handlePrevious();
|
||||||
|
} else {
|
||||||
|
handleNext();
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
mainSrc={images[index]}
|
>
|
||||||
prevSrc={index > 0 ? images[index - 1] : undefined}
|
<Popup setState={setState} state={state} padding={false} fillViewport>
|
||||||
nextSrc={index < images.length ? images[index + 1] : undefined}
|
<div
|
||||||
onMovePrevRequest={() => setIndex(index - 1)}
|
{...handlers}
|
||||||
onMoveNextRequest={() => setIndex(index + 1)}
|
className={`grid grid-cols-[4em,1fr,4em] mobile:grid-cols-2
|
||||||
imageCaption=""
|
[grid-template-areas:"left_image_right"]
|
||||||
imageTitle=""
|
mobile:[grid-template-areas:"image_image""left_right"]
|
||||||
onCloseRequest={() => setState(false)}
|
place-items-center first-letter:gap-4 w-full h-full overflow-hidden`}
|
||||||
imagePadding={mobile ? 0 : 70}
|
>
|
||||||
/>
|
<div className="[grid-area:left]">
|
||||||
|
{index > 0 && (
|
||||||
|
<Button onClick={handlePrevious}>
|
||||||
|
<span className="material-icons">chevron_left</span>
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Img
|
||||||
|
className="max-h-full [grid-area:image]"
|
||||||
|
image={images[index]}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="[grid-area:right]">
|
||||||
|
{index < images.length - 1 && (
|
||||||
|
<Button onClick={handleNext}>
|
||||||
|
<span className="material-icons">chevron_right</span>
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Popup>
|
||||||
|
</Hotkeys>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
import HorizontalLine from "components/HorizontalLine";
|
import { HorizontalLine } from "components/HorizontalLine";
|
||||||
import Img, { getAssetURL, ImageQuality } from "components/Img";
|
import { Img } from "components/Img";
|
||||||
import InsetBox from "components/InsetBox";
|
import { InsetBox } from "components/InsetBox";
|
||||||
import LightBox from "components/LightBox";
|
import { ToolTip } from "components/ToolTip";
|
||||||
import ToolTip from "components/ToolTip";
|
|
||||||
import { useAppLayout } from "contexts/AppLayoutContext";
|
import { useAppLayout } from "contexts/AppLayoutContext";
|
||||||
|
import { slugify } from "helpers/formatters";
|
||||||
|
import { getAssetURL, ImageQuality } from "helpers/img";
|
||||||
|
import { Immutable } from "helpers/types";
|
||||||
|
import { useLightBox } from "hooks/useLightBox";
|
||||||
import Markdown from "markdown-to-jsx";
|
import Markdown from "markdown-to-jsx";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { slugify } from "queries/helpers";
|
import React from "react";
|
||||||
import React, { useState } from "react";
|
|
||||||
import ReactDOMServer from "react-dom/server";
|
import ReactDOMServer from "react-dom/server";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
@ -15,26 +17,18 @@ interface Props {
|
||||||
text: string;
|
text: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Markdawn(props: Props): JSX.Element {
|
export function Markdawn(props: Immutable<Props>): JSX.Element {
|
||||||
const appLayout = useAppLayout();
|
const appLayout = useAppLayout();
|
||||||
const text = preprocessMarkDawn(props.text);
|
const text = preprocessMarkDawn(props.text);
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const [lightboxOpen, setLightboxOpen] = useState(false);
|
const [openLightBox, LightBox] = useLightBox();
|
||||||
const [lightboxImages, setLightboxImages] = useState([""]);
|
|
||||||
const [lightboxIndex, setLightboxIndex] = useState(0);
|
|
||||||
|
|
||||||
if (text) {
|
if (text) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<LightBox
|
<LightBox />
|
||||||
state={lightboxOpen}
|
|
||||||
setState={setLightboxOpen}
|
|
||||||
images={lightboxImages}
|
|
||||||
index={lightboxIndex}
|
|
||||||
setIndex={setLightboxIndex}
|
|
||||||
/>
|
|
||||||
<Markdown
|
<Markdown
|
||||||
className={`formatted ${props.className}`}
|
className={`formatted ${props.className}`}
|
||||||
options={{
|
options={{
|
||||||
|
@ -253,13 +247,11 @@ export default function Markdawn(props: Props): JSX.Element {
|
||||||
<div
|
<div
|
||||||
className="my-8 cursor-pointer place-content-center grid"
|
className="my-8 cursor-pointer place-content-center grid"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setLightboxOpen(true);
|
openLightBox([
|
||||||
setLightboxImages([
|
|
||||||
compProps.src.startsWith("/uploads/")
|
compProps.src.startsWith("/uploads/")
|
||||||
? getAssetURL(compProps.src, ImageQuality.Large)
|
? getAssetURL(compProps.src, ImageQuality.Large)
|
||||||
: compProps.src,
|
: compProps.src,
|
||||||
]);
|
]);
|
||||||
setLightboxIndex(0);
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Img
|
<Img
|
||||||
|
@ -268,8 +260,6 @@ export default function Markdawn(props: Props): JSX.Element {
|
||||||
? getAssetURL(compProps.src, ImageQuality.Small)
|
? getAssetURL(compProps.src, ImageQuality.Small)
|
||||||
: compProps.src
|
: compProps.src
|
||||||
}
|
}
|
||||||
layout="fill"
|
|
||||||
objectFit="contain"
|
|
||||||
quality={ImageQuality.Medium}
|
quality={ImageQuality.Medium}
|
||||||
></Img>
|
></Img>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
|
import { slugify } from "helpers/formatters";
|
||||||
|
import { Immutable } from "helpers/types";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { slugify } from "queries/helpers";
|
|
||||||
import { preprocessMarkDawn } from "./Markdawn";
|
import { preprocessMarkDawn } from "./Markdawn";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
@ -7,7 +8,7 @@ interface Props {
|
||||||
title?: string;
|
title?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function TOCComponent(props: Props): JSX.Element {
|
export function TOC(props: Immutable<Props>): JSX.Element {
|
||||||
const { text, title } = props;
|
const { text, title } = props;
|
||||||
const toc = getTocFromMarkdawn(preprocessMarkDawn(text), title);
|
const toc = getTocFromMarkdawn(preprocessMarkDawn(text), title);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
@ -28,7 +29,7 @@ export default function TOCComponent(props: Props): JSX.Element {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface LevelProps {
|
interface LevelProps {
|
||||||
tocchildren: TOC[];
|
tocchildren: TOCInterface[];
|
||||||
parentNumbering: string;
|
parentNumbering: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,14 +61,14 @@ function TOCLevel(props: LevelProps): JSX.Element {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TOC {
|
interface TOCInterface {
|
||||||
title: string;
|
title: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
children: TOC[];
|
children: TOCInterface[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getTocFromMarkdawn(text: string, title?: string): TOC {
|
export function getTocFromMarkdawn(text: string, title?: string): TOCInterface {
|
||||||
const toc: TOC = {
|
const toc: TOCInterface = {
|
||||||
title: title ?? "Return to top",
|
title: title ?? "Return to top",
|
||||||
slug: slugify(title),
|
slug: slugify(title),
|
||||||
children: [],
|
children: [],
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import ToolTip from "components/ToolTip";
|
import { ToolTip } from "components/ToolTip";
|
||||||
|
import { Immutable } from "helpers/types";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { MouseEventHandler } from "react";
|
import { MouseEventHandler } from "react";
|
||||||
|
|
||||||
|
@ -12,15 +13,19 @@ interface Props {
|
||||||
onClick?: MouseEventHandler<HTMLDivElement>;
|
onClick?: MouseEventHandler<HTMLDivElement>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function NavOption(props: Props): JSX.Element {
|
export function NavOption(props: Immutable<Props>): JSX.Element {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const isActive = router.asPath.startsWith(props.url);
|
const isActive = router.asPath.startsWith(props.url);
|
||||||
const divActive = "bg-mid shadow-inner-sm shadow-shade";
|
const divActive = "bg-mid shadow-inner-sm shadow-shade";
|
||||||
|
|
||||||
const border =
|
const border =
|
||||||
"outline outline-mid outline-2 outline-offset-[-2px] hover:outline-[transparent]";
|
"outline outline-mid outline-2 outline-offset-[-2px] hover:outline-[transparent]";
|
||||||
const divCommon = `gap-x-5 w-full rounded-2xl cursor-pointer p-4 hover:bg-mid hover:shadow-inner-sm hover:shadow-shade hover:active:shadow-inner hover:active:shadow-shade transition-all ${
|
|
||||||
props.border ? border : ""
|
const divCommon = `gap-x-5 w-full rounded-2xl cursor-pointer p-4 hover:bg-mid
|
||||||
} ${isActive ? divActive : ""}`;
|
hover:shadow-inner-sm hover:shadow-shade hover:active:shadow-inner
|
||||||
|
hover:active:shadow-shade transition-all ${props.border ? border : ""} ${
|
||||||
|
isActive ? divActive : ""
|
||||||
|
}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ToolTip
|
<ToolTip
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import HorizontalLine from "components/HorizontalLine";
|
import { HorizontalLine } from "components/HorizontalLine";
|
||||||
|
import { Immutable } from "helpers/types";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
icon?: string;
|
icon?: string;
|
||||||
|
@ -6,7 +7,7 @@ interface Props {
|
||||||
description?: string | null | undefined;
|
description?: string | null | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function PanelHeader(props: Props): JSX.Element {
|
export function PanelHeader(props: Immutable<Props>): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="w-full grid place-items-center">
|
<div className="w-full grid place-items-center">
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import HorizontalLine from "components/HorizontalLine";
|
import { HorizontalLine } from "components/HorizontalLine";
|
||||||
import Button from "components/Inputs/Button";
|
import { Button } from "components/Inputs/Button";
|
||||||
import { useAppLayout } from "contexts/AppLayoutContext";
|
import { useAppLayout } from "contexts/AppLayoutContext";
|
||||||
import { AppStaticProps } from "queries/getAppStaticProps";
|
import { AppStaticProps } from "graphql/getAppStaticProps";
|
||||||
|
import { Immutable } from "helpers/types";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
href: string;
|
href: string;
|
||||||
|
@ -18,7 +19,7 @@ export enum ReturnButtonType {
|
||||||
both = "both",
|
both = "both",
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ReturnButton(props: Props): JSX.Element {
|
export function ReturnButton(props: Immutable<Props>): JSX.Element {
|
||||||
const appLayout = useAppLayout();
|
const appLayout = useAppLayout();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { Immutable } from "helpers/types";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
autoformat?: boolean;
|
autoformat?: boolean;
|
||||||
|
@ -9,13 +11,13 @@ export enum ContentPanelWidthSizes {
|
||||||
large = "large",
|
large = "large",
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ContentPanel(props: Props): JSX.Element {
|
export function ContentPanel(props: Immutable<Props>): JSX.Element {
|
||||||
const width = props.width ? props.width : ContentPanelWidthSizes.default;
|
const width = props.width ? props.width : ContentPanelWidthSizes.default;
|
||||||
const widthCSS =
|
const widthCSS =
|
||||||
width === ContentPanelWidthSizes.default ? "max-w-2xl" : "w-full";
|
width === ContentPanelWidthSizes.default ? "max-w-2xl" : "w-full";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`grid pt-10 pb-20 px-6 desktop:py-20 desktop:px-10`}>
|
<div className={`grid pt-10 pb-20 px-4 desktop:py-20 desktop:px-10`}>
|
||||||
<main
|
<main
|
||||||
className={`${
|
className={`${
|
||||||
props.autoformat && "formatted"
|
props.autoformat && "formatted"
|
||||||
|
|
|
@ -1,25 +1,27 @@
|
||||||
import HorizontalLine from "components/HorizontalLine";
|
import { HorizontalLine } from "components/HorizontalLine";
|
||||||
import Button from "components/Inputs/Button";
|
import { Button } from "components/Inputs/Button";
|
||||||
import NavOption from "components/PanelComponents/NavOption";
|
import { NavOption } from "components/PanelComponents/NavOption";
|
||||||
import ToolTip from "components/ToolTip";
|
import { ToolTip } from "components/ToolTip";
|
||||||
import { useAppLayout } from "contexts/AppLayoutContext";
|
import { useAppLayout } from "contexts/AppLayoutContext";
|
||||||
|
import { AppStaticProps } from "graphql/getAppStaticProps";
|
||||||
|
import { Immutable } from "helpers/types";
|
||||||
import { useMediaDesktop } from "hooks/useMediaQuery";
|
import { useMediaDesktop } from "hooks/useMediaQuery";
|
||||||
import Markdown from "markdown-to-jsx";
|
import Markdown from "markdown-to-jsx";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { AppStaticProps } from "queries/getAppStaticProps";
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
langui: AppStaticProps["langui"];
|
langui: AppStaticProps["langui"];
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function MainPanel(props: Props): JSX.Element {
|
export function MainPanel(props: Immutable<Props>): JSX.Element {
|
||||||
const { langui } = props;
|
const { langui } = props;
|
||||||
const isDesktop = useMediaDesktop();
|
const isDesktop = useMediaDesktop();
|
||||||
const appLayout = useAppLayout();
|
const appLayout = useAppLayout();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`flex flex-col justify-center content-start gap-y-2 justify-items-center text-center p-8 ${
|
className={`flex flex-col justify-center content-start
|
||||||
|
gap-y-2 justify-items-center text-center p-8 ${
|
||||||
appLayout.mainPanelReduced && isDesktop && "px-4"
|
appLayout.mainPanelReduced && isDesktop && "px-4"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
|
@ -44,7 +46,9 @@ export default function MainPanel(props: Props): JSX.Element {
|
||||||
onClick={() => appLayout.setMainPanelOpen(false)}
|
onClick={() => appLayout.setMainPanelOpen(false)}
|
||||||
className={`${
|
className={`${
|
||||||
appLayout.mainPanelReduced && isDesktop ? "w-12" : "w-1/2"
|
appLayout.mainPanelReduced && isDesktop ? "w-12" : "w-1/2"
|
||||||
} aspect-square cursor-pointer transition-colors [mask:url('/icons/accords.svg')] ![mask-size:contain] ![mask-repeat:no-repeat] ![mask-position:center] bg-black hover:bg-dark mb-4`}
|
} aspect-square cursor-pointer transition-colors [mask:url('/icons/accords.svg')]
|
||||||
|
![mask-size:contain] ![mask-repeat:no-repeat]
|
||||||
|
![mask-position:center] bg-black hover:bg-dark mb-4`}
|
||||||
></div>
|
></div>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
|
@ -68,22 +72,11 @@ export default function MainPanel(props: Props): JSX.Element {
|
||||||
disabled={!appLayout.mainPanelReduced}
|
disabled={!appLayout.mainPanelReduced}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
className={
|
|
||||||
appLayout.mainPanelReduced && isDesktop
|
|
||||||
? ""
|
|
||||||
: "!py-0.5 !px-2.5"
|
|
||||||
}
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
appLayout.setConfigPanelOpen(true);
|
appLayout.setConfigPanelOpen(true);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span
|
<span className={"material-icons"}>settings</span>
|
||||||
className={`material-icons ${
|
|
||||||
!(appLayout.mainPanelReduced && isDesktop) && "!text-sm"
|
|
||||||
} `}
|
|
||||||
>
|
|
||||||
settings
|
|
||||||
</span>
|
|
||||||
</Button>
|
</Button>
|
||||||
</ToolTip>
|
</ToolTip>
|
||||||
|
|
||||||
|
@ -218,10 +211,22 @@ export default function MainPanel(props: Props): JSX.Element {
|
||||||
className="transition-[filter] colorize-black hover:colorize-dark"
|
className="transition-[filter] colorize-black hover:colorize-dark"
|
||||||
href="https://creativecommons.org/licenses/by-sa/4.0/"
|
href="https://creativecommons.org/licenses/by-sa/4.0/"
|
||||||
>
|
>
|
||||||
<div className="mt-4 mb-8 grid grid-flow-col place-content-center gap-1 hover:[--theme-color-black:var(--theme-color-dark)]">
|
<div
|
||||||
<div className="w-6 aspect-square [mask:url('/icons/creative-commons-brands.svg')] ![mask-size:contain] ![mask-repeat:no-repeat] ![mask-position:center] bg-black" />
|
className="mt-4 mb-8 grid grid-flow-col place-content-center gap-1
|
||||||
<div className="w-6 aspect-square [mask:url('/icons/creative-commons-by-brands.svg')] ![mask-size:contain] ![mask-repeat:no-repeat] ![mask-position:center] bg-black" />
|
hover:[--theme-color-black:var(--theme-color-dark)]"
|
||||||
<div className="w-6 aspect-square [mask:url('/icons/creative-commons-sa-brands.svg')] ![mask-size:contain] ![mask-repeat:no-repeat] ![mask-position:center] bg-black" />
|
>
|
||||||
|
<div
|
||||||
|
className="w-6 aspect-square [mask:url('/icons/creative-commons-brands.svg')]
|
||||||
|
![mask-size:contain] ![mask-repeat:no-repeat] ![mask-position:center] bg-black"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className="w-6 aspect-square [mask:url('/icons/creative-commons-by-brands.svg')]
|
||||||
|
![mask-size:contain] ![mask-repeat:no-repeat] ![mask-position:center] bg-black"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className="w-6 aspect-square [mask:url('/icons/creative-commons-sa-brands.svg')]
|
||||||
|
![mask-size:contain] ![mask-repeat:no-repeat] ![mask-position:center] bg-black"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
<p>
|
<p>
|
||||||
|
@ -232,14 +237,18 @@ export default function MainPanel(props: Props): JSX.Element {
|
||||||
<div className="mt-12 mb-4 grid h-4 grid-flow-col place-content-center gap-8">
|
<div className="mt-12 mb-4 grid h-4 grid-flow-col place-content-center gap-8">
|
||||||
<a
|
<a
|
||||||
aria-label="Browse our GitHub repository, which include this website source code"
|
aria-label="Browse our GitHub repository, which include this website source code"
|
||||||
className="transition-colors [mask:url('/icons/github-brands.svg')] ![mask-size:contain] ![mask-repeat:no-repeat] ![mask-position:center] w-10 aspect-square bg-black hover:bg-dark"
|
className="transition-colors [mask:url('/icons/github-brands.svg')]
|
||||||
|
![mask-size:contain] ![mask-repeat:no-repeat] ![mask-position:center]
|
||||||
|
w-10 aspect-square bg-black hover:bg-dark"
|
||||||
href="https://github.com/Accords-Library"
|
href="https://github.com/Accords-Library"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
></a>
|
></a>
|
||||||
<a
|
<a
|
||||||
aria-label="Join our Discord server!"
|
aria-label="Join our Discord server!"
|
||||||
className="transition-colors [mask:url('/icons/discord-brands.svg')] ![mask-size:contain] ![mask-repeat:no-repeat] ![mask-position:center] w-10 aspect-square bg-black hover:bg-dark"
|
className="transition-colors [mask:url('/icons/discord-brands.svg')]
|
||||||
|
![mask-size:contain] ![mask-repeat:no-repeat] ![mask-position:center]
|
||||||
|
w-10 aspect-square bg-black hover:bg-dark"
|
||||||
href="/discord"
|
href="/discord"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
|
import { Immutable } from "helpers/types";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function SubPanel(props: Props): JSX.Element {
|
export function SubPanel(props: Immutable<Props>): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<div className="grid pt-10 pb-20 px-6 desktop:py-8 desktop:px-10 gap-y-2 text-center">
|
<div className="grid pt-10 pb-20 px-6 desktop:py-8 desktop:px-10 gap-y-2 text-center">
|
||||||
{props.children}
|
{props.children}
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
import { Dispatch, SetStateAction } from "react";
|
import { useAppLayout } from "contexts/AppLayoutContext";
|
||||||
|
import { Immutable } from "helpers/types";
|
||||||
|
import { Dispatch, SetStateAction, useEffect } from "react";
|
||||||
|
import Hotkeys from "react-hot-keys";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
setState:
|
setState:
|
||||||
|
@ -8,42 +11,65 @@ interface Props {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
fillViewport?: boolean;
|
fillViewport?: boolean;
|
||||||
hideBackground?: boolean;
|
hideBackground?: boolean;
|
||||||
|
padding?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Popup(props: Props): JSX.Element {
|
export function Popup(props: Immutable<Props>): JSX.Element {
|
||||||
|
const {
|
||||||
|
setState,
|
||||||
|
state,
|
||||||
|
children,
|
||||||
|
fillViewport,
|
||||||
|
hideBackground,
|
||||||
|
padding = true,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const appLayout = useAppLayout();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
appLayout.setMenuGestures(!state);
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [state]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<Hotkeys
|
||||||
className={`fixed inset-0 z-50 grid place-content-center
|
keyName="escape"
|
||||||
transition-[backdrop-filter] duration-500 ${
|
allowRepeat
|
||||||
props.state
|
onKeyDown={() => {
|
||||||
? "[backdrop-filter:blur(2px)]"
|
setState(false);
|
||||||
: "pointer-events-none touch-none"
|
}}
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={`fixed bg-shade inset-0 transition-all duration-500 ${
|
className={`fixed inset-0 z-50 grid place-content-center
|
||||||
props.state ? "bg-opacity-50" : "bg-opacity-0"
|
transition-[backdrop-filter] duration-500 ${
|
||||||
}`}
|
state ? "[backdrop-filter:blur(2px)]" : "pointer-events-none touch-none"
|
||||||
onClick={() => {
|
}`}
|
||||||
props.setState(false);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div
|
|
||||||
className={`p-10 grid gap-4 place-items-center transition-transform ${
|
|
||||||
props.state ? "scale-100" : "scale-0"
|
|
||||||
} ${
|
|
||||||
props.fillViewport
|
|
||||||
? "absolute inset-10 top-20"
|
|
||||||
: "relative max-h-[80vh] overflow-y-auto mobile:w-[85vw]"
|
|
||||||
} ${
|
|
||||||
props.hideBackground
|
|
||||||
? ""
|
|
||||||
: "bg-light rounded-lg shadow-2xl shadow-shade"
|
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
{props.children}
|
<div
|
||||||
|
className={`fixed bg-shade inset-0 transition-all duration-500 ${
|
||||||
|
state ? "bg-opacity-50" : "bg-opacity-0"
|
||||||
|
}`}
|
||||||
|
onClick={() => {
|
||||||
|
setState(false);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className={`${
|
||||||
|
padding && "p-10 mobile:p-6"
|
||||||
|
} grid gap-4 place-items-center transition-transform ${
|
||||||
|
state ? "scale-100" : "scale-0"
|
||||||
|
} ${
|
||||||
|
fillViewport
|
||||||
|
? "absolute inset-10"
|
||||||
|
: "relative max-h-[80vh] overflow-y-auto mobile:w-[85vw]"
|
||||||
|
} ${
|
||||||
|
hideBackground ? "" : "bg-light rounded-lg shadow-2xl shadow-shade"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Hotkeys>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,29 +1,22 @@
|
||||||
import { GetPostQuery } from "graphql/generated";
|
import { AppStaticProps } from "graphql/getAppStaticProps";
|
||||||
import useSmartLanguage from "hooks/useSmartLanguage";
|
import { prettySlug } from "helpers/formatters";
|
||||||
import { AppStaticProps } from "queries/getAppStaticProps";
|
import { getStatusDescription } from "helpers/others";
|
||||||
import { getStatusDescription, prettySlug } from "queries/helpers";
|
import { Immutable, PostWithTranslations } from "helpers/types";
|
||||||
import AppLayout from "./AppLayout";
|
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
||||||
import Chip from "./Chip";
|
import { AppLayout } from "./AppLayout";
|
||||||
import HorizontalLine from "./HorizontalLine";
|
import { Chip } from "./Chip";
|
||||||
import Markdawn from "./Markdown/Markdawn";
|
import { HorizontalLine } from "./HorizontalLine";
|
||||||
import TOC from "./Markdown/TOC";
|
import { Markdawn } from "./Markdown/Markdawn";
|
||||||
import ReturnButton, { ReturnButtonType } from "./PanelComponents/ReturnButton";
|
import { TOC } from "./Markdown/TOC";
|
||||||
import ContentPanel from "./Panels/ContentPanel";
|
import { ReturnButton, ReturnButtonType } from "./PanelComponents/ReturnButton";
|
||||||
import SubPanel from "./Panels/SubPanel";
|
import { ContentPanel } from "./Panels/ContentPanel";
|
||||||
import RecorderChip from "./RecorderChip";
|
import { SubPanel } from "./Panels/SubPanel";
|
||||||
import ThumbnailHeader from "./ThumbnailHeader";
|
import { RecorderChip } from "./RecorderChip";
|
||||||
import ToolTip from "./ToolTip";
|
import { ThumbnailHeader } from "./ThumbnailHeader";
|
||||||
|
import { ToolTip } from "./ToolTip";
|
||||||
export type Post = Exclude<
|
|
||||||
Exclude<
|
|
||||||
GetPostQuery["posts"],
|
|
||||||
null | undefined
|
|
||||||
>["data"][number]["attributes"],
|
|
||||||
null | undefined
|
|
||||||
>;
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
post: Post;
|
post: PostWithTranslations;
|
||||||
langui: AppStaticProps["langui"];
|
langui: AppStaticProps["langui"];
|
||||||
languages: AppStaticProps["languages"];
|
languages: AppStaticProps["languages"];
|
||||||
currencies: AppStaticProps["currencies"];
|
currencies: AppStaticProps["currencies"];
|
||||||
|
@ -38,7 +31,7 @@ interface Props {
|
||||||
appendBody?: JSX.Element;
|
appendBody?: JSX.Element;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function PostPage(props: Props): JSX.Element {
|
export function PostPage(props: Immutable<Props>): JSX.Element {
|
||||||
const {
|
const {
|
||||||
post,
|
post,
|
||||||
langui,
|
langui,
|
||||||
|
|
|
@ -4,16 +4,18 @@ import {
|
||||||
PricePickerFragment,
|
PricePickerFragment,
|
||||||
UploadImageFragment,
|
UploadImageFragment,
|
||||||
} from "graphql/generated";
|
} from "graphql/generated";
|
||||||
import Link from "next/link";
|
import { AppStaticProps } from "graphql/getAppStaticProps";
|
||||||
import { AppStaticProps } from "queries/getAppStaticProps";
|
|
||||||
import {
|
import {
|
||||||
prettyDate,
|
prettyDate,
|
||||||
prettyDuration,
|
prettyDuration,
|
||||||
prettyPrice,
|
prettyPrice,
|
||||||
prettyShortenNumber,
|
prettyShortenNumber,
|
||||||
} from "queries/helpers";
|
} from "helpers/formatters";
|
||||||
import Chip from "./Chip";
|
import { ImageQuality } from "helpers/img";
|
||||||
import Img, { ImageQuality } from "./Img";
|
import { Immutable } from "helpers/types";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { Chip } from "./Chip";
|
||||||
|
import { Img } from "./Img";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
thumbnail?: UploadImageFragment | string | null | undefined;
|
thumbnail?: UploadImageFragment | string | null | undefined;
|
||||||
|
@ -26,6 +28,7 @@ interface Props {
|
||||||
topChips?: string[];
|
topChips?: string[];
|
||||||
bottomChips?: string[];
|
bottomChips?: string[];
|
||||||
keepInfoVisible?: boolean;
|
keepInfoVisible?: boolean;
|
||||||
|
stackNumber?: number;
|
||||||
metadata?: {
|
metadata?: {
|
||||||
currencies?: AppStaticProps["currencies"];
|
currencies?: AppStaticProps["currencies"];
|
||||||
release_date?: DatePickerFragment | null;
|
release_date?: DatePickerFragment | null;
|
||||||
|
@ -42,7 +45,7 @@ interface Props {
|
||||||
| { __typename: "anotherHoverlayName" };
|
| { __typename: "anotherHoverlayName" };
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ThumbnailPreview(props: Props): JSX.Element {
|
export function PreviewCard(props: Immutable<Props>): JSX.Element {
|
||||||
const {
|
const {
|
||||||
href,
|
href,
|
||||||
thumbnail,
|
thumbnail,
|
||||||
|
@ -50,6 +53,7 @@ export default function ThumbnailPreview(props: Props): JSX.Element {
|
||||||
title,
|
title,
|
||||||
subtitle,
|
subtitle,
|
||||||
description,
|
description,
|
||||||
|
stackNumber = 0,
|
||||||
topChips,
|
topChips,
|
||||||
bottomChips,
|
bottomChips,
|
||||||
keepInfoVisible,
|
keepInfoVisible,
|
||||||
|
@ -110,8 +114,41 @@ export default function ThumbnailPreview(props: Props): JSX.Element {
|
||||||
className="drop-shadow-shade-xl cursor-pointer grid items-end
|
className="drop-shadow-shade-xl cursor-pointer grid items-end
|
||||||
fine:[--cover-opacity:0] hover:[--cover-opacity:1] hover:scale-[1.02]
|
fine:[--cover-opacity:0] hover:[--cover-opacity:1] hover:scale-[1.02]
|
||||||
[--bg-opacity:0] hover:[--bg-opacity:0.5] [--play-opacity:0]
|
[--bg-opacity:0] hover:[--bg-opacity:0.5] [--play-opacity:0]
|
||||||
hover:[--play-opacity:100] transition-transform"
|
hover:[--play-opacity:100] transition-transform
|
||||||
|
[--stacked-top:0] hover:[--stacked-top:1]"
|
||||||
>
|
>
|
||||||
|
{stackNumber > 0 && (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
className="bg-light rounded-md overflow-hidden absolute transition-[top_transform]
|
||||||
|
inset-0 -top-[var(--stacked-top)*2.1rem] brightness-[0.8] sepia-[0.5]
|
||||||
|
scale-[calc(1-0.15*var(--stacked-top))]"
|
||||||
|
>
|
||||||
|
{thumbnail && (
|
||||||
|
<Img
|
||||||
|
className="opacity-30 "
|
||||||
|
image={thumbnail}
|
||||||
|
quality={ImageQuality.Medium}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className="bg-light rounded-md overflow-hidden absolute transition-[top_transform]
|
||||||
|
-top-[var(--stacked-top)*1rem] inset-0 brightness-[0.9] sepia-[0.2]
|
||||||
|
scale-[calc(1-0.06*var(--stacked-top))]"
|
||||||
|
>
|
||||||
|
{thumbnail && (
|
||||||
|
<Img
|
||||||
|
className="opacity-70"
|
||||||
|
image={thumbnail}
|
||||||
|
quality={ImageQuality.Medium}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
{thumbnail ? (
|
{thumbnail ? (
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Img
|
<Img
|
||||||
|
@ -123,6 +160,14 @@ export default function ThumbnailPreview(props: Props): JSX.Element {
|
||||||
image={thumbnail}
|
image={thumbnail}
|
||||||
quality={ImageQuality.Medium}
|
quality={ImageQuality.Medium}
|
||||||
/>
|
/>
|
||||||
|
{stackNumber > 0 && (
|
||||||
|
<div
|
||||||
|
className="absolute right-2 top-2 text-light bg-black
|
||||||
|
bg-opacity-60 px-2 rounded-full"
|
||||||
|
>
|
||||||
|
{stackNumber}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{hoverlay && hoverlay.__typename === "Video" && (
|
{hoverlay && hoverlay.__typename === "Video" && (
|
||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
|
@ -149,18 +194,26 @@ export default function ThumbnailPreview(props: Props): JSX.Element {
|
||||||
) : (
|
) : (
|
||||||
<div
|
<div
|
||||||
style={{ aspectRatio: thumbnailAspectRatio }}
|
style={{ aspectRatio: thumbnailAspectRatio }}
|
||||||
className={`w-full bg-light ${
|
className={`w-full bg-light relative ${
|
||||||
keepInfoVisible
|
keepInfoVisible
|
||||||
? "rounded-t-md"
|
? "rounded-t-md"
|
||||||
: "rounded-md coarse:rounded-b-none"
|
: "rounded-md coarse:rounded-b-none"
|
||||||
}`}
|
}`}
|
||||||
></div>
|
>
|
||||||
|
{stackNumber > 0 && (
|
||||||
|
<div
|
||||||
|
className="absolute right-2 top-2 text-light bg-black
|
||||||
|
bg-opacity-60 px-2 rounded-full"
|
||||||
|
>
|
||||||
|
{stackNumber}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
<div
|
<div
|
||||||
className={`linearbg-obi ${
|
className={`linearbg-obi ${
|
||||||
keepInfoVisible
|
!keepInfoVisible &&
|
||||||
? "-mt-[0.3333em]"
|
`fine:drop-shadow-shade-lg fine:absolute coarse:rounded-b-md
|
||||||
: `fine:drop-shadow-shade-lg fine:absolute coarse:rounded-b-md
|
|
||||||
bottom-2 -inset-x-0.5 opacity-[var(--cover-opacity)]`
|
bottom-2 -inset-x-0.5 opacity-[var(--cover-opacity)]`
|
||||||
} transition-opacity z-20 grid p-4 gap-2`}
|
} transition-opacity z-20 grid p-4 gap-2`}
|
||||||
>
|
>
|
||||||
|
@ -173,11 +226,15 @@ export default function ThumbnailPreview(props: Props): JSX.Element {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="my-1">
|
<div className="my-1">
|
||||||
{pre_title && <p className="leading-none mb-1">{pre_title}</p>}
|
{pre_title && (
|
||||||
{title && (
|
<p className="leading-none mb-1 break-words">{pre_title}</p>
|
||||||
<p className="font-headers text-lg leading-none">{title}</p>
|
|
||||||
)}
|
)}
|
||||||
{subtitle && <p className="leading-none">{subtitle}</p>}
|
{title && (
|
||||||
|
<p className="font-headers text-lg leading-none break-words">
|
||||||
|
{title}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
{subtitle && <p className="leading-none break-words">{subtitle}</p>}
|
||||||
</div>
|
</div>
|
||||||
{description && <p>{description}</p>}
|
{description && <p>{description}</p>}
|
||||||
{bottomChips && bottomChips.length > 0 && (
|
{bottomChips && bottomChips.length > 0 && (
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import { UploadImageFragment } from "graphql/generated";
|
import { UploadImageFragment } from "graphql/generated";
|
||||||
|
import { ImageQuality } from "helpers/img";
|
||||||
|
import { Immutable } from "helpers/types";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import Chip from "./Chip";
|
import { Chip } from "./Chip";
|
||||||
import Img, { ImageQuality } from "./Img";
|
import { Img } from "./Img";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
thumbnail?: UploadImageFragment | string | null | undefined;
|
thumbnail?: UploadImageFragment | string | null | undefined;
|
||||||
|
@ -14,7 +16,7 @@ interface Props {
|
||||||
bottomChips?: string[];
|
bottomChips?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function PreviewLine(props: Props): JSX.Element {
|
export function PreviewLine(props: Immutable<Props>): JSX.Element {
|
||||||
const {
|
const {
|
||||||
href,
|
href,
|
||||||
thumbnail,
|
thumbnail,
|
||||||
|
@ -29,8 +31,9 @@ export default function PreviewLine(props: Props): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<Link href={href} passHref>
|
<Link href={href} passHref>
|
||||||
<div
|
<div
|
||||||
className="drop-shadow-shade-xl rounded-md bg-light cursor-pointer hover:scale-[1.02]
|
className="drop-shadow-shade-xl rounded-md bg-light cursor-pointer
|
||||||
transition-transform flex flex-row gap-4 overflow-hidden place-items-center pr-4 w-full h-36"
|
hover:scale-[1.02] transition-transform flex flex-row gap-4
|
||||||
|
overflow-hidden place-items-center pr-4 w-full h-36"
|
||||||
>
|
>
|
||||||
{thumbnail ? (
|
{thumbnail ? (
|
||||||
<div className="h-full aspect-[3/2]">
|
<div className="h-full aspect-[3/2]">
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
import Chip from "components/Chip";
|
import { Chip } from "components/Chip";
|
||||||
import { RecorderChipFragment } from "graphql/generated";
|
import { RecorderChipFragment } from "graphql/generated";
|
||||||
import { AppStaticProps } from "queries/getAppStaticProps";
|
import { AppStaticProps } from "graphql/getAppStaticProps";
|
||||||
import Img, { ImageQuality } from "./Img";
|
import { ImageQuality } from "helpers/img";
|
||||||
import Markdawn from "./Markdown/Markdawn";
|
import { Immutable } from "helpers/types";
|
||||||
import ToolTip from "./ToolTip";
|
import { Img } from "./Img";
|
||||||
|
import { Markdawn } from "./Markdown/Markdawn";
|
||||||
|
import { ToolTip } from "./ToolTip";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
className?: string;
|
className?: string;
|
||||||
|
@ -11,7 +13,7 @@ interface Props {
|
||||||
langui: AppStaticProps["langui"];
|
langui: AppStaticProps["langui"];
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function RecorderChip(props: Props): JSX.Element {
|
export function RecorderChip(props: Immutable<Props>): JSX.Element {
|
||||||
const { recorder, langui } = props;
|
const { recorder, langui } = props;
|
||||||
return (
|
return (
|
||||||
<ToolTip
|
<ToolTip
|
||||||
|
@ -49,10 +51,7 @@ export default function RecorderChip(props: Props): JSX.Element {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{recorder.bio?.[0] && <Markdawn text={recorder.bio[0].bio ?? ""} />}
|
{recorder.bio?.[0] && <Markdawn text={recorder.bio[0].bio ?? ""} />}
|
||||||
|
|
||||||
{/* <Button className="cursor-not-allowed">View profile</Button> */}
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
placement="top"
|
placement="top"
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { Immutable } from "helpers/types";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
@ -6,7 +7,7 @@ interface Props {
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function SVG(props: Props): JSX.Element {
|
export function SVG(props: Immutable<Props>): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<div className={props.className}>
|
<div className={props.className}>
|
||||||
<Image
|
<Image
|
||||||
|
|
|
@ -1,36 +1,31 @@
|
||||||
import Chip from "components/Chip";
|
import { Chip } from "components/Chip";
|
||||||
import Img, { ImageQuality } from "components/Img";
|
import { Img } from "components/Img";
|
||||||
import InsetBox from "components/InsetBox";
|
import { InsetBox } from "components/InsetBox";
|
||||||
import Markdawn from "components/Markdown/Markdawn";
|
import { Markdawn } from "components/Markdown/Markdawn";
|
||||||
import { GetContentQuery, UploadImageFragment } from "graphql/generated";
|
import { GetContentTextQuery, UploadImageFragment } from "graphql/generated";
|
||||||
import { AppStaticProps } from "queries/getAppStaticProps";
|
import { AppStaticProps } from "graphql/getAppStaticProps";
|
||||||
import { prettyinlineTitle, prettySlug, slugify } from "queries/helpers";
|
import { prettyinlineTitle, prettySlug, slugify } from "helpers/formatters";
|
||||||
|
import { getAssetURL, ImageQuality } from "helpers/img";
|
||||||
|
import { Immutable } from "helpers/types";
|
||||||
|
import { useLightBox } from "hooks/useLightBox";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
pre_title?: string | null | undefined;
|
pre_title?: string | null | undefined;
|
||||||
title: string | null | undefined;
|
title: string | null | undefined;
|
||||||
subtitle?: string | null | undefined;
|
subtitle?: string | null | undefined;
|
||||||
description?: string | null | undefined;
|
description?: string | null | undefined;
|
||||||
type?: Exclude<
|
type?: NonNullable<
|
||||||
Exclude<
|
NonNullable<GetContentTextQuery["contents"]>["data"][number]["attributes"]
|
||||||
GetContentQuery["contents"],
|
|
||||||
null | undefined
|
|
||||||
>["data"][number]["attributes"],
|
|
||||||
null | undefined
|
|
||||||
>["type"];
|
>["type"];
|
||||||
categories?: Exclude<
|
categories?: NonNullable<
|
||||||
Exclude<
|
NonNullable<GetContentTextQuery["contents"]>["data"][number]["attributes"]
|
||||||
GetContentQuery["contents"],
|
|
||||||
null | undefined
|
|
||||||
>["data"][number]["attributes"],
|
|
||||||
null | undefined
|
|
||||||
>["categories"];
|
>["categories"];
|
||||||
thumbnail?: UploadImageFragment | null | undefined;
|
thumbnail?: UploadImageFragment | null | undefined;
|
||||||
langui: AppStaticProps["langui"];
|
langui: AppStaticProps["langui"];
|
||||||
languageSwitcher?: JSX.Element;
|
languageSwitcher?: JSX.Element;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ThumbnailHeader(props: Props): JSX.Element {
|
export function ThumbnailHeader(props: Immutable<Props>): JSX.Element {
|
||||||
const {
|
const {
|
||||||
langui,
|
langui,
|
||||||
pre_title,
|
pre_title,
|
||||||
|
@ -43,16 +38,21 @@ export default function ThumbnailHeader(props: Props): JSX.Element {
|
||||||
languageSwitcher,
|
languageSwitcher,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
|
const [openLightBox, LightBox] = useLightBox();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<LightBox />
|
||||||
<div className="grid place-items-center gap-12 mb-12">
|
<div className="grid place-items-center gap-12 mb-12">
|
||||||
<div className="drop-shadow-shade-lg">
|
<div className="drop-shadow-shade-lg">
|
||||||
{thumbnail ? (
|
{thumbnail ? (
|
||||||
<Img
|
<Img
|
||||||
className=" rounded-xl"
|
className="rounded-xl cursor-pointer"
|
||||||
image={thumbnail}
|
image={thumbnail}
|
||||||
quality={ImageQuality.Medium}
|
quality={ImageQuality.Medium}
|
||||||
priority
|
onClick={() => {
|
||||||
|
openLightBox([getAssetURL(thumbnail.url, ImageQuality.Large)]);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="w-96 aspect-[4/3] bg-light rounded-xl"></div>
|
<div className="w-96 aspect-[4/3] bg-light rounded-xl"></div>
|
||||||
|
|
|
@ -3,13 +3,13 @@ import "tippy.js/animations/scale-subtle.css";
|
||||||
|
|
||||||
interface Props extends TippyProps {}
|
interface Props extends TippyProps {}
|
||||||
|
|
||||||
export default function ToolTip(props: Props): JSX.Element {
|
export function ToolTip(props: Props): JSX.Element {
|
||||||
const newProps = { ...props };
|
const newProps: Props = {
|
||||||
|
delay: [150, 0],
|
||||||
// Set defaults
|
interactive: true,
|
||||||
if (newProps.delay === undefined) newProps.delay = [150, 0];
|
animation: "scale-subtle",
|
||||||
if (newProps.interactive === undefined) newProps.interactive = true;
|
...props,
|
||||||
if (newProps.animation === undefined) newProps.animation = "scale-subtle";
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tippy className={`text-[80%] ${newProps.className}`} {...newProps}>
|
<Tippy className={`text-[80%] ${newProps.className}`} {...newProps}>
|
||||||
|
|
|
@ -1,22 +1,20 @@
|
||||||
import Chip from "components/Chip";
|
import { Chip } from "components/Chip";
|
||||||
import ToolTip from "components/ToolTip";
|
import { ToolTip } from "components/ToolTip";
|
||||||
import {
|
import {
|
||||||
Enum_Componenttranslationschronologyitem_Status,
|
Enum_Componenttranslationschronologyitem_Status,
|
||||||
GetChronologyItemsQuery,
|
GetChronologyItemsQuery,
|
||||||
} from "graphql/generated";
|
} from "graphql/generated";
|
||||||
import { AppStaticProps } from "queries/getAppStaticProps";
|
import { AppStaticProps } from "graphql/getAppStaticProps";
|
||||||
import { getStatusDescription } from "queries/helpers";
|
import { getStatusDescription } from "helpers/others";
|
||||||
|
import { Immutable } from "helpers/types";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
item: Exclude<
|
item: NonNullable<GetChronologyItemsQuery["chronologyItems"]>["data"][number];
|
||||||
GetChronologyItemsQuery["chronologyItems"],
|
|
||||||
null | undefined
|
|
||||||
>["data"][number];
|
|
||||||
displayYear: boolean;
|
displayYear: boolean;
|
||||||
langui: AppStaticProps["langui"];
|
langui: AppStaticProps["langui"];
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ChronologyItemComponent(props: Props): JSX.Element {
|
export function ChronologyItemComponent(props: Immutable<Props>): JSX.Element {
|
||||||
const { langui } = props;
|
const { langui } = props;
|
||||||
|
|
||||||
function generateAnchor(
|
function generateAnchor(
|
||||||
|
@ -71,7 +69,8 @@ export default function ChronologyItemComponent(props: Props): JSX.Element {
|
||||||
if (props.item.attributes) {
|
if (props.item.attributes) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="grid place-content-start grid-rows-[auto_1fr] grid-cols-[4em] py-4 px-8 rounded-2xl target:bg-mid target:py-8 target:my-4"
|
className="grid place-content-start grid-rows-[auto_1fr] grid-cols-[4em]
|
||||||
|
py-4 px-8 rounded-2xl target:bg-mid target:py-8 target:my-4"
|
||||||
id={generateAnchor(
|
id={generateAnchor(
|
||||||
props.item.attributes.year,
|
props.item.attributes.year,
|
||||||
props.item.attributes.month,
|
props.item.attributes.month,
|
||||||
|
@ -100,7 +99,10 @@ export default function ChronologyItemComponent(props: Props): JSX.Element {
|
||||||
<>
|
<>
|
||||||
{translation && (
|
{translation && (
|
||||||
<>
|
<>
|
||||||
<div className="place-items-start place-content-start grid grid-flow-col gap-2">
|
<div
|
||||||
|
className="place-items-start
|
||||||
|
place-content-start grid grid-flow-col gap-2"
|
||||||
|
>
|
||||||
{translation.status !==
|
{translation.status !==
|
||||||
Enum_Componenttranslationschronologyitem_Status.Done && (
|
Enum_Componenttranslationschronologyitem_Status.Done && (
|
||||||
<ToolTip
|
<ToolTip
|
||||||
|
@ -125,7 +127,8 @@ export default function ChronologyItemComponent(props: Props): JSX.Element {
|
||||||
className={
|
className={
|
||||||
event.translations &&
|
event.translations &&
|
||||||
event.translations.length > 1
|
event.translations.length > 1
|
||||||
? "before:content-['-'] before:text-dark before:inline-block before:w-4 before:ml-[-1em] mt-2 whitespace-pre-line"
|
? `before:content-['-'] before:text-dark before:inline-block
|
||||||
|
before:w-4 before:ml-[-1em] mt-2 whitespace-pre-line`
|
||||||
: "whitespace-pre-line"
|
: "whitespace-pre-line"
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
import ChronologyItemComponent from "components/Wiki/Chronology/ChronologyItemComponent";
|
import { ChronologyItemComponent } from "components/Wiki/Chronology/ChronologyItemComponent";
|
||||||
import { GetChronologyItemsQuery } from "graphql/generated";
|
import { GetChronologyItemsQuery } from "graphql/generated";
|
||||||
import { AppStaticProps } from "queries/getAppStaticProps";
|
import { AppStaticProps } from "graphql/getAppStaticProps";
|
||||||
|
import { Immutable } from "helpers/types";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
year: number;
|
year: number;
|
||||||
items: Exclude<
|
items: NonNullable<
|
||||||
GetChronologyItemsQuery["chronologyItems"],
|
GetChronologyItemsQuery["chronologyItems"]
|
||||||
null | undefined
|
|
||||||
>["data"][number][];
|
>["data"][number][];
|
||||||
langui: AppStaticProps["langui"];
|
langui: AppStaticProps["langui"];
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ChronologyYearComponent(props: Props): JSX.Element {
|
export function ChronologyYearComponent(props: Immutable<Props>): JSX.Element {
|
||||||
const { langui } = props;
|
const { langui } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import useDarkMode from "hooks/useDarkMode";
|
import { Immutable } from "helpers/types";
|
||||||
import useStateWithLocalStorage from "hooks/useStateWithLocalStorage";
|
import { useDarkMode } from "hooks/useDarkMode";
|
||||||
import React, { ReactNode, useContext } from "react";
|
import { useStateWithLocalStorage } from "hooks/useStateWithLocalStorage";
|
||||||
|
import React, { ReactNode, useContext, useState } from "react";
|
||||||
|
|
||||||
interface AppLayoutState {
|
interface AppLayoutState {
|
||||||
subPanelOpen: boolean | undefined;
|
subPanelOpen: boolean | undefined;
|
||||||
|
@ -14,6 +15,7 @@ interface AppLayoutState {
|
||||||
currency: string | undefined;
|
currency: string | undefined;
|
||||||
playerName: string | undefined;
|
playerName: string | undefined;
|
||||||
preferredLanguages: string[] | undefined;
|
preferredLanguages: string[] | undefined;
|
||||||
|
menuGestures: boolean;
|
||||||
setSubPanelOpen: React.Dispatch<React.SetStateAction<boolean | undefined>>;
|
setSubPanelOpen: React.Dispatch<React.SetStateAction<boolean | undefined>>;
|
||||||
setConfigPanelOpen: React.Dispatch<React.SetStateAction<boolean | undefined>>;
|
setConfigPanelOpen: React.Dispatch<React.SetStateAction<boolean | undefined>>;
|
||||||
setMainPanelReduced: React.Dispatch<
|
setMainPanelReduced: React.Dispatch<
|
||||||
|
@ -31,6 +33,7 @@ interface AppLayoutState {
|
||||||
setPreferredLanguages: React.Dispatch<
|
setPreferredLanguages: React.Dispatch<
|
||||||
React.SetStateAction<string[] | undefined>
|
React.SetStateAction<string[] | undefined>
|
||||||
>;
|
>;
|
||||||
|
setMenuGestures: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* eslint-disable @typescript-eslint/no-empty-function */
|
/* eslint-disable @typescript-eslint/no-empty-function */
|
||||||
|
@ -46,6 +49,7 @@ const initialState: AppLayoutState = {
|
||||||
currency: "USD",
|
currency: "USD",
|
||||||
playerName: "",
|
playerName: "",
|
||||||
preferredLanguages: [],
|
preferredLanguages: [],
|
||||||
|
menuGestures: true,
|
||||||
setSubPanelOpen: () => {},
|
setSubPanelOpen: () => {},
|
||||||
setMainPanelReduced: () => {},
|
setMainPanelReduced: () => {},
|
||||||
setMainPanelOpen: () => {},
|
setMainPanelOpen: () => {},
|
||||||
|
@ -57,6 +61,7 @@ const initialState: AppLayoutState = {
|
||||||
setCurrency: () => {},
|
setCurrency: () => {},
|
||||||
setPlayerName: () => {},
|
setPlayerName: () => {},
|
||||||
setPreferredLanguages: () => {},
|
setPreferredLanguages: () => {},
|
||||||
|
setMenuGestures: () => {},
|
||||||
};
|
};
|
||||||
/* eslint-enable @typescript-eslint/no-empty-function */
|
/* eslint-enable @typescript-eslint/no-empty-function */
|
||||||
|
|
||||||
|
@ -72,7 +77,7 @@ interface Props {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function AppContextProvider(props: Props): JSX.Element {
|
export function AppContextProvider(props: Immutable<Props>): JSX.Element {
|
||||||
const [subPanelOpen, setSubPanelOpen] = useStateWithLocalStorage<
|
const [subPanelOpen, setSubPanelOpen] = useStateWithLocalStorage<
|
||||||
boolean | undefined
|
boolean | undefined
|
||||||
>("subPanelOpen", initialState.subPanelOpen);
|
>("subPanelOpen", initialState.subPanelOpen);
|
||||||
|
@ -115,6 +120,8 @@ export function AppContextProvider(props: Props): JSX.Element {
|
||||||
string[] | undefined
|
string[] | undefined
|
||||||
>("preferredLanguages", initialState.preferredLanguages);
|
>("preferredLanguages", initialState.preferredLanguages);
|
||||||
|
|
||||||
|
const [menuGestures, setMenuGestures] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppContext.Provider
|
<AppContext.Provider
|
||||||
value={{
|
value={{
|
||||||
|
@ -129,6 +136,7 @@ export function AppContextProvider(props: Props): JSX.Element {
|
||||||
currency,
|
currency,
|
||||||
playerName,
|
playerName,
|
||||||
preferredLanguages,
|
preferredLanguages,
|
||||||
|
menuGestures,
|
||||||
setSubPanelOpen,
|
setSubPanelOpen,
|
||||||
setConfigPanelOpen,
|
setConfigPanelOpen,
|
||||||
setMainPanelReduced,
|
setMainPanelReduced,
|
||||||
|
@ -140,6 +148,7 @@ export function AppContextProvider(props: Props): JSX.Element {
|
||||||
setCurrency,
|
setCurrency,
|
||||||
setPlayerName,
|
setPlayerName,
|
||||||
setPreferredLanguages,
|
setPreferredLanguages,
|
||||||
|
setMenuGestures,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{props.children}
|
{props.children}
|
||||||
|
|
|
@ -4,22 +4,18 @@ import {
|
||||||
GetWebsiteInterfaceQuery,
|
GetWebsiteInterfaceQuery,
|
||||||
} from "graphql/generated";
|
} from "graphql/generated";
|
||||||
import { getReadySdk } from "graphql/sdk";
|
import { getReadySdk } from "graphql/sdk";
|
||||||
|
import { Immutable } from "helpers/types";
|
||||||
import { GetStaticPropsContext } from "next";
|
import { GetStaticPropsContext } from "next";
|
||||||
|
|
||||||
export interface AppStaticProps {
|
export type AppStaticProps = Immutable<{
|
||||||
langui: Exclude<
|
langui: NonNullable<
|
||||||
Exclude<
|
NonNullable<
|
||||||
GetWebsiteInterfaceQuery["websiteInterfaces"],
|
GetWebsiteInterfaceQuery["websiteInterfaces"]
|
||||||
null | undefined
|
>["data"][number]["attributes"]
|
||||||
>["data"][number]["attributes"],
|
|
||||||
null | undefined
|
|
||||||
>;
|
>;
|
||||||
currencies: Exclude<
|
currencies: NonNullable<GetCurrenciesQuery["currencies"]>["data"];
|
||||||
GetCurrenciesQuery["currencies"],
|
languages: NonNullable<GetLanguagesQuery["languages"]>["data"];
|
||||||
null | undefined
|
}>;
|
||||||
>["data"];
|
|
||||||
languages: Exclude<GetLanguagesQuery["languages"], null | undefined>["data"];
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getAppStaticProps(
|
export async function getAppStaticProps(
|
||||||
context: GetStaticPropsContext
|
context: GetStaticPropsContext
|
||||||
|
@ -50,9 +46,11 @@ export async function getAppStaticProps(
|
||||||
})
|
})
|
||||||
).websiteInterfaces?.data[0].attributes;
|
).websiteInterfaces?.data[0].attributes;
|
||||||
|
|
||||||
return {
|
const appStaticProps: AppStaticProps = {
|
||||||
langui: langui ?? ({} as AppStaticProps["langui"]),
|
langui: langui ?? {},
|
||||||
currencies: currencies?.data ?? ({} as AppStaticProps["currencies"]),
|
currencies: currencies?.data ?? [],
|
||||||
languages: languages?.data ?? ({} as AppStaticProps["languages"]),
|
languages: languages?.data ?? [],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
return appStaticProps;
|
||||||
}
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
import { PostWithTranslations } from "helpers/types";
|
||||||
|
import { GetStaticPropsContext } from "next";
|
||||||
|
import { AppStaticProps, getAppStaticProps } from "./getAppStaticProps";
|
||||||
|
import { getReadySdk } from "./sdk";
|
||||||
|
|
||||||
|
export interface PostStaticProps extends AppStaticProps {
|
||||||
|
post: PostWithTranslations;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPostStaticProps(
|
||||||
|
slug: string
|
||||||
|
): (
|
||||||
|
context: GetStaticPropsContext
|
||||||
|
) => Promise<{ notFound: boolean } | { props: PostStaticProps }> {
|
||||||
|
return async (context: GetStaticPropsContext) => {
|
||||||
|
const sdk = getReadySdk();
|
||||||
|
const post = await sdk.getPost({
|
||||||
|
slug: slug,
|
||||||
|
language_code: context.locale ?? "en",
|
||||||
|
});
|
||||||
|
if (post.posts?.data[0].attributes?.translations) {
|
||||||
|
const props: PostStaticProps = {
|
||||||
|
...(await getAppStaticProps(context)),
|
||||||
|
post: post.posts.data[0].attributes as PostWithTranslations,
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
props: props,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return { notFound: true };
|
||||||
|
};
|
||||||
|
}
|
|
@ -14,7 +14,13 @@ query devGetContents {
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
titles {
|
|
||||||
|
ranged_contents {
|
||||||
|
data {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
translations {
|
||||||
language {
|
language {
|
||||||
data {
|
data {
|
||||||
id
|
id
|
||||||
|
@ -22,62 +28,36 @@ query devGetContents {
|
||||||
}
|
}
|
||||||
title
|
title
|
||||||
description
|
description
|
||||||
}
|
text_set {
|
||||||
ranged_contents {
|
source_language {
|
||||||
data {
|
data {
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
}
|
|
||||||
text_set {
|
|
||||||
language {
|
|
||||||
data {
|
|
||||||
id
|
|
||||||
}
|
}
|
||||||
}
|
status
|
||||||
source_language {
|
transcribers {
|
||||||
data {
|
data {
|
||||||
id
|
id
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
translators {
|
||||||
status
|
data {
|
||||||
transcribers {
|
id
|
||||||
data {
|
}
|
||||||
id
|
|
||||||
}
|
}
|
||||||
}
|
proofreaders {
|
||||||
translators {
|
data {
|
||||||
data {
|
id
|
||||||
id
|
}
|
||||||
}
|
}
|
||||||
|
text
|
||||||
}
|
}
|
||||||
proofreaders {
|
|
||||||
data {
|
|
||||||
id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
text
|
|
||||||
}
|
|
||||||
video_set {
|
|
||||||
id
|
|
||||||
}
|
|
||||||
audio_set {
|
|
||||||
id
|
|
||||||
}
|
}
|
||||||
thumbnail {
|
thumbnail {
|
||||||
data {
|
data {
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
next_recommended {
|
|
||||||
data {
|
|
||||||
id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
previous_recommended {
|
|
||||||
data {
|
|
||||||
id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,77 +0,0 @@
|
||||||
query getContent($slug: String, $language_code: String) {
|
|
||||||
contents(filters: { slug: { eq: $slug } }) {
|
|
||||||
data {
|
|
||||||
attributes {
|
|
||||||
slug
|
|
||||||
titles(filters: { language: { code: { eq: $language_code } } }) {
|
|
||||||
pre_title
|
|
||||||
title
|
|
||||||
subtitle
|
|
||||||
description
|
|
||||||
}
|
|
||||||
categories {
|
|
||||||
data {
|
|
||||||
id
|
|
||||||
attributes {
|
|
||||||
name
|
|
||||||
short
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
type {
|
|
||||||
data {
|
|
||||||
attributes {
|
|
||||||
slug
|
|
||||||
titles(filters: { language: { code: { eq: $language_code } } }) {
|
|
||||||
title
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ranged_contents {
|
|
||||||
data {
|
|
||||||
id
|
|
||||||
attributes {
|
|
||||||
slug
|
|
||||||
scan_set {
|
|
||||||
id
|
|
||||||
}
|
|
||||||
library_item {
|
|
||||||
data {
|
|
||||||
attributes {
|
|
||||||
slug
|
|
||||||
title
|
|
||||||
subtitle
|
|
||||||
thumbnail {
|
|
||||||
data {
|
|
||||||
attributes {
|
|
||||||
...uploadImage
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
text_set {
|
|
||||||
id
|
|
||||||
}
|
|
||||||
video_set {
|
|
||||||
id
|
|
||||||
}
|
|
||||||
audio_set {
|
|
||||||
id
|
|
||||||
}
|
|
||||||
thumbnail {
|
|
||||||
data {
|
|
||||||
attributes {
|
|
||||||
...uploadImage
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -4,12 +4,7 @@ query getContentText($slug: String, $language_code: String) {
|
||||||
id
|
id
|
||||||
attributes {
|
attributes {
|
||||||
slug
|
slug
|
||||||
titles {
|
|
||||||
pre_title
|
|
||||||
title
|
|
||||||
subtitle
|
|
||||||
description
|
|
||||||
}
|
|
||||||
categories {
|
categories {
|
||||||
data {
|
data {
|
||||||
id
|
id
|
||||||
|
@ -56,9 +51,7 @@ query getContentText($slug: String, $language_code: String) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
text_set {
|
translations {
|
||||||
status
|
|
||||||
text
|
|
||||||
language {
|
language {
|
||||||
data {
|
data {
|
||||||
attributes {
|
attributes {
|
||||||
|
@ -66,39 +59,48 @@ query getContentText($slug: String, $language_code: String) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
source_language {
|
pre_title
|
||||||
data {
|
title
|
||||||
attributes {
|
subtitle
|
||||||
code
|
description
|
||||||
|
text_set {
|
||||||
|
status
|
||||||
|
text
|
||||||
|
source_language {
|
||||||
|
data {
|
||||||
|
attributes {
|
||||||
|
code
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
transcribers {
|
||||||
transcribers {
|
data {
|
||||||
data {
|
id
|
||||||
id
|
attributes {
|
||||||
attributes {
|
...recorderChip
|
||||||
...recorderChip
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
translators {
|
||||||
translators {
|
data {
|
||||||
data {
|
id
|
||||||
id
|
attributes {
|
||||||
attributes {
|
...recorderChip
|
||||||
...recorderChip
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
proofreaders {
|
||||||
proofreaders {
|
data {
|
||||||
data {
|
id
|
||||||
id
|
attributes {
|
||||||
attributes {
|
...recorderChip
|
||||||
...recorderChip
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
notes
|
||||||
}
|
}
|
||||||
notes
|
|
||||||
}
|
}
|
||||||
|
|
||||||
thumbnail {
|
thumbnail {
|
||||||
data {
|
data {
|
||||||
attributes {
|
attributes {
|
||||||
|
@ -106,78 +108,47 @@ query getContentText($slug: String, $language_code: String) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
previous_recommended {
|
group {
|
||||||
data {
|
data {
|
||||||
attributes {
|
attributes {
|
||||||
slug
|
contents {
|
||||||
titles(filters: { language: { code: { eq: $language_code } } }) {
|
|
||||||
pre_title
|
|
||||||
title
|
|
||||||
subtitle
|
|
||||||
}
|
|
||||||
categories {
|
|
||||||
data {
|
|
||||||
id
|
|
||||||
attributes {
|
|
||||||
short
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
type {
|
|
||||||
data {
|
data {
|
||||||
attributes {
|
attributes {
|
||||||
slug
|
slug
|
||||||
titles(
|
translations {
|
||||||
filters: { language: { code: { eq: $language_code } } }
|
pre_title
|
||||||
) {
|
|
||||||
title
|
title
|
||||||
|
subtitle
|
||||||
}
|
}
|
||||||
}
|
categories {
|
||||||
}
|
data {
|
||||||
}
|
id
|
||||||
thumbnail {
|
attributes {
|
||||||
data {
|
short
|
||||||
attributes {
|
}
|
||||||
...uploadImage
|
}
|
||||||
}
|
}
|
||||||
}
|
type {
|
||||||
}
|
data {
|
||||||
}
|
attributes {
|
||||||
}
|
slug
|
||||||
}
|
titles(
|
||||||
next_recommended {
|
filters: {
|
||||||
data {
|
language: { code: { eq: $language_code } }
|
||||||
attributes {
|
}
|
||||||
slug
|
) {
|
||||||
titles(filters: { language: { code: { eq: $language_code } } }) {
|
title
|
||||||
pre_title
|
}
|
||||||
title
|
}
|
||||||
subtitle
|
}
|
||||||
}
|
}
|
||||||
categories {
|
thumbnail {
|
||||||
data {
|
data {
|
||||||
id
|
attributes {
|
||||||
attributes {
|
...uploadImage
|
||||||
short
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
type {
|
|
||||||
data {
|
|
||||||
attributes {
|
|
||||||
slug
|
|
||||||
titles(
|
|
||||||
filters: { language: { code: { eq: $language_code } } }
|
|
||||||
) {
|
|
||||||
title
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
thumbnail {
|
|
||||||
data {
|
|
||||||
attributes {
|
|
||||||
...uploadImage
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ query getContents($language_code: String) {
|
||||||
id
|
id
|
||||||
attributes {
|
attributes {
|
||||||
slug
|
slug
|
||||||
titles(filters: { language: { code: { eq: $language_code } } }) {
|
translations {
|
||||||
pre_title
|
pre_title
|
||||||
title
|
title
|
||||||
subtitle
|
subtitle
|
||||||
|
@ -55,14 +55,17 @@ query getContents($language_code: String) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
text_set {
|
group {
|
||||||
id
|
data {
|
||||||
}
|
attributes {
|
||||||
video_set {
|
combine
|
||||||
id
|
contents {
|
||||||
}
|
data {
|
||||||
audio_set {
|
id
|
||||||
id
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
thumbnail {
|
thumbnail {
|
||||||
data {
|
data {
|
||||||
|
|
|
@ -362,22 +362,18 @@ query getLibraryItem($slug: String, $language_code: String) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
titles(
|
translations {
|
||||||
filters: { language: { code: { eq: $language_code } } }
|
language {
|
||||||
) {
|
data {
|
||||||
|
attributes {
|
||||||
|
code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
pre_title
|
pre_title
|
||||||
title
|
title
|
||||||
subtitle
|
subtitle
|
||||||
}
|
}
|
||||||
text_set {
|
|
||||||
id
|
|
||||||
}
|
|
||||||
video_set {
|
|
||||||
id
|
|
||||||
}
|
|
||||||
audio_set {
|
|
||||||
id
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -132,6 +132,19 @@ query getWebsiteInterface($language_code: String) {
|
||||||
response_invalid_code
|
response_invalid_code
|
||||||
response_invalid_email
|
response_invalid_email
|
||||||
response_email_success
|
response_email_success
|
||||||
|
always_show_info
|
||||||
|
item_not_available
|
||||||
|
primary_language
|
||||||
|
secondary_language
|
||||||
|
combine_related_contents
|
||||||
|
previous_content
|
||||||
|
followup_content
|
||||||
|
videos
|
||||||
|
view_on
|
||||||
|
channel
|
||||||
|
subscribers
|
||||||
|
description
|
||||||
|
available_at
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
import { ContentWithTranslations, Immutable } from "./types";
|
||||||
|
|
||||||
|
type Group = Immutable<
|
||||||
|
NonNullable<
|
||||||
|
NonNullable<
|
||||||
|
NonNullable<
|
||||||
|
NonNullable<ContentWithTranslations["group"]>["data"]
|
||||||
|
>["attributes"]
|
||||||
|
>["contents"]
|
||||||
|
>["data"]
|
||||||
|
>;
|
||||||
|
|
||||||
|
export function getPreviousContent(group: Group, currentSlug: string) {
|
||||||
|
for (let index = 0; index < group.length; index += 1) {
|
||||||
|
const content = group[index];
|
||||||
|
if (content.attributes?.slug === currentSlug && index > 0) {
|
||||||
|
return group[index - 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getNextContent(group: Group, currentSlug: string) {
|
||||||
|
for (let index = 0; index < group.length; index += 1) {
|
||||||
|
const content = group[index];
|
||||||
|
if (content.attributes?.slug === currentSlug && index < group.length - 1) {
|
||||||
|
return group[index + 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
|
@ -1,20 +1,9 @@
|
||||||
import {
|
import { DatePickerFragment, PricePickerFragment } from "graphql/generated";
|
||||||
getAssetURL,
|
import { AppStaticProps } from "../graphql/getAppStaticProps";
|
||||||
getImgSizesByQuality,
|
import { convertPrice } from "./numbers";
|
||||||
ImageQuality,
|
import { Immutable } from "./types";
|
||||||
} from "components/Img";
|
|
||||||
import {
|
|
||||||
DatePickerFragment,
|
|
||||||
Enum_Componentsetstextset_Status,
|
|
||||||
GetCurrenciesQuery,
|
|
||||||
GetLibraryItemQuery,
|
|
||||||
GetLibraryItemScansQuery,
|
|
||||||
PricePickerFragment,
|
|
||||||
UploadImageFragment,
|
|
||||||
} from "graphql/generated";
|
|
||||||
import { AppStaticProps } from "./getAppStaticProps";
|
|
||||||
|
|
||||||
export function prettyDate(datePicker: DatePickerFragment): string {
|
export function prettyDate(datePicker: Immutable<DatePickerFragment>): string {
|
||||||
let result = "";
|
let result = "";
|
||||||
if (datePicker.year) result += datePicker.year.toString();
|
if (datePicker.year) result += datePicker.year.toString();
|
||||||
if (datePicker.month)
|
if (datePicker.month)
|
||||||
|
@ -25,7 +14,7 @@ export function prettyDate(datePicker: DatePickerFragment): string {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function prettyPrice(
|
export function prettyPrice(
|
||||||
pricePicker: PricePickerFragment,
|
pricePicker: Immutable<PricePickerFragment>,
|
||||||
currencies: AppStaticProps["currencies"],
|
currencies: AppStaticProps["currencies"],
|
||||||
targetCurrencyCode?: string
|
targetCurrencyCode?: string
|
||||||
): string {
|
): string {
|
||||||
|
@ -45,25 +34,6 @@ export function prettyPrice(
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function convertPrice(
|
|
||||||
pricePicker: PricePickerFragment,
|
|
||||||
targetCurrency: Exclude<
|
|
||||||
GetCurrenciesQuery["currencies"],
|
|
||||||
null | undefined
|
|
||||||
>["data"][number]
|
|
||||||
): number {
|
|
||||||
if (
|
|
||||||
pricePicker.amount &&
|
|
||||||
pricePicker.currency?.data?.attributes &&
|
|
||||||
targetCurrency.attributes
|
|
||||||
)
|
|
||||||
return (
|
|
||||||
(pricePicker.amount * pricePicker.currency.data.attributes.rate_to_usd) /
|
|
||||||
targetCurrency.attributes.rate_to_usd
|
|
||||||
);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function prettySlug(slug?: string, parentSlug?: string): string {
|
export function prettySlug(slug?: string, parentSlug?: string): string {
|
||||||
if (slug) {
|
if (slug) {
|
||||||
if (parentSlug && slug.startsWith(parentSlug))
|
if (parentSlug && slug.startsWith(parentSlug))
|
||||||
|
@ -88,7 +58,7 @@ export function prettyinlineTitle(
|
||||||
}
|
}
|
||||||
|
|
||||||
export function prettyItemType(
|
export function prettyItemType(
|
||||||
metadata: any,
|
metadata: Immutable<any>,
|
||||||
langui: AppStaticProps["langui"]
|
langui: AppStaticProps["langui"]
|
||||||
): string | undefined | null {
|
): string | undefined | null {
|
||||||
switch (metadata.__typename) {
|
switch (metadata.__typename) {
|
||||||
|
@ -110,7 +80,7 @@ export function prettyItemType(
|
||||||
}
|
}
|
||||||
|
|
||||||
export function prettyItemSubType(
|
export function prettyItemSubType(
|
||||||
metadata:
|
metadata: Immutable<
|
||||||
| {
|
| {
|
||||||
__typename: "ComponentMetadataAudio";
|
__typename: "ComponentMetadataAudio";
|
||||||
subtype?: {
|
subtype?: {
|
||||||
|
@ -187,6 +157,7 @@ export function prettyItemSubType(
|
||||||
}
|
}
|
||||||
| { __typename: "Error" }
|
| { __typename: "Error" }
|
||||||
| null
|
| null
|
||||||
|
>
|
||||||
): string {
|
): string {
|
||||||
if (metadata) {
|
if (metadata) {
|
||||||
switch (metadata.__typename) {
|
switch (metadata.__typename) {
|
||||||
|
@ -300,87 +271,6 @@ export function capitalizeString(string: string): string {
|
||||||
return words.join(" ");
|
return words.join(" ");
|
||||||
}
|
}
|
||||||
|
|
||||||
export function convertMmToInch(mm: number | null | undefined): string {
|
|
||||||
return mm ? (mm * 0.03937008).toPrecision(3) : "";
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface OgImage {
|
|
||||||
image: string;
|
|
||||||
width: number;
|
|
||||||
height: number;
|
|
||||||
alt: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getOgImage(
|
|
||||||
quality: ImageQuality,
|
|
||||||
image: UploadImageFragment
|
|
||||||
): OgImage {
|
|
||||||
const imgSize = getImgSizesByQuality(
|
|
||||||
image.width ?? 0,
|
|
||||||
image.height ?? 0,
|
|
||||||
quality ? quality : ImageQuality.Small
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
image: getAssetURL(image.url, quality),
|
|
||||||
width: imgSize.width,
|
|
||||||
height: imgSize.height,
|
|
||||||
alt: image.alternativeText || "",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function sortContent(
|
|
||||||
contents:
|
|
||||||
| Exclude<
|
|
||||||
Exclude<
|
|
||||||
GetLibraryItemQuery["libraryItems"],
|
|
||||||
null | undefined
|
|
||||||
>["data"][number]["attributes"],
|
|
||||||
null | undefined
|
|
||||||
>["contents"]
|
|
||||||
| Exclude<
|
|
||||||
Exclude<
|
|
||||||
GetLibraryItemScansQuery["libraryItems"],
|
|
||||||
null | undefined
|
|
||||||
>["data"][number]["attributes"],
|
|
||||||
null | undefined
|
|
||||||
>["contents"]
|
|
||||||
) {
|
|
||||||
contents?.data.sort((a, b) => {
|
|
||||||
if (
|
|
||||||
a.attributes?.range[0]?.__typename === "ComponentRangePageRange" &&
|
|
||||||
b.attributes?.range[0]?.__typename === "ComponentRangePageRange"
|
|
||||||
) {
|
|
||||||
return (
|
|
||||||
a.attributes.range[0].starting_page -
|
|
||||||
b.attributes.range[0].starting_page
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getStatusDescription(
|
|
||||||
status: string,
|
|
||||||
langui: AppStaticProps["langui"]
|
|
||||||
): string | null | undefined {
|
|
||||||
switch (status) {
|
|
||||||
case Enum_Componentsetstextset_Status.Incomplete:
|
|
||||||
return langui.status_incomplete;
|
|
||||||
|
|
||||||
case Enum_Componentsetstextset_Status.Draft:
|
|
||||||
return langui.status_draft;
|
|
||||||
|
|
||||||
case Enum_Componentsetstextset_Status.Review:
|
|
||||||
return langui.status_review;
|
|
||||||
|
|
||||||
case Enum_Componentsetstextset_Status.Done:
|
|
||||||
return langui.status_done;
|
|
||||||
|
|
||||||
default:
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function slugify(string: string | undefined): string {
|
export function slugify(string: string | undefined): string {
|
||||||
if (!string) {
|
if (!string) {
|
||||||
return "";
|
return "";
|
||||||
|
@ -400,51 +290,3 @@ export function slugify(string: string | undefined): string {
|
||||||
.trim()
|
.trim()
|
||||||
.replace(/ /gu, "-");
|
.replace(/ /gu, "-");
|
||||||
}
|
}
|
||||||
|
|
||||||
export function randomInt(min: number, max: number) {
|
|
||||||
return Math.floor(Math.random() * (max - min)) + min;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getLocalesFromLanguages(
|
|
||||||
languages?: Array<{
|
|
||||||
language?: {
|
|
||||||
data?: {
|
|
||||||
attributes?: { code: string } | null;
|
|
||||||
} | null;
|
|
||||||
} | null;
|
|
||||||
} | null> | null
|
|
||||||
) {
|
|
||||||
return languages
|
|
||||||
? languages.map((language) => language?.language?.data?.attributes?.code)
|
|
||||||
: [];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getVideoThumbnailURL(uid: string): string {
|
|
||||||
return `${process.env.NEXT_PUBLIC_URL_WATCH}/videos/${uid}.webp`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getVideoFile(uid: string): string {
|
|
||||||
return `${process.env.NEXT_PUBLIC_URL_WATCH}/videos/${uid}.mp4`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function arrayMove<T>(arr: T[], old_index: number, new_index: number) {
|
|
||||||
arr.splice(new_index, 0, arr.splice(old_index, 1)[0]);
|
|
||||||
return arr;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getPreferredLanguage(
|
|
||||||
preferredLanguages: (string | undefined)[],
|
|
||||||
availableLanguages: Map<string, number>
|
|
||||||
): number | undefined {
|
|
||||||
for (const locale of preferredLanguages) {
|
|
||||||
if (locale && availableLanguages.has(locale)) {
|
|
||||||
return availableLanguages.get(locale);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isInteger(value: string): boolean {
|
|
||||||
// eslint-disable-next-line require-unicode-regexp
|
|
||||||
return /^[+-]?[0-9]+$/.test(value);
|
|
||||||
}
|
|
|
@ -0,0 +1,88 @@
|
||||||
|
import { UploadImageFragment } from "graphql/generated";
|
||||||
|
import { Immutable } from "./types";
|
||||||
|
|
||||||
|
export enum ImageQuality {
|
||||||
|
Small = "small",
|
||||||
|
Medium = "medium",
|
||||||
|
Large = "large",
|
||||||
|
Og = "og",
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OgImage {
|
||||||
|
image: string;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
alt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getAssetFilename(path: string): string {
|
||||||
|
let result = path.split("/");
|
||||||
|
result = result[result.length - 1].split(".");
|
||||||
|
result = result
|
||||||
|
.splice(0, result.length - 1)
|
||||||
|
.join(".")
|
||||||
|
.split("_");
|
||||||
|
return result[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getAssetURL(
|
||||||
|
url: string,
|
||||||
|
quality: Immutable<ImageQuality>
|
||||||
|
): string {
|
||||||
|
let newUrl = url;
|
||||||
|
newUrl = newUrl.replace(/^\/uploads/u, `/${quality}`);
|
||||||
|
newUrl = newUrl.replace(/.jpg$/u, ".webp");
|
||||||
|
newUrl = newUrl.replace(/.jpeg$/u, ".webp");
|
||||||
|
newUrl = newUrl.replace(/.png$/u, ".webp");
|
||||||
|
if (quality === ImageQuality.Og) newUrl = newUrl.replace(/.webp$/u, ".jpg");
|
||||||
|
return process.env.NEXT_PUBLIC_URL_IMG + newUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getImgSizesByMaxSize(
|
||||||
|
width: number,
|
||||||
|
height: number,
|
||||||
|
maxSize: number
|
||||||
|
): { width: number; height: number } {
|
||||||
|
if (width > height) {
|
||||||
|
if (width < maxSize) return { width: width, height: height };
|
||||||
|
return { width: maxSize, height: (height / width) * maxSize };
|
||||||
|
}
|
||||||
|
if (height < maxSize) return { width: width, height: height };
|
||||||
|
return { width: (width / height) * maxSize, height: maxSize };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getImgSizesByQuality(
|
||||||
|
width: number,
|
||||||
|
height: number,
|
||||||
|
quality: ImageQuality
|
||||||
|
): { width: number; height: number } {
|
||||||
|
switch (quality) {
|
||||||
|
case ImageQuality.Og:
|
||||||
|
return getImgSizesByMaxSize(width, height, 512);
|
||||||
|
case ImageQuality.Small:
|
||||||
|
return getImgSizesByMaxSize(width, height, 512);
|
||||||
|
case ImageQuality.Medium:
|
||||||
|
return getImgSizesByMaxSize(width, height, 1024);
|
||||||
|
case ImageQuality.Large:
|
||||||
|
return getImgSizesByMaxSize(width, height, 2048);
|
||||||
|
default:
|
||||||
|
return { width: 0, height: 0 };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getOgImage(
|
||||||
|
quality: Immutable<ImageQuality>,
|
||||||
|
image: Immutable<UploadImageFragment>
|
||||||
|
): OgImage {
|
||||||
|
const imgSize = getImgSizesByQuality(
|
||||||
|
image.width ?? 0,
|
||||||
|
image.height ?? 0,
|
||||||
|
quality ? quality : ImageQuality.Small
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
image: getAssetURL(image.url, quality),
|
||||||
|
width: imgSize.width,
|
||||||
|
height: imgSize.height,
|
||||||
|
alt: image.alternativeText || "",
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
import { GetCurrenciesQuery, PricePickerFragment } from "graphql/generated";
|
||||||
|
import { Immutable } from "./types";
|
||||||
|
|
||||||
|
export function convertPrice(
|
||||||
|
pricePicker: Immutable<PricePickerFragment>,
|
||||||
|
targetCurrency: Immutable<
|
||||||
|
NonNullable<GetCurrenciesQuery["currencies"]>["data"][number]
|
||||||
|
>
|
||||||
|
): number {
|
||||||
|
if (
|
||||||
|
pricePicker.amount &&
|
||||||
|
pricePicker.currency?.data?.attributes &&
|
||||||
|
targetCurrency.attributes
|
||||||
|
)
|
||||||
|
return (
|
||||||
|
(pricePicker.amount * pricePicker.currency.data.attributes.rate_to_usd) /
|
||||||
|
targetCurrency.attributes.rate_to_usd
|
||||||
|
);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function convertMmToInch(mm: number | null | undefined): string {
|
||||||
|
return mm ? (mm * 0.03937008).toPrecision(3) : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function randomInt(min: number, max: number) {
|
||||||
|
return Math.floor(Math.random() * (max - min)) + min;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isInteger(value: string): boolean {
|
||||||
|
// eslint-disable-next-line require-unicode-regexp
|
||||||
|
return /^[+-]?[0-9]+$/.test(value);
|
||||||
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
import {
|
||||||
|
Enum_Componentsetstextset_Status,
|
||||||
|
GetLibraryItemQuery,
|
||||||
|
GetLibraryItemScansQuery,
|
||||||
|
} from "graphql/generated";
|
||||||
|
import { AppStaticProps } from "../graphql/getAppStaticProps";
|
||||||
|
import { Immutable } from "./types";
|
||||||
|
|
||||||
|
type SortContentProps =
|
||||||
|
| NonNullable<
|
||||||
|
NonNullable<
|
||||||
|
GetLibraryItemQuery["libraryItems"]
|
||||||
|
>["data"][number]["attributes"]
|
||||||
|
>["contents"]
|
||||||
|
| NonNullable<
|
||||||
|
NonNullable<
|
||||||
|
GetLibraryItemScansQuery["libraryItems"]
|
||||||
|
>["data"][number]["attributes"]
|
||||||
|
>["contents"];
|
||||||
|
|
||||||
|
export function sortContent(contents: Immutable<SortContentProps>) {
|
||||||
|
if (contents) {
|
||||||
|
const newContent = { ...contents } as SortContentProps;
|
||||||
|
newContent?.data.sort((a, b) => {
|
||||||
|
if (
|
||||||
|
a.attributes?.range[0]?.__typename === "ComponentRangePageRange" &&
|
||||||
|
b.attributes?.range[0]?.__typename === "ComponentRangePageRange"
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
a.attributes.range[0].starting_page -
|
||||||
|
b.attributes.range[0].starting_page
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
return newContent as Immutable<SortContentProps>;
|
||||||
|
}
|
||||||
|
return contents;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getStatusDescription(
|
||||||
|
status: string,
|
||||||
|
langui: AppStaticProps["langui"]
|
||||||
|
): string | null | undefined {
|
||||||
|
switch (status) {
|
||||||
|
case Enum_Componentsetstextset_Status.Incomplete:
|
||||||
|
return langui.status_incomplete;
|
||||||
|
|
||||||
|
case Enum_Componentsetstextset_Status.Draft:
|
||||||
|
return langui.status_draft;
|
||||||
|
|
||||||
|
case Enum_Componentsetstextset_Status.Review:
|
||||||
|
return langui.status_review;
|
||||||
|
|
||||||
|
case Enum_Componentsetstextset_Status.Done:
|
||||||
|
return langui.status_done;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function arrayMove<T>(arr: T[], old_index: number, new_index: number) {
|
||||||
|
arr.splice(new_index, 0, arr.splice(old_index, 1)[0]);
|
||||||
|
return arr;
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { GetContentTextQuery, GetPostQuery } from "graphql/generated";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
type Post = NonNullable<
|
||||||
|
NonNullable<GetPostQuery["posts"]>["data"][number]["attributes"]
|
||||||
|
>;
|
||||||
|
|
||||||
|
export interface PostWithTranslations extends Omit<Post, "translations"> {
|
||||||
|
translations: NonNullable<Post["translations"]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
type Content = NonNullable<
|
||||||
|
NonNullable<GetContentTextQuery["contents"]>["data"][number]["attributes"]
|
||||||
|
>;
|
||||||
|
|
||||||
|
export interface ContentWithTranslations extends Omit<Content, "translations"> {
|
||||||
|
translations: NonNullable<Content["translations"]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
type ImmutableBlackList<T> = JSX.Element | React.ReactNode | Function;
|
||||||
|
|
||||||
|
export type Immutable<T> = {
|
||||||
|
readonly [K in keyof T]: T[K] extends ImmutableBlackList<T>
|
||||||
|
? T[K]
|
||||||
|
: Immutable<T[K]>;
|
||||||
|
};
|
|
@ -0,0 +1,7 @@
|
||||||
|
export function getVideoThumbnailURL(uid: string): string {
|
||||||
|
return `${process.env.NEXT_PUBLIC_URL_WATCH}/videos/${uid}.webp`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getVideoFile(uid: string): string {
|
||||||
|
return `${process.env.NEXT_PUBLIC_URL_WATCH}/videos/${uid}.mp4`;
|
||||||
|
}
|
|
@ -1,8 +1,8 @@
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { usePrefersDarkMode } from "./useMediaQuery";
|
import { usePrefersDarkMode } from "./useMediaQuery";
|
||||||
import useStateWithLocalStorage from "./useStateWithLocalStorage";
|
import { useStateWithLocalStorage } from "./useStateWithLocalStorage";
|
||||||
|
|
||||||
export default function useDarkMode(
|
export function useDarkMode(
|
||||||
key: string,
|
key: string,
|
||||||
initialValue: boolean | undefined
|
initialValue: boolean | undefined
|
||||||
): [
|
): [
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
import { LightBox } from "components/LightBox";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
|
export function useLightBox(): [
|
||||||
|
(images: string[], index?: number) => void,
|
||||||
|
() => JSX.Element
|
||||||
|
] {
|
||||||
|
const [lightboxOpen, setLightboxOpen] = useState(false);
|
||||||
|
const [lightboxImages, setLightboxImages] = useState([""]);
|
||||||
|
const [lightboxIndex, setLightboxIndex] = useState(0);
|
||||||
|
|
||||||
|
return [
|
||||||
|
(images: string[], index = 0) => {
|
||||||
|
setLightboxOpen(true);
|
||||||
|
setLightboxImages(images);
|
||||||
|
setLightboxIndex(index);
|
||||||
|
},
|
||||||
|
() => (
|
||||||
|
<LightBox
|
||||||
|
state={lightboxOpen}
|
||||||
|
setState={setLightboxOpen}
|
||||||
|
images={lightboxImages}
|
||||||
|
index={lightboxIndex}
|
||||||
|
setIndex={setLightboxIndex}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
export default function useMediaQuery(query: string): boolean {
|
export function useMediaQuery(query: string): boolean {
|
||||||
function getMatches(query: string): boolean {
|
function getMatches(query: string): boolean {
|
||||||
// Prevents SSR issues
|
// Prevents SSR issues
|
||||||
if (typeof window !== "undefined") {
|
if (typeof window !== "undefined") {
|
||||||
|
|
|
@ -1,20 +1,32 @@
|
||||||
import LanguageSwitcher from "components/Inputs/LanguageSwitcher";
|
import { LanguageSwitcher } from "components/Inputs/LanguageSwitcher";
|
||||||
import { useAppLayout } from "contexts/AppLayoutContext";
|
import { useAppLayout } from "contexts/AppLayoutContext";
|
||||||
|
import { AppStaticProps } from "graphql/getAppStaticProps";
|
||||||
|
import { Immutable } from "helpers/types";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { AppStaticProps } from "queries/getAppStaticProps";
|
|
||||||
import { getPreferredLanguage } from "queries/helpers";
|
|
||||||
import { useEffect, useMemo, useState } from "react";
|
import { useEffect, useMemo, useState } from "react";
|
||||||
|
|
||||||
interface Props<T> {
|
interface Props<T> {
|
||||||
items: T[];
|
items: Immutable<T[]>;
|
||||||
languages: AppStaticProps["languages"];
|
languages: AppStaticProps["languages"];
|
||||||
languageExtractor: (item: T) => string | undefined;
|
languageExtractor: (item: NonNullable<Immutable<T>>) => string | undefined;
|
||||||
transform?: (item: T) => T;
|
transform?: (item: NonNullable<Immutable<T>>) => NonNullable<Immutable<T>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function useSmartLanguage<T>(
|
function getPreferredLanguage(
|
||||||
|
preferredLanguages: (string | undefined)[],
|
||||||
|
availableLanguages: Map<string, number>
|
||||||
|
): number | undefined {
|
||||||
|
for (const locale of preferredLanguages) {
|
||||||
|
if (locale && availableLanguages.has(locale)) {
|
||||||
|
return availableLanguages.get(locale);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useSmartLanguage<T>(
|
||||||
props: Props<T>
|
props: Props<T>
|
||||||
): [T | undefined, () => JSX.Element] {
|
): [Immutable<T | undefined>, () => JSX.Element] {
|
||||||
const {
|
const {
|
||||||
items,
|
items,
|
||||||
languageExtractor,
|
languageExtractor,
|
||||||
|
@ -28,12 +40,15 @@ export default function useSmartLanguage<T>(
|
||||||
const [selectedTranslationIndex, setSelectedTranslationIndex] = useState<
|
const [selectedTranslationIndex, setSelectedTranslationIndex] = useState<
|
||||||
number | undefined
|
number | undefined
|
||||||
>();
|
>();
|
||||||
const [selectedTranslation, setSelectedTranslation] = useState<T>();
|
const [selectedTranslation, setSelectedTranslation] =
|
||||||
|
useState<Immutable<T>>();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
items.map((elem, index) => {
|
items.map((elem, index) => {
|
||||||
const result = languageExtractor(elem);
|
if (elem !== null && elem !== undefined) {
|
||||||
if (result !== undefined) availableLocales.set(result, index);
|
const result = languageExtractor(elem);
|
||||||
|
if (result !== undefined) availableLocales.set(result, index);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}, [availableLocales, items, languageExtractor]);
|
}, [availableLocales, items, languageExtractor]);
|
||||||
|
|
||||||
|
@ -47,8 +62,9 @@ export default function useSmartLanguage<T>(
|
||||||
}, [appLayout.preferredLanguages, availableLocales, router.locale]);
|
}, [appLayout.preferredLanguages, availableLocales, router.locale]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (selectedTranslationIndex !== undefined)
|
if (selectedTranslationIndex !== undefined) {
|
||||||
setSelectedTranslation(transform(items[selectedTranslationIndex]));
|
setSelectedTranslation(transform(items[selectedTranslationIndex]));
|
||||||
|
}
|
||||||
}, [items, selectedTranslationIndex, transform]);
|
}, [items, selectedTranslationIndex, transform]);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
export default function useStateWithLocalStorage<T>(
|
export function useStateWithLocalStorage<T>(
|
||||||
key: string,
|
key: string,
|
||||||
initialValue: T
|
initialValue: T
|
||||||
): [T | undefined, React.Dispatch<React.SetStateAction<T | undefined>>] {
|
): [T | undefined, React.Dispatch<React.SetStateAction<T | undefined>>] {
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
import AppLayout from "components/AppLayout";
|
import { AppLayout } from "components/AppLayout";
|
||||||
import ReturnButton, {
|
import {
|
||||||
|
ReturnButton,
|
||||||
ReturnButtonType,
|
ReturnButtonType,
|
||||||
} from "components/PanelComponents/ReturnButton";
|
} from "components/PanelComponents/ReturnButton";
|
||||||
import ContentPanel from "components/Panels/ContentPanel";
|
import { ContentPanel } from "components/Panels/ContentPanel";
|
||||||
|
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
|
||||||
|
import { Immutable } from "helpers/types";
|
||||||
import { GetStaticPropsContext } from "next";
|
import { GetStaticPropsContext } from "next";
|
||||||
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
|
||||||
|
|
||||||
interface Props extends AppStaticProps {}
|
interface Props extends AppStaticProps {}
|
||||||
|
|
||||||
export default function FourOhFour(props: Props): JSX.Element {
|
export default function FourOhFour(props: Immutable<Props>): JSX.Element {
|
||||||
const { langui } = props;
|
const { langui } = props;
|
||||||
const contentPanel = (
|
const contentPanel = (
|
||||||
<ContentPanel>
|
<ContentPanel>
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
import AppLayout from "components/AppLayout";
|
import { AppLayout } from "components/AppLayout";
|
||||||
import ReturnButton, {
|
import {
|
||||||
|
ReturnButton,
|
||||||
ReturnButtonType,
|
ReturnButtonType,
|
||||||
} from "components/PanelComponents/ReturnButton";
|
} from "components/PanelComponents/ReturnButton";
|
||||||
import ContentPanel from "components/Panels/ContentPanel";
|
import { ContentPanel } from "components/Panels/ContentPanel";
|
||||||
|
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
|
||||||
|
import { Immutable } from "helpers/types";
|
||||||
import { GetStaticPropsContext } from "next";
|
import { GetStaticPropsContext } from "next";
|
||||||
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
|
||||||
|
|
||||||
interface Props extends AppStaticProps {}
|
interface Props extends AppStaticProps {}
|
||||||
|
|
||||||
export default function FiveHundred(props: Props): JSX.Element {
|
export default function FiveHundred(props: Immutable<Props>): JSX.Element {
|
||||||
const { langui } = props;
|
const { langui } = props;
|
||||||
const contentPanel = (
|
const contentPanel = (
|
||||||
<ContentPanel>
|
<ContentPanel>
|
||||||
|
|
|
@ -6,7 +6,7 @@ import Document, {
|
||||||
NextScript,
|
NextScript,
|
||||||
} from "next/document";
|
} from "next/document";
|
||||||
|
|
||||||
class MyDocument extends Document {
|
export default class MyDocument extends Document {
|
||||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||||
static async getInitialProps(ctx: DocumentContext) {
|
static async getInitialProps(ctx: DocumentContext) {
|
||||||
const initialProps = await Document.getInitialProps(ctx);
|
const initialProps = await Document.getInitialProps(ctx);
|
||||||
|
@ -65,5 +65,3 @@ class MyDocument extends Document {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default MyDocument;
|
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import PostPage, { Post } from "components/PostPage";
|
import { PostPage } from "components/PostPage";
|
||||||
import { getReadySdk } from "graphql/sdk";
|
import {
|
||||||
import { GetStaticPropsContext } from "next";
|
getPostStaticProps,
|
||||||
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
PostStaticProps,
|
||||||
|
} from "graphql/getPostStaticProps";
|
||||||
|
import { Immutable } from "helpers/types";
|
||||||
|
|
||||||
interface Props extends AppStaticProps {
|
export default function AccordsHandbook(
|
||||||
post: Post;
|
props: Immutable<PostStaticProps>
|
||||||
}
|
): JSX.Element {
|
||||||
|
|
||||||
export default function AccordsHandbook(props: Props): JSX.Element {
|
|
||||||
const { post, langui, languages, currencies } = props;
|
const { post, langui, languages, currencies } = props;
|
||||||
return (
|
return (
|
||||||
<PostPage
|
<PostPage
|
||||||
|
@ -23,21 +23,4 @@ export default function AccordsHandbook(props: Props): JSX.Element {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getStaticProps(
|
export const getStaticProps = getPostStaticProps("accords-handbook");
|
||||||
context: GetStaticPropsContext
|
|
||||||
): Promise<{ notFound: boolean } | { props: Props }> {
|
|
||||||
const sdk = getReadySdk();
|
|
||||||
const slug = "accords-handbook";
|
|
||||||
const post = await sdk.getPost({
|
|
||||||
slug: slug,
|
|
||||||
language_code: context.locale ?? "en",
|
|
||||||
});
|
|
||||||
if (!post.posts?.data[0].attributes) return { notFound: true };
|
|
||||||
const props: Props = {
|
|
||||||
...(await getAppStaticProps(context)),
|
|
||||||
post: post.posts.data[0].attributes,
|
|
||||||
};
|
|
||||||
return {
|
|
||||||
props: props,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
import InsetBox from "components/InsetBox";
|
import { InsetBox } from "components/InsetBox";
|
||||||
import PostPage, { Post } from "components/PostPage";
|
import { PostPage } from "components/PostPage";
|
||||||
import { getReadySdk } from "graphql/sdk";
|
import {
|
||||||
import { GetStaticPropsContext } from "next";
|
getPostStaticProps,
|
||||||
|
PostStaticProps,
|
||||||
|
} from "graphql/getPostStaticProps";
|
||||||
|
import { randomInt } from "helpers/numbers";
|
||||||
|
import { Immutable } from "helpers/types";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { RequestMailProps, ResponseMailProps } from "pages/api/mail";
|
import { RequestMailProps, ResponseMailProps } from "pages/api/mail";
|
||||||
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
|
||||||
import { randomInt } from "queries/helpers";
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
interface Props extends AppStaticProps {
|
export default function AboutUs(
|
||||||
post: Post;
|
props: Immutable<PostStaticProps>
|
||||||
}
|
): JSX.Element {
|
||||||
|
|
||||||
export default function AboutUs(props: Props): JSX.Element {
|
|
||||||
const { post, langui, languages, currencies } = props;
|
const { post, langui, languages, currencies } = props;
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
@ -181,21 +181,4 @@ export default function AboutUs(props: Props): JSX.Element {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getStaticProps(
|
export const getStaticProps = getPostStaticProps("contact");
|
||||||
context: GetStaticPropsContext
|
|
||||||
): Promise<{ notFound: boolean } | { props: Props }> {
|
|
||||||
const sdk = getReadySdk();
|
|
||||||
const slug = "contact";
|
|
||||||
const post = await sdk.getPost({
|
|
||||||
slug: slug,
|
|
||||||
language_code: context.locale ?? "en",
|
|
||||||
});
|
|
||||||
if (!post.posts?.data[0].attributes) return { notFound: true };
|
|
||||||
const props: Props = {
|
|
||||||
...(await getAppStaticProps(context)),
|
|
||||||
post: post.posts.data[0].attributes,
|
|
||||||
};
|
|
||||||
return {
|
|
||||||
props: props,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
import AppLayout from "components/AppLayout";
|
import { AppLayout } from "components/AppLayout";
|
||||||
import NavOption from "components/PanelComponents/NavOption";
|
import { NavOption } from "components/PanelComponents/NavOption";
|
||||||
import PanelHeader from "components/PanelComponents/PanelHeader";
|
import { PanelHeader } from "components/PanelComponents/PanelHeader";
|
||||||
import SubPanel from "components/Panels/SubPanel";
|
import { SubPanel } from "components/Panels/SubPanel";
|
||||||
|
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
|
||||||
|
import { Immutable } from "helpers/types";
|
||||||
import { GetStaticPropsContext } from "next";
|
import { GetStaticPropsContext } from "next";
|
||||||
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
|
||||||
|
|
||||||
interface Props extends AppStaticProps {}
|
interface Props extends AppStaticProps {}
|
||||||
|
|
||||||
export default function AboutUs(props: Props): JSX.Element {
|
export default function AboutUs(props: Immutable<Props>): JSX.Element {
|
||||||
const { langui } = props;
|
const { langui } = props;
|
||||||
const subPanel = (
|
const subPanel = (
|
||||||
<SubPanel>
|
<SubPanel>
|
||||||
|
|
|
@ -1,13 +1,10 @@
|
||||||
import PostPage, { Post } from "components/PostPage";
|
import { PostPage } from "components/PostPage";
|
||||||
import { getReadySdk } from "graphql/sdk";
|
import {
|
||||||
import { GetStaticPropsContext } from "next";
|
getPostStaticProps,
|
||||||
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
PostStaticProps,
|
||||||
|
} from "graphql/getPostStaticProps";
|
||||||
|
|
||||||
interface Props extends AppStaticProps {
|
export default function Legality(props: PostStaticProps): JSX.Element {
|
||||||
post: Post;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function SiteInformation(props: Props): JSX.Element {
|
|
||||||
const { post, langui, languages, currencies } = props;
|
const { post, langui, languages, currencies } = props;
|
||||||
return (
|
return (
|
||||||
<PostPage
|
<PostPage
|
||||||
|
@ -23,21 +20,4 @@ export default function SiteInformation(props: Props): JSX.Element {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getStaticProps(
|
export const getStaticProps = getPostStaticProps("legality");
|
||||||
context: GetStaticPropsContext
|
|
||||||
): Promise<{ notFound: boolean } | { props: Props }> {
|
|
||||||
const sdk = getReadySdk();
|
|
||||||
const slug = "legality";
|
|
||||||
const post = await sdk.getPost({
|
|
||||||
slug: slug,
|
|
||||||
language_code: context.locale ?? "en",
|
|
||||||
});
|
|
||||||
if (!post.posts?.data[0].attributes) return { notFound: true };
|
|
||||||
const props: Props = {
|
|
||||||
...(await getAppStaticProps(context)),
|
|
||||||
post: post.posts.data[0].attributes,
|
|
||||||
};
|
|
||||||
return {
|
|
||||||
props: props,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
import PostPage, { Post } from "components/PostPage";
|
import { PostPage } from "components/PostPage";
|
||||||
import { getReadySdk } from "graphql/sdk";
|
import {
|
||||||
import { GetStaticPropsContext } from "next";
|
getPostStaticProps,
|
||||||
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
PostStaticProps,
|
||||||
|
} from "graphql/getPostStaticProps";
|
||||||
|
|
||||||
interface Props extends AppStaticProps {
|
export default function SharingPolicy(props: PostStaticProps): JSX.Element {
|
||||||
post: Post;
|
|
||||||
}
|
|
||||||
export default function SharingPolicy(props: Props): JSX.Element {
|
|
||||||
const { post, langui, languages, currencies } = props;
|
const { post, langui, languages, currencies } = props;
|
||||||
return (
|
return (
|
||||||
<PostPage
|
<PostPage
|
||||||
|
@ -22,21 +20,4 @@ export default function SharingPolicy(props: Props): JSX.Element {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getStaticProps(
|
export const getStaticProps = getPostStaticProps("sharing-policy");
|
||||||
context: GetStaticPropsContext
|
|
||||||
): Promise<{ notFound: boolean } | { props: Props }> {
|
|
||||||
const sdk = getReadySdk();
|
|
||||||
const slug = "sharing-policy";
|
|
||||||
const post = await sdk.getPost({
|
|
||||||
slug: slug,
|
|
||||||
language_code: context.locale ?? "en",
|
|
||||||
});
|
|
||||||
if (!post.posts?.data[0].attributes) return { notFound: true };
|
|
||||||
const props: Props = {
|
|
||||||
...(await getAppStaticProps(context)),
|
|
||||||
post: post.posts.data[0].attributes,
|
|
||||||
};
|
|
||||||
return {
|
|
||||||
props: props,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ export interface RequestMailProps {
|
||||||
formName: string;
|
formName: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function Mail(
|
export async function Mail(
|
||||||
req: NextApiRequest,
|
req: NextApiRequest,
|
||||||
res: NextApiResponse<ResponseMailProps>
|
res: NextApiResponse<ResponseMailProps>
|
||||||
) {
|
) {
|
||||||
|
|
|
@ -70,7 +70,7 @@ type ResponseMailProps = {
|
||||||
revalidated: boolean;
|
revalidated: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default async function Mail(
|
export async function Mail(
|
||||||
req: NextApiRequest,
|
req: NextApiRequest,
|
||||||
res: NextApiResponse<ResponseMailProps>
|
res: NextApiResponse<ResponseMailProps>
|
||||||
) {
|
) {
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
import AppLayout from "components/AppLayout";
|
import { AppLayout } from "components/AppLayout";
|
||||||
import NavOption from "components/PanelComponents/NavOption";
|
import { NavOption } from "components/PanelComponents/NavOption";
|
||||||
import PanelHeader from "components/PanelComponents/PanelHeader";
|
import { PanelHeader } from "components/PanelComponents/PanelHeader";
|
||||||
import SubPanel from "components/Panels/SubPanel";
|
import { SubPanel } from "components/Panels/SubPanel";
|
||||||
|
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
|
||||||
|
import { Immutable } from "helpers/types";
|
||||||
import { GetStaticPropsContext } from "next";
|
import { GetStaticPropsContext } from "next";
|
||||||
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
|
||||||
|
|
||||||
interface Props extends AppStaticProps {}
|
interface Props extends AppStaticProps {}
|
||||||
|
|
||||||
export default function Archives(props: Props): JSX.Element {
|
export default function Archives(props: Immutable<Props>): JSX.Element {
|
||||||
const { langui } = props;
|
const { langui } = props;
|
||||||
const subPanel = (
|
const subPanel = (
|
||||||
<SubPanel>
|
<SubPanel>
|
||||||
|
|
|
@ -1,29 +1,30 @@
|
||||||
import AppLayout from "components/AppLayout";
|
import { AppLayout } from "components/AppLayout";
|
||||||
import Switch from "components/Inputs/Switch";
|
import { Switch } from "components/Inputs/Switch";
|
||||||
import PanelHeader from "components/PanelComponents/PanelHeader";
|
import { PanelHeader } from "components/PanelComponents/PanelHeader";
|
||||||
import ReturnButton, {
|
import {
|
||||||
|
ReturnButton,
|
||||||
ReturnButtonType,
|
ReturnButtonType,
|
||||||
} from "components/PanelComponents/ReturnButton";
|
} from "components/PanelComponents/ReturnButton";
|
||||||
import ContentPanel, {
|
import {
|
||||||
|
ContentPanel,
|
||||||
ContentPanelWidthSizes,
|
ContentPanelWidthSizes,
|
||||||
} from "components/Panels/ContentPanel";
|
} from "components/Panels/ContentPanel";
|
||||||
import SubPanel from "components/Panels/SubPanel";
|
import { SubPanel } from "components/Panels/SubPanel";
|
||||||
import ThumbnailPreview from "components/PreviewCard";
|
import { PreviewCard } from "components/PreviewCard";
|
||||||
import { GetVideoChannelQuery } from "graphql/generated";
|
import { GetVideoChannelQuery } from "graphql/generated";
|
||||||
|
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
|
||||||
import { getReadySdk } from "graphql/sdk";
|
import { getReadySdk } from "graphql/sdk";
|
||||||
|
import { getVideoThumbnailURL } from "helpers/videos";
|
||||||
import {
|
import {
|
||||||
GetStaticPathsContext,
|
GetStaticPathsContext,
|
||||||
GetStaticPathsResult,
|
GetStaticPathsResult,
|
||||||
GetStaticPropsContext,
|
GetStaticPropsContext,
|
||||||
} from "next";
|
} from "next";
|
||||||
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
|
||||||
import { getVideoThumbnailURL } from "queries/helpers";
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
interface Props extends AppStaticProps {
|
interface Props extends AppStaticProps {
|
||||||
channel: Exclude<
|
channel: NonNullable<
|
||||||
GetVideoChannelQuery["videoChannels"],
|
GetVideoChannelQuery["videoChannels"]
|
||||||
null | undefined
|
|
||||||
>["data"][number]["attributes"];
|
>["data"][number]["attributes"];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,7 +36,7 @@ export default function Channel(props: Props): JSX.Element {
|
||||||
<SubPanel>
|
<SubPanel>
|
||||||
<ReturnButton
|
<ReturnButton
|
||||||
href="/archives/videos/"
|
href="/archives/videos/"
|
||||||
title={"Videos"}
|
title={langui.videos}
|
||||||
langui={langui}
|
langui={langui}
|
||||||
displayOn={ReturnButtonType.desktop}
|
displayOn={ReturnButtonType.desktop}
|
||||||
className="mb-10"
|
className="mb-10"
|
||||||
|
@ -43,12 +44,12 @@ export default function Channel(props: Props): JSX.Element {
|
||||||
|
|
||||||
<PanelHeader
|
<PanelHeader
|
||||||
icon="movie"
|
icon="movie"
|
||||||
title="Videos"
|
title={langui.videos}
|
||||||
description={langui.archives_description}
|
description={langui.archives_description}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="flex flex-row gap-2 place-items-center coarse:hidden">
|
<div className="flex flex-row gap-2 place-items-center coarse:hidden">
|
||||||
<p className="flex-shrink-0">{"Always show info"}:</p>
|
<p className="flex-shrink-0">{langui.always_show_info}:</p>
|
||||||
<Switch setState={setKeepInfoVisible} state={keepInfoVisible} />
|
<Switch setState={setKeepInfoVisible} state={keepInfoVisible} />
|
||||||
</div>
|
</div>
|
||||||
</SubPanel>
|
</SubPanel>
|
||||||
|
@ -60,11 +61,15 @@ export default function Channel(props: Props): JSX.Element {
|
||||||
<h1 className="text-3xl">{channel?.title}</h1>
|
<h1 className="text-3xl">{channel?.title}</h1>
|
||||||
<p>{channel?.subscribers.toLocaleString()} subscribers</p>
|
<p>{channel?.subscribers.toLocaleString()} subscribers</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid gap-8 items-start mobile:grid-cols-2 desktop:grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))] pb-12 border-b-[3px] border-dotted last-of-type:border-0">
|
<div
|
||||||
|
className="grid gap-8 items-start mobile:grid-cols-2
|
||||||
|
desktop:grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))]
|
||||||
|
pb-12 border-b-[3px] border-dotted last-of-type:border-0"
|
||||||
|
>
|
||||||
{channel?.videos?.data.map((video) => (
|
{channel?.videos?.data.map((video) => (
|
||||||
<>
|
<>
|
||||||
{video.attributes && (
|
{video.attributes && (
|
||||||
<ThumbnailPreview
|
<PreviewCard
|
||||||
key={video.id}
|
key={video.id}
|
||||||
href={`/archives/videos/v/${video.attributes.uid}`}
|
href={`/archives/videos/v/${video.attributes.uid}`}
|
||||||
title={video.attributes.title}
|
title={video.attributes.title}
|
||||||
|
|
|
@ -1,24 +1,27 @@
|
||||||
import AppLayout from "components/AppLayout";
|
import { AppLayout } from "components/AppLayout";
|
||||||
import PageSelector from "components/Inputs/PageSelector";
|
import { PageSelector } from "components/Inputs/PageSelector";
|
||||||
import Switch from "components/Inputs/Switch";
|
import { Switch } from "components/Inputs/Switch";
|
||||||
import PanelHeader from "components/PanelComponents/PanelHeader";
|
import { PanelHeader } from "components/PanelComponents/PanelHeader";
|
||||||
import ReturnButton, {
|
import {
|
||||||
|
ReturnButton,
|
||||||
ReturnButtonType,
|
ReturnButtonType,
|
||||||
} from "components/PanelComponents/ReturnButton";
|
} from "components/PanelComponents/ReturnButton";
|
||||||
import ContentPanel, {
|
import {
|
||||||
|
ContentPanel,
|
||||||
ContentPanelWidthSizes,
|
ContentPanelWidthSizes,
|
||||||
} from "components/Panels/ContentPanel";
|
} from "components/Panels/ContentPanel";
|
||||||
import SubPanel from "components/Panels/SubPanel";
|
import { SubPanel } from "components/Panels/SubPanel";
|
||||||
import ThumbnailPreview from "components/PreviewCard";
|
import { PreviewCard } from "components/PreviewCard";
|
||||||
import { GetVideosPreviewQuery } from "graphql/generated";
|
import { GetVideosPreviewQuery } from "graphql/generated";
|
||||||
|
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
|
||||||
import { getReadySdk } from "graphql/sdk";
|
import { getReadySdk } from "graphql/sdk";
|
||||||
|
import { prettyDate } from "helpers/formatters";
|
||||||
|
import { getVideoThumbnailURL } from "helpers/videos";
|
||||||
import { GetStaticPropsContext } from "next";
|
import { GetStaticPropsContext } from "next";
|
||||||
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
|
||||||
import { getVideoThumbnailURL, prettyDate } from "queries/helpers";
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
interface Props extends AppStaticProps {
|
interface Props extends AppStaticProps {
|
||||||
videos: Exclude<GetVideosPreviewQuery["videos"], null | undefined>["data"];
|
videos: NonNullable<GetVideosPreviewQuery["videos"]>["data"];
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Videos(props: Props): JSX.Element {
|
export default function Videos(props: Props): JSX.Element {
|
||||||
|
@ -64,7 +67,7 @@ export default function Videos(props: Props): JSX.Element {
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="flex flex-row gap-2 place-items-center coarse:hidden">
|
<div className="flex flex-row gap-2 place-items-center coarse:hidden">
|
||||||
<p className="flex-shrink-0">{"Always show info"}:</p>
|
<p className="flex-shrink-0">{langui.always_show_info}:</p>
|
||||||
<Switch setState={setKeepInfoVisible} state={keepInfoVisible} />
|
<Switch setState={setKeepInfoVisible} state={keepInfoVisible} />
|
||||||
</div>
|
</div>
|
||||||
</SubPanel>
|
</SubPanel>
|
||||||
|
@ -79,11 +82,15 @@ export default function Videos(props: Props): JSX.Element {
|
||||||
className="mb-12"
|
className="mb-12"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="grid gap-8 items-start thin:grid-cols-1 mobile:grid-cols-2 desktop:grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))] pb-12 border-b-[3px] border-dotted last-of-type:border-0">
|
<div
|
||||||
|
className="grid gap-8 items-start thin:grid-cols-1 mobile:grid-cols-2
|
||||||
|
desktop:grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))]
|
||||||
|
pb-12 border-b-[3px] border-dotted last-of-type:border-0"
|
||||||
|
>
|
||||||
{paginatedVideos[page].map((video) => (
|
{paginatedVideos[page].map((video) => (
|
||||||
<>
|
<>
|
||||||
{video.attributes && (
|
{video.attributes && (
|
||||||
<ThumbnailPreview
|
<PreviewCard
|
||||||
key={video.id}
|
key={video.id}
|
||||||
href={`/archives/videos/v/${video.attributes.uid}`}
|
href={`/archives/videos/v/${video.attributes.uid}`}
|
||||||
title={video.attributes.title}
|
title={video.attributes.title}
|
||||||
|
|
|
@ -1,34 +1,33 @@
|
||||||
import AppLayout from "components/AppLayout";
|
import { AppLayout } from "components/AppLayout";
|
||||||
import HorizontalLine from "components/HorizontalLine";
|
import { HorizontalLine } from "components/HorizontalLine";
|
||||||
import Button from "components/Inputs/Button";
|
import { Button } from "components/Inputs/Button";
|
||||||
import InsetBox from "components/InsetBox";
|
import { InsetBox } from "components/InsetBox";
|
||||||
import NavOption from "components/PanelComponents/NavOption";
|
import { NavOption } from "components/PanelComponents/NavOption";
|
||||||
import ReturnButton, {
|
import {
|
||||||
|
ReturnButton,
|
||||||
ReturnButtonType,
|
ReturnButtonType,
|
||||||
} from "components/PanelComponents/ReturnButton";
|
} from "components/PanelComponents/ReturnButton";
|
||||||
import ContentPanel, {
|
import {
|
||||||
|
ContentPanel,
|
||||||
ContentPanelWidthSizes,
|
ContentPanelWidthSizes,
|
||||||
} from "components/Panels/ContentPanel";
|
} from "components/Panels/ContentPanel";
|
||||||
import SubPanel from "components/Panels/SubPanel";
|
import { SubPanel } from "components/Panels/SubPanel";
|
||||||
import { useAppLayout } from "contexts/AppLayoutContext";
|
import { useAppLayout } from "contexts/AppLayoutContext";
|
||||||
import { GetVideoQuery } from "graphql/generated";
|
import { GetVideoQuery } from "graphql/generated";
|
||||||
|
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
|
||||||
import { getReadySdk } from "graphql/sdk";
|
import { getReadySdk } from "graphql/sdk";
|
||||||
|
import { prettyDate, prettyShortenNumber } from "helpers/formatters";
|
||||||
|
import { getVideoFile } from "helpers/videos";
|
||||||
import { useMediaMobile } from "hooks/useMediaQuery";
|
import { useMediaMobile } from "hooks/useMediaQuery";
|
||||||
import {
|
import {
|
||||||
GetStaticPathsContext,
|
GetStaticPathsContext,
|
||||||
GetStaticPathsResult,
|
GetStaticPathsResult,
|
||||||
GetStaticPropsContext,
|
GetStaticPropsContext,
|
||||||
} from "next";
|
} from "next";
|
||||||
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
|
||||||
import { getVideoFile, prettyDate, prettyShortenNumber } from "queries/helpers";
|
|
||||||
|
|
||||||
interface Props extends AppStaticProps {
|
interface Props extends AppStaticProps {
|
||||||
video: Exclude<
|
video: NonNullable<
|
||||||
Exclude<
|
NonNullable<GetVideoQuery["videos"]>["data"][number]["attributes"]
|
||||||
GetVideoQuery["videos"],
|
|
||||||
null | undefined
|
|
||||||
>["data"][number]["attributes"],
|
|
||||||
null | undefined
|
|
||||||
>;
|
>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,7 +39,7 @@ export default function Video(props: Props): JSX.Element {
|
||||||
<SubPanel>
|
<SubPanel>
|
||||||
<ReturnButton
|
<ReturnButton
|
||||||
href="/archives/videos/"
|
href="/archives/videos/"
|
||||||
title={"Videos"}
|
title={langui.videos}
|
||||||
langui={langui}
|
langui={langui}
|
||||||
displayOn={ReturnButtonType.desktop}
|
displayOn={ReturnButtonType.desktop}
|
||||||
className="mb-10"
|
className="mb-10"
|
||||||
|
@ -56,14 +55,14 @@ export default function Video(props: Props): JSX.Element {
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<NavOption
|
<NavOption
|
||||||
title={"Channel"}
|
title={langui.channel}
|
||||||
url="#channel"
|
url="#channel"
|
||||||
border
|
border
|
||||||
onClick={() => appLayout.setSubPanelOpen(false)}
|
onClick={() => appLayout.setSubPanelOpen(false)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<NavOption
|
<NavOption
|
||||||
title={"Description"}
|
title={langui.description}
|
||||||
url="#description"
|
url="#description"
|
||||||
border
|
border
|
||||||
onClick={() => appLayout.setSubPanelOpen(false)}
|
onClick={() => appLayout.setSubPanelOpen(false)}
|
||||||
|
@ -98,7 +97,8 @@ export default function Video(props: Props): JSX.Element {
|
||||||
className="w-full aspect-video"
|
className="w-full aspect-video"
|
||||||
title="YouTube video player"
|
title="YouTube video player"
|
||||||
frameBorder="0"
|
frameBorder="0"
|
||||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
allow="accelerometer; autoplay; clipboard-write;
|
||||||
|
encrypted-media; gyroscope; picture-in-picture"
|
||||||
allowFullScreen
|
allowFullScreen
|
||||||
></iframe>
|
></iframe>
|
||||||
)}
|
)}
|
||||||
|
@ -135,7 +135,7 @@ export default function Video(props: Props): JSX.Element {
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
>
|
>
|
||||||
<Button className="!py-0 !px-3">{`View on ${video.source}`}</Button>
|
<Button className="!py-0 !px-3">{`${langui.view_on} ${video.source}`}</Button>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -144,7 +144,7 @@ export default function Video(props: Props): JSX.Element {
|
||||||
{video.channel?.data?.attributes && (
|
{video.channel?.data?.attributes && (
|
||||||
<InsetBox id="channel" className="grid place-items-center">
|
<InsetBox id="channel" className="grid place-items-center">
|
||||||
<div className="w-[clamp(0px,100%,42rem)] grid place-items-center gap-4 text-center">
|
<div className="w-[clamp(0px,100%,42rem)] grid place-items-center gap-4 text-center">
|
||||||
<h2 className="text-2xl">{"Channel"}</h2>
|
<h2 className="text-2xl">{langui.channel}</h2>
|
||||||
<div>
|
<div>
|
||||||
<Button
|
<Button
|
||||||
href={`/archives/videos/c/${video.channel.data.attributes.uid}`}
|
href={`/archives/videos/c/${video.channel.data.attributes.uid}`}
|
||||||
|
@ -153,8 +153,7 @@ export default function Video(props: Props): JSX.Element {
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
{video.channel.data.attributes.subscribers.toLocaleString()}{" "}
|
{`${video.channel.data.attributes.subscribers.toLocaleString()} ${langui.subscribers?.toLowerCase()}`}
|
||||||
subscribers
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -163,7 +162,7 @@ export default function Video(props: Props): JSX.Element {
|
||||||
|
|
||||||
<InsetBox id="description" className="grid place-items-center">
|
<InsetBox id="description" className="grid place-items-center">
|
||||||
<div className="w-[clamp(0px,100%,42rem)] grid place-items-center gap-8">
|
<div className="w-[clamp(0px,100%,42rem)] grid place-items-center gap-8">
|
||||||
<h2 className="text-2xl">{"Description"}</h2>
|
<h2 className="text-2xl">{langui.description}</h2>
|
||||||
<p className="whitespace-pre-line">{video.description}</p>
|
<p className="whitespace-pre-line">{video.description}</p>
|
||||||
</div>
|
</div>
|
||||||
</InsetBox>
|
</InsetBox>
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
import AppLayout from "components/AppLayout";
|
import { AppLayout } from "components/AppLayout";
|
||||||
import PanelHeader from "components/PanelComponents/PanelHeader";
|
import { PanelHeader } from "components/PanelComponents/PanelHeader";
|
||||||
import SubPanel from "components/Panels/SubPanel";
|
import { SubPanel } from "components/Panels/SubPanel";
|
||||||
|
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
|
||||||
|
import { Immutable } from "helpers/types";
|
||||||
import { GetStaticPropsContext } from "next";
|
import { GetStaticPropsContext } from "next";
|
||||||
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
|
||||||
|
|
||||||
interface Props extends AppStaticProps {}
|
interface Props extends AppStaticProps {}
|
||||||
|
|
||||||
export default function Chronicles(props: Props): JSX.Element {
|
export default function Chronicles(props: Immutable<Props>): JSX.Element {
|
||||||
const { langui } = props;
|
const { langui } = props;
|
||||||
const subPanel = (
|
const subPanel = (
|
||||||
<SubPanel>
|
<SubPanel>
|
||||||
|
|
|
@ -1,56 +1,60 @@
|
||||||
import AppLayout from "components/AppLayout";
|
import { AppLayout } from "components/AppLayout";
|
||||||
import Chip from "components/Chip";
|
import { Chip } from "components/Chip";
|
||||||
import HorizontalLine from "components/HorizontalLine";
|
import { HorizontalLine } from "components/HorizontalLine";
|
||||||
import Markdawn from "components/Markdown/Markdawn";
|
import { Markdawn } from "components/Markdown/Markdawn";
|
||||||
import TOC from "components/Markdown/TOC";
|
import { TOC } from "components/Markdown/TOC";
|
||||||
import ReturnButton, {
|
import {
|
||||||
|
ReturnButton,
|
||||||
ReturnButtonType,
|
ReturnButtonType,
|
||||||
} from "components/PanelComponents/ReturnButton";
|
} from "components/PanelComponents/ReturnButton";
|
||||||
import ContentPanel from "components/Panels/ContentPanel";
|
import { ContentPanel } from "components/Panels/ContentPanel";
|
||||||
import SubPanel from "components/Panels/SubPanel";
|
import { SubPanel } from "components/Panels/SubPanel";
|
||||||
import PreviewLine from "components/PreviewLine";
|
import { PreviewLine } from "components/PreviewLine";
|
||||||
import RecorderChip from "components/RecorderChip";
|
import { RecorderChip } from "components/RecorderChip";
|
||||||
import ThumbnailHeader from "components/ThumbnailHeader";
|
import { ThumbnailHeader } from "components/ThumbnailHeader";
|
||||||
import ToolTip from "components/ToolTip";
|
import { ToolTip } from "components/ToolTip";
|
||||||
import { GetContentTextQuery } from "graphql/generated";
|
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
|
||||||
import { getReadySdk } from "graphql/sdk";
|
import { getReadySdk } from "graphql/sdk";
|
||||||
|
import { getNextContent, getPreviousContent } from "helpers/contents";
|
||||||
|
import {
|
||||||
|
prettyinlineTitle,
|
||||||
|
prettyLanguage,
|
||||||
|
prettySlug,
|
||||||
|
} from "helpers/formatters";
|
||||||
|
import { getStatusDescription } from "helpers/others";
|
||||||
|
import { ContentWithTranslations, Immutable } from "helpers/types";
|
||||||
import { useMediaMobile } from "hooks/useMediaQuery";
|
import { useMediaMobile } from "hooks/useMediaQuery";
|
||||||
import useSmartLanguage from "hooks/useSmartLanguage";
|
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
||||||
import {
|
import {
|
||||||
GetStaticPathsContext,
|
GetStaticPathsContext,
|
||||||
GetStaticPathsResult,
|
GetStaticPathsResult,
|
||||||
GetStaticPropsContext,
|
GetStaticPropsContext,
|
||||||
} from "next";
|
} from "next";
|
||||||
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
|
||||||
import {
|
|
||||||
getStatusDescription,
|
|
||||||
prettyinlineTitle,
|
|
||||||
prettyLanguage,
|
|
||||||
prettySlug,
|
|
||||||
} from "queries/helpers";
|
|
||||||
|
|
||||||
interface Props extends AppStaticProps {
|
interface Props extends AppStaticProps {
|
||||||
content: Exclude<
|
content: ContentWithTranslations;
|
||||||
GetContentTextQuery["contents"],
|
|
||||||
null | undefined
|
|
||||||
>["data"][number]["attributes"];
|
|
||||||
contentId: Exclude<
|
|
||||||
GetContentTextQuery["contents"],
|
|
||||||
null | undefined
|
|
||||||
>["data"][number]["id"];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Content(props: Props): JSX.Element {
|
export default function Content(props: Immutable<Props>): JSX.Element {
|
||||||
const { langui, content, languages } = props;
|
const { langui, content, languages } = props;
|
||||||
const isMobile = useMediaMobile();
|
const isMobile = useMediaMobile();
|
||||||
|
|
||||||
const [selectedTextSet, LanguageSwitcher] = useSmartLanguage({
|
const [selectedTranslation, LanguageSwitcher] = useSmartLanguage({
|
||||||
items: content?.text_set,
|
items: content.translations,
|
||||||
languages: languages,
|
languages: languages,
|
||||||
languageExtractor: (item) => item?.language?.data?.attributes?.code,
|
languageExtractor: (item) => item.language?.data?.attributes?.code,
|
||||||
});
|
});
|
||||||
|
|
||||||
const selectedTitle = content?.titles?.[0];
|
const previousContent = content.group?.data?.attributes?.contents
|
||||||
|
? getPreviousContent(
|
||||||
|
content.group.data.attributes.contents.data,
|
||||||
|
content.slug
|
||||||
|
)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
const nextContent = content.group?.data?.attributes?.contents
|
||||||
|
? getNextContent(content.group.data.attributes.contents.data, content.slug)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
const subPanel = (
|
const subPanel = (
|
||||||
<SubPanel>
|
<SubPanel>
|
||||||
|
@ -62,124 +66,133 @@ export default function Content(props: Props): JSX.Element {
|
||||||
horizontalLine
|
horizontalLine
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{selectedTextSet?.source_language?.data?.attributes && (
|
{selectedTranslation?.text_set && (
|
||||||
<div className="grid gap-5">
|
<div className="grid gap-5">
|
||||||
<h2 className="text-xl">
|
<h2 className="text-xl">
|
||||||
{selectedTextSet.source_language.data.attributes.code ===
|
{selectedTranslation.text_set.source_language?.data?.attributes
|
||||||
selectedTextSet.language?.data?.attributes?.code
|
?.code === selectedTranslation.language?.data?.attributes?.code
|
||||||
? langui.transcript_notice
|
? langui.transcript_notice
|
||||||
: langui.translation_notice}
|
: langui.translation_notice}
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
{selectedTextSet.source_language.data.attributes.code !==
|
{selectedTranslation.text_set.source_language?.data?.attributes
|
||||||
selectedTextSet.language?.data?.attributes?.code && (
|
?.code &&
|
||||||
<div className="grid place-items-center gap-2">
|
selectedTranslation.text_set.source_language.data.attributes
|
||||||
<p className="font-headers">{langui.source_language}:</p>
|
.code !==
|
||||||
<Chip>
|
selectedTranslation.language?.data?.attributes?.code && (
|
||||||
{prettyLanguage(
|
<div className="grid place-items-center gap-2">
|
||||||
selectedTextSet.source_language.data.attributes.code,
|
<p className="font-headers">{langui.source_language}:</p>
|
||||||
languages
|
<Chip>
|
||||||
)}
|
{prettyLanguage(
|
||||||
</Chip>
|
selectedTranslation.text_set.source_language.data.attributes
|
||||||
</div>
|
.code,
|
||||||
)}
|
languages
|
||||||
|
)}
|
||||||
|
</Chip>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="grid grid-flow-col place-items-center place-content-center gap-2">
|
<div className="grid grid-flow-col place-items-center place-content-center gap-2">
|
||||||
<p className="font-headers">{langui.status}:</p>
|
<p className="font-headers">{langui.status}:</p>
|
||||||
|
|
||||||
<ToolTip
|
<ToolTip
|
||||||
content={getStatusDescription(selectedTextSet.status, langui)}
|
content={getStatusDescription(
|
||||||
|
selectedTranslation.text_set.status,
|
||||||
|
langui
|
||||||
|
)}
|
||||||
maxWidth={"20rem"}
|
maxWidth={"20rem"}
|
||||||
>
|
>
|
||||||
<Chip>{selectedTextSet.status}</Chip>
|
<Chip>{selectedTranslation.text_set.status}</Chip>
|
||||||
</ToolTip>
|
</ToolTip>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{selectedTextSet.transcribers &&
|
{selectedTranslation.text_set.transcribers &&
|
||||||
selectedTextSet.transcribers.data.length > 0 && (
|
selectedTranslation.text_set.transcribers.data.length > 0 && (
|
||||||
<div>
|
<div>
|
||||||
<p className="font-headers">{langui.transcribers}:</p>
|
<p className="font-headers">{langui.transcribers}:</p>
|
||||||
<div className="grid place-items-center place-content-center gap-2">
|
<div className="grid place-items-center place-content-center gap-2">
|
||||||
{selectedTextSet.transcribers.data.map((recorder) => (
|
{selectedTranslation.text_set.transcribers.data.map(
|
||||||
<>
|
(recorder) => (
|
||||||
{recorder.attributes && (
|
<>
|
||||||
<RecorderChip
|
{recorder.attributes && (
|
||||||
key={recorder.id}
|
<RecorderChip
|
||||||
langui={langui}
|
key={recorder.id}
|
||||||
recorder={recorder.attributes}
|
langui={langui}
|
||||||
/>
|
recorder={recorder.attributes}
|
||||||
)}
|
/>
|
||||||
</>
|
)}
|
||||||
))}
|
</>
|
||||||
|
)
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{selectedTextSet.translators &&
|
{selectedTranslation.text_set.translators &&
|
||||||
selectedTextSet.translators.data.length > 0 && (
|
selectedTranslation.text_set.translators.data.length > 0 && (
|
||||||
<div>
|
<div>
|
||||||
<p className="font-headers">{langui.translators}:</p>
|
<p className="font-headers">{langui.translators}:</p>
|
||||||
<div className="grid place-items-center place-content-center gap-2">
|
<div className="grid place-items-center place-content-center gap-2">
|
||||||
{selectedTextSet.translators.data.map((recorder) => (
|
{selectedTranslation.text_set.translators.data.map(
|
||||||
<>
|
(recorder) => (
|
||||||
{recorder.attributes && (
|
<>
|
||||||
<RecorderChip
|
{recorder.attributes && (
|
||||||
key={recorder.id}
|
<RecorderChip
|
||||||
langui={langui}
|
key={recorder.id}
|
||||||
recorder={recorder.attributes}
|
langui={langui}
|
||||||
/>
|
recorder={recorder.attributes}
|
||||||
)}
|
/>
|
||||||
</>
|
)}
|
||||||
))}
|
</>
|
||||||
|
)
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{selectedTextSet.proofreaders &&
|
{selectedTranslation.text_set.proofreaders &&
|
||||||
selectedTextSet.proofreaders.data.length > 0 && (
|
selectedTranslation.text_set.proofreaders.data.length > 0 && (
|
||||||
<div>
|
<div>
|
||||||
<p className="font-headers">{langui.proofreaders}:</p>
|
<p className="font-headers">{langui.proofreaders}:</p>
|
||||||
<div className="grid place-items-center place-content-center gap-2">
|
<div className="grid place-items-center place-content-center gap-2">
|
||||||
{selectedTextSet.proofreaders.data.map((recorder) => (
|
{selectedTranslation.text_set.proofreaders.data.map(
|
||||||
<>
|
(recorder) => (
|
||||||
{recorder.attributes && (
|
<>
|
||||||
<RecorderChip
|
{recorder.attributes && (
|
||||||
key={recorder.id}
|
<RecorderChip
|
||||||
langui={langui}
|
key={recorder.id}
|
||||||
recorder={recorder.attributes}
|
langui={langui}
|
||||||
/>
|
recorder={recorder.attributes}
|
||||||
)}
|
/>
|
||||||
</>
|
)}
|
||||||
))}
|
</>
|
||||||
|
)
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{selectedTextSet.notes && (
|
{selectedTranslation.text_set.notes && (
|
||||||
<div>
|
<div>
|
||||||
<p className="font-headers">{"Notes"}:</p>
|
<p className="font-headers">{"Notes"}:</p>
|
||||||
<div className="grid place-items-center place-content-center gap-2">
|
<div className="grid place-items-center place-content-center gap-2">
|
||||||
<Markdawn text={selectedTextSet.notes} />
|
<Markdawn text={selectedTranslation.text_set.notes} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{selectedTextSet && content?.text_set && selectedTextSet.text && (
|
{selectedTranslation?.text_set?.text && (
|
||||||
<>
|
<>
|
||||||
<HorizontalLine />
|
<HorizontalLine />
|
||||||
<TOC
|
<TOC
|
||||||
text={selectedTextSet.text}
|
text={selectedTranslation.text_set.text}
|
||||||
title={
|
title={prettyinlineTitle(
|
||||||
content.titles && content.titles.length > 0 && selectedTitle
|
selectedTranslation.pre_title,
|
||||||
? prettyinlineTitle(
|
selectedTranslation.title,
|
||||||
selectedTitle.pre_title,
|
selectedTranslation.subtitle
|
||||||
selectedTitle.title,
|
)}
|
||||||
selectedTitle.subtitle
|
|
||||||
)
|
|
||||||
: prettySlug(content.slug)
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
@ -188,142 +201,119 @@ export default function Content(props: Props): JSX.Element {
|
||||||
const contentPanel = (
|
const contentPanel = (
|
||||||
<ContentPanel>
|
<ContentPanel>
|
||||||
<ReturnButton
|
<ReturnButton
|
||||||
href={`/contents/${content?.slug}`}
|
href={`/contents/${content.slug}`}
|
||||||
title={langui.content}
|
title={langui.content}
|
||||||
langui={langui}
|
langui={langui}
|
||||||
displayOn={ReturnButtonType.mobile}
|
displayOn={ReturnButtonType.mobile}
|
||||||
className="mb-10"
|
className="mb-10"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{content && (
|
<div className="grid place-items-center">
|
||||||
<div className="grid place-items-center">
|
<ThumbnailHeader
|
||||||
<ThumbnailHeader
|
thumbnail={content.thumbnail?.data?.attributes}
|
||||||
thumbnail={content.thumbnail?.data?.attributes}
|
pre_title={selectedTranslation?.pre_title}
|
||||||
pre_title={
|
title={selectedTranslation?.title}
|
||||||
selectedTitle?.pre_title ?? content.titles?.[0]?.pre_title
|
subtitle={selectedTranslation?.subtitle}
|
||||||
}
|
description={selectedTranslation?.description}
|
||||||
title={selectedTitle?.title ?? content.titles?.[0]?.title}
|
type={content.type}
|
||||||
subtitle={selectedTitle?.subtitle ?? content.titles?.[0]?.subtitle}
|
categories={content.categories}
|
||||||
description={
|
langui={langui}
|
||||||
selectedTitle?.description ?? content.titles?.[0]?.description
|
languageSwitcher={<LanguageSwitcher />}
|
||||||
}
|
/>
|
||||||
type={content.type}
|
|
||||||
categories={content.categories}
|
|
||||||
langui={langui}
|
|
||||||
languageSwitcher={<LanguageSwitcher />}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{content.previous_recommended?.data?.attributes && (
|
{previousContent?.attributes && (
|
||||||
<div className="mt-12 mb-8 w-full">
|
<div className="mt-12 mb-8 w-full">
|
||||||
<h2 className="text-center text-2xl mb-4">Previous content</h2>
|
<h2 className="text-center text-2xl mb-4">
|
||||||
<PreviewLine
|
{langui.previous_content}
|
||||||
href={`/contents/${content.previous_recommended.data.attributes.slug}`}
|
</h2>
|
||||||
pre_title={
|
<PreviewLine
|
||||||
content.previous_recommended.data.attributes.titles?.[0]
|
href={`/contents/${previousContent.attributes.slug}`}
|
||||||
?.pre_title
|
pre_title={
|
||||||
}
|
previousContent.attributes.translations?.[0]?.pre_title
|
||||||
title={
|
}
|
||||||
content.previous_recommended.data.attributes.titles?.[0]
|
title={
|
||||||
?.title ??
|
previousContent.attributes.translations?.[0]?.title ??
|
||||||
prettySlug(content.previous_recommended.data.attributes.slug)
|
prettySlug(previousContent.attributes.slug)
|
||||||
}
|
}
|
||||||
subtitle={
|
subtitle={previousContent.attributes.translations?.[0]?.subtitle}
|
||||||
content.previous_recommended.data.attributes.titles?.[0]
|
thumbnail={previousContent.attributes.thumbnail?.data?.attributes}
|
||||||
?.subtitle
|
thumbnailAspectRatio="3/2"
|
||||||
}
|
topChips={
|
||||||
thumbnail={
|
isMobile
|
||||||
content.previous_recommended.data.attributes.thumbnail?.data
|
? undefined
|
||||||
?.attributes
|
: previousContent.attributes.type?.data?.attributes
|
||||||
}
|
? [
|
||||||
thumbnailAspectRatio="3/2"
|
previousContent.attributes.type.data.attributes
|
||||||
topChips={
|
.titles?.[0]
|
||||||
isMobile
|
? previousContent.attributes.type.data.attributes
|
||||||
? undefined
|
.titles[0]?.title
|
||||||
: content.previous_recommended.data.attributes.type?.data
|
: prettySlug(
|
||||||
?.attributes
|
previousContent.attributes.type.data.attributes.slug
|
||||||
? [
|
),
|
||||||
content.previous_recommended.data.attributes.type.data
|
]
|
||||||
.attributes.titles?.[0]
|
: undefined
|
||||||
? content.previous_recommended.data.attributes.type
|
}
|
||||||
.data.attributes.titles[0]?.title
|
bottomChips={
|
||||||
: prettySlug(
|
isMobile
|
||||||
content.previous_recommended.data.attributes.type
|
? undefined
|
||||||
.data.attributes.slug
|
: previousContent.attributes.categories?.data.map(
|
||||||
),
|
(category) => category.attributes?.short ?? ""
|
||||||
]
|
)
|
||||||
: undefined
|
}
|
||||||
}
|
/>
|
||||||
bottomChips={
|
</div>
|
||||||
isMobile
|
)}
|
||||||
? undefined
|
|
||||||
: content.previous_recommended.data.attributes.categories?.data.map(
|
|
||||||
(category) => category.attributes?.short ?? ""
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<HorizontalLine />
|
<HorizontalLine />
|
||||||
|
|
||||||
<Markdawn text={selectedTextSet?.text ?? ""} />
|
<Markdawn text={selectedTranslation?.text_set?.text ?? ""} />
|
||||||
|
|
||||||
{content.next_recommended?.data?.attributes && (
|
{nextContent?.attributes && (
|
||||||
<>
|
<>
|
||||||
<HorizontalLine />
|
<HorizontalLine />
|
||||||
<h2 className="text-center text-2xl mb-4">Follow-up content</h2>
|
<h2 className="text-center text-2xl mb-4">
|
||||||
<PreviewLine
|
{langui.followup_content}
|
||||||
href={`/contents/${content.next_recommended.data.attributes.slug}`}
|
</h2>
|
||||||
pre_title={
|
<PreviewLine
|
||||||
content.next_recommended.data.attributes.titles?.[0]
|
href={`/contents/${nextContent.attributes.slug}`}
|
||||||
?.pre_title
|
pre_title={nextContent.attributes.translations?.[0]?.pre_title}
|
||||||
}
|
title={
|
||||||
title={
|
nextContent.attributes.translations?.[0]?.title ??
|
||||||
content.next_recommended.data.attributes.titles?.[0]?.title ??
|
prettySlug(nextContent.attributes.slug)
|
||||||
prettySlug(content.next_recommended.data.attributes.slug)
|
}
|
||||||
}
|
subtitle={nextContent.attributes.translations?.[0]?.subtitle}
|
||||||
subtitle={
|
thumbnail={nextContent.attributes.thumbnail?.data?.attributes}
|
||||||
content.next_recommended.data.attributes.titles?.[0]?.subtitle
|
thumbnailAspectRatio="3/2"
|
||||||
}
|
topChips={
|
||||||
thumbnail={
|
isMobile
|
||||||
content.next_recommended.data.attributes.thumbnail?.data
|
? undefined
|
||||||
?.attributes
|
: nextContent.attributes.type?.data?.attributes
|
||||||
}
|
? [
|
||||||
thumbnailAspectRatio="3/2"
|
nextContent.attributes.type.data.attributes.titles?.[0]
|
||||||
topChips={
|
? nextContent.attributes.type.data.attributes.titles[0]
|
||||||
isMobile
|
?.title
|
||||||
? undefined
|
: prettySlug(
|
||||||
: content.next_recommended.data.attributes.type?.data
|
nextContent.attributes.type.data.attributes.slug
|
||||||
?.attributes
|
),
|
||||||
? [
|
]
|
||||||
content.next_recommended.data.attributes.type.data
|
: undefined
|
||||||
.attributes.titles?.[0]
|
}
|
||||||
? content.next_recommended.data.attributes.type.data
|
bottomChips={
|
||||||
.attributes.titles[0]?.title
|
isMobile
|
||||||
: prettySlug(
|
? undefined
|
||||||
content.next_recommended.data.attributes.type.data
|
: nextContent.attributes.categories?.data.map(
|
||||||
.attributes.slug
|
(category) => category.attributes?.short ?? ""
|
||||||
),
|
)
|
||||||
]
|
}
|
||||||
: undefined
|
/>
|
||||||
}
|
</>
|
||||||
bottomChips={
|
)}
|
||||||
isMobile
|
</div>
|
||||||
? undefined
|
|
||||||
: content.next_recommended.data.attributes.categories?.data.map(
|
|
||||||
(category) => category.attributes?.short ?? ""
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</ContentPanel>
|
</ContentPanel>
|
||||||
);
|
);
|
||||||
|
|
||||||
let description = "";
|
let description = "";
|
||||||
if (content?.type?.data) {
|
if (content.type?.data) {
|
||||||
description += `${langui.type}: `;
|
description += `${langui.type}: `;
|
||||||
|
|
||||||
description +=
|
description +=
|
||||||
|
@ -332,7 +322,7 @@ export default function Content(props: Props): JSX.Element {
|
||||||
|
|
||||||
description += "\n";
|
description += "\n";
|
||||||
}
|
}
|
||||||
if (content?.categories?.data && content.categories.data.length > 0) {
|
if (content.categories?.data && content.categories.data.length > 0) {
|
||||||
description += `${langui.categories}: `;
|
description += `${langui.categories}: `;
|
||||||
description += content.categories.data
|
description += content.categories.data
|
||||||
.map((category) => category.attributes?.short)
|
.map((category) => category.attributes?.short)
|
||||||
|
@ -343,15 +333,15 @@ export default function Content(props: Props): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<AppLayout
|
<AppLayout
|
||||||
navTitle={
|
navTitle={
|
||||||
content?.titles && content.titles.length > 0 && content.titles[0]
|
selectedTranslation
|
||||||
? prettyinlineTitle(
|
? prettyinlineTitle(
|
||||||
content.titles[0].pre_title,
|
selectedTranslation.pre_title,
|
||||||
content.titles[0].title,
|
selectedTranslation.title,
|
||||||
content.titles[0].subtitle
|
selectedTranslation.subtitle
|
||||||
)
|
)
|
||||||
: prettySlug(content?.slug)
|
: prettySlug(content.slug)
|
||||||
}
|
}
|
||||||
thumbnail={content?.thumbnail?.data?.attributes ?? undefined}
|
thumbnail={content.thumbnail?.data?.attributes ?? undefined}
|
||||||
contentPanel={contentPanel}
|
contentPanel={contentPanel}
|
||||||
subPanel={subPanel}
|
subPanel={subPanel}
|
||||||
description={description}
|
description={description}
|
||||||
|
@ -370,12 +360,12 @@ export async function getStaticProps(
|
||||||
language_code: context.locale ?? "en",
|
language_code: context.locale ?? "en",
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!content.contents || content.contents.data.length === 0)
|
if (!content.contents || !content.contents.data[0].attributes?.translations) {
|
||||||
return { notFound: true };
|
return { notFound: true };
|
||||||
|
}
|
||||||
const props: Props = {
|
const props: Props = {
|
||||||
...(await getAppStaticProps(context)),
|
...(await getAppStaticProps(context)),
|
||||||
content: content.contents.data[0].attributes,
|
content: content.contents.data[0].attributes as ContentWithTranslations,
|
||||||
contentId: content.contents.data[0].id,
|
|
||||||
};
|
};
|
||||||
return {
|
return {
|
||||||
props: props,
|
props: props,
|
||||||
|
|
|
@ -1,39 +1,51 @@
|
||||||
import AppLayout from "components/AppLayout";
|
import { AppLayout } from "components/AppLayout";
|
||||||
import Chip from "components/Chip";
|
import { Chip } from "components/Chip";
|
||||||
import Select from "components/Inputs/Select";
|
import { Select } from "components/Inputs/Select";
|
||||||
import Switch from "components/Inputs/Switch";
|
import { Switch } from "components/Inputs/Switch";
|
||||||
import PanelHeader from "components/PanelComponents/PanelHeader";
|
import { PanelHeader } from "components/PanelComponents/PanelHeader";
|
||||||
import ContentPanel, {
|
import {
|
||||||
|
ContentPanel,
|
||||||
ContentPanelWidthSizes,
|
ContentPanelWidthSizes,
|
||||||
} from "components/Panels/ContentPanel";
|
} from "components/Panels/ContentPanel";
|
||||||
import SubPanel from "components/Panels/SubPanel";
|
import { SubPanel } from "components/Panels/SubPanel";
|
||||||
import ThumbnailPreview from "components/PreviewCard";
|
import { PreviewCard } from "components/PreviewCard";
|
||||||
import { GetContentsQuery } from "graphql/generated";
|
import { GetContentsQuery } from "graphql/generated";
|
||||||
|
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
|
||||||
import { getReadySdk } from "graphql/sdk";
|
import { getReadySdk } from "graphql/sdk";
|
||||||
|
import { prettyinlineTitle, prettySlug } from "helpers/formatters";
|
||||||
|
import { Immutable } from "helpers/types";
|
||||||
import { GetStaticPropsContext } from "next";
|
import { GetStaticPropsContext } from "next";
|
||||||
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
|
||||||
import { prettyinlineTitle, prettySlug } from "queries/helpers";
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
interface Props extends AppStaticProps {
|
interface Props extends AppStaticProps {
|
||||||
contents: Exclude<GetContentsQuery["contents"], null | undefined>["data"];
|
contents: NonNullable<GetContentsQuery["contents"]>["data"];
|
||||||
}
|
}
|
||||||
|
|
||||||
type GroupContentItems = Map<string, Props["contents"]>;
|
type GroupContentItems = Map<string, Immutable<Props["contents"]>>;
|
||||||
|
|
||||||
export default function Contents(props: Props): JSX.Element {
|
export default function Contents(props: Immutable<Props>): JSX.Element {
|
||||||
const { langui, contents } = props;
|
const { langui, contents } = props;
|
||||||
|
|
||||||
const [groupingMethod, setGroupingMethod] = useState<number>(-1);
|
const [groupingMethod, setGroupingMethod] = useState<number>(-1);
|
||||||
const [keepInfoVisible, setKeepInfoVisible] = useState(false);
|
const [keepInfoVisible, setKeepInfoVisible] = useState(false);
|
||||||
|
|
||||||
|
const [combineRelatedContent, setCombineRelatedContent] = useState(true);
|
||||||
|
|
||||||
|
const [filteredItems, setFilteredItems] = useState(
|
||||||
|
filterContents(combineRelatedContent, contents)
|
||||||
|
);
|
||||||
|
|
||||||
const [groups, setGroups] = useState<GroupContentItems>(
|
const [groups, setGroups] = useState<GroupContentItems>(
|
||||||
getGroups(langui, groupingMethod, contents)
|
getGroups(langui, groupingMethod, filteredItems)
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setGroups(getGroups(langui, groupingMethod, contents));
|
setFilteredItems(filterContents(combineRelatedContent, contents));
|
||||||
}, [langui, groupingMethod, contents]);
|
}, [combineRelatedContent, contents]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setGroups(getGroups(langui, groupingMethod, filteredItems));
|
||||||
|
}, [langui, groupingMethod, filteredItems]);
|
||||||
|
|
||||||
const subPanel = (
|
const subPanel = (
|
||||||
<SubPanel>
|
<SubPanel>
|
||||||
|
@ -55,7 +67,15 @@ export default function Contents(props: Props): JSX.Element {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-row gap-2 place-items-center coarse:hidden">
|
<div className="flex flex-row gap-2 place-items-center coarse:hidden">
|
||||||
<p className="flex-shrink-0">{"Always show info"}:</p>
|
<p className="flex-shrink-0">{langui.combine_related_contents}:</p>
|
||||||
|
<Switch
|
||||||
|
setState={setCombineRelatedContent}
|
||||||
|
state={combineRelatedContent}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-row gap-2 place-items-center coarse:hidden">
|
||||||
|
<p className="flex-shrink-0">{langui.always_show_info}:</p>
|
||||||
<Switch setState={setKeepInfoVisible} state={keepInfoVisible} />
|
<Switch setState={setKeepInfoVisible} state={keepInfoVisible} />
|
||||||
</div>
|
</div>
|
||||||
</SubPanel>
|
</SubPanel>
|
||||||
|
@ -69,7 +89,8 @@ export default function Contents(props: Props): JSX.Element {
|
||||||
{name && (
|
{name && (
|
||||||
<h2
|
<h2
|
||||||
key={`h2${name}`}
|
key={`h2${name}`}
|
||||||
className="text-2xl pb-2 pt-10 first-of-type:pt-0 flex flex-row place-items-center gap-2"
|
className="text-2xl pb-2 pt-10 first-of-type:pt-0
|
||||||
|
flex flex-row place-items-center gap-2"
|
||||||
>
|
>
|
||||||
{name}
|
{name}
|
||||||
<Chip>{`${items.length} ${
|
<Chip>{`${items.length} ${
|
||||||
|
@ -81,22 +102,30 @@ export default function Contents(props: Props): JSX.Element {
|
||||||
)}
|
)}
|
||||||
<div
|
<div
|
||||||
key={`items${name}`}
|
key={`items${name}`}
|
||||||
className="grid gap-8 items-end grid-cols-2 desktop:grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))]"
|
className="grid gap-8 mobile:gap-4 items-end grid-cols-2
|
||||||
|
desktop:grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))]"
|
||||||
>
|
>
|
||||||
{items.map((item) => (
|
{items.map((item) => (
|
||||||
<>
|
<>
|
||||||
{item.attributes && (
|
{item.attributes && (
|
||||||
<ThumbnailPreview
|
<PreviewCard
|
||||||
key={item.id}
|
key={item.id}
|
||||||
href={`/contents/${item.attributes.slug}`}
|
href={`/contents/${item.attributes.slug}`}
|
||||||
pre_title={item.attributes.titles?.[0]?.pre_title}
|
pre_title={item.attributes.translations?.[0]?.pre_title}
|
||||||
title={
|
title={
|
||||||
item.attributes.titles?.[0]?.title ??
|
item.attributes.translations?.[0]?.title ??
|
||||||
prettySlug(item.attributes.slug)
|
prettySlug(item.attributes.slug)
|
||||||
}
|
}
|
||||||
subtitle={item.attributes.titles?.[0]?.subtitle}
|
subtitle={item.attributes.translations?.[0]?.subtitle}
|
||||||
thumbnail={item.attributes.thumbnail?.data?.attributes}
|
thumbnail={item.attributes.thumbnail?.data?.attributes}
|
||||||
thumbnailAspectRatio="3/2"
|
thumbnailAspectRatio="3/2"
|
||||||
|
stackNumber={
|
||||||
|
combineRelatedContent &&
|
||||||
|
item.attributes.group?.data?.attributes?.combine
|
||||||
|
? item.attributes.group.data.attributes.contents
|
||||||
|
?.data.length
|
||||||
|
: 0
|
||||||
|
}
|
||||||
topChips={
|
topChips={
|
||||||
item.attributes.type?.data?.attributes
|
item.attributes.type?.data?.attributes
|
||||||
? [
|
? [
|
||||||
|
@ -143,18 +172,18 @@ export async function getStaticProps(
|
||||||
});
|
});
|
||||||
if (!contents.contents) return { notFound: true };
|
if (!contents.contents) return { notFound: true };
|
||||||
contents.contents.data.sort((a, b) => {
|
contents.contents.data.sort((a, b) => {
|
||||||
const titleA = a.attributes?.titles?.[0]
|
const titleA = a.attributes?.translations?.[0]
|
||||||
? prettyinlineTitle(
|
? prettyinlineTitle(
|
||||||
a.attributes.titles[0].pre_title,
|
a.attributes.translations[0].pre_title,
|
||||||
a.attributes.titles[0].title,
|
a.attributes.translations[0].title,
|
||||||
a.attributes.titles[0].subtitle
|
a.attributes.translations[0].subtitle
|
||||||
)
|
)
|
||||||
: a.attributes?.slug ?? "";
|
: a.attributes?.slug ?? "";
|
||||||
const titleB = b.attributes?.titles?.[0]
|
const titleB = b.attributes?.translations?.[0]
|
||||||
? prettyinlineTitle(
|
? prettyinlineTitle(
|
||||||
b.attributes.titles[0].pre_title,
|
b.attributes.translations[0].pre_title,
|
||||||
b.attributes.titles[0].title,
|
b.attributes.translations[0].title,
|
||||||
b.attributes.titles[0].subtitle
|
b.attributes.translations[0].subtitle
|
||||||
)
|
)
|
||||||
: b.attributes?.slug ?? "";
|
: b.attributes?.slug ?? "";
|
||||||
return titleA.localeCompare(titleB);
|
return titleA.localeCompare(titleB);
|
||||||
|
@ -172,7 +201,7 @@ export async function getStaticProps(
|
||||||
function getGroups(
|
function getGroups(
|
||||||
langui: AppStaticProps["langui"],
|
langui: AppStaticProps["langui"],
|
||||||
groupByType: number,
|
groupByType: number,
|
||||||
items: Props["contents"]
|
items: Immutable<Props["contents"]>
|
||||||
): GroupContentItems {
|
): GroupContentItems {
|
||||||
switch (groupByType) {
|
switch (groupByType) {
|
||||||
case 0: {
|
case 0: {
|
||||||
|
@ -209,15 +238,16 @@ function getGroups(
|
||||||
}
|
}
|
||||||
|
|
||||||
case 1: {
|
case 1: {
|
||||||
const group: GroupContentItems = new Map();
|
const group = new Map();
|
||||||
items.map((item) => {
|
items.map((item) => {
|
||||||
const type =
|
const type =
|
||||||
item.attributes?.type?.data?.attributes?.titles?.[0]?.title ??
|
item.attributes?.type?.data?.attributes?.titles?.[0]?.title ??
|
||||||
prettySlug(item.attributes?.type?.data?.attributes?.slug);
|
item.attributes?.type?.data?.attributes?.slug
|
||||||
|
? prettySlug(item.attributes.type.data.attributes.slug)
|
||||||
|
: langui.no_type;
|
||||||
if (!group.has(type)) group.set(type, []);
|
if (!group.has(type)) group.set(type, []);
|
||||||
group.get(type)?.push(item);
|
group.get(type)?.push(item);
|
||||||
});
|
});
|
||||||
|
|
||||||
return group;
|
return group;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -228,3 +258,19 @@ function getGroups(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function filterContents(
|
||||||
|
combineRelatedContent: boolean,
|
||||||
|
contents: Immutable<Props["contents"]>
|
||||||
|
): Immutable<Props["contents"]> {
|
||||||
|
if (combineRelatedContent) {
|
||||||
|
return [...contents].filter(
|
||||||
|
(content) =>
|
||||||
|
!content.attributes?.group?.data?.attributes ||
|
||||||
|
!content.attributes.group.data.attributes.combine ||
|
||||||
|
content.attributes.group.data.attributes.contents?.data[0].id ===
|
||||||
|
content.id
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return contents;
|
||||||
|
}
|
||||||
|
|
|
@ -1,23 +1,22 @@
|
||||||
import AppLayout from "components/AppLayout";
|
import { AppLayout } from "components/AppLayout";
|
||||||
import Chip from "components/Chip";
|
import { Chip } from "components/Chip";
|
||||||
import Button from "components/Inputs/Button";
|
import { Button } from "components/Inputs/Button";
|
||||||
import ContentPanel, {
|
import {
|
||||||
|
ContentPanel,
|
||||||
ContentPanelWidthSizes,
|
ContentPanelWidthSizes,
|
||||||
} from "components/Panels/ContentPanel";
|
} from "components/Panels/ContentPanel";
|
||||||
import ToolTip from "components/ToolTip";
|
import { ToolTip } from "components/ToolTip";
|
||||||
import {
|
import { DevGetContentsQuery } from "graphql/generated";
|
||||||
DevGetContentsQuery,
|
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
|
||||||
Enum_Componentsetstextset_Status,
|
|
||||||
} from "graphql/generated";
|
|
||||||
import { getReadySdk } from "graphql/sdk";
|
import { getReadySdk } from "graphql/sdk";
|
||||||
|
import { Immutable } from "helpers/types";
|
||||||
import { GetStaticPropsContext } from "next";
|
import { GetStaticPropsContext } from "next";
|
||||||
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
|
||||||
|
|
||||||
interface Props extends AppStaticProps {
|
interface Props extends AppStaticProps {
|
||||||
contents: DevGetContentsQuery;
|
contents: DevGetContentsQuery;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function CheckupContents(props: Props): JSX.Element {
|
export default function CheckupContents(props: Immutable<Props>): JSX.Element {
|
||||||
const { contents } = props;
|
const { contents } = props;
|
||||||
const testReport = testingContent(contents);
|
const testReport = testingContent(contents);
|
||||||
|
|
||||||
|
@ -38,7 +37,8 @@ export default function CheckupContents(props: Props): JSX.Element {
|
||||||
{testReport.lines.map((line, index) => (
|
{testReport.lines.map((line, index) => (
|
||||||
<div
|
<div
|
||||||
key={index}
|
key={index}
|
||||||
className="grid grid-cols-[2em,3em,2fr,1fr,0.5fr,0.5fr,2fr] gap-2 items-center mb-2 justify-items-start"
|
className="grid grid-cols-[2em,3em,2fr,1fr,0.5fr,0.5fr,2fr]
|
||||||
|
gap-2 items-center mb-2 justify-items-start"
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
href={line.frontendUrl}
|
href={line.frontendUrl}
|
||||||
|
@ -112,7 +112,7 @@ type ReportLine = {
|
||||||
frontendUrl: string;
|
frontendUrl: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
function testingContent(contents: Props["contents"]): Report {
|
function testingContent(contents: Immutable<Props["contents"]>): Report {
|
||||||
const report: Report = {
|
const report: Report = {
|
||||||
title: "Contents",
|
title: "Contents",
|
||||||
lines: [],
|
lines: [],
|
||||||
|
@ -163,23 +163,6 @@ function testingContent(contents: Props["contents"]): Report {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
|
||||||
content.attributes.next_recommended?.data?.id === content.id ||
|
|
||||||
content.attributes.previous_recommended?.data?.id === content.id
|
|
||||||
) {
|
|
||||||
report.lines.push({
|
|
||||||
subitems: [content.attributes.slug],
|
|
||||||
name: "Self Recommendation",
|
|
||||||
type: "Error",
|
|
||||||
severity: "Very High",
|
|
||||||
description:
|
|
||||||
"The Content is referring to itself as a Next or Previous Recommended.",
|
|
||||||
recommandation: "",
|
|
||||||
backendUrl: backendUrl,
|
|
||||||
frontendUrl: frontendUrl,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!content.attributes.thumbnail?.data?.id) {
|
if (!content.attributes.thumbnail?.data?.id) {
|
||||||
report.lines.push({
|
report.lines.push({
|
||||||
subitems: [content.attributes.slug],
|
subitems: [content.attributes.slug],
|
||||||
|
@ -193,7 +176,7 @@ function testingContent(contents: Props["contents"]): Report {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (content.attributes.titles?.length === 0) {
|
if (content.attributes.translations?.length === 0) {
|
||||||
report.lines.push({
|
report.lines.push({
|
||||||
subitems: [content.attributes.slug],
|
subitems: [content.attributes.slug],
|
||||||
name: "No Titles",
|
name: "No Titles",
|
||||||
|
@ -207,10 +190,10 @@ function testingContent(contents: Props["contents"]): Report {
|
||||||
} else {
|
} else {
|
||||||
const titleLanguages: string[] = [];
|
const titleLanguages: string[] = [];
|
||||||
|
|
||||||
content.attributes.titles?.map((title, titleIndex) => {
|
content.attributes.translations?.map((translation, titleIndex) => {
|
||||||
if (title && content.attributes) {
|
if (translation && content.attributes) {
|
||||||
if (title.language?.data?.id) {
|
if (translation.language?.data?.id) {
|
||||||
if (title.language.data.id in titleLanguages) {
|
if (translation.language.data.id in titleLanguages) {
|
||||||
report.lines.push({
|
report.lines.push({
|
||||||
subitems: [
|
subitems: [
|
||||||
content.attributes.slug,
|
content.attributes.slug,
|
||||||
|
@ -225,7 +208,7 @@ function testingContent(contents: Props["contents"]): Report {
|
||||||
frontendUrl: frontendUrl,
|
frontendUrl: frontendUrl,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
titleLanguages.push(title.language.data.id);
|
titleLanguages.push(translation.language.data.id);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
report.lines.push({
|
report.lines.push({
|
||||||
|
@ -242,7 +225,7 @@ function testingContent(contents: Props["contents"]): Report {
|
||||||
frontendUrl: frontendUrl,
|
frontendUrl: frontendUrl,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (!title.description) {
|
if (!translation.description) {
|
||||||
report.lines.push({
|
report.lines.push({
|
||||||
subitems: [
|
subitems: [
|
||||||
content.attributes.slug,
|
content.attributes.slug,
|
||||||
|
@ -257,229 +240,199 @@ function testingContent(contents: Props["contents"]): Report {
|
||||||
frontendUrl: frontendUrl,
|
frontendUrl: frontendUrl,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (translation.text_set) {
|
||||||
|
report.lines.push({
|
||||||
|
subitems: [content.attributes.slug],
|
||||||
|
name: "No Text Set",
|
||||||
|
type: "Missing",
|
||||||
|
severity: "Medium",
|
||||||
|
description: "The Content has no Text Set.",
|
||||||
|
recommandation: "",
|
||||||
|
backendUrl: backendUrl,
|
||||||
|
frontendUrl: frontendUrl,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
/*
|
||||||
|
*const textSetLanguages: string[] = [];
|
||||||
|
*if (content.attributes && textSet) {
|
||||||
|
* if (textSet.language?.data?.id) {
|
||||||
|
* if (textSet.language.data.id in textSetLanguages) {
|
||||||
|
* report.lines.push({
|
||||||
|
* subitems: [
|
||||||
|
* content.attributes.slug,
|
||||||
|
* `TextSet ${textSetIndex.toString()}`,
|
||||||
|
* ],
|
||||||
|
* name: "Duplicate Language",
|
||||||
|
* type: "Error",
|
||||||
|
* severity: "High",
|
||||||
|
* description: "",
|
||||||
|
* recommandation: "",
|
||||||
|
* backendUrl: backendUrl,
|
||||||
|
* frontendUrl: frontendUrl,
|
||||||
|
* });
|
||||||
|
* } else {
|
||||||
|
* textSetLanguages.push(textSet.language.data.id);
|
||||||
|
* }
|
||||||
|
* } else {
|
||||||
|
* report.lines.push({
|
||||||
|
* subitems: [
|
||||||
|
* content.attributes.slug,
|
||||||
|
* `TextSet ${textSetIndex.toString()}`,
|
||||||
|
* ],
|
||||||
|
* name: "No Language",
|
||||||
|
* type: "Error",
|
||||||
|
* severity: "Very High",
|
||||||
|
* description: "",
|
||||||
|
* recommandation: "",
|
||||||
|
* backendUrl: backendUrl,
|
||||||
|
* frontendUrl: frontendUrl,
|
||||||
|
* });
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* if (!textSet.source_language?.data?.id) {
|
||||||
|
* report.lines.push({
|
||||||
|
* subitems: [
|
||||||
|
* content.attributes.slug,
|
||||||
|
* `TextSet ${textSetIndex.toString()}`,
|
||||||
|
* ],
|
||||||
|
* name: "No Source Language",
|
||||||
|
* type: "Error",
|
||||||
|
* severity: "High",
|
||||||
|
* description: "",
|
||||||
|
* recommandation: "",
|
||||||
|
* backendUrl: backendUrl,
|
||||||
|
* frontendUrl: frontendUrl,
|
||||||
|
* });
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* if (textSet.status !== Enum_Componentsetstextset_Status.Done) {
|
||||||
|
* report.lines.push({
|
||||||
|
* subitems: [
|
||||||
|
* content.attributes.slug,
|
||||||
|
* `TextSet ${textSetIndex.toString()}`,
|
||||||
|
* ],
|
||||||
|
* name: "Not Done Status",
|
||||||
|
* type: "Improvement",
|
||||||
|
* severity: "Low",
|
||||||
|
* description: "",
|
||||||
|
* recommandation: "",
|
||||||
|
* backendUrl: backendUrl,
|
||||||
|
* frontendUrl: frontendUrl,
|
||||||
|
* });
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* if (!textSet.text || textSet.text.length < 10) {
|
||||||
|
* report.lines.push({
|
||||||
|
* subitems: [
|
||||||
|
* content.attributes.slug,
|
||||||
|
* `TextSet ${textSetIndex.toString()}`,
|
||||||
|
* ],
|
||||||
|
* name: "No Text",
|
||||||
|
* type: "Missing",
|
||||||
|
* severity: "Medium",
|
||||||
|
* description: "",
|
||||||
|
* recommandation: "",
|
||||||
|
* backendUrl: backendUrl,
|
||||||
|
* frontendUrl: frontendUrl,
|
||||||
|
* });
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* if (
|
||||||
|
* textSet.source_language?.data?.id ===
|
||||||
|
* textSet.language?.data?.id
|
||||||
|
* ) {
|
||||||
|
* if (textSet.transcribers?.data.length === 0) {
|
||||||
|
* report.lines.push({
|
||||||
|
* subitems: [
|
||||||
|
* content.attributes.slug,
|
||||||
|
* `TextSet ${textSetIndex.toString()}`,
|
||||||
|
* ],
|
||||||
|
* name: "No Transcribers",
|
||||||
|
* type: "Missing",
|
||||||
|
* severity: "High",
|
||||||
|
* description:
|
||||||
|
* "The Content is a Transcription but doesn't credit any Transcribers.",
|
||||||
|
* recommandation: "Add the appropriate Transcribers.",
|
||||||
|
* backendUrl: backendUrl,
|
||||||
|
* frontendUrl: frontendUrl,
|
||||||
|
* });
|
||||||
|
* }
|
||||||
|
* if (
|
||||||
|
* textSet.translators?.data &&
|
||||||
|
* textSet.translators.data.length > 0
|
||||||
|
* ) {
|
||||||
|
* report.lines.push({
|
||||||
|
* subitems: [
|
||||||
|
* content.attributes.slug,
|
||||||
|
* `TextSet ${textSetIndex.toString()}`,
|
||||||
|
* ],
|
||||||
|
* name: "Credited Translators",
|
||||||
|
* type: "Error",
|
||||||
|
* severity: "High",
|
||||||
|
* description:
|
||||||
|
* "The Content is a Transcription but credits one or more Translators.",
|
||||||
|
* recommandation:
|
||||||
|
* "If appropriate, create a Translation Text Set with the Translator credited there.",
|
||||||
|
* backendUrl: backendUrl,
|
||||||
|
* frontendUrl: frontendUrl,
|
||||||
|
* });
|
||||||
|
* }
|
||||||
|
* } else {
|
||||||
|
* if (textSet.translators?.data.length === 0) {
|
||||||
|
* report.lines.push({
|
||||||
|
* subitems: [
|
||||||
|
* content.attributes.slug,
|
||||||
|
* `TextSet ${textSetIndex.toString()}`,
|
||||||
|
* ],
|
||||||
|
* name: "No Translators",
|
||||||
|
* type: "Missing",
|
||||||
|
* severity: "High",
|
||||||
|
* description:
|
||||||
|
* "The Content is a Transcription but doesn't credit any Translators.",
|
||||||
|
* recommandation: "Add the appropriate Translators.",
|
||||||
|
* backendUrl: backendUrl,
|
||||||
|
* frontendUrl: frontendUrl,
|
||||||
|
* });
|
||||||
|
* }
|
||||||
|
* if (
|
||||||
|
* textSet.transcribers?.data &&
|
||||||
|
* textSet.transcribers.data.length > 0
|
||||||
|
* ) {
|
||||||
|
* report.lines.push({
|
||||||
|
* subitems: [
|
||||||
|
* content.attributes.slug,
|
||||||
|
* `TextSet ${textSetIndex.toString()}`,
|
||||||
|
* ],
|
||||||
|
* name: "Credited Transcribers",
|
||||||
|
* type: "Error",
|
||||||
|
* severity: "High",
|
||||||
|
* description:
|
||||||
|
* "The Content is a Translation but credits one or more Transcribers.",
|
||||||
|
* recommandation:
|
||||||
|
* "If appropriate, create a Transcription Text Set with the Transcribers credited there.",
|
||||||
|
* backendUrl: backendUrl,
|
||||||
|
* frontendUrl: frontendUrl,
|
||||||
|
* });
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
report.lines.push({
|
||||||
|
subitems: [content.attributes.slug],
|
||||||
|
name: "No Sets",
|
||||||
|
type: "Missing",
|
||||||
|
severity: "Medium",
|
||||||
|
description: "The Content has no Sets.",
|
||||||
|
recommandation: "",
|
||||||
|
backendUrl: backendUrl,
|
||||||
|
frontendUrl: frontendUrl,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
|
||||||
content.attributes.text_set?.length === 0 &&
|
|
||||||
content.attributes.audio_set?.length === 0 &&
|
|
||||||
content.attributes.video_set?.length === 0
|
|
||||||
) {
|
|
||||||
report.lines.push({
|
|
||||||
subitems: [content.attributes.slug],
|
|
||||||
name: "No Sets",
|
|
||||||
type: "Missing",
|
|
||||||
severity: "Medium",
|
|
||||||
description: "The Content has no Sets.",
|
|
||||||
recommandation: "",
|
|
||||||
backendUrl: backendUrl,
|
|
||||||
frontendUrl: frontendUrl,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
if (content.attributes.video_set?.length === 0) {
|
|
||||||
report.lines.push({
|
|
||||||
subitems: [content.attributes.slug],
|
|
||||||
name: "No Video Sets",
|
|
||||||
type: "Missing",
|
|
||||||
severity: "Very Low",
|
|
||||||
description: "The Content has no Video Sets.",
|
|
||||||
recommandation: "",
|
|
||||||
backendUrl: backendUrl,
|
|
||||||
frontendUrl: frontendUrl,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (content.attributes.audio_set?.length === 0) {
|
|
||||||
report.lines.push({
|
|
||||||
subitems: [content.attributes.slug],
|
|
||||||
name: "No Audio Sets",
|
|
||||||
type: "Missing",
|
|
||||||
severity: "Very Low",
|
|
||||||
description: "The Content has no Audio Sets.",
|
|
||||||
recommandation: "",
|
|
||||||
backendUrl: backendUrl,
|
|
||||||
frontendUrl: frontendUrl,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (content.attributes.text_set?.length === 0) {
|
|
||||||
report.lines.push({
|
|
||||||
subitems: [content.attributes.slug],
|
|
||||||
name: "No Text Set",
|
|
||||||
type: "Missing",
|
|
||||||
severity: "Medium",
|
|
||||||
description: "The Content has no Text Set.",
|
|
||||||
recommandation: "",
|
|
||||||
backendUrl: backendUrl,
|
|
||||||
frontendUrl: frontendUrl,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
const textSetLanguages: string[] = [];
|
|
||||||
|
|
||||||
content.attributes.text_set?.map((textSet, textSetIndex) => {
|
|
||||||
if (content.attributes && textSet) {
|
|
||||||
if (textSet.language?.data?.id) {
|
|
||||||
if (textSet.language.data.id in textSetLanguages) {
|
|
||||||
report.lines.push({
|
|
||||||
subitems: [
|
|
||||||
content.attributes.slug,
|
|
||||||
`TextSet ${textSetIndex.toString()}`,
|
|
||||||
],
|
|
||||||
name: "Duplicate Language",
|
|
||||||
type: "Error",
|
|
||||||
severity: "High",
|
|
||||||
description: "",
|
|
||||||
recommandation: "",
|
|
||||||
backendUrl: backendUrl,
|
|
||||||
frontendUrl: frontendUrl,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
textSetLanguages.push(textSet.language.data.id);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
report.lines.push({
|
|
||||||
subitems: [
|
|
||||||
content.attributes.slug,
|
|
||||||
`TextSet ${textSetIndex.toString()}`,
|
|
||||||
],
|
|
||||||
name: "No Language",
|
|
||||||
type: "Error",
|
|
||||||
severity: "Very High",
|
|
||||||
description: "",
|
|
||||||
recommandation: "",
|
|
||||||
backendUrl: backendUrl,
|
|
||||||
frontendUrl: frontendUrl,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!textSet.source_language?.data?.id) {
|
|
||||||
report.lines.push({
|
|
||||||
subitems: [
|
|
||||||
content.attributes.slug,
|
|
||||||
`TextSet ${textSetIndex.toString()}`,
|
|
||||||
],
|
|
||||||
name: "No Source Language",
|
|
||||||
type: "Error",
|
|
||||||
severity: "High",
|
|
||||||
description: "",
|
|
||||||
recommandation: "",
|
|
||||||
backendUrl: backendUrl,
|
|
||||||
frontendUrl: frontendUrl,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (textSet.status !== Enum_Componentsetstextset_Status.Done) {
|
|
||||||
report.lines.push({
|
|
||||||
subitems: [
|
|
||||||
content.attributes.slug,
|
|
||||||
`TextSet ${textSetIndex.toString()}`,
|
|
||||||
],
|
|
||||||
name: "Not Done Status",
|
|
||||||
type: "Improvement",
|
|
||||||
severity: "Low",
|
|
||||||
description: "",
|
|
||||||
recommandation: "",
|
|
||||||
backendUrl: backendUrl,
|
|
||||||
frontendUrl: frontendUrl,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!textSet.text || textSet.text.length < 10) {
|
|
||||||
report.lines.push({
|
|
||||||
subitems: [
|
|
||||||
content.attributes.slug,
|
|
||||||
`TextSet ${textSetIndex.toString()}`,
|
|
||||||
],
|
|
||||||
name: "No Text",
|
|
||||||
type: "Missing",
|
|
||||||
severity: "Medium",
|
|
||||||
description: "",
|
|
||||||
recommandation: "",
|
|
||||||
backendUrl: backendUrl,
|
|
||||||
frontendUrl: frontendUrl,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
textSet.source_language?.data?.id === textSet.language?.data?.id
|
|
||||||
) {
|
|
||||||
if (textSet.transcribers?.data.length === 0) {
|
|
||||||
report.lines.push({
|
|
||||||
subitems: [
|
|
||||||
content.attributes.slug,
|
|
||||||
`TextSet ${textSetIndex.toString()}`,
|
|
||||||
],
|
|
||||||
name: "No Transcribers",
|
|
||||||
type: "Missing",
|
|
||||||
severity: "High",
|
|
||||||
description:
|
|
||||||
"The Content is a Transcription but doesn't credit any Transcribers.",
|
|
||||||
recommandation: "Add the appropriate Transcribers.",
|
|
||||||
backendUrl: backendUrl,
|
|
||||||
frontendUrl: frontendUrl,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
textSet.translators?.data &&
|
|
||||||
textSet.translators.data.length > 0
|
|
||||||
) {
|
|
||||||
report.lines.push({
|
|
||||||
subitems: [
|
|
||||||
content.attributes.slug,
|
|
||||||
`TextSet ${textSetIndex.toString()}`,
|
|
||||||
],
|
|
||||||
name: "Credited Translators",
|
|
||||||
type: "Error",
|
|
||||||
severity: "High",
|
|
||||||
description:
|
|
||||||
"The Content is a Transcription but credits one or more Translators.",
|
|
||||||
recommandation:
|
|
||||||
"If appropriate, create a Translation Text Set with the Translator credited there.",
|
|
||||||
backendUrl: backendUrl,
|
|
||||||
frontendUrl: frontendUrl,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (textSet.translators?.data.length === 0) {
|
|
||||||
report.lines.push({
|
|
||||||
subitems: [
|
|
||||||
content.attributes.slug,
|
|
||||||
`TextSet ${textSetIndex.toString()}`,
|
|
||||||
],
|
|
||||||
name: "No Translators",
|
|
||||||
type: "Missing",
|
|
||||||
severity: "High",
|
|
||||||
description:
|
|
||||||
"The Content is a Transcription but doesn't credit any Translators.",
|
|
||||||
recommandation: "Add the appropriate Translators.",
|
|
||||||
backendUrl: backendUrl,
|
|
||||||
frontendUrl: frontendUrl,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
textSet.transcribers?.data &&
|
|
||||||
textSet.transcribers.data.length > 0
|
|
||||||
) {
|
|
||||||
report.lines.push({
|
|
||||||
subitems: [
|
|
||||||
content.attributes.slug,
|
|
||||||
`TextSet ${textSetIndex.toString()}`,
|
|
||||||
],
|
|
||||||
name: "Credited Transcribers",
|
|
||||||
type: "Error",
|
|
||||||
severity: "High",
|
|
||||||
description:
|
|
||||||
"The Content is a Translation but credits one or more Transcribers.",
|
|
||||||
recommandation:
|
|
||||||
"If appropriate, create a Transcription Text Set with the Transcribers credited there.",
|
|
||||||
backendUrl: backendUrl,
|
|
||||||
frontendUrl: frontendUrl,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return report;
|
return report;
|
||||||
|
|
|
@ -1,23 +1,27 @@
|
||||||
import AppLayout from "components/AppLayout";
|
import { AppLayout } from "components/AppLayout";
|
||||||
import Chip from "components/Chip";
|
import { Chip } from "components/Chip";
|
||||||
import Button from "components/Inputs/Button";
|
import { Button } from "components/Inputs/Button";
|
||||||
import ContentPanel, {
|
import {
|
||||||
|
ContentPanel,
|
||||||
ContentPanelWidthSizes,
|
ContentPanelWidthSizes,
|
||||||
} from "components/Panels/ContentPanel";
|
} from "components/Panels/ContentPanel";
|
||||||
import ToolTip from "components/ToolTip";
|
import { ToolTip } from "components/ToolTip";
|
||||||
import {
|
import {
|
||||||
DevGetLibraryItemsQuery,
|
DevGetLibraryItemsQuery,
|
||||||
Enum_Componentcollectionscomponentlibraryimages_Status,
|
Enum_Componentcollectionscomponentlibraryimages_Status,
|
||||||
} from "graphql/generated";
|
} from "graphql/generated";
|
||||||
|
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
|
||||||
import { getReadySdk } from "graphql/sdk";
|
import { getReadySdk } from "graphql/sdk";
|
||||||
|
import { Immutable } from "helpers/types";
|
||||||
import { GetStaticPropsContext } from "next";
|
import { GetStaticPropsContext } from "next";
|
||||||
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
|
||||||
|
|
||||||
interface Props extends AppStaticProps {
|
interface Props extends AppStaticProps {
|
||||||
libraryItems: DevGetLibraryItemsQuery;
|
libraryItems: DevGetLibraryItemsQuery;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function CheckupLibraryItems(props: Props): JSX.Element {
|
export default function CheckupLibraryItems(
|
||||||
|
props: Immutable<Props>
|
||||||
|
): JSX.Element {
|
||||||
const { libraryItems } = props;
|
const { libraryItems } = props;
|
||||||
const testReport = testingLibraryItem(libraryItems);
|
const testReport = testingLibraryItem(libraryItems);
|
||||||
|
|
||||||
|
@ -38,7 +42,8 @@ export default function CheckupLibraryItems(props: Props): JSX.Element {
|
||||||
{testReport.lines.map((line, index) => (
|
{testReport.lines.map((line, index) => (
|
||||||
<div
|
<div
|
||||||
key={index}
|
key={index}
|
||||||
className="grid grid-cols-[2em,3em,2fr,1fr,0.5fr,0.5fr,2fr] gap-2 items-center mb-2 justify-items-start"
|
className="grid grid-cols-[2em,3em,2fr,1fr,0.5fr,0.5fr,2fr]
|
||||||
|
gap-2 items-center mb-2 justify-items-start"
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
href={line.frontendUrl}
|
href={line.frontendUrl}
|
||||||
|
@ -113,7 +118,9 @@ type ReportLine = {
|
||||||
};
|
};
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
function testingLibraryItem(libraryItems: Props["libraryItems"]): Report {
|
function testingLibraryItem(
|
||||||
|
libraryItems: Immutable<Props["libraryItems"]>
|
||||||
|
): Report {
|
||||||
const report: Report = {
|
const report: Report = {
|
||||||
title: "Contents",
|
title: "Contents",
|
||||||
lines: [],
|
lines: [],
|
||||||
|
|
|
@ -1,19 +1,21 @@
|
||||||
import AppLayout from "components/AppLayout";
|
import { AppLayout } from "components/AppLayout";
|
||||||
import Button from "components/Inputs/Button";
|
import { Button } from "components/Inputs/Button";
|
||||||
import Markdawn from "components/Markdown/Markdawn";
|
import { Markdawn } from "components/Markdown/Markdawn";
|
||||||
import ContentPanel, {
|
import {
|
||||||
|
ContentPanel,
|
||||||
ContentPanelWidthSizes,
|
ContentPanelWidthSizes,
|
||||||
} from "components/Panels/ContentPanel";
|
} from "components/Panels/ContentPanel";
|
||||||
import Popup from "components/Popup";
|
import { Popup } from "components/Popup";
|
||||||
import ToolTip from "components/ToolTip";
|
import { ToolTip } from "components/ToolTip";
|
||||||
|
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
|
||||||
|
import { Immutable } from "helpers/types";
|
||||||
import { GetStaticPropsContext } from "next";
|
import { GetStaticPropsContext } from "next";
|
||||||
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
|
||||||
import { useCallback, useState } from "react";
|
import { useCallback, useState } from "react";
|
||||||
import TurndownService from "turndown";
|
import TurndownService from "turndown";
|
||||||
|
|
||||||
interface Props extends AppStaticProps {}
|
interface Props extends AppStaticProps {}
|
||||||
|
|
||||||
export default function Editor(props: Props): JSX.Element {
|
export default function Editor(props: Immutable<Props>): JSX.Element {
|
||||||
const handleInput = useCallback((text: string) => {
|
const handleInput = useCallback((text: string) => {
|
||||||
setMarkdown(text);
|
setMarkdown(text);
|
||||||
}, []);
|
}, []);
|
||||||
|
@ -337,7 +339,8 @@ export default function Editor(props: Props): JSX.Element {
|
||||||
const textarea = event.target as HTMLTextAreaElement;
|
const textarea = event.target as HTMLTextAreaElement;
|
||||||
handleInput(textarea.value);
|
handleInput(textarea.value);
|
||||||
}}
|
}}
|
||||||
className="bg-mid !bg-opacity-40 rounded-xl outline-none p-8 w-full text-black font-monospace h-[70vh]"
|
className="bg-mid !bg-opacity-40 rounded-xl
|
||||||
|
outline-none p-8 w-full text-black font-monospace h-[70vh]"
|
||||||
value={markdown}
|
value={markdown}
|
||||||
title="Input textarea"
|
title="Input textarea"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
import AppLayout from "components/AppLayout";
|
import { AppLayout } from "components/AppLayout";
|
||||||
|
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
|
||||||
|
import { Immutable } from "helpers/types";
|
||||||
import { GetStaticPropsContext } from "next";
|
import { GetStaticPropsContext } from "next";
|
||||||
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
|
||||||
|
|
||||||
interface Props extends AppStaticProps {}
|
interface Props extends AppStaticProps {}
|
||||||
|
|
||||||
export default function Gallery(props: Props): JSX.Element {
|
export default function Gallery(props: Immutable<Props>): JSX.Element {
|
||||||
const { langui } = props;
|
const { langui } = props;
|
||||||
const contentPanel = (
|
const contentPanel = (
|
||||||
<iframe
|
<iframe
|
||||||
|
|
|
@ -1,13 +1,11 @@
|
||||||
import PostPage, { Post } from "components/PostPage";
|
import { PostPage } from "components/PostPage";
|
||||||
import { getReadySdk } from "graphql/sdk";
|
import {
|
||||||
import { GetStaticPropsContext } from "next";
|
getPostStaticProps,
|
||||||
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
PostStaticProps,
|
||||||
|
} from "graphql/getPostStaticProps";
|
||||||
|
import { Immutable } from "helpers/types";
|
||||||
|
|
||||||
interface Props extends AppStaticProps {
|
export default function Home(props: Immutable<PostStaticProps>): JSX.Element {
|
||||||
post: Post;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Home(props: Props): JSX.Element {
|
|
||||||
const { post, langui, languages, currencies } = props;
|
const { post, langui, languages, currencies } = props;
|
||||||
return (
|
return (
|
||||||
<PostPage
|
<PostPage
|
||||||
|
@ -17,7 +15,11 @@ export default function Home(props: Props): JSX.Element {
|
||||||
post={post}
|
post={post}
|
||||||
prependBody={
|
prependBody={
|
||||||
<div className="grid place-items-center place-content-center w-full gap-5 text-center">
|
<div className="grid place-items-center place-content-center w-full gap-5 text-center">
|
||||||
<div className="[mask:url('/icons/accords.svg')] [mask-size:contain] [mask-repeat:no-repeat] [mask-position:center] w-32 aspect-square mobile:w-[50vw] bg-black" />
|
<div
|
||||||
|
className="[mask:url('/icons/accords.svg')] [mask-size:contain]
|
||||||
|
[mask-repeat:no-repeat] [mask-position:center] w-32 aspect-square
|
||||||
|
mobile:w-[50vw] bg-black"
|
||||||
|
/>
|
||||||
<h1 className="text-5xl mb-0">Accord’s Library</h1>
|
<h1 className="text-5xl mb-0">Accord’s Library</h1>
|
||||||
<h2 className="text-xl -mt-5">
|
<h2 className="text-xl -mt-5">
|
||||||
Discover • Analyze • Translate • Archive
|
Discover • Analyze • Translate • Archive
|
||||||
|
@ -30,21 +32,4 @@ export default function Home(props: Props): JSX.Element {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getStaticProps(
|
export const getStaticProps = getPostStaticProps("home");
|
||||||
context: GetStaticPropsContext
|
|
||||||
): Promise<{ notFound: boolean } | { props: Props }> {
|
|
||||||
const sdk = getReadySdk();
|
|
||||||
const slug = "home";
|
|
||||||
const post = await sdk.getPost({
|
|
||||||
slug: slug,
|
|
||||||
language_code: context.locale ?? "en",
|
|
||||||
});
|
|
||||||
if (!post.posts?.data[0].attributes) return { notFound: true };
|
|
||||||
const props: Props = {
|
|
||||||
...(await getAppStaticProps(context)),
|
|
||||||
post: post.posts.data[0].attributes,
|
|
||||||
};
|
|
||||||
return {
|
|
||||||
props: props,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,57 +1,59 @@
|
||||||
import AppLayout from "components/AppLayout";
|
import { AppLayout } from "components/AppLayout";
|
||||||
import Chip from "components/Chip";
|
import { Chip } from "components/Chip";
|
||||||
import Img, { getAssetURL, ImageQuality } from "components/Img";
|
import { Img } from "components/Img";
|
||||||
import Button from "components/Inputs/Button";
|
import { Button } from "components/Inputs/Button";
|
||||||
import Switch from "components/Inputs/Switch";
|
import { Switch } from "components/Inputs/Switch";
|
||||||
import InsetBox from "components/InsetBox";
|
import { InsetBox } from "components/InsetBox";
|
||||||
import ContentLine from "components/Library/ContentLine";
|
import { ContentLine } from "components/Library/ContentLine";
|
||||||
import LightBox from "components/LightBox";
|
import { NavOption } from "components/PanelComponents/NavOption";
|
||||||
import NavOption from "components/PanelComponents/NavOption";
|
import {
|
||||||
import ReturnButton, {
|
ReturnButton,
|
||||||
ReturnButtonType,
|
ReturnButtonType,
|
||||||
} from "components/PanelComponents/ReturnButton";
|
} from "components/PanelComponents/ReturnButton";
|
||||||
import ContentPanel, {
|
import {
|
||||||
|
ContentPanel,
|
||||||
ContentPanelWidthSizes,
|
ContentPanelWidthSizes,
|
||||||
} from "components/Panels/ContentPanel";
|
} from "components/Panels/ContentPanel";
|
||||||
import SubPanel from "components/Panels/SubPanel";
|
import { SubPanel } from "components/Panels/SubPanel";
|
||||||
import ThumbnailPreview from "components/PreviewCard";
|
import { PreviewCard } from "components/PreviewCard";
|
||||||
import { useAppLayout } from "contexts/AppLayoutContext";
|
import { useAppLayout } from "contexts/AppLayoutContext";
|
||||||
import {
|
import {
|
||||||
Enum_Componentmetadatabooks_Binding_Type,
|
Enum_Componentmetadatabooks_Binding_Type,
|
||||||
Enum_Componentmetadatabooks_Page_Order,
|
Enum_Componentmetadatabooks_Page_Order,
|
||||||
GetLibraryItemQuery,
|
GetLibraryItemQuery,
|
||||||
} from "graphql/generated";
|
} from "graphql/generated";
|
||||||
|
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
|
||||||
import { getReadySdk } from "graphql/sdk";
|
import { getReadySdk } from "graphql/sdk";
|
||||||
import {
|
import {
|
||||||
GetStaticPathsContext,
|
|
||||||
GetStaticPathsResult,
|
|
||||||
GetStaticPropsContext,
|
|
||||||
} from "next";
|
|
||||||
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
|
||||||
import {
|
|
||||||
convertMmToInch,
|
|
||||||
prettyDate,
|
prettyDate,
|
||||||
prettyinlineTitle,
|
prettyinlineTitle,
|
||||||
prettyItemSubType,
|
prettyItemSubType,
|
||||||
prettyItemType,
|
prettyItemType,
|
||||||
prettyPrice,
|
prettyPrice,
|
||||||
prettyURL,
|
prettyURL,
|
||||||
sortContent,
|
} from "helpers/formatters";
|
||||||
} from "queries/helpers";
|
import { getAssetURL, ImageQuality } from "helpers/img";
|
||||||
|
import { convertMmToInch } from "helpers/numbers";
|
||||||
|
import { sortContent } from "helpers/others";
|
||||||
|
import { Immutable } from "helpers/types";
|
||||||
|
import { useLightBox } from "hooks/useLightBox";
|
||||||
|
import {
|
||||||
|
GetStaticPathsContext,
|
||||||
|
GetStaticPathsResult,
|
||||||
|
GetStaticPropsContext,
|
||||||
|
} from "next";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
interface Props extends AppStaticProps {
|
interface Props extends AppStaticProps {
|
||||||
item: Exclude<
|
item: NonNullable<
|
||||||
GetLibraryItemQuery["libraryItems"],
|
GetLibraryItemQuery["libraryItems"]
|
||||||
null | undefined
|
|
||||||
>["data"][number]["attributes"];
|
>["data"][number]["attributes"];
|
||||||
itemId: Exclude<
|
itemId: NonNullable<
|
||||||
GetLibraryItemQuery["libraryItems"],
|
GetLibraryItemQuery["libraryItems"]
|
||||||
null | undefined
|
|
||||||
>["data"][number]["id"];
|
>["data"][number]["id"];
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function LibrarySlug(props: Props): JSX.Element {
|
export default function LibrarySlug(props: Immutable<Props>): JSX.Element {
|
||||||
const { item, langui, currencies } = props;
|
const { item, langui, currencies } = props;
|
||||||
const appLayout = useAppLayout();
|
const appLayout = useAppLayout();
|
||||||
|
|
||||||
|
@ -61,10 +63,7 @@ export default function LibrarySlug(props: Props): JSX.Element {
|
||||||
|
|
||||||
sortContent(item?.contents);
|
sortContent(item?.contents);
|
||||||
|
|
||||||
const [lightboxOpen, setLightboxOpen] = useState(false);
|
const [openLightBox, LightBox] = useLightBox();
|
||||||
const [lightboxImages, setLightboxImages] = useState([""]);
|
|
||||||
const [lightboxIndex, setLightboxIndex] = useState(0);
|
|
||||||
|
|
||||||
const [keepInfoVisible, setKeepInfoVisible] = useState(false);
|
const [keepInfoVisible, setKeepInfoVisible] = useState(false);
|
||||||
|
|
||||||
let displayOpenScans = false;
|
let displayOpenScans = false;
|
||||||
|
@ -134,13 +133,7 @@ export default function LibrarySlug(props: Props): JSX.Element {
|
||||||
|
|
||||||
const contentPanel = (
|
const contentPanel = (
|
||||||
<ContentPanel width={ContentPanelWidthSizes.large}>
|
<ContentPanel width={ContentPanelWidthSizes.large}>
|
||||||
<LightBox
|
<LightBox />
|
||||||
state={lightboxOpen}
|
|
||||||
setState={setLightboxOpen}
|
|
||||||
images={lightboxImages}
|
|
||||||
index={lightboxIndex}
|
|
||||||
setIndex={setLightboxIndex}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<ReturnButton
|
<ReturnButton
|
||||||
href="/library/"
|
href="/library/"
|
||||||
|
@ -151,17 +144,16 @@ export default function LibrarySlug(props: Props): JSX.Element {
|
||||||
/>
|
/>
|
||||||
<div className="grid place-items-center gap-12">
|
<div className="grid place-items-center gap-12">
|
||||||
<div
|
<div
|
||||||
className="drop-shadow-shade-xl w-full h-[50vh] mobile:h-[60vh] desktop:mb-16 relative cursor-pointer"
|
className="drop-shadow-shade-xl w-full h-[50vh]
|
||||||
|
mobile:h-[60vh] desktop:mb-16 relative cursor-pointer"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (item?.thumbnail?.data?.attributes) {
|
if (item?.thumbnail?.data?.attributes) {
|
||||||
setLightboxOpen(true);
|
openLightBox([
|
||||||
setLightboxImages([
|
|
||||||
getAssetURL(
|
getAssetURL(
|
||||||
item.thumbnail.data.attributes.url,
|
item.thumbnail.data.attributes.url,
|
||||||
ImageQuality.Large
|
ImageQuality.Large
|
||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
setLightboxIndex(0);
|
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -169,9 +161,7 @@ export default function LibrarySlug(props: Props): JSX.Element {
|
||||||
<Img
|
<Img
|
||||||
image={item.thumbnail.data.attributes}
|
image={item.thumbnail.data.attributes}
|
||||||
quality={ImageQuality.Large}
|
quality={ImageQuality.Large}
|
||||||
layout="fill"
|
className="w-full h-full object-contain"
|
||||||
objectFit="contain"
|
|
||||||
priority
|
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="w-full aspect-[21/29.7] bg-light rounded-xl"></div>
|
<div className="w-full aspect-[21/29.7] bg-light rounded-xl"></div>
|
||||||
|
@ -212,7 +202,7 @@ export default function LibrarySlug(props: Props): JSX.Element {
|
||||||
<>
|
<>
|
||||||
{item?.urls && item.urls.length ? (
|
{item?.urls && item.urls.length ? (
|
||||||
<div className="flex flex-row place-items-center gap-3">
|
<div className="flex flex-row place-items-center gap-3">
|
||||||
<p>Available at</p>
|
<p>{langui.available_at}</p>
|
||||||
{item.urls.map((url) => (
|
{item.urls.map((url) => (
|
||||||
<>
|
<>
|
||||||
{url?.url && (
|
{url?.url && (
|
||||||
|
@ -228,7 +218,7 @@ export default function LibrarySlug(props: Props): JSX.Element {
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<p>This item is not for sale or is no longer available</p>
|
<p>{langui.item_not_available}</p>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
@ -238,13 +228,17 @@ export default function LibrarySlug(props: Props): JSX.Element {
|
||||||
{item?.gallery && item.gallery.data.length > 0 && (
|
{item?.gallery && item.gallery.data.length > 0 && (
|
||||||
<div id="gallery" className="grid place-items-center gap-8 w-full">
|
<div id="gallery" className="grid place-items-center gap-8 w-full">
|
||||||
<h2 className="text-2xl">{langui.gallery}</h2>
|
<h2 className="text-2xl">{langui.gallery}</h2>
|
||||||
<div className="grid w-full gap-8 items-end grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))]">
|
<div
|
||||||
|
className="grid w-full gap-8 items-end
|
||||||
|
grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))]"
|
||||||
|
>
|
||||||
{item.gallery.data.map((galleryItem, index) => (
|
{item.gallery.data.map((galleryItem, index) => (
|
||||||
<>
|
<>
|
||||||
{galleryItem.attributes && (
|
{galleryItem.attributes && (
|
||||||
<div
|
<div
|
||||||
key={galleryItem.id}
|
key={galleryItem.id}
|
||||||
className="relative aspect-square hover:scale-[1.02] transition-transform cursor-pointer"
|
className="relative aspect-square hover:scale-[1.02]
|
||||||
|
transition-transform cursor-pointer"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (item.gallery?.data) {
|
if (item.gallery?.data) {
|
||||||
const images: string[] = [];
|
const images: string[] = [];
|
||||||
|
@ -257,18 +251,14 @@ export default function LibrarySlug(props: Props): JSX.Element {
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
setLightboxOpen(true);
|
openLightBox(images, index);
|
||||||
setLightboxImages(images);
|
|
||||||
setLightboxIndex(index);
|
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="bg-light absolute inset-0 rounded-lg drop-shadow-shade-md"></div>
|
|
||||||
<Img
|
<Img
|
||||||
className="rounded-lg"
|
className="bg-light rounded-lg drop-shadow-shade-md
|
||||||
|
w-full h-full object-cover"
|
||||||
image={galleryItem.attributes}
|
image={galleryItem.attributes}
|
||||||
layout="fill"
|
|
||||||
objectFit="cover"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
@ -425,14 +415,17 @@ export default function LibrarySlug(props: Props): JSX.Element {
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div className="-mt-6 mb-8 flex flex-row gap-2 place-items-center coarse:hidden">
|
<div className="-mt-6 mb-8 flex flex-row gap-2 place-items-center coarse:hidden">
|
||||||
<p className="flex-shrink-0">{"Always show info"}:</p>
|
<p className="flex-shrink-0">{langui.always_show_info}:</p>
|
||||||
<Switch setState={setKeepInfoVisible} state={keepInfoVisible} />
|
<Switch setState={setKeepInfoVisible} state={keepInfoVisible} />
|
||||||
</div>
|
</div>
|
||||||
<div className="grid gap-8 items-end mobile:grid-cols-2 grid-cols-[repeat(auto-fill,minmax(15rem,1fr))] w-full">
|
<div
|
||||||
|
className="grid gap-8 items-end mobile:grid-cols-2
|
||||||
|
grid-cols-[repeat(auto-fill,minmax(15rem,1fr))] w-full"
|
||||||
|
>
|
||||||
{item.subitems.data.map((subitem) => (
|
{item.subitems.data.map((subitem) => (
|
||||||
<>
|
<>
|
||||||
{subitem.attributes && (
|
{subitem.attributes && (
|
||||||
<ThumbnailPreview
|
<PreviewCard
|
||||||
key={subitem.id}
|
key={subitem.id}
|
||||||
href={`/library/${subitem.attributes.slug}`}
|
href={`/library/${subitem.attributes.slug}`}
|
||||||
title={subitem.attributes.title}
|
title={subitem.attributes.title}
|
||||||
|
|
|
@ -1,47 +1,46 @@
|
||||||
import AppLayout from "components/AppLayout";
|
import { AppLayout } from "components/AppLayout";
|
||||||
import ScanSet from "components/Library/ScanSet";
|
import { ScanSet } from "components/Library/ScanSet";
|
||||||
import ScanSetCover from "components/Library/ScanSetCover";
|
import { ScanSetCover } from "components/Library/ScanSetCover";
|
||||||
import LightBox from "components/LightBox";
|
import { NavOption } from "components/PanelComponents/NavOption";
|
||||||
import NavOption from "components/PanelComponents/NavOption";
|
import {
|
||||||
import ReturnButton, {
|
ReturnButton,
|
||||||
ReturnButtonType,
|
ReturnButtonType,
|
||||||
} from "components/PanelComponents/ReturnButton";
|
} from "components/PanelComponents/ReturnButton";
|
||||||
import ContentPanel, {
|
import {
|
||||||
|
ContentPanel,
|
||||||
ContentPanelWidthSizes,
|
ContentPanelWidthSizes,
|
||||||
} from "components/Panels/ContentPanel";
|
} from "components/Panels/ContentPanel";
|
||||||
import SubPanel from "components/Panels/SubPanel";
|
import { SubPanel } from "components/Panels/SubPanel";
|
||||||
import { useAppLayout } from "contexts/AppLayoutContext";
|
import { useAppLayout } from "contexts/AppLayoutContext";
|
||||||
import { GetLibraryItemScansQuery } from "graphql/generated";
|
import { GetLibraryItemScansQuery } from "graphql/generated";
|
||||||
|
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
|
||||||
import { getReadySdk } from "graphql/sdk";
|
import { getReadySdk } from "graphql/sdk";
|
||||||
|
import { prettyinlineTitle, prettySlug } from "helpers/formatters";
|
||||||
|
import { sortContent } from "helpers/others";
|
||||||
|
import { Immutable } from "helpers/types";
|
||||||
|
import { useLightBox } from "hooks/useLightBox";
|
||||||
import {
|
import {
|
||||||
GetStaticPathsContext,
|
GetStaticPathsContext,
|
||||||
GetStaticPathsResult,
|
GetStaticPathsResult,
|
||||||
GetStaticPropsContext,
|
GetStaticPropsContext,
|
||||||
} from "next";
|
} from "next";
|
||||||
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
|
||||||
import { prettyinlineTitle, prettySlug, sortContent } from "queries/helpers";
|
|
||||||
import { useState } from "react";
|
|
||||||
|
|
||||||
interface Props extends AppStaticProps {
|
interface Props extends AppStaticProps {
|
||||||
item: Exclude<
|
item: NonNullable<
|
||||||
GetLibraryItemScansQuery["libraryItems"],
|
GetLibraryItemScansQuery["libraryItems"]
|
||||||
null | undefined
|
|
||||||
>["data"][number]["attributes"];
|
>["data"][number]["attributes"];
|
||||||
itemId: Exclude<
|
itemId: NonNullable<
|
||||||
GetLibraryItemScansQuery["libraryItems"],
|
GetLibraryItemScansQuery["libraryItems"]
|
||||||
null | undefined
|
|
||||||
>["data"][number]["id"];
|
>["data"][number]["id"];
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function LibrarySlug(props: Props): JSX.Element {
|
export default function LibrarySlug(props: Immutable<Props>): JSX.Element {
|
||||||
const { item, langui, languages } = props;
|
const { item, langui, languages } = props;
|
||||||
const appLayout = useAppLayout();
|
const appLayout = useAppLayout();
|
||||||
|
|
||||||
sortContent(item?.contents);
|
sortContent(item?.contents);
|
||||||
|
|
||||||
const [lightboxOpen, setLightboxOpen] = useState(false);
|
const [openLightBox, LightBox] = useLightBox();
|
||||||
const [lightboxImages, setLightboxImages] = useState([""]);
|
|
||||||
const [lightboxIndex, setLightboxIndex] = useState(0);
|
|
||||||
|
|
||||||
const subPanel = (
|
const subPanel = (
|
||||||
<SubPanel>
|
<SubPanel>
|
||||||
|
@ -61,7 +60,9 @@ export default function LibrarySlug(props: Props): JSX.Element {
|
||||||
subtitle={
|
subtitle={
|
||||||
content.attributes?.range[0]?.__typename ===
|
content.attributes?.range[0]?.__typename ===
|
||||||
"ComponentRangePageRange"
|
"ComponentRangePageRange"
|
||||||
? `${content.attributes.range[0].starting_page} → ${content.attributes.range[0].ending_page}`
|
? `${content.attributes.range[0].starting_page}` +
|
||||||
|
`→` +
|
||||||
|
`${content.attributes.range[0].ending_page}`
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
onClick={() => appLayout.setSubPanelOpen(false)}
|
onClick={() => appLayout.setSubPanelOpen(false)}
|
||||||
|
@ -73,13 +74,7 @@ export default function LibrarySlug(props: Props): JSX.Element {
|
||||||
|
|
||||||
const contentPanel = (
|
const contentPanel = (
|
||||||
<ContentPanel width={ContentPanelWidthSizes.large}>
|
<ContentPanel width={ContentPanelWidthSizes.large}>
|
||||||
<LightBox
|
<LightBox />
|
||||||
state={lightboxOpen}
|
|
||||||
setState={setLightboxOpen}
|
|
||||||
images={lightboxImages}
|
|
||||||
index={lightboxIndex}
|
|
||||||
setIndex={setLightboxIndex}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<ReturnButton
|
<ReturnButton
|
||||||
href={`/library/${item?.slug}`}
|
href={`/library/${item?.slug}`}
|
||||||
|
@ -92,9 +87,7 @@ export default function LibrarySlug(props: Props): JSX.Element {
|
||||||
{item?.images && (
|
{item?.images && (
|
||||||
<ScanSetCover
|
<ScanSetCover
|
||||||
images={item.images}
|
images={item.images}
|
||||||
setLightboxImages={setLightboxImages}
|
openLightBox={openLightBox}
|
||||||
setLightboxIndex={setLightboxIndex}
|
|
||||||
setLightboxOpen={setLightboxOpen}
|
|
||||||
languages={languages}
|
languages={languages}
|
||||||
langui={langui}
|
langui={langui}
|
||||||
/>
|
/>
|
||||||
|
@ -106,9 +99,7 @@ export default function LibrarySlug(props: Props): JSX.Element {
|
||||||
<ScanSet
|
<ScanSet
|
||||||
key={content.id}
|
key={content.id}
|
||||||
scanSet={content.attributes.scan_set}
|
scanSet={content.attributes.scan_set}
|
||||||
setLightboxImages={setLightboxImages}
|
openLightBox={openLightBox}
|
||||||
setLightboxIndex={setLightboxIndex}
|
|
||||||
setLightboxOpen={setLightboxOpen}
|
|
||||||
slug={content.attributes.slug}
|
slug={content.attributes.slug}
|
||||||
title={prettySlug(content.attributes.slug, item.slug)}
|
title={prettySlug(content.attributes.slug, item.slug)}
|
||||||
languages={languages}
|
languages={languages}
|
||||||
|
|
|
@ -1,35 +1,34 @@
|
||||||
import AppLayout from "components/AppLayout";
|
import { AppLayout } from "components/AppLayout";
|
||||||
import Chip from "components/Chip";
|
import { Chip } from "components/Chip";
|
||||||
import Select from "components/Inputs/Select";
|
import { Select } from "components/Inputs/Select";
|
||||||
import Switch from "components/Inputs/Switch";
|
import { Switch } from "components/Inputs/Switch";
|
||||||
import PanelHeader from "components/PanelComponents/PanelHeader";
|
import { PanelHeader } from "components/PanelComponents/PanelHeader";
|
||||||
import ContentPanel, {
|
import {
|
||||||
|
ContentPanel,
|
||||||
ContentPanelWidthSizes,
|
ContentPanelWidthSizes,
|
||||||
} from "components/Panels/ContentPanel";
|
} from "components/Panels/ContentPanel";
|
||||||
import SubPanel from "components/Panels/SubPanel";
|
import { SubPanel } from "components/Panels/SubPanel";
|
||||||
import ThumbnailPreview from "components/PreviewCard";
|
import { PreviewCard } from "components/PreviewCard";
|
||||||
import { GetLibraryItemsPreviewQuery } from "graphql/generated";
|
import { GetLibraryItemsPreviewQuery } from "graphql/generated";
|
||||||
|
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
|
||||||
import { getReadySdk } from "graphql/sdk";
|
import { getReadySdk } from "graphql/sdk";
|
||||||
import { GetStaticPropsContext } from "next";
|
|
||||||
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
|
||||||
import {
|
import {
|
||||||
convertPrice,
|
|
||||||
prettyDate,
|
prettyDate,
|
||||||
prettyinlineTitle,
|
prettyinlineTitle,
|
||||||
prettyItemSubType,
|
prettyItemSubType,
|
||||||
} from "queries/helpers";
|
} from "helpers/formatters";
|
||||||
|
import { convertPrice } from "helpers/numbers";
|
||||||
|
import { Immutable } from "helpers/types";
|
||||||
|
import { GetStaticPropsContext } from "next";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
interface Props extends AppStaticProps {
|
interface Props extends AppStaticProps {
|
||||||
items: Exclude<
|
items: NonNullable<GetLibraryItemsPreviewQuery["libraryItems"]>["data"];
|
||||||
GetLibraryItemsPreviewQuery["libraryItems"],
|
|
||||||
null | undefined
|
|
||||||
>["data"];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type GroupLibraryItems = Map<string, Props["items"]>;
|
type GroupLibraryItems = Map<string, Immutable<Props["items"]>>;
|
||||||
|
|
||||||
export default function Library(props: Props): JSX.Element {
|
export default function Library(props: Immutable<Props>): JSX.Element {
|
||||||
const { langui, items: libraryItems, currencies } = props;
|
const { langui, items: libraryItems, currencies } = props;
|
||||||
|
|
||||||
const [showSubitems, setShowSubitems] = useState<boolean>(false);
|
const [showSubitems, setShowSubitems] = useState<boolean>(false);
|
||||||
|
@ -39,7 +38,7 @@ export default function Library(props: Props): JSX.Element {
|
||||||
const [groupingMethod, setGroupingMethod] = useState<number>(-1);
|
const [groupingMethod, setGroupingMethod] = useState<number>(-1);
|
||||||
const [keepInfoVisible, setKeepInfoVisible] = useState(false);
|
const [keepInfoVisible, setKeepInfoVisible] = useState(false);
|
||||||
|
|
||||||
const [filteredItems, setFilteredItems] = useState<Props["items"]>(
|
const [filteredItems, setFilteredItems] = useState(
|
||||||
filterItems(
|
filterItems(
|
||||||
showSubitems,
|
showSubitems,
|
||||||
showPrimaryItems,
|
showPrimaryItems,
|
||||||
|
@ -48,11 +47,11 @@ export default function Library(props: Props): JSX.Element {
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
const [sortedItems, setSortedItem] = useState<Props["items"]>(
|
const [sortedItems, setSortedItem] = useState(
|
||||||
sortBy(groupingMethod, filteredItems, currencies)
|
sortBy(groupingMethod, filteredItems, currencies)
|
||||||
);
|
);
|
||||||
|
|
||||||
const [groups, setGroups] = useState<GroupLibraryItems>(
|
const [groups, setGroups] = useState(
|
||||||
getGroups(langui, groupingMethod, sortedItems)
|
getGroups(langui, groupingMethod, sortedItems)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -128,7 +127,7 @@ export default function Library(props: Props): JSX.Element {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-row gap-2 place-items-center coarse:hidden">
|
<div className="flex flex-row gap-2 place-items-center coarse:hidden">
|
||||||
<p className="flex-shrink-0">{"Always show info"}:</p>
|
<p className="flex-shrink-0">{langui.always_show_info}:</p>
|
||||||
<Switch setState={setKeepInfoVisible} state={keepInfoVisible} />
|
<Switch setState={setKeepInfoVisible} state={keepInfoVisible} />
|
||||||
</div>
|
</div>
|
||||||
</SubPanel>
|
</SubPanel>
|
||||||
|
@ -142,7 +141,8 @@ export default function Library(props: Props): JSX.Element {
|
||||||
{name && (
|
{name && (
|
||||||
<h2
|
<h2
|
||||||
key={`h2${name}`}
|
key={`h2${name}`}
|
||||||
className="text-2xl pb-2 pt-10 first-of-type:pt-0 flex flex-row place-items-center gap-2"
|
className="text-2xl pb-2 pt-10 first-of-type:pt-0
|
||||||
|
flex flex-row place-items-center gap-2"
|
||||||
>
|
>
|
||||||
{name}
|
{name}
|
||||||
<Chip>{`${items.length} ${
|
<Chip>{`${items.length} ${
|
||||||
|
@ -154,12 +154,14 @@ export default function Library(props: Props): JSX.Element {
|
||||||
)}
|
)}
|
||||||
<div
|
<div
|
||||||
key={`items${name}`}
|
key={`items${name}`}
|
||||||
className="grid gap-8 items-end mobile:grid-cols-2 desktop:grid-cols-[repeat(auto-fill,_minmax(13rem,1fr))] pb-12 border-b-[3px] border-dotted last-of-type:border-0"
|
className="grid gap-8 mobile:gap-4 items-end mobile:grid-cols-2
|
||||||
|
desktop:grid-cols-[repeat(auto-fill,_minmax(13rem,1fr))]
|
||||||
|
pb-12 border-b-[3px] border-dotted last-of-type:border-0"
|
||||||
>
|
>
|
||||||
{items.map((item) => (
|
{items.map((item) => (
|
||||||
<>
|
<>
|
||||||
{item.attributes && (
|
{item.attributes && (
|
||||||
<ThumbnailPreview
|
<PreviewCard
|
||||||
key={item.id}
|
key={item.id}
|
||||||
href={`/library/${item.attributes.slug}`}
|
href={`/library/${item.attributes.slug}`}
|
||||||
title={item.attributes.title}
|
title={item.attributes.title}
|
||||||
|
@ -224,7 +226,7 @@ export async function getStaticProps(
|
||||||
function getGroups(
|
function getGroups(
|
||||||
langui: AppStaticProps["langui"],
|
langui: AppStaticProps["langui"],
|
||||||
groupByType: number,
|
groupByType: number,
|
||||||
items: Props["items"]
|
items: Immutable<Props["items"]>
|
||||||
): GroupLibraryItems {
|
): GroupLibraryItems {
|
||||||
switch (groupByType) {
|
switch (groupByType) {
|
||||||
case 0: {
|
case 0: {
|
||||||
|
@ -262,7 +264,7 @@ function getGroups(
|
||||||
}
|
}
|
||||||
|
|
||||||
case 1: {
|
case 1: {
|
||||||
const group: GroupLibraryItems = new Map();
|
const group = new Map();
|
||||||
group.set(langui.audio ?? "Audio", []);
|
group.set(langui.audio ?? "Audio", []);
|
||||||
group.set(langui.game ?? "Game", []);
|
group.set(langui.game ?? "Game", []);
|
||||||
group.set(langui.textual ?? "Textual", []);
|
group.set(langui.textual ?? "Textual", []);
|
||||||
|
@ -334,7 +336,7 @@ function getGroups(
|
||||||
years.push(item.attributes.release_date.year);
|
years.push(item.attributes.release_date.year);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const group: GroupLibraryItems = new Map();
|
const group = new Map();
|
||||||
years.sort((a, b) => a - b);
|
years.sort((a, b) => a - b);
|
||||||
years.map((year) => {
|
years.map((year) => {
|
||||||
group.set(year.toString(), []);
|
group.set(year.toString(), []);
|
||||||
|
@ -352,7 +354,7 @@ function getGroups(
|
||||||
}
|
}
|
||||||
|
|
||||||
default: {
|
default: {
|
||||||
const group: GroupLibraryItems = new Map();
|
const group = new Map();
|
||||||
group.set("", items);
|
group.set("", items);
|
||||||
return group;
|
return group;
|
||||||
}
|
}
|
||||||
|
@ -363,8 +365,8 @@ function filterItems(
|
||||||
showSubitems: boolean,
|
showSubitems: boolean,
|
||||||
showPrimaryItems: boolean,
|
showPrimaryItems: boolean,
|
||||||
showSecondaryItems: boolean,
|
showSecondaryItems: boolean,
|
||||||
items: Props["items"]
|
items: Immutable<Props["items"]>
|
||||||
): Props["items"] {
|
): Immutable<Props["items"]> {
|
||||||
return [...items].filter((item) => {
|
return [...items].filter((item) => {
|
||||||
if (!showSubitems && !item.attributes?.root_item) return false;
|
if (!showSubitems && !item.attributes?.root_item) return false;
|
||||||
if (
|
if (
|
||||||
|
@ -384,9 +386,9 @@ function filterItems(
|
||||||
|
|
||||||
function sortBy(
|
function sortBy(
|
||||||
orderByType: number,
|
orderByType: number,
|
||||||
items: Props["items"],
|
items: Immutable<Props["items"]>,
|
||||||
currencies: AppStaticProps["currencies"]
|
currencies: AppStaticProps["currencies"]
|
||||||
): Props["items"] {
|
): Immutable<Props["items"]> {
|
||||||
switch (orderByType) {
|
switch (orderByType) {
|
||||||
case 0:
|
case 0:
|
||||||
return [...items].sort((a, b) => {
|
return [...items].sort((a, b) => {
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import AppLayout from "components/AppLayout";
|
import { AppLayout } from "components/AppLayout";
|
||||||
import PanelHeader from "components/PanelComponents/PanelHeader";
|
import { PanelHeader } from "components/PanelComponents/PanelHeader";
|
||||||
import SubPanel from "components/Panels/SubPanel";
|
import { SubPanel } from "components/Panels/SubPanel";
|
||||||
|
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
|
||||||
|
import { Immutable } from "helpers/types";
|
||||||
import { GetStaticPropsContext } from "next";
|
import { GetStaticPropsContext } from "next";
|
||||||
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
|
||||||
|
|
||||||
interface Props extends AppStaticProps {}
|
interface Props extends AppStaticProps {}
|
||||||
export default function Merch(props: Props): JSX.Element {
|
export default function Merch(props: Immutable<Props>): JSX.Element {
|
||||||
const { langui } = props;
|
const { langui } = props;
|
||||||
const subPanel = (
|
const subPanel = (
|
||||||
<SubPanel>
|
<SubPanel>
|
||||||
|
|
|
@ -1,22 +1,20 @@
|
||||||
import PostPage, { Post } from "components/PostPage";
|
import { PostPage } from "components/PostPage";
|
||||||
import { GetPostQuery } from "graphql/generated";
|
import { AppStaticProps } from "graphql/getAppStaticProps";
|
||||||
|
import {
|
||||||
|
getPostStaticProps,
|
||||||
|
PostStaticProps,
|
||||||
|
} from "graphql/getPostStaticProps";
|
||||||
import { getReadySdk } from "graphql/sdk";
|
import { getReadySdk } from "graphql/sdk";
|
||||||
|
import { Immutable } from "helpers/types";
|
||||||
import {
|
import {
|
||||||
GetStaticPathsContext,
|
GetStaticPathsContext,
|
||||||
GetStaticPathsResult,
|
GetStaticPathsResult,
|
||||||
GetStaticPropsContext,
|
GetStaticPropsContext,
|
||||||
} from "next";
|
} from "next";
|
||||||
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
|
||||||
|
|
||||||
interface Props extends AppStaticProps {
|
interface Props extends AppStaticProps, PostStaticProps {}
|
||||||
post: Post;
|
|
||||||
postId: Exclude<
|
|
||||||
GetPostQuery["posts"],
|
|
||||||
null | undefined
|
|
||||||
>["data"][number]["id"];
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function LibrarySlug(props: Props): JSX.Element {
|
export default function LibrarySlug(props: Immutable<Props>): JSX.Element {
|
||||||
const { post, langui, languages, currencies } = props;
|
const { post, langui, languages, currencies } = props;
|
||||||
return (
|
return (
|
||||||
<PostPage
|
<PostPage
|
||||||
|
@ -36,21 +34,8 @@ export default function LibrarySlug(props: Props): JSX.Element {
|
||||||
export async function getStaticProps(
|
export async function getStaticProps(
|
||||||
context: GetStaticPropsContext
|
context: GetStaticPropsContext
|
||||||
): Promise<{ notFound: boolean } | { props: Props }> {
|
): Promise<{ notFound: boolean } | { props: Props }> {
|
||||||
const sdk = getReadySdk();
|
|
||||||
const slug = context.params?.slug ? context.params.slug.toString() : "";
|
const slug = context.params?.slug ? context.params.slug.toString() : "";
|
||||||
const post = await sdk.getPost({
|
return await getPostStaticProps(slug)(context);
|
||||||
slug: slug,
|
|
||||||
language_code: context.locale ?? "en",
|
|
||||||
});
|
|
||||||
if (!post.posts?.data[0].attributes) return { notFound: true };
|
|
||||||
const props: Props = {
|
|
||||||
...(await getAppStaticProps(context)),
|
|
||||||
post: post.posts.data[0].attributes,
|
|
||||||
postId: post.posts.data[0].id,
|
|
||||||
};
|
|
||||||
return {
|
|
||||||
props: props,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getStaticPaths(
|
export async function getStaticPaths(
|
||||||
|
|
|
@ -1,35 +1,30 @@
|
||||||
import AppLayout from "components/AppLayout";
|
import { AppLayout } from "components/AppLayout";
|
||||||
import Switch from "components/Inputs/Switch";
|
import { Switch } from "components/Inputs/Switch";
|
||||||
import PanelHeader from "components/PanelComponents/PanelHeader";
|
import { PanelHeader } from "components/PanelComponents/PanelHeader";
|
||||||
import ContentPanel, {
|
import {
|
||||||
|
ContentPanel,
|
||||||
ContentPanelWidthSizes,
|
ContentPanelWidthSizes,
|
||||||
} from "components/Panels/ContentPanel";
|
} from "components/Panels/ContentPanel";
|
||||||
import SubPanel from "components/Panels/SubPanel";
|
import { SubPanel } from "components/Panels/SubPanel";
|
||||||
import ThumbnailPreview from "components/PreviewCard";
|
import { PreviewCard } from "components/PreviewCard";
|
||||||
import { GetPostsPreviewQuery } from "graphql/generated";
|
import { GetPostsPreviewQuery } from "graphql/generated";
|
||||||
|
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
|
||||||
import { getReadySdk } from "graphql/sdk";
|
import { getReadySdk } from "graphql/sdk";
|
||||||
|
import { prettyDate, prettySlug } from "helpers/formatters";
|
||||||
|
import { Immutable } from "helpers/types";
|
||||||
import { GetStaticPropsContext } from "next";
|
import { GetStaticPropsContext } from "next";
|
||||||
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
|
||||||
import { prettyDate, prettySlug } from "queries/helpers";
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
interface Props extends AppStaticProps {
|
interface Props extends AppStaticProps {
|
||||||
posts: Exclude<GetPostsPreviewQuery["posts"], null | undefined>["data"];
|
posts: NonNullable<GetPostsPreviewQuery["posts"]>["data"];
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function News(props: Props): JSX.Element {
|
export default function News(props: Immutable<Props>): JSX.Element {
|
||||||
const { langui, posts } = props;
|
const { langui } = props;
|
||||||
|
const posts = sortPosts(props.posts);
|
||||||
|
|
||||||
const [keepInfoVisible, setKeepInfoVisible] = useState(true);
|
const [keepInfoVisible, setKeepInfoVisible] = useState(true);
|
||||||
|
|
||||||
posts
|
|
||||||
.sort((a, b) => {
|
|
||||||
const dateA = a.attributes?.date ? prettyDate(a.attributes.date) : "9999";
|
|
||||||
const dateB = b.attributes?.date ? prettyDate(b.attributes.date) : "9999";
|
|
||||||
return dateA.localeCompare(dateB);
|
|
||||||
})
|
|
||||||
.reverse();
|
|
||||||
|
|
||||||
const subPanel = (
|
const subPanel = (
|
||||||
<SubPanel>
|
<SubPanel>
|
||||||
<PanelHeader
|
<PanelHeader
|
||||||
|
@ -39,7 +34,7 @@ export default function News(props: Props): JSX.Element {
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="flex flex-row gap-2 place-items-center coarse:hidden">
|
<div className="flex flex-row gap-2 place-items-center coarse:hidden">
|
||||||
<p className="flex-shrink-0">{"Always show info"}:</p>
|
<p className="flex-shrink-0">{langui.always_show_info}:</p>
|
||||||
<Switch setState={setKeepInfoVisible} state={keepInfoVisible} />
|
<Switch setState={setKeepInfoVisible} state={keepInfoVisible} />
|
||||||
</div>
|
</div>
|
||||||
</SubPanel>
|
</SubPanel>
|
||||||
|
@ -47,11 +42,14 @@ export default function News(props: Props): JSX.Element {
|
||||||
|
|
||||||
const contentPanel = (
|
const contentPanel = (
|
||||||
<ContentPanel width={ContentPanelWidthSizes.large}>
|
<ContentPanel width={ContentPanelWidthSizes.large}>
|
||||||
<div className="grid gap-8 items-end grid-cols-1 desktop:grid-cols-[repeat(auto-fill,_minmax(20rem,1fr))]">
|
<div
|
||||||
|
className="grid gap-8 items-end grid-cols-1
|
||||||
|
desktop:grid-cols-[repeat(auto-fill,_minmax(20rem,1fr))]"
|
||||||
|
>
|
||||||
{posts.map((post) => (
|
{posts.map((post) => (
|
||||||
<>
|
<>
|
||||||
{post.attributes && (
|
{post.attributes && (
|
||||||
<ThumbnailPreview
|
<PreviewCard
|
||||||
key={post.id}
|
key={post.id}
|
||||||
href={`/news/${post.attributes.slug}`}
|
href={`/news/${post.attributes.slug}`}
|
||||||
title={
|
title={
|
||||||
|
@ -103,3 +101,17 @@ export async function getStaticProps(
|
||||||
props: props,
|
props: props,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function sortPosts(
|
||||||
|
posts: Immutable<Props["posts"]>
|
||||||
|
): Immutable<Props["posts"]> {
|
||||||
|
const sortedPosts = [...posts] as Props["posts"];
|
||||||
|
sortedPosts
|
||||||
|
.sort((a, b) => {
|
||||||
|
const dateA = a.attributes?.date ? prettyDate(a.attributes.date) : "9999";
|
||||||
|
const dateB = b.attributes?.date ? prettyDate(b.attributes.date) : "9999";
|
||||||
|
return dateA.localeCompare(dateB);
|
||||||
|
})
|
||||||
|
.reverse();
|
||||||
|
return sortedPosts as Immutable<Props["posts"]>;
|
||||||
|
}
|
||||||
|
|
|
@ -1,28 +1,25 @@
|
||||||
import AppLayout from "components/AppLayout";
|
import { AppLayout } from "components/AppLayout";
|
||||||
import InsetBox from "components/InsetBox";
|
import { InsetBox } from "components/InsetBox";
|
||||||
import NavOption from "components/PanelComponents/NavOption";
|
import { NavOption } from "components/PanelComponents/NavOption";
|
||||||
import ReturnButton, {
|
import {
|
||||||
|
ReturnButton,
|
||||||
ReturnButtonType,
|
ReturnButtonType,
|
||||||
} from "components/PanelComponents/ReturnButton";
|
} from "components/PanelComponents/ReturnButton";
|
||||||
import ContentPanel from "components/Panels/ContentPanel";
|
import { ContentPanel } from "components/Panels/ContentPanel";
|
||||||
import SubPanel from "components/Panels/SubPanel";
|
import { SubPanel } from "components/Panels/SubPanel";
|
||||||
import ChronologyYearComponent from "components/Wiki/Chronology/ChronologyYearComponent";
|
import { ChronologyYearComponent } from "components/Wiki/Chronology/ChronologyYearComponent";
|
||||||
import { useAppLayout } from "contexts/AppLayoutContext";
|
import { useAppLayout } from "contexts/AppLayoutContext";
|
||||||
import { GetChronologyItemsQuery, GetErasQuery } from "graphql/generated";
|
import { GetChronologyItemsQuery, GetErasQuery } from "graphql/generated";
|
||||||
|
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
|
||||||
import { getReadySdk } from "graphql/sdk";
|
import { getReadySdk } from "graphql/sdk";
|
||||||
|
import { prettySlug } from "helpers/formatters";
|
||||||
import { GetStaticPropsContext } from "next";
|
import { GetStaticPropsContext } from "next";
|
||||||
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
|
||||||
import { prettySlug } from "queries/helpers";
|
|
||||||
|
|
||||||
interface Props extends AppStaticProps {
|
interface Props extends AppStaticProps {
|
||||||
chronologyItems: Exclude<
|
chronologyItems: NonNullable<
|
||||||
GetChronologyItemsQuery["chronologyItems"],
|
GetChronologyItemsQuery["chronologyItems"]
|
||||||
null | undefined
|
|
||||||
>["data"];
|
|
||||||
chronologyEras: Exclude<
|
|
||||||
GetErasQuery["chronologyEras"],
|
|
||||||
null | undefined
|
|
||||||
>["data"];
|
>["data"];
|
||||||
|
chronologyEras: NonNullable<GetErasQuery["chronologyEras"]>["data"];
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Chronology(props: Props): JSX.Element {
|
export default function Chronology(props: Props): JSX.Element {
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
import AppLayout from "components/AppLayout";
|
import { AppLayout } from "components/AppLayout";
|
||||||
import NavOption from "components/PanelComponents/NavOption";
|
import { NavOption } from "components/PanelComponents/NavOption";
|
||||||
import PanelHeader from "components/PanelComponents/PanelHeader";
|
import { PanelHeader } from "components/PanelComponents/PanelHeader";
|
||||||
import SubPanel from "components/Panels/SubPanel";
|
import { SubPanel } from "components/Panels/SubPanel";
|
||||||
|
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
|
||||||
|
import { Immutable } from "helpers/types";
|
||||||
import { GetStaticPropsContext } from "next";
|
import { GetStaticPropsContext } from "next";
|
||||||
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
|
||||||
|
|
||||||
interface Props extends AppStaticProps {}
|
interface Props extends AppStaticProps {}
|
||||||
|
|
||||||
export default function Wiki(props: Props): JSX.Element {
|
export default function Wiki(props: Immutable<Props>): JSX.Element {
|
||||||
const { langui } = props;
|
const { langui } = props;
|
||||||
const subPanel = (
|
const subPanel = (
|
||||||
<SubPanel>
|
<SubPanel>
|
||||||
|
|
|
@ -159,5 +159,13 @@ module.exports = {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
plugin(function ({ addUtilities }) {
|
||||||
|
addUtilities({
|
||||||
|
".break-words": {
|
||||||
|
"word-break": "break-word",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue