import { AppLayout } from "components/AppLayout"; import { Chip } from "components/Chip"; import { Select } from "components/Inputs/Select"; import { Switch } from "components/Inputs/Switch"; import { PanelHeader } from "components/PanelComponents/PanelHeader"; import { ContentPanel, ContentPanelWidthSizes, } from "components/Panels/ContentPanel"; import { SubPanel } from "components/Panels/SubPanel"; import { PreviewCard } from "components/PreviewCard"; import { GetContentsQuery } from "graphql/generated"; import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps"; import { getReadySdk } from "graphql/sdk"; import { prettyinlineTitle, prettySlug } from "helpers/formatters"; import { Immutable } from "helpers/types"; import { GetStaticPropsContext } from "next"; import { useEffect, useState } from "react"; interface Props extends AppStaticProps { contents: NonNullable<GetContentsQuery["contents"]>["data"]; } type GroupContentItems = Map<string, Immutable<Props["contents"]>>; export default function Contents(props: Immutable<Props>): JSX.Element { const { langui, contents } = props; const [groupingMethod, setGroupingMethod] = useState<number>(-1); const [keepInfoVisible, setKeepInfoVisible] = useState(false); const [combineRelatedContent, setCombineRelatedContent] = useState(true); const [filteredItems, setFilteredItems] = useState( filterContents(combineRelatedContent, contents) ); const [groups, setGroups] = useState<GroupContentItems>( getGroups(langui, groupingMethod, filteredItems) ); useEffect(() => { setFilteredItems(filterContents(combineRelatedContent, contents)); }, [combineRelatedContent, contents]); useEffect(() => { setGroups(getGroups(langui, groupingMethod, filteredItems)); }, [langui, groupingMethod, filteredItems]); const subPanel = ( <SubPanel> <PanelHeader icon="workspaces" title={langui.contents} description={langui.contents_description} /> <div className="flex flex-row gap-2 place-items-center"> <p className="flex-shrink-0">{langui.group_by}:</p> <Select className="w-full" options={[langui.category ?? "", langui.type ?? ""]} state={groupingMethod} setState={setGroupingMethod} allowEmpty /> </div> <div className="flex flex-row gap-2 place-items-center coarse:hidden"> <p className="flex-shrink-0">{langui.combine_related_contents}:</p> <Switch setState={setCombineRelatedContent} state={combineRelatedContent} /> </div> <div className="flex flex-row gap-2 place-items-center coarse:hidden"> <p className="flex-shrink-0">{langui.always_show_info}:</p> <Switch setState={setKeepInfoVisible} state={keepInfoVisible} /> </div> </SubPanel> ); const contentPanel = ( <ContentPanel width={ContentPanelWidthSizes.large}> {[...groups].map(([name, items]) => ( <> {items.length > 0 && ( <> {name && ( <h2 key={`h2${name}`} className="text-2xl pb-2 pt-10 first-of-type:pt-0 flex flex-row place-items-center gap-2" > {name} <Chip>{`${items.length} ${ items.length <= 1 ? langui.result?.toLowerCase() ?? "" : langui.results?.toLowerCase() ?? "" }`}</Chip> </h2> )} <div key={`items${name}`} className="grid gap-8 mobile:gap-4 items-end grid-cols-2 desktop:grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))]" > {items.map((item) => ( <> {item.attributes && ( <PreviewCard key={item.id} href={`/contents/${item.attributes.slug}`} pre_title={item.attributes.translations?.[0]?.pre_title} title={ item.attributes.translations?.[0]?.title ?? prettySlug(item.attributes.slug) } subtitle={item.attributes.translations?.[0]?.subtitle} thumbnail={item.attributes.thumbnail?.data?.attributes} thumbnailAspectRatio="3/2" stackNumber={ combineRelatedContent && item.attributes.group?.data?.attributes?.combine ? 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} /> )} </> ))} </div> </> )} </> ))} </ContentPanel> ); return ( <AppLayout navTitle={langui.contents} subPanel={subPanel} contentPanel={contentPanel} {...props} /> ); } export async function getStaticProps( context: GetStaticPropsContext ): Promise<{ notFound: boolean } | { props: Props }> { const sdk = getReadySdk(); const contents = await sdk.getContents({ language_code: context.locale ?? "en", }); if (!contents.contents) return { notFound: true }; contents.contents.data.sort((a, b) => { const titleA = a.attributes?.translations?.[0] ? prettyinlineTitle( a.attributes.translations[0].pre_title, a.attributes.translations[0].title, a.attributes.translations[0].subtitle ) : a.attributes?.slug ?? ""; const titleB = b.attributes?.translations?.[0] ? prettyinlineTitle( b.attributes.translations[0].pre_title, b.attributes.translations[0].title, b.attributes.translations[0].subtitle ) : b.attributes?.slug ?? ""; return titleA.localeCompare(titleB); }); const props: Props = { ...(await getAppStaticProps(context)), contents: contents.contents.data, }; return { props: props, }; } function getGroups( langui: AppStaticProps["langui"], groupByType: number, items: Immutable<Props["contents"]> ): GroupContentItems { switch (groupByType) { case 0: { const group = new Map(); group.set("Drakengard 1", []); group.set("Drakengard 1.3", []); group.set("Drakengard 2", []); group.set("Drakengard 3", []); group.set("Drakengard 4", []); group.set("NieR Gestalt", []); group.set("NieR Replicant", []); group.set("NieR Replicant ver.1.22474487139...", []); group.set("NieR:Automata", []); group.set("NieR Re[in]carnation", []); group.set("SINoALICE", []); group.set("Voice of Cards", []); group.set("Final Fantasy XIV", []); group.set("Thou Shalt Not Die", []); group.set("Bakuken", []); group.set("YoRHa", []); group.set("YoRHa Boys", []); group.set(langui.no_category, []); items.map((item) => { if (item.attributes?.categories?.data.length === 0) { group.get(langui.no_category)?.push(item); } else { item.attributes?.categories?.data.map((category) => { group.get(category.attributes?.name)?.push(item); }); } }); return group; } case 1: { const group = new Map(); items.map((item) => { const type = item.attributes?.type?.data?.attributes?.titles?.[0]?.title ?? item.attributes?.type?.data?.attributes?.slug ? prettySlug(item.attributes.type.data.attributes.slug) : langui.no_type; if (!group.has(type)) group.set(type, []); group.get(type)?.push(item); }); return group; } default: { const group: GroupContentItems = new Map(); group.set("", items); return group; } } } function filterContents( combineRelatedContent: boolean, contents: Immutable<Props["contents"]> ): Immutable<Props["contents"]> { if (combineRelatedContent) { return [...contents].filter( (content) => !content.attributes?.group?.data?.attributes || !content.attributes.group.data.attributes.combine || content.attributes.group.data.attributes.contents?.data[0].id === content.id ); } return contents; }