Lots of bs
This commit is contained in:
		
							parent
							
								
									5a963294b7
								
							
						
					
					
						commit
						625f436163
					
				| @ -77,4 +77,9 @@ interface ComponentProps {} | |||||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ | // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ | ||||||
| 
 | 
 | ||||||
| export const Component = () => {}; | export const Component = () => {}; | ||||||
|  | 
 | ||||||
|  | /* | ||||||
|  |  *                                    ╭──────────────────────╮ | ||||||
|  |  * ───────────────────────────────────╯  TRANSLATED VARIANT  ╰────────────────────────────────────── | ||||||
|  |  */ | ||||||
| ``` | ``` | ||||||
|  | |||||||
| @ -4,7 +4,7 @@ const locales = ["en", "es", "fr", "pt-br", "ja"]; | |||||||
| 
 | 
 | ||||||
| /* END CONFIG */ | /* END CONFIG */ | ||||||
| 
 | 
 | ||||||
| /** @type {import('next').NextConfig} */ | /* @type {import('next').NextConfig} */ | ||||||
| module.exports = { | module.exports = { | ||||||
|   swcMinify: true, |   swcMinify: true, | ||||||
|   reactStrictMode: true, |   reactStrictMode: true, | ||||||
| @ -15,9 +15,6 @@ module.exports = { | |||||||
|   images: { |   images: { | ||||||
|     domains: ["img.accords-library.com", "watch.accords-library.com"], |     domains: ["img.accords-library.com", "watch.accords-library.com"], | ||||||
|   }, |   }, | ||||||
|   serverRuntimeConfig: { |  | ||||||
|     locales: locales, |  | ||||||
|   }, |  | ||||||
|   async redirects() { |   async redirects() { | ||||||
|     return [ |     return [ | ||||||
|       { |       { | ||||||
|  | |||||||
| @ -3,12 +3,12 @@ import { useRouter } from "next/router"; | |||||||
| import { useEffect, useLayoutEffect, useMemo, useState } from "react"; | import { useEffect, useLayoutEffect, useMemo, useState } from "react"; | ||||||
| import { useSwipeable } from "react-swipeable"; | import { useSwipeable } from "react-swipeable"; | ||||||
| import UAParser from "ua-parser-js"; | import UAParser from "ua-parser-js"; | ||||||
|  | import { useBoolean, useIsClient } from "usehooks-ts"; | ||||||
| import { Ico, Icon } from "./Ico"; | import { Ico, Icon } from "./Ico"; | ||||||
| import { ButtonGroup } from "./Inputs/ButtonGroup"; | import { ButtonGroup } from "./Inputs/ButtonGroup"; | ||||||
| import { OrderableList } from "./Inputs/OrderableList"; | import { OrderableList } from "./Inputs/OrderableList"; | ||||||
| import { Select } from "./Inputs/Select"; | import { Select } from "./Inputs/Select"; | ||||||
| import { TextInput } from "./Inputs/TextInput"; | import { TextInput } from "./Inputs/TextInput"; | ||||||
| import { ContentPlaceholder } from "./PanelComponents/ContentPlaceholder"; |  | ||||||
| import { MainPanel } from "./Panels/MainPanel"; | import { MainPanel } from "./Panels/MainPanel"; | ||||||
| import { Popup } from "./Popup"; | import { Popup } from "./Popup"; | ||||||
| import { AnchorIds } from "hooks/useScrollTopOnChange"; | import { AnchorIds } from "hooks/useScrollTopOnChange"; | ||||||
| @ -27,8 +27,6 @@ import { useAppLayout } from "contexts/AppLayoutContext"; | |||||||
| import { Button } from "components/Inputs/Button"; | import { Button } from "components/Inputs/Button"; | ||||||
| import { OpenGraph, TITLE_PREFIX } from "helpers/openGraph"; | import { OpenGraph, TITLE_PREFIX } from "helpers/openGraph"; | ||||||
| import { getDefaultPreferredLanguages } from "helpers/locales"; | import { getDefaultPreferredLanguages } from "helpers/locales"; | ||||||
| import useIsClient from "hooks/useIsClient"; |  | ||||||
| import { useBoolean } from "hooks/useBoolean"; |  | ||||||
| 
 | 
 | ||||||
| /* | /* | ||||||
|  *                                         ╭─────────────╮ |  *                                         ╭─────────────╮ | ||||||
| @ -195,7 +193,7 @@ export const AppLayout = ({ | |||||||
|   }, [mainPanelReduced, subPanel]); |   }, [mainPanelReduced, subPanel]); | ||||||
| 
 | 
 | ||||||
|   const isClient = useIsClient(); |   const isClient = useIsClient(); | ||||||
|   const { state: hasDisgardSafariWarning, setTrue: disgardSafariWarning } = |   const { value: hasDisgardSafariWarning, setTrue: disgardSafariWarning } = | ||||||
|     useBoolean(false); |     useBoolean(false); | ||||||
|   const isSafari = useMemo<boolean>(() => { |   const isSafari = useMemo<boolean>(() => { | ||||||
|     if (isClient) { |     if (isClient) { | ||||||
| @ -548,3 +546,29 @@ export const AppLayout = ({ | |||||||
|     </div> |     </div> | ||||||
|   ); |   ); | ||||||
| }; | }; | ||||||
|  | 
 | ||||||
|  | // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | ||||||
|  | 
 | ||||||
|  | interface ContentPlaceholderProps { | ||||||
|  |   message: string; | ||||||
|  |   icon?: Icon; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const ContentPlaceholder = ({ | ||||||
|  |   message, | ||||||
|  |   icon, | ||||||
|  | }: ContentPlaceholderProps): JSX.Element => ( | ||||||
|  |   <div className="grid h-full place-content-center"> | ||||||
|  |     <div | ||||||
|  |       className="grid grid-flow-col place-items-center gap-9 rounded-2xl border-2 border-dotted | ||||||
|  |         border-dark p-8 text-dark opacity-40" | ||||||
|  |     > | ||||||
|  |       {isDefined(icon) && <Ico icon={icon} className="!text-[300%]" />} | ||||||
|  |       <p | ||||||
|  |         className={cJoin("w-64 text-2xl", cIf(!isDefined(icon), "text-center"))} | ||||||
|  |       > | ||||||
|  |         {message} | ||||||
|  |       </p> | ||||||
|  |     </div> | ||||||
|  |   </div> | ||||||
|  | ); | ||||||
|  | |||||||
| @ -1,6 +1,9 @@ | |||||||
|  | import { useCallback } from "react"; | ||||||
| import { Link } from "components/Inputs/Link"; | import { Link } from "components/Inputs/Link"; | ||||||
| import { DatePickerFragment } from "graphql/generated"; | import { DatePickerFragment } from "graphql/generated"; | ||||||
| import { cIf, cJoin } from "helpers/className"; | import { cIf, cJoin } from "helpers/className"; | ||||||
|  | import { TranslatedProps } from "helpers/types/TranslatedProps"; | ||||||
|  | import { useSmartLanguage } from "hooks/useSmartLanguage"; | ||||||
| 
 | 
 | ||||||
| /* | /* | ||||||
|  *                                        ╭─────────────╮ |  *                                        ╭─────────────╮ | ||||||
| @ -14,7 +17,7 @@ interface Props { | |||||||
|   isActive?: boolean; |   isActive?: boolean; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export const ChroniclePreview = ({ | const ChroniclePreview = ({ | ||||||
|   date, |   date, | ||||||
|   url, |   url, | ||||||
|   title, |   title, | ||||||
| @ -40,6 +43,35 @@ export const ChroniclePreview = ({ | |||||||
|   </Link> |   </Link> | ||||||
| ); | ); | ||||||
| 
 | 
 | ||||||
|  | /* | ||||||
|  |  *                                    ╭──────────────────────╮ | ||||||
|  |  * ───────────────────────────────────╯  TRANSLATED VARIANT  ╰────────────────────────────────────── | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | export const TranslatedChroniclePreview = ({ | ||||||
|  |   translations, | ||||||
|  |   fallback, | ||||||
|  |   ...otherProps | ||||||
|  | }: TranslatedProps< | ||||||
|  |   Parameters<typeof ChroniclePreview>[0], | ||||||
|  |   "title" | ||||||
|  | >): JSX.Element => { | ||||||
|  |   const [selectedTranslation] = useSmartLanguage({ | ||||||
|  |     items: translations, | ||||||
|  |     languageExtractor: useCallback( | ||||||
|  |       (item: { language: string }): string => item.language, | ||||||
|  |       [] | ||||||
|  |     ), | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   return ( | ||||||
|  |     <ChroniclePreview | ||||||
|  |       title={selectedTranslation?.title ?? fallback.title} | ||||||
|  |       {...otherProps} | ||||||
|  |     /> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
| /* | /* | ||||||
|  *                                      ╭───────────────────╮ |  *                                      ╭───────────────────╮ | ||||||
|  * ─────────────────────────────────────╯  PRIVATE METHODS  ╰─────────────────────────────────────── |  * ─────────────────────────────────────╯  PRIVATE METHODS  ╰─────────────────────────────────────── | ||||||
|  | |||||||
| @ -1,10 +1,13 @@ | |||||||
|  | import { useCallback } from "react"; | ||||||
|  | import { useBoolean } from "usehooks-ts"; | ||||||
|  | import { TranslatedChroniclePreview } from "./ChroniclePreview"; | ||||||
| import { GetChroniclesChaptersQuery } from "graphql/generated"; | import { GetChroniclesChaptersQuery } from "graphql/generated"; | ||||||
| import { filterHasAttributes } from "helpers/others"; | import { filterHasAttributes } from "helpers/others"; | ||||||
| import { TranslatedChroniclePreview } from "components/Translated"; |  | ||||||
| import { prettyInlineTitle, prettySlug } from "helpers/formatters"; | import { prettyInlineTitle, prettySlug } from "helpers/formatters"; | ||||||
| import { Ico, Icon } from "components/Ico"; | import { Ico, Icon } from "components/Ico"; | ||||||
| import { useBoolean } from "hooks/useBoolean"; |  | ||||||
| import { compareDate } from "helpers/date"; | import { compareDate } from "helpers/date"; | ||||||
|  | import { TranslatedProps } from "helpers/types/TranslatedProps"; | ||||||
|  | import { useSmartLanguage } from "hooks/useSmartLanguage"; | ||||||
| 
 | 
 | ||||||
| /* | /* | ||||||
|  *                                        ╭─────────────╮ |  *                                        ╭─────────────╮ | ||||||
| @ -23,12 +26,12 @@ interface Props { | |||||||
|   title: string; |   title: string; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export const ChroniclesList = ({ | const ChroniclesList = ({ | ||||||
|   chronicles, |   chronicles, | ||||||
|   currentSlug, |   currentSlug, | ||||||
|   title, |   title, | ||||||
| }: Props): JSX.Element => { | }: Props): JSX.Element => { | ||||||
|   const { state: isOpen, toggleState: toggleOpen } = useBoolean( |   const { value: isOpen, toggle: toggleOpen } = useBoolean( | ||||||
|     chronicles.some((chronicle) => chronicle.attributes?.slug === currentSlug) |     chronicles.some((chronicle) => chronicle.attributes?.slug === currentSlug) | ||||||
|   ); |   ); | ||||||
| 
 | 
 | ||||||
| @ -112,3 +115,29 @@ export const ChroniclesList = ({ | |||||||
|     </div> |     </div> | ||||||
|   ); |   ); | ||||||
| }; | }; | ||||||
|  | 
 | ||||||
|  | /* | ||||||
|  |  *                                    ╭──────────────────────╮ | ||||||
|  |  * ───────────────────────────────────╯  TRANSLATED VARIANT  ╰────────────────────────────────────── | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | export const TranslatedChroniclesList = ({ | ||||||
|  |   translations, | ||||||
|  |   fallback, | ||||||
|  |   ...otherProps | ||||||
|  | }: TranslatedProps<Props, "title">): JSX.Element => { | ||||||
|  |   const [selectedTranslation] = useSmartLanguage({ | ||||||
|  |     items: translations, | ||||||
|  |     languageExtractor: useCallback( | ||||||
|  |       (item: { language: string }): string => item.language, | ||||||
|  |       [] | ||||||
|  |     ), | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   return ( | ||||||
|  |     <ChroniclesList | ||||||
|  |       title={selectedTranslation?.title ?? fallback.title} | ||||||
|  |       {...otherProps} | ||||||
|  |     /> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
|  | |||||||
| @ -1,9 +1,11 @@ | |||||||
| import React, { MouseEventHandler } from "react"; | import React, { MouseEventHandler, useCallback } from "react"; | ||||||
| import { Link } from "./Link"; | import { Link } from "./Link"; | ||||||
| import { Ico, Icon } from "components/Ico"; | import { Ico, Icon } from "components/Ico"; | ||||||
| import { cIf, cJoin } from "helpers/className"; | import { cIf, cJoin } from "helpers/className"; | ||||||
| import { ConditionalWrapper, Wrapper } from "helpers/component"; | import { ConditionalWrapper, Wrapper } from "helpers/component"; | ||||||
| import { isDefined, isDefinedAndNotEmpty } from "helpers/others"; | import { isDefined, isDefinedAndNotEmpty } from "helpers/others"; | ||||||
|  | import { TranslatedProps } from "helpers/types/TranslatedProps"; | ||||||
|  | import { useSmartLanguage } from "hooks/useSmartLanguage"; | ||||||
| 
 | 
 | ||||||
| /* | /* | ||||||
|  *                                        ╭─────────────╮ |  *                                        ╭─────────────╮ | ||||||
| @ -89,6 +91,29 @@ export const Button = ({ | |||||||
|   </ConditionalWrapper> |   </ConditionalWrapper> | ||||||
| ); | ); | ||||||
| 
 | 
 | ||||||
|  | /* | ||||||
|  |  *                                    ╭──────────────────────╮ | ||||||
|  |  * ───────────────────────────────────╯  TRANSLATED VARIANT  ╰────────────────────────────────────── | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | export const TranslatedButton = ({ | ||||||
|  |   translations, | ||||||
|  |   fallback, | ||||||
|  |   ...otherProps | ||||||
|  | }: TranslatedProps<Props, "text">): JSX.Element => { | ||||||
|  |   const [selectedTranslation] = useSmartLanguage({ | ||||||
|  |     items: translations, | ||||||
|  |     languageExtractor: useCallback( | ||||||
|  |       (item: { language: string }): string => item.language, | ||||||
|  |       [] | ||||||
|  |     ), | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   return ( | ||||||
|  |     <Button text={selectedTranslation?.text ?? fallback.text} {...otherProps} /> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
| /* | /* | ||||||
|  *                                    ╭──────────────────────╮ |  *                                    ╭──────────────────────╮ | ||||||
|  * ───────────────────────────────────╯  PRIVATE COMPONENTS  ╰────────────────────────────────────── |  * ───────────────────────────────────╯  PRIVATE COMPONENTS  ╰────────────────────────────────────── | ||||||
|  | |||||||
| @ -1,7 +1,7 @@ | |||||||
| import { Fragment, useCallback } from "react"; | import { Fragment, useCallback } from "react"; | ||||||
|  | import { useBoolean } from "usehooks-ts"; | ||||||
| import { Ico, Icon } from "components/Ico"; | import { Ico, Icon } from "components/Ico"; | ||||||
| import { cIf, cJoin } from "helpers/className"; | import { cIf, cJoin } from "helpers/className"; | ||||||
| import { useBoolean } from "hooks/useBoolean"; |  | ||||||
| 
 | 
 | ||||||
| /* | /* | ||||||
|  *                                        ╭─────────────╮ |  *                                        ╭─────────────╮ | ||||||
| @ -27,9 +27,9 @@ export const Select = ({ | |||||||
|   onChange, |   onChange, | ||||||
| }: Props): JSX.Element => { | }: Props): JSX.Element => { | ||||||
|   const { |   const { | ||||||
|     state: isOpened, |     value: isOpened, | ||||||
|     setFalse: setClosed, |     setFalse: setClosed, | ||||||
|     toggleState: toggleOpened, |     toggle: toggleOpened, | ||||||
|   } = useBoolean(false); |   } = useBoolean(false); | ||||||
| 
 | 
 | ||||||
|   const tryToggling = useCallback(() => { |   const tryToggling = useCallback(() => { | ||||||
| @ -39,6 +39,9 @@ export const Select = ({ | |||||||
| 
 | 
 | ||||||
|   return ( |   return ( | ||||||
|     <div |     <div | ||||||
|  |       onClickCapture={() => { | ||||||
|  |         setClosed(); | ||||||
|  |       }} | ||||||
|       className={cJoin( |       className={cJoin( | ||||||
|         "relative text-center transition-[filter]", |         "relative text-center transition-[filter]", | ||||||
|         cIf(isOpened, "z-10 drop-shadow-shade-lg"), |         cIf(isOpened, "z-10 drop-shadow-shade-lg"), | ||||||
|  | |||||||
| @ -8,13 +8,17 @@ import { isDefinedAndNotEmpty } from "helpers/others"; | |||||||
| 
 | 
 | ||||||
| interface Props { | interface Props { | ||||||
|   label: string | null | undefined; |   label: string | null | undefined; | ||||||
|   input: JSX.Element; |  | ||||||
|   disabled?: boolean; |   disabled?: boolean; | ||||||
|  |   children: React.ReactNode; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | ||||||
| 
 | 
 | ||||||
| export const WithLabel = ({ label, input, disabled }: Props): JSX.Element => ( | export const WithLabel = ({ | ||||||
|  |   label, | ||||||
|  |   children, | ||||||
|  |   disabled, | ||||||
|  | }: Props): JSX.Element => ( | ||||||
|   <div |   <div | ||||||
|     className={cJoin( |     className={cJoin( | ||||||
|       "flex flex-row place-content-between place-items-center gap-2", |       "flex flex-row place-content-between place-items-center gap-2", | ||||||
| @ -28,6 +32,6 @@ export const WithLabel = ({ label, input, disabled }: Props): JSX.Element => ( | |||||||
|         {label}: |         {label}: | ||||||
|       </p> |       </p> | ||||||
|     )} |     )} | ||||||
|     {input} |     {children} | ||||||
|   </div> |   </div> | ||||||
| ); | ); | ||||||
|  | |||||||
| @ -39,7 +39,7 @@ export const PreviewCardCTAs = ({ | |||||||
|             onClick={(event) => { |             onClick={(event) => { | ||||||
|               event.preventDefault(); |               event.preventDefault(); | ||||||
|               setLibraryItemUserStatus((current) => { |               setLibraryItemUserStatus((current) => { | ||||||
|                 const newLibraryItemUserStatus = current ? { ...current } : {}; |                 const newLibraryItemUserStatus = { ...current }; | ||||||
|                 newLibraryItemUserStatus[id] = |                 newLibraryItemUserStatus[id] = | ||||||
|                   newLibraryItemUserStatus[id] === LibraryItemUserStatus.Want |                   newLibraryItemUserStatus[id] === LibraryItemUserStatus.Want | ||||||
|                     ? LibraryItemUserStatus.None |                     ? LibraryItemUserStatus.None | ||||||
| @ -57,7 +57,7 @@ export const PreviewCardCTAs = ({ | |||||||
|             onClick={(event) => { |             onClick={(event) => { | ||||||
|               event.preventDefault(); |               event.preventDefault(); | ||||||
|               setLibraryItemUserStatus((current) => { |               setLibraryItemUserStatus((current) => { | ||||||
|                 const newLibraryItemUserStatus = current ? { ...current } : {}; |                 const newLibraryItemUserStatus = { ...current }; | ||||||
|                 newLibraryItemUserStatus[id] = |                 newLibraryItemUserStatus[id] = | ||||||
|                   newLibraryItemUserStatus[id] === LibraryItemUserStatus.Have |                   newLibraryItemUserStatus[id] === LibraryItemUserStatus.Have | ||||||
|                     ? LibraryItemUserStatus.None |                     ? LibraryItemUserStatus.None | ||||||
|  | |||||||
| @ -1,246 +0,0 @@ | |||||||
| import { Fragment, useCallback, useMemo } from "react"; |  | ||||||
| import { Chip } from "components/Chip"; |  | ||||||
| import { Img } from "components/Img"; |  | ||||||
| import { Button } from "components/Inputs/Button"; |  | ||||||
| import { RecorderChip } from "components/RecorderChip"; |  | ||||||
| import { ToolTip } from "components/ToolTip"; |  | ||||||
| import { GetLibraryItemScansQuery } from "graphql/generated"; |  | ||||||
| import { AppStaticProps } from "graphql/getAppStaticProps"; |  | ||||||
| import { getAssetFilename, getAssetURL, ImageQuality } from "helpers/img"; |  | ||||||
| import { isInteger } from "helpers/numbers"; |  | ||||||
| import { |  | ||||||
|   filterHasAttributes, |  | ||||||
|   getStatusDescription, |  | ||||||
|   isDefined, |  | ||||||
|   isDefinedAndNotEmpty, |  | ||||||
| } from "helpers/others"; |  | ||||||
| import { useSmartLanguage } from "hooks/useSmartLanguage"; |  | ||||||
| 
 |  | ||||||
| /* |  | ||||||
|  *                                        ╭─────────────╮ |  | ||||||
|  * ───────────────────────────────────────╯  COMPONENT  ╰─────────────────────────────────────────── |  | ||||||
|  */ |  | ||||||
| 
 |  | ||||||
| interface Props { |  | ||||||
|   openLightBox: (images: string[], index?: number) => void; |  | ||||||
|   scanSet: NonNullable< |  | ||||||
|     NonNullable< |  | ||||||
|       NonNullable< |  | ||||||
|         NonNullable< |  | ||||||
|           NonNullable< |  | ||||||
|             GetLibraryItemScansQuery["libraryItems"] |  | ||||||
|           >["data"][number]["attributes"] |  | ||||||
|         >["contents"] |  | ||||||
|       >["data"][number]["attributes"] |  | ||||||
|     >["scan_set"] |  | ||||||
|   >; |  | ||||||
|   id: string; |  | ||||||
|   title: string; |  | ||||||
|   languages: AppStaticProps["languages"]; |  | ||||||
|   langui: AppStaticProps["langui"]; |  | ||||||
|   content: NonNullable< |  | ||||||
|     NonNullable< |  | ||||||
|       NonNullable< |  | ||||||
|         NonNullable< |  | ||||||
|           GetLibraryItemScansQuery["libraryItems"] |  | ||||||
|         >["data"][number]["attributes"] |  | ||||||
|       >["contents"] |  | ||||||
|     >["data"][number]["attributes"] |  | ||||||
|   >["content"]; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 |  | ||||||
| 
 |  | ||||||
| export const ScanSet = ({ |  | ||||||
|   openLightBox, |  | ||||||
|   scanSet, |  | ||||||
|   id, |  | ||||||
|   title, |  | ||||||
|   languages, |  | ||||||
|   langui, |  | ||||||
|   content, |  | ||||||
| }: Props): JSX.Element => { |  | ||||||
|   const [selectedScan, LanguageSwitcher, languageSwitcherProps] = |  | ||||||
|     useSmartLanguage({ |  | ||||||
|       items: scanSet, |  | ||||||
|       languages: languages, |  | ||||||
|       languageExtractor: useCallback( |  | ||||||
|         (item: NonNullable<Props["scanSet"][number]>) => |  | ||||||
|           item.language?.data?.attributes?.code, |  | ||||||
|         [] |  | ||||||
|       ), |  | ||||||
|       transform: useCallback((item: NonNullable<Props["scanSet"][number]>) => { |  | ||||||
|         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 (isInteger(aName) && isInteger(bName)) { |  | ||||||
|               return parseInt(aName, 10) - parseInt(bName, 10); |  | ||||||
|             } |  | ||||||
|             return a.attributes.url.localeCompare(b.attributes.url); |  | ||||||
|           } |  | ||||||
|           return 0; |  | ||||||
|         }); |  | ||||||
|         return item; |  | ||||||
|       }, []), |  | ||||||
|     }); |  | ||||||
| 
 |  | ||||||
|   const pages = useMemo( |  | ||||||
|     () => filterHasAttributes(selectedScan?.pages?.data, ["attributes"]), |  | ||||||
|     [selectedScan] |  | ||||||
|   ); |  | ||||||
| 
 |  | ||||||
|   return ( |  | ||||||
|     <> |  | ||||||
|       {selectedScan && isDefined(pages) && ( |  | ||||||
|         <div> |  | ||||||
|           <div |  | ||||||
|             className="flex flex-row flex-wrap place-items-center |  | ||||||
|           gap-6 pt-10 text-base first-of-type:pt-0" |  | ||||||
|           > |  | ||||||
|             <h2 id={id} className="text-2xl"> |  | ||||||
|               {title} |  | ||||||
|             </h2> |  | ||||||
| 
 |  | ||||||
|             <Chip |  | ||||||
|               text={ |  | ||||||
|                 selectedScan.language?.data?.attributes?.code === |  | ||||||
|                 selectedScan.source_language?.data?.attributes?.code |  | ||||||
|                   ? langui.scan ?? "Scan" |  | ||||||
|                   : langui.scanlation ?? "Scanlation" |  | ||||||
|               } |  | ||||||
|             /> |  | ||||||
|           </div> |  | ||||||
| 
 |  | ||||||
|           <div className="flex flex-row flex-wrap place-items-center gap-4 pb-6"> |  | ||||||
|             {content?.data?.attributes && |  | ||||||
|               isDefinedAndNotEmpty(content.data.attributes.slug) && ( |  | ||||||
|                 <Button |  | ||||||
|                   href={`/contents/${content.data.attributes.slug}`} |  | ||||||
|                   text={langui.open_content} |  | ||||||
|                 /> |  | ||||||
|               )} |  | ||||||
| 
 |  | ||||||
|             {languageSwitcherProps.locales.size > 1 && ( |  | ||||||
|               <LanguageSwitcher {...languageSwitcherProps} /> |  | ||||||
|             )} |  | ||||||
| 
 |  | ||||||
|             <div className="grid place-content-center place-items-center"> |  | ||||||
|               <p className="font-headers font-bold">{langui.status}:</p> |  | ||||||
|               <ToolTip |  | ||||||
|                 content={getStatusDescription(selectedScan.status, langui)} |  | ||||||
|                 maxWidth={"20rem"} |  | ||||||
|               > |  | ||||||
|                 <Chip text={selectedScan.status} /> |  | ||||||
|               </ToolTip> |  | ||||||
|             </div> |  | ||||||
| 
 |  | ||||||
|             {selectedScan.scanners && selectedScan.scanners.data.length > 0 && ( |  | ||||||
|               <div> |  | ||||||
|                 <p className="font-headers font-bold">{langui.scanners}:</p> |  | ||||||
|                 <div className="grid place-content-center place-items-center gap-2"> |  | ||||||
|                   {filterHasAttributes(selectedScan.scanners.data, [ |  | ||||||
|                     "id", |  | ||||||
|                     "attributes", |  | ||||||
|                   ] as const).map((scanner) => ( |  | ||||||
|                     <Fragment key={scanner.id}> |  | ||||||
|                       <RecorderChip |  | ||||||
|                         langui={langui} |  | ||||||
|                         recorder={scanner.attributes} |  | ||||||
|                       /> |  | ||||||
|                     </Fragment> |  | ||||||
|                   ))} |  | ||||||
|                 </div> |  | ||||||
|               </div> |  | ||||||
|             )} |  | ||||||
| 
 |  | ||||||
|             {selectedScan.cleaners && selectedScan.cleaners.data.length > 0 && ( |  | ||||||
|               <div> |  | ||||||
|                 <p className="font-headers font-bold">{langui.cleaners}:</p> |  | ||||||
|                 <div className="grid place-content-center place-items-center gap-2"> |  | ||||||
|                   {filterHasAttributes(selectedScan.cleaners.data, [ |  | ||||||
|                     "id", |  | ||||||
|                     "attributes", |  | ||||||
|                   ] as const).map((cleaner) => ( |  | ||||||
|                     <Fragment key={cleaner.id}> |  | ||||||
|                       <RecorderChip |  | ||||||
|                         langui={langui} |  | ||||||
|                         recorder={cleaner.attributes} |  | ||||||
|                       /> |  | ||||||
|                     </Fragment> |  | ||||||
|                   ))} |  | ||||||
|                 </div> |  | ||||||
|               </div> |  | ||||||
|             )} |  | ||||||
| 
 |  | ||||||
|             {selectedScan.typesetters && |  | ||||||
|               selectedScan.typesetters.data.length > 0 && ( |  | ||||||
|                 <div> |  | ||||||
|                   <p className="font-headers font-bold"> |  | ||||||
|                     {langui.typesetters}: |  | ||||||
|                   </p> |  | ||||||
|                   <div className="grid place-content-center place-items-center gap-2"> |  | ||||||
|                     {filterHasAttributes(selectedScan.typesetters.data, [ |  | ||||||
|                       "id", |  | ||||||
|                       "attributes", |  | ||||||
|                     ] as const).map((typesetter) => ( |  | ||||||
|                       <Fragment key={typesetter.id}> |  | ||||||
|                         <RecorderChip |  | ||||||
|                           langui={langui} |  | ||||||
|                           recorder={typesetter.attributes} |  | ||||||
|                         /> |  | ||||||
|                       </Fragment> |  | ||||||
|                     ))} |  | ||||||
|                   </div> |  | ||||||
|                 </div> |  | ||||||
|               )} |  | ||||||
| 
 |  | ||||||
|             {isDefinedAndNotEmpty(selectedScan.notes) && ( |  | ||||||
|               <ToolTip content={selectedScan.notes}> |  | ||||||
|                 <Chip text={langui.notes ?? "Notes"} /> |  | ||||||
|               </ToolTip> |  | ||||||
|             )} |  | ||||||
|           </div> |  | ||||||
| 
 |  | ||||||
|           <div |  | ||||||
|             className="grid items-end gap-8 border-b-[3px] border-dotted pb-12 last-of-type:border-0 |  | ||||||
|              desktop:grid-cols-[repeat(auto-fill,_minmax(10rem,1fr))] mobile:grid-cols-2" |  | ||||||
|           > |  | ||||||
|             {pages.map((page, index) => ( |  | ||||||
|               <div |  | ||||||
|                 key={page.id} |  | ||||||
|                 className="cursor-pointer transition-transform |  | ||||||
|                 drop-shadow-shade-lg hover:scale-[1.02]" |  | ||||||
|                 onClick={() => { |  | ||||||
|                   const images = pages.map((image) => |  | ||||||
|                     getAssetURL(image.attributes.url, ImageQuality.Large) |  | ||||||
|                   ); |  | ||||||
|                   openLightBox(images, index); |  | ||||||
|                 }} |  | ||||||
|               > |  | ||||||
|                 <Img src={page.attributes} quality={ImageQuality.Small} /> |  | ||||||
|               </div> |  | ||||||
|             ))} |  | ||||||
|           </div> |  | ||||||
|         </div> |  | ||||||
|       )} |  | ||||||
|     </> |  | ||||||
|   ); |  | ||||||
| }; |  | ||||||
| @ -1,191 +0,0 @@ | |||||||
| import { Fragment, useCallback, useMemo } from "react"; |  | ||||||
| import { Chip } from "components/Chip"; |  | ||||||
| import { Img } from "components/Img"; |  | ||||||
| import { RecorderChip } from "components/RecorderChip"; |  | ||||||
| import { ToolTip } from "components/ToolTip"; |  | ||||||
| import { |  | ||||||
|   GetLibraryItemScansQuery, |  | ||||||
|   UploadImageFragment, |  | ||||||
| } from "graphql/generated"; |  | ||||||
| import { AppStaticProps } from "graphql/getAppStaticProps"; |  | ||||||
| import { getAssetURL, ImageQuality } from "helpers/img"; |  | ||||||
| import { filterHasAttributes, getStatusDescription } from "helpers/others"; |  | ||||||
| import { useSmartLanguage } from "hooks/useSmartLanguage"; |  | ||||||
| 
 |  | ||||||
| /* |  | ||||||
|  *                                        ╭─────────────╮ |  | ||||||
|  * ───────────────────────────────────────╯  COMPONENT  ╰─────────────────────────────────────────── |  | ||||||
|  */ |  | ||||||
| 
 |  | ||||||
| interface Props { |  | ||||||
|   openLightBox: (images: string[], index?: number) => void; |  | ||||||
|   images: NonNullable< |  | ||||||
|     NonNullable< |  | ||||||
|       NonNullable< |  | ||||||
|         GetLibraryItemScansQuery["libraryItems"] |  | ||||||
|       >["data"][number]["attributes"] |  | ||||||
|     >["images"] |  | ||||||
|   >; |  | ||||||
|   languages: AppStaticProps["languages"]; |  | ||||||
|   langui: AppStaticProps["langui"]; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 |  | ||||||
| 
 |  | ||||||
| export const ScanSetCover = ({ |  | ||||||
|   openLightBox, |  | ||||||
|   images, |  | ||||||
|   languages, |  | ||||||
|   langui, |  | ||||||
| }: Props): JSX.Element => { |  | ||||||
|   const [selectedScan, LanguageSwitcher, languageSwitcherProps] = |  | ||||||
|     useSmartLanguage({ |  | ||||||
|       items: images, |  | ||||||
|       languages: languages, |  | ||||||
|       languageExtractor: useCallback( |  | ||||||
|         (item: NonNullable<Props["images"][number]>) => |  | ||||||
|           item.language?.data?.attributes?.code, |  | ||||||
|         [] |  | ||||||
|       ), |  | ||||||
|     }); |  | ||||||
| 
 |  | ||||||
|   const coverImages = useMemo(() => { |  | ||||||
|     const memo: UploadImageFragment[] = []; |  | ||||||
|     if (selectedScan?.obi_belt?.full?.data?.attributes) |  | ||||||
|       memo.push(selectedScan.obi_belt.full.data.attributes); |  | ||||||
|     if (selectedScan?.obi_belt?.inside_full?.data?.attributes) |  | ||||||
|       memo.push(selectedScan.obi_belt.inside_full.data.attributes); |  | ||||||
|     if (selectedScan?.dust_jacket?.full?.data?.attributes) |  | ||||||
|       memo.push(selectedScan.dust_jacket.full.data.attributes); |  | ||||||
|     if (selectedScan?.dust_jacket?.inside_full?.data?.attributes) |  | ||||||
|       memo.push(selectedScan.dust_jacket.inside_full.data.attributes); |  | ||||||
|     if (selectedScan?.cover?.full?.data?.attributes) |  | ||||||
|       memo.push(selectedScan.cover.full.data.attributes); |  | ||||||
|     if (selectedScan?.cover?.inside_full?.data?.attributes) |  | ||||||
|       memo.push(selectedScan.cover.inside_full.data.attributes); |  | ||||||
|     return memo; |  | ||||||
|   }, [selectedScan]); |  | ||||||
| 
 |  | ||||||
|   return ( |  | ||||||
|     <> |  | ||||||
|       {coverImages.length > 0 && selectedScan && ( |  | ||||||
|         <div> |  | ||||||
|           <div |  | ||||||
|             className="flex flex-row flex-wrap place-items-center |  | ||||||
|           gap-6 pt-10 text-base first-of-type:pt-0" |  | ||||||
|           > |  | ||||||
|             <h2 id={"cover"} className="text-2xl"> |  | ||||||
|               {langui.cover} |  | ||||||
|             </h2> |  | ||||||
| 
 |  | ||||||
|             <Chip |  | ||||||
|               text={ |  | ||||||
|                 selectedScan.language?.data?.attributes?.code === |  | ||||||
|                 selectedScan.source_language?.data?.attributes?.code |  | ||||||
|                   ? langui.scan ?? "Scan" |  | ||||||
|                   : langui.scanlation ?? "Scanlation" |  | ||||||
|               } |  | ||||||
|             /> |  | ||||||
|           </div> |  | ||||||
| 
 |  | ||||||
|           <div className="flex flex-row flex-wrap place-items-center gap-4 pb-6"> |  | ||||||
|             <LanguageSwitcher {...languageSwitcherProps} /> |  | ||||||
| 
 |  | ||||||
|             <div className="grid place-content-center place-items-center"> |  | ||||||
|               <p className="font-headers font-bold">{langui.status}:</p> |  | ||||||
|               <ToolTip |  | ||||||
|                 content={getStatusDescription(selectedScan.status, langui)} |  | ||||||
|                 maxWidth={"20rem"} |  | ||||||
|               > |  | ||||||
|                 <Chip text={selectedScan.status} /> |  | ||||||
|               </ToolTip> |  | ||||||
|             </div> |  | ||||||
| 
 |  | ||||||
|             {selectedScan.scanners && selectedScan.scanners.data.length > 0 && ( |  | ||||||
|               <div> |  | ||||||
|                 <p className="font-headers font-bold">{langui.scanners}:</p> |  | ||||||
|                 <div className="grid place-content-center place-items-center gap-2"> |  | ||||||
|                   {filterHasAttributes(selectedScan.scanners.data, [ |  | ||||||
|                     "id", |  | ||||||
|                     "attributes", |  | ||||||
|                   ] as const).map((scanner) => ( |  | ||||||
|                     <Fragment key={scanner.id}> |  | ||||||
|                       <RecorderChip |  | ||||||
|                         langui={langui} |  | ||||||
|                         recorder={scanner.attributes} |  | ||||||
|                       /> |  | ||||||
|                     </Fragment> |  | ||||||
|                   ))} |  | ||||||
|                 </div> |  | ||||||
|               </div> |  | ||||||
|             )} |  | ||||||
| 
 |  | ||||||
|             {selectedScan.cleaners && selectedScan.cleaners.data.length > 0 && ( |  | ||||||
|               <div> |  | ||||||
|                 <p className="font-headers font-bold">{langui.cleaners}:</p> |  | ||||||
|                 <div className="grid place-content-center place-items-center gap-2"> |  | ||||||
|                   {filterHasAttributes(selectedScan.cleaners.data, [ |  | ||||||
|                     "id", |  | ||||||
|                     "attributes", |  | ||||||
|                   ] as const).map((cleaner) => ( |  | ||||||
|                     <Fragment key={cleaner.id}> |  | ||||||
|                       <RecorderChip |  | ||||||
|                         langui={langui} |  | ||||||
|                         recorder={cleaner.attributes} |  | ||||||
|                       /> |  | ||||||
|                     </Fragment> |  | ||||||
|                   ))} |  | ||||||
|                 </div> |  | ||||||
|               </div> |  | ||||||
|             )} |  | ||||||
| 
 |  | ||||||
|             {selectedScan.typesetters && |  | ||||||
|               selectedScan.typesetters.data.length > 0 && ( |  | ||||||
|                 <div> |  | ||||||
|                   <p className="font-headers font-bold"> |  | ||||||
|                     {langui.typesetters}: |  | ||||||
|                   </p> |  | ||||||
|                   <div className="grid place-content-center place-items-center gap-2"> |  | ||||||
|                     {filterHasAttributes(selectedScan.typesetters.data, [ |  | ||||||
|                       "id", |  | ||||||
|                       "attributes", |  | ||||||
|                     ] as const).map((typesetter) => ( |  | ||||||
|                       <Fragment key={typesetter.id}> |  | ||||||
|                         <RecorderChip |  | ||||||
|                           langui={langui} |  | ||||||
|                           recorder={typesetter.attributes} |  | ||||||
|                         /> |  | ||||||
|                       </Fragment> |  | ||||||
|                     ))} |  | ||||||
|                   </div> |  | ||||||
|                 </div> |  | ||||||
|               )} |  | ||||||
|           </div> |  | ||||||
| 
 |  | ||||||
|           <div |  | ||||||
|             className="grid items-end gap-8 border-b-[3px] border-dotted pb-12 |  | ||||||
|               last-of-type:border-0 desktop:grid-cols-[repeat(auto-fill,_minmax(10rem,1fr))] |  | ||||||
|               mobile:grid-cols-2" |  | ||||||
|           > |  | ||||||
|             {coverImages.map((image, index) => ( |  | ||||||
|               <div |  | ||||||
|                 key={image.url} |  | ||||||
|                 className="cursor-pointer transition-transform |  | ||||||
|                   drop-shadow-shade-lg hover:scale-[1.02]" |  | ||||||
|                 onClick={() => { |  | ||||||
|                   const imgs = coverImages.map((img) => |  | ||||||
|                     getAssetURL(img.url, ImageQuality.Large) |  | ||||||
|                   ); |  | ||||||
| 
 |  | ||||||
|                   openLightBox(imgs, index); |  | ||||||
|                 }} |  | ||||||
|               > |  | ||||||
|                 <Img src={image} quality={ImageQuality.Small} /> |  | ||||||
|               </div> |  | ||||||
|             ))} |  | ||||||
|           </div> |  | ||||||
|         </div> |  | ||||||
|       )} |  | ||||||
|     </> |  | ||||||
|   ); |  | ||||||
| }; |  | ||||||
| @ -1,31 +0,0 @@ | |||||||
| import { Ico, Icon } from "components/Ico"; |  | ||||||
| import { cIf, cJoin } from "helpers/className"; |  | ||||||
| import { isDefined } from "helpers/others"; |  | ||||||
| 
 |  | ||||||
| /* |  | ||||||
|  *                                        ╭─────────────╮ |  | ||||||
|  * ───────────────────────────────────────╯  COMPONENT  ╰─────────────────────────────────────────── |  | ||||||
|  */ |  | ||||||
| 
 |  | ||||||
| interface Props { |  | ||||||
|   message: string; |  | ||||||
|   icon?: Icon; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 |  | ||||||
| 
 |  | ||||||
| export const ContentPlaceholder = ({ message, icon }: Props): JSX.Element => ( |  | ||||||
|   <div className="grid h-full place-content-center"> |  | ||||||
|     <div |  | ||||||
|       className="grid grid-flow-col place-items-center gap-9 rounded-2xl border-2 border-dotted |  | ||||||
|         border-dark p-8 text-dark opacity-40" |  | ||||||
|     > |  | ||||||
|       {isDefined(icon) && <Ico icon={icon} className="!text-[300%]" />} |  | ||||||
|       <p |  | ||||||
|         className={cJoin("w-64 text-2xl", cIf(!isDefined(icon), "text-center"))} |  | ||||||
|       > |  | ||||||
|         {message} |  | ||||||
|       </p> |  | ||||||
|     </div> |  | ||||||
|   </div> |  | ||||||
| ); |  | ||||||
| @ -1,10 +1,12 @@ | |||||||
| import { useRouter } from "next/router"; | import { useRouter } from "next/router"; | ||||||
| import { MouseEventHandler, useMemo } from "react"; | import { MouseEventHandler, useCallback, useMemo } from "react"; | ||||||
| import { Ico, Icon } from "components/Ico"; | import { Ico, Icon } from "components/Ico"; | ||||||
| import { ToolTip } from "components/ToolTip"; | import { ToolTip } from "components/ToolTip"; | ||||||
| import { cJoin, cIf } from "helpers/className"; | import { cJoin, cIf } from "helpers/className"; | ||||||
| import { isDefinedAndNotEmpty } from "helpers/others"; | import { isDefinedAndNotEmpty } from "helpers/others"; | ||||||
| import { Link } from "components/Inputs/Link"; | import { Link } from "components/Inputs/Link"; | ||||||
|  | import { TranslatedProps } from "helpers/types/TranslatedProps"; | ||||||
|  | import { useSmartLanguage } from "hooks/useSmartLanguage"; | ||||||
| 
 | 
 | ||||||
| /* | /* | ||||||
|  *                                        ╭─────────────╮ |  *                                        ╭─────────────╮ | ||||||
| @ -81,3 +83,29 @@ export const NavOption = ({ | |||||||
|     </ToolTip> |     </ToolTip> | ||||||
|   ); |   ); | ||||||
| }; | }; | ||||||
|  | 
 | ||||||
|  | /* | ||||||
|  |  *                                    ╭──────────────────────╮ | ||||||
|  |  * ───────────────────────────────────╯  TRANSLATED VARIANT  ╰────────────────────────────────────── | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | export const TranslatedNavOption = ({ | ||||||
|  |   translations, | ||||||
|  |   fallback, | ||||||
|  |   ...otherProps | ||||||
|  | }: TranslatedProps<Props, "subtitle" | "title">): JSX.Element => { | ||||||
|  |   const [selectedTranslation] = useSmartLanguage({ | ||||||
|  |     items: translations, | ||||||
|  |     languageExtractor: useCallback( | ||||||
|  |       (item: { language: string }): string => item.language, | ||||||
|  |       [] | ||||||
|  |     ), | ||||||
|  |   }); | ||||||
|  |   return ( | ||||||
|  |     <NavOption | ||||||
|  |       title={selectedTranslation?.title ?? fallback.title} | ||||||
|  |       subtitle={selectedTranslation?.subtitle ?? fallback.subtitle} | ||||||
|  |       {...otherProps} | ||||||
|  |     /> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
|  | |||||||
| @ -1,9 +1,12 @@ | |||||||
|  | import { useCallback } from "react"; | ||||||
| import { HorizontalLine } from "components/HorizontalLine"; | import { HorizontalLine } from "components/HorizontalLine"; | ||||||
| import { Icon } from "components/Ico"; | import { Icon } from "components/Ico"; | ||||||
| import { Button } from "components/Inputs/Button"; | import { Button } from "components/Inputs/Button"; | ||||||
| import { useAppLayout } from "contexts/AppLayoutContext"; | import { useAppLayout } from "contexts/AppLayoutContext"; | ||||||
| import { AppStaticProps } from "graphql/getAppStaticProps"; | import { AppStaticProps } from "graphql/getAppStaticProps"; | ||||||
| import { cJoin } from "helpers/className"; | import { cIf, cJoin } from "helpers/className"; | ||||||
|  | import { TranslatedProps } from "helpers/types/TranslatedProps"; | ||||||
|  | import { useSmartLanguage } from "hooks/useSmartLanguage"; | ||||||
| 
 | 
 | ||||||
| /* | /* | ||||||
|  *                                        ╭─────────────╮ |  *                                        ╭─────────────╮ | ||||||
| @ -40,11 +43,8 @@ export const ReturnButton = ({ | |||||||
|   return ( |   return ( | ||||||
|     <div |     <div | ||||||
|       className={cJoin( |       className={cJoin( | ||||||
|         displayOn === ReturnButtonType.Mobile |         cIf(displayOn === ReturnButtonType.Mobile, "desktop:hidden"), | ||||||
|           ? "desktop:hidden" |         cIf(displayOn === ReturnButtonType.Desktop, "mobile:hidden"), | ||||||
|           : displayOn === ReturnButtonType.Desktop |  | ||||||
|           ? "mobile:hidden" |  | ||||||
|           : "", |  | ||||||
|         className |         className | ||||||
|       )} |       )} | ||||||
|     > |     > | ||||||
| @ -58,3 +58,29 @@ export const ReturnButton = ({ | |||||||
|     </div> |     </div> | ||||||
|   ); |   ); | ||||||
| }; | }; | ||||||
|  | 
 | ||||||
|  | /* | ||||||
|  |  *                                    ╭──────────────────────╮ | ||||||
|  |  * ───────────────────────────────────╯  TRANSLATED VARIANT  ╰────────────────────────────────────── | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | export const TranslatedReturnButton = ({ | ||||||
|  |   translations, | ||||||
|  |   fallback, | ||||||
|  |   ...otherProps | ||||||
|  | }: TranslatedProps<Props, "title">): JSX.Element => { | ||||||
|  |   const [selectedTranslation] = useSmartLanguage({ | ||||||
|  |     items: translations, | ||||||
|  |     languageExtractor: useCallback( | ||||||
|  |       (item: { language: string }): string => item.language, | ||||||
|  |       [] | ||||||
|  |     ), | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   return ( | ||||||
|  |     <ReturnButton | ||||||
|  |       title={selectedTranslation?.title ?? fallback.title} | ||||||
|  |       {...otherProps} | ||||||
|  |     /> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
|  | |||||||
| @ -1,4 +1,4 @@ | |||||||
| import { useMemo } from "react"; | import { useCallback, useMemo } from "react"; | ||||||
| import { useRouter } from "next/router"; | import { useRouter } from "next/router"; | ||||||
| import { Chip } from "./Chip"; | import { Chip } from "./Chip"; | ||||||
| import { Ico, Icon } from "./Ico"; | import { Ico, Icon } from "./Ico"; | ||||||
| @ -20,6 +20,8 @@ import { | |||||||
| } from "helpers/formatters"; | } from "helpers/formatters"; | ||||||
| import { ImageQuality } from "helpers/img"; | import { ImageQuality } from "helpers/img"; | ||||||
| import { useMediaHoverable } from "hooks/useMediaQuery"; | import { useMediaHoverable } from "hooks/useMediaQuery"; | ||||||
|  | import { useSmartLanguage } from "hooks/useSmartLanguage"; | ||||||
|  | import { TranslatedProps } from "helpers/types/TranslatedProps"; | ||||||
| 
 | 
 | ||||||
| /* | /* | ||||||
|  *                                        ╭─────────────╮ |  *                                        ╭─────────────╮ | ||||||
| @ -296,3 +298,34 @@ export const PreviewCard = ({ | |||||||
|     </Link> |     </Link> | ||||||
|   ); |   ); | ||||||
| }; | }; | ||||||
|  | 
 | ||||||
|  | /* | ||||||
|  |  *                                    ╭──────────────────────╮ | ||||||
|  |  * ───────────────────────────────────╯  TRANSLATED VARIANT  ╰────────────────────────────────────── | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | export const TranslatedPreviewCard = ({ | ||||||
|  |   translations, | ||||||
|  |   fallback, | ||||||
|  |   ...otherProps | ||||||
|  | }: TranslatedProps< | ||||||
|  |   Props, | ||||||
|  |   "description" | "pre_title" | "subtitle" | "title" | ||||||
|  | >): JSX.Element => { | ||||||
|  |   const [selectedTranslation] = useSmartLanguage({ | ||||||
|  |     items: translations, | ||||||
|  |     languageExtractor: useCallback( | ||||||
|  |       (item: { language: string }): string => item.language, | ||||||
|  |       [] | ||||||
|  |     ), | ||||||
|  |   }); | ||||||
|  |   return ( | ||||||
|  |     <PreviewCard | ||||||
|  |       pre_title={selectedTranslation?.pre_title ?? fallback.pre_title} | ||||||
|  |       title={selectedTranslation?.title ?? fallback.title} | ||||||
|  |       subtitle={selectedTranslation?.subtitle ?? fallback.subtitle} | ||||||
|  |       description={selectedTranslation?.description ?? fallback.description} | ||||||
|  |       {...otherProps} | ||||||
|  |     /> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
|  | |||||||
| @ -1,8 +1,11 @@ | |||||||
|  | import { useCallback } from "react"; | ||||||
| import { Chip } from "./Chip"; | import { Chip } from "./Chip"; | ||||||
| import { Img } from "./Img"; | import { Img } from "./Img"; | ||||||
| import { Link } from "./Inputs/Link"; | import { Link } from "./Inputs/Link"; | ||||||
| import { UploadImageFragment } from "graphql/generated"; | import { UploadImageFragment } from "graphql/generated"; | ||||||
| import { ImageQuality } from "helpers/img"; | import { ImageQuality } from "helpers/img"; | ||||||
|  | import { TranslatedProps } from "helpers/types/TranslatedProps"; | ||||||
|  | import { useSmartLanguage } from "hooks/useSmartLanguage"; | ||||||
| 
 | 
 | ||||||
| /* | /* | ||||||
|  *                                        ╭─────────────╮ |  *                                        ╭─────────────╮ | ||||||
| @ -22,7 +25,7 @@ interface Props { | |||||||
| 
 | 
 | ||||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | ||||||
| 
 | 
 | ||||||
| export const PreviewLine = ({ | const PreviewLine = ({ | ||||||
|   href, |   href, | ||||||
|   thumbnail, |   thumbnail, | ||||||
|   pre_title, |   pre_title, | ||||||
| @ -73,3 +76,30 @@ export const PreviewLine = ({ | |||||||
|     </div> |     </div> | ||||||
|   </Link> |   </Link> | ||||||
| ); | ); | ||||||
|  | 
 | ||||||
|  | /* | ||||||
|  |  *                                    ╭──────────────────────╮ | ||||||
|  |  * ───────────────────────────────────╯  TRANSLATED VARIANT  ╰────────────────────────────────────── | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | export const TranslatedPreviewLine = ({ | ||||||
|  |   translations, | ||||||
|  |   fallback, | ||||||
|  |   ...otherProps | ||||||
|  | }: TranslatedProps<Props, "pre_title" | "subtitle" | "title">): JSX.Element => { | ||||||
|  |   const [selectedTranslation] = useSmartLanguage({ | ||||||
|  |     items: translations, | ||||||
|  |     languageExtractor: useCallback( | ||||||
|  |       (item: { language: string }): string => item.language, | ||||||
|  |       [] | ||||||
|  |     ), | ||||||
|  |   }); | ||||||
|  |   return ( | ||||||
|  |     <PreviewLine | ||||||
|  |       pre_title={selectedTranslation?.pre_title ?? fallback.pre_title} | ||||||
|  |       title={selectedTranslation?.title ?? fallback.title} | ||||||
|  |       subtitle={selectedTranslation?.subtitle ?? fallback.subtitle} | ||||||
|  |       {...otherProps} | ||||||
|  |     /> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
|  | |||||||
| @ -1,226 +0,0 @@ | |||||||
| import { PreviewCard } from "./PreviewCard"; |  | ||||||
| import { PreviewLine } from "./PreviewLine"; |  | ||||||
| import { ScanSet } from "./Library/ScanSet"; |  | ||||||
| import { NavOption } from "./PanelComponents/NavOption"; |  | ||||||
| import { ChroniclePreview } from "./Chronicles/ChroniclePreview"; |  | ||||||
| import { ChroniclesList } from "./Chronicles/ChroniclesList"; |  | ||||||
| import { Button } from "./Inputs/Button"; |  | ||||||
| import { ReturnButton } from "./PanelComponents/ReturnButton"; |  | ||||||
| import { useSmartLanguage } from "hooks/useSmartLanguage"; |  | ||||||
| import { PreviewFolder } from "pages/contents/folder/[slug]"; |  | ||||||
| 
 |  | ||||||
| export type TranslatedProps<P, K extends keyof P> = Omit<P, K> & { |  | ||||||
|   translations: (Pick<P, K> & { language: string })[]; |  | ||||||
|   fallback: Pick<P, K>; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 |  | ||||||
| 
 |  | ||||||
| type TranslatedPreviewCardProps = TranslatedProps< |  | ||||||
|   Parameters<typeof PreviewCard>[0], |  | ||||||
|   "description" | "pre_title" | "subtitle" | "title" |  | ||||||
| >; |  | ||||||
| 
 |  | ||||||
| const languageExtractor = (item: { language: string }): string => item.language; |  | ||||||
| 
 |  | ||||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 |  | ||||||
| 
 |  | ||||||
| export const TranslatedPreviewCard = ({ |  | ||||||
|   translations, |  | ||||||
|   fallback, |  | ||||||
|   ...otherProps |  | ||||||
| }: TranslatedPreviewCardProps): JSX.Element => { |  | ||||||
|   const [selectedTranslation] = useSmartLanguage({ |  | ||||||
|     items: translations, |  | ||||||
|     languageExtractor, |  | ||||||
|   }); |  | ||||||
| 
 |  | ||||||
|   return ( |  | ||||||
|     <PreviewCard |  | ||||||
|       pre_title={selectedTranslation?.pre_title ?? fallback.pre_title} |  | ||||||
|       title={selectedTranslation?.title ?? fallback.title} |  | ||||||
|       subtitle={selectedTranslation?.subtitle ?? fallback.subtitle} |  | ||||||
|       description={selectedTranslation?.description ?? fallback.description} |  | ||||||
|       {...otherProps} |  | ||||||
|     /> |  | ||||||
|   ); |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 |  | ||||||
| 
 |  | ||||||
| export const TranslatedPreviewLine = ({ |  | ||||||
|   translations, |  | ||||||
|   fallback, |  | ||||||
|   ...otherProps |  | ||||||
| }: TranslatedProps< |  | ||||||
|   Parameters<typeof PreviewLine>[0], |  | ||||||
|   "pre_title" | "subtitle" | "title" |  | ||||||
| >): JSX.Element => { |  | ||||||
|   const [selectedTranslation] = useSmartLanguage({ |  | ||||||
|     items: translations, |  | ||||||
| 
 |  | ||||||
|     languageExtractor, |  | ||||||
|   }); |  | ||||||
| 
 |  | ||||||
|   return ( |  | ||||||
|     <PreviewLine |  | ||||||
|       pre_title={selectedTranslation?.pre_title ?? fallback.pre_title} |  | ||||||
|       title={selectedTranslation?.title ?? fallback.title} |  | ||||||
|       subtitle={selectedTranslation?.subtitle ?? fallback.subtitle} |  | ||||||
|       {...otherProps} |  | ||||||
|     /> |  | ||||||
|   ); |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 |  | ||||||
| 
 |  | ||||||
| export const TranslatedScanSet = ({ |  | ||||||
|   translations, |  | ||||||
|   fallback, |  | ||||||
|   ...otherProps |  | ||||||
| }: TranslatedProps<Parameters<typeof ScanSet>[0], "title">): JSX.Element => { |  | ||||||
|   const [selectedTranslation] = useSmartLanguage({ |  | ||||||
|     items: translations, |  | ||||||
|     languageExtractor, |  | ||||||
|   }); |  | ||||||
| 
 |  | ||||||
|   return ( |  | ||||||
|     <ScanSet |  | ||||||
|       title={selectedTranslation?.title ?? fallback.title} |  | ||||||
|       {...otherProps} |  | ||||||
|     /> |  | ||||||
|   ); |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 |  | ||||||
| 
 |  | ||||||
| export const TranslatedNavOption = ({ |  | ||||||
|   translations, |  | ||||||
|   fallback, |  | ||||||
|   ...otherProps |  | ||||||
| }: TranslatedProps< |  | ||||||
|   Parameters<typeof NavOption>[0], |  | ||||||
|   "subtitle" | "title" |  | ||||||
| >): JSX.Element => { |  | ||||||
|   const [selectedTranslation] = useSmartLanguage({ |  | ||||||
|     items: translations, |  | ||||||
|     languageExtractor, |  | ||||||
|   }); |  | ||||||
| 
 |  | ||||||
|   return ( |  | ||||||
|     <NavOption |  | ||||||
|       title={selectedTranslation?.title ?? fallback.title} |  | ||||||
|       subtitle={selectedTranslation?.subtitle ?? fallback.subtitle} |  | ||||||
|       {...otherProps} |  | ||||||
|     /> |  | ||||||
|   ); |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 |  | ||||||
| 
 |  | ||||||
| export const TranslatedChroniclePreview = ({ |  | ||||||
|   translations, |  | ||||||
|   fallback, |  | ||||||
|   ...otherProps |  | ||||||
| }: TranslatedProps< |  | ||||||
|   Parameters<typeof ChroniclePreview>[0], |  | ||||||
|   "title" |  | ||||||
| >): JSX.Element => { |  | ||||||
|   const [selectedTranslation] = useSmartLanguage({ |  | ||||||
|     items: translations, |  | ||||||
|     languageExtractor, |  | ||||||
|   }); |  | ||||||
| 
 |  | ||||||
|   return ( |  | ||||||
|     <ChroniclePreview |  | ||||||
|       title={selectedTranslation?.title ?? fallback.title} |  | ||||||
|       {...otherProps} |  | ||||||
|     /> |  | ||||||
|   ); |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 |  | ||||||
| 
 |  | ||||||
| export const TranslatedChroniclesList = ({ |  | ||||||
|   translations, |  | ||||||
|   fallback, |  | ||||||
|   ...otherProps |  | ||||||
| }: TranslatedProps< |  | ||||||
|   Parameters<typeof ChroniclesList>[0], |  | ||||||
|   "title" |  | ||||||
| >): JSX.Element => { |  | ||||||
|   const [selectedTranslation] = useSmartLanguage({ |  | ||||||
|     items: translations, |  | ||||||
|     languageExtractor, |  | ||||||
|   }); |  | ||||||
| 
 |  | ||||||
|   return ( |  | ||||||
|     <ChroniclesList |  | ||||||
|       title={selectedTranslation?.title ?? fallback.title} |  | ||||||
|       {...otherProps} |  | ||||||
|     /> |  | ||||||
|   ); |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 |  | ||||||
| 
 |  | ||||||
| export const TranslatedButton = ({ |  | ||||||
|   translations, |  | ||||||
|   fallback, |  | ||||||
|   ...otherProps |  | ||||||
| }: TranslatedProps<Parameters<typeof Button>[0], "text">): JSX.Element => { |  | ||||||
|   const [selectedTranslation] = useSmartLanguage({ |  | ||||||
|     items: translations, |  | ||||||
|     languageExtractor, |  | ||||||
|   }); |  | ||||||
| 
 |  | ||||||
|   return ( |  | ||||||
|     <Button text={selectedTranslation?.text ?? fallback.text} {...otherProps} /> |  | ||||||
|   ); |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 |  | ||||||
| 
 |  | ||||||
| export const TranslatedPreviewFolder = ({ |  | ||||||
|   translations, |  | ||||||
|   fallback, |  | ||||||
|   ...otherProps |  | ||||||
| }: TranslatedProps< |  | ||||||
|   Parameters<typeof PreviewFolder>[0], |  | ||||||
|   "title" |  | ||||||
| >): JSX.Element => { |  | ||||||
|   const [selectedTranslation] = useSmartLanguage({ |  | ||||||
|     items: translations, |  | ||||||
|     languageExtractor, |  | ||||||
|   }); |  | ||||||
| 
 |  | ||||||
|   return ( |  | ||||||
|     <PreviewFolder |  | ||||||
|       title={selectedTranslation?.title ?? fallback.title} |  | ||||||
|       {...otherProps} |  | ||||||
|     /> |  | ||||||
|   ); |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 |  | ||||||
| 
 |  | ||||||
| export const TranslatedReturnButton = ({ |  | ||||||
|   translations, |  | ||||||
|   fallback, |  | ||||||
|   ...otherProps |  | ||||||
| }: TranslatedProps< |  | ||||||
|   Parameters<typeof ReturnButton>[0], |  | ||||||
|   "title" |  | ||||||
| >): JSX.Element => { |  | ||||||
|   const [selectedTranslation] = useSmartLanguage({ |  | ||||||
|     items: translations, |  | ||||||
|     languageExtractor, |  | ||||||
|   }); |  | ||||||
| 
 |  | ||||||
|   return ( |  | ||||||
|     <ReturnButton |  | ||||||
|       title={selectedTranslation?.title ?? fallback.title} |  | ||||||
|       {...otherProps} |  | ||||||
|     /> |  | ||||||
|   ); |  | ||||||
| }; |  | ||||||
| @ -2,7 +2,7 @@ export interface Wrapper { | |||||||
|   children: React.ReactNode; |   children: React.ReactNode; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| interface Props<T> { | interface ConditionalWrapperProps<T> { | ||||||
|   isWrapping: boolean; |   isWrapping: boolean; | ||||||
|   children: React.ReactNode; |   children: React.ReactNode; | ||||||
|   wrapper: (wrapperProps: T & Wrapper) => JSX.Element; |   wrapper: (wrapperProps: T & Wrapper) => JSX.Element; | ||||||
| @ -14,7 +14,7 @@ export const ConditionalWrapper = <T,>({ | |||||||
|   children, |   children, | ||||||
|   wrapper: Wrapper, |   wrapper: Wrapper, | ||||||
|   wrapperProps, |   wrapperProps, | ||||||
| }: Props<T>): JSX.Element => | }: ConditionalWrapperProps<T>): JSX.Element => | ||||||
|   isWrapping ? ( |   isWrapping ? ( | ||||||
|     <Wrapper {...wrapperProps}>{children}</Wrapper> |     <Wrapper {...wrapperProps}>{children}</Wrapper> | ||||||
|   ) : ( |   ) : ( | ||||||
|  | |||||||
| @ -24,13 +24,3 @@ export const randomInt = (min: number, max: number): number => | |||||||
| 
 | 
 | ||||||
| export const isInteger = (value: string): boolean => | export const isInteger = (value: string): boolean => | ||||||
|   /^[+-]?[0-9]+$/u.test(value); |   /^[+-]?[0-9]+$/u.test(value); | ||||||
| 
 |  | ||||||
| export const clamp = ( |  | ||||||
|   value: number, |  | ||||||
|   minValue: number, |  | ||||||
|   maxValue: number |  | ||||||
| ): number => { |  | ||||||
|   if (value > maxValue) return maxValue; |  | ||||||
|   if (value < minValue) return minValue; |  | ||||||
|   return value; |  | ||||||
| }; |  | ||||||
|  | |||||||
| @ -1,3 +1,5 @@ | |||||||
|  | /* eslint-disable @typescript-eslint/no-unused-vars */ | ||||||
|  | 
 | ||||||
| type JoinDot<K extends string, P extends string> = `${K}${"" extends K | type JoinDot<K extends string, P extends string> = `${K}${"" extends K | ||||||
|   ? "" |   ? "" | ||||||
|   : "."}${P}`;
 |   : "."}${P}`;
 | ||||||
|  | |||||||
							
								
								
									
										4
									
								
								src/helpers/types/TranslatedProps.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								src/helpers/types/TranslatedProps.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,4 @@ | |||||||
|  | export type TranslatedProps<P, K extends keyof P> = Omit<P, K> & { | ||||||
|  |   translations: (Pick<P, K> & { language: string })[]; | ||||||
|  |   fallback: Pick<P, K>; | ||||||
|  | }; | ||||||
| @ -1,20 +0,0 @@ | |||||||
| import { Dispatch, SetStateAction, useCallback, useState } from "react"; |  | ||||||
| 
 |  | ||||||
| export const useBoolean = ( |  | ||||||
|   initialState: boolean |  | ||||||
| ): { |  | ||||||
|   state: boolean; |  | ||||||
|   toggleState: () => void; |  | ||||||
|   setTrue: () => void; |  | ||||||
|   setFalse: () => void; |  | ||||||
|   setState: Dispatch<SetStateAction<boolean>>; |  | ||||||
| } => { |  | ||||||
|   const [state, setState] = useState(initialState); |  | ||||||
|   const toggleState = useCallback( |  | ||||||
|     () => setState((currentState) => !currentState), |  | ||||||
|     [] |  | ||||||
|   ); |  | ||||||
|   const setTrue = useCallback(() => setState(true), []); |  | ||||||
|   const setFalse = useCallback(() => setState(false), []); |  | ||||||
|   return { state, toggleState, setTrue, setFalse, setState }; |  | ||||||
| }; |  | ||||||
| @ -1,13 +0,0 @@ | |||||||
| import { useEffect, useState } from "react"; |  | ||||||
| 
 |  | ||||||
| const useIsClient = (): boolean => { |  | ||||||
|   const [isClient, setClient] = useState(false); |  | ||||||
| 
 |  | ||||||
|   useEffect(() => { |  | ||||||
|     setClient(true); |  | ||||||
|   }, []); |  | ||||||
| 
 |  | ||||||
|   return isClient; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export default useIsClient; |  | ||||||
| @ -1,38 +1,6 @@ | |||||||
| import { useCallback, useEffect, useState } from "react"; | import { useMediaQuery } from "usehooks-ts"; | ||||||
| import { breaks } from "../../design.config"; | import { breaks } from "../../design.config"; | ||||||
| 
 | 
 | ||||||
| const useMediaQuery = (query: string): boolean => { |  | ||||||
|   const getMatches = useCallback((): boolean => { |  | ||||||
|     // Prevents SSR issues
 |  | ||||||
|     if (typeof window !== "undefined") { |  | ||||||
|       return window.matchMedia(query).matches; |  | ||||||
|     } |  | ||||||
|     return false; |  | ||||||
|   }, [query]); |  | ||||||
| 
 |  | ||||||
|   const [matches, setMatches] = useState<boolean>(getMatches()); |  | ||||||
| 
 |  | ||||||
|   useEffect(() => { |  | ||||||
|     const handleChange = () => { |  | ||||||
|       setMatches(getMatches()); |  | ||||||
|     }; |  | ||||||
| 
 |  | ||||||
|     const matchMedia = window.matchMedia(query); |  | ||||||
| 
 |  | ||||||
|     // Triggered at the first client-side load and if query changes
 |  | ||||||
|     handleChange(); |  | ||||||
| 
 |  | ||||||
|     // Listen matchMedia
 |  | ||||||
|     matchMedia.addEventListener("change", handleChange); |  | ||||||
| 
 |  | ||||||
|     return () => { |  | ||||||
|       matchMedia.removeEventListener("change", handleChange); |  | ||||||
|     }; |  | ||||||
|   }, [getMatches, query]); |  | ||||||
| 
 |  | ||||||
|   return matches; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| // ts-unused-exports:disable-next-line
 | // ts-unused-exports:disable-next-line
 | ||||||
| export const useMediaThin = (): boolean => useMediaQuery(breaks.thin.raw); | export const useMediaThin = (): boolean => useMediaQuery(breaks.thin.raw); | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										38
									
								
								src/hooks/useTernaryDarkMode.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/hooks/useTernaryDarkMode.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,38 @@ | |||||||
|  | import { useMemo } from "react"; | ||||||
|  | import { usePrefersDarkMode } from "./useMediaQuery"; | ||||||
|  | import { useStateWithLocalStorage } from "./useStateWithLocalStorage"; | ||||||
|  | 
 | ||||||
|  | export enum TernaryDarkMode { | ||||||
|  |   Dark = "dark", | ||||||
|  |   Auto = "auto", | ||||||
|  |   Light = "light", | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export const useTernaryDarkMode = ( | ||||||
|  |   key: string, | ||||||
|  |   initialValue: TernaryDarkMode | ||||||
|  | ): { | ||||||
|  |   isDarkMode: boolean; | ||||||
|  |   ternaryDarkMode: TernaryDarkMode | undefined; | ||||||
|  |   setTernaryDarkMode: React.Dispatch< | ||||||
|  |     React.SetStateAction<TernaryDarkMode | undefined> | ||||||
|  |   >; | ||||||
|  | } => { | ||||||
|  |   const [ternaryDarkMode, setTernaryDarkMode] = useStateWithLocalStorage( | ||||||
|  |     key, | ||||||
|  |     initialValue | ||||||
|  |   ); | ||||||
|  |   const prefersDarkMode = usePrefersDarkMode(); | ||||||
|  |   const isDarkMode = useMemo(() => { | ||||||
|  |     switch (ternaryDarkMode) { | ||||||
|  |       case TernaryDarkMode.Light: | ||||||
|  |         return false; | ||||||
|  |       case TernaryDarkMode.Dark: | ||||||
|  |         return true; | ||||||
|  |       default: | ||||||
|  |         return prefersDarkMode; | ||||||
|  |     } | ||||||
|  |   }, [prefersDarkMode, ternaryDarkMode]); | ||||||
|  | 
 | ||||||
|  |   return { isDarkMode, ternaryDarkMode, setTernaryDarkMode }; | ||||||
|  | }; | ||||||
| @ -1,5 +1,5 @@ | |||||||
| import type { NextApiRequest, NextApiResponse } from "next"; | import type { NextApiRequest, NextApiResponse } from "next"; | ||||||
| import getConfig from "next/config"; | import { i18n } from "../../../next.config"; | ||||||
| 
 | 
 | ||||||
| type RequestProps = | type RequestProps = | ||||||
|   | HookChronicle |   | HookChronicle | ||||||
| @ -119,7 +119,6 @@ const Revalidate = ( | |||||||
|   res: NextApiResponse<ResponseMailProps> |   res: NextApiResponse<ResponseMailProps> | ||||||
| ): void => { | ): void => { | ||||||
|   const body = req.body as RequestProps; |   const body = req.body as RequestProps; | ||||||
|   const { serverRuntimeConfig } = getConfig(); |  | ||||||
| 
 | 
 | ||||||
|   // Check for secret to confirm this is a valid request
 |   // Check for secret to confirm this is a valid request
 | ||||||
|   if ( |   if ( | ||||||
| @ -135,7 +134,7 @@ const Revalidate = ( | |||||||
|     case "post": { |     case "post": { | ||||||
|       paths.push(`/news`); |       paths.push(`/news`); | ||||||
|       paths.push(`/news/${body.entry.slug}`); |       paths.push(`/news/${body.entry.slug}`); | ||||||
|       serverRuntimeConfig.locales?.forEach((locale: string) => { |       i18n.locales.forEach((locale: string) => { | ||||||
|         paths.push(`/${locale}/news`); |         paths.push(`/${locale}/news`); | ||||||
|         paths.push(`/${locale}/news/${body.entry.slug}`); |         paths.push(`/${locale}/news/${body.entry.slug}`); | ||||||
|       }); |       }); | ||||||
| @ -149,7 +148,7 @@ const Revalidate = ( | |||||||
|       body.entry.subitem_of.forEach((parentItem) => { |       body.entry.subitem_of.forEach((parentItem) => { | ||||||
|         paths.push(`/library/${parentItem.slug}`); |         paths.push(`/library/${parentItem.slug}`); | ||||||
|       }); |       }); | ||||||
|       serverRuntimeConfig.locales?.forEach((locale: string) => { |       i18n.locales.forEach((locale: string) => { | ||||||
|         paths.push(`/${locale}/library`); |         paths.push(`/${locale}/library`); | ||||||
|         paths.push(`/${locale}/library/${body.entry.slug}`); |         paths.push(`/${locale}/library/${body.entry.slug}`); | ||||||
|         paths.push(`/${locale}/library/${body.entry.slug}/scans`); |         paths.push(`/${locale}/library/${body.entry.slug}/scans`); | ||||||
| @ -166,7 +165,7 @@ const Revalidate = ( | |||||||
|       if (body.entry.folder?.slug) { |       if (body.entry.folder?.slug) { | ||||||
|         paths.push(`/contents/folder/${body.entry.folder.slug}`); |         paths.push(`/contents/folder/${body.entry.folder.slug}`); | ||||||
|       } |       } | ||||||
|       serverRuntimeConfig.locales?.forEach((locale: string) => { |       i18n.locales.forEach((locale: string) => { | ||||||
|         paths.push(`/${locale}/contents`); |         paths.push(`/${locale}/contents`); | ||||||
|         paths.push(`/${locale}/contents/${body.entry.slug}`); |         paths.push(`/${locale}/contents/${body.entry.slug}`); | ||||||
|         if (body.entry.folder?.slug) { |         if (body.entry.folder?.slug) { | ||||||
| @ -181,7 +180,7 @@ const Revalidate = ( | |||||||
|           ); |           ); | ||||||
|           paths.push(`/library/${parentSlug}`); |           paths.push(`/library/${parentSlug}`); | ||||||
|           paths.push(`/library/${parentSlug}/scans`); |           paths.push(`/library/${parentSlug}/scans`); | ||||||
|           serverRuntimeConfig.locales?.forEach((locale: string) => { |           i18n.locales.forEach((locale: string) => { | ||||||
|             paths.push(`/${locale}/library/${parentSlug}`); |             paths.push(`/${locale}/library/${parentSlug}`); | ||||||
|             paths.push(`/${locale}/library/${parentSlug}/scans`); |             paths.push(`/${locale}/library/${parentSlug}/scans`); | ||||||
|           }); |           }); | ||||||
| @ -193,7 +192,7 @@ const Revalidate = ( | |||||||
|     case "chronology-era": |     case "chronology-era": | ||||||
|     case "chronology-item": { |     case "chronology-item": { | ||||||
|       paths.push(`/wiki/chronology`); |       paths.push(`/wiki/chronology`); | ||||||
|       serverRuntimeConfig.locales?.forEach((locale: string) => { |       i18n.locales.forEach((locale: string) => { | ||||||
|         paths.push(`/${locale}/wiki/chronology`); |         paths.push(`/${locale}/wiki/chronology`); | ||||||
|       }); |       }); | ||||||
|       break; |       break; | ||||||
| @ -207,7 +206,7 @@ const Revalidate = ( | |||||||
|       if (body.entry.content) { |       if (body.entry.content) { | ||||||
|         paths.push(`/contents/${body.entry.content.slug}`); |         paths.push(`/contents/${body.entry.content.slug}`); | ||||||
|       } |       } | ||||||
|       serverRuntimeConfig.locales?.forEach((locale: string) => { |       i18n.locales.forEach((locale: string) => { | ||||||
|         if (body.entry.library_item) { |         if (body.entry.library_item) { | ||||||
|           paths.push(`/${locale}/library/${body.entry.library_item.slug}`); |           paths.push(`/${locale}/library/${body.entry.library_item.slug}`); | ||||||
|           paths.push( |           paths.push( | ||||||
| @ -235,7 +234,7 @@ const Revalidate = ( | |||||||
|       body.entry.contents.forEach((content) => |       body.entry.contents.forEach((content) => | ||||||
|         paths.push(`/contents/${content.slug}`) |         paths.push(`/contents/${content.slug}`) | ||||||
|       ); |       ); | ||||||
|       serverRuntimeConfig.locales?.forEach((locale: string) => { |       i18n.locales.forEach((locale: string) => { | ||||||
|         if (body.entry.slug === "root") { |         if (body.entry.slug === "root") { | ||||||
|           paths.push(`/${locale}/contents`); |           paths.push(`/${locale}/contents`); | ||||||
|         } |         } | ||||||
| @ -258,7 +257,7 @@ const Revalidate = ( | |||||||
|     case "wiki-page": { |     case "wiki-page": { | ||||||
|       paths.push(`/wiki`); |       paths.push(`/wiki`); | ||||||
|       paths.push(`/wiki/${body.entry.slug}`); |       paths.push(`/wiki/${body.entry.slug}`); | ||||||
|       serverRuntimeConfig.locales?.forEach((locale: string) => { |       i18n.locales.forEach((locale: string) => { | ||||||
|         paths.push(`/${locale}/wiki`); |         paths.push(`/${locale}/wiki`); | ||||||
|         paths.push(`/${locale}/wiki/${body.entry.slug}`); |         paths.push(`/${locale}/wiki/${body.entry.slug}`); | ||||||
|       }); |       }); | ||||||
| @ -269,7 +268,7 @@ const Revalidate = ( | |||||||
|     case "chronicle": { |     case "chronicle": { | ||||||
|       paths.push(`/chronicles`); |       paths.push(`/chronicles`); | ||||||
|       paths.push(`/chronicles/${body.entry.slug}`); |       paths.push(`/chronicles/${body.entry.slug}`); | ||||||
|       serverRuntimeConfig.locales?.forEach((locale: string) => { |       i18n.locales.forEach((locale: string) => { | ||||||
|         paths.push(`/${locale}/chronicles`); |         paths.push(`/${locale}/chronicles`); | ||||||
|         paths.push(`/${locale}/chronicles/${body.entry.slug}`); |         paths.push(`/${locale}/chronicles/${body.entry.slug}`); | ||||||
|       }); |       }); | ||||||
| @ -278,12 +277,12 @@ const Revalidate = ( | |||||||
| 
 | 
 | ||||||
|     case "chronicles-chapter": { |     case "chronicles-chapter": { | ||||||
|       paths.push(`/chronicles`); |       paths.push(`/chronicles`); | ||||||
|       serverRuntimeConfig.locales?.forEach((locale: string) => { |       i18n.locales.forEach((locale: string) => { | ||||||
|         paths.push(`/${locale}/chronicles`); |         paths.push(`/${locale}/chronicles`); | ||||||
|       }); |       }); | ||||||
|       body.entry.chronicles.forEach((chronicle) => { |       body.entry.chronicles.forEach((chronicle) => { | ||||||
|         paths.push(`/chronicles/${chronicle.slug}`); |         paths.push(`/chronicles/${chronicle.slug}`); | ||||||
|         serverRuntimeConfig.locales?.forEach((locale: string) => { |         i18n.locales.forEach((locale: string) => { | ||||||
|           paths.push(`/${locale}/chronicles/${chronicle.slug}`); |           paths.push(`/${locale}/chronicles/${chronicle.slug}`); | ||||||
|         }); |         }); | ||||||
|       }); |       }); | ||||||
|  | |||||||
| @ -1,5 +1,6 @@ | |||||||
| import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next"; | import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next"; | ||||||
| import { Fragment, useMemo } from "react"; | import { Fragment, useMemo } from "react"; | ||||||
|  | import { useBoolean } from "usehooks-ts"; | ||||||
| import { AppLayout, AppLayoutRequired } from "components/AppLayout"; | import { AppLayout, AppLayoutRequired } from "components/AppLayout"; | ||||||
| import { Switch } from "components/Inputs/Switch"; | import { Switch } from "components/Inputs/Switch"; | ||||||
| import { PanelHeader } from "components/PanelComponents/PanelHeader"; | import { PanelHeader } from "components/PanelComponents/PanelHeader"; | ||||||
| @ -21,7 +22,6 @@ import { Icon } from "components/Ico"; | |||||||
| import { useMediaHoverable } from "hooks/useMediaQuery"; | import { useMediaHoverable } from "hooks/useMediaQuery"; | ||||||
| import { WithLabel } from "components/Inputs/WithLabel"; | import { WithLabel } from "components/Inputs/WithLabel"; | ||||||
| import { filterHasAttributes, isDefined } from "helpers/others"; | import { filterHasAttributes, isDefined } from "helpers/others"; | ||||||
| import { useBoolean } from "hooks/useBoolean"; |  | ||||||
| import { getOpenGraph } from "helpers/openGraph"; | import { getOpenGraph } from "helpers/openGraph"; | ||||||
| 
 | 
 | ||||||
| /* | /* | ||||||
| @ -36,7 +36,7 @@ interface Props extends AppStaticProps, AppLayoutRequired { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| const Channel = ({ langui, channel, ...otherProps }: Props): JSX.Element => { | const Channel = ({ langui, channel, ...otherProps }: Props): JSX.Element => { | ||||||
|   const { state: keepInfoVisible, toggleState: toggleKeepInfoVisible } = |   const { value: keepInfoVisible, toggle: toggleKeepInfoVisible } = | ||||||
|     useBoolean(true); |     useBoolean(true); | ||||||
|   const hoverable = useMediaHoverable(); |   const hoverable = useMediaHoverable(); | ||||||
| 
 | 
 | ||||||
| @ -58,12 +58,9 @@ const Channel = ({ langui, channel, ...otherProps }: Props): JSX.Element => { | |||||||
|         /> |         /> | ||||||
| 
 | 
 | ||||||
|         {hoverable && ( |         {hoverable && ( | ||||||
|           <WithLabel |           <WithLabel label={langui.always_show_info}> | ||||||
|             label={langui.always_show_info} |             <Switch value={keepInfoVisible} onClick={toggleKeepInfoVisible} /> | ||||||
|             input={ |           </WithLabel> | ||||||
|               <Switch value={keepInfoVisible} onClick={toggleKeepInfoVisible} /> |  | ||||||
|             } |  | ||||||
|           /> |  | ||||||
|         )} |         )} | ||||||
|       </SubPanel> |       </SubPanel> | ||||||
|     ), |     ), | ||||||
|  | |||||||
| @ -1,5 +1,6 @@ | |||||||
| import { GetStaticProps } from "next"; | import { GetStaticProps } from "next"; | ||||||
| import { useMemo, useState } from "react"; | import { useMemo, useState } from "react"; | ||||||
|  | import { useBoolean } from "usehooks-ts"; | ||||||
| import { AppLayout, AppLayoutRequired } from "components/AppLayout"; | import { AppLayout, AppLayoutRequired } from "components/AppLayout"; | ||||||
| import { SmartList } from "components/SmartList"; | import { SmartList } from "components/SmartList"; | ||||||
| import { Icon } from "components/Ico"; | import { Icon } from "components/Ico"; | ||||||
| @ -23,7 +24,6 @@ import { getReadySdk } from "graphql/sdk"; | |||||||
| import { filterHasAttributes } from "helpers/others"; | import { filterHasAttributes } from "helpers/others"; | ||||||
| import { getVideoThumbnailURL } from "helpers/videos"; | import { getVideoThumbnailURL } from "helpers/videos"; | ||||||
| import { useMediaHoverable } from "hooks/useMediaQuery"; | import { useMediaHoverable } from "hooks/useMediaQuery"; | ||||||
| import { useBoolean } from "hooks/useBoolean"; |  | ||||||
| import { getOpenGraph } from "helpers/openGraph"; | import { getOpenGraph } from "helpers/openGraph"; | ||||||
| import { compareDate } from "helpers/date"; | import { compareDate } from "helpers/date"; | ||||||
| 
 | 
 | ||||||
| @ -48,7 +48,7 @@ interface Props extends AppStaticProps, AppLayoutRequired { | |||||||
| const Videos = ({ langui, videos, ...otherProps }: Props): JSX.Element => { | const Videos = ({ langui, videos, ...otherProps }: Props): JSX.Element => { | ||||||
|   const hoverable = useMediaHoverable(); |   const hoverable = useMediaHoverable(); | ||||||
| 
 | 
 | ||||||
|   const { state: keepInfoVisible, toggleState: toggleKeepInfoVisible } = |   const { value: keepInfoVisible, toggle: toggleKeepInfoVisible } = | ||||||
|     useBoolean(true); |     useBoolean(true); | ||||||
| 
 | 
 | ||||||
|   const [searchName, setSearchName] = useState( |   const [searchName, setSearchName] = useState( | ||||||
| @ -80,12 +80,9 @@ const Videos = ({ langui, videos, ...otherProps }: Props): JSX.Element => { | |||||||
|         /> |         /> | ||||||
| 
 | 
 | ||||||
|         {hoverable && ( |         {hoverable && ( | ||||||
|           <WithLabel |           <WithLabel label={langui.always_show_info}> | ||||||
|             label={langui.always_show_info} |             <Switch value={keepInfoVisible} onClick={toggleKeepInfoVisible} /> | ||||||
|             input={ |           </WithLabel> | ||||||
|               <Switch value={keepInfoVisible} onClick={toggleKeepInfoVisible} /> |  | ||||||
|             } |  | ||||||
|           /> |  | ||||||
|         )} |         )} | ||||||
|       </SubPanel> |       </SubPanel> | ||||||
|     ), |     ), | ||||||
|  | |||||||
| @ -17,7 +17,6 @@ import { | |||||||
|   ReturnButton, |   ReturnButton, | ||||||
|   ReturnButtonType, |   ReturnButtonType, | ||||||
| } from "components/PanelComponents/ReturnButton"; | } from "components/PanelComponents/ReturnButton"; | ||||||
| import { TranslatedChroniclesList } from "components/Translated"; |  | ||||||
| import { Icon } from "components/Ico"; | import { Icon } from "components/Ico"; | ||||||
| import { getOpenGraph } from "helpers/openGraph"; | import { getOpenGraph } from "helpers/openGraph"; | ||||||
| import { | import { | ||||||
| @ -25,6 +24,7 @@ import { | |||||||
|   staticSmartLanguage, |   staticSmartLanguage, | ||||||
| } from "helpers/locales"; | } from "helpers/locales"; | ||||||
| import { getDescription } from "helpers/description"; | import { getDescription } from "helpers/description"; | ||||||
|  | import { TranslatedChroniclesList } from "components/Chronicles/ChroniclesList"; | ||||||
| 
 | 
 | ||||||
| /* | /* | ||||||
|  *                                           ╭────────╮ |  *                                           ╭────────╮ | ||||||
|  | |||||||
| @ -9,8 +9,8 @@ import { getReadySdk } from "graphql/sdk"; | |||||||
| import { GetChroniclesChaptersQuery } from "graphql/generated"; | import { GetChroniclesChaptersQuery } from "graphql/generated"; | ||||||
| import { filterHasAttributes } from "helpers/others"; | import { filterHasAttributes } from "helpers/others"; | ||||||
| import { prettySlug } from "helpers/formatters"; | import { prettySlug } from "helpers/formatters"; | ||||||
| import { TranslatedChroniclesList } from "components/Translated"; |  | ||||||
| import { getOpenGraph } from "helpers/openGraph"; | import { getOpenGraph } from "helpers/openGraph"; | ||||||
|  | import { TranslatedChroniclesList } from "components/Chronicles/ChroniclesList"; | ||||||
| 
 | 
 | ||||||
| /* | /* | ||||||
|  *                                           ╭────────╮ |  *                                           ╭────────╮ | ||||||
|  | |||||||
| @ -5,7 +5,10 @@ import { Chip } from "components/Chip"; | |||||||
| import { HorizontalLine } from "components/HorizontalLine"; | import { HorizontalLine } from "components/HorizontalLine"; | ||||||
| import { PreviewCardCTAs } from "components/Library/PreviewCardCTAs"; | import { PreviewCardCTAs } from "components/Library/PreviewCardCTAs"; | ||||||
| import { Markdawn, TableOfContents } from "components/Markdown/Markdawn"; | import { Markdawn, TableOfContents } from "components/Markdown/Markdawn"; | ||||||
| import { ReturnButtonType } from "components/PanelComponents/ReturnButton"; | import { | ||||||
|  |   ReturnButtonType, | ||||||
|  |   TranslatedReturnButton, | ||||||
|  | } from "components/PanelComponents/ReturnButton"; | ||||||
| import { ContentPanel } from "components/Panels/ContentPanel"; | import { ContentPanel } from "components/Panels/ContentPanel"; | ||||||
| import { SubPanel } from "components/Panels/SubPanel"; | import { SubPanel } from "components/Panels/SubPanel"; | ||||||
| import { PreviewCard } from "components/PreviewCard"; | import { PreviewCard } from "components/PreviewCard"; | ||||||
| @ -30,16 +33,13 @@ import { ContentWithTranslations } from "helpers/types"; | |||||||
| import { useMediaMobile } from "hooks/useMediaQuery"; | import { useMediaMobile } from "hooks/useMediaQuery"; | ||||||
| import { AnchorIds, useScrollTopOnChange } from "hooks/useScrollTopOnChange"; | import { AnchorIds, useScrollTopOnChange } from "hooks/useScrollTopOnChange"; | ||||||
| import { useSmartLanguage } from "hooks/useSmartLanguage"; | import { useSmartLanguage } from "hooks/useSmartLanguage"; | ||||||
| import { |  | ||||||
|   TranslatedPreviewLine, |  | ||||||
|   TranslatedReturnButton, |  | ||||||
| } from "components/Translated"; |  | ||||||
| import { getOpenGraph } from "helpers/openGraph"; | import { getOpenGraph } from "helpers/openGraph"; | ||||||
| import { | import { | ||||||
|   getDefaultPreferredLanguages, |   getDefaultPreferredLanguages, | ||||||
|   staticSmartLanguage, |   staticSmartLanguage, | ||||||
| } from "helpers/locales"; | } from "helpers/locales"; | ||||||
| import { getDescription } from "helpers/description"; | import { getDescription } from "helpers/description"; | ||||||
|  | import { TranslatedPreviewLine } from "components/PreviewLine"; | ||||||
| 
 | 
 | ||||||
| /* | /* | ||||||
|  *                                           ╭────────╮ |  *                                           ╭────────╮ | ||||||
|  | |||||||
| @ -1,5 +1,6 @@ | |||||||
| import { GetStaticProps } from "next"; | import { GetStaticProps } from "next"; | ||||||
| import { useState, useMemo, useCallback } from "react"; | import { useState, useMemo, useCallback } from "react"; | ||||||
|  | import { useBoolean } from "usehooks-ts"; | ||||||
| import { AppLayout, AppLayoutRequired } from "components/AppLayout"; | import { AppLayout, AppLayoutRequired } from "components/AppLayout"; | ||||||
| import { Select } from "components/Inputs/Select"; | import { Select } from "components/Inputs/Select"; | ||||||
| import { Switch } from "components/Inputs/Switch"; | import { Switch } from "components/Inputs/Switch"; | ||||||
| @ -21,10 +22,9 @@ import { filterDefined, filterHasAttributes } from "helpers/others"; | |||||||
| import { GetContentsQuery } from "graphql/generated"; | import { GetContentsQuery } from "graphql/generated"; | ||||||
| import { SmartList } from "components/SmartList"; | import { SmartList } from "components/SmartList"; | ||||||
| import { SelectiveNonNullable } from "helpers/types/SelectiveNonNullable"; | import { SelectiveNonNullable } from "helpers/types/SelectiveNonNullable"; | ||||||
| import { useBoolean } from "hooks/useBoolean"; |  | ||||||
| import { TranslatedPreviewCard } from "components/Translated"; |  | ||||||
| import { getOpenGraph } from "helpers/openGraph"; | import { getOpenGraph } from "helpers/openGraph"; | ||||||
| import { HorizontalLine } from "components/HorizontalLine"; | import { HorizontalLine } from "components/HorizontalLine"; | ||||||
|  | import { TranslatedPreviewCard } from "components/PreviewCard"; | ||||||
| 
 | 
 | ||||||
| /* | /* | ||||||
|  *                                         ╭─────────────╮ |  *                                         ╭─────────────╮ | ||||||
| @ -58,9 +58,9 @@ const Contents = ({ | |||||||
|     DEFAULT_FILTERS_STATE.groupingMethod |     DEFAULT_FILTERS_STATE.groupingMethod | ||||||
|   ); |   ); | ||||||
|   const { |   const { | ||||||
|     state: keepInfoVisible, |     value: keepInfoVisible, | ||||||
|     toggleState: toggleKeepInfoVisible, |     toggle: toggleKeepInfoVisible, | ||||||
|     setState: setKeepInfoVisible, |     setValue: setKeepInfoVisible, | ||||||
|   } = useBoolean(DEFAULT_FILTERS_STATE.keepInfoVisible); |   } = useBoolean(DEFAULT_FILTERS_STATE.keepInfoVisible); | ||||||
| 
 | 
 | ||||||
|   const [searchName, setSearchName] = useState( |   const [searchName, setSearchName] = useState( | ||||||
| @ -150,26 +150,20 @@ const Contents = ({ | |||||||
|           onChange={setSearchName} |           onChange={setSearchName} | ||||||
|         /> |         /> | ||||||
| 
 | 
 | ||||||
|         <WithLabel |         <WithLabel label={langui.group_by}> | ||||||
|           label={langui.group_by} |           <Select | ||||||
|           input={ |             className="w-full" | ||||||
|             <Select |             options={[langui.category ?? "Category", langui.type ?? "Type"]} | ||||||
|               className="w-full" |             value={groupingMethod} | ||||||
|               options={[langui.category ?? "Category", langui.type ?? "Type"]} |             onChange={setGroupingMethod} | ||||||
|               value={groupingMethod} |             allowEmpty | ||||||
|               onChange={setGroupingMethod} |           /> | ||||||
|               allowEmpty |         </WithLabel> | ||||||
|             /> |  | ||||||
|           } |  | ||||||
|         /> |  | ||||||
| 
 | 
 | ||||||
|         {hoverable && ( |         {hoverable && ( | ||||||
|           <WithLabel |           <WithLabel label={langui.always_show_info}> | ||||||
|             label={langui.always_show_info} |             <Switch onClick={toggleKeepInfoVisible} value={keepInfoVisible} /> | ||||||
|             input={ |           </WithLabel> | ||||||
|               <Switch onClick={toggleKeepInfoVisible} value={keepInfoVisible} /> |  | ||||||
|             } |  | ||||||
|           /> |  | ||||||
|         )} |         )} | ||||||
| 
 | 
 | ||||||
|         <Button |         <Button | ||||||
|  | |||||||
| @ -1,5 +1,5 @@ | |||||||
| import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next"; | import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next"; | ||||||
| import { useMemo } from "react"; | import { useCallback, useMemo } from "react"; | ||||||
| import { AppLayout, AppLayoutRequired } from "components/AppLayout"; | import { AppLayout, AppLayoutRequired } from "components/AppLayout"; | ||||||
| import { | import { | ||||||
|   ContentPanel, |   ContentPanel, | ||||||
| @ -16,16 +16,14 @@ import { | |||||||
| } from "helpers/locales"; | } from "helpers/locales"; | ||||||
| import { prettySlug } from "helpers/formatters"; | import { prettySlug } from "helpers/formatters"; | ||||||
| import { SmartList } from "components/SmartList"; | import { SmartList } from "components/SmartList"; | ||||||
| import { |  | ||||||
|   TranslatedButton, |  | ||||||
|   TranslatedPreviewCard, |  | ||||||
|   TranslatedPreviewFolder, |  | ||||||
| } from "components/Translated"; |  | ||||||
| import { Ico, Icon } from "components/Ico"; | import { Ico, Icon } from "components/Ico"; | ||||||
| import { Button } from "components/Inputs/Button"; | import { Button, TranslatedButton } from "components/Inputs/Button"; | ||||||
| import { Link } from "components/Inputs/Link"; | import { Link } from "components/Inputs/Link"; | ||||||
| import { PanelHeader } from "components/PanelComponents/PanelHeader"; | import { PanelHeader } from "components/PanelComponents/PanelHeader"; | ||||||
| import { SubPanel } from "components/Panels/SubPanel"; | import { SubPanel } from "components/Panels/SubPanel"; | ||||||
|  | import { TranslatedProps } from "helpers/types/TranslatedProps"; | ||||||
|  | import { useSmartLanguage } from "hooks/useSmartLanguage"; | ||||||
|  | import { TranslatedPreviewCard } from "components/PreviewCard"; | ||||||
| 
 | 
 | ||||||
| /* | /* | ||||||
|  *                                           ╭────────╮ |  *                                           ╭────────╮ | ||||||
| @ -304,6 +302,30 @@ export const PreviewFolder = ({ | |||||||
|   </Link> |   </Link> | ||||||
| ); | ); | ||||||
| 
 | 
 | ||||||
|  | // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | ||||||
|  | 
 | ||||||
|  | export const TranslatedPreviewFolder = ({ | ||||||
|  |   translations, | ||||||
|  |   fallback, | ||||||
|  |   ...otherProps | ||||||
|  | }: TranslatedProps<PreviewFolderProps, "title">): JSX.Element => { | ||||||
|  |   const [selectedTranslation] = useSmartLanguage({ | ||||||
|  |     items: translations, | ||||||
|  |     languageExtractor: useCallback( | ||||||
|  |       (item: { language: string }): string => item.language, | ||||||
|  |       [] | ||||||
|  |     ), | ||||||
|  |   }); | ||||||
|  |   return ( | ||||||
|  |     <PreviewFolder | ||||||
|  |       title={selectedTranslation?.title ?? fallback.title} | ||||||
|  |       {...otherProps} | ||||||
|  |     /> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | ||||||
|  | 
 | ||||||
| interface NoContentNorFolderMessageProps { | interface NoContentNorFolderMessageProps { | ||||||
|   langui: AppStaticProps["langui"]; |   langui: AppStaticProps["langui"]; | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,6 +1,7 @@ | |||||||
| import { Fragment, useCallback, useMemo } from "react"; | import { Fragment, useCallback, useMemo } from "react"; | ||||||
| import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next"; | import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next"; | ||||||
| import { useRouter } from "next/router"; | import { useRouter } from "next/router"; | ||||||
|  | import { useBoolean } from "usehooks-ts"; | ||||||
| import { AppLayout, AppLayoutRequired } from "components/AppLayout"; | import { AppLayout, AppLayoutRequired } from "components/AppLayout"; | ||||||
| import { Chip } from "components/Chip"; | import { Chip } from "components/Chip"; | ||||||
| import { Img } from "components/Img"; | import { Img } from "components/Img"; | ||||||
| @ -53,7 +54,6 @@ import { WithLabel } from "components/Inputs/WithLabel"; | |||||||
| import { Ico, Icon } from "components/Ico"; | import { Ico, Icon } from "components/Ico"; | ||||||
| import { cJoin, cIf } from "helpers/className"; | import { cJoin, cIf } from "helpers/className"; | ||||||
| import { useSmartLanguage } from "hooks/useSmartLanguage"; | import { useSmartLanguage } from "hooks/useSmartLanguage"; | ||||||
| import { useBoolean } from "hooks/useBoolean"; |  | ||||||
| import { getOpenGraph } from "helpers/openGraph"; | import { getOpenGraph } from "helpers/openGraph"; | ||||||
| import { getDescription } from "helpers/description"; | import { getDescription } from "helpers/description"; | ||||||
| 
 | 
 | ||||||
| @ -85,7 +85,7 @@ const LibrarySlug = ({ | |||||||
|   const hoverable = useMediaHoverable(); |   const hoverable = useMediaHoverable(); | ||||||
|   const router = useRouter(); |   const router = useRouter(); | ||||||
|   const [openLightBox, LightBox] = useLightBox(); |   const [openLightBox, LightBox] = useLightBox(); | ||||||
|   const { state: keepInfoVisible, toggleState: toggleKeepInfoVisible } = |   const { value: keepInfoVisible, toggle: toggleKeepInfoVisible } = | ||||||
|     useBoolean(false); |     useBoolean(false); | ||||||
| 
 | 
 | ||||||
|   useScrollTopOnChange(AnchorIds.ContentPanel, [item]); |   useScrollTopOnChange(AnchorIds.ContentPanel, [item]); | ||||||
| @ -446,15 +446,12 @@ const LibrarySlug = ({ | |||||||
|               </h2> |               </h2> | ||||||
| 
 | 
 | ||||||
|               {hoverable && ( |               {hoverable && ( | ||||||
|                 <WithLabel |                 <WithLabel label={langui.always_show_info}> | ||||||
|                   label={langui.always_show_info} |                   <Switch | ||||||
|                   input={ |                     onClick={toggleKeepInfoVisible} | ||||||
|                     <Switch |                     value={keepInfoVisible} | ||||||
|                       onClick={toggleKeepInfoVisible} |                   /> | ||||||
|                       value={keepInfoVisible} |                 </WithLabel> | ||||||
|                     /> |  | ||||||
|                   } |  | ||||||
|                 /> |  | ||||||
|               )} |               )} | ||||||
| 
 | 
 | ||||||
|               <div |               <div | ||||||
| @ -725,7 +722,7 @@ const ContentLine = ({ | |||||||
|   slug, |   slug, | ||||||
|   parentSlug, |   parentSlug, | ||||||
| }: ContentLineProps): JSX.Element => { | }: ContentLineProps): JSX.Element => { | ||||||
|   const { state: isOpened, toggleState: toggleOpened } = useBoolean(false); |   const { value: isOpened, toggle: toggleOpened } = useBoolean(false); | ||||||
| 
 | 
 | ||||||
|   const [selectedTranslation] = useSmartLanguage({ |   const [selectedTranslation] = useSmartLanguage({ | ||||||
|     items: content?.translations ?? [], |     items: content?.translations ?? [], | ||||||
|  | |||||||
| @ -1,7 +1,6 @@ | |||||||
| import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next"; | import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next"; | ||||||
| import { Fragment, useMemo } from "react"; | import { Fragment, useCallback, useMemo } from "react"; | ||||||
| import { AppLayout, AppLayoutRequired } from "components/AppLayout"; | import { AppLayout, AppLayoutRequired } from "components/AppLayout"; | ||||||
| import { ScanSetCover } from "components/Library/ScanSetCover"; |  | ||||||
| import { | import { | ||||||
|   ReturnButton, |   ReturnButton, | ||||||
|   ReturnButtonType, |   ReturnButtonType, | ||||||
| @ -11,7 +10,10 @@ import { | |||||||
|   ContentPanelWidthSizes, |   ContentPanelWidthSizes, | ||||||
| } from "components/Panels/ContentPanel"; | } from "components/Panels/ContentPanel"; | ||||||
| import { SubPanel } from "components/Panels/SubPanel"; | import { SubPanel } from "components/Panels/SubPanel"; | ||||||
| import { GetLibraryItemScansQuery } from "graphql/generated"; | import { | ||||||
|  |   GetLibraryItemScansQuery, | ||||||
|  |   UploadImageFragment, | ||||||
|  | } from "graphql/generated"; | ||||||
| import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps"; | import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps"; | ||||||
| import { getReadySdk } from "graphql/sdk"; | import { getReadySdk } from "graphql/sdk"; | ||||||
| import { | import { | ||||||
| @ -21,7 +23,9 @@ import { | |||||||
| } from "helpers/formatters"; | } from "helpers/formatters"; | ||||||
| import { | import { | ||||||
|   filterHasAttributes, |   filterHasAttributes, | ||||||
|  |   getStatusDescription, | ||||||
|   isDefined, |   isDefined, | ||||||
|  |   isDefinedAndNotEmpty, | ||||||
|   sortRangedContent, |   sortRangedContent, | ||||||
| } from "helpers/others"; | } from "helpers/others"; | ||||||
| import { useLightBox } from "hooks/useLightBox"; | import { useLightBox } from "hooks/useLightBox"; | ||||||
| @ -29,8 +33,17 @@ import { isUntangibleGroupItem } from "helpers/libraryItem"; | |||||||
| import { PreviewCardCTAs } from "components/Library/PreviewCardCTAs"; | import { PreviewCardCTAs } from "components/Library/PreviewCardCTAs"; | ||||||
| import { PreviewCard } from "components/PreviewCard"; | import { PreviewCard } from "components/PreviewCard"; | ||||||
| import { HorizontalLine } from "components/HorizontalLine"; | import { HorizontalLine } from "components/HorizontalLine"; | ||||||
| import { TranslatedNavOption, TranslatedScanSet } from "components/Translated"; |  | ||||||
| import { getOpenGraph } from "helpers/openGraph"; | import { getOpenGraph } from "helpers/openGraph"; | ||||||
|  | import { Chip } from "components/Chip"; | ||||||
|  | import { Img } from "components/Img"; | ||||||
|  | import { Button } from "components/Inputs/Button"; | ||||||
|  | import { RecorderChip } from "components/RecorderChip"; | ||||||
|  | import { ToolTip } from "components/ToolTip"; | ||||||
|  | import { getAssetFilename, getAssetURL, ImageQuality } from "helpers/img"; | ||||||
|  | import { isInteger } from "helpers/numbers"; | ||||||
|  | import { useSmartLanguage } from "hooks/useSmartLanguage"; | ||||||
|  | import { TranslatedProps } from "helpers/types/TranslatedProps"; | ||||||
|  | import { TranslatedNavOption } from "components/PanelComponents/NavOption"; | ||||||
| 
 | 
 | ||||||
| /* | /* | ||||||
|  *                                           ╭────────╮ |  *                                           ╭────────╮ | ||||||
| @ -297,3 +310,434 @@ export const getStaticPaths: GetStaticPaths = async (context) => { | |||||||
|     fallback: "blocking", |     fallback: "blocking", | ||||||
|   }; |   }; | ||||||
| }; | }; | ||||||
|  | 
 | ||||||
|  | /* | ||||||
|  |  *                                    ╭──────────────────────╮ | ||||||
|  |  * ───────────────────────────────────╯  PRIVATE COMPONENTS  ╰────────────────────────────────────── | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | /* | ||||||
|  |  *                                        ╭─────────────╮ | ||||||
|  |  * ───────────────────────────────────────╯  COMPONENT  ╰─────────────────────────────────────────── | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | interface ScanSetProps { | ||||||
|  |   openLightBox: (images: string[], index?: number) => void; | ||||||
|  |   scanSet: NonNullable< | ||||||
|  |     NonNullable< | ||||||
|  |       NonNullable< | ||||||
|  |         NonNullable< | ||||||
|  |           NonNullable< | ||||||
|  |             GetLibraryItemScansQuery["libraryItems"] | ||||||
|  |           >["data"][number]["attributes"] | ||||||
|  |         >["contents"] | ||||||
|  |       >["data"][number]["attributes"] | ||||||
|  |     >["scan_set"] | ||||||
|  |   >; | ||||||
|  |   id: string; | ||||||
|  |   title: string; | ||||||
|  |   languages: AppStaticProps["languages"]; | ||||||
|  |   langui: AppStaticProps["langui"]; | ||||||
|  |   content: NonNullable< | ||||||
|  |     NonNullable< | ||||||
|  |       NonNullable< | ||||||
|  |         NonNullable< | ||||||
|  |           GetLibraryItemScansQuery["libraryItems"] | ||||||
|  |         >["data"][number]["attributes"] | ||||||
|  |       >["contents"] | ||||||
|  |     >["data"][number]["attributes"] | ||||||
|  |   >["content"]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const ScanSet = ({ | ||||||
|  |   openLightBox, | ||||||
|  |   scanSet, | ||||||
|  |   id, | ||||||
|  |   title, | ||||||
|  |   languages, | ||||||
|  |   langui, | ||||||
|  |   content, | ||||||
|  | }: ScanSetProps): JSX.Element => { | ||||||
|  |   const [selectedScan, LanguageSwitcher, languageSwitcherProps] = | ||||||
|  |     useSmartLanguage({ | ||||||
|  |       items: scanSet, | ||||||
|  |       languages: languages, | ||||||
|  |       languageExtractor: useCallback( | ||||||
|  |         (item: NonNullable<ScanSetProps["scanSet"][number]>) => | ||||||
|  |           item.language?.data?.attributes?.code, | ||||||
|  |         [] | ||||||
|  |       ), | ||||||
|  |       transform: useCallback( | ||||||
|  |         (item: NonNullable<ScanSetProps["scanSet"][number]>) => { | ||||||
|  |           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 (isInteger(aName) && isInteger(bName)) { | ||||||
|  |                 return parseInt(aName, 10) - parseInt(bName, 10); | ||||||
|  |               } | ||||||
|  |               return a.attributes.url.localeCompare(b.attributes.url); | ||||||
|  |             } | ||||||
|  |             return 0; | ||||||
|  |           }); | ||||||
|  |           return item; | ||||||
|  |         }, | ||||||
|  |         [] | ||||||
|  |       ), | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |   const pages = useMemo( | ||||||
|  |     () => filterHasAttributes(selectedScan?.pages?.data, ["attributes"]), | ||||||
|  |     [selectedScan] | ||||||
|  |   ); | ||||||
|  | 
 | ||||||
|  |   return ( | ||||||
|  |     <> | ||||||
|  |       {selectedScan && isDefined(pages) && ( | ||||||
|  |         <div> | ||||||
|  |           <div | ||||||
|  |             className="flex flex-row flex-wrap place-items-center | ||||||
|  |           gap-6 pt-10 text-base first-of-type:pt-0" | ||||||
|  |           > | ||||||
|  |             <h2 id={id} className="text-2xl"> | ||||||
|  |               {title} | ||||||
|  |             </h2> | ||||||
|  | 
 | ||||||
|  |             <Chip | ||||||
|  |               text={ | ||||||
|  |                 selectedScan.language?.data?.attributes?.code === | ||||||
|  |                 selectedScan.source_language?.data?.attributes?.code | ||||||
|  |                   ? langui.scan ?? "Scan" | ||||||
|  |                   : langui.scanlation ?? "Scanlation" | ||||||
|  |               } | ||||||
|  |             /> | ||||||
|  |           </div> | ||||||
|  | 
 | ||||||
|  |           <div className="flex flex-row flex-wrap place-items-center gap-4 pb-6"> | ||||||
|  |             {content?.data?.attributes && | ||||||
|  |               isDefinedAndNotEmpty(content.data.attributes.slug) && ( | ||||||
|  |                 <Button | ||||||
|  |                   href={`/contents/${content.data.attributes.slug}`} | ||||||
|  |                   text={langui.open_content} | ||||||
|  |                 /> | ||||||
|  |               )} | ||||||
|  | 
 | ||||||
|  |             {languageSwitcherProps.locales.size > 1 && ( | ||||||
|  |               <LanguageSwitcher {...languageSwitcherProps} /> | ||||||
|  |             )} | ||||||
|  | 
 | ||||||
|  |             <div className="grid place-content-center place-items-center"> | ||||||
|  |               <p className="font-headers font-bold">{langui.status}:</p> | ||||||
|  |               <ToolTip | ||||||
|  |                 content={getStatusDescription(selectedScan.status, langui)} | ||||||
|  |                 maxWidth={"20rem"} | ||||||
|  |               > | ||||||
|  |                 <Chip text={selectedScan.status} /> | ||||||
|  |               </ToolTip> | ||||||
|  |             </div> | ||||||
|  | 
 | ||||||
|  |             {selectedScan.scanners && selectedScan.scanners.data.length > 0 && ( | ||||||
|  |               <div> | ||||||
|  |                 <p className="font-headers font-bold">{langui.scanners}:</p> | ||||||
|  |                 <div className="grid place-content-center place-items-center gap-2"> | ||||||
|  |                   {filterHasAttributes(selectedScan.scanners.data, [ | ||||||
|  |                     "id", | ||||||
|  |                     "attributes", | ||||||
|  |                   ] as const).map((scanner) => ( | ||||||
|  |                     <Fragment key={scanner.id}> | ||||||
|  |                       <RecorderChip | ||||||
|  |                         langui={langui} | ||||||
|  |                         recorder={scanner.attributes} | ||||||
|  |                       /> | ||||||
|  |                     </Fragment> | ||||||
|  |                   ))} | ||||||
|  |                 </div> | ||||||
|  |               </div> | ||||||
|  |             )} | ||||||
|  | 
 | ||||||
|  |             {selectedScan.cleaners && selectedScan.cleaners.data.length > 0 && ( | ||||||
|  |               <div> | ||||||
|  |                 <p className="font-headers font-bold">{langui.cleaners}:</p> | ||||||
|  |                 <div className="grid place-content-center place-items-center gap-2"> | ||||||
|  |                   {filterHasAttributes(selectedScan.cleaners.data, [ | ||||||
|  |                     "id", | ||||||
|  |                     "attributes", | ||||||
|  |                   ] as const).map((cleaner) => ( | ||||||
|  |                     <Fragment key={cleaner.id}> | ||||||
|  |                       <RecorderChip | ||||||
|  |                         langui={langui} | ||||||
|  |                         recorder={cleaner.attributes} | ||||||
|  |                       /> | ||||||
|  |                     </Fragment> | ||||||
|  |                   ))} | ||||||
|  |                 </div> | ||||||
|  |               </div> | ||||||
|  |             )} | ||||||
|  | 
 | ||||||
|  |             {selectedScan.typesetters && | ||||||
|  |               selectedScan.typesetters.data.length > 0 && ( | ||||||
|  |                 <div> | ||||||
|  |                   <p className="font-headers font-bold"> | ||||||
|  |                     {langui.typesetters}: | ||||||
|  |                   </p> | ||||||
|  |                   <div className="grid place-content-center place-items-center gap-2"> | ||||||
|  |                     {filterHasAttributes(selectedScan.typesetters.data, [ | ||||||
|  |                       "id", | ||||||
|  |                       "attributes", | ||||||
|  |                     ] as const).map((typesetter) => ( | ||||||
|  |                       <Fragment key={typesetter.id}> | ||||||
|  |                         <RecorderChip | ||||||
|  |                           langui={langui} | ||||||
|  |                           recorder={typesetter.attributes} | ||||||
|  |                         /> | ||||||
|  |                       </Fragment> | ||||||
|  |                     ))} | ||||||
|  |                   </div> | ||||||
|  |                 </div> | ||||||
|  |               )} | ||||||
|  | 
 | ||||||
|  |             {isDefinedAndNotEmpty(selectedScan.notes) && ( | ||||||
|  |               <ToolTip content={selectedScan.notes}> | ||||||
|  |                 <Chip text={langui.notes ?? "Notes"} /> | ||||||
|  |               </ToolTip> | ||||||
|  |             )} | ||||||
|  |           </div> | ||||||
|  | 
 | ||||||
|  |           <div | ||||||
|  |             className="grid items-end gap-8 border-b-[3px] border-dotted pb-12 last-of-type:border-0 | ||||||
|  |              desktop:grid-cols-[repeat(auto-fill,_minmax(10rem,1fr))] mobile:grid-cols-2" | ||||||
|  |           > | ||||||
|  |             {pages.map((page, index) => ( | ||||||
|  |               <div | ||||||
|  |                 key={page.id} | ||||||
|  |                 className="cursor-pointer transition-transform | ||||||
|  |                 drop-shadow-shade-lg hover:scale-[1.02]" | ||||||
|  |                 onClick={() => { | ||||||
|  |                   const images = pages.map((image) => | ||||||
|  |                     getAssetURL(image.attributes.url, ImageQuality.Large) | ||||||
|  |                   ); | ||||||
|  |                   openLightBox(images, index); | ||||||
|  |                 }} | ||||||
|  |               > | ||||||
|  |                 <Img src={page.attributes} quality={ImageQuality.Small} /> | ||||||
|  |               </div> | ||||||
|  |             ))} | ||||||
|  |           </div> | ||||||
|  |         </div> | ||||||
|  |       )} | ||||||
|  |     </> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | ||||||
|  | 
 | ||||||
|  | const TranslatedScanSet = ({ | ||||||
|  |   translations, | ||||||
|  |   fallback, | ||||||
|  |   ...otherProps | ||||||
|  | }: TranslatedProps<ScanSetProps, "title">): JSX.Element => { | ||||||
|  |   const [selectedTranslation] = useSmartLanguage({ | ||||||
|  |     items: translations, | ||||||
|  |     languageExtractor: useCallback( | ||||||
|  |       (item: { language: string }): string => item.language, | ||||||
|  |       [] | ||||||
|  |     ), | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   return ( | ||||||
|  |     <ScanSet | ||||||
|  |       title={selectedTranslation?.title ?? fallback.title} | ||||||
|  |       {...otherProps} | ||||||
|  |     /> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | ||||||
|  | 
 | ||||||
|  | interface ScanSetCoverProps { | ||||||
|  |   openLightBox: (images: string[], index?: number) => void; | ||||||
|  |   images: NonNullable< | ||||||
|  |     NonNullable< | ||||||
|  |       NonNullable< | ||||||
|  |         GetLibraryItemScansQuery["libraryItems"] | ||||||
|  |       >["data"][number]["attributes"] | ||||||
|  |     >["images"] | ||||||
|  |   >; | ||||||
|  |   languages: AppStaticProps["languages"]; | ||||||
|  |   langui: AppStaticProps["langui"]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const ScanSetCover = ({ | ||||||
|  |   openLightBox, | ||||||
|  |   images, | ||||||
|  |   languages, | ||||||
|  |   langui, | ||||||
|  | }: ScanSetCoverProps): JSX.Element => { | ||||||
|  |   const [selectedScan, LanguageSwitcher, languageSwitcherProps] = | ||||||
|  |     useSmartLanguage({ | ||||||
|  |       items: images, | ||||||
|  |       languages: languages, | ||||||
|  |       languageExtractor: useCallback( | ||||||
|  |         (item: NonNullable<ScanSetCoverProps["images"][number]>) => | ||||||
|  |           item.language?.data?.attributes?.code, | ||||||
|  |         [] | ||||||
|  |       ), | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |   const coverImages = useMemo(() => { | ||||||
|  |     const memo: UploadImageFragment[] = []; | ||||||
|  |     if (selectedScan?.obi_belt?.full?.data?.attributes) | ||||||
|  |       memo.push(selectedScan.obi_belt.full.data.attributes); | ||||||
|  |     if (selectedScan?.obi_belt?.inside_full?.data?.attributes) | ||||||
|  |       memo.push(selectedScan.obi_belt.inside_full.data.attributes); | ||||||
|  |     if (selectedScan?.dust_jacket?.full?.data?.attributes) | ||||||
|  |       memo.push(selectedScan.dust_jacket.full.data.attributes); | ||||||
|  |     if (selectedScan?.dust_jacket?.inside_full?.data?.attributes) | ||||||
|  |       memo.push(selectedScan.dust_jacket.inside_full.data.attributes); | ||||||
|  |     if (selectedScan?.cover?.full?.data?.attributes) | ||||||
|  |       memo.push(selectedScan.cover.full.data.attributes); | ||||||
|  |     if (selectedScan?.cover?.inside_full?.data?.attributes) | ||||||
|  |       memo.push(selectedScan.cover.inside_full.data.attributes); | ||||||
|  |     return memo; | ||||||
|  |   }, [selectedScan]); | ||||||
|  | 
 | ||||||
|  |   return ( | ||||||
|  |     <> | ||||||
|  |       {coverImages.length > 0 && selectedScan && ( | ||||||
|  |         <div> | ||||||
|  |           <div | ||||||
|  |             className="flex flex-row flex-wrap place-items-center | ||||||
|  |           gap-6 pt-10 text-base first-of-type:pt-0" | ||||||
|  |           > | ||||||
|  |             <h2 id={"cover"} className="text-2xl"> | ||||||
|  |               {langui.cover} | ||||||
|  |             </h2> | ||||||
|  | 
 | ||||||
|  |             <Chip | ||||||
|  |               text={ | ||||||
|  |                 selectedScan.language?.data?.attributes?.code === | ||||||
|  |                 selectedScan.source_language?.data?.attributes?.code | ||||||
|  |                   ? langui.scan ?? "Scan" | ||||||
|  |                   : langui.scanlation ?? "Scanlation" | ||||||
|  |               } | ||||||
|  |             /> | ||||||
|  |           </div> | ||||||
|  | 
 | ||||||
|  |           <div className="flex flex-row flex-wrap place-items-center gap-4 pb-6"> | ||||||
|  |             <LanguageSwitcher {...languageSwitcherProps} /> | ||||||
|  | 
 | ||||||
|  |             <div className="grid place-content-center place-items-center"> | ||||||
|  |               <p className="font-headers font-bold">{langui.status}:</p> | ||||||
|  |               <ToolTip | ||||||
|  |                 content={getStatusDescription(selectedScan.status, langui)} | ||||||
|  |                 maxWidth={"20rem"} | ||||||
|  |               > | ||||||
|  |                 <Chip text={selectedScan.status} /> | ||||||
|  |               </ToolTip> | ||||||
|  |             </div> | ||||||
|  | 
 | ||||||
|  |             {selectedScan.scanners && selectedScan.scanners.data.length > 0 && ( | ||||||
|  |               <div> | ||||||
|  |                 <p className="font-headers font-bold">{langui.scanners}:</p> | ||||||
|  |                 <div className="grid place-content-center place-items-center gap-2"> | ||||||
|  |                   {filterHasAttributes(selectedScan.scanners.data, [ | ||||||
|  |                     "id", | ||||||
|  |                     "attributes", | ||||||
|  |                   ] as const).map((scanner) => ( | ||||||
|  |                     <Fragment key={scanner.id}> | ||||||
|  |                       <RecorderChip | ||||||
|  |                         langui={langui} | ||||||
|  |                         recorder={scanner.attributes} | ||||||
|  |                       /> | ||||||
|  |                     </Fragment> | ||||||
|  |                   ))} | ||||||
|  |                 </div> | ||||||
|  |               </div> | ||||||
|  |             )} | ||||||
|  | 
 | ||||||
|  |             {selectedScan.cleaners && selectedScan.cleaners.data.length > 0 && ( | ||||||
|  |               <div> | ||||||
|  |                 <p className="font-headers font-bold">{langui.cleaners}:</p> | ||||||
|  |                 <div className="grid place-content-center place-items-center gap-2"> | ||||||
|  |                   {filterHasAttributes(selectedScan.cleaners.data, [ | ||||||
|  |                     "id", | ||||||
|  |                     "attributes", | ||||||
|  |                   ] as const).map((cleaner) => ( | ||||||
|  |                     <Fragment key={cleaner.id}> | ||||||
|  |                       <RecorderChip | ||||||
|  |                         langui={langui} | ||||||
|  |                         recorder={cleaner.attributes} | ||||||
|  |                       /> | ||||||
|  |                     </Fragment> | ||||||
|  |                   ))} | ||||||
|  |                 </div> | ||||||
|  |               </div> | ||||||
|  |             )} | ||||||
|  | 
 | ||||||
|  |             {selectedScan.typesetters && | ||||||
|  |               selectedScan.typesetters.data.length > 0 && ( | ||||||
|  |                 <div> | ||||||
|  |                   <p className="font-headers font-bold"> | ||||||
|  |                     {langui.typesetters}: | ||||||
|  |                   </p> | ||||||
|  |                   <div className="grid place-content-center place-items-center gap-2"> | ||||||
|  |                     {filterHasAttributes(selectedScan.typesetters.data, [ | ||||||
|  |                       "id", | ||||||
|  |                       "attributes", | ||||||
|  |                     ] as const).map((typesetter) => ( | ||||||
|  |                       <Fragment key={typesetter.id}> | ||||||
|  |                         <RecorderChip | ||||||
|  |                           langui={langui} | ||||||
|  |                           recorder={typesetter.attributes} | ||||||
|  |                         /> | ||||||
|  |                       </Fragment> | ||||||
|  |                     ))} | ||||||
|  |                   </div> | ||||||
|  |                 </div> | ||||||
|  |               )} | ||||||
|  |           </div> | ||||||
|  | 
 | ||||||
|  |           <div | ||||||
|  |             className="grid items-end gap-8 border-b-[3px] border-dotted pb-12 | ||||||
|  |               last-of-type:border-0 desktop:grid-cols-[repeat(auto-fill,_minmax(10rem,1fr))] | ||||||
|  |               mobile:grid-cols-2" | ||||||
|  |           > | ||||||
|  |             {coverImages.map((image, index) => ( | ||||||
|  |               <div | ||||||
|  |                 key={image.url} | ||||||
|  |                 className="cursor-pointer transition-transform | ||||||
|  |                   drop-shadow-shade-lg hover:scale-[1.02]" | ||||||
|  |                 onClick={() => { | ||||||
|  |                   const imgs = coverImages.map((img) => | ||||||
|  |                     getAssetURL(img.url, ImageQuality.Large) | ||||||
|  |                   ); | ||||||
|  | 
 | ||||||
|  |                   openLightBox(imgs, index); | ||||||
|  |                 }} | ||||||
|  |               > | ||||||
|  |                 <Img src={image} quality={ImageQuality.Small} /> | ||||||
|  |               </div> | ||||||
|  |             ))} | ||||||
|  |           </div> | ||||||
|  |         </div> | ||||||
|  |       )} | ||||||
|  |     </> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
|  | |||||||
| @ -1,5 +1,6 @@ | |||||||
| import { GetStaticProps } from "next"; | import { GetStaticProps } from "next"; | ||||||
| import { useState, useMemo, useCallback } from "react"; | import { useState, useMemo, useCallback } from "react"; | ||||||
|  | import { useBoolean } from "usehooks-ts"; | ||||||
| import { AppLayout, AppLayoutRequired } from "components/AppLayout"; | import { AppLayout, AppLayoutRequired } from "components/AppLayout"; | ||||||
| import { Select } from "components/Inputs/Select"; | import { Select } from "components/Inputs/Select"; | ||||||
| import { Switch } from "components/Inputs/Switch"; | import { Switch } from "components/Inputs/Switch"; | ||||||
| @ -28,7 +29,6 @@ import { useAppLayout } from "contexts/AppLayoutContext"; | |||||||
| import { convertPrice } from "helpers/numbers"; | import { convertPrice } from "helpers/numbers"; | ||||||
| import { SmartList } from "components/SmartList"; | import { SmartList } from "components/SmartList"; | ||||||
| import { SelectiveNonNullable } from "helpers/types/SelectiveNonNullable"; | import { SelectiveNonNullable } from "helpers/types/SelectiveNonNullable"; | ||||||
| import { useBoolean } from "hooks/useBoolean"; |  | ||||||
| import { getOpenGraph } from "helpers/openGraph"; | import { getOpenGraph } from "helpers/openGraph"; | ||||||
| import { compareDate } from "helpers/date"; | import { compareDate } from "helpers/date"; | ||||||
| 
 | 
 | ||||||
| @ -71,27 +71,27 @@ const Library = ({ | |||||||
|   ); |   ); | ||||||
| 
 | 
 | ||||||
|   const { |   const { | ||||||
|     state: showSubitems, |     value: showSubitems, | ||||||
|     toggleState: toggleShowSubitems, |     toggle: toggleShowSubitems, | ||||||
|     setState: setShowSubitems, |     setValue: setShowSubitems, | ||||||
|   } = useBoolean(DEFAULT_FILTERS_STATE.showSubitems); |   } = useBoolean(DEFAULT_FILTERS_STATE.showSubitems); | ||||||
| 
 | 
 | ||||||
|   const { |   const { | ||||||
|     state: showPrimaryItems, |     value: showPrimaryItems, | ||||||
|     toggleState: toggleShowPrimaryItems, |     toggle: toggleShowPrimaryItems, | ||||||
|     setState: setShowPrimaryItems, |     setValue: setShowPrimaryItems, | ||||||
|   } = useBoolean(DEFAULT_FILTERS_STATE.showPrimaryItems); |   } = useBoolean(DEFAULT_FILTERS_STATE.showPrimaryItems); | ||||||
| 
 | 
 | ||||||
|   const { |   const { | ||||||
|     state: showSecondaryItems, |     value: showSecondaryItems, | ||||||
|     toggleState: toggleShowSecondaryItems, |     toggle: toggleShowSecondaryItems, | ||||||
|     setState: setShowSecondaryItems, |     setValue: setShowSecondaryItems, | ||||||
|   } = useBoolean(DEFAULT_FILTERS_STATE.showSecondaryItems); |   } = useBoolean(DEFAULT_FILTERS_STATE.showSecondaryItems); | ||||||
| 
 | 
 | ||||||
|   const { |   const { | ||||||
|     state: keepInfoVisible, |     value: keepInfoVisible, | ||||||
|     toggleState: toggleKeepInfoVisible, |     toggle: toggleKeepInfoVisible, | ||||||
|     setState: setKeepInfoVisible, |     setValue: setKeepInfoVisible, | ||||||
|   } = useBoolean(DEFAULT_FILTERS_STATE.keepInfoVisible); |   } = useBoolean(DEFAULT_FILTERS_STATE.keepInfoVisible); | ||||||
| 
 | 
 | ||||||
|   const [sortingMethod, setSortingMethod] = useState<number>( |   const [sortingMethod, setSortingMethod] = useState<number>( | ||||||
| @ -268,68 +268,52 @@ const Library = ({ | |||||||
|           onChange={setSearchName} |           onChange={setSearchName} | ||||||
|         /> |         /> | ||||||
| 
 | 
 | ||||||
|         <WithLabel |         <WithLabel label={langui.group_by}> | ||||||
|           label={langui.group_by} |           <Select | ||||||
|           input={ |             className="w-full" | ||||||
|             <Select |             options={[ | ||||||
|               className="w-full" |               langui.category ?? "Category", | ||||||
|               options={[ |               langui.type ?? "Type", | ||||||
|                 langui.category ?? "Category", |               langui.release_year ?? "Year", | ||||||
|                 langui.type ?? "Type", |             ]} | ||||||
|                 langui.release_year ?? "Year", |             value={groupingMethod} | ||||||
|               ]} |             onChange={setGroupingMethod} | ||||||
|               value={groupingMethod} |             allowEmpty | ||||||
|               onChange={setGroupingMethod} |           /> | ||||||
|               allowEmpty |         </WithLabel> | ||||||
|             /> |  | ||||||
|           } |  | ||||||
|         /> |  | ||||||
| 
 | 
 | ||||||
|         <WithLabel |         <WithLabel label={langui.order_by}> | ||||||
|           label={langui.order_by} |           <Select | ||||||
|           input={ |             className="w-full" | ||||||
|             <Select |             options={[ | ||||||
|               className="w-full" |               langui.name ?? "Name", | ||||||
|               options={[ |               langui.price ?? "Price", | ||||||
|                 langui.name ?? "Name", |               langui.release_date ?? "Release date", | ||||||
|                 langui.price ?? "Price", |             ]} | ||||||
|                 langui.release_date ?? "Release date", |             value={sortingMethod} | ||||||
|               ]} |             onChange={setSortingMethod} | ||||||
|               value={sortingMethod} |           /> | ||||||
|               onChange={setSortingMethod} |         </WithLabel> | ||||||
|             /> |  | ||||||
|           } |  | ||||||
|         /> |  | ||||||
| 
 | 
 | ||||||
|         <WithLabel |         <WithLabel label={langui.show_subitems}> | ||||||
|           label={langui.show_subitems} |           <Switch value={showSubitems} onClick={toggleShowSubitems} /> | ||||||
|           input={<Switch value={showSubitems} onClick={toggleShowSubitems} />} |         </WithLabel> | ||||||
|         /> |  | ||||||
| 
 | 
 | ||||||
|         <WithLabel |         <WithLabel label={langui.show_primary_items}> | ||||||
|           label={langui.show_primary_items} |           <Switch value={showPrimaryItems} onClick={toggleShowPrimaryItems} /> | ||||||
|           input={ |         </WithLabel> | ||||||
|             <Switch value={showPrimaryItems} onClick={toggleShowPrimaryItems} /> |  | ||||||
|           } |  | ||||||
|         /> |  | ||||||
| 
 | 
 | ||||||
|         <WithLabel |         <WithLabel label={langui.show_secondary_items}> | ||||||
|           label={langui.show_secondary_items} |           <Switch | ||||||
|           input={ |             value={showSecondaryItems} | ||||||
|             <Switch |             onClick={toggleShowSecondaryItems} | ||||||
|               value={showSecondaryItems} |           /> | ||||||
|               onClick={toggleShowSecondaryItems} |         </WithLabel> | ||||||
|             /> |  | ||||||
|           } |  | ||||||
|         /> |  | ||||||
| 
 | 
 | ||||||
|         {hoverable && ( |         {hoverable && ( | ||||||
|           <WithLabel |           <WithLabel label={langui.always_show_info}> | ||||||
|             label={langui.always_show_info} |             <Switch value={keepInfoVisible} onClick={toggleKeepInfoVisible} /> | ||||||
|             input={ |           </WithLabel> | ||||||
|               <Switch value={keepInfoVisible} onClick={toggleKeepInfoVisible} /> |  | ||||||
|             } |  | ||||||
|           /> |  | ||||||
|         )} |         )} | ||||||
| 
 | 
 | ||||||
|         <ButtonGroup |         <ButtonGroup | ||||||
|  | |||||||
| @ -1,5 +1,6 @@ | |||||||
| import { GetStaticProps } from "next"; | import { GetStaticProps } from "next"; | ||||||
| import { useMemo, useState } from "react"; | import { useMemo, useState } from "react"; | ||||||
|  | import { useBoolean } from "usehooks-ts"; | ||||||
| import { AppLayout, AppLayoutRequired } from "components/AppLayout"; | import { AppLayout, AppLayoutRequired } from "components/AppLayout"; | ||||||
| import { Switch } from "components/Inputs/Switch"; | import { Switch } from "components/Inputs/Switch"; | ||||||
| import { PanelHeader } from "components/PanelComponents/PanelHeader"; | import { PanelHeader } from "components/PanelComponents/PanelHeader"; | ||||||
| @ -19,10 +20,9 @@ import { Button } from "components/Inputs/Button"; | |||||||
| import { useMediaHoverable } from "hooks/useMediaQuery"; | import { useMediaHoverable } from "hooks/useMediaQuery"; | ||||||
| import { filterHasAttributes } from "helpers/others"; | import { filterHasAttributes } from "helpers/others"; | ||||||
| import { SmartList } from "components/SmartList"; | import { SmartList } from "components/SmartList"; | ||||||
| import { useBoolean } from "hooks/useBoolean"; |  | ||||||
| import { TranslatedPreviewCard } from "components/Translated"; |  | ||||||
| import { getOpenGraph } from "helpers/openGraph"; | import { getOpenGraph } from "helpers/openGraph"; | ||||||
| import { compareDate } from "helpers/date"; | import { compareDate } from "helpers/date"; | ||||||
|  | import { TranslatedPreviewCard } from "components/PreviewCard"; | ||||||
| 
 | 
 | ||||||
| /* | /* | ||||||
|  *                                         ╭─────────────╮ |  *                                         ╭─────────────╮ | ||||||
| @ -49,9 +49,9 @@ const News = ({ langui, posts, ...otherProps }: Props): JSX.Element => { | |||||||
|     DEFAULT_FILTERS_STATE.searchName |     DEFAULT_FILTERS_STATE.searchName | ||||||
|   ); |   ); | ||||||
|   const { |   const { | ||||||
|     state: keepInfoVisible, |     value: keepInfoVisible, | ||||||
|     toggleState: toggleKeepInfoVisible, |     toggle: toggleKeepInfoVisible, | ||||||
|     setState: setKeepInfoVisible, |     setValue: setKeepInfoVisible, | ||||||
|   } = useBoolean(DEFAULT_FILTERS_STATE.keepInfoVisible); |   } = useBoolean(DEFAULT_FILTERS_STATE.keepInfoVisible); | ||||||
| 
 | 
 | ||||||
|   const subPanel = useMemo( |   const subPanel = useMemo( | ||||||
| @ -71,12 +71,9 @@ const News = ({ langui, posts, ...otherProps }: Props): JSX.Element => { | |||||||
|         /> |         /> | ||||||
| 
 | 
 | ||||||
|         {hoverable && ( |         {hoverable && ( | ||||||
|           <WithLabel |           <WithLabel label={langui.always_show_info}> | ||||||
|             label={langui.always_show_info} |             <Switch onClick={toggleKeepInfoVisible} value={keepInfoVisible} /> | ||||||
|             input={ |           </WithLabel> | ||||||
|               <Switch onClick={toggleKeepInfoVisible} value={keepInfoVisible} /> |  | ||||||
|             } |  | ||||||
|           /> |  | ||||||
|         )} |         )} | ||||||
| 
 | 
 | ||||||
|         <Button |         <Button | ||||||
|  | |||||||
| @ -24,13 +24,14 @@ import { | |||||||
|   isDefinedAndNotEmpty, |   isDefinedAndNotEmpty, | ||||||
| } from "helpers/others"; | } from "helpers/others"; | ||||||
| import { getOpenGraph } from "helpers/openGraph"; | import { getOpenGraph } from "helpers/openGraph"; | ||||||
| import { TranslatedNavOption, TranslatedProps } from "components/Translated"; |  | ||||||
| import { useSmartLanguage } from "hooks/useSmartLanguage"; | import { useSmartLanguage } from "hooks/useSmartLanguage"; | ||||||
| import { ToolTip } from "components/ToolTip"; | import { ToolTip } from "components/ToolTip"; | ||||||
| import { Chip } from "components/Chip"; | import { Chip } from "components/Chip"; | ||||||
| import { Ico, Icon } from "components/Ico"; | import { Ico, Icon } from "components/Ico"; | ||||||
| import { AnchorShare } from "components/AnchorShare"; | import { AnchorShare } from "components/AnchorShare"; | ||||||
| import { datePickerToDate } from "helpers/date"; | import { datePickerToDate } from "helpers/date"; | ||||||
|  | import { TranslatedProps } from "helpers/types/TranslatedProps"; | ||||||
|  | import { TranslatedNavOption } from "components/PanelComponents/NavOption"; | ||||||
| 
 | 
 | ||||||
| /* | /* | ||||||
|  *                                           ╭────────╮ |  *                                           ╭────────╮ | ||||||
|  | |||||||
| @ -1,5 +1,6 @@ | |||||||
| import { GetStaticProps } from "next"; | import { GetStaticProps } from "next"; | ||||||
| import { useCallback, useMemo, useState } from "react"; | import { useCallback, useMemo, useState } from "react"; | ||||||
|  | import { useBoolean } from "usehooks-ts"; | ||||||
| import { AppLayout, AppLayoutRequired } from "components/AppLayout"; | import { AppLayout, AppLayoutRequired } from "components/AppLayout"; | ||||||
| import { NavOption } from "components/PanelComponents/NavOption"; | import { NavOption } from "components/PanelComponents/NavOption"; | ||||||
| import { PanelHeader } from "components/PanelComponents/PanelHeader"; | import { PanelHeader } from "components/PanelComponents/PanelHeader"; | ||||||
| @ -23,9 +24,8 @@ import { SmartList } from "components/SmartList"; | |||||||
| import { Select } from "components/Inputs/Select"; | import { Select } from "components/Inputs/Select"; | ||||||
| import { SelectiveNonNullable } from "helpers/types/SelectiveNonNullable"; | import { SelectiveNonNullable } from "helpers/types/SelectiveNonNullable"; | ||||||
| import { prettySlug } from "helpers/formatters"; | import { prettySlug } from "helpers/formatters"; | ||||||
| import { useBoolean } from "hooks/useBoolean"; |  | ||||||
| import { TranslatedPreviewCard } from "components/Translated"; |  | ||||||
| import { getOpenGraph } from "helpers/openGraph"; | import { getOpenGraph } from "helpers/openGraph"; | ||||||
|  | import { TranslatedPreviewCard } from "components/PreviewCard"; | ||||||
| 
 | 
 | ||||||
| /* | /* | ||||||
|  *                                         ╭─────────────╮ |  *                                         ╭─────────────╮ | ||||||
| @ -59,9 +59,9 @@ const Wiki = ({ langui, pages, ...otherProps }: Props): JSX.Element => { | |||||||
|   ); |   ); | ||||||
| 
 | 
 | ||||||
|   const { |   const { | ||||||
|     state: keepInfoVisible, |     value: keepInfoVisible, | ||||||
|     toggleState: toggleKeepInfoVisible, |     toggle: toggleKeepInfoVisible, | ||||||
|     setState: setKeepInfoVisible, |     setValue: setKeepInfoVisible, | ||||||
|   } = useBoolean(DEFAULT_FILTERS_STATE.keepInfoVisible); |   } = useBoolean(DEFAULT_FILTERS_STATE.keepInfoVisible); | ||||||
| 
 | 
 | ||||||
|   const subPanel = useMemo( |   const subPanel = useMemo( | ||||||
| @ -80,26 +80,20 @@ const Wiki = ({ langui, pages, ...otherProps }: Props): JSX.Element => { | |||||||
|           onChange={setSearchName} |           onChange={setSearchName} | ||||||
|         /> |         /> | ||||||
| 
 | 
 | ||||||
|         <WithLabel |         <WithLabel label={langui.group_by}> | ||||||
|           label={langui.group_by} |           <Select | ||||||
|           input={ |             className="w-full" | ||||||
|             <Select |             options={[langui.category ?? "Category"]} | ||||||
|               className="w-full" |             value={groupingMethod} | ||||||
|               options={[langui.category ?? "Category"]} |             onChange={setGroupingMethod} | ||||||
|               value={groupingMethod} |             allowEmpty | ||||||
|               onChange={setGroupingMethod} |           /> | ||||||
|               allowEmpty |         </WithLabel> | ||||||
|             /> |  | ||||||
|           } |  | ||||||
|         /> |  | ||||||
| 
 | 
 | ||||||
|         {hoverable && ( |         {hoverable && ( | ||||||
|           <WithLabel |           <WithLabel label={langui.always_show_info}> | ||||||
|             label={langui.always_show_info} |             <Switch value={keepInfoVisible} onClick={toggleKeepInfoVisible} /> | ||||||
|             input={ |           </WithLabel> | ||||||
|               <Switch value={keepInfoVisible} onClick={toggleKeepInfoVisible} /> |  | ||||||
|             } |  | ||||||
|           /> |  | ||||||
|         )} |         )} | ||||||
| 
 | 
 | ||||||
|         <Button |         <Button | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 DrMint
						DrMint