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",
"func-name-matching": "warn",
"func-names": "warn",
"func-style": ["warn", "declaration"],
"func-style": ["warn", "expression"],
"grouped-accessor-pairs": "warn",
"guard-for-in": "warn",
"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 { Popup } from "./Popup";
/*
*
* CONSTANTS
*/
const SENSIBILITY_SWIPE = 1.1;
const TITLE_PREFIX = "Accords Library";
/*
*
* COMPONENT
*/
interface Props extends AppStaticProps {
subPanel?: React.ReactNode;
subPanelIcon?: Icon;
@ -38,24 +51,21 @@ interface Props extends AppStaticProps {
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 {
configPanelOpen,
currency,
@ -396,7 +406,7 @@ export function AppLayout(props: Props): JSX.Element {
insertLabels={
new Map([
[0, langui.primary_language],
[1, langui.secondary_language],
[1, langui.secondary_language],
])
}
onChange={(items) => {
@ -517,4 +527,4 @@ export function AppLayout(props: Props): JSX.Element {
</div>
</div>
);
}
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,11 +3,15 @@ import { AppStaticProps } from "graphql/getAppStaticProps";
import { cJoin } from "helpers/className";
import { prettyLanguage } from "helpers/formatters";
import { iterateMap } from "helpers/others";
import { Fragment } from "react";
import { ToolTip } from "../ToolTip";
import { Button } from "./Button";
/*
*
* COMPONENT
*/
interface Props {
className?: string;
languages: AppStaticProps["languages"];
@ -16,29 +20,33 @@ interface Props {
onLanguageChanged: (index: number) => void;
}
export function LanguageSwitcher(props: Props): JSX.Element {
const { locales, className, localesIndex, onLanguageChanged } = props;
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
return (
<ToolTip
content={
<div className={cJoin("flex flex-col gap-2", className)}>
{iterateMap(locales, (locale, value, index) => (
<Fragment key={index}>
<Button
active={value === localesIndex}
onClick={() => onLanguageChanged(value)}
text={prettyLanguage(locale, props.languages)}
/>
</Fragment>
))}
</div>
}
>
<Button
badgeNumber={locales.size > 1 ? locales.size : undefined}
icon={Icon.Translate}
/>
</ToolTip>
);
}
export const LanguageSwitcher = ({
className,
locales,
localesIndex,
languages,
onLanguageChanged,
}: Props): JSX.Element => (
<ToolTip
content={
<div className={cJoin("flex flex-col gap-2", className)}>
{iterateMap(locales, (locale, value, index) => (
<Fragment key={index}>
<Button
active={value === localesIndex}
onClick={() => onLanguageChanged(value)}
text={prettyLanguage(locale, languages)}
/>
</Fragment>
))}
</div>
}
>
<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 { isDefinedAndNotEmpty, iterateMap, mapMoveEntry } from "helpers/others";
import { Fragment, useCallback, useState } from "react";
/*
*
* COMPONENT
*/
interface Props {
className?: string;
items: Map<string, string>;
@ -10,9 +14,14 @@ interface Props {
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(
(sourceIndex: number, targetIndex: number) => {
@ -27,10 +36,9 @@ export function OrderableList(props: Props): JSX.Element {
<div className="grid gap-2">
{iterateMap(items, (key, value, index) => (
<Fragment key={key}>
{props.insertLabels &&
isDefinedAndNotEmpty(props.insertLabels.get(index)) && (
<p>{props.insertLabels.get(index)}</p>
)}
{insertLabels && isDefinedAndNotEmpty(insertLabels.get(index)) && (
<p>{insertLabels.get(index)}</p>
)}
<div
onDragStart={(event) => {
const source = event.target as HTMLElement;
@ -89,4 +97,4 @@ export function OrderableList(props: Props): JSX.Element {
))}
</div>
);
}
};

View File

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

View File

@ -1,9 +1,13 @@
import { Ico, Icon } from "components/Ico";
import { cIf, cJoin } from "helpers/className";
import { useToggle } from "hooks/useToggle";
import { Dispatch, Fragment, SetStateAction, useState } from "react";
/*
*
* COMPONENT
*/
interface Props {
setState: Dispatch<SetStateAction<number>>;
state: number;
@ -13,8 +17,15 @@ interface Props {
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 toggleOpened = useToggle(setOpened);
@ -80,4 +91,4 @@ export function Select(props: Props): JSX.Element {
</div>
</div>
);
}
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -13,10 +13,14 @@ import {
isDefined,
isDefinedAndNotEmpty,
} from "helpers/others";
import { useSmartLanguage } from "hooks/useSmartLanguage";
import { Fragment, useCallback, useMemo } from "react";
/*
*
* COMPONENT
*/
interface Props {
openLightBox: (images: string[], index?: number) => void;
scanSet: NonNullable<
@ -45,10 +49,17 @@ interface Props {
>["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] =
useSmartLanguage({
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 { getAssetURL, ImageQuality } from "helpers/img";
import { filterHasAttributes, getStatusDescription } from "helpers/others";
import { useSmartLanguage } from "hooks/useSmartLanguage";
import { Fragment, useCallback, useMemo } from "react";
/*
*
* COMPONENT
*/
interface Props {
openLightBox: (images: string[], index?: number) => void;
images: NonNullable<
@ -26,9 +30,14 @@ interface Props {
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] =
useSmartLanguage({
items: images,
@ -176,4 +185,4 @@ export function ScanSetCover(props: Props): JSX.Element {
);
}
return <></>;
}
};

View File

@ -6,6 +6,18 @@ import { Button } from "./Inputs/Button";
import { Popup } from "./Popup";
import { Icon } from "components/Ico";
/*
*
* CONSTANTS
*/
const SENSIBILITY_SWIPE = 0.5;
/*
*
* COMPONENT
*/
interface Props {
setState:
| Dispatch<SetStateAction<boolean | undefined>>
@ -16,11 +28,15 @@ interface Props {
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(() => {
if (index > 0) setIndex(index - 1);
}, [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 { ToolTip } from "components/ToolTip";
import { useAppLayout } from "contexts/AppLayoutContext";
import { AppStaticProps } from "graphql/getAppStaticProps";
import { cJoin } from "helpers/className";
import { slugify } from "helpers/formatters";
import { getAssetURL, ImageQuality } from "helpers/img";
import { isDefined, isDefinedAndNotEmpty } from "helpers/others";
import { useLightBox } from "hooks/useLightBox";
import Markdown from "markdown-to-jsx";
import { useRouter } from "next/router";
import React, { useMemo } from "react";
import React, { Fragment, useMemo } from "react";
import ReactDOMServer from "react-dom/server";
interface Props {
/*
*
* COMPONENT
*/
interface MarkdawnProps {
className?: 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 router = useRouter();
const [openLightBox, LightBox] = useLightBox();
@ -299,33 +308,121 @@ export function Markdawn(props: Props): JSX.Element {
);
}
return <></>;
};
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
interface TableOfContentsProps {
text: string;
title?: string;
langui: AppStaticProps["langui"];
}
function HeaderToolTip(props: { id: string }) {
return (
<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>
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
export const TableOfContents = ({
text,
title,
langui,
}: TableOfContentsProps): JSX.Element => {
const router = useRouter();
const toc = useMemo(
() => getTocFromMarkdawn(preprocessMarkDawn(text), title),
[text, title]
);
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;
newText = newText.replace(/--/gu, "");
/*
@ -336,9 +433,20 @@ function typographicRules(text: string): string {
* newText = newText.replace(/'/gu, "");
*/
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 "";
let preprocessed = typographicRules(text);
@ -383,22 +491,15 @@ export function preprocessMarkDawn(text: string): string {
.join("\n");
return preprocessed;
}
};
enum HeaderLevels {
H1 = 1,
H2 = 2,
H3 = 3,
H4 = 4,
H5 = 5,
H6 = 6,
}
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
function markdawnHeadersParser(
const markdawnHeadersParser = (
headerLevel: HeaderLevels,
line: string,
visitedSlugs: string[]
): string {
): string => {
const lineText = line.slice(headerLevel + 1);
const slug = slugify(lineText);
let newSlug = slug;
@ -409,4 +510,102 @@ function markdawnHeadersParser(
}
visitedSlugs.push(newSlug);
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 { isDefined } from "helpers/others";
/*
*
* COMPONENT
*/
interface Props {
message: string;
icon?: Icon;
}
export function ContentPlaceholder(props: Props): JSX.Element {
const { message, icon } = props;
return (
<div className="grid h-full place-content-center">
<div
className="grid grid-flow-col place-items-center gap-9 rounded-2xl border-2 border-dotted
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
export const ContentPlaceholder = ({ message, icon }: Props): JSX.Element => (
<div className="grid h-full place-content-center">
<div
className="grid grid-flow-col place-items-center gap-9 rounded-2xl border-2 border-dotted
border-dark p-8 text-dark opacity-40"
>
{isDefined(icon) && <Ico icon={icon} className="!text-[300%]" />}
<p
className={cJoin("w-64 text-2xl", cIf(!isDefined(icon), "text-center"))}
>
{isDefined(icon) && <Ico icon={icon} className="!text-[300%]" />}
<p
className={cJoin(
"w-64 text-2xl",
cIf(!isDefined(icon), "text-center")
)}
>
{message}
</p>
</div>
{message}
</p>
</div>
);
}
</div>
);

View File

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

View File

@ -2,22 +2,30 @@ import { HorizontalLine } from "components/HorizontalLine";
import { Ico, Icon } from "components/Ico";
import { isDefinedAndNotEmpty } from "helpers/others";
/*
*
* COMPONENT
*/
interface Props {
icon?: Icon;
title: string | null | undefined;
description?: string | null | undefined;
}
export function PanelHeader(props: Props): JSX.Element {
const { icon, description, title } = props;
return (
<>
<div className="grid w-full place-items-center">
{icon && <Ico icon={icon} className="mb-3 !text-4xl" />}
<h2 className="text-2xl">{title}</h2>
{isDefinedAndNotEmpty(description) && <p>{description}</p>}
</div>
<HorizontalLine />
</>
);
}
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
export const PanelHeader = ({
icon,
description,
title,
}: Props): JSX.Element => (
<>
<div className="grid w-full place-items-center">
{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 { cJoin } from "helpers/className";
/*
*
* COMPONENT
*/
interface Props {
href: string;
title: string | null | undefined;
@ -20,8 +25,16 @@ export enum ReturnButtonType {
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();
return (
@ -44,4 +57,4 @@ export function ReturnButton(props: Props): JSX.Element {
{horizontalLine === true && <HorizontalLine />}
</div>
);
}
};

View File

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

View File

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

View File

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

View File

@ -1,9 +1,13 @@
import { useAppLayout } from "contexts/AppLayoutContext";
import { cIf, cJoin } from "helpers/className";
import { Dispatch, SetStateAction, useEffect } from "react";
import Hotkeys from "react-hot-keys";
/*
*
* COMPONENT
*/
interface Props {
setState:
| Dispatch<SetStateAction<boolean | undefined>>
@ -15,16 +19,16 @@ interface Props {
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();
useEffect(() => {
@ -77,4 +81,4 @@ export function Popup(props: Props): JSX.Element {
</div>
</Hotkeys>
);
}
};

View File

@ -8,8 +8,7 @@ import { Fragment, useCallback, useMemo } from "react";
import { AppLayout } from "./AppLayout";
import { Chip } from "./Chip";
import { HorizontalLine } from "./HorizontalLine";
import { Markdawn } from "./Markdown/Markdawn";
import { TOC } from "./Markdown/TOC";
import { Markdawn, TableOfContents } from "./Markdown/Markdawn";
import { ReturnButton, ReturnButtonType } from "./PanelComponents/ReturnButton";
import { ContentPanel } from "./Panels/ContentPanel";
import { SubPanel } from "./Panels/SubPanel";
@ -17,6 +16,11 @@ import { RecorderChip } from "./RecorderChip";
import { ThumbnailHeader } from "./ThumbnailHeader";
import { ToolTip } from "./ToolTip";
/*
*
* COMPONENT
*/
interface Props {
post: PostWithTranslations;
langui: AppStaticProps["langui"];
@ -33,22 +37,23 @@ interface Props {
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] =
useSmartLanguage({
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>
) : undefined,
[
@ -216,7 +223,9 @@ export function PostPage(props: Props): JSX.Element {
contentPanel={contentPanel}
subPanel={subPanel}
thumbnail={thumbnail ?? undefined}
{...props}
currencies={currencies}
languages={languages}
langui={langui}
/>
);
}
};

View File

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

View File

@ -2,13 +2,17 @@ import { UploadImageFragment } from "graphql/generated";
import { AppStaticProps } from "graphql/getAppStaticProps";
import { prettySlug } from "helpers/formatters";
import { ImageQuality } from "helpers/img";
import { useSmartLanguage } from "hooks/useSmartLanguage";
import Link from "next/link";
import { useCallback } from "react";
import { Chip } from "./Chip";
import { Img } from "./Img";
/*
*
* COMPONENT
*/
interface Props {
thumbnail?: UploadImageFragment | string | null | undefined;
thumbnailAspectRatio?: string;
@ -20,60 +24,60 @@ interface Props {
bottomChips?: string[];
}
function PreviewLine(props: Props): JSX.Element {
const {
href,
thumbnail,
pre_title,
title,
subtitle,
topChips,
bottomChips,
thumbnailAspectRatio,
} = props;
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
return (
<Link href={href} passHref>
<div
className="flex h-36 w-full cursor-pointer flex-row place-items-center gap-4 overflow-hidden
const PreviewLine = ({
href,
thumbnail,
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]"
>
{thumbnail ? (
<div className="aspect-[3/2] h-full">
<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>
)}
>
{thumbnail ? (
<div className="aspect-[3/2] h-full">
<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>
</Link>
);
}
</div>
</Link>
);
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
interface TranslatedProps
extends Omit<Props, "pre_title" | "subtitle" | "title"> {
@ -88,13 +92,14 @@ interface TranslatedProps
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({
items: translations,
languages: languages,
@ -109,7 +114,7 @@ export function TranslatedPreviewLine(props: TranslatedProps): JSX.Element {
pre_title={selectedTranslation?.pre_title}
title={selectedTranslation?.title ?? prettySlug(slug)}
subtitle={selectedTranslation?.subtitle}
{...props}
{...otherProps}
/>
);
}
};

View File

@ -3,64 +3,67 @@ import { RecorderChipFragment } from "graphql/generated";
import { AppStaticProps } from "graphql/getAppStaticProps";
import { ImageQuality } from "helpers/img";
import { filterHasAttributes } from "helpers/others";
import { Fragment } from "react";
import { Img } from "./Img";
import { Markdawn } from "./Markdown/Markdawn";
import { ToolTip } from "./ToolTip";
/*
*
* COMPONENT
*/
interface Props {
className?: string;
recorder: RecorderChipFragment;
langui: AppStaticProps["langui"];
}
export function RecorderChip(props: Props): JSX.Element {
const { recorder, langui } = props;
return (
<ToolTip
content={
<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">
{recorder.avatar?.data?.attributes && (
<Img
className="w-20 rounded-full border-4 border-mid"
image={recorder.avatar.data.attributes}
quality={ImageQuality.Small}
/>
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
export const RecorderChip = ({ recorder, langui }: Props): JSX.Element => (
<ToolTip
content={
<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">
{recorder.avatar?.data?.attributes && (
<Img
className="w-20 rounded-full border-4 border-mid"
image={recorder.avatar.data.attributes}
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>
{recorder.bio?.[0] && <Markdawn text={recorder.bio[0].bio ?? ""} />}
</div>
}
placement="top"
>
<Chip key={recorder.anonymous_code}>
{recorder.anonymize
? `Recorder#${recorder.anonymous_code}`
: recorder.username}
</Chip>
</ToolTip>
);
}
{recorder.bio?.[0] && <Markdawn text={recorder.bio[0].bio ?? ""} />}
</div>
}
placement="top"
>
<Chip key={recorder.anonymous_code}>
{recorder.anonymize
? `Recorder#${recorder.anonymous_code}`
: recorder.username}
</Chip>
</ToolTip>
);

View File

@ -7,9 +7,13 @@ import { AppStaticProps } from "graphql/getAppStaticProps";
import { prettyinlineTitle, prettySlug, slugify } from "helpers/formatters";
import { getAssetURL, ImageQuality } from "helpers/img";
import { filterHasAttributes } from "helpers/others";
import { useLightBox } from "hooks/useLightBox";
/*
*
* COMPONENT
*/
interface Props {
pre_title?: string | null | undefined;
title: string | null | undefined;
@ -26,19 +30,19 @@ interface Props {
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();
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 "tippy.js/animations/scale-subtle.css";
/*
*
* COMPONENT
*/
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 (
<Tippy {...newProps}>
<div>{props.children}</div>
</Tippy>
);
}
export const ToolTip = ({
className,
delay = [150, 0],
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"];
}
export function ChronologyItemComponent(props: Props): JSX.Element {
const { langui } = props;
if (props.item.attributes) {
export const ChronologyItemComponent = ({
langui,
item,
displayYear,
}: Props): JSX.Element => {
if (item.attributes) {
return (
<div
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"
id={generateAnchor(
props.item.attributes.year,
props.item.attributes.month,
props.item.attributes.day
item.attributes.year,
item.attributes.month,
item.attributes.day
)}
>
{props.displayYear && (
{displayYear && (
<p className="mt-[-.2em] text-lg font-bold">
{generateYear(
props.item.attributes.displayed_date,
props.item.attributes.year
)}
{generateYear(item.attributes.displayed_date, item.attributes.year)}
</p>
)}
<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>
<div className="col-start-2 row-span-2 row-start-1 grid gap-4">
{props.item.attributes.events &&
filterHasAttributes(props.item.attributes.events, [
{item.attributes.events &&
filterHasAttributes(item.attributes.events, [
"id",
"translations",
]).map((event) => (
@ -123,31 +122,29 @@ export function ChronologyItemComponent(props: Props): JSX.Element {
}
return <></>;
}
};
function generateAnchor(
const generateAnchor = (
year: number | undefined,
month: number | null | undefined,
day: number | null | undefined
): string {
): string => {
let result = "";
if (year) result += year;
if (month) result += `- ${month.toString().padStart(2, "0")}`;
if (day) result += `- ${day.toString().padStart(2, "0")}`;
return result;
}
};
function generateYear(
const generateYear = (
displayed_date: string | null | undefined,
year: number | undefined
): string {
return displayed_date ?? year?.toString() ?? "";
}
): string => displayed_date ?? year?.toString() ?? "";
function generateDate(
const generateDate = (
month: number | null | undefined,
day: number | null | undefined
): string {
): string => {
const lut = [
"Jan",
"Feb",
@ -172,4 +169,4 @@ function generateDate(
}
return result;
}
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -154,6 +154,11 @@ query getWebsiteInterface($language_code: String) {
only_display_items_i_want
only_display_unmarked_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 { getSdk } from "graphql/generated";
export function getReadySdk() {
export const getReadySdk = () => {
const client = new GraphQLClient(process.env.URL_GRAPHQL ?? "", {
headers: { Authorization: `Bearer ${process.env.ACCESS_TOKEN}` },
});
return getSdk(client);
}
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,9 +1,9 @@
import { GetCurrenciesQuery, PricePickerFragment } from "graphql/generated";
export function convertPrice(
export const convertPrice = (
pricePicker: PricePickerFragment,
targetCurrency: NonNullable<GetCurrenciesQuery["currencies"]>["data"][number]
): number {
): number => {
if (
pricePicker.amount &&
pricePicker.currency?.data?.attributes &&
@ -14,17 +14,17 @@ export function convertPrice(
targetCurrency.attributes.rate_to_usd
);
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) : "";
}
};
export function randomInt(min: number, max: number) {
export const randomInt = (min: number, max: number) => {
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
return /^[+-]?[0-9]+$/.test(value);
}
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -13,31 +13,28 @@ interface Props<T> {
transform?: (item: NonNullable<T>) => NonNullable<T>;
}
function getPreferredLanguage(
const getPreferredLanguage = (
preferredLanguages: (string | undefined)[],
availableLanguages: Map<string, number>
): number | undefined {
): 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>
): [
export const useSmartLanguage = <T>({
items,
languageExtractor,
languages,
transform = (item) => item,
}: Props<T>): [
T | undefined,
typeof LanguageSwitcher,
Parameters<typeof LanguageSwitcher>[0]
] {
const {
items,
languageExtractor,
languages,
transform = (item) => item,
} = props;
] => {
const { preferredLanguages } = useAppLayout();
const router = useRouter();
@ -81,4 +78,4 @@ export function useSmartLanguage<T>(
};
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 { useEffect, useState } from "react";
export function useStateWithLocalStorage<T>(
export const useStateWithLocalStorage = <T>(
key: string,
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 [, setFromLocaleStorage] = useState<boolean>(false);
@ -28,4 +28,4 @@ export function useStateWithLocalStorage<T>(
}, [value, key]);
return [value, setValue];
}
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -4,16 +4,19 @@ import { NavOption } from "components/PanelComponents/NavOption";
import { PanelHeader } from "components/PanelComponents/PanelHeader";
import { SubPanel } from "components/Panels/SubPanel";
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
import { GetStaticProps } from "next";
import { GetStaticPropsContext } from "next";
import { useMemo } from "react";
/*
*
* PAGE
*/
interface Props extends AppStaticProps {}
export default function AboutUs(props: Props): JSX.Element {
const { langui } = props;
const subPanel = useMemo(
() => (
const AboutUs = ({ langui, ...otherProps }: Props): JSX.Element => (
<AppLayout
navTitle={langui.about_us}
subPanel={
<SubPanel>
<PanelHeader
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 />
</SubPanel>
),
[langui]
);
return (
<AppLayout navTitle={langui.about_us} subPanel={subPanel} {...props} />
);
}
}
langui={langui}
{...otherProps}
/>
);
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 = {
...(await getAppStaticProps(context)),
};
return {
props: props,
};
}
};

View File

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

View File

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

View File

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

View File

@ -81,10 +81,10 @@ type ResponseMailProps = {
revalidated: boolean;
};
export default async function Revalidate(
const Revalidate = async (
req: NextApiRequest,
res: NextApiResponse<ResponseMailProps>
) {
) => {
const body = req.body as RequestProps;
const { serverRuntimeConfig } = getConfig();
@ -217,4 +217,5 @@ export default async function Revalidate(
.status(500)
.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 { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
import { GetStaticPropsContext } from "next";
import { GetStaticProps } from "next";
import { Icon } from "components/Ico";
import { useMemo } from "react";
/*
*
* PAGE
*/
interface Props extends AppStaticProps {}
export default function Archives(props: Props): JSX.Element {
const { langui } = props;
const Archives = ({ langui, ...otherProps }: Props): JSX.Element => {
const subPanel = useMemo(
() => (
<SubPanel>
@ -26,17 +30,26 @@ export default function Archives(props: Props): JSX.Element {
[langui]
);
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 = {
...(await getAppStaticProps(context)),
};
return {
props: props,
};
}
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,4 @@
import { AppLayout } from "components/AppLayout";
import { Icon } from "components/Ico";
import { Button } from "components/Inputs/Button";
import { ButtonGroup } from "components/Inputs/ButtonGroup";
import {
@ -8,42 +7,43 @@ import {
} from "components/Panels/ContentPanel";
import { ToolTip } from "components/ToolTip";
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
import { GetStaticPropsContext } from "next";
import { GetStaticProps } from "next";
import { useCallback, useMemo, useRef, useState } from "react";
interface Props extends AppStaticProps {}
/*
*
* CONSTANTS
*/
const SIZE_MULTIPLIER = 1000;
function replaceSelection(
/*
*
* PAGE
*/
interface Props extends AppStaticProps {}
const replaceSelection = (
text: string,
selectionStart: number,
selectionEnd: number,
newSelectedText: string
) {
return (
text.substring(0, selectionStart) +
newSelectedText +
text.substring(selectionEnd)
);
}
) =>
text.substring(0, selectionStart) +
newSelectedText +
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++) {
if (char === swaps[index]) {
console.log(
"found it",
char,
" returning",
swaps[(index + 1) % swaps.length]
);
return swaps[(index + 1) % swaps.length];
}
}
return char;
}
};
export default function Transcript(props: Props): JSX.Element {
const Transcript = (props: Props): JSX.Element => {
const [text, setText] = useState("");
const [fontSize, setFontSize] = useState(1);
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(() => {
if (textAreaRef.current) {
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("~", "");
updateDisplayedText();
}
}, [updateDisplayedText]);
@ -320,14 +430,19 @@ export default function Transcript(props: Props): JSX.Element {
}
></input>
</div>
<ToolTip content="Automatically convert punctuations">
<Button icon={Icon.QuestionMark} onClick={convertPunctuation} />
<ToolTip content="Automatically convert Western punctuations to Japanese ones.">
<Button text=". ⟹ 。" onClick={convertPunctuation} />
</ToolTip>
<Button text={"か ⟺ が"} onClick={toggleDakuten} />
<Button text={"つ ⟺ っ"} onClick={toggleSmallForm} />
<Button text={"。"} onClick={() => insert("。")} />
<Button text={""} onClick={() => insert("")} />
<Button text={""} onClick={() => insert("")} />
<ToolTip content="Swap a kana for one of its variant (different diacritics).">
<Button text="か ⟺ が" onClick={toggleDakuten} />
</ToolTip>
<ToolTip content="Toggle a kana's small form">
<Button text="つ ⟺ っ" onClick={toggleSmallForm} />
</ToolTip>
<ToolTip content="Convert standard characters to their full width variant.">
<Button text="123 ⟹ " onClick={convertFullWidth} />
</ToolTip>
<ToolTip
content={
<div className="grid gap-2">
@ -351,8 +466,8 @@ export default function Transcript(props: Props): JSX.Element {
/>
<ButtonGroup
buttonsProps={[
{ text: "", onClick: () => insert("") },
{ text: "", onClick: () => insert("") },
{ text: "", onClick: () => insert("") },
{ text: "", onClick: () => insert("") },
]}
/>
<ButtonGroup
@ -361,6 +476,18 @@ export default function Transcript(props: Props): JSX.Element {
{ text: "〟", onClick: () => insert("〟") },
]}
/>
<ButtonGroup
buttonsProps={[
{ text: "", onClick: () => insert("") },
{ text: "", onClick: () => insert("") },
]}
/>
<ButtonGroup
buttonsProps={[
{ text: "⦅", onClick: () => insert("⦅") },
{ text: "⦆", onClick: () => insert("⦆") },
]}
/>
<ButtonGroup
buttonsProps={[
{ text: "〈", onClick: () => insert("〈") },
@ -373,19 +500,57 @@ export default function Transcript(props: Props): JSX.Element {
{ 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>
}
>
<Button text={"Quotations"} />
</ToolTip>
<Button text={"⋯"} onClick={() => insert("⋯")} />
<Button text={"※"} onClick={() => insert("※")} />
<Button text={'" "'} onClick={() => insert(" ")} />
<ToolTip
content={
<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(" ")} />
</div>
}
>
<Button text="Insert" />
</ToolTip>
</div>
</ContentPanel>
),
[
convertFullWidth,
convertPunctuation,
fontSize,
insert,
@ -407,15 +572,19 @@ export default function Transcript(props: Props): JSX.Element {
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 = {
...(await getAppStaticProps(context)),
};
return {
props: props,
};
}
};

View File

@ -4,30 +4,44 @@ import {
PostStaticProps,
} from "graphql/getPostStaticProps";
export default function Home(props: PostStaticProps): JSX.Element {
const { post, langui, languages, currencies } = props;
return (
<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')]
/*
*
* PAGE
*/
const Home = ({
post,
langui,
languages,
currencies,
}: PostStaticProps): JSX.Element => (
<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]"
/>
<h1 className="mb-0 text-5xl">Accord&rsquo;s Library</h1>
<h2 className="-mt-5 text-xl">
Discover Analyze Translate Archive
</h2>
</div>
}
displayTitle={false}
displayLanguageSwitcher
/>
);
}
/>
<h1 className="mb-0 text-5xl">Accord&rsquo;s Library</h1>
<h2 className="-mt-5 text-xl">
Discover Analyze Translate Archive
</h2>
</div>
}
displayTitle={false}
displayLanguageSwitcher
/>
);
export default Home;
/*
*
* NEXT DATA FETCHING
*/
export const getStaticProps = getPostStaticProps("home");

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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