From 2443dee83fe37df7ddb6cfbb460b0b7bb8613f37 Mon Sep 17 00:00:00 2001 From: DrMint Date: Sat, 25 Jun 2022 01:25:45 +0200 Subject: [PATCH] Continue to improve code with hooks --- package-lock.json | 135 +++++++++------ package.json | 25 +-- src/components/AppLayout.tsx | 145 ++++++---------- src/components/Inputs/Button.tsx | 2 +- src/components/Inputs/ButtonGroup.tsx | 68 +++++--- src/components/Library/ContentLine.tsx | 119 ------------- src/components/Library/ScanSet.tsx | 75 +++++---- src/components/Library/ScanSetCover.tsx | 19 ++- src/components/Markdown/TOC.tsx | 2 +- src/components/PostPage.tsx | 22 ++- src/components/PreviewCard.tsx | 22 +-- src/components/PreviewLine.tsx | 23 +-- src/components/Wiki/DefinitionCard.tsx | 31 ++-- src/contexts/AppLayoutContext.tsx | 2 - src/helpers/contents.ts | 29 ---- src/helpers/formatters.ts | 14 +- src/helpers/img.ts | 4 +- src/helpers/libraryItem.ts | 4 +- src/helpers/search.ts | 74 --------- src/helpers/types.ts | 1 - src/hooks/useMediaQuery.ts | 3 +- src/hooks/useSmartLanguage.ts | 84 ++++++++++ src/hooks/useSmartLanguage.tsx | 35 ++-- src/pages/contents/[slug]/index.tsx | 88 +++++++--- src/pages/contents/index.tsx | 104 ++++++------ src/pages/library/[slug]/index.tsx | 212 +++++++++++++++++++++--- src/pages/library/index.tsx | 60 +++---- src/pages/merch/index.tsx | 2 +- src/pages/wiki/[slug]/index.tsx | 20 ++- src/pages/wiki/index.tsx | 42 ++--- 30 files changed, 781 insertions(+), 685 deletions(-) delete mode 100644 src/components/Library/ContentLine.tsx delete mode 100644 src/helpers/contents.ts delete mode 100644 src/helpers/search.ts create mode 100644 src/hooks/useSmartLanguage.ts diff --git a/package-lock.json b/package-lock.json index 4b0336d..ff73093 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,8 +18,8 @@ "meilisearch": "^0.25.1", "next": "^12.1.6", "nodemailer": "^6.7.5", - "react": "18.2.0", - "react-dom": "18.2.0", + "react": "18.1.0", + "react-dom": "18.1.0", "react-hot-keys": "^2.7.2", "react-swipeable": "^7.0.0", "turndown": "^7.1.1" @@ -29,22 +29,23 @@ "@graphql-codegen/typescript": "2.5.1", "@graphql-codegen/typescript-graphql-request": "^4.4.10", "@graphql-codegen/typescript-operations": "^2.4.2", - "@types/node": "18.0.0", + "@types/node": "17.0.41", "@types/nodemailer": "^6.4.4", - "@types/react": "18.0.14", + "@types/react": "18.0.12", "@types/react-dom": "^18.0.5", "@types/turndown": "^5.0.1", - "@typescript-eslint/eslint-plugin": "^5.28.0", - "@typescript-eslint/parser": "^5.28.0", - "eslint": "^8.18.0", + "@typescript-eslint/eslint-plugin": "^5.27.1", + "@typescript-eslint/parser": "^5.27.1", + "eslint": "^8.17.0", "eslint-config-next": "12.1.6", "graphql": "^16.5.0", "next-sitemap": "^3.0.5", - "prettier": "^2.7.1", - "prettier-plugin-organize-imports": "^3.0.0", + "prettier": "^2.6.2", + "prettier-plugin-organize-imports": "^2.3.4", "prettier-plugin-tailwindcss": "^0.1.11", - "tailwindcss": "^3.1.3", - "typescript": "^4.7.4" + "tailwindcss": "^3.1.2", + "ts-unused-exports": "^8.0.0", + "typescript": "^4.7.3" } }, "node_modules/@ampproject/remapping": { @@ -2604,9 +2605,9 @@ } }, "node_modules/@types/node": { - "version": "18.0.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.0.0.tgz", - "integrity": "sha512-cHlGmko4gWLVI27cGJntjs/Sj8th9aYwplmZFwmmgYQQvL5NUsgVJG7OddLvNfLqYS31KFN0s3qlaD9qCaxACA==", + "version": "17.0.41", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.41.tgz", + "integrity": "sha512-xA6drNNeqb5YyV5fO3OAEsnXLfO7uF0whiOfPTz5AeDo8KeZFmODKnvwPymMNO8qE/an8pVY/O50tig2SQCrGw==", "dev": true }, "node_modules/@types/nodemailer": { @@ -2631,9 +2632,9 @@ "dev": true }, "node_modules/@types/react": { - "version": "18.0.14", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.14.tgz", - "integrity": "sha512-x4gGuASSiWmo0xjDLpm5mPb52syZHJx02VKbqUKdLmKtAwIh63XClGsiTI1K6DO5q7ox4xAsQrU+Gl3+gGXF9Q==", + "version": "18.0.12", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.12.tgz", + "integrity": "sha512-duF1OTASSBQtcigUvhuiTB1Ya3OvSy+xORCiEf20H0P0lzx+/KeVsA99U5UjLXSbyo1DRJDlLKqTeM1ngosqtg==", "dev": true, "dependencies": { "@types/prop-types": "*", @@ -7587,9 +7588,9 @@ } }, "node_modules/prettier-plugin-organize-imports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-3.0.0.tgz", - "integrity": "sha512-juSCJs5TMOqGGPaN/A/1xzWFzRPH2LG1LPLCr64dzKaVnPafJdtgDNmDVlU+8A4LbQzVJg0DTvgA8swBuIUhlg==", + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-2.3.4.tgz", + "integrity": "sha512-R8o23sf5iVL/U71h9SFUdhdOEPsi3nm42FD/oDYIZ2PQa4TNWWuWecxln6jlIQzpZTDMUeO1NicJP6lLn2TtRw==", "dev": true, "peerDependencies": { "prettier": ">=2.0", @@ -7703,9 +7704,9 @@ } }, "node_modules/react": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", - "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "version": "18.1.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.1.0.tgz", + "integrity": "sha512-4oL8ivCz5ZEPyclFQXaNksK3adutVS8l2xzZU0cqEFrE9Sb7fC0EFK5uEk74wIreL1DERyjvsU915j1pcT2uEQ==", "dependencies": { "loose-envify": "^1.1.0" }, @@ -7714,15 +7715,15 @@ } }, "node_modules/react-dom": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", - "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "version": "18.1.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.1.0.tgz", + "integrity": "sha512-fU1Txz7Budmvamp7bshe4Zi32d0ll7ect+ccxNu9FlObT605GOEB8BfO4tmRJ39R5Zj831VCpvQ05QPBW5yb+w==", "dependencies": { "loose-envify": "^1.1.0", - "scheduler": "^0.23.0" + "scheduler": "^0.22.0" }, "peerDependencies": { - "react": "^18.2.0" + "react": "^18.1.0" } }, "node_modules/react-hot-keys": { @@ -8045,9 +8046,9 @@ "dev": true }, "node_modules/scheduler": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", - "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.22.0.tgz", + "integrity": "sha512-6QAm1BgQI88NPYymgGQLCZgvep4FyePDWFpXVK+zNSUgHwlqpJy8VEh8Et0KxTACS4VWwMousBElAZOH9nkkoQ==", "dependencies": { "loose-envify": "^1.1.0" } @@ -8525,6 +8526,30 @@ "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", "dev": true }, + "node_modules/ts-unused-exports": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/ts-unused-exports/-/ts-unused-exports-8.0.0.tgz", + "integrity": "sha512-gylHFyJqC80PSb4zy35KTckykEW1vmKjnOHjBeX9iKBo4b/SzqQIcXXbYSuif4YMgNm6ewFF62VM1C9z0bGZPw==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "tsconfig-paths": "^3.9.0" + }, + "bin": { + "ts-unused-exports": "bin/ts-unused-exports" + }, + "funding": { + "url": "https://github.com/pzavolinsky/ts-unused-exports?sponsor=1" + }, + "peerDependencies": { + "typescript": ">=3.8.3" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": false + } + } + }, "node_modules/tsconfig-paths": { "version": "3.14.1", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", @@ -11003,9 +11028,9 @@ } }, "@types/node": { - "version": "18.0.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.0.0.tgz", - "integrity": "sha512-cHlGmko4gWLVI27cGJntjs/Sj8th9aYwplmZFwmmgYQQvL5NUsgVJG7OddLvNfLqYS31KFN0s3qlaD9qCaxACA==", + "version": "17.0.41", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.41.tgz", + "integrity": "sha512-xA6drNNeqb5YyV5fO3OAEsnXLfO7uF0whiOfPTz5AeDo8KeZFmODKnvwPymMNO8qE/an8pVY/O50tig2SQCrGw==", "dev": true }, "@types/nodemailer": { @@ -11030,9 +11055,9 @@ "dev": true }, "@types/react": { - "version": "18.0.14", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.14.tgz", - "integrity": "sha512-x4gGuASSiWmo0xjDLpm5mPb52syZHJx02VKbqUKdLmKtAwIh63XClGsiTI1K6DO5q7ox4xAsQrU+Gl3+gGXF9Q==", + "version": "18.0.12", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.12.tgz", + "integrity": "sha512-duF1OTASSBQtcigUvhuiTB1Ya3OvSy+xORCiEf20H0P0lzx+/KeVsA99U5UjLXSbyo1DRJDlLKqTeM1ngosqtg==", "dev": true, "requires": { "@types/prop-types": "*", @@ -14738,9 +14763,9 @@ "dev": true }, "prettier-plugin-organize-imports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-3.0.0.tgz", - "integrity": "sha512-juSCJs5TMOqGGPaN/A/1xzWFzRPH2LG1LPLCr64dzKaVnPafJdtgDNmDVlU+8A4LbQzVJg0DTvgA8swBuIUhlg==", + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-2.3.4.tgz", + "integrity": "sha512-R8o23sf5iVL/U71h9SFUdhdOEPsi3nm42FD/oDYIZ2PQa4TNWWuWecxln6jlIQzpZTDMUeO1NicJP6lLn2TtRw==", "dev": true, "requires": {} }, @@ -14819,20 +14844,20 @@ } }, "react": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", - "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "version": "18.1.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.1.0.tgz", + "integrity": "sha512-4oL8ivCz5ZEPyclFQXaNksK3adutVS8l2xzZU0cqEFrE9Sb7fC0EFK5uEk74wIreL1DERyjvsU915j1pcT2uEQ==", "requires": { "loose-envify": "^1.1.0" } }, "react-dom": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", - "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "version": "18.1.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.1.0.tgz", + "integrity": "sha512-fU1Txz7Budmvamp7bshe4Zi32d0ll7ect+ccxNu9FlObT605GOEB8BfO4tmRJ39R5Zj831VCpvQ05QPBW5yb+w==", "requires": { "loose-envify": "^1.1.0", - "scheduler": "^0.23.0" + "scheduler": "^0.22.0" } }, "react-hot-keys": { @@ -15059,9 +15084,9 @@ "dev": true }, "scheduler": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", - "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.22.0.tgz", + "integrity": "sha512-6QAm1BgQI88NPYymgGQLCZgvep4FyePDWFpXVK+zNSUgHwlqpJy8VEh8Et0KxTACS4VWwMousBElAZOH9nkkoQ==", "requires": { "loose-envify": "^1.1.0" } @@ -15428,6 +15453,16 @@ } } }, + "ts-unused-exports": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/ts-unused-exports/-/ts-unused-exports-8.0.0.tgz", + "integrity": "sha512-gylHFyJqC80PSb4zy35KTckykEW1vmKjnOHjBeX9iKBo4b/SzqQIcXXbYSuif4YMgNm6ewFF62VM1C9z0bGZPw==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "tsconfig-paths": "^3.9.0" + } + }, "tsconfig-paths": { "version": "3.14.1", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", diff --git a/package.json b/package.json index 88d4901..8c7c155 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,8 @@ "private": true, "scripts": { "dev": "next dev -p 12499", + "precommit": "npm run prettier && npm run unused-exports && npm run lint && npm run tsc && npm run generate && echo ALL PRECOMMIT CHECKS PASSED SUCCESSFULLY, LET\\'S FUCKING GO!", + "unused-exports": "ts-unused-exports ./tsconfig.json --excludePathsFromReport=src/pages --ignoreFiles=generated", "prebuild": "npm run generate", "build": "next build", "postbuild": "next-sitemap --config next-sitemap.config.js", @@ -25,8 +27,8 @@ "meilisearch": "^0.25.1", "next": "^12.1.6", "nodemailer": "^6.7.5", - "react": "18.2.0", - "react-dom": "18.2.0", + "react": "18.1.0", + "react-dom": "18.1.0", "react-hot-keys": "^2.7.2", "react-swipeable": "^7.0.0", "turndown": "^7.1.1" @@ -36,21 +38,22 @@ "@graphql-codegen/typescript": "2.5.1", "@graphql-codegen/typescript-graphql-request": "^4.4.10", "@graphql-codegen/typescript-operations": "^2.4.2", - "@types/node": "18.0.0", + "@types/node": "17.0.41", "@types/nodemailer": "^6.4.4", - "@types/react": "18.0.14", + "@types/react": "18.0.12", "@types/react-dom": "^18.0.5", "@types/turndown": "^5.0.1", - "@typescript-eslint/eslint-plugin": "^5.28.0", - "@typescript-eslint/parser": "^5.28.0", - "eslint": "^8.18.0", + "@typescript-eslint/eslint-plugin": "^5.27.1", + "@typescript-eslint/parser": "^5.27.1", + "eslint": "^8.17.0", "eslint-config-next": "12.1.6", "graphql": "^16.5.0", "next-sitemap": "^3.0.5", - "prettier": "^2.7.1", - "prettier-plugin-organize-imports": "^3.0.0", + "prettier": "^2.6.2", + "prettier-plugin-organize-imports": "^2.3.4", "prettier-plugin-tailwindcss": "^0.1.11", - "tailwindcss": "^3.1.3", - "typescript": "^4.7.4" + "tailwindcss": "^3.1.2", + "ts-unused-exports": "^8.0.0", + "typescript": "^4.7.3" } } diff --git a/src/components/AppLayout.tsx b/src/components/AppLayout.tsx index ea24828..07cb352 100644 --- a/src/components/AppLayout.tsx +++ b/src/components/AppLayout.tsx @@ -12,8 +12,6 @@ import { isUndefined, iterateMap, } from "helpers/others"; -// import { getClient, Indexes, search, SearchResult } from "helpers/search"; - import { useMediaMobile } from "hooks/useMediaQuery"; import { AnchorIds } from "hooks/useScrollTopOnChange"; import Head from "next/head"; @@ -415,31 +413,33 @@ export function AppLayout(props: Props): JSX.Element {

{langui.theme}

- -
@@ -456,22 +456,27 @@ export function AppLayout(props: Props): JSX.Element {

{langui.font_size}

- -
@@ -504,61 +509,7 @@ export function AppLayout(props: Props): JSX.Element {
- - {/* -
- TODO: add to langui -

{"Search"}

- -
- TODO: add to langui -
-

In news:

-
- {searchResult?.hits.map((hit) => ( - - ))} -
-
-
*/}
); } - -/* - * const [searchQuery, setSearchQuery] = useState(""); - * const [searchResult, setSearchResult] = useState(); - */ - -/* - * const client = getClient(); - * useEffect(() => { - * if (searchQuery.length > 1) { - * search(client, Indexes.Post, searchQuery).then((result) => { - * setSearchResult(result); - * }); - * } else { - * setSearchResult(undefined); - * } - * // eslint-disable-next-line react-hooks/exhaustive-deps - * }, [searchQuery]); - */ diff --git a/src/components/Inputs/Button.tsx b/src/components/Inputs/Button.tsx index 314c04c..4140312 100644 --- a/src/components/Inputs/Button.tsx +++ b/src/components/Inputs/Button.tsx @@ -58,7 +58,7 @@ export function Button(props: Props): JSX.Element { id={id} onClick={onClick} className={cJoin( - `component-button group grid select-none grid-flow-col place-content-center + `group grid select-none grid-flow-col place-content-center place-items-center gap-2 rounded-full border-[1px] border-dark py-3 px-4 leading-none text-dark transition-all`, cIf( diff --git a/src/components/Inputs/ButtonGroup.tsx b/src/components/Inputs/ButtonGroup.tsx index b252758..bb5e0db 100644 --- a/src/components/Inputs/ButtonGroup.tsx +++ b/src/components/Inputs/ButtonGroup.tsx @@ -1,39 +1,53 @@ +import { ToolTip } from "components/ToolTip"; import { cJoin } from "helpers/className"; - -import { useLayoutEffect, useRef } from "react"; +import { ConditionalWrapper, Wrapper } from "helpers/component"; +import { isDefinedAndNotEmpty } from "helpers/others"; +import { Button } from "./Button"; interface Props { - children: React.ReactNode; className?: string; + buttonsProps: (Parameters[0] & { + tooltip?: string | null | undefined; + })[]; } export function ButtonGroup(props: Props): JSX.Element { - const { children, className } = props; - const ref = useRef(null); - - useLayoutEffect(() => { - if (ref.current) { - const buttons = ref.current.querySelectorAll(".component-button"); - buttons.forEach((button, index) => { - button.classList.remove("rounded-full"); - button.classList.remove("border-[1px]"); - if (index === 0) { - button.classList.add("rounded-l-full"); - button.classList.add("border-l-[1px]"); - } else if (index === buttons.length - 1) { - button.classList.add("rounded-r-full"); - button.classList.add("border-r-[1px]"); - } else { - button.classList.add("rounded-none"); - } - button.classList.add("border-y-[1px]"); - }); - } - }, [children]); + const { buttonsProps, className } = props; return ( -
- {children} +
+ {buttonsProps.map((buttonProps, index) => ( + +
); } + +interface ToolTipWrapperProps { + text: string; +} + +function ToolTipWrapper(props: ToolTipWrapperProps & Wrapper) { + const { text, children } = props; + return ( + + <>{children} + + ); +} diff --git a/src/components/Library/ContentLine.tsx b/src/components/Library/ContentLine.tsx deleted file mode 100644 index ced8998..0000000 --- a/src/components/Library/ContentLine.tsx +++ /dev/null @@ -1,119 +0,0 @@ -import { Chip } from "components/Chip"; -import { Ico, Icon } from "components/Ico"; -import { Button } from "components/Inputs/Button"; -import { GetLibraryItemQuery } from "graphql/generated"; -import { AppStaticProps } from "graphql/getAppStaticProps"; -import { cIf, cJoin } from "helpers/className"; -import { prettyinlineTitle, prettySlug } from "helpers/formatters"; -import { filterHasAttributes } from "helpers/others"; - -import { useToggle } from "hooks/useToggle"; -import { useState } from "react"; - -interface Props { - content: NonNullable< - NonNullable< - NonNullable< - GetLibraryItemQuery["libraryItems"] - >["data"][number]["attributes"] - >["contents"] - >["data"][number]; - parentSlug: string; - langui: AppStaticProps["langui"]; -} - -export function ContentLine(props: Props): JSX.Element { - const { content, langui, parentSlug } = props; - - const [opened, setOpened] = useState(false); - const toggleOpened = useToggle(setOpened); - - if (content.attributes) { - return ( -
-
- -

- {content.attributes.content?.data?.attributes?.translations?.[0] - ? prettyinlineTitle( - content.attributes.content.data.attributes.translations[0] - ?.pre_title, - content.attributes.content.data.attributes.translations[0] - ?.title, - content.attributes.content.data.attributes.translations[0] - ?.subtitle - ) - : prettySlug(content.attributes.slug, props.parentSlug)} -

-
-
- {filterHasAttributes( - content.attributes.content?.data?.attributes?.categories?.data - ).map((category) => ( - {category.attributes.short} - ))} -
-

-

- {content.attributes.range[0]?.__typename === - "ComponentRangePageRange" - ? content.attributes.range[0].starting_page - : ""} -

- {content.attributes.content?.data?.attributes?.type?.data - ?.attributes && ( - - {content.attributes.content.data.attributes.type.data.attributes - .titles && - content.attributes.content.data.attributes.type.data.attributes - .titles.length > 0 - ? content.attributes.content.data.attributes.type.data - .attributes.titles[0]?.title - : prettySlug( - content.attributes.content.data.attributes.type.data - .attributes.slug - )} - - )} -
-
- - - {content.attributes.scan_set && - content.attributes.scan_set.length > 0 && ( -
-
- ); - } - return <>; -} diff --git a/src/components/Library/ScanSet.tsx b/src/components/Library/ScanSet.tsx index c79d7e0..73b5873 100644 --- a/src/components/Library/ScanSet.tsx +++ b/src/components/Library/ScanSet.tsx @@ -15,7 +15,7 @@ import { } from "helpers/others"; import { useSmartLanguage } from "hooks/useSmartLanguage"; -import { Fragment, useMemo } from "react"; +import { Fragment, useCallback, useMemo } from "react"; interface Props { openLightBox: (images: string[], index?: number) => void; @@ -49,42 +49,47 @@ export function ScanSet(props: 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, - transform: (item) => { - item.pages?.data.sort((a, b) => { - if ( - a.attributes && - b.attributes && - isDefinedAndNotEmpty(a.attributes.url) && - isDefinedAndNotEmpty(b.attributes.url) - ) { - let aName = getAssetFilename(a.attributes.url); - let bName = getAssetFilename(b.attributes.url); + const [selectedScan, LanguageSwitcher, languageSwitcherProps] = + useSmartLanguage({ + items: scanSet, + languages: languages, + languageExtractor: useCallback( + (item: NonNullable) => + item.language?.data?.attributes?.code, + [] + ), + transform: useCallback((item: NonNullable) => { + item.pages?.data.sort((a, b) => { + if ( + a.attributes && + b.attributes && + isDefinedAndNotEmpty(a.attributes.url) && + isDefinedAndNotEmpty(b.attributes.url) + ) { + let aName = getAssetFilename(a.attributes.url); + let bName = getAssetFilename(b.attributes.url); - /* - * If the number is a succession of 0s, make the number - * incrementally smaller than 0 (i.e: 00 becomes -1) - */ - if (aName.replaceAll("0", "").length === 0) { - aName = (1 - aName.length).toString(10); - } - if (bName.replaceAll("0", "").length === 0) { - bName = (1 - bName.length).toString(10); - } + /* + * If the number is a succession of 0s, make the number + * incrementally smaller than 0 (i.e: 00 becomes -1) + */ + if (aName.replaceAll("0", "").length === 0) { + aName = (1 - aName.length).toString(10); + } + if (bName.replaceAll("0", "").length === 0) { + bName = (1 - bName.length).toString(10); + } - if (isInteger(aName) && isInteger(bName)) { - return parseInt(aName, 10) - parseInt(bName, 10); + if (isInteger(aName) && isInteger(bName)) { + return parseInt(aName, 10) - parseInt(bName, 10); + } + return a.attributes.url.localeCompare(b.attributes.url); } - return a.attributes.url.localeCompare(b.attributes.url); - } - return 0; - }); - return item; - }, - }); + return 0; + }); + return item; + }, []), + }); const pages = useMemo( () => selectedScan && filterHasAttributes(selectedScan.pages?.data), @@ -120,7 +125,7 @@ export function ScanSet(props: Props): JSX.Element { /> )} - +

{langui.status}:

diff --git a/src/components/Library/ScanSetCover.tsx b/src/components/Library/ScanSetCover.tsx index 9fd12eb..d4f8bd0 100644 --- a/src/components/Library/ScanSetCover.tsx +++ b/src/components/Library/ScanSetCover.tsx @@ -11,7 +11,7 @@ import { getAssetURL, ImageQuality } from "helpers/img"; import { filterHasAttributes, getStatusDescription } from "helpers/others"; import { useSmartLanguage } from "hooks/useSmartLanguage"; -import { Fragment, useMemo } from "react"; +import { Fragment, useCallback, useMemo } from "react"; interface Props { openLightBox: (images: string[], index?: number) => void; @@ -29,11 +29,16 @@ interface Props { export function ScanSetCover(props: Props): JSX.Element { const { openLightBox, images, languages, langui } = props; - const [selectedScan, LanguageSwitcher] = useSmartLanguage({ - items: images, - languages: languages, - languageExtractor: (item) => item.language?.data?.attributes?.code, - }); + const [selectedScan, LanguageSwitcher, languageSwitcherProps] = + useSmartLanguage({ + items: images, + languages: languages, + languageExtractor: useCallback( + (item: NonNullable) => + item.language?.data?.attributes?.code, + [] + ), + }); const coverImages = useMemo(() => { const memo: UploadImageFragment[] = []; @@ -74,7 +79,7 @@ export function ScanSetCover(props: Props): JSX.Element {
- +

{langui.status}:

diff --git a/src/components/Markdown/TOC.tsx b/src/components/Markdown/TOC.tsx index fa70c93..bdde8fb 100644 --- a/src/components/Markdown/TOC.tsx +++ b/src/components/Markdown/TOC.tsx @@ -69,7 +69,7 @@ interface TOCInterface { children: TOCInterface[]; } -export function getTocFromMarkdawn(text: string, title?: string): TOCInterface { +function getTocFromMarkdawn(text: string, title?: string): TOCInterface { const toc: TOCInterface = { title: title ?? "Return to top", slug: slugify(title), diff --git a/src/components/PostPage.tsx b/src/components/PostPage.tsx index 9c95f07..1c9f7d6 100644 --- a/src/components/PostPage.tsx +++ b/src/components/PostPage.tsx @@ -4,7 +4,7 @@ import { prettySlug } from "helpers/formatters"; import { filterHasAttributes, getStatusDescription } from "helpers/others"; import { PostWithTranslations } from "helpers/types"; import { useSmartLanguage } from "hooks/useSmartLanguage"; -import { Fragment, useMemo } from "react"; +import { Fragment, useCallback, useMemo } from "react"; import { AppLayout } from "./AppLayout"; import { Chip } from "./Chip"; import { HorizontalLine } from "./HorizontalLine"; @@ -49,11 +49,16 @@ export function PostPage(props: Props): JSX.Element { } = props; const displayTitle = props.displayTitle ?? true; - const [selectedTranslation, LanguageSwitcher] = useSmartLanguage({ - items: post.translations, - languages: languages, - languageExtractor: (item) => item.language?.data?.attributes?.code, - }); + const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] = + useSmartLanguage({ + items: post.translations, + languages: languages, + languageExtractor: useCallback( + (item: NonNullable) => + item.language?.data?.attributes?.code, + [] + ), + }); const { thumbnail, body, title, excerpt } = useMemo( () => ({ @@ -156,7 +161,7 @@ export function PostPage(props: Props): JSX.Element { description={excerpt} langui={langui} categories={post.categories} - languageSwitcher={} + languageSwitcher={} /> @@ -165,7 +170,7 @@ export function PostPage(props: Props): JSX.Element { <> {displayLanguageSwitcher && (
- +
)} {displayTitle && ( @@ -189,6 +194,7 @@ export function PostPage(props: Props): JSX.Element { displayThumbnailHeader, displayTitle, excerpt, + languageSwitcherProps, langui, post.categories, prependBody, diff --git a/src/components/PreviewCard.tsx b/src/components/PreviewCard.tsx index 0295406..9d3b62f 100644 --- a/src/components/PreviewCard.tsx +++ b/src/components/PreviewCard.tsx @@ -17,6 +17,7 @@ import { ImageQuality } from "helpers/img"; import { useSmartLanguage } from "hooks/useSmartLanguage"; import Link from "next/link"; +import { useCallback } from "react"; import { Chip } from "./Chip"; import { Ico, Icon } from "./Ico"; import { Img } from "./Img"; @@ -290,15 +291,13 @@ export function PreviewCard(props: Props): JSX.Element { interface TranslatedProps extends Omit { - translations: - | { - pre_title?: string | null | undefined; - title: string | null | undefined; - subtitle?: string | null | undefined; - description?: string | null | undefined; - language: string | undefined; - }[] - | undefined; + translations: { + pre_title?: string | null | undefined; + title: string | null | undefined; + subtitle?: string | null | undefined; + description?: string | null | undefined; + language: string | undefined; + }[]; slug: string; languages: AppStaticProps["languages"]; } @@ -313,7 +312,10 @@ export function TranslatedPreviewCard(props: TranslatedProps): JSX.Element { const [selectedTranslation] = useSmartLanguage({ items: translations, languages: languages, - languageExtractor: (item) => item.language, + languageExtractor: useCallback( + (item: TranslatedProps["translations"][number]) => item.language, + [] + ), }); return ( diff --git a/src/components/PreviewLine.tsx b/src/components/PreviewLine.tsx index 377fa47..3ff73c0 100644 --- a/src/components/PreviewLine.tsx +++ b/src/components/PreviewLine.tsx @@ -5,6 +5,7 @@ import { ImageQuality } from "helpers/img"; import { useSmartLanguage } from "hooks/useSmartLanguage"; import Link from "next/link"; +import { useCallback } from "react"; import { Chip } from "./Chip"; import { Img } from "./Img"; @@ -19,7 +20,7 @@ interface Props { bottomChips?: string[]; } -export function PreviewLine(props: Props): JSX.Element { +function PreviewLine(props: Props): JSX.Element { const { href, thumbnail, @@ -76,14 +77,13 @@ export function PreviewLine(props: Props): JSX.Element { interface TranslatedProps extends Omit { - translations: - | { - pre_title?: string | null | undefined; - title: string | null | undefined; - subtitle?: string | null | undefined; - language: string | undefined; - }[] - | undefined; + translations: { + pre_title?: string | null | undefined; + title: string | null | undefined; + subtitle?: string | null | undefined; + language: string | undefined; + }[]; + slug: string; languages: AppStaticProps["languages"]; } @@ -98,7 +98,10 @@ export function TranslatedPreviewLine(props: TranslatedProps): JSX.Element { const [selectedTranslation] = useSmartLanguage({ items: translations, languages: languages, - languageExtractor: (item) => item.language, + languageExtractor: useCallback( + (item: TranslatedProps["translations"][number]) => item.language, + [] + ), }); return ( diff --git a/src/components/Wiki/DefinitionCard.tsx b/src/components/Wiki/DefinitionCard.tsx index 4380132..7e60cd2 100644 --- a/src/components/Wiki/DefinitionCard.tsx +++ b/src/components/Wiki/DefinitionCard.tsx @@ -3,16 +3,15 @@ import { ToolTip } from "components/ToolTip"; import { AppStaticProps } from "graphql/getAppStaticProps"; import { getStatusDescription } from "helpers/others"; import { useSmartLanguage } from "hooks/useSmartLanguage"; +import { useCallback } from "react"; interface Props { source?: string; - translations: - | { - language: string | undefined; - definition: string | null | undefined; - status: string | undefined; - }[] - | undefined; + translations: { + language: string | undefined; + definition: string | null | undefined; + status: string | undefined; + }[]; languages: AppStaticProps["languages"]; langui: AppStaticProps["langui"]; index: number; @@ -21,11 +20,15 @@ interface Props { export default function DefinitionCard(props: Props): JSX.Element { const { source, translations = [], languages, langui, index } = props; - const [selectedTranslation, LanguageSwitcher] = useSmartLanguage({ - items: translations, - languages: languages, - languageExtractor: (item) => item.language, - }); + const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] = + useSmartLanguage({ + items: translations, + languages: languages, + languageExtractor: useCallback( + (item: Props["translations"][number]) => item.language, + [] + ), + }); return ( <> @@ -40,7 +43,9 @@ export default function DefinitionCard(props: Props): JSX.Element { {selectedTranslation.status} )} - {translations.length > 1 && } + {translations.length > 1 && ( + + )}

{`${langui.source}: ${source}`}

diff --git a/src/contexts/AppLayoutContext.tsx b/src/contexts/AppLayoutContext.tsx index 4de374d..9c7dc64 100644 --- a/src/contexts/AppLayoutContext.tsx +++ b/src/contexts/AppLayoutContext.tsx @@ -106,8 +106,6 @@ const initialState: RequiredNonNullable = { const AppContext = React.createContext(initialState); -export default AppContext; - export function useAppLayout(): AppLayoutState { return useContext(AppContext); } diff --git a/src/helpers/contents.ts b/src/helpers/contents.ts deleted file mode 100644 index ca98d27..0000000 --- a/src/helpers/contents.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { ContentWithTranslations } from "./types"; - -type Group = NonNullable< - NonNullable< - NonNullable< - NonNullable["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; -} diff --git a/src/helpers/formatters.ts b/src/helpers/formatters.ts index dd04b2f..bc71cdf 100644 --- a/src/helpers/formatters.ts +++ b/src/helpers/formatters.ts @@ -242,24 +242,12 @@ export function prettyLanguage( return result; } -export function prettyLanguageToCode( - prettyLanguage: string, - languages: AppStaticProps["languages"] -): string { - let result = prettyLanguage; - languages.forEach((language) => { - if (language?.attributes?.localized_name === prettyLanguage) - result = language.attributes.code; - }); - return result; -} - export function prettyURL(url: string): string { let domain = new URL(url); return domain.hostname.replace("www.", ""); } -export function capitalizeString(string: string): string { +function capitalizeString(string: string): string { function capitalizeWord(word: string): string { return word.charAt(0).toUpperCase() + word.substring(1); } diff --git a/src/helpers/img.ts b/src/helpers/img.ts index b2687cb..b0db378 100644 --- a/src/helpers/img.ts +++ b/src/helpers/img.ts @@ -7,7 +7,7 @@ export enum ImageQuality { Og = "og", } -export interface OgImage { +interface OgImage { image: string; width: number; height: number; @@ -34,7 +34,7 @@ export function getAssetURL(url: string, quality: ImageQuality): string { return process.env.NEXT_PUBLIC_URL_IMG + newUrl; } -export function getImgSizesByMaxSize( +function getImgSizesByMaxSize( width: number, height: number, maxSize: number diff --git a/src/helpers/libraryItem.ts b/src/helpers/libraryItem.ts index 19f4933..4c56a8f 100644 --- a/src/helpers/libraryItem.ts +++ b/src/helpers/libraryItem.ts @@ -5,7 +5,9 @@ import { prettyinlineTitle, prettyDate } from "./formatters"; import { convertPrice } from "./numbers"; import { isDefined, mapRemoveEmptyValues } from "./others"; import { LibraryItemUserStatus } from "./types"; -type Items = NonNullable["data"]; +import LibraryPage from "../pages/library/index"; + +type Items = Parameters[0]["items"]; type GroupLibraryItems = Map; export function getGroups( diff --git a/src/helpers/search.ts b/src/helpers/search.ts deleted file mode 100644 index 40f6a05..0000000 --- a/src/helpers/search.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { UploadImageFragment } from "graphql/generated"; -import { MeiliSearch, SearchResponse } from "meilisearch"; -import { prettySlug } from "./formatters"; - -export enum Indexes { - ChronologyEra = "chronology-era", - ChronologyItem = "chronology-item", - Content = "content", - GlossaryItem = "glossary-item", - LibraryItem = "library-item", - MerchItem = "merch-item", - Post = "post", - Video = "video", - VideoChannel = "video-channel", - WeaponStory = "weapon-story", -} - -export function getClient() { - return new MeiliSearch({ - host: process.env.NEXT_PUBLIC_URL_SEARCH ?? "", - apiKey: "", - }); -} - -export async function getIndexes(client: MeiliSearch) { - return await client.getIndexes(); -} - -export async function search( - client: MeiliSearch, - indexName: Indexes, - query: string -) { - const index = await client.getIndex(indexName); - const results = await index.search(query); - return processSearchResults(results, indexName); -} - -export type SearchResult = { - hits: { - id: string; - href: string; - title: string; - thumbnail?: UploadImageFragment; - }[]; - indexName: string; -}; - -export function processSearchResults( - result: SearchResponse>, - indexName: Indexes -): SearchResult { - return { - hits: result.hits.map((hit) => { - switch (indexName) { - case Indexes.Post: { - return { - id: hit.id, - title: - hit.translations.length > 0 - ? hit.translations[0].title - : prettySlug(hit.slug), - href: `/news/${hit.slug}`, - thumbnail: hit.thumbnail, - }; - } - default: { - return { id: hit.id, title: prettySlug(hit.slug), href: "error" }; - } - } - }), - indexName: indexName, - }; -} diff --git a/src/helpers/types.ts b/src/helpers/types.ts index 68714b9..1a836b0 100644 --- a/src/helpers/types.ts +++ b/src/helpers/types.ts @@ -3,7 +3,6 @@ import { GetPostQuery, GetWikiPageQuery, } from "graphql/generated"; -import React from "react"; type Post = NonNullable< NonNullable["data"][number]["attributes"] diff --git a/src/hooks/useMediaQuery.ts b/src/hooks/useMediaQuery.ts index e833360..244d025 100644 --- a/src/hooks/useMediaQuery.ts +++ b/src/hooks/useMediaQuery.ts @@ -1,7 +1,7 @@ import { useEffect, useState } from "react"; import { breaks } from "../../design.config"; -export function useMediaQuery(query: string): boolean { +function useMediaQuery(query: string): boolean { function getMatches(query: string): boolean { // Prevents SSR issues if (typeof window !== "undefined") { @@ -33,6 +33,7 @@ export function useMediaQuery(query: string): boolean { return matches; } +// ts-unused-exports:disable-next-line export function useMediaThin() { return useMediaQuery(breaks.thin.raw); } diff --git a/src/hooks/useSmartLanguage.ts b/src/hooks/useSmartLanguage.ts new file mode 100644 index 0000000..001d00a --- /dev/null +++ b/src/hooks/useSmartLanguage.ts @@ -0,0 +1,84 @@ +import { LanguageSwitcher } from "components/Inputs/LanguageSwitcher"; +import { useAppLayout } from "contexts/AppLayoutContext"; +import { AppStaticProps } from "graphql/getAppStaticProps"; +import { filterDefined, isDefined } from "helpers/others"; + +import { useRouter } from "next/router"; +import { useEffect, useMemo, useState } from "react"; + +interface Props { + items: T[]; + languages: AppStaticProps["languages"]; + languageExtractor: (item: NonNullable) => string | undefined; + transform?: (item: NonNullable) => NonNullable; +} + +function getPreferredLanguage( + preferredLanguages: (string | undefined)[], + availableLanguages: Map +): number | undefined { + for (const locale of preferredLanguages) { + if (isDefined(locale) && availableLanguages.has(locale)) { + return availableLanguages.get(locale); + } + } + return undefined; +} + +export function useSmartLanguage( + props: Props +): [ + T | undefined, + typeof LanguageSwitcher, + Parameters[0] +] { + const { + items, + languageExtractor, + languages, + transform = (item) => item, + } = props; + const { preferredLanguages } = useAppLayout(); + const router = useRouter(); + + const availableLocales = useMemo(() => { + const memo = new Map(); + filterDefined(items).map((elem, index) => { + const result = languageExtractor(elem); + if (isDefined(result)) memo.set(result, index); + }); + return memo; + }, [items, languageExtractor]); + + const [selectedTranslationIndex, setSelectedTranslationIndex] = useState< + number | undefined + >(); + + useEffect(() => { + setSelectedTranslationIndex( + getPreferredLanguage( + preferredLanguages ?? [router.locale], + availableLocales + ) + ); + }, [preferredLanguages, availableLocales, router.locale]); + + const selectedTranslation = useMemo(() => { + if (isDefined(selectedTranslationIndex)) { + const item = items[selectedTranslationIndex]; + if (isDefined(item)) { + return transform(item); + } + } + return undefined; + }, [items, selectedTranslationIndex, transform]); + + const languageSwitcherProps = { + languages: languages, + locales: availableLocales, + localesIndex: selectedTranslationIndex, + onLanguageChanged: setSelectedTranslationIndex, + }; + + return [selectedTranslation, LanguageSwitcher, languageSwitcherProps]; +} diff --git a/src/hooks/useSmartLanguage.tsx b/src/hooks/useSmartLanguage.tsx index 6f8d3df..001d00a 100644 --- a/src/hooks/useSmartLanguage.tsx +++ b/src/hooks/useSmartLanguage.tsx @@ -27,7 +27,11 @@ function getPreferredLanguage( export function useSmartLanguage( props: Props -): [T | undefined, () => JSX.Element] { +): [ + T | undefined, + typeof LanguageSwitcher, + Parameters[0] +] { const { items, languageExtractor, @@ -52,12 +56,10 @@ export function useSmartLanguage( useEffect(() => { setSelectedTranslationIndex( - (current) => - current ?? - getPreferredLanguage( - preferredLanguages ?? [router.locale], - availableLocales - ) + getPreferredLanguage( + preferredLanguages ?? [router.locale], + availableLocales + ) ); }, [preferredLanguages, availableLocales, router.locale]); @@ -71,15 +73,12 @@ export function useSmartLanguage( return undefined; }, [items, selectedTranslationIndex, transform]); - return [ - selectedTranslation, - () => ( - - ), - ]; + const languageSwitcherProps = { + languages: languages, + locales: availableLocales, + localesIndex: selectedTranslationIndex, + onLanguageChanged: setSelectedTranslationIndex, + }; + + return [selectedTranslation, LanguageSwitcher, languageSwitcherProps]; } diff --git a/src/pages/contents/[slug]/index.tsx b/src/pages/contents/[slug]/index.tsx index 17fc91d..a6f926e 100644 --- a/src/pages/contents/[slug]/index.tsx +++ b/src/pages/contents/[slug]/index.tsx @@ -17,7 +17,6 @@ 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 { getDescription } from "helpers/description"; import { prettyinlineTitle, @@ -27,6 +26,7 @@ import { } from "helpers/formatters"; import { isUntangibleGroupItem } from "helpers/libraryItem"; import { + filterDefined, filterHasAttributes, getStatusDescription, isDefinedAndNotEmpty, @@ -40,21 +40,34 @@ import { GetStaticPathsResult, GetStaticPropsContext, } from "next"; -import { Fragment, useMemo } from "react"; +import { Fragment, useCallback, useMemo } from "react"; interface Props extends AppStaticProps { content: ContentWithTranslations; } +type Group = NonNullable< + NonNullable< + NonNullable< + NonNullable["data"] + >["attributes"] + >["contents"] +>["data"]; + export default function Content(props: Props): JSX.Element { const { langui, content, languages, currencies } = props; const isMobile = useMediaMobile(); - const [selectedTranslation, LanguageSwitcher] = useSmartLanguage({ - items: content.translations, - languages: languages, - languageExtractor: (item) => item.language?.data?.attributes?.code, - }); + const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] = + useSmartLanguage({ + items: content.translations, + languages: languages, + languageExtractor: useCallback( + (item: NonNullable) => + item.language?.data?.attributes?.code, + [] + ), + }); useScrollTopOnChange(AnchorIds.ContentPanel, [selectedTranslation]); @@ -305,7 +318,7 @@ export default function Content(props: Props): JSX.Element { type={content.type} categories={content.categories} langui={langui} - languageSwitcher={} + languageSwitcher={} /> {previousContent?.attributes && ( @@ -315,14 +328,14 @@ export default function Content(props: Props): JSX.Element { ({ - pre_title: translation?.pre_title, - title: translation?.title, - subtitle: translation?.subtitle, - language: translation?.language?.data?.attributes?.code, - }) - )} + translations={filterDefined( + previousContent.attributes.translations + ).map((translation) => ({ + pre_title: translation.pre_title, + title: translation.title, + subtitle: translation.subtitle, + language: translation.language?.data?.attributes?.code, + }))} slug={previousContent.attributes.slug} languages={languages} thumbnail={ @@ -368,14 +381,14 @@ export default function Content(props: Props): JSX.Element { ({ - pre_title: translation?.pre_title, - title: translation?.title, - subtitle: translation?.subtitle, - language: translation?.language?.data?.attributes?.code, - }) - )} + translations={filterDefined( + nextContent.attributes.translations + ).map((translation) => ({ + pre_title: translation.pre_title, + title: translation.title, + subtitle: translation.subtitle, + language: translation.language?.data?.attributes?.code, + }))} slug={nextContent.attributes.slug} languages={languages} thumbnail={nextContent.attributes.thumbnail?.data?.attributes} @@ -413,11 +426,16 @@ export default function Content(props: Props): JSX.Element { content.thumbnail?.data?.attributes, content.type, isMobile, + languageSwitcherProps, languages, langui, nextContent?.attributes, previousContent?.attributes, - selectedTranslation, + selectedTranslation?.description, + selectedTranslation?.pre_title, + selectedTranslation?.subtitle, + selectedTranslation?.text_set?.text, + selectedTranslation?.title, ] ); @@ -487,3 +505,23 @@ export async function getStaticPaths( fallback: "blocking", }; } + +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; +} + +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; +} diff --git a/src/pages/contents/index.tsx b/src/pages/contents/index.tsx index ad9133e..34e4aba 100644 --- a/src/pages/contents/index.tsx +++ b/src/pages/contents/index.tsx @@ -9,7 +9,7 @@ import { } from "components/Panels/ContentPanel"; import { SubPanel } from "components/Panels/SubPanel"; import { TranslatedPreviewCard } 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"; @@ -27,6 +27,7 @@ import { mapRemoveEmptyValues, } from "helpers/others"; import { ContentPlaceholder } from "components/PanelComponents/ContentPlaceholder"; +import { GetContentsQuery } from "graphql/generated"; interface Props extends AppStaticProps { contents: NonNullable["data"]; @@ -194,47 +195,53 @@ export default function Contents(props: Props): JSX.Element { > {filterHasAttributes(items).map((item) => ( - ({ - pre_title: translation?.pre_title, - title: translation?.title, - subtitle: translation?.subtitle, - language: - translation?.language?.data?.attributes?.code, - }) - )} - slug={item.attributes.slug} - languages={languages} - thumbnail={item.attributes.thumbnail?.data?.attributes} - thumbnailAspectRatio="3/2" - thumbnailForceAspectRatio - stackNumber={ - effectiveCombineRelatedContent && - item.attributes.group?.data?.attributes?.combine === - true - ? item.attributes.group.data.attributes.contents - ?.data.length - : 0 - } - topChips={ - item.attributes.type?.data?.attributes - ? [ - item.attributes.type.data.attributes.titles?.[0] - ? item.attributes.type.data.attributes - .titles[0]?.title - : prettySlug( - item.attributes.type.data.attributes.slug - ), - ] - : undefined - } - bottomChips={item.attributes.categories?.data.map( - (category) => category.attributes?.short ?? "" - )} - keepInfoVisible={keepInfoVisible} - /> + {item.attributes.translations && ( + ({ + pre_title: translation?.pre_title, + title: translation?.title, + subtitle: translation?.subtitle, + language: + translation?.language?.data?.attributes?.code, + }) + )} + slug={item.attributes.slug} + languages={languages} + thumbnail={ + item.attributes.thumbnail?.data?.attributes + } + thumbnailAspectRatio="3/2" + thumbnailForceAspectRatio + stackNumber={ + effectiveCombineRelatedContent && + item.attributes.group?.data?.attributes?.combine === + true + ? item.attributes.group.data.attributes.contents + ?.data.length + : 0 + } + topChips={ + item.attributes.type?.data?.attributes + ? [ + item.attributes.type.data.attributes + .titles?.[0] + ? item.attributes.type.data.attributes + .titles[0]?.title + : prettySlug( + item.attributes.type.data.attributes + .slug + ), + ] + : undefined + } + bottomChips={item.attributes.categories?.data.map( + (category) => category.attributes?.short ?? "" + )} + keepInfoVisible={keepInfoVisible} + /> + )} ))}
@@ -243,14 +250,7 @@ export default function Contents(props: Props): JSX.Element { )} ), - [ - effectiveCombineRelatedContent, - groups, - keepInfoVisible, - languages, - langui.result, - langui.results, - ] + [effectiveCombineRelatedContent, groups, keepInfoVisible, languages, langui] ); return ( @@ -287,7 +287,7 @@ export async function getStaticProps( }; } -function getGroups( +export function getGroups( langui: AppStaticProps["langui"], groupByType: number, items: Props["contents"] @@ -349,7 +349,7 @@ function getGroups( return mapRemoveEmptyValues(groups); } -function filterContents( +export function filterContents( contents: Props["contents"], combineRelatedContent: boolean, searchName: string diff --git a/src/pages/library/[slug]/index.tsx b/src/pages/library/[slug]/index.tsx index 660cc72..a37ee64 100644 --- a/src/pages/library/[slug]/index.tsx +++ b/src/pages/library/[slug]/index.tsx @@ -4,7 +4,6 @@ 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 { PreviewCardCTAs } from "components/Library/PreviewCardCTAs"; import { NavOption } from "components/PanelComponents/NavOption"; import { @@ -31,14 +30,17 @@ import { prettyItemSubType, prettyItemType, prettyPrice, + prettySlug, prettyURL, } from "helpers/formatters"; import { getAssetURL, ImageQuality } from "helpers/img"; import { convertMmToInch } from "helpers/numbers"; import { + filterDefined, filterHasAttributes, isDefined, isDefinedAndNotEmpty, + isUndefined, sortContent, } from "helpers/others"; @@ -49,10 +51,14 @@ import { GetStaticPathsResult, GetStaticPropsContext, } from "next"; -import { Fragment, useMemo, useState } from "react"; +import { Fragment, useCallback, useMemo, useState } from "react"; import { isUntangibleGroupItem } from "helpers/libraryItem"; import { useMediaHoverable } from "hooks/useMediaQuery"; import { WithLabel } from "components/Inputs/WithLabel"; +import { useToggle } from "hooks/useToggle"; +import { Ico, Icon } from "components/Ico"; +import { cJoin, cIf } from "helpers/className"; +import { useSmartLanguage } from "hooks/useSmartLanguage"; interface Props extends AppStaticProps { item: NonNullable< @@ -66,7 +72,7 @@ interface Props extends AppStaticProps { } export default function LibrarySlug(props: Props): JSX.Element { - const { item, itemId, langui, currencies } = props; + const { item, itemId, langui, currencies, languages } = props; const appLayout = useAppLayout(); const hoverable = useMediaHoverable(); const [openLightBox, LightBox] = useLightBox(); @@ -501,14 +507,56 @@ export default function LibrarySlug(props: Props): JSX.Element { /> )}
- {item.contents.data.map((content) => ( - - ))} + {filterHasAttributes(item.contents.data).map( + (rangedContent) => ( + ({ + pre_title: translation.pre_title, + title: translation.title, + subtitle: translation.subtitle, + language: + translation.language?.data?.attributes?.code, + })), + categories: filterHasAttributes( + rangedContent.attributes.content.data.attributes + .categories?.data + ).map((category) => category.attributes.short), + type: + rangedContent.attributes.content.data.attributes + .type?.data?.attributes?.titles?.[0]?.title ?? + prettySlug( + rangedContent.attributes.content.data + .attributes.type?.data?.attributes?.slug + ), + slug: rangedContent.attributes.content.data + .attributes.slug, + } + : undefined + } + langui={langui} + rangeStart={ + rangedContent.attributes.range[0]?.__typename === + "ComponentRangePageRange" + ? `${rangedContent.attributes.range[0].starting_page}` + : "" + } + slug={rangedContent.attributes.slug} + parentSlug={item.slug} + key={rangedContent.id} + languages={languages} + hasScanSet={ + isDefined(rangedContent.attributes.scan_set) && + rangedContent.attributes.scan_set.length > 0 + } + /> + ) + )}
)} @@ -517,16 +565,31 @@ export default function LibrarySlug(props: Props): JSX.Element { ), [ LightBox, - openLightBox, - appLayout.currency, - currencies, - displayOpenScans, - hoverable, - isVariantSet, - item, - itemId, - keepInfoVisible, langui, + item.thumbnail?.data?.attributes, + item.subitem_of?.data, + item.title, + item.subtitle, + item.metadata, + item.descriptions, + item.urls, + item.gallery, + item.release_date, + item.price, + item.categories, + item.size, + item.subitems, + item.contents, + item.slug, + itemId, + currencies, + appLayout.currency, + isVariantSet, + hoverable, + keepInfoVisible, + displayOpenScans, + openLightBox, + languages, ] ); @@ -582,3 +645,112 @@ export async function getStaticPaths( fallback: "blocking", }; } + +interface ContentLineProps { + content?: { + translations: { + pre_title: string | null | undefined; + title: string; + subtitle: string | null | undefined; + language: string | undefined; + }[]; + categories?: string[]; + type?: string; + slug: string; + }; + rangeStart: string; + parentSlug: string; + slug: string; + langui: AppStaticProps["langui"]; + languages: AppStaticProps["languages"]; + hasScanSet: boolean; +} + +export function ContentLine(props: ContentLineProps): JSX.Element { + const { + rangeStart, + content, + langui, + languages, + hasScanSet, + slug, + parentSlug, + } = props; + + const [opened, setOpened] = useState(false); + const toggleOpened = useToggle(setOpened); + + const [selectedTranslation] = useSmartLanguage({ + items: content?.translations ?? [], + languages: languages, + languageExtractor: useCallback( + ( + item: NonNullable["translations"][number] + ) => item.language, + [] + ), + }); + + return ( +
+ +
+ + + {hasScanSet || isDefined(content) ? ( + <> + {hasScanSet && ( +
+
+ ); + + return <>; +} diff --git a/src/pages/library/index.tsx b/src/pages/library/index.tsx index 5c4f8b4..daff476 100644 --- a/src/pages/library/index.tsx +++ b/src/pages/library/index.tsx @@ -194,36 +194,36 @@ export default function Library(props: Props): JSX.Element { /> )} - - -