Continued using hooks
This commit is contained in:
parent
efcf01e8a0
commit
d0b91f9db6
|
@ -161,30 +161,30 @@ export function AppLayout(props: Props): JSX.Element {
|
|||
}, [fontSize]);
|
||||
|
||||
const defaultPreferredLanguages = useMemo(() => {
|
||||
let list: string[] = [];
|
||||
let memo: string[] = [];
|
||||
if (isDefinedAndNotEmpty(router.locale) && router.locales) {
|
||||
if (router.locale === "en") {
|
||||
list = [router.locale];
|
||||
memo = [router.locale];
|
||||
router.locales.map((locale) => {
|
||||
if (locale !== router.locale) list.push(locale);
|
||||
if (locale !== router.locale) memo.push(locale);
|
||||
});
|
||||
} else {
|
||||
list = [router.locale, "en"];
|
||||
memo = [router.locale, "en"];
|
||||
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]);
|
||||
|
||||
const currencyOptions = useMemo(() => {
|
||||
const list: string[] = [];
|
||||
const memo: string[] = [];
|
||||
filterHasAttributes(currencies).map((currentCurrency) => {
|
||||
if (isDefinedAndNotEmpty(currentCurrency.attributes.code))
|
||||
list.push(currentCurrency.attributes.code);
|
||||
memo.push(currentCurrency.attributes.code);
|
||||
});
|
||||
return list;
|
||||
return memo;
|
||||
}, [currencies]);
|
||||
|
||||
const [currencySelect, setCurrencySelect] = useState<number>(-1);
|
||||
|
|
|
@ -157,12 +157,10 @@ export function ScanSet(props: Props): JSX.Element {
|
|||
{filterHasAttributes(selectedScan.cleaners.data).map(
|
||||
(cleaner) => (
|
||||
<Fragment key={cleaner.id}>
|
||||
{cleaner.attributes && (
|
||||
<RecorderChip
|
||||
langui={langui}
|
||||
recorder={cleaner.attributes}
|
||||
/>
|
||||
)}
|
||||
</Fragment>
|
||||
)
|
||||
)}
|
||||
|
@ -178,12 +176,10 @@ export function ScanSet(props: Props): JSX.Element {
|
|||
{filterHasAttributes(selectedScan.typesetters.data).map(
|
||||
(typesetter) => (
|
||||
<Fragment key={typesetter.id}>
|
||||
{typesetter.attributes && (
|
||||
<RecorderChip
|
||||
langui={langui}
|
||||
recorder={typesetter.attributes}
|
||||
/>
|
||||
)}
|
||||
</Fragment>
|
||||
)
|
||||
)}
|
||||
|
@ -218,9 +214,7 @@ export function ScanSet(props: Props): JSX.Element {
|
|||
openLightBox(images, index);
|
||||
}}
|
||||
>
|
||||
{page.attributes && (
|
||||
<Img image={page.attributes} quality={ImageQuality.Small} />
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
@ -11,7 +11,7 @@ import { getAssetURL, ImageQuality } from "helpers/img";
|
|||
import { filterHasAttributes, getStatusDescription } from "helpers/others";
|
||||
|
||||
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
||||
import { Fragment } from "react";
|
||||
import { Fragment, useMemo } from "react";
|
||||
|
||||
interface Props {
|
||||
openLightBox: (images: string[], index?: number) => void;
|
||||
|
@ -35,19 +35,22 @@ export function ScanSetCover(props: Props): JSX.Element {
|
|||
languageExtractor: (item) => item.language?.data?.attributes?.code,
|
||||
});
|
||||
|
||||
const coverImages: UploadImageFragment[] = [];
|
||||
const coverImages = useMemo(() => {
|
||||
const memo: UploadImageFragment[] = [];
|
||||
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)
|
||||
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)
|
||||
coverImages.push(selectedScan.dust_jacket.full.data.attributes);
|
||||
memo.push(selectedScan.dust_jacket.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)
|
||||
coverImages.push(selectedScan.cover.full.data.attributes);
|
||||
memo.push(selectedScan.cover.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) {
|
||||
return (
|
||||
|
|
|
@ -67,7 +67,8 @@ export function PostPage(props: Props): JSX.Element {
|
|||
[post.slug, post.thumbnail, selectedTranslation]
|
||||
);
|
||||
|
||||
const subPanel =
|
||||
const subPanel = useMemo(
|
||||
() =>
|
||||
returnHref || returnTitle || displayCredits || displayToc ? (
|
||||
<SubPanel>
|
||||
{returnHref && returnTitle && (
|
||||
|
@ -120,9 +121,22 @@ export function PostPage(props: Props): JSX.Element {
|
|||
|
||||
{displayToc && <TOC text={body} title={title} />}
|
||||
</SubPanel>
|
||||
) : undefined;
|
||||
) : undefined,
|
||||
[
|
||||
body,
|
||||
displayCredits,
|
||||
displayToc,
|
||||
langui,
|
||||
post.authors,
|
||||
returnHref,
|
||||
returnTitle,
|
||||
selectedTranslation,
|
||||
title,
|
||||
]
|
||||
);
|
||||
|
||||
const contentPanel = (
|
||||
const contentPanel = useMemo(
|
||||
() => (
|
||||
<ContentPanel>
|
||||
{returnHref && returnTitle && (
|
||||
<ReturnButton
|
||||
|
@ -166,6 +180,23 @@ export function PostPage(props: Props): JSX.Element {
|
|||
<Markdawn text={body} />
|
||||
{appendBody}
|
||||
</ContentPanel>
|
||||
),
|
||||
[
|
||||
LanguageSwitcher,
|
||||
appendBody,
|
||||
body,
|
||||
displayLanguageSwitcher,
|
||||
displayThumbnailHeader,
|
||||
displayTitle,
|
||||
excerpt,
|
||||
langui,
|
||||
post.categories,
|
||||
prependBody,
|
||||
returnHref,
|
||||
returnTitle,
|
||||
thumbnail,
|
||||
title,
|
||||
]
|
||||
);
|
||||
|
||||
return (
|
||||
|
|
|
@ -85,7 +85,6 @@ export function ChronologyItemComponent(props: Props): JSX.Element {
|
|||
{translation.description && (
|
||||
<p
|
||||
className={
|
||||
event.translations &&
|
||||
event.translations.length > 1
|
||||
? `mt-2 whitespace-pre-line before:ml-[-1em] before:inline-block
|
||||
before:w-4 before:text-dark before:content-['-']`
|
||||
|
|
|
@ -208,7 +208,7 @@ export function sortBy(
|
|||
orderByType: number,
|
||||
items: Items,
|
||||
currencies: AppStaticProps["currencies"]
|
||||
): Items {
|
||||
) {
|
||||
switch (orderByType) {
|
||||
case 0:
|
||||
return items.sort((a, b) => {
|
||||
|
|
|
@ -19,9 +19,7 @@ type SortContentProps =
|
|||
>["contents"];
|
||||
|
||||
export function sortContent(contents: SortContentProps) {
|
||||
if (contents) {
|
||||
const newContent = { ...contents };
|
||||
newContent?.data.sort((a, b) => {
|
||||
contents?.data.sort((a, b) => {
|
||||
if (
|
||||
a.attributes?.range[0]?.__typename === "ComponentRangePageRange" &&
|
||||
b.attributes?.range[0]?.__typename === "ComponentRangePageRange"
|
||||
|
@ -33,9 +31,6 @@ export function sortContent(contents: SortContentProps) {
|
|||
}
|
||||
return 0;
|
||||
});
|
||||
return newContent;
|
||||
}
|
||||
return contents;
|
||||
}
|
||||
|
||||
export function getStatusDescription(
|
||||
|
|
|
@ -38,12 +38,12 @@ export function useSmartLanguage<T>(
|
|||
const router = useRouter();
|
||||
|
||||
const availableLocales = useMemo(() => {
|
||||
const map = new Map<string, number>();
|
||||
const memo = new Map<string, number>();
|
||||
filterDefined(items).map((elem, index) => {
|
||||
const result = languageExtractor(elem);
|
||||
if (isDefined(result)) map.set(result, index);
|
||||
if (isDefined(result)) memo.set(result, index);
|
||||
});
|
||||
return map;
|
||||
return memo;
|
||||
}, [items, languageExtractor]);
|
||||
|
||||
const [selectedTranslationIndex, setSelectedTranslationIndex] = useState<
|
||||
|
|
|
@ -7,12 +7,14 @@ import { ContentPanel } from "components/Panels/ContentPanel";
|
|||
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
|
||||
|
||||
import { GetStaticPropsContext } from "next";
|
||||
import { useMemo } from "react";
|
||||
|
||||
interface Props extends AppStaticProps {}
|
||||
|
||||
export default function FourOhFour(props: Props): JSX.Element {
|
||||
const { langui } = props;
|
||||
const contentPanel = (
|
||||
const contentPanel = useMemo(
|
||||
() => (
|
||||
<ContentPanel>
|
||||
<h1>404 - {langui.page_not_found}</h1>
|
||||
<ReturnButton
|
||||
|
@ -22,6 +24,8 @@ export default function FourOhFour(props: Props): JSX.Element {
|
|||
displayOn={ReturnButtonType.Both}
|
||||
/>
|
||||
</ContentPanel>
|
||||
),
|
||||
[langui]
|
||||
);
|
||||
return <AppLayout navTitle="404" contentPanel={contentPanel} {...props} />;
|
||||
}
|
||||
|
|
|
@ -7,12 +7,14 @@ import { ContentPanel } from "components/Panels/ContentPanel";
|
|||
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
|
||||
|
||||
import { GetStaticPropsContext } from "next";
|
||||
import { useMemo } from "react";
|
||||
|
||||
interface Props extends AppStaticProps {}
|
||||
|
||||
export default function FiveHundred(props: Props): JSX.Element {
|
||||
const { langui } = props;
|
||||
const contentPanel = (
|
||||
const contentPanel = useMemo(
|
||||
() => (
|
||||
<ContentPanel>
|
||||
<h1>500 - Internal Server Error</h1>
|
||||
<ReturnButton
|
||||
|
@ -22,6 +24,8 @@ export default function FiveHundred(props: Props): JSX.Element {
|
|||
displayOn={ReturnButtonType.Both}
|
||||
/>
|
||||
</ContentPanel>
|
||||
),
|
||||
[langui]
|
||||
);
|
||||
return <AppLayout navTitle="500" contentPanel={contentPanel} {...props} />;
|
||||
}
|
||||
|
|
|
@ -6,12 +6,14 @@ import { SubPanel } from "components/Panels/SubPanel";
|
|||
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
|
||||
|
||||
import { GetStaticPropsContext } from "next";
|
||||
import { useMemo } from "react";
|
||||
|
||||
interface Props extends AppStaticProps {}
|
||||
|
||||
export default function AboutUs(props: Props): JSX.Element {
|
||||
const { langui } = props;
|
||||
const subPanel = (
|
||||
const subPanel = useMemo(
|
||||
() => (
|
||||
<SubPanel>
|
||||
<PanelHeader
|
||||
icon={Icon.Info}
|
||||
|
@ -24,7 +26,6 @@ export default function AboutUs(props: Props): JSX.Element {
|
|||
border
|
||||
/>
|
||||
<NavOption title={langui.legality} url="/about-us/legality" border />
|
||||
{/* <NavOption title={langui.members} url="/about-us/members" border /> */}
|
||||
<NavOption
|
||||
title={langui.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 />
|
||||
</SubPanel>
|
||||
),
|
||||
[langui]
|
||||
);
|
||||
return (
|
||||
<AppLayout navTitle={langui.about_us} subPanel={subPanel} {...props} />
|
||||
|
|
|
@ -6,12 +6,14 @@ import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
|
|||
|
||||
import { GetStaticPropsContext } from "next";
|
||||
import { Icon } from "components/Ico";
|
||||
import { useMemo } from "react";
|
||||
|
||||
interface Props extends AppStaticProps {}
|
||||
|
||||
export default function Archives(props: Props): JSX.Element {
|
||||
const { langui } = props;
|
||||
const subPanel = (
|
||||
const subPanel = useMemo(
|
||||
() => (
|
||||
<SubPanel>
|
||||
<PanelHeader
|
||||
icon={Icon.Inventory}
|
||||
|
@ -20,6 +22,8 @@ export default function Archives(props: Props): JSX.Element {
|
|||
/>
|
||||
<NavOption title={"Videos"} url="/archives/videos/" border />
|
||||
</SubPanel>
|
||||
),
|
||||
[langui]
|
||||
);
|
||||
return (
|
||||
<AppLayout navTitle={langui.archives} subPanel={subPanel} {...props} />
|
||||
|
|
|
@ -20,7 +20,7 @@ import {
|
|||
GetStaticPathsResult,
|
||||
GetStaticPropsContext,
|
||||
} from "next";
|
||||
import { Fragment, useState } from "react";
|
||||
import { Fragment, useState, useMemo } from "react";
|
||||
import { Icon } from "components/Ico";
|
||||
import { useMediaHoverable } from "hooks/useMediaQuery";
|
||||
import { WithLabel } from "components/Inputs/WithLabel";
|
||||
|
@ -37,7 +37,8 @@ export default function Channel(props: Props): JSX.Element {
|
|||
const [keepInfoVisible, setKeepInfoVisible] = useState(true);
|
||||
const hoverable = useMediaHoverable();
|
||||
|
||||
const subPanel = (
|
||||
const subPanel = useMemo(
|
||||
() => (
|
||||
<SubPanel>
|
||||
<ReturnButton
|
||||
href="/archives/videos/"
|
||||
|
@ -62,9 +63,12 @@ export default function Channel(props: Props): JSX.Element {
|
|||
/>
|
||||
)}
|
||||
</SubPanel>
|
||||
),
|
||||
[hoverable, keepInfoVisible, langui]
|
||||
);
|
||||
|
||||
const contentPanel = (
|
||||
const contentPanel = useMemo(
|
||||
() => (
|
||||
<ContentPanel width={ContentPanelWidthSizes.Full}>
|
||||
<div className="mb-8">
|
||||
<h1 className="text-3xl">{channel?.title}</h1>
|
||||
|
@ -97,7 +101,15 @@ export default function Channel(props: Props): JSX.Element {
|
|||
))}
|
||||
</div>
|
||||
</ContentPanel>
|
||||
),
|
||||
[
|
||||
channel?.subscribers,
|
||||
channel?.title,
|
||||
channel?.videos?.data,
|
||||
keepInfoVisible,
|
||||
]
|
||||
);
|
||||
|
||||
return (
|
||||
<AppLayout
|
||||
navTitle={langui.archives}
|
||||
|
|
|
@ -22,40 +22,33 @@ import { filterHasAttributes } from "helpers/others";
|
|||
import { getVideoThumbnailURL } from "helpers/videos";
|
||||
import { useMediaHoverable } from "hooks/useMediaQuery";
|
||||
import { GetStaticPropsContext } from "next";
|
||||
import { Fragment, useState } from "react";
|
||||
import { Fragment, useMemo, useState } from "react";
|
||||
|
||||
interface Props extends AppStaticProps {
|
||||
videos: NonNullable<GetVideosPreviewQuery["videos"]>["data"];
|
||||
}
|
||||
|
||||
const ITEM_PER_PAGE = 50;
|
||||
|
||||
export default function Videos(props: Props): JSX.Element {
|
||||
const { langui, videos } = props;
|
||||
const hoverable = useMediaHoverable();
|
||||
|
||||
videos
|
||||
.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 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)
|
||||
const paginatedVideos = useMemo(() => {
|
||||
const memo = [];
|
||||
for (let index = 0; ITEM_PER_PAGE * index < videos.length; index += 1) {
|
||||
memo.push(
|
||||
videos.slice(index * ITEM_PER_PAGE, (index + 1) * ITEM_PER_PAGE)
|
||||
);
|
||||
}
|
||||
return memo;
|
||||
}, [videos]);
|
||||
|
||||
const [page, setPage] = useState(0);
|
||||
const [keepInfoVisible, setKeepInfoVisible] = useState(true);
|
||||
|
||||
const subPanel = (
|
||||
const subPanel = useMemo(
|
||||
() => (
|
||||
<SubPanel>
|
||||
<ReturnButton
|
||||
href="/archives/"
|
||||
|
@ -80,12 +73,15 @@ export default function Videos(props: Props): JSX.Element {
|
|||
/>
|
||||
)}
|
||||
</SubPanel>
|
||||
),
|
||||
[hoverable, keepInfoVisible, langui]
|
||||
);
|
||||
|
||||
const contentPanel = (
|
||||
const contentPanel = useMemo(
|
||||
() => (
|
||||
<ContentPanel width={ContentPanelWidthSizes.Full}>
|
||||
<PageSelector
|
||||
maxPage={Math.floor(videos.length / itemPerPage)}
|
||||
maxPage={Math.floor(videos.length / ITEM_PER_PAGE)}
|
||||
page={page}
|
||||
setPage={setPage}
|
||||
className="mb-12"
|
||||
|
@ -120,12 +116,14 @@ export default function Videos(props: Props): JSX.Element {
|
|||
</div>
|
||||
|
||||
<PageSelector
|
||||
maxPage={Math.floor(videos.length / itemPerPage)}
|
||||
maxPage={Math.floor(videos.length / ITEM_PER_PAGE)}
|
||||
page={page}
|
||||
setPage={setPage}
|
||||
className="mt-12"
|
||||
/>
|
||||
</ContentPanel>
|
||||
),
|
||||
[keepInfoVisible, page, paginatedVideos, videos.length]
|
||||
);
|
||||
return (
|
||||
<AppLayout
|
||||
|
@ -143,6 +141,17 @@ export async function getStaticProps(
|
|||
const sdk = getReadySdk();
|
||||
const videos = await sdk.getVideosPreview();
|
||||
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 = {
|
||||
...(await getAppStaticProps(context)),
|
||||
videos: videos.videos.data,
|
||||
|
|
|
@ -26,6 +26,7 @@ import {
|
|||
GetStaticPathsResult,
|
||||
GetStaticPropsContext,
|
||||
} from "next";
|
||||
import { useMemo } from "react";
|
||||
|
||||
interface Props extends AppStaticProps {
|
||||
video: NonNullable<
|
||||
|
@ -37,7 +38,8 @@ export default function Video(props: Props): JSX.Element {
|
|||
const { langui, video } = props;
|
||||
const isMobile = useMediaMobile();
|
||||
const appLayout = useAppLayout();
|
||||
const subPanel = (
|
||||
const subPanel = useMemo(
|
||||
() => (
|
||||
<SubPanel>
|
||||
<ReturnButton
|
||||
href="/archives/videos/"
|
||||
|
@ -70,9 +72,12 @@ export default function Video(props: Props): JSX.Element {
|
|||
onClick={() => appLayout.setSubPanelOpen(false)}
|
||||
/>
|
||||
</SubPanel>
|
||||
),
|
||||
[appLayout, langui]
|
||||
);
|
||||
|
||||
const contentPanel = (
|
||||
const contentPanel = useMemo(
|
||||
() => (
|
||||
<ContentPanel width={ContentPanelWidthSizes.Full}>
|
||||
<ReturnButton
|
||||
href="/library/"
|
||||
|
@ -175,7 +180,22 @@ export default function Video(props: Props): JSX.Element {
|
|||
</InsetBox>
|
||||
</div>
|
||||
</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 (
|
||||
<AppLayout
|
||||
navTitle={langui.archives}
|
||||
|
|
|
@ -5,12 +5,14 @@ import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
|
|||
|
||||
import { GetStaticPropsContext } from "next";
|
||||
import { Icon } from "components/Ico";
|
||||
import { useMemo } from "react";
|
||||
|
||||
interface Props extends AppStaticProps {}
|
||||
|
||||
export default function Chronicles(props: Props): JSX.Element {
|
||||
const { langui } = props;
|
||||
const subPanel = (
|
||||
const subPanel = useMemo(
|
||||
() => (
|
||||
<SubPanel>
|
||||
<PanelHeader
|
||||
icon={Icon.WatchLater}
|
||||
|
@ -18,7 +20,10 @@ export default function Chronicles(props: Props): JSX.Element {
|
|||
description={langui.chronicles_description}
|
||||
/>
|
||||
</SubPanel>
|
||||
),
|
||||
[langui]
|
||||
);
|
||||
|
||||
return (
|
||||
<AppLayout navTitle={langui.chronicles} subPanel={subPanel} {...props} />
|
||||
);
|
||||
|
|
|
@ -76,7 +76,8 @@ export default function Content(props: Props): JSX.Element {
|
|||
[content.group, content.slug]
|
||||
);
|
||||
|
||||
const subPanel = (
|
||||
const subPanel = useMemo(
|
||||
() => (
|
||||
<SubPanel>
|
||||
<ReturnButton
|
||||
href={`/contents`}
|
||||
|
@ -96,7 +97,8 @@ export default function Content(props: Props): JSX.Element {
|
|||
: langui.translation_notice}
|
||||
</h2>
|
||||
|
||||
{selectedTranslation.text_set.source_language.data.attributes.code !==
|
||||
{selectedTranslation.text_set.source_language.data.attributes
|
||||
.code !==
|
||||
selectedTranslation.language?.data?.attributes?.code && (
|
||||
<div className="grid place-items-center gap-2">
|
||||
<p className="font-headers">{langui.source_language}:</p>
|
||||
|
@ -272,8 +274,18 @@ export default function Content(props: Props): JSX.Element {
|
|||
</>
|
||||
)}
|
||||
</SubPanel>
|
||||
),
|
||||
[
|
||||
content.ranged_contents?.data,
|
||||
currencies,
|
||||
languages,
|
||||
langui,
|
||||
selectedTranslation,
|
||||
]
|
||||
);
|
||||
const contentPanel = (
|
||||
|
||||
const contentPanel = useMemo(
|
||||
() => (
|
||||
<ContentPanel>
|
||||
<ReturnButton
|
||||
href={`/contents`}
|
||||
|
@ -313,7 +325,9 @@ export default function Content(props: Props): JSX.Element {
|
|||
)}
|
||||
slug={previousContent.attributes.slug}
|
||||
languages={languages}
|
||||
thumbnail={previousContent.attributes.thumbnail?.data?.attributes}
|
||||
thumbnail={
|
||||
previousContent.attributes.thumbnail?.data?.attributes
|
||||
}
|
||||
thumbnailAspectRatio="3/2"
|
||||
topChips={
|
||||
isMobile
|
||||
|
@ -325,7 +339,8 @@ export default function Content(props: Props): JSX.Element {
|
|||
? previousContent.attributes.type.data.attributes
|
||||
.titles[0]?.title
|
||||
: prettySlug(
|
||||
previousContent.attributes.type.data.attributes.slug
|
||||
previousContent.attributes.type.data.attributes
|
||||
.slug
|
||||
),
|
||||
]
|
||||
: undefined
|
||||
|
@ -371,8 +386,8 @@ export default function Content(props: Props): JSX.Element {
|
|||
: nextContent.attributes.type?.data?.attributes
|
||||
? [
|
||||
nextContent.attributes.type.data.attributes.titles?.[0]
|
||||
? nextContent.attributes.type.data.attributes.titles[0]
|
||||
?.title
|
||||
? nextContent.attributes.type.data.attributes
|
||||
.titles[0]?.title
|
||||
: prettySlug(
|
||||
nextContent.attributes.type.data.attributes.slug
|
||||
),
|
||||
|
@ -391,6 +406,19 @@ export default function Content(props: Props): JSX.Element {
|
|||
)}
|
||||
</div>
|
||||
</ContentPanel>
|
||||
),
|
||||
[
|
||||
LanguageSwitcher,
|
||||
content.categories,
|
||||
content.thumbnail?.data?.attributes,
|
||||
content.type,
|
||||
isMobile,
|
||||
languages,
|
||||
langui,
|
||||
nextContent?.attributes,
|
||||
previousContent?.attributes,
|
||||
selectedTranslation,
|
||||
]
|
||||
);
|
||||
|
||||
return (
|
||||
|
|
|
@ -71,7 +71,8 @@ export default function Contents(props: Props): JSX.Element {
|
|||
[combineRelatedContent, searchName.length]
|
||||
);
|
||||
|
||||
const subPanel = (
|
||||
const subPanel = useMemo(
|
||||
() => (
|
||||
<SubPanel>
|
||||
<PanelHeader
|
||||
icon={Icon.Workspaces}
|
||||
|
@ -131,8 +132,19 @@ export default function Contents(props: Props): JSX.Element {
|
|||
}}
|
||||
/>
|
||||
</SubPanel>
|
||||
),
|
||||
[
|
||||
effectiveCombineRelatedContent,
|
||||
groupingMethod,
|
||||
hoverable,
|
||||
keepInfoVisible,
|
||||
langui,
|
||||
searchName,
|
||||
]
|
||||
);
|
||||
const contentPanel = (
|
||||
|
||||
const contentPanel = useMemo(
|
||||
() => (
|
||||
<ContentPanel width={ContentPanelWidthSizes.Full}>
|
||||
{/* TODO: Add to langui */}
|
||||
{groups.size === 0 && (
|
||||
|
@ -162,8 +174,8 @@ export default function Contents(props: Props): JSX.Element {
|
|||
) {
|
||||
return (
|
||||
currentSum +
|
||||
(item.attributes.group.data.attributes.contents?.data
|
||||
.length ?? 1)
|
||||
(item.attributes.group.data.attributes.contents
|
||||
?.data.length ?? 1)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -202,16 +214,16 @@ export default function Contents(props: Props): JSX.Element {
|
|||
effectiveCombineRelatedContent &&
|
||||
item.attributes.group?.data?.attributes?.combine ===
|
||||
true
|
||||
? item.attributes.group.data.attributes.contents?.data
|
||||
.length
|
||||
? item.attributes.group.data.attributes.contents
|
||||
?.data.length
|
||||
: 0
|
||||
}
|
||||
topChips={
|
||||
item.attributes.type?.data?.attributes
|
||||
? [
|
||||
item.attributes.type.data.attributes.titles?.[0]
|
||||
? item.attributes.type.data.attributes.titles[0]
|
||||
?.title
|
||||
? item.attributes.type.data.attributes
|
||||
.titles[0]?.title
|
||||
: prettySlug(
|
||||
item.attributes.type.data.attributes.slug
|
||||
),
|
||||
|
@ -230,7 +242,17 @@ export default function Contents(props: Props): JSX.Element {
|
|||
)
|
||||
)}
|
||||
</ContentPanel>
|
||||
),
|
||||
[
|
||||
effectiveCombineRelatedContent,
|
||||
groups,
|
||||
keepInfoVisible,
|
||||
languages,
|
||||
langui.result,
|
||||
langui.results,
|
||||
]
|
||||
);
|
||||
|
||||
return (
|
||||
<AppLayout
|
||||
navTitle={langui.contents}
|
||||
|
|
|
@ -12,6 +12,7 @@ import { getReadySdk } from "graphql/sdk";
|
|||
import { filterDefined, filterHasAttributes } from "helpers/others";
|
||||
|
||||
import { GetStaticPropsContext } from "next";
|
||||
import { useMemo } from "react";
|
||||
|
||||
interface Props extends AppStaticProps {
|
||||
contents: DevGetContentsQuery;
|
||||
|
@ -21,7 +22,8 @@ export default function CheckupContents(props: Props): JSX.Element {
|
|||
const { contents } = props;
|
||||
const testReport = testingContent(contents);
|
||||
|
||||
const contentPanel = (
|
||||
const contentPanel = useMemo(
|
||||
() => (
|
||||
<ContentPanel width={ContentPanelWidthSizes.Full}>
|
||||
{<h2 className="text-2xl">{testReport.title}</h2>}
|
||||
|
||||
|
@ -75,7 +77,10 @@ export default function CheckupContents(props: Props): JSX.Element {
|
|||
</div>
|
||||
))}
|
||||
</ContentPanel>
|
||||
),
|
||||
[testReport.lines, testReport.title]
|
||||
);
|
||||
|
||||
return (
|
||||
<AppLayout navTitle={"Checkup"} contentPanel={contentPanel} {...props} />
|
||||
);
|
||||
|
|
|
@ -14,6 +14,7 @@ import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
|
|||
import { getReadySdk } from "graphql/sdk";
|
||||
|
||||
import { GetStaticPropsContext } from "next";
|
||||
import { useMemo } from "react";
|
||||
|
||||
interface Props extends AppStaticProps {
|
||||
libraryItems: DevGetLibraryItemsQuery;
|
||||
|
@ -23,7 +24,8 @@ export default function CheckupLibraryItems(props: Props): JSX.Element {
|
|||
const { libraryItems } = props;
|
||||
const testReport = testingLibraryItem(libraryItems);
|
||||
|
||||
const contentPanel = (
|
||||
const contentPanel = useMemo(
|
||||
() => (
|
||||
<ContentPanel width={ContentPanelWidthSizes.Full}>
|
||||
{<h2 className="text-2xl">{testReport.title}</h2>}
|
||||
|
||||
|
@ -77,7 +79,10 @@ export default function CheckupLibraryItems(props: Props): JSX.Element {
|
|||
</div>
|
||||
))}
|
||||
</ContentPanel>
|
||||
),
|
||||
[testReport.lines, testReport.title]
|
||||
);
|
||||
|
||||
return (
|
||||
<AppLayout navTitle={"Checkup"} contentPanel={contentPanel} {...props} />
|
||||
);
|
||||
|
|
|
@ -10,7 +10,7 @@ import { ToolTip } from "components/ToolTip";
|
|||
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
|
||||
|
||||
import { GetStaticPropsContext } from "next";
|
||||
import { useCallback, useState } from "react";
|
||||
import { useCallback, useMemo, useState } from "react";
|
||||
import TurndownService from "turndown";
|
||||
import { Icon } from "components/Ico";
|
||||
import { TOC } from "components/Markdown/TOC";
|
||||
|
@ -25,11 +25,42 @@ export default function Editor(props: Props): JSX.Element {
|
|||
const [markdown, setMarkdown] = useState("");
|
||||
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,
|
||||
properties?: Record<string, string>,
|
||||
addInnerNewLines?: boolean
|
||||
) {
|
||||
) => {
|
||||
transformationWrapper((value, selectionStart, selectionEnd) => {
|
||||
let prepend = wrapper;
|
||||
let append = wrapper;
|
||||
|
@ -55,13 +86,29 @@ export default function Editor(props: Props): JSX.Element {
|
|||
newValue += value.slice(selectionEnd);
|
||||
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,
|
||||
properties?: Record<string, string>,
|
||||
addInnerNewLines?: boolean
|
||||
) {
|
||||
) => {
|
||||
const textarea =
|
||||
document.querySelector<HTMLTextAreaElement>("#editorTextArea");
|
||||
if (textarea) {
|
||||
|
@ -77,21 +124,15 @@ export default function Editor(props: Props): JSX.Element {
|
|||
wrap(wrapper, properties, addInnerNewLines);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
[unwrap, wrap]
|
||||
);
|
||||
|
||||
function unwrap(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 };
|
||||
});
|
||||
}
|
||||
|
||||
function preline(prepend: string) {
|
||||
const preline = useCallback(
|
||||
(prepend: string) => {
|
||||
transformationWrapper((value, selectionStart) => {
|
||||
const lastNewLine = value.slice(0, selectionStart).lastIndexOf("\n") + 1;
|
||||
const lastNewLine =
|
||||
value.slice(0, selectionStart).lastIndexOf("\n") + 1;
|
||||
|
||||
let newValue = "";
|
||||
newValue += value.slice(0, lastNewLine);
|
||||
|
@ -100,9 +141,12 @@ export default function Editor(props: Props): JSX.Element {
|
|||
|
||||
return { prependLength: prepend.length, transformedValue: newValue };
|
||||
});
|
||||
}
|
||||
},
|
||||
[transformationWrapper]
|
||||
);
|
||||
|
||||
function insert(prepend: string) {
|
||||
const insert = useCallback(
|
||||
(prepend: string) => {
|
||||
transformationWrapper((value, selectionStart) => {
|
||||
let newValue = "";
|
||||
newValue += value.slice(0, selectionStart);
|
||||
|
@ -111,54 +155,33 @@ export default function Editor(props: Props): JSX.Element {
|
|||
|
||||
return { prependLength: prepend.length, transformedValue: newValue };
|
||||
});
|
||||
}
|
||||
},
|
||||
[transformationWrapper]
|
||||
);
|
||||
|
||||
function appendDoc(append: string) {
|
||||
const appendDoc = useCallback(
|
||||
(append: string) => {
|
||||
transformationWrapper((value) => {
|
||||
const newValue = value + append;
|
||||
return { prependLength: 0, transformedValue: newValue };
|
||||
});
|
||||
}
|
||||
|
||||
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
|
||||
},
|
||||
[transformationWrapper]
|
||||
);
|
||||
|
||||
textarea.value = transformedValue;
|
||||
handleInput(textarea.value);
|
||||
|
||||
textarea.focus();
|
||||
textarea.selectionStart = selectionStart + prependLength;
|
||||
textarea.selectionEnd = selectionEnd + prependLength;
|
||||
}
|
||||
}
|
||||
|
||||
const contentPanel = (
|
||||
const contentPanel = useMemo(
|
||||
() => (
|
||||
<ContentPanel width={ContentPanelWidthSizes.Full}>
|
||||
<Popup setState={setConverterOpened} state={converterOpened}>
|
||||
<div className="text-center">
|
||||
<h2 className="mt-4">Convert HTML to markdown</h2>
|
||||
<p>
|
||||
Copy and paste any HTML content (content from web pages) here.{" "}
|
||||
Copy and paste any HTML content (content from web pages) here.
|
||||
<br />
|
||||
The text will immediatly be converted to valid Markdown.
|
||||
<br />
|
||||
You can then copy the converted text and paste it anywhere you want
|
||||
in the editor
|
||||
You can then copy the converted text and paste it anywhere you
|
||||
want in the editor
|
||||
</p>
|
||||
</div>
|
||||
<textarea
|
||||
|
@ -258,8 +281,8 @@ export default function Editor(props: Props): JSX.Element {
|
|||
<>
|
||||
<h3 className="text-lg">Transcripts</h3>
|
||||
<p>
|
||||
Use this to create dialogues and transcripts. Start by adding a
|
||||
container, then add transcript speech line within.
|
||||
Use this to create dialogues and transcripts. Start by adding
|
||||
a container, then add transcript speech line within.
|
||||
</p>
|
||||
<div className="grid gap-2">
|
||||
<ToolTip
|
||||
|
@ -381,7 +404,9 @@ export default function Editor(props: Props): JSX.Element {
|
|||
|
||||
<ToolTip
|
||||
placement="bottom"
|
||||
content={<h3 className="text-lg">Player’s name placeholder</h3>}
|
||||
content={
|
||||
<h3 className="text-lg">Player’s name placeholder</h3>
|
||||
}
|
||||
>
|
||||
<Button onClick={() => insert("<player>")} icon={Icon.Person} />
|
||||
</ToolTip>
|
||||
|
@ -426,7 +451,19 @@ export default function Editor(props: Props): JSX.Element {
|
|||
<TOC text={markdown} />
|
||||
</div>
|
||||
</ContentPanel>
|
||||
),
|
||||
[
|
||||
appendDoc,
|
||||
converterOpened,
|
||||
handleInput,
|
||||
insert,
|
||||
markdown,
|
||||
preline,
|
||||
toggleWrap,
|
||||
wrap,
|
||||
]
|
||||
);
|
||||
|
||||
return (
|
||||
<AppLayout
|
||||
navTitle="Markdawn Editor"
|
||||
|
|
|
@ -49,15 +49,17 @@ import {
|
|||
GetStaticPathsResult,
|
||||
GetStaticPropsContext,
|
||||
} from "next";
|
||||
import { Fragment, useState } from "react";
|
||||
import { Fragment, useMemo, useState } from "react";
|
||||
import { isUntangibleGroupItem } from "helpers/libraryItem";
|
||||
import { useMediaHoverable } from "hooks/useMediaQuery";
|
||||
import { WithLabel } from "components/Inputs/WithLabel";
|
||||
|
||||
interface Props extends AppStaticProps {
|
||||
item: NonNullable<
|
||||
NonNullable<
|
||||
GetLibraryItemQuery["libraryItems"]
|
||||
>["data"][number]["attributes"];
|
||||
>["data"][number]["attributes"]
|
||||
>;
|
||||
itemId: NonNullable<
|
||||
GetLibraryItemQuery["libraryItems"]
|
||||
>["data"][number]["id"];
|
||||
|
@ -67,29 +69,29 @@ export default function LibrarySlug(props: Props): JSX.Element {
|
|||
const { item, itemId, langui, currencies } = props;
|
||||
const appLayout = useAppLayout();
|
||||
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 [keepInfoVisible, setKeepInfoVisible] = useState(false);
|
||||
|
||||
let displayOpenScans = false;
|
||||
if (item?.contents?.data)
|
||||
for (const content of item.contents.data) {
|
||||
if (
|
||||
content.attributes?.scan_set &&
|
||||
content.attributes.scan_set.length > 0
|
||||
)
|
||||
displayOpenScans = true;
|
||||
}
|
||||
useScrollTopOnChange(AnchorIds.ContentPanel, [item]);
|
||||
|
||||
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>
|
||||
<ReturnButton
|
||||
href="/library/"
|
||||
|
@ -102,13 +104,13 @@ export default function LibrarySlug(props: Props): JSX.Element {
|
|||
<div className="grid gap-4">
|
||||
<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.details} url="#details" border />
|
||||
|
||||
{item?.subitems && item.subitems.data.length > 0 && (
|
||||
{item.subitems && item.subitems.data.length > 0 && (
|
||||
<NavOption
|
||||
title={isVariantSet ? langui.variants : langui.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 />
|
||||
)}
|
||||
</div>
|
||||
</SubPanel>
|
||||
),
|
||||
[isVariantSet, item.contents, item.gallery, item.subitems, langui]
|
||||
);
|
||||
|
||||
const contentPanel = (
|
||||
const contentPanel = useMemo(
|
||||
() => (
|
||||
<ContentPanel width={ContentPanelWidthSizes.Full}>
|
||||
<LightBox />
|
||||
|
||||
|
@ -139,7 +144,7 @@ export default function LibrarySlug(props: Props): JSX.Element {
|
|||
className="relative h-[50vh] w-full
|
||||
cursor-pointer drop-shadow-shade-xl desktop:mb-16 mobile:h-[60vh]"
|
||||
onClick={() => {
|
||||
if (item?.thumbnail?.data?.attributes) {
|
||||
if (item.thumbnail?.data?.attributes) {
|
||||
openLightBox([
|
||||
getAssetURL(
|
||||
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
|
||||
image={item.thumbnail.data.attributes}
|
||||
quality={ImageQuality.Large}
|
||||
|
@ -162,7 +167,7 @@ export default function LibrarySlug(props: Props): JSX.Element {
|
|||
|
||||
<InsetBox id="summary" className="grid place-items-center">
|
||||
<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">
|
||||
<p>{langui.subitem_of}</p>
|
||||
<Button
|
||||
|
@ -176,23 +181,25 @@ export default function LibrarySlug(props: Props): JSX.Element {
|
|||
</div>
|
||||
)}
|
||||
<div className="grid place-items-center text-center">
|
||||
<h1 className="text-3xl">{item?.title}</h1>
|
||||
{item && isDefinedAndNotEmpty(item.subtitle) && (
|
||||
<h1 className="text-3xl">{item.title}</h1>
|
||||
{isDefinedAndNotEmpty(item.subtitle) && (
|
||||
<h2 className="text-2xl">{item.subtitle}</h2>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<PreviewCardCTAs
|
||||
id={itemId}
|
||||
displayCTAs={!isUntangibleGroupItem(item?.metadata?.[0])}
|
||||
displayCTAs={!isUntangibleGroupItem(item.metadata?.[0])}
|
||||
langui={langui}
|
||||
expand
|
||||
/>
|
||||
{item?.descriptions?.[0] && (
|
||||
<p className="text-justify">{item.descriptions[0].description}</p>
|
||||
{item.descriptions?.[0] && (
|
||||
<p className="text-justify">
|
||||
{item.descriptions[0].description}
|
||||
</p>
|
||||
)}
|
||||
{!(
|
||||
item?.metadata &&
|
||||
item.metadata &&
|
||||
item.metadata[0]?.__typename === "ComponentMetadataGroup" &&
|
||||
(item.metadata[0].subtype?.data?.attributes?.slug ===
|
||||
"variant-set" ||
|
||||
|
@ -200,7 +207,7 @@ export default function LibrarySlug(props: Props): JSX.Element {
|
|||
"relation-set")
|
||||
) && (
|
||||
<>
|
||||
{item?.urls && item.urls.length ? (
|
||||
{item.urls?.length ? (
|
||||
<div className="flex flex-row place-items-center gap-3">
|
||||
<p>{langui.available_at}</p>
|
||||
{filterHasAttributes(item.urls).map((url, index) => (
|
||||
|
@ -221,7 +228,7 @@ export default function LibrarySlug(props: Props): JSX.Element {
|
|||
</div>
|
||||
</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">
|
||||
<h2 className="text-2xl">{langui.gallery}</h2>
|
||||
<div
|
||||
|
@ -238,7 +245,10 @@ export default function LibrarySlug(props: Props): JSX.Element {
|
|||
const images: string[] = filterHasAttributes(
|
||||
item.gallery?.data
|
||||
).map((image) =>
|
||||
getAssetURL(image.attributes.url, ImageQuality.Large)
|
||||
getAssetURL(
|
||||
image.attributes.url,
|
||||
ImageQuality.Large
|
||||
)
|
||||
);
|
||||
openLightBox(images, index);
|
||||
}}
|
||||
|
@ -263,7 +273,7 @@ export default function LibrarySlug(props: Props): JSX.Element {
|
|||
className="grid place-items-center gap-y-8
|
||||
desktop:grid-flow-col desktop:place-content-between"
|
||||
>
|
||||
{item?.metadata?.[0] && (
|
||||
{item.metadata?.[0] && (
|
||||
<div className="grid place-content-start place-items-center">
|
||||
<h3 className="text-xl">{langui.type}</h3>
|
||||
<div className="grid grid-flow-col gap-1">
|
||||
|
@ -274,14 +284,14 @@ export default function LibrarySlug(props: Props): JSX.Element {
|
|||
</div>
|
||||
)}
|
||||
|
||||
{item?.release_date && (
|
||||
{item.release_date && (
|
||||
<div className="grid place-content-start place-items-center">
|
||||
<h3 className="text-xl">{langui.release_date}</h3>
|
||||
<p>{prettyDate(item.release_date)}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{item?.price && (
|
||||
{item.price && (
|
||||
<div className="grid place-content-start place-items-center text-center">
|
||||
<h3 className="text-xl">{langui.price}</h3>
|
||||
<p>
|
||||
|
@ -294,7 +304,11 @@ export default function LibrarySlug(props: Props): JSX.Element {
|
|||
{item.price.currency?.data?.attributes?.code !==
|
||||
appLayout.currency && (
|
||||
<p>
|
||||
{prettyPrice(item.price, currencies, appLayout.currency)}{" "}
|
||||
{prettyPrice(
|
||||
item.price,
|
||||
currencies,
|
||||
appLayout.currency
|
||||
)}{" "}
|
||||
<br />({langui.calculated?.toLowerCase()})
|
||||
</p>
|
||||
)}
|
||||
|
@ -302,7 +316,7 @@ export default function LibrarySlug(props: Props): JSX.Element {
|
|||
)}
|
||||
</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">
|
||||
<h3 className="text-xl">{langui.categories}</h3>
|
||||
<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>
|
||||
)}
|
||||
|
||||
{item?.size && (
|
||||
{item.size && (
|
||||
<div className="grid gap-8 mobile:place-items-center">
|
||||
<h3 className="text-xl">{langui.size}</h3>
|
||||
<div
|
||||
|
@ -356,12 +370,12 @@ export default function LibrarySlug(props: Props): JSX.Element {
|
|||
</div>
|
||||
)}
|
||||
|
||||
{item?.metadata?.[0]?.__typename !== "ComponentMetadataGroup" &&
|
||||
item?.metadata?.[0]?.__typename !== "ComponentMetadataOther" && (
|
||||
{item.metadata?.[0]?.__typename !== "ComponentMetadataGroup" &&
|
||||
item.metadata?.[0]?.__typename !== "ComponentMetadataOther" && (
|
||||
<>
|
||||
<h3 className="text-xl">{langui.type_information}</h3>
|
||||
<div className="grid w-full grid-cols-2 place-content-between">
|
||||
{item?.metadata?.[0]?.__typename ===
|
||||
{item.metadata?.[0]?.__typename ===
|
||||
"ComponentMetadataBooks" && (
|
||||
<>
|
||||
<div className="flex flex-row place-content-start gap-4">
|
||||
|
@ -408,7 +422,7 @@ export default function LibrarySlug(props: Props): JSX.Element {
|
|||
</div>
|
||||
</InsetBox>
|
||||
|
||||
{item?.subitems && item.subitems.data.length > 0 && (
|
||||
{item.subitems && item.subitems.data.length > 0 && (
|
||||
<div
|
||||
id={isVariantSet ? "variants" : "subitems"}
|
||||
className="grid w-full place-items-center gap-8"
|
||||
|
@ -477,7 +491,7 @@ export default function LibrarySlug(props: Props): JSX.Element {
|
|||
</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">
|
||||
<h2 className="-mb-6 text-2xl">{langui.contents}</h2>
|
||||
{displayOpenScans && (
|
||||
|
@ -500,15 +514,29 @@ export default function LibrarySlug(props: Props): JSX.Element {
|
|||
)}
|
||||
</div>
|
||||
</ContentPanel>
|
||||
),
|
||||
[
|
||||
LightBox,
|
||||
openLightBox,
|
||||
appLayout.currency,
|
||||
currencies,
|
||||
displayOpenScans,
|
||||
hoverable,
|
||||
isVariantSet,
|
||||
item,
|
||||
itemId,
|
||||
keepInfoVisible,
|
||||
langui,
|
||||
]
|
||||
);
|
||||
|
||||
return (
|
||||
<AppLayout
|
||||
navTitle={prettyinlineTitle("", item?.title, item?.subtitle)}
|
||||
navTitle={prettyinlineTitle("", item.title, item.subtitle)}
|
||||
contentPanel={contentPanel}
|
||||
subPanel={subPanel}
|
||||
thumbnail={item?.thumbnail?.data?.attributes ?? undefined}
|
||||
description={item?.descriptions?.[0]?.description ?? undefined}
|
||||
thumbnail={item.thumbnail?.data?.attributes ?? undefined}
|
||||
description={item.descriptions?.[0]?.description ?? undefined}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
@ -526,6 +554,7 @@ export async function getStaticProps(
|
|||
language_code: context.locale ?? "en",
|
||||
});
|
||||
if (!item.libraryItems?.data[0]?.attributes) return { notFound: true };
|
||||
sortContent(item.libraryItems.data[0].attributes.contents);
|
||||
const props: Props = {
|
||||
...(await getAppStaticProps(context)),
|
||||
item: item.libraryItems.data[0].attributes,
|
||||
|
@ -544,7 +573,7 @@ export async function getStaticPaths(
|
|||
const paths: GetStaticPathsResult["paths"] = [];
|
||||
filterHasAttributes(libraryItems.libraryItems?.data).map((item) => {
|
||||
context.locales?.map((local) =>
|
||||
paths.push({ params: { slug: item.attributes?.slug }, locale: local })
|
||||
paths.push({ params: { slug: item.attributes.slug }, locale: local })
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
@ -23,33 +23,36 @@ import {
|
|||
GetStaticPathsResult,
|
||||
GetStaticPropsContext,
|
||||
} from "next";
|
||||
import { Fragment } from "react";
|
||||
import { Fragment, useMemo } from "react";
|
||||
|
||||
interface Props extends AppStaticProps {
|
||||
item: NonNullable<
|
||||
NonNullable<
|
||||
GetLibraryItemScansQuery["libraryItems"]
|
||||
>["data"][number]["attributes"];
|
||||
>["data"][number]["attributes"]
|
||||
>;
|
||||
itemId: NonNullable<
|
||||
GetLibraryItemScansQuery["libraryItems"]
|
||||
>["data"][number]["id"];
|
||||
NonNullable<GetLibraryItemScansQuery["libraryItems"]>["data"][number]["id"]
|
||||
>;
|
||||
}
|
||||
|
||||
export default function LibrarySlug(props: Props): JSX.Element {
|
||||
const { item, langui, languages } = props;
|
||||
const [openLightBox, LightBox] = useLightBox();
|
||||
sortContent(item?.contents);
|
||||
sortContent(item.contents);
|
||||
|
||||
const subPanel = (
|
||||
const subPanel = useMemo(
|
||||
() => (
|
||||
<SubPanel>
|
||||
<ReturnButton
|
||||
href={`/library/${item?.slug}`}
|
||||
href={`/library/${item.slug}`}
|
||||
title={langui.item}
|
||||
langui={langui}
|
||||
displayOn={ReturnButtonType.Desktop}
|
||||
horizontalLine
|
||||
/>
|
||||
|
||||
{item?.contents?.data.map((content) => (
|
||||
{item.contents?.data.map((content) => (
|
||||
<NavOption
|
||||
key={content.id}
|
||||
url={`#${content.attributes?.slug}`}
|
||||
|
@ -66,21 +69,24 @@ export default function LibrarySlug(props: Props): JSX.Element {
|
|||
/>
|
||||
))}
|
||||
</SubPanel>
|
||||
),
|
||||
[item.contents?.data, item.slug, langui]
|
||||
);
|
||||
|
||||
const contentPanel = (
|
||||
const contentPanel = useMemo(
|
||||
() => (
|
||||
<ContentPanel width={ContentPanelWidthSizes.Full}>
|
||||
<LightBox />
|
||||
|
||||
<ReturnButton
|
||||
href={`/library/${item?.slug}`}
|
||||
href={`/library/${item.slug}`}
|
||||
title={langui.item}
|
||||
langui={langui}
|
||||
displayOn={ReturnButtonType.Mobile}
|
||||
className="mb-10"
|
||||
/>
|
||||
|
||||
{item?.images && (
|
||||
{item.images && (
|
||||
<ScanSetCover
|
||||
images={item.images}
|
||||
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}>
|
||||
{content.attributes?.scan_set?.[0] && (
|
||||
<ScanSet
|
||||
|
@ -105,14 +111,24 @@ export default function LibrarySlug(props: Props): JSX.Element {
|
|||
</Fragment>
|
||||
))}
|
||||
</ContentPanel>
|
||||
),
|
||||
[
|
||||
LightBox,
|
||||
openLightBox,
|
||||
item.contents?.data,
|
||||
item.images,
|
||||
item.slug,
|
||||
languages,
|
||||
langui,
|
||||
]
|
||||
);
|
||||
|
||||
return (
|
||||
<AppLayout
|
||||
navTitle={prettyinlineTitle("", item?.title, item?.subtitle)}
|
||||
navTitle={prettyinlineTitle("", item.title, item.subtitle)}
|
||||
contentPanel={contentPanel}
|
||||
subPanel={subPanel}
|
||||
thumbnail={item?.thumbnail?.data?.attributes ?? undefined}
|
||||
thumbnail={item.thumbnail?.data?.attributes ?? undefined}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
@ -129,7 +145,8 @@ export async function getStaticProps(
|
|||
: "",
|
||||
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 = {
|
||||
...(await getAppStaticProps(context)),
|
||||
item: item.libraryItems.data[0].attributes,
|
||||
|
|
|
@ -114,7 +114,8 @@ export default function Library(props: Props): JSX.Element {
|
|||
[langui, groupingMethod, sortedItems]
|
||||
);
|
||||
|
||||
const subPanel = (
|
||||
const subPanel = useMemo(
|
||||
() => (
|
||||
<SubPanel>
|
||||
<PanelHeader
|
||||
icon={Icon.LibraryBooks}
|
||||
|
@ -177,7 +178,10 @@ export default function Library(props: Props): JSX.Element {
|
|||
<WithLabel
|
||||
label={langui.show_secondary_items}
|
||||
input={
|
||||
<Switch state={showSecondaryItems} setState={setShowSecondaryItems} />
|
||||
<Switch
|
||||
state={showSecondaryItems}
|
||||
setState={setShowSecondaryItems}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
|
@ -237,8 +241,23 @@ export default function Library(props: Props): JSX.Element {
|
|||
}}
|
||||
/>
|
||||
</SubPanel>
|
||||
),
|
||||
[
|
||||
filterUserStatus,
|
||||
groupingMethod,
|
||||
hoverable,
|
||||
keepInfoVisible,
|
||||
langui,
|
||||
searchName,
|
||||
showPrimaryItems,
|
||||
showSecondaryItems,
|
||||
showSubitems,
|
||||
sortingMethod,
|
||||
]
|
||||
);
|
||||
const contentPanel = (
|
||||
|
||||
const contentPanel = useMemo(
|
||||
() => (
|
||||
<ContentPanel width={ContentPanelWidthSizes.Full}>
|
||||
{/* TODO: Add to langui */}
|
||||
{groups.size === 0 && (
|
||||
|
@ -311,7 +330,10 @@ export default function Library(props: Props): JSX.Element {
|
|||
</Fragment>
|
||||
))}
|
||||
</ContentPanel>
|
||||
),
|
||||
[currencies, groups, keepInfoVisible, langui]
|
||||
);
|
||||
|
||||
return (
|
||||
<AppLayout
|
||||
navTitle={langui.library}
|
||||
|
|
|
@ -5,11 +5,13 @@ import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
|
|||
|
||||
import { GetStaticPropsContext } from "next";
|
||||
import { Icon } from "components/Ico";
|
||||
import { useMemo } from "react";
|
||||
|
||||
interface Props extends AppStaticProps {}
|
||||
export default function Merch(props: Props): JSX.Element {
|
||||
const { langui } = props;
|
||||
const subPanel = (
|
||||
const subPanel = useMemo(
|
||||
() => (
|
||||
<SubPanel>
|
||||
<PanelHeader
|
||||
icon={Icon.Store}
|
||||
|
@ -17,6 +19,8 @@ export default function Merch(props: Props): JSX.Element {
|
|||
description={langui.merch_description}
|
||||
/>
|
||||
</SubPanel>
|
||||
),
|
||||
[langui]
|
||||
);
|
||||
|
||||
return <AppLayout navTitle={langui.merch} subPanel={subPanel} {...props} />;
|
||||
|
|
|
@ -46,7 +46,8 @@ export default function News(props: Props): JSX.Element {
|
|||
[posts, searchName]
|
||||
);
|
||||
|
||||
const subPanel = (
|
||||
const subPanel = useMemo(
|
||||
() => (
|
||||
<SubPanel>
|
||||
<PanelHeader
|
||||
icon={Icon.Feed}
|
||||
|
@ -80,9 +81,12 @@ export default function News(props: Props): JSX.Element {
|
|||
}}
|
||||
/>
|
||||
</SubPanel>
|
||||
),
|
||||
[hoverable, keepInfoVisible, langui, searchName]
|
||||
);
|
||||
|
||||
const contentPanel = (
|
||||
const contentPanel = useMemo(
|
||||
() => (
|
||||
<ContentPanel width={ContentPanelWidthSizes.Full}>
|
||||
<div
|
||||
className="grid grid-cols-1 items-end gap-8
|
||||
|
@ -113,6 +117,8 @@ export default function News(props: Props): JSX.Element {
|
|||
))}
|
||||
</div>
|
||||
</ContentPanel>
|
||||
),
|
||||
[filteredItems, keepInfoVisible]
|
||||
);
|
||||
|
||||
return (
|
||||
|
|
|
@ -26,6 +26,7 @@ import {
|
|||
GetStaticPathsResult,
|
||||
GetStaticPropsContext,
|
||||
} from "next";
|
||||
import { useMemo } from "react";
|
||||
|
||||
interface Props extends AppStaticProps {
|
||||
page: WikiPageWithTranslations;
|
||||
|
@ -40,7 +41,8 @@ export default function WikiPage(props: Props): JSX.Element {
|
|||
languageExtractor: (item) => item.language?.data?.attributes?.code,
|
||||
});
|
||||
|
||||
const subPanel = (
|
||||
const subPanel = useMemo(
|
||||
() => (
|
||||
<SubPanel>
|
||||
<ReturnButton
|
||||
href={`/wiki`}
|
||||
|
@ -50,8 +52,12 @@ export default function WikiPage(props: Props): JSX.Element {
|
|||
horizontalLine
|
||||
/>
|
||||
</SubPanel>
|
||||
),
|
||||
[langui]
|
||||
);
|
||||
const contentPanel = (
|
||||
|
||||
const contentPanel = useMemo(
|
||||
() => (
|
||||
<ContentPanel width={ContentPanelWidthSizes.Large}>
|
||||
<ReturnButton
|
||||
href={`/wiki`}
|
||||
|
@ -98,13 +104,13 @@ export default function WikiPage(props: Props): JSX.Element {
|
|||
<DefinitionCard
|
||||
key={index}
|
||||
source={definition.source?.data?.attributes?.name}
|
||||
translations={filterHasAttributes(definition.translations).map(
|
||||
(translation) => ({
|
||||
translations={filterHasAttributes(
|
||||
definition.translations
|
||||
).map((translation) => ({
|
||||
language: translation.language.data?.attributes?.code,
|
||||
definition: translation.definition,
|
||||
status: translation.status,
|
||||
})
|
||||
)}
|
||||
}))}
|
||||
index={index + 1}
|
||||
languages={languages}
|
||||
langui={langui}
|
||||
|
@ -114,6 +120,16 @@ export default function WikiPage(props: Props): JSX.Element {
|
|||
</div>
|
||||
)}
|
||||
</ContentPanel>
|
||||
),
|
||||
[
|
||||
LanguageSwitcher,
|
||||
languages,
|
||||
langui,
|
||||
page.categories?.data,
|
||||
page.definitions,
|
||||
page.thumbnail?.data?.attributes,
|
||||
selectedTranslation,
|
||||
]
|
||||
);
|
||||
|
||||
return (
|
||||
|
|
|
@ -14,7 +14,7 @@ import { getReadySdk } from "graphql/sdk";
|
|||
import { prettySlug } from "helpers/formatters";
|
||||
import { filterHasAttributes, isDefined } from "helpers/others";
|
||||
import { GetStaticPropsContext } from "next";
|
||||
import { Fragment } from "react";
|
||||
import { Fragment, useMemo } from "react";
|
||||
|
||||
interface Props extends AppStaticProps {
|
||||
chronologyItems: NonNullable<
|
||||
|
@ -27,10 +27,10 @@ export default function Chronology(props: Props): JSX.Element {
|
|||
const { chronologyItems, chronologyEras, langui } = props;
|
||||
|
||||
// Group by year the Chronology items
|
||||
const chronologyItemYearGroups: Props["chronologyItems"][number][][][] = [];
|
||||
|
||||
const chronologyItemYearGroups = useMemo(() => {
|
||||
const memo: Props["chronologyItems"][number][][][] = [];
|
||||
chronologyEras.map(() => {
|
||||
chronologyItemYearGroups.push([]);
|
||||
memo.push([]);
|
||||
});
|
||||
|
||||
let currentChronologyEraIndex = 0;
|
||||
|
@ -45,22 +45,21 @@ export default function Chronology(props: Props): JSX.Element {
|
|||
}
|
||||
if (
|
||||
Object.prototype.hasOwnProperty.call(
|
||||
chronologyItemYearGroups[currentChronologyEraIndex],
|
||||
memo[currentChronologyEraIndex],
|
||||
item.attributes.year
|
||||
)
|
||||
) {
|
||||
chronologyItemYearGroups[currentChronologyEraIndex][
|
||||
item.attributes.year
|
||||
].push(item);
|
||||
memo[currentChronologyEraIndex][item.attributes.year].push(item);
|
||||
} else {
|
||||
chronologyItemYearGroups[currentChronologyEraIndex][
|
||||
item.attributes.year
|
||||
] = [item];
|
||||
memo[currentChronologyEraIndex][item.attributes.year] = [item];
|
||||
}
|
||||
}
|
||||
});
|
||||
return memo;
|
||||
}, [chronologyEras, chronologyItems]);
|
||||
|
||||
const subPanel = (
|
||||
const subPanel = useMemo(
|
||||
() => (
|
||||
<SubPanel>
|
||||
<ReturnButton
|
||||
href="/wiki"
|
||||
|
@ -87,9 +86,12 @@ export default function Chronology(props: Props): JSX.Element {
|
|||
</Fragment>
|
||||
))}
|
||||
</SubPanel>
|
||||
),
|
||||
[chronologyEras, langui]
|
||||
);
|
||||
|
||||
const contentPanel = (
|
||||
const contentPanel = useMemo(
|
||||
() => (
|
||||
<ContentPanel>
|
||||
<ReturnButton
|
||||
href="/wiki"
|
||||
|
@ -130,6 +132,8 @@ export default function Chronology(props: Props): JSX.Element {
|
|||
</Fragment>
|
||||
))}
|
||||
</ContentPanel>
|
||||
),
|
||||
[chronologyEras, chronologyItemYearGroups, langui]
|
||||
);
|
||||
|
||||
return (
|
||||
|
|
|
@ -47,7 +47,8 @@ export default function Wiki(props: Props): JSX.Element {
|
|||
[pages, searchName]
|
||||
);
|
||||
|
||||
const subPanel = (
|
||||
const subPanel = useMemo(
|
||||
() => (
|
||||
<SubPanel>
|
||||
<PanelHeader
|
||||
icon={Icon.TravelExplore}
|
||||
|
@ -87,9 +88,12 @@ export default function Wiki(props: Props): JSX.Element {
|
|||
|
||||
<NavOption title={langui.chronology} url="/wiki/chronology" border />
|
||||
</SubPanel>
|
||||
),
|
||||
[hoverable, keepInfoVisible, langui, searchName]
|
||||
);
|
||||
|
||||
const contentPanel = (
|
||||
const contentPanel = useMemo(
|
||||
() => (
|
||||
<ContentPanel width={ContentPanelWidthSizes.Full}>
|
||||
<div
|
||||
className="grid grid-cols-2 items-end gap-8
|
||||
|
@ -130,6 +134,8 @@ export default function Wiki(props: Props): JSX.Element {
|
|||
))}
|
||||
</div>
|
||||
</ContentPanel>
|
||||
),
|
||||
[filteredPages, keepInfoVisible, languages]
|
||||
);
|
||||
|
||||
return (
|
||||
|
|
Loading…
Reference in New Issue