2022-05-17 23:16:22 +02:00

504 lines
18 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { Button } from "components/Inputs/Button";
import { useAppLayout } from "contexts/AppLayoutContext";
import { UploadImageFragment } from "graphql/generated";
import { AppStaticProps } from "graphql/getAppStaticProps";
import { prettyLanguage, prettySlug } from "helpers/formatters";
import { getOgImage, ImageQuality, OgImage } from "helpers/img";
import { Immutable } from "helpers/types";
import { useMediaMobile } from "hooks/useMediaQuery";
import { AnchorIds } from "hooks/useScrollTopOnChange";
import Head from "next/head";
import { useRouter } from "next/router";
import { useEffect, useMemo, useState } from "react";
import { useSwipeable } from "react-swipeable";
import { OrderableList } from "./Inputs/OrderableList";
import { Select } from "./Inputs/Select";
import { MainPanel } from "./Panels/MainPanel";
import { Popup } from "./Popup";
interface Props extends AppStaticProps {
subPanel?: React.ReactNode;
subPanelIcon?: string;
contentPanel?: React.ReactNode;
title?: string;
navTitle: string | null | undefined;
thumbnail?: UploadImageFragment;
description?: string;
}
export function AppLayout(props: Immutable<Props>): JSX.Element {
const {
langui,
currencies,
languages,
subPanel,
contentPanel,
thumbnail,
title,
navTitle,
description,
subPanelIcon,
} = props;
const router = useRouter();
const isMobile = useMediaMobile();
const appLayout = useAppLayout();
const sensibilitySwipe = 1.1;
useMemo(() => {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
router.events?.on("routeChangeStart", () => {
appLayout.setConfigPanelOpen(false);
appLayout.setMainPanelOpen(false);
appLayout.setSubPanelOpen(false);
});
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
router.events?.on("hashChangeStart", () => {
appLayout.setSubPanelOpen(false);
});
}, [appLayout, router.events]);
const handlers = useSwipeable({
onSwipedLeft: (SwipeEventData) => {
if (appLayout.menuGestures) {
if (SwipeEventData.velocity < sensibilitySwipe) return;
if (appLayout.mainPanelOpen) {
appLayout.setMainPanelOpen(false);
} else if (subPanel && contentPanel) {
appLayout.setSubPanelOpen(true);
}
}
},
onSwipedRight: (SwipeEventData) => {
if (appLayout.menuGestures) {
if (SwipeEventData.velocity < sensibilitySwipe) return;
if (appLayout.subPanelOpen) {
appLayout.setSubPanelOpen(false);
} else {
appLayout.setMainPanelOpen(true);
}
}
},
});
const turnSubIntoContent = subPanel && !contentPanel;
const titlePrefix = "Accords Library";
const metaImage: OgImage = thumbnail
? getOgImage(ImageQuality.Og, thumbnail)
: {
image: "/default_og.jpg",
width: 1200,
height: 630,
alt: "Accord's Library Logo",
};
const ogTitle =
title ?? navTitle ?? prettySlug(router.asPath.split("/").pop());
const metaDescription = description ?? langui.default_description ?? "";
useEffect(() => {
document.getElementsByTagName("html")[0].style.fontSize = `${
(appLayout.fontSize ?? 1) * 100
}%`;
}, [appLayout.fontSize]);
const currencyOptions: string[] = [];
currencies.map((currency) => {
if (currency.attributes?.code)
currencyOptions.push(currency.attributes.code);
});
const [currencySelect, setCurrencySelect] = useState<number>(-1);
let defaultPreferredLanguages: string[] = [];
if (router.locale && router.locales) {
if (router.locale === "en") {
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(() => {
if (appLayout.currency)
setCurrencySelect(currencyOptions.indexOf(appLayout.currency));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [appLayout.currency]);
useEffect(() => {
if (currencySelect >= 0)
appLayout.setCurrency(currencyOptions[currencySelect]);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [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) {
gridCol = "grid-cols-[6rem_0px_1fr]";
} else {
gridCol = "grid-cols-[20rem_0px_1fr]";
}
return (
<div
className={`${
appLayout.darkMode ? "set-theme-dark" : "set-theme-light"
} ${
appLayout.dyslexic
? "set-theme-font-dyslexic"
: "set-theme-font-standard"
}`}
>
<div
{...handlers}
className={`fixed inset-0 touch-pan-y p-0 m-0 bg-light text-black grid
[grid-template-areas:'main_sub_content'] ${gridCol} mobile:grid-cols-[1fr]
mobile:grid-rows-[1fr_5rem] mobile:[grid-template-areas:'content''navbar']`}
>
<Head>
<title>{`${titlePrefix} - ${ogTitle}`}</title>
<meta
name="twitter:title"
content={`${titlePrefix} - ${ogTitle}`}
></meta>
<meta name="description" content={metaDescription} />
<meta name="twitter:description" content={metaDescription}></meta>
<meta property="og:image" content={metaImage.image}></meta>
<meta property="og:image:secure_url" content={metaImage.image}></meta>
<meta
property="og:image:width"
content={metaImage.width.toString()}
></meta>
<meta
property="og:image:height"
content={metaImage.height.toString()}
></meta>
<meta property="og:image:alt" content={metaImage.alt}></meta>
<meta property="og:image:type" content="image/jpeg"></meta>
<meta name="twitter:card" content="summary_large_image"></meta>
<meta name="twitter:image" content={metaImage.image}></meta>
</Head>
{/* Background when navbar is opened */}
<div
className={`[grid-area:content] mobile:z-10 absolute
inset-0 transition-[backdrop-filter] duration-500 ${
(appLayout.mainPanelOpen || appLayout.subPanelOpen) && isMobile
? "[backdrop-filter:blur(2px)]"
: "pointer-events-none touch-none "
}`}
>
<div
className={`absolute bg-shade inset-0 transition-opacity duration-500
${turnSubIntoContent ? "" : ""}
${
(appLayout.mainPanelOpen || appLayout.subPanelOpen) && isMobile
? "opacity-60"
: "opacity-0"
}`}
onClick={() => {
appLayout.setMainPanelOpen(false);
appLayout.setSubPanelOpen(false);
}}
></div>
</div>
{/* Content panel */}
<div
id={AnchorIds.CONTENT_PANEL}
className={`[grid-area:content] overflow-y-scroll bg-light texture-paper-dots`}
>
{contentPanel ? (
contentPanel
) : (
<div className="grid place-content-center h-full">
<div
className="text-dark border-dark border-2 border-dotted rounded-2xl
p-8 grid grid-flow-col place-items-center gap-9 opacity-40"
>
<p className="text-4xl"></p>
<p className="text-2xl w-64">{langui.select_option_sidebar}</p>
</div>
</div>
)}
</div>
{/* Sub panel */}
{subPanel && (
<div
className={`[grid-area:sub] mobile:[grid-area:content] mobile:z-10 mobile:w-[90%]
mobile:justify-self-end border-r-[1px] mobile:border-r-0 mobile:border-l-[1px]
border-black border-dotted overflow-y-scroll webkit-scrollbar:w-0
[scrollbar-width:none] transition-transform duration-300 bg-light texture-paper-dots
${
turnSubIntoContent
? "mobile:border-l-0 mobile:w-full"
: !appLayout.subPanelOpen && "mobile:translate-x-[100vw]"
}`}
>
{subPanel}
</div>
)}
{/* Main panel */}
<div
className={`[grid-area:main] mobile:[grid-area:content] mobile:z-10 mobile:w-[90%]
mobile:justify-self-start border-r-[1px] border-black border-dotted overflow-y-scroll
webkit-scrollbar:w-0 [scrollbar-width:none] transition-transform duration-300 bg-light
texture-paper-dots ${
appLayout.mainPanelOpen ? "" : "mobile:-translate-x-full"
}`}
>
<MainPanel langui={langui} />
</div>
{/* Navbar */}
<div
className="[grid-area:navbar] border-t-[1px] border-black border-dotted grid
grid-cols-[5rem_1fr_5rem] place-items-center desktop:hidden bg-light texture-paper-dots"
>
<span
className="material-icons mt-[.1em] cursor-pointer"
onClick={() => {
appLayout.setMainPanelOpen(!appLayout.mainPanelOpen);
appLayout.setSubPanelOpen(false);
}}
>
{appLayout.mainPanelOpen ? "close" : "menu"}
</span>
<p
className={`font-black font-headers text-center overflow-hidden ${
ogTitle && ogTitle.length > 30
? "text-xl max-h-14"
: "text-2xl max-h-16"
}`}
>
{ogTitle}
</p>
<span
className="material-icons mt-[.1em] cursor-pointer"
onClick={() => {
appLayout.setSubPanelOpen(!appLayout.subPanelOpen);
appLayout.setMainPanelOpen(false);
}}
>
{subPanel && !turnSubIntoContent
? appLayout.subPanelOpen
? "close"
: subPanelIcon
? subPanelIcon
: "tune"
: ""}
</span>
</div>
<Popup
state={appLayout.configPanelOpen}
setState={appLayout.setConfigPanelOpen}
>
<h2 className="text-2xl">{langui.settings}</h2>
<div
className="mt-4 grid gap-16 justify-items-center
text-center desktop:grid-cols-[auto_auto]"
>
{router.locales && (
<div>
<h3 className="text-xl">{langui.languages}</h3>
{appLayout.preferredLanguages && (
<OrderableList
items={
appLayout.preferredLanguages.length > 0
? new Map(
appLayout.preferredLanguages.map((locale) => [
locale,
prettyLanguage(locale, languages),
])
)
: new Map(
defaultPreferredLanguages.map((locale) => [
locale,
prettyLanguage(locale, languages),
])
)
}
insertLabels={
new Map([
[0, langui.primary_language],
[1, langui.secondary_language],
])
}
onChange={(items) => {
const preferredLanguages = [...items].map(
([code]) => code
);
appLayout.setPreferredLanguages(preferredLanguages);
if (router.locale !== preferredLanguages[0]) {
router.push(router.asPath, router.asPath, {
locale: preferredLanguages[0],
});
}
}}
/>
)}
</div>
)}
<div className="grid gap-8 place-items-center text-center desktop:grid-cols-2">
<div>
<h3 className="text-xl">{langui.theme}</h3>
<div className="flex flex-row">
<Button
onClick={() => {
appLayout.setDarkMode(false);
appLayout.setSelectedThemeMode(true);
}}
active={
appLayout.selectedThemeMode === true &&
appLayout.darkMode === false
}
className="rounded-r-none"
>
{langui.light}
</Button>
<Button
onClick={() => {
appLayout.setSelectedThemeMode(false);
}}
active={appLayout.selectedThemeMode === false}
className="rounded-l-none rounded-r-none border-x-0"
>
{langui.auto}
</Button>
<Button
onClick={() => {
appLayout.setDarkMode(true);
appLayout.setSelectedThemeMode(true);
}}
active={
appLayout.selectedThemeMode === true &&
appLayout.darkMode === true
}
className="rounded-l-none"
>
{langui.dark}
</Button>
</div>
</div>
<div>
<h3 className="text-xl">{langui.currency}</h3>
<div>
<Select
options={currencyOptions}
state={currencySelect}
setState={setCurrencySelect}
className="w-28"
/>
</div>
</div>
<div>
<h3 className="text-xl">{langui.font_size}</h3>
<div className="flex flex-row">
<Button
className="rounded-r-none"
onClick={() =>
appLayout.setFontSize(
appLayout.fontSize
? appLayout.fontSize / 1.05
: 1 / 1.05
)
}
>
<span className="material-icons !text-base">
text_decrease
</span>
</Button>
<Button
className="rounded-l-none rounded-r-none border-x-0"
onClick={() => appLayout.setFontSize(1)}
>
{((appLayout.fontSize ?? 1) * 100).toLocaleString(
undefined,
{
maximumFractionDigits: 0,
}
)}
%
</Button>
<Button
className="rounded-l-none"
onClick={() =>
appLayout.setFontSize(
appLayout.fontSize
? appLayout.fontSize * 1.05
: 1 * 1.05
)
}
>
<span className="material-icons !text-base">
text_increase
</span>
</Button>
</div>
</div>
<div>
<h3 className="text-xl">{langui.font}</h3>
<div className="grid gap-2">
<Button
active={appLayout.dyslexic === false}
onClick={() => appLayout.setDyslexic(false)}
className="font-zenMaruGothic"
>
Zen Maru Gothic
</Button>
<Button
active={appLayout.dyslexic === true}
onClick={() => appLayout.setDyslexic(true)}
className="font-openDyslexic"
>
OpenDyslexic
</Button>
</div>
</div>
<div>
<h3 className="text-xl">{langui.player_name}</h3>
<input
type="text"
placeholder="<player>"
className="w-48"
onInput={(event) =>
appLayout.setPlayerName(
(event.target as HTMLInputElement).value
)
}
value={appLayout.playerName}
/>
</div>
</div>
</div>
</Popup>
</div>
</div>
);
}