useBoolean + many fixes

This commit is contained in:
DrMint 2022-07-13 03:46:58 +02:00
parent b6c2363093
commit 260bdd5577
36 changed files with 1065 additions and 737 deletions

View File

@ -6,4 +6,5 @@ next-sitemap.config.js
next.config.js
postcss.config.js
tailwind.config.js
design.config.js
design.config.js
graphql.config.js

View File

@ -30,7 +30,6 @@ The following is all the tests done on the data entries coming from Strapi. This
| Text Sets | Credited Translators | Error | High | The Content is a Transcription but credits one or more Translators. | If appropriate, create a Translation Text Set with the Translator credited there. |
| Text Sets | Duplicate Language | Error | High | | |
## LibraryItems
| Subitem | Name | Type | Severity | Description | Recommendation |
@ -100,4 +99,4 @@ The following is all the tests done on the data entries coming from Strapi. This
| Metadata Group | Has URLs | Error | High | Variant Sets shouldn't have URLs. | |
| Metadata Group | Has Contents | Error | High | Variant Sets and Relation Set shouldn't have Contents. | |
| Metadata Group | Has Images | Error | High | Variant Sets and Relation Set shouldn't have Images. | |
| Metadata Group | No Subitems | Missing | High | Group Items should have subitems. |
| Metadata Group | No Subitems | Missing | High | Group Items should have subitems. |

19
graphql.config.js Normal file
View File

@ -0,0 +1,19 @@
module.exports = {
projects: {
app: {
schema: process.env.URL_GRAPHQL,
documents: [
"src/graphql/operations/*.graphql",
"src/graphql/fragments/*.graphql",
],
extensions: {
endpoints: {
default: {
url: process.env.URL_GRAPHQL,
headers: { Authorization: `Bearer ${process.env.ACCESS_TOKEN}` },
},
},
},
},
},
};

View File

@ -97,14 +97,13 @@ export const AppLayout = ({
const isMobile = useMediaMobile();
useEffect(() => {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
router.events?.on("routeChangeStart", () => {
router.events.on("routeChangeStart", () => {
setConfigPanelOpen(false);
setMainPanelOpen(false);
setSubPanelOpen(false);
});
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
router.events?.on("hashChangeStart", () => {
router.events.on("hashChangeStart", () => {
setSubPanelOpen(false);
});
}, [router.events, setConfigPanelOpen, setMainPanelOpen, setSubPanelOpen]);
@ -461,8 +460,8 @@ export const AppLayout = ({
<div>
<Select
options={currencyOptions}
state={currencySelect}
setState={setCurrencySelect}
value={currencySelect}
onChange={setCurrencySelect}
className="w-28"
/>
</div>
@ -516,8 +515,8 @@ export const AppLayout = ({
<TextInput
placeholder="<player>"
className="w-48"
state={playerName}
setState={setPlayerName}
value={playerName ?? ""}
onChange={setPlayerName}
/>
</div>
</div>

View File

@ -64,13 +64,13 @@ export const Button = ({
id={id}
onClick={onClick}
className={cJoin(
`group grid select-none grid-flow-col place-content-center
place-items-center gap-2 rounded-full border-[1px] border-dark py-3 px-4 leading-none
text-dark transition-all`,
`group grid cursor-pointer select-none grid-flow-col
place-content-center place-items-center gap-2 rounded-full border-[1px] border-dark py-3 px-4
leading-none text-dark transition-all`,
cIf(
active,
"!border-black bg-black !text-light drop-shadow-black-lg",
"cursor-pointer hover:bg-dark hover:text-light hover:drop-shadow-shade-lg"
"hover:bg-dark hover:text-light hover:drop-shadow-shade-lg"
),
cIf(size === "small", "px-3 py-1 text-xs"),
className

View File

@ -1,4 +1,3 @@
import { Dispatch, SetStateAction } from "react";
import { ButtonGroup } from "./ButtonGroup";
import { Icon } from "components/Ico";
import { cJoin } from "helpers/className";
@ -10,34 +9,23 @@ import { cJoin } from "helpers/className";
interface Props {
className?: string;
maxPage: number;
page: number;
setPage: Dispatch<SetStateAction<number>>;
onChange: (value: number) => void;
}
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
export const PageSelector = ({
page,
setPage,
maxPage,
className,
onChange,
}: Props): JSX.Element => (
<ButtonGroup
className={cJoin("flex flex-row place-content-center", className)}
buttonsProps={[
{
onClick: () => setPage((current) => (page > 0 ? current - 1 : current)),
icon: Icon.NavigateBefore,
},
{
text: (page + 1).toString(),
},
{
onClick: () =>
setPage((current) => (page < maxPage ? page + 1 : current)),
icon: Icon.NavigateNext,
},
{ onClick: () => onChange(page - 1), icon: Icon.NavigateBefore },
{ text: (page + 1).toString() },
{ onClick: () => onChange(page + 1), icon: Icon.NavigateNext },
]}
/>
);

View File

@ -1,10 +1,4 @@
import {
Dispatch,
Fragment,
SetStateAction,
useCallback,
useState,
} from "react";
import { Fragment, useCallback, useState } from "react";
import { Ico, Icon } from "components/Ico";
import { cIf, cJoin } from "helpers/className";
import { useToggle } from "hooks/useToggle";
@ -15,30 +9,30 @@ import { useToggle } from "hooks/useToggle";
*/
interface Props {
setState: Dispatch<SetStateAction<number>>;
state: number;
value: number;
options: string[];
selected?: number;
allowEmpty?: boolean;
className?: string;
onChange: (value: number) => void;
}
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
export const Select = ({
className,
state,
value,
options,
allowEmpty,
setState,
onChange,
}: Props): JSX.Element => {
const [opened, setOpened] = useState(false);
const toggleOpened = useToggle(setOpened);
const tryToggling = useCallback(() => {
const optionCount = options.length + (state === -1 ? 1 : 0);
const optionCount = options.length + (value === -1 ? 1 : 0);
if (optionCount > 1) toggleOpened();
}, [options.length, state, toggleOpened]);
}, [options.length, value, toggleOpened]);
return (
<div
@ -57,14 +51,14 @@ export const Select = ({
)}
>
<p onClick={tryToggling} className="w-full">
{state === -1 ? "—" : options[state]}
{value === -1 ? "—" : options[value]}
</p>
{state >= 0 && allowEmpty && (
{value >= 0 && allowEmpty && (
<Ico
icon={Icon.Close}
className="!text-xs"
onClick={() => {
setState(-1);
onChange(-1);
setOpened(false);
}}
/>
@ -82,7 +76,7 @@ export const Select = ({
>
{options.map((option, index) => (
<Fragment key={index}>
{index !== state && (
{index !== value && (
<div
className={cJoin(
"cursor-pointer p-1 transition-colors last-of-type:rounded-b-[1em] hover:bg-mid",
@ -91,7 +85,7 @@ export const Select = ({
id={option}
onClick={() => {
setOpened(false);
setState(index);
onChange(index);
}}
>
{option}

View File

@ -1,6 +1,4 @@
import { Dispatch, SetStateAction } from "react";
import { cIf, cJoin } from "helpers/className";
import { useToggle } from "hooks/useToggle";
/*
*
@ -8,8 +6,8 @@ import { useToggle } from "hooks/useToggle";
*/
interface Props {
setState: Dispatch<SetStateAction<boolean>>;
state: boolean;
onClick: () => void;
value: boolean;
className?: string;
disabled?: boolean;
}
@ -17,38 +15,31 @@ interface Props {
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
export const Switch = ({
state,
setState,
value,
onClick,
className,
disabled = false,
}: Props): JSX.Element => {
const toggleState = useToggle(setState);
return (
}: Props): JSX.Element => (
<div
className={cJoin(
"relative grid h-6 w-12 rounded-full border-2 border-mid transition-colors",
cIf(disabled, "cursor-not-allowed", "cursor-pointer"),
cIf(value, "border-none bg-mid shadow-inner-sm shadow-shade", "bg-light"),
className
)}
onClick={() => {
if (!disabled) onClick();
}}
>
<div
className={cJoin(
"relative grid h-6 w-12 rounded-full border-2 border-mid transition-colors",
cIf(disabled, "cursor-not-allowed", "cursor-pointer"),
"absolute aspect-square rounded-full bg-dark transition-transform",
cIf(
state,
"border-none bg-mid shadow-inner-sm shadow-shade",
"bg-light"
),
className
value,
"top-[2px] bottom-[2px] left-[2px] translate-x-[120%]",
"top-0 bottom-0 left-0"
)
)}
onClick={() => {
if (!disabled) toggleState();
}}
>
<div
className={cJoin(
"absolute aspect-square rounded-full bg-dark transition-transform",
cIf(
state,
"top-[2px] bottom-[2px] left-[2px] translate-x-[120%]",
"top-0 bottom-0 left-0"
)
)}
></div>
</div>
);
};
></div>
</div>
);

View File

@ -1,4 +1,3 @@
import { Dispatch, SetStateAction } from "react";
import { Ico, Icon } from "components/Ico";
import { cJoin } from "helpers/className";
import { isDefinedAndNotEmpty } from "helpers/others";
@ -9,10 +8,8 @@ import { isDefinedAndNotEmpty } from "helpers/others";
*/
interface Props {
state: string | undefined;
setState:
| Dispatch<SetStateAction<string | undefined>>
| Dispatch<SetStateAction<string>>;
value: string;
onChange: (newValue: string) => void;
className?: string;
name?: string;
placeholder?: string;
@ -21,8 +18,8 @@ interface Props {
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
export const TextInput = ({
state,
setState,
value,
onChange,
className,
name,
placeholder,
@ -32,19 +29,19 @@ export const TextInput = ({
className="w-full"
type="text"
name={name}
value={state}
value={value}
placeholder={placeholder}
onChange={(event) => {
setState(event.target.value);
onChange(event.target.value);
}}
/>
{isDefinedAndNotEmpty(state) && (
{isDefinedAndNotEmpty(value) && (
<div className="absolute right-4 top-0 bottom-0 grid place-items-center">
<Ico
className="cursor-pointer !text-xs"
icon={Icon.Close}
onClick={() => {
setState("");
onChange("");
}}
/>
</div>

View File

@ -23,7 +23,7 @@ export const PreviewCardCTAs = ({
expand = false,
langui,
}: Props): JSX.Element => {
const appLayout = useAppLayout();
const { libraryItemUserStatus, setLibraryItemUserStatus } = useAppLayout();
return (
<>
<div
@ -35,13 +35,10 @@ export const PreviewCardCTAs = ({
<Button
icon={Icon.Favorite}
text={expand ? langui.want_it : undefined}
active={
appLayout.libraryItemUserStatus?.[id] ===
LibraryItemUserStatus.Want
}
active={libraryItemUserStatus?.[id] === LibraryItemUserStatus.Want}
onClick={(event) => {
event.preventDefault();
appLayout.setLibraryItemUserStatus((current) => {
setLibraryItemUserStatus((current) => {
const newLibraryItemUserStatus = current ? { ...current } : {};
newLibraryItemUserStatus[id] =
newLibraryItemUserStatus[id] === LibraryItemUserStatus.Want
@ -56,13 +53,10 @@ export const PreviewCardCTAs = ({
<Button
icon={Icon.BackHand}
text={expand ? langui.have_it : undefined}
active={
appLayout.libraryItemUserStatus?.[id] ===
LibraryItemUserStatus.Have
}
active={libraryItemUserStatus?.[id] === LibraryItemUserStatus.Have}
onClick={(event) => {
event.preventDefault();
appLayout.setLibraryItemUserStatus((current) => {
setLibraryItemUserStatus((current) => {
const newLibraryItemUserStatus = current ? { ...current } : {};
newLibraryItemUserStatus[id] =
newLibraryItemUserStatus[id] === LibraryItemUserStatus.Have

View File

@ -34,7 +34,7 @@ interface Props {
>["data"][number]["attributes"]
>["scan_set"]
>;
slug: string;
id: string;
title: string;
languages: AppStaticProps["languages"];
langui: AppStaticProps["langui"];
@ -51,10 +51,10 @@ interface Props {
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
export const ScanSet = ({
const ScanSet = ({
openLightBox,
scanSet,
slug,
id,
title,
languages,
langui,
@ -115,17 +115,16 @@ export const ScanSet = ({
className="flex flex-row flex-wrap place-items-center
gap-6 pt-10 text-base first-of-type:pt-0"
>
<h2 id={slug} className="text-2xl">
<h2 id={id} className="text-2xl">
{title}
</h2>
{/* TODO: Add Scan and Scanlation to langui */}
<Chip
text={
selectedScan.language?.data?.attributes?.code ===
selectedScan.source_language?.data?.attributes?.code
? "Scan"
: "Scanlation"
? langui.scan ?? "Scan"
: langui.scanlation ?? "Scanlation"
}
/>
</div>
@ -153,8 +152,7 @@ export const ScanSet = ({
{selectedScan.scanners && selectedScan.scanners.data.length > 0 && (
<div>
{/* TODO: Add Scanner to langui */}
<p className="font-headers font-bold">{"Scanners"}:</p>
<p className="font-headers font-bold">{langui.scanners}:</p>
<div className="grid place-content-center place-items-center gap-2">
{filterHasAttributes(selectedScan.scanners.data, [
"id",
@ -173,8 +171,7 @@ export const ScanSet = ({
{selectedScan.cleaners && selectedScan.cleaners.data.length > 0 && (
<div>
{/* TODO: Add Cleaners to langui */}
<p className="font-headers font-bold">{"Cleaners"}:</p>
<p className="font-headers font-bold">{langui.cleaners}:</p>
<div className="grid place-content-center place-items-center gap-2">
{filterHasAttributes(selectedScan.cleaners.data, [
"id",
@ -194,8 +191,9 @@ export const ScanSet = ({
{selectedScan.typesetters &&
selectedScan.typesetters.data.length > 0 && (
<div>
{/* TODO: Add typesetter to langui */}
<p className="font-headers font-bold">{"Typesetters"}:</p>
<p className="font-headers font-bold">
{langui.typesetters}:
</p>
<div className="grid place-content-center place-items-center gap-2">
{filterHasAttributes(selectedScan.typesetters.data, [
"id",
@ -214,8 +212,7 @@ export const ScanSet = ({
{isDefinedAndNotEmpty(selectedScan.notes) && (
<ToolTip content={selectedScan.notes}>
{/* TODO: Add Notes to langui */}
<Chip text={"Notes"} />
<Chip text={langui.notes ?? "Notes"} />
</ToolTip>
)}
</div>
@ -245,3 +242,40 @@ export const ScanSet = ({
</>
);
};
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
interface TranslatedProps extends Omit<Props, "title"> {
translations: {
title: string;
language: string;
}[];
fallbackTitle: TranslatedProps["translations"][number]["title"];
languages: AppStaticProps["languages"];
}
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
export const TranslatedScanSet = ({
fallbackTitle,
translations = [{ title: fallbackTitle, language: "default" }],
languages,
...otherProps
}: TranslatedProps): JSX.Element => {
const [selectedTranslation] = useSmartLanguage({
items: translations,
languages: languages,
languageExtractor: useCallback(
(item: TranslatedProps["translations"][number]) => item.language,
[]
),
});
return (
<ScanSet
title={selectedTranslation?.title ?? fallbackTitle}
languages={languages}
{...otherProps}
/>
);
};

View File

@ -66,132 +66,126 @@ export const ScanSetCover = ({
return memo;
}, [selectedScan]);
if (coverImages.length > 0) {
return (
<>
{selectedScan && (
<div>
<div
className="flex flex-row flex-wrap place-items-center
return (
<>
{coverImages.length > 0 && selectedScan && (
<div>
<div
className="flex flex-row flex-wrap place-items-center
gap-6 pt-10 text-base first-of-type:pt-0"
>
<h2 id={"cover"} className="text-2xl">
{/* TODO: Add Cover to langui */}
{"Cover"}
</h2>
>
<h2 id={"cover"} className="text-2xl">
{langui.cover}
</h2>
{/* TODO: Add Scan and Scanlation to langui */}
<Chip
text={
selectedScan.language?.data?.attributes?.code ===
selectedScan.source_language?.data?.attributes?.code
? "Scan"
: "Scanlation"
}
/>
<Chip
text={
selectedScan.language?.data?.attributes?.code ===
selectedScan.source_language?.data?.attributes?.code
? langui.scan ?? "Scan"
: langui.scanlation ?? "Scanlation"
}
/>
</div>
<div className="flex flex-row flex-wrap place-items-center gap-4 pb-6">
<LanguageSwitcher {...languageSwitcherProps} />
<div className="grid place-content-center place-items-center">
<p className="font-headers font-bold">{langui.status}:</p>
<ToolTip
content={getStatusDescription(selectedScan.status, langui)}
maxWidth={"20rem"}
>
<Chip text={selectedScan.status} />
</ToolTip>
</div>
<div className="flex flex-row flex-wrap place-items-center gap-4 pb-6">
<LanguageSwitcher {...languageSwitcherProps} />
<div className="grid place-content-center place-items-center">
<p className="font-headers font-bold">{langui.status}:</p>
<ToolTip
content={getStatusDescription(selectedScan.status, langui)}
maxWidth={"20rem"}
>
<Chip text={selectedScan.status} />
</ToolTip>
{selectedScan.scanners && selectedScan.scanners.data.length > 0 && (
<div>
<p className="font-headers font-bold">{langui.scanners}:</p>
<div className="grid place-content-center place-items-center gap-2">
{filterHasAttributes(selectedScan.scanners.data, [
"id",
"attributes",
] as const).map((scanner) => (
<Fragment key={scanner.id}>
<RecorderChip
langui={langui}
recorder={scanner.attributes}
/>
</Fragment>
))}
</div>
</div>
)}
{selectedScan.scanners && selectedScan.scanners.data.length > 0 && (
{selectedScan.cleaners && selectedScan.cleaners.data.length > 0 && (
<div>
<p className="font-headers font-bold">{langui.cleaners}:</p>
<div className="grid place-content-center place-items-center gap-2">
{filterHasAttributes(selectedScan.cleaners.data, [
"id",
"attributes",
] as const).map((cleaner) => (
<Fragment key={cleaner.id}>
<RecorderChip
langui={langui}
recorder={cleaner.attributes}
/>
</Fragment>
))}
</div>
</div>
)}
{selectedScan.typesetters &&
selectedScan.typesetters.data.length > 0 && (
<div>
{/* TODO: Add Scanner to langui */}
<p className="font-headers font-bold">{"Scanners"}:</p>
<p className="font-headers font-bold">
{langui.typesetters}:
</p>
<div className="grid place-content-center place-items-center gap-2">
{filterHasAttributes(selectedScan.scanners.data, [
{filterHasAttributes(selectedScan.typesetters.data, [
"id",
"attributes",
] as const).map((scanner) => (
<Fragment key={scanner.id}>
] as const).map((typesetter) => (
<Fragment key={typesetter.id}>
<RecorderChip
langui={langui}
recorder={scanner.attributes}
recorder={typesetter.attributes}
/>
</Fragment>
))}
</div>
</div>
)}
</div>
{selectedScan.cleaners && selectedScan.cleaners.data.length > 0 && (
<div>
{/* TODO: Add Cleaners to langui */}
<p className="font-headers font-bold">{"Cleaners"}:</p>
<div className="grid place-content-center place-items-center gap-2">
{filterHasAttributes(selectedScan.cleaners.data, [
"id",
"attributes",
] as const).map((cleaner) => (
<Fragment key={cleaner.id}>
<RecorderChip
langui={langui}
recorder={cleaner.attributes}
/>
</Fragment>
))}
</div>
</div>
)}
{selectedScan.typesetters &&
selectedScan.typesetters.data.length > 0 && (
<div>
{/* TODO: Add Cleaners to Typesetters */}
<p className="font-headers font-bold">{"Typesetters"}:</p>
<div className="grid place-content-center place-items-center gap-2">
{filterHasAttributes(selectedScan.typesetters.data, [
"id",
"attributes",
] as const).map((typesetter) => (
<Fragment key={typesetter.id}>
<RecorderChip
langui={langui}
recorder={typesetter.attributes}
/>
</Fragment>
))}
</div>
</div>
)}
</div>
<div
className="grid items-end gap-8 border-b-[3px] border-dotted pb-12
<div
className="grid items-end gap-8 border-b-[3px] border-dotted pb-12
last-of-type:border-0 desktop:grid-cols-[repeat(auto-fill,_minmax(10rem,1fr))]
mobile:grid-cols-2"
>
{coverImages.map((image, index) => (
<div
key={image.url}
className="cursor-pointer transition-transform
>
{coverImages.map((image, index) => (
<div
key={image.url}
className="cursor-pointer transition-transform
drop-shadow-shade-lg hover:scale-[1.02]"
onClick={() => {
const imgs = coverImages.map((img) =>
getAssetURL(img.url, ImageQuality.Large)
);
onClick={() => {
const imgs = coverImages.map((img) =>
getAssetURL(img.url, ImageQuality.Large)
);
openLightBox(imgs, index);
}}
>
<Img image={image} quality={ImageQuality.Small} />
</div>
))}
</div>
openLightBox(imgs, index);
}}
>
<Img image={image} quality={ImageQuality.Small} />
</div>
))}
</div>
)}
</>
);
}
return <></>;
</div>
)}
</>
);
};

View File

@ -12,7 +12,7 @@ import { AppStaticProps } from "graphql/getAppStaticProps";
import { cJoin } from "helpers/className";
import { slugify } from "helpers/formatters";
import { getAssetURL, ImageQuality } from "helpers/img";
import { isDefined, isDefinedAndNotEmpty } from "helpers/others";
import { isDefined, isDefinedAndNotEmpty, isUndefined } from "helpers/others";
import { useLightBox } from "hooks/useLightBox";
/*
@ -31,283 +31,276 @@ export const Markdawn = ({
className,
text: rawText,
}: MarkdawnProps): JSX.Element => {
const appLayout = useAppLayout();
const { playerName } = useAppLayout();
const router = useRouter();
const [openLightBox, LightBox] = useLightBox();
/* eslint-disable no-irregular-whitespace */
const text = useMemo(
() => `${preprocessMarkDawn(rawText)}
() => `${preprocessMarkDawn(rawText, playerName)}
`,
[rawText]
[playerName, rawText]
);
/* eslint-enable no-irregular-whitespace */
if (text) {
return (
<>
<LightBox />
<Markdown
className={cJoin("formatted", className)}
options={{
slugify: slugify,
overrides: {
a: {
component: (compProps: {
href: string;
children: React.ReactNode;
}) => {
if (
compProps.href.startsWith("/") ||
compProps.href.startsWith("#")
) {
return (
<a onClick={async () => router.push(compProps.href)}>
{compProps.children}
</a>
);
}
if (isUndefined(text) || text === "") {
return <></>;
}
return (
<>
<LightBox />
<Markdown
className={cJoin("formatted", className)}
options={{
slugify: slugify,
overrides: {
a: {
component: (compProps: {
href: string;
children: React.ReactNode;
}) => {
if (
compProps.href.startsWith("/") ||
compProps.href.startsWith("#")
) {
return (
<a href={compProps.href} target="_blank" rel="noreferrer">
<a onClick={async () => router.push(compProps.href)}>
{compProps.children}
</a>
);
},
},
h1: {
component: (compProps: {
id: string;
style: React.CSSProperties;
children: React.ReactNode;
}) => (
<h1 id={compProps.id} style={compProps.style}>
}
return (
<a href={compProps.href} target="_blank" rel="noreferrer">
{compProps.children}
<HeaderToolTip id={compProps.id} />
</h1>
),
</a>
);
},
},
h2: {
component: (compProps: {
id: string;
style: React.CSSProperties;
children: React.ReactNode;
}) => (
<h2 id={compProps.id} style={compProps.style}>
{compProps.children}
<HeaderToolTip id={compProps.id} />
</h2>
),
},
h1: {
component: (compProps: {
id: string;
style: React.CSSProperties;
children: React.ReactNode;
}) => (
<h1 id={compProps.id} style={compProps.style}>
{compProps.children}
<HeaderToolTip id={compProps.id} />
</h1>
),
},
h3: {
component: (compProps: {
id: string;
style: React.CSSProperties;
children: React.ReactNode;
}) => (
<h3 id={compProps.id} style={compProps.style}>
{compProps.children}
<HeaderToolTip id={compProps.id} />
</h3>
),
},
h2: {
component: (compProps: {
id: string;
style: React.CSSProperties;
children: React.ReactNode;
}) => (
<h2 id={compProps.id} style={compProps.style}>
{compProps.children}
<HeaderToolTip id={compProps.id} />
</h2>
),
},
h4: {
component: (compProps: {
id: string;
style: React.CSSProperties;
children: React.ReactNode;
}) => (
<h4 id={compProps.id} style={compProps.style}>
{compProps.children}
<HeaderToolTip id={compProps.id} />
</h4>
),
},
h3: {
component: (compProps: {
id: string;
style: React.CSSProperties;
children: React.ReactNode;
}) => (
<h3 id={compProps.id} style={compProps.style}>
{compProps.children}
<HeaderToolTip id={compProps.id} />
</h3>
),
},
h5: {
component: (compProps: {
id: string;
style: React.CSSProperties;
children: React.ReactNode;
}) => (
<h5 id={compProps.id} style={compProps.style}>
{compProps.children}
<HeaderToolTip id={compProps.id} />
</h5>
),
},
h4: {
component: (compProps: {
id: string;
style: React.CSSProperties;
children: React.ReactNode;
}) => (
<h4 id={compProps.id} style={compProps.style}>
{compProps.children}
<HeaderToolTip id={compProps.id} />
</h4>
),
},
h6: {
component: (compProps: {
id: string;
style: React.CSSProperties;
children: React.ReactNode;
}) => (
<h6 id={compProps.id} style={compProps.style}>
{compProps.children}
<HeaderToolTip id={compProps.id} />
</h6>
),
},
h5: {
component: (compProps: {
id: string;
style: React.CSSProperties;
children: React.ReactNode;
}) => (
<h5 id={compProps.id} style={compProps.style}>
{compProps.children}
<HeaderToolTip id={compProps.id} />
</h5>
),
},
SceneBreak: {
component: (compProps: { id: string }) => (
<div
id={compProps.id}
className={"mt-16 mb-20 h-0 text-center text-3xl text-dark"}
>
* * *
</div>
),
},
h6: {
component: (compProps: {
id: string;
style: React.CSSProperties;
children: React.ReactNode;
}) => (
<h6 id={compProps.id} style={compProps.style}>
{compProps.children}
<HeaderToolTip id={compProps.id} />
</h6>
),
},
IntraLink: {
component: (compProps: {
children: React.ReactNode;
target?: string;
page?: string;
}) => {
const slug = isDefinedAndNotEmpty(compProps.target)
? slugify(compProps.target)
: slugify(compProps.children?.toString());
return (
<a
onClick={async () =>
router.replace(`${compProps.page ?? ""}#${slug}`)
}
>
{compProps.children}
</a>
);
},
},
SceneBreak: {
component: (compProps: { id: string }) => (
<div
id={compProps.id}
className={"mt-16 mb-20 h-0 text-center text-3xl text-dark"}
>
* * *
</div>
),
},
player: {
component: () => (
<span className="text-dark opacity-70">
{appLayout.playerName ?? "<player>"}
</span>
),
},
Transcript: {
component: (compProps) => (
<div className="grid grid-cols-[auto_1fr] gap-x-6 gap-y-2 mobile:grid-cols-1">
{compProps.children}
</div>
),
},
Line: {
component: (compProps) => (
<>
<strong className="text-dark opacity-60 mobile:!-mb-4">
{compProps.name}
</strong>
<p className="whitespace-pre-line">{compProps.children}</p>
</>
),
},
InsetBox: {
component: (compProps) => (
<InsetBox className="my-12">{compProps.children}</InsetBox>
),
},
li: {
component: (compProps: { children: React.ReactNode }) => (
<li
className={
isDefined(compProps.children) &&
ReactDOMServer.renderToStaticMarkup(
<>{compProps.children}</>
).length > 100
? "my-4"
: ""
IntraLink: {
component: (compProps: {
children: React.ReactNode;
target?: string;
page?: string;
}) => {
const slug = isDefinedAndNotEmpty(compProps.target)
? slugify(compProps.target)
: slugify(compProps.children?.toString());
return (
<a
onClick={async () =>
router.replace(`${compProps.page ?? ""}#${slug}`)
}
>
{compProps.children}
</li>
),
},
Highlight: {
component: (compProps: { children: React.ReactNode }) => (
<mark>{compProps.children}</mark>
),
},
footer: {
component: (compProps: { children: React.ReactNode }) => (
<>
<HorizontalLine />
<div className="grid gap-8">{compProps.children}</div>
</>
),
},
blockquote: {
component: (compProps: {
children: React.ReactNode;
cite?: string;
}) => (
<blockquote>
{isDefinedAndNotEmpty(compProps.cite) ? (
<>
&ldquo;{compProps.children}&rdquo;
<cite> {compProps.cite}</cite>
</>
) : (
compProps.children
)}
</blockquote>
),
},
img: {
component: (compProps: {
alt: string;
src: string;
width?: number;
height?: number;
caption?: string;
name?: string;
}) => (
<div
className="mt-8 mb-12 grid cursor-pointer place-content-center"
onClick={() => {
openLightBox([
compProps.src.startsWith("/uploads/")
? getAssetURL(compProps.src, ImageQuality.Large)
: compProps.src,
]);
}}
>
<Img
image={
compProps.src.startsWith("/uploads/")
? getAssetURL(compProps.src, ImageQuality.Small)
: compProps.src
}
quality={ImageQuality.Medium}
className="drop-shadow-shade-lg"
></Img>
</div>
),
</a>
);
},
},
}}
>
{text}
</Markdown>
</>
);
}
return <></>;
Transcript: {
component: (compProps) => (
<div className="grid grid-cols-[auto_1fr] gap-x-6 gap-y-2 mobile:grid-cols-1">
{compProps.children}
</div>
),
},
Line: {
component: (compProps) => (
<>
<strong className="text-dark opacity-60 mobile:!-mb-4">
{compProps.name}
</strong>
<p className="whitespace-pre-line">{compProps.children}</p>
</>
),
},
InsetBox: {
component: (compProps) => (
<InsetBox className="my-12">{compProps.children}</InsetBox>
),
},
li: {
component: (compProps: { children: React.ReactNode }) => (
<li
className={
isDefined(compProps.children) &&
ReactDOMServer.renderToStaticMarkup(
<>{compProps.children}</>
).length > 100
? "my-4"
: ""
}
>
{compProps.children}
</li>
),
},
Highlight: {
component: (compProps: { children: React.ReactNode }) => (
<mark>{compProps.children}</mark>
),
},
footer: {
component: (compProps: { children: React.ReactNode }) => (
<>
<HorizontalLine />
<div className="grid gap-8">{compProps.children}</div>
</>
),
},
blockquote: {
component: (compProps: {
children: React.ReactNode;
cite?: string;
}) => (
<blockquote>
{isDefinedAndNotEmpty(compProps.cite) ? (
<>
&ldquo;{compProps.children}&rdquo;
<cite> {compProps.cite}</cite>
</>
) : (
compProps.children
)}
</blockquote>
),
},
img: {
component: (compProps: {
alt: string;
src: string;
width?: number;
height?: number;
caption?: string;
name?: string;
}) => (
<div
className="mt-8 mb-12 grid cursor-pointer place-content-center"
onClick={() => {
openLightBox([
compProps.src.startsWith("/uploads/")
? getAssetURL(compProps.src, ImageQuality.Large)
: compProps.src,
]);
}}
>
<Img
image={
compProps.src.startsWith("/uploads/")
? getAssetURL(compProps.src, ImageQuality.Small)
: compProps.src
}
quality={ImageQuality.Medium}
className="drop-shadow-shade-lg"
></Img>
</div>
),
},
},
}}
>
{text}
</Markdown>
</>
);
};
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
@ -422,21 +415,6 @@ const HeaderToolTip = (props: { id: string }): JSX.Element => (
* PRIVATE COMPONENTS
*/
const typographicRules = (text: string): string => {
let newText = text;
newText = newText.replace(/--/gu, "");
/*
* newText = newText.replace(/\.\.\./gu, "");
* newText = newText.replace(/(?:^|[\s{[(<'"\u2018\u201C])(")/gu, " ");
* newText = newText.replace(/"/gu, "");
* newText = newText.replace(/(?:^|[\s{[(<'"\u2018\u201C])(')/gu, " ");
* newText = newText.replace(/'/gu, "");
*/
return newText;
};
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
enum HeaderLevels {
H1 = 1,
H2 = 2,
@ -446,10 +424,17 @@ enum HeaderLevels {
H6 = 6,
}
const preprocessMarkDawn = (text: string): string => {
const preprocessMarkDawn = (text: string, playerName = ""): string => {
if (!text) return "";
let preprocessed = typographicRules(text);
let preprocessed = text
.replaceAll("--", "—")
.replaceAll(
"@player",
isDefinedAndNotEmpty(playerName) ? playerName : "(player)"
);
console.log();
let scenebreakIndex = 0;
const visitedSlugs: string[] = [];

View File

@ -1,9 +1,11 @@
import { useRouter } from "next/router";
import { MouseEventHandler, useMemo } from "react";
import { MouseEventHandler, useCallback, useMemo } from "react";
import { Ico, Icon } from "components/Ico";
import { ToolTip } from "components/ToolTip";
import { cJoin, cIf } from "helpers/className";
import { isDefinedAndNotEmpty } from "helpers/others";
import { AppStaticProps } from "graphql/getAppStaticProps";
import { useSmartLanguage } from "hooks/useSmartLanguage";
/*
*
@ -88,3 +90,45 @@ export const NavOption = ({
</ToolTip>
);
};
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
interface TranslatedProps extends Omit<Props, "subtitle" | "title"> {
translations: {
title: string | null | undefined;
subtitle?: string | null | undefined;
language: string;
}[];
fallbackTitle: TranslatedProps["translations"][number]["title"];
fallbackSubtitle: TranslatedProps["translations"][number]["subtitle"];
languages: AppStaticProps["languages"];
}
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
export const TranslatedNavOption = ({
fallbackTitle,
fallbackSubtitle,
translations = [
{ title: fallbackTitle, subtitle: fallbackSubtitle, language: "default" },
],
languages,
...otherProps
}: TranslatedProps): JSX.Element => {
const [selectedTranslation] = useSmartLanguage({
items: translations,
languages: languages,
languageExtractor: useCallback(
(item: TranslatedProps["translations"][number]) => item.language,
[]
),
});
return (
<NavOption
title={selectedTranslation?.title ?? fallbackTitle}
subtitle={selectedTranslation?.subtitle ?? fallbackSubtitle}
{...otherProps}
/>
);
};

View File

@ -35,7 +35,7 @@ export const ReturnButton = ({
horizontalLine,
className,
}: Props): JSX.Element => {
const appLayout = useAppLayout();
const { setSubPanelOpen } = useAppLayout();
return (
<div
@ -49,7 +49,7 @@ export const ReturnButton = ({
)}
>
<Button
onClick={() => appLayout.setSubPanelOpen(false)}
onClick={() => setSubPanelOpen(false)}
href={href}
text={`${langui.return_to} ${title}`}
icon={Icon.NavigateBefore}

View File

@ -171,7 +171,11 @@ export const PostPage = ({
description={excerpt}
langui={langui}
categories={post.categories}
languageSwitcher={<LanguageSwitcher {...languageSwitcherProps} />}
languageSwitcher={
languageSwitcherProps.locales.size > 1 ? (
<LanguageSwitcher {...languageSwitcherProps} />
) : undefined
}
/>
<HorizontalLine />

View File

@ -77,64 +77,61 @@ export const PreviewCard = ({
hoverlay,
infoAppend,
}: Props): JSX.Element => {
const appLayout = useAppLayout();
const { currency } = useAppLayout();
const metadataJSX = useMemo(
() =>
metadata && (metadata.release_date || metadata.price) ? (
<div className="flex w-full flex-row flex-wrap gap-x-3">
{metadata.release_date && (
<p className="text-sm mobile:text-xs">
<Ico
icon={Icon.Event}
className="mr-1 translate-y-[.15em] !text-base"
/>
{prettyDate(metadata.release_date)}
</p>
)}
{metadata.price && metadata.currencies && (
<p className="justify-self-end text-sm mobile:text-xs">
<Ico
icon={Icon.ShoppingCart}
className="mr-1 translate-y-[.15em] !text-base"
/>
{prettyPrice(
metadata.price,
metadata.currencies,
appLayout.currency
)}
</p>
)}
{metadata.views && (
<p className="text-sm mobile:text-xs">
<Ico
icon={Icon.Visibility}
className="mr-1 translate-y-[.15em] !text-base"
/>
{prettyShortenNumber(metadata.views)}
</p>
)}
{metadata.author && (
<p className="text-sm mobile:text-xs">
<Ico
icon={Icon.Person}
className="mr-1 translate-y-[.15em] !text-base"
/>
{metadata.author}
</p>
)}
</div>
) : (
<></>
),
[appLayout.currency, metadata]
() => (
<>
{metadata && (metadata.release_date || metadata.price) && (
<div className="flex w-full flex-row flex-wrap gap-x-3">
{metadata.release_date && (
<p className="text-sm mobile:text-xs">
<Ico
icon={Icon.Event}
className="mr-1 translate-y-[.15em] !text-base"
/>
{prettyDate(metadata.release_date)}
</p>
)}
{metadata.price && metadata.currencies && (
<p className="justify-self-end text-sm mobile:text-xs">
<Ico
icon={Icon.ShoppingCart}
className="mr-1 translate-y-[.15em] !text-base"
/>
{prettyPrice(metadata.price, metadata.currencies, currency)}
</p>
)}
{metadata.views && (
<p className="text-sm mobile:text-xs">
<Ico
icon={Icon.Visibility}
className="mr-1 translate-y-[.15em] !text-base"
/>
{prettyShortenNumber(metadata.views)}
</p>
)}
{metadata.author && (
<p className="text-sm mobile:text-xs">
<Ico
icon={Icon.Person}
className="mr-1 translate-y-[.15em] !text-base"
/>
{metadata.author}
</p>
)}
</div>
)}
</>
),
[currency, metadata]
);
return (
<Link href={href} passHref>
<div
className="group grid cursor-pointer items-end transition-transform drop-shadow-shade-xl
hover:scale-[1.02]"
className="group grid cursor-pointer items-end text-left transition-transform
drop-shadow-shade-xl hover:scale-[1.02]"
>
{stackNumber > 0 && (
<>

View File

@ -53,6 +53,7 @@ export const SmartList = <T,>({
langui,
}: Props<T>): JSX.Element => {
const [page, setPage] = useState(0);
useScrollTopOnChange(AnchorIds.ContentPanel, [page], paginationScroolTop);
type Group = Map<string, T[]>;
@ -123,62 +124,68 @@ export const SmartList = <T,>({
[paginationItemPerPage, filteredItems.length]
);
const changePage = useCallback(
(newPage: number) =>
setPage(() => {
if (newPage <= 0) {
return 0;
}
if (newPage >= pageCount) {
return pageCount;
}
return newPage;
}),
[pageCount]
);
return (
<>
{pageCount > 1 && paginationSelectorTop && (
<PageSelector
maxPage={pageCount}
page={page}
setPage={setPage}
className="mb-12"
/>
<PageSelector className="mb-12" page={page} onChange={changePage} />
)}
{groupedList.size > 0
? iterateMap(
groupedList,
(name, groupItems) =>
groupItems.length > 0 && (
<Fragment key={name}>
{name.length > 0 && (
<h2
className="flex flex-row place-items-center gap-2 pb-2 pt-10 text-2xl
<div className="mb-8">
{groupedList.size > 0
? iterateMap(
groupedList,
(name, groupItems) =>
groupItems.length > 0 && (
<Fragment key={name}>
{name.length > 0 && (
<h2
className="flex flex-row place-items-center gap-2 pb-2 pt-10 text-2xl
first-of-type:pt-0"
>
{name}
<Chip
text={`${groupItems.length} ${
groupItems.length <= 1
? langui.result?.toLowerCase() ?? ""
: langui.results?.toLowerCase() ?? ""
}`}
/>
</h2>
)}
<div
className={cJoin(
`grid items-start gap-8 border-b-[3px] border-dotted pb-12
last-of-type:border-0 mobile:gap-4`,
className
>
{name}
<Chip
text={`${groupItems.length} ${
groupItems.length <= 1
? langui.result?.toLowerCase() ?? ""
: langui.results?.toLowerCase() ?? ""
}`}
/>
</h2>
)}
>
{groupItems.map((item) => (
<RenderItem item={item} key={getItemId(item)} />
))}
</div>
</Fragment>
),
([a], [b]) => groupSortingFunction(a, b)
)
: isDefined(RenderWhenEmpty) && <RenderWhenEmpty />}
<div
className={cJoin(
`grid items-start gap-8 border-b-[3px] border-dotted pb-12
last-of-type:border-0 mobile:gap-4`,
className
)}
>
{groupItems.map((item) => (
<RenderItem item={item} key={getItemId(item)} />
))}
</div>
</Fragment>
),
([a], [b]) => groupSortingFunction(a, b)
)
: isDefined(RenderWhenEmpty) && <RenderWhenEmpty />}
</div>
{pageCount > 1 && paginationSelectorBottom && (
<PageSelector
maxPage={pageCount}
page={page}
setPage={setPage}
className="mt-12"
/>
<PageSelector className="mb-12" page={page} onChange={changePage} />
)}
</>
);

View File

@ -11,6 +11,7 @@ import {
filterDefined,
filterHasAttributes,
getStatusDescription,
isDefined,
} from "helpers/others";
/*
@ -30,9 +31,9 @@ export const ChronologyItemComponent = ({
langui,
item,
displayYear,
}: Props): JSX.Element => {
if (item.attributes) {
return (
}: Props): JSX.Element => (
<>
{isDefined(item.attributes) && (
<div
className="grid grid-cols-[4em] grid-rows-[auto_1fr] place-content-start
rounded-2xl py-4 px-8 target:my-4 target:bg-mid target:py-8"
@ -100,7 +101,7 @@ export const ChronologyItemComponent = ({
</p>
)}
{translation.note ? (
<em>{`Notes: ${translation.note}`}</em>
<em>{`${langui.notes}: ${translation.note}`}</em>
) : (
""
)}
@ -115,7 +116,7 @@ export const ChronologyItemComponent = ({
) : (
<div className="flex items-center gap-1">
<Ico icon={Icon.Warning} className="!text-sm" />
No sources!
{langui.no_source_warning}
</div>
)}
</p>
@ -124,11 +125,9 @@ export const ChronologyItemComponent = ({
))}
</div>
</div>
);
}
return <></>;
};
)}
</>
);
/*
*

View File

@ -1,10 +1,10 @@
import { useCallback } from "react";
import Link from "next/link";
import { Chip } from "components/Chip";
import { ToolTip } from "components/ToolTip";
import { AppStaticProps } from "graphql/getAppStaticProps";
import { getStatusDescription } from "helpers/others";
import { useSmartLanguage } from "hooks/useSmartLanguage";
import Link from "next/link";
import { Button } from "components/Inputs/Button";
/*
@ -88,7 +88,7 @@ const DefinitionCard = ({
{source?.url && source.name && (
<Link href={source.url}>
<div className="flex place-items-center gap-2 mt-3">
<div className="mt-3 flex place-items-center gap-2">
<p>{langui.source}: </p>
<Button size="small" text={source.name} />
</div>

View File

@ -102,6 +102,102 @@ query getLibraryItemScans($slug: String, $language_code: String) {
}
}
}
release_date {
...datePicker
}
price {
...pricePicker
}
categories {
data {
id
attributes {
name
short
}
}
}
metadata {
__typename
... on ComponentMetadataBooks {
subtype {
data {
attributes {
slug
titles(
filters: { language: { code: { eq: $language_code } } }
) {
title
}
}
}
}
}
... on ComponentMetadataGame {
platforms {
data {
id
attributes {
short
}
}
}
}
... on ComponentMetadataVideo {
subtype {
data {
attributes {
slug
titles(
filters: { language: { code: { eq: $language_code } } }
) {
title
}
}
}
}
}
... on ComponentMetadataAudio {
subtype {
data {
attributes {
slug
titles(
filters: { language: { code: { eq: $language_code } } }
) {
title
}
}
}
}
}
... on ComponentMetadataGroup {
subtype {
data {
attributes {
slug
titles(
filters: { language: { code: { eq: $language_code } } }
) {
title
}
}
}
}
subitems_type {
data {
attributes {
slug
titles(
filters: { language: { code: { eq: $language_code } } }
) {
title
}
}
}
}
}
}
contents(pagination: { limit: -1 }) {
data {
id
@ -122,6 +218,18 @@ query getLibraryItemScans($slug: String, $language_code: String) {
data {
attributes {
slug
translations {
pre_title
title
subtitle
language {
data {
attributes {
code
}
}
}
}
}
}
}

View File

@ -159,6 +159,15 @@ query getWebsiteInterface($language_code: String) {
no_results_message
all
special_pages
scan
scanlation
scanners
cleaners
typesetters
notes
cover
tags
no_source_warning
}
}
}

20
src/hooks/useBoolean.ts Normal file
View File

@ -0,0 +1,20 @@
import { Dispatch, SetStateAction, useCallback, useState } from "react";
export const useBoolean = (
initialState: boolean
): {
state: boolean;
toggleState: () => void;
setTrue: () => void;
setFalse: () => void;
setState: Dispatch<SetStateAction<boolean>>;
} => {
const [state, setState] = useState(initialState);
const toggleState = useCallback(
() => setState((currentState) => !currentState),
[]
);
const setTrue = useCallback(() => setState(true), []);
const setFalse = useCallback(() => setState(false), []);
return { state, toggleState, setTrue, setFalse, setState };
};

View File

@ -15,5 +15,6 @@ export const useScrollTopOnChange = (
document
.querySelector(`#${id}`)
?.scrollTo({ top: 0, behavior: "smooth" });
}, [id, deps, enabled]);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [id, ...deps, enabled]);
};

View File

@ -1,5 +1,6 @@
import Document, {
DocumentContext,
DocumentInitialProps,
Head,
Html,
Main,
@ -7,8 +8,9 @@ import Document, {
} from "next/document";
export default class MyDocument extends Document {
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
static async getInitialProps(ctx: DocumentContext) {
static async getInitialProps(
ctx: DocumentContext
): Promise<DocumentInitialProps> {
const initialProps = await Document.getInitialProps(ctx);
return { ...initialProps };
}

View File

@ -1,5 +1,5 @@
import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next";
import { Fragment, useState, useMemo } from "react";
import { Fragment, useMemo } from "react";
import { AppLayout } from "components/AppLayout";
import { Switch } from "components/Inputs/Switch";
import { PanelHeader } from "components/PanelComponents/PanelHeader";
@ -21,6 +21,7 @@ import { Icon } from "components/Ico";
import { useMediaHoverable } from "hooks/useMediaQuery";
import { WithLabel } from "components/Inputs/WithLabel";
import { filterHasAttributes, isDefined } from "helpers/others";
import { useBoolean } from "hooks/useBoolean";
/*
*
@ -34,7 +35,8 @@ interface Props extends AppStaticProps {
}
const Channel = ({ langui, channel, ...otherProps }: Props): JSX.Element => {
const [keepInfoVisible, setKeepInfoVisible] = useState(true);
const { state: keepInfoVisible, toggleState: toggleKeepInfoVisible } =
useBoolean(true);
const hoverable = useMediaHoverable();
const subPanel = useMemo(
@ -58,13 +60,13 @@ const Channel = ({ langui, channel, ...otherProps }: Props): JSX.Element => {
<WithLabel
label={langui.always_show_info}
input={
<Switch setState={setKeepInfoVisible} state={keepInfoVisible} />
<Switch value={keepInfoVisible} onClick={toggleKeepInfoVisible} />
}
/>
)}
</SubPanel>
),
[hoverable, keepInfoVisible, langui]
[hoverable, keepInfoVisible, langui, toggleKeepInfoVisible]
);
const contentPanel = useMemo(

View File

@ -25,6 +25,7 @@ import { prettyDate } from "helpers/formatters";
import { filterHasAttributes } from "helpers/others";
import { getVideoThumbnailURL } from "helpers/videos";
import { useMediaHoverable } from "hooks/useMediaQuery";
import { useBoolean } from "hooks/useBoolean";
/*
*
@ -46,7 +47,10 @@ interface Props extends AppStaticProps {
const Videos = ({ langui, videos, ...otherProps }: Props): JSX.Element => {
const hoverable = useMediaHoverable();
const [keepInfoVisible, setKeepInfoVisible] = useState(true);
const { state: keepInfoVisible, toggleState: toggleKeepInfoVisible } =
useBoolean(true);
const [searchName, setSearchName] = useState(
DEFAULT_FILTERS_STATE.searchName
);
@ -71,21 +75,21 @@ const Videos = ({ langui, videos, ...otherProps }: Props): JSX.Element => {
<TextInput
className="mb-6 w-full"
placeholder={langui.search_title ?? undefined}
state={searchName}
setState={setSearchName}
value={searchName}
onChange={setSearchName}
/>
{hoverable && (
<WithLabel
label={langui.always_show_info}
input={
<Switch setState={setKeepInfoVisible} state={keepInfoVisible} />
<Switch value={keepInfoVisible} onClick={toggleKeepInfoVisible} />
}
/>
)}
</SubPanel>
),
[hoverable, keepInfoVisible, langui, searchName]
[hoverable, keepInfoVisible, langui, searchName, toggleKeepInfoVisible]
);
const contentPanel = useMemo(

View File

@ -37,7 +37,7 @@ interface Props extends AppStaticProps {
const Video = ({ langui, video, ...otherProps }: Props): JSX.Element => {
const isMobile = useMediaMobile();
const appLayout = useAppLayout();
const { setSubPanelOpen } = useAppLayout();
const subPanel = useMemo(
() => (
<SubPanel>
@ -55,25 +55,25 @@ const Video = ({ langui, video, ...otherProps }: Props): JSX.Element => {
title={langui.video}
url="#video"
border
onClick={() => appLayout.setSubPanelOpen(false)}
onClick={() => setSubPanelOpen(false)}
/>
<NavOption
title={langui.channel}
url="#channel"
border
onClick={() => appLayout.setSubPanelOpen(false)}
onClick={() => setSubPanelOpen(false)}
/>
<NavOption
title={langui.description}
url="#description"
border
onClick={() => appLayout.setSubPanelOpen(false)}
onClick={() => setSubPanelOpen(false)}
/>
</SubPanel>
),
[appLayout, langui]
[setSubPanelOpen, langui]
);
const contentPanel = useMemo(

View File

@ -206,7 +206,7 @@ const Content = ({
{isDefinedAndNotEmpty(selectedTranslation.text_set.notes) && (
<div>
<p className="font-headers font-bold">{"Notes"}:</p>
<p className="font-headers font-bold">{langui.notes}:</p>
<div className="grid place-content-center place-items-center gap-2">
<Markdawn text={selectedTranslation.text_set.notes} />
</div>
@ -223,7 +223,7 @@ const Content = ({
<p className="font-headers text-2xl font-bold">
{langui.source}
</p>
<div className="mt-6 grid place-items-center gap-6 text-left">
<div className="mt-6 grid place-items-center gap-6">
{filterHasAttributes(content.ranged_contents.data, [
"attributes.library_item.data.attributes",
"attributes.library_item.data.id",
@ -330,7 +330,11 @@ const Content = ({
type={content.type}
categories={content.categories}
langui={langui}
languageSwitcher={<LanguageSwitcher {...languageSwitcherProps} />}
languageSwitcher={
languageSwitcherProps.locales.size > 1 ? (
<LanguageSwitcher {...languageSwitcherProps} />
) : undefined
}
/>
{previousContent?.attributes && (

View File

@ -23,6 +23,7 @@ import { GetContentsQuery } from "graphql/generated";
import { SmartList } from "components/SmartList";
import { ContentPlaceholder } from "components/PanelComponents/ContentPlaceholder";
import { SelectiveNonNullable } from "helpers/types/SelectiveNonNullable";
import { useBoolean } from "hooks/useBoolean";
/*
*
@ -56,9 +57,11 @@ const Contents = ({
const [groupingMethod, setGroupingMethod] = useState<number>(
DEFAULT_FILTERS_STATE.groupingMethod
);
const [keepInfoVisible, setKeepInfoVisible] = useState(
DEFAULT_FILTERS_STATE.keepInfoVisible
);
const {
state: keepInfoVisible,
toggleState: toggleKeepInfoVisible,
setState: setKeepInfoVisible,
} = useBoolean(DEFAULT_FILTERS_STATE.keepInfoVisible);
const [combineRelatedContent, setCombineRelatedContent] = useState(
DEFAULT_FILTERS_STATE.combineRelatedContent
);
@ -149,8 +152,8 @@ const Contents = ({
<TextInput
className="mb-6 w-full"
placeholder={langui.search_title ?? undefined}
state={searchName}
setState={setSearchName}
value={searchName}
onChange={setSearchName}
/>
<WithLabel
@ -159,8 +162,8 @@ const Contents = ({
<Select
className="w-full"
options={[langui.category ?? "", langui.type ?? ""]}
state={groupingMethod}
setState={setGroupingMethod}
value={groupingMethod}
onChange={setGroupingMethod}
allowEmpty
/>
}
@ -171,8 +174,8 @@ const Contents = ({
disabled={searchName.length > 1}
input={
<Switch
setState={setCombineRelatedContent}
state={effectiveCombineRelatedContent}
value={effectiveCombineRelatedContent}
onClick={toggleKeepInfoVisible}
/>
}
/>
@ -181,7 +184,7 @@ const Contents = ({
<WithLabel
label={langui.always_show_info}
input={
<Switch setState={setKeepInfoVisible} state={keepInfoVisible} />
<Switch onClick={toggleKeepInfoVisible} value={keepInfoVisible} />
}
/>
)}
@ -208,6 +211,8 @@ const Contents = ({
keepInfoVisible,
langui,
searchName,
setKeepInfoVisible,
toggleKeepInfoVisible,
]
);

View File

@ -54,6 +54,7 @@ import { Ico, Icon } from "components/Ico";
import { cJoin, cIf } from "helpers/className";
import { useSmartLanguage } from "hooks/useSmartLanguage";
import { getDescription } from "helpers/description";
import { useBoolean } from "hooks/useBoolean";
/*
*
@ -79,10 +80,11 @@ const LibrarySlug = ({
languages,
...otherProps
}: Props): JSX.Element => {
const appLayout = useAppLayout();
const { currency } = useAppLayout();
const hoverable = useMediaHoverable();
const [openLightBox, LightBox] = useLightBox();
const [keepInfoVisible, setKeepInfoVisible] = useState(false);
const { state: keepInfoVisible, toggleState: toggleKeepInfoVisible } =
useBoolean(false);
useScrollTopOnChange(AnchorIds.ContentPanel, [item]);
@ -314,14 +316,10 @@ const LibrarySlug = ({
)}
</p>
{item.price.currency?.data?.attributes?.code !==
appLayout.currency && (
currency && (
<p>
{prettyPrice(
item.price,
currencies,
appLayout.currency
)}{" "}
<br />({langui.calculated?.toLowerCase()})
{prettyPrice(item.price, currencies, currency)} <br />(
{langui.calculated?.toLowerCase()})
</p>
)}
</div>
@ -450,8 +448,8 @@ const LibrarySlug = ({
label={langui.always_show_info}
input={
<Switch
setState={setKeepInfoVisible}
state={keepInfoVisible}
onClick={toggleKeepInfoVisible}
value={keepInfoVisible}
/>
}
/>
@ -572,26 +570,13 @@ const LibrarySlug = ({
[
LightBox,
langui,
item.thumbnail?.data?.attributes,
item.subitem_of?.data,
item.title,
item.subtitle,
item.metadata,
item.descriptions,
item.urls,
item.gallery,
item.release_date,
item.price,
item.categories,
item.size,
item.subitems,
item.contents,
item.slug,
item,
itemId,
currencies,
appLayout.currency,
currency,
isVariantSet,
hoverable,
toggleKeepInfoVisible,
keepInfoVisible,
displayOpenScans,
openLightBox,

View File

@ -1,9 +1,9 @@
import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next";
import { Fragment, useMemo } from "react";
import { AppLayout } from "components/AppLayout";
import { ScanSet } from "components/Library/ScanSet";
import { TranslatedScanSet } from "components/Library/ScanSet";
import { ScanSetCover } from "components/Library/ScanSetCover";
import { NavOption } from "components/PanelComponents/NavOption";
import { TranslatedNavOption } from "components/PanelComponents/NavOption";
import {
ReturnButton,
ReturnButtonType,
@ -16,13 +16,21 @@ import { SubPanel } from "components/Panels/SubPanel";
import { GetLibraryItemScansQuery } from "graphql/generated";
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
import { getReadySdk } from "graphql/sdk";
import { prettyinlineTitle, prettySlug } from "helpers/formatters";
import {
prettyinlineTitle,
prettySlug,
prettyItemSubType,
} from "helpers/formatters";
import {
filterHasAttributes,
isDefined,
sortRangedContent,
} from "helpers/others";
import { useLightBox } from "hooks/useLightBox";
import { isUntangibleGroupItem } from "helpers/libraryItem";
import { PreviewCardCTAs } from "components/Library/PreviewCardCTAs";
import { PreviewCard } from "components/PreviewCard";
import { HorizontalLine } from "components/HorizontalLine";
/*
*
@ -42,8 +50,10 @@ interface Props extends AppStaticProps {
const LibrarySlug = ({
item,
itemId,
langui,
languages,
currencies,
...otherProps
}: Props): JSX.Element => {
const [openLightBox, LightBox] = useLightBox();
@ -55,29 +65,109 @@ const LibrarySlug = ({
href={`/library/${item.slug}`}
title={langui.item}
langui={langui}
className="mb-4"
displayOn={ReturnButtonType.Desktop}
horizontalLine
/>
{item.contents?.data.map((content) => (
<NavOption
key={content.id}
url={`#${content.attributes?.slug}`}
title={prettySlug(content.attributes?.slug, item.slug)}
subtitle={
content.attributes?.range[0]?.__typename ===
"ComponentRangePageRange"
? `${content.attributes.range[0].starting_page}` +
`` +
`${content.attributes.range[0].ending_page}`
: undefined
<div className="mobile:w-[80%]">
<PreviewCard
href={`/library/${item.slug}`}
title={item.title}
subtitle={item.subtitle}
thumbnail={item.thumbnail?.data?.attributes}
thumbnailAspectRatio="21/29.7"
thumbnailRounded={false}
topChips={
item.metadata && item.metadata.length > 0 && item.metadata[0]
? [prettyItemSubType(item.metadata[0])]
: []
}
bottomChips={filterHasAttributes(item.categories?.data, [
"attributes",
] as const).map((category) => category.attributes.short)}
metadata={{
currencies: currencies,
release_date: item.release_date,
price: item.price,
position: "Bottom",
}}
infoAppend={
!isUntangibleGroupItem(item.metadata?.[0]) && (
<PreviewCardCTAs id={itemId} langui={langui} />
)
}
border
/>
))}
</div>
<HorizontalLine />
<p className="mb-4 font-headers text-2xl font-bold">
{langui.contents}
</p>
{filterHasAttributes(item.contents?.data, ["attributes"] as const).map(
(content) => (
<>
{content.attributes.scan_set &&
content.attributes.scan_set.length > 0 && (
<TranslatedNavOption
key={content.id}
url={`#${content.attributes.slug}`}
translations={filterHasAttributes(
content.attributes.content?.data?.attributes
?.translations,
["language.data.attributes"] as const
).map((translation) => ({
language: translation.language.data.attributes.code,
title: prettyinlineTitle(
translation.pre_title,
translation.title,
translation.subtitle
),
subtitle:
content.attributes.range[0]?.__typename ===
"ComponentRangePageRange"
? `${content.attributes.range[0].starting_page}` +
`` +
`${content.attributes.range[0].ending_page}`
: undefined,
}))}
fallbackTitle={prettySlug(
content.attributes.slug,
item.slug
)}
fallbackSubtitle={
content.attributes.range[0]?.__typename ===
"ComponentRangePageRange"
? `${content.attributes.range[0].starting_page}` +
`` +
`${content.attributes.range[0].ending_page}`
: undefined
}
border
languages={languages}
/>
)}
</>
)
)}
</SubPanel>
),
[item.contents?.data, item.slug, langui]
[
currencies,
item.categories?.data,
item.contents?.data,
item.metadata,
item.price,
item.release_date,
item.slug,
item.subtitle,
item.thumbnail?.data?.attributes,
item.title,
itemId,
languages,
langui,
]
);
const contentPanel = useMemo(
@ -105,11 +195,22 @@ const LibrarySlug = ({
{item.contents?.data.map((content) => (
<Fragment key={content.id}>
{content.attributes?.scan_set?.[0] && (
<ScanSet
<TranslatedScanSet
scanSet={content.attributes.scan_set}
openLightBox={openLightBox}
slug={content.attributes.slug}
title={prettySlug(content.attributes.slug, item.slug)}
id={content.attributes.slug}
translations={filterHasAttributes(
content.attributes.content?.data?.attributes?.translations,
["language.data.attributes"] as const
).map((translation) => ({
language: translation.language.data.attributes.code,
title: prettyinlineTitle(
translation.pre_title,
translation.title,
translation.subtitle
),
}))}
fallbackTitle={prettySlug(content.attributes.slug, item.slug)}
languages={languages}
langui={langui}
content={content.attributes.content}
@ -138,6 +239,7 @@ const LibrarySlug = ({
thumbnail={item.thumbnail?.data?.attributes ?? undefined}
languages={languages}
langui={langui}
currencies={currencies}
{...otherProps}
/>
);

View File

@ -33,6 +33,7 @@ import { useAppLayout } from "contexts/AppLayoutContext";
import { convertPrice } from "helpers/numbers";
import { SmartList } from "components/SmartList";
import { SelectiveNonNullable } from "helpers/types/SelectiveNonNullable";
import { useBoolean } from "hooks/useBoolean";
/*
*
@ -66,29 +67,44 @@ const Library = ({
...otherProps
}: Props): JSX.Element => {
const hoverable = useMediaHoverable();
const appLayout = useAppLayout();
const { libraryItemUserStatus } = useAppLayout();
const [searchName, setSearchName] = useState(
DEFAULT_FILTERS_STATE.searchName
);
const [showSubitems, setShowSubitems] = useState<boolean>(
DEFAULT_FILTERS_STATE.showSubitems
);
const [showPrimaryItems, setShowPrimaryItems] = useState<boolean>(
DEFAULT_FILTERS_STATE.showPrimaryItems
);
const [showSecondaryItems, setShowSecondaryItems] = useState<boolean>(
DEFAULT_FILTERS_STATE.showSecondaryItems
);
const {
state: showSubitems,
toggleState: toggleShowSubitems,
setState: setShowSubitems,
} = useBoolean(DEFAULT_FILTERS_STATE.showSubitems);
const {
state: showPrimaryItems,
toggleState: toggleShowPrimaryItems,
setState: setShowPrimaryItems,
} = useBoolean(DEFAULT_FILTERS_STATE.showPrimaryItems);
const {
state: showSecondaryItems,
toggleState: toggleShowSecondaryItems,
setState: setShowSecondaryItems,
} = useBoolean(DEFAULT_FILTERS_STATE.showSecondaryItems);
const {
state: keepInfoVisible,
toggleState: toggleKeepInfoVisible,
setState: setKeepInfoVisible,
} = useBoolean(DEFAULT_FILTERS_STATE.keepInfoVisible);
const [sortingMethod, setSortingMethod] = useState<number>(
DEFAULT_FILTERS_STATE.sortingMethod
);
const [groupingMethod, setGroupingMethod] = useState<number>(
DEFAULT_FILTERS_STATE.groupingMethod
);
const [keepInfoVisible, setKeepInfoVisible] = useState(
DEFAULT_FILTERS_STATE.keepInfoVisible
);
const [filterUserStatus, setFilterUserStatus] = useState<
LibraryItemUserStatus | undefined
>(DEFAULT_FILTERS_STATE.filterUserStatus);
@ -107,28 +123,22 @@ const Library = ({
if (item.attributes.primary && !showPrimaryItems) return false;
if (!item.attributes.primary && !showSecondaryItems) return false;
if (
isDefined(filterUserStatus) &&
item.id &&
appLayout.libraryItemUserStatus
) {
if (isDefined(filterUserStatus) && item.id && libraryItemUserStatus) {
if (isUntangibleGroupItem(item.attributes.metadata?.[0])) {
return false;
}
if (filterUserStatus === LibraryItemUserStatus.None) {
if (appLayout.libraryItemUserStatus[item.id]) {
if (libraryItemUserStatus[item.id]) {
return false;
}
} else if (
filterUserStatus !== appLayout.libraryItemUserStatus[item.id]
) {
} else if (filterUserStatus !== libraryItemUserStatus[item.id]) {
return false;
}
}
return true;
},
[
appLayout.libraryItemUserStatus,
libraryItemUserStatus,
filterUserStatus,
showPrimaryItems,
showSecondaryItems,
@ -260,8 +270,8 @@ const Library = ({
<TextInput
className="mb-6 w-full"
placeholder={langui.search_title ?? undefined}
state={searchName}
setState={setSearchName}
value={searchName}
onChange={setSearchName}
/>
<WithLabel
@ -274,8 +284,8 @@ const Library = ({
langui.type ?? "Type",
langui.release_year ?? "Year",
]}
state={groupingMethod}
setState={setGroupingMethod}
value={groupingMethod}
onChange={setGroupingMethod}
allowEmpty
/>
}
@ -291,21 +301,21 @@ const Library = ({
langui.price ?? "Price",
langui.release_date ?? "Release date",
]}
state={sortingMethod}
setState={setSortingMethod}
value={sortingMethod}
onChange={setSortingMethod}
/>
}
/>
<WithLabel
label={langui.show_subitems}
input={<Switch state={showSubitems} setState={setShowSubitems} />}
input={<Switch value={showSubitems} onClick={toggleShowSubitems} />}
/>
<WithLabel
label={langui.show_primary_items}
input={
<Switch state={showPrimaryItems} setState={setShowPrimaryItems} />
<Switch value={showPrimaryItems} onClick={toggleShowPrimaryItems} />
}
/>
@ -313,8 +323,8 @@ const Library = ({
label={langui.show_secondary_items}
input={
<Switch
state={showSecondaryItems}
setState={setShowSecondaryItems}
value={showSecondaryItems}
onClick={toggleShowSecondaryItems}
/>
}
/>
@ -323,7 +333,7 @@ const Library = ({
<WithLabel
label={langui.always_show_info}
input={
<Switch state={keepInfoVisible} setState={setKeepInfoVisible} />
<Switch value={keepInfoVisible} onClick={toggleKeepInfoVisible} />
}
/>
)}
@ -382,10 +392,18 @@ const Library = ({
keepInfoVisible,
langui,
searchName,
setKeepInfoVisible,
setShowPrimaryItems,
setShowSecondaryItems,
setShowSubitems,
showPrimaryItems,
showSecondaryItems,
showSubitems,
sortingMethod,
toggleKeepInfoVisible,
toggleShowPrimaryItems,
toggleShowSecondaryItems,
toggleShowSubitems,
]
);

View File

@ -20,6 +20,7 @@ import { Button } from "components/Inputs/Button";
import { useMediaHoverable } from "hooks/useMediaQuery";
import { filterDefined, filterHasAttributes } from "helpers/others";
import { SmartList } from "components/SmartList";
import { useBoolean } from "hooks/useBoolean";
/*
*
@ -50,9 +51,11 @@ const News = ({
const [searchName, setSearchName] = useState(
DEFAULT_FILTERS_STATE.searchName
);
const [keepInfoVisible, setKeepInfoVisible] = useState(
DEFAULT_FILTERS_STATE.keepInfoVisible
);
const {
state: keepInfoVisible,
toggleState: toggleKeepInfoVisible,
setState: setKeepInfoVisible,
} = useBoolean(DEFAULT_FILTERS_STATE.keepInfoVisible);
const subPanel = useMemo(
() => (
@ -66,15 +69,15 @@ const News = ({
<TextInput
className="mb-6 w-full"
placeholder={langui.search_title ?? undefined}
state={searchName}
setState={setSearchName}
value={searchName}
onChange={setSearchName}
/>
{hoverable && (
<WithLabel
label={langui.always_show_info}
input={
<Switch setState={setKeepInfoVisible} state={keepInfoVisible} />
<Switch onClick={toggleKeepInfoVisible} value={keepInfoVisible} />
}
/>
)}
@ -90,7 +93,14 @@ const News = ({
/>
</SubPanel>
),
[hoverable, keepInfoVisible, langui, searchName]
[
hoverable,
keepInfoVisible,
langui,
searchName,
setKeepInfoVisible,
toggleKeepInfoVisible,
]
);
const contentPanel = useMemo(

View File

@ -139,11 +139,10 @@ const WikiPage = ({
{page.tags?.data && page.tags.data.length > 0 && (
<>
<p className="font-headers text-xl font-bold">
{/* TODO: Add Tags to langui */}
{"Tags"}
{langui.tags}
</p>
<div className="flex flex-row flex-wrap place-content-center gap-2">
{filterHasAttributes(page.tags?.data, [
{filterHasAttributes(page.tags.data, [
"attributes",
] as const).map((tag) => (
<Chip
@ -203,9 +202,11 @@ const WikiPage = ({
),
[
LanguageSwitcher,
LightBox,
languageSwitcherProps,
languages,
langui,
openLightBox,
page,
selectedTranslation,
]

View File

@ -25,6 +25,7 @@ import { SmartList } from "components/SmartList";
import { Select } from "components/Inputs/Select";
import { SelectiveNonNullable } from "helpers/types/SelectiveNonNullable";
import { prettySlug } from "helpers/formatters";
import { useBoolean } from "hooks/useBoolean";
/*
*
@ -62,9 +63,11 @@ const Wiki = ({
DEFAULT_FILTERS_STATE.groupingMethod
);
const [keepInfoVisible, setKeepInfoVisible] = useState(
DEFAULT_FILTERS_STATE.keepInfoVisible
);
const {
state: keepInfoVisible,
toggleState: toggleKeepInfoVisible,
setState: setKeepInfoVisible,
} = useBoolean(DEFAULT_FILTERS_STATE.keepInfoVisible);
const subPanel = useMemo(
() => (
@ -78,8 +81,8 @@ const Wiki = ({
<TextInput
className="mb-6 w-full"
placeholder={langui.search_title ?? undefined}
state={searchName}
setState={setSearchName}
value={searchName}
onChange={setSearchName}
/>
<WithLabel
@ -88,8 +91,8 @@ const Wiki = ({
<Select
className="w-full"
options={[langui.category ?? ""]}
state={groupingMethod}
setState={setGroupingMethod}
value={groupingMethod}
onChange={setGroupingMethod}
allowEmpty
/>
}
@ -99,7 +102,7 @@ const Wiki = ({
<WithLabel
label={langui.always_show_info}
input={
<Switch setState={setKeepInfoVisible} state={keepInfoVisible} />
<Switch value={keepInfoVisible} onClick={toggleKeepInfoVisible} />
}
/>
)}
@ -122,7 +125,15 @@ const Wiki = ({
<NavOption title={langui.chronology} url="/wiki/chronology" border />
</SubPanel>
),
[groupingMethod, hoverable, keepInfoVisible, langui, searchName]
[
groupingMethod,
hoverable,
keepInfoVisible,
langui,
searchName,
setKeepInfoVisible,
toggleKeepInfoVisible,
]
);
const groupingFunction = useCallback(