diff --git a/src/components/Library/ScanSet.tsx b/src/components/Library/ScanSet.tsx index c3c8392..1dd924d 100644 --- a/src/components/Library/ScanSet.tsx +++ b/src/components/Library/ScanSet.tsx @@ -1,15 +1,17 @@ import Chip from "components/Chip"; -import Img, { getAssetURL, ImageQuality } from "components/Img"; +import Img, { + getAssetFilename, + getAssetURL, + ImageQuality, +} from "components/Img"; import Button from "components/Inputs/Button"; -import LanguageSwitcher from "components/Inputs/LanguageSwitcher"; import RecorderChip from "components/RecorderChip"; import ToolTip from "components/ToolTip"; -import { useAppLayout } from "contexts/AppLayoutContext"; import { GetLibraryItemScansQuery } from "graphql/generated"; -import { useRouter } from "next/router"; +import useSmartLanguage from "hooks/useSmartLanguage"; import { AppStaticProps } from "queries/getAppStaticProps"; -import { getPreferredLanguage, getStatusDescription } from "queries/helpers"; -import { Dispatch, SetStateAction, useEffect, useMemo, useState } from "react"; +import { getStatusDescription, isInteger } from "queries/helpers"; +import { Dispatch, SetStateAction } from "react"; interface Props { setLightboxOpen: Dispatch>; @@ -62,52 +64,28 @@ export default function ScanSet(props: Props): JSX.Element { langui, content, } = props; - const appLayout = useAppLayout(); - const router = useRouter(); - const [selectedScan, setSelectedScan] = useState(); - const scanLocales: Map = new Map(); - - const [selectedScanIndex, setSelectedScanIndex] = useState< - number | undefined - >(); - - scanSet.map((scan, index) => { - if (scan?.language?.data?.attributes?.code) { - scanLocales.set(scan.language.data.attributes.code, index); - } - }); - - useMemo(() => { - setSelectedScanIndex( - getPreferredLanguage( - appLayout.preferredLanguages ?? [router.locale], - scanLocales - ) - ); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [appLayout.preferredLanguages]); - - useEffect(() => { - if (selectedScanIndex !== undefined) { - const selectedScanSet = scanSet[selectedScanIndex]; - selectedScanSet?.pages?.data.sort((a, b) => { - function isInteger(value: string): boolean { - // eslint-disable-next-line require-unicode-regexp - return /^\d+$/.test(value); - } - function getFileName(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]; - } + 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?.url && b.attributes?.url) { - const aName = getFileName(a.attributes.url); - const bName = getFileName(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 (isInteger(aName) && isInteger(bName)) { return parseInt(aName, 10) - parseInt(bName, 10); } @@ -115,10 +93,9 @@ export default function ScanSet(props: Props): JSX.Element { } return 0; }); - setSelectedScan(selectedScanSet); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [selectedScanIndex]); + return item; + }, + }); return ( <> @@ -144,12 +121,7 @@ export default function ScanSet(props: Props): JSX.Element { )} - +

{langui.status}:

diff --git a/src/components/Library/ScanSetCover.tsx b/src/components/Library/ScanSetCover.tsx index 4cbc812..6cea335 100644 --- a/src/components/Library/ScanSetCover.tsx +++ b/src/components/Library/ScanSetCover.tsx @@ -1,17 +1,15 @@ import Chip from "components/Chip"; import Img, { getAssetURL, ImageQuality } from "components/Img"; -import LanguageSwitcher from "components/Inputs/LanguageSwitcher"; import RecorderChip from "components/RecorderChip"; import ToolTip from "components/ToolTip"; -import { useAppLayout } from "contexts/AppLayoutContext"; import { GetLibraryItemScansQuery, UploadImageFragment, } from "graphql/generated"; -import { useRouter } from "next/router"; +import useSmartLanguage from "hooks/useSmartLanguage"; import { AppStaticProps } from "queries/getAppStaticProps"; -import { getPreferredLanguage, getStatusDescription } from "queries/helpers"; -import { Dispatch, SetStateAction, useEffect, useMemo, useState } from "react"; +import { getStatusDescription } from "queries/helpers"; +import { Dispatch, SetStateAction } from "react"; interface Props { setLightboxOpen: Dispatch>; @@ -40,38 +38,13 @@ export default function ScanSetCover(props: Props): JSX.Element { languages, langui, } = props; - const appLayout = useAppLayout(); - const router = useRouter(); - const [selectedScan, setSelectedScan] = useState(); - const scanLocales: Map = new Map(); - - const [selectedScanIndex, setSelectedScanIndex] = useState< - number | undefined - >(); - - images.map((scan, index) => { - if (scan?.language?.data?.attributes?.code) { - scanLocales.set(scan.language.data.attributes.code, index); - } + const [selectedScan, LanguageSwitcher] = useSmartLanguage({ + items: images, + languages: languages, + languageExtractor: (item) => item?.language?.data?.attributes?.code, }); - useMemo(() => { - setSelectedScanIndex( - getPreferredLanguage( - appLayout.preferredLanguages ?? [router.locale], - scanLocales - ) - ); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [appLayout.preferredLanguages]); - - useEffect(() => { - if (selectedScanIndex !== undefined) - setSelectedScan(images[selectedScanIndex]); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [selectedScanIndex]); - const coverImages: UploadImageFragment[] = []; if (selectedScan?.obi_belt?.full?.data?.attributes) coverImages.push(selectedScan.obi_belt.full.data.attributes); @@ -105,12 +78,7 @@ export default function ScanSetCover(props: Props): JSX.Element {
- +

{langui.status}:

diff --git a/src/components/Post.tsx b/src/components/Post.tsx index 570302f..f58e7c0 100644 --- a/src/components/Post.tsx +++ b/src/components/Post.tsx @@ -1,17 +1,10 @@ -import { useAppLayout } from "contexts/AppLayoutContext"; import { GetPostQuery } from "graphql/generated"; -import { useRouter } from "next/router"; +import useSmartLanguage from "hooks/useSmartLanguage"; import { AppStaticProps } from "queries/getAppStaticProps"; -import { - getPreferredLanguage, - getStatusDescription, - prettySlug, -} from "queries/helpers"; -import { useEffect, useMemo, useState } from "react"; +import { getStatusDescription, prettySlug } from "queries/helpers"; import AppLayout from "./AppLayout"; import Chip from "./Chip"; import HorizontalLine from "./HorizontalLine"; -import LanguageSwitcher from "./Inputs/LanguageSwitcher"; import Markdawn from "./Markdown/Markdawn"; import TOC from "./Markdown/TOC"; import ReturnButton, { ReturnButtonType } from "./PanelComponents/ReturnButton"; @@ -23,9 +16,12 @@ import ToolTip from "./ToolTip"; interface Props { post: Exclude< - GetPostQuery["posts"], + Exclude< + GetPostQuery["posts"], + null | undefined + >["data"][number]["attributes"], null | undefined - >["data"][number]["attributes"]; + >; langui: AppStaticProps["langui"]; languages: AppStaticProps["languages"]; currencies: AppStaticProps["currencies"]; @@ -56,54 +52,18 @@ export default function Post(props: Props): JSX.Element { } = props; const displayTitle = props.displayTitle ?? true; - const appLayout = useAppLayout(); - const router = useRouter(); - - const [selectedTranslation, setSelectedTranslation] = useState< - | Exclude< - Exclude["translations"], - null | undefined - >[number] - >(); - const translationLocales: Map = new Map(); - - const [selectedTranslationIndex, setSelectedTranslationIndex] = useState< - number | undefined - >(); - - if (post?.translations) { - post.translations.map((translation, index) => { - if (translation?.language?.data?.attributes?.code) { - translationLocales.set( - translation.language.data.attributes.code, - index - ); - } - }); - } - - useMemo(() => { - setSelectedTranslationIndex( - getPreferredLanguage( - appLayout.preferredLanguages ?? [router.locale], - translationLocales - ) - ); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [appLayout.preferredLanguages]); - - useEffect(() => { - if (selectedTranslationIndex !== undefined) - setSelectedTranslation(post?.translations?.[selectedTranslationIndex]); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [selectedTranslationIndex]); + const [selectedTranslation, LanguageSwitcher] = useSmartLanguage({ + items: post.translations, + languages: languages, + languageExtractor: (item) => item?.language?.data?.attributes?.code, + }); const thumbnail = selectedTranslation?.thumbnail?.data?.attributes ?? - post?.thumbnail?.data?.attributes; + post.thumbnail?.data?.attributes; const body = selectedTranslation?.body ?? ""; - const title = selectedTranslation?.title ?? prettySlug(post?.slug); + const title = selectedTranslation?.title ?? prettySlug(post.slug); const except = selectedTranslation?.excerpt ?? ""; const subPanel = @@ -137,7 +97,7 @@ export default function Post(props: Props): JSX.Element {
)} - {post?.authors && post.authors.data.length > 0 && ( + {post.authors && post.authors.data.length > 0 && (

{"Authors"}:

@@ -183,15 +143,8 @@ export default function Post(props: Props): JSX.Element { title={title} description={except} langui={langui} - categories={post?.categories} - languageSwitcher={ - - } + categories={post.categories} + languageSwitcher={} /> @@ -200,12 +153,7 @@ export default function Post(props: Props): JSX.Element { <> {displayLanguageSwitcher && (
- +
)} {displayTitle && ( diff --git a/src/components/PostPage.tsx b/src/components/PostPage.tsx new file mode 100644 index 0000000..da7b5f2 --- /dev/null +++ b/src/components/PostPage.tsx @@ -0,0 +1,184 @@ +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 +>; + +interface Props { + post: Post; + langui: AppStaticProps["langui"]; + languages: AppStaticProps["languages"]; + currencies: AppStaticProps["currencies"]; + returnHref?: string; + returnTitle?: string | null | undefined; + displayCredits?: boolean; + displayToc?: boolean; + displayThumbnailHeader?: boolean; + displayTitle?: boolean; + displayLanguageSwitcher?: boolean; + prependBody?: JSX.Element; + appendBody?: JSX.Element; +} + +export default function PostPage(props: Props): JSX.Element { + const { + post, + langui, + languages, + returnHref, + returnTitle, + displayCredits, + displayToc, + displayThumbnailHeader, + displayLanguageSwitcher, + appendBody, + prependBody, + } = props; + const displayTitle = props.displayTitle ?? true; + + const [selectedTranslation, LanguageSwitcher] = useSmartLanguage({ + items: post.translations, + languages: languages, + languageExtractor: (item) => item?.language?.data?.attributes?.code, + }); + + const thumbnail = + selectedTranslation?.thumbnail?.data?.attributes ?? + post.thumbnail?.data?.attributes; + + const body = selectedTranslation?.body ?? ""; + const title = selectedTranslation?.title ?? prettySlug(post.slug); + const except = selectedTranslation?.excerpt ?? ""; + + const subPanel = + returnHref || returnTitle || displayCredits || displayToc ? ( + + {returnHref && returnTitle && ( + + )} + + {displayCredits && ( + <> + {selectedTranslation && ( +
+

{langui.status}:

+ + + {selectedTranslation.status} + +
+ )} + + {post.authors && post.authors.data.length > 0 && ( +
+

{"Authors"}:

+
+ {post.authors.data.map((author) => ( + <> + {author.attributes && ( + + )} + + ))} +
+
+ )} + + + + )} + + {displayToc && } +
+ ) : undefined; + + const contentPanel = ( + + {returnHref && returnTitle && ( + + )} + + {displayThumbnailHeader ? ( + <> + } + /> + + + + ) : ( + <> + {displayLanguageSwitcher && ( +
+ +
+ )} + {displayTitle && ( +

+ {title} +

+ )} + + )} + + {prependBody} + + {appendBody} +
+ ); + + return ( + + ); +} diff --git a/src/hooks/useSmartLanguage.tsx b/src/hooks/useSmartLanguage.tsx new file mode 100644 index 0000000..ec6bf53 --- /dev/null +++ b/src/hooks/useSmartLanguage.tsx @@ -0,0 +1,65 @@ +import LanguageSwitcher from "components/Inputs/LanguageSwitcher"; +import { useAppLayout } from "contexts/AppLayoutContext"; +import { useRouter } from "next/router"; +import { AppStaticProps } from "queries/getAppStaticProps"; +import { getPreferredLanguage } from "queries/helpers"; +import { useEffect, useMemo, useState } from "react"; + +interface Props { + items: T[]; + languages: AppStaticProps["languages"]; + languageExtractor: (item: T) => string | undefined; + transform?: (item: T) => T; +} + +export default function useSmartLanguage( + props: Props +): [T | undefined, () => JSX.Element] { + const { + items, + languageExtractor, + languages, + transform = (item) => item, + } = props; + const appLayout = useAppLayout(); + const router = useRouter(); + + const availableLocales: Map = useMemo(() => new Map(), []); + const [selectedTranslationIndex, setSelectedTranslationIndex] = useState< + number | undefined + >(); + const [selectedTranslation, setSelectedTranslation] = useState(); + + useEffect(() => { + items.map((elem, index) => { + const result = languageExtractor(elem); + if (result !== undefined) availableLocales.set(result, index); + }); + }, [availableLocales, items, languageExtractor]); + + useEffect(() => { + setSelectedTranslationIndex( + getPreferredLanguage( + appLayout.preferredLanguages ?? [router.locale], + availableLocales + ) + ); + }, [appLayout.preferredLanguages, availableLocales, router.locale]); + + useEffect(() => { + if (selectedTranslationIndex !== undefined) + setSelectedTranslation(transform(items[selectedTranslationIndex])); + }, [items, selectedTranslationIndex, transform]); + + return [ + selectedTranslation, + () => ( + + ), + ]; +} diff --git a/src/pages/contents/[slug]/index.tsx b/src/pages/contents/[slug]/index.tsx index 1198b6d..37625a5 100644 --- a/src/pages/contents/[slug]/index.tsx +++ b/src/pages/contents/[slug]/index.tsx @@ -1,7 +1,6 @@ import AppLayout from "components/AppLayout"; import Chip from "components/Chip"; import HorizontalLine from "components/HorizontalLine"; -import LanguageSwitcher from "components/Inputs/LanguageSwitcher"; import Markdawn from "components/Markdown/Markdawn"; import TOC from "components/Markdown/TOC"; import ReturnButton, { @@ -13,25 +12,22 @@ import PreviewLine from "components/PreviewLine"; import RecorderChip from "components/RecorderChip"; import ThumbnailHeader from "components/ThumbnailHeader"; import ToolTip from "components/ToolTip"; -import { useAppLayout } from "contexts/AppLayoutContext"; import { GetContentTextQuery } from "graphql/generated"; import { getReadySdk } from "graphql/sdk"; import { useMediaMobile } from "hooks/useMediaQuery"; +import useSmartLanguage from "hooks/useSmartLanguage"; import { GetStaticPathsContext, GetStaticPathsResult, GetStaticPropsContext, } from "next"; -import { useRouter } from "next/router"; import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps"; import { - getPreferredLanguage, getStatusDescription, prettyinlineTitle, prettyLanguage, prettySlug, } from "queries/helpers"; -import { useEffect, useMemo, useState } from "react"; interface Props extends AppStaticProps { content: Exclude< @@ -46,53 +42,15 @@ interface Props extends AppStaticProps { export default function Content(props: Props): JSX.Element { const { langui, content, languages } = props; - const router = useRouter(); - const appLayout = useAppLayout(); - const isMobile = useMediaMobile(); - const [selectedTextSet, setSelectedTextSet] = useState< - | Exclude< - Exclude["text_set"], - null | undefined - >[number] - >(); - const [selectedTitle, setSelectedTitle] = useState< - | Exclude< - Exclude["titles"], - null | undefined - >[number] - >(); - const textSetLocales: Map = new Map(); + const [selectedTextSet, LanguageSwitcher] = useSmartLanguage({ + items: content?.text_set, + languages: languages, + languageExtractor: (item) => item?.language?.data?.attributes?.code, + }); - const [selectedTextSetIndex, setSelectedTextSetIndex] = useState< - number | undefined - >(); - - if (content?.text_set) { - content.text_set.map((textSet, index) => { - if (textSet?.language?.data?.attributes?.code && textSet.text) { - textSetLocales.set(textSet.language.data.attributes.code, index); - } - }); - } - - useMemo(() => { - setSelectedTextSetIndex( - getPreferredLanguage( - appLayout.preferredLanguages ?? [router.locale], - textSetLocales - ) - ); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [appLayout.preferredLanguages]); - - useEffect(() => { - if (selectedTextSetIndex !== undefined) { - setSelectedTextSet(content?.text_set?.[selectedTextSetIndex]); - setSelectedTitle(content?.titles?.[selectedTextSetIndex]); - } - }, [content?.text_set, content?.titles, selectedTextSetIndex]); + const selectedTitle = content?.titles?.[0]; const subPanel = ( @@ -252,16 +210,7 @@ export default function Content(props: Props): JSX.Element { type={content.type} categories={content.categories} langui={langui} - languageSwitcher={ - selectedTextSet ? ( - - ) : undefined - } + languageSwitcher={} /> {content.previous_recommended?.data?.attributes && ( diff --git a/src/pages/news/[slug].tsx b/src/pages/news/[slug].tsx index 698345c..2a166ea 100644 --- a/src/pages/news/[slug].tsx +++ b/src/pages/news/[slug].tsx @@ -10,9 +10,12 @@ import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps"; interface Props extends AppStaticProps { post: Exclude< - GetPostQuery["posts"], + Exclude< + GetPostQuery["posts"], + null | undefined + >["data"][number]["attributes"], null | undefined - >["data"][number]["attributes"]; + >; postId: Exclude< GetPostQuery["posts"], null | undefined @@ -45,7 +48,7 @@ export async function getStaticProps( slug: slug, language_code: context.locale ?? "en", }); - if (!post.posts?.data[0]) return { notFound: true }; + if (!post.posts?.data[0].attributes) return { notFound: true }; const props: Props = { ...(await getAppStaticProps(context)), post: post.posts.data[0].attributes,