247 lines
8.7 KiB
TypeScript
247 lines
8.7 KiB
TypeScript
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>
|
|
)}
|
|
</>
|
|
);
|
|
};
|