2022-08-16 00:17:26 +02:00

264 lines
9.4 KiB
TypeScript

import { GetStaticProps } from "next";
import { useCallback, useMemo, useState } from "react";
import { useBoolean } from "usehooks-ts";
import { AppLayout, AppLayoutRequired } from "components/AppLayout";
import { NavOption } from "components/PanelComponents/NavOption";
import { PanelHeader } from "components/PanelComponents/PanelHeader";
import { SubPanel } from "components/Panels/SubPanel";
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
import { Icon } from "components/Ico";
import { getReadySdk } from "graphql/sdk";
import { GetWikiPageQuery, GetWikiPagesPreviewsQuery } from "graphql/generated";
import {
ContentPanel,
ContentPanelWidthSizes,
} from "components/Panels/ContentPanel";
import { HorizontalLine } from "components/HorizontalLine";
import { Button } from "components/Inputs/Button";
import { Switch } from "components/Inputs/Switch";
import { TextInput } from "components/Inputs/TextInput";
import { WithLabel } from "components/Inputs/WithLabel";
import { useMediaHoverable } from "hooks/useMediaQuery";
import { filterDefined, filterHasAttributes } from "helpers/others";
import { SmartList } from "components/SmartList";
import { Select } from "components/Inputs/Select";
import { SelectiveNonNullable } from "helpers/types/SelectiveNonNullable";
import { prettySlug } from "helpers/formatters";
import { getOpenGraph } from "helpers/openGraph";
import { TranslatedPreviewCard } from "components/PreviewCard";
/*
* ╭─────────────╮
* ────────────────────────────────────────╯ CONSTANTS ╰──────────────────────────────────────────
*/
const DEFAULT_FILTERS_STATE = {
searchName: "",
keepInfoVisible: true,
groupingMethod: -1,
};
/*
* ╭────────╮
* ──────────────────────────────────────────╯ PAGE ╰─────────────────────────────────────────────
*/
interface Props extends AppStaticProps, AppLayoutRequired {
pages: NonNullable<GetWikiPagesPreviewsQuery["wikiPages"]>["data"];
}
const Wiki = ({ langui, pages, ...otherProps }: Props): JSX.Element => {
const hoverable = useMediaHoverable();
const [searchName, setSearchName] = useState(
DEFAULT_FILTERS_STATE.searchName
);
const [groupingMethod, setGroupingMethod] = useState<number>(
DEFAULT_FILTERS_STATE.groupingMethod
);
const {
value: keepInfoVisible,
toggle: toggleKeepInfoVisible,
setValue: setKeepInfoVisible,
} = useBoolean(DEFAULT_FILTERS_STATE.keepInfoVisible);
const subPanel = useMemo(
() => (
<SubPanel>
<PanelHeader
icon={Icon.TravelExplore}
title={langui.wiki}
description={langui.wiki_description}
/>
<TextInput
className="mb-6 w-full"
placeholder={langui.search_title ?? "Search..."}
value={searchName}
onChange={setSearchName}
/>
<WithLabel label={langui.group_by}>
<Select
className="w-full"
options={[langui.category ?? "Category"]}
value={groupingMethod}
onChange={setGroupingMethod}
allowEmpty
/>
</WithLabel>
{hoverable && (
<WithLabel label={langui.always_show_info}>
<Switch value={keepInfoVisible} onClick={toggleKeepInfoVisible} />
</WithLabel>
)}
<Button
className="mt-8"
text={langui.reset_all_filters}
icon={Icon.Replay}
onClick={() => {
setSearchName(DEFAULT_FILTERS_STATE.searchName);
setKeepInfoVisible(DEFAULT_FILTERS_STATE.keepInfoVisible);
}}
/>
<HorizontalLine />
<p className="mb-4 font-headers text-xl font-bold">
{langui.special_pages}
</p>
<NavOption title={langui.chronology} url="/wiki/chronology" border />
</SubPanel>
),
[
groupingMethod,
hoverable,
keepInfoVisible,
langui,
searchName,
setKeepInfoVisible,
toggleKeepInfoVisible,
]
);
const groupingFunction = useCallback(
(
item: SelectiveNonNullable<
NonNullable<GetWikiPageQuery["wikiPages"]>["data"][number],
"attributes" | "id"
>
): string[] => {
switch (groupingMethod) {
case 0: {
const categories = filterHasAttributes(
item.attributes.categories?.data,
["attributes"] as const
);
if (categories.length > 0) {
return categories.map((category) => category.attributes.name);
}
return [langui.no_category ?? "No category"];
}
default: {
return [""];
}
}
},
[groupingMethod, langui]
);
const contentPanel = useMemo(
() => (
<ContentPanel width={ContentPanelWidthSizes.Full}>
<SmartList
items={filterHasAttributes(pages, ["id", "attributes"] as const)}
getItemId={(item) => item.id}
renderItem={({ item }) => (
<TranslatedPreviewCard
href={`/wiki/${item.attributes.slug}`}
translations={filterHasAttributes(item.attributes.translations, [
"language.data.attributes.code",
] as const).map((translation) => ({
title: translation.title,
subtitle:
translation.aliases && translation.aliases.length > 0
? translation.aliases
.map((alias) => alias?.alias)
.join("・")
: undefined,
description: translation.summary,
language: translation.language.data.attributes.code,
}))}
fallback={{ title: prettySlug(item.attributes.slug) }}
thumbnail={item.attributes.thumbnail?.data?.attributes}
thumbnailAspectRatio={"4/3"}
thumbnailRounded
thumbnailForceAspectRatio
keepInfoVisible={keepInfoVisible}
topChips={filterHasAttributes(item.attributes.tags?.data, [
"attributes",
] as const).map(
(tag) =>
tag.attributes.titles?.[0]?.title ??
prettySlug(tag.attributes.slug)
)}
bottomChips={filterHasAttributes(
item.attributes.categories?.data,
["attributes"] as const
).map((category) => category.attributes.short)}
/>
)}
langui={langui}
className="grid-cols-2 desktop:grid-cols-[repeat(auto-fill,_minmax(20rem,1fr))]"
searchingTerm={searchName}
searchingBy={(item) =>
filterDefined(item.attributes.translations)
.map(
(translation) =>
`${translation.title} ${filterDefined(translation.aliases)
.map((alias) => alias.alias)
.join(" ")}`
)
.join(" ")
}
groupingFunction={groupingFunction}
paginationItemPerPage={25}
/>
</ContentPanel>
),
[groupingFunction, keepInfoVisible, langui, pages, searchName]
);
return (
<AppLayout
subPanel={subPanel}
contentPanel={contentPanel}
subPanelIcon={Icon.Search}
langui={langui}
{...otherProps}
/>
);
};
export default Wiki;
/*
* ╭──────────────────────╮
* ───────────────────────────────────╯ NEXT DATA FETCHING ╰──────────────────────────────────────
*/
export const getStaticProps: GetStaticProps = async (context) => {
const sdk = getReadySdk();
const pages = await sdk.getWikiPagesPreviews({
language_code: context.locale ?? "en",
});
if (!pages.wikiPages?.data) return { notFound: true };
const appStaticProps = await getAppStaticProps(context);
const props: Props = {
...appStaticProps,
pages: sortPages(pages.wikiPages.data),
openGraph: getOpenGraph(
appStaticProps.langui,
appStaticProps.langui.wiki ?? "Wiki"
),
};
return {
props: props,
};
};
/*
* ╭───────────────────╮
* ─────────────────────────────────────╯ PRIVATE METHODS ╰───────────────────────────────────────
*/
const sortPages = (pages: Props["pages"]): Props["pages"] =>
pages.sort((a, b) => {
const slugA = a.attributes?.slug ?? "";
const slugB = b.attributes?.slug ?? "";
return slugA.localeCompare(slugB);
});