Continued using hooks
This commit is contained in:
parent
efcf01e8a0
commit
d0b91f9db6
|
@ -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);
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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(() => {
|
||||||
if (selectedScan?.obi_belt?.full?.data?.attributes)
|
const memo: UploadImageFragment[] = [];
|
||||||
coverImages.push(selectedScan.obi_belt.full.data.attributes);
|
if (selectedScan?.obi_belt?.full?.data?.attributes)
|
||||||
if (selectedScan?.obi_belt?.inside_full?.data?.attributes)
|
memo.push(selectedScan.obi_belt.full.data.attributes);
|
||||||
coverImages.push(selectedScan.obi_belt.inside_full.data.attributes);
|
if (selectedScan?.obi_belt?.inside_full?.data?.attributes)
|
||||||
if (selectedScan?.dust_jacket?.full?.data?.attributes)
|
memo.push(selectedScan.obi_belt.inside_full.data.attributes);
|
||||||
coverImages.push(selectedScan.dust_jacket.full.data.attributes);
|
if (selectedScan?.dust_jacket?.full?.data?.attributes)
|
||||||
if (selectedScan?.dust_jacket?.inside_full?.data?.attributes)
|
memo.push(selectedScan.dust_jacket.full.data.attributes);
|
||||||
coverImages.push(selectedScan.dust_jacket.inside_full.data.attributes);
|
if (selectedScan?.dust_jacket?.inside_full?.data?.attributes)
|
||||||
if (selectedScan?.cover?.full?.data?.attributes)
|
memo.push(selectedScan.dust_jacket.inside_full.data.attributes);
|
||||||
coverImages.push(selectedScan.cover.full.data.attributes);
|
if (selectedScan?.cover?.full?.data?.attributes)
|
||||||
if (selectedScan?.cover?.inside_full?.data?.attributes)
|
memo.push(selectedScan.cover.full.data.attributes);
|
||||||
coverImages.push(selectedScan.cover.inside_full.data.attributes);
|
if (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 (
|
||||||
|
|
|
@ -67,105 +67,136 @@ 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 ? (
|
() =>
|
||||||
<SubPanel>
|
returnHref || returnTitle || displayCredits || displayToc ? (
|
||||||
|
<SubPanel>
|
||||||
|
{returnHref && returnTitle && (
|
||||||
|
<ReturnButton
|
||||||
|
href={returnHref}
|
||||||
|
title={returnTitle}
|
||||||
|
langui={langui}
|
||||||
|
displayOn={ReturnButtonType.Desktop}
|
||||||
|
horizontalLine
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{displayCredits && (
|
||||||
|
<>
|
||||||
|
{selectedTranslation && (
|
||||||
|
<div className="grid grid-flow-col place-content-center place-items-center gap-2">
|
||||||
|
<p className="font-headers">{langui.status}:</p>
|
||||||
|
|
||||||
|
<ToolTip
|
||||||
|
content={getStatusDescription(
|
||||||
|
selectedTranslation.status,
|
||||||
|
langui
|
||||||
|
)}
|
||||||
|
maxWidth={"20rem"}
|
||||||
|
>
|
||||||
|
<Chip>{selectedTranslation.status}</Chip>
|
||||||
|
</ToolTip>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{post.authors && post.authors.data.length > 0 && (
|
||||||
|
<div>
|
||||||
|
<p className="font-headers">{"Authors"}:</p>
|
||||||
|
<div className="grid place-content-center place-items-center gap-2">
|
||||||
|
{filterHasAttributes(post.authors.data).map((author) => (
|
||||||
|
<Fragment key={author.id}>
|
||||||
|
<RecorderChip
|
||||||
|
langui={langui}
|
||||||
|
recorder={author.attributes}
|
||||||
|
/>
|
||||||
|
</Fragment>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<HorizontalLine />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{displayToc && <TOC text={body} title={title} />}
|
||||||
|
</SubPanel>
|
||||||
|
) : undefined,
|
||||||
|
[
|
||||||
|
body,
|
||||||
|
displayCredits,
|
||||||
|
displayToc,
|
||||||
|
langui,
|
||||||
|
post.authors,
|
||||||
|
returnHref,
|
||||||
|
returnTitle,
|
||||||
|
selectedTranslation,
|
||||||
|
title,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
const contentPanel = useMemo(
|
||||||
|
() => (
|
||||||
|
<ContentPanel>
|
||||||
{returnHref && returnTitle && (
|
{returnHref && returnTitle && (
|
||||||
<ReturnButton
|
<ReturnButton
|
||||||
href={returnHref}
|
href={returnHref}
|
||||||
title={returnTitle}
|
title={returnTitle}
|
||||||
langui={langui}
|
langui={langui}
|
||||||
displayOn={ReturnButtonType.Desktop}
|
displayOn={ReturnButtonType.Mobile}
|
||||||
horizontalLine
|
horizontalLine
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{displayCredits && (
|
{displayThumbnailHeader ? (
|
||||||
<>
|
<>
|
||||||
{selectedTranslation && (
|
<ThumbnailHeader
|
||||||
<div className="grid grid-flow-col place-content-center place-items-center gap-2">
|
thumbnail={thumbnail}
|
||||||
<p className="font-headers">{langui.status}:</p>
|
title={title}
|
||||||
|
description={excerpt}
|
||||||
<ToolTip
|
langui={langui}
|
||||||
content={getStatusDescription(
|
categories={post.categories}
|
||||||
selectedTranslation.status,
|
languageSwitcher={<LanguageSwitcher />}
|
||||||
langui
|
/>
|
||||||
)}
|
|
||||||
maxWidth={"20rem"}
|
|
||||||
>
|
|
||||||
<Chip>{selectedTranslation.status}</Chip>
|
|
||||||
</ToolTip>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{post.authors && post.authors.data.length > 0 && (
|
|
||||||
<div>
|
|
||||||
<p className="font-headers">{"Authors"}:</p>
|
|
||||||
<div className="grid place-content-center place-items-center gap-2">
|
|
||||||
{filterHasAttributes(post.authors.data).map((author) => (
|
|
||||||
<Fragment key={author.id}>
|
|
||||||
<RecorderChip
|
|
||||||
langui={langui}
|
|
||||||
recorder={author.attributes}
|
|
||||||
/>
|
|
||||||
</Fragment>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<HorizontalLine />
|
<HorizontalLine />
|
||||||
</>
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{displayLanguageSwitcher && (
|
||||||
|
<div className="grid place-content-end place-items-start">
|
||||||
|
<LanguageSwitcher />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{displayTitle && (
|
||||||
|
<h1 className="my-16 flex justify-center gap-3 text-center text-4xl">
|
||||||
|
{title}
|
||||||
|
</h1>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{displayToc && <TOC text={body} title={title} />}
|
{prependBody}
|
||||||
</SubPanel>
|
<Markdawn text={body} />
|
||||||
) : undefined;
|
{appendBody}
|
||||||
|
</ContentPanel>
|
||||||
const contentPanel = (
|
),
|
||||||
<ContentPanel>
|
[
|
||||||
{returnHref && returnTitle && (
|
LanguageSwitcher,
|
||||||
<ReturnButton
|
appendBody,
|
||||||
href={returnHref}
|
body,
|
||||||
title={returnTitle}
|
displayLanguageSwitcher,
|
||||||
langui={langui}
|
displayThumbnailHeader,
|
||||||
displayOn={ReturnButtonType.Mobile}
|
displayTitle,
|
||||||
horizontalLine
|
excerpt,
|
||||||
/>
|
langui,
|
||||||
)}
|
post.categories,
|
||||||
|
prependBody,
|
||||||
{displayThumbnailHeader ? (
|
returnHref,
|
||||||
<>
|
returnTitle,
|
||||||
<ThumbnailHeader
|
thumbnail,
|
||||||
thumbnail={thumbnail}
|
title,
|
||||||
title={title}
|
]
|
||||||
description={excerpt}
|
|
||||||
langui={langui}
|
|
||||||
categories={post.categories}
|
|
||||||
languageSwitcher={<LanguageSwitcher />}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<HorizontalLine />
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
{displayLanguageSwitcher && (
|
|
||||||
<div className="grid place-content-end place-items-start">
|
|
||||||
<LanguageSwitcher />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{displayTitle && (
|
|
||||||
<h1 className="my-16 flex justify-center gap-3 text-center text-4xl">
|
|
||||||
{title}
|
|
||||||
</h1>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{prependBody}
|
|
||||||
<Markdawn text={body} />
|
|
||||||
{appendBody}
|
|
||||||
</ContentPanel>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -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-['-']`
|
||||||
|
|
|
@ -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) => {
|
||||||
|
|
|
@ -19,23 +19,18 @@ type SortContentProps =
|
||||||
>["contents"];
|
>["contents"];
|
||||||
|
|
||||||
export function sortContent(contents: SortContentProps) {
|
export function sortContent(contents: SortContentProps) {
|
||||||
if (contents) {
|
contents?.data.sort((a, b) => {
|
||||||
const newContent = { ...contents };
|
if (
|
||||||
newContent?.data.sort((a, b) => {
|
a.attributes?.range[0]?.__typename === "ComponentRangePageRange" &&
|
||||||
if (
|
b.attributes?.range[0]?.__typename === "ComponentRangePageRange"
|
||||||
a.attributes?.range[0]?.__typename === "ComponentRangePageRange" &&
|
) {
|
||||||
b.attributes?.range[0]?.__typename === "ComponentRangePageRange"
|
return (
|
||||||
) {
|
a.attributes.range[0].starting_page -
|
||||||
return (
|
b.attributes.range[0].starting_page
|
||||||
a.attributes.range[0].starting_page -
|
);
|
||||||
b.attributes.range[0].starting_page
|
}
|
||||||
);
|
return 0;
|
||||||
}
|
});
|
||||||
return 0;
|
|
||||||
});
|
|
||||||
return newContent;
|
|
||||||
}
|
|
||||||
return contents;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getStatusDescription(
|
export function getStatusDescription(
|
||||||
|
|
|
@ -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<
|
||||||
|
|
|
@ -7,21 +7,25 @@ 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>
|
() => (
|
||||||
<h1>404 - {langui.page_not_found}</h1>
|
<ContentPanel>
|
||||||
<ReturnButton
|
<h1>404 - {langui.page_not_found}</h1>
|
||||||
href="/"
|
<ReturnButton
|
||||||
title="Home"
|
href="/"
|
||||||
langui={langui}
|
title="Home"
|
||||||
displayOn={ReturnButtonType.Both}
|
langui={langui}
|
||||||
/>
|
displayOn={ReturnButtonType.Both}
|
||||||
</ContentPanel>
|
/>
|
||||||
|
</ContentPanel>
|
||||||
|
),
|
||||||
|
[langui]
|
||||||
);
|
);
|
||||||
return <AppLayout navTitle="404" contentPanel={contentPanel} {...props} />;
|
return <AppLayout navTitle="404" contentPanel={contentPanel} {...props} />;
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,21 +7,25 @@ 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>
|
() => (
|
||||||
<h1>500 - Internal Server Error</h1>
|
<ContentPanel>
|
||||||
<ReturnButton
|
<h1>500 - Internal Server Error</h1>
|
||||||
href="/"
|
<ReturnButton
|
||||||
title="Home"
|
href="/"
|
||||||
langui={langui}
|
title="Home"
|
||||||
displayOn={ReturnButtonType.Both}
|
langui={langui}
|
||||||
/>
|
displayOn={ReturnButtonType.Both}
|
||||||
</ContentPanel>
|
/>
|
||||||
|
</ContentPanel>
|
||||||
|
),
|
||||||
|
[langui]
|
||||||
);
|
);
|
||||||
return <AppLayout navTitle="500" contentPanel={contentPanel} {...props} />;
|
return <AppLayout navTitle="500" contentPanel={contentPanel} {...props} />;
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,32 +6,35 @@ 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>
|
() => (
|
||||||
<PanelHeader
|
<SubPanel>
|
||||||
icon={Icon.Info}
|
<PanelHeader
|
||||||
title={langui.about_us}
|
icon={Icon.Info}
|
||||||
description={langui.about_us_description}
|
title={langui.about_us}
|
||||||
/>
|
description={langui.about_us_description}
|
||||||
<NavOption
|
/>
|
||||||
title={langui.accords_handbook}
|
<NavOption
|
||||||
url="/about-us/accords-handbook"
|
title={langui.accords_handbook}
|
||||||
border
|
url="/about-us/accords-handbook"
|
||||||
/>
|
border
|
||||||
<NavOption title={langui.legality} url="/about-us/legality" border />
|
/>
|
||||||
{/* <NavOption title={langui.members} url="/about-us/members" border /> */}
|
<NavOption title={langui.legality} url="/about-us/legality" border />
|
||||||
<NavOption
|
<NavOption
|
||||||
title={langui.sharing_policy}
|
title={langui.sharing_policy}
|
||||||
url="/about-us/sharing-policy"
|
url="/about-us/sharing-policy"
|
||||||
border
|
border
|
||||||
/>
|
/>
|
||||||
<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} />
|
||||||
|
|
|
@ -6,20 +6,24 @@ 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>
|
() => (
|
||||||
<PanelHeader
|
<SubPanel>
|
||||||
icon={Icon.Inventory}
|
<PanelHeader
|
||||||
title={langui.archives}
|
icon={Icon.Inventory}
|
||||||
description={langui.archives_description}
|
title={langui.archives}
|
||||||
/>
|
description={langui.archives_description}
|
||||||
<NavOption title={"Videos"} url="/archives/videos/" border />
|
/>
|
||||||
</SubPanel>
|
<NavOption title={"Videos"} url="/archives/videos/" border />
|
||||||
|
</SubPanel>
|
||||||
|
),
|
||||||
|
[langui]
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<AppLayout navTitle={langui.archives} subPanel={subPanel} {...props} />
|
<AppLayout navTitle={langui.archives} subPanel={subPanel} {...props} />
|
||||||
|
|
|
@ -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,67 +37,79 @@ 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>
|
() => (
|
||||||
<ReturnButton
|
<SubPanel>
|
||||||
href="/archives/videos/"
|
<ReturnButton
|
||||||
title={langui.videos}
|
href="/archives/videos/"
|
||||||
langui={langui}
|
title={langui.videos}
|
||||||
displayOn={ReturnButtonType.Desktop}
|
langui={langui}
|
||||||
className="mb-10"
|
displayOn={ReturnButtonType.Desktop}
|
||||||
/>
|
className="mb-10"
|
||||||
|
|
||||||
<PanelHeader
|
|
||||||
icon={Icon.Movie}
|
|
||||||
title={langui.videos}
|
|
||||||
description={langui.archives_description}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{hoverable && (
|
|
||||||
<WithLabel
|
|
||||||
label={langui.always_show_info}
|
|
||||||
input={
|
|
||||||
<Switch setState={setKeepInfoVisible} state={keepInfoVisible} />
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
</SubPanel>
|
<PanelHeader
|
||||||
|
icon={Icon.Movie}
|
||||||
|
title={langui.videos}
|
||||||
|
description={langui.archives_description}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{hoverable && (
|
||||||
|
<WithLabel
|
||||||
|
label={langui.always_show_info}
|
||||||
|
input={
|
||||||
|
<Switch setState={setKeepInfoVisible} state={keepInfoVisible} />
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</SubPanel>
|
||||||
|
),
|
||||||
|
[hoverable, keepInfoVisible, langui]
|
||||||
);
|
);
|
||||||
|
|
||||||
const contentPanel = (
|
const contentPanel = useMemo(
|
||||||
<ContentPanel width={ContentPanelWidthSizes.Full}>
|
() => (
|
||||||
<div className="mb-8">
|
<ContentPanel width={ContentPanelWidthSizes.Full}>
|
||||||
<h1 className="text-3xl">{channel?.title}</h1>
|
<div className="mb-8">
|
||||||
<p>{channel?.subscribers.toLocaleString()} subscribers</p>
|
<h1 className="text-3xl">{channel?.title}</h1>
|
||||||
</div>
|
<p>{channel?.subscribers.toLocaleString()} subscribers</p>
|
||||||
<div
|
</div>
|
||||||
className="grid items-start gap-8 border-b-[3px] border-dotted pb-12 last-of-type:border-0
|
<div
|
||||||
|
className="grid items-start gap-8 border-b-[3px] border-dotted pb-12 last-of-type:border-0
|
||||||
desktop:grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))] mobile:grid-cols-2"
|
desktop:grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))] mobile:grid-cols-2"
|
||||||
>
|
>
|
||||||
{filterHasAttributes(channel?.videos?.data).map((video) => (
|
{filterHasAttributes(channel?.videos?.data).map((video) => (
|
||||||
<Fragment key={video.id}>
|
<Fragment key={video.id}>
|
||||||
<PreviewCard
|
<PreviewCard
|
||||||
href={`/archives/videos/v/${video.attributes.uid}`}
|
href={`/archives/videos/v/${video.attributes.uid}`}
|
||||||
title={video.attributes.title}
|
title={video.attributes.title}
|
||||||
thumbnail={getVideoThumbnailURL(video.attributes.uid)}
|
thumbnail={getVideoThumbnailURL(video.attributes.uid)}
|
||||||
thumbnailAspectRatio="16/9"
|
thumbnailAspectRatio="16/9"
|
||||||
keepInfoVisible={keepInfoVisible}
|
keepInfoVisible={keepInfoVisible}
|
||||||
metadata={{
|
metadata={{
|
||||||
release_date: video.attributes.published_date,
|
release_date: video.attributes.published_date,
|
||||||
views: video.attributes.views,
|
views: video.attributes.views,
|
||||||
author: channel?.title,
|
author: channel?.title,
|
||||||
position: "Top",
|
position: "Top",
|
||||||
}}
|
}}
|
||||||
hoverlay={{
|
hoverlay={{
|
||||||
__typename: "Video",
|
__typename: "Video",
|
||||||
duration: video.attributes.duration,
|
duration: video.attributes.duration,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</ContentPanel>
|
</ContentPanel>
|
||||||
|
),
|
||||||
|
[
|
||||||
|
channel?.subscribers,
|
||||||
|
channel?.title,
|
||||||
|
channel?.videos?.data,
|
||||||
|
keepInfoVisible,
|
||||||
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppLayout
|
<AppLayout
|
||||||
navTitle={langui.archives}
|
navTitle={langui.archives}
|
||||||
|
|
|
@ -22,110 +22,108 @@ 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 memo;
|
||||||
return dateA.localeCompare(dateB);
|
}, [videos]);
|
||||||
})
|
|
||||||
.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 [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>
|
() => (
|
||||||
<ReturnButton
|
<SubPanel>
|
||||||
href="/archives/"
|
<ReturnButton
|
||||||
title={"Archives"}
|
href="/archives/"
|
||||||
langui={langui}
|
title={"Archives"}
|
||||||
displayOn={ReturnButtonType.Desktop}
|
langui={langui}
|
||||||
className="mb-10"
|
displayOn={ReturnButtonType.Desktop}
|
||||||
/>
|
className="mb-10"
|
||||||
|
|
||||||
<PanelHeader
|
|
||||||
icon={Icon.Movie}
|
|
||||||
title="Videos"
|
|
||||||
description={langui.archives_description}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{hoverable && (
|
|
||||||
<WithLabel
|
|
||||||
label={langui.always_show_info}
|
|
||||||
input={
|
|
||||||
<Switch setState={setKeepInfoVisible} state={keepInfoVisible} />
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
</SubPanel>
|
<PanelHeader
|
||||||
|
icon={Icon.Movie}
|
||||||
|
title="Videos"
|
||||||
|
description={langui.archives_description}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{hoverable && (
|
||||||
|
<WithLabel
|
||||||
|
label={langui.always_show_info}
|
||||||
|
input={
|
||||||
|
<Switch setState={setKeepInfoVisible} state={keepInfoVisible} />
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</SubPanel>
|
||||||
|
),
|
||||||
|
[hoverable, keepInfoVisible, langui]
|
||||||
);
|
);
|
||||||
|
|
||||||
const contentPanel = (
|
const contentPanel = useMemo(
|
||||||
<ContentPanel width={ContentPanelWidthSizes.Full}>
|
() => (
|
||||||
<PageSelector
|
<ContentPanel width={ContentPanelWidthSizes.Full}>
|
||||||
maxPage={Math.floor(videos.length / itemPerPage)}
|
<PageSelector
|
||||||
page={page}
|
maxPage={Math.floor(videos.length / ITEM_PER_PAGE)}
|
||||||
setPage={setPage}
|
page={page}
|
||||||
className="mb-12"
|
setPage={setPage}
|
||||||
/>
|
className="mb-12"
|
||||||
|
/>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className="grid items-start gap-8 border-b-[3px] border-dotted pb-12 last-of-type:border-0
|
className="grid items-start gap-8 border-b-[3px] border-dotted pb-12 last-of-type:border-0
|
||||||
desktop:grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))] mobile:grid-cols-2
|
desktop:grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))] mobile:grid-cols-2
|
||||||
thin:grid-cols-1"
|
thin:grid-cols-1"
|
||||||
>
|
>
|
||||||
{filterHasAttributes(paginatedVideos[page]).map((video) => (
|
{filterHasAttributes(paginatedVideos[page]).map((video) => (
|
||||||
<Fragment key={video.id}>
|
<Fragment key={video.id}>
|
||||||
<PreviewCard
|
<PreviewCard
|
||||||
href={`/archives/videos/v/${video.attributes.uid}`}
|
href={`/archives/videos/v/${video.attributes.uid}`}
|
||||||
title={video.attributes.title}
|
title={video.attributes.title}
|
||||||
thumbnail={getVideoThumbnailURL(video.attributes.uid)}
|
thumbnail={getVideoThumbnailURL(video.attributes.uid)}
|
||||||
thumbnailAspectRatio="16/9"
|
thumbnailAspectRatio="16/9"
|
||||||
keepInfoVisible={keepInfoVisible}
|
keepInfoVisible={keepInfoVisible}
|
||||||
metadata={{
|
metadata={{
|
||||||
release_date: video.attributes.published_date,
|
release_date: video.attributes.published_date,
|
||||||
views: video.attributes.views,
|
views: video.attributes.views,
|
||||||
author: video.attributes.channel?.data?.attributes?.title,
|
author: video.attributes.channel?.data?.attributes?.title,
|
||||||
position: "Top",
|
position: "Top",
|
||||||
}}
|
}}
|
||||||
hoverlay={{
|
hoverlay={{
|
||||||
__typename: "Video",
|
__typename: "Video",
|
||||||
duration: video.attributes.duration,
|
duration: video.attributes.duration,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
))}
|
))}
|
||||||
</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,
|
||||||
|
|
|
@ -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,145 +38,164 @@ 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>
|
() => (
|
||||||
<ReturnButton
|
<SubPanel>
|
||||||
href="/archives/videos/"
|
<ReturnButton
|
||||||
title={langui.videos}
|
href="/archives/videos/"
|
||||||
langui={langui}
|
title={langui.videos}
|
||||||
displayOn={ReturnButtonType.Desktop}
|
langui={langui}
|
||||||
className="mb-10"
|
displayOn={ReturnButtonType.Desktop}
|
||||||
/>
|
className="mb-10"
|
||||||
|
/>
|
||||||
|
|
||||||
<HorizontalLine />
|
<HorizontalLine />
|
||||||
|
|
||||||
<NavOption
|
<NavOption
|
||||||
title={langui.video}
|
title={langui.video}
|
||||||
url="#video"
|
url="#video"
|
||||||
border
|
border
|
||||||
onClick={() => appLayout.setSubPanelOpen(false)}
|
onClick={() => appLayout.setSubPanelOpen(false)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<NavOption
|
<NavOption
|
||||||
title={langui.channel}
|
title={langui.channel}
|
||||||
url="#channel"
|
url="#channel"
|
||||||
border
|
border
|
||||||
onClick={() => appLayout.setSubPanelOpen(false)}
|
onClick={() => appLayout.setSubPanelOpen(false)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<NavOption
|
<NavOption
|
||||||
title={langui.description}
|
title={langui.description}
|
||||||
url="#description"
|
url="#description"
|
||||||
border
|
border
|
||||||
onClick={() => appLayout.setSubPanelOpen(false)}
|
onClick={() => appLayout.setSubPanelOpen(false)}
|
||||||
/>
|
/>
|
||||||
</SubPanel>
|
</SubPanel>
|
||||||
|
),
|
||||||
|
[appLayout, langui]
|
||||||
);
|
);
|
||||||
|
|
||||||
const contentPanel = (
|
const contentPanel = useMemo(
|
||||||
<ContentPanel width={ContentPanelWidthSizes.Full}>
|
() => (
|
||||||
<ReturnButton
|
<ContentPanel width={ContentPanelWidthSizes.Full}>
|
||||||
href="/library/"
|
<ReturnButton
|
||||||
title={langui.library}
|
href="/library/"
|
||||||
langui={langui}
|
title={langui.library}
|
||||||
displayOn={ReturnButtonType.Mobile}
|
langui={langui}
|
||||||
className="mb-10"
|
displayOn={ReturnButtonType.Mobile}
|
||||||
/>
|
className="mb-10"
|
||||||
|
/>
|
||||||
|
|
||||||
<div className="grid place-items-center gap-12">
|
<div className="grid place-items-center gap-12">
|
||||||
<div
|
<div
|
||||||
id="video"
|
id="video"
|
||||||
className="w-full overflow-hidden rounded-xl shadow-lg shadow-shade"
|
className="w-full overflow-hidden rounded-xl shadow-lg shadow-shade"
|
||||||
>
|
>
|
||||||
{video.gone ? (
|
{video.gone ? (
|
||||||
<video
|
<video
|
||||||
className="w-full"
|
className="w-full"
|
||||||
src={getVideoFile(video.uid)}
|
src={getVideoFile(video.uid)}
|
||||||
controls
|
controls
|
||||||
></video>
|
></video>
|
||||||
) : (
|
) : (
|
||||||
<iframe
|
<iframe
|
||||||
src={`https://www.youtube-nocookie.com/embed/${video.uid}`}
|
src={`https://www.youtube-nocookie.com/embed/${video.uid}`}
|
||||||
className="aspect-video w-full"
|
className="aspect-video w-full"
|
||||||
title="YouTube video player"
|
title="YouTube video player"
|
||||||
frameBorder="0"
|
frameBorder="0"
|
||||||
allow="accelerometer; autoplay; clipboard-write;
|
allow="accelerometer; autoplay; clipboard-write;
|
||||||
encrypted-media; gyroscope; picture-in-picture"
|
encrypted-media; gyroscope; picture-in-picture"
|
||||||
allowFullScreen
|
allowFullScreen
|
||||||
></iframe>
|
></iframe>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="mt-2 p-6">
|
<div className="mt-2 p-6">
|
||||||
<h1 className="text-2xl">{video.title}</h1>
|
<h1 className="text-2xl">{video.title}</h1>
|
||||||
<div className="flex w-full flex-row flex-wrap gap-x-6">
|
<div className="flex w-full flex-row flex-wrap gap-x-6">
|
||||||
<p>
|
|
||||||
<Ico
|
|
||||||
icon={Icon.Event}
|
|
||||||
className="mr-1 translate-y-[.15em] !text-base"
|
|
||||||
/>
|
|
||||||
{prettyDate(video.published_date)}
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<Ico
|
|
||||||
icon={Icon.Visibility}
|
|
||||||
className="mr-1 translate-y-[.15em] !text-base"
|
|
||||||
/>
|
|
||||||
{isMobile
|
|
||||||
? prettyShortenNumber(video.views)
|
|
||||||
: video.views.toLocaleString()}
|
|
||||||
</p>
|
|
||||||
{video.channel?.data?.attributes && (
|
|
||||||
<p>
|
<p>
|
||||||
<Ico
|
<Ico
|
||||||
icon={Icon.ThumbUp}
|
icon={Icon.Event}
|
||||||
|
className="mr-1 translate-y-[.15em] !text-base"
|
||||||
|
/>
|
||||||
|
{prettyDate(video.published_date)}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<Ico
|
||||||
|
icon={Icon.Visibility}
|
||||||
className="mr-1 translate-y-[.15em] !text-base"
|
className="mr-1 translate-y-[.15em] !text-base"
|
||||||
/>
|
/>
|
||||||
{isMobile
|
{isMobile
|
||||||
? prettyShortenNumber(video.likes)
|
? prettyShortenNumber(video.views)
|
||||||
: video.likes.toLocaleString()}
|
: video.views.toLocaleString()}
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
<a
|
|
||||||
href={`https://youtu.be/${video.uid}`}
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
className="!py-0 !px-3"
|
|
||||||
text={`${langui.view_on} ${video.source}`}
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{video.channel?.data?.attributes && (
|
|
||||||
<InsetBox id="channel" className="grid place-items-center">
|
|
||||||
<div className="grid w-[clamp(0px,100%,42rem)] place-items-center gap-4 text-center">
|
|
||||||
<h2 className="text-2xl">{langui.channel}</h2>
|
|
||||||
<div>
|
|
||||||
<Button
|
|
||||||
href={`/archives/videos/c/${video.channel.data.attributes.uid}`}
|
|
||||||
text={video.channel.data.attributes.title}
|
|
||||||
/>
|
|
||||||
<p>
|
|
||||||
{`${video.channel.data.attributes.subscribers.toLocaleString()}
|
|
||||||
${langui.subscribers?.toLowerCase()}`}
|
|
||||||
</p>
|
</p>
|
||||||
|
{video.channel?.data?.attributes && (
|
||||||
|
<p>
|
||||||
|
<Ico
|
||||||
|
icon={Icon.ThumbUp}
|
||||||
|
className="mr-1 translate-y-[.15em] !text-base"
|
||||||
|
/>
|
||||||
|
{isMobile
|
||||||
|
? prettyShortenNumber(video.likes)
|
||||||
|
: video.likes.toLocaleString()}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
<a
|
||||||
|
href={`https://youtu.be/${video.uid}`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
className="!py-0 !px-3"
|
||||||
|
text={`${langui.view_on} ${video.source}`}
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</InsetBox>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<InsetBox id="description" className="grid place-items-center">
|
|
||||||
<div className="grid w-[clamp(0px,100%,42rem)] place-items-center gap-8">
|
|
||||||
<h2 className="text-2xl">{langui.description}</h2>
|
|
||||||
<p className="whitespace-pre-line">{video.description}</p>
|
|
||||||
</div>
|
</div>
|
||||||
</InsetBox>
|
|
||||||
</div>
|
{video.channel?.data?.attributes && (
|
||||||
</ContentPanel>
|
<InsetBox id="channel" className="grid place-items-center">
|
||||||
|
<div className="grid w-[clamp(0px,100%,42rem)] place-items-center gap-4 text-center">
|
||||||
|
<h2 className="text-2xl">{langui.channel}</h2>
|
||||||
|
<div>
|
||||||
|
<Button
|
||||||
|
href={`/archives/videos/c/${video.channel.data.attributes.uid}`}
|
||||||
|
text={video.channel.data.attributes.title}
|
||||||
|
/>
|
||||||
|
<p>
|
||||||
|
{`${video.channel.data.attributes.subscribers.toLocaleString()}
|
||||||
|
${langui.subscribers?.toLowerCase()}`}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</InsetBox>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<InsetBox id="description" className="grid place-items-center">
|
||||||
|
<div className="grid w-[clamp(0px,100%,42rem)] place-items-center gap-8">
|
||||||
|
<h2 className="text-2xl">{langui.description}</h2>
|
||||||
|
<p className="whitespace-pre-line">{video.description}</p>
|
||||||
|
</div>
|
||||||
|
</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 (
|
return (
|
||||||
<AppLayout
|
<AppLayout
|
||||||
navTitle={langui.archives}
|
navTitle={langui.archives}
|
||||||
|
|
|
@ -5,20 +5,25 @@ 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>
|
() => (
|
||||||
<PanelHeader
|
<SubPanel>
|
||||||
icon={Icon.WatchLater}
|
<PanelHeader
|
||||||
title={langui.chronicles}
|
icon={Icon.WatchLater}
|
||||||
description={langui.chronicles_description}
|
title={langui.chronicles}
|
||||||
/>
|
description={langui.chronicles_description}
|
||||||
</SubPanel>
|
/>
|
||||||
|
</SubPanel>
|
||||||
|
),
|
||||||
|
[langui]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppLayout navTitle={langui.chronicles} subPanel={subPanel} {...props} />
|
<AppLayout navTitle={langui.chronicles} subPanel={subPanel} {...props} />
|
||||||
);
|
);
|
||||||
|
|
|
@ -76,321 +76,349 @@ export default function Content(props: Props): JSX.Element {
|
||||||
[content.group, content.slug]
|
[content.group, content.slug]
|
||||||
);
|
);
|
||||||
|
|
||||||
const subPanel = (
|
const subPanel = useMemo(
|
||||||
<SubPanel>
|
() => (
|
||||||
<ReturnButton
|
<SubPanel>
|
||||||
href={`/contents`}
|
<ReturnButton
|
||||||
title={langui.contents}
|
href={`/contents`}
|
||||||
langui={langui}
|
title={langui.contents}
|
||||||
displayOn={ReturnButtonType.Desktop}
|
|
||||||
horizontalLine
|
|
||||||
/>
|
|
||||||
|
|
||||||
{selectedTranslation?.text_set?.source_language?.data?.attributes
|
|
||||||
?.code !== undefined && (
|
|
||||||
<div className="grid gap-5">
|
|
||||||
<h2 className="text-xl">
|
|
||||||
{selectedTranslation.text_set.source_language.data.attributes
|
|
||||||
.code === selectedTranslation.language?.data?.attributes?.code
|
|
||||||
? langui.transcript_notice
|
|
||||||
: langui.translation_notice}
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
{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>
|
|
||||||
<Chip>
|
|
||||||
{prettyLanguage(
|
|
||||||
selectedTranslation.text_set.source_language.data.attributes
|
|
||||||
.code,
|
|
||||||
languages
|
|
||||||
)}
|
|
||||||
</Chip>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="grid grid-flow-col place-content-center place-items-center gap-2">
|
|
||||||
<p className="font-headers">{langui.status}:</p>
|
|
||||||
|
|
||||||
<ToolTip
|
|
||||||
content={getStatusDescription(
|
|
||||||
selectedTranslation.text_set.status,
|
|
||||||
langui
|
|
||||||
)}
|
|
||||||
maxWidth={"20rem"}
|
|
||||||
>
|
|
||||||
<Chip>{selectedTranslation.text_set.status}</Chip>
|
|
||||||
</ToolTip>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{selectedTranslation.text_set.transcribers &&
|
|
||||||
selectedTranslation.text_set.transcribers.data.length > 0 && (
|
|
||||||
<div>
|
|
||||||
<p className="font-headers">{langui.transcribers}:</p>
|
|
||||||
<div className="grid place-content-center place-items-center gap-2">
|
|
||||||
{filterHasAttributes(
|
|
||||||
selectedTranslation.text_set.transcribers.data
|
|
||||||
).map((recorder) => (
|
|
||||||
<Fragment key={recorder.id}>
|
|
||||||
<RecorderChip
|
|
||||||
langui={langui}
|
|
||||||
recorder={recorder.attributes}
|
|
||||||
/>
|
|
||||||
</Fragment>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{selectedTranslation.text_set.translators &&
|
|
||||||
selectedTranslation.text_set.translators.data.length > 0 && (
|
|
||||||
<div>
|
|
||||||
<p className="font-headers">{langui.translators}:</p>
|
|
||||||
<div className="grid place-content-center place-items-center gap-2">
|
|
||||||
{filterHasAttributes(
|
|
||||||
selectedTranslation.text_set.translators.data
|
|
||||||
).map((recorder) => (
|
|
||||||
<Fragment key={recorder.id}>
|
|
||||||
<RecorderChip
|
|
||||||
langui={langui}
|
|
||||||
recorder={recorder.attributes}
|
|
||||||
/>
|
|
||||||
</Fragment>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{selectedTranslation.text_set.proofreaders &&
|
|
||||||
selectedTranslation.text_set.proofreaders.data.length > 0 && (
|
|
||||||
<div>
|
|
||||||
<p className="font-headers">{langui.proofreaders}:</p>
|
|
||||||
<div className="grid place-content-center place-items-center gap-2">
|
|
||||||
{filterHasAttributes(
|
|
||||||
selectedTranslation.text_set.proofreaders.data
|
|
||||||
).map((recorder) => (
|
|
||||||
<Fragment key={recorder.id}>
|
|
||||||
<RecorderChip
|
|
||||||
langui={langui}
|
|
||||||
recorder={recorder.attributes}
|
|
||||||
/>
|
|
||||||
</Fragment>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{isDefinedAndNotEmpty(selectedTranslation.text_set.notes) && (
|
|
||||||
<div>
|
|
||||||
<p className="font-headers">{"Notes"}:</p>
|
|
||||||
<div className="grid place-content-center place-items-center gap-2">
|
|
||||||
<Markdawn text={selectedTranslation.text_set.notes} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{content.ranged_contents?.data &&
|
|
||||||
content.ranged_contents.data.length > 0 && (
|
|
||||||
<>
|
|
||||||
<HorizontalLine />
|
|
||||||
<div>
|
|
||||||
<p className="font-headers text-2xl">{langui.source}</p>
|
|
||||||
<div className="mt-6 grid place-items-center gap-6 text-left">
|
|
||||||
{content.ranged_contents.data.map((rangedContent) => {
|
|
||||||
const libraryItem =
|
|
||||||
rangedContent.attributes?.library_item?.data;
|
|
||||||
if (libraryItem?.attributes && libraryItem.id) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={libraryItem.attributes.slug}
|
|
||||||
className="mobile:w-[80%]"
|
|
||||||
>
|
|
||||||
<PreviewCard
|
|
||||||
href={`/library/${libraryItem.attributes.slug}`}
|
|
||||||
title={libraryItem.attributes.title}
|
|
||||||
subtitle={libraryItem.attributes.subtitle}
|
|
||||||
thumbnail={
|
|
||||||
libraryItem.attributes.thumbnail?.data?.attributes
|
|
||||||
}
|
|
||||||
thumbnailAspectRatio="21/29.7"
|
|
||||||
topChips={
|
|
||||||
libraryItem.attributes.metadata &&
|
|
||||||
libraryItem.attributes.metadata.length > 0 &&
|
|
||||||
libraryItem.attributes.metadata[0]
|
|
||||||
? [
|
|
||||||
prettyItemSubType(
|
|
||||||
libraryItem.attributes.metadata[0]
|
|
||||||
),
|
|
||||||
]
|
|
||||||
: []
|
|
||||||
}
|
|
||||||
bottomChips={libraryItem.attributes.categories?.data.map(
|
|
||||||
(category) => category.attributes?.short ?? ""
|
|
||||||
)}
|
|
||||||
metadata={{
|
|
||||||
currencies: currencies,
|
|
||||||
release_date: libraryItem.attributes.release_date,
|
|
||||||
price: libraryItem.attributes.price,
|
|
||||||
position: "Bottom",
|
|
||||||
}}
|
|
||||||
infoAppend={
|
|
||||||
<PreviewCardCTAs
|
|
||||||
id={libraryItem.id}
|
|
||||||
displayCTAs={
|
|
||||||
!isUntangibleGroupItem(
|
|
||||||
libraryItem.attributes.metadata?.[0]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
langui={langui}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return <></>;
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{selectedTranslation?.text_set?.text && (
|
|
||||||
<>
|
|
||||||
<HorizontalLine />
|
|
||||||
<TOC
|
|
||||||
text={selectedTranslation.text_set.text}
|
|
||||||
title={prettyinlineTitle(
|
|
||||||
selectedTranslation.pre_title,
|
|
||||||
selectedTranslation.title,
|
|
||||||
selectedTranslation.subtitle
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</SubPanel>
|
|
||||||
);
|
|
||||||
const contentPanel = (
|
|
||||||
<ContentPanel>
|
|
||||||
<ReturnButton
|
|
||||||
href={`/contents`}
|
|
||||||
title={langui.contents}
|
|
||||||
langui={langui}
|
|
||||||
displayOn={ReturnButtonType.Mobile}
|
|
||||||
className="mb-10"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className="grid place-items-center">
|
|
||||||
<ThumbnailHeader
|
|
||||||
thumbnail={content.thumbnail?.data?.attributes}
|
|
||||||
pre_title={selectedTranslation?.pre_title}
|
|
||||||
title={selectedTranslation?.title}
|
|
||||||
subtitle={selectedTranslation?.subtitle}
|
|
||||||
description={selectedTranslation?.description}
|
|
||||||
type={content.type}
|
|
||||||
categories={content.categories}
|
|
||||||
langui={langui}
|
langui={langui}
|
||||||
languageSwitcher={<LanguageSwitcher />}
|
displayOn={ReturnButtonType.Desktop}
|
||||||
|
horizontalLine
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{previousContent?.attributes && (
|
{selectedTranslation?.text_set?.source_language?.data?.attributes
|
||||||
<div className="mt-12 mb-8 w-full">
|
?.code !== undefined && (
|
||||||
<h2 className="mb-4 text-center text-2xl">
|
<div className="grid gap-5">
|
||||||
{langui.previous_content}
|
<h2 className="text-xl">
|
||||||
|
{selectedTranslation.text_set.source_language.data.attributes
|
||||||
|
.code === selectedTranslation.language?.data?.attributes?.code
|
||||||
|
? langui.transcript_notice
|
||||||
|
: langui.translation_notice}
|
||||||
</h2>
|
</h2>
|
||||||
<TranslatedPreviewLine
|
|
||||||
href={`/contents/${previousContent.attributes.slug}`}
|
{selectedTranslation.text_set.source_language.data.attributes
|
||||||
translations={previousContent.attributes.translations?.map(
|
.code !==
|
||||||
(translation) => ({
|
selectedTranslation.language?.data?.attributes?.code && (
|
||||||
pre_title: translation?.pre_title,
|
<div className="grid place-items-center gap-2">
|
||||||
title: translation?.title,
|
<p className="font-headers">{langui.source_language}:</p>
|
||||||
subtitle: translation?.subtitle,
|
<Chip>
|
||||||
language: translation?.language?.data?.attributes?.code,
|
{prettyLanguage(
|
||||||
})
|
selectedTranslation.text_set.source_language.data.attributes
|
||||||
|
.code,
|
||||||
|
languages
|
||||||
|
)}
|
||||||
|
</Chip>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="grid grid-flow-col place-content-center place-items-center gap-2">
|
||||||
|
<p className="font-headers">{langui.status}:</p>
|
||||||
|
|
||||||
|
<ToolTip
|
||||||
|
content={getStatusDescription(
|
||||||
|
selectedTranslation.text_set.status,
|
||||||
|
langui
|
||||||
|
)}
|
||||||
|
maxWidth={"20rem"}
|
||||||
|
>
|
||||||
|
<Chip>{selectedTranslation.text_set.status}</Chip>
|
||||||
|
</ToolTip>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{selectedTranslation.text_set.transcribers &&
|
||||||
|
selectedTranslation.text_set.transcribers.data.length > 0 && (
|
||||||
|
<div>
|
||||||
|
<p className="font-headers">{langui.transcribers}:</p>
|
||||||
|
<div className="grid place-content-center place-items-center gap-2">
|
||||||
|
{filterHasAttributes(
|
||||||
|
selectedTranslation.text_set.transcribers.data
|
||||||
|
).map((recorder) => (
|
||||||
|
<Fragment key={recorder.id}>
|
||||||
|
<RecorderChip
|
||||||
|
langui={langui}
|
||||||
|
recorder={recorder.attributes}
|
||||||
|
/>
|
||||||
|
</Fragment>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
slug={previousContent.attributes.slug}
|
|
||||||
languages={languages}
|
{selectedTranslation.text_set.translators &&
|
||||||
thumbnail={previousContent.attributes.thumbnail?.data?.attributes}
|
selectedTranslation.text_set.translators.data.length > 0 && (
|
||||||
thumbnailAspectRatio="3/2"
|
<div>
|
||||||
topChips={
|
<p className="font-headers">{langui.translators}:</p>
|
||||||
isMobile
|
<div className="grid place-content-center place-items-center gap-2">
|
||||||
? undefined
|
{filterHasAttributes(
|
||||||
: previousContent.attributes.type?.data?.attributes
|
selectedTranslation.text_set.translators.data
|
||||||
? [
|
).map((recorder) => (
|
||||||
previousContent.attributes.type.data.attributes
|
<Fragment key={recorder.id}>
|
||||||
.titles?.[0]
|
<RecorderChip
|
||||||
? previousContent.attributes.type.data.attributes
|
langui={langui}
|
||||||
.titles[0]?.title
|
recorder={recorder.attributes}
|
||||||
: prettySlug(
|
/>
|
||||||
previousContent.attributes.type.data.attributes.slug
|
</Fragment>
|
||||||
),
|
))}
|
||||||
]
|
</div>
|
||||||
: undefined
|
</div>
|
||||||
}
|
)}
|
||||||
bottomChips={
|
|
||||||
isMobile
|
{selectedTranslation.text_set.proofreaders &&
|
||||||
? undefined
|
selectedTranslation.text_set.proofreaders.data.length > 0 && (
|
||||||
: previousContent.attributes.categories?.data.map(
|
<div>
|
||||||
(category) => category.attributes?.short ?? ""
|
<p className="font-headers">{langui.proofreaders}:</p>
|
||||||
)
|
<div className="grid place-content-center place-items-center gap-2">
|
||||||
}
|
{filterHasAttributes(
|
||||||
/>
|
selectedTranslation.text_set.proofreaders.data
|
||||||
|
).map((recorder) => (
|
||||||
|
<Fragment key={recorder.id}>
|
||||||
|
<RecorderChip
|
||||||
|
langui={langui}
|
||||||
|
recorder={recorder.attributes}
|
||||||
|
/>
|
||||||
|
</Fragment>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{isDefinedAndNotEmpty(selectedTranslation.text_set.notes) && (
|
||||||
|
<div>
|
||||||
|
<p className="font-headers">{"Notes"}:</p>
|
||||||
|
<div className="grid place-content-center place-items-center gap-2">
|
||||||
|
<Markdawn text={selectedTranslation.text_set.notes} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<HorizontalLine />
|
{content.ranged_contents?.data &&
|
||||||
|
content.ranged_contents.data.length > 0 && (
|
||||||
|
<>
|
||||||
|
<HorizontalLine />
|
||||||
|
<div>
|
||||||
|
<p className="font-headers text-2xl">{langui.source}</p>
|
||||||
|
<div className="mt-6 grid place-items-center gap-6 text-left">
|
||||||
|
{content.ranged_contents.data.map((rangedContent) => {
|
||||||
|
const libraryItem =
|
||||||
|
rangedContent.attributes?.library_item?.data;
|
||||||
|
if (libraryItem?.attributes && libraryItem.id) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={libraryItem.attributes.slug}
|
||||||
|
className="mobile:w-[80%]"
|
||||||
|
>
|
||||||
|
<PreviewCard
|
||||||
|
href={`/library/${libraryItem.attributes.slug}`}
|
||||||
|
title={libraryItem.attributes.title}
|
||||||
|
subtitle={libraryItem.attributes.subtitle}
|
||||||
|
thumbnail={
|
||||||
|
libraryItem.attributes.thumbnail?.data?.attributes
|
||||||
|
}
|
||||||
|
thumbnailAspectRatio="21/29.7"
|
||||||
|
topChips={
|
||||||
|
libraryItem.attributes.metadata &&
|
||||||
|
libraryItem.attributes.metadata.length > 0 &&
|
||||||
|
libraryItem.attributes.metadata[0]
|
||||||
|
? [
|
||||||
|
prettyItemSubType(
|
||||||
|
libraryItem.attributes.metadata[0]
|
||||||
|
),
|
||||||
|
]
|
||||||
|
: []
|
||||||
|
}
|
||||||
|
bottomChips={libraryItem.attributes.categories?.data.map(
|
||||||
|
(category) => category.attributes?.short ?? ""
|
||||||
|
)}
|
||||||
|
metadata={{
|
||||||
|
currencies: currencies,
|
||||||
|
release_date: libraryItem.attributes.release_date,
|
||||||
|
price: libraryItem.attributes.price,
|
||||||
|
position: "Bottom",
|
||||||
|
}}
|
||||||
|
infoAppend={
|
||||||
|
<PreviewCardCTAs
|
||||||
|
id={libraryItem.id}
|
||||||
|
displayCTAs={
|
||||||
|
!isUntangibleGroupItem(
|
||||||
|
libraryItem.attributes.metadata?.[0]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
langui={langui}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return <></>;
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
<Markdawn text={selectedTranslation?.text_set?.text ?? ""} />
|
{selectedTranslation?.text_set?.text && (
|
||||||
|
|
||||||
{nextContent?.attributes && (
|
|
||||||
<>
|
<>
|
||||||
<HorizontalLine />
|
<HorizontalLine />
|
||||||
<h2 className="mb-4 text-center text-2xl">
|
<TOC
|
||||||
{langui.followup_content}
|
text={selectedTranslation.text_set.text}
|
||||||
</h2>
|
title={prettyinlineTitle(
|
||||||
<TranslatedPreviewLine
|
selectedTranslation.pre_title,
|
||||||
href={`/contents/${nextContent.attributes.slug}`}
|
selectedTranslation.title,
|
||||||
translations={nextContent.attributes.translations?.map(
|
selectedTranslation.subtitle
|
||||||
(translation) => ({
|
|
||||||
pre_title: translation?.pre_title,
|
|
||||||
title: translation?.title,
|
|
||||||
subtitle: translation?.subtitle,
|
|
||||||
language: translation?.language?.data?.attributes?.code,
|
|
||||||
})
|
|
||||||
)}
|
)}
|
||||||
slug={nextContent.attributes.slug}
|
|
||||||
languages={languages}
|
|
||||||
thumbnail={nextContent.attributes.thumbnail?.data?.attributes}
|
|
||||||
thumbnailAspectRatio="3/2"
|
|
||||||
topChips={
|
|
||||||
isMobile
|
|
||||||
? undefined
|
|
||||||
: nextContent.attributes.type?.data?.attributes
|
|
||||||
? [
|
|
||||||
nextContent.attributes.type.data.attributes.titles?.[0]
|
|
||||||
? nextContent.attributes.type.data.attributes.titles[0]
|
|
||||||
?.title
|
|
||||||
: prettySlug(
|
|
||||||
nextContent.attributes.type.data.attributes.slug
|
|
||||||
),
|
|
||||||
]
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
bottomChips={
|
|
||||||
isMobile
|
|
||||||
? undefined
|
|
||||||
: nextContent.attributes.categories?.data.map(
|
|
||||||
(category) => category.attributes?.short ?? ""
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</SubPanel>
|
||||||
</ContentPanel>
|
),
|
||||||
|
[
|
||||||
|
content.ranged_contents?.data,
|
||||||
|
currencies,
|
||||||
|
languages,
|
||||||
|
langui,
|
||||||
|
selectedTranslation,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
const contentPanel = useMemo(
|
||||||
|
() => (
|
||||||
|
<ContentPanel>
|
||||||
|
<ReturnButton
|
||||||
|
href={`/contents`}
|
||||||
|
title={langui.contents}
|
||||||
|
langui={langui}
|
||||||
|
displayOn={ReturnButtonType.Mobile}
|
||||||
|
className="mb-10"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="grid place-items-center">
|
||||||
|
<ThumbnailHeader
|
||||||
|
thumbnail={content.thumbnail?.data?.attributes}
|
||||||
|
pre_title={selectedTranslation?.pre_title}
|
||||||
|
title={selectedTranslation?.title}
|
||||||
|
subtitle={selectedTranslation?.subtitle}
|
||||||
|
description={selectedTranslation?.description}
|
||||||
|
type={content.type}
|
||||||
|
categories={content.categories}
|
||||||
|
langui={langui}
|
||||||
|
languageSwitcher={<LanguageSwitcher />}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{previousContent?.attributes && (
|
||||||
|
<div className="mt-12 mb-8 w-full">
|
||||||
|
<h2 className="mb-4 text-center text-2xl">
|
||||||
|
{langui.previous_content}
|
||||||
|
</h2>
|
||||||
|
<TranslatedPreviewLine
|
||||||
|
href={`/contents/${previousContent.attributes.slug}`}
|
||||||
|
translations={previousContent.attributes.translations?.map(
|
||||||
|
(translation) => ({
|
||||||
|
pre_title: translation?.pre_title,
|
||||||
|
title: translation?.title,
|
||||||
|
subtitle: translation?.subtitle,
|
||||||
|
language: translation?.language?.data?.attributes?.code,
|
||||||
|
})
|
||||||
|
)}
|
||||||
|
slug={previousContent.attributes.slug}
|
||||||
|
languages={languages}
|
||||||
|
thumbnail={
|
||||||
|
previousContent.attributes.thumbnail?.data?.attributes
|
||||||
|
}
|
||||||
|
thumbnailAspectRatio="3/2"
|
||||||
|
topChips={
|
||||||
|
isMobile
|
||||||
|
? undefined
|
||||||
|
: previousContent.attributes.type?.data?.attributes
|
||||||
|
? [
|
||||||
|
previousContent.attributes.type.data.attributes
|
||||||
|
.titles?.[0]
|
||||||
|
? previousContent.attributes.type.data.attributes
|
||||||
|
.titles[0]?.title
|
||||||
|
: prettySlug(
|
||||||
|
previousContent.attributes.type.data.attributes
|
||||||
|
.slug
|
||||||
|
),
|
||||||
|
]
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
bottomChips={
|
||||||
|
isMobile
|
||||||
|
? undefined
|
||||||
|
: previousContent.attributes.categories?.data.map(
|
||||||
|
(category) => category.attributes?.short ?? ""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<HorizontalLine />
|
||||||
|
|
||||||
|
<Markdawn text={selectedTranslation?.text_set?.text ?? ""} />
|
||||||
|
|
||||||
|
{nextContent?.attributes && (
|
||||||
|
<>
|
||||||
|
<HorizontalLine />
|
||||||
|
<h2 className="mb-4 text-center text-2xl">
|
||||||
|
{langui.followup_content}
|
||||||
|
</h2>
|
||||||
|
<TranslatedPreviewLine
|
||||||
|
href={`/contents/${nextContent.attributes.slug}`}
|
||||||
|
translations={nextContent.attributes.translations?.map(
|
||||||
|
(translation) => ({
|
||||||
|
pre_title: translation?.pre_title,
|
||||||
|
title: translation?.title,
|
||||||
|
subtitle: translation?.subtitle,
|
||||||
|
language: translation?.language?.data?.attributes?.code,
|
||||||
|
})
|
||||||
|
)}
|
||||||
|
slug={nextContent.attributes.slug}
|
||||||
|
languages={languages}
|
||||||
|
thumbnail={nextContent.attributes.thumbnail?.data?.attributes}
|
||||||
|
thumbnailAspectRatio="3/2"
|
||||||
|
topChips={
|
||||||
|
isMobile
|
||||||
|
? undefined
|
||||||
|
: nextContent.attributes.type?.data?.attributes
|
||||||
|
? [
|
||||||
|
nextContent.attributes.type.data.attributes.titles?.[0]
|
||||||
|
? nextContent.attributes.type.data.attributes
|
||||||
|
.titles[0]?.title
|
||||||
|
: prettySlug(
|
||||||
|
nextContent.attributes.type.data.attributes.slug
|
||||||
|
),
|
||||||
|
]
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
bottomChips={
|
||||||
|
isMobile
|
||||||
|
? undefined
|
||||||
|
: nextContent.attributes.categories?.data.map(
|
||||||
|
(category) => category.attributes?.short ?? ""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</ContentPanel>
|
||||||
|
),
|
||||||
|
[
|
||||||
|
LanguageSwitcher,
|
||||||
|
content.categories,
|
||||||
|
content.thumbnail?.data?.attributes,
|
||||||
|
content.type,
|
||||||
|
isMobile,
|
||||||
|
languages,
|
||||||
|
langui,
|
||||||
|
nextContent?.attributes,
|
||||||
|
previousContent?.attributes,
|
||||||
|
selectedTranslation,
|
||||||
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -71,166 +71,188 @@ export default function Contents(props: Props): JSX.Element {
|
||||||
[combineRelatedContent, searchName.length]
|
[combineRelatedContent, searchName.length]
|
||||||
);
|
);
|
||||||
|
|
||||||
const subPanel = (
|
const subPanel = useMemo(
|
||||||
<SubPanel>
|
() => (
|
||||||
<PanelHeader
|
<SubPanel>
|
||||||
icon={Icon.Workspaces}
|
<PanelHeader
|
||||||
title={langui.contents}
|
icon={Icon.Workspaces}
|
||||||
description={langui.contents_description}
|
title={langui.contents}
|
||||||
/>
|
description={langui.contents_description}
|
||||||
|
/>
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
className="mb-6 w-full"
|
className="mb-6 w-full"
|
||||||
placeholder={langui.search_title ?? undefined}
|
placeholder={langui.search_title ?? undefined}
|
||||||
state={searchName}
|
state={searchName}
|
||||||
setState={setSearchName}
|
setState={setSearchName}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<WithLabel
|
|
||||||
label={langui.group_by}
|
|
||||||
input={
|
|
||||||
<Select
|
|
||||||
className="w-full"
|
|
||||||
options={[langui.category ?? "", langui.type ?? ""]}
|
|
||||||
state={groupingMethod}
|
|
||||||
setState={setGroupingMethod}
|
|
||||||
allowEmpty
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<WithLabel
|
|
||||||
label={langui.combine_related_contents}
|
|
||||||
disabled={searchName.length > 1}
|
|
||||||
input={
|
|
||||||
<Switch
|
|
||||||
setState={setCombineRelatedContent}
|
|
||||||
state={effectiveCombineRelatedContent}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{hoverable && (
|
|
||||||
<WithLabel
|
<WithLabel
|
||||||
label={langui.always_show_info}
|
label={langui.group_by}
|
||||||
input={
|
input={
|
||||||
<Switch setState={setKeepInfoVisible} state={keepInfoVisible} />
|
<Select
|
||||||
|
className="w-full"
|
||||||
|
options={[langui.category ?? "", langui.type ?? ""]}
|
||||||
|
state={groupingMethod}
|
||||||
|
setState={setGroupingMethod}
|
||||||
|
allowEmpty
|
||||||
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
|
|
||||||
<Button
|
<WithLabel
|
||||||
className="mt-8"
|
label={langui.combine_related_contents}
|
||||||
text={langui.reset_all_filters}
|
disabled={searchName.length > 1}
|
||||||
icon={Icon.Replay}
|
input={
|
||||||
onClick={() => {
|
<Switch
|
||||||
setSearchName(defaultFiltersState.searchName);
|
setState={setCombineRelatedContent}
|
||||||
setGroupingMethod(defaultFiltersState.groupingMethod);
|
state={effectiveCombineRelatedContent}
|
||||||
setKeepInfoVisible(defaultFiltersState.keepInfoVisible);
|
/>
|
||||||
setCombineRelatedContent(defaultFiltersState.combineRelatedContent);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</SubPanel>
|
|
||||||
);
|
|
||||||
const contentPanel = (
|
|
||||||
<ContentPanel width={ContentPanelWidthSizes.Full}>
|
|
||||||
{/* TODO: Add to langui */}
|
|
||||||
{groups.size === 0 && (
|
|
||||||
<ContentPlaceholder
|
|
||||||
message={
|
|
||||||
"No results. You can try changing or resetting the search parameters."
|
|
||||||
}
|
}
|
||||||
icon={Icon.ChevronLeft}
|
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
{iterateMap(
|
{hoverable && (
|
||||||
groups,
|
<WithLabel
|
||||||
(name, items, index) =>
|
label={langui.always_show_info}
|
||||||
items.length > 0 && (
|
input={
|
||||||
<Fragment key={index}>
|
<Switch setState={setKeepInfoVisible} state={keepInfoVisible} />
|
||||||
{name && (
|
}
|
||||||
<h2
|
/>
|
||||||
className="flex flex-row place-items-center gap-2 pb-2 pt-10 text-2xl
|
)}
|
||||||
|
|
||||||
|
<Button
|
||||||
|
className="mt-8"
|
||||||
|
text={langui.reset_all_filters}
|
||||||
|
icon={Icon.Replay}
|
||||||
|
onClick={() => {
|
||||||
|
setSearchName(defaultFiltersState.searchName);
|
||||||
|
setGroupingMethod(defaultFiltersState.groupingMethod);
|
||||||
|
setKeepInfoVisible(defaultFiltersState.keepInfoVisible);
|
||||||
|
setCombineRelatedContent(defaultFiltersState.combineRelatedContent);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</SubPanel>
|
||||||
|
),
|
||||||
|
[
|
||||||
|
effectiveCombineRelatedContent,
|
||||||
|
groupingMethod,
|
||||||
|
hoverable,
|
||||||
|
keepInfoVisible,
|
||||||
|
langui,
|
||||||
|
searchName,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
const contentPanel = useMemo(
|
||||||
|
() => (
|
||||||
|
<ContentPanel width={ContentPanelWidthSizes.Full}>
|
||||||
|
{/* TODO: Add to langui */}
|
||||||
|
{groups.size === 0 && (
|
||||||
|
<ContentPlaceholder
|
||||||
|
message={
|
||||||
|
"No results. You can try changing or resetting the search parameters."
|
||||||
|
}
|
||||||
|
icon={Icon.ChevronLeft}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{iterateMap(
|
||||||
|
groups,
|
||||||
|
(name, items, index) =>
|
||||||
|
items.length > 0 && (
|
||||||
|
<Fragment key={index}>
|
||||||
|
{name && (
|
||||||
|
<h2
|
||||||
|
className="flex flex-row place-items-center gap-2 pb-2 pt-10 text-2xl
|
||||||
first-of-type:pt-0"
|
first-of-type:pt-0"
|
||||||
>
|
>
|
||||||
{name}
|
{name}
|
||||||
<Chip>{`${items.reduce((currentSum, item) => {
|
<Chip>{`${items.reduce((currentSum, item) => {
|
||||||
if (effectiveCombineRelatedContent) {
|
if (effectiveCombineRelatedContent) {
|
||||||
if (
|
if (
|
||||||
item.attributes?.group?.data?.attributes?.combine ===
|
item.attributes?.group?.data?.attributes?.combine ===
|
||||||
true
|
|
||||||
) {
|
|
||||||
return (
|
|
||||||
currentSum +
|
|
||||||
(item.attributes.group.data.attributes.contents?.data
|
|
||||||
.length ?? 1)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return currentSum + 1;
|
|
||||||
}, 0)} ${
|
|
||||||
items.length <= 1
|
|
||||||
? langui.result?.toLowerCase() ?? ""
|
|
||||||
: langui.results?.toLowerCase() ?? ""
|
|
||||||
}`}</Chip>
|
|
||||||
</h2>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div
|
|
||||||
className="grid grid-cols-2 items-end gap-8
|
|
||||||
desktop:grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))] mobile:gap-4"
|
|
||||||
>
|
|
||||||
{filterHasAttributes(items).map((item) => (
|
|
||||||
<Fragment key={item.id}>
|
|
||||||
<TranslatedPreviewCard
|
|
||||||
href={`/contents/${item.attributes.slug}`}
|
|
||||||
translations={item.attributes.translations?.map(
|
|
||||||
(translation) => ({
|
|
||||||
pre_title: translation?.pre_title,
|
|
||||||
title: translation?.title,
|
|
||||||
subtitle: translation?.subtitle,
|
|
||||||
language:
|
|
||||||
translation?.language?.data?.attributes?.code,
|
|
||||||
})
|
|
||||||
)}
|
|
||||||
slug={item.attributes.slug}
|
|
||||||
languages={languages}
|
|
||||||
thumbnail={item.attributes.thumbnail?.data?.attributes}
|
|
||||||
thumbnailAspectRatio="3/2"
|
|
||||||
thumbnailForceAspectRatio
|
|
||||||
stackNumber={
|
|
||||||
effectiveCombineRelatedContent &&
|
|
||||||
item.attributes.group?.data?.attributes?.combine ===
|
|
||||||
true
|
true
|
||||||
? item.attributes.group.data.attributes.contents?.data
|
) {
|
||||||
.length
|
return (
|
||||||
: 0
|
currentSum +
|
||||||
|
(item.attributes.group.data.attributes.contents
|
||||||
|
?.data.length ?? 1)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
topChips={
|
return currentSum + 1;
|
||||||
item.attributes.type?.data?.attributes
|
}, 0)} ${
|
||||||
? [
|
items.length <= 1
|
||||||
item.attributes.type.data.attributes.titles?.[0]
|
? langui.result?.toLowerCase() ?? ""
|
||||||
? item.attributes.type.data.attributes.titles[0]
|
: langui.results?.toLowerCase() ?? ""
|
||||||
?.title
|
}`}</Chip>
|
||||||
: prettySlug(
|
</h2>
|
||||||
item.attributes.type.data.attributes.slug
|
)}
|
||||||
),
|
|
||||||
]
|
<div
|
||||||
: undefined
|
className="grid grid-cols-2 items-end gap-8
|
||||||
}
|
desktop:grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))] mobile:gap-4"
|
||||||
bottomChips={item.attributes.categories?.data.map(
|
>
|
||||||
(category) => category.attributes?.short ?? ""
|
{filterHasAttributes(items).map((item) => (
|
||||||
)}
|
<Fragment key={item.id}>
|
||||||
keepInfoVisible={keepInfoVisible}
|
<TranslatedPreviewCard
|
||||||
/>
|
href={`/contents/${item.attributes.slug}`}
|
||||||
</Fragment>
|
translations={item.attributes.translations?.map(
|
||||||
))}
|
(translation) => ({
|
||||||
</div>
|
pre_title: translation?.pre_title,
|
||||||
</Fragment>
|
title: translation?.title,
|
||||||
)
|
subtitle: translation?.subtitle,
|
||||||
)}
|
language:
|
||||||
</ContentPanel>
|
translation?.language?.data?.attributes?.code,
|
||||||
|
})
|
||||||
|
)}
|
||||||
|
slug={item.attributes.slug}
|
||||||
|
languages={languages}
|
||||||
|
thumbnail={item.attributes.thumbnail?.data?.attributes}
|
||||||
|
thumbnailAspectRatio="3/2"
|
||||||
|
thumbnailForceAspectRatio
|
||||||
|
stackNumber={
|
||||||
|
effectiveCombineRelatedContent &&
|
||||||
|
item.attributes.group?.data?.attributes?.combine ===
|
||||||
|
true
|
||||||
|
? 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
|
||||||
|
: prettySlug(
|
||||||
|
item.attributes.type.data.attributes.slug
|
||||||
|
),
|
||||||
|
]
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
bottomChips={item.attributes.categories?.data.map(
|
||||||
|
(category) => category.attributes?.short ?? ""
|
||||||
|
)}
|
||||||
|
keepInfoVisible={keepInfoVisible}
|
||||||
|
/>
|
||||||
|
</Fragment>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Fragment>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</ContentPanel>
|
||||||
|
),
|
||||||
|
[
|
||||||
|
effectiveCombineRelatedContent,
|
||||||
|
groups,
|
||||||
|
keepInfoVisible,
|
||||||
|
languages,
|
||||||
|
langui.result,
|
||||||
|
langui.results,
|
||||||
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppLayout
|
<AppLayout
|
||||||
navTitle={langui.contents}
|
navTitle={langui.contents}
|
||||||
|
|
|
@ -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,61 +22,65 @@ 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}>
|
() => (
|
||||||
{<h2 className="text-2xl">{testReport.title}</h2>}
|
<ContentPanel width={ContentPanelWidthSizes.Full}>
|
||||||
|
{<h2 className="text-2xl">{testReport.title}</h2>}
|
||||||
|
|
||||||
<div className="my-4 grid grid-cols-[2em,3em,2fr,1fr,0.5fr,0.5fr,2fr] items-center gap-2">
|
<div className="my-4 grid grid-cols-[2em,3em,2fr,1fr,0.5fr,0.5fr,2fr] items-center gap-2">
|
||||||
<p></p>
|
<p></p>
|
||||||
<p></p>
|
<p></p>
|
||||||
<p className="font-headers">Ref</p>
|
<p className="font-headers">Ref</p>
|
||||||
<p className="font-headers">Name</p>
|
<p className="font-headers">Name</p>
|
||||||
<p className="font-headers">Type</p>
|
<p className="font-headers">Type</p>
|
||||||
<p className="font-headers">Severity</p>
|
<p className="font-headers">Severity</p>
|
||||||
<p className="font-headers">Description</p>
|
<p className="font-headers">Description</p>
|
||||||
</div>
|
|
||||||
|
|
||||||
{testReport.lines.map((line, index) => (
|
|
||||||
<div
|
|
||||||
key={index}
|
|
||||||
className="mb-2 grid grid-cols-[2em,3em,2fr,1fr,0.5fr,0.5fr,2fr] items-center
|
|
||||||
justify-items-start gap-2"
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
href={line.frontendUrl}
|
|
||||||
target="_blank"
|
|
||||||
className="w-4 text-xs"
|
|
||||||
text="F"
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
href={line.backendUrl}
|
|
||||||
target="_blank"
|
|
||||||
className="w-4 text-xs"
|
|
||||||
text="B"
|
|
||||||
/>
|
|
||||||
<p>{line.subitems.join(" -> ")}</p>
|
|
||||||
<p>{line.name}</p>
|
|
||||||
<Chip>{line.type}</Chip>
|
|
||||||
<Chip
|
|
||||||
className={
|
|
||||||
line.severity === "Very High"
|
|
||||||
? "bg-[#f00] font-bold !opacity-100"
|
|
||||||
: line.severity === "High"
|
|
||||||
? "bg-[#ff6600] font-bold !opacity-100"
|
|
||||||
: line.severity === "Medium"
|
|
||||||
? "bg-[#fff344] !opacity-100"
|
|
||||||
: ""
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{line.severity}
|
|
||||||
</Chip>
|
|
||||||
<ToolTip content={line.recommandation} placement="left">
|
|
||||||
<p>{line.description}</p>
|
|
||||||
</ToolTip>
|
|
||||||
</div>
|
</div>
|
||||||
))}
|
|
||||||
</ContentPanel>
|
{testReport.lines.map((line, index) => (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className="mb-2 grid grid-cols-[2em,3em,2fr,1fr,0.5fr,0.5fr,2fr] items-center
|
||||||
|
justify-items-start gap-2"
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
href={line.frontendUrl}
|
||||||
|
target="_blank"
|
||||||
|
className="w-4 text-xs"
|
||||||
|
text="F"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
href={line.backendUrl}
|
||||||
|
target="_blank"
|
||||||
|
className="w-4 text-xs"
|
||||||
|
text="B"
|
||||||
|
/>
|
||||||
|
<p>{line.subitems.join(" -> ")}</p>
|
||||||
|
<p>{line.name}</p>
|
||||||
|
<Chip>{line.type}</Chip>
|
||||||
|
<Chip
|
||||||
|
className={
|
||||||
|
line.severity === "Very High"
|
||||||
|
? "bg-[#f00] font-bold !opacity-100"
|
||||||
|
: line.severity === "High"
|
||||||
|
? "bg-[#ff6600] font-bold !opacity-100"
|
||||||
|
: line.severity === "Medium"
|
||||||
|
? "bg-[#fff344] !opacity-100"
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{line.severity}
|
||||||
|
</Chip>
|
||||||
|
<ToolTip content={line.recommandation} placement="left">
|
||||||
|
<p>{line.description}</p>
|
||||||
|
</ToolTip>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</ContentPanel>
|
||||||
|
),
|
||||||
|
[testReport.lines, testReport.title]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppLayout navTitle={"Checkup"} contentPanel={contentPanel} {...props} />
|
<AppLayout navTitle={"Checkup"} contentPanel={contentPanel} {...props} />
|
||||||
);
|
);
|
||||||
|
|
|
@ -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,61 +24,65 @@ 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}>
|
() => (
|
||||||
{<h2 className="text-2xl">{testReport.title}</h2>}
|
<ContentPanel width={ContentPanelWidthSizes.Full}>
|
||||||
|
{<h2 className="text-2xl">{testReport.title}</h2>}
|
||||||
|
|
||||||
<div className="my-4 grid grid-cols-[2em,3em,2fr,1fr,0.5fr,0.5fr,2fr] items-center gap-2">
|
<div className="my-4 grid grid-cols-[2em,3em,2fr,1fr,0.5fr,0.5fr,2fr] items-center gap-2">
|
||||||
<p></p>
|
<p></p>
|
||||||
<p></p>
|
<p></p>
|
||||||
<p className="font-headers">Ref</p>
|
<p className="font-headers">Ref</p>
|
||||||
<p className="font-headers">Name</p>
|
<p className="font-headers">Name</p>
|
||||||
<p className="font-headers">Type</p>
|
<p className="font-headers">Type</p>
|
||||||
<p className="font-headers">Severity</p>
|
<p className="font-headers">Severity</p>
|
||||||
<p className="font-headers">Description</p>
|
<p className="font-headers">Description</p>
|
||||||
</div>
|
|
||||||
|
|
||||||
{testReport.lines.map((line, index) => (
|
|
||||||
<div
|
|
||||||
key={index}
|
|
||||||
className="mb-2 grid
|
|
||||||
grid-cols-[2em,3em,2fr,1fr,0.5fr,0.5fr,2fr] items-center justify-items-start gap-2"
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
href={line.frontendUrl}
|
|
||||||
target="_blank"
|
|
||||||
className="w-4 text-xs"
|
|
||||||
text="F"
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
href={line.backendUrl}
|
|
||||||
target="_blank"
|
|
||||||
className="w-4 text-xs"
|
|
||||||
text="B"
|
|
||||||
/>
|
|
||||||
<p>{line.subitems.join(" -> ")}</p>
|
|
||||||
<p>{line.name}</p>
|
|
||||||
<Chip>{line.type}</Chip>
|
|
||||||
<Chip
|
|
||||||
className={
|
|
||||||
line.severity === "Very High"
|
|
||||||
? "bg-[#f00] font-bold !opacity-100"
|
|
||||||
: line.severity === "High"
|
|
||||||
? "bg-[#ff6600] font-bold !opacity-100"
|
|
||||||
: line.severity === "Medium"
|
|
||||||
? "bg-[#fff344] !opacity-100"
|
|
||||||
: ""
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{line.severity}
|
|
||||||
</Chip>
|
|
||||||
<ToolTip content={line.recommandation} placement="left">
|
|
||||||
<p>{line.description}</p>
|
|
||||||
</ToolTip>
|
|
||||||
</div>
|
</div>
|
||||||
))}
|
|
||||||
</ContentPanel>
|
{testReport.lines.map((line, index) => (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className="mb-2 grid
|
||||||
|
grid-cols-[2em,3em,2fr,1fr,0.5fr,0.5fr,2fr] items-center justify-items-start gap-2"
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
href={line.frontendUrl}
|
||||||
|
target="_blank"
|
||||||
|
className="w-4 text-xs"
|
||||||
|
text="F"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
href={line.backendUrl}
|
||||||
|
target="_blank"
|
||||||
|
className="w-4 text-xs"
|
||||||
|
text="B"
|
||||||
|
/>
|
||||||
|
<p>{line.subitems.join(" -> ")}</p>
|
||||||
|
<p>{line.name}</p>
|
||||||
|
<Chip>{line.type}</Chip>
|
||||||
|
<Chip
|
||||||
|
className={
|
||||||
|
line.severity === "Very High"
|
||||||
|
? "bg-[#f00] font-bold !opacity-100"
|
||||||
|
: line.severity === "High"
|
||||||
|
? "bg-[#ff6600] font-bold !opacity-100"
|
||||||
|
: line.severity === "Medium"
|
||||||
|
? "bg-[#fff344] !opacity-100"
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{line.severity}
|
||||||
|
</Chip>
|
||||||
|
<ToolTip content={line.recommandation} placement="left">
|
||||||
|
<p>{line.description}</p>
|
||||||
|
</ToolTip>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</ContentPanel>
|
||||||
|
),
|
||||||
|
[testReport.lines, testReport.title]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppLayout navTitle={"Checkup"} contentPanel={contentPanel} {...props} />
|
<AppLayout navTitle={"Checkup"} contentPanel={contentPanel} {...props} />
|
||||||
);
|
);
|
||||||
|
|
|
@ -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,408 +25,445 @@ 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(
|
||||||
wrapper: string,
|
(
|
||||||
properties?: Record<string, string>,
|
transformation: (
|
||||||
addInnerNewLines?: boolean
|
value: string,
|
||||||
) {
|
selectionStart: number,
|
||||||
transformationWrapper((value, selectionStart, selectionEnd) => {
|
selectedEnd: number
|
||||||
let prepend = wrapper;
|
) => { prependLength: number; transformedValue: string }
|
||||||
let append = wrapper;
|
) => {
|
||||||
|
const textarea =
|
||||||
|
document.querySelector<HTMLTextAreaElement>("#editorTextArea");
|
||||||
|
if (textarea) {
|
||||||
|
const { value, selectionStart, selectionEnd } = textarea;
|
||||||
|
|
||||||
if (properties) {
|
const { prependLength, transformedValue } = transformation(
|
||||||
prepend = `<${wrapper}${Object.entries(properties).map(
|
value,
|
||||||
([propertyName, propertyValue]) =>
|
selectionStart,
|
||||||
` ${propertyName}="${propertyValue}"`
|
selectionEnd
|
||||||
)}>`;
|
);
|
||||||
append = `</${wrapper}>`;
|
|
||||||
|
textarea.value = transformedValue;
|
||||||
|
handleInput(textarea.value);
|
||||||
|
|
||||||
|
textarea.focus();
|
||||||
|
textarea.selectionStart = selectionStart + prependLength;
|
||||||
|
textarea.selectionEnd = selectionEnd + prependLength;
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
[handleInput]
|
||||||
|
);
|
||||||
|
|
||||||
if (addInnerNewLines === true) {
|
const wrap = useCallback(
|
||||||
prepend = `${prepend}\n`;
|
(
|
||||||
append = `\n${append}`;
|
wrapper: string,
|
||||||
|
properties?: Record<string, string>,
|
||||||
|
addInnerNewLines?: boolean
|
||||||
|
) => {
|
||||||
|
transformationWrapper((value, selectionStart, selectionEnd) => {
|
||||||
|
let prepend = wrapper;
|
||||||
|
let append = wrapper;
|
||||||
|
|
||||||
|
if (properties) {
|
||||||
|
prepend = `<${wrapper}${Object.entries(properties).map(
|
||||||
|
([propertyName, propertyValue]) =>
|
||||||
|
` ${propertyName}="${propertyValue}"`
|
||||||
|
)}>`;
|
||||||
|
append = `</${wrapper}>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (addInnerNewLines === true) {
|
||||||
|
prepend = `${prepend}\n`;
|
||||||
|
append = `\n${append}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
let newValue = "";
|
||||||
|
newValue += value.slice(0, selectionStart);
|
||||||
|
newValue += prepend;
|
||||||
|
newValue += value.slice(selectionStart, selectionEnd);
|
||||||
|
newValue += append;
|
||||||
|
newValue += value.slice(selectionEnd);
|
||||||
|
return { prependLength: prepend.length, transformedValue: newValue };
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[transformationWrapper]
|
||||||
|
);
|
||||||
|
|
||||||
|
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) {
|
||||||
|
const { value, selectionStart, selectionEnd } = textarea;
|
||||||
|
|
||||||
|
if (
|
||||||
|
value.slice(selectionStart - wrapper.length, selectionStart) ===
|
||||||
|
wrapper &&
|
||||||
|
value.slice(selectionEnd, selectionEnd + wrapper.length) === wrapper
|
||||||
|
) {
|
||||||
|
unwrap(wrapper);
|
||||||
|
} else {
|
||||||
|
wrap(wrapper, properties, addInnerNewLines);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
[unwrap, wrap]
|
||||||
|
);
|
||||||
|
|
||||||
let newValue = "";
|
const preline = useCallback(
|
||||||
newValue += value.slice(0, selectionStart);
|
(prepend: string) => {
|
||||||
newValue += prepend;
|
transformationWrapper((value, selectionStart) => {
|
||||||
newValue += value.slice(selectionStart, selectionEnd);
|
const lastNewLine =
|
||||||
newValue += append;
|
value.slice(0, selectionStart).lastIndexOf("\n") + 1;
|
||||||
newValue += value.slice(selectionEnd);
|
|
||||||
return { prependLength: prepend.length, transformedValue: newValue };
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleWrap(
|
let newValue = "";
|
||||||
wrapper: string,
|
newValue += value.slice(0, lastNewLine);
|
||||||
properties?: Record<string, string>,
|
newValue += prepend;
|
||||||
addInnerNewLines?: boolean
|
newValue += value.slice(lastNewLine);
|
||||||
) {
|
|
||||||
const textarea =
|
|
||||||
document.querySelector<HTMLTextAreaElement>("#editorTextArea");
|
|
||||||
if (textarea) {
|
|
||||||
const { value, selectionStart, selectionEnd } = textarea;
|
|
||||||
|
|
||||||
if (
|
return { prependLength: prepend.length, transformedValue: newValue };
|
||||||
value.slice(selectionStart - wrapper.length, selectionStart) ===
|
});
|
||||||
wrapper &&
|
},
|
||||||
value.slice(selectionEnd, selectionEnd + wrapper.length) === wrapper
|
[transformationWrapper]
|
||||||
) {
|
);
|
||||||
unwrap(wrapper);
|
|
||||||
} else {
|
|
||||||
wrap(wrapper, properties, addInnerNewLines);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function unwrap(wrapper: string) {
|
const insert = useCallback(
|
||||||
transformationWrapper((value, selectionStart, selectionEnd) => {
|
(prepend: string) => {
|
||||||
let newValue = "";
|
transformationWrapper((value, selectionStart) => {
|
||||||
newValue += value.slice(0, selectionStart - wrapper.length);
|
let newValue = "";
|
||||||
newValue += value.slice(selectionStart, selectionEnd);
|
newValue += value.slice(0, selectionStart);
|
||||||
newValue += value.slice(wrapper.length + selectionEnd);
|
newValue += prepend;
|
||||||
return { prependLength: -wrapper.length, transformedValue: newValue };
|
newValue += value.slice(selectionStart);
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function preline(prepend: string) {
|
return { prependLength: prepend.length, transformedValue: newValue };
|
||||||
transformationWrapper((value, selectionStart) => {
|
});
|
||||||
const lastNewLine = value.slice(0, selectionStart).lastIndexOf("\n") + 1;
|
},
|
||||||
|
[transformationWrapper]
|
||||||
|
);
|
||||||
|
|
||||||
let newValue = "";
|
const appendDoc = useCallback(
|
||||||
newValue += value.slice(0, lastNewLine);
|
(append: string) => {
|
||||||
newValue += prepend;
|
transformationWrapper((value) => {
|
||||||
newValue += value.slice(lastNewLine);
|
const newValue = value + append;
|
||||||
|
return { prependLength: 0, transformedValue: newValue };
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[transformationWrapper]
|
||||||
|
);
|
||||||
|
|
||||||
return { prependLength: prepend.length, transformedValue: newValue };
|
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.
|
||||||
|
<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
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<textarea
|
||||||
|
readOnly
|
||||||
|
id="htmlMdTextArea"
|
||||||
|
title="Ouput textarea"
|
||||||
|
onPaste={(event) => {
|
||||||
|
const turndownService = new TurndownService({
|
||||||
|
headingStyle: "atx",
|
||||||
|
codeBlockStyle: "fenced",
|
||||||
|
bulletListMarker: "-",
|
||||||
|
emDelimiter: "_",
|
||||||
|
strongDelimiter: "**",
|
||||||
|
});
|
||||||
|
|
||||||
function insert(prepend: string) {
|
let paste = event.clipboardData.getData("text/html");
|
||||||
transformationWrapper((value, selectionStart) => {
|
paste = paste.replace(/<!--.*?-->/u, "");
|
||||||
let newValue = "";
|
paste = turndownService.turndown(paste);
|
||||||
newValue += value.slice(0, selectionStart);
|
paste = paste.replace(/<!--.*?-->/u, "");
|
||||||
newValue += prepend;
|
|
||||||
newValue += value.slice(selectionStart);
|
|
||||||
|
|
||||||
return { prependLength: prepend.length, transformedValue: newValue };
|
const target = event.target as HTMLTextAreaElement;
|
||||||
});
|
target.value = paste;
|
||||||
}
|
target.select();
|
||||||
|
event.preventDefault();
|
||||||
function appendDoc(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
|
|
||||||
);
|
|
||||||
|
|
||||||
textarea.value = transformedValue;
|
|
||||||
handleInput(textarea.value);
|
|
||||||
|
|
||||||
textarea.focus();
|
|
||||||
textarea.selectionStart = selectionStart + prependLength;
|
|
||||||
textarea.selectionEnd = selectionEnd + prependLength;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const contentPanel = (
|
|
||||||
<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.{" "}
|
|
||||||
<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
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<textarea
|
|
||||||
readOnly
|
|
||||||
id="htmlMdTextArea"
|
|
||||||
title="Ouput textarea"
|
|
||||||
onPaste={(event) => {
|
|
||||||
const turndownService = new TurndownService({
|
|
||||||
headingStyle: "atx",
|
|
||||||
codeBlockStyle: "fenced",
|
|
||||||
bulletListMarker: "-",
|
|
||||||
emDelimiter: "_",
|
|
||||||
strongDelimiter: "**",
|
|
||||||
});
|
|
||||||
|
|
||||||
let paste = event.clipboardData.getData("text/html");
|
|
||||||
paste = paste.replace(/<!--.*?-->/u, "");
|
|
||||||
paste = turndownService.turndown(paste);
|
|
||||||
paste = paste.replace(/<!--.*?-->/u, "");
|
|
||||||
|
|
||||||
const target = event.target as HTMLTextAreaElement;
|
|
||||||
target.value = paste;
|
|
||||||
target.select();
|
|
||||||
event.preventDefault();
|
|
||||||
}}
|
|
||||||
className="h-[50vh] w-[50vw] mobile:w-[75vw]"
|
|
||||||
/>
|
|
||||||
</Popup>
|
|
||||||
|
|
||||||
<div className="mb-4 flex flex-row gap-2">
|
|
||||||
<ToolTip
|
|
||||||
content={
|
|
||||||
<div className="grid gap-2">
|
|
||||||
<h3 className="text-lg">Headers</h3>
|
|
||||||
<Button onClick={() => preline("# ")} text={"H1"} />
|
|
||||||
<Button onClick={() => preline("## ")} text={"H2"} />
|
|
||||||
<Button onClick={() => preline("### ")} text={"H3"} />
|
|
||||||
<Button onClick={() => preline("#### ")} text={"H4"} />
|
|
||||||
<Button onClick={() => preline("##### ")} text={"H5"} />
|
|
||||||
<Button onClick={() => preline("###### ")} text={"H6"} />
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Button icon={Icon.Title} />
|
|
||||||
</ToolTip>
|
|
||||||
|
|
||||||
<ToolTip
|
|
||||||
placement="bottom"
|
|
||||||
content={<h3 className="text-lg">Toggle Bold</h3>}
|
|
||||||
>
|
|
||||||
<Button onClick={() => toggleWrap("**")} icon={Icon.FormatBold} />
|
|
||||||
</ToolTip>
|
|
||||||
|
|
||||||
<ToolTip
|
|
||||||
placement="bottom"
|
|
||||||
content={<h3 className="text-lg">Toggle Italic</h3>}
|
|
||||||
>
|
|
||||||
<Button onClick={() => toggleWrap("_")} icon={Icon.FormatItalic} />
|
|
||||||
</ToolTip>
|
|
||||||
|
|
||||||
<ToolTip
|
|
||||||
placement="bottom"
|
|
||||||
content={
|
|
||||||
<>
|
|
||||||
<h3 className="text-lg">Toggle Inline Code</h3>
|
|
||||||
<p>
|
|
||||||
Makes the text monospace (like text from a computer terminal).
|
|
||||||
Usually used for stylistic purposes in transcripts.
|
|
||||||
</p>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Button onClick={() => toggleWrap("`")} icon={Icon.Code} />
|
|
||||||
</ToolTip>
|
|
||||||
|
|
||||||
<ToolTip
|
|
||||||
placement="bottom"
|
|
||||||
content={
|
|
||||||
<>
|
|
||||||
<h3 className="text-lg">Insert footnote</h3>
|
|
||||||
<p>When inserted “x”</p>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
insert("[^x]");
|
|
||||||
appendDoc("\n\n[^x]: This is a footnote.");
|
|
||||||
}}
|
}}
|
||||||
icon={Icon.Superscript}
|
className="h-[50vh] w-[50vw] mobile:w-[75vw]"
|
||||||
/>
|
/>
|
||||||
</ToolTip>
|
</Popup>
|
||||||
|
|
||||||
<ToolTip
|
<div className="mb-4 flex flex-row gap-2">
|
||||||
placement="bottom"
|
<ToolTip
|
||||||
content={
|
content={
|
||||||
<>
|
|
||||||
<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.
|
|
||||||
</p>
|
|
||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
|
<h3 className="text-lg">Headers</h3>
|
||||||
|
<Button onClick={() => preline("# ")} text={"H1"} />
|
||||||
|
<Button onClick={() => preline("## ")} text={"H2"} />
|
||||||
|
<Button onClick={() => preline("### ")} text={"H3"} />
|
||||||
|
<Button onClick={() => preline("#### ")} text={"H4"} />
|
||||||
|
<Button onClick={() => preline("##### ")} text={"H5"} />
|
||||||
|
<Button onClick={() => preline("###### ")} text={"H6"} />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Button icon={Icon.Title} />
|
||||||
|
</ToolTip>
|
||||||
|
|
||||||
|
<ToolTip
|
||||||
|
placement="bottom"
|
||||||
|
content={<h3 className="text-lg">Toggle Bold</h3>}
|
||||||
|
>
|
||||||
|
<Button onClick={() => toggleWrap("**")} icon={Icon.FormatBold} />
|
||||||
|
</ToolTip>
|
||||||
|
|
||||||
|
<ToolTip
|
||||||
|
placement="bottom"
|
||||||
|
content={<h3 className="text-lg">Toggle Italic</h3>}
|
||||||
|
>
|
||||||
|
<Button onClick={() => toggleWrap("_")} icon={Icon.FormatItalic} />
|
||||||
|
</ToolTip>
|
||||||
|
|
||||||
|
<ToolTip
|
||||||
|
placement="bottom"
|
||||||
|
content={
|
||||||
|
<>
|
||||||
|
<h3 className="text-lg">Toggle Inline Code</h3>
|
||||||
|
<p>
|
||||||
|
Makes the text monospace (like text from a computer terminal).
|
||||||
|
Usually used for stylistic purposes in transcripts.
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Button onClick={() => toggleWrap("`")} icon={Icon.Code} />
|
||||||
|
</ToolTip>
|
||||||
|
|
||||||
|
<ToolTip
|
||||||
|
placement="bottom"
|
||||||
|
content={
|
||||||
|
<>
|
||||||
|
<h3 className="text-lg">Insert footnote</h3>
|
||||||
|
<p>When inserted “x”</p>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
insert("[^x]");
|
||||||
|
appendDoc("\n\n[^x]: This is a footnote.");
|
||||||
|
}}
|
||||||
|
icon={Icon.Superscript}
|
||||||
|
/>
|
||||||
|
</ToolTip>
|
||||||
|
|
||||||
|
<ToolTip
|
||||||
|
placement="bottom"
|
||||||
|
content={
|
||||||
|
<>
|
||||||
|
<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.
|
||||||
|
</p>
|
||||||
|
<div className="grid gap-2">
|
||||||
|
<ToolTip
|
||||||
|
placement="right"
|
||||||
|
content={
|
||||||
|
<>
|
||||||
|
<h3 className="text-lg">Transcript container</h3>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
onClick={() => wrap("Transcript", {}, true)}
|
||||||
|
icon={Icon.AddBox}
|
||||||
|
/>
|
||||||
|
</ToolTip>
|
||||||
|
<ToolTip
|
||||||
|
placement="right"
|
||||||
|
content={
|
||||||
|
<>
|
||||||
|
<h3 className="text-lg">Transcript speech line</h3>
|
||||||
|
<p>
|
||||||
|
Use to add a dialogue/transcript line. Change the{" "}
|
||||||
|
<kbd>name</kbd> property to chang the name of the
|
||||||
|
speaker
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
onClick={() => wrap("Line", { name: "speaker" })}
|
||||||
|
icon={Icon.RecordVoiceOver}
|
||||||
|
/>
|
||||||
|
</ToolTip>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Button icon={Icon.RecordVoiceOver} />
|
||||||
|
</ToolTip>
|
||||||
|
|
||||||
|
<ToolTip
|
||||||
|
placement="bottom"
|
||||||
|
content={<h3 className="text-lg">Inset box</h3>}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
onClick={() => wrap("InsetBox", {}, true)}
|
||||||
|
icon={Icon.CheckBoxOutlineBlank}
|
||||||
|
/>
|
||||||
|
</ToolTip>
|
||||||
|
<ToolTip
|
||||||
|
placement="bottom"
|
||||||
|
content={<h3 className="text-lg">Scene break</h3>}
|
||||||
|
>
|
||||||
|
<Button onClick={() => insert("\n* * *\n")} icon={Icon.MoreHoriz} />
|
||||||
|
</ToolTip>
|
||||||
|
<ToolTip
|
||||||
|
content={
|
||||||
|
<div className="flex flex-col place-items-center gap-2">
|
||||||
|
<h3 className="text-lg">Links</h3>
|
||||||
<ToolTip
|
<ToolTip
|
||||||
placement="right"
|
placement="right"
|
||||||
content={
|
content={
|
||||||
<>
|
<>
|
||||||
<h3 className="text-lg">Transcript container</h3>
|
<h3 className="text-lg">External Link</h3>
|
||||||
</>
|
<p className="text-xs">
|
||||||
}
|
Provides a link to another webpage / website
|
||||||
>
|
|
||||||
<Button
|
|
||||||
onClick={() => wrap("Transcript", {}, true)}
|
|
||||||
icon={Icon.AddBox}
|
|
||||||
/>
|
|
||||||
</ToolTip>
|
|
||||||
<ToolTip
|
|
||||||
placement="right"
|
|
||||||
content={
|
|
||||||
<>
|
|
||||||
<h3 className="text-lg">Transcript speech line</h3>
|
|
||||||
<p>
|
|
||||||
Use to add a dialogue/transcript line. Change the{" "}
|
|
||||||
<kbd>name</kbd> property to chang the name of the
|
|
||||||
speaker
|
|
||||||
</p>
|
</p>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => wrap("Line", { name: "speaker" })}
|
onClick={() => insert("[Link name](https://domain.com)")}
|
||||||
icon={Icon.RecordVoiceOver}
|
icon={Icon.Link}
|
||||||
|
text={"External"}
|
||||||
|
/>
|
||||||
|
</ToolTip>
|
||||||
|
|
||||||
|
<ToolTip
|
||||||
|
placement="right"
|
||||||
|
content={
|
||||||
|
<>
|
||||||
|
<h3 className="text-lg">Intralink</h3>
|
||||||
|
<p className="text-xs">
|
||||||
|
Interlinks are used to add links to a header within the
|
||||||
|
same document
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
onClick={() => wrap("IntraLink", {})}
|
||||||
|
icon={Icon.Link}
|
||||||
|
text={"Internal"}
|
||||||
|
/>
|
||||||
|
</ToolTip>
|
||||||
|
<ToolTip
|
||||||
|
placement="right"
|
||||||
|
content={
|
||||||
|
<>
|
||||||
|
<h3 className="text-lg">Intralink (with target)</h3>{" "}
|
||||||
|
<p className="text-xs">
|
||||||
|
Use this one if you want the intralink text to be
|
||||||
|
different from the target header’s name.
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
onClick={() => wrap("IntraLink", { target: "target" })}
|
||||||
|
icon={Icon.Link}
|
||||||
|
text="Internal (w/ target)"
|
||||||
/>
|
/>
|
||||||
</ToolTip>
|
</ToolTip>
|
||||||
</div>
|
</div>
|
||||||
</>
|
}
|
||||||
}
|
>
|
||||||
>
|
<Button icon={Icon.Link} />
|
||||||
<Button icon={Icon.RecordVoiceOver} />
|
</ToolTip>
|
||||||
</ToolTip>
|
|
||||||
|
|
||||||
<ToolTip
|
<ToolTip
|
||||||
placement="bottom"
|
placement="bottom"
|
||||||
content={<h3 className="text-lg">Inset box</h3>}
|
content={
|
||||||
>
|
<h3 className="text-lg">Player’s name placeholder</h3>
|
||||||
<Button
|
}
|
||||||
onClick={() => wrap("InsetBox", {}, true)}
|
>
|
||||||
icon={Icon.CheckBoxOutlineBlank}
|
<Button onClick={() => insert("<player>")} icon={Icon.Person} />
|
||||||
/>
|
</ToolTip>
|
||||||
</ToolTip>
|
|
||||||
<ToolTip
|
|
||||||
placement="bottom"
|
|
||||||
content={<h3 className="text-lg">Scene break</h3>}
|
|
||||||
>
|
|
||||||
<Button onClick={() => insert("\n* * *\n")} icon={Icon.MoreHoriz} />
|
|
||||||
</ToolTip>
|
|
||||||
<ToolTip
|
|
||||||
content={
|
|
||||||
<div className="flex flex-col place-items-center gap-2">
|
|
||||||
<h3 className="text-lg">Links</h3>
|
|
||||||
<ToolTip
|
|
||||||
placement="right"
|
|
||||||
content={
|
|
||||||
<>
|
|
||||||
<h3 className="text-lg">External Link</h3>
|
|
||||||
<p className="text-xs">
|
|
||||||
Provides a link to another webpage / website
|
|
||||||
</p>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
onClick={() => insert("[Link name](https://domain.com)")}
|
|
||||||
icon={Icon.Link}
|
|
||||||
text={"External"}
|
|
||||||
/>
|
|
||||||
</ToolTip>
|
|
||||||
|
|
||||||
<ToolTip
|
<ToolTip
|
||||||
placement="right"
|
placement="bottom"
|
||||||
content={
|
content={<h3 className="text-lg">Open HTML Converter</h3>}
|
||||||
<>
|
>
|
||||||
<h3 className="text-lg">Intralink</h3>
|
<Button
|
||||||
<p className="text-xs">
|
onClick={() => {
|
||||||
Interlinks are used to add links to a header within the
|
setConverterOpened(true);
|
||||||
same document
|
}}
|
||||||
</p>
|
icon={Icon.Html}
|
||||||
</>
|
/>
|
||||||
}
|
</ToolTip>
|
||||||
>
|
|
||||||
<Button
|
|
||||||
onClick={() => wrap("IntraLink", {})}
|
|
||||||
icon={Icon.Link}
|
|
||||||
text={"Internal"}
|
|
||||||
/>
|
|
||||||
</ToolTip>
|
|
||||||
<ToolTip
|
|
||||||
placement="right"
|
|
||||||
content={
|
|
||||||
<>
|
|
||||||
<h3 className="text-lg">Intralink (with target)</h3>{" "}
|
|
||||||
<p className="text-xs">
|
|
||||||
Use this one if you want the intralink text to be
|
|
||||||
different from the target header’s name.
|
|
||||||
</p>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
onClick={() => wrap("IntraLink", { target: "target" })}
|
|
||||||
icon={Icon.Link}
|
|
||||||
text="Internal (w/ target)"
|
|
||||||
/>
|
|
||||||
</ToolTip>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Button icon={Icon.Link} />
|
|
||||||
</ToolTip>
|
|
||||||
|
|
||||||
<ToolTip
|
|
||||||
placement="bottom"
|
|
||||||
content={<h3 className="text-lg">Player’s name placeholder</h3>}
|
|
||||||
>
|
|
||||||
<Button onClick={() => insert("<player>")} icon={Icon.Person} />
|
|
||||||
</ToolTip>
|
|
||||||
|
|
||||||
<ToolTip
|
|
||||||
placement="bottom"
|
|
||||||
content={<h3 className="text-lg">Open HTML Converter</h3>}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
setConverterOpened(true);
|
|
||||||
}}
|
|
||||||
icon={Icon.Html}
|
|
||||||
/>
|
|
||||||
</ToolTip>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="grid grid-cols-2 gap-8">
|
|
||||||
<div>
|
|
||||||
<h2>Editor</h2>
|
|
||||||
<textarea
|
|
||||||
id="editorTextArea"
|
|
||||||
onInput={(event) => {
|
|
||||||
const textarea = event.target as HTMLTextAreaElement;
|
|
||||||
handleInput(textarea.value);
|
|
||||||
}}
|
|
||||||
className="h-[70vh] w-full rounded-xl bg-mid !bg-opacity-40 p-8
|
|
||||||
font-mono text-black outline-none"
|
|
||||||
value={markdown}
|
|
||||||
title="Input textarea"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
<h2>Preview</h2>
|
<div className="grid grid-cols-2 gap-8">
|
||||||
<div className="h-[70vh] overflow-scroll rounded-xl bg-mid bg-opacity-40 p-8">
|
<div>
|
||||||
<Markdawn className="w-full" text={markdown} />
|
<h2>Editor</h2>
|
||||||
|
<textarea
|
||||||
|
id="editorTextArea"
|
||||||
|
onInput={(event) => {
|
||||||
|
const textarea = event.target as HTMLTextAreaElement;
|
||||||
|
handleInput(textarea.value);
|
||||||
|
}}
|
||||||
|
className="h-[70vh] w-full rounded-xl bg-mid !bg-opacity-40 p-8
|
||||||
|
font-mono text-black outline-none"
|
||||||
|
value={markdown}
|
||||||
|
title="Input textarea"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2>Preview</h2>
|
||||||
|
<div className="h-[70vh] overflow-scroll rounded-xl bg-mid bg-opacity-40 p-8">
|
||||||
|
<Markdawn className="w-full" text={markdown} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="mt-8">
|
<div className="mt-8">
|
||||||
<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"
|
||||||
|
|
|
@ -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<
|
||||||
GetLibraryItemQuery["libraryItems"]
|
NonNullable<
|
||||||
>["data"][number]["attributes"];
|
GetLibraryItemQuery["libraryItems"]
|
||||||
|
>["data"][number]["attributes"]
|
||||||
|
>;
|
||||||
itemId: NonNullable<
|
itemId: NonNullable<
|
||||||
GetLibraryItemQuery["libraryItems"]
|
GetLibraryItemQuery["libraryItems"]
|
||||||
>["data"][number]["id"];
|
>["data"][number]["id"];
|
||||||
|
@ -67,448 +69,474 @@ 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(
|
||||||
<SubPanel>
|
() =>
|
||||||
<ReturnButton
|
item.metadata?.[0]?.__typename === "ComponentMetadataGroup" &&
|
||||||
href="/library/"
|
item.metadata[0].subtype?.data?.attributes?.slug === "variant-set",
|
||||||
title={langui.library}
|
[item.metadata]
|
||||||
langui={langui}
|
|
||||||
displayOn={ReturnButtonType.Desktop}
|
|
||||||
horizontalLine
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className="grid gap-4">
|
|
||||||
<NavOption title={langui.summary} url="#summary" border />
|
|
||||||
|
|
||||||
{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 && (
|
|
||||||
<NavOption
|
|
||||||
title={isVariantSet ? langui.variants : langui.subitems}
|
|
||||||
url={isVariantSet ? "#variants" : "#subitems"}
|
|
||||||
border
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{item?.contents && item.contents.data.length > 0 && (
|
|
||||||
<NavOption title={langui.contents} url="#contents" border />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</SubPanel>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const contentPanel = (
|
const displayOpenScans = useMemo(
|
||||||
<ContentPanel width={ContentPanelWidthSizes.Full}>
|
() =>
|
||||||
<LightBox />
|
item.contents?.data.some(
|
||||||
|
(content) =>
|
||||||
|
content.attributes?.scan_set && content.attributes.scan_set.length > 0
|
||||||
|
),
|
||||||
|
[item.contents?.data]
|
||||||
|
);
|
||||||
|
|
||||||
<ReturnButton
|
const subPanel = useMemo(
|
||||||
href="/library/"
|
() => (
|
||||||
title={langui.library}
|
<SubPanel>
|
||||||
langui={langui}
|
<ReturnButton
|
||||||
displayOn={ReturnButtonType.Mobile}
|
href="/library/"
|
||||||
className="mb-10"
|
title={langui.library}
|
||||||
/>
|
langui={langui}
|
||||||
<div className="grid place-items-center gap-12">
|
displayOn={ReturnButtonType.Desktop}
|
||||||
<div
|
horizontalLine
|
||||||
className="relative h-[50vh] w-full
|
/>
|
||||||
cursor-pointer drop-shadow-shade-xl desktop:mb-16 mobile:h-[60vh]"
|
|
||||||
onClick={() => {
|
<div className="grid gap-4">
|
||||||
if (item?.thumbnail?.data?.attributes) {
|
<NavOption title={langui.summary} url="#summary" border />
|
||||||
openLightBox([
|
|
||||||
getAssetURL(
|
{item.gallery && item.gallery.data.length > 0 && (
|
||||||
item.thumbnail.data.attributes.url,
|
<NavOption title={langui.gallery} url="#gallery" border />
|
||||||
ImageQuality.Large
|
)}
|
||||||
),
|
|
||||||
]);
|
<NavOption title={langui.details} url="#details" border />
|
||||||
}
|
|
||||||
}}
|
{item.subitems && item.subitems.data.length > 0 && (
|
||||||
>
|
<NavOption
|
||||||
{item?.thumbnail?.data?.attributes ? (
|
title={isVariantSet ? langui.variants : langui.subitems}
|
||||||
<Img
|
url={isVariantSet ? "#variants" : "#subitems"}
|
||||||
image={item.thumbnail.data.attributes}
|
border
|
||||||
quality={ImageQuality.Large}
|
|
||||||
className="h-full w-full object-contain"
|
|
||||||
/>
|
/>
|
||||||
) : (
|
)}
|
||||||
<div className="aspect-[21/29.7] w-full rounded-xl bg-light"></div>
|
|
||||||
|
{item.contents && item.contents.data.length > 0 && (
|
||||||
|
<NavOption title={langui.contents} url="#contents" border />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
</SubPanel>
|
||||||
|
),
|
||||||
|
[isVariantSet, item.contents, item.gallery, item.subitems, langui]
|
||||||
|
);
|
||||||
|
|
||||||
<InsetBox id="summary" className="grid place-items-center">
|
const contentPanel = useMemo(
|
||||||
<div className="grid w-[clamp(0px,100%,42rem)] place-items-center gap-8">
|
() => (
|
||||||
{item?.subitem_of?.data[0]?.attributes && (
|
<ContentPanel width={ContentPanelWidthSizes.Full}>
|
||||||
<div className="grid place-items-center">
|
<LightBox />
|
||||||
<p>{langui.subitem_of}</p>
|
|
||||||
<Button
|
<ReturnButton
|
||||||
href={`/library/${item.subitem_of.data[0].attributes.slug}`}
|
href="/library/"
|
||||||
text={prettyinlineTitle(
|
title={langui.library}
|
||||||
"",
|
langui={langui}
|
||||||
item.subitem_of.data[0].attributes.title,
|
displayOn={ReturnButtonType.Mobile}
|
||||||
item.subitem_of.data[0].attributes.subtitle
|
className="mb-10"
|
||||||
)}
|
/>
|
||||||
/>
|
<div className="grid place-items-center gap-12">
|
||||||
</div>
|
<div
|
||||||
|
className="relative h-[50vh] w-full
|
||||||
|
cursor-pointer drop-shadow-shade-xl desktop:mb-16 mobile:h-[60vh]"
|
||||||
|
onClick={() => {
|
||||||
|
if (item.thumbnail?.data?.attributes) {
|
||||||
|
openLightBox([
|
||||||
|
getAssetURL(
|
||||||
|
item.thumbnail.data.attributes.url,
|
||||||
|
ImageQuality.Large
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item.thumbnail?.data?.attributes ? (
|
||||||
|
<Img
|
||||||
|
image={item.thumbnail.data.attributes}
|
||||||
|
quality={ImageQuality.Large}
|
||||||
|
className="h-full w-full object-contain"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className="aspect-[21/29.7] w-full rounded-xl bg-light"></div>
|
||||||
)}
|
)}
|
||||||
<div className="grid place-items-center text-center">
|
</div>
|
||||||
<h1 className="text-3xl">{item?.title}</h1>
|
|
||||||
{item && isDefinedAndNotEmpty(item.subtitle) && (
|
<InsetBox id="summary" className="grid place-items-center">
|
||||||
<h2 className="text-2xl">{item.subtitle}</h2>
|
<div className="grid w-[clamp(0px,100%,42rem)] place-items-center gap-8">
|
||||||
|
{item.subitem_of?.data[0]?.attributes && (
|
||||||
|
<div className="grid place-items-center">
|
||||||
|
<p>{langui.subitem_of}</p>
|
||||||
|
<Button
|
||||||
|
href={`/library/${item.subitem_of.data[0].attributes.slug}`}
|
||||||
|
text={prettyinlineTitle(
|
||||||
|
"",
|
||||||
|
item.subitem_of.data[0].attributes.title,
|
||||||
|
item.subitem_of.data[0].attributes.subtitle
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
<div className="grid place-items-center text-center">
|
||||||
|
<h1 className="text-3xl">{item.title}</h1>
|
||||||
<PreviewCardCTAs
|
{isDefinedAndNotEmpty(item.subtitle) && (
|
||||||
id={itemId}
|
<h2 className="text-2xl">{item.subtitle}</h2>
|
||||||
displayCTAs={!isUntangibleGroupItem(item?.metadata?.[0])}
|
|
||||||
langui={langui}
|
|
||||||
expand
|
|
||||||
/>
|
|
||||||
{item?.descriptions?.[0] && (
|
|
||||||
<p className="text-justify">{item.descriptions[0].description}</p>
|
|
||||||
)}
|
|
||||||
{!(
|
|
||||||
item?.metadata &&
|
|
||||||
item.metadata[0]?.__typename === "ComponentMetadataGroup" &&
|
|
||||||
(item.metadata[0].subtype?.data?.attributes?.slug ===
|
|
||||||
"variant-set" ||
|
|
||||||
item.metadata[0].subtype?.data?.attributes?.slug ===
|
|
||||||
"relation-set")
|
|
||||||
) && (
|
|
||||||
<>
|
|
||||||
{item?.urls && item.urls.length ? (
|
|
||||||
<div className="flex flex-row place-items-center gap-3">
|
|
||||||
<p>{langui.available_at}</p>
|
|
||||||
{filterHasAttributes(item.urls).map((url, index) => (
|
|
||||||
<Fragment key={index}>
|
|
||||||
<Button
|
|
||||||
href={url.url}
|
|
||||||
target={"_blank"}
|
|
||||||
text={prettyURL(url.url)}
|
|
||||||
/>
|
|
||||||
</Fragment>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<p>{langui.item_not_available}</p>
|
|
||||||
)}
|
)}
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</InsetBox>
|
|
||||||
|
|
||||||
{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
|
|
||||||
className="grid w-full grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))] items-end
|
|
||||||
gap-8"
|
|
||||||
>
|
|
||||||
{filterHasAttributes(item.gallery.data).map(
|
|
||||||
(galleryItem, index) => (
|
|
||||||
<Fragment key={galleryItem.id}>
|
|
||||||
<div
|
|
||||||
className="relative aspect-square cursor-pointer
|
|
||||||
transition-transform hover:scale-[1.02]"
|
|
||||||
onClick={() => {
|
|
||||||
const images: string[] = filterHasAttributes(
|
|
||||||
item.gallery?.data
|
|
||||||
).map((image) =>
|
|
||||||
getAssetURL(image.attributes.url, ImageQuality.Large)
|
|
||||||
);
|
|
||||||
openLightBox(images, index);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Img
|
|
||||||
className="h-full w-full rounded-lg
|
|
||||||
bg-light object-cover drop-shadow-shade-md"
|
|
||||||
image={galleryItem.attributes}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Fragment>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<InsetBox id="details" className="grid place-items-center">
|
|
||||||
<div className="place-items grid w-[clamp(0px,100%,42rem)] gap-8">
|
|
||||||
<h2 className="text-center text-2xl">{langui.details}</h2>
|
|
||||||
<div
|
|
||||||
className="grid place-items-center gap-y-8
|
|
||||||
desktop:grid-flow-col desktop:place-content-between"
|
|
||||||
>
|
|
||||||
{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">
|
|
||||||
<Chip>{prettyItemType(item.metadata[0], langui)}</Chip>
|
|
||||||
{"›"}
|
|
||||||
<Chip>{prettyItemSubType(item.metadata[0])}</Chip>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{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 && (
|
|
||||||
<div className="grid place-content-start place-items-center text-center">
|
|
||||||
<h3 className="text-xl">{langui.price}</h3>
|
|
||||||
<p>
|
|
||||||
{prettyPrice(
|
|
||||||
item.price,
|
|
||||||
currencies,
|
|
||||||
item.price.currency?.data?.attributes?.code
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
{item.price.currency?.data?.attributes?.code !==
|
|
||||||
appLayout.currency && (
|
|
||||||
<p>
|
|
||||||
{prettyPrice(item.price, currencies, appLayout.currency)}{" "}
|
|
||||||
<br />({langui.calculated?.toLowerCase()})
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{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">
|
|
||||||
{item.categories.data.map((category) => (
|
|
||||||
<Chip key={category.id}>{category.attributes?.name}</Chip>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
|
|
||||||
{item?.size && (
|
<PreviewCardCTAs
|
||||||
<div className="grid gap-8 mobile:place-items-center">
|
id={itemId}
|
||||||
<h3 className="text-xl">{langui.size}</h3>
|
displayCTAs={!isUntangibleGroupItem(item.metadata?.[0])}
|
||||||
<div
|
langui={langui}
|
||||||
className="grid w-full grid-flow-col place-content-between thin:grid-flow-row
|
expand
|
||||||
thin:place-content-center thin:gap-8"
|
/>
|
||||||
>
|
{item.descriptions?.[0] && (
|
||||||
<div
|
<p className="text-justify">
|
||||||
className="grid place-items-center gap-x-4 desktop:grid-flow-col
|
{item.descriptions[0].description}
|
||||||
desktop:place-items-start"
|
</p>
|
||||||
>
|
)}
|
||||||
<p className="font-bold">{langui.width}:</p>
|
{!(
|
||||||
<div>
|
item.metadata &&
|
||||||
<p>{item.size.width} mm</p>
|
item.metadata[0]?.__typename === "ComponentMetadataGroup" &&
|
||||||
<p>{convertMmToInch(item.size.width)} in</p>
|
(item.metadata[0].subtype?.data?.attributes?.slug ===
|
||||||
</div>
|
"variant-set" ||
|
||||||
</div>
|
item.metadata[0].subtype?.data?.attributes?.slug ===
|
||||||
<div
|
"relation-set")
|
||||||
className="grid place-items-center gap-x-4 desktop:grid-flow-col
|
) && (
|
||||||
desktop:place-items-start"
|
|
||||||
>
|
|
||||||
<p className="font-bold">{langui.height}:</p>
|
|
||||||
<div>
|
|
||||||
<p>{item.size.height} mm</p>
|
|
||||||
<p>{convertMmToInch(item.size.height)} in</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{isDefined(item.size.thickness) && (
|
|
||||||
<div
|
|
||||||
className="grid place-items-center gap-x-4 desktop:grid-flow-col
|
|
||||||
desktop:place-items-start"
|
|
||||||
>
|
|
||||||
<p className="font-bold">{langui.thickness}:</p>
|
|
||||||
<div>
|
|
||||||
<p>{item.size.thickness} mm</p>
|
|
||||||
<p>{convertMmToInch(item.size.thickness)} in</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{item?.metadata?.[0]?.__typename !== "ComponentMetadataGroup" &&
|
|
||||||
item?.metadata?.[0]?.__typename !== "ComponentMetadataOther" && (
|
|
||||||
<>
|
<>
|
||||||
<h3 className="text-xl">{langui.type_information}</h3>
|
{item.urls?.length ? (
|
||||||
<div className="grid w-full grid-cols-2 place-content-between">
|
<div className="flex flex-row place-items-center gap-3">
|
||||||
{item?.metadata?.[0]?.__typename ===
|
<p>{langui.available_at}</p>
|
||||||
"ComponentMetadataBooks" && (
|
{filterHasAttributes(item.urls).map((url, index) => (
|
||||||
<>
|
<Fragment key={index}>
|
||||||
<div className="flex flex-row place-content-start gap-4">
|
<Button
|
||||||
<p className="font-bold">{langui.pages}:</p>
|
href={url.url}
|
||||||
<p>{item.metadata[0].page_count}</p>
|
target={"_blank"}
|
||||||
</div>
|
text={prettyURL(url.url)}
|
||||||
|
/>
|
||||||
<div className="flex flex-row place-content-start gap-4">
|
</Fragment>
|
||||||
<p className="font-bold">{langui.binding}:</p>
|
))}
|
||||||
<p>
|
</div>
|
||||||
{item.metadata[0].binding_type ===
|
) : (
|
||||||
Enum_Componentmetadatabooks_Binding_Type.Paperback
|
<p>{langui.item_not_available}</p>
|
||||||
? langui.paperback
|
)}
|
||||||
: item.metadata[0].binding_type ===
|
|
||||||
Enum_Componentmetadatabooks_Binding_Type.Hardcover
|
|
||||||
? langui.hardcover
|
|
||||||
: ""}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex flex-row place-content-start gap-4">
|
|
||||||
<p className="font-bold">{langui.page_order}:</p>
|
|
||||||
<p>
|
|
||||||
{item.metadata[0].page_order ===
|
|
||||||
Enum_Componentmetadatabooks_Page_Order.LeftToRight
|
|
||||||
? langui.left_to_right
|
|
||||||
: langui.right_to_left}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex flex-row place-content-start gap-4">
|
|
||||||
<p className="font-bold">{langui.languages}:</p>
|
|
||||||
{item.metadata[0]?.languages?.data.map((lang) => (
|
|
||||||
<p key={lang.attributes?.code}>
|
|
||||||
{lang.attributes?.name}
|
|
||||||
</p>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</InsetBox>
|
</InsetBox>
|
||||||
|
|
||||||
{item?.subitems && item.subitems.data.length > 0 && (
|
{item.gallery && item.gallery.data.length > 0 && (
|
||||||
<div
|
<div id="gallery" className="grid w-full place-items-center gap-8">
|
||||||
id={isVariantSet ? "variants" : "subitems"}
|
<h2 className="text-2xl">{langui.gallery}</h2>
|
||||||
className="grid w-full place-items-center gap-8"
|
<div
|
||||||
>
|
className="grid w-full grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))] items-end
|
||||||
<h2 className="text-2xl">
|
gap-8"
|
||||||
{isVariantSet ? langui.variants : langui.subitems}
|
>
|
||||||
</h2>
|
{filterHasAttributes(item.gallery.data).map(
|
||||||
|
(galleryItem, index) => (
|
||||||
|
<Fragment key={galleryItem.id}>
|
||||||
|
<div
|
||||||
|
className="relative aspect-square cursor-pointer
|
||||||
|
transition-transform hover:scale-[1.02]"
|
||||||
|
onClick={() => {
|
||||||
|
const images: string[] = filterHasAttributes(
|
||||||
|
item.gallery?.data
|
||||||
|
).map((image) =>
|
||||||
|
getAssetURL(
|
||||||
|
image.attributes.url,
|
||||||
|
ImageQuality.Large
|
||||||
|
)
|
||||||
|
);
|
||||||
|
openLightBox(images, index);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Img
|
||||||
|
className="h-full w-full rounded-lg
|
||||||
|
bg-light object-cover drop-shadow-shade-md"
|
||||||
|
image={galleryItem.attributes}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Fragment>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{hoverable && (
|
<InsetBox id="details" className="grid place-items-center">
|
||||||
<WithLabel
|
<div className="place-items grid w-[clamp(0px,100%,42rem)] gap-8">
|
||||||
label={langui.always_show_info}
|
<h2 className="text-center text-2xl">{langui.details}</h2>
|
||||||
input={
|
<div
|
||||||
<Switch
|
className="grid place-items-center gap-y-8
|
||||||
setState={setKeepInfoVisible}
|
desktop:grid-flow-col desktop:place-content-between"
|
||||||
state={keepInfoVisible}
|
>
|
||||||
/>
|
{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">
|
||||||
|
<Chip>{prettyItemType(item.metadata[0], langui)}</Chip>
|
||||||
|
{"›"}
|
||||||
|
<Chip>{prettyItemSubType(item.metadata[0])}</Chip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div
|
{item.release_date && (
|
||||||
className="grid w-full grid-cols-[repeat(auto-fill,minmax(15rem,1fr))]
|
<div className="grid place-content-start place-items-center">
|
||||||
items-end gap-8 mobile:grid-cols-2 thin:grid-cols-1"
|
<h3 className="text-xl">{langui.release_date}</h3>
|
||||||
>
|
<p>{prettyDate(item.release_date)}</p>
|
||||||
{filterHasAttributes(item.subitems.data).map((subitem) => (
|
</div>
|
||||||
<Fragment key={subitem.id}>
|
)}
|
||||||
<PreviewCard
|
|
||||||
href={`/library/${subitem.attributes.slug}`}
|
{item.price && (
|
||||||
title={subitem.attributes.title}
|
<div className="grid place-content-start place-items-center text-center">
|
||||||
subtitle={subitem.attributes.subtitle}
|
<h3 className="text-xl">{langui.price}</h3>
|
||||||
thumbnail={subitem.attributes.thumbnail?.data?.attributes}
|
<p>
|
||||||
thumbnailAspectRatio="21/29.7"
|
{prettyPrice(
|
||||||
thumbnailRounded={false}
|
item.price,
|
||||||
keepInfoVisible={keepInfoVisible}
|
currencies,
|
||||||
topChips={
|
item.price.currency?.data?.attributes?.code
|
||||||
subitem.attributes.metadata &&
|
)}
|
||||||
subitem.attributes.metadata.length > 0 &&
|
</p>
|
||||||
subitem.attributes.metadata[0]
|
{item.price.currency?.data?.attributes?.code !==
|
||||||
? [prettyItemSubType(subitem.attributes.metadata[0])]
|
appLayout.currency && (
|
||||||
: []
|
<p>
|
||||||
}
|
{prettyPrice(
|
||||||
bottomChips={subitem.attributes.categories?.data.map(
|
item.price,
|
||||||
(category) => category.attributes?.short ?? ""
|
currencies,
|
||||||
|
appLayout.currency
|
||||||
|
)}{" "}
|
||||||
|
<br />({langui.calculated?.toLowerCase()})
|
||||||
|
</p>
|
||||||
)}
|
)}
|
||||||
metadata={{
|
</div>
|
||||||
currencies: currencies,
|
)}
|
||||||
release_date: subitem.attributes.release_date,
|
</div>
|
||||||
price: subitem.attributes.price,
|
|
||||||
position: "Bottom",
|
|
||||||
}}
|
|
||||||
infoAppend={
|
|
||||||
<PreviewCardCTAs
|
|
||||||
id={subitem.id}
|
|
||||||
langui={langui}
|
|
||||||
displayCTAs={
|
|
||||||
!isUntangibleGroupItem(
|
|
||||||
subitem.attributes.metadata?.[0]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Fragment>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{item?.contents && item.contents.data.length > 0 && (
|
{item.categories && item.categories.data.length > 0 && (
|
||||||
<div id="contents" className="grid w-full place-items-center gap-8">
|
<div className="flex flex-col place-items-center gap-2">
|
||||||
<h2 className="-mb-6 text-2xl">{langui.contents}</h2>
|
<h3 className="text-xl">{langui.categories}</h3>
|
||||||
{displayOpenScans && (
|
<div className="flex flex-row flex-wrap place-content-center gap-2">
|
||||||
<Button
|
{item.categories.data.map((category) => (
|
||||||
href={`/library/${item.slug}/scans`}
|
<Chip key={category.id}>{category.attributes?.name}</Chip>
|
||||||
text={langui.view_scans}
|
))}
|
||||||
/>
|
</div>
|
||||||
)}
|
</div>
|
||||||
<div className="grid w-full gap-4">
|
)}
|
||||||
{item.contents.data.map((content) => (
|
|
||||||
<ContentLine
|
{item.size && (
|
||||||
langui={langui}
|
<div className="grid gap-8 mobile:place-items-center">
|
||||||
content={content}
|
<h3 className="text-xl">{langui.size}</h3>
|
||||||
parentSlug={item.slug}
|
<div
|
||||||
key={content.id}
|
className="grid w-full grid-flow-col place-content-between thin:grid-flow-row
|
||||||
/>
|
thin:place-content-center thin:gap-8"
|
||||||
))}
|
>
|
||||||
|
<div
|
||||||
|
className="grid place-items-center gap-x-4 desktop:grid-flow-col
|
||||||
|
desktop:place-items-start"
|
||||||
|
>
|
||||||
|
<p className="font-bold">{langui.width}:</p>
|
||||||
|
<div>
|
||||||
|
<p>{item.size.width} mm</p>
|
||||||
|
<p>{convertMmToInch(item.size.width)} in</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="grid place-items-center gap-x-4 desktop:grid-flow-col
|
||||||
|
desktop:place-items-start"
|
||||||
|
>
|
||||||
|
<p className="font-bold">{langui.height}:</p>
|
||||||
|
<div>
|
||||||
|
<p>{item.size.height} mm</p>
|
||||||
|
<p>{convertMmToInch(item.size.height)} in</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{isDefined(item.size.thickness) && (
|
||||||
|
<div
|
||||||
|
className="grid place-items-center gap-x-4 desktop:grid-flow-col
|
||||||
|
desktop:place-items-start"
|
||||||
|
>
|
||||||
|
<p className="font-bold">{langui.thickness}:</p>
|
||||||
|
<div>
|
||||||
|
<p>{item.size.thickness} mm</p>
|
||||||
|
<p>{convertMmToInch(item.size.thickness)} in</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{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 ===
|
||||||
|
"ComponentMetadataBooks" && (
|
||||||
|
<>
|
||||||
|
<div className="flex flex-row place-content-start gap-4">
|
||||||
|
<p className="font-bold">{langui.pages}:</p>
|
||||||
|
<p>{item.metadata[0].page_count}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-row place-content-start gap-4">
|
||||||
|
<p className="font-bold">{langui.binding}:</p>
|
||||||
|
<p>
|
||||||
|
{item.metadata[0].binding_type ===
|
||||||
|
Enum_Componentmetadatabooks_Binding_Type.Paperback
|
||||||
|
? langui.paperback
|
||||||
|
: item.metadata[0].binding_type ===
|
||||||
|
Enum_Componentmetadatabooks_Binding_Type.Hardcover
|
||||||
|
? langui.hardcover
|
||||||
|
: ""}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-row place-content-start gap-4">
|
||||||
|
<p className="font-bold">{langui.page_order}:</p>
|
||||||
|
<p>
|
||||||
|
{item.metadata[0].page_order ===
|
||||||
|
Enum_Componentmetadatabooks_Page_Order.LeftToRight
|
||||||
|
? langui.left_to_right
|
||||||
|
: langui.right_to_left}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-row place-content-start gap-4">
|
||||||
|
<p className="font-bold">{langui.languages}:</p>
|
||||||
|
{item.metadata[0]?.languages?.data.map((lang) => (
|
||||||
|
<p key={lang.attributes?.code}>
|
||||||
|
{lang.attributes?.name}
|
||||||
|
</p>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</InsetBox>
|
||||||
)}
|
|
||||||
</div>
|
{item.subitems && item.subitems.data.length > 0 && (
|
||||||
</ContentPanel>
|
<div
|
||||||
|
id={isVariantSet ? "variants" : "subitems"}
|
||||||
|
className="grid w-full place-items-center gap-8"
|
||||||
|
>
|
||||||
|
<h2 className="text-2xl">
|
||||||
|
{isVariantSet ? langui.variants : langui.subitems}
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
{hoverable && (
|
||||||
|
<WithLabel
|
||||||
|
label={langui.always_show_info}
|
||||||
|
input={
|
||||||
|
<Switch
|
||||||
|
setState={setKeepInfoVisible}
|
||||||
|
state={keepInfoVisible}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div
|
||||||
|
className="grid w-full grid-cols-[repeat(auto-fill,minmax(15rem,1fr))]
|
||||||
|
items-end gap-8 mobile:grid-cols-2 thin:grid-cols-1"
|
||||||
|
>
|
||||||
|
{filterHasAttributes(item.subitems.data).map((subitem) => (
|
||||||
|
<Fragment key={subitem.id}>
|
||||||
|
<PreviewCard
|
||||||
|
href={`/library/${subitem.attributes.slug}`}
|
||||||
|
title={subitem.attributes.title}
|
||||||
|
subtitle={subitem.attributes.subtitle}
|
||||||
|
thumbnail={subitem.attributes.thumbnail?.data?.attributes}
|
||||||
|
thumbnailAspectRatio="21/29.7"
|
||||||
|
thumbnailRounded={false}
|
||||||
|
keepInfoVisible={keepInfoVisible}
|
||||||
|
topChips={
|
||||||
|
subitem.attributes.metadata &&
|
||||||
|
subitem.attributes.metadata.length > 0 &&
|
||||||
|
subitem.attributes.metadata[0]
|
||||||
|
? [prettyItemSubType(subitem.attributes.metadata[0])]
|
||||||
|
: []
|
||||||
|
}
|
||||||
|
bottomChips={subitem.attributes.categories?.data.map(
|
||||||
|
(category) => category.attributes?.short ?? ""
|
||||||
|
)}
|
||||||
|
metadata={{
|
||||||
|
currencies: currencies,
|
||||||
|
release_date: subitem.attributes.release_date,
|
||||||
|
price: subitem.attributes.price,
|
||||||
|
position: "Bottom",
|
||||||
|
}}
|
||||||
|
infoAppend={
|
||||||
|
<PreviewCardCTAs
|
||||||
|
id={subitem.id}
|
||||||
|
langui={langui}
|
||||||
|
displayCTAs={
|
||||||
|
!isUntangibleGroupItem(
|
||||||
|
subitem.attributes.metadata?.[0]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Fragment>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{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 && (
|
||||||
|
<Button
|
||||||
|
href={`/library/${item.slug}/scans`}
|
||||||
|
text={langui.view_scans}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<div className="grid w-full gap-4">
|
||||||
|
{item.contents.data.map((content) => (
|
||||||
|
<ContentLine
|
||||||
|
langui={langui}
|
||||||
|
content={content}
|
||||||
|
parentSlug={item.slug}
|
||||||
|
key={content.id}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</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 })
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -23,96 +23,112 @@ 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<
|
||||||
GetLibraryItemScansQuery["libraryItems"]
|
NonNullable<
|
||||||
>["data"][number]["attributes"];
|
GetLibraryItemScansQuery["libraryItems"]
|
||||||
|
>["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>
|
() => (
|
||||||
<ReturnButton
|
<SubPanel>
|
||||||
href={`/library/${item?.slug}`}
|
<ReturnButton
|
||||||
title={langui.item}
|
href={`/library/${item.slug}`}
|
||||||
langui={langui}
|
title={langui.item}
|
||||||
displayOn={ReturnButtonType.Desktop}
|
langui={langui}
|
||||||
horizontalLine
|
displayOn={ReturnButtonType.Desktop}
|
||||||
/>
|
horizontalLine
|
||||||
|
|
||||||
{item?.contents?.data.map((content) => (
|
|
||||||
<NavOption
|
|
||||||
key={content.id}
|
|
||||||
url={`#${content.attributes?.slug}`}
|
|
||||||
title={prettySlug(content.attributes?.slug, item.slug)}
|
|
||||||
subtitle={
|
|
||||||
content.attributes?.range[0]?.__typename ===
|
|
||||||
"ComponentRangePageRange"
|
|
||||||
? `${content.attributes.range[0].starting_page}` +
|
|
||||||
`→` +
|
|
||||||
`${content.attributes.range[0].ending_page}`
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
border
|
|
||||||
/>
|
/>
|
||||||
))}
|
|
||||||
</SubPanel>
|
{item.contents?.data.map((content) => (
|
||||||
|
<NavOption
|
||||||
|
key={content.id}
|
||||||
|
url={`#${content.attributes?.slug}`}
|
||||||
|
title={prettySlug(content.attributes?.slug, item.slug)}
|
||||||
|
subtitle={
|
||||||
|
content.attributes?.range[0]?.__typename ===
|
||||||
|
"ComponentRangePageRange"
|
||||||
|
? `${content.attributes.range[0].starting_page}` +
|
||||||
|
`→` +
|
||||||
|
`${content.attributes.range[0].ending_page}`
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
border
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</SubPanel>
|
||||||
|
),
|
||||||
|
[item.contents?.data, item.slug, langui]
|
||||||
);
|
);
|
||||||
|
|
||||||
const contentPanel = (
|
const contentPanel = useMemo(
|
||||||
<ContentPanel width={ContentPanelWidthSizes.Full}>
|
() => (
|
||||||
<LightBox />
|
<ContentPanel width={ContentPanelWidthSizes.Full}>
|
||||||
|
<LightBox />
|
||||||
|
|
||||||
<ReturnButton
|
<ReturnButton
|
||||||
href={`/library/${item?.slug}`}
|
href={`/library/${item.slug}`}
|
||||||
title={langui.item}
|
title={langui.item}
|
||||||
langui={langui}
|
|
||||||
displayOn={ReturnButtonType.Mobile}
|
|
||||||
className="mb-10"
|
|
||||||
/>
|
|
||||||
|
|
||||||
{item?.images && (
|
|
||||||
<ScanSetCover
|
|
||||||
images={item.images}
|
|
||||||
openLightBox={openLightBox}
|
|
||||||
languages={languages}
|
|
||||||
langui={langui}
|
langui={langui}
|
||||||
|
displayOn={ReturnButtonType.Mobile}
|
||||||
|
className="mb-10"
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
|
|
||||||
{item?.contents?.data.map((content) => (
|
{item.images && (
|
||||||
<Fragment key={content.id}>
|
<ScanSetCover
|
||||||
{content.attributes?.scan_set?.[0] && (
|
images={item.images}
|
||||||
<ScanSet
|
openLightBox={openLightBox}
|
||||||
scanSet={content.attributes.scan_set}
|
languages={languages}
|
||||||
openLightBox={openLightBox}
|
langui={langui}
|
||||||
slug={content.attributes.slug}
|
/>
|
||||||
title={prettySlug(content.attributes.slug, item.slug)}
|
)}
|
||||||
languages={languages}
|
|
||||||
langui={langui}
|
{item.contents?.data.map((content) => (
|
||||||
content={content.attributes.content}
|
<Fragment key={content.id}>
|
||||||
/>
|
{content.attributes?.scan_set?.[0] && (
|
||||||
)}
|
<ScanSet
|
||||||
</Fragment>
|
scanSet={content.attributes.scan_set}
|
||||||
))}
|
openLightBox={openLightBox}
|
||||||
</ContentPanel>
|
slug={content.attributes.slug}
|
||||||
|
title={prettySlug(content.attributes.slug, item.slug)}
|
||||||
|
languages={languages}
|
||||||
|
langui={langui}
|
||||||
|
content={content.attributes.content}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Fragment>
|
||||||
|
))}
|
||||||
|
</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,
|
||||||
|
|
|
@ -114,204 +114,226 @@ export default function Library(props: Props): JSX.Element {
|
||||||
[langui, groupingMethod, sortedItems]
|
[langui, groupingMethod, sortedItems]
|
||||||
);
|
);
|
||||||
|
|
||||||
const subPanel = (
|
const subPanel = useMemo(
|
||||||
<SubPanel>
|
() => (
|
||||||
<PanelHeader
|
<SubPanel>
|
||||||
icon={Icon.LibraryBooks}
|
<PanelHeader
|
||||||
title={langui.library}
|
icon={Icon.LibraryBooks}
|
||||||
description={langui.library_description}
|
title={langui.library}
|
||||||
/>
|
description={langui.library_description}
|
||||||
|
/>
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
className="mb-6 w-full"
|
className="mb-6 w-full"
|
||||||
placeholder={langui.search_title ?? undefined}
|
placeholder={langui.search_title ?? undefined}
|
||||||
state={searchName}
|
state={searchName}
|
||||||
setState={setSearchName}
|
setState={setSearchName}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<WithLabel
|
|
||||||
label={langui.group_by}
|
|
||||||
input={
|
|
||||||
<Select
|
|
||||||
className="w-full"
|
|
||||||
options={[
|
|
||||||
langui.category ?? "Category",
|
|
||||||
langui.type ?? "Type",
|
|
||||||
langui.release_year ?? "Year",
|
|
||||||
]}
|
|
||||||
state={groupingMethod}
|
|
||||||
setState={setGroupingMethod}
|
|
||||||
allowEmpty
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<WithLabel
|
|
||||||
label={langui.order_by}
|
|
||||||
input={
|
|
||||||
<Select
|
|
||||||
className="w-full"
|
|
||||||
options={[
|
|
||||||
langui.name ?? "Name",
|
|
||||||
langui.price ?? "Price",
|
|
||||||
langui.release_date ?? "Release date",
|
|
||||||
]}
|
|
||||||
state={sortingMethod}
|
|
||||||
setState={setSortingMethod}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<WithLabel
|
|
||||||
label={langui.show_subitems}
|
|
||||||
input={<Switch state={showSubitems} setState={setShowSubitems} />}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<WithLabel
|
|
||||||
label={langui.show_primary_items}
|
|
||||||
input={
|
|
||||||
<Switch state={showPrimaryItems} setState={setShowPrimaryItems} />
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<WithLabel
|
|
||||||
label={langui.show_secondary_items}
|
|
||||||
input={
|
|
||||||
<Switch state={showSecondaryItems} setState={setShowSecondaryItems} />
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{hoverable && (
|
|
||||||
<WithLabel
|
<WithLabel
|
||||||
label={langui.always_show_info}
|
label={langui.group_by}
|
||||||
input={
|
input={
|
||||||
<Switch state={keepInfoVisible} setState={setKeepInfoVisible} />
|
<Select
|
||||||
|
className="w-full"
|
||||||
|
options={[
|
||||||
|
langui.category ?? "Category",
|
||||||
|
langui.type ?? "Type",
|
||||||
|
langui.release_year ?? "Year",
|
||||||
|
]}
|
||||||
|
state={groupingMethod}
|
||||||
|
setState={setGroupingMethod}
|
||||||
|
allowEmpty
|
||||||
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
|
|
||||||
<ButtonGroup className="mt-4">
|
<WithLabel
|
||||||
<ToolTip content={langui.only_display_items_i_want}>
|
label={langui.order_by}
|
||||||
<Button
|
input={
|
||||||
icon={Icon.Favorite}
|
<Select
|
||||||
onClick={() => setFilterUserStatus(LibraryItemUserStatus.Want)}
|
className="w-full"
|
||||||
active={filterUserStatus === LibraryItemUserStatus.Want}
|
options={[
|
||||||
/>
|
langui.name ?? "Name",
|
||||||
</ToolTip>
|
langui.price ?? "Price",
|
||||||
<ToolTip content={langui.only_display_items_i_have}>
|
langui.release_date ?? "Release date",
|
||||||
<Button
|
]}
|
||||||
icon={Icon.BackHand}
|
state={sortingMethod}
|
||||||
onClick={() => setFilterUserStatus(LibraryItemUserStatus.Have)}
|
setState={setSortingMethod}
|
||||||
active={filterUserStatus === LibraryItemUserStatus.Have}
|
/>
|
||||||
/>
|
}
|
||||||
</ToolTip>
|
/>
|
||||||
<ToolTip content={langui.only_display_unmarked_items}>
|
|
||||||
<Button
|
|
||||||
icon={Icon.RadioButtonUnchecked}
|
|
||||||
onClick={() => setFilterUserStatus(LibraryItemUserStatus.None)}
|
|
||||||
active={filterUserStatus === LibraryItemUserStatus.None}
|
|
||||||
/>
|
|
||||||
</ToolTip>
|
|
||||||
<ToolTip content={langui.display_all_items}>
|
|
||||||
<Button
|
|
||||||
text={"All"}
|
|
||||||
onClick={() => setFilterUserStatus(undefined)}
|
|
||||||
active={isUndefined(filterUserStatus)}
|
|
||||||
/>
|
|
||||||
</ToolTip>
|
|
||||||
</ButtonGroup>
|
|
||||||
|
|
||||||
<Button
|
<WithLabel
|
||||||
className="mt-8"
|
label={langui.show_subitems}
|
||||||
text={langui.reset_all_filters}
|
input={<Switch state={showSubitems} setState={setShowSubitems} />}
|
||||||
icon={Icon.Replay}
|
/>
|
||||||
onClick={() => {
|
|
||||||
setSearchName(defaultFiltersState.searchName);
|
<WithLabel
|
||||||
setShowSubitems(defaultFiltersState.showSubitems);
|
label={langui.show_primary_items}
|
||||||
setShowPrimaryItems(defaultFiltersState.showPrimaryItems);
|
input={
|
||||||
setShowSecondaryItems(defaultFiltersState.showSecondaryItems);
|
<Switch state={showPrimaryItems} setState={setShowPrimaryItems} />
|
||||||
setSortingMethod(defaultFiltersState.sortingMethod);
|
}
|
||||||
setGroupingMethod(defaultFiltersState.groupingMethod);
|
/>
|
||||||
setKeepInfoVisible(defaultFiltersState.keepInfoVisible);
|
|
||||||
setFilterUserStatus(defaultFiltersState.filterUserStatus);
|
<WithLabel
|
||||||
}}
|
label={langui.show_secondary_items}
|
||||||
/>
|
input={
|
||||||
</SubPanel>
|
<Switch
|
||||||
|
state={showSecondaryItems}
|
||||||
|
setState={setShowSecondaryItems}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{hoverable && (
|
||||||
|
<WithLabel
|
||||||
|
label={langui.always_show_info}
|
||||||
|
input={
|
||||||
|
<Switch state={keepInfoVisible} setState={setKeepInfoVisible} />
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<ButtonGroup className="mt-4">
|
||||||
|
<ToolTip content={langui.only_display_items_i_want}>
|
||||||
|
<Button
|
||||||
|
icon={Icon.Favorite}
|
||||||
|
onClick={() => setFilterUserStatus(LibraryItemUserStatus.Want)}
|
||||||
|
active={filterUserStatus === LibraryItemUserStatus.Want}
|
||||||
|
/>
|
||||||
|
</ToolTip>
|
||||||
|
<ToolTip content={langui.only_display_items_i_have}>
|
||||||
|
<Button
|
||||||
|
icon={Icon.BackHand}
|
||||||
|
onClick={() => setFilterUserStatus(LibraryItemUserStatus.Have)}
|
||||||
|
active={filterUserStatus === LibraryItemUserStatus.Have}
|
||||||
|
/>
|
||||||
|
</ToolTip>
|
||||||
|
<ToolTip content={langui.only_display_unmarked_items}>
|
||||||
|
<Button
|
||||||
|
icon={Icon.RadioButtonUnchecked}
|
||||||
|
onClick={() => setFilterUserStatus(LibraryItemUserStatus.None)}
|
||||||
|
active={filterUserStatus === LibraryItemUserStatus.None}
|
||||||
|
/>
|
||||||
|
</ToolTip>
|
||||||
|
<ToolTip content={langui.display_all_items}>
|
||||||
|
<Button
|
||||||
|
text={"All"}
|
||||||
|
onClick={() => setFilterUserStatus(undefined)}
|
||||||
|
active={isUndefined(filterUserStatus)}
|
||||||
|
/>
|
||||||
|
</ToolTip>
|
||||||
|
</ButtonGroup>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
className="mt-8"
|
||||||
|
text={langui.reset_all_filters}
|
||||||
|
icon={Icon.Replay}
|
||||||
|
onClick={() => {
|
||||||
|
setSearchName(defaultFiltersState.searchName);
|
||||||
|
setShowSubitems(defaultFiltersState.showSubitems);
|
||||||
|
setShowPrimaryItems(defaultFiltersState.showPrimaryItems);
|
||||||
|
setShowSecondaryItems(defaultFiltersState.showSecondaryItems);
|
||||||
|
setSortingMethod(defaultFiltersState.sortingMethod);
|
||||||
|
setGroupingMethod(defaultFiltersState.groupingMethod);
|
||||||
|
setKeepInfoVisible(defaultFiltersState.keepInfoVisible);
|
||||||
|
setFilterUserStatus(defaultFiltersState.filterUserStatus);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</SubPanel>
|
||||||
|
),
|
||||||
|
[
|
||||||
|
filterUserStatus,
|
||||||
|
groupingMethod,
|
||||||
|
hoverable,
|
||||||
|
keepInfoVisible,
|
||||||
|
langui,
|
||||||
|
searchName,
|
||||||
|
showPrimaryItems,
|
||||||
|
showSecondaryItems,
|
||||||
|
showSubitems,
|
||||||
|
sortingMethod,
|
||||||
|
]
|
||||||
);
|
);
|
||||||
const contentPanel = (
|
|
||||||
<ContentPanel width={ContentPanelWidthSizes.Full}>
|
const contentPanel = useMemo(
|
||||||
{/* TODO: Add to langui */}
|
() => (
|
||||||
{groups.size === 0 && (
|
<ContentPanel width={ContentPanelWidthSizes.Full}>
|
||||||
<ContentPlaceholder
|
{/* TODO: Add to langui */}
|
||||||
message={
|
{groups.size === 0 && (
|
||||||
"No results. You can try changing or resetting the search parameters."
|
<ContentPlaceholder
|
||||||
}
|
message={
|
||||||
icon={Icon.ChevronLeft}
|
"No results. You can try changing or resetting the search parameters."
|
||||||
/>
|
}
|
||||||
)}
|
icon={Icon.ChevronLeft}
|
||||||
{iterateMap(groups, (name, items) => (
|
/>
|
||||||
<Fragment key={name}>
|
)}
|
||||||
{isDefinedAndNotEmpty(name) && (
|
{iterateMap(groups, (name, items) => (
|
||||||
<h2
|
<Fragment key={name}>
|
||||||
className="flex flex-row place-items-center gap-2
|
{isDefinedAndNotEmpty(name) && (
|
||||||
|
<h2
|
||||||
|
className="flex flex-row place-items-center gap-2
|
||||||
pb-2 pt-10 text-2xl first-of-type:pt-0"
|
pb-2 pt-10 text-2xl first-of-type:pt-0"
|
||||||
>
|
>
|
||||||
{name}
|
{name}
|
||||||
<Chip>{`${items.length} ${
|
<Chip>{`${items.length} ${
|
||||||
items.length <= 1
|
items.length <= 1
|
||||||
? langui.result?.toLowerCase() ?? "result"
|
? langui.result?.toLowerCase() ?? "result"
|
||||||
: langui.results?.toLowerCase() ?? "results"
|
: langui.results?.toLowerCase() ?? "results"
|
||||||
}`}</Chip>
|
}`}</Chip>
|
||||||
</h2>
|
</h2>
|
||||||
)}
|
)}
|
||||||
<div
|
<div
|
||||||
className="grid items-end gap-8 border-b-[3px] border-dotted pb-12
|
className="grid items-end gap-8 border-b-[3px] border-dotted pb-12
|
||||||
last-of-type:border-0 desktop:grid-cols-[repeat(auto-fill,_minmax(13rem,1fr))]
|
last-of-type:border-0 desktop:grid-cols-[repeat(auto-fill,_minmax(13rem,1fr))]
|
||||||
mobile:grid-cols-2 mobile:gap-4"
|
mobile:grid-cols-2 mobile:gap-4"
|
||||||
>
|
>
|
||||||
{filterHasAttributes(items).map((item) => (
|
{filterHasAttributes(items).map((item) => (
|
||||||
<Fragment key={item.id}>
|
<Fragment key={item.id}>
|
||||||
<PreviewCard
|
<PreviewCard
|
||||||
href={`/library/${item.attributes.slug}`}
|
href={`/library/${item.attributes.slug}`}
|
||||||
title={item.attributes.title}
|
title={item.attributes.title}
|
||||||
subtitle={item.attributes.subtitle}
|
subtitle={item.attributes.subtitle}
|
||||||
thumbnail={item.attributes.thumbnail?.data?.attributes}
|
thumbnail={item.attributes.thumbnail?.data?.attributes}
|
||||||
thumbnailAspectRatio="21/29.7"
|
thumbnailAspectRatio="21/29.7"
|
||||||
thumbnailRounded={false}
|
thumbnailRounded={false}
|
||||||
keepInfoVisible={keepInfoVisible}
|
keepInfoVisible={keepInfoVisible}
|
||||||
topChips={
|
topChips={
|
||||||
item.attributes.metadata &&
|
item.attributes.metadata &&
|
||||||
item.attributes.metadata.length > 0 &&
|
item.attributes.metadata.length > 0 &&
|
||||||
item.attributes.metadata[0]
|
item.attributes.metadata[0]
|
||||||
? [prettyItemSubType(item.attributes.metadata[0])]
|
? [prettyItemSubType(item.attributes.metadata[0])]
|
||||||
: []
|
: []
|
||||||
}
|
}
|
||||||
bottomChips={item.attributes.categories?.data.map(
|
bottomChips={item.attributes.categories?.data.map(
|
||||||
(category) => category.attributes?.short ?? ""
|
(category) => category.attributes?.short ?? ""
|
||||||
)}
|
)}
|
||||||
metadata={{
|
metadata={{
|
||||||
currencies: currencies,
|
currencies: currencies,
|
||||||
release_date: item.attributes.release_date,
|
release_date: item.attributes.release_date,
|
||||||
price: item.attributes.price,
|
price: item.attributes.price,
|
||||||
position: "Bottom",
|
position: "Bottom",
|
||||||
}}
|
}}
|
||||||
infoAppend={
|
infoAppend={
|
||||||
<PreviewCardCTAs
|
<PreviewCardCTAs
|
||||||
id={item.id}
|
id={item.id}
|
||||||
displayCTAs={
|
displayCTAs={
|
||||||
!isUntangibleGroupItem(item.attributes.metadata?.[0])
|
!isUntangibleGroupItem(item.attributes.metadata?.[0])
|
||||||
}
|
}
|
||||||
langui={langui}
|
langui={langui}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
))}
|
))}
|
||||||
</ContentPanel>
|
</ContentPanel>
|
||||||
|
),
|
||||||
|
[currencies, groups, keepInfoVisible, langui]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppLayout
|
<AppLayout
|
||||||
navTitle={langui.library}
|
navTitle={langui.library}
|
||||||
|
|
|
@ -5,20 +5,24 @@ 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>
|
() => (
|
||||||
<PanelHeader
|
<SubPanel>
|
||||||
icon={Icon.Store}
|
<PanelHeader
|
||||||
title={langui.merch}
|
icon={Icon.Store}
|
||||||
description={langui.merch_description}
|
title={langui.merch}
|
||||||
/>
|
description={langui.merch_description}
|
||||||
</SubPanel>
|
/>
|
||||||
|
</SubPanel>
|
||||||
|
),
|
||||||
|
[langui]
|
||||||
);
|
);
|
||||||
|
|
||||||
return <AppLayout navTitle={langui.merch} subPanel={subPanel} {...props} />;
|
return <AppLayout navTitle={langui.merch} subPanel={subPanel} {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -46,73 +46,79 @@ export default function News(props: Props): JSX.Element {
|
||||||
[posts, searchName]
|
[posts, searchName]
|
||||||
);
|
);
|
||||||
|
|
||||||
const subPanel = (
|
const subPanel = useMemo(
|
||||||
<SubPanel>
|
() => (
|
||||||
<PanelHeader
|
<SubPanel>
|
||||||
icon={Icon.Feed}
|
<PanelHeader
|
||||||
title={langui.news}
|
icon={Icon.Feed}
|
||||||
description={langui.news_description}
|
title={langui.news}
|
||||||
/>
|
description={langui.news_description}
|
||||||
|
|
||||||
<TextInput
|
|
||||||
className="mb-6 w-full"
|
|
||||||
placeholder={langui.search_title ?? undefined}
|
|
||||||
state={searchName}
|
|
||||||
setState={setSearchName}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{hoverable && (
|
|
||||||
<WithLabel
|
|
||||||
label={langui.always_show_info}
|
|
||||||
input={
|
|
||||||
<Switch setState={setKeepInfoVisible} state={keepInfoVisible} />
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
|
|
||||||
<Button
|
<TextInput
|
||||||
className="mt-8"
|
className="mb-6 w-full"
|
||||||
text={langui.reset_all_filters}
|
placeholder={langui.search_title ?? undefined}
|
||||||
icon={Icon.Replay}
|
state={searchName}
|
||||||
onClick={() => {
|
setState={setSearchName}
|
||||||
setSearchName(defaultFiltersState.searchName);
|
/>
|
||||||
setKeepInfoVisible(defaultFiltersState.keepInfoVisible);
|
|
||||||
}}
|
{hoverable && (
|
||||||
/>
|
<WithLabel
|
||||||
</SubPanel>
|
label={langui.always_show_info}
|
||||||
|
input={
|
||||||
|
<Switch setState={setKeepInfoVisible} state={keepInfoVisible} />
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Button
|
||||||
|
className="mt-8"
|
||||||
|
text={langui.reset_all_filters}
|
||||||
|
icon={Icon.Replay}
|
||||||
|
onClick={() => {
|
||||||
|
setSearchName(defaultFiltersState.searchName);
|
||||||
|
setKeepInfoVisible(defaultFiltersState.keepInfoVisible);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</SubPanel>
|
||||||
|
),
|
||||||
|
[hoverable, keepInfoVisible, langui, searchName]
|
||||||
);
|
);
|
||||||
|
|
||||||
const contentPanel = (
|
const contentPanel = useMemo(
|
||||||
<ContentPanel width={ContentPanelWidthSizes.Full}>
|
() => (
|
||||||
<div
|
<ContentPanel width={ContentPanelWidthSizes.Full}>
|
||||||
className="grid grid-cols-1 items-end gap-8
|
<div
|
||||||
|
className="grid grid-cols-1 items-end gap-8
|
||||||
desktop:grid-cols-[repeat(auto-fill,_minmax(20rem,1fr))]"
|
desktop:grid-cols-[repeat(auto-fill,_minmax(20rem,1fr))]"
|
||||||
>
|
>
|
||||||
{filterHasAttributes(filteredItems).map((post) => (
|
{filterHasAttributes(filteredItems).map((post) => (
|
||||||
<Fragment key={post.id}>
|
<Fragment key={post.id}>
|
||||||
<PreviewCard
|
<PreviewCard
|
||||||
href={`/news/${post.attributes.slug}`}
|
href={`/news/${post.attributes.slug}`}
|
||||||
title={
|
title={
|
||||||
post.attributes.translations?.[0]?.title ??
|
post.attributes.translations?.[0]?.title ??
|
||||||
prettySlug(post.attributes.slug)
|
prettySlug(post.attributes.slug)
|
||||||
}
|
}
|
||||||
description={post.attributes.translations?.[0]?.excerpt}
|
description={post.attributes.translations?.[0]?.excerpt}
|
||||||
thumbnail={post.attributes.thumbnail?.data?.attributes}
|
thumbnail={post.attributes.thumbnail?.data?.attributes}
|
||||||
thumbnailAspectRatio="3/2"
|
thumbnailAspectRatio="3/2"
|
||||||
thumbnailForceAspectRatio
|
thumbnailForceAspectRatio
|
||||||
bottomChips={post.attributes.categories?.data.map(
|
bottomChips={post.attributes.categories?.data.map(
|
||||||
(category) => category.attributes?.short ?? ""
|
(category) => category.attributes?.short ?? ""
|
||||||
)}
|
)}
|
||||||
keepInfoVisible={keepInfoVisible}
|
keepInfoVisible={keepInfoVisible}
|
||||||
metadata={{
|
metadata={{
|
||||||
release_date: post.attributes.date,
|
release_date: post.attributes.date,
|
||||||
position: "Top",
|
position: "Top",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</ContentPanel>
|
</ContentPanel>
|
||||||
|
),
|
||||||
|
[filteredItems, keepInfoVisible]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -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,80 +41,95 @@ 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>
|
() => (
|
||||||
<ReturnButton
|
<SubPanel>
|
||||||
href={`/wiki`}
|
<ReturnButton
|
||||||
title={langui.wiki}
|
href={`/wiki`}
|
||||||
langui={langui}
|
title={langui.wiki}
|
||||||
displayOn={ReturnButtonType.Desktop}
|
langui={langui}
|
||||||
horizontalLine
|
displayOn={ReturnButtonType.Desktop}
|
||||||
/>
|
horizontalLine
|
||||||
</SubPanel>
|
/>
|
||||||
|
</SubPanel>
|
||||||
|
),
|
||||||
|
[langui]
|
||||||
);
|
);
|
||||||
const contentPanel = (
|
|
||||||
<ContentPanel width={ContentPanelWidthSizes.Large}>
|
|
||||||
<ReturnButton
|
|
||||||
href={`/wiki`}
|
|
||||||
title={langui.wiki}
|
|
||||||
langui={langui}
|
|
||||||
displayOn={ReturnButtonType.Mobile}
|
|
||||||
className="mb-10"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className="flex place-content-center gap-4">
|
const contentPanel = useMemo(
|
||||||
<h1 className="text-center text-3xl">{selectedTranslation?.title}</h1>
|
() => (
|
||||||
<LanguageSwitcher />
|
<ContentPanel width={ContentPanelWidthSizes.Large}>
|
||||||
</div>
|
<ReturnButton
|
||||||
|
href={`/wiki`}
|
||||||
|
title={langui.wiki}
|
||||||
|
langui={langui}
|
||||||
|
displayOn={ReturnButtonType.Mobile}
|
||||||
|
className="mb-10"
|
||||||
|
/>
|
||||||
|
|
||||||
<HorizontalLine />
|
<div className="flex place-content-center gap-4">
|
||||||
|
<h1 className="text-center text-3xl">{selectedTranslation?.title}</h1>
|
||||||
|
<LanguageSwitcher />
|
||||||
|
</div>
|
||||||
|
|
||||||
{selectedTranslation && (
|
<HorizontalLine />
|
||||||
<div className="text-justify">
|
|
||||||
<div
|
{selectedTranslation && (
|
||||||
className="float-right ml-8 mb-8 w-[25rem] overflow-hidden rounded-lg bg-mid
|
<div className="text-justify">
|
||||||
|
<div
|
||||||
|
className="float-right ml-8 mb-8 w-[25rem] overflow-hidden rounded-lg bg-mid
|
||||||
text-center"
|
text-center"
|
||||||
>
|
>
|
||||||
{page.thumbnail?.data?.attributes && (
|
{page.thumbnail?.data?.attributes && (
|
||||||
<Img image={page.thumbnail.data.attributes} />
|
<Img image={page.thumbnail.data.attributes} />
|
||||||
)}
|
)}
|
||||||
<div className="my-4 grid gap-4 p-4">
|
<div className="my-4 grid gap-4 p-4">
|
||||||
<p className="font-headers text-xl">{langui.categories}</p>
|
<p className="font-headers text-xl">{langui.categories}</p>
|
||||||
<div className="flex flex-row flex-wrap place-content-center gap-2">
|
<div className="flex flex-row flex-wrap place-content-center gap-2">
|
||||||
{page.categories?.data.map((category) => (
|
{page.categories?.data.map((category) => (
|
||||||
<Chip key={category.id}>{category.attributes?.name}</Chip>
|
<Chip key={category.id}>{category.attributes?.name}</Chip>
|
||||||
))}
|
))}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{isDefinedAndNotEmpty(selectedTranslation.summary) && (
|
||||||
{isDefinedAndNotEmpty(selectedTranslation.summary) && (
|
<div className="mb-6">
|
||||||
<div className="mb-6">
|
<p className="font-headers text-lg">{langui.summary}</p>
|
||||||
<p className="font-headers text-lg">{langui.summary}</p>
|
<p>{selectedTranslation.summary}</p>
|
||||||
<p>{selectedTranslation.summary}</p>
|
</div>
|
||||||
</div>
|
)}
|
||||||
)}
|
|
||||||
|
|
||||||
{filterHasAttributes(page.definitions, ["translations"]).map(
|
{filterHasAttributes(page.definitions, ["translations"]).map(
|
||||||
(definition, index) => (
|
(definition, index) => (
|
||||||
<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}
|
/>
|
||||||
/>
|
)
|
||||||
)
|
)}
|
||||||
)}
|
</div>
|
||||||
</div>
|
)}
|
||||||
)}
|
</ContentPanel>
|
||||||
</ContentPanel>
|
),
|
||||||
|
[
|
||||||
|
LanguageSwitcher,
|
||||||
|
languages,
|
||||||
|
langui,
|
||||||
|
page.categories?.data,
|
||||||
|
page.definitions,
|
||||||
|
page.thumbnail?.data?.attributes,
|
||||||
|
selectedTranslation,
|
||||||
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -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,109 +27,113 @@ 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(() => {
|
||||||
|
memo.push([]);
|
||||||
|
});
|
||||||
|
|
||||||
chronologyEras.map(() => {
|
let currentChronologyEraIndex = 0;
|
||||||
chronologyItemYearGroups.push([]);
|
chronologyItems.map((item) => {
|
||||||
});
|
if (item.attributes) {
|
||||||
|
if (
|
||||||
let currentChronologyEraIndex = 0;
|
item.attributes.year >
|
||||||
chronologyItems.map((item) => {
|
(chronologyEras[currentChronologyEraIndex].attributes?.ending_year ??
|
||||||
if (item.attributes) {
|
999999)
|
||||||
if (
|
) {
|
||||||
item.attributes.year >
|
currentChronologyEraIndex += 1;
|
||||||
(chronologyEras[currentChronologyEraIndex].attributes?.ending_year ??
|
}
|
||||||
999999)
|
if (
|
||||||
) {
|
Object.prototype.hasOwnProperty.call(
|
||||||
currentChronologyEraIndex += 1;
|
memo[currentChronologyEraIndex],
|
||||||
|
item.attributes.year
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
memo[currentChronologyEraIndex][item.attributes.year].push(item);
|
||||||
|
} else {
|
||||||
|
memo[currentChronologyEraIndex][item.attributes.year] = [item];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (
|
});
|
||||||
Object.prototype.hasOwnProperty.call(
|
return memo;
|
||||||
chronologyItemYearGroups[currentChronologyEraIndex],
|
}, [chronologyEras, chronologyItems]);
|
||||||
item.attributes.year
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
chronologyItemYearGroups[currentChronologyEraIndex][
|
|
||||||
item.attributes.year
|
|
||||||
].push(item);
|
|
||||||
} else {
|
|
||||||
chronologyItemYearGroups[currentChronologyEraIndex][
|
|
||||||
item.attributes.year
|
|
||||||
] = [item];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const subPanel = (
|
const subPanel = useMemo(
|
||||||
<SubPanel>
|
() => (
|
||||||
<ReturnButton
|
<SubPanel>
|
||||||
href="/wiki"
|
<ReturnButton
|
||||||
title={langui.wiki}
|
href="/wiki"
|
||||||
langui={langui}
|
title={langui.wiki}
|
||||||
displayOn={ReturnButtonType.Desktop}
|
langui={langui}
|
||||||
horizontalLine
|
displayOn={ReturnButtonType.Desktop}
|
||||||
/>
|
horizontalLine
|
||||||
|
/>
|
||||||
|
|
||||||
{filterHasAttributes(chronologyEras).map((era) => (
|
{filterHasAttributes(chronologyEras).map((era) => (
|
||||||
<Fragment key={era.id}>
|
<Fragment key={era.id}>
|
||||||
<NavOption
|
<NavOption
|
||||||
url={`#${era.attributes.slug}`}
|
url={`#${era.attributes.slug}`}
|
||||||
title={
|
title={
|
||||||
era.attributes.title &&
|
era.attributes.title &&
|
||||||
era.attributes.title.length > 0 &&
|
era.attributes.title.length > 0 &&
|
||||||
era.attributes.title[0]
|
era.attributes.title[0]
|
||||||
? era.attributes.title[0].title
|
? era.attributes.title[0].title
|
||||||
: prettySlug(era.attributes.slug)
|
: prettySlug(era.attributes.slug)
|
||||||
}
|
}
|
||||||
subtitle={`${era.attributes.starting_year} → ${era.attributes.ending_year}`}
|
subtitle={`${era.attributes.starting_year} → ${era.attributes.ending_year}`}
|
||||||
border
|
border
|
||||||
/>
|
/>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
))}
|
))}
|
||||||
</SubPanel>
|
</SubPanel>
|
||||||
|
),
|
||||||
|
[chronologyEras, langui]
|
||||||
);
|
);
|
||||||
|
|
||||||
const contentPanel = (
|
const contentPanel = useMemo(
|
||||||
<ContentPanel>
|
() => (
|
||||||
<ReturnButton
|
<ContentPanel>
|
||||||
href="/wiki"
|
<ReturnButton
|
||||||
title={langui.wiki}
|
href="/wiki"
|
||||||
langui={langui}
|
title={langui.wiki}
|
||||||
displayOn={ReturnButtonType.Mobile}
|
langui={langui}
|
||||||
className="mb-10"
|
displayOn={ReturnButtonType.Mobile}
|
||||||
/>
|
className="mb-10"
|
||||||
|
/>
|
||||||
|
|
||||||
{chronologyItemYearGroups.map((era, eraIndex) => (
|
{chronologyItemYearGroups.map((era, eraIndex) => (
|
||||||
<Fragment key={eraIndex}>
|
<Fragment key={eraIndex}>
|
||||||
<InsetBox
|
<InsetBox
|
||||||
id={chronologyEras[eraIndex].attributes?.slug}
|
id={chronologyEras[eraIndex].attributes?.slug}
|
||||||
className="my-8 grid gap-4 text-center"
|
className="my-8 grid gap-4 text-center"
|
||||||
>
|
>
|
||||||
<h2 className="text-2xl">
|
<h2 className="text-2xl">
|
||||||
{chronologyEras[eraIndex].attributes?.title?.[0]
|
{chronologyEras[eraIndex].attributes?.title?.[0]
|
||||||
? chronologyEras[eraIndex].attributes?.title?.[0]?.title
|
? chronologyEras[eraIndex].attributes?.title?.[0]?.title
|
||||||
: prettySlug(chronologyEras[eraIndex].attributes?.slug)}
|
: prettySlug(chronologyEras[eraIndex].attributes?.slug)}
|
||||||
</h2>
|
</h2>
|
||||||
<p className="whitespace-pre-line ">
|
<p className="whitespace-pre-line ">
|
||||||
{chronologyEras[eraIndex].attributes?.title?.[0]
|
{chronologyEras[eraIndex].attributes?.title?.[0]
|
||||||
? chronologyEras[eraIndex].attributes?.title?.[0]?.description
|
? chronologyEras[eraIndex].attributes?.title?.[0]?.description
|
||||||
: ""}
|
: ""}
|
||||||
</p>
|
</p>
|
||||||
</InsetBox>
|
</InsetBox>
|
||||||
{era.map((items, index) => (
|
{era.map((items, index) => (
|
||||||
<Fragment key={index}>
|
<Fragment key={index}>
|
||||||
{items[0].attributes && isDefined(items[0].attributes.year) && (
|
{items[0].attributes && isDefined(items[0].attributes.year) && (
|
||||||
<ChronologyYearComponent
|
<ChronologyYearComponent
|
||||||
year={items[0].attributes.year}
|
year={items[0].attributes.year}
|
||||||
items={items}
|
items={items}
|
||||||
langui={langui}
|
langui={langui}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Fragment>
|
</Fragment>
|
||||||
))}
|
))}
|
||||||
</Fragment>
|
</Fragment>
|
||||||
))}
|
))}
|
||||||
</ContentPanel>
|
</ContentPanel>
|
||||||
|
),
|
||||||
|
[chronologyEras, chronologyItemYearGroups, langui]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -47,89 +47,95 @@ export default function Wiki(props: Props): JSX.Element {
|
||||||
[pages, searchName]
|
[pages, searchName]
|
||||||
);
|
);
|
||||||
|
|
||||||
const subPanel = (
|
const subPanel = useMemo(
|
||||||
<SubPanel>
|
() => (
|
||||||
<PanelHeader
|
<SubPanel>
|
||||||
icon={Icon.TravelExplore}
|
<PanelHeader
|
||||||
title={langui.wiki}
|
icon={Icon.TravelExplore}
|
||||||
description={langui.wiki_description}
|
title={langui.wiki}
|
||||||
/>
|
description={langui.wiki_description}
|
||||||
|
|
||||||
<TextInput
|
|
||||||
className="mb-6 w-full"
|
|
||||||
placeholder={langui.search_title ?? undefined}
|
|
||||||
state={searchName}
|
|
||||||
setState={setSearchName}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{hoverable && (
|
|
||||||
<WithLabel
|
|
||||||
label={langui.always_show_info}
|
|
||||||
input={
|
|
||||||
<Switch setState={setKeepInfoVisible} state={keepInfoVisible} />
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
|
|
||||||
<Button
|
<TextInput
|
||||||
className="mt-8"
|
className="mb-6 w-full"
|
||||||
text={langui.reset_all_filters}
|
placeholder={langui.search_title ?? undefined}
|
||||||
icon={Icon.Replay}
|
state={searchName}
|
||||||
onClick={() => {
|
setState={setSearchName}
|
||||||
setSearchName(defaultFiltersState.searchName);
|
/>
|
||||||
setKeepInfoVisible(defaultFiltersState.keepInfoVisible);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<HorizontalLine />
|
|
||||||
|
|
||||||
{/* TODO: Langui */}
|
{hoverable && (
|
||||||
<p className="mb-4 font-headers text-xl">Special Pages</p>
|
<WithLabel
|
||||||
|
label={langui.always_show_info}
|
||||||
<NavOption title={langui.chronology} url="/wiki/chronology" border />
|
input={
|
||||||
</SubPanel>
|
<Switch setState={setKeepInfoVisible} state={keepInfoVisible} />
|
||||||
);
|
|
||||||
|
|
||||||
const contentPanel = (
|
|
||||||
<ContentPanel width={ContentPanelWidthSizes.Full}>
|
|
||||||
<div
|
|
||||||
className="grid grid-cols-2 items-end gap-8
|
|
||||||
desktop:grid-cols-[repeat(auto-fill,_minmax(20rem,1fr))] mobile:gap-4"
|
|
||||||
>
|
|
||||||
{/* TODO: Add to langui */}
|
|
||||||
{filteredPages.length === 0 && (
|
|
||||||
<ContentPlaceholder
|
|
||||||
message={
|
|
||||||
"No results. You can try changing or resetting the search parameters."
|
|
||||||
}
|
}
|
||||||
icon={Icon.ChevronLeft}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{filterHasAttributes(filteredPages).map((page) => (
|
|
||||||
<Fragment key={page.id}>
|
<Button
|
||||||
<TranslatedPreviewCard
|
className="mt-8"
|
||||||
href={`/wiki/${page.attributes.slug}`}
|
text={langui.reset_all_filters}
|
||||||
translations={page.attributes.translations?.map(
|
icon={Icon.Replay}
|
||||||
(translation) => ({
|
onClick={() => {
|
||||||
title: translation?.title,
|
setSearchName(defaultFiltersState.searchName);
|
||||||
description: translation?.summary,
|
setKeepInfoVisible(defaultFiltersState.keepInfoVisible);
|
||||||
language: translation?.language?.data?.attributes?.code,
|
}}
|
||||||
})
|
/>
|
||||||
)}
|
<HorizontalLine />
|
||||||
thumbnail={page.attributes.thumbnail?.data?.attributes}
|
|
||||||
thumbnailAspectRatio={"4/3"}
|
{/* TODO: Langui */}
|
||||||
thumbnailRounded
|
<p className="mb-4 font-headers text-xl">Special Pages</p>
|
||||||
thumbnailForceAspectRatio
|
|
||||||
languages={languages}
|
<NavOption title={langui.chronology} url="/wiki/chronology" border />
|
||||||
slug={page.attributes.slug}
|
</SubPanel>
|
||||||
keepInfoVisible={keepInfoVisible}
|
),
|
||||||
bottomChips={page.attributes.categories?.data.map(
|
[hoverable, keepInfoVisible, langui, searchName]
|
||||||
(category) => category.attributes?.short ?? ""
|
);
|
||||||
)}
|
|
||||||
|
const contentPanel = useMemo(
|
||||||
|
() => (
|
||||||
|
<ContentPanel width={ContentPanelWidthSizes.Full}>
|
||||||
|
<div
|
||||||
|
className="grid grid-cols-2 items-end gap-8
|
||||||
|
desktop:grid-cols-[repeat(auto-fill,_minmax(20rem,1fr))] mobile:gap-4"
|
||||||
|
>
|
||||||
|
{/* TODO: Add to langui */}
|
||||||
|
{filteredPages.length === 0 && (
|
||||||
|
<ContentPlaceholder
|
||||||
|
message={
|
||||||
|
"No results. You can try changing or resetting the search parameters."
|
||||||
|
}
|
||||||
|
icon={Icon.ChevronLeft}
|
||||||
/>
|
/>
|
||||||
</Fragment>
|
)}
|
||||||
))}
|
{filterHasAttributes(filteredPages).map((page) => (
|
||||||
</div>
|
<Fragment key={page.id}>
|
||||||
</ContentPanel>
|
<TranslatedPreviewCard
|
||||||
|
href={`/wiki/${page.attributes.slug}`}
|
||||||
|
translations={page.attributes.translations?.map(
|
||||||
|
(translation) => ({
|
||||||
|
title: translation?.title,
|
||||||
|
description: translation?.summary,
|
||||||
|
language: translation?.language?.data?.attributes?.code,
|
||||||
|
})
|
||||||
|
)}
|
||||||
|
thumbnail={page.attributes.thumbnail?.data?.attributes}
|
||||||
|
thumbnailAspectRatio={"4/3"}
|
||||||
|
thumbnailRounded
|
||||||
|
thumbnailForceAspectRatio
|
||||||
|
languages={languages}
|
||||||
|
slug={page.attributes.slug}
|
||||||
|
keepInfoVisible={keepInfoVisible}
|
||||||
|
bottomChips={page.attributes.categories?.data.map(
|
||||||
|
(category) => category.attributes?.short ?? ""
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Fragment>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</ContentPanel>
|
||||||
|
),
|
||||||
|
[filteredPages, keepInfoVisible, languages]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
Loading…
Reference in New Issue