From ae25df8d72a589f72532f1f1b00c072054e4e39a Mon Sep 17 00:00:00 2001 From: DrMint Date: Fri, 8 Jul 2022 01:42:38 +0200 Subject: [PATCH] There's a lot to unpack here --- .eslintignore | 11 +- .eslintrc.js | 51 ++- package-lock.json | 149 ++++--- package.json | 6 +- src/components/AppLayout.tsx | 34 +- src/components/Ico.tsx | 3 +- src/components/Img.tsx | 4 +- src/components/Inputs/Button.tsx | 14 +- src/components/Inputs/ButtonGroup.tsx | 2 +- src/components/Inputs/LanguageSwitcher.tsx | 9 +- src/components/Inputs/OrderableList.tsx | 2 +- src/components/Inputs/PageSelector.tsx | 36 +- src/components/Inputs/Select.tsx | 2 +- src/components/Inputs/Switch.tsx | 2 +- src/components/Inputs/TextInput.tsx | 2 +- src/components/Library/ScanSet.tsx | 14 +- src/components/Library/ScanSetCover.tsx | 18 +- src/components/Markdown/Markdawn.tsx | 24 +- src/components/PanelComponents/NavOption.tsx | 4 +- src/components/Panels/MainPanel.tsx | 4 +- src/components/Popup.tsx | 4 +- src/components/PostPage.tsx | 16 +- src/components/PreviewCard.tsx | 14 +- src/components/PreviewLine.tsx | 12 +- src/components/RecorderChip.tsx | 8 +- src/components/SmartList.tsx | 183 +++++++++ src/components/ToolTip.tsx | 1 + .../Chronology/ChronologyItemComponent.tsx | 13 +- .../Chronology/ChronologyYearComponent.tsx | 7 + src/components/Wiki/DefinitionCard.tsx | 60 ++- src/contexts/AppLayoutContext.tsx | 4 +- src/graphql/getAppStaticProps.ts | 7 +- src/graphql/getPostStaticProps.ts | 10 +- .../operations/getPostsPreview.graphql | 11 +- src/graphql/operations/getWikiPage.graphql | 10 +- src/graphql/sdk.ts | 2 +- src/helpers/className.ts | 11 +- src/helpers/description.ts | 12 +- src/helpers/formatters.ts | 116 +++--- src/helpers/img.ts | 4 +- src/helpers/libraryItem.ts | 257 +----------- src/helpers/numbers.ts | 16 +- src/helpers/others.ts | 37 +- src/hooks/useLightBox.tsx | 2 +- src/hooks/useMediaQuery.ts | 22 +- src/hooks/useScrollTopOnChange.ts | 13 +- src/hooks/useSmartLanguage.ts | 5 +- src/hooks/useStateWithLocalStorage.ts | 2 +- src/hooks/useToggle.ts | 6 +- src/pages/404.tsx | 2 +- src/pages/500.tsx | 2 +- src/pages/_app.tsx | 2 +- src/pages/about-us/contact.tsx | 5 +- src/pages/about-us/index.tsx | 2 +- src/pages/api/mail.ts | 2 +- src/pages/api/revalidate.ts | 46 +-- src/pages/archives/index.tsx | 5 +- src/pages/archives/videos/c/[uid].tsx | 4 +- src/pages/archives/videos/index.tsx | 94 ++--- src/pages/archives/videos/v/[uid].tsx | 4 +- src/pages/chronicles/index.tsx | 5 +- src/pages/contents/[slug]/index.tsx | 32 +- src/pages/contents/index.tsx | 375 ++++++++---------- src/pages/dev/checkup/contents.tsx | 4 +- src/pages/dev/checkup/libraryitems.tsx | 5 +- src/pages/dev/editor.tsx | 6 +- src/pages/dev/transcript.tsx | 4 +- src/pages/library/[slug]/index.tsx | 12 +- src/pages/library/[slug]/scans.tsx | 13 +- src/pages/library/index.tsx | 341 +++++++++++----- src/pages/merch/index.tsx | 3 +- src/pages/news/[slug].tsx | 2 +- src/pages/news/index.tsx | 111 +++--- src/pages/wiki/[slug]/index.tsx | 57 ++- src/pages/wiki/chronology.tsx | 11 +- src/pages/wiki/index.tsx | 15 +- 76 files changed, 1313 insertions(+), 1107 deletions(-) create mode 100644 src/components/SmartList.tsx diff --git a/.eslintignore b/.eslintignore index f42b52a..bb32f2c 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,2 +1,9 @@ -*.js -*.ts \ No newline at end of file +src/graphql/generated.ts +.eslintrc.js +graphql-codegen.config.js +next-env.d.ts +next-sitemap.config.js +next.config.js +postcss.config.js +tailwind.config.js +design.config.js \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.js index 29118c6..047db04 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -7,6 +7,8 @@ module.exports = { extends: [ "eslint:recommended", "plugin:@typescript-eslint/recommended", + "plugin:import/recommended", + "plugin:import/typescript", "next/core-web-vitals", ], rules: { @@ -122,7 +124,7 @@ module.exports = { "prefer-exponentiation-operator": "warn", "prefer-named-capture-group": "warn", "prefer-numeric-literals": "warn", - // "prefer-object-has-own": "warn", + "prefer-object-has-own": "warn", "prefer-object-spread": "warn", "prefer-promise-reject-errors": "warn", "prefer-regex-literals": "warn", @@ -214,5 +216,52 @@ module.exports = { /* NEXTJS */ "@next/next/no-img-element": "off", + + /* IMPORTS */ + "import/no-unresolved": "error", + "import/named": "error", + "import/default": "error", + "import/namespace": "error", + "import/no-restricted-paths": "error", + "import/no-absolute-path": "error", + "import/no-dynamic-require": "error", + // "import/no-internal-modules": "error", + "import/no-webpack-loader-syntax": "error", + "import/no-self-import": "error", + "import/no-cycle": "error", + "import/no-useless-path-segments": "error", + // "import/no-relative-parent-imports": "error", + "import/no-relative-packages": "error", + + "import/export": "error", + "import/no-named-as-default": "error", + "import/no-named-as-default-member": "error", + "import/no-deprecated": "error", + "import/no-extraneous-dependencies": "error", + "import/no-mutable-exports": "error", + "import/no-unused-modules": "error", + + "import/unambiguous": "error", + "import/no-commonjs": "error", + "import/no-amd": "error", + "import/no-nodejs-modules": "error", + "import/no-import-module-exports": "error", + + "import/first": "error", + // "import/exports-last": "error", + "import/no-duplicates": "error", + "import/no-namespace": "error", + "import/extensions": "error", + "import/order": "warn", + "import/newline-after-import": "error", + // "import/prefer-default-export": "error", + // "import/max-dependencies": "error", + // "import/no-unassigned-import": "error", + "import/no-named-default": "error", + // "import/no-default-export": "error", + // "import/no-named-export": "error", + "import/no-anonymous-default-export": "error", + // "import/group-exports": "error", + // "import/dynamic-import-chunkname)": "error", }, }; diff --git a/package-lock.json b/package-lock.json index 0f84b70..2de4518 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,7 @@ "react-dom": "18.2.0", "react-hot-keys": "^2.7.2", "react-swipeable": "^7.0.0", + "tippy.js": "^6.3.7", "turndown": "^7.1.1" }, "devDependencies": { @@ -38,10 +39,10 @@ "@typescript-eslint/parser": "^5.30.3", "eslint": "^8.19.0", "eslint-config-next": "12.2.0", + "eslint-plugin-import": "^2.26.0", "graphql": "^16.5.0", "next-sitemap": "^3.1.7", "prettier": "^2.7.1", - "prettier-plugin-organize-imports": "^3.0.0", "prettier-plugin-tailwindcss": "^0.1.11", "tailwindcss": "^3.1.4", "ts-unused-exports": "^8.0.0", @@ -1336,18 +1337,6 @@ "node": ">=10" } }, - "node_modules/@graphql-codegen/cli/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true, - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@graphql-codegen/cli/node_modules/mute-stream": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", @@ -4414,6 +4403,29 @@ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true }, + "node_modules/encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "optional": true, + "peer": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "optional": true, + "peer": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/end-of-stream": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", @@ -5589,9 +5601,9 @@ } }, "node_modules/https-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", - "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", "dev": true, "dependencies": { "agent-base": "6", @@ -6780,6 +6792,18 @@ "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", "dev": true }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -7611,16 +7635,6 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, - "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==", - "dev": true, - "peerDependencies": { - "prettier": ">=2.0", - "typescript": ">=2.9" - } - }, "node_modules/prettier-plugin-tailwindcss": { "version": "0.1.11", "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.1.11.tgz", @@ -8067,7 +8081,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true + "devOptional": true }, "node_modules/scheduler": { "version": "0.23.0", @@ -8175,6 +8189,15 @@ "node": ">=0.10.0" } }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-js": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", @@ -8193,15 +8216,6 @@ "source-map": "^0.6.0" } }, - "node_modules/source-map-support/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/sponge-case": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/sponge-case/-/sponge-case-1.0.1.tgz", @@ -10052,12 +10066,6 @@ "brace-expansion": "^1.1.7" } }, - "mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true - }, "mute-stream": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", @@ -12457,6 +12465,28 @@ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true }, + "encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "optional": true, + "peer": true, + "requires": { + "iconv-lite": "^0.6.2" + }, + "dependencies": { + "iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "optional": true, + "peer": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + } + } + }, "end-of-stream": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", @@ -13362,9 +13392,9 @@ } }, "https-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", - "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", "dev": true, "requires": { "agent-base": "6", @@ -14271,6 +14301,12 @@ "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", "dev": true }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -14806,13 +14842,6 @@ "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==", "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==", - "dev": true, - "requires": {} - }, "prettier-plugin-tailwindcss": { "version": "0.1.11", "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.1.11.tgz", @@ -15125,7 +15154,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true + "devOptional": true }, "scheduler": { "version": "0.23.0", @@ -15212,6 +15241,12 @@ "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=", "dev": true }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, "source-map-js": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", @@ -15225,14 +15260,6 @@ "requires": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } } }, "sponge-case": { diff --git a/package.json b/package.json index fc75733..df3d5e9 100644 --- a/package.json +++ b/package.json @@ -3,13 +3,14 @@ "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!", + "precommit": "npm run prettier && npm run unused-exports && npm run eslint && 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", "start": "next start -p 12500", "lint": "next lint", + "eslint": "npx eslint .", "generate": "graphql-codegen --config graphql-codegen.config.js", "tsc": "tsc", "prettier": "prettier --end-of-line auto --write ." @@ -31,6 +32,7 @@ "react-dom": "18.2.0", "react-hot-keys": "^2.7.2", "react-swipeable": "^7.0.0", + "tippy.js": "^6.3.7", "turndown": "^7.1.1" }, "devDependencies": { @@ -47,10 +49,10 @@ "@typescript-eslint/parser": "^5.30.3", "eslint": "^8.19.0", "eslint-config-next": "12.2.0", + "eslint-plugin-import": "^2.26.0", "graphql": "^16.5.0", "next-sitemap": "^3.1.7", "prettier": "^2.7.1", - "prettier-plugin-organize-imports": "^3.0.0", "prettier-plugin-tailwindcss": "^0.1.11", "tailwindcss": "^3.1.4", "ts-unused-exports": "^8.0.0", diff --git a/src/components/AppLayout.tsx b/src/components/AppLayout.tsx index 2d44daf..1ff4041 100644 --- a/src/components/AppLayout.tsx +++ b/src/components/AppLayout.tsx @@ -1,19 +1,3 @@ -import { Button } from "components/Inputs/Button"; -import { useAppLayout } from "contexts/AppLayoutContext"; -import { UploadImageFragment } from "graphql/generated"; -import { AppStaticProps } from "graphql/getAppStaticProps"; -import { cIf, cJoin } from "helpers/className"; -import { prettyLanguage, prettySlug } from "helpers/formatters"; -import { getOgImage, ImageQuality } from "helpers/img"; -import { - filterHasAttributes, - isDefined, - isDefinedAndNotEmpty, - isUndefined, - iterateMap, -} from "helpers/others"; -import { useMediaMobile } from "hooks/useMediaQuery"; -import { AnchorIds } from "hooks/useScrollTopOnChange"; import Head from "next/head"; import { useRouter } from "next/router"; import { useEffect, useLayoutEffect, useMemo, useState } from "react"; @@ -26,6 +10,22 @@ import { TextInput } from "./Inputs/TextInput"; import { ContentPlaceholder } from "./PanelComponents/ContentPlaceholder"; import { MainPanel } from "./Panels/MainPanel"; import { Popup } from "./Popup"; +import { AnchorIds } from "hooks/useScrollTopOnChange"; +import { useMediaMobile } from "hooks/useMediaQuery"; +import { + filterHasAttributes, + isDefined, + isDefinedAndNotEmpty, + isUndefined, + iterateMap, +} from "helpers/others"; +import { getOgImage, ImageQuality } from "helpers/img"; +import { prettyLanguage, prettySlug } from "helpers/formatters"; +import { cIf, cJoin } from "helpers/className"; +import { AppStaticProps } from "graphql/getAppStaticProps"; +import { UploadImageFragment } from "graphql/generated"; +import { useAppLayout } from "contexts/AppLayoutContext"; +import { Button } from "components/Inputs/Button"; /* * ╭─────────────╮ @@ -406,7 +406,7 @@ export const AppLayout = ({ insertLabels={ new Map([ [0, langui.primary_language], - [1, langui.secondary_language], + [1, langui.secondary_language], ]) } onChange={(items) => { diff --git a/src/components/Ico.tsx b/src/components/Ico.tsx index 2c9c670..f73226d 100644 --- a/src/components/Ico.tsx +++ b/src/components/Ico.tsx @@ -1,5 +1,5 @@ -import { cJoin } from "helpers/className"; import { MouseEventHandler } from "react"; +import { cJoin } from "helpers/className"; /* * ╭─────────────╮ @@ -31,6 +31,7 @@ export const Ico = ({ onClick, icon, className }: Props): JSX.Element => ( * ─────────────────────────────────────────╯ OTHER ╰───────────────────────────────────────────── */ +/* eslint-disable max-len */ export enum Icon { Onek = "1k", OnekPlus = "1k_plus", diff --git a/src/components/Img.tsx b/src/components/Img.tsx index c38c641..a4f8272 100644 --- a/src/components/Img.tsx +++ b/src/components/Img.tsx @@ -1,7 +1,7 @@ -import { UploadImageFragment } from "graphql/generated"; -import { getAssetURL, getImgSizesByQuality, ImageQuality } from "helpers/img"; import { ImageProps } from "next/image"; import { MouseEventHandler } from "react"; +import { UploadImageFragment } from "graphql/generated"; +import { getAssetURL, getImgSizesByQuality, ImageQuality } from "helpers/img"; /* * ╭─────────────╮ diff --git a/src/components/Inputs/Button.tsx b/src/components/Inputs/Button.tsx index d13f74e..92c9794 100644 --- a/src/components/Inputs/Button.tsx +++ b/src/components/Inputs/Button.tsx @@ -1,9 +1,9 @@ +import { useRouter } from "next/router"; +import React, { MouseEventHandler } from "react"; import { Ico, Icon } from "components/Ico"; import { cIf, cJoin } from "helpers/className"; import { ConditionalWrapper, Wrapper } from "helpers/component"; import { isDefined, isDefinedAndNotEmpty } from "helpers/others"; -import { useRouter } from "next/router"; -import React, { MouseEventHandler } from "react"; /* * ╭─────────────╮ @@ -22,6 +22,7 @@ interface Props { onClick?: MouseEventHandler; draggable?: boolean; badgeNumber?: number; + size?: "normal" | "small"; } // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ @@ -38,6 +39,7 @@ export const Button = ({ href, locale, badgeNumber, + size = "normal", }: Props): JSX.Element => { const router = useRouter(); @@ -70,13 +72,17 @@ export const Button = ({ "!border-black bg-black !text-light drop-shadow-black-lg", "cursor-pointer hover:bg-dark hover:text-light hover:drop-shadow-shade-lg" ), + cIf(size === "small", "px-3 py-1 text-xs"), className )} > {isDefined(badgeNumber) && (

{badgeNumber}

diff --git a/src/components/Inputs/ButtonGroup.tsx b/src/components/Inputs/ButtonGroup.tsx index 5eea5ba..9816fcc 100644 --- a/src/components/Inputs/ButtonGroup.tsx +++ b/src/components/Inputs/ButtonGroup.tsx @@ -1,8 +1,8 @@ +import { Button } from "./Button"; import { ToolTip } from "components/ToolTip"; import { cJoin } from "helpers/className"; import { ConditionalWrapper, Wrapper } from "helpers/component"; import { isDefinedAndNotEmpty } from "helpers/others"; -import { Button } from "./Button"; /* * ╭─────────────╮ diff --git a/src/components/Inputs/LanguageSwitcher.tsx b/src/components/Inputs/LanguageSwitcher.tsx index 828a0c1..dd00893 100644 --- a/src/components/Inputs/LanguageSwitcher.tsx +++ b/src/components/Inputs/LanguageSwitcher.tsx @@ -1,11 +1,11 @@ +import { Fragment } from "react"; +import { ToolTip } from "../ToolTip"; +import { Button } from "./Button"; import { Icon } from "components/Ico"; import { AppStaticProps } from "graphql/getAppStaticProps"; import { cJoin } from "helpers/className"; import { prettyLanguage } from "helpers/formatters"; import { iterateMap } from "helpers/others"; -import { Fragment } from "react"; -import { ToolTip } from "../ToolTip"; -import { Button } from "./Button"; /* * ╭─────────────╮ @@ -18,6 +18,7 @@ interface Props { locales: Map; localesIndex: number | undefined; onLanguageChanged: (index: number) => void; + size?: Parameters[0]["size"]; } // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ @@ -27,6 +28,7 @@ export const LanguageSwitcher = ({ locales, localesIndex, languages, + size, onLanguageChanged, }: Props): JSX.Element => ( 1 ? locales.size : undefined} icon={Icon.Translate} + size={size} /> ); diff --git a/src/components/Inputs/OrderableList.tsx b/src/components/Inputs/OrderableList.tsx index 7a6019e..6f6cb0f 100644 --- a/src/components/Inputs/OrderableList.tsx +++ b/src/components/Inputs/OrderableList.tsx @@ -1,6 +1,6 @@ +import { Fragment, useCallback, useState } from "react"; import { Ico, Icon } from "components/Ico"; import { isDefinedAndNotEmpty, iterateMap, mapMoveEntry } from "helpers/others"; -import { Fragment, useCallback, useState } from "react"; /* * ╭─────────────╮ diff --git a/src/components/Inputs/PageSelector.tsx b/src/components/Inputs/PageSelector.tsx index 3551dcd..5c4b82e 100644 --- a/src/components/Inputs/PageSelector.tsx +++ b/src/components/Inputs/PageSelector.tsx @@ -1,7 +1,7 @@ +import { Dispatch, SetStateAction } from "react"; +import { ButtonGroup } from "./ButtonGroup"; import { Icon } from "components/Ico"; import { cJoin } from "helpers/className"; -import { Dispatch, SetStateAction } from "react"; -import { Button } from "./Button"; /* * ╭─────────────╮ @@ -23,19 +23,21 @@ export const PageSelector = ({ maxPage, className, }: Props): JSX.Element => ( -
-
+ setPage((current) => (page > 0 ? current - 1 : current)), + icon: Icon.NavigateBefore, + }, + { + text: (page + 1).toString(), + }, + { + onClick: () => + setPage((current) => (page < maxPage ? page + 1 : current)), + icon: Icon.NavigateNext, + }, + ]} + /> ); diff --git a/src/components/Inputs/Select.tsx b/src/components/Inputs/Select.tsx index f8d9768..0eed39f 100644 --- a/src/components/Inputs/Select.tsx +++ b/src/components/Inputs/Select.tsx @@ -1,7 +1,7 @@ +import { Dispatch, Fragment, SetStateAction, useState } from "react"; import { Ico, Icon } from "components/Ico"; import { cIf, cJoin } from "helpers/className"; import { useToggle } from "hooks/useToggle"; -import { Dispatch, Fragment, SetStateAction, useState } from "react"; /* * ╭─────────────╮ diff --git a/src/components/Inputs/Switch.tsx b/src/components/Inputs/Switch.tsx index 97eabb3..ff8184b 100644 --- a/src/components/Inputs/Switch.tsx +++ b/src/components/Inputs/Switch.tsx @@ -1,6 +1,6 @@ +import { Dispatch, SetStateAction } from "react"; import { cIf, cJoin } from "helpers/className"; import { useToggle } from "hooks/useToggle"; -import { Dispatch, SetStateAction } from "react"; /* * ╭─────────────╮ diff --git a/src/components/Inputs/TextInput.tsx b/src/components/Inputs/TextInput.tsx index 31abfad..8f81017 100644 --- a/src/components/Inputs/TextInput.tsx +++ b/src/components/Inputs/TextInput.tsx @@ -1,7 +1,7 @@ +import { Dispatch, SetStateAction } from "react"; import { Ico, Icon } from "components/Ico"; import { cJoin } from "helpers/className"; import { isDefinedAndNotEmpty } from "helpers/others"; -import { Dispatch, SetStateAction } from "react"; /* * ╭─────────────╮ diff --git a/src/components/Library/ScanSet.tsx b/src/components/Library/ScanSet.tsx index a8f845d..c7a3410 100644 --- a/src/components/Library/ScanSet.tsx +++ b/src/components/Library/ScanSet.tsx @@ -1,3 +1,4 @@ +import { Fragment, useCallback, useMemo } from "react"; import { Chip } from "components/Chip"; import { Img } from "components/Img"; import { Button } from "components/Inputs/Button"; @@ -14,7 +15,6 @@ import { isDefinedAndNotEmpty, } from "helpers/others"; import { useSmartLanguage } from "hooks/useSmartLanguage"; -import { Fragment, useCallback, useMemo } from "react"; /* * ╭─────────────╮ @@ -139,7 +139,7 @@ export const ScanSet = ({
-

{langui.status}:

+

{langui.status}:

0 && (
-

{"Scanners"}:

+ {/* TODO: Add Scanner to langui */} +

{"Scanners"}:

{filterHasAttributes(selectedScan.scanners.data).map( (scanner) => ( @@ -168,7 +169,8 @@ export const ScanSet = ({ {selectedScan.cleaners && selectedScan.cleaners.data.length > 0 && (
-

{"Cleaners"}:

+ {/* TODO: Add Cleaners to langui */} +

{"Cleaners"}:

{filterHasAttributes(selectedScan.cleaners.data).map( (cleaner) => ( @@ -187,7 +189,8 @@ export const ScanSet = ({ {selectedScan.typesetters && selectedScan.typesetters.data.length > 0 && (
-

{"Typesetters"}:

+ {/* TODO: Add Cleaners to Typesetters */} +

{"Typesetters"}:

{filterHasAttributes(selectedScan.typesetters.data).map( (typesetter) => ( @@ -205,6 +208,7 @@ export const ScanSet = ({ {isDefinedAndNotEmpty(selectedScan.notes) && ( + {/* TODO: Add Notes to Typesetters */} {"Notes"} )} diff --git a/src/components/Library/ScanSetCover.tsx b/src/components/Library/ScanSetCover.tsx index f88e0b6..dd9ba70 100644 --- a/src/components/Library/ScanSetCover.tsx +++ b/src/components/Library/ScanSetCover.tsx @@ -1,3 +1,4 @@ +import { Fragment, useCallback, useMemo } from "react"; import { Chip } from "components/Chip"; import { Img } from "components/Img"; import { RecorderChip } from "components/RecorderChip"; @@ -10,7 +11,6 @@ import { AppStaticProps } from "graphql/getAppStaticProps"; import { getAssetURL, ImageQuality } from "helpers/img"; import { filterHasAttributes, getStatusDescription } from "helpers/others"; import { useSmartLanguage } from "hooks/useSmartLanguage"; -import { Fragment, useCallback, useMemo } from "react"; /* * ╭─────────────╮ @@ -73,9 +73,10 @@ export const ScanSetCover = ({
-

+

+ {/* TODO: Add Cover to langui */} {"Cover"}

@@ -91,7 +92,7 @@ export const ScanSetCover = ({
-

{langui.status}:

+

{langui.status}:

0 && (
-

{"Scanners"}:

+ {/* TODO: Add Scanner to langui */} +

{"Scanners"}:

{filterHasAttributes(selectedScan.scanners.data).map( (scanner) => ( @@ -120,7 +122,8 @@ export const ScanSetCover = ({ {selectedScan.cleaners && selectedScan.cleaners.data.length > 0 && (
-

{"Cleaners"}:

+ {/* TODO: Add Cleaners to langui */} +

{"Cleaners"}:

{filterHasAttributes(selectedScan.cleaners.data).map( (cleaner) => ( @@ -139,7 +142,8 @@ export const ScanSetCover = ({ {selectedScan.typesetters && selectedScan.typesetters.data.length > 0 && (
-

{"Typesetters"}:

+ {/* TODO: Add Cleaners to Typesetters */} +

{"Typesetters"}:

{filterHasAttributes(selectedScan.typesetters.data).map( (typesetter) => ( diff --git a/src/components/Markdown/Markdawn.tsx b/src/components/Markdown/Markdawn.tsx index 62fc111..58dadbd 100644 --- a/src/components/Markdown/Markdawn.tsx +++ b/src/components/Markdown/Markdawn.tsx @@ -1,3 +1,7 @@ +import Markdown from "markdown-to-jsx"; +import { useRouter } from "next/router"; +import React, { Fragment, useMemo } from "react"; +import ReactDOMServer from "react-dom/server"; import { HorizontalLine } from "components/HorizontalLine"; import { Ico, Icon } from "components/Ico"; import { Img } from "components/Img"; @@ -10,10 +14,6 @@ import { slugify } from "helpers/formatters"; import { getAssetURL, ImageQuality } from "helpers/img"; import { isDefined, isDefinedAndNotEmpty } from "helpers/others"; import { useLightBox } from "hooks/useLightBox"; -import Markdown from "markdown-to-jsx"; -import { useRouter } from "next/router"; -import React, { Fragment, useMemo } from "react"; -import ReactDOMServer from "react-dom/server"; /* * ╭─────────────╮ @@ -458,7 +458,7 @@ const preprocessMarkDawn = (text: string): string => { .split("\n") .map((line) => { if (line === "* * *" || line === "---") { - scenebreakIndex += 1; + scenebreakIndex++; return ``; } @@ -506,7 +506,7 @@ const markdawnHeadersParser = ( let index = 2; while (visitedSlugs.includes(newSlug)) { newSlug = `${slug}-${index}`; - index += 1; + index++; } visitedSlugs.push(newSlug); return `${lineText}`; @@ -543,7 +543,7 @@ const getTocFromMarkdawn = (text: string, title?: string): TocInterface => { slug: getSlug(line), children: [], }); - h2 += 1; + h2++; h3 = -1; h4 = -1; h5 = -1; @@ -554,7 +554,7 @@ const getTocFromMarkdawn = (text: string, title?: string): TocInterface => { slug: getSlug(line), children: [], }); - h3 += 1; + h3++; h4 = -1; h5 = -1; scenebreak = 0; @@ -564,7 +564,7 @@ const getTocFromMarkdawn = (text: string, title?: string): TocInterface => { slug: getSlug(line), children: [], }); - h4 += 1; + h4++; h5 = -1; scenebreak = 0; } else if (h4 >= 0 && line.startsWith("
{selectedTranslation && (
-

{langui.status}:

+

{langui.status}:

0 && (
-

{"Authors"}:

+

{"Authors"}:

{filterHasAttributes(post.authors.data).map((author) => ( diff --git a/src/components/PreviewCard.tsx b/src/components/PreviewCard.tsx index b6c64b9..3edeae2 100644 --- a/src/components/PreviewCard.tsx +++ b/src/components/PreviewCard.tsx @@ -1,3 +1,8 @@ +import Link from "next/link"; +import { useCallback, useMemo } from "react"; +import { Chip } from "./Chip"; +import { Ico, Icon } from "./Ico"; +import { Img } from "./Img"; import { useAppLayout } from "contexts/AppLayoutContext"; import { DatePickerFragment, @@ -15,11 +20,6 @@ import { } from "helpers/formatters"; import { ImageQuality } from "helpers/img"; import { useSmartLanguage } from "hooks/useSmartLanguage"; -import Link from "next/link"; -import { useCallback, useMemo } from "react"; -import { Chip } from "./Chip"; -import { Ico, Icon } from "./Ico"; -import { Img } from "./Img"; /* * ╭─────────────╮ @@ -212,7 +212,7 @@ export const PreviewCard = ({ >
{pre_title}

)} {title && ( -

+

{title}

)} diff --git a/src/components/PreviewLine.tsx b/src/components/PreviewLine.tsx index 1c674f8..399c0cb 100644 --- a/src/components/PreviewLine.tsx +++ b/src/components/PreviewLine.tsx @@ -1,12 +1,12 @@ +import Link from "next/link"; +import { useCallback } from "react"; +import { Chip } from "./Chip"; +import { Img } from "./Img"; import { UploadImageFragment } from "graphql/generated"; import { AppStaticProps } from "graphql/getAppStaticProps"; import { prettySlug } from "helpers/formatters"; import { ImageQuality } from "helpers/img"; import { useSmartLanguage } from "hooks/useSmartLanguage"; -import Link from "next/link"; -import { useCallback } from "react"; -import { Chip } from "./Chip"; -import { Img } from "./Img"; /* * ╭─────────────╮ @@ -59,7 +59,9 @@ const PreviewLine = ({
{pre_title &&

{pre_title}

} {title && ( -

{title}

+

+ {title} +

)} {subtitle &&

{subtitle}

}
diff --git a/src/components/RecorderChip.tsx b/src/components/RecorderChip.tsx index 100508c..0da17e1 100644 --- a/src/components/RecorderChip.tsx +++ b/src/components/RecorderChip.tsx @@ -1,12 +1,12 @@ +import { Fragment } from "react"; +import { Img } from "./Img"; +import { Markdawn } from "./Markdown/Markdawn"; +import { ToolTip } from "./ToolTip"; import { Chip } from "components/Chip"; import { RecorderChipFragment } from "graphql/generated"; import { AppStaticProps } from "graphql/getAppStaticProps"; import { ImageQuality } from "helpers/img"; import { filterHasAttributes } from "helpers/others"; -import { Fragment } from "react"; -import { Img } from "./Img"; -import { Markdawn } from "./Markdown/Markdawn"; -import { ToolTip } from "./ToolTip"; /* * ╭─────────────╮ diff --git a/src/components/SmartList.tsx b/src/components/SmartList.tsx new file mode 100644 index 0000000..93b3adf --- /dev/null +++ b/src/components/SmartList.tsx @@ -0,0 +1,183 @@ +import { Fragment, useCallback, useEffect, useMemo, useState } from "react"; +import { Chip } from "./Chip"; +import { PageSelector } from "./Inputs/PageSelector"; +import { AppStaticProps } from "graphql/getAppStaticProps"; +import { cJoin } from "helpers/className"; +import { isDefined, isDefinedAndNotEmpty, iterateMap } from "helpers/others"; +import { AnchorIds, useScrollTopOnChange } from "hooks/useScrollTopOnChange"; + +interface Props { + // Items + items: T[]; + getItemId: (item: T) => string; + renderItem: (props: { item: T }) => JSX.Element; + renderWhenEmpty?: () => JSX.Element; + // Pagination + paginationItemPerPage?: number; + paginationSelectorTop?: boolean; + paginationSelectorBottom?: boolean; + paginationScroolTop?: boolean; + // Searching + searchingTerm?: string; + searchingBy?: (item: T) => string; + searchingCaseInsensitive?: boolean; + // Grouping + groupingFunction?: (item: T) => string[]; + groupSortingFunction?: (a: string, b: string) => number; + // Filtering + filteringFunction?: (item: T) => boolean; + // Sorting + sortingFunction?: (a: T, b: T) => number; + // Other + className?: string; + langui: AppStaticProps["langui"]; +} + +export const SmartList = ({ + items, + getItemId, + renderItem: RenderItem, + renderWhenEmpty: RenderWhenEmpty, + paginationItemPerPage = 0, + paginationSelectorTop = true, + paginationSelectorBottom = true, + paginationScroolTop = true, + searchingTerm, + searchingBy, + searchingCaseInsensitive = true, + groupingFunction = () => [""], + groupSortingFunction = (a, b) => a.localeCompare(b), + filteringFunction = () => true, + sortingFunction = () => 0, + className, + langui, +}: Props): JSX.Element => { + const [page, setPage] = useState(0); + useScrollTopOnChange(AnchorIds.ContentPanel, [page], paginationScroolTop); + + type Group = Map; + + useEffect(() => setPage(0), [searchingTerm]); + + const searchFilter = useCallback(() => { + if (isDefinedAndNotEmpty(searchingTerm) && isDefined(searchingBy)) { + if (searchingCaseInsensitive) { + return items.filter((item) => + searchingBy(item).toLowerCase().includes(searchingTerm.toLowerCase()) + ); + } + return items.filter((item) => searchingBy(item).includes(searchingTerm)); + } + return items; + }, [items, searchingBy, searchingCaseInsensitive, searchingTerm]); + + const filteredItems = useMemo(() => { + const filteredBySearch = searchFilter(); + return filteredBySearch.filter(filteringFunction); + }, [filteringFunction, searchFilter]); + + const sortedItem = useMemo( + () => filteredItems.sort(sortingFunction), + [filteredItems, sortingFunction] + ); + + const paginatedItems = useMemo(() => { + if (paginationItemPerPage > 0) { + const memo = []; + for ( + let index = 0; + paginationItemPerPage * index < sortedItem.length; + index++ + ) { + memo.push( + sortedItem.slice( + index * paginationItemPerPage, + (index + 1) * paginationItemPerPage + ) + ); + } + return memo; + } + return [sortedItem]; + }, [paginationItemPerPage, sortedItem]); + + const groupedList = useMemo(() => { + const groups: Group = new Map(); + paginatedItems[page]?.forEach((item) => { + groupingFunction(item).forEach((category) => { + if (groups.has(category)) { + groups.get(category)?.push(item); + } else { + groups.set(category, [item]); + } + }); + }); + return groups; + }, [groupingFunction, page, paginatedItems]); + + const pageCount = useMemo( + () => + paginationItemPerPage > 0 + ? Math.floor(filteredItems.length / paginationItemPerPage) + : 1, + [paginationItemPerPage, filteredItems.length] + ); + + return ( + <> + {pageCount > 1 && paginationSelectorTop && ( + + )} + + {groupedList.size > 0 + ? iterateMap( + groupedList, + (name, groupItems) => + groupItems.length > 0 && ( + + {name.length > 0 && ( +

+ {name} + {`${groupItems.length} ${ + groupItems.length <= 1 + ? langui.result?.toLowerCase() ?? "" + : langui.results?.toLowerCase() ?? "" + }`} +

+ )} +
+ {groupItems.map((item) => ( + + ))} +
+
+ ), + ([a], [b]) => groupSortingFunction(a, b) + ) + : isDefined(RenderWhenEmpty) && } + + {pageCount > 1 && paginationSelectorBottom && ( + + )} + + ); +}; diff --git a/src/components/ToolTip.tsx b/src/components/ToolTip.tsx index be6c154..577d23c 100644 --- a/src/components/ToolTip.tsx +++ b/src/components/ToolTip.tsx @@ -1,3 +1,4 @@ +// eslint-disable-next-line import/named import Tippy, { TippyProps } from "@tippyjs/react"; import { cJoin } from "helpers/className"; import "tippy.js/animations/scale-subtle.css"; diff --git a/src/components/Wiki/Chronology/ChronologyItemComponent.tsx b/src/components/Wiki/Chronology/ChronologyItemComponent.tsx index 9fe92d2..7399ffc 100644 --- a/src/components/Wiki/Chronology/ChronologyItemComponent.tsx +++ b/src/components/Wiki/Chronology/ChronologyItemComponent.tsx @@ -1,3 +1,4 @@ +import { Fragment } from "react"; import { Chip } from "components/Chip"; import { Ico, Icon } from "components/Ico"; import { ToolTip } from "components/ToolTip"; @@ -12,7 +13,10 @@ import { getStatusDescription, } from "helpers/others"; -import { Fragment } from "react"; +/* + * ╭─────────────╮ + * ───────────────────────────────────────╯ COMPONENT ╰─────────────────────────────────────────── + */ interface Props { item: NonNullable["data"][number]; @@ -20,6 +24,8 @@ interface Props { langui: AppStaticProps["langui"]; } +// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ + export const ChronologyItemComponent = ({ langui, item, @@ -124,6 +130,11 @@ export const ChronologyItemComponent = ({ return <>; }; +/* + * ╭───────────────────╮ + * ─────────────────────────────────────╯ PRIVATE METHODS ╰─────────────────────────────────────── + */ + const generateAnchor = ( year: number | undefined, month: number | null | undefined, diff --git a/src/components/Wiki/Chronology/ChronologyYearComponent.tsx b/src/components/Wiki/Chronology/ChronologyYearComponent.tsx index 30b710c..e9777bc 100644 --- a/src/components/Wiki/Chronology/ChronologyYearComponent.tsx +++ b/src/components/Wiki/Chronology/ChronologyYearComponent.tsx @@ -2,6 +2,11 @@ import { ChronologyItemComponent } from "components/Wiki/Chronology/ChronologyIt import { GetChronologyItemsQuery } from "graphql/generated"; import { AppStaticProps } from "graphql/getAppStaticProps"; +/* + * ╭─────────────╮ + * ───────────────────────────────────────╯ COMPONENT ╰─────────────────────────────────────────── + */ + interface Props { year: number; items: NonNullable< @@ -10,6 +15,8 @@ interface Props { langui: AppStaticProps["langui"]; } +// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ + export const ChronologyYearComponent = ({ langui, year, diff --git a/src/components/Wiki/DefinitionCard.tsx b/src/components/Wiki/DefinitionCard.tsx index 287122c..92912ec 100644 --- a/src/components/Wiki/DefinitionCard.tsx +++ b/src/components/Wiki/DefinitionCard.tsx @@ -1,9 +1,14 @@ +import { useCallback } from "react"; import { Chip } from "components/Chip"; import { ToolTip } from "components/ToolTip"; import { AppStaticProps } from "graphql/getAppStaticProps"; import { getStatusDescription } from "helpers/others"; import { useSmartLanguage } from "hooks/useSmartLanguage"; -import { useCallback } from "react"; + +/* + * ╭─────────────╮ + * ───────────────────────────────────────╯ COMPONENT ╰─────────────────────────────────────────── + */ interface Props { source?: string; @@ -15,14 +20,18 @@ interface Props { languages: AppStaticProps["languages"]; langui: AppStaticProps["langui"]; index: number; + categories: string[]; } +// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ + const DefinitionCard = ({ source, translations = [], languages, langui, index, + categories, }: Props): JSX.Element => { const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] = useSmartLanguage({ @@ -36,24 +45,51 @@ const DefinitionCard = ({ return ( <> -
-

{`${langui.definition} ${index}`}

- {selectedTranslation?.status && ( - - {selectedTranslation.status} - - )} +
+

{`${langui.definition} ${index}`}

+ {translations.length > 1 && ( - + <> + + + + )} + + {selectedTranslation?.status && ( + <> + + + {selectedTranslation.status} + + + )} + + {categories.length > 0 && ( + <> + +
+ {categories.map((category, categoryIndex) => ( + {category} + ))} +
+ )}

{`${langui.source}: ${source}`}

+

{selectedTranslation?.definition}

); }; export default DefinitionCard; + +/* + * ╭──────────────────────╮ + * ───────────────────────────────────╯ PRIVATE COMPONENTS ╰────────────────────────────────────── + */ + +const Separator = () =>
; diff --git a/src/contexts/AppLayoutContext.tsx b/src/contexts/AppLayoutContext.tsx index 5002935..27f4017 100644 --- a/src/contexts/AppLayoutContext.tsx +++ b/src/contexts/AppLayoutContext.tsx @@ -1,10 +1,10 @@ +import React, { ReactNode, useContext, useState } from "react"; import { isDefined } from "helpers/others"; import { LibraryItemUserStatus, RequiredNonNullable } from "helpers/types"; import { useDarkMode } from "hooks/useDarkMode"; import { useStateWithLocalStorage } from "hooks/useStateWithLocalStorage"; -import React, { ReactNode, useContext, useState } from "react"; -export interface AppLayoutState { +interface AppLayoutState { subPanelOpen: boolean | undefined; toggleSubPanelOpen: () => void; setSubPanelOpen: React.Dispatch< diff --git a/src/graphql/getAppStaticProps.ts b/src/graphql/getAppStaticProps.ts index ee040d9..c73367c 100644 --- a/src/graphql/getAppStaticProps.ts +++ b/src/graphql/getAppStaticProps.ts @@ -1,3 +1,4 @@ +import { GetStaticPropsContext } from "next"; import { GetCurrenciesQuery, GetLanguagesQuery, @@ -5,8 +6,6 @@ import { } from "graphql/generated"; import { getReadySdk } from "graphql/sdk"; -import { GetStaticPropsContext } from "next"; - export type AppStaticProps = { langui: NonNullable< NonNullable< @@ -21,7 +20,7 @@ export const getAppStaticProps = async ( context: GetStaticPropsContext ): Promise => { const sdk = getReadySdk(); - const languages = (await sdk.getLanguages()).languages; + const { languages } = await sdk.getLanguages(); if (languages?.data) { languages.data.sort((a, b) => @@ -31,7 +30,7 @@ export const getAppStaticProps = async ( ); } - const currencies = (await sdk.getCurrencies()).currencies; + const { currencies } = await sdk.getCurrencies(); if (currencies?.data) { currencies.data.sort((a, b) => a.attributes && b.attributes diff --git a/src/graphql/getPostStaticProps.ts b/src/graphql/getPostStaticProps.ts index 97b5a46..2e86c2d 100644 --- a/src/graphql/getPostStaticProps.ts +++ b/src/graphql/getPostStaticProps.ts @@ -1,14 +1,15 @@ -import { PostWithTranslations } from "helpers/types"; -import { GetStaticProps, GetStaticPropsContext } from "next"; +import { GetStaticProps } from "next"; import { AppStaticProps, getAppStaticProps } from "./getAppStaticProps"; import { getReadySdk } from "./sdk"; +import { PostWithTranslations } from "helpers/types"; export interface PostStaticProps extends AppStaticProps { post: PostWithTranslations; } -export const getPostStaticProps = (slug: string): GetStaticProps => { - return async (context) => { +export const getPostStaticProps = + (slug: string): GetStaticProps => + async (context) => { const sdk = getReadySdk(); const post = await sdk.getPost({ slug: slug, @@ -25,4 +26,3 @@ export const getPostStaticProps = (slug: string): GetStaticProps => { } return { notFound: true }; }; -}; diff --git a/src/graphql/operations/getPostsPreview.graphql b/src/graphql/operations/getPostsPreview.graphql index 2d083f2..c9db9eb 100644 --- a/src/graphql/operations/getPostsPreview.graphql +++ b/src/graphql/operations/getPostsPreview.graphql @@ -1,4 +1,4 @@ -query getPostsPreview($language_code: String) { +query getPostsPreview { posts(filters: { hidden: { eq: false } }) { data { id @@ -22,7 +22,14 @@ query getPostsPreview($language_code: String) { } } } - translations(filters: { language: { code: { eq: $language_code } } }) { + translations { + language { + data { + attributes { + code + } + } + } title excerpt thumbnail { diff --git a/src/graphql/operations/getWikiPage.graphql b/src/graphql/operations/getWikiPage.graphql index 2ba3a3f..cc3a8e1 100644 --- a/src/graphql/operations/getWikiPage.graphql +++ b/src/graphql/operations/getWikiPage.graphql @@ -77,6 +77,15 @@ query getWikiPage($slug: String, $language_code: String) { } } } + categories { + data { + id + attributes { + name + short + } + } + } translations { language { data { @@ -85,7 +94,6 @@ query getWikiPage($slug: String, $language_code: String) { } } } - source_language { data { attributes { diff --git a/src/graphql/sdk.ts b/src/graphql/sdk.ts index 190df8b..553a597 100644 --- a/src/graphql/sdk.ts +++ b/src/graphql/sdk.ts @@ -1,7 +1,7 @@ import { GraphQLClient } from "graphql-request"; import { getSdk } from "graphql/generated"; -export const getReadySdk = () => { +export const getReadySdk = (): ReturnType => { const client = new GraphQLClient(process.env.URL_GRAPHQL ?? "", { headers: { Authorization: `Bearer ${process.env.ACCESS_TOKEN}` }, }); diff --git a/src/helpers/className.ts b/src/helpers/className.ts index d7473de..956bb36 100644 --- a/src/helpers/className.ts +++ b/src/helpers/className.ts @@ -1,11 +1,8 @@ export const cIf = ( - condition: boolean | null | undefined | string, + condition: boolean | string | null | undefined, ifTrueCss: string, ifFalseCss?: string -) => { - return condition ? ifTrueCss : ifFalseCss ?? ""; -}; +): string => (condition ? ifTrueCss : ifFalseCss ?? ""); -export const cJoin = (...args: (string | undefined)[]): string => { - return args.filter((elem) => elem).join(" "); -}; +export const cJoin = (...args: (string | undefined)[]): string => + args.filter((elem) => elem).join(" "); diff --git a/src/helpers/description.ts b/src/helpers/description.ts index 03ee88c..d713c2d 100644 --- a/src/helpers/description.ts +++ b/src/helpers/description.ts @@ -1,7 +1,7 @@ -import { AppStaticProps } from "graphql/getAppStaticProps"; import { prettySlug } from "./formatters"; import { isDefined } from "./others"; import { Content } from "./types"; +import { AppStaticProps } from "graphql/getAppStaticProps"; interface Description { langui: AppStaticProps["langui"]; @@ -49,13 +49,11 @@ export const getDescription = ({ return description; }; -const prettyMarkdown = (markdown: string): string => { - return markdown.replace(/[*]/gu, "").replace(/[_]/gu, ""); -}; +const prettyMarkdown = (markdown: string): string => + markdown.replace(/[*]/gu, "").replace(/[_]/gu, ""); -const prettyChip = (items: (string | undefined)[]): string => { - return items +const prettyChip = (items: (string | undefined)[]): string => + items .filter((item) => isDefined(item)) .map((item) => `(${item})`) .join(" "); -}; diff --git a/src/helpers/formatters.ts b/src/helpers/formatters.ts index 11412e2..bb9ee56 100644 --- a/src/helpers/formatters.ts +++ b/src/helpers/formatters.ts @@ -1,6 +1,7 @@ -import { DatePickerFragment, PricePickerFragment } from "graphql/generated"; import { AppStaticProps } from "../graphql/getAppStaticProps"; import { convertPrice } from "./numbers"; +import { isDefinedAndNotEmpty } from "./others"; +import { DatePickerFragment, PricePickerFragment } from "graphql/generated"; export const prettyDate = (datePicker: DatePickerFragment): string => { let result = ""; @@ -20,7 +21,7 @@ export const prettyPrice = ( if (!targetCurrencyCode) return ""; let result = ""; currencies.map((currency) => { - if (currency?.attributes?.code === targetCurrencyCode) { + if (currency.attributes?.code === targetCurrencyCode) { const amountInTargetCurrency = convertPrice(pricePicker, currency); result = currency.attributes.symbol + @@ -34,20 +35,20 @@ export const prettyPrice = ( }; export const prettySlug = (slug?: string, parentSlug?: string): string => { - if (slug) { - if (parentSlug && slug.startsWith(parentSlug)) - slug = slug.substring(parentSlug.length + 1); - slug = slug.replace(new RegExp("-", "g"), " "); - slug = slug.replace(new RegExp("_", "g"), " "); - return capitalizeString(slug); + let newSlug = slug; + if (newSlug) { + if (isDefinedAndNotEmpty(parentSlug) && newSlug.startsWith(parentSlug)) + newSlug = newSlug.substring(parentSlug.length + 1); + newSlug = newSlug.replaceAll("-", " "); + return capitalizeString(newSlug); } return ""; }; export const prettyinlineTitle = ( - pretitle: string | undefined | null, - title: string | undefined | null, - subtitle: string | undefined | null + pretitle: string | null | undefined, + title: string | null | undefined, + subtitle: string | null | undefined ): string => { let result = ""; if (pretitle) result += `${pretitle}: `; @@ -59,7 +60,7 @@ export const prettyinlineTitle = ( export const prettyItemType = ( metadata: any, langui: AppStaticProps["langui"] -): string | undefined | null => { +): string | null | undefined => { switch (metadata.__typename) { case "ComponentMetadataAudio": return langui.audio; @@ -78,6 +79,7 @@ export const prettyItemType = ( } }; +/* eslint-disable id-denylist */ export const prettyItemSubType = ( metadata: | { @@ -86,9 +88,11 @@ export const prettyItemSubType = ( data?: { attributes?: { slug: string; - titles?: Array<{ - title: string; - } | null> | null; + titles?: + | ({ + title: string; + } | null)[] + | null; } | null; } | null; } | null; @@ -99,9 +103,11 @@ export const prettyItemSubType = ( data?: { attributes?: { slug: string; - titles?: Array<{ - title: string; - } | null> | null; + titles?: + | ({ + title: string; + } | null)[] + | null; } | null; } | null; } | null; @@ -109,12 +115,12 @@ export const prettyItemSubType = ( | { __typename: "ComponentMetadataGame"; platforms?: { - data: Array<{ + data: { id?: string | null; attributes?: { short: string; } | null; - }>; + }[]; } | null; } | { @@ -123,9 +129,11 @@ export const prettyItemSubType = ( data?: { attributes?: { slug: string; - titles?: Array<{ - title: string; - } | null> | null; + titles?: + | ({ + title: string; + } | null)[] + | null; } | null; } | null; } | null; @@ -133,27 +141,31 @@ export const prettyItemSubType = ( data?: { attributes?: { slug: string; - titles?: Array<{ - title: string; - } | null> | null; + titles?: + | ({ + title: string; + } | null)[] + | null; } | null; } | null; } | null; } - | { __typename: "ComponentMetadataOther" } | { __typename: "ComponentMetadataVideo"; subtype?: { data?: { attributes?: { slug: string; - titles?: Array<{ - title: string; - } | null> | null; + titles?: + | ({ + title: string; + } | null)[] + | null; } | null; } | null; } | null; } + | { __typename: "ComponentMetadataOther" } | { __typename: "Error" } | null ): string => { @@ -163,27 +175,27 @@ export const prettyItemSubType = ( case "ComponentMetadataBooks": case "ComponentMetadataVideo": return metadata.subtype?.data?.attributes?.titles && - metadata.subtype?.data?.attributes?.titles.length > 0 && + metadata.subtype.data.attributes.titles.length > 0 && metadata.subtype.data.attributes.titles[0] ? metadata.subtype.data.attributes.titles[0].title : prettySlug(metadata.subtype?.data?.attributes?.slug); case "ComponentMetadataGame": return metadata.platforms?.data && - metadata.platforms?.data.length > 0 && + metadata.platforms.data.length > 0 && metadata.platforms.data[0].attributes ? metadata.platforms.data[0].attributes.short : ""; case "ComponentMetadataGroup": { const firstPart = metadata.subtype?.data?.attributes?.titles && - metadata.subtype?.data?.attributes?.titles.length > 0 && + metadata.subtype.data.attributes.titles.length > 0 && metadata.subtype.data.attributes.titles[0] ? metadata.subtype.data.attributes.titles[0].title : prettySlug(metadata.subtype?.data?.attributes?.slug); const secondPart = metadata.subitems_type?.data?.attributes?.titles && - metadata.subitems_type?.data?.attributes?.titles.length > 0 && + metadata.subitems_type.data.attributes.titles.length > 0 && metadata.subitems_type.data.attributes.titles[0] ? metadata.subitems_type.data.attributes.titles[0].title : prettySlug(metadata.subitems_type?.data?.attributes?.slug); @@ -194,8 +206,8 @@ export const prettyItemSubType = ( } } return ""; - /* eslint-enable @typescript-eslint/no-explicit-any */ }; +/* eslint-enable id-denylist */ export const prettyShortenNumber = (number: number): string => { if (number > 1000000) { @@ -203,11 +215,9 @@ export const prettyShortenNumber = (number: number): string => { maximumSignificantDigits: 3, }); } else if (number > 1000) { - return ( - (number / 1000).toLocaleString(undefined, { - maximumSignificantDigits: 2, - }) + "K" - ); + return `${(number / 1000).toLocaleString(undefined, { + maximumSignificantDigits: 2, + })}K`; } return number.toLocaleString(); }; @@ -215,18 +225,19 @@ export const prettyShortenNumber = (number: number): string => { export const prettyDuration = (seconds: number): string => { let hours = 0; let minutes = 0; - while (seconds > 60) { - minutes += 1; - seconds -= 60; + let remainingSeconds = seconds; + while (remainingSeconds > 60) { + minutes++; + remainingSeconds -= 60; } while (minutes > 60) { - hours += 1; + hours++; minutes -= 60; } let result = ""; - if (hours) result += hours.toString().padStart(2, "0") + ":"; - result += minutes.toString().padStart(2, "0") + ":"; - result += seconds.toString().padStart(2, "0"); + if (hours) result += `${hours.toString().padStart(2, "0")}:`; + result += `${minutes.toString().padStart(2, "0")}:`; + result += remainingSeconds.toString().padStart(2, "0"); return result; }; @@ -236,21 +247,20 @@ export const prettyLanguage = ( ): string => { let result = code; languages.forEach((language) => { - if (language?.attributes?.code === code) + if (language.attributes?.code === code) result = language.attributes.localized_name; }); return result; }; export const prettyURL = (url: string): string => { - let domain = new URL(url); + const domain = new URL(url); return domain.hostname.replace("www.", ""); }; const capitalizeString = (string: string): string => { - const capitalizeWord = (word: string): string => { - return word.charAt(0).toUpperCase() + word.substring(1); - }; + const capitalizeWord = (word: string): string => + word.charAt(0).toUpperCase() + word.substring(1); let words = string.split(" "); words = words.map((word) => capitalizeWord(word)); @@ -262,7 +272,7 @@ export const slugify = (string: string | undefined): string => { return ""; } return string - .replace(/[ÀÁÂÃÄÅàáâãäåæÆ]/g, "a") + .replace(/[ÀÁÂÃÄÅàáâãäåæÆ]/gu, "a") .replace(/[çÇ]/gu, "c") .replace(/[ðÐ]/gu, "d") .replace(/[ÈÉÊËéèêë]/gu, "e") diff --git a/src/helpers/img.ts b/src/helpers/img.ts index 089e6c9..0519ae4 100644 --- a/src/helpers/img.ts +++ b/src/helpers/img.ts @@ -73,12 +73,12 @@ export const getOgImage = ( const imgSize = getImgSizesByQuality( image.width ?? 0, image.height ?? 0, - quality ? quality : ImageQuality.Small + quality ); return { image: getAssetURL(image.url, quality), width: imgSize.width, height: imgSize.height, - alt: image.alternativeText || "", + alt: image.alternativeText ?? "", }; }; diff --git a/src/helpers/libraryItem.ts b/src/helpers/libraryItem.ts index 1061a5b..f051438 100644 --- a/src/helpers/libraryItem.ts +++ b/src/helpers/libraryItem.ts @@ -1,252 +1,5 @@ -import { AppLayoutState } from "contexts/AppLayoutContext"; -import { GetLibraryItemsPreviewQuery } from "graphql/generated"; -import { AppStaticProps } from "graphql/getAppStaticProps"; -import { prettyinlineTitle, prettyDate } from "./formatters"; -import { convertPrice } from "./numbers"; -import { isDefined, mapRemoveEmptyValues } from "./others"; -import { LibraryItemUserStatus } from "./types"; -import LibraryPage from "../pages/library/index"; - -type Items = Parameters[0]["items"]; -type GroupLibraryItems = Map; - -export const getGroups = ( - langui: AppStaticProps["langui"], - groupByType: number, - items: Items -): GroupLibraryItems => { - const groups: GroupLibraryItems = new Map(); - - switch (groupByType) { - case 0: { - const noCategory = langui.no_category ?? "No category"; - groups.set("Drakengard 1", []); - groups.set("Drakengard 1.3", []); - groups.set("Drakengard 2", []); - groups.set("Drakengard 3", []); - groups.set("Drakengard 4", []); - groups.set("NieR Gestalt", []); - groups.set("NieR Replicant", []); - groups.set("NieR Replicant ver.1.22474487139...", []); - groups.set("NieR:Automata", []); - groups.set("NieR Re[in]carnation", []); - groups.set("SINoALICE", []); - groups.set("Voice of Cards", []); - groups.set("Final Fantasy XIV", []); - groups.set("Thou Shalt Not Die", []); - groups.set("Bakuken", []); - groups.set("YoRHa", []); - groups.set("YoRHa Boys", []); - groups.set(noCategory, []); - - items.map((item) => { - if (item.attributes?.categories?.data.length === 0) { - groups.get(noCategory)?.push(item); - } else { - item.attributes?.categories?.data.map((category) => { - groups.get(category.attributes?.name ?? noCategory)?.push(item); - }); - } - }); - break; - } - - case 1: { - groups.set(langui.audio ?? "Audio", []); - groups.set(langui.game ?? "Game", []); - groups.set(langui.textual ?? "Textual", []); - groups.set(langui.video ?? "Video", []); - groups.set(langui.other ?? "Other", []); - groups.set(langui.group ?? "Group", []); - groups.set(langui.no_type ?? "No type", []); - items.map((item) => { - if (item.attributes?.metadata && item.attributes.metadata.length > 0) { - switch (item.attributes.metadata[0]?.__typename) { - case "ComponentMetadataAudio": - groups.get(langui.audio ?? "Audio")?.push(item); - break; - case "ComponentMetadataGame": - groups.get(langui.game ?? "Game")?.push(item); - break; - case "ComponentMetadataBooks": - groups.get(langui.textual ?? "Textual")?.push(item); - break; - case "ComponentMetadataVideo": - groups.get(langui.video ?? "Video")?.push(item); - break; - case "ComponentMetadataOther": - groups.get(langui.other ?? "Other")?.push(item); - break; - case "ComponentMetadataGroup": - switch ( - item.attributes.metadata[0]?.subitems_type?.data?.attributes - ?.slug - ) { - case "audio": - groups.get(langui.audio ?? "Audio")?.push(item); - break; - case "video": - groups.get(langui.video ?? "Video")?.push(item); - break; - case "game": - groups.get(langui.game ?? "Game")?.push(item); - break; - case "textual": - groups.get(langui.textual ?? "Textual")?.push(item); - break; - case "mixed": - groups.get(langui.group ?? "Group")?.push(item); - break; - default: { - throw new Error( - "An unexpected subtype of group-metadata was given" - ); - } - } - break; - default: { - throw new Error("An unexpected type of metadata was given"); - } - } - } else { - groups.get(langui.no_type ?? "No type")?.push(item); - } - }); - break; - } - - case 2: { - const years: number[] = []; - items.map((item) => { - if (item.attributes?.release_date?.year) { - if (!years.includes(item.attributes.release_date.year)) - years.push(item.attributes.release_date.year); - } - }); - - years.sort((a, b) => a - b); - years.map((year) => { - groups.set(year.toString(), []); - }); - groups.set(langui.no_year ?? "No year", []); - items.map((item) => { - if (item.attributes?.release_date?.year) { - groups.get(item.attributes.release_date.year.toString())?.push(item); - } else { - groups.get(langui.no_year ?? "No year")?.push(item); - } - }); - break; - } - - default: { - groups.set("", items); - break; - } - } - return mapRemoveEmptyValues(groups); -}; - -export const filterItems = ( - appLayout: AppLayoutState, - items: Items, - searchName: string, - showSubitems: boolean, - showPrimaryItems: boolean, - showSecondaryItems: boolean, - filterUserStatus: LibraryItemUserStatus | undefined -): Items => { - return items.filter((item) => { - if (!showSubitems && !item.attributes?.root_item) return false; - if (showSubitems && isUntangibleGroupItem(item.attributes?.metadata?.[0])) { - return false; - } - if (item.attributes?.primary && !showPrimaryItems) return false; - if (!item.attributes?.primary && !showSecondaryItems) return false; - - if ( - searchName.length > 1 && - !prettyinlineTitle("", item.attributes?.title, item.attributes?.subtitle) - .toLowerCase() - .includes(searchName.toLowerCase()) - ) { - return false; - } - - if ( - isDefined(filterUserStatus) && - item.id && - appLayout.libraryItemUserStatus - ) { - if (isUntangibleGroupItem(item.attributes?.metadata?.[0])) { - return false; - } - if (filterUserStatus === LibraryItemUserStatus.None) { - if (appLayout.libraryItemUserStatus[item.id]) { - return false; - } - } else if ( - filterUserStatus !== appLayout.libraryItemUserStatus[item.id] - ) { - return false; - } - } - - return true; - }); -}; - -// TODO: Properly type this shit -export const isUntangibleGroupItem = (metadata: any) => { - return ( - metadata && - metadata.__typename === "ComponentMetadataGroup" && - (metadata.subtype?.data?.attributes?.slug === "variant-set" || - metadata.subtype?.data?.attributes?.slug === "relation-set") - ); -}; - -export const sortBy = ( - orderByType: number, - items: Items, - currencies: AppStaticProps["currencies"] -) => { - switch (orderByType) { - case 0: - return items.sort((a, b) => { - const titleA = prettyinlineTitle( - "", - a.attributes?.title, - a.attributes?.subtitle - ); - const titleB = prettyinlineTitle( - "", - b.attributes?.title, - b.attributes?.subtitle - ); - return titleA.localeCompare(titleB); - }); - case 1: - return items.sort((a, b) => { - const priceA = a.attributes?.price - ? convertPrice(a.attributes.price, currencies[0]) - : 99999; - const priceB = b.attributes?.price - ? convertPrice(b.attributes.price, currencies[0]) - : 99999; - return priceA - priceB; - }); - case 2: - return items.sort((a, b) => { - const dateA = a.attributes?.release_date - ? prettyDate(a.attributes.release_date) - : "9999"; - const dateB = b.attributes?.release_date - ? prettyDate(b.attributes.release_date) - : "9999"; - return dateA.localeCompare(dateB); - }); - default: - return items; - } -}; +export const isUntangibleGroupItem = (metadata: any): boolean => + metadata && + metadata.__typename === "ComponentMetadataGroup" && + (metadata.subtype?.data?.attributes?.slug === "variant-set" || + metadata.subtype?.data?.attributes?.slug === "relation-set"); diff --git a/src/helpers/numbers.ts b/src/helpers/numbers.ts index cf14369..740bf0b 100644 --- a/src/helpers/numbers.ts +++ b/src/helpers/numbers.ts @@ -16,15 +16,11 @@ export const convertPrice = ( return 0; }; -export const convertMmToInch = (mm: number | null | undefined): string => { - return mm ? (mm * 0.03937008).toPrecision(3) : ""; -}; +export const convertMmToInch = (mm: number | null | undefined): string => + mm ? (mm * 0.03937008).toPrecision(3) : ""; -export const randomInt = (min: number, max: number) => { - return Math.floor(Math.random() * (max - min)) + min; -}; +export const randomInt = (min: number, max: number): number => + Math.floor(Math.random() * (max - min)) + min; -export const isInteger = (value: string): boolean => { - // eslint-disable-next-line require-unicode-regexp - return /^[+-]?[0-9]+$/.test(value); -}; +export const isInteger = (value: string): boolean => + /^[+-]?[0-9]+$/u.test(value); diff --git a/src/helpers/others.ts b/src/helpers/others.ts index c4b8ab3..a836541 100644 --- a/src/helpers/others.ts +++ b/src/helpers/others.ts @@ -1,12 +1,12 @@ +import { AppStaticProps } from "../graphql/getAppStaticProps"; +import { SelectiveRequiredNonNullable } from "./types"; import { Enum_Componentsetstextset_Status, GetLibraryItemQuery, GetLibraryItemScansQuery, } from "graphql/generated"; -import { AppStaticProps } from "../graphql/getAppStaticProps"; -import { SelectiveRequiredNonNullable } from "./types"; -type SortContentProps = +type SortRangedContentProps = | NonNullable< NonNullable< GetLibraryItemQuery["libraryItems"] @@ -18,7 +18,7 @@ type SortContentProps = >["data"][number]["attributes"] >["contents"]; -export const sortContent = (contents: SortContentProps) => { +export const sortRangedContent = (contents: SortRangedContentProps): void => { contents?.data.sort((a, b) => { if ( a.attributes?.range[0]?.__typename === "ComponentRangePageRange" && @@ -59,20 +59,20 @@ export const isDefined = (t: T): t is NonNullable => t !== null && t !== undefined; export const isUndefined = ( - t: T | undefined | null -): t is undefined | null => t === null || t === undefined; + t: T | null | undefined +): t is null | undefined => t === null || t === undefined; export const isDefinedAndNotEmpty = ( - string: string | undefined | null + string: string | null | undefined ): string is string => isDefined(string) && string.length > 0; -export const filterDefined = (t: T[] | undefined | null): NonNullable[] => +export const filterDefined = (t: T[] | null | undefined): NonNullable[] => isUndefined(t) ? [] : (t.filter((item) => isDefined(item)) as NonNullable[]); export const filterHasAttributes = >( - t: T[] | undefined | null, + t: T[] | null | undefined, attributes?: P[] ): SelectiveRequiredNonNullable, P>[] => isUndefined(t) @@ -89,27 +89,24 @@ export const filterHasAttributes = >( export const iterateMap = ( map: Map, - callbackfn: (key: K, value: V, index: number) => U + callbackfn: (key: K, value: V, index: number) => U, + sortingFunction?: (a: [K, V], b: [K, V]) => number ): U[] => { - const result: U[] = []; - let index = 0; - for (const [key, value] of map.entries()) { - result.push(callbackfn(key, value, index)); - index += 1; + const toList = [...map]; + if (isDefined(sortingFunction)) { + toList.sort(sortingFunction); + console.log(toList.sort(sortingFunction)); } - return result; + return toList.map(([key, value], index) => callbackfn(key, value, index)); }; export const mapMoveEntry = ( map: Map, sourceIndex: number, targetIndex: number -) => new Map(arrayMove([...map], sourceIndex, targetIndex)); +): Map => new Map(arrayMove([...map], sourceIndex, targetIndex)); const arrayMove = (arr: T[], sourceIndex: number, targetIndex: number) => { arr.splice(targetIndex, 0, arr.splice(sourceIndex, 1)[0]); return arr; }; - -export const mapRemoveEmptyValues = (groups: Map): Map => - new Map([...groups].filter(([_, items]) => items.length > 0)); diff --git a/src/hooks/useLightBox.tsx b/src/hooks/useLightBox.tsx index 4730b11..2679d57 100644 --- a/src/hooks/useLightBox.tsx +++ b/src/hooks/useLightBox.tsx @@ -1,5 +1,5 @@ -import { LightBox } from "components/LightBox"; import { useState } from "react"; +import { LightBox } from "components/LightBox"; export const useLightBox = (): [ (images: string[], index?: number) => void, diff --git a/src/hooks/useMediaQuery.ts b/src/hooks/useMediaQuery.ts index df888e6..4beacaa 100644 --- a/src/hooks/useMediaQuery.ts +++ b/src/hooks/useMediaQuery.ts @@ -1,20 +1,20 @@ -import { useEffect, useState } from "react"; +import { useCallback, useEffect, useState } from "react"; import { breaks } from "../../design.config"; const useMediaQuery = (query: string): boolean => { - const getMatches = (query: string): boolean => { + const getMatches = useCallback((): boolean => { // Prevents SSR issues if (typeof window !== "undefined") { return window.matchMedia(query).matches; } return false; - }; + }, [query]); - const [matches, setMatches] = useState(getMatches(query)); + const [matches, setMatches] = useState(getMatches()); useEffect(() => { const handleChange = () => { - setMatches(getMatches(query)); + setMatches(getMatches()); }; const matchMedia = window.matchMedia(query); @@ -28,19 +28,19 @@ const useMediaQuery = (query: string): boolean => { return () => { matchMedia.removeEventListener("change", handleChange); }; - }, [query]); + }, [getMatches, query]); return matches; }; // ts-unused-exports:disable-next-line -export const useMediaThin = () => useMediaQuery(breaks.thin.raw); +export const useMediaThin = (): boolean => useMediaQuery(breaks.thin.raw); -export const useMediaMobile = () => useMediaQuery(breaks.mobile.raw); +export const useMediaMobile = (): boolean => useMediaQuery(breaks.mobile.raw); -export const useMediaDesktop = () => useMediaQuery(breaks.desktop.raw); +export const useMediaDesktop = (): boolean => useMediaQuery(breaks.desktop.raw); -export const useMediaHoverable = () => useMediaQuery("(hover: hover)"); +export const useMediaHoverable = (): boolean => useMediaQuery("(hover: hover)"); -export const usePrefersDarkMode = () => +export const usePrefersDarkMode = (): boolean => useMediaQuery("(prefers-color-scheme: dark)"); diff --git a/src/hooks/useScrollTopOnChange.ts b/src/hooks/useScrollTopOnChange.ts index 26e06f6..ca8af3a 100644 --- a/src/hooks/useScrollTopOnChange.ts +++ b/src/hooks/useScrollTopOnChange.ts @@ -5,8 +5,15 @@ export enum AnchorIds { } // Scroll to top of element "id" when "deps" update. -export const useScrollTopOnChange = (id: AnchorIds, deps: DependencyList) => { +export const useScrollTopOnChange = ( + id: AnchorIds, + deps: DependencyList, + enabled = true +): void => { useEffect(() => { - document.querySelector(`#${id}`)?.scrollTo({ top: 0, behavior: "smooth" }); - }, deps); + if (enabled) + document + .querySelector(`#${id}`) + ?.scrollTo({ top: 0, behavior: "smooth" }); + }, [id, deps, enabled]); }; diff --git a/src/hooks/useSmartLanguage.ts b/src/hooks/useSmartLanguage.ts index b38d2db..392a7f4 100644 --- a/src/hooks/useSmartLanguage.ts +++ b/src/hooks/useSmartLanguage.ts @@ -1,11 +1,10 @@ +import { useRouter } from "next/router"; +import { useEffect, useMemo, useState } from "react"; 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 { items: T[]; languages: AppStaticProps["languages"]; diff --git a/src/hooks/useStateWithLocalStorage.ts b/src/hooks/useStateWithLocalStorage.ts index 4deba5c..c78f533 100644 --- a/src/hooks/useStateWithLocalStorage.ts +++ b/src/hooks/useStateWithLocalStorage.ts @@ -1,5 +1,5 @@ -import { isDefined } from "helpers/others"; import { useEffect, useState } from "react"; +import { isDefined } from "helpers/others"; export const useStateWithLocalStorage = ( key: string, diff --git a/src/hooks/useToggle.ts b/src/hooks/useToggle.ts index 2b43dd3..b5d374b 100644 --- a/src/hooks/useToggle.ts +++ b/src/hooks/useToggle.ts @@ -1,6 +1,8 @@ import { Dispatch, SetStateAction, useCallback } from "react"; -export const useToggle = (setState: Dispatch>) => +export const useToggle = ( + setState: Dispatch> +): (() => void) => useCallback(() => { setState((current) => !current); - }, []); + }, [setState]); diff --git a/src/pages/404.tsx b/src/pages/404.tsx index 1f0dce0..34bcfe7 100644 --- a/src/pages/404.tsx +++ b/src/pages/404.tsx @@ -1,3 +1,4 @@ +import { GetStaticProps } from "next"; import { AppLayout } from "components/AppLayout"; import { ReturnButton, @@ -5,7 +6,6 @@ import { } from "components/PanelComponents/ReturnButton"; import { ContentPanel } from "components/Panels/ContentPanel"; import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps"; -import { GetStaticProps } from "next"; /* * ╭────────╮ diff --git a/src/pages/500.tsx b/src/pages/500.tsx index 95f475f..1ee938a 100644 --- a/src/pages/500.tsx +++ b/src/pages/500.tsx @@ -1,3 +1,4 @@ +import { GetStaticProps } from "next"; import { AppLayout } from "components/AppLayout"; import { ReturnButton, @@ -5,7 +6,6 @@ import { } from "components/PanelComponents/ReturnButton"; import { ContentPanel } from "components/Panels/ContentPanel"; import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps"; -import { GetStaticProps } from "next"; /* * ╭────────╮ diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index 47591d0..ceb5b43 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -5,8 +5,8 @@ import "@fontsource/opendyslexic/700.css"; import "@fontsource/vollkorn/700.css"; import "@fontsource/zen-maru-gothic/500.css"; import "@fontsource/zen-maru-gothic/900.css"; -import { AppContextProvider } from "contexts/AppLayoutContext"; import type { AppProps } from "next/app"; +import { AppContextProvider } from "contexts/AppLayoutContext"; import "tailwind.css"; const AccordsLibraryApp = (props: AppProps): JSX.Element => ( diff --git a/src/pages/about-us/contact.tsx b/src/pages/about-us/contact.tsx index 81b0970..136763a 100644 --- a/src/pages/about-us/contact.tsx +++ b/src/pages/about-us/contact.tsx @@ -1,3 +1,5 @@ +import { useRouter } from "next/router"; +import { useState } from "react"; import { InsetBox } from "components/InsetBox"; import { PostPage } from "components/PostPage"; import { @@ -6,10 +8,7 @@ import { } from "graphql/getPostStaticProps"; import { cIf, cJoin } from "helpers/className"; import { randomInt } from "helpers/numbers"; - -import { useRouter } from "next/router"; import { RequestMailProps, ResponseMailProps } from "pages/api/mail"; -import { useState } from "react"; /* * ╭────────╮ diff --git a/src/pages/about-us/index.tsx b/src/pages/about-us/index.tsx index 05e6d75..6fd6cae 100644 --- a/src/pages/about-us/index.tsx +++ b/src/pages/about-us/index.tsx @@ -1,10 +1,10 @@ +import { GetStaticProps } from "next"; import { AppLayout } from "components/AppLayout"; import { Icon } from "components/Ico"; import { NavOption } from "components/PanelComponents/NavOption"; import { PanelHeader } from "components/PanelComponents/PanelHeader"; import { SubPanel } from "components/Panels/SubPanel"; import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps"; -import { GetStaticProps } from "next"; /* * ╭────────╮ diff --git a/src/pages/api/mail.ts b/src/pages/api/mail.ts index 6375293..7c06663 100644 --- a/src/pages/api/mail.ts +++ b/src/pages/api/mail.ts @@ -17,7 +17,7 @@ export interface RequestMailProps { const Mail = async ( req: NextApiRequest, res: NextApiResponse -) => { +): Promise => { if (req.method === "POST") { const body = req.body as RequestMailProps; diff --git a/src/pages/api/revalidate.ts b/src/pages/api/revalidate.ts index efcd978..4dbc839 100644 --- a/src/pages/api/revalidate.ts +++ b/src/pages/api/revalidate.ts @@ -2,17 +2,17 @@ import type { NextApiRequest, NextApiResponse } from "next"; import getConfig from "next/config"; type RequestProps = - | HookRangedContent - | HookPostContent - | HookLibraryItem | HookChronology | HookContent | HookContentGroup | HookCustom + | HookLibraryItem + | HookPostContent + | HookRangedContent | HookWiki; type HookRangedContent = { - event: "entry.update" | "entry.delete" | "entry.create"; + event: "entry.create" | "entry.delete" | "entry.update"; model: "ranged-content"; entry: { library_item?: { @@ -33,16 +33,14 @@ type HookContent = { model: "content"; entry: { slug: string; - ranged_contents: [ - { - slug: string; - } - ]; + ranged_contents: { + slug: string; + }[]; }; }; type HookPostContent = { - event: "entry.update" | "entry.delete" | "entry.create"; + event: "entry.create" | "entry.delete" | "entry.update"; model: "post"; entry: { slug: string; @@ -50,7 +48,7 @@ type HookPostContent = { }; type HookLibraryItem = { - event: "entry.update" | "entry.delete" | "entry.create"; + event: "entry.create" | "entry.delete" | "entry.update"; model: "library-item"; entry: { slug: string; @@ -63,7 +61,7 @@ type HookLibraryItem = { }; type HookContentGroup = { - event: "entry.update" | "entry.delete" | "entry.create"; + event: "entry.create" | "entry.delete" | "entry.update"; model: "contents-group"; entry: { contents: { @@ -73,12 +71,12 @@ type HookContentGroup = { }; type HookChronology = { - event: "entry.update" | "entry.delete" | "entry.create"; + event: "entry.create" | "entry.delete" | "entry.update"; model: "chronology-era" | "chronology-item"; }; type HookWiki = { - event: "entry.update" | "entry.delete" | "entry.create"; + event: "entry.create" | "entry.delete" | "entry.update"; model: "wiki-page"; entry: { slug: string; @@ -90,10 +88,10 @@ type ResponseMailProps = { revalidated: boolean; }; -const Revalidate = async ( +const Revalidate = ( req: NextApiRequest, res: NextApiResponse -) => { +): void => { const body = req.body as RequestProps; const { serverRuntimeConfig } = getConfig(); @@ -101,9 +99,8 @@ const Revalidate = async ( if ( req.headers.authorization !== `Bearer ${process.env.REVALIDATION_TOKEN}` ) { - return res - .status(401) - .json({ message: "Invalid token", revalidated: false }); + res.status(401).json({ message: "Invalid token", revalidated: false }); + return; } const paths: string[] = []; @@ -229,13 +226,12 @@ const Revalidate = async ( await res.revalidate(path); }) ); - return res.json({ message: "Success!", revalidated: true }); - } catch (err) { - // If there was an error, Next.js will continue - // to show the last successfully generated page - return res + res.json({ message: "Success!", revalidated: true }); + return; + } catch (error) { + res .status(500) - .send({ message: "Error revalidating", revalidated: false }); + .send({ message: `Error revalidating: ${error}`, revalidated: false }); } }; export default Revalidate; diff --git a/src/pages/archives/index.tsx b/src/pages/archives/index.tsx index 6f3fc75..283fc34 100644 --- a/src/pages/archives/index.tsx +++ b/src/pages/archives/index.tsx @@ -1,12 +1,11 @@ +import { GetStaticProps } from "next"; +import { useMemo } from "react"; import { AppLayout } from "components/AppLayout"; import { NavOption } from "components/PanelComponents/NavOption"; import { PanelHeader } from "components/PanelComponents/PanelHeader"; import { SubPanel } from "components/Panels/SubPanel"; import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps"; - -import { GetStaticProps } from "next"; import { Icon } from "components/Ico"; -import { useMemo } from "react"; /* * ╭────────╮ diff --git a/src/pages/archives/videos/c/[uid].tsx b/src/pages/archives/videos/c/[uid].tsx index 0e59f11..c80a18e 100644 --- a/src/pages/archives/videos/c/[uid].tsx +++ b/src/pages/archives/videos/c/[uid].tsx @@ -1,3 +1,5 @@ +import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next"; +import { Fragment, useState, useMemo } from "react"; import { AppLayout } from "components/AppLayout"; import { Switch } from "components/Inputs/Switch"; import { PanelHeader } from "components/PanelComponents/PanelHeader"; @@ -15,8 +17,6 @@ import { GetVideoChannelQuery } from "graphql/generated"; import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps"; import { getReadySdk } from "graphql/sdk"; import { getVideoThumbnailURL } from "helpers/videos"; -import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next"; -import { Fragment, useState, useMemo } from "react"; import { Icon } from "components/Ico"; import { useMediaHoverable } from "hooks/useMediaQuery"; import { WithLabel } from "components/Inputs/WithLabel"; diff --git a/src/pages/archives/videos/index.tsx b/src/pages/archives/videos/index.tsx index 2f921b1..0b5b3c3 100644 --- a/src/pages/archives/videos/index.tsx +++ b/src/pages/archives/videos/index.tsx @@ -1,8 +1,12 @@ +import { GetStaticProps } from "next"; +import { useMemo, useState } from "react"; import { AppLayout } from "components/AppLayout"; +import { SmartList } from "components/SmartList"; import { Icon } from "components/Ico"; -import { PageSelector } from "components/Inputs/PageSelector"; import { Switch } from "components/Inputs/Switch"; +import { TextInput } from "components/Inputs/TextInput"; import { WithLabel } from "components/Inputs/WithLabel"; +import { ContentPlaceholder } from "components/PanelComponents/ContentPlaceholder"; import { PanelHeader } from "components/PanelComponents/PanelHeader"; import { ReturnButton, @@ -21,15 +25,15 @@ import { prettyDate } from "helpers/formatters"; import { filterHasAttributes } from "helpers/others"; import { getVideoThumbnailURL } from "helpers/videos"; import { useMediaHoverable } from "hooks/useMediaQuery"; -import { GetStaticProps } from "next"; -import { Fragment, useMemo, useState } from "react"; /* * ╭─────────────╮ * ────────────────────────────────────────╯ CONSTANTS ╰────────────────────────────────────────── */ -const ITEM_PER_PAGE = 50; +const DEFAULT_FILTERS_STATE = { + searchName: "", +}; /* * ╭────────╮ @@ -42,18 +46,10 @@ interface Props extends AppStaticProps { const Videos = ({ langui, videos, ...otherProps }: Props): JSX.Element => { const hoverable = useMediaHoverable(); - const [page, setPage] = useState(0); const [keepInfoVisible, setKeepInfoVisible] = useState(true); - - const paginatedVideos = useMemo(() => { - const memo = []; - for (let index = 0; ITEM_PER_PAGE * index < videos.length; index += 1) { - memo.push( - videos.slice(index * ITEM_PER_PAGE, (index + 1) * ITEM_PER_PAGE) - ); - } - return memo; - }, [videos]); + const [searchName, setSearchName] = useState( + DEFAULT_FILTERS_STATE.searchName + ); const subPanel = useMemo( () => ( @@ -72,6 +68,13 @@ const Videos = ({ langui, videos, ...otherProps }: Props): JSX.Element => { description={langui.archives_description} /> + + {hoverable && ( { )} ), - [hoverable, keepInfoVisible, langui] + [hoverable, keepInfoVisible, langui, searchName] ); const contentPanel = useMemo( () => ( - - -
- {filterHasAttributes(paginatedVideos[page]).map((video) => ( - + item.id} + renderItem={({ item }) => ( + <> - - ))} -
- - + )} + renderWhenEmpty={() => ( + + )} + className="desktop:grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))] mobile:grid-cols-2 + thin:grid-cols-1" + paginationItemPerPage={20} + searchingTerm={searchName} + searchingBy={(item) => item.attributes.title} + langui={langui} />
), - [keepInfoVisible, page, paginatedVideos, videos.length] + [keepInfoVisible, langui, searchName, videos] ); return ( -

{langui.source_language}:

+

+ {langui.source_language}: +

{prettyLanguage( selectedTranslation.text_set.source_language.data.attributes @@ -123,7 +125,7 @@ const Content = ({ )}
-

{langui.status}:

+

{langui.status}:

0 && (
-

{langui.transcribers}:

+

+ {langui.transcribers}: +

{filterHasAttributes( selectedTranslation.text_set.transcribers.data @@ -158,7 +162,9 @@ const Content = ({ {selectedTranslation.text_set.translators && selectedTranslation.text_set.translators.data.length > 0 && (
-

{langui.translators}:

+

+ {langui.translators}: +

{filterHasAttributes( selectedTranslation.text_set.translators.data @@ -177,7 +183,9 @@ const Content = ({ {selectedTranslation.text_set.proofreaders && selectedTranslation.text_set.proofreaders.data.length > 0 && (
-

{langui.proofreaders}:

+

+ {langui.proofreaders}: +

{filterHasAttributes( selectedTranslation.text_set.proofreaders.data @@ -195,7 +203,7 @@ const Content = ({ {isDefinedAndNotEmpty(selectedTranslation.text_set.notes) && (
-

{"Notes"}:

+

{"Notes"}:

@@ -209,7 +217,9 @@ const Content = ({ <>
-

{langui.source}

+

+ {langui.source} +

{content.ranged_contents.data.map((rangedContent) => { const libraryItem = @@ -525,7 +535,7 @@ type Group = NonNullable< >["data"]; const getPreviousContent = (group: Group, currentSlug: string) => { - for (let index = 0; index < group.length; index += 1) { + for (let index = 0; index < group.length; index++) { const content = group[index]; if (content.attributes?.slug === currentSlug && index > 0) { return group[index - 1]; @@ -537,7 +547,7 @@ const getPreviousContent = (group: Group, currentSlug: string) => { // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ const getNextContent = (group: Group, currentSlug: string) => { - for (let index = 0; index < group.length; index += 1) { + for (let index = 0; index < group.length; index++) { const content = group[index]; if (content.attributes?.slug === currentSlug && index < group.length - 1) { return group[index + 1]; diff --git a/src/pages/contents/index.tsx b/src/pages/contents/index.tsx index e65bf2e..817d53d 100644 --- a/src/pages/contents/index.tsx +++ b/src/pages/contents/index.tsx @@ -1,5 +1,6 @@ +import { GetStaticProps } from "next"; +import { useState, useMemo, useCallback } from "react"; import { AppLayout } from "components/AppLayout"; -import { Chip } from "components/Chip"; import { Select } from "components/Inputs/Select"; import { Switch } from "components/Inputs/Switch"; import { PanelHeader } from "components/PanelComponents/PanelHeader"; @@ -12,20 +13,16 @@ import { TranslatedPreviewCard } from "components/PreviewCard"; import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps"; import { getReadySdk } from "graphql/sdk"; import { prettyinlineTitle, prettySlug } from "helpers/formatters"; -import { GetStaticProps } from "next"; -import { Fragment, useState, useMemo } from "react"; -import { Icon } from "components/Ico"; +import { TextInput } from "components/Inputs/TextInput"; import { WithLabel } from "components/Inputs/WithLabel"; import { Button } from "components/Inputs/Button"; -import { TextInput } from "components/Inputs/TextInput"; import { useMediaHoverable } from "hooks/useMediaQuery"; -import { - filterHasAttributes, - iterateMap, - mapRemoveEmptyValues, -} from "helpers/others"; -import { ContentPlaceholder } from "components/PanelComponents/ContentPlaceholder"; +import { Icon } from "components/Ico"; +import { filterDefined, filterHasAttributes } from "helpers/others"; import { GetContentsQuery } from "graphql/generated"; +import { SmartList } from "components/SmartList"; +import { SelectiveRequiredNonNullable } from "helpers/types"; +import { ContentPlaceholder } from "components/PanelComponents/ContentPlaceholder"; /* * ╭─────────────╮ @@ -74,14 +71,72 @@ const Contents = ({ [combineRelatedContent, searchName.length] ); - const filteredItems = useMemo( - () => filterContents(contents, effectiveCombineRelatedContent, searchName), - [effectiveCombineRelatedContent, contents, searchName] + const groupingFunction = useCallback( + ( + item: SelectiveRequiredNonNullable< + NonNullable["data"][number], + "attributes" | "id" + > + ): string[] => { + switch (groupingMethod) { + case 0: { + const categories = filterHasAttributes( + item.attributes.categories?.data + ); + if (categories.length > 0) { + return categories.map((category) => category.attributes.name); + } + return [langui.no_category ?? "No category"]; + } + case 1: { + return [ + item.attributes.type?.data?.attributes?.titles?.[0]?.title ?? + item.attributes.type?.data?.attributes?.slug + ? prettySlug(item.attributes.type.data.attributes.slug) + : langui.no_type ?? "No type", + ]; + } + default: { + return [""]; + } + } + }, + [groupingMethod, langui] ); - const groups = useMemo( - () => getGroups(langui, groupingMethod, filteredItems), - [langui, groupingMethod, filteredItems] + const filteringFunction = useCallback( + ( + item: SelectiveRequiredNonNullable< + Props["contents"][number], + "attributes" | "id" + > + ) => { + if ( + effectiveCombineRelatedContent && + item.attributes.group?.data?.attributes?.combine === true && + item.attributes.group.data.attributes.contents?.data[0].id !== item.id + ) { + return false; + } + if (searchName.length > 1) { + if ( + filterDefined(item.attributes.translations).find((translation) => + prettyinlineTitle( + translation.pre_title, + translation.title, + translation.subtitle + ) + .toLowerCase() + .includes(searchName.toLowerCase()) + ) + ) { + return true; + } + return false; + } + return true; + }, + [effectiveCombineRelatedContent, searchName] ); const subPanel = useMemo( @@ -161,107 +216,91 @@ const Contents = ({ const contentPanel = useMemo( () => ( - {groups.size === 0 && ( - - )} - {iterateMap( - groups, - (name, items, index) => - items.length > 0 && ( - - {name && ( -

- {name} - {`${items.reduce((currentSum, item) => { - if (effectiveCombineRelatedContent) { - if ( - item.attributes?.group?.data?.attributes?.combine === - true - ) { - return ( - currentSum + - (item.attributes.group.data.attributes.contents - ?.data.length ?? 1) - ); - } - } - return currentSum + 1; - }, 0)} ${ - items.length <= 1 - ? langui.result?.toLowerCase() ?? "" - : langui.results?.toLowerCase() ?? "" - }`} -

- )} - -
- {filterHasAttributes(items).map((item) => ( - - {item.attributes.translations && ( - ({ - 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.id} + renderItem={({ item }) => ( + <> + {item.attributes.translations && ( + ({ + 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} + /> + )} + + )} + renderWhenEmpty={() => ( + + )} + className="grid-cols-2 items-end desktop:grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))]" + groupingFunction={groupingFunction} + filteringFunction={filteringFunction} + searchingTerm={searchName} + searchingBy={(item) => + ` + ${item.attributes.slug} + ${filterDefined(item.attributes.translations) + .map((translation) => + prettyinlineTitle( + translation.pre_title, + translation.title, + translation.subtitle + ) + ) + .join(" ")}` + } + langui={langui} + />
), - [effectiveCombineRelatedContent, groups, keepInfoVisible, languages, langui] + [ + contents, + effectiveCombineRelatedContent, + filteringFunction, + groupingFunction, + keepInfoVisible, + languages, + langui, + searchName, + ] ); return ( @@ -303,107 +342,3 @@ export const getStaticProps: GetStaticProps = async (context) => { props: props, }; }; - -/* - * ╭───────────────────╮ - * ─────────────────────────────────────╯ PRIVATE METHODS ╰─────────────────────────────────────── - */ - -type GroupContentItems = Map; - -export const getGroups = ( - langui: AppStaticProps["langui"], - groupByType: number, - items: Props["contents"] -): GroupContentItems => { - const groups: GroupContentItems = new Map(); - - switch (groupByType) { - case 0: { - const noCategory = langui.no_category ?? "No category"; - groups.set("Drakengard 1", []); - groups.set("Drakengard 1.3", []); - groups.set("Drakengard 2", []); - groups.set("Drakengard 3", []); - groups.set("Drakengard 4", []); - groups.set("NieR Gestalt", []); - groups.set("NieR Replicant", []); - groups.set("NieR Replicant ver.1.22474487139...", []); - groups.set("NieR:Automata", []); - groups.set("NieR Re[in]carnation", []); - groups.set("SINoALICE", []); - groups.set("Voice of Cards", []); - groups.set("Final Fantasy XIV", []); - groups.set("Thou Shalt Not Die", []); - groups.set("Bakuken", []); - groups.set("YoRHa", []); - groups.set("YoRHa Boys", []); - groups.set(noCategory, []); - - items.map((item) => { - if (item.attributes?.categories?.data.length === 0) { - groups.get(noCategory)?.push(item); - } else { - item.attributes?.categories?.data.map((category) => { - groups.get(category.attributes?.name ?? noCategory)?.push(item); - }); - } - }); - break; - } - - case 1: { - items.map((item) => { - const noType = langui.no_type ?? "No type"; - const type = - item.attributes?.type?.data?.attributes?.titles?.[0]?.title ?? - item.attributes?.type?.data?.attributes?.slug - ? prettySlug(item.attributes.type.data.attributes.slug) - : langui.no_type; - if (!groups.has(type ?? noType)) groups.set(type ?? noType, []); - groups.get(type ?? noType)?.push(item); - }); - break; - } - - default: { - groups.set("", items); - } - } - return mapRemoveEmptyValues(groups); -}; - -// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ - -export const filterContents = ( - contents: Props["contents"], - combineRelatedContent: boolean, - searchName: string -): Props["contents"] => - contents.filter((content) => { - if ( - combineRelatedContent && - content.attributes?.group?.data?.attributes?.combine === true && - content.attributes.group.data.attributes.contents?.data[0].id !== - content.id - ) { - return false; - } - if (searchName.length > 1) { - if ( - content.attributes?.translations?.find((translation) => - prettyinlineTitle( - translation?.pre_title, - translation?.title, - translation?.subtitle - ) - .toLowerCase() - .includes(searchName.toLowerCase()) - ) - ) { - return true; - } - return false; - } - return true; - }); diff --git a/src/pages/dev/checkup/contents.tsx b/src/pages/dev/checkup/contents.tsx index e0fb18a..dafaeb0 100644 --- a/src/pages/dev/checkup/contents.tsx +++ b/src/pages/dev/checkup/contents.tsx @@ -1,3 +1,5 @@ +import { GetStaticProps } from "next"; +import { useMemo } from "react"; import { AppLayout } from "components/AppLayout"; import { Chip } from "components/Chip"; import { Button } from "components/Inputs/Button"; @@ -10,8 +12,6 @@ import { DevGetContentsQuery } from "graphql/generated"; import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps"; import { getReadySdk } from "graphql/sdk"; import { filterDefined, filterHasAttributes } from "helpers/others"; -import { GetStaticProps } from "next"; -import { useMemo } from "react"; /* * ╭────────╮ diff --git a/src/pages/dev/checkup/libraryitems.tsx b/src/pages/dev/checkup/libraryitems.tsx index 26e157a..ebcc49c 100644 --- a/src/pages/dev/checkup/libraryitems.tsx +++ b/src/pages/dev/checkup/libraryitems.tsx @@ -1,3 +1,5 @@ +import { GetStaticProps } from "next"; +import { useMemo } from "react"; import { AppLayout } from "components/AppLayout"; import { Chip } from "components/Chip"; import { Button } from "components/Inputs/Button"; @@ -13,9 +15,6 @@ import { import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps"; import { getReadySdk } from "graphql/sdk"; -import { GetStaticProps } from "next"; -import { useMemo } from "react"; - /* * ╭────────╮ * ──────────────────────────────────────────╯ PAGE ╰───────────────────────────────────────────── diff --git a/src/pages/dev/editor.tsx b/src/pages/dev/editor.tsx index 6bed228..4ae0c4f 100644 --- a/src/pages/dev/editor.tsx +++ b/src/pages/dev/editor.tsx @@ -1,3 +1,6 @@ +import { GetStaticProps } from "next"; +import { useCallback, useMemo, useRef, useState } from "react"; +import TurndownService from "turndown"; import { AppLayout } from "components/AppLayout"; import { Button } from "components/Inputs/Button"; import { Markdawn, TableOfContents } from "components/Markdown/Markdawn"; @@ -8,9 +11,6 @@ import { import { Popup } from "components/Popup"; import { ToolTip } from "components/ToolTip"; import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps"; -import { GetStaticProps } from "next"; -import { useCallback, useMemo, useRef, useState } from "react"; -import TurndownService from "turndown"; import { Icon } from "components/Ico"; /* diff --git a/src/pages/dev/transcript.tsx b/src/pages/dev/transcript.tsx index 99f9272..93a8f95 100644 --- a/src/pages/dev/transcript.tsx +++ b/src/pages/dev/transcript.tsx @@ -1,3 +1,5 @@ +import { GetStaticProps } from "next"; +import { useCallback, useMemo, useRef, useState } from "react"; import { AppLayout } from "components/AppLayout"; import { Button } from "components/Inputs/Button"; import { ButtonGroup } from "components/Inputs/ButtonGroup"; @@ -7,8 +9,6 @@ import { } from "components/Panels/ContentPanel"; import { ToolTip } from "components/ToolTip"; import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps"; -import { GetStaticProps } from "next"; -import { useCallback, useMemo, useRef, useState } from "react"; /* * ╭─────────────╮ diff --git a/src/pages/library/[slug]/index.tsx b/src/pages/library/[slug]/index.tsx index d85eb3f..f3a02d6 100644 --- a/src/pages/library/[slug]/index.tsx +++ b/src/pages/library/[slug]/index.tsx @@ -1,3 +1,5 @@ +import { Fragment, useCallback, useMemo, useState } from "react"; +import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next"; import { AppLayout } from "components/AppLayout"; import { Chip } from "components/Chip"; import { Img } from "components/Img"; @@ -40,12 +42,10 @@ import { filterHasAttributes, isDefined, isDefinedAndNotEmpty, - sortContent, + sortRangedContent, } from "helpers/others"; import { useLightBox } from "hooks/useLightBox"; import { AnchorIds, useScrollTopOnChange } from "hooks/useScrollTopOnChange"; -import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next"; -import { Fragment, useCallback, useMemo, useState } from "react"; import { isUntangibleGroupItem } from "helpers/libraryItem"; import { useMediaHoverable } from "hooks/useMediaQuery"; import { WithLabel } from "components/Inputs/WithLabel"; @@ -626,7 +626,7 @@ export const getStaticProps: GetStaticProps = async (context) => { language_code: context.locale ?? "en", }); if (!item.libraryItems?.data[0]?.attributes) return { notFound: true }; - sortContent(item.libraryItems.data[0].attributes.contents); + sortRangedContent(item.libraryItems.data[0].attributes.contents); const props: Props = { ...(await getAppStaticProps(context)), item: item.libraryItems.data[0].attributes, @@ -702,6 +702,8 @@ const ContentLine = ({ ), }); + console.log(prettySlug(slug, parentSlug)); + return (
diff --git a/src/pages/library/[slug]/scans.tsx b/src/pages/library/[slug]/scans.tsx index 16ecd4c..710aad7 100644 --- a/src/pages/library/[slug]/scans.tsx +++ b/src/pages/library/[slug]/scans.tsx @@ -1,3 +1,5 @@ +import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next"; +import { Fragment, useMemo } from "react"; import { AppLayout } from "components/AppLayout"; import { ScanSet } from "components/Library/ScanSet"; import { ScanSetCover } from "components/Library/ScanSetCover"; @@ -15,11 +17,12 @@ import { GetLibraryItemScansQuery } from "graphql/generated"; import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps"; import { getReadySdk } from "graphql/sdk"; import { prettyinlineTitle, prettySlug } from "helpers/formatters"; -import { filterHasAttributes, isDefined, sortContent } from "helpers/others"; - +import { + filterHasAttributes, + isDefined, + sortRangedContent, +} from "helpers/others"; import { useLightBox } from "hooks/useLightBox"; -import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next"; -import { Fragment, useMemo } from "react"; /* * ╭────────╮ @@ -44,7 +47,6 @@ const LibrarySlug = ({ ...otherProps }: Props): JSX.Element => { const [openLightBox, LightBox] = useLightBox(); - sortContent(item.contents); const subPanel = useMemo( () => ( @@ -158,6 +160,7 @@ export const getStaticProps: GetStaticProps = async (context) => { }); if (!item.libraryItems?.data[0]?.attributes || !item.libraryItems.data[0]?.id) return { notFound: true }; + sortRangedContent(item.libraryItems.data[0].attributes.contents); const props: Props = { ...(await getAppStaticProps(context)), item: item.libraryItems.data[0].attributes, diff --git a/src/pages/library/index.tsx b/src/pages/library/index.tsx index aca6e9f..11cd7d9 100644 --- a/src/pages/library/index.tsx +++ b/src/pages/library/index.tsx @@ -1,5 +1,6 @@ +import { GetStaticProps } from "next"; +import { useState, useMemo, useCallback } from "react"; import { AppLayout } from "components/AppLayout"; -import { Chip } from "components/Chip"; import { Select } from "components/Inputs/Select"; import { Switch } from "components/Inputs/Switch"; import { PanelHeader } from "components/PanelComponents/PanelHeader"; @@ -11,32 +12,29 @@ import { SubPanel } from "components/Panels/SubPanel"; import { GetLibraryItemsPreviewQuery } from "graphql/generated"; import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps"; import { getReadySdk } from "graphql/sdk"; -import { prettyItemSubType } from "helpers/formatters"; -import { LibraryItemUserStatus } from "helpers/types"; -import { GetStaticProps } from "next"; -import { Fragment, useState, useMemo } from "react"; +import { + prettyDate, + prettyinlineTitle, + prettyItemSubType, +} from "helpers/formatters"; +import { + LibraryItemUserStatus, + SelectiveRequiredNonNullable, +} from "helpers/types"; import { Icon } from "components/Ico"; import { WithLabel } from "components/Inputs/WithLabel"; import { TextInput } from "components/Inputs/TextInput"; import { Button } from "components/Inputs/Button"; import { PreviewCardCTAs } from "components/Library/PreviewCardCTAs"; -import { useAppLayout } from "contexts/AppLayoutContext"; -import { - filterItems, - getGroups, - sortBy, - isUntangibleGroupItem, -} from "helpers/libraryItem"; +import { isUntangibleGroupItem } from "helpers/libraryItem"; import { PreviewCard } from "components/PreviewCard"; import { useMediaHoverable } from "hooks/useMediaQuery"; import { ButtonGroup } from "components/Inputs/ButtonGroup"; -import { - filterHasAttributes, - isDefinedAndNotEmpty, - isUndefined, - iterateMap, -} from "helpers/others"; +import { filterHasAttributes, isDefined, isUndefined } from "helpers/others"; import { ContentPlaceholder } from "components/PanelComponents/ContentPlaceholder"; +import { useAppLayout } from "contexts/AppLayoutContext"; +import { convertPrice } from "helpers/numbers"; +import { SmartList } from "components/SmartList"; /* * ╭─────────────╮ @@ -65,12 +63,12 @@ interface Props extends AppStaticProps { const Library = ({ langui, - items: libraryItems, + items, currencies, ...otherProps }: Props): JSX.Element => { - const appLayout = useAppLayout(); const hoverable = useMediaHoverable(); + const appLayout = useAppLayout(); const [searchName, setSearchName] = useState( DEFAULT_FILTERS_STATE.searchName @@ -97,36 +95,170 @@ const Library = ({ LibraryItemUserStatus | undefined >(DEFAULT_FILTERS_STATE.filterUserStatus); - const filteredItems = useMemo( - () => - filterItems( - appLayout, - libraryItems, - searchName, - showSubitems, - showPrimaryItems, - showSecondaryItems, - filterUserStatus - ), + const filteringFunction = useCallback( + ( + item: SelectiveRequiredNonNullable< + Props["items"][number], + "attributes" | "id" + > + ) => { + if (!showSubitems && !item.attributes.root_item) return false; + if ( + showSubitems && + isUntangibleGroupItem(item.attributes.metadata?.[0]) + ) { + return false; + } + if (item.attributes.primary && !showPrimaryItems) return false; + if (!item.attributes.primary && !showSecondaryItems) return false; + + if ( + isDefined(filterUserStatus) && + item.id && + appLayout.libraryItemUserStatus + ) { + if (isUntangibleGroupItem(item.attributes.metadata?.[0])) { + return false; + } + if (filterUserStatus === LibraryItemUserStatus.None) { + if (appLayout.libraryItemUserStatus[item.id]) { + return false; + } + } else if ( + filterUserStatus !== appLayout.libraryItemUserStatus[item.id] + ) { + return false; + } + } + return true; + }, [ - appLayout, + appLayout.libraryItemUserStatus, filterUserStatus, - libraryItems, - searchName, showPrimaryItems, showSecondaryItems, showSubitems, ] ); - const sortedItems = useMemo( - () => sortBy(sortingMethod, filteredItems, currencies), - [currencies, filteredItems, sortingMethod] + const sortingFunction = useCallback( + ( + a: SelectiveRequiredNonNullable< + Props["items"][number], + "attributes" | "id" + >, + b: SelectiveRequiredNonNullable< + Props["items"][number], + "attributes" | "id" + > + ) => { + switch (sortingMethod) { + case 0: { + const titleA = prettyinlineTitle( + "", + a.attributes.title, + a.attributes.subtitle + ); + const titleB = prettyinlineTitle( + "", + b.attributes.title, + b.attributes.subtitle + ); + return titleA.localeCompare(titleB); + } + case 1: { + const priceA = a.attributes.price + ? convertPrice(a.attributes.price, currencies[0]) + : 99999; + const priceB = b.attributes.price + ? convertPrice(b.attributes.price, currencies[0]) + : 99999; + return priceA - priceB; + } + case 2: { + const dateA = a.attributes.release_date + ? prettyDate(a.attributes.release_date) + : "9999"; + const dateB = b.attributes.release_date + ? prettyDate(b.attributes.release_date) + : "9999"; + return dateA.localeCompare(dateB); + } + default: + return 0; + } + }, + [currencies, sortingMethod] ); - const groups = useMemo( - () => getGroups(langui, groupingMethod, sortedItems), - [langui, groupingMethod, sortedItems] + const groupingFunction = useCallback( + ( + item: SelectiveRequiredNonNullable< + Props["items"][number], + "attributes" | "id" + > + ): string[] => { + switch (groupingMethod) { + case 0: { + const categories = filterHasAttributes( + item.attributes.categories?.data + ); + if (categories.length > 0) { + return categories.map((category) => category.attributes.name); + } + return [langui.no_category ?? "No category"]; + } + case 1: { + if (item.attributes.metadata && item.attributes.metadata.length > 0) { + switch (item.attributes.metadata[0]?.__typename) { + case "ComponentMetadataAudio": + return [langui.audio ?? "Audio"]; + case "ComponentMetadataGame": + return [langui.game ?? "Game"]; + case "ComponentMetadataBooks": + return [langui.textual ?? "Textual"]; + case "ComponentMetadataVideo": + return [langui.video ?? "Video"]; + case "ComponentMetadataOther": + return [langui.other ?? "Other"]; + case "ComponentMetadataGroup": { + switch ( + item.attributes.metadata[0]?.subitems_type?.data?.attributes + ?.slug + ) { + case "audio": + return [langui.audio ?? "Audio"]; + case "video": + return [langui.video ?? "Video"]; + case "game": + return [langui.game ?? "Game"]; + case "textual": + return [langui.textual ?? "Textual"]; + case "mixed": + return [langui.group ?? "Group"]; + default: { + return [langui.no_type ?? "No type"]; + } + } + } + default: + return [langui.no_type ?? "No type"]; + } + } else { + return [langui.no_type ?? "No type"]; + } + } + case 2: { + if (item.attributes.release_date?.year) { + return [item.attributes.release_date.year.toString()]; + } + return [langui.no_year ?? "No year"]; + } + default: + return [""]; + } + }, + [groupingMethod, langui] ); const subPanel = useMemo( @@ -273,72 +405,73 @@ const Library = ({ const contentPanel = useMemo( () => ( - {groups.size === 0 && ( - - )} - {iterateMap(groups, (name, items) => ( - - {isDefinedAndNotEmpty(name) && ( -

- {name} - {`${items.length} ${ - items.length <= 1 - ? langui.result?.toLowerCase() ?? "result" - : langui.results?.toLowerCase() ?? "results" - }`} -

- )} -
- {filterHasAttributes(items).map((item) => ( - - 0 && - item.attributes.metadata[0] - ? [prettyItemSubType(item.attributes.metadata[0])] - : [] - } - bottomChips={item.attributes.categories?.data.map( - (category) => category.attributes?.short ?? "" - )} - metadata={{ - currencies: currencies, - release_date: item.attributes.release_date, - price: item.attributes.price, - position: "Bottom", - }} - infoAppend={ - !isUntangibleGroupItem(item.attributes.metadata?.[0]) && ( - - ) - } - /> - - ))} -
-
- ))} + item.id} + renderItem={({ item }) => ( + 0 && + item.attributes.metadata[0] + ? [prettyItemSubType(item.attributes.metadata[0])] + : [] + } + bottomChips={item.attributes.categories?.data.map( + (category) => category.attributes?.short ?? "" + )} + metadata={{ + currencies: currencies, + release_date: item.attributes.release_date, + price: item.attributes.price, + position: "Bottom", + }} + infoAppend={ + !isUntangibleGroupItem(item.attributes.metadata?.[0]) && ( + + ) + } + /> + )} + renderWhenEmpty={() => ( + + )} + className="grid-cols-2 items-end desktop:grid-cols-[repeat(auto-fill,_minmax(13rem,1fr))]" + searchingTerm={searchName} + sortingFunction={sortingFunction} + groupingFunction={groupingFunction} + searchingBy={(item) => + prettyinlineTitle( + "", + item.attributes.title, + item.attributes.subtitle + ) + } + filteringFunction={filteringFunction} + langui={langui} + />
), - [currencies, groups, keepInfoVisible, langui] + [ + currencies, + filteringFunction, + groupingFunction, + items, + keepInfoVisible, + langui, + searchName, + sortingFunction, + ] ); return ( diff --git a/src/pages/merch/index.tsx b/src/pages/merch/index.tsx index 97c2a79..9065c8f 100644 --- a/src/pages/merch/index.tsx +++ b/src/pages/merch/index.tsx @@ -1,9 +1,8 @@ +import { GetStaticProps } from "next"; import { AppLayout } from "components/AppLayout"; import { PanelHeader } from "components/PanelComponents/PanelHeader"; import { SubPanel } from "components/Panels/SubPanel"; import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps"; - -import { GetStaticProps } from "next"; import { Icon } from "components/Ico"; /* diff --git a/src/pages/news/[slug].tsx b/src/pages/news/[slug].tsx index df47c7a..0f5d7f1 100644 --- a/src/pages/news/[slug].tsx +++ b/src/pages/news/[slug].tsx @@ -1,3 +1,4 @@ +import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next"; import { PostPage } from "components/PostPage"; import { AppStaticProps } from "graphql/getAppStaticProps"; import { @@ -6,7 +7,6 @@ import { } from "graphql/getPostStaticProps"; import { getReadySdk } from "graphql/sdk"; import { filterHasAttributes, isDefined } from "helpers/others"; -import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next"; /* * ╭────────╮ diff --git a/src/pages/news/index.tsx b/src/pages/news/index.tsx index c764895..2c6308a 100644 --- a/src/pages/news/index.tsx +++ b/src/pages/news/index.tsx @@ -1,3 +1,5 @@ +import { GetStaticProps } from "next"; +import { useMemo, useState } from "react"; import { AppLayout } from "components/AppLayout"; import { Switch } from "components/Inputs/Switch"; import { PanelHeader } from "components/PanelComponents/PanelHeader"; @@ -6,20 +8,18 @@ import { ContentPanelWidthSizes, } from "components/Panels/ContentPanel"; import { SubPanel } from "components/Panels/SubPanel"; -import { PreviewCard } from "components/PreviewCard"; +import { TranslatedPreviewCard } from "components/PreviewCard"; import { GetPostsPreviewQuery } from "graphql/generated"; import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps"; import { getReadySdk } from "graphql/sdk"; import { prettyDate, prettySlug } from "helpers/formatters"; - -import { GetStaticProps } from "next"; -import { Fragment, useMemo, useState } from "react"; import { Icon } from "components/Ico"; import { WithLabel } from "components/Inputs/WithLabel"; import { TextInput } from "components/Inputs/TextInput"; import { Button } from "components/Inputs/Button"; import { useMediaHoverable } from "hooks/useMediaQuery"; -import { filterHasAttributes } from "helpers/others"; +import { filterDefined, filterHasAttributes } from "helpers/others"; +import { SmartList } from "components/SmartList"; /* * ╭─────────────╮ @@ -40,7 +40,12 @@ interface Props extends AppStaticProps { posts: NonNullable["data"]; } -const News = ({ langui, posts, ...otherProps }: Props): JSX.Element => { +const News = ({ + langui, + posts, + languages, + ...otherProps +}: Props): JSX.Element => { const hoverable = useMediaHoverable(); const [searchName, setSearchName] = useState( DEFAULT_FILTERS_STATE.searchName @@ -49,11 +54,6 @@ const News = ({ langui, posts, ...otherProps }: Props): JSX.Element => { DEFAULT_FILTERS_STATE.keepInfoVisible ); - const filteredItems = useMemo( - () => filterItems(posts, searchName), - [posts, searchName] - ); - const subPanel = useMemo( () => ( @@ -96,37 +96,46 @@ const News = ({ langui, posts, ...otherProps }: Props): JSX.Element => { const contentPanel = useMemo( () => ( -
- {filterHasAttributes(filteredItems).map((post) => ( - - category.attributes?.short ?? "" - )} - keepInfoVisible={keepInfoVisible} - metadata={{ - release_date: post.attributes.date, - position: "Top", - }} - /> - - ))} -
+ post.id} + langui={langui} + renderItem={({ item: post }) => ( + ({ + language: translation.language?.data?.attributes?.code, + title: translation.title, + description: translation.excerpt, + }) + )} + languages={languages} + slug={post.attributes.slug} + thumbnail={post.attributes.thumbnail?.data?.attributes} + thumbnailAspectRatio="3/2" + thumbnailForceAspectRatio + bottomChips={post.attributes.categories?.data.map( + (category) => category.attributes?.short ?? "" + )} + keepInfoVisible={keepInfoVisible} + metadata={{ + release_date: post.attributes.date, + position: "Top", + }} + /> + )} + className="grid-cols-1 desktop:grid-cols-[repeat(auto-fill,_minmax(20rem,1fr))]" + searchingTerm={searchName} + searchingBy={(post) => + `${prettySlug(post.attributes.slug)} ${post.attributes.translations + ?.map((translation) => translation?.title) + .join(" ")}` + } + />
), - [filteredItems, keepInfoVisible] + [keepInfoVisible, languages, langui, posts, searchName] ); return ( @@ -136,6 +145,7 @@ const News = ({ langui, posts, ...otherProps }: Props): JSX.Element => { contentPanel={contentPanel} subPanelIcon={Icon.Search} langui={langui} + languages={languages} {...otherProps} /> ); @@ -149,9 +159,7 @@ export default News; export const getStaticProps: GetStaticProps = async (context) => { const sdk = getReadySdk(); - const posts = await sdk.getPostsPreview({ - language_code: context.locale ?? "en", - }); + const posts = await sdk.getPostsPreview(); if (!posts.posts) return { notFound: true }; const props: Props = { ...(await getAppStaticProps(context)), @@ -175,20 +183,3 @@ const sortPosts = (posts: Props["posts"]): Props["posts"] => return dateA.localeCompare(dateB); }) .reverse(); - -// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ - -const filterItems = (posts: Props["posts"], searchName: string) => - posts.filter((post) => { - if (searchName.length > 1) { - if ( - post.attributes?.translations?.[0]?.title - .toLowerCase() - .includes(searchName.toLowerCase()) === true - ) { - return true; - } - return false; - } - return true; - }); diff --git a/src/pages/wiki/[slug]/index.tsx b/src/pages/wiki/[slug]/index.tsx index 5069bc1..4bbd722 100644 --- a/src/pages/wiki/[slug]/index.tsx +++ b/src/pages/wiki/[slug]/index.tsx @@ -1,3 +1,5 @@ +import { useCallback, useMemo } from "react"; +import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next"; import { AppLayout } from "components/AppLayout"; import { Chip } from "components/Chip"; import { HorizontalLine } from "components/HorizontalLine"; @@ -21,8 +23,6 @@ import { } from "helpers/others"; import { WikiPageWithTranslations } from "helpers/types"; import { useSmartLanguage } from "hooks/useSmartLanguage"; -import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next"; -import { useCallback, useMemo } from "react"; interface Props extends AppStaticProps { page: WikiPageWithTranslations; @@ -71,8 +71,16 @@ const WikiPage = ({ className="mb-10" /> -
+

{selectedTranslation?.title}

+ {selectedTranslation?.aliases && + selectedTranslation.aliases.length > 0 && ( +

+ {`(${selectedTranslation.aliases + .map((alias) => alias?.alias) + .join(", ")})`} +

+ )}
@@ -88,7 +96,9 @@ const WikiPage = ({ )}
-

{langui.categories}

+

+ {langui.categories} +

{page.categories?.data.map((category) => ( {category.attributes?.name} @@ -96,29 +106,38 @@ const WikiPage = ({
+ {isDefinedAndNotEmpty(selectedTranslation.summary) && (
-

{langui.summary}

+

+ {langui.summary} +

{selectedTranslation.summary}

)} {filterHasAttributes(page.definitions, ["translations"]).map( (definition, index) => ( - ({ - language: translation.language.data?.attributes?.code, - definition: translation.definition, - status: translation.status, - }))} - index={index + 1} - languages={languages} - langui={langui} - /> + <> + ({ + language: translation.language.data?.attributes?.code, + definition: translation.definition, + status: translation.status, + }))} + index={index + 1} + languages={languages} + langui={langui} + categories={filterHasAttributes( + definition.categories?.data + ).map((category) => category.attributes.short)} + /> +
+ ) )}
diff --git a/src/pages/wiki/chronology.tsx b/src/pages/wiki/chronology.tsx index 1b2f4af..dcaf051 100644 --- a/src/pages/wiki/chronology.tsx +++ b/src/pages/wiki/chronology.tsx @@ -1,3 +1,5 @@ +import { GetStaticProps } from "next"; +import { Fragment, useMemo } from "react"; import { AppLayout } from "components/AppLayout"; import { InsetBox } from "components/InsetBox"; import { NavOption } from "components/PanelComponents/NavOption"; @@ -13,8 +15,6 @@ import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps"; import { getReadySdk } from "graphql/sdk"; import { prettySlug } from "helpers/formatters"; import { filterHasAttributes, isDefined } from "helpers/others"; -import { GetStaticProps } from "next"; -import { Fragment, useMemo } from "react"; /* * ╭────────╮ @@ -49,13 +49,10 @@ const Chronology = ({ (chronologyEras[currentChronologyEraIndex].attributes?.ending_year ?? 999999) ) { - currentChronologyEraIndex += 1; + currentChronologyEraIndex++; } if ( - Object.prototype.hasOwnProperty.call( - memo[currentChronologyEraIndex], - item.attributes.year - ) + Object.hasOwn(memo[currentChronologyEraIndex], item.attributes.year) ) { memo[currentChronologyEraIndex][item.attributes.year].push(item); } else { diff --git a/src/pages/wiki/index.tsx b/src/pages/wiki/index.tsx index f0e194d..d958fcd 100644 --- a/src/pages/wiki/index.tsx +++ b/src/pages/wiki/index.tsx @@ -1,10 +1,10 @@ +import { GetStaticProps } from "next"; +import { Fragment, useMemo, useState } from "react"; import { AppLayout } from "components/AppLayout"; import { NavOption } from "components/PanelComponents/NavOption"; import { PanelHeader } from "components/PanelComponents/PanelHeader"; import { SubPanel } from "components/Panels/SubPanel"; import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps"; - -import { GetStaticProps } from "next"; import { Icon } from "components/Ico"; import { getReadySdk } from "graphql/sdk"; import { GetWikiPagesPreviewsQuery } from "graphql/generated"; @@ -12,7 +12,6 @@ import { ContentPanel, ContentPanelWidthSizes, } from "components/Panels/ContentPanel"; -import { Fragment, useMemo, useState } from "react"; import { TranslatedPreviewCard } from "components/PreviewCard"; import { HorizontalLine } from "components/HorizontalLine"; import { Button } from "components/Inputs/Button"; @@ -98,7 +97,9 @@ const Wiki = ({ /> -

{langui.special_pages}

+

+ {langui.special_pages} +

@@ -127,6 +128,12 @@ const Wiki = ({ translations={page.attributes.translations.map( (translation) => ({ title: translation?.title, + subtitle: + translation?.aliases && translation.aliases.length > 0 + ? translation.aliases + .map((alias) => alias?.alias) + .join(" | ") + : undefined, description: translation?.summary, language: translation?.language?.data?.attributes?.code, })