useBoolean + many fixes
This commit is contained in:
parent
b6c2363093
commit
260bdd5577
|
@ -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
|
|
@ -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. |
|
||||
|
|
|
@ -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}` },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 },
|
||||
]}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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) ? (
|
||||
<>
|
||||
“{compProps.children}”
|
||||
<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) ? (
|
||||
<>
|
||||
“{compProps.children}”
|
||||
<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[] = [];
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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 />
|
||||
|
|
|
@ -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 && (
|
||||
<>
|
||||
|
|
|
@ -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} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -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 <></>;
|
||||
};
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
/*
|
||||
* ╭───────────────────╮
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 };
|
||||
};
|
|
@ -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]);
|
||||
};
|
||||
|
|
|
@ -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 };
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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 && (
|
||||
|
|
|
@ -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,
|
||||
]
|
||||
);
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -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,
|
||||
]
|
||||
);
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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,
|
||||
]
|
||||
|
|
|
@ -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(
|
||||
|
|
Loading…
Reference in New Issue