useBoolean + many fixes
This commit is contained in:
parent
b6c2363093
commit
260bdd5577
|
@ -6,4 +6,5 @@ next-sitemap.config.js
|
||||||
next.config.js
|
next.config.js
|
||||||
postcss.config.js
|
postcss.config.js
|
||||||
tailwind.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 | 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 | | |
|
| Text Sets | Duplicate Language | Error | High | | |
|
||||||
|
|
||||||
|
|
||||||
## LibraryItems
|
## LibraryItems
|
||||||
|
|
||||||
| Subitem | Name | Type | Severity | Description | Recommendation |
|
| 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 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 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 | 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();
|
const isMobile = useMediaMobile();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
router.events.on("routeChangeStart", () => {
|
||||||
router.events?.on("routeChangeStart", () => {
|
|
||||||
setConfigPanelOpen(false);
|
setConfigPanelOpen(false);
|
||||||
setMainPanelOpen(false);
|
setMainPanelOpen(false);
|
||||||
setSubPanelOpen(false);
|
setSubPanelOpen(false);
|
||||||
});
|
});
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
||||||
router.events?.on("hashChangeStart", () => {
|
router.events.on("hashChangeStart", () => {
|
||||||
setSubPanelOpen(false);
|
setSubPanelOpen(false);
|
||||||
});
|
});
|
||||||
}, [router.events, setConfigPanelOpen, setMainPanelOpen, setSubPanelOpen]);
|
}, [router.events, setConfigPanelOpen, setMainPanelOpen, setSubPanelOpen]);
|
||||||
|
@ -461,8 +460,8 @@ export const AppLayout = ({
|
||||||
<div>
|
<div>
|
||||||
<Select
|
<Select
|
||||||
options={currencyOptions}
|
options={currencyOptions}
|
||||||
state={currencySelect}
|
value={currencySelect}
|
||||||
setState={setCurrencySelect}
|
onChange={setCurrencySelect}
|
||||||
className="w-28"
|
className="w-28"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -516,8 +515,8 @@ export const AppLayout = ({
|
||||||
<TextInput
|
<TextInput
|
||||||
placeholder="<player>"
|
placeholder="<player>"
|
||||||
className="w-48"
|
className="w-48"
|
||||||
state={playerName}
|
value={playerName ?? ""}
|
||||||
setState={setPlayerName}
|
onChange={setPlayerName}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -64,13 +64,13 @@ export const Button = ({
|
||||||
id={id}
|
id={id}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
className={cJoin(
|
className={cJoin(
|
||||||
`group grid select-none grid-flow-col place-content-center
|
`group grid cursor-pointer select-none grid-flow-col
|
||||||
place-items-center gap-2 rounded-full border-[1px] border-dark py-3 px-4 leading-none
|
place-content-center place-items-center gap-2 rounded-full border-[1px] border-dark py-3 px-4
|
||||||
text-dark transition-all`,
|
leading-none text-dark transition-all`,
|
||||||
cIf(
|
cIf(
|
||||||
active,
|
active,
|
||||||
"!border-black bg-black !text-light drop-shadow-black-lg",
|
"!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"),
|
cIf(size === "small", "px-3 py-1 text-xs"),
|
||||||
className
|
className
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import { Dispatch, SetStateAction } from "react";
|
|
||||||
import { ButtonGroup } from "./ButtonGroup";
|
import { ButtonGroup } from "./ButtonGroup";
|
||||||
import { Icon } from "components/Ico";
|
import { Icon } from "components/Ico";
|
||||||
import { cJoin } from "helpers/className";
|
import { cJoin } from "helpers/className";
|
||||||
|
@ -10,34 +9,23 @@ import { cJoin } from "helpers/className";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
className?: string;
|
className?: string;
|
||||||
maxPage: number;
|
|
||||||
page: number;
|
page: number;
|
||||||
setPage: Dispatch<SetStateAction<number>>;
|
onChange: (value: number) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||||
|
|
||||||
export const PageSelector = ({
|
export const PageSelector = ({
|
||||||
page,
|
page,
|
||||||
setPage,
|
|
||||||
maxPage,
|
|
||||||
className,
|
className,
|
||||||
|
onChange,
|
||||||
}: Props): JSX.Element => (
|
}: Props): JSX.Element => (
|
||||||
<ButtonGroup
|
<ButtonGroup
|
||||||
className={cJoin("flex flex-row place-content-center", className)}
|
className={cJoin("flex flex-row place-content-center", className)}
|
||||||
buttonsProps={[
|
buttonsProps={[
|
||||||
{
|
{ onClick: () => onChange(page - 1), icon: Icon.NavigateBefore },
|
||||||
onClick: () => setPage((current) => (page > 0 ? current - 1 : current)),
|
{ text: (page + 1).toString() },
|
||||||
icon: Icon.NavigateBefore,
|
{ onClick: () => onChange(page + 1), icon: Icon.NavigateNext },
|
||||||
},
|
|
||||||
{
|
|
||||||
text: (page + 1).toString(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
onClick: () =>
|
|
||||||
setPage((current) => (page < maxPage ? page + 1 : current)),
|
|
||||||
icon: Icon.NavigateNext,
|
|
||||||
},
|
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,10 +1,4 @@
|
||||||
import {
|
import { Fragment, useCallback, useState } from "react";
|
||||||
Dispatch,
|
|
||||||
Fragment,
|
|
||||||
SetStateAction,
|
|
||||||
useCallback,
|
|
||||||
useState,
|
|
||||||
} from "react";
|
|
||||||
import { Ico, Icon } from "components/Ico";
|
import { Ico, Icon } from "components/Ico";
|
||||||
import { cIf, cJoin } from "helpers/className";
|
import { cIf, cJoin } from "helpers/className";
|
||||||
import { useToggle } from "hooks/useToggle";
|
import { useToggle } from "hooks/useToggle";
|
||||||
|
@ -15,30 +9,30 @@ import { useToggle } from "hooks/useToggle";
|
||||||
*/
|
*/
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
setState: Dispatch<SetStateAction<number>>;
|
value: number;
|
||||||
state: number;
|
|
||||||
options: string[];
|
options: string[];
|
||||||
selected?: number;
|
selected?: number;
|
||||||
allowEmpty?: boolean;
|
allowEmpty?: boolean;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
onChange: (value: number) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||||
|
|
||||||
export const Select = ({
|
export const Select = ({
|
||||||
className,
|
className,
|
||||||
state,
|
value,
|
||||||
options,
|
options,
|
||||||
allowEmpty,
|
allowEmpty,
|
||||||
setState,
|
onChange,
|
||||||
}: Props): JSX.Element => {
|
}: Props): JSX.Element => {
|
||||||
const [opened, setOpened] = useState(false);
|
const [opened, setOpened] = useState(false);
|
||||||
const toggleOpened = useToggle(setOpened);
|
const toggleOpened = useToggle(setOpened);
|
||||||
|
|
||||||
const tryToggling = useCallback(() => {
|
const tryToggling = useCallback(() => {
|
||||||
const optionCount = options.length + (state === -1 ? 1 : 0);
|
const optionCount = options.length + (value === -1 ? 1 : 0);
|
||||||
if (optionCount > 1) toggleOpened();
|
if (optionCount > 1) toggleOpened();
|
||||||
}, [options.length, state, toggleOpened]);
|
}, [options.length, value, toggleOpened]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
@ -57,14 +51,14 @@ export const Select = ({
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<p onClick={tryToggling} className="w-full">
|
<p onClick={tryToggling} className="w-full">
|
||||||
{state === -1 ? "—" : options[state]}
|
{value === -1 ? "—" : options[value]}
|
||||||
</p>
|
</p>
|
||||||
{state >= 0 && allowEmpty && (
|
{value >= 0 && allowEmpty && (
|
||||||
<Ico
|
<Ico
|
||||||
icon={Icon.Close}
|
icon={Icon.Close}
|
||||||
className="!text-xs"
|
className="!text-xs"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setState(-1);
|
onChange(-1);
|
||||||
setOpened(false);
|
setOpened(false);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -82,7 +76,7 @@ export const Select = ({
|
||||||
>
|
>
|
||||||
{options.map((option, index) => (
|
{options.map((option, index) => (
|
||||||
<Fragment key={index}>
|
<Fragment key={index}>
|
||||||
{index !== state && (
|
{index !== value && (
|
||||||
<div
|
<div
|
||||||
className={cJoin(
|
className={cJoin(
|
||||||
"cursor-pointer p-1 transition-colors last-of-type:rounded-b-[1em] hover:bg-mid",
|
"cursor-pointer p-1 transition-colors last-of-type:rounded-b-[1em] hover:bg-mid",
|
||||||
|
@ -91,7 +85,7 @@ export const Select = ({
|
||||||
id={option}
|
id={option}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setOpened(false);
|
setOpened(false);
|
||||||
setState(index);
|
onChange(index);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{option}
|
{option}
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
import { Dispatch, SetStateAction } from "react";
|
|
||||||
import { cIf, cJoin } from "helpers/className";
|
import { cIf, cJoin } from "helpers/className";
|
||||||
import { useToggle } from "hooks/useToggle";
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* ╭─────────────╮
|
* ╭─────────────╮
|
||||||
|
@ -8,8 +6,8 @@ import { useToggle } from "hooks/useToggle";
|
||||||
*/
|
*/
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
setState: Dispatch<SetStateAction<boolean>>;
|
onClick: () => void;
|
||||||
state: boolean;
|
value: boolean;
|
||||||
className?: string;
|
className?: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
}
|
}
|
||||||
|
@ -17,38 +15,31 @@ interface Props {
|
||||||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||||
|
|
||||||
export const Switch = ({
|
export const Switch = ({
|
||||||
state,
|
value,
|
||||||
setState,
|
onClick,
|
||||||
className,
|
className,
|
||||||
disabled = false,
|
disabled = false,
|
||||||
}: Props): JSX.Element => {
|
}: Props): JSX.Element => (
|
||||||
const toggleState = useToggle(setState);
|
<div
|
||||||
return (
|
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
|
<div
|
||||||
className={cJoin(
|
className={cJoin(
|
||||||
"relative grid h-6 w-12 rounded-full border-2 border-mid transition-colors",
|
"absolute aspect-square rounded-full bg-dark transition-transform",
|
||||||
cIf(disabled, "cursor-not-allowed", "cursor-pointer"),
|
|
||||||
cIf(
|
cIf(
|
||||||
state,
|
value,
|
||||||
"border-none bg-mid shadow-inner-sm shadow-shade",
|
"top-[2px] bottom-[2px] left-[2px] translate-x-[120%]",
|
||||||
"bg-light"
|
"top-0 bottom-0 left-0"
|
||||||
),
|
)
|
||||||
className
|
|
||||||
)}
|
)}
|
||||||
onClick={() => {
|
></div>
|
||||||
if (!disabled) toggleState();
|
</div>
|
||||||
}}
|
);
|
||||||
>
|
|
||||||
<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>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import { Dispatch, SetStateAction } from "react";
|
|
||||||
import { Ico, Icon } from "components/Ico";
|
import { Ico, Icon } from "components/Ico";
|
||||||
import { cJoin } from "helpers/className";
|
import { cJoin } from "helpers/className";
|
||||||
import { isDefinedAndNotEmpty } from "helpers/others";
|
import { isDefinedAndNotEmpty } from "helpers/others";
|
||||||
|
@ -9,10 +8,8 @@ import { isDefinedAndNotEmpty } from "helpers/others";
|
||||||
*/
|
*/
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
state: string | undefined;
|
value: string;
|
||||||
setState:
|
onChange: (newValue: string) => void;
|
||||||
| Dispatch<SetStateAction<string | undefined>>
|
|
||||||
| Dispatch<SetStateAction<string>>;
|
|
||||||
className?: string;
|
className?: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
|
@ -21,8 +18,8 @@ interface Props {
|
||||||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||||
|
|
||||||
export const TextInput = ({
|
export const TextInput = ({
|
||||||
state,
|
value,
|
||||||
setState,
|
onChange,
|
||||||
className,
|
className,
|
||||||
name,
|
name,
|
||||||
placeholder,
|
placeholder,
|
||||||
|
@ -32,19 +29,19 @@ export const TextInput = ({
|
||||||
className="w-full"
|
className="w-full"
|
||||||
type="text"
|
type="text"
|
||||||
name={name}
|
name={name}
|
||||||
value={state}
|
value={value}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
onChange={(event) => {
|
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">
|
<div className="absolute right-4 top-0 bottom-0 grid place-items-center">
|
||||||
<Ico
|
<Ico
|
||||||
className="cursor-pointer !text-xs"
|
className="cursor-pointer !text-xs"
|
||||||
icon={Icon.Close}
|
icon={Icon.Close}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setState("");
|
onChange("");
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -23,7 +23,7 @@ export const PreviewCardCTAs = ({
|
||||||
expand = false,
|
expand = false,
|
||||||
langui,
|
langui,
|
||||||
}: Props): JSX.Element => {
|
}: Props): JSX.Element => {
|
||||||
const appLayout = useAppLayout();
|
const { libraryItemUserStatus, setLibraryItemUserStatus } = useAppLayout();
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
|
@ -35,13 +35,10 @@ export const PreviewCardCTAs = ({
|
||||||
<Button
|
<Button
|
||||||
icon={Icon.Favorite}
|
icon={Icon.Favorite}
|
||||||
text={expand ? langui.want_it : undefined}
|
text={expand ? langui.want_it : undefined}
|
||||||
active={
|
active={libraryItemUserStatus?.[id] === LibraryItemUserStatus.Want}
|
||||||
appLayout.libraryItemUserStatus?.[id] ===
|
|
||||||
LibraryItemUserStatus.Want
|
|
||||||
}
|
|
||||||
onClick={(event) => {
|
onClick={(event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
appLayout.setLibraryItemUserStatus((current) => {
|
setLibraryItemUserStatus((current) => {
|
||||||
const newLibraryItemUserStatus = current ? { ...current } : {};
|
const newLibraryItemUserStatus = current ? { ...current } : {};
|
||||||
newLibraryItemUserStatus[id] =
|
newLibraryItemUserStatus[id] =
|
||||||
newLibraryItemUserStatus[id] === LibraryItemUserStatus.Want
|
newLibraryItemUserStatus[id] === LibraryItemUserStatus.Want
|
||||||
|
@ -56,13 +53,10 @@ export const PreviewCardCTAs = ({
|
||||||
<Button
|
<Button
|
||||||
icon={Icon.BackHand}
|
icon={Icon.BackHand}
|
||||||
text={expand ? langui.have_it : undefined}
|
text={expand ? langui.have_it : undefined}
|
||||||
active={
|
active={libraryItemUserStatus?.[id] === LibraryItemUserStatus.Have}
|
||||||
appLayout.libraryItemUserStatus?.[id] ===
|
|
||||||
LibraryItemUserStatus.Have
|
|
||||||
}
|
|
||||||
onClick={(event) => {
|
onClick={(event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
appLayout.setLibraryItemUserStatus((current) => {
|
setLibraryItemUserStatus((current) => {
|
||||||
const newLibraryItemUserStatus = current ? { ...current } : {};
|
const newLibraryItemUserStatus = current ? { ...current } : {};
|
||||||
newLibraryItemUserStatus[id] =
|
newLibraryItemUserStatus[id] =
|
||||||
newLibraryItemUserStatus[id] === LibraryItemUserStatus.Have
|
newLibraryItemUserStatus[id] === LibraryItemUserStatus.Have
|
||||||
|
|
|
@ -34,7 +34,7 @@ interface Props {
|
||||||
>["data"][number]["attributes"]
|
>["data"][number]["attributes"]
|
||||||
>["scan_set"]
|
>["scan_set"]
|
||||||
>;
|
>;
|
||||||
slug: string;
|
id: string;
|
||||||
title: string;
|
title: string;
|
||||||
languages: AppStaticProps["languages"];
|
languages: AppStaticProps["languages"];
|
||||||
langui: AppStaticProps["langui"];
|
langui: AppStaticProps["langui"];
|
||||||
|
@ -51,10 +51,10 @@ interface Props {
|
||||||
|
|
||||||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||||
|
|
||||||
export const ScanSet = ({
|
const ScanSet = ({
|
||||||
openLightBox,
|
openLightBox,
|
||||||
scanSet,
|
scanSet,
|
||||||
slug,
|
id,
|
||||||
title,
|
title,
|
||||||
languages,
|
languages,
|
||||||
langui,
|
langui,
|
||||||
|
@ -115,17 +115,16 @@ export const ScanSet = ({
|
||||||
className="flex flex-row flex-wrap place-items-center
|
className="flex flex-row flex-wrap place-items-center
|
||||||
gap-6 pt-10 text-base first-of-type:pt-0"
|
gap-6 pt-10 text-base first-of-type:pt-0"
|
||||||
>
|
>
|
||||||
<h2 id={slug} className="text-2xl">
|
<h2 id={id} className="text-2xl">
|
||||||
{title}
|
{title}
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
{/* TODO: Add Scan and Scanlation to langui */}
|
|
||||||
<Chip
|
<Chip
|
||||||
text={
|
text={
|
||||||
selectedScan.language?.data?.attributes?.code ===
|
selectedScan.language?.data?.attributes?.code ===
|
||||||
selectedScan.source_language?.data?.attributes?.code
|
selectedScan.source_language?.data?.attributes?.code
|
||||||
? "Scan"
|
? langui.scan ?? "Scan"
|
||||||
: "Scanlation"
|
: langui.scanlation ?? "Scanlation"
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -153,8 +152,7 @@ export const ScanSet = ({
|
||||||
|
|
||||||
{selectedScan.scanners && selectedScan.scanners.data.length > 0 && (
|
{selectedScan.scanners && selectedScan.scanners.data.length > 0 && (
|
||||||
<div>
|
<div>
|
||||||
{/* TODO: Add Scanner to langui */}
|
<p className="font-headers font-bold">{langui.scanners}:</p>
|
||||||
<p className="font-headers font-bold">{"Scanners"}:</p>
|
|
||||||
<div className="grid place-content-center place-items-center gap-2">
|
<div className="grid place-content-center place-items-center gap-2">
|
||||||
{filterHasAttributes(selectedScan.scanners.data, [
|
{filterHasAttributes(selectedScan.scanners.data, [
|
||||||
"id",
|
"id",
|
||||||
|
@ -173,8 +171,7 @@ export const ScanSet = ({
|
||||||
|
|
||||||
{selectedScan.cleaners && selectedScan.cleaners.data.length > 0 && (
|
{selectedScan.cleaners && selectedScan.cleaners.data.length > 0 && (
|
||||||
<div>
|
<div>
|
||||||
{/* TODO: Add Cleaners to langui */}
|
<p className="font-headers font-bold">{langui.cleaners}:</p>
|
||||||
<p className="font-headers font-bold">{"Cleaners"}:</p>
|
|
||||||
<div className="grid place-content-center place-items-center gap-2">
|
<div className="grid place-content-center place-items-center gap-2">
|
||||||
{filterHasAttributes(selectedScan.cleaners.data, [
|
{filterHasAttributes(selectedScan.cleaners.data, [
|
||||||
"id",
|
"id",
|
||||||
|
@ -194,8 +191,9 @@ export const ScanSet = ({
|
||||||
{selectedScan.typesetters &&
|
{selectedScan.typesetters &&
|
||||||
selectedScan.typesetters.data.length > 0 && (
|
selectedScan.typesetters.data.length > 0 && (
|
||||||
<div>
|
<div>
|
||||||
{/* TODO: Add typesetter to langui */}
|
<p className="font-headers font-bold">
|
||||||
<p className="font-headers font-bold">{"Typesetters"}:</p>
|
{langui.typesetters}:
|
||||||
|
</p>
|
||||||
<div className="grid place-content-center place-items-center gap-2">
|
<div className="grid place-content-center place-items-center gap-2">
|
||||||
{filterHasAttributes(selectedScan.typesetters.data, [
|
{filterHasAttributes(selectedScan.typesetters.data, [
|
||||||
"id",
|
"id",
|
||||||
|
@ -214,8 +212,7 @@ export const ScanSet = ({
|
||||||
|
|
||||||
{isDefinedAndNotEmpty(selectedScan.notes) && (
|
{isDefinedAndNotEmpty(selectedScan.notes) && (
|
||||||
<ToolTip content={selectedScan.notes}>
|
<ToolTip content={selectedScan.notes}>
|
||||||
{/* TODO: Add Notes to langui */}
|
<Chip text={langui.notes ?? "Notes"} />
|
||||||
<Chip text={"Notes"} />
|
|
||||||
</ToolTip>
|
</ToolTip>
|
||||||
)}
|
)}
|
||||||
</div>
|
</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;
|
return memo;
|
||||||
}, [selectedScan]);
|
}, [selectedScan]);
|
||||||
|
|
||||||
if (coverImages.length > 0) {
|
return (
|
||||||
return (
|
<>
|
||||||
<>
|
{coverImages.length > 0 && selectedScan && (
|
||||||
{selectedScan && (
|
<div>
|
||||||
<div>
|
<div
|
||||||
<div
|
className="flex flex-row flex-wrap place-items-center
|
||||||
className="flex flex-row flex-wrap place-items-center
|
|
||||||
gap-6 pt-10 text-base first-of-type:pt-0"
|
gap-6 pt-10 text-base first-of-type:pt-0"
|
||||||
>
|
>
|
||||||
<h2 id={"cover"} className="text-2xl">
|
<h2 id={"cover"} className="text-2xl">
|
||||||
{/* TODO: Add Cover to langui */}
|
{langui.cover}
|
||||||
{"Cover"}
|
</h2>
|
||||||
</h2>
|
|
||||||
|
|
||||||
{/* TODO: Add Scan and Scanlation to langui */}
|
<Chip
|
||||||
<Chip
|
text={
|
||||||
text={
|
selectedScan.language?.data?.attributes?.code ===
|
||||||
selectedScan.language?.data?.attributes?.code ===
|
selectedScan.source_language?.data?.attributes?.code
|
||||||
selectedScan.source_language?.data?.attributes?.code
|
? langui.scan ?? "Scan"
|
||||||
? "Scan"
|
: langui.scanlation ?? "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>
|
||||||
|
|
||||||
<div className="flex flex-row flex-wrap place-items-center gap-4 pb-6">
|
{selectedScan.scanners && selectedScan.scanners.data.length > 0 && (
|
||||||
<LanguageSwitcher {...languageSwitcherProps} />
|
<div>
|
||||||
|
<p className="font-headers font-bold">{langui.scanners}:</p>
|
||||||
<div className="grid place-content-center place-items-center">
|
<div className="grid place-content-center place-items-center gap-2">
|
||||||
<p className="font-headers font-bold">{langui.status}:</p>
|
{filterHasAttributes(selectedScan.scanners.data, [
|
||||||
<ToolTip
|
"id",
|
||||||
content={getStatusDescription(selectedScan.status, langui)}
|
"attributes",
|
||||||
maxWidth={"20rem"}
|
] as const).map((scanner) => (
|
||||||
>
|
<Fragment key={scanner.id}>
|
||||||
<Chip text={selectedScan.status} />
|
<RecorderChip
|
||||||
</ToolTip>
|
langui={langui}
|
||||||
|
recorder={scanner.attributes}
|
||||||
|
/>
|
||||||
|
</Fragment>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
</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>
|
<div>
|
||||||
{/* TODO: Add Scanner to langui */}
|
<p className="font-headers font-bold">
|
||||||
<p className="font-headers font-bold">{"Scanners"}:</p>
|
{langui.typesetters}:
|
||||||
|
</p>
|
||||||
<div className="grid place-content-center place-items-center gap-2">
|
<div className="grid place-content-center place-items-center gap-2">
|
||||||
{filterHasAttributes(selectedScan.scanners.data, [
|
{filterHasAttributes(selectedScan.typesetters.data, [
|
||||||
"id",
|
"id",
|
||||||
"attributes",
|
"attributes",
|
||||||
] as const).map((scanner) => (
|
] as const).map((typesetter) => (
|
||||||
<Fragment key={scanner.id}>
|
<Fragment key={typesetter.id}>
|
||||||
<RecorderChip
|
<RecorderChip
|
||||||
langui={langui}
|
langui={langui}
|
||||||
recorder={scanner.attributes}
|
recorder={typesetter.attributes}
|
||||||
/>
|
/>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
{selectedScan.cleaners && selectedScan.cleaners.data.length > 0 && (
|
<div
|
||||||
<div>
|
className="grid items-end gap-8 border-b-[3px] border-dotted pb-12
|
||||||
{/* 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
|
|
||||||
last-of-type:border-0 desktop:grid-cols-[repeat(auto-fill,_minmax(10rem,1fr))]
|
last-of-type:border-0 desktop:grid-cols-[repeat(auto-fill,_minmax(10rem,1fr))]
|
||||||
mobile:grid-cols-2"
|
mobile:grid-cols-2"
|
||||||
>
|
>
|
||||||
{coverImages.map((image, index) => (
|
{coverImages.map((image, index) => (
|
||||||
<div
|
<div
|
||||||
key={image.url}
|
key={image.url}
|
||||||
className="cursor-pointer transition-transform
|
className="cursor-pointer transition-transform
|
||||||
drop-shadow-shade-lg hover:scale-[1.02]"
|
drop-shadow-shade-lg hover:scale-[1.02]"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const imgs = coverImages.map((img) =>
|
const imgs = coverImages.map((img) =>
|
||||||
getAssetURL(img.url, ImageQuality.Large)
|
getAssetURL(img.url, ImageQuality.Large)
|
||||||
);
|
);
|
||||||
|
|
||||||
openLightBox(imgs, index);
|
openLightBox(imgs, index);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Img image={image} quality={ImageQuality.Small} />
|
<Img image={image} quality={ImageQuality.Small} />
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
</div>
|
||||||
</>
|
)}
|
||||||
);
|
</>
|
||||||
}
|
);
|
||||||
return <></>;
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -12,7 +12,7 @@ import { AppStaticProps } from "graphql/getAppStaticProps";
|
||||||
import { cJoin } from "helpers/className";
|
import { cJoin } from "helpers/className";
|
||||||
import { slugify } from "helpers/formatters";
|
import { slugify } from "helpers/formatters";
|
||||||
import { getAssetURL, ImageQuality } from "helpers/img";
|
import { getAssetURL, ImageQuality } from "helpers/img";
|
||||||
import { isDefined, isDefinedAndNotEmpty } from "helpers/others";
|
import { isDefined, isDefinedAndNotEmpty, isUndefined } from "helpers/others";
|
||||||
import { useLightBox } from "hooks/useLightBox";
|
import { useLightBox } from "hooks/useLightBox";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -31,283 +31,276 @@ export const Markdawn = ({
|
||||||
className,
|
className,
|
||||||
text: rawText,
|
text: rawText,
|
||||||
}: MarkdawnProps): JSX.Element => {
|
}: MarkdawnProps): JSX.Element => {
|
||||||
const appLayout = useAppLayout();
|
const { playerName } = useAppLayout();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [openLightBox, LightBox] = useLightBox();
|
const [openLightBox, LightBox] = useLightBox();
|
||||||
|
|
||||||
/* eslint-disable no-irregular-whitespace */
|
/* eslint-disable no-irregular-whitespace */
|
||||||
const text = useMemo(
|
const text = useMemo(
|
||||||
() => `${preprocessMarkDawn(rawText)}
|
() => `${preprocessMarkDawn(rawText, playerName)}
|
||||||
`,
|
`,
|
||||||
[rawText]
|
[playerName, rawText]
|
||||||
);
|
);
|
||||||
/* eslint-enable no-irregular-whitespace */
|
/* eslint-enable no-irregular-whitespace */
|
||||||
|
|
||||||
if (text) {
|
if (isUndefined(text) || text === "") {
|
||||||
return (
|
return <></>;
|
||||||
<>
|
}
|
||||||
<LightBox />
|
|
||||||
<Markdown
|
return (
|
||||||
className={cJoin("formatted", className)}
|
<>
|
||||||
options={{
|
<LightBox />
|
||||||
slugify: slugify,
|
<Markdown
|
||||||
overrides: {
|
className={cJoin("formatted", className)}
|
||||||
a: {
|
options={{
|
||||||
component: (compProps: {
|
slugify: slugify,
|
||||||
href: string;
|
overrides: {
|
||||||
children: React.ReactNode;
|
a: {
|
||||||
}) => {
|
component: (compProps: {
|
||||||
if (
|
href: string;
|
||||||
compProps.href.startsWith("/") ||
|
children: React.ReactNode;
|
||||||
compProps.href.startsWith("#")
|
}) => {
|
||||||
) {
|
if (
|
||||||
return (
|
compProps.href.startsWith("/") ||
|
||||||
<a onClick={async () => router.push(compProps.href)}>
|
compProps.href.startsWith("#")
|
||||||
{compProps.children}
|
) {
|
||||||
</a>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<a href={compProps.href} target="_blank" rel="noreferrer">
|
<a onClick={async () => router.push(compProps.href)}>
|
||||||
{compProps.children}
|
{compProps.children}
|
||||||
</a>
|
</a>
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
},
|
return (
|
||||||
|
<a href={compProps.href} target="_blank" rel="noreferrer">
|
||||||
h1: {
|
|
||||||
component: (compProps: {
|
|
||||||
id: string;
|
|
||||||
style: React.CSSProperties;
|
|
||||||
children: React.ReactNode;
|
|
||||||
}) => (
|
|
||||||
<h1 id={compProps.id} style={compProps.style}>
|
|
||||||
{compProps.children}
|
{compProps.children}
|
||||||
<HeaderToolTip id={compProps.id} />
|
</a>
|
||||||
</h1>
|
);
|
||||||
),
|
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
|
||||||
h2: {
|
h1: {
|
||||||
component: (compProps: {
|
component: (compProps: {
|
||||||
id: string;
|
id: string;
|
||||||
style: React.CSSProperties;
|
style: React.CSSProperties;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}) => (
|
}) => (
|
||||||
<h2 id={compProps.id} style={compProps.style}>
|
<h1 id={compProps.id} style={compProps.style}>
|
||||||
{compProps.children}
|
{compProps.children}
|
||||||
<HeaderToolTip id={compProps.id} />
|
<HeaderToolTip id={compProps.id} />
|
||||||
</h2>
|
</h1>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
||||||
h3: {
|
h2: {
|
||||||
component: (compProps: {
|
component: (compProps: {
|
||||||
id: string;
|
id: string;
|
||||||
style: React.CSSProperties;
|
style: React.CSSProperties;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}) => (
|
}) => (
|
||||||
<h3 id={compProps.id} style={compProps.style}>
|
<h2 id={compProps.id} style={compProps.style}>
|
||||||
{compProps.children}
|
{compProps.children}
|
||||||
<HeaderToolTip id={compProps.id} />
|
<HeaderToolTip id={compProps.id} />
|
||||||
</h3>
|
</h2>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
||||||
h4: {
|
h3: {
|
||||||
component: (compProps: {
|
component: (compProps: {
|
||||||
id: string;
|
id: string;
|
||||||
style: React.CSSProperties;
|
style: React.CSSProperties;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}) => (
|
}) => (
|
||||||
<h4 id={compProps.id} style={compProps.style}>
|
<h3 id={compProps.id} style={compProps.style}>
|
||||||
{compProps.children}
|
{compProps.children}
|
||||||
<HeaderToolTip id={compProps.id} />
|
<HeaderToolTip id={compProps.id} />
|
||||||
</h4>
|
</h3>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
||||||
h5: {
|
h4: {
|
||||||
component: (compProps: {
|
component: (compProps: {
|
||||||
id: string;
|
id: string;
|
||||||
style: React.CSSProperties;
|
style: React.CSSProperties;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}) => (
|
}) => (
|
||||||
<h5 id={compProps.id} style={compProps.style}>
|
<h4 id={compProps.id} style={compProps.style}>
|
||||||
{compProps.children}
|
{compProps.children}
|
||||||
<HeaderToolTip id={compProps.id} />
|
<HeaderToolTip id={compProps.id} />
|
||||||
</h5>
|
</h4>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
||||||
h6: {
|
h5: {
|
||||||
component: (compProps: {
|
component: (compProps: {
|
||||||
id: string;
|
id: string;
|
||||||
style: React.CSSProperties;
|
style: React.CSSProperties;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}) => (
|
}) => (
|
||||||
<h6 id={compProps.id} style={compProps.style}>
|
<h5 id={compProps.id} style={compProps.style}>
|
||||||
{compProps.children}
|
{compProps.children}
|
||||||
<HeaderToolTip id={compProps.id} />
|
<HeaderToolTip id={compProps.id} />
|
||||||
</h6>
|
</h5>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
||||||
SceneBreak: {
|
h6: {
|
||||||
component: (compProps: { id: string }) => (
|
component: (compProps: {
|
||||||
<div
|
id: string;
|
||||||
id={compProps.id}
|
style: React.CSSProperties;
|
||||||
className={"mt-16 mb-20 h-0 text-center text-3xl text-dark"}
|
children: React.ReactNode;
|
||||||
>
|
}) => (
|
||||||
* * *
|
<h6 id={compProps.id} style={compProps.style}>
|
||||||
</div>
|
{compProps.children}
|
||||||
),
|
<HeaderToolTip id={compProps.id} />
|
||||||
},
|
</h6>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
IntraLink: {
|
SceneBreak: {
|
||||||
component: (compProps: {
|
component: (compProps: { id: string }) => (
|
||||||
children: React.ReactNode;
|
<div
|
||||||
target?: string;
|
id={compProps.id}
|
||||||
page?: string;
|
className={"mt-16 mb-20 h-0 text-center text-3xl text-dark"}
|
||||||
}) => {
|
>
|
||||||
const slug = isDefinedAndNotEmpty(compProps.target)
|
* * *
|
||||||
? slugify(compProps.target)
|
</div>
|
||||||
: slugify(compProps.children?.toString());
|
),
|
||||||
return (
|
},
|
||||||
<a
|
|
||||||
onClick={async () =>
|
|
||||||
router.replace(`${compProps.page ?? ""}#${slug}`)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{compProps.children}
|
|
||||||
</a>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
player: {
|
IntraLink: {
|
||||||
component: () => (
|
component: (compProps: {
|
||||||
<span className="text-dark opacity-70">
|
children: React.ReactNode;
|
||||||
{appLayout.playerName ?? "<player>"}
|
target?: string;
|
||||||
</span>
|
page?: string;
|
||||||
),
|
}) => {
|
||||||
},
|
const slug = isDefinedAndNotEmpty(compProps.target)
|
||||||
|
? slugify(compProps.target)
|
||||||
Transcript: {
|
: slugify(compProps.children?.toString());
|
||||||
component: (compProps) => (
|
return (
|
||||||
<div className="grid grid-cols-[auto_1fr] gap-x-6 gap-y-2 mobile:grid-cols-1">
|
<a
|
||||||
{compProps.children}
|
onClick={async () =>
|
||||||
</div>
|
router.replace(`${compProps.page ?? ""}#${slug}`)
|
||||||
),
|
|
||||||
},
|
|
||||||
|
|
||||||
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}
|
{compProps.children}
|
||||||
</li>
|
</a>
|
||||||
),
|
);
|
||||||
},
|
|
||||||
|
|
||||||
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>
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}}
|
|
||||||
>
|
Transcript: {
|
||||||
{text}
|
component: (compProps) => (
|
||||||
</Markdown>
|
<div className="grid grid-cols-[auto_1fr] gap-x-6 gap-y-2 mobile:grid-cols-1">
|
||||||
</>
|
{compProps.children}
|
||||||
);
|
</div>
|
||||||
}
|
),
|
||||||
return <></>;
|
},
|
||||||
|
|
||||||
|
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 ╰──────────────────────────────────────
|
* ───────────────────────────────────╯ 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 {
|
enum HeaderLevels {
|
||||||
H1 = 1,
|
H1 = 1,
|
||||||
H2 = 2,
|
H2 = 2,
|
||||||
|
@ -446,10 +424,17 @@ enum HeaderLevels {
|
||||||
H6 = 6,
|
H6 = 6,
|
||||||
}
|
}
|
||||||
|
|
||||||
const preprocessMarkDawn = (text: string): string => {
|
const preprocessMarkDawn = (text: string, playerName = ""): string => {
|
||||||
if (!text) return "";
|
if (!text) return "";
|
||||||
|
|
||||||
let preprocessed = typographicRules(text);
|
let preprocessed = text
|
||||||
|
.replaceAll("--", "—")
|
||||||
|
.replaceAll(
|
||||||
|
"@player",
|
||||||
|
isDefinedAndNotEmpty(playerName) ? playerName : "(player)"
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log();
|
||||||
|
|
||||||
let scenebreakIndex = 0;
|
let scenebreakIndex = 0;
|
||||||
const visitedSlugs: string[] = [];
|
const visitedSlugs: string[] = [];
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { MouseEventHandler, useMemo } from "react";
|
import { MouseEventHandler, useCallback, useMemo } from "react";
|
||||||
import { Ico, Icon } from "components/Ico";
|
import { Ico, Icon } from "components/Ico";
|
||||||
import { ToolTip } from "components/ToolTip";
|
import { ToolTip } from "components/ToolTip";
|
||||||
import { cJoin, cIf } from "helpers/className";
|
import { cJoin, cIf } from "helpers/className";
|
||||||
import { isDefinedAndNotEmpty } from "helpers/others";
|
import { isDefinedAndNotEmpty } from "helpers/others";
|
||||||
|
import { AppStaticProps } from "graphql/getAppStaticProps";
|
||||||
|
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* ╭─────────────╮
|
* ╭─────────────╮
|
||||||
|
@ -88,3 +90,45 @@ export const NavOption = ({
|
||||||
</ToolTip>
|
</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,
|
horizontalLine,
|
||||||
className,
|
className,
|
||||||
}: Props): JSX.Element => {
|
}: Props): JSX.Element => {
|
||||||
const appLayout = useAppLayout();
|
const { setSubPanelOpen } = useAppLayout();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
@ -49,7 +49,7 @@ export const ReturnButton = ({
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => appLayout.setSubPanelOpen(false)}
|
onClick={() => setSubPanelOpen(false)}
|
||||||
href={href}
|
href={href}
|
||||||
text={`${langui.return_to} ${title}`}
|
text={`${langui.return_to} ${title}`}
|
||||||
icon={Icon.NavigateBefore}
|
icon={Icon.NavigateBefore}
|
||||||
|
|
|
@ -171,7 +171,11 @@ export const PostPage = ({
|
||||||
description={excerpt}
|
description={excerpt}
|
||||||
langui={langui}
|
langui={langui}
|
||||||
categories={post.categories}
|
categories={post.categories}
|
||||||
languageSwitcher={<LanguageSwitcher {...languageSwitcherProps} />}
|
languageSwitcher={
|
||||||
|
languageSwitcherProps.locales.size > 1 ? (
|
||||||
|
<LanguageSwitcher {...languageSwitcherProps} />
|
||||||
|
) : undefined
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<HorizontalLine />
|
<HorizontalLine />
|
||||||
|
|
|
@ -77,64 +77,61 @@ export const PreviewCard = ({
|
||||||
hoverlay,
|
hoverlay,
|
||||||
infoAppend,
|
infoAppend,
|
||||||
}: Props): JSX.Element => {
|
}: Props): JSX.Element => {
|
||||||
const appLayout = useAppLayout();
|
const { currency } = useAppLayout();
|
||||||
|
|
||||||
const metadataJSX = useMemo(
|
const metadataJSX = useMemo(
|
||||||
() =>
|
() => (
|
||||||
metadata && (metadata.release_date || metadata.price) ? (
|
<>
|
||||||
<div className="flex w-full flex-row flex-wrap gap-x-3">
|
{metadata && (metadata.release_date || metadata.price) && (
|
||||||
{metadata.release_date && (
|
<div className="flex w-full flex-row flex-wrap gap-x-3">
|
||||||
<p className="text-sm mobile:text-xs">
|
{metadata.release_date && (
|
||||||
<Ico
|
<p className="text-sm mobile:text-xs">
|
||||||
icon={Icon.Event}
|
<Ico
|
||||||
className="mr-1 translate-y-[.15em] !text-base"
|
icon={Icon.Event}
|
||||||
/>
|
className="mr-1 translate-y-[.15em] !text-base"
|
||||||
{prettyDate(metadata.release_date)}
|
/>
|
||||||
</p>
|
{prettyDate(metadata.release_date)}
|
||||||
)}
|
</p>
|
||||||
{metadata.price && metadata.currencies && (
|
)}
|
||||||
<p className="justify-self-end text-sm mobile:text-xs">
|
{metadata.price && metadata.currencies && (
|
||||||
<Ico
|
<p className="justify-self-end text-sm mobile:text-xs">
|
||||||
icon={Icon.ShoppingCart}
|
<Ico
|
||||||
className="mr-1 translate-y-[.15em] !text-base"
|
icon={Icon.ShoppingCart}
|
||||||
/>
|
className="mr-1 translate-y-[.15em] !text-base"
|
||||||
{prettyPrice(
|
/>
|
||||||
metadata.price,
|
{prettyPrice(metadata.price, metadata.currencies, currency)}
|
||||||
metadata.currencies,
|
</p>
|
||||||
appLayout.currency
|
)}
|
||||||
)}
|
{metadata.views && (
|
||||||
</p>
|
<p className="text-sm mobile:text-xs">
|
||||||
)}
|
<Ico
|
||||||
{metadata.views && (
|
icon={Icon.Visibility}
|
||||||
<p className="text-sm mobile:text-xs">
|
className="mr-1 translate-y-[.15em] !text-base"
|
||||||
<Ico
|
/>
|
||||||
icon={Icon.Visibility}
|
{prettyShortenNumber(metadata.views)}
|
||||||
className="mr-1 translate-y-[.15em] !text-base"
|
</p>
|
||||||
/>
|
)}
|
||||||
{prettyShortenNumber(metadata.views)}
|
{metadata.author && (
|
||||||
</p>
|
<p className="text-sm mobile:text-xs">
|
||||||
)}
|
<Ico
|
||||||
{metadata.author && (
|
icon={Icon.Person}
|
||||||
<p className="text-sm mobile:text-xs">
|
className="mr-1 translate-y-[.15em] !text-base"
|
||||||
<Ico
|
/>
|
||||||
icon={Icon.Person}
|
{metadata.author}
|
||||||
className="mr-1 translate-y-[.15em] !text-base"
|
</p>
|
||||||
/>
|
)}
|
||||||
{metadata.author}
|
</div>
|
||||||
</p>
|
)}
|
||||||
)}
|
</>
|
||||||
</div>
|
),
|
||||||
) : (
|
[currency, metadata]
|
||||||
<></>
|
|
||||||
),
|
|
||||||
[appLayout.currency, metadata]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link href={href} passHref>
|
<Link href={href} passHref>
|
||||||
<div
|
<div
|
||||||
className="group grid cursor-pointer items-end transition-transform drop-shadow-shade-xl
|
className="group grid cursor-pointer items-end text-left transition-transform
|
||||||
hover:scale-[1.02]"
|
drop-shadow-shade-xl hover:scale-[1.02]"
|
||||||
>
|
>
|
||||||
{stackNumber > 0 && (
|
{stackNumber > 0 && (
|
||||||
<>
|
<>
|
||||||
|
|
|
@ -53,6 +53,7 @@ export const SmartList = <T,>({
|
||||||
langui,
|
langui,
|
||||||
}: Props<T>): JSX.Element => {
|
}: Props<T>): JSX.Element => {
|
||||||
const [page, setPage] = useState(0);
|
const [page, setPage] = useState(0);
|
||||||
|
|
||||||
useScrollTopOnChange(AnchorIds.ContentPanel, [page], paginationScroolTop);
|
useScrollTopOnChange(AnchorIds.ContentPanel, [page], paginationScroolTop);
|
||||||
|
|
||||||
type Group = Map<string, T[]>;
|
type Group = Map<string, T[]>;
|
||||||
|
@ -123,62 +124,68 @@ export const SmartList = <T,>({
|
||||||
[paginationItemPerPage, filteredItems.length]
|
[paginationItemPerPage, filteredItems.length]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const changePage = useCallback(
|
||||||
|
(newPage: number) =>
|
||||||
|
setPage(() => {
|
||||||
|
if (newPage <= 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (newPage >= pageCount) {
|
||||||
|
return pageCount;
|
||||||
|
}
|
||||||
|
return newPage;
|
||||||
|
}),
|
||||||
|
[pageCount]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{pageCount > 1 && paginationSelectorTop && (
|
{pageCount > 1 && paginationSelectorTop && (
|
||||||
<PageSelector
|
<PageSelector className="mb-12" page={page} onChange={changePage} />
|
||||||
maxPage={pageCount}
|
|
||||||
page={page}
|
|
||||||
setPage={setPage}
|
|
||||||
className="mb-12"
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{groupedList.size > 0
|
<div className="mb-8">
|
||||||
? iterateMap(
|
{groupedList.size > 0
|
||||||
groupedList,
|
? iterateMap(
|
||||||
(name, groupItems) =>
|
groupedList,
|
||||||
groupItems.length > 0 && (
|
(name, groupItems) =>
|
||||||
<Fragment key={name}>
|
groupItems.length > 0 && (
|
||||||
{name.length > 0 && (
|
<Fragment key={name}>
|
||||||
<h2
|
{name.length > 0 && (
|
||||||
className="flex flex-row place-items-center gap-2 pb-2 pt-10 text-2xl
|
<h2
|
||||||
|
className="flex flex-row place-items-center gap-2 pb-2 pt-10 text-2xl
|
||||||
first-of-type:pt-0"
|
first-of-type:pt-0"
|
||||||
>
|
>
|
||||||
{name}
|
{name}
|
||||||
<Chip
|
<Chip
|
||||||
text={`${groupItems.length} ${
|
text={`${groupItems.length} ${
|
||||||
groupItems.length <= 1
|
groupItems.length <= 1
|
||||||
? langui.result?.toLowerCase() ?? ""
|
? langui.result?.toLowerCase() ?? ""
|
||||||
: langui.results?.toLowerCase() ?? ""
|
: langui.results?.toLowerCase() ?? ""
|
||||||
}`}
|
}`}
|
||||||
/>
|
/>
|
||||||
</h2>
|
</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
|
|
||||||
)}
|
)}
|
||||||
>
|
<div
|
||||||
{groupItems.map((item) => (
|
className={cJoin(
|
||||||
<RenderItem item={item} key={getItemId(item)} />
|
`grid items-start gap-8 border-b-[3px] border-dotted pb-12
|
||||||
))}
|
last-of-type:border-0 mobile:gap-4`,
|
||||||
</div>
|
className
|
||||||
</Fragment>
|
)}
|
||||||
),
|
>
|
||||||
([a], [b]) => groupSortingFunction(a, b)
|
{groupItems.map((item) => (
|
||||||
)
|
<RenderItem item={item} key={getItemId(item)} />
|
||||||
: isDefined(RenderWhenEmpty) && <RenderWhenEmpty />}
|
))}
|
||||||
|
</div>
|
||||||
|
</Fragment>
|
||||||
|
),
|
||||||
|
([a], [b]) => groupSortingFunction(a, b)
|
||||||
|
)
|
||||||
|
: isDefined(RenderWhenEmpty) && <RenderWhenEmpty />}
|
||||||
|
</div>
|
||||||
|
|
||||||
{pageCount > 1 && paginationSelectorBottom && (
|
{pageCount > 1 && paginationSelectorBottom && (
|
||||||
<PageSelector
|
<PageSelector className="mb-12" page={page} onChange={changePage} />
|
||||||
maxPage={pageCount}
|
|
||||||
page={page}
|
|
||||||
setPage={setPage}
|
|
||||||
className="mt-12"
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -11,6 +11,7 @@ import {
|
||||||
filterDefined,
|
filterDefined,
|
||||||
filterHasAttributes,
|
filterHasAttributes,
|
||||||
getStatusDescription,
|
getStatusDescription,
|
||||||
|
isDefined,
|
||||||
} from "helpers/others";
|
} from "helpers/others";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -30,9 +31,9 @@ export const ChronologyItemComponent = ({
|
||||||
langui,
|
langui,
|
||||||
item,
|
item,
|
||||||
displayYear,
|
displayYear,
|
||||||
}: Props): JSX.Element => {
|
}: Props): JSX.Element => (
|
||||||
if (item.attributes) {
|
<>
|
||||||
return (
|
{isDefined(item.attributes) && (
|
||||||
<div
|
<div
|
||||||
className="grid grid-cols-[4em] grid-rows-[auto_1fr] place-content-start
|
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"
|
rounded-2xl py-4 px-8 target:my-4 target:bg-mid target:py-8"
|
||||||
|
@ -100,7 +101,7 @@ export const ChronologyItemComponent = ({
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
{translation.note ? (
|
{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">
|
<div className="flex items-center gap-1">
|
||||||
<Ico icon={Icon.Warning} className="!text-sm" />
|
<Ico icon={Icon.Warning} className="!text-sm" />
|
||||||
No sources!
|
{langui.no_source_warning}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
|
@ -124,11 +125,9 @@ export const ChronologyItemComponent = ({
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
)}
|
||||||
}
|
</>
|
||||||
|
);
|
||||||
return <></>;
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* ╭───────────────────╮
|
* ╭───────────────────╮
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import { useCallback } from "react";
|
import { useCallback } from "react";
|
||||||
|
import Link from "next/link";
|
||||||
import { Chip } from "components/Chip";
|
import { Chip } from "components/Chip";
|
||||||
import { ToolTip } from "components/ToolTip";
|
import { ToolTip } from "components/ToolTip";
|
||||||
import { AppStaticProps } from "graphql/getAppStaticProps";
|
import { AppStaticProps } from "graphql/getAppStaticProps";
|
||||||
import { getStatusDescription } from "helpers/others";
|
import { getStatusDescription } from "helpers/others";
|
||||||
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
||||||
import Link from "next/link";
|
|
||||||
import { Button } from "components/Inputs/Button";
|
import { Button } from "components/Inputs/Button";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -88,7 +88,7 @@ const DefinitionCard = ({
|
||||||
|
|
||||||
{source?.url && source.name && (
|
{source?.url && source.name && (
|
||||||
<Link href={source.url}>
|
<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>
|
<p>{langui.source}: </p>
|
||||||
<Button size="small" text={source.name} />
|
<Button size="small" text={source.name} />
|
||||||
</div>
|
</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 }) {
|
contents(pagination: { limit: -1 }) {
|
||||||
data {
|
data {
|
||||||
id
|
id
|
||||||
|
@ -122,6 +218,18 @@ query getLibraryItemScans($slug: String, $language_code: String) {
|
||||||
data {
|
data {
|
||||||
attributes {
|
attributes {
|
||||||
slug
|
slug
|
||||||
|
translations {
|
||||||
|
pre_title
|
||||||
|
title
|
||||||
|
subtitle
|
||||||
|
language {
|
||||||
|
data {
|
||||||
|
attributes {
|
||||||
|
code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -159,6 +159,15 @@ query getWebsiteInterface($language_code: String) {
|
||||||
no_results_message
|
no_results_message
|
||||||
all
|
all
|
||||||
special_pages
|
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
|
document
|
||||||
.querySelector(`#${id}`)
|
.querySelector(`#${id}`)
|
||||||
?.scrollTo({ top: 0, behavior: "smooth" });
|
?.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, {
|
import Document, {
|
||||||
DocumentContext,
|
DocumentContext,
|
||||||
|
DocumentInitialProps,
|
||||||
Head,
|
Head,
|
||||||
Html,
|
Html,
|
||||||
Main,
|
Main,
|
||||||
|
@ -7,8 +8,9 @@ import Document, {
|
||||||
} from "next/document";
|
} from "next/document";
|
||||||
|
|
||||||
export default class MyDocument extends Document {
|
export default class MyDocument extends Document {
|
||||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
static async getInitialProps(
|
||||||
static async getInitialProps(ctx: DocumentContext) {
|
ctx: DocumentContext
|
||||||
|
): Promise<DocumentInitialProps> {
|
||||||
const initialProps = await Document.getInitialProps(ctx);
|
const initialProps = await Document.getInitialProps(ctx);
|
||||||
return { ...initialProps };
|
return { ...initialProps };
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next";
|
import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next";
|
||||||
import { Fragment, useState, useMemo } from "react";
|
import { Fragment, useMemo } from "react";
|
||||||
import { AppLayout } from "components/AppLayout";
|
import { AppLayout } from "components/AppLayout";
|
||||||
import { Switch } from "components/Inputs/Switch";
|
import { Switch } from "components/Inputs/Switch";
|
||||||
import { PanelHeader } from "components/PanelComponents/PanelHeader";
|
import { PanelHeader } from "components/PanelComponents/PanelHeader";
|
||||||
|
@ -21,6 +21,7 @@ import { Icon } from "components/Ico";
|
||||||
import { useMediaHoverable } from "hooks/useMediaQuery";
|
import { useMediaHoverable } from "hooks/useMediaQuery";
|
||||||
import { WithLabel } from "components/Inputs/WithLabel";
|
import { WithLabel } from "components/Inputs/WithLabel";
|
||||||
import { filterHasAttributes, isDefined } from "helpers/others";
|
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 Channel = ({ langui, channel, ...otherProps }: Props): JSX.Element => {
|
||||||
const [keepInfoVisible, setKeepInfoVisible] = useState(true);
|
const { state: keepInfoVisible, toggleState: toggleKeepInfoVisible } =
|
||||||
|
useBoolean(true);
|
||||||
const hoverable = useMediaHoverable();
|
const hoverable = useMediaHoverable();
|
||||||
|
|
||||||
const subPanel = useMemo(
|
const subPanel = useMemo(
|
||||||
|
@ -58,13 +60,13 @@ const Channel = ({ langui, channel, ...otherProps }: Props): JSX.Element => {
|
||||||
<WithLabel
|
<WithLabel
|
||||||
label={langui.always_show_info}
|
label={langui.always_show_info}
|
||||||
input={
|
input={
|
||||||
<Switch setState={setKeepInfoVisible} state={keepInfoVisible} />
|
<Switch value={keepInfoVisible} onClick={toggleKeepInfoVisible} />
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</SubPanel>
|
</SubPanel>
|
||||||
),
|
),
|
||||||
[hoverable, keepInfoVisible, langui]
|
[hoverable, keepInfoVisible, langui, toggleKeepInfoVisible]
|
||||||
);
|
);
|
||||||
|
|
||||||
const contentPanel = useMemo(
|
const contentPanel = useMemo(
|
||||||
|
|
|
@ -25,6 +25,7 @@ import { prettyDate } from "helpers/formatters";
|
||||||
import { filterHasAttributes } from "helpers/others";
|
import { filterHasAttributes } from "helpers/others";
|
||||||
import { getVideoThumbnailURL } from "helpers/videos";
|
import { getVideoThumbnailURL } from "helpers/videos";
|
||||||
import { useMediaHoverable } from "hooks/useMediaQuery";
|
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 Videos = ({ langui, videos, ...otherProps }: Props): JSX.Element => {
|
||||||
const hoverable = useMediaHoverable();
|
const hoverable = useMediaHoverable();
|
||||||
const [keepInfoVisible, setKeepInfoVisible] = useState(true);
|
|
||||||
|
const { state: keepInfoVisible, toggleState: toggleKeepInfoVisible } =
|
||||||
|
useBoolean(true);
|
||||||
|
|
||||||
const [searchName, setSearchName] = useState(
|
const [searchName, setSearchName] = useState(
|
||||||
DEFAULT_FILTERS_STATE.searchName
|
DEFAULT_FILTERS_STATE.searchName
|
||||||
);
|
);
|
||||||
|
@ -71,21 +75,21 @@ const Videos = ({ langui, videos, ...otherProps }: Props): JSX.Element => {
|
||||||
<TextInput
|
<TextInput
|
||||||
className="mb-6 w-full"
|
className="mb-6 w-full"
|
||||||
placeholder={langui.search_title ?? undefined}
|
placeholder={langui.search_title ?? undefined}
|
||||||
state={searchName}
|
value={searchName}
|
||||||
setState={setSearchName}
|
onChange={setSearchName}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{hoverable && (
|
{hoverable && (
|
||||||
<WithLabel
|
<WithLabel
|
||||||
label={langui.always_show_info}
|
label={langui.always_show_info}
|
||||||
input={
|
input={
|
||||||
<Switch setState={setKeepInfoVisible} state={keepInfoVisible} />
|
<Switch value={keepInfoVisible} onClick={toggleKeepInfoVisible} />
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</SubPanel>
|
</SubPanel>
|
||||||
),
|
),
|
||||||
[hoverable, keepInfoVisible, langui, searchName]
|
[hoverable, keepInfoVisible, langui, searchName, toggleKeepInfoVisible]
|
||||||
);
|
);
|
||||||
|
|
||||||
const contentPanel = useMemo(
|
const contentPanel = useMemo(
|
||||||
|
|
|
@ -37,7 +37,7 @@ interface Props extends AppStaticProps {
|
||||||
|
|
||||||
const Video = ({ langui, video, ...otherProps }: Props): JSX.Element => {
|
const Video = ({ langui, video, ...otherProps }: Props): JSX.Element => {
|
||||||
const isMobile = useMediaMobile();
|
const isMobile = useMediaMobile();
|
||||||
const appLayout = useAppLayout();
|
const { setSubPanelOpen } = useAppLayout();
|
||||||
const subPanel = useMemo(
|
const subPanel = useMemo(
|
||||||
() => (
|
() => (
|
||||||
<SubPanel>
|
<SubPanel>
|
||||||
|
@ -55,25 +55,25 @@ const Video = ({ langui, video, ...otherProps }: Props): JSX.Element => {
|
||||||
title={langui.video}
|
title={langui.video}
|
||||||
url="#video"
|
url="#video"
|
||||||
border
|
border
|
||||||
onClick={() => appLayout.setSubPanelOpen(false)}
|
onClick={() => setSubPanelOpen(false)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<NavOption
|
<NavOption
|
||||||
title={langui.channel}
|
title={langui.channel}
|
||||||
url="#channel"
|
url="#channel"
|
||||||
border
|
border
|
||||||
onClick={() => appLayout.setSubPanelOpen(false)}
|
onClick={() => setSubPanelOpen(false)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<NavOption
|
<NavOption
|
||||||
title={langui.description}
|
title={langui.description}
|
||||||
url="#description"
|
url="#description"
|
||||||
border
|
border
|
||||||
onClick={() => appLayout.setSubPanelOpen(false)}
|
onClick={() => setSubPanelOpen(false)}
|
||||||
/>
|
/>
|
||||||
</SubPanel>
|
</SubPanel>
|
||||||
),
|
),
|
||||||
[appLayout, langui]
|
[setSubPanelOpen, langui]
|
||||||
);
|
);
|
||||||
|
|
||||||
const contentPanel = useMemo(
|
const contentPanel = useMemo(
|
||||||
|
|
|
@ -206,7 +206,7 @@ const Content = ({
|
||||||
|
|
||||||
{isDefinedAndNotEmpty(selectedTranslation.text_set.notes) && (
|
{isDefinedAndNotEmpty(selectedTranslation.text_set.notes) && (
|
||||||
<div>
|
<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">
|
<div className="grid place-content-center place-items-center gap-2">
|
||||||
<Markdawn text={selectedTranslation.text_set.notes} />
|
<Markdawn text={selectedTranslation.text_set.notes} />
|
||||||
</div>
|
</div>
|
||||||
|
@ -223,7 +223,7 @@ const Content = ({
|
||||||
<p className="font-headers text-2xl font-bold">
|
<p className="font-headers text-2xl font-bold">
|
||||||
{langui.source}
|
{langui.source}
|
||||||
</p>
|
</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, [
|
{filterHasAttributes(content.ranged_contents.data, [
|
||||||
"attributes.library_item.data.attributes",
|
"attributes.library_item.data.attributes",
|
||||||
"attributes.library_item.data.id",
|
"attributes.library_item.data.id",
|
||||||
|
@ -330,7 +330,11 @@ const Content = ({
|
||||||
type={content.type}
|
type={content.type}
|
||||||
categories={content.categories}
|
categories={content.categories}
|
||||||
langui={langui}
|
langui={langui}
|
||||||
languageSwitcher={<LanguageSwitcher {...languageSwitcherProps} />}
|
languageSwitcher={
|
||||||
|
languageSwitcherProps.locales.size > 1 ? (
|
||||||
|
<LanguageSwitcher {...languageSwitcherProps} />
|
||||||
|
) : undefined
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{previousContent?.attributes && (
|
{previousContent?.attributes && (
|
||||||
|
|
|
@ -23,6 +23,7 @@ import { GetContentsQuery } from "graphql/generated";
|
||||||
import { SmartList } from "components/SmartList";
|
import { SmartList } from "components/SmartList";
|
||||||
import { ContentPlaceholder } from "components/PanelComponents/ContentPlaceholder";
|
import { ContentPlaceholder } from "components/PanelComponents/ContentPlaceholder";
|
||||||
import { SelectiveNonNullable } from "helpers/types/SelectiveNonNullable";
|
import { SelectiveNonNullable } from "helpers/types/SelectiveNonNullable";
|
||||||
|
import { useBoolean } from "hooks/useBoolean";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* ╭─────────────╮
|
* ╭─────────────╮
|
||||||
|
@ -56,9 +57,11 @@ const Contents = ({
|
||||||
const [groupingMethod, setGroupingMethod] = useState<number>(
|
const [groupingMethod, setGroupingMethod] = useState<number>(
|
||||||
DEFAULT_FILTERS_STATE.groupingMethod
|
DEFAULT_FILTERS_STATE.groupingMethod
|
||||||
);
|
);
|
||||||
const [keepInfoVisible, setKeepInfoVisible] = useState(
|
const {
|
||||||
DEFAULT_FILTERS_STATE.keepInfoVisible
|
state: keepInfoVisible,
|
||||||
);
|
toggleState: toggleKeepInfoVisible,
|
||||||
|
setState: setKeepInfoVisible,
|
||||||
|
} = useBoolean(DEFAULT_FILTERS_STATE.keepInfoVisible);
|
||||||
const [combineRelatedContent, setCombineRelatedContent] = useState(
|
const [combineRelatedContent, setCombineRelatedContent] = useState(
|
||||||
DEFAULT_FILTERS_STATE.combineRelatedContent
|
DEFAULT_FILTERS_STATE.combineRelatedContent
|
||||||
);
|
);
|
||||||
|
@ -149,8 +152,8 @@ const Contents = ({
|
||||||
<TextInput
|
<TextInput
|
||||||
className="mb-6 w-full"
|
className="mb-6 w-full"
|
||||||
placeholder={langui.search_title ?? undefined}
|
placeholder={langui.search_title ?? undefined}
|
||||||
state={searchName}
|
value={searchName}
|
||||||
setState={setSearchName}
|
onChange={setSearchName}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<WithLabel
|
<WithLabel
|
||||||
|
@ -159,8 +162,8 @@ const Contents = ({
|
||||||
<Select
|
<Select
|
||||||
className="w-full"
|
className="w-full"
|
||||||
options={[langui.category ?? "", langui.type ?? ""]}
|
options={[langui.category ?? "", langui.type ?? ""]}
|
||||||
state={groupingMethod}
|
value={groupingMethod}
|
||||||
setState={setGroupingMethod}
|
onChange={setGroupingMethod}
|
||||||
allowEmpty
|
allowEmpty
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
@ -171,8 +174,8 @@ const Contents = ({
|
||||||
disabled={searchName.length > 1}
|
disabled={searchName.length > 1}
|
||||||
input={
|
input={
|
||||||
<Switch
|
<Switch
|
||||||
setState={setCombineRelatedContent}
|
value={effectiveCombineRelatedContent}
|
||||||
state={effectiveCombineRelatedContent}
|
onClick={toggleKeepInfoVisible}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
@ -181,7 +184,7 @@ const Contents = ({
|
||||||
<WithLabel
|
<WithLabel
|
||||||
label={langui.always_show_info}
|
label={langui.always_show_info}
|
||||||
input={
|
input={
|
||||||
<Switch setState={setKeepInfoVisible} state={keepInfoVisible} />
|
<Switch onClick={toggleKeepInfoVisible} value={keepInfoVisible} />
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
@ -208,6 +211,8 @@ const Contents = ({
|
||||||
keepInfoVisible,
|
keepInfoVisible,
|
||||||
langui,
|
langui,
|
||||||
searchName,
|
searchName,
|
||||||
|
setKeepInfoVisible,
|
||||||
|
toggleKeepInfoVisible,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -54,6 +54,7 @@ import { Ico, Icon } from "components/Ico";
|
||||||
import { cJoin, cIf } from "helpers/className";
|
import { cJoin, cIf } from "helpers/className";
|
||||||
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
||||||
import { getDescription } from "helpers/description";
|
import { getDescription } from "helpers/description";
|
||||||
|
import { useBoolean } from "hooks/useBoolean";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* ╭────────╮
|
* ╭────────╮
|
||||||
|
@ -79,10 +80,11 @@ const LibrarySlug = ({
|
||||||
languages,
|
languages,
|
||||||
...otherProps
|
...otherProps
|
||||||
}: Props): JSX.Element => {
|
}: Props): JSX.Element => {
|
||||||
const appLayout = useAppLayout();
|
const { currency } = useAppLayout();
|
||||||
const hoverable = useMediaHoverable();
|
const hoverable = useMediaHoverable();
|
||||||
const [openLightBox, LightBox] = useLightBox();
|
const [openLightBox, LightBox] = useLightBox();
|
||||||
const [keepInfoVisible, setKeepInfoVisible] = useState(false);
|
const { state: keepInfoVisible, toggleState: toggleKeepInfoVisible } =
|
||||||
|
useBoolean(false);
|
||||||
|
|
||||||
useScrollTopOnChange(AnchorIds.ContentPanel, [item]);
|
useScrollTopOnChange(AnchorIds.ContentPanel, [item]);
|
||||||
|
|
||||||
|
@ -314,14 +316,10 @@ const LibrarySlug = ({
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
{item.price.currency?.data?.attributes?.code !==
|
{item.price.currency?.data?.attributes?.code !==
|
||||||
appLayout.currency && (
|
currency && (
|
||||||
<p>
|
<p>
|
||||||
{prettyPrice(
|
{prettyPrice(item.price, currencies, currency)} <br />(
|
||||||
item.price,
|
{langui.calculated?.toLowerCase()})
|
||||||
currencies,
|
|
||||||
appLayout.currency
|
|
||||||
)}{" "}
|
|
||||||
<br />({langui.calculated?.toLowerCase()})
|
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -450,8 +448,8 @@ const LibrarySlug = ({
|
||||||
label={langui.always_show_info}
|
label={langui.always_show_info}
|
||||||
input={
|
input={
|
||||||
<Switch
|
<Switch
|
||||||
setState={setKeepInfoVisible}
|
onClick={toggleKeepInfoVisible}
|
||||||
state={keepInfoVisible}
|
value={keepInfoVisible}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
@ -572,26 +570,13 @@ const LibrarySlug = ({
|
||||||
[
|
[
|
||||||
LightBox,
|
LightBox,
|
||||||
langui,
|
langui,
|
||||||
item.thumbnail?.data?.attributes,
|
item,
|
||||||
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,
|
|
||||||
itemId,
|
itemId,
|
||||||
currencies,
|
currencies,
|
||||||
appLayout.currency,
|
currency,
|
||||||
isVariantSet,
|
isVariantSet,
|
||||||
hoverable,
|
hoverable,
|
||||||
|
toggleKeepInfoVisible,
|
||||||
keepInfoVisible,
|
keepInfoVisible,
|
||||||
displayOpenScans,
|
displayOpenScans,
|
||||||
openLightBox,
|
openLightBox,
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next";
|
import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next";
|
||||||
import { Fragment, useMemo } from "react";
|
import { Fragment, useMemo } from "react";
|
||||||
import { AppLayout } from "components/AppLayout";
|
import { AppLayout } from "components/AppLayout";
|
||||||
import { ScanSet } from "components/Library/ScanSet";
|
import { TranslatedScanSet } from "components/Library/ScanSet";
|
||||||
import { ScanSetCover } from "components/Library/ScanSetCover";
|
import { ScanSetCover } from "components/Library/ScanSetCover";
|
||||||
import { NavOption } from "components/PanelComponents/NavOption";
|
import { TranslatedNavOption } from "components/PanelComponents/NavOption";
|
||||||
import {
|
import {
|
||||||
ReturnButton,
|
ReturnButton,
|
||||||
ReturnButtonType,
|
ReturnButtonType,
|
||||||
|
@ -16,13 +16,21 @@ import { SubPanel } from "components/Panels/SubPanel";
|
||||||
import { GetLibraryItemScansQuery } from "graphql/generated";
|
import { GetLibraryItemScansQuery } from "graphql/generated";
|
||||||
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
|
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
|
||||||
import { getReadySdk } from "graphql/sdk";
|
import { getReadySdk } from "graphql/sdk";
|
||||||
import { prettyinlineTitle, prettySlug } from "helpers/formatters";
|
import {
|
||||||
|
prettyinlineTitle,
|
||||||
|
prettySlug,
|
||||||
|
prettyItemSubType,
|
||||||
|
} from "helpers/formatters";
|
||||||
import {
|
import {
|
||||||
filterHasAttributes,
|
filterHasAttributes,
|
||||||
isDefined,
|
isDefined,
|
||||||
sortRangedContent,
|
sortRangedContent,
|
||||||
} from "helpers/others";
|
} from "helpers/others";
|
||||||
import { useLightBox } from "hooks/useLightBox";
|
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 = ({
|
const LibrarySlug = ({
|
||||||
item,
|
item,
|
||||||
|
itemId,
|
||||||
langui,
|
langui,
|
||||||
languages,
|
languages,
|
||||||
|
currencies,
|
||||||
...otherProps
|
...otherProps
|
||||||
}: Props): JSX.Element => {
|
}: Props): JSX.Element => {
|
||||||
const [openLightBox, LightBox] = useLightBox();
|
const [openLightBox, LightBox] = useLightBox();
|
||||||
|
@ -55,29 +65,109 @@ const LibrarySlug = ({
|
||||||
href={`/library/${item.slug}`}
|
href={`/library/${item.slug}`}
|
||||||
title={langui.item}
|
title={langui.item}
|
||||||
langui={langui}
|
langui={langui}
|
||||||
|
className="mb-4"
|
||||||
displayOn={ReturnButtonType.Desktop}
|
displayOn={ReturnButtonType.Desktop}
|
||||||
horizontalLine
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{item.contents?.data.map((content) => (
|
<div className="mobile:w-[80%]">
|
||||||
<NavOption
|
<PreviewCard
|
||||||
key={content.id}
|
href={`/library/${item.slug}`}
|
||||||
url={`#${content.attributes?.slug}`}
|
title={item.title}
|
||||||
title={prettySlug(content.attributes?.slug, item.slug)}
|
subtitle={item.subtitle}
|
||||||
subtitle={
|
thumbnail={item.thumbnail?.data?.attributes}
|
||||||
content.attributes?.range[0]?.__typename ===
|
thumbnailAspectRatio="21/29.7"
|
||||||
"ComponentRangePageRange"
|
thumbnailRounded={false}
|
||||||
? `${content.attributes.range[0].starting_page}` +
|
topChips={
|
||||||
`→` +
|
item.metadata && item.metadata.length > 0 && item.metadata[0]
|
||||||
`${content.attributes.range[0].ending_page}`
|
? [prettyItemSubType(item.metadata[0])]
|
||||||
: undefined
|
: []
|
||||||
|
}
|
||||||
|
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>
|
</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(
|
const contentPanel = useMemo(
|
||||||
|
@ -105,11 +195,22 @@ const LibrarySlug = ({
|
||||||
{item.contents?.data.map((content) => (
|
{item.contents?.data.map((content) => (
|
||||||
<Fragment key={content.id}>
|
<Fragment key={content.id}>
|
||||||
{content.attributes?.scan_set?.[0] && (
|
{content.attributes?.scan_set?.[0] && (
|
||||||
<ScanSet
|
<TranslatedScanSet
|
||||||
scanSet={content.attributes.scan_set}
|
scanSet={content.attributes.scan_set}
|
||||||
openLightBox={openLightBox}
|
openLightBox={openLightBox}
|
||||||
slug={content.attributes.slug}
|
id={content.attributes.slug}
|
||||||
title={prettySlug(content.attributes.slug, item.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}
|
languages={languages}
|
||||||
langui={langui}
|
langui={langui}
|
||||||
content={content.attributes.content}
|
content={content.attributes.content}
|
||||||
|
@ -138,6 +239,7 @@ const LibrarySlug = ({
|
||||||
thumbnail={item.thumbnail?.data?.attributes ?? undefined}
|
thumbnail={item.thumbnail?.data?.attributes ?? undefined}
|
||||||
languages={languages}
|
languages={languages}
|
||||||
langui={langui}
|
langui={langui}
|
||||||
|
currencies={currencies}
|
||||||
{...otherProps}
|
{...otherProps}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
@ -33,6 +33,7 @@ import { useAppLayout } from "contexts/AppLayoutContext";
|
||||||
import { convertPrice } from "helpers/numbers";
|
import { convertPrice } from "helpers/numbers";
|
||||||
import { SmartList } from "components/SmartList";
|
import { SmartList } from "components/SmartList";
|
||||||
import { SelectiveNonNullable } from "helpers/types/SelectiveNonNullable";
|
import { SelectiveNonNullable } from "helpers/types/SelectiveNonNullable";
|
||||||
|
import { useBoolean } from "hooks/useBoolean";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* ╭─────────────╮
|
* ╭─────────────╮
|
||||||
|
@ -66,29 +67,44 @@ const Library = ({
|
||||||
...otherProps
|
...otherProps
|
||||||
}: Props): JSX.Element => {
|
}: Props): JSX.Element => {
|
||||||
const hoverable = useMediaHoverable();
|
const hoverable = useMediaHoverable();
|
||||||
const appLayout = useAppLayout();
|
const { libraryItemUserStatus } = useAppLayout();
|
||||||
|
|
||||||
const [searchName, setSearchName] = useState(
|
const [searchName, setSearchName] = useState(
|
||||||
DEFAULT_FILTERS_STATE.searchName
|
DEFAULT_FILTERS_STATE.searchName
|
||||||
);
|
);
|
||||||
const [showSubitems, setShowSubitems] = useState<boolean>(
|
|
||||||
DEFAULT_FILTERS_STATE.showSubitems
|
const {
|
||||||
);
|
state: showSubitems,
|
||||||
const [showPrimaryItems, setShowPrimaryItems] = useState<boolean>(
|
toggleState: toggleShowSubitems,
|
||||||
DEFAULT_FILTERS_STATE.showPrimaryItems
|
setState: setShowSubitems,
|
||||||
);
|
} = useBoolean(DEFAULT_FILTERS_STATE.showSubitems);
|
||||||
const [showSecondaryItems, setShowSecondaryItems] = useState<boolean>(
|
|
||||||
DEFAULT_FILTERS_STATE.showSecondaryItems
|
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>(
|
const [sortingMethod, setSortingMethod] = useState<number>(
|
||||||
DEFAULT_FILTERS_STATE.sortingMethod
|
DEFAULT_FILTERS_STATE.sortingMethod
|
||||||
);
|
);
|
||||||
|
|
||||||
const [groupingMethod, setGroupingMethod] = useState<number>(
|
const [groupingMethod, setGroupingMethod] = useState<number>(
|
||||||
DEFAULT_FILTERS_STATE.groupingMethod
|
DEFAULT_FILTERS_STATE.groupingMethod
|
||||||
);
|
);
|
||||||
const [keepInfoVisible, setKeepInfoVisible] = useState(
|
|
||||||
DEFAULT_FILTERS_STATE.keepInfoVisible
|
|
||||||
);
|
|
||||||
const [filterUserStatus, setFilterUserStatus] = useState<
|
const [filterUserStatus, setFilterUserStatus] = useState<
|
||||||
LibraryItemUserStatus | undefined
|
LibraryItemUserStatus | undefined
|
||||||
>(DEFAULT_FILTERS_STATE.filterUserStatus);
|
>(DEFAULT_FILTERS_STATE.filterUserStatus);
|
||||||
|
@ -107,28 +123,22 @@ const Library = ({
|
||||||
if (item.attributes.primary && !showPrimaryItems) return false;
|
if (item.attributes.primary && !showPrimaryItems) return false;
|
||||||
if (!item.attributes.primary && !showSecondaryItems) return false;
|
if (!item.attributes.primary && !showSecondaryItems) return false;
|
||||||
|
|
||||||
if (
|
if (isDefined(filterUserStatus) && item.id && libraryItemUserStatus) {
|
||||||
isDefined(filterUserStatus) &&
|
|
||||||
item.id &&
|
|
||||||
appLayout.libraryItemUserStatus
|
|
||||||
) {
|
|
||||||
if (isUntangibleGroupItem(item.attributes.metadata?.[0])) {
|
if (isUntangibleGroupItem(item.attributes.metadata?.[0])) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (filterUserStatus === LibraryItemUserStatus.None) {
|
if (filterUserStatus === LibraryItemUserStatus.None) {
|
||||||
if (appLayout.libraryItemUserStatus[item.id]) {
|
if (libraryItemUserStatus[item.id]) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else if (
|
} else if (filterUserStatus !== libraryItemUserStatus[item.id]) {
|
||||||
filterUserStatus !== appLayout.libraryItemUserStatus[item.id]
|
|
||||||
) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
appLayout.libraryItemUserStatus,
|
libraryItemUserStatus,
|
||||||
filterUserStatus,
|
filterUserStatus,
|
||||||
showPrimaryItems,
|
showPrimaryItems,
|
||||||
showSecondaryItems,
|
showSecondaryItems,
|
||||||
|
@ -260,8 +270,8 @@ const Library = ({
|
||||||
<TextInput
|
<TextInput
|
||||||
className="mb-6 w-full"
|
className="mb-6 w-full"
|
||||||
placeholder={langui.search_title ?? undefined}
|
placeholder={langui.search_title ?? undefined}
|
||||||
state={searchName}
|
value={searchName}
|
||||||
setState={setSearchName}
|
onChange={setSearchName}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<WithLabel
|
<WithLabel
|
||||||
|
@ -274,8 +284,8 @@ const Library = ({
|
||||||
langui.type ?? "Type",
|
langui.type ?? "Type",
|
||||||
langui.release_year ?? "Year",
|
langui.release_year ?? "Year",
|
||||||
]}
|
]}
|
||||||
state={groupingMethod}
|
value={groupingMethod}
|
||||||
setState={setGroupingMethod}
|
onChange={setGroupingMethod}
|
||||||
allowEmpty
|
allowEmpty
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
@ -291,21 +301,21 @@ const Library = ({
|
||||||
langui.price ?? "Price",
|
langui.price ?? "Price",
|
||||||
langui.release_date ?? "Release date",
|
langui.release_date ?? "Release date",
|
||||||
]}
|
]}
|
||||||
state={sortingMethod}
|
value={sortingMethod}
|
||||||
setState={setSortingMethod}
|
onChange={setSortingMethod}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<WithLabel
|
<WithLabel
|
||||||
label={langui.show_subitems}
|
label={langui.show_subitems}
|
||||||
input={<Switch state={showSubitems} setState={setShowSubitems} />}
|
input={<Switch value={showSubitems} onClick={toggleShowSubitems} />}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<WithLabel
|
<WithLabel
|
||||||
label={langui.show_primary_items}
|
label={langui.show_primary_items}
|
||||||
input={
|
input={
|
||||||
<Switch state={showPrimaryItems} setState={setShowPrimaryItems} />
|
<Switch value={showPrimaryItems} onClick={toggleShowPrimaryItems} />
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
@ -313,8 +323,8 @@ const Library = ({
|
||||||
label={langui.show_secondary_items}
|
label={langui.show_secondary_items}
|
||||||
input={
|
input={
|
||||||
<Switch
|
<Switch
|
||||||
state={showSecondaryItems}
|
value={showSecondaryItems}
|
||||||
setState={setShowSecondaryItems}
|
onClick={toggleShowSecondaryItems}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
@ -323,7 +333,7 @@ const Library = ({
|
||||||
<WithLabel
|
<WithLabel
|
||||||
label={langui.always_show_info}
|
label={langui.always_show_info}
|
||||||
input={
|
input={
|
||||||
<Switch state={keepInfoVisible} setState={setKeepInfoVisible} />
|
<Switch value={keepInfoVisible} onClick={toggleKeepInfoVisible} />
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
@ -382,10 +392,18 @@ const Library = ({
|
||||||
keepInfoVisible,
|
keepInfoVisible,
|
||||||
langui,
|
langui,
|
||||||
searchName,
|
searchName,
|
||||||
|
setKeepInfoVisible,
|
||||||
|
setShowPrimaryItems,
|
||||||
|
setShowSecondaryItems,
|
||||||
|
setShowSubitems,
|
||||||
showPrimaryItems,
|
showPrimaryItems,
|
||||||
showSecondaryItems,
|
showSecondaryItems,
|
||||||
showSubitems,
|
showSubitems,
|
||||||
sortingMethod,
|
sortingMethod,
|
||||||
|
toggleKeepInfoVisible,
|
||||||
|
toggleShowPrimaryItems,
|
||||||
|
toggleShowSecondaryItems,
|
||||||
|
toggleShowSubitems,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@ import { Button } from "components/Inputs/Button";
|
||||||
import { useMediaHoverable } from "hooks/useMediaQuery";
|
import { useMediaHoverable } from "hooks/useMediaQuery";
|
||||||
import { filterDefined, filterHasAttributes } from "helpers/others";
|
import { filterDefined, filterHasAttributes } from "helpers/others";
|
||||||
import { SmartList } from "components/SmartList";
|
import { SmartList } from "components/SmartList";
|
||||||
|
import { useBoolean } from "hooks/useBoolean";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* ╭─────────────╮
|
* ╭─────────────╮
|
||||||
|
@ -50,9 +51,11 @@ const News = ({
|
||||||
const [searchName, setSearchName] = useState(
|
const [searchName, setSearchName] = useState(
|
||||||
DEFAULT_FILTERS_STATE.searchName
|
DEFAULT_FILTERS_STATE.searchName
|
||||||
);
|
);
|
||||||
const [keepInfoVisible, setKeepInfoVisible] = useState(
|
const {
|
||||||
DEFAULT_FILTERS_STATE.keepInfoVisible
|
state: keepInfoVisible,
|
||||||
);
|
toggleState: toggleKeepInfoVisible,
|
||||||
|
setState: setKeepInfoVisible,
|
||||||
|
} = useBoolean(DEFAULT_FILTERS_STATE.keepInfoVisible);
|
||||||
|
|
||||||
const subPanel = useMemo(
|
const subPanel = useMemo(
|
||||||
() => (
|
() => (
|
||||||
|
@ -66,15 +69,15 @@ const News = ({
|
||||||
<TextInput
|
<TextInput
|
||||||
className="mb-6 w-full"
|
className="mb-6 w-full"
|
||||||
placeholder={langui.search_title ?? undefined}
|
placeholder={langui.search_title ?? undefined}
|
||||||
state={searchName}
|
value={searchName}
|
||||||
setState={setSearchName}
|
onChange={setSearchName}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{hoverable && (
|
{hoverable && (
|
||||||
<WithLabel
|
<WithLabel
|
||||||
label={langui.always_show_info}
|
label={langui.always_show_info}
|
||||||
input={
|
input={
|
||||||
<Switch setState={setKeepInfoVisible} state={keepInfoVisible} />
|
<Switch onClick={toggleKeepInfoVisible} value={keepInfoVisible} />
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
@ -90,7 +93,14 @@ const News = ({
|
||||||
/>
|
/>
|
||||||
</SubPanel>
|
</SubPanel>
|
||||||
),
|
),
|
||||||
[hoverable, keepInfoVisible, langui, searchName]
|
[
|
||||||
|
hoverable,
|
||||||
|
keepInfoVisible,
|
||||||
|
langui,
|
||||||
|
searchName,
|
||||||
|
setKeepInfoVisible,
|
||||||
|
toggleKeepInfoVisible,
|
||||||
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
const contentPanel = useMemo(
|
const contentPanel = useMemo(
|
||||||
|
|
|
@ -139,11 +139,10 @@ const WikiPage = ({
|
||||||
{page.tags?.data && page.tags.data.length > 0 && (
|
{page.tags?.data && page.tags.data.length > 0 && (
|
||||||
<>
|
<>
|
||||||
<p className="font-headers text-xl font-bold">
|
<p className="font-headers text-xl font-bold">
|
||||||
{/* TODO: Add Tags to langui */}
|
{langui.tags}
|
||||||
{"Tags"}
|
|
||||||
</p>
|
</p>
|
||||||
<div className="flex flex-row flex-wrap place-content-center gap-2">
|
<div className="flex flex-row flex-wrap place-content-center gap-2">
|
||||||
{filterHasAttributes(page.tags?.data, [
|
{filterHasAttributes(page.tags.data, [
|
||||||
"attributes",
|
"attributes",
|
||||||
] as const).map((tag) => (
|
] as const).map((tag) => (
|
||||||
<Chip
|
<Chip
|
||||||
|
@ -203,9 +202,11 @@ const WikiPage = ({
|
||||||
),
|
),
|
||||||
[
|
[
|
||||||
LanguageSwitcher,
|
LanguageSwitcher,
|
||||||
|
LightBox,
|
||||||
languageSwitcherProps,
|
languageSwitcherProps,
|
||||||
languages,
|
languages,
|
||||||
langui,
|
langui,
|
||||||
|
openLightBox,
|
||||||
page,
|
page,
|
||||||
selectedTranslation,
|
selectedTranslation,
|
||||||
]
|
]
|
||||||
|
|
|
@ -25,6 +25,7 @@ import { SmartList } from "components/SmartList";
|
||||||
import { Select } from "components/Inputs/Select";
|
import { Select } from "components/Inputs/Select";
|
||||||
import { SelectiveNonNullable } from "helpers/types/SelectiveNonNullable";
|
import { SelectiveNonNullable } from "helpers/types/SelectiveNonNullable";
|
||||||
import { prettySlug } from "helpers/formatters";
|
import { prettySlug } from "helpers/formatters";
|
||||||
|
import { useBoolean } from "hooks/useBoolean";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* ╭─────────────╮
|
* ╭─────────────╮
|
||||||
|
@ -62,9 +63,11 @@ const Wiki = ({
|
||||||
DEFAULT_FILTERS_STATE.groupingMethod
|
DEFAULT_FILTERS_STATE.groupingMethod
|
||||||
);
|
);
|
||||||
|
|
||||||
const [keepInfoVisible, setKeepInfoVisible] = useState(
|
const {
|
||||||
DEFAULT_FILTERS_STATE.keepInfoVisible
|
state: keepInfoVisible,
|
||||||
);
|
toggleState: toggleKeepInfoVisible,
|
||||||
|
setState: setKeepInfoVisible,
|
||||||
|
} = useBoolean(DEFAULT_FILTERS_STATE.keepInfoVisible);
|
||||||
|
|
||||||
const subPanel = useMemo(
|
const subPanel = useMemo(
|
||||||
() => (
|
() => (
|
||||||
|
@ -78,8 +81,8 @@ const Wiki = ({
|
||||||
<TextInput
|
<TextInput
|
||||||
className="mb-6 w-full"
|
className="mb-6 w-full"
|
||||||
placeholder={langui.search_title ?? undefined}
|
placeholder={langui.search_title ?? undefined}
|
||||||
state={searchName}
|
value={searchName}
|
||||||
setState={setSearchName}
|
onChange={setSearchName}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<WithLabel
|
<WithLabel
|
||||||
|
@ -88,8 +91,8 @@ const Wiki = ({
|
||||||
<Select
|
<Select
|
||||||
className="w-full"
|
className="w-full"
|
||||||
options={[langui.category ?? ""]}
|
options={[langui.category ?? ""]}
|
||||||
state={groupingMethod}
|
value={groupingMethod}
|
||||||
setState={setGroupingMethod}
|
onChange={setGroupingMethod}
|
||||||
allowEmpty
|
allowEmpty
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
@ -99,7 +102,7 @@ const Wiki = ({
|
||||||
<WithLabel
|
<WithLabel
|
||||||
label={langui.always_show_info}
|
label={langui.always_show_info}
|
||||||
input={
|
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 />
|
<NavOption title={langui.chronology} url="/wiki/chronology" border />
|
||||||
</SubPanel>
|
</SubPanel>
|
||||||
),
|
),
|
||||||
[groupingMethod, hoverable, keepInfoVisible, langui, searchName]
|
[
|
||||||
|
groupingMethod,
|
||||||
|
hoverable,
|
||||||
|
keepInfoVisible,
|
||||||
|
langui,
|
||||||
|
searchName,
|
||||||
|
setKeepInfoVisible,
|
||||||
|
toggleKeepInfoVisible,
|
||||||
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
const groupingFunction = useCallback(
|
const groupingFunction = useCallback(
|
||||||
|
|
Loading…
Reference in New Issue