Now using expression function style + some sections

This commit is contained in:
DrMint 2022-07-03 14:34:00 +02:00
parent 8b80ec4ca3
commit be1a32181e
94 changed files with 2537 additions and 1811 deletions

View File

@ -41,7 +41,7 @@ module.exports = {
eqeqeq: "error", eqeqeq: "error",
"func-name-matching": "warn", "func-name-matching": "warn",
"func-names": "warn", "func-names": "warn",
"func-style": ["warn", "declaration"], "func-style": ["warn", "expression"],
"grouped-accessor-pairs": "warn", "grouped-accessor-pairs": "warn",
"guard-for-in": "warn", "guard-for-in": "warn",
"id-denylist": ["error", "data", "err", "e", "cb", "callback", "i"], "id-denylist": ["error", "data", "err", "e", "cb", "callback", "i"],

80
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,80 @@
# CONTRIBUTING
## Styling choices
### Pages
```tsx
import ...
/*
* ╭─────────────╮
* ────────────────────────────────────────╯ CONSTANTS ╰──────────────────────────────────────────
*/
const MY_CONSTANT = "value"
const DEFAULT_FILTERS_STATE = {}
/*
* ╭────────╮
* ──────────────────────────────────────────╯ PAGE ╰─────────────────────────────────────────────
*/
interface Props {}
const PageName = () => {}
export default PageName;
/*
* ╭──────────────────────╮
* ───────────────────────────────────╯ NEXT DATA FETCHING ╰──────────────────────────────────────
*/
export const getStaticProps: GetStaticProps = async (context) => {}
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
export const getStaticPaths: GetStaticPaths = async (context) => {}
/*
* ╭───────────────────╮
* ─────────────────────────────────────╯ PRIVATE METHODS ╰───────────────────────────────────────
*/
/*
* ╭──────────────────────╮
* ───────────────────────────────────╯ PRIVATE COMPONENTS ╰──────────────────────────────────────
*/
interface Component1Interface {}
const Component1 = () => {}
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
interface Component2Interface {}
const Component2 = () => {}
```
### Components
```tsx
/*
* ╭─────────────╮
* ────────────────────────────────────────╯ CONSTANTS ╰──────────────────────────────────────────
*/
const MY_CONSTANT = "value";
const DEFAULT_FILTERS_STATE = {};
/*
* ╭─────────────╮
* ───────────────────────────────────────╯ COMPONENT ╰───────────────────────────────────────────
*/
interface ComponentProps {}
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
export const Component = () => {};
```

View File

@ -27,6 +27,19 @@ import { ContentPlaceholder } from "./PanelComponents/ContentPlaceholder";
import { MainPanel } from "./Panels/MainPanel"; import { MainPanel } from "./Panels/MainPanel";
import { Popup } from "./Popup"; import { Popup } from "./Popup";
/*
*
* CONSTANTS
*/
const SENSIBILITY_SWIPE = 1.1;
const TITLE_PREFIX = "Accords Library";
/*
*
* COMPONENT
*/
interface Props extends AppStaticProps { interface Props extends AppStaticProps {
subPanel?: React.ReactNode; subPanel?: React.ReactNode;
subPanelIcon?: Icon; subPanelIcon?: Icon;
@ -38,24 +51,21 @@ interface Props extends AppStaticProps {
contentPanelScroolbar?: boolean; contentPanelScroolbar?: boolean;
} }
const SENSIBILITY_SWIPE = 1.1; // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
const TITLE_PREFIX = "Accords Library";
export function AppLayout(props: Props): JSX.Element {
const {
langui,
currencies,
languages,
subPanel,
contentPanel,
thumbnail,
title,
navTitle,
description,
subPanelIcon = Icon.Tune,
contentPanelScroolbar = true,
} = props;
export const AppLayout = ({
langui,
currencies,
languages,
subPanel,
contentPanel,
thumbnail,
title,
navTitle,
description,
subPanelIcon = Icon.Tune,
contentPanelScroolbar = true,
}: Props): JSX.Element => {
const { const {
configPanelOpen, configPanelOpen,
currency, currency,
@ -396,7 +406,7 @@ export function AppLayout(props: Props): JSX.Element {
insertLabels={ insertLabels={
new Map([ new Map([
[0, langui.primary_language], [0, langui.primary_language],
[1, langui.secondary_language], [1, langui.secondary_language],
]) ])
} }
onChange={(items) => { onChange={(items) => {
@ -517,4 +527,4 @@ export function AppLayout(props: Props): JSX.Element {
</div> </div>
</div> </div>
); );
} };

View File

@ -1,21 +1,26 @@
import { cJoin } from "helpers/className"; import { cJoin } from "helpers/className";
/*
*
* COMPONENT
*/
interface Props { interface Props {
className?: string; className?: string;
children: React.ReactNode; children: React.ReactNode;
} }
export function Chip(props: Props): JSX.Element { // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
return (
<div export const Chip = ({ className, children }: Props): JSX.Element => (
className={cJoin( <div
`grid place-content-center place-items-center whitespace-nowrap rounded-full className={cJoin(
`grid place-content-center place-items-center whitespace-nowrap rounded-full
border-[1px] px-1.5 pb-[0.14rem] text-xs opacity-70 border-[1px] px-1.5 pb-[0.14rem] text-xs opacity-70
transition-[color,_opacity,_border-color] hover:opacity-100`, transition-[color,_opacity,_border-color] hover:opacity-100`,
props.className className
)} )}
> >
{props.children} {children}
</div> </div>
); );
}

View File

@ -1,17 +1,21 @@
import { cJoin } from "helpers/className"; import { cJoin } from "helpers/className";
/*
*
* COMPONENT
*/
interface Props { interface Props {
className?: string; className?: string;
} }
export function HorizontalLine(props: Props): JSX.Element { // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
const { className } = props;
return ( export const HorizontalLine = ({ className }: Props): JSX.Element => (
<div <div
className={cJoin( className={cJoin(
"my-8 h-0 w-full border-t-[3px] border-dotted border-black", "my-8 h-0 w-full border-t-[3px] border-dotted border-black",
className className
)} )}
></div> ></div>
); );
}

View File

@ -1,27 +1,35 @@
import { cJoin } from "helpers/className"; import { cJoin } from "helpers/className";
import { MouseEventHandler } from "react"; import { MouseEventHandler } from "react";
/*
*
* COMPONENT
*/
interface Props { interface Props {
className?: string; className?: string;
onClick?: MouseEventHandler<HTMLSpanElement> | undefined; onClick?: MouseEventHandler<HTMLSpanElement> | undefined;
icon: Icon; icon: Icon;
} }
export function Ico(props: Props): JSX.Element { // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
const { onClick, icon, className } = props;
return ( export const Ico = ({ onClick, icon, className }: Props): JSX.Element => (
<span <span
onClick={onClick} onClick={onClick}
className={cJoin( className={cJoin(
"material-icons [font-size:inherit] [line-height:inherit]", "material-icons [font-size:inherit] [line-height:inherit]",
className className
)} )}
> >
{icon} {icon}
</span> </span>
); );
}
/*
*
* OTHER
*/
export enum Icon { export enum Icon {
Onek = "1k", Onek = "1k",

View File

@ -1,9 +1,13 @@
import { UploadImageFragment } from "graphql/generated"; import { UploadImageFragment } from "graphql/generated";
import { getAssetURL, getImgSizesByQuality, ImageQuality } from "helpers/img"; import { getAssetURL, getImgSizesByQuality, ImageQuality } from "helpers/img";
import { ImageProps } from "next/image"; import { ImageProps } from "next/image";
import { MouseEventHandler } from "react"; import { MouseEventHandler } from "react";
/*
*
* CONSTANTS
*/
interface Props { interface Props {
className?: string; className?: string;
image?: UploadImageFragment | string; image?: UploadImageFragment | string;
@ -12,15 +16,15 @@ interface Props {
onClick?: MouseEventHandler<HTMLImageElement>; onClick?: MouseEventHandler<HTMLImageElement>;
} }
export function Img(props: Props): JSX.Element { // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
const {
className,
image,
quality = ImageQuality.Small,
alt,
onClick,
} = props;
export const Img = ({
className,
image,
quality = ImageQuality.Small,
alt,
onClick,
}: Props): JSX.Element => {
if (typeof image === "string") { if (typeof image === "string") {
return ( return (
<img className={className} src={image} alt={alt ?? ""} loading="lazy" /> <img className={className} src={image} alt={alt ?? ""} loading="lazy" />
@ -40,4 +44,4 @@ export function Img(props: Props): JSX.Element {
); );
} }
return <></>; return <></>;
} };

View File

@ -1,11 +1,15 @@
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 { ConditionalWrapper, Wrapper } from "helpers/component";
import { isDefined, isDefinedAndNotEmpty } from "helpers/others"; import { isDefined, isDefinedAndNotEmpty } from "helpers/others";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import React, { MouseEventHandler } from "react"; import React, { MouseEventHandler } from "react";
/*
*
* COMPONENT
*/
interface Props { interface Props {
id?: string; id?: string;
className?: string; className?: string;
@ -20,21 +24,21 @@ interface Props {
badgeNumber?: number; badgeNumber?: number;
} }
export function Button(props: Props): JSX.Element { // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
const {
draggable,
id,
onClick,
active,
className,
icon,
text,
target,
href,
locale,
badgeNumber,
} = props;
export const Button = ({
draggable,
id,
onClick,
active,
className,
icon,
text,
target,
href,
locale,
badgeNumber,
}: Props): JSX.Element => {
const router = useRouter(); const router = useRouter();
return ( return (
@ -87,17 +91,19 @@ export function Button(props: Props): JSX.Element {
</div> </div>
</ConditionalWrapper> </ConditionalWrapper>
); );
} };
/*
*
* PRIVATE COMPONENTS
*/
interface LinkWrapperProps { interface LinkWrapperProps {
href?: string; href?: string;
} }
function LinkWrapper(props: LinkWrapperProps & Wrapper) { const LinkWrapper = ({ children, href }: LinkWrapperProps & Wrapper) => (
const { children, href } = props; <a href={href} target="_blank" rel="noreferrer">
return ( {children}
<a href={href} target="_blank" rel="noreferrer"> </a>
{children} );
</a>
);
}

View File

@ -4,6 +4,11 @@ import { ConditionalWrapper, Wrapper } from "helpers/component";
import { isDefinedAndNotEmpty } from "helpers/others"; import { isDefinedAndNotEmpty } from "helpers/others";
import { Button } from "./Button"; import { Button } from "./Button";
/*
*
* COMPONENT
*/
interface Props { interface Props {
className?: string; className?: string;
buttonsProps: (Parameters<typeof Button>[0] & { buttonsProps: (Parameters<typeof Button>[0] & {
@ -11,43 +16,46 @@ interface Props {
})[]; })[];
} }
export function ButtonGroup(props: Props): JSX.Element { // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
const { buttonsProps, className } = props;
return ( export const ButtonGroup = ({
<div className={cJoin("grid grid-flow-col", className)}> buttonsProps,
{buttonsProps.map((buttonProps, index) => ( className,
<ConditionalWrapper }: Props): JSX.Element => (
key={index} <div className={cJoin("grid grid-flow-col", className)}>
isWrapping={isDefinedAndNotEmpty(buttonProps.tooltip)} {buttonsProps.map((buttonProps, index) => (
wrapper={ToolTipWrapper} <ConditionalWrapper
wrapperProps={{ text: buttonProps.tooltip ?? "" }} key={index}
> isWrapping={isDefinedAndNotEmpty(buttonProps.tooltip)}
<Button wrapper={ToolTipWrapper}
{...buttonProps} wrapperProps={{ text: buttonProps.tooltip ?? "" }}
className={ >
index === 0 <Button
? "rounded-r-none border-r-0" {...buttonProps}
: index === buttonsProps.length - 1 className={
? "rounded-l-none" index === 0
: "rounded-none border-r-0" ? "rounded-r-none border-r-0"
} : index === buttonsProps.length - 1
/> ? "rounded-l-none"
</ConditionalWrapper> : "rounded-none border-r-0"
))} }
</div> />
); </ConditionalWrapper>
} ))}
</div>
);
/*
*
* PRIVATE COMPONENTS
*/
interface ToolTipWrapperProps { interface ToolTipWrapperProps {
text: string; text: string;
} }
function ToolTipWrapper(props: ToolTipWrapperProps & Wrapper) { const ToolTipWrapper = ({ text, children }: ToolTipWrapperProps & Wrapper) => (
const { text, children } = props; <ToolTip content={text}>
return ( <>{children}</>
<ToolTip content={text}> </ToolTip>
<>{children}</> );
</ToolTip>
);
}

View File

@ -3,11 +3,15 @@ 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 { iterateMap } from "helpers/others"; import { iterateMap } from "helpers/others";
import { Fragment } from "react"; import { Fragment } from "react";
import { ToolTip } from "../ToolTip"; import { ToolTip } from "../ToolTip";
import { Button } from "./Button"; import { Button } from "./Button";
/*
*
* COMPONENT
*/
interface Props { interface Props {
className?: string; className?: string;
languages: AppStaticProps["languages"]; languages: AppStaticProps["languages"];
@ -16,29 +20,33 @@ interface Props {
onLanguageChanged: (index: number) => void; onLanguageChanged: (index: number) => void;
} }
export function LanguageSwitcher(props: Props): JSX.Element { // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
const { locales, className, localesIndex, onLanguageChanged } = props;
return ( export const LanguageSwitcher = ({
<ToolTip className,
content={ locales,
<div className={cJoin("flex flex-col gap-2", className)}> localesIndex,
{iterateMap(locales, (locale, value, index) => ( languages,
<Fragment key={index}> onLanguageChanged,
<Button }: Props): JSX.Element => (
active={value === localesIndex} <ToolTip
onClick={() => onLanguageChanged(value)} content={
text={prettyLanguage(locale, props.languages)} <div className={cJoin("flex flex-col gap-2", className)}>
/> {iterateMap(locales, (locale, value, index) => (
</Fragment> <Fragment key={index}>
))} <Button
</div> active={value === localesIndex}
} onClick={() => onLanguageChanged(value)}
> text={prettyLanguage(locale, languages)}
<Button />
badgeNumber={locales.size > 1 ? locales.size : undefined} </Fragment>
icon={Icon.Translate} ))}
/> </div>
</ToolTip> }
); >
} <Button
badgeNumber={locales.size > 1 ? locales.size : undefined}
icon={Icon.Translate}
/>
</ToolTip>
);

View File

@ -1,8 +1,12 @@
import { Ico, Icon } from "components/Ico"; import { Ico, Icon } from "components/Ico";
import { isDefinedAndNotEmpty, iterateMap, mapMoveEntry } from "helpers/others"; import { isDefinedAndNotEmpty, iterateMap, mapMoveEntry } from "helpers/others";
import { Fragment, useCallback, useState } from "react"; import { Fragment, useCallback, useState } from "react";
/*
*
* COMPONENT
*/
interface Props { interface Props {
className?: string; className?: string;
items: Map<string, string>; items: Map<string, string>;
@ -10,9 +14,14 @@ interface Props {
onChange?: (items: Map<string, string>) => void; onChange?: (items: Map<string, string>) => void;
} }
export function OrderableList(props: Props): JSX.Element { // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
const { onChange } = props;
const [items, setItems] = useState<Map<string, string>>(props.items); export const OrderableList = ({
onChange,
items: propsItems,
insertLabels,
}: Props): JSX.Element => {
const [items, setItems] = useState<Map<string, string>>(propsItems);
const updateOrder = useCallback( const updateOrder = useCallback(
(sourceIndex: number, targetIndex: number) => { (sourceIndex: number, targetIndex: number) => {
@ -27,10 +36,9 @@ export function OrderableList(props: Props): JSX.Element {
<div className="grid gap-2"> <div className="grid gap-2">
{iterateMap(items, (key, value, index) => ( {iterateMap(items, (key, value, index) => (
<Fragment key={key}> <Fragment key={key}>
{props.insertLabels && {insertLabels && isDefinedAndNotEmpty(insertLabels.get(index)) && (
isDefinedAndNotEmpty(props.insertLabels.get(index)) && ( <p>{insertLabels.get(index)}</p>
<p>{props.insertLabels.get(index)}</p> )}
)}
<div <div
onDragStart={(event) => { onDragStart={(event) => {
const source = event.target as HTMLElement; const source = event.target as HTMLElement;
@ -89,4 +97,4 @@ export function OrderableList(props: Props): JSX.Element {
))} ))}
</div> </div>
); );
} };

View File

@ -1,9 +1,13 @@
import { Icon } from "components/Ico"; import { Icon } from "components/Ico";
import { cJoin } from "helpers/className"; import { cJoin } from "helpers/className";
import { Dispatch, SetStateAction } from "react"; import { Dispatch, SetStateAction } from "react";
import { Button } from "./Button"; import { Button } from "./Button";
/*
*
* COMPONENT
*/
interface Props { interface Props {
className?: string; className?: string;
maxPage: number; maxPage: number;
@ -11,27 +15,27 @@ interface Props {
setPage: Dispatch<SetStateAction<number>>; setPage: Dispatch<SetStateAction<number>>;
} }
export function PageSelector(props: Props): JSX.Element { // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
const { page, setPage, maxPage, className } = props;
return ( export const PageSelector = ({
<div className={cJoin("flex flex-row place-content-center", className)}> page,
<Button setPage,
onClick={() => setPage((current) => (page > 0 ? current - 1 : current))} maxPage,
className="rounded-r-none" className,
icon={Icon.NavigateBefore} }: Props): JSX.Element => (
/> <div className={cJoin("flex flex-row place-content-center", className)}>
<Button <Button
className="rounded-none border-x-0" onClick={() => setPage((current) => (page > 0 ? current - 1 : current))}
text={(page + 1).toString()} className="rounded-r-none"
/> icon={Icon.NavigateBefore}
<Button />
onClick={() => <Button className="rounded-none border-x-0" text={(page + 1).toString()} />
setPage((current) => (page < maxPage ? page + 1 : current)) <Button
} onClick={() =>
className="rounded-l-none" setPage((current) => (page < maxPage ? page + 1 : current))
icon={Icon.NavigateNext} }
/> className="rounded-l-none"
</div> icon={Icon.NavigateNext}
); />
} </div>
);

View File

@ -1,9 +1,13 @@
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 { useToggle } from "hooks/useToggle"; import { useToggle } from "hooks/useToggle";
import { Dispatch, Fragment, SetStateAction, useState } from "react"; import { Dispatch, Fragment, SetStateAction, useState } from "react";
/*
*
* COMPONENT
*/
interface Props { interface Props {
setState: Dispatch<SetStateAction<number>>; setState: Dispatch<SetStateAction<number>>;
state: number; state: number;
@ -13,8 +17,15 @@ interface Props {
className?: string; className?: string;
} }
export function Select(props: Props): JSX.Element { // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
const { className, state, options, allowEmpty, setState } = props;
export const Select = ({
className,
state,
options,
allowEmpty,
setState,
}: Props): JSX.Element => {
const [opened, setOpened] = useState(false); const [opened, setOpened] = useState(false);
const toggleOpened = useToggle(setOpened); const toggleOpened = useToggle(setOpened);
@ -80,4 +91,4 @@ export function Select(props: Props): JSX.Element {
</div> </div>
</div> </div>
); );
} };

View File

@ -1,8 +1,12 @@
import { cIf, cJoin } from "helpers/className"; import { cIf, cJoin } from "helpers/className";
import { useToggle } from "hooks/useToggle"; import { useToggle } from "hooks/useToggle";
import { Dispatch, SetStateAction } from "react"; import { Dispatch, SetStateAction } from "react";
/*
*
* COMPONENT
*/
interface Props { interface Props {
setState: Dispatch<SetStateAction<boolean>>; setState: Dispatch<SetStateAction<boolean>>;
state: boolean; state: boolean;
@ -10,8 +14,14 @@ interface Props {
disabled?: boolean; disabled?: boolean;
} }
export function Switch(props: Props): JSX.Element { // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
const { state, setState, className, disabled = false } = props;
export const Switch = ({
state,
setState,
className,
disabled = false,
}: Props): JSX.Element => {
const toggleState = useToggle(setState); const toggleState = useToggle(setState);
return ( return (
<div <div
@ -41,4 +51,4 @@ export function Switch(props: Props): JSX.Element {
></div> ></div>
</div> </div>
); );
} };

View File

@ -1,9 +1,13 @@
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 { isDefinedAndNotEmpty } from "helpers/others";
import { Dispatch, SetStateAction } from "react"; import { Dispatch, SetStateAction } from "react";
/*
*
* COMPONENT
*/
interface Props { interface Props {
state: string | undefined; state: string | undefined;
setState: setState:
@ -14,32 +18,36 @@ interface Props {
placeholder?: string; placeholder?: string;
} }
export function TextInput(props: Props): JSX.Element { // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
const { state, setState, className, name, placeholder } = props;
return ( export const TextInput = ({
<div className={cJoin("relative", className)}> state,
<input setState,
className="w-full" className,
type="text" name,
name={name} placeholder,
value={state} }: Props): JSX.Element => (
placeholder={placeholder} <div className={cJoin("relative", className)}>
onChange={(event) => { <input
setState(event.target.value); className="w-full"
}} type="text"
/> name={name}
{isDefinedAndNotEmpty(state) && ( value={state}
<div className="absolute right-4 top-0 bottom-0 grid place-items-center"> placeholder={placeholder}
<Ico onChange={(event) => {
className="cursor-pointer !text-xs" setState(event.target.value);
icon={Icon.Close} }}
onClick={() => { />
setState(""); {isDefinedAndNotEmpty(state) && (
}} <div className="absolute right-4 top-0 bottom-0 grid place-items-center">
/> <Ico
</div> className="cursor-pointer !text-xs"
)} icon={Icon.Close}
</div> onClick={() => {
); setState("");
} }}
/>
</div>
)}
</div>
);

View File

@ -1,32 +1,33 @@
import { cIf, cJoin } from "helpers/className"; import { cIf, cJoin } from "helpers/className";
import { isDefinedAndNotEmpty } from "helpers/others"; import { isDefinedAndNotEmpty } from "helpers/others";
/*
*
* COMPONENT
*/
interface Props { interface Props {
label: string | null | undefined; label: string | null | undefined;
input: JSX.Element; input: JSX.Element;
disabled?: boolean; disabled?: boolean;
} }
export function WithLabel(props: Props): JSX.Element { // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
const { label, input, disabled } = props;
return ( export const WithLabel = ({ label, input, disabled }: Props): JSX.Element => (
<div <div
className={cJoin( className={cJoin(
"flex flex-row place-content-between place-items-center gap-2", "flex flex-row place-content-between place-items-center gap-2",
cIf(disabled, "text-dark brightness-150 contrast-75 grayscale") cIf(disabled, "text-dark brightness-150 contrast-75 grayscale")
)} )}
> >
{isDefinedAndNotEmpty(label) && ( {isDefinedAndNotEmpty(label) && (
<p <p
className={cJoin( className={cJoin("text-left", cIf(label.length < 10, "flex-shrink-0"))}
"text-left", >
cIf(label.length < 10, "flex-shrink-0") {label}:
)} </p>
> )}
{label}: {input}
</p> </div>
)} );
{input}
</div>
);
}

View File

@ -1,21 +1,26 @@
import { cJoin } from "helpers/className"; import { cJoin } from "helpers/className";
/*
*
* COMPONENT
*/
interface Props { interface Props {
className?: string; className?: string;
children: React.ReactNode; children: React.ReactNode;
id?: string; id?: string;
} }
export function InsetBox(props: Props): JSX.Element { // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
return (
<div export const InsetBox = ({ id, className, children }: Props): JSX.Element => (
id={props.id} <div
className={cJoin( id={id}
"w-full rounded-xl bg-mid p-8 shadow-inner-sm shadow-shade", className={cJoin(
props.className "w-full rounded-xl bg-mid p-8 shadow-inner-sm shadow-shade",
)} className
> )}
{props.children} >
</div> {children}
); </div>
} );

View File

@ -5,73 +5,75 @@ import { useAppLayout } from "contexts/AppLayoutContext";
import { AppStaticProps } from "graphql/getAppStaticProps"; import { AppStaticProps } from "graphql/getAppStaticProps";
import { LibraryItemUserStatus } from "helpers/types"; import { LibraryItemUserStatus } from "helpers/types";
/*
*
* COMPONENT
*/
interface Props { interface Props {
id: string | null | undefined; id: string;
displayCTAs: boolean;
expand?: boolean; expand?: boolean;
langui: AppStaticProps["langui"]; langui: AppStaticProps["langui"];
} }
export function PreviewCardCTAs(props: Props): JSX.Element { // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
const { id, displayCTAs, expand = false, langui } = props;
const appLayout = useAppLayout();
export const PreviewCardCTAs = ({
id,
expand = false,
langui,
}: Props): JSX.Element => {
const appLayout = useAppLayout();
return ( return (
<> <>
{displayCTAs && id && ( <div
<div className={`flex flex-row place-content-center place-items-center ${
className={`flex flex-row place-content-center place-items-center ${ expand ? "gap-4" : "gap-2"
expand ? "gap-4" : "gap-2" }`}
}`} >
> <ToolTip content={langui.want_it} disabled={expand}>
<ToolTip content={langui.want_it} disabled={expand}> <Button
<Button icon={Icon.Favorite}
icon={Icon.Favorite} text={expand ? langui.want_it : undefined}
text={expand ? langui.want_it : undefined} active={
active={ appLayout.libraryItemUserStatus?.[id] ===
appLayout.libraryItemUserStatus?.[id] === LibraryItemUserStatus.Want
LibraryItemUserStatus.Want }
} onClick={(event) => {
onClick={(event) => { event.preventDefault();
event.preventDefault(); appLayout.setLibraryItemUserStatus((current) => {
appLayout.setLibraryItemUserStatus((current) => { const newLibraryItemUserStatus = current ? { ...current } : {};
const newLibraryItemUserStatus = current newLibraryItemUserStatus[id] =
? { ...current } newLibraryItemUserStatus[id] === LibraryItemUserStatus.Want
: {}; ? LibraryItemUserStatus.None
newLibraryItemUserStatus[id] = : LibraryItemUserStatus.Want;
newLibraryItemUserStatus[id] === LibraryItemUserStatus.Want return newLibraryItemUserStatus;
? LibraryItemUserStatus.None });
: LibraryItemUserStatus.Want; }}
return newLibraryItemUserStatus; />
}); </ToolTip>
}} <ToolTip content={langui.have_it} disabled={expand}>
/> <Button
</ToolTip> icon={Icon.BackHand}
<ToolTip content={langui.have_it} disabled={expand}> text={expand ? langui.have_it : undefined}
<Button active={
icon={Icon.BackHand} appLayout.libraryItemUserStatus?.[id] ===
text={expand ? langui.have_it : undefined} LibraryItemUserStatus.Have
active={ }
appLayout.libraryItemUserStatus?.[id] === onClick={(event) => {
LibraryItemUserStatus.Have event.preventDefault();
} appLayout.setLibraryItemUserStatus((current) => {
onClick={(event) => { const newLibraryItemUserStatus = current ? { ...current } : {};
event.preventDefault(); newLibraryItemUserStatus[id] =
appLayout.setLibraryItemUserStatus((current) => { newLibraryItemUserStatus[id] === LibraryItemUserStatus.Have
const newLibraryItemUserStatus = current ? LibraryItemUserStatus.None
? { ...current } : LibraryItemUserStatus.Have;
: {}; return newLibraryItemUserStatus;
newLibraryItemUserStatus[id] = });
newLibraryItemUserStatus[id] === LibraryItemUserStatus.Have }}
? LibraryItemUserStatus.None />
: LibraryItemUserStatus.Have; </ToolTip>
return newLibraryItemUserStatus; </div>
});
}}
/>
</ToolTip>
</div>
)}
</> </>
); );
} };

View File

@ -13,10 +13,14 @@ import {
isDefined, isDefined,
isDefinedAndNotEmpty, isDefinedAndNotEmpty,
} from "helpers/others"; } from "helpers/others";
import { useSmartLanguage } from "hooks/useSmartLanguage"; import { useSmartLanguage } from "hooks/useSmartLanguage";
import { Fragment, useCallback, useMemo } from "react"; import { Fragment, useCallback, useMemo } from "react";
/*
*
* COMPONENT
*/
interface Props { interface Props {
openLightBox: (images: string[], index?: number) => void; openLightBox: (images: string[], index?: number) => void;
scanSet: NonNullable< scanSet: NonNullable<
@ -45,10 +49,17 @@ interface Props {
>["content"]; >["content"];
} }
export function ScanSet(props: Props): JSX.Element { // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
const { openLightBox, scanSet, slug, title, languages, langui, content } =
props;
export const ScanSet = ({
openLightBox,
scanSet,
slug,
title,
languages,
langui,
content,
}: Props): JSX.Element => {
const [selectedScan, LanguageSwitcher, languageSwitcherProps] = const [selectedScan, LanguageSwitcher, languageSwitcherProps] =
useSmartLanguage({ useSmartLanguage({
items: scanSet, items: scanSet,
@ -227,4 +238,4 @@ export function ScanSet(props: Props): JSX.Element {
)} )}
</> </>
); );
} };

View File

@ -9,10 +9,14 @@ import {
import { AppStaticProps } from "graphql/getAppStaticProps"; import { AppStaticProps } from "graphql/getAppStaticProps";
import { getAssetURL, ImageQuality } from "helpers/img"; import { getAssetURL, ImageQuality } from "helpers/img";
import { filterHasAttributes, getStatusDescription } from "helpers/others"; import { filterHasAttributes, getStatusDescription } from "helpers/others";
import { useSmartLanguage } from "hooks/useSmartLanguage"; import { useSmartLanguage } from "hooks/useSmartLanguage";
import { Fragment, useCallback, useMemo } from "react"; import { Fragment, useCallback, useMemo } from "react";
/*
*
* COMPONENT
*/
interface Props { interface Props {
openLightBox: (images: string[], index?: number) => void; openLightBox: (images: string[], index?: number) => void;
images: NonNullable< images: NonNullable<
@ -26,9 +30,14 @@ interface Props {
langui: AppStaticProps["langui"]; langui: AppStaticProps["langui"];
} }
export function ScanSetCover(props: Props): JSX.Element { // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
const { openLightBox, images, languages, langui } = props;
export const ScanSetCover = ({
openLightBox,
images,
languages,
langui,
}: Props): JSX.Element => {
const [selectedScan, LanguageSwitcher, languageSwitcherProps] = const [selectedScan, LanguageSwitcher, languageSwitcherProps] =
useSmartLanguage({ useSmartLanguage({
items: images, items: images,
@ -176,4 +185,4 @@ export function ScanSetCover(props: Props): JSX.Element {
); );
} }
return <></>; return <></>;
} };

View File

@ -6,6 +6,18 @@ import { Button } from "./Inputs/Button";
import { Popup } from "./Popup"; import { Popup } from "./Popup";
import { Icon } from "components/Ico"; import { Icon } from "components/Ico";
/*
*
* CONSTANTS
*/
const SENSIBILITY_SWIPE = 0.5;
/*
*
* COMPONENT
*/
interface Props { interface Props {
setState: setState:
| Dispatch<SetStateAction<boolean | undefined>> | Dispatch<SetStateAction<boolean | undefined>>
@ -16,11 +28,15 @@ interface Props {
setIndex: Dispatch<SetStateAction<number>>; setIndex: Dispatch<SetStateAction<number>>;
} }
const SENSIBILITY_SWIPE = 0.5; // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
export function LightBox(props: Props): JSX.Element {
const { state, setState, images, index, setIndex } = props;
export const LightBox = ({
state,
setState,
images,
index,
setIndex,
}: Props): JSX.Element => {
const handlePrevious = useCallback(() => { const handlePrevious = useCallback(() => {
if (index > 0) setIndex(index - 1); if (index > 0) setIndex(index - 1);
}, [index, setIndex]); }, [index, setIndex]);
@ -83,4 +99,4 @@ export function LightBox(props: Props): JSX.Element {
)} )}
</> </>
); );
} };

View File

@ -4,24 +4,33 @@ import { Img } from "components/Img";
import { InsetBox } from "components/InsetBox"; import { InsetBox } from "components/InsetBox";
import { ToolTip } from "components/ToolTip"; import { ToolTip } from "components/ToolTip";
import { useAppLayout } from "contexts/AppLayoutContext"; import { useAppLayout } from "contexts/AppLayoutContext";
import { AppStaticProps } from "graphql/getAppStaticProps";
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 { isDefined, isDefinedAndNotEmpty } from "helpers/others";
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, { useMemo } from "react"; import React, { Fragment, useMemo } from "react";
import ReactDOMServer from "react-dom/server"; import ReactDOMServer from "react-dom/server";
interface Props { /*
*
* COMPONENT
*/
interface MarkdawnProps {
className?: string; className?: string;
text: string; text: string;
} }
export function Markdawn(props: Props): JSX.Element { // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
const { className, text: rawText } = props;
export const Markdawn = ({
className,
text: rawText,
}: MarkdawnProps): JSX.Element => {
const appLayout = useAppLayout(); const appLayout = useAppLayout();
const router = useRouter(); const router = useRouter();
const [openLightBox, LightBox] = useLightBox(); const [openLightBox, LightBox] = useLightBox();
@ -299,33 +308,121 @@ export function Markdawn(props: Props): JSX.Element {
); );
} }
return <></>; return <></>;
};
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
interface TableOfContentsProps {
text: string;
title?: string;
langui: AppStaticProps["langui"];
} }
function HeaderToolTip(props: { id: string }) { // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
return (
<ToolTip export const TableOfContents = ({
content={"Copy anchor link"} text,
trigger="mouseenter" title,
className="text-sm" langui,
> }: TableOfContentsProps): JSX.Element => {
<ToolTip content={"Copied! 👍"} trigger="click" className="text-sm"> const router = useRouter();
<Ico const toc = useMemo(
icon={Icon.Link} () => getTocFromMarkdawn(preprocessMarkDawn(text), title),
className="transition-color cursor-pointer hover:text-dark" [text, title]
onClick={() => {
navigator.clipboard.writeText(
`${process.env.NEXT_PUBLIC_URL_SELF + window.location.pathname}#${
props.id
}`
);
}}
/>
</ToolTip>
</ToolTip>
); );
return (
<>
<h3 className="text-xl">{langui.table_of_contents}</h3>
<div className="max-w-[14.5rem] text-left">
<p className="relative my-2 overflow-x-hidden text-ellipsis whitespace-nowrap text-left">
<a onClick={async () => router.replace(`#${toc.slug}`)}>
{<abbr title={toc.title}>{toc.title}</abbr>}
</a>
</p>
<TocLevel tocchildren={toc.children} parentNumbering="" />
</div>
</>
);
};
/*
*
* PRIVATE COMPONENTS
*/
interface TocInterface {
title: string;
slug: string;
children: TocInterface[];
} }
function typographicRules(text: string): string { interface LevelProps {
tocchildren: TocInterface[];
parentNumbering: string;
}
const TocLevel = ({
tocchildren,
parentNumbering,
}: LevelProps): JSX.Element => {
const router = useRouter();
return (
<ol className="pl-4 text-left">
{tocchildren.map((child, childIndex) => (
<Fragment key={child.slug}>
<li className="my-2 w-full overflow-x-hidden text-ellipsis whitespace-nowrap">
<span className="text-dark">{`${parentNumbering}${
childIndex + 1
}.`}</span>{" "}
<a onClick={async () => router.replace(`#${child.slug}`)}>
{<abbr title={child.title}>{child.title}</abbr>}
</a>
</li>
<TocLevel
tocchildren={child.children}
parentNumbering={`${parentNumbering}${childIndex + 1}.`}
/>
</Fragment>
))}
</ol>
);
};
/*
*
* PRIVATE METHODS
*/
const HeaderToolTip = (props: { id: string }): JSX.Element => (
<ToolTip
content={"Copy anchor link"}
trigger="mouseenter"
className="text-sm"
>
<ToolTip content={"Copied! 👍"} trigger="click" className="text-sm">
<Ico
icon={Icon.Link}
className="transition-color cursor-pointer hover:text-dark"
onClick={() => {
navigator.clipboard.writeText(
`${process.env.NEXT_PUBLIC_URL_SELF + window.location.pathname}#${
props.id
}`
);
}}
/>
</ToolTip>
</ToolTip>
);
/*
*
* PRIVATE COMPONENTS
*/
const typographicRules = (text: string): string => {
let newText = text; let newText = text;
newText = newText.replace(/--/gu, ""); newText = newText.replace(/--/gu, "");
/* /*
@ -336,9 +433,20 @@ function typographicRules(text: string): string {
* newText = newText.replace(/'/gu, ""); * newText = newText.replace(/'/gu, "");
*/ */
return newText; return newText;
};
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
enum HeaderLevels {
H1 = 1,
H2 = 2,
H3 = 3,
H4 = 4,
H5 = 5,
H6 = 6,
} }
export function preprocessMarkDawn(text: string): string { const preprocessMarkDawn = (text: string): string => {
if (!text) return ""; if (!text) return "";
let preprocessed = typographicRules(text); let preprocessed = typographicRules(text);
@ -383,22 +491,15 @@ export function preprocessMarkDawn(text: string): string {
.join("\n"); .join("\n");
return preprocessed; return preprocessed;
} };
enum HeaderLevels { // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
H1 = 1,
H2 = 2,
H3 = 3,
H4 = 4,
H5 = 5,
H6 = 6,
}
function markdawnHeadersParser( const markdawnHeadersParser = (
headerLevel: HeaderLevels, headerLevel: HeaderLevels,
line: string, line: string,
visitedSlugs: string[] visitedSlugs: string[]
): string { ): string => {
const lineText = line.slice(headerLevel + 1); const lineText = line.slice(headerLevel + 1);
const slug = slugify(lineText); const slug = slugify(lineText);
let newSlug = slug; let newSlug = slug;
@ -409,4 +510,102 @@ function markdawnHeadersParser(
} }
visitedSlugs.push(newSlug); visitedSlugs.push(newSlug);
return `<h${headerLevel} id="${newSlug}">${lineText}</h${headerLevel}>`; return `<h${headerLevel} id="${newSlug}">${lineText}</h${headerLevel}>`;
} };
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
const getTocFromMarkdawn = (text: string, title?: string): TocInterface => {
const toc: TocInterface = {
title: title ?? "Return to top",
slug: slugify(title),
children: [],
};
let h2 = -1;
let h3 = -1;
let h4 = -1;
let h5 = -1;
let scenebreak = 0;
let scenebreakIndex = 0;
const getTitle = (line: string): string =>
line.slice(line.indexOf(`">`) + 2, line.indexOf("</"));
const getSlug = (line: string): string =>
line.slice(line.indexOf(`id="`) + 4, line.indexOf(`">`));
text.split("\n").map((line) => {
if (line.startsWith("<h1 id=")) {
toc.title = getTitle(line);
toc.slug = getSlug(line);
} else if (line.startsWith("<h2 id=")) {
toc.children.push({
title: getTitle(line),
slug: getSlug(line),
children: [],
});
h2 += 1;
h3 = -1;
h4 = -1;
h5 = -1;
scenebreak = 0;
} else if (h2 >= 0 && line.startsWith("<h3 id=")) {
toc.children[h2].children.push({
title: getTitle(line),
slug: getSlug(line),
children: [],
});
h3 += 1;
h4 = -1;
h5 = -1;
scenebreak = 0;
} else if (h3 >= 0 && line.startsWith("<h4 id=")) {
toc.children[h2].children[h3].children.push({
title: getTitle(line),
slug: getSlug(line),
children: [],
});
h4 += 1;
h5 = -1;
scenebreak = 0;
} else if (h4 >= 0 && line.startsWith("<h5 id=")) {
toc.children[h2].children[h3].children[h4].children.push({
title: getTitle(line),
slug: getSlug(line),
children: [],
});
h5 += 1;
scenebreak = 0;
} else if (h5 >= 0 && line.startsWith("<h6 id=")) {
toc.children[h2].children[h3].children[h4].children[h5].children.push({
title: getTitle(line),
slug: getSlug(line),
children: [],
});
} else if (line.startsWith(`<SceneBreak`)) {
scenebreak += 1;
scenebreakIndex += 1;
const child = {
title: `Scene break ${scenebreak}`,
slug: slugify(`scene-break-${scenebreakIndex}`),
children: [],
};
if (h5 >= 0) {
toc.children[h2].children[h3].children[h4].children[h5].children.push(
child
);
} else if (h4 >= 0) {
toc.children[h2].children[h3].children[h4].children.push(child);
} else if (h3 >= 0) {
toc.children[h2].children[h3].children.push(child);
} else if (h2 >= 0) {
toc.children[h2].children.push(child);
} else {
toc.children.push(child);
}
}
});
return toc;
};

View File

@ -1,168 +0,0 @@
import { slugify } from "helpers/formatters";
import { useRouter } from "next/router";
import { Fragment, useMemo } from "react";
import { preprocessMarkDawn } from "./Markdawn";
interface Props {
text: string;
title?: string;
}
export function TOC(props: Props): JSX.Element {
const { text, title } = props;
const router = useRouter();
const toc = useMemo(
() => getTocFromMarkdawn(preprocessMarkDawn(text), title),
[text, title]
);
return (
<>
{/* TODO: add to LANGUI */}
<h3 className="text-xl">Table of content</h3>
<div className="max-w-[14.5rem] text-left">
<p className="relative my-2 overflow-x-hidden text-ellipsis whitespace-nowrap text-left">
<a onClick={async () => router.replace(`#${toc.slug}`)}>
{<abbr title={toc.title}>{toc.title}</abbr>}
</a>
</p>
<TOCLevel tocchildren={toc.children} parentNumbering="" />
</div>
</>
);
}
interface LevelProps {
tocchildren: TOCInterface[];
parentNumbering: string;
}
function TOCLevel(props: LevelProps): JSX.Element {
const router = useRouter();
const { tocchildren, parentNumbering } = props;
return (
<ol className="pl-4 text-left">
{tocchildren.map((child, childIndex) => (
<Fragment key={child.slug}>
<li className="my-2 w-full overflow-x-hidden text-ellipsis whitespace-nowrap">
<span className="text-dark">{`${parentNumbering}${
childIndex + 1
}.`}</span>{" "}
<a onClick={async () => router.replace(`#${child.slug}`)}>
{<abbr title={child.title}>{child.title}</abbr>}
</a>
</li>
<TOCLevel
tocchildren={child.children}
parentNumbering={`${parentNumbering}${childIndex + 1}.`}
/>
</Fragment>
))}
</ol>
);
}
interface TOCInterface {
title: string;
slug: string;
children: TOCInterface[];
}
function getTocFromMarkdawn(text: string, title?: string): TOCInterface {
const toc: TOCInterface = {
title: title ?? "Return to top",
slug: slugify(title),
children: [],
};
let h2 = -1;
let h3 = -1;
let h4 = -1;
let h5 = -1;
let scenebreak = 0;
let scenebreakIndex = 0;
function getTitle(line: string): string {
return line.slice(line.indexOf(`">`) + 2, line.indexOf("</"));
}
function getSlug(line: string): string {
return line.slice(line.indexOf(`id="`) + 4, line.indexOf(`">`));
}
text.split("\n").map((line) => {
if (line.startsWith("<h1 id=")) {
toc.title = getTitle(line);
toc.slug = getSlug(line);
} else if (line.startsWith("<h2 id=")) {
toc.children.push({
title: getTitle(line),
slug: getSlug(line),
children: [],
});
h2 += 1;
h3 = -1;
h4 = -1;
h5 = -1;
scenebreak = 0;
} else if (h2 >= 0 && line.startsWith("<h3 id=")) {
toc.children[h2].children.push({
title: getTitle(line),
slug: getSlug(line),
children: [],
});
h3 += 1;
h4 = -1;
h5 = -1;
scenebreak = 0;
} else if (h3 >= 0 && line.startsWith("<h4 id=")) {
toc.children[h2].children[h3].children.push({
title: getTitle(line),
slug: getSlug(line),
children: [],
});
h4 += 1;
h5 = -1;
scenebreak = 0;
} else if (h4 >= 0 && line.startsWith("<h5 id=")) {
toc.children[h2].children[h3].children[h4].children.push({
title: getTitle(line),
slug: getSlug(line),
children: [],
});
h5 += 1;
scenebreak = 0;
} else if (h5 >= 0 && line.startsWith("<h6 id=")) {
toc.children[h2].children[h3].children[h4].children[h5].children.push({
title: getTitle(line),
slug: getSlug(line),
children: [],
});
} else if (line.startsWith(`<SceneBreak`)) {
scenebreak += 1;
scenebreakIndex += 1;
const child = {
title: `Scene break ${scenebreak}`,
slug: slugify(`scene-break-${scenebreakIndex}`),
children: [],
};
if (h5 >= 0) {
toc.children[h2].children[h3].children[h4].children[h5].children.push(
child
);
} else if (h4 >= 0) {
toc.children[h2].children[h3].children[h4].children.push(child);
} else if (h3 >= 0) {
toc.children[h2].children[h3].children.push(child);
} else if (h2 >= 0) {
toc.children[h2].children.push(child);
} else {
toc.children.push(child);
}
}
});
return toc;
}

View File

@ -2,29 +2,30 @@ import { Ico, Icon } from "components/Ico";
import { cIf, cJoin } from "helpers/className"; import { cIf, cJoin } from "helpers/className";
import { isDefined } from "helpers/others"; import { isDefined } from "helpers/others";
/*
*
* COMPONENT
*/
interface Props { interface Props {
message: string; message: string;
icon?: Icon; icon?: Icon;
} }
export function ContentPlaceholder(props: Props): JSX.Element { // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
const { message, icon } = props;
return ( export const ContentPlaceholder = ({ message, icon }: Props): JSX.Element => (
<div className="grid h-full place-content-center"> <div className="grid h-full place-content-center">
<div <div
className="grid grid-flow-col place-items-center gap-9 rounded-2xl border-2 border-dotted className="grid grid-flow-col place-items-center gap-9 rounded-2xl border-2 border-dotted
border-dark p-8 text-dark opacity-40" border-dark p-8 text-dark opacity-40"
>
{isDefined(icon) && <Ico icon={icon} className="!text-[300%]" />}
<p
className={cJoin("w-64 text-2xl", cIf(!isDefined(icon), "text-center"))}
> >
{isDefined(icon) && <Ico icon={icon} className="!text-[300%]" />} {message}
<p </p>
className={cJoin(
"w-64 text-2xl",
cIf(!isDefined(icon), "text-center")
)}
>
{message}
</p>
</div>
</div> </div>
); </div>
} );

View File

@ -2,10 +2,14 @@ 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 { isDefinedAndNotEmpty } from "helpers/others";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { MouseEventHandler, useMemo } from "react"; import { MouseEventHandler, useMemo } from "react";
/*
*
* COMPONENT
*/
interface Props { interface Props {
url: string; url: string;
icon?: Icon; icon?: Icon;
@ -16,16 +20,17 @@ interface Props {
onClick?: MouseEventHandler<HTMLDivElement>; onClick?: MouseEventHandler<HTMLDivElement>;
} }
export function NavOption(props: Props): JSX.Element { // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
const {
url, export const NavOption = ({
icon, url,
title, icon,
subtitle, title,
border = false, subtitle,
reduced = false, border = false,
onClick, reduced = false,
} = props; onClick,
}: Props): JSX.Element => {
const router = useRouter(); const router = useRouter();
const isActive = useMemo( const isActive = useMemo(
() => router.asPath.startsWith(url), () => router.asPath.startsWith(url),
@ -82,4 +87,4 @@ export function NavOption(props: Props): JSX.Element {
</div> </div>
</ToolTip> </ToolTip>
); );
} };

View File

@ -2,22 +2,30 @@ import { HorizontalLine } from "components/HorizontalLine";
import { Ico, Icon } from "components/Ico"; import { Ico, Icon } from "components/Ico";
import { isDefinedAndNotEmpty } from "helpers/others"; import { isDefinedAndNotEmpty } from "helpers/others";
/*
*
* COMPONENT
*/
interface Props { interface Props {
icon?: Icon; icon?: Icon;
title: string | null | undefined; title: string | null | undefined;
description?: string | null | undefined; description?: string | null | undefined;
} }
export function PanelHeader(props: Props): JSX.Element { // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
const { icon, description, title } = props;
return ( export const PanelHeader = ({
<> icon,
<div className="grid w-full place-items-center"> description,
{icon && <Ico icon={icon} className="mb-3 !text-4xl" />} title,
<h2 className="text-2xl">{title}</h2> }: Props): JSX.Element => (
{isDefinedAndNotEmpty(description) && <p>{description}</p>} <>
</div> <div className="grid w-full place-items-center">
<HorizontalLine /> {icon && <Ico icon={icon} className="mb-3 !text-4xl" />}
</> <h2 className="text-2xl">{title}</h2>
); {isDefinedAndNotEmpty(description) && <p>{description}</p>}
} </div>
<HorizontalLine />
</>
);

View File

@ -5,6 +5,11 @@ import { useAppLayout } from "contexts/AppLayoutContext";
import { AppStaticProps } from "graphql/getAppStaticProps"; import { AppStaticProps } from "graphql/getAppStaticProps";
import { cJoin } from "helpers/className"; import { cJoin } from "helpers/className";
/*
*
* COMPONENT
*/
interface Props { interface Props {
href: string; href: string;
title: string | null | undefined; title: string | null | undefined;
@ -20,8 +25,16 @@ export enum ReturnButtonType {
Both = "both", Both = "both",
} }
export function ReturnButton(props: Props): JSX.Element { // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
const { href, title, langui, displayOn, horizontalLine, className } = props;
export const ReturnButton = ({
href,
title,
langui,
displayOn,
horizontalLine,
className,
}: Props): JSX.Element => {
const appLayout = useAppLayout(); const appLayout = useAppLayout();
return ( return (
@ -44,4 +57,4 @@ export function ReturnButton(props: Props): JSX.Element {
{horizontalLine === true && <HorizontalLine />} {horizontalLine === true && <HorizontalLine />}
</div> </div>
); );
} };

View File

@ -1,5 +1,10 @@
import { cJoin } from "helpers/className"; import { cJoin } from "helpers/className";
/*
*
* COMPONENT
*/
interface Props { interface Props {
children: React.ReactNode; children: React.ReactNode;
width?: ContentPanelWidthSizes; width?: ContentPanelWidthSizes;
@ -12,24 +17,26 @@ export enum ContentPanelWidthSizes {
Full = "full", Full = "full",
} }
export function ContentPanel(props: Props): JSX.Element { // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
const { width = ContentPanelWidthSizes.Default, children, className } = props;
return ( export const ContentPanel = ({
<div className="grid h-full"> width = ContentPanelWidthSizes.Default,
<main children,
className={cJoin( className,
"justify-self-center px-4 pt-10 pb-20 desktop:px-10 desktop:pt-20 desktop:pb-32", }: Props): JSX.Element => (
width === ContentPanelWidthSizes.Default <div className="grid h-full">
? "max-w-2xl" <main
: width === ContentPanelWidthSizes.Large className={cJoin(
? "max-w-4xl" "justify-self-center px-4 pt-10 pb-20 desktop:px-10 desktop:pt-20 desktop:pb-32",
: "w-full", width === ContentPanelWidthSizes.Default
className ? "max-w-2xl"
)} : width === ContentPanelWidthSizes.Large
> ? "max-w-4xl"
{children} : "w-full",
</main> className
</div> )}
); >
} {children}
</main>
</div>
);

View File

@ -12,12 +12,18 @@ import { Icon } from "components/Ico";
import { cIf, cJoin } from "helpers/className"; import { cIf, cJoin } from "helpers/className";
import { isDefinedAndNotEmpty } from "helpers/others"; import { isDefinedAndNotEmpty } from "helpers/others";
/*
*
* COMPONENT
*/
interface Props { interface Props {
langui: AppStaticProps["langui"]; langui: AppStaticProps["langui"];
} }
export function MainPanel(props: Props): JSX.Element { // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
const { langui } = props;
export const MainPanel = ({ langui }: Props): JSX.Element => {
const isDesktop = useMediaDesktop(); const isDesktop = useMediaDesktop();
const { const {
mainPanelReduced = false, mainPanelReduced = false,
@ -239,4 +245,4 @@ export function MainPanel(props: Props): JSX.Element {
</div> </div>
</div> </div>
); );
} };

View File

@ -1,11 +1,16 @@
/*
*
* COMPONENT
*/
interface Props { interface Props {
children: React.ReactNode; children: React.ReactNode;
} }
export function SubPanel(props: Props): JSX.Element { // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
return (
<div className="grid gap-y-2 px-6 pt-10 pb-20 text-center desktop:py-8 desktop:px-10"> export const SubPanel = ({ children }: Props): JSX.Element => (
{props.children} <div className="grid gap-y-2 px-6 pt-10 pb-20 text-center desktop:py-8 desktop:px-10">
</div> {children}
); </div>
} );

View File

@ -1,9 +1,13 @@
import { useAppLayout } from "contexts/AppLayoutContext"; import { useAppLayout } from "contexts/AppLayoutContext";
import { cIf, cJoin } from "helpers/className"; import { cIf, cJoin } from "helpers/className";
import { Dispatch, SetStateAction, useEffect } from "react"; import { Dispatch, SetStateAction, useEffect } from "react";
import Hotkeys from "react-hot-keys"; import Hotkeys from "react-hot-keys";
/*
*
* COMPONENT
*/
interface Props { interface Props {
setState: setState:
| Dispatch<SetStateAction<boolean | undefined>> | Dispatch<SetStateAction<boolean | undefined>>
@ -15,16 +19,16 @@ interface Props {
padding?: boolean; padding?: boolean;
} }
export function Popup(props: Props): JSX.Element { // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
const {
setState,
state,
children,
fillViewport,
hideBackground = false,
padding = true,
} = props;
export const Popup = ({
setState,
state,
children,
fillViewport,
hideBackground = false,
padding = true,
}: Props): JSX.Element => {
const { setMenuGestures } = useAppLayout(); const { setMenuGestures } = useAppLayout();
useEffect(() => { useEffect(() => {
@ -77,4 +81,4 @@ export function Popup(props: Props): JSX.Element {
</div> </div>
</Hotkeys> </Hotkeys>
); );
} };

View File

@ -8,8 +8,7 @@ import { Fragment, useCallback, 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";
import { Markdawn } from "./Markdown/Markdawn"; import { Markdawn, TableOfContents } from "./Markdown/Markdawn";
import { TOC } from "./Markdown/TOC";
import { ReturnButton, ReturnButtonType } from "./PanelComponents/ReturnButton"; import { ReturnButton, ReturnButtonType } from "./PanelComponents/ReturnButton";
import { ContentPanel } from "./Panels/ContentPanel"; import { ContentPanel } from "./Panels/ContentPanel";
import { SubPanel } from "./Panels/SubPanel"; import { SubPanel } from "./Panels/SubPanel";
@ -17,6 +16,11 @@ import { RecorderChip } from "./RecorderChip";
import { ThumbnailHeader } from "./ThumbnailHeader"; import { ThumbnailHeader } from "./ThumbnailHeader";
import { ToolTip } from "./ToolTip"; import { ToolTip } from "./ToolTip";
/*
*
* COMPONENT
*/
interface Props { interface Props {
post: PostWithTranslations; post: PostWithTranslations;
langui: AppStaticProps["langui"]; langui: AppStaticProps["langui"];
@ -33,22 +37,23 @@ interface Props {
appendBody?: JSX.Element; appendBody?: JSX.Element;
} }
export function PostPage(props: Props): JSX.Element { // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
const {
post,
langui,
languages,
returnHref,
returnTitle,
displayCredits,
displayToc,
displayThumbnailHeader,
displayLanguageSwitcher,
appendBody,
prependBody,
} = props;
const displayTitle = props.displayTitle ?? true;
export const PostPage = ({
post,
langui,
languages,
returnHref,
returnTitle,
displayCredits,
displayToc,
displayThumbnailHeader,
displayLanguageSwitcher,
appendBody,
prependBody,
displayTitle = true,
currencies,
}: Props): JSX.Element => {
const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] = const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] =
useSmartLanguage({ useSmartLanguage({
items: post.translations, items: post.translations,
@ -124,7 +129,9 @@ export function PostPage(props: Props): JSX.Element {
</> </>
)} )}
{displayToc && <TOC text={body} title={title} />} {displayToc && (
<TableOfContents text={body} title={title} langui={langui} />
)}
</SubPanel> </SubPanel>
) : undefined, ) : undefined,
[ [
@ -216,7 +223,9 @@ export function PostPage(props: Props): JSX.Element {
contentPanel={contentPanel} contentPanel={contentPanel}
subPanel={subPanel} subPanel={subPanel}
thumbnail={thumbnail ?? undefined} thumbnail={thumbnail ?? undefined}
{...props} currencies={currencies}
languages={languages}
langui={langui}
/> />
); );
} };

View File

@ -14,14 +14,18 @@ import {
prettySlug, prettySlug,
} from "helpers/formatters"; } from "helpers/formatters";
import { ImageQuality } from "helpers/img"; import { ImageQuality } from "helpers/img";
import { useSmartLanguage } from "hooks/useSmartLanguage"; import { useSmartLanguage } from "hooks/useSmartLanguage";
import Link from "next/link"; import Link from "next/link";
import { useCallback } from "react"; import { useCallback, useMemo } from "react";
import { Chip } from "./Chip"; import { Chip } from "./Chip";
import { Ico, Icon } from "./Ico"; import { Ico, Icon } from "./Ico";
import { Img } from "./Img"; import { Img } from "./Img";
/*
*
* COMPONENT
*/
interface Props { interface Props {
thumbnail?: UploadImageFragment | string | null | undefined; thumbnail?: UploadImageFragment | string | null | undefined;
thumbnailAspectRatio?: string; thumbnailAspectRatio?: string;
@ -53,75 +57,78 @@ interface Props {
| { __typename: "anotherHoverlayName" }; | { __typename: "anotherHoverlayName" };
} }
export function PreviewCard(props: Props): JSX.Element { // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
const {
href,
thumbnail,
thumbnailAspectRatio = "4/3",
thumbnailForceAspectRatio = false,
thumbnailRounded = true,
pre_title,
title,
subtitle,
description,
stackNumber = 0,
topChips,
bottomChips,
keepInfoVisible,
metadata,
hoverlay,
infoAppend,
} = props;
export const PreviewCard = ({
href,
thumbnail,
thumbnailAspectRatio = "4/3",
thumbnailForceAspectRatio = false,
thumbnailRounded = true,
pre_title,
title,
subtitle,
description,
stackNumber = 0,
topChips,
bottomChips,
keepInfoVisible,
metadata,
hoverlay,
infoAppend,
}: Props): JSX.Element => {
const appLayout = useAppLayout(); const appLayout = useAppLayout();
const metadataJSX = const metadataJSX = useMemo(
metadata && (metadata.release_date || metadata.price) ? ( () =>
<div className="flex w-full flex-row flex-wrap gap-x-3"> metadata && (metadata.release_date || metadata.price) ? (
{metadata.release_date && ( <div className="flex w-full flex-row flex-wrap gap-x-3">
<p className="text-sm mobile:text-xs"> {metadata.release_date && (
<Ico <p className="text-sm mobile:text-xs">
icon={Icon.Event} <Ico
className="mr-1 translate-y-[.15em] !text-base" icon={Icon.Event}
/> className="mr-1 translate-y-[.15em] !text-base"
{prettyDate(metadata.release_date)} />
</p> {prettyDate(metadata.release_date)}
)} </p>
{metadata.price && metadata.currencies && ( )}
<p className="justify-self-end text-sm mobile:text-xs"> {metadata.price && metadata.currencies && (
<Ico <p className="justify-self-end text-sm mobile:text-xs">
icon={Icon.ShoppingCart} <Ico
className="mr-1 translate-y-[.15em] !text-base" icon={Icon.ShoppingCart}
/> className="mr-1 translate-y-[.15em] !text-base"
{prettyPrice( />
metadata.price, {prettyPrice(
metadata.currencies, metadata.price,
appLayout.currency metadata.currencies,
)} appLayout.currency
</p> )}
)} </p>
{metadata.views && ( )}
<p className="text-sm mobile:text-xs"> {metadata.views && (
<Ico <p className="text-sm mobile:text-xs">
icon={Icon.Visibility} <Ico
className="mr-1 translate-y-[.15em] !text-base" icon={Icon.Visibility}
/> className="mr-1 translate-y-[.15em] !text-base"
{prettyShortenNumber(metadata.views)} />
</p> {prettyShortenNumber(metadata.views)}
)} </p>
{metadata.author && ( )}
<p className="text-sm mobile:text-xs"> {metadata.author && (
<Ico <p className="text-sm mobile:text-xs">
icon={Icon.Person} <Ico
className="mr-1 translate-y-[.15em] !text-base" icon={Icon.Person}
/> className="mr-1 translate-y-[.15em] !text-base"
{metadata.author} />
</p> {metadata.author}
)} </p>
</div> )}
) : ( </div>
<></> ) : (
); <></>
),
[appLayout.currency, metadata]
);
return ( return (
<Link href={href} passHref> <Link href={href} passHref>
@ -241,12 +248,13 @@ export function PreviewCard(props: Props): JSX.Element {
)} )}
<div <div
className={cJoin( className={cJoin(
"z-20 grid gap-2 p-4 transition-opacity linearbg-obi", "grid gap-2 p-4 transition-opacity linearbg-obi",
cIf( cIf(
!keepInfoVisible, !keepInfoVisible,
`-inset-x-0.5 bottom-2 opacity-0 group-hover:opacity-100 hoverable:absolute `-inset-x-0.5 bottom-2 opacity-0 [border-radius:10%_10%_10%_10%_/_1%_1%_3%_3%]
hoverable:drop-shadow-shade-lg notHoverable:rounded-b-md group-hover:opacity-100 hoverable:absolute hoverable:drop-shadow-shade-lg
notHoverable:opacity-100` notHoverable:rounded-b-md notHoverable:opacity-100`,
"[border-radius:0%_0%_10%_10%_/_0%_0%_3%_3%]"
) )
)} )}
> >
@ -287,7 +295,9 @@ export function PreviewCard(props: Props): JSX.Element {
</div> </div>
</Link> </Link>
); );
} };
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
interface TranslatedProps interface TranslatedProps
extends Omit<Props, "description" | "pre_title" | "subtitle" | "title"> { extends Omit<Props, "description" | "pre_title" | "subtitle" | "title"> {
@ -302,13 +312,14 @@ interface TranslatedProps
languages: AppStaticProps["languages"]; languages: AppStaticProps["languages"];
} }
export function TranslatedPreviewCard(props: TranslatedProps): JSX.Element { // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
const {
translations = [{ title: props.slug, language: "default" }],
slug,
languages,
} = props;
export const TranslatedPreviewCard = ({
slug,
translations = [{ title: slug, language: "default" }],
languages,
...otherProps
}: TranslatedProps): JSX.Element => {
const [selectedTranslation] = useSmartLanguage({ const [selectedTranslation] = useSmartLanguage({
items: translations, items: translations,
languages: languages, languages: languages,
@ -324,7 +335,7 @@ export function TranslatedPreviewCard(props: TranslatedProps): JSX.Element {
title={selectedTranslation?.title ?? prettySlug(slug)} title={selectedTranslation?.title ?? prettySlug(slug)}
subtitle={selectedTranslation?.subtitle} subtitle={selectedTranslation?.subtitle}
description={selectedTranslation?.description} description={selectedTranslation?.description}
{...props} {...otherProps}
/> />
); );
} };

View File

@ -2,13 +2,17 @@ import { UploadImageFragment } from "graphql/generated";
import { AppStaticProps } from "graphql/getAppStaticProps"; import { AppStaticProps } from "graphql/getAppStaticProps";
import { prettySlug } from "helpers/formatters"; import { prettySlug } from "helpers/formatters";
import { ImageQuality } from "helpers/img"; import { ImageQuality } from "helpers/img";
import { useSmartLanguage } from "hooks/useSmartLanguage"; import { useSmartLanguage } from "hooks/useSmartLanguage";
import Link from "next/link"; import Link from "next/link";
import { useCallback } from "react"; import { useCallback } from "react";
import { Chip } from "./Chip"; import { Chip } from "./Chip";
import { Img } from "./Img"; import { Img } from "./Img";
/*
*
* COMPONENT
*/
interface Props { interface Props {
thumbnail?: UploadImageFragment | string | null | undefined; thumbnail?: UploadImageFragment | string | null | undefined;
thumbnailAspectRatio?: string; thumbnailAspectRatio?: string;
@ -20,60 +24,60 @@ interface Props {
bottomChips?: string[]; bottomChips?: string[];
} }
function PreviewLine(props: Props): JSX.Element { // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
const {
href,
thumbnail,
pre_title,
title,
subtitle,
topChips,
bottomChips,
thumbnailAspectRatio,
} = props;
return ( const PreviewLine = ({
<Link href={href} passHref> href,
<div thumbnail,
className="flex h-36 w-full cursor-pointer flex-row place-items-center gap-4 overflow-hidden pre_title,
title,
subtitle,
topChips,
bottomChips,
thumbnailAspectRatio,
}: Props): JSX.Element => (
<Link href={href} passHref>
<div
className="flex h-36 w-full cursor-pointer flex-row place-items-center gap-4 overflow-hidden
rounded-md bg-light pr-4 transition-transform drop-shadow-shade-xl hover:scale-[1.02]" rounded-md bg-light pr-4 transition-transform drop-shadow-shade-xl hover:scale-[1.02]"
> >
{thumbnail ? ( {thumbnail ? (
<div className="aspect-[3/2] h-full"> <div className="aspect-[3/2] h-full">
<Img image={thumbnail} quality={ImageQuality.Medium} /> <Img image={thumbnail} quality={ImageQuality.Medium} />
</div>
) : (
<div style={{ aspectRatio: thumbnailAspectRatio }}></div>
)}
<div className="grid gap-2">
{topChips && topChips.length > 0 && (
<div className="grid grid-flow-col place-content-start gap-1 overflow-hidden">
{topChips.map((text, index) => (
<Chip key={index}>{text}</Chip>
))}
</div>
)}
<div className="my-1 flex flex-col">
{pre_title && <p className="mb-1 leading-none">{pre_title}</p>}
{title && (
<p className="font-headers text-lg leading-none">{title}</p>
)}
{subtitle && <p className="leading-none">{subtitle}</p>}
</div>
{bottomChips && bottomChips.length > 0 && (
<div className="grid grid-flow-col place-content-start gap-1 overflow-hidden">
{bottomChips.map((text, index) => (
<Chip key={index} className="text-sm">
{text}
</Chip>
))}
</div>
)}
</div> </div>
) : (
<div style={{ aspectRatio: thumbnailAspectRatio }}></div>
)}
<div className="grid gap-2">
{topChips && topChips.length > 0 && (
<div className="grid grid-flow-col place-content-start gap-1 overflow-hidden">
{topChips.map((text, index) => (
<Chip key={index}>{text}</Chip>
))}
</div>
)}
<div className="my-1 flex flex-col">
{pre_title && <p className="mb-1 leading-none">{pre_title}</p>}
{title && (
<p className="font-headers text-lg leading-none">{title}</p>
)}
{subtitle && <p className="leading-none">{subtitle}</p>}
</div>
{bottomChips && bottomChips.length > 0 && (
<div className="grid grid-flow-col place-content-start gap-1 overflow-hidden">
{bottomChips.map((text, index) => (
<Chip key={index} className="text-sm">
{text}
</Chip>
))}
</div>
)}
</div> </div>
</Link> </div>
); </Link>
} );
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
interface TranslatedProps interface TranslatedProps
extends Omit<Props, "pre_title" | "subtitle" | "title"> { extends Omit<Props, "pre_title" | "subtitle" | "title"> {
@ -88,13 +92,14 @@ interface TranslatedProps
languages: AppStaticProps["languages"]; languages: AppStaticProps["languages"];
} }
export function TranslatedPreviewLine(props: TranslatedProps): JSX.Element { // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
const {
translations = [{ title: props.slug, language: "default" }],
slug,
languages,
} = props;
export const TranslatedPreviewLine = ({
slug,
translations = [{ title: slug, language: "default" }],
languages,
...otherProps
}: TranslatedProps): JSX.Element => {
const [selectedTranslation] = useSmartLanguage({ const [selectedTranslation] = useSmartLanguage({
items: translations, items: translations,
languages: languages, languages: languages,
@ -109,7 +114,7 @@ export function TranslatedPreviewLine(props: TranslatedProps): JSX.Element {
pre_title={selectedTranslation?.pre_title} pre_title={selectedTranslation?.pre_title}
title={selectedTranslation?.title ?? prettySlug(slug)} title={selectedTranslation?.title ?? prettySlug(slug)}
subtitle={selectedTranslation?.subtitle} subtitle={selectedTranslation?.subtitle}
{...props} {...otherProps}
/> />
); );
} };

View File

@ -3,64 +3,67 @@ import { RecorderChipFragment } from "graphql/generated";
import { AppStaticProps } from "graphql/getAppStaticProps"; import { AppStaticProps } from "graphql/getAppStaticProps";
import { ImageQuality } from "helpers/img"; import { ImageQuality } from "helpers/img";
import { filterHasAttributes } from "helpers/others"; import { filterHasAttributes } from "helpers/others";
import { Fragment } from "react"; import { Fragment } from "react";
import { Img } from "./Img"; import { Img } from "./Img";
import { Markdawn } from "./Markdown/Markdawn"; import { Markdawn } from "./Markdown/Markdawn";
import { ToolTip } from "./ToolTip"; import { ToolTip } from "./ToolTip";
/*
*
* COMPONENT
*/
interface Props { interface Props {
className?: string; className?: string;
recorder: RecorderChipFragment; recorder: RecorderChipFragment;
langui: AppStaticProps["langui"]; langui: AppStaticProps["langui"];
} }
export function RecorderChip(props: Props): JSX.Element { // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
const { recorder, langui } = props;
return ( export const RecorderChip = ({ recorder, langui }: Props): JSX.Element => (
<ToolTip <ToolTip
content={ content={
<div className="grid gap-8 p-2 py-5 text-left"> <div className="grid gap-8 p-2 py-5 text-left">
<div className="grid grid-flow-col place-content-start place-items-center gap-6"> <div className="grid grid-flow-col place-content-start place-items-center gap-6">
{recorder.avatar?.data?.attributes && ( {recorder.avatar?.data?.attributes && (
<Img <Img
className="w-20 rounded-full border-4 border-mid" className="w-20 rounded-full border-4 border-mid"
image={recorder.avatar.data.attributes} image={recorder.avatar.data.attributes}
quality={ImageQuality.Small} quality={ImageQuality.Small}
/> />
)}
<div className="grid gap-2">
<h3 className=" text-2xl">{recorder.username}</h3>
{recorder.languages?.data && recorder.languages.data.length > 0 && (
<div className="flex flex-row flex-wrap gap-1">
<p>{langui.languages}:</p>
{filterHasAttributes(recorder.languages.data).map(
(language) => (
<Fragment key={language.attributes.code}>
<Chip>{language.attributes.code.toUpperCase()}</Chip>
</Fragment>
)
)}
</div>
)}
{recorder.pronouns && (
<div className="flex flex-row flex-wrap gap-1">
<p>{langui.pronouns}:</p>
<Chip>{recorder.pronouns}</Chip>
</div>
)} )}
<div className="grid gap-2">
<h3 className=" text-2xl">{recorder.username}</h3>
{recorder.languages?.data && recorder.languages.data.length > 0 && (
<div className="flex flex-row flex-wrap gap-1">
<p>{langui.languages}:</p>
{filterHasAttributes(recorder.languages.data).map(
(language) => (
<Fragment key={language.attributes.code}>
<Chip>{language.attributes.code.toUpperCase()}</Chip>
</Fragment>
)
)}
</div>
)}
{recorder.pronouns && (
<div className="flex flex-row flex-wrap gap-1">
<p>{langui.pronouns}:</p>
<Chip>{recorder.pronouns}</Chip>
</div>
)}
</div>
</div> </div>
{recorder.bio?.[0] && <Markdawn text={recorder.bio[0].bio ?? ""} />}
</div> </div>
} {recorder.bio?.[0] && <Markdawn text={recorder.bio[0].bio ?? ""} />}
placement="top" </div>
> }
<Chip key={recorder.anonymous_code}> placement="top"
{recorder.anonymize >
? `Recorder#${recorder.anonymous_code}` <Chip key={recorder.anonymous_code}>
: recorder.username} {recorder.anonymize
</Chip> ? `Recorder#${recorder.anonymous_code}`
</ToolTip> : recorder.username}
); </Chip>
} </ToolTip>
);

View File

@ -7,9 +7,13 @@ import { AppStaticProps } from "graphql/getAppStaticProps";
import { prettyinlineTitle, prettySlug, slugify } from "helpers/formatters"; import { prettyinlineTitle, prettySlug, slugify } from "helpers/formatters";
import { getAssetURL, ImageQuality } from "helpers/img"; import { getAssetURL, ImageQuality } from "helpers/img";
import { filterHasAttributes } from "helpers/others"; import { filterHasAttributes } from "helpers/others";
import { useLightBox } from "hooks/useLightBox"; import { useLightBox } from "hooks/useLightBox";
/*
*
* COMPONENT
*/
interface Props { interface Props {
pre_title?: string | null | undefined; pre_title?: string | null | undefined;
title: string | null | undefined; title: string | null | undefined;
@ -26,19 +30,19 @@ interface Props {
languageSwitcher?: JSX.Element; languageSwitcher?: JSX.Element;
} }
export function ThumbnailHeader(props: Props): JSX.Element { // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
const {
langui,
pre_title,
title,
subtitle,
thumbnail,
type,
categories,
description,
languageSwitcher,
} = props;
export const ThumbnailHeader = ({
langui,
pre_title,
title,
subtitle,
thumbnail,
type,
categories,
description,
languageSwitcher,
}: Props): JSX.Element => {
const [openLightBox, LightBox] = useLightBox(); const [openLightBox, LightBox] = useLightBox();
return ( return (
@ -105,4 +109,4 @@ export function ThumbnailHeader(props: Props): JSX.Element {
)} )}
</> </>
); );
} };

View File

@ -2,20 +2,30 @@ import Tippy, { TippyProps } from "@tippyjs/react";
import { cJoin } from "helpers/className"; import { cJoin } from "helpers/className";
import "tippy.js/animations/scale-subtle.css"; import "tippy.js/animations/scale-subtle.css";
/*
*
* COMPONENT
*/
interface Props extends TippyProps {} interface Props extends TippyProps {}
export function ToolTip(props: Props): JSX.Element { // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
const newProps: Props = {
className: cJoin("text-sm", props.className),
delay: [150, 0],
interactive: true,
animation: "scale-subtle",
...props,
};
return ( export const ToolTip = ({
<Tippy {...newProps}> className,
<div>{props.children}</div> delay = [150, 0],
</Tippy> interactive = true,
); animation = "scale-subtle",
} children,
...otherProps
}: Props): JSX.Element => (
<Tippy
className={cJoin("text-sm", className)}
delay={delay}
interactive={interactive}
animation={animation}
{...otherProps}
>
<div>{children}</div>
</Tippy>
);

View File

@ -20,36 +20,35 @@ interface Props {
langui: AppStaticProps["langui"]; langui: AppStaticProps["langui"];
} }
export function ChronologyItemComponent(props: Props): JSX.Element { export const ChronologyItemComponent = ({
const { langui } = props; langui,
item,
if (props.item.attributes) { displayYear,
}: Props): JSX.Element => {
if (item.attributes) {
return ( return (
<div <div
className="grid grid-cols-[4em] grid-rows-[auto_1fr] place-content-start className="grid grid-cols-[4em] grid-rows-[auto_1fr] place-content-start
rounded-2xl py-4 px-8 target:my-4 target:bg-mid target:py-8" rounded-2xl py-4 px-8 target:my-4 target:bg-mid target:py-8"
id={generateAnchor( id={generateAnchor(
props.item.attributes.year, item.attributes.year,
props.item.attributes.month, item.attributes.month,
props.item.attributes.day item.attributes.day
)} )}
> >
{props.displayYear && ( {displayYear && (
<p className="mt-[-.2em] text-lg font-bold"> <p className="mt-[-.2em] text-lg font-bold">
{generateYear( {generateYear(item.attributes.displayed_date, item.attributes.year)}
props.item.attributes.displayed_date,
props.item.attributes.year
)}
</p> </p>
)} )}
<p className="col-start-1 text-sm text-dark"> <p className="col-start-1 text-sm text-dark">
{generateDate(props.item.attributes.month, props.item.attributes.day)} {generateDate(item.attributes.month, item.attributes.day)}
</p> </p>
<div className="col-start-2 row-span-2 row-start-1 grid gap-4"> <div className="col-start-2 row-span-2 row-start-1 grid gap-4">
{props.item.attributes.events && {item.attributes.events &&
filterHasAttributes(props.item.attributes.events, [ filterHasAttributes(item.attributes.events, [
"id", "id",
"translations", "translations",
]).map((event) => ( ]).map((event) => (
@ -123,31 +122,29 @@ export function ChronologyItemComponent(props: Props): JSX.Element {
} }
return <></>; return <></>;
} };
function generateAnchor( const generateAnchor = (
year: number | undefined, year: number | undefined,
month: number | null | undefined, month: number | null | undefined,
day: number | null | undefined day: number | null | undefined
): string { ): string => {
let result = ""; let result = "";
if (year) result += year; if (year) result += year;
if (month) result += `- ${month.toString().padStart(2, "0")}`; if (month) result += `- ${month.toString().padStart(2, "0")}`;
if (day) result += `- ${day.toString().padStart(2, "0")}`; if (day) result += `- ${day.toString().padStart(2, "0")}`;
return result; return result;
} };
function generateYear( const generateYear = (
displayed_date: string | null | undefined, displayed_date: string | null | undefined,
year: number | undefined year: number | undefined
): string { ): string => displayed_date ?? year?.toString() ?? "";
return displayed_date ?? year?.toString() ?? "";
}
function generateDate( const generateDate = (
month: number | null | undefined, month: number | null | undefined,
day: number | null | undefined day: number | null | undefined
): string { ): string => {
const lut = [ const lut = [
"Jan", "Jan",
"Feb", "Feb",
@ -172,4 +169,4 @@ function generateDate(
} }
return result; return result;
} };

View File

@ -10,22 +10,22 @@ interface Props {
langui: AppStaticProps["langui"]; langui: AppStaticProps["langui"];
} }
export function ChronologyYearComponent(props: Props): JSX.Element { export const ChronologyYearComponent = ({
const { langui } = props; langui,
year,
return ( items,
<div }: Props): JSX.Element => (
className="rounded-2xl target:my-4 target:bg-mid target:py-4" <div
id={props.items.length > 1 ? props.year.toString() : undefined} className="rounded-2xl target:my-4 target:bg-mid target:py-4"
> id={items.length > 1 ? year.toString() : undefined}
{props.items.map((item, index) => ( >
<ChronologyItemComponent {items.map((item, index) => (
key={index} <ChronologyItemComponent
item={item} key={index}
displayYear={index === 0} item={item}
langui={langui} displayYear={index === 0}
/> langui={langui}
))} />
</div> ))}
); </div>
} );

View File

@ -17,9 +17,13 @@ interface Props {
index: number; index: number;
} }
export default function DefinitionCard(props: Props): JSX.Element { const DefinitionCard = ({
const { source, translations = [], languages, langui, index } = props; source,
translations = [],
languages,
langui,
index,
}: Props): JSX.Element => {
const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] = const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] =
useSmartLanguage({ useSmartLanguage({
items: translations, items: translations,
@ -33,8 +37,7 @@ export default function DefinitionCard(props: Props): JSX.Element {
return ( return (
<> <>
<div className="flex place-items-center gap-2"> <div className="flex place-items-center gap-2">
{/* TODO: Langui */} <p className="font-headers text-lg">{`${langui.definition} ${index}`}</p>
<p className="font-headers text-lg">{`Definition ${index}`}</p>
{selectedTranslation?.status && ( {selectedTranslation?.status && (
<ToolTip <ToolTip
content={getStatusDescription(selectedTranslation.status, langui)} content={getStatusDescription(selectedTranslation.status, langui)}
@ -52,4 +55,5 @@ export default function DefinitionCard(props: Props): JSX.Element {
<p>{selectedTranslation?.definition}</p> <p>{selectedTranslation?.definition}</p>
</> </>
); );
} };
export default DefinitionCard;

View File

@ -106,15 +106,13 @@ const initialState: RequiredNonNullable<AppLayoutState> = {
const AppContext = React.createContext<AppLayoutState>(initialState); const AppContext = React.createContext<AppLayoutState>(initialState);
export function useAppLayout(): AppLayoutState { export const useAppLayout = (): AppLayoutState => useContext(AppContext);
return useContext(AppContext);
}
interface Props { interface Props {
children: ReactNode; children: ReactNode;
} }
export function AppContextProvider(props: Props): JSX.Element { export const AppContextProvider = (props: Props): JSX.Element => {
const [subPanelOpen, setSubPanelOpen] = useStateWithLocalStorage( const [subPanelOpen, setSubPanelOpen] = useStateWithLocalStorage(
"subPanelOpen", "subPanelOpen",
initialState.subPanelOpen initialState.subPanelOpen
@ -176,43 +174,43 @@ export function AppContextProvider(props: Props): JSX.Element {
initialState.libraryItemUserStatus initialState.libraryItemUserStatus
); );
function toggleSubPanelOpen() { const toggleSubPanelOpen = () => {
setSubPanelOpen((current) => (isDefined(current) ? !current : current)); setSubPanelOpen((current) => (isDefined(current) ? !current : current));
} };
function toggleConfigPanelOpen() { const toggleConfigPanelOpen = () => {
setConfigPanelOpen((current) => (isDefined(current) ? !current : current)); setConfigPanelOpen((current) => (isDefined(current) ? !current : current));
} };
function toggleSearchPanelOpen() { const toggleSearchPanelOpen = () => {
setSearchPanelOpen((current) => (isDefined(current) ? !current : current)); setSearchPanelOpen((current) => (isDefined(current) ? !current : current));
} };
function toggleMainPanelReduced() { const toggleMainPanelReduced = () => {
setMainPanelReduced((current) => (isDefined(current) ? !current : current)); setMainPanelReduced((current) => (isDefined(current) ? !current : current));
} };
function toggleMainPanelOpen() { const toggleMainPanelOpen = () => {
setMainPanelOpen((current) => (isDefined(current) ? !current : current)); setMainPanelOpen((current) => (isDefined(current) ? !current : current));
} };
function toggleDarkMode() { const toggleDarkMode = () => {
setDarkMode((current) => (isDefined(current) ? !current : current)); setDarkMode((current) => (isDefined(current) ? !current : current));
} };
function toggleMenuGestures() { const toggleMenuGestures = () => {
setMenuGestures((current) => !current); setMenuGestures((current) => !current);
} };
function toggleSelectedThemeMode() { const toggleSelectedThemeMode = () => {
setSelectedThemeMode((current) => setSelectedThemeMode((current) =>
isDefined(current) ? !current : current isDefined(current) ? !current : current
); );
} };
function toggleDyslexic() { const toggleDyslexic = () => {
setDyslexic((current) => (isDefined(current) ? !current : current)); setDyslexic((current) => (isDefined(current) ? !current : current));
} };
return ( return (
<AppContext.Provider <AppContext.Provider
@ -259,4 +257,4 @@ export function AppContextProvider(props: Props): JSX.Element {
{props.children} {props.children}
</AppContext.Provider> </AppContext.Provider>
); );
} };

View File

@ -17,9 +17,9 @@ export type AppStaticProps = {
languages: NonNullable<GetLanguagesQuery["languages"]>["data"]; languages: NonNullable<GetLanguagesQuery["languages"]>["data"];
}; };
export async function getAppStaticProps( export const getAppStaticProps = async (
context: GetStaticPropsContext context: GetStaticPropsContext
): Promise<AppStaticProps> { ): Promise<AppStaticProps> => {
const sdk = getReadySdk(); const sdk = getReadySdk();
const languages = (await sdk.getLanguages()).languages; const languages = (await sdk.getLanguages()).languages;
@ -53,4 +53,4 @@ export async function getAppStaticProps(
}; };
return appStaticProps; return appStaticProps;
} };

View File

@ -1,5 +1,5 @@
import { PostWithTranslations } from "helpers/types"; import { PostWithTranslations } from "helpers/types";
import { GetStaticPropsContext } from "next"; import { GetStaticProps, GetStaticPropsContext } from "next";
import { AppStaticProps, getAppStaticProps } from "./getAppStaticProps"; import { AppStaticProps, getAppStaticProps } from "./getAppStaticProps";
import { getReadySdk } from "./sdk"; import { getReadySdk } from "./sdk";
@ -7,12 +7,8 @@ export interface PostStaticProps extends AppStaticProps {
post: PostWithTranslations; post: PostWithTranslations;
} }
export function getPostStaticProps( export const getPostStaticProps = (slug: string): GetStaticProps => {
slug: string return async (context) => {
): (
context: GetStaticPropsContext
) => Promise<{ notFound: boolean } | { props: PostStaticProps }> {
return async (context: GetStaticPropsContext) => {
const sdk = getReadySdk(); const sdk = getReadySdk();
const post = await sdk.getPost({ const post = await sdk.getPost({
slug: slug, slug: slug,
@ -29,4 +25,4 @@ export function getPostStaticProps(
} }
return { notFound: true }; return { notFound: true };
}; };
} };

View File

@ -154,6 +154,11 @@ query getWebsiteInterface($language_code: String) {
only_display_items_i_want only_display_items_i_want
only_display_unmarked_items only_display_unmarked_items
display_all_items display_all_items
table_of_contents
definition
no_results_message
all
special_pages
} }
} }
} }

View File

@ -1,9 +1,9 @@
import { GraphQLClient } from "graphql-request"; import { GraphQLClient } from "graphql-request";
import { getSdk } from "graphql/generated"; import { getSdk } from "graphql/generated";
export function getReadySdk() { export const getReadySdk = () => {
const client = new GraphQLClient(process.env.URL_GRAPHQL ?? "", { const client = new GraphQLClient(process.env.URL_GRAPHQL ?? "", {
headers: { Authorization: `Bearer ${process.env.ACCESS_TOKEN}` }, headers: { Authorization: `Bearer ${process.env.ACCESS_TOKEN}` },
}); });
return getSdk(client); return getSdk(client);
} };

View File

@ -1,11 +1,11 @@
export function cIf( export const cIf = (
condition: boolean | null | undefined | string, condition: boolean | null | undefined | string,
ifTrueCss: string, ifTrueCss: string,
ifFalseCss?: string ifFalseCss?: string
) { ) => {
return condition ? ifTrueCss : ifFalseCss ?? ""; return condition ? ifTrueCss : ifFalseCss ?? "";
} };
export function cJoin(...args: (string | undefined)[]): string { export const cJoin = (...args: (string | undefined)[]): string => {
return args.filter((elem) => elem).join(" "); return args.filter((elem) => elem).join(" ");
} };

View File

@ -2,16 +2,21 @@ export interface Wrapper {
children: React.ReactNode; children: React.ReactNode;
} }
export function ConditionalWrapper<T>(props: { interface Props<T> {
isWrapping: boolean; isWrapping: boolean;
children: React.ReactNode; children: React.ReactNode;
wrapper: (wrapperProps: T & Wrapper) => JSX.Element; wrapper: (wrapperProps: T & Wrapper) => JSX.Element;
wrapperProps: T; wrapperProps: T;
}): JSX.Element { }
const { isWrapping, children, wrapper: Wrapper, wrapperProps } = props;
return isWrapping ? ( export const ConditionalWrapper = <T,>({
isWrapping,
children,
wrapper: Wrapper,
wrapperProps,
}: Props<T>): JSX.Element =>
isWrapping ? (
<Wrapper {...wrapperProps}>{children}</Wrapper> <Wrapper {...wrapperProps}>{children}</Wrapper>
) : ( ) : (
<>{children}</> <>{children}</>
); );
}

View File

@ -10,9 +10,12 @@ interface Description {
categories?: Content["categories"]; categories?: Content["categories"];
} }
export function getDescription(props: Description): string { export const getDescription = ({
const { langui, description: text, type, categories } = props; langui,
description: text,
type,
categories,
}: Description): string => {
let description = ""; let description = "";
// TEXT // TEXT
@ -44,15 +47,15 @@ export function getDescription(props: Description): string {
} }
return description; return description;
} };
function prettyMarkdown(markdown: string): string { const prettyMarkdown = (markdown: string): string => {
return markdown.replace(/[*]/gu, "").replace(/[_]/gu, ""); return markdown.replace(/[*]/gu, "").replace(/[_]/gu, "");
} };
function prettyChip(items: (string | undefined)[]): string { const prettyChip = (items: (string | undefined)[]): string => {
return items return items
.filter((item) => isDefined(item)) .filter((item) => isDefined(item))
.map((item) => `(${item})`) .map((item) => `(${item})`)
.join(" "); .join(" ");
} };

View File

@ -2,7 +2,7 @@ import { DatePickerFragment, PricePickerFragment } from "graphql/generated";
import { AppStaticProps } from "../graphql/getAppStaticProps"; import { AppStaticProps } from "../graphql/getAppStaticProps";
import { convertPrice } from "./numbers"; import { convertPrice } from "./numbers";
export function prettyDate(datePicker: DatePickerFragment): string { export const prettyDate = (datePicker: DatePickerFragment): string => {
let result = ""; let result = "";
if (datePicker.year) result += datePicker.year.toString(); if (datePicker.year) result += datePicker.year.toString();
if (datePicker.month) if (datePicker.month)
@ -10,13 +10,13 @@ export function prettyDate(datePicker: DatePickerFragment): string {
if (datePicker.day) if (datePicker.day)
result += `/${datePicker.day.toString().padStart(2, "0")}`; result += `/${datePicker.day.toString().padStart(2, "0")}`;
return result; return result;
} };
export function prettyPrice( export const prettyPrice = (
pricePicker: PricePickerFragment, pricePicker: PricePickerFragment,
currencies: AppStaticProps["currencies"], currencies: AppStaticProps["currencies"],
targetCurrencyCode?: string targetCurrencyCode?: string
): string { ): string => {
if (!targetCurrencyCode) return ""; if (!targetCurrencyCode) return "";
let result = ""; let result = "";
currencies.map((currency) => { currencies.map((currency) => {
@ -31,9 +31,9 @@ export function prettyPrice(
} }
}); });
return result; return result;
} };
export function prettySlug(slug?: string, parentSlug?: string): string { export const prettySlug = (slug?: string, parentSlug?: string): string => {
if (slug) { if (slug) {
if (parentSlug && slug.startsWith(parentSlug)) if (parentSlug && slug.startsWith(parentSlug))
slug = slug.substring(parentSlug.length + 1); slug = slug.substring(parentSlug.length + 1);
@ -42,24 +42,24 @@ export function prettySlug(slug?: string, parentSlug?: string): string {
return capitalizeString(slug); return capitalizeString(slug);
} }
return ""; return "";
} };
export function prettyinlineTitle( export const prettyinlineTitle = (
pretitle: string | undefined | null, pretitle: string | undefined | null,
title: string | undefined | null, title: string | undefined | null,
subtitle: string | undefined | null subtitle: string | undefined | null
): string { ): string => {
let result = ""; let result = "";
if (pretitle) result += `${pretitle}: `; if (pretitle) result += `${pretitle}: `;
result += title; result += title;
if (subtitle) result += ` - ${subtitle}`; if (subtitle) result += ` - ${subtitle}`;
return result; return result;
} };
export function prettyItemType( export const prettyItemType = (
metadata: any, metadata: any,
langui: AppStaticProps["langui"] langui: AppStaticProps["langui"]
): string | undefined | null { ): string | undefined | null => {
switch (metadata.__typename) { switch (metadata.__typename) {
case "ComponentMetadataAudio": case "ComponentMetadataAudio":
return langui.audio; return langui.audio;
@ -76,9 +76,9 @@ export function prettyItemType(
default: default:
return ""; return "";
} }
} };
export function prettyItemSubType( export const prettyItemSubType = (
metadata: metadata:
| { | {
__typename: "ComponentMetadataAudio"; __typename: "ComponentMetadataAudio";
@ -156,7 +156,7 @@ export function prettyItemSubType(
} }
| { __typename: "Error" } | { __typename: "Error" }
| null | null
): string { ): string => {
if (metadata) { if (metadata) {
switch (metadata.__typename) { switch (metadata.__typename) {
case "ComponentMetadataAudio": case "ComponentMetadataAudio":
@ -195,9 +195,9 @@ export function prettyItemSubType(
} }
return ""; return "";
/* eslint-enable @typescript-eslint/no-explicit-any */ /* eslint-enable @typescript-eslint/no-explicit-any */
} };
export function prettyShortenNumber(number: number): string { export const prettyShortenNumber = (number: number): string => {
if (number > 1000000) { if (number > 1000000) {
return number.toLocaleString(undefined, { return number.toLocaleString(undefined, {
maximumSignificantDigits: 3, maximumSignificantDigits: 3,
@ -210,9 +210,9 @@ export function prettyShortenNumber(number: number): string {
); );
} }
return number.toLocaleString(); return number.toLocaleString();
} };
export function prettyDuration(seconds: number): string { export const prettyDuration = (seconds: number): string => {
let hours = 0; let hours = 0;
let minutes = 0; let minutes = 0;
while (seconds > 60) { while (seconds > 60) {
@ -228,36 +228,36 @@ export function prettyDuration(seconds: number): string {
result += minutes.toString().padStart(2, "0") + ":"; result += minutes.toString().padStart(2, "0") + ":";
result += seconds.toString().padStart(2, "0"); result += seconds.toString().padStart(2, "0");
return result; return result;
} };
export function prettyLanguage( export const prettyLanguage = (
code: string, code: string,
languages: AppStaticProps["languages"] languages: AppStaticProps["languages"]
): string { ): string => {
let result = code; let result = code;
languages.forEach((language) => { languages.forEach((language) => {
if (language?.attributes?.code === code) if (language?.attributes?.code === code)
result = language.attributes.localized_name; result = language.attributes.localized_name;
}); });
return result; return result;
} };
export function prettyURL(url: string): string { export const prettyURL = (url: string): string => {
let domain = new URL(url); let domain = new URL(url);
return domain.hostname.replace("www.", ""); return domain.hostname.replace("www.", "");
} };
function capitalizeString(string: string): string { const capitalizeString = (string: string): string => {
function capitalizeWord(word: string): string { const capitalizeWord = (word: string): string => {
return word.charAt(0).toUpperCase() + word.substring(1); return word.charAt(0).toUpperCase() + word.substring(1);
} };
let words = string.split(" "); let words = string.split(" ");
words = words.map((word) => capitalizeWord(word)); words = words.map((word) => capitalizeWord(word));
return words.join(" "); return words.join(" ");
} };
export function slugify(string: string | undefined): string { export const slugify = (string: string | undefined): string => {
if (!string) { if (!string) {
return ""; return "";
} }
@ -275,4 +275,4 @@ export function slugify(string: string | undefined): string {
.replace(/[^a-z0-9- ]/gu, "") .replace(/[^a-z0-9- ]/gu, "")
.trim() .trim()
.replace(/ /gu, "-"); .replace(/ /gu, "-");
} };

View File

@ -14,7 +14,7 @@ interface OgImage {
alt: string; alt: string;
} }
export function getAssetFilename(path: string): string { export const getAssetFilename = (path: string): string => {
let result = path.split("/"); let result = path.split("/");
result = result[result.length - 1].split("."); result = result[result.length - 1].split(".");
result = result result = result
@ -22,9 +22,9 @@ export function getAssetFilename(path: string): string {
.join(".") .join(".")
.split("_"); .split("_");
return result[0]; return result[0];
} };
export function getAssetURL(url: string, quality: ImageQuality): string { export const getAssetURL = (url: string, quality: ImageQuality): string => {
let newUrl = url; let newUrl = url;
newUrl = newUrl.replace(/^\/uploads/u, `/${quality}`); newUrl = newUrl.replace(/^\/uploads/u, `/${quality}`);
newUrl = newUrl.replace(/.jpg$/u, ".webp"); newUrl = newUrl.replace(/.jpg$/u, ".webp");
@ -32,26 +32,26 @@ export function getAssetURL(url: string, quality: ImageQuality): string {
newUrl = newUrl.replace(/.png$/u, ".webp"); newUrl = newUrl.replace(/.png$/u, ".webp");
if (quality === ImageQuality.Og) newUrl = newUrl.replace(/.webp$/u, ".jpg"); if (quality === ImageQuality.Og) newUrl = newUrl.replace(/.webp$/u, ".jpg");
return process.env.NEXT_PUBLIC_URL_IMG + newUrl; return process.env.NEXT_PUBLIC_URL_IMG + newUrl;
} };
function getImgSizesByMaxSize( const getImgSizesByMaxSize = (
width: number, width: number,
height: number, height: number,
maxSize: number maxSize: number
): { width: number; height: number } { ): { width: number; height: number } => {
if (width > height) { if (width > height) {
if (width < maxSize) return { width: width, height: height }; if (width < maxSize) return { width: width, height: height };
return { width: maxSize, height: (height / width) * maxSize }; return { width: maxSize, height: (height / width) * maxSize };
} }
if (height < maxSize) return { width: width, height: height }; if (height < maxSize) return { width: width, height: height };
return { width: (width / height) * maxSize, height: maxSize }; return { width: (width / height) * maxSize, height: maxSize };
} };
export function getImgSizesByQuality( export const getImgSizesByQuality = (
width: number, width: number,
height: number, height: number,
quality: ImageQuality quality: ImageQuality
): { width: number; height: number } { ): { width: number; height: number } => {
switch (quality) { switch (quality) {
case ImageQuality.Og: case ImageQuality.Og:
return getImgSizesByMaxSize(width, height, 512); return getImgSizesByMaxSize(width, height, 512);
@ -64,12 +64,12 @@ export function getImgSizesByQuality(
default: default:
return { width: 0, height: 0 }; return { width: 0, height: 0 };
} }
} };
export function getOgImage( export const getOgImage = (
quality: ImageQuality, quality: ImageQuality,
image: UploadImageFragment image: UploadImageFragment
): OgImage { ): OgImage => {
const imgSize = getImgSizesByQuality( const imgSize = getImgSizesByQuality(
image.width ?? 0, image.width ?? 0,
image.height ?? 0, image.height ?? 0,
@ -81,4 +81,4 @@ export function getOgImage(
height: imgSize.height, height: imgSize.height,
alt: image.alternativeText || "", alt: image.alternativeText || "",
}; };
} };

View File

@ -10,11 +10,11 @@ import LibraryPage from "../pages/library/index";
type Items = Parameters<typeof LibraryPage>[0]["items"]; type Items = Parameters<typeof LibraryPage>[0]["items"];
type GroupLibraryItems = Map<string, Items>; type GroupLibraryItems = Map<string, Items>;
export function getGroups( export const getGroups = (
langui: AppStaticProps["langui"], langui: AppStaticProps["langui"],
groupByType: number, groupByType: number,
items: Items items: Items
): GroupLibraryItems { ): GroupLibraryItems => {
const groups: GroupLibraryItems = new Map(); const groups: GroupLibraryItems = new Map();
switch (groupByType) { switch (groupByType) {
@ -145,9 +145,9 @@ export function getGroups(
} }
} }
return mapRemoveEmptyValues(groups); return mapRemoveEmptyValues(groups);
} };
export function filterItems( export const filterItems = (
appLayout: AppLayoutState, appLayout: AppLayoutState,
items: Items, items: Items,
searchName: string, searchName: string,
@ -155,7 +155,7 @@ export function filterItems(
showPrimaryItems: boolean, showPrimaryItems: boolean,
showSecondaryItems: boolean, showSecondaryItems: boolean,
filterUserStatus: LibraryItemUserStatus | undefined filterUserStatus: LibraryItemUserStatus | undefined
): Items { ): Items => {
return items.filter((item) => { return items.filter((item) => {
if (!showSubitems && !item.attributes?.root_item) return false; if (!showSubitems && !item.attributes?.root_item) return false;
if (showSubitems && isUntangibleGroupItem(item.attributes?.metadata?.[0])) { if (showSubitems && isUntangibleGroupItem(item.attributes?.metadata?.[0])) {
@ -194,23 +194,23 @@ export function filterItems(
return true; return true;
}); });
} };
// TODO: Properly type this shit // TODO: Properly type this shit
export function isUntangibleGroupItem(metadata: any) { export const isUntangibleGroupItem = (metadata: any) => {
return ( return (
metadata && metadata &&
metadata.__typename === "ComponentMetadataGroup" && metadata.__typename === "ComponentMetadataGroup" &&
(metadata.subtype?.data?.attributes?.slug === "variant-set" || (metadata.subtype?.data?.attributes?.slug === "variant-set" ||
metadata.subtype?.data?.attributes?.slug === "relation-set") metadata.subtype?.data?.attributes?.slug === "relation-set")
); );
} };
export function sortBy( export const sortBy = (
orderByType: number, orderByType: number,
items: Items, items: Items,
currencies: AppStaticProps["currencies"] currencies: AppStaticProps["currencies"]
) { ) => {
switch (orderByType) { switch (orderByType) {
case 0: case 0:
return items.sort((a, b) => { return items.sort((a, b) => {
@ -249,4 +249,4 @@ export function sortBy(
default: default:
return items; return items;
} }
} };

View File

@ -1,9 +1,9 @@
import { GetCurrenciesQuery, PricePickerFragment } from "graphql/generated"; import { GetCurrenciesQuery, PricePickerFragment } from "graphql/generated";
export function convertPrice( export const convertPrice = (
pricePicker: PricePickerFragment, pricePicker: PricePickerFragment,
targetCurrency: NonNullable<GetCurrenciesQuery["currencies"]>["data"][number] targetCurrency: NonNullable<GetCurrenciesQuery["currencies"]>["data"][number]
): number { ): number => {
if ( if (
pricePicker.amount && pricePicker.amount &&
pricePicker.currency?.data?.attributes && pricePicker.currency?.data?.attributes &&
@ -14,17 +14,17 @@ export function convertPrice(
targetCurrency.attributes.rate_to_usd targetCurrency.attributes.rate_to_usd
); );
return 0; return 0;
} };
export function convertMmToInch(mm: number | null | undefined): string { export const convertMmToInch = (mm: number | null | undefined): string => {
return mm ? (mm * 0.03937008).toPrecision(3) : ""; return mm ? (mm * 0.03937008).toPrecision(3) : "";
} };
export function randomInt(min: number, max: number) { export const randomInt = (min: number, max: number) => {
return Math.floor(Math.random() * (max - min)) + min; return Math.floor(Math.random() * (max - min)) + min;
} };
export function isInteger(value: string): boolean { export const isInteger = (value: string): boolean => {
// eslint-disable-next-line require-unicode-regexp // eslint-disable-next-line require-unicode-regexp
return /^[+-]?[0-9]+$/.test(value); return /^[+-]?[0-9]+$/.test(value);
} };

View File

@ -18,7 +18,7 @@ type SortContentProps =
>["data"][number]["attributes"] >["data"][number]["attributes"]
>["contents"]; >["contents"];
export function sortContent(contents: SortContentProps) { export const sortContent = (contents: SortContentProps) => {
contents?.data.sort((a, b) => { contents?.data.sort((a, b) => {
if ( if (
a.attributes?.range[0]?.__typename === "ComponentRangePageRange" && a.attributes?.range[0]?.__typename === "ComponentRangePageRange" &&
@ -31,12 +31,12 @@ export function sortContent(contents: SortContentProps) {
} }
return 0; return 0;
}); });
} };
export function getStatusDescription( export const getStatusDescription = (
status: string, status: string,
langui: AppStaticProps["langui"] langui: AppStaticProps["langui"]
): string | null | undefined { ): string | null | undefined => {
switch (status) { switch (status) {
case Enum_Componentsetstextset_Status.Incomplete: case Enum_Componentsetstextset_Status.Incomplete:
return langui.status_incomplete; return langui.status_incomplete;
@ -53,45 +53,44 @@ export function getStatusDescription(
default: default:
return ""; return "";
} }
} };
export function isDefined<T>(t: T): t is NonNullable<T> { export const isDefined = <T>(t: T): t is NonNullable<T> =>
return t !== null && t !== undefined; t !== null && t !== undefined;
}
export function isUndefined<T>(t: T | undefined | null): t is undefined | null { export const isUndefined = <T>(
return t === null || t === undefined; t: T | undefined | null
} ): t is undefined | null => t === null || t === undefined;
export function isDefinedAndNotEmpty( export const isDefinedAndNotEmpty = (
string: string | undefined | null string: string | undefined | null
): string is string { ): string is string => isDefined(string) && string.length > 0;
return isDefined(string) && string.length > 0;
}
export function filterDefined<T>(t: T[] | undefined | null): NonNullable<T>[] { export const filterDefined = <T>(t: T[] | undefined | null): NonNullable<T>[] =>
if (isUndefined(t)) return []; isUndefined(t)
return t.filter((item) => isDefined(item)) as NonNullable<T>[]; ? []
} : (t.filter((item) => isDefined(item)) as NonNullable<T>[]);
export function filterHasAttributes<T, P extends keyof NonNullable<T>>( export const filterHasAttributes = <T, P extends keyof NonNullable<T>>(
t: T[] | undefined | null, t: T[] | undefined | null,
attributes?: P[] attributes?: P[]
): SelectiveRequiredNonNullable<NonNullable<T>, P>[] { ): SelectiveRequiredNonNullable<NonNullable<T>, P>[] =>
if (isUndefined(t)) return []; isUndefined(t)
return t.filter((item) => { ? []
if (isDefined(item)) { : (t.filter((item) => {
const attributesToCheck = attributes ?? (Object.keys(item) as P[]); if (isDefined(item)) {
return attributesToCheck.every((attribute) => isDefined(item[attribute])); const attributesToCheck = attributes ?? (Object.keys(item) as P[]);
} return attributesToCheck.every((attribute) =>
return false; isDefined(item[attribute])
}) as unknown as SelectiveRequiredNonNullable<NonNullable<T>, P>[]; );
} }
return false;
}) as unknown as SelectiveRequiredNonNullable<NonNullable<T>, P>[]);
export function iterateMap<K, V, U>( export const iterateMap = <K, V, U>(
map: Map<K, V>, map: Map<K, V>,
callbackfn: (key: K, value: V, index: number) => U callbackfn: (key: K, value: V, index: number) => U
): U[] { ): U[] => {
const result: U[] = []; const result: U[] = [];
let index = 0; let index = 0;
for (const [key, value] of map.entries()) { for (const [key, value] of map.entries()) {
@ -99,21 +98,18 @@ export function iterateMap<K, V, U>(
index += 1; index += 1;
} }
return result; return result;
} };
export function mapMoveEntry<K, V>( export const mapMoveEntry = <K, V>(
map: Map<K, V>, map: Map<K, V>,
sourceIndex: number, sourceIndex: number,
targetIndex: number targetIndex: number
) { ) => new Map(arrayMove([...map], sourceIndex, targetIndex));
return new Map(arrayMove([...map], sourceIndex, targetIndex));
}
function arrayMove<T>(arr: T[], sourceIndex: number, targetIndex: number) { const arrayMove = <T>(arr: T[], sourceIndex: number, targetIndex: number) => {
arr.splice(targetIndex, 0, arr.splice(sourceIndex, 1)[0]); arr.splice(targetIndex, 0, arr.splice(sourceIndex, 1)[0]);
return arr; return arr;
} };
export function mapRemoveEmptyValues<K, V>(groups: Map<K, V[]>): Map<K, V[]> { export const mapRemoveEmptyValues = <K, V>(groups: Map<K, V[]>): Map<K, V[]> =>
return new Map([...groups].filter(([_, items]) => items.length > 0)); new Map([...groups].filter(([_, items]) => items.length > 0));
}

View File

@ -1,7 +1,5 @@
export function getVideoThumbnailURL(uid: string): string { export const getVideoThumbnailURL = (uid: string): string =>
return `${process.env.NEXT_PUBLIC_URL_WATCH}/videos/${uid}.webp`; `${process.env.NEXT_PUBLIC_URL_WATCH}/videos/${uid}.webp`;
}
export function getVideoFile(uid: string): string { export const getVideoFile = (uid: string): string =>
return `${process.env.NEXT_PUBLIC_URL_WATCH}/videos/${uid}.mp4`; `${process.env.NEXT_PUBLIC_URL_WATCH}/videos/${uid}.mp4`;
}

View File

@ -2,7 +2,7 @@ import { useEffect } from "react";
import { usePrefersDarkMode } from "./useMediaQuery"; import { usePrefersDarkMode } from "./useMediaQuery";
import { useStateWithLocalStorage } from "./useStateWithLocalStorage"; import { useStateWithLocalStorage } from "./useStateWithLocalStorage";
export function useDarkMode( export const useDarkMode = (
key: string, key: string,
initialValue: boolean | undefined initialValue: boolean | undefined
): [ ): [
@ -10,11 +10,9 @@ export function useDarkMode(
boolean | undefined, boolean | undefined,
React.Dispatch<React.SetStateAction<boolean | undefined>>, React.Dispatch<React.SetStateAction<boolean | undefined>>,
React.Dispatch<React.SetStateAction<boolean | undefined>> React.Dispatch<React.SetStateAction<boolean | undefined>>
] { ] => {
const [darkMode, setDarkMode] = useStateWithLocalStorage(key, initialValue); const [darkMode, setDarkMode] = useStateWithLocalStorage(key, initialValue);
const prefersDarkMode = usePrefersDarkMode(); const prefersDarkMode = usePrefersDarkMode();
const [selectedThemeMode, setSelectedThemeMode] = useStateWithLocalStorage( const [selectedThemeMode, setSelectedThemeMode] = useStateWithLocalStorage(
"selectedThemeMode", "selectedThemeMode",
false false
@ -25,4 +23,4 @@ export function useDarkMode(
}, [selectedThemeMode, prefersDarkMode, setDarkMode]); }, [selectedThemeMode, prefersDarkMode, setDarkMode]);
return [darkMode, selectedThemeMode, setDarkMode, setSelectedThemeMode]; return [darkMode, selectedThemeMode, setDarkMode, setSelectedThemeMode];
} };

View File

@ -1,10 +1,10 @@
import { LightBox } from "components/LightBox"; import { LightBox } from "components/LightBox";
import { useState } from "react"; import { useState } from "react";
export function useLightBox(): [ export const useLightBox = (): [
(images: string[], index?: number) => void, (images: string[], index?: number) => void,
() => JSX.Element () => JSX.Element
] { ] => {
const [lightboxOpen, setLightboxOpen] = useState(false); const [lightboxOpen, setLightboxOpen] = useState(false);
const [lightboxImages, setLightboxImages] = useState([""]); const [lightboxImages, setLightboxImages] = useState([""]);
const [lightboxIndex, setLightboxIndex] = useState(0); const [lightboxIndex, setLightboxIndex] = useState(0);
@ -25,4 +25,4 @@ export function useLightBox(): [
/> />
), ),
]; ];
} };

View File

@ -1,21 +1,21 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { breaks } from "../../design.config"; import { breaks } from "../../design.config";
function useMediaQuery(query: string): boolean { const useMediaQuery = (query: string): boolean => {
function getMatches(query: string): boolean { const getMatches = (query: string): boolean => {
// Prevents SSR issues // Prevents SSR issues
if (typeof window !== "undefined") { if (typeof window !== "undefined") {
return window.matchMedia(query).matches; return window.matchMedia(query).matches;
} }
return false; return false;
} };
const [matches, setMatches] = useState<boolean>(getMatches(query)); const [matches, setMatches] = useState<boolean>(getMatches(query));
useEffect(() => { useEffect(() => {
function handleChange() { const handleChange = () => {
setMatches(getMatches(query)); setMatches(getMatches(query));
} };
const matchMedia = window.matchMedia(query); const matchMedia = window.matchMedia(query);
@ -31,25 +31,16 @@ function useMediaQuery(query: string): boolean {
}, [query]); }, [query]);
return matches; return matches;
} };
// ts-unused-exports:disable-next-line // ts-unused-exports:disable-next-line
export function useMediaThin() { export const useMediaThin = () => useMediaQuery(breaks.thin.raw);
return useMediaQuery(breaks.thin.raw);
}
export function useMediaMobile() { export const useMediaMobile = () => useMediaQuery(breaks.mobile.raw);
return useMediaQuery(breaks.mobile.raw);
}
export function useMediaDesktop() { export const useMediaDesktop = () => useMediaQuery(breaks.desktop.raw);
return useMediaQuery(breaks.desktop.raw);
}
export function useMediaHoverable() { export const useMediaHoverable = () => useMediaQuery("(hover: hover)");
return useMediaQuery("(hover: hover)");
}
export function usePrefersDarkMode() { export const usePrefersDarkMode = () =>
return useMediaQuery("(prefers-color-scheme: dark)"); useMediaQuery("(prefers-color-scheme: dark)");
}

View File

@ -5,8 +5,8 @@ export enum AnchorIds {
} }
// Scroll to top of element "id" when "deps" update. // Scroll to top of element "id" when "deps" update.
export function useScrollTopOnChange(id: AnchorIds, deps: DependencyList) { export const useScrollTopOnChange = (id: AnchorIds, deps: DependencyList) => {
useEffect(() => { useEffect(() => {
document.querySelector(`#${id}`)?.scrollTo({ top: 0, behavior: "smooth" }); document.querySelector(`#${id}`)?.scrollTo({ top: 0, behavior: "smooth" });
}, deps); }, deps);
} };

View File

@ -13,31 +13,28 @@ interface Props<T> {
transform?: (item: NonNullable<T>) => NonNullable<T>; transform?: (item: NonNullable<T>) => NonNullable<T>;
} }
function getPreferredLanguage( const getPreferredLanguage = (
preferredLanguages: (string | undefined)[], preferredLanguages: (string | undefined)[],
availableLanguages: Map<string, number> availableLanguages: Map<string, number>
): number | undefined { ): number | undefined => {
for (const locale of preferredLanguages) { for (const locale of preferredLanguages) {
if (isDefined(locale) && availableLanguages.has(locale)) { if (isDefined(locale) && availableLanguages.has(locale)) {
return availableLanguages.get(locale); return availableLanguages.get(locale);
} }
} }
return undefined; return undefined;
} };
export function useSmartLanguage<T>( export const useSmartLanguage = <T>({
props: Props<T> items,
): [ languageExtractor,
languages,
transform = (item) => item,
}: Props<T>): [
T | undefined, T | undefined,
typeof LanguageSwitcher, typeof LanguageSwitcher,
Parameters<typeof LanguageSwitcher>[0] Parameters<typeof LanguageSwitcher>[0]
] { ] => {
const {
items,
languageExtractor,
languages,
transform = (item) => item,
} = props;
const { preferredLanguages } = useAppLayout(); const { preferredLanguages } = useAppLayout();
const router = useRouter(); const router = useRouter();
@ -81,4 +78,4 @@ export function useSmartLanguage<T>(
}; };
return [selectedTranslation, LanguageSwitcher, languageSwitcherProps]; return [selectedTranslation, LanguageSwitcher, languageSwitcherProps];
} };

View File

@ -1,84 +0,0 @@
import { LanguageSwitcher } from "components/Inputs/LanguageSwitcher";
import { useAppLayout } from "contexts/AppLayoutContext";
import { AppStaticProps } from "graphql/getAppStaticProps";
import { filterDefined, isDefined } from "helpers/others";
import { useRouter } from "next/router";
import { useEffect, useMemo, useState } from "react";
interface Props<T> {
items: T[];
languages: AppStaticProps["languages"];
languageExtractor: (item: NonNullable<T>) => string | undefined;
transform?: (item: NonNullable<T>) => NonNullable<T>;
}
function getPreferredLanguage(
preferredLanguages: (string | undefined)[],
availableLanguages: Map<string, number>
): number | undefined {
for (const locale of preferredLanguages) {
if (isDefined(locale) && availableLanguages.has(locale)) {
return availableLanguages.get(locale);
}
}
return undefined;
}
export function useSmartLanguage<T>(
props: Props<T>
): [
T | undefined,
typeof LanguageSwitcher,
Parameters<typeof LanguageSwitcher>[0]
] {
const {
items,
languageExtractor,
languages,
transform = (item) => item,
} = props;
const { preferredLanguages } = useAppLayout();
const router = useRouter();
const availableLocales = useMemo(() => {
const memo = new Map<string, number>();
filterDefined(items).map((elem, index) => {
const result = languageExtractor(elem);
if (isDefined(result)) memo.set(result, index);
});
return memo;
}, [items, languageExtractor]);
const [selectedTranslationIndex, setSelectedTranslationIndex] = useState<
number | undefined
>();
useEffect(() => {
setSelectedTranslationIndex(
getPreferredLanguage(
preferredLanguages ?? [router.locale],
availableLocales
)
);
}, [preferredLanguages, availableLocales, router.locale]);
const selectedTranslation = useMemo(() => {
if (isDefined(selectedTranslationIndex)) {
const item = items[selectedTranslationIndex];
if (isDefined(item)) {
return transform(item);
}
}
return undefined;
}, [items, selectedTranslationIndex, transform]);
const languageSwitcherProps = {
languages: languages,
locales: availableLocales,
localesIndex: selectedTranslationIndex,
onLanguageChanged: setSelectedTranslationIndex,
};
return [selectedTranslation, LanguageSwitcher, languageSwitcherProps];
}

View File

@ -1,10 +1,10 @@
import { isDefined } from "helpers/others"; import { isDefined } from "helpers/others";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
export function useStateWithLocalStorage<T>( export const useStateWithLocalStorage = <T>(
key: string, key: string,
initialValue: T initialValue: T
): [T | undefined, React.Dispatch<React.SetStateAction<T | undefined>>] { ): [T | undefined, React.Dispatch<React.SetStateAction<T | undefined>>] => {
const [value, setValue] = useState<T | undefined>(undefined); const [value, setValue] = useState<T | undefined>(undefined);
const [, setFromLocaleStorage] = useState<boolean>(false); const [, setFromLocaleStorage] = useState<boolean>(false);
@ -28,4 +28,4 @@ export function useStateWithLocalStorage<T>(
}, [value, key]); }, [value, key]);
return [value, setValue]; return [value, setValue];
} };

View File

@ -1,7 +1,6 @@
import { Dispatch, SetStateAction, useCallback } from "react"; import { Dispatch, SetStateAction, useCallback } from "react";
export function useToggle(setState: Dispatch<SetStateAction<boolean>>) { export const useToggle = (setState: Dispatch<SetStateAction<boolean>>) =>
return useCallback(() => { useCallback(() => {
setState((current) => !current); setState((current) => !current);
}, []); }, []);
}

View File

@ -5,16 +5,19 @@ import {
} from "components/PanelComponents/ReturnButton"; } from "components/PanelComponents/ReturnButton";
import { ContentPanel } from "components/Panels/ContentPanel"; import { ContentPanel } from "components/Panels/ContentPanel";
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps"; import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
import { GetStaticProps } from "next";
import { GetStaticPropsContext } from "next"; /*
import { useMemo } from "react"; *
* PAGE
*/
interface Props extends AppStaticProps {} interface Props extends AppStaticProps {}
export default function FourOhFour(props: Props): JSX.Element { const FourOhFour = ({ langui, ...otherProps }: Props): JSX.Element => (
const { langui } = props; <AppLayout
const contentPanel = useMemo( navTitle="404"
() => ( contentPanel={
<ContentPanel> <ContentPanel>
<h1>404 - {langui.page_not_found}</h1> <h1>404 - {langui.page_not_found}</h1>
<ReturnButton <ReturnButton
@ -24,19 +27,23 @@ export default function FourOhFour(props: Props): JSX.Element {
displayOn={ReturnButtonType.Both} displayOn={ReturnButtonType.Both}
/> />
</ContentPanel> </ContentPanel>
), }
[langui] langui={langui}
); {...otherProps}
return <AppLayout navTitle="404" contentPanel={contentPanel} {...props} />; />
} );
export default FourOhFour;
export async function getStaticProps( /*
context: GetStaticPropsContext *
): Promise<{ notFound: boolean } | { props: Props }> { * NEXT DATA FETCHING
*/
export const getStaticProps: GetStaticProps = async (context) => {
const props: Props = { const props: Props = {
...(await getAppStaticProps(context)), ...(await getAppStaticProps(context)),
}; };
return { return {
props: props, props: props,
}; };
} };

View File

@ -5,16 +5,19 @@ import {
} from "components/PanelComponents/ReturnButton"; } from "components/PanelComponents/ReturnButton";
import { ContentPanel } from "components/Panels/ContentPanel"; import { ContentPanel } from "components/Panels/ContentPanel";
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps"; import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
import { GetStaticProps } from "next";
import { GetStaticPropsContext } from "next"; /*
import { useMemo } from "react"; *
* PAGE
*/
interface Props extends AppStaticProps {} interface Props extends AppStaticProps {}
export default function FiveHundred(props: Props): JSX.Element { const FiveHundred = ({ langui, ...otherProps }: Props): JSX.Element => (
const { langui } = props; <AppLayout
const contentPanel = useMemo( navTitle="500"
() => ( contentPanel={
<ContentPanel> <ContentPanel>
<h1>500 - Internal Server Error</h1> <h1>500 - Internal Server Error</h1>
<ReturnButton <ReturnButton
@ -24,19 +27,23 @@ export default function FiveHundred(props: Props): JSX.Element {
displayOn={ReturnButtonType.Both} displayOn={ReturnButtonType.Both}
/> />
</ContentPanel> </ContentPanel>
), }
[langui] langui={langui}
); {...otherProps}
return <AppLayout navTitle="500" contentPanel={contentPanel} {...props} />; />
} );
export default FiveHundred;
export async function getStaticProps( /*
context: GetStaticPropsContext *
): Promise<{ notFound: boolean } | { props: Props }> { * NEXT DATA FETCHING
*/
export const getStaticProps: GetStaticProps = async (context) => {
const props: Props = { const props: Props = {
...(await getAppStaticProps(context)), ...(await getAppStaticProps(context)),
}; };
return { return {
props: props, props: props,
}; };
} };

View File

@ -9,10 +9,9 @@ import { AppContextProvider } from "contexts/AppLayoutContext";
import type { AppProps } from "next/app"; import type { AppProps } from "next/app";
import "tailwind.css"; import "tailwind.css";
export default function AccordsLibraryApp(props: AppProps): JSX.Element { const AccordsLibraryApp = (props: AppProps): JSX.Element => (
return ( <AppContextProvider>
<AppContextProvider> <props.Component {...props.pageProps} />
<props.Component {...props.pageProps} /> </AppContextProvider>
</AppContextProvider> );
); export default AccordsLibraryApp;
}

View File

@ -4,20 +4,33 @@ import {
PostStaticProps, PostStaticProps,
} from "graphql/getPostStaticProps"; } from "graphql/getPostStaticProps";
export default function AccordsHandbook(props: PostStaticProps): JSX.Element { /*
const { post, langui, languages, currencies } = props; *
return ( * PAGE
<PostPage */
currencies={currencies}
languages={languages} const AccordsHandbook = ({
langui={langui} post,
post={post} langui,
returnHref="/about-us/" languages,
returnTitle={langui.about_us} currencies,
displayToc }: PostStaticProps): JSX.Element => (
displayLanguageSwitcher <PostPage
/> currencies={currencies}
); languages={languages}
} langui={langui}
post={post}
returnHref="/about-us/"
returnTitle={langui.about_us}
displayToc
displayLanguageSwitcher
/>
);
export default AccordsHandbook;
/*
*
* NEXT DATA FETCHING
*/
export const getStaticProps = getPostStaticProps("accords-handbook"); export const getStaticProps = getPostStaticProps("accords-handbook");

View File

@ -11,9 +11,17 @@ import { useRouter } from "next/router";
import { RequestMailProps, ResponseMailProps } from "pages/api/mail"; import { RequestMailProps, ResponseMailProps } from "pages/api/mail";
import { useState } from "react"; import { useState } from "react";
export default function AboutUs(props: PostStaticProps): JSX.Element { /*
const { post, langui, languages, currencies } = props; *
* PAGE
*/
const AboutUs = ({
post,
langui,
languages,
currencies,
}: PostStaticProps): JSX.Element => {
const router = useRouter(); const router = useRouter();
const [formResponse, setFormResponse] = useState(""); const [formResponse, setFormResponse] = useState("");
const [formState, setFormState] = useState<"completed" | "ongoing" | "stale">( const [formState, setFormState] = useState<"completed" | "ongoing" | "stale">(
@ -181,6 +189,12 @@ export default function AboutUs(props: PostStaticProps): JSX.Element {
displayLanguageSwitcher displayLanguageSwitcher
/> />
); );
} };
export default AboutUs;
/*
*
* NEXT DATA FETCHING
*/
export const getStaticProps = getPostStaticProps("contact"); export const getStaticProps = getPostStaticProps("contact");

View File

@ -4,16 +4,19 @@ import { NavOption } from "components/PanelComponents/NavOption";
import { PanelHeader } from "components/PanelComponents/PanelHeader"; import { PanelHeader } from "components/PanelComponents/PanelHeader";
import { SubPanel } from "components/Panels/SubPanel"; import { SubPanel } from "components/Panels/SubPanel";
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps"; import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
import { GetStaticProps } from "next";
import { GetStaticPropsContext } from "next"; /*
import { useMemo } from "react"; *
* PAGE
*/
interface Props extends AppStaticProps {} interface Props extends AppStaticProps {}
export default function AboutUs(props: Props): JSX.Element { const AboutUs = ({ langui, ...otherProps }: Props): JSX.Element => (
const { langui } = props; <AppLayout
const subPanel = useMemo( navTitle={langui.about_us}
() => ( subPanel={
<SubPanel> <SubPanel>
<PanelHeader <PanelHeader
icon={Icon.Info} icon={Icon.Info}
@ -33,21 +36,23 @@ export default function AboutUs(props: Props): JSX.Element {
/> />
<NavOption title={langui.contact_us} url="/about-us/contact" border /> <NavOption title={langui.contact_us} url="/about-us/contact" border />
</SubPanel> </SubPanel>
), }
[langui] langui={langui}
); {...otherProps}
return ( />
<AppLayout navTitle={langui.about_us} subPanel={subPanel} {...props} /> );
); export default AboutUs;
}
export async function getStaticProps( /*
context: GetStaticPropsContext *
): Promise<{ notFound: boolean } | { props: Props }> { * NEXT DATA FETCHING
*/
export const getStaticProps: GetStaticProps = async (context) => {
const props: Props = { const props: Props = {
...(await getAppStaticProps(context)), ...(await getAppStaticProps(context)),
}; };
return { return {
props: props, props: props,
}; };
} };

View File

@ -4,20 +4,33 @@ import {
PostStaticProps, PostStaticProps,
} from "graphql/getPostStaticProps"; } from "graphql/getPostStaticProps";
export default function Legality(props: PostStaticProps): JSX.Element { /*
const { post, langui, languages, currencies } = props; *
return ( * PAGE
<PostPage */
currencies={currencies}
languages={languages} const Legality = ({
langui={langui} post,
post={post} langui,
returnHref="/about-us/" languages,
returnTitle={langui.about_us} currencies,
displayToc }: PostStaticProps): JSX.Element => (
displayLanguageSwitcher <PostPage
/> currencies={currencies}
); languages={languages}
} langui={langui}
post={post}
returnHref="/about-us/"
returnTitle={langui.about_us}
displayToc
displayLanguageSwitcher
/>
);
export default Legality;
/*
*
* NEXT DATA FETCHING
*/
export const getStaticProps = getPostStaticProps("legality"); export const getStaticProps = getPostStaticProps("legality");

View File

@ -4,20 +4,33 @@ import {
PostStaticProps, PostStaticProps,
} from "graphql/getPostStaticProps"; } from "graphql/getPostStaticProps";
export default function SharingPolicy(props: PostStaticProps): JSX.Element { /*
const { post, langui, languages, currencies } = props; *
return ( * PAGE
<PostPage */
currencies={currencies}
languages={languages} const SharingPolicy = ({
langui={langui} post,
post={post} langui,
returnHref="/about-us/" languages,
returnTitle={langui.about_us} currencies,
displayToc }: PostStaticProps): JSX.Element => (
displayLanguageSwitcher <PostPage
/> currencies={currencies}
); languages={languages}
} langui={langui}
post={post}
returnHref="/about-us/"
returnTitle={langui.about_us}
displayToc
displayLanguageSwitcher
/>
);
export default SharingPolicy;
/*
*
* NEXT DATA FETCHING
*/
export const getStaticProps = getPostStaticProps("sharing-policy"); export const getStaticProps = getPostStaticProps("sharing-policy");

View File

@ -14,10 +14,10 @@ export interface RequestMailProps {
formName: string; formName: string;
} }
export default async function Mail( const Mail = async (
req: NextApiRequest, req: NextApiRequest,
res: NextApiResponse<ResponseMailProps> res: NextApiResponse<ResponseMailProps>
) { ) => {
if (req.method === "POST") { if (req.method === "POST") {
const body = req.body as RequestMailProps; const body = req.body as RequestMailProps;
@ -48,4 +48,5 @@ export default async function Mail(
} }
res.status(200).json({ code: "OKAY" }); res.status(200).json({ code: "OKAY" });
} };
export default Mail;

View File

@ -81,10 +81,10 @@ type ResponseMailProps = {
revalidated: boolean; revalidated: boolean;
}; };
export default async function Revalidate( const Revalidate = async (
req: NextApiRequest, req: NextApiRequest,
res: NextApiResponse<ResponseMailProps> res: NextApiResponse<ResponseMailProps>
) { ) => {
const body = req.body as RequestProps; const body = req.body as RequestProps;
const { serverRuntimeConfig } = getConfig(); const { serverRuntimeConfig } = getConfig();
@ -217,4 +217,5 @@ export default async function Revalidate(
.status(500) .status(500)
.send({ message: "Error revalidating", revalidated: false }); .send({ message: "Error revalidating", revalidated: false });
} }
} };
export default Revalidate;

View File

@ -4,14 +4,18 @@ import { PanelHeader } from "components/PanelComponents/PanelHeader";
import { SubPanel } from "components/Panels/SubPanel"; import { SubPanel } from "components/Panels/SubPanel";
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps"; import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
import { GetStaticPropsContext } from "next"; import { GetStaticProps } from "next";
import { Icon } from "components/Ico"; import { Icon } from "components/Ico";
import { useMemo } from "react"; import { useMemo } from "react";
/*
*
* PAGE
*/
interface Props extends AppStaticProps {} interface Props extends AppStaticProps {}
export default function Archives(props: Props): JSX.Element { const Archives = ({ langui, ...otherProps }: Props): JSX.Element => {
const { langui } = props;
const subPanel = useMemo( const subPanel = useMemo(
() => ( () => (
<SubPanel> <SubPanel>
@ -26,17 +30,26 @@ export default function Archives(props: Props): JSX.Element {
[langui] [langui]
); );
return ( return (
<AppLayout navTitle={langui.archives} subPanel={subPanel} {...props} /> <AppLayout
navTitle={langui.archives}
subPanel={subPanel}
langui={langui}
{...otherProps}
/>
); );
} };
export default Archives;
export async function getStaticProps( /*
context: GetStaticPropsContext *
): Promise<{ notFound: boolean } | { props: Props }> { * NEXT DATA FETCHING
*/
export const getStaticProps: GetStaticProps = async (context) => {
const props: Props = { const props: Props = {
...(await getAppStaticProps(context)), ...(await getAppStaticProps(context)),
}; };
return { return {
props: props, props: props,
}; };
} };

View File

@ -15,25 +15,25 @@ import { GetVideoChannelQuery } 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 { getVideoThumbnailURL } from "helpers/videos"; import { getVideoThumbnailURL } from "helpers/videos";
import { import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next";
GetStaticPathsContext,
GetStaticPathsResult,
GetStaticPropsContext,
} from "next";
import { Fragment, useState, useMemo } from "react"; import { Fragment, useState, useMemo } 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 { filterHasAttributes, isDefined } from "helpers/others"; import { filterHasAttributes, isDefined } from "helpers/others";
/*
*
* PAGE
*/
interface Props extends AppStaticProps { interface Props extends AppStaticProps {
channel: NonNullable< channel: NonNullable<
GetVideoChannelQuery["videoChannels"] GetVideoChannelQuery["videoChannels"]
>["data"][number]["attributes"]; >["data"][number]["attributes"];
} }
export default function Channel(props: Props): JSX.Element { const Channel = ({ langui, channel, ...otherProps }: Props): JSX.Element => {
const { langui, channel } = props;
const [keepInfoVisible, setKeepInfoVisible] = useState(true); const [keepInfoVisible, setKeepInfoVisible] = useState(true);
const hoverable = useMediaHoverable(); const hoverable = useMediaHoverable();
@ -115,14 +115,19 @@ export default function Channel(props: Props): JSX.Element {
navTitle={langui.archives} navTitle={langui.archives}
subPanel={subPanel} subPanel={subPanel}
contentPanel={contentPanel} contentPanel={contentPanel}
{...props} langui={langui}
{...otherProps}
/> />
); );
} };
export default Channel;
export async function getStaticProps( /*
context: GetStaticPropsContext *
): Promise<{ notFound: boolean } | { props: Props }> { * NEXT DATA FETCHING
*/
export const getStaticProps: GetStaticProps = async (context) => {
const sdk = getReadySdk(); const sdk = getReadySdk();
const channel = await sdk.getVideoChannel({ const channel = await sdk.getVideoChannel({
channel: channel:
@ -138,11 +143,11 @@ export async function getStaticProps(
return { return {
props: props, props: props,
}; };
} };
export async function getStaticPaths( // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
context: GetStaticPathsContext
): Promise<GetStaticPathsResult> { export const getStaticPaths: GetStaticPaths = async (context) => {
const sdk = getReadySdk(); const sdk = getReadySdk();
const channels = await sdk.getVideoChannelsSlugs(); const channels = await sdk.getVideoChannelsSlugs();
const paths: GetStaticPathsResult["paths"] = []; const paths: GetStaticPathsResult["paths"] = [];
@ -159,4 +164,4 @@ export async function getStaticPaths(
paths, paths,
fallback: "blocking", fallback: "blocking",
}; };
} };

View File

@ -21,18 +21,29 @@ import { prettyDate } from "helpers/formatters";
import { filterHasAttributes } from "helpers/others"; import { filterHasAttributes } from "helpers/others";
import { getVideoThumbnailURL } from "helpers/videos"; import { getVideoThumbnailURL } from "helpers/videos";
import { useMediaHoverable } from "hooks/useMediaQuery"; import { useMediaHoverable } from "hooks/useMediaQuery";
import { GetStaticPropsContext } from "next"; import { GetStaticProps } from "next";
import { Fragment, useMemo, useState } from "react"; import { Fragment, useMemo, useState } from "react";
/*
*
* CONSTANTS
*/
const ITEM_PER_PAGE = 50;
/*
*
* PAGE
*/
interface Props extends AppStaticProps { interface Props extends AppStaticProps {
videos: NonNullable<GetVideosPreviewQuery["videos"]>["data"]; videos: NonNullable<GetVideosPreviewQuery["videos"]>["data"];
} }
const ITEM_PER_PAGE = 50; const Videos = ({ langui, videos, ...otherProps }: Props): JSX.Element => {
export default function Videos(props: Props): JSX.Element {
const { langui, videos } = props;
const hoverable = useMediaHoverable(); const hoverable = useMediaHoverable();
const [page, setPage] = useState(0);
const [keepInfoVisible, setKeepInfoVisible] = useState(true);
const paginatedVideos = useMemo(() => { const paginatedVideos = useMemo(() => {
const memo = []; const memo = [];
@ -44,9 +55,6 @@ export default function Videos(props: Props): JSX.Element {
return memo; return memo;
}, [videos]); }, [videos]);
const [page, setPage] = useState(0);
const [keepInfoVisible, setKeepInfoVisible] = useState(true);
const subPanel = useMemo( const subPanel = useMemo(
() => ( () => (
<SubPanel> <SubPanel>
@ -130,14 +138,19 @@ export default function Videos(props: Props): JSX.Element {
navTitle={langui.archives} navTitle={langui.archives}
subPanel={subPanel} subPanel={subPanel}
contentPanel={contentPanel} contentPanel={contentPanel}
{...props} langui={langui}
{...otherProps}
/> />
); );
} };
export default Videos;
export async function getStaticProps( /*
context: GetStaticPropsContext *
): Promise<{ notFound: boolean } | { props: Props }> { * NEXT DATA FETCHING
*/
export const getStaticProps: GetStaticProps = async (context) => {
const sdk = getReadySdk(); const sdk = getReadySdk();
const videos = await sdk.getVideosPreview(); const videos = await sdk.getVideosPreview();
if (!videos.videos) return { notFound: true }; if (!videos.videos) return { notFound: true };
@ -159,4 +172,4 @@ export async function getStaticProps(
return { return {
props: props, props: props,
}; };
} };

View File

@ -21,21 +21,21 @@ import { prettyDate, prettyShortenNumber } from "helpers/formatters";
import { filterHasAttributes, isDefined } from "helpers/others"; import { filterHasAttributes, 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 { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next";
GetStaticPathsContext,
GetStaticPathsResult,
GetStaticPropsContext,
} from "next";
import { useMemo } from "react"; import { useMemo } from "react";
/*
*
* PAGE
*/
interface Props extends AppStaticProps { interface Props extends AppStaticProps {
video: NonNullable< video: NonNullable<
NonNullable<GetVideoQuery["videos"]>["data"][number]["attributes"] NonNullable<GetVideoQuery["videos"]>["data"][number]["attributes"]
>; >;
} }
export default function Video(props: Props): JSX.Element { const Video = ({ langui, video, ...otherProps }: Props): JSX.Element => {
const { langui, video } = props;
const isMobile = useMediaMobile(); const isMobile = useMediaMobile();
const appLayout = useAppLayout(); const appLayout = useAppLayout();
const subPanel = useMemo( const subPanel = useMemo(
@ -201,14 +201,19 @@ export default function Video(props: Props): JSX.Element {
navTitle={langui.archives} navTitle={langui.archives}
subPanel={subPanel} subPanel={subPanel}
contentPanel={contentPanel} contentPanel={contentPanel}
{...props} langui={langui}
{...otherProps}
/> />
); );
} };
export default Video;
export async function getStaticProps( /*
context: GetStaticPropsContext *
): Promise<{ notFound: boolean } | { props: Props }> { * NEXT DATA FETCHING
*/
export const getStaticProps: GetStaticProps = async (context) => {
const sdk = getReadySdk(); const sdk = getReadySdk();
const videos = await sdk.getVideo({ const videos = await sdk.getVideo({
uid: uid:
@ -224,11 +229,11 @@ export async function getStaticProps(
return { return {
props: props, props: props,
}; };
} };
export async function getStaticPaths( // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
context: GetStaticPathsContext
): Promise<GetStaticPathsResult> { export const getStaticPaths: GetStaticPaths = async (context) => {
const sdk = getReadySdk(); const sdk = getReadySdk();
const videos = await sdk.getVideosSlugs(); const videos = await sdk.getVideosSlugs();
const paths: GetStaticPathsResult["paths"] = []; const paths: GetStaticPathsResult["paths"] = [];
@ -242,4 +247,4 @@ export async function getStaticPaths(
paths, paths,
fallback: "blocking", fallback: "blocking",
}; };
} };

View File

@ -3,14 +3,18 @@ import { PanelHeader } from "components/PanelComponents/PanelHeader";
import { SubPanel } from "components/Panels/SubPanel"; import { SubPanel } from "components/Panels/SubPanel";
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps"; import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
import { GetStaticPropsContext } from "next"; import { GetStaticProps } from "next";
import { Icon } from "components/Ico"; import { Icon } from "components/Ico";
import { useMemo } from "react"; import { useMemo } from "react";
/*
*
* PAGE
*/
interface Props extends AppStaticProps {} interface Props extends AppStaticProps {}
export default function Chronicles(props: Props): JSX.Element { const Chronicles = ({ langui, ...otherProps }: Props): JSX.Element => {
const { langui } = props;
const subPanel = useMemo( const subPanel = useMemo(
() => ( () => (
<SubPanel> <SubPanel>
@ -25,17 +29,26 @@ export default function Chronicles(props: Props): JSX.Element {
); );
return ( return (
<AppLayout navTitle={langui.chronicles} subPanel={subPanel} {...props} /> <AppLayout
navTitle={langui.chronicles}
subPanel={subPanel}
langui={langui}
{...otherProps}
/>
); );
} };
export default Chronicles;
export async function getStaticProps( /*
context: GetStaticPropsContext *
): Promise<{ notFound: boolean } | { props: Props }> { * NEXT DATA FETCHING
*/
export const getStaticProps: GetStaticProps = async (context) => {
const props: Props = { const props: Props = {
...(await getAppStaticProps(context)), ...(await getAppStaticProps(context)),
}; };
return { return {
props: props, props: props,
}; };
} };

View File

@ -2,8 +2,7 @@ import { AppLayout } from "components/AppLayout";
import { Chip } from "components/Chip"; import { Chip } from "components/Chip";
import { HorizontalLine } from "components/HorizontalLine"; import { HorizontalLine } from "components/HorizontalLine";
import { PreviewCardCTAs } from "components/Library/PreviewCardCTAs"; import { PreviewCardCTAs } from "components/Library/PreviewCardCTAs";
import { Markdawn } from "components/Markdown/Markdawn"; import { Markdawn, TableOfContents } from "components/Markdown/Markdawn";
import { TOC } from "components/Markdown/TOC";
import { import {
ReturnButton, ReturnButton,
ReturnButtonType, ReturnButtonType,
@ -35,27 +34,25 @@ import { ContentWithTranslations } from "helpers/types";
import { useMediaMobile } from "hooks/useMediaQuery"; import { useMediaMobile } from "hooks/useMediaQuery";
import { AnchorIds, useScrollTopOnChange } from "hooks/useScrollTopOnChange"; import { AnchorIds, useScrollTopOnChange } from "hooks/useScrollTopOnChange";
import { useSmartLanguage } from "hooks/useSmartLanguage"; import { useSmartLanguage } from "hooks/useSmartLanguage";
import { import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next";
GetStaticPathsContext,
GetStaticPathsResult,
GetStaticPropsContext,
} from "next";
import { Fragment, useCallback, useMemo } from "react"; import { Fragment, useCallback, useMemo } from "react";
/*
*
* PAGE
*/
interface Props extends AppStaticProps { interface Props extends AppStaticProps {
content: ContentWithTranslations; content: ContentWithTranslations;
} }
type Group = NonNullable< const Content = ({
NonNullable< langui,
NonNullable< content,
NonNullable<ContentWithTranslations["group"]>["data"] languages,
>["attributes"] currencies,
>["contents"] ...otherProps
>["data"]; }: Props): JSX.Element => {
export default function Content(props: Props): JSX.Element {
const { langui, content, languages, currencies } = props;
const isMobile = useMediaMobile(); const isMobile = useMediaMobile();
const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] = const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] =
@ -253,15 +250,14 @@ export default function Content(props: Props): JSX.Element {
position: "Bottom", position: "Bottom",
}} }}
infoAppend={ infoAppend={
<PreviewCardCTAs !isUntangibleGroupItem(
id={libraryItem.id} libraryItem.attributes.metadata?.[0]
displayCTAs={ ) && (
!isUntangibleGroupItem( <PreviewCardCTAs
libraryItem.attributes.metadata?.[0] id={libraryItem.id}
) langui={langui}
} />
langui={langui} )
/>
} }
/> />
</div> </div>
@ -277,13 +273,14 @@ export default function Content(props: Props): JSX.Element {
{selectedTranslation?.text_set?.text && ( {selectedTranslation?.text_set?.text && (
<> <>
<HorizontalLine /> <HorizontalLine />
<TOC <TableOfContents
text={selectedTranslation.text_set.text} text={selectedTranslation.text_set.text}
title={prettyinlineTitle( title={prettyinlineTitle(
selectedTranslation.pre_title, selectedTranslation.pre_title,
selectedTranslation.title, selectedTranslation.title,
selectedTranslation.subtitle selectedTranslation.subtitle
)} )}
langui={langui}
/> />
</> </>
)} )}
@ -460,14 +457,21 @@ export default function Content(props: Props): JSX.Element {
thumbnail={content.thumbnail?.data?.attributes ?? undefined} thumbnail={content.thumbnail?.data?.attributes ?? undefined}
contentPanel={contentPanel} contentPanel={contentPanel}
subPanel={subPanel} subPanel={subPanel}
{...props} currencies={currencies}
languages={languages}
langui={langui}
{...otherProps}
/> />
); );
} };
export default Content;
export async function getStaticProps( /*
context: GetStaticPropsContext *
): Promise<{ notFound: boolean } | { props: Props }> { * NEXT DATA FETCHING
*/
export const getStaticProps: GetStaticProps = async (context) => {
const sdk = getReadySdk(); const sdk = getReadySdk();
const slug = context.params?.slug ? context.params.slug.toString() : ""; const slug = context.params?.slug ? context.params.slug.toString() : "";
const content = await sdk.getContentText({ const content = await sdk.getContentText({
@ -485,11 +489,11 @@ export async function getStaticProps(
return { return {
props: props, props: props,
}; };
} };
export async function getStaticPaths( // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
context: GetStaticPathsContext
): Promise<GetStaticPathsResult> { export const getStaticPaths: GetStaticPaths = async (context) => {
const sdk = getReadySdk(); const sdk = getReadySdk();
const contents = await sdk.getContentsSlugs(); const contents = await sdk.getContentsSlugs();
const paths: GetStaticPathsResult["paths"] = []; const paths: GetStaticPathsResult["paths"] = [];
@ -505,9 +509,22 @@ export async function getStaticPaths(
paths, paths,
fallback: "blocking", fallback: "blocking",
}; };
} };
function getPreviousContent(group: Group, currentSlug: string) { /*
*
* PRIVATE METHODS
*/
type Group = NonNullable<
NonNullable<
NonNullable<
NonNullable<ContentWithTranslations["group"]>["data"]
>["attributes"]
>["contents"]
>["data"];
const getPreviousContent = (group: Group, currentSlug: string) => {
for (let index = 0; index < group.length; index += 1) { for (let index = 0; index < group.length; index += 1) {
const content = group[index]; const content = group[index];
if (content.attributes?.slug === currentSlug && index > 0) { if (content.attributes?.slug === currentSlug && index > 0) {
@ -515,9 +532,11 @@ function getPreviousContent(group: Group, currentSlug: string) {
} }
} }
return undefined; return undefined;
} };
function getNextContent(group: Group, currentSlug: string) { // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
const getNextContent = (group: Group, currentSlug: string) => {
for (let index = 0; index < group.length; index += 1) { for (let index = 0; index < group.length; index += 1) {
const content = group[index]; const content = group[index];
if (content.attributes?.slug === currentSlug && index < group.length - 1) { if (content.attributes?.slug === currentSlug && index < group.length - 1) {
@ -525,4 +544,4 @@ function getNextContent(group: Group, currentSlug: string) {
} }
} }
return undefined; return undefined;
} };

View File

@ -9,12 +9,10 @@ import {
} from "components/Panels/ContentPanel"; } from "components/Panels/ContentPanel";
import { SubPanel } from "components/Panels/SubPanel"; import { SubPanel } from "components/Panels/SubPanel";
import { TranslatedPreviewCard } from "components/PreviewCard"; import { TranslatedPreviewCard } from "components/PreviewCard";
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 { GetStaticProps } from "next";
import { GetStaticPropsContext } from "next";
import { Fragment, useState, useMemo } 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";
@ -29,33 +27,47 @@ import {
import { ContentPlaceholder } from "components/PanelComponents/ContentPlaceholder"; import { ContentPlaceholder } from "components/PanelComponents/ContentPlaceholder";
import { GetContentsQuery } from "graphql/generated"; import { GetContentsQuery } from "graphql/generated";
interface Props extends AppStaticProps { /*
contents: NonNullable<GetContentsQuery["contents"]>["data"]; *
} * CONSTANTS
*/
type GroupContentItems = Map<string, Props["contents"]>; const DEFAULT_FILTERS_STATE = {
const defaultFiltersState = {
groupingMethod: -1, groupingMethod: -1,
keepInfoVisible: false, keepInfoVisible: false,
combineRelatedContent: true, combineRelatedContent: true,
searchName: "", searchName: "",
}; };
export default function Contents(props: Props): JSX.Element { /*
const { langui, contents, languages } = props; *
* PAGE
*/
interface Props extends AppStaticProps {
contents: NonNullable<GetContentsQuery["contents"]>["data"];
}
const Contents = ({
langui,
contents,
languages,
...otherProps
}: Props): JSX.Element => {
const hoverable = useMediaHoverable(); const hoverable = useMediaHoverable();
const [groupingMethod, setGroupingMethod] = useState<number>( const [groupingMethod, setGroupingMethod] = useState<number>(
defaultFiltersState.groupingMethod DEFAULT_FILTERS_STATE.groupingMethod
); );
const [keepInfoVisible, setKeepInfoVisible] = useState( const [keepInfoVisible, setKeepInfoVisible] = useState(
defaultFiltersState.keepInfoVisible DEFAULT_FILTERS_STATE.keepInfoVisible
); );
const [combineRelatedContent, setCombineRelatedContent] = useState( const [combineRelatedContent, setCombineRelatedContent] = useState(
defaultFiltersState.combineRelatedContent DEFAULT_FILTERS_STATE.combineRelatedContent
);
const [searchName, setSearchName] = useState(
DEFAULT_FILTERS_STATE.searchName
); );
const [searchName, setSearchName] = useState(defaultFiltersState.searchName);
const effectiveCombineRelatedContent = useMemo( const effectiveCombineRelatedContent = useMemo(
() => (searchName.length > 1 ? false : combineRelatedContent), () => (searchName.length > 1 ? false : combineRelatedContent),
@ -126,10 +138,12 @@ export default function Contents(props: Props): JSX.Element {
text={langui.reset_all_filters} text={langui.reset_all_filters}
icon={Icon.Replay} icon={Icon.Replay}
onClick={() => { onClick={() => {
setSearchName(defaultFiltersState.searchName); setSearchName(DEFAULT_FILTERS_STATE.searchName);
setGroupingMethod(defaultFiltersState.groupingMethod); setGroupingMethod(DEFAULT_FILTERS_STATE.groupingMethod);
setKeepInfoVisible(defaultFiltersState.keepInfoVisible); setKeepInfoVisible(DEFAULT_FILTERS_STATE.keepInfoVisible);
setCombineRelatedContent(defaultFiltersState.combineRelatedContent); setCombineRelatedContent(
DEFAULT_FILTERS_STATE.combineRelatedContent
);
}} }}
/> />
</SubPanel> </SubPanel>
@ -147,12 +161,9 @@ export default function Contents(props: Props): JSX.Element {
const contentPanel = useMemo( const contentPanel = useMemo(
() => ( () => (
<ContentPanel width={ContentPanelWidthSizes.Full}> <ContentPanel width={ContentPanelWidthSizes.Full}>
{/* TODO: Add to langui */}
{groups.size === 0 && ( {groups.size === 0 && (
<ContentPlaceholder <ContentPlaceholder
message={ message={langui.no_results_message ?? "No results"}
"No results. You can try changing or resetting the search parameters."
}
icon={Icon.ChevronLeft} icon={Icon.ChevronLeft}
/> />
)} )}
@ -259,14 +270,20 @@ export default function Contents(props: Props): JSX.Element {
subPanel={subPanel} subPanel={subPanel}
contentPanel={contentPanel} contentPanel={contentPanel}
subPanelIcon={Icon.Search} subPanelIcon={Icon.Search}
{...props} languages={languages}
langui={langui}
{...otherProps}
/> />
); );
} };
export default Contents;
export async function getStaticProps( /*
context: GetStaticPropsContext *
): Promise<{ notFound: boolean } | { props: Props }> { * NEXT DATA FETCHING
*/
export const getStaticProps: GetStaticProps = async (context) => {
const sdk = getReadySdk(); const sdk = getReadySdk();
const contents = await sdk.getContents({ const contents = await sdk.getContents({
language_code: context.locale ?? "en", language_code: context.locale ?? "en",
@ -285,13 +302,20 @@ export async function getStaticProps(
return { return {
props: props, props: props,
}; };
} };
export function getGroups( /*
*
* PRIVATE METHODS
*/
type GroupContentItems = Map<string, Props["contents"]>;
export const getGroups = (
langui: AppStaticProps["langui"], langui: AppStaticProps["langui"],
groupByType: number, groupByType: number,
items: Props["contents"] items: Props["contents"]
): GroupContentItems { ): GroupContentItems => {
const groups: GroupContentItems = new Map(); const groups: GroupContentItems = new Map();
switch (groupByType) { switch (groupByType) {
@ -347,14 +371,16 @@ export function getGroups(
} }
} }
return mapRemoveEmptyValues(groups); return mapRemoveEmptyValues(groups);
} };
export function filterContents( // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
export const filterContents = (
contents: Props["contents"], contents: Props["contents"],
combineRelatedContent: boolean, combineRelatedContent: boolean,
searchName: string searchName: string
): Props["contents"] { ): Props["contents"] =>
return contents.filter((content) => { contents.filter((content) => {
if ( if (
combineRelatedContent && combineRelatedContent &&
content.attributes?.group?.data?.attributes?.combine === true && content.attributes?.group?.data?.attributes?.combine === true &&
@ -381,4 +407,3 @@ export function filterContents(
} }
return true; return true;
}); });
}

View File

@ -10,16 +10,19 @@ import { DevGetContentsQuery } 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 { filterDefined, filterHasAttributes } from "helpers/others"; import { filterDefined, filterHasAttributes } from "helpers/others";
import { GetStaticProps } from "next";
import { GetStaticPropsContext } from "next";
import { useMemo } from "react"; import { useMemo } from "react";
/*
*
* PAGE
*/
interface Props extends AppStaticProps { interface Props extends AppStaticProps {
contents: DevGetContentsQuery; contents: DevGetContentsQuery;
} }
export default function CheckupContents(props: Props): JSX.Element { const CheckupContents = ({ contents, ...otherProps }: Props): JSX.Element => {
const { contents } = props;
const testReport = testingContent(contents); const testReport = testingContent(contents);
const contentPanel = useMemo( const contentPanel = useMemo(
@ -82,13 +85,21 @@ export default function CheckupContents(props: Props): JSX.Element {
); );
return ( return (
<AppLayout navTitle={"Checkup"} contentPanel={contentPanel} {...props} /> <AppLayout
navTitle={"Checkup"}
contentPanel={contentPanel}
{...otherProps}
/>
); );
} };
export default CheckupContents;
export async function getStaticProps( /*
context: GetStaticPropsContext *
): Promise<{ notFound: boolean } | { props: Props }> { * NEXT DATA FETCHING
*/
export const getStaticProps: GetStaticProps = async (context) => {
const sdk = getReadySdk(); const sdk = getReadySdk();
const contents = await sdk.devGetContents(); const contents = await sdk.devGetContents();
const props: Props = { const props: Props = {
@ -98,7 +109,12 @@ export async function getStaticProps(
return { return {
props: props, props: props,
}; };
} };
/*
*
* PRIVATE METHODS
*/
type Report = { type Report = {
title: string; title: string;
@ -116,7 +132,7 @@ type ReportLine = {
frontendUrl: string; frontendUrl: string;
}; };
function testingContent(contents: Props["contents"]): Report { const testingContent = (contents: Props["contents"]): Report => {
const report: Report = { const report: Report = {
title: "Contents", title: "Contents",
lines: [], lines: [],
@ -438,4 +454,4 @@ function testingContent(contents: Props["contents"]): Report {
} }
}); });
return report; return report;
} };

View File

@ -13,15 +13,22 @@ import {
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps"; import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
import { getReadySdk } from "graphql/sdk"; import { getReadySdk } from "graphql/sdk";
import { GetStaticPropsContext } from "next"; import { GetStaticProps } from "next";
import { useMemo } from "react"; import { useMemo } from "react";
/*
*
* PAGE
*/
interface Props extends AppStaticProps { interface Props extends AppStaticProps {
libraryItems: DevGetLibraryItemsQuery; libraryItems: DevGetLibraryItemsQuery;
} }
export default function CheckupLibraryItems(props: Props): JSX.Element { const CheckupLibraryItems = ({
const { libraryItems } = props; libraryItems,
...otherProps
}: Props): JSX.Element => {
const testReport = testingLibraryItem(libraryItems); const testReport = testingLibraryItem(libraryItems);
const contentPanel = useMemo( const contentPanel = useMemo(
@ -84,13 +91,21 @@ export default function CheckupLibraryItems(props: Props): JSX.Element {
); );
return ( return (
<AppLayout navTitle={"Checkup"} contentPanel={contentPanel} {...props} /> <AppLayout
navTitle={"Checkup"}
contentPanel={contentPanel}
{...otherProps}
/>
); );
} };
export default CheckupLibraryItems;
export async function getStaticProps( /*
context: GetStaticPropsContext *
): Promise<{ notFound: boolean } | { props: Props }> { * NEXT DATA FETCHING
*/
export const getStaticProps: GetStaticProps = async (context) => {
const sdk = getReadySdk(); const sdk = getReadySdk();
const libraryItems = await sdk.devGetLibraryItems(); const libraryItems = await sdk.devGetLibraryItems();
const props: Props = { const props: Props = {
@ -100,7 +115,12 @@ export async function getStaticProps(
return { return {
props: props, props: props,
}; };
} };
/*
*
* PRIVATE METHODS
*/
type Report = { type Report = {
title: string; title: string;
@ -118,7 +138,7 @@ type ReportLine = {
frontendUrl: string; frontendUrl: string;
}; };
function testingLibraryItem(libraryItems: Props["libraryItems"]): Report { const testingLibraryItem = (libraryItems: Props["libraryItems"]): Report => {
const report: Report = { const report: Report = {
title: "Contents", title: "Contents",
lines: [], lines: [],
@ -757,4 +777,4 @@ function testingLibraryItem(libraryItems: Props["libraryItems"]): Report {
}); });
return report; return report;
} };

View File

@ -1,6 +1,6 @@
import { AppLayout } from "components/AppLayout"; import { AppLayout } from "components/AppLayout";
import { Button } from "components/Inputs/Button"; import { Button } from "components/Inputs/Button";
import { Markdawn } from "components/Markdown/Markdawn"; import { Markdawn, TableOfContents } from "components/Markdown/Markdawn";
import { import {
ContentPanel, ContentPanel,
ContentPanelWidthSizes, ContentPanelWidthSizes,
@ -8,16 +8,19 @@ import {
import { Popup } from "components/Popup"; import { Popup } from "components/Popup";
import { ToolTip } from "components/ToolTip"; import { ToolTip } from "components/ToolTip";
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps"; import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
import { GetStaticProps } from "next";
import { GetStaticPropsContext } from "next";
import { useCallback, useMemo, useRef, useState } from "react"; import { useCallback, useMemo, useRef, useState } from "react";
import TurndownService from "turndown"; import TurndownService from "turndown";
import { Icon } from "components/Ico"; import { Icon } from "components/Ico";
import { TOC } from "components/Markdown/TOC";
/*
*
* PAGE
*/
interface Props extends AppStaticProps {} interface Props extends AppStaticProps {}
export default function Editor(props: Props): JSX.Element { const Editor = ({ langui, ...otherProps }: Props): JSX.Element => {
const handleInput = useCallback((text: string) => { const handleInput = useCallback((text: string) => {
setMarkdown(text); setMarkdown(text);
}, []); }, []);
@ -444,7 +447,7 @@ export default function Editor(props: Props): JSX.Element {
</div> </div>
<div className="mt-8"> <div className="mt-8">
<TOC text={markdown} /> <TableOfContents text={markdown} langui={langui} />
</div> </div>
</ContentPanel> </ContentPanel>
), ),
@ -453,6 +456,7 @@ export default function Editor(props: Props): JSX.Element {
converterOpened, converterOpened,
handleInput, handleInput,
insert, insert,
langui,
markdown, markdown,
preline, preline,
toggleWrap, toggleWrap,
@ -464,18 +468,23 @@ export default function Editor(props: Props): JSX.Element {
<AppLayout <AppLayout
navTitle="Markdawn Editor" navTitle="Markdawn Editor"
contentPanel={contentPanel} contentPanel={contentPanel}
{...props} langui={langui}
{...otherProps}
/> />
); );
} };
export default Editor;
export async function getStaticProps( /*
context: GetStaticPropsContext *
): Promise<{ notFound: boolean } | { props: Props }> { * NEXT DATA FETCHING
*/
export const getStaticProps: GetStaticProps = async (context) => {
const props: Props = { const props: Props = {
...(await getAppStaticProps(context)), ...(await getAppStaticProps(context)),
}; };
return { return {
props: props, props: props,
}; };
} };

View File

@ -1,5 +1,4 @@
import { AppLayout } from "components/AppLayout"; import { AppLayout } from "components/AppLayout";
import { Icon } from "components/Ico";
import { Button } from "components/Inputs/Button"; import { Button } from "components/Inputs/Button";
import { ButtonGroup } from "components/Inputs/ButtonGroup"; import { ButtonGroup } from "components/Inputs/ButtonGroup";
import { import {
@ -8,42 +7,43 @@ import {
} from "components/Panels/ContentPanel"; } from "components/Panels/ContentPanel";
import { ToolTip } from "components/ToolTip"; import { ToolTip } from "components/ToolTip";
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps"; import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
import { GetStaticPropsContext } from "next"; import { GetStaticProps } from "next";
import { useCallback, useMemo, useRef, useState } from "react"; import { useCallback, useMemo, useRef, useState } from "react";
interface Props extends AppStaticProps {} /*
*
* CONSTANTS
*/
const SIZE_MULTIPLIER = 1000; const SIZE_MULTIPLIER = 1000;
function replaceSelection( /*
*
* PAGE
*/
interface Props extends AppStaticProps {}
const replaceSelection = (
text: string, text: string,
selectionStart: number, selectionStart: number,
selectionEnd: number, selectionEnd: number,
newSelectedText: string newSelectedText: string
) { ) =>
return ( text.substring(0, selectionStart) +
text.substring(0, selectionStart) + newSelectedText +
newSelectedText + text.substring(selectionEnd);
text.substring(selectionEnd)
);
}
function swapChar(char: string, swaps: string[]): string { const swapChar = (char: string, swaps: string[]): string => {
for (let index = 0; index < swaps.length; index++) { for (let index = 0; index < swaps.length; index++) {
if (char === swaps[index]) { if (char === swaps[index]) {
console.log(
"found it",
char,
" returning",
swaps[(index + 1) % swaps.length]
);
return swaps[(index + 1) % swaps.length]; return swaps[(index + 1) % swaps.length];
} }
} }
return char; return char;
} };
export default function Transcript(props: Props): JSX.Element { const Transcript = (props: Props): JSX.Element => {
const [text, setText] = useState(""); const [text, setText] = useState("");
const [fontSize, setFontSize] = useState(1); const [fontSize, setFontSize] = useState(1);
const [xOffset, setXOffset] = useState(0); const [xOffset, setXOffset] = useState(0);
@ -67,6 +67,114 @@ export default function Transcript(props: Props): JSX.Element {
} }
}, []); }, []);
const convertFullWidth = useCallback(() => {
if (textAreaRef.current) {
textAreaRef.current.value = textAreaRef.current.value
// Numbers
.replaceAll("0", "")
.replaceAll("1", "")
.replaceAll("2", "")
.replaceAll("3", "")
.replaceAll("4", "")
.replaceAll("5", "")
.replaceAll("6", "")
.replaceAll("7", "")
.replaceAll("8", "")
.replaceAll("9", "")
// Uppercase letters
.replaceAll("A", "")
.replaceAll("B", "")
.replaceAll("C", "")
.replaceAll("D", "")
.replaceAll("E", "")
.replaceAll("F", "")
.replaceAll("G", "")
.replaceAll("H", "")
.replaceAll("I", "")
.replaceAll("J", "")
.replaceAll("K", "")
.replaceAll("L", "")
.replaceAll("M", "")
.replaceAll("N", "")
.replaceAll("O", "")
.replaceAll("P", "")
.replaceAll("Q", "")
.replaceAll("R", "")
.replaceAll("S", "")
.replaceAll("T", "")
.replaceAll("U", "")
.replaceAll("V", "")
.replaceAll("W", "")
.replaceAll("X", "")
.replaceAll("Y", "")
.replaceAll("Z", "")
// Lowercase letters
.replaceAll("a", "")
.replaceAll("b", "")
.replaceAll("c", "")
.replaceAll("d", "")
.replaceAll("e", "")
.replaceAll("f", "")
.replaceAll("g", "")
.replaceAll("h", "")
.replaceAll("i", "")
.replaceAll("j", "")
.replaceAll("k", "")
.replaceAll("l", "")
.replaceAll("m", "")
.replaceAll("n", "")
.replaceAll("o", "")
.replaceAll("p", "")
.replaceAll("q", "")
.replaceAll("r", "")
.replaceAll("s", "")
.replaceAll("t", "")
.replaceAll("u", "")
.replaceAll("v", "")
.replaceAll("w", "")
.replaceAll("x", "")
.replaceAll("y", "")
.replaceAll("z", "")
// Others
.replaceAll(" ", " ")
.replaceAll(",", "")
.replaceAll(".", "")
.replaceAll(":", "")
.replaceAll(";", "")
.replaceAll("!", "")
.replaceAll("?", "")
.replaceAll('"', "")
.replaceAll("'", "")
.replaceAll("`", "")
.replaceAll("^", "")
.replaceAll("~", "")
.replaceAll("_", "_")
.replaceAll("&", "")
.replaceAll("@", "")
.replaceAll("#", "")
.replaceAll("%", "")
.replaceAll("+", "")
.replaceAll("-", "")
.replaceAll("*", "")
.replaceAll("=", "")
.replaceAll("<", "")
.replaceAll(">", "")
.replaceAll("(", "")
.replaceAll(")", "")
.replaceAll("[", "")
.replaceAll("]", "")
.replaceAll("{", "")
.replaceAll("}", "")
.replaceAll("|", "")
.replaceAll("$", "")
.replaceAll("£", "£")
.replaceAll("¢", "¢")
.replaceAll("₩", "₩")
.replaceAll("¥", "¥");
updateDisplayedText();
}
}, [updateDisplayedText]);
const convertPunctuation = useCallback(() => { const convertPunctuation = useCallback(() => {
if (textAreaRef.current) { if (textAreaRef.current) {
textAreaRef.current.value = textAreaRef.current.value textAreaRef.current.value = textAreaRef.current.value
@ -76,7 +184,9 @@ export default function Transcript(props: Props): JSX.Element {
.replaceAll(".", "。") .replaceAll(".", "。")
.replaceAll(",", "、") .replaceAll(",", "、")
.replaceAll("?", "") .replaceAll("?", "")
.replaceAll("!", ""); .replaceAll("!", "")
.replaceAll(":", "")
.replaceAll("~", "");
updateDisplayedText(); updateDisplayedText();
} }
}, [updateDisplayedText]); }, [updateDisplayedText]);
@ -320,14 +430,19 @@ export default function Transcript(props: Props): JSX.Element {
} }
></input> ></input>
</div> </div>
<ToolTip content="Automatically convert punctuations"> <ToolTip content="Automatically convert Western punctuations to Japanese ones.">
<Button icon={Icon.QuestionMark} onClick={convertPunctuation} /> <Button text=". ⟹ 。" onClick={convertPunctuation} />
</ToolTip> </ToolTip>
<Button text={"か ⟺ が"} onClick={toggleDakuten} /> <ToolTip content="Swap a kana for one of its variant (different diacritics).">
<Button text={"つ ⟺ っ"} onClick={toggleSmallForm} /> <Button text="か ⟺ が" onClick={toggleDakuten} />
<Button text={"。"} onClick={() => insert("。")} /> </ToolTip>
<Button text={""} onClick={() => insert("")} /> <ToolTip content="Toggle a kana's small form">
<Button text={""} onClick={() => insert("")} /> <Button text="つ ⟺ っ" onClick={toggleSmallForm} />
</ToolTip>
<ToolTip content="Convert standard characters to their full width variant.">
<Button text="123 ⟹ " onClick={convertFullWidth} />
</ToolTip>
<ToolTip <ToolTip
content={ content={
<div className="grid gap-2"> <div className="grid gap-2">
@ -351,8 +466,8 @@ export default function Transcript(props: Props): JSX.Element {
/> />
<ButtonGroup <ButtonGroup
buttonsProps={[ buttonsProps={[
{ text: "", onClick: () => insert("") }, { text: "", onClick: () => insert("") },
{ text: "", onClick: () => insert("") }, { text: "", onClick: () => insert("") },
]} ]}
/> />
<ButtonGroup <ButtonGroup
@ -361,6 +476,18 @@ export default function Transcript(props: Props): JSX.Element {
{ text: "〟", onClick: () => insert("〟") }, { text: "〟", onClick: () => insert("〟") },
]} ]}
/> />
<ButtonGroup
buttonsProps={[
{ text: "", onClick: () => insert("") },
{ text: "", onClick: () => insert("") },
]}
/>
<ButtonGroup
buttonsProps={[
{ text: "⦅", onClick: () => insert("⦅") },
{ text: "⦆", onClick: () => insert("⦆") },
]}
/>
<ButtonGroup <ButtonGroup
buttonsProps={[ buttonsProps={[
{ text: "〈", onClick: () => insert("〈") }, { text: "〈", onClick: () => insert("〈") },
@ -373,19 +500,57 @@ export default function Transcript(props: Props): JSX.Element {
{ text: "》", onClick: () => insert("》") }, { text: "》", onClick: () => insert("》") },
]} ]}
/> />
<ButtonGroup
buttonsProps={[
{ text: "", onClick: () => insert("") },
{ text: "", onClick: () => insert("") },
]}
/>
<ButtonGroup
buttonsProps={[
{ text: "", onClick: () => insert("") },
{ text: "", onClick: () => insert("") },
]}
/>
<ButtonGroup
buttonsProps={[
{ text: "", onClick: () => insert("") },
{ text: "", onClick: () => insert("") },
]}
/>
<ButtonGroup
buttonsProps={[
{ text: "〘", onClick: () => insert("〘") },
{ text: "〙", onClick: () => insert("〙") },
]}
/>
</div> </div>
} }
> >
<Button text={"Quotations"} /> <Button text={"Quotations"} />
</ToolTip> </ToolTip>
<ToolTip
<Button text={"⋯"} onClick={() => insert("⋯")} /> content={
<Button text={"※"} onClick={() => insert("※")} /> <div className="grid gap-2">
<Button text={'" "'} onClick={() => insert(" ")} /> <Button text={"。"} onClick={() => insert("。")} />
<Button text={""} onClick={() => insert("")} />
<Button text={""} onClick={() => insert("")} />
<Button text={"⋯"} onClick={() => insert("⋯")} />
<Button text={"※"} onClick={() => insert("※")} />
<Button text={"♪"} onClick={() => insert("♪")} />
<Button text={"・"} onClick={() => insert("・")} />
<Button text={""} onClick={() => insert("")} />
<Button text={'" "'} onClick={() => insert(" ")} />
</div>
}
>
<Button text="Insert" />
</ToolTip>
</div> </div>
</ContentPanel> </ContentPanel>
), ),
[ [
convertFullWidth,
convertPunctuation, convertPunctuation,
fontSize, fontSize,
insert, insert,
@ -407,15 +572,19 @@ export default function Transcript(props: Props): JSX.Element {
contentPanelScroolbar={false} contentPanelScroolbar={false}
/> />
); );
} };
export default Transcript;
export async function getStaticProps( /*
context: GetStaticPropsContext *
): Promise<{ notFound: boolean } | { props: Props }> { * NEXT DATA FETCHING
*/
export const getStaticProps: GetStaticProps = async (context) => {
const props: Props = { const props: Props = {
...(await getAppStaticProps(context)), ...(await getAppStaticProps(context)),
}; };
return { return {
props: props, props: props,
}; };
} };

View File

@ -4,30 +4,44 @@ import {
PostStaticProps, PostStaticProps,
} from "graphql/getPostStaticProps"; } from "graphql/getPostStaticProps";
export default function Home(props: PostStaticProps): JSX.Element { /*
const { post, langui, languages, currencies } = props; *
return ( * PAGE
<PostPage */
currencies={currencies}
languages={languages} const Home = ({
langui={langui} post,
post={post} langui,
prependBody={ languages,
<div className="grid w-full place-content-center place-items-center gap-5 text-center"> currencies,
<div }: PostStaticProps): JSX.Element => (
className="aspect-square w-32 bg-black [mask:url('/icons/accords.svg')] <PostPage
currencies={currencies}
languages={languages}
langui={langui}
post={post}
prependBody={
<div className="grid w-full place-content-center place-items-center gap-5 text-center">
<div
className="aspect-square w-32 bg-black [mask:url('/icons/accords.svg')]
[mask-size:contain] [mask-repeat:no-repeat] [mask-position:center] mobile:w-[50vw]" [mask-size:contain] [mask-repeat:no-repeat] [mask-position:center] mobile:w-[50vw]"
/> />
<h1 className="mb-0 text-5xl">Accord&rsquo;s Library</h1> <h1 className="mb-0 text-5xl">Accord&rsquo;s Library</h1>
<h2 className="-mt-5 text-xl"> <h2 className="-mt-5 text-xl">
Discover Analyze Translate Archive Discover Analyze Translate Archive
</h2> </h2>
</div> </div>
} }
displayTitle={false} displayTitle={false}
displayLanguageSwitcher displayLanguageSwitcher
/> />
); );
}
export default Home;
/*
*
* NEXT DATA FETCHING
*/
export const getStaticProps = getPostStaticProps("home"); export const getStaticProps = getPostStaticProps("home");

View File

@ -42,14 +42,9 @@ import {
isDefinedAndNotEmpty, isDefinedAndNotEmpty,
sortContent, sortContent,
} from "helpers/others"; } from "helpers/others";
import { useLightBox } from "hooks/useLightBox"; import { useLightBox } from "hooks/useLightBox";
import { AnchorIds, useScrollTopOnChange } from "hooks/useScrollTopOnChange"; import { AnchorIds, useScrollTopOnChange } from "hooks/useScrollTopOnChange";
import { import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next";
GetStaticPathsContext,
GetStaticPathsResult,
GetStaticPropsContext,
} from "next";
import { Fragment, useCallback, useMemo, useState } from "react"; import { Fragment, useCallback, useMemo, useState } from "react";
import { isUntangibleGroupItem } from "helpers/libraryItem"; import { isUntangibleGroupItem } from "helpers/libraryItem";
import { useMediaHoverable } from "hooks/useMediaQuery"; import { useMediaHoverable } from "hooks/useMediaQuery";
@ -58,6 +53,12 @@ import { useToggle } from "hooks/useToggle";
import { Ico, Icon } from "components/Ico"; import { Ico, Icon } from "components/Ico";
import { cJoin, cIf } from "helpers/className"; import { cJoin, cIf } from "helpers/className";
import { useSmartLanguage } from "hooks/useSmartLanguage"; import { useSmartLanguage } from "hooks/useSmartLanguage";
import { getDescription } from "helpers/description";
/*
*
* PAGE
*/
interface Props extends AppStaticProps { interface Props extends AppStaticProps {
item: NonNullable< item: NonNullable<
@ -70,8 +71,14 @@ interface Props extends AppStaticProps {
>["data"][number]["id"]; >["data"][number]["id"];
} }
export default function LibrarySlug(props: Props): JSX.Element { const LibrarySlug = ({
const { item, itemId, langui, currencies, languages } = props; item,
itemId,
langui,
currencies,
languages,
...otherProps
}: Props): JSX.Element => {
const appLayout = useAppLayout(); const appLayout = useAppLayout();
const hoverable = useMediaHoverable(); const hoverable = useMediaHoverable();
const [openLightBox, LightBox] = useLightBox(); const [openLightBox, LightBox] = useLightBox();
@ -192,12 +199,11 @@ export default function LibrarySlug(props: Props): JSX.Element {
)} )}
</div> </div>
<PreviewCardCTAs {!isUntangibleGroupItem(item.metadata?.[0]) &&
id={itemId} isDefinedAndNotEmpty(itemId) && (
displayCTAs={!isUntangibleGroupItem(item.metadata?.[0])} <PreviewCardCTAs id={itemId} langui={langui} expand />
langui={langui} )}
expand
/>
{item.descriptions?.[0] && ( {item.descriptions?.[0] && (
<p className="text-justify"> <p className="text-justify">
{item.descriptions[0].description} {item.descriptions[0].description}
@ -479,15 +485,9 @@ export default function LibrarySlug(props: Props): JSX.Element {
position: "Bottom", position: "Bottom",
}} }}
infoAppend={ infoAppend={
<PreviewCardCTAs !isUntangibleGroupItem(
id={subitem.id} subitem.attributes.metadata?.[0]
langui={langui} ) && <PreviewCardCTAs id={subitem.id} langui={langui} />
displayCTAs={
!isUntangibleGroupItem(
subitem.attributes.metadata?.[0]
)
}
/>
} }
/> />
</Fragment> </Fragment>
@ -598,15 +598,25 @@ export default function LibrarySlug(props: Props): JSX.Element {
contentPanel={contentPanel} contentPanel={contentPanel}
subPanel={subPanel} subPanel={subPanel}
thumbnail={item.thumbnail?.data?.attributes ?? undefined} thumbnail={item.thumbnail?.data?.attributes ?? undefined}
description={item.descriptions?.[0]?.description ?? undefined} description={getDescription({
{...props} langui,
description: item.descriptions?.[0]?.description,
})}
currencies={currencies}
languages={languages}
langui={langui}
{...otherProps}
/> />
); );
} };
export default LibrarySlug;
export async function getStaticProps( /*
context: GetStaticPropsContext *
): Promise<{ notFound: boolean } | { props: Props }> { * NEXT DATA FETCHING
*/
export const getStaticProps: GetStaticProps = async (context) => {
const sdk = getReadySdk(); const sdk = getReadySdk();
const item = await sdk.getLibraryItem({ const item = await sdk.getLibraryItem({
slug: slug:
@ -625,11 +635,11 @@ export async function getStaticProps(
return { return {
props: props, props: props,
}; };
} };
export async function getStaticPaths( // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
context: GetStaticPathsContext
): Promise<GetStaticPathsResult> { export const getStaticPaths: GetStaticPaths = async (context) => {
const sdk = getReadySdk(); const sdk = getReadySdk();
const libraryItems = await sdk.getLibraryItemsSlugs(); const libraryItems = await sdk.getLibraryItemsSlugs();
const paths: GetStaticPathsResult["paths"] = []; const paths: GetStaticPathsResult["paths"] = [];
@ -638,12 +648,16 @@ export async function getStaticPaths(
paths.push({ params: { slug: item.attributes.slug }, locale: local }) paths.push({ params: { slug: item.attributes.slug }, locale: local })
); );
}); });
return { return {
paths, paths,
fallback: "blocking", fallback: "blocking",
}; };
} };
/*
*
* PRIVATE COMPONENTS
*/
interface ContentLineProps { interface ContentLineProps {
content?: { content?: {
@ -665,17 +679,15 @@ interface ContentLineProps {
hasScanSet: boolean; hasScanSet: boolean;
} }
export function ContentLine(props: ContentLineProps): JSX.Element { const ContentLine = ({
const { rangeStart,
rangeStart, content,
content, langui,
langui, languages,
languages, hasScanSet,
hasScanSet, slug,
slug, parentSlug,
parentSlug, }: ContentLineProps): JSX.Element => {
} = props;
const [opened, setOpened] = useState(false); const [opened, setOpened] = useState(false);
const toggleOpened = useToggle(setOpened); const toggleOpened = useToggle(setOpened);
@ -753,6 +765,4 @@ export function ContentLine(props: ContentLineProps): JSX.Element {
</div> </div>
</div> </div>
); );
};
return <></>;
}

View File

@ -18,13 +18,14 @@ import { prettyinlineTitle, prettySlug } from "helpers/formatters";
import { filterHasAttributes, isDefined, sortContent } from "helpers/others"; import { filterHasAttributes, isDefined, sortContent } from "helpers/others";
import { useLightBox } from "hooks/useLightBox"; import { useLightBox } from "hooks/useLightBox";
import { import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next";
GetStaticPathsContext,
GetStaticPathsResult,
GetStaticPropsContext,
} from "next";
import { Fragment, useMemo } from "react"; import { Fragment, useMemo } from "react";
/*
*
* PAGE
*/
interface Props extends AppStaticProps { interface Props extends AppStaticProps {
item: NonNullable< item: NonNullable<
NonNullable< NonNullable<
@ -36,8 +37,12 @@ interface Props extends AppStaticProps {
>; >;
} }
export default function LibrarySlug(props: Props): JSX.Element { const LibrarySlug = ({
const { item, langui, languages } = props; item,
langui,
languages,
...otherProps
}: Props): JSX.Element => {
const [openLightBox, LightBox] = useLightBox(); const [openLightBox, LightBox] = useLightBox();
sortContent(item.contents); sortContent(item.contents);
@ -129,14 +134,20 @@ export default function LibrarySlug(props: Props): JSX.Element {
contentPanel={contentPanel} contentPanel={contentPanel}
subPanel={subPanel} subPanel={subPanel}
thumbnail={item.thumbnail?.data?.attributes ?? undefined} thumbnail={item.thumbnail?.data?.attributes ?? undefined}
{...props} languages={languages}
langui={langui}
{...otherProps}
/> />
); );
} };
export default LibrarySlug;
export async function getStaticProps( /*
context: GetStaticPropsContext *
): Promise<{ notFound: boolean } | { props: Props }> { * NEXT DATA FETCHING
*/
export const getStaticProps: GetStaticProps = async (context) => {
const sdk = getReadySdk(); const sdk = getReadySdk();
const item = await sdk.getLibraryItemScans({ const item = await sdk.getLibraryItemScans({
slug: slug:
@ -155,11 +166,11 @@ export async function getStaticProps(
return { return {
props: props, props: props,
}; };
} };
export async function getStaticPaths( // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
context: GetStaticPathsContext
): Promise<GetStaticPathsResult> { export const getStaticPaths: GetStaticPaths = async (context) => {
const sdk = getReadySdk(); const sdk = getReadySdk();
const libraryItems = await sdk.getLibraryItemsSlugs({}); const libraryItems = await sdk.getLibraryItemsSlugs({});
const paths: GetStaticPathsResult["paths"] = []; const paths: GetStaticPathsResult["paths"] = [];
@ -173,4 +184,4 @@ export async function getStaticPaths(
paths, paths,
fallback: "blocking", fallback: "blocking",
}; };
} };

View File

@ -13,7 +13,7 @@ import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
import { getReadySdk } from "graphql/sdk"; import { getReadySdk } from "graphql/sdk";
import { prettyItemSubType } from "helpers/formatters"; import { prettyItemSubType } from "helpers/formatters";
import { LibraryItemUserStatus } from "helpers/types"; import { LibraryItemUserStatus } from "helpers/types";
import { GetStaticPropsContext } from "next"; import { GetStaticProps } from "next";
import { Fragment, useState, useMemo } 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";
@ -38,11 +38,12 @@ import {
} from "helpers/others"; } from "helpers/others";
import { ContentPlaceholder } from "components/PanelComponents/ContentPlaceholder"; import { ContentPlaceholder } from "components/PanelComponents/ContentPlaceholder";
interface Props extends AppStaticProps { /*
items: NonNullable<GetLibraryItemsPreviewQuery["libraryItems"]>["data"]; *
} * CONSTANTS
*/
const defaultFiltersState = { const DEFAULT_FILTERS_STATE = {
searchName: "", searchName: "",
showSubitems: false, showSubitems: false,
showPrimaryItems: true, showPrimaryItems: true,
@ -53,33 +54,48 @@ const defaultFiltersState = {
filterUserStatus: undefined, filterUserStatus: undefined,
}; };
export default function Library(props: Props): JSX.Element { /*
const { langui, items: libraryItems, currencies } = props; *
* PAGE
*/
interface Props extends AppStaticProps {
items: NonNullable<GetLibraryItemsPreviewQuery["libraryItems"]>["data"];
}
const Library = ({
langui,
items: libraryItems,
currencies,
...otherProps
}: Props): JSX.Element => {
const appLayout = useAppLayout(); const appLayout = useAppLayout();
const hoverable = useMediaHoverable(); const hoverable = useMediaHoverable();
const [searchName, setSearchName] = useState(defaultFiltersState.searchName); const [searchName, setSearchName] = useState(
DEFAULT_FILTERS_STATE.searchName
);
const [showSubitems, setShowSubitems] = useState<boolean>( const [showSubitems, setShowSubitems] = useState<boolean>(
defaultFiltersState.showSubitems DEFAULT_FILTERS_STATE.showSubitems
); );
const [showPrimaryItems, setShowPrimaryItems] = useState<boolean>( const [showPrimaryItems, setShowPrimaryItems] = useState<boolean>(
defaultFiltersState.showPrimaryItems DEFAULT_FILTERS_STATE.showPrimaryItems
); );
const [showSecondaryItems, setShowSecondaryItems] = useState<boolean>( const [showSecondaryItems, setShowSecondaryItems] = useState<boolean>(
defaultFiltersState.showSecondaryItems DEFAULT_FILTERS_STATE.showSecondaryItems
); );
const [sortingMethod, setSortingMethod] = useState<number>( const [sortingMethod, setSortingMethod] = useState<number>(
defaultFiltersState.sortingMethod DEFAULT_FILTERS_STATE.sortingMethod
); );
const [groupingMethod, setGroupingMethod] = useState<number>( const [groupingMethod, setGroupingMethod] = useState<number>(
defaultFiltersState.groupingMethod DEFAULT_FILTERS_STATE.groupingMethod
); );
const [keepInfoVisible, setKeepInfoVisible] = useState( const [keepInfoVisible, setKeepInfoVisible] = useState(
defaultFiltersState.keepInfoVisible DEFAULT_FILTERS_STATE.keepInfoVisible
); );
const [filterUserStatus, setFilterUserStatus] = useState< const [filterUserStatus, setFilterUserStatus] = useState<
LibraryItemUserStatus | undefined LibraryItemUserStatus | undefined
>(defaultFiltersState.filterUserStatus); >(DEFAULT_FILTERS_STATE.filterUserStatus);
const filteredItems = useMemo( const filteredItems = useMemo(
() => () =>
@ -193,7 +209,6 @@ export default function Library(props: Props): JSX.Element {
/> />
)} )}
{/* TODO: Add "All" to langui */}
<ButtonGroup <ButtonGroup
className="mt-4" className="mt-4"
buttonsProps={[ buttonsProps={[
@ -217,7 +232,7 @@ export default function Library(props: Props): JSX.Element {
}, },
{ {
tooltip: langui.only_display_unmarked_items, tooltip: langui.only_display_unmarked_items,
text: "All", text: langui.all,
onClick: () => setFilterUserStatus(undefined), onClick: () => setFilterUserStatus(undefined),
active: isUndefined(filterUserStatus), active: isUndefined(filterUserStatus),
}, },
@ -229,14 +244,14 @@ export default function Library(props: Props): JSX.Element {
text={langui.reset_all_filters} text={langui.reset_all_filters}
icon={Icon.Replay} icon={Icon.Replay}
onClick={() => { onClick={() => {
setSearchName(defaultFiltersState.searchName); setSearchName(DEFAULT_FILTERS_STATE.searchName);
setShowSubitems(defaultFiltersState.showSubitems); setShowSubitems(DEFAULT_FILTERS_STATE.showSubitems);
setShowPrimaryItems(defaultFiltersState.showPrimaryItems); setShowPrimaryItems(DEFAULT_FILTERS_STATE.showPrimaryItems);
setShowSecondaryItems(defaultFiltersState.showSecondaryItems); setShowSecondaryItems(DEFAULT_FILTERS_STATE.showSecondaryItems);
setSortingMethod(defaultFiltersState.sortingMethod); setSortingMethod(DEFAULT_FILTERS_STATE.sortingMethod);
setGroupingMethod(defaultFiltersState.groupingMethod); setGroupingMethod(DEFAULT_FILTERS_STATE.groupingMethod);
setKeepInfoVisible(defaultFiltersState.keepInfoVisible); setKeepInfoVisible(DEFAULT_FILTERS_STATE.keepInfoVisible);
setFilterUserStatus(defaultFiltersState.filterUserStatus); setFilterUserStatus(DEFAULT_FILTERS_STATE.filterUserStatus);
}} }}
/> />
</SubPanel> </SubPanel>
@ -258,12 +273,9 @@ export default function Library(props: Props): JSX.Element {
const contentPanel = useMemo( const contentPanel = useMemo(
() => ( () => (
<ContentPanel width={ContentPanelWidthSizes.Full}> <ContentPanel width={ContentPanelWidthSizes.Full}>
{/* TODO: Add to langui */}
{groups.size === 0 && ( {groups.size === 0 && (
<ContentPlaceholder <ContentPlaceholder
message={ message={langui.no_results_message ?? "No results"}
"No results. You can try changing or resetting the search parameters."
}
icon={Icon.ChevronLeft} icon={Icon.ChevronLeft}
/> />
)} )}
@ -314,13 +326,9 @@ export default function Library(props: Props): JSX.Element {
position: "Bottom", position: "Bottom",
}} }}
infoAppend={ infoAppend={
<PreviewCardCTAs !isUntangibleGroupItem(item.attributes.metadata?.[0]) && (
id={item.id} <PreviewCardCTAs id={item.id} langui={langui} />
displayCTAs={ )
!isUntangibleGroupItem(item.attributes.metadata?.[0])
}
langui={langui}
/>
} }
/> />
</Fragment> </Fragment>
@ -339,14 +347,20 @@ export default function Library(props: Props): JSX.Element {
subPanel={subPanel} subPanel={subPanel}
contentPanel={contentPanel} contentPanel={contentPanel}
subPanelIcon={Icon.Search} subPanelIcon={Icon.Search}
{...props} currencies={currencies}
langui={langui}
{...otherProps}
/> />
); );
} };
export default Library;
export async function getStaticProps( /*
context: GetStaticPropsContext *
): Promise<{ notFound: boolean } | { props: Props }> { * NEXT DATA FETCHING
*/
export const getStaticProps: GetStaticProps = async (context) => {
const sdk = getReadySdk(); const sdk = getReadySdk();
const items = await sdk.getLibraryItemsPreview({ const items = await sdk.getLibraryItemsPreview({
language_code: context.locale ?? "en", language_code: context.locale ?? "en",
@ -359,4 +373,4 @@ export async function getStaticProps(
return { return {
props: props, props: props,
}; };
} };

View File

@ -3,15 +3,19 @@ import { PanelHeader } from "components/PanelComponents/PanelHeader";
import { SubPanel } from "components/Panels/SubPanel"; import { SubPanel } from "components/Panels/SubPanel";
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps"; import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
import { GetStaticPropsContext } from "next"; import { GetStaticProps } from "next";
import { Icon } from "components/Ico"; import { Icon } from "components/Ico";
import { useMemo } from "react";
/*
*
* PAGE
*/
interface Props extends AppStaticProps {} interface Props extends AppStaticProps {}
export default function Merch(props: Props): JSX.Element { const Merch = ({ langui, ...otherProps }: Props): JSX.Element => (
const { langui } = props; <AppLayout
const subPanel = useMemo( navTitle={langui.merch}
() => ( subPanel={
<SubPanel> <SubPanel>
<PanelHeader <PanelHeader
icon={Icon.Store} icon={Icon.Store}
@ -19,20 +23,23 @@ export default function Merch(props: Props): JSX.Element {
description={langui.merch_description} description={langui.merch_description}
/> />
</SubPanel> </SubPanel>
), }
[langui] langui={langui}
); {...otherProps}
/>
);
export default Merch;
return <AppLayout navTitle={langui.merch} subPanel={subPanel} {...props} />; /*
} *
* NEXT DATA FETCHING
*/
export async function getStaticProps( export const getStaticProps: GetStaticProps = async (context) => {
context: GetStaticPropsContext
): Promise<{ notFound: boolean } | { props: Props }> {
const props: Props = { const props: Props = {
...(await getAppStaticProps(context)), ...(await getAppStaticProps(context)),
}; };
return { return {
props: props, props: props,
}; };
} };

View File

@ -6,45 +6,43 @@ import {
} from "graphql/getPostStaticProps"; } from "graphql/getPostStaticProps";
import { getReadySdk } from "graphql/sdk"; import { getReadySdk } from "graphql/sdk";
import { filterHasAttributes, isDefined } from "helpers/others"; import { filterHasAttributes, isDefined } from "helpers/others";
import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next";
import { /*
GetStaticPathsContext, *
GetStaticPathsResult, * PAGE
GetStaticPropsContext, */
} from "next";
interface Props extends AppStaticProps, PostStaticProps {} interface Props extends AppStaticProps, PostStaticProps {}
export default function LibrarySlug(props: Props): JSX.Element { const LibrarySlug = (props: Props): JSX.Element => (
const { post, langui, languages, currencies } = props; <PostPage
return ( returnHref="/news"
<PostPage returnTitle={props.langui.news}
currencies={currencies} displayCredits
languages={languages} displayThumbnailHeader
langui={langui} displayToc
post={post} {...props}
returnHref="/news" />
returnTitle={langui.news} );
displayCredits export default LibrarySlug;
displayThumbnailHeader
displayToc
/>
);
}
export async function getStaticProps( /*
context: GetStaticPropsContext *
): Promise<{ notFound: boolean } | { props: Props }> { * NEXT DATA FETCHING
*/
export const getStaticProps: GetStaticProps = async (context) => {
const slug = const slug =
context.params && isDefined(context.params.slug) context.params && isDefined(context.params.slug)
? context.params.slug.toString() ? context.params.slug.toString()
: ""; : "";
return await getPostStaticProps(slug)(context); return await getPostStaticProps(slug)(context);
} };
export async function getStaticPaths( // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
context: GetStaticPathsContext
): Promise<GetStaticPathsResult> { export const getStaticPaths: GetStaticPaths = async (context) => {
const sdk = getReadySdk(); const sdk = getReadySdk();
const posts = await sdk.getPostsSlugs(); const posts = await sdk.getPostsSlugs();
const paths: GetStaticPathsResult["paths"] = []; const paths: GetStaticPathsResult["paths"] = [];
@ -58,4 +56,4 @@ export async function getStaticPaths(
paths, paths,
fallback: "blocking", fallback: "blocking",
}; };
} };

View File

@ -12,7 +12,7 @@ import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
import { getReadySdk } from "graphql/sdk"; import { getReadySdk } from "graphql/sdk";
import { prettyDate, prettySlug } from "helpers/formatters"; import { prettyDate, prettySlug } from "helpers/formatters";
import { GetStaticPropsContext } from "next"; import { GetStaticProps } from "next";
import { Fragment, useMemo, 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";
@ -21,24 +21,32 @@ import { Button } from "components/Inputs/Button";
import { useMediaHoverable } from "hooks/useMediaQuery"; import { useMediaHoverable } from "hooks/useMediaQuery";
import { filterHasAttributes } from "helpers/others"; import { filterHasAttributes } from "helpers/others";
interface Props extends AppStaticProps { /*
posts: NonNullable<GetPostsPreviewQuery["posts"]>["data"]; *
} * CONSTANTS
*/
const defaultFiltersState = { const DEFAULT_FILTERS_STATE = {
searchName: "", searchName: "",
keepInfoVisible: true, keepInfoVisible: true,
}; };
export default function News(props: Props): JSX.Element { /*
const { langui } = props; *
const posts = sortPosts(props.posts); * PAGE
*/
interface Props extends AppStaticProps {
posts: NonNullable<GetPostsPreviewQuery["posts"]>["data"];
}
const News = ({ langui, posts, ...otherProps }: Props): JSX.Element => {
const hoverable = useMediaHoverable(); const hoverable = useMediaHoverable();
const [searchName, setSearchName] = useState(
const [searchName, setSearchName] = useState(defaultFiltersState.searchName); DEFAULT_FILTERS_STATE.searchName
);
const [keepInfoVisible, setKeepInfoVisible] = useState( const [keepInfoVisible, setKeepInfoVisible] = useState(
defaultFiltersState.keepInfoVisible DEFAULT_FILTERS_STATE.keepInfoVisible
); );
const filteredItems = useMemo( const filteredItems = useMemo(
@ -76,8 +84,8 @@ export default function News(props: Props): JSX.Element {
text={langui.reset_all_filters} text={langui.reset_all_filters}
icon={Icon.Replay} icon={Icon.Replay}
onClick={() => { onClick={() => {
setSearchName(defaultFiltersState.searchName); setSearchName(DEFAULT_FILTERS_STATE.searchName);
setKeepInfoVisible(defaultFiltersState.keepInfoVisible); setKeepInfoVisible(DEFAULT_FILTERS_STATE.keepInfoVisible);
}} }}
/> />
</SubPanel> </SubPanel>
@ -127,14 +135,19 @@ export default function News(props: Props): JSX.Element {
subPanel={subPanel} subPanel={subPanel}
contentPanel={contentPanel} contentPanel={contentPanel}
subPanelIcon={Icon.Search} subPanelIcon={Icon.Search}
{...props} langui={langui}
{...otherProps}
/> />
); );
} };
export default News;
export async function getStaticProps( /*
context: GetStaticPropsContext *
): Promise<{ notFound: boolean } | { props: Props }> { * NEXT DATA FETCHING
*/
export const getStaticProps: GetStaticProps = async (context) => {
const sdk = getReadySdk(); const sdk = getReadySdk();
const posts = await sdk.getPostsPreview({ const posts = await sdk.getPostsPreview({
language_code: context.locale ?? "en", language_code: context.locale ?? "en",
@ -142,25 +155,31 @@ export async function getStaticProps(
if (!posts.posts) return { notFound: true }; if (!posts.posts) return { notFound: true };
const props: Props = { const props: Props = {
...(await getAppStaticProps(context)), ...(await getAppStaticProps(context)),
posts: posts.posts.data, posts: sortPosts(posts.posts.data),
}; };
return { return {
props: props, props: props,
}; };
} };
function sortPosts(posts: Props["posts"]): Props["posts"] { /*
return posts *
* PRIVATE METHODS
*/
const sortPosts = (posts: Props["posts"]): Props["posts"] =>
posts
.sort((a, b) => { .sort((a, b) => {
const dateA = a.attributes?.date ? prettyDate(a.attributes.date) : "9999"; const dateA = a.attributes?.date ? prettyDate(a.attributes.date) : "9999";
const dateB = b.attributes?.date ? prettyDate(b.attributes.date) : "9999"; const dateB = b.attributes?.date ? prettyDate(b.attributes.date) : "9999";
return dateA.localeCompare(dateB); return dateA.localeCompare(dateB);
}) })
.reverse(); .reverse();
}
function filterItems(posts: Props["posts"], searchName: string) { // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
return posts.filter((post) => {
const filterItems = (posts: Props["posts"], searchName: string) =>
posts.filter((post) => {
if (searchName.length > 1) { if (searchName.length > 1) {
if ( if (
post.attributes?.translations?.[0]?.title post.attributes?.translations?.[0]?.title
@ -173,4 +192,3 @@ function filterItems(posts: Props["posts"], searchName: string) {
} }
return true; return true;
}); });
}

View File

@ -21,20 +21,19 @@ import {
} from "helpers/others"; } from "helpers/others";
import { WikiPageWithTranslations } from "helpers/types"; import { WikiPageWithTranslations } from "helpers/types";
import { useSmartLanguage } from "hooks/useSmartLanguage"; import { useSmartLanguage } from "hooks/useSmartLanguage";
import { import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next";
GetStaticPathsContext,
GetStaticPathsResult,
GetStaticPropsContext,
} from "next";
import { useCallback, useMemo } from "react"; import { useCallback, useMemo } from "react";
interface Props extends AppStaticProps { interface Props extends AppStaticProps {
page: WikiPageWithTranslations; page: WikiPageWithTranslations;
} }
export default function WikiPage(props: Props): JSX.Element { const WikiPage = ({
const { page, langui, languages } = props; page,
langui,
languages,
...otherProps
}: Props): JSX.Element => {
const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] = const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] =
useSmartLanguage({ useSmartLanguage({
items: page.translations, items: page.translations,
@ -143,14 +142,15 @@ export default function WikiPage(props: Props): JSX.Element {
navTitle={langui.news} navTitle={langui.news}
subPanel={subPanel} subPanel={subPanel}
contentPanel={contentPanel} contentPanel={contentPanel}
{...props} languages={languages}
langui={langui}
{...otherProps}
/> />
); );
} };
export default WikiPage;
export async function getStaticProps( export const getStaticProps: GetStaticProps = async (context) => {
context: GetStaticPropsContext
): Promise<{ notFound: boolean } | { props: Props }> {
const sdk = getReadySdk(); const sdk = getReadySdk();
const slug = const slug =
context.params && isDefined(context.params.slug) context.params && isDefined(context.params.slug)
@ -169,11 +169,9 @@ export async function getStaticProps(
return { return {
props: props, props: props,
}; };
} };
export async function getStaticPaths( export const getStaticPaths: GetStaticPaths = async (context) => {
context: GetStaticPathsContext
): Promise<GetStaticPathsResult> {
const sdk = getReadySdk(); const sdk = getReadySdk();
const contents = await sdk.getWikiPagesSlugs(); const contents = await sdk.getWikiPagesSlugs();
const paths: GetStaticPathsResult["paths"] = []; const paths: GetStaticPathsResult["paths"] = [];
@ -189,4 +187,4 @@ export async function getStaticPaths(
paths, paths,
fallback: "blocking", fallback: "blocking",
}; };
} };

View File

@ -13,9 +13,14 @@ 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 { filterHasAttributes, isDefined } from "helpers/others"; import { filterHasAttributes, isDefined } from "helpers/others";
import { GetStaticPropsContext } from "next"; import { GetStaticProps } from "next";
import { Fragment, useMemo } from "react"; import { Fragment, useMemo } from "react";
/*
*
* PAGE
*/
interface Props extends AppStaticProps { interface Props extends AppStaticProps {
chronologyItems: NonNullable< chronologyItems: NonNullable<
GetChronologyItemsQuery["chronologyItems"] GetChronologyItemsQuery["chronologyItems"]
@ -23,9 +28,12 @@ interface Props extends AppStaticProps {
chronologyEras: NonNullable<GetErasQuery["chronologyEras"]>["data"]; chronologyEras: NonNullable<GetErasQuery["chronologyEras"]>["data"];
} }
export default function Chronology(props: Props): JSX.Element { const Chronology = ({
const { chronologyItems, chronologyEras, langui } = props; chronologyItems,
chronologyEras,
langui,
...otherProps
}: Props): JSX.Element => {
// Group by year the Chronology items // Group by year the Chronology items
const chronologyItemYearGroups = useMemo(() => { const chronologyItemYearGroups = useMemo(() => {
const memo: Props["chronologyItems"][number][][][] = []; const memo: Props["chronologyItems"][number][][][] = [];
@ -141,14 +149,19 @@ export default function Chronology(props: Props): JSX.Element {
navTitle={langui.chronology} navTitle={langui.chronology}
contentPanel={contentPanel} contentPanel={contentPanel}
subPanel={subPanel} subPanel={subPanel}
{...props} langui={langui}
{...otherProps}
/> />
); );
} };
export default Chronology;
export async function getStaticProps( /*
context: GetStaticPropsContext *
): Promise<{ notFound: boolean } | { props: Props }> { * NEXT DATA FETCHING
*/
export const getStaticProps: GetStaticProps = async (context) => {
const sdk = getReadySdk(); const sdk = getReadySdk();
const chronologyItems = await sdk.getChronologyItems({ const chronologyItems = await sdk.getChronologyItems({
language_code: context.locale ?? "en", language_code: context.locale ?? "en",
@ -166,4 +179,4 @@ export async function getStaticProps(
return { return {
props: props, props: props,
}; };
} };

View File

@ -4,7 +4,7 @@ import { PanelHeader } from "components/PanelComponents/PanelHeader";
import { SubPanel } from "components/Panels/SubPanel"; import { SubPanel } from "components/Panels/SubPanel";
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps"; import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
import { GetStaticPropsContext } from "next"; import { GetStaticProps } from "next";
import { Icon } from "components/Ico"; import { Icon } from "components/Ico";
import { getReadySdk } from "graphql/sdk"; import { getReadySdk } from "graphql/sdk";
import { GetWikiPagesPreviewsQuery } from "graphql/generated"; import { GetWikiPagesPreviewsQuery } from "graphql/generated";
@ -23,23 +23,38 @@ import { useMediaHoverable } from "hooks/useMediaQuery";
import { filterHasAttributes } from "helpers/others"; import { filterHasAttributes } from "helpers/others";
import { ContentPlaceholder } from "components/PanelComponents/ContentPlaceholder"; import { ContentPlaceholder } from "components/PanelComponents/ContentPlaceholder";
interface Props extends AppStaticProps { /*
pages: NonNullable<GetWikiPagesPreviewsQuery["wikiPages"]>["data"]; *
} * CONSTANTS
*/
const defaultFiltersState = { const DEFAULT_FILTERS_STATE = {
searchName: "", searchName: "",
keepInfoVisible: true, keepInfoVisible: true,
}; };
export default function Wiki(props: Props): JSX.Element { /*
const { langui, languages } = props; *
const pages = sortPages(props.pages); * PAGE
*/
interface Props extends AppStaticProps {
pages: NonNullable<GetWikiPagesPreviewsQuery["wikiPages"]>["data"];
}
const Wiki = ({
langui,
languages,
pages,
...otherProps
}: Props): JSX.Element => {
const hoverable = useMediaHoverable(); const hoverable = useMediaHoverable();
const [searchName, setSearchName] = useState(defaultFiltersState.searchName); const [searchName, setSearchName] = useState(
DEFAULT_FILTERS_STATE.searchName
);
const [keepInfoVisible, setKeepInfoVisible] = useState( const [keepInfoVisible, setKeepInfoVisible] = useState(
defaultFiltersState.keepInfoVisible DEFAULT_FILTERS_STATE.keepInfoVisible
); );
const filteredPages = useMemo( const filteredPages = useMemo(
@ -77,14 +92,13 @@ export default function Wiki(props: Props): JSX.Element {
text={langui.reset_all_filters} text={langui.reset_all_filters}
icon={Icon.Replay} icon={Icon.Replay}
onClick={() => { onClick={() => {
setSearchName(defaultFiltersState.searchName); setSearchName(DEFAULT_FILTERS_STATE.searchName);
setKeepInfoVisible(defaultFiltersState.keepInfoVisible); setKeepInfoVisible(DEFAULT_FILTERS_STATE.keepInfoVisible);
}} }}
/> />
<HorizontalLine /> <HorizontalLine />
{/* TODO: Langui */} <p className="mb-4 font-headers text-xl">{langui.special_pages}</p>
<p className="mb-4 font-headers text-xl">Special Pages</p>
<NavOption title={langui.chronology} url="/wiki/chronology" border /> <NavOption title={langui.chronology} url="/wiki/chronology" border />
</SubPanel> </SubPanel>
@ -99,12 +113,9 @@ export default function Wiki(props: Props): JSX.Element {
className="grid grid-cols-2 items-end gap-8 className="grid grid-cols-2 items-end gap-8
desktop:grid-cols-[repeat(auto-fill,_minmax(20rem,1fr))] mobile:gap-4" desktop:grid-cols-[repeat(auto-fill,_minmax(20rem,1fr))] mobile:gap-4"
> >
{/* TODO: Add to langui */}
{filteredPages.length === 0 && ( {filteredPages.length === 0 && (
<ContentPlaceholder <ContentPlaceholder
message={ message={langui.no_results_message ?? "No results"}
"No results. You can try changing or resetting the search parameters."
}
icon={Icon.ChevronLeft} icon={Icon.ChevronLeft}
/> />
)} )}
@ -137,7 +148,7 @@ export default function Wiki(props: Props): JSX.Element {
</div> </div>
</ContentPanel> </ContentPanel>
), ),
[filteredPages, keepInfoVisible, languages] [filteredPages, keepInfoVisible, languages, langui]
); );
return ( return (
@ -146,36 +157,48 @@ export default function Wiki(props: Props): JSX.Element {
subPanel={subPanel} subPanel={subPanel}
contentPanel={contentPanel} contentPanel={contentPanel}
subPanelIcon={Icon.Search} subPanelIcon={Icon.Search}
{...props} languages={languages}
langui={langui}
{...otherProps}
/> />
); );
} };
export default Wiki;
export async function getStaticProps( /*
context: GetStaticPropsContext *
): Promise<{ notFound: boolean } | { props: Props }> { * NEXT DATA FETCHING
*/
export const getStaticProps: GetStaticProps = async (context) => {
const sdk = getReadySdk(); const sdk = getReadySdk();
const pages = await sdk.getWikiPagesPreviews({}); const pages = await sdk.getWikiPagesPreviews({});
if (!pages.wikiPages?.data) return { notFound: true }; if (!pages.wikiPages?.data) return { notFound: true };
const props: Props = { const props: Props = {
...(await getAppStaticProps(context)), ...(await getAppStaticProps(context)),
pages: pages.wikiPages.data, pages: sortPages(pages.wikiPages.data),
}; };
return { return {
props: props, props: props,
}; };
} };
function sortPages(pages: Props["pages"]): Props["pages"] { /*
return pages.sort((a, b) => { *
* PRIVATE METHODS
*/
const sortPages = (pages: Props["pages"]): Props["pages"] =>
pages.sort((a, b) => {
const slugA = a.attributes?.slug ?? ""; const slugA = a.attributes?.slug ?? "";
const slugB = b.attributes?.slug ?? ""; const slugB = b.attributes?.slug ?? "";
return slugA.localeCompare(slugB); return slugA.localeCompare(slugB);
}); });
}
function filterPages(posts: Props["pages"], searchName: string) { // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
return posts.filter((post) => {
const filterPages = (posts: Props["pages"], searchName: string) =>
posts.filter((post) => {
if (searchName.length > 1) { if (searchName.length > 1) {
if ( if (
post.attributes?.translations?.[0]?.title post.attributes?.translations?.[0]?.title
@ -188,4 +211,3 @@ function filterPages(posts: Props["pages"], searchName: string) {
} }
return true; return true;
}); });
}

View File

@ -1,9 +1,7 @@
const plugin = require("tailwindcss/plugin"); const plugin = require("tailwindcss/plugin");
const { breaks, colors, fonts, fontFamilies } = require("./design.config.js"); const { breaks, colors, fonts, fontFamilies } = require("./design.config.js");
function rgb(color) { const rgb = (color) => [color.r, color.g, color.b].join(" ");
return [color.r, color.g, color.b].join(" ");
}
/** @type {import('tailwindcss').Config} */ /** @type {import('tailwindcss').Config} */
module.exports = { module.exports = {
@ -38,7 +36,7 @@ module.exports = {
}, },
}, },
plugins: [ plugins: [
plugin(function ({ addUtilities }) { plugin(({ addUtilities }) => {
addUtilities({ addUtilities({
".set-theme-light": { ".set-theme-light": {
"--theme-color-highlight": rgb(colors.light.hightlight), "--theme-color-highlight": rgb(colors.light.hightlight),
@ -63,7 +61,7 @@ module.exports = {
}); });
}), }),
plugin(function ({ addUtilities }) { plugin(({ addUtilities }) => {
addUtilities({ addUtilities({
".set-theme-font-standard": { ".set-theme-font-standard": {
"--theme-font-body": fontFamilies.standard.body, "--theme-font-body": fontFamilies.standard.body,
@ -78,7 +76,7 @@ module.exports = {
}); });
}), }),
plugin(function ({ addVariant, e }) { plugin(({ addVariant, e }) => {
addVariant("webkit-scrollbar", ({ modifySelectors, separator }) => { addVariant("webkit-scrollbar", ({ modifySelectors, separator }) => {
modifySelectors(({ className }) => { modifySelectors(({ className }) => {
return `.${e( return `.${e(
@ -89,7 +87,7 @@ module.exports = {
}), }),
// Colored Dropshadow // Colored Dropshadow
plugin(function ({ addUtilities }) { plugin(({ addUtilities }) => {
addUtilities({ addUtilities({
".drop-shadow-shade-md": { ".drop-shadow-shade-md": {
filter: ` filter: `
@ -131,7 +129,7 @@ module.exports = {
}); });
}), }),
plugin(function ({ addUtilities }) { plugin(({ addUtilities }) => {
addUtilities({ addUtilities({
".linearbg-obi": { ".linearbg-obi": {
background: `linear-gradient( background: `linear-gradient(
@ -145,7 +143,7 @@ module.exports = {
}); });
}), }),
plugin(function ({ addUtilities }) { plugin(({ addUtilities }) => {
addUtilities({ addUtilities({
".break-words": { ".break-words": {
"word-break": "break-word", "word-break": "break-word",