Extracted language selector logic into a custom hook
This commit is contained in:
		
							parent
							
								
									6f69aaf236
								
							
						
					
					
						commit
						85106d1735
					
				| @ -1,15 +1,17 @@ | |||||||
| import Chip from "components/Chip"; | 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 Button from "components/Inputs/Button"; | ||||||
| import LanguageSwitcher from "components/Inputs/LanguageSwitcher"; |  | ||||||
| import RecorderChip from "components/RecorderChip"; | import RecorderChip from "components/RecorderChip"; | ||||||
| import ToolTip from "components/ToolTip"; | import ToolTip from "components/ToolTip"; | ||||||
| import { useAppLayout } from "contexts/AppLayoutContext"; |  | ||||||
| import { GetLibraryItemScansQuery } from "graphql/generated"; | import { GetLibraryItemScansQuery } from "graphql/generated"; | ||||||
| import { useRouter } from "next/router"; | import useSmartLanguage from "hooks/useSmartLanguage"; | ||||||
| import { AppStaticProps } from "queries/getAppStaticProps"; | import { AppStaticProps } from "queries/getAppStaticProps"; | ||||||
| import { getPreferredLanguage, getStatusDescription } from "queries/helpers"; | import { getStatusDescription, isInteger } from "queries/helpers"; | ||||||
| import { Dispatch, SetStateAction, useEffect, useMemo, useState } from "react"; | import { Dispatch, SetStateAction } from "react"; | ||||||
| 
 | 
 | ||||||
| interface Props { | interface Props { | ||||||
|   setLightboxOpen: Dispatch<SetStateAction<boolean>>; |   setLightboxOpen: Dispatch<SetStateAction<boolean>>; | ||||||
| @ -62,52 +64,28 @@ export default function ScanSet(props: Props): JSX.Element { | |||||||
|     langui, |     langui, | ||||||
|     content, |     content, | ||||||
|   } = props; |   } = props; | ||||||
|   const appLayout = useAppLayout(); |  | ||||||
|   const router = useRouter(); |  | ||||||
| 
 | 
 | ||||||
|   const [selectedScan, setSelectedScan] = useState<Props["scanSet"][number]>(); |   const [selectedScan, LanguageSwitcher] = useSmartLanguage({ | ||||||
|   const scanLocales: Map<string, number> = new Map(); |     items: scanSet, | ||||||
| 
 |     languages: languages, | ||||||
|   const [selectedScanIndex, setSelectedScanIndex] = useState< |     languageExtractor: (item) => item?.language?.data?.attributes?.code, | ||||||
|     number | undefined |     transform: (item) => { | ||||||
|   >(); |       item?.pages?.data.sort((a, b) => { | ||||||
| 
 |  | ||||||
|   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]; |  | ||||||
|         } |  | ||||||
|         if (a.attributes?.url && b.attributes?.url) { |         if (a.attributes?.url && b.attributes?.url) { | ||||||
|           const aName = getFileName(a.attributes.url); |           let aName = getAssetFilename(a.attributes.url); | ||||||
|           const bName = getFileName(b.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)) { |           if (isInteger(aName) && isInteger(bName)) { | ||||||
|             return parseInt(aName, 10) - parseInt(bName, 10); |             return parseInt(aName, 10) - parseInt(bName, 10); | ||||||
|           } |           } | ||||||
| @ -115,10 +93,9 @@ export default function ScanSet(props: Props): JSX.Element { | |||||||
|         } |         } | ||||||
|         return 0; |         return 0; | ||||||
|       }); |       }); | ||||||
|       setSelectedScan(selectedScanSet); |       return item; | ||||||
|     } |     }, | ||||||
|     // eslint-disable-next-line react-hooks/exhaustive-deps
 |   }); | ||||||
|   }, [selectedScanIndex]); |  | ||||||
| 
 | 
 | ||||||
|   return ( |   return ( | ||||||
|     <> |     <> | ||||||
| @ -144,12 +121,7 @@ export default function ScanSet(props: Props): JSX.Element { | |||||||
|               </Button> |               </Button> | ||||||
|             )} |             )} | ||||||
| 
 | 
 | ||||||
|             <LanguageSwitcher |             <LanguageSwitcher /> | ||||||
|               languages={languages} |  | ||||||
|               locales={scanLocales} |  | ||||||
|               localesIndex={selectedScanIndex} |  | ||||||
|               setLocalesIndex={setSelectedScanIndex} |  | ||||||
|             /> |  | ||||||
| 
 | 
 | ||||||
|             <div className="grid place-items-center place-content-center"> |             <div className="grid place-items-center place-content-center"> | ||||||
|               <p className="font-headers">{langui.status}:</p> |               <p className="font-headers">{langui.status}:</p> | ||||||
|  | |||||||
| @ -1,17 +1,15 @@ | |||||||
| import Chip from "components/Chip"; | import Chip from "components/Chip"; | ||||||
| import Img, { getAssetURL, ImageQuality } from "components/Img"; | import Img, { getAssetURL, ImageQuality } from "components/Img"; | ||||||
| import LanguageSwitcher from "components/Inputs/LanguageSwitcher"; |  | ||||||
| import RecorderChip from "components/RecorderChip"; | import RecorderChip from "components/RecorderChip"; | ||||||
| import ToolTip from "components/ToolTip"; | import ToolTip from "components/ToolTip"; | ||||||
| import { useAppLayout } from "contexts/AppLayoutContext"; |  | ||||||
| import { | import { | ||||||
|   GetLibraryItemScansQuery, |   GetLibraryItemScansQuery, | ||||||
|   UploadImageFragment, |   UploadImageFragment, | ||||||
| } from "graphql/generated"; | } from "graphql/generated"; | ||||||
| import { useRouter } from "next/router"; | import useSmartLanguage from "hooks/useSmartLanguage"; | ||||||
| import { AppStaticProps } from "queries/getAppStaticProps"; | import { AppStaticProps } from "queries/getAppStaticProps"; | ||||||
| import { getPreferredLanguage, getStatusDescription } from "queries/helpers"; | import { getStatusDescription } from "queries/helpers"; | ||||||
| import { Dispatch, SetStateAction, useEffect, useMemo, useState } from "react"; | import { Dispatch, SetStateAction } from "react"; | ||||||
| 
 | 
 | ||||||
| interface Props { | interface Props { | ||||||
|   setLightboxOpen: Dispatch<SetStateAction<boolean>>; |   setLightboxOpen: Dispatch<SetStateAction<boolean>>; | ||||||
| @ -40,38 +38,13 @@ export default function ScanSetCover(props: Props): JSX.Element { | |||||||
|     languages, |     languages, | ||||||
|     langui, |     langui, | ||||||
|   } = props; |   } = props; | ||||||
|   const appLayout = useAppLayout(); |  | ||||||
|   const router = useRouter(); |  | ||||||
| 
 | 
 | ||||||
|   const [selectedScan, setSelectedScan] = useState<Props["images"][number]>(); |   const [selectedScan, LanguageSwitcher] = useSmartLanguage({ | ||||||
|   const scanLocales: Map<string, number> = new Map(); |     items: images, | ||||||
| 
 |     languages: languages, | ||||||
|   const [selectedScanIndex, setSelectedScanIndex] = useState< |     languageExtractor: (item) => item?.language?.data?.attributes?.code, | ||||||
|     number | undefined |  | ||||||
|   >(); |  | ||||||
| 
 |  | ||||||
|   images.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) |  | ||||||
|       setSelectedScan(images[selectedScanIndex]); |  | ||||||
|     // eslint-disable-next-line react-hooks/exhaustive-deps
 |  | ||||||
|   }, [selectedScanIndex]); |  | ||||||
| 
 |  | ||||||
|   const coverImages: UploadImageFragment[] = []; |   const coverImages: UploadImageFragment[] = []; | ||||||
|   if (selectedScan?.obi_belt?.full?.data?.attributes) |   if (selectedScan?.obi_belt?.full?.data?.attributes) | ||||||
|     coverImages.push(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 { | |||||||
|             </div> |             </div> | ||||||
| 
 | 
 | ||||||
|             <div className="flex flex-row flex-wrap gap-4 pb-6 place-items-center"> |             <div className="flex flex-row flex-wrap gap-4 pb-6 place-items-center"> | ||||||
|               <LanguageSwitcher |               <LanguageSwitcher /> | ||||||
|                 languages={languages} |  | ||||||
|                 locales={scanLocales} |  | ||||||
|                 localesIndex={selectedScanIndex} |  | ||||||
|                 setLocalesIndex={setSelectedScanIndex} |  | ||||||
|               /> |  | ||||||
| 
 | 
 | ||||||
|               <div className="grid place-items-center place-content-center"> |               <div className="grid place-items-center place-content-center"> | ||||||
|                 <p className="font-headers">{langui.status}:</p> |                 <p className="font-headers">{langui.status}:</p> | ||||||
|  | |||||||
| @ -1,17 +1,10 @@ | |||||||
| import { useAppLayout } from "contexts/AppLayoutContext"; |  | ||||||
| import { GetPostQuery } from "graphql/generated"; | import { GetPostQuery } from "graphql/generated"; | ||||||
| import { useRouter } from "next/router"; | import useSmartLanguage from "hooks/useSmartLanguage"; | ||||||
| import { AppStaticProps } from "queries/getAppStaticProps"; | import { AppStaticProps } from "queries/getAppStaticProps"; | ||||||
| import { | import { getStatusDescription, prettySlug } from "queries/helpers"; | ||||||
|   getPreferredLanguage, |  | ||||||
|   getStatusDescription, |  | ||||||
|   prettySlug, |  | ||||||
| } from "queries/helpers"; |  | ||||||
| import { useEffect, useMemo, useState } from "react"; |  | ||||||
| import AppLayout from "./AppLayout"; | import AppLayout from "./AppLayout"; | ||||||
| import Chip from "./Chip"; | import Chip from "./Chip"; | ||||||
| import HorizontalLine from "./HorizontalLine"; | import HorizontalLine from "./HorizontalLine"; | ||||||
| import LanguageSwitcher from "./Inputs/LanguageSwitcher"; |  | ||||||
| import Markdawn from "./Markdown/Markdawn"; | import Markdawn from "./Markdown/Markdawn"; | ||||||
| import TOC from "./Markdown/TOC"; | import TOC from "./Markdown/TOC"; | ||||||
| import ReturnButton, { ReturnButtonType } from "./PanelComponents/ReturnButton"; | import ReturnButton, { ReturnButtonType } from "./PanelComponents/ReturnButton"; | ||||||
| @ -23,9 +16,12 @@ import ToolTip from "./ToolTip"; | |||||||
| 
 | 
 | ||||||
| interface Props { | interface Props { | ||||||
|   post: Exclude< |   post: Exclude< | ||||||
|  |     Exclude< | ||||||
|       GetPostQuery["posts"], |       GetPostQuery["posts"], | ||||||
|       null | undefined |       null | undefined | ||||||
|   >["data"][number]["attributes"]; |     >["data"][number]["attributes"], | ||||||
|  |     null | undefined | ||||||
|  |   >; | ||||||
|   langui: AppStaticProps["langui"]; |   langui: AppStaticProps["langui"]; | ||||||
|   languages: AppStaticProps["languages"]; |   languages: AppStaticProps["languages"]; | ||||||
|   currencies: AppStaticProps["currencies"]; |   currencies: AppStaticProps["currencies"]; | ||||||
| @ -56,54 +52,18 @@ export default function Post(props: Props): JSX.Element { | |||||||
|   } = props; |   } = props; | ||||||
|   const displayTitle = props.displayTitle ?? true; |   const displayTitle = props.displayTitle ?? true; | ||||||
| 
 | 
 | ||||||
|   const appLayout = useAppLayout(); |   const [selectedTranslation, LanguageSwitcher] = useSmartLanguage({ | ||||||
|   const router = useRouter(); |     items: post.translations, | ||||||
| 
 |     languages: languages, | ||||||
|   const [selectedTranslation, setSelectedTranslation] = useState< |     languageExtractor: (item) => item?.language?.data?.attributes?.code, | ||||||
|     | Exclude< |  | ||||||
|         Exclude<Props["post"], null | undefined>["translations"], |  | ||||||
|         null | undefined |  | ||||||
|       >[number] |  | ||||||
|   >(); |  | ||||||
|   const translationLocales: Map<string, number> = 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 thumbnail = |   const thumbnail = | ||||||
|     selectedTranslation?.thumbnail?.data?.attributes ?? |     selectedTranslation?.thumbnail?.data?.attributes ?? | ||||||
|     post?.thumbnail?.data?.attributes; |     post.thumbnail?.data?.attributes; | ||||||
| 
 | 
 | ||||||
|   const body = selectedTranslation?.body ?? ""; |   const body = selectedTranslation?.body ?? ""; | ||||||
|   const title = selectedTranslation?.title ?? prettySlug(post?.slug); |   const title = selectedTranslation?.title ?? prettySlug(post.slug); | ||||||
|   const except = selectedTranslation?.excerpt ?? ""; |   const except = selectedTranslation?.excerpt ?? ""; | ||||||
| 
 | 
 | ||||||
|   const subPanel = |   const subPanel = | ||||||
| @ -137,7 +97,7 @@ export default function Post(props: Props): JSX.Element { | |||||||
|               </div> |               </div> | ||||||
|             )} |             )} | ||||||
| 
 | 
 | ||||||
|             {post?.authors && post.authors.data.length > 0 && ( |             {post.authors && post.authors.data.length > 0 && ( | ||||||
|               <div> |               <div> | ||||||
|                 <p className="font-headers">{"Authors"}:</p> |                 <p className="font-headers">{"Authors"}:</p> | ||||||
|                 <div className="grid place-items-center place-content-center gap-2"> |                 <div className="grid place-items-center place-content-center gap-2"> | ||||||
| @ -183,15 +143,8 @@ export default function Post(props: Props): JSX.Element { | |||||||
|             title={title} |             title={title} | ||||||
|             description={except} |             description={except} | ||||||
|             langui={langui} |             langui={langui} | ||||||
|             categories={post?.categories} |             categories={post.categories} | ||||||
|             languageSwitcher={ |             languageSwitcher={<LanguageSwitcher />} | ||||||
|               <LanguageSwitcher |  | ||||||
|                 languages={languages} |  | ||||||
|                 locales={translationLocales} |  | ||||||
|                 localesIndex={selectedTranslationIndex} |  | ||||||
|                 setLocalesIndex={setSelectedTranslationIndex} |  | ||||||
|               /> |  | ||||||
|             } |  | ||||||
|           /> |           /> | ||||||
| 
 | 
 | ||||||
|           <HorizontalLine /> |           <HorizontalLine /> | ||||||
| @ -200,12 +153,7 @@ export default function Post(props: Props): JSX.Element { | |||||||
|         <> |         <> | ||||||
|           {displayLanguageSwitcher && ( |           {displayLanguageSwitcher && ( | ||||||
|             <div className="grid place-content-end place-items-start"> |             <div className="grid place-content-end place-items-start"> | ||||||
|               <LanguageSwitcher |               <LanguageSwitcher /> | ||||||
|                 languages={languages} |  | ||||||
|                 locales={translationLocales} |  | ||||||
|                 localesIndex={selectedTranslationIndex} |  | ||||||
|                 setLocalesIndex={setSelectedTranslationIndex} |  | ||||||
|               /> |  | ||||||
|             </div> |             </div> | ||||||
|           )} |           )} | ||||||
|           {displayTitle && ( |           {displayTitle && ( | ||||||
|  | |||||||
							
								
								
									
										184
									
								
								src/components/PostPage.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										184
									
								
								src/components/PostPage.tsx
									
									
									
									
									
										Normal file
									
								
							| @ -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 ? ( | ||||||
|  |       <SubPanel> | ||||||
|  |         {returnHref && returnTitle && ( | ||||||
|  |           <ReturnButton | ||||||
|  |             href={returnHref} | ||||||
|  |             title={returnTitle} | ||||||
|  |             langui={langui} | ||||||
|  |             displayOn={ReturnButtonType.desktop} | ||||||
|  |             horizontalLine | ||||||
|  |           /> | ||||||
|  |         )} | ||||||
|  | 
 | ||||||
|  |         {displayCredits && ( | ||||||
|  |           <> | ||||||
|  |             {selectedTranslation && ( | ||||||
|  |               <div className="grid grid-flow-col place-items-center place-content-center gap-2"> | ||||||
|  |                 <p className="font-headers">{langui.status}:</p> | ||||||
|  | 
 | ||||||
|  |                 <ToolTip | ||||||
|  |                   content={getStatusDescription( | ||||||
|  |                     selectedTranslation.status, | ||||||
|  |                     langui | ||||||
|  |                   )} | ||||||
|  |                   maxWidth={"20rem"} | ||||||
|  |                 > | ||||||
|  |                   <Chip>{selectedTranslation.status}</Chip> | ||||||
|  |                 </ToolTip> | ||||||
|  |               </div> | ||||||
|  |             )} | ||||||
|  | 
 | ||||||
|  |             {post.authors && post.authors.data.length > 0 && ( | ||||||
|  |               <div> | ||||||
|  |                 <p className="font-headers">{"Authors"}:</p> | ||||||
|  |                 <div className="grid place-items-center place-content-center gap-2"> | ||||||
|  |                   {post.authors.data.map((author) => ( | ||||||
|  |                     <> | ||||||
|  |                       {author.attributes && ( | ||||||
|  |                         <RecorderChip | ||||||
|  |                           key={author.id} | ||||||
|  |                           langui={langui} | ||||||
|  |                           recorder={author.attributes} | ||||||
|  |                         /> | ||||||
|  |                       )} | ||||||
|  |                     </> | ||||||
|  |                   ))} | ||||||
|  |                 </div> | ||||||
|  |               </div> | ||||||
|  |             )} | ||||||
|  | 
 | ||||||
|  |             <HorizontalLine /> | ||||||
|  |           </> | ||||||
|  |         )} | ||||||
|  | 
 | ||||||
|  |         {displayToc && <TOC text={body} title={title} />} | ||||||
|  |       </SubPanel> | ||||||
|  |     ) : undefined; | ||||||
|  | 
 | ||||||
|  |   const contentPanel = ( | ||||||
|  |     <ContentPanel> | ||||||
|  |       {returnHref && returnTitle && ( | ||||||
|  |         <ReturnButton | ||||||
|  |           href={returnHref} | ||||||
|  |           title={returnTitle} | ||||||
|  |           langui={langui} | ||||||
|  |           displayOn={ReturnButtonType.mobile} | ||||||
|  |           horizontalLine | ||||||
|  |         /> | ||||||
|  |       )} | ||||||
|  | 
 | ||||||
|  |       {displayThumbnailHeader ? ( | ||||||
|  |         <> | ||||||
|  |           <ThumbnailHeader | ||||||
|  |             thumbnail={thumbnail} | ||||||
|  |             title={title} | ||||||
|  |             description={except} | ||||||
|  |             langui={langui} | ||||||
|  |             categories={post.categories} | ||||||
|  |             languageSwitcher={<LanguageSwitcher />} | ||||||
|  |           /> | ||||||
|  | 
 | ||||||
|  |           <HorizontalLine /> | ||||||
|  |         </> | ||||||
|  |       ) : ( | ||||||
|  |         <> | ||||||
|  |           {displayLanguageSwitcher && ( | ||||||
|  |             <div className="grid place-content-end place-items-start"> | ||||||
|  |               <LanguageSwitcher /> | ||||||
|  |             </div> | ||||||
|  |           )} | ||||||
|  |           {displayTitle && ( | ||||||
|  |             <h1 className="text-center flex gap-3 justify-center text-4xl my-16"> | ||||||
|  |               {title} | ||||||
|  |             </h1> | ||||||
|  |           )} | ||||||
|  |         </> | ||||||
|  |       )} | ||||||
|  | 
 | ||||||
|  |       {prependBody} | ||||||
|  |       <Markdawn text={body} /> | ||||||
|  |       {appendBody} | ||||||
|  |     </ContentPanel> | ||||||
|  |   ); | ||||||
|  | 
 | ||||||
|  |   return ( | ||||||
|  |     <AppLayout | ||||||
|  |       navTitle={title} | ||||||
|  |       contentPanel={contentPanel} | ||||||
|  |       subPanel={subPanel} | ||||||
|  |       thumbnail={thumbnail ?? undefined} | ||||||
|  |       {...props} | ||||||
|  |     /> | ||||||
|  |   ); | ||||||
|  | } | ||||||
							
								
								
									
										65
									
								
								src/hooks/useSmartLanguage.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								src/hooks/useSmartLanguage.tsx
									
									
									
									
									
										Normal file
									
								
							| @ -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<T> { | ||||||
|  |   items: T[]; | ||||||
|  |   languages: AppStaticProps["languages"]; | ||||||
|  |   languageExtractor: (item: T) => string | undefined; | ||||||
|  |   transform?: (item: T) => T; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export default function useSmartLanguage<T>( | ||||||
|  |   props: Props<T> | ||||||
|  | ): [T | undefined, () => JSX.Element] { | ||||||
|  |   const { | ||||||
|  |     items, | ||||||
|  |     languageExtractor, | ||||||
|  |     languages, | ||||||
|  |     transform = (item) => item, | ||||||
|  |   } = props; | ||||||
|  |   const appLayout = useAppLayout(); | ||||||
|  |   const router = useRouter(); | ||||||
|  | 
 | ||||||
|  |   const availableLocales: Map<string, number> = useMemo(() => new Map(), []); | ||||||
|  |   const [selectedTranslationIndex, setSelectedTranslationIndex] = useState< | ||||||
|  |     number | undefined | ||||||
|  |   >(); | ||||||
|  |   const [selectedTranslation, setSelectedTranslation] = useState<T>(); | ||||||
|  | 
 | ||||||
|  |   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, | ||||||
|  |     () => ( | ||||||
|  |       <LanguageSwitcher | ||||||
|  |         languages={languages} | ||||||
|  |         locales={availableLocales} | ||||||
|  |         localesIndex={selectedTranslationIndex} | ||||||
|  |         setLocalesIndex={setSelectedTranslationIndex} | ||||||
|  |       /> | ||||||
|  |     ), | ||||||
|  |   ]; | ||||||
|  | } | ||||||
| @ -1,7 +1,6 @@ | |||||||
| import AppLayout from "components/AppLayout"; | import AppLayout from "components/AppLayout"; | ||||||
| import Chip from "components/Chip"; | import Chip from "components/Chip"; | ||||||
| import HorizontalLine from "components/HorizontalLine"; | import HorizontalLine from "components/HorizontalLine"; | ||||||
| import LanguageSwitcher from "components/Inputs/LanguageSwitcher"; |  | ||||||
| import Markdawn from "components/Markdown/Markdawn"; | import Markdawn from "components/Markdown/Markdawn"; | ||||||
| import TOC from "components/Markdown/TOC"; | import TOC from "components/Markdown/TOC"; | ||||||
| import ReturnButton, { | import ReturnButton, { | ||||||
| @ -13,25 +12,22 @@ import PreviewLine from "components/PreviewLine"; | |||||||
| import RecorderChip from "components/RecorderChip"; | import RecorderChip from "components/RecorderChip"; | ||||||
| import ThumbnailHeader from "components/ThumbnailHeader"; | import ThumbnailHeader from "components/ThumbnailHeader"; | ||||||
| import ToolTip from "components/ToolTip"; | import ToolTip from "components/ToolTip"; | ||||||
| import { useAppLayout } from "contexts/AppLayoutContext"; |  | ||||||
| import { GetContentTextQuery } from "graphql/generated"; | import { GetContentTextQuery } from "graphql/generated"; | ||||||
| import { getReadySdk } from "graphql/sdk"; | import { getReadySdk } from "graphql/sdk"; | ||||||
| import { useMediaMobile } from "hooks/useMediaQuery"; | import { useMediaMobile } from "hooks/useMediaQuery"; | ||||||
|  | import useSmartLanguage from "hooks/useSmartLanguage"; | ||||||
| import { | import { | ||||||
|   GetStaticPathsContext, |   GetStaticPathsContext, | ||||||
|   GetStaticPathsResult, |   GetStaticPathsResult, | ||||||
|   GetStaticPropsContext, |   GetStaticPropsContext, | ||||||
| } from "next"; | } from "next"; | ||||||
| import { useRouter } from "next/router"; |  | ||||||
| import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps"; | import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps"; | ||||||
| import { | import { | ||||||
|   getPreferredLanguage, |  | ||||||
|   getStatusDescription, |   getStatusDescription, | ||||||
|   prettyinlineTitle, |   prettyinlineTitle, | ||||||
|   prettyLanguage, |   prettyLanguage, | ||||||
|   prettySlug, |   prettySlug, | ||||||
| } from "queries/helpers"; | } from "queries/helpers"; | ||||||
| import { useEffect, useMemo, useState } from "react"; |  | ||||||
| 
 | 
 | ||||||
| interface Props extends AppStaticProps { | interface Props extends AppStaticProps { | ||||||
|   content: Exclude< |   content: Exclude< | ||||||
| @ -46,53 +42,15 @@ interface Props extends AppStaticProps { | |||||||
| 
 | 
 | ||||||
| export default function Content(props: Props): JSX.Element { | export default function Content(props: Props): JSX.Element { | ||||||
|   const { langui, content, languages } = props; |   const { langui, content, languages } = props; | ||||||
|   const router = useRouter(); |  | ||||||
|   const appLayout = useAppLayout(); |  | ||||||
| 
 |  | ||||||
|   const isMobile = useMediaMobile(); |   const isMobile = useMediaMobile(); | ||||||
| 
 | 
 | ||||||
|   const [selectedTextSet, setSelectedTextSet] = useState< |   const [selectedTextSet, LanguageSwitcher] = useSmartLanguage({ | ||||||
|     | Exclude< |     items: content?.text_set, | ||||||
|         Exclude<Props["content"], null | undefined>["text_set"], |     languages: languages, | ||||||
|         null | undefined |     languageExtractor: (item) => item?.language?.data?.attributes?.code, | ||||||
|       >[number] |  | ||||||
|   >(); |  | ||||||
|   const [selectedTitle, setSelectedTitle] = useState< |  | ||||||
|     | Exclude< |  | ||||||
|         Exclude<Props["content"], null | undefined>["titles"], |  | ||||||
|         null | undefined |  | ||||||
|       >[number] |  | ||||||
|   >(); |  | ||||||
|   const textSetLocales: Map<string, number> = new Map(); |  | ||||||
| 
 |  | ||||||
|   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(() => { |   const selectedTitle = content?.titles?.[0]; | ||||||
|     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 subPanel = ( |   const subPanel = ( | ||||||
|     <SubPanel> |     <SubPanel> | ||||||
| @ -252,16 +210,7 @@ export default function Content(props: Props): JSX.Element { | |||||||
|             type={content.type} |             type={content.type} | ||||||
|             categories={content.categories} |             categories={content.categories} | ||||||
|             langui={langui} |             langui={langui} | ||||||
|             languageSwitcher={ |             languageSwitcher={<LanguageSwitcher />} | ||||||
|               selectedTextSet ? ( |  | ||||||
|                 <LanguageSwitcher |  | ||||||
|                   locales={textSetLocales} |  | ||||||
|                   languages={props.languages} |  | ||||||
|                   localesIndex={selectedTextSetIndex} |  | ||||||
|                   setLocalesIndex={setSelectedTextSetIndex} |  | ||||||
|                 /> |  | ||||||
|               ) : undefined |  | ||||||
|             } |  | ||||||
|           /> |           /> | ||||||
| 
 | 
 | ||||||
|           {content.previous_recommended?.data?.attributes && ( |           {content.previous_recommended?.data?.attributes && ( | ||||||
|  | |||||||
| @ -10,9 +10,12 @@ import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps"; | |||||||
| 
 | 
 | ||||||
| interface Props extends AppStaticProps { | interface Props extends AppStaticProps { | ||||||
|   post: Exclude< |   post: Exclude< | ||||||
|  |     Exclude< | ||||||
|       GetPostQuery["posts"], |       GetPostQuery["posts"], | ||||||
|       null | undefined |       null | undefined | ||||||
|   >["data"][number]["attributes"]; |     >["data"][number]["attributes"], | ||||||
|  |     null | undefined | ||||||
|  |   >; | ||||||
|   postId: Exclude< |   postId: Exclude< | ||||||
|     GetPostQuery["posts"], |     GetPostQuery["posts"], | ||||||
|     null | undefined |     null | undefined | ||||||
| @ -45,7 +48,7 @@ export async function getStaticProps( | |||||||
|     slug: slug, |     slug: slug, | ||||||
|     language_code: context.locale ?? "en", |     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 = { |   const props: Props = { | ||||||
|     ...(await getAppStaticProps(context)), |     ...(await getAppStaticProps(context)), | ||||||
|     post: post.posts.data[0].attributes, |     post: post.posts.data[0].attributes, | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 DrMint
						DrMint