288 lines
10 KiB
TypeScript

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.reduce((currentSum, item) => {
if (combineRelatedContent) {
if (item.attributes?.group?.data?.attributes?.combine) {
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
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;
}