Lots of bs

This commit is contained in:
DrMint 2022-08-16 00:17:26 +02:00
parent 5a963294b7
commit 625f436163
39 changed files with 926 additions and 989 deletions

View File

@ -77,4 +77,9 @@ interface ComponentProps {}
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
export const Component = () => {};
/*
* ╭──────────────────────╮
* ───────────────────────────────────╯ TRANSLATED VARIANT ╰──────────────────────────────────────
*/
```

View File

@ -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 [
{

View File

@ -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>
);

View File

@ -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

View File

@ -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}
/>
);
};

View File

@ -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

View File

@ -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"),

View File

@ -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>
);

View File

@ -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

View File

@ -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>
)}
</>
);
};

View File

@ -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>
)}
</>
);
};

View File

@ -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>
);

View File

@ -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}
/>
);
};

View File

@ -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}
/>
);
};

View File

@ -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}
/>
);
};

View File

@ -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}
/>
);
};

View File

@ -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}
/>
);
};

View File

@ -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>
) : (

View File

@ -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;
};

View File

@ -1,3 +1,5 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
type JoinDot<K extends string, P extends string> = `${K}${"" extends K
? ""
: "."}${P}`;

View File

@ -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>;
};

View File

@ -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 };
};

View File

@ -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;

View File

@ -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);

View File

@ -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 };
};

View File

@ -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}`);
});
});

View File

@ -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>
),

View File

@ -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>
),

View File

@ -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";
/*
*

View File

@ -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";
/*
*

View File

@ -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";
/*
*

View File

@ -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

View File

@ -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"];
}

View File

@ -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 ?? [],

View File

@ -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>
)}
</>
);
};

View File

@ -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

View File

@ -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

View File

@ -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";
/*
*

View File

@ -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