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

@ -8,3 +8,4 @@ postcss.config.js
tailwind.config.js tailwind.config.js
design.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-magic-numbers": "warn",
// "no-mixed-operators": "warn", // "no-mixed-operators": "warn",
"no-multi-assign": "warn", "no-multi-assign": "warn",
"no-multi-str": "warn", // "no-multi-str": "warn",
"no-negated-condition": "warn", "no-negated-condition": "warn",
// "no-nested-ternary": "warn", // "no-nested-ternary": "warn",
"no-new": "warn", "no-new": "warn",
@ -149,24 +149,15 @@ module.exports = {
"@typescript-eslint/ban-tslint-comment": "warn", "@typescript-eslint/ban-tslint-comment": "warn",
"@typescript-eslint/class-literal-property-style": "warn", "@typescript-eslint/class-literal-property-style": "warn",
"@typescript-eslint/consistent-indexed-object-style": "warn", "@typescript-eslint/consistent-indexed-object-style": "warn",
"@typescript-eslint/consistent-type-assertions": [ "@typescript-eslint/consistent-type-assertions": ["warn", { assertionStyle: "as" }],
"warn",
{ assertionStyle: "as" },
],
"@typescript-eslint/consistent-type-exports": "error", "@typescript-eslint/consistent-type-exports": "error",
"@typescript-eslint/explicit-module-boundary-types": "warn", "@typescript-eslint/explicit-module-boundary-types": "warn",
"@typescript-eslint/method-signature-style": ["error", "property"], "@typescript-eslint/method-signature-style": ["error", "property"],
"@typescript-eslint/no-base-to-string": "warn", "@typescript-eslint/no-base-to-string": "warn",
"@typescript-eslint/no-confusing-non-null-assertion": "warn", "@typescript-eslint/no-confusing-non-null-assertion": "warn",
"@typescript-eslint/no-confusing-void-expression": [ "@typescript-eslint/no-confusing-void-expression": ["error", { ignoreArrowShorthand: true }],
"error",
{ ignoreArrowShorthand: true },
],
"@typescript-eslint/no-dynamic-delete": "error", "@typescript-eslint/no-dynamic-delete": "error",
"@typescript-eslint/no-empty-interface": [ "@typescript-eslint/no-empty-interface": ["error", { allowSingleExtends: true }],
"error",
{ allowSingleExtends: true },
],
"@typescript-eslint/no-invalid-void-type": "error", "@typescript-eslint/no-invalid-void-type": "error",
"@typescript-eslint/no-meaningless-void-operator": "error", "@typescript-eslint/no-meaningless-void-operator": "error",
"@typescript-eslint/no-non-null-asserted-nullish-coalescing": "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}` }, headers: { Authorization: `Bearer ${process.env.ACCESS_TOKEN}` },
}, },
}, },
documents: [ documents: ["src/graphql/operations/**/*.graphql", "src/graphql/fragments/*.graphql"],
"src/graphql/operations/**/*.graphql",
"src/graphql/fragments/*.graphql",
],
generates: { generates: {
"src/graphql/generated.ts": { "src/graphql/generated.ts": {
plugins: [ plugins: ["typescript", "typescript-operations", "typescript-graphql-request"],
"typescript",
"typescript-operations",
"typescript-graphql-request",
],
}, },
}, },
}; };

View File

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

View File

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

View File

@ -15,12 +15,11 @@ interface Props {
export const Chip = ({ className, text }: Props): JSX.Element => ( export const Chip = ({ className, text }: Props): JSX.Element => (
<div <div
className={cJoin( className={cJoin(
`grid place-content-center place-items-center whitespace-nowrap rounded-full `grid place-content-center place-items-center whitespace-nowrap rounded-full border-[1px]
border-[1px] px-1.5 pb-[0.14rem] text-xs opacity-70 px-1.5 pb-[0.14rem] text-xs opacity-70 transition-[color,_opacity,_border-color]
transition-[color,_opacity,_border-color] hover:opacity-100`, hover:opacity-100`,
className className
)} )}>
>
{text} {text}
</div> </div>
); );

View File

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

View File

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

View File

@ -13,9 +13,5 @@ interface Props {
export const HorizontalLine = ({ className }: Props): JSX.Element => ( export const HorizontalLine = ({ className }: Props): JSX.Element => (
<div <div
className={cJoin( className={cJoin("my-8 h-0 w-full border-t-[3px] border-dotted border-black", className)}></div>
"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 => ( export const Ico = ({ onClick, icon, className }: Props): JSX.Element => (
<span <span
onClick={onClick} onClick={onClick}
className={cJoin( className={cJoin("material-icons [font-size:inherit] [line-height:inherit]", className)}>
"material-icons [font-size:inherit] [line-height:inherit]",
className
)}
>
{icon} {icon}
</span> </span>
); );

View File

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

View File

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

View File

@ -18,18 +18,14 @@ interface Props {
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
export const ButtonGroup = ({ export const ButtonGroup = ({ buttonsProps, className }: Props): JSX.Element => (
buttonsProps,
className,
}: Props): JSX.Element => (
<div className={cJoin("grid grid-flow-col", className)}> <div className={cJoin("grid grid-flow-col", className)}>
{buttonsProps.map((buttonProps, index) => ( {buttonsProps.map((buttonProps, index) => (
<ConditionalWrapper <ConditionalWrapper
key={index} key={index}
isWrapping={isDefinedAndNotEmpty(buttonProps.tooltip)} isWrapping={isDefinedAndNotEmpty(buttonProps.tooltip)}
wrapper={ToolTipWrapper} wrapper={ToolTipWrapper}
wrapperProps={{ text: buttonProps.tooltip ?? "" }} wrapperProps={{ text: buttonProps.tooltip ?? "" }}>
>
<Button <Button
{...buttonProps} {...buttonProps}
className={ className={

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -14,23 +14,14 @@ interface Props {
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
export const WithLabel = ({ export const WithLabel = ({ label, children, disabled }: Props): JSX.Element => (
label,
children,
disabled,
}: Props): JSX.Element => (
<div <div
className={cJoin( className={cJoin(
"flex flex-row place-content-between place-items-center gap-2", "flex flex-row place-content-between place-items-center gap-2",
cIf(disabled, "text-dark brightness-150 contrast-75 grayscale") cIf(disabled, "text-dark brightness-150 contrast-75 grayscale")
)} )}>
>
{isDefinedAndNotEmpty(label) && ( {isDefinedAndNotEmpty(label) && (
<p <p className={cJoin("text-left", cIf(label.length < 10, "flex-shrink-0"))}>{label}:</p>
className={cJoin("text-left", cIf(label.length < 10, "flex-shrink-0"))}
>
{label}:
</p>
)} )}
{children} {children}
</div> </div>

View File

@ -16,11 +16,7 @@ interface Props {
export const InsetBox = ({ id, className, children }: Props): JSX.Element => ( export const InsetBox = ({ id, className, children }: Props): JSX.Element => (
<div <div
id={id} id={id}
className={cJoin( className={cJoin("w-full rounded-xl bg-mid p-8 shadow-inner-sm shadow-shade", className)}>
"w-full rounded-xl bg-mid p-8 shadow-inner-sm shadow-shade",
className
)}
>
{children} {children}
</div> </div>
); );

View File

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

View File

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

View File

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

View File

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

View File

@ -14,11 +14,7 @@ interface Props {
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
export const PanelHeader = ({ export const PanelHeader = ({ icon, description, title }: Props): JSX.Element => (
icon,
description,
title,
}: Props): JSX.Element => (
<> <>
<div className="grid w-full place-items-center"> <div className="grid w-full place-items-center">
{icon && <Ico icon={icon} className="mb-3 !text-4xl" />} {icon && <Ico icon={icon} className="mb-3 !text-4xl" />}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -177,6 +177,9 @@ query localDataGetWebsiteInterfaces {
anchor_link_copied anchor_link_copied
folders folders
empty_folder_message 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, wrapper: Wrapper,
wrapperProps, wrapperProps,
}: ConditionalWrapperProps<T>): JSX.Element => }: ConditionalWrapperProps<T>): JSX.Element =>
isWrapping ? ( isWrapping ? <Wrapper {...wrapperProps}>{children}</Wrapper> : <>{children}</>;
<Wrapper {...wrapperProps}>{children}</Wrapper>
) : (
<>{children}</>
);

View File

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

View File

@ -58,7 +58,7 @@ export const prettyInlineTitle = (
return result; return result;
}; };
export const prettyItemType = (metadata: any, langui: Langui): string => { export const prettyItemType = (metadata: { __typename: string }, langui: Langui): string => {
switch (metadata.__typename) { switch (metadata.__typename) {
case "ComponentMetadataAudio": case "ComponentMetadataAudio":
return langui.audio ?? "Audio"; return langui.audio ?? "Audio";
@ -242,8 +242,7 @@ export const prettyDuration = (seconds: number): string => {
export const prettyLanguage = (code: string, languages: Languages): string => { export const prettyLanguage = (code: string, languages: Languages): string => {
let result = code; let result = code;
languages.forEach((language) => { languages.forEach((language) => {
if (language.attributes?.code === code) if (language.attributes?.code === code) result = language.attributes.localized_name;
result = language.attributes.localized_name;
}); });
return result; return result;
}; };
@ -254,8 +253,7 @@ export const prettyURL = (url: string): string => {
}; };
const capitalizeString = (string: string): string => { const capitalizeString = (string: string): string => {
const capitalizeWord = (word: string): string => const capitalizeWord = (word: string): string => word.charAt(0).toUpperCase() + word.substring(1);
word.charAt(0).toUpperCase() + word.substring(1);
let words = string.split(" "); let words = string.split(" ");
words = words.map((word) => capitalizeWord(word)); words = words.map((word) => capitalizeWord(word));
@ -281,3 +279,5 @@ export const slugify = (string: string | undefined): string => {
.trim() .trim()
.replace(/ /gu, "-"); .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 { export enum ImageQuality {
Small = "small", Small = "small",
Medium = "medium", Medium = "medium",
@ -17,7 +19,7 @@ export interface OgImage {
alt: string; alt: string;
} }
export const imageQualityProperties: Record<ImageQuality, ImageProperties> = { const imageQualityProperties: Record<ImageQuality, ImageProperties> = {
small: { maxSize: 512, extension: "webp" }, small: { maxSize: 512, extension: "webp" },
medium: { maxSize: 1024, extension: "webp" }, medium: { maxSize: 1024, extension: "webp" },
large: { maxSize: 2048, 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 => { export const getAssetURL = (url: string, quality: ImageQuality): string => {
const indexEndPath = url.indexOf("/uploads/") + "/uploads/".length; const indexEndPath = url.indexOf("/uploads/") + "/uploads/".length;
const indexStartExtension = url.lastIndexOf("."); const indexStartExtension = url.lastIndexOf(".");
const assetPathWithoutExtension = url.slice( const assetPathWithoutExtension = url.slice(indexEndPath, indexStartExtension);
indexEndPath, return sJoin(
indexStartExtension 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 = ( const getImgSizesByMaxSize = (

View File

@ -1,5 +1,16 @@
export const isUntangibleGroupItem = (metadata: any): boolean => import { isDefined } from "./others";
metadata &&
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.__typename === "ComponentMetadataGroup" &&
(metadata.subtype?.data?.attributes?.slug === "variant-set" || (metadata.subtype?.data?.attributes?.slug === "variant-set" ||
metadata.subtype?.data?.attributes?.slug === "relation-set"); metadata.subtype?.data?.attributes?.slug === "relation-set");

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -4,10 +4,7 @@ import TurndownService from "turndown";
import { AppLayout, AppLayoutRequired } from "components/AppLayout"; import { AppLayout, AppLayoutRequired } from "components/AppLayout";
import { Button } from "components/Inputs/Button"; import { Button } from "components/Inputs/Button";
import { Markdawn, TableOfContents } from "components/Markdown/Markdawn"; import { Markdawn, TableOfContents } from "components/Markdown/Markdawn";
import { import { ContentPanel, ContentPanelWidthSizes } from "components/Panels/ContentPanel";
ContentPanel,
ContentPanelWidthSizes,
} from "components/Panels/ContentPanel";
import { Popup } from "components/Popup"; import { Popup } from "components/Popup";
import { ToolTip } from "components/ToolTip"; import { ToolTip } from "components/ToolTip";
import { Icon } from "components/Ico"; import { Icon } from "components/Ico";
@ -59,19 +56,14 @@ const Editor = (props: Props): JSX.Element => {
); );
const wrap = useCallback( const wrap = useCallback(
( (wrapper: string, properties?: Record<string, string>, addInnerNewLines?: boolean) => {
wrapper: string,
properties?: Record<string, string>,
addInnerNewLines?: boolean
) => {
transformationWrapper((value, selectionStart, selectionEnd) => { transformationWrapper((value, selectionStart, selectionEnd) => {
let prepend = wrapper; let prepend = wrapper;
let append = wrapper; let append = wrapper;
if (properties) { if (properties) {
prepend = `<${wrapper}${Object.entries(properties).map( prepend = `<${wrapper}${Object.entries(properties).map(
([propertyName, propertyValue]) => ([propertyName, propertyValue]) => ` ${propertyName}="${propertyValue}"`
` ${propertyName}="${propertyValue}"`
)}>`; )}>`;
append = `</${wrapper}>`; append = `</${wrapper}>`;
} }
@ -107,17 +99,12 @@ const Editor = (props: Props): JSX.Element => {
); );
const toggleWrap = useCallback( const toggleWrap = useCallback(
( (wrapper: string, properties?: Record<string, string>, addInnerNewLines?: boolean) => {
wrapper: string,
properties?: Record<string, string>,
addInnerNewLines?: boolean
) => {
if (textAreaRef.current) { if (textAreaRef.current) {
const { value, selectionStart, selectionEnd } = textAreaRef.current; const { value, selectionStart, selectionEnd } = textAreaRef.current;
if ( if (
value.slice(selectionStart - wrapper.length, selectionStart) === value.slice(selectionStart - wrapper.length, selectionStart) === wrapper &&
wrapper &&
value.slice(selectionEnd, selectionEnd + wrapper.length) === wrapper value.slice(selectionEnd, selectionEnd + wrapper.length) === wrapper
) { ) {
unwrap(wrapper); unwrap(wrapper);
@ -132,8 +119,7 @@ const Editor = (props: Props): JSX.Element => {
const preline = useCallback( const preline = useCallback(
(prepend: string) => { (prepend: string) => {
transformationWrapper((value, selectionStart) => { transformationWrapper((value, selectionStart) => {
const lastNewLine = const lastNewLine = value.slice(0, selectionStart).lastIndexOf("\n") + 1;
value.slice(0, selectionStart).lastIndexOf("\n") + 1;
let newValue = ""; let newValue = "";
newValue += value.slice(0, lastNewLine); newValue += value.slice(0, lastNewLine);
@ -173,10 +159,7 @@ const Editor = (props: Props): JSX.Element => {
const contentPanel = useMemo( const contentPanel = useMemo(
() => ( () => (
<ContentPanel width={ContentPanelWidthSizes.Full}> <ContentPanel width={ContentPanelWidthSizes.Full}>
<Popup <Popup onClose={() => setConverterOpened(false)} state={converterOpened}>
onClose={() => setConverterOpened(false)}
state={converterOpened}
>
<div className="text-center"> <div className="text-center">
<h2 className="mt-4">Convert HTML to markdown</h2> <h2 className="mt-4">Convert HTML to markdown</h2>
<p> <p>
@ -184,8 +167,7 @@ const Editor = (props: Props): JSX.Element => {
<br /> <br />
The text will immediatly be converted to valid Markdown. The text will immediatly be converted to valid Markdown.
<br /> <br />
You can then copy the converted text and paste it anywhere you You can then copy the converted text and paste it anywhere you want in the editor
want in the editor
</p> </p>
</div> </div>
<textarea <textarea
@ -226,22 +208,15 @@ const Editor = (props: Props): JSX.Element => {
<Button onClick={() => preline("##### ")} text={"H5"} /> <Button onClick={() => preline("##### ")} text={"H5"} />
<Button onClick={() => preline("###### ")} text={"H6"} /> <Button onClick={() => preline("###### ")} text={"H6"} />
</div> </div>
} }>
>
<Button icon={Icon.Title} /> <Button icon={Icon.Title} />
</ToolTip> </ToolTip>
<ToolTip <ToolTip placement="bottom" content={<h3 className="text-lg">Toggle Bold</h3>}>
placement="bottom"
content={<h3 className="text-lg">Toggle Bold</h3>}
>
<Button onClick={() => toggleWrap("**")} icon={Icon.FormatBold} /> <Button onClick={() => toggleWrap("**")} icon={Icon.FormatBold} />
</ToolTip> </ToolTip>
<ToolTip <ToolTip placement="bottom" content={<h3 className="text-lg">Toggle Italic</h3>}>
placement="bottom"
content={<h3 className="text-lg">Toggle Italic</h3>}
>
<Button onClick={() => toggleWrap("_")} icon={Icon.FormatItalic} /> <Button onClick={() => toggleWrap("_")} icon={Icon.FormatItalic} />
</ToolTip> </ToolTip>
@ -251,12 +226,11 @@ const Editor = (props: Props): JSX.Element => {
<> <>
<h3 className="text-lg">Toggle Inline Code</h3> <h3 className="text-lg">Toggle Inline Code</h3>
<p> <p>
Makes the text monospace (like text from a computer terminal). Makes the text monospace (like text from a computer terminal). Usually used for
Usually used for stylistic purposes in transcripts. stylistic purposes in transcripts.
</p> </p>
</> </>
} }>
>
<Button onClick={() => toggleWrap("`")} icon={Icon.Code} /> <Button onClick={() => toggleWrap("`")} icon={Icon.Code} />
</ToolTip> </ToolTip>
@ -267,8 +241,7 @@ const Editor = (props: Props): JSX.Element => {
<h3 className="text-lg">Insert footnote</h3> <h3 className="text-lg">Insert footnote</h3>
<p>When inserted &ldquo;x&rdquo;</p> <p>When inserted &ldquo;x&rdquo;</p>
</> </>
} }>
>
<Button <Button
onClick={() => { onClick={() => {
insert("[^x]"); insert("[^x]");
@ -284,8 +257,8 @@ const Editor = (props: Props): JSX.Element => {
<> <>
<h3 className="text-lg">Transcripts</h3> <h3 className="text-lg">Transcripts</h3>
<p> <p>
Use this to create dialogues and transcripts. Start by adding Use this to create dialogues and transcripts. Start by adding a container, then
a container, then add transcript speech line within. add transcript speech line within.
</p> </p>
<div className="grid gap-2"> <div className="grid gap-2">
<ToolTip <ToolTip
@ -294,12 +267,8 @@ const Editor = (props: Props): JSX.Element => {
<> <>
<h3 className="text-lg">Transcript container</h3> <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>
<ToolTip <ToolTip
placement="right" placement="right"
@ -307,13 +276,11 @@ const Editor = (props: Props): JSX.Element => {
<> <>
<h3 className="text-lg">Transcript speech line</h3> <h3 className="text-lg">Transcript speech line</h3>
<p> <p>
Use to add a dialogue/transcript line. Change the{" "} Use to add a dialogue/transcript line. Change the <kbd>name</kbd> property
<kbd>name</kbd> property to chang the name of the to chang the name of the speaker
speaker
</p> </p>
</> </>
} }>
>
<Button <Button
onClick={() => wrap("Line", { name: "speaker" })} onClick={() => wrap("Line", { name: "speaker" })}
icon={Icon.RecordVoiceOver} icon={Icon.RecordVoiceOver}
@ -321,24 +288,14 @@ const Editor = (props: Props): JSX.Element => {
</ToolTip> </ToolTip>
</div> </div>
</> </>
} }>
>
<Button icon={Icon.RecordVoiceOver} /> <Button icon={Icon.RecordVoiceOver} />
</ToolTip> </ToolTip>
<ToolTip <ToolTip placement="bottom" content={<h3 className="text-lg">Inset box</h3>}>
placement="bottom" <Button onClick={() => wrap("InsetBox", {}, true)} icon={Icon.CheckBoxOutlineBlank} />
content={<h3 className="text-lg">Inset box</h3>}
>
<Button
onClick={() => wrap("InsetBox", {}, true)}
icon={Icon.CheckBoxOutlineBlank}
/>
</ToolTip> </ToolTip>
<ToolTip <ToolTip placement="bottom" content={<h3 className="text-lg">Scene break</h3>}>
placement="bottom"
content={<h3 className="text-lg">Scene break</h3>}
>
<Button onClick={() => insert("\n* * *\n")} icon={Icon.MoreHoriz} /> <Button onClick={() => insert("\n* * *\n")} icon={Icon.MoreHoriz} />
</ToolTip> </ToolTip>
<ToolTip <ToolTip
@ -350,12 +307,9 @@ const Editor = (props: Props): JSX.Element => {
content={ content={
<> <>
<h3 className="text-lg">External Link</h3> <h3 className="text-lg">External Link</h3>
<p className="text-xs"> <p className="text-xs">Provides a link to another webpage / website</p>
Provides a link to another webpage / website
</p>
</> </>
} }>
>
<Button <Button
onClick={() => insert("[Link name](https://domain.com)")} onClick={() => insert("[Link name](https://domain.com)")}
icon={Icon.Link} icon={Icon.Link}
@ -369,12 +323,10 @@ const Editor = (props: Props): JSX.Element => {
<> <>
<h3 className="text-lg">Intralink</h3> <h3 className="text-lg">Intralink</h3>
<p className="text-xs"> <p className="text-xs">
Interlinks are used to add links to a header within the Interlinks are used to add links to a header within the same document
same document
</p> </p>
</> </>
} }>
>
<Button <Button
onClick={() => wrap("IntraLink", {})} onClick={() => wrap("IntraLink", {})}
icon={Icon.Link} icon={Icon.Link}
@ -387,12 +339,11 @@ const Editor = (props: Props): JSX.Element => {
<> <>
<h3 className="text-lg">Intralink (with target)</h3>{" "} <h3 className="text-lg">Intralink (with target)</h3>{" "}
<p className="text-xs"> <p className="text-xs">
Use this one if you want the intralink text to be Use this one if you want the intralink text to be different from the target
different from the target header&rsquo;s name. header&rsquo;s name.
</p> </p>
</> </>
} }>
>
<Button <Button
onClick={() => wrap("IntraLink", { target: "target" })} onClick={() => wrap("IntraLink", { target: "target" })}
icon={Icon.Link} icon={Icon.Link}
@ -400,24 +351,17 @@ const Editor = (props: Props): JSX.Element => {
/> />
</ToolTip> </ToolTip>
</div> </div>
} }>
>
<Button icon={Icon.Link} /> <Button icon={Icon.Link} />
</ToolTip> </ToolTip>
<ToolTip <ToolTip
placement="bottom" placement="bottom"
content={ content={<h3 className="text-lg">Player&rsquo;s name placeholder</h3>}>
<h3 className="text-lg">Player&rsquo;s name placeholder</h3>
}
>
<Button onClick={() => insert("@player")} icon={Icon.Person} /> <Button onClick={() => insert("@player")} icon={Icon.Person} />
</ToolTip> </ToolTip>
<ToolTip <ToolTip placement="bottom" content={<h3 className="text-lg">Open HTML Converter</h3>}>
placement="bottom"
content={<h3 className="text-lg">Open HTML Converter</h3>}
>
<Button <Button
onClick={() => { onClick={() => {
setConverterOpened(true); setConverterOpened(true);
@ -455,16 +399,7 @@ const Editor = (props: Props): JSX.Element => {
</div> </div>
</ContentPanel> </ContentPanel>
), ),
[ [appendDoc, converterOpened, handleInput, insert, markdown, preline, toggleWrap, wrap]
appendDoc,
converterOpened,
handleInput,
insert,
markdown,
preline,
toggleWrap,
wrap,
]
); );
return <AppLayout contentPanel={contentPanel} {...props} />; 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 { AppLayout, AppLayoutRequired } from "components/AppLayout";
import { Button } from "components/Inputs/Button"; import { Button } from "components/Inputs/Button";
import { ButtonGroup } from "components/Inputs/ButtonGroup"; import { ButtonGroup } from "components/Inputs/ButtonGroup";
import { import { ContentPanel, ContentPanelWidthSizes } from "components/Panels/ContentPanel";
ContentPanel,
ContentPanelWidthSizes,
} from "components/Panels/ContentPanel";
import { ToolTip } from "components/ToolTip"; import { ToolTip } from "components/ToolTip";
import { getOpenGraph } from "helpers/openGraph"; import { getOpenGraph } from "helpers/openGraph";
import { getLangui } from "graphql/fetchLocalData"; import { getLangui } from "graphql/fetchLocalData";
@ -30,10 +27,7 @@ const replaceSelection = (
selectionStart: number, selectionStart: number,
selectionEnd: number, selectionEnd: number,
newSelectedText: string 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 => { const swapChar = (char: string, swaps: string[]): string => {
for (let index = 0; index < swaps.length; index++) { for (let index = 0; index < swaps.length; index++) {
@ -60,10 +54,7 @@ const Transcript = (props: Props): JSX.Element => {
const updateLineIndex = useCallback(() => { const updateLineIndex = useCallback(() => {
if (textAreaRef.current) { if (textAreaRef.current) {
const subText = textAreaRef.current.value.substring( const subText = textAreaRef.current.value.substring(0, textAreaRef.current.selectionStart);
0,
textAreaRef.current.selectionStart
);
setLineIndex(subText.split("\n").length - 1); setLineIndex(subText.split("\n").length - 1);
} }
}, []); }, []);
@ -202,10 +193,7 @@ const Transcript = (props: Props): JSX.Element => {
textAreaRef.current.selectionStart, textAreaRef.current.selectionStart,
textAreaRef.current.selectionEnd textAreaRef.current.selectionEnd
); );
const selection = textAreaRef.current.value.substring( const selection = textAreaRef.current.value.substring(selectionStart, selectionEnd);
selectionStart,
selectionEnd
);
if (selection.length === 1) { if (selection.length === 1) {
let newSelection = selection; let newSelection = selection;
@ -301,10 +289,7 @@ const Transcript = (props: Props): JSX.Element => {
textAreaRef.current.selectionStart, textAreaRef.current.selectionStart,
textAreaRef.current.selectionEnd textAreaRef.current.selectionEnd
); );
const selection = textAreaRef.current.value.substring( const selection = textAreaRef.current.value.substring(selectionStart, selectionEnd);
selectionStart,
selectionEnd
);
if (selection.length === 1) { if (selection.length === 1) {
let newSelection = selection; let newSelection = selection;
@ -376,10 +361,7 @@ const Transcript = (props: Props): JSX.Element => {
const contentPanel = useMemo( const contentPanel = useMemo(
() => ( () => (
<ContentPanel <ContentPanel width={ContentPanelWidthSizes.Full} className="overflow-hidden !pr-0 !pt-4">
width={ContentPanelWidthSizes.Full}
className="overflow-hidden !pr-0 !pt-4"
>
<div className="grid grid-flow-col grid-cols-[1fr_5rem]"> <div className="grid grid-flow-col grid-cols-[1fr_5rem]">
<textarea <textarea
ref={textAreaRef} ref={textAreaRef}
@ -387,18 +369,14 @@ const Transcript = (props: Props): JSX.Element => {
onClick={updateLineIndex} onClick={updateLineIndex}
onKeyUp={updateLineIndex} onKeyUp={updateLineIndex}
title="Input textarea" title="Input textarea"
className="whitespace-pre" className="whitespace-pre"></textarea>
></textarea>
<p <p
className="h-[80vh] whitespace-nowrap font-[initial] font-bold className="h-[80vh] whitespace-nowrap font-[initial] font-bold
[writing-mode:vertical-rl] [transform-origin:top_right]" [writing-mode:vertical-rl] [transform-origin:top_right]"
style={{ style={{
transform: `scale(${fontSize}) translateX(${ transform: `scale(${fontSize}) translateX(${fontSize * xOffset}px)`,
fontSize * xOffset }}>
}px)`,
}}
>
{text.split("\n")[lineIndex]} {text.split("\n")[lineIndex]}
</p> </p>
</div> </div>
@ -412,10 +390,7 @@ const Transcript = (props: Props): JSX.Element => {
min="0" min="0"
max="100" max="100"
value={xOffset * 10} value={xOffset * 10}
onChange={(event) => onChange={(event) => setXOffset(parseInt(event.target.value, 10) / 10)}></input>
setXOffset(parseInt(event.target.value, 10) / 10)
}
></input>
</div> </div>
<div className="grid place-items-center"> <div className="grid place-items-center">
@ -428,8 +403,7 @@ const Transcript = (props: Props): JSX.Element => {
value={fontSize * SIZE_MULTIPLIER} value={fontSize * SIZE_MULTIPLIER}
onChange={(event) => onChange={(event) =>
setFontSize(parseInt(event.target.value, 10) / SIZE_MULTIPLIER) setFontSize(parseInt(event.target.value, 10) / SIZE_MULTIPLIER)
} }></input>
></input>
</div> </div>
<ToolTip content="Automatically convert Western punctuations to Japanese ones."> <ToolTip content="Automatically convert Western punctuations to Japanese ones.">
<Button text=". ⟹ 。" onClick={convertPunctuation} /> <Button text=". ⟹ 。" onClick={convertPunctuation} />
@ -526,8 +500,7 @@ const Transcript = (props: Props): JSX.Element => {
]} ]}
/> />
</div> </div>
} }>
>
<Button text={"Quotations"} /> <Button text={"Quotations"} />
</ToolTip> </ToolTip>
<ToolTip <ToolTip
@ -543,8 +516,7 @@ const Transcript = (props: Props): JSX.Element => {
<Button text={""} onClick={() => insert("")} /> <Button text={""} onClick={() => insert("")} />
<Button text={'" "'} onClick={() => insert(" ")} /> <Button text={'" "'} onClick={() => insert(" ")} />
</div> </div>
} }>
>
<Button text="Insert" /> <Button text="Insert" />
</ToolTip> </ToolTip>
</div> </div>
@ -565,13 +537,7 @@ const Transcript = (props: Props): JSX.Element => {
] ]
); );
return ( return <AppLayout contentPanel={contentPanel} {...props} contentPanelScroolbar={false} />;
<AppLayout
contentPanel={contentPanel}
{...props}
contentPanelScroolbar={false}
/>
);
}; };
export default Transcript; export default Transcript;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -4,10 +4,7 @@ import { useBoolean } from "usehooks-ts";
import { AppLayout, AppLayoutRequired } from "components/AppLayout"; import { AppLayout, AppLayoutRequired } from "components/AppLayout";
import { Switch } from "components/Inputs/Switch"; import { Switch } from "components/Inputs/Switch";
import { PanelHeader } from "components/PanelComponents/PanelHeader"; import { PanelHeader } from "components/PanelComponents/PanelHeader";
import { import { ContentPanel, ContentPanelWidthSizes } from "components/Panels/ContentPanel";
ContentPanel,
ContentPanelWidthSizes,
} from "components/Panels/ContentPanel";
import { SubPanel } from "components/Panels/SubPanel"; import { SubPanel } from "components/Panels/SubPanel";
import { GetPostsPreviewQuery } from "graphql/generated"; import { GetPostsPreviewQuery } from "graphql/generated";
import { getReadySdk } from "graphql/sdk"; import { getReadySdk } from "graphql/sdk";
@ -51,9 +48,7 @@ const News = ({ posts, ...otherProps }: Props): JSX.Element => {
const isContentPanelAtLeast4xl = useIsContentPanelAtLeast("4xl"); const isContentPanelAtLeast4xl = useIsContentPanelAtLeast("4xl");
const { langui } = useAppLayout(); const { langui } = useAppLayout();
const hoverable = useDeviceSupportsHover(); const hoverable = useDeviceSupportsHover();
const [searchName, setSearchName] = useState( const [searchName, setSearchName] = useState(DEFAULT_FILTERS_STATE.searchName);
DEFAULT_FILTERS_STATE.searchName
);
const { const {
value: keepInfoVisible, value: keepInfoVisible,
toggle: toggleKeepInfoVisible, toggle: toggleKeepInfoVisible,
@ -63,11 +58,7 @@ const News = ({ posts, ...otherProps }: Props): JSX.Element => {
const subPanel = useMemo( const subPanel = useMemo(
() => ( () => (
<SubPanel> <SubPanel>
<PanelHeader <PanelHeader icon={Icon.Feed} title={langui.news} description={langui.news_description} />
icon={Icon.Feed}
title={langui.news}
description={langui.news_description}
/>
<HorizontalLine /> <HorizontalLine />
@ -91,9 +82,7 @@ const News = ({ posts, ...otherProps }: Props): JSX.Element => {
value={keepInfoVisible} value={keepInfoVisible}
onClick={() => { onClick={() => {
toggleKeepInfoVisible(); toggleKeepInfoVisible();
umami( umami(`[News] Always ${keepInfoVisible ? "hide" : "show"} info`);
`[News] Always ${keepInfoVisible ? "hide" : "show"} info`
);
}} }}
/> />
</WithLabel> </WithLabel>
@ -111,14 +100,7 @@ const News = ({ posts, ...otherProps }: Props): JSX.Element => {
/> />
</SubPanel> </SubPanel>
), ),
[ [hoverable, keepInfoVisible, langui, searchName, setKeepInfoVisible, toggleKeepInfoVisible]
hoverable,
keepInfoVisible,
langui,
searchName,
setKeepInfoVisible,
toggleKeepInfoVisible,
]
); );
const contentPanel = useMemo( const contentPanel = useMemo(
@ -207,6 +189,4 @@ export const getStaticProps: GetStaticProps = async (context) => {
*/ */
const sortPosts = (posts: Props["posts"]): Props["posts"] => const sortPosts = (posts: Props["posts"]): Props["posts"] =>
posts posts.sort((a, b) => compareDate(a.attributes?.date, b.attributes?.date)).reverse();
.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 { HorizontalLine } from "components/HorizontalLine";
import { Img } from "components/Img"; import { Img } from "components/Img";
import { ReturnButton } from "components/PanelComponents/ReturnButton"; import { ReturnButton } from "components/PanelComponents/ReturnButton";
import { import { ContentPanel, ContentPanelWidthSizes } from "components/Panels/ContentPanel";
ContentPanel,
ContentPanelWidthSizes,
} from "components/Panels/ContentPanel";
import { SubPanel } from "components/Panels/SubPanel"; import { SubPanel } from "components/Panels/SubPanel";
import DefinitionCard from "components/Wiki/DefinitionCard"; import DefinitionCard from "components/Wiki/DefinitionCard";
import { getReadySdk } from "graphql/sdk"; import { getReadySdk } from "graphql/sdk";
import { import { filterHasAttributes, isDefined, isDefinedAndNotEmpty } from "helpers/others";
filterHasAttributes, import { WikiPageWithTranslations } from "types/types";
isDefined,
isDefinedAndNotEmpty,
} from "helpers/others";
import { WikiPageWithTranslations } from "helpers/types";
import { useSmartLanguage } from "hooks/useSmartLanguage"; import { useSmartLanguage } from "hooks/useSmartLanguage";
import { prettySlug } from "helpers/formatters"; import { prettySlug, sJoin } from "helpers/formatters";
import { useLightBox } from "hooks/useLightBox"; import { useLightBox } from "hooks/useLightBox";
import { getAssetURL, ImageQuality } from "helpers/img"; import { getAssetURL, ImageQuality } from "helpers/img";
import { getOpenGraph } from "helpers/openGraph"; import { getOpenGraph } from "helpers/openGraph";
import { import { getDefaultPreferredLanguages, staticSmartLanguage } from "helpers/locales";
getDefaultPreferredLanguages,
staticSmartLanguage,
} from "helpers/locales";
import { getDescription } from "helpers/description"; import { getDescription } from "helpers/description";
import { cIf, cJoin } from "helpers/className"; import { cIf, cJoin } from "helpers/className";
import { useIs3ColumnsLayout } from "hooks/useContainerQuery"; import { useIs3ColumnsLayout } from "hooks/useContainerQuery";
@ -44,15 +34,14 @@ interface Props extends AppLayoutRequired {
const WikiPage = ({ page, ...otherProps }: Props): JSX.Element => { const WikiPage = ({ page, ...otherProps }: Props): JSX.Element => {
const { langui } = useAppLayout(); const { langui } = useAppLayout();
const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] = const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] = useSmartLanguage({
useSmartLanguage({ items: page.translations,
items: page.translations, languageExtractor: useCallback(
languageExtractor: useCallback( (item: NonNullable<Props["page"]["translations"][number]>) =>
(item: NonNullable<Props["page"]["translations"][number]>) => item.language?.data?.attributes?.code,
item.language?.data?.attributes?.code, []
[] ),
), });
});
const [openLightBox, LightBox] = useLightBox(); const [openLightBox, LightBox] = useLightBox();
const is3ColumnsLayout = useIs3ColumnsLayout(); const is3ColumnsLayout = useIs3ColumnsLayout();
@ -60,11 +49,7 @@ const WikiPage = ({ page, ...otherProps }: Props): JSX.Element => {
const subPanel = useMemo( const subPanel = useMemo(
() => ( () => (
<SubPanel> <SubPanel>
<ReturnButton <ReturnButton href={`/wiki`} title={langui.wiki} displayOnlyOn={"3ColumnsLayout"} />
href={`/wiki`}
title={langui.wiki}
displayOnlyOn={"3ColumnsLayout"}
/>
</SubPanel> </SubPanel>
), ),
[langui] [langui]
@ -84,14 +69,11 @@ const WikiPage = ({ page, ...otherProps }: Props): JSX.Element => {
<div className="flex flex-wrap place-content-center gap-3"> <div className="flex flex-wrap place-content-center gap-3">
<h1 className="text-center text-3xl">{selectedTranslation?.title}</h1> <h1 className="text-center text-3xl">{selectedTranslation?.title}</h1>
{selectedTranslation?.aliases && {selectedTranslation?.aliases && selectedTranslation.aliases.length > 0 && (
selectedTranslation.aliases.length > 0 && ( <p className="mr-3 text-center text-2xl">
<p className="mr-3 text-center text-2xl"> {`(${selectedTranslation.aliases.map((alias) => alias?.alias).join("・")})`}
{`(${selectedTranslation.aliases </p>
.map((alias) => alias?.alias) )}
.join("・")})`}
</p>
)}
<LanguageSwitcher {...languageSwitcherProps} /> <LanguageSwitcher {...languageSwitcherProps} />
</div> </div>
@ -103,8 +85,7 @@ const WikiPage = ({ page, ...otherProps }: Props): JSX.Element => {
className={cJoin( className={cJoin(
"mb-8 overflow-hidden rounded-lg bg-mid text-center", "mb-8 overflow-hidden rounded-lg bg-mid text-center",
cIf(is3ColumnsLayout, "float-right ml-8 w-[25rem]") cIf(is3ColumnsLayout, "float-right ml-8 w-[25rem]")
)} )}>
>
{page.thumbnail?.data?.attributes && ( {page.thumbnail?.data?.attributes && (
<Img <Img
src={page.thumbnail.data.attributes} src={page.thumbnail.data.attributes}
@ -113,10 +94,7 @@ const WikiPage = ({ page, ...otherProps }: Props): JSX.Element => {
onClick={() => { onClick={() => {
if (page.thumbnail?.data?.attributes?.url) { if (page.thumbnail?.data?.attributes?.url) {
openLightBox([ openLightBox([
getAssetURL( getAssetURL(page.thumbnail.data.attributes.url, ImageQuality.Large),
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"> <div className="my-4 grid gap-4 p-4">
{page.categories?.data && page.categories.data.length > 0 && ( {page.categories?.data && page.categories.data.length > 0 && (
<> <>
<p className="font-headers text-xl font-bold"> <p className="font-headers text-xl font-bold">{langui.categories}</p>
{langui.categories}
</p>
<div className="flex flex-row flex-wrap place-content-center gap-2"> <div className="flex flex-row flex-wrap place-content-center gap-2">
{filterHasAttributes(page.categories.data, [ {filterHasAttributes(page.categories.data, ["attributes"] as const).map(
"attributes", (category) => (
] as const).map((category) => ( <Chip key={category.id} text={category.attributes.name} />
<Chip )
key={category.id} )}
text={category.attributes.name}
/>
))}
</div> </div>
</> </>
)} )}
{page.tags?.data && page.tags.data.length > 0 && ( {page.tags?.data && page.tags.data.length > 0 && (
<> <>
<p className="font-headers text-xl font-bold"> <p className="font-headers text-xl font-bold">{langui.tags}</p>
{langui.tags}
</p>
<div className="flex flex-row flex-wrap place-content-center gap-2"> <div className="flex flex-row flex-wrap place-content-center gap-2">
{filterHasAttributes(page.tags.data, [ {filterHasAttributes(page.tags.data, ["attributes"] as const).map((tag) => (
"attributes",
] as const).map((tag) => (
<Chip <Chip
key={tag.id} key={tag.id}
text={ text={
tag.attributes.titles?.[0]?.title ?? tag.attributes.titles?.[0]?.title ?? prettySlug(tag.attributes.slug)
prettySlug(tag.attributes.slug)
} }
/> />
))} ))}
@ -167,40 +135,41 @@ const WikiPage = ({ page, ...otherProps }: Props): JSX.Element => {
{isDefinedAndNotEmpty(selectedTranslation.summary) && ( {isDefinedAndNotEmpty(selectedTranslation.summary) && (
<div className="mb-12"> <div className="mb-12">
<p className="font-headers text-lg font-bold"> <p className="font-headers text-lg font-bold">{langui.summary}</p>
{langui.summary}
</p>
<p>{selectedTranslation.summary}</p> <p>{selectedTranslation.summary}</p>
</div> </div>
)} )}
{filterHasAttributes(page.definitions, [ {filterHasAttributes(page.definitions, ["translations"] as const).map(
"translations", (definition, index) => (
] as const).map((definition, index) => ( <div key={index} className="mb-12">
<div key={index} className="mb-12"> <DefinitionCard
<DefinitionCard source={{
source={{ name: definition.source?.data?.attributes?.name,
name: definition.source?.data?.attributes?.name, url: definition.source?.data?.attributes?.content?.data?.attributes?.slug
url: definition.source?.data?.attributes?.content?.data ? sJoin(
?.attributes?.slug "/contents/",
? `/contents/${definition.source.data.attributes.content.data.attributes.slug}` definition.source.data.attributes.content.data.attributes.slug
: `/library/${definition.source?.data?.attributes?.ranged_content?.data?.attributes?.library_item?.data?.attributes?.slug}`, )
}} : cJoin(
translations={definition.translations.map( "/library/",
(translation) => ({ definition.source?.data?.attributes?.ranged_content?.data?.attributes
?.library_item?.data?.attributes?.slug
),
}}
translations={definition.translations.map((translation) => ({
language: translation?.language?.data?.attributes?.code, language: translation?.language?.data?.attributes?.code,
definition: translation?.definition, definition: translation?.definition,
status: translation?.status, status: translation?.status,
}) }))}
)} index={index + 1}
index={index + 1} categories={filterHasAttributes(definition.categories?.data, [
categories={filterHasAttributes( "attributes",
definition.categories?.data, ] as const).map((category) => category.attributes.short)}
["attributes"] as const />
).map((category) => category.attributes.short)} </div>
/> )
</div> )}
))}
</div> </div>
</> </>
)} )}
@ -221,13 +190,7 @@ const WikiPage = ({ page, ...otherProps }: Props): JSX.Element => {
] ]
); );
return ( return <AppLayout subPanel={subPanel} contentPanel={contentPanel} {...otherProps} />;
<AppLayout
subPanel={subPanel}
contentPanel={contentPanel}
{...otherProps}
/>
);
}; };
export default WikiPage; export default WikiPage;
@ -240,24 +203,19 @@ export const getStaticProps: GetStaticProps = async (context) => {
const sdk = getReadySdk(); const sdk = getReadySdk();
const langui = getLangui(context.locale); const langui = getLangui(context.locale);
const slug = const slug =
context.params && isDefined(context.params.slug) context.params && isDefined(context.params.slug) ? context.params.slug.toString() : "";
? context.params.slug.toString()
: "";
const page = await sdk.getWikiPage({ const page = await sdk.getWikiPage({
language_code: context.locale ?? "en", language_code: context.locale ?? "en",
slug: slug, slug: slug,
}); });
if (!page.wikiPages?.data[0].attributes?.translations) if (!page.wikiPages?.data[0].attributes?.translations) return { notFound: true };
return { notFound: true };
const { title, description } = (() => { const { title, description } = (() => {
const chipsGroups = { const chipsGroups = {
[langui.tags ?? "Tags"]: filterHasAttributes( [langui.tags ?? "Tags"]: filterHasAttributes(page.wikiPages.data[0].attributes.tags?.data, [
page.wikiPages.data[0].attributes.tags?.data, "attributes",
["attributes"] as const ] as const).map(
).map( (tag) => tag.attributes.titles?.[0]?.title ?? prettySlug(tag.attributes.slug)
(tag) =>
tag.attributes.titles?.[0]?.title ?? prettySlug(tag.attributes.slug)
), ),
[langui.categories ?? "Categories"]: filterHasAttributes( [langui.categories ?? "Categories"]: filterHasAttributes(
page.wikiPages.data[0].attributes.categories?.data, page.wikiPages.data[0].attributes.categories?.data,
@ -269,10 +227,7 @@ export const getStaticProps: GetStaticProps = async (context) => {
const selectedTranslation = staticSmartLanguage({ const selectedTranslation = staticSmartLanguage({
items: page.wikiPages.data[0].attributes.translations, items: page.wikiPages.data[0].attributes.translations,
languageExtractor: (item) => item.language?.data?.attributes?.code, languageExtractor: (item) => item.language?.data?.attributes?.code,
preferredLanguages: getDefaultPreferredLanguages( preferredLanguages: getDefaultPreferredLanguages(context.locale, context.locales),
context.locale,
context.locales
),
}); });
if (selectedTranslation) { if (selectedTranslation) {
return { return {
@ -288,8 +243,7 @@ export const getStaticProps: GetStaticProps = async (context) => {
}; };
})(); })();
const thumbnail = const thumbnail = page.wikiPages.data[0].attributes.thumbnail?.data?.attributes;
page.wikiPages.data[0].attributes.thumbnail?.data?.attributes;
const props: Props = { const props: Props = {
page: page.wikiPages.data[0].attributes as WikiPageWithTranslations, page: page.wikiPages.data[0].attributes as WikiPageWithTranslations,
@ -306,16 +260,14 @@ export const getStaticPaths: GetStaticPaths = async (context) => {
const sdk = getReadySdk(); const sdk = getReadySdk();
const contents = await sdk.getWikiPagesSlugs(); const contents = await sdk.getWikiPagesSlugs();
const paths: GetStaticPathsResult["paths"] = []; const paths: GetStaticPathsResult["paths"] = [];
filterHasAttributes(contents.wikiPages?.data, ["attributes"] as const).map( filterHasAttributes(contents.wikiPages?.data, ["attributes"] as const).map((wikiPage) => {
(wikiPage) => { context.locales?.map((local) =>
context.locales?.map((local) => paths.push({
paths.push({ params: { slug: wikiPage.attributes.slug },
params: { slug: wikiPage.attributes.slug }, locale: local,
locale: local, })
}) );
); });
}
);
return { return {
paths, paths,
fallback: "blocking", fallback: "blocking",

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