Lots of bs
This commit is contained in:
parent
5a963294b7
commit
625f436163
|
@ -77,4 +77,9 @@ interface ComponentProps {}
|
|||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||
|
||||
export const Component = () => {};
|
||||
|
||||
/*
|
||||
* ╭──────────────────────╮
|
||||
* ───────────────────────────────────╯ TRANSLATED VARIANT ╰──────────────────────────────────────
|
||||
*/
|
||||
```
|
||||
|
|
|
@ -4,7 +4,7 @@ const locales = ["en", "es", "fr", "pt-br", "ja"];
|
|||
|
||||
/* END CONFIG */
|
||||
|
||||
/** @type {import('next').NextConfig} */
|
||||
/* @type {import('next').NextConfig} */
|
||||
module.exports = {
|
||||
swcMinify: true,
|
||||
reactStrictMode: true,
|
||||
|
@ -15,9 +15,6 @@ module.exports = {
|
|||
images: {
|
||||
domains: ["img.accords-library.com", "watch.accords-library.com"],
|
||||
},
|
||||
serverRuntimeConfig: {
|
||||
locales: locales,
|
||||
},
|
||||
async redirects() {
|
||||
return [
|
||||
{
|
||||
|
|
|
@ -3,12 +3,12 @@ import { useRouter } from "next/router";
|
|||
import { useEffect, useLayoutEffect, useMemo, useState } from "react";
|
||||
import { useSwipeable } from "react-swipeable";
|
||||
import UAParser from "ua-parser-js";
|
||||
import { useBoolean, useIsClient } from "usehooks-ts";
|
||||
import { Ico, Icon } from "./Ico";
|
||||
import { ButtonGroup } from "./Inputs/ButtonGroup";
|
||||
import { OrderableList } from "./Inputs/OrderableList";
|
||||
import { Select } from "./Inputs/Select";
|
||||
import { TextInput } from "./Inputs/TextInput";
|
||||
import { ContentPlaceholder } from "./PanelComponents/ContentPlaceholder";
|
||||
import { MainPanel } from "./Panels/MainPanel";
|
||||
import { Popup } from "./Popup";
|
||||
import { AnchorIds } from "hooks/useScrollTopOnChange";
|
||||
|
@ -27,8 +27,6 @@ import { useAppLayout } from "contexts/AppLayoutContext";
|
|||
import { Button } from "components/Inputs/Button";
|
||||
import { OpenGraph, TITLE_PREFIX } from "helpers/openGraph";
|
||||
import { getDefaultPreferredLanguages } from "helpers/locales";
|
||||
import useIsClient from "hooks/useIsClient";
|
||||
import { useBoolean } from "hooks/useBoolean";
|
||||
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
|
@ -195,7 +193,7 @@ export const AppLayout = ({
|
|||
}, [mainPanelReduced, subPanel]);
|
||||
|
||||
const isClient = useIsClient();
|
||||
const { state: hasDisgardSafariWarning, setTrue: disgardSafariWarning } =
|
||||
const { value: hasDisgardSafariWarning, setTrue: disgardSafariWarning } =
|
||||
useBoolean(false);
|
||||
const isSafari = useMemo<boolean>(() => {
|
||||
if (isClient) {
|
||||
|
@ -548,3 +546,29 @@ export const AppLayout = ({
|
|||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||
|
||||
interface ContentPlaceholderProps {
|
||||
message: string;
|
||||
icon?: Icon;
|
||||
}
|
||||
|
||||
const ContentPlaceholder = ({
|
||||
message,
|
||||
icon,
|
||||
}: ContentPlaceholderProps): JSX.Element => (
|
||||
<div className="grid h-full place-content-center">
|
||||
<div
|
||||
className="grid grid-flow-col place-items-center gap-9 rounded-2xl border-2 border-dotted
|
||||
border-dark p-8 text-dark opacity-40"
|
||||
>
|
||||
{isDefined(icon) && <Ico icon={icon} className="!text-[300%]" />}
|
||||
<p
|
||||
className={cJoin("w-64 text-2xl", cIf(!isDefined(icon), "text-center"))}
|
||||
>
|
||||
{message}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
import { useCallback } from "react";
|
||||
import { Link } from "components/Inputs/Link";
|
||||
import { DatePickerFragment } from "graphql/generated";
|
||||
import { cIf, cJoin } from "helpers/className";
|
||||
import { TranslatedProps } from "helpers/types/TranslatedProps";
|
||||
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
||||
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
|
@ -14,7 +17,7 @@ interface Props {
|
|||
isActive?: boolean;
|
||||
}
|
||||
|
||||
export const ChroniclePreview = ({
|
||||
const ChroniclePreview = ({
|
||||
date,
|
||||
url,
|
||||
title,
|
||||
|
@ -40,6 +43,35 @@ export const ChroniclePreview = ({
|
|||
</Link>
|
||||
);
|
||||
|
||||
/*
|
||||
* ╭──────────────────────╮
|
||||
* ───────────────────────────────────╯ TRANSLATED VARIANT ╰──────────────────────────────────────
|
||||
*/
|
||||
|
||||
export const TranslatedChroniclePreview = ({
|
||||
translations,
|
||||
fallback,
|
||||
...otherProps
|
||||
}: TranslatedProps<
|
||||
Parameters<typeof ChroniclePreview>[0],
|
||||
"title"
|
||||
>): JSX.Element => {
|
||||
const [selectedTranslation] = useSmartLanguage({
|
||||
items: translations,
|
||||
languageExtractor: useCallback(
|
||||
(item: { language: string }): string => item.language,
|
||||
[]
|
||||
),
|
||||
});
|
||||
|
||||
return (
|
||||
<ChroniclePreview
|
||||
title={selectedTranslation?.title ?? fallback.title}
|
||||
{...otherProps}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
/*
|
||||
* ╭───────────────────╮
|
||||
* ─────────────────────────────────────╯ PRIVATE METHODS ╰───────────────────────────────────────
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
import { useCallback } from "react";
|
||||
import { useBoolean } from "usehooks-ts";
|
||||
import { TranslatedChroniclePreview } from "./ChroniclePreview";
|
||||
import { GetChroniclesChaptersQuery } from "graphql/generated";
|
||||
import { filterHasAttributes } from "helpers/others";
|
||||
import { TranslatedChroniclePreview } from "components/Translated";
|
||||
import { prettyInlineTitle, prettySlug } from "helpers/formatters";
|
||||
import { Ico, Icon } from "components/Ico";
|
||||
import { useBoolean } from "hooks/useBoolean";
|
||||
import { compareDate } from "helpers/date";
|
||||
import { TranslatedProps } from "helpers/types/TranslatedProps";
|
||||
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
||||
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
|
@ -23,12 +26,12 @@ interface Props {
|
|||
title: string;
|
||||
}
|
||||
|
||||
export const ChroniclesList = ({
|
||||
const ChroniclesList = ({
|
||||
chronicles,
|
||||
currentSlug,
|
||||
title,
|
||||
}: Props): JSX.Element => {
|
||||
const { state: isOpen, toggleState: toggleOpen } = useBoolean(
|
||||
const { value: isOpen, toggle: toggleOpen } = useBoolean(
|
||||
chronicles.some((chronicle) => chronicle.attributes?.slug === currentSlug)
|
||||
);
|
||||
|
||||
|
@ -112,3 +115,29 @@ export const ChroniclesList = ({
|
|||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
/*
|
||||
* ╭──────────────────────╮
|
||||
* ───────────────────────────────────╯ TRANSLATED VARIANT ╰──────────────────────────────────────
|
||||
*/
|
||||
|
||||
export const TranslatedChroniclesList = ({
|
||||
translations,
|
||||
fallback,
|
||||
...otherProps
|
||||
}: TranslatedProps<Props, "title">): JSX.Element => {
|
||||
const [selectedTranslation] = useSmartLanguage({
|
||||
items: translations,
|
||||
languageExtractor: useCallback(
|
||||
(item: { language: string }): string => item.language,
|
||||
[]
|
||||
),
|
||||
});
|
||||
|
||||
return (
|
||||
<ChroniclesList
|
||||
title={selectedTranslation?.title ?? fallback.title}
|
||||
{...otherProps}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
import React, { MouseEventHandler } from "react";
|
||||
import React, { MouseEventHandler, useCallback } from "react";
|
||||
import { Link } from "./Link";
|
||||
import { Ico, Icon } from "components/Ico";
|
||||
import { cIf, cJoin } from "helpers/className";
|
||||
import { ConditionalWrapper, Wrapper } from "helpers/component";
|
||||
import { isDefined, isDefinedAndNotEmpty } from "helpers/others";
|
||||
import { TranslatedProps } from "helpers/types/TranslatedProps";
|
||||
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
||||
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
|
@ -89,6 +91,29 @@ export const Button = ({
|
|||
</ConditionalWrapper>
|
||||
);
|
||||
|
||||
/*
|
||||
* ╭──────────────────────╮
|
||||
* ───────────────────────────────────╯ TRANSLATED VARIANT ╰──────────────────────────────────────
|
||||
*/
|
||||
|
||||
export const TranslatedButton = ({
|
||||
translations,
|
||||
fallback,
|
||||
...otherProps
|
||||
}: TranslatedProps<Props, "text">): JSX.Element => {
|
||||
const [selectedTranslation] = useSmartLanguage({
|
||||
items: translations,
|
||||
languageExtractor: useCallback(
|
||||
(item: { language: string }): string => item.language,
|
||||
[]
|
||||
),
|
||||
});
|
||||
|
||||
return (
|
||||
<Button text={selectedTranslation?.text ?? fallback.text} {...otherProps} />
|
||||
);
|
||||
};
|
||||
|
||||
/*
|
||||
* ╭──────────────────────╮
|
||||
* ───────────────────────────────────╯ PRIVATE COMPONENTS ╰──────────────────────────────────────
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { Fragment, useCallback } from "react";
|
||||
import { useBoolean } from "usehooks-ts";
|
||||
import { Ico, Icon } from "components/Ico";
|
||||
import { cIf, cJoin } from "helpers/className";
|
||||
import { useBoolean } from "hooks/useBoolean";
|
||||
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
|
@ -27,9 +27,9 @@ export const Select = ({
|
|||
onChange,
|
||||
}: Props): JSX.Element => {
|
||||
const {
|
||||
state: isOpened,
|
||||
value: isOpened,
|
||||
setFalse: setClosed,
|
||||
toggleState: toggleOpened,
|
||||
toggle: toggleOpened,
|
||||
} = useBoolean(false);
|
||||
|
||||
const tryToggling = useCallback(() => {
|
||||
|
@ -39,6 +39,9 @@ export const Select = ({
|
|||
|
||||
return (
|
||||
<div
|
||||
onClickCapture={() => {
|
||||
setClosed();
|
||||
}}
|
||||
className={cJoin(
|
||||
"relative text-center transition-[filter]",
|
||||
cIf(isOpened, "z-10 drop-shadow-shade-lg"),
|
||||
|
|
|
@ -8,13 +8,17 @@ import { isDefinedAndNotEmpty } from "helpers/others";
|
|||
|
||||
interface Props {
|
||||
label: string | null | undefined;
|
||||
input: JSX.Element;
|
||||
disabled?: boolean;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||
|
||||
export const WithLabel = ({ label, input, disabled }: Props): JSX.Element => (
|
||||
export const WithLabel = ({
|
||||
label,
|
||||
children,
|
||||
disabled,
|
||||
}: Props): JSX.Element => (
|
||||
<div
|
||||
className={cJoin(
|
||||
"flex flex-row place-content-between place-items-center gap-2",
|
||||
|
@ -28,6 +32,6 @@ export const WithLabel = ({ label, input, disabled }: Props): JSX.Element => (
|
|||
{label}:
|
||||
</p>
|
||||
)}
|
||||
{input}
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -39,7 +39,7 @@ export const PreviewCardCTAs = ({
|
|||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
setLibraryItemUserStatus((current) => {
|
||||
const newLibraryItemUserStatus = current ? { ...current } : {};
|
||||
const newLibraryItemUserStatus = { ...current };
|
||||
newLibraryItemUserStatus[id] =
|
||||
newLibraryItemUserStatus[id] === LibraryItemUserStatus.Want
|
||||
? LibraryItemUserStatus.None
|
||||
|
@ -57,7 +57,7 @@ export const PreviewCardCTAs = ({
|
|||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
setLibraryItemUserStatus((current) => {
|
||||
const newLibraryItemUserStatus = current ? { ...current } : {};
|
||||
const newLibraryItemUserStatus = { ...current };
|
||||
newLibraryItemUserStatus[id] =
|
||||
newLibraryItemUserStatus[id] === LibraryItemUserStatus.Have
|
||||
? LibraryItemUserStatus.None
|
||||
|
|
|
@ -1,246 +0,0 @@
|
|||
import { Fragment, useCallback, useMemo } from "react";
|
||||
import { Chip } from "components/Chip";
|
||||
import { Img } from "components/Img";
|
||||
import { Button } from "components/Inputs/Button";
|
||||
import { RecorderChip } from "components/RecorderChip";
|
||||
import { ToolTip } from "components/ToolTip";
|
||||
import { GetLibraryItemScansQuery } from "graphql/generated";
|
||||
import { AppStaticProps } from "graphql/getAppStaticProps";
|
||||
import { getAssetFilename, getAssetURL, ImageQuality } from "helpers/img";
|
||||
import { isInteger } from "helpers/numbers";
|
||||
import {
|
||||
filterHasAttributes,
|
||||
getStatusDescription,
|
||||
isDefined,
|
||||
isDefinedAndNotEmpty,
|
||||
} from "helpers/others";
|
||||
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
||||
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
* ───────────────────────────────────────╯ COMPONENT ╰───────────────────────────────────────────
|
||||
*/
|
||||
|
||||
interface Props {
|
||||
openLightBox: (images: string[], index?: number) => void;
|
||||
scanSet: NonNullable<
|
||||
NonNullable<
|
||||
NonNullable<
|
||||
NonNullable<
|
||||
NonNullable<
|
||||
GetLibraryItemScansQuery["libraryItems"]
|
||||
>["data"][number]["attributes"]
|
||||
>["contents"]
|
||||
>["data"][number]["attributes"]
|
||||
>["scan_set"]
|
||||
>;
|
||||
id: string;
|
||||
title: string;
|
||||
languages: AppStaticProps["languages"];
|
||||
langui: AppStaticProps["langui"];
|
||||
content: NonNullable<
|
||||
NonNullable<
|
||||
NonNullable<
|
||||
NonNullable<
|
||||
GetLibraryItemScansQuery["libraryItems"]
|
||||
>["data"][number]["attributes"]
|
||||
>["contents"]
|
||||
>["data"][number]["attributes"]
|
||||
>["content"];
|
||||
}
|
||||
|
||||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||
|
||||
export const ScanSet = ({
|
||||
openLightBox,
|
||||
scanSet,
|
||||
id,
|
||||
title,
|
||||
languages,
|
||||
langui,
|
||||
content,
|
||||
}: Props): JSX.Element => {
|
||||
const [selectedScan, LanguageSwitcher, languageSwitcherProps] =
|
||||
useSmartLanguage({
|
||||
items: scanSet,
|
||||
languages: languages,
|
||||
languageExtractor: useCallback(
|
||||
(item: NonNullable<Props["scanSet"][number]>) =>
|
||||
item.language?.data?.attributes?.code,
|
||||
[]
|
||||
),
|
||||
transform: useCallback((item: NonNullable<Props["scanSet"][number]>) => {
|
||||
item.pages?.data.sort((a, b) => {
|
||||
if (
|
||||
a.attributes &&
|
||||
b.attributes &&
|
||||
isDefinedAndNotEmpty(a.attributes.url) &&
|
||||
isDefinedAndNotEmpty(b.attributes.url)
|
||||
) {
|
||||
let aName = getAssetFilename(a.attributes.url);
|
||||
let bName = getAssetFilename(b.attributes.url);
|
||||
|
||||
/*
|
||||
* If the number is a succession of 0s, make the number
|
||||
* incrementally smaller than 0 (i.e: 00 becomes -1)
|
||||
*/
|
||||
if (aName.replaceAll("0", "").length === 0) {
|
||||
aName = (1 - aName.length).toString(10);
|
||||
}
|
||||
if (bName.replaceAll("0", "").length === 0) {
|
||||
bName = (1 - bName.length).toString(10);
|
||||
}
|
||||
|
||||
if (isInteger(aName) && isInteger(bName)) {
|
||||
return parseInt(aName, 10) - parseInt(bName, 10);
|
||||
}
|
||||
return a.attributes.url.localeCompare(b.attributes.url);
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
return item;
|
||||
}, []),
|
||||
});
|
||||
|
||||
const pages = useMemo(
|
||||
() => filterHasAttributes(selectedScan?.pages?.data, ["attributes"]),
|
||||
[selectedScan]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{selectedScan && isDefined(pages) && (
|
||||
<div>
|
||||
<div
|
||||
className="flex flex-row flex-wrap place-items-center
|
||||
gap-6 pt-10 text-base first-of-type:pt-0"
|
||||
>
|
||||
<h2 id={id} className="text-2xl">
|
||||
{title}
|
||||
</h2>
|
||||
|
||||
<Chip
|
||||
text={
|
||||
selectedScan.language?.data?.attributes?.code ===
|
||||
selectedScan.source_language?.data?.attributes?.code
|
||||
? langui.scan ?? "Scan"
|
||||
: langui.scanlation ?? "Scanlation"
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-row flex-wrap place-items-center gap-4 pb-6">
|
||||
{content?.data?.attributes &&
|
||||
isDefinedAndNotEmpty(content.data.attributes.slug) && (
|
||||
<Button
|
||||
href={`/contents/${content.data.attributes.slug}`}
|
||||
text={langui.open_content}
|
||||
/>
|
||||
)}
|
||||
|
||||
{languageSwitcherProps.locales.size > 1 && (
|
||||
<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>
|
||||
|
||||
{selectedScan.scanners && selectedScan.scanners.data.length > 0 && (
|
||||
<div>
|
||||
<p className="font-headers font-bold">{langui.scanners}:</p>
|
||||
<div className="grid place-content-center place-items-center gap-2">
|
||||
{filterHasAttributes(selectedScan.scanners.data, [
|
||||
"id",
|
||||
"attributes",
|
||||
] as const).map((scanner) => (
|
||||
<Fragment key={scanner.id}>
|
||||
<RecorderChip
|
||||
langui={langui}
|
||||
recorder={scanner.attributes}
|
||||
/>
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{selectedScan.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>
|
||||
<p className="font-headers font-bold">
|
||||
{langui.typesetters}:
|
||||
</p>
|
||||
<div className="grid place-content-center place-items-center gap-2">
|
||||
{filterHasAttributes(selectedScan.typesetters.data, [
|
||||
"id",
|
||||
"attributes",
|
||||
] as const).map((typesetter) => (
|
||||
<Fragment key={typesetter.id}>
|
||||
<RecorderChip
|
||||
langui={langui}
|
||||
recorder={typesetter.attributes}
|
||||
/>
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isDefinedAndNotEmpty(selectedScan.notes) && (
|
||||
<ToolTip content={selectedScan.notes}>
|
||||
<Chip text={langui.notes ?? "Notes"} />
|
||||
</ToolTip>
|
||||
)}
|
||||
</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))] mobile:grid-cols-2"
|
||||
>
|
||||
{pages.map((page, index) => (
|
||||
<div
|
||||
key={page.id}
|
||||
className="cursor-pointer transition-transform
|
||||
drop-shadow-shade-lg hover:scale-[1.02]"
|
||||
onClick={() => {
|
||||
const images = pages.map((image) =>
|
||||
getAssetURL(image.attributes.url, ImageQuality.Large)
|
||||
);
|
||||
openLightBox(images, index);
|
||||
}}
|
||||
>
|
||||
<Img src={page.attributes} quality={ImageQuality.Small} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -1,191 +0,0 @@
|
|||
import { Fragment, useCallback, useMemo } from "react";
|
||||
import { Chip } from "components/Chip";
|
||||
import { Img } from "components/Img";
|
||||
import { RecorderChip } from "components/RecorderChip";
|
||||
import { ToolTip } from "components/ToolTip";
|
||||
import {
|
||||
GetLibraryItemScansQuery,
|
||||
UploadImageFragment,
|
||||
} from "graphql/generated";
|
||||
import { AppStaticProps } from "graphql/getAppStaticProps";
|
||||
import { getAssetURL, ImageQuality } from "helpers/img";
|
||||
import { filterHasAttributes, getStatusDescription } from "helpers/others";
|
||||
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
||||
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
* ───────────────────────────────────────╯ COMPONENT ╰───────────────────────────────────────────
|
||||
*/
|
||||
|
||||
interface Props {
|
||||
openLightBox: (images: string[], index?: number) => void;
|
||||
images: NonNullable<
|
||||
NonNullable<
|
||||
NonNullable<
|
||||
GetLibraryItemScansQuery["libraryItems"]
|
||||
>["data"][number]["attributes"]
|
||||
>["images"]
|
||||
>;
|
||||
languages: AppStaticProps["languages"];
|
||||
langui: AppStaticProps["langui"];
|
||||
}
|
||||
|
||||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||
|
||||
export const ScanSetCover = ({
|
||||
openLightBox,
|
||||
images,
|
||||
languages,
|
||||
langui,
|
||||
}: Props): JSX.Element => {
|
||||
const [selectedScan, LanguageSwitcher, languageSwitcherProps] =
|
||||
useSmartLanguage({
|
||||
items: images,
|
||||
languages: languages,
|
||||
languageExtractor: useCallback(
|
||||
(item: NonNullable<Props["images"][number]>) =>
|
||||
item.language?.data?.attributes?.code,
|
||||
[]
|
||||
),
|
||||
});
|
||||
|
||||
const coverImages = useMemo(() => {
|
||||
const memo: UploadImageFragment[] = [];
|
||||
if (selectedScan?.obi_belt?.full?.data?.attributes)
|
||||
memo.push(selectedScan.obi_belt.full.data.attributes);
|
||||
if (selectedScan?.obi_belt?.inside_full?.data?.attributes)
|
||||
memo.push(selectedScan.obi_belt.inside_full.data.attributes);
|
||||
if (selectedScan?.dust_jacket?.full?.data?.attributes)
|
||||
memo.push(selectedScan.dust_jacket.full.data.attributes);
|
||||
if (selectedScan?.dust_jacket?.inside_full?.data?.attributes)
|
||||
memo.push(selectedScan.dust_jacket.inside_full.data.attributes);
|
||||
if (selectedScan?.cover?.full?.data?.attributes)
|
||||
memo.push(selectedScan.cover.full.data.attributes);
|
||||
if (selectedScan?.cover?.inside_full?.data?.attributes)
|
||||
memo.push(selectedScan.cover.inside_full.data.attributes);
|
||||
return memo;
|
||||
}, [selectedScan]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{coverImages.length > 0 && selectedScan && (
|
||||
<div>
|
||||
<div
|
||||
className="flex flex-row flex-wrap place-items-center
|
||||
gap-6 pt-10 text-base first-of-type:pt-0"
|
||||
>
|
||||
<h2 id={"cover"} className="text-2xl">
|
||||
{langui.cover}
|
||||
</h2>
|
||||
|
||||
<Chip
|
||||
text={
|
||||
selectedScan.language?.data?.attributes?.code ===
|
||||
selectedScan.source_language?.data?.attributes?.code
|
||||
? langui.scan ?? "Scan"
|
||||
: langui.scanlation ?? "Scanlation"
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-row flex-wrap place-items-center gap-4 pb-6">
|
||||
<LanguageSwitcher {...languageSwitcherProps} />
|
||||
|
||||
<div className="grid place-content-center place-items-center">
|
||||
<p className="font-headers font-bold">{langui.status}:</p>
|
||||
<ToolTip
|
||||
content={getStatusDescription(selectedScan.status, langui)}
|
||||
maxWidth={"20rem"}
|
||||
>
|
||||
<Chip text={selectedScan.status} />
|
||||
</ToolTip>
|
||||
</div>
|
||||
|
||||
{selectedScan.scanners && selectedScan.scanners.data.length > 0 && (
|
||||
<div>
|
||||
<p className="font-headers font-bold">{langui.scanners}:</p>
|
||||
<div className="grid place-content-center place-items-center gap-2">
|
||||
{filterHasAttributes(selectedScan.scanners.data, [
|
||||
"id",
|
||||
"attributes",
|
||||
] as const).map((scanner) => (
|
||||
<Fragment key={scanner.id}>
|
||||
<RecorderChip
|
||||
langui={langui}
|
||||
recorder={scanner.attributes}
|
||||
/>
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{selectedScan.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>
|
||||
<p className="font-headers font-bold">
|
||||
{langui.typesetters}:
|
||||
</p>
|
||||
<div className="grid place-content-center place-items-center gap-2">
|
||||
{filterHasAttributes(selectedScan.typesetters.data, [
|
||||
"id",
|
||||
"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))]
|
||||
mobile:grid-cols-2"
|
||||
>
|
||||
{coverImages.map((image, index) => (
|
||||
<div
|
||||
key={image.url}
|
||||
className="cursor-pointer transition-transform
|
||||
drop-shadow-shade-lg hover:scale-[1.02]"
|
||||
onClick={() => {
|
||||
const imgs = coverImages.map((img) =>
|
||||
getAssetURL(img.url, ImageQuality.Large)
|
||||
);
|
||||
|
||||
openLightBox(imgs, index);
|
||||
}}
|
||||
>
|
||||
<Img src={image} quality={ImageQuality.Small} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -1,31 +0,0 @@
|
|||
import { Ico, Icon } from "components/Ico";
|
||||
import { cIf, cJoin } from "helpers/className";
|
||||
import { isDefined } from "helpers/others";
|
||||
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
* ───────────────────────────────────────╯ COMPONENT ╰───────────────────────────────────────────
|
||||
*/
|
||||
|
||||
interface Props {
|
||||
message: string;
|
||||
icon?: Icon;
|
||||
}
|
||||
|
||||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||
|
||||
export const ContentPlaceholder = ({ message, icon }: Props): JSX.Element => (
|
||||
<div className="grid h-full place-content-center">
|
||||
<div
|
||||
className="grid grid-flow-col place-items-center gap-9 rounded-2xl border-2 border-dotted
|
||||
border-dark p-8 text-dark opacity-40"
|
||||
>
|
||||
{isDefined(icon) && <Ico icon={icon} className="!text-[300%]" />}
|
||||
<p
|
||||
className={cJoin("w-64 text-2xl", cIf(!isDefined(icon), "text-center"))}
|
||||
>
|
||||
{message}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
|
@ -1,10 +1,12 @@
|
|||
import { useRouter } from "next/router";
|
||||
import { MouseEventHandler, useMemo } from "react";
|
||||
import { MouseEventHandler, useCallback, useMemo } from "react";
|
||||
import { Ico, Icon } from "components/Ico";
|
||||
import { ToolTip } from "components/ToolTip";
|
||||
import { cJoin, cIf } from "helpers/className";
|
||||
import { isDefinedAndNotEmpty } from "helpers/others";
|
||||
import { Link } from "components/Inputs/Link";
|
||||
import { TranslatedProps } from "helpers/types/TranslatedProps";
|
||||
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
||||
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
|
@ -81,3 +83,29 @@ export const NavOption = ({
|
|||
</ToolTip>
|
||||
);
|
||||
};
|
||||
|
||||
/*
|
||||
* ╭──────────────────────╮
|
||||
* ───────────────────────────────────╯ TRANSLATED VARIANT ╰──────────────────────────────────────
|
||||
*/
|
||||
|
||||
export const TranslatedNavOption = ({
|
||||
translations,
|
||||
fallback,
|
||||
...otherProps
|
||||
}: TranslatedProps<Props, "subtitle" | "title">): JSX.Element => {
|
||||
const [selectedTranslation] = useSmartLanguage({
|
||||
items: translations,
|
||||
languageExtractor: useCallback(
|
||||
(item: { language: string }): string => item.language,
|
||||
[]
|
||||
),
|
||||
});
|
||||
return (
|
||||
<NavOption
|
||||
title={selectedTranslation?.title ?? fallback.title}
|
||||
subtitle={selectedTranslation?.subtitle ?? fallback.subtitle}
|
||||
{...otherProps}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
import { useCallback } from "react";
|
||||
import { HorizontalLine } from "components/HorizontalLine";
|
||||
import { Icon } from "components/Ico";
|
||||
import { Button } from "components/Inputs/Button";
|
||||
import { useAppLayout } from "contexts/AppLayoutContext";
|
||||
import { AppStaticProps } from "graphql/getAppStaticProps";
|
||||
import { cJoin } from "helpers/className";
|
||||
import { cIf, cJoin } from "helpers/className";
|
||||
import { TranslatedProps } from "helpers/types/TranslatedProps";
|
||||
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
||||
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
|
@ -40,11 +43,8 @@ export const ReturnButton = ({
|
|||
return (
|
||||
<div
|
||||
className={cJoin(
|
||||
displayOn === ReturnButtonType.Mobile
|
||||
? "desktop:hidden"
|
||||
: displayOn === ReturnButtonType.Desktop
|
||||
? "mobile:hidden"
|
||||
: "",
|
||||
cIf(displayOn === ReturnButtonType.Mobile, "desktop:hidden"),
|
||||
cIf(displayOn === ReturnButtonType.Desktop, "mobile:hidden"),
|
||||
className
|
||||
)}
|
||||
>
|
||||
|
@ -58,3 +58,29 @@ export const ReturnButton = ({
|
|||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
/*
|
||||
* ╭──────────────────────╮
|
||||
* ───────────────────────────────────╯ TRANSLATED VARIANT ╰──────────────────────────────────────
|
||||
*/
|
||||
|
||||
export const TranslatedReturnButton = ({
|
||||
translations,
|
||||
fallback,
|
||||
...otherProps
|
||||
}: TranslatedProps<Props, "title">): JSX.Element => {
|
||||
const [selectedTranslation] = useSmartLanguage({
|
||||
items: translations,
|
||||
languageExtractor: useCallback(
|
||||
(item: { language: string }): string => item.language,
|
||||
[]
|
||||
),
|
||||
});
|
||||
|
||||
return (
|
||||
<ReturnButton
|
||||
title={selectedTranslation?.title ?? fallback.title}
|
||||
{...otherProps}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { useMemo } from "react";
|
||||
import { useCallback, useMemo } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { Chip } from "./Chip";
|
||||
import { Ico, Icon } from "./Ico";
|
||||
|
@ -20,6 +20,8 @@ import {
|
|||
} from "helpers/formatters";
|
||||
import { ImageQuality } from "helpers/img";
|
||||
import { useMediaHoverable } from "hooks/useMediaQuery";
|
||||
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
||||
import { TranslatedProps } from "helpers/types/TranslatedProps";
|
||||
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
|
@ -296,3 +298,34 @@ export const PreviewCard = ({
|
|||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
/*
|
||||
* ╭──────────────────────╮
|
||||
* ───────────────────────────────────╯ TRANSLATED VARIANT ╰──────────────────────────────────────
|
||||
*/
|
||||
|
||||
export const TranslatedPreviewCard = ({
|
||||
translations,
|
||||
fallback,
|
||||
...otherProps
|
||||
}: TranslatedProps<
|
||||
Props,
|
||||
"description" | "pre_title" | "subtitle" | "title"
|
||||
>): JSX.Element => {
|
||||
const [selectedTranslation] = useSmartLanguage({
|
||||
items: translations,
|
||||
languageExtractor: useCallback(
|
||||
(item: { language: string }): string => item.language,
|
||||
[]
|
||||
),
|
||||
});
|
||||
return (
|
||||
<PreviewCard
|
||||
pre_title={selectedTranslation?.pre_title ?? fallback.pre_title}
|
||||
title={selectedTranslation?.title ?? fallback.title}
|
||||
subtitle={selectedTranslation?.subtitle ?? fallback.subtitle}
|
||||
description={selectedTranslation?.description ?? fallback.description}
|
||||
{...otherProps}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
import { useCallback } from "react";
|
||||
import { Chip } from "./Chip";
|
||||
import { Img } from "./Img";
|
||||
import { Link } from "./Inputs/Link";
|
||||
import { UploadImageFragment } from "graphql/generated";
|
||||
import { ImageQuality } from "helpers/img";
|
||||
import { TranslatedProps } from "helpers/types/TranslatedProps";
|
||||
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
||||
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
|
@ -22,7 +25,7 @@ interface Props {
|
|||
|
||||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||
|
||||
export const PreviewLine = ({
|
||||
const PreviewLine = ({
|
||||
href,
|
||||
thumbnail,
|
||||
pre_title,
|
||||
|
@ -73,3 +76,30 @@ export const PreviewLine = ({
|
|||
</div>
|
||||
</Link>
|
||||
);
|
||||
|
||||
/*
|
||||
* ╭──────────────────────╮
|
||||
* ───────────────────────────────────╯ TRANSLATED VARIANT ╰──────────────────────────────────────
|
||||
*/
|
||||
|
||||
export const TranslatedPreviewLine = ({
|
||||
translations,
|
||||
fallback,
|
||||
...otherProps
|
||||
}: TranslatedProps<Props, "pre_title" | "subtitle" | "title">): JSX.Element => {
|
||||
const [selectedTranslation] = useSmartLanguage({
|
||||
items: translations,
|
||||
languageExtractor: useCallback(
|
||||
(item: { language: string }): string => item.language,
|
||||
[]
|
||||
),
|
||||
});
|
||||
return (
|
||||
<PreviewLine
|
||||
pre_title={selectedTranslation?.pre_title ?? fallback.pre_title}
|
||||
title={selectedTranslation?.title ?? fallback.title}
|
||||
subtitle={selectedTranslation?.subtitle ?? fallback.subtitle}
|
||||
{...otherProps}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,226 +0,0 @@
|
|||
import { PreviewCard } from "./PreviewCard";
|
||||
import { PreviewLine } from "./PreviewLine";
|
||||
import { ScanSet } from "./Library/ScanSet";
|
||||
import { NavOption } from "./PanelComponents/NavOption";
|
||||
import { ChroniclePreview } from "./Chronicles/ChroniclePreview";
|
||||
import { ChroniclesList } from "./Chronicles/ChroniclesList";
|
||||
import { Button } from "./Inputs/Button";
|
||||
import { ReturnButton } from "./PanelComponents/ReturnButton";
|
||||
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
||||
import { PreviewFolder } from "pages/contents/folder/[slug]";
|
||||
|
||||
export type TranslatedProps<P, K extends keyof P> = Omit<P, K> & {
|
||||
translations: (Pick<P, K> & { language: string })[];
|
||||
fallback: Pick<P, K>;
|
||||
};
|
||||
|
||||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||
|
||||
type TranslatedPreviewCardProps = TranslatedProps<
|
||||
Parameters<typeof PreviewCard>[0],
|
||||
"description" | "pre_title" | "subtitle" | "title"
|
||||
>;
|
||||
|
||||
const languageExtractor = (item: { language: string }): string => item.language;
|
||||
|
||||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||
|
||||
export const TranslatedPreviewCard = ({
|
||||
translations,
|
||||
fallback,
|
||||
...otherProps
|
||||
}: TranslatedPreviewCardProps): JSX.Element => {
|
||||
const [selectedTranslation] = useSmartLanguage({
|
||||
items: translations,
|
||||
languageExtractor,
|
||||
});
|
||||
|
||||
return (
|
||||
<PreviewCard
|
||||
pre_title={selectedTranslation?.pre_title ?? fallback.pre_title}
|
||||
title={selectedTranslation?.title ?? fallback.title}
|
||||
subtitle={selectedTranslation?.subtitle ?? fallback.subtitle}
|
||||
description={selectedTranslation?.description ?? fallback.description}
|
||||
{...otherProps}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||
|
||||
export const TranslatedPreviewLine = ({
|
||||
translations,
|
||||
fallback,
|
||||
...otherProps
|
||||
}: TranslatedProps<
|
||||
Parameters<typeof PreviewLine>[0],
|
||||
"pre_title" | "subtitle" | "title"
|
||||
>): JSX.Element => {
|
||||
const [selectedTranslation] = useSmartLanguage({
|
||||
items: translations,
|
||||
|
||||
languageExtractor,
|
||||
});
|
||||
|
||||
return (
|
||||
<PreviewLine
|
||||
pre_title={selectedTranslation?.pre_title ?? fallback.pre_title}
|
||||
title={selectedTranslation?.title ?? fallback.title}
|
||||
subtitle={selectedTranslation?.subtitle ?? fallback.subtitle}
|
||||
{...otherProps}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||
|
||||
export const TranslatedScanSet = ({
|
||||
translations,
|
||||
fallback,
|
||||
...otherProps
|
||||
}: TranslatedProps<Parameters<typeof ScanSet>[0], "title">): JSX.Element => {
|
||||
const [selectedTranslation] = useSmartLanguage({
|
||||
items: translations,
|
||||
languageExtractor,
|
||||
});
|
||||
|
||||
return (
|
||||
<ScanSet
|
||||
title={selectedTranslation?.title ?? fallback.title}
|
||||
{...otherProps}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||
|
||||
export const TranslatedNavOption = ({
|
||||
translations,
|
||||
fallback,
|
||||
...otherProps
|
||||
}: TranslatedProps<
|
||||
Parameters<typeof NavOption>[0],
|
||||
"subtitle" | "title"
|
||||
>): JSX.Element => {
|
||||
const [selectedTranslation] = useSmartLanguage({
|
||||
items: translations,
|
||||
languageExtractor,
|
||||
});
|
||||
|
||||
return (
|
||||
<NavOption
|
||||
title={selectedTranslation?.title ?? fallback.title}
|
||||
subtitle={selectedTranslation?.subtitle ?? fallback.subtitle}
|
||||
{...otherProps}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||
|
||||
export const TranslatedChroniclePreview = ({
|
||||
translations,
|
||||
fallback,
|
||||
...otherProps
|
||||
}: TranslatedProps<
|
||||
Parameters<typeof ChroniclePreview>[0],
|
||||
"title"
|
||||
>): JSX.Element => {
|
||||
const [selectedTranslation] = useSmartLanguage({
|
||||
items: translations,
|
||||
languageExtractor,
|
||||
});
|
||||
|
||||
return (
|
||||
<ChroniclePreview
|
||||
title={selectedTranslation?.title ?? fallback.title}
|
||||
{...otherProps}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||
|
||||
export const TranslatedChroniclesList = ({
|
||||
translations,
|
||||
fallback,
|
||||
...otherProps
|
||||
}: TranslatedProps<
|
||||
Parameters<typeof ChroniclesList>[0],
|
||||
"title"
|
||||
>): JSX.Element => {
|
||||
const [selectedTranslation] = useSmartLanguage({
|
||||
items: translations,
|
||||
languageExtractor,
|
||||
});
|
||||
|
||||
return (
|
||||
<ChroniclesList
|
||||
title={selectedTranslation?.title ?? fallback.title}
|
||||
{...otherProps}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||
|
||||
export const TranslatedButton = ({
|
||||
translations,
|
||||
fallback,
|
||||
...otherProps
|
||||
}: TranslatedProps<Parameters<typeof Button>[0], "text">): JSX.Element => {
|
||||
const [selectedTranslation] = useSmartLanguage({
|
||||
items: translations,
|
||||
languageExtractor,
|
||||
});
|
||||
|
||||
return (
|
||||
<Button text={selectedTranslation?.text ?? fallback.text} {...otherProps} />
|
||||
);
|
||||
};
|
||||
|
||||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||
|
||||
export const TranslatedPreviewFolder = ({
|
||||
translations,
|
||||
fallback,
|
||||
...otherProps
|
||||
}: TranslatedProps<
|
||||
Parameters<typeof PreviewFolder>[0],
|
||||
"title"
|
||||
>): JSX.Element => {
|
||||
const [selectedTranslation] = useSmartLanguage({
|
||||
items: translations,
|
||||
languageExtractor,
|
||||
});
|
||||
|
||||
return (
|
||||
<PreviewFolder
|
||||
title={selectedTranslation?.title ?? fallback.title}
|
||||
{...otherProps}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||
|
||||
export const TranslatedReturnButton = ({
|
||||
translations,
|
||||
fallback,
|
||||
...otherProps
|
||||
}: TranslatedProps<
|
||||
Parameters<typeof ReturnButton>[0],
|
||||
"title"
|
||||
>): JSX.Element => {
|
||||
const [selectedTranslation] = useSmartLanguage({
|
||||
items: translations,
|
||||
languageExtractor,
|
||||
});
|
||||
|
||||
return (
|
||||
<ReturnButton
|
||||
title={selectedTranslation?.title ?? fallback.title}
|
||||
{...otherProps}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -2,7 +2,7 @@ export interface Wrapper {
|
|||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
interface Props<T> {
|
||||
interface ConditionalWrapperProps<T> {
|
||||
isWrapping: boolean;
|
||||
children: React.ReactNode;
|
||||
wrapper: (wrapperProps: T & Wrapper) => JSX.Element;
|
||||
|
@ -14,7 +14,7 @@ export const ConditionalWrapper = <T,>({
|
|||
children,
|
||||
wrapper: Wrapper,
|
||||
wrapperProps,
|
||||
}: Props<T>): JSX.Element =>
|
||||
}: ConditionalWrapperProps<T>): JSX.Element =>
|
||||
isWrapping ? (
|
||||
<Wrapper {...wrapperProps}>{children}</Wrapper>
|
||||
) : (
|
||||
|
|
|
@ -24,13 +24,3 @@ export const randomInt = (min: number, max: number): number =>
|
|||
|
||||
export const isInteger = (value: string): boolean =>
|
||||
/^[+-]?[0-9]+$/u.test(value);
|
||||
|
||||
export const clamp = (
|
||||
value: number,
|
||||
minValue: number,
|
||||
maxValue: number
|
||||
): number => {
|
||||
if (value > maxValue) return maxValue;
|
||||
if (value < minValue) return minValue;
|
||||
return value;
|
||||
};
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
|
||||
type JoinDot<K extends string, P extends string> = `${K}${"" extends K
|
||||
? ""
|
||||
: "."}${P}`;
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
export type TranslatedProps<P, K extends keyof P> = Omit<P, K> & {
|
||||
translations: (Pick<P, K> & { language: string })[];
|
||||
fallback: Pick<P, K>;
|
||||
};
|
|
@ -1,20 +0,0 @@
|
|||
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 };
|
||||
};
|
|
@ -1,13 +0,0 @@
|
|||
import { useEffect, useState } from "react";
|
||||
|
||||
const useIsClient = (): boolean => {
|
||||
const [isClient, setClient] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setClient(true);
|
||||
}, []);
|
||||
|
||||
return isClient;
|
||||
};
|
||||
|
||||
export default useIsClient;
|
|
@ -1,38 +1,6 @@
|
|||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useMediaQuery } from "usehooks-ts";
|
||||
import { breaks } from "../../design.config";
|
||||
|
||||
const useMediaQuery = (query: string): boolean => {
|
||||
const getMatches = useCallback((): boolean => {
|
||||
// Prevents SSR issues
|
||||
if (typeof window !== "undefined") {
|
||||
return window.matchMedia(query).matches;
|
||||
}
|
||||
return false;
|
||||
}, [query]);
|
||||
|
||||
const [matches, setMatches] = useState<boolean>(getMatches());
|
||||
|
||||
useEffect(() => {
|
||||
const handleChange = () => {
|
||||
setMatches(getMatches());
|
||||
};
|
||||
|
||||
const matchMedia = window.matchMedia(query);
|
||||
|
||||
// Triggered at the first client-side load and if query changes
|
||||
handleChange();
|
||||
|
||||
// Listen matchMedia
|
||||
matchMedia.addEventListener("change", handleChange);
|
||||
|
||||
return () => {
|
||||
matchMedia.removeEventListener("change", handleChange);
|
||||
};
|
||||
}, [getMatches, query]);
|
||||
|
||||
return matches;
|
||||
};
|
||||
|
||||
// ts-unused-exports:disable-next-line
|
||||
export const useMediaThin = (): boolean => useMediaQuery(breaks.thin.raw);
|
||||
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
import { useMemo } from "react";
|
||||
import { usePrefersDarkMode } from "./useMediaQuery";
|
||||
import { useStateWithLocalStorage } from "./useStateWithLocalStorage";
|
||||
|
||||
export enum TernaryDarkMode {
|
||||
Dark = "dark",
|
||||
Auto = "auto",
|
||||
Light = "light",
|
||||
}
|
||||
|
||||
export const useTernaryDarkMode = (
|
||||
key: string,
|
||||
initialValue: TernaryDarkMode
|
||||
): {
|
||||
isDarkMode: boolean;
|
||||
ternaryDarkMode: TernaryDarkMode | undefined;
|
||||
setTernaryDarkMode: React.Dispatch<
|
||||
React.SetStateAction<TernaryDarkMode | undefined>
|
||||
>;
|
||||
} => {
|
||||
const [ternaryDarkMode, setTernaryDarkMode] = useStateWithLocalStorage(
|
||||
key,
|
||||
initialValue
|
||||
);
|
||||
const prefersDarkMode = usePrefersDarkMode();
|
||||
const isDarkMode = useMemo(() => {
|
||||
switch (ternaryDarkMode) {
|
||||
case TernaryDarkMode.Light:
|
||||
return false;
|
||||
case TernaryDarkMode.Dark:
|
||||
return true;
|
||||
default:
|
||||
return prefersDarkMode;
|
||||
}
|
||||
}, [prefersDarkMode, ternaryDarkMode]);
|
||||
|
||||
return { isDarkMode, ternaryDarkMode, setTernaryDarkMode };
|
||||
};
|
|
@ -1,5 +1,5 @@
|
|||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import getConfig from "next/config";
|
||||
import { i18n } from "../../../next.config";
|
||||
|
||||
type RequestProps =
|
||||
| HookChronicle
|
||||
|
@ -119,7 +119,6 @@ const Revalidate = (
|
|||
res: NextApiResponse<ResponseMailProps>
|
||||
): void => {
|
||||
const body = req.body as RequestProps;
|
||||
const { serverRuntimeConfig } = getConfig();
|
||||
|
||||
// Check for secret to confirm this is a valid request
|
||||
if (
|
||||
|
@ -135,7 +134,7 @@ const Revalidate = (
|
|||
case "post": {
|
||||
paths.push(`/news`);
|
||||
paths.push(`/news/${body.entry.slug}`);
|
||||
serverRuntimeConfig.locales?.forEach((locale: string) => {
|
||||
i18n.locales.forEach((locale: string) => {
|
||||
paths.push(`/${locale}/news`);
|
||||
paths.push(`/${locale}/news/${body.entry.slug}`);
|
||||
});
|
||||
|
@ -149,7 +148,7 @@ const Revalidate = (
|
|||
body.entry.subitem_of.forEach((parentItem) => {
|
||||
paths.push(`/library/${parentItem.slug}`);
|
||||
});
|
||||
serverRuntimeConfig.locales?.forEach((locale: string) => {
|
||||
i18n.locales.forEach((locale: string) => {
|
||||
paths.push(`/${locale}/library`);
|
||||
paths.push(`/${locale}/library/${body.entry.slug}`);
|
||||
paths.push(`/${locale}/library/${body.entry.slug}/scans`);
|
||||
|
@ -166,7 +165,7 @@ const Revalidate = (
|
|||
if (body.entry.folder?.slug) {
|
||||
paths.push(`/contents/folder/${body.entry.folder.slug}`);
|
||||
}
|
||||
serverRuntimeConfig.locales?.forEach((locale: string) => {
|
||||
i18n.locales.forEach((locale: string) => {
|
||||
paths.push(`/${locale}/contents`);
|
||||
paths.push(`/${locale}/contents/${body.entry.slug}`);
|
||||
if (body.entry.folder?.slug) {
|
||||
|
@ -181,7 +180,7 @@ const Revalidate = (
|
|||
);
|
||||
paths.push(`/library/${parentSlug}`);
|
||||
paths.push(`/library/${parentSlug}/scans`);
|
||||
serverRuntimeConfig.locales?.forEach((locale: string) => {
|
||||
i18n.locales.forEach((locale: string) => {
|
||||
paths.push(`/${locale}/library/${parentSlug}`);
|
||||
paths.push(`/${locale}/library/${parentSlug}/scans`);
|
||||
});
|
||||
|
@ -193,7 +192,7 @@ const Revalidate = (
|
|||
case "chronology-era":
|
||||
case "chronology-item": {
|
||||
paths.push(`/wiki/chronology`);
|
||||
serverRuntimeConfig.locales?.forEach((locale: string) => {
|
||||
i18n.locales.forEach((locale: string) => {
|
||||
paths.push(`/${locale}/wiki/chronology`);
|
||||
});
|
||||
break;
|
||||
|
@ -207,7 +206,7 @@ const Revalidate = (
|
|||
if (body.entry.content) {
|
||||
paths.push(`/contents/${body.entry.content.slug}`);
|
||||
}
|
||||
serverRuntimeConfig.locales?.forEach((locale: string) => {
|
||||
i18n.locales.forEach((locale: string) => {
|
||||
if (body.entry.library_item) {
|
||||
paths.push(`/${locale}/library/${body.entry.library_item.slug}`);
|
||||
paths.push(
|
||||
|
@ -235,7 +234,7 @@ const Revalidate = (
|
|||
body.entry.contents.forEach((content) =>
|
||||
paths.push(`/contents/${content.slug}`)
|
||||
);
|
||||
serverRuntimeConfig.locales?.forEach((locale: string) => {
|
||||
i18n.locales.forEach((locale: string) => {
|
||||
if (body.entry.slug === "root") {
|
||||
paths.push(`/${locale}/contents`);
|
||||
}
|
||||
|
@ -258,7 +257,7 @@ const Revalidate = (
|
|||
case "wiki-page": {
|
||||
paths.push(`/wiki`);
|
||||
paths.push(`/wiki/${body.entry.slug}`);
|
||||
serverRuntimeConfig.locales?.forEach((locale: string) => {
|
||||
i18n.locales.forEach((locale: string) => {
|
||||
paths.push(`/${locale}/wiki`);
|
||||
paths.push(`/${locale}/wiki/${body.entry.slug}`);
|
||||
});
|
||||
|
@ -269,7 +268,7 @@ const Revalidate = (
|
|||
case "chronicle": {
|
||||
paths.push(`/chronicles`);
|
||||
paths.push(`/chronicles/${body.entry.slug}`);
|
||||
serverRuntimeConfig.locales?.forEach((locale: string) => {
|
||||
i18n.locales.forEach((locale: string) => {
|
||||
paths.push(`/${locale}/chronicles`);
|
||||
paths.push(`/${locale}/chronicles/${body.entry.slug}`);
|
||||
});
|
||||
|
@ -278,12 +277,12 @@ const Revalidate = (
|
|||
|
||||
case "chronicles-chapter": {
|
||||
paths.push(`/chronicles`);
|
||||
serverRuntimeConfig.locales?.forEach((locale: string) => {
|
||||
i18n.locales.forEach((locale: string) => {
|
||||
paths.push(`/${locale}/chronicles`);
|
||||
});
|
||||
body.entry.chronicles.forEach((chronicle) => {
|
||||
paths.push(`/chronicles/${chronicle.slug}`);
|
||||
serverRuntimeConfig.locales?.forEach((locale: string) => {
|
||||
i18n.locales.forEach((locale: string) => {
|
||||
paths.push(`/${locale}/chronicles/${chronicle.slug}`);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next";
|
||||
import { Fragment, useMemo } from "react";
|
||||
import { useBoolean } from "usehooks-ts";
|
||||
import { AppLayout, AppLayoutRequired } from "components/AppLayout";
|
||||
import { Switch } from "components/Inputs/Switch";
|
||||
import { PanelHeader } from "components/PanelComponents/PanelHeader";
|
||||
|
@ -21,7 +22,6 @@ import { Icon } from "components/Ico";
|
|||
import { useMediaHoverable } from "hooks/useMediaQuery";
|
||||
import { WithLabel } from "components/Inputs/WithLabel";
|
||||
import { filterHasAttributes, isDefined } from "helpers/others";
|
||||
import { useBoolean } from "hooks/useBoolean";
|
||||
import { getOpenGraph } from "helpers/openGraph";
|
||||
|
||||
/*
|
||||
|
@ -36,7 +36,7 @@ interface Props extends AppStaticProps, AppLayoutRequired {
|
|||
}
|
||||
|
||||
const Channel = ({ langui, channel, ...otherProps }: Props): JSX.Element => {
|
||||
const { state: keepInfoVisible, toggleState: toggleKeepInfoVisible } =
|
||||
const { value: keepInfoVisible, toggle: toggleKeepInfoVisible } =
|
||||
useBoolean(true);
|
||||
const hoverable = useMediaHoverable();
|
||||
|
||||
|
@ -58,12 +58,9 @@ const Channel = ({ langui, channel, ...otherProps }: Props): JSX.Element => {
|
|||
/>
|
||||
|
||||
{hoverable && (
|
||||
<WithLabel
|
||||
label={langui.always_show_info}
|
||||
input={
|
||||
<Switch value={keepInfoVisible} onClick={toggleKeepInfoVisible} />
|
||||
}
|
||||
/>
|
||||
<WithLabel label={langui.always_show_info}>
|
||||
<Switch value={keepInfoVisible} onClick={toggleKeepInfoVisible} />
|
||||
</WithLabel>
|
||||
)}
|
||||
</SubPanel>
|
||||
),
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { GetStaticProps } from "next";
|
||||
import { useMemo, useState } from "react";
|
||||
import { useBoolean } from "usehooks-ts";
|
||||
import { AppLayout, AppLayoutRequired } from "components/AppLayout";
|
||||
import { SmartList } from "components/SmartList";
|
||||
import { Icon } from "components/Ico";
|
||||
|
@ -23,7 +24,6 @@ import { getReadySdk } from "graphql/sdk";
|
|||
import { filterHasAttributes } from "helpers/others";
|
||||
import { getVideoThumbnailURL } from "helpers/videos";
|
||||
import { useMediaHoverable } from "hooks/useMediaQuery";
|
||||
import { useBoolean } from "hooks/useBoolean";
|
||||
import { getOpenGraph } from "helpers/openGraph";
|
||||
import { compareDate } from "helpers/date";
|
||||
|
||||
|
@ -48,7 +48,7 @@ interface Props extends AppStaticProps, AppLayoutRequired {
|
|||
const Videos = ({ langui, videos, ...otherProps }: Props): JSX.Element => {
|
||||
const hoverable = useMediaHoverable();
|
||||
|
||||
const { state: keepInfoVisible, toggleState: toggleKeepInfoVisible } =
|
||||
const { value: keepInfoVisible, toggle: toggleKeepInfoVisible } =
|
||||
useBoolean(true);
|
||||
|
||||
const [searchName, setSearchName] = useState(
|
||||
|
@ -80,12 +80,9 @@ const Videos = ({ langui, videos, ...otherProps }: Props): JSX.Element => {
|
|||
/>
|
||||
|
||||
{hoverable && (
|
||||
<WithLabel
|
||||
label={langui.always_show_info}
|
||||
input={
|
||||
<Switch value={keepInfoVisible} onClick={toggleKeepInfoVisible} />
|
||||
}
|
||||
/>
|
||||
<WithLabel label={langui.always_show_info}>
|
||||
<Switch value={keepInfoVisible} onClick={toggleKeepInfoVisible} />
|
||||
</WithLabel>
|
||||
)}
|
||||
</SubPanel>
|
||||
),
|
||||
|
|
|
@ -17,7 +17,6 @@ import {
|
|||
ReturnButton,
|
||||
ReturnButtonType,
|
||||
} from "components/PanelComponents/ReturnButton";
|
||||
import { TranslatedChroniclesList } from "components/Translated";
|
||||
import { Icon } from "components/Ico";
|
||||
import { getOpenGraph } from "helpers/openGraph";
|
||||
import {
|
||||
|
@ -25,6 +24,7 @@ import {
|
|||
staticSmartLanguage,
|
||||
} from "helpers/locales";
|
||||
import { getDescription } from "helpers/description";
|
||||
import { TranslatedChroniclesList } from "components/Chronicles/ChroniclesList";
|
||||
|
||||
/*
|
||||
* ╭────────╮
|
||||
|
|
|
@ -9,8 +9,8 @@ import { getReadySdk } from "graphql/sdk";
|
|||
import { GetChroniclesChaptersQuery } from "graphql/generated";
|
||||
import { filterHasAttributes } from "helpers/others";
|
||||
import { prettySlug } from "helpers/formatters";
|
||||
import { TranslatedChroniclesList } from "components/Translated";
|
||||
import { getOpenGraph } from "helpers/openGraph";
|
||||
import { TranslatedChroniclesList } from "components/Chronicles/ChroniclesList";
|
||||
|
||||
/*
|
||||
* ╭────────╮
|
||||
|
|
|
@ -5,7 +5,10 @@ import { Chip } from "components/Chip";
|
|||
import { HorizontalLine } from "components/HorizontalLine";
|
||||
import { PreviewCardCTAs } from "components/Library/PreviewCardCTAs";
|
||||
import { Markdawn, TableOfContents } from "components/Markdown/Markdawn";
|
||||
import { ReturnButtonType } from "components/PanelComponents/ReturnButton";
|
||||
import {
|
||||
ReturnButtonType,
|
||||
TranslatedReturnButton,
|
||||
} from "components/PanelComponents/ReturnButton";
|
||||
import { ContentPanel } from "components/Panels/ContentPanel";
|
||||
import { SubPanel } from "components/Panels/SubPanel";
|
||||
import { PreviewCard } from "components/PreviewCard";
|
||||
|
@ -30,16 +33,13 @@ import { ContentWithTranslations } from "helpers/types";
|
|||
import { useMediaMobile } from "hooks/useMediaQuery";
|
||||
import { AnchorIds, useScrollTopOnChange } from "hooks/useScrollTopOnChange";
|
||||
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
||||
import {
|
||||
TranslatedPreviewLine,
|
||||
TranslatedReturnButton,
|
||||
} from "components/Translated";
|
||||
import { getOpenGraph } from "helpers/openGraph";
|
||||
import {
|
||||
getDefaultPreferredLanguages,
|
||||
staticSmartLanguage,
|
||||
} from "helpers/locales";
|
||||
import { getDescription } from "helpers/description";
|
||||
import { TranslatedPreviewLine } from "components/PreviewLine";
|
||||
|
||||
/*
|
||||
* ╭────────╮
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { GetStaticProps } from "next";
|
||||
import { useState, useMemo, useCallback } from "react";
|
||||
import { useBoolean } from "usehooks-ts";
|
||||
import { AppLayout, AppLayoutRequired } from "components/AppLayout";
|
||||
import { Select } from "components/Inputs/Select";
|
||||
import { Switch } from "components/Inputs/Switch";
|
||||
|
@ -21,10 +22,9 @@ import { filterDefined, filterHasAttributes } from "helpers/others";
|
|||
import { GetContentsQuery } from "graphql/generated";
|
||||
import { SmartList } from "components/SmartList";
|
||||
import { SelectiveNonNullable } from "helpers/types/SelectiveNonNullable";
|
||||
import { useBoolean } from "hooks/useBoolean";
|
||||
import { TranslatedPreviewCard } from "components/Translated";
|
||||
import { getOpenGraph } from "helpers/openGraph";
|
||||
import { HorizontalLine } from "components/HorizontalLine";
|
||||
import { TranslatedPreviewCard } from "components/PreviewCard";
|
||||
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
|
@ -58,9 +58,9 @@ const Contents = ({
|
|||
DEFAULT_FILTERS_STATE.groupingMethod
|
||||
);
|
||||
const {
|
||||
state: keepInfoVisible,
|
||||
toggleState: toggleKeepInfoVisible,
|
||||
setState: setKeepInfoVisible,
|
||||
value: keepInfoVisible,
|
||||
toggle: toggleKeepInfoVisible,
|
||||
setValue: setKeepInfoVisible,
|
||||
} = useBoolean(DEFAULT_FILTERS_STATE.keepInfoVisible);
|
||||
|
||||
const [searchName, setSearchName] = useState(
|
||||
|
@ -150,26 +150,20 @@ const Contents = ({
|
|||
onChange={setSearchName}
|
||||
/>
|
||||
|
||||
<WithLabel
|
||||
label={langui.group_by}
|
||||
input={
|
||||
<Select
|
||||
className="w-full"
|
||||
options={[langui.category ?? "Category", langui.type ?? "Type"]}
|
||||
value={groupingMethod}
|
||||
onChange={setGroupingMethod}
|
||||
allowEmpty
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<WithLabel label={langui.group_by}>
|
||||
<Select
|
||||
className="w-full"
|
||||
options={[langui.category ?? "Category", langui.type ?? "Type"]}
|
||||
value={groupingMethod}
|
||||
onChange={setGroupingMethod}
|
||||
allowEmpty
|
||||
/>
|
||||
</WithLabel>
|
||||
|
||||
{hoverable && (
|
||||
<WithLabel
|
||||
label={langui.always_show_info}
|
||||
input={
|
||||
<Switch onClick={toggleKeepInfoVisible} value={keepInfoVisible} />
|
||||
}
|
||||
/>
|
||||
<WithLabel label={langui.always_show_info}>
|
||||
<Switch onClick={toggleKeepInfoVisible} value={keepInfoVisible} />
|
||||
</WithLabel>
|
||||
)}
|
||||
|
||||
<Button
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next";
|
||||
import { useMemo } from "react";
|
||||
import { useCallback, useMemo } from "react";
|
||||
import { AppLayout, AppLayoutRequired } from "components/AppLayout";
|
||||
import {
|
||||
ContentPanel,
|
||||
|
@ -16,16 +16,14 @@ import {
|
|||
} from "helpers/locales";
|
||||
import { prettySlug } from "helpers/formatters";
|
||||
import { SmartList } from "components/SmartList";
|
||||
import {
|
||||
TranslatedButton,
|
||||
TranslatedPreviewCard,
|
||||
TranslatedPreviewFolder,
|
||||
} from "components/Translated";
|
||||
import { Ico, Icon } from "components/Ico";
|
||||
import { Button } from "components/Inputs/Button";
|
||||
import { Button, TranslatedButton } from "components/Inputs/Button";
|
||||
import { Link } from "components/Inputs/Link";
|
||||
import { PanelHeader } from "components/PanelComponents/PanelHeader";
|
||||
import { SubPanel } from "components/Panels/SubPanel";
|
||||
import { TranslatedProps } from "helpers/types/TranslatedProps";
|
||||
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
||||
import { TranslatedPreviewCard } from "components/PreviewCard";
|
||||
|
||||
/*
|
||||
* ╭────────╮
|
||||
|
@ -304,6 +302,30 @@ export const PreviewFolder = ({
|
|||
</Link>
|
||||
);
|
||||
|
||||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||
|
||||
export const TranslatedPreviewFolder = ({
|
||||
translations,
|
||||
fallback,
|
||||
...otherProps
|
||||
}: TranslatedProps<PreviewFolderProps, "title">): JSX.Element => {
|
||||
const [selectedTranslation] = useSmartLanguage({
|
||||
items: translations,
|
||||
languageExtractor: useCallback(
|
||||
(item: { language: string }): string => item.language,
|
||||
[]
|
||||
),
|
||||
});
|
||||
return (
|
||||
<PreviewFolder
|
||||
title={selectedTranslation?.title ?? fallback.title}
|
||||
{...otherProps}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||
|
||||
interface NoContentNorFolderMessageProps {
|
||||
langui: AppStaticProps["langui"];
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { Fragment, useCallback, useMemo } from "react";
|
||||
import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next";
|
||||
import { useRouter } from "next/router";
|
||||
import { useBoolean } from "usehooks-ts";
|
||||
import { AppLayout, AppLayoutRequired } from "components/AppLayout";
|
||||
import { Chip } from "components/Chip";
|
||||
import { Img } from "components/Img";
|
||||
|
@ -53,7 +54,6 @@ import { WithLabel } from "components/Inputs/WithLabel";
|
|||
import { Ico, Icon } from "components/Ico";
|
||||
import { cJoin, cIf } from "helpers/className";
|
||||
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
||||
import { useBoolean } from "hooks/useBoolean";
|
||||
import { getOpenGraph } from "helpers/openGraph";
|
||||
import { getDescription } from "helpers/description";
|
||||
|
||||
|
@ -85,7 +85,7 @@ const LibrarySlug = ({
|
|||
const hoverable = useMediaHoverable();
|
||||
const router = useRouter();
|
||||
const [openLightBox, LightBox] = useLightBox();
|
||||
const { state: keepInfoVisible, toggleState: toggleKeepInfoVisible } =
|
||||
const { value: keepInfoVisible, toggle: toggleKeepInfoVisible } =
|
||||
useBoolean(false);
|
||||
|
||||
useScrollTopOnChange(AnchorIds.ContentPanel, [item]);
|
||||
|
@ -446,15 +446,12 @@ const LibrarySlug = ({
|
|||
</h2>
|
||||
|
||||
{hoverable && (
|
||||
<WithLabel
|
||||
label={langui.always_show_info}
|
||||
input={
|
||||
<Switch
|
||||
onClick={toggleKeepInfoVisible}
|
||||
value={keepInfoVisible}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<WithLabel label={langui.always_show_info}>
|
||||
<Switch
|
||||
onClick={toggleKeepInfoVisible}
|
||||
value={keepInfoVisible}
|
||||
/>
|
||||
</WithLabel>
|
||||
)}
|
||||
|
||||
<div
|
||||
|
@ -725,7 +722,7 @@ const ContentLine = ({
|
|||
slug,
|
||||
parentSlug,
|
||||
}: ContentLineProps): JSX.Element => {
|
||||
const { state: isOpened, toggleState: toggleOpened } = useBoolean(false);
|
||||
const { value: isOpened, toggle: toggleOpened } = useBoolean(false);
|
||||
|
||||
const [selectedTranslation] = useSmartLanguage({
|
||||
items: content?.translations ?? [],
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next";
|
||||
import { Fragment, useMemo } from "react";
|
||||
import { Fragment, useCallback, useMemo } from "react";
|
||||
import { AppLayout, AppLayoutRequired } from "components/AppLayout";
|
||||
import { ScanSetCover } from "components/Library/ScanSetCover";
|
||||
import {
|
||||
ReturnButton,
|
||||
ReturnButtonType,
|
||||
|
@ -11,7 +10,10 @@ import {
|
|||
ContentPanelWidthSizes,
|
||||
} from "components/Panels/ContentPanel";
|
||||
import { SubPanel } from "components/Panels/SubPanel";
|
||||
import { GetLibraryItemScansQuery } from "graphql/generated";
|
||||
import {
|
||||
GetLibraryItemScansQuery,
|
||||
UploadImageFragment,
|
||||
} from "graphql/generated";
|
||||
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
|
||||
import { getReadySdk } from "graphql/sdk";
|
||||
import {
|
||||
|
@ -21,7 +23,9 @@ import {
|
|||
} from "helpers/formatters";
|
||||
import {
|
||||
filterHasAttributes,
|
||||
getStatusDescription,
|
||||
isDefined,
|
||||
isDefinedAndNotEmpty,
|
||||
sortRangedContent,
|
||||
} from "helpers/others";
|
||||
import { useLightBox } from "hooks/useLightBox";
|
||||
|
@ -29,8 +33,17 @@ import { isUntangibleGroupItem } from "helpers/libraryItem";
|
|||
import { PreviewCardCTAs } from "components/Library/PreviewCardCTAs";
|
||||
import { PreviewCard } from "components/PreviewCard";
|
||||
import { HorizontalLine } from "components/HorizontalLine";
|
||||
import { TranslatedNavOption, TranslatedScanSet } from "components/Translated";
|
||||
import { getOpenGraph } from "helpers/openGraph";
|
||||
import { Chip } from "components/Chip";
|
||||
import { Img } from "components/Img";
|
||||
import { Button } from "components/Inputs/Button";
|
||||
import { RecorderChip } from "components/RecorderChip";
|
||||
import { ToolTip } from "components/ToolTip";
|
||||
import { getAssetFilename, getAssetURL, ImageQuality } from "helpers/img";
|
||||
import { isInteger } from "helpers/numbers";
|
||||
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
||||
import { TranslatedProps } from "helpers/types/TranslatedProps";
|
||||
import { TranslatedNavOption } from "components/PanelComponents/NavOption";
|
||||
|
||||
/*
|
||||
* ╭────────╮
|
||||
|
@ -297,3 +310,434 @@ export const getStaticPaths: GetStaticPaths = async (context) => {
|
|||
fallback: "blocking",
|
||||
};
|
||||
};
|
||||
|
||||
/*
|
||||
* ╭──────────────────────╮
|
||||
* ───────────────────────────────────╯ PRIVATE COMPONENTS ╰──────────────────────────────────────
|
||||
*/
|
||||
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
* ───────────────────────────────────────╯ COMPONENT ╰───────────────────────────────────────────
|
||||
*/
|
||||
|
||||
interface ScanSetProps {
|
||||
openLightBox: (images: string[], index?: number) => void;
|
||||
scanSet: NonNullable<
|
||||
NonNullable<
|
||||
NonNullable<
|
||||
NonNullable<
|
||||
NonNullable<
|
||||
GetLibraryItemScansQuery["libraryItems"]
|
||||
>["data"][number]["attributes"]
|
||||
>["contents"]
|
||||
>["data"][number]["attributes"]
|
||||
>["scan_set"]
|
||||
>;
|
||||
id: string;
|
||||
title: string;
|
||||
languages: AppStaticProps["languages"];
|
||||
langui: AppStaticProps["langui"];
|
||||
content: NonNullable<
|
||||
NonNullable<
|
||||
NonNullable<
|
||||
NonNullable<
|
||||
GetLibraryItemScansQuery["libraryItems"]
|
||||
>["data"][number]["attributes"]
|
||||
>["contents"]
|
||||
>["data"][number]["attributes"]
|
||||
>["content"];
|
||||
}
|
||||
|
||||
const ScanSet = ({
|
||||
openLightBox,
|
||||
scanSet,
|
||||
id,
|
||||
title,
|
||||
languages,
|
||||
langui,
|
||||
content,
|
||||
}: ScanSetProps): JSX.Element => {
|
||||
const [selectedScan, LanguageSwitcher, languageSwitcherProps] =
|
||||
useSmartLanguage({
|
||||
items: scanSet,
|
||||
languages: languages,
|
||||
languageExtractor: useCallback(
|
||||
(item: NonNullable<ScanSetProps["scanSet"][number]>) =>
|
||||
item.language?.data?.attributes?.code,
|
||||
[]
|
||||
),
|
||||
transform: useCallback(
|
||||
(item: NonNullable<ScanSetProps["scanSet"][number]>) => {
|
||||
item.pages?.data.sort((a, b) => {
|
||||
if (
|
||||
a.attributes &&
|
||||
b.attributes &&
|
||||
isDefinedAndNotEmpty(a.attributes.url) &&
|
||||
isDefinedAndNotEmpty(b.attributes.url)
|
||||
) {
|
||||
let aName = getAssetFilename(a.attributes.url);
|
||||
let bName = getAssetFilename(b.attributes.url);
|
||||
|
||||
/*
|
||||
* If the number is a succession of 0s, make the number
|
||||
* incrementally smaller than 0 (i.e: 00 becomes -1)
|
||||
*/
|
||||
if (aName.replaceAll("0", "").length === 0) {
|
||||
aName = (1 - aName.length).toString(10);
|
||||
}
|
||||
if (bName.replaceAll("0", "").length === 0) {
|
||||
bName = (1 - bName.length).toString(10);
|
||||
}
|
||||
|
||||
if (isInteger(aName) && isInteger(bName)) {
|
||||
return parseInt(aName, 10) - parseInt(bName, 10);
|
||||
}
|
||||
return a.attributes.url.localeCompare(b.attributes.url);
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
return item;
|
||||
},
|
||||
[]
|
||||
),
|
||||
});
|
||||
|
||||
const pages = useMemo(
|
||||
() => filterHasAttributes(selectedScan?.pages?.data, ["attributes"]),
|
||||
[selectedScan]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{selectedScan && isDefined(pages) && (
|
||||
<div>
|
||||
<div
|
||||
className="flex flex-row flex-wrap place-items-center
|
||||
gap-6 pt-10 text-base first-of-type:pt-0"
|
||||
>
|
||||
<h2 id={id} className="text-2xl">
|
||||
{title}
|
||||
</h2>
|
||||
|
||||
<Chip
|
||||
text={
|
||||
selectedScan.language?.data?.attributes?.code ===
|
||||
selectedScan.source_language?.data?.attributes?.code
|
||||
? langui.scan ?? "Scan"
|
||||
: langui.scanlation ?? "Scanlation"
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-row flex-wrap place-items-center gap-4 pb-6">
|
||||
{content?.data?.attributes &&
|
||||
isDefinedAndNotEmpty(content.data.attributes.slug) && (
|
||||
<Button
|
||||
href={`/contents/${content.data.attributes.slug}`}
|
||||
text={langui.open_content}
|
||||
/>
|
||||
)}
|
||||
|
||||
{languageSwitcherProps.locales.size > 1 && (
|
||||
<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>
|
||||
|
||||
{selectedScan.scanners && selectedScan.scanners.data.length > 0 && (
|
||||
<div>
|
||||
<p className="font-headers font-bold">{langui.scanners}:</p>
|
||||
<div className="grid place-content-center place-items-center gap-2">
|
||||
{filterHasAttributes(selectedScan.scanners.data, [
|
||||
"id",
|
||||
"attributes",
|
||||
] as const).map((scanner) => (
|
||||
<Fragment key={scanner.id}>
|
||||
<RecorderChip
|
||||
langui={langui}
|
||||
recorder={scanner.attributes}
|
||||
/>
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{selectedScan.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>
|
||||
<p className="font-headers font-bold">
|
||||
{langui.typesetters}:
|
||||
</p>
|
||||
<div className="grid place-content-center place-items-center gap-2">
|
||||
{filterHasAttributes(selectedScan.typesetters.data, [
|
||||
"id",
|
||||
"attributes",
|
||||
] as const).map((typesetter) => (
|
||||
<Fragment key={typesetter.id}>
|
||||
<RecorderChip
|
||||
langui={langui}
|
||||
recorder={typesetter.attributes}
|
||||
/>
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isDefinedAndNotEmpty(selectedScan.notes) && (
|
||||
<ToolTip content={selectedScan.notes}>
|
||||
<Chip text={langui.notes ?? "Notes"} />
|
||||
</ToolTip>
|
||||
)}
|
||||
</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))] mobile:grid-cols-2"
|
||||
>
|
||||
{pages.map((page, index) => (
|
||||
<div
|
||||
key={page.id}
|
||||
className="cursor-pointer transition-transform
|
||||
drop-shadow-shade-lg hover:scale-[1.02]"
|
||||
onClick={() => {
|
||||
const images = pages.map((image) =>
|
||||
getAssetURL(image.attributes.url, ImageQuality.Large)
|
||||
);
|
||||
openLightBox(images, index);
|
||||
}}
|
||||
>
|
||||
<Img src={page.attributes} quality={ImageQuality.Small} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||
|
||||
const TranslatedScanSet = ({
|
||||
translations,
|
||||
fallback,
|
||||
...otherProps
|
||||
}: TranslatedProps<ScanSetProps, "title">): JSX.Element => {
|
||||
const [selectedTranslation] = useSmartLanguage({
|
||||
items: translations,
|
||||
languageExtractor: useCallback(
|
||||
(item: { language: string }): string => item.language,
|
||||
[]
|
||||
),
|
||||
});
|
||||
|
||||
return (
|
||||
<ScanSet
|
||||
title={selectedTranslation?.title ?? fallback.title}
|
||||
{...otherProps}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||
|
||||
interface ScanSetCoverProps {
|
||||
openLightBox: (images: string[], index?: number) => void;
|
||||
images: NonNullable<
|
||||
NonNullable<
|
||||
NonNullable<
|
||||
GetLibraryItemScansQuery["libraryItems"]
|
||||
>["data"][number]["attributes"]
|
||||
>["images"]
|
||||
>;
|
||||
languages: AppStaticProps["languages"];
|
||||
langui: AppStaticProps["langui"];
|
||||
}
|
||||
|
||||
const ScanSetCover = ({
|
||||
openLightBox,
|
||||
images,
|
||||
languages,
|
||||
langui,
|
||||
}: ScanSetCoverProps): JSX.Element => {
|
||||
const [selectedScan, LanguageSwitcher, languageSwitcherProps] =
|
||||
useSmartLanguage({
|
||||
items: images,
|
||||
languages: languages,
|
||||
languageExtractor: useCallback(
|
||||
(item: NonNullable<ScanSetCoverProps["images"][number]>) =>
|
||||
item.language?.data?.attributes?.code,
|
||||
[]
|
||||
),
|
||||
});
|
||||
|
||||
const coverImages = useMemo(() => {
|
||||
const memo: UploadImageFragment[] = [];
|
||||
if (selectedScan?.obi_belt?.full?.data?.attributes)
|
||||
memo.push(selectedScan.obi_belt.full.data.attributes);
|
||||
if (selectedScan?.obi_belt?.inside_full?.data?.attributes)
|
||||
memo.push(selectedScan.obi_belt.inside_full.data.attributes);
|
||||
if (selectedScan?.dust_jacket?.full?.data?.attributes)
|
||||
memo.push(selectedScan.dust_jacket.full.data.attributes);
|
||||
if (selectedScan?.dust_jacket?.inside_full?.data?.attributes)
|
||||
memo.push(selectedScan.dust_jacket.inside_full.data.attributes);
|
||||
if (selectedScan?.cover?.full?.data?.attributes)
|
||||
memo.push(selectedScan.cover.full.data.attributes);
|
||||
if (selectedScan?.cover?.inside_full?.data?.attributes)
|
||||
memo.push(selectedScan.cover.inside_full.data.attributes);
|
||||
return memo;
|
||||
}, [selectedScan]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{coverImages.length > 0 && selectedScan && (
|
||||
<div>
|
||||
<div
|
||||
className="flex flex-row flex-wrap place-items-center
|
||||
gap-6 pt-10 text-base first-of-type:pt-0"
|
||||
>
|
||||
<h2 id={"cover"} className="text-2xl">
|
||||
{langui.cover}
|
||||
</h2>
|
||||
|
||||
<Chip
|
||||
text={
|
||||
selectedScan.language?.data?.attributes?.code ===
|
||||
selectedScan.source_language?.data?.attributes?.code
|
||||
? langui.scan ?? "Scan"
|
||||
: langui.scanlation ?? "Scanlation"
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-row flex-wrap place-items-center gap-4 pb-6">
|
||||
<LanguageSwitcher {...languageSwitcherProps} />
|
||||
|
||||
<div className="grid place-content-center place-items-center">
|
||||
<p className="font-headers font-bold">{langui.status}:</p>
|
||||
<ToolTip
|
||||
content={getStatusDescription(selectedScan.status, langui)}
|
||||
maxWidth={"20rem"}
|
||||
>
|
||||
<Chip text={selectedScan.status} />
|
||||
</ToolTip>
|
||||
</div>
|
||||
|
||||
{selectedScan.scanners && selectedScan.scanners.data.length > 0 && (
|
||||
<div>
|
||||
<p className="font-headers font-bold">{langui.scanners}:</p>
|
||||
<div className="grid place-content-center place-items-center gap-2">
|
||||
{filterHasAttributes(selectedScan.scanners.data, [
|
||||
"id",
|
||||
"attributes",
|
||||
] as const).map((scanner) => (
|
||||
<Fragment key={scanner.id}>
|
||||
<RecorderChip
|
||||
langui={langui}
|
||||
recorder={scanner.attributes}
|
||||
/>
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{selectedScan.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>
|
||||
<p className="font-headers font-bold">
|
||||
{langui.typesetters}:
|
||||
</p>
|
||||
<div className="grid place-content-center place-items-center gap-2">
|
||||
{filterHasAttributes(selectedScan.typesetters.data, [
|
||||
"id",
|
||||
"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))]
|
||||
mobile:grid-cols-2"
|
||||
>
|
||||
{coverImages.map((image, index) => (
|
||||
<div
|
||||
key={image.url}
|
||||
className="cursor-pointer transition-transform
|
||||
drop-shadow-shade-lg hover:scale-[1.02]"
|
||||
onClick={() => {
|
||||
const imgs = coverImages.map((img) =>
|
||||
getAssetURL(img.url, ImageQuality.Large)
|
||||
);
|
||||
|
||||
openLightBox(imgs, index);
|
||||
}}
|
||||
>
|
||||
<Img src={image} quality={ImageQuality.Small} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { GetStaticProps } from "next";
|
||||
import { useState, useMemo, useCallback } from "react";
|
||||
import { useBoolean } from "usehooks-ts";
|
||||
import { AppLayout, AppLayoutRequired } from "components/AppLayout";
|
||||
import { Select } from "components/Inputs/Select";
|
||||
import { Switch } from "components/Inputs/Switch";
|
||||
|
@ -28,7 +29,6 @@ import { useAppLayout } from "contexts/AppLayoutContext";
|
|||
import { convertPrice } from "helpers/numbers";
|
||||
import { SmartList } from "components/SmartList";
|
||||
import { SelectiveNonNullable } from "helpers/types/SelectiveNonNullable";
|
||||
import { useBoolean } from "hooks/useBoolean";
|
||||
import { getOpenGraph } from "helpers/openGraph";
|
||||
import { compareDate } from "helpers/date";
|
||||
|
||||
|
@ -71,27 +71,27 @@ const Library = ({
|
|||
);
|
||||
|
||||
const {
|
||||
state: showSubitems,
|
||||
toggleState: toggleShowSubitems,
|
||||
setState: setShowSubitems,
|
||||
value: showSubitems,
|
||||
toggle: toggleShowSubitems,
|
||||
setValue: setShowSubitems,
|
||||
} = useBoolean(DEFAULT_FILTERS_STATE.showSubitems);
|
||||
|
||||
const {
|
||||
state: showPrimaryItems,
|
||||
toggleState: toggleShowPrimaryItems,
|
||||
setState: setShowPrimaryItems,
|
||||
value: showPrimaryItems,
|
||||
toggle: toggleShowPrimaryItems,
|
||||
setValue: setShowPrimaryItems,
|
||||
} = useBoolean(DEFAULT_FILTERS_STATE.showPrimaryItems);
|
||||
|
||||
const {
|
||||
state: showSecondaryItems,
|
||||
toggleState: toggleShowSecondaryItems,
|
||||
setState: setShowSecondaryItems,
|
||||
value: showSecondaryItems,
|
||||
toggle: toggleShowSecondaryItems,
|
||||
setValue: setShowSecondaryItems,
|
||||
} = useBoolean(DEFAULT_FILTERS_STATE.showSecondaryItems);
|
||||
|
||||
const {
|
||||
state: keepInfoVisible,
|
||||
toggleState: toggleKeepInfoVisible,
|
||||
setState: setKeepInfoVisible,
|
||||
value: keepInfoVisible,
|
||||
toggle: toggleKeepInfoVisible,
|
||||
setValue: setKeepInfoVisible,
|
||||
} = useBoolean(DEFAULT_FILTERS_STATE.keepInfoVisible);
|
||||
|
||||
const [sortingMethod, setSortingMethod] = useState<number>(
|
||||
|
@ -268,68 +268,52 @@ const Library = ({
|
|||
onChange={setSearchName}
|
||||
/>
|
||||
|
||||
<WithLabel
|
||||
label={langui.group_by}
|
||||
input={
|
||||
<Select
|
||||
className="w-full"
|
||||
options={[
|
||||
langui.category ?? "Category",
|
||||
langui.type ?? "Type",
|
||||
langui.release_year ?? "Year",
|
||||
]}
|
||||
value={groupingMethod}
|
||||
onChange={setGroupingMethod}
|
||||
allowEmpty
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<WithLabel label={langui.group_by}>
|
||||
<Select
|
||||
className="w-full"
|
||||
options={[
|
||||
langui.category ?? "Category",
|
||||
langui.type ?? "Type",
|
||||
langui.release_year ?? "Year",
|
||||
]}
|
||||
value={groupingMethod}
|
||||
onChange={setGroupingMethod}
|
||||
allowEmpty
|
||||
/>
|
||||
</WithLabel>
|
||||
|
||||
<WithLabel
|
||||
label={langui.order_by}
|
||||
input={
|
||||
<Select
|
||||
className="w-full"
|
||||
options={[
|
||||
langui.name ?? "Name",
|
||||
langui.price ?? "Price",
|
||||
langui.release_date ?? "Release date",
|
||||
]}
|
||||
value={sortingMethod}
|
||||
onChange={setSortingMethod}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<WithLabel label={langui.order_by}>
|
||||
<Select
|
||||
className="w-full"
|
||||
options={[
|
||||
langui.name ?? "Name",
|
||||
langui.price ?? "Price",
|
||||
langui.release_date ?? "Release date",
|
||||
]}
|
||||
value={sortingMethod}
|
||||
onChange={setSortingMethod}
|
||||
/>
|
||||
</WithLabel>
|
||||
|
||||
<WithLabel
|
||||
label={langui.show_subitems}
|
||||
input={<Switch value={showSubitems} onClick={toggleShowSubitems} />}
|
||||
/>
|
||||
<WithLabel label={langui.show_subitems}>
|
||||
<Switch value={showSubitems} onClick={toggleShowSubitems} />
|
||||
</WithLabel>
|
||||
|
||||
<WithLabel
|
||||
label={langui.show_primary_items}
|
||||
input={
|
||||
<Switch value={showPrimaryItems} onClick={toggleShowPrimaryItems} />
|
||||
}
|
||||
/>
|
||||
<WithLabel label={langui.show_primary_items}>
|
||||
<Switch value={showPrimaryItems} onClick={toggleShowPrimaryItems} />
|
||||
</WithLabel>
|
||||
|
||||
<WithLabel
|
||||
label={langui.show_secondary_items}
|
||||
input={
|
||||
<Switch
|
||||
value={showSecondaryItems}
|
||||
onClick={toggleShowSecondaryItems}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<WithLabel label={langui.show_secondary_items}>
|
||||
<Switch
|
||||
value={showSecondaryItems}
|
||||
onClick={toggleShowSecondaryItems}
|
||||
/>
|
||||
</WithLabel>
|
||||
|
||||
{hoverable && (
|
||||
<WithLabel
|
||||
label={langui.always_show_info}
|
||||
input={
|
||||
<Switch value={keepInfoVisible} onClick={toggleKeepInfoVisible} />
|
||||
}
|
||||
/>
|
||||
<WithLabel label={langui.always_show_info}>
|
||||
<Switch value={keepInfoVisible} onClick={toggleKeepInfoVisible} />
|
||||
</WithLabel>
|
||||
)}
|
||||
|
||||
<ButtonGroup
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { GetStaticProps } from "next";
|
||||
import { useMemo, useState } from "react";
|
||||
import { useBoolean } from "usehooks-ts";
|
||||
import { AppLayout, AppLayoutRequired } from "components/AppLayout";
|
||||
import { Switch } from "components/Inputs/Switch";
|
||||
import { PanelHeader } from "components/PanelComponents/PanelHeader";
|
||||
|
@ -19,10 +20,9 @@ import { Button } from "components/Inputs/Button";
|
|||
import { useMediaHoverable } from "hooks/useMediaQuery";
|
||||
import { filterHasAttributes } from "helpers/others";
|
||||
import { SmartList } from "components/SmartList";
|
||||
import { useBoolean } from "hooks/useBoolean";
|
||||
import { TranslatedPreviewCard } from "components/Translated";
|
||||
import { getOpenGraph } from "helpers/openGraph";
|
||||
import { compareDate } from "helpers/date";
|
||||
import { TranslatedPreviewCard } from "components/PreviewCard";
|
||||
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
|
@ -49,9 +49,9 @@ const News = ({ langui, posts, ...otherProps }: Props): JSX.Element => {
|
|||
DEFAULT_FILTERS_STATE.searchName
|
||||
);
|
||||
const {
|
||||
state: keepInfoVisible,
|
||||
toggleState: toggleKeepInfoVisible,
|
||||
setState: setKeepInfoVisible,
|
||||
value: keepInfoVisible,
|
||||
toggle: toggleKeepInfoVisible,
|
||||
setValue: setKeepInfoVisible,
|
||||
} = useBoolean(DEFAULT_FILTERS_STATE.keepInfoVisible);
|
||||
|
||||
const subPanel = useMemo(
|
||||
|
@ -71,12 +71,9 @@ const News = ({ langui, posts, ...otherProps }: Props): JSX.Element => {
|
|||
/>
|
||||
|
||||
{hoverable && (
|
||||
<WithLabel
|
||||
label={langui.always_show_info}
|
||||
input={
|
||||
<Switch onClick={toggleKeepInfoVisible} value={keepInfoVisible} />
|
||||
}
|
||||
/>
|
||||
<WithLabel label={langui.always_show_info}>
|
||||
<Switch onClick={toggleKeepInfoVisible} value={keepInfoVisible} />
|
||||
</WithLabel>
|
||||
)}
|
||||
|
||||
<Button
|
||||
|
|
|
@ -24,13 +24,14 @@ import {
|
|||
isDefinedAndNotEmpty,
|
||||
} from "helpers/others";
|
||||
import { getOpenGraph } from "helpers/openGraph";
|
||||
import { TranslatedNavOption, TranslatedProps } from "components/Translated";
|
||||
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
||||
import { ToolTip } from "components/ToolTip";
|
||||
import { Chip } from "components/Chip";
|
||||
import { Ico, Icon } from "components/Ico";
|
||||
import { AnchorShare } from "components/AnchorShare";
|
||||
import { datePickerToDate } from "helpers/date";
|
||||
import { TranslatedProps } from "helpers/types/TranslatedProps";
|
||||
import { TranslatedNavOption } from "components/PanelComponents/NavOption";
|
||||
|
||||
/*
|
||||
* ╭────────╮
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { GetStaticProps } from "next";
|
||||
import { useCallback, useMemo, useState } from "react";
|
||||
import { useBoolean } from "usehooks-ts";
|
||||
import { AppLayout, AppLayoutRequired } from "components/AppLayout";
|
||||
import { NavOption } from "components/PanelComponents/NavOption";
|
||||
import { PanelHeader } from "components/PanelComponents/PanelHeader";
|
||||
|
@ -23,9 +24,8 @@ import { SmartList } from "components/SmartList";
|
|||
import { Select } from "components/Inputs/Select";
|
||||
import { SelectiveNonNullable } from "helpers/types/SelectiveNonNullable";
|
||||
import { prettySlug } from "helpers/formatters";
|
||||
import { useBoolean } from "hooks/useBoolean";
|
||||
import { TranslatedPreviewCard } from "components/Translated";
|
||||
import { getOpenGraph } from "helpers/openGraph";
|
||||
import { TranslatedPreviewCard } from "components/PreviewCard";
|
||||
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
|
@ -59,9 +59,9 @@ const Wiki = ({ langui, pages, ...otherProps }: Props): JSX.Element => {
|
|||
);
|
||||
|
||||
const {
|
||||
state: keepInfoVisible,
|
||||
toggleState: toggleKeepInfoVisible,
|
||||
setState: setKeepInfoVisible,
|
||||
value: keepInfoVisible,
|
||||
toggle: toggleKeepInfoVisible,
|
||||
setValue: setKeepInfoVisible,
|
||||
} = useBoolean(DEFAULT_FILTERS_STATE.keepInfoVisible);
|
||||
|
||||
const subPanel = useMemo(
|
||||
|
@ -80,26 +80,20 @@ const Wiki = ({ langui, pages, ...otherProps }: Props): JSX.Element => {
|
|||
onChange={setSearchName}
|
||||
/>
|
||||
|
||||
<WithLabel
|
||||
label={langui.group_by}
|
||||
input={
|
||||
<Select
|
||||
className="w-full"
|
||||
options={[langui.category ?? "Category"]}
|
||||
value={groupingMethod}
|
||||
onChange={setGroupingMethod}
|
||||
allowEmpty
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<WithLabel label={langui.group_by}>
|
||||
<Select
|
||||
className="w-full"
|
||||
options={[langui.category ?? "Category"]}
|
||||
value={groupingMethod}
|
||||
onChange={setGroupingMethod}
|
||||
allowEmpty
|
||||
/>
|
||||
</WithLabel>
|
||||
|
||||
{hoverable && (
|
||||
<WithLabel
|
||||
label={langui.always_show_info}
|
||||
input={
|
||||
<Switch value={keepInfoVisible} onClick={toggleKeepInfoVisible} />
|
||||
}
|
||||
/>
|
||||
<WithLabel label={langui.always_show_info}>
|
||||
<Switch value={keepInfoVisible} onClick={toggleKeepInfoVisible} />
|
||||
</WithLabel>
|
||||
)}
|
||||
|
||||
<Button
|
||||
|
|
Loading…
Reference in New Issue