Fixed the hooks problems plus other things
This commit is contained in:
parent
c076ec06ad
commit
24a8b43701
|
@ -60,7 +60,7 @@ module.exports = {
|
||||||
"no-alert": "warn",
|
"no-alert": "warn",
|
||||||
"no-bitwise": "warn",
|
"no-bitwise": "warn",
|
||||||
"no-caller": "warn",
|
"no-caller": "warn",
|
||||||
"no-confusing-arrow": "warn",
|
// "no-confusing-arrow": "warn",
|
||||||
"no-continue": "warn",
|
"no-continue": "warn",
|
||||||
"no-else-return": "warn",
|
"no-else-return": "warn",
|
||||||
"no-eq-null": "warn",
|
"no-eq-null": "warn",
|
||||||
|
@ -190,7 +190,10 @@ module.exports = {
|
||||||
"@typescript-eslint/promise-function-async": "error",
|
"@typescript-eslint/promise-function-async": "error",
|
||||||
"@typescript-eslint/require-array-sort-compare": "error",
|
"@typescript-eslint/require-array-sort-compare": "error",
|
||||||
"@typescript-eslint/sort-type-union-intersection-members": "warn",
|
"@typescript-eslint/sort-type-union-intersection-members": "warn",
|
||||||
// "@typescript-eslint/strict-boolean-expressions": "error",
|
"@typescript-eslint/strict-boolean-expressions": [
|
||||||
|
"error",
|
||||||
|
{ allowAny: true },
|
||||||
|
],
|
||||||
"@typescript-eslint/switch-exhaustiveness-check": "error",
|
"@typescript-eslint/switch-exhaustiveness-check": "error",
|
||||||
"@typescript-eslint/typedef": "error",
|
"@typescript-eslint/typedef": "error",
|
||||||
"@typescript-eslint/unified-signatures": "error",
|
"@typescript-eslint/unified-signatures": "error",
|
||||||
|
|
|
@ -4,14 +4,15 @@ import { UploadImageFragment } from "graphql/generated";
|
||||||
import { AppStaticProps } from "graphql/getAppStaticProps";
|
import { AppStaticProps } from "graphql/getAppStaticProps";
|
||||||
import { cIf, cJoin } from "helpers/className";
|
import { cIf, cJoin } from "helpers/className";
|
||||||
import { prettyLanguage, prettySlug } from "helpers/formatters";
|
import { prettyLanguage, prettySlug } from "helpers/formatters";
|
||||||
import { getOgImage, ImageQuality, OgImage } from "helpers/img";
|
import { getOgImage, ImageQuality } from "helpers/img";
|
||||||
|
import { isDefined, isDefinedAndNotEmpty } from "helpers/others";
|
||||||
// import { getClient, Indexes, search, SearchResult } from "helpers/search";
|
// import { getClient, Indexes, search, SearchResult } from "helpers/search";
|
||||||
import { Immutable } from "helpers/types";
|
import { Immutable } from "helpers/types";
|
||||||
import { useMediaMobile } from "hooks/useMediaQuery";
|
import { useMediaMobile } from "hooks/useMediaQuery";
|
||||||
import { AnchorIds } from "hooks/useScrollTopOnChange";
|
import { AnchorIds } from "hooks/useScrollTopOnChange";
|
||||||
import Head from "next/head";
|
import Head from "next/head";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { useEffect, useMemo, useState } from "react";
|
import { useEffect, useLayoutEffect, useMemo, useState } from "react";
|
||||||
import { useSwipeable } from "react-swipeable";
|
import { useSwipeable } from "react-swipeable";
|
||||||
import { Ico, Icon } from "./Ico";
|
import { Ico, Icon } from "./Ico";
|
||||||
import { ButtonGroup } from "./Inputs/ButtonGroup";
|
import { ButtonGroup } from "./Inputs/ButtonGroup";
|
||||||
|
@ -31,6 +32,9 @@ interface Props extends AppStaticProps {
|
||||||
description?: string;
|
description?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const SENSIBILITY_SWIPE = 1.1;
|
||||||
|
const TITLE_PREFIX = "Accord’s Library";
|
||||||
|
|
||||||
export function AppLayout(props: Immutable<Props>): JSX.Element {
|
export function AppLayout(props: Immutable<Props>): JSX.Element {
|
||||||
const {
|
const {
|
||||||
langui,
|
langui,
|
||||||
|
@ -44,151 +48,169 @@ export function AppLayout(props: Immutable<Props>): JSX.Element {
|
||||||
description,
|
description,
|
||||||
subPanelIcon = Icon.Tune,
|
subPanelIcon = Icon.Tune,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
|
const {
|
||||||
|
configPanelOpen,
|
||||||
|
currency,
|
||||||
|
darkMode,
|
||||||
|
dyslexic,
|
||||||
|
fontSize,
|
||||||
|
mainPanelOpen,
|
||||||
|
mainPanelReduced,
|
||||||
|
menuGestures,
|
||||||
|
playerName,
|
||||||
|
preferredLanguages,
|
||||||
|
selectedThemeMode,
|
||||||
|
subPanelOpen,
|
||||||
|
setConfigPanelOpen,
|
||||||
|
setCurrency,
|
||||||
|
setDarkMode,
|
||||||
|
setDyslexic,
|
||||||
|
setFontSize,
|
||||||
|
setMainPanelOpen,
|
||||||
|
setPlayerName,
|
||||||
|
setPreferredLanguages,
|
||||||
|
setSelectedThemeMode,
|
||||||
|
setSubPanelOpen,
|
||||||
|
toggleMainPanelOpen,
|
||||||
|
toggleSubPanelOpen,
|
||||||
|
} = useAppLayout();
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const isMobile = useMediaMobile();
|
const isMobile = useMediaMobile();
|
||||||
const appLayout = useAppLayout();
|
|
||||||
|
|
||||||
/*
|
useEffect(() => {
|
||||||
* const [searchQuery, setSearchQuery] = useState("");
|
|
||||||
* const [searchResult, setSearchResult] = useState<SearchResult>();
|
|
||||||
*/
|
|
||||||
|
|
||||||
const sensibilitySwipe = 1.1;
|
|
||||||
|
|
||||||
useMemo(() => {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||||
router.events?.on("routeChangeStart", () => {
|
router.events?.on("routeChangeStart", () => {
|
||||||
appLayout.setConfigPanelOpen(false);
|
setConfigPanelOpen(false);
|
||||||
appLayout.setMainPanelOpen(false);
|
setMainPanelOpen(false);
|
||||||
appLayout.setSubPanelOpen(false);
|
setSubPanelOpen(false);
|
||||||
});
|
});
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||||
router.events?.on("hashChangeStart", () => {
|
router.events?.on("hashChangeStart", () => {
|
||||||
appLayout.setSubPanelOpen(false);
|
setSubPanelOpen(false);
|
||||||
});
|
});
|
||||||
}, [appLayout, router.events]);
|
}, [router.events, setConfigPanelOpen, setMainPanelOpen, setSubPanelOpen]);
|
||||||
|
|
||||||
const handlers = useSwipeable({
|
const handlers = useSwipeable({
|
||||||
onSwipedLeft: (SwipeEventData) => {
|
onSwipedLeft: (SwipeEventData) => {
|
||||||
if (appLayout.menuGestures) {
|
if (menuGestures) {
|
||||||
if (SwipeEventData.velocity < sensibilitySwipe) return;
|
if (SwipeEventData.velocity < SENSIBILITY_SWIPE) return;
|
||||||
if (appLayout.mainPanelOpen) {
|
if (mainPanelOpen === true) {
|
||||||
appLayout.setMainPanelOpen(false);
|
setMainPanelOpen(false);
|
||||||
} else if (subPanel && contentPanel) {
|
} else if (subPanel === true && contentPanel === true) {
|
||||||
appLayout.setSubPanelOpen(true);
|
setSubPanelOpen(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onSwipedRight: (SwipeEventData) => {
|
onSwipedRight: (SwipeEventData) => {
|
||||||
if (appLayout.menuGestures) {
|
if (menuGestures) {
|
||||||
if (SwipeEventData.velocity < sensibilitySwipe) return;
|
if (SwipeEventData.velocity < SENSIBILITY_SWIPE) return;
|
||||||
if (appLayout.subPanelOpen) {
|
if (subPanelOpen === true) {
|
||||||
appLayout.setSubPanelOpen(false);
|
setSubPanelOpen(false);
|
||||||
} else {
|
} else {
|
||||||
appLayout.setMainPanelOpen(true);
|
setMainPanelOpen(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
/*
|
const turnSubIntoContent = useMemo(
|
||||||
* const client = getClient();
|
() => isDefined(subPanel) && isDefined(contentPanel),
|
||||||
* useEffect(() => {
|
[contentPanel, subPanel]
|
||||||
* if (searchQuery.length > 1) {
|
);
|
||||||
* search(client, Indexes.Post, searchQuery).then((result) => {
|
|
||||||
* setSearchResult(result);
|
|
||||||
* });
|
|
||||||
* } else {
|
|
||||||
* setSearchResult(undefined);
|
|
||||||
* }
|
|
||||||
* // eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
* }, [searchQuery]);
|
|
||||||
*/
|
|
||||||
|
|
||||||
const turnSubIntoContent = subPanel && !contentPanel;
|
const metaImage = useMemo(
|
||||||
|
() =>
|
||||||
const titlePrefix = "Accord’s Library";
|
thumbnail
|
||||||
const metaImage: OgImage = thumbnail
|
|
||||||
? getOgImage(ImageQuality.Og, thumbnail)
|
? getOgImage(ImageQuality.Og, thumbnail)
|
||||||
: {
|
: {
|
||||||
image: "/default_og.jpg",
|
image: "/default_og.jpg",
|
||||||
width: 1200,
|
width: 1200,
|
||||||
height: 630,
|
height: 630,
|
||||||
alt: "Accord's Library Logo",
|
alt: "Accord's Library Logo",
|
||||||
};
|
},
|
||||||
const ogTitle =
|
[thumbnail]
|
||||||
|
);
|
||||||
|
|
||||||
|
const { ogTitle, metaTitle } = useMemo(() => {
|
||||||
|
const resultTitle =
|
||||||
title ?? navTitle ?? prettySlug(router.asPath.split("/").pop());
|
title ?? navTitle ?? prettySlug(router.asPath.split("/").pop());
|
||||||
|
return {
|
||||||
|
ogTitle: resultTitle,
|
||||||
|
metaTitle: `${TITLE_PREFIX} - ${resultTitle}`,
|
||||||
|
};
|
||||||
|
}, [navTitle, router.asPath, title]);
|
||||||
|
|
||||||
const metaTitle = `${titlePrefix} - ${ogTitle}`;
|
const metaDescription = useMemo(
|
||||||
|
() => description ?? langui.default_description ?? "",
|
||||||
|
[description, langui.default_description]
|
||||||
|
);
|
||||||
|
|
||||||
const metaDescription = description
|
useLayoutEffect(() => {
|
||||||
? description
|
|
||||||
: langui.default_description ?? "";
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
document.getElementsByTagName("html")[0].style.fontSize = `${
|
document.getElementsByTagName("html")[0].style.fontSize = `${
|
||||||
(appLayout.fontSize ?? 1) * 100
|
(fontSize ?? 1) * 100
|
||||||
}%`;
|
}%`;
|
||||||
}, [appLayout.fontSize]);
|
}, [fontSize]);
|
||||||
|
|
||||||
const currencyOptions: string[] = [];
|
const defaultPreferredLanguages = useMemo(() => {
|
||||||
currencies.map((currency) => {
|
let list: string[] = [];
|
||||||
if (currency.attributes?.code)
|
if (isDefinedAndNotEmpty(router.locale) && router.locales) {
|
||||||
currencyOptions.push(currency.attributes.code);
|
if (router.locale === "en") {
|
||||||
|
list = [router.locale];
|
||||||
|
router.locales.map((locale) => {
|
||||||
|
if (locale !== router.locale) list.push(locale);
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
list = [router.locale, "en"];
|
||||||
|
router.locales.map((locale) => {
|
||||||
|
if (locale !== router.locale && locale !== "en") list.push(locale);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}, [router.locale, router.locales]);
|
||||||
|
|
||||||
|
const currencyOptions = useMemo(() => {
|
||||||
|
const list: string[] = [];
|
||||||
|
currencies.map((currentCurrency) => {
|
||||||
|
if (
|
||||||
|
currentCurrency.attributes &&
|
||||||
|
isDefinedAndNotEmpty(currentCurrency.attributes.code)
|
||||||
|
)
|
||||||
|
list.push(currentCurrency.attributes.code);
|
||||||
|
});
|
||||||
|
return list;
|
||||||
|
}, [currencies]);
|
||||||
|
|
||||||
const [currencySelect, setCurrencySelect] = useState<number>(-1);
|
const [currencySelect, setCurrencySelect] = useState<number>(-1);
|
||||||
|
|
||||||
let defaultPreferredLanguages: string[] = [];
|
useEffect(() => {
|
||||||
|
if (isDefined(currency))
|
||||||
if (router.locale && router.locales) {
|
setCurrencySelect(currencyOptions.indexOf(currency));
|
||||||
if (router.locale === "en") {
|
}, [currency, currencyOptions]);
|
||||||
defaultPreferredLanguages = [router.locale];
|
|
||||||
router.locales.map((locale) => {
|
|
||||||
if (locale !== router.locale) defaultPreferredLanguages.push(locale);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
defaultPreferredLanguages = [router.locale, "en"];
|
|
||||||
router.locales.map((locale) => {
|
|
||||||
if (locale !== router.locale && locale !== "en")
|
|
||||||
defaultPreferredLanguages.push(locale);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (appLayout.currency)
|
if (currencySelect >= 0) setCurrency(currencyOptions[currencySelect]);
|
||||||
setCurrencySelect(currencyOptions.indexOf(appLayout.currency));
|
}, [currencyOptions, currencySelect, setCurrency]);
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [appLayout.currency]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
const gridCol = useMemo(() => {
|
||||||
if (currencySelect >= 0)
|
if (isDefined(subPanel)) {
|
||||||
appLayout.setCurrency(currencyOptions[currencySelect]);
|
if (mainPanelReduced === true) {
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
return "grid-cols-[6rem_20rem_1fr]";
|
||||||
}, [currencySelect]);
|
|
||||||
|
|
||||||
let gridCol = "";
|
|
||||||
if (subPanel) {
|
|
||||||
if (appLayout.mainPanelReduced) {
|
|
||||||
gridCol = "grid-cols-[6rem_20rem_1fr]";
|
|
||||||
} else {
|
|
||||||
gridCol = "grid-cols-[20rem_20rem_1fr]";
|
|
||||||
}
|
}
|
||||||
} else if (appLayout.mainPanelReduced) {
|
return "grid-cols-[20rem_20rem_1fr]";
|
||||||
gridCol = "grid-cols-[6rem_0px_1fr]";
|
} else if (mainPanelReduced === true) {
|
||||||
} else {
|
return "grid-cols-[6rem_0px_1fr]";
|
||||||
gridCol = "grid-cols-[20rem_0px_1fr]";
|
|
||||||
}
|
}
|
||||||
|
return "grid-cols-[20rem_0px_1fr]";
|
||||||
|
}, [mainPanelReduced, subPanel]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cJoin(
|
className={cJoin(
|
||||||
cIf(appLayout.darkMode, "set-theme-dark", "set-theme-light"),
|
cIf(darkMode, "set-theme-dark", "set-theme-light"),
|
||||||
cIf(
|
cIf(dyslexic, "set-theme-font-dyslexic", "set-theme-font-standard")
|
||||||
appLayout.dyslexic,
|
|
||||||
"set-theme-font-dyslexic",
|
|
||||||
"set-theme-font-standard"
|
|
||||||
)
|
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
@ -231,7 +253,7 @@ export function AppLayout(props: Immutable<Props>): JSX.Element {
|
||||||
`absolute inset-0 transition-[backdrop-filter] duration-500 [grid-area:content]
|
`absolute inset-0 transition-[backdrop-filter] duration-500 [grid-area:content]
|
||||||
mobile:z-10`,
|
mobile:z-10`,
|
||||||
cIf(
|
cIf(
|
||||||
(appLayout.mainPanelOpen || appLayout.subPanelOpen) && isMobile,
|
(mainPanelOpen === true || subPanelOpen === true) && isMobile,
|
||||||
"[backdrop-filter:blur(2px)]",
|
"[backdrop-filter:blur(2px)]",
|
||||||
"pointer-events-none touch-none"
|
"pointer-events-none touch-none"
|
||||||
)
|
)
|
||||||
|
@ -241,14 +263,14 @@ export function AppLayout(props: Immutable<Props>): JSX.Element {
|
||||||
className={cJoin(
|
className={cJoin(
|
||||||
"absolute inset-0 bg-shade transition-opacity duration-500",
|
"absolute inset-0 bg-shade transition-opacity duration-500",
|
||||||
cIf(
|
cIf(
|
||||||
(appLayout.mainPanelOpen || appLayout.subPanelOpen) && isMobile,
|
(mainPanelOpen === true || subPanelOpen === true) && isMobile,
|
||||||
"opacity-60",
|
"opacity-60",
|
||||||
"opacity-0"
|
"opacity-0"
|
||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
appLayout.setMainPanelOpen(false);
|
setMainPanelOpen(false);
|
||||||
appLayout.setSubPanelOpen(false);
|
setSubPanelOpen(false);
|
||||||
}}
|
}}
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -258,7 +280,7 @@ export function AppLayout(props: Immutable<Props>): JSX.Element {
|
||||||
id={AnchorIds.ContentPanel}
|
id={AnchorIds.ContentPanel}
|
||||||
className={`texture-paper-dots overflow-y-scroll bg-light [grid-area:content]`}
|
className={`texture-paper-dots overflow-y-scroll bg-light [grid-area:content]`}
|
||||||
>
|
>
|
||||||
{contentPanel ? (
|
{isDefined(contentPanel) ? (
|
||||||
contentPanel
|
contentPanel
|
||||||
) : (
|
) : (
|
||||||
<div className="grid h-full place-content-center">
|
<div className="grid h-full place-content-center">
|
||||||
|
@ -274,7 +296,7 @@ export function AppLayout(props: Immutable<Props>): JSX.Element {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Sub panel */}
|
{/* Sub panel */}
|
||||||
{subPanel && (
|
{isDefined(subPanel) && (
|
||||||
<div
|
<div
|
||||||
className={cJoin(
|
className={cJoin(
|
||||||
`texture-paper-dots overflow-y-scroll border-r-[1px] border-dotted
|
`texture-paper-dots overflow-y-scroll border-r-[1px] border-dotted
|
||||||
|
@ -284,7 +306,7 @@ export function AppLayout(props: Immutable<Props>): JSX.Element {
|
||||||
mobile:[grid-area:content]`,
|
mobile:[grid-area:content]`,
|
||||||
turnSubIntoContent
|
turnSubIntoContent
|
||||||
? "mobile:w-full mobile:border-l-0"
|
? "mobile:w-full mobile:border-l-0"
|
||||||
: appLayout.subPanelOpen
|
: subPanelOpen === true
|
||||||
? ""
|
? ""
|
||||||
: "mobile:translate-x-[100vw]"
|
: "mobile:translate-x-[100vw]"
|
||||||
)}
|
)}
|
||||||
|
@ -300,7 +322,7 @@ export function AppLayout(props: Immutable<Props>): JSX.Element {
|
||||||
border-black bg-light transition-transform duration-300 [grid-area:main]
|
border-black bg-light transition-transform duration-300 [grid-area:main]
|
||||||
[scrollbar-width:none] webkit-scrollbar:w-0 mobile:z-10 mobile:w-[90%]
|
[scrollbar-width:none] webkit-scrollbar:w-0 mobile:z-10 mobile:w-[90%]
|
||||||
mobile:justify-self-start mobile:[grid-area:content]`,
|
mobile:justify-self-start mobile:[grid-area:content]`,
|
||||||
cIf(!appLayout.mainPanelOpen, "mobile:-translate-x-full")
|
cIf(mainPanelOpen === false, "mobile:-translate-x-full")
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<MainPanel langui={langui} />
|
<MainPanel langui={langui} />
|
||||||
|
@ -312,11 +334,11 @@ export function AppLayout(props: Immutable<Props>): JSX.Element {
|
||||||
border-t-[1px] border-dotted border-black bg-light [grid-area:navbar] desktop:hidden"
|
border-t-[1px] border-dotted border-black bg-light [grid-area:navbar] desktop:hidden"
|
||||||
>
|
>
|
||||||
<Ico
|
<Ico
|
||||||
icon={appLayout.mainPanelOpen ? Icon.Close : Icon.Menu}
|
icon={mainPanelOpen === true ? Icon.Close : Icon.Menu}
|
||||||
className="mt-[.1em] cursor-pointer !text-2xl"
|
className="mt-[.1em] cursor-pointer !text-2xl"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
appLayout.setMainPanelOpen(!appLayout.mainPanelOpen);
|
toggleMainPanelOpen();
|
||||||
appLayout.setSubPanelOpen(false);
|
setSubPanelOpen(false);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<p
|
<p
|
||||||
|
@ -331,22 +353,19 @@ export function AppLayout(props: Immutable<Props>): JSX.Element {
|
||||||
>
|
>
|
||||||
{ogTitle}
|
{ogTitle}
|
||||||
</p>
|
</p>
|
||||||
{subPanel && !turnSubIntoContent && (
|
{isDefined(subPanel) && !turnSubIntoContent && (
|
||||||
<Ico
|
<Ico
|
||||||
icon={appLayout.subPanelOpen ? Icon.Close : subPanelIcon}
|
icon={subPanelOpen === true ? Icon.Close : subPanelIcon}
|
||||||
className="mt-[.1em] cursor-pointer !text-2xl"
|
className="mt-[.1em] cursor-pointer !text-2xl"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
appLayout.setSubPanelOpen(!appLayout.subPanelOpen);
|
toggleSubPanelOpen();
|
||||||
appLayout.setMainPanelOpen(false);
|
setMainPanelOpen(false);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Popup
|
<Popup state={configPanelOpen ?? false} setState={setConfigPanelOpen}>
|
||||||
state={appLayout.configPanelOpen}
|
|
||||||
setState={appLayout.setConfigPanelOpen}
|
|
||||||
>
|
|
||||||
<h2 className="text-2xl">{langui.settings}</h2>
|
<h2 className="text-2xl">{langui.settings}</h2>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
@ -356,12 +375,12 @@ export function AppLayout(props: Immutable<Props>): JSX.Element {
|
||||||
{router.locales && (
|
{router.locales && (
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-xl">{langui.languages}</h3>
|
<h3 className="text-xl">{langui.languages}</h3>
|
||||||
{appLayout.preferredLanguages && (
|
{preferredLanguages && (
|
||||||
<OrderableList
|
<OrderableList
|
||||||
items={
|
items={
|
||||||
appLayout.preferredLanguages.length > 0
|
preferredLanguages.length > 0
|
||||||
? new Map(
|
? new Map(
|
||||||
appLayout.preferredLanguages.map((locale) => [
|
preferredLanguages.map((locale) => [
|
||||||
locale,
|
locale,
|
||||||
prettyLanguage(locale, languages),
|
prettyLanguage(locale, languages),
|
||||||
])
|
])
|
||||||
|
@ -380,13 +399,13 @@ export function AppLayout(props: Immutable<Props>): JSX.Element {
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
onChange={(items) => {
|
onChange={(items) => {
|
||||||
const preferredLanguages = [...items].map(
|
const newPreferredLanguages = [...items].map(
|
||||||
([code]) => code
|
([code]) => code
|
||||||
);
|
);
|
||||||
appLayout.setPreferredLanguages(preferredLanguages);
|
setPreferredLanguages(newPreferredLanguages);
|
||||||
if (router.locale !== preferredLanguages[0]) {
|
if (router.locale !== newPreferredLanguages[0]) {
|
||||||
router.push(router.asPath, router.asPath, {
|
router.push(router.asPath, router.asPath, {
|
||||||
locale: preferredLanguages[0],
|
locale: newPreferredLanguages[0],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
@ -400,31 +419,25 @@ export function AppLayout(props: Immutable<Props>): JSX.Element {
|
||||||
<ButtonGroup>
|
<ButtonGroup>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
appLayout.setDarkMode(false);
|
setDarkMode(false);
|
||||||
appLayout.setSelectedThemeMode(true);
|
setSelectedThemeMode(true);
|
||||||
}}
|
}}
|
||||||
active={
|
active={selectedThemeMode === true && darkMode === false}
|
||||||
appLayout.selectedThemeMode === true &&
|
|
||||||
appLayout.darkMode === false
|
|
||||||
}
|
|
||||||
text={langui.light}
|
text={langui.light}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
appLayout.setSelectedThemeMode(false);
|
setSelectedThemeMode(false);
|
||||||
}}
|
}}
|
||||||
active={appLayout.selectedThemeMode === false}
|
active={selectedThemeMode === false}
|
||||||
text={langui.auto}
|
text={langui.auto}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
appLayout.setDarkMode(true);
|
setDarkMode(true);
|
||||||
appLayout.setSelectedThemeMode(true);
|
setSelectedThemeMode(true);
|
||||||
}}
|
}}
|
||||||
active={
|
active={selectedThemeMode === true && darkMode === true}
|
||||||
appLayout.selectedThemeMode === true &&
|
|
||||||
appLayout.darkMode === true
|
|
||||||
}
|
|
||||||
text={langui.dark}
|
text={langui.dark}
|
||||||
/>
|
/>
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
|
@ -446,32 +459,17 @@ export function AppLayout(props: Immutable<Props>): JSX.Element {
|
||||||
<h3 className="text-xl">{langui.font_size}</h3>
|
<h3 className="text-xl">{langui.font_size}</h3>
|
||||||
<ButtonGroup>
|
<ButtonGroup>
|
||||||
<Button
|
<Button
|
||||||
onClick={() =>
|
onClick={() => setFontSize((fontSize ?? 1) / 1.05)}
|
||||||
appLayout.setFontSize(
|
|
||||||
appLayout.fontSize
|
|
||||||
? appLayout.fontSize / 1.05
|
|
||||||
: 1 / 1.05
|
|
||||||
)
|
|
||||||
}
|
|
||||||
icon={Icon.TextDecrease}
|
icon={Icon.TextDecrease}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => appLayout.setFontSize(1)}
|
onClick={() => setFontSize(1)}
|
||||||
text={`${((appLayout.fontSize ?? 1) * 100).toLocaleString(
|
text={`${((fontSize ?? 1) * 100).toLocaleString(undefined, {
|
||||||
undefined,
|
|
||||||
{
|
|
||||||
maximumFractionDigits: 0,
|
maximumFractionDigits: 0,
|
||||||
}
|
})}%`}
|
||||||
)}%`}
|
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
onClick={() =>
|
onClick={() => setFontSize((fontSize ?? 1) * 1.05)}
|
||||||
appLayout.setFontSize(
|
|
||||||
appLayout.fontSize
|
|
||||||
? appLayout.fontSize * 1.05
|
|
||||||
: 1 * 1.05
|
|
||||||
)
|
|
||||||
}
|
|
||||||
icon={Icon.TextIncrease}
|
icon={Icon.TextIncrease}
|
||||||
/>
|
/>
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
|
@ -481,14 +479,14 @@ export function AppLayout(props: Immutable<Props>): JSX.Element {
|
||||||
<h3 className="text-xl">{langui.font}</h3>
|
<h3 className="text-xl">{langui.font}</h3>
|
||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
<Button
|
<Button
|
||||||
active={appLayout.dyslexic === false}
|
active={dyslexic === false}
|
||||||
onClick={() => appLayout.setDyslexic(false)}
|
onClick={() => setDyslexic(false)}
|
||||||
className="font-zenMaruGothic"
|
className="font-zenMaruGothic"
|
||||||
text="Zen Maru Gothic"
|
text="Zen Maru Gothic"
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
active={appLayout.dyslexic === true}
|
active={dyslexic === true}
|
||||||
onClick={() => appLayout.setDyslexic(true)}
|
onClick={() => setDyslexic(true)}
|
||||||
className="font-openDyslexic"
|
className="font-openDyslexic"
|
||||||
text="OpenDyslexic"
|
text="OpenDyslexic"
|
||||||
/>
|
/>
|
||||||
|
@ -500,8 +498,8 @@ export function AppLayout(props: Immutable<Props>): JSX.Element {
|
||||||
<TextInput
|
<TextInput
|
||||||
placeholder="<player>"
|
placeholder="<player>"
|
||||||
className="w-48"
|
className="w-48"
|
||||||
state={appLayout.playerName}
|
state={playerName}
|
||||||
setState={appLayout.setPlayerName}
|
setState={setPlayerName}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -509,8 +507,8 @@ export function AppLayout(props: Immutable<Props>): JSX.Element {
|
||||||
</Popup>
|
</Popup>
|
||||||
|
|
||||||
{/* <Popup
|
{/* <Popup
|
||||||
state={appLayout.searchPanelOpen}
|
state={searchPanelOpen}
|
||||||
setState={appLayout.setSearchPanelOpen}
|
setState={setSearchPanelOpen}
|
||||||
>
|
>
|
||||||
<div className="grid place-items-center gap-2">
|
<div className="grid place-items-center gap-2">
|
||||||
TODO: add to langui
|
TODO: add to langui
|
||||||
|
@ -546,3 +544,22 @@ export function AppLayout(props: Immutable<Props>): JSX.Element {
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* const [searchQuery, setSearchQuery] = useState("");
|
||||||
|
* const [searchResult, setSearchResult] = useState<SearchResult>();
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* const client = getClient();
|
||||||
|
* useEffect(() => {
|
||||||
|
* if (searchQuery.length > 1) {
|
||||||
|
* search(client, Indexes.Post, searchQuery).then((result) => {
|
||||||
|
* setSearchResult(result);
|
||||||
|
* });
|
||||||
|
* } else {
|
||||||
|
* setSearchResult(undefined);
|
||||||
|
* }
|
||||||
|
* // eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
* }, [searchQuery]);
|
||||||
|
*/
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
import { Ico, Icon } from "components/Ico";
|
import { Ico, Icon } from "components/Ico";
|
||||||
import { cIf, cJoin } from "helpers/className";
|
import { cIf, cJoin } from "helpers/className";
|
||||||
|
import { ConditionalWrapper, Wrapper } from "helpers/component";
|
||||||
import { Immutable } from "helpers/types";
|
import { Immutable } from "helpers/types";
|
||||||
|
import { isDefined, isDefinedAndNotEmpty } from "helpers/others";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { MouseEventHandler } from "react";
|
import React, { MouseEventHandler } from "react";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
id?: string;
|
id?: string;
|
||||||
|
@ -32,9 +34,25 @@ export function Button(props: Immutable<Props>): JSX.Element {
|
||||||
locale,
|
locale,
|
||||||
badgeNumber,
|
badgeNumber,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const button = (
|
return (
|
||||||
|
<ConditionalWrapper
|
||||||
|
isWrapping={isDefined(target)}
|
||||||
|
wrapperProps={{ href: href }}
|
||||||
|
wrapper={LinkWrapper}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="relative"
|
||||||
|
onClick={() => {
|
||||||
|
if (isDefined(href) || isDefined(locale)) {
|
||||||
|
router.push(href ?? router.asPath, href, {
|
||||||
|
locale: locale,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
draggable={draggable}
|
draggable={draggable}
|
||||||
id={id}
|
id={id}
|
||||||
|
@ -52,40 +70,35 @@ export function Button(props: Immutable<Props>): JSX.Element {
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{badgeNumber && (
|
{isDefined(badgeNumber) && (
|
||||||
<div
|
<div
|
||||||
className="absolute -top-3 -right-2 grid h-8 w-8 place-items-center rounded-full bg-dark
|
className="absolute -top-3 -right-2 grid h-8 w-8 place-items-center rounded-full
|
||||||
font-bold text-light transition-opacity group-hover:opacity-0"
|
bg-dark font-bold text-light transition-opacity group-hover:opacity-0"
|
||||||
>
|
>
|
||||||
<p className="-translate-y-[0.05em]">{badgeNumber}</p>
|
<p className="-translate-y-[0.05em]">{badgeNumber}</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{icon && (
|
{isDefinedAndNotEmpty(icon) && (
|
||||||
<Ico className="[font-size:150%] [line-height:0.66]" icon={icon} />
|
<Ico className="[font-size:150%] [line-height:0.66]" icon={icon} />
|
||||||
)}
|
)}
|
||||||
{text && <p className="-translate-y-[0.05em] text-center">{text}</p>}
|
{isDefinedAndNotEmpty(text) && (
|
||||||
|
<p className="-translate-y-[0.05em] text-center">{text}</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</ConditionalWrapper>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (target) {
|
interface LinkWrapperProps {
|
||||||
|
href?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function LinkWrapper(props: LinkWrapperProps & Wrapper) {
|
||||||
|
const { children, href } = props;
|
||||||
return (
|
return (
|
||||||
<a href={href} target={target} rel="noreferrer">
|
<a href={href} target="_blank" rel="noreferrer">
|
||||||
<div className="relative">{button}</div>
|
{children}
|
||||||
</a>
|
</a>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className="relative"
|
|
||||||
onClick={() => {
|
|
||||||
if (href || locale)
|
|
||||||
router.push(href ?? router.asPath, href, {
|
|
||||||
locale: locale,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{button}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { cJoin } from "helpers/className";
|
import { cJoin } from "helpers/className";
|
||||||
import { Immutable } from "helpers/types";
|
import { Immutable } from "helpers/types";
|
||||||
import { useEffect, useRef } from "react";
|
import { useLayoutEffect, useRef } from "react";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
|
@ -11,7 +11,7 @@ export function ButtonGroup(props: Immutable<Props>): JSX.Element {
|
||||||
const { children, className } = props;
|
const { children, className } = props;
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useLayoutEffect(() => {
|
||||||
if (ref.current) {
|
if (ref.current) {
|
||||||
const buttons = ref.current.querySelectorAll(".component-button");
|
const buttons = ref.current.querySelectorAll(".component-button");
|
||||||
buttons.forEach((button, index) => {
|
buttons.forEach((button, index) => {
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { AppStaticProps } from "graphql/getAppStaticProps";
|
||||||
import { cJoin } from "helpers/className";
|
import { cJoin } from "helpers/className";
|
||||||
import { prettyLanguage } from "helpers/formatters";
|
import { prettyLanguage } from "helpers/formatters";
|
||||||
import { Immutable } from "helpers/types";
|
import { Immutable } from "helpers/types";
|
||||||
import { Dispatch, Fragment, SetStateAction } from "react";
|
import { Fragment } from "react";
|
||||||
import { ToolTip } from "../ToolTip";
|
import { ToolTip } from "../ToolTip";
|
||||||
import { Button } from "./Button";
|
import { Button } from "./Button";
|
||||||
|
|
||||||
|
@ -12,11 +12,11 @@ interface Props {
|
||||||
languages: AppStaticProps["languages"];
|
languages: AppStaticProps["languages"];
|
||||||
locales: Map<string, number>;
|
locales: Map<string, number>;
|
||||||
localesIndex: number | undefined;
|
localesIndex: number | undefined;
|
||||||
setLocalesIndex: Dispatch<SetStateAction<number | undefined>>;
|
onLanguageChanged: (index: number) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function LanguageSwitcher(props: Immutable<Props>): JSX.Element {
|
export function LanguageSwitcher(props: Immutable<Props>): JSX.Element {
|
||||||
const { locales, className, localesIndex, setLocalesIndex } = props;
|
const { locales, className, localesIndex, onLanguageChanged } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ToolTip
|
<ToolTip
|
||||||
|
@ -27,7 +27,7 @@ export function LanguageSwitcher(props: Immutable<Props>): JSX.Element {
|
||||||
{locale && (
|
{locale && (
|
||||||
<Button
|
<Button
|
||||||
active={value === localesIndex}
|
active={value === localesIndex}
|
||||||
onClick={() => setLocalesIndex(value)}
|
onClick={() => onLanguageChanged(value)}
|
||||||
text={prettyLanguage(locale, props.languages)}
|
text={prettyLanguage(locale, props.languages)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { Ico, Icon } from "components/Ico";
|
import { Ico, Icon } from "components/Ico";
|
||||||
import { arrayMove } from "helpers/others";
|
import { arrayMove, isDefinedAndNotEmpty } from "helpers/others";
|
||||||
import { Immutable } from "helpers/types";
|
import { Immutable } from "helpers/types";
|
||||||
import { Fragment, useEffect, useState } from "react";
|
import { Fragment, useCallback, useState } from "react";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
className?: string;
|
className?: string;
|
||||||
|
@ -11,23 +11,25 @@ interface Props {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function OrderableList(props: Immutable<Props>): JSX.Element {
|
export function OrderableList(props: Immutable<Props>): JSX.Element {
|
||||||
|
const { onChange } = props;
|
||||||
const [items, setItems] = useState<Map<string, string>>(props.items);
|
const [items, setItems] = useState<Map<string, string>>(props.items);
|
||||||
|
|
||||||
useEffect(() => {
|
const updateOrder = useCallback(
|
||||||
props.onChange?.(items);
|
(sourceIndex: number, targetIndex: number) => {
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [items]);
|
|
||||||
|
|
||||||
function updateOrder(sourceIndex: number, targetIndex: number) {
|
|
||||||
const newItems = arrayMove([...items], sourceIndex, targetIndex);
|
const newItems = arrayMove([...items], sourceIndex, targetIndex);
|
||||||
setItems(new Map(newItems));
|
const map = new Map(newItems);
|
||||||
}
|
setItems(map);
|
||||||
|
onChange?.(map);
|
||||||
|
},
|
||||||
|
[items, onChange]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
{[...items].map(([key, value], index) => (
|
{[...items].map(([key, value], index) => (
|
||||||
<Fragment key={key}>
|
<Fragment key={key}>
|
||||||
{props.insertLabels?.get(index) && (
|
{props.insertLabels &&
|
||||||
|
isDefinedAndNotEmpty(props.insertLabels.get(index)) && (
|
||||||
<p>{props.insertLabels.get(index)}</p>
|
<p>{props.insertLabels.get(index)}</p>
|
||||||
)}
|
)}
|
||||||
<div
|
<div
|
||||||
|
|
|
@ -17,9 +17,7 @@ export function PageSelector(props: Immutable<Props>): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<div className={cJoin("flex flex-row place-content-center", className)}>
|
<div className={cJoin("flex flex-row place-content-center", className)}>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => setPage((current) => (page > 0 ? current - 1 : current))}
|
||||||
if (page > 0) setPage(page - 1);
|
|
||||||
}}
|
|
||||||
className="rounded-r-none"
|
className="rounded-r-none"
|
||||||
icon={Icon.NavigateBefore}
|
icon={Icon.NavigateBefore}
|
||||||
/>
|
/>
|
||||||
|
@ -28,9 +26,9 @@ export function PageSelector(props: Immutable<Props>): JSX.Element {
|
||||||
text={(page + 1).toString()}
|
text={(page + 1).toString()}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() =>
|
||||||
if (page < maxPage) setPage(page + 1);
|
setPage((current) => (page < maxPage ? page + 1 : current))
|
||||||
}}
|
}
|
||||||
className="rounded-l-none"
|
className="rounded-l-none"
|
||||||
icon={Icon.NavigateNext}
|
icon={Icon.NavigateNext}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { Ico, Icon } from "components/Ico";
|
import { Ico, Icon } from "components/Ico";
|
||||||
import { cIf, cJoin } from "helpers/className";
|
import { cIf, cJoin } from "helpers/className";
|
||||||
import { Immutable } from "helpers/types";
|
import { Immutable } from "helpers/types";
|
||||||
|
import { useToggle } from "hooks/useToggle";
|
||||||
import { Dispatch, Fragment, SetStateAction, useState } from "react";
|
import { Dispatch, Fragment, SetStateAction, useState } from "react";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
@ -15,6 +16,7 @@ interface Props {
|
||||||
export function Select(props: Immutable<Props>): JSX.Element {
|
export function Select(props: Immutable<Props>): JSX.Element {
|
||||||
const { className, state, options, allowEmpty, setState } = props;
|
const { className, state, options, allowEmpty, setState } = props;
|
||||||
const [opened, setOpened] = useState(false);
|
const [opened, setOpened] = useState(false);
|
||||||
|
const toggleOpened = useToggle(setOpened);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
@ -32,18 +34,21 @@ export function Select(props: Immutable<Props>): JSX.Element {
|
||||||
cIf(opened, "rounded-b-none bg-highlight outline-[transparent]")
|
cIf(opened, "rounded-b-none bg-highlight outline-[transparent]")
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<p onClick={() => setOpened(!opened)} className="w-full">
|
<p onClick={toggleOpened} className="w-full">
|
||||||
{state === -1 ? "—" : options[state]}
|
{state === -1 ? "—" : options[state]}
|
||||||
</p>
|
</p>
|
||||||
{state >= 0 && allowEmpty && (
|
{state >= 0 && allowEmpty && (
|
||||||
<Ico
|
<Ico
|
||||||
icon={Icon.Close}
|
icon={Icon.Close}
|
||||||
className="!text-xs"
|
className="!text-xs"
|
||||||
onClick={() => setState(-1)}
|
onClick={() => {
|
||||||
|
setState(-1);
|
||||||
|
toggleOpened();
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<Ico
|
<Ico
|
||||||
onClick={() => setOpened(!opened)}
|
onClick={toggleOpened}
|
||||||
icon={opened ? Icon.ArrowDropUp : Icon.ArrowDropDown}
|
icon={opened ? Icon.ArrowDropUp : Icon.ArrowDropDown}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { cIf, cJoin } from "helpers/className";
|
import { cIf, cJoin } from "helpers/className";
|
||||||
import { Immutable } from "helpers/types";
|
import { Immutable } from "helpers/types";
|
||||||
|
import { useToggle } from "hooks/useToggle";
|
||||||
import { Dispatch, SetStateAction } from "react";
|
import { Dispatch, SetStateAction } from "react";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
@ -11,6 +12,7 @@ interface Props {
|
||||||
|
|
||||||
export function Switch(props: Immutable<Props>): JSX.Element {
|
export function Switch(props: Immutable<Props>): JSX.Element {
|
||||||
const { state, setState, className, disabled } = props;
|
const { state, setState, className, disabled } = props;
|
||||||
|
const toggleState = useToggle(setState);
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cJoin(
|
className={cJoin(
|
||||||
|
@ -24,7 +26,7 @@ export function Switch(props: Immutable<Props>): JSX.Element {
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (!disabled) setState(!state);
|
if (disabled === false) toggleState();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { Ico, Icon } from "components/Ico";
|
import { Ico, Icon } from "components/Ico";
|
||||||
import { cJoin } from "helpers/className";
|
import { cJoin } from "helpers/className";
|
||||||
|
import { isDefinedAndNotEmpty } from "helpers/others";
|
||||||
import { Immutable } from "helpers/types";
|
import { Immutable } from "helpers/types";
|
||||||
import { Dispatch, SetStateAction } from "react";
|
import { Dispatch, SetStateAction } from "react";
|
||||||
|
|
||||||
|
@ -28,8 +29,8 @@ export function TextInput(props: Immutable<Props>): JSX.Element {
|
||||||
setState(event.target.value);
|
setState(event.target.value);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
{isDefinedAndNotEmpty(state) && (
|
||||||
<div className="absolute right-4 top-0 bottom-0 grid place-items-center">
|
<div className="absolute right-4 top-0 bottom-0 grid place-items-center">
|
||||||
{state && (
|
|
||||||
<Ico
|
<Ico
|
||||||
className="cursor-pointer !text-xs"
|
className="cursor-pointer !text-xs"
|
||||||
icon={Icon.Close}
|
icon={Icon.Close}
|
||||||
|
@ -37,8 +38,8 @@ export function TextInput(props: Immutable<Props>): JSX.Element {
|
||||||
setState("");
|
setState("");
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { cIf, cJoin } from "helpers/className";
|
import { cIf, cJoin } from "helpers/className";
|
||||||
|
import { isDefinedAndNotEmpty } from "helpers/others";
|
||||||
import { Immutable } from "helpers/types";
|
import { Immutable } from "helpers/types";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
@ -16,7 +17,7 @@ export function WithLabel(props: Immutable<Props>): JSX.Element {
|
||||||
cIf(disabled, "text-dark brightness-150 contrast-75 grayscale")
|
cIf(disabled, "text-dark brightness-150 contrast-75 grayscale")
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{label && (
|
{isDefinedAndNotEmpty(label) && (
|
||||||
<p
|
<p
|
||||||
className={cJoin(
|
className={cJoin(
|
||||||
"text-left",
|
"text-left",
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { GetLibraryItemQuery } from "graphql/generated";
|
||||||
import { AppStaticProps } from "graphql/getAppStaticProps";
|
import { AppStaticProps } from "graphql/getAppStaticProps";
|
||||||
import { prettyinlineTitle, prettySlug } from "helpers/formatters";
|
import { prettyinlineTitle, prettySlug } from "helpers/formatters";
|
||||||
import { Immutable } from "helpers/types";
|
import { Immutable } from "helpers/types";
|
||||||
|
import { useToggle } from "hooks/useToggle";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
@ -23,6 +24,7 @@ export function ContentLine(props: Immutable<Props>): JSX.Element {
|
||||||
const { content, langui, parentSlug } = props;
|
const { content, langui, parentSlug } = props;
|
||||||
|
|
||||||
const [opened, setOpened] = useState(false);
|
const [opened, setOpened] = useState(false);
|
||||||
|
const toggleOpened = useToggle(setOpened);
|
||||||
|
|
||||||
if (content.attributes) {
|
if (content.attributes) {
|
||||||
return (
|
return (
|
||||||
|
@ -36,7 +38,7 @@ export function ContentLine(props: Immutable<Props>): JSX.Element {
|
||||||
gap-4 thin:grid-cols-[auto_auto_1fr_auto]"
|
gap-4 thin:grid-cols-[auto_auto_1fr_auto]"
|
||||||
>
|
>
|
||||||
<a>
|
<a>
|
||||||
<h3 className="cursor-pointer" onClick={() => setOpened(!opened)}>
|
<h3 className="cursor-pointer" onClick={toggleOpened}>
|
||||||
{content.attributes.content?.data?.attributes?.translations?.[0]
|
{content.attributes.content?.data?.attributes?.translations?.[0]
|
||||||
? prettyinlineTitle(
|
? prettyinlineTitle(
|
||||||
content.attributes.content.data.attributes.translations[0]
|
content.attributes.content.data.attributes.translations[0]
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { GetLibraryItemScansQuery } from "graphql/generated";
|
||||||
import { AppStaticProps } from "graphql/getAppStaticProps";
|
import { AppStaticProps } from "graphql/getAppStaticProps";
|
||||||
import { getAssetFilename, getAssetURL, ImageQuality } from "helpers/img";
|
import { getAssetFilename, getAssetURL, ImageQuality } from "helpers/img";
|
||||||
import { isInteger } from "helpers/numbers";
|
import { isInteger } from "helpers/numbers";
|
||||||
import { getStatusDescription } from "helpers/others";
|
import { getStatusDescription, isDefinedAndNotEmpty } from "helpers/others";
|
||||||
import { Immutable } from "helpers/types";
|
import { Immutable } from "helpers/types";
|
||||||
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
||||||
import { Fragment } from "react";
|
import { Fragment } from "react";
|
||||||
|
@ -51,7 +51,12 @@ export function ScanSet(props: Immutable<Props>): JSX.Element {
|
||||||
transform: (item) => {
|
transform: (item) => {
|
||||||
(item as NonNullable<Props["scanSet"][number]>).pages?.data.sort(
|
(item as NonNullable<Props["scanSet"][number]>).pages?.data.sort(
|
||||||
(a, b) => {
|
(a, b) => {
|
||||||
if (a.attributes?.url && b.attributes?.url) {
|
if (
|
||||||
|
a.attributes &&
|
||||||
|
b.attributes &&
|
||||||
|
isDefinedAndNotEmpty(a.attributes.url) &&
|
||||||
|
isDefinedAndNotEmpty(b.attributes.url)
|
||||||
|
) {
|
||||||
let aName = getAssetFilename(a.attributes.url);
|
let aName = getAssetFilename(a.attributes.url);
|
||||||
let bName = getAssetFilename(b.attributes.url);
|
let bName = getAssetFilename(b.attributes.url);
|
||||||
|
|
||||||
|
@ -99,7 +104,8 @@ export function ScanSet(props: Immutable<Props>): JSX.Element {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-row flex-wrap place-items-center gap-4 pb-6">
|
<div className="flex flex-row flex-wrap place-items-center gap-4 pb-6">
|
||||||
{content?.data?.attributes?.slug && (
|
{content?.data?.attributes &&
|
||||||
|
isDefinedAndNotEmpty(content.data.attributes.slug) && (
|
||||||
<Button
|
<Button
|
||||||
href={`/contents/${content.data.attributes.slug}`}
|
href={`/contents/${content.data.attributes.slug}`}
|
||||||
text={langui.open_content}
|
text={langui.open_content}
|
||||||
|
@ -173,7 +179,7 @@ export function ScanSet(props: Immutable<Props>): JSX.Element {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{selectedScan.notes && (
|
{isDefinedAndNotEmpty(selectedScan.notes) && (
|
||||||
<ToolTip content={selectedScan.notes}>
|
<ToolTip content={selectedScan.notes}>
|
||||||
<Chip>{"Notes"}</Chip>
|
<Chip>{"Notes"}</Chip>
|
||||||
</ToolTip>
|
</ToolTip>
|
||||||
|
@ -192,7 +198,10 @@ export function ScanSet(props: Immutable<Props>): JSX.Element {
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const images: string[] = [];
|
const images: string[] = [];
|
||||||
selectedScan.pages?.data.map((image) => {
|
selectedScan.pages?.data.map((image) => {
|
||||||
if (image.attributes?.url)
|
if (
|
||||||
|
image.attributes &&
|
||||||
|
isDefinedAndNotEmpty(image.attributes.url)
|
||||||
|
)
|
||||||
images.push(
|
images.push(
|
||||||
getAssetURL(image.attributes.url, ImageQuality.Large)
|
getAssetURL(image.attributes.url, ImageQuality.Large)
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { Immutable } from "helpers/types";
|
import { Immutable } from "helpers/types";
|
||||||
import { Dispatch, SetStateAction } from "react";
|
import { Dispatch, SetStateAction, useCallback } from "react";
|
||||||
import Hotkeys from "react-hot-keys";
|
import Hotkeys from "react-hot-keys";
|
||||||
import { useSwipeable } from "react-swipeable";
|
import { useSwipeable } from "react-swipeable";
|
||||||
import { Img } from "./Img";
|
import { Img } from "./Img";
|
||||||
|
@ -17,26 +17,26 @@ interface Props {
|
||||||
setIndex: Dispatch<SetStateAction<number>>;
|
setIndex: Dispatch<SetStateAction<number>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const SENSIBILITY_SWIPE = 0.5;
|
||||||
|
|
||||||
export function LightBox(props: Immutable<Props>): JSX.Element {
|
export function LightBox(props: Immutable<Props>): JSX.Element {
|
||||||
const { state, setState, images, index, setIndex } = props;
|
const { state, setState, images, index, setIndex } = props;
|
||||||
|
|
||||||
function handlePrevious() {
|
const handlePrevious = useCallback(() => {
|
||||||
if (index > 0) setIndex(index - 1);
|
if (index > 0) setIndex(index - 1);
|
||||||
}
|
}, [index, setIndex]);
|
||||||
|
|
||||||
function handleNext() {
|
const handleNext = useCallback(() => {
|
||||||
if (index < images.length - 1) setIndex(index + 1);
|
if (index < images.length - 1) setIndex(index + 1);
|
||||||
}
|
}, [images.length, index, setIndex]);
|
||||||
|
|
||||||
const sensibilitySwipe = 0.5;
|
|
||||||
|
|
||||||
const handlers = useSwipeable({
|
const handlers = useSwipeable({
|
||||||
onSwipedLeft: (SwipeEventData) => {
|
onSwipedLeft: (SwipeEventData) => {
|
||||||
if (SwipeEventData.velocity < sensibilitySwipe) return;
|
if (SwipeEventData.velocity < SENSIBILITY_SWIPE) return;
|
||||||
handleNext();
|
handleNext();
|
||||||
},
|
},
|
||||||
onSwipedRight: (SwipeEventData) => {
|
onSwipedRight: (SwipeEventData) => {
|
||||||
if (SwipeEventData.velocity < sensibilitySwipe) return;
|
if (SwipeEventData.velocity < SENSIBILITY_SWIPE) return;
|
||||||
handlePrevious();
|
handlePrevious();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -7,11 +7,12 @@ import { useAppLayout } from "contexts/AppLayoutContext";
|
||||||
import { cJoin } from "helpers/className";
|
import { cJoin } from "helpers/className";
|
||||||
import { slugify } from "helpers/formatters";
|
import { slugify } from "helpers/formatters";
|
||||||
import { getAssetURL, ImageQuality } from "helpers/img";
|
import { getAssetURL, ImageQuality } from "helpers/img";
|
||||||
|
import { isDefined, isDefinedAndNotEmpty } from "helpers/others";
|
||||||
import { Immutable } from "helpers/types";
|
import { Immutable } from "helpers/types";
|
||||||
import { useLightBox } from "hooks/useLightBox";
|
import { useLightBox } from "hooks/useLightBox";
|
||||||
import Markdown from "markdown-to-jsx";
|
import Markdown from "markdown-to-jsx";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import React from "react";
|
import React, { useMemo } from "react";
|
||||||
import ReactDOMServer from "react-dom/server";
|
import ReactDOMServer from "react-dom/server";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
@ -20,20 +21,20 @@ interface Props {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Markdawn(props: Immutable<Props>): JSX.Element {
|
export function Markdawn(props: Immutable<Props>): JSX.Element {
|
||||||
|
const { className, text: rawText } = props;
|
||||||
const appLayout = useAppLayout();
|
const appLayout = useAppLayout();
|
||||||
// eslint-disable-next-line no-irregular-whitespace
|
|
||||||
const text = `${preprocessMarkDawn(props.text)}`;
|
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const [openLightBox, LightBox] = useLightBox();
|
const [openLightBox, LightBox] = useLightBox();
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-irregular-whitespace
|
||||||
|
const text = useMemo(() => `${preprocessMarkDawn(rawText)}`, [rawText]);
|
||||||
|
|
||||||
if (text) {
|
if (text) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<LightBox />
|
<LightBox />
|
||||||
<Markdown
|
<Markdown
|
||||||
className={cJoin("formatted", props.className)}
|
className={cJoin("formatted", className)}
|
||||||
options={{
|
options={{
|
||||||
slugify: slugify,
|
slugify: slugify,
|
||||||
overrides: {
|
overrides: {
|
||||||
|
@ -155,15 +156,13 @@ export function Markdawn(props: Immutable<Props>): JSX.Element {
|
||||||
target?: string;
|
target?: string;
|
||||||
page?: string;
|
page?: string;
|
||||||
}) => {
|
}) => {
|
||||||
const slug = compProps.target
|
const slug = isDefinedAndNotEmpty(compProps.target)
|
||||||
? slugify(compProps.target)
|
? slugify(compProps.target)
|
||||||
: slugify(compProps.children?.toString());
|
: slugify(compProps.children?.toString());
|
||||||
return (
|
return (
|
||||||
<a
|
<a
|
||||||
onClick={async () =>
|
onClick={async () =>
|
||||||
router.replace(
|
router.replace(`${compProps.page ?? ""}#${slug}`)
|
||||||
`${compProps.page ? compProps.page : ""}#${slug}`
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{compProps.children}
|
{compProps.children}
|
||||||
|
@ -175,7 +174,7 @@ export function Markdawn(props: Immutable<Props>): JSX.Element {
|
||||||
player: {
|
player: {
|
||||||
component: () => (
|
component: () => (
|
||||||
<span className="text-dark opacity-70">
|
<span className="text-dark opacity-70">
|
||||||
{appLayout.playerName ? appLayout.playerName : "<player>"}
|
{appLayout.playerName ?? "<player>"}
|
||||||
</span>
|
</span>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
@ -209,7 +208,7 @@ export function Markdawn(props: Immutable<Props>): JSX.Element {
|
||||||
component: (compProps: { children: React.ReactNode }) => (
|
component: (compProps: { children: React.ReactNode }) => (
|
||||||
<li
|
<li
|
||||||
className={
|
className={
|
||||||
compProps.children &&
|
isDefined(compProps.children) &&
|
||||||
ReactDOMServer.renderToStaticMarkup(
|
ReactDOMServer.renderToStaticMarkup(
|
||||||
<>{compProps.children}</>
|
<>{compProps.children}</>
|
||||||
).length > 100
|
).length > 100
|
||||||
|
@ -243,7 +242,7 @@ export function Markdawn(props: Immutable<Props>): JSX.Element {
|
||||||
cite?: string;
|
cite?: string;
|
||||||
}) => (
|
}) => (
|
||||||
<blockquote>
|
<blockquote>
|
||||||
{compProps.cite ? (
|
{isDefinedAndNotEmpty(compProps.cite) ? (
|
||||||
<>
|
<>
|
||||||
“{compProps.children}”
|
“{compProps.children}”
|
||||||
<cite>— {compProps.cite}</cite>
|
<cite>— {compProps.cite}</cite>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { slugify } from "helpers/formatters";
|
import { slugify } from "helpers/formatters";
|
||||||
import { Immutable } from "helpers/types";
|
import { Immutable } from "helpers/types";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { Fragment } from "react";
|
import { Fragment, useMemo } from "react";
|
||||||
import { preprocessMarkDawn } from "./Markdawn";
|
import { preprocessMarkDawn } from "./Markdawn";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
@ -11,11 +11,15 @@ interface Props {
|
||||||
|
|
||||||
export function TOC(props: Immutable<Props>): JSX.Element {
|
export function TOC(props: Immutable<Props>): JSX.Element {
|
||||||
const { text, title } = props;
|
const { text, title } = props;
|
||||||
const toc = getTocFromMarkdawn(preprocessMarkDawn(text), title);
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const toc = useMemo(
|
||||||
|
() => getTocFromMarkdawn(preprocessMarkDawn(text), title),
|
||||||
|
[text, title]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
{/* TODO: add to LANGUI */}
|
||||||
<h3 className="text-xl">Table of content</h3>
|
<h3 className="text-xl">Table of content</h3>
|
||||||
<div className="max-w-[14.5rem] text-left">
|
<div className="max-w-[14.5rem] text-left">
|
||||||
<p className="relative my-2 overflow-x-hidden text-ellipsis whitespace-nowrap text-left">
|
<p className="relative my-2 overflow-x-hidden text-ellipsis whitespace-nowrap text-left">
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import { Ico, Icon } from "components/Ico";
|
import { Ico, Icon } from "components/Ico";
|
||||||
import { ToolTip } from "components/ToolTip";
|
import { ToolTip } from "components/ToolTip";
|
||||||
import { cJoin, cIf } from "helpers/className";
|
import { cJoin, cIf } from "helpers/className";
|
||||||
|
import { isDefinedAndNotEmpty } from "helpers/others";
|
||||||
import { Immutable } from "helpers/types";
|
import { Immutable } from "helpers/types";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { MouseEventHandler } from "react";
|
import { MouseEventHandler, useMemo } from "react";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
url: string;
|
url: string;
|
||||||
|
@ -16,29 +17,35 @@ interface Props {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function NavOption(props: Immutable<Props>): JSX.Element {
|
export function NavOption(props: Immutable<Props>): JSX.Element {
|
||||||
|
const { url, icon, title, subtitle, border, reduced, onClick } = props;
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const isActive = router.asPath.startsWith(props.url);
|
const isActive = useMemo(
|
||||||
|
() => router.asPath.startsWith(url),
|
||||||
|
[url, router.asPath]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ToolTip
|
<ToolTip
|
||||||
content={
|
content={
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-2xl">{props.title}</h3>
|
<h3 className="text-2xl">{title}</h3>
|
||||||
{props.subtitle && <p className="col-start-2">{props.subtitle}</p>}
|
{isDefinedAndNotEmpty(subtitle) && (
|
||||||
|
<p className="col-start-2">{subtitle}</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
placement="right"
|
placement="right"
|
||||||
className="text-left"
|
className="text-left"
|
||||||
disabled={!props.reduced}
|
disabled={reduced === false}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
onClick={(event) => {
|
onClick={(event) => {
|
||||||
if (props.onClick) props.onClick(event);
|
if (onClick) onClick(event);
|
||||||
if (props.url) {
|
if (url) {
|
||||||
if (props.url.startsWith("#")) {
|
if (url.startsWith("#")) {
|
||||||
router.replace(props.url);
|
router.replace(url);
|
||||||
} else {
|
} else {
|
||||||
router.push(props.url);
|
router.push(url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
@ -46,22 +53,20 @@ export function NavOption(props: Immutable<Props>): JSX.Element {
|
||||||
`relative grid w-full cursor-pointer auto-cols-fr grid-flow-col grid-cols-[auto]
|
`relative grid w-full cursor-pointer auto-cols-fr grid-flow-col grid-cols-[auto]
|
||||||
justify-center gap-x-5 rounded-2xl p-4 transition-all hover:bg-mid hover:shadow-inner-sm
|
justify-center gap-x-5 rounded-2xl p-4 transition-all hover:bg-mid hover:shadow-inner-sm
|
||||||
hover:shadow-shade hover:active:shadow-inner hover:active:shadow-shade`,
|
hover:shadow-shade hover:active:shadow-inner hover:active:shadow-shade`,
|
||||||
cIf(props.icon, "text-left", "text-center"),
|
cIf(icon, "text-left", "text-center"),
|
||||||
cIf(
|
cIf(
|
||||||
props.border,
|
border,
|
||||||
"outline outline-2 outline-offset-[-2px] outline-mid hover:outline-[transparent]"
|
"outline outline-2 outline-offset-[-2px] outline-mid hover:outline-[transparent]"
|
||||||
),
|
),
|
||||||
cIf(isActive, "bg-mid shadow-inner-sm shadow-shade")
|
cIf(isActive, "bg-mid shadow-inner-sm shadow-shade")
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{props.icon && (
|
{icon && <Ico icon={icon} className="mt-[-.1em] !text-2xl" />}
|
||||||
<Ico icon={props.icon} className="mt-[-.1em] !text-2xl" />
|
|
||||||
)}
|
|
||||||
|
|
||||||
{!props.reduced && (
|
{reduced === false && (
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-2xl">{props.title}</h3>
|
<h3 className="text-2xl">{title}</h3>
|
||||||
{props.subtitle && <p className="col-start-2">{props.subtitle}</p>}
|
{isDefinedAndNotEmpty(subtitle) && <p className="col-start-2">{subtitle}</p>}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { HorizontalLine } from "components/HorizontalLine";
|
import { HorizontalLine } from "components/HorizontalLine";
|
||||||
import { Ico, Icon } from "components/Ico";
|
import { Ico, Icon } from "components/Ico";
|
||||||
|
import { isDefinedAndNotEmpty } from "helpers/others";
|
||||||
import { Immutable } from "helpers/types";
|
import { Immutable } from "helpers/types";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
@ -9,12 +10,13 @@ interface Props {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function PanelHeader(props: Immutable<Props>): JSX.Element {
|
export function PanelHeader(props: Immutable<Props>): JSX.Element {
|
||||||
|
const { icon, description, title } = props;
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="grid w-full place-items-center">
|
<div className="grid w-full place-items-center">
|
||||||
{props.icon && <Ico icon={props.icon} className="mb-3 !text-4xl" />}
|
{icon && <Ico icon={icon} className="mb-3 !text-4xl" />}
|
||||||
<h2 className="text-2xl">{props.title}</h2>
|
<h2 className="text-2xl">{title}</h2>
|
||||||
{props.description ? <p>{props.description}</p> : ""}
|
{isDefinedAndNotEmpty(description) && <p>{description}</p>}
|
||||||
</div>
|
</div>
|
||||||
<HorizontalLine />
|
<HorizontalLine />
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -22,26 +22,27 @@ export enum ReturnButtonType {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ReturnButton(props: Immutable<Props>): JSX.Element {
|
export function ReturnButton(props: Immutable<Props>): JSX.Element {
|
||||||
|
const { href, title, langui, displayOn, horizontalLine, className } = props;
|
||||||
const appLayout = useAppLayout();
|
const appLayout = useAppLayout();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cJoin(
|
className={cJoin(
|
||||||
props.displayOn === ReturnButtonType.Mobile
|
displayOn === ReturnButtonType.Mobile
|
||||||
? "desktop:hidden"
|
? "desktop:hidden"
|
||||||
: props.displayOn === ReturnButtonType.Desktop
|
: displayOn === ReturnButtonType.Desktop
|
||||||
? "mobile:hidden"
|
? "mobile:hidden"
|
||||||
: "",
|
: "",
|
||||||
props.className
|
className
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => appLayout.setSubPanelOpen(false)}
|
onClick={() => appLayout.setSubPanelOpen(false)}
|
||||||
href={props.href}
|
href={href}
|
||||||
text={`${props.langui.return_to} ${props.title}`}
|
text={`${langui.return_to} ${title}`}
|
||||||
icon={Icon.NavigateBefore}
|
icon={Icon.NavigateBefore}
|
||||||
/>
|
/>
|
||||||
{props.horizontalLine && <HorizontalLine />}
|
{horizontalLine === true && <HorizontalLine />}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import Markdown from "markdown-to-jsx";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { Icon } from "components/Ico";
|
import { Icon } from "components/Ico";
|
||||||
import { cIf, cJoin } from "helpers/className";
|
import { cIf, cJoin } from "helpers/className";
|
||||||
|
import { isDefinedAndNotEmpty } from "helpers/others";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
langui: AppStaticProps["langui"];
|
langui: AppStaticProps["langui"];
|
||||||
|
@ -18,30 +19,30 @@ interface Props {
|
||||||
export function MainPanel(props: Immutable<Props>): JSX.Element {
|
export function MainPanel(props: Immutable<Props>): JSX.Element {
|
||||||
const { langui } = props;
|
const { langui } = props;
|
||||||
const isDesktop = useMediaDesktop();
|
const isDesktop = useMediaDesktop();
|
||||||
const appLayout = useAppLayout();
|
const {
|
||||||
|
mainPanelReduced = false,
|
||||||
|
toggleMainPanelReduced,
|
||||||
|
setConfigPanelOpen,
|
||||||
|
} = useAppLayout();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cJoin(
|
className={cJoin(
|
||||||
"grid content-start justify-center gap-y-2 p-8 text-center",
|
"grid content-start justify-center gap-y-2 p-8 text-center",
|
||||||
cIf(appLayout.mainPanelReduced && isDesktop, "px-4")
|
cIf(mainPanelReduced && isDesktop, "px-4")
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{/* Reduce/expand main menu */}
|
{/* Reduce/expand main menu */}
|
||||||
<div
|
<div
|
||||||
className={cJoin(
|
className={cJoin(
|
||||||
"fixed top-1/2 mobile:hidden",
|
"fixed top-1/2 mobile:hidden",
|
||||||
cIf(appLayout.mainPanelReduced, "left-[4.65rem]", "left-[18.65rem]")
|
cIf(mainPanelReduced, "left-[4.65rem]", "left-[18.65rem]")
|
||||||
)}
|
)}
|
||||||
onClick={() =>
|
onClick={toggleMainPanelReduced}
|
||||||
appLayout.setMainPanelReduced(!appLayout.mainPanelReduced)
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
className="bg-light !px-2"
|
className="bg-light !px-2"
|
||||||
icon={
|
icon={mainPanelReduced ? Icon.ChevronRight : Icon.ChevronLeft}
|
||||||
appLayout.mainPanelReduced ? Icon.ChevronRight : Icon.ChevronLeft
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -53,34 +54,30 @@ export function MainPanel(props: Immutable<Props>): JSX.Element {
|
||||||
`mb-4 aspect-square cursor-pointer bg-black transition-colors
|
`mb-4 aspect-square cursor-pointer bg-black transition-colors
|
||||||
[mask:url('/icons/accords.svg')] ![mask-size:contain] ![mask-repeat:no-repeat]
|
[mask:url('/icons/accords.svg')] ![mask-size:contain] ![mask-repeat:no-repeat]
|
||||||
![mask-position:center] hover:bg-dark`,
|
![mask-position:center] hover:bg-dark`,
|
||||||
cIf(appLayout.mainPanelReduced && isDesktop, "w-12", "w-1/2")
|
cIf(mainPanelReduced && isDesktop, "w-12", "w-1/2")
|
||||||
)}
|
)}
|
||||||
></div>
|
></div>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
{(!appLayout.mainPanelReduced || !isDesktop) && (
|
{(!mainPanelReduced || !isDesktop) && (
|
||||||
<h2 className="text-3xl">Accord’s Library</h2>
|
<h2 className="text-3xl">Accord’s Library</h2>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className={cJoin(
|
className={cJoin(
|
||||||
"flex flex-wrap gap-2",
|
"flex flex-wrap gap-2",
|
||||||
cIf(
|
cIf(mainPanelReduced && isDesktop, "flex-col gap-3", "flex-row")
|
||||||
appLayout.mainPanelReduced && isDesktop,
|
|
||||||
"flex-col gap-3",
|
|
||||||
"flex-row"
|
|
||||||
)
|
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<ToolTip
|
<ToolTip
|
||||||
content={<h3 className="text-2xl">{langui.open_settings}</h3>}
|
content={<h3 className="text-2xl">{langui.open_settings}</h3>}
|
||||||
placement="right"
|
placement="right"
|
||||||
className="text-left"
|
className="text-left"
|
||||||
disabled={!appLayout.mainPanelReduced}
|
disabled={!mainPanelReduced}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
appLayout.setConfigPanelOpen(true);
|
setConfigPanelOpen(true);
|
||||||
}}
|
}}
|
||||||
icon={Icon.Settings}
|
icon={Icon.Settings}
|
||||||
/>
|
/>
|
||||||
|
@ -90,11 +87,11 @@ export function MainPanel(props: Immutable<Props>): JSX.Element {
|
||||||
content={<h3 className="text-2xl">{langui.open_search}</h3>}
|
content={<h3 className="text-2xl">{langui.open_search}</h3>}
|
||||||
placement="right"
|
placement="right"
|
||||||
className="text-left"
|
className="text-left"
|
||||||
disabled={!appLayout.mainPanelReduced}
|
disabled={!mainPanelReduced}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
appLayout.setSearchPanelOpen(true);
|
setSearchPanelOpen(true);
|
||||||
}}
|
}}
|
||||||
icon={Icon.Search}
|
icon={Icon.Search}
|
||||||
/>
|
/>
|
||||||
|
@ -110,7 +107,7 @@ export function MainPanel(props: Immutable<Props>): JSX.Element {
|
||||||
icon={Icon.LibraryBooks}
|
icon={Icon.LibraryBooks}
|
||||||
title={langui.library}
|
title={langui.library}
|
||||||
subtitle={langui.library_short_description}
|
subtitle={langui.library_short_description}
|
||||||
reduced={appLayout.mainPanelReduced && isDesktop}
|
reduced={mainPanelReduced && isDesktop}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<NavOption
|
<NavOption
|
||||||
|
@ -118,7 +115,7 @@ export function MainPanel(props: Immutable<Props>): JSX.Element {
|
||||||
icon={Icon.Workspaces}
|
icon={Icon.Workspaces}
|
||||||
title={langui.contents}
|
title={langui.contents}
|
||||||
subtitle={langui.contents_short_description}
|
subtitle={langui.contents_short_description}
|
||||||
reduced={appLayout.mainPanelReduced && isDesktop}
|
reduced={mainPanelReduced && isDesktop}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<NavOption
|
<NavOption
|
||||||
|
@ -126,7 +123,7 @@ export function MainPanel(props: Immutable<Props>): JSX.Element {
|
||||||
icon={Icon.TravelExplore}
|
icon={Icon.TravelExplore}
|
||||||
title={langui.wiki}
|
title={langui.wiki}
|
||||||
subtitle={langui.wiki_short_description}
|
subtitle={langui.wiki_short_description}
|
||||||
reduced={appLayout.mainPanelReduced && isDesktop}
|
reduced={mainPanelReduced && isDesktop}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/*
|
{/*
|
||||||
|
@ -135,7 +132,7 @@ export function MainPanel(props: Immutable<Props>): JSX.Element {
|
||||||
icon={Icon.WatchLater}
|
icon={Icon.WatchLater}
|
||||||
title={langui.chronicles}
|
title={langui.chronicles}
|
||||||
subtitle={langui.chronicles_short_description}
|
subtitle={langui.chronicles_short_description}
|
||||||
reduced={appLayout.mainPanelReduced && isDesktop}
|
reduced={mainPanelReduced && isDesktop}
|
||||||
/>
|
/>
|
||||||
*/}
|
*/}
|
||||||
|
|
||||||
|
@ -145,7 +142,7 @@ export function MainPanel(props: Immutable<Props>): JSX.Element {
|
||||||
url="/news"
|
url="/news"
|
||||||
icon={Icon.Feed}
|
icon={Icon.Feed}
|
||||||
title={langui.news}
|
title={langui.news}
|
||||||
reduced={appLayout.mainPanelReduced && isDesktop}
|
reduced={mainPanelReduced && isDesktop}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/*
|
{/*
|
||||||
|
@ -153,7 +150,7 @@ export function MainPanel(props: Immutable<Props>): JSX.Element {
|
||||||
url="/merch"
|
url="/merch"
|
||||||
icon={Icon.Store}
|
icon={Icon.Store}
|
||||||
title={langui.merch}
|
title={langui.merch}
|
||||||
reduced={appLayout.mainPanelReduced && isDesktop}
|
reduced={mainPanelReduced && isDesktop}
|
||||||
/>
|
/>
|
||||||
*/}
|
*/}
|
||||||
|
|
||||||
|
@ -161,36 +158,36 @@ export function MainPanel(props: Immutable<Props>): JSX.Element {
|
||||||
url="https://gallery.accords-library.com/"
|
url="https://gallery.accords-library.com/"
|
||||||
icon={Icon.Collections}
|
icon={Icon.Collections}
|
||||||
title={langui.gallery}
|
title={langui.gallery}
|
||||||
reduced={appLayout.mainPanelReduced && isDesktop}
|
reduced={mainPanelReduced && isDesktop}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<NavOption
|
<NavOption
|
||||||
url="/archives"
|
url="/archives"
|
||||||
icon={Icon.Inventory}
|
icon={Icon.Inventory}
|
||||||
title={langui.archives}
|
title={langui.archives}
|
||||||
reduced={appLayout.mainPanelReduced && isDesktop}
|
reduced={mainPanelReduced && isDesktop}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<NavOption
|
<NavOption
|
||||||
url="/about-us"
|
url="/about-us"
|
||||||
icon={Icon.Info}
|
icon={Icon.Info}
|
||||||
title={langui.about_us}
|
title={langui.about_us}
|
||||||
reduced={appLayout.mainPanelReduced && isDesktop}
|
reduced={mainPanelReduced && isDesktop}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{appLayout.mainPanelReduced && isDesktop ? "" : <HorizontalLine />}
|
{mainPanelReduced && isDesktop ? "" : <HorizontalLine />}
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className={cJoin(
|
className={cJoin(
|
||||||
"text-center",
|
"text-center",
|
||||||
cIf(appLayout.mainPanelReduced && isDesktop, "hidden")
|
cIf(mainPanelReduced && isDesktop, "hidden")
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
{isDefinedAndNotEmpty(langui.licensing_notice) && (
|
||||||
<p>
|
<p>
|
||||||
{langui.licensing_notice && (
|
|
||||||
<Markdown>{langui.licensing_notice}</Markdown>
|
<Markdown>{langui.licensing_notice}</Markdown>
|
||||||
)}
|
|
||||||
</p>
|
</p>
|
||||||
|
)}
|
||||||
<div className="mt-4 mb-8 grid place-content-center">
|
<div className="mt-4 mb-8 grid place-content-center">
|
||||||
<a
|
<a
|
||||||
aria-label="Read more about the license we use for this website"
|
aria-label="Read more about the license we use for this website"
|
||||||
|
@ -214,11 +211,11 @@ export function MainPanel(props: Immutable<Props>): JSX.Element {
|
||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
{isDefinedAndNotEmpty(langui.copyright_notice) && (
|
||||||
<p>
|
<p>
|
||||||
{langui.copyright_notice && (
|
|
||||||
<Markdown>{langui.copyright_notice}</Markdown>
|
<Markdown>{langui.copyright_notice}</Markdown>
|
||||||
)}
|
|
||||||
</p>
|
</p>
|
||||||
|
)}
|
||||||
<div className="mt-12 mb-4 grid h-4 grid-flow-col place-content-center gap-8">
|
<div className="mt-12 mb-4 grid h-4 grid-flow-col place-content-center gap-8">
|
||||||
<a
|
<a
|
||||||
aria-label="Browse our GitHub repository, which include this website source code"
|
aria-label="Browse our GitHub repository, which include this website source code"
|
||||||
|
|
|
@ -8,7 +8,7 @@ interface Props {
|
||||||
setState:
|
setState:
|
||||||
| Dispatch<SetStateAction<boolean | undefined>>
|
| Dispatch<SetStateAction<boolean | undefined>>
|
||||||
| Dispatch<SetStateAction<boolean>>;
|
| Dispatch<SetStateAction<boolean>>;
|
||||||
state?: boolean;
|
state: boolean;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
fillViewport?: boolean;
|
fillViewport?: boolean;
|
||||||
hideBackground?: boolean;
|
hideBackground?: boolean;
|
||||||
|
@ -21,16 +21,15 @@ export function Popup(props: Immutable<Props>): JSX.Element {
|
||||||
state,
|
state,
|
||||||
children,
|
children,
|
||||||
fillViewport,
|
fillViewport,
|
||||||
hideBackground,
|
hideBackground = false,
|
||||||
padding = true,
|
padding = true,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const appLayout = useAppLayout();
|
const { setMenuGestures } = useAppLayout();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
appLayout.setMenuGestures(!state);
|
setMenuGestures(state);
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
}, [setMenuGestures, state]);
|
||||||
}, [state]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Hotkeys
|
<Hotkeys
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { prettySlug } from "helpers/formatters";
|
||||||
import { getStatusDescription } from "helpers/others";
|
import { getStatusDescription } from "helpers/others";
|
||||||
import { Immutable, PostWithTranslations } from "helpers/types";
|
import { Immutable, PostWithTranslations } from "helpers/types";
|
||||||
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
||||||
import { Fragment } from "react";
|
import { Fragment, useMemo } from "react";
|
||||||
import { AppLayout } from "./AppLayout";
|
import { AppLayout } from "./AppLayout";
|
||||||
import { Chip } from "./Chip";
|
import { Chip } from "./Chip";
|
||||||
import { HorizontalLine } from "./HorizontalLine";
|
import { HorizontalLine } from "./HorizontalLine";
|
||||||
|
@ -55,13 +55,17 @@ export function PostPage(props: Immutable<Props>): JSX.Element {
|
||||||
languageExtractor: (item) => item.language?.data?.attributes?.code,
|
languageExtractor: (item) => item.language?.data?.attributes?.code,
|
||||||
});
|
});
|
||||||
|
|
||||||
const thumbnail =
|
const { thumbnail, body, title, excerpt } = useMemo(
|
||||||
|
() => ({
|
||||||
|
thumbnail:
|
||||||
selectedTranslation?.thumbnail?.data?.attributes ??
|
selectedTranslation?.thumbnail?.data?.attributes ??
|
||||||
post.thumbnail?.data?.attributes;
|
post.thumbnail?.data?.attributes,
|
||||||
|
body: selectedTranslation?.body ?? "",
|
||||||
const body = selectedTranslation?.body ?? "";
|
title: selectedTranslation?.title ?? prettySlug(post.slug),
|
||||||
const title = selectedTranslation?.title ?? prettySlug(post.slug);
|
excerpt: selectedTranslation?.excerpt ?? "",
|
||||||
const except = selectedTranslation?.excerpt ?? "";
|
}),
|
||||||
|
[post.slug, post.thumbnail, selectedTranslation]
|
||||||
|
);
|
||||||
|
|
||||||
const subPanel =
|
const subPanel =
|
||||||
returnHref || returnTitle || displayCredits || displayToc ? (
|
returnHref || returnTitle || displayCredits || displayToc ? (
|
||||||
|
@ -137,7 +141,7 @@ export function PostPage(props: Immutable<Props>): JSX.Element {
|
||||||
<ThumbnailHeader
|
<ThumbnailHeader
|
||||||
thumbnail={thumbnail}
|
thumbnail={thumbnail}
|
||||||
title={title}
|
title={title}
|
||||||
description={except}
|
description={excerpt}
|
||||||
langui={langui}
|
langui={langui}
|
||||||
categories={post.categories}
|
categories={post.categories}
|
||||||
languageSwitcher={<LanguageSwitcher />}
|
languageSwitcher={<LanguageSwitcher />}
|
||||||
|
|
|
@ -19,55 +19,6 @@ interface Props {
|
||||||
export function ChronologyItemComponent(props: Immutable<Props>): JSX.Element {
|
export function ChronologyItemComponent(props: Immutable<Props>): JSX.Element {
|
||||||
const { langui } = props;
|
const { langui } = props;
|
||||||
|
|
||||||
function generateAnchor(
|
|
||||||
year: number | undefined,
|
|
||||||
month: number | null | undefined,
|
|
||||||
day: number | null | undefined
|
|
||||||
): string {
|
|
||||||
let result = "";
|
|
||||||
if (year) result += year;
|
|
||||||
if (month) result += `- ${month.toString().padStart(2, "0")}`;
|
|
||||||
if (day) result += `- ${day.toString().padStart(2, "0")}`;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
function generateYear(
|
|
||||||
displayed_date: string | null | undefined,
|
|
||||||
year: number | undefined
|
|
||||||
): string {
|
|
||||||
return displayed_date ?? year?.toString() ?? "";
|
|
||||||
}
|
|
||||||
|
|
||||||
function generateDate(
|
|
||||||
month: number | null | undefined,
|
|
||||||
day: number | null | undefined
|
|
||||||
): string {
|
|
||||||
const lut = [
|
|
||||||
"Jan",
|
|
||||||
"Feb",
|
|
||||||
"Mar",
|
|
||||||
"Apr",
|
|
||||||
"May",
|
|
||||||
"Jun",
|
|
||||||
"Jul",
|
|
||||||
"Aug",
|
|
||||||
"Sep",
|
|
||||||
"Oct",
|
|
||||||
"Nov",
|
|
||||||
"Dec",
|
|
||||||
];
|
|
||||||
|
|
||||||
let result = "";
|
|
||||||
if (month && month >= 1 && month <= 12) {
|
|
||||||
result += lut[month - 1];
|
|
||||||
if (day) {
|
|
||||||
result += ` ${day}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (props.item.attributes) {
|
if (props.item.attributes) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
@ -168,3 +119,52 @@ export function ChronologyItemComponent(props: Immutable<Props>): JSX.Element {
|
||||||
|
|
||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function generateAnchor(
|
||||||
|
year: number | undefined,
|
||||||
|
month: number | null | undefined,
|
||||||
|
day: number | null | undefined
|
||||||
|
): string {
|
||||||
|
let result = "";
|
||||||
|
if (year) result += year;
|
||||||
|
if (month) result += `- ${month.toString().padStart(2, "0")}`;
|
||||||
|
if (day) result += `- ${day.toString().padStart(2, "0")}`;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateYear(
|
||||||
|
displayed_date: string | null | undefined,
|
||||||
|
year: number | undefined
|
||||||
|
): string {
|
||||||
|
return displayed_date ?? year?.toString() ?? "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateDate(
|
||||||
|
month: number | null | undefined,
|
||||||
|
day: number | null | undefined
|
||||||
|
): string {
|
||||||
|
const lut = [
|
||||||
|
"Jan",
|
||||||
|
"Feb",
|
||||||
|
"Mar",
|
||||||
|
"Apr",
|
||||||
|
"May",
|
||||||
|
"Jun",
|
||||||
|
"Jul",
|
||||||
|
"Aug",
|
||||||
|
"Sep",
|
||||||
|
"Oct",
|
||||||
|
"Nov",
|
||||||
|
"Dec",
|
||||||
|
];
|
||||||
|
|
||||||
|
let result = "";
|
||||||
|
if (month && month >= 1 && month <= 12) {
|
||||||
|
result += lut[month - 1];
|
||||||
|
if (day) {
|
||||||
|
result += ` ${day}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
|
@ -1,38 +1,51 @@
|
||||||
import { Immutable, LibraryItemUserStatus } from "helpers/types";
|
import { isDefined } from "helpers/others";
|
||||||
|
import {
|
||||||
|
Immutable,
|
||||||
|
LibraryItemUserStatus,
|
||||||
|
RequiredNonNullable,
|
||||||
|
} from "helpers/types";
|
||||||
import { useDarkMode } from "hooks/useDarkMode";
|
import { useDarkMode } from "hooks/useDarkMode";
|
||||||
import { useStateWithLocalStorage } from "hooks/useStateWithLocalStorage";
|
import { useStateWithLocalStorage } from "hooks/useStateWithLocalStorage";
|
||||||
import React, { ReactNode, useContext, useState } from "react";
|
import React, { ReactNode, useContext, useState } from "react";
|
||||||
|
|
||||||
export interface AppLayoutState {
|
export interface AppLayoutState {
|
||||||
subPanelOpen: boolean | undefined;
|
subPanelOpen: boolean | undefined;
|
||||||
|
toggleSubPanelOpen: () => void;
|
||||||
setSubPanelOpen: React.Dispatch<
|
setSubPanelOpen: React.Dispatch<
|
||||||
React.SetStateAction<AppLayoutState["subPanelOpen"]>
|
React.SetStateAction<AppLayoutState["subPanelOpen"]>
|
||||||
>;
|
>;
|
||||||
configPanelOpen: boolean | undefined;
|
configPanelOpen: boolean | undefined;
|
||||||
|
toggleConfigPanelOpen: () => void;
|
||||||
setConfigPanelOpen: React.Dispatch<
|
setConfigPanelOpen: React.Dispatch<
|
||||||
React.SetStateAction<AppLayoutState["configPanelOpen"]>
|
React.SetStateAction<AppLayoutState["configPanelOpen"]>
|
||||||
>;
|
>;
|
||||||
searchPanelOpen: boolean | undefined;
|
searchPanelOpen: boolean | undefined;
|
||||||
|
toggleSearchPanelOpen: () => void;
|
||||||
setSearchPanelOpen: React.Dispatch<
|
setSearchPanelOpen: React.Dispatch<
|
||||||
React.SetStateAction<AppLayoutState["searchPanelOpen"]>
|
React.SetStateAction<AppLayoutState["searchPanelOpen"]>
|
||||||
>;
|
>;
|
||||||
mainPanelReduced: boolean | undefined;
|
mainPanelReduced: boolean | undefined;
|
||||||
|
toggleMainPanelReduced: () => void;
|
||||||
setMainPanelReduced: React.Dispatch<
|
setMainPanelReduced: React.Dispatch<
|
||||||
React.SetStateAction<AppLayoutState["mainPanelReduced"]>
|
React.SetStateAction<AppLayoutState["mainPanelReduced"]>
|
||||||
>;
|
>;
|
||||||
mainPanelOpen: boolean | undefined;
|
mainPanelOpen: boolean | undefined;
|
||||||
|
toggleMainPanelOpen: () => void;
|
||||||
setMainPanelOpen: React.Dispatch<
|
setMainPanelOpen: React.Dispatch<
|
||||||
React.SetStateAction<AppLayoutState["mainPanelOpen"]>
|
React.SetStateAction<AppLayoutState["mainPanelOpen"]>
|
||||||
>;
|
>;
|
||||||
darkMode: boolean | undefined;
|
darkMode: boolean | undefined;
|
||||||
|
toggleDarkMode: () => void;
|
||||||
setDarkMode: React.Dispatch<React.SetStateAction<AppLayoutState["darkMode"]>>;
|
setDarkMode: React.Dispatch<React.SetStateAction<AppLayoutState["darkMode"]>>;
|
||||||
selectedThemeMode: boolean | undefined;
|
selectedThemeMode: boolean | undefined;
|
||||||
|
toggleSelectedThemeMode: () => void;
|
||||||
setSelectedThemeMode: React.Dispatch<
|
setSelectedThemeMode: React.Dispatch<
|
||||||
React.SetStateAction<AppLayoutState["selectedThemeMode"]>
|
React.SetStateAction<AppLayoutState["selectedThemeMode"]>
|
||||||
>;
|
>;
|
||||||
fontSize: number | undefined;
|
fontSize: number | undefined;
|
||||||
setFontSize: React.Dispatch<React.SetStateAction<AppLayoutState["fontSize"]>>;
|
setFontSize: React.Dispatch<React.SetStateAction<AppLayoutState["fontSize"]>>;
|
||||||
dyslexic: boolean | undefined;
|
dyslexic: boolean | undefined;
|
||||||
|
toggleDyslexic: () => void;
|
||||||
setDyslexic: React.Dispatch<React.SetStateAction<AppLayoutState["dyslexic"]>>;
|
setDyslexic: React.Dispatch<React.SetStateAction<AppLayoutState["dyslexic"]>>;
|
||||||
currency: string | undefined;
|
currency: string | undefined;
|
||||||
setCurrency: React.Dispatch<React.SetStateAction<AppLayoutState["currency"]>>;
|
setCurrency: React.Dispatch<React.SetStateAction<AppLayoutState["currency"]>>;
|
||||||
|
@ -45,6 +58,7 @@ export interface AppLayoutState {
|
||||||
React.SetStateAction<AppLayoutState["preferredLanguages"]>
|
React.SetStateAction<AppLayoutState["preferredLanguages"]>
|
||||||
>;
|
>;
|
||||||
menuGestures: boolean;
|
menuGestures: boolean;
|
||||||
|
toggleMenuGestures: () => void;
|
||||||
setMenuGestures: React.Dispatch<
|
setMenuGestures: React.Dispatch<
|
||||||
React.SetStateAction<AppLayoutState["menuGestures"]>
|
React.SetStateAction<AppLayoutState["menuGestures"]>
|
||||||
>;
|
>;
|
||||||
|
@ -54,38 +68,45 @@ export interface AppLayoutState {
|
||||||
>;
|
>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* eslint-disable @typescript-eslint/no-empty-function */
|
const initialState: RequiredNonNullable<AppLayoutState> = {
|
||||||
const initialState: AppLayoutState = {
|
|
||||||
subPanelOpen: false,
|
subPanelOpen: false,
|
||||||
|
toggleSubPanelOpen: () => null,
|
||||||
|
setSubPanelOpen: () => null,
|
||||||
configPanelOpen: false,
|
configPanelOpen: false,
|
||||||
|
setConfigPanelOpen: () => null,
|
||||||
|
toggleConfigPanelOpen: () => null,
|
||||||
searchPanelOpen: false,
|
searchPanelOpen: false,
|
||||||
|
setSearchPanelOpen: () => null,
|
||||||
|
toggleSearchPanelOpen: () => null,
|
||||||
mainPanelReduced: false,
|
mainPanelReduced: false,
|
||||||
|
setMainPanelReduced: () => null,
|
||||||
|
toggleMainPanelReduced: () => null,
|
||||||
mainPanelOpen: false,
|
mainPanelOpen: false,
|
||||||
|
toggleMainPanelOpen: () => null,
|
||||||
|
setMainPanelOpen: () => null,
|
||||||
darkMode: false,
|
darkMode: false,
|
||||||
|
toggleDarkMode: () => null,
|
||||||
|
setDarkMode: () => null,
|
||||||
selectedThemeMode: false,
|
selectedThemeMode: false,
|
||||||
|
toggleSelectedThemeMode: () => null,
|
||||||
|
setSelectedThemeMode: () => null,
|
||||||
fontSize: 1,
|
fontSize: 1,
|
||||||
|
setFontSize: () => null,
|
||||||
dyslexic: false,
|
dyslexic: false,
|
||||||
|
toggleDyslexic: () => null,
|
||||||
|
setDyslexic: () => null,
|
||||||
currency: "USD",
|
currency: "USD",
|
||||||
|
setCurrency: () => null,
|
||||||
playerName: "",
|
playerName: "",
|
||||||
|
setPlayerName: () => null,
|
||||||
preferredLanguages: [],
|
preferredLanguages: [],
|
||||||
|
setPreferredLanguages: () => null,
|
||||||
menuGestures: true,
|
menuGestures: true,
|
||||||
|
toggleMenuGestures: () => null,
|
||||||
|
setMenuGestures: () => null,
|
||||||
libraryItemUserStatus: {},
|
libraryItemUserStatus: {},
|
||||||
setSubPanelOpen: () => {},
|
setLibraryItemUserStatus: () => null,
|
||||||
setMainPanelReduced: () => {},
|
|
||||||
setMainPanelOpen: () => {},
|
|
||||||
setDarkMode: () => {},
|
|
||||||
setSelectedThemeMode: () => {},
|
|
||||||
setConfigPanelOpen: () => {},
|
|
||||||
setSearchPanelOpen: () => {},
|
|
||||||
setFontSize: () => {},
|
|
||||||
setDyslexic: () => {},
|
|
||||||
setCurrency: () => {},
|
|
||||||
setPlayerName: () => {},
|
|
||||||
setPreferredLanguages: () => {},
|
|
||||||
setMenuGestures: () => {},
|
|
||||||
setLibraryItemUserStatus: () => {},
|
|
||||||
};
|
};
|
||||||
/* eslint-enable @typescript-eslint/no-empty-function */
|
|
||||||
|
|
||||||
const AppContext = React.createContext<AppLayoutState>(initialState);
|
const AppContext = React.createContext<AppLayoutState>(initialState);
|
||||||
|
|
||||||
|
@ -161,6 +182,44 @@ export function AppContextProvider(props: Immutable<Props>): JSX.Element {
|
||||||
initialState.libraryItemUserStatus
|
initialState.libraryItemUserStatus
|
||||||
);
|
);
|
||||||
|
|
||||||
|
function toggleSubPanelOpen() {
|
||||||
|
setSubPanelOpen((current) => (isDefined(current) ? !current : current));
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleConfigPanelOpen() {
|
||||||
|
setConfigPanelOpen((current) => (isDefined(current) ? !current : current));
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleSearchPanelOpen() {
|
||||||
|
setSearchPanelOpen((current) => (isDefined(current) ? !current : current));
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleMainPanelReduced() {
|
||||||
|
setMainPanelReduced((current) => (isDefined(current) ? !current : current));
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleMainPanelOpen() {
|
||||||
|
setMainPanelOpen((current) => (isDefined(current) ? !current : current));
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleDarkMode() {
|
||||||
|
setDarkMode((current) => (isDefined(current) ? !current : current));
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleMenuGestures() {
|
||||||
|
setMenuGestures((current) => !current);
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleSelectedThemeMode() {
|
||||||
|
setSelectedThemeMode((current) =>
|
||||||
|
isDefined(current) ? !current : current
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleDyslexic() {
|
||||||
|
setDyslexic((current) => (isDefined(current) ? !current : current));
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppContext.Provider
|
<AppContext.Provider
|
||||||
value={{
|
value={{
|
||||||
|
@ -192,6 +251,15 @@ export function AppContextProvider(props: Immutable<Props>): JSX.Element {
|
||||||
setPreferredLanguages,
|
setPreferredLanguages,
|
||||||
setMenuGestures,
|
setMenuGestures,
|
||||||
setLibraryItemUserStatus,
|
setLibraryItemUserStatus,
|
||||||
|
toggleSubPanelOpen,
|
||||||
|
toggleConfigPanelOpen,
|
||||||
|
toggleSearchPanelOpen,
|
||||||
|
toggleMainPanelReduced,
|
||||||
|
toggleMainPanelOpen,
|
||||||
|
toggleDarkMode,
|
||||||
|
toggleMenuGestures,
|
||||||
|
toggleSelectedThemeMode,
|
||||||
|
toggleDyslexic,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{props.children}
|
{props.children}
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
export interface Wrapper {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ConditionalWrapper<T>(props: {
|
||||||
|
isWrapping: boolean;
|
||||||
|
children: React.ReactNode;
|
||||||
|
wrapper: (wrapperProps: T & Wrapper) => JSX.Element;
|
||||||
|
wrapperProps: T;
|
||||||
|
}): JSX.Element {
|
||||||
|
const { isWrapping, children, wrapper: Wrapper, wrapperProps } = props;
|
||||||
|
return isWrapping ? (
|
||||||
|
<Wrapper {...wrapperProps}>{children}</Wrapper>
|
||||||
|
) : (
|
||||||
|
<>{children}</>
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
import { AppStaticProps } from "graphql/getAppStaticProps";
|
import { AppStaticProps } from "graphql/getAppStaticProps";
|
||||||
import { prettySlug } from "./formatters";
|
import { prettySlug } from "./formatters";
|
||||||
|
import { isDefined } from "./others";
|
||||||
import { Content, Immutable } from "./types";
|
import { Content, Immutable } from "./types";
|
||||||
|
|
||||||
interface Description {
|
interface Description {
|
||||||
|
@ -51,7 +52,7 @@ function prettyMarkdown(markdown: string): string {
|
||||||
|
|
||||||
function prettyChip(items: (string | undefined)[]): string {
|
function prettyChip(items: (string | undefined)[]): string {
|
||||||
return items
|
return items
|
||||||
.filter((item) => item !== undefined)
|
.filter((item) => isDefined(item))
|
||||||
.map((item) => `(${item})`)
|
.map((item) => `(${item})`)
|
||||||
.join(" ");
|
.join(" ");
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { GetLibraryItemsPreviewQuery } from "graphql/generated";
|
||||||
import { AppStaticProps } from "graphql/getAppStaticProps";
|
import { AppStaticProps } from "graphql/getAppStaticProps";
|
||||||
import { prettyinlineTitle, prettyDate } from "./formatters";
|
import { prettyinlineTitle, prettyDate } from "./formatters";
|
||||||
import { convertPrice } from "./numbers";
|
import { convertPrice } from "./numbers";
|
||||||
|
import { isDefined } from "./others";
|
||||||
import { Immutable, LibraryItemUserStatus } from "./types";
|
import { Immutable, LibraryItemUserStatus } from "./types";
|
||||||
type Items = NonNullable<GetLibraryItemsPreviewQuery["libraryItems"]>["data"];
|
type Items = NonNullable<GetLibraryItemsPreviewQuery["libraryItems"]>["data"];
|
||||||
type GroupLibraryItems = Map<string, Immutable<Items>>;
|
type GroupLibraryItems = Map<string, Immutable<Items>>;
|
||||||
|
@ -172,7 +173,7 @@ export function filterItems(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
filterUserStatus !== undefined &&
|
isDefined(filterUserStatus) &&
|
||||||
item.id &&
|
item.id &&
|
||||||
appLayout.libraryItemUserStatus
|
appLayout.libraryItemUserStatus
|
||||||
) {
|
) {
|
||||||
|
|
|
@ -64,3 +64,21 @@ export function arrayMove<T>(arr: T[], old_index: number, new_index: number) {
|
||||||
arr.splice(new_index, 0, arr.splice(old_index, 1)[0]);
|
arr.splice(new_index, 0, arr.splice(old_index, 1)[0]);
|
||||||
return arr;
|
return arr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isDefined<T>(t: T): t is NonNullable<T> {
|
||||||
|
return t !== null && t !== undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isUndefined<T>(t: T | undefined | null): t is undefined | null {
|
||||||
|
return t === null || t === undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isDefinedAndNotEmpty(
|
||||||
|
string: string | undefined | null
|
||||||
|
): string is string {
|
||||||
|
return isDefined(string) && string.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function filterDefined<T>(t: T[]): NonNullable<T>[] {
|
||||||
|
return t.filter((item) => isDefined(item)) as NonNullable<T>[];
|
||||||
|
}
|
||||||
|
|
|
@ -38,6 +38,10 @@ export type Immutable<T> = {
|
||||||
: Immutable<T[K]>;
|
: Immutable<T[K]>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type RequiredNonNullable<T> = Required<{
|
||||||
|
[P in keyof T]: NonNullable<T[P]>;
|
||||||
|
}>;
|
||||||
|
|
||||||
export enum LibraryItemUserStatus {
|
export enum LibraryItemUserStatus {
|
||||||
None = 0,
|
None = 0,
|
||||||
Want = 1,
|
Want = 1,
|
||||||
|
|
|
@ -12,11 +12,11 @@ export function useMediaQuery(query: string): boolean {
|
||||||
|
|
||||||
const [matches, setMatches] = useState<boolean>(getMatches(query));
|
const [matches, setMatches] = useState<boolean>(getMatches(query));
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
function handleChange() {
|
function handleChange() {
|
||||||
setMatches(getMatches(query));
|
setMatches(getMatches(query));
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const matchMedia = window.matchMedia(query);
|
const matchMedia = window.matchMedia(query);
|
||||||
|
|
||||||
// Triggered at the first client-side load and if query changes
|
// Triggered at the first client-side load and if query changes
|
||||||
|
@ -28,7 +28,6 @@ export function useMediaQuery(query: string): boolean {
|
||||||
return () => {
|
return () => {
|
||||||
matchMedia.removeEventListener("change", handleChange);
|
matchMedia.removeEventListener("change", handleChange);
|
||||||
};
|
};
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [query]);
|
}, [query]);
|
||||||
|
|
||||||
return matches;
|
return matches;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { LanguageSwitcher } from "components/Inputs/LanguageSwitcher";
|
import { LanguageSwitcher } from "components/Inputs/LanguageSwitcher";
|
||||||
import { useAppLayout } from "contexts/AppLayoutContext";
|
import { useAppLayout } from "contexts/AppLayoutContext";
|
||||||
import { AppStaticProps } from "graphql/getAppStaticProps";
|
import { AppStaticProps } from "graphql/getAppStaticProps";
|
||||||
|
import { isDefined } from "helpers/others";
|
||||||
import { Immutable } from "helpers/types";
|
import { Immutable } from "helpers/types";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { useEffect, useMemo, useState } from "react";
|
import { useEffect, useMemo, useState } from "react";
|
||||||
|
@ -17,7 +18,7 @@ function getPreferredLanguage(
|
||||||
availableLanguages: Map<string, number>
|
availableLanguages: Map<string, number>
|
||||||
): number | undefined {
|
): number | undefined {
|
||||||
for (const locale of preferredLanguages) {
|
for (const locale of preferredLanguages) {
|
||||||
if (locale && availableLanguages.has(locale)) {
|
if (isDefined(locale) && availableLanguages.has(locale)) {
|
||||||
return availableLanguages.get(locale);
|
return availableLanguages.get(locale);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,39 +34,42 @@ export function useSmartLanguage<T>(
|
||||||
languages,
|
languages,
|
||||||
transform = (item) => item,
|
transform = (item) => item,
|
||||||
} = props;
|
} = props;
|
||||||
const appLayout = useAppLayout();
|
const { preferredLanguages } = useAppLayout();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const availableLocales: Map<string, number> = useMemo(() => new Map(), []);
|
const availableLocales = useMemo(() => {
|
||||||
|
const map = new Map<string, number>();
|
||||||
|
items.map((elem, index) => {
|
||||||
|
if (isDefined(elem)) {
|
||||||
|
const result = languageExtractor(elem);
|
||||||
|
if (isDefined(result)) map.set(result, index);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return map;
|
||||||
|
}, [items, languageExtractor]);
|
||||||
|
|
||||||
const [selectedTranslationIndex, setSelectedTranslationIndex] = useState<
|
const [selectedTranslationIndex, setSelectedTranslationIndex] = useState<
|
||||||
number | undefined
|
number | undefined
|
||||||
>();
|
>();
|
||||||
const [selectedTranslation, setSelectedTranslation] =
|
|
||||||
useState<Immutable<T>>();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
items.map((elem, index) => {
|
|
||||||
if (elem !== null && elem !== undefined) {
|
|
||||||
const result = languageExtractor(elem);
|
|
||||||
if (result !== undefined) availableLocales.set(result, index);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}, [availableLocales, items, languageExtractor]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setSelectedTranslationIndex(
|
setSelectedTranslationIndex(
|
||||||
|
(current) =>
|
||||||
|
current ??
|
||||||
getPreferredLanguage(
|
getPreferredLanguage(
|
||||||
appLayout.preferredLanguages ?? [router.locale],
|
preferredLanguages ?? [router.locale],
|
||||||
availableLocales
|
availableLocales
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}, [appLayout.preferredLanguages, availableLocales, router.locale]);
|
}, [preferredLanguages, availableLocales, router.locale]);
|
||||||
|
|
||||||
useEffect(() => {
|
const selectedTranslation = useMemo(
|
||||||
if (selectedTranslationIndex !== undefined) {
|
() =>
|
||||||
setSelectedTranslation(transform(items[selectedTranslationIndex]));
|
isDefined(selectedTranslationIndex)
|
||||||
}
|
? transform(items[selectedTranslationIndex])
|
||||||
}, [items, selectedTranslationIndex, transform]);
|
: undefined,
|
||||||
|
[items, selectedTranslationIndex, transform]
|
||||||
|
);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
selectedTranslation,
|
selectedTranslation,
|
||||||
|
@ -74,7 +78,7 @@ export function useSmartLanguage<T>(
|
||||||
languages={languages}
|
languages={languages}
|
||||||
locales={availableLocales}
|
locales={availableLocales}
|
||||||
localesIndex={selectedTranslationIndex}
|
localesIndex={selectedTranslationIndex}
|
||||||
setLocalesIndex={setSelectedTranslationIndex}
|
onLanguageChanged={setSelectedTranslationIndex}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { isDefined } from "helpers/others";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
export function useStateWithLocalStorage<T>(
|
export function useStateWithLocalStorage<T>(
|
||||||
|
@ -10,7 +11,7 @@ export function useStateWithLocalStorage<T>(
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
try {
|
try {
|
||||||
const item = localStorage.getItem(key);
|
const item = localStorage.getItem(key);
|
||||||
if (item !== "undefined" && item !== null) {
|
if (isDefined(item)) {
|
||||||
setValue(JSON.parse(item) as T);
|
setValue(JSON.parse(item) as T);
|
||||||
} else {
|
} else {
|
||||||
setValue(initialValue);
|
setValue(initialValue);
|
||||||
|
@ -23,7 +24,7 @@ export function useStateWithLocalStorage<T>(
|
||||||
}, [initialValue, key]);
|
}, [initialValue, key]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (value !== undefined) localStorage.setItem(key, JSON.stringify(value));
|
if (isDefined(value)) localStorage.setItem(key, JSON.stringify(value));
|
||||||
}, [value, key]);
|
}, [value, key]);
|
||||||
|
|
||||||
return [value, setValue];
|
return [value, setValue];
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { Dispatch, SetStateAction, useCallback } from "react";
|
||||||
|
|
||||||
|
export function useToggle(setState: Dispatch<SetStateAction<boolean>>) {
|
||||||
|
return useCallback(() => {
|
||||||
|
setState((current) => !current);
|
||||||
|
}, []);
|
||||||
|
}
|
|
@ -24,6 +24,7 @@ import { Fragment, useState } from "react";
|
||||||
import { Icon } from "components/Ico";
|
import { Icon } from "components/Ico";
|
||||||
import { useMediaHoverable } from "hooks/useMediaQuery";
|
import { useMediaHoverable } from "hooks/useMediaQuery";
|
||||||
import { WithLabel } from "components/Inputs/WithLabel";
|
import { WithLabel } from "components/Inputs/WithLabel";
|
||||||
|
import { isDefined } from "helpers/others";
|
||||||
|
|
||||||
interface Props extends AppStaticProps {
|
interface Props extends AppStaticProps {
|
||||||
channel: NonNullable<
|
channel: NonNullable<
|
||||||
|
@ -114,7 +115,10 @@ export async function getStaticProps(
|
||||||
): Promise<{ notFound: boolean } | { props: Props }> {
|
): Promise<{ notFound: boolean } | { props: Props }> {
|
||||||
const sdk = getReadySdk();
|
const sdk = getReadySdk();
|
||||||
const channel = await sdk.getVideoChannel({
|
const channel = await sdk.getVideoChannel({
|
||||||
channel: context.params?.uid ? context.params.uid.toString() : "",
|
channel:
|
||||||
|
context.params && isDefined(context.params.uid)
|
||||||
|
? context.params.uid.toString()
|
||||||
|
: "",
|
||||||
});
|
});
|
||||||
if (!channel.videoChannels?.data[0].attributes) return { notFound: true };
|
if (!channel.videoChannels?.data[0].attributes) return { notFound: true };
|
||||||
const props: Props = {
|
const props: Props = {
|
||||||
|
|
|
@ -18,6 +18,7 @@ import { GetVideoQuery } from "graphql/generated";
|
||||||
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
|
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
|
||||||
import { getReadySdk } from "graphql/sdk";
|
import { getReadySdk } from "graphql/sdk";
|
||||||
import { prettyDate, prettyShortenNumber } from "helpers/formatters";
|
import { prettyDate, prettyShortenNumber } from "helpers/formatters";
|
||||||
|
import { isDefined } from "helpers/others";
|
||||||
import { getVideoFile } from "helpers/videos";
|
import { getVideoFile } from "helpers/videos";
|
||||||
import { useMediaMobile } from "hooks/useMediaQuery";
|
import { useMediaMobile } from "hooks/useMediaQuery";
|
||||||
import {
|
import {
|
||||||
|
@ -190,7 +191,10 @@ export async function getStaticProps(
|
||||||
): Promise<{ notFound: boolean } | { props: Props }> {
|
): Promise<{ notFound: boolean } | { props: Props }> {
|
||||||
const sdk = getReadySdk();
|
const sdk = getReadySdk();
|
||||||
const videos = await sdk.getVideo({
|
const videos = await sdk.getVideo({
|
||||||
uid: context.params?.uid ? context.params.uid.toString() : "",
|
uid:
|
||||||
|
context.params && isDefined(context.params.uid)
|
||||||
|
? context.params.uid.toString()
|
||||||
|
: "",
|
||||||
});
|
});
|
||||||
if (!videos.videos?.data[0]?.attributes) return { notFound: true };
|
if (!videos.videos?.data[0]?.attributes) return { notFound: true };
|
||||||
const props: Props = {
|
const props: Props = {
|
||||||
|
|
|
@ -36,7 +36,7 @@ import {
|
||||||
GetStaticPathsResult,
|
GetStaticPathsResult,
|
||||||
GetStaticPropsContext,
|
GetStaticPropsContext,
|
||||||
} from "next";
|
} from "next";
|
||||||
import { Fragment } from "react";
|
import { Fragment, useMemo } from "react";
|
||||||
|
|
||||||
interface Props extends AppStaticProps {
|
interface Props extends AppStaticProps {
|
||||||
content: ContentWithTranslations;
|
content: ContentWithTranslations;
|
||||||
|
@ -54,16 +54,23 @@ export default function Content(props: Immutable<Props>): JSX.Element {
|
||||||
|
|
||||||
useScrollTopOnChange(AnchorIds.ContentPanel, [selectedTranslation]);
|
useScrollTopOnChange(AnchorIds.ContentPanel, [selectedTranslation]);
|
||||||
|
|
||||||
const previousContent = content.group?.data?.attributes?.contents
|
const { previousContent, nextContent } = useMemo(
|
||||||
|
() => ({
|
||||||
|
previousContent: content.group?.data?.attributes?.contents
|
||||||
? getPreviousContent(
|
? getPreviousContent(
|
||||||
content.group.data.attributes.contents.data,
|
content.group.data.attributes.contents.data,
|
||||||
content.slug
|
content.slug
|
||||||
)
|
)
|
||||||
: undefined;
|
: undefined,
|
||||||
|
nextContent: content.group?.data?.attributes?.contents
|
||||||
const nextContent = content.group?.data?.attributes?.contents
|
? getNextContent(
|
||||||
? getNextContent(content.group.data.attributes.contents.data, content.slug)
|
content.group.data.attributes.contents.data,
|
||||||
: undefined;
|
content.slug
|
||||||
|
)
|
||||||
|
: undefined,
|
||||||
|
}),
|
||||||
|
[content.group, content.slug]
|
||||||
|
);
|
||||||
|
|
||||||
const subPanel = (
|
const subPanel = (
|
||||||
<SubPanel>
|
<SubPanel>
|
||||||
|
|
|
@ -15,7 +15,7 @@ import { getReadySdk } from "graphql/sdk";
|
||||||
import { prettyinlineTitle, prettySlug } from "helpers/formatters";
|
import { prettyinlineTitle, prettySlug } from "helpers/formatters";
|
||||||
import { Immutable } from "helpers/types";
|
import { Immutable } from "helpers/types";
|
||||||
import { GetStaticPropsContext } from "next";
|
import { GetStaticPropsContext } from "next";
|
||||||
import { Fragment, useEffect, useState } from "react";
|
import { Fragment, useState, useMemo } from "react";
|
||||||
import { Icon } from "components/Ico";
|
import { Icon } from "components/Ico";
|
||||||
import { WithLabel } from "components/Inputs/WithLabel";
|
import { WithLabel } from "components/Inputs/WithLabel";
|
||||||
import { Button } from "components/Inputs/Button";
|
import { Button } from "components/Inputs/Button";
|
||||||
|
@ -50,36 +50,20 @@ export default function Contents(props: Immutable<Props>): JSX.Element {
|
||||||
);
|
);
|
||||||
const [searchName, setSearchName] = useState(defaultFiltersState.searchName);
|
const [searchName, setSearchName] = useState(defaultFiltersState.searchName);
|
||||||
|
|
||||||
const [effectiveCombineRelatedContent, setEffectiveCombineRelatedContent] =
|
const filteredItems = useMemo(
|
||||||
useState(true);
|
() => filterContents(contents, combineRelatedContent, searchName),
|
||||||
|
[combineRelatedContent, contents, searchName]
|
||||||
const [filteredItems, setFilteredItems] = useState(
|
|
||||||
filterContents(contents, combineRelatedContent, searchName)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const [groups, setGroups] = useState<GroupContentItems>(
|
const groups = useMemo(
|
||||||
getGroups(langui, groupingMethod, filteredItems)
|
() => getGroups(langui, groupingMethod, filteredItems),
|
||||||
|
[langui, groupingMethod, filteredItems]
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
const effectiveCombineRelatedContent = useMemo(
|
||||||
if (searchName.length > 1) {
|
() => (searchName.length > 1 ? false : combineRelatedContent),
|
||||||
setEffectiveCombineRelatedContent(false);
|
[combineRelatedContent, searchName.length]
|
||||||
} else {
|
|
||||||
setEffectiveCombineRelatedContent(combineRelatedContent);
|
|
||||||
}
|
|
||||||
setFilteredItems(
|
|
||||||
filterContents(contents, effectiveCombineRelatedContent, searchName)
|
|
||||||
);
|
);
|
||||||
}, [
|
|
||||||
effectiveCombineRelatedContent,
|
|
||||||
contents,
|
|
||||||
searchName,
|
|
||||||
combineRelatedContent,
|
|
||||||
]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setGroups(getGroups(langui, groupingMethod, filteredItems));
|
|
||||||
}, [langui, groupingMethod, filteredItems]);
|
|
||||||
|
|
||||||
const subPanel = (
|
const subPanel = (
|
||||||
<SubPanel>
|
<SubPanel>
|
||||||
|
|
|
@ -115,7 +115,6 @@ type ReportLine = {
|
||||||
frontendUrl: string;
|
frontendUrl: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
function testingLibraryItem(
|
function testingLibraryItem(
|
||||||
libraryItems: Immutable<Props["libraryItems"]>
|
libraryItems: Immutable<Props["libraryItems"]>
|
||||||
): Report {
|
): Report {
|
||||||
|
|
|
@ -42,7 +42,7 @@ export default function Editor(props: Immutable<Props>): JSX.Element {
|
||||||
append = `</${wrapper}>`;
|
append = `</${wrapper}>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (addInnerNewLines) {
|
if (addInnerNewLines === true) {
|
||||||
prepend = `${prepend}\n`;
|
prepend = `${prepend}\n`;
|
||||||
append = `\n${append}`;
|
append = `\n${append}`;
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,7 +35,7 @@ import {
|
||||||
} from "helpers/formatters";
|
} from "helpers/formatters";
|
||||||
import { getAssetURL, ImageQuality } from "helpers/img";
|
import { getAssetURL, ImageQuality } from "helpers/img";
|
||||||
import { convertMmToInch } from "helpers/numbers";
|
import { convertMmToInch } from "helpers/numbers";
|
||||||
import { sortContent } from "helpers/others";
|
import { isDefined, sortContent } from "helpers/others";
|
||||||
import { Immutable } from "helpers/types";
|
import { Immutable } from "helpers/types";
|
||||||
import { useLightBox } from "hooks/useLightBox";
|
import { useLightBox } from "hooks/useLightBox";
|
||||||
import { AnchorIds, useScrollTopOnChange } from "hooks/useScrollTopOnChange";
|
import { AnchorIds, useScrollTopOnChange } from "hooks/useScrollTopOnChange";
|
||||||
|
@ -196,7 +196,9 @@ export default function LibrarySlug(props: Immutable<Props>): JSX.Element {
|
||||||
{item?.urls && item.urls.length ? (
|
{item?.urls && item.urls.length ? (
|
||||||
<div className="flex flex-row place-items-center gap-3">
|
<div className="flex flex-row place-items-center gap-3">
|
||||||
<p>{langui.available_at}</p>
|
<p>{langui.available_at}</p>
|
||||||
{item.urls.map((url, index) => (
|
{item.urls
|
||||||
|
.filter((url) => url)
|
||||||
|
.map((url, index) => (
|
||||||
<Fragment key={index}>
|
<Fragment key={index}>
|
||||||
{url?.url && (
|
{url?.url && (
|
||||||
<Button
|
<Button
|
||||||
|
@ -342,7 +344,7 @@ export default function LibrarySlug(props: Immutable<Props>): JSX.Element {
|
||||||
<p>{convertMmToInch(item.size.height)} in</p>
|
<p>{convertMmToInch(item.size.height)} in</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{item.size.thickness && (
|
{isDefined(item.size.thickness) && (
|
||||||
<div
|
<div
|
||||||
className="grid place-items-center gap-x-4 desktop:grid-flow-col
|
className="grid place-items-center gap-x-4 desktop:grid-flow-col
|
||||||
desktop:place-items-start"
|
desktop:place-items-start"
|
||||||
|
@ -523,7 +525,10 @@ export async function getStaticProps(
|
||||||
): Promise<{ notFound: boolean } | { props: Props }> {
|
): Promise<{ notFound: boolean } | { props: Props }> {
|
||||||
const sdk = getReadySdk();
|
const sdk = getReadySdk();
|
||||||
const item = await sdk.getLibraryItem({
|
const item = await sdk.getLibraryItem({
|
||||||
slug: context.params?.slug ? context.params.slug.toString() : "",
|
slug:
|
||||||
|
context.params && isDefined(context.params.slug)
|
||||||
|
? context.params.slug.toString()
|
||||||
|
: "",
|
||||||
language_code: context.locale ?? "en",
|
language_code: context.locale ?? "en",
|
||||||
});
|
});
|
||||||
if (!item.libraryItems?.data[0]?.attributes) return { notFound: true };
|
if (!item.libraryItems?.data[0]?.attributes) return { notFound: true };
|
||||||
|
|
|
@ -15,7 +15,7 @@ import { GetLibraryItemScansQuery } from "graphql/generated";
|
||||||
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
|
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
|
||||||
import { getReadySdk } from "graphql/sdk";
|
import { getReadySdk } from "graphql/sdk";
|
||||||
import { prettyinlineTitle, prettySlug } from "helpers/formatters";
|
import { prettyinlineTitle, prettySlug } from "helpers/formatters";
|
||||||
import { sortContent } from "helpers/others";
|
import { isDefined, sortContent } from "helpers/others";
|
||||||
import { Immutable } from "helpers/types";
|
import { Immutable } from "helpers/types";
|
||||||
import { useLightBox } from "hooks/useLightBox";
|
import { useLightBox } from "hooks/useLightBox";
|
||||||
import {
|
import {
|
||||||
|
@ -123,7 +123,10 @@ export async function getStaticProps(
|
||||||
): Promise<{ notFound: boolean } | { props: Props }> {
|
): Promise<{ notFound: boolean } | { props: Props }> {
|
||||||
const sdk = getReadySdk();
|
const sdk = getReadySdk();
|
||||||
const item = await sdk.getLibraryItemScans({
|
const item = await sdk.getLibraryItemScans({
|
||||||
slug: context.params?.slug ? context.params.slug.toString() : "",
|
slug:
|
||||||
|
context.params && isDefined(context.params.slug)
|
||||||
|
? context.params.slug.toString()
|
||||||
|
: "",
|
||||||
language_code: context.locale ?? "en",
|
language_code: context.locale ?? "en",
|
||||||
});
|
});
|
||||||
if (!item.libraryItems?.data[0]?.attributes) return { notFound: true };
|
if (!item.libraryItems?.data[0]?.attributes) return { notFound: true };
|
||||||
|
|
|
@ -14,7 +14,7 @@ import { getReadySdk } from "graphql/sdk";
|
||||||
import { prettyItemSubType } from "helpers/formatters";
|
import { prettyItemSubType } from "helpers/formatters";
|
||||||
import { Immutable, LibraryItemUserStatus } from "helpers/types";
|
import { Immutable, LibraryItemUserStatus } from "helpers/types";
|
||||||
import { GetStaticPropsContext } from "next";
|
import { GetStaticPropsContext } from "next";
|
||||||
import { Fragment, useEffect, useState } from "react";
|
import { Fragment, useState, useMemo } from "react";
|
||||||
import { Icon } from "components/Ico";
|
import { Icon } from "components/Ico";
|
||||||
import { WithLabel } from "components/Inputs/WithLabel";
|
import { WithLabel } from "components/Inputs/WithLabel";
|
||||||
import { TextInput } from "components/Inputs/TextInput";
|
import { TextInput } from "components/Inputs/TextInput";
|
||||||
|
@ -31,6 +31,7 @@ import {
|
||||||
import { PreviewCard } from "components/PreviewCard";
|
import { PreviewCard } from "components/PreviewCard";
|
||||||
import { useMediaHoverable } from "hooks/useMediaQuery";
|
import { useMediaHoverable } from "hooks/useMediaQuery";
|
||||||
import { ButtonGroup } from "components/Inputs/ButtonGroup";
|
import { ButtonGroup } from "components/Inputs/ButtonGroup";
|
||||||
|
import { isDefinedAndNotEmpty, isUndefined } from "helpers/others";
|
||||||
|
|
||||||
interface Props extends AppStaticProps {
|
interface Props extends AppStaticProps {
|
||||||
items: NonNullable<GetLibraryItemsPreviewQuery["libraryItems"]>["data"];
|
items: NonNullable<GetLibraryItemsPreviewQuery["libraryItems"]>["data"];
|
||||||
|
@ -75,7 +76,8 @@ export default function Library(props: Immutable<Props>): JSX.Element {
|
||||||
LibraryItemUserStatus | undefined
|
LibraryItemUserStatus | undefined
|
||||||
>(defaultFiltersState.filterUserStatus);
|
>(defaultFiltersState.filterUserStatus);
|
||||||
|
|
||||||
const [filteredItems, setFilteredItems] = useState(
|
const filteredItems = useMemo(
|
||||||
|
() =>
|
||||||
filterItems(
|
filterItems(
|
||||||
appLayout,
|
appLayout,
|
||||||
libraryItems,
|
libraryItems,
|
||||||
|
@ -84,46 +86,27 @@ export default function Library(props: Immutable<Props>): JSX.Element {
|
||||||
showPrimaryItems,
|
showPrimaryItems,
|
||||||
showSecondaryItems,
|
showSecondaryItems,
|
||||||
filterUserStatus
|
filterUserStatus
|
||||||
)
|
),
|
||||||
);
|
[
|
||||||
|
|
||||||
const [sortedItems, setSortedItem] = useState(
|
|
||||||
sortBy(groupingMethod, filteredItems, currencies)
|
|
||||||
);
|
|
||||||
|
|
||||||
const [groups, setGroups] = useState(
|
|
||||||
getGroups(langui, groupingMethod, sortedItems)
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setFilteredItems(
|
|
||||||
filterItems(
|
|
||||||
appLayout,
|
appLayout,
|
||||||
libraryItems,
|
|
||||||
searchName,
|
|
||||||
showSubitems,
|
|
||||||
showPrimaryItems,
|
|
||||||
showSecondaryItems,
|
|
||||||
filterUserStatus
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}, [
|
|
||||||
showSubitems,
|
|
||||||
libraryItems,
|
|
||||||
showPrimaryItems,
|
|
||||||
showSecondaryItems,
|
|
||||||
searchName,
|
|
||||||
filterUserStatus,
|
filterUserStatus,
|
||||||
appLayout,
|
libraryItems,
|
||||||
]);
|
searchName,
|
||||||
|
showPrimaryItems,
|
||||||
|
showSecondaryItems,
|
||||||
|
showSubitems,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
const sortedItems = useMemo(
|
||||||
setSortedItem(sortBy(sortingMethod, filteredItems, currencies));
|
() => sortBy(sortingMethod, filteredItems, currencies),
|
||||||
}, [currencies, filteredItems, sortingMethod]);
|
[currencies, filteredItems, sortingMethod]
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
const groups = useMemo(
|
||||||
setGroups(getGroups(langui, groupingMethod, sortedItems));
|
() => getGroups(langui, groupingMethod, sortedItems),
|
||||||
}, [langui, groupingMethod, sortedItems]);
|
[langui, groupingMethod, sortedItems]
|
||||||
|
);
|
||||||
|
|
||||||
const subPanel = (
|
const subPanel = (
|
||||||
<SubPanel>
|
<SubPanel>
|
||||||
|
@ -227,7 +210,7 @@ export default function Library(props: Immutable<Props>): JSX.Element {
|
||||||
<Button
|
<Button
|
||||||
text={"All"}
|
text={"All"}
|
||||||
onClick={() => setFilterUserStatus(undefined)}
|
onClick={() => setFilterUserStatus(undefined)}
|
||||||
active={filterUserStatus === undefined}
|
active={isUndefined(filterUserStatus)}
|
||||||
/>
|
/>
|
||||||
</ToolTip>
|
</ToolTip>
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
|
@ -275,7 +258,7 @@ export default function Library(props: Immutable<Props>): JSX.Element {
|
||||||
>
|
>
|
||||||
{items.map((item) => (
|
{items.map((item) => (
|
||||||
<Fragment key={item.id}>
|
<Fragment key={item.id}>
|
||||||
{item.id && item.attributes && (
|
{isDefinedAndNotEmpty(item.id) && item.attributes && (
|
||||||
<PreviewCard
|
<PreviewCard
|
||||||
href={`/library/${item.attributes.slug}`}
|
href={`/library/${item.attributes.slug}`}
|
||||||
title={item.attributes.title}
|
title={item.attributes.title}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import {
|
||||||
PostStaticProps,
|
PostStaticProps,
|
||||||
} from "graphql/getPostStaticProps";
|
} from "graphql/getPostStaticProps";
|
||||||
import { getReadySdk } from "graphql/sdk";
|
import { getReadySdk } from "graphql/sdk";
|
||||||
|
import { isDefined } from "helpers/others";
|
||||||
import { Immutable } from "helpers/types";
|
import { Immutable } from "helpers/types";
|
||||||
import {
|
import {
|
||||||
GetStaticPathsContext,
|
GetStaticPathsContext,
|
||||||
|
@ -34,7 +35,10 @@ export default function LibrarySlug(props: Immutable<Props>): JSX.Element {
|
||||||
export async function getStaticProps(
|
export async function getStaticProps(
|
||||||
context: GetStaticPropsContext
|
context: GetStaticPropsContext
|
||||||
): Promise<{ notFound: boolean } | { props: Props }> {
|
): Promise<{ notFound: boolean } | { props: Props }> {
|
||||||
const slug = context.params?.slug ? context.params.slug.toString() : "";
|
const slug =
|
||||||
|
context.params && isDefined(context.params.slug)
|
||||||
|
? context.params.slug.toString()
|
||||||
|
: "";
|
||||||
return await getPostStaticProps(slug)(context);
|
return await getPostStaticProps(slug)(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ import { getReadySdk } from "graphql/sdk";
|
||||||
import { prettyDate, prettySlug } from "helpers/formatters";
|
import { prettyDate, prettySlug } from "helpers/formatters";
|
||||||
import { Immutable } from "helpers/types";
|
import { Immutable } from "helpers/types";
|
||||||
import { GetStaticPropsContext } from "next";
|
import { GetStaticPropsContext } from "next";
|
||||||
import { Fragment, useEffect, useState } from "react";
|
import { Fragment, useMemo, useState } from "react";
|
||||||
import { Icon } from "components/Ico";
|
import { Icon } from "components/Ico";
|
||||||
import { WithLabel } from "components/Inputs/WithLabel";
|
import { WithLabel } from "components/Inputs/WithLabel";
|
||||||
import { TextInput } from "components/Inputs/TextInput";
|
import { TextInput } from "components/Inputs/TextInput";
|
||||||
|
@ -35,19 +35,16 @@ export default function News(props: Immutable<Props>): JSX.Element {
|
||||||
const hoverable = useMediaHoverable();
|
const hoverable = useMediaHoverable();
|
||||||
|
|
||||||
const [searchName, setSearchName] = useState(defaultFiltersState.searchName);
|
const [searchName, setSearchName] = useState(defaultFiltersState.searchName);
|
||||||
|
|
||||||
const [keepInfoVisible, setKeepInfoVisible] = useState(
|
const [keepInfoVisible, setKeepInfoVisible] = useState(
|
||||||
defaultFiltersState.keepInfoVisible
|
defaultFiltersState.keepInfoVisible
|
||||||
);
|
);
|
||||||
|
|
||||||
const [filteredItems, setFilteredItems] = useState(
|
const filteredItems = useMemo(
|
||||||
filterItems(posts, searchName)
|
() => filterItems(posts, searchName),
|
||||||
|
[posts, searchName]
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setFilteredItems(filterItems(posts, searchName));
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [searchName]);
|
|
||||||
|
|
||||||
const subPanel = (
|
const subPanel = (
|
||||||
<SubPanel>
|
<SubPanel>
|
||||||
<PanelHeader
|
<PanelHeader
|
||||||
|
@ -167,7 +164,7 @@ function filterItems(posts: Immutable<Props["posts"]>, searchName: string) {
|
||||||
if (
|
if (
|
||||||
post.attributes?.translations?.[0]?.title
|
post.attributes?.translations?.[0]?.title
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.includes(searchName.toLowerCase())
|
.includes(searchName.toLowerCase()) === true
|
||||||
) {
|
) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ import { SubPanel } from "components/Panels/SubPanel";
|
||||||
import DefinitionCard from "components/Wiki/DefinitionCard";
|
import DefinitionCard from "components/Wiki/DefinitionCard";
|
||||||
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
|
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
|
||||||
import { getReadySdk } from "graphql/sdk";
|
import { getReadySdk } from "graphql/sdk";
|
||||||
|
import { isDefined, isDefinedAndNotEmpty } from "helpers/others";
|
||||||
import { Immutable, WikiPageWithTranslations } from "helpers/types";
|
import { Immutable, WikiPageWithTranslations } from "helpers/types";
|
||||||
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
||||||
import {
|
import {
|
||||||
|
@ -81,7 +82,7 @@ export default function WikiPage(props: Immutable<Props>): JSX.Element {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{selectedTranslation.summary && (
|
{isDefinedAndNotEmpty(selectedTranslation.summary) && (
|
||||||
<div className="mb-6">
|
<div className="mb-6">
|
||||||
<p className="font-headers text-lg">{langui.summary}</p>
|
<p className="font-headers text-lg">{langui.summary}</p>
|
||||||
<p>{selectedTranslation.summary}</p>
|
<p>{selectedTranslation.summary}</p>
|
||||||
|
@ -121,7 +122,10 @@ export async function getStaticProps(
|
||||||
context: GetStaticPropsContext
|
context: GetStaticPropsContext
|
||||||
): Promise<{ notFound: boolean } | { props: Props }> {
|
): Promise<{ notFound: boolean } | { props: Props }> {
|
||||||
const sdk = getReadySdk();
|
const sdk = getReadySdk();
|
||||||
const slug = context.params?.slug ? context.params.slug.toString() : "";
|
const slug =
|
||||||
|
context.params && isDefined(context.params.slug)
|
||||||
|
? context.params.slug.toString()
|
||||||
|
: "";
|
||||||
const page = await sdk.getWikiPage({
|
const page = await sdk.getWikiPage({
|
||||||
language_code: context.locale ?? "en",
|
language_code: context.locale ?? "en",
|
||||||
slug: slug,
|
slug: slug,
|
||||||
|
|
|
@ -12,6 +12,7 @@ import { GetChronologyItemsQuery, GetErasQuery } from "graphql/generated";
|
||||||
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
|
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
|
||||||
import { getReadySdk } from "graphql/sdk";
|
import { getReadySdk } from "graphql/sdk";
|
||||||
import { prettySlug } from "helpers/formatters";
|
import { prettySlug } from "helpers/formatters";
|
||||||
|
import { isDefined } from "helpers/others";
|
||||||
import { GetStaticPropsContext } from "next";
|
import { GetStaticPropsContext } from "next";
|
||||||
import { Fragment } from "react";
|
import { Fragment } from "react";
|
||||||
|
|
||||||
|
@ -119,7 +120,7 @@ export default function Chronology(props: Props): JSX.Element {
|
||||||
</InsetBox>
|
</InsetBox>
|
||||||
{era.map((items, index) => (
|
{era.map((items, index) => (
|
||||||
<Fragment key={index}>
|
<Fragment key={index}>
|
||||||
{items[0].attributes?.year && (
|
{items[0].attributes && isDefined(items[0].attributes.year) && (
|
||||||
<ChronologyYearComponent
|
<ChronologyYearComponent
|
||||||
year={items[0].attributes.year}
|
year={items[0].attributes.year}
|
||||||
items={items}
|
items={items}
|
||||||
|
|
|
@ -12,7 +12,7 @@ import {
|
||||||
ContentPanel,
|
ContentPanel,
|
||||||
ContentPanelWidthSizes,
|
ContentPanelWidthSizes,
|
||||||
} from "components/Panels/ContentPanel";
|
} from "components/Panels/ContentPanel";
|
||||||
import { Fragment, useEffect, useState } from "react";
|
import { Fragment, useMemo, useState } from "react";
|
||||||
import { TranslatedPreviewCard } from "components/PreviewCard";
|
import { TranslatedPreviewCard } from "components/PreviewCard";
|
||||||
import { HorizontalLine } from "components/HorizontalLine";
|
import { HorizontalLine } from "components/HorizontalLine";
|
||||||
import { Button } from "components/Inputs/Button";
|
import { Button } from "components/Inputs/Button";
|
||||||
|
@ -40,15 +40,11 @@ export default function Wiki(props: Immutable<Props>): JSX.Element {
|
||||||
defaultFiltersState.keepInfoVisible
|
defaultFiltersState.keepInfoVisible
|
||||||
);
|
);
|
||||||
|
|
||||||
const [filteredPages, setFilteredPages] = useState(
|
const filteredPages = useMemo(
|
||||||
filterPages(pages, searchName)
|
() => filterPages(pages, searchName),
|
||||||
|
[pages, searchName]
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setFilteredPages(filterPages(pages, searchName));
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [searchName]);
|
|
||||||
|
|
||||||
const subPanel = (
|
const subPanel = (
|
||||||
<SubPanel>
|
<SubPanel>
|
||||||
<PanelHeader
|
<PanelHeader
|
||||||
|
@ -171,7 +167,7 @@ function filterPages(posts: Immutable<Props["pages"]>, searchName: string) {
|
||||||
if (
|
if (
|
||||||
post.attributes?.translations?.[0]?.title
|
post.attributes?.translations?.[0]?.title
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.includes(searchName.toLowerCase())
|
.includes(searchName.toLowerCase()) === true
|
||||||
) {
|
) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue