Continued using hooks

This commit is contained in:
DrMint 2022-06-23 00:39:59 +02:00
parent efcf01e8a0
commit d0b91f9db6
29 changed files with 2640 additions and 2356 deletions

View File

@ -161,30 +161,30 @@ export function AppLayout(props: Props): JSX.Element {
}, [fontSize]); }, [fontSize]);
const defaultPreferredLanguages = useMemo(() => { const defaultPreferredLanguages = useMemo(() => {
let list: string[] = []; let memo: string[] = [];
if (isDefinedAndNotEmpty(router.locale) && router.locales) { if (isDefinedAndNotEmpty(router.locale) && router.locales) {
if (router.locale === "en") { if (router.locale === "en") {
list = [router.locale]; memo = [router.locale];
router.locales.map((locale) => { router.locales.map((locale) => {
if (locale !== router.locale) list.push(locale); if (locale !== router.locale) memo.push(locale);
}); });
} else { } else {
list = [router.locale, "en"]; memo = [router.locale, "en"];
router.locales.map((locale) => { router.locales.map((locale) => {
if (locale !== router.locale && locale !== "en") list.push(locale); if (locale !== router.locale && locale !== "en") memo.push(locale);
}); });
} }
} }
return list; return memo;
}, [router.locale, router.locales]); }, [router.locale, router.locales]);
const currencyOptions = useMemo(() => { const currencyOptions = useMemo(() => {
const list: string[] = []; const memo: string[] = [];
filterHasAttributes(currencies).map((currentCurrency) => { filterHasAttributes(currencies).map((currentCurrency) => {
if (isDefinedAndNotEmpty(currentCurrency.attributes.code)) if (isDefinedAndNotEmpty(currentCurrency.attributes.code))
list.push(currentCurrency.attributes.code); memo.push(currentCurrency.attributes.code);
}); });
return list; return memo;
}, [currencies]); }, [currencies]);
const [currencySelect, setCurrencySelect] = useState<number>(-1); const [currencySelect, setCurrencySelect] = useState<number>(-1);

View File

@ -157,12 +157,10 @@ export function ScanSet(props: Props): JSX.Element {
{filterHasAttributes(selectedScan.cleaners.data).map( {filterHasAttributes(selectedScan.cleaners.data).map(
(cleaner) => ( (cleaner) => (
<Fragment key={cleaner.id}> <Fragment key={cleaner.id}>
{cleaner.attributes && (
<RecorderChip <RecorderChip
langui={langui} langui={langui}
recorder={cleaner.attributes} recorder={cleaner.attributes}
/> />
)}
</Fragment> </Fragment>
) )
)} )}
@ -178,12 +176,10 @@ export function ScanSet(props: Props): JSX.Element {
{filterHasAttributes(selectedScan.typesetters.data).map( {filterHasAttributes(selectedScan.typesetters.data).map(
(typesetter) => ( (typesetter) => (
<Fragment key={typesetter.id}> <Fragment key={typesetter.id}>
{typesetter.attributes && (
<RecorderChip <RecorderChip
langui={langui} langui={langui}
recorder={typesetter.attributes} recorder={typesetter.attributes}
/> />
)}
</Fragment> </Fragment>
) )
)} )}
@ -218,9 +214,7 @@ export function ScanSet(props: Props): JSX.Element {
openLightBox(images, index); openLightBox(images, index);
}} }}
> >
{page.attributes && (
<Img image={page.attributes} quality={ImageQuality.Small} /> <Img image={page.attributes} quality={ImageQuality.Small} />
)}
</div> </div>
))} ))}
</div> </div>

View File

@ -11,7 +11,7 @@ import { getAssetURL, ImageQuality } from "helpers/img";
import { filterHasAttributes, getStatusDescription } from "helpers/others"; import { filterHasAttributes, getStatusDescription } from "helpers/others";
import { useSmartLanguage } from "hooks/useSmartLanguage"; import { useSmartLanguage } from "hooks/useSmartLanguage";
import { Fragment } from "react"; import { Fragment, useMemo } from "react";
interface Props { interface Props {
openLightBox: (images: string[], index?: number) => void; openLightBox: (images: string[], index?: number) => void;
@ -35,19 +35,22 @@ export function ScanSetCover(props: Props): JSX.Element {
languageExtractor: (item) => item.language?.data?.attributes?.code, languageExtractor: (item) => item.language?.data?.attributes?.code,
}); });
const coverImages: UploadImageFragment[] = []; const coverImages = useMemo(() => {
const memo: UploadImageFragment[] = [];
if (selectedScan?.obi_belt?.full?.data?.attributes) if (selectedScan?.obi_belt?.full?.data?.attributes)
coverImages.push(selectedScan.obi_belt.full.data.attributes); memo.push(selectedScan.obi_belt.full.data.attributes);
if (selectedScan?.obi_belt?.inside_full?.data?.attributes) if (selectedScan?.obi_belt?.inside_full?.data?.attributes)
coverImages.push(selectedScan.obi_belt.inside_full.data.attributes); memo.push(selectedScan.obi_belt.inside_full.data.attributes);
if (selectedScan?.dust_jacket?.full?.data?.attributes) if (selectedScan?.dust_jacket?.full?.data?.attributes)
coverImages.push(selectedScan.dust_jacket.full.data.attributes); memo.push(selectedScan.dust_jacket.full.data.attributes);
if (selectedScan?.dust_jacket?.inside_full?.data?.attributes) if (selectedScan?.dust_jacket?.inside_full?.data?.attributes)
coverImages.push(selectedScan.dust_jacket.inside_full.data.attributes); memo.push(selectedScan.dust_jacket.inside_full.data.attributes);
if (selectedScan?.cover?.full?.data?.attributes) if (selectedScan?.cover?.full?.data?.attributes)
coverImages.push(selectedScan.cover.full.data.attributes); memo.push(selectedScan.cover.full.data.attributes);
if (selectedScan?.cover?.inside_full?.data?.attributes) if (selectedScan?.cover?.inside_full?.data?.attributes)
coverImages.push(selectedScan.cover.inside_full.data.attributes); memo.push(selectedScan.cover.inside_full.data.attributes);
return memo;
}, [selectedScan]);
if (coverImages.length > 0) { if (coverImages.length > 0) {
return ( return (

View File

@ -67,7 +67,8 @@ export function PostPage(props: Props): JSX.Element {
[post.slug, post.thumbnail, selectedTranslation] [post.slug, post.thumbnail, selectedTranslation]
); );
const subPanel = const subPanel = useMemo(
() =>
returnHref || returnTitle || displayCredits || displayToc ? ( returnHref || returnTitle || displayCredits || displayToc ? (
<SubPanel> <SubPanel>
{returnHref && returnTitle && ( {returnHref && returnTitle && (
@ -120,9 +121,22 @@ export function PostPage(props: Props): JSX.Element {
{displayToc && <TOC text={body} title={title} />} {displayToc && <TOC text={body} title={title} />}
</SubPanel> </SubPanel>
) : undefined; ) : undefined,
[
body,
displayCredits,
displayToc,
langui,
post.authors,
returnHref,
returnTitle,
selectedTranslation,
title,
]
);
const contentPanel = ( const contentPanel = useMemo(
() => (
<ContentPanel> <ContentPanel>
{returnHref && returnTitle && ( {returnHref && returnTitle && (
<ReturnButton <ReturnButton
@ -166,6 +180,23 @@ export function PostPage(props: Props): JSX.Element {
<Markdawn text={body} /> <Markdawn text={body} />
{appendBody} {appendBody}
</ContentPanel> </ContentPanel>
),
[
LanguageSwitcher,
appendBody,
body,
displayLanguageSwitcher,
displayThumbnailHeader,
displayTitle,
excerpt,
langui,
post.categories,
prependBody,
returnHref,
returnTitle,
thumbnail,
title,
]
); );
return ( return (

View File

@ -85,7 +85,6 @@ export function ChronologyItemComponent(props: Props): JSX.Element {
{translation.description && ( {translation.description && (
<p <p
className={ className={
event.translations &&
event.translations.length > 1 event.translations.length > 1
? `mt-2 whitespace-pre-line before:ml-[-1em] before:inline-block ? `mt-2 whitespace-pre-line before:ml-[-1em] before:inline-block
before:w-4 before:text-dark before:content-['-']` before:w-4 before:text-dark before:content-['-']`

View File

@ -208,7 +208,7 @@ export function sortBy(
orderByType: number, orderByType: number,
items: Items, items: Items,
currencies: AppStaticProps["currencies"] currencies: AppStaticProps["currencies"]
): Items { ) {
switch (orderByType) { switch (orderByType) {
case 0: case 0:
return items.sort((a, b) => { return items.sort((a, b) => {

View File

@ -19,9 +19,7 @@ type SortContentProps =
>["contents"]; >["contents"];
export function sortContent(contents: SortContentProps) { export function sortContent(contents: SortContentProps) {
if (contents) { contents?.data.sort((a, b) => {
const newContent = { ...contents };
newContent?.data.sort((a, b) => {
if ( if (
a.attributes?.range[0]?.__typename === "ComponentRangePageRange" && a.attributes?.range[0]?.__typename === "ComponentRangePageRange" &&
b.attributes?.range[0]?.__typename === "ComponentRangePageRange" b.attributes?.range[0]?.__typename === "ComponentRangePageRange"
@ -33,9 +31,6 @@ export function sortContent(contents: SortContentProps) {
} }
return 0; return 0;
}); });
return newContent;
}
return contents;
} }
export function getStatusDescription( export function getStatusDescription(

View File

@ -38,12 +38,12 @@ export function useSmartLanguage<T>(
const router = useRouter(); const router = useRouter();
const availableLocales = useMemo(() => { const availableLocales = useMemo(() => {
const map = new Map<string, number>(); const memo = new Map<string, number>();
filterDefined(items).map((elem, index) => { filterDefined(items).map((elem, index) => {
const result = languageExtractor(elem); const result = languageExtractor(elem);
if (isDefined(result)) map.set(result, index); if (isDefined(result)) memo.set(result, index);
}); });
return map; return memo;
}, [items, languageExtractor]); }, [items, languageExtractor]);
const [selectedTranslationIndex, setSelectedTranslationIndex] = useState< const [selectedTranslationIndex, setSelectedTranslationIndex] = useState<

View File

@ -7,12 +7,14 @@ import { ContentPanel } from "components/Panels/ContentPanel";
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps"; import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
import { GetStaticPropsContext } from "next"; import { GetStaticPropsContext } from "next";
import { useMemo } from "react";
interface Props extends AppStaticProps {} interface Props extends AppStaticProps {}
export default function FourOhFour(props: Props): JSX.Element { export default function FourOhFour(props: Props): JSX.Element {
const { langui } = props; const { langui } = props;
const contentPanel = ( const contentPanel = useMemo(
() => (
<ContentPanel> <ContentPanel>
<h1>404 - {langui.page_not_found}</h1> <h1>404 - {langui.page_not_found}</h1>
<ReturnButton <ReturnButton
@ -22,6 +24,8 @@ export default function FourOhFour(props: Props): JSX.Element {
displayOn={ReturnButtonType.Both} displayOn={ReturnButtonType.Both}
/> />
</ContentPanel> </ContentPanel>
),
[langui]
); );
return <AppLayout navTitle="404" contentPanel={contentPanel} {...props} />; return <AppLayout navTitle="404" contentPanel={contentPanel} {...props} />;
} }

View File

@ -7,12 +7,14 @@ import { ContentPanel } from "components/Panels/ContentPanel";
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps"; import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
import { GetStaticPropsContext } from "next"; import { GetStaticPropsContext } from "next";
import { useMemo } from "react";
interface Props extends AppStaticProps {} interface Props extends AppStaticProps {}
export default function FiveHundred(props: Props): JSX.Element { export default function FiveHundred(props: Props): JSX.Element {
const { langui } = props; const { langui } = props;
const contentPanel = ( const contentPanel = useMemo(
() => (
<ContentPanel> <ContentPanel>
<h1>500 - Internal Server Error</h1> <h1>500 - Internal Server Error</h1>
<ReturnButton <ReturnButton
@ -22,6 +24,8 @@ export default function FiveHundred(props: Props): JSX.Element {
displayOn={ReturnButtonType.Both} displayOn={ReturnButtonType.Both}
/> />
</ContentPanel> </ContentPanel>
),
[langui]
); );
return <AppLayout navTitle="500" contentPanel={contentPanel} {...props} />; return <AppLayout navTitle="500" contentPanel={contentPanel} {...props} />;
} }

View File

@ -6,12 +6,14 @@ import { SubPanel } from "components/Panels/SubPanel";
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps"; import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
import { GetStaticPropsContext } from "next"; import { GetStaticPropsContext } from "next";
import { useMemo } from "react";
interface Props extends AppStaticProps {} interface Props extends AppStaticProps {}
export default function AboutUs(props: Props): JSX.Element { export default function AboutUs(props: Props): JSX.Element {
const { langui } = props; const { langui } = props;
const subPanel = ( const subPanel = useMemo(
() => (
<SubPanel> <SubPanel>
<PanelHeader <PanelHeader
icon={Icon.Info} icon={Icon.Info}
@ -24,7 +26,6 @@ export default function AboutUs(props: Props): JSX.Element {
border border
/> />
<NavOption title={langui.legality} url="/about-us/legality" border /> <NavOption title={langui.legality} url="/about-us/legality" border />
{/* <NavOption title={langui.members} url="/about-us/members" border /> */}
<NavOption <NavOption
title={langui.sharing_policy} title={langui.sharing_policy}
url="/about-us/sharing-policy" url="/about-us/sharing-policy"
@ -32,6 +33,8 @@ export default function AboutUs(props: Props): JSX.Element {
/> />
<NavOption title={langui.contact_us} url="/about-us/contact" border /> <NavOption title={langui.contact_us} url="/about-us/contact" border />
</SubPanel> </SubPanel>
),
[langui]
); );
return ( return (
<AppLayout navTitle={langui.about_us} subPanel={subPanel} {...props} /> <AppLayout navTitle={langui.about_us} subPanel={subPanel} {...props} />

View File

@ -6,12 +6,14 @@ import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
import { GetStaticPropsContext } from "next"; import { GetStaticPropsContext } from "next";
import { Icon } from "components/Ico"; import { Icon } from "components/Ico";
import { useMemo } from "react";
interface Props extends AppStaticProps {} interface Props extends AppStaticProps {}
export default function Archives(props: Props): JSX.Element { export default function Archives(props: Props): JSX.Element {
const { langui } = props; const { langui } = props;
const subPanel = ( const subPanel = useMemo(
() => (
<SubPanel> <SubPanel>
<PanelHeader <PanelHeader
icon={Icon.Inventory} icon={Icon.Inventory}
@ -20,6 +22,8 @@ export default function Archives(props: Props): JSX.Element {
/> />
<NavOption title={"Videos"} url="/archives/videos/" border /> <NavOption title={"Videos"} url="/archives/videos/" border />
</SubPanel> </SubPanel>
),
[langui]
); );
return ( return (
<AppLayout navTitle={langui.archives} subPanel={subPanel} {...props} /> <AppLayout navTitle={langui.archives} subPanel={subPanel} {...props} />

View File

@ -20,7 +20,7 @@ import {
GetStaticPathsResult, GetStaticPathsResult,
GetStaticPropsContext, GetStaticPropsContext,
} from "next"; } from "next";
import { Fragment, useState } from "react"; import { Fragment, useState, useMemo } from "react";
import { Icon } from "components/Ico"; 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";
@ -37,7 +37,8 @@ export default function Channel(props: Props): JSX.Element {
const [keepInfoVisible, setKeepInfoVisible] = useState(true); const [keepInfoVisible, setKeepInfoVisible] = useState(true);
const hoverable = useMediaHoverable(); const hoverable = useMediaHoverable();
const subPanel = ( const subPanel = useMemo(
() => (
<SubPanel> <SubPanel>
<ReturnButton <ReturnButton
href="/archives/videos/" href="/archives/videos/"
@ -62,9 +63,12 @@ export default function Channel(props: Props): JSX.Element {
/> />
)} )}
</SubPanel> </SubPanel>
),
[hoverable, keepInfoVisible, langui]
); );
const contentPanel = ( const contentPanel = useMemo(
() => (
<ContentPanel width={ContentPanelWidthSizes.Full}> <ContentPanel width={ContentPanelWidthSizes.Full}>
<div className="mb-8"> <div className="mb-8">
<h1 className="text-3xl">{channel?.title}</h1> <h1 className="text-3xl">{channel?.title}</h1>
@ -97,7 +101,15 @@ export default function Channel(props: Props): JSX.Element {
))} ))}
</div> </div>
</ContentPanel> </ContentPanel>
),
[
channel?.subscribers,
channel?.title,
channel?.videos?.data,
keepInfoVisible,
]
); );
return ( return (
<AppLayout <AppLayout
navTitle={langui.archives} navTitle={langui.archives}

View File

@ -22,40 +22,33 @@ 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 { GetStaticPropsContext } from "next"; import { GetStaticPropsContext } from "next";
import { Fragment, useState } from "react"; import { Fragment, useMemo, useState } from "react";
interface Props extends AppStaticProps { interface Props extends AppStaticProps {
videos: NonNullable<GetVideosPreviewQuery["videos"]>["data"]; videos: NonNullable<GetVideosPreviewQuery["videos"]>["data"];
} }
const ITEM_PER_PAGE = 50;
export default function Videos(props: Props): JSX.Element { export default function Videos(props: Props): JSX.Element {
const { langui, videos } = props; const { langui, videos } = props;
const hoverable = useMediaHoverable(); const hoverable = useMediaHoverable();
videos const paginatedVideos = useMemo(() => {
.sort((a, b) => { const memo = [];
const dateA = a.attributes?.published_date for (let index = 0; ITEM_PER_PAGE * index < videos.length; index += 1) {
? prettyDate(a.attributes.published_date) memo.push(
: "9999"; videos.slice(index * ITEM_PER_PAGE, (index + 1) * ITEM_PER_PAGE)
const dateB = b.attributes?.published_date
? prettyDate(b.attributes.published_date)
: "9999";
return dateA.localeCompare(dateB);
})
.reverse();
const itemPerPage = 50;
const paginatedVideos: Props["videos"][] = [];
for (let index = 0; itemPerPage * index < videos.length; index += 1) {
paginatedVideos.push(
videos.slice(index * itemPerPage, (index + 1) * itemPerPage)
); );
} }
return memo;
}, [videos]);
const [page, setPage] = useState(0); const [page, setPage] = useState(0);
const [keepInfoVisible, setKeepInfoVisible] = useState(true); const [keepInfoVisible, setKeepInfoVisible] = useState(true);
const subPanel = ( const subPanel = useMemo(
() => (
<SubPanel> <SubPanel>
<ReturnButton <ReturnButton
href="/archives/" href="/archives/"
@ -80,12 +73,15 @@ export default function Videos(props: Props): JSX.Element {
/> />
)} )}
</SubPanel> </SubPanel>
),
[hoverable, keepInfoVisible, langui]
); );
const contentPanel = ( const contentPanel = useMemo(
() => (
<ContentPanel width={ContentPanelWidthSizes.Full}> <ContentPanel width={ContentPanelWidthSizes.Full}>
<PageSelector <PageSelector
maxPage={Math.floor(videos.length / itemPerPage)} maxPage={Math.floor(videos.length / ITEM_PER_PAGE)}
page={page} page={page}
setPage={setPage} setPage={setPage}
className="mb-12" className="mb-12"
@ -120,12 +116,14 @@ export default function Videos(props: Props): JSX.Element {
</div> </div>
<PageSelector <PageSelector
maxPage={Math.floor(videos.length / itemPerPage)} maxPage={Math.floor(videos.length / ITEM_PER_PAGE)}
page={page} page={page}
setPage={setPage} setPage={setPage}
className="mt-12" className="mt-12"
/> />
</ContentPanel> </ContentPanel>
),
[keepInfoVisible, page, paginatedVideos, videos.length]
); );
return ( return (
<AppLayout <AppLayout
@ -143,6 +141,17 @@ export async function getStaticProps(
const sdk = getReadySdk(); const sdk = getReadySdk();
const videos = await sdk.getVideosPreview(); const videos = await sdk.getVideosPreview();
if (!videos.videos) return { notFound: true }; if (!videos.videos) return { notFound: true };
videos.videos.data
.sort((a, b) => {
const dateA = a.attributes?.published_date
? prettyDate(a.attributes.published_date)
: "9999";
const dateB = b.attributes?.published_date
? prettyDate(b.attributes.published_date)
: "9999";
return dateA.localeCompare(dateB);
})
.reverse();
const props: Props = { const props: Props = {
...(await getAppStaticProps(context)), ...(await getAppStaticProps(context)),
videos: videos.videos.data, videos: videos.videos.data,

View File

@ -26,6 +26,7 @@ import {
GetStaticPathsResult, GetStaticPathsResult,
GetStaticPropsContext, GetStaticPropsContext,
} from "next"; } from "next";
import { useMemo } from "react";
interface Props extends AppStaticProps { interface Props extends AppStaticProps {
video: NonNullable< video: NonNullable<
@ -37,7 +38,8 @@ export default function Video(props: Props): JSX.Element {
const { langui, video } = props; const { langui, video } = props;
const isMobile = useMediaMobile(); const isMobile = useMediaMobile();
const appLayout = useAppLayout(); const appLayout = useAppLayout();
const subPanel = ( const subPanel = useMemo(
() => (
<SubPanel> <SubPanel>
<ReturnButton <ReturnButton
href="/archives/videos/" href="/archives/videos/"
@ -70,9 +72,12 @@ export default function Video(props: Props): JSX.Element {
onClick={() => appLayout.setSubPanelOpen(false)} onClick={() => appLayout.setSubPanelOpen(false)}
/> />
</SubPanel> </SubPanel>
),
[appLayout, langui]
); );
const contentPanel = ( const contentPanel = useMemo(
() => (
<ContentPanel width={ContentPanelWidthSizes.Full}> <ContentPanel width={ContentPanelWidthSizes.Full}>
<ReturnButton <ReturnButton
href="/library/" href="/library/"
@ -175,7 +180,22 @@ export default function Video(props: Props): JSX.Element {
</InsetBox> </InsetBox>
</div> </div>
</ContentPanel> </ContentPanel>
),
[
isMobile,
langui,
video.channel?.data?.attributes,
video.description,
video.gone,
video.likes,
video.published_date,
video.source,
video.title,
video.uid,
video.views,
]
); );
return ( return (
<AppLayout <AppLayout
navTitle={langui.archives} navTitle={langui.archives}

View File

@ -5,12 +5,14 @@ import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
import { GetStaticPropsContext } from "next"; import { GetStaticPropsContext } from "next";
import { Icon } from "components/Ico"; import { Icon } from "components/Ico";
import { useMemo } from "react";
interface Props extends AppStaticProps {} interface Props extends AppStaticProps {}
export default function Chronicles(props: Props): JSX.Element { export default function Chronicles(props: Props): JSX.Element {
const { langui } = props; const { langui } = props;
const subPanel = ( const subPanel = useMemo(
() => (
<SubPanel> <SubPanel>
<PanelHeader <PanelHeader
icon={Icon.WatchLater} icon={Icon.WatchLater}
@ -18,7 +20,10 @@ export default function Chronicles(props: Props): JSX.Element {
description={langui.chronicles_description} description={langui.chronicles_description}
/> />
</SubPanel> </SubPanel>
),
[langui]
); );
return ( return (
<AppLayout navTitle={langui.chronicles} subPanel={subPanel} {...props} /> <AppLayout navTitle={langui.chronicles} subPanel={subPanel} {...props} />
); );

View File

@ -76,7 +76,8 @@ export default function Content(props: Props): JSX.Element {
[content.group, content.slug] [content.group, content.slug]
); );
const subPanel = ( const subPanel = useMemo(
() => (
<SubPanel> <SubPanel>
<ReturnButton <ReturnButton
href={`/contents`} href={`/contents`}
@ -96,7 +97,8 @@ export default function Content(props: Props): JSX.Element {
: langui.translation_notice} : langui.translation_notice}
</h2> </h2>
{selectedTranslation.text_set.source_language.data.attributes.code !== {selectedTranslation.text_set.source_language.data.attributes
.code !==
selectedTranslation.language?.data?.attributes?.code && ( selectedTranslation.language?.data?.attributes?.code && (
<div className="grid place-items-center gap-2"> <div className="grid place-items-center gap-2">
<p className="font-headers">{langui.source_language}:</p> <p className="font-headers">{langui.source_language}:</p>
@ -272,8 +274,18 @@ export default function Content(props: Props): JSX.Element {
</> </>
)} )}
</SubPanel> </SubPanel>
),
[
content.ranged_contents?.data,
currencies,
languages,
langui,
selectedTranslation,
]
); );
const contentPanel = (
const contentPanel = useMemo(
() => (
<ContentPanel> <ContentPanel>
<ReturnButton <ReturnButton
href={`/contents`} href={`/contents`}
@ -313,7 +325,9 @@ export default function Content(props: Props): JSX.Element {
)} )}
slug={previousContent.attributes.slug} slug={previousContent.attributes.slug}
languages={languages} languages={languages}
thumbnail={previousContent.attributes.thumbnail?.data?.attributes} thumbnail={
previousContent.attributes.thumbnail?.data?.attributes
}
thumbnailAspectRatio="3/2" thumbnailAspectRatio="3/2"
topChips={ topChips={
isMobile isMobile
@ -325,7 +339,8 @@ export default function Content(props: Props): JSX.Element {
? previousContent.attributes.type.data.attributes ? previousContent.attributes.type.data.attributes
.titles[0]?.title .titles[0]?.title
: prettySlug( : prettySlug(
previousContent.attributes.type.data.attributes.slug previousContent.attributes.type.data.attributes
.slug
), ),
] ]
: undefined : undefined
@ -371,8 +386,8 @@ export default function Content(props: Props): JSX.Element {
: nextContent.attributes.type?.data?.attributes : nextContent.attributes.type?.data?.attributes
? [ ? [
nextContent.attributes.type.data.attributes.titles?.[0] nextContent.attributes.type.data.attributes.titles?.[0]
? nextContent.attributes.type.data.attributes.titles[0] ? nextContent.attributes.type.data.attributes
?.title .titles[0]?.title
: prettySlug( : prettySlug(
nextContent.attributes.type.data.attributes.slug nextContent.attributes.type.data.attributes.slug
), ),
@ -391,6 +406,19 @@ export default function Content(props: Props): JSX.Element {
)} )}
</div> </div>
</ContentPanel> </ContentPanel>
),
[
LanguageSwitcher,
content.categories,
content.thumbnail?.data?.attributes,
content.type,
isMobile,
languages,
langui,
nextContent?.attributes,
previousContent?.attributes,
selectedTranslation,
]
); );
return ( return (

View File

@ -71,7 +71,8 @@ export default function Contents(props: Props): JSX.Element {
[combineRelatedContent, searchName.length] [combineRelatedContent, searchName.length]
); );
const subPanel = ( const subPanel = useMemo(
() => (
<SubPanel> <SubPanel>
<PanelHeader <PanelHeader
icon={Icon.Workspaces} icon={Icon.Workspaces}
@ -131,8 +132,19 @@ export default function Contents(props: Props): JSX.Element {
}} }}
/> />
</SubPanel> </SubPanel>
),
[
effectiveCombineRelatedContent,
groupingMethod,
hoverable,
keepInfoVisible,
langui,
searchName,
]
); );
const contentPanel = (
const contentPanel = useMemo(
() => (
<ContentPanel width={ContentPanelWidthSizes.Full}> <ContentPanel width={ContentPanelWidthSizes.Full}>
{/* TODO: Add to langui */} {/* TODO: Add to langui */}
{groups.size === 0 && ( {groups.size === 0 && (
@ -162,8 +174,8 @@ export default function Contents(props: Props): JSX.Element {
) { ) {
return ( return (
currentSum + currentSum +
(item.attributes.group.data.attributes.contents?.data (item.attributes.group.data.attributes.contents
.length ?? 1) ?.data.length ?? 1)
); );
} }
} }
@ -202,16 +214,16 @@ export default function Contents(props: Props): JSX.Element {
effectiveCombineRelatedContent && effectiveCombineRelatedContent &&
item.attributes.group?.data?.attributes?.combine === item.attributes.group?.data?.attributes?.combine ===
true true
? item.attributes.group.data.attributes.contents?.data ? item.attributes.group.data.attributes.contents
.length ?.data.length
: 0 : 0
} }
topChips={ topChips={
item.attributes.type?.data?.attributes item.attributes.type?.data?.attributes
? [ ? [
item.attributes.type.data.attributes.titles?.[0] item.attributes.type.data.attributes.titles?.[0]
? item.attributes.type.data.attributes.titles[0] ? item.attributes.type.data.attributes
?.title .titles[0]?.title
: prettySlug( : prettySlug(
item.attributes.type.data.attributes.slug item.attributes.type.data.attributes.slug
), ),
@ -230,7 +242,17 @@ export default function Contents(props: Props): JSX.Element {
) )
)} )}
</ContentPanel> </ContentPanel>
),
[
effectiveCombineRelatedContent,
groups,
keepInfoVisible,
languages,
langui.result,
langui.results,
]
); );
return ( return (
<AppLayout <AppLayout
navTitle={langui.contents} navTitle={langui.contents}

View File

@ -12,6 +12,7 @@ import { getReadySdk } from "graphql/sdk";
import { filterDefined, filterHasAttributes } from "helpers/others"; import { filterDefined, filterHasAttributes } from "helpers/others";
import { GetStaticPropsContext } from "next"; import { GetStaticPropsContext } from "next";
import { useMemo } from "react";
interface Props extends AppStaticProps { interface Props extends AppStaticProps {
contents: DevGetContentsQuery; contents: DevGetContentsQuery;
@ -21,7 +22,8 @@ export default function CheckupContents(props: Props): JSX.Element {
const { contents } = props; const { contents } = props;
const testReport = testingContent(contents); const testReport = testingContent(contents);
const contentPanel = ( const contentPanel = useMemo(
() => (
<ContentPanel width={ContentPanelWidthSizes.Full}> <ContentPanel width={ContentPanelWidthSizes.Full}>
{<h2 className="text-2xl">{testReport.title}</h2>} {<h2 className="text-2xl">{testReport.title}</h2>}
@ -75,7 +77,10 @@ export default function CheckupContents(props: Props): JSX.Element {
</div> </div>
))} ))}
</ContentPanel> </ContentPanel>
),
[testReport.lines, testReport.title]
); );
return ( return (
<AppLayout navTitle={"Checkup"} contentPanel={contentPanel} {...props} /> <AppLayout navTitle={"Checkup"} contentPanel={contentPanel} {...props} />
); );

View File

@ -14,6 +14,7 @@ import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
import { getReadySdk } from "graphql/sdk"; import { getReadySdk } from "graphql/sdk";
import { GetStaticPropsContext } from "next"; import { GetStaticPropsContext } from "next";
import { useMemo } from "react";
interface Props extends AppStaticProps { interface Props extends AppStaticProps {
libraryItems: DevGetLibraryItemsQuery; libraryItems: DevGetLibraryItemsQuery;
@ -23,7 +24,8 @@ export default function CheckupLibraryItems(props: Props): JSX.Element {
const { libraryItems } = props; const { libraryItems } = props;
const testReport = testingLibraryItem(libraryItems); const testReport = testingLibraryItem(libraryItems);
const contentPanel = ( const contentPanel = useMemo(
() => (
<ContentPanel width={ContentPanelWidthSizes.Full}> <ContentPanel width={ContentPanelWidthSizes.Full}>
{<h2 className="text-2xl">{testReport.title}</h2>} {<h2 className="text-2xl">{testReport.title}</h2>}
@ -77,7 +79,10 @@ export default function CheckupLibraryItems(props: Props): JSX.Element {
</div> </div>
))} ))}
</ContentPanel> </ContentPanel>
),
[testReport.lines, testReport.title]
); );
return ( return (
<AppLayout navTitle={"Checkup"} contentPanel={contentPanel} {...props} /> <AppLayout navTitle={"Checkup"} contentPanel={contentPanel} {...props} />
); );

View File

@ -10,7 +10,7 @@ import { ToolTip } from "components/ToolTip";
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps"; import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
import { GetStaticPropsContext } from "next"; import { GetStaticPropsContext } from "next";
import { useCallback, useState } from "react"; import { useCallback, useMemo, useState } from "react";
import TurndownService from "turndown"; import TurndownService from "turndown";
import { Icon } from "components/Ico"; import { Icon } from "components/Ico";
import { TOC } from "components/Markdown/TOC"; import { TOC } from "components/Markdown/TOC";
@ -25,11 +25,42 @@ export default function Editor(props: Props): JSX.Element {
const [markdown, setMarkdown] = useState(""); const [markdown, setMarkdown] = useState("");
const [converterOpened, setConverterOpened] = useState(false); const [converterOpened, setConverterOpened] = useState(false);
function wrap( const transformationWrapper = useCallback(
(
transformation: (
value: string,
selectionStart: number,
selectedEnd: number
) => { prependLength: number; transformedValue: string }
) => {
const textarea =
document.querySelector<HTMLTextAreaElement>("#editorTextArea");
if (textarea) {
const { value, selectionStart, selectionEnd } = textarea;
const { prependLength, transformedValue } = transformation(
value,
selectionStart,
selectionEnd
);
textarea.value = transformedValue;
handleInput(textarea.value);
textarea.focus();
textarea.selectionStart = selectionStart + prependLength;
textarea.selectionEnd = selectionEnd + prependLength;
}
},
[handleInput]
);
const wrap = useCallback(
(
wrapper: string, wrapper: string,
properties?: Record<string, string>, properties?: Record<string, string>,
addInnerNewLines?: boolean addInnerNewLines?: boolean
) { ) => {
transformationWrapper((value, selectionStart, selectionEnd) => { transformationWrapper((value, selectionStart, selectionEnd) => {
let prepend = wrapper; let prepend = wrapper;
let append = wrapper; let append = wrapper;
@ -55,13 +86,29 @@ export default function Editor(props: Props): JSX.Element {
newValue += value.slice(selectionEnd); newValue += value.slice(selectionEnd);
return { prependLength: prepend.length, transformedValue: newValue }; return { prependLength: prepend.length, transformedValue: newValue };
}); });
} },
[transformationWrapper]
);
function toggleWrap( const unwrap = useCallback(
(wrapper: string) => {
transformationWrapper((value, selectionStart, selectionEnd) => {
let newValue = "";
newValue += value.slice(0, selectionStart - wrapper.length);
newValue += value.slice(selectionStart, selectionEnd);
newValue += value.slice(wrapper.length + selectionEnd);
return { prependLength: -wrapper.length, transformedValue: newValue };
});
},
[transformationWrapper]
);
const toggleWrap = useCallback(
(
wrapper: string, wrapper: string,
properties?: Record<string, string>, properties?: Record<string, string>,
addInnerNewLines?: boolean addInnerNewLines?: boolean
) { ) => {
const textarea = const textarea =
document.querySelector<HTMLTextAreaElement>("#editorTextArea"); document.querySelector<HTMLTextAreaElement>("#editorTextArea");
if (textarea) { if (textarea) {
@ -77,21 +124,15 @@ export default function Editor(props: Props): JSX.Element {
wrap(wrapper, properties, addInnerNewLines); wrap(wrapper, properties, addInnerNewLines);
} }
} }
} },
[unwrap, wrap]
);
function unwrap(wrapper: string) { const preline = useCallback(
transformationWrapper((value, selectionStart, selectionEnd) => { (prepend: string) => {
let newValue = "";
newValue += value.slice(0, selectionStart - wrapper.length);
newValue += value.slice(selectionStart, selectionEnd);
newValue += value.slice(wrapper.length + selectionEnd);
return { prependLength: -wrapper.length, transformedValue: newValue };
});
}
function preline(prepend: string) {
transformationWrapper((value, selectionStart) => { transformationWrapper((value, selectionStart) => {
const lastNewLine = value.slice(0, selectionStart).lastIndexOf("\n") + 1; const lastNewLine =
value.slice(0, selectionStart).lastIndexOf("\n") + 1;
let newValue = ""; let newValue = "";
newValue += value.slice(0, lastNewLine); newValue += value.slice(0, lastNewLine);
@ -100,9 +141,12 @@ export default function Editor(props: Props): JSX.Element {
return { prependLength: prepend.length, transformedValue: newValue }; return { prependLength: prepend.length, transformedValue: newValue };
}); });
} },
[transformationWrapper]
);
function insert(prepend: string) { const insert = useCallback(
(prepend: string) => {
transformationWrapper((value, selectionStart) => { transformationWrapper((value, selectionStart) => {
let newValue = ""; let newValue = "";
newValue += value.slice(0, selectionStart); newValue += value.slice(0, selectionStart);
@ -111,54 +155,33 @@ export default function Editor(props: Props): JSX.Element {
return { prependLength: prepend.length, transformedValue: newValue }; return { prependLength: prepend.length, transformedValue: newValue };
}); });
} },
[transformationWrapper]
);
function appendDoc(append: string) { const appendDoc = useCallback(
(append: string) => {
transformationWrapper((value) => { transformationWrapper((value) => {
const newValue = value + append; const newValue = value + append;
return { prependLength: 0, transformedValue: newValue }; return { prependLength: 0, transformedValue: newValue };
}); });
} },
[transformationWrapper]
function transformationWrapper(
transformation: (
value: string,
selectionStart: number,
selectedEnd: number
) => { prependLength: number; transformedValue: string }
) {
const textarea =
document.querySelector<HTMLTextAreaElement>("#editorTextArea");
if (textarea) {
const { value, selectionStart, selectionEnd } = textarea;
const { prependLength, transformedValue } = transformation(
value,
selectionStart,
selectionEnd
); );
textarea.value = transformedValue; const contentPanel = useMemo(
handleInput(textarea.value); () => (
textarea.focus();
textarea.selectionStart = selectionStart + prependLength;
textarea.selectionEnd = selectionEnd + prependLength;
}
}
const contentPanel = (
<ContentPanel width={ContentPanelWidthSizes.Full}> <ContentPanel width={ContentPanelWidthSizes.Full}>
<Popup setState={setConverterOpened} state={converterOpened}> <Popup setState={setConverterOpened} state={converterOpened}>
<div className="text-center"> <div className="text-center">
<h2 className="mt-4">Convert HTML to markdown</h2> <h2 className="mt-4">Convert HTML to markdown</h2>
<p> <p>
Copy and paste any HTML content (content from web pages) here.{" "} Copy and paste any HTML content (content from web pages) here.
<br /> <br />
The text will immediatly be converted to valid Markdown. The text will immediatly be converted to valid Markdown.
<br /> <br />
You can then copy the converted text and paste it anywhere you want You can then copy the converted text and paste it anywhere you
in the editor want in the editor
</p> </p>
</div> </div>
<textarea <textarea
@ -258,8 +281,8 @@ export default function Editor(props: Props): JSX.Element {
<> <>
<h3 className="text-lg">Transcripts</h3> <h3 className="text-lg">Transcripts</h3>
<p> <p>
Use this to create dialogues and transcripts. Start by adding a Use this to create dialogues and transcripts. Start by adding
container, then add transcript speech line within. a container, then add transcript speech line within.
</p> </p>
<div className="grid gap-2"> <div className="grid gap-2">
<ToolTip <ToolTip
@ -381,7 +404,9 @@ export default function Editor(props: Props): JSX.Element {
<ToolTip <ToolTip
placement="bottom" placement="bottom"
content={<h3 className="text-lg">Player&rsquo;s name placeholder</h3>} content={
<h3 className="text-lg">Player&rsquo;s name placeholder</h3>
}
> >
<Button onClick={() => insert("<player>")} icon={Icon.Person} /> <Button onClick={() => insert("<player>")} icon={Icon.Person} />
</ToolTip> </ToolTip>
@ -426,7 +451,19 @@ export default function Editor(props: Props): JSX.Element {
<TOC text={markdown} /> <TOC text={markdown} />
</div> </div>
</ContentPanel> </ContentPanel>
),
[
appendDoc,
converterOpened,
handleInput,
insert,
markdown,
preline,
toggleWrap,
wrap,
]
); );
return ( return (
<AppLayout <AppLayout
navTitle="Markdawn Editor" navTitle="Markdawn Editor"

View File

@ -49,15 +49,17 @@ import {
GetStaticPathsResult, GetStaticPathsResult,
GetStaticPropsContext, GetStaticPropsContext,
} from "next"; } from "next";
import { Fragment, useState } from "react"; import { Fragment, useMemo, useState } from "react";
import { isUntangibleGroupItem } from "helpers/libraryItem"; import { isUntangibleGroupItem } from "helpers/libraryItem";
import { useMediaHoverable } from "hooks/useMediaQuery"; import { useMediaHoverable } from "hooks/useMediaQuery";
import { WithLabel } from "components/Inputs/WithLabel"; import { WithLabel } from "components/Inputs/WithLabel";
interface Props extends AppStaticProps { interface Props extends AppStaticProps {
item: NonNullable< item: NonNullable<
NonNullable<
GetLibraryItemQuery["libraryItems"] GetLibraryItemQuery["libraryItems"]
>["data"][number]["attributes"]; >["data"][number]["attributes"]
>;
itemId: NonNullable< itemId: NonNullable<
GetLibraryItemQuery["libraryItems"] GetLibraryItemQuery["libraryItems"]
>["data"][number]["id"]; >["data"][number]["id"];
@ -67,29 +69,29 @@ export default function LibrarySlug(props: Props): JSX.Element {
const { item, itemId, langui, currencies } = props; const { item, itemId, langui, currencies } = props;
const appLayout = useAppLayout(); const appLayout = useAppLayout();
const hoverable = useMediaHoverable(); const hoverable = useMediaHoverable();
useScrollTopOnChange(AnchorIds.ContentPanel, [item]);
const isVariantSet =
item?.metadata?.[0]?.__typename === "ComponentMetadataGroup" &&
item.metadata[0].subtype?.data?.attributes?.slug === "variant-set";
sortContent(item?.contents);
const [openLightBox, LightBox] = useLightBox(); const [openLightBox, LightBox] = useLightBox();
const [keepInfoVisible, setKeepInfoVisible] = useState(false); const [keepInfoVisible, setKeepInfoVisible] = useState(false);
let displayOpenScans = false; useScrollTopOnChange(AnchorIds.ContentPanel, [item]);
if (item?.contents?.data)
for (const content of item.contents.data) {
if (
content.attributes?.scan_set &&
content.attributes.scan_set.length > 0
)
displayOpenScans = true;
}
const subPanel = ( const isVariantSet = useMemo(
() =>
item.metadata?.[0]?.__typename === "ComponentMetadataGroup" &&
item.metadata[0].subtype?.data?.attributes?.slug === "variant-set",
[item.metadata]
);
const displayOpenScans = useMemo(
() =>
item.contents?.data.some(
(content) =>
content.attributes?.scan_set && content.attributes.scan_set.length > 0
),
[item.contents?.data]
);
const subPanel = useMemo(
() => (
<SubPanel> <SubPanel>
<ReturnButton <ReturnButton
href="/library/" href="/library/"
@ -102,13 +104,13 @@ export default function LibrarySlug(props: Props): JSX.Element {
<div className="grid gap-4"> <div className="grid gap-4">
<NavOption title={langui.summary} url="#summary" border /> <NavOption title={langui.summary} url="#summary" border />
{item?.gallery && item.gallery.data.length > 0 && ( {item.gallery && item.gallery.data.length > 0 && (
<NavOption title={langui.gallery} url="#gallery" border /> <NavOption title={langui.gallery} url="#gallery" border />
)} )}
<NavOption title={langui.details} url="#details" border /> <NavOption title={langui.details} url="#details" border />
{item?.subitems && item.subitems.data.length > 0 && ( {item.subitems && item.subitems.data.length > 0 && (
<NavOption <NavOption
title={isVariantSet ? langui.variants : langui.subitems} title={isVariantSet ? langui.variants : langui.subitems}
url={isVariantSet ? "#variants" : "#subitems"} url={isVariantSet ? "#variants" : "#subitems"}
@ -116,14 +118,17 @@ export default function LibrarySlug(props: Props): JSX.Element {
/> />
)} )}
{item?.contents && item.contents.data.length > 0 && ( {item.contents && item.contents.data.length > 0 && (
<NavOption title={langui.contents} url="#contents" border /> <NavOption title={langui.contents} url="#contents" border />
)} )}
</div> </div>
</SubPanel> </SubPanel>
),
[isVariantSet, item.contents, item.gallery, item.subitems, langui]
); );
const contentPanel = ( const contentPanel = useMemo(
() => (
<ContentPanel width={ContentPanelWidthSizes.Full}> <ContentPanel width={ContentPanelWidthSizes.Full}>
<LightBox /> <LightBox />
@ -139,7 +144,7 @@ export default function LibrarySlug(props: Props): JSX.Element {
className="relative h-[50vh] w-full className="relative h-[50vh] w-full
cursor-pointer drop-shadow-shade-xl desktop:mb-16 mobile:h-[60vh]" cursor-pointer drop-shadow-shade-xl desktop:mb-16 mobile:h-[60vh]"
onClick={() => { onClick={() => {
if (item?.thumbnail?.data?.attributes) { if (item.thumbnail?.data?.attributes) {
openLightBox([ openLightBox([
getAssetURL( getAssetURL(
item.thumbnail.data.attributes.url, item.thumbnail.data.attributes.url,
@ -149,7 +154,7 @@ export default function LibrarySlug(props: Props): JSX.Element {
} }
}} }}
> >
{item?.thumbnail?.data?.attributes ? ( {item.thumbnail?.data?.attributes ? (
<Img <Img
image={item.thumbnail.data.attributes} image={item.thumbnail.data.attributes}
quality={ImageQuality.Large} quality={ImageQuality.Large}
@ -162,7 +167,7 @@ export default function LibrarySlug(props: Props): JSX.Element {
<InsetBox id="summary" className="grid place-items-center"> <InsetBox id="summary" className="grid place-items-center">
<div className="grid w-[clamp(0px,100%,42rem)] place-items-center gap-8"> <div className="grid w-[clamp(0px,100%,42rem)] place-items-center gap-8">
{item?.subitem_of?.data[0]?.attributes && ( {item.subitem_of?.data[0]?.attributes && (
<div className="grid place-items-center"> <div className="grid place-items-center">
<p>{langui.subitem_of}</p> <p>{langui.subitem_of}</p>
<Button <Button
@ -176,23 +181,25 @@ export default function LibrarySlug(props: Props): JSX.Element {
</div> </div>
)} )}
<div className="grid place-items-center text-center"> <div className="grid place-items-center text-center">
<h1 className="text-3xl">{item?.title}</h1> <h1 className="text-3xl">{item.title}</h1>
{item && isDefinedAndNotEmpty(item.subtitle) && ( {isDefinedAndNotEmpty(item.subtitle) && (
<h2 className="text-2xl">{item.subtitle}</h2> <h2 className="text-2xl">{item.subtitle}</h2>
)} )}
</div> </div>
<PreviewCardCTAs <PreviewCardCTAs
id={itemId} id={itemId}
displayCTAs={!isUntangibleGroupItem(item?.metadata?.[0])} displayCTAs={!isUntangibleGroupItem(item.metadata?.[0])}
langui={langui} langui={langui}
expand expand
/> />
{item?.descriptions?.[0] && ( {item.descriptions?.[0] && (
<p className="text-justify">{item.descriptions[0].description}</p> <p className="text-justify">
{item.descriptions[0].description}
</p>
)} )}
{!( {!(
item?.metadata && item.metadata &&
item.metadata[0]?.__typename === "ComponentMetadataGroup" && item.metadata[0]?.__typename === "ComponentMetadataGroup" &&
(item.metadata[0].subtype?.data?.attributes?.slug === (item.metadata[0].subtype?.data?.attributes?.slug ===
"variant-set" || "variant-set" ||
@ -200,7 +207,7 @@ export default function LibrarySlug(props: Props): JSX.Element {
"relation-set") "relation-set")
) && ( ) && (
<> <>
{item?.urls && item.urls.length ? ( {item.urls?.length ? (
<div className="flex flex-row place-items-center gap-3"> <div className="flex flex-row place-items-center gap-3">
<p>{langui.available_at}</p> <p>{langui.available_at}</p>
{filterHasAttributes(item.urls).map((url, index) => ( {filterHasAttributes(item.urls).map((url, index) => (
@ -221,7 +228,7 @@ export default function LibrarySlug(props: Props): JSX.Element {
</div> </div>
</InsetBox> </InsetBox>
{item?.gallery && item.gallery.data.length > 0 && ( {item.gallery && item.gallery.data.length > 0 && (
<div id="gallery" className="grid w-full place-items-center gap-8"> <div id="gallery" className="grid w-full place-items-center gap-8">
<h2 className="text-2xl">{langui.gallery}</h2> <h2 className="text-2xl">{langui.gallery}</h2>
<div <div
@ -238,7 +245,10 @@ export default function LibrarySlug(props: Props): JSX.Element {
const images: string[] = filterHasAttributes( const images: string[] = filterHasAttributes(
item.gallery?.data item.gallery?.data
).map((image) => ).map((image) =>
getAssetURL(image.attributes.url, ImageQuality.Large) getAssetURL(
image.attributes.url,
ImageQuality.Large
)
); );
openLightBox(images, index); openLightBox(images, index);
}} }}
@ -263,7 +273,7 @@ export default function LibrarySlug(props: Props): JSX.Element {
className="grid place-items-center gap-y-8 className="grid place-items-center gap-y-8
desktop:grid-flow-col desktop:place-content-between" desktop:grid-flow-col desktop:place-content-between"
> >
{item?.metadata?.[0] && ( {item.metadata?.[0] && (
<div className="grid place-content-start place-items-center"> <div className="grid place-content-start place-items-center">
<h3 className="text-xl">{langui.type}</h3> <h3 className="text-xl">{langui.type}</h3>
<div className="grid grid-flow-col gap-1"> <div className="grid grid-flow-col gap-1">
@ -274,14 +284,14 @@ export default function LibrarySlug(props: Props): JSX.Element {
</div> </div>
)} )}
{item?.release_date && ( {item.release_date && (
<div className="grid place-content-start place-items-center"> <div className="grid place-content-start place-items-center">
<h3 className="text-xl">{langui.release_date}</h3> <h3 className="text-xl">{langui.release_date}</h3>
<p>{prettyDate(item.release_date)}</p> <p>{prettyDate(item.release_date)}</p>
</div> </div>
)} )}
{item?.price && ( {item.price && (
<div className="grid place-content-start place-items-center text-center"> <div className="grid place-content-start place-items-center text-center">
<h3 className="text-xl">{langui.price}</h3> <h3 className="text-xl">{langui.price}</h3>
<p> <p>
@ -294,7 +304,11 @@ export default function LibrarySlug(props: Props): JSX.Element {
{item.price.currency?.data?.attributes?.code !== {item.price.currency?.data?.attributes?.code !==
appLayout.currency && ( appLayout.currency && (
<p> <p>
{prettyPrice(item.price, currencies, appLayout.currency)}{" "} {prettyPrice(
item.price,
currencies,
appLayout.currency
)}{" "}
<br />({langui.calculated?.toLowerCase()}) <br />({langui.calculated?.toLowerCase()})
</p> </p>
)} )}
@ -302,7 +316,7 @@ export default function LibrarySlug(props: Props): JSX.Element {
)} )}
</div> </div>
{item?.categories && item.categories.data.length > 0 && ( {item.categories && item.categories.data.length > 0 && (
<div className="flex flex-col place-items-center gap-2"> <div className="flex flex-col place-items-center gap-2">
<h3 className="text-xl">{langui.categories}</h3> <h3 className="text-xl">{langui.categories}</h3>
<div className="flex flex-row flex-wrap place-content-center gap-2"> <div className="flex flex-row flex-wrap place-content-center gap-2">
@ -313,7 +327,7 @@ export default function LibrarySlug(props: Props): JSX.Element {
</div> </div>
)} )}
{item?.size && ( {item.size && (
<div className="grid gap-8 mobile:place-items-center"> <div className="grid gap-8 mobile:place-items-center">
<h3 className="text-xl">{langui.size}</h3> <h3 className="text-xl">{langui.size}</h3>
<div <div
@ -356,12 +370,12 @@ export default function LibrarySlug(props: Props): JSX.Element {
</div> </div>
)} )}
{item?.metadata?.[0]?.__typename !== "ComponentMetadataGroup" && {item.metadata?.[0]?.__typename !== "ComponentMetadataGroup" &&
item?.metadata?.[0]?.__typename !== "ComponentMetadataOther" && ( item.metadata?.[0]?.__typename !== "ComponentMetadataOther" && (
<> <>
<h3 className="text-xl">{langui.type_information}</h3> <h3 className="text-xl">{langui.type_information}</h3>
<div className="grid w-full grid-cols-2 place-content-between"> <div className="grid w-full grid-cols-2 place-content-between">
{item?.metadata?.[0]?.__typename === {item.metadata?.[0]?.__typename ===
"ComponentMetadataBooks" && ( "ComponentMetadataBooks" && (
<> <>
<div className="flex flex-row place-content-start gap-4"> <div className="flex flex-row place-content-start gap-4">
@ -408,7 +422,7 @@ export default function LibrarySlug(props: Props): JSX.Element {
</div> </div>
</InsetBox> </InsetBox>
{item?.subitems && item.subitems.data.length > 0 && ( {item.subitems && item.subitems.data.length > 0 && (
<div <div
id={isVariantSet ? "variants" : "subitems"} id={isVariantSet ? "variants" : "subitems"}
className="grid w-full place-items-center gap-8" className="grid w-full place-items-center gap-8"
@ -477,7 +491,7 @@ export default function LibrarySlug(props: Props): JSX.Element {
</div> </div>
)} )}
{item?.contents && item.contents.data.length > 0 && ( {item.contents && item.contents.data.length > 0 && (
<div id="contents" className="grid w-full place-items-center gap-8"> <div id="contents" className="grid w-full place-items-center gap-8">
<h2 className="-mb-6 text-2xl">{langui.contents}</h2> <h2 className="-mb-6 text-2xl">{langui.contents}</h2>
{displayOpenScans && ( {displayOpenScans && (
@ -500,15 +514,29 @@ export default function LibrarySlug(props: Props): JSX.Element {
)} )}
</div> </div>
</ContentPanel> </ContentPanel>
),
[
LightBox,
openLightBox,
appLayout.currency,
currencies,
displayOpenScans,
hoverable,
isVariantSet,
item,
itemId,
keepInfoVisible,
langui,
]
); );
return ( return (
<AppLayout <AppLayout
navTitle={prettyinlineTitle("", item?.title, item?.subtitle)} navTitle={prettyinlineTitle("", item.title, item.subtitle)}
contentPanel={contentPanel} contentPanel={contentPanel}
subPanel={subPanel} subPanel={subPanel}
thumbnail={item?.thumbnail?.data?.attributes ?? undefined} thumbnail={item.thumbnail?.data?.attributes ?? undefined}
description={item?.descriptions?.[0]?.description ?? undefined} description={item.descriptions?.[0]?.description ?? undefined}
{...props} {...props}
/> />
); );
@ -526,6 +554,7 @@ export async function getStaticProps(
language_code: context.locale ?? "en", language_code: context.locale ?? "en",
}); });
if (!item.libraryItems?.data[0]?.attributes) return { notFound: true }; if (!item.libraryItems?.data[0]?.attributes) return { notFound: true };
sortContent(item.libraryItems.data[0].attributes.contents);
const props: Props = { const props: Props = {
...(await getAppStaticProps(context)), ...(await getAppStaticProps(context)),
item: item.libraryItems.data[0].attributes, item: item.libraryItems.data[0].attributes,
@ -544,7 +573,7 @@ export async function getStaticPaths(
const paths: GetStaticPathsResult["paths"] = []; const paths: GetStaticPathsResult["paths"] = [];
filterHasAttributes(libraryItems.libraryItems?.data).map((item) => { filterHasAttributes(libraryItems.libraryItems?.data).map((item) => {
context.locales?.map((local) => context.locales?.map((local) =>
paths.push({ params: { slug: item.attributes?.slug }, locale: local }) paths.push({ params: { slug: item.attributes.slug }, locale: local })
); );
}); });

View File

@ -23,33 +23,36 @@ import {
GetStaticPathsResult, GetStaticPathsResult,
GetStaticPropsContext, GetStaticPropsContext,
} from "next"; } from "next";
import { Fragment } from "react"; import { Fragment, useMemo } from "react";
interface Props extends AppStaticProps { interface Props extends AppStaticProps {
item: NonNullable< item: NonNullable<
NonNullable<
GetLibraryItemScansQuery["libraryItems"] GetLibraryItemScansQuery["libraryItems"]
>["data"][number]["attributes"]; >["data"][number]["attributes"]
>;
itemId: NonNullable< itemId: NonNullable<
GetLibraryItemScansQuery["libraryItems"] NonNullable<GetLibraryItemScansQuery["libraryItems"]>["data"][number]["id"]
>["data"][number]["id"]; >;
} }
export default function LibrarySlug(props: Props): JSX.Element { export default function LibrarySlug(props: Props): JSX.Element {
const { item, langui, languages } = props; const { item, langui, languages } = props;
const [openLightBox, LightBox] = useLightBox(); const [openLightBox, LightBox] = useLightBox();
sortContent(item?.contents); sortContent(item.contents);
const subPanel = ( const subPanel = useMemo(
() => (
<SubPanel> <SubPanel>
<ReturnButton <ReturnButton
href={`/library/${item?.slug}`} href={`/library/${item.slug}`}
title={langui.item} title={langui.item}
langui={langui} langui={langui}
displayOn={ReturnButtonType.Desktop} displayOn={ReturnButtonType.Desktop}
horizontalLine horizontalLine
/> />
{item?.contents?.data.map((content) => ( {item.contents?.data.map((content) => (
<NavOption <NavOption
key={content.id} key={content.id}
url={`#${content.attributes?.slug}`} url={`#${content.attributes?.slug}`}
@ -66,21 +69,24 @@ export default function LibrarySlug(props: Props): JSX.Element {
/> />
))} ))}
</SubPanel> </SubPanel>
),
[item.contents?.data, item.slug, langui]
); );
const contentPanel = ( const contentPanel = useMemo(
() => (
<ContentPanel width={ContentPanelWidthSizes.Full}> <ContentPanel width={ContentPanelWidthSizes.Full}>
<LightBox /> <LightBox />
<ReturnButton <ReturnButton
href={`/library/${item?.slug}`} href={`/library/${item.slug}`}
title={langui.item} title={langui.item}
langui={langui} langui={langui}
displayOn={ReturnButtonType.Mobile} displayOn={ReturnButtonType.Mobile}
className="mb-10" className="mb-10"
/> />
{item?.images && ( {item.images && (
<ScanSetCover <ScanSetCover
images={item.images} images={item.images}
openLightBox={openLightBox} openLightBox={openLightBox}
@ -89,7 +95,7 @@ export default function LibrarySlug(props: Props): JSX.Element {
/> />
)} )}
{item?.contents?.data.map((content) => ( {item.contents?.data.map((content) => (
<Fragment key={content.id}> <Fragment key={content.id}>
{content.attributes?.scan_set?.[0] && ( {content.attributes?.scan_set?.[0] && (
<ScanSet <ScanSet
@ -105,14 +111,24 @@ export default function LibrarySlug(props: Props): JSX.Element {
</Fragment> </Fragment>
))} ))}
</ContentPanel> </ContentPanel>
),
[
LightBox,
openLightBox,
item.contents?.data,
item.images,
item.slug,
languages,
langui,
]
); );
return ( return (
<AppLayout <AppLayout
navTitle={prettyinlineTitle("", item?.title, item?.subtitle)} navTitle={prettyinlineTitle("", item.title, item.subtitle)}
contentPanel={contentPanel} contentPanel={contentPanel}
subPanel={subPanel} subPanel={subPanel}
thumbnail={item?.thumbnail?.data?.attributes ?? undefined} thumbnail={item.thumbnail?.data?.attributes ?? undefined}
{...props} {...props}
/> />
); );
@ -129,7 +145,8 @@ export async function getStaticProps(
: "", : "",
language_code: context.locale ?? "en", language_code: context.locale ?? "en",
}); });
if (!item.libraryItems?.data[0]?.attributes) return { notFound: true }; if (!item.libraryItems?.data[0]?.attributes || !item.libraryItems.data[0]?.id)
return { notFound: true };
const props: Props = { const props: Props = {
...(await getAppStaticProps(context)), ...(await getAppStaticProps(context)),
item: item.libraryItems.data[0].attributes, item: item.libraryItems.data[0].attributes,

View File

@ -114,7 +114,8 @@ export default function Library(props: Props): JSX.Element {
[langui, groupingMethod, sortedItems] [langui, groupingMethod, sortedItems]
); );
const subPanel = ( const subPanel = useMemo(
() => (
<SubPanel> <SubPanel>
<PanelHeader <PanelHeader
icon={Icon.LibraryBooks} icon={Icon.LibraryBooks}
@ -177,7 +178,10 @@ export default function Library(props: Props): JSX.Element {
<WithLabel <WithLabel
label={langui.show_secondary_items} label={langui.show_secondary_items}
input={ input={
<Switch state={showSecondaryItems} setState={setShowSecondaryItems} /> <Switch
state={showSecondaryItems}
setState={setShowSecondaryItems}
/>
} }
/> />
@ -237,8 +241,23 @@ export default function Library(props: Props): JSX.Element {
}} }}
/> />
</SubPanel> </SubPanel>
),
[
filterUserStatus,
groupingMethod,
hoverable,
keepInfoVisible,
langui,
searchName,
showPrimaryItems,
showSecondaryItems,
showSubitems,
sortingMethod,
]
); );
const contentPanel = (
const contentPanel = useMemo(
() => (
<ContentPanel width={ContentPanelWidthSizes.Full}> <ContentPanel width={ContentPanelWidthSizes.Full}>
{/* TODO: Add to langui */} {/* TODO: Add to langui */}
{groups.size === 0 && ( {groups.size === 0 && (
@ -311,7 +330,10 @@ export default function Library(props: Props): JSX.Element {
</Fragment> </Fragment>
))} ))}
</ContentPanel> </ContentPanel>
),
[currencies, groups, keepInfoVisible, langui]
); );
return ( return (
<AppLayout <AppLayout
navTitle={langui.library} navTitle={langui.library}

View File

@ -5,11 +5,13 @@ import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
import { GetStaticPropsContext } from "next"; import { GetStaticPropsContext } from "next";
import { Icon } from "components/Ico"; import { Icon } from "components/Ico";
import { useMemo } from "react";
interface Props extends AppStaticProps {} interface Props extends AppStaticProps {}
export default function Merch(props: Props): JSX.Element { export default function Merch(props: Props): JSX.Element {
const { langui } = props; const { langui } = props;
const subPanel = ( const subPanel = useMemo(
() => (
<SubPanel> <SubPanel>
<PanelHeader <PanelHeader
icon={Icon.Store} icon={Icon.Store}
@ -17,6 +19,8 @@ export default function Merch(props: Props): JSX.Element {
description={langui.merch_description} description={langui.merch_description}
/> />
</SubPanel> </SubPanel>
),
[langui]
); );
return <AppLayout navTitle={langui.merch} subPanel={subPanel} {...props} />; return <AppLayout navTitle={langui.merch} subPanel={subPanel} {...props} />;

View File

@ -46,7 +46,8 @@ export default function News(props: Props): JSX.Element {
[posts, searchName] [posts, searchName]
); );
const subPanel = ( const subPanel = useMemo(
() => (
<SubPanel> <SubPanel>
<PanelHeader <PanelHeader
icon={Icon.Feed} icon={Icon.Feed}
@ -80,9 +81,12 @@ export default function News(props: Props): JSX.Element {
}} }}
/> />
</SubPanel> </SubPanel>
),
[hoverable, keepInfoVisible, langui, searchName]
); );
const contentPanel = ( const contentPanel = useMemo(
() => (
<ContentPanel width={ContentPanelWidthSizes.Full}> <ContentPanel width={ContentPanelWidthSizes.Full}>
<div <div
className="grid grid-cols-1 items-end gap-8 className="grid grid-cols-1 items-end gap-8
@ -113,6 +117,8 @@ export default function News(props: Props): JSX.Element {
))} ))}
</div> </div>
</ContentPanel> </ContentPanel>
),
[filteredItems, keepInfoVisible]
); );
return ( return (

View File

@ -26,6 +26,7 @@ import {
GetStaticPathsResult, GetStaticPathsResult,
GetStaticPropsContext, GetStaticPropsContext,
} from "next"; } from "next";
import { useMemo } from "react";
interface Props extends AppStaticProps { interface Props extends AppStaticProps {
page: WikiPageWithTranslations; page: WikiPageWithTranslations;
@ -40,7 +41,8 @@ export default function WikiPage(props: Props): JSX.Element {
languageExtractor: (item) => item.language?.data?.attributes?.code, languageExtractor: (item) => item.language?.data?.attributes?.code,
}); });
const subPanel = ( const subPanel = useMemo(
() => (
<SubPanel> <SubPanel>
<ReturnButton <ReturnButton
href={`/wiki`} href={`/wiki`}
@ -50,8 +52,12 @@ export default function WikiPage(props: Props): JSX.Element {
horizontalLine horizontalLine
/> />
</SubPanel> </SubPanel>
),
[langui]
); );
const contentPanel = (
const contentPanel = useMemo(
() => (
<ContentPanel width={ContentPanelWidthSizes.Large}> <ContentPanel width={ContentPanelWidthSizes.Large}>
<ReturnButton <ReturnButton
href={`/wiki`} href={`/wiki`}
@ -98,13 +104,13 @@ export default function WikiPage(props: Props): JSX.Element {
<DefinitionCard <DefinitionCard
key={index} key={index}
source={definition.source?.data?.attributes?.name} source={definition.source?.data?.attributes?.name}
translations={filterHasAttributes(definition.translations).map( translations={filterHasAttributes(
(translation) => ({ definition.translations
).map((translation) => ({
language: translation.language.data?.attributes?.code, language: translation.language.data?.attributes?.code,
definition: translation.definition, definition: translation.definition,
status: translation.status, status: translation.status,
}) }))}
)}
index={index + 1} index={index + 1}
languages={languages} languages={languages}
langui={langui} langui={langui}
@ -114,6 +120,16 @@ export default function WikiPage(props: Props): JSX.Element {
</div> </div>
)} )}
</ContentPanel> </ContentPanel>
),
[
LanguageSwitcher,
languages,
langui,
page.categories?.data,
page.definitions,
page.thumbnail?.data?.attributes,
selectedTranslation,
]
); );
return ( return (

View File

@ -14,7 +14,7 @@ import { getReadySdk } from "graphql/sdk";
import { prettySlug } from "helpers/formatters"; import { prettySlug } from "helpers/formatters";
import { filterHasAttributes, isDefined } from "helpers/others"; import { filterHasAttributes, isDefined } from "helpers/others";
import { GetStaticPropsContext } from "next"; import { GetStaticPropsContext } from "next";
import { Fragment } from "react"; import { Fragment, useMemo } from "react";
interface Props extends AppStaticProps { interface Props extends AppStaticProps {
chronologyItems: NonNullable< chronologyItems: NonNullable<
@ -27,10 +27,10 @@ export default function Chronology(props: Props): JSX.Element {
const { chronologyItems, chronologyEras, langui } = props; const { chronologyItems, chronologyEras, langui } = props;
// Group by year the Chronology items // Group by year the Chronology items
const chronologyItemYearGroups: Props["chronologyItems"][number][][][] = []; const chronologyItemYearGroups = useMemo(() => {
const memo: Props["chronologyItems"][number][][][] = [];
chronologyEras.map(() => { chronologyEras.map(() => {
chronologyItemYearGroups.push([]); memo.push([]);
}); });
let currentChronologyEraIndex = 0; let currentChronologyEraIndex = 0;
@ -45,22 +45,21 @@ export default function Chronology(props: Props): JSX.Element {
} }
if ( if (
Object.prototype.hasOwnProperty.call( Object.prototype.hasOwnProperty.call(
chronologyItemYearGroups[currentChronologyEraIndex], memo[currentChronologyEraIndex],
item.attributes.year item.attributes.year
) )
) { ) {
chronologyItemYearGroups[currentChronologyEraIndex][ memo[currentChronologyEraIndex][item.attributes.year].push(item);
item.attributes.year
].push(item);
} else { } else {
chronologyItemYearGroups[currentChronologyEraIndex][ memo[currentChronologyEraIndex][item.attributes.year] = [item];
item.attributes.year
] = [item];
} }
} }
}); });
return memo;
}, [chronologyEras, chronologyItems]);
const subPanel = ( const subPanel = useMemo(
() => (
<SubPanel> <SubPanel>
<ReturnButton <ReturnButton
href="/wiki" href="/wiki"
@ -87,9 +86,12 @@ export default function Chronology(props: Props): JSX.Element {
</Fragment> </Fragment>
))} ))}
</SubPanel> </SubPanel>
),
[chronologyEras, langui]
); );
const contentPanel = ( const contentPanel = useMemo(
() => (
<ContentPanel> <ContentPanel>
<ReturnButton <ReturnButton
href="/wiki" href="/wiki"
@ -130,6 +132,8 @@ export default function Chronology(props: Props): JSX.Element {
</Fragment> </Fragment>
))} ))}
</ContentPanel> </ContentPanel>
),
[chronologyEras, chronologyItemYearGroups, langui]
); );
return ( return (

View File

@ -47,7 +47,8 @@ export default function Wiki(props: Props): JSX.Element {
[pages, searchName] [pages, searchName]
); );
const subPanel = ( const subPanel = useMemo(
() => (
<SubPanel> <SubPanel>
<PanelHeader <PanelHeader
icon={Icon.TravelExplore} icon={Icon.TravelExplore}
@ -87,9 +88,12 @@ export default function Wiki(props: Props): JSX.Element {
<NavOption title={langui.chronology} url="/wiki/chronology" border /> <NavOption title={langui.chronology} url="/wiki/chronology" border />
</SubPanel> </SubPanel>
),
[hoverable, keepInfoVisible, langui, searchName]
); );
const contentPanel = ( const contentPanel = useMemo(
() => (
<ContentPanel width={ContentPanelWidthSizes.Full}> <ContentPanel width={ContentPanelWidthSizes.Full}>
<div <div
className="grid grid-cols-2 items-end gap-8 className="grid grid-cols-2 items-end gap-8
@ -130,6 +134,8 @@ export default function Wiki(props: Props): JSX.Element {
))} ))}
</div> </div>
</ContentPanel> </ContentPanel>
),
[filteredPages, keepInfoVisible, languages]
); );
return ( return (