There's a lot to unpack here

This commit is contained in:
DrMint 2022-07-08 01:42:38 +02:00
parent ba13c736b0
commit ae25df8d72
76 changed files with 1313 additions and 1107 deletions

View File

@ -1,2 +1,9 @@
*.js
*.ts
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

View File

@ -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",
},
};

149
package-lock.json generated
View File

@ -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": {

View File

@ -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",

View File

@ -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) => {

View File

@ -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",

View File

@ -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";
/*
*

View File

@ -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<HTMLDivElement>;
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) && (
<div
className="absolute -top-3 -right-2 grid h-8 w-8 place-items-center rounded-full
bg-dark font-bold text-light transition-opacity group-hover:opacity-0"
className={cJoin(
`absolute -top-3 -right-2 grid h-8 w-8 place-items-center
rounded-full bg-dark font-bold text-light transition-opacity group-hover:opacity-0`,
cIf(size === "small", "-top-2 -right-2 h-5 w-5")
)}
>
<p className="-translate-y-[0.05em]">{badgeNumber}</p>
</div>

View File

@ -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";
/*
*

View File

@ -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<string, number>;
localesIndex: number | undefined;
onLanguageChanged: (index: number) => void;
size?: Parameters<typeof Button>[0]["size"];
}
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
@ -27,6 +28,7 @@ export const LanguageSwitcher = ({
locales,
localesIndex,
languages,
size,
onLanguageChanged,
}: Props): JSX.Element => (
<ToolTip
@ -47,6 +49,7 @@ export const LanguageSwitcher = ({
<Button
badgeNumber={locales.size > 1 ? locales.size : undefined}
icon={Icon.Translate}
size={size}
/>
</ToolTip>
);

View File

@ -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";
/*
*

View File

@ -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 => (
<div className={cJoin("flex flex-row place-content-center", className)}>
<Button
onClick={() => setPage((current) => (page > 0 ? current - 1 : current))}
className="rounded-r-none"
icon={Icon.NavigateBefore}
/>
<Button className="rounded-none border-x-0" text={(page + 1).toString()} />
<Button
onClick={() =>
setPage((current) => (page < maxPage ? page + 1 : current))
}
className="rounded-l-none"
icon={Icon.NavigateNext}
/>
</div>
<ButtonGroup
className={cJoin("flex flex-row place-content-center", className)}
buttonsProps={[
{
onClick: () => 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,
},
]}
/>
);

View File

@ -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";
/*
*

View File

@ -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";
/*
*

View File

@ -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";
/*
*

View File

@ -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 = ({
<LanguageSwitcher {...languageSwitcherProps} />
<div className="grid place-content-center place-items-center">
<p className="font-headers">{langui.status}:</p>
<p className="font-headers font-bold">{langui.status}:</p>
<ToolTip
content={getStatusDescription(selectedScan.status, langui)}
maxWidth={"20rem"}
@ -150,7 +150,8 @@ export const ScanSet = ({
{selectedScan.scanners && selectedScan.scanners.data.length > 0 && (
<div>
<p className="font-headers">{"Scanners"}:</p>
{/* TODO: Add Scanner to langui */}
<p className="font-headers font-bold">{"Scanners"}:</p>
<div className="grid place-content-center place-items-center gap-2">
{filterHasAttributes(selectedScan.scanners.data).map(
(scanner) => (
@ -168,7 +169,8 @@ export const ScanSet = ({
{selectedScan.cleaners && selectedScan.cleaners.data.length > 0 && (
<div>
<p className="font-headers">{"Cleaners"}:</p>
{/* TODO: Add Cleaners to langui */}
<p className="font-headers font-bold">{"Cleaners"}:</p>
<div className="grid place-content-center place-items-center gap-2">
{filterHasAttributes(selectedScan.cleaners.data).map(
(cleaner) => (
@ -187,7 +189,8 @@ export const ScanSet = ({
{selectedScan.typesetters &&
selectedScan.typesetters.data.length > 0 && (
<div>
<p className="font-headers">{"Typesetters"}:</p>
{/* TODO: Add Cleaners to Typesetters */}
<p className="font-headers font-bold">{"Typesetters"}:</p>
<div className="grid place-content-center place-items-center gap-2">
{filterHasAttributes(selectedScan.typesetters.data).map(
(typesetter) => (
@ -205,6 +208,7 @@ export const ScanSet = ({
{isDefinedAndNotEmpty(selectedScan.notes) && (
<ToolTip content={selectedScan.notes}>
{/* TODO: Add Notes to Typesetters */}
<Chip>{"Notes"}</Chip>
</ToolTip>
)}

View File

@ -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 = ({
<div>
<div
className="flex flex-row flex-wrap place-items-center
gap-6 pt-10 text-base first-of-type:pt-0"
gap-6 pt-10 text-base first-of-type:pt-0"
>
<h2 id="cover" className="text-2xl">
<h2 id={"cover"} className="text-2xl">
{/* TODO: Add Cover to langui */}
{"Cover"}
</h2>
@ -91,7 +92,7 @@ export const ScanSetCover = ({
<LanguageSwitcher {...languageSwitcherProps} />
<div className="grid place-content-center place-items-center">
<p className="font-headers">{langui.status}:</p>
<p className="font-headers font-bold">{langui.status}:</p>
<ToolTip
content={getStatusDescription(selectedScan.status, langui)}
maxWidth={"20rem"}
@ -102,7 +103,8 @@ export const ScanSetCover = ({
{selectedScan.scanners && selectedScan.scanners.data.length > 0 && (
<div>
<p className="font-headers">{"Scanners"}:</p>
{/* TODO: Add Scanner to langui */}
<p className="font-headers font-bold">{"Scanners"}:</p>
<div className="grid place-content-center place-items-center gap-2">
{filterHasAttributes(selectedScan.scanners.data).map(
(scanner) => (
@ -120,7 +122,8 @@ export const ScanSetCover = ({
{selectedScan.cleaners && selectedScan.cleaners.data.length > 0 && (
<div>
<p className="font-headers">{"Cleaners"}:</p>
{/* TODO: Add Cleaners to langui */}
<p className="font-headers font-bold">{"Cleaners"}:</p>
<div className="grid place-content-center place-items-center gap-2">
{filterHasAttributes(selectedScan.cleaners.data).map(
(cleaner) => (
@ -139,7 +142,8 @@ export const ScanSetCover = ({
{selectedScan.typesetters &&
selectedScan.typesetters.data.length > 0 && (
<div>
<p className="font-headers">{"Typesetters"}:</p>
{/* TODO: Add Cleaners to Typesetters */}
<p className="font-headers font-bold">{"Typesetters"}:</p>
<div className="grid place-content-center place-items-center gap-2">
{filterHasAttributes(selectedScan.typesetters.data).map(
(typesetter) => (

View File

@ -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 `<SceneBreak id="scene-break-${scenebreakIndex}">`;
}
@ -506,7 +506,7 @@ const markdawnHeadersParser = (
let index = 2;
while (visitedSlugs.includes(newSlug)) {
newSlug = `${slug}-${index}`;
index += 1;
index++;
}
visitedSlugs.push(newSlug);
return `<h${headerLevel} id="${newSlug}">${lineText}</h${headerLevel}>`;
@ -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("<h5 id=")) {
@ -573,7 +573,7 @@ const getTocFromMarkdawn = (text: string, title?: string): TocInterface => {
slug: getSlug(line),
children: [],
});
h5 += 1;
h5++;
scenebreak = 0;
} else if (h5 >= 0 && line.startsWith("<h6 id=")) {
toc.children[h2].children[h3].children[h4].children[h5].children.push({
@ -582,8 +582,8 @@ const getTocFromMarkdawn = (text: string, title?: string): TocInterface => {
children: [],
});
} else if (line.startsWith(`<SceneBreak`)) {
scenebreak += 1;
scenebreakIndex += 1;
scenebreak++;
scenebreakIndex++;
const child = {
title: `Scene break ${scenebreak}`,

View File

@ -1,9 +1,9 @@
import { useRouter } from "next/router";
import { MouseEventHandler, useMemo } from "react";
import { Ico, Icon } from "components/Ico";
import { ToolTip } from "components/ToolTip";
import { cJoin, cIf } from "helpers/className";
import { isDefinedAndNotEmpty } from "helpers/others";
import { useRouter } from "next/router";
import { MouseEventHandler, useMemo } from "react";
/*
*

View File

@ -1,3 +1,5 @@
import Markdown from "markdown-to-jsx";
import Link from "next/link";
import { HorizontalLine } from "components/HorizontalLine";
import { Button } from "components/Inputs/Button";
import { NavOption } from "components/PanelComponents/NavOption";
@ -6,8 +8,6 @@ import { useAppLayout } from "contexts/AppLayoutContext";
import { AppStaticProps } from "graphql/getAppStaticProps";
import { useMediaDesktop } from "hooks/useMediaQuery";
import Markdown from "markdown-to-jsx";
import Link from "next/link";
import { Icon } from "components/Ico";
import { cIf, cJoin } from "helpers/className";
import { isDefinedAndNotEmpty } from "helpers/others";

View File

@ -1,7 +1,7 @@
import { useAppLayout } from "contexts/AppLayoutContext";
import { cIf, cJoin } from "helpers/className";
import { Dispatch, SetStateAction, useEffect } from "react";
import Hotkeys from "react-hot-keys";
import { useAppLayout } from "contexts/AppLayoutContext";
import { cIf, cJoin } from "helpers/className";
/*
*

View File

@ -1,9 +1,3 @@
import { AppStaticProps } from "graphql/getAppStaticProps";
import { getDescription } from "helpers/description";
import { prettySlug } from "helpers/formatters";
import { filterHasAttributes, getStatusDescription } from "helpers/others";
import { PostWithTranslations } from "helpers/types";
import { useSmartLanguage } from "hooks/useSmartLanguage";
import { Fragment, useCallback, useMemo } from "react";
import { AppLayout } from "./AppLayout";
import { Chip } from "./Chip";
@ -15,6 +9,12 @@ import { SubPanel } from "./Panels/SubPanel";
import { RecorderChip } from "./RecorderChip";
import { ThumbnailHeader } from "./ThumbnailHeader";
import { ToolTip } from "./ToolTip";
import { useSmartLanguage } from "hooks/useSmartLanguage";
import { PostWithTranslations } from "helpers/types";
import { filterHasAttributes, getStatusDescription } from "helpers/others";
import { prettySlug } from "helpers/formatters";
import { getDescription } from "helpers/description";
import { AppStaticProps } from "graphql/getAppStaticProps";
/*
*
@ -95,7 +95,7 @@ export const PostPage = ({
<>
{selectedTranslation && (
<div className="grid grid-flow-col place-content-center place-items-center gap-2">
<p className="font-headers">{langui.status}:</p>
<p className="font-headers font-bold">{langui.status}:</p>
<ToolTip
content={getStatusDescription(
@ -111,7 +111,7 @@ export const PostPage = ({
{post.authors && post.authors.data.length > 0 && (
<div>
<p className="font-headers">{"Authors"}:</p>
<p className="font-headers font-bold">{"Authors"}:</p>
<div className="grid place-content-center place-items-center gap-2">
{filterHasAttributes(post.authors.data).map((author) => (
<Fragment key={author.id}>

View File

@ -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 = ({
>
<Ico
icon={Icon.PlayCircleOutline}
className="text-6xl opacity-0 transition-opacity group-hover:opacity-100"
className="!text-6xl opacity-0 transition-opacity group-hover:opacity-100"
/>
</div>
<div
@ -271,7 +271,7 @@ export const PreviewCard = ({
<p className="mb-1 break-words leading-none">{pre_title}</p>
)}
{title && (
<p className="break-words font-headers font-bold text-lg leading-none">
<p className="break-words font-headers text-lg font-bold leading-none">
{title}
</p>
)}

View File

@ -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 = ({
<div className="my-1 flex flex-col">
{pre_title && <p className="mb-1 leading-none">{pre_title}</p>}
{title && (
<p className="font-headers text-lg leading-none">{title}</p>
<p className="font-headers text-lg font-bold leading-none">
{title}
</p>
)}
{subtitle && <p className="leading-none">{subtitle}</p>}
</div>

View File

@ -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";
/*
*

View File

@ -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<T> {
// 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 = <T,>({
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<T>): JSX.Element => {
const [page, setPage] = useState(0);
useScrollTopOnChange(AnchorIds.ContentPanel, [page], paginationScroolTop);
type Group = Map<string, T[]>;
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 && (
<PageSelector
maxPage={pageCount}
page={page}
setPage={setPage}
className="mb-12"
/>
)}
{groupedList.size > 0
? iterateMap(
groupedList,
(name, groupItems) =>
groupItems.length > 0 && (
<Fragment key={name}>
{name.length > 0 && (
<h2
className="flex flex-row place-items-center gap-2 pb-2 pt-10 text-2xl
first-of-type:pt-0"
>
{name}
<Chip>{`${groupItems.length} ${
groupItems.length <= 1
? langui.result?.toLowerCase() ?? ""
: langui.results?.toLowerCase() ?? ""
}`}</Chip>
</h2>
)}
<div
className={cJoin(
`grid items-start gap-8 border-b-[3px] border-dotted pb-12
last-of-type:border-0 mobile:gap-4`,
className
)}
>
{groupItems.map((item) => (
<RenderItem item={item} key={getItemId(item)} />
))}
</div>
</Fragment>
),
([a], [b]) => groupSortingFunction(a, b)
)
: isDefined(RenderWhenEmpty) && <RenderWhenEmpty />}
{pageCount > 1 && paginationSelectorBottom && (
<PageSelector
maxPage={pageCount}
page={page}
setPage={setPage}
className="mt-12"
/>
)}
</>
);
};

View File

@ -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";

View File

@ -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<GetChronologyItemsQuery["chronologyItems"]>["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,

View File

@ -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,

View File

@ -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 (
<>
<div className="flex place-items-center gap-2">
<p className="font-headers text-lg">{`${langui.definition} ${index}`}</p>
{selectedTranslation?.status && (
<ToolTip
content={getStatusDescription(selectedTranslation.status, langui)}
maxWidth={"20rem"}
>
<Chip>{selectedTranslation.status}</Chip>
</ToolTip>
)}
<div className="flex flex-wrap place-items-center gap-2">
<p className="font-headers text-lg font-bold">{`${langui.definition} ${index}`}</p>
{translations.length > 1 && (
<LanguageSwitcher {...languageSwitcherProps} />
<>
<Separator />
<LanguageSwitcher {...languageSwitcherProps} size={"small"} />
</>
)}
{selectedTranslation?.status && (
<>
<Separator />
<ToolTip
content={getStatusDescription(selectedTranslation.status, langui)}
maxWidth={"20rem"}
>
<Chip>{selectedTranslation.status}</Chip>
</ToolTip>
</>
)}
{categories.length > 0 && (
<>
<Separator />
<div className="flex flex-row gap-1">
{categories.map((category, categoryIndex) => (
<Chip key={categoryIndex}>{category}</Chip>
))}
</div>
</>
)}
</div>
<p className="italic">{`${langui.source}: ${source}`}</p>
<p>{selectedTranslation?.definition}</p>
</>
);
};
export default DefinitionCard;
/*
*
* PRIVATE COMPONENTS
*/
const Separator = () => <div className="mx-1 h-5 w-[1px] bg-dark" />;

View File

@ -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<

View File

@ -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<AppStaticProps> => {
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

View File

@ -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 };
};
};

View File

@ -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 {

View File

@ -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 {

View File

@ -1,7 +1,7 @@
import { GraphQLClient } from "graphql-request";
import { getSdk } from "graphql/generated";
export const getReadySdk = () => {
export const getReadySdk = (): ReturnType<typeof getSdk> => {
const client = new GraphQLClient(process.env.URL_GRAPHQL ?? "", {
headers: { Authorization: `Bearer ${process.env.ACCESS_TOKEN}` },
});

View File

@ -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(" ");

View File

@ -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(" ");
};

View File

@ -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")

View File

@ -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 ?? "",
};
};

View File

@ -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<typeof LibraryPage>[0]["items"];
type GroupLibraryItems = Map<string, Items>;
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");

View File

@ -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);

View File

@ -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): t is NonNullable<T> =>
t !== null && t !== undefined;
export const isUndefined = <T>(
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: T[] | undefined | null): NonNullable<T>[] =>
export const filterDefined = <T>(t: T[] | null | undefined): NonNullable<T>[] =>
isUndefined(t)
? []
: (t.filter((item) => isDefined(item)) as NonNullable<T>[]);
export const filterHasAttributes = <T, P extends keyof NonNullable<T>>(
t: T[] | undefined | null,
t: T[] | null | undefined,
attributes?: P[]
): SelectiveRequiredNonNullable<NonNullable<T>, P>[] =>
isUndefined(t)
@ -89,27 +89,24 @@ export const filterHasAttributes = <T, P extends keyof NonNullable<T>>(
export const iterateMap = <K, V, U>(
map: Map<K, V>,
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 = <K, V>(
map: Map<K, V>,
sourceIndex: number,
targetIndex: number
) => new Map(arrayMove([...map], sourceIndex, targetIndex));
): Map<K, V> => new Map(arrayMove([...map], sourceIndex, targetIndex));
const arrayMove = <T>(arr: T[], sourceIndex: number, targetIndex: number) => {
arr.splice(targetIndex, 0, arr.splice(sourceIndex, 1)[0]);
return arr;
};
export const mapRemoveEmptyValues = <K, V>(groups: Map<K, V[]>): Map<K, V[]> =>
new Map([...groups].filter(([_, items]) => items.length > 0));

View File

@ -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,

View File

@ -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<boolean>(getMatches(query));
const [matches, setMatches] = useState<boolean>(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)");

View File

@ -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]);
};

View File

@ -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<T> {
items: T[];
languages: AppStaticProps["languages"];

View File

@ -1,5 +1,5 @@
import { isDefined } from "helpers/others";
import { useEffect, useState } from "react";
import { isDefined } from "helpers/others";
export const useStateWithLocalStorage = <T>(
key: string,

View File

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

View File

@ -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";
/*
*

View File

@ -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";
/*
*

View File

@ -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 => (

View File

@ -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";
/*
*

View File

@ -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";
/*
*

View File

@ -17,7 +17,7 @@ export interface RequestMailProps {
const Mail = async (
req: NextApiRequest,
res: NextApiResponse<ResponseMailProps>
) => {
): Promise<void> => {
if (req.method === "POST") {
const body = req.body as RequestMailProps;

View File

@ -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<ResponseMailProps>
) => {
): 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;

View File

@ -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";
/*
*

View File

@ -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";

View File

@ -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}
/>
<TextInput
className="mb-6 w-full"
placeholder={langui.search_title ?? undefined}
state={searchName}
setState={setSearchName}
/>
{hoverable && (
<WithLabel
label={langui.always_show_info}
@ -82,56 +85,53 @@ const Videos = ({ langui, videos, ...otherProps }: Props): JSX.Element => {
)}
</SubPanel>
),
[hoverable, keepInfoVisible, langui]
[hoverable, keepInfoVisible, langui, searchName]
);
const contentPanel = useMemo(
() => (
<ContentPanel width={ContentPanelWidthSizes.Full}>
<PageSelector
maxPage={Math.floor(videos.length / ITEM_PER_PAGE)}
page={page}
setPage={setPage}
className="mb-12"
/>
<div
className="grid items-start gap-8 border-b-[3px] border-dotted pb-12 last-of-type:border-0
desktop:grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))] mobile:grid-cols-2
thin:grid-cols-1"
>
{filterHasAttributes(paginatedVideos[page]).map((video) => (
<Fragment key={video.id}>
<SmartList
items={filterHasAttributes(videos)}
getItemId={(item) => item.id}
renderItem={({ item }) => (
<>
<PreviewCard
href={`/archives/videos/v/${video.attributes.uid}`}
title={video.attributes.title}
thumbnail={getVideoThumbnailURL(video.attributes.uid)}
href={`/archives/videos/v/${item.attributes.uid}`}
title={item.attributes.title}
thumbnail={getVideoThumbnailURL(item.attributes.uid)}
thumbnailAspectRatio="16/9"
thumbnailForceAspectRatio
keepInfoVisible={keepInfoVisible}
metadata={{
release_date: video.attributes.published_date,
views: video.attributes.views,
author: video.attributes.channel?.data?.attributes?.title,
release_date: item.attributes.published_date,
views: item.attributes.views,
author: item.attributes.channel?.data?.attributes?.title,
position: "Top",
}}
hoverlay={{
__typename: "Video",
duration: video.attributes.duration,
duration: item.attributes.duration,
}}
/>
</Fragment>
))}
</div>
<PageSelector
maxPage={Math.floor(videos.length / ITEM_PER_PAGE)}
page={page}
setPage={setPage}
className="mt-12"
</>
)}
renderWhenEmpty={() => (
<ContentPlaceholder
message={langui.no_results_message ?? "No results"}
icon={Icon.ChevronLeft}
/>
)}
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}
/>
</ContentPanel>
),
[keepInfoVisible, page, paginatedVideos, videos.length]
[keepInfoVisible, langui, searchName, videos]
);
return (
<AppLayout

View File

@ -1,3 +1,5 @@
import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next";
import { useMemo } from "react";
import { AppLayout } from "components/AppLayout";
import { HorizontalLine } from "components/HorizontalLine";
import { Ico, Icon } from "components/Ico";
@ -21,8 +23,6 @@ import { prettyDate, prettyShortenNumber } from "helpers/formatters";
import { filterHasAttributes, isDefined } from "helpers/others";
import { getVideoFile } from "helpers/videos";
import { useMediaMobile } from "hooks/useMediaQuery";
import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next";
import { useMemo } from "react";
/*
*

View File

@ -1,11 +1,10 @@
import { GetStaticProps } from "next";
import { useMemo } from "react";
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";
import { useMemo } from "react";
/*
*

View File

@ -1,3 +1,5 @@
import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next";
import { Fragment, useCallback, useMemo } from "react";
import { AppLayout } from "components/AppLayout";
import { Chip } from "components/Chip";
import { HorizontalLine } from "components/HorizontalLine";
@ -34,8 +36,6 @@ import { ContentWithTranslations } from "helpers/types";
import { useMediaMobile } from "hooks/useMediaQuery";
import { AnchorIds, useScrollTopOnChange } from "hooks/useScrollTopOnChange";
import { useSmartLanguage } from "hooks/useSmartLanguage";
import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next";
import { Fragment, useCallback, useMemo } from "react";
/*
*
@ -111,7 +111,9 @@ const Content = ({
.code !==
selectedTranslation.language?.data?.attributes?.code && (
<div className="grid place-items-center gap-2">
<p className="font-headers">{langui.source_language}:</p>
<p className="font-headers font-bold">
{langui.source_language}:
</p>
<Chip>
{prettyLanguage(
selectedTranslation.text_set.source_language.data.attributes
@ -123,7 +125,7 @@ const Content = ({
)}
<div className="grid grid-flow-col place-content-center place-items-center gap-2">
<p className="font-headers">{langui.status}:</p>
<p className="font-headers font-bold">{langui.status}:</p>
<ToolTip
content={getStatusDescription(
@ -139,7 +141,9 @@ const Content = ({
{selectedTranslation.text_set.transcribers &&
selectedTranslation.text_set.transcribers.data.length > 0 && (
<div>
<p className="font-headers">{langui.transcribers}:</p>
<p className="font-headers font-bold">
{langui.transcribers}:
</p>
<div className="grid place-content-center place-items-center gap-2">
{filterHasAttributes(
selectedTranslation.text_set.transcribers.data
@ -158,7 +162,9 @@ const Content = ({
{selectedTranslation.text_set.translators &&
selectedTranslation.text_set.translators.data.length > 0 && (
<div>
<p className="font-headers">{langui.translators}:</p>
<p className="font-headers font-bold">
{langui.translators}:
</p>
<div className="grid place-content-center place-items-center gap-2">
{filterHasAttributes(
selectedTranslation.text_set.translators.data
@ -177,7 +183,9 @@ const Content = ({
{selectedTranslation.text_set.proofreaders &&
selectedTranslation.text_set.proofreaders.data.length > 0 && (
<div>
<p className="font-headers">{langui.proofreaders}:</p>
<p className="font-headers font-bold">
{langui.proofreaders}:
</p>
<div className="grid place-content-center place-items-center gap-2">
{filterHasAttributes(
selectedTranslation.text_set.proofreaders.data
@ -195,7 +203,7 @@ const Content = ({
{isDefinedAndNotEmpty(selectedTranslation.text_set.notes) && (
<div>
<p className="font-headers">{"Notes"}:</p>
<p className="font-headers font-bold">{"Notes"}:</p>
<div className="grid place-content-center place-items-center gap-2">
<Markdawn text={selectedTranslation.text_set.notes} />
</div>
@ -209,7 +217,9 @@ const Content = ({
<>
<HorizontalLine />
<div>
<p className="font-headers text-2xl">{langui.source}</p>
<p className="font-headers text-2xl font-bold">
{langui.source}
</p>
<div className="mt-6 grid place-items-center gap-6 text-left">
{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];

View File

@ -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<GetContentsQuery["contents"]>["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(
() => (
<ContentPanel width={ContentPanelWidthSizes.Full}>
{groups.size === 0 && (
<ContentPlaceholder
message={langui.no_results_message ?? "No results"}
icon={Icon.ChevronLeft}
/>
)}
{iterateMap(
groups,
(name, items, index) =>
items.length > 0 && (
<Fragment key={index}>
{name && (
<h2
className="flex flex-row place-items-center gap-2 pb-2 pt-10 text-2xl
first-of-type:pt-0"
>
{name}
<Chip>{`${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() ?? ""
}`}</Chip>
</h2>
)}
<div
className="grid grid-cols-2 items-end gap-8
desktop:grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))] mobile:gap-4"
>
{filterHasAttributes(items).map((item) => (
<Fragment key={item.id}>
{item.attributes.translations && (
<TranslatedPreviewCard
href={`/contents/${item.attributes.slug}`}
translations={item.attributes.translations.map(
(translation) => ({
pre_title: translation?.pre_title,
title: translation?.title,
subtitle: translation?.subtitle,
language:
translation?.language?.data?.attributes?.code,
})
)}
slug={item.attributes.slug}
languages={languages}
thumbnail={
item.attributes.thumbnail?.data?.attributes
}
thumbnailAspectRatio="3/2"
thumbnailForceAspectRatio
stackNumber={
effectiveCombineRelatedContent &&
item.attributes.group?.data?.attributes?.combine ===
true
? item.attributes.group.data.attributes.contents
?.data.length
: 0
}
topChips={
item.attributes.type?.data?.attributes
? [
item.attributes.type.data.attributes
.titles?.[0]
? item.attributes.type.data.attributes
.titles[0]?.title
: prettySlug(
item.attributes.type.data.attributes
.slug
),
]
: undefined
}
bottomChips={item.attributes.categories?.data.map(
(category) => category.attributes?.short ?? ""
)}
keepInfoVisible={keepInfoVisible}
/>
)}
</Fragment>
))}
</div>
</Fragment>
)
)}
<SmartList
items={filterHasAttributes(contents)}
getItemId={(item) => item.id}
renderItem={({ item }) => (
<>
{item.attributes.translations && (
<TranslatedPreviewCard
href={`/contents/${item.attributes.slug}`}
translations={item.attributes.translations.map(
(translation) => ({
pre_title: translation?.pre_title,
title: translation?.title,
subtitle: translation?.subtitle,
language: translation?.language?.data?.attributes?.code,
})
)}
slug={item.attributes.slug}
languages={languages}
thumbnail={item.attributes.thumbnail?.data?.attributes}
thumbnailAspectRatio="3/2"
thumbnailForceAspectRatio
stackNumber={
effectiveCombineRelatedContent &&
item.attributes.group?.data?.attributes?.combine === true
? item.attributes.group.data.attributes.contents?.data
.length
: 0
}
topChips={
item.attributes.type?.data?.attributes
? [
item.attributes.type.data.attributes.titles?.[0]
? item.attributes.type.data.attributes.titles[0]
?.title
: prettySlug(
item.attributes.type.data.attributes.slug
),
]
: undefined
}
bottomChips={item.attributes.categories?.data.map(
(category) => category.attributes?.short ?? ""
)}
keepInfoVisible={keepInfoVisible}
/>
)}
</>
)}
renderWhenEmpty={() => (
<ContentPlaceholder
message={langui.no_results_message ?? "No results"}
icon={Icon.ChevronLeft}
/>
)}
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}
/>
</ContentPanel>
),
[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<string, Props["contents"]>;
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;
});

View File

@ -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";
/*
*

View File

@ -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

View File

@ -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";
/*

View File

@ -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";
/*
*

View File

@ -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 (
<div
className={cJoin(
@ -722,7 +724,7 @@ const ContentLine = ({
selectedTranslation.subtitle
)
: content
? prettySlug(content.slug)
? prettySlug(content.slug, parentSlug)
: prettySlug(slug, parentSlug)}
</h3>
</a>

View File

@ -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,

View File

@ -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(
() => (
<ContentPanel width={ContentPanelWidthSizes.Full}>
{groups.size === 0 && (
<ContentPlaceholder
message={langui.no_results_message ?? "No results"}
icon={Icon.ChevronLeft}
/>
)}
{iterateMap(groups, (name, items) => (
<Fragment key={name}>
{isDefinedAndNotEmpty(name) && (
<h2
className="flex flex-row place-items-center gap-2
pb-2 pt-10 text-2xl first-of-type:pt-0"
>
{name}
<Chip>{`${items.length} ${
items.length <= 1
? langui.result?.toLowerCase() ?? "result"
: langui.results?.toLowerCase() ?? "results"
}`}</Chip>
</h2>
)}
<div
className="grid items-end gap-8 border-b-[3px] border-dotted pb-12
last-of-type:border-0 desktop:grid-cols-[repeat(auto-fill,_minmax(13rem,1fr))]
mobile:grid-cols-2 mobile:gap-4"
>
{filterHasAttributes(items).map((item) => (
<Fragment key={item.id}>
<PreviewCard
href={`/library/${item.attributes.slug}`}
title={item.attributes.title}
subtitle={item.attributes.subtitle}
thumbnail={item.attributes.thumbnail?.data?.attributes}
thumbnailAspectRatio="21/29.7"
thumbnailRounded={false}
keepInfoVisible={keepInfoVisible}
topChips={
item.attributes.metadata &&
item.attributes.metadata.length > 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]) && (
<PreviewCardCTAs id={item.id} langui={langui} />
)
}
/>
</Fragment>
))}
</div>
</Fragment>
))}
<SmartList
items={filterHasAttributes(items)}
getItemId={(item) => item.id}
renderItem={({ item }) => (
<PreviewCard
href={`/library/${item.attributes.slug}`}
title={item.attributes.title}
subtitle={item.attributes.subtitle}
thumbnail={item.attributes.thumbnail?.data?.attributes}
thumbnailAspectRatio="21/29.7"
thumbnailRounded={false}
keepInfoVisible={keepInfoVisible}
topChips={
item.attributes.metadata &&
item.attributes.metadata.length > 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]) && (
<PreviewCardCTAs id={item.id} langui={langui} />
)
}
/>
)}
renderWhenEmpty={() => (
<ContentPlaceholder
message={langui.no_results_message ?? "No results"}
icon={Icon.ChevronLeft}
/>
)}
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}
/>
</ContentPanel>
),
[currencies, groups, keepInfoVisible, langui]
[
currencies,
filteringFunction,
groupingFunction,
items,
keepInfoVisible,
langui,
searchName,
sortingFunction,
]
);
return (

View File

@ -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";
/*

View File

@ -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";
/*
*

View File

@ -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<GetPostsPreviewQuery["posts"]>["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(
() => (
<SubPanel>
@ -96,37 +96,46 @@ const News = ({ langui, posts, ...otherProps }: Props): JSX.Element => {
const contentPanel = useMemo(
() => (
<ContentPanel width={ContentPanelWidthSizes.Full}>
<div
className="grid grid-cols-1 items-end gap-8
desktop:grid-cols-[repeat(auto-fill,_minmax(20rem,1fr))]"
>
{filterHasAttributes(filteredItems).map((post) => (
<Fragment key={post.id}>
<PreviewCard
href={`/news/${post.attributes.slug}`}
title={
post.attributes.translations?.[0]?.title ??
prettySlug(post.attributes.slug)
}
description={post.attributes.translations?.[0]?.excerpt}
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",
}}
/>
</Fragment>
))}
</div>
<SmartList
items={filterHasAttributes(posts)}
getItemId={(post) => post.id}
langui={langui}
renderItem={({ item: post }) => (
<TranslatedPreviewCard
href={`/news/${post.attributes.slug}`}
translations={filterDefined(post.attributes.translations).map(
(translation) => ({
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(" ")}`
}
/>
</ContentPanel>
),
[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;
});

View File

@ -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"
/>
<div className="flex place-content-center gap-4">
<div className="flex place-content-center gap-3">
<h1 className="text-center text-3xl">{selectedTranslation?.title}</h1>
{selectedTranslation?.aliases &&
selectedTranslation.aliases.length > 0 && (
<p className="mr-3 text-center text-2xl">
{`(${selectedTranslation.aliases
.map((alias) => alias?.alias)
.join(", ")})`}
</p>
)}
<LanguageSwitcher {...languageSwitcherProps} />
</div>
@ -88,7 +96,9 @@ const WikiPage = ({
<Img image={page.thumbnail.data.attributes} />
)}
<div className="my-4 grid gap-4 p-4">
<p className="font-headers text-xl">{langui.categories}</p>
<p className="font-headers text-xl font-bold">
{langui.categories}
</p>
<div className="flex flex-row flex-wrap place-content-center gap-2">
{page.categories?.data.map((category) => (
<Chip key={category.id}>{category.attributes?.name}</Chip>
@ -96,29 +106,38 @@ const WikiPage = ({
</div>
</div>
</div>
{isDefinedAndNotEmpty(selectedTranslation.summary) && (
<div className="mb-6">
<p className="font-headers text-lg">{langui.summary}</p>
<p className="font-headers text-lg font-bold">
{langui.summary}
</p>
<p>{selectedTranslation.summary}</p>
</div>
)}
{filterHasAttributes(page.definitions, ["translations"]).map(
(definition, index) => (
<DefinitionCard
key={index}
source={definition.source?.data?.attributes?.name}
translations={filterHasAttributes(
definition.translations
).map((translation) => ({
language: translation.language.data?.attributes?.code,
definition: translation.definition,
status: translation.status,
}))}
index={index + 1}
languages={languages}
langui={langui}
/>
<>
<DefinitionCard
key={index}
source={definition.source?.data?.attributes?.name}
translations={filterHasAttributes(
definition.translations
).map((translation) => ({
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)}
/>
<br />
</>
)
)}
</div>

View File

@ -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 {

View File

@ -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 = ({
/>
<HorizontalLine />
<p className="mb-4 font-headers text-xl">{langui.special_pages}</p>
<p className="mb-4 font-headers text-xl font-bold">
{langui.special_pages}
</p>
<NavOption title={langui.chronology} url="/wiki/chronology" border />
</SubPanel>
@ -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,
})