import { Fragment, useCallback } from "react"; import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next"; import { useRouter } from "next/router"; import { useBoolean } from "usehooks-ts"; import { AppLayout, AppLayoutRequired } 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/Containers/InsetBox"; import { PreviewCardCTAs } from "components/Library/PreviewCardCTAs"; import { NavOption } from "components/PanelComponents/NavOption"; import { ReturnButton } from "components/PanelComponents/ReturnButton"; import { ContentPanel, ContentPanelWidthSizes } from "components/Containers/ContentPanel"; import { SubPanel } from "components/Containers/SubPanel"; import { PreviewCard } from "components/PreviewCard"; import { Enum_Componentmetadatabooks_Binding_Type, Enum_Componentmetadatabooks_Page_Order, GetLibraryItemQuery, } from "graphql/generated"; import { getReadySdk } from "graphql/sdk"; import { prettyDate, prettyInlineTitle, prettyItemSubType, prettyPrice, prettySlug, prettyURL, } from "helpers/formatters"; import { ImageQuality } from "helpers/img"; import { convertMmToInch } from "helpers/numbers"; import { sortRangedContent } from "helpers/others"; import { filterDefined, filterHasAttributes, isDefined, isDefinedAndNotEmpty, } from "helpers/asserts"; import { useScrollTopOnChange } from "hooks/useScrollTopOnChange"; import { getScanArchiveURL, isUntangibleGroupItem } from "helpers/libraryItem"; import { useDeviceSupportsHover } from "hooks/useMediaQuery"; import { WithLabel } from "components/Inputs/WithLabel"; import { cJoin, cIf } from "helpers/className"; import { useSmartLanguage } from "hooks/useSmartLanguage"; import { getOpenGraph } from "helpers/openGraph"; import { getDescription } from "helpers/description"; import { useIntersectionList } from "hooks/useIntersectionList"; import { Ids } from "types/ids"; import { atoms } from "contexts/atoms"; import { useAtomGetter, useAtomSetter } from "helpers/atoms"; import { useFormat } from "hooks/useFormat"; import { getFormat } from "helpers/i18n"; import { ElementsSeparator } from "helpers/component"; import { ToolTip } from "components/ToolTip"; /* * ╭─────────────╮ * ────────────────────────────────────────╯ CONSTANTS ╰────────────────────────────────────────── */ const intersectionIds = ["summary", "gallery", "details", "subitems", "contents"]; /* * ╭────────╮ * ──────────────────────────────────────────╯ PAGE ╰───────────────────────────────────────────── */ interface Props extends AppLayoutRequired { item: NonNullable["data"][number]["attributes"]>; itemId: NonNullable["data"][number]["id"]; } const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => { const currency = useAtomGetter(atoms.settings.currency); const { format, formatLibraryItemType } = useFormat(); const currencies = useAtomGetter(atoms.localData.currencies); const isContentPanelAtLeast3xl = useAtomGetter(atoms.containerQueries.isContentPanelAtLeast3xl); const isContentPanelAtLeastSm = useAtomGetter(atoms.containerQueries.isContentPanelAtLeastSm); const hoverable = useDeviceSupportsHover(); const router = useRouter(); const { value: keepInfoVisible, toggle: toggleKeepInfoVisible } = useBoolean(false); const { showLightBox } = useAtomGetter(atoms.lightBox); const setSubPanelOpened = useAtomSetter(atoms.layout.subPanelOpened); const closeSubPanel = useCallback(() => setSubPanelOpened(false), [setSubPanelOpened]); useScrollTopOnChange(Ids.ContentPanel, [itemId]); const currentIntersection = useIntersectionList(intersectionIds); const isVariantSet = item.metadata?.[0]?.__typename === "ComponentMetadataGroup" && item.metadata[0].subtype?.data?.attributes?.slug === "variant-set"; const hasContentScans = item.contents?.data.some( (content) => content.attributes?.scan_set && content.attributes.scan_set.length > 0 ); const hasContentSection = (item.contents && item.contents.data.length > 0) || item.download_available; const is3ColumnsLayout = useAtomGetter(atoms.containerQueries.is3ColumnsLayout); const subPanel = ( {[ is3ColumnsLayout && ( ),
{item.gallery && item.gallery.data.length > 0 && ( )} {item.subitems && item.subitems.data.length > 0 && ( )} {item.contents && item.contents.data.length > 0 && ( )}
, ]}
); const contentPanel = (
{item.thumbnail?.data?.attributes ? ( { showLightBox([item.thumbnail?.data?.attributes]); }} /> ) : (
)}
{item.subitem_of?.data[0]?.attributes && (

{format("subitem_of_x", { x: "" })}

)}

{item.title}

{isDefinedAndNotEmpty(item.subtitle) &&

{item.subtitle}

}
{!isUntangibleGroupItem(item.metadata?.[0]) && isDefinedAndNotEmpty(itemId) && ( )} {item.descriptions?.[0] && (

{item.descriptions[0].description}

)} {!( 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?.length ? (

{format("available_at")}

{filterHasAttributes(item.urls, ["url"]).map((url, index) => (
) : (

{format("item_not_available")}

)} )}
{item.gallery && item.gallery.data.length > 0 && (

{format("gallery")}

{filterHasAttributes(item.gallery.data, ["id", "attributes"]).map( (galleryItem, index) => (
{ showLightBox( filterHasAttributes(item.gallery?.data, ["attributes"]).map( (image) => image.attributes ), index ); }}>
) )}
)}

{format("details")}

{item.metadata?.[0] && (

{format("type", { count: 1 })}

{"›"}
)} {item.release_date && (

{format("release_date")}

{prettyDate(item.release_date, router.locale)}

)} {item.price && (

{format("price")}

{prettyPrice( item.price, currencies, item.price.currency?.data?.attributes?.code )}

{item.price.currency?.data?.attributes?.code !== currency && (

{prettyPrice(item.price, currencies, currency)}
( {format("calculated").toLowerCase()})

)}
)}
{item.categories && item.categories.data.length > 0 && (

{format("category", { count: item.categories.data.length })}

{filterHasAttributes(item.categories.data, ["attributes"]).map((category) => ( ))}
)} {item.size && (

{format("size")}

{format("width")}:

{item.size.width} mm

{convertMmToInch(item.size.width)} in

{format("height")}:

{item.size.height} mm

{convertMmToInch(item.size.height)} in

{isDefined(item.size.thickness) && (

{format("thickness")}:

{item.size.thickness} mm

{convertMmToInch(item.size.thickness)} in

)}
)} {item.metadata?.[0]?.__typename !== "ComponentMetadataGroup" && item.metadata?.[0]?.__typename !== "ComponentMetadataOther" && (

{format("type_information")}

{item.metadata?.[0]?.__typename === "ComponentMetadataBooks" && ( <> {isDefined(item.metadata[0].page_count) && (

{format("page", { count: Infinity })}:

{item.metadata[0].page_count}

)}

{format("binding")}:

{item.metadata[0].binding_type === Enum_Componentmetadatabooks_Binding_Type.Paperback ? format("paperback") : item.metadata[0].binding_type === Enum_Componentmetadatabooks_Binding_Type.Hardcover ? format("hardcover") : ""}

{format("page_order")}:

{item.metadata[0].page_order === Enum_Componentmetadatabooks_Page_Order.LeftToRight ? format("left_to_right") : format("right_to_left")}

{isDefined(item.metadata[0].languages) && (

{format("language", { count: item.metadata[0].languages.data.length, })} :

{item.metadata[0].languages.data.map((lang) => (

{lang.attributes?.name}

))}
)} )}
)}
{item.subitems && item.subitems.data.length > 0 && (

{format(isVariantSet ? "variant" : "subitem", { count: Infinity })}

{hoverable && ( )}
{filterHasAttributes(item.subitems.data, ["id", "attributes"]).map((subitem) => ( 0 && subitem.attributes.metadata[0] ? [prettyItemSubType(subitem.attributes.metadata[0])] : [] } bottomChips={subitem.attributes.categories?.data.map( (category) => category.attributes?.short ?? "" )} metadata={{ releaseDate: subitem.attributes.release_date, price: subitem.attributes.price, position: "Bottom", }} infoAppend={ !isUntangibleGroupItem(subitem.attributes.metadata?.[0]) && ( ) } /> ))}
)} {hasContentSection && (

{format("contents")}

{hasContentScans && (
{filterHasAttributes(item.contents?.data, ["attributes"]).map((rangedContent) => ( ({ pre_title: translation.pre_title, title: translation.title, subtitle: translation.subtitle, language: translation.language?.data?.attributes?.code, })), categories: filterHasAttributes( rangedContent.attributes.content.data.attributes.categories?.data, ["attributes"] ).map((category) => category.attributes.short), type: rangedContent.attributes.content.data.attributes.type?.data ?.attributes?.titles?.[0]?.title ?? prettySlug( rangedContent.attributes.content.data.attributes.type?.data ?.attributes?.slug ), slug: rangedContent.attributes.content.data.attributes.slug, } : undefined } rangeStart={ rangedContent.attributes.range[0]?.__typename === "ComponentRangePageRange" ? `${rangedContent.attributes.range[0].starting_page}` : "" } slug={rangedContent.attributes.slug} parentSlug={item.slug} key={rangedContent.id} hasScanSet={ isDefined(rangedContent.attributes.scan_set) && rangedContent.attributes.scan_set.length > 0 } displayType={isContentPanelAtLeast3xl ? "row" : "card"} /> ))}
)}
); return ; }; export default LibrarySlug; /* * ╭──────────────────────╮ * ───────────────────────────────────╯ NEXT DATA FETCHING ╰────────────────────────────────────── */ export const getStaticProps: GetStaticProps = async (context) => { const sdk = getReadySdk(); const { format } = getFormat(context.locale); const item = await sdk.getLibraryItem({ slug: context.params && isDefined(context.params.slug) ? context.params.slug.toString() : "", language_code: context.locale ?? "en", }); if (!item.libraryItems?.data[0]?.attributes) return { notFound: true }; sortRangedContent(item.libraryItems.data[0].attributes.contents); const { title, thumbnail } = item.libraryItems.data[0].attributes; const description = getDescription( item.libraryItems.data[0].attributes.descriptions?.[0]?.description, { [format("category", { count: Infinity })]: filterHasAttributes( item.libraryItems.data[0].attributes.categories?.data, ["attributes.short"] ).map((category) => category.attributes.short), [format("type", { count: Infinity })]: item.libraryItems.data[0].attributes.metadata?.[0] ? [prettyItemSubType(item.libraryItems.data[0].attributes.metadata[0])] : [], [format("release_date")]: [ item.libraryItems.data[0].attributes.release_date ? prettyDate(item.libraryItems.data[0].attributes.release_date, context.locale) : undefined, ], } ); const props: Props = { item: item.libraryItems.data[0].attributes, itemId: item.libraryItems.data[0].id, openGraph: getOpenGraph(format, title, description, thumbnail?.data?.attributes), }; return { props: props, }; }; // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ export const getStaticPaths: GetStaticPaths = async (context) => { const sdk = getReadySdk(); const libraryItems = await sdk.getLibraryItemsSlugs(); const paths: GetStaticPathsResult["paths"] = []; filterHasAttributes(libraryItems.libraryItems?.data, ["attributes"]).map((item) => { context.locales?.map((local) => paths.push({ params: { slug: item.attributes.slug }, locale: local }) ); }); return { paths, fallback: "blocking", }; }; /* * ╭──────────────────────╮ * ───────────────────────────────────╯ PRIVATE COMPONENTS ╰────────────────────────────────────── */ interface ContentItemProps { content?: { translations: { pre_title: string | null | undefined; title: string; subtitle: string | null | undefined; language: string | undefined; }[]; categories?: string[]; type?: string; slug: string; }; rangeStart: string; parentSlug: string; slug: string; hasScanSet: boolean; displayType: "card" | "row"; } const ContentItem = ({ rangeStart, content, hasScanSet, slug, parentSlug, displayType, }: ContentItemProps): JSX.Element => { const { format } = useFormat(); const [selectedTranslation] = useSmartLanguage({ items: content?.translations ?? [], languageExtractor: useCallback( (item: NonNullable["translations"][number]) => item.language, [] ), }); if (displayType === "card") { return (

{selectedTranslation ? prettyInlineTitle( selectedTranslation.pre_title, selectedTranslation.title, selectedTranslation.subtitle ) : content ? prettySlug(content.slug, parentSlug) : prettySlug(slug, parentSlug)}

{rangeStart}

{content && (
{content.categories && (

{format("category", { count: content.categories.length })}

{content.categories.map((category, index) => ( ))}
)} {content.type && (

{format("type", { count: 1 })}

)}
)} {(hasScanSet || content) && (
{hasScanSet && (
)}
); } return ( <>

{selectedTranslation ? prettyInlineTitle( selectedTranslation.pre_title, selectedTranslation.title, selectedTranslation.subtitle ) : content ? prettySlug(content.slug, parentSlug) : prettySlug(slug, parentSlug)}

{content?.categories?.map((category, index) => ( ))}

{content?.type && }

{rangeStart}

{hasScanSet && (
{isDefined(content) && (
); };