Fixed eslint warnings + configure prettier

This commit is contained in:
DrMint 2022-08-28 17:40:41 +02:00
parent 669d4358e7
commit 77d96a3dc3
106 changed files with 2637 additions and 3413 deletions

View File

@ -7,4 +7,5 @@ next.config.js
postcss.config.js
tailwind.config.js
design.config.js
graphql.config.js
graphql.config.js
prettier.config.js

View File

@ -81,7 +81,7 @@ module.exports = {
// "no-magic-numbers": "warn",
// "no-mixed-operators": "warn",
"no-multi-assign": "warn",
"no-multi-str": "warn",
// "no-multi-str": "warn",
"no-negated-condition": "warn",
// "no-nested-ternary": "warn",
"no-new": "warn",
@ -149,24 +149,15 @@ module.exports = {
"@typescript-eslint/ban-tslint-comment": "warn",
"@typescript-eslint/class-literal-property-style": "warn",
"@typescript-eslint/consistent-indexed-object-style": "warn",
"@typescript-eslint/consistent-type-assertions": [
"warn",
{ assertionStyle: "as" },
],
"@typescript-eslint/consistent-type-assertions": ["warn", { assertionStyle: "as" }],
"@typescript-eslint/consistent-type-exports": "error",
"@typescript-eslint/explicit-module-boundary-types": "warn",
"@typescript-eslint/method-signature-style": ["error", "property"],
"@typescript-eslint/no-base-to-string": "warn",
"@typescript-eslint/no-confusing-non-null-assertion": "warn",
"@typescript-eslint/no-confusing-void-expression": [
"error",
{ ignoreArrowShorthand: true },
],
"@typescript-eslint/no-confusing-void-expression": ["error", { ignoreArrowShorthand: true }],
"@typescript-eslint/no-dynamic-delete": "error",
"@typescript-eslint/no-empty-interface": [
"error",
{ allowSingleExtends: true },
],
"@typescript-eslint/no-empty-interface": ["error", { allowSingleExtends: true }],
"@typescript-eslint/no-invalid-void-type": "error",
"@typescript-eslint/no-meaningless-void-operator": "error",
"@typescript-eslint/no-non-null-asserted-nullish-coalescing": "error",

View File

@ -8,17 +8,10 @@ module.exports = {
headers: { Authorization: `Bearer ${process.env.ACCESS_TOKEN}` },
},
},
documents: [
"src/graphql/operations/**/*.graphql",
"src/graphql/fragments/*.graphql",
],
documents: ["src/graphql/operations/**/*.graphql", "src/graphql/fragments/*.graphql"],
generates: {
"src/graphql/generated.ts": {
plugins: [
"typescript",
"typescript-operations",
"typescript-graphql-request",
],
plugins: ["typescript", "typescript-operations", "typescript-graphql-request"],
},
},
};

View File

@ -24,14 +24,5 @@ module.exports = {
hreflang: "ja",
},
],
exclude: [
"/en/*",
"/fr/*",
"/ja/*",
"/es/*",
"/pt-br/*",
"/404",
"/500",
"/dev/*",
],
exclude: ["/en/*", "/fr/*", "/ja/*", "/es/*", "/pt-br/*", "/404", "/500", "/dev/*"],
};

View File

@ -3,10 +3,10 @@
"private": true,
"scripts": {
"dev": "next dev -p 12499",
"precommit": "npm run generate && npm run fetch-local-data && npm run prettier && npm run unused-exports && npm run eslint && npm run tsc && echo ALL PRECOMMIT CHECKS PASSED SUCCESSFULLY, LET\\'S FUCKING GO!",
"precommit": "npm run fetch-local-data && npm run prettier && npm run unused-exports && npm run eslint && npm run tsc && echo ALL PRECOMMIT CHECKS PASSED SUCCESSFULLY, LET\\'S FUCKING GO!",
"unused-exports": "ts-unused-exports ./tsconfig.json --excludePathsFromReport=src/pages --ignoreFiles=generated",
"fetch-local-data": "esrun src/graphql/fetchLocalData.ts",
"prebuild": "npm run generate && npm run fetch-local-data",
"fetch-local-data": "npm run generate && esrun src/graphql/fetchLocalData.ts",
"prebuild": "npm run fetch-local-data",
"build": "next build",
"postbuild": "next-sitemap --config next-sitemap.config.js",
"start": "next start -p 12500",

21
prettier.config.js Normal file
View File

@ -0,0 +1,21 @@
module.exports = {
printWidth: 100,
tabWidth: 2,
useTabs: false,
semi: true,
singleQuote: false,
quoteProps: "as-needed",
jsxSingleQuote: false,
trailingComma: "es5",
bracketSpacing: true,
bracketSameLine: true,
arrowParens: "always",
rangeStart: 0,
rangeEnd: Infinity,
requirePragma: false,
insertPragma: false,
proseWrap: "preserve",
htmlWhitespaceSensitivity: "ignore",
endOfLine: "lf",
singleAttributePerLine: false,
};

View File

@ -1 +1,91 @@
{"currencies":{"data":[{"id":"1","attributes":{"code":"EUR","symbol":"€","rate_to_usd":1.1062771,"display_decimals":true}},{"id":"2","attributes":{"code":"CAD","symbol":"$","rate_to_usd":0.79319156,"display_decimals":true}},{"id":"3","attributes":{"code":"USD","symbol":"$","rate_to_usd":1,"display_decimals":true}},{"id":"4","attributes":{"code":"JPY","symbol":"¥","rate_to_usd":0.0083864261,"display_decimals":false}},{"id":"5","attributes":{"code":"BRL","symbol":"R$","rate_to_usd":0.19904328,"display_decimals":true}},{"id":"6","attributes":{"code":"GBP","symbol":"£","rate_to_usd":1.3181323,"display_decimals":true}},{"id":"7","attributes":{"code":"AUD","symbol":"$","rate_to_usd":0.7422,"display_decimals":true}},{"id":"8","attributes":{"code":"INR","symbol":"₹","rate_to_usd":0.013162881,"display_decimals":false}},{"id":"9","attributes":{"code":"NZD","symbol":"$","rate_to_usd":0.69089984,"display_decimals":true}},{"id":"10","attributes":{"code":"CHF","symbol":"CHF","rate_to_usd":1.0728706,"display_decimals":true}}]}}
{
"currencies": {
"data": [
{
"id": "1",
"attributes": {
"code": "EUR",
"symbol": "€",
"rate_to_usd": 1.1062771,
"display_decimals": true
}
},
{
"id": "2",
"attributes": {
"code": "CAD",
"symbol": "$",
"rate_to_usd": 0.79319156,
"display_decimals": true
}
},
{
"id": "3",
"attributes": { "code": "USD", "symbol": "$", "rate_to_usd": 1, "display_decimals": true }
},
{
"id": "4",
"attributes": {
"code": "JPY",
"symbol": "¥",
"rate_to_usd": 0.0083864261,
"display_decimals": false
}
},
{
"id": "5",
"attributes": {
"code": "BRL",
"symbol": "R$",
"rate_to_usd": 0.19904328,
"display_decimals": true
}
},
{
"id": "6",
"attributes": {
"code": "GBP",
"symbol": "£",
"rate_to_usd": 1.3181323,
"display_decimals": true
}
},
{
"id": "7",
"attributes": {
"code": "AUD",
"symbol": "$",
"rate_to_usd": 0.7422,
"display_decimals": true
}
},
{
"id": "8",
"attributes": {
"code": "INR",
"symbol": "₹",
"rate_to_usd": 0.013162881,
"display_decimals": false
}
},
{
"id": "9",
"attributes": {
"code": "NZD",
"symbol": "$",
"rate_to_usd": 0.69089984,
"display_decimals": true
}
},
{
"id": "10",
"attributes": {
"code": "CHF",
"symbol": "CHF",
"rate_to_usd": 1.0728706,
"display_decimals": true
}
}
]
}
}

View File

@ -1 +1,36 @@
{"languages":{"data":[{"id":"1","attributes":{"name":"French","code":"fr","localized_name":"Français"}},{"id":"2","attributes":{"name":"English","code":"en","localized_name":"English"}},{"id":"3","attributes":{"name":"Japanese","code":"ja","localized_name":"日本語"}},{"id":"4","attributes":{"name":"Spanish","code":"es","localized_name":"Español"}},{"id":"6","attributes":{"name":"Portuguese (Brazil)","code":"pt-br","localized_name":"Português (Brasil)"}},{"id":"8","attributes":{"name":"German","code":"de","localized_name":"Deutsch"}},{"id":"9","attributes":{"name":"Italian","code":"it","localized_name":"Italiano"}},{"id":"10","attributes":{"name":"Russian","code":"ru","localized_name":"русский"}},{"id":"11","attributes":{"name":"Korean","code":"ko","localized_name":"한국어"}},{"id":"12","attributes":{"name":"Chinese (Traditional)","code":"zh-cht","localized_name":"中文(繁體)"}}]}}
{
"languages": {
"data": [
{ "id": "1", "attributes": { "name": "French", "code": "fr", "localized_name": "Français" } },
{ "id": "2", "attributes": { "name": "English", "code": "en", "localized_name": "English" } },
{ "id": "3", "attributes": { "name": "Japanese", "code": "ja", "localized_name": "日本語" } },
{ "id": "4", "attributes": { "name": "Spanish", "code": "es", "localized_name": "Español" } },
{
"id": "6",
"attributes": {
"name": "Portuguese (Brazil)",
"code": "pt-br",
"localized_name": "Português (Brasil)"
}
},
{ "id": "8", "attributes": { "name": "German", "code": "de", "localized_name": "Deutsch" } },
{
"id": "9",
"attributes": { "name": "Italian", "code": "it", "localized_name": "Italiano" }
},
{
"id": "10",
"attributes": { "name": "Russian", "code": "ru", "localized_name": "русский" }
},
{ "id": "11", "attributes": { "name": "Korean", "code": "ko", "localized_name": "한국어" } },
{
"id": "12",
"attributes": {
"name": "Chinese (Traditional)",
"code": "zh-cht",
"localized_name": "中文(繁體)"
}
}
]
}
}

File diff suppressed because one or more lines are too long

View File

@ -18,27 +18,14 @@ interface Props {
export const AnchorShare = ({ id, className }: Props): JSX.Element => {
const { langui } = useAppLayout();
return (
<ToolTip
content={langui.copy_anchor_link}
trigger="mouseenter"
className="text-sm"
>
<ToolTip
content={langui.anchor_link_copied}
trigger="click"
className="text-sm"
>
<ToolTip content={langui.copy_anchor_link} trigger="mouseenter" className="text-sm">
<ToolTip content={langui.anchor_link_copied} trigger="click" className="text-sm">
<Ico
icon={Icon.Link}
className={cJoin(
"transition-color cursor-pointer hover:text-dark",
className
)}
className={cJoin("transition-color cursor-pointer hover:text-dark", className)}
onClick={() => {
navigator.clipboard.writeText(
`${
process.env.NEXT_PUBLIC_URL_SELF + window.location.pathname
}#${id}`
`${process.env.NEXT_PUBLIC_URL_SELF + window.location.pathname}#${id}`
);
}}
/>

View File

@ -19,10 +19,7 @@ import { cIf, cJoin } from "helpers/className";
import { useAppLayout } from "contexts/AppLayoutContext";
import { Button } from "components/Inputs/Button";
import { OpenGraph, TITLE_PREFIX, TITLE_SEPARATOR } from "helpers/openGraph";
import {
useIs1ColumnLayout,
useIsScreenAtLeast,
} from "hooks/useContainerQuery";
import { useIs1ColumnLayout, useIsScreenAtLeast } from "hooks/useContainerQuery";
import { useOnResize } from "hooks/useOnResize";
import { Ids } from "types/ids";
@ -138,8 +135,7 @@ export const AppLayout = ({
const [currencySelect, setCurrencySelect] = useState<number>(-1);
useEffect(() => {
if (isDefined(currency))
setCurrencySelect(currencyOptions.indexOf(currency));
if (isDefined(currency)) setCurrencySelect(currencyOptions.indexOf(currency));
}, [currency, currencyOptions]);
useEffect(() => {
@ -147,14 +143,11 @@ export const AppLayout = ({
}, [currencyOptions, currencySelect, setCurrency]);
const isClient = useIsClient();
const { value: hasDisgardSafariWarning, setTrue: disgardSafariWarning } =
useBoolean(false);
const { value: hasDisgardSafariWarning, setTrue: disgardSafariWarning } = useBoolean(false);
const isSafari = useMemo<boolean>(() => {
if (isClient) {
const parser = new UAParser();
return (
parser.getBrowser().name === "Safari" || parser.getOS().name === "iOS"
);
return parser.getBrowser().name === "Safari" || parser.getOS().name === "iOS";
}
return false;
}, [isClient]);
@ -164,27 +157,22 @@ export const AppLayout = ({
className={cJoin(
cIf(darkMode, "set-theme-dark", "set-theme-light"),
cIf(dyslexic, "set-theme-font-dyslexic", "set-theme-font-standard")
)}
>
)}>
<div
{...handlers}
id={Ids.Body}
className={cJoin(
`fixed inset-0 m-0 grid touch-pan-y bg-light p-0 text-black
[grid-template-areas:'main_sub_content']`,
cIf(
is1ColumnLayout,
"grid-rows-[1fr_5rem] [grid-template-areas:'content''navbar']"
)
cIf(is1ColumnLayout, "grid-rows-[1fr_5rem] [grid-template-areas:'content''navbar']")
)}
style={{
gridTemplateColumns: is1ColumnLayout
? "1fr"
: `${
mainPanelReduced ? layout.mainMenuReduced : layout.mainMenu
}rem ${isDefined(subPanel) ? layout.subMenu : 0}rem 1fr`,
}}
>
: `${mainPanelReduced ? layout.mainMenuReduced : layout.mainMenu}rem ${
isDefined(subPanel) ? layout.subMenu : 0
}rem 1fr`,
}}>
<Head>
<title>{openGraph.title}</title>
<meta name="description" content={openGraph.description} />
@ -197,18 +185,9 @@ export const AppLayout = ({
<meta property="og:title" content={openGraph.title} />
<meta property="og:description" content={openGraph.description} />
<meta property="og:image" content={openGraph.thumbnail.image} />
<meta
property="og:image:secure_url"
content={openGraph.thumbnail.image}
/>
<meta
property="og:image:width"
content={openGraph.thumbnail.width.toString()}
/>
<meta
property="og:image:height"
content={openGraph.thumbnail.height.toString()}
/>
<meta property="og:image:secure_url" content={openGraph.thumbnail.image} />
<meta property="og:image:width" content={openGraph.thumbnail.width.toString()} />
<meta property="og:image:height" content={openGraph.thumbnail.height.toString()} />
<meta property="og:image:alt" content={openGraph.thumbnail.alt} />
<meta property="og:image:type" content="image/jpeg" />
</Head>
@ -230,22 +209,16 @@ export const AppLayout = ({
"z-10 [backdrop-filter:blur(2px)]",
"pointer-events-none touch-none"
)
)}
>
)}>
<div
className={cJoin(
"absolute inset-0 bg-shade transition-opacity duration-500",
cIf(
(mainPanelOpen || subPanelOpen) && is1ColumnLayout,
"opacity-60",
"opacity-0"
)
cIf((mainPanelOpen || subPanelOpen) && is1ColumnLayout, "opacity-60", "opacity-0")
)}
onClick={() => {
setMainPanelOpen(false);
setSubPanelOpen(false);
}}
></div>
}}></div>
</div>
{/* Content panel */}
@ -254,8 +227,7 @@ export const AppLayout = ({
className={cJoin(
"texture-paper-dots bg-light [grid-area:content]",
cIf(contentPanelScroolbar, "overflow-y-scroll")
)}
>
)}>
{isDefined(contentPanel) ? (
contentPanel
) : (
@ -280,17 +252,10 @@ export const AppLayout = ({
[grid-area:content]`,
"[grid-area:sub]"
),
cIf(
is1ColumnLayout && isScreenAtLeastXs,
"w-[min(30rem,90%)] border-l-[1px]"
),
cIf(
is1ColumnLayout && !subPanelOpen && !turnSubIntoContent,
"translate-x-[100vw]"
),
cIf(is1ColumnLayout && isScreenAtLeastXs, "w-[min(30rem,90%)] border-l-[1px]"),
cIf(is1ColumnLayout && !subPanelOpen && !turnSubIntoContent, "translate-x-[100vw]"),
cIf(is1ColumnLayout && turnSubIntoContent, "w-full border-l-0")
)}
>
)}>
{subPanel}
</div>
)}
@ -300,15 +265,10 @@ export const AppLayout = ({
className={cJoin(
`texture-paper-dots overflow-y-scroll border-r-[1px] border-dark/50 bg-light
transition-transform duration-300 [scrollbar-width:none] webkit-scrollbar:w-0`,
cIf(
is1ColumnLayout,
"z-10 justify-self-start [grid-area:content]",
"[grid-area:main]"
),
cIf(is1ColumnLayout, "z-10 justify-self-start [grid-area:content]", "[grid-area:main]"),
cIf(is1ColumnLayout && isScreenAtLeastXs, "w-[min(30rem,90%)]"),
cIf(!mainPanelOpen && is1ColumnLayout, "-translate-x-full")
)}
>
)}>
<MainPanel />
</div>
@ -318,8 +278,7 @@ export const AppLayout = ({
`texture-paper-dots grid grid-cols-[5rem_1fr_5rem] place-items-center
border-t-[1px] border-dotted border-black bg-light [grid-area:navbar]`,
cIf(!is1ColumnLayout, "hidden")
)}
>
)}>
<Ico
icon={mainPanelOpen ? Icon.Close : Icon.Menu}
className="mt-[.1em] cursor-pointer !text-2xl"
@ -331,19 +290,10 @@ export const AppLayout = ({
<p
className={cJoin(
"overflow-hidden text-center font-headers font-black",
cIf(
openGraph.title.length > 30,
"max-h-14 text-xl",
"max-h-16 text-2xl"
)
)}
>
{openGraph.title.substring(
TITLE_PREFIX.length + TITLE_SEPARATOR.length
)
? openGraph.title.substring(
TITLE_PREFIX.length + TITLE_SEPARATOR.length
)
cIf(openGraph.title.length > 30, "max-h-14 text-xl", "max-h-16 text-2xl")
)}>
{openGraph.title.substring(TITLE_PREFIX.length + TITLE_SEPARATOR.length)
? openGraph.title.substring(TITLE_PREFIX.length + TITLE_SEPARATOR.length)
: "Accords Library"}
</p>
{isDefined(subPanel) && !turnSubIntoContent && (
@ -358,26 +308,16 @@ export const AppLayout = ({
)}
</div>
<Popup
state={isSafari && !hasDisgardSafariWarning}
onClose={() => null}
>
<Popup state={isSafari && !hasDisgardSafariWarning} onClose={() => null}>
<h1 className="text-2xl">Hi, you are using Safari!</h1>
<p className="max-w-lg text-center">
In most cases this wouldn&rsquo;t be a problem but our website
isfor some obscure reasonperforming terribly on Safari (WebKit).
Because of that, we have decided to display this message instead of
letting you have a slow and painful experience. We are looking into
the problem, and are hoping to fix this soon.
</p>
<p>
In the meanwhile, if you are using an iPhone/iPad, please try using
another device.
</p>
<p>
If you are on macOS, please use another browser such as Firefox or
Chrome.
In most cases this wouldn&rsquo;t be a problem but our website isfor some obscure
reasonperforming terribly on Safari (WebKit). Because of that, we have decided to
display this message instead of letting you have a slow and painful experience. We are
looking into the problem, and are hoping to fix this soon.
</p>
<p>In the meanwhile, if you are using an iPhone/iPad, please try using another device.</p>
<p>If you are on macOS, please use another browser such as Firefox or Chrome.</p>
<Button
text="Let me in regardless"
@ -394,16 +334,14 @@ export const AppLayout = ({
onClose={() => {
setConfigPanelOpen(false);
umami("[Settings] Close settings");
}}
>
}}>
<h2 className="text-2xl">{langui.settings}</h2>
<div
className={cJoin(
`mt-4 grid justify-items-center gap-16 text-center`,
cIf(!is1ColumnLayout, "grid-cols-[auto_auto]")
)}
>
)}>
{router.locales && (
<div>
<h3 className="text-xl">{langui.languages}</h3>
@ -420,14 +358,11 @@ export const AppLayout = ({
},
{
insertAt: 1,
name:
langui.secondary_language ?? "Secondary languages",
name: langui.secondary_language ?? "Secondary languages",
},
]}
onChange={(items) => {
const newPreferredLanguages = items.map(
(item) => item.code
);
const newPreferredLanguages = items.map((item) => item.code);
setPreferredLanguages(newPreferredLanguages);
umami("[Settings] Change preferred languages");
}}
@ -439,8 +374,7 @@ export const AppLayout = ({
className={cJoin(
"grid place-items-center gap-8 text-center",
cIf(!is1ColumnLayout, "grid-cols-2")
)}
>
)}>
<div>
<h3 className="text-xl">{langui.theme}</h3>
<ButtonGroup
@ -483,9 +417,7 @@ export const AppLayout = ({
value={currencySelect}
onChange={(newCurrency) => {
setCurrencySelect(newCurrency);
umami(
`[Settings] Change currency (${currencyOptions[newCurrency]})}`
);
umami(`[Settings] Change currency (${currencyOptions[newCurrency]})}`);
}}
className="w-28"
/>
@ -500,12 +432,12 @@ export const AppLayout = ({
onClick: () => {
setFontSize((current) => current / 1.05);
umami(
`[Settings] Change font size (${(
(fontSize / 1.05) *
100
).toLocaleString(undefined, {
maximumFractionDigits: 0,
})}%)`
`[Settings] Change font size (${((fontSize / 1.05) * 100).toLocaleString(
undefined,
{
maximumFractionDigits: 0,
}
)}%)`
);
},
icon: Icon.TextDecrease,
@ -523,13 +455,12 @@ export const AppLayout = ({
onClick: () => {
setFontSize((current) => current * 1.05);
umami(
`[Settings] Change font size (${(
fontSize *
1.05 *
100
).toLocaleString(undefined, {
maximumFractionDigits: 0,
})}%)`
`[Settings] Change font size (${(fontSize * 1.05 * 100).toLocaleString(
undefined,
{
maximumFractionDigits: 0,
}
)}%)`
);
},
icon: Icon.TextIncrease,
@ -589,21 +520,13 @@ interface ContentPlaceholderProps {
icon?: Icon;
}
const ContentPlaceholder = ({
message,
icon,
}: ContentPlaceholderProps): JSX.Element => (
const ContentPlaceholder = ({ message, icon }: ContentPlaceholderProps): JSX.Element => (
<div className="grid h-full place-content-center">
<div
className="grid grid-flow-col place-items-center gap-9 rounded-2xl border-2 border-dotted
border-dark p-8 text-dark opacity-40"
>
border-dark p-8 text-dark opacity-40">
{isDefined(icon) && <Ico icon={icon} className="!text-[300%]" />}
<p
className={cJoin("w-64 text-2xl", cIf(!isDefined(icon), "text-center"))}
>
{message}
</p>
<p className={cJoin("w-64 text-2xl", cIf(!isDefined(icon), "text-center"))}>{message}</p>
</div>
</div>
);

View File

@ -15,12 +15,11 @@ interface Props {
export const Chip = ({ className, text }: 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`,
`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`,
className
)}
>
)}>
{text}
</div>
);

View File

@ -17,27 +17,19 @@ interface Props {
isActive?: boolean;
}
const ChroniclePreview = ({
date,
url,
title,
isActive,
}: Props): JSX.Element => (
const ChroniclePreview = ({ date, url, title, isActive }: Props): JSX.Element => (
<Link
href={url}
className={cJoin(
`flex w-full cursor-pointer gap-4 rounded-2xl py-4 px-5
text-left align-top outline outline-2 outline-offset-[-2px] outline-mid transition-all
hover:bg-mid hover:shadow-inner-sm hover:shadow-shade
hover:outline-[transparent] hover:active:shadow-inner hover:active:shadow-shade`,
`flex w-full cursor-pointer gap-4 rounded-2xl py-4 px-5 text-left align-top outline outline-2
outline-offset-[-2px] outline-mid transition-all hover:bg-mid hover:shadow-inner-sm
hover:shadow-shade hover:outline-[transparent] hover:active:shadow-inner
hover:active:shadow-shade`,
cIf(isActive, "bg-mid shadow-inner-sm shadow-shade outline-[transparent]")
)}
>
)}>
<div className="text-right">
<p>{date.year}</p>
<p className="text-sm text-dark">
{prettyMonthDay(date.month, date.day)}
</p>
<p className="text-sm text-dark">{prettyMonthDay(date.month, date.day)}</p>
</div>
<p className="text-lg leading-tight">{title}</p>
</Link>
@ -52,24 +44,13 @@ export const TranslatedChroniclePreview = ({
translations,
fallback,
...otherProps
}: TranslatedProps<
Parameters<typeof ChroniclePreview>[0],
"title"
>): JSX.Element => {
}: TranslatedProps<Parameters<typeof ChroniclePreview>[0], "title">): JSX.Element => {
const [selectedTranslation] = useSmartLanguage({
items: translations,
languageExtractor: useCallback(
(item: { language: string }): string => item.language,
[]
),
languageExtractor: useCallback((item: { language: string }): string => item.language, []),
});
return (
<ChroniclePreview
title={selectedTranslation?.title ?? fallback.title}
{...otherProps}
/>
);
return <ChroniclePreview title={selectedTranslation?.title ?? fallback.title} {...otherProps} />;
};
/*

View File

@ -3,7 +3,7 @@ import { useBoolean } from "usehooks-ts";
import { TranslatedChroniclePreview } from "./ChroniclePreview";
import { GetChroniclesChaptersQuery } from "graphql/generated";
import { filterHasAttributes } from "helpers/others";
import { prettyInlineTitle, prettySlug } from "helpers/formatters";
import { prettyInlineTitle, prettySlug, sJoin } from "helpers/formatters";
import { Ico, Icon } from "components/Ico";
import { compareDate } from "helpers/date";
import { TranslatedProps } from "types/TranslatedProps";
@ -17,20 +17,14 @@ import { useSmartLanguage } from "hooks/useSmartLanguage";
interface Props {
chronicles: NonNullable<
NonNullable<
NonNullable<
GetChroniclesChaptersQuery["chroniclesChapters"]
>["data"][number]["attributes"]
NonNullable<GetChroniclesChaptersQuery["chroniclesChapters"]>["data"][number]["attributes"]
>["chronicles"]
>["data"];
currentSlug?: string;
title: string;
}
const ChroniclesList = ({
chronicles,
currentSlug,
title,
}: Props): JSX.Element => {
const ChroniclesList = ({ chronicles, currentSlug, title }: Props): JSX.Element => {
const { value: isOpen, toggle: toggleOpen } = useBoolean(
chronicles.some((chronicle) => chronicle.attributes?.slug === currentSlug)
);
@ -38,33 +32,21 @@ const ChroniclesList = ({
return (
<div>
<div className="grid place-content-center">
<div
className="grid cursor-pointer grid-cols-[1em_1fr] gap-4"
onClick={toggleOpen}
>
<Ico
className="!text-xl"
icon={isOpen ? Icon.ArrowDropUp : Icon.ArrowDropDown}
/>
<div className="grid cursor-pointer grid-cols-[1em_1fr] gap-4" onClick={toggleOpen}>
<Ico className="!text-xl" icon={isOpen ? Icon.ArrowDropUp : Icon.ArrowDropDown} />
<p className="mb-4 font-headers text-xl">{title}</p>
</div>
</div>
<div
className="grid gap-4 overflow-hidden transition-[max-height] duration-500"
style={{ maxHeight: isOpen ? `${8 * chronicles.length}rem` : 0 }}
>
style={{ maxHeight: isOpen ? `${8 * chronicles.length}rem` : 0 }}>
{filterHasAttributes(chronicles, [
"attributes.contents",
"attributes.translations",
] as const)
.sort((a, b) =>
compareDate(a.attributes.date_start, b.attributes.date_start)
)
.sort((a, b) => compareDate(a.attributes.date_start, b.attributes.date_start))
.map((chronicle) => (
<div
key={chronicle.id}
id={`chronicle-${chronicle.attributes.slug}`}
>
<div key={chronicle.id} id={`chronicle-${chronicle.attributes.slug}`}>
{chronicle.attributes.translations.length === 0 &&
chronicle.attributes.contents.data.length === 1
? filterHasAttributes(chronicle.attributes.contents.data, [
@ -74,10 +56,9 @@ const ChroniclesList = ({
key={index}
isActive={chronicle.attributes.slug === currentSlug}
date={chronicle.attributes.date_start}
translations={filterHasAttributes(
content.attributes.translations,
["language.data.attributes.code"] as const
).map((translation) => ({
translations={filterHasAttributes(content.attributes.translations, [
"language.data.attributes.code",
] as const).map((translation) => ({
title: prettyInlineTitle(
translation.pre_title,
translation.title,
@ -88,24 +69,34 @@ const ChroniclesList = ({
fallback={{
title: prettySlug(chronicle.attributes.slug),
}}
url={`/chronicles/${chronicle.attributes.slug}/#chronicle-${chronicle.attributes.slug}`}
url={sJoin(
"/chronicles/",
chronicle.attributes.slug,
"/#chronicle-",
chronicle.attributes.slug
)}
/>
))
: chronicle.attributes.translations.length > 0 && (
<TranslatedChroniclePreview
date={chronicle.attributes.date_start}
isActive={chronicle.attributes.slug === currentSlug}
translations={filterHasAttributes(
chronicle.attributes.translations,
["language.data.attributes.code", "title"] as const
).map((translation) => ({
translations={filterHasAttributes(chronicle.attributes.translations, [
"language.data.attributes.code",
"title",
] as const).map((translation) => ({
title: translation.title,
language: translation.language.data.attributes.code,
}))}
fallback={{
title: prettySlug(chronicle.attributes.slug),
}}
url={`/chronicles/${chronicle.attributes.slug}/#chronicle-${chronicle.attributes.slug}`}
url={sJoin(
"/chronicles/",
chronicle.attributes.slug,
"/#chronicle-",
chronicle.attributes.slug
)}
/>
)}
</div>
@ -127,16 +118,8 @@ export const TranslatedChroniclesList = ({
}: TranslatedProps<Props, "title">): JSX.Element => {
const [selectedTranslation] = useSmartLanguage({
items: translations,
languageExtractor: useCallback(
(item: { language: string }): string => item.language,
[]
),
languageExtractor: useCallback((item: { language: string }): string => item.language, []),
});
return (
<ChroniclesList
title={selectedTranslation?.title ?? fallback.title}
{...otherProps}
/>
);
return <ChroniclesList title={selectedTranslation?.title ?? fallback.title} {...otherProps} />;
};

View File

@ -13,9 +13,5 @@ interface Props {
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>
className={cJoin("my-8 h-0 w-full border-t-[3px] border-dotted border-black", className)}></div>
);

View File

@ -17,11 +17,7 @@ interface Props {
export const Ico = ({ onClick, icon, className }: Props): JSX.Element => (
<span
onClick={onClick}
className={cJoin(
"material-icons [font-size:inherit] [line-height:inherit]",
className
)}
>
className={cJoin("material-icons [font-size:inherit] [line-height:inherit]", className)}>
{icon}
</span>
);

View File

@ -8,10 +8,7 @@ import { getAssetURL, ImageQuality } from "helpers/img";
*/
interface Props
extends Omit<
DetailedHTMLProps<ImgHTMLAttributes<HTMLImageElement>, HTMLImageElement>,
"src"
> {
extends Omit<DetailedHTMLProps<ImgHTMLAttributes<HTMLImageElement>, HTMLImageElement>, "src"> {
src: UploadImageFragment | string;
quality?: ImageQuality;
}
@ -26,15 +23,6 @@ export const Img = ({
loading = "lazy",
...otherProps
}: Props): JSX.Element => {
const src =
typeof rawSrc === "string" ? rawSrc : getAssetURL(rawSrc.url, quality);
return (
<img
className={className}
src={src}
alt={alt}
loading={loading}
{...otherProps}
/>
);
const src = typeof rawSrc === "string" ? rawSrc : getAssetURL(rawSrc.url, quality);
return <img className={className} src={src} alt={alt} loading={loading} {...otherProps} />;
};

View File

@ -46,8 +46,7 @@ export const Button = ({
<ConditionalWrapper
isWrapping={isDefinedAndNotEmpty(href)}
wrapperProps={{ href: href ?? "", alwaysNewTab }}
wrapper={LinkWrapper}
>
wrapper={LinkWrapper}>
<div className="relative">
<div
draggable={draggable}
@ -56,36 +55,32 @@ export const Button = ({
onFocus={(event) => event.target.blur()}
className={cJoin(
`group grid cursor-pointer select-none grid-flow-col place-content-center
place-items-center gap-2 rounded-full border-[1px] border-dark py-3 px-4
leading-none text-dark transition-all`,
place-items-center gap-2 rounded-full border-[1px] border-dark py-3 px-4
leading-none text-dark transition-all`,
cIf(
active,
"!border-black bg-black !text-light drop-shadow-black-lg",
`hover:bg-dark hover:text-light hover:drop-shadow-shade-lg active:hover:!border-black
active:hover:bg-black active:hover:!text-light active:hover:drop-shadow-black-lg`
active:hover:bg-black active:hover:!text-light active:hover:drop-shadow-black-lg`
),
cIf(size === "small", "px-3 py-1 text-xs"),
cIf(disabled, "cursor-not-allowed"),
className
)}
>
)}>
{isDefined(badgeNumber) && (
<div
className={cJoin(
`absolute -top-3 -right-2 grid h-8 w-8 place-items-center
rounded-full bg-dark font-bold text-light transition-opacity group-hover:opacity-0`,
`absolute -top-3 -right-2 grid h-8 w-8 place-items-center rounded-full bg-dark
font-bold text-light transition-opacity group-hover:opacity-0`,
cIf(size === "small", "-top-2 -right-2 h-5 w-5")
)}
>
)}>
<p className="-translate-y-[0.05em]">{badgeNumber}</p>
</div>
)}
{isDefinedAndNotEmpty(icon) && (
<Ico className="[font-size:150%] [line-height:0.66]" icon={icon} />
)}
{isDefinedAndNotEmpty(text) && (
<p className="-translate-y-[0.05em] text-center">{text}</p>
)}
{isDefinedAndNotEmpty(text) && <p className="-translate-y-[0.05em] text-center">{text}</p>}
</div>
</div>
</ConditionalWrapper>
@ -103,15 +98,10 @@ export const TranslatedButton = ({
}: TranslatedProps<Props, "text">): JSX.Element => {
const [selectedTranslation] = useSmartLanguage({
items: translations,
languageExtractor: useCallback(
(item: { language: string }): string => item.language,
[]
),
languageExtractor: useCallback((item: { language: string }): string => item.language, []),
});
return (
<Button text={selectedTranslation?.text ?? fallback.text} {...otherProps} />
);
return <Button text={selectedTranslation?.text ?? fallback.text} {...otherProps} />;
};
/*
@ -124,11 +114,7 @@ interface LinkWrapperProps {
alwaysNewTab: boolean;
}
const LinkWrapper = ({
children,
alwaysNewTab,
href,
}: LinkWrapperProps & Wrapper) => (
const LinkWrapper = ({ children, alwaysNewTab, href }: LinkWrapperProps & Wrapper) => (
<Link href={href} alwaysNewTab={alwaysNewTab}>
{children}
</Link>

View File

@ -18,18 +18,14 @@ interface Props {
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
export const ButtonGroup = ({
buttonsProps,
className,
}: Props): JSX.Element => (
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 ?? "" }}
>
wrapperProps={{ text: buttonProps.tooltip ?? "" }}>
<Button
{...buttonProps}
className={

View File

@ -49,8 +49,7 @@ export const LanguageSwitcher = ({
</Fragment>
))}
</div>
}
>
}>
<Button
badgeNumber={showBadge && locales.size > 1 ? locales.size : undefined}
icon={Icon.Translate}

View File

@ -48,8 +48,7 @@ export const Link = ({
}
}
}
}}
>
}}>
{children}
</div>
);

View File

@ -16,11 +16,7 @@ interface Props {
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
export const OrderableList = ({
onChange,
items,
insertLabels,
}: Props): JSX.Element => {
export const OrderableList = ({ onChange, items, insertLabels }: Props): JSX.Element => {
const updateOrder = useCallback(
(sourceIndex: number, targetIndex: number) => {
console.log("updateOrder");
@ -57,17 +53,13 @@ export const OrderableList = ({
.filter((element) => element.tagName === "DIV")
.indexOf(target)
: -1;
const sourceIndex = parseInt(
event.dataTransfer.getData("text"),
10
);
const sourceIndex = parseInt(event.dataTransfer.getData("text"), 10);
updateOrder(sourceIndex, targetIndex);
}}
className="grid cursor-grab select-none grid-cols-[auto_1fr] place-content-center gap-2
rounded-full border-[1px] border-dark bg-light px-1 py-2 pr-4 text-dark transition-all
hover:bg-dark hover:text-light hover:drop-shadow-shade-lg"
draggable
>
draggable>
<div className="grid grid-rows-[.8em_.8em] place-items-center">
{index > 0 && (
<Ico

View File

@ -16,12 +16,7 @@ interface Props {
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
export const PageSelector = ({
page,
className,
pagesCount,
onChange,
}: Props): JSX.Element => (
export const PageSelector = ({ page, className, pagesCount, onChange }: Props): JSX.Element => (
<ButtonGroup
className={cJoin("flex flex-row place-content-center", className)}
buttonsProps={[

View File

@ -19,18 +19,8 @@ interface Props {
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
export const Select = ({
className,
value,
options,
allowEmpty,
onChange,
}: Props): JSX.Element => {
const {
value: isOpened,
setFalse: setClosed,
toggle: toggleOpened,
} = useBoolean(false);
export const Select = ({ className, value, options, allowEmpty, onChange }: Props): JSX.Element => {
const { value: isOpened, setFalse: setClosed, toggle: toggleOpened } = useBoolean(false);
const tryToggling = useCallback(() => {
const optionCount = options.length + (value === -1 ? 1 : 0);
@ -47,16 +37,14 @@ export const Select = ({
"relative text-center transition-[filter]",
cIf(isOpened, "z-10 drop-shadow-shade-lg"),
className
)}
>
)}>
<div
className={cJoin(
`grid cursor-pointer grid-flow-col grid-cols-[1fr_auto_auto] place-items-center
rounded-[1em] bg-light p-1 outline outline-2 outline-offset-[-2px] outline-mid
transition-all hover:bg-mid hover:outline-[transparent]`,
cIf(isOpened, "rounded-b-none bg-highlight outline-[transparent]")
)}
>
)}>
<p onClick={tryToggling} className="w-full">
{value === -1 ? "—" : options[value]}
</p>
@ -70,17 +58,9 @@ export const Select = ({
}}
/>
)}
<Ico
onClick={tryToggling}
icon={isOpened ? Icon.ArrowDropUp : Icon.ArrowDropDown}
/>
<Ico onClick={tryToggling} icon={isOpened ? Icon.ArrowDropUp : Icon.ArrowDropDown} />
</div>
<div
className={cJoin(
"left-0 right-0 rounded-b-[1em]",
cIf(isOpened, "absolute", "hidden")
)}
>
<div className={cJoin("left-0 right-0 rounded-b-[1em]", cIf(isOpened, "absolute", "hidden"))}>
{options.map((option, index) => (
<Fragment key={index}>
{index !== value && (
@ -93,8 +73,7 @@ export const Select = ({
onClick={() => {
setClosed();
onChange(index);
}}
>
}}>
{option}
</div>
)}

View File

@ -14,12 +14,7 @@ interface Props {
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
export const Switch = ({
value,
onClick,
className,
disabled = false,
}: Props): JSX.Element => (
export const Switch = ({ value, onClick, className, disabled = false }: Props): JSX.Element => (
<div
className={cJoin(
"relative grid h-6 w-12 rounded-full border-2 border-mid transition-colors",
@ -29,17 +24,11 @@ export const Switch = ({
)}
onClick={() => {
if (!disabled) onClick();
}}
>
}}>
<div
className={cJoin(
"absolute aspect-square rounded-full bg-dark transition-transform",
cIf(
value,
"top-[2px] bottom-[2px] left-[2px] translate-x-[120%]",
"top-0 bottom-0 left-0"
)
)}
></div>
cIf(value, "top-[2px] bottom-[2px] left-[2px] translate-x-[120%]", "top-0 bottom-0 left-0")
)}></div>
</div>
);

View File

@ -14,23 +14,14 @@ interface Props {
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
export const WithLabel = ({
label,
children,
disabled,
}: Props): JSX.Element => (
export const WithLabel = ({ label, children, disabled }: Props): JSX.Element => (
<div
className={cJoin(
"flex flex-row place-content-between place-items-center gap-2",
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>
<p className={cJoin("text-left", cIf(label.length < 10, "flex-shrink-0"))}>{label}:</p>
)}
{children}
</div>

View File

@ -16,11 +16,7 @@ interface Props {
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
)}
>
className={cJoin("w-full rounded-xl bg-mid p-8 shadow-inner-sm shadow-shade", className)}>
{children}
</div>
);

View File

@ -1,8 +1,9 @@
import { Icon } from "components/Ico";
import { Button } from "components/Inputs/Button";
import { ToolTip } from "components/ToolTip";
import { LibraryItemUserStatus } from "helpers/types";
import { LibraryItemUserStatus } from "types/types";
import { useAppLayout } from "contexts/AppLayoutContext";
import { cIf, cJoin } from "helpers/className";
/*
*
@ -17,52 +18,49 @@ interface Props {
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
export const PreviewCardCTAs = ({ id, expand = false }: Props): JSX.Element => {
const { libraryItemUserStatus, setLibraryItemUserStatus, langui } =
useAppLayout();
const { libraryItemUserStatus, setLibraryItemUserStatus, langui } = useAppLayout();
return (
<>
<div
className={`flex flex-row flex-wrap 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={libraryItemUserStatus[id] === LibraryItemUserStatus.Want}
onClick={(event) => {
event.preventDefault();
setLibraryItemUserStatus((current) => {
const newLibraryItemUserStatus = { ...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={libraryItemUserStatus[id] === LibraryItemUserStatus.Have}
onClick={(event) => {
event.preventDefault();
setLibraryItemUserStatus((current) => {
const newLibraryItemUserStatus = { ...current };
newLibraryItemUserStatus[id] =
newLibraryItemUserStatus[id] === LibraryItemUserStatus.Have
? LibraryItemUserStatus.None
: LibraryItemUserStatus.Have;
return newLibraryItemUserStatus;
});
}}
/>
</ToolTip>
</div>
</>
<div
className={cJoin(
"flex flex-row flex-wrap place-content-center place-items-center",
cIf(expand, "gap-4", "gap-2")
)}>
<ToolTip content={langui.want_it} disabled={expand}>
<Button
icon={Icon.Favorite}
text={expand ? langui.want_it : undefined}
active={libraryItemUserStatus[id] === LibraryItemUserStatus.Want}
onClick={(event) => {
event.preventDefault();
setLibraryItemUserStatus((current) => {
const newLibraryItemUserStatus = { ...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={libraryItemUserStatus[id] === LibraryItemUserStatus.Have}
onClick={(event) => {
event.preventDefault();
setLibraryItemUserStatus((current) => {
const newLibraryItemUserStatus = { ...current };
newLibraryItemUserStatus[id] =
newLibraryItemUserStatus[id] === LibraryItemUserStatus.Have
? LibraryItemUserStatus.None
: LibraryItemUserStatus.Have;
return newLibraryItemUserStatus;
});
}}
/>
</ToolTip>
</div>
);
};

View File

@ -21,9 +21,7 @@ const SENSIBILITY_SWIPE = 0.5;
*/
interface Props {
setState:
| Dispatch<SetStateAction<boolean | undefined>>
| Dispatch<SetStateAction<boolean>>;
setState: Dispatch<SetStateAction<boolean | undefined>> | Dispatch<SetStateAction<boolean>>;
state: boolean;
images: string[];
index: number;
@ -32,13 +30,7 @@ interface Props {
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
export const LightBox = ({
state,
setState,
images,
index,
setIndex,
}: Props): JSX.Element => {
export const LightBox = ({ state, setState, images, index, setIndex }: Props): JSX.Element => {
const handlePrevious = useCallback(() => {
if (index > 0) setIndex(index - 1);
}, [index, setIndex]);
@ -71,14 +63,8 @@ export const LightBox = ({
} else {
handleNext();
}
}}
>
<Popup
onClose={() => setState(false)}
state={state}
padding={false}
fillViewport
>
}}>
<Popup onClose={() => setState(false)} state={state} padding={false} fillViewport>
<div
{...handlers}
className={cJoin(
@ -88,18 +74,12 @@ export const LightBox = ({
`grid-cols-[4em,1fr,4em] [grid-template-areas:"left_image_right"]`,
`grid-cols-2 [grid-template-areas:"image_image""left_right"]`
)
)}
>
)}>
<div className="ml-4 [grid-area:left]">
{index > 0 && (
<Button onClick={handlePrevious} icon={Icon.ChevronLeft} />
)}
{index > 0 && <Button onClick={handlePrevious} icon={Icon.ChevronLeft} />}
</div>
<Img
className="max-h-full min-h-fit [grid-area:image]"
src={images[index]}
/>
<Img className="max-h-full min-h-fit [grid-area:image]" src={images[index]} />
<div className="mr-4 [grid-area:right]">
{index < images.length - 1 && (

View File

@ -29,10 +29,7 @@ interface MarkdawnProps {
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
export const Markdawn = ({
className,
text: rawText,
}: MarkdawnProps): JSX.Element => {
export const Markdawn = ({ className, text: rawText }: MarkdawnProps): JSX.Element => {
const { playerName } = useAppLayout();
const router = useRouter();
const isContentPanelAtLeastLg = useIsContentPanelAtLeast("lg");
@ -59,18 +56,10 @@ export const Markdawn = ({
slugify: slugify,
overrides: {
a: {
component: (compProps: {
href: string;
children: React.ReactNode;
}) => {
if (
compProps.href.startsWith("/") ||
compProps.href.startsWith("#")
) {
component: (compProps: { href: string; children: React.ReactNode }) => {
if (compProps.href.startsWith("/") || compProps.href.startsWith("#")) {
return (
<a onClick={async () => router.push(compProps.href)}>
{compProps.children}
</a>
<a onClick={async () => router.push(compProps.href)}>{compProps.children}</a>
);
}
return (
@ -112,11 +101,7 @@ export const Markdawn = ({
? slugify(compProps.target)
: slugify(compProps.children?.toString());
return (
<a
onClick={async () =>
router.replace(`${compProps.page ?? ""}#${slug}`)
}
>
<a onClick={async () => router.replace(`${compProps.page ?? ""}#${slug}`)}>
{compProps.children}
</a>
);
@ -128,13 +113,8 @@ export const Markdawn = ({
<div
className={cJoin(
"grid gap-x-6 gap-y-2",
cIf(
isContentPanelAtLeastLg,
"grid-cols-[auto_1fr]",
"grid-cols-1"
)
)}
>
cIf(isContentPanelAtLeastLg, "grid-cols-[auto_1fr]", "grid-cols-1")
)}>
{compProps.children}
</div>
),
@ -147,8 +127,7 @@ export const Markdawn = ({
className={cJoin(
"!my-0 text-dark/60",
cIf(!isContentPanelAtLeastLg, "!-mb-4")
)}
>
)}>
<Markdawn text={compProps.name} />
</strong>
<p className="whitespace-pre-line">{compProps.children}</p>
@ -157,9 +136,7 @@ export const Markdawn = ({
},
InsetBox: {
component: (compProps) => (
<InsetBox className="my-12">{compProps.children}</InsetBox>
),
component: (compProps) => <InsetBox className="my-12">{compProps.children}</InsetBox>,
},
li: {
@ -167,13 +144,10 @@ export const Markdawn = ({
<li
className={
isDefined(compProps.children) &&
ReactDOMServer.renderToStaticMarkup(
<>{compProps.children}</>
).length > 100
ReactDOMServer.renderToStaticMarkup(<>{compProps.children}</>).length > 100
? "my-4"
: ""
}
>
}>
{compProps.children}
</li>
),
@ -195,10 +169,7 @@ export const Markdawn = ({
},
blockquote: {
component: (compProps: {
children: React.ReactNode;
cite?: string;
}) => (
component: (compProps: { children: React.ReactNode; cite?: string }) => (
<blockquote>
{isDefinedAndNotEmpty(compProps.cite) ? (
<>
@ -229,8 +200,7 @@ export const Markdawn = ({
? getAssetURL(compProps.src, ImageQuality.Large)
: compProps.src,
]);
}}
>
}}>
<Img
src={
compProps.src.startsWith("/uploads/")
@ -238,14 +208,12 @@ export const Markdawn = ({
: compProps.src
}
quality={ImageQuality.Medium}
className="drop-shadow-shade-lg"
></Img>
className="drop-shadow-shade-lg"></Img>
</div>
),
},
},
}}
>
}}>
{text}
</Markdown>
</>
@ -269,10 +237,7 @@ export const TableOfContents = ({
}: TableOfContentsProps): JSX.Element => {
const router = useRouter();
const { langui } = useAppLayout();
const toc = useMemo(
() => getTocFromMarkdawn(preprocessMarkDawn(text), title),
[text, title]
);
const toc = useMemo(() => getTocFromMarkdawn(preprocessMarkDawn(text), title), [text, title]);
return (
<>
@ -283,8 +248,7 @@ export const TableOfContents = ({
<div className="max-w-[14.5rem] text-left">
<p
className="relative my-2 overflow-x-hidden text-ellipsis whitespace-nowrap
text-left"
>
text-left">
<a onClick={async () => router.replace(`#${toc.slug}`)}>
{<abbr title={toc.title}>{toc.title}</abbr>}
</a>
@ -324,10 +288,7 @@ const Header = ({ level, title, slug }: HeaderProps): JSX.Element => {
<div className="font-headers">{title}</div>
)}
<AnchorShare
className={cIf(
isHoverable,
"opacity-0 transition-opacity group-hover:opacity-100"
)}
className={cIf(isHoverable, "opacity-0 transition-opacity group-hover:opacity-100")}
id={slug}
/>
</div>
@ -395,10 +356,7 @@ const TocLevel = ({
}: LevelProps): JSX.Element => {
const router = useRouter();
const ids = useMemo(
() => tocchildren.map((child) => child.slug),
[tocchildren]
);
const ids = useMemo(() => tocchildren.map((child) => child.slug), [tocchildren]);
const currentIntersection = useIntersectionList(ids);
return (
@ -408,15 +366,9 @@ const TocLevel = ({
<li
className={cJoin(
"my-2 w-full overflow-x-hidden text-ellipsis whitespace-nowrap",
cIf(
allowIntersection && currentIntersection === childIndex,
"text-dark"
)
)}
>
<span className="text-dark">{`${parentNumbering}${
childIndex + 1
}.`}</span>{" "}
cIf(allowIntersection && currentIntersection === childIndex, "text-dark")
)}>
<span className="text-dark">{`${parentNumbering}${childIndex + 1}.`}</span>{" "}
<a onClick={async () => router.replace(`#${child.slug}`)}>
{<abbr title={child.title}>{child.title}</abbr>}
</a>
@ -424,9 +376,7 @@ const TocLevel = ({
<TocLevel
tocchildren={child.children}
parentNumbering={`${parentNumbering}${childIndex + 1}.`}
allowIntersection={
allowIntersection && currentIntersection === childIndex
}
allowIntersection={allowIntersection && currentIntersection === childIndex}
/>
</Fragment>
))}
@ -442,17 +392,13 @@ const TocLevel = ({
const preprocessMarkDawn = (text: string, playerName = ""): string => {
if (!text) return "";
const processedPlayerName = playerName
.replaceAll("_", "\\_")
.replaceAll("*", "\\*");
const processedPlayerName = playerName.replaceAll("_", "\\_").replaceAll("*", "\\*");
let preprocessed = text
.replaceAll("--", "—")
.replaceAll(
"@player",
isDefinedAndNotEmpty(processedPlayerName)
? processedPlayerName
: "(player)"
isDefinedAndNotEmpty(processedPlayerName) ? processedPlayerName : "(player)"
);
let scenebreakIndex = 0;
@ -511,8 +457,7 @@ const getTocFromMarkdawn = (text: string, title?: string): TocInterface => {
let scenebreak = 0;
let scenebreakIndex = 0;
const getTitle = (line: string): string =>
line.slice(line.indexOf(`">`) + 2, line.indexOf("</"));
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(`">`));
@ -573,9 +518,7 @@ const getTocFromMarkdawn = (text: string, title?: string): TocInterface => {
};
if (h5 >= 0) {
toc.children[h2].children[h3].children[h4].children[h5].children.push(
child
);
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) {

View File

@ -47,15 +47,12 @@ export const NavOption = ({
content={
<div>
<h3 className="text-2xl">{title}</h3>
{isDefinedAndNotEmpty(subtitle) && (
<p className="col-start-2">{subtitle}</p>
)}
{isDefinedAndNotEmpty(subtitle) && <p className="col-start-2">{subtitle}</p>}
</div>
}
placement="right"
className="text-left"
disabled={!reduced}
>
disabled={!reduced}>
<Link
href={url}
onClick={onClick}
@ -69,16 +66,13 @@ export const NavOption = ({
"outline outline-2 outline-offset-[-2px] outline-mid hover:outline-[transparent]"
),
cIf(isActive, "bg-mid shadow-inner-sm shadow-shade")
)}
>
)}>
{icon && <Ico icon={icon} className="mt-[-.1em] !text-2xl" />}
{!reduced && (
<div>
<h3 className="text-2xl">{title}</h3>
{isDefinedAndNotEmpty(subtitle) && (
<p className="col-start-2">{subtitle}</p>
)}
{isDefinedAndNotEmpty(subtitle) && <p className="col-start-2">{subtitle}</p>}
</div>
)}
</Link>
@ -98,10 +92,7 @@ export const TranslatedNavOption = ({
}: TranslatedProps<Props, "subtitle" | "title">): JSX.Element => {
const [selectedTranslation] = useSmartLanguage({
items: translations,
languageExtractor: useCallback(
(item: { language: string }): string => item.language,
[]
),
languageExtractor: useCallback((item: { language: string }): string => item.language, []),
});
return (
<NavOption

View File

@ -14,11 +14,7 @@ interface Props {
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
export const PanelHeader = ({
icon,
description,
title,
}: Props): JSX.Element => (
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" />}

View File

@ -22,12 +22,7 @@ interface Props {
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
export const ReturnButton = ({
href,
title,
displayOnlyOn,
className,
}: Props): JSX.Element => {
export const ReturnButton = ({ href, title, displayOnlyOn, className }: Props): JSX.Element => {
const { setSubPanelOpen, langui } = useAppLayout();
const is3ColumnsLayout = useIs3ColumnsLayout();
@ -61,16 +56,8 @@ export const TranslatedReturnButton = ({
}: TranslatedProps<Props, "title">): JSX.Element => {
const [selectedTranslation] = useSmartLanguage({
items: translations,
languageExtractor: useCallback(
(item: { language: string }): string => item.language,
[]
),
languageExtractor: useCallback((item: { language: string }): string => item.language, []),
});
return (
<ReturnButton
title={selectedTranslation?.title ?? fallback.title}
{...otherProps}
/>
);
return <ReturnButton title={selectedTranslation?.title ?? fallback.title} {...otherProps} />;
};

View File

@ -38,8 +38,7 @@ export const ContentPanel = ({
? "max-w-4xl"
: "w-full",
className
)}
>
)}>
{children}
</main>
</div>

View File

@ -29,16 +29,14 @@ export const MainPanel = (): JSX.Element => {
className={cJoin(
"grid content-start justify-center gap-y-2 p-8 text-center",
cIf(mainPanelReduced && is3ColumnsLayout, "px-4")
)}
>
)}>
{/* Reduce/expand main menu */}
{is3ColumnsLayout && (
<div
className={cJoin(
"fixed top-1/2",
cIf(mainPanelReduced, "left-[4.65rem]", "left-[18.65rem]")
)}
>
)}>
<Button
onClick={() => {
if (mainPanelReduced) {
@ -63,8 +61,7 @@ export const MainPanel = (): JSX.Element => {
[mask:url('/icons/accords.svg')] ![mask-size:contain] ![mask-repeat:no-repeat]
![mask-position:center] hover:bg-dark`,
cIf(mainPanelReduced && is3ColumnsLayout, "w-12", "w-1/2")
)}
></div>
)}></div>
</Link>
{(!mainPanelReduced || !is3ColumnsLayout) && (
@ -74,19 +71,13 @@ export const MainPanel = (): JSX.Element => {
<div
className={cJoin(
"flex flex-wrap gap-2",
cIf(
mainPanelReduced && is3ColumnsLayout,
"flex-col gap-3",
"flex-row"
)
)}
>
cIf(mainPanelReduced && is3ColumnsLayout, "flex-col gap-3", "flex-row")
)}>
<ToolTip
content={<h3 className="text-2xl">{langui.open_settings}</h3>}
placement="right"
className="text-left"
disabled={!mainPanelReduced}
>
disabled={!mainPanelReduced}>
<Button
onClick={() => {
setConfigPanelOpen(true);
@ -174,12 +165,7 @@ export const MainPanel = (): JSX.Element => {
{mainPanelReduced && is3ColumnsLayout ? "" : <HorizontalLine />}
<div
className={cJoin(
"text-center",
cIf(mainPanelReduced && is3ColumnsLayout, "hidden")
)}
>
<div className={cJoin("text-center", cIf(mainPanelReduced && is3ColumnsLayout, "hidden"))}>
{isDefinedAndNotEmpty(langui.licensing_notice) && (
<p>
<Markdown>{langui.licensing_notice}</Markdown>
@ -190,8 +176,7 @@ export const MainPanel = (): JSX.Element => {
onClick={() => umami("[MainPanel] Visit license")}
aria-label="Read more about the license we use for this website"
className="group grid grid-flow-col place-content-center gap-1 transition-[filter]"
href="https://creativecommons.org/licenses/by-sa/4.0/"
>
href="https://creativecommons.org/licenses/by-sa/4.0/">
<div
className="aspect-square w-6 bg-black transition-colors
[mask:url('/icons/creative-commons-brands.svg')] ![mask-size:contain]
@ -223,8 +208,7 @@ export const MainPanel = (): JSX.Element => {
![mask-size:contain] ![mask-repeat:no-repeat] ![mask-position:center] hover:bg-dark"
href="https://github.com/Accords-Library"
target="_blank"
rel="noopener noreferrer"
></a>
rel="noopener noreferrer"></a>
<a
aria-label="Follow us on Twitter"
onClick={() => umami("[MainPanel] Visit Twitter")}
@ -233,8 +217,7 @@ export const MainPanel = (): JSX.Element => {
![mask-size:contain] ![mask-repeat:no-repeat] ![mask-position:center] hover:bg-dark"
href="https://twitter.com/AccordsLibrary"
target="_blank"
rel="noopener noreferrer"
></a>
rel="noopener noreferrer"></a>
<a
aria-label="Join our Discord server!"
onClick={() => umami("[MainPanel] Visit Discord")}
@ -243,8 +226,7 @@ export const MainPanel = (): JSX.Element => {
![mask-size:contain] ![mask-repeat:no-repeat] ![mask-position:center] hover:bg-dark"
href="/discord"
target="_blank"
rel="noopener noreferrer"
></a>
rel="noopener noreferrer"></a>
</div>
</div>
</div>

View File

@ -19,8 +19,7 @@ export const SubPanel = ({ children }: Props): JSX.Element => {
className={cJoin(
"grid gap-y-2 text-center",
cIf(isSubPanelAtLeastSm, "px-10 pt-10 pb-20", "p-4")
)}
>
)}>
{children}
</div>
);

View File

@ -38,13 +38,8 @@ export const Popup = ({
<div
className={cJoin(
"fixed inset-0 z-50 grid place-content-center transition-[backdrop-filter] duration-500",
cIf(
state,
"[backdrop-filter:blur(2px)]",
"pointer-events-none touch-none"
)
)}
>
cIf(state, "[backdrop-filter:blur(2px)]", "pointer-events-none touch-none")
)}>
<div
className={cJoin(
"fixed inset-0 bg-shade transition-all duration-500",
@ -58,14 +53,9 @@ export const Popup = ({
"grid place-items-center gap-4 transition-transform",
cIf(padding, "p-10"),
cIf(state, "scale-100", "scale-0"),
cIf(
fillViewport,
"absolute inset-10",
"relative max-h-[80vh] overflow-y-auto"
),
cIf(fillViewport, "absolute inset-10", "relative max-h-[80vh] overflow-y-auto"),
cIf(!hideBackground, "rounded-lg bg-light shadow-2xl shadow-shade")
)}
>
)}>
{children}
</div>
</div>

View File

@ -10,7 +10,7 @@ import { RecorderChip } from "./RecorderChip";
import { ThumbnailHeader } from "./ThumbnailHeader";
import { ToolTip } from "./ToolTip";
import { useSmartLanguage } from "hooks/useSmartLanguage";
import { PostWithTranslations } from "helpers/types";
import { PostWithTranslations } from "types/types";
import { filterHasAttributes, getStatusDescription } from "helpers/others";
import { prettySlug } from "helpers/formatters";
import { useAppLayout } from "contexts/AppLayoutContext";
@ -49,21 +49,19 @@ export const PostPage = ({
...otherProps
}: Props): JSX.Element => {
const { langui } = useAppLayout();
const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] =
useSmartLanguage({
items: post.translations,
languageExtractor: useCallback(
(item: NonNullable<PostWithTranslations["translations"][number]>) =>
item.language?.data?.attributes?.code,
[]
),
});
const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] = useSmartLanguage({
items: post.translations,
languageExtractor: useCallback(
(item: NonNullable<PostWithTranslations["translations"][number]>) =>
item.language?.data?.attributes?.code,
[]
),
});
const { thumbnail, body, title, excerpt } = useMemo(
() => ({
thumbnail:
selectedTranslation?.thumbnail?.data?.attributes ??
post.thumbnail?.data?.attributes,
selectedTranslation?.thumbnail?.data?.attributes ?? post.thumbnail?.data?.attributes,
body: selectedTranslation?.body ?? "",
title: selectedTranslation?.title ?? prettySlug(post.slug),
excerpt: selectedTranslation?.excerpt ?? "",
@ -76,11 +74,7 @@ export const PostPage = ({
returnHref || returnTitle || displayCredits || displayToc ? (
<SubPanel>
{returnHref && returnTitle && (
<ReturnButton
href={returnHref}
title={returnTitle}
displayOnlyOn={"3ColumnsLayout"}
/>
<ReturnButton href={returnHref} title={returnTitle} displayOnlyOn={"3ColumnsLayout"} />
)}
{displayCredits && (
@ -92,12 +86,8 @@ export const PostPage = ({
<p className="font-headers font-bold">{langui.status}:</p>
<ToolTip
content={getStatusDescription(
selectedTranslation.status,
langui
)}
maxWidth={"20rem"}
>
content={getStatusDescription(selectedTranslation.status, langui)}
maxWidth={"20rem"}>
<Chip text={selectedTranslation.status} />
</ToolTip>
</div>
@ -107,23 +97,20 @@ export const PostPage = ({
<div>
<p className="font-headers font-bold">{"Authors"}:</p>
<div className="grid place-content-center place-items-center gap-2">
{filterHasAttributes(post.authors.data, [
"id",
"attributes",
] as const).map((author) => (
<Fragment key={author.id}>
<RecorderChip recorder={author.attributes} />
</Fragment>
))}
{filterHasAttributes(post.authors.data, ["id", "attributes"] as const).map(
(author) => (
<Fragment key={author.id}>
<RecorderChip recorder={author.attributes} />
</Fragment>
)
)}
</div>
</div>
)}
</>
)}
{displayToc && (
<TableOfContents text={body} title={title} horizontalLine />
)}
{displayToc && <TableOfContents text={body} title={title} horizontalLine />}
</SubPanel>
) : undefined,
[
@ -173,9 +160,7 @@ export const PostPage = ({
</div>
)}
{displayTitle && (
<h1 className="my-16 flex justify-center gap-3 text-center text-4xl">
{title}
</h1>
<h1 className="my-16 flex justify-center gap-3 text-center text-4xl">{title}</h1>
)}
</>
)}
@ -209,11 +194,5 @@ export const PostPage = ({
]
);
return (
<AppLayout
{...otherProps}
contentPanel={contentPanel}
subPanel={subPanel}
/>
);
return <AppLayout {...otherProps} contentPanel={contentPanel} subPanel={subPanel} />;
};

View File

@ -5,18 +5,9 @@ import { Ico, Icon } from "./Ico";
import { Img } from "./Img";
import { Link } from "./Inputs/Link";
import { useAppLayout } from "contexts/AppLayoutContext";
import {
DatePickerFragment,
PricePickerFragment,
UploadImageFragment,
} from "graphql/generated";
import { DatePickerFragment, PricePickerFragment, UploadImageFragment } from "graphql/generated";
import { cIf, cJoin } from "helpers/className";
import {
prettyDate,
prettyDuration,
prettyPrice,
prettyShortenNumber,
} from "helpers/formatters";
import { prettyDate, prettyDuration, prettyPrice, prettyShortenNumber } from "helpers/formatters";
import { ImageQuality } from "helpers/img";
import { useDeviceSupportsHover } from "hooks/useMediaQuery";
import { useSmartLanguage } from "hooks/useSmartLanguage";
@ -91,37 +82,25 @@ export const PreviewCard = ({
<div className="flex w-full flex-row flex-wrap gap-x-3">
{metadata.releaseDate && (
<p className="text-sm">
<Ico
icon={Icon.Event}
className="mr-1 translate-y-[.15em] !text-base"
/>
<Ico icon={Icon.Event} className="mr-1 translate-y-[.15em] !text-base" />
{prettyDate(metadata.releaseDate, router.locale)}
</p>
)}
{metadata.price && (
<p className="justify-self-end text-sm">
<Ico
icon={Icon.ShoppingCart}
className="mr-1 translate-y-[.15em] !text-base"
/>
<Ico icon={Icon.ShoppingCart} className="mr-1 translate-y-[.15em] !text-base" />
{prettyPrice(metadata.price, currencies, currency)}
</p>
)}
{metadata.views && (
<p className="text-sm">
<Ico
icon={Icon.Visibility}
className="mr-1 translate-y-[.15em] !text-base"
/>
<Ico icon={Icon.Visibility} className="mr-1 translate-y-[.15em] !text-base" />
{prettyShortenNumber(metadata.views)}
</p>
)}
{metadata.author && (
<p className="text-sm">
<Ico
icon={Icon.Person}
className="mr-1 translate-y-[.15em] !text-base"
/>
<Ico icon={Icon.Person} className="mr-1 translate-y-[.15em] !text-base" />
{metadata.author}
</p>
)}
@ -136,8 +115,7 @@ export const PreviewCard = ({
<Link
href={href}
className="group grid cursor-pointer items-end text-left transition-transform
drop-shadow-shade-xl hover:scale-[1.02]"
>
drop-shadow-shade-xl hover:scale-[1.02]">
{stackNumber > 0 && (
<>
<div
@ -145,14 +123,9 @@ export const PreviewCard = ({
`absolute inset-0 scale-[.85] overflow-hidden bg-light brightness-[0.8] sepia-[0.5]
transition-[top_transform] group-hover:-top-9`,
cIf(thumbnailRounded, "rounded-md")
)}
>
)}>
{thumbnail && (
<Img
className="opacity-30"
src={thumbnail}
quality={ImageQuality.Medium}
/>
<Img className="opacity-30" src={thumbnail} quality={ImageQuality.Medium} />
)}
</div>
@ -161,14 +134,9 @@ export const PreviewCard = ({
`absolute inset-0 overflow-hidden bg-light brightness-[0.9] sepia-[0.2]
transition-[top_transform] group-hover:-top-4 group-hover:scale-[.94]`,
cIf(thumbnailRounded, "rounded-md")
)}
>
)}>
{thumbnail && (
<Img
className="opacity-70"
src={thumbnail}
quality={ImageQuality.Medium}
/>
<Img className="opacity-70" src={thumbnail} quality={ImageQuality.Medium} />
)}
</div>
</>
@ -178,20 +146,13 @@ export const PreviewCard = ({
<div
className="relative"
style={{
aspectRatio: thumbnailForceAspectRatio
? thumbnailAspectRatio
: "unset",
}}
>
aspectRatio: thumbnailForceAspectRatio ? thumbnailAspectRatio : "unset",
}}>
<Img
className={cJoin(
cIf(
thumbnailRounded,
cIf(
keepInfoVisible,
"rounded-t-md",
"rounded-md notHoverable:rounded-b-none"
)
cIf(keepInfoVisible, "rounded-t-md", "rounded-md notHoverable:rounded-b-none")
),
cIf(thumbnailForceAspectRatio, "h-full w-full object-cover")
)}
@ -201,8 +162,7 @@ export const PreviewCard = ({
{stackNumber > 0 && (
<div
className="absolute right-2 top-2 rounded-full bg-black
bg-opacity-60 px-2 text-light"
>
bg-opacity-60 px-2 text-light">
{stackNumber}
</div>
)}
@ -210,8 +170,7 @@ export const PreviewCard = ({
<>
<div
className="absolute inset-0 grid place-content-center bg-shade bg-opacity-0
text-light transition-colors drop-shadow-shade-lg group-hover:bg-opacity-50"
>
text-light transition-colors drop-shadow-shade-lg group-hover:bg-opacity-50">
<Ico
icon={Icon.PlayCircleOutline}
className="!text-6xl opacity-0 transition-opacity group-hover:opacity-100"
@ -219,8 +178,7 @@ export const PreviewCard = ({
</div>
<div
className="absolute right-2 bottom-2 rounded-full bg-black bg-opacity-60 px-2
text-light"
>
text-light">
{prettyDuration(hoverlay.duration)}
</div>
</>
@ -231,18 +189,12 @@ export const PreviewCard = ({
style={{ aspectRatio: thumbnailAspectRatio }}
className={cJoin(
"relative w-full bg-light",
cIf(
keepInfoVisible,
"rounded-t-md",
"rounded-md notHoverable:rounded-b-none"
)
)}
>
cIf(keepInfoVisible, "rounded-t-md", "rounded-md notHoverable:rounded-b-none")
)}>
{stackNumber > 0 && (
<div
className="absolute right-2 top-2 rounded-full bg-black
bg-opacity-60 px-2 text-light"
>
bg-opacity-60 px-2 text-light">
{stackNumber}
</div>
)}
@ -258,8 +210,7 @@ export const PreviewCard = ({
notHoverable:rounded-b-md notHoverable:opacity-100`,
"[border-radius:0%_0%_10%_10%_/_0%_0%_3%_3%]"
)
)}
>
)}>
{metadata?.position === "Top" && metadataJSX}
{topChips && topChips.length > 0 && (
<div className="grid grid-flow-col place-content-start gap-1 overflow-hidden">
@ -269,13 +220,9 @@ export const PreviewCard = ({
</div>
)}
<div className="my-1">
{pre_title && (
<p className="mb-1 break-words leading-none">{pre_title}</p>
)}
{pre_title && <p className="mb-1 break-words leading-none">{pre_title}</p>}
{title && (
<p className="break-words font-headers text-lg font-bold leading-none">
{title}
</p>
<p className="break-words font-headers text-lg font-bold leading-none">{title}</p>
)}
{subtitle && <p className="break-words leading-none">{subtitle}</p>}
</div>
@ -283,8 +230,7 @@ export const PreviewCard = ({
{bottomChips && bottomChips.length > 0 && (
<div
className="grid grid-flow-col place-content-start gap-1 overflow-x-scroll
[scrollbar-width:none] webkit-scrollbar:h-0"
>
[scrollbar-width:none] webkit-scrollbar:h-0">
{bottomChips.map((text, index) => (
<Chip key={index} className="text-sm" text={text} />
))}
@ -308,16 +254,10 @@ export const TranslatedPreviewCard = ({
translations,
fallback,
...otherProps
}: TranslatedProps<
Props,
"description" | "pre_title" | "subtitle" | "title"
>): JSX.Element => {
}: TranslatedProps<Props, "description" | "pre_title" | "subtitle" | "title">): JSX.Element => {
const [selectedTranslation] = useSmartLanguage({
items: translations,
languageExtractor: useCallback(
(item: { language: string }): string => item.language,
[]
),
languageExtractor: useCallback((item: { language: string }): string => item.language, []),
});
return (
<PreviewCard

View File

@ -38,15 +38,10 @@ const PreviewLine = ({
<Link
href={href}
className="flex h-36 w-full cursor-pointer flex-row place-items-center gap-4 overflow-hidden
rounded-md bg-light pr-4 transition-transform drop-shadow-shade-xl hover:scale-[1.02]"
>
rounded-md bg-light pr-4 transition-transform drop-shadow-shade-xl hover:scale-[1.02]">
{thumbnail ? (
<div className="aspect-[3/2] h-full">
<Img
className="h-full object-cover"
src={thumbnail}
quality={ImageQuality.Medium}
/>
<Img className="h-full object-cover" src={thumbnail} quality={ImageQuality.Medium} />
</div>
) : (
<div style={{ aspectRatio: thumbnailAspectRatio }}></div>
@ -61,9 +56,7 @@ const PreviewLine = ({
)}
<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 font-bold leading-none">{title}</p>
)}
{title && <p className="font-headers text-lg font-bold leading-none">{title}</p>}
{subtitle && <p className="leading-none">{subtitle}</p>}
</div>
{bottomChips && bottomChips.length > 0 && (
@ -89,10 +82,7 @@ export const TranslatedPreviewLine = ({
}: TranslatedProps<Props, "pre_title" | "subtitle" | "title">): JSX.Element => {
const [selectedTranslation] = useSmartLanguage({
items: translations,
languageExtractor: useCallback(
(item: { language: string }): string => item.language,
[]
),
languageExtractor: useCallback((item: { language: string }): string => item.language, []),
});
return (
<PreviewLine

View File

@ -40,13 +40,13 @@ export const RecorderChip = ({ recorder }: Props): JSX.Element => {
{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, [
"attributes",
] as const).map((language) => (
<Fragment key={language.__typename}>
<Chip text={language.attributes.code.toUpperCase()} />
</Fragment>
))}
{filterHasAttributes(recorder.languages.data, ["attributes"] as const).map(
(language) => (
<Fragment key={language.__typename}>
<Chip text={language.attributes.code.toUpperCase()} />
</Fragment>
)
)}
</div>
)}
{recorder.pronouns && (
@ -60,15 +60,10 @@ export const RecorderChip = ({ recorder }: Props): JSX.Element => {
{recorder.bio?.[0] && <Markdawn text={recorder.bio[0].bio ?? ""} />}
</div>
}
placement="top"
>
placement="top">
<Chip
key={recorder.anonymous_code}
text={
recorder.anonymize
? `Recorder#${recorder.anonymous_code}`
: recorder.username
}
text={recorder.anonymize ? `Recorder#${recorder.anonymous_code}` : recorder.username}
/>
</ToolTip>
);

View File

@ -72,10 +72,7 @@ export const SmartList = <T,>({
const [page, setPage] = useState(0);
const { langui } = useAppLayout();
useScrollTopOnChange(Ids.ContentPanel, [page], paginationScroolTop);
useEffect(
() => setPage(0),
[searchingTerm, groupingFunction, groupSortingFunction, items]
);
useEffect(() => setPage(0), [searchingTerm, groupingFunction, groupSortingFunction, items]);
const searchFilter = useCallback(() => {
if (isDefinedAndNotEmpty(searchingTerm) && isDefined(searchingBy)) {
@ -118,12 +115,7 @@ export const SmartList = <T,>({
});
});
return memo.sort(groupSortingFunction);
}, [
groupCountingFunction,
groupSortingFunction,
groupingFunction,
sortedItem,
]);
}, [groupCountingFunction, groupSortingFunction, groupingFunction, sortedItem]);
const pages = useMemo(() => {
const memo: Group<T>[][] = [];
@ -174,12 +166,7 @@ export const SmartList = <T,>({
return (
<>
{pages.length > 1 && paginationSelectorTop && (
<PageSelector
className="mb-12"
page={page}
pagesCount={pages.length}
onChange={setPage}
/>
<PageSelector className="mb-12" page={page} pagesCount={pages.length} onChange={setPage} />
)}
<div className="mb-8">
@ -191,8 +178,7 @@ export const SmartList = <T,>({
{group.name.length > 0 && (
<h2
className="flex flex-row place-items-center gap-2 pb-2 pt-10 text-2xl
first-of-type:pt-0"
>
first-of-type:pt-0">
{group.name}
<Chip
text={`${group.totalCount} ${
@ -208,8 +194,7 @@ export const SmartList = <T,>({
`grid items-start gap-8 border-b-[3px] border-dotted pb-12
last-of-type:border-0`,
className
)}
>
)}>
{group.items.map((item) => (
<RenderItem item={item} key={getItemId(item)} />
))}
@ -225,12 +210,7 @@ export const SmartList = <T,>({
</div>
{pages.length > 1 && paginationSelectorBottom && (
<PageSelector
className="mb-12"
page={page}
pagesCount={pages.length}
onChange={setPage}
/>
<PageSelector className="mb-12" page={page} pagesCount={pages.length} onChange={setPage} />
)}
</>
);
@ -248,15 +228,10 @@ const DefaultRenderWhenEmpty = () => {
<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"
>
{is3ColumnsLayout && (
<Ico icon={Icon.ChevronLeft} className="!text-[300%]" />
)}
border-dark p-8 text-dark opacity-40">
{is3ColumnsLayout && <Ico icon={Icon.ChevronLeft} className="!text-[300%]" />}
<p className="max-w-xs text-2xl">{langui.no_results_message}</p>
{!is3ColumnsLayout && (
<Ico icon={Icon.ChevronRight} className="!text-[300%]" />
)}
{!is3ColumnsLayout && <Ico icon={Icon.ChevronRight} className="!text-[300%]" />}
</div>
</div>
);

View File

@ -64,11 +64,8 @@ export const ThumbnailHeader = ({
)}
</div>
<div
id={slugify(
prettyInlineTitle(pre_title ?? "", title, subtitle ?? "")
)}
className="grid place-items-center text-center"
>
id={slugify(prettyInlineTitle(pre_title ?? "", title, subtitle ?? ""))}
className="grid place-items-center text-center">
<p className="text-2xl">{pre_title}</p>
<h1 className="text-3xl">{title}</h1>
<h2 className="text-2xl">{subtitle}</h2>
@ -82,8 +79,7 @@ export const ThumbnailHeader = ({
<div className="flex flex-row flex-wrap">
<Chip
text={
type.data.attributes.titles?.[0]?.title ??
prettySlug(type.data.attributes.slug)
type.data.attributes.titles?.[0]?.title ?? prettySlug(type.data.attributes.slug)
}
/>
</div>
@ -94,20 +90,17 @@ export const ThumbnailHeader = ({
<div className="flex flex-col place-items-center gap-2">
<h3 className="text-xl">{langui.categories}</h3>
<div className="flex flex-row flex-wrap place-content-center gap-2">
{filterHasAttributes(categories.data, [
"attributes",
"id",
] as const).map((category) => (
<Chip key={category.id} text={category.attributes.name} />
))}
{filterHasAttributes(categories.data, ["attributes", "id"] as const).map(
(category) => (
<Chip key={category.id} text={category.attributes.name} />
)
)}
</div>
</div>
)}
{languageSwitcher}
</div>
{description && (
<InsetBox className="mt-8">{<Markdawn text={description} />}</InsetBox>
)}
{description && <InsetBox className="mt-8">{<Markdawn text={description} />}</InsetBox>}
</>
);
};

View File

@ -25,8 +25,7 @@ export const ToolTip = ({
delay={delay}
interactive={interactive}
animation={animation}
{...otherProps}
>
{...otherProps}>
<div>{children}</div>
</Tippy>
);

View File

@ -29,22 +29,13 @@ interface Props {
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
const DefinitionCard = ({
source,
translations = [],
index,
categories,
}: Props): JSX.Element => {
const DefinitionCard = ({ source, translations = [], index, categories }: Props): JSX.Element => {
const isContentPanelNoMoreThanMd = useIsContentPanelNoMoreThan("md");
const { langui } = useAppLayout();
const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] =
useSmartLanguage({
items: translations,
languageExtractor: useCallback(
(item: Props["translations"][number]) => item.language,
[]
),
});
const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] = useSmartLanguage({
items: translations,
languageExtractor: useCallback((item: Props["translations"][number]) => item.language, []),
});
return (
<>
@ -63,8 +54,7 @@ const DefinitionCard = ({
<Separator />
<ToolTip
content={getStatusDescription(selectedTranslation.status, langui)}
maxWidth={"20rem"}
>
maxWidth={"20rem"}>
<Chip text={selectedTranslation.status} />
</ToolTip>
</>
@ -89,8 +79,7 @@ const DefinitionCard = ({
className={cJoin(
"mt-3 flex place-items-center gap-2",
cIf(isContentPanelNoMoreThanMd, "flex-col text-center")
)}
>
)}>
<p>{langui.source}: </p>
<Button href={source.url} size="small" text={source.name} />
</div>

View File

@ -1,14 +1,8 @@
import React, {
ReactNode,
useContext,
useEffect,
useLayoutEffect,
useState,
} from "react";
import React, { ReactNode, useContext, useEffect, useLayoutEffect, useState } from "react";
import { useRouter } from "next/router";
import { useLocalStorage } from "usehooks-ts";
import { isDefined, isDefinedAndNotEmpty } from "helpers/others";
import { LibraryItemUserStatus, RequiredNonNullable } from "helpers/types";
import { LibraryItemUserStatus, RequiredNonNullable } from "types/types";
import { useDarkMode } from "hooks/useDarkMode";
import { Currencies, Languages, Langui } from "helpers/localData";
import { useCurrencies, useLanguages, useLangui } from "hooks/useLocalData";
@ -19,27 +13,19 @@ import { useScrollIntoView } from "hooks/useScrollIntoView";
interface AppLayoutState {
subPanelOpen: boolean;
toggleSubPanelOpen: () => void;
setSubPanelOpen: React.Dispatch<
React.SetStateAction<AppLayoutState["subPanelOpen"]>
>;
setSubPanelOpen: React.Dispatch<React.SetStateAction<AppLayoutState["subPanelOpen"]>>;
configPanelOpen: boolean;
toggleConfigPanelOpen: () => void;
setConfigPanelOpen: React.Dispatch<
React.SetStateAction<AppLayoutState["configPanelOpen"]>
>;
setConfigPanelOpen: React.Dispatch<React.SetStateAction<AppLayoutState["configPanelOpen"]>>;
mainPanelReduced: boolean;
toggleMainPanelReduced: () => void;
setMainPanelReduced: React.Dispatch<
React.SetStateAction<AppLayoutState["mainPanelReduced"]>
>;
setMainPanelReduced: React.Dispatch<React.SetStateAction<AppLayoutState["mainPanelReduced"]>>;
mainPanelOpen: boolean;
toggleMainPanelOpen: () => void;
setMainPanelOpen: React.Dispatch<
React.SetStateAction<AppLayoutState["mainPanelOpen"]>
>;
setMainPanelOpen: React.Dispatch<React.SetStateAction<AppLayoutState["mainPanelOpen"]>>;
darkMode: boolean;
toggleDarkMode: () => void;
@ -47,9 +33,7 @@ interface AppLayoutState {
selectedThemeMode: boolean;
toggleSelectedThemeMode: () => void;
setSelectedThemeMode: React.Dispatch<
React.SetStateAction<AppLayoutState["selectedThemeMode"]>
>;
setSelectedThemeMode: React.Dispatch<React.SetStateAction<AppLayoutState["selectedThemeMode"]>>;
fontSize: number;
setFontSize: React.Dispatch<React.SetStateAction<AppLayoutState["fontSize"]>>;
@ -62,20 +46,14 @@ interface AppLayoutState {
setCurrency: React.Dispatch<React.SetStateAction<AppLayoutState["currency"]>>;
playerName: string;
setPlayerName: React.Dispatch<
React.SetStateAction<AppLayoutState["playerName"]>
>;
setPlayerName: React.Dispatch<React.SetStateAction<AppLayoutState["playerName"]>>;
preferredLanguages: string[];
setPreferredLanguages: React.Dispatch<
React.SetStateAction<AppLayoutState["preferredLanguages"]>
>;
setPreferredLanguages: React.Dispatch<React.SetStateAction<AppLayoutState["preferredLanguages"]>>;
menuGestures: boolean;
toggleMenuGestures: () => void;
setMenuGestures: React.Dispatch<
React.SetStateAction<AppLayoutState["menuGestures"]>
>;
setMenuGestures: React.Dispatch<React.SetStateAction<AppLayoutState["menuGestures"]>>;
libraryItemUserStatus: Record<string, LibraryItemUserStatus>;
setLibraryItemUserStatus: React.Dispatch<
@ -83,19 +61,13 @@ interface AppLayoutState {
>;
screenWidth: number;
setScreenWidth: React.Dispatch<
React.SetStateAction<AppLayoutState["screenWidth"]>
>;
setScreenWidth: React.Dispatch<React.SetStateAction<AppLayoutState["screenWidth"]>>;
contentPanelWidth: number;
setContentPanelWidth: React.Dispatch<
React.SetStateAction<AppLayoutState["contentPanelWidth"]>
>;
setContentPanelWidth: React.Dispatch<React.SetStateAction<AppLayoutState["contentPanelWidth"]>>;
subPanelWidth: number;
setSubPanelWidth: React.Dispatch<
React.SetStateAction<AppLayoutState["subPanelWidth"]>
>;
setSubPanelWidth: React.Dispatch<React.SetStateAction<AppLayoutState["subPanelWidth"]>>;
langui: Langui;
languages: Languages;
@ -195,28 +167,18 @@ export const AppContextProvider = (props: Props): JSX.Element => {
initialState.mainPanelOpen
);
const [darkMode, selectedThemeMode, setDarkMode, setSelectedThemeMode] =
useDarkMode("darkMode", initialState.darkMode);
const [fontSize, setFontSize] = useLocalStorage(
"fontSize",
initialState.fontSize
const [darkMode, selectedThemeMode, setDarkMode, setSelectedThemeMode] = useDarkMode(
"darkMode",
initialState.darkMode
);
const [dyslexic, setDyslexic] = useLocalStorage(
"dyslexic",
initialState.dyslexic
);
const [fontSize, setFontSize] = useLocalStorage("fontSize", initialState.fontSize);
const [currency, setCurrency] = useLocalStorage(
"currency",
initialState.currency
);
const [dyslexic, setDyslexic] = useLocalStorage("dyslexic", initialState.dyslexic);
const [playerName, setPlayerName] = useLocalStorage(
"playerName",
initialState.playerName
);
const [currency, setCurrency] = useLocalStorage("currency", initialState.currency);
const [playerName, setPlayerName] = useLocalStorage("playerName", initialState.playerName);
const [preferredLanguages, setPreferredLanguages] = useLocalStorage(
"preferredLanguages",
@ -255,9 +217,7 @@ export const AppContextProvider = (props: Props): JSX.Element => {
};
const toggleSelectedThemeMode = () => {
setSelectedThemeMode((current) =>
isDefined(current) ? !current : current
);
setSelectedThemeMode((current) => (isDefined(current) ? !current : current));
};
const toggleDyslexic = () => {
@ -275,9 +235,7 @@ export const AppContextProvider = (props: Props): JSX.Element => {
useEffect(() => {
if (preferredLanguages.length === 0) {
if (isDefinedAndNotEmpty(router.locale) && router.locales) {
setPreferredLanguages(
getDefaultPreferredLanguages(router.locale, router.locales)
);
setPreferredLanguages(getDefaultPreferredLanguages(router.locale, router.locales));
}
} else if (router.locale !== preferredLanguages[0]) {
/*
@ -292,13 +250,7 @@ export const AppContextProvider = (props: Props): JSX.Element => {
250
);
}
}, [
preferredLanguages,
router,
router.locale,
router.locales,
setPreferredLanguages,
]);
}, [preferredLanguages, router, router.locale, router.locales, setPreferredLanguages]);
useEffect(() => {
router.events.on("routeChangeStart", () => {
@ -315,9 +267,7 @@ export const AppContextProvider = (props: Props): JSX.Element => {
}, [router.events, setConfigPanelOpen, setMainPanelOpen, setSubPanelOpen]);
useLayoutEffect(() => {
document.getElementsByTagName("html")[0].style.fontSize = `${
fontSize * 100
}%`;
document.getElementsByTagName("html")[0].style.fontSize = `${fontSize * 100}%`;
}, [fontSize]);
useScrollIntoView();
@ -368,8 +318,7 @@ export const AppContextProvider = (props: Props): JSX.Element => {
languages,
langui,
currencies,
}}
>
}}>
{props.children}
</AppContext.Provider>
);

View File

@ -13,7 +13,7 @@ const LOCAL_DATA_FOLDER = `${process.cwd()}/public/local-data`;
const writeLocalData = (name: LocalDataFile, localData: unknown) => {
const path = `${LOCAL_DATA_FOLDER}/${name}.json`;
writeFileSync(path, JSON.stringify(localData), { encoding: "utf-8" });
console.log(`${path} has been written!`)
console.log(`${path} has been written!`);
};
const readLocalData = <T>(name: LocalDataFile): T => {
@ -24,10 +24,7 @@ const readLocalData = <T>(name: LocalDataFile): T => {
const sdk = getReadySdk();
(async () => {
writeLocalData(
"websiteInterfaces",
await sdk.localDataGetWebsiteInterfaces()
);
writeLocalData("websiteInterfaces", await sdk.localDataGetWebsiteInterfaces());
writeLocalData("currencies", await sdk.localDataGetCurrencies());
writeLocalData("languages", await sdk.localDataGetLanguages());
})();
@ -37,7 +34,6 @@ const sdk = getReadySdk();
export type LocalDataFile = "currencies" | "languages" | "websiteInterfaces";
export const getLangui = (locale: string | undefined): Langui => {
const websiteInterfaces =
readLocalData<LocalDataGetWebsiteInterfacesQuery>("websiteInterfaces");
const websiteInterfaces = readLocalData<LocalDataGetWebsiteInterfacesQuery>("websiteInterfaces");
return processLangui(websiteInterfaces, locale);
};

View File

@ -1,13 +1,10 @@
import { GetStaticProps } from "next";
import { getReadySdk } from "./sdk";
import { getLangui } from "./fetchLocalData";
import { PostWithTranslations } from "helpers/types";
import { PostWithTranslations } from "types/types";
import { getOpenGraph } from "helpers/openGraph";
import { prettyDate, prettySlug } from "helpers/formatters";
import {
getDefaultPreferredLanguages,
staticSmartLanguage,
} from "helpers/locales";
import { getDefaultPreferredLanguages, staticSmartLanguage } from "helpers/locales";
import { filterHasAttributes, isDefined } from "helpers/others";
import { getDescription } from "helpers/description";
import { AppLayoutRequired } from "components/AppLayout";
@ -35,10 +32,7 @@ export const getPostStaticProps =
const selectedTranslation = staticSmartLanguage({
items: post.posts.data[0].attributes.translations,
languageExtractor: (item) => item.language?.data?.attributes?.code,
preferredLanguages: getDefaultPreferredLanguages(
context.locale,
context.locales
),
preferredLanguages: getDefaultPreferredLanguages(context.locale, context.locales),
});
const title = selectedTranslation?.title ?? prettySlug(slug);

View File

@ -91,9 +91,7 @@ query getChronicle($slug: String, $language_code: String) {
data {
attributes {
slug
titles(
filters: { language: { code: { eq: $language_code } } }
) {
titles(filters: { language: { code: { eq: $language_code } } }) {
title
}
}

View File

@ -1,8 +1,5 @@
query getChronologyItems {
chronologyItems(
pagination: { limit: -1 }
sort: ["year:asc", "month:asc", "day:asc"]
) {
chronologyItems(pagination: { limit: -1 }, sort: ["year:asc", "month:asc", "day:asc"]) {
data {
id
attributes {

View File

@ -67,11 +67,7 @@ query getContentText($slug: String, $language_code: String) {
data {
attributes {
slug
titles(
filters: {
language: { code: { eq: $language_code } }
}
) {
titles(filters: { language: { code: { eq: $language_code } } }) {
title
}
}
@ -93,11 +89,7 @@ query getContentText($slug: String, $language_code: String) {
data {
attributes {
slug
titles(
filters: {
language: { code: { eq: $language_code } }
}
) {
titles(filters: { language: { code: { eq: $language_code } } }) {
title
}
}
@ -109,11 +101,7 @@ query getContentText($slug: String, $language_code: String) {
data {
attributes {
slug
titles(
filters: {
language: { code: { eq: $language_code } }
}
) {
titles(filters: { language: { code: { eq: $language_code } } }) {
title
}
}
@ -125,11 +113,7 @@ query getContentText($slug: String, $language_code: String) {
data {
attributes {
slug
titles(
filters: {
language: { code: { eq: $language_code } }
}
) {
titles(filters: { language: { code: { eq: $language_code } } }) {
title
}
}
@ -139,11 +123,7 @@ query getContentText($slug: String, $language_code: String) {
data {
attributes {
slug
titles(
filters: {
language: { code: { eq: $language_code } }
}
) {
titles(filters: { language: { code: { eq: $language_code } } }) {
title
}
}
@ -257,11 +237,7 @@ query getContentText($slug: String, $language_code: String) {
data {
attributes {
slug
titles(
filters: {
language: { code: { eq: $language_code } }
}
) {
titles(filters: { language: { code: { eq: $language_code } } }) {
title
}
}

View File

@ -62,9 +62,7 @@ query getContentsFolder($slug: String, $language_code: String) {
data {
attributes {
slug
titles(
filters: { language: { code: { eq: $language_code } } }
) {
titles(filters: { language: { code: { eq: $language_code } } }) {
title
}
}

View File

@ -57,9 +57,7 @@ query getLibraryItem($slug: String, $language_code: String) {
data {
attributes {
slug
titles(
filters: { language: { code: { eq: $language_code } } }
) {
titles(filters: { language: { code: { eq: $language_code } } }) {
title
}
}
@ -82,9 +80,7 @@ query getLibraryItem($slug: String, $language_code: String) {
data {
attributes {
slug
titles(
filters: { language: { code: { eq: $language_code } } }
) {
titles(filters: { language: { code: { eq: $language_code } } }) {
title
}
}
@ -130,9 +126,7 @@ query getLibraryItem($slug: String, $language_code: String) {
data {
attributes {
slug
titles(
filters: { language: { code: { eq: $language_code } } }
) {
titles(filters: { language: { code: { eq: $language_code } } }) {
title
}
}
@ -144,9 +138,7 @@ query getLibraryItem($slug: String, $language_code: String) {
data {
attributes {
slug
titles(
filters: { language: { code: { eq: $language_code } } }
) {
titles(filters: { language: { code: { eq: $language_code } } }) {
title
}
}
@ -156,9 +148,7 @@ query getLibraryItem($slug: String, $language_code: String) {
data {
attributes {
slug
titles(
filters: { language: { code: { eq: $language_code } } }
) {
titles(filters: { language: { code: { eq: $language_code } } }) {
title
}
}
@ -212,11 +202,7 @@ query getLibraryItem($slug: String, $language_code: String) {
data {
attributes {
slug
titles(
filters: {
language: { code: { eq: $language_code } }
}
) {
titles(filters: { language: { code: { eq: $language_code } } }) {
title
}
}
@ -238,11 +224,7 @@ query getLibraryItem($slug: String, $language_code: String) {
data {
attributes {
slug
titles(
filters: {
language: { code: { eq: $language_code } }
}
) {
titles(filters: { language: { code: { eq: $language_code } } }) {
title
}
}
@ -254,11 +236,7 @@ query getLibraryItem($slug: String, $language_code: String) {
data {
attributes {
slug
titles(
filters: {
language: { code: { eq: $language_code } }
}
) {
titles(filters: { language: { code: { eq: $language_code } } }) {
title
}
}
@ -270,11 +248,7 @@ query getLibraryItem($slug: String, $language_code: String) {
data {
attributes {
slug
titles(
filters: {
language: { code: { eq: $language_code } }
}
) {
titles(filters: { language: { code: { eq: $language_code } } }) {
title
}
}
@ -284,11 +258,7 @@ query getLibraryItem($slug: String, $language_code: String) {
data {
attributes {
slug
titles(
filters: {
language: { code: { eq: $language_code } }
}
) {
titles(filters: { language: { code: { eq: $language_code } } }) {
title
}
}
@ -352,11 +322,7 @@ query getLibraryItem($slug: String, $language_code: String) {
data {
attributes {
slug
titles(
filters: {
language: { code: { eq: $language_code } }
}
) {
titles(filters: { language: { code: { eq: $language_code } } }) {
title
}
}

View File

@ -124,9 +124,7 @@ query getLibraryItemScans($slug: String, $language_code: String) {
data {
attributes {
slug
titles(
filters: { language: { code: { eq: $language_code } } }
) {
titles(filters: { language: { code: { eq: $language_code } } }) {
title
}
}
@ -148,9 +146,7 @@ query getLibraryItemScans($slug: String, $language_code: String) {
data {
attributes {
slug
titles(
filters: { language: { code: { eq: $language_code } } }
) {
titles(filters: { language: { code: { eq: $language_code } } }) {
title
}
}
@ -162,9 +158,7 @@ query getLibraryItemScans($slug: String, $language_code: String) {
data {
attributes {
slug
titles(
filters: { language: { code: { eq: $language_code } } }
) {
titles(filters: { language: { code: { eq: $language_code } } }) {
title
}
}
@ -176,9 +170,7 @@ query getLibraryItemScans($slug: String, $language_code: String) {
data {
attributes {
slug
titles(
filters: { language: { code: { eq: $language_code } } }
) {
titles(filters: { language: { code: { eq: $language_code } } }) {
title
}
}
@ -188,9 +180,7 @@ query getLibraryItemScans($slug: String, $language_code: String) {
data {
attributes {
slug
titles(
filters: { language: { code: { eq: $language_code } } }
) {
titles(filters: { language: { code: { eq: $language_code } } }) {
title
}
}

View File

@ -37,9 +37,7 @@ query getLibraryItemsPreview($language_code: String) {
data {
attributes {
slug
titles(
filters: { language: { code: { eq: $language_code } } }
) {
titles(filters: { language: { code: { eq: $language_code } } }) {
title
}
}
@ -61,9 +59,7 @@ query getLibraryItemsPreview($language_code: String) {
data {
attributes {
slug
titles(
filters: { language: { code: { eq: $language_code } } }
) {
titles(filters: { language: { code: { eq: $language_code } } }) {
title
}
}
@ -75,9 +71,7 @@ query getLibraryItemsPreview($language_code: String) {
data {
attributes {
slug
titles(
filters: { language: { code: { eq: $language_code } } }
) {
titles(filters: { language: { code: { eq: $language_code } } }) {
title
}
}
@ -89,9 +83,7 @@ query getLibraryItemsPreview($language_code: String) {
data {
attributes {
slug
titles(
filters: { language: { code: { eq: $language_code } } }
) {
titles(filters: { language: { code: { eq: $language_code } } }) {
title
}
}
@ -101,9 +93,7 @@ query getLibraryItemsPreview($language_code: String) {
data {
attributes {
slug
titles(
filters: { language: { code: { eq: $language_code } } }
) {
titles(filters: { language: { code: { eq: $language_code } } }) {
title
}
}

View File

@ -177,6 +177,9 @@ query localDataGetWebsiteInterfaces {
anchor_link_copied
folders
empty_folder_message
switch_to_grid_view
switch_to_folder_view
content_is_not_available
}
}
}

View File

@ -15,8 +15,4 @@ export const ConditionalWrapper = <T,>({
wrapper: Wrapper,
wrapperProps,
}: ConditionalWrapperProps<T>): JSX.Element =>
isWrapping ? (
<Wrapper {...wrapperProps}>{children}</Wrapper>
) : (
<>{children}</>
);
isWrapping ? <Wrapper {...wrapperProps}>{children}</Wrapper> : <>{children}</>;

View File

@ -8,10 +8,8 @@ export const compareDate = (
if (isUndefined(a) || isUndefined(b)) {
return 0;
}
const dateA =
(a.year ?? Infinity) * 365 + (a.month ?? 12) * 31 + (a.day ?? 31);
const dateB =
(b.year ?? Infinity) * 365 + (b.month ?? 12) * 31 + (b.day ?? 31);
const dateA = (a.year ?? Infinity) * 365 + (a.month ?? 12) * 31 + (a.day ?? 31);
const dateB = (b.year ?? Infinity) * 365 + (b.month ?? 12) * 31 + (b.day ?? 31);
return dateA - dateB;
};

View File

@ -58,7 +58,7 @@ export const prettyInlineTitle = (
return result;
};
export const prettyItemType = (metadata: any, langui: Langui): string => {
export const prettyItemType = (metadata: { __typename: string }, langui: Langui): string => {
switch (metadata.__typename) {
case "ComponentMetadataAudio":
return langui.audio ?? "Audio";
@ -242,8 +242,7 @@ export const prettyDuration = (seconds: number): string => {
export const prettyLanguage = (code: string, languages: Languages): string => {
let result = code;
languages.forEach((language) => {
if (language.attributes?.code === code)
result = language.attributes.localized_name;
if (language.attributes?.code === code) result = language.attributes.localized_name;
});
return result;
};
@ -254,8 +253,7 @@ export const prettyURL = (url: string): string => {
};
const capitalizeString = (string: string): string => {
const capitalizeWord = (word: string): string =>
word.charAt(0).toUpperCase() + word.substring(1);
const capitalizeWord = (word: string): string => word.charAt(0).toUpperCase() + word.substring(1);
let words = string.split(" ");
words = words.map((word) => capitalizeWord(word));
@ -281,3 +279,5 @@ export const slugify = (string: string | undefined): string => {
.trim()
.replace(/ /gu, "-");
};
export const sJoin = (...args: (string | null | undefined)[]): string => args.join("");

View File

@ -1,3 +1,5 @@
import { sJoin } from "./formatters";
export enum ImageQuality {
Small = "small",
Medium = "medium",
@ -17,7 +19,7 @@ export interface OgImage {
alt: string;
}
export const imageQualityProperties: Record<ImageQuality, ImageProperties> = {
const imageQualityProperties: Record<ImageQuality, ImageProperties> = {
small: { maxSize: 512, extension: "webp" },
medium: { maxSize: 1024, extension: "webp" },
large: { maxSize: 2048, extension: "webp" },
@ -37,11 +39,16 @@ export const getAssetFilename = (path: string): string => {
export const getAssetURL = (url: string, quality: ImageQuality): string => {
const indexEndPath = url.indexOf("/uploads/") + "/uploads/".length;
const indexStartExtension = url.lastIndexOf(".");
const assetPathWithoutExtension = url.slice(
indexEndPath,
indexStartExtension
const assetPathWithoutExtension = url.slice(indexEndPath, indexStartExtension);
return sJoin(
process.env.NEXT_PUBLIC_URL_IMG,
"/",
quality,
"/",
assetPathWithoutExtension,
".",
imageQualityProperties[quality].extension
);
return `${process.env.NEXT_PUBLIC_URL_IMG}/${quality}/${assetPathWithoutExtension}.${imageQualityProperties[quality].extension}`;
};
const getImgSizesByMaxSize = (

View File

@ -1,5 +1,16 @@
export const isUntangibleGroupItem = (metadata: any): boolean =>
metadata &&
import { isDefined } from "./others";
export const isUntangibleGroupItem = (
metadata:
| {
__typename: string;
// eslint-disable-next-line id-denylist
subtype?: { data?: { attributes?: { slug: string } | null } | null } | null;
}
| null
| undefined
): boolean =>
isDefined(metadata) &&
metadata.__typename === "ComponentMetadataGroup" &&
(metadata.subtype?.data?.attributes?.slug === "variant-set" ||
metadata.subtype?.data?.attributes?.slug === "relation-set");

View File

@ -5,9 +5,7 @@ import {
} from "graphql/generated";
export type Langui = NonNullable<
NonNullable<
LocalDataGetWebsiteInterfacesQuery["websiteInterfaces"]
>["data"][number]["attributes"]
NonNullable<LocalDataGetWebsiteInterfacesQuery["websiteInterfaces"]>["data"][number]["attributes"]
>;
export const processLangui = (
@ -15,24 +13,19 @@ export const processLangui = (
locale: string | undefined
): Langui =>
websiteInterfaces?.websiteInterfaces?.data.find(
(langui) =>
langui.attributes?.ui_language?.data?.attributes?.code === locale
(langui) => langui.attributes?.ui_language?.data?.attributes?.code === locale
)?.attributes ?? {};
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
export type Currencies = NonNullable<
LocalDataGetCurrenciesQuery["currencies"]
>["data"];
export type Currencies = NonNullable<LocalDataGetCurrenciesQuery["currencies"]>["data"];
export const processCurrencies = (
currencies: LocalDataGetCurrenciesQuery | undefined
): Currencies => {
if (currencies?.currencies?.data) {
currencies.currencies.data.sort((a, b) =>
a.attributes && b.attributes
? a.attributes.code.localeCompare(b.attributes.code)
: 0
a.attributes && b.attributes ? a.attributes.code.localeCompare(b.attributes.code) : 0
);
}
return currencies?.currencies?.data ?? [];
@ -40,13 +33,9 @@ export const processCurrencies = (
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
export type Languages = NonNullable<
LocalDataGetLanguagesQuery["languages"]
>["data"];
export type Languages = NonNullable<LocalDataGetLanguagesQuery["languages"]>["data"];
export const processLanguages = (
languages: LocalDataGetLanguagesQuery | undefined
): Languages => {
export const processLanguages = (languages: LocalDataGetLanguagesQuery | undefined): Languages => {
if (languages?.languages?.data) {
languages.languages.data.sort((a, b) =>
a.attributes && b.attributes

View File

@ -1,9 +1,6 @@
import { isDefined } from "./others";
export const getDefaultPreferredLanguages = (
routerLocal: string,
locales: string[]
): string[] => {
export const getDefaultPreferredLanguages = (routerLocal: string, locales: string[]): string[] => {
let defaultPreferredLanguages: string[] = [];
if (routerLocal === "en") {
defaultPreferredLanguages = [routerLocal];
@ -13,8 +10,7 @@ export const getDefaultPreferredLanguages = (
} else {
defaultPreferredLanguages = [routerLocal, "en"];
locales.map((locale) => {
if (locale !== routerLocal && locale !== "en")
defaultPreferredLanguages.push(locale);
if (locale !== routerLocal && locale !== "en") defaultPreferredLanguages.push(locale);
});
}
return defaultPreferredLanguages;

View File

@ -5,11 +5,7 @@ export const convertPrice = (
pricePicker: PricePickerFragment,
targetCurrency: Currencies[number]
): number => {
if (
pricePicker.amount &&
pricePicker.currency?.data?.attributes &&
targetCurrency.attributes
)
if (pricePicker.amount && pricePicker.currency?.data?.attributes && targetCurrency.attributes)
return (
(pricePicker.amount * pricePicker.currency.data.attributes.rate_to_usd) /
targetCurrency.attributes.rate_to_usd
@ -23,5 +19,4 @@ export const convertMmToInch = (mm: number | null | undefined): string =>
export const randomInt = (min: number, max: number): number =>
Math.floor(Math.random() * (max - min)) + min;
export const isInteger = (value: string): boolean =>
/^[+-]?[0-9]+$/u.test(value);
export const isInteger = (value: string): boolean => /^[+-]?[0-9]+$/u.test(value);

View File

@ -1,9 +1,4 @@
import {
OgImage,
getImgSizesByQuality,
ImageQuality,
getAssetURL,
} from "./img";
import { OgImage, getImgSizesByQuality, ImageQuality, getAssetURL } from "./img";
import { isDefinedAndNotEmpty } from "./others";
import { Langui } from "./localData";
import { UploadImageFragment } from "graphql/generated";
@ -30,21 +25,13 @@ export const getOpenGraph = (
description?: string | null | undefined,
thumbnail?: UploadImageFragment | null | undefined
): OpenGraph => ({
title: `${TITLE_PREFIX}${
isDefinedAndNotEmpty(title) ? `${TITLE_SEPARATOR}${title}` : ""
}`,
description: isDefinedAndNotEmpty(description)
? description
: langui.default_description ?? "",
title: `${TITLE_PREFIX}${isDefinedAndNotEmpty(title) ? `${TITLE_SEPARATOR}${title}` : ""}`,
description: isDefinedAndNotEmpty(description) ? description : langui.default_description ?? "",
thumbnail: thumbnail ? getOgImage(thumbnail) : DEFAULT_OG_THUMBNAIL,
});
const getOgImage = (image: UploadImageFragment): OgImage => {
const imgSize = getImgSizesByQuality(
image.width ?? 0,
image.height ?? 0,
ImageQuality.Og
);
const imgSize = getImgSizesByQuality(image.width ?? 0, image.height ?? 0, ImageQuality.Og);
return {
image: getAssetURL(image.url, ImageQuality.Og),
width: imgSize.width,

View File

@ -8,14 +8,10 @@ import {
type SortRangedContentProps =
| NonNullable<
NonNullable<
GetLibraryItemQuery["libraryItems"]
>["data"][number]["attributes"]
NonNullable<GetLibraryItemQuery["libraryItems"]>["data"][number]["attributes"]
>["contents"]
| NonNullable<
NonNullable<
GetLibraryItemScansQuery["libraryItems"]
>["data"][number]["attributes"]
NonNullable<GetLibraryItemScansQuery["libraryItems"]>["data"][number]["attributes"]
>["contents"];
export const sortRangedContent = (contents: SortRangedContentProps): void => {
@ -24,19 +20,13 @@ export const sortRangedContent = (contents: SortRangedContentProps): void => {
a.attributes?.range[0]?.__typename === "ComponentRangePageRange" &&
b.attributes?.range[0]?.__typename === "ComponentRangePageRange"
) {
return (
a.attributes.range[0].starting_page -
b.attributes.range[0].starting_page
);
return a.attributes.range[0].starting_page - b.attributes.range[0].starting_page;
}
return 0;
});
};
export const getStatusDescription = (
status: string,
langui: Langui
): string | null | undefined => {
export const getStatusDescription = (status: string, langui: Langui): string | null | undefined => {
switch (status) {
case Enum_Componentsetstextset_Status.Incomplete:
return langui.status_incomplete;
@ -55,30 +45,26 @@ export const getStatusDescription = (
}
};
export const isDefined = <T>(t: T): t is NonNullable<T> =>
t !== null && t !== undefined;
export const isDefined = <T>(t: T): t is NonNullable<T> => t !== null && t !== undefined;
export const isUndefined = <T>(
t: T | null | undefined
): t is null | undefined => t === null || t === undefined;
export const isUndefined = <T>(t: T | null | undefined): t is null | undefined =>
t === null || t === undefined;
export const isDefinedAndNotEmpty = (
string: string | null | undefined
): string is string => isDefined(string) && string.length > 0;
export const isDefinedAndNotEmpty = (string: string | null | undefined): string is string =>
isDefined(string) && string.length > 0;
export const filterDefined = <T>(t: T[] | null | undefined): NonNullable<T>[] =>
isUndefined(t)
? []
: (t.filter((item) => isDefined(item)) as NonNullable<T>[]);
isUndefined(t) ? [] : (t.filter((item) => isDefined(item)) as NonNullable<T>[]);
export const filterHasAttributes = <T, P extends PathDot<T>>(
t: T[] | null | undefined,
paths: readonly P[]
): SelectiveNonNullable<T, typeof paths[number]>[] =>
isDefined(t)
? (t.filter((item) =>
hasAttributes(item, paths)
) as unknown as SelectiveNonNullable<T, typeof paths[number]>[])
? (t.filter((item) => hasAttributes(item, paths)) as unknown as SelectiveNonNullable<
T,
typeof paths[number]
>[])
: [];
const hasAttributes = <T>(item: T, paths: readonly PathDot<T>[]): boolean =>
@ -112,11 +98,7 @@ export const iterateMap = <K, V, U>(
return toList.map(([key, value], index) => callbackfn(key, value, index));
};
export const arrayMove = <T>(
arr: T[],
sourceIndex: number,
targetIndex: number
): T[] => {
export const arrayMove = <T>(arr: T[], sourceIndex: number, targetIndex: number): T[] => {
arr.splice(targetIndex, 0, arr.splice(sourceIndex, 1)[0]);
return arr;
};

View File

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

View File

@ -18,10 +18,7 @@ const useFetchLocalData = (name: LocalDataFile) =>
export const useLangui = (): Langui => {
const { locale } = useRouter();
const { data: websiteInterfaces } = useFetchLocalData("websiteInterfaces");
return useMemo(
() => processLangui(websiteInterfaces, locale),
[websiteInterfaces, locale]
);
return useMemo(() => processLangui(websiteInterfaces, locale), [websiteInterfaces, locale]);
};
export const useCurrencies = (): Currencies => {

View File

@ -1,7 +1,5 @@
import { useMediaQuery } from "usehooks-ts";
export const useDeviceSupportsHover = (): boolean =>
useMediaQuery("(hover: hover)");
export const useDeviceSupportsHover = (): boolean => useMediaQuery("(hover: hover)");
export const usePrefersDarkMode = (): boolean =>
useMediaQuery("(prefers-color-scheme: dark)");
export const usePrefersDarkMode = (): boolean => useMediaQuery("(prefers-color-scheme: dark)");

View File

@ -12,10 +12,7 @@ export const useOnResize = (
console.log("[useOnResize]", id);
const elem = isClient ? document.querySelector(`#${id}`) : null;
const ro = new ResizeObserver((resizeObserverEntry) =>
onResize(
resizeObserverEntry[0].contentRect.width,
resizeObserverEntry[0].contentRect.height
)
onResize(resizeObserverEntry[0].contentRect.width, resizeObserverEntry[0].contentRect.height)
);
if (isDefined(elem)) {
ro.observe(elem);

View File

@ -2,15 +2,9 @@ import { useMemo, useCallback, useEffect } from "react";
import { useIsClient } from "usehooks-ts";
import { Ids } from "types/ids";
export const useOnScroll = (
id: Ids,
onScroll: (scroll: number) => void
): void => {
export const useOnScroll = (id: Ids, onScroll: (scroll: number) => void): void => {
const isClient = useIsClient();
const elem = useMemo(
() => (isClient ? document.querySelector(`#${id}`) : null),
[id, isClient]
);
const elem = useMemo(() => (isClient ? document.querySelector(`#${id}`) : null), [id, isClient]);
const listener = useCallback(() => {
if (elem?.scrollTop) {
onScroll(elem.scrollTop);

View File

@ -2,16 +2,9 @@ import { DependencyList, useEffect } from "react";
import { Ids } from "types/ids";
// Scroll to top of element "id" when "deps" update.
export const useScrollTopOnChange = (
id: Ids,
deps: DependencyList,
enabled = true
): void => {
export const useScrollTopOnChange = (id: Ids, deps: DependencyList, enabled = true): void => {
useEffect(() => {
if (enabled)
document
.querySelector(`#${id}`)
?.scrollTo({ top: 0, behavior: "smooth" });
if (enabled) document.querySelector(`#${id}`)?.scrollTo({ top: 0, behavior: "smooth" });
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [id, ...deps, enabled]);
};

View File

@ -16,11 +16,7 @@ export const useSmartLanguage = <T>({
items,
languageExtractor,
transform = (item) => item,
}: Props<T>): [
T | undefined,
typeof LanguageSwitcher,
Parameters<typeof LanguageSwitcher>[0]
] => {
}: Props<T>): [T | undefined, typeof LanguageSwitcher, Parameters<typeof LanguageSwitcher>[0]] => {
const { preferredLanguages } = useAppLayout();
const languages = useLanguages();
const router = useRouter();
@ -34,14 +30,10 @@ export const useSmartLanguage = <T>({
return memo;
}, [items, languageExtractor]);
const [selectedTranslationIndex, setSelectedTranslationIndex] = useState<
number | undefined
>();
const [selectedTranslationIndex, setSelectedTranslationIndex] = useState<number | undefined>();
useEffect(() => {
setSelectedTranslationIndex(
getPreferredLanguage(preferredLanguages, availableLocales)
);
setSelectedTranslationIndex(getPreferredLanguage(preferredLanguages, availableLocales));
}, [preferredLanguages, availableLocales, router.locale]);
const selectedTranslation = useMemo(() => {

View File

@ -8,9 +8,7 @@ import Document, {
} from "next/document";
export default class MyDocument extends Document {
static async getInitialProps(
ctx: DocumentContext
): Promise<DocumentInitialProps> {
static async getInitialProps(ctx: DocumentContext): Promise<DocumentInitialProps> {
const initialProps = await Document.getInitialProps(ctx);
return { ...initialProps };
}
@ -19,45 +17,20 @@ export default class MyDocument extends Document {
return (
<Html>
<Head>
<link
rel="apple-touch-icon"
sizes="180x180"
href="/apple-touch-icon.png"
/>
<link
rel="icon"
type="image/png"
sizes="32x32"
href="/favicon-32x32.png"
/>
<link
rel="icon"
type="image/png"
sizes="16x16"
href="/favicon-16x16.png"
/>
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
<link rel="manifest" href="/site.webmanifest" />
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#9c6644" />
<meta name="apple-mobile-web-app-title" content="Accord's Library" />
<meta name="application-name" content="Accord's Library" />
<meta name="msapplication-TileColor" content="#feecd6" />
<meta name="msapplication-TileImage" content="/mstile-144x144.png" />
<meta
name="theme-color"
media="(prefers-color-scheme: light)"
content="#feecd6"
/>
<meta
name="theme-color"
media="(prefers-color-scheme: dark)"
content="#26221e"
/>
<meta name="theme-color" media="(prefers-color-scheme: light)" content="#feecd6" />
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#26221e" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta
name="apple-mobile-web-app-status-bar-style"
content="black-translucent"
/>
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
</Head>
<body>
<Main />

View File

@ -1,8 +1,5 @@
import { PostPage } from "components/PostPage";
import {
getPostStaticProps,
PostStaticProps,
} from "graphql/getPostStaticProps";
import { getPostStaticProps, PostStaticProps } from "graphql/getPostStaticProps";
import { useAppLayout } from "contexts/AppLayoutContext";
/*

View File

@ -2,10 +2,7 @@ import { useRouter } from "next/router";
import { useState } from "react";
import { InsetBox } from "components/InsetBox";
import { PostPage } from "components/PostPage";
import {
getPostStaticProps,
PostStaticProps,
} from "graphql/getPostStaticProps";
import { getPostStaticProps, PostStaticProps } from "graphql/getPostStaticProps";
import { cIf, cJoin } from "helpers/className";
import { randomInt } from "helpers/numbers";
import { RequestMailProps, ResponseMailProps } from "pages/api/mail";
@ -22,9 +19,7 @@ const AboutUs = (props: PostStaticProps): JSX.Element => {
const { langui } = useAppLayout();
const is1ColumnLayout = useIs1ColumnLayout();
const [formResponse, setFormResponse] = useState("");
const [formState, setFormState] = useState<"completed" | "ongoing" | "stale">(
"stale"
);
const [formState, setFormState] = useState<"completed" | "ongoing" | "stale">("stale");
const [randomNumber1, setRandomNumber1] = useState(randomInt(0, 10));
const [randomNumber2, setRandomNumber2] = useState(randomInt(0, 10));
@ -34,10 +29,7 @@ const AboutUs = (props: PostStaticProps): JSX.Element => {
<form
className={cJoin(
"grid gap-8",
cIf(
formState !== "stale",
"pointer-events-none cursor-not-allowed touch-none opacity-60"
)
cIf(formState !== "stale", "pointer-events-none cursor-not-allowed touch-none opacity-60")
)}
onSubmit={(event) => {
event.preventDefault();
@ -52,8 +44,7 @@ const AboutUs = (props: PostStaticProps): JSX.Element => {
setFormState("ongoing");
if (
parseInt(fields.verif.value, 10) ===
randomNumber1 + randomNumber2 &&
parseInt(fields.verif.value, 10) === randomNumber1 + randomNumber2 &&
formState !== "completed"
) {
const content: RequestMailProps = {
@ -100,8 +91,7 @@ const AboutUs = (props: PostStaticProps): JSX.Element => {
router.replace("#send-response");
fields.verif.value = "";
}}
>
}}>
<div className="flex flex-col place-items-center gap-1">
<label htmlFor="name">{langui.name}:</label>
<input
@ -124,9 +114,7 @@ const AboutUs = (props: PostStaticProps): JSX.Element => {
required
disabled={formState !== "stale"}
/>
<p className="text-sm italic text-dark opacity-70">
{langui.email_gdpr_notice}
</p>
<p className="text-sm italic text-dark opacity-70">{langui.email_gdpr_notice}</p>
</div>
<div className="flex w-full flex-col place-items-center gap-1">
@ -145,8 +133,7 @@ const AboutUs = (props: PostStaticProps): JSX.Element => {
<div className="flex flex-row place-items-center gap-2">
<label
className="flex-shrink-0"
htmlFor="verif"
>{`${randomNumber1} + ${randomNumber2} =`}</label>
htmlFor="verif">{`${randomNumber1} + ${randomNumber2} =`}</label>
<input
className="w-24"
type="number"

View File

@ -30,17 +30,9 @@ const AboutUs = (props: Props): JSX.Element => {
<HorizontalLine />
<NavOption
title={langui.accords_handbook}
url="/about-us/accords-handbook"
border
/>
<NavOption title={langui.accords_handbook} url="/about-us/accords-handbook" border />
<NavOption title={langui.legality} url="/about-us/legality" border />
<NavOption
title={langui.sharing_policy}
url="/about-us/sharing-policy"
border
/>
<NavOption title={langui.sharing_policy} url="/about-us/sharing-policy" border />
<NavOption title={langui.contact_us} url="/about-us/contact" border />
</SubPanel>
}

View File

@ -1,8 +1,5 @@
import { PostPage } from "components/PostPage";
import {
getPostStaticProps,
PostStaticProps,
} from "graphql/getPostStaticProps";
import { getPostStaticProps, PostStaticProps } from "graphql/getPostStaticProps";
import { useAppLayout } from "contexts/AppLayoutContext";
/*

View File

@ -1,8 +1,5 @@
import { PostPage } from "components/PostPage";
import {
getPostStaticProps,
PostStaticProps,
} from "graphql/getPostStaticProps";
import { getPostStaticProps, PostStaticProps } from "graphql/getPostStaticProps";
import { useAppLayout } from "contexts/AppLayoutContext";
/*

View File

@ -114,16 +114,11 @@ type ResponseMailProps = {
revalidated: boolean;
};
const Revalidate = (
req: NextApiRequest,
res: NextApiResponse<ResponseMailProps>
): void => {
const Revalidate = (req: NextApiRequest, res: NextApiResponse<ResponseMailProps>): void => {
const body = req.body as RequestProps;
// Check for secret to confirm this is a valid request
if (
req.headers.authorization !== `Bearer ${process.env.REVALIDATION_TOKEN}`
) {
if (req.headers.authorization !== `Bearer ${process.env.REVALIDATION_TOKEN}`) {
res.status(401).json({ message: "Invalid token", revalidated: false });
return;
}
@ -210,9 +205,7 @@ const Revalidate = (
i18n.locales.forEach((locale: string) => {
if (body.entry.library_item) {
paths.push(`/${locale}/library/${body.entry.library_item.slug}`);
paths.push(
`/${locale}/library/${body.entry.library_item.slug}/scans`
);
paths.push(`/${locale}/library/${body.entry.library_item.slug}/scans`);
}
if (body.entry.content) {
paths.push(`/${locale}/contents/${body.entry.content.slug}`);
@ -232,25 +225,19 @@ const Revalidate = (
body.entry.subfolders.forEach((subfolder) =>
paths.push(`/contents/folder/${subfolder.slug}`)
);
body.entry.contents.forEach((content) =>
paths.push(`/contents/${content.slug}`)
);
body.entry.contents.forEach((content) => paths.push(`/contents/${content.slug}`));
i18n.locales.forEach((locale: string) => {
if (body.entry.slug === "root") {
paths.push(`/${locale}/contents`);
}
paths.push(`/${locale}/contents/folder/${body.entry.slug}`);
if (body.entry.parent_folder) {
paths.push(
`/${locale}/contents/folder/${body.entry.parent_folder.slug}`
);
paths.push(`/${locale}/contents/folder/${body.entry.parent_folder.slug}`);
}
body.entry.subfolders.forEach((subfolder) =>
paths.push(`/${locale}/contents/folder/${subfolder.slug}`)
);
body.entry.contents.forEach((content) =>
paths.push(`/${locale}/contents/${content.slug}`)
);
body.entry.contents.forEach((content) => paths.push(`/${locale}/contents/${content.slug}`));
});
break;
}
@ -311,9 +298,7 @@ const Revalidate = (
res.json({ message: "Success!", revalidated: true });
return;
} catch (error) {
res
.status(500)
.send({ message: `Error revalidating: ${error}`, revalidated: false });
res.status(500).send({ message: `Error revalidating: ${error}`, revalidated: false });
}
};
export default Revalidate;

View File

@ -5,10 +5,7 @@ import { AppLayout, AppLayoutRequired } from "components/AppLayout";
import { Switch } from "components/Inputs/Switch";
import { PanelHeader } from "components/PanelComponents/PanelHeader";
import { ReturnButton } from "components/PanelComponents/ReturnButton";
import {
ContentPanel,
ContentPanelWidthSizes,
} from "components/Panels/ContentPanel";
import { ContentPanel, ContentPanelWidthSizes } from "components/Panels/ContentPanel";
import { SubPanel } from "components/Panels/SubPanel";
import { PreviewCard } from "components/PreviewCard";
import { GetVideoChannelQuery } from "graphql/generated";
@ -43,21 +40,16 @@ const DEFAULT_FILTERS_STATE = {
*/
interface Props extends AppLayoutRequired {
channel: NonNullable<
GetVideoChannelQuery["videoChannels"]
>["data"][number]["attributes"];
channel: NonNullable<GetVideoChannelQuery["videoChannels"]>["data"][number]["attributes"];
}
const Channel = ({ channel, ...otherProps }: Props): JSX.Element => {
const { value: keepInfoVisible, toggle: toggleKeepInfoVisible } =
useBoolean(true);
const { value: keepInfoVisible, toggle: toggleKeepInfoVisible } = useBoolean(true);
const { langui } = useAppLayout();
const hoverable = useDeviceSupportsHover();
const isContentPanelAtLeast4xl = useIsContentPanelAtLeast("4xl");
const [searchName, setSearchName] = useState(
DEFAULT_FILTERS_STATE.searchName
);
const [searchName, setSearchName] = useState(DEFAULT_FILTERS_STATE.searchName);
const subPanel = useMemo(
() => (
@ -98,10 +90,7 @@ const Channel = ({ channel, ...otherProps }: Props): JSX.Element => {
() => (
<ContentPanel width={ContentPanelWidthSizes.Full}>
<SmartList
items={filterHasAttributes(channel?.videos?.data, [
"id",
"attributes",
] as const)}
items={filterHasAttributes(channel?.videos?.data, ["id", "attributes"] as const)}
getItemId={(item) => item.id}
renderItem={({ item }) => (
<PreviewCard
@ -135,22 +124,10 @@ const Channel = ({ channel, ...otherProps }: Props): JSX.Element => {
/>
</ContentPanel>
),
[
channel?.title,
channel?.videos?.data,
isContentPanelAtLeast4xl,
keepInfoVisible,
searchName,
]
[channel?.title, channel?.videos?.data, isContentPanelAtLeast4xl, keepInfoVisible, searchName]
);
return (
<AppLayout
subPanel={subPanel}
contentPanel={contentPanel}
{...otherProps}
/>
);
return <AppLayout subPanel={subPanel} contentPanel={contentPanel} {...otherProps} />;
};
export default Channel;
@ -163,25 +140,17 @@ export const getStaticProps: GetStaticProps = async (context) => {
const sdk = getReadySdk();
const langui = getLangui(context.locale);
const channel = await sdk.getVideoChannel({
channel:
context.params && isDefined(context.params.uid)
? context.params.uid.toString()
: "",
channel: context.params && isDefined(context.params.uid) ? context.params.uid.toString() : "",
});
if (!channel.videoChannels?.data[0].attributes) return { notFound: true };
channel.videoChannels.data[0].attributes.videos?.data
.sort((a, b) =>
compareDate(a.attributes?.published_date, b.attributes?.published_date)
)
.sort((a, b) => compareDate(a.attributes?.published_date, b.attributes?.published_date))
.reverse();
const props: Props = {
channel: channel.videoChannels.data[0].attributes,
openGraph: getOpenGraph(
langui,
channel.videoChannels.data[0].attributes.title
),
openGraph: getOpenGraph(langui, channel.videoChannels.data[0].attributes.title),
};
return {
props: props,
@ -196,9 +165,7 @@ export const getStaticPaths: GetStaticPaths = async (context) => {
const paths: GetStaticPathsResult["paths"] = [];
if (channels.videoChannels?.data)
filterHasAttributes(channels.videoChannels.data, [
"attributes",
] as const).map((channel) => {
filterHasAttributes(channels.videoChannels.data, ["attributes"] as const).map((channel) => {
context.locales?.map((local) => {
paths.push({
params: { uid: channel.attributes.uid },

View File

@ -9,10 +9,7 @@ import { TextInput } from "components/Inputs/TextInput";
import { WithLabel } from "components/Inputs/WithLabel";
import { PanelHeader } from "components/PanelComponents/PanelHeader";
import { ReturnButton } from "components/PanelComponents/ReturnButton";
import {
ContentPanel,
ContentPanelWidthSizes,
} from "components/Panels/ContentPanel";
import { ContentPanel, ContentPanelWidthSizes } from "components/Panels/ContentPanel";
import { SubPanel } from "components/Panels/SubPanel";
import { PreviewCard } from "components/PreviewCard";
import { GetVideosPreviewQuery } from "graphql/generated";
@ -51,12 +48,9 @@ const Videos = ({ videos, ...otherProps }: Props): JSX.Element => {
const hoverable = useDeviceSupportsHover();
const isContentPanelAtLeast4xl = useIsContentPanelAtLeast("4xl");
const { value: keepInfoVisible, toggle: toggleKeepInfoVisible } =
useBoolean(true);
const { value: keepInfoVisible, toggle: toggleKeepInfoVisible } = useBoolean(true);
const [searchName, setSearchName] = useState(
DEFAULT_FILTERS_STATE.searchName
);
const [searchName, setSearchName] = useState(DEFAULT_FILTERS_STATE.searchName);
const subPanel = useMemo(
() => (
@ -68,11 +62,7 @@ const Videos = ({ videos, ...otherProps }: Props): JSX.Element => {
className="mb-10"
/>
<PanelHeader
icon={Icon.Movie}
title="Videos"
description={langui.archives_description}
/>
<PanelHeader icon={Icon.Movie} title="Videos" description={langui.archives_description} />
<HorizontalLine />
@ -132,13 +122,7 @@ const Videos = ({ videos, ...otherProps }: Props): JSX.Element => {
),
[isContentPanelAtLeast4xl, keepInfoVisible, searchName, videos]
);
return (
<AppLayout
subPanel={subPanel}
contentPanel={contentPanel}
{...otherProps}
/>
);
return <AppLayout subPanel={subPanel} contentPanel={contentPanel} {...otherProps} />;
};
export default Videos;
@ -153,9 +137,7 @@ export const getStaticProps: GetStaticProps = async (context) => {
const videos = await sdk.getVideosPreview();
if (!videos.videos) return { notFound: true };
videos.videos.data
.sort((a, b) =>
compareDate(a.attributes?.published_date, b.attributes?.published_date)
)
.sort((a, b) => compareDate(a.attributes?.published_date, b.attributes?.published_date))
.reverse();
const props: Props = {

View File

@ -8,10 +8,7 @@ import { Button } from "components/Inputs/Button";
import { InsetBox } from "components/InsetBox";
import { NavOption } from "components/PanelComponents/NavOption";
import { ReturnButton } from "components/PanelComponents/ReturnButton";
import {
ContentPanel,
ContentPanelWidthSizes,
} from "components/Panels/ContentPanel";
import { ContentPanel, ContentPanelWidthSizes } from "components/Panels/ContentPanel";
import { SubPanel } from "components/Panels/SubPanel";
import { useAppLayout } from "contexts/AppLayoutContext";
import { GetVideoQuery } from "graphql/generated";
@ -29,9 +26,7 @@ import { getLangui } from "graphql/fetchLocalData";
*/
interface Props extends AppLayoutRequired {
video: NonNullable<
NonNullable<GetVideoQuery["videos"]>["data"][number]["attributes"]
>;
video: NonNullable<NonNullable<GetVideoQuery["videos"]>["data"][number]["attributes"]>;
}
const Video = ({ video, ...otherProps }: Props): JSX.Element => {
@ -86,16 +81,9 @@ const Video = ({ video, ...otherProps }: Props): JSX.Element => {
/>
<div className="grid place-items-center gap-12">
<div
id="video"
className="w-full overflow-hidden rounded-xl shadow-lg shadow-shade"
>
<div id="video" className="w-full overflow-hidden rounded-xl shadow-lg shadow-shade">
{video.gone ? (
<video
className="w-full"
src={getVideoFile(video.uid)}
controls
></video>
<video className="w-full" src={getVideoFile(video.uid)} controls></video>
) : (
<iframe
src={`https://www.youtube-nocookie.com/embed/${video.uid}`}
@ -104,49 +92,32 @@ const Video = ({ video, ...otherProps }: Props): JSX.Element => {
frameBorder="0"
allow="accelerometer; autoplay; clipboard-write;
encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
></iframe>
allowFullScreen></iframe>
)}
<div className="mt-2 p-6">
<h1 className="text-2xl">{video.title}</h1>
<div className="flex w-full flex-row flex-wrap gap-x-6">
<p>
<Ico
icon={Icon.Event}
className="mr-1 translate-y-[.15em] !text-base"
/>
<Ico icon={Icon.Event} className="mr-1 translate-y-[.15em] !text-base" />
{prettyDate(video.published_date, router.locale)}
</p>
<p>
<Ico
icon={Icon.Visibility}
className="mr-1 translate-y-[.15em] !text-base"
/>
<Ico icon={Icon.Visibility} className="mr-1 translate-y-[.15em] !text-base" />
{isContentPanelAtLeast4xl
? video.views.toLocaleString()
: prettyShortenNumber(video.views)}
</p>
{video.channel?.data?.attributes && (
<p>
<Ico
icon={Icon.ThumbUp}
className="mr-1 translate-y-[.15em] !text-base"
/>
<Ico icon={Icon.ThumbUp} className="mr-1 translate-y-[.15em] !text-base" />
{isContentPanelAtLeast4xl
? video.likes.toLocaleString()
: prettyShortenNumber(video.likes)}
</p>
)}
<a
href={`https://youtu.be/${video.uid}`}
target="_blank"
rel="noreferrer"
>
<Button
className="!py-0 !px-3"
text={`${langui.view_on} ${video.source}`}
/>
<a href={`https://youtu.be/${video.uid}`} target="_blank" rel="noreferrer">
<Button className="!py-0 !px-3" text={`${langui.view_on} ${video.source}`} />
</a>
</div>
</div>
@ -195,13 +166,7 @@ const Video = ({ video, ...otherProps }: Props): JSX.Element => {
]
);
return (
<AppLayout
subPanel={subPanel}
contentPanel={contentPanel}
{...otherProps}
/>
);
return <AppLayout subPanel={subPanel} contentPanel={contentPanel} {...otherProps} />;
};
export default Video;
@ -214,10 +179,7 @@ export const getStaticProps: GetStaticProps = async (context) => {
const sdk = getReadySdk();
const langui = getLangui(context.locale);
const videos = await sdk.getVideo({
uid:
context.params && isDefined(context.params.uid)
? context.params.uid.toString()
: "",
uid: context.params && isDefined(context.params.uid) ? context.params.uid.toString() : "",
});
if (!videos.videos?.data[0]?.attributes) return { notFound: true };
@ -237,13 +199,11 @@ export const getStaticPaths: GetStaticPaths = async (context) => {
const videos = await sdk.getVideosSlugs();
const paths: GetStaticPathsResult["paths"] = [];
if (videos.videos?.data)
filterHasAttributes(videos.videos.data, ["attributes"] as const).map(
(video) => {
context.locales?.map((local) => {
paths.push({ params: { uid: video.attributes.uid }, locale: local });
});
}
);
filterHasAttributes(videos.videos.data, ["attributes"] as const).map((video) => {
context.locales?.map((local) => {
paths.push({ params: { uid: video.attributes.uid }, locale: local });
});
});
return {
paths,
fallback: "blocking",

View File

@ -2,7 +2,7 @@ import { GetStaticProps, GetStaticPaths, GetStaticPathsResult } from "next";
import { useCallback, useMemo } from "react";
import { getReadySdk } from "graphql/sdk";
import { isDefined, filterHasAttributes } from "helpers/others";
import { ChronicleWithTranslations } from "helpers/types";
import { ChronicleWithTranslations } from "types/types";
import { AppLayout, AppLayoutRequired } from "components/AppLayout";
import { useSmartLanguage } from "hooks/useSmartLanguage";
import { ContentPanel } from "components/Panels/ContentPanel";
@ -15,10 +15,7 @@ import { prettyInlineTitle, prettySlug } from "helpers/formatters";
import { ReturnButton } from "components/PanelComponents/ReturnButton";
import { Icon } from "components/Ico";
import { getOpenGraph } from "helpers/openGraph";
import {
getDefaultPreferredLanguages,
staticSmartLanguage,
} from "helpers/locales";
import { getDefaultPreferredLanguages, staticSmartLanguage } from "helpers/locales";
import { getDescription } from "helpers/description";
import { TranslatedChroniclesList } from "components/Chronicles/ChroniclesList";
import { useAppLayout } from "contexts/AppLayoutContext";
@ -31,59 +28,44 @@ import { getLangui } from "graphql/fetchLocalData";
interface Props extends AppLayoutRequired {
chronicle: ChronicleWithTranslations;
chapters: NonNullable<
GetChroniclesChaptersQuery["chroniclesChapters"]
>["data"];
chapters: NonNullable<GetChroniclesChaptersQuery["chroniclesChapters"]>["data"];
}
const Chronicle = ({
chronicle,
chapters,
...otherProps
}: Props): JSX.Element => {
const Chronicle = ({ chronicle, chapters, ...otherProps }: Props): JSX.Element => {
const { langui } = useAppLayout();
const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] =
useSmartLanguage({
items: chronicle.translations,
languageExtractor: useCallback(
(item: ChronicleWithTranslations["translations"][number]) =>
item?.language?.data?.attributes?.code,
[]
),
});
const primaryContent = useMemo<
NonNullable<
ChronicleWithTranslations["contents"]
>["data"][number]["attributes"]
>(
() =>
filterHasAttributes(chronicle.contents?.data, [
"attributes.translations",
] as const)[0]?.attributes,
[chronicle.contents?.data]
);
const [
selectedContentTranslation,
ContentLanguageSwitcher,
ContentLanguageSwitcherProps,
] = useSmartLanguage({
items: primaryContent?.translations ?? [],
const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] = useSmartLanguage({
items: chronicle.translations,
languageExtractor: useCallback(
(
item: NonNullable<
NonNullable<
NonNullable<
ChronicleWithTranslations["contents"]
>["data"][number]["attributes"]
>["translations"]
>[number]
) => item?.language?.data?.attributes?.code,
(item: ChronicleWithTranslations["translations"][number]) =>
item?.language?.data?.attributes?.code,
[]
),
});
const primaryContent = useMemo<
NonNullable<ChronicleWithTranslations["contents"]>["data"][number]["attributes"]
>(
() =>
filterHasAttributes(chronicle.contents?.data, ["attributes.translations"] as const)[0]
?.attributes,
[chronicle.contents?.data]
);
const [selectedContentTranslation, ContentLanguageSwitcher, ContentLanguageSwitcherProps] =
useSmartLanguage({
items: primaryContent?.translations ?? [],
languageExtractor: useCallback(
(
item: NonNullable<
NonNullable<
NonNullable<ChronicleWithTranslations["contents"]>["data"][number]["attributes"]
>["translations"]
>[number]
) => item?.language?.data?.attributes?.code,
[]
),
});
const contentPanel = useMemo(
() => (
<ContentPanel>
@ -96,9 +78,7 @@ const Chronicle = ({
{isDefined(selectedTranslation) ? (
<>
<h1 className="mb-16 text-center text-3xl">
{selectedTranslation.title}
</h1>
<h1 className="mb-16 text-center text-3xl">{selectedTranslation.title}</h1>
{languageSwitcherProps.locales.size > 1 && (
<LanguageSwitcher {...languageSwitcherProps} />
@ -118,9 +98,7 @@ const Chronicle = ({
subtitle={selectedContentTranslation.subtitle}
languageSwitcher={
ContentLanguageSwitcherProps.locales.size > 1 ? (
<ContentLanguageSwitcher
{...ContentLanguageSwitcherProps}
/>
<ContentLanguageSwitcher {...ContentLanguageSwitcherProps} />
) : undefined
}
categories={primaryContent?.categories}
@ -167,23 +145,22 @@ const Chronicle = ({
<HorizontalLine />
<div className="grid gap-16">
{filterHasAttributes(chapters, [
"attributes.chronicles",
"id",
] as const).map((chapter) => (
<TranslatedChroniclesList
key={chapter.id}
chronicles={chapter.attributes.chronicles.data}
translations={filterHasAttributes(chapter.attributes.titles, [
"language.data.attributes.code",
] as const).map((translation) => ({
title: translation.title,
language: translation.language.data.attributes.code,
}))}
fallback={{ title: prettySlug(chapter.attributes.slug) }}
currentSlug={chronicle.slug}
/>
))}
{filterHasAttributes(chapters, ["attributes.chronicles", "id"] as const).map(
(chapter) => (
<TranslatedChroniclesList
key={chapter.id}
chronicles={chapter.attributes.chronicles.data}
translations={filterHasAttributes(chapter.attributes.titles, [
"language.data.attributes.code",
] as const).map((translation) => ({
title: translation.title,
language: translation.language.data.attributes.code,
}))}
fallback={{ title: prettySlug(chapter.attributes.slug) }}
currentSlug={chronicle.slug}
/>
)
)}
</div>
</SubPanel>
),
@ -210,9 +187,7 @@ export const getStaticProps: GetStaticProps = async (context) => {
const sdk = getReadySdk();
const langui = getLangui(context.locale);
const slug =
context.params && isDefined(context.params.slug)
? context.params.slug.toString()
: "";
context.params && isDefined(context.params.slug) ? context.params.slug.toString() : "";
const chronicle = await sdk.getChronicle({
language_code: context.locale ?? "en",
slug: slug,
@ -226,19 +201,11 @@ export const getStaticProps: GetStaticProps = async (context) => {
const { title, description } = (() => {
if (context.locale && context.locales) {
if (
chronicle.chronicles.data[0].attributes.contents?.data[0]?.attributes
?.translations
) {
if (chronicle.chronicles.data[0].attributes.contents?.data[0]?.attributes?.translations) {
const selectedContentTranslation = staticSmartLanguage({
items:
chronicle.chronicles.data[0].attributes.contents.data[0].attributes
.translations,
items: chronicle.chronicles.data[0].attributes.contents.data[0].attributes.translations,
languageExtractor: (item) => item.language?.data?.attributes?.code,
preferredLanguages: getDefaultPreferredLanguages(
context.locale,
context.locales
),
preferredLanguages: getDefaultPreferredLanguages(context.locale, context.locales),
});
if (selectedContentTranslation) {
return {
@ -247,30 +214,24 @@ export const getStaticProps: GetStaticProps = async (context) => {
selectedContentTranslation.title,
selectedContentTranslation.subtitle
),
description: getDescription(
selectedContentTranslation.description,
{
[langui.type ?? "Type"]: [
chronicle.chronicles.data[0].attributes.contents.data[0]
.attributes.type?.data?.attributes?.titles?.[0]?.title,
],
[langui.categories ?? "Categories"]: filterHasAttributes(
chronicle.chronicles.data[0].attributes.contents.data[0]
.attributes.categories?.data,
["attributes"] as const
).map((category) => category.attributes.short),
}
),
description: getDescription(selectedContentTranslation.description, {
[langui.type ?? "Type"]: [
chronicle.chronicles.data[0].attributes.contents.data[0].attributes.type?.data
?.attributes?.titles?.[0]?.title,
],
[langui.categories ?? "Categories"]: filterHasAttributes(
chronicle.chronicles.data[0].attributes.contents.data[0].attributes.categories
?.data,
["attributes"] as const
).map((category) => category.attributes.short),
}),
};
}
} else {
const selectedTranslation = staticSmartLanguage({
items: chronicle.chronicles.data[0].attributes.translations,
languageExtractor: (item) => item.language?.data?.attributes?.code,
preferredLanguages: getDefaultPreferredLanguages(
context.locale,
context.locales
),
preferredLanguages: getDefaultPreferredLanguages(context.locale, context.locales),
});
if (selectedTranslation) {
return {
@ -288,13 +249,12 @@ export const getStaticProps: GetStaticProps = async (context) => {
const thumbnail =
chronicle.chronicles.data[0].attributes.translations.length === 0
? chronicle.chronicles.data[0].attributes.contents?.data[0]?.attributes
?.thumbnail?.data?.attributes
? chronicle.chronicles.data[0].attributes.contents?.data[0]?.attributes?.thumbnail?.data
?.attributes
: undefined;
const props: Props = {
chronicle: chronicle.chronicles.data[0]
.attributes as ChronicleWithTranslations,
chronicle: chronicle.chronicles.data[0].attributes as ChronicleWithTranslations,
chapters: chronicles.chroniclesChapters.data,
openGraph: getOpenGraph(langui, title, description, thumbnail),
};
@ -309,16 +269,14 @@ export const getStaticPaths: GetStaticPaths = async (context) => {
const sdk = getReadySdk();
const contents = await sdk.getChroniclesSlugs();
const paths: GetStaticPathsResult["paths"] = [];
filterHasAttributes(contents.chronicles?.data, ["attributes"] as const).map(
(wikiPage) => {
context.locales?.map((local) =>
paths.push({
params: { slug: wikiPage.attributes.slug },
locale: local,
})
);
}
);
filterHasAttributes(contents.chronicles?.data, ["attributes"] as const).map((wikiPage) => {
context.locales?.map((local) =>
paths.push({
params: { slug: wikiPage.attributes.slug },
locale: local,
})
);
});
return {
paths,
fallback: "blocking",

View File

@ -20,9 +20,7 @@ import { getLangui } from "graphql/fetchLocalData";
*/
interface Props extends AppLayoutRequired {
chapters: NonNullable<
GetChroniclesChaptersQuery["chroniclesChapters"]
>["data"];
chapters: NonNullable<GetChroniclesChaptersQuery["chroniclesChapters"]>["data"];
}
const Chronicles = ({ chapters, ...otherProps }: Props): JSX.Element => {
@ -39,22 +37,21 @@ const Chronicles = ({ chapters, ...otherProps }: Props): JSX.Element => {
<HorizontalLine />
<div className="grid gap-16">
{filterHasAttributes(chapters, [
"attributes.chronicles",
"id",
] as const).map((chapter) => (
<TranslatedChroniclesList
key={chapter.id}
chronicles={chapter.attributes.chronicles.data}
translations={filterHasAttributes(chapter.attributes.titles, [
"language.data.attributes.code",
] as const).map((translation) => ({
title: translation.title,
language: translation.language.data.attributes.code,
}))}
fallback={{ title: prettySlug(chapter.attributes.slug) }}
/>
))}
{filterHasAttributes(chapters, ["attributes.chronicles", "id"] as const).map(
(chapter) => (
<TranslatedChroniclesList
key={chapter.id}
chronicles={chapter.attributes.chronicles.data}
translations={filterHasAttributes(chapter.attributes.titles, [
"language.data.attributes.code",
] as const).map((translation) => ({
title: translation.title,
language: translation.language.data.attributes.code,
}))}
fallback={{ title: prettySlug(chapter.attributes.slug) }}
/>
)
)}
</div>
</SubPanel>
),

View File

@ -20,25 +20,15 @@ import {
prettySlug,
} from "helpers/formatters";
import { isUntangibleGroupItem } from "helpers/libraryItem";
import {
filterHasAttributes,
getStatusDescription,
isDefinedAndNotEmpty,
} from "helpers/others";
import { ContentWithTranslations } from "helpers/types";
import { filterHasAttributes, getStatusDescription, isDefinedAndNotEmpty } from "helpers/others";
import { ContentWithTranslations } from "types/types";
import { useScrollTopOnChange } from "hooks/useScrollTopOnChange";
import { useSmartLanguage } from "hooks/useSmartLanguage";
import { getOpenGraph } from "helpers/openGraph";
import {
getDefaultPreferredLanguages,
staticSmartLanguage,
} from "helpers/locales";
import { getDefaultPreferredLanguages, staticSmartLanguage } from "helpers/locales";
import { getDescription } from "helpers/description";
import { TranslatedPreviewLine } from "components/PreviewLine";
import {
useIs1ColumnLayout,
useIsContentPanelAtLeast,
} from "hooks/useContainerQuery";
import { useIs1ColumnLayout, useIsContentPanelAtLeast } from "hooks/useContainerQuery";
import { cIf } from "helpers/className";
import { getLangui } from "graphql/fetchLocalData";
import { useAppLayout } from "contexts/AppLayoutContext";
@ -58,35 +48,26 @@ const Content = ({ content, ...otherProps }: Props): JSX.Element => {
const is1ColumnLayout = useIs1ColumnLayout();
const { langui, languages } = useAppLayout();
const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] =
useSmartLanguage({
items: content.translations,
languageExtractor: useCallback(
(item: NonNullable<Props["content"]["translations"][number]>) =>
item.language?.data?.attributes?.code,
[]
),
});
const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] = useSmartLanguage({
items: content.translations,
languageExtractor: useCallback(
(item: NonNullable<Props["content"]["translations"][number]>) =>
item.language?.data?.attributes?.code,
[]
),
});
useScrollTopOnChange(Ids.ContentPanel, [selectedTranslation]);
const { previousContent, nextContent } = useMemo(
() => ({
previousContent:
content.folder?.data?.attributes?.contents &&
content.folder.data.attributes.sequence
? getPreviousContent(
content.folder.data.attributes.contents.data,
content.slug
)
content.folder?.data?.attributes?.contents && content.folder.data.attributes.sequence
? getPreviousContent(content.folder.data.attributes.contents.data, content.slug)
: undefined,
nextContent:
content.folder?.data?.attributes?.contents &&
content.folder.data.attributes.sequence
? getNextContent(
content.folder.data.attributes.contents.data,
content.slug
)
content.folder?.data?.attributes?.contents && content.folder.data.attributes.sequence
? getNextContent(content.folder.data.attributes.contents.data, content.slug)
: undefined,
}),
[content.folder, content.slug]
@ -98,10 +79,9 @@ const Content = ({ content, ...otherProps }: Props): JSX.Element => {
? `/contents/folder/${content.folder.data.attributes.slug}`
: "/contents",
translations: filterHasAttributes(
content.folder?.data?.attributes?.titles,
["language.data.attributes.code"] as const
).map((title) => ({
translations: filterHasAttributes(content.folder?.data?.attributes?.titles, [
"language.data.attributes.code",
] as const).map((title) => ({
language: title.language.data.attributes.code,
title: title.title,
})),
@ -118,34 +98,26 @@ const Content = ({ content, ...otherProps }: Props): JSX.Element => {
const subPanel = useMemo(
() => (
<SubPanel>
<TranslatedReturnButton
{...returnButtonProps}
displayOnlyOn="3ColumnsLayout"
/>
<TranslatedReturnButton {...returnButtonProps} displayOnlyOn="3ColumnsLayout" />
{selectedTranslation?.text_set?.source_language?.data?.attributes
?.code !== undefined && (
{selectedTranslation?.text_set?.source_language?.data?.attributes?.code !== undefined && (
<>
<HorizontalLine />
<div className="grid gap-5">
<h2 className="text-xl">
{selectedTranslation.text_set.source_language.data.attributes
.code === selectedTranslation.language?.data?.attributes?.code
{selectedTranslation.text_set.source_language.data.attributes.code ===
selectedTranslation.language?.data?.attributes?.code
? langui.transcript_notice
: langui.translation_notice}
</h2>
{selectedTranslation.text_set.source_language.data.attributes
.code !==
{selectedTranslation.text_set.source_language.data.attributes.code !==
selectedTranslation.language?.data?.attributes?.code && (
<div className="grid place-items-center gap-2">
<p className="font-headers font-bold">
{langui.source_language}:
</p>
<p className="font-headers font-bold">{langui.source_language}:</p>
<Chip
text={prettyLanguage(
selectedTranslation.text_set.source_language.data
.attributes.code,
selectedTranslation.text_set.source_language.data.attributes.code,
languages
)}
/>
@ -156,12 +128,8 @@ const Content = ({ content, ...otherProps }: Props): JSX.Element => {
<p className="font-headers font-bold">{langui.status}:</p>
<ToolTip
content={getStatusDescription(
selectedTranslation.text_set.status,
langui
)}
maxWidth={"20rem"}
>
content={getStatusDescription(selectedTranslation.text_set.status, langui)}
maxWidth={"20rem"}>
<Chip text={selectedTranslation.text_set.status} />
</ToolTip>
</div>
@ -169,14 +137,12 @@ const Content = ({ content, ...otherProps }: Props): JSX.Element => {
{selectedTranslation.text_set.transcribers &&
selectedTranslation.text_set.transcribers.data.length > 0 && (
<div>
<p className="font-headers font-bold">
{langui.transcribers}:
</p>
<p className="font-headers font-bold">{langui.transcribers}:</p>
<div className="grid place-content-center place-items-center gap-2">
{filterHasAttributes(
selectedTranslation.text_set.transcribers.data,
["attributes", "id"] as const
).map((recorder) => (
{filterHasAttributes(selectedTranslation.text_set.transcribers.data, [
"attributes",
"id",
] as const).map((recorder) => (
<Fragment key={recorder.id}>
<RecorderChip recorder={recorder.attributes} />
</Fragment>
@ -188,14 +154,12 @@ const Content = ({ content, ...otherProps }: Props): JSX.Element => {
{selectedTranslation.text_set.translators &&
selectedTranslation.text_set.translators.data.length > 0 && (
<div>
<p className="font-headers font-bold">
{langui.translators}:
</p>
<p className="font-headers font-bold">{langui.translators}:</p>
<div className="grid place-content-center place-items-center gap-2">
{filterHasAttributes(
selectedTranslation.text_set.translators.data,
["attributes", "id"] as const
).map((recorder) => (
{filterHasAttributes(selectedTranslation.text_set.translators.data, [
"attributes",
"id",
] as const).map((recorder) => (
<Fragment key={recorder.id}>
<RecorderChip recorder={recorder.attributes} />
</Fragment>
@ -207,14 +171,12 @@ const Content = ({ content, ...otherProps }: Props): JSX.Element => {
{selectedTranslation.text_set.proofreaders &&
selectedTranslation.text_set.proofreaders.data.length > 0 && (
<div>
<p className="font-headers font-bold">
{langui.proofreaders}:
</p>
<p className="font-headers font-bold">{langui.proofreaders}:</p>
<div className="grid place-content-center place-items-center gap-2">
{filterHasAttributes(
selectedTranslation.text_set.proofreaders.data,
["attributes", "id"] as const
).map((recorder) => (
{filterHasAttributes(selectedTranslation.text_set.proofreaders.data, [
"attributes",
"id",
] as const).map((recorder) => (
<Fragment key={recorder.id}>
<RecorderChip recorder={recorder.attributes} />
</Fragment>
@ -249,68 +211,56 @@ const Content = ({ content, ...otherProps }: Props): JSX.Element => {
</>
)}
{content.ranged_contents?.data &&
content.ranged_contents.data.length > 0 && (
<>
<HorizontalLine />
<div>
<p className="font-headers text-2xl font-bold">
{langui.source}
</p>
<div className="mt-6 grid place-items-center gap-6">
{filterHasAttributes(content.ranged_contents.data, [
"attributes.library_item.data.attributes",
"attributes.library_item.data.id",
] as const).map((rangedContent) => {
const libraryItem =
rangedContent.attributes.library_item.data;
return (
<div
key={libraryItem.attributes.slug}
className={cIf(is1ColumnLayout, "w-3/4")}
>
<PreviewCard
href={`/library/${libraryItem.attributes.slug}`}
title={libraryItem.attributes.title}
subtitle={libraryItem.attributes.subtitle}
thumbnail={
libraryItem.attributes.thumbnail?.data?.attributes
}
thumbnailAspectRatio="21/29.7"
thumbnailRounded={false}
topChips={
libraryItem.attributes.metadata &&
libraryItem.attributes.metadata.length > 0 &&
libraryItem.attributes.metadata[0]
? [
prettyItemSubType(
libraryItem.attributes.metadata[0]
),
]
: []
}
bottomChips={filterHasAttributes(
libraryItem.attributes.categories?.data,
["attributes"] as const
).map((category) => category.attributes.short)}
metadata={{
releaseDate: libraryItem.attributes.release_date,
price: libraryItem.attributes.price,
position: "Bottom",
}}
infoAppend={
!isUntangibleGroupItem(
libraryItem.attributes.metadata?.[0]
) && <PreviewCardCTAs id={libraryItem.id} />
}
/>
</div>
);
})}
</div>
{content.ranged_contents?.data && content.ranged_contents.data.length > 0 && (
<>
<HorizontalLine />
<div>
<p className="font-headers text-2xl font-bold">{langui.source}</p>
<div className="mt-6 grid place-items-center gap-6">
{filterHasAttributes(content.ranged_contents.data, [
"attributes.library_item.data.attributes",
"attributes.library_item.data.id",
] as const).map((rangedContent) => {
const libraryItem = rangedContent.attributes.library_item.data;
return (
<div
key={libraryItem.attributes.slug}
className={cIf(is1ColumnLayout, "w-3/4")}>
<PreviewCard
href={`/library/${libraryItem.attributes.slug}`}
title={libraryItem.attributes.title}
subtitle={libraryItem.attributes.subtitle}
thumbnail={libraryItem.attributes.thumbnail?.data?.attributes}
thumbnailAspectRatio="21/29.7"
thumbnailRounded={false}
topChips={
libraryItem.attributes.metadata &&
libraryItem.attributes.metadata.length > 0 &&
libraryItem.attributes.metadata[0]
? [prettyItemSubType(libraryItem.attributes.metadata[0])]
: []
}
bottomChips={filterHasAttributes(libraryItem.attributes.categories?.data, [
"attributes",
] as const).map((category) => category.attributes.short)}
metadata={{
releaseDate: libraryItem.attributes.release_date,
price: libraryItem.attributes.price,
position: "Bottom",
}}
infoAppend={
!isUntangibleGroupItem(libraryItem.attributes.metadata?.[0]) && (
<PreviewCardCTAs id={libraryItem.id} />
)
}
/>
</div>
);
})}
</div>
</>
)}
</div>
</>
)}
</SubPanel>
),
[
@ -350,15 +300,12 @@ const Content = ({ content, ...otherProps }: Props): JSX.Element => {
{previousContent?.attributes && (
<div className="mt-12 mb-8 w-full">
<h2 className="mb-4 text-center text-2xl">
{langui.previous_content}
</h2>
<h2 className="mb-4 text-center text-2xl">{langui.previous_content}</h2>
<TranslatedPreviewLine
href={`/contents/${previousContent.attributes.slug}`}
translations={filterHasAttributes(
previousContent.attributes.translations,
["language.data.attributes.code"] as const
).map((translation) => ({
translations={filterHasAttributes(previousContent.attributes.translations, [
"language.data.attributes.code",
] as const).map((translation) => ({
pre_title: translation.pre_title,
title: translation.title,
subtitle: translation.subtitle,
@ -367,22 +314,14 @@ const Content = ({ content, ...otherProps }: Props): JSX.Element => {
fallback={{
title: prettySlug(previousContent.attributes.slug),
}}
thumbnail={
previousContent.attributes.thumbnail?.data?.attributes
}
thumbnail={previousContent.attributes.thumbnail?.data?.attributes}
thumbnailAspectRatio="3/2"
topChips={
isContentPanelAtLeast2xl &&
previousContent.attributes.type?.data?.attributes
isContentPanelAtLeast2xl && previousContent.attributes.type?.data?.attributes
? [
previousContent.attributes.type.data.attributes
.titles?.[0]
? previousContent.attributes.type.data.attributes
.titles[0]?.title
: prettySlug(
previousContent.attributes.type.data.attributes
.slug
),
previousContent.attributes.type.data.attributes.titles?.[0]
? previousContent.attributes.type.data.attributes.titles[0]?.title
: prettySlug(previousContent.attributes.type.data.attributes.slug),
]
: undefined
}
@ -407,15 +346,12 @@ const Content = ({ content, ...otherProps }: Props): JSX.Element => {
{nextContent?.attributes && (
<>
<HorizontalLine />
<h2 className="mb-4 text-center text-2xl">
{langui.followup_content}
</h2>
<h2 className="mb-4 text-center text-2xl">{langui.followup_content}</h2>
<TranslatedPreviewLine
href={`/contents/${nextContent.attributes.slug}`}
translations={filterHasAttributes(
nextContent.attributes.translations,
["language.data.attributes.code"] as const
).map((translation) => ({
translations={filterHasAttributes(nextContent.attributes.translations, [
"language.data.attributes.code",
] as const).map((translation) => ({
pre_title: translation.pre_title,
title: translation.title,
subtitle: translation.subtitle,
@ -425,15 +361,11 @@ const Content = ({ content, ...otherProps }: Props): JSX.Element => {
thumbnail={nextContent.attributes.thumbnail?.data?.attributes}
thumbnailAspectRatio="3/2"
topChips={
isContentPanelAtLeast2xl &&
nextContent.attributes.type?.data?.attributes
isContentPanelAtLeast2xl && nextContent.attributes.type?.data?.attributes
? [
nextContent.attributes.type.data.attributes.titles?.[0]
? nextContent.attributes.type.data.attributes
.titles[0]?.title
: prettySlug(
nextContent.attributes.type.data.attributes.slug
),
? nextContent.attributes.type.data.attributes.titles[0]?.title
: prettySlug(nextContent.attributes.type.data.attributes.slug),
]
: undefined
}
@ -469,13 +401,7 @@ const Content = ({ content, ...otherProps }: Props): JSX.Element => {
]
);
return (
<AppLayout
contentPanel={contentPanel}
subPanel={subPanel}
{...otherProps}
/>
);
return <AppLayout contentPanel={contentPanel} subPanel={subPanel} {...otherProps} />;
};
export default Content;
@ -502,10 +428,7 @@ export const getStaticProps: GetStaticProps = async (context) => {
const selectedTranslation = staticSmartLanguage({
items: content.contents.data[0].attributes.translations,
languageExtractor: (item) => item.language?.data?.attributes?.code,
preferredLanguages: getDefaultPreferredLanguages(
context.locale,
context.locales
),
preferredLanguages: getDefaultPreferredLanguages(context.locale, context.locales),
});
if (selectedTranslation) {
return {
@ -516,8 +439,7 @@ export const getStaticProps: GetStaticProps = async (context) => {
),
description: getDescription(selectedTranslation.description, {
[langui.type ?? "Type"]: [
content.contents.data[0].attributes.type?.data?.attributes
?.titles?.[0]?.title,
content.contents.data[0].attributes.type?.data?.attributes?.titles?.[0]?.title,
],
[langui.categories ?? "Categories"]: filterHasAttributes(
content.contents.data[0].attributes.categories?.data,
@ -533,8 +455,7 @@ export const getStaticProps: GetStaticProps = async (context) => {
};
})();
const thumbnail =
content.contents.data[0].attributes.thumbnail?.data?.attributes;
const thumbnail = content.contents.data[0].attributes.thumbnail?.data?.attributes;
const props: Props = {
content: content.contents.data[0].attributes as ContentWithTranslations,
@ -551,16 +472,14 @@ export const getStaticPaths: GetStaticPaths = async (context) => {
const sdk = getReadySdk();
const contents = await sdk.getContentsSlugs();
const paths: GetStaticPathsResult["paths"] = [];
filterHasAttributes(contents.contents?.data, ["attributes"] as const).map(
(item) => {
context.locales?.map((local) => {
paths.push({
params: { slug: item.attributes.slug },
locale: local,
});
filterHasAttributes(contents.contents?.data, ["attributes"] as const).map((item) => {
context.locales?.map((local) => {
paths.push({
params: { slug: item.attributes.slug },
locale: local,
});
}
);
});
});
return {
paths,
fallback: "blocking",
@ -574,9 +493,7 @@ export const getStaticPaths: GetStaticPaths = async (context) => {
type FolderContents = NonNullable<
NonNullable<
NonNullable<
NonNullable<ContentWithTranslations["folder"]>["data"]
>["attributes"]
NonNullable<NonNullable<ContentWithTranslations["folder"]>["data"]>["attributes"]
>["contents"]
>["data"];
@ -595,10 +512,7 @@ const getPreviousContent = (contents: FolderContents, currentSlug: string) => {
const getNextContent = (contents: FolderContents, currentSlug: string) => {
for (let index = 0; index < contents.length; index++) {
const content = contents[index];
if (
content.attributes?.slug === currentSlug &&
index < contents.length - 1
) {
if (content.attributes?.slug === currentSlug && index < contents.length - 1) {
return contents[index + 1];
}
}

View File

@ -6,10 +6,7 @@ import { AppLayout, AppLayoutRequired } from "components/AppLayout";
import { Select } from "components/Inputs/Select";
import { Switch } from "components/Inputs/Switch";
import { PanelHeader } from "components/PanelComponents/PanelHeader";
import {
ContentPanel,
ContentPanelWidthSizes,
} from "components/Panels/ContentPanel";
import { ContentPanel, ContentPanelWidthSizes } from "components/Panels/ContentPanel";
import { SubPanel } from "components/Panels/SubPanel";
import { getReadySdk } from "graphql/sdk";
import { prettyInlineTitle, prettySlug } from "helpers/formatters";
@ -18,11 +15,7 @@ import { WithLabel } from "components/Inputs/WithLabel";
import { Button } from "components/Inputs/Button";
import { useDeviceSupportsHover } from "hooks/useMediaQuery";
import { Icon } from "components/Ico";
import {
filterDefined,
filterHasAttributes,
isDefinedAndNotEmpty,
} from "helpers/others";
import { filterDefined, filterHasAttributes, isDefinedAndNotEmpty } from "helpers/others";
import { GetContentsQuery } from "graphql/generated";
import { SmartList } from "components/SmartList";
import { SelectiveNonNullable } from "types/SelectiveNonNullable";
@ -68,9 +61,7 @@ const Contents = ({ contents, ...otherProps }: Props): JSX.Element => {
setValue: setKeepInfoVisible,
} = useBoolean(DEFAULT_FILTERS_STATE.keepInfoVisible);
const [searchName, setSearchName] = useState(
DEFAULT_FILTERS_STATE.searchName
);
const [searchName, setSearchName] = useState(DEFAULT_FILTERS_STATE.searchName);
const groupingFunction = useCallback(
(
@ -81,10 +72,9 @@ const Contents = ({ contents, ...otherProps }: Props): JSX.Element => {
): string[] => {
switch (groupingMethod) {
case 0: {
const categories = filterHasAttributes(
item.attributes.categories?.data,
["attributes"] as const
);
const categories = filterHasAttributes(item.attributes.categories?.data, [
"attributes",
] as const);
if (categories.length > 0) {
return categories.map((category) => category.attributes.name);
}
@ -107,17 +97,11 @@ const Contents = ({ contents, ...otherProps }: Props): JSX.Element => {
);
const filteringFunction = useCallback(
(
item: SelectiveNonNullable<Props["contents"][number], "attributes" | "id">
) => {
(item: SelectiveNonNullable<Props["contents"][number], "attributes" | "id">) => {
if (searchName.length > 1) {
if (
filterDefined(item.attributes.translations).find((translation) =>
prettyInlineTitle(
translation.pre_title,
translation.title,
translation.subtitle
)
prettyInlineTitle(translation.pre_title, translation.title, translation.subtitle)
.toLowerCase()
.includes(searchName.toLowerCase())
)
@ -142,12 +126,7 @@ const Contents = ({ contents, ...otherProps }: Props): JSX.Element => {
<HorizontalLine />
<Button
href="/contents"
/* TODO: Langui */
text={"Switch to folder view"}
icon={Icon.Folder}
/>
<Button href="/contents" text={langui.switch_to_folder_view} icon={Icon.Folder} />
<HorizontalLine />
@ -173,9 +152,7 @@ const Contents = ({ contents, ...otherProps }: Props): JSX.Element => {
onChange={(value) => {
setGroupingMethod(value);
umami(
`[Contents/All] Change grouping method (${
["none", "category", "type"][value + 1]
})`
`[Contents/All] Change grouping method (${["none", "category", "type"][value + 1]})`
);
}}
allowEmpty
@ -188,11 +165,7 @@ const Contents = ({ contents, ...otherProps }: Props): JSX.Element => {
value={keepInfoVisible}
onClick={() => {
toggleKeepInfoVisible();
umami(
`[Contents/All] Always ${
keepInfoVisible ? "hide" : "show"
} info`
);
umami(`[Contents/All] Always ${keepInfoVisible ? "hide" : "show"} info`);
}}
/>
</WithLabel>
@ -222,6 +195,7 @@ const Contents = ({ contents, ...otherProps }: Props): JSX.Element => {
langui.group_by,
langui.reset_all_filters,
langui.search_title,
langui.switch_to_folder_view,
langui.type,
searchName,
setKeepInfoVisible,
@ -281,11 +255,7 @@ const Contents = ({ contents, ...otherProps }: Props): JSX.Element => {
${item.attributes.slug}
${filterDefined(item.attributes.translations)
.map((translation) =>
prettyInlineTitle(
translation.pre_title,
translation.title,
translation.subtitle
)
prettyInlineTitle(translation.pre_title, translation.title, translation.subtitle)
)
.join(" ")}`
}

View File

@ -2,18 +2,12 @@ import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next";
import { useCallback, useMemo } from "react";
import naturalCompare from "string-natural-compare";
import { AppLayout, AppLayoutRequired } from "components/AppLayout";
import {
ContentPanel,
ContentPanelWidthSizes,
} from "components/Panels/ContentPanel";
import { ContentPanel, ContentPanelWidthSizes } from "components/Panels/ContentPanel";
import { getOpenGraph } from "helpers/openGraph";
import { getReadySdk } from "graphql/sdk";
import { filterHasAttributes } from "helpers/others";
import { GetContentsFolderQuery } from "graphql/generated";
import {
getDefaultPreferredLanguages,
staticSmartLanguage,
} from "helpers/locales";
import { getDefaultPreferredLanguages, staticSmartLanguage } from "helpers/locales";
import { prettySlug } from "helpers/formatters";
import { SmartList } from "components/SmartList";
import { Ico, Icon } from "components/Ico";
@ -37,17 +31,11 @@ import { getLangui } from "graphql/fetchLocalData";
interface Props extends AppLayoutRequired {
folder: NonNullable<
NonNullable<
GetContentsFolderQuery["contentsFolders"]
>["data"][number]["attributes"]
NonNullable<GetContentsFolderQuery["contentsFolders"]>["data"][number]["attributes"]
>;
}
const ContentsFolder = ({
openGraph,
folder,
...otherProps
}: Props): JSX.Element => {
const ContentsFolder = ({ openGraph, folder, ...otherProps }: Props): JSX.Element => {
const { langui } = useAppLayout();
const isContentPanelAtLeast4xl = useIsContentPanelAtLeast("4xl");
@ -62,15 +50,10 @@ const ContentsFolder = ({
<HorizontalLine />
<Button
href="/contents/all"
/* TODO: Langui */
text={"Switch to grid view"}
icon={Icon.Apps}
/>
<Button href="/contents/all" text={langui.switch_to_grid_view} icon={Icon.Apps} />
</SubPanel>
),
[langui.contents, langui.contents_description]
[langui.contents, langui.contents_description, langui.switch_to_grid_view]
);
const contentPanel = useMemo(
@ -84,10 +67,9 @@ const ContentsFolder = ({
) : (
<TranslatedButton
href={`/contents/folder/${folder.parent_folder.data.attributes.slug}`}
translations={filterHasAttributes(
folder.parent_folder.data.attributes.titles,
["language.data.attributes.code"] as const
).map((title) => ({
translations={filterHasAttributes(folder.parent_folder.data.attributes.titles, [
"language.data.attributes.code",
] as const).map((title) => ({
language: title.language.data.attributes.code,
text: title.title,
}))}
@ -119,10 +101,7 @@ const ContentsFolder = ({
</div>
<SmartList
items={filterHasAttributes(folder.subfolders?.data, [
"id",
"attributes",
] as const)}
items={filterHasAttributes(folder.subfolders?.data, ["id", "attributes"] as const)}
getItemId={(item) => item.id}
renderItem={({ item }) => (
<TranslatedPreviewFolder
@ -149,10 +128,7 @@ const ContentsFolder = ({
/>
<SmartList
items={filterHasAttributes(folder.contents?.data, [
"id",
"attributes",
] as const)}
items={filterHasAttributes(folder.contents?.data, ["id", "attributes"] as const)}
getItemId={(item) => item.id}
renderItem={({ item }) => (
<TranslatedPreviewCard
@ -193,8 +169,9 @@ const ContentsFolder = ({
groupingFunction={() => [langui.contents ?? "Contents"]}
/>
{folder.contents?.data.length === 0 &&
folder.subfolders?.data.length === 0 && <NoContentNorFolderMessage />}
{folder.contents?.data.length === 0 && folder.subfolders?.data.length === 0 && (
<NoContentNorFolderMessage />
)}
</ContentPanel>
),
[
@ -240,15 +217,15 @@ export const getStaticProps: GetStaticProps = async (context) => {
const subFolders = {
// eslint-disable-next-line id-denylist
data: filterHasAttributes(folder.subfolders?.data, [
"attributes.slug",
]).sort((a, b) => naturalCompare(a.attributes.slug, b.attributes.slug)),
data: filterHasAttributes(folder.subfolders?.data, ["attributes.slug"]).sort((a, b) =>
naturalCompare(a.attributes.slug, b.attributes.slug)
),
};
const contents = {
// eslint-disable-next-line id-denylist
data: filterHasAttributes(folder.contents?.data, ["attributes.slug"]).sort(
(a, b) => naturalCompare(a.attributes.slug, b.attributes.slug)
data: filterHasAttributes(folder.contents?.data, ["attributes.slug"]).sort((a, b) =>
naturalCompare(a.attributes.slug, b.attributes.slug)
),
};
@ -263,10 +240,7 @@ export const getStaticProps: GetStaticProps = async (context) => {
const selectedTranslation = staticSmartLanguage({
items: folder.titles,
languageExtractor: (item) => item.language?.data?.attributes?.code,
preferredLanguages: getDefaultPreferredLanguages(
context.locale,
context.locales
),
preferredLanguages: getDefaultPreferredLanguages(context.locale, context.locales),
});
if (selectedTranslation) {
return selectedTranslation.title;
@ -290,9 +264,7 @@ export const getStaticPaths: GetStaticPaths = async (context) => {
const sdk = getReadySdk();
const contents = await sdk.getContentsFoldersSlugs();
const paths: GetStaticPathsResult["paths"] = [];
filterHasAttributes(contents.contentsFolders?.data, [
"attributes",
] as const).map((item) => {
filterHasAttributes(contents.contentsFolders?.data, ["attributes"] as const).map((item) => {
context.locales?.map((local) => {
paths.push({
params: { slug: item.attributes.slug },
@ -320,13 +292,8 @@ const PreviewFolder = ({ href, title }: PreviewFolderProps): JSX.Element => (
<Link
href={href}
className="flex w-full cursor-pointer flex-row place-content-center place-items-center gap-4
rounded-md bg-light p-6 transition-transform drop-shadow-shade-xl hover:scale-[1.02]"
>
{title && (
<p className="text-center font-headers text-lg font-bold leading-none">
{title}
</p>
)}
rounded-md bg-light p-6 transition-transform drop-shadow-shade-xl hover:scale-[1.02]">
{title && <p className="text-center font-headers text-lg font-bold leading-none">{title}</p>}
</Link>
);
@ -339,17 +306,9 @@ const TranslatedPreviewFolder = ({
}: TranslatedProps<PreviewFolderProps, "title">): JSX.Element => {
const [selectedTranslation] = useSmartLanguage({
items: translations,
languageExtractor: useCallback(
(item: { language: string }): string => item.language,
[]
),
languageExtractor: useCallback((item: { language: string }): string => item.language, []),
});
return (
<PreviewFolder
title={selectedTranslation?.title ?? fallback.title}
{...otherProps}
/>
);
return <PreviewFolder title={selectedTranslation?.title ?? fallback.title} {...otherProps} />;
};
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
@ -360,8 +319,7 @@ const NoContentNorFolderMessage = () => {
<div className="grid 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"
>
border-dark p-8 text-dark opacity-40">
<p className="max-w-xs text-2xl">{langui.empty_folder_message}</p>
</div>
</div>

View File

@ -1,7 +1,5 @@
import { GetStaticProps } from "next";
import ContentsFolder, {
getStaticProps as folderGetStaticProps,
} from "./folder/[slug]";
import ContentsFolder, { getStaticProps as folderGetStaticProps } from "./folder/[slug]";
/*
*

View File

@ -3,10 +3,7 @@ import { useMemo } from "react";
import { AppLayout, AppLayoutRequired } from "components/AppLayout";
import { Chip } from "components/Chip";
import { Button } from "components/Inputs/Button";
import {
ContentPanel,
ContentPanelWidthSizes,
} from "components/Panels/ContentPanel";
import { ContentPanel, ContentPanelWidthSizes } from "components/Panels/ContentPanel";
import { ToolTip } from "components/ToolTip";
import { DevGetContentsQuery } from "graphql/generated";
import { getReadySdk } from "graphql/sdk";
@ -14,6 +11,7 @@ import { filterDefined, filterHasAttributes } from "helpers/others";
import { Report, Severity } from "types/Report";
import { getOpenGraph } from "helpers/openGraph";
import { getLangui } from "graphql/fetchLocalData";
import { sJoin } from "helpers/formatters";
/*
*
@ -49,20 +47,9 @@ const CheckupContents = ({ contents, ...otherProps }: Props): JSX.Element => {
<div
key={index}
className="mb-2 grid grid-cols-[2em,3em,2fr,1fr,0.5fr,0.5fr,2fr] items-center
justify-items-start gap-2"
>
<Button
href={line.frontendUrl}
className="w-4 text-xs"
text="F"
alwaysNewTab
/>
<Button
href={line.backendUrl}
className="w-4 text-xs"
text="B"
alwaysNewTab
/>
justify-items-start gap-2">
<Button href={line.frontendUrl} className="w-4 text-xs" text="F" alwaysNewTab />
<Button href={line.backendUrl} className="w-4 text-xs" text="B" alwaysNewTab />
<p>{line.subitems.join(" -> ")}</p>
<p>{line.name}</p>
<Chip text={line.type} />
@ -121,164 +108,157 @@ const testingContent = (contents: Props["contents"]): Report => {
lines: [],
};
filterHasAttributes(contents.contents?.data, ["attributes"] as const).map(
(content) => {
const backendUrl = `${process.env.NEXT_PUBLIC_URL_CMS}/admin/content-manager/collectionType/api::content.content/${content.id}`;
const frontendUrl = `${process.env.NEXT_PUBLIC_URL_SELF}/contents/${content.attributes.slug}`;
filterHasAttributes(contents.contents?.data, ["attributes"] as const).map((content) => {
const backendUrl = sJoin(
process.env.NEXT_PUBLIC_URL_CMS,
"/admin/content-manager/collectionType/api::content.content/",
content.id
);
const frontendUrl = sJoin(
process.env.NEXT_PUBLIC_URL_SELF,
"/contents/",
content.attributes.slug
);
if (content.attributes.categories?.data.length === 0) {
report.lines.push({
subitems: [content.attributes.slug],
name: "No Category",
type: "Missing",
severity: Severity.High,
description: "The Content has no Category.",
recommandation: "Select a Category in relation with the Content",
backendUrl: backendUrl,
frontendUrl: frontendUrl,
});
}
if (content.attributes.categories?.data.length === 0) {
report.lines.push({
subitems: [content.attributes.slug],
name: "No Category",
type: "Missing",
severity: Severity.High,
description: "The Content has no Category.",
recommandation: "Select a Category in relation with the Content",
backendUrl: backendUrl,
frontendUrl: frontendUrl,
});
}
if (!content.attributes.type?.data?.id) {
report.lines.push({
subitems: [content.attributes.slug],
name: "No Type",
type: "Missing",
severity: Severity.High,
description: "The Content has no Type.",
recommandation: 'If unsure, use the "Other" Type.',
backendUrl: backendUrl,
frontendUrl: frontendUrl,
});
}
if (!content.attributes.type?.data?.id) {
report.lines.push({
subitems: [content.attributes.slug],
name: "No Type",
type: "Missing",
severity: Severity.High,
description: "The Content has no Type.",
recommandation: 'If unsure, use the "Other" Type.',
backendUrl: backendUrl,
frontendUrl: frontendUrl,
});
}
if (content.attributes.ranged_contents?.data.length === 0) {
report.lines.push({
subitems: [content.attributes.slug],
name: "No Ranged Content",
type: "Improvement",
severity: Severity.Low,
description: "The Content has no Ranged Content.",
recommandation:
"If this Content is available in one or multiple Library Item(s), create a Range Content to connect the Content to its Library Item(s).",
backendUrl: backendUrl,
frontendUrl: frontendUrl,
});
}
if (content.attributes.ranged_contents?.data.length === 0) {
report.lines.push({
subitems: [content.attributes.slug],
name: "No Ranged Content",
type: "Improvement",
severity: Severity.Low,
description: "The Content has no Ranged Content.",
recommandation:
"If this Content is available in one or multiple Library Item(s),\
create a Range Content to connect the Content to its Library Item(s).",
backendUrl: backendUrl,
frontendUrl: frontendUrl,
});
}
if (!content.attributes.thumbnail?.data?.id) {
report.lines.push({
subitems: [content.attributes.slug],
name: "No Thumbnail",
type: "Missing",
severity: Severity.High,
description: "The Content has no Thumbnail.",
recommandation: "",
backendUrl: backendUrl,
frontendUrl: frontendUrl,
});
}
if (!content.attributes.thumbnail?.data?.id) {
report.lines.push({
subitems: [content.attributes.slug],
name: "No Thumbnail",
type: "Missing",
severity: Severity.High,
description: "The Content has no Thumbnail.",
recommandation: "",
backendUrl: backendUrl,
frontendUrl: frontendUrl,
});
}
if (content.attributes.translations?.length === 0) {
report.lines.push({
subitems: [content.attributes.slug],
name: "No Titles",
type: "Missing",
severity: Severity.High,
description: "The Content has no Titles.",
recommandation: "",
backendUrl: backendUrl,
frontendUrl: frontendUrl,
if (content.attributes.translations?.length === 0) {
report.lines.push({
subitems: [content.attributes.slug],
name: "No Titles",
type: "Missing",
severity: Severity.High,
description: "The Content has no Titles.",
recommandation: "",
backendUrl: backendUrl,
frontendUrl: frontendUrl,
});
} else {
const titleLanguages: string[] = [];
if (content.attributes.translations && content.attributes.translations.length > 0) {
filterDefined(content.attributes.translations).map((translation, titleIndex) => {
if (translation.language?.data?.id) {
if (translation.language.data.id in titleLanguages) {
report.lines.push({
subitems: [content.attributes.slug, `Title ${titleIndex.toString()}`],
name: "Duplicate Language",
type: "Error",
severity: Severity.High,
description: "",
recommandation: "",
backendUrl: backendUrl,
frontendUrl: frontendUrl,
});
} else {
titleLanguages.push(translation.language.data.id);
}
} else {
report.lines.push({
subitems: [content.attributes.slug, `Title ${titleIndex.toString()}`],
name: "No Language",
type: "Error",
severity: Severity.VeryHigh,
description: "",
recommandation: "",
backendUrl: backendUrl,
frontendUrl: frontendUrl,
});
}
if (!translation.description) {
report.lines.push({
subitems: [content.attributes.slug, `Title ${titleIndex.toString()}`],
name: "No Description",
type: "Missing",
severity: Severity.Medium,
description: "",
recommandation: "",
backendUrl: backendUrl,
frontendUrl: frontendUrl,
});
}
if (!translation.text_set) {
report.lines.push({
subitems: [
content.attributes.slug,
translation.language?.data?.attributes?.code ?? "",
],
name: "No Text Set",
type: "Missing",
severity: Severity.High,
description: "The Content has no Text Set.",
recommandation: "",
backendUrl: backendUrl,
frontendUrl: frontendUrl,
});
}
});
} else {
const titleLanguages: string[] = [];
if (
content.attributes.translations &&
content.attributes.translations.length > 0
) {
filterDefined(content.attributes.translations).map(
(translation, titleIndex) => {
if (translation.language?.data?.id) {
if (translation.language.data.id in titleLanguages) {
report.lines.push({
subitems: [
content.attributes.slug,
`Title ${titleIndex.toString()}`,
],
name: "Duplicate Language",
type: "Error",
severity: Severity.High,
description: "",
recommandation: "",
backendUrl: backendUrl,
frontendUrl: frontendUrl,
});
} else {
titleLanguages.push(translation.language.data.id);
}
} else {
report.lines.push({
subitems: [
content.attributes.slug,
`Title ${titleIndex.toString()}`,
],
name: "No Language",
type: "Error",
severity: Severity.VeryHigh,
description: "",
recommandation: "",
backendUrl: backendUrl,
frontendUrl: frontendUrl,
});
}
if (!translation.description) {
report.lines.push({
subitems: [
content.attributes.slug,
`Title ${titleIndex.toString()}`,
],
name: "No Description",
type: "Missing",
severity: Severity.Medium,
description: "",
recommandation: "",
backendUrl: backendUrl,
frontendUrl: frontendUrl,
});
}
if (!translation.text_set) {
report.lines.push({
subitems: [
content.attributes.slug,
translation.language?.data?.attributes?.code ?? "",
],
name: "No Text Set",
type: "Missing",
severity: Severity.High,
description: "The Content has no Text Set.",
recommandation: "",
backendUrl: backendUrl,
frontendUrl: frontendUrl,
});
}
}
);
} else {
report.lines.push({
subitems: [content.attributes.slug],
name: "No Translations",
type: "Missing",
severity: Severity.High,
description: "The Content has no Translations.",
recommandation: "",
backendUrl: backendUrl,
frontendUrl: frontendUrl,
});
}
report.lines.push({
subitems: [content.attributes.slug],
name: "No Translations",
type: "Missing",
severity: Severity.High,
description: "The Content has no Translations.",
recommandation: "",
backendUrl: backendUrl,
frontendUrl: frontendUrl,
});
}
}
);
});
return report;
};

View File

@ -3,10 +3,7 @@ import { useMemo } from "react";
import { AppLayout, AppLayoutRequired } from "components/AppLayout";
import { Chip } from "components/Chip";
import { Button } from "components/Inputs/Button";
import {
ContentPanel,
ContentPanelWidthSizes,
} from "components/Panels/ContentPanel";
import { ContentPanel, ContentPanelWidthSizes } from "components/Panels/ContentPanel";
import { ToolTip } from "components/ToolTip";
import {
DevGetLibraryItemsQuery,
@ -16,6 +13,7 @@ import { getReadySdk } from "graphql/sdk";
import { Report, Severity } from "types/Report";
import { getOpenGraph } from "helpers/openGraph";
import { getLangui } from "graphql/fetchLocalData";
import { sJoin } from "helpers/formatters";
/*
*
@ -26,10 +24,7 @@ interface Props extends AppLayoutRequired {
libraryItems: DevGetLibraryItemsQuery;
}
const CheckupLibraryItems = ({
libraryItems,
...otherProps
}: Props): JSX.Element => {
const CheckupLibraryItems = ({ libraryItems, ...otherProps }: Props): JSX.Element => {
const testReport = testingLibraryItem(libraryItems);
const contentPanel = useMemo(
@ -54,20 +49,9 @@ const CheckupLibraryItems = ({
<div
key={index}
className="mb-2 grid
grid-cols-[2em,3em,2fr,1fr,0.5fr,0.5fr,2fr] items-center justify-items-start gap-2"
>
<Button
href={line.frontendUrl}
className="w-4 text-xs"
text="F"
alwaysNewTab
/>
<Button
href={line.backendUrl}
className="w-4 text-xs"
text="B"
alwaysNewTab
/>
grid-cols-[2em,3em,2fr,1fr,0.5fr,0.5fr,2fr] items-center justify-items-start gap-2">
<Button href={line.frontendUrl} className="w-4 text-xs" text="F" alwaysNewTab />
<Button href={line.backendUrl} className="w-4 text-xs" text="B" alwaysNewTab />
<p>{line.subitems.join(" -> ")}</p>
<p>{line.name}</p>
<Chip text={line.type} />
@ -129,8 +113,16 @@ const testingLibraryItem = (libraryItems: Props["libraryItems"]): Report => {
libraryItems.libraryItems?.data.map((item) => {
if (item.attributes) {
const backendUrl = `${process.env.NEXT_PUBLIC_URL_CMS}/admin/content-manager/collectionType/api::library-item.library-item/${item.id}`;
const frontendUrl = `${process.env.NEXT_PUBLIC_URL_SELF}/library/${item.attributes.slug}`;
const backendUrl = sJoin(
process.env.NEXT_PUBLIC_URL_CMS,
"/admin/content-manager/collectionType/api::library-item.library-item/",
item.id
);
const frontendUrl = sJoin(
process.env.NEXT_PUBLIC_URL_SELF,
"/library/",
item.attributes.slug
);
if (item.attributes.categories?.data.length === 0) {
report.lines.push({
@ -145,17 +137,13 @@ const testingLibraryItem = (libraryItems: Props["libraryItems"]): Report => {
});
}
if (
!item.attributes.root_item &&
item.attributes.subitem_of?.data.length === 0
) {
if (!item.attributes.root_item && item.attributes.subitem_of?.data.length === 0) {
report.lines.push({
subitems: [item.attributes.slug],
name: "Disconnected Item",
type: "Error",
severity: Severity.VeryHigh,
description:
"The Item is neither a Root Item, nor is it a subitem of another item.",
description: "The Item is neither a Root Item, nor is it a subitem of another item.",
recommandation: "",
backendUrl: backendUrl,
frontendUrl: frontendUrl,
@ -207,10 +195,7 @@ const testingLibraryItem = (libraryItems: Props["libraryItems"]): Report => {
if (image.language?.data?.id) {
if (image.language.data.id in imagesLanguages) {
report.lines.push({
subitems: [
item.attributes.slug,
`Images ${imageIndex.toString()}`,
],
subitems: [item.attributes.slug, `Images ${imageIndex.toString()}`],
name: "Duplicate Language",
type: "Error",
severity: Severity.High,
@ -224,10 +209,7 @@ const testingLibraryItem = (libraryItems: Props["libraryItems"]): Report => {
}
} else {
report.lines.push({
subitems: [
item.attributes.slug,
`Images ${imageIndex.toString()}`,
],
subitems: [item.attributes.slug, `Images ${imageIndex.toString()}`],
name: "No Language",
type: "Error",
severity: Severity.VeryHigh,
@ -240,10 +222,7 @@ const testingLibraryItem = (libraryItems: Props["libraryItems"]): Report => {
if (!image.source_language?.data?.id) {
report.lines.push({
subitems: [
item.attributes.slug,
`Images ${imageIndex.toString()}`,
],
subitems: [item.attributes.slug, `Images ${imageIndex.toString()}`],
name: "No Source Language",
type: "Error",
severity: Severity.VeryHigh,
@ -254,15 +233,9 @@ const testingLibraryItem = (libraryItems: Props["libraryItems"]): Report => {
});
}
if (
image.status !==
Enum_Componentcollectionscomponentlibraryimages_Status.Done
) {
if (image.status !== Enum_Componentcollectionscomponentlibraryimages_Status.Done) {
report.lines.push({
subitems: [
item.attributes.slug,
`Images ${imageIndex.toString()}`,
],
subitems: [item.attributes.slug, `Images ${imageIndex.toString()}`],
name: "Not Done Status",
type: "Improvement",
severity: Severity.Low,
@ -276,15 +249,11 @@ const testingLibraryItem = (libraryItems: Props["libraryItems"]): Report => {
if (image.source_language?.data?.id === image.language?.data?.id) {
if (image.scanners?.data.length === 0) {
report.lines.push({
subitems: [
item.attributes.slug,
`Images ${imageIndex.toString()}`,
],
subitems: [item.attributes.slug, `Images ${imageIndex.toString()}`],
name: "No Scanners",
type: "Missing",
severity: Severity.High,
description:
"The Item is a Scan but doesn't credit any Scanners.",
description: "The Item is a Scan but doesn't credit any Scanners.",
recommandation: "Add the appropriate Scanners.",
backendUrl: backendUrl,
frontendUrl: frontendUrl,
@ -292,36 +261,26 @@ const testingLibraryItem = (libraryItems: Props["libraryItems"]): Report => {
}
if (image.cleaners?.data.length === 0) {
report.lines.push({
subitems: [
item.attributes.slug,
`Images ${imageIndex.toString()}`,
],
subitems: [item.attributes.slug, `Images ${imageIndex.toString()}`],
name: "No Cleaners",
type: "Missing",
severity: Severity.High,
description:
"The Item is a Scan but doesn't credit any Cleaners.",
description: "The Item is a Scan but doesn't credit any Cleaners.",
recommandation: "Add the appropriate Cleaners.",
backendUrl: backendUrl,
frontendUrl: frontendUrl,
});
}
if (
image.typesetters?.data &&
image.typesetters.data.length > 0
) {
if (image.typesetters?.data && image.typesetters.data.length > 0) {
report.lines.push({
subitems: [
item.attributes.slug,
`Images ${imageIndex.toString()}`,
],
subitems: [item.attributes.slug, `Images ${imageIndex.toString()}`],
name: "Credited Typesetters",
type: "Error",
severity: Severity.High,
description:
"The Item is a Scan but credits one or more Typesetters.",
description: "The Item is a Scan but credits one or more Typesetters.",
recommandation:
"If appropriate, create a Scanlation Images Set Set with the Typesetters credited there.",
"If appropriate, create a Scanlation Images Set\
with the Typesetters credited there.",
backendUrl: backendUrl,
frontendUrl: frontendUrl,
});
@ -329,15 +288,11 @@ const testingLibraryItem = (libraryItems: Props["libraryItems"]): Report => {
} else {
if (image.typesetters?.data.length === 0) {
report.lines.push({
subitems: [
item.attributes.slug,
`Images ${imageIndex.toString()}`,
],
subitems: [item.attributes.slug, `Images ${imageIndex.toString()}`],
name: "No Typesetters",
type: "Missing",
severity: Severity.High,
description:
"The Item is a Scanlation but doesn't credit any Typesetters.",
description: "The Item is a Scanlation but doesn't credit any Typesetters.",
recommandation: "Add the appropriate Typesetters.",
backendUrl: backendUrl,
frontendUrl: frontendUrl,
@ -345,17 +300,14 @@ const testingLibraryItem = (libraryItems: Props["libraryItems"]): Report => {
}
if (image.scanners?.data && image.scanners.data.length > 0) {
report.lines.push({
subitems: [
item.attributes.slug,
`Images ${imageIndex.toString()}`,
],
subitems: [item.attributes.slug, `Images ${imageIndex.toString()}`],
name: "Credited Scanners",
type: "Error",
severity: Severity.High,
description:
"The Item is a Scanlation but credits one or more Scanners.",
description: "The Item is a Scanlation but credits one or more Scanners.",
recommandation:
"If appropriate, create a Scanners Images Set Set with the Scanners credited there.",
"If appropriate, create a Scanners Images Set\
with the Scanners credited there.",
backendUrl: backendUrl,
frontendUrl: frontendUrl,
});
@ -365,11 +317,7 @@ const testingLibraryItem = (libraryItems: Props["libraryItems"]): Report => {
if (image.cover) {
if (!image.cover.front?.data?.id) {
report.lines.push({
subitems: [
item.attributes.slug,
`Images ${imageIndex.toString()}`,
"Cover",
],
subitems: [item.attributes.slug, `Images ${imageIndex.toString()}`, "Cover"],
name: "No Front",
type: "Missing",
severity: Severity.VeryHigh,
@ -381,11 +329,7 @@ const testingLibraryItem = (libraryItems: Props["libraryItems"]): Report => {
}
if (!image.cover.spine?.data?.id) {
report.lines.push({
subitems: [
item.attributes.slug,
`Images ${imageIndex.toString()}`,
"Cover",
],
subitems: [item.attributes.slug, `Images ${imageIndex.toString()}`, "Cover"],
name: "No spine",
type: "Missing",
severity: Severity.Low,
@ -397,11 +341,7 @@ const testingLibraryItem = (libraryItems: Props["libraryItems"]): Report => {
}
if (!image.cover.back?.data?.id) {
report.lines.push({
subitems: [
item.attributes.slug,
`Images ${imageIndex.toString()}`,
"Cover",
],
subitems: [item.attributes.slug, `Images ${imageIndex.toString()}`, "Cover"],
name: "No Back",
type: "Missing",
severity: Severity.High,
@ -413,11 +353,7 @@ const testingLibraryItem = (libraryItems: Props["libraryItems"]): Report => {
}
if (!image.cover.full?.data?.id) {
report.lines.push({
subitems: [
item.attributes.slug,
`Images ${imageIndex.toString()}`,
"Cover",
],
subitems: [item.attributes.slug, `Images ${imageIndex.toString()}`, "Cover"],
name: "No Full",
type: "Missing",
severity: Severity.Low,
@ -429,10 +365,7 @@ const testingLibraryItem = (libraryItems: Props["libraryItems"]): Report => {
}
} else {
report.lines.push({
subitems: [
item.attributes.slug,
`Images ${imageIndex.toString()}`,
],
subitems: [item.attributes.slug, `Images ${imageIndex.toString()}`],
name: "No Cover",
type: "Missing",
severity: Severity.Medium,
@ -542,10 +475,7 @@ const testingLibraryItem = (libraryItems: Props["libraryItems"]): Report => {
}
} else {
report.lines.push({
subitems: [
item.attributes.slug,
`Images ${imageIndex.toString()}`,
],
subitems: [item.attributes.slug, `Images ${imageIndex.toString()}`],
name: "No Dust Jacket",
type: "Missing",
severity: Severity.VeryLow,
@ -559,11 +489,7 @@ const testingLibraryItem = (libraryItems: Props["libraryItems"]): Report => {
if (image.obi_belt) {
if (!image.obi_belt.front?.data?.id) {
report.lines.push({
subitems: [
item.attributes.slug,
`Images ${imageIndex.toString()}`,
"Obi Belt",
],
subitems: [item.attributes.slug, `Images ${imageIndex.toString()}`, "Obi Belt"],
name: "No Front",
type: "Missing",
severity: Severity.VeryHigh,
@ -575,11 +501,7 @@ const testingLibraryItem = (libraryItems: Props["libraryItems"]): Report => {
}
if (!image.obi_belt.spine?.data?.id) {
report.lines.push({
subitems: [
item.attributes.slug,
`Images ${imageIndex.toString()}`,
"Obi Belt",
],
subitems: [item.attributes.slug, `Images ${imageIndex.toString()}`, "Obi Belt"],
name: "No spine",
type: "Missing",
severity: Severity.Low,
@ -591,11 +513,7 @@ const testingLibraryItem = (libraryItems: Props["libraryItems"]): Report => {
}
if (!image.obi_belt.back?.data?.id) {
report.lines.push({
subitems: [
item.attributes.slug,
`Images ${imageIndex.toString()}`,
"Obi Belt",
],
subitems: [item.attributes.slug, `Images ${imageIndex.toString()}`, "Obi Belt"],
name: "No Back",
type: "Missing",
severity: Severity.High,
@ -607,11 +525,7 @@ const testingLibraryItem = (libraryItems: Props["libraryItems"]): Report => {
}
if (!image.obi_belt.full?.data?.id) {
report.lines.push({
subitems: [
item.attributes.slug,
`Images ${imageIndex.toString()}`,
"Obi Belt",
],
subitems: [item.attributes.slug, `Images ${imageIndex.toString()}`, "Obi Belt"],
name: "No Full",
type: "Missing",
severity: Severity.Low,
@ -623,11 +537,7 @@ const testingLibraryItem = (libraryItems: Props["libraryItems"]): Report => {
}
if (!image.obi_belt.flap_front?.data?.id) {
report.lines.push({
subitems: [
item.attributes.slug,
`Images ${imageIndex.toString()}`,
"Obi Belt",
],
subitems: [item.attributes.slug, `Images ${imageIndex.toString()}`, "Obi Belt"],
name: "No Flap Front",
type: "Missing",
severity: Severity.Medium,
@ -639,11 +549,7 @@ const testingLibraryItem = (libraryItems: Props["libraryItems"]): Report => {
}
if (!image.obi_belt.flap_back?.data?.id) {
report.lines.push({
subitems: [
item.attributes.slug,
`Images ${imageIndex.toString()}`,
"Obi Belt",
],
subitems: [item.attributes.slug, `Images ${imageIndex.toString()}`, "Obi Belt"],
name: "No Flap Back",
type: "Missing",
severity: Severity.Medium,
@ -655,10 +561,7 @@ const testingLibraryItem = (libraryItems: Props["libraryItems"]): Report => {
}
} else {
report.lines.push({
subitems: [
item.attributes.slug,
`Images ${imageIndex.toString()}`,
],
subitems: [item.attributes.slug, `Images ${imageIndex.toString()}`],
name: "No Obi Belt",
type: "Missing",
severity: Severity.VeryLow,
@ -672,20 +575,14 @@ const testingLibraryItem = (libraryItems: Props["libraryItems"]): Report => {
});
}
if (
item.attributes.descriptions &&
item.attributes.descriptions.length > 0
) {
if (item.attributes.descriptions && item.attributes.descriptions.length > 0) {
const descriptionLanguages: string[] = [];
item.attributes.descriptions.map((description, descriptionIndex) => {
if (description && item.attributes) {
if (description.description.length < 10) {
report.lines.push({
subitems: [
item.attributes.slug,
`Description ${descriptionIndex}`,
],
subitems: [item.attributes.slug, `Description ${descriptionIndex}`],
name: "No Text",
type: "Missing",
severity: Severity.VeryHigh,
@ -699,10 +596,7 @@ const testingLibraryItem = (libraryItems: Props["libraryItems"]): Report => {
if (description.language?.data?.id) {
if (description.language.data.id in descriptionLanguages) {
report.lines.push({
subitems: [
item.attributes.slug,
`Description ${descriptionIndex}`,
],
subitems: [item.attributes.slug, `Description ${descriptionIndex}`],
name: "Duplicate Language",
type: "Error",
severity: Severity.High,
@ -716,10 +610,7 @@ const testingLibraryItem = (libraryItems: Props["libraryItems"]): Report => {
}
} else {
report.lines.push({
subitems: [
item.attributes.slug,
`Description ${descriptionIndex}`,
],
subitems: [item.attributes.slug, `Description ${descriptionIndex}`],
name: "No Language",
type: "Error",
severity: Severity.VeryHigh,

View File

@ -4,10 +4,7 @@ import TurndownService from "turndown";
import { AppLayout, AppLayoutRequired } from "components/AppLayout";
import { Button } from "components/Inputs/Button";
import { Markdawn, TableOfContents } from "components/Markdown/Markdawn";
import {
ContentPanel,
ContentPanelWidthSizes,
} from "components/Panels/ContentPanel";
import { ContentPanel, ContentPanelWidthSizes } from "components/Panels/ContentPanel";
import { Popup } from "components/Popup";
import { ToolTip } from "components/ToolTip";
import { Icon } from "components/Ico";
@ -59,19 +56,14 @@ const Editor = (props: Props): JSX.Element => {
);
const wrap = useCallback(
(
wrapper: string,
properties?: Record<string, string>,
addInnerNewLines?: boolean
) => {
(wrapper: string, properties?: Record<string, string>, addInnerNewLines?: boolean) => {
transformationWrapper((value, selectionStart, selectionEnd) => {
let prepend = wrapper;
let append = wrapper;
if (properties) {
prepend = `<${wrapper}${Object.entries(properties).map(
([propertyName, propertyValue]) =>
` ${propertyName}="${propertyValue}"`
([propertyName, propertyValue]) => ` ${propertyName}="${propertyValue}"`
)}>`;
append = `</${wrapper}>`;
}
@ -107,17 +99,12 @@ const Editor = (props: Props): JSX.Element => {
);
const toggleWrap = useCallback(
(
wrapper: string,
properties?: Record<string, string>,
addInnerNewLines?: boolean
) => {
(wrapper: string, properties?: Record<string, string>, addInnerNewLines?: boolean) => {
if (textAreaRef.current) {
const { value, selectionStart, selectionEnd } = textAreaRef.current;
if (
value.slice(selectionStart - wrapper.length, selectionStart) ===
wrapper &&
value.slice(selectionStart - wrapper.length, selectionStart) === wrapper &&
value.slice(selectionEnd, selectionEnd + wrapper.length) === wrapper
) {
unwrap(wrapper);
@ -132,8 +119,7 @@ const Editor = (props: Props): JSX.Element => {
const preline = useCallback(
(prepend: string) => {
transformationWrapper((value, selectionStart) => {
const lastNewLine =
value.slice(0, selectionStart).lastIndexOf("\n") + 1;
const lastNewLine = value.slice(0, selectionStart).lastIndexOf("\n") + 1;
let newValue = "";
newValue += value.slice(0, lastNewLine);
@ -173,10 +159,7 @@ const Editor = (props: Props): JSX.Element => {
const contentPanel = useMemo(
() => (
<ContentPanel width={ContentPanelWidthSizes.Full}>
<Popup
onClose={() => setConverterOpened(false)}
state={converterOpened}
>
<Popup onClose={() => setConverterOpened(false)} state={converterOpened}>
<div className="text-center">
<h2 className="mt-4">Convert HTML to markdown</h2>
<p>
@ -184,8 +167,7 @@ const Editor = (props: Props): JSX.Element => {
<br />
The text will immediatly be converted to valid Markdown.
<br />
You can then copy the converted text and paste it anywhere you
want in the editor
You can then copy the converted text and paste it anywhere you want in the editor
</p>
</div>
<textarea
@ -226,22 +208,15 @@ const Editor = (props: Props): JSX.Element => {
<Button onClick={() => preline("##### ")} text={"H5"} />
<Button onClick={() => preline("###### ")} text={"H6"} />
</div>
}
>
}>
<Button icon={Icon.Title} />
</ToolTip>
<ToolTip
placement="bottom"
content={<h3 className="text-lg">Toggle Bold</h3>}
>
<ToolTip placement="bottom" content={<h3 className="text-lg">Toggle Bold</h3>}>
<Button onClick={() => toggleWrap("**")} icon={Icon.FormatBold} />
</ToolTip>
<ToolTip
placement="bottom"
content={<h3 className="text-lg">Toggle Italic</h3>}
>
<ToolTip placement="bottom" content={<h3 className="text-lg">Toggle Italic</h3>}>
<Button onClick={() => toggleWrap("_")} icon={Icon.FormatItalic} />
</ToolTip>
@ -251,12 +226,11 @@ const Editor = (props: Props): JSX.Element => {
<>
<h3 className="text-lg">Toggle Inline Code</h3>
<p>
Makes the text monospace (like text from a computer terminal).
Usually used for stylistic purposes in transcripts.
Makes the text monospace (like text from a computer terminal). Usually used for
stylistic purposes in transcripts.
</p>
</>
}
>
}>
<Button onClick={() => toggleWrap("`")} icon={Icon.Code} />
</ToolTip>
@ -267,8 +241,7 @@ const Editor = (props: Props): JSX.Element => {
<h3 className="text-lg">Insert footnote</h3>
<p>When inserted &ldquo;x&rdquo;</p>
</>
}
>
}>
<Button
onClick={() => {
insert("[^x]");
@ -284,8 +257,8 @@ const Editor = (props: Props): JSX.Element => {
<>
<h3 className="text-lg">Transcripts</h3>
<p>
Use this to create dialogues and transcripts. Start by adding
a container, then add transcript speech line within.
Use this to create dialogues and transcripts. Start by adding a container, then
add transcript speech line within.
</p>
<div className="grid gap-2">
<ToolTip
@ -294,12 +267,8 @@ const Editor = (props: Props): JSX.Element => {
<>
<h3 className="text-lg">Transcript container</h3>
</>
}
>
<Button
onClick={() => wrap("Transcript", {}, true)}
icon={Icon.AddBox}
/>
}>
<Button onClick={() => wrap("Transcript", {}, true)} icon={Icon.AddBox} />
</ToolTip>
<ToolTip
placement="right"
@ -307,13 +276,11 @@ const Editor = (props: Props): JSX.Element => {
<>
<h3 className="text-lg">Transcript speech line</h3>
<p>
Use to add a dialogue/transcript line. Change the{" "}
<kbd>name</kbd> property to chang the name of the
speaker
Use to add a dialogue/transcript line. Change the <kbd>name</kbd> property
to chang the name of the speaker
</p>
</>
}
>
}>
<Button
onClick={() => wrap("Line", { name: "speaker" })}
icon={Icon.RecordVoiceOver}
@ -321,24 +288,14 @@ const Editor = (props: Props): JSX.Element => {
</ToolTip>
</div>
</>
}
>
}>
<Button icon={Icon.RecordVoiceOver} />
</ToolTip>
<ToolTip
placement="bottom"
content={<h3 className="text-lg">Inset box</h3>}
>
<Button
onClick={() => wrap("InsetBox", {}, true)}
icon={Icon.CheckBoxOutlineBlank}
/>
<ToolTip placement="bottom" content={<h3 className="text-lg">Inset box</h3>}>
<Button onClick={() => wrap("InsetBox", {}, true)} icon={Icon.CheckBoxOutlineBlank} />
</ToolTip>
<ToolTip
placement="bottom"
content={<h3 className="text-lg">Scene break</h3>}
>
<ToolTip placement="bottom" content={<h3 className="text-lg">Scene break</h3>}>
<Button onClick={() => insert("\n* * *\n")} icon={Icon.MoreHoriz} />
</ToolTip>
<ToolTip
@ -350,12 +307,9 @@ const Editor = (props: Props): JSX.Element => {
content={
<>
<h3 className="text-lg">External Link</h3>
<p className="text-xs">
Provides a link to another webpage / website
</p>
<p className="text-xs">Provides a link to another webpage / website</p>
</>
}
>
}>
<Button
onClick={() => insert("[Link name](https://domain.com)")}
icon={Icon.Link}
@ -369,12 +323,10 @@ const Editor = (props: Props): JSX.Element => {
<>
<h3 className="text-lg">Intralink</h3>
<p className="text-xs">
Interlinks are used to add links to a header within the
same document
Interlinks are used to add links to a header within the same document
</p>
</>
}
>
}>
<Button
onClick={() => wrap("IntraLink", {})}
icon={Icon.Link}
@ -387,12 +339,11 @@ const Editor = (props: Props): JSX.Element => {
<>
<h3 className="text-lg">Intralink (with target)</h3>{" "}
<p className="text-xs">
Use this one if you want the intralink text to be
different from the target header&rsquo;s name.
Use this one if you want the intralink text to be different from the target
header&rsquo;s name.
</p>
</>
}
>
}>
<Button
onClick={() => wrap("IntraLink", { target: "target" })}
icon={Icon.Link}
@ -400,24 +351,17 @@ const Editor = (props: Props): JSX.Element => {
/>
</ToolTip>
</div>
}
>
}>
<Button icon={Icon.Link} />
</ToolTip>
<ToolTip
placement="bottom"
content={
<h3 className="text-lg">Player&rsquo;s name placeholder</h3>
}
>
content={<h3 className="text-lg">Player&rsquo;s name placeholder</h3>}>
<Button onClick={() => insert("@player")} icon={Icon.Person} />
</ToolTip>
<ToolTip
placement="bottom"
content={<h3 className="text-lg">Open HTML Converter</h3>}
>
<ToolTip placement="bottom" content={<h3 className="text-lg">Open HTML Converter</h3>}>
<Button
onClick={() => {
setConverterOpened(true);
@ -455,16 +399,7 @@ const Editor = (props: Props): JSX.Element => {
</div>
</ContentPanel>
),
[
appendDoc,
converterOpened,
handleInput,
insert,
markdown,
preline,
toggleWrap,
wrap,
]
[appendDoc, converterOpened, handleInput, insert, markdown, preline, toggleWrap, wrap]
);
return <AppLayout contentPanel={contentPanel} {...props} />;

View File

@ -3,10 +3,7 @@ import { useCallback, useMemo, useRef, useState } from "react";
import { AppLayout, AppLayoutRequired } from "components/AppLayout";
import { Button } from "components/Inputs/Button";
import { ButtonGroup } from "components/Inputs/ButtonGroup";
import {
ContentPanel,
ContentPanelWidthSizes,
} from "components/Panels/ContentPanel";
import { ContentPanel, ContentPanelWidthSizes } from "components/Panels/ContentPanel";
import { ToolTip } from "components/ToolTip";
import { getOpenGraph } from "helpers/openGraph";
import { getLangui } from "graphql/fetchLocalData";
@ -30,10 +27,7 @@ const replaceSelection = (
selectionStart: number,
selectionEnd: number,
newSelectedText: string
) =>
text.substring(0, selectionStart) +
newSelectedText +
text.substring(selectionEnd);
) => text.substring(0, selectionStart) + newSelectedText + text.substring(selectionEnd);
const swapChar = (char: string, swaps: string[]): string => {
for (let index = 0; index < swaps.length; index++) {
@ -60,10 +54,7 @@ const Transcript = (props: Props): JSX.Element => {
const updateLineIndex = useCallback(() => {
if (textAreaRef.current) {
const subText = textAreaRef.current.value.substring(
0,
textAreaRef.current.selectionStart
);
const subText = textAreaRef.current.value.substring(0, textAreaRef.current.selectionStart);
setLineIndex(subText.split("\n").length - 1);
}
}, []);
@ -202,10 +193,7 @@ const Transcript = (props: Props): JSX.Element => {
textAreaRef.current.selectionStart,
textAreaRef.current.selectionEnd
);
const selection = textAreaRef.current.value.substring(
selectionStart,
selectionEnd
);
const selection = textAreaRef.current.value.substring(selectionStart, selectionEnd);
if (selection.length === 1) {
let newSelection = selection;
@ -301,10 +289,7 @@ const Transcript = (props: Props): JSX.Element => {
textAreaRef.current.selectionStart,
textAreaRef.current.selectionEnd
);
const selection = textAreaRef.current.value.substring(
selectionStart,
selectionEnd
);
const selection = textAreaRef.current.value.substring(selectionStart, selectionEnd);
if (selection.length === 1) {
let newSelection = selection;
@ -376,10 +361,7 @@ const Transcript = (props: Props): JSX.Element => {
const contentPanel = useMemo(
() => (
<ContentPanel
width={ContentPanelWidthSizes.Full}
className="overflow-hidden !pr-0 !pt-4"
>
<ContentPanel width={ContentPanelWidthSizes.Full} className="overflow-hidden !pr-0 !pt-4">
<div className="grid grid-flow-col grid-cols-[1fr_5rem]">
<textarea
ref={textAreaRef}
@ -387,18 +369,14 @@ const Transcript = (props: Props): JSX.Element => {
onClick={updateLineIndex}
onKeyUp={updateLineIndex}
title="Input textarea"
className="whitespace-pre"
></textarea>
className="whitespace-pre"></textarea>
<p
className="h-[80vh] whitespace-nowrap font-[initial] font-bold
[writing-mode:vertical-rl] [transform-origin:top_right]"
style={{
transform: `scale(${fontSize}) translateX(${
fontSize * xOffset
}px)`,
}}
>
transform: `scale(${fontSize}) translateX(${fontSize * xOffset}px)`,
}}>
{text.split("\n")[lineIndex]}
</p>
</div>
@ -412,10 +390,7 @@ const Transcript = (props: Props): JSX.Element => {
min="0"
max="100"
value={xOffset * 10}
onChange={(event) =>
setXOffset(parseInt(event.target.value, 10) / 10)
}
></input>
onChange={(event) => setXOffset(parseInt(event.target.value, 10) / 10)}></input>
</div>
<div className="grid place-items-center">
@ -428,8 +403,7 @@ const Transcript = (props: Props): JSX.Element => {
value={fontSize * SIZE_MULTIPLIER}
onChange={(event) =>
setFontSize(parseInt(event.target.value, 10) / SIZE_MULTIPLIER)
}
></input>
}></input>
</div>
<ToolTip content="Automatically convert Western punctuations to Japanese ones.">
<Button text=". ⟹ 。" onClick={convertPunctuation} />
@ -526,8 +500,7 @@ const Transcript = (props: Props): JSX.Element => {
]}
/>
</div>
}
>
}>
<Button text={"Quotations"} />
</ToolTip>
<ToolTip
@ -543,8 +516,7 @@ const Transcript = (props: Props): JSX.Element => {
<Button text={""} onClick={() => insert("")} />
<Button text={'" "'} onClick={() => insert(" ")} />
</div>
}
>
}>
<Button text="Insert" />
</ToolTip>
</div>
@ -565,13 +537,7 @@ const Transcript = (props: Props): JSX.Element => {
]
);
return (
<AppLayout
contentPanel={contentPanel}
{...props}
contentPanelScroolbar={false}
/>
);
return <AppLayout contentPanel={contentPanel} {...props} contentPanelScroolbar={false} />;
};
export default Transcript;

View File

@ -1,9 +1,6 @@
import { PostPage } from "components/PostPage";
import { useAppLayout } from "contexts/AppLayoutContext";
import {
getPostStaticProps,
PostStaticProps,
} from "graphql/getPostStaticProps";
import { getPostStaticProps, PostStaticProps } from "graphql/getPostStaticProps";
import { getOpenGraph } from "helpers/openGraph";
/*
@ -23,9 +20,7 @@ const Home = ({ ...otherProps }: PostStaticProps): JSX.Element => {
[mask-size:contain] [mask-repeat:no-repeat] [mask-position:center]"
/>
<h1 className="mb-0 text-5xl">Accord&rsquo;s Library</h1>
<h2 className="-mt-5 text-xl">
Discover Analyze Translate Archive
</h2>
<h2 className="-mt-5 text-xl">Discover Analyze Translate Archive</h2>
</div>
}
displayTitle={false}

View File

@ -11,10 +11,7 @@ import { InsetBox } from "components/InsetBox";
import { PreviewCardCTAs } from "components/Library/PreviewCardCTAs";
import { NavOption } from "components/PanelComponents/NavOption";
import { ReturnButton } from "components/PanelComponents/ReturnButton";
import {
ContentPanel,
ContentPanelWidthSizes,
} from "components/Panels/ContentPanel";
import { ContentPanel, ContentPanelWidthSizes } from "components/Panels/ContentPanel";
import { SubPanel } from "components/Panels/SubPanel";
import { PreviewCard } from "components/PreviewCard";
import { useAppLayout } from "contexts/AppLayoutContext";
@ -64,13 +61,7 @@ import { Ids } from "types/ids";
* CONSTANTS
*/
const intersectionIds = [
"summary",
"gallery",
"details",
"subitems",
"contents",
];
const intersectionIds = ["summary", "gallery", "details", "subitems", "contents"];
/*
*
@ -78,14 +69,8 @@ const intersectionIds = [
*/
interface Props extends AppLayoutRequired {
item: NonNullable<
NonNullable<
GetLibraryItemQuery["libraryItems"]
>["data"][number]["attributes"]
>;
itemId: NonNullable<
GetLibraryItemQuery["libraryItems"]
>["data"][number]["id"];
item: NonNullable<NonNullable<GetLibraryItemQuery["libraryItems"]>["data"][number]["attributes"]>;
itemId: NonNullable<GetLibraryItemQuery["libraryItems"]>["data"][number]["id"];
}
const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
@ -97,8 +82,7 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
const hoverable = useDeviceSupportsHover();
const router = useRouter();
const [openLightBox, LightBox] = useLightBox();
const { value: keepInfoVisible, toggle: toggleKeepInfoVisible } =
useBoolean(false);
const { value: keepInfoVisible, toggle: toggleKeepInfoVisible } = useBoolean(false);
useScrollTopOnChange(Ids.ContentPanel, [item]);
const currentIntersection = useIntersectionList(intersectionIds);
@ -113,8 +97,7 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
const displayOpenScans = useMemo(
() =>
item.contents?.data.some(
(content) =>
content.attributes?.scan_set && content.attributes.scan_set.length > 0
(content) => content.attributes?.scan_set && content.attributes.scan_set.length > 0
),
[item.contents?.data]
);
@ -122,11 +105,7 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
const subPanel = useMemo(
() => (
<SubPanel>
<ReturnButton
href="/library/"
title={langui.library}
displayOnlyOn="3ColumnsLayout"
/>
<ReturnButton href="/library/" title={langui.library} displayOnlyOn="3ColumnsLayout" />
<HorizontalLine />
@ -174,14 +153,7 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
</div>
</SubPanel>
),
[
currentIntersection,
isVariantSet,
item.contents,
item.gallery,
item.subitems,
langui,
]
[currentIntersection, isVariantSet, item.contents, item.gallery, item.subitems, langui]
);
const contentPanel = useMemo(
@ -203,15 +175,9 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
)}
onClick={() => {
if (item.thumbnail?.data?.attributes) {
openLightBox([
getAssetURL(
item.thumbnail.data.attributes.url,
ImageQuality.Large
),
]);
openLightBox([getAssetURL(item.thumbnail.data.attributes.url, ImageQuality.Large)]);
}
}}
>
}}>
{item.thumbnail?.data?.attributes ? (
<Img
src={item.thumbnail.data.attributes}
@ -245,39 +211,28 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
)}
</div>
{!isUntangibleGroupItem(item.metadata?.[0]) &&
isDefinedAndNotEmpty(itemId) && (
<PreviewCardCTAs id={itemId} expand />
)}
{!isUntangibleGroupItem(item.metadata?.[0]) && isDefinedAndNotEmpty(itemId) && (
<PreviewCardCTAs id={itemId} expand />
)}
{item.descriptions?.[0] && (
<p className="text-justify">
{item.descriptions[0].description}
</p>
<p className="text-justify">{item.descriptions[0].description}</p>
)}
{!(
item.metadata &&
item.metadata[0]?.__typename === "ComponentMetadataGroup" &&
(item.metadata[0].subtype?.data?.attributes?.slug ===
"variant-set" ||
item.metadata[0].subtype?.data?.attributes?.slug ===
"relation-set")
(item.metadata[0].subtype?.data?.attributes?.slug === "variant-set" ||
item.metadata[0].subtype?.data?.attributes?.slug === "relation-set")
) && (
<>
{item.urls?.length ? (
<div className="flex flex-row place-items-center gap-3">
<p>{langui.available_at}</p>
{filterHasAttributes(item.urls, ["url"] as const).map(
(url, index) => (
<Fragment key={index}>
<Button
href={url.url}
text={prettyURL(url.url)}
alwaysNewTab
/>
</Fragment>
)
)}
{filterHasAttributes(item.urls, ["url"] as const).map((url, index) => (
<Fragment key={index}>
<Button href={url.url} text={prettyURL(url.url)} alwaysNewTab />
</Fragment>
))}
</div>
) : (
<p>{langui.item_not_available}</p>
@ -288,41 +243,34 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
</InsetBox>
{item.gallery && item.gallery.data.length > 0 && (
<div
id={intersectionIds[1]}
className="grid w-full place-items-center gap-8"
>
<div id={intersectionIds[1]} className="grid w-full place-items-center gap-8">
<h2 className="text-2xl">{langui.gallery}</h2>
<div
className="grid w-full grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))] items-end
gap-8"
>
{filterHasAttributes(item.gallery.data, [
"id",
"attributes",
] as const).map((galleryItem, index) => (
<Fragment key={galleryItem.id}>
<div
className="relative aspect-square cursor-pointer
gap-8">
{filterHasAttributes(item.gallery.data, ["id", "attributes"] as const).map(
(galleryItem, index) => (
<Fragment key={galleryItem.id}>
<div
className="relative aspect-square cursor-pointer
transition-transform hover:scale-[1.02]"
onClick={() => {
const images: string[] = filterHasAttributes(
item.gallery?.data,
["attributes"] as const
).map((image) =>
getAssetURL(image.attributes.url, ImageQuality.Large)
);
openLightBox(images, index);
}}
>
<Img
className="h-full w-full rounded-lg
onClick={() => {
const images: string[] = filterHasAttributes(item.gallery?.data, [
"attributes",
] as const).map((image) =>
getAssetURL(image.attributes.url, ImageQuality.Large)
);
openLightBox(images, index);
}}>
<Img
className="h-full w-full rounded-lg
bg-light object-cover drop-shadow-shade-md"
src={galleryItem.attributes}
/>
</div>
</Fragment>
))}
src={galleryItem.attributes}
/>
</div>
</Fragment>
)
)}
</div>
</div>
)}
@ -333,12 +281,8 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
<div
className={cJoin(
"grid place-items-center gap-y-8",
cIf(
!isContentPanelNoMoreThan3xl,
"grid-flow-col place-content-between"
)
)}
>
cIf(!isContentPanelNoMoreThan3xl, "grid-flow-col place-content-between")
)}>
{item.metadata?.[0] && (
<div className="grid place-content-start place-items-center">
<h3 className="text-xl">{langui.type}</h3>
@ -367,8 +311,7 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
item.price.currency?.data?.attributes?.code
)}
</p>
{item.price.currency?.data?.attributes?.code !==
currency && (
{item.price.currency?.data?.attributes?.code !== currency && (
<p>
{prettyPrice(item.price, currencies, currency)} <br />(
{langui.calculated?.toLowerCase()})
@ -382,11 +325,11 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
<div className="flex flex-col place-items-center gap-2">
<h3 className="text-xl">{langui.categories}</h3>
<div className="flex flex-row flex-wrap place-content-center gap-2">
{filterHasAttributes(item.categories.data, [
"attributes",
] as const).map((category) => (
<Chip key={category.id} text={category.attributes.name} />
))}
{filterHasAttributes(item.categories.data, ["attributes"] as const).map(
(category) => (
<Chip key={category.id} text={category.attributes.name} />
)
)}
</div>
</div>
)}
@ -396,8 +339,7 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
className={cJoin(
"grid gap-4",
cIf(isContentPanelNoMoreThan3xl, "place-items-center")
)}
>
)}>
<h3 className="text-xl">{langui.size}</h3>
<div
className={cJoin(
@ -407,8 +349,7 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
"grid-flow-row place-content-center gap-8",
"grid-flow-col place-content-between"
)
)}
>
)}>
<div
className={cJoin(
"grid gap-x-4",
@ -417,8 +358,7 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
"place-items-center",
"grid-flow-col place-items-start"
)
)}
>
)}>
<p className="font-bold">{langui.width}:</p>
<div>
<p>{item.size.width} mm</p>
@ -433,8 +373,7 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
"place-items-center",
"grid-flow-col place-items-start"
)
)}
>
)}>
<p className="font-bold">{langui.height}:</p>
<div>
<p>{item.size.height} mm</p>
@ -450,8 +389,7 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
"place-items-center",
"grid-flow-col place-items-start"
)
)}
>
)}>
<p className="font-bold">{langui.thickness}:</p>
<div>
<p>{item.size.thickness} mm</p>
@ -469,12 +407,10 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
className={cJoin(
"grid gap-4",
cIf(isContentPanelNoMoreThan3xl, "place-items-center")
)}
>
)}>
<h3 className="text-xl">{langui.type_information}</h3>
<div className="flex flex-wrap place-content-between gap-x-8">
{item.metadata?.[0]?.__typename ===
"ComponentMetadataBooks" && (
{item.metadata?.[0]?.__typename === "ComponentMetadataBooks" && (
<>
<div className="flex flex-row place-content-start gap-4">
<p className="font-bold">{langui.pages}:</p>
@ -507,9 +443,7 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
<div className="flex flex-row place-content-start gap-4">
<p className="font-bold">{langui.languages}:</p>
{item.metadata[0]?.languages?.data.map((lang) => (
<p key={lang.attributes?.code}>
{lang.attributes?.name}
</p>
<p key={lang.attributes?.code}>{lang.attributes?.name}</p>
))}
</div>
</>
@ -521,130 +455,109 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
</InsetBox>
{item.subitems && item.subitems.data.length > 0 && (
<div
id={intersectionIds[3]}
className="grid w-full place-items-center gap-8"
>
<h2 className="text-2xl">
{isVariantSet ? langui.variants : langui.subitems}
</h2>
<div id={intersectionIds[3]} className="grid w-full place-items-center gap-8">
<h2 className="text-2xl">{isVariantSet ? langui.variants : langui.subitems}</h2>
{hoverable && (
<WithLabel label={langui.always_show_info}>
<Switch
onClick={toggleKeepInfoVisible}
value={keepInfoVisible}
/>
<Switch onClick={toggleKeepInfoVisible} value={keepInfoVisible} />
</WithLabel>
)}
<div
className="grid w-full grid-cols-[repeat(auto-fill,minmax(13rem,1fr))]
items-end gap-8"
>
{filterHasAttributes(item.subitems.data, [
"id",
"attributes",
] as const).map((subitem) => (
<Fragment key={subitem.id}>
<PreviewCard
href={`/library/${subitem.attributes.slug}`}
title={subitem.attributes.title}
subtitle={subitem.attributes.subtitle}
thumbnail={subitem.attributes.thumbnail?.data?.attributes}
thumbnailAspectRatio="21/29.7"
thumbnailRounded={false}
keepInfoVisible={keepInfoVisible}
topChips={
subitem.attributes.metadata &&
subitem.attributes.metadata.length > 0 &&
subitem.attributes.metadata[0]
? [prettyItemSubType(subitem.attributes.metadata[0])]
: []
}
bottomChips={subitem.attributes.categories?.data.map(
(category) => category.attributes?.short ?? ""
)}
metadata={{
releaseDate: subitem.attributes.release_date,
price: subitem.attributes.price,
position: "Bottom",
}}
infoAppend={
!isUntangibleGroupItem(
subitem.attributes.metadata?.[0]
) && <PreviewCardCTAs id={subitem.id} />
}
/>
</Fragment>
))}
items-end gap-8">
{filterHasAttributes(item.subitems.data, ["id", "attributes"] as const).map(
(subitem) => (
<Fragment key={subitem.id}>
<PreviewCard
href={`/library/${subitem.attributes.slug}`}
title={subitem.attributes.title}
subtitle={subitem.attributes.subtitle}
thumbnail={subitem.attributes.thumbnail?.data?.attributes}
thumbnailAspectRatio="21/29.7"
thumbnailRounded={false}
keepInfoVisible={keepInfoVisible}
topChips={
subitem.attributes.metadata &&
subitem.attributes.metadata.length > 0 &&
subitem.attributes.metadata[0]
? [prettyItemSubType(subitem.attributes.metadata[0])]
: []
}
bottomChips={subitem.attributes.categories?.data.map(
(category) => category.attributes?.short ?? ""
)}
metadata={{
releaseDate: subitem.attributes.release_date,
price: subitem.attributes.price,
position: "Bottom",
}}
infoAppend={
!isUntangibleGroupItem(subitem.attributes.metadata?.[0]) && (
<PreviewCardCTAs id={subitem.id} />
)
}
/>
</Fragment>
)
)}
</div>
</div>
)}
{item.contents && item.contents.data.length > 0 && (
<div
id={intersectionIds[4]}
className="grid w-full place-items-center gap-8"
>
<div id={intersectionIds[4]} className="grid w-full place-items-center gap-8">
<h2 className="-mb-6 text-2xl">{langui.contents}</h2>
{displayOpenScans && (
<Button
href={`/library/${item.slug}/scans`}
text={langui.view_scans}
/>
<Button href={`/library/${item.slug}/scans`} text={langui.view_scans} />
)}
<div className="max-w- grid w-full gap-4">
{filterHasAttributes(item.contents.data, [
"attributes",
] as const).map((rangedContent) => (
<ContentLine
content={
rangedContent.attributes.content?.data?.attributes
? {
translations: filterDefined(
rangedContent.attributes.content.data.attributes
.translations
).map((translation) => ({
pre_title: translation.pre_title,
title: translation.title,
subtitle: translation.subtitle,
language:
translation.language?.data?.attributes?.code,
})),
categories: filterHasAttributes(
rangedContent.attributes.content.data.attributes
.categories?.data,
["attributes"]
).map((category) => category.attributes.short),
type:
rangedContent.attributes.content.data.attributes
.type?.data?.attributes?.titles?.[0]?.title ??
prettySlug(
rangedContent.attributes.content.data.attributes
.type?.data?.attributes?.slug
),
slug: rangedContent.attributes.content.data
.attributes.slug,
}
: undefined
}
rangeStart={
rangedContent.attributes.range[0]?.__typename ===
"ComponentRangePageRange"
? `${rangedContent.attributes.range[0].starting_page}`
: ""
}
slug={rangedContent.attributes.slug}
parentSlug={item.slug}
key={rangedContent.id}
hasScanSet={
isDefined(rangedContent.attributes.scan_set) &&
rangedContent.attributes.scan_set.length > 0
}
condensed={isContentPanelNoMoreThan3xl}
/>
))}
{filterHasAttributes(item.contents.data, ["attributes"] as const).map(
(rangedContent) => (
<ContentLine
content={
rangedContent.attributes.content?.data?.attributes
? {
translations: filterDefined(
rangedContent.attributes.content.data.attributes.translations
).map((translation) => ({
pre_title: translation.pre_title,
title: translation.title,
subtitle: translation.subtitle,
language: translation.language?.data?.attributes?.code,
})),
categories: filterHasAttributes(
rangedContent.attributes.content.data.attributes.categories?.data,
["attributes"]
).map((category) => category.attributes.short),
type:
rangedContent.attributes.content.data.attributes.type?.data
?.attributes?.titles?.[0]?.title ??
prettySlug(
rangedContent.attributes.content.data.attributes.type?.data
?.attributes?.slug
),
slug: rangedContent.attributes.content.data.attributes.slug,
}
: undefined
}
rangeStart={
rangedContent.attributes.range[0]?.__typename === "ComponentRangePageRange"
? `${rangedContent.attributes.range[0].starting_page}`
: ""
}
slug={rangedContent.attributes.slug}
parentSlug={item.slug}
key={rangedContent.id}
hasScanSet={
isDefined(rangedContent.attributes.scan_set) &&
rangedContent.attributes.scan_set.length > 0
}
condensed={isContentPanelNoMoreThan3xl}
/>
)
)}
</div>
</div>
)}
@ -684,13 +597,7 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
]
);
return (
<AppLayout
contentPanel={contentPanel}
subPanel={subPanel}
{...otherProps}
/>
);
return <AppLayout contentPanel={contentPanel} subPanel={subPanel} {...otherProps} />;
};
export default LibrarySlug;
@ -703,10 +610,7 @@ export const getStaticProps: GetStaticProps = async (context) => {
const sdk = getReadySdk();
const langui = getLangui(context.locale);
const item = await sdk.getLibraryItem({
slug:
context.params && isDefined(context.params.slug)
? context.params.slug.toString()
: "",
slug: context.params && isDefined(context.params.slug) ? context.params.slug.toString() : "",
language_code: context.locale ?? "en",
});
if (!item.libraryItems?.data[0]?.attributes) return { notFound: true };
@ -721,16 +625,12 @@ export const getStaticProps: GetStaticProps = async (context) => {
item.libraryItems.data[0].attributes.categories?.data,
["attributes.short"]
).map((category) => category.attributes.short),
[langui.type ?? "Type"]: item.libraryItems.data[0].attributes
.metadata?.[0]
[langui.type ?? "Type"]: item.libraryItems.data[0].attributes.metadata?.[0]
? [prettyItemSubType(item.libraryItems.data[0].attributes.metadata[0])]
: [],
[langui.release_date ?? "Release date"]: [
item.libraryItems.data[0].attributes.release_date
? prettyDate(
item.libraryItems.data[0].attributes.release_date,
context.locale
)
? prettyDate(item.libraryItems.data[0].attributes.release_date, context.locale)
: undefined,
],
}
@ -739,12 +639,7 @@ export const getStaticProps: GetStaticProps = async (context) => {
const props: Props = {
item: item.libraryItems.data[0].attributes,
itemId: item.libraryItems.data[0].id,
openGraph: getOpenGraph(
langui,
title,
description,
thumbnail?.data?.attributes
),
openGraph: getOpenGraph(langui, title, description, thumbnail?.data?.attributes),
};
return {
props: props,
@ -757,9 +652,7 @@ export const getStaticPaths: GetStaticPaths = async (context) => {
const sdk = getReadySdk();
const libraryItems = await sdk.getLibraryItemsSlugs();
const paths: GetStaticPathsResult["paths"] = [];
filterHasAttributes(libraryItems.libraryItems?.data, [
"attributes",
] as const).map((item) => {
filterHasAttributes(libraryItems.libraryItems?.data, ["attributes"] as const).map((item) => {
context.locales?.map((local) =>
paths.push({ params: { slug: item.attributes.slug }, locale: local })
);
@ -808,9 +701,7 @@ const ContentLine = ({
const [selectedTranslation] = useSmartLanguage({
items: content?.translations ?? [],
languageExtractor: useCallback(
(
item: NonNullable<ContentLineProps["content"]>["translations"][number]
) => item.language,
(item: NonNullable<ContentLineProps["content"]>["translations"][number]) => item.language,
[]
),
});
@ -844,21 +735,14 @@ const ContentLine = ({
{hasScanSet || isDefined(content) ? (
<>
{hasScanSet && (
<Button
href={`/library/${parentSlug}/scans#${slug}`}
text={langui.view_scans}
/>
<Button href={`/library/${parentSlug}/scans#${slug}`} text={langui.view_scans} />
)}
{isDefined(content) && (
<Button
href={`/contents/${content.slug}`}
text={langui.open_content}
/>
<Button href={`/contents/${content.slug}`} text={langui.open_content} />
)}
</>
) : (
/* TODO: Add to langui */
"The content is not available"
langui.content_is_not_available
)}
</div>
</div>
@ -870,8 +754,7 @@ const ContentLine = ({
className={cJoin(
"grid gap-2 rounded-lg px-4",
cIf(isOpened, "my-2 h-auto bg-mid py-3 shadow-inner-sm shadow-shade")
)}
>
)}>
<div className="grid grid-cols-[auto_auto_1fr_auto_12ch] place-items-center gap-4">
<a>
<h3 className="cursor-pointer" onClick={toggleOpened}>
@ -893,35 +776,25 @@ const ContentLine = ({
</div>
<p className="h-4 w-full border-b-2 border-dotted border-black opacity-30"></p>
<p>{rangeStart}</p>
{content?.type && (
<Chip className="justify-self-end" text={content.type} />
)}
{content?.type && <Chip className="justify-self-end" text={content.type} />}
</div>
<div
className={`grid-flow-col place-content-start place-items-center gap-2 ${
isOpened ? "grid" : "hidden"
}`}
>
}`}>
<Ico icon={Icon.SubdirectoryArrowRight} className="text-dark" />
{hasScanSet || isDefined(content) ? (
<>
{hasScanSet && (
<Button
href={`/library/${parentSlug}/scans#${slug}`}
text={langui.view_scans}
/>
<Button href={`/library/${parentSlug}/scans#${slug}`} text={langui.view_scans} />
)}
{isDefined(content) && (
<Button
href={`/contents/${content.slug}`}
text={langui.open_content}
/>
<Button href={`/contents/${content.slug}`} text={langui.open_content} />
)}
</>
) : (
/* TODO: Add to langui */
"The content is not available"
langui.content_is_not_available
)}
</div>
</div>

View File

@ -2,21 +2,11 @@ import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next";
import { Fragment, useCallback, useMemo } from "react";
import { AppLayout, AppLayoutRequired } from "components/AppLayout";
import { ReturnButton } from "components/PanelComponents/ReturnButton";
import {
ContentPanel,
ContentPanelWidthSizes,
} from "components/Panels/ContentPanel";
import { ContentPanel, ContentPanelWidthSizes } from "components/Panels/ContentPanel";
import { SubPanel } from "components/Panels/SubPanel";
import {
GetLibraryItemScansQuery,
UploadImageFragment,
} from "graphql/generated";
import { GetLibraryItemScansQuery, UploadImageFragment } from "graphql/generated";
import { getReadySdk } from "graphql/sdk";
import {
prettyInlineTitle,
prettySlug,
prettyItemSubType,
} from "helpers/formatters";
import { prettyInlineTitle, prettySlug, prettyItemSubType } from "helpers/formatters";
import {
filterHasAttributes,
getStatusDescription,
@ -41,10 +31,7 @@ import { useSmartLanguage } from "hooks/useSmartLanguage";
import { TranslatedProps } from "types/TranslatedProps";
import { TranslatedNavOption } from "components/PanelComponents/NavOption";
import { useIntersectionList } from "hooks/useIntersectionList";
import {
useIs1ColumnLayout,
useIsContentPanelNoMoreThan,
} from "hooks/useContainerQuery";
import { useIs1ColumnLayout, useIsContentPanelNoMoreThan } from "hooks/useContainerQuery";
import { cIf, cJoin } from "helpers/className";
import { useAppLayout } from "contexts/AppLayoutContext";
import { getLangui } from "graphql/fetchLocalData";
@ -56,13 +43,9 @@ import { getLangui } from "graphql/fetchLocalData";
interface Props extends AppLayoutRequired {
item: NonNullable<
NonNullable<
GetLibraryItemScansQuery["libraryItems"]
>["data"][number]["attributes"]
>;
itemId: NonNullable<
NonNullable<GetLibraryItemScansQuery["libraryItems"]>["data"][number]["id"]
NonNullable<GetLibraryItemScansQuery["libraryItems"]>["data"][number]["attributes"]
>;
itemId: NonNullable<NonNullable<GetLibraryItemScansQuery["libraryItems"]>["data"][number]["id"]>;
}
const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
@ -72,9 +55,9 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
const ids = useMemo(
() =>
filterHasAttributes(item.contents?.data, [
"attributes.slug",
] as const).map((content) => content.attributes.slug),
filterHasAttributes(item.contents?.data, ["attributes.slug"] as const).map(
(content) => content.attributes.slug
),
[item.contents?.data]
);
const currentIntersection = useIntersectionList(ids);
@ -103,18 +86,16 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
? [prettyItemSubType(item.metadata[0])]
: []
}
bottomChips={filterHasAttributes(item.categories?.data, [
"attributes",
] as const).map((category) => category.attributes.short)}
bottomChips={filterHasAttributes(item.categories?.data, ["attributes"] as const).map(
(category) => category.attributes.short
)}
metadata={{
releaseDate: item.release_date,
price: item.price,
position: "Bottom",
}}
infoAppend={
!isUntangibleGroupItem(item.metadata?.[0]) && (
<PreviewCardCTAs id={itemId} />
)
!isUntangibleGroupItem(item.metadata?.[0]) && <PreviewCardCTAs id={itemId} />
}
/>
</div>
@ -122,54 +103,46 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
<HorizontalLine />
<p className="mb-4 font-headers text-2xl font-bold">
{langui.contents}
</p>
<p className="mb-4 font-headers text-2xl font-bold">{langui.contents}</p>
{filterHasAttributes(item.contents?.data, ["attributes"] as const).map(
(content, index) => (
<>
{content.attributes.scan_set &&
content.attributes.scan_set.length > 0 && (
<TranslatedNavOption
key={content.id}
url={`#${content.attributes.slug}`}
translations={filterHasAttributes(
content.attributes.content?.data?.attributes
?.translations,
["language.data.attributes"] as const
).map((translation) => ({
language: translation.language.data.attributes.code,
title: prettyInlineTitle(
translation.pre_title,
translation.title,
translation.subtitle
),
subtitle:
content.attributes.range[0]?.__typename ===
"ComponentRangePageRange"
? `${content.attributes.range[0].starting_page}` +
`` +
`${content.attributes.range[0].ending_page}`
: undefined,
}))}
fallback={{
title: prettySlug(content.attributes.slug, item.slug),
subtitle:
content.attributes.range[0]?.__typename ===
"ComponentRangePageRange"
? `${content.attributes.range[0].starting_page}` +
`` +
`${content.attributes.range[0].ending_page}`
: undefined,
}}
border
active={index === currentIntersection}
/>
)}
</>
)
)}
{filterHasAttributes(item.contents?.data, ["attributes"] as const).map((content, index) => (
<>
{content.attributes.scan_set && content.attributes.scan_set.length > 0 && (
<TranslatedNavOption
key={content.id}
url={`#${content.attributes.slug}`}
translations={filterHasAttributes(
content.attributes.content?.data?.attributes?.translations,
["language.data.attributes"] as const
).map((translation) => ({
language: translation.language.data.attributes.code,
title: prettyInlineTitle(
translation.pre_title,
translation.title,
translation.subtitle
),
subtitle:
content.attributes.range[0]?.__typename === "ComponentRangePageRange"
? `${content.attributes.range[0].starting_page}` +
`` +
`${content.attributes.range[0].ending_page}`
: undefined,
}))}
fallback={{
title: prettySlug(content.attributes.slug, item.slug),
subtitle:
content.attributes.range[0]?.__typename === "ComponentRangePageRange"
? `${content.attributes.range[0].starting_page}` +
`` +
`${content.attributes.range[0].ending_page}`
: undefined,
}}
border
active={index === currentIntersection}
/>
)}
</>
))}
</SubPanel>
),
[
@ -201,9 +174,7 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
className="mb-10"
/>
{item.images && (
<ScanSetCover images={item.images} openLightBox={openLightBox} />
)}
{item.images && <ScanSetCover images={item.images} openLightBox={openLightBox} />}
{item.contents?.data.map((content) => (
<Fragment key={content.id}>
@ -233,23 +204,10 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
))}
</ContentPanel>
),
[
LightBox,
openLightBox,
item.contents?.data,
item.images,
item.slug,
langui,
]
[LightBox, openLightBox, item.contents?.data, item.images, item.slug, langui]
);
return (
<AppLayout
contentPanel={contentPanel}
subPanel={subPanel}
{...otherProps}
/>
);
return <AppLayout contentPanel={contentPanel} subPanel={subPanel} {...otherProps} />;
};
export default LibrarySlug;
@ -262,10 +220,7 @@ export const getStaticProps: GetStaticProps = async (context) => {
const sdk = getReadySdk();
const langui = getLangui(context.locale);
const item = await sdk.getLibraryItemScans({
slug:
context.params && isDefined(context.params.slug)
? context.params.slug.toString()
: "",
slug: context.params && isDefined(context.params.slug) ? context.params.slug.toString() : "",
language_code: context.locale ?? "en",
});
if (!item.libraryItems?.data[0]?.attributes || !item.libraryItems.data[0]?.id)
@ -293,9 +248,7 @@ export const getStaticPaths: GetStaticPaths = async (context) => {
const sdk = getReadySdk();
const libraryItems = await sdk.getLibraryItemsSlugs({});
const paths: GetStaticPathsResult["paths"] = [];
filterHasAttributes(libraryItems.libraryItems?.data, [
"attributes",
] as const).map((item) => {
filterHasAttributes(libraryItems.libraryItems?.data, ["attributes"] as const).map((item) => {
context.locales?.map((local) =>
paths.push({ params: { slug: item.attributes.slug }, locale: local })
);
@ -323,9 +276,7 @@ interface ScanSetProps {
NonNullable<
NonNullable<
NonNullable<
NonNullable<
GetLibraryItemScansQuery["libraryItems"]
>["data"][number]["attributes"]
NonNullable<GetLibraryItemScansQuery["libraryItems"]>["data"][number]["attributes"]
>["contents"]
>["data"][number]["attributes"]
>["scan_set"]
@ -336,66 +287,53 @@ interface ScanSetProps {
content: NonNullable<
NonNullable<
NonNullable<
NonNullable<
GetLibraryItemScansQuery["libraryItems"]
>["data"][number]["attributes"]
NonNullable<GetLibraryItemScansQuery["libraryItems"]>["data"][number]["attributes"]
>["contents"]
>["data"][number]["attributes"]
>["content"];
}
const ScanSet = ({
openLightBox,
scanSet,
id,
title,
content,
}: ScanSetProps): JSX.Element => {
const ScanSet = ({ openLightBox, scanSet, id, title, content }: ScanSetProps): JSX.Element => {
const is1ColumnLayout = useIsContentPanelNoMoreThan("2xl");
const { langui } = useAppLayout();
const [selectedScan, LanguageSwitcher, languageSwitcherProps] =
useSmartLanguage({
items: scanSet,
languageExtractor: useCallback(
(item: NonNullable<ScanSetProps["scanSet"][number]>) =>
item.language?.data?.attributes?.code,
[]
),
transform: useCallback(
(item: NonNullable<ScanSetProps["scanSet"][number]>) => {
item.pages?.data.sort((a, b) => {
if (
a.attributes &&
b.attributes &&
isDefinedAndNotEmpty(a.attributes.url) &&
isDefinedAndNotEmpty(b.attributes.url)
) {
let aName = getAssetFilename(a.attributes.url);
let bName = getAssetFilename(b.attributes.url);
const [selectedScan, LanguageSwitcher, languageSwitcherProps] = useSmartLanguage({
items: scanSet,
languageExtractor: useCallback(
(item: NonNullable<ScanSetProps["scanSet"][number]>) => item.language?.data?.attributes?.code,
[]
),
transform: useCallback((item: NonNullable<ScanSetProps["scanSet"][number]>) => {
item.pages?.data.sort((a, b) => {
if (
a.attributes &&
b.attributes &&
isDefinedAndNotEmpty(a.attributes.url) &&
isDefinedAndNotEmpty(b.attributes.url)
) {
let aName = getAssetFilename(a.attributes.url);
let bName = getAssetFilename(b.attributes.url);
/*
* If the number is a succession of 0s, make the number
* incrementally smaller than 0 (i.e: 00 becomes -1)
*/
if (aName.replaceAll("0", "").length === 0) {
aName = (1 - aName.length).toString(10);
}
if (bName.replaceAll("0", "").length === 0) {
bName = (1 - bName.length).toString(10);
}
/*
* If the number is a succession of 0s, make the number
* incrementally smaller than 0 (i.e: 00 becomes -1)
*/
if (aName.replaceAll("0", "").length === 0) {
aName = (1 - aName.length).toString(10);
}
if (bName.replaceAll("0", "").length === 0) {
bName = (1 - bName.length).toString(10);
}
if (isInteger(aName) && isInteger(bName)) {
return parseInt(aName, 10) - parseInt(bName, 10);
}
return a.attributes.url.localeCompare(b.attributes.url);
}
return 0;
});
return item;
},
[]
),
});
if (isInteger(aName) && isInteger(bName)) {
return parseInt(aName, 10) - parseInt(bName, 10);
}
return a.attributes.url.localeCompare(b.attributes.url);
}
return 0;
});
return item;
}, []),
});
const pages = useMemo(
() => filterHasAttributes(selectedScan?.pages?.data, ["attributes"]),
@ -408,8 +346,7 @@ const ScanSet = ({
<div>
<div
className="flex flex-row flex-wrap place-items-center
gap-6 pt-10 text-base first-of-type:pt-0"
>
gap-6 pt-10 text-base first-of-type:pt-0">
<h2 id={id} className="text-2xl">
{title}
</h2>
@ -425,13 +362,12 @@ const ScanSet = ({
</div>
<div className="flex flex-row flex-wrap place-items-center gap-4 pb-6">
{content?.data?.attributes &&
isDefinedAndNotEmpty(content.data.attributes.slug) && (
<Button
href={`/contents/${content.data.attributes.slug}`}
text={langui.open_content}
/>
)}
{content?.data?.attributes && isDefinedAndNotEmpty(content.data.attributes.slug) && (
<Button
href={`/contents/${content.data.attributes.slug}`}
text={langui.open_content}
/>
)}
{languageSwitcherProps.locales.size > 1 && (
<LanguageSwitcher {...languageSwitcherProps} />
@ -441,8 +377,7 @@ const ScanSet = ({
<p className="font-headers font-bold">{langui.status}:</p>
<ToolTip
content={getStatusDescription(selectedScan.status, langui)}
maxWidth={"20rem"}
>
maxWidth={"20rem"}>
<Chip text={selectedScan.status} />
</ToolTip>
</div>
@ -479,24 +414,21 @@ const ScanSet = ({
</div>
)}
{selectedScan.typesetters &&
selectedScan.typesetters.data.length > 0 && (
<div>
<p className="font-headers font-bold">
{langui.typesetters}:
</p>
<div className="grid place-content-center place-items-center gap-2">
{filterHasAttributes(selectedScan.typesetters.data, [
"id",
"attributes",
] as const).map((typesetter) => (
<Fragment key={typesetter.id}>
<RecorderChip recorder={typesetter.attributes} />
</Fragment>
))}
</div>
{selectedScan.typesetters && selectedScan.typesetters.data.length > 0 && (
<div>
<p className="font-headers font-bold">{langui.typesetters}:</p>
<div className="grid place-content-center place-items-center gap-2">
{filterHasAttributes(selectedScan.typesetters.data, [
"id",
"attributes",
] as const).map((typesetter) => (
<Fragment key={typesetter.id}>
<RecorderChip recorder={typesetter.attributes} />
</Fragment>
))}
</div>
)}
</div>
)}
{isDefinedAndNotEmpty(selectedScan.notes) && (
<ToolTip content={selectedScan.notes}>
@ -514,8 +446,7 @@ const ScanSet = ({
"grid-cols-2 gap-[4vmin]",
"grid-cols-[repeat(auto-fill,_minmax(10rem,1fr))]"
)
)}
>
)}>
{pages.map((page, index) => (
<div
key={page.id}
@ -526,8 +457,7 @@ const ScanSet = ({
getAssetURL(image.attributes.url, ImageQuality.Large)
);
openLightBox(images, index);
}}
>
}}>
<Img src={page.attributes} quality={ImageQuality.Small} />
</div>
))}
@ -547,18 +477,10 @@ const TranslatedScanSet = ({
}: TranslatedProps<ScanSetProps, "title">): JSX.Element => {
const [selectedTranslation] = useSmartLanguage({
items: translations,
languageExtractor: useCallback(
(item: { language: string }): string => item.language,
[]
),
languageExtractor: useCallback((item: { language: string }): string => item.language, []),
});
return (
<ScanSet
title={selectedTranslation?.title ?? fallback.title}
{...otherProps}
/>
);
return <ScanSet title={selectedTranslation?.title ?? fallback.title} {...otherProps} />;
};
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
@ -567,28 +489,22 @@ interface ScanSetCoverProps {
openLightBox: (images: string[], index?: number) => void;
images: NonNullable<
NonNullable<
NonNullable<
GetLibraryItemScansQuery["libraryItems"]
>["data"][number]["attributes"]
NonNullable<GetLibraryItemScansQuery["libraryItems"]>["data"][number]["attributes"]
>["images"]
>;
}
const ScanSetCover = ({
openLightBox,
images,
}: ScanSetCoverProps): JSX.Element => {
const ScanSetCover = ({ openLightBox, images }: ScanSetCoverProps): JSX.Element => {
const is1ColumnLayout = useIsContentPanelNoMoreThan("4xl");
const { langui } = useAppLayout();
const [selectedScan, LanguageSwitcher, languageSwitcherProps] =
useSmartLanguage({
items: images,
languageExtractor: useCallback(
(item: NonNullable<ScanSetCoverProps["images"][number]>) =>
item.language?.data?.attributes?.code,
[]
),
});
const [selectedScan, LanguageSwitcher, languageSwitcherProps] = useSmartLanguage({
items: images,
languageExtractor: useCallback(
(item: NonNullable<ScanSetCoverProps["images"][number]>) =>
item.language?.data?.attributes?.code,
[]
),
});
const coverImages = useMemo(() => {
const memo: UploadImageFragment[] = [];
@ -613,8 +529,7 @@ const ScanSetCover = ({
<div>
<div
className="flex flex-row flex-wrap place-items-center
gap-6 pt-10 text-base first-of-type:pt-0"
>
gap-6 pt-10 text-base first-of-type:pt-0">
<h2 id={"cover"} className="text-2xl">
{langui.cover}
</h2>
@ -636,8 +551,7 @@ const ScanSetCover = ({
<p className="font-headers font-bold">{langui.status}:</p>
<ToolTip
content={getStatusDescription(selectedScan.status, langui)}
maxWidth={"20rem"}
>
maxWidth={"20rem"}>
<Chip text={selectedScan.status} />
</ToolTip>
</div>
@ -674,24 +588,21 @@ const ScanSetCover = ({
</div>
)}
{selectedScan.typesetters &&
selectedScan.typesetters.data.length > 0 && (
<div>
<p className="font-headers font-bold">
{langui.typesetters}:
</p>
<div className="grid place-content-center place-items-center gap-2">
{filterHasAttributes(selectedScan.typesetters.data, [
"id",
"attributes",
] as const).map((typesetter) => (
<Fragment key={typesetter.id}>
<RecorderChip recorder={typesetter.attributes} />
</Fragment>
))}
</div>
{selectedScan.typesetters && selectedScan.typesetters.data.length > 0 && (
<div>
<p className="font-headers font-bold">{langui.typesetters}:</p>
<div className="grid place-content-center place-items-center gap-2">
{filterHasAttributes(selectedScan.typesetters.data, [
"id",
"attributes",
] as const).map((typesetter) => (
<Fragment key={typesetter.id}>
<RecorderChip recorder={typesetter.attributes} />
</Fragment>
))}
</div>
)}
</div>
)}
</div>
<div
@ -703,21 +614,17 @@ const ScanSetCover = ({
"grid-cols-2 gap-[4vmin]",
"grid-cols-[repeat(auto-fill,_minmax(10rem,1fr))]"
)
)}
>
)}>
{coverImages.map((image, index) => (
<div
key={image.url}
className="cursor-pointer transition-transform
drop-shadow-shade-lg hover:scale-[1.02]"
onClick={() => {
const imgs = coverImages.map((img) =>
getAssetURL(img.url, ImageQuality.Large)
);
const imgs = coverImages.map((img) => getAssetURL(img.url, ImageQuality.Large));
openLightBox(imgs, index);
}}
>
}}>
<Img src={image} quality={ImageQuality.Small} />
</div>
))}

View File

@ -6,15 +6,12 @@ import { AppLayout, AppLayoutRequired } from "components/AppLayout";
import { Select } from "components/Inputs/Select";
import { Switch } from "components/Inputs/Switch";
import { PanelHeader } from "components/PanelComponents/PanelHeader";
import {
ContentPanel,
ContentPanelWidthSizes,
} from "components/Panels/ContentPanel";
import { ContentPanel, ContentPanelWidthSizes } from "components/Panels/ContentPanel";
import { SubPanel } from "components/Panels/SubPanel";
import { GetLibraryItemsPreviewQuery } from "graphql/generated";
import { getReadySdk } from "graphql/sdk";
import { prettyInlineTitle, prettyItemSubType } from "helpers/formatters";
import { LibraryItemUserStatus } from "helpers/types";
import { LibraryItemUserStatus } from "types/types";
import { Icon } from "components/Ico";
import { WithLabel } from "components/Inputs/WithLabel";
import { TextInput } from "components/Inputs/TextInput";
@ -24,12 +21,7 @@ import { isUntangibleGroupItem } from "helpers/libraryItem";
import { PreviewCard } from "components/PreviewCard";
import { useDeviceSupportsHover } from "hooks/useMediaQuery";
import { ButtonGroup } from "components/Inputs/ButtonGroup";
import {
filterHasAttributes,
isDefined,
isDefinedAndNotEmpty,
isUndefined,
} from "helpers/others";
import { filterHasAttributes, isDefined, isDefinedAndNotEmpty, isUndefined } from "helpers/others";
import { useAppLayout } from "contexts/AppLayoutContext";
import { convertPrice } from "helpers/numbers";
import { SmartList } from "components/SmartList";
@ -74,9 +66,7 @@ const Library = ({ items, ...otherProps }: Props): JSX.Element => {
const { libraryItemUserStatus } = useAppLayout();
const isContentPanelAtLeast4xl = useIsContentPanelAtLeast("4xl");
const [searchName, setSearchName] = useState(
DEFAULT_FILTERS_STATE.searchName
);
const [searchName, setSearchName] = useState(DEFAULT_FILTERS_STATE.searchName);
const {
value: showSubitems,
@ -102,27 +92,20 @@ const Library = ({ items, ...otherProps }: Props): JSX.Element => {
setValue: setKeepInfoVisible,
} = useBoolean(DEFAULT_FILTERS_STATE.keepInfoVisible);
const [sortingMethod, setSortingMethod] = useState<number>(
DEFAULT_FILTERS_STATE.sortingMethod
);
const [sortingMethod, setSortingMethod] = useState<number>(DEFAULT_FILTERS_STATE.sortingMethod);
const [groupingMethod, setGroupingMethod] = useState<number>(
DEFAULT_FILTERS_STATE.groupingMethod
);
const [filterUserStatus, setFilterUserStatus] = useState<
LibraryItemUserStatus | undefined
>(DEFAULT_FILTERS_STATE.filterUserStatus);
const [filterUserStatus, setFilterUserStatus] = useState<LibraryItemUserStatus | undefined>(
DEFAULT_FILTERS_STATE.filterUserStatus
);
const filteringFunction = useCallback(
(
item: SelectiveNonNullable<Props["items"][number], "attributes" | "id">
) => {
(item: SelectiveNonNullable<Props["items"][number], "attributes" | "id">) => {
if (!showSubitems && !item.attributes.root_item) return false;
if (
showSubitems &&
isUntangibleGroupItem(item.attributes.metadata?.[0])
) {
if (showSubitems && isUntangibleGroupItem(item.attributes.metadata?.[0])) {
return false;
}
if (item.attributes.primary && !showPrimaryItems) return false;
@ -142,13 +125,7 @@ const Library = ({ items, ...otherProps }: Props): JSX.Element => {
}
return true;
},
[
libraryItemUserStatus,
filterUserStatus,
showPrimaryItems,
showSecondaryItems,
showSubitems,
]
[libraryItemUserStatus, filterUserStatus, showPrimaryItems, showSecondaryItems, showSubitems]
);
const sortingFunction = useCallback(
@ -158,16 +135,8 @@ const Library = ({ items, ...otherProps }: Props): JSX.Element => {
) => {
switch (sortingMethod) {
case 0: {
const titleA = prettyInlineTitle(
"",
a.attributes.title,
a.attributes.subtitle
);
const titleB = prettyInlineTitle(
"",
b.attributes.title,
b.attributes.subtitle
);
const titleA = prettyInlineTitle("", a.attributes.title, a.attributes.subtitle);
const titleB = prettyInlineTitle("", b.attributes.title, b.attributes.subtitle);
return naturalCompare(titleA, titleB);
}
case 1: {
@ -180,10 +149,7 @@ const Library = ({ items, ...otherProps }: Props): JSX.Element => {
return priceA - priceB;
}
case 2: {
return compareDate(
a.attributes.release_date,
b.attributes.release_date
);
return compareDate(a.attributes.release_date, b.attributes.release_date);
}
default:
return 0;
@ -193,15 +159,12 @@ const Library = ({ items, ...otherProps }: Props): JSX.Element => {
);
const groupingFunction = useCallback(
(
item: SelectiveNonNullable<Props["items"][number], "attributes" | "id">
): string[] => {
(item: SelectiveNonNullable<Props["items"][number], "attributes" | "id">): string[] => {
switch (groupingMethod) {
case 0: {
const categories = filterHasAttributes(
item.attributes.categories?.data,
["attributes"] as const
);
const categories = filterHasAttributes(item.attributes.categories?.data, [
"attributes",
] as const);
if (categories.length > 0) {
return categories.map((category) => category.attributes.name);
}
@ -221,10 +184,7 @@ const Library = ({ items, ...otherProps }: Props): JSX.Element => {
case "ComponentMetadataOther":
return [langui.other ?? "Other"];
case "ComponentMetadataGroup": {
switch (
item.attributes.metadata[0]?.subitems_type?.data?.attributes
?.slug
) {
switch (item.attributes.metadata[0]?.subitems_type?.data?.attributes?.slug) {
case "audio":
return [langui.audio ?? "Audio"];
case "video":
@ -318,9 +278,7 @@ const Library = ({ items, ...otherProps }: Props): JSX.Element => {
onChange={(value) => {
setSortingMethod(value);
umami(
`[Library] Change sorting method (${
["name", "price", "release date"][value]
})`
`[Library] Change sorting method (${["name", "price", "release date"][value]})`
);
}}
/>
@ -341,9 +299,7 @@ const Library = ({ items, ...otherProps }: Props): JSX.Element => {
value={showPrimaryItems}
onClick={() => {
toggleShowPrimaryItems();
umami(
`[Library] ${showPrimaryItems ? "Hide" : "Show"} primary items`
);
umami(`[Library] ${showPrimaryItems ? "Hide" : "Show"} primary items`);
}}
/>
</WithLabel>
@ -353,11 +309,7 @@ const Library = ({ items, ...otherProps }: Props): JSX.Element => {
value={showSecondaryItems}
onClick={() => {
toggleShowSecondaryItems();
umami(
`[Library] ${
showSecondaryItems ? "Hide" : "Show"
} secondary items`
);
umami(`[Library] ${showSecondaryItems ? "Hide" : "Show"} secondary items`);
}}
/>
</WithLabel>
@ -368,9 +320,7 @@ const Library = ({ items, ...otherProps }: Props): JSX.Element => {
value={keepInfoVisible}
onClick={() => {
toggleKeepInfoVisible();
umami(
`[Library] Always ${keepInfoVisible ? "hide" : "show"} info`
);
umami(`[Library] Always ${keepInfoVisible ? "hide" : "show"} info`);
}}
/>
</WithLabel>
@ -497,20 +447,13 @@ const Library = ({ items, ...otherProps }: Props): JSX.Element => {
)}
className={cJoin(
"grid-cols-2 items-end",
cIf(
isContentPanelAtLeast4xl,
"grid-cols-[repeat(auto-fill,_minmax(13rem,1fr))]"
)
cIf(isContentPanelAtLeast4xl, "grid-cols-[repeat(auto-fill,_minmax(13rem,1fr))]")
)}
searchingTerm={searchName}
sortingFunction={sortingFunction}
groupingFunction={groupingFunction}
searchingBy={(item) =>
prettyInlineTitle(
"",
item.attributes.title,
item.attributes.subtitle
)
prettyInlineTitle("", item.attributes.title, item.attributes.subtitle)
}
filteringFunction={filteringFunction}
paginationItemPerPage={25}

View File

@ -1,9 +1,6 @@
import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next";
import { PostPage } from "components/PostPage";
import {
getPostStaticProps,
PostStaticProps,
} from "graphql/getPostStaticProps";
import { getPostStaticProps, PostStaticProps } from "graphql/getPostStaticProps";
import { getReadySdk } from "graphql/sdk";
import { filterHasAttributes, isDefined } from "helpers/others";
import { useAppLayout } from "contexts/AppLayoutContext";
@ -37,9 +34,7 @@ export default LibrarySlug;
export const getStaticProps: GetStaticProps = async (context) => {
const slug =
context.params && isDefined(context.params.slug)
? context.params.slug.toString()
: "";
context.params && isDefined(context.params.slug) ? context.params.slug.toString() : "";
return await getPostStaticProps(slug)(context);
};
@ -50,13 +45,11 @@ export const getStaticPaths: GetStaticPaths = async (context) => {
const posts = await sdk.getPostsSlugs();
const paths: GetStaticPathsResult["paths"] = [];
filterHasAttributes(posts.posts?.data, ["attributes"] as const).map(
(item) => {
context.locales?.map((local) =>
paths.push({ params: { slug: item.attributes.slug }, locale: local })
);
}
);
filterHasAttributes(posts.posts?.data, ["attributes"] as const).map((item) => {
context.locales?.map((local) =>
paths.push({ params: { slug: item.attributes.slug }, locale: local })
);
});
return {
paths,
fallback: "blocking",

View File

@ -4,10 +4,7 @@ import { useBoolean } from "usehooks-ts";
import { AppLayout, AppLayoutRequired } from "components/AppLayout";
import { Switch } from "components/Inputs/Switch";
import { PanelHeader } from "components/PanelComponents/PanelHeader";
import {
ContentPanel,
ContentPanelWidthSizes,
} from "components/Panels/ContentPanel";
import { ContentPanel, ContentPanelWidthSizes } from "components/Panels/ContentPanel";
import { SubPanel } from "components/Panels/SubPanel";
import { GetPostsPreviewQuery } from "graphql/generated";
import { getReadySdk } from "graphql/sdk";
@ -51,9 +48,7 @@ const News = ({ posts, ...otherProps }: Props): JSX.Element => {
const isContentPanelAtLeast4xl = useIsContentPanelAtLeast("4xl");
const { langui } = useAppLayout();
const hoverable = useDeviceSupportsHover();
const [searchName, setSearchName] = useState(
DEFAULT_FILTERS_STATE.searchName
);
const [searchName, setSearchName] = useState(DEFAULT_FILTERS_STATE.searchName);
const {
value: keepInfoVisible,
toggle: toggleKeepInfoVisible,
@ -63,11 +58,7 @@ const News = ({ posts, ...otherProps }: Props): JSX.Element => {
const subPanel = useMemo(
() => (
<SubPanel>
<PanelHeader
icon={Icon.Feed}
title={langui.news}
description={langui.news_description}
/>
<PanelHeader icon={Icon.Feed} title={langui.news} description={langui.news_description} />
<HorizontalLine />
@ -91,9 +82,7 @@ const News = ({ posts, ...otherProps }: Props): JSX.Element => {
value={keepInfoVisible}
onClick={() => {
toggleKeepInfoVisible();
umami(
`[News] Always ${keepInfoVisible ? "hide" : "show"} info`
);
umami(`[News] Always ${keepInfoVisible ? "hide" : "show"} info`);
}}
/>
</WithLabel>
@ -111,14 +100,7 @@ const News = ({ posts, ...otherProps }: Props): JSX.Element => {
/>
</SubPanel>
),
[
hoverable,
keepInfoVisible,
langui,
searchName,
setKeepInfoVisible,
toggleKeepInfoVisible,
]
[hoverable, keepInfoVisible, langui, searchName, setKeepInfoVisible, toggleKeepInfoVisible]
);
const contentPanel = useMemo(
@ -207,6 +189,4 @@ export const getStaticProps: GetStaticProps = async (context) => {
*/
const sortPosts = (posts: Props["posts"]): Props["posts"] =>
posts
.sort((a, b) => compareDate(a.attributes?.date, b.attributes?.date))
.reverse();
posts.sort((a, b) => compareDate(a.attributes?.date, b.attributes?.date)).reverse();

View File

@ -5,28 +5,18 @@ import { Chip } from "components/Chip";
import { HorizontalLine } from "components/HorizontalLine";
import { Img } from "components/Img";
import { ReturnButton } from "components/PanelComponents/ReturnButton";
import {
ContentPanel,
ContentPanelWidthSizes,
} from "components/Panels/ContentPanel";
import { ContentPanel, ContentPanelWidthSizes } from "components/Panels/ContentPanel";
import { SubPanel } from "components/Panels/SubPanel";
import DefinitionCard from "components/Wiki/DefinitionCard";
import { getReadySdk } from "graphql/sdk";
import {
filterHasAttributes,
isDefined,
isDefinedAndNotEmpty,
} from "helpers/others";
import { WikiPageWithTranslations } from "helpers/types";
import { filterHasAttributes, isDefined, isDefinedAndNotEmpty } from "helpers/others";
import { WikiPageWithTranslations } from "types/types";
import { useSmartLanguage } from "hooks/useSmartLanguage";
import { prettySlug } from "helpers/formatters";
import { prettySlug, sJoin } from "helpers/formatters";
import { useLightBox } from "hooks/useLightBox";
import { getAssetURL, ImageQuality } from "helpers/img";
import { getOpenGraph } from "helpers/openGraph";
import {
getDefaultPreferredLanguages,
staticSmartLanguage,
} from "helpers/locales";
import { getDefaultPreferredLanguages, staticSmartLanguage } from "helpers/locales";
import { getDescription } from "helpers/description";
import { cIf, cJoin } from "helpers/className";
import { useIs3ColumnsLayout } from "hooks/useContainerQuery";
@ -44,15 +34,14 @@ interface Props extends AppLayoutRequired {
const WikiPage = ({ page, ...otherProps }: Props): JSX.Element => {
const { langui } = useAppLayout();
const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] =
useSmartLanguage({
items: page.translations,
languageExtractor: useCallback(
(item: NonNullable<Props["page"]["translations"][number]>) =>
item.language?.data?.attributes?.code,
[]
),
});
const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] = useSmartLanguage({
items: page.translations,
languageExtractor: useCallback(
(item: NonNullable<Props["page"]["translations"][number]>) =>
item.language?.data?.attributes?.code,
[]
),
});
const [openLightBox, LightBox] = useLightBox();
const is3ColumnsLayout = useIs3ColumnsLayout();
@ -60,11 +49,7 @@ const WikiPage = ({ page, ...otherProps }: Props): JSX.Element => {
const subPanel = useMemo(
() => (
<SubPanel>
<ReturnButton
href={`/wiki`}
title={langui.wiki}
displayOnlyOn={"3ColumnsLayout"}
/>
<ReturnButton href={`/wiki`} title={langui.wiki} displayOnlyOn={"3ColumnsLayout"} />
</SubPanel>
),
[langui]
@ -84,14 +69,11 @@ const WikiPage = ({ page, ...otherProps }: Props): JSX.Element => {
<div className="flex flex-wrap place-content-center gap-3">
<h1 className="text-center text-3xl">{selectedTranslation?.title}</h1>
{selectedTranslation?.aliases &&
selectedTranslation.aliases.length > 0 && (
<p className="mr-3 text-center text-2xl">
{`(${selectedTranslation.aliases
.map((alias) => alias?.alias)
.join("・")})`}
</p>
)}
{selectedTranslation?.aliases && selectedTranslation.aliases.length > 0 && (
<p className="mr-3 text-center text-2xl">
{`(${selectedTranslation.aliases.map((alias) => alias?.alias).join("・")})`}
</p>
)}
<LanguageSwitcher {...languageSwitcherProps} />
</div>
@ -103,8 +85,7 @@ const WikiPage = ({ page, ...otherProps }: Props): JSX.Element => {
className={cJoin(
"mb-8 overflow-hidden rounded-lg bg-mid text-center",
cIf(is3ColumnsLayout, "float-right ml-8 w-[25rem]")
)}
>
)}>
{page.thumbnail?.data?.attributes && (
<Img
src={page.thumbnail.data.attributes}
@ -113,10 +94,7 @@ const WikiPage = ({ page, ...otherProps }: Props): JSX.Element => {
onClick={() => {
if (page.thumbnail?.data?.attributes?.url) {
openLightBox([
getAssetURL(
page.thumbnail.data.attributes.url,
ImageQuality.Large
),
getAssetURL(page.thumbnail.data.attributes.url, ImageQuality.Large),
]);
}
}}
@ -125,37 +103,27 @@ const WikiPage = ({ page, ...otherProps }: Props): JSX.Element => {
<div className="my-4 grid gap-4 p-4">
{page.categories?.data && page.categories.data.length > 0 && (
<>
<p className="font-headers text-xl font-bold">
{langui.categories}
</p>
<p className="font-headers text-xl font-bold">{langui.categories}</p>
<div className="flex flex-row flex-wrap place-content-center gap-2">
{filterHasAttributes(page.categories.data, [
"attributes",
] as const).map((category) => (
<Chip
key={category.id}
text={category.attributes.name}
/>
))}
{filterHasAttributes(page.categories.data, ["attributes"] as const).map(
(category) => (
<Chip key={category.id} text={category.attributes.name} />
)
)}
</div>
</>
)}
{page.tags?.data && page.tags.data.length > 0 && (
<>
<p className="font-headers text-xl font-bold">
{langui.tags}
</p>
<p className="font-headers text-xl font-bold">{langui.tags}</p>
<div className="flex flex-row flex-wrap place-content-center gap-2">
{filterHasAttributes(page.tags.data, [
"attributes",
] as const).map((tag) => (
{filterHasAttributes(page.tags.data, ["attributes"] as const).map((tag) => (
<Chip
key={tag.id}
text={
tag.attributes.titles?.[0]?.title ??
prettySlug(tag.attributes.slug)
tag.attributes.titles?.[0]?.title ?? prettySlug(tag.attributes.slug)
}
/>
))}
@ -167,40 +135,41 @@ const WikiPage = ({ page, ...otherProps }: Props): JSX.Element => {
{isDefinedAndNotEmpty(selectedTranslation.summary) && (
<div className="mb-12">
<p className="font-headers text-lg font-bold">
{langui.summary}
</p>
<p className="font-headers text-lg font-bold">{langui.summary}</p>
<p>{selectedTranslation.summary}</p>
</div>
)}
{filterHasAttributes(page.definitions, [
"translations",
] as const).map((definition, index) => (
<div key={index} className="mb-12">
<DefinitionCard
source={{
name: definition.source?.data?.attributes?.name,
url: definition.source?.data?.attributes?.content?.data
?.attributes?.slug
? `/contents/${definition.source.data.attributes.content.data.attributes.slug}`
: `/library/${definition.source?.data?.attributes?.ranged_content?.data?.attributes?.library_item?.data?.attributes?.slug}`,
}}
translations={definition.translations.map(
(translation) => ({
{filterHasAttributes(page.definitions, ["translations"] as const).map(
(definition, index) => (
<div key={index} className="mb-12">
<DefinitionCard
source={{
name: definition.source?.data?.attributes?.name,
url: definition.source?.data?.attributes?.content?.data?.attributes?.slug
? sJoin(
"/contents/",
definition.source.data.attributes.content.data.attributes.slug
)
: cJoin(
"/library/",
definition.source?.data?.attributes?.ranged_content?.data?.attributes
?.library_item?.data?.attributes?.slug
),
}}
translations={definition.translations.map((translation) => ({
language: translation?.language?.data?.attributes?.code,
definition: translation?.definition,
status: translation?.status,
})
)}
index={index + 1}
categories={filterHasAttributes(
definition.categories?.data,
["attributes"] as const
).map((category) => category.attributes.short)}
/>
</div>
))}
}))}
index={index + 1}
categories={filterHasAttributes(definition.categories?.data, [
"attributes",
] as const).map((category) => category.attributes.short)}
/>
</div>
)
)}
</div>
</>
)}
@ -221,13 +190,7 @@ const WikiPage = ({ page, ...otherProps }: Props): JSX.Element => {
]
);
return (
<AppLayout
subPanel={subPanel}
contentPanel={contentPanel}
{...otherProps}
/>
);
return <AppLayout subPanel={subPanel} contentPanel={contentPanel} {...otherProps} />;
};
export default WikiPage;
@ -240,24 +203,19 @@ export const getStaticProps: GetStaticProps = async (context) => {
const sdk = getReadySdk();
const langui = getLangui(context.locale);
const slug =
context.params && isDefined(context.params.slug)
? context.params.slug.toString()
: "";
context.params && isDefined(context.params.slug) ? context.params.slug.toString() : "";
const page = await sdk.getWikiPage({
language_code: context.locale ?? "en",
slug: slug,
});
if (!page.wikiPages?.data[0].attributes?.translations)
return { notFound: true };
if (!page.wikiPages?.data[0].attributes?.translations) return { notFound: true };
const { title, description } = (() => {
const chipsGroups = {
[langui.tags ?? "Tags"]: filterHasAttributes(
page.wikiPages.data[0].attributes.tags?.data,
["attributes"] as const
).map(
(tag) =>
tag.attributes.titles?.[0]?.title ?? prettySlug(tag.attributes.slug)
[langui.tags ?? "Tags"]: filterHasAttributes(page.wikiPages.data[0].attributes.tags?.data, [
"attributes",
] as const).map(
(tag) => tag.attributes.titles?.[0]?.title ?? prettySlug(tag.attributes.slug)
),
[langui.categories ?? "Categories"]: filterHasAttributes(
page.wikiPages.data[0].attributes.categories?.data,
@ -269,10 +227,7 @@ export const getStaticProps: GetStaticProps = async (context) => {
const selectedTranslation = staticSmartLanguage({
items: page.wikiPages.data[0].attributes.translations,
languageExtractor: (item) => item.language?.data?.attributes?.code,
preferredLanguages: getDefaultPreferredLanguages(
context.locale,
context.locales
),
preferredLanguages: getDefaultPreferredLanguages(context.locale, context.locales),
});
if (selectedTranslation) {
return {
@ -288,8 +243,7 @@ export const getStaticProps: GetStaticProps = async (context) => {
};
})();
const thumbnail =
page.wikiPages.data[0].attributes.thumbnail?.data?.attributes;
const thumbnail = page.wikiPages.data[0].attributes.thumbnail?.data?.attributes;
const props: Props = {
page: page.wikiPages.data[0].attributes as WikiPageWithTranslations,
@ -306,16 +260,14 @@ export const getStaticPaths: GetStaticPaths = async (context) => {
const sdk = getReadySdk();
const contents = await sdk.getWikiPagesSlugs();
const paths: GetStaticPathsResult["paths"] = [];
filterHasAttributes(contents.wikiPages?.data, ["attributes"] as const).map(
(wikiPage) => {
context.locales?.map((local) =>
paths.push({
params: { slug: wikiPage.attributes.slug },
locale: local,
})
);
}
);
filterHasAttributes(contents.wikiPages?.data, ["attributes"] as const).map((wikiPage) => {
context.locales?.map((local) =>
paths.push({
params: { slug: wikiPage.attributes.slug },
locale: local,
})
);
});
return {
paths,
fallback: "blocking",

Some files were not shown because too many files have changed in this diff Show More