commit
5de174d63e
|
@ -50,6 +50,7 @@ module.exports = {
|
|||
"max-classes-per-file": ["error", 1],
|
||||
// "max-depth": ["warn", 4],
|
||||
// "max-lines": "warn",
|
||||
"max-len": ["warn", { code: 100 }],
|
||||
// "max-lines-per-function": "warn",
|
||||
// "max-nested-callbacks": "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)
|
||||
|
||||
- Language: [TypeScript](https://www.typescriptlang.org/)
|
||||
- Framework: [Next.js](https://nextjs.org/) (React)
|
||||
- Queries: [GraphQL Code Generator](https://www.graphql-code-generator.com/)
|
||||
- Fetch the GraphQL schema from the GraphQL back-end endpoint
|
||||
- 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!
|
||||
- Autogenerated multi-level table of content and anchor links for the different headers
|
||||
- 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
|
||||
- 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 many screen sizes and resolutions
|
||||
- Framework: [Next.js](https://nextjs.org/) (React)
|
||||
- Multilanguage support
|
||||
- State Management: [React Context](https://reactjs.org/docs/context.html)
|
||||
- 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):
|
||||
- The website is built before running in production
|
||||
- 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
|
||||
- Good defaults for the metadate and OpenGraph properties
|
||||
- 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)
|
||||
- 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
|
||||
- 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
|
||||
|
||||
## 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-rounded": "^4.5.4",
|
||||
"@fontsource/opendyslexic": "^4.5.4",
|
||||
"@fontsource/vollkorn": "^4.5.6",
|
||||
"@fontsource/zen-maru-gothic": "^4.5.8",
|
||||
"@fontsource/vollkorn": "^4.5.9",
|
||||
"@fontsource/zen-maru-gothic": "^4.5.11",
|
||||
"@tippyjs/react": "^4.2.6",
|
||||
"autoprefixer": "^10.4.5",
|
||||
"autoprefixer": "^10.4.7",
|
||||
"graphql-request": "^4.2.0",
|
||||
"markdown-to-jsx": "^7.1.7",
|
||||
"next": "^12.1.2",
|
||||
"nodemailer": "^6.7.3",
|
||||
"react": "17.0.2",
|
||||
"react-dom": "17.0.2",
|
||||
"react-image-lightbox": "^5.1.4",
|
||||
"react-swipeable": "^6.2.1",
|
||||
"next": "^12.1.6",
|
||||
"nodemailer": "^6.7.5",
|
||||
"react": "18.1.0",
|
||||
"react-dom": "18.1.0",
|
||||
"react-hot-keys": "^2.7.2",
|
||||
"react-swipeable": "^7.0.0",
|
||||
"turndown": "^7.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@graphql-codegen/cli": "^2.6.2",
|
||||
"@graphql-codegen/typescript": "2.4.8",
|
||||
"@graphql-codegen/typescript-graphql-request": "^4.4.5",
|
||||
"@graphql-codegen/typescript-operations": "^2.3.5",
|
||||
"@types/node": "17.0.25",
|
||||
"@graphql-codegen/typescript": "2.4.11",
|
||||
"@graphql-codegen/typescript-graphql-request": "^4.4.8",
|
||||
"@graphql-codegen/typescript-operations": "^2.4.0",
|
||||
"@types/node": "17.0.33",
|
||||
"@types/nodemailer": "^6.4.4",
|
||||
"@types/react": "17.0.43",
|
||||
"@types/react-dom": "^17.0.14",
|
||||
"@types/react": "18.0.9",
|
||||
"@types/react-dom": "^18.0.4",
|
||||
"@types/turndown": "^5.0.1",
|
||||
"@typescript-eslint/eslint-plugin": "^5.20.0",
|
||||
"@typescript-eslint/parser": "^5.20.0",
|
||||
"eslint": "^8.14.0",
|
||||
"eslint-config-next": "12.1.5",
|
||||
"graphql": "^14.7.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.23.0",
|
||||
"@typescript-eslint/parser": "^5.23.0",
|
||||
"eslint": "^8.15.0",
|
||||
"eslint-config-next": "12.1.6",
|
||||
"graphql": "^16.5.0",
|
||||
"next-sitemap": "^2.5.20",
|
||||
"prettier": "^2.6.2",
|
||||
"prettier-plugin-organize-imports": "^2.3.4",
|
||||
"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 { 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 Head from "next/head";
|
||||
import { useRouter } from "next/router";
|
||||
import { AppStaticProps } from "queries/getAppStaticProps";
|
||||
import {
|
||||
getOgImage,
|
||||
OgImage,
|
||||
prettyLanguage,
|
||||
prettySlug,
|
||||
} from "queries/helpers";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useSwipeable } from "react-swipeable";
|
||||
import { ImageQuality } from "./Img";
|
||||
import OrderableList from "./Inputs/OrderableList";
|
||||
import Select from "./Inputs/Select";
|
||||
import MainPanel from "./Panels/MainPanel";
|
||||
import Popup from "./Popup";
|
||||
import { OrderableList } from "./Inputs/OrderableList";
|
||||
import { Select } from "./Inputs/Select";
|
||||
import { MainPanel } from "./Panels/MainPanel";
|
||||
import { Popup } from "./Popup";
|
||||
|
||||
interface Props extends AppStaticProps {
|
||||
subPanel?: React.ReactNode;
|
||||
|
@ -29,8 +25,19 @@ interface Props extends AppStaticProps {
|
|||
description?: string;
|
||||
}
|
||||
|
||||
export default function AppLayout(props: Props): JSX.Element {
|
||||
const { langui, currencies, languages, subPanel, contentPanel } = props;
|
||||
export function AppLayout(props: Immutable<Props>): JSX.Element {
|
||||
const {
|
||||
langui,
|
||||
currencies,
|
||||
languages,
|
||||
subPanel,
|
||||
contentPanel,
|
||||
thumbnail,
|
||||
title,
|
||||
navTitle,
|
||||
description,
|
||||
subPanelIcon,
|
||||
} = props;
|
||||
const router = useRouter();
|
||||
const isMobile = useMediaMobile();
|
||||
const appLayout = useAppLayout();
|
||||
|
@ -39,19 +46,23 @@ export default function AppLayout(props: Props): JSX.Element {
|
|||
|
||||
const handlers = useSwipeable({
|
||||
onSwipedLeft: (SwipeEventData) => {
|
||||
if (SwipeEventData.velocity < sensibilitySwipe) return;
|
||||
if (appLayout.mainPanelOpen) {
|
||||
appLayout.setMainPanelOpen(false);
|
||||
} else if (subPanel && contentPanel) {
|
||||
appLayout.setSubPanelOpen(true);
|
||||
if (appLayout.menuGestures) {
|
||||
if (SwipeEventData.velocity < sensibilitySwipe) return;
|
||||
if (appLayout.mainPanelOpen) {
|
||||
appLayout.setMainPanelOpen(false);
|
||||
} else if (subPanel && contentPanel) {
|
||||
appLayout.setSubPanelOpen(true);
|
||||
}
|
||||
}
|
||||
},
|
||||
onSwipedRight: (SwipeEventData) => {
|
||||
if (SwipeEventData.velocity < sensibilitySwipe) return;
|
||||
if (appLayout.subPanelOpen) {
|
||||
appLayout.setSubPanelOpen(false);
|
||||
} else {
|
||||
appLayout.setMainPanelOpen(true);
|
||||
if (appLayout.menuGestures) {
|
||||
if (SwipeEventData.velocity < sensibilitySwipe) return;
|
||||
if (appLayout.subPanelOpen) {
|
||||
appLayout.setSubPanelOpen(false);
|
||||
} else {
|
||||
appLayout.setMainPanelOpen(true);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
@ -59,8 +70,8 @@ export default function AppLayout(props: Props): JSX.Element {
|
|||
const turnSubIntoContent = subPanel && !contentPanel;
|
||||
|
||||
const titlePrefix = "Accord’s Library";
|
||||
const metaImage: OgImage = props.thumbnail
|
||||
? getOgImage(ImageQuality.Og, props.thumbnail)
|
||||
const metaImage: OgImage = thumbnail
|
||||
? getOgImage(ImageQuality.Og, thumbnail)
|
||||
: {
|
||||
image: "/default_og.jpg",
|
||||
width: 1200,
|
||||
|
@ -68,9 +79,9 @@ export default function AppLayout(props: Props): JSX.Element {
|
|||
alt: "Accord's Library Logo",
|
||||
};
|
||||
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(() => {
|
||||
document.getElementsByTagName("html")[0].style.fontSize = `${
|
||||
|
@ -115,7 +126,7 @@ export default function AppLayout(props: Props): JSX.Element {
|
|||
}, [currencySelect]);
|
||||
|
||||
let gridCol = "";
|
||||
if (props.subPanel) {
|
||||
if (subPanel) {
|
||||
if (appLayout.mainPanelReduced) {
|
||||
gridCol = "grid-cols-[6rem_20rem_1fr]";
|
||||
} else {
|
||||
|
@ -140,7 +151,9 @@ export default function AppLayout(props: Props): JSX.Element {
|
|||
>
|
||||
<div
|
||||
{...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>
|
||||
<title>{`${titlePrefix} - ${ogTitle}`}</title>
|
||||
|
@ -172,7 +185,8 @@ export default function AppLayout(props: Props): JSX.Element {
|
|||
|
||||
{/* Background when navbar is opened */}
|
||||
<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
|
||||
? "[backdrop-filter:blur(2px)]"
|
||||
: "pointer-events-none touch-none "
|
||||
|
@ -201,7 +215,10 @@ export default function AppLayout(props: Props): JSX.Element {
|
|||
contentPanel
|
||||
) : (
|
||||
<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-2xl w-64">{langui.select_option_sidebar}</p>
|
||||
</div>
|
||||
|
@ -212,7 +229,10 @@ export default function AppLayout(props: Props): JSX.Element {
|
|||
{/* Sub panel */}
|
||||
{subPanel && (
|
||||
<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
|
||||
? "mobile:border-l-0 mobile:w-full"
|
||||
|
@ -225,14 +245,21 @@ export default function AppLayout(props: Props): JSX.Element {
|
|||
|
||||
{/* Main panel */}
|
||||
<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
|
||||
${appLayout.mainPanelOpen ? "" : "mobile:-translate-x-full"}`}
|
||||
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 ${
|
||||
appLayout.mainPanelOpen ? "" : "mobile:-translate-x-full"
|
||||
}`}
|
||||
>
|
||||
<MainPanel langui={langui} />
|
||||
</div>
|
||||
|
||||
{/* 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
|
||||
className="material-icons mt-[.1em] cursor-pointer"
|
||||
onClick={() => {
|
||||
|
@ -261,8 +288,8 @@ export default function AppLayout(props: Props): JSX.Element {
|
|||
{subPanel && !turnSubIntoContent
|
||||
? appLayout.subPanelOpen
|
||||
? "close"
|
||||
: props.subPanelIcon
|
||||
? props.subPanelIcon
|
||||
: subPanelIcon
|
||||
? subPanelIcon
|
||||
: "tune"
|
||||
: ""}
|
||||
</span>
|
||||
|
@ -274,7 +301,10 @@ export default function AppLayout(props: Props): JSX.Element {
|
|||
>
|
||||
<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 && (
|
||||
<div>
|
||||
<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) => {
|
||||
const preferredLanguages = [...items].map(
|
||||
([code]) => code
|
||||
|
@ -437,6 +473,7 @@ export default function AppLayout(props: Props): JSX.Element {
|
|||
(event.target as HTMLInputElement).value
|
||||
)
|
||||
}
|
||||
value={appLayout.playerName}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,12 +1,16 @@
|
|||
import { Immutable } from "helpers/types";
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export default function Chip(props: Props): JSX.Element {
|
||||
export function Chip(props: Immutable<Props>): JSX.Element {
|
||||
return (
|
||||
<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}
|
||||
</div>
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import { Immutable } from "helpers/types";
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export default function HorizontalLine(props: Props): JSX.Element {
|
||||
export function HorizontalLine(props: Immutable<Props>): JSX.Element {
|
||||
return (
|
||||
<div
|
||||
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 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 {
|
||||
className?: string;
|
||||
image?: UploadImageFragment | string;
|
||||
quality?: ImageQuality;
|
||||
alt?: ImageProps["alt"];
|
||||
layout?: ImageProps["layout"];
|
||||
objectFit?: ImageProps["objectFit"];
|
||||
priority?: ImageProps["priority"];
|
||||
onClick?: MouseEventHandler<HTMLImageElement>;
|
||||
}
|
||||
|
||||
export default function Img(props: Props): JSX.Element {
|
||||
if (typeof props.image === "string") {
|
||||
export function Img(props: Immutable<Props>): JSX.Element {
|
||||
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 (
|
||||
<img
|
||||
className={props.className}
|
||||
src={props.image}
|
||||
alt={props.alt ?? ""}
|
||||
/>
|
||||
);
|
||||
} else if (props.image?.width && props.image.height) {
|
||||
const imgSize = getImgSizesByQuality(
|
||||
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
|
||||
className={className}
|
||||
src={getAssetURL(image.url, quality)}
|
||||
alt={alt ?? image.alternativeText ?? ""}
|
||||
width={imgSize.width}
|
||||
height={imgSize.height}
|
||||
loading="lazy"
|
||||
onClick={onClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
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 { MouseEventHandler } from "react";
|
||||
|
||||
|
@ -14,7 +15,7 @@ interface Props {
|
|||
badgeNumber?: number;
|
||||
}
|
||||
|
||||
export default function Button(props: Props): JSX.Element {
|
||||
export function Button(props: Immutable<Props>): JSX.Element {
|
||||
const {
|
||||
draggable,
|
||||
id,
|
||||
|
@ -39,11 +40,15 @@ export default function Button(props: Props): JSX.Element {
|
|||
transition-all select-none hover:[--opacityBadge:0] --opacityBadge:100 ${className} ${
|
||||
active
|
||||
? "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 && (
|
||||
<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}
|
||||
</div>
|
||||
)}
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import { AppStaticProps } from "queries/getAppStaticProps";
|
||||
import { prettyLanguage } from "queries/helpers";
|
||||
import { AppStaticProps } from "graphql/getAppStaticProps";
|
||||
import { prettyLanguage } from "helpers/formatters";
|
||||
import { Immutable } from "helpers/types";
|
||||
import { Dispatch, SetStateAction } from "react";
|
||||
import ToolTip from "../ToolTip";
|
||||
import Button from "./Button";
|
||||
import { ToolTip } from "../ToolTip";
|
||||
import { Button } from "./Button";
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
|
@ -12,7 +13,7 @@ interface Props {
|
|||
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;
|
||||
|
||||
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";
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
items: Map<string, string>;
|
||||
insertLabels?: Map<number, string | null | undefined>;
|
||||
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);
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -24,12 +26,8 @@ export default function OrderableList(props: Props): JSX.Element {
|
|||
<div className="grid gap-2">
|
||||
{[...items].map(([key, value], index) => (
|
||||
<>
|
||||
{index === 0 ? (
|
||||
<p>Primary language</p>
|
||||
) : index === 1 ? (
|
||||
<p>Secondary languages</p>
|
||||
) : (
|
||||
""
|
||||
{props.insertLabels?.get(index) && (
|
||||
<p>{props.insertLabels.get(index)}</p>
|
||||
)}
|
||||
<div
|
||||
onDragStart={(event) => {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { Immutable } from "helpers/types";
|
||||
import { Dispatch, SetStateAction } from "react";
|
||||
import Button from "./Button";
|
||||
import { Button } from "./Button";
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
|
@ -8,7 +9,7 @@ interface Props {
|
|||
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;
|
||||
|
||||
return (
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { Immutable } from "helpers/types";
|
||||
import { Dispatch, SetStateAction, useState } from "react";
|
||||
|
||||
interface Props {
|
||||
|
@ -9,7 +10,7 @@ interface Props {
|
|||
className?: string;
|
||||
}
|
||||
|
||||
export default function Select(props: Props): JSX.Element {
|
||||
export function Select(props: Immutable<Props>): JSX.Element {
|
||||
const [opened, setOpened] = useState(false);
|
||||
|
||||
return (
|
||||
|
@ -19,7 +20,9 @@ export default function Select(props: Props): JSX.Element {
|
|||
} ${props.className}`}
|
||||
>
|
||||
<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"
|
||||
}`}
|
||||
>
|
||||
|
@ -47,7 +50,8 @@ export default function Select(props: Props): JSX.Element {
|
|||
<>
|
||||
{index !== props.state && (
|
||||
<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}
|
||||
id={option}
|
||||
onClick={() => {
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { Immutable } from "helpers/types";
|
||||
import { Dispatch, SetStateAction } from "react";
|
||||
|
||||
interface Props {
|
||||
|
@ -6,18 +7,20 @@ interface Props {
|
|||
className?: string;
|
||||
}
|
||||
|
||||
export default function Switch(props: Props): JSX.Element {
|
||||
export function Switch(props: Immutable<Props>): JSX.Element {
|
||||
return (
|
||||
<div
|
||||
className={`h-6 w-12 rounded-full border-2 border-mid grid transition-colors relative cursor-pointer ${
|
||||
props.className
|
||||
} ${props.state ? "bg-mid" : "bg-light"}`}
|
||||
className={`h-6 w-12 rounded-full border-2 border-mid grid
|
||||
transition-colors relative cursor-pointer ${props.className} ${
|
||||
props.state ? "bg-mid" : "bg-light"
|
||||
}`}
|
||||
onClick={() => {
|
||||
props.setState(!props.state);
|
||||
}}
|
||||
>
|
||||
<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%]"
|
||||
}`}
|
||||
></div>
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
import { Immutable } from "helpers/types";
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
children: React.ReactNode;
|
||||
id?: string;
|
||||
}
|
||||
|
||||
export default function InsetBox(props: Props): JSX.Element {
|
||||
export function InsetBox(props: Immutable<Props>): JSX.Element {
|
||||
return (
|
||||
<div
|
||||
id={props.id}
|
||||
|
|
|
@ -1,26 +1,24 @@
|
|||
import Chip from "components/Chip";
|
||||
import Button from "components/Inputs/Button";
|
||||
import { Chip } from "components/Chip";
|
||||
import { Button } from "components/Inputs/Button";
|
||||
import { GetLibraryItemQuery } from "graphql/generated";
|
||||
import { AppStaticProps } from "queries/getAppStaticProps";
|
||||
import { prettyinlineTitle, prettySlug } from "queries/helpers";
|
||||
import { AppStaticProps } from "graphql/getAppStaticProps";
|
||||
import { prettyinlineTitle, prettySlug } from "helpers/formatters";
|
||||
import { Immutable } from "helpers/types";
|
||||
import { useState } from "react";
|
||||
|
||||
interface Props {
|
||||
content: Exclude<
|
||||
Exclude<
|
||||
Exclude<
|
||||
GetLibraryItemQuery["libraryItems"],
|
||||
null | undefined
|
||||
>["data"][number]["attributes"],
|
||||
null | undefined
|
||||
>["contents"],
|
||||
null | undefined
|
||||
content: NonNullable<
|
||||
NonNullable<
|
||||
NonNullable<
|
||||
GetLibraryItemQuery["libraryItems"]
|
||||
>["data"][number]["attributes"]
|
||||
>["contents"]
|
||||
>["data"][number];
|
||||
parentSlug: string;
|
||||
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 [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"
|
||||
}`}
|
||||
>
|
||||
<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>
|
||||
<h3 className="cursor-pointer" onClick={() => setOpened(!opened)}>
|
||||
{content.attributes.content?.data?.attributes?.titles?.[0]
|
||||
{content.attributes.content?.data?.attributes?.translations?.[0]
|
||||
? prettyinlineTitle(
|
||||
content.attributes.content.data.attributes.titles[0]
|
||||
content.attributes.content.data.attributes.translations[0]
|
||||
?.pre_title,
|
||||
content.attributes.content.data.attributes.titles[0]?.title,
|
||||
content.attributes.content.data.attributes.titles[0]
|
||||
content.attributes.content.data.attributes.translations[0]
|
||||
?.title,
|
||||
content.attributes.content.data.attributes.translations[0]
|
||||
?.subtitle
|
||||
)
|
||||
: prettySlug(content.attributes.slug, props.parentSlug)}
|
||||
|
|
|
@ -1,76 +1,55 @@
|
|||
import Chip from "components/Chip";
|
||||
import Img, {
|
||||
getAssetFilename,
|
||||
getAssetURL,
|
||||
ImageQuality,
|
||||
} from "components/Img";
|
||||
import Button from "components/Inputs/Button";
|
||||
import RecorderChip from "components/RecorderChip";
|
||||
import ToolTip from "components/ToolTip";
|
||||
import { Chip } from "components/Chip";
|
||||
import { Img } 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 useSmartLanguage from "hooks/useSmartLanguage";
|
||||
import { AppStaticProps } from "queries/getAppStaticProps";
|
||||
import { getStatusDescription, isInteger } from "queries/helpers";
|
||||
import { Dispatch, SetStateAction } from "react";
|
||||
import { AppStaticProps } from "graphql/getAppStaticProps";
|
||||
import { getAssetFilename, getAssetURL, ImageQuality } from "helpers/img";
|
||||
import { isInteger } from "helpers/numbers";
|
||||
import { getStatusDescription } from "helpers/others";
|
||||
import { Immutable } from "helpers/types";
|
||||
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
||||
|
||||
interface Props {
|
||||
setLightboxOpen: Dispatch<SetStateAction<boolean>>;
|
||||
setLightboxImages: Dispatch<SetStateAction<string[]>>;
|
||||
setLightboxIndex: Dispatch<SetStateAction<number>>;
|
||||
scanSet: Exclude<
|
||||
Exclude<
|
||||
Exclude<
|
||||
Exclude<
|
||||
Exclude<
|
||||
GetLibraryItemScansQuery["libraryItems"],
|
||||
null | undefined
|
||||
>["data"][number]["attributes"],
|
||||
null | undefined
|
||||
>["contents"],
|
||||
null | undefined
|
||||
>["data"][number]["attributes"],
|
||||
null | undefined
|
||||
>["scan_set"],
|
||||
null | undefined
|
||||
openLightBox: (images: string[], index?: number) => void;
|
||||
scanSet: NonNullable<
|
||||
NonNullable<
|
||||
NonNullable<
|
||||
NonNullable<
|
||||
NonNullable<
|
||||
GetLibraryItemScansQuery["libraryItems"]
|
||||
>["data"][number]["attributes"]
|
||||
>["contents"]
|
||||
>["data"][number]["attributes"]
|
||||
>["scan_set"]
|
||||
>;
|
||||
slug: string;
|
||||
title: string;
|
||||
languages: AppStaticProps["languages"];
|
||||
langui: AppStaticProps["langui"];
|
||||
content: Exclude<
|
||||
Exclude<
|
||||
Exclude<
|
||||
Exclude<
|
||||
GetLibraryItemScansQuery["libraryItems"],
|
||||
null | undefined
|
||||
>["data"][number]["attributes"],
|
||||
null | undefined
|
||||
>["contents"],
|
||||
null | undefined
|
||||
>["data"][number]["attributes"],
|
||||
null | undefined
|
||||
content: NonNullable<
|
||||
NonNullable<
|
||||
NonNullable<
|
||||
NonNullable<
|
||||
GetLibraryItemScansQuery["libraryItems"]
|
||||
>["data"][number]["attributes"]
|
||||
>["contents"]
|
||||
>["data"][number]["attributes"]
|
||||
>["content"];
|
||||
}
|
||||
|
||||
export default function ScanSet(props: Props): JSX.Element {
|
||||
const {
|
||||
setLightboxOpen,
|
||||
setLightboxImages,
|
||||
setLightboxIndex,
|
||||
scanSet,
|
||||
slug,
|
||||
title,
|
||||
languages,
|
||||
langui,
|
||||
content,
|
||||
} = props;
|
||||
export function ScanSet(props: Immutable<Props>): JSX.Element {
|
||||
const { openLightBox, scanSet, slug, title, languages, langui, content } =
|
||||
props;
|
||||
|
||||
const [selectedScan, LanguageSwitcher] = useSmartLanguage({
|
||||
items: scanSet,
|
||||
languages: languages,
|
||||
languageExtractor: (item) => item?.language?.data?.attributes?.code,
|
||||
languageExtractor: (item) => item.language?.data?.attributes?.code,
|
||||
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) {
|
||||
let aName = getAssetFilename(a.attributes.url);
|
||||
let bName = getAssetFilename(b.attributes.url);
|
||||
|
@ -93,7 +72,7 @@ export default function ScanSet(props: Props): JSX.Element {
|
|||
}
|
||||
return 0;
|
||||
});
|
||||
return item;
|
||||
return newItem;
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -101,7 +80,10 @@ export default function ScanSet(props: Props): JSX.Element {
|
|||
<>
|
||||
{selectedScan && (
|
||||
<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">
|
||||
{title}
|
||||
</h2>
|
||||
|
@ -198,11 +180,16 @@ export default function ScanSet(props: Props): JSX.Element {
|
|||
)}
|
||||
</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) => (
|
||||
<div
|
||||
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={() => {
|
||||
const images: string[] = [];
|
||||
selectedScan.pages?.data.map((image) => {
|
||||
|
@ -211,9 +198,7 @@ export default function ScanSet(props: Props): JSX.Element {
|
|||
getAssetURL(image.attributes.url, ImageQuality.Large)
|
||||
);
|
||||
});
|
||||
setLightboxOpen(true);
|
||||
setLightboxImages(images);
|
||||
setLightboxIndex(index);
|
||||
openLightBox(images, index);
|
||||
}}
|
||||
>
|
||||
{page.attributes && (
|
||||
|
|
|
@ -1,48 +1,37 @@
|
|||
import Chip from "components/Chip";
|
||||
import Img, { getAssetURL, ImageQuality } from "components/Img";
|
||||
import RecorderChip from "components/RecorderChip";
|
||||
import ToolTip from "components/ToolTip";
|
||||
import { Chip } from "components/Chip";
|
||||
import { Img } from "components/Img";
|
||||
import { RecorderChip } from "components/RecorderChip";
|
||||
import { ToolTip } from "components/ToolTip";
|
||||
import {
|
||||
GetLibraryItemScansQuery,
|
||||
UploadImageFragment,
|
||||
} from "graphql/generated";
|
||||
import useSmartLanguage from "hooks/useSmartLanguage";
|
||||
import { AppStaticProps } from "queries/getAppStaticProps";
|
||||
import { getStatusDescription } from "queries/helpers";
|
||||
import { Dispatch, SetStateAction } from "react";
|
||||
import { AppStaticProps } from "graphql/getAppStaticProps";
|
||||
import { getAssetURL, ImageQuality } from "helpers/img";
|
||||
import { getStatusDescription } from "helpers/others";
|
||||
import { Immutable } from "helpers/types";
|
||||
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
||||
|
||||
interface Props {
|
||||
setLightboxOpen: Dispatch<SetStateAction<boolean>>;
|
||||
setLightboxImages: Dispatch<SetStateAction<string[]>>;
|
||||
setLightboxIndex: Dispatch<SetStateAction<number>>;
|
||||
images: Exclude<
|
||||
Exclude<
|
||||
Exclude<
|
||||
GetLibraryItemScansQuery["libraryItems"],
|
||||
null | undefined
|
||||
>["data"][number]["attributes"],
|
||||
null | undefined
|
||||
>["images"],
|
||||
null | undefined
|
||||
openLightBox: (images: string[], index?: number) => void;
|
||||
images: NonNullable<
|
||||
NonNullable<
|
||||
NonNullable<
|
||||
GetLibraryItemScansQuery["libraryItems"]
|
||||
>["data"][number]["attributes"]
|
||||
>["images"]
|
||||
>;
|
||||
languages: AppStaticProps["languages"];
|
||||
langui: AppStaticProps["langui"];
|
||||
}
|
||||
|
||||
export default function ScanSetCover(props: Props): JSX.Element {
|
||||
const {
|
||||
setLightboxOpen,
|
||||
setLightboxImages,
|
||||
setLightboxIndex,
|
||||
images,
|
||||
languages,
|
||||
langui,
|
||||
} = props;
|
||||
export function ScanSetCover(props: Immutable<Props>): JSX.Element {
|
||||
const { openLightBox, images, languages, langui } = props;
|
||||
|
||||
const [selectedScan, LanguageSwitcher] = useSmartLanguage({
|
||||
items: images,
|
||||
languages: languages,
|
||||
languageExtractor: (item) => item?.language?.data?.attributes?.code,
|
||||
languageExtractor: (item) => item.language?.data?.attributes?.code,
|
||||
});
|
||||
|
||||
const coverImages: UploadImageFragment[] = [];
|
||||
|
@ -64,7 +53,10 @@ export default function ScanSetCover(props: Props): JSX.Element {
|
|||
<>
|
||||
{selectedScan && (
|
||||
<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">
|
||||
{"Cover"}
|
||||
</h2>
|
||||
|
@ -149,20 +141,23 @@ export default function ScanSetCover(props: Props): JSX.Element {
|
|||
)}
|
||||
</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) => (
|
||||
<div
|
||||
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={() => {
|
||||
const imgs: string[] = [];
|
||||
coverImages.map((img) => {
|
||||
if (img.url)
|
||||
imgs.push(getAssetURL(img.url, ImageQuality.Large));
|
||||
});
|
||||
setLightboxOpen(true);
|
||||
setLightboxImages(imgs);
|
||||
setLightboxIndex(index);
|
||||
openLightBox(imgs, index);
|
||||
}}
|
||||
>
|
||||
<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 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 {
|
||||
setState:
|
||||
|
@ -12,27 +16,75 @@ interface Props {
|
|||
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 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 (
|
||||
<>
|
||||
{state && (
|
||||
<Lightbox
|
||||
reactModalProps={{
|
||||
parentSelector: () => document.getElementById("MyAppLayout"),
|
||||
<Hotkeys
|
||||
keyName="left,right"
|
||||
allowRepeat
|
||||
onKeyDown={(keyName) => {
|
||||
if (keyName === "left") {
|
||||
handlePrevious();
|
||||
} else {
|
||||
handleNext();
|
||||
}
|
||||
}}
|
||||
mainSrc={images[index]}
|
||||
prevSrc={index > 0 ? images[index - 1] : undefined}
|
||||
nextSrc={index < images.length ? images[index + 1] : undefined}
|
||||
onMovePrevRequest={() => setIndex(index - 1)}
|
||||
onMoveNextRequest={() => setIndex(index + 1)}
|
||||
imageCaption=""
|
||||
imageTitle=""
|
||||
onCloseRequest={() => setState(false)}
|
||||
imagePadding={mobile ? 0 : 70}
|
||||
/>
|
||||
>
|
||||
<Popup setState={setState} state={state} padding={false} fillViewport>
|
||||
<div
|
||||
{...handlers}
|
||||
className={`grid grid-cols-[4em,1fr,4em] mobile:grid-cols-2
|
||||
[grid-template-areas:"left_image_right"]
|
||||
mobile:[grid-template-areas:"image_image""left_right"]
|
||||
place-items-center first-letter:gap-4 w-full h-full overflow-hidden`}
|
||||
>
|
||||
<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 Img, { getAssetURL, ImageQuality } from "components/Img";
|
||||
import InsetBox from "components/InsetBox";
|
||||
import LightBox from "components/LightBox";
|
||||
import ToolTip from "components/ToolTip";
|
||||
import { HorizontalLine } from "components/HorizontalLine";
|
||||
import { Img } from "components/Img";
|
||||
import { InsetBox } from "components/InsetBox";
|
||||
import { ToolTip } from "components/ToolTip";
|
||||
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 { useRouter } from "next/router";
|
||||
import { slugify } from "queries/helpers";
|
||||
import React, { useState } from "react";
|
||||
import React from "react";
|
||||
import ReactDOMServer from "react-dom/server";
|
||||
|
||||
interface Props {
|
||||
|
@ -15,26 +17,18 @@ interface Props {
|
|||
text: string;
|
||||
}
|
||||
|
||||
export default function Markdawn(props: Props): JSX.Element {
|
||||
export function Markdawn(props: Immutable<Props>): JSX.Element {
|
||||
const appLayout = useAppLayout();
|
||||
const text = preprocessMarkDawn(props.text);
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const [lightboxOpen, setLightboxOpen] = useState(false);
|
||||
const [lightboxImages, setLightboxImages] = useState([""]);
|
||||
const [lightboxIndex, setLightboxIndex] = useState(0);
|
||||
const [openLightBox, LightBox] = useLightBox();
|
||||
|
||||
if (text) {
|
||||
return (
|
||||
<>
|
||||
<LightBox
|
||||
state={lightboxOpen}
|
||||
setState={setLightboxOpen}
|
||||
images={lightboxImages}
|
||||
index={lightboxIndex}
|
||||
setIndex={setLightboxIndex}
|
||||
/>
|
||||
<LightBox />
|
||||
<Markdown
|
||||
className={`formatted ${props.className}`}
|
||||
options={{
|
||||
|
@ -253,13 +247,11 @@ export default function Markdawn(props: Props): JSX.Element {
|
|||
<div
|
||||
className="my-8 cursor-pointer place-content-center grid"
|
||||
onClick={() => {
|
||||
setLightboxOpen(true);
|
||||
setLightboxImages([
|
||||
openLightBox([
|
||||
compProps.src.startsWith("/uploads/")
|
||||
? getAssetURL(compProps.src, ImageQuality.Large)
|
||||
: compProps.src,
|
||||
]);
|
||||
setLightboxIndex(0);
|
||||
}}
|
||||
>
|
||||
<Img
|
||||
|
@ -268,8 +260,6 @@ export default function Markdawn(props: Props): JSX.Element {
|
|||
? getAssetURL(compProps.src, ImageQuality.Small)
|
||||
: compProps.src
|
||||
}
|
||||
layout="fill"
|
||||
objectFit="contain"
|
||||
quality={ImageQuality.Medium}
|
||||
></Img>
|
||||
</div>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { slugify } from "helpers/formatters";
|
||||
import { Immutable } from "helpers/types";
|
||||
import { useRouter } from "next/router";
|
||||
import { slugify } from "queries/helpers";
|
||||
import { preprocessMarkDawn } from "./Markdawn";
|
||||
|
||||
interface Props {
|
||||
|
@ -7,7 +8,7 @@ interface Props {
|
|||
title?: string;
|
||||
}
|
||||
|
||||
export default function TOCComponent(props: Props): JSX.Element {
|
||||
export function TOC(props: Immutable<Props>): JSX.Element {
|
||||
const { text, title } = props;
|
||||
const toc = getTocFromMarkdawn(preprocessMarkDawn(text), title);
|
||||
const router = useRouter();
|
||||
|
@ -28,7 +29,7 @@ export default function TOCComponent(props: Props): JSX.Element {
|
|||
}
|
||||
|
||||
interface LevelProps {
|
||||
tocchildren: TOC[];
|
||||
tocchildren: TOCInterface[];
|
||||
parentNumbering: string;
|
||||
}
|
||||
|
||||
|
@ -60,14 +61,14 @@ function TOCLevel(props: LevelProps): JSX.Element {
|
|||
);
|
||||
}
|
||||
|
||||
interface TOC {
|
||||
interface TOCInterface {
|
||||
title: string;
|
||||
slug: string;
|
||||
children: TOC[];
|
||||
children: TOCInterface[];
|
||||
}
|
||||
|
||||
export function getTocFromMarkdawn(text: string, title?: string): TOC {
|
||||
const toc: TOC = {
|
||||
export function getTocFromMarkdawn(text: string, title?: string): TOCInterface {
|
||||
const toc: TOCInterface = {
|
||||
title: title ?? "Return to top",
|
||||
slug: slugify(title),
|
||||
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 { MouseEventHandler } from "react";
|
||||
|
||||
|
@ -12,15 +13,19 @@ interface Props {
|
|||
onClick?: MouseEventHandler<HTMLDivElement>;
|
||||
}
|
||||
|
||||
export default function NavOption(props: Props): JSX.Element {
|
||||
export function NavOption(props: Immutable<Props>): JSX.Element {
|
||||
const router = useRouter();
|
||||
const isActive = router.asPath.startsWith(props.url);
|
||||
const divActive = "bg-mid shadow-inner-sm shadow-shade";
|
||||
|
||||
const border =
|
||||
"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 : ""
|
||||
} ${isActive ? divActive : ""}`;
|
||||
|
||||
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 : ""} ${
|
||||
isActive ? divActive : ""
|
||||
}`;
|
||||
|
||||
return (
|
||||
<ToolTip
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import HorizontalLine from "components/HorizontalLine";
|
||||
import { HorizontalLine } from "components/HorizontalLine";
|
||||
import { Immutable } from "helpers/types";
|
||||
|
||||
interface Props {
|
||||
icon?: string;
|
||||
|
@ -6,7 +7,7 @@ interface Props {
|
|||
description?: string | null | undefined;
|
||||
}
|
||||
|
||||
export default function PanelHeader(props: Props): JSX.Element {
|
||||
export function PanelHeader(props: Immutable<Props>): JSX.Element {
|
||||
return (
|
||||
<>
|
||||
<div className="w-full grid place-items-center">
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import HorizontalLine from "components/HorizontalLine";
|
||||
import Button from "components/Inputs/Button";
|
||||
import { HorizontalLine } from "components/HorizontalLine";
|
||||
import { Button } from "components/Inputs/Button";
|
||||
import { useAppLayout } from "contexts/AppLayoutContext";
|
||||
import { AppStaticProps } from "queries/getAppStaticProps";
|
||||
import { AppStaticProps } from "graphql/getAppStaticProps";
|
||||
import { Immutable } from "helpers/types";
|
||||
|
||||
interface Props {
|
||||
href: string;
|
||||
|
@ -18,7 +19,7 @@ export enum ReturnButtonType {
|
|||
both = "both",
|
||||
}
|
||||
|
||||
export default function ReturnButton(props: Props): JSX.Element {
|
||||
export function ReturnButton(props: Immutable<Props>): JSX.Element {
|
||||
const appLayout = useAppLayout();
|
||||
|
||||
return (
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { Immutable } from "helpers/types";
|
||||
|
||||
interface Props {
|
||||
children: React.ReactNode;
|
||||
autoformat?: boolean;
|
||||
|
@ -9,13 +11,13 @@ export enum ContentPanelWidthSizes {
|
|||
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 widthCSS =
|
||||
width === ContentPanelWidthSizes.default ? "max-w-2xl" : "w-full";
|
||||
|
||||
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
|
||||
className={`${
|
||||
props.autoformat && "formatted"
|
||||
|
|
|
@ -1,25 +1,27 @@
|
|||
import HorizontalLine from "components/HorizontalLine";
|
||||
import Button from "components/Inputs/Button";
|
||||
import NavOption from "components/PanelComponents/NavOption";
|
||||
import ToolTip from "components/ToolTip";
|
||||
import { HorizontalLine } from "components/HorizontalLine";
|
||||
import { Button } from "components/Inputs/Button";
|
||||
import { NavOption } from "components/PanelComponents/NavOption";
|
||||
import { ToolTip } from "components/ToolTip";
|
||||
import { useAppLayout } from "contexts/AppLayoutContext";
|
||||
import { AppStaticProps } from "graphql/getAppStaticProps";
|
||||
import { Immutable } from "helpers/types";
|
||||
import { useMediaDesktop } from "hooks/useMediaQuery";
|
||||
import Markdown from "markdown-to-jsx";
|
||||
import Link from "next/link";
|
||||
import { AppStaticProps } from "queries/getAppStaticProps";
|
||||
|
||||
interface Props {
|
||||
langui: AppStaticProps["langui"];
|
||||
}
|
||||
|
||||
export default function MainPanel(props: Props): JSX.Element {
|
||||
export function MainPanel(props: Immutable<Props>): JSX.Element {
|
||||
const { langui } = props;
|
||||
const isDesktop = useMediaDesktop();
|
||||
const appLayout = useAppLayout();
|
||||
|
||||
return (
|
||||
<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"
|
||||
}`}
|
||||
>
|
||||
|
@ -44,7 +46,9 @@ export default function MainPanel(props: Props): JSX.Element {
|
|||
onClick={() => appLayout.setMainPanelOpen(false)}
|
||||
className={`${
|
||||
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>
|
||||
</Link>
|
||||
|
||||
|
@ -68,22 +72,11 @@ export default function MainPanel(props: Props): JSX.Element {
|
|||
disabled={!appLayout.mainPanelReduced}
|
||||
>
|
||||
<Button
|
||||
className={
|
||||
appLayout.mainPanelReduced && isDesktop
|
||||
? ""
|
||||
: "!py-0.5 !px-2.5"
|
||||
}
|
||||
onClick={() => {
|
||||
appLayout.setConfigPanelOpen(true);
|
||||
}}
|
||||
>
|
||||
<span
|
||||
className={`material-icons ${
|
||||
!(appLayout.mainPanelReduced && isDesktop) && "!text-sm"
|
||||
} `}
|
||||
>
|
||||
settings
|
||||
</span>
|
||||
<span className={"material-icons"}>settings</span>
|
||||
</Button>
|
||||
</ToolTip>
|
||||
|
||||
|
@ -218,10 +211,22 @@ export default function MainPanel(props: Props): JSX.Element {
|
|||
className="transition-[filter] colorize-black hover:colorize-dark"
|
||||
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 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
|
||||
className="mt-4 mb-8 grid grid-flow-col place-content-center gap-1
|
||||
hover:[--theme-color-black:var(--theme-color-dark)]"
|
||||
>
|
||||
<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>
|
||||
</a>
|
||||
<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">
|
||||
<a
|
||||
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"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
></a>
|
||||
<a
|
||||
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"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import { Immutable } from "helpers/types";
|
||||
|
||||
interface Props {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export default function SubPanel(props: Props): JSX.Element {
|
||||
export function SubPanel(props: Immutable<Props>): JSX.Element {
|
||||
return (
|
||||
<div className="grid pt-10 pb-20 px-6 desktop:py-8 desktop:px-10 gap-y-2 text-center">
|
||||
{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 {
|
||||
setState:
|
||||
|
@ -8,42 +11,65 @@ interface Props {
|
|||
children: React.ReactNode;
|
||||
fillViewport?: 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 (
|
||||
<div
|
||||
className={`fixed inset-0 z-50 grid place-content-center
|
||||
transition-[backdrop-filter] duration-500 ${
|
||||
props.state
|
||||
? "[backdrop-filter:blur(2px)]"
|
||||
: "pointer-events-none touch-none"
|
||||
}`}
|
||||
<Hotkeys
|
||||
keyName="escape"
|
||||
allowRepeat
|
||||
onKeyDown={() => {
|
||||
setState(false);
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className={`fixed bg-shade inset-0 transition-all duration-500 ${
|
||||
props.state ? "bg-opacity-50" : "bg-opacity-0"
|
||||
}`}
|
||||
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"
|
||||
}`}
|
||||
className={`fixed inset-0 z-50 grid place-content-center
|
||||
transition-[backdrop-filter] duration-500 ${
|
||||
state ? "[backdrop-filter:blur(2px)]" : "pointer-events-none touch-none"
|
||||
}`}
|
||||
>
|
||||
{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>
|
||||
</Hotkeys>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,29 +1,22 @@
|
|||
import { GetPostQuery } from "graphql/generated";
|
||||
import useSmartLanguage from "hooks/useSmartLanguage";
|
||||
import { AppStaticProps } from "queries/getAppStaticProps";
|
||||
import { getStatusDescription, prettySlug } from "queries/helpers";
|
||||
import AppLayout from "./AppLayout";
|
||||
import Chip from "./Chip";
|
||||
import HorizontalLine from "./HorizontalLine";
|
||||
import Markdawn from "./Markdown/Markdawn";
|
||||
import TOC from "./Markdown/TOC";
|
||||
import ReturnButton, { ReturnButtonType } from "./PanelComponents/ReturnButton";
|
||||
import ContentPanel from "./Panels/ContentPanel";
|
||||
import SubPanel from "./Panels/SubPanel";
|
||||
import RecorderChip from "./RecorderChip";
|
||||
import ThumbnailHeader from "./ThumbnailHeader";
|
||||
import ToolTip from "./ToolTip";
|
||||
|
||||
export type Post = Exclude<
|
||||
Exclude<
|
||||
GetPostQuery["posts"],
|
||||
null | undefined
|
||||
>["data"][number]["attributes"],
|
||||
null | undefined
|
||||
>;
|
||||
import { AppStaticProps } from "graphql/getAppStaticProps";
|
||||
import { prettySlug } from "helpers/formatters";
|
||||
import { getStatusDescription } from "helpers/others";
|
||||
import { Immutable, PostWithTranslations } from "helpers/types";
|
||||
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
||||
import { AppLayout } from "./AppLayout";
|
||||
import { Chip } from "./Chip";
|
||||
import { HorizontalLine } from "./HorizontalLine";
|
||||
import { Markdawn } from "./Markdown/Markdawn";
|
||||
import { TOC } from "./Markdown/TOC";
|
||||
import { ReturnButton, ReturnButtonType } from "./PanelComponents/ReturnButton";
|
||||
import { ContentPanel } from "./Panels/ContentPanel";
|
||||
import { SubPanel } from "./Panels/SubPanel";
|
||||
import { RecorderChip } from "./RecorderChip";
|
||||
import { ThumbnailHeader } from "./ThumbnailHeader";
|
||||
import { ToolTip } from "./ToolTip";
|
||||
|
||||
interface Props {
|
||||
post: Post;
|
||||
post: PostWithTranslations;
|
||||
langui: AppStaticProps["langui"];
|
||||
languages: AppStaticProps["languages"];
|
||||
currencies: AppStaticProps["currencies"];
|
||||
|
@ -38,7 +31,7 @@ interface Props {
|
|||
appendBody?: JSX.Element;
|
||||
}
|
||||
|
||||
export default function PostPage(props: Props): JSX.Element {
|
||||
export function PostPage(props: Immutable<Props>): JSX.Element {
|
||||
const {
|
||||
post,
|
||||
langui,
|
||||
|
|
|
@ -4,16 +4,18 @@ import {
|
|||
PricePickerFragment,
|
||||
UploadImageFragment,
|
||||
} from "graphql/generated";
|
||||
import Link from "next/link";
|
||||
import { AppStaticProps } from "queries/getAppStaticProps";
|
||||
import { AppStaticProps } from "graphql/getAppStaticProps";
|
||||
import {
|
||||
prettyDate,
|
||||
prettyDuration,
|
||||
prettyPrice,
|
||||
prettyShortenNumber,
|
||||
} from "queries/helpers";
|
||||
import Chip from "./Chip";
|
||||
import Img, { ImageQuality } from "./Img";
|
||||
} from "helpers/formatters";
|
||||
import { ImageQuality } from "helpers/img";
|
||||
import { Immutable } from "helpers/types";
|
||||
import Link from "next/link";
|
||||
import { Chip } from "./Chip";
|
||||
import { Img } from "./Img";
|
||||
|
||||
interface Props {
|
||||
thumbnail?: UploadImageFragment | string | null | undefined;
|
||||
|
@ -26,6 +28,7 @@ interface Props {
|
|||
topChips?: string[];
|
||||
bottomChips?: string[];
|
||||
keepInfoVisible?: boolean;
|
||||
stackNumber?: number;
|
||||
metadata?: {
|
||||
currencies?: AppStaticProps["currencies"];
|
||||
release_date?: DatePickerFragment | null;
|
||||
|
@ -42,7 +45,7 @@ interface Props {
|
|||
| { __typename: "anotherHoverlayName" };
|
||||
}
|
||||
|
||||
export default function ThumbnailPreview(props: Props): JSX.Element {
|
||||
export function PreviewCard(props: Immutable<Props>): JSX.Element {
|
||||
const {
|
||||
href,
|
||||
thumbnail,
|
||||
|
@ -50,6 +53,7 @@ export default function ThumbnailPreview(props: Props): JSX.Element {
|
|||
title,
|
||||
subtitle,
|
||||
description,
|
||||
stackNumber = 0,
|
||||
topChips,
|
||||
bottomChips,
|
||||
keepInfoVisible,
|
||||
|
@ -110,8 +114,41 @@ export default function ThumbnailPreview(props: Props): JSX.Element {
|
|||
className="drop-shadow-shade-xl cursor-pointer grid items-end
|
||||
fine:[--cover-opacity:0] hover:[--cover-opacity:1] hover:scale-[1.02]
|
||||
[--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 ? (
|
||||
<div className="relative">
|
||||
<Img
|
||||
|
@ -123,6 +160,14 @@ export default function ThumbnailPreview(props: Props): JSX.Element {
|
|||
image={thumbnail}
|
||||
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" && (
|
||||
<>
|
||||
<div
|
||||
|
@ -149,18 +194,26 @@ export default function ThumbnailPreview(props: Props): JSX.Element {
|
|||
) : (
|
||||
<div
|
||||
style={{ aspectRatio: thumbnailAspectRatio }}
|
||||
className={`w-full bg-light ${
|
||||
className={`w-full bg-light relative ${
|
||||
keepInfoVisible
|
||||
? "rounded-t-md"
|
||||
: "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
|
||||
className={`linearbg-obi ${
|
||||
keepInfoVisible
|
||||
? "-mt-[0.3333em]"
|
||||
: `fine:drop-shadow-shade-lg fine:absolute coarse:rounded-b-md
|
||||
!keepInfoVisible &&
|
||||
`fine:drop-shadow-shade-lg fine:absolute coarse:rounded-b-md
|
||||
bottom-2 -inset-x-0.5 opacity-[var(--cover-opacity)]`
|
||||
} transition-opacity z-20 grid p-4 gap-2`}
|
||||
>
|
||||
|
@ -173,11 +226,15 @@ export default function ThumbnailPreview(props: Props): JSX.Element {
|
|||
</div>
|
||||
)}
|
||||
<div className="my-1">
|
||||
{pre_title && <p className="leading-none mb-1">{pre_title}</p>}
|
||||
{title && (
|
||||
<p className="font-headers text-lg leading-none">{title}</p>
|
||||
{pre_title && (
|
||||
<p className="leading-none mb-1 break-words">{pre_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>
|
||||
{description && <p>{description}</p>}
|
||||
{bottomChips && bottomChips.length > 0 && (
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import { UploadImageFragment } from "graphql/generated";
|
||||
import { ImageQuality } from "helpers/img";
|
||||
import { Immutable } from "helpers/types";
|
||||
import Link from "next/link";
|
||||
import Chip from "./Chip";
|
||||
import Img, { ImageQuality } from "./Img";
|
||||
import { Chip } from "./Chip";
|
||||
import { Img } from "./Img";
|
||||
|
||||
interface Props {
|
||||
thumbnail?: UploadImageFragment | string | null | undefined;
|
||||
|
@ -14,7 +16,7 @@ interface Props {
|
|||
bottomChips?: string[];
|
||||
}
|
||||
|
||||
export default function PreviewLine(props: Props): JSX.Element {
|
||||
export function PreviewLine(props: Immutable<Props>): JSX.Element {
|
||||
const {
|
||||
href,
|
||||
thumbnail,
|
||||
|
@ -29,8 +31,9 @@ export default function PreviewLine(props: Props): JSX.Element {
|
|||
return (
|
||||
<Link href={href} passHref>
|
||||
<div
|
||||
className="drop-shadow-shade-xl rounded-md bg-light cursor-pointer hover:scale-[1.02]
|
||||
transition-transform flex flex-row gap-4 overflow-hidden place-items-center pr-4 w-full h-36"
|
||||
className="drop-shadow-shade-xl rounded-md bg-light cursor-pointer
|
||||
hover:scale-[1.02] transition-transform flex flex-row gap-4
|
||||
overflow-hidden place-items-center pr-4 w-full h-36"
|
||||
>
|
||||
{thumbnail ? (
|
||||
<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 { AppStaticProps } from "queries/getAppStaticProps";
|
||||
import Img, { ImageQuality } from "./Img";
|
||||
import Markdawn from "./Markdown/Markdawn";
|
||||
import ToolTip from "./ToolTip";
|
||||
import { AppStaticProps } from "graphql/getAppStaticProps";
|
||||
import { ImageQuality } from "helpers/img";
|
||||
import { Immutable } from "helpers/types";
|
||||
import { Img } from "./Img";
|
||||
import { Markdawn } from "./Markdown/Markdawn";
|
||||
import { ToolTip } from "./ToolTip";
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
|
@ -11,7 +13,7 @@ interface Props {
|
|||
langui: AppStaticProps["langui"];
|
||||
}
|
||||
|
||||
export default function RecorderChip(props: Props): JSX.Element {
|
||||
export function RecorderChip(props: Immutable<Props>): JSX.Element {
|
||||
const { recorder, langui } = props;
|
||||
return (
|
||||
<ToolTip
|
||||
|
@ -49,10 +51,7 @@ export default function RecorderChip(props: Props): JSX.Element {
|
|||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{recorder.bio?.[0] && <Markdawn text={recorder.bio[0].bio ?? ""} />}
|
||||
|
||||
{/* <Button className="cursor-not-allowed">View profile</Button> */}
|
||||
</div>
|
||||
}
|
||||
placement="top"
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { Immutable } from "helpers/types";
|
||||
import Image from "next/image";
|
||||
|
||||
interface Props {
|
||||
|
@ -6,7 +7,7 @@ interface Props {
|
|||
className?: string;
|
||||
}
|
||||
|
||||
export default function SVG(props: Props): JSX.Element {
|
||||
export function SVG(props: Immutable<Props>): JSX.Element {
|
||||
return (
|
||||
<div className={props.className}>
|
||||
<Image
|
||||
|
|
|
@ -1,36 +1,31 @@
|
|||
import Chip from "components/Chip";
|
||||
import Img, { ImageQuality } from "components/Img";
|
||||
import InsetBox from "components/InsetBox";
|
||||
import Markdawn from "components/Markdown/Markdawn";
|
||||
import { GetContentQuery, UploadImageFragment } from "graphql/generated";
|
||||
import { AppStaticProps } from "queries/getAppStaticProps";
|
||||
import { prettyinlineTitle, prettySlug, slugify } from "queries/helpers";
|
||||
import { Chip } from "components/Chip";
|
||||
import { Img } from "components/Img";
|
||||
import { InsetBox } from "components/InsetBox";
|
||||
import { Markdawn } from "components/Markdown/Markdawn";
|
||||
import { GetContentTextQuery, UploadImageFragment } from "graphql/generated";
|
||||
import { AppStaticProps } from "graphql/getAppStaticProps";
|
||||
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 {
|
||||
pre_title?: string | null | undefined;
|
||||
title: string | null | undefined;
|
||||
subtitle?: string | null | undefined;
|
||||
description?: string | null | undefined;
|
||||
type?: Exclude<
|
||||
Exclude<
|
||||
GetContentQuery["contents"],
|
||||
null | undefined
|
||||
>["data"][number]["attributes"],
|
||||
null | undefined
|
||||
type?: NonNullable<
|
||||
NonNullable<GetContentTextQuery["contents"]>["data"][number]["attributes"]
|
||||
>["type"];
|
||||
categories?: Exclude<
|
||||
Exclude<
|
||||
GetContentQuery["contents"],
|
||||
null | undefined
|
||||
>["data"][number]["attributes"],
|
||||
null | undefined
|
||||
categories?: NonNullable<
|
||||
NonNullable<GetContentTextQuery["contents"]>["data"][number]["attributes"]
|
||||
>["categories"];
|
||||
thumbnail?: UploadImageFragment | null | undefined;
|
||||
langui: AppStaticProps["langui"];
|
||||
languageSwitcher?: JSX.Element;
|
||||
}
|
||||
|
||||
export default function ThumbnailHeader(props: Props): JSX.Element {
|
||||
export function ThumbnailHeader(props: Immutable<Props>): JSX.Element {
|
||||
const {
|
||||
langui,
|
||||
pre_title,
|
||||
|
@ -43,16 +38,21 @@ export default function ThumbnailHeader(props: Props): JSX.Element {
|
|||
languageSwitcher,
|
||||
} = props;
|
||||
|
||||
const [openLightBox, LightBox] = useLightBox();
|
||||
|
||||
return (
|
||||
<>
|
||||
<LightBox />
|
||||
<div className="grid place-items-center gap-12 mb-12">
|
||||
<div className="drop-shadow-shade-lg">
|
||||
{thumbnail ? (
|
||||
<Img
|
||||
className=" rounded-xl"
|
||||
className="rounded-xl cursor-pointer"
|
||||
image={thumbnail}
|
||||
quality={ImageQuality.Medium}
|
||||
priority
|
||||
onClick={() => {
|
||||
openLightBox([getAssetURL(thumbnail.url, ImageQuality.Large)]);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<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 {}
|
||||
|
||||
export default function ToolTip(props: Props): JSX.Element {
|
||||
const newProps = { ...props };
|
||||
|
||||
// Set defaults
|
||||
if (newProps.delay === undefined) newProps.delay = [150, 0];
|
||||
if (newProps.interactive === undefined) newProps.interactive = true;
|
||||
if (newProps.animation === undefined) newProps.animation = "scale-subtle";
|
||||
export function ToolTip(props: Props): JSX.Element {
|
||||
const newProps: Props = {
|
||||
delay: [150, 0],
|
||||
interactive: true,
|
||||
animation: "scale-subtle",
|
||||
...props,
|
||||
};
|
||||
|
||||
return (
|
||||
<Tippy className={`text-[80%] ${newProps.className}`} {...newProps}>
|
||||
|
|
|
@ -1,22 +1,20 @@
|
|||
import Chip from "components/Chip";
|
||||
import ToolTip from "components/ToolTip";
|
||||
import { Chip } from "components/Chip";
|
||||
import { ToolTip } from "components/ToolTip";
|
||||
import {
|
||||
Enum_Componenttranslationschronologyitem_Status,
|
||||
GetChronologyItemsQuery,
|
||||
} from "graphql/generated";
|
||||
import { AppStaticProps } from "queries/getAppStaticProps";
|
||||
import { getStatusDescription } from "queries/helpers";
|
||||
import { AppStaticProps } from "graphql/getAppStaticProps";
|
||||
import { getStatusDescription } from "helpers/others";
|
||||
import { Immutable } from "helpers/types";
|
||||
|
||||
interface Props {
|
||||
item: Exclude<
|
||||
GetChronologyItemsQuery["chronologyItems"],
|
||||
null | undefined
|
||||
>["data"][number];
|
||||
item: NonNullable<GetChronologyItemsQuery["chronologyItems"]>["data"][number];
|
||||
displayYear: boolean;
|
||||
langui: AppStaticProps["langui"];
|
||||
}
|
||||
|
||||
export default function ChronologyItemComponent(props: Props): JSX.Element {
|
||||
export function ChronologyItemComponent(props: Immutable<Props>): JSX.Element {
|
||||
const { langui } = props;
|
||||
|
||||
function generateAnchor(
|
||||
|
@ -71,7 +69,8 @@ export default function ChronologyItemComponent(props: Props): JSX.Element {
|
|||
if (props.item.attributes) {
|
||||
return (
|
||||
<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(
|
||||
props.item.attributes.year,
|
||||
props.item.attributes.month,
|
||||
|
@ -100,7 +99,10 @@ export default function ChronologyItemComponent(props: Props): JSX.Element {
|
|||
<>
|
||||
{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 !==
|
||||
Enum_Componenttranslationschronologyitem_Status.Done && (
|
||||
<ToolTip
|
||||
|
@ -125,7 +127,8 @@ export default function ChronologyItemComponent(props: Props): JSX.Element {
|
|||
className={
|
||||
event.translations &&
|
||||
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"
|
||||
}
|
||||
>
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
import ChronologyItemComponent from "components/Wiki/Chronology/ChronologyItemComponent";
|
||||
import { ChronologyItemComponent } from "components/Wiki/Chronology/ChronologyItemComponent";
|
||||
import { GetChronologyItemsQuery } from "graphql/generated";
|
||||
import { AppStaticProps } from "queries/getAppStaticProps";
|
||||
import { AppStaticProps } from "graphql/getAppStaticProps";
|
||||
import { Immutable } from "helpers/types";
|
||||
|
||||
interface Props {
|
||||
year: number;
|
||||
items: Exclude<
|
||||
GetChronologyItemsQuery["chronologyItems"],
|
||||
null | undefined
|
||||
items: NonNullable<
|
||||
GetChronologyItemsQuery["chronologyItems"]
|
||||
>["data"][number][];
|
||||
langui: AppStaticProps["langui"];
|
||||
}
|
||||
|
||||
export default function ChronologyYearComponent(props: Props): JSX.Element {
|
||||
export function ChronologyYearComponent(props: Immutable<Props>): JSX.Element {
|
||||
const { langui } = props;
|
||||
|
||||
return (
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import useDarkMode from "hooks/useDarkMode";
|
||||
import useStateWithLocalStorage from "hooks/useStateWithLocalStorage";
|
||||
import React, { ReactNode, useContext } from "react";
|
||||
import { Immutable } from "helpers/types";
|
||||
import { useDarkMode } from "hooks/useDarkMode";
|
||||
import { useStateWithLocalStorage } from "hooks/useStateWithLocalStorage";
|
||||
import React, { ReactNode, useContext, useState } from "react";
|
||||
|
||||
interface AppLayoutState {
|
||||
subPanelOpen: boolean | undefined;
|
||||
|
@ -14,6 +15,7 @@ interface AppLayoutState {
|
|||
currency: string | undefined;
|
||||
playerName: string | undefined;
|
||||
preferredLanguages: string[] | undefined;
|
||||
menuGestures: boolean;
|
||||
setSubPanelOpen: React.Dispatch<React.SetStateAction<boolean | undefined>>;
|
||||
setConfigPanelOpen: React.Dispatch<React.SetStateAction<boolean | undefined>>;
|
||||
setMainPanelReduced: React.Dispatch<
|
||||
|
@ -31,6 +33,7 @@ interface AppLayoutState {
|
|||
setPreferredLanguages: React.Dispatch<
|
||||
React.SetStateAction<string[] | undefined>
|
||||
>;
|
||||
setMenuGestures: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
}
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-empty-function */
|
||||
|
@ -46,6 +49,7 @@ const initialState: AppLayoutState = {
|
|||
currency: "USD",
|
||||
playerName: "",
|
||||
preferredLanguages: [],
|
||||
menuGestures: true,
|
||||
setSubPanelOpen: () => {},
|
||||
setMainPanelReduced: () => {},
|
||||
setMainPanelOpen: () => {},
|
||||
|
@ -57,6 +61,7 @@ const initialState: AppLayoutState = {
|
|||
setCurrency: () => {},
|
||||
setPlayerName: () => {},
|
||||
setPreferredLanguages: () => {},
|
||||
setMenuGestures: () => {},
|
||||
};
|
||||
/* eslint-enable @typescript-eslint/no-empty-function */
|
||||
|
||||
|
@ -72,7 +77,7 @@ interface Props {
|
|||
children: ReactNode;
|
||||
}
|
||||
|
||||
export function AppContextProvider(props: Props): JSX.Element {
|
||||
export function AppContextProvider(props: Immutable<Props>): JSX.Element {
|
||||
const [subPanelOpen, setSubPanelOpen] = useStateWithLocalStorage<
|
||||
boolean | undefined
|
||||
>("subPanelOpen", initialState.subPanelOpen);
|
||||
|
@ -115,6 +120,8 @@ export function AppContextProvider(props: Props): JSX.Element {
|
|||
string[] | undefined
|
||||
>("preferredLanguages", initialState.preferredLanguages);
|
||||
|
||||
const [menuGestures, setMenuGestures] = useState(false);
|
||||
|
||||
return (
|
||||
<AppContext.Provider
|
||||
value={{
|
||||
|
@ -129,6 +136,7 @@ export function AppContextProvider(props: Props): JSX.Element {
|
|||
currency,
|
||||
playerName,
|
||||
preferredLanguages,
|
||||
menuGestures,
|
||||
setSubPanelOpen,
|
||||
setConfigPanelOpen,
|
||||
setMainPanelReduced,
|
||||
|
@ -140,6 +148,7 @@ export function AppContextProvider(props: Props): JSX.Element {
|
|||
setCurrency,
|
||||
setPlayerName,
|
||||
setPreferredLanguages,
|
||||
setMenuGestures,
|
||||
}}
|
||||
>
|
||||
{props.children}
|
||||
|
|
|
@ -4,22 +4,18 @@ import {
|
|||
GetWebsiteInterfaceQuery,
|
||||
} from "graphql/generated";
|
||||
import { getReadySdk } from "graphql/sdk";
|
||||
import { Immutable } from "helpers/types";
|
||||
import { GetStaticPropsContext } from "next";
|
||||
|
||||
export interface AppStaticProps {
|
||||
langui: Exclude<
|
||||
Exclude<
|
||||
GetWebsiteInterfaceQuery["websiteInterfaces"],
|
||||
null | undefined
|
||||
>["data"][number]["attributes"],
|
||||
null | undefined
|
||||
export type AppStaticProps = Immutable<{
|
||||
langui: NonNullable<
|
||||
NonNullable<
|
||||
GetWebsiteInterfaceQuery["websiteInterfaces"]
|
||||
>["data"][number]["attributes"]
|
||||
>;
|
||||
currencies: Exclude<
|
||||
GetCurrenciesQuery["currencies"],
|
||||
null | undefined
|
||||
>["data"];
|
||||
languages: Exclude<GetLanguagesQuery["languages"], null | undefined>["data"];
|
||||
}
|
||||
currencies: NonNullable<GetCurrenciesQuery["currencies"]>["data"];
|
||||
languages: NonNullable<GetLanguagesQuery["languages"]>["data"];
|
||||
}>;
|
||||
|
||||
export async function getAppStaticProps(
|
||||
context: GetStaticPropsContext
|
||||
|
@ -50,9 +46,11 @@ export async function getAppStaticProps(
|
|||
})
|
||||
).websiteInterfaces?.data[0].attributes;
|
||||
|
||||
return {
|
||||
langui: langui ?? ({} as AppStaticProps["langui"]),
|
||||
currencies: currencies?.data ?? ({} as AppStaticProps["currencies"]),
|
||||
languages: languages?.data ?? ({} as AppStaticProps["languages"]),
|
||||
const appStaticProps: AppStaticProps = {
|
||||
langui: langui ?? {},
|
||||
currencies: currencies?.data ?? [],
|
||||
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
|
||||
}
|
||||
}
|
||||
titles {
|
||||
|
||||
ranged_contents {
|
||||
data {
|
||||
id
|
||||
}
|
||||
}
|
||||
translations {
|
||||
language {
|
||||
data {
|
||||
id
|
||||
|
@ -22,62 +28,36 @@ query devGetContents {
|
|||
}
|
||||
title
|
||||
description
|
||||
}
|
||||
ranged_contents {
|
||||
data {
|
||||
id
|
||||
}
|
||||
}
|
||||
text_set {
|
||||
language {
|
||||
data {
|
||||
id
|
||||
text_set {
|
||||
source_language {
|
||||
data {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
source_language {
|
||||
data {
|
||||
id
|
||||
status
|
||||
transcribers {
|
||||
data {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
status
|
||||
transcribers {
|
||||
data {
|
||||
id
|
||||
translators {
|
||||
data {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
translators {
|
||||
data {
|
||||
id
|
||||
proofreaders {
|
||||
data {
|
||||
id
|
||||
}
|
||||
}
|
||||
text
|
||||
}
|
||||
proofreaders {
|
||||
data {
|
||||
id
|
||||
}
|
||||
}
|
||||
text
|
||||
}
|
||||
video_set {
|
||||
id
|
||||
}
|
||||
audio_set {
|
||||
id
|
||||
}
|
||||
thumbnail {
|
||||
data {
|
||||
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
|
||||
attributes {
|
||||
slug
|
||||
titles {
|
||||
pre_title
|
||||
title
|
||||
subtitle
|
||||
description
|
||||
}
|
||||
|
||||
categories {
|
||||
data {
|
||||
id
|
||||
|
@ -56,9 +51,7 @@ query getContentText($slug: String, $language_code: String) {
|
|||
}
|
||||
}
|
||||
}
|
||||
text_set {
|
||||
status
|
||||
text
|
||||
translations {
|
||||
language {
|
||||
data {
|
||||
attributes {
|
||||
|
@ -66,39 +59,48 @@ query getContentText($slug: String, $language_code: String) {
|
|||
}
|
||||
}
|
||||
}
|
||||
source_language {
|
||||
data {
|
||||
attributes {
|
||||
code
|
||||
pre_title
|
||||
title
|
||||
subtitle
|
||||
description
|
||||
text_set {
|
||||
status
|
||||
text
|
||||
source_language {
|
||||
data {
|
||||
attributes {
|
||||
code
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
transcribers {
|
||||
data {
|
||||
id
|
||||
attributes {
|
||||
...recorderChip
|
||||
transcribers {
|
||||
data {
|
||||
id
|
||||
attributes {
|
||||
...recorderChip
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
translators {
|
||||
data {
|
||||
id
|
||||
attributes {
|
||||
...recorderChip
|
||||
translators {
|
||||
data {
|
||||
id
|
||||
attributes {
|
||||
...recorderChip
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
proofreaders {
|
||||
data {
|
||||
id
|
||||
attributes {
|
||||
...recorderChip
|
||||
proofreaders {
|
||||
data {
|
||||
id
|
||||
attributes {
|
||||
...recorderChip
|
||||
}
|
||||
}
|
||||
}
|
||||
notes
|
||||
}
|
||||
notes
|
||||
}
|
||||
|
||||
thumbnail {
|
||||
data {
|
||||
attributes {
|
||||
|
@ -106,78 +108,47 @@ query getContentText($slug: String, $language_code: String) {
|
|||
}
|
||||
}
|
||||
}
|
||||
previous_recommended {
|
||||
group {
|
||||
data {
|
||||
attributes {
|
||||
slug
|
||||
titles(filters: { language: { code: { eq: $language_code } } }) {
|
||||
pre_title
|
||||
title
|
||||
subtitle
|
||||
}
|
||||
categories {
|
||||
data {
|
||||
id
|
||||
attributes {
|
||||
short
|
||||
}
|
||||
}
|
||||
}
|
||||
type {
|
||||
contents {
|
||||
data {
|
||||
attributes {
|
||||
slug
|
||||
titles(
|
||||
filters: { language: { code: { eq: $language_code } } }
|
||||
) {
|
||||
translations {
|
||||
pre_title
|
||||
title
|
||||
subtitle
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
thumbnail {
|
||||
data {
|
||||
attributes {
|
||||
...uploadImage
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
next_recommended {
|
||||
data {
|
||||
attributes {
|
||||
slug
|
||||
titles(filters: { language: { code: { eq: $language_code } } }) {
|
||||
pre_title
|
||||
title
|
||||
subtitle
|
||||
}
|
||||
categories {
|
||||
data {
|
||||
id
|
||||
attributes {
|
||||
short
|
||||
}
|
||||
}
|
||||
}
|
||||
type {
|
||||
data {
|
||||
attributes {
|
||||
slug
|
||||
titles(
|
||||
filters: { language: { code: { eq: $language_code } } }
|
||||
) {
|
||||
title
|
||||
categories {
|
||||
data {
|
||||
id
|
||||
attributes {
|
||||
short
|
||||
}
|
||||
}
|
||||
}
|
||||
type {
|
||||
data {
|
||||
attributes {
|
||||
slug
|
||||
titles(
|
||||
filters: {
|
||||
language: { code: { eq: $language_code } }
|
||||
}
|
||||
) {
|
||||
title
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
thumbnail {
|
||||
data {
|
||||
attributes {
|
||||
...uploadImage
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
thumbnail {
|
||||
data {
|
||||
attributes {
|
||||
...uploadImage
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ query getContents($language_code: String) {
|
|||
id
|
||||
attributes {
|
||||
slug
|
||||
titles(filters: { language: { code: { eq: $language_code } } }) {
|
||||
translations {
|
||||
pre_title
|
||||
title
|
||||
subtitle
|
||||
|
@ -55,14 +55,17 @@ query getContents($language_code: String) {
|
|||
}
|
||||
}
|
||||
}
|
||||
text_set {
|
||||
id
|
||||
}
|
||||
video_set {
|
||||
id
|
||||
}
|
||||
audio_set {
|
||||
id
|
||||
group {
|
||||
data {
|
||||
attributes {
|
||||
combine
|
||||
contents {
|
||||
data {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
thumbnail {
|
||||
data {
|
||||
|
|
|
@ -362,22 +362,18 @@ query getLibraryItem($slug: String, $language_code: String) {
|
|||
}
|
||||
}
|
||||
}
|
||||
titles(
|
||||
filters: { language: { code: { eq: $language_code } } }
|
||||
) {
|
||||
translations {
|
||||
language {
|
||||
data {
|
||||
attributes {
|
||||
code
|
||||
}
|
||||
}
|
||||
}
|
||||
pre_title
|
||||
title
|
||||
subtitle
|
||||
}
|
||||
text_set {
|
||||
id
|
||||
}
|
||||
video_set {
|
||||
id
|
||||
}
|
||||
audio_set {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -132,6 +132,19 @@ query getWebsiteInterface($language_code: String) {
|
|||
response_invalid_code
|
||||
response_invalid_email
|
||||
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 {
|
||||
getAssetURL,
|
||||
getImgSizesByQuality,
|
||||
ImageQuality,
|
||||
} from "components/Img";
|
||||
import {
|
||||
DatePickerFragment,
|
||||
Enum_Componentsetstextset_Status,
|
||||
GetCurrenciesQuery,
|
||||
GetLibraryItemQuery,
|
||||
GetLibraryItemScansQuery,
|
||||
PricePickerFragment,
|
||||
UploadImageFragment,
|
||||
} from "graphql/generated";
|
||||
import { AppStaticProps } from "./getAppStaticProps";
|
||||
import { DatePickerFragment, PricePickerFragment } from "graphql/generated";
|
||||
import { AppStaticProps } from "../graphql/getAppStaticProps";
|
||||
import { convertPrice } from "./numbers";
|
||||
import { Immutable } from "./types";
|
||||
|
||||
export function prettyDate(datePicker: DatePickerFragment): string {
|
||||
export function prettyDate(datePicker: Immutable<DatePickerFragment>): string {
|
||||
let result = "";
|
||||
if (datePicker.year) result += datePicker.year.toString();
|
||||
if (datePicker.month)
|
||||
|
@ -25,7 +14,7 @@ export function prettyDate(datePicker: DatePickerFragment): string {
|
|||
}
|
||||
|
||||
export function prettyPrice(
|
||||
pricePicker: PricePickerFragment,
|
||||
pricePicker: Immutable<PricePickerFragment>,
|
||||
currencies: AppStaticProps["currencies"],
|
||||
targetCurrencyCode?: string
|
||||
): string {
|
||||
|
@ -45,25 +34,6 @@ export function prettyPrice(
|
|||
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 {
|
||||
if (slug) {
|
||||
if (parentSlug && slug.startsWith(parentSlug))
|
||||
|
@ -88,7 +58,7 @@ export function prettyinlineTitle(
|
|||
}
|
||||
|
||||
export function prettyItemType(
|
||||
metadata: any,
|
||||
metadata: Immutable<any>,
|
||||
langui: AppStaticProps["langui"]
|
||||
): string | undefined | null {
|
||||
switch (metadata.__typename) {
|
||||
|
@ -110,7 +80,7 @@ export function prettyItemType(
|
|||
}
|
||||
|
||||
export function prettyItemSubType(
|
||||
metadata:
|
||||
metadata: Immutable<
|
||||
| {
|
||||
__typename: "ComponentMetadataAudio";
|
||||
subtype?: {
|
||||
|
@ -187,6 +157,7 @@ export function prettyItemSubType(
|
|||
}
|
||||
| { __typename: "Error" }
|
||||
| null
|
||||
>
|
||||
): string {
|
||||
if (metadata) {
|
||||
switch (metadata.__typename) {
|
||||
|
@ -300,87 +271,6 @@ export function capitalizeString(string: string): string {
|
|||
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 {
|
||||
if (!string) {
|
||||
return "";
|
||||
|
@ -400,51 +290,3 @@ export function slugify(string: string | undefined): string {
|
|||
.trim()
|
||||
.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 { usePrefersDarkMode } from "./useMediaQuery";
|
||||
import useStateWithLocalStorage from "./useStateWithLocalStorage";
|
||||
import { useStateWithLocalStorage } from "./useStateWithLocalStorage";
|
||||
|
||||
export default function useDarkMode(
|
||||
export function useDarkMode(
|
||||
key: string,
|
||||
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";
|
||||
|
||||
export default function useMediaQuery(query: string): boolean {
|
||||
export function useMediaQuery(query: string): boolean {
|
||||
function getMatches(query: string): boolean {
|
||||
// Prevents SSR issues
|
||||
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 { AppStaticProps } from "graphql/getAppStaticProps";
|
||||
import { Immutable } from "helpers/types";
|
||||
import { useRouter } from "next/router";
|
||||
import { AppStaticProps } from "queries/getAppStaticProps";
|
||||
import { getPreferredLanguage } from "queries/helpers";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
|
||||
interface Props<T> {
|
||||
items: T[];
|
||||
items: Immutable<T[]>;
|
||||
languages: AppStaticProps["languages"];
|
||||
languageExtractor: (item: T) => string | undefined;
|
||||
transform?: (item: T) => T;
|
||||
languageExtractor: (item: NonNullable<Immutable<T>>) => string | undefined;
|
||||
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>
|
||||
): [T | undefined, () => JSX.Element] {
|
||||
): [Immutable<T | undefined>, () => JSX.Element] {
|
||||
const {
|
||||
items,
|
||||
languageExtractor,
|
||||
|
@ -28,12 +40,15 @@ export default function useSmartLanguage<T>(
|
|||
const [selectedTranslationIndex, setSelectedTranslationIndex] = useState<
|
||||
number | undefined
|
||||
>();
|
||||
const [selectedTranslation, setSelectedTranslation] = useState<T>();
|
||||
const [selectedTranslation, setSelectedTranslation] =
|
||||
useState<Immutable<T>>();
|
||||
|
||||
useEffect(() => {
|
||||
items.map((elem, index) => {
|
||||
const result = languageExtractor(elem);
|
||||
if (result !== undefined) availableLocales.set(result, index);
|
||||
if (elem !== null && elem !== undefined) {
|
||||
const result = languageExtractor(elem);
|
||||
if (result !== undefined) availableLocales.set(result, index);
|
||||
}
|
||||
});
|
||||
}, [availableLocales, items, languageExtractor]);
|
||||
|
||||
|
@ -47,8 +62,9 @@ export default function useSmartLanguage<T>(
|
|||
}, [appLayout.preferredLanguages, availableLocales, router.locale]);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedTranslationIndex !== undefined)
|
||||
if (selectedTranslationIndex !== undefined) {
|
||||
setSelectedTranslation(transform(items[selectedTranslationIndex]));
|
||||
}
|
||||
}, [items, selectedTranslationIndex, transform]);
|
||||
|
||||
return [
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { useEffect, useState } from "react";
|
||||
|
||||
export default function useStateWithLocalStorage<T>(
|
||||
export function useStateWithLocalStorage<T>(
|
||||
key: string,
|
||||
initialValue: T
|
||||
): [T | undefined, React.Dispatch<React.SetStateAction<T | undefined>>] {
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
import AppLayout from "components/AppLayout";
|
||||
import ReturnButton, {
|
||||
import { AppLayout } from "components/AppLayout";
|
||||
import {
|
||||
ReturnButton,
|
||||
ReturnButtonType,
|
||||
} 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 { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
||||
|
||||
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 contentPanel = (
|
||||
<ContentPanel>
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
import AppLayout from "components/AppLayout";
|
||||
import ReturnButton, {
|
||||
import { AppLayout } from "components/AppLayout";
|
||||
import {
|
||||
ReturnButton,
|
||||
ReturnButtonType,
|
||||
} 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 { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
||||
|
||||
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 contentPanel = (
|
||||
<ContentPanel>
|
||||
|
|
|
@ -6,7 +6,7 @@ import Document, {
|
|||
NextScript,
|
||||
} from "next/document";
|
||||
|
||||
class MyDocument extends Document {
|
||||
export default class MyDocument extends Document {
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
static async getInitialProps(ctx: DocumentContext) {
|
||||
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 { getReadySdk } from "graphql/sdk";
|
||||
import { GetStaticPropsContext } from "next";
|
||||
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
||||
import { PostPage } from "components/PostPage";
|
||||
import {
|
||||
getPostStaticProps,
|
||||
PostStaticProps,
|
||||
} from "graphql/getPostStaticProps";
|
||||
import { Immutable } from "helpers/types";
|
||||
|
||||
interface Props extends AppStaticProps {
|
||||
post: Post;
|
||||
}
|
||||
|
||||
export default function AccordsHandbook(props: Props): JSX.Element {
|
||||
export default function AccordsHandbook(
|
||||
props: Immutable<PostStaticProps>
|
||||
): JSX.Element {
|
||||
const { post, langui, languages, currencies } = props;
|
||||
return (
|
||||
<PostPage
|
||||
|
@ -23,21 +23,4 @@ export default function AccordsHandbook(props: Props): JSX.Element {
|
|||
);
|
||||
}
|
||||
|
||||
export async function getStaticProps(
|
||||
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,
|
||||
};
|
||||
}
|
||||
export const getStaticProps = getPostStaticProps("accords-handbook");
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
import InsetBox from "components/InsetBox";
|
||||
import PostPage, { Post } from "components/PostPage";
|
||||
import { getReadySdk } from "graphql/sdk";
|
||||
import { GetStaticPropsContext } from "next";
|
||||
import { InsetBox } from "components/InsetBox";
|
||||
import { PostPage } from "components/PostPage";
|
||||
import {
|
||||
getPostStaticProps,
|
||||
PostStaticProps,
|
||||
} from "graphql/getPostStaticProps";
|
||||
import { randomInt } from "helpers/numbers";
|
||||
import { Immutable } from "helpers/types";
|
||||
import { useRouter } from "next/router";
|
||||
import { RequestMailProps, ResponseMailProps } from "pages/api/mail";
|
||||
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
||||
import { randomInt } from "queries/helpers";
|
||||
import { useState } from "react";
|
||||
|
||||
interface Props extends AppStaticProps {
|
||||
post: Post;
|
||||
}
|
||||
|
||||
export default function AboutUs(props: Props): JSX.Element {
|
||||
export default function AboutUs(
|
||||
props: Immutable<PostStaticProps>
|
||||
): JSX.Element {
|
||||
const { post, langui, languages, currencies } = props;
|
||||
|
||||
const router = useRouter();
|
||||
|
@ -181,21 +181,4 @@ export default function AboutUs(props: Props): JSX.Element {
|
|||
);
|
||||
}
|
||||
|
||||
export async function getStaticProps(
|
||||
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,
|
||||
};
|
||||
}
|
||||
export const getStaticProps = getPostStaticProps("contact");
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
import AppLayout from "components/AppLayout";
|
||||
import NavOption from "components/PanelComponents/NavOption";
|
||||
import PanelHeader from "components/PanelComponents/PanelHeader";
|
||||
import SubPanel from "components/Panels/SubPanel";
|
||||
import { AppLayout } from "components/AppLayout";
|
||||
import { NavOption } from "components/PanelComponents/NavOption";
|
||||
import { PanelHeader } from "components/PanelComponents/PanelHeader";
|
||||
import { SubPanel } from "components/Panels/SubPanel";
|
||||
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
|
||||
import { Immutable } from "helpers/types";
|
||||
import { GetStaticPropsContext } from "next";
|
||||
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
||||
|
||||
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 subPanel = (
|
||||
<SubPanel>
|
||||
|
|
|
@ -1,13 +1,10 @@
|
|||
import PostPage, { Post } from "components/PostPage";
|
||||
import { getReadySdk } from "graphql/sdk";
|
||||
import { GetStaticPropsContext } from "next";
|
||||
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
||||
import { PostPage } from "components/PostPage";
|
||||
import {
|
||||
getPostStaticProps,
|
||||
PostStaticProps,
|
||||
} from "graphql/getPostStaticProps";
|
||||
|
||||
interface Props extends AppStaticProps {
|
||||
post: Post;
|
||||
}
|
||||
|
||||
export default function SiteInformation(props: Props): JSX.Element {
|
||||
export default function Legality(props: PostStaticProps): JSX.Element {
|
||||
const { post, langui, languages, currencies } = props;
|
||||
return (
|
||||
<PostPage
|
||||
|
@ -23,21 +20,4 @@ export default function SiteInformation(props: Props): JSX.Element {
|
|||
);
|
||||
}
|
||||
|
||||
export async function getStaticProps(
|
||||
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,
|
||||
};
|
||||
}
|
||||
export const getStaticProps = getPostStaticProps("legality");
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
import PostPage, { Post } from "components/PostPage";
|
||||
import { getReadySdk } from "graphql/sdk";
|
||||
import { GetStaticPropsContext } from "next";
|
||||
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
||||
import { PostPage } from "components/PostPage";
|
||||
import {
|
||||
getPostStaticProps,
|
||||
PostStaticProps,
|
||||
} from "graphql/getPostStaticProps";
|
||||
|
||||
interface Props extends AppStaticProps {
|
||||
post: Post;
|
||||
}
|
||||
export default function SharingPolicy(props: Props): JSX.Element {
|
||||
export default function SharingPolicy(props: PostStaticProps): JSX.Element {
|
||||
const { post, langui, languages, currencies } = props;
|
||||
return (
|
||||
<PostPage
|
||||
|
@ -22,21 +20,4 @@ export default function SharingPolicy(props: Props): JSX.Element {
|
|||
);
|
||||
}
|
||||
|
||||
export async function getStaticProps(
|
||||
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,
|
||||
};
|
||||
}
|
||||
export const getStaticProps = getPostStaticProps("sharing-policy");
|
||||
|
|
|
@ -14,7 +14,7 @@ export interface RequestMailProps {
|
|||
formName: string;
|
||||
}
|
||||
|
||||
export default async function Mail(
|
||||
export async function Mail(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse<ResponseMailProps>
|
||||
) {
|
||||
|
|
|
@ -70,7 +70,7 @@ type ResponseMailProps = {
|
|||
revalidated: boolean;
|
||||
};
|
||||
|
||||
export default async function Mail(
|
||||
export async function Mail(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse<ResponseMailProps>
|
||||
) {
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
import AppLayout from "components/AppLayout";
|
||||
import NavOption from "components/PanelComponents/NavOption";
|
||||
import PanelHeader from "components/PanelComponents/PanelHeader";
|
||||
import SubPanel from "components/Panels/SubPanel";
|
||||
import { AppLayout } from "components/AppLayout";
|
||||
import { NavOption } from "components/PanelComponents/NavOption";
|
||||
import { PanelHeader } from "components/PanelComponents/PanelHeader";
|
||||
import { SubPanel } from "components/Panels/SubPanel";
|
||||
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
|
||||
import { Immutable } from "helpers/types";
|
||||
import { GetStaticPropsContext } from "next";
|
||||
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
||||
|
||||
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 subPanel = (
|
||||
<SubPanel>
|
||||
|
|
|
@ -1,29 +1,30 @@
|
|||
import AppLayout from "components/AppLayout";
|
||||
import Switch from "components/Inputs/Switch";
|
||||
import PanelHeader from "components/PanelComponents/PanelHeader";
|
||||
import ReturnButton, {
|
||||
import { AppLayout } from "components/AppLayout";
|
||||
import { Switch } from "components/Inputs/Switch";
|
||||
import { PanelHeader } from "components/PanelComponents/PanelHeader";
|
||||
import {
|
||||
ReturnButton,
|
||||
ReturnButtonType,
|
||||
} from "components/PanelComponents/ReturnButton";
|
||||
import ContentPanel, {
|
||||
import {
|
||||
ContentPanel,
|
||||
ContentPanelWidthSizes,
|
||||
} from "components/Panels/ContentPanel";
|
||||
import SubPanel from "components/Panels/SubPanel";
|
||||
import ThumbnailPreview from "components/PreviewCard";
|
||||
import { SubPanel } from "components/Panels/SubPanel";
|
||||
import { PreviewCard } from "components/PreviewCard";
|
||||
import { GetVideoChannelQuery } from "graphql/generated";
|
||||
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
|
||||
import { getReadySdk } from "graphql/sdk";
|
||||
import { getVideoThumbnailURL } from "helpers/videos";
|
||||
import {
|
||||
GetStaticPathsContext,
|
||||
GetStaticPathsResult,
|
||||
GetStaticPropsContext,
|
||||
} from "next";
|
||||
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
||||
import { getVideoThumbnailURL } from "queries/helpers";
|
||||
import { useState } from "react";
|
||||
|
||||
interface Props extends AppStaticProps {
|
||||
channel: Exclude<
|
||||
GetVideoChannelQuery["videoChannels"],
|
||||
null | undefined
|
||||
channel: NonNullable<
|
||||
GetVideoChannelQuery["videoChannels"]
|
||||
>["data"][number]["attributes"];
|
||||
}
|
||||
|
||||
|
@ -35,7 +36,7 @@ export default function Channel(props: Props): JSX.Element {
|
|||
<SubPanel>
|
||||
<ReturnButton
|
||||
href="/archives/videos/"
|
||||
title={"Videos"}
|
||||
title={langui.videos}
|
||||
langui={langui}
|
||||
displayOn={ReturnButtonType.desktop}
|
||||
className="mb-10"
|
||||
|
@ -43,12 +44,12 @@ export default function Channel(props: Props): JSX.Element {
|
|||
|
||||
<PanelHeader
|
||||
icon="movie"
|
||||
title="Videos"
|
||||
title={langui.videos}
|
||||
description={langui.archives_description}
|
||||
/>
|
||||
|
||||
<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} />
|
||||
</div>
|
||||
</SubPanel>
|
||||
|
@ -60,11 +61,15 @@ export default function Channel(props: Props): JSX.Element {
|
|||
<h1 className="text-3xl">{channel?.title}</h1>
|
||||
<p>{channel?.subscribers.toLocaleString()} subscribers</p>
|
||||
</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) => (
|
||||
<>
|
||||
{video.attributes && (
|
||||
<ThumbnailPreview
|
||||
<PreviewCard
|
||||
key={video.id}
|
||||
href={`/archives/videos/v/${video.attributes.uid}`}
|
||||
title={video.attributes.title}
|
||||
|
|
|
@ -1,24 +1,27 @@
|
|||
import AppLayout from "components/AppLayout";
|
||||
import PageSelector from "components/Inputs/PageSelector";
|
||||
import Switch from "components/Inputs/Switch";
|
||||
import PanelHeader from "components/PanelComponents/PanelHeader";
|
||||
import ReturnButton, {
|
||||
import { AppLayout } from "components/AppLayout";
|
||||
import { PageSelector } from "components/Inputs/PageSelector";
|
||||
import { Switch } from "components/Inputs/Switch";
|
||||
import { PanelHeader } from "components/PanelComponents/PanelHeader";
|
||||
import {
|
||||
ReturnButton,
|
||||
ReturnButtonType,
|
||||
} from "components/PanelComponents/ReturnButton";
|
||||
import ContentPanel, {
|
||||
import {
|
||||
ContentPanel,
|
||||
ContentPanelWidthSizes,
|
||||
} from "components/Panels/ContentPanel";
|
||||
import SubPanel from "components/Panels/SubPanel";
|
||||
import ThumbnailPreview from "components/PreviewCard";
|
||||
import { SubPanel } from "components/Panels/SubPanel";
|
||||
import { PreviewCard } from "components/PreviewCard";
|
||||
import { GetVideosPreviewQuery } from "graphql/generated";
|
||||
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
|
||||
import { getReadySdk } from "graphql/sdk";
|
||||
import { prettyDate } from "helpers/formatters";
|
||||
import { getVideoThumbnailURL } from "helpers/videos";
|
||||
import { GetStaticPropsContext } from "next";
|
||||
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
||||
import { getVideoThumbnailURL, prettyDate } from "queries/helpers";
|
||||
import { useState } from "react";
|
||||
|
||||
interface Props extends AppStaticProps {
|
||||
videos: Exclude<GetVideosPreviewQuery["videos"], null | undefined>["data"];
|
||||
videos: NonNullable<GetVideosPreviewQuery["videos"]>["data"];
|
||||
}
|
||||
|
||||
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">
|
||||
<p className="flex-shrink-0">{"Always show info"}:</p>
|
||||
<p className="flex-shrink-0">{langui.always_show_info}:</p>
|
||||
<Switch setState={setKeepInfoVisible} state={keepInfoVisible} />
|
||||
</div>
|
||||
</SubPanel>
|
||||
|
@ -79,11 +82,15 @@ export default function Videos(props: Props): JSX.Element {
|
|||
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) => (
|
||||
<>
|
||||
{video.attributes && (
|
||||
<ThumbnailPreview
|
||||
<PreviewCard
|
||||
key={video.id}
|
||||
href={`/archives/videos/v/${video.attributes.uid}`}
|
||||
title={video.attributes.title}
|
||||
|
|
|
@ -1,34 +1,33 @@
|
|||
import AppLayout from "components/AppLayout";
|
||||
import HorizontalLine from "components/HorizontalLine";
|
||||
import Button from "components/Inputs/Button";
|
||||
import InsetBox from "components/InsetBox";
|
||||
import NavOption from "components/PanelComponents/NavOption";
|
||||
import ReturnButton, {
|
||||
import { AppLayout } from "components/AppLayout";
|
||||
import { HorizontalLine } from "components/HorizontalLine";
|
||||
import { Button } from "components/Inputs/Button";
|
||||
import { InsetBox } from "components/InsetBox";
|
||||
import { NavOption } from "components/PanelComponents/NavOption";
|
||||
import {
|
||||
ReturnButton,
|
||||
ReturnButtonType,
|
||||
} from "components/PanelComponents/ReturnButton";
|
||||
import ContentPanel, {
|
||||
import {
|
||||
ContentPanel,
|
||||
ContentPanelWidthSizes,
|
||||
} from "components/Panels/ContentPanel";
|
||||
import SubPanel from "components/Panels/SubPanel";
|
||||
import { SubPanel } from "components/Panels/SubPanel";
|
||||
import { useAppLayout } from "contexts/AppLayoutContext";
|
||||
import { GetVideoQuery } from "graphql/generated";
|
||||
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
|
||||
import { getReadySdk } from "graphql/sdk";
|
||||
import { prettyDate, prettyShortenNumber } from "helpers/formatters";
|
||||
import { getVideoFile } from "helpers/videos";
|
||||
import { useMediaMobile } from "hooks/useMediaQuery";
|
||||
import {
|
||||
GetStaticPathsContext,
|
||||
GetStaticPathsResult,
|
||||
GetStaticPropsContext,
|
||||
} from "next";
|
||||
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
||||
import { getVideoFile, prettyDate, prettyShortenNumber } from "queries/helpers";
|
||||
|
||||
interface Props extends AppStaticProps {
|
||||
video: Exclude<
|
||||
Exclude<
|
||||
GetVideoQuery["videos"],
|
||||
null | undefined
|
||||
>["data"][number]["attributes"],
|
||||
null | undefined
|
||||
video: NonNullable<
|
||||
NonNullable<GetVideoQuery["videos"]>["data"][number]["attributes"]
|
||||
>;
|
||||
}
|
||||
|
||||
|
@ -40,7 +39,7 @@ export default function Video(props: Props): JSX.Element {
|
|||
<SubPanel>
|
||||
<ReturnButton
|
||||
href="/archives/videos/"
|
||||
title={"Videos"}
|
||||
title={langui.videos}
|
||||
langui={langui}
|
||||
displayOn={ReturnButtonType.desktop}
|
||||
className="mb-10"
|
||||
|
@ -56,14 +55,14 @@ export default function Video(props: Props): JSX.Element {
|
|||
/>
|
||||
|
||||
<NavOption
|
||||
title={"Channel"}
|
||||
title={langui.channel}
|
||||
url="#channel"
|
||||
border
|
||||
onClick={() => appLayout.setSubPanelOpen(false)}
|
||||
/>
|
||||
|
||||
<NavOption
|
||||
title={"Description"}
|
||||
title={langui.description}
|
||||
url="#description"
|
||||
border
|
||||
onClick={() => appLayout.setSubPanelOpen(false)}
|
||||
|
@ -98,7 +97,8 @@ export default function Video(props: Props): JSX.Element {
|
|||
className="w-full aspect-video"
|
||||
title="YouTube video player"
|
||||
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
|
||||
></iframe>
|
||||
)}
|
||||
|
@ -135,7 +135,7 @@ export default function Video(props: Props): JSX.Element {
|
|||
target="_blank"
|
||||
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>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -144,7 +144,7 @@ export default function Video(props: Props): JSX.Element {
|
|||
{video.channel?.data?.attributes && (
|
||||
<InsetBox id="channel" className="grid place-items-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>
|
||||
<Button
|
||||
href={`/archives/videos/c/${video.channel.data.attributes.uid}`}
|
||||
|
@ -153,8 +153,7 @@ export default function Video(props: Props): JSX.Element {
|
|||
</Button>
|
||||
|
||||
<p>
|
||||
{video.channel.data.attributes.subscribers.toLocaleString()}{" "}
|
||||
subscribers
|
||||
{`${video.channel.data.attributes.subscribers.toLocaleString()} ${langui.subscribers?.toLowerCase()}`}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -163,7 +162,7 @@ export default function Video(props: Props): JSX.Element {
|
|||
|
||||
<InsetBox id="description" className="grid place-items-center">
|
||||
<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>
|
||||
</div>
|
||||
</InsetBox>
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
import AppLayout from "components/AppLayout";
|
||||
import PanelHeader from "components/PanelComponents/PanelHeader";
|
||||
import SubPanel from "components/Panels/SubPanel";
|
||||
import { AppLayout } from "components/AppLayout";
|
||||
import { PanelHeader } from "components/PanelComponents/PanelHeader";
|
||||
import { SubPanel } from "components/Panels/SubPanel";
|
||||
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
|
||||
import { Immutable } from "helpers/types";
|
||||
import { GetStaticPropsContext } from "next";
|
||||
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
||||
|
||||
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 subPanel = (
|
||||
<SubPanel>
|
||||
|
|
|
@ -1,56 +1,60 @@
|
|||
import AppLayout from "components/AppLayout";
|
||||
import Chip from "components/Chip";
|
||||
import HorizontalLine from "components/HorizontalLine";
|
||||
import Markdawn from "components/Markdown/Markdawn";
|
||||
import TOC from "components/Markdown/TOC";
|
||||
import ReturnButton, {
|
||||
import { AppLayout } from "components/AppLayout";
|
||||
import { Chip } from "components/Chip";
|
||||
import { HorizontalLine } from "components/HorizontalLine";
|
||||
import { Markdawn } from "components/Markdown/Markdawn";
|
||||
import { TOC } from "components/Markdown/TOC";
|
||||
import {
|
||||
ReturnButton,
|
||||
ReturnButtonType,
|
||||
} from "components/PanelComponents/ReturnButton";
|
||||
import ContentPanel from "components/Panels/ContentPanel";
|
||||
import SubPanel from "components/Panels/SubPanel";
|
||||
import PreviewLine from "components/PreviewLine";
|
||||
import RecorderChip from "components/RecorderChip";
|
||||
import ThumbnailHeader from "components/ThumbnailHeader";
|
||||
import ToolTip from "components/ToolTip";
|
||||
import { GetContentTextQuery } from "graphql/generated";
|
||||
import { ContentPanel } from "components/Panels/ContentPanel";
|
||||
import { SubPanel } from "components/Panels/SubPanel";
|
||||
import { PreviewLine } from "components/PreviewLine";
|
||||
import { RecorderChip } from "components/RecorderChip";
|
||||
import { ThumbnailHeader } from "components/ThumbnailHeader";
|
||||
import { ToolTip } from "components/ToolTip";
|
||||
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
|
||||
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 useSmartLanguage from "hooks/useSmartLanguage";
|
||||
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
||||
import {
|
||||
GetStaticPathsContext,
|
||||
GetStaticPathsResult,
|
||||
GetStaticPropsContext,
|
||||
} from "next";
|
||||
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
||||
import {
|
||||
getStatusDescription,
|
||||
prettyinlineTitle,
|
||||
prettyLanguage,
|
||||
prettySlug,
|
||||
} from "queries/helpers";
|
||||
|
||||
interface Props extends AppStaticProps {
|
||||
content: Exclude<
|
||||
GetContentTextQuery["contents"],
|
||||
null | undefined
|
||||
>["data"][number]["attributes"];
|
||||
contentId: Exclude<
|
||||
GetContentTextQuery["contents"],
|
||||
null | undefined
|
||||
>["data"][number]["id"];
|
||||
content: ContentWithTranslations;
|
||||
}
|
||||
|
||||
export default function Content(props: Props): JSX.Element {
|
||||
export default function Content(props: Immutable<Props>): JSX.Element {
|
||||
const { langui, content, languages } = props;
|
||||
const isMobile = useMediaMobile();
|
||||
|
||||
const [selectedTextSet, LanguageSwitcher] = useSmartLanguage({
|
||||
items: content?.text_set,
|
||||
const [selectedTranslation, LanguageSwitcher] = useSmartLanguage({
|
||||
items: content.translations,
|
||||
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 = (
|
||||
<SubPanel>
|
||||
|
@ -62,124 +66,133 @@ export default function Content(props: Props): JSX.Element {
|
|||
horizontalLine
|
||||
/>
|
||||
|
||||
{selectedTextSet?.source_language?.data?.attributes && (
|
||||
{selectedTranslation?.text_set && (
|
||||
<div className="grid gap-5">
|
||||
<h2 className="text-xl">
|
||||
{selectedTextSet.source_language.data.attributes.code ===
|
||||
selectedTextSet.language?.data?.attributes?.code
|
||||
{selectedTranslation.text_set.source_language?.data?.attributes
|
||||
?.code === selectedTranslation.language?.data?.attributes?.code
|
||||
? langui.transcript_notice
|
||||
: langui.translation_notice}
|
||||
</h2>
|
||||
|
||||
{selectedTextSet.source_language.data.attributes.code !==
|
||||
selectedTextSet.language?.data?.attributes?.code && (
|
||||
<div className="grid place-items-center gap-2">
|
||||
<p className="font-headers">{langui.source_language}:</p>
|
||||
<Chip>
|
||||
{prettyLanguage(
|
||||
selectedTextSet.source_language.data.attributes.code,
|
||||
languages
|
||||
)}
|
||||
</Chip>
|
||||
</div>
|
||||
)}
|
||||
{selectedTranslation.text_set.source_language?.data?.attributes
|
||||
?.code &&
|
||||
selectedTranslation.text_set.source_language.data.attributes
|
||||
.code !==
|
||||
selectedTranslation.language?.data?.attributes?.code && (
|
||||
<div className="grid place-items-center gap-2">
|
||||
<p className="font-headers">{langui.source_language}:</p>
|
||||
<Chip>
|
||||
{prettyLanguage(
|
||||
selectedTranslation.text_set.source_language.data.attributes
|
||||
.code,
|
||||
languages
|
||||
)}
|
||||
</Chip>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="grid grid-flow-col place-items-center place-content-center gap-2">
|
||||
<p className="font-headers">{langui.status}:</p>
|
||||
|
||||
<ToolTip
|
||||
content={getStatusDescription(selectedTextSet.status, langui)}
|
||||
content={getStatusDescription(
|
||||
selectedTranslation.text_set.status,
|
||||
langui
|
||||
)}
|
||||
maxWidth={"20rem"}
|
||||
>
|
||||
<Chip>{selectedTextSet.status}</Chip>
|
||||
<Chip>{selectedTranslation.text_set.status}</Chip>
|
||||
</ToolTip>
|
||||
</div>
|
||||
|
||||
{selectedTextSet.transcribers &&
|
||||
selectedTextSet.transcribers.data.length > 0 && (
|
||||
{selectedTranslation.text_set.transcribers &&
|
||||
selectedTranslation.text_set.transcribers.data.length > 0 && (
|
||||
<div>
|
||||
<p className="font-headers">{langui.transcribers}:</p>
|
||||
<div className="grid place-items-center place-content-center gap-2">
|
||||
{selectedTextSet.transcribers.data.map((recorder) => (
|
||||
<>
|
||||
{recorder.attributes && (
|
||||
<RecorderChip
|
||||
key={recorder.id}
|
||||
langui={langui}
|
||||
recorder={recorder.attributes}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
))}
|
||||
{selectedTranslation.text_set.transcribers.data.map(
|
||||
(recorder) => (
|
||||
<>
|
||||
{recorder.attributes && (
|
||||
<RecorderChip
|
||||
key={recorder.id}
|
||||
langui={langui}
|
||||
recorder={recorder.attributes}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{selectedTextSet.translators &&
|
||||
selectedTextSet.translators.data.length > 0 && (
|
||||
{selectedTranslation.text_set.translators &&
|
||||
selectedTranslation.text_set.translators.data.length > 0 && (
|
||||
<div>
|
||||
<p className="font-headers">{langui.translators}:</p>
|
||||
<div className="grid place-items-center place-content-center gap-2">
|
||||
{selectedTextSet.translators.data.map((recorder) => (
|
||||
<>
|
||||
{recorder.attributes && (
|
||||
<RecorderChip
|
||||
key={recorder.id}
|
||||
langui={langui}
|
||||
recorder={recorder.attributes}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
))}
|
||||
{selectedTranslation.text_set.translators.data.map(
|
||||
(recorder) => (
|
||||
<>
|
||||
{recorder.attributes && (
|
||||
<RecorderChip
|
||||
key={recorder.id}
|
||||
langui={langui}
|
||||
recorder={recorder.attributes}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{selectedTextSet.proofreaders &&
|
||||
selectedTextSet.proofreaders.data.length > 0 && (
|
||||
{selectedTranslation.text_set.proofreaders &&
|
||||
selectedTranslation.text_set.proofreaders.data.length > 0 && (
|
||||
<div>
|
||||
<p className="font-headers">{langui.proofreaders}:</p>
|
||||
<div className="grid place-items-center place-content-center gap-2">
|
||||
{selectedTextSet.proofreaders.data.map((recorder) => (
|
||||
<>
|
||||
{recorder.attributes && (
|
||||
<RecorderChip
|
||||
key={recorder.id}
|
||||
langui={langui}
|
||||
recorder={recorder.attributes}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
))}
|
||||
{selectedTranslation.text_set.proofreaders.data.map(
|
||||
(recorder) => (
|
||||
<>
|
||||
{recorder.attributes && (
|
||||
<RecorderChip
|
||||
key={recorder.id}
|
||||
langui={langui}
|
||||
recorder={recorder.attributes}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{selectedTextSet.notes && (
|
||||
{selectedTranslation.text_set.notes && (
|
||||
<div>
|
||||
<p className="font-headers">{"Notes"}:</p>
|
||||
<div className="grid place-items-center place-content-center gap-2">
|
||||
<Markdawn text={selectedTextSet.notes} />
|
||||
<Markdawn text={selectedTranslation.text_set.notes} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{selectedTextSet && content?.text_set && selectedTextSet.text && (
|
||||
{selectedTranslation?.text_set?.text && (
|
||||
<>
|
||||
<HorizontalLine />
|
||||
<TOC
|
||||
text={selectedTextSet.text}
|
||||
title={
|
||||
content.titles && content.titles.length > 0 && selectedTitle
|
||||
? prettyinlineTitle(
|
||||
selectedTitle.pre_title,
|
||||
selectedTitle.title,
|
||||
selectedTitle.subtitle
|
||||
)
|
||||
: prettySlug(content.slug)
|
||||
}
|
||||
text={selectedTranslation.text_set.text}
|
||||
title={prettyinlineTitle(
|
||||
selectedTranslation.pre_title,
|
||||
selectedTranslation.title,
|
||||
selectedTranslation.subtitle
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
@ -188,142 +201,119 @@ export default function Content(props: Props): JSX.Element {
|
|||
const contentPanel = (
|
||||
<ContentPanel>
|
||||
<ReturnButton
|
||||
href={`/contents/${content?.slug}`}
|
||||
href={`/contents/${content.slug}`}
|
||||
title={langui.content}
|
||||
langui={langui}
|
||||
displayOn={ReturnButtonType.mobile}
|
||||
className="mb-10"
|
||||
/>
|
||||
|
||||
{content && (
|
||||
<div className="grid place-items-center">
|
||||
<ThumbnailHeader
|
||||
thumbnail={content.thumbnail?.data?.attributes}
|
||||
pre_title={
|
||||
selectedTitle?.pre_title ?? content.titles?.[0]?.pre_title
|
||||
}
|
||||
title={selectedTitle?.title ?? content.titles?.[0]?.title}
|
||||
subtitle={selectedTitle?.subtitle ?? content.titles?.[0]?.subtitle}
|
||||
description={
|
||||
selectedTitle?.description ?? content.titles?.[0]?.description
|
||||
}
|
||||
type={content.type}
|
||||
categories={content.categories}
|
||||
langui={langui}
|
||||
languageSwitcher={<LanguageSwitcher />}
|
||||
/>
|
||||
<div className="grid place-items-center">
|
||||
<ThumbnailHeader
|
||||
thumbnail={content.thumbnail?.data?.attributes}
|
||||
pre_title={selectedTranslation?.pre_title}
|
||||
title={selectedTranslation?.title}
|
||||
subtitle={selectedTranslation?.subtitle}
|
||||
description={selectedTranslation?.description}
|
||||
type={content.type}
|
||||
categories={content.categories}
|
||||
langui={langui}
|
||||
languageSwitcher={<LanguageSwitcher />}
|
||||
/>
|
||||
|
||||
{content.previous_recommended?.data?.attributes && (
|
||||
<div className="mt-12 mb-8 w-full">
|
||||
<h2 className="text-center text-2xl mb-4">Previous content</h2>
|
||||
<PreviewLine
|
||||
href={`/contents/${content.previous_recommended.data.attributes.slug}`}
|
||||
pre_title={
|
||||
content.previous_recommended.data.attributes.titles?.[0]
|
||||
?.pre_title
|
||||
}
|
||||
title={
|
||||
content.previous_recommended.data.attributes.titles?.[0]
|
||||
?.title ??
|
||||
prettySlug(content.previous_recommended.data.attributes.slug)
|
||||
}
|
||||
subtitle={
|
||||
content.previous_recommended.data.attributes.titles?.[0]
|
||||
?.subtitle
|
||||
}
|
||||
thumbnail={
|
||||
content.previous_recommended.data.attributes.thumbnail?.data
|
||||
?.attributes
|
||||
}
|
||||
thumbnailAspectRatio="3/2"
|
||||
topChips={
|
||||
isMobile
|
||||
? undefined
|
||||
: content.previous_recommended.data.attributes.type?.data
|
||||
?.attributes
|
||||
? [
|
||||
content.previous_recommended.data.attributes.type.data
|
||||
.attributes.titles?.[0]
|
||||
? content.previous_recommended.data.attributes.type
|
||||
.data.attributes.titles[0]?.title
|
||||
: prettySlug(
|
||||
content.previous_recommended.data.attributes.type
|
||||
.data.attributes.slug
|
||||
),
|
||||
]
|
||||
: undefined
|
||||
}
|
||||
bottomChips={
|
||||
isMobile
|
||||
? undefined
|
||||
: content.previous_recommended.data.attributes.categories?.data.map(
|
||||
(category) => category.attributes?.short ?? ""
|
||||
)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{previousContent?.attributes && (
|
||||
<div className="mt-12 mb-8 w-full">
|
||||
<h2 className="text-center text-2xl mb-4">
|
||||
{langui.previous_content}
|
||||
</h2>
|
||||
<PreviewLine
|
||||
href={`/contents/${previousContent.attributes.slug}`}
|
||||
pre_title={
|
||||
previousContent.attributes.translations?.[0]?.pre_title
|
||||
}
|
||||
title={
|
||||
previousContent.attributes.translations?.[0]?.title ??
|
||||
prettySlug(previousContent.attributes.slug)
|
||||
}
|
||||
subtitle={previousContent.attributes.translations?.[0]?.subtitle}
|
||||
thumbnail={previousContent.attributes.thumbnail?.data?.attributes}
|
||||
thumbnailAspectRatio="3/2"
|
||||
topChips={
|
||||
isMobile
|
||||
? undefined
|
||||
: previousContent.attributes.type?.data?.attributes
|
||||
? [
|
||||
previousContent.attributes.type.data.attributes
|
||||
.titles?.[0]
|
||||
? previousContent.attributes.type.data.attributes
|
||||
.titles[0]?.title
|
||||
: prettySlug(
|
||||
previousContent.attributes.type.data.attributes.slug
|
||||
),
|
||||
]
|
||||
: undefined
|
||||
}
|
||||
bottomChips={
|
||||
isMobile
|
||||
? undefined
|
||||
: previousContent.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 && (
|
||||
<>
|
||||
<HorizontalLine />
|
||||
<h2 className="text-center text-2xl mb-4">Follow-up content</h2>
|
||||
<PreviewLine
|
||||
href={`/contents/${content.next_recommended.data.attributes.slug}`}
|
||||
pre_title={
|
||||
content.next_recommended.data.attributes.titles?.[0]
|
||||
?.pre_title
|
||||
}
|
||||
title={
|
||||
content.next_recommended.data.attributes.titles?.[0]?.title ??
|
||||
prettySlug(content.next_recommended.data.attributes.slug)
|
||||
}
|
||||
subtitle={
|
||||
content.next_recommended.data.attributes.titles?.[0]?.subtitle
|
||||
}
|
||||
thumbnail={
|
||||
content.next_recommended.data.attributes.thumbnail?.data
|
||||
?.attributes
|
||||
}
|
||||
thumbnailAspectRatio="3/2"
|
||||
topChips={
|
||||
isMobile
|
||||
? undefined
|
||||
: content.next_recommended.data.attributes.type?.data
|
||||
?.attributes
|
||||
? [
|
||||
content.next_recommended.data.attributes.type.data
|
||||
.attributes.titles?.[0]
|
||||
? content.next_recommended.data.attributes.type.data
|
||||
.attributes.titles[0]?.title
|
||||
: prettySlug(
|
||||
content.next_recommended.data.attributes.type.data
|
||||
.attributes.slug
|
||||
),
|
||||
]
|
||||
: undefined
|
||||
}
|
||||
bottomChips={
|
||||
isMobile
|
||||
? undefined
|
||||
: content.next_recommended.data.attributes.categories?.data.map(
|
||||
(category) => category.attributes?.short ?? ""
|
||||
)
|
||||
}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{nextContent?.attributes && (
|
||||
<>
|
||||
<HorizontalLine />
|
||||
<h2 className="text-center text-2xl mb-4">
|
||||
{langui.followup_content}
|
||||
</h2>
|
||||
<PreviewLine
|
||||
href={`/contents/${nextContent.attributes.slug}`}
|
||||
pre_title={nextContent.attributes.translations?.[0]?.pre_title}
|
||||
title={
|
||||
nextContent.attributes.translations?.[0]?.title ??
|
||||
prettySlug(nextContent.attributes.slug)
|
||||
}
|
||||
subtitle={nextContent.attributes.translations?.[0]?.subtitle}
|
||||
thumbnail={nextContent.attributes.thumbnail?.data?.attributes}
|
||||
thumbnailAspectRatio="3/2"
|
||||
topChips={
|
||||
isMobile
|
||||
? undefined
|
||||
: nextContent.attributes.type?.data?.attributes
|
||||
? [
|
||||
nextContent.attributes.type.data.attributes.titles?.[0]
|
||||
? nextContent.attributes.type.data.attributes.titles[0]
|
||||
?.title
|
||||
: prettySlug(
|
||||
nextContent.attributes.type.data.attributes.slug
|
||||
),
|
||||
]
|
||||
: undefined
|
||||
}
|
||||
bottomChips={
|
||||
isMobile
|
||||
? undefined
|
||||
: nextContent.attributes.categories?.data.map(
|
||||
(category) => category.attributes?.short ?? ""
|
||||
)
|
||||
}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</ContentPanel>
|
||||
);
|
||||
|
||||
let description = "";
|
||||
if (content?.type?.data) {
|
||||
if (content.type?.data) {
|
||||
description += `${langui.type}: `;
|
||||
|
||||
description +=
|
||||
|
@ -332,7 +322,7 @@ export default function Content(props: Props): JSX.Element {
|
|||
|
||||
description += "\n";
|
||||
}
|
||||
if (content?.categories?.data && content.categories.data.length > 0) {
|
||||
if (content.categories?.data && content.categories.data.length > 0) {
|
||||
description += `${langui.categories}: `;
|
||||
description += content.categories.data
|
||||
.map((category) => category.attributes?.short)
|
||||
|
@ -343,15 +333,15 @@ export default function Content(props: Props): JSX.Element {
|
|||
return (
|
||||
<AppLayout
|
||||
navTitle={
|
||||
content?.titles && content.titles.length > 0 && content.titles[0]
|
||||
selectedTranslation
|
||||
? prettyinlineTitle(
|
||||
content.titles[0].pre_title,
|
||||
content.titles[0].title,
|
||||
content.titles[0].subtitle
|
||||
selectedTranslation.pre_title,
|
||||
selectedTranslation.title,
|
||||
selectedTranslation.subtitle
|
||||
)
|
||||
: prettySlug(content?.slug)
|
||||
: prettySlug(content.slug)
|
||||
}
|
||||
thumbnail={content?.thumbnail?.data?.attributes ?? undefined}
|
||||
thumbnail={content.thumbnail?.data?.attributes ?? undefined}
|
||||
contentPanel={contentPanel}
|
||||
subPanel={subPanel}
|
||||
description={description}
|
||||
|
@ -370,12 +360,12 @@ export async function getStaticProps(
|
|||
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 };
|
||||
}
|
||||
const props: Props = {
|
||||
...(await getAppStaticProps(context)),
|
||||
content: content.contents.data[0].attributes,
|
||||
contentId: content.contents.data[0].id,
|
||||
content: content.contents.data[0].attributes as ContentWithTranslations,
|
||||
};
|
||||
return {
|
||||
props: props,
|
||||
|
|
|
@ -1,39 +1,51 @@
|
|||
import AppLayout from "components/AppLayout";
|
||||
import Chip from "components/Chip";
|
||||
import Select from "components/Inputs/Select";
|
||||
import Switch from "components/Inputs/Switch";
|
||||
import PanelHeader from "components/PanelComponents/PanelHeader";
|
||||
import ContentPanel, {
|
||||
import { AppLayout } from "components/AppLayout";
|
||||
import { Chip } from "components/Chip";
|
||||
import { Select } from "components/Inputs/Select";
|
||||
import { Switch } from "components/Inputs/Switch";
|
||||
import { PanelHeader } from "components/PanelComponents/PanelHeader";
|
||||
import {
|
||||
ContentPanel,
|
||||
ContentPanelWidthSizes,
|
||||
} from "components/Panels/ContentPanel";
|
||||
import SubPanel from "components/Panels/SubPanel";
|
||||
import ThumbnailPreview from "components/PreviewCard";
|
||||
import { SubPanel } from "components/Panels/SubPanel";
|
||||
import { PreviewCard } from "components/PreviewCard";
|
||||
import { GetContentsQuery } from "graphql/generated";
|
||||
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
|
||||
import { getReadySdk } from "graphql/sdk";
|
||||
import { prettyinlineTitle, prettySlug } from "helpers/formatters";
|
||||
import { Immutable } from "helpers/types";
|
||||
import { GetStaticPropsContext } from "next";
|
||||
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
||||
import { prettyinlineTitle, prettySlug } from "queries/helpers";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
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 [groupingMethod, setGroupingMethod] = useState<number>(-1);
|
||||
const [keepInfoVisible, setKeepInfoVisible] = useState(false);
|
||||
|
||||
const [combineRelatedContent, setCombineRelatedContent] = useState(true);
|
||||
|
||||
const [filteredItems, setFilteredItems] = useState(
|
||||
filterContents(combineRelatedContent, contents)
|
||||
);
|
||||
|
||||
const [groups, setGroups] = useState<GroupContentItems>(
|
||||
getGroups(langui, groupingMethod, contents)
|
||||
getGroups(langui, groupingMethod, filteredItems)
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setGroups(getGroups(langui, groupingMethod, contents));
|
||||
}, [langui, groupingMethod, contents]);
|
||||
setFilteredItems(filterContents(combineRelatedContent, contents));
|
||||
}, [combineRelatedContent, contents]);
|
||||
|
||||
useEffect(() => {
|
||||
setGroups(getGroups(langui, groupingMethod, filteredItems));
|
||||
}, [langui, groupingMethod, filteredItems]);
|
||||
|
||||
const subPanel = (
|
||||
<SubPanel>
|
||||
|
@ -55,7 +67,15 @@ export default function Contents(props: Props): JSX.Element {
|
|||
</div>
|
||||
|
||||
<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} />
|
||||
</div>
|
||||
</SubPanel>
|
||||
|
@ -69,7 +89,8 @@ export default function Contents(props: Props): JSX.Element {
|
|||
{name && (
|
||||
<h2
|
||||
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}
|
||||
<Chip>{`${items.length} ${
|
||||
|
@ -81,22 +102,30 @@ export default function Contents(props: Props): JSX.Element {
|
|||
)}
|
||||
<div
|
||||
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) => (
|
||||
<>
|
||||
{item.attributes && (
|
||||
<ThumbnailPreview
|
||||
<PreviewCard
|
||||
key={item.id}
|
||||
href={`/contents/${item.attributes.slug}`}
|
||||
pre_title={item.attributes.titles?.[0]?.pre_title}
|
||||
pre_title={item.attributes.translations?.[0]?.pre_title}
|
||||
title={
|
||||
item.attributes.titles?.[0]?.title ??
|
||||
item.attributes.translations?.[0]?.title ??
|
||||
prettySlug(item.attributes.slug)
|
||||
}
|
||||
subtitle={item.attributes.titles?.[0]?.subtitle}
|
||||
subtitle={item.attributes.translations?.[0]?.subtitle}
|
||||
thumbnail={item.attributes.thumbnail?.data?.attributes}
|
||||
thumbnailAspectRatio="3/2"
|
||||
stackNumber={
|
||||
combineRelatedContent &&
|
||||
item.attributes.group?.data?.attributes?.combine
|
||||
? item.attributes.group.data.attributes.contents
|
||||
?.data.length
|
||||
: 0
|
||||
}
|
||||
topChips={
|
||||
item.attributes.type?.data?.attributes
|
||||
? [
|
||||
|
@ -143,18 +172,18 @@ export async function getStaticProps(
|
|||
});
|
||||
if (!contents.contents) return { notFound: true };
|
||||
contents.contents.data.sort((a, b) => {
|
||||
const titleA = a.attributes?.titles?.[0]
|
||||
const titleA = a.attributes?.translations?.[0]
|
||||
? prettyinlineTitle(
|
||||
a.attributes.titles[0].pre_title,
|
||||
a.attributes.titles[0].title,
|
||||
a.attributes.titles[0].subtitle
|
||||
a.attributes.translations[0].pre_title,
|
||||
a.attributes.translations[0].title,
|
||||
a.attributes.translations[0].subtitle
|
||||
)
|
||||
: a.attributes?.slug ?? "";
|
||||
const titleB = b.attributes?.titles?.[0]
|
||||
const titleB = b.attributes?.translations?.[0]
|
||||
? prettyinlineTitle(
|
||||
b.attributes.titles[0].pre_title,
|
||||
b.attributes.titles[0].title,
|
||||
b.attributes.titles[0].subtitle
|
||||
b.attributes.translations[0].pre_title,
|
||||
b.attributes.translations[0].title,
|
||||
b.attributes.translations[0].subtitle
|
||||
)
|
||||
: b.attributes?.slug ?? "";
|
||||
return titleA.localeCompare(titleB);
|
||||
|
@ -172,7 +201,7 @@ export async function getStaticProps(
|
|||
function getGroups(
|
||||
langui: AppStaticProps["langui"],
|
||||
groupByType: number,
|
||||
items: Props["contents"]
|
||||
items: Immutable<Props["contents"]>
|
||||
): GroupContentItems {
|
||||
switch (groupByType) {
|
||||
case 0: {
|
||||
|
@ -209,15 +238,16 @@ function getGroups(
|
|||
}
|
||||
|
||||
case 1: {
|
||||
const group: GroupContentItems = new Map();
|
||||
const group = new Map();
|
||||
items.map((item) => {
|
||||
const type =
|
||||
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, []);
|
||||
group.get(type)?.push(item);
|
||||
});
|
||||
|
||||
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 Chip from "components/Chip";
|
||||
import Button from "components/Inputs/Button";
|
||||
import ContentPanel, {
|
||||
import { AppLayout } from "components/AppLayout";
|
||||
import { Chip } from "components/Chip";
|
||||
import { Button } from "components/Inputs/Button";
|
||||
import {
|
||||
ContentPanel,
|
||||
ContentPanelWidthSizes,
|
||||
} from "components/Panels/ContentPanel";
|
||||
import ToolTip from "components/ToolTip";
|
||||
import {
|
||||
DevGetContentsQuery,
|
||||
Enum_Componentsetstextset_Status,
|
||||
} from "graphql/generated";
|
||||
import { ToolTip } from "components/ToolTip";
|
||||
import { DevGetContentsQuery } from "graphql/generated";
|
||||
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
|
||||
import { getReadySdk } from "graphql/sdk";
|
||||
import { Immutable } from "helpers/types";
|
||||
import { GetStaticPropsContext } from "next";
|
||||
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
||||
|
||||
interface Props extends AppStaticProps {
|
||||
contents: DevGetContentsQuery;
|
||||
}
|
||||
|
||||
export default function CheckupContents(props: Props): JSX.Element {
|
||||
export default function CheckupContents(props: Immutable<Props>): JSX.Element {
|
||||
const { contents } = props;
|
||||
const testReport = testingContent(contents);
|
||||
|
||||
|
@ -38,7 +37,8 @@ export default function CheckupContents(props: Props): JSX.Element {
|
|||
{testReport.lines.map((line, index) => (
|
||||
<div
|
||||
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
|
||||
href={line.frontendUrl}
|
||||
|
@ -112,7 +112,7 @@ type ReportLine = {
|
|||
frontendUrl: string;
|
||||
};
|
||||
|
||||
function testingContent(contents: Props["contents"]): Report {
|
||||
function testingContent(contents: Immutable<Props["contents"]>): Report {
|
||||
const report: Report = {
|
||||
title: "Contents",
|
||||
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) {
|
||||
report.lines.push({
|
||||
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({
|
||||
subitems: [content.attributes.slug],
|
||||
name: "No Titles",
|
||||
|
@ -207,10 +190,10 @@ function testingContent(contents: Props["contents"]): Report {
|
|||
} else {
|
||||
const titleLanguages: string[] = [];
|
||||
|
||||
content.attributes.titles?.map((title, titleIndex) => {
|
||||
if (title && content.attributes) {
|
||||
if (title.language?.data?.id) {
|
||||
if (title.language.data.id in titleLanguages) {
|
||||
content.attributes.translations?.map((translation, titleIndex) => {
|
||||
if (translation && content.attributes) {
|
||||
if (translation.language?.data?.id) {
|
||||
if (translation.language.data.id in titleLanguages) {
|
||||
report.lines.push({
|
||||
subitems: [
|
||||
content.attributes.slug,
|
||||
|
@ -225,7 +208,7 @@ function testingContent(contents: Props["contents"]): Report {
|
|||
frontendUrl: frontendUrl,
|
||||
});
|
||||
} else {
|
||||
titleLanguages.push(title.language.data.id);
|
||||
titleLanguages.push(translation.language.data.id);
|
||||
}
|
||||
} else {
|
||||
report.lines.push({
|
||||
|
@ -242,7 +225,7 @@ function testingContent(contents: Props["contents"]): Report {
|
|||
frontendUrl: frontendUrl,
|
||||
});
|
||||
}
|
||||
if (!title.description) {
|
||||
if (!translation.description) {
|
||||
report.lines.push({
|
||||
subitems: [
|
||||
content.attributes.slug,
|
||||
|
@ -257,229 +240,199 @@ function testingContent(contents: Props["contents"]): Report {
|
|||
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;
|
||||
|
|
|
@ -1,23 +1,27 @@
|
|||
import AppLayout from "components/AppLayout";
|
||||
import Chip from "components/Chip";
|
||||
import Button from "components/Inputs/Button";
|
||||
import ContentPanel, {
|
||||
import { AppLayout } from "components/AppLayout";
|
||||
import { Chip } from "components/Chip";
|
||||
import { Button } from "components/Inputs/Button";
|
||||
import {
|
||||
ContentPanel,
|
||||
ContentPanelWidthSizes,
|
||||
} from "components/Panels/ContentPanel";
|
||||
import ToolTip from "components/ToolTip";
|
||||
import { ToolTip } from "components/ToolTip";
|
||||
import {
|
||||
DevGetLibraryItemsQuery,
|
||||
Enum_Componentcollectionscomponentlibraryimages_Status,
|
||||
} from "graphql/generated";
|
||||
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
|
||||
import { getReadySdk } from "graphql/sdk";
|
||||
import { Immutable } from "helpers/types";
|
||||
import { GetStaticPropsContext } from "next";
|
||||
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
||||
|
||||
interface Props extends AppStaticProps {
|
||||
libraryItems: DevGetLibraryItemsQuery;
|
||||
}
|
||||
|
||||
export default function CheckupLibraryItems(props: Props): JSX.Element {
|
||||
export default function CheckupLibraryItems(
|
||||
props: Immutable<Props>
|
||||
): JSX.Element {
|
||||
const { libraryItems } = props;
|
||||
const testReport = testingLibraryItem(libraryItems);
|
||||
|
||||
|
@ -38,7 +42,8 @@ export default function CheckupLibraryItems(props: Props): JSX.Element {
|
|||
{testReport.lines.map((line, index) => (
|
||||
<div
|
||||
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
|
||||
href={line.frontendUrl}
|
||||
|
@ -113,7 +118,9 @@ type ReportLine = {
|
|||
};
|
||||
|
||||
// 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 = {
|
||||
title: "Contents",
|
||||
lines: [],
|
||||
|
|
|
@ -1,19 +1,21 @@
|
|||
import AppLayout from "components/AppLayout";
|
||||
import Button from "components/Inputs/Button";
|
||||
import Markdawn from "components/Markdown/Markdawn";
|
||||
import ContentPanel, {
|
||||
import { AppLayout } from "components/AppLayout";
|
||||
import { Button } from "components/Inputs/Button";
|
||||
import { Markdawn } from "components/Markdown/Markdawn";
|
||||
import {
|
||||
ContentPanel,
|
||||
ContentPanelWidthSizes,
|
||||
} from "components/Panels/ContentPanel";
|
||||
import Popup from "components/Popup";
|
||||
import ToolTip from "components/ToolTip";
|
||||
import { Popup } from "components/Popup";
|
||||
import { ToolTip } from "components/ToolTip";
|
||||
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
|
||||
import { Immutable } from "helpers/types";
|
||||
import { GetStaticPropsContext } from "next";
|
||||
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
||||
import { useCallback, useState } from "react";
|
||||
import TurndownService from "turndown";
|
||||
|
||||
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) => {
|
||||
setMarkdown(text);
|
||||
}, []);
|
||||
|
@ -337,7 +339,8 @@ export default function Editor(props: Props): JSX.Element {
|
|||
const textarea = event.target as HTMLTextAreaElement;
|
||||
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}
|
||||
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 { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
||||
|
||||
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 contentPanel = (
|
||||
<iframe
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
import PostPage, { Post } from "components/PostPage";
|
||||
import { getReadySdk } from "graphql/sdk";
|
||||
import { GetStaticPropsContext } from "next";
|
||||
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
||||
import { PostPage } from "components/PostPage";
|
||||
import {
|
||||
getPostStaticProps,
|
||||
PostStaticProps,
|
||||
} from "graphql/getPostStaticProps";
|
||||
import { Immutable } from "helpers/types";
|
||||
|
||||
interface Props extends AppStaticProps {
|
||||
post: Post;
|
||||
}
|
||||
|
||||
export default function Home(props: Props): JSX.Element {
|
||||
export default function Home(props: Immutable<PostStaticProps>): JSX.Element {
|
||||
const { post, langui, languages, currencies } = props;
|
||||
return (
|
||||
<PostPage
|
||||
|
@ -17,7 +15,11 @@ export default function Home(props: Props): JSX.Element {
|
|||
post={post}
|
||||
prependBody={
|
||||
<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>
|
||||
<h2 className="text-xl -mt-5">
|
||||
Discover • Analyze • Translate • Archive
|
||||
|
@ -30,21 +32,4 @@ export default function Home(props: Props): JSX.Element {
|
|||
);
|
||||
}
|
||||
|
||||
export async function getStaticProps(
|
||||
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,
|
||||
};
|
||||
}
|
||||
export const getStaticProps = getPostStaticProps("home");
|
||||
|
|
|
@ -1,57 +1,59 @@
|
|||
import AppLayout from "components/AppLayout";
|
||||
import Chip from "components/Chip";
|
||||
import Img, { getAssetURL, ImageQuality } from "components/Img";
|
||||
import Button from "components/Inputs/Button";
|
||||
import Switch from "components/Inputs/Switch";
|
||||
import InsetBox from "components/InsetBox";
|
||||
import ContentLine from "components/Library/ContentLine";
|
||||
import LightBox from "components/LightBox";
|
||||
import NavOption from "components/PanelComponents/NavOption";
|
||||
import ReturnButton, {
|
||||
import { AppLayout } from "components/AppLayout";
|
||||
import { Chip } from "components/Chip";
|
||||
import { Img } from "components/Img";
|
||||
import { Button } from "components/Inputs/Button";
|
||||
import { Switch } from "components/Inputs/Switch";
|
||||
import { InsetBox } from "components/InsetBox";
|
||||
import { ContentLine } from "components/Library/ContentLine";
|
||||
import { NavOption } from "components/PanelComponents/NavOption";
|
||||
import {
|
||||
ReturnButton,
|
||||
ReturnButtonType,
|
||||
} from "components/PanelComponents/ReturnButton";
|
||||
import ContentPanel, {
|
||||
import {
|
||||
ContentPanel,
|
||||
ContentPanelWidthSizes,
|
||||
} from "components/Panels/ContentPanel";
|
||||
import SubPanel from "components/Panels/SubPanel";
|
||||
import ThumbnailPreview from "components/PreviewCard";
|
||||
import { SubPanel } from "components/Panels/SubPanel";
|
||||
import { PreviewCard } from "components/PreviewCard";
|
||||
import { useAppLayout } from "contexts/AppLayoutContext";
|
||||
import {
|
||||
Enum_Componentmetadatabooks_Binding_Type,
|
||||
Enum_Componentmetadatabooks_Page_Order,
|
||||
GetLibraryItemQuery,
|
||||
} from "graphql/generated";
|
||||
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
|
||||
import { getReadySdk } from "graphql/sdk";
|
||||
import {
|
||||
GetStaticPathsContext,
|
||||
GetStaticPathsResult,
|
||||
GetStaticPropsContext,
|
||||
} from "next";
|
||||
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
||||
import {
|
||||
convertMmToInch,
|
||||
prettyDate,
|
||||
prettyinlineTitle,
|
||||
prettyItemSubType,
|
||||
prettyItemType,
|
||||
prettyPrice,
|
||||
prettyURL,
|
||||
sortContent,
|
||||
} from "queries/helpers";
|
||||
} from "helpers/formatters";
|
||||
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";
|
||||
|
||||
interface Props extends AppStaticProps {
|
||||
item: Exclude<
|
||||
GetLibraryItemQuery["libraryItems"],
|
||||
null | undefined
|
||||
item: NonNullable<
|
||||
GetLibraryItemQuery["libraryItems"]
|
||||
>["data"][number]["attributes"];
|
||||
itemId: Exclude<
|
||||
GetLibraryItemQuery["libraryItems"],
|
||||
null | undefined
|
||||
itemId: NonNullable<
|
||||
GetLibraryItemQuery["libraryItems"]
|
||||
>["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 appLayout = useAppLayout();
|
||||
|
||||
|
@ -61,10 +63,7 @@ export default function LibrarySlug(props: Props): JSX.Element {
|
|||
|
||||
sortContent(item?.contents);
|
||||
|
||||
const [lightboxOpen, setLightboxOpen] = useState(false);
|
||||
const [lightboxImages, setLightboxImages] = useState([""]);
|
||||
const [lightboxIndex, setLightboxIndex] = useState(0);
|
||||
|
||||
const [openLightBox, LightBox] = useLightBox();
|
||||
const [keepInfoVisible, setKeepInfoVisible] = useState(false);
|
||||
|
||||
let displayOpenScans = false;
|
||||
|
@ -134,13 +133,7 @@ export default function LibrarySlug(props: Props): JSX.Element {
|
|||
|
||||
const contentPanel = (
|
||||
<ContentPanel width={ContentPanelWidthSizes.large}>
|
||||
<LightBox
|
||||
state={lightboxOpen}
|
||||
setState={setLightboxOpen}
|
||||
images={lightboxImages}
|
||||
index={lightboxIndex}
|
||||
setIndex={setLightboxIndex}
|
||||
/>
|
||||
<LightBox />
|
||||
|
||||
<ReturnButton
|
||||
href="/library/"
|
||||
|
@ -151,17 +144,16 @@ export default function LibrarySlug(props: Props): JSX.Element {
|
|||
/>
|
||||
<div className="grid place-items-center gap-12">
|
||||
<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={() => {
|
||||
if (item?.thumbnail?.data?.attributes) {
|
||||
setLightboxOpen(true);
|
||||
setLightboxImages([
|
||||
openLightBox([
|
||||
getAssetURL(
|
||||
item.thumbnail.data.attributes.url,
|
||||
ImageQuality.Large
|
||||
),
|
||||
]);
|
||||
setLightboxIndex(0);
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
@ -169,9 +161,7 @@ export default function LibrarySlug(props: Props): JSX.Element {
|
|||
<Img
|
||||
image={item.thumbnail.data.attributes}
|
||||
quality={ImageQuality.Large}
|
||||
layout="fill"
|
||||
objectFit="contain"
|
||||
priority
|
||||
className="w-full h-full object-contain"
|
||||
/>
|
||||
) : (
|
||||
<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 ? (
|
||||
<div className="flex flex-row place-items-center gap-3">
|
||||
<p>Available at</p>
|
||||
<p>{langui.available_at}</p>
|
||||
{item.urls.map((url) => (
|
||||
<>
|
||||
{url?.url && (
|
||||
|
@ -228,7 +218,7 @@ export default function LibrarySlug(props: Props): JSX.Element {
|
|||
))}
|
||||
</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 && (
|
||||
<div id="gallery" className="grid place-items-center gap-8 w-full">
|
||||
<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) => (
|
||||
<>
|
||||
{galleryItem.attributes && (
|
||||
<div
|
||||
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={() => {
|
||||
if (item.gallery?.data) {
|
||||
const images: string[] = [];
|
||||
|
@ -257,18 +251,14 @@ export default function LibrarySlug(props: Props): JSX.Element {
|
|||
)
|
||||
);
|
||||
});
|
||||
setLightboxOpen(true);
|
||||
setLightboxImages(images);
|
||||
setLightboxIndex(index);
|
||||
openLightBox(images, index);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="bg-light absolute inset-0 rounded-lg drop-shadow-shade-md"></div>
|
||||
<Img
|
||||
className="rounded-lg"
|
||||
className="bg-light rounded-lg drop-shadow-shade-md
|
||||
w-full h-full object-cover"
|
||||
image={galleryItem.attributes}
|
||||
layout="fill"
|
||||
objectFit="cover"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
@ -425,14 +415,17 @@ export default function LibrarySlug(props: Props): JSX.Element {
|
|||
</h2>
|
||||
|
||||
<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} />
|
||||
</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) => (
|
||||
<>
|
||||
{subitem.attributes && (
|
||||
<ThumbnailPreview
|
||||
<PreviewCard
|
||||
key={subitem.id}
|
||||
href={`/library/${subitem.attributes.slug}`}
|
||||
title={subitem.attributes.title}
|
||||
|
|
|
@ -1,47 +1,46 @@
|
|||
import AppLayout from "components/AppLayout";
|
||||
import ScanSet from "components/Library/ScanSet";
|
||||
import ScanSetCover from "components/Library/ScanSetCover";
|
||||
import LightBox from "components/LightBox";
|
||||
import NavOption from "components/PanelComponents/NavOption";
|
||||
import ReturnButton, {
|
||||
import { AppLayout } from "components/AppLayout";
|
||||
import { ScanSet } from "components/Library/ScanSet";
|
||||
import { ScanSetCover } from "components/Library/ScanSetCover";
|
||||
import { NavOption } from "components/PanelComponents/NavOption";
|
||||
import {
|
||||
ReturnButton,
|
||||
ReturnButtonType,
|
||||
} from "components/PanelComponents/ReturnButton";
|
||||
import ContentPanel, {
|
||||
import {
|
||||
ContentPanel,
|
||||
ContentPanelWidthSizes,
|
||||
} from "components/Panels/ContentPanel";
|
||||
import SubPanel from "components/Panels/SubPanel";
|
||||
import { SubPanel } from "components/Panels/SubPanel";
|
||||
import { useAppLayout } from "contexts/AppLayoutContext";
|
||||
import { GetLibraryItemScansQuery } from "graphql/generated";
|
||||
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
|
||||
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 {
|
||||
GetStaticPathsContext,
|
||||
GetStaticPathsResult,
|
||||
GetStaticPropsContext,
|
||||
} from "next";
|
||||
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
||||
import { prettyinlineTitle, prettySlug, sortContent } from "queries/helpers";
|
||||
import { useState } from "react";
|
||||
|
||||
interface Props extends AppStaticProps {
|
||||
item: Exclude<
|
||||
GetLibraryItemScansQuery["libraryItems"],
|
||||
null | undefined
|
||||
item: NonNullable<
|
||||
GetLibraryItemScansQuery["libraryItems"]
|
||||
>["data"][number]["attributes"];
|
||||
itemId: Exclude<
|
||||
GetLibraryItemScansQuery["libraryItems"],
|
||||
null | undefined
|
||||
itemId: NonNullable<
|
||||
GetLibraryItemScansQuery["libraryItems"]
|
||||
>["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 appLayout = useAppLayout();
|
||||
|
||||
sortContent(item?.contents);
|
||||
|
||||
const [lightboxOpen, setLightboxOpen] = useState(false);
|
||||
const [lightboxImages, setLightboxImages] = useState([""]);
|
||||
const [lightboxIndex, setLightboxIndex] = useState(0);
|
||||
const [openLightBox, LightBox] = useLightBox();
|
||||
|
||||
const subPanel = (
|
||||
<SubPanel>
|
||||
|
@ -61,7 +60,9 @@ export default function LibrarySlug(props: Props): JSX.Element {
|
|||
subtitle={
|
||||
content.attributes?.range[0]?.__typename ===
|
||||
"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
|
||||
}
|
||||
onClick={() => appLayout.setSubPanelOpen(false)}
|
||||
|
@ -73,13 +74,7 @@ export default function LibrarySlug(props: Props): JSX.Element {
|
|||
|
||||
const contentPanel = (
|
||||
<ContentPanel width={ContentPanelWidthSizes.large}>
|
||||
<LightBox
|
||||
state={lightboxOpen}
|
||||
setState={setLightboxOpen}
|
||||
images={lightboxImages}
|
||||
index={lightboxIndex}
|
||||
setIndex={setLightboxIndex}
|
||||
/>
|
||||
<LightBox />
|
||||
|
||||
<ReturnButton
|
||||
href={`/library/${item?.slug}`}
|
||||
|
@ -92,9 +87,7 @@ export default function LibrarySlug(props: Props): JSX.Element {
|
|||
{item?.images && (
|
||||
<ScanSetCover
|
||||
images={item.images}
|
||||
setLightboxImages={setLightboxImages}
|
||||
setLightboxIndex={setLightboxIndex}
|
||||
setLightboxOpen={setLightboxOpen}
|
||||
openLightBox={openLightBox}
|
||||
languages={languages}
|
||||
langui={langui}
|
||||
/>
|
||||
|
@ -106,9 +99,7 @@ export default function LibrarySlug(props: Props): JSX.Element {
|
|||
<ScanSet
|
||||
key={content.id}
|
||||
scanSet={content.attributes.scan_set}
|
||||
setLightboxImages={setLightboxImages}
|
||||
setLightboxIndex={setLightboxIndex}
|
||||
setLightboxOpen={setLightboxOpen}
|
||||
openLightBox={openLightBox}
|
||||
slug={content.attributes.slug}
|
||||
title={prettySlug(content.attributes.slug, item.slug)}
|
||||
languages={languages}
|
||||
|
|
|
@ -1,35 +1,34 @@
|
|||
import AppLayout from "components/AppLayout";
|
||||
import Chip from "components/Chip";
|
||||
import Select from "components/Inputs/Select";
|
||||
import Switch from "components/Inputs/Switch";
|
||||
import PanelHeader from "components/PanelComponents/PanelHeader";
|
||||
import ContentPanel, {
|
||||
import { AppLayout } from "components/AppLayout";
|
||||
import { Chip } from "components/Chip";
|
||||
import { Select } from "components/Inputs/Select";
|
||||
import { Switch } from "components/Inputs/Switch";
|
||||
import { PanelHeader } from "components/PanelComponents/PanelHeader";
|
||||
import {
|
||||
ContentPanel,
|
||||
ContentPanelWidthSizes,
|
||||
} from "components/Panels/ContentPanel";
|
||||
import SubPanel from "components/Panels/SubPanel";
|
||||
import ThumbnailPreview from "components/PreviewCard";
|
||||
import { SubPanel } from "components/Panels/SubPanel";
|
||||
import { PreviewCard } from "components/PreviewCard";
|
||||
import { GetLibraryItemsPreviewQuery } from "graphql/generated";
|
||||
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
|
||||
import { getReadySdk } from "graphql/sdk";
|
||||
import { GetStaticPropsContext } from "next";
|
||||
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
||||
import {
|
||||
convertPrice,
|
||||
prettyDate,
|
||||
prettyinlineTitle,
|
||||
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";
|
||||
|
||||
interface Props extends AppStaticProps {
|
||||
items: Exclude<
|
||||
GetLibraryItemsPreviewQuery["libraryItems"],
|
||||
null | undefined
|
||||
>["data"];
|
||||
items: NonNullable<GetLibraryItemsPreviewQuery["libraryItems"]>["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 [showSubitems, setShowSubitems] = useState<boolean>(false);
|
||||
|
@ -39,7 +38,7 @@ export default function Library(props: Props): JSX.Element {
|
|||
const [groupingMethod, setGroupingMethod] = useState<number>(-1);
|
||||
const [keepInfoVisible, setKeepInfoVisible] = useState(false);
|
||||
|
||||
const [filteredItems, setFilteredItems] = useState<Props["items"]>(
|
||||
const [filteredItems, setFilteredItems] = useState(
|
||||
filterItems(
|
||||
showSubitems,
|
||||
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)
|
||||
);
|
||||
|
||||
const [groups, setGroups] = useState<GroupLibraryItems>(
|
||||
const [groups, setGroups] = useState(
|
||||
getGroups(langui, groupingMethod, sortedItems)
|
||||
);
|
||||
|
||||
|
@ -128,7 +127,7 @@ export default function Library(props: Props): JSX.Element {
|
|||
</div>
|
||||
|
||||
<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} />
|
||||
</div>
|
||||
</SubPanel>
|
||||
|
@ -142,7 +141,8 @@ export default function Library(props: Props): JSX.Element {
|
|||
{name && (
|
||||
<h2
|
||||
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}
|
||||
<Chip>{`${items.length} ${
|
||||
|
@ -154,12 +154,14 @@ export default function Library(props: Props): JSX.Element {
|
|||
)}
|
||||
<div
|
||||
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) => (
|
||||
<>
|
||||
{item.attributes && (
|
||||
<ThumbnailPreview
|
||||
<PreviewCard
|
||||
key={item.id}
|
||||
href={`/library/${item.attributes.slug}`}
|
||||
title={item.attributes.title}
|
||||
|
@ -224,7 +226,7 @@ export async function getStaticProps(
|
|||
function getGroups(
|
||||
langui: AppStaticProps["langui"],
|
||||
groupByType: number,
|
||||
items: Props["items"]
|
||||
items: Immutable<Props["items"]>
|
||||
): GroupLibraryItems {
|
||||
switch (groupByType) {
|
||||
case 0: {
|
||||
|
@ -262,7 +264,7 @@ function getGroups(
|
|||
}
|
||||
|
||||
case 1: {
|
||||
const group: GroupLibraryItems = new Map();
|
||||
const group = new Map();
|
||||
group.set(langui.audio ?? "Audio", []);
|
||||
group.set(langui.game ?? "Game", []);
|
||||
group.set(langui.textual ?? "Textual", []);
|
||||
|
@ -334,7 +336,7 @@ function getGroups(
|
|||
years.push(item.attributes.release_date.year);
|
||||
}
|
||||
});
|
||||
const group: GroupLibraryItems = new Map();
|
||||
const group = new Map();
|
||||
years.sort((a, b) => a - b);
|
||||
years.map((year) => {
|
||||
group.set(year.toString(), []);
|
||||
|
@ -352,7 +354,7 @@ function getGroups(
|
|||
}
|
||||
|
||||
default: {
|
||||
const group: GroupLibraryItems = new Map();
|
||||
const group = new Map();
|
||||
group.set("", items);
|
||||
return group;
|
||||
}
|
||||
|
@ -363,8 +365,8 @@ function filterItems(
|
|||
showSubitems: boolean,
|
||||
showPrimaryItems: boolean,
|
||||
showSecondaryItems: boolean,
|
||||
items: Props["items"]
|
||||
): Props["items"] {
|
||||
items: Immutable<Props["items"]>
|
||||
): Immutable<Props["items"]> {
|
||||
return [...items].filter((item) => {
|
||||
if (!showSubitems && !item.attributes?.root_item) return false;
|
||||
if (
|
||||
|
@ -384,9 +386,9 @@ function filterItems(
|
|||
|
||||
function sortBy(
|
||||
orderByType: number,
|
||||
items: Props["items"],
|
||||
items: Immutable<Props["items"]>,
|
||||
currencies: AppStaticProps["currencies"]
|
||||
): Props["items"] {
|
||||
): Immutable<Props["items"]> {
|
||||
switch (orderByType) {
|
||||
case 0:
|
||||
return [...items].sort((a, b) => {
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import AppLayout from "components/AppLayout";
|
||||
import PanelHeader from "components/PanelComponents/PanelHeader";
|
||||
import SubPanel from "components/Panels/SubPanel";
|
||||
import { AppLayout } from "components/AppLayout";
|
||||
import { PanelHeader } from "components/PanelComponents/PanelHeader";
|
||||
import { SubPanel } from "components/Panels/SubPanel";
|
||||
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
|
||||
import { Immutable } from "helpers/types";
|
||||
import { GetStaticPropsContext } from "next";
|
||||
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
||||
|
||||
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 subPanel = (
|
||||
<SubPanel>
|
||||
|
|
|
@ -1,22 +1,20 @@
|
|||
import PostPage, { Post } from "components/PostPage";
|
||||
import { GetPostQuery } from "graphql/generated";
|
||||
import { PostPage } from "components/PostPage";
|
||||
import { AppStaticProps } from "graphql/getAppStaticProps";
|
||||
import {
|
||||
getPostStaticProps,
|
||||
PostStaticProps,
|
||||
} from "graphql/getPostStaticProps";
|
||||
import { getReadySdk } from "graphql/sdk";
|
||||
import { Immutable } from "helpers/types";
|
||||
import {
|
||||
GetStaticPathsContext,
|
||||
GetStaticPathsResult,
|
||||
GetStaticPropsContext,
|
||||
} from "next";
|
||||
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
||||
|
||||
interface Props extends AppStaticProps {
|
||||
post: Post;
|
||||
postId: Exclude<
|
||||
GetPostQuery["posts"],
|
||||
null | undefined
|
||||
>["data"][number]["id"];
|
||||
}
|
||||
interface Props extends AppStaticProps, PostStaticProps {}
|
||||
|
||||
export default function LibrarySlug(props: Props): JSX.Element {
|
||||
export default function LibrarySlug(props: Immutable<Props>): JSX.Element {
|
||||
const { post, langui, languages, currencies } = props;
|
||||
return (
|
||||
<PostPage
|
||||
|
@ -36,21 +34,8 @@ export default function LibrarySlug(props: Props): JSX.Element {
|
|||
export async function getStaticProps(
|
||||
context: GetStaticPropsContext
|
||||
): Promise<{ notFound: boolean } | { props: Props }> {
|
||||
const sdk = getReadySdk();
|
||||
const slug = context.params?.slug ? context.params.slug.toString() : "";
|
||||
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,
|
||||
postId: post.posts.data[0].id,
|
||||
};
|
||||
return {
|
||||
props: props,
|
||||
};
|
||||
return await getPostStaticProps(slug)(context);
|
||||
}
|
||||
|
||||
export async function getStaticPaths(
|
||||
|
|
|
@ -1,35 +1,30 @@
|
|||
import AppLayout from "components/AppLayout";
|
||||
import Switch from "components/Inputs/Switch";
|
||||
import PanelHeader from "components/PanelComponents/PanelHeader";
|
||||
import ContentPanel, {
|
||||
import { AppLayout } from "components/AppLayout";
|
||||
import { Switch } from "components/Inputs/Switch";
|
||||
import { PanelHeader } from "components/PanelComponents/PanelHeader";
|
||||
import {
|
||||
ContentPanel,
|
||||
ContentPanelWidthSizes,
|
||||
} from "components/Panels/ContentPanel";
|
||||
import SubPanel from "components/Panels/SubPanel";
|
||||
import ThumbnailPreview from "components/PreviewCard";
|
||||
import { SubPanel } from "components/Panels/SubPanel";
|
||||
import { PreviewCard } from "components/PreviewCard";
|
||||
import { GetPostsPreviewQuery } from "graphql/generated";
|
||||
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
|
||||
import { getReadySdk } from "graphql/sdk";
|
||||
import { prettyDate, prettySlug } from "helpers/formatters";
|
||||
import { Immutable } from "helpers/types";
|
||||
import { GetStaticPropsContext } from "next";
|
||||
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
||||
import { prettyDate, prettySlug } from "queries/helpers";
|
||||
import { useState } from "react";
|
||||
|
||||
interface Props extends AppStaticProps {
|
||||
posts: Exclude<GetPostsPreviewQuery["posts"], null | undefined>["data"];
|
||||
posts: NonNullable<GetPostsPreviewQuery["posts"]>["data"];
|
||||
}
|
||||
|
||||
export default function News(props: Props): JSX.Element {
|
||||
const { langui, posts } = props;
|
||||
export default function News(props: Immutable<Props>): JSX.Element {
|
||||
const { langui } = props;
|
||||
const posts = sortPosts(props.posts);
|
||||
|
||||
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 = (
|
||||
<SubPanel>
|
||||
<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">
|
||||
<p className="flex-shrink-0">{"Always show info"}:</p>
|
||||
<p className="flex-shrink-0">{langui.always_show_info}:</p>
|
||||
<Switch setState={setKeepInfoVisible} state={keepInfoVisible} />
|
||||
</div>
|
||||
</SubPanel>
|
||||
|
@ -47,11 +42,14 @@ export default function News(props: Props): JSX.Element {
|
|||
|
||||
const contentPanel = (
|
||||
<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) => (
|
||||
<>
|
||||
{post.attributes && (
|
||||
<ThumbnailPreview
|
||||
<PreviewCard
|
||||
key={post.id}
|
||||
href={`/news/${post.attributes.slug}`}
|
||||
title={
|
||||
|
@ -103,3 +101,17 @@ export async function getStaticProps(
|
|||
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 InsetBox from "components/InsetBox";
|
||||
import NavOption from "components/PanelComponents/NavOption";
|
||||
import ReturnButton, {
|
||||
import { AppLayout } from "components/AppLayout";
|
||||
import { InsetBox } from "components/InsetBox";
|
||||
import { NavOption } from "components/PanelComponents/NavOption";
|
||||
import {
|
||||
ReturnButton,
|
||||
ReturnButtonType,
|
||||
} from "components/PanelComponents/ReturnButton";
|
||||
import ContentPanel from "components/Panels/ContentPanel";
|
||||
import SubPanel from "components/Panels/SubPanel";
|
||||
import ChronologyYearComponent from "components/Wiki/Chronology/ChronologyYearComponent";
|
||||
import { ContentPanel } from "components/Panels/ContentPanel";
|
||||
import { SubPanel } from "components/Panels/SubPanel";
|
||||
import { ChronologyYearComponent } from "components/Wiki/Chronology/ChronologyYearComponent";
|
||||
import { useAppLayout } from "contexts/AppLayoutContext";
|
||||
import { GetChronologyItemsQuery, GetErasQuery } from "graphql/generated";
|
||||
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
|
||||
import { getReadySdk } from "graphql/sdk";
|
||||
import { prettySlug } from "helpers/formatters";
|
||||
import { GetStaticPropsContext } from "next";
|
||||
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
||||
import { prettySlug } from "queries/helpers";
|
||||
|
||||
interface Props extends AppStaticProps {
|
||||
chronologyItems: Exclude<
|
||||
GetChronologyItemsQuery["chronologyItems"],
|
||||
null | undefined
|
||||
>["data"];
|
||||
chronologyEras: Exclude<
|
||||
GetErasQuery["chronologyEras"],
|
||||
null | undefined
|
||||
chronologyItems: NonNullable<
|
||||
GetChronologyItemsQuery["chronologyItems"]
|
||||
>["data"];
|
||||
chronologyEras: NonNullable<GetErasQuery["chronologyEras"]>["data"];
|
||||
}
|
||||
|
||||
export default function Chronology(props: Props): JSX.Element {
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
import AppLayout from "components/AppLayout";
|
||||
import NavOption from "components/PanelComponents/NavOption";
|
||||
import PanelHeader from "components/PanelComponents/PanelHeader";
|
||||
import SubPanel from "components/Panels/SubPanel";
|
||||
import { AppLayout } from "components/AppLayout";
|
||||
import { NavOption } from "components/PanelComponents/NavOption";
|
||||
import { PanelHeader } from "components/PanelComponents/PanelHeader";
|
||||
import { SubPanel } from "components/Panels/SubPanel";
|
||||
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
|
||||
import { Immutable } from "helpers/types";
|
||||
import { GetStaticPropsContext } from "next";
|
||||
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
||||
|
||||
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 subPanel = (
|
||||
<SubPanel>
|
||||
|
|
|
@ -159,5 +159,13 @@ module.exports = {
|
|||
},
|
||||
});
|
||||
}),
|
||||
|
||||
plugin(function ({ addUtilities }) {
|
||||
addUtilities({
|
||||
".break-words": {
|
||||
"word-break": "break-word",
|
||||
},
|
||||
});
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue