2022-05-08 12:21:29 +02:00

541 lines
20 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import AppLayout from "components/AppLayout";
import Chip from "components/Chip";
import Img from "components/Img";
import Button from "components/Inputs/Button";
import Switch from "components/Inputs/Switch";
import InsetBox from "components/InsetBox";
import ContentLine from "components/Library/ContentLine";
import LightBox from "components/LightBox";
import NavOption from "components/PanelComponents/NavOption";
import ReturnButton, {
ReturnButtonType,
} from "components/PanelComponents/ReturnButton";
import ContentPanel, {
ContentPanelWidthSizes,
} from "components/Panels/ContentPanel";
import SubPanel from "components/Panels/SubPanel";
import ThumbnailPreview from "components/PreviewCard";
import { useAppLayout } from "contexts/AppLayoutContext";
import {
Enum_Componentmetadatabooks_Binding_Type,
Enum_Componentmetadatabooks_Page_Order,
GetLibraryItemQuery,
} from "graphql/generated";
import { getReadySdk } from "graphql/sdk";
import {
prettyDate,
prettyinlineTitle,
prettyItemSubType,
prettyItemType,
prettyPrice,
prettyURL,
} from "helpers/formatters";
import { AppStaticProps, getAppStaticProps } from "helpers/getAppStaticProps";
import { sortContent } from "helpers/others";
import { getAssetURL, ImageQuality } from "helpers/img";
import { convertMmToInch } from "helpers/numbers";
import {
GetStaticPathsContext,
GetStaticPathsResult,
GetStaticPropsContext,
} from "next";
import { useState } from "react";
interface Props extends AppStaticProps {
item: Exclude<
GetLibraryItemQuery["libraryItems"],
null | undefined
>["data"][number]["attributes"];
itemId: Exclude<
GetLibraryItemQuery["libraryItems"],
null | undefined
>["data"][number]["id"];
}
export default function LibrarySlug(props: Props): JSX.Element {
const { item, langui, currencies } = props;
const appLayout = useAppLayout();
const isVariantSet =
item?.metadata?.[0]?.__typename === "ComponentMetadataGroup" &&
item.metadata[0].subtype?.data?.attributes?.slug === "variant-set";
sortContent(item?.contents);
const [lightboxOpen, setLightboxOpen] = useState(false);
const [lightboxImages, setLightboxImages] = useState([""]);
const [lightboxIndex, setLightboxIndex] = useState(0);
const [keepInfoVisible, setKeepInfoVisible] = useState(false);
let displayOpenScans = false;
if (item?.contents?.data)
for (const content of item.contents.data) {
if (
content.attributes?.scan_set &&
content.attributes.scan_set.length > 0
)
displayOpenScans = true;
}
const subPanel = (
<SubPanel>
<ReturnButton
href="/library/"
title={langui.library}
langui={langui}
displayOn={ReturnButtonType.desktop}
horizontalLine
/>
<div className="grid gap-4">
<NavOption
title={langui.summary}
url="#summary"
border
onClick={() => appLayout.setSubPanelOpen(false)}
/>
{item?.gallery && item.gallery.data.length > 0 && (
<NavOption
title={langui.gallery}
url="#gallery"
border
onClick={() => appLayout.setSubPanelOpen(false)}
/>
)}
<NavOption
title={langui.details}
url="#details"
border
onClick={() => appLayout.setSubPanelOpen(false)}
/>
{item?.subitems && item.subitems.data.length > 0 && (
<NavOption
title={isVariantSet ? langui.variants : langui.subitems}
url={isVariantSet ? "#variants" : "#subitems"}
border
onClick={() => appLayout.setSubPanelOpen(false)}
/>
)}
{item?.contents && item.contents.data.length > 0 && (
<NavOption
title={langui.contents}
url="#contents"
border
onClick={() => appLayout.setSubPanelOpen(false)}
/>
)}
</div>
</SubPanel>
);
const contentPanel = (
<ContentPanel width={ContentPanelWidthSizes.large}>
<LightBox
state={lightboxOpen}
setState={setLightboxOpen}
images={lightboxImages}
index={lightboxIndex}
setIndex={setLightboxIndex}
/>
<ReturnButton
href="/library/"
title={langui.library}
langui={langui}
displayOn={ReturnButtonType.mobile}
className="mb-10"
/>
<div className="grid place-items-center gap-12">
<div
className="drop-shadow-shade-xl w-full h-[50vh] mobile:h-[60vh] desktop:mb-16 relative cursor-pointer"
onClick={() => {
if (item?.thumbnail?.data?.attributes) {
setLightboxOpen(true);
setLightboxImages([
getAssetURL(
item.thumbnail.data.attributes.url,
ImageQuality.Large
),
]);
setLightboxIndex(0);
}
}}
>
{item?.thumbnail?.data?.attributes ? (
<Img
image={item.thumbnail.data.attributes}
quality={ImageQuality.Large}
layout="fill"
objectFit="contain"
priority
/>
) : (
<div className="w-full aspect-[21/29.7] bg-light rounded-xl"></div>
)}
</div>
<InsetBox id="summary" className="grid place-items-center">
<div className="w-[clamp(0px,100%,42rem)] grid 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}`}
>
{prettyinlineTitle(
"",
item.subitem_of.data[0].attributes.title,
item.subitem_of.data[0].attributes.subtitle
)}
</Button>
</div>
)}
<div className="grid place-items-center">
<h1 className="text-3xl">{item?.title}</h1>
{item?.subtitle && <h2 className="text-2xl">{item.subtitle}</h2>}
</div>
{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>Available at</p>
{item.urls.map((url) => (
<>
{url?.url && (
<Button
href={url.url}
key={url.url}
target={"_blank"}
>
{prettyURL(url.url)}
</Button>
)}
</>
))}
</div>
) : (
<p>This item is not for sale or is no longer available</p>
)}
</>
)}
</div>
</InsetBox>
{item?.gallery && item.gallery.data.length > 0 && (
<div id="gallery" className="grid place-items-center gap-8 w-full">
<h2 className="text-2xl">{langui.gallery}</h2>
<div className="grid w-full gap-8 items-end grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))]">
{item.gallery.data.map((galleryItem, index) => (
<>
{galleryItem.attributes && (
<div
key={galleryItem.id}
className="relative aspect-square hover:scale-[1.02] transition-transform cursor-pointer"
onClick={() => {
if (item.gallery?.data) {
const images: string[] = [];
item.gallery.data.map((image) => {
if (image.attributes)
images.push(
getAssetURL(
image.attributes.url,
ImageQuality.Large
)
);
});
setLightboxOpen(true);
setLightboxImages(images);
setLightboxIndex(index);
}
}}
>
<div className="bg-light absolute inset-0 rounded-lg drop-shadow-shade-md"></div>
<Img
className="rounded-lg"
image={galleryItem.attributes}
layout="fill"
objectFit="cover"
/>
</div>
)}
</>
))}
</div>
</div>
)}
<InsetBox id="details" className="grid place-items-center">
<div className="w-[clamp(0px,100%,42rem)] grid place-items gap-8">
<h2 className="text-2xl text-center">{langui.details}</h2>
<div className="grid grid-flow-col w-full place-content-between">
{item?.metadata?.[0] && (
<div className="grid place-items-center place-content-start">
<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-items-center place-content-start">
<h3 className="text-xl">{langui.release_date}</h3>
<p>{prettyDate(item.release_date)}</p>
</div>
)}
{item?.price && (
<div className="grid place-items-center text-center place-content-start">
<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>
)}
{item?.size && (
<>
<h3 className="text-xl">{langui.size}</h3>
<div className="grid grid-flow-col w-full place-content-between">
<div className="flex flex-row flex-wrap place-items-start gap-4">
<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="flex flex-row flex-wrap place-items-start gap-4">
<p className="font-bold">{langui.height}:</p>
<div>
<p>{item.size.height} mm</p>
<p>{convertMmToInch(item.size.height)} in</p>
</div>
</div>
{item.size.thickness && (
<div className="flex flex-row flex-wrap place-items-start gap-4">
<p className="font-bold">{langui.thickness}:</p>
<div>
<p>{item.size.thickness} mm</p>
<p>{convertMmToInch(item.size.thickness)} in</p>
</div>
</div>
)}
</div>
</>
)}
{item?.metadata?.[0]?.__typename !== "ComponentMetadataGroup" &&
item?.metadata?.[0]?.__typename !== "ComponentMetadataOther" && (
<>
<h3 className="text-xl">{langui.type_information}</h3>
<div className="grid grid-cols-2 w-full 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>
</InsetBox>
{item?.subitems && item.subitems.data.length > 0 && (
<div
id={isVariantSet ? "variants" : "subitems"}
className="grid place-items-center gap-8 w-full"
>
<h2 className="text-2xl">
{isVariantSet ? langui.variants : langui.subitems}
</h2>
<div className="-mt-6 mb-8 flex flex-row gap-2 place-items-center coarse:hidden">
<p className="flex-shrink-0">{"Always show info"}:</p>
<Switch setState={setKeepInfoVisible} state={keepInfoVisible} />
</div>
<div className="grid gap-8 items-end mobile:grid-cols-2 grid-cols-[repeat(auto-fill,minmax(15rem,1fr))] w-full">
{item.subitems.data.map((subitem) => (
<>
{subitem.attributes && (
<ThumbnailPreview
key={subitem.id}
href={`/library/${subitem.attributes.slug}`}
title={subitem.attributes.title}
subtitle={subitem.attributes.subtitle}
thumbnail={subitem.attributes.thumbnail?.data?.attributes}
thumbnailAspectRatio="21/29.7"
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",
}}
/>
)}
</>
))}
</div>
</div>
)}
{item?.contents && item.contents.data.length > 0 && (
<div id="contents" className="w-full grid place-items-center gap-8">
<h2 className="text-2xl -mb-6">{langui.contents}</h2>
{displayOpenScans && (
<Button href={`/library/${item.slug}/scans`}>
{langui.view_scans}
</Button>
)}
<div className="grid gap-4 w-full">
{item.contents.data.map((content) => (
<ContentLine
langui={langui}
content={content}
parentSlug={item.slug}
key={content.id}
/>
))}
</div>
</div>
)}
</div>
</ContentPanel>
);
return (
<AppLayout
navTitle={prettyinlineTitle("", item?.title, item?.subtitle)}
contentPanel={contentPanel}
subPanel={subPanel}
thumbnail={item?.thumbnail?.data?.attributes ?? undefined}
description={item?.descriptions?.[0]?.description ?? undefined}
{...props}
/>
);
}
export async function getStaticProps(
context: GetStaticPropsContext
): Promise<{ notFound: boolean } | { props: Props }> {
const sdk = getReadySdk();
const item = await sdk.getLibraryItem({
slug: context.params?.slug ? context.params.slug.toString() : "",
language_code: context.locale ?? "en",
});
if (!item.libraryItems?.data[0]?.attributes) return { notFound: true };
const props: Props = {
...(await getAppStaticProps(context)),
item: item.libraryItems.data[0].attributes,
itemId: item.libraryItems.data[0].id,
};
return {
props: props,
};
}
export async function getStaticPaths(
context: GetStaticPathsContext
): Promise<GetStaticPathsResult> {
const sdk = getReadySdk();
const libraryItems = await sdk.getLibraryItemsSlugs();
const paths: GetStaticPathsResult["paths"] = [];
if (libraryItems.libraryItems) {
libraryItems.libraryItems.data.map((item) => {
context.locales?.map((local) => {
paths.push({ params: { slug: item.attributes?.slug }, locale: local });
});
});
}
return {
paths,
fallback: "blocking",
};
}