Improved the wiki section

This commit is contained in:
DrMint 2022-07-11 02:35:09 +02:00
parent bb42e2a56f
commit d1d055de29
7 changed files with 278 additions and 91 deletions

View File

@ -1,4 +1,10 @@
import { Dispatch, Fragment, SetStateAction, useState } from "react"; import {
Dispatch,
Fragment,
SetStateAction,
useCallback,
useState,
} from "react";
import { Ico, Icon } from "components/Ico"; import { Ico, Icon } from "components/Ico";
import { cIf, cJoin } from "helpers/className"; import { cIf, cJoin } from "helpers/className";
import { useToggle } from "hooks/useToggle"; import { useToggle } from "hooks/useToggle";
@ -29,6 +35,11 @@ export const Select = ({
const [opened, setOpened] = useState(false); const [opened, setOpened] = useState(false);
const toggleOpened = useToggle(setOpened); const toggleOpened = useToggle(setOpened);
const tryToggling = useCallback(() => {
const optionCount = options.length + (state === -1 ? 1 : 0);
if (optionCount > 1) toggleOpened();
}, [options.length, state, toggleOpened]);
return ( return (
<div <div
className={cJoin( className={cJoin(
@ -45,7 +56,7 @@ export const Select = ({
cIf(opened, "rounded-b-none bg-highlight outline-[transparent]") cIf(opened, "rounded-b-none bg-highlight outline-[transparent]")
)} )}
> >
<p onClick={toggleOpened} className="w-full"> <p onClick={tryToggling} className="w-full">
{state === -1 ? "—" : options[state]} {state === -1 ? "—" : options[state]}
</p> </p>
{state >= 0 && allowEmpty && ( {state >= 0 && allowEmpty && (
@ -59,7 +70,7 @@ export const Select = ({
/> />
)} )}
<Ico <Ico
onClick={toggleOpened} onClick={tryToggling}
icon={opened ? Icon.ArrowDropUp : Icon.ArrowDropDown} icon={opened ? Icon.ArrowDropUp : Icon.ArrowDropDown}
/> />
</div> </div>

View File

@ -4,6 +4,8 @@ import { ToolTip } from "components/ToolTip";
import { AppStaticProps } from "graphql/getAppStaticProps"; import { AppStaticProps } from "graphql/getAppStaticProps";
import { getStatusDescription } from "helpers/others"; import { getStatusDescription } from "helpers/others";
import { useSmartLanguage } from "hooks/useSmartLanguage"; import { useSmartLanguage } from "hooks/useSmartLanguage";
import Link from "next/link";
import { Button } from "components/Inputs/Button";
/* /*
* *
@ -11,7 +13,10 @@ import { useSmartLanguage } from "hooks/useSmartLanguage";
*/ */
interface Props { interface Props {
source?: string; source?: {
name?: string;
url?: string;
};
translations: { translations: {
language: string | undefined; language: string | undefined;
definition: string | null | undefined; definition: string | null | undefined;
@ -79,9 +84,16 @@ const DefinitionCard = ({
)} )}
</div> </div>
<p className="italic">{`${langui.source}: ${source}`}</p>
<p>{selectedTranslation?.definition}</p> <p>{selectedTranslation?.definition}</p>
{source?.url && source.name && (
<Link href={source.url}>
<div className="flex place-items-center gap-2 mt-3">
<p>{langui.source}: </p>
<Button size="small" text={source.name} />
</div>
</Link>
)}
</> </>
); );
}; };

View File

@ -0,0 +1,24 @@
fragment source on Source {
name
content {
data {
attributes {
slug
}
}
}
ranged_content {
data {
attributes {
slug
library_item {
data {
attributes {
slug
}
}
}
}
}
}
}

View File

@ -20,6 +20,24 @@ query getWikiPage($slug: String, $language_code: String) {
} }
} }
} }
tags {
data {
id
attributes {
slug
titles(filters: { language: { code: { eq: $language_code } } }) {
language {
data {
attributes {
code
}
}
}
title
}
}
}
}
translations { translations {
title title
aliases { aliases {
@ -73,7 +91,7 @@ query getWikiPage($slug: String, $language_code: String) {
source { source {
data { data {
attributes { attributes {
name ...source
} }
} }
} }

View File

@ -1,4 +1,4 @@
query getWikiPagesPreviews { query getWikiPagesPreviews($language_code: String) {
wikiPages(pagination: { limit: -1 }) { wikiPages(pagination: { limit: -1 }) {
data { data {
id id
@ -20,6 +20,24 @@ query getWikiPagesPreviews {
} }
} }
} }
tags {
data {
id
attributes {
slug
titles(filters: { language: { code: { eq: $language_code } } }) {
language {
data {
attributes {
code
}
}
}
title
}
}
}
}
translations { translations {
title title
aliases { aliases {

View File

@ -23,6 +23,9 @@ import {
} from "helpers/others"; } from "helpers/others";
import { WikiPageWithTranslations } from "helpers/types"; import { WikiPageWithTranslations } from "helpers/types";
import { useSmartLanguage } from "hooks/useSmartLanguage"; import { useSmartLanguage } from "hooks/useSmartLanguage";
import { prettySlug } from "helpers/formatters";
import { useLightBox } from "hooks/useLightBox";
import { getAssetURL, ImageQuality } from "helpers/img";
interface Props extends AppStaticProps { interface Props extends AppStaticProps {
page: WikiPageWithTranslations; page: WikiPageWithTranslations;
@ -45,6 +48,8 @@ const WikiPage = ({
), ),
}); });
const [openLightBox, LightBox] = useLightBox();
const subPanel = useMemo( const subPanel = useMemo(
() => ( () => (
<SubPanel> <SubPanel>
@ -63,6 +68,8 @@ const WikiPage = ({
const contentPanel = useMemo( const contentPanel = useMemo(
() => ( () => (
<ContentPanel width={ContentPanelWidthSizes.Large}> <ContentPanel width={ContentPanelWidthSizes.Large}>
<LightBox />
<ReturnButton <ReturnButton
href={`/wiki`} href={`/wiki`}
title={langui.wiki} title={langui.wiki}
@ -78,7 +85,7 @@ const WikiPage = ({
<p className="mr-3 text-center text-2xl"> <p className="mr-3 text-center text-2xl">
{`(${selectedTranslation.aliases {`(${selectedTranslation.aliases
.map((alias) => alias?.alias) .map((alias) => alias?.alias)
.join(", ")})`} .join("")})`}
</p> </p>
)} )}
<LanguageSwitcher {...languageSwitcherProps} /> <LanguageSwitcher {...languageSwitcherProps} />
@ -93,19 +100,63 @@ const WikiPage = ({
text-center" text-center"
> >
{page.thumbnail?.data?.attributes && ( {page.thumbnail?.data?.attributes && (
<Img image={page.thumbnail.data.attributes} /> <Img
image={page.thumbnail.data.attributes}
quality={ImageQuality.Medium}
className="w-full cursor-pointer"
onClick={() => {
if (page.thumbnail?.data?.attributes?.url) {
openLightBox([
getAssetURL(
page.thumbnail.data.attributes.url,
ImageQuality.Large
),
]);
}
}}
/>
)} )}
<div className="my-4 grid gap-4 p-4"> <div className="my-4 grid gap-4 p-4">
{page.categories?.data && page.categories.data.length > 0 && (
<>
<p className="font-headers text-xl font-bold"> <p className="font-headers text-xl font-bold">
{langui.categories} {langui.categories}
</p> </p>
<div className="flex flex-row flex-wrap place-content-center gap-2"> <div className="flex flex-row flex-wrap place-content-center gap-2">
{filterHasAttributes(page.categories?.data, [ {filterHasAttributes(page.categories.data, [
"attributes", "attributes",
] as const).map((category) => ( ] as const).map((category) => (
<Chip key={category.id} text={category.attributes.name} /> <Chip
key={category.id}
text={category.attributes.name}
/>
))} ))}
</div> </div>
</>
)}
{page.tags?.data && page.tags.data.length > 0 && (
<>
<p className="font-headers text-xl font-bold">
{/* TODO: Add Tags to langui */}
{"Tags"}
</p>
<div className="flex flex-row flex-wrap place-content-center gap-2">
{filterHasAttributes(page.tags?.data, [
"attributes",
] as const).map((tag) => (
<Chip
key={tag.id}
text={
tag.attributes.titles?.[0]?.title ??
prettySlug(tag.attributes.slug)
}
/>
))}
</div>
</>
)}
</div> </div>
</div> </div>
@ -124,7 +175,13 @@ const WikiPage = ({
<> <>
<DefinitionCard <DefinitionCard
key={index} key={index}
source={definition.source?.data?.attributes?.name} source={{
name: definition.source?.data?.attributes?.name,
url: definition.source?.data?.attributes?.content?.data
?.attributes?.slug
? `/contents/${definition.source.data.attributes.content.data.attributes.slug}`
: `/library/${definition.source?.data?.attributes?.ranged_content?.data?.attributes?.library_item?.data?.attributes?.slug}`,
}}
translations={definition.translations.map((translation) => ({ translations={definition.translations.map((translation) => ({
language: translation?.language?.data?.attributes?.code, language: translation?.language?.data?.attributes?.code,
definition: translation?.definition, definition: translation?.definition,
@ -149,9 +206,7 @@ const WikiPage = ({
languageSwitcherProps, languageSwitcherProps,
languages, languages,
langui, langui,
page.categories?.data, page,
page.definitions,
page.thumbnail?.data?.attributes,
selectedTranslation, selectedTranslation,
] ]
); );

View File

@ -1,5 +1,5 @@
import { GetStaticProps } from "next"; import { GetStaticProps } from "next";
import { Fragment, useMemo, useState } from "react"; import { useCallback, useMemo, useState } from "react";
import { AppLayout } from "components/AppLayout"; import { AppLayout } from "components/AppLayout";
import { NavOption } from "components/PanelComponents/NavOption"; import { NavOption } from "components/PanelComponents/NavOption";
import { PanelHeader } from "components/PanelComponents/PanelHeader"; import { PanelHeader } from "components/PanelComponents/PanelHeader";
@ -7,7 +7,7 @@ import { SubPanel } from "components/Panels/SubPanel";
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps"; import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
import { Icon } from "components/Ico"; import { Icon } from "components/Ico";
import { getReadySdk } from "graphql/sdk"; import { getReadySdk } from "graphql/sdk";
import { GetWikiPagesPreviewsQuery } from "graphql/generated"; import { GetWikiPageQuery, GetWikiPagesPreviewsQuery } from "graphql/generated";
import { import {
ContentPanel, ContentPanel,
ContentPanelWidthSizes, ContentPanelWidthSizes,
@ -19,8 +19,12 @@ import { Switch } from "components/Inputs/Switch";
import { TextInput } from "components/Inputs/TextInput"; import { TextInput } from "components/Inputs/TextInput";
import { WithLabel } from "components/Inputs/WithLabel"; import { WithLabel } from "components/Inputs/WithLabel";
import { useMediaHoverable } from "hooks/useMediaQuery"; import { useMediaHoverable } from "hooks/useMediaQuery";
import { filterHasAttributes } from "helpers/others"; import { filterDefined, filterHasAttributes, isDefined } from "helpers/others";
import { ContentPlaceholder } from "components/PanelComponents/ContentPlaceholder"; import { ContentPlaceholder } from "components/PanelComponents/ContentPlaceholder";
import { SmartList } from "components/SmartList";
import { Select } from "components/Inputs/Select";
import { SelectiveNonNullable } from "helpers/types/SelectiveNonNullable";
import { prettySlug } from "helpers/formatters";
/* /*
* *
@ -30,6 +34,7 @@ import { ContentPlaceholder } from "components/PanelComponents/ContentPlaceholde
const DEFAULT_FILTERS_STATE = { const DEFAULT_FILTERS_STATE = {
searchName: "", searchName: "",
keepInfoVisible: true, keepInfoVisible: true,
groupingMethod: -1,
}; };
/* /*
@ -52,13 +57,13 @@ const Wiki = ({
const [searchName, setSearchName] = useState( const [searchName, setSearchName] = useState(
DEFAULT_FILTERS_STATE.searchName DEFAULT_FILTERS_STATE.searchName
); );
const [keepInfoVisible, setKeepInfoVisible] = useState(
DEFAULT_FILTERS_STATE.keepInfoVisible const [groupingMethod, setGroupingMethod] = useState<number>(
DEFAULT_FILTERS_STATE.groupingMethod
); );
const filteredPages = useMemo( const [keepInfoVisible, setKeepInfoVisible] = useState(
() => filterPages(pages, searchName), DEFAULT_FILTERS_STATE.keepInfoVisible
[pages, searchName]
); );
const subPanel = useMemo( const subPanel = useMemo(
@ -77,6 +82,19 @@ const Wiki = ({
setState={setSearchName} setState={setSearchName}
/> />
<WithLabel
label={langui.group_by}
input={
<Select
className="w-full"
options={[langui.category ?? ""]}
state={groupingMethod}
setState={setGroupingMethod}
allowEmpty
/>
}
/>
{hoverable && ( {hoverable && (
<WithLabel <WithLabel
label={langui.always_show_info} label={langui.always_show_info}
@ -104,59 +122,105 @@ const Wiki = ({
<NavOption title={langui.chronology} url="/wiki/chronology" border /> <NavOption title={langui.chronology} url="/wiki/chronology" border />
</SubPanel> </SubPanel>
), ),
[hoverable, keepInfoVisible, langui, searchName] [groupingMethod, hoverable, keepInfoVisible, langui, searchName]
);
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( const contentPanel = useMemo(
() => ( () => (
<ContentPanel width={ContentPanelWidthSizes.Full}> <ContentPanel width={ContentPanelWidthSizes.Full}>
<div <SmartList
className="grid grid-cols-2 items-end gap-8 items={filterHasAttributes(pages, ["id", "attributes"] as const)}
desktop:grid-cols-[repeat(auto-fill,_minmax(20rem,1fr))] mobile:gap-4" getItemId={(item) => item.id}
> renderItem={({ item }) => (
{filteredPages.length === 0 && ( <>
<ContentPlaceholder {isDefined(item.attributes.translations) && (
message={langui.no_results_message ?? "No results"}
icon={Icon.ChevronLeft}
/>
)}
{filterHasAttributes(filteredPages, [
"id",
"attributes.translations",
] as const).map((page) => (
<Fragment key={page.id}>
<TranslatedPreviewCard <TranslatedPreviewCard
href={`/wiki/${page.attributes.slug}`} href={`/wiki/${item.attributes.slug}`}
translations={page.attributes.translations.map( translations={item.attributes.translations.map(
(translation) => ({ (translation) => ({
title: translation?.title, title: translation?.title,
subtitle: subtitle:
translation?.aliases && translation.aliases.length > 0 translation?.aliases && translation.aliases.length > 0
? translation.aliases ? translation.aliases
.map((alias) => alias?.alias) .map((alias) => alias?.alias)
.join(" | ") .join("・")
: undefined, : undefined,
description: translation?.summary, description: translation?.summary,
language: translation?.language?.data?.attributes?.code, language: translation?.language?.data?.attributes?.code,
}) })
)} )}
thumbnail={page.attributes.thumbnail?.data?.attributes} thumbnail={item.attributes.thumbnail?.data?.attributes}
thumbnailAspectRatio={"4/3"} thumbnailAspectRatio={"4/3"}
thumbnailRounded thumbnailRounded
thumbnailForceAspectRatio thumbnailForceAspectRatio
languages={languages} languages={languages}
slug={page.attributes.slug} slug={item.attributes.slug}
keepInfoVisible={keepInfoVisible} keepInfoVisible={keepInfoVisible}
bottomChips={page.attributes.categories?.data.map( topChips={filterHasAttributes(item.attributes.tags?.data, [
(category) => category.attributes?.short ?? "" "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)}
/>
)}
</>
)}
renderWhenEmpty={() => (
<ContentPlaceholder
message={langui.no_results_message ?? "No results"}
icon={Icon.ChevronLeft}
/>
)}
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}
/> />
</Fragment>
))}
</div>
</ContentPanel> </ContentPanel>
), ),
[filteredPages, keepInfoVisible, languages, langui] [groupingFunction, keepInfoVisible, languages, langui, pages, searchName]
); );
return ( return (
@ -180,7 +244,9 @@ export default Wiki;
export const getStaticProps: GetStaticProps = async (context) => { export const getStaticProps: GetStaticProps = async (context) => {
const sdk = getReadySdk(); const sdk = getReadySdk();
const pages = await sdk.getWikiPagesPreviews({}); const pages = await sdk.getWikiPagesPreviews({
language_code: context.locale ?? "en",
});
if (!pages.wikiPages?.data) return { notFound: true }; if (!pages.wikiPages?.data) return { notFound: true };
const props: Props = { const props: Props = {
...(await getAppStaticProps(context)), ...(await getAppStaticProps(context)),
@ -202,20 +268,3 @@ const sortPages = (pages: Props["pages"]): Props["pages"] =>
const slugB = b.attributes?.slug ?? ""; const slugB = b.attributes?.slug ?? "";
return slugA.localeCompare(slugB); return slugA.localeCompare(slugB);
}); });
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
const filterPages = (posts: Props["pages"], searchName: string) =>
posts.filter((post) => {
if (searchName.length > 1) {
if (
post.attributes?.translations?.[0]?.title
.toLowerCase()
.includes(searchName.toLowerCase()) === true
) {
return true;
}
return false;
}
return true;
});