2022-05-04 04:13:17 +02:00

429 lines
14 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 ThumbnailPreview from "components/PreviewCard";
import { GetLibraryItemsPreviewQuery } from "graphql/generated";
import { getReadySdk } from "graphql/sdk";
import { GetStaticPropsContext } from "next";
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
import {
convertPrice,
prettyDate,
prettyinlineTitle,
prettyItemSubType,
} from "queries/helpers";
import { useEffect, useState } from "react";
interface Props extends AppStaticProps {
items: Exclude<
GetLibraryItemsPreviewQuery["libraryItems"],
null | undefined
>["data"];
}
type GroupLibraryItems = Map<string, Props["items"]>;
export default function Library(props: Props): JSX.Element {
const { langui, items: libraryItems, currencies } = props;
const [showSubitems, setShowSubitems] = useState<boolean>(false);
const [showPrimaryItems, setShowPrimaryItems] = useState<boolean>(true);
const [showSecondaryItems, setShowSecondaryItems] = useState<boolean>(false);
const [sortingMethod, setSortingMethod] = useState<number>(0);
const [groupingMethod, setGroupingMethod] = useState<number>(-1);
const [keepInfoVisible, setKeepInfoVisible] = useState(false);
const [filteredItems, setFilteredItems] = useState<Props["items"]>(
filterItems(
showSubitems,
showPrimaryItems,
showSecondaryItems,
libraryItems
)
);
const [sortedItems, setSortedItem] = useState<Props["items"]>(
sortBy(groupingMethod, filteredItems, currencies)
);
const [groups, setGroups] = useState<GroupLibraryItems>(
getGroups(langui, groupingMethod, sortedItems)
);
useEffect(() => {
setFilteredItems(
filterItems(
showSubitems,
showPrimaryItems,
showSecondaryItems,
libraryItems
)
);
}, [showSubitems, libraryItems, showPrimaryItems, showSecondaryItems]);
useEffect(() => {
setSortedItem(sortBy(sortingMethod, filteredItems, currencies));
}, [currencies, filteredItems, sortingMethod]);
useEffect(() => {
setGroups(getGroups(langui, groupingMethod, sortedItems));
}, [langui, groupingMethod, sortedItems]);
const subPanel = (
<SubPanel>
<PanelHeader
icon="library_books"
title={langui.library}
description={langui.library_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 ?? "Category",
langui.type ?? "Type",
langui.release_year ?? "Year",
]}
state={groupingMethod}
setState={setGroupingMethod}
allowEmpty
/>
</div>
<div className="flex flex-row gap-2 place-items-center">
<p className="flex-shrink-0">{langui.order_by}:</p>
<Select
className="w-full"
options={[
langui.name ?? "Name",
langui.price ?? "Price",
langui.release_date ?? "Release date",
]}
state={sortingMethod}
setState={setSortingMethod}
/>
</div>
<div className="flex flex-row gap-2 place-items-center">
<p className="flex-shrink-0">{langui.show_subitems}:</p>
<Switch state={showSubitems} setState={setShowSubitems} />
</div>
<div className="flex flex-row gap-2 place-items-center">
<p className="flex-shrink-0">{langui.show_primary_items}:</p>
<Switch state={showPrimaryItems} setState={setShowPrimaryItems} />
</div>
<div className="flex flex-row gap-2 place-items-center">
<p className="flex-shrink-0">{langui.show_secondary_items}:</p>
<Switch state={showSecondaryItems} setState={setShowSecondaryItems} />
</div>
<div className="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>
</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() ?? "result"
: langui.results?.toLowerCase() ?? "results"
}`}</Chip>
</h2>
)}
<div
key={`items${name}`}
className="grid gap-8 items-end mobile:grid-cols-2 desktop:grid-cols-[repeat(auto-fill,_minmax(13rem,1fr))] pb-12 border-b-[3px] border-dotted last-of-type:border-0"
>
{items.map((item) => (
<>
{item.attributes && (
<ThumbnailPreview
key={item.id}
href={`/library/${item.attributes.slug}`}
title={item.attributes.title}
subtitle={item.attributes.subtitle}
thumbnail={item.attributes.thumbnail?.data?.attributes}
thumbnailAspectRatio="21/29.7"
keepInfoVisible={keepInfoVisible}
topChips={
item.attributes.metadata &&
item.attributes.metadata.length > 0 &&
item.attributes.metadata[0]
? [prettyItemSubType(item.attributes.metadata[0])]
: []
}
bottomChips={item.attributes.categories?.data.map(
(category) => category.attributes?.short ?? ""
)}
metadata={{
currencies: currencies,
release_date: item.attributes.release_date,
price: item.attributes.price,
position: "Bottom",
}}
/>
)}
</>
))}
</div>
</>
)}
</>
))}
</ContentPanel>
);
return (
<AppLayout
navTitle={langui.library}
subPanel={subPanel}
contentPanel={contentPanel}
{...props}
/>
);
}
export async function getStaticProps(
context: GetStaticPropsContext
): Promise<{ notFound: boolean } | { props: Props }> {
const sdk = getReadySdk();
const items = await sdk.getLibraryItemsPreview({
language_code: context.locale ?? "en",
});
if (!items.libraryItems) return { notFound: true };
const props: Props = {
...(await getAppStaticProps(context)),
items: items.libraryItems.data,
};
return {
props: props,
};
}
function getGroups(
langui: AppStaticProps["langui"],
groupByType: number,
items: Props["items"]
): GroupLibraryItems {
switch (groupByType) {
case 0: {
const typeGroup = new Map();
typeGroup.set("Drakengard 1", []);
typeGroup.set("Drakengard 1.3", []);
typeGroup.set("Drakengard 2", []);
typeGroup.set("Drakengard 3", []);
typeGroup.set("Drakengard 4", []);
typeGroup.set("NieR Gestalt", []);
typeGroup.set("NieR Replicant", []);
typeGroup.set("NieR Replicant ver.1.22474487139...", []);
typeGroup.set("NieR:Automata", []);
typeGroup.set("NieR Re[in]carnation", []);
typeGroup.set("SINoALICE", []);
typeGroup.set("Voice of Cards", []);
typeGroup.set("Final Fantasy XIV", []);
typeGroup.set("Thou Shalt Not Die", []);
typeGroup.set("Bakuken", []);
typeGroup.set("YoRHa", []);
typeGroup.set("YoRHa Boys", []);
typeGroup.set(langui.no_category, []);
items.map((item) => {
if (item.attributes?.categories?.data.length === 0) {
typeGroup.get(langui.no_category)?.push(item);
} else {
item.attributes?.categories?.data.map((category) => {
typeGroup.get(category.attributes?.name)?.push(item);
});
}
});
return typeGroup;
}
case 1: {
const group: GroupLibraryItems = new Map();
group.set(langui.audio ?? "Audio", []);
group.set(langui.game ?? "Game", []);
group.set(langui.textual ?? "Textual", []);
group.set(langui.video ?? "Video", []);
group.set(langui.other ?? "Other", []);
group.set(langui.group ?? "Group", []);
group.set(langui.no_type ?? "No type", []);
items.map((item) => {
if (item.attributes?.metadata && item.attributes.metadata.length > 0) {
switch (item.attributes.metadata[0]?.__typename) {
case "ComponentMetadataAudio":
group.get(langui.audio ?? "Audio")?.push(item);
break;
case "ComponentMetadataGame":
group.get(langui.game ?? "Game")?.push(item);
break;
case "ComponentMetadataBooks":
group.get(langui.textual ?? "Textual")?.push(item);
break;
case "ComponentMetadataVideo":
group.get(langui.video ?? "Video")?.push(item);
break;
case "ComponentMetadataOther":
group.get(langui.other ?? "Other")?.push(item);
break;
case "ComponentMetadataGroup":
switch (
item.attributes.metadata[0]?.subitems_type?.data?.attributes
?.slug
) {
case "audio":
group.get(langui.audio ?? "Audio")?.push(item);
break;
case "video":
group.get(langui.video ?? "Video")?.push(item);
break;
case "game":
group.get(langui.game ?? "Game")?.push(item);
break;
case "textual":
group.get(langui.textual ?? "Textual")?.push(item);
break;
case "mixed":
group.get(langui.group ?? "Group")?.push(item);
break;
default: {
throw new Error(
"An unexpected subtype of group-metadata was given"
);
}
}
break;
default: {
throw new Error("An unexpected type of metadata was given");
}
}
} else {
group.get(langui.no_type ?? "No type")?.push(item);
}
});
return group;
}
case 2: {
const years: number[] = [];
items.map((item) => {
if (item.attributes?.release_date?.year) {
if (!years.includes(item.attributes.release_date.year))
years.push(item.attributes.release_date.year);
}
});
const group: GroupLibraryItems = new Map();
years.sort((a, b) => a - b);
years.map((year) => {
group.set(year.toString(), []);
});
group.set(langui.no_year ?? "No year", []);
items.map((item) => {
if (item.attributes?.release_date?.year) {
group.get(item.attributes.release_date.year.toString())?.push(item);
} else {
group.get(langui.no_year ?? "No year")?.push(item);
}
});
return group;
}
default: {
const group: GroupLibraryItems = new Map();
group.set("", items);
return group;
}
}
}
function filterItems(
showSubitems: boolean,
showPrimaryItems: boolean,
showSecondaryItems: boolean,
items: Props["items"]
): Props["items"] {
return [...items].filter((item) => {
if (!showSubitems && !item.attributes?.root_item) return false;
if (
showSubitems &&
item.attributes?.metadata?.[0]?.__typename === "ComponentMetadataGroup" &&
(item.attributes.metadata[0].subtype?.data?.attributes?.slug ===
"variant-set" ||
item.attributes.metadata[0].subtype?.data?.attributes?.slug ===
"relation-set")
)
return false;
if (item.attributes?.primary && !showPrimaryItems) return false;
if (!item.attributes?.primary && !showSecondaryItems) return false;
return true;
});
}
function sortBy(
orderByType: number,
items: Props["items"],
currencies: AppStaticProps["currencies"]
): Props["items"] {
switch (orderByType) {
case 0:
return [...items].sort((a, b) => {
const titleA = prettyinlineTitle(
"",
a.attributes?.title,
a.attributes?.subtitle
);
const titleB = prettyinlineTitle(
"",
b.attributes?.title,
b.attributes?.subtitle
);
return titleA.localeCompare(titleB);
});
case 1:
return [...items].sort((a, b) => {
const priceA = a.attributes?.price
? convertPrice(a.attributes.price, currencies[0])
: 99999;
const priceB = b.attributes?.price
? convertPrice(b.attributes.price, currencies[0])
: 99999;
return priceA - priceB;
});
case 2:
return [...items].sort((a, b) => {
const dateA = a.attributes?.release_date
? prettyDate(a.attributes.release_date)
: "9999";
const dateB = b.attributes?.release_date
? prettyDate(b.attributes.release_date)
: "9999";
return dateA.localeCompare(dateB);
});
default:
return items;
}
}