Continue to improve code with hooks
This commit is contained in:
parent
d0b91f9db6
commit
2443dee83f
|
@ -18,8 +18,8 @@
|
|||
"meilisearch": "^0.25.1",
|
||||
"next": "^12.1.6",
|
||||
"nodemailer": "^6.7.5",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react": "18.1.0",
|
||||
"react-dom": "18.1.0",
|
||||
"react-hot-keys": "^2.7.2",
|
||||
"react-swipeable": "^7.0.0",
|
||||
"turndown": "^7.1.1"
|
||||
|
@ -29,22 +29,23 @@
|
|||
"@graphql-codegen/typescript": "2.5.1",
|
||||
"@graphql-codegen/typescript-graphql-request": "^4.4.10",
|
||||
"@graphql-codegen/typescript-operations": "^2.4.2",
|
||||
"@types/node": "18.0.0",
|
||||
"@types/node": "17.0.41",
|
||||
"@types/nodemailer": "^6.4.4",
|
||||
"@types/react": "18.0.14",
|
||||
"@types/react": "18.0.12",
|
||||
"@types/react-dom": "^18.0.5",
|
||||
"@types/turndown": "^5.0.1",
|
||||
"@typescript-eslint/eslint-plugin": "^5.28.0",
|
||||
"@typescript-eslint/parser": "^5.28.0",
|
||||
"eslint": "^8.18.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.27.1",
|
||||
"@typescript-eslint/parser": "^5.27.1",
|
||||
"eslint": "^8.17.0",
|
||||
"eslint-config-next": "12.1.6",
|
||||
"graphql": "^16.5.0",
|
||||
"next-sitemap": "^3.0.5",
|
||||
"prettier": "^2.7.1",
|
||||
"prettier-plugin-organize-imports": "^3.0.0",
|
||||
"prettier": "^2.6.2",
|
||||
"prettier-plugin-organize-imports": "^2.3.4",
|
||||
"prettier-plugin-tailwindcss": "^0.1.11",
|
||||
"tailwindcss": "^3.1.3",
|
||||
"typescript": "^4.7.4"
|
||||
"tailwindcss": "^3.1.2",
|
||||
"ts-unused-exports": "^8.0.0",
|
||||
"typescript": "^4.7.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@ampproject/remapping": {
|
||||
|
@ -2604,9 +2605,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "18.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.0.0.tgz",
|
||||
"integrity": "sha512-cHlGmko4gWLVI27cGJntjs/Sj8th9aYwplmZFwmmgYQQvL5NUsgVJG7OddLvNfLqYS31KFN0s3qlaD9qCaxACA==",
|
||||
"version": "17.0.41",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.41.tgz",
|
||||
"integrity": "sha512-xA6drNNeqb5YyV5fO3OAEsnXLfO7uF0whiOfPTz5AeDo8KeZFmODKnvwPymMNO8qE/an8pVY/O50tig2SQCrGw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/nodemailer": {
|
||||
|
@ -2631,9 +2632,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"node_modules/@types/react": {
|
||||
"version": "18.0.14",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.14.tgz",
|
||||
"integrity": "sha512-x4gGuASSiWmo0xjDLpm5mPb52syZHJx02VKbqUKdLmKtAwIh63XClGsiTI1K6DO5q7ox4xAsQrU+Gl3+gGXF9Q==",
|
||||
"version": "18.0.12",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.12.tgz",
|
||||
"integrity": "sha512-duF1OTASSBQtcigUvhuiTB1Ya3OvSy+xORCiEf20H0P0lzx+/KeVsA99U5UjLXSbyo1DRJDlLKqTeM1ngosqtg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/prop-types": "*",
|
||||
|
@ -7587,9 +7588,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/prettier-plugin-organize-imports": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-3.0.0.tgz",
|
||||
"integrity": "sha512-juSCJs5TMOqGGPaN/A/1xzWFzRPH2LG1LPLCr64dzKaVnPafJdtgDNmDVlU+8A4LbQzVJg0DTvgA8swBuIUhlg==",
|
||||
"version": "2.3.4",
|
||||
"resolved": "https://registry.npmjs.org/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-2.3.4.tgz",
|
||||
"integrity": "sha512-R8o23sf5iVL/U71h9SFUdhdOEPsi3nm42FD/oDYIZ2PQa4TNWWuWecxln6jlIQzpZTDMUeO1NicJP6lLn2TtRw==",
|
||||
"dev": true,
|
||||
"peerDependencies": {
|
||||
"prettier": ">=2.0",
|
||||
|
@ -7703,9 +7704,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/react": {
|
||||
"version": "18.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
|
||||
"integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==",
|
||||
"version": "18.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-18.1.0.tgz",
|
||||
"integrity": "sha512-4oL8ivCz5ZEPyclFQXaNksK3adutVS8l2xzZU0cqEFrE9Sb7fC0EFK5uEk74wIreL1DERyjvsU915j1pcT2uEQ==",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0"
|
||||
},
|
||||
|
@ -7714,15 +7715,15 @@
|
|||
}
|
||||
},
|
||||
"node_modules/react-dom": {
|
||||
"version": "18.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
|
||||
"integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==",
|
||||
"version": "18.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.1.0.tgz",
|
||||
"integrity": "sha512-fU1Txz7Budmvamp7bshe4Zi32d0ll7ect+ccxNu9FlObT605GOEB8BfO4tmRJ39R5Zj831VCpvQ05QPBW5yb+w==",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"scheduler": "^0.23.0"
|
||||
"scheduler": "^0.22.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.2.0"
|
||||
"react": "^18.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-hot-keys": {
|
||||
|
@ -8045,9 +8046,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"node_modules/scheduler": {
|
||||
"version": "0.23.0",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz",
|
||||
"integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==",
|
||||
"version": "0.22.0",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.22.0.tgz",
|
||||
"integrity": "sha512-6QAm1BgQI88NPYymgGQLCZgvep4FyePDWFpXVK+zNSUgHwlqpJy8VEh8Et0KxTACS4VWwMousBElAZOH9nkkoQ==",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0"
|
||||
}
|
||||
|
@ -8525,6 +8526,30 @@
|
|||
"integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/ts-unused-exports": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ts-unused-exports/-/ts-unused-exports-8.0.0.tgz",
|
||||
"integrity": "sha512-gylHFyJqC80PSb4zy35KTckykEW1vmKjnOHjBeX9iKBo4b/SzqQIcXXbYSuif4YMgNm6ewFF62VM1C9z0bGZPw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"chalk": "^4.0.0",
|
||||
"tsconfig-paths": "^3.9.0"
|
||||
},
|
||||
"bin": {
|
||||
"ts-unused-exports": "bin/ts-unused-exports"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/pzavolinsky/ts-unused-exports?sponsor=1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">=3.8.3"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"typescript": {
|
||||
"optional": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/tsconfig-paths": {
|
||||
"version": "3.14.1",
|
||||
"resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz",
|
||||
|
@ -11003,9 +11028,9 @@
|
|||
}
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "18.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.0.0.tgz",
|
||||
"integrity": "sha512-cHlGmko4gWLVI27cGJntjs/Sj8th9aYwplmZFwmmgYQQvL5NUsgVJG7OddLvNfLqYS31KFN0s3qlaD9qCaxACA==",
|
||||
"version": "17.0.41",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.41.tgz",
|
||||
"integrity": "sha512-xA6drNNeqb5YyV5fO3OAEsnXLfO7uF0whiOfPTz5AeDo8KeZFmODKnvwPymMNO8qE/an8pVY/O50tig2SQCrGw==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/nodemailer": {
|
||||
|
@ -11030,9 +11055,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"@types/react": {
|
||||
"version": "18.0.14",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.14.tgz",
|
||||
"integrity": "sha512-x4gGuASSiWmo0xjDLpm5mPb52syZHJx02VKbqUKdLmKtAwIh63XClGsiTI1K6DO5q7ox4xAsQrU+Gl3+gGXF9Q==",
|
||||
"version": "18.0.12",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.12.tgz",
|
||||
"integrity": "sha512-duF1OTASSBQtcigUvhuiTB1Ya3OvSy+xORCiEf20H0P0lzx+/KeVsA99U5UjLXSbyo1DRJDlLKqTeM1ngosqtg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/prop-types": "*",
|
||||
|
@ -14738,9 +14763,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"prettier-plugin-organize-imports": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-3.0.0.tgz",
|
||||
"integrity": "sha512-juSCJs5TMOqGGPaN/A/1xzWFzRPH2LG1LPLCr64dzKaVnPafJdtgDNmDVlU+8A4LbQzVJg0DTvgA8swBuIUhlg==",
|
||||
"version": "2.3.4",
|
||||
"resolved": "https://registry.npmjs.org/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-2.3.4.tgz",
|
||||
"integrity": "sha512-R8o23sf5iVL/U71h9SFUdhdOEPsi3nm42FD/oDYIZ2PQa4TNWWuWecxln6jlIQzpZTDMUeO1NicJP6lLn2TtRw==",
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
},
|
||||
|
@ -14819,20 +14844,20 @@
|
|||
}
|
||||
},
|
||||
"react": {
|
||||
"version": "18.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
|
||||
"integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==",
|
||||
"version": "18.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-18.1.0.tgz",
|
||||
"integrity": "sha512-4oL8ivCz5ZEPyclFQXaNksK3adutVS8l2xzZU0cqEFrE9Sb7fC0EFK5uEk74wIreL1DERyjvsU915j1pcT2uEQ==",
|
||||
"requires": {
|
||||
"loose-envify": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"react-dom": {
|
||||
"version": "18.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
|
||||
"integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==",
|
||||
"version": "18.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.1.0.tgz",
|
||||
"integrity": "sha512-fU1Txz7Budmvamp7bshe4Zi32d0ll7ect+ccxNu9FlObT605GOEB8BfO4tmRJ39R5Zj831VCpvQ05QPBW5yb+w==",
|
||||
"requires": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"scheduler": "^0.23.0"
|
||||
"scheduler": "^0.22.0"
|
||||
}
|
||||
},
|
||||
"react-hot-keys": {
|
||||
|
@ -15059,9 +15084,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"scheduler": {
|
||||
"version": "0.23.0",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz",
|
||||
"integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==",
|
||||
"version": "0.22.0",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.22.0.tgz",
|
||||
"integrity": "sha512-6QAm1BgQI88NPYymgGQLCZgvep4FyePDWFpXVK+zNSUgHwlqpJy8VEh8Et0KxTACS4VWwMousBElAZOH9nkkoQ==",
|
||||
"requires": {
|
||||
"loose-envify": "^1.1.0"
|
||||
}
|
||||
|
@ -15428,6 +15453,16 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"ts-unused-exports": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ts-unused-exports/-/ts-unused-exports-8.0.0.tgz",
|
||||
"integrity": "sha512-gylHFyJqC80PSb4zy35KTckykEW1vmKjnOHjBeX9iKBo4b/SzqQIcXXbYSuif4YMgNm6ewFF62VM1C9z0bGZPw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"chalk": "^4.0.0",
|
||||
"tsconfig-paths": "^3.9.0"
|
||||
}
|
||||
},
|
||||
"tsconfig-paths": {
|
||||
"version": "3.14.1",
|
||||
"resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz",
|
||||
|
|
25
package.json
25
package.json
|
@ -3,6 +3,8 @@
|
|||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev -p 12499",
|
||||
"precommit": "npm run prettier && npm run unused-exports && npm run lint && npm run tsc && npm run generate && echo ALL PRECOMMIT CHECKS PASSED SUCCESSFULLY, LET\\'S FUCKING GO!",
|
||||
"unused-exports": "ts-unused-exports ./tsconfig.json --excludePathsFromReport=src/pages --ignoreFiles=generated",
|
||||
"prebuild": "npm run generate",
|
||||
"build": "next build",
|
||||
"postbuild": "next-sitemap --config next-sitemap.config.js",
|
||||
|
@ -25,8 +27,8 @@
|
|||
"meilisearch": "^0.25.1",
|
||||
"next": "^12.1.6",
|
||||
"nodemailer": "^6.7.5",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react": "18.1.0",
|
||||
"react-dom": "18.1.0",
|
||||
"react-hot-keys": "^2.7.2",
|
||||
"react-swipeable": "^7.0.0",
|
||||
"turndown": "^7.1.1"
|
||||
|
@ -36,21 +38,22 @@
|
|||
"@graphql-codegen/typescript": "2.5.1",
|
||||
"@graphql-codegen/typescript-graphql-request": "^4.4.10",
|
||||
"@graphql-codegen/typescript-operations": "^2.4.2",
|
||||
"@types/node": "18.0.0",
|
||||
"@types/node": "17.0.41",
|
||||
"@types/nodemailer": "^6.4.4",
|
||||
"@types/react": "18.0.14",
|
||||
"@types/react": "18.0.12",
|
||||
"@types/react-dom": "^18.0.5",
|
||||
"@types/turndown": "^5.0.1",
|
||||
"@typescript-eslint/eslint-plugin": "^5.28.0",
|
||||
"@typescript-eslint/parser": "^5.28.0",
|
||||
"eslint": "^8.18.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.27.1",
|
||||
"@typescript-eslint/parser": "^5.27.1",
|
||||
"eslint": "^8.17.0",
|
||||
"eslint-config-next": "12.1.6",
|
||||
"graphql": "^16.5.0",
|
||||
"next-sitemap": "^3.0.5",
|
||||
"prettier": "^2.7.1",
|
||||
"prettier-plugin-organize-imports": "^3.0.0",
|
||||
"prettier": "^2.6.2",
|
||||
"prettier-plugin-organize-imports": "^2.3.4",
|
||||
"prettier-plugin-tailwindcss": "^0.1.11",
|
||||
"tailwindcss": "^3.1.3",
|
||||
"typescript": "^4.7.4"
|
||||
"tailwindcss": "^3.1.2",
|
||||
"ts-unused-exports": "^8.0.0",
|
||||
"typescript": "^4.7.3"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,8 +12,6 @@ import {
|
|||
isUndefined,
|
||||
iterateMap,
|
||||
} from "helpers/others";
|
||||
// import { getClient, Indexes, search, SearchResult } from "helpers/search";
|
||||
|
||||
import { useMediaMobile } from "hooks/useMediaQuery";
|
||||
import { AnchorIds } from "hooks/useScrollTopOnChange";
|
||||
import Head from "next/head";
|
||||
|
@ -415,31 +413,33 @@ export function AppLayout(props: Props): JSX.Element {
|
|||
<div className="grid place-items-center gap-8 text-center desktop:grid-cols-2">
|
||||
<div>
|
||||
<h3 className="text-xl">{langui.theme}</h3>
|
||||
<ButtonGroup>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setDarkMode(false);
|
||||
setSelectedThemeMode(true);
|
||||
}}
|
||||
active={selectedThemeMode === true && darkMode === false}
|
||||
text={langui.light}
|
||||
/>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setSelectedThemeMode(false);
|
||||
}}
|
||||
active={selectedThemeMode === false}
|
||||
text={langui.auto}
|
||||
/>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setDarkMode(true);
|
||||
setSelectedThemeMode(true);
|
||||
}}
|
||||
active={selectedThemeMode === true && darkMode === true}
|
||||
text={langui.dark}
|
||||
/>
|
||||
</ButtonGroup>
|
||||
<ButtonGroup
|
||||
buttonsProps={[
|
||||
{
|
||||
onClick: () => {
|
||||
setDarkMode(false);
|
||||
setSelectedThemeMode(true);
|
||||
},
|
||||
active: selectedThemeMode === true && darkMode === false,
|
||||
text: langui.light,
|
||||
},
|
||||
{
|
||||
onClick: () => {
|
||||
setSelectedThemeMode(false);
|
||||
},
|
||||
active: selectedThemeMode === false,
|
||||
text: langui.auto,
|
||||
},
|
||||
{
|
||||
onClick: () => {
|
||||
setDarkMode(true);
|
||||
setSelectedThemeMode(true);
|
||||
},
|
||||
active: selectedThemeMode === true && darkMode === true,
|
||||
text: langui.dark,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
|
@ -456,22 +456,27 @@ export function AppLayout(props: Props): JSX.Element {
|
|||
|
||||
<div>
|
||||
<h3 className="text-xl">{langui.font_size}</h3>
|
||||
<ButtonGroup>
|
||||
<Button
|
||||
onClick={() => setFontSize((fontSize ?? 1) / 1.05)}
|
||||
icon={Icon.TextDecrease}
|
||||
/>
|
||||
<Button
|
||||
onClick={() => setFontSize(1)}
|
||||
text={`${((fontSize ?? 1) * 100).toLocaleString(undefined, {
|
||||
maximumFractionDigits: 0,
|
||||
})}%`}
|
||||
/>
|
||||
<Button
|
||||
onClick={() => setFontSize((fontSize ?? 1) * 1.05)}
|
||||
icon={Icon.TextIncrease}
|
||||
/>
|
||||
</ButtonGroup>
|
||||
<ButtonGroup
|
||||
buttonsProps={[
|
||||
{
|
||||
onClick: () => setFontSize((fontSize ?? 1) / 1.05),
|
||||
icon: Icon.TextDecrease,
|
||||
},
|
||||
{
|
||||
onClick: () => setFontSize(1),
|
||||
text: `${((fontSize ?? 1) * 100).toLocaleString(
|
||||
undefined,
|
||||
{
|
||||
maximumFractionDigits: 0,
|
||||
}
|
||||
)}%`,
|
||||
},
|
||||
{
|
||||
onClick: () => setFontSize((fontSize ?? 1) * 1.05),
|
||||
icon: Icon.TextIncrease,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
|
@ -504,61 +509,7 @@ export function AppLayout(props: Props): JSX.Element {
|
|||
</div>
|
||||
</div>
|
||||
</Popup>
|
||||
|
||||
{/* <Popup
|
||||
state={searchPanelOpen}
|
||||
setState={setSearchPanelOpen}
|
||||
>
|
||||
<div className="grid place-items-center gap-2">
|
||||
TODO: add to langui
|
||||
<h2 className="text-2xl">{"Search"}</h2>
|
||||
<TextInput
|
||||
className="mb-6 w-full"
|
||||
placeholder={"Search query..."}
|
||||
state={searchQuery}
|
||||
setState={setSearchQuery}
|
||||
/>
|
||||
</div>
|
||||
TODO: add to langui
|
||||
<div className="grid gap-4">
|
||||
<p className="font-headers text-xl">In news:</p>
|
||||
<div
|
||||
className="grid grid-cols-2 items-end gap-8
|
||||
desktop:grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))] mobile:gap-4"
|
||||
>
|
||||
{searchResult?.hits.map((hit) => (
|
||||
<PreviewCard
|
||||
key={hit.id}
|
||||
href={hit.href}
|
||||
title={hit.title}
|
||||
thumbnailAspectRatio={"3/2"}
|
||||
thumbnail={hit.thumbnail}
|
||||
keepInfoVisible
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</Popup> */}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
* const [searchQuery, setSearchQuery] = useState("");
|
||||
* const [searchResult, setSearchResult] = useState<SearchResult>();
|
||||
*/
|
||||
|
||||
/*
|
||||
* const client = getClient();
|
||||
* useEffect(() => {
|
||||
* if (searchQuery.length > 1) {
|
||||
* search(client, Indexes.Post, searchQuery).then((result) => {
|
||||
* setSearchResult(result);
|
||||
* });
|
||||
* } else {
|
||||
* setSearchResult(undefined);
|
||||
* }
|
||||
* // eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
* }, [searchQuery]);
|
||||
*/
|
||||
|
|
|
@ -58,7 +58,7 @@ export function Button(props: Props): JSX.Element {
|
|||
id={id}
|
||||
onClick={onClick}
|
||||
className={cJoin(
|
||||
`component-button group grid select-none grid-flow-col place-content-center
|
||||
`group grid select-none grid-flow-col place-content-center
|
||||
place-items-center gap-2 rounded-full border-[1px] border-dark py-3 px-4 leading-none
|
||||
text-dark transition-all`,
|
||||
cIf(
|
||||
|
|
|
@ -1,39 +1,53 @@
|
|||
import { ToolTip } from "components/ToolTip";
|
||||
import { cJoin } from "helpers/className";
|
||||
|
||||
import { useLayoutEffect, useRef } from "react";
|
||||
import { ConditionalWrapper, Wrapper } from "helpers/component";
|
||||
import { isDefinedAndNotEmpty } from "helpers/others";
|
||||
import { Button } from "./Button";
|
||||
|
||||
interface Props {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
buttonsProps: (Parameters<typeof Button>[0] & {
|
||||
tooltip?: string | null | undefined;
|
||||
})[];
|
||||
}
|
||||
|
||||
export function ButtonGroup(props: Props): JSX.Element {
|
||||
const { children, className } = props;
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (ref.current) {
|
||||
const buttons = ref.current.querySelectorAll(".component-button");
|
||||
buttons.forEach((button, index) => {
|
||||
button.classList.remove("rounded-full");
|
||||
button.classList.remove("border-[1px]");
|
||||
if (index === 0) {
|
||||
button.classList.add("rounded-l-full");
|
||||
button.classList.add("border-l-[1px]");
|
||||
} else if (index === buttons.length - 1) {
|
||||
button.classList.add("rounded-r-full");
|
||||
button.classList.add("border-r-[1px]");
|
||||
} else {
|
||||
button.classList.add("rounded-none");
|
||||
}
|
||||
button.classList.add("border-y-[1px]");
|
||||
});
|
||||
}
|
||||
}, [children]);
|
||||
const { buttonsProps, className } = props;
|
||||
|
||||
return (
|
||||
<div ref={ref} className={cJoin("grid grid-flow-col", className)}>
|
||||
{children}
|
||||
<div className={cJoin("grid grid-flow-col", className)}>
|
||||
{buttonsProps.map((buttonProps, index) => (
|
||||
<ConditionalWrapper
|
||||
key={index}
|
||||
isWrapping={isDefinedAndNotEmpty(buttonProps.tooltip)}
|
||||
wrapper={ToolTipWrapper}
|
||||
wrapperProps={{ text: buttonProps.tooltip ?? "" }}
|
||||
>
|
||||
<Button
|
||||
{...buttonProps}
|
||||
className={
|
||||
index === 0
|
||||
? "rounded-r-none border-r-0"
|
||||
: index === buttonsProps.length - 1
|
||||
? "rounded-l-none"
|
||||
: "rounded-none border-r-0"
|
||||
}
|
||||
/>
|
||||
</ConditionalWrapper>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface ToolTipWrapperProps {
|
||||
text: string;
|
||||
}
|
||||
|
||||
function ToolTipWrapper(props: ToolTipWrapperProps & Wrapper) {
|
||||
const { text, children } = props;
|
||||
return (
|
||||
<ToolTip content={text}>
|
||||
<>{children}</>
|
||||
</ToolTip>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,119 +0,0 @@
|
|||
import { Chip } from "components/Chip";
|
||||
import { Ico, Icon } from "components/Ico";
|
||||
import { Button } from "components/Inputs/Button";
|
||||
import { GetLibraryItemQuery } from "graphql/generated";
|
||||
import { AppStaticProps } from "graphql/getAppStaticProps";
|
||||
import { cIf, cJoin } from "helpers/className";
|
||||
import { prettyinlineTitle, prettySlug } from "helpers/formatters";
|
||||
import { filterHasAttributes } from "helpers/others";
|
||||
|
||||
import { useToggle } from "hooks/useToggle";
|
||||
import { useState } from "react";
|
||||
|
||||
interface Props {
|
||||
content: NonNullable<
|
||||
NonNullable<
|
||||
NonNullable<
|
||||
GetLibraryItemQuery["libraryItems"]
|
||||
>["data"][number]["attributes"]
|
||||
>["contents"]
|
||||
>["data"][number];
|
||||
parentSlug: string;
|
||||
langui: AppStaticProps["langui"];
|
||||
}
|
||||
|
||||
export function ContentLine(props: Props): JSX.Element {
|
||||
const { content, langui, parentSlug } = props;
|
||||
|
||||
const [opened, setOpened] = useState(false);
|
||||
const toggleOpened = useToggle(setOpened);
|
||||
|
||||
if (content.attributes) {
|
||||
return (
|
||||
<div
|
||||
className={cJoin(
|
||||
"grid gap-2 rounded-lg px-4",
|
||||
cIf(opened, "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 thin:grid-cols-[auto_auto_1fr_auto]"
|
||||
>
|
||||
<a>
|
||||
<h3 className="cursor-pointer" onClick={toggleOpened}>
|
||||
{content.attributes.content?.data?.attributes?.translations?.[0]
|
||||
? prettyinlineTitle(
|
||||
content.attributes.content.data.attributes.translations[0]
|
||||
?.pre_title,
|
||||
content.attributes.content.data.attributes.translations[0]
|
||||
?.title,
|
||||
content.attributes.content.data.attributes.translations[0]
|
||||
?.subtitle
|
||||
)
|
||||
: prettySlug(content.attributes.slug, props.parentSlug)}
|
||||
</h3>
|
||||
</a>
|
||||
<div className="flex flex-row flex-wrap gap-1">
|
||||
{filterHasAttributes(
|
||||
content.attributes.content?.data?.attributes?.categories?.data
|
||||
).map((category) => (
|
||||
<Chip key={category.id}>{category.attributes.short}</Chip>
|
||||
))}
|
||||
</div>
|
||||
<p className="h-4 w-full border-b-2 border-dotted border-black opacity-30"></p>
|
||||
<p>
|
||||
{content.attributes.range[0]?.__typename ===
|
||||
"ComponentRangePageRange"
|
||||
? content.attributes.range[0].starting_page
|
||||
: ""}
|
||||
</p>
|
||||
{content.attributes.content?.data?.attributes?.type?.data
|
||||
?.attributes && (
|
||||
<Chip className="justify-self-end thin:hidden">
|
||||
{content.attributes.content.data.attributes.type.data.attributes
|
||||
.titles &&
|
||||
content.attributes.content.data.attributes.type.data.attributes
|
||||
.titles.length > 0
|
||||
? content.attributes.content.data.attributes.type.data
|
||||
.attributes.titles[0]?.title
|
||||
: prettySlug(
|
||||
content.attributes.content.data.attributes.type.data
|
||||
.attributes.slug
|
||||
)}
|
||||
</Chip>
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
className={`grid-flow-col place-content-start place-items-center gap-2 ${
|
||||
opened ? "grid" : "hidden"
|
||||
}`}
|
||||
>
|
||||
<Ico icon={Icon.SubdirectoryArrowRight} className="text-dark" />
|
||||
|
||||
{content.attributes.scan_set &&
|
||||
content.attributes.scan_set.length > 0 && (
|
||||
<Button
|
||||
href={`/library/${parentSlug}/scans#${content.attributes.slug}`}
|
||||
text={langui.view_scans}
|
||||
/>
|
||||
)}
|
||||
|
||||
{content.attributes.content?.data && (
|
||||
<Button
|
||||
href={`/contents/${content.attributes.content.data.attributes?.slug}`}
|
||||
text={langui.open_content}
|
||||
/>
|
||||
)}
|
||||
|
||||
{content.attributes.scan_set &&
|
||||
content.attributes.scan_set.length === 0 &&
|
||||
!content.attributes.content?.data
|
||||
? "The content is not available"
|
||||
: ""}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return <></>;
|
||||
}
|
|
@ -15,7 +15,7 @@ import {
|
|||
} from "helpers/others";
|
||||
|
||||
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
||||
import { Fragment, useMemo } from "react";
|
||||
import { Fragment, useCallback, useMemo } from "react";
|
||||
|
||||
interface Props {
|
||||
openLightBox: (images: string[], index?: number) => void;
|
||||
|
@ -49,42 +49,47 @@ export function ScanSet(props: Props): JSX.Element {
|
|||
const { openLightBox, scanSet, slug, title, languages, langui, content } =
|
||||
props;
|
||||
|
||||
const [selectedScan, LanguageSwitcher] = useSmartLanguage({
|
||||
items: scanSet,
|
||||
languages: languages,
|
||||
languageExtractor: (item) => item.language?.data?.attributes?.code,
|
||||
transform: (item) => {
|
||||
item.pages?.data.sort((a, b) => {
|
||||
if (
|
||||
a.attributes &&
|
||||
b.attributes &&
|
||||
isDefinedAndNotEmpty(a.attributes.url) &&
|
||||
isDefinedAndNotEmpty(b.attributes.url)
|
||||
) {
|
||||
let aName = getAssetFilename(a.attributes.url);
|
||||
let bName = getAssetFilename(b.attributes.url);
|
||||
const [selectedScan, LanguageSwitcher, languageSwitcherProps] =
|
||||
useSmartLanguage({
|
||||
items: scanSet,
|
||||
languages: languages,
|
||||
languageExtractor: useCallback(
|
||||
(item: NonNullable<Props["scanSet"][number]>) =>
|
||||
item.language?.data?.attributes?.code,
|
||||
[]
|
||||
),
|
||||
transform: useCallback((item: NonNullable<Props["scanSet"][number]>) => {
|
||||
item.pages?.data.sort((a, b) => {
|
||||
if (
|
||||
a.attributes &&
|
||||
b.attributes &&
|
||||
isDefinedAndNotEmpty(a.attributes.url) &&
|
||||
isDefinedAndNotEmpty(b.attributes.url)
|
||||
) {
|
||||
let aName = getAssetFilename(a.attributes.url);
|
||||
let bName = getAssetFilename(b.attributes.url);
|
||||
|
||||
/*
|
||||
* If the number is a succession of 0s, make the number
|
||||
* incrementally smaller than 0 (i.e: 00 becomes -1)
|
||||
*/
|
||||
if (aName.replaceAll("0", "").length === 0) {
|
||||
aName = (1 - aName.length).toString(10);
|
||||
}
|
||||
if (bName.replaceAll("0", "").length === 0) {
|
||||
bName = (1 - bName.length).toString(10);
|
||||
}
|
||||
/*
|
||||
* If the number is a succession of 0s, make the number
|
||||
* incrementally smaller than 0 (i.e: 00 becomes -1)
|
||||
*/
|
||||
if (aName.replaceAll("0", "").length === 0) {
|
||||
aName = (1 - aName.length).toString(10);
|
||||
}
|
||||
if (bName.replaceAll("0", "").length === 0) {
|
||||
bName = (1 - bName.length).toString(10);
|
||||
}
|
||||
|
||||
if (isInteger(aName) && isInteger(bName)) {
|
||||
return parseInt(aName, 10) - parseInt(bName, 10);
|
||||
if (isInteger(aName) && isInteger(bName)) {
|
||||
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 item;
|
||||
},
|
||||
});
|
||||
return 0;
|
||||
});
|
||||
return item;
|
||||
}, []),
|
||||
});
|
||||
|
||||
const pages = useMemo(
|
||||
() => selectedScan && filterHasAttributes(selectedScan.pages?.data),
|
||||
|
@ -120,7 +125,7 @@ export function ScanSet(props: Props): JSX.Element {
|
|||
/>
|
||||
)}
|
||||
|
||||
<LanguageSwitcher />
|
||||
<LanguageSwitcher {...languageSwitcherProps} />
|
||||
|
||||
<div className="grid place-content-center place-items-center">
|
||||
<p className="font-headers">{langui.status}:</p>
|
||||
|
|
|
@ -11,7 +11,7 @@ import { getAssetURL, ImageQuality } from "helpers/img";
|
|||
import { filterHasAttributes, getStatusDescription } from "helpers/others";
|
||||
|
||||
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
||||
import { Fragment, useMemo } from "react";
|
||||
import { Fragment, useCallback, useMemo } from "react";
|
||||
|
||||
interface Props {
|
||||
openLightBox: (images: string[], index?: number) => void;
|
||||
|
@ -29,11 +29,16 @@ interface Props {
|
|||
export function ScanSetCover(props: Props): JSX.Element {
|
||||
const { openLightBox, images, languages, langui } = props;
|
||||
|
||||
const [selectedScan, LanguageSwitcher] = useSmartLanguage({
|
||||
items: images,
|
||||
languages: languages,
|
||||
languageExtractor: (item) => item.language?.data?.attributes?.code,
|
||||
});
|
||||
const [selectedScan, LanguageSwitcher, languageSwitcherProps] =
|
||||
useSmartLanguage({
|
||||
items: images,
|
||||
languages: languages,
|
||||
languageExtractor: useCallback(
|
||||
(item: NonNullable<Props["images"][number]>) =>
|
||||
item.language?.data?.attributes?.code,
|
||||
[]
|
||||
),
|
||||
});
|
||||
|
||||
const coverImages = useMemo(() => {
|
||||
const memo: UploadImageFragment[] = [];
|
||||
|
@ -74,7 +79,7 @@ export function ScanSetCover(props: Props): JSX.Element {
|
|||
</div>
|
||||
|
||||
<div className="flex flex-row flex-wrap place-items-center gap-4 pb-6">
|
||||
<LanguageSwitcher />
|
||||
<LanguageSwitcher {...languageSwitcherProps} />
|
||||
|
||||
<div className="grid place-content-center place-items-center">
|
||||
<p className="font-headers">{langui.status}:</p>
|
||||
|
|
|
@ -69,7 +69,7 @@ interface TOCInterface {
|
|||
children: TOCInterface[];
|
||||
}
|
||||
|
||||
export function getTocFromMarkdawn(text: string, title?: string): TOCInterface {
|
||||
function getTocFromMarkdawn(text: string, title?: string): TOCInterface {
|
||||
const toc: TOCInterface = {
|
||||
title: title ?? "Return to top",
|
||||
slug: slugify(title),
|
||||
|
|
|
@ -4,7 +4,7 @@ import { prettySlug } from "helpers/formatters";
|
|||
import { filterHasAttributes, getStatusDescription } from "helpers/others";
|
||||
import { PostWithTranslations } from "helpers/types";
|
||||
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
||||
import { Fragment, useMemo } from "react";
|
||||
import { Fragment, useCallback, useMemo } from "react";
|
||||
import { AppLayout } from "./AppLayout";
|
||||
import { Chip } from "./Chip";
|
||||
import { HorizontalLine } from "./HorizontalLine";
|
||||
|
@ -49,11 +49,16 @@ export function PostPage(props: Props): JSX.Element {
|
|||
} = props;
|
||||
const displayTitle = props.displayTitle ?? true;
|
||||
|
||||
const [selectedTranslation, LanguageSwitcher] = useSmartLanguage({
|
||||
items: post.translations,
|
||||
languages: languages,
|
||||
languageExtractor: (item) => item.language?.data?.attributes?.code,
|
||||
});
|
||||
const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] =
|
||||
useSmartLanguage({
|
||||
items: post.translations,
|
||||
languages: languages,
|
||||
languageExtractor: useCallback(
|
||||
(item: NonNullable<PostWithTranslations["translations"][number]>) =>
|
||||
item.language?.data?.attributes?.code,
|
||||
[]
|
||||
),
|
||||
});
|
||||
|
||||
const { thumbnail, body, title, excerpt } = useMemo(
|
||||
() => ({
|
||||
|
@ -156,7 +161,7 @@ export function PostPage(props: Props): JSX.Element {
|
|||
description={excerpt}
|
||||
langui={langui}
|
||||
categories={post.categories}
|
||||
languageSwitcher={<LanguageSwitcher />}
|
||||
languageSwitcher={<LanguageSwitcher {...languageSwitcherProps} />}
|
||||
/>
|
||||
|
||||
<HorizontalLine />
|
||||
|
@ -165,7 +170,7 @@ export function PostPage(props: Props): JSX.Element {
|
|||
<>
|
||||
{displayLanguageSwitcher && (
|
||||
<div className="grid place-content-end place-items-start">
|
||||
<LanguageSwitcher />
|
||||
<LanguageSwitcher {...languageSwitcherProps} />
|
||||
</div>
|
||||
)}
|
||||
{displayTitle && (
|
||||
|
@ -189,6 +194,7 @@ export function PostPage(props: Props): JSX.Element {
|
|||
displayThumbnailHeader,
|
||||
displayTitle,
|
||||
excerpt,
|
||||
languageSwitcherProps,
|
||||
langui,
|
||||
post.categories,
|
||||
prependBody,
|
||||
|
|
|
@ -17,6 +17,7 @@ import { ImageQuality } from "helpers/img";
|
|||
|
||||
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
||||
import Link from "next/link";
|
||||
import { useCallback } from "react";
|
||||
import { Chip } from "./Chip";
|
||||
import { Ico, Icon } from "./Ico";
|
||||
import { Img } from "./Img";
|
||||
|
@ -290,15 +291,13 @@ export function PreviewCard(props: Props): JSX.Element {
|
|||
|
||||
interface TranslatedProps
|
||||
extends Omit<Props, "description" | "pre_title" | "subtitle" | "title"> {
|
||||
translations:
|
||||
| {
|
||||
pre_title?: string | null | undefined;
|
||||
title: string | null | undefined;
|
||||
subtitle?: string | null | undefined;
|
||||
description?: string | null | undefined;
|
||||
language: string | undefined;
|
||||
}[]
|
||||
| undefined;
|
||||
translations: {
|
||||
pre_title?: string | null | undefined;
|
||||
title: string | null | undefined;
|
||||
subtitle?: string | null | undefined;
|
||||
description?: string | null | undefined;
|
||||
language: string | undefined;
|
||||
}[];
|
||||
slug: string;
|
||||
languages: AppStaticProps["languages"];
|
||||
}
|
||||
|
@ -313,7 +312,10 @@ export function TranslatedPreviewCard(props: TranslatedProps): JSX.Element {
|
|||
const [selectedTranslation] = useSmartLanguage({
|
||||
items: translations,
|
||||
languages: languages,
|
||||
languageExtractor: (item) => item.language,
|
||||
languageExtractor: useCallback(
|
||||
(item: TranslatedProps["translations"][number]) => item.language,
|
||||
[]
|
||||
),
|
||||
});
|
||||
|
||||
return (
|
||||
|
|
|
@ -5,6 +5,7 @@ import { ImageQuality } from "helpers/img";
|
|||
|
||||
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
||||
import Link from "next/link";
|
||||
import { useCallback } from "react";
|
||||
import { Chip } from "./Chip";
|
||||
import { Img } from "./Img";
|
||||
|
||||
|
@ -19,7 +20,7 @@ interface Props {
|
|||
bottomChips?: string[];
|
||||
}
|
||||
|
||||
export function PreviewLine(props: Props): JSX.Element {
|
||||
function PreviewLine(props: Props): JSX.Element {
|
||||
const {
|
||||
href,
|
||||
thumbnail,
|
||||
|
@ -76,14 +77,13 @@ export function PreviewLine(props: Props): JSX.Element {
|
|||
|
||||
interface TranslatedProps
|
||||
extends Omit<Props, "pre_title" | "subtitle" | "title"> {
|
||||
translations:
|
||||
| {
|
||||
pre_title?: string | null | undefined;
|
||||
title: string | null | undefined;
|
||||
subtitle?: string | null | undefined;
|
||||
language: string | undefined;
|
||||
}[]
|
||||
| undefined;
|
||||
translations: {
|
||||
pre_title?: string | null | undefined;
|
||||
title: string | null | undefined;
|
||||
subtitle?: string | null | undefined;
|
||||
language: string | undefined;
|
||||
}[];
|
||||
|
||||
slug: string;
|
||||
languages: AppStaticProps["languages"];
|
||||
}
|
||||
|
@ -98,7 +98,10 @@ export function TranslatedPreviewLine(props: TranslatedProps): JSX.Element {
|
|||
const [selectedTranslation] = useSmartLanguage({
|
||||
items: translations,
|
||||
languages: languages,
|
||||
languageExtractor: (item) => item.language,
|
||||
languageExtractor: useCallback(
|
||||
(item: TranslatedProps["translations"][number]) => item.language,
|
||||
[]
|
||||
),
|
||||
});
|
||||
|
||||
return (
|
||||
|
|
|
@ -3,16 +3,15 @@ import { ToolTip } from "components/ToolTip";
|
|||
import { AppStaticProps } from "graphql/getAppStaticProps";
|
||||
import { getStatusDescription } from "helpers/others";
|
||||
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
||||
import { useCallback } from "react";
|
||||
|
||||
interface Props {
|
||||
source?: string;
|
||||
translations:
|
||||
| {
|
||||
language: string | undefined;
|
||||
definition: string | null | undefined;
|
||||
status: string | undefined;
|
||||
}[]
|
||||
| undefined;
|
||||
translations: {
|
||||
language: string | undefined;
|
||||
definition: string | null | undefined;
|
||||
status: string | undefined;
|
||||
}[];
|
||||
languages: AppStaticProps["languages"];
|
||||
langui: AppStaticProps["langui"];
|
||||
index: number;
|
||||
|
@ -21,11 +20,15 @@ interface Props {
|
|||
export default function DefinitionCard(props: Props): JSX.Element {
|
||||
const { source, translations = [], languages, langui, index } = props;
|
||||
|
||||
const [selectedTranslation, LanguageSwitcher] = useSmartLanguage({
|
||||
items: translations,
|
||||
languages: languages,
|
||||
languageExtractor: (item) => item.language,
|
||||
});
|
||||
const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] =
|
||||
useSmartLanguage({
|
||||
items: translations,
|
||||
languages: languages,
|
||||
languageExtractor: useCallback(
|
||||
(item: Props["translations"][number]) => item.language,
|
||||
[]
|
||||
),
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -40,7 +43,9 @@ export default function DefinitionCard(props: Props): JSX.Element {
|
|||
<Chip>{selectedTranslation.status}</Chip>
|
||||
</ToolTip>
|
||||
)}
|
||||
{translations.length > 1 && <LanguageSwitcher />}
|
||||
{translations.length > 1 && (
|
||||
<LanguageSwitcher {...languageSwitcherProps} />
|
||||
)}
|
||||
</div>
|
||||
|
||||
<p className="italic">{`${langui.source}: ${source}`}</p>
|
||||
|
|
|
@ -106,8 +106,6 @@ const initialState: RequiredNonNullable<AppLayoutState> = {
|
|||
|
||||
const AppContext = React.createContext<AppLayoutState>(initialState);
|
||||
|
||||
export default AppContext;
|
||||
|
||||
export function useAppLayout(): AppLayoutState {
|
||||
return useContext(AppContext);
|
||||
}
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
import { ContentWithTranslations } from "./types";
|
||||
|
||||
type Group = NonNullable<
|
||||
NonNullable<
|
||||
NonNullable<
|
||||
NonNullable<ContentWithTranslations["group"]>["data"]
|
||||
>["attributes"]
|
||||
>["contents"]
|
||||
>["data"];
|
||||
|
||||
export function getPreviousContent(group: Group, currentSlug: string) {
|
||||
for (let index = 0; index < group.length; index += 1) {
|
||||
const content = group[index];
|
||||
if (content.attributes?.slug === currentSlug && index > 0) {
|
||||
return group[index - 1];
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function getNextContent(group: Group, currentSlug: string) {
|
||||
for (let index = 0; index < group.length; index += 1) {
|
||||
const content = group[index];
|
||||
if (content.attributes?.slug === currentSlug && index < group.length - 1) {
|
||||
return group[index + 1];
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
|
@ -242,24 +242,12 @@ export function prettyLanguage(
|
|||
return result;
|
||||
}
|
||||
|
||||
export function prettyLanguageToCode(
|
||||
prettyLanguage: string,
|
||||
languages: AppStaticProps["languages"]
|
||||
): string {
|
||||
let result = prettyLanguage;
|
||||
languages.forEach((language) => {
|
||||
if (language?.attributes?.localized_name === prettyLanguage)
|
||||
result = language.attributes.code;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
export function prettyURL(url: string): string {
|
||||
let domain = new URL(url);
|
||||
return domain.hostname.replace("www.", "");
|
||||
}
|
||||
|
||||
export function capitalizeString(string: string): string {
|
||||
function capitalizeString(string: string): string {
|
||||
function capitalizeWord(word: string): string {
|
||||
return word.charAt(0).toUpperCase() + word.substring(1);
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ export enum ImageQuality {
|
|||
Og = "og",
|
||||
}
|
||||
|
||||
export interface OgImage {
|
||||
interface OgImage {
|
||||
image: string;
|
||||
width: number;
|
||||
height: number;
|
||||
|
@ -34,7 +34,7 @@ export function getAssetURL(url: string, quality: ImageQuality): string {
|
|||
return process.env.NEXT_PUBLIC_URL_IMG + newUrl;
|
||||
}
|
||||
|
||||
export function getImgSizesByMaxSize(
|
||||
function getImgSizesByMaxSize(
|
||||
width: number,
|
||||
height: number,
|
||||
maxSize: number
|
||||
|
|
|
@ -5,7 +5,9 @@ import { prettyinlineTitle, prettyDate } from "./formatters";
|
|||
import { convertPrice } from "./numbers";
|
||||
import { isDefined, mapRemoveEmptyValues } from "./others";
|
||||
import { LibraryItemUserStatus } from "./types";
|
||||
type Items = NonNullable<GetLibraryItemsPreviewQuery["libraryItems"]>["data"];
|
||||
import LibraryPage from "../pages/library/index";
|
||||
|
||||
type Items = Parameters<typeof LibraryPage>[0]["items"];
|
||||
type GroupLibraryItems = Map<string, Items>;
|
||||
|
||||
export function getGroups(
|
||||
|
|
|
@ -1,74 +0,0 @@
|
|||
import { UploadImageFragment } from "graphql/generated";
|
||||
import { MeiliSearch, SearchResponse } from "meilisearch";
|
||||
import { prettySlug } from "./formatters";
|
||||
|
||||
export enum Indexes {
|
||||
ChronologyEra = "chronology-era",
|
||||
ChronologyItem = "chronology-item",
|
||||
Content = "content",
|
||||
GlossaryItem = "glossary-item",
|
||||
LibraryItem = "library-item",
|
||||
MerchItem = "merch-item",
|
||||
Post = "post",
|
||||
Video = "video",
|
||||
VideoChannel = "video-channel",
|
||||
WeaponStory = "weapon-story",
|
||||
}
|
||||
|
||||
export function getClient() {
|
||||
return new MeiliSearch({
|
||||
host: process.env.NEXT_PUBLIC_URL_SEARCH ?? "",
|
||||
apiKey: "",
|
||||
});
|
||||
}
|
||||
|
||||
export async function getIndexes(client: MeiliSearch) {
|
||||
return await client.getIndexes();
|
||||
}
|
||||
|
||||
export async function search(
|
||||
client: MeiliSearch,
|
||||
indexName: Indexes,
|
||||
query: string
|
||||
) {
|
||||
const index = await client.getIndex(indexName);
|
||||
const results = await index.search(query);
|
||||
return processSearchResults(results, indexName);
|
||||
}
|
||||
|
||||
export type SearchResult = {
|
||||
hits: {
|
||||
id: string;
|
||||
href: string;
|
||||
title: string;
|
||||
thumbnail?: UploadImageFragment;
|
||||
}[];
|
||||
indexName: string;
|
||||
};
|
||||
|
||||
export function processSearchResults(
|
||||
result: SearchResponse<Record<string, any>>,
|
||||
indexName: Indexes
|
||||
): SearchResult {
|
||||
return {
|
||||
hits: result.hits.map((hit) => {
|
||||
switch (indexName) {
|
||||
case Indexes.Post: {
|
||||
return {
|
||||
id: hit.id,
|
||||
title:
|
||||
hit.translations.length > 0
|
||||
? hit.translations[0].title
|
||||
: prettySlug(hit.slug),
|
||||
href: `/news/${hit.slug}`,
|
||||
thumbnail: hit.thumbnail,
|
||||
};
|
||||
}
|
||||
default: {
|
||||
return { id: hit.id, title: prettySlug(hit.slug), href: "error" };
|
||||
}
|
||||
}
|
||||
}),
|
||||
indexName: indexName,
|
||||
};
|
||||
}
|
|
@ -3,7 +3,6 @@ import {
|
|||
GetPostQuery,
|
||||
GetWikiPageQuery,
|
||||
} from "graphql/generated";
|
||||
import React from "react";
|
||||
|
||||
type Post = NonNullable<
|
||||
NonNullable<GetPostQuery["posts"]>["data"][number]["attributes"]
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import { breaks } from "../../design.config";
|
||||
|
||||
export function useMediaQuery(query: string): boolean {
|
||||
function useMediaQuery(query: string): boolean {
|
||||
function getMatches(query: string): boolean {
|
||||
// Prevents SSR issues
|
||||
if (typeof window !== "undefined") {
|
||||
|
@ -33,6 +33,7 @@ export function useMediaQuery(query: string): boolean {
|
|||
return matches;
|
||||
}
|
||||
|
||||
// ts-unused-exports:disable-next-line
|
||||
export function useMediaThin() {
|
||||
return useMediaQuery(breaks.thin.raw);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
import { LanguageSwitcher } from "components/Inputs/LanguageSwitcher";
|
||||
import { useAppLayout } from "contexts/AppLayoutContext";
|
||||
import { AppStaticProps } from "graphql/getAppStaticProps";
|
||||
import { filterDefined, isDefined } from "helpers/others";
|
||||
|
||||
import { useRouter } from "next/router";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
|
||||
interface Props<T> {
|
||||
items: T[];
|
||||
languages: AppStaticProps["languages"];
|
||||
languageExtractor: (item: NonNullable<T>) => string | undefined;
|
||||
transform?: (item: NonNullable<T>) => NonNullable<T>;
|
||||
}
|
||||
|
||||
function getPreferredLanguage(
|
||||
preferredLanguages: (string | undefined)[],
|
||||
availableLanguages: Map<string, number>
|
||||
): number | undefined {
|
||||
for (const locale of preferredLanguages) {
|
||||
if (isDefined(locale) && availableLanguages.has(locale)) {
|
||||
return availableLanguages.get(locale);
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function useSmartLanguage<T>(
|
||||
props: Props<T>
|
||||
): [
|
||||
T | undefined,
|
||||
typeof LanguageSwitcher,
|
||||
Parameters<typeof LanguageSwitcher>[0]
|
||||
] {
|
||||
const {
|
||||
items,
|
||||
languageExtractor,
|
||||
languages,
|
||||
transform = (item) => item,
|
||||
} = props;
|
||||
const { preferredLanguages } = useAppLayout();
|
||||
const router = useRouter();
|
||||
|
||||
const availableLocales = useMemo(() => {
|
||||
const memo = new Map<string, number>();
|
||||
filterDefined(items).map((elem, index) => {
|
||||
const result = languageExtractor(elem);
|
||||
if (isDefined(result)) memo.set(result, index);
|
||||
});
|
||||
return memo;
|
||||
}, [items, languageExtractor]);
|
||||
|
||||
const [selectedTranslationIndex, setSelectedTranslationIndex] = useState<
|
||||
number | undefined
|
||||
>();
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedTranslationIndex(
|
||||
getPreferredLanguage(
|
||||
preferredLanguages ?? [router.locale],
|
||||
availableLocales
|
||||
)
|
||||
);
|
||||
}, [preferredLanguages, availableLocales, router.locale]);
|
||||
|
||||
const selectedTranslation = useMemo(() => {
|
||||
if (isDefined(selectedTranslationIndex)) {
|
||||
const item = items[selectedTranslationIndex];
|
||||
if (isDefined(item)) {
|
||||
return transform(item);
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}, [items, selectedTranslationIndex, transform]);
|
||||
|
||||
const languageSwitcherProps = {
|
||||
languages: languages,
|
||||
locales: availableLocales,
|
||||
localesIndex: selectedTranslationIndex,
|
||||
onLanguageChanged: setSelectedTranslationIndex,
|
||||
};
|
||||
|
||||
return [selectedTranslation, LanguageSwitcher, languageSwitcherProps];
|
||||
}
|
|
@ -27,7 +27,11 @@ function getPreferredLanguage(
|
|||
|
||||
export function useSmartLanguage<T>(
|
||||
props: Props<T>
|
||||
): [T | undefined, () => JSX.Element] {
|
||||
): [
|
||||
T | undefined,
|
||||
typeof LanguageSwitcher,
|
||||
Parameters<typeof LanguageSwitcher>[0]
|
||||
] {
|
||||
const {
|
||||
items,
|
||||
languageExtractor,
|
||||
|
@ -52,12 +56,10 @@ export function useSmartLanguage<T>(
|
|||
|
||||
useEffect(() => {
|
||||
setSelectedTranslationIndex(
|
||||
(current) =>
|
||||
current ??
|
||||
getPreferredLanguage(
|
||||
preferredLanguages ?? [router.locale],
|
||||
availableLocales
|
||||
)
|
||||
getPreferredLanguage(
|
||||
preferredLanguages ?? [router.locale],
|
||||
availableLocales
|
||||
)
|
||||
);
|
||||
}, [preferredLanguages, availableLocales, router.locale]);
|
||||
|
||||
|
@ -71,15 +73,12 @@ export function useSmartLanguage<T>(
|
|||
return undefined;
|
||||
}, [items, selectedTranslationIndex, transform]);
|
||||
|
||||
return [
|
||||
selectedTranslation,
|
||||
() => (
|
||||
<LanguageSwitcher
|
||||
languages={languages}
|
||||
locales={availableLocales}
|
||||
localesIndex={selectedTranslationIndex}
|
||||
onLanguageChanged={setSelectedTranslationIndex}
|
||||
/>
|
||||
),
|
||||
];
|
||||
const languageSwitcherProps = {
|
||||
languages: languages,
|
||||
locales: availableLocales,
|
||||
localesIndex: selectedTranslationIndex,
|
||||
onLanguageChanged: setSelectedTranslationIndex,
|
||||
};
|
||||
|
||||
return [selectedTranslation, LanguageSwitcher, languageSwitcherProps];
|
||||
}
|
||||
|
|
|
@ -17,7 +17,6 @@ import { ThumbnailHeader } from "components/ThumbnailHeader";
|
|||
import { ToolTip } from "components/ToolTip";
|
||||
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
|
||||
import { getReadySdk } from "graphql/sdk";
|
||||
import { getNextContent, getPreviousContent } from "helpers/contents";
|
||||
import { getDescription } from "helpers/description";
|
||||
import {
|
||||
prettyinlineTitle,
|
||||
|
@ -27,6 +26,7 @@ import {
|
|||
} from "helpers/formatters";
|
||||
import { isUntangibleGroupItem } from "helpers/libraryItem";
|
||||
import {
|
||||
filterDefined,
|
||||
filterHasAttributes,
|
||||
getStatusDescription,
|
||||
isDefinedAndNotEmpty,
|
||||
|
@ -40,21 +40,34 @@ import {
|
|||
GetStaticPathsResult,
|
||||
GetStaticPropsContext,
|
||||
} from "next";
|
||||
import { Fragment, useMemo } from "react";
|
||||
import { Fragment, useCallback, useMemo } from "react";
|
||||
|
||||
interface Props extends AppStaticProps {
|
||||
content: ContentWithTranslations;
|
||||
}
|
||||
|
||||
type Group = NonNullable<
|
||||
NonNullable<
|
||||
NonNullable<
|
||||
NonNullable<ContentWithTranslations["group"]>["data"]
|
||||
>["attributes"]
|
||||
>["contents"]
|
||||
>["data"];
|
||||
|
||||
export default function Content(props: Props): JSX.Element {
|
||||
const { langui, content, languages, currencies } = props;
|
||||
const isMobile = useMediaMobile();
|
||||
|
||||
const [selectedTranslation, LanguageSwitcher] = useSmartLanguage({
|
||||
items: content.translations,
|
||||
languages: languages,
|
||||
languageExtractor: (item) => item.language?.data?.attributes?.code,
|
||||
});
|
||||
const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] =
|
||||
useSmartLanguage({
|
||||
items: content.translations,
|
||||
languages: languages,
|
||||
languageExtractor: useCallback(
|
||||
(item: NonNullable<Props["content"]["translations"][number]>) =>
|
||||
item.language?.data?.attributes?.code,
|
||||
[]
|
||||
),
|
||||
});
|
||||
|
||||
useScrollTopOnChange(AnchorIds.ContentPanel, [selectedTranslation]);
|
||||
|
||||
|
@ -305,7 +318,7 @@ export default function Content(props: Props): JSX.Element {
|
|||
type={content.type}
|
||||
categories={content.categories}
|
||||
langui={langui}
|
||||
languageSwitcher={<LanguageSwitcher />}
|
||||
languageSwitcher={<LanguageSwitcher {...languageSwitcherProps} />}
|
||||
/>
|
||||
|
||||
{previousContent?.attributes && (
|
||||
|
@ -315,14 +328,14 @@ export default function Content(props: Props): JSX.Element {
|
|||
</h2>
|
||||
<TranslatedPreviewLine
|
||||
href={`/contents/${previousContent.attributes.slug}`}
|
||||
translations={previousContent.attributes.translations?.map(
|
||||
(translation) => ({
|
||||
pre_title: translation?.pre_title,
|
||||
title: translation?.title,
|
||||
subtitle: translation?.subtitle,
|
||||
language: translation?.language?.data?.attributes?.code,
|
||||
})
|
||||
)}
|
||||
translations={filterDefined(
|
||||
previousContent.attributes.translations
|
||||
).map((translation) => ({
|
||||
pre_title: translation.pre_title,
|
||||
title: translation.title,
|
||||
subtitle: translation.subtitle,
|
||||
language: translation.language?.data?.attributes?.code,
|
||||
}))}
|
||||
slug={previousContent.attributes.slug}
|
||||
languages={languages}
|
||||
thumbnail={
|
||||
|
@ -368,14 +381,14 @@ export default function Content(props: Props): JSX.Element {
|
|||
</h2>
|
||||
<TranslatedPreviewLine
|
||||
href={`/contents/${nextContent.attributes.slug}`}
|
||||
translations={nextContent.attributes.translations?.map(
|
||||
(translation) => ({
|
||||
pre_title: translation?.pre_title,
|
||||
title: translation?.title,
|
||||
subtitle: translation?.subtitle,
|
||||
language: translation?.language?.data?.attributes?.code,
|
||||
})
|
||||
)}
|
||||
translations={filterDefined(
|
||||
nextContent.attributes.translations
|
||||
).map((translation) => ({
|
||||
pre_title: translation.pre_title,
|
||||
title: translation.title,
|
||||
subtitle: translation.subtitle,
|
||||
language: translation.language?.data?.attributes?.code,
|
||||
}))}
|
||||
slug={nextContent.attributes.slug}
|
||||
languages={languages}
|
||||
thumbnail={nextContent.attributes.thumbnail?.data?.attributes}
|
||||
|
@ -413,11 +426,16 @@ export default function Content(props: Props): JSX.Element {
|
|||
content.thumbnail?.data?.attributes,
|
||||
content.type,
|
||||
isMobile,
|
||||
languageSwitcherProps,
|
||||
languages,
|
||||
langui,
|
||||
nextContent?.attributes,
|
||||
previousContent?.attributes,
|
||||
selectedTranslation,
|
||||
selectedTranslation?.description,
|
||||
selectedTranslation?.pre_title,
|
||||
selectedTranslation?.subtitle,
|
||||
selectedTranslation?.text_set?.text,
|
||||
selectedTranslation?.title,
|
||||
]
|
||||
);
|
||||
|
||||
|
@ -487,3 +505,23 @@ export async function getStaticPaths(
|
|||
fallback: "blocking",
|
||||
};
|
||||
}
|
||||
|
||||
function getPreviousContent(group: Group, currentSlug: string) {
|
||||
for (let index = 0; index < group.length; index += 1) {
|
||||
const content = group[index];
|
||||
if (content.attributes?.slug === currentSlug && index > 0) {
|
||||
return group[index - 1];
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function getNextContent(group: Group, currentSlug: string) {
|
||||
for (let index = 0; index < group.length; index += 1) {
|
||||
const content = group[index];
|
||||
if (content.attributes?.slug === currentSlug && index < group.length - 1) {
|
||||
return group[index + 1];
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ import {
|
|||
} from "components/Panels/ContentPanel";
|
||||
import { SubPanel } from "components/Panels/SubPanel";
|
||||
import { TranslatedPreviewCard } from "components/PreviewCard";
|
||||
import { GetContentsQuery } from "graphql/generated";
|
||||
|
||||
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
|
||||
import { getReadySdk } from "graphql/sdk";
|
||||
import { prettyinlineTitle, prettySlug } from "helpers/formatters";
|
||||
|
@ -27,6 +27,7 @@ import {
|
|||
mapRemoveEmptyValues,
|
||||
} from "helpers/others";
|
||||
import { ContentPlaceholder } from "components/PanelComponents/ContentPlaceholder";
|
||||
import { GetContentsQuery } from "graphql/generated";
|
||||
|
||||
interface Props extends AppStaticProps {
|
||||
contents: NonNullable<GetContentsQuery["contents"]>["data"];
|
||||
|
@ -194,47 +195,53 @@ export default function Contents(props: Props): JSX.Element {
|
|||
>
|
||||
{filterHasAttributes(items).map((item) => (
|
||||
<Fragment key={item.id}>
|
||||
<TranslatedPreviewCard
|
||||
href={`/contents/${item.attributes.slug}`}
|
||||
translations={item.attributes.translations?.map(
|
||||
(translation) => ({
|
||||
pre_title: translation?.pre_title,
|
||||
title: translation?.title,
|
||||
subtitle: translation?.subtitle,
|
||||
language:
|
||||
translation?.language?.data?.attributes?.code,
|
||||
})
|
||||
)}
|
||||
slug={item.attributes.slug}
|
||||
languages={languages}
|
||||
thumbnail={item.attributes.thumbnail?.data?.attributes}
|
||||
thumbnailAspectRatio="3/2"
|
||||
thumbnailForceAspectRatio
|
||||
stackNumber={
|
||||
effectiveCombineRelatedContent &&
|
||||
item.attributes.group?.data?.attributes?.combine ===
|
||||
true
|
||||
? item.attributes.group.data.attributes.contents
|
||||
?.data.length
|
||||
: 0
|
||||
}
|
||||
topChips={
|
||||
item.attributes.type?.data?.attributes
|
||||
? [
|
||||
item.attributes.type.data.attributes.titles?.[0]
|
||||
? item.attributes.type.data.attributes
|
||||
.titles[0]?.title
|
||||
: prettySlug(
|
||||
item.attributes.type.data.attributes.slug
|
||||
),
|
||||
]
|
||||
: undefined
|
||||
}
|
||||
bottomChips={item.attributes.categories?.data.map(
|
||||
(category) => category.attributes?.short ?? ""
|
||||
)}
|
||||
keepInfoVisible={keepInfoVisible}
|
||||
/>
|
||||
{item.attributes.translations && (
|
||||
<TranslatedPreviewCard
|
||||
href={`/contents/${item.attributes.slug}`}
|
||||
translations={item.attributes.translations.map(
|
||||
(translation) => ({
|
||||
pre_title: translation?.pre_title,
|
||||
title: translation?.title,
|
||||
subtitle: translation?.subtitle,
|
||||
language:
|
||||
translation?.language?.data?.attributes?.code,
|
||||
})
|
||||
)}
|
||||
slug={item.attributes.slug}
|
||||
languages={languages}
|
||||
thumbnail={
|
||||
item.attributes.thumbnail?.data?.attributes
|
||||
}
|
||||
thumbnailAspectRatio="3/2"
|
||||
thumbnailForceAspectRatio
|
||||
stackNumber={
|
||||
effectiveCombineRelatedContent &&
|
||||
item.attributes.group?.data?.attributes?.combine ===
|
||||
true
|
||||
? item.attributes.group.data.attributes.contents
|
||||
?.data.length
|
||||
: 0
|
||||
}
|
||||
topChips={
|
||||
item.attributes.type?.data?.attributes
|
||||
? [
|
||||
item.attributes.type.data.attributes
|
||||
.titles?.[0]
|
||||
? item.attributes.type.data.attributes
|
||||
.titles[0]?.title
|
||||
: prettySlug(
|
||||
item.attributes.type.data.attributes
|
||||
.slug
|
||||
),
|
||||
]
|
||||
: undefined
|
||||
}
|
||||
bottomChips={item.attributes.categories?.data.map(
|
||||
(category) => category.attributes?.short ?? ""
|
||||
)}
|
||||
keepInfoVisible={keepInfoVisible}
|
||||
/>
|
||||
)}
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
|
@ -243,14 +250,7 @@ export default function Contents(props: Props): JSX.Element {
|
|||
)}
|
||||
</ContentPanel>
|
||||
),
|
||||
[
|
||||
effectiveCombineRelatedContent,
|
||||
groups,
|
||||
keepInfoVisible,
|
||||
languages,
|
||||
langui.result,
|
||||
langui.results,
|
||||
]
|
||||
[effectiveCombineRelatedContent, groups, keepInfoVisible, languages, langui]
|
||||
);
|
||||
|
||||
return (
|
||||
|
@ -287,7 +287,7 @@ export async function getStaticProps(
|
|||
};
|
||||
}
|
||||
|
||||
function getGroups(
|
||||
export function getGroups(
|
||||
langui: AppStaticProps["langui"],
|
||||
groupByType: number,
|
||||
items: Props["contents"]
|
||||
|
@ -349,7 +349,7 @@ function getGroups(
|
|||
return mapRemoveEmptyValues(groups);
|
||||
}
|
||||
|
||||
function filterContents(
|
||||
export function filterContents(
|
||||
contents: Props["contents"],
|
||||
combineRelatedContent: boolean,
|
||||
searchName: string
|
||||
|
|
|
@ -4,7 +4,6 @@ import { Img } from "components/Img";
|
|||
import { Button } from "components/Inputs/Button";
|
||||
import { Switch } from "components/Inputs/Switch";
|
||||
import { InsetBox } from "components/InsetBox";
|
||||
import { ContentLine } from "components/Library/ContentLine";
|
||||
import { PreviewCardCTAs } from "components/Library/PreviewCardCTAs";
|
||||
import { NavOption } from "components/PanelComponents/NavOption";
|
||||
import {
|
||||
|
@ -31,14 +30,17 @@ import {
|
|||
prettyItemSubType,
|
||||
prettyItemType,
|
||||
prettyPrice,
|
||||
prettySlug,
|
||||
prettyURL,
|
||||
} from "helpers/formatters";
|
||||
import { getAssetURL, ImageQuality } from "helpers/img";
|
||||
import { convertMmToInch } from "helpers/numbers";
|
||||
import {
|
||||
filterDefined,
|
||||
filterHasAttributes,
|
||||
isDefined,
|
||||
isDefinedAndNotEmpty,
|
||||
isUndefined,
|
||||
sortContent,
|
||||
} from "helpers/others";
|
||||
|
||||
|
@ -49,10 +51,14 @@ import {
|
|||
GetStaticPathsResult,
|
||||
GetStaticPropsContext,
|
||||
} from "next";
|
||||
import { Fragment, useMemo, useState } from "react";
|
||||
import { Fragment, useCallback, useMemo, useState } from "react";
|
||||
import { isUntangibleGroupItem } from "helpers/libraryItem";
|
||||
import { useMediaHoverable } from "hooks/useMediaQuery";
|
||||
import { WithLabel } from "components/Inputs/WithLabel";
|
||||
import { useToggle } from "hooks/useToggle";
|
||||
import { Ico, Icon } from "components/Ico";
|
||||
import { cJoin, cIf } from "helpers/className";
|
||||
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
||||
|
||||
interface Props extends AppStaticProps {
|
||||
item: NonNullable<
|
||||
|
@ -66,7 +72,7 @@ interface Props extends AppStaticProps {
|
|||
}
|
||||
|
||||
export default function LibrarySlug(props: Props): JSX.Element {
|
||||
const { item, itemId, langui, currencies } = props;
|
||||
const { item, itemId, langui, currencies, languages } = props;
|
||||
const appLayout = useAppLayout();
|
||||
const hoverable = useMediaHoverable();
|
||||
const [openLightBox, LightBox] = useLightBox();
|
||||
|
@ -501,14 +507,56 @@ export default function LibrarySlug(props: Props): JSX.Element {
|
|||
/>
|
||||
)}
|
||||
<div className="grid w-full gap-4">
|
||||
{item.contents.data.map((content) => (
|
||||
<ContentLine
|
||||
langui={langui}
|
||||
content={content}
|
||||
parentSlug={item.slug}
|
||||
key={content.id}
|
||||
/>
|
||||
))}
|
||||
{filterHasAttributes(item.contents.data).map(
|
||||
(rangedContent) => (
|
||||
<ContentLine
|
||||
content={
|
||||
rangedContent.attributes.content?.data?.attributes
|
||||
? {
|
||||
translations: filterDefined(
|
||||
rangedContent.attributes.content.data.attributes
|
||||
.translations
|
||||
).map((translation) => ({
|
||||
pre_title: translation.pre_title,
|
||||
title: translation.title,
|
||||
subtitle: translation.subtitle,
|
||||
language:
|
||||
translation.language?.data?.attributes?.code,
|
||||
})),
|
||||
categories: filterHasAttributes(
|
||||
rangedContent.attributes.content.data.attributes
|
||||
.categories?.data
|
||||
).map((category) => category.attributes.short),
|
||||
type:
|
||||
rangedContent.attributes.content.data.attributes
|
||||
.type?.data?.attributes?.titles?.[0]?.title ??
|
||||
prettySlug(
|
||||
rangedContent.attributes.content.data
|
||||
.attributes.type?.data?.attributes?.slug
|
||||
),
|
||||
slug: rangedContent.attributes.content.data
|
||||
.attributes.slug,
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
langui={langui}
|
||||
rangeStart={
|
||||
rangedContent.attributes.range[0]?.__typename ===
|
||||
"ComponentRangePageRange"
|
||||
? `${rangedContent.attributes.range[0].starting_page}`
|
||||
: ""
|
||||
}
|
||||
slug={rangedContent.attributes.slug}
|
||||
parentSlug={item.slug}
|
||||
key={rangedContent.id}
|
||||
languages={languages}
|
||||
hasScanSet={
|
||||
isDefined(rangedContent.attributes.scan_set) &&
|
||||
rangedContent.attributes.scan_set.length > 0
|
||||
}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
@ -517,16 +565,31 @@ export default function LibrarySlug(props: Props): JSX.Element {
|
|||
),
|
||||
[
|
||||
LightBox,
|
||||
openLightBox,
|
||||
appLayout.currency,
|
||||
currencies,
|
||||
displayOpenScans,
|
||||
hoverable,
|
||||
isVariantSet,
|
||||
item,
|
||||
itemId,
|
||||
keepInfoVisible,
|
||||
langui,
|
||||
item.thumbnail?.data?.attributes,
|
||||
item.subitem_of?.data,
|
||||
item.title,
|
||||
item.subtitle,
|
||||
item.metadata,
|
||||
item.descriptions,
|
||||
item.urls,
|
||||
item.gallery,
|
||||
item.release_date,
|
||||
item.price,
|
||||
item.categories,
|
||||
item.size,
|
||||
item.subitems,
|
||||
item.contents,
|
||||
item.slug,
|
||||
itemId,
|
||||
currencies,
|
||||
appLayout.currency,
|
||||
isVariantSet,
|
||||
hoverable,
|
||||
keepInfoVisible,
|
||||
displayOpenScans,
|
||||
openLightBox,
|
||||
languages,
|
||||
]
|
||||
);
|
||||
|
||||
|
@ -582,3 +645,112 @@ export async function getStaticPaths(
|
|||
fallback: "blocking",
|
||||
};
|
||||
}
|
||||
|
||||
interface ContentLineProps {
|
||||
content?: {
|
||||
translations: {
|
||||
pre_title: string | null | undefined;
|
||||
title: string;
|
||||
subtitle: string | null | undefined;
|
||||
language: string | undefined;
|
||||
}[];
|
||||
categories?: string[];
|
||||
type?: string;
|
||||
slug: string;
|
||||
};
|
||||
rangeStart: string;
|
||||
parentSlug: string;
|
||||
slug: string;
|
||||
langui: AppStaticProps["langui"];
|
||||
languages: AppStaticProps["languages"];
|
||||
hasScanSet: boolean;
|
||||
}
|
||||
|
||||
export function ContentLine(props: ContentLineProps): JSX.Element {
|
||||
const {
|
||||
rangeStart,
|
||||
content,
|
||||
langui,
|
||||
languages,
|
||||
hasScanSet,
|
||||
slug,
|
||||
parentSlug,
|
||||
} = props;
|
||||
|
||||
const [opened, setOpened] = useState(false);
|
||||
const toggleOpened = useToggle(setOpened);
|
||||
|
||||
const [selectedTranslation] = useSmartLanguage({
|
||||
items: content?.translations ?? [],
|
||||
languages: languages,
|
||||
languageExtractor: useCallback(
|
||||
(
|
||||
item: NonNullable<ContentLineProps["content"]>["translations"][number]
|
||||
) => item.language,
|
||||
[]
|
||||
),
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cJoin(
|
||||
"grid gap-2 rounded-lg px-4",
|
||||
cIf(opened, "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 thin:grid-cols-[auto_auto_1fr_auto]"
|
||||
>
|
||||
<a>
|
||||
<h3 className="cursor-pointer" onClick={toggleOpened}>
|
||||
{selectedTranslation
|
||||
? prettyinlineTitle(
|
||||
selectedTranslation.pre_title,
|
||||
selectedTranslation.title,
|
||||
selectedTranslation.subtitle
|
||||
)
|
||||
: content
|
||||
? prettySlug(content.slug)
|
||||
: prettySlug(slug, parentSlug)}
|
||||
</h3>
|
||||
</a>
|
||||
<div className="flex flex-row flex-wrap gap-1">
|
||||
{content?.categories?.map((category, index) => (
|
||||
<Chip key={index}>{category}</Chip>
|
||||
))}
|
||||
</div>
|
||||
<p className="h-4 w-full border-b-2 border-dotted border-black opacity-30"></p>
|
||||
<p>{rangeStart}</p>
|
||||
{content?.type && (
|
||||
<Chip className="justify-self-end thin:hidden">{content.type}</Chip>
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
className={`grid-flow-col place-content-start place-items-center gap-2 ${
|
||||
opened ? "grid" : "hidden"
|
||||
}`}
|
||||
>
|
||||
<Ico icon={Icon.SubdirectoryArrowRight} className="text-dark" />
|
||||
|
||||
{hasScanSet || isDefined(content) ? (
|
||||
<>
|
||||
{hasScanSet && (
|
||||
<Button
|
||||
href={`/library/${parentSlug}/scans#${slug}`}
|
||||
text={langui.view_scans}
|
||||
/>
|
||||
)}
|
||||
{isDefined(content) && (
|
||||
<Button href={`/contents/${slug}`} text={langui.open_content} />
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
"The content is not available"
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
return <></>;
|
||||
}
|
||||
|
|
|
@ -194,36 +194,36 @@ export default function Library(props: Props): JSX.Element {
|
|||
/>
|
||||
)}
|
||||
|
||||
<ButtonGroup className="mt-4">
|
||||
<ToolTip content={langui.only_display_items_i_want}>
|
||||
<Button
|
||||
icon={Icon.Favorite}
|
||||
onClick={() => setFilterUserStatus(LibraryItemUserStatus.Want)}
|
||||
active={filterUserStatus === LibraryItemUserStatus.Want}
|
||||
/>
|
||||
</ToolTip>
|
||||
<ToolTip content={langui.only_display_items_i_have}>
|
||||
<Button
|
||||
icon={Icon.BackHand}
|
||||
onClick={() => setFilterUserStatus(LibraryItemUserStatus.Have)}
|
||||
active={filterUserStatus === LibraryItemUserStatus.Have}
|
||||
/>
|
||||
</ToolTip>
|
||||
<ToolTip content={langui.only_display_unmarked_items}>
|
||||
<Button
|
||||
icon={Icon.RadioButtonUnchecked}
|
||||
onClick={() => setFilterUserStatus(LibraryItemUserStatus.None)}
|
||||
active={filterUserStatus === LibraryItemUserStatus.None}
|
||||
/>
|
||||
</ToolTip>
|
||||
<ToolTip content={langui.display_all_items}>
|
||||
<Button
|
||||
text={"All"}
|
||||
onClick={() => setFilterUserStatus(undefined)}
|
||||
active={isUndefined(filterUserStatus)}
|
||||
/>
|
||||
</ToolTip>
|
||||
</ButtonGroup>
|
||||
{/* TODO: Add "All" to langui */}
|
||||
<ButtonGroup
|
||||
className="mt-4"
|
||||
buttonsProps={[
|
||||
{
|
||||
tooltip: langui.only_display_items_i_want,
|
||||
icon: Icon.Favorite,
|
||||
onClick: () => setFilterUserStatus(LibraryItemUserStatus.Want),
|
||||
active: filterUserStatus === LibraryItemUserStatus.Want,
|
||||
},
|
||||
{
|
||||
tooltip: langui.only_display_items_i_have,
|
||||
icon: Icon.BackHand,
|
||||
onClick: () => setFilterUserStatus(LibraryItemUserStatus.Have),
|
||||
active: filterUserStatus === LibraryItemUserStatus.Have,
|
||||
},
|
||||
{
|
||||
tooltip: langui.only_display_unmarked_items,
|
||||
icon: Icon.RadioButtonUnchecked,
|
||||
onClick: () => setFilterUserStatus(LibraryItemUserStatus.None),
|
||||
active: filterUserStatus === LibraryItemUserStatus.None,
|
||||
},
|
||||
{
|
||||
tooltip: langui.only_display_unmarked_items,
|
||||
text: "All",
|
||||
onClick: () => setFilterUserStatus(undefined),
|
||||
active: isUndefined(filterUserStatus),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
<Button
|
||||
className="mt-8"
|
||||
|
|
|
@ -26,7 +26,7 @@ import {
|
|||
GetStaticPathsResult,
|
||||
GetStaticPropsContext,
|
||||
} from "next";
|
||||
import { useMemo } from "react";
|
||||
import { useCallback, useMemo } from "react";
|
||||
|
||||
interface Props extends AppStaticProps {
|
||||
page: WikiPageWithTranslations;
|
||||
|
@ -35,11 +35,16 @@ interface Props extends AppStaticProps {
|
|||
export default function WikiPage(props: Props): JSX.Element {
|
||||
const { page, langui, languages } = props;
|
||||
|
||||
const [selectedTranslation, LanguageSwitcher] = useSmartLanguage({
|
||||
items: page.translations,
|
||||
languages: languages,
|
||||
languageExtractor: (item) => item.language?.data?.attributes?.code,
|
||||
});
|
||||
const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] =
|
||||
useSmartLanguage({
|
||||
items: page.translations,
|
||||
languages: languages,
|
||||
languageExtractor: useCallback(
|
||||
(item: NonNullable<Props["page"]["translations"][number]>) =>
|
||||
item.language?.data?.attributes?.code,
|
||||
[]
|
||||
),
|
||||
});
|
||||
|
||||
const subPanel = useMemo(
|
||||
() => (
|
||||
|
@ -69,7 +74,7 @@ export default function WikiPage(props: Props): JSX.Element {
|
|||
|
||||
<div className="flex place-content-center gap-4">
|
||||
<h1 className="text-center text-3xl">{selectedTranslation?.title}</h1>
|
||||
<LanguageSwitcher />
|
||||
<LanguageSwitcher {...languageSwitcherProps} />
|
||||
</div>
|
||||
|
||||
<HorizontalLine />
|
||||
|
@ -123,6 +128,7 @@ export default function WikiPage(props: Props): JSX.Element {
|
|||
),
|
||||
[
|
||||
LanguageSwitcher,
|
||||
languageSwitcherProps,
|
||||
languages,
|
||||
langui,
|
||||
page.categories?.data,
|
||||
|
|
|
@ -110,26 +110,28 @@ export default function Wiki(props: Props): JSX.Element {
|
|||
)}
|
||||
{filterHasAttributes(filteredPages).map((page) => (
|
||||
<Fragment key={page.id}>
|
||||
<TranslatedPreviewCard
|
||||
href={`/wiki/${page.attributes.slug}`}
|
||||
translations={page.attributes.translations?.map(
|
||||
(translation) => ({
|
||||
title: translation?.title,
|
||||
description: translation?.summary,
|
||||
language: translation?.language?.data?.attributes?.code,
|
||||
})
|
||||
)}
|
||||
thumbnail={page.attributes.thumbnail?.data?.attributes}
|
||||
thumbnailAspectRatio={"4/3"}
|
||||
thumbnailRounded
|
||||
thumbnailForceAspectRatio
|
||||
languages={languages}
|
||||
slug={page.attributes.slug}
|
||||
keepInfoVisible={keepInfoVisible}
|
||||
bottomChips={page.attributes.categories?.data.map(
|
||||
(category) => category.attributes?.short ?? ""
|
||||
)}
|
||||
/>
|
||||
{page.attributes.translations && (
|
||||
<TranslatedPreviewCard
|
||||
href={`/wiki/${page.attributes.slug}`}
|
||||
translations={page.attributes.translations.map(
|
||||
(translation) => ({
|
||||
title: translation?.title,
|
||||
description: translation?.summary,
|
||||
language: translation?.language?.data?.attributes?.code,
|
||||
})
|
||||
)}
|
||||
thumbnail={page.attributes.thumbnail?.data?.attributes}
|
||||
thumbnailAspectRatio={"4/3"}
|
||||
thumbnailRounded
|
||||
thumbnailForceAspectRatio
|
||||
languages={languages}
|
||||
slug={page.attributes.slug}
|
||||
keepInfoVisible={keepInfoVisible}
|
||||
bottomChips={page.attributes.categories?.data.map(
|
||||
(category) => category.attributes?.short ?? ""
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
|
|
Loading…
Reference in New Issue