Merge branch 'main' of github.com:Accords-Library/accords-library.com
This commit is contained in:
commit
afbcbe7a3c
2
.eslintignore
Normal file
2
.eslintignore
Normal file
@ -0,0 +1,2 @@
|
||||
*.js
|
||||
*.ts
|
214
.eslintrc.js
Normal file
214
.eslintrc.js
Normal file
@ -0,0 +1,214 @@
|
||||
module.exports = {
|
||||
parser: "@typescript-eslint/parser",
|
||||
parserOptions: {
|
||||
project: `./tsconfig.json`,
|
||||
},
|
||||
plugins: ["@typescript-eslint"],
|
||||
extends: [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"next/core-web-vitals",
|
||||
],
|
||||
rules: {
|
||||
/* POSSIBLES PROBLEMS */
|
||||
|
||||
// "array-callback-return": "error",
|
||||
"no-await-in-loop": "error",
|
||||
"no-constructor-return": "error",
|
||||
"no-promise-executor-return": "error",
|
||||
"no-self-compare": "error",
|
||||
"no-template-curly-in-string": "error",
|
||||
"no-unmodified-loop-condition": "error",
|
||||
"no-unreachable-loop": "error",
|
||||
"no-unused-private-class-members": "error",
|
||||
// "no-use-before-define": "error",
|
||||
"require-atomic-updates": "error",
|
||||
|
||||
/* SUGGESTIONS */
|
||||
|
||||
"accessor-pairs": "warn",
|
||||
"arrow-body-style": "warn",
|
||||
"block-scoped-var": "warn",
|
||||
// camelcase: "warn",
|
||||
// "capitalized-comments": "warn",
|
||||
// "class-methods-use-this": "warn",
|
||||
// complexity: "warn",
|
||||
"consistent-return": "warn",
|
||||
"consistent-this": "warn",
|
||||
// curly: "warn",
|
||||
"default-case": "warn",
|
||||
"default-case-last": "warn",
|
||||
eqeqeq: "error",
|
||||
"func-name-matching": "warn",
|
||||
"func-names": "warn",
|
||||
"func-style": ["warn", "declaration"],
|
||||
"grouped-accessor-pairs": "warn",
|
||||
"guard-for-in": "warn",
|
||||
"id-denylist": ["error", "data", "err", "e", "cb", "callback", "i"],
|
||||
// "id-length": "warn",
|
||||
"id-match": "warn",
|
||||
"max-classes-per-file": ["error", 1],
|
||||
// "max-depth": ["warn", 4],
|
||||
// "max-lines": "warn",
|
||||
// "max-lines-per-function": "warn",
|
||||
// "max-nested-callbacks": "warn",
|
||||
// "max-params": "warn",
|
||||
// "max-statements": "warn",
|
||||
"multiline-comment-style": "warn",
|
||||
"new-cap": "warn",
|
||||
"no-alert": "warn",
|
||||
"no-bitwise": "warn",
|
||||
"no-caller": "warn",
|
||||
"no-confusing-arrow": "warn",
|
||||
"no-continue": "warn",
|
||||
"no-else-return": "warn",
|
||||
"no-eq-null": "warn",
|
||||
"no-eval": "warn",
|
||||
"no-extend-native": "warn",
|
||||
"no-extra-bind": "warn",
|
||||
"no-floating-decimal": "warn",
|
||||
"no-implicit-coercion": "warn",
|
||||
"no-implicit-globals": "warn",
|
||||
"no-inline-comments": "warn",
|
||||
"no-iterator": "warn",
|
||||
"no-label-var": "warn",
|
||||
"no-labels": "warn",
|
||||
"no-lone-blocks": "warn",
|
||||
"no-lonely-if": "warn",
|
||||
// "no-magic-numbers": "warn",
|
||||
"no-mixed-operators": "warn",
|
||||
"no-multi-assign": "warn",
|
||||
"no-multi-str": "warn",
|
||||
"no-negated-condition": "warn",
|
||||
// "no-nested-ternary": "warn",
|
||||
"no-new": "warn",
|
||||
"no-new-func": "warn",
|
||||
"no-new-object": "warn",
|
||||
"no-new-wrappers": "warn",
|
||||
"no-octal-escape": "warn",
|
||||
"no-param-reassign": "warn",
|
||||
"no-plusplus": "warn",
|
||||
"no-proto": "warn",
|
||||
"no-restricted-exports": "warn",
|
||||
"no-restricted-globals": "warn",
|
||||
"no-restricted-imports": "warn",
|
||||
"no-restricted-properties": "warn",
|
||||
"no-restricted-syntax": "warn",
|
||||
"no-return-assign": "warn",
|
||||
// "no-return-await": "warn",
|
||||
"no-script-url": "warn",
|
||||
"no-sequences": "warn",
|
||||
// "no-ternary": "off",
|
||||
"no-throw-literal": "warn",
|
||||
"no-undef": "off",
|
||||
"no-undef-init": "warn",
|
||||
// "no-undefined": "warn",
|
||||
// "no-underscore-dangle": "warn",
|
||||
"no-unneeded-ternary": "warn",
|
||||
"no-useless-call": "warn",
|
||||
"no-useless-computed-key": "warn",
|
||||
"no-useless-concat": "warn",
|
||||
"no-useless-rename": "warn",
|
||||
"no-useless-return": "warn",
|
||||
"no-var": "warn",
|
||||
"no-void": "warn",
|
||||
"no-warning-comments": "warn",
|
||||
// "object-shorthand": "warn",
|
||||
"operator-assignment": "warn",
|
||||
"prefer-arrow-callback": "warn",
|
||||
"prefer-const": "warn",
|
||||
"prefer-destructuring": ["warn", { array: false, object: true }],
|
||||
"prefer-exponentiation-operator": "warn",
|
||||
"prefer-named-capture-group": "warn",
|
||||
"prefer-numeric-literals": "warn",
|
||||
// "prefer-object-has-own": "warn",
|
||||
"prefer-object-spread": "warn",
|
||||
"prefer-promise-reject-errors": "warn",
|
||||
"prefer-regex-literals": "warn",
|
||||
"prefer-rest-params": "warn",
|
||||
"prefer-spread": "warn",
|
||||
"prefer-template": "warn",
|
||||
// "quote-props": "warn",
|
||||
radix: "warn",
|
||||
"require-unicode-regexp": "warn",
|
||||
// "sort-imports": "warn",
|
||||
// "sort-keys": "warn",
|
||||
"sort-vars": "warn",
|
||||
"spaced-comment": "warn",
|
||||
strict: "warn",
|
||||
"symbol-description": "warn",
|
||||
"vars-on-top": "warn",
|
||||
yoda: "warn",
|
||||
|
||||
/* TYPESCRIPT */
|
||||
|
||||
"@typescript-eslint/array-type": "warn",
|
||||
"@typescript-eslint/ban-tslint-comment": "warn",
|
||||
"@typescript-eslint/class-literal-property-style": "warn",
|
||||
"@typescript-eslint/consistent-indexed-object-style": "warn",
|
||||
"@typescript-eslint/consistent-type-assertions": [
|
||||
"warn",
|
||||
{ assertionStyle: "as" },
|
||||
],
|
||||
"@typescript-eslint/consistent-type-exports": "error",
|
||||
"@typescript-eslint/explicit-module-boundary-types": "warn",
|
||||
"@typescript-eslint/method-signature-style": ["error", "property"],
|
||||
"@typescript-eslint/no-base-to-string": "warn",
|
||||
"@typescript-eslint/no-confusing-non-null-assertion": "warn",
|
||||
"@typescript-eslint/no-confusing-void-expression": [
|
||||
"error",
|
||||
{ ignoreArrowShorthand: true },
|
||||
],
|
||||
"@typescript-eslint/no-dynamic-delete": "error",
|
||||
"@typescript-eslint/no-empty-interface": [
|
||||
"error",
|
||||
{ allowSingleExtends: true },
|
||||
],
|
||||
"@typescript-eslint/no-invalid-void-type": "error",
|
||||
"@typescript-eslint/no-meaningless-void-operator": "error",
|
||||
"@typescript-eslint/no-non-null-asserted-nullish-coalescing": "error",
|
||||
"@typescript-eslint/no-parameter-properties": "error",
|
||||
"@typescript-eslint/no-require-imports": "error",
|
||||
// "@typescript-eslint/no-type-alias": "warn",
|
||||
"@typescript-eslint/no-unnecessary-boolean-literal-compare": "warn",
|
||||
// "@typescript-eslint/no-unnecessary-condition": "warn",
|
||||
"@typescript-eslint/no-unnecessary-qualifier": "warn",
|
||||
"@typescript-eslint/no-unnecessary-type-arguments": "warn",
|
||||
"@typescript-eslint/prefer-enum-initializers": "error",
|
||||
"@typescript-eslint/prefer-for-of": "error",
|
||||
"@typescript-eslint/prefer-includes": "error",
|
||||
"@typescript-eslint/prefer-literal-enum-member": "error",
|
||||
"@typescript-eslint/prefer-nullish-coalescing": "warn",
|
||||
"@typescript-eslint/prefer-optional-chain": "warn",
|
||||
"@typescript-eslint/prefer-readonly": "warn",
|
||||
// "@typescript-eslint/prefer-readonly-parameter-types": "warn",
|
||||
"@typescript-eslint/prefer-reduce-type-parameter": "warn",
|
||||
// "@typescript-eslint/prefer-regexp-exec": "warn",
|
||||
"@typescript-eslint/prefer-return-this-type": "warn",
|
||||
"@typescript-eslint/prefer-string-starts-ends-with": "error",
|
||||
"@typescript-eslint/promise-function-async": "error",
|
||||
"@typescript-eslint/require-array-sort-compare": "error",
|
||||
"@typescript-eslint/sort-type-union-intersection-members": "warn",
|
||||
// "@typescript-eslint/strict-boolean-expressions": "error",
|
||||
"@typescript-eslint/switch-exhaustiveness-check": "error",
|
||||
"@typescript-eslint/typedef": "error",
|
||||
"@typescript-eslint/unified-signatures": "error",
|
||||
|
||||
/* EXTENSION OF ESLINT */
|
||||
"@typescript-eslint/no-duplicate-imports": "error",
|
||||
"@typescript-eslint/default-param-last": "warn",
|
||||
"@typescript-eslint/dot-notation": "warn",
|
||||
"@typescript-eslint/init-declarations": "warn",
|
||||
"@typescript-eslint/no-array-constructor": "warn",
|
||||
"@typescript-eslint/no-implied-eval": "warn",
|
||||
"@typescript-eslint/no-invalid-this": "warn",
|
||||
"@typescript-eslint/no-loop-func": "warn",
|
||||
"@typescript-eslint/no-shadow": "warn",
|
||||
"@typescript-eslint/no-unused-expressions": "warn",
|
||||
"@typescript-eslint/no-useless-constructor": "warn",
|
||||
"@typescript-eslint/require-await": "warn",
|
||||
|
||||
/* NEXTJS */
|
||||
"@next/next/no-img-element": "off",
|
||||
},
|
||||
};
|
@ -1,3 +0,0 @@
|
||||
{
|
||||
"extends": "next/core-web-vitals"
|
||||
}
|
37
.github/workflows/node.js.yml
vendored
Normal file
37
.github/workflows/node.js.yml
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
|
||||
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
|
||||
|
||||
name: Node.js CI
|
||||
|
||||
on:
|
||||
# push:
|
||||
# branches: [ main ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [16.x]
|
||||
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: 'npm'
|
||||
- run: npm ci
|
||||
- run: npm run lint
|
||||
- run: npm run build --if-present
|
||||
env:
|
||||
ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }}
|
||||
NEXT_PUBLIC_URL_CMS: ${{ secrets.NEXT_PUBLIC_URL_CMS }}
|
||||
NEXT_PUBLIC_URL_IMG: ${{ secrets.NEXT_PUBLIC_URL_IMG }}
|
||||
NEXT_PUBLIC_URL_SELF: ${{ secrets.NEXT_PUBLIC_URL_SELF }}
|
||||
URL_GRAPHQL: ${{ secrets.URL_GRAPHQL }}
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -16,6 +16,7 @@
|
||||
|
||||
# production
|
||||
/build
|
||||
/public/sitemap*
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
|
10
.hintrc
Normal file
10
.hintrc
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": ["development"],
|
||||
"hints": {
|
||||
"no-inline-styles": "off",
|
||||
"apple-touch-icons": "off",
|
||||
"compat-api/html": "off",
|
||||
"axe/text-alternatives": "off",
|
||||
"axe/parsing": "off"
|
||||
}
|
||||
}
|
1
.prettierignore
Normal file
1
.prettierignore
Normal file
@ -0,0 +1 @@
|
||||
.next
|
21
README.md
21
README.md
@ -1,5 +1,9 @@
|
||||
# Accords-library.com
|
||||
|
||||
[](https://github.com/Accords-Library/accords-library.com/actions/workflows/node.js.yml)
|
||||
[](https://github.com/Accords-Library/accords-library.com/blob/main/LICENSE)
|
||||

|
||||
|
||||
## Technologies
|
||||
|
||||
#### [Back](https://github.com/Accords-Library/strapi.accords-library.com)
|
||||
@ -9,6 +13,14 @@
|
||||
- Multilanguage support
|
||||
- Markdown format for the rich text fields
|
||||
|
||||
#### [Image Processor](https://github.com/Accords-Library/img.accords-library.com)
|
||||
|
||||
- Convert the images from the CMS to 4 formats
|
||||
- Small: 512x512, quality 60, .webp
|
||||
- Medium: 1024x1024, quality 75, .webp
|
||||
- Large: 2048x2048, quality 80, .webp
|
||||
- Og: 512x512, quality 60, .jpg
|
||||
|
||||
#### [Front](https://github.com/Accords-Library/accords-library.com) (this repository)
|
||||
|
||||
- Language: [TypeScript](https://www.typescriptlang.org/)
|
||||
@ -29,6 +41,12 @@
|
||||
- State Management: [React Context](https://reactjs.org/docs/context.html)
|
||||
- Persistent app state using LocalStorage
|
||||
- Support for many screen sizes and resolutions
|
||||
- SSG (Static Site Generation):
|
||||
- The website is built before running in production
|
||||
- Performances are great, and possibility to deploy the app using a CDN
|
||||
- OpenGraph and Metadata
|
||||
- Good defaults for the metadate and OpenGraph properties
|
||||
- Each page can provide the thumbnail, title, description to be used
|
||||
- Data quality testing
|
||||
- Data from the CMS is subject to a battery of tests (about 20 warning types and 40 error types) at build time
|
||||
- Each warning/error comes with a front-end link to the incriminating element, as well as a link to the CMS to fix it.
|
||||
@ -53,6 +71,9 @@ Enter the followind information:
|
||||
```txt
|
||||
URL_GRAPHQL=https://url-to.strapi-accords-library.com/graphql
|
||||
ACCESS_TOKEN=genatedcode-by-strapi-api
|
||||
SMTP_HOST=email.provider.com
|
||||
SMTP_USER=email@example.com
|
||||
SMTP_PASSWORD=mypassword123
|
||||
NEXT_PUBLIC_URL_CMS=https://url-to.strapi-accords-library.com/
|
||||
NEXT_PUBLIC_URL_IMG=https://url-to.img-accords-library.com/
|
||||
NEXT_PUBLIC_URL_SELF=https://url-to-front-accords-library.com
|
||||
|
10
next-env.d.ts
vendored
10
next-env.d.ts
vendored
@ -1,5 +1,5 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/basic-features/typescript for more information.
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/basic-features/typescript for more information.
|
||||
|
28
next-sitemap.js
Normal file
28
next-sitemap.js
Normal file
@ -0,0 +1,28 @@
|
||||
/** @type {import('next-sitemap').IConfig} */
|
||||
module.exports = {
|
||||
siteUrl: process.env.NEXT_PUBLIC_URL_SELF && "https://accords-library.com",
|
||||
generateRobotsTxt: true,
|
||||
alternateRefs: [
|
||||
{
|
||||
href: `${process.env.NEXT_PUBLIC_URL_SELF}/en/`,
|
||||
hreflang: "en",
|
||||
},
|
||||
{
|
||||
href: `${process.env.NEXT_PUBLIC_URL_SELF}/fr/`,
|
||||
hreflang: "fr",
|
||||
},
|
||||
{
|
||||
href: `${process.env.NEXT_PUBLIC_URL_SELF}/ja/`,
|
||||
hreflang: "ja",
|
||||
},
|
||||
{
|
||||
href: `${process.env.NEXT_PUBLIC_URL_SELF}/es/`,
|
||||
hreflang: "es",
|
||||
},
|
||||
{
|
||||
href: `${process.env.NEXT_PUBLIC_URL_SELF}/pt-br/`,
|
||||
hreflang: "pt-br",
|
||||
},
|
||||
],
|
||||
exclude: ["/en/*", "/fr/*", "/ja/*", "/es/*", "/pt-br/*"],
|
||||
};
|
@ -3,7 +3,7 @@ module.exports = {
|
||||
swcMinify: true,
|
||||
reactStrictMode: true,
|
||||
i18n: {
|
||||
locales: ["en", "fr", "ja", "es", "xx"],
|
||||
locales: ["en", "fr", "ja", "es", "pt-br"],
|
||||
defaultLocale: "en",
|
||||
},
|
||||
images: {
|
||||
|
744
package-lock.json
generated
744
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
25
package.json
25
package.json
@ -4,30 +4,39 @@
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"postbuild": "next-sitemap",
|
||||
"start": "next start",
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fontsource/material-icons": "^4.5.2",
|
||||
"@fontsource/material-icons-rounded": "^4.5.2",
|
||||
"@fontsource/opendyslexic": "^4.5.2",
|
||||
"@fontsource/vollkorn": "^4.5.4",
|
||||
"@fontsource/zen-maru-gothic": "^4.5.5",
|
||||
"markdown-to-jsx": "^7.1.6",
|
||||
"@tippyjs/react": "^4.2.6",
|
||||
"markdown-to-jsx": "^7.1.7",
|
||||
"next": "^12.1.0",
|
||||
"nodemailer": "^6.7.3",
|
||||
"react": "17.0.2",
|
||||
"react-dom": "17.0.2",
|
||||
"react-image-lightbox": "^5.1.4",
|
||||
"react-swipeable": "^6.2.0",
|
||||
"react-tooltip": "^4.2.21",
|
||||
"turndown": "^7.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/typography": "^0.5.2",
|
||||
"@types/node": "17.0.18",
|
||||
"@types/react": "17.0.39",
|
||||
"@types/react-dom": "^17.0.11",
|
||||
"eslint": "8.9.0",
|
||||
"@types/node": "17.0.21",
|
||||
"@types/nodemailer": "^6.4.4",
|
||||
"@types/react": "17.0.40",
|
||||
"@types/react-dom": "^17.0.13",
|
||||
"@types/turndown": "^5.0.1",
|
||||
"@typescript-eslint/eslint-plugin": "^5.16.0",
|
||||
"@typescript-eslint/parser": "^5.16.0",
|
||||
"eslint": "^8.10.0",
|
||||
"eslint-config-next": "12.1.0",
|
||||
"next-sitemap": "^2.5.14",
|
||||
"prettier-plugin-organize-imports": "^2.3.4",
|
||||
"tailwindcss": "^3.0.23",
|
||||
"typescript": "4.5.5"
|
||||
"typescript": "^4.6.2"
|
||||
}
|
||||
}
|
||||
|
@ -2,5 +2,5 @@ module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
@ -21,4 +21,4 @@ Insert the following code in the `head` section of your pages:
|
||||
<meta name="msapplication-TileImage" content="/mstile-144x144.png">
|
||||
<meta name="theme-color" content="#feecd6">
|
||||
|
||||
*Optional* - Check your favicon with the [favicon checker](https://realfavicongenerator.net/favicon_checker)
|
||||
_Optional_ - Check your favicon with the [favicon checker](https://realfavicongenerator.net/favicon_checker)
|
||||
|
@ -1,10 +1,10 @@
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
|
||||
<link rel="manifest" href="/site.webmanifest">
|
||||
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#9c6644">
|
||||
<meta name="apple-mobile-web-app-title" content="Accord's Library">
|
||||
<meta name="application-name" content="Accord's Library">
|
||||
<meta name="msapplication-TileColor" content="#feecd6">
|
||||
<meta name="msapplication-TileImage" content="/mstile-144x144.png">
|
||||
<meta name="theme-color" content="#feecd6">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
|
||||
<link rel="manifest" href="/site.webmanifest" />
|
||||
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#9c6644" />
|
||||
<meta name="apple-mobile-web-app-title" content="Accord's Library" />
|
||||
<meta name="application-name" content="Accord's Library" />
|
||||
<meta name="msapplication-TileColor" content="#feecd6" />
|
||||
<meta name="msapplication-TileImage" content="/mstile-144x144.png" />
|
||||
<meta name="theme-color" content="#feecd6" />
|
||||
|
9
public/robots.txt
Normal file
9
public/robots.txt
Normal file
@ -0,0 +1,9 @@
|
||||
# *
|
||||
User-agent: *
|
||||
Allow: /
|
||||
|
||||
# Host
|
||||
Host: https://accords-library.com
|
||||
|
||||
# Sitemaps
|
||||
Sitemap: https://accords-library.com/sitemap.xml
|
1
run_accords_prettier.sh
Executable file
1
run_accords_prettier.sh
Executable file
@ -0,0 +1 @@
|
||||
npx prettier --end-of-line auto --write .
|
@ -1,4 +1,2 @@
|
||||
NODE_ENV=test
|
||||
# npx next build | tee ./testing_logs/$(date +"%Y-%m-%d---%H-%M-%S").log
|
||||
|
||||
npx next build 2> >(tee ./testing_logs/$(date +"%Y-%m-%d---%H-%M-%S").stderr.tsv) 1> >(tee ./testing_logs/$(date +"%Y-%m-%d---%H-%M-%S").stdout.tsv)
|
||||
export ENABLE_TESTING_LOG=true
|
||||
npx next build 2> >(tee ./testing_logs/$(date +"%Y-%m-%d---%H-%M-%S").stderr.tsv) 1> >(tee ./testing_logs/$(date +"%Y-%m-%d---%H-%M-%S").stdout.tsv)
|
||||
|
@ -1,34 +1,32 @@
|
||||
import {
|
||||
GetWebsiteInterfaceQuery,
|
||||
StrapiImage,
|
||||
} from "graphql/operations-types";
|
||||
import MainPanel from "./Panels/MainPanel";
|
||||
import Head from "next/head";
|
||||
import { useSwipeable } from "react-swipeable";
|
||||
import { useRouter } from "next/router";
|
||||
import Button from "components/Button";
|
||||
import { getOgImage, OgImage, prettyLanguage } from "queries/helpers";
|
||||
import { useMediaCoarse, useMediaMobile } from "hooks/useMediaQuery";
|
||||
import ReactTooltip from "react-tooltip";
|
||||
import { useAppLayout } from "contexts/AppLayoutContext";
|
||||
import { StrapiImage } from "graphql/operations-types";
|
||||
import { useMediaMobile } from "hooks/useMediaQuery";
|
||||
import Head from "next/head";
|
||||
import { useRouter } from "next/router";
|
||||
import { AppStaticProps } from "queries/getAppStaticProps";
|
||||
import { getOgImage, OgImage, prettyLanguage } from "queries/helpers";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useSwipeable } from "react-swipeable";
|
||||
import { ImageQuality } from "./Img";
|
||||
import MainPanel from "./Panels/MainPanel";
|
||||
import Popup from "./Popup";
|
||||
import Select from "./Select";
|
||||
|
||||
type AppLayoutProps = {
|
||||
interface AppLayoutProps extends AppStaticProps {
|
||||
subPanel?: React.ReactNode;
|
||||
subPanelIcon?: string;
|
||||
contentPanel?: React.ReactNode;
|
||||
langui: GetWebsiteInterfaceQuery["websiteInterfaces"]["data"][number]["attributes"];
|
||||
title?: string;
|
||||
navTitle: string;
|
||||
thumbnail?: StrapiImage;
|
||||
description?: string;
|
||||
extra?: React.ReactNode;
|
||||
};
|
||||
}
|
||||
|
||||
export default function AppLayout(props: AppLayoutProps): JSX.Element {
|
||||
const { langui, currencies, languages, subPanel, contentPanel } = props;
|
||||
const router = useRouter();
|
||||
const isMobile = useMediaMobile();
|
||||
const isCoarse = useMediaCoarse();
|
||||
const appLayout = useAppLayout();
|
||||
|
||||
const sensibilitySwipe = 1.1;
|
||||
@ -38,7 +36,7 @@ export default function AppLayout(props: AppLayoutProps): JSX.Element {
|
||||
if (SwipeEventData.velocity < sensibilitySwipe) return;
|
||||
if (appLayout.mainPanelOpen) {
|
||||
appLayout.setMainPanelOpen(false);
|
||||
} else if (props.subPanel && props.contentPanel) {
|
||||
} else if (subPanel && contentPanel) {
|
||||
appLayout.setSubPanelOpen(true);
|
||||
}
|
||||
},
|
||||
@ -52,28 +50,7 @@ export default function AppLayout(props: AppLayoutProps): JSX.Element {
|
||||
},
|
||||
});
|
||||
|
||||
const mainPanelClass = `fixed desktop:left-0 desktop:top-0 desktop:bottom-0 ${
|
||||
appLayout.mainPanelReduced ? "desktop:w-[6rem]" : "desktop:w-[20rem]"
|
||||
}`;
|
||||
const subPanelClass = `fixed desktop:top-0 desktop:bottom-0 desktop:w-[20rem] ${
|
||||
appLayout.mainPanelReduced ? " desktop:left-[6rem]" : "desktop:left-[20rem]"
|
||||
}`;
|
||||
let contentPanelClass = "";
|
||||
if (props.subPanel) {
|
||||
contentPanelClass = `fixed desktop:top-0 desktop:bottom-0 desktop:right-0 ${
|
||||
appLayout.mainPanelReduced
|
||||
? "desktop:left-[26rem]"
|
||||
: "desktop:left-[40rem]"
|
||||
}`;
|
||||
} else if (props.contentPanel) {
|
||||
contentPanelClass = `fixed desktop:top-0 desktop:bottom-0 desktop:right-0 ${
|
||||
appLayout.mainPanelReduced
|
||||
? "desktop:left-[6rem]"
|
||||
: "desktop:left-[20rem]"
|
||||
}`;
|
||||
}
|
||||
|
||||
const turnSubIntoContent = props.subPanel && !props.contentPanel;
|
||||
const turnSubIntoContent = subPanel && !contentPanel;
|
||||
|
||||
const titlePrefix = "Accord’s Library";
|
||||
const metaImage: OgImage = props.thumbnail
|
||||
@ -88,13 +65,58 @@ export default function AppLayout(props: AppLayoutProps): JSX.Element {
|
||||
|
||||
const metaDescription = props.description
|
||||
? props.description
|
||||
: "Accord's Library aims at gathering and archiving all of Yoko Taro’s work. Yoko Taro is a Japanese video game director and scenario writer.";
|
||||
: langui.default_description;
|
||||
|
||||
useEffect(() => {
|
||||
document.getElementsByTagName("html")[0].style.fontSize = `${
|
||||
(appLayout.fontSize ?? 1) * 100
|
||||
}%`;
|
||||
}, [appLayout.fontSize]);
|
||||
|
||||
const currencyOptions = currencies.map(
|
||||
(currency) => currency.attributes.code
|
||||
);
|
||||
const [currencySelect, setCurrencySelect] = useState<number>(-1);
|
||||
|
||||
useEffect(() => {
|
||||
if (appLayout.currency)
|
||||
setCurrencySelect(currencyOptions.indexOf(appLayout.currency));
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [appLayout.currency]);
|
||||
|
||||
useEffect(() => {
|
||||
if (currencySelect >= 0)
|
||||
appLayout.setCurrency(currencyOptions[currencySelect]);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [currencySelect]);
|
||||
|
||||
let gridCol = "";
|
||||
if (props.subPanel) {
|
||||
if (appLayout.mainPanelReduced) {
|
||||
gridCol = "grid-cols-[6rem_20rem_1fr]";
|
||||
} else {
|
||||
gridCol = "grid-cols-[20rem_20rem_1fr]";
|
||||
}
|
||||
} else if (appLayout.mainPanelReduced) {
|
||||
gridCol = "grid-cols-[6rem_0px_1fr]";
|
||||
} else {
|
||||
gridCol = "grid-cols-[20rem_0px_1fr]";
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={appLayout.darkMode ? "set-theme-dark" : "set-theme-light"}>
|
||||
<div
|
||||
id="MyAppLayout"
|
||||
className={`${
|
||||
appLayout.darkMode ? "set-theme-dark" : "set-theme-light"
|
||||
} ${
|
||||
appLayout.dyslexic
|
||||
? "set-theme-font-dyslexic"
|
||||
: "set-theme-font-standard"
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
{...handlers}
|
||||
className="fixed inset-0 touch-pan-y p-0 m-0 bg-light text-black"
|
||||
className={`fixed inset-0 touch-pan-y p-0 m-0 bg-light text-black grid [grid-template-areas:'main_sub_content'] ${gridCol} mobile:grid-cols-[1fr] mobile:grid-rows-[1fr_5rem] mobile:[grid-template-areas:'content''navbar']`}
|
||||
>
|
||||
<Head>
|
||||
<title>{`${titlePrefix} - ${ogTitle}`}</title>
|
||||
@ -104,12 +126,8 @@ export default function AppLayout(props: AppLayoutProps): JSX.Element {
|
||||
content={`${titlePrefix} - ${ogTitle}`}
|
||||
></meta>
|
||||
|
||||
{props.description && (
|
||||
<>
|
||||
<meta name="description" content={metaDescription} />
|
||||
<meta name="twitter:description" content={metaDescription}></meta>
|
||||
</>
|
||||
)}
|
||||
<meta name="description" content={metaDescription} />
|
||||
<meta name="twitter:description" content={metaDescription}></meta>
|
||||
|
||||
<meta property="og:image" content={metaImage.image}></meta>
|
||||
<meta property="og:image:secure_url" content={metaImage.image}></meta>
|
||||
@ -128,145 +146,247 @@ export default function AppLayout(props: AppLayoutProps): JSX.Element {
|
||||
<meta name="twitter:image" content={metaImage.image}></meta>
|
||||
</Head>
|
||||
|
||||
{/* Background when navbar is opened */}
|
||||
<div
|
||||
className={`[grid-area:content] mobile:z-10 absolute inset-0 transition-[backdrop-filter] duration-500 ${
|
||||
(appLayout.mainPanelOpen || appLayout.subPanelOpen) && isMobile
|
||||
? "[backdrop-filter:blur(2px)]"
|
||||
: "pointer-events-none touch-none "
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
className={`absolute bg-shade inset-0 transition-opacity duration-500
|
||||
${turnSubIntoContent ? "" : ""}
|
||||
${
|
||||
(appLayout.mainPanelOpen || appLayout.subPanelOpen) && isMobile
|
||||
? "opacity-60"
|
||||
: "opacity-0"
|
||||
}`}
|
||||
onClick={() => {
|
||||
appLayout.setMainPanelOpen(false);
|
||||
appLayout.setSubPanelOpen(false);
|
||||
}}
|
||||
></div>
|
||||
</div>
|
||||
|
||||
{/* Content panel */}
|
||||
<div
|
||||
className={`[grid-area:content] overflow-y-scroll bg-light texture-paper-dots`}
|
||||
>
|
||||
{contentPanel ? (
|
||||
contentPanel
|
||||
) : (
|
||||
<div className="grid place-content-center h-full">
|
||||
<div className="text-dark border-dark border-2 border-dotted rounded-2xl p-8 grid grid-flow-col place-items-center gap-9 opacity-40">
|
||||
<p className="text-4xl">❮</p>
|
||||
<p className="text-2xl w-64">{langui.select_option_sidebar}</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Sub panel */}
|
||||
{subPanel && (
|
||||
<div
|
||||
className={`[grid-area:sub] mobile:[grid-area:content] mobile:z-10 mobile:w-[90%] mobile:justify-self-end border-r-[1px] mobile:border-r-0 mobile:border-l-[1px] border-black border-dotted overflow-y-scroll webkit-scrollbar:w-0 [scrollbar-width:none] transition-transform duration-300 bg-light texture-paper-dots
|
||||
${
|
||||
turnSubIntoContent
|
||||
? "mobile:border-l-0 mobile:w-full"
|
||||
: !appLayout.subPanelOpen && "mobile:translate-x-[100vw]"
|
||||
}`}
|
||||
>
|
||||
{subPanel}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Main panel */}
|
||||
<div
|
||||
className={`[grid-area:main] mobile:[grid-area:content] mobile:z-10 mobile:w-[90%] mobile:justify-self-start border-r-[1px] border-black border-dotted overflow-y-scroll webkit-scrollbar:w-0 [scrollbar-width:none] transition-transform duration-300 bg-light texture-paper-dots
|
||||
${appLayout.mainPanelOpen ? "" : "mobile:-translate-x-full"}`}
|
||||
>
|
||||
<MainPanel langui={langui} />
|
||||
</div>
|
||||
|
||||
{/* Navbar */}
|
||||
<div className="fixed inset-0 top-auto h-20 border-t-[1px] border-black border-dotted grid grid-cols-[5rem_1fr_5rem] place-items-center desktop:hidden bg-light texture-paper-dots">
|
||||
<div className="[grid-area:navbar] border-t-[1px] border-black border-dotted grid grid-cols-[5rem_1fr_5rem] place-items-center desktop:hidden bg-light texture-paper-dots">
|
||||
<span
|
||||
className="material-icons mt-[.1em] cursor-pointer"
|
||||
onClick={() => appLayout.setMainPanelOpen(true)}
|
||||
onClick={() => {
|
||||
appLayout.setMainPanelOpen(!appLayout.mainPanelOpen);
|
||||
appLayout.setSubPanelOpen(false);
|
||||
}}
|
||||
>
|
||||
menu
|
||||
{appLayout.mainPanelOpen ? "close" : "menu"}
|
||||
</span>
|
||||
<p className="text-2xl font-black font-headers">{props.navTitle}</p>
|
||||
<span
|
||||
className="material-icons mt-[.1em] cursor-pointer"
|
||||
onClick={() => appLayout.setSubPanelOpen(true)}
|
||||
onClick={() => {
|
||||
appLayout.setSubPanelOpen(!appLayout.subPanelOpen);
|
||||
appLayout.setMainPanelOpen(false);
|
||||
}}
|
||||
>
|
||||
{props.subPanel && !turnSubIntoContent
|
||||
? props.subPanelIcon
|
||||
{subPanel && !turnSubIntoContent
|
||||
? appLayout.subPanelOpen
|
||||
? "close"
|
||||
: props.subPanelIcon
|
||||
? props.subPanelIcon
|
||||
: "tune"
|
||||
: ""}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Content panel */}
|
||||
<div
|
||||
className={`top-0 left-0 right-0 bottom-20 overflow-y-scroll bg-light texture-paper-dots ${contentPanelClass}`}
|
||||
<Popup
|
||||
state={appLayout.languagePanelOpen}
|
||||
setState={appLayout.setLanguagePanelOpen}
|
||||
>
|
||||
{props.contentPanel ? (
|
||||
props.contentPanel
|
||||
) : (
|
||||
<div className="grid place-content-center h-full">
|
||||
<div className="text-dark border-dark border-2 border-dotted rounded-2xl p-8 grid grid-flow-col place-items-center gap-9 opacity-40">
|
||||
<p className="text-4xl">❮</p>
|
||||
<p className="text-2xl w-64">
|
||||
Select one of the options in the sidebar
|
||||
</p>
|
||||
<h2 className="text-2xl">{langui.select_language}</h2>
|
||||
<div className="flex flex-wrap flex-row gap-2 mobile:flex-col">
|
||||
{router.locales?.map((locale) => (
|
||||
<Button
|
||||
key={locale}
|
||||
active={locale === router.locale}
|
||||
href={router.asPath}
|
||||
locale={locale}
|
||||
onClick={() => appLayout.setLanguagePanelOpen(false)}
|
||||
>
|
||||
{prettyLanguage(locale, languages)}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</Popup>
|
||||
|
||||
<Popup
|
||||
state={appLayout.configPanelOpen}
|
||||
setState={appLayout.setConfigPanelOpen}
|
||||
>
|
||||
<h2 className="text-2xl">{langui.settings}</h2>
|
||||
|
||||
<div className="mt-4 grid gap-8 place-items-center text-center desktop:grid-cols-2">
|
||||
<div>
|
||||
<h3 className="text-xl">{langui.theme}</h3>
|
||||
<div className="flex flex-row">
|
||||
<Button
|
||||
onClick={() => {
|
||||
appLayout.setDarkMode(false);
|
||||
appLayout.setSelectedThemeMode(true);
|
||||
}}
|
||||
active={
|
||||
appLayout.selectedThemeMode === true &&
|
||||
appLayout.darkMode === false
|
||||
}
|
||||
className="rounded-r-none"
|
||||
>
|
||||
{langui.light}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
appLayout.setSelectedThemeMode(false);
|
||||
}}
|
||||
active={appLayout.selectedThemeMode === false}
|
||||
className="rounded-l-none rounded-r-none border-x-0"
|
||||
>
|
||||
{langui.auto}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
appLayout.setDarkMode(true);
|
||||
appLayout.setSelectedThemeMode(true);
|
||||
}}
|
||||
active={
|
||||
appLayout.selectedThemeMode === true &&
|
||||
appLayout.darkMode === true
|
||||
}
|
||||
className="rounded-l-none"
|
||||
>
|
||||
{langui.dark}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Background when navbar is opened */}
|
||||
<div
|
||||
className={`fixed bg-shade inset-0 transition-opacity duration-500
|
||||
${turnSubIntoContent ? "z-10" : ""}
|
||||
${
|
||||
(appLayout.mainPanelOpen || appLayout.subPanelOpen) && isMobile
|
||||
? "opacity-60"
|
||||
: "opacity-0 pointer-events-none touch-none"
|
||||
}`}
|
||||
onClick={() => {
|
||||
appLayout.setMainPanelOpen(false);
|
||||
appLayout.setSubPanelOpen(false);
|
||||
}}
|
||||
></div>
|
||||
<div>
|
||||
<h3 className="text-xl">{langui.currency}</h3>
|
||||
<div>
|
||||
<Select
|
||||
options={currencyOptions}
|
||||
state={currencySelect}
|
||||
setState={setCurrencySelect}
|
||||
className="w-28"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Sub panel */}
|
||||
{props.subPanel ? (
|
||||
<div
|
||||
className={`${subPanelClass} border-r-[1px] mobile:border-r-0 mobile:border-l-[1px] border-black border-dotted top-0 bottom-0 right-0 left-12 overflow-y-scroll webkit-scrollbar:w-0 [scrollbar-width:none] transition-transform duration-300 bg-light texture-paper-dots
|
||||
${
|
||||
turnSubIntoContent
|
||||
? "mobile:translate-x-0 mobile:bottom-20 mobile:left-0 mobile:border-l-0"
|
||||
: !appLayout.subPanelOpen
|
||||
? "mobile:translate-x-full"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
{props.subPanel}
|
||||
</div>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
|
||||
{/* Main panel */}
|
||||
<div
|
||||
className={`${mainPanelClass} border-r-[1px] border-black border-dotted top-0 bottom-0 left-0 right-12 overflow-y-scroll webkit-scrollbar:w-0 [scrollbar-width:none] transition-transform duration-300 z-20 bg-light texture-paper-dots
|
||||
${appLayout.mainPanelOpen ? "" : "mobile:-translate-x-full"}`}
|
||||
>
|
||||
<MainPanel langui={props.langui} />
|
||||
</div>
|
||||
|
||||
{/* Main panel minimize button*/}
|
||||
<div
|
||||
className={`mobile:hidden translate-x-0 fixed top-1/2 z-20 ${
|
||||
appLayout.mainPanelReduced ? "left-[4.65rem]" : "left-[18.65rem]"
|
||||
}`}
|
||||
onClick={() =>
|
||||
appLayout.setMainPanelReduced(!appLayout.mainPanelReduced)
|
||||
}
|
||||
>
|
||||
<Button className="material-icons bg-light !px-2">
|
||||
{appLayout.mainPanelReduced ? "chevron_right" : "chevron_left"}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Language selection background */}
|
||||
<div
|
||||
className={`fixed bg-shade inset-0 transition-all duration-500 z-20 grid place-content-center ${
|
||||
appLayout.languagePanelOpen
|
||||
? "bg-opacity-60"
|
||||
: "bg-opacity-0 pointer-events-none touch-none"
|
||||
}`}
|
||||
onClick={() => {
|
||||
appLayout.setLanguagePanelOpen(false);
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className={`p-10 bg-light rounded-lg shadow-2xl shadow-shade grid gap-4 place-items-center transition-transform ${
|
||||
appLayout.languagePanelOpen ? "scale-100" : "scale-0"
|
||||
}`}
|
||||
>
|
||||
<h2 className="text-2xl">Select a language</h2>
|
||||
<div className="flex flex-wrap flex-row gap-2">
|
||||
{router.locales?.sort().map((locale) => (
|
||||
<div>
|
||||
<h3 className="text-xl">{langui.font_size}</h3>
|
||||
<div className="flex flex-row">
|
||||
<Button
|
||||
key={locale}
|
||||
active={locale === router.locale}
|
||||
href={router.asPath}
|
||||
locale={locale}
|
||||
className="rounded-r-none"
|
||||
onClick={() =>
|
||||
appLayout.setFontSize(
|
||||
appLayout.fontSize ? appLayout.fontSize / 1.05 : 1 / 1.05
|
||||
)
|
||||
}
|
||||
>
|
||||
{prettyLanguage(locale)}
|
||||
<span className="material-icons">text_decrease</span>
|
||||
</Button>
|
||||
))}
|
||||
<Button
|
||||
className="rounded-l-none rounded-r-none border-x-0"
|
||||
onClick={() => appLayout.setFontSize(1)}
|
||||
>
|
||||
{((appLayout.fontSize ?? 1) * 100).toLocaleString(undefined, {
|
||||
maximumFractionDigits: 0,
|
||||
})}
|
||||
%
|
||||
</Button>
|
||||
<Button
|
||||
className="rounded-l-none"
|
||||
onClick={() =>
|
||||
appLayout.setFontSize(
|
||||
appLayout.fontSize ? appLayout.fontSize * 1.05 : 1 * 1.05
|
||||
)
|
||||
}
|
||||
>
|
||||
<span className="material-icons">text_increase</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 className="text-xl">{langui.font}</h3>
|
||||
<div className="grid gap-2">
|
||||
<Button
|
||||
active={appLayout.dyslexic === false}
|
||||
onClick={() => appLayout.setDyslexic(false)}
|
||||
className="font-zenMaruGothic"
|
||||
>
|
||||
Zen Maru Gothic
|
||||
</Button>
|
||||
<Button
|
||||
active={appLayout.dyslexic === true}
|
||||
onClick={() => appLayout.setDyslexic(true)}
|
||||
className="font-openDyslexic"
|
||||
>
|
||||
OpenDyslexic
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 className="text-xl">{langui.player_name}</h3>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="<player>"
|
||||
className="w-48"
|
||||
onInput={(event) =>
|
||||
appLayout.setPlayerName(
|
||||
(event.target as HTMLInputElement).value
|
||||
)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ReactTooltip
|
||||
id="MainPanelTooltip"
|
||||
place="right"
|
||||
type="light"
|
||||
effect="solid"
|
||||
delayShow={300}
|
||||
delayHide={100}
|
||||
disable={!appLayout.mainPanelReduced || isMobile || isCoarse}
|
||||
className="drop-shadow-shade-xl !opacity-100 !bg-light !rounded-lg after:!border-r-light text-left !text-black"
|
||||
/>
|
||||
</Popup>
|
||||
</div>
|
||||
|
||||
{props.extra}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -1,17 +1,19 @@
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import { MouseEventHandler } from "react";
|
||||
|
||||
type ButtonProps = {
|
||||
id?: string;
|
||||
className?: string;
|
||||
href?: string;
|
||||
children: React.ReactChild | React.ReactChild[];
|
||||
children: React.ReactNode;
|
||||
active?: boolean;
|
||||
locale?: string;
|
||||
onClick?: MouseEventHandler<HTMLDivElement>;
|
||||
};
|
||||
|
||||
export default function Button(props: ButtonProps): JSX.Element {
|
||||
const router = useRouter();
|
||||
|
||||
const button = (
|
||||
<div
|
||||
id={props.id}
|
||||
@ -21,19 +23,23 @@ export default function Button(props: ButtonProps): JSX.Element {
|
||||
} ${
|
||||
props.active
|
||||
? "text-light bg-black drop-shadow-black-lg !border-black cursor-not-allowed"
|
||||
: "cursor-pointer hover:text-light hover:bg-dark hover:drop-shadow-shade-lg active:bg-black active:drop-shadow-black-lg active:border-black"
|
||||
: "cursor-pointer hover:text-light hover:bg-dark hover:drop-shadow-shade-lg active:bg-black active:text-light active:drop-shadow-black-lg active:border-black"
|
||||
}`}
|
||||
>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
|
||||
const result = props.href ? (
|
||||
<Link href={props.href} locale={props.locale} passHref>
|
||||
return (
|
||||
<div
|
||||
onClick={() => {
|
||||
if (props.href || props.locale)
|
||||
router.push(props.href ?? router.asPath, props.href, {
|
||||
locale: props.locale,
|
||||
});
|
||||
}}
|
||||
>
|
||||
{button}
|
||||
</Link>
|
||||
) : (
|
||||
button
|
||||
</div>
|
||||
);
|
||||
return result;
|
||||
}
|
||||
|
@ -1,25 +1,12 @@
|
||||
type ChipProps = {
|
||||
className?: string;
|
||||
children: React.ReactChild | React.ReactChild[];
|
||||
"data-tip"?: string;
|
||||
"data-for"?: string;
|
||||
"data-html"?: boolean;
|
||||
"data-multiline"?: boolean;
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
export default function Chip(props: ChipProps): JSX.Element {
|
||||
return (
|
||||
<div
|
||||
className={`grid place-content-center place-items-center text-xs pb-[0.14rem] px-1.5 border-[1px] rounded-full opacity-70 transition-[color,_opacity,border-color] ${
|
||||
props.className
|
||||
} ${
|
||||
props["data-tip"] &&
|
||||
"hover:text-dark hover:border-dark hover:opacity-100"
|
||||
}`}
|
||||
data-tip={props["data-tip"]}
|
||||
data-for={props["data-for"]}
|
||||
data-html={props["data-html"]}
|
||||
data-multiline={props["data-multiline"]}
|
||||
className={`grid place-content-center place-items-center text-xs pb-[0.14rem] whitespace-nowrap px-1.5 border-[1px] rounded-full opacity-70 transition-[color,_opacity,_border-color] hover:opacity-100 ${props.className}`}
|
||||
>
|
||||
{props.children}
|
||||
</div>
|
||||
|
@ -1,35 +1,40 @@
|
||||
import Chip from "components/Chip";
|
||||
import ToolTip from "components/ToolTip";
|
||||
import {
|
||||
Enum_Componenttranslationschronologyitem_Status,
|
||||
GetChronologyItemsQuery,
|
||||
GetWebsiteInterfaceQuery,
|
||||
} from "graphql/operations-types";
|
||||
import { getStatusDescription } from "queries/helpers";
|
||||
|
||||
export type ChronologyItemComponentProps = {
|
||||
item: GetChronologyItemsQuery["chronologyItems"]["data"][number];
|
||||
displayYear: boolean;
|
||||
langui: GetWebsiteInterfaceQuery["websiteInterfaces"]["data"][number]["attributes"];
|
||||
};
|
||||
|
||||
export default function ChronologyItemComponent(
|
||||
props: ChronologyItemComponentProps
|
||||
): JSX.Element {
|
||||
const { langui } = props;
|
||||
|
||||
function generateAnchor(year: number, month: number, day: number): string {
|
||||
let result: string = "";
|
||||
let result = "";
|
||||
result += year;
|
||||
if (month) result += "-" + month.toString().padStart(2, "0");
|
||||
if (day) result += "-" + day.toString().padStart(2, "0");
|
||||
if (month) result += `- ${month.toString().padStart(2, "0")}`;
|
||||
if (day) result += `- ${day.toString().padStart(2, "0")}`;
|
||||
return result;
|
||||
}
|
||||
|
||||
function generateYear(displayed_date: string, year: number): string {
|
||||
if (displayed_date) {
|
||||
return displayed_date;
|
||||
} else {
|
||||
return year.toString();
|
||||
}
|
||||
return year.toString();
|
||||
}
|
||||
|
||||
function generateDate(month: number, day: number): string {
|
||||
let lut = [
|
||||
const lut = [
|
||||
"Jan",
|
||||
"Feb",
|
||||
"Mar",
|
||||
@ -44,11 +49,11 @@ export default function ChronologyItemComponent(
|
||||
"Dec",
|
||||
];
|
||||
|
||||
let result: string = "";
|
||||
let result = "";
|
||||
if (month) {
|
||||
result += lut[month - 1];
|
||||
if (day) {
|
||||
result += " " + day;
|
||||
result += ` ${day}`;
|
||||
}
|
||||
}
|
||||
|
||||
@ -64,15 +69,13 @@ export default function ChronologyItemComponent(
|
||||
props.item.attributes.day
|
||||
)}
|
||||
>
|
||||
{props.displayYear ? (
|
||||
{props.displayYear && (
|
||||
<p className="text-lg mt-[-.2em] font-bold">
|
||||
{generateYear(
|
||||
props.item.attributes.displayed_date,
|
||||
props.item.attributes.year
|
||||
)}
|
||||
</p>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
|
||||
<p className="col-start-1 text-dark text-sm">
|
||||
@ -87,28 +90,17 @@ export default function ChronologyItemComponent(
|
||||
<div className="place-items-start place-content-start grid grid-flow-col gap-2">
|
||||
{translation.status !==
|
||||
Enum_Componenttranslationschronologyitem_Status.Done && (
|
||||
<Chip
|
||||
data-tip={
|
||||
translation.status ===
|
||||
Enum_Componenttranslationschronologyitem_Status.Incomplete
|
||||
? "This entry is only partially translated/transcribed."
|
||||
: translation.status ===
|
||||
Enum_Componenttranslationschronologyitem_Status.Draft
|
||||
? "This entry is just a draft. It usually means that this is a work-in-progress. Translation/transcription might be poor and/or computer-generated."
|
||||
: translation.status ===
|
||||
Enum_Componenttranslationschronologyitem_Status.Review
|
||||
? "This entry has not yet being proofread. The content should still be accurate."
|
||||
: ""
|
||||
}
|
||||
data-for={"ChronologyTooltip"}
|
||||
<ToolTip
|
||||
content={getStatusDescription(translation.status, langui)}
|
||||
maxWidth={"20rem"}
|
||||
>
|
||||
{translation.status}
|
||||
</Chip>
|
||||
<Chip>{translation.status}</Chip>
|
||||
</ToolTip>
|
||||
)}
|
||||
{translation.title ? <h3>{translation.title}</h3> : ""}
|
||||
</div>
|
||||
|
||||
{translation.description ? (
|
||||
{translation.description && (
|
||||
<p
|
||||
className={
|
||||
event.translations.length > 1
|
||||
@ -118,11 +110,9 @@ export default function ChronologyItemComponent(
|
||||
>
|
||||
{translation.description}
|
||||
</p>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
{translation.note ? (
|
||||
<em>{"Notes: " + translation.note}</em>
|
||||
<em>{`Notes: ${translation.note}`}</em>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
@ -131,7 +121,7 @@ export default function ChronologyItemComponent(
|
||||
|
||||
<p className="text-dark text-xs grid place-self-start grid-flow-col gap-1 mt-1">
|
||||
{event.source.data ? (
|
||||
"(" + event.source.data.attributes.name + ")"
|
||||
`(${event.source.data.attributes.name})`
|
||||
) : (
|
||||
<>
|
||||
<span className="material-icons !text-sm">warning</span>No
|
||||
|
@ -1,14 +1,20 @@
|
||||
import ChronologyItemComponent from "components/Chronology/ChronologyItemComponent";
|
||||
import { GetChronologyItemsQuery } from "graphql/operations-types";
|
||||
import {
|
||||
GetChronologyItemsQuery,
|
||||
GetWebsiteInterfaceQuery,
|
||||
} from "graphql/operations-types";
|
||||
|
||||
type ChronologyYearComponentProps = {
|
||||
year: number;
|
||||
items: GetChronologyItemsQuery["chronologyItems"]["data"][number][];
|
||||
langui: GetWebsiteInterfaceQuery["websiteInterfaces"]["data"][number]["attributes"];
|
||||
};
|
||||
|
||||
export default function ChronologyYearComponent(
|
||||
props: ChronologyYearComponentProps
|
||||
): JSX.Element {
|
||||
const { langui } = props;
|
||||
|
||||
return (
|
||||
<div
|
||||
className="target:bg-mid rounded-2xl target:py-4 target:my-4"
|
||||
@ -19,6 +25,7 @@ export default function ChronologyYearComponent(
|
||||
key={index}
|
||||
item={item}
|
||||
displayYear={index === 0}
|
||||
langui={langui}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
@ -1,85 +1,90 @@
|
||||
import Chip from "components/Chip";
|
||||
import Img, { ImageQuality } from "components/Img";
|
||||
import InsetBox from "components/InsetBox";
|
||||
import {
|
||||
GetContentQuery,
|
||||
GetWebsiteInterfaceQuery,
|
||||
} from "graphql/operations-types";
|
||||
import { prettySlug } from "queries/helpers";
|
||||
import Button from "components/Button";
|
||||
import Img, { ImageQuality } from "components/Img";
|
||||
import InsetBox from "components/InsetBox";
|
||||
import { prettyinlineTitle, prettySlug, slugify } from "queries/helpers";
|
||||
|
||||
export type ThumbnailHeaderProps = {
|
||||
content: {
|
||||
slug: GetContentQuery["contents"]["data"][number]["attributes"]["slug"];
|
||||
thumbnail: GetContentQuery["contents"]["data"][number]["attributes"]["thumbnail"];
|
||||
titles: GetContentQuery["contents"]["data"][number]["attributes"]["titles"];
|
||||
type: GetContentQuery["contents"]["data"][number]["attributes"]["type"];
|
||||
categories: GetContentQuery["contents"]["data"][number]["attributes"]["categories"];
|
||||
};
|
||||
pre_title?: string;
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
description?: string;
|
||||
type?: GetContentQuery["contents"]["data"][number]["attributes"]["type"];
|
||||
categories?: GetContentQuery["contents"]["data"][number]["attributes"]["categories"];
|
||||
thumbnail?: GetContentQuery["contents"]["data"][number]["attributes"]["thumbnail"]["data"]["attributes"];
|
||||
langui: GetWebsiteInterfaceQuery["websiteInterfaces"]["data"][number]["attributes"];
|
||||
};
|
||||
|
||||
export default function ThumbnailHeader(
|
||||
props: ThumbnailHeaderProps
|
||||
): JSX.Element {
|
||||
const content = props.content;
|
||||
const langui = props.langui;
|
||||
const {
|
||||
langui,
|
||||
pre_title,
|
||||
title,
|
||||
subtitle,
|
||||
thumbnail,
|
||||
type,
|
||||
categories,
|
||||
description,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="grid place-items-center gap-12 mb-12">
|
||||
<div className="drop-shadow-shade-lg">
|
||||
{content.thumbnail.data ? (
|
||||
{thumbnail ? (
|
||||
<Img
|
||||
className=" rounded-xl"
|
||||
image={content.thumbnail.data.attributes}
|
||||
image={thumbnail}
|
||||
quality={ImageQuality.Medium}
|
||||
priority
|
||||
/>
|
||||
) : (
|
||||
<div className="w-full aspect-[4/3] bg-light rounded-xl"></div>
|
||||
<div className="w-96 aspect-[4/3] bg-light rounded-xl"></div>
|
||||
)}
|
||||
</div>
|
||||
<div className="grid place-items-center text-center">
|
||||
{content.titles.length > 0 ? (
|
||||
<>
|
||||
<p className="text-2xl">{content.titles[0].pre_title}</p>
|
||||
<h1 className="text-3xl">{content.titles[0].title}</h1>
|
||||
<h2 className="text-2xl">{content.titles[0].subtitle}</h2>
|
||||
</>
|
||||
) : (
|
||||
<h1 className="text-3xl">{prettySlug(content.slug)}</h1>
|
||||
<div
|
||||
id={slugify(
|
||||
prettyinlineTitle(pre_title ?? "", title, subtitle ?? "")
|
||||
)}
|
||||
className="grid place-items-center text-center"
|
||||
>
|
||||
<p className="text-2xl">{pre_title}</p>
|
||||
<h1 className="text-3xl">{title}</h1>
|
||||
<h2 className="text-2xl">{subtitle}</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-flow-col gap-8">
|
||||
{content.type ? (
|
||||
<div className="grid place-items-center place-content-start gap-2">
|
||||
<h3 className="text-xl">{langui.global_type}</h3>
|
||||
<Button>
|
||||
{content.type.data.attributes.titles.length > 0
|
||||
? content.type.data.attributes.titles[0].title
|
||||
: prettySlug(content.type.data.attributes.slug)}
|
||||
</Button>
|
||||
{type?.data && (
|
||||
<div className="flex flex-col place-items-center gap-2">
|
||||
<h3 className="text-xl">{langui.type}</h3>
|
||||
<div className="flex flex-row flex-wrap">
|
||||
<Chip>
|
||||
{type.data.attributes.titles.length > 0
|
||||
? type.data.attributes.titles[0].title
|
||||
: prettySlug(type.data.attributes.slug)}
|
||||
</Chip>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
|
||||
{content.categories.data.length > 0 ? (
|
||||
<div className="grid place-items-center place-content-start gap-2">
|
||||
<h3 className="text-xl">{langui.global_categories}</h3>
|
||||
{content.categories.data.map((category) => (
|
||||
<Button key={category.id}>{category.attributes.name}</Button>
|
||||
))}
|
||||
{categories && categories.data.length > 0 && (
|
||||
<div className="flex flex-col place-items-center gap-2">
|
||||
<h3 className="text-xl">{langui.categories}</h3>
|
||||
<div className="flex flex-row flex-wrap place-content-center gap-2">
|
||||
{categories.data.map((category) => (
|
||||
<Chip key={category.id}>{category.attributes.name}</Chip>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
</div>
|
||||
{content.titles.length > 0 && content.titles[0].description && (
|
||||
<InsetBox className="mt-8">{content.titles[0].description}</InsetBox>
|
||||
)}
|
||||
{description && <InsetBox className="mt-8">{description}</InsetBox>}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { StrapiImage } from "graphql/operations-types";
|
||||
import { ImageProps } from "next/image";
|
||||
import Image from "next/image";
|
||||
import Image, { ImageProps } from "next/image";
|
||||
|
||||
export enum ImageQuality {
|
||||
Small = "small",
|
||||
@ -10,11 +9,12 @@ export enum ImageQuality {
|
||||
}
|
||||
|
||||
export function getAssetURL(url: string, quality: ImageQuality): string {
|
||||
url = url.replace(/^\/uploads/, "/" + quality);
|
||||
url = url.replace(/.jpg$/, ".webp");
|
||||
url = url.replace(/.png$/, ".webp");
|
||||
if (quality === ImageQuality.Og) url = url.replace(/.webp$/, ".jpg");
|
||||
return process.env.NEXT_PUBLIC_URL_IMG + url;
|
||||
let newUrl = url;
|
||||
newUrl = newUrl.replace(/^\/uploads/u, `/${quality}`);
|
||||
newUrl = newUrl.replace(/.jpg$/u, ".webp");
|
||||
newUrl = newUrl.replace(/.png$/u, ".webp");
|
||||
if (quality === ImageQuality.Og) newUrl = newUrl.replace(/.webp$/u, ".jpg");
|
||||
return process.env.NEXT_PUBLIC_URL_IMG + newUrl;
|
||||
}
|
||||
|
||||
export function getImgSizesByMaxSize(
|
||||
@ -25,10 +25,9 @@ export function getImgSizesByMaxSize(
|
||||
if (width > height) {
|
||||
if (width < maxSize) return { width: width, height: height };
|
||||
return { width: maxSize, height: (height / width) * maxSize };
|
||||
} else {
|
||||
if (height < maxSize) return { width: width, height: height };
|
||||
return { width: (width / height) * maxSize, height: maxSize };
|
||||
}
|
||||
if (height < maxSize) return { width: width, height: height };
|
||||
return { width: (width / height) * maxSize, height: maxSize };
|
||||
}
|
||||
|
||||
export function getImgSizesByQuality(
|
||||
@ -45,12 +44,14 @@ export function getImgSizesByQuality(
|
||||
return getImgSizesByMaxSize(width, height, 1024);
|
||||
case ImageQuality.Large:
|
||||
return getImgSizesByMaxSize(width, height, 2048);
|
||||
default:
|
||||
return { width: 0, height: 0 };
|
||||
}
|
||||
}
|
||||
|
||||
type ImgProps = {
|
||||
className?: string;
|
||||
image: StrapiImage;
|
||||
image?: StrapiImage;
|
||||
quality?: ImageQuality;
|
||||
alt?: ImageProps["alt"];
|
||||
layout?: ImageProps["layout"];
|
||||
@ -60,27 +61,28 @@ type ImgProps = {
|
||||
};
|
||||
|
||||
export default function Img(props: ImgProps): JSX.Element {
|
||||
const imgSize = getImgSizesByQuality(
|
||||
props.image.width,
|
||||
props.image.height,
|
||||
props.quality ? props.quality : ImageQuality.Small
|
||||
);
|
||||
|
||||
if (props.rawImg) {
|
||||
return (
|
||||
// eslint-disable-next-line @next/next/no-img-element
|
||||
<img
|
||||
className={props.className}
|
||||
src={getAssetURL(
|
||||
props.image.url,
|
||||
props.quality ? props.quality : ImageQuality.Small
|
||||
)}
|
||||
alt={props.alt ? props.alt : props.image.alternativeText}
|
||||
width={imgSize.width}
|
||||
height={imgSize.height}
|
||||
/>
|
||||
if (props.image) {
|
||||
const imgSize = getImgSizesByQuality(
|
||||
props.image.width,
|
||||
props.image.height,
|
||||
props.quality ? props.quality : ImageQuality.Small
|
||||
);
|
||||
} else {
|
||||
|
||||
if (props.rawImg) {
|
||||
return (
|
||||
// eslint-disable-next-line @next/next/no-img-element
|
||||
<img
|
||||
className={props.className}
|
||||
src={getAssetURL(
|
||||
props.image.url,
|
||||
props.quality ? props.quality : ImageQuality.Small
|
||||
)}
|
||||
alt={props.alt ? props.alt : props.image.alternativeText}
|
||||
width={imgSize.width}
|
||||
height={imgSize.height}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Image
|
||||
className={props.className}
|
||||
@ -98,4 +100,5 @@ export default function Img(props: ImgProps): JSX.Element {
|
||||
/>
|
||||
);
|
||||
}
|
||||
return <></>;
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
type InsetBoxProps = {
|
||||
className?: string;
|
||||
children: React.ReactChild | React.ReactChild[];
|
||||
children: React.ReactNode;
|
||||
id?: string;
|
||||
};
|
||||
|
||||
|
42
src/components/LanguageSwitcher.tsx
Normal file
42
src/components/LanguageSwitcher.tsx
Normal file
@ -0,0 +1,42 @@
|
||||
import {
|
||||
GetLanguagesQuery,
|
||||
GetWebsiteInterfaceQuery,
|
||||
} from "graphql/operations-types";
|
||||
import { useRouter } from "next/router";
|
||||
import { prettyLanguage } from "queries/helpers";
|
||||
import Button from "./Button";
|
||||
|
||||
type HorizontalLineProps = {
|
||||
className?: string;
|
||||
locales: string[];
|
||||
languages: GetLanguagesQuery["languages"]["data"];
|
||||
langui: GetWebsiteInterfaceQuery["websiteInterfaces"]["data"][number]["attributes"];
|
||||
href?: string;
|
||||
};
|
||||
|
||||
export default function HorizontalLine(
|
||||
props: HorizontalLineProps
|
||||
): JSX.Element {
|
||||
const { locales, langui, href } = props;
|
||||
const router = useRouter();
|
||||
|
||||
return (
|
||||
<div className="w-full grid place-content-center">
|
||||
<div className="flex flex-col place-items-center text-center gap-4 my-12 border-2 border-mid rounded-xl p-8 max-w-lg">
|
||||
<p>{langui.language_switch_message}</p>
|
||||
<div className="flex flex-wrap flex-row gap-2">
|
||||
{locales.map((locale, index) => (
|
||||
<Button
|
||||
key={index}
|
||||
active={locale === router.locale}
|
||||
href={href}
|
||||
locale={locale}
|
||||
>
|
||||
{prettyLanguage(locale, props.languages)}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
101
src/components/Library/ContentTOCLine.tsx
Normal file
101
src/components/Library/ContentTOCLine.tsx
Normal file
@ -0,0 +1,101 @@
|
||||
import Button from "components/Button";
|
||||
import Chip from "components/Chip";
|
||||
import {
|
||||
GetLibraryItemQuery,
|
||||
GetWebsiteInterfaceQuery,
|
||||
} from "graphql/operations-types";
|
||||
import { prettyinlineTitle, prettySlug } from "queries/helpers";
|
||||
import { useState } from "react";
|
||||
|
||||
type ContentTOCLineProps = {
|
||||
content: GetLibraryItemQuery["libraryItems"]["data"][number]["attributes"]["contents"]["data"][number];
|
||||
parentSlug: string;
|
||||
langui: GetWebsiteInterfaceQuery["websiteInterfaces"]["data"][number]["attributes"];
|
||||
};
|
||||
|
||||
export default function ContentTOCLine(
|
||||
props: ContentTOCLineProps
|
||||
): JSX.Element {
|
||||
const { content, langui, parentSlug } = props;
|
||||
|
||||
const [opened, setOpened] = useState(false);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`grid gap-2 px-4 rounded-lg ${
|
||||
opened && "bg-mid shadow-inner-sm shadow-shade h-auto py-3 my-2"
|
||||
}`}
|
||||
>
|
||||
<div className="grid gap-4 place-items-center grid-cols-[auto_auto_1fr_auto_12ch] thin:grid-cols-[auto_auto_1fr_auto]">
|
||||
<a>
|
||||
<h3 className="cursor-pointer" onClick={() => setOpened(!opened)}>
|
||||
{content.attributes.content.data &&
|
||||
content.attributes.content.data.attributes.titles.length > 0
|
||||
? prettyinlineTitle(
|
||||
content.attributes.content.data.attributes.titles[0]
|
||||
.pre_title,
|
||||
content.attributes.content.data.attributes.titles[0].title,
|
||||
content.attributes.content.data.attributes.titles[0].subtitle
|
||||
)
|
||||
: prettySlug(content.attributes.slug, props.parentSlug)}
|
||||
</h3>
|
||||
</a>
|
||||
<div className="flex flex-row flex-wrap gap-1">
|
||||
{content.attributes.content.data?.attributes.categories.data.map(
|
||||
(category) => (
|
||||
<Chip key={category.id}>{category.attributes.short}</Chip>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
<p className="border-b-2 h-4 w-full border-black border-dotted opacity-30"></p>
|
||||
<p>
|
||||
{content.attributes.range[0].__typename === "ComponentRangePageRange"
|
||||
? content.attributes.range[0].starting_page
|
||||
: ""}
|
||||
</p>
|
||||
{content.attributes.content.data && (
|
||||
<Chip className="justify-self-end thin:hidden">
|
||||
{content.attributes.content.data.attributes.type.data.attributes
|
||||
.titles.length > 0
|
||||
? content.attributes.content.data.attributes.type.data.attributes
|
||||
.titles[0].title
|
||||
: prettySlug(
|
||||
content.attributes.content.data.attributes.type.data
|
||||
.attributes.slug
|
||||
)}
|
||||
</Chip>
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
className={`grid-flow-col place-content-start place-items-center gap-2 ${
|
||||
opened ? "grid" : "hidden"
|
||||
}`}
|
||||
>
|
||||
<span className="material-icons text-dark">
|
||||
subdirectory_arrow_right
|
||||
</span>
|
||||
|
||||
{content.attributes.scan_set.length > 0 && (
|
||||
<Button
|
||||
href={`/library/${parentSlug}/scans#${content.attributes.slug}`}
|
||||
>
|
||||
{langui.view_scans}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{content.attributes.content.data && (
|
||||
<Button
|
||||
href={`/contents/${content.attributes.content.data.attributes.slug}`}
|
||||
>
|
||||
{langui.open_content}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{content.attributes.scan_set.length === 0 &&
|
||||
!content.attributes.content.data
|
||||
? "The content is not available"
|
||||
: ""}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
import Link from "next/link";
|
||||
import { GetContentsQuery } from "graphql/operations-types";
|
||||
import { prettySlug } from "queries/helpers";
|
||||
import Chip from "components/Chip";
|
||||
import Img, { ImageQuality } from "components/Img";
|
||||
import { GetContentsQuery } from "graphql/operations-types";
|
||||
import Link from "next/link";
|
||||
import { prettySlug } from "queries/helpers";
|
||||
|
||||
export type LibraryContentPreviewProps = {
|
||||
item: {
|
||||
@ -17,10 +17,10 @@ export type LibraryContentPreviewProps = {
|
||||
export default function LibraryContentPreview(
|
||||
props: LibraryContentPreviewProps
|
||||
): JSX.Element {
|
||||
const item = props.item;
|
||||
const { item } = props;
|
||||
|
||||
return (
|
||||
<Link href={"/contents/" + item.slug} passHref>
|
||||
<Link href={`/contents/${item.slug}`} passHref>
|
||||
<div className="drop-shadow-shade-xl cursor-pointer grid items-end fine:[--cover-opacity:0] hover:[--cover-opacity:1] hover:scale-[1.02] transition-transform">
|
||||
{item.thumbnail.data ? (
|
||||
<Img
|
||||
@ -33,14 +33,12 @@ export default function LibraryContentPreview(
|
||||
)}
|
||||
<div className="linearbg-obi fine:drop-shadow-shade-lg fine:absolute coarse:rounded-b-md bottom-2 -inset-x-0.5 opacity-[var(--cover-opacity)] transition-opacity z-20 grid p-4 gap-2">
|
||||
<div className="grid grid-flow-col gap-1 overflow-hidden place-content-start">
|
||||
{item.type.data ? (
|
||||
{item.type.data && (
|
||||
<Chip>
|
||||
{item.type.data.attributes.titles.length > 0
|
||||
? item.type.data.attributes.titles[0].title
|
||||
: prettySlug(item.type.data.attributes.slug)}
|
||||
</Chip>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
|
@ -1,8 +1,12 @@
|
||||
import Link from "next/link";
|
||||
import { GetLibraryItemsPreviewQuery } from "graphql/operations-types";
|
||||
import { prettyDate, prettyPrice, prettyItemSubType } from "queries/helpers";
|
||||
import Chip from "components/Chip";
|
||||
import Img, { ImageQuality } from "components/Img";
|
||||
import { useAppLayout } from "contexts/AppLayoutContext";
|
||||
import {
|
||||
GetCurrenciesQuery,
|
||||
GetLibraryItemsPreviewQuery,
|
||||
} from "graphql/operations-types";
|
||||
import Link from "next/link";
|
||||
import { prettyDate, prettyItemSubType, prettyPrice } from "queries/helpers";
|
||||
|
||||
export type LibraryItemsPreviewProps = {
|
||||
className?: string;
|
||||
@ -12,68 +16,76 @@ export type LibraryItemsPreviewProps = {
|
||||
title: GetLibraryItemsPreviewQuery["libraryItems"]["data"][number]["attributes"]["title"];
|
||||
subtitle: GetLibraryItemsPreviewQuery["libraryItems"]["data"][number]["attributes"]["subtitle"];
|
||||
price?: GetLibraryItemsPreviewQuery["libraryItems"]["data"][number]["attributes"]["price"];
|
||||
categories: GetLibraryItemsPreviewQuery["libraryItems"]["data"][number]["attributes"]["categories"];
|
||||
release_date?: GetLibraryItemsPreviewQuery["libraryItems"]["data"][number]["attributes"]["release_date"];
|
||||
metadata?: GetLibraryItemsPreviewQuery["libraryItems"]["data"][number]["attributes"]["metadata"];
|
||||
};
|
||||
currencies?: GetCurrenciesQuery["currencies"]["data"];
|
||||
};
|
||||
|
||||
export default function LibraryItemsPreview(
|
||||
props: LibraryItemsPreviewProps
|
||||
): JSX.Element {
|
||||
const item = props.item;
|
||||
const { item } = props;
|
||||
const appLayout = useAppLayout();
|
||||
|
||||
return (
|
||||
<Link href={"/library/" + item.slug} passHref>
|
||||
<Link href={`/library/${item.slug}`} passHref>
|
||||
<div
|
||||
className={`drop-shadow-shade-xl cursor-pointer grid items-end hover:rounded-3xl fine:[--cover-opacity:0] hover:[--cover-opacity:1] hover:scale-[1.02] transition-transform ${props.className}`}
|
||||
>
|
||||
{item.thumbnail.data ? (
|
||||
<Img
|
||||
image={item.thumbnail.data.attributes}
|
||||
quality={ImageQuality.Medium}
|
||||
quality={ImageQuality.Small}
|
||||
/>
|
||||
) : (
|
||||
<div className="w-full aspect-[21/29.7] bg-light rounded-lg"></div>
|
||||
)}
|
||||
|
||||
<div className="linearbg-obi fine:drop-shadow-shade-lg fine:absolute place-items-start bottom-2 -inset-x-0.5 opacity-[var(--cover-opacity)] transition-opacity z-20 grid p-4 gap-2">
|
||||
{item.metadata && item.metadata.length > 0 ? (
|
||||
{item.metadata && item.metadata.length > 0 && (
|
||||
<div className="flex flex-row gap-1">
|
||||
<Chip>{prettyItemSubType(item.metadata[0])}</Chip>
|
||||
</div>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
|
||||
<div>
|
||||
<h2 className="mobile:text-sm text-lg leading-5">{item.title}</h2>
|
||||
<h3 className="mobile:text-xs leading-3">{item.subtitle}</h3>
|
||||
</div>
|
||||
{item.release_date || item.price ? (
|
||||
|
||||
<div className="w-full grid grid-flow-col gap-1 overflow-x-scroll webkit-scrollbar:h-0 [scrollbar-width:none] place-content-start">
|
||||
{item.categories.data.map((category) => (
|
||||
<Chip key={category.id} className="text-sm">
|
||||
{category.attributes.short}
|
||||
</Chip>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{(item.release_date || item.price) && (
|
||||
<div className="grid grid-flow-col w-full">
|
||||
{item.release_date ? (
|
||||
{item.release_date && (
|
||||
<p className="mobile:text-xs text-sm">
|
||||
<span className="material-icons !text-base translate-y-[.15em] mr-1">
|
||||
event
|
||||
</span>
|
||||
{prettyDate(item.release_date)}
|
||||
</p>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
{item.price ? (
|
||||
{item.price && props.currencies && (
|
||||
<p className="mobile:text-xs text-sm justify-self-end">
|
||||
<span className="material-icons !text-base translate-y-[.15em] mr-1">
|
||||
shopping_cart
|
||||
</span>
|
||||
{prettyPrice(item.price)}
|
||||
{prettyPrice(
|
||||
item.price,
|
||||
props.currencies,
|
||||
appLayout.currency
|
||||
)}
|
||||
</p>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
39
src/components/LightBox.tsx
Normal file
39
src/components/LightBox.tsx
Normal file
@ -0,0 +1,39 @@
|
||||
import { useMediaMobile } from "hooks/useMediaQuery";
|
||||
import { Dispatch, SetStateAction } from "react";
|
||||
import Lightbox from "react-image-lightbox";
|
||||
|
||||
export type LightBoxProps = {
|
||||
setState:
|
||||
| Dispatch<SetStateAction<boolean | undefined>>
|
||||
| Dispatch<SetStateAction<boolean>>;
|
||||
state: boolean;
|
||||
images: string[];
|
||||
index: number;
|
||||
setIndex: Dispatch<SetStateAction<number>>;
|
||||
};
|
||||
|
||||
export default function LightBox(props: LightBoxProps): JSX.Element {
|
||||
const { state, setState, images, index, setIndex } = props;
|
||||
const mobile = useMediaMobile();
|
||||
|
||||
return (
|
||||
<>
|
||||
{state && (
|
||||
<Lightbox
|
||||
reactModalProps={{
|
||||
parentSelector: () => document.getElementById("MyAppLayout"),
|
||||
}}
|
||||
mainSrc={images[index]}
|
||||
prevSrc={index > 0 ? images[index - 1] : undefined}
|
||||
nextSrc={index < images.length ? images[index + 1] : undefined}
|
||||
onMovePrevRequest={() => setIndex(index - 1)}
|
||||
onMoveNextRequest={() => setIndex(index + 1)}
|
||||
imageCaption=""
|
||||
imageTitle=""
|
||||
onCloseRequest={() => setState(false)}
|
||||
imagePadding={mobile ? 0 : 70}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
@ -1,30 +1,388 @@
|
||||
import HorizontalLine from "components/HorizontalLine";
|
||||
import Img, { getAssetURL, ImageQuality } from "components/Img";
|
||||
import InsetBox from "components/InsetBox";
|
||||
import LightBox from "components/LightBox";
|
||||
import ToolTip from "components/ToolTip";
|
||||
import { useAppLayout } from "contexts/AppLayoutContext";
|
||||
import Markdown from "markdown-to-jsx";
|
||||
import SceneBreak from "./SceneBreak";
|
||||
import { useRouter } from "next/router";
|
||||
import { slugify } from "queries/helpers";
|
||||
import React, { useState } from "react";
|
||||
import ReactDOMServer from "react-dom/server";
|
||||
|
||||
type ScenBreakProps = {
|
||||
type MarkdawnProps = {
|
||||
className?: string;
|
||||
text: string;
|
||||
};
|
||||
|
||||
export default function Markdawn(props: ScenBreakProps): JSX.Element {
|
||||
if (props.text) {
|
||||
export default function Markdawn(props: MarkdawnProps): JSX.Element {
|
||||
const appLayout = useAppLayout();
|
||||
const text = preprocessMarkDawn(props.text);
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const [lightboxOpen, setLightboxOpen] = useState(false);
|
||||
const [lightboxImages, setLightboxImages] = useState([""]);
|
||||
const [lightboxIndex, setLightboxIndex] = useState(0);
|
||||
|
||||
if (text) {
|
||||
return (
|
||||
<Markdown
|
||||
className={`prose prose-p:text-justify text-black ${props.className}`}
|
||||
options={{
|
||||
overrides: {
|
||||
hr: {
|
||||
component: SceneBreak,
|
||||
<>
|
||||
<LightBox
|
||||
state={lightboxOpen}
|
||||
setState={setLightboxOpen}
|
||||
images={lightboxImages}
|
||||
index={lightboxIndex}
|
||||
setIndex={setLightboxIndex}
|
||||
/>
|
||||
<Markdown
|
||||
className={`formatted ${props.className}`}
|
||||
options={{
|
||||
slugify: slugify,
|
||||
overrides: {
|
||||
a: {
|
||||
component: (compProps: {
|
||||
href: string;
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
if (compProps.href.startsWith("/")) {
|
||||
return (
|
||||
<a onClick={async () => router.push(compProps.href)}>
|
||||
{compProps.children}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
return <a href={compProps.href}>{compProps.children}</a>;
|
||||
},
|
||||
},
|
||||
h1: {
|
||||
component: (compProps: {
|
||||
id: string;
|
||||
style: React.CSSProperties;
|
||||
children: React.ReactNode;
|
||||
}) => (
|
||||
<h1 id={compProps.id} style={compProps.style}>
|
||||
{compProps.children}
|
||||
<HeaderToolTip id={compProps.id} />
|
||||
</h1>
|
||||
),
|
||||
},
|
||||
h2: {
|
||||
component: (compProps: {
|
||||
id: string;
|
||||
style: React.CSSProperties;
|
||||
children: React.ReactNode;
|
||||
}) => (
|
||||
<h2 id={compProps.id} style={compProps.style}>
|
||||
{compProps.children}
|
||||
<HeaderToolTip id={compProps.id} />
|
||||
</h2>
|
||||
),
|
||||
},
|
||||
h3: {
|
||||
component: (compProps: {
|
||||
id: string;
|
||||
style: React.CSSProperties;
|
||||
children: React.ReactNode;
|
||||
}) => (
|
||||
<h3 id={compProps.id} style={compProps.style}>
|
||||
{compProps.children}
|
||||
<HeaderToolTip id={compProps.id} />
|
||||
</h3>
|
||||
),
|
||||
},
|
||||
h4: {
|
||||
component: (compProps: {
|
||||
id: string;
|
||||
style: React.CSSProperties;
|
||||
children: React.ReactNode;
|
||||
}) => (
|
||||
<h4 id={compProps.id} style={compProps.style}>
|
||||
{compProps.children}
|
||||
<HeaderToolTip id={compProps.id} />
|
||||
</h4>
|
||||
),
|
||||
},
|
||||
h5: {
|
||||
component: (compProps: {
|
||||
id: string;
|
||||
style: React.CSSProperties;
|
||||
children: React.ReactNode;
|
||||
}) => (
|
||||
<h5 id={compProps.id} style={compProps.style}>
|
||||
{compProps.children}
|
||||
<HeaderToolTip id={compProps.id} />
|
||||
</h5>
|
||||
),
|
||||
},
|
||||
h6: {
|
||||
component: (compProps: {
|
||||
id: string;
|
||||
style: React.CSSProperties;
|
||||
children: React.ReactNode;
|
||||
}) => (
|
||||
<h6 id={compProps.id} style={compProps.style}>
|
||||
{compProps.children}
|
||||
<HeaderToolTip id={compProps.id} />
|
||||
</h6>
|
||||
),
|
||||
},
|
||||
Sep: {
|
||||
component: () => <div className="my-24"></div>,
|
||||
},
|
||||
SceneBreak: {
|
||||
component: (compProps: { id: string }) => (
|
||||
<div
|
||||
id={compProps.id}
|
||||
className={"h-0 text-center text-3xl text-dark mt-16 mb-20"}
|
||||
>
|
||||
* * *
|
||||
</div>
|
||||
),
|
||||
},
|
||||
IntraLink: {
|
||||
component: (compProps: {
|
||||
children: React.ReactNode;
|
||||
target?: string;
|
||||
page?: string;
|
||||
}) => {
|
||||
const slug = compProps.target
|
||||
? slugify(compProps.target)
|
||||
: slugify(compProps.children?.toString());
|
||||
return (
|
||||
<a
|
||||
onClick={async () =>
|
||||
router.replace(
|
||||
`${compProps.page ? compProps.page : ""}#${slug}`
|
||||
)
|
||||
}
|
||||
>
|
||||
{compProps.children}
|
||||
</a>
|
||||
);
|
||||
},
|
||||
},
|
||||
player: {
|
||||
component: () => (
|
||||
<span className="text-dark opacity-70">
|
||||
{appLayout.playerName ? appLayout.playerName : "<player>"}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
Transcript: {
|
||||
component: (compProps) => (
|
||||
<div className="grid grid-cols-[auto_1fr] mobile:grid-cols-1 gap-x-6 gap-y-2">
|
||||
{compProps.children}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
Line: {
|
||||
component: (compProps) => (
|
||||
<>
|
||||
<strong className="text-dark opacity-60 mobile:!-mb-4">
|
||||
{compProps.name}
|
||||
</strong>
|
||||
<p className="whitespace-pre-line">{compProps.children}</p>
|
||||
</>
|
||||
),
|
||||
},
|
||||
InsetBox: {
|
||||
component: (compProps) => (
|
||||
<InsetBox className="my-12">{compProps.children}</InsetBox>
|
||||
),
|
||||
},
|
||||
li: {
|
||||
component: (compProps: { children: React.ReactNode }) => (
|
||||
<li
|
||||
className={
|
||||
compProps.children &&
|
||||
ReactDOMServer.renderToStaticMarkup(
|
||||
<>{compProps.children}</>
|
||||
).length > 100
|
||||
? "my-4"
|
||||
: ""
|
||||
}
|
||||
>
|
||||
{compProps.children}
|
||||
</li>
|
||||
),
|
||||
},
|
||||
Highlight: {
|
||||
component: (compProps: { children: React.ReactNode }) => (
|
||||
<mark>{compProps.children}</mark>
|
||||
),
|
||||
},
|
||||
footer: {
|
||||
component: (compProps: { children: React.ReactNode }) => (
|
||||
<>
|
||||
<HorizontalLine />
|
||||
<div>{compProps.children}</div>
|
||||
</>
|
||||
),
|
||||
},
|
||||
blockquote: {
|
||||
component: (compProps: {
|
||||
children: React.ReactNode;
|
||||
cite?: string;
|
||||
}) => (
|
||||
<blockquote>
|
||||
{compProps.cite ? (
|
||||
<>
|
||||
“{compProps.children}”
|
||||
<cite>— {compProps.cite}</cite>
|
||||
</>
|
||||
) : (
|
||||
compProps.children
|
||||
)}
|
||||
</blockquote>
|
||||
),
|
||||
},
|
||||
img: {
|
||||
component: (compProps: {
|
||||
alt: string;
|
||||
src: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
caption?: string;
|
||||
name?: string;
|
||||
}) => (
|
||||
<div
|
||||
className="my-8 cursor-pointer"
|
||||
onClick={() => {
|
||||
setLightboxOpen(true);
|
||||
setLightboxImages([
|
||||
compProps.src.startsWith("/uploads/")
|
||||
? getAssetURL(compProps.src, ImageQuality.Large)
|
||||
: compProps.src,
|
||||
]);
|
||||
setLightboxIndex(0);
|
||||
}}
|
||||
>
|
||||
{compProps.src.startsWith("/uploads/") ? (
|
||||
<div className="relative w-full aspect-video">
|
||||
<Img
|
||||
image={{
|
||||
__typename: "UploadFile",
|
||||
alternativeText: compProps.alt,
|
||||
url: compProps.src,
|
||||
width: compProps.width ?? 1500,
|
||||
height: compProps.height ?? 1000,
|
||||
caption: compProps.caption ?? "",
|
||||
name: compProps.name ?? "",
|
||||
}}
|
||||
layout="fill"
|
||||
objectFit="contain"
|
||||
quality={ImageQuality.Medium}
|
||||
></Img>
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid place-content-center">
|
||||
{/* eslint-disable-next-line jsx-a11y/alt-text */}
|
||||
<img {...compProps} className="max-h-[50vh] " />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
},
|
||||
player: {
|
||||
component: () => {return <span className="text-dark opacity-70">{"<player>"}</span>}
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
{props.text}
|
||||
</Markdown>
|
||||
}}
|
||||
>
|
||||
{text}
|
||||
</Markdown>
|
||||
</>
|
||||
);
|
||||
}
|
||||
return <></>;
|
||||
}
|
||||
}
|
||||
|
||||
function HeaderToolTip(props: { id: string }) {
|
||||
return (
|
||||
<ToolTip
|
||||
content={"Copy anchor link"}
|
||||
trigger="mouseenter"
|
||||
className="text-sm"
|
||||
>
|
||||
<ToolTip content={"Copied! 👍"} trigger="click" className="text-sm">
|
||||
<span
|
||||
className="material-icons transition-color hover:text-dark cursor-pointer"
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(
|
||||
`${process.env.NEXT_PUBLIC_URL_SELF + window.location.pathname}#${
|
||||
props.id
|
||||
}`
|
||||
);
|
||||
}}
|
||||
>
|
||||
link
|
||||
</span>
|
||||
</ToolTip>
|
||||
</ToolTip>
|
||||
);
|
||||
}
|
||||
|
||||
export function preprocessMarkDawn(text: string): string {
|
||||
if (!text) return "";
|
||||
|
||||
let scenebreakIndex = 0;
|
||||
const visitedSlugs: string[] = [];
|
||||
|
||||
const result = text.split("\n").map((line) => {
|
||||
if (line === "* * *" || line === "---") {
|
||||
scenebreakIndex += 1;
|
||||
return `<SceneBreak id="scene-break-${scenebreakIndex}">`;
|
||||
}
|
||||
|
||||
if (line.startsWith("# ")) {
|
||||
return markdawnHeadersParser(headerLevels.h1, line, visitedSlugs);
|
||||
}
|
||||
|
||||
if (line.startsWith("## ")) {
|
||||
return markdawnHeadersParser(headerLevels.h2, line, visitedSlugs);
|
||||
}
|
||||
|
||||
if (line.startsWith("### ")) {
|
||||
return markdawnHeadersParser(headerLevels.h3, line, visitedSlugs);
|
||||
}
|
||||
|
||||
if (line.startsWith("#### ")) {
|
||||
return markdawnHeadersParser(headerLevels.h4, line, visitedSlugs);
|
||||
}
|
||||
|
||||
if (line.startsWith("##### ")) {
|
||||
return markdawnHeadersParser(headerLevels.h5, line, visitedSlugs);
|
||||
}
|
||||
|
||||
if (line.startsWith("###### ")) {
|
||||
return markdawnHeadersParser(headerLevels.h6, line, visitedSlugs);
|
||||
}
|
||||
|
||||
return line;
|
||||
});
|
||||
return result.join("\n");
|
||||
}
|
||||
|
||||
enum headerLevels {
|
||||
h1 = 1,
|
||||
h2 = 2,
|
||||
h3 = 3,
|
||||
h4 = 4,
|
||||
h5 = 5,
|
||||
h6 = 6,
|
||||
}
|
||||
|
||||
function markdawnHeadersParser(
|
||||
headerLevel: headerLevels,
|
||||
line: string,
|
||||
visitedSlugs: string[]
|
||||
): string {
|
||||
const lineText = line.slice(headerLevel + 1);
|
||||
const slug = slugify(lineText);
|
||||
let newSlug = slug;
|
||||
let index = 2;
|
||||
while (visitedSlugs.includes(newSlug)) {
|
||||
newSlug = `${slug}-${index}`;
|
||||
index += 1;
|
||||
}
|
||||
visitedSlugs.push(newSlug);
|
||||
return `<${headerLevels[headerLevel]} id="${newSlug}">${lineText}</${headerLevels[headerLevel]}>`;
|
||||
}
|
||||
|
@ -1,15 +0,0 @@
|
||||
type ScenBreakProps = {
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export default function SceneBreak(props: ScenBreakProps): JSX.Element {
|
||||
return (
|
||||
<div
|
||||
className={
|
||||
"h-0 text-center text-3xl text-dark mt-16 mb-20" + " " + props.className
|
||||
}
|
||||
>
|
||||
* * *
|
||||
</div>
|
||||
);
|
||||
}
|
165
src/components/Markdown/TOC.tsx
Normal file
165
src/components/Markdown/TOC.tsx
Normal file
@ -0,0 +1,165 @@
|
||||
import { useRouter } from "next/router";
|
||||
import { slugify } from "queries/helpers";
|
||||
import { preprocessMarkDawn } from "./Markdawn";
|
||||
|
||||
type TOCProps = {
|
||||
text: string;
|
||||
title?: string;
|
||||
};
|
||||
|
||||
export default function TOCComponent(props: TOCProps): JSX.Element {
|
||||
const { text, title } = props;
|
||||
const toc = getTocFromMarkdawn(preprocessMarkDawn(text), title);
|
||||
const router = useRouter();
|
||||
|
||||
return (
|
||||
<>
|
||||
<h3 className="text-xl">Table of content</h3>
|
||||
<div className="text-left max-w-[14.5rem]">
|
||||
<p className="my-2 overflow-x-hidden relative text-ellipsis whitespace-nowrap text-left">
|
||||
<a className="" onClick={async () => router.replace(`#${toc.slug}`)}>
|
||||
{<abbr title={toc.title}>{toc.title}</abbr>}
|
||||
</a>
|
||||
</p>
|
||||
<TOCLevel tocchildren={toc.children} parentNumbering="" />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
type TOCLevelProps = {
|
||||
tocchildren: TOC[];
|
||||
parentNumbering: string;
|
||||
};
|
||||
|
||||
function TOCLevel(props: TOCLevelProps): JSX.Element {
|
||||
const router = useRouter();
|
||||
const { tocchildren, parentNumbering } = props;
|
||||
return (
|
||||
<ol className="pl-4 text-left">
|
||||
{tocchildren.map((child, childIndex) => (
|
||||
<>
|
||||
<li
|
||||
key={child.slug}
|
||||
className="my-2 overflow-x-hidden w-full text-ellipsis whitespace-nowrap"
|
||||
>
|
||||
<span className="text-dark">{`${parentNumbering}${
|
||||
childIndex + 1
|
||||
}.`}</span>{" "}
|
||||
<a onClick={async () => router.replace(`#${child.slug}`)}>
|
||||
{<abbr title={child.title}>{child.title}</abbr>}
|
||||
</a>
|
||||
</li>
|
||||
<TOCLevel
|
||||
tocchildren={child.children}
|
||||
parentNumbering={`${parentNumbering}${childIndex + 1}.`}
|
||||
/>
|
||||
</>
|
||||
))}
|
||||
</ol>
|
||||
);
|
||||
}
|
||||
|
||||
export type TOC = {
|
||||
title: string;
|
||||
slug: string;
|
||||
children: TOC[];
|
||||
};
|
||||
|
||||
export function getTocFromMarkdawn(text: string, title?: string): TOC {
|
||||
const toc: TOC = {
|
||||
title: title ?? "Return to top",
|
||||
slug: slugify(title) ?? "",
|
||||
children: [],
|
||||
};
|
||||
let h2 = -1;
|
||||
let h3 = -1;
|
||||
let h4 = -1;
|
||||
let h5 = -1;
|
||||
let scenebreak = 0;
|
||||
let scenebreakIndex = 0;
|
||||
|
||||
function getTitle(line: string): string {
|
||||
return line.slice(line.indexOf(`">`) + 2, line.indexOf("</"));
|
||||
}
|
||||
|
||||
function getSlug(line: string): string {
|
||||
return line.slice(line.indexOf(`id="`) + 4, line.indexOf(`">`));
|
||||
}
|
||||
|
||||
text.split("\n").map((line) => {
|
||||
if (line.startsWith("<h1 id=")) {
|
||||
toc.title = getTitle(line);
|
||||
toc.slug = getSlug(line);
|
||||
} else if (line.startsWith("<h2 id=")) {
|
||||
toc.children.push({
|
||||
title: getTitle(line),
|
||||
slug: getSlug(line),
|
||||
children: [],
|
||||
});
|
||||
h2 += 1;
|
||||
h3 = -1;
|
||||
h4 = -1;
|
||||
h5 = -1;
|
||||
scenebreak = 0;
|
||||
} else if (line.startsWith("<h3 id=")) {
|
||||
toc.children[h2].children.push({
|
||||
title: getTitle(line),
|
||||
slug: getSlug(line),
|
||||
children: [],
|
||||
});
|
||||
h3 += 1;
|
||||
h4 = -1;
|
||||
h5 = -1;
|
||||
scenebreak = 0;
|
||||
} else if (line.startsWith("<h4 id=")) {
|
||||
toc.children[h2].children[h3].children.push({
|
||||
title: getTitle(line),
|
||||
slug: getSlug(line),
|
||||
children: [],
|
||||
});
|
||||
h4 += 1;
|
||||
h5 = -1;
|
||||
scenebreak = 0;
|
||||
} else if (line.startsWith("<h5 id=")) {
|
||||
toc.children[h2].children[h3].children[h4].children.push({
|
||||
title: getTitle(line),
|
||||
slug: getSlug(line),
|
||||
children: [],
|
||||
});
|
||||
h5 += 1;
|
||||
scenebreak = 0;
|
||||
} else if (line.startsWith("<h6 id=")) {
|
||||
toc.children[h2].children[h3].children[h4].children[h5].children.push({
|
||||
title: getTitle(line),
|
||||
slug: getSlug(line),
|
||||
children: [],
|
||||
});
|
||||
} else if (line.startsWith(`<SceneBreak`)) {
|
||||
scenebreak += 1;
|
||||
scenebreakIndex += 1;
|
||||
|
||||
const child = {
|
||||
title: `Scene break ${scenebreak}`,
|
||||
slug: slugify(`scene-break-${scenebreakIndex}`),
|
||||
children: [],
|
||||
};
|
||||
|
||||
if (h5 >= 0) {
|
||||
toc.children[h2].children[h3].children[h4].children[h5].children.push(
|
||||
child
|
||||
);
|
||||
} else if (h4 >= 0) {
|
||||
toc.children[h2].children[h3].children[h4].children.push(child);
|
||||
} else if (h3 >= 0) {
|
||||
toc.children[h2].children[h3].children.push(child);
|
||||
} else if (h2 >= 0) {
|
||||
toc.children[h2].children.push(child);
|
||||
} else {
|
||||
toc.children.push(child);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return toc;
|
||||
}
|
64
src/components/News/PostsPreview.tsx
Normal file
64
src/components/News/PostsPreview.tsx
Normal file
@ -0,0 +1,64 @@
|
||||
import Chip from "components/Chip";
|
||||
import Img, { ImageQuality } from "components/Img";
|
||||
import { GetPostsPreviewQuery } from "graphql/operations-types";
|
||||
import Link from "next/link";
|
||||
import { prettyDate, prettySlug } from "queries/helpers";
|
||||
|
||||
export type PostPreviewProps = {
|
||||
post: {
|
||||
slug: GetPostsPreviewQuery["posts"]["data"][number]["attributes"]["slug"];
|
||||
thumbnail: GetPostsPreviewQuery["posts"]["data"][number]["attributes"]["thumbnail"];
|
||||
translations: GetPostsPreviewQuery["posts"]["data"][number]["attributes"]["translations"];
|
||||
categories: GetPostsPreviewQuery["posts"]["data"][number]["attributes"]["categories"];
|
||||
date: GetPostsPreviewQuery["posts"]["data"][number]["attributes"]["date"];
|
||||
};
|
||||
};
|
||||
|
||||
export default function PostPreview(props: PostPreviewProps): JSX.Element {
|
||||
const { post } = props;
|
||||
|
||||
return (
|
||||
<Link href={`/news/${post.slug}`} passHref>
|
||||
<div className="drop-shadow-shade-xl cursor-pointer grid items-end hover:scale-[1.02] transition-transform">
|
||||
{post.thumbnail.data ? (
|
||||
<Img
|
||||
className="rounded-md rounded-b-none"
|
||||
image={post.thumbnail.data.attributes}
|
||||
quality={ImageQuality.Medium}
|
||||
/>
|
||||
) : (
|
||||
<div className="w-full aspect-[3/2] bg-light rounded-lg"></div>
|
||||
)}
|
||||
<div className="linearbg-obi fine:drop-shadow-shade-lg rounded-b-md top-full transition-opacity z-20 grid p-4 gap-2">
|
||||
<div className="grid grid-flow-col w-full">
|
||||
{post.date && (
|
||||
<p className="mobile:text-xs text-sm">
|
||||
<span className="material-icons !text-base translate-y-[.15em] mr-1">
|
||||
event
|
||||
</span>
|
||||
{prettyDate(post.date)}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
{post.translations.length > 0 ? (
|
||||
<>
|
||||
<h1 className="text-xl">{post.translations[0].title}</h1>
|
||||
<p>{post.translations[0].excerpt}</p>
|
||||
</>
|
||||
) : (
|
||||
<h1 className="text-lg">{prettySlug(post.slug)}</h1>
|
||||
)}
|
||||
</div>
|
||||
<div className="grid grid-flow-col gap-1 overflow-x-scroll webkit-scrollbar:w-0 [scrollbar-width:none] place-content-start">
|
||||
{post.categories.data.map((category) => (
|
||||
<Chip key={category.id} className="text-sm">
|
||||
{category.attributes.short}
|
||||
</Chip>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
}
|
@ -1,14 +1,12 @@
|
||||
import ToolTip from "components/ToolTip";
|
||||
import { useRouter } from "next/router";
|
||||
import Link from "next/link";
|
||||
import { MouseEventHandler } from "react";
|
||||
import ReactDOMServer from "react-dom/server";
|
||||
|
||||
type NavOptionProps = {
|
||||
url: string;
|
||||
icon?: string;
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
tooltipId?: string;
|
||||
border?: boolean;
|
||||
reduced?: boolean;
|
||||
onClick?: MouseEventHandler<HTMLDivElement>;
|
||||
@ -25,21 +23,29 @@ export default function NavOption(props: NavOptionProps): JSX.Element {
|
||||
} ${isActive ? divActive : ""}`;
|
||||
|
||||
return (
|
||||
<Link href={props.url} passHref>
|
||||
<ToolTip
|
||||
content={
|
||||
<div>
|
||||
<h3 className="text-2xl">{props.title}</h3>
|
||||
{props.subtitle && <p className="col-start-2">{props.subtitle}</p>}
|
||||
</div>
|
||||
}
|
||||
placement="right"
|
||||
className="text-left"
|
||||
disabled={!props.reduced}
|
||||
>
|
||||
<div
|
||||
onClick={props.onClick}
|
||||
data-html
|
||||
data-multiline
|
||||
data-tip={ReactDOMServer.renderToStaticMarkup(
|
||||
<div className="px-4 py-3">
|
||||
<h3 className="text-2xl">{props.title}</h3>
|
||||
{props.subtitle && (
|
||||
<p className="max-w-[10rem]">{props.subtitle}</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
data-for={props.tooltipId}
|
||||
className={`grid grid-flow-col grid-cols-[auto] auto-cols-fr justify-center ${
|
||||
onClick={(event) => {
|
||||
if (props.onClick) props.onClick(event);
|
||||
if (props.url) {
|
||||
if (props.url.startsWith("#")) {
|
||||
router.replace(props.url);
|
||||
} else {
|
||||
router.push(props.url);
|
||||
}
|
||||
}
|
||||
}}
|
||||
className={`relative grid grid-flow-col grid-cols-[auto] auto-cols-fr justify-center ${
|
||||
props.icon ? "text-left" : "text-center"
|
||||
} ${divCommon}`}
|
||||
>
|
||||
@ -54,6 +60,6 @@ export default function NavOption(props: NavOptionProps): JSX.Element {
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Link>
|
||||
</ToolTip>
|
||||
);
|
||||
}
|
||||
|
@ -10,10 +10,8 @@ export default function PanelHeader(props: PanelHeaderProps): JSX.Element {
|
||||
return (
|
||||
<>
|
||||
<div className="w-full grid place-items-center">
|
||||
{props.icon ? (
|
||||
{props.icon && (
|
||||
<span className="material-icons !text-4xl mb-3">{props.icon}</span>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
<h2 className="text-2xl">{props.title}</h2>
|
||||
{props.description ? <p>{props.description}</p> : ""}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import Button from "components/Button";
|
||||
import HorizontalLine from "components/HorizontalLine";
|
||||
import { useAppLayout } from "contexts/AppLayoutContext";
|
||||
import { GetWebsiteInterfaceQuery } from "graphql/operations-types";
|
||||
|
||||
@ -6,14 +7,39 @@ type ReturnButtonProps = {
|
||||
href: string;
|
||||
title: string;
|
||||
langui: GetWebsiteInterfaceQuery["websiteInterfaces"]["data"][number]["attributes"];
|
||||
displayOn: ReturnButtonType;
|
||||
horizontalLine?: boolean;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export enum ReturnButtonType {
|
||||
mobile = "mobile",
|
||||
desktop = "desktop",
|
||||
both = "both",
|
||||
}
|
||||
|
||||
export default function ReturnButton(props: ReturnButtonProps): JSX.Element {
|
||||
const appLayout = useAppLayout();
|
||||
|
||||
return (
|
||||
<Button onClick={() => appLayout.setSubPanelOpen(false)} href={props.href}>
|
||||
❮ {props.langui.global_return_label} {props.title}
|
||||
</Button>
|
||||
<div
|
||||
className={`${
|
||||
props.displayOn === ReturnButtonType.mobile
|
||||
? "desktop:hidden"
|
||||
: props.displayOn === ReturnButtonType.desktop
|
||||
? "mobile:hidden"
|
||||
: ""
|
||||
} ${props.className}`}
|
||||
>
|
||||
<Button
|
||||
onClick={() => appLayout.setSubPanelOpen(false)}
|
||||
href={props.href}
|
||||
className="grid grid-flow-col gap-2"
|
||||
>
|
||||
<span className="material-icons">navigate_before</span>
|
||||
{props.langui.return_to} {props.title}
|
||||
</Button>
|
||||
{props.horizontalLine && <HorizontalLine />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -5,19 +5,22 @@ type ContentPanelProps = {
|
||||
};
|
||||
|
||||
export enum ContentPanelWidthSizes {
|
||||
default,
|
||||
large,
|
||||
default = "default",
|
||||
large = "large",
|
||||
}
|
||||
|
||||
export default function ContentPanel(props: ContentPanelProps): JSX.Element {
|
||||
const width = props.width ? props.width : ContentPanelWidthSizes.default;
|
||||
const widthCSS =
|
||||
width === ContentPanelWidthSizes.default ? "max-w-[45rem]" : "w-full";
|
||||
const prose = props.autoformat ? "prose text-justify" : "";
|
||||
width === ContentPanelWidthSizes.default ? "max-w-2xl" : "w-full";
|
||||
|
||||
return (
|
||||
<div className={`grid pt-10 pb-20 px-6 desktop:py-20 desktop:px-10`}>
|
||||
<main className={`${prose} ${widthCSS} place-self-center`}>
|
||||
<main
|
||||
className={`${
|
||||
props.autoformat && "formatted"
|
||||
} ${widthCSS} place-self-center`}
|
||||
>
|
||||
{props.children}
|
||||
</main>
|
||||
</div>
|
||||
|
@ -1,20 +1,20 @@
|
||||
import Link from "next/link";
|
||||
import NavOption from "components/PanelComponents/NavOption";
|
||||
import SVG from "components/SVG";
|
||||
import { useRouter } from "next/router";
|
||||
import Button from "components/Button";
|
||||
import HorizontalLine from "components/HorizontalLine";
|
||||
import { GetWebsiteInterfaceQuery } from "graphql/operations-types";
|
||||
import Markdown from "markdown-to-jsx";
|
||||
import { useMediaDesktop } from "hooks/useMediaQuery";
|
||||
import NavOption from "components/PanelComponents/NavOption";
|
||||
import ToolTip from "components/ToolTip";
|
||||
import { useAppLayout } from "contexts/AppLayoutContext";
|
||||
import { GetWebsiteInterfaceQuery } from "graphql/operations-types";
|
||||
import { useMediaDesktop } from "hooks/useMediaQuery";
|
||||
import Markdown from "markdown-to-jsx";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
type MainPanelProps = {
|
||||
langui: GetWebsiteInterfaceQuery["websiteInterfaces"]["data"][number]["attributes"];
|
||||
};
|
||||
|
||||
export default function MainPanel(props: MainPanelProps): JSX.Element {
|
||||
const langui = props.langui;
|
||||
const { langui } = props;
|
||||
const router = useRouter();
|
||||
const isDesktop = useMediaDesktop();
|
||||
const appLayout = useAppLayout();
|
||||
@ -25,6 +25,20 @@ export default function MainPanel(props: MainPanelProps): JSX.Element {
|
||||
appLayout.mainPanelReduced && isDesktop && "px-4"
|
||||
}`}
|
||||
>
|
||||
{/* Reduce/expand main menu */}
|
||||
<div
|
||||
className={`mobile:hidden top-1/2 fixed ${
|
||||
appLayout.mainPanelReduced ? "left-[4.65rem]" : "left-[18.65rem]"
|
||||
}`}
|
||||
onClick={() =>
|
||||
appLayout.setMainPanelReduced(!appLayout.mainPanelReduced)
|
||||
}
|
||||
>
|
||||
<Button className="material-icons bg-light !px-2">
|
||||
{appLayout.mainPanelReduced ? "chevron_right" : "chevron_left"}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className="grid place-items-center">
|
||||
<Link href="/" passHref>
|
||||
@ -49,32 +63,74 @@ export default function MainPanel(props: MainPanelProps): JSX.Element {
|
||||
: "flex-row"
|
||||
} flex-wrap gap-2`}
|
||||
>
|
||||
<Button
|
||||
onClick={() => {
|
||||
appLayout.setDarkMode(!appLayout.darkMode);
|
||||
appLayout.setSelectedThemeMode(true);
|
||||
}}
|
||||
className={
|
||||
appLayout.mainPanelReduced && isDesktop ? "" : "!py-0.5 !px-2.5"
|
||||
}
|
||||
<ToolTip
|
||||
content={<h3 className="text-2xl">{langui.open_settings}</h3>}
|
||||
placement="right"
|
||||
className="text-left"
|
||||
disabled={!appLayout.mainPanelReduced}
|
||||
>
|
||||
<span className="material-icons !text-sm">
|
||||
{appLayout.darkMode ? "dark_mode" : "light_mode"}
|
||||
</span>
|
||||
</Button>
|
||||
|
||||
{router.locale && (
|
||||
<Button
|
||||
onClick={() => appLayout.setLanguagePanelOpen(true)}
|
||||
className={
|
||||
appLayout.mainPanelReduced && isDesktop
|
||||
? ""
|
||||
: "!py-0.5 !px-2.5 !text-sm"
|
||||
: "!py-0.5 !px-2.5"
|
||||
}
|
||||
onClick={() => {
|
||||
appLayout.setConfigPanelOpen(true);
|
||||
}}
|
||||
>
|
||||
<span
|
||||
className={`material-icons ${
|
||||
!(appLayout.mainPanelReduced && isDesktop) && "!text-sm"
|
||||
} `}
|
||||
>
|
||||
settings
|
||||
</span>
|
||||
</Button>
|
||||
</ToolTip>
|
||||
|
||||
{router.locale && (
|
||||
<ToolTip
|
||||
content={<h3 className="text-2xl">{langui.change_language}</h3>}
|
||||
placement="right"
|
||||
className="text-left"
|
||||
disabled={!appLayout.mainPanelReduced}
|
||||
>
|
||||
<Button
|
||||
onClick={() => appLayout.setLanguagePanelOpen(true)}
|
||||
className={
|
||||
appLayout.mainPanelReduced && isDesktop
|
||||
? ""
|
||||
: "!py-0.5 !px-2.5 !text-sm"
|
||||
}
|
||||
>
|
||||
{router.locale.toUpperCase()}
|
||||
</Button>
|
||||
</ToolTip>
|
||||
)}
|
||||
|
||||
{/* <ToolTip
|
||||
content={<h3 className="text-2xl">{langui.open_search}</h3>}
|
||||
placement="right"
|
||||
className="text-left"
|
||||
disabled={!appLayout.mainPanelReduced}
|
||||
>
|
||||
<Button
|
||||
className={
|
||||
appLayout.mainPanelReduced && isDesktop
|
||||
? ""
|
||||
: "!py-0.5 !px-2.5"
|
||||
}
|
||||
>
|
||||
{router.locale.toUpperCase()}
|
||||
<span
|
||||
className={`material-icons ${
|
||||
!(appLayout.mainPanelReduced && isDesktop) && "!text-sm"
|
||||
} `}
|
||||
>
|
||||
search
|
||||
</span>
|
||||
</Button>
|
||||
)}
|
||||
</ToolTip> */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -84,9 +140,8 @@ export default function MainPanel(props: MainPanelProps): JSX.Element {
|
||||
<NavOption
|
||||
url="/library"
|
||||
icon="library_books"
|
||||
title={langui.main_library}
|
||||
subtitle={langui.main_library_description}
|
||||
tooltipId="MainPanelTooltip"
|
||||
title={langui.library}
|
||||
subtitle={langui.library_short_description}
|
||||
reduced={appLayout.mainPanelReduced && isDesktop}
|
||||
onClick={() => appLayout.setMainPanelOpen(false)}
|
||||
/>
|
||||
@ -94,9 +149,8 @@ export default function MainPanel(props: MainPanelProps): JSX.Element {
|
||||
<NavOption
|
||||
url="/contents"
|
||||
icon="workspaces"
|
||||
title="Contents"
|
||||
subtitle="Explore all content and filter by type or category"
|
||||
tooltipId="MainPanelTooltip"
|
||||
title={langui.contents}
|
||||
subtitle={langui.contents_short_description}
|
||||
reduced={appLayout.mainPanelReduced && isDesktop}
|
||||
onClick={() => appLayout.setMainPanelOpen(false)}
|
||||
/>
|
||||
@ -104,66 +158,73 @@ export default function MainPanel(props: MainPanelProps): JSX.Element {
|
||||
<NavOption
|
||||
url="/wiki"
|
||||
icon="travel_explore"
|
||||
title={langui.main_wiki}
|
||||
subtitle={langui.main_wiki_description}
|
||||
tooltipId="MainPanelTooltip"
|
||||
title={langui.wiki}
|
||||
subtitle={langui.wiki_short_description}
|
||||
reduced={appLayout.mainPanelReduced && isDesktop}
|
||||
onClick={() => appLayout.setMainPanelOpen(false)}
|
||||
/>
|
||||
|
||||
{/*
|
||||
|
||||
<NavOption
|
||||
url="/chronicles"
|
||||
icon="watch_later"
|
||||
title={langui.main_chronicles}
|
||||
subtitle={langui.main_chronicles_description}
|
||||
tooltipId="MainPanelTooltip"
|
||||
title={langui.chronicles}
|
||||
subtitle={langui.chronicles_short_description}
|
||||
|
||||
reduced={appLayout.mainPanelReduced && isDesktop}
|
||||
onClick={() => appLayout.setMainPanelOpen(false)}
|
||||
/>
|
||||
|
||||
*/}
|
||||
|
||||
<HorizontalLine />
|
||||
|
||||
<NavOption
|
||||
url="/news"
|
||||
icon="feed"
|
||||
title={langui.main_news}
|
||||
tooltipId="MainPanelTooltip"
|
||||
title={langui.news}
|
||||
reduced={appLayout.mainPanelReduced && isDesktop}
|
||||
onClick={() => appLayout.setMainPanelOpen(false)}
|
||||
/>
|
||||
|
||||
{/*
|
||||
<NavOption
|
||||
url="/merch"
|
||||
icon="store"
|
||||
title={langui.main_merch}
|
||||
tooltipId="MainPanelTooltip"
|
||||
title={langui.merch}
|
||||
|
||||
reduced={appLayout.mainPanelReduced && isDesktop}
|
||||
onClick={() => appLayout.setMainPanelOpen(false)}
|
||||
/>
|
||||
|
||||
*/}
|
||||
|
||||
<NavOption
|
||||
url="/gallery"
|
||||
icon="collections"
|
||||
title={langui.main_gallery}
|
||||
tooltipId="MainPanelTooltip"
|
||||
title={langui.gallery}
|
||||
reduced={appLayout.mainPanelReduced && isDesktop}
|
||||
onClick={() => appLayout.setMainPanelOpen(false)}
|
||||
/>
|
||||
|
||||
{/*
|
||||
|
||||
<NavOption
|
||||
url="/archives"
|
||||
icon="inventory"
|
||||
title={langui.main_archives}
|
||||
tooltipId="MainPanelTooltip"
|
||||
title={langui.archives}
|
||||
|
||||
reduced={appLayout.mainPanelReduced && isDesktop}
|
||||
onClick={() => appLayout.setMainPanelOpen(false)}
|
||||
/>
|
||||
|
||||
|
||||
*/}
|
||||
|
||||
<NavOption
|
||||
url="/about-us"
|
||||
icon="info"
|
||||
title={langui.main_about_us}
|
||||
tooltipId="MainPanelTooltip"
|
||||
title={langui.about_us}
|
||||
reduced={appLayout.mainPanelReduced && isDesktop}
|
||||
onClick={() => appLayout.setMainPanelOpen(false)}
|
||||
/>
|
||||
@ -176,10 +237,8 @@ export default function MainPanel(props: MainPanelProps): JSX.Element {
|
||||
}`}
|
||||
>
|
||||
<p>
|
||||
{langui.main_licensing ? (
|
||||
<Markdown>{langui.main_licensing}</Markdown>
|
||||
) : (
|
||||
""
|
||||
{langui.licensing_notice && (
|
||||
<Markdown>{langui.licensing_notice}</Markdown>
|
||||
)}
|
||||
</p>
|
||||
<a
|
||||
@ -194,10 +253,8 @@ export default function MainPanel(props: MainPanelProps): JSX.Element {
|
||||
</div>
|
||||
</a>
|
||||
<p>
|
||||
{langui.main_copyright ? (
|
||||
<Markdown>{langui.main_copyright}</Markdown>
|
||||
) : (
|
||||
""
|
||||
{langui.copyright_notice && (
|
||||
<Markdown>{langui.copyright_notice}</Markdown>
|
||||
)}
|
||||
</p>
|
||||
<div className="mt-12 mb-4 grid h-4 grid-flow-col place-content-center gap-8">
|
||||
|
@ -4,7 +4,7 @@ type SubPanelProps = {
|
||||
|
||||
export default function SubPanel(props: SubPanelProps): JSX.Element {
|
||||
return (
|
||||
<div className="flex flex-col p-8 gap-y-2 justify-items-center text-center mobile:h-full">
|
||||
<div className="grid pt-10 pb-20 px-6 desktop:py-8 desktop:px-10 gap-y-2 text-center">
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
|
54
src/components/Popup.tsx
Normal file
54
src/components/Popup.tsx
Normal file
@ -0,0 +1,54 @@
|
||||
import { Dispatch, SetStateAction } from "react";
|
||||
import Button from "./Button";
|
||||
|
||||
export type PopupProps = {
|
||||
setState:
|
||||
| Dispatch<SetStateAction<boolean | undefined>>
|
||||
| Dispatch<SetStateAction<boolean>>;
|
||||
state?: boolean;
|
||||
children: React.ReactNode;
|
||||
fillViewport?: boolean;
|
||||
hideBackground?: boolean;
|
||||
};
|
||||
|
||||
export default function Popup(props: PopupProps): JSX.Element {
|
||||
return (
|
||||
<div
|
||||
className={`fixed inset-0 z-50 grid place-content-center transition-[backdrop-filter] duration-500 ${
|
||||
props.state
|
||||
? "[backdrop-filter:blur(2px)]"
|
||||
: "pointer-events-none touch-none"
|
||||
}`}
|
||||
onKeyUp={(event) => {
|
||||
if (event.key.match("Escape")) props.setState(false);
|
||||
}}
|
||||
tabIndex={0}
|
||||
>
|
||||
<div
|
||||
className={`fixed bg-shade inset-0 transition-all duration-500 ${
|
||||
props.state ? "bg-opacity-50" : "bg-opacity-0"
|
||||
}`}
|
||||
onClick={() => {
|
||||
props.setState(false);
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
className={`p-10 grid gap-4 place-items-center transition-transform ${
|
||||
props.state ? "scale-100" : "scale-0"
|
||||
} ${props.fillViewport ? "absolute inset-10 top-20" : "relative"} ${
|
||||
props.hideBackground
|
||||
? ""
|
||||
: "bg-light rounded-lg shadow-2xl shadow-shade"
|
||||
}`}
|
||||
>
|
||||
<Button
|
||||
className="!p-1 absolute -top-16 bg-light border-light border-4"
|
||||
onClick={() => props.setState(false)}
|
||||
>
|
||||
<span className="material-icons p-1">close</span>
|
||||
</Button>
|
||||
{props.children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -1,60 +1,69 @@
|
||||
import Chip from "components/Chip";
|
||||
import { GetContentTextQuery } from "graphql/operations-types";
|
||||
import {
|
||||
GetContentTextQuery,
|
||||
GetWebsiteInterfaceQuery,
|
||||
} from "graphql/operations-types";
|
||||
import Button from "./Button";
|
||||
import Img, { ImageQuality } from "./Img";
|
||||
import ReactDOMServer from "react-dom/server";
|
||||
import ToolTip from "./ToolTip";
|
||||
|
||||
type RecorderChipProps = {
|
||||
className?: string;
|
||||
recorder: GetContentTextQuery["contents"]["data"][number]["attributes"]["text_set"][number]["transcribers"]["data"][number];
|
||||
langui: GetWebsiteInterfaceQuery["websiteInterfaces"]["data"][number]["attributes"];
|
||||
};
|
||||
|
||||
export default function RecorderChip(props: RecorderChipProps): JSX.Element {
|
||||
const recorder = props.recorder;
|
||||
const { recorder, langui } = props;
|
||||
|
||||
return (
|
||||
<Chip
|
||||
key={recorder.id}
|
||||
data-tip={ReactDOMServer.renderToStaticMarkup(
|
||||
<div className="p-2 py-5 grid gap-2">
|
||||
<div className="grid grid-flow-col gap-2 place-items-center place-content-start">
|
||||
<ToolTip
|
||||
content={
|
||||
<div className="text-left p-2 py-5 grid gap-8">
|
||||
<div className="grid grid-flow-col gap-6 place-items-center place-content-start">
|
||||
{recorder.attributes.avatar.data && (
|
||||
<Img
|
||||
className="w-8 rounded-full"
|
||||
className="w-20 rounded-full border-4 border-mid"
|
||||
image={recorder.attributes.avatar.data.attributes}
|
||||
quality={ImageQuality.Small}
|
||||
rawImg
|
||||
/>
|
||||
)}
|
||||
<h3 className="text-xl">{recorder.attributes.username}</h3>
|
||||
<div className="grid gap-2">
|
||||
<h3 className=" text-2xl">{recorder.attributes.username}</h3>
|
||||
{recorder.attributes.languages.data.length > 0 && (
|
||||
<div className="flex flex-row flex-wrap gap-1">
|
||||
<p>{langui.languages}:</p>
|
||||
{recorder.attributes.languages.data.map((language) => (
|
||||
<Chip key={language.attributes.code}>
|
||||
{language.attributes.code.toUpperCase()}
|
||||
</Chip>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{recorder.attributes.pronouns && (
|
||||
<div className="flex flex-row flex-wrap gap-1">
|
||||
<p>{langui.pronouns}:</p>
|
||||
<Chip>{recorder.attributes.pronouns}</Chip>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{recorder.attributes.languages.data.length > 0 && (
|
||||
<div className="flex flex-row flex-wrap gap-1">
|
||||
<p>Languages:</p>
|
||||
{recorder.attributes.languages.data.map((language) => (
|
||||
<Chip key={language.attributes.code}>
|
||||
{language.attributes.code.toUpperCase()}
|
||||
</Chip>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{recorder.attributes.bio.length > 0 && (
|
||||
<p>{recorder.attributes.bio[0].bio}</p>
|
||||
)}
|
||||
{recorder.attributes.pronouns && (
|
||||
<div className="flex flex-row flex-wrap gap-1">
|
||||
<p>Pronouns:</p>
|
||||
<Chip>{recorder.attributes.pronouns}</Chip>
|
||||
</div>
|
||||
)}
|
||||
<p>
|
||||
{recorder.attributes.bio.length > 0 &&
|
||||
recorder.attributes.bio[0].bio}
|
||||
</p>
|
||||
|
||||
<Button className="cursor-not-allowed">View profile</Button>
|
||||
</div>
|
||||
)}
|
||||
data-for={"RecordersTooltip"}
|
||||
data-multiline
|
||||
data-html
|
||||
}
|
||||
placement="top"
|
||||
>
|
||||
{recorder.attributes.anonymize
|
||||
? `Recorder#${recorder.attributes.anonymous_code}`
|
||||
: recorder.attributes.username}
|
||||
</Chip>
|
||||
<Chip key={recorder.id}>
|
||||
{recorder.attributes.anonymize
|
||||
? `Recorder#${recorder.attributes.anonymous_code}`
|
||||
: recorder.attributes.username}
|
||||
</Chip>
|
||||
</ToolTip>
|
||||
);
|
||||
}
|
||||
|
66
src/components/Select.tsx
Normal file
66
src/components/Select.tsx
Normal file
@ -0,0 +1,66 @@
|
||||
import { Dispatch, SetStateAction, useState } from "react";
|
||||
|
||||
export type SelectProps = {
|
||||
setState: Dispatch<SetStateAction<number>>;
|
||||
state: number;
|
||||
options: string[];
|
||||
selected?: number;
|
||||
allowEmpty?: boolean;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export default function Select(props: SelectProps): JSX.Element {
|
||||
const [opened, setOpened] = useState(false);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`relative text-center transition-[filter] ${
|
||||
opened && "drop-shadow-shade-lg z-10"
|
||||
} ${props.className}`}
|
||||
>
|
||||
<div
|
||||
className={`outline outline-mid outline-2 outline-offset-[-2px] hover:outline-[transparent] bg-light rounded-[1em] p-1 grid grid-flow-col grid-cols-[1fr_auto_auto] place-items-center cursor-pointer hover:bg-mid transition-all ${
|
||||
opened && "outline-[transparent] rounded-b-none"
|
||||
}`}
|
||||
>
|
||||
<p onClick={() => setOpened(!opened)} className="w-full">
|
||||
{props.state === -1 ? "—" : props.options[props.state]}
|
||||
</p>
|
||||
{props.state >= 0 && props.allowEmpty && (
|
||||
<span
|
||||
onClick={() => props.setState(-1)}
|
||||
className="material-icons !text-xs"
|
||||
>
|
||||
close
|
||||
</span>
|
||||
)}
|
||||
<span onClick={() => setOpened(!opened)} className="material-icons">
|
||||
{opened ? "arrow_drop_up" : "arrow_drop_down"}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
className={`left-0 right-0 rounded-b-[1em] ${
|
||||
opened ? "absolute" : "hidden"
|
||||
}`}
|
||||
>
|
||||
{props.options.map((option, index) => (
|
||||
<>
|
||||
{index !== props.state && (
|
||||
<div
|
||||
className="bg-light hover:bg-mid transition-colors cursor-pointer p-1 last-of-type:rounded-b-[1em]"
|
||||
key={index}
|
||||
id={option}
|
||||
onClick={() => {
|
||||
setOpened(false);
|
||||
props.setState(index);
|
||||
}}
|
||||
>
|
||||
{option}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
26
src/components/Switch.tsx
Normal file
26
src/components/Switch.tsx
Normal file
@ -0,0 +1,26 @@
|
||||
import { Dispatch, SetStateAction } from "react";
|
||||
|
||||
export type SwitchProps = {
|
||||
setState: Dispatch<SetStateAction<boolean>>;
|
||||
state: boolean;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export default function Switch(props: SwitchProps): JSX.Element {
|
||||
return (
|
||||
<div
|
||||
className={`h-6 w-12 rounded-full border-2 border-mid grid transition-colors relative cursor-pointer ${
|
||||
props.className
|
||||
} ${props.state ? "bg-mid" : "bg-light"}`}
|
||||
onClick={() => {
|
||||
props.setState(!props.state);
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className={`bg-dark aspect-square rounded-full absolute top-0 bottom-0 left-0 transition-transform ${
|
||||
props.state && "translate-x-[115%]"
|
||||
}`}
|
||||
></div>
|
||||
</div>
|
||||
);
|
||||
}
|
19
src/components/ToolTip.tsx
Normal file
19
src/components/ToolTip.tsx
Normal file
@ -0,0 +1,19 @@
|
||||
import Tippy, { TippyProps } from "@tippyjs/react";
|
||||
import "tippy.js/animations/scale-subtle.css";
|
||||
|
||||
interface ToolTipProps extends TippyProps {}
|
||||
|
||||
export default function ToolTip(props: ToolTipProps): JSX.Element {
|
||||
const newProps = { ...props };
|
||||
|
||||
// Set defaults
|
||||
if (newProps.delay === undefined) newProps.delay = [150, 0];
|
||||
if (newProps.interactive === undefined) newProps.interactive = true;
|
||||
if (newProps.animation === undefined) newProps.animation = "scale-subtle";
|
||||
|
||||
return (
|
||||
<Tippy className={`text-[80%] ${newProps.className}`} {...newProps}>
|
||||
<div>{props.children}</div>
|
||||
</Tippy>
|
||||
);
|
||||
}
|
@ -5,13 +5,20 @@ import React, { ReactNode, useContext } from "react";
|
||||
export interface AppLayoutState {
|
||||
subPanelOpen: boolean | undefined;
|
||||
languagePanelOpen: boolean | undefined;
|
||||
configPanelOpen: boolean | undefined;
|
||||
mainPanelReduced: boolean | undefined;
|
||||
mainPanelOpen: boolean | undefined;
|
||||
darkMode: boolean | undefined;
|
||||
selectedThemeMode: boolean | undefined;
|
||||
fontSize: number | undefined;
|
||||
dyslexic: boolean | undefined;
|
||||
currency: string | undefined;
|
||||
playerName: string | undefined;
|
||||
setSubPanelOpen: React.Dispatch<React.SetStateAction<boolean | undefined>>;
|
||||
setLanguagePanelOpen: React.Dispatch<
|
||||
React.SetStateAction<boolean | undefined>
|
||||
>;
|
||||
setConfigPanelOpen: React.Dispatch<React.SetStateAction<boolean | undefined>>;
|
||||
setMainPanelReduced: React.Dispatch<
|
||||
React.SetStateAction<boolean | undefined>
|
||||
>;
|
||||
@ -20,27 +27,44 @@ export interface AppLayoutState {
|
||||
setSelectedThemeMode: React.Dispatch<
|
||||
React.SetStateAction<boolean | undefined>
|
||||
>;
|
||||
setFontSize: React.Dispatch<React.SetStateAction<number | undefined>>;
|
||||
setDyslexic: React.Dispatch<React.SetStateAction<boolean | undefined>>;
|
||||
setCurrency: React.Dispatch<React.SetStateAction<string | undefined>>;
|
||||
setPlayerName: React.Dispatch<React.SetStateAction<string | undefined>>;
|
||||
}
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-empty-function */
|
||||
const initialState: AppLayoutState = {
|
||||
subPanelOpen: false,
|
||||
languagePanelOpen: false,
|
||||
configPanelOpen: false,
|
||||
mainPanelReduced: false,
|
||||
mainPanelOpen: false,
|
||||
darkMode: false,
|
||||
selectedThemeMode: false,
|
||||
fontSize: 1,
|
||||
dyslexic: false,
|
||||
currency: "USD",
|
||||
playerName: "",
|
||||
setSubPanelOpen: () => {},
|
||||
setLanguagePanelOpen: () => {},
|
||||
setMainPanelReduced: () => {},
|
||||
setMainPanelOpen: () => {},
|
||||
setDarkMode: () => {},
|
||||
setSelectedThemeMode: () => {},
|
||||
setConfigPanelOpen: () => {},
|
||||
setFontSize: () => {},
|
||||
setDyslexic: () => {},
|
||||
setCurrency: () => {},
|
||||
setPlayerName: () => {},
|
||||
};
|
||||
/* eslint-enable @typescript-eslint/no-empty-function */
|
||||
|
||||
const AppContext = React.createContext<AppLayoutState>(initialState);
|
||||
|
||||
export default AppContext;
|
||||
|
||||
export function useAppLayout() {
|
||||
export function useAppLayout(): AppLayoutState {
|
||||
return useContext(AppContext);
|
||||
}
|
||||
|
||||
@ -48,7 +72,7 @@ type Props = {
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
export const AppContextProvider = (props: Props) => {
|
||||
export function AppContextProvider(props: Props): JSX.Element {
|
||||
const [subPanelOpen, setSubPanelOpen] = useStateWithLocalStorage<
|
||||
boolean | undefined
|
||||
>("subPanelOpen", initialState.subPanelOpen);
|
||||
@ -57,6 +81,10 @@ export const AppContextProvider = (props: Props) => {
|
||||
boolean | undefined
|
||||
>("languagePanelOpen", initialState.languagePanelOpen);
|
||||
|
||||
const [configPanelOpen, setConfigPanelOpen] = useStateWithLocalStorage<
|
||||
boolean | undefined
|
||||
>("configPanelOpen", initialState.configPanelOpen);
|
||||
|
||||
const [mainPanelReduced, setMainPanelReduced] = useStateWithLocalStorage<
|
||||
boolean | undefined
|
||||
>("mainPanelReduced", initialState.mainPanelReduced);
|
||||
@ -65,28 +93,56 @@ export const AppContextProvider = (props: Props) => {
|
||||
boolean | undefined
|
||||
>("mainPanelOpen", initialState.mainPanelOpen);
|
||||
|
||||
const [darkMode, setDarkMode, setSelectedThemeMode] = useDarkMode(
|
||||
"darkMode",
|
||||
initialState.darkMode
|
||||
const [darkMode, selectedThemeMode, setDarkMode, setSelectedThemeMode] =
|
||||
useDarkMode("darkMode", initialState.darkMode);
|
||||
|
||||
const [fontSize, setFontSize] = useStateWithLocalStorage<number | undefined>(
|
||||
"fontSize",
|
||||
initialState.fontSize
|
||||
);
|
||||
|
||||
const [dyslexic, setDyslexic] = useStateWithLocalStorage<boolean | undefined>(
|
||||
"dyslexic",
|
||||
initialState.dyslexic
|
||||
);
|
||||
|
||||
const [currency, setCurrency] = useStateWithLocalStorage<string | undefined>(
|
||||
"currency",
|
||||
initialState.currency
|
||||
);
|
||||
|
||||
const [playerName, setPlayerName] = useStateWithLocalStorage<
|
||||
string | undefined
|
||||
>("playerName", initialState.playerName);
|
||||
|
||||
return (
|
||||
<AppContext.Provider
|
||||
value={{
|
||||
subPanelOpen,
|
||||
languagePanelOpen,
|
||||
configPanelOpen,
|
||||
mainPanelReduced,
|
||||
mainPanelOpen,
|
||||
darkMode,
|
||||
selectedThemeMode,
|
||||
fontSize,
|
||||
dyslexic,
|
||||
currency,
|
||||
playerName,
|
||||
setSubPanelOpen,
|
||||
setLanguagePanelOpen,
|
||||
setConfigPanelOpen,
|
||||
setMainPanelReduced,
|
||||
setMainPanelOpen,
|
||||
setDarkMode,
|
||||
setSelectedThemeMode,
|
||||
setFontSize,
|
||||
setDyslexic,
|
||||
setCurrency,
|
||||
setPlayerName,
|
||||
}}
|
||||
>
|
||||
{props.children}
|
||||
</AppContext.Provider>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
@ -1,72 +1,137 @@
|
||||
query getWebsiteInterface($language_code: String) {
|
||||
websiteInterfaces(filters: { language: { code: { eq: $language_code } } }) {
|
||||
websiteInterfaces(
|
||||
filters: { ui_language: { code: { eq: $language_code } } }
|
||||
) {
|
||||
data {
|
||||
attributes {
|
||||
main_library
|
||||
main_library_description
|
||||
main_news
|
||||
main_merch
|
||||
main_gallery
|
||||
main_archives
|
||||
main_about_us
|
||||
main_licensing
|
||||
main_copyright
|
||||
library
|
||||
contents
|
||||
wiki
|
||||
chronicles
|
||||
library_short_description
|
||||
contents_short_description
|
||||
wiki_short_description
|
||||
chronicles_short_description
|
||||
news
|
||||
merch
|
||||
gallery
|
||||
archives
|
||||
about_us
|
||||
licensing_notice
|
||||
copyright_notice
|
||||
contents_description
|
||||
type
|
||||
category
|
||||
categories
|
||||
size
|
||||
release_date
|
||||
release_year
|
||||
details
|
||||
price
|
||||
width
|
||||
height
|
||||
thickness
|
||||
subitem
|
||||
subitems
|
||||
subitem_of
|
||||
variant
|
||||
variants
|
||||
variant_of
|
||||
summary
|
||||
audio
|
||||
video
|
||||
textual
|
||||
game
|
||||
other
|
||||
return_to
|
||||
left_to_right
|
||||
right_to_left
|
||||
page
|
||||
pages
|
||||
page_order
|
||||
binding
|
||||
type_information
|
||||
front_matter
|
||||
back_matter
|
||||
open_content
|
||||
read_content
|
||||
watch_content
|
||||
listen_content
|
||||
view_scans
|
||||
paperback
|
||||
hardcover
|
||||
languages
|
||||
select_language
|
||||
language
|
||||
library_description
|
||||
library_item_summary
|
||||
library_item_gallery
|
||||
library_item_details
|
||||
library_item_subitems
|
||||
library_item_variants
|
||||
library_item_content
|
||||
global_return_label
|
||||
global_subitem_of
|
||||
global_type
|
||||
global_width
|
||||
global_height
|
||||
global_thickness
|
||||
global_binding
|
||||
global_language
|
||||
global_languages
|
||||
global_page
|
||||
global_pages
|
||||
global_page_order
|
||||
global_release_date
|
||||
global_price
|
||||
library_item_physical_size
|
||||
library_item_type_information
|
||||
library_item_front_matter
|
||||
library_item_back_matter
|
||||
library_item_type_textual
|
||||
library_item_type_audio
|
||||
library_item_type_game
|
||||
library_item_type_video
|
||||
library_item_type_other
|
||||
library_item_open_content
|
||||
library_item_view_scans
|
||||
content_read_content
|
||||
content_watch_content
|
||||
content_listen_content
|
||||
global_category
|
||||
global_categories
|
||||
global_paperback
|
||||
global_hardcover
|
||||
global_left_to_right
|
||||
global_right_to_left
|
||||
main_wiki
|
||||
main_wiki_description
|
||||
main_chronicles
|
||||
main_chronicles_description
|
||||
library_items
|
||||
library_items_description
|
||||
library_content
|
||||
library_content_description
|
||||
wiki_description
|
||||
news_description
|
||||
chronicles_description
|
||||
news_description
|
||||
merch_description
|
||||
gallery_description
|
||||
archives_description
|
||||
about_us_description
|
||||
merch_description
|
||||
page_not_found
|
||||
default_description
|
||||
name
|
||||
show_subitems
|
||||
show_primary_items
|
||||
show_secondary_items
|
||||
no_type
|
||||
no_year
|
||||
order_by
|
||||
group_by
|
||||
select_option_sidebar
|
||||
group
|
||||
settings
|
||||
theme
|
||||
light
|
||||
auto
|
||||
dark
|
||||
font_size
|
||||
player_name
|
||||
currency
|
||||
font
|
||||
calculated
|
||||
status_incomplete
|
||||
status_draft
|
||||
status_review
|
||||
status_done
|
||||
incomplete
|
||||
draft
|
||||
review
|
||||
done
|
||||
status
|
||||
transcribers
|
||||
translators
|
||||
proofreaders
|
||||
transcript_notice
|
||||
translation_notice
|
||||
source_language
|
||||
pronouns
|
||||
no_category
|
||||
item
|
||||
items
|
||||
content
|
||||
result
|
||||
results
|
||||
language_switch_message
|
||||
open_settings
|
||||
change_language
|
||||
open_search
|
||||
chronology
|
||||
accords_handbook
|
||||
legality
|
||||
members
|
||||
sharing_policy
|
||||
contact_us
|
||||
email
|
||||
email_gdpr_notice
|
||||
message
|
||||
send
|
||||
response_invalid_code
|
||||
response_invalid_email
|
||||
response_email_success
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -125,17 +190,15 @@ query getChronologyItems($language_code: String) {
|
||||
}
|
||||
|
||||
query getLibraryItemsPreview($language_code: String) {
|
||||
libraryItems(
|
||||
filters: { root_item: { eq: true } }
|
||||
pagination: { limit: -1 }
|
||||
sort: ["title:asc", "subtitle:asc"]
|
||||
) {
|
||||
libraryItems(pagination: { limit: -1 }) {
|
||||
data {
|
||||
id
|
||||
attributes {
|
||||
title
|
||||
subtitle
|
||||
slug
|
||||
root_item
|
||||
primary
|
||||
thumbnail {
|
||||
data {
|
||||
attributes {
|
||||
@ -160,10 +223,20 @@ query getLibraryItemsPreview($language_code: String) {
|
||||
attributes {
|
||||
symbol
|
||||
code
|
||||
rate_to_usd
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
categories {
|
||||
data {
|
||||
id
|
||||
attributes {
|
||||
name
|
||||
short
|
||||
}
|
||||
}
|
||||
}
|
||||
metadata {
|
||||
__typename
|
||||
... on ComponentMetadataBooks {
|
||||
@ -218,7 +291,7 @@ query getLibraryItemsPreview($language_code: String) {
|
||||
}
|
||||
}
|
||||
}
|
||||
... on ComponentMetadataOther {
|
||||
... on ComponentMetadataGroup {
|
||||
subtype {
|
||||
data {
|
||||
attributes {
|
||||
@ -231,6 +304,18 @@ query getLibraryItemsPreview($language_code: String) {
|
||||
}
|
||||
}
|
||||
}
|
||||
subitems_type {
|
||||
data {
|
||||
attributes {
|
||||
slug
|
||||
titles(
|
||||
filters: { language: { code: { eq: $language_code } } }
|
||||
) {
|
||||
title
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -296,10 +381,20 @@ query getLibraryItem($slug: String, $language_code: String) {
|
||||
attributes {
|
||||
symbol
|
||||
code
|
||||
rate_to_usd
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
categories {
|
||||
data {
|
||||
id
|
||||
attributes {
|
||||
name
|
||||
short
|
||||
}
|
||||
}
|
||||
}
|
||||
size {
|
||||
width
|
||||
height
|
||||
@ -396,7 +491,7 @@ query getLibraryItem($slug: String, $language_code: String) {
|
||||
}
|
||||
}
|
||||
}
|
||||
... on ComponentMetadataOther {
|
||||
... on ComponentMetadataGroup {
|
||||
subtype {
|
||||
data {
|
||||
attributes {
|
||||
@ -409,6 +504,18 @@ query getLibraryItem($slug: String, $language_code: String) {
|
||||
}
|
||||
}
|
||||
}
|
||||
subitems_type {
|
||||
data {
|
||||
attributes {
|
||||
slug
|
||||
titles(
|
||||
filters: { language: { code: { eq: $language_code } } }
|
||||
) {
|
||||
title
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
subitem_of {
|
||||
@ -452,10 +559,20 @@ query getLibraryItem($slug: String, $language_code: String) {
|
||||
attributes {
|
||||
symbol
|
||||
code
|
||||
rate_to_usd
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
categories {
|
||||
data {
|
||||
id
|
||||
attributes {
|
||||
name
|
||||
short
|
||||
}
|
||||
}
|
||||
}
|
||||
metadata {
|
||||
__typename
|
||||
... on ComponentMetadataBooks {
|
||||
@ -516,7 +633,7 @@ query getLibraryItem($slug: String, $language_code: String) {
|
||||
}
|
||||
}
|
||||
}
|
||||
... on ComponentMetadataOther {
|
||||
... on ComponentMetadataGroup {
|
||||
subtype {
|
||||
data {
|
||||
attributes {
|
||||
@ -531,6 +648,20 @@ query getLibraryItem($slug: String, $language_code: String) {
|
||||
}
|
||||
}
|
||||
}
|
||||
subitems_type {
|
||||
data {
|
||||
attributes {
|
||||
slug
|
||||
titles(
|
||||
filters: {
|
||||
language: { code: { eq: $language_code } }
|
||||
}
|
||||
) {
|
||||
title
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -585,6 +716,7 @@ query getLibraryItem($slug: String, $language_code: String) {
|
||||
data {
|
||||
id
|
||||
attributes {
|
||||
name
|
||||
short
|
||||
}
|
||||
}
|
||||
@ -655,6 +787,7 @@ query getContents($language_code: String) {
|
||||
data {
|
||||
id
|
||||
attributes {
|
||||
name
|
||||
short
|
||||
}
|
||||
}
|
||||
@ -878,6 +1011,15 @@ query getContentText($slug: String, $language_code: String) {
|
||||
}
|
||||
}
|
||||
}
|
||||
text_set_languages: text_set {
|
||||
language {
|
||||
data {
|
||||
attributes {
|
||||
code
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
text_set(filters: { language: { code: { eq: $language_code } } }) {
|
||||
status
|
||||
text
|
||||
@ -1005,3 +1147,417 @@ query getContentText($slug: String, $language_code: String) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
query getCurrencies {
|
||||
currencies {
|
||||
data {
|
||||
id
|
||||
attributes {
|
||||
code
|
||||
symbol
|
||||
rate_to_usd
|
||||
display_decimals
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
query getLanguages {
|
||||
languages {
|
||||
data {
|
||||
id
|
||||
attributes {
|
||||
name
|
||||
code
|
||||
localized_name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
query getPost($slug: String, $language_code: String) {
|
||||
posts(filters: { slug: { eq: $slug } }) {
|
||||
data {
|
||||
id
|
||||
attributes {
|
||||
slug
|
||||
updatedAt
|
||||
date {
|
||||
year
|
||||
month
|
||||
day
|
||||
}
|
||||
authors {
|
||||
data {
|
||||
id
|
||||
attributes {
|
||||
username
|
||||
anonymize
|
||||
anonymous_code
|
||||
pronouns
|
||||
bio(filters: { language: { code: { eq: $language_code } } }) {
|
||||
bio
|
||||
}
|
||||
languages {
|
||||
data {
|
||||
attributes {
|
||||
code
|
||||
}
|
||||
}
|
||||
}
|
||||
avatar {
|
||||
data {
|
||||
attributes {
|
||||
name
|
||||
alternativeText
|
||||
caption
|
||||
width
|
||||
height
|
||||
url
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
categories {
|
||||
data {
|
||||
id
|
||||
attributes {
|
||||
name
|
||||
short
|
||||
}
|
||||
}
|
||||
}
|
||||
hidden
|
||||
thumbnail {
|
||||
data {
|
||||
attributes {
|
||||
name
|
||||
alternativeText
|
||||
caption
|
||||
width
|
||||
height
|
||||
url
|
||||
}
|
||||
}
|
||||
}
|
||||
translations_languages: translations {
|
||||
language {
|
||||
data {
|
||||
attributes {
|
||||
code
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
translations(filters: { language: { code: { eq: $language_code } } }) {
|
||||
status
|
||||
title
|
||||
excerpt
|
||||
thumbnail {
|
||||
data {
|
||||
attributes {
|
||||
name
|
||||
alternativeText
|
||||
caption
|
||||
width
|
||||
height
|
||||
url
|
||||
}
|
||||
}
|
||||
}
|
||||
body
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
query getPostsSlugs {
|
||||
posts(filters: { hidden: { eq: false } }) {
|
||||
data {
|
||||
id
|
||||
attributes {
|
||||
slug
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
query getPostsPreview($language_code: String) {
|
||||
posts(filters: { hidden: { eq: false } }) {
|
||||
data {
|
||||
id
|
||||
attributes {
|
||||
slug
|
||||
date {
|
||||
year
|
||||
month
|
||||
day
|
||||
}
|
||||
categories {
|
||||
data {
|
||||
id
|
||||
attributes {
|
||||
short
|
||||
}
|
||||
}
|
||||
}
|
||||
thumbnail {
|
||||
data {
|
||||
attributes {
|
||||
name
|
||||
alternativeText
|
||||
caption
|
||||
width
|
||||
height
|
||||
url
|
||||
}
|
||||
}
|
||||
}
|
||||
translations(filters: { language: { code: { eq: $language_code } } }) {
|
||||
title
|
||||
excerpt
|
||||
thumbnail {
|
||||
data {
|
||||
attributes {
|
||||
name
|
||||
alternativeText
|
||||
caption
|
||||
width
|
||||
height
|
||||
url
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
query getContentLanguages($slug: String) {
|
||||
contents(filters: { slug: { eq: $slug } }) {
|
||||
data {
|
||||
attributes {
|
||||
text_set {
|
||||
language {
|
||||
data {
|
||||
attributes {
|
||||
code
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
video_set {
|
||||
language {
|
||||
data {
|
||||
attributes {
|
||||
code
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
audio_set {
|
||||
language {
|
||||
data {
|
||||
attributes {
|
||||
code
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
query getLibraryItemScans($slug: String, $language_code: String) {
|
||||
libraryItems(filters: { slug: { eq: $slug } }) {
|
||||
data {
|
||||
id
|
||||
attributes {
|
||||
slug
|
||||
title
|
||||
subtitle
|
||||
thumbnail {
|
||||
data {
|
||||
attributes {
|
||||
name
|
||||
alternativeText
|
||||
caption
|
||||
width
|
||||
height
|
||||
url
|
||||
}
|
||||
}
|
||||
}
|
||||
contents(pagination: { limit: -1 }) {
|
||||
data {
|
||||
id
|
||||
attributes {
|
||||
slug
|
||||
range {
|
||||
__typename
|
||||
... on ComponentRangePageRange {
|
||||
starting_page
|
||||
ending_page
|
||||
}
|
||||
... on ComponentRangeTimeRange {
|
||||
starting_time
|
||||
ending_time
|
||||
}
|
||||
}
|
||||
scan_set_languages: scan_set {
|
||||
language {
|
||||
data {
|
||||
attributes {
|
||||
code
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
scan_set(
|
||||
filters: {
|
||||
or: [
|
||||
{ language: { code: { eq: "xx" } } }
|
||||
{ language: { code: { eq: $language_code } } }
|
||||
]
|
||||
}
|
||||
) {
|
||||
status
|
||||
source_language {
|
||||
data {
|
||||
attributes {
|
||||
code
|
||||
}
|
||||
}
|
||||
}
|
||||
scanners {
|
||||
data {
|
||||
id
|
||||
attributes {
|
||||
username
|
||||
anonymize
|
||||
anonymous_code
|
||||
pronouns
|
||||
bio(
|
||||
filters: { language: { code: { eq: $language_code } } }
|
||||
) {
|
||||
bio
|
||||
}
|
||||
languages {
|
||||
data {
|
||||
attributes {
|
||||
code
|
||||
}
|
||||
}
|
||||
}
|
||||
avatar {
|
||||
data {
|
||||
attributes {
|
||||
name
|
||||
alternativeText
|
||||
caption
|
||||
width
|
||||
height
|
||||
url
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
cleaners {
|
||||
data {
|
||||
id
|
||||
attributes {
|
||||
username
|
||||
anonymize
|
||||
anonymous_code
|
||||
pronouns
|
||||
bio(
|
||||
filters: { language: { code: { eq: $language_code } } }
|
||||
) {
|
||||
bio
|
||||
}
|
||||
languages {
|
||||
data {
|
||||
attributes {
|
||||
code
|
||||
}
|
||||
}
|
||||
}
|
||||
avatar {
|
||||
data {
|
||||
attributes {
|
||||
name
|
||||
alternativeText
|
||||
caption
|
||||
width
|
||||
height
|
||||
url
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
typesetters {
|
||||
data {
|
||||
id
|
||||
attributes {
|
||||
username
|
||||
anonymize
|
||||
anonymous_code
|
||||
pronouns
|
||||
bio(
|
||||
filters: { language: { code: { eq: $language_code } } }
|
||||
) {
|
||||
bio
|
||||
}
|
||||
languages {
|
||||
data {
|
||||
attributes {
|
||||
code
|
||||
}
|
||||
}
|
||||
}
|
||||
avatar {
|
||||
data {
|
||||
attributes {
|
||||
name
|
||||
alternativeText
|
||||
caption
|
||||
width
|
||||
height
|
||||
url
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
notes
|
||||
pages(pagination: { limit: -1 }) {
|
||||
data {
|
||||
id
|
||||
attributes {
|
||||
name
|
||||
alternativeText
|
||||
caption
|
||||
width
|
||||
height
|
||||
url
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,4 @@
|
||||
import { readFileSync } from "fs";
|
||||
|
||||
import {
|
||||
GetChronologyItemsQuery,
|
||||
GetChronologyItemsQueryVariables,
|
||||
@ -11,19 +10,31 @@ import {
|
||||
GetContentsSlugsQueryVariables,
|
||||
GetContentTextQuery,
|
||||
GetContentTextQueryVariables,
|
||||
GetCurrenciesQuery,
|
||||
GetCurrenciesQueryVariables,
|
||||
GetErasQuery,
|
||||
GetErasQueryVariables,
|
||||
GetLanguagesQuery,
|
||||
GetLanguagesQueryVariables,
|
||||
GetLibraryItemQuery,
|
||||
GetLibraryItemQueryVariables,
|
||||
GetLibraryItemScansQuery,
|
||||
GetLibraryItemScansQueryVariables,
|
||||
GetLibraryItemsPreviewQuery,
|
||||
GetLibraryItemsPreviewQueryVariables,
|
||||
GetLibraryItemsSlugsQuery,
|
||||
GetLibraryItemsSlugsQueryVariables,
|
||||
GetPostQuery,
|
||||
GetPostQueryVariables,
|
||||
GetPostsPreviewQuery,
|
||||
GetPostsPreviewQueryVariables,
|
||||
GetPostsSlugsQuery,
|
||||
GetPostsSlugsQueryVariables,
|
||||
GetWebsiteInterfaceQuery,
|
||||
GetWebsiteInterfaceQueryVariables,
|
||||
} from "graphql/operations-types";
|
||||
|
||||
const graphQL = async (query: string, variables?: string) => {
|
||||
async function graphQL(query: string, variables?: string) {
|
||||
const res = await fetch(`${process.env.URL_GRAPHQL}`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
@ -32,11 +43,11 @@ const graphQL = async (query: string, variables?: string) => {
|
||||
}),
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
Authorization: "Bearer " + process.env.ACCESS_TOKEN,
|
||||
Authorization: `Bearer ${process.env.ACCESS_TOKEN}`,
|
||||
},
|
||||
});
|
||||
return (await res.json()).data;
|
||||
};
|
||||
}
|
||||
|
||||
function getQueryFromOperations(queryName: string): string {
|
||||
const operations = readFileSync("./src/graphql/operation.graphql", "utf8");
|
||||
@ -103,7 +114,6 @@ export async function getContentsSlugs(
|
||||
return await graphQL(query, JSON.stringify(variables));
|
||||
}
|
||||
|
||||
|
||||
export async function getContents(
|
||||
variables: GetContentsQueryVariables
|
||||
): Promise<GetContentsQuery> {
|
||||
@ -124,3 +134,45 @@ export async function getContentText(
|
||||
const query = getQueryFromOperations("getContentText");
|
||||
return await graphQL(query, JSON.stringify(variables));
|
||||
}
|
||||
|
||||
export async function getCurrencies(
|
||||
variables: GetCurrenciesQueryVariables
|
||||
): Promise<GetCurrenciesQuery> {
|
||||
const query = getQueryFromOperations("getCurrencies");
|
||||
return await graphQL(query, JSON.stringify(variables));
|
||||
}
|
||||
|
||||
export async function getLanguages(
|
||||
variables: GetLanguagesQueryVariables
|
||||
): Promise<GetLanguagesQuery> {
|
||||
const query = getQueryFromOperations("getLanguages");
|
||||
return await graphQL(query, JSON.stringify(variables));
|
||||
}
|
||||
|
||||
export async function getPost(
|
||||
variables: GetPostQueryVariables
|
||||
): Promise<GetPostQuery> {
|
||||
const query = getQueryFromOperations("getPost");
|
||||
return await graphQL(query, JSON.stringify(variables));
|
||||
}
|
||||
|
||||
export async function getPostsSlugs(
|
||||
variables: GetPostsSlugsQueryVariables
|
||||
): Promise<GetPostsSlugsQuery> {
|
||||
const query = getQueryFromOperations("getPostsSlugs");
|
||||
return await graphQL(query, JSON.stringify(variables));
|
||||
}
|
||||
|
||||
export async function getPostsPreview(
|
||||
variables: GetPostsPreviewQueryVariables
|
||||
): Promise<GetPostsPreviewQuery> {
|
||||
const query = getQueryFromOperations("getPostsPreview");
|
||||
return await graphQL(query, JSON.stringify(variables));
|
||||
}
|
||||
|
||||
export async function getLibraryItemScans(
|
||||
variables: GetLibraryItemScansQueryVariables
|
||||
): Promise<GetLibraryItemScansQuery> {
|
||||
const query = getQueryFromOperations("getLibraryItemScans");
|
||||
return await graphQL(query, JSON.stringify(variables));
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -6,6 +6,7 @@ export default function useDarkMode(
|
||||
key: string,
|
||||
initialValue: boolean | undefined
|
||||
): [
|
||||
boolean | undefined,
|
||||
boolean | undefined,
|
||||
React.Dispatch<React.SetStateAction<boolean | undefined>>,
|
||||
React.Dispatch<React.SetStateAction<boolean | undefined>>
|
||||
@ -23,5 +24,5 @@ export default function useDarkMode(
|
||||
if (selectedThemeMode === false) setDarkMode(prefersDarkMode);
|
||||
}, [selectedThemeMode, prefersDarkMode, setDarkMode]);
|
||||
|
||||
return [darkMode, setDarkMode, setSelectedThemeMode];
|
||||
return [darkMode, selectedThemeMode, setDarkMode, setSelectedThemeMode];
|
||||
}
|
||||
|
@ -1,13 +1,13 @@
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export default function useMediaQuery(query: string): boolean {
|
||||
const getMatches = (query: string): boolean => {
|
||||
function getMatches(query: string): boolean {
|
||||
// Prevents SSR issues
|
||||
if (typeof window !== "undefined") {
|
||||
return window.matchMedia(query).matches;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
const [matches, setMatches] = useState<boolean>(getMatches(query));
|
||||
|
||||
|
@ -1,37 +1,36 @@
|
||||
import Link from "next/link";
|
||||
import ContentPanel from "components/Panels/ContentPanel";
|
||||
import { getWebsiteInterface } from "graphql/operations";
|
||||
import { GetStaticProps } from "next";
|
||||
import { GetWebsiteInterfaceQuery } from "graphql/operations-types";
|
||||
import AppLayout from "components/AppLayout";
|
||||
import ReturnButton, {
|
||||
ReturnButtonType,
|
||||
} from "components/PanelComponents/ReturnButton";
|
||||
import ContentPanel from "components/Panels/ContentPanel";
|
||||
import { GetStaticPropsContext } from "next";
|
||||
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
||||
|
||||
type FourOhFourProps = {
|
||||
langui: GetWebsiteInterfaceQuery;
|
||||
};
|
||||
interface FourOhFourProps extends AppStaticProps {}
|
||||
|
||||
export default function FourOhFour(props: FourOhFourProps): JSX.Element {
|
||||
const langui = props.langui.websiteInterfaces.data[0].attributes;
|
||||
const { langui } = props;
|
||||
const contentPanel = (
|
||||
<ContentPanel>
|
||||
<h1>404 - Page Not Found</h1>
|
||||
<Link href="/">
|
||||
<a>Go back home</a>
|
||||
</Link>
|
||||
<h1>404 - {langui.page_not_found}</h1>
|
||||
<ReturnButton
|
||||
href="/"
|
||||
title="Home"
|
||||
langui={langui}
|
||||
displayOn={ReturnButtonType.both}
|
||||
/>
|
||||
</ContentPanel>
|
||||
);
|
||||
return <AppLayout navTitle="404" langui={langui} contentPanel={contentPanel} />;
|
||||
return <AppLayout navTitle="404" contentPanel={contentPanel} {...props} />;
|
||||
}
|
||||
|
||||
export const getStaticProps: GetStaticProps = async (context) => {
|
||||
if (context.locale) {
|
||||
const props: FourOhFourProps = {
|
||||
langui: await getWebsiteInterface({
|
||||
language_code: context.locale,
|
||||
}),
|
||||
};
|
||||
return {
|
||||
props: props,
|
||||
};
|
||||
}
|
||||
return { props: {} };
|
||||
};
|
||||
export async function getStaticProps(
|
||||
context: GetStaticPropsContext
|
||||
): Promise<{ props: FourOhFourProps }> {
|
||||
const props: FourOhFourProps = {
|
||||
...(await getAppStaticProps(context)),
|
||||
};
|
||||
return {
|
||||
props: props,
|
||||
};
|
||||
}
|
||||
|
@ -1,15 +1,17 @@
|
||||
import "@fontsource/material-icons";
|
||||
import "@fontsource/opendyslexic/400.css";
|
||||
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 "tailwind.css";
|
||||
import "@fontsource/zen-maru-gothic/500.css";
|
||||
import "@fontsource/vollkorn/700.css";
|
||||
import "@fontsource/material-icons";
|
||||
|
||||
import { AppContextProvider } from "contexts/AppLayoutContext";
|
||||
|
||||
export default function AccordsLibraryApp(appProps: AppProps) {
|
||||
export default function AccordsLibraryApp(props: AppProps): JSX.Element {
|
||||
return (
|
||||
<AppContextProvider>
|
||||
<appProps.Component {...appProps.pageProps} />
|
||||
<props.Component {...props.pageProps} />
|
||||
</AppContextProvider>
|
||||
);
|
||||
}
|
||||
|
@ -1,18 +1,19 @@
|
||||
import Document, {
|
||||
Html,
|
||||
DocumentContext,
|
||||
Head,
|
||||
Html,
|
||||
Main,
|
||||
NextScript,
|
||||
DocumentContext,
|
||||
} from "next/document";
|
||||
|
||||
class MyDocument extends Document {
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
static async getInitialProps(ctx: DocumentContext) {
|
||||
const initialProps = await Document.getInitialProps(ctx);
|
||||
return { ...initialProps };
|
||||
}
|
||||
|
||||
render() {
|
||||
render(): JSX.Element {
|
||||
return (
|
||||
<Html>
|
||||
<Head>
|
||||
|
97
src/pages/about-us/accords-handbook.tsx
Normal file
97
src/pages/about-us/accords-handbook.tsx
Normal file
@ -0,0 +1,97 @@
|
||||
import AppLayout from "components/AppLayout";
|
||||
import LanguageSwitcher from "components/LanguageSwitcher";
|
||||
import Markdawn from "components/Markdown/Markdawn";
|
||||
import TOC from "components/Markdown/TOC";
|
||||
import ReturnButton, {
|
||||
ReturnButtonType,
|
||||
} from "components/PanelComponents/ReturnButton";
|
||||
import ContentPanel from "components/Panels/ContentPanel";
|
||||
import SubPanel from "components/Panels/SubPanel";
|
||||
import { getPost } from "graphql/operations";
|
||||
import { GetPostQuery } from "graphql/operations-types";
|
||||
import { GetStaticPropsContext } from "next";
|
||||
import { useRouter } from "next/router";
|
||||
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
||||
import { getLocalesFromLanguages, prettySlug } from "queries/helpers";
|
||||
|
||||
interface AccordsHandbookProps extends AppStaticProps {
|
||||
post: GetPostQuery["posts"]["data"][number]["attributes"];
|
||||
}
|
||||
|
||||
export default function AccordsHandbook(
|
||||
props: AccordsHandbookProps
|
||||
): JSX.Element {
|
||||
const { langui, post } = props;
|
||||
const router = useRouter();
|
||||
const locales = getLocalesFromLanguages(post.translations_languages);
|
||||
|
||||
const subPanel = (
|
||||
<SubPanel>
|
||||
<ReturnButton
|
||||
href="/about-us"
|
||||
displayOn={ReturnButtonType.desktop}
|
||||
langui={langui}
|
||||
title={langui.about_us}
|
||||
horizontalLine
|
||||
/>
|
||||
{post.translations.length > 0 && post.translations[0].body && (
|
||||
<TOC
|
||||
text={post.translations[0].body}
|
||||
title={post.translations[0].title}
|
||||
/>
|
||||
)}
|
||||
</SubPanel>
|
||||
);
|
||||
|
||||
const contentPanel = (
|
||||
<ContentPanel>
|
||||
<ReturnButton
|
||||
href="/about-us"
|
||||
displayOn={ReturnButtonType.mobile}
|
||||
langui={langui}
|
||||
title={langui.about_us}
|
||||
className="mb-10"
|
||||
/>
|
||||
{locales.includes(router.locale ?? "en") ? (
|
||||
<Markdawn text={post.translations[0].body} />
|
||||
) : (
|
||||
<LanguageSwitcher
|
||||
locales={locales}
|
||||
languages={props.languages}
|
||||
langui={props.langui}
|
||||
/>
|
||||
)}
|
||||
</ContentPanel>
|
||||
);
|
||||
|
||||
return (
|
||||
<AppLayout
|
||||
navTitle={
|
||||
post.translations.length > 0
|
||||
? post.translations[0].title
|
||||
: prettySlug(post.slug)
|
||||
}
|
||||
subPanel={subPanel}
|
||||
contentPanel={contentPanel}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export async function getStaticProps(
|
||||
context: GetStaticPropsContext
|
||||
): Promise<{ props: AccordsHandbookProps }> {
|
||||
const slug = "accords-handbook";
|
||||
const props: AccordsHandbookProps = {
|
||||
...(await getAppStaticProps(context)),
|
||||
post: (
|
||||
await getPost({
|
||||
slug: slug,
|
||||
language_code: context.locale ?? "en",
|
||||
})
|
||||
).posts.data[0].attributes,
|
||||
};
|
||||
return {
|
||||
props: props,
|
||||
};
|
||||
}
|
241
src/pages/about-us/contact.tsx
Normal file
241
src/pages/about-us/contact.tsx
Normal file
@ -0,0 +1,241 @@
|
||||
import AppLayout from "components/AppLayout";
|
||||
import InsetBox from "components/InsetBox";
|
||||
import LanguageSwitcher from "components/LanguageSwitcher";
|
||||
import Markdawn from "components/Markdown/Markdawn";
|
||||
import TOC from "components/Markdown/TOC";
|
||||
import ReturnButton, {
|
||||
ReturnButtonType,
|
||||
} from "components/PanelComponents/ReturnButton";
|
||||
import ContentPanel from "components/Panels/ContentPanel";
|
||||
import SubPanel from "components/Panels/SubPanel";
|
||||
import { getPost } from "graphql/operations";
|
||||
import { GetPostQuery } from "graphql/operations-types";
|
||||
import { GetStaticPropsContext } from "next";
|
||||
import { useRouter } from "next/router";
|
||||
import { RequestMailProps, ResponseMailProps } from "pages/api/mail";
|
||||
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
||||
import { getLocalesFromLanguages, randomInt } from "queries/helpers";
|
||||
import { useState } from "react";
|
||||
|
||||
interface ContactProps extends AppStaticProps {
|
||||
post: GetPostQuery["posts"]["data"][number]["attributes"];
|
||||
}
|
||||
|
||||
export default function AboutUs(props: ContactProps): JSX.Element {
|
||||
const { langui, post } = props;
|
||||
const router = useRouter();
|
||||
const [formResponse, setFormResponse] = useState("");
|
||||
const [formState, setFormState] = useState<"completed" | "ongoing" | "stale">(
|
||||
"stale"
|
||||
);
|
||||
const locales = getLocalesFromLanguages(post.translations_languages);
|
||||
|
||||
const [randomNumber1, setRandomNumber1] = useState(randomInt(0, 10));
|
||||
const [randomNumber2, setRandomNumber2] = useState(randomInt(0, 10));
|
||||
|
||||
const subPanel = (
|
||||
<SubPanel>
|
||||
<ReturnButton
|
||||
href="/about-us"
|
||||
displayOn={ReturnButtonType.desktop}
|
||||
langui={langui}
|
||||
title={langui.about_us}
|
||||
horizontalLine
|
||||
/>
|
||||
{post.translations.length > 0 && post.translations[0].body && (
|
||||
<TOC
|
||||
text={post.translations[0].body}
|
||||
title={post.translations[0].title}
|
||||
/>
|
||||
)}
|
||||
</SubPanel>
|
||||
);
|
||||
|
||||
const contentPanel = (
|
||||
<ContentPanel>
|
||||
<ReturnButton
|
||||
href="/about-us"
|
||||
displayOn={ReturnButtonType.mobile}
|
||||
langui={langui}
|
||||
title={langui.about_us}
|
||||
className="mb-10"
|
||||
/>
|
||||
{locales.includes(router.locale ?? "en") ? (
|
||||
<Markdawn text={post.translations[0].body} />
|
||||
) : (
|
||||
<LanguageSwitcher
|
||||
locales={locales}
|
||||
languages={props.languages}
|
||||
langui={props.langui}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className="flex flex-col gap-8 text-center">
|
||||
<form
|
||||
className={`gap-8 grid ${
|
||||
formState !== "stale" &&
|
||||
"opacity-60 cursor-not-allowed touch-none pointer-events-none"
|
||||
}`}
|
||||
onSubmit={(event) => {
|
||||
event.preventDefault();
|
||||
|
||||
const fields = event.target as unknown as {
|
||||
verif: HTMLInputElement;
|
||||
name: HTMLInputElement;
|
||||
email: HTMLInputElement;
|
||||
message: HTMLInputElement;
|
||||
};
|
||||
|
||||
setFormState("ongoing");
|
||||
|
||||
if (
|
||||
parseInt(fields.verif.value, 10) ===
|
||||
randomNumber1 + randomNumber2 &&
|
||||
formState !== "completed"
|
||||
) {
|
||||
const content: RequestMailProps = {
|
||||
name: fields.name.value,
|
||||
email: fields.email.value,
|
||||
message: fields.message.value,
|
||||
formName: "Contact Form",
|
||||
};
|
||||
fetch("/api/mail", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(content),
|
||||
headers: {
|
||||
"Content-type": "application/json; charset=UTF-8",
|
||||
},
|
||||
})
|
||||
.then(async (responseJson) => responseJson.json())
|
||||
.then((response: ResponseMailProps) => {
|
||||
switch (response.code) {
|
||||
case "OKAY":
|
||||
setFormResponse(langui.response_email_success);
|
||||
setFormState("completed");
|
||||
|
||||
break;
|
||||
|
||||
case "EENVELOPE":
|
||||
setFormResponse(langui.response_invalid_email);
|
||||
setFormState("stale");
|
||||
break;
|
||||
|
||||
default:
|
||||
setFormResponse(response.message ?? "");
|
||||
setFormState("stale");
|
||||
break;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
setFormResponse(langui.response_invalid_code);
|
||||
setFormState("stale");
|
||||
setRandomNumber1(randomInt(0, 10));
|
||||
setRandomNumber2(randomInt(0, 10));
|
||||
}
|
||||
|
||||
router.replace("#send-response");
|
||||
fields.verif.value = "";
|
||||
}}
|
||||
>
|
||||
<div className="flex flex-col place-items-center gap-1">
|
||||
<label htmlFor="name">{langui.name}:</label>
|
||||
<input
|
||||
type="text"
|
||||
className="mobile:w-full"
|
||||
name="name"
|
||||
id="name"
|
||||
required
|
||||
disabled={formState !== "stale"}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col place-items-center gap-1">
|
||||
<label htmlFor="email">{langui.email}:</label>
|
||||
<input
|
||||
type="email"
|
||||
className="mobile:w-full"
|
||||
name="email"
|
||||
id="email"
|
||||
required
|
||||
disabled={formState !== "stale"}
|
||||
/>
|
||||
<p className="text-sm text-dark italic opacity-70">
|
||||
{langui.email_gdpr_notice}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col place-items-center gap-1 w-full">
|
||||
<label htmlFor="message">{langui.message}:</label>
|
||||
<textarea
|
||||
name="message"
|
||||
id="message"
|
||||
className="w-full"
|
||||
rows={8}
|
||||
required
|
||||
disabled={formState !== "stale"}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 place-items-center">
|
||||
<div className="flex flex-row place-items-center gap-2">
|
||||
<label
|
||||
className="flex-shrink-0"
|
||||
htmlFor="verif"
|
||||
>{`${randomNumber1} + ${randomNumber2} =`}</label>
|
||||
<input
|
||||
className="w-24"
|
||||
type="number"
|
||||
name="verif"
|
||||
id="verif"
|
||||
required
|
||||
disabled={formState !== "stale"}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<input
|
||||
type="submit"
|
||||
value={langui.send}
|
||||
className="w-min !px-6"
|
||||
disabled={formState !== "stale"}
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div id="send-response">
|
||||
{formResponse && (
|
||||
<InsetBox>
|
||||
<p>{formResponse}</p>
|
||||
</InsetBox>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</ContentPanel>
|
||||
);
|
||||
|
||||
return (
|
||||
<AppLayout
|
||||
navTitle={"Contact"}
|
||||
subPanel={subPanel}
|
||||
contentPanel={contentPanel}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export async function getStaticProps(
|
||||
context: GetStaticPropsContext
|
||||
): Promise<{ props: ContactProps }> {
|
||||
const slug = "contact";
|
||||
const props: ContactProps = {
|
||||
...(await getAppStaticProps(context)),
|
||||
post: (
|
||||
await getPost({
|
||||
slug: slug,
|
||||
language_code: context.locale ?? "en",
|
||||
})
|
||||
).posts.data[0].attributes,
|
||||
};
|
||||
return {
|
||||
props: props,
|
||||
};
|
||||
}
|
@ -1,44 +1,48 @@
|
||||
import SubPanel from "components/Panels/SubPanel";
|
||||
import PanelHeader from "components/PanelComponents/PanelHeader";
|
||||
import { GetWebsiteInterfaceQuery } from "graphql/operations-types";
|
||||
import { GetStaticProps } from "next";
|
||||
import { getWebsiteInterface } from "graphql/operations";
|
||||
import AppLayout from "components/AppLayout";
|
||||
import NavOption from "components/PanelComponents/NavOption";
|
||||
import PanelHeader from "components/PanelComponents/PanelHeader";
|
||||
import SubPanel from "components/Panels/SubPanel";
|
||||
import { GetStaticPropsContext } from "next";
|
||||
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
||||
|
||||
type AboutUsProps = {
|
||||
langui: GetWebsiteInterfaceQuery;
|
||||
};
|
||||
interface AboutUsProps extends AppStaticProps {}
|
||||
|
||||
export default function AboutUs(props: AboutUsProps): JSX.Element {
|
||||
const langui = props.langui.websiteInterfaces.data[0].attributes;
|
||||
const { langui } = props;
|
||||
const subPanel = (
|
||||
<SubPanel>
|
||||
<PanelHeader
|
||||
icon="info"
|
||||
title={langui.main_about_us}
|
||||
title={langui.about_us}
|
||||
description={langui.about_us_description}
|
||||
/>
|
||||
<NavOption
|
||||
title={langui.accords_handbook}
|
||||
url="/about-us/accords-handbook"
|
||||
border
|
||||
/>
|
||||
<NavOption title={langui.legality} url="/about-us/legality" border />
|
||||
{/* <NavOption title={langui.members} url="/about-us/members" border /> */}
|
||||
<NavOption
|
||||
title={langui.sharing_policy}
|
||||
url="/about-us/sharing-policy"
|
||||
border
|
||||
/>
|
||||
<NavOption title={langui.contact_us} url="/about-us/contact" border />
|
||||
</SubPanel>
|
||||
);
|
||||
return (
|
||||
<AppLayout
|
||||
navTitle={langui.main_about_us}
|
||||
langui={langui}
|
||||
subPanel={subPanel}
|
||||
/>
|
||||
<AppLayout navTitle={langui.about_us} subPanel={subPanel} {...props} />
|
||||
);
|
||||
}
|
||||
|
||||
export const getStaticProps: GetStaticProps = async (context) => {
|
||||
if (context.locale) {
|
||||
const props: AboutUsProps = {
|
||||
langui: await getWebsiteInterface({
|
||||
language_code: context.locale,
|
||||
}),
|
||||
};
|
||||
return {
|
||||
props: props,
|
||||
};
|
||||
}
|
||||
return { props: {} };
|
||||
};
|
||||
export async function getStaticProps(
|
||||
context: GetStaticPropsContext
|
||||
): Promise<{ props: AboutUsProps }> {
|
||||
const props: AboutUsProps = {
|
||||
...(await getAppStaticProps(context)),
|
||||
};
|
||||
return {
|
||||
props: props,
|
||||
};
|
||||
}
|
||||
|
95
src/pages/about-us/legality.tsx
Normal file
95
src/pages/about-us/legality.tsx
Normal file
@ -0,0 +1,95 @@
|
||||
import AppLayout from "components/AppLayout";
|
||||
import LanguageSwitcher from "components/LanguageSwitcher";
|
||||
import Markdawn from "components/Markdown/Markdawn";
|
||||
import TOC from "components/Markdown/TOC";
|
||||
import ReturnButton, {
|
||||
ReturnButtonType,
|
||||
} from "components/PanelComponents/ReturnButton";
|
||||
import ContentPanel from "components/Panels/ContentPanel";
|
||||
import SubPanel from "components/Panels/SubPanel";
|
||||
import { getPost } from "graphql/operations";
|
||||
import { GetPostQuery } from "graphql/operations-types";
|
||||
import { GetStaticPropsContext } from "next";
|
||||
import { useRouter } from "next/router";
|
||||
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
||||
import { getLocalesFromLanguages, prettySlug } from "queries/helpers";
|
||||
|
||||
interface SiteInfoProps extends AppStaticProps {
|
||||
post: GetPostQuery["posts"]["data"][number]["attributes"];
|
||||
}
|
||||
|
||||
export default function SiteInformation(props: SiteInfoProps): JSX.Element {
|
||||
const { langui, post } = props;
|
||||
const router = useRouter();
|
||||
const locales = getLocalesFromLanguages(post.translations_languages);
|
||||
|
||||
const subPanel = (
|
||||
<SubPanel>
|
||||
<ReturnButton
|
||||
href="/about-us"
|
||||
displayOn={ReturnButtonType.desktop}
|
||||
langui={langui}
|
||||
title={langui.about_us}
|
||||
horizontalLine
|
||||
/>
|
||||
{post.translations.length > 0 && post.translations[0].body && (
|
||||
<TOC
|
||||
text={post.translations[0].body}
|
||||
title={post.translations[0].title}
|
||||
/>
|
||||
)}
|
||||
</SubPanel>
|
||||
);
|
||||
|
||||
const contentPanel = (
|
||||
<ContentPanel>
|
||||
<ReturnButton
|
||||
href="/about-us"
|
||||
displayOn={ReturnButtonType.mobile}
|
||||
langui={langui}
|
||||
title={langui.about_us}
|
||||
className="mb-10"
|
||||
/>
|
||||
{locales.includes(router.locale ?? "en") ? (
|
||||
<Markdawn text={post.translations[0].body} />
|
||||
) : (
|
||||
<LanguageSwitcher
|
||||
locales={locales}
|
||||
languages={props.languages}
|
||||
langui={props.langui}
|
||||
/>
|
||||
)}
|
||||
</ContentPanel>
|
||||
);
|
||||
|
||||
return (
|
||||
<AppLayout
|
||||
navTitle={
|
||||
post.translations.length > 0
|
||||
? post.translations[0].title
|
||||
: prettySlug(post.slug)
|
||||
}
|
||||
subPanel={subPanel}
|
||||
contentPanel={contentPanel}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export async function getStaticProps(
|
||||
context: GetStaticPropsContext
|
||||
): Promise<{ props: SiteInfoProps }> {
|
||||
const slug = "legality";
|
||||
const props: SiteInfoProps = {
|
||||
...(await getAppStaticProps(context)),
|
||||
post: (
|
||||
await getPost({
|
||||
slug: slug,
|
||||
language_code: context.locale ?? "en",
|
||||
})
|
||||
).posts.data[0].attributes,
|
||||
};
|
||||
return {
|
||||
props: props,
|
||||
};
|
||||
}
|
95
src/pages/about-us/sharing-policy.tsx
Normal file
95
src/pages/about-us/sharing-policy.tsx
Normal file
@ -0,0 +1,95 @@
|
||||
import AppLayout from "components/AppLayout";
|
||||
import LanguageSwitcher from "components/LanguageSwitcher";
|
||||
import Markdawn from "components/Markdown/Markdawn";
|
||||
import TOC from "components/Markdown/TOC";
|
||||
import ReturnButton, {
|
||||
ReturnButtonType,
|
||||
} from "components/PanelComponents/ReturnButton";
|
||||
import ContentPanel from "components/Panels/ContentPanel";
|
||||
import SubPanel from "components/Panels/SubPanel";
|
||||
import { getPost } from "graphql/operations";
|
||||
import { GetPostQuery } from "graphql/operations-types";
|
||||
import { GetStaticPropsContext } from "next";
|
||||
import { useRouter } from "next/router";
|
||||
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
||||
import { getLocalesFromLanguages, prettySlug } from "queries/helpers";
|
||||
|
||||
interface SharingPolicyProps extends AppStaticProps {
|
||||
post: GetPostQuery["posts"]["data"][number]["attributes"];
|
||||
}
|
||||
|
||||
export default function SharingPolicy(props: SharingPolicyProps): JSX.Element {
|
||||
const { langui, post } = props;
|
||||
const locales = getLocalesFromLanguages(post.translations_languages);
|
||||
const router = useRouter();
|
||||
|
||||
const subPanel = (
|
||||
<SubPanel>
|
||||
<ReturnButton
|
||||
href="/about-us"
|
||||
displayOn={ReturnButtonType.desktop}
|
||||
langui={langui}
|
||||
title={langui.about_us}
|
||||
horizontalLine
|
||||
/>
|
||||
{post.translations.length > 0 && post.translations[0].body && (
|
||||
<TOC
|
||||
text={post.translations[0].body}
|
||||
title={post.translations[0].title}
|
||||
/>
|
||||
)}
|
||||
</SubPanel>
|
||||
);
|
||||
|
||||
const contentPanel = (
|
||||
<ContentPanel>
|
||||
<ReturnButton
|
||||
href="/about-us"
|
||||
displayOn={ReturnButtonType.mobile}
|
||||
langui={langui}
|
||||
title={langui.about_us}
|
||||
className="mb-10"
|
||||
/>
|
||||
{locales.includes(router.locale ?? "en") ? (
|
||||
<Markdawn text={post.translations[0].body} />
|
||||
) : (
|
||||
<LanguageSwitcher
|
||||
locales={locales}
|
||||
languages={props.languages}
|
||||
langui={props.langui}
|
||||
/>
|
||||
)}
|
||||
</ContentPanel>
|
||||
);
|
||||
|
||||
return (
|
||||
<AppLayout
|
||||
navTitle={
|
||||
post.translations.length > 0
|
||||
? post.translations[0].title
|
||||
: prettySlug(post.slug)
|
||||
}
|
||||
subPanel={subPanel}
|
||||
contentPanel={contentPanel}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export async function getStaticProps(
|
||||
context: GetStaticPropsContext
|
||||
): Promise<{ props: SharingPolicyProps }> {
|
||||
const slug = "sharing-policy";
|
||||
const props: SharingPolicyProps = {
|
||||
...(await getAppStaticProps(context)),
|
||||
post: (
|
||||
await getPost({
|
||||
slug: slug,
|
||||
language_code: context.locale ?? "en",
|
||||
})
|
||||
).posts.data[0].attributes,
|
||||
};
|
||||
return {
|
||||
props: props,
|
||||
};
|
||||
}
|
51
src/pages/api/mail.ts
Normal file
51
src/pages/api/mail.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import nodemailer from "nodemailer";
|
||||
import { SMTPError } from "nodemailer/lib/smtp-connection";
|
||||
|
||||
export type ResponseMailProps = {
|
||||
code?: string;
|
||||
message?: string;
|
||||
};
|
||||
|
||||
export type RequestMailProps = {
|
||||
name: string;
|
||||
email: string;
|
||||
message: string;
|
||||
formName: string;
|
||||
};
|
||||
|
||||
export default async function Mail(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse<ResponseMailProps>
|
||||
) {
|
||||
if (req.method === "POST") {
|
||||
const body = req.body as RequestMailProps;
|
||||
|
||||
const transporter = nodemailer.createTransport({
|
||||
host: process.env.SMTP_HOST,
|
||||
port: 587,
|
||||
secure: false,
|
||||
auth: {
|
||||
user: process.env.SMTP_USER,
|
||||
pass: process.env.SMTP_PASSWORD,
|
||||
},
|
||||
});
|
||||
|
||||
// send mail with defined transport object
|
||||
await transporter
|
||||
.sendMail({
|
||||
from: `"${body.name}" <${body.email}>`,
|
||||
to: "contact@accords-library.com",
|
||||
subject: `New ${body.formName} from ${body.name}`,
|
||||
text: body.message,
|
||||
})
|
||||
.catch((reason: SMTPError) => {
|
||||
res.status(reason.responseCode ?? 500).json({
|
||||
code: reason.code,
|
||||
message: reason.response,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
res.status(200).json({ code: "OKAY" });
|
||||
}
|
@ -1,44 +1,34 @@
|
||||
import SubPanel from "components/Panels/SubPanel";
|
||||
import PanelHeader from "components/PanelComponents/PanelHeader";
|
||||
import { GetWebsiteInterfaceQuery } from "graphql/operations-types";
|
||||
import { GetStaticProps } from "next";
|
||||
import { getWebsiteInterface } from "graphql/operations";
|
||||
import AppLayout from "components/AppLayout";
|
||||
import PanelHeader from "components/PanelComponents/PanelHeader";
|
||||
import SubPanel from "components/Panels/SubPanel";
|
||||
import { GetStaticPropsContext } from "next";
|
||||
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
||||
|
||||
type ArchivesProps = {
|
||||
langui: GetWebsiteInterfaceQuery;
|
||||
};
|
||||
interface ArchivesProps extends AppStaticProps {}
|
||||
|
||||
export default function Archives(props: ArchivesProps): JSX.Element {
|
||||
const langui = props.langui.websiteInterfaces.data[0].attributes;
|
||||
const { langui } = props;
|
||||
const subPanel = (
|
||||
<SubPanel>
|
||||
<PanelHeader
|
||||
icon="inventory"
|
||||
title={langui.main_archives}
|
||||
title={langui.archives}
|
||||
description={langui.archives_description}
|
||||
/>
|
||||
</SubPanel>
|
||||
);
|
||||
return (
|
||||
<AppLayout
|
||||
navTitle={langui.main_archives}
|
||||
langui={langui}
|
||||
subPanel={subPanel}
|
||||
/>
|
||||
<AppLayout navTitle={langui.archives} subPanel={subPanel} {...props} />
|
||||
);
|
||||
}
|
||||
|
||||
export const getStaticProps: GetStaticProps = async (context) => {
|
||||
if (context.locale) {
|
||||
const props: ArchivesProps = {
|
||||
langui: await getWebsiteInterface({
|
||||
language_code: context.locale,
|
||||
}),
|
||||
};
|
||||
return {
|
||||
props: props,
|
||||
};
|
||||
}
|
||||
return { props: {} };
|
||||
};
|
||||
export async function getStaticProps(
|
||||
context: GetStaticPropsContext
|
||||
): Promise<{ props: ArchivesProps }> {
|
||||
const props: ArchivesProps = {
|
||||
...(await getAppStaticProps(context)),
|
||||
};
|
||||
return {
|
||||
props: props,
|
||||
};
|
||||
}
|
||||
|
@ -1,44 +1,34 @@
|
||||
import SubPanel from "components/Panels/SubPanel";
|
||||
import PanelHeader from "components/PanelComponents/PanelHeader";
|
||||
import { GetWebsiteInterfaceQuery } from "graphql/operations-types";
|
||||
import { GetStaticProps } from "next";
|
||||
import { getWebsiteInterface } from "graphql/operations";
|
||||
import AppLayout from "components/AppLayout";
|
||||
import PanelHeader from "components/PanelComponents/PanelHeader";
|
||||
import SubPanel from "components/Panels/SubPanel";
|
||||
import { GetStaticPropsContext } from "next";
|
||||
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
||||
|
||||
type ChroniclesProps = {
|
||||
langui: GetWebsiteInterfaceQuery;
|
||||
};
|
||||
interface ChroniclesProps extends AppStaticProps {}
|
||||
|
||||
export default function Chronicles(props: ChroniclesProps): JSX.Element {
|
||||
const langui = props.langui.websiteInterfaces.data[0].attributes;
|
||||
const { langui } = props;
|
||||
const subPanel = (
|
||||
<SubPanel>
|
||||
<PanelHeader
|
||||
icon="watch_later"
|
||||
title={langui.main_chronicles}
|
||||
title={langui.chronicles}
|
||||
description={langui.chronicles_description}
|
||||
/>
|
||||
</SubPanel>
|
||||
);
|
||||
return (
|
||||
<AppLayout
|
||||
navTitle={langui.main_chronicles}
|
||||
langui={langui}
|
||||
subPanel={subPanel}
|
||||
/>
|
||||
<AppLayout navTitle={langui.chronicles} subPanel={subPanel} {...props} />
|
||||
);
|
||||
}
|
||||
|
||||
export const getStaticProps: GetStaticProps = async (context) => {
|
||||
if (context.locale) {
|
||||
const props: ChroniclesProps = {
|
||||
langui: await getWebsiteInterface({
|
||||
language_code: context.locale,
|
||||
}),
|
||||
};
|
||||
return {
|
||||
props: props,
|
||||
};
|
||||
}
|
||||
return { props: {} };
|
||||
};
|
||||
export async function getStaticProps(context: GetStaticPropsContext): Promise<{
|
||||
props: ChroniclesProps;
|
||||
}> {
|
||||
const props: ChroniclesProps = {
|
||||
...(await getAppStaticProps(context)),
|
||||
};
|
||||
return {
|
||||
props: props,
|
||||
};
|
||||
}
|
||||
|
@ -1,70 +1,118 @@
|
||||
import { GetStaticPaths, GetStaticProps } from "next";
|
||||
import {
|
||||
getContent,
|
||||
getContentsSlugs,
|
||||
getWebsiteInterface,
|
||||
} from "graphql/operations";
|
||||
import {
|
||||
GetContentQuery,
|
||||
GetWebsiteInterfaceQuery,
|
||||
} from "graphql/operations-types";
|
||||
import ContentPanel from "components/Panels/ContentPanel";
|
||||
import Button from "components/Button";
|
||||
import HorizontalLine from "components/HorizontalLine";
|
||||
import ThumbnailHeader from "components/Content/ThumbnailHeader";
|
||||
import AppLayout from "components/AppLayout";
|
||||
import Button from "components/Button";
|
||||
import ThumbnailHeader from "components/Content/ThumbnailHeader";
|
||||
import HorizontalLine from "components/HorizontalLine";
|
||||
import ReturnButton, {
|
||||
ReturnButtonType,
|
||||
} from "components/PanelComponents/ReturnButton";
|
||||
import ContentPanel from "components/Panels/ContentPanel";
|
||||
import SubPanel from "components/Panels/SubPanel";
|
||||
import ReturnButton from "components/PanelComponents/ReturnButton";
|
||||
import { getContent, getContentsSlugs } from "graphql/operations";
|
||||
import { GetContentQuery } from "graphql/operations-types";
|
||||
import {
|
||||
GetStaticPathsContext,
|
||||
GetStaticPathsResult,
|
||||
GetStaticPropsContext,
|
||||
} from "next";
|
||||
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
||||
import { prettyinlineTitle, prettySlug } from "queries/helpers";
|
||||
|
||||
type ContentIndexProps = {
|
||||
content: GetContentQuery;
|
||||
langui: GetWebsiteInterfaceQuery;
|
||||
};
|
||||
interface ContentIndexProps extends AppStaticProps {
|
||||
content: GetContentQuery["contents"]["data"][number]["attributes"];
|
||||
}
|
||||
|
||||
export default function ContentIndex(props: ContentIndexProps): JSX.Element {
|
||||
const content = props.content.contents.data[0].attributes;
|
||||
const langui = props.langui.websiteInterfaces.data[0].attributes;
|
||||
const { content, langui } = props;
|
||||
const subPanel = (
|
||||
<SubPanel>
|
||||
<ReturnButton href="/contents" title={"Contents"} langui={langui} />
|
||||
<HorizontalLine />
|
||||
<ReturnButton
|
||||
href="/contents"
|
||||
title={"Contents"}
|
||||
langui={langui}
|
||||
displayOn={ReturnButtonType.desktop}
|
||||
horizontalLine
|
||||
/>
|
||||
</SubPanel>
|
||||
);
|
||||
const contentPanel = (
|
||||
<ContentPanel>
|
||||
<ReturnButton
|
||||
href="/contents"
|
||||
title={"Contents"}
|
||||
langui={langui}
|
||||
displayOn={ReturnButtonType.mobile}
|
||||
className="mb-10"
|
||||
/>
|
||||
<div className="grid place-items-center">
|
||||
<ThumbnailHeader content={content} langui={langui} />
|
||||
<ThumbnailHeader
|
||||
thumbnail={content.thumbnail.data?.attributes}
|
||||
pre_title={
|
||||
content.titles.length > 0 ? content.titles[0].pre_title : undefined
|
||||
}
|
||||
title={
|
||||
content.titles.length > 0
|
||||
? content.titles[0].title
|
||||
: prettySlug(content.slug)
|
||||
}
|
||||
subtitle={
|
||||
content.titles.length > 0 ? content.titles[0].subtitle : undefined
|
||||
}
|
||||
description={
|
||||
content.titles.length > 0
|
||||
? content.titles[0].description
|
||||
: undefined
|
||||
}
|
||||
type={content.type}
|
||||
categories={content.categories}
|
||||
langui={langui}
|
||||
/>
|
||||
|
||||
<HorizontalLine />
|
||||
|
||||
{content.text_set.length > 0 ? (
|
||||
{content.text_set.length > 0 && (
|
||||
<Button href={`/contents/${content.slug}/read/`}>
|
||||
{langui.content_read_content}
|
||||
{langui.read_content}
|
||||
</Button>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
|
||||
{content.audio_set.length > 0 ? (
|
||||
{content.audio_set.length > 0 && (
|
||||
<Button href={`/contents/${content.slug}/listen/`}>
|
||||
{langui.content_listen_content}
|
||||
{langui.listen_content}
|
||||
</Button>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
|
||||
{content.video_set.length > 0 ? (
|
||||
{content.video_set.length > 0 && (
|
||||
<Button href={`/contents/${content.slug}/watch/`}>
|
||||
{langui.content_watch_content}
|
||||
{langui.watch_content}
|
||||
</Button>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
</div>
|
||||
</ContentPanel>
|
||||
);
|
||||
|
||||
let description = "";
|
||||
if (content.type.data) {
|
||||
description += `${langui.type}: `;
|
||||
if (content.type.data.attributes.titles.length > 0) {
|
||||
description += content.type.data.attributes.titles[0].title;
|
||||
} else {
|
||||
description += prettySlug(content.type.data.attributes.slug);
|
||||
}
|
||||
description += "\n";
|
||||
}
|
||||
if (content.categories.data.length > 0) {
|
||||
description += `${langui.categories}: `;
|
||||
description += content.categories.data
|
||||
.map((category) => category.attributes.short)
|
||||
.join(" | ");
|
||||
description += "\n";
|
||||
}
|
||||
|
||||
if (content.titles.length > 0 && content.titles[0].description) {
|
||||
description += "\n";
|
||||
description += content.titles[0].description;
|
||||
}
|
||||
|
||||
return (
|
||||
<AppLayout
|
||||
navTitle="Contents"
|
||||
@ -78,50 +126,37 @@ export default function ContentIndex(props: ContentIndexProps): JSX.Element {
|
||||
: prettySlug(content.slug)
|
||||
}
|
||||
thumbnail={content.thumbnail.data?.attributes}
|
||||
langui={langui}
|
||||
contentPanel={contentPanel}
|
||||
subPanel={subPanel}
|
||||
description={
|
||||
content.titles.length > 0 ? content.titles[0].description : undefined
|
||||
}
|
||||
description={description}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export const getStaticProps: GetStaticProps = async (context) => {
|
||||
if (context.params) {
|
||||
if (context.params.slug && context.locale) {
|
||||
if (context.params.slug instanceof Array)
|
||||
context.params.slug = context.params.slug.join("");
|
||||
|
||||
const props: ContentIndexProps = {
|
||||
content: await getContent({
|
||||
slug: context.params.slug,
|
||||
language_code: context.locale,
|
||||
}),
|
||||
langui: await getWebsiteInterface({
|
||||
language_code: context.locale,
|
||||
}),
|
||||
};
|
||||
return {
|
||||
props: props,
|
||||
};
|
||||
}
|
||||
}
|
||||
return { props: {} };
|
||||
};
|
||||
|
||||
export const getStaticPaths: GetStaticPaths = async (context) => {
|
||||
type Path = {
|
||||
params: {
|
||||
slug: string;
|
||||
};
|
||||
locale: string;
|
||||
export async function getStaticProps(context: GetStaticPropsContext): Promise<{
|
||||
props: ContentIndexProps;
|
||||
}> {
|
||||
const props: ContentIndexProps = {
|
||||
...(await getAppStaticProps(context)),
|
||||
content: (
|
||||
await getContent({
|
||||
slug: context.params?.slug?.toString() ?? "",
|
||||
language_code: context.locale ?? "en",
|
||||
})
|
||||
).contents.data[0].attributes,
|
||||
};
|
||||
return {
|
||||
props: props,
|
||||
};
|
||||
}
|
||||
|
||||
const data = await getContentsSlugs({});
|
||||
const paths: Path[] = [];
|
||||
data.contents.data.map((item) => {
|
||||
export async function getStaticPaths(
|
||||
context: GetStaticPathsContext
|
||||
): Promise<GetStaticPathsResult> {
|
||||
const contents = await getContentsSlugs({});
|
||||
const paths: GetStaticPathsResult["paths"] = [];
|
||||
contents.contents.data.map((item) => {
|
||||
context.locales?.map((local) => {
|
||||
paths.push({ params: { slug: item.attributes.slug }, locale: local });
|
||||
});
|
||||
@ -130,4 +165,4 @@ export const getStaticPaths: GetStaticPaths = async (context) => {
|
||||
paths,
|
||||
fallback: false,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
@ -1,44 +1,47 @@
|
||||
import { GetStaticPaths, GetStaticProps } from "next";
|
||||
import {
|
||||
getContentsSlugs,
|
||||
getContentText,
|
||||
getWebsiteInterface,
|
||||
} from "graphql/operations";
|
||||
import {
|
||||
Enum_Componentsetstextset_Status,
|
||||
GetContentTextQuery,
|
||||
GetWebsiteInterfaceQuery,
|
||||
} from "graphql/operations-types";
|
||||
import ContentPanel from "components/Panels/ContentPanel";
|
||||
import HorizontalLine from "components/HorizontalLine";
|
||||
import SubPanel from "components/Panels/SubPanel";
|
||||
import ReturnButton from "components/PanelComponents/ReturnButton";
|
||||
import ThumbnailHeader from "components/Content/ThumbnailHeader";
|
||||
import AppLayout from "components/AppLayout";
|
||||
import Button from "components/Button";
|
||||
import Chip from "components/Chip";
|
||||
import ThumbnailHeader from "components/Content/ThumbnailHeader";
|
||||
import HorizontalLine from "components/HorizontalLine";
|
||||
import LanguageSwitcher from "components/LanguageSwitcher";
|
||||
import Markdawn from "components/Markdown/Markdawn";
|
||||
import TOC from "components/Markdown/TOC";
|
||||
import ReturnButton, {
|
||||
ReturnButtonType,
|
||||
} from "components/PanelComponents/ReturnButton";
|
||||
import ContentPanel from "components/Panels/ContentPanel";
|
||||
import SubPanel from "components/Panels/SubPanel";
|
||||
import RecorderChip from "components/RecorderChip";
|
||||
import ToolTip from "components/ToolTip";
|
||||
import { getContentsSlugs, getContentText } from "graphql/operations";
|
||||
import { GetContentTextQuery } from "graphql/operations-types";
|
||||
import {
|
||||
GetStaticPathsContext,
|
||||
GetStaticPathsResult,
|
||||
GetStaticPropsContext,
|
||||
} from "next";
|
||||
import { useRouter } from "next/router";
|
||||
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
||||
import {
|
||||
getLocalesFromLanguages,
|
||||
getStatusDescription,
|
||||
prettyinlineTitle,
|
||||
prettyLanguage,
|
||||
prettySlug,
|
||||
prettyTestError,
|
||||
prettyTestWarning,
|
||||
} from "queries/helpers";
|
||||
import Button from "components/Button";
|
||||
import { useRouter } from "next/router";
|
||||
import Chip from "components/Chip";
|
||||
import ReactTooltip from "react-tooltip";
|
||||
import RecorderChip from "components/RecorderChip";
|
||||
|
||||
interface ContentReadProps {
|
||||
content: GetContentTextQuery;
|
||||
langui: GetWebsiteInterfaceQuery;
|
||||
interface ContentReadProps extends AppStaticProps {
|
||||
content: GetContentTextQuery["contents"]["data"][number]["attributes"];
|
||||
contentId: GetContentTextQuery["contents"]["data"][number]["id"];
|
||||
}
|
||||
|
||||
export default function ContentRead(props: ContentReadProps): JSX.Element {
|
||||
useTesting(props);
|
||||
const content = props.content.contents.data[0].attributes;
|
||||
const langui = props.langui.websiteInterfaces.data[0].attributes;
|
||||
const { langui, content, languages } = props;
|
||||
const router = useRouter();
|
||||
const locales = getLocalesFromLanguages(content.text_set_languages);
|
||||
|
||||
const subPanel = (
|
||||
<SubPanel>
|
||||
@ -46,23 +49,23 @@ export default function ContentRead(props: ContentReadProps): JSX.Element {
|
||||
href={`/contents/${content.slug}`}
|
||||
title={"Content"}
|
||||
langui={langui}
|
||||
displayOn={ReturnButtonType.desktop}
|
||||
horizontalLine
|
||||
/>
|
||||
|
||||
<HorizontalLine />
|
||||
|
||||
{content.text_set.length > 0 ? (
|
||||
{content.text_set.length > 0 && content.text_set[0].source_language.data && (
|
||||
<div className="grid gap-5">
|
||||
<h2 className="text-xl">
|
||||
{content.text_set[0].source_language.data.attributes.code ===
|
||||
router.locale
|
||||
? "This content is a transcript"
|
||||
: "This content is a fan-translation"}
|
||||
? langui.transcript_notice
|
||||
: langui.translation_notice}
|
||||
</h2>
|
||||
|
||||
{content.text_set[0].source_language.data.attributes.code !==
|
||||
router.locale && (
|
||||
<div className="grid place-items-center gap-2">
|
||||
<p className="font-headers">Source language:</p>
|
||||
<p className="font-headers">{langui.source_language}:</p>
|
||||
<Button
|
||||
href={router.asPath}
|
||||
locale={
|
||||
@ -70,40 +73,34 @@ export default function ContentRead(props: ContentReadProps): JSX.Element {
|
||||
}
|
||||
>
|
||||
{prettyLanguage(
|
||||
content.text_set[0].source_language.data.attributes.code
|
||||
content.text_set[0].source_language.data.attributes.code,
|
||||
languages
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="grid grid-flow-col place-items-center place-content-center gap-2">
|
||||
<p className="font-headers">Status:</p>
|
||||
<p className="font-headers">{langui.status}:</p>
|
||||
|
||||
<Chip
|
||||
data-tip={
|
||||
content.text_set[0].status ===
|
||||
Enum_Componentsetstextset_Status.Incomplete
|
||||
? "This entry is only partially translated/transcribed."
|
||||
: content.text_set[0].status ===
|
||||
Enum_Componentsetstextset_Status.Draft
|
||||
? "This entry is just a draft. It usually means that this is a work-in-progress. Translation/transcription might be poor and/or computer-generated."
|
||||
: content.text_set[0].status ===
|
||||
Enum_Componentsetstextset_Status.Review
|
||||
? "This entry has not yet being proofread. The content should still be accurate."
|
||||
: "This entry has been checked and proofread. If you notice any translation errors or typos, please contact us so we can fix it!"
|
||||
}
|
||||
data-for={"StatusTooltip"}
|
||||
<ToolTip
|
||||
content={getStatusDescription(content.text_set[0].status, langui)}
|
||||
maxWidth={"20rem"}
|
||||
>
|
||||
{content.text_set[0].status}
|
||||
</Chip>
|
||||
<Chip>{content.text_set[0].status}</Chip>
|
||||
</ToolTip>
|
||||
</div>
|
||||
|
||||
{content.text_set[0].transcribers.data.length > 0 && (
|
||||
<div>
|
||||
<p className="font-headers">Transcribers:</p>
|
||||
<p className="font-headers">{langui.transcribers}:</p>
|
||||
<div className="grid place-items-center place-content-center gap-2">
|
||||
{content.text_set[0].transcribers.data.map((recorder) => (
|
||||
<RecorderChip key={recorder.id} recorder={recorder} />
|
||||
<RecorderChip
|
||||
key={recorder.id}
|
||||
langui={langui}
|
||||
recorder={recorder}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
@ -111,10 +108,14 @@ export default function ContentRead(props: ContentReadProps): JSX.Element {
|
||||
|
||||
{content.text_set[0].translators.data.length > 0 && (
|
||||
<div>
|
||||
<p className="font-headers">Translators:</p>
|
||||
<p className="font-headers">{langui.translators}:</p>
|
||||
<div className="grid place-items-center place-content-center gap-2">
|
||||
{content.text_set[0].translators.data.map((recorder) => (
|
||||
<RecorderChip key={recorder.id} recorder={recorder} />
|
||||
<RecorderChip
|
||||
key={recorder.id}
|
||||
langui={langui}
|
||||
recorder={recorder}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
@ -122,61 +123,105 @@ export default function ContentRead(props: ContentReadProps): JSX.Element {
|
||||
|
||||
{content.text_set[0].proofreaders.data.length > 0 && (
|
||||
<div>
|
||||
<p className="font-headers">Proofreaders:</p>
|
||||
<p className="font-headers">{langui.proofreaders}:</p>
|
||||
<div className="grid place-items-center place-content-center gap-2">
|
||||
{content.text_set[0].proofreaders.data.map((recorder) => (
|
||||
<RecorderChip key={recorder.id} recorder={recorder} />
|
||||
<RecorderChip
|
||||
key={recorder.id}
|
||||
langui={langui}
|
||||
recorder={recorder}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
|
||||
{content.text_set.length > 0 && content.text_set[0].text && (
|
||||
<>
|
||||
<HorizontalLine />
|
||||
<TOC
|
||||
text={content.text_set[0].text}
|
||||
title={
|
||||
content.titles.length > 0
|
||||
? prettyinlineTitle(
|
||||
content.titles[0].pre_title,
|
||||
content.titles[0].title,
|
||||
content.titles[0].subtitle
|
||||
)
|
||||
: prettySlug(content.slug)
|
||||
}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</SubPanel>
|
||||
);
|
||||
const contentPanel = (
|
||||
<ContentPanel>
|
||||
<ReturnButton
|
||||
href={`/contents/${content.slug}`}
|
||||
title={langui.content}
|
||||
langui={langui}
|
||||
displayOn={ReturnButtonType.mobile}
|
||||
className="mb-10"
|
||||
/>
|
||||
<div className="grid place-items-center">
|
||||
<ThumbnailHeader content={content} langui={langui} />
|
||||
<ThumbnailHeader
|
||||
thumbnail={content.thumbnail.data?.attributes}
|
||||
pre_title={
|
||||
content.titles.length > 0 ? content.titles[0].pre_title : undefined
|
||||
}
|
||||
title={
|
||||
content.titles.length > 0
|
||||
? content.titles[0].title
|
||||
: prettySlug(content.slug)
|
||||
}
|
||||
subtitle={
|
||||
content.titles.length > 0 ? content.titles[0].subtitle : undefined
|
||||
}
|
||||
description={
|
||||
content.titles.length > 0
|
||||
? content.titles[0].description
|
||||
: undefined
|
||||
}
|
||||
type={content.type}
|
||||
categories={content.categories}
|
||||
langui={langui}
|
||||
/>
|
||||
|
||||
<HorizontalLine />
|
||||
|
||||
{content.text_set.length > 0 ? (
|
||||
{locales.includes(router.locale ?? "en") ? (
|
||||
<Markdawn text={content.text_set[0].text} />
|
||||
) : (
|
||||
""
|
||||
<LanguageSwitcher
|
||||
locales={locales}
|
||||
languages={props.languages}
|
||||
langui={props.langui}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</ContentPanel>
|
||||
);
|
||||
|
||||
const extra = (
|
||||
<>
|
||||
<ReactTooltip
|
||||
id="StatusTooltip"
|
||||
place="top"
|
||||
type="light"
|
||||
effect="solid"
|
||||
delayShow={50}
|
||||
clickable={true}
|
||||
className="drop-shadow-shade-xl !opacity-100 !bg-light !rounded-lg desktop:after:!border-t-light text-left !text-black max-w-xs"
|
||||
/>
|
||||
|
||||
<ReactTooltip
|
||||
id="RecordersTooltip"
|
||||
place="top"
|
||||
type="light"
|
||||
effect="solid"
|
||||
delayShow={100}
|
||||
delayUpdate={100}
|
||||
delayHide={100}
|
||||
clickable={true}
|
||||
className="drop-shadow-shade-xl !opacity-100 !bg-light !rounded-lg desktop:after:!border-t-light text-left !text-black max-w-[22rem]"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
let description = "";
|
||||
if (content.type.data) {
|
||||
description += `${langui.type}: `;
|
||||
if (content.type.data.attributes.titles.length > 0) {
|
||||
description += content.type.data.attributes.titles[0].title;
|
||||
} else {
|
||||
description += prettySlug(content.type.data.attributes.slug);
|
||||
}
|
||||
description += "\n";
|
||||
}
|
||||
if (content.categories.data.length > 0) {
|
||||
description += `${langui.categories}: `;
|
||||
description += content.categories.data
|
||||
.map((category) => category.attributes.short)
|
||||
.join(" | ");
|
||||
description += "\n";
|
||||
}
|
||||
|
||||
return (
|
||||
<AppLayout
|
||||
@ -191,48 +236,40 @@ export default function ContentRead(props: ContentReadProps): JSX.Element {
|
||||
: prettySlug(content.slug)
|
||||
}
|
||||
thumbnail={content.thumbnail.data?.attributes}
|
||||
langui={langui}
|
||||
contentPanel={contentPanel}
|
||||
subPanel={subPanel}
|
||||
extra={extra}
|
||||
description={description}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export const getStaticProps: GetStaticProps = async (context) => {
|
||||
if (context.params) {
|
||||
if (context.params.slug && context.locale) {
|
||||
if (context.params.slug instanceof Array)
|
||||
context.params.slug = context.params.slug.join("");
|
||||
|
||||
const props: ContentReadProps = {
|
||||
content: await getContentText({
|
||||
slug: context.params.slug,
|
||||
language_code: context.locale,
|
||||
}),
|
||||
langui: await getWebsiteInterface({
|
||||
language_code: context.locale,
|
||||
}),
|
||||
};
|
||||
return {
|
||||
props: props,
|
||||
};
|
||||
}
|
||||
}
|
||||
return { props: {} };
|
||||
};
|
||||
|
||||
export const getStaticPaths: GetStaticPaths = async (context) => {
|
||||
type Path = {
|
||||
params: {
|
||||
slug: string;
|
||||
};
|
||||
locale: string;
|
||||
export async function getStaticProps(
|
||||
context: GetStaticPropsContext
|
||||
): Promise<{ props: ContentReadProps }> {
|
||||
const slug = context.params?.slug?.toString() ?? "";
|
||||
const content = (
|
||||
await getContentText({
|
||||
slug: slug,
|
||||
language_code: context.locale ?? "en",
|
||||
})
|
||||
).contents.data[0];
|
||||
const props: ContentReadProps = {
|
||||
...(await getAppStaticProps(context)),
|
||||
content: content.attributes,
|
||||
contentId: content.id,
|
||||
};
|
||||
return {
|
||||
props: props,
|
||||
};
|
||||
}
|
||||
|
||||
const data = await getContentsSlugs({});
|
||||
const paths: Path[] = [];
|
||||
data.contents.data.map((item) => {
|
||||
export async function getStaticPaths(
|
||||
context: GetStaticPathsContext
|
||||
): Promise<GetStaticPathsResult> {
|
||||
const contents = await getContentsSlugs({});
|
||||
const paths: GetStaticPathsResult["paths"] = [];
|
||||
contents.contents.data.map((item) => {
|
||||
context.locales?.map((local) => {
|
||||
paths.push({ params: { slug: item.attributes.slug }, locale: local });
|
||||
});
|
||||
@ -241,15 +278,13 @@ export const getStaticPaths: GetStaticPaths = async (context) => {
|
||||
paths,
|
||||
fallback: false,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export function useTesting(props: ContentReadProps) {
|
||||
function useTesting(props: ContentReadProps) {
|
||||
const router = useRouter();
|
||||
const content = props.content.contents.data[0].attributes;
|
||||
const { content, contentId } = props;
|
||||
|
||||
const contentURL =
|
||||
"/admin/content-manager/collectionType/api::content.content/" +
|
||||
props.content.contents.data[0].id;
|
||||
const contentURL = `/admin/content-manager/collectionType/api::content.content/${contentId}`;
|
||||
|
||||
if (router.locale === "en") {
|
||||
if (content.categories.data.length === 0) {
|
||||
@ -276,18 +311,17 @@ export function useTesting(props: ContentReadProps) {
|
||||
}
|
||||
|
||||
if (content.text_set.length > 1) {
|
||||
console.warn(
|
||||
prettyTestError(
|
||||
router,
|
||||
"More than one textset for this language",
|
||||
["content", "text_set"],
|
||||
contentURL
|
||||
)
|
||||
prettyTestError(
|
||||
router,
|
||||
"More than one textset for this language",
|
||||
["content", "text_set"],
|
||||
contentURL
|
||||
);
|
||||
}
|
||||
|
||||
if (content.text_set.length === 1) {
|
||||
const textset = content.text_set[0];
|
||||
|
||||
if (!textset.text) {
|
||||
prettyTestError(
|
||||
router,
|
||||
@ -303,8 +337,7 @@ export function useTesting(props: ContentReadProps) {
|
||||
["content", "text_set"],
|
||||
contentURL
|
||||
);
|
||||
}
|
||||
if (textset.source_language.data.attributes.code === router.locale) {
|
||||
} else if (textset.source_language.data.attributes.code === router.locale) {
|
||||
// This is a transcript
|
||||
if (textset.transcribers.data.length === 0) {
|
||||
prettyTestError(
|
||||
|
@ -1,27 +1,114 @@
|
||||
import { GetStaticProps } from "next";
|
||||
import SubPanel from "components/Panels/SubPanel";
|
||||
import AppLayout from "components/AppLayout";
|
||||
import Chip from "components/Chip";
|
||||
import LibraryContentPreview from "components/Library/LibraryContentPreview";
|
||||
import PanelHeader from "components/PanelComponents/PanelHeader";
|
||||
import ContentPanel, {
|
||||
ContentPanelWidthSizes,
|
||||
} from "components/Panels/ContentPanel";
|
||||
import SubPanel from "components/Panels/SubPanel";
|
||||
import Select from "components/Select";
|
||||
import { getContents } from "graphql/operations";
|
||||
import {
|
||||
GetContentsQuery,
|
||||
GetWebsiteInterfaceQuery,
|
||||
} from "graphql/operations-types";
|
||||
import { getContents, getWebsiteInterface } from "graphql/operations";
|
||||
import PanelHeader from "components/PanelComponents/PanelHeader";
|
||||
import AppLayout from "components/AppLayout";
|
||||
import LibraryContentPreview from "components/Library/LibraryContentPreview";
|
||||
import { prettyinlineTitle } from "queries/helpers";
|
||||
import { GetStaticPropsContext } from "next";
|
||||
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
||||
import { prettyinlineTitle, prettySlug } from "queries/helpers";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
type LibraryProps = {
|
||||
contents: GetContentsQuery;
|
||||
langui: GetWebsiteInterfaceQuery;
|
||||
};
|
||||
interface ContentsProps extends AppStaticProps {
|
||||
contents: GetContentsQuery["contents"]["data"];
|
||||
}
|
||||
|
||||
export default function Library(props: LibraryProps): JSX.Element {
|
||||
const langui = props.langui.websiteInterfaces.data[0].attributes;
|
||||
type GroupContentItems = Map<string, GetContentsQuery["contents"]["data"]>;
|
||||
|
||||
props.contents.contents.data.sort((a, b) => {
|
||||
export default function Contents(props: ContentsProps): JSX.Element {
|
||||
const { langui, contents } = props;
|
||||
|
||||
const [groupingMethod, setGroupingMethod] = useState<number>(-1);
|
||||
|
||||
const [groups, setGroups] = useState<GroupContentItems>(
|
||||
getGroups(langui, groupingMethod, contents)
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setGroups(getGroups(langui, groupingMethod, contents));
|
||||
}, [langui, groupingMethod, contents]);
|
||||
|
||||
const subPanel = (
|
||||
<SubPanel>
|
||||
<PanelHeader
|
||||
icon="workspaces"
|
||||
title={langui.contents}
|
||||
description={langui.contents_description}
|
||||
/>
|
||||
|
||||
<div className="flex flex-row gap-2 place-items-center">
|
||||
<p className="flex-shrink-0">{langui.group_by}:</p>
|
||||
<Select
|
||||
className="w-full"
|
||||
options={[langui.category, langui.type]}
|
||||
state={groupingMethod}
|
||||
setState={setGroupingMethod}
|
||||
allowEmpty
|
||||
/>
|
||||
</div>
|
||||
</SubPanel>
|
||||
);
|
||||
const contentPanel = (
|
||||
<ContentPanel width={ContentPanelWidthSizes.large}>
|
||||
{[...groups].map(([name, items]) => (
|
||||
<>
|
||||
{items.length > 0 && (
|
||||
<>
|
||||
{name && (
|
||||
<h2
|
||||
key={`h2${name}`}
|
||||
className="text-2xl pb-2 pt-10 first-of-type:pt-0 flex flex-row place-items-center gap-2"
|
||||
>
|
||||
{name}
|
||||
<Chip>{`${items.length} ${
|
||||
items.length <= 1
|
||||
? langui.result.toLowerCase()
|
||||
: langui.results.toLowerCase()
|
||||
}`}</Chip>
|
||||
</h2>
|
||||
)}
|
||||
<div
|
||||
key={`items${name}`}
|
||||
className="grid gap-8 items-end grid-cols-2 desktop:grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))]"
|
||||
>
|
||||
{items.map((item) => (
|
||||
<LibraryContentPreview key={item.id} item={item.attributes} />
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
))}
|
||||
</ContentPanel>
|
||||
);
|
||||
return (
|
||||
<AppLayout
|
||||
navTitle={langui.contents}
|
||||
subPanel={subPanel}
|
||||
contentPanel={contentPanel}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export async function getStaticProps(
|
||||
context: GetStaticPropsContext
|
||||
): Promise<{ props: ContentsProps }> {
|
||||
const contents = (
|
||||
await getContents({
|
||||
language_code: context.locale ?? "en",
|
||||
})
|
||||
).contents.data;
|
||||
|
||||
contents.sort((a, b) => {
|
||||
const titleA =
|
||||
a.attributes.titles.length > 0
|
||||
? prettyinlineTitle(
|
||||
@ -41,48 +128,73 @@ export default function Library(props: LibraryProps): JSX.Element {
|
||||
return titleA.localeCompare(titleB);
|
||||
});
|
||||
|
||||
const subPanel = (
|
||||
<SubPanel>
|
||||
<PanelHeader
|
||||
icon="workspaces"
|
||||
title="Contents"
|
||||
description="Laboriosam vitae velit quis. Non et dolor reiciendis officia earum et molestias excepturi. Cupiditate officiis quis qui reprehenderit. Ut neque eos ipsa corrupti autem mollitia inventore. Exercitationem iste magni vel harum."
|
||||
/>
|
||||
</SubPanel>
|
||||
);
|
||||
const contentPanel = (
|
||||
<ContentPanel width={ContentPanelWidthSizes.large}>
|
||||
<div className="grid gap-8 items-end grid-cols-2 desktop:grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))]">
|
||||
{props.contents.contents.data.map((item) => (
|
||||
<LibraryContentPreview key={item.id} item={item.attributes} />
|
||||
))}
|
||||
</div>
|
||||
</ContentPanel>
|
||||
);
|
||||
return (
|
||||
<AppLayout
|
||||
navTitle="Contents"
|
||||
langui={langui}
|
||||
subPanel={subPanel}
|
||||
contentPanel={contentPanel}
|
||||
/>
|
||||
);
|
||||
const props: ContentsProps = {
|
||||
...(await getAppStaticProps(context)),
|
||||
contents: contents,
|
||||
};
|
||||
return {
|
||||
props: props,
|
||||
};
|
||||
}
|
||||
|
||||
export const getStaticProps: GetStaticProps = async (context) => {
|
||||
if (context.locale) {
|
||||
const props: LibraryProps = {
|
||||
contents: await getContents({
|
||||
language_code: context.locale,
|
||||
}),
|
||||
langui: await getWebsiteInterface({
|
||||
language_code: context.locale,
|
||||
}),
|
||||
};
|
||||
return {
|
||||
props: props,
|
||||
};
|
||||
} else {
|
||||
return { props: {} };
|
||||
function getGroups(
|
||||
langui: GetWebsiteInterfaceQuery["websiteInterfaces"]["data"][number]["attributes"],
|
||||
groupByType: number,
|
||||
items: ContentsProps["contents"]
|
||||
): GroupContentItems {
|
||||
switch (groupByType) {
|
||||
case 0: {
|
||||
const group = new Map();
|
||||
group.set("Drakengard 1", []);
|
||||
group.set("Drakengard 1.3", []);
|
||||
group.set("Drakengard 2", []);
|
||||
group.set("Drakengard 3", []);
|
||||
group.set("Drakengard 4", []);
|
||||
group.set("NieR Gestalt", []);
|
||||
group.set("NieR Replicant", []);
|
||||
group.set("NieR Replicant ver.1.22474487139...", []);
|
||||
group.set("NieR:Automata", []);
|
||||
group.set("NieR Re[in]carnation", []);
|
||||
group.set("SINoALICE", []);
|
||||
group.set("Voice of Cards", []);
|
||||
group.set("Final Fantasy XIV", []);
|
||||
group.set("Thou Shalt Not Die", []);
|
||||
group.set("Bakuken", []);
|
||||
group.set("YoRHa", []);
|
||||
group.set("YoRHa Boys", []);
|
||||
group.set(langui.no_category, []);
|
||||
|
||||
items.map((item) => {
|
||||
if (item.attributes.categories.data.length === 0) {
|
||||
group.get(langui.no_category)?.push(item);
|
||||
} else {
|
||||
item.attributes.categories.data.map((category) => {
|
||||
group.get(category.attributes.name)?.push(item);
|
||||
});
|
||||
}
|
||||
});
|
||||
return group;
|
||||
}
|
||||
|
||||
case 1: {
|
||||
const group: GroupContentItems = new Map();
|
||||
items.map((item) => {
|
||||
const type =
|
||||
item.attributes.type.data.attributes.titles.length > 0
|
||||
? item.attributes.type.data.attributes.titles[0].title
|
||||
: prettySlug(item.attributes.type.data.attributes.slug);
|
||||
|
||||
if (!group.has(type)) group.set(type, []);
|
||||
group.get(type)?.push(item);
|
||||
});
|
||||
|
||||
return group;
|
||||
}
|
||||
|
||||
default: {
|
||||
const group: GroupContentItems = new Map();
|
||||
group.set("", items);
|
||||
return group;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -1,23 +1,19 @@
|
||||
import AppLayout from "components/AppLayout";
|
||||
import Markdawn from "components/Markdown/Markdawn";
|
||||
import ContentPanel, {
|
||||
ContentPanelWidthSizes,
|
||||
} from "components/Panels/ContentPanel";
|
||||
import { getWebsiteInterface } from "graphql/operations";
|
||||
import { GetStaticProps } from "next";
|
||||
import { GetWebsiteInterfaceQuery } from "graphql/operations-types";
|
||||
import AppLayout from "components/AppLayout";
|
||||
import { useCallback, useState } from "react";
|
||||
import Markdawn from "components/Markdown/Markdawn";
|
||||
import { GetStaticPropsContext } from "next";
|
||||
import Script from "next/script";
|
||||
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
||||
import { useCallback, useState } from "react";
|
||||
import { default as TurndownService } from "turndown";
|
||||
|
||||
type EditorProps = {
|
||||
langui: GetWebsiteInterfaceQuery;
|
||||
};
|
||||
interface EditorProps extends AppStaticProps {}
|
||||
|
||||
export default function Editor(props: EditorProps): JSX.Element {
|
||||
const langui = props.langui.websiteInterfaces.data[0].attributes;
|
||||
|
||||
const handleInput = useCallback((e) => {
|
||||
setMarkdown(e.target.value);
|
||||
const handleInput = useCallback((event) => {
|
||||
setMarkdown(event.target.value);
|
||||
}, []);
|
||||
|
||||
const [markdown, setMarkdown] = useState("");
|
||||
@ -45,14 +41,15 @@ export default function Editor(props: EditorProps): JSX.Element {
|
||||
onInput={handleInput}
|
||||
className="bg-mid rounded-xl p-8 w-full font-monospace"
|
||||
value={markdown}
|
||||
title="Input textarea"
|
||||
/>
|
||||
|
||||
<h2 className="mt-4">Convert text to markdown</h2>
|
||||
<textarea
|
||||
readOnly
|
||||
id="htmlMdTextArea"
|
||||
title="Ouput textarea"
|
||||
onPaste={(event) => {
|
||||
const TurndownService = require("turndown").default;
|
||||
const turndownService = new TurndownService({
|
||||
headingStyle: "atx",
|
||||
codeBlockStyle: "fenced",
|
||||
@ -62,16 +59,16 @@ export default function Editor(props: EditorProps): JSX.Element {
|
||||
});
|
||||
|
||||
let paste = event.clipboardData.getData("text/html");
|
||||
paste = paste.replace(/<\!--.*?-->/g, "");
|
||||
paste = paste.replace(/<!--.*?-->/u, "");
|
||||
paste = turndownService.turndown(paste);
|
||||
paste = paste.replace(/<\!--.*?-->/g, "");
|
||||
paste = paste.replace(/<!--.*?-->/u, "");
|
||||
|
||||
const target = event.target as HTMLTextAreaElement;
|
||||
target.value = paste;
|
||||
target.select();
|
||||
event.preventDefault();
|
||||
}}
|
||||
className="bg-mid rounded-xl p-8 w-full font-monospace"
|
||||
className="font-monospace"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
@ -86,22 +83,19 @@ export default function Editor(props: EditorProps): JSX.Element {
|
||||
return (
|
||||
<AppLayout
|
||||
navTitle="Markdawn Editor"
|
||||
langui={langui}
|
||||
contentPanel={contentPanel}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export const getStaticProps: GetStaticProps = async (context) => {
|
||||
if (context.locale) {
|
||||
const props: EditorProps = {
|
||||
langui: await getWebsiteInterface({
|
||||
language_code: context.locale,
|
||||
}),
|
||||
};
|
||||
return {
|
||||
props: props,
|
||||
};
|
||||
}
|
||||
return { props: {} };
|
||||
};
|
||||
export async function getStaticProps(
|
||||
context: GetStaticPropsContext
|
||||
): Promise<{ props: EditorProps }> {
|
||||
const props: EditorProps = {
|
||||
...(await getAppStaticProps(context)),
|
||||
};
|
||||
return {
|
||||
props: props,
|
||||
};
|
||||
}
|
||||
|
@ -1,14 +1,11 @@
|
||||
import AppLayout from "components/AppLayout";
|
||||
import { getWebsiteInterface } from "graphql/operations";
|
||||
import { GetWebsiteInterfaceQuery } from "graphql/operations-types";
|
||||
import { GetStaticProps } from "next";
|
||||
import { GetStaticPropsContext } from "next";
|
||||
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
||||
|
||||
type GalleryProps = {
|
||||
langui: GetWebsiteInterfaceQuery;
|
||||
};
|
||||
interface GalleryProps extends AppStaticProps {}
|
||||
|
||||
export default function Gallery(props: GalleryProps): JSX.Element {
|
||||
const langui = props.langui.websiteInterfaces.data[0].attributes;
|
||||
const { langui } = props;
|
||||
const contentPanel = (
|
||||
<iframe
|
||||
className="w-full h-screen"
|
||||
@ -18,23 +15,20 @@ export default function Gallery(props: GalleryProps): JSX.Element {
|
||||
|
||||
return (
|
||||
<AppLayout
|
||||
navTitle={langui.main_gallery}
|
||||
langui={langui}
|
||||
navTitle={langui.gallery}
|
||||
contentPanel={contentPanel}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export const getStaticProps: GetStaticProps = async (context) => {
|
||||
if (context.locale) {
|
||||
const props: GalleryProps = {
|
||||
langui: await getWebsiteInterface({
|
||||
language_code: context.locale,
|
||||
}),
|
||||
};
|
||||
return {
|
||||
props: props,
|
||||
};
|
||||
}
|
||||
return { props: {} };
|
||||
};
|
||||
export async function getStaticProps(
|
||||
context: GetStaticPropsContext
|
||||
): Promise<{ props: GalleryProps }> {
|
||||
const props: GalleryProps = {
|
||||
...(await getAppStaticProps(context)),
|
||||
};
|
||||
return {
|
||||
props: props,
|
||||
};
|
||||
}
|
||||
|
@ -1,167 +1,71 @@
|
||||
import AppLayout from "components/AppLayout";
|
||||
import LanguageSwitcher from "components/LanguageSwitcher";
|
||||
import Markdawn from "components/Markdown/Markdawn";
|
||||
import ContentPanel from "components/Panels/ContentPanel";
|
||||
import SVG from "components/SVG";
|
||||
import { getWebsiteInterface } from "graphql/operations";
|
||||
import { GetWebsiteInterfaceQuery } from "graphql/operations-types";
|
||||
import { GetStaticProps } from "next";
|
||||
type HomeProps = {
|
||||
langui: GetWebsiteInterfaceQuery;
|
||||
};
|
||||
import { getPost } from "graphql/operations";
|
||||
import { GetPostQuery } from "graphql/operations-types";
|
||||
import { GetStaticPropsContext } from "next";
|
||||
import { useRouter } from "next/router";
|
||||
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
||||
import { getLocalesFromLanguages, prettySlug } from "queries/helpers";
|
||||
|
||||
interface HomeProps extends AppStaticProps {
|
||||
post: GetPostQuery["posts"]["data"][number]["attributes"];
|
||||
}
|
||||
|
||||
export default function Home(props: HomeProps): JSX.Element {
|
||||
const langui = props.langui.websiteInterfaces.data[0].attributes;
|
||||
const { post } = props;
|
||||
const locales = getLocalesFromLanguages(post.translations_languages);
|
||||
const router = useRouter();
|
||||
|
||||
const contentPanel = (
|
||||
<ContentPanel autoformat>
|
||||
<ContentPanel>
|
||||
<div className="grid place-items-center place-content-center w-full gap-5 text-center">
|
||||
<div className="[mask:url('/icons/accords.svg')] [mask-size:contain] [mask-repeat:no-repeat] [mask-position:center] w-32 aspect-square mobile:w-[50vw] bg-black" />
|
||||
<h1 className="text-5xl mb-0">Accord’s Library</h1>
|
||||
<h2 className="mt-0">Discover • Analyse • Translate • Archive</h2>
|
||||
<h2 className="text-xl -mt-5">
|
||||
Discover • Analyse • Translate • Archive
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<h2>What is this?</h2>
|
||||
<p>
|
||||
Accord’s Library aims at gathering and archiving all of Yoko
|
||||
Taro’s work. Yoko Taro is a Japanese video game director and
|
||||
scenario writer. He is best-known for his work on the NieR and
|
||||
Drakengard (Drag-on Dragoon) franchises. To complement his games, Yoko
|
||||
Taro likes to publish side materials in the form of books, novellas,
|
||||
artbooks, stage plays, manga, drama CDs, and comics. Those side
|
||||
materials can be very difficult to find. His work goes all the way back
|
||||
to 2003, and most of them are out of print after having been released
|
||||
solely in Japan, sometimes in limited quantities. Their prices on the
|
||||
second hand market have skyrocketed, ranging all the way to hundreds if
|
||||
not thousand of dollars for the rarest items.
|
||||
</p>
|
||||
<p>
|
||||
This is where this library takes its meaning, in trying to help the
|
||||
community grow by providing translators, writers, and wiki’s
|
||||
contributors a simple way to access these records filled with stories,
|
||||
artworks, and knowledge.
|
||||
</p>
|
||||
<p>
|
||||
We are a small group of Yoko Taro’s fans that decided to join
|
||||
forces and create a website and a community. Our motto is{" "}
|
||||
<strong>Discover • Analyze • Translate • Archive</strong> (D.A.T.A. for
|
||||
short). We started with the goal of gathering and archiving as much
|
||||
side-materials/merch as possible. But since then, our ambition grew and
|
||||
we decided to create a full-fledged website that will also include news
|
||||
articles, lore, summaries, translations, and transcriptions. Hopefully
|
||||
one day, we will be up there in the list of notable resources for
|
||||
Drakengard and NieR fans.
|
||||
</p>
|
||||
<h2>What’s on this website?</h2>
|
||||
<p>
|
||||
<strong>
|
||||
<a href="https://accords-library.com/compendium/">The Compendium</a>
|
||||
</strong>
|
||||
: This is where we will list every NieR/DOD/other Yoko Tato merch,
|
||||
games, books, novel, stage play, CD... well everything! For each, we
|
||||
will provide photos and/or scans of the content, information about what
|
||||
it is, when and how it was released, size, initial price...
|
||||
</p>
|
||||
<p>
|
||||
<strong>
|
||||
<a href="https://accords-library.com/news/">News</a>
|
||||
</strong>
|
||||
: Yes because we also want to create our own content! So there you will
|
||||
find translations, transcriptions, unboxing, news about future
|
||||
merch/game releases, maybe some guides. We don’t see this website
|
||||
as being purely a showcase of our work, but also of the community, and
|
||||
as such, we will be accepting applications for becoming contributors on
|
||||
the website. For the applicant, there is no deadline or article quota,
|
||||
it merely means that we will have access to the website Post Writing
|
||||
tools and will be able to submit a draft that can be published once
|
||||
verified by an editor. Anyway, that’s at least the plan, we will
|
||||
think more about this until the website’s official launch.
|
||||
</p>
|
||||
<p>
|
||||
<strong>
|
||||
<a href="https://accords-library.com/data/">Data</a>
|
||||
</strong>
|
||||
: There we will publish lore/knowledge about the Yokoverse: Dictionary,
|
||||
Timeline, Weapons Stories, Game summaries... We have not yet decided how
|
||||
deep we want to go as they are already quite a few resources out there.{" "}
|
||||
</p>
|
||||
<p>
|
||||
<strong>
|
||||
<a
|
||||
href="https://gallery.accords-library.com/posts"
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
>
|
||||
Gallery
|
||||
</a>
|
||||
</strong>
|
||||
: A fully tagged Danbooru-styled gallery with currently more than a
|
||||
thousand unique artworks. If you are unfamiliar with this kind of
|
||||
gallery, it comes with a powerful search function that allows you to
|
||||
search for specific images: want to search for images with both Caim and
|
||||
Inuart, just type{" "}
|
||||
<kbd>
|
||||
<a
|
||||
href="https://gallery.accords-library.com/posts/query=Caim%20Inuart"
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
>
|
||||
Caim Inuart
|
||||
</a>
|
||||
</kbd>
|
||||
. If you want images of Devola OR Popola, you can use a comma{" "}
|
||||
<kbd>
|
||||
<a
|
||||
href="https://gallery.accords-library.com/posts/query=Popola%2CDevola"
|
||||
data-type="URL"
|
||||
data-id="https://gallery.accords-library.com/posts/query=Popola%2CDevola"
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
>
|
||||
Popola,Devola
|
||||
</a>
|
||||
</kbd>
|
||||
. You can also negate a tag: i.e. images of 9S without any pods around,
|
||||
search for{" "}
|
||||
<kbd>
|
||||
<a
|
||||
href="https://gallery.accords-library.com/posts/query=9S%20-Pods"
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
>
|
||||
9S -Pods
|
||||
</a>
|
||||
</kbd>
|
||||
. Anyway, there is a lot more to it, you can click on "Syntax
|
||||
help" next to the Search button for even neater functions. Btw, you
|
||||
can create an account to favorite, upvote/downvote posts, or if you want
|
||||
to help tagging them. There isn’t currently a way for new users to
|
||||
upload images, you’ll have to contact us first and we can decide
|
||||
to enable this function on your account.
|
||||
</p>
|
||||
{locales.includes(router.locale ?? "en") ? (
|
||||
<Markdawn text={post.translations[0].body} />
|
||||
) : (
|
||||
<LanguageSwitcher
|
||||
locales={locales}
|
||||
languages={props.languages}
|
||||
langui={props.langui}
|
||||
/>
|
||||
)}
|
||||
</ContentPanel>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<AppLayout
|
||||
navTitle={"Accord’s Library"}
|
||||
langui={langui}
|
||||
contentPanel={contentPanel}
|
||||
/>
|
||||
</>
|
||||
<AppLayout
|
||||
navTitle={
|
||||
post.translations.length > 0
|
||||
? post.translations[0].title
|
||||
: prettySlug(post.slug)
|
||||
}
|
||||
contentPanel={contentPanel}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export const getStaticProps: GetStaticProps = async (context) => {
|
||||
if (context.locale) {
|
||||
const props: HomeProps = {
|
||||
langui: await getWebsiteInterface({
|
||||
language_code: context.locale,
|
||||
}),
|
||||
};
|
||||
return {
|
||||
props: props,
|
||||
};
|
||||
} else {
|
||||
return { props: {} };
|
||||
}
|
||||
};
|
||||
export async function getStaticProps(
|
||||
context: GetStaticPropsContext
|
||||
): Promise<{ props: HomeProps }> {
|
||||
const slug = "home";
|
||||
const props: HomeProps = {
|
||||
...(await getAppStaticProps(context)),
|
||||
post: (
|
||||
await getPost({
|
||||
slug: slug,
|
||||
language_code: context.locale ?? "en",
|
||||
})
|
||||
).posts.data[0].attributes,
|
||||
};
|
||||
return {
|
||||
props: props,
|
||||
};
|
||||
}
|
||||
|
@ -1,925 +0,0 @@
|
||||
import ContentPanel, {
|
||||
ContentPanelWidthSizes,
|
||||
} from "components/Panels/ContentPanel";
|
||||
import { GetStaticPaths, GetStaticProps } from "next";
|
||||
import {
|
||||
getLibraryItem,
|
||||
getLibraryItemsSlugs,
|
||||
getWebsiteInterface,
|
||||
} from "graphql/operations";
|
||||
import {
|
||||
Enum_Componentmetadatabooks_Binding_Type,
|
||||
Enum_Componentmetadatabooks_Page_Order,
|
||||
GetLibraryItemQuery,
|
||||
GetWebsiteInterfaceQuery,
|
||||
} from "graphql/operations-types";
|
||||
import {
|
||||
convertMmToInch,
|
||||
prettyDate,
|
||||
prettyinlineTitle,
|
||||
prettyItemType,
|
||||
prettyItemSubType,
|
||||
prettyPrice,
|
||||
prettySlug,
|
||||
prettyTestError,
|
||||
prettyTestWarning,
|
||||
sortContent,
|
||||
} from "queries/helpers";
|
||||
import SubPanel from "components/Panels/SubPanel";
|
||||
import ReturnButton from "components/PanelComponents/ReturnButton";
|
||||
import NavOption from "components/PanelComponents/NavOption";
|
||||
import Chip from "components/Chip";
|
||||
import Button from "components/Button";
|
||||
import HorizontalLine from "components/HorizontalLine";
|
||||
import AppLayout from "components/AppLayout";
|
||||
import LibraryItemsPreview from "components/Library/LibraryItemsPreview";
|
||||
import InsetBox from "components/InsetBox";
|
||||
import Img, { ImageQuality } from "components/Img";
|
||||
import { useAppLayout } from "contexts/AppLayoutContext";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
interface LibrarySlugProps {
|
||||
libraryItem: GetLibraryItemQuery;
|
||||
langui: GetWebsiteInterfaceQuery;
|
||||
}
|
||||
|
||||
export default function LibrarySlug(props: LibrarySlugProps): JSX.Element {
|
||||
useTesting(props);
|
||||
const item = props.libraryItem.libraryItems.data[0].attributes;
|
||||
const langui = props.langui.websiteInterfaces.data[0].attributes;
|
||||
const appLayout = useAppLayout();
|
||||
|
||||
const isVariantSet =
|
||||
item.metadata.length > 0 &&
|
||||
item.metadata[0].__typename === "ComponentMetadataOther" &&
|
||||
item.metadata[0].subtype.data.attributes.slug === "variant-set";
|
||||
|
||||
sortContent(item.contents);
|
||||
|
||||
const subPanel = (
|
||||
<SubPanel>
|
||||
<ReturnButton
|
||||
href="/library/"
|
||||
title={langui.main_library}
|
||||
langui={langui}
|
||||
/>
|
||||
<HorizontalLine />
|
||||
|
||||
<div className="grid gap-4">
|
||||
<NavOption
|
||||
title={langui.library_item_summary}
|
||||
url="#summary"
|
||||
border
|
||||
onClick={() => appLayout.setSubPanelOpen(false)}
|
||||
/>
|
||||
|
||||
{item.gallery.data.length > 0 ? (
|
||||
<NavOption
|
||||
title={langui.library_item_gallery}
|
||||
url="#gallery"
|
||||
border
|
||||
onClick={() => appLayout.setSubPanelOpen(false)}
|
||||
/>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
|
||||
<NavOption
|
||||
title={langui.library_item_details}
|
||||
url="#details"
|
||||
border
|
||||
onClick={() => appLayout.setSubPanelOpen(false)}
|
||||
/>
|
||||
|
||||
{item.subitems.data.length > 0 ? (
|
||||
<NavOption
|
||||
title={
|
||||
isVariantSet
|
||||
? langui.library_item_variants
|
||||
: langui.library_item_subitems
|
||||
}
|
||||
url={isVariantSet ? "#variants" : "#subitems"}
|
||||
border
|
||||
onClick={() => appLayout.setSubPanelOpen(false)}
|
||||
/>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
|
||||
{item.contents.data.length > 0 ? (
|
||||
<NavOption
|
||||
title={langui.library_item_content}
|
||||
url="#content"
|
||||
border
|
||||
/>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
</div>
|
||||
</SubPanel>
|
||||
);
|
||||
|
||||
const contentPanel = (
|
||||
<ContentPanel width={ContentPanelWidthSizes.large}>
|
||||
<div className="grid place-items-center gap-12">
|
||||
<div className="drop-shadow-shade-xl w-full h-[50vh] mobile:h-[80vh] mb-16 relative cursor-pointer">
|
||||
{item.thumbnail.data ? (
|
||||
<Img
|
||||
image={item.thumbnail.data.attributes}
|
||||
quality={ImageQuality.Medium}
|
||||
layout="fill"
|
||||
objectFit="contain"
|
||||
priority
|
||||
/>
|
||||
) : (
|
||||
<div className="w-full aspect-[21/29.7] bg-light rounded-xl"></div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<InsetBox id="summary" className="grid place-items-center">
|
||||
<div className="w-[clamp(0px,100%,42rem)] grid place-items-center gap-8">
|
||||
{item.subitem_of.data.length > 0 ? (
|
||||
<div className="grid place-items-center">
|
||||
<p>{langui.global_subitem_of}</p>
|
||||
<Button
|
||||
href={`/library/${item.subitem_of.data[0].attributes.slug}`}
|
||||
>
|
||||
{prettyinlineTitle(
|
||||
"",
|
||||
item.subitem_of.data[0].attributes.title,
|
||||
item.subitem_of.data[0].attributes.subtitle
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
<div className="grid place-items-center">
|
||||
<h1 className="text-3xl">{item.title}</h1>
|
||||
{item.subtitle ? (
|
||||
<h2 className="text-2xl">{item.subtitle}</h2>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
</div>
|
||||
{item.descriptions.length > 0 ? (
|
||||
<p className="text-justify">{item.descriptions[0].description}</p>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
</div>
|
||||
</InsetBox>
|
||||
|
||||
{item.gallery.data.length > 0 ? (
|
||||
<div id="gallery" className="grid place-items-center gap-8 w-full">
|
||||
<h2 className="text-2xl">{langui.library_item_gallery}</h2>
|
||||
<div className="grid w-full gap-8 items-end grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))]">
|
||||
{item.gallery.data.map((galleryItem) => (
|
||||
<div
|
||||
key={galleryItem.id}
|
||||
className="relative aspect-square hover:scale-[1.02] transition-transform cursor-pointer"
|
||||
>
|
||||
<div className="bg-light absolute inset-0 rounded-lg drop-shadow-shade-md"></div>
|
||||
<Img
|
||||
className="rounded-lg"
|
||||
image={galleryItem.attributes}
|
||||
layout="fill"
|
||||
objectFit="cover"
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
|
||||
<InsetBox id="details" className="grid place-items-center">
|
||||
<div className="w-[clamp(0px,100%,42rem)] grid place-items gap-8">
|
||||
<h2 className="text-2xl text-center">
|
||||
{langui.library_item_details}
|
||||
</h2>
|
||||
<div className="grid grid-flow-col w-full place-content-between">
|
||||
{item.metadata.length > 0 ? (
|
||||
<div className="grid place-items-center">
|
||||
<h3 className="text-xl">{langui.global_type}</h3>
|
||||
<div className="grid grid-flow-col gap-1">
|
||||
<Chip>{prettyItemType(item.metadata[0], langui)}</Chip>
|
||||
{"›"}
|
||||
<Chip>{prettyItemSubType(item.metadata[0])}</Chip>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
|
||||
{item.release_date ? (
|
||||
<div className="grid place-items-center">
|
||||
<h3 className="text-xl">{langui.global_release_date}</h3>
|
||||
<p>{prettyDate(item.release_date)}</p>
|
||||
</div>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
|
||||
{item.price ? (
|
||||
<div className="grid place-items-center">
|
||||
<h3 className="text-xl">{langui.global_price}</h3>
|
||||
<p>{prettyPrice(item.price)}</p>
|
||||
</div>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
</div>
|
||||
{item.size ? (
|
||||
<>
|
||||
<h3 className="text-xl">{langui.library_item_physical_size}</h3>
|
||||
<div className="grid grid-flow-col w-full place-content-between">
|
||||
<div className="flex flex-row flex-wrap place-items-start gap-4">
|
||||
<p className="font-bold">{langui.global_width}:</p>
|
||||
<div>
|
||||
<p>{item.size.width} mm</p>
|
||||
<p>{convertMmToInch(item.size.width)} in</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-row flex-wrap place-items-start gap-4">
|
||||
<p className="font-bold">{langui.global_height}:</p>
|
||||
<div>
|
||||
<p>{item.size.height} mm</p>
|
||||
<p>{convertMmToInch(item.size.height)} in</p>
|
||||
</div>
|
||||
</div>
|
||||
{item.size.thickness ? (
|
||||
<div className="flex flex-row flex-wrap place-items-start gap-4">
|
||||
<p className="font-bold">{langui.global_thickness}:</p>
|
||||
<div>
|
||||
<p>{item.size.thickness} mm</p>
|
||||
<p>{convertMmToInch(item.size.thickness)} in</p>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
|
||||
{item.metadata.length > 0 ? (
|
||||
<>
|
||||
<h3 className="text-xl">
|
||||
{langui.library_item_type_information}
|
||||
</h3>
|
||||
<div className="grid grid-cols-2 w-full place-content-between">
|
||||
{item.metadata[0].__typename === "ComponentMetadataBooks" ? (
|
||||
<>
|
||||
<div className="flex flex-row place-content-start gap-4">
|
||||
<p className="font-bold">{langui.global_pages}:</p>
|
||||
<p>{item.metadata[0].page_count}</p>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-row place-content-start gap-4">
|
||||
<p className="font-bold">{langui.global_binding}:</p>
|
||||
<p>
|
||||
{item.metadata[0].binding_type ===
|
||||
Enum_Componentmetadatabooks_Binding_Type.Paperback
|
||||
? langui.global_paperback
|
||||
: item.metadata[0].binding_type ===
|
||||
Enum_Componentmetadatabooks_Binding_Type.Hardcover
|
||||
? langui.global_hardcover
|
||||
: ""}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-row place-content-start gap-4">
|
||||
<p className="font-bold">{langui.global_page_order}:</p>
|
||||
<p>
|
||||
{item.metadata[0].page_order ===
|
||||
Enum_Componentmetadatabooks_Page_Order.LeftToRight
|
||||
? langui.global_left_to_right
|
||||
: item.metadata[0].page_order ===
|
||||
Enum_Componentmetadatabooks_Page_Order.RightToLeft
|
||||
? langui.global_right_to_left
|
||||
: ""}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-row place-content-start gap-4">
|
||||
<p className="font-bold">{langui.global_languages}:</p>
|
||||
{item.metadata[0].languages.data.map((lang) => (
|
||||
<p key={lang.attributes.code}>
|
||||
{lang.attributes.name}
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
) : item.metadata[0].__typename ===
|
||||
"ComponentMetadataAudio" ? (
|
||||
<></>
|
||||
) : item.metadata[0].__typename ===
|
||||
"ComponentMetadataVideo" ? (
|
||||
<></>
|
||||
) : item.metadata[0].__typename ===
|
||||
"ComponentMetadataGame" ? (
|
||||
<></>
|
||||
) : item.metadata[0].__typename ===
|
||||
"ComponentMetadataOther" ? (
|
||||
<>
|
||||
<div className="flex flex-row place-content-start gap-4">
|
||||
<p className="font-bold">{langui.global_type}:</p>
|
||||
<Chip>
|
||||
{item.metadata[0].subtype.data.attributes.titles
|
||||
.length > 0
|
||||
? item.metadata[0].subtype.data.attributes.titles[0]
|
||||
.title
|
||||
: prettySlug(
|
||||
item.metadata[0].subtype.data.attributes.slug
|
||||
)}
|
||||
</Chip>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
</div>
|
||||
</InsetBox>
|
||||
|
||||
{item.subitems.data.length > 0 ? (
|
||||
<div
|
||||
id={isVariantSet ? "variants" : "subitems"}
|
||||
className="grid place-items-center gap-8 w-full"
|
||||
>
|
||||
<h2 className="text-2xl">
|
||||
{isVariantSet
|
||||
? langui.library_item_variants
|
||||
: langui.library_item_subitems}
|
||||
</h2>
|
||||
<div className="grid gap-8 items-end mobile:grid-cols-2 grid-cols-[repeat(auto-fill,minmax(15rem,1fr))] w-full">
|
||||
{item.subitems.data.map((subitem) => (
|
||||
<LibraryItemsPreview
|
||||
key={subitem.id}
|
||||
item={subitem.attributes}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
|
||||
{item.contents.data.length > 0 ? (
|
||||
<div id="content" className="w-full grid place-items-center gap-8">
|
||||
<h2 className="text-2xl">{langui.library_item_content}</h2>
|
||||
<div className="grid gap-4 w-full">
|
||||
{item.contents.data.map((content) => (
|
||||
<div
|
||||
id={content.attributes.slug}
|
||||
key={content.id}
|
||||
className="grid gap-2 px-4 rounded-lg target:bg-mid target:shadow-inner-sm target:shadow-shade target:h-auto target:py-3 target:my-2 target:[--displaySubContentMenu:grid] [--displaySubContentMenu:none]"
|
||||
>
|
||||
<div className="grid gap-4 place-items-center grid-cols-[auto_auto_1fr_auto_12ch] thin:grid-cols-[auto_auto_1fr_auto]">
|
||||
<a href={`#${content.attributes.slug}`}>
|
||||
<h3>
|
||||
{content.attributes.content.data &&
|
||||
content.attributes.content.data.attributes.titles
|
||||
.length > 0
|
||||
? prettyinlineTitle(
|
||||
content.attributes.content.data.attributes
|
||||
.titles[0].pre_title,
|
||||
content.attributes.content.data.attributes
|
||||
.titles[0].title,
|
||||
content.attributes.content.data.attributes
|
||||
.titles[0].subtitle
|
||||
)
|
||||
: prettySlug(content.attributes.slug, item.slug)}
|
||||
</h3>
|
||||
</a>
|
||||
<div className="flex flex-row flex-wrap gap-1">
|
||||
{content.attributes.content.data?.attributes.categories.data.map(
|
||||
(category) => (
|
||||
<Chip key={category.id}>
|
||||
{category.attributes.short}
|
||||
</Chip>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
<p className="border-b-2 h-4 w-full border-black border-dotted opacity-30"></p>
|
||||
<p>
|
||||
{content.attributes.range[0].__typename ===
|
||||
"ComponentRangePageRange"
|
||||
? content.attributes.range[0].starting_page
|
||||
: ""}
|
||||
</p>
|
||||
{content.attributes.content.data ? (
|
||||
<Chip className="justify-self-end thin:hidden">
|
||||
{content.attributes.content.data.attributes.type.data
|
||||
.attributes.titles.length > 0
|
||||
? content.attributes.content.data.attributes.type.data
|
||||
.attributes.titles[0].title
|
||||
: prettySlug(
|
||||
content.attributes.content.data.attributes.type
|
||||
.data.attributes.slug
|
||||
)}
|
||||
</Chip>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
</div>
|
||||
<div className="grid-flow-col place-content-start place-items-center gap-2 [display:var(--displaySubContentMenu)]">
|
||||
<span className="material-icons text-dark">
|
||||
subdirectory_arrow_right
|
||||
</span>
|
||||
|
||||
{content.attributes.scan_set.length > 0 ? (
|
||||
<Button
|
||||
href={`/contents/${content.attributes.content.data.attributes.slug}/scans/`}
|
||||
>
|
||||
{langui.library_item_view_scans}
|
||||
</Button>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
|
||||
{content.attributes.content.data ? (
|
||||
<Button
|
||||
href={`/contents/${content.attributes.content.data.attributes.slug}`}
|
||||
>
|
||||
{langui.library_item_open_content}
|
||||
</Button>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
|
||||
{content.attributes.scan_set.length === 0 &&
|
||||
!content.attributes.content.data
|
||||
? "The content is not available"
|
||||
: ""}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
</div>
|
||||
</ContentPanel>
|
||||
);
|
||||
|
||||
return (
|
||||
<AppLayout
|
||||
navTitle={langui.main_library}
|
||||
title={prettyinlineTitle("", item.title, item.subtitle)}
|
||||
langui={langui}
|
||||
contentPanel={contentPanel}
|
||||
subPanel={subPanel}
|
||||
thumbnail={item.thumbnail.data?.attributes}
|
||||
description={
|
||||
item.descriptions.length > 0
|
||||
? item.descriptions[0].description
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export const getStaticProps: GetStaticProps = async (context) => {
|
||||
if (context.params) {
|
||||
if (context.params.slug && context.locale) {
|
||||
if (context.params.slug instanceof Array)
|
||||
context.params.slug = context.params.slug.join("");
|
||||
|
||||
const props: LibrarySlugProps = {
|
||||
libraryItem: await getLibraryItem({
|
||||
slug: context.params.slug,
|
||||
language_code: context.locale,
|
||||
}),
|
||||
langui: await getWebsiteInterface({
|
||||
language_code: context.locale,
|
||||
}),
|
||||
};
|
||||
return {
|
||||
props: props,
|
||||
};
|
||||
}
|
||||
}
|
||||
return { props: {} };
|
||||
};
|
||||
|
||||
export const getStaticPaths: GetStaticPaths = async (context) => {
|
||||
type Path = {
|
||||
params: {
|
||||
slug: string;
|
||||
};
|
||||
locale: string;
|
||||
};
|
||||
|
||||
const data = await getLibraryItemsSlugs({});
|
||||
const paths: Path[] = [];
|
||||
data.libraryItems.data.map((item) => {
|
||||
context.locales?.map((local) => {
|
||||
paths.push({ params: { slug: item.attributes.slug }, locale: local });
|
||||
});
|
||||
});
|
||||
return {
|
||||
paths,
|
||||
fallback: false,
|
||||
};
|
||||
};
|
||||
|
||||
function useTesting(props: LibrarySlugProps) {
|
||||
const libraryItem = props.libraryItem.libraryItems.data[0].attributes;
|
||||
const router = useRouter();
|
||||
|
||||
const libraryItemURL =
|
||||
"/admin/content-manager/collectionType/api::library-item.library-item/" +
|
||||
props.libraryItem.libraryItems.data[0].id;
|
||||
|
||||
sortContent(libraryItem.contents);
|
||||
|
||||
if (router.locale === "en") {
|
||||
if (!libraryItem.thumbnail.data) {
|
||||
prettyTestError(
|
||||
router,
|
||||
"Missing thumbnail",
|
||||
["libraryItem"],
|
||||
libraryItemURL
|
||||
);
|
||||
}
|
||||
if (libraryItem.metadata.length === 0) {
|
||||
prettyTestError(
|
||||
router,
|
||||
"Missing metadata",
|
||||
["libraryItem"],
|
||||
libraryItemURL
|
||||
);
|
||||
} else {
|
||||
if (
|
||||
libraryItem.metadata[0].__typename === "ComponentMetadataOther" &&
|
||||
(libraryItem.metadata[0].subtype.data.attributes.slug ===
|
||||
"relation-set" ||
|
||||
libraryItem.metadata[0].subtype.data.attributes.slug ===
|
||||
"variant-set")
|
||||
) {
|
||||
// This is a group type item
|
||||
if (libraryItem.price) {
|
||||
prettyTestError(
|
||||
router,
|
||||
"Group-type items shouldn't have price",
|
||||
["libraryItem"],
|
||||
libraryItemURL
|
||||
);
|
||||
}
|
||||
if (libraryItem.size) {
|
||||
prettyTestError(
|
||||
router,
|
||||
"Group-type items shouldn't have size",
|
||||
["libraryItem"],
|
||||
libraryItemURL
|
||||
);
|
||||
}
|
||||
if (libraryItem.release_date) {
|
||||
prettyTestError(
|
||||
router,
|
||||
"Group-type items shouldn't have release_date",
|
||||
["libraryItem"],
|
||||
libraryItemURL
|
||||
);
|
||||
}
|
||||
if (libraryItem.contents.data.length > 0) {
|
||||
prettyTestError(
|
||||
router,
|
||||
"Group-type items shouldn't have contents",
|
||||
["libraryItem"],
|
||||
libraryItemURL
|
||||
);
|
||||
}
|
||||
if (libraryItem.subitems.data.length === 0) {
|
||||
prettyTestError(
|
||||
router,
|
||||
"Group-type items should have subitems",
|
||||
["libraryItem"],
|
||||
libraryItemURL
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// This is a normal item
|
||||
|
||||
if (libraryItem.metadata[0].__typename === "ComponentMetadataOther") {
|
||||
if (
|
||||
libraryItem.metadata[0].subtype.data.attributes.slug ===
|
||||
"audio-case"
|
||||
) {
|
||||
let hasAudioSubItem = false;
|
||||
libraryItem.subitems.data.map((subitem) => {
|
||||
if (
|
||||
subitem.attributes.metadata.length > 0 &&
|
||||
subitem.attributes.metadata[0].__typename ===
|
||||
"ComponentMetadataAudio"
|
||||
)
|
||||
hasAudioSubItem = true;
|
||||
});
|
||||
if (!hasAudioSubItem) {
|
||||
prettyTestError(
|
||||
router,
|
||||
"Audio-case item doesn't have an audio-typed subitem",
|
||||
["libraryItem"],
|
||||
libraryItemURL
|
||||
);
|
||||
}
|
||||
} else if (
|
||||
libraryItem.metadata[0].subtype.data.attributes.slug === "game-case"
|
||||
) {
|
||||
let hasGameSubItem = false;
|
||||
libraryItem.subitems.data.map((subitem) => {
|
||||
if (
|
||||
subitem.attributes.metadata.length > 0 &&
|
||||
subitem.attributes.metadata[0].__typename ===
|
||||
"ComponentMetadataGame"
|
||||
)
|
||||
hasGameSubItem = true;
|
||||
});
|
||||
if (!hasGameSubItem) {
|
||||
prettyTestError(
|
||||
router,
|
||||
"Game-case item doesn't have an Game-typed subitem",
|
||||
["libraryItem"],
|
||||
libraryItemURL
|
||||
);
|
||||
}
|
||||
} else if (
|
||||
libraryItem.metadata[0].subtype.data.attributes.slug ===
|
||||
"video-case"
|
||||
) {
|
||||
let hasVideoSubItem = false;
|
||||
libraryItem.subitems.data.map((subitem) => {
|
||||
if (
|
||||
subitem.attributes.metadata.length > 0 &&
|
||||
subitem.attributes.metadata[0].__typename ===
|
||||
"ComponentMetadataVideo"
|
||||
)
|
||||
hasVideoSubItem = true;
|
||||
});
|
||||
if (!hasVideoSubItem) {
|
||||
prettyTestError(
|
||||
router,
|
||||
"Video-case item doesn't have an Video-typed subitem",
|
||||
["libraryItem"],
|
||||
libraryItemURL
|
||||
);
|
||||
}
|
||||
} else if (
|
||||
libraryItem.metadata[0].subtype.data.attributes.slug === "item-set"
|
||||
) {
|
||||
if (libraryItem.subitems.data.length === 0) {
|
||||
prettyTestError(
|
||||
router,
|
||||
"Item-set item should have subitems",
|
||||
["libraryItem"],
|
||||
libraryItemURL
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!libraryItem.price) {
|
||||
prettyTestWarning(
|
||||
router,
|
||||
"Missing price",
|
||||
["libraryItem"],
|
||||
libraryItemURL
|
||||
);
|
||||
} else {
|
||||
if (!libraryItem.price.amount) {
|
||||
prettyTestError(
|
||||
router,
|
||||
"Missing amount",
|
||||
["libraryItem", "price"],
|
||||
libraryItemURL
|
||||
);
|
||||
}
|
||||
if (!libraryItem.price.currency) {
|
||||
prettyTestError(
|
||||
router,
|
||||
"Missing currency",
|
||||
["libraryItem", "price"],
|
||||
libraryItemURL
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!libraryItem.digital) {
|
||||
if (!libraryItem.size) {
|
||||
prettyTestWarning(
|
||||
router,
|
||||
"Missing size",
|
||||
["libraryItem"],
|
||||
libraryItemURL
|
||||
);
|
||||
} else {
|
||||
if (!libraryItem.size.width) {
|
||||
prettyTestWarning(
|
||||
router,
|
||||
"Missing width",
|
||||
["libraryItem", "size"],
|
||||
libraryItemURL
|
||||
);
|
||||
}
|
||||
if (!libraryItem.size.height) {
|
||||
prettyTestWarning(
|
||||
router,
|
||||
"Missing height",
|
||||
["libraryItem", "size"],
|
||||
libraryItemURL
|
||||
);
|
||||
}
|
||||
if (!libraryItem.size.thickness) {
|
||||
prettyTestWarning(
|
||||
router,
|
||||
"Missing thickness",
|
||||
["libraryItem", "size"],
|
||||
libraryItemURL
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!libraryItem.release_date) {
|
||||
prettyTestWarning(
|
||||
router,
|
||||
"Missing release_date",
|
||||
["libraryItem"],
|
||||
libraryItemURL
|
||||
);
|
||||
} else {
|
||||
if (!libraryItem.release_date.year) {
|
||||
prettyTestError(
|
||||
router,
|
||||
"Missing year",
|
||||
["libraryItem", "release_date"],
|
||||
libraryItemURL
|
||||
);
|
||||
}
|
||||
if (!libraryItem.release_date.month) {
|
||||
prettyTestError(
|
||||
router,
|
||||
"Missing month",
|
||||
["libraryItem", "release_date"],
|
||||
libraryItemURL
|
||||
);
|
||||
}
|
||||
if (!libraryItem.release_date.day) {
|
||||
prettyTestError(
|
||||
router,
|
||||
"Missing day",
|
||||
["libraryItem", "release_date"],
|
||||
libraryItemURL
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (libraryItem.contents.data.length === 0) {
|
||||
prettyTestWarning(
|
||||
router,
|
||||
"Missing contents",
|
||||
["libraryItem"],
|
||||
libraryItemURL
|
||||
);
|
||||
} else {
|
||||
let currentRangePage = 0;
|
||||
libraryItem.contents.data.map((content) => {
|
||||
const contentURL =
|
||||
"/admin/content-manager/collectionType/api::content.content/" +
|
||||
content.id;
|
||||
|
||||
if (content.attributes.scan_set.length === 0) {
|
||||
prettyTestWarning(
|
||||
router,
|
||||
"Missing scan_set",
|
||||
["libraryItem", "content", content.id],
|
||||
contentURL
|
||||
);
|
||||
}
|
||||
if (content.attributes.range.length === 0) {
|
||||
prettyTestWarning(
|
||||
router,
|
||||
"Missing range",
|
||||
["libraryItem", "content", content.id],
|
||||
contentURL
|
||||
);
|
||||
} else if (
|
||||
content.attributes.range[0].__typename ===
|
||||
"ComponentRangePageRange"
|
||||
) {
|
||||
if (
|
||||
content.attributes.range[0].starting_page <
|
||||
currentRangePage + 1
|
||||
) {
|
||||
prettyTestError(
|
||||
router,
|
||||
`Overlapping pages ${content.attributes.range[0].starting_page} to ${currentRangePage}`,
|
||||
["libraryItem", "content", content.id, "range"],
|
||||
libraryItemURL
|
||||
);
|
||||
} else if (
|
||||
content.attributes.range[0].starting_page >
|
||||
currentRangePage + 1
|
||||
) {
|
||||
prettyTestError(
|
||||
router,
|
||||
`Missing pages ${currentRangePage + 1} to ${
|
||||
content.attributes.range[0].starting_page - 1
|
||||
}`,
|
||||
["libraryItem", "content", content.id, "range"],
|
||||
libraryItemURL
|
||||
);
|
||||
}
|
||||
|
||||
if (!content.attributes.content.data) {
|
||||
prettyTestWarning(
|
||||
router,
|
||||
"Missing content",
|
||||
["libraryItem", "content", content.id, "range"],
|
||||
libraryItemURL
|
||||
);
|
||||
}
|
||||
|
||||
currentRangePage = content.attributes.range[0].ending_page;
|
||||
}
|
||||
});
|
||||
|
||||
if (libraryItem.metadata[0].__typename === "ComponentMetadataBooks") {
|
||||
if (currentRangePage < libraryItem.metadata[0].page_count) {
|
||||
prettyTestError(
|
||||
router,
|
||||
`Missing pages ${currentRangePage + 1} to ${
|
||||
libraryItem.metadata[0].page_count
|
||||
}`,
|
||||
["libraryItem", "content"],
|
||||
libraryItemURL
|
||||
);
|
||||
} else if (currentRangePage > libraryItem.metadata[0].page_count) {
|
||||
prettyTestError(
|
||||
router,
|
||||
`Page overflow, content references pages up to ${currentRangePage} when the highest expected was ${libraryItem.metadata[0].page_count}`,
|
||||
["libraryItem", "content"],
|
||||
libraryItemURL
|
||||
);
|
||||
}
|
||||
|
||||
if (libraryItem.metadata[0].languages.data.length === 0) {
|
||||
prettyTestWarning(
|
||||
router,
|
||||
"Missing language",
|
||||
["libraryItem", "metadata"],
|
||||
libraryItemURL
|
||||
);
|
||||
}
|
||||
|
||||
if (!libraryItem.metadata[0].page_count) {
|
||||
prettyTestWarning(
|
||||
router,
|
||||
"Missing page_count",
|
||||
["libraryItem", "metadata"],
|
||||
libraryItemURL
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!libraryItem.root_item && libraryItem.subitem_of.data.length === 0) {
|
||||
prettyTestError(
|
||||
router,
|
||||
"This item is inaccessible (not root item and not subitem of another item)",
|
||||
["libraryItem"],
|
||||
libraryItemURL
|
||||
);
|
||||
}
|
||||
|
||||
if (libraryItem.gallery.data.length === 0) {
|
||||
prettyTestWarning(
|
||||
router,
|
||||
"Missing gallery",
|
||||
["libraryItem"],
|
||||
libraryItemURL
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (libraryItem.descriptions.length === 0) {
|
||||
prettyTestWarning(
|
||||
router,
|
||||
"Missing description",
|
||||
["libraryItem"],
|
||||
libraryItemURL
|
||||
);
|
||||
}
|
||||
}
|
766
src/pages/library/[slug]/index.tsx
Normal file
766
src/pages/library/[slug]/index.tsx
Normal file
@ -0,0 +1,766 @@
|
||||
import AppLayout from "components/AppLayout";
|
||||
import Button from "components/Button";
|
||||
import Chip from "components/Chip";
|
||||
import Img, { getAssetURL, ImageQuality } from "components/Img";
|
||||
import InsetBox from "components/InsetBox";
|
||||
import ContentTOCLine from "components/Library/ContentTOCLine";
|
||||
import LibraryItemsPreview from "components/Library/LibraryItemsPreview";
|
||||
import LightBox from "components/LightBox";
|
||||
import NavOption from "components/PanelComponents/NavOption";
|
||||
import ReturnButton, {
|
||||
ReturnButtonType,
|
||||
} from "components/PanelComponents/ReturnButton";
|
||||
import ContentPanel, {
|
||||
ContentPanelWidthSizes,
|
||||
} from "components/Panels/ContentPanel";
|
||||
import SubPanel from "components/Panels/SubPanel";
|
||||
import { useAppLayout } from "contexts/AppLayoutContext";
|
||||
import { getLibraryItem, getLibraryItemsSlugs } from "graphql/operations";
|
||||
import {
|
||||
Enum_Componentmetadatabooks_Binding_Type,
|
||||
Enum_Componentmetadatabooks_Page_Order,
|
||||
GetLibraryItemQuery,
|
||||
} from "graphql/operations-types";
|
||||
import {
|
||||
GetStaticPathsContext,
|
||||
GetStaticPathsResult,
|
||||
GetStaticPropsContext,
|
||||
} from "next";
|
||||
import { useRouter } from "next/router";
|
||||
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
||||
import {
|
||||
convertMmToInch,
|
||||
prettyDate,
|
||||
prettyinlineTitle,
|
||||
prettyItemSubType,
|
||||
prettyItemType,
|
||||
prettyPrice,
|
||||
prettyTestError,
|
||||
prettyTestWarning,
|
||||
sortContent,
|
||||
} from "queries/helpers";
|
||||
import { useState } from "react";
|
||||
|
||||
interface LibrarySlugProps extends AppStaticProps {
|
||||
item: GetLibraryItemQuery["libraryItems"]["data"][number]["attributes"];
|
||||
itemId: GetLibraryItemQuery["libraryItems"]["data"][number]["id"];
|
||||
}
|
||||
|
||||
export default function LibrarySlug(props: LibrarySlugProps): JSX.Element {
|
||||
useTesting(props);
|
||||
const { item, langui, currencies } = props;
|
||||
const appLayout = useAppLayout();
|
||||
|
||||
const isVariantSet =
|
||||
item.metadata.length > 0 &&
|
||||
item.metadata[0].__typename === "ComponentMetadataGroup" &&
|
||||
item.metadata[0].subtype.data.attributes.slug === "variant-set";
|
||||
|
||||
sortContent(item.contents);
|
||||
|
||||
const [lightboxOpen, setLightboxOpen] = useState(false);
|
||||
const [lightboxImages, setLightboxImages] = useState([""]);
|
||||
const [lightboxIndex, setLightboxIndex] = useState(0);
|
||||
|
||||
const subPanel = (
|
||||
<SubPanel>
|
||||
<ReturnButton
|
||||
href="/library/"
|
||||
title={langui.library}
|
||||
langui={langui}
|
||||
displayOn={ReturnButtonType.desktop}
|
||||
horizontalLine
|
||||
/>
|
||||
|
||||
<div className="grid gap-4">
|
||||
<NavOption
|
||||
title={langui.summary}
|
||||
url="#summary"
|
||||
border
|
||||
onClick={() => appLayout.setSubPanelOpen(false)}
|
||||
/>
|
||||
|
||||
{item.gallery.data.length > 0 && (
|
||||
<NavOption
|
||||
title={langui.gallery}
|
||||
url="#gallery"
|
||||
border
|
||||
onClick={() => appLayout.setSubPanelOpen(false)}
|
||||
/>
|
||||
)}
|
||||
|
||||
<NavOption
|
||||
title={langui.details}
|
||||
url="#details"
|
||||
border
|
||||
onClick={() => appLayout.setSubPanelOpen(false)}
|
||||
/>
|
||||
|
||||
{item.subitems.data.length > 0 && (
|
||||
<NavOption
|
||||
title={isVariantSet ? langui.variants : langui.subitems}
|
||||
url={isVariantSet ? "#variants" : "#subitems"}
|
||||
border
|
||||
onClick={() => appLayout.setSubPanelOpen(false)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{item.contents.data.length > 0 && (
|
||||
<NavOption
|
||||
title={langui.contents}
|
||||
url="#contents"
|
||||
border
|
||||
onClick={() => appLayout.setSubPanelOpen(false)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</SubPanel>
|
||||
);
|
||||
|
||||
const contentPanel = (
|
||||
<ContentPanel width={ContentPanelWidthSizes.large}>
|
||||
<LightBox
|
||||
state={lightboxOpen}
|
||||
setState={setLightboxOpen}
|
||||
images={lightboxImages}
|
||||
index={lightboxIndex}
|
||||
setIndex={setLightboxIndex}
|
||||
/>
|
||||
|
||||
<ReturnButton
|
||||
href="/library/"
|
||||
title={langui.library}
|
||||
langui={langui}
|
||||
displayOn={ReturnButtonType.mobile}
|
||||
className="mb-10"
|
||||
/>
|
||||
<div className="grid place-items-center gap-12">
|
||||
<div
|
||||
className="drop-shadow-shade-xl w-full h-[50vh] mobile:h-[60vh] desktop:mb-16 relative cursor-pointer"
|
||||
onClick={() => {
|
||||
setLightboxOpen(true);
|
||||
setLightboxImages([
|
||||
getAssetURL(
|
||||
item.thumbnail.data.attributes.url,
|
||||
ImageQuality.Large
|
||||
),
|
||||
]);
|
||||
setLightboxIndex(0);
|
||||
}}
|
||||
>
|
||||
{item.thumbnail.data ? (
|
||||
<Img
|
||||
image={item.thumbnail.data.attributes}
|
||||
quality={ImageQuality.Large}
|
||||
layout="fill"
|
||||
objectFit="contain"
|
||||
priority
|
||||
/>
|
||||
) : (
|
||||
<div className="w-full aspect-[21/29.7] bg-light rounded-xl"></div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<InsetBox id="summary" className="grid place-items-center">
|
||||
<div className="w-[clamp(0px,100%,42rem)] grid place-items-center gap-8">
|
||||
{item.subitem_of.data.length > 0 && (
|
||||
<div className="grid place-items-center">
|
||||
<p>{langui.subitem_of}</p>
|
||||
<Button
|
||||
href={`/library/${item.subitem_of.data[0].attributes.slug}`}
|
||||
>
|
||||
{prettyinlineTitle(
|
||||
"",
|
||||
item.subitem_of.data[0].attributes.title,
|
||||
item.subitem_of.data[0].attributes.subtitle
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
<div className="grid place-items-center">
|
||||
<h1 className="text-3xl">{item.title}</h1>
|
||||
{item.subtitle && <h2 className="text-2xl">{item.subtitle}</h2>}
|
||||
</div>
|
||||
{item.descriptions.length > 0 && (
|
||||
<p className="text-justify">{item.descriptions[0].description}</p>
|
||||
)}
|
||||
</div>
|
||||
</InsetBox>
|
||||
|
||||
{item.gallery.data.length > 0 && (
|
||||
<div id="gallery" className="grid place-items-center gap-8 w-full">
|
||||
<h2 className="text-2xl">{langui.gallery}</h2>
|
||||
<div className="grid w-full gap-8 items-end grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))]">
|
||||
{item.gallery.data.map((galleryItem, index) => (
|
||||
<div
|
||||
key={galleryItem.id}
|
||||
className="relative aspect-square hover:scale-[1.02] transition-transform cursor-pointer"
|
||||
onClick={() => {
|
||||
setLightboxOpen(true);
|
||||
setLightboxImages(
|
||||
item.gallery.data.map((image) =>
|
||||
getAssetURL(image.attributes.url, ImageQuality.Large)
|
||||
)
|
||||
);
|
||||
setLightboxIndex(index);
|
||||
}}
|
||||
>
|
||||
<div className="bg-light absolute inset-0 rounded-lg drop-shadow-shade-md"></div>
|
||||
<Img
|
||||
className="rounded-lg"
|
||||
image={galleryItem.attributes}
|
||||
layout="fill"
|
||||
objectFit="cover"
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<InsetBox id="details" className="grid place-items-center">
|
||||
<div className="w-[clamp(0px,100%,42rem)] grid place-items gap-8">
|
||||
<h2 className="text-2xl text-center">{langui.details}</h2>
|
||||
<div className="grid grid-flow-col w-full place-content-between">
|
||||
{item.metadata.length > 0 && (
|
||||
<div className="grid place-items-center place-content-start">
|
||||
<h3 className="text-xl">{langui.type}</h3>
|
||||
<div className="grid grid-flow-col gap-1">
|
||||
<Chip>{prettyItemType(item.metadata[0], langui)}</Chip>
|
||||
{"›"}
|
||||
<Chip>{prettyItemSubType(item.metadata[0])}</Chip>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{item.release_date && (
|
||||
<div className="grid place-items-center place-content-start">
|
||||
<h3 className="text-xl">{langui.release_date}</h3>
|
||||
<p>{prettyDate(item.release_date)}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{item.price && (
|
||||
<div className="grid place-items-center text-center place-content-start">
|
||||
<h3 className="text-xl">{langui.price}</h3>
|
||||
<p>
|
||||
{prettyPrice(
|
||||
item.price,
|
||||
currencies,
|
||||
item.price.currency.data.attributes.code
|
||||
)}
|
||||
</p>
|
||||
{item.price.currency.data.attributes.code !==
|
||||
appLayout.currency && (
|
||||
<p>
|
||||
{prettyPrice(item.price, currencies, appLayout.currency)}{" "}
|
||||
<br />({langui.calculated?.toLowerCase()})
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{item.categories.data.length > 0 && (
|
||||
<div className="flex flex-col place-items-center gap-2">
|
||||
<h3 className="text-xl">{langui.categories}</h3>
|
||||
<div className="flex flex-row flex-wrap place-content-center gap-2">
|
||||
{item.categories.data.map((category) => (
|
||||
<Chip key={category.id}>{category.attributes.name}</Chip>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{item.size && (
|
||||
<>
|
||||
<h3 className="text-xl">{langui.size}</h3>
|
||||
<div className="grid grid-flow-col w-full place-content-between">
|
||||
<div className="flex flex-row flex-wrap place-items-start gap-4">
|
||||
<p className="font-bold">{langui.width}:</p>
|
||||
<div>
|
||||
<p>{item.size.width} mm</p>
|
||||
<p>{convertMmToInch(item.size.width)} in</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-row flex-wrap place-items-start gap-4">
|
||||
<p className="font-bold">{langui.height}:</p>
|
||||
<div>
|
||||
<p>{item.size.height} mm</p>
|
||||
<p>{convertMmToInch(item.size.height)} in</p>
|
||||
</div>
|
||||
</div>
|
||||
{item.size.thickness && (
|
||||
<div className="flex flex-row flex-wrap place-items-start gap-4">
|
||||
<p className="font-bold">{langui.thickness}:</p>
|
||||
<div>
|
||||
<p>{item.size.thickness} mm</p>
|
||||
<p>{convertMmToInch(item.size.thickness)} in</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{item.metadata.length > 0 &&
|
||||
item.metadata[0].__typename !== "ComponentMetadataGroup" &&
|
||||
item.metadata[0].__typename !== "ComponentMetadataOther" && (
|
||||
<>
|
||||
<h3 className="text-xl">{langui.type_information}</h3>
|
||||
<div className="grid grid-cols-2 w-full place-content-between">
|
||||
{item.metadata[0].__typename ===
|
||||
"ComponentMetadataBooks" && (
|
||||
<>
|
||||
<div className="flex flex-row place-content-start gap-4">
|
||||
<p className="font-bold">{langui.pages}:</p>
|
||||
<p>{item.metadata[0].page_count}</p>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-row place-content-start gap-4">
|
||||
<p className="font-bold">{langui.binding}:</p>
|
||||
<p>
|
||||
{item.metadata[0].binding_type ===
|
||||
Enum_Componentmetadatabooks_Binding_Type.Paperback
|
||||
? langui.paperback
|
||||
: item.metadata[0].binding_type ===
|
||||
Enum_Componentmetadatabooks_Binding_Type.Hardcover
|
||||
? langui.hardcover
|
||||
: ""}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-row place-content-start gap-4">
|
||||
<p className="font-bold">{langui.page_order}:</p>
|
||||
<p>
|
||||
{item.metadata[0].page_order ===
|
||||
Enum_Componentmetadatabooks_Page_Order.LeftToRight
|
||||
? langui.left_to_right
|
||||
: item.metadata[0].page_order ===
|
||||
Enum_Componentmetadatabooks_Page_Order.RightToLeft
|
||||
? langui.right_to_left
|
||||
: ""}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-row place-content-start gap-4">
|
||||
<p className="font-bold">{langui.languages}:</p>
|
||||
{item.metadata[0].languages.data.map((lang) => (
|
||||
<p key={lang.attributes.code}>
|
||||
{lang.attributes.name}
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</InsetBox>
|
||||
|
||||
{item.subitems.data.length > 0 && (
|
||||
<div
|
||||
id={isVariantSet ? "variants" : "subitems"}
|
||||
className="grid place-items-center gap-8 w-full"
|
||||
>
|
||||
<h2 className="text-2xl">
|
||||
{isVariantSet ? langui.variants : langui.subitems}
|
||||
</h2>
|
||||
<div className="grid gap-8 items-end mobile:grid-cols-2 grid-cols-[repeat(auto-fill,minmax(15rem,1fr))] w-full">
|
||||
{item.subitems.data.map((subitem) => (
|
||||
<LibraryItemsPreview
|
||||
key={subitem.id}
|
||||
item={subitem.attributes}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{item.contents.data.length > 0 && (
|
||||
<div id="contents" className="w-full grid place-items-center gap-8">
|
||||
<h2 className="text-2xl">{langui.contents}</h2>
|
||||
<div className="grid gap-4 w-full">
|
||||
{item.contents.data.map((content) => (
|
||||
<ContentTOCLine
|
||||
langui={langui}
|
||||
content={content}
|
||||
parentSlug={item.slug}
|
||||
key={content.id}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</ContentPanel>
|
||||
);
|
||||
|
||||
return (
|
||||
<AppLayout
|
||||
navTitle={langui.library}
|
||||
title={prettyinlineTitle("", item.title, item.subtitle)}
|
||||
contentPanel={contentPanel}
|
||||
subPanel={subPanel}
|
||||
thumbnail={item.thumbnail.data?.attributes}
|
||||
description={
|
||||
item.descriptions.length > 0
|
||||
? item.descriptions[0].description
|
||||
: undefined
|
||||
}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export async function getStaticProps(
|
||||
context: GetStaticPropsContext
|
||||
): Promise<{ props: LibrarySlugProps }> {
|
||||
const item = (
|
||||
await getLibraryItem({
|
||||
slug: context.params?.slug?.toString() ?? "",
|
||||
language_code: context.locale ?? "en",
|
||||
})
|
||||
).libraryItems.data[0];
|
||||
const props: LibrarySlugProps = {
|
||||
...(await getAppStaticProps(context)),
|
||||
item: item.attributes,
|
||||
itemId: item.id,
|
||||
};
|
||||
return {
|
||||
props: props,
|
||||
};
|
||||
}
|
||||
|
||||
export async function getStaticPaths(
|
||||
context: GetStaticPathsContext
|
||||
): Promise<GetStaticPathsResult> {
|
||||
const libraryItems = await getLibraryItemsSlugs({});
|
||||
const paths: GetStaticPathsResult["paths"] = [];
|
||||
libraryItems.libraryItems.data.map((item) => {
|
||||
context.locales?.map((local) => {
|
||||
paths.push({ params: { slug: item.attributes.slug }, locale: local });
|
||||
});
|
||||
});
|
||||
return {
|
||||
paths,
|
||||
fallback: false,
|
||||
};
|
||||
}
|
||||
|
||||
function useTesting(props: LibrarySlugProps) {
|
||||
const { item, itemId } = props;
|
||||
const router = useRouter();
|
||||
|
||||
const libraryItemURL = `/admin/content-manager/collectionType/api::library-item.library-item/${itemId}`;
|
||||
|
||||
sortContent(item.contents);
|
||||
|
||||
if (router.locale === "en") {
|
||||
if (!item.thumbnail.data) {
|
||||
prettyTestError(
|
||||
router,
|
||||
"Missing thumbnail",
|
||||
["libraryItem"],
|
||||
libraryItemURL
|
||||
);
|
||||
}
|
||||
if (item.metadata.length === 0) {
|
||||
prettyTestError(
|
||||
router,
|
||||
"Missing metadata",
|
||||
["libraryItem"],
|
||||
libraryItemURL
|
||||
);
|
||||
} else if (
|
||||
item.metadata[0].__typename === "ComponentMetadataGroup" &&
|
||||
(item.metadata[0].subtype.data.attributes.slug === "relation-set" ||
|
||||
item.metadata[0].subtype.data.attributes.slug === "variant-set")
|
||||
) {
|
||||
// This is a group type item
|
||||
if (item.price) {
|
||||
prettyTestError(
|
||||
router,
|
||||
"Group-type items shouldn't have price",
|
||||
["libraryItem"],
|
||||
libraryItemURL
|
||||
);
|
||||
}
|
||||
if (item.size) {
|
||||
prettyTestError(
|
||||
router,
|
||||
"Group-type items shouldn't have size",
|
||||
["libraryItem"],
|
||||
libraryItemURL
|
||||
);
|
||||
}
|
||||
if (item.release_date) {
|
||||
prettyTestError(
|
||||
router,
|
||||
"Group-type items shouldn't have release_date",
|
||||
["libraryItem"],
|
||||
libraryItemURL
|
||||
);
|
||||
}
|
||||
if (item.contents.data.length > 0) {
|
||||
prettyTestError(
|
||||
router,
|
||||
"Group-type items shouldn't have contents",
|
||||
["libraryItem"],
|
||||
libraryItemURL
|
||||
);
|
||||
}
|
||||
if (item.subitems.data.length === 0) {
|
||||
prettyTestError(
|
||||
router,
|
||||
"Group-type items should have subitems",
|
||||
["libraryItem"],
|
||||
libraryItemURL
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// This is a normal item
|
||||
|
||||
if (item.metadata[0].__typename === "ComponentMetadataGroup") {
|
||||
if (item.subitems.data.length === 0) {
|
||||
prettyTestError(
|
||||
router,
|
||||
"Group-type item should have subitems",
|
||||
["libraryItem"],
|
||||
libraryItemURL
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (item.price) {
|
||||
if (!item.price.amount) {
|
||||
prettyTestError(
|
||||
router,
|
||||
"Missing amount",
|
||||
["libraryItem", "price"],
|
||||
libraryItemURL
|
||||
);
|
||||
}
|
||||
if (!item.price.currency) {
|
||||
prettyTestError(
|
||||
router,
|
||||
"Missing currency",
|
||||
["libraryItem", "price"],
|
||||
libraryItemURL
|
||||
);
|
||||
}
|
||||
} else {
|
||||
prettyTestWarning(
|
||||
router,
|
||||
"Missing price",
|
||||
["libraryItem"],
|
||||
libraryItemURL
|
||||
);
|
||||
}
|
||||
|
||||
if (!item.digital) {
|
||||
if (item.size) {
|
||||
if (!item.size.width) {
|
||||
prettyTestWarning(
|
||||
router,
|
||||
"Missing width",
|
||||
["libraryItem", "size"],
|
||||
libraryItemURL
|
||||
);
|
||||
}
|
||||
if (!item.size.height) {
|
||||
prettyTestWarning(
|
||||
router,
|
||||
"Missing height",
|
||||
["libraryItem", "size"],
|
||||
libraryItemURL
|
||||
);
|
||||
}
|
||||
if (!item.size.thickness) {
|
||||
prettyTestWarning(
|
||||
router,
|
||||
"Missing thickness",
|
||||
["libraryItem", "size"],
|
||||
libraryItemURL
|
||||
);
|
||||
}
|
||||
} else {
|
||||
prettyTestWarning(
|
||||
router,
|
||||
"Missing size",
|
||||
["libraryItem"],
|
||||
libraryItemURL
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (item.release_date) {
|
||||
if (!item.release_date.year) {
|
||||
prettyTestError(
|
||||
router,
|
||||
"Missing year",
|
||||
["libraryItem", "release_date"],
|
||||
libraryItemURL
|
||||
);
|
||||
}
|
||||
if (!item.release_date.month) {
|
||||
prettyTestError(
|
||||
router,
|
||||
"Missing month",
|
||||
["libraryItem", "release_date"],
|
||||
libraryItemURL
|
||||
);
|
||||
}
|
||||
if (!item.release_date.day) {
|
||||
prettyTestError(
|
||||
router,
|
||||
"Missing day",
|
||||
["libraryItem", "release_date"],
|
||||
libraryItemURL
|
||||
);
|
||||
}
|
||||
} else {
|
||||
prettyTestWarning(
|
||||
router,
|
||||
"Missing release_date",
|
||||
["libraryItem"],
|
||||
libraryItemURL
|
||||
);
|
||||
}
|
||||
|
||||
if (item.contents.data.length === 0) {
|
||||
prettyTestWarning(
|
||||
router,
|
||||
"Missing contents",
|
||||
["libraryItem"],
|
||||
libraryItemURL
|
||||
);
|
||||
} else {
|
||||
let currentRangePage = 0;
|
||||
item.contents.data.map((content) => {
|
||||
const contentURL = `/admin/content-manager/collectionType/api::content.content/${content.id}`;
|
||||
|
||||
if (content.attributes.scan_set.length === 0) {
|
||||
prettyTestWarning(
|
||||
router,
|
||||
"Missing scan_set",
|
||||
["libraryItem", "content", content.id],
|
||||
contentURL
|
||||
);
|
||||
}
|
||||
if (content.attributes.range.length === 0) {
|
||||
prettyTestWarning(
|
||||
router,
|
||||
"Missing range",
|
||||
["libraryItem", "content", content.id],
|
||||
contentURL
|
||||
);
|
||||
} else if (
|
||||
content.attributes.range[0].__typename === "ComponentRangePageRange"
|
||||
) {
|
||||
if (
|
||||
content.attributes.range[0].starting_page <
|
||||
currentRangePage + 1
|
||||
) {
|
||||
prettyTestError(
|
||||
router,
|
||||
`Overlapping pages ${content.attributes.range[0].starting_page} to ${currentRangePage}`,
|
||||
["libraryItem", "content", content.id, "range"],
|
||||
libraryItemURL
|
||||
);
|
||||
} else if (
|
||||
content.attributes.range[0].starting_page >
|
||||
currentRangePage + 1
|
||||
) {
|
||||
prettyTestError(
|
||||
router,
|
||||
`Missing pages ${currentRangePage + 1} to ${
|
||||
content.attributes.range[0].starting_page - 1
|
||||
}`,
|
||||
["libraryItem", "content", content.id, "range"],
|
||||
libraryItemURL
|
||||
);
|
||||
}
|
||||
|
||||
if (!content.attributes.content.data) {
|
||||
prettyTestWarning(
|
||||
router,
|
||||
"Missing content",
|
||||
["libraryItem", "content", content.id, "range"],
|
||||
libraryItemURL
|
||||
);
|
||||
}
|
||||
|
||||
currentRangePage = content.attributes.range[0].ending_page;
|
||||
}
|
||||
});
|
||||
|
||||
if (item.metadata[0].__typename === "ComponentMetadataBooks") {
|
||||
if (currentRangePage < item.metadata[0].page_count) {
|
||||
prettyTestError(
|
||||
router,
|
||||
`Missing pages ${currentRangePage + 1} to ${
|
||||
item.metadata[0].page_count
|
||||
}`,
|
||||
["libraryItem", "content"],
|
||||
libraryItemURL
|
||||
);
|
||||
} else if (currentRangePage > item.metadata[0].page_count) {
|
||||
prettyTestError(
|
||||
router,
|
||||
`Page overflow, content references pages up to ${currentRangePage} when the highest expected was ${item.metadata[0].page_count}`,
|
||||
["libraryItem", "content"],
|
||||
libraryItemURL
|
||||
);
|
||||
}
|
||||
|
||||
if (item.metadata[0].languages.data.length === 0) {
|
||||
prettyTestWarning(
|
||||
router,
|
||||
"Missing language",
|
||||
["libraryItem", "metadata"],
|
||||
libraryItemURL
|
||||
);
|
||||
}
|
||||
|
||||
if (!item.metadata[0].page_count) {
|
||||
prettyTestWarning(
|
||||
router,
|
||||
"Missing page_count",
|
||||
["libraryItem", "metadata"],
|
||||
libraryItemURL
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!item.root_item && item.subitem_of.data.length === 0) {
|
||||
prettyTestError(
|
||||
router,
|
||||
"This item is inaccessible (not root item and not subitem of another item)",
|
||||
["libraryItem"],
|
||||
libraryItemURL
|
||||
);
|
||||
}
|
||||
|
||||
if (item.gallery.data.length === 0) {
|
||||
prettyTestWarning(
|
||||
router,
|
||||
"Missing gallery",
|
||||
["libraryItem"],
|
||||
libraryItemURL
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (item.descriptions.length === 0) {
|
||||
prettyTestWarning(
|
||||
router,
|
||||
"Missing description",
|
||||
["libraryItem"],
|
||||
libraryItemURL
|
||||
);
|
||||
}
|
||||
}
|
180
src/pages/library/[slug]/scans.tsx
Normal file
180
src/pages/library/[slug]/scans.tsx
Normal file
@ -0,0 +1,180 @@
|
||||
import AppLayout from "components/AppLayout";
|
||||
import Img, { getAssetURL, ImageQuality } from "components/Img";
|
||||
import LanguageSwitcher from "components/LanguageSwitcher";
|
||||
import LightBox from "components/LightBox";
|
||||
import NavOption from "components/PanelComponents/NavOption";
|
||||
import ReturnButton, {
|
||||
ReturnButtonType,
|
||||
} from "components/PanelComponents/ReturnButton";
|
||||
import ContentPanel, {
|
||||
ContentPanelWidthSizes,
|
||||
} from "components/Panels/ContentPanel";
|
||||
import SubPanel from "components/Panels/SubPanel";
|
||||
import { useAppLayout } from "contexts/AppLayoutContext";
|
||||
import { getLibraryItemScans, getLibraryItemsSlugs } from "graphql/operations";
|
||||
import { GetLibraryItemScansQuery } from "graphql/operations-types";
|
||||
import {
|
||||
GetStaticPathsContext,
|
||||
GetStaticPathsResult,
|
||||
GetStaticPropsContext,
|
||||
} from "next";
|
||||
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
||||
import { prettyinlineTitle, prettySlug, sortContent } from "queries/helpers";
|
||||
import { useState } from "react";
|
||||
|
||||
interface Props extends AppStaticProps {
|
||||
item: GetLibraryItemScansQuery["libraryItems"]["data"][number]["attributes"];
|
||||
itemId: GetLibraryItemScansQuery["libraryItems"]["data"][number]["id"];
|
||||
}
|
||||
|
||||
export default function LibrarySlug(props: Props): JSX.Element {
|
||||
const { item, langui } = props;
|
||||
const appLayout = useAppLayout();
|
||||
|
||||
sortContent(item.contents);
|
||||
|
||||
const [lightboxOpen, setLightboxOpen] = useState(false);
|
||||
const [lightboxImages, setLightboxImages] = useState([""]);
|
||||
const [lightboxIndex, setLightboxIndex] = useState(0);
|
||||
|
||||
const subPanel = (
|
||||
<SubPanel>
|
||||
<ReturnButton
|
||||
href={`/library/${item.slug}`}
|
||||
title={langui.item}
|
||||
langui={langui}
|
||||
displayOn={ReturnButtonType.desktop}
|
||||
horizontalLine
|
||||
/>
|
||||
|
||||
{item.contents.data.map((content) => (
|
||||
<NavOption
|
||||
key={content.id}
|
||||
url={`#${content.attributes.slug}`}
|
||||
title={prettySlug(content.attributes.slug, item.slug)}
|
||||
subtitle={
|
||||
content.attributes.range.length > 0 &&
|
||||
content.attributes.range[0].__typename === "ComponentRangePageRange"
|
||||
? `${content.attributes.range[0].starting_page} → ${content.attributes.range[0].ending_page}`
|
||||
: undefined
|
||||
}
|
||||
onClick={() => appLayout.setSubPanelOpen(false)}
|
||||
border
|
||||
/>
|
||||
))}
|
||||
</SubPanel>
|
||||
);
|
||||
|
||||
const contentPanel = (
|
||||
<ContentPanel width={ContentPanelWidthSizes.large}>
|
||||
<LightBox
|
||||
state={lightboxOpen}
|
||||
setState={setLightboxOpen}
|
||||
images={lightboxImages}
|
||||
index={lightboxIndex}
|
||||
setIndex={setLightboxIndex}
|
||||
/>
|
||||
|
||||
<ReturnButton
|
||||
href={`/library/${item.slug}`}
|
||||
title={langui.item}
|
||||
langui={langui}
|
||||
displayOn={ReturnButtonType.mobile}
|
||||
className="mb-10"
|
||||
/>
|
||||
{item.contents.data.map((content) => (
|
||||
<>
|
||||
<h2
|
||||
id={content.attributes.slug}
|
||||
key={`h2${content.id}`}
|
||||
className="text-2xl pb-2 pt-10 first-of-type:pt-0 flex flex-row place-items-center gap-2"
|
||||
>
|
||||
{prettySlug(content.attributes.slug, item.slug)}
|
||||
</h2>
|
||||
|
||||
{content.attributes.scan_set.length > 0 ? (
|
||||
<div
|
||||
key={`items${content.id}`}
|
||||
className="grid gap-8 items-end mobile:grid-cols-2 desktop:grid-cols-[repeat(auto-fill,_minmax(10rem,1fr))] pb-12 border-b-[3px] border-dotted last-of-type:border-0"
|
||||
>
|
||||
{content.attributes.scan_set[0].pages.data.map((page, index) => (
|
||||
<div
|
||||
key={page.id}
|
||||
className="drop-shadow-shade-lg hover:scale-[1.02] cursor-pointer transition-transform"
|
||||
onClick={() => {
|
||||
setLightboxOpen(true);
|
||||
setLightboxImages(
|
||||
content.attributes.scan_set[0].pages.data.map((image) =>
|
||||
getAssetURL(image.attributes.url, ImageQuality.Large)
|
||||
)
|
||||
);
|
||||
setLightboxIndex(index);
|
||||
}}
|
||||
>
|
||||
<Img image={page.attributes} quality={ImageQuality.Small} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="pb-12 border-b-[3px] border-dotted last-of-type:border-0">
|
||||
<LanguageSwitcher
|
||||
locales={content.attributes.scan_set_languages.map(
|
||||
(language) => language.language.data.attributes.code
|
||||
)}
|
||||
languages={props.languages}
|
||||
langui={props.langui}
|
||||
href={`#${content.attributes.slug}`}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
))}
|
||||
</ContentPanel>
|
||||
);
|
||||
|
||||
return (
|
||||
<AppLayout
|
||||
navTitle={langui.library}
|
||||
title={prettyinlineTitle("", item.title, item.subtitle)}
|
||||
contentPanel={contentPanel}
|
||||
subPanel={subPanel}
|
||||
thumbnail={item.thumbnail.data?.attributes}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export async function getStaticProps(
|
||||
context: GetStaticPropsContext
|
||||
): Promise<{ props: Props }> {
|
||||
const item = (
|
||||
await getLibraryItemScans({
|
||||
slug: context.params?.slug?.toString() ?? "",
|
||||
language_code: context.locale ?? "en",
|
||||
})
|
||||
).libraryItems.data[0];
|
||||
const props: Props = {
|
||||
...(await getAppStaticProps(context)),
|
||||
item: item.attributes,
|
||||
itemId: item.id,
|
||||
};
|
||||
return {
|
||||
props: props,
|
||||
};
|
||||
}
|
||||
|
||||
export async function getStaticPaths(
|
||||
context: GetStaticPathsContext
|
||||
): Promise<GetStaticPathsResult> {
|
||||
const libraryItems = await getLibraryItemsSlugs({});
|
||||
const paths: GetStaticPathsResult["paths"] = [];
|
||||
libraryItems.libraryItems.data.map((item) => {
|
||||
context.locales?.map((local) => {
|
||||
paths.push({ params: { slug: item.attributes.slug }, locale: local });
|
||||
});
|
||||
});
|
||||
return {
|
||||
paths,
|
||||
fallback: false,
|
||||
};
|
||||
}
|
@ -1,69 +1,390 @@
|
||||
import { GetStaticProps } from "next";
|
||||
import SubPanel from "components/Panels/SubPanel";
|
||||
import AppLayout from "components/AppLayout";
|
||||
import Chip from "components/Chip";
|
||||
import LibraryItemsPreview from "components/Library/LibraryItemsPreview";
|
||||
import PanelHeader from "components/PanelComponents/PanelHeader";
|
||||
import ContentPanel, {
|
||||
ContentPanelWidthSizes,
|
||||
} from "components/Panels/ContentPanel";
|
||||
import SubPanel from "components/Panels/SubPanel";
|
||||
import Select from "components/Select";
|
||||
import Switch from "components/Switch";
|
||||
import { getLibraryItemsPreview } from "graphql/operations";
|
||||
import {
|
||||
GetCurrenciesQuery,
|
||||
GetLibraryItemsPreviewQuery,
|
||||
GetWebsiteInterfaceQuery,
|
||||
} from "graphql/operations-types";
|
||||
import {
|
||||
getLibraryItemsPreview,
|
||||
getWebsiteInterface,
|
||||
} from "graphql/operations";
|
||||
import PanelHeader from "components/PanelComponents/PanelHeader";
|
||||
import AppLayout from "components/AppLayout";
|
||||
import LibraryItemsPreview from "components/Library/LibraryItemsPreview";
|
||||
import { GetStaticPropsContext } from "next";
|
||||
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
||||
import { convertPrice, prettyDate, prettyinlineTitle } from "queries/helpers";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
type LibraryProps = {
|
||||
libraryItems: GetLibraryItemsPreviewQuery;
|
||||
langui: GetWebsiteInterfaceQuery;
|
||||
};
|
||||
interface LibraryProps extends AppStaticProps {
|
||||
items: GetLibraryItemsPreviewQuery["libraryItems"]["data"];
|
||||
}
|
||||
|
||||
type GroupLibraryItems = Map<
|
||||
string,
|
||||
GetLibraryItemsPreviewQuery["libraryItems"]["data"]
|
||||
>;
|
||||
|
||||
export default function Library(props: LibraryProps): JSX.Element {
|
||||
const langui = props.langui.websiteInterfaces.data[0].attributes;
|
||||
const { langui, items: libraryItems, currencies } = props;
|
||||
|
||||
const [showSubitems, setShowSubitems] = useState<boolean>(false);
|
||||
const [showPrimaryItems, setShowPrimaryItems] = useState<boolean>(true);
|
||||
const [showSecondaryItems, setShowSecondaryItems] = useState<boolean>(false);
|
||||
const [sortingMethod, setSortingMethod] = useState<number>(0);
|
||||
const [groupingMethod, setGroupingMethod] = useState<number>(-1);
|
||||
|
||||
const [filteredItems, setFilteredItems] = useState<LibraryProps["items"]>(
|
||||
filterItems(
|
||||
showSubitems,
|
||||
showPrimaryItems,
|
||||
showSecondaryItems,
|
||||
libraryItems
|
||||
)
|
||||
);
|
||||
|
||||
const [sortedItems, setSortedItem] = useState<LibraryProps["items"]>(
|
||||
sortBy(groupingMethod, filteredItems, currencies)
|
||||
);
|
||||
|
||||
const [groups, setGroups] = useState<GroupLibraryItems>(
|
||||
getGroups(langui, groupingMethod, sortedItems)
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setFilteredItems(
|
||||
filterItems(
|
||||
showSubitems,
|
||||
showPrimaryItems,
|
||||
showSecondaryItems,
|
||||
libraryItems
|
||||
)
|
||||
);
|
||||
}, [showSubitems, libraryItems, showPrimaryItems, showSecondaryItems]);
|
||||
|
||||
useEffect(() => {
|
||||
setSortedItem(sortBy(sortingMethod, filteredItems, currencies));
|
||||
}, [currencies, filteredItems, sortingMethod]);
|
||||
|
||||
useEffect(() => {
|
||||
setGroups(getGroups(langui, groupingMethod, sortedItems));
|
||||
}, [langui, groupingMethod, sortedItems]);
|
||||
|
||||
const subPanel = (
|
||||
<SubPanel>
|
||||
<PanelHeader
|
||||
icon="library_books"
|
||||
title={langui.main_library}
|
||||
title={langui.library}
|
||||
description={langui.library_description}
|
||||
/>
|
||||
|
||||
<div className="flex flex-row gap-2 place-items-center">
|
||||
<p className="flex-shrink-0">{langui.group_by}:</p>
|
||||
<Select
|
||||
className="w-full"
|
||||
options={[langui.category, langui.type, langui.release_year]}
|
||||
state={groupingMethod}
|
||||
setState={setGroupingMethod}
|
||||
allowEmpty
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-row gap-2 place-items-center">
|
||||
<p className="flex-shrink-0">{langui.order_by}:</p>
|
||||
<Select
|
||||
className="w-full"
|
||||
options={[langui.name, langui.price, langui.release_date]}
|
||||
state={sortingMethod}
|
||||
setState={setSortingMethod}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-row gap-2 place-items-center">
|
||||
<p className="flex-shrink-0">{langui.show_subitems}:</p>
|
||||
<Switch state={showSubitems} setState={setShowSubitems} />
|
||||
</div>
|
||||
|
||||
<div className="flex flex-row gap-2 place-items-center">
|
||||
<p className="flex-shrink-0">{langui.show_primary_items}:</p>
|
||||
<Switch state={showPrimaryItems} setState={setShowPrimaryItems} />
|
||||
</div>
|
||||
|
||||
<div className="flex flex-row gap-2 place-items-center">
|
||||
<p className="flex-shrink-0">{langui.show_secondary_items}:</p>
|
||||
<Switch state={showSecondaryItems} setState={setShowSecondaryItems} />
|
||||
</div>
|
||||
</SubPanel>
|
||||
);
|
||||
const contentPanel = (
|
||||
<ContentPanel width={ContentPanelWidthSizes.large}>
|
||||
<div className="grid gap-8 items-end mobile:grid-cols-2 desktop:grid-cols-[repeat(auto-fill,_minmax(13rem,1fr))]">
|
||||
{props.libraryItems.libraryItems.data.map((item) => (
|
||||
<LibraryItemsPreview key={item.id} item={item.attributes} />
|
||||
))}
|
||||
</div>
|
||||
{[...groups].map(([name, items]) => (
|
||||
<>
|
||||
{items.length > 0 && (
|
||||
<>
|
||||
{name && (
|
||||
<h2
|
||||
key={`h2${name}`}
|
||||
className="text-2xl pb-2 pt-10 first-of-type:pt-0 flex flex-row place-items-center gap-2"
|
||||
>
|
||||
{name}
|
||||
<Chip>{`${items.length} ${
|
||||
items.length <= 1
|
||||
? langui.result.toLowerCase()
|
||||
: langui.results.toLowerCase()
|
||||
}`}</Chip>
|
||||
</h2>
|
||||
)}
|
||||
<div
|
||||
key={`items${name}`}
|
||||
className="grid gap-8 items-end mobile:grid-cols-2 desktop:grid-cols-[repeat(auto-fill,_minmax(13rem,1fr))] pb-12 border-b-[3px] border-dotted last-of-type:border-0"
|
||||
>
|
||||
{items.map((item) => (
|
||||
<LibraryItemsPreview
|
||||
key={item.id}
|
||||
item={item.attributes}
|
||||
currencies={props.currencies}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
))}
|
||||
</ContentPanel>
|
||||
);
|
||||
return (
|
||||
<AppLayout
|
||||
navTitle={langui.main_library}
|
||||
langui={langui}
|
||||
navTitle={langui.library}
|
||||
subPanel={subPanel}
|
||||
contentPanel={contentPanel}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export const getStaticProps: GetStaticProps = async (context) => {
|
||||
if (context.locale) {
|
||||
const props: LibraryProps = {
|
||||
libraryItems: await getLibraryItemsPreview({
|
||||
language_code: context.locale,
|
||||
}),
|
||||
langui: await getWebsiteInterface({
|
||||
language_code: context.locale,
|
||||
}),
|
||||
};
|
||||
return {
|
||||
props: props,
|
||||
};
|
||||
} else {
|
||||
return { props: {} };
|
||||
export async function getStaticProps(
|
||||
context: GetStaticPropsContext
|
||||
): Promise<{ props: LibraryProps }> {
|
||||
const props: LibraryProps = {
|
||||
...(await getAppStaticProps(context)),
|
||||
items: (
|
||||
await getLibraryItemsPreview({
|
||||
language_code: context.locale ?? "en",
|
||||
})
|
||||
).libraryItems.data,
|
||||
};
|
||||
return {
|
||||
props: props,
|
||||
};
|
||||
}
|
||||
|
||||
function getGroups(
|
||||
langui: GetWebsiteInterfaceQuery["websiteInterfaces"]["data"][number]["attributes"],
|
||||
groupByType: number,
|
||||
items: LibraryProps["items"]
|
||||
): GroupLibraryItems {
|
||||
switch (groupByType) {
|
||||
case 0: {
|
||||
const typeGroup = new Map();
|
||||
typeGroup.set("Drakengard 1", []);
|
||||
typeGroup.set("Drakengard 1.3", []);
|
||||
typeGroup.set("Drakengard 2", []);
|
||||
typeGroup.set("Drakengard 3", []);
|
||||
typeGroup.set("Drakengard 4", []);
|
||||
typeGroup.set("NieR Gestalt", []);
|
||||
typeGroup.set("NieR Replicant", []);
|
||||
typeGroup.set("NieR Replicant ver.1.22474487139...", []);
|
||||
typeGroup.set("NieR:Automata", []);
|
||||
typeGroup.set("NieR Re[in]carnation", []);
|
||||
typeGroup.set("SINoALICE", []);
|
||||
typeGroup.set("Voice of Cards", []);
|
||||
typeGroup.set("Final Fantasy XIV", []);
|
||||
typeGroup.set("Thou Shalt Not Die", []);
|
||||
typeGroup.set("Bakuken", []);
|
||||
typeGroup.set("YoRHa", []);
|
||||
typeGroup.set("YoRHa Boys", []);
|
||||
typeGroup.set(langui.no_category, []);
|
||||
|
||||
items.map((item) => {
|
||||
if (item.attributes.categories.data.length === 0) {
|
||||
typeGroup.get(langui.no_category)?.push(item);
|
||||
} else {
|
||||
item.attributes.categories.data.map((category) => {
|
||||
typeGroup.get(category.attributes.name)?.push(item);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return typeGroup;
|
||||
}
|
||||
|
||||
case 1: {
|
||||
const group: GroupLibraryItems = new Map();
|
||||
group.set(langui.audio, []);
|
||||
group.set(langui.game, []);
|
||||
group.set(langui.textual, []);
|
||||
group.set(langui.video, []);
|
||||
group.set(langui.other, []);
|
||||
group.set(langui.group, []);
|
||||
group.set(langui.no_type, []);
|
||||
items.map((item) => {
|
||||
if (item.attributes.metadata.length > 0) {
|
||||
switch (item.attributes.metadata[0].__typename) {
|
||||
case "ComponentMetadataAudio":
|
||||
group.get(langui.audio)?.push(item);
|
||||
break;
|
||||
case "ComponentMetadataGame":
|
||||
group.get(langui.game)?.push(item);
|
||||
break;
|
||||
case "ComponentMetadataBooks":
|
||||
group.get(langui.textual)?.push(item);
|
||||
break;
|
||||
case "ComponentMetadataVideo":
|
||||
group.get(langui.video)?.push(item);
|
||||
break;
|
||||
case "ComponentMetadataOther":
|
||||
group.get(langui.other)?.push(item);
|
||||
break;
|
||||
case "ComponentMetadataGroup":
|
||||
switch (
|
||||
item.attributes.metadata[0].subitems_type.data.attributes.slug
|
||||
) {
|
||||
case "audio":
|
||||
group.get(langui.audio)?.push(item);
|
||||
break;
|
||||
case "video":
|
||||
group.get(langui.video)?.push(item);
|
||||
break;
|
||||
case "game":
|
||||
group.get(langui.game)?.push(item);
|
||||
break;
|
||||
case "textual":
|
||||
group.get(langui.textual)?.push(item);
|
||||
break;
|
||||
case "mixed":
|
||||
group.get(langui.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 {
|
||||
group.get(langui.no_type)?.push(item);
|
||||
}
|
||||
});
|
||||
return group;
|
||||
}
|
||||
|
||||
case 2: {
|
||||
const years: number[] = [];
|
||||
items.map((item) => {
|
||||
if (item.attributes.release_date) {
|
||||
if (!years.includes(item.attributes.release_date.year))
|
||||
years.push(item.attributes.release_date.year);
|
||||
}
|
||||
});
|
||||
const group: GroupLibraryItems = new Map();
|
||||
years.sort((a, b) => a - b);
|
||||
years.map((year) => {
|
||||
group.set(year.toString(), []);
|
||||
});
|
||||
group.set(langui.no_year, []);
|
||||
items.map((item) => {
|
||||
if (item.attributes.release_date) {
|
||||
group.get(item.attributes.release_date.year.toString())?.push(item);
|
||||
} else {
|
||||
group.get(langui.no_year)?.push(item);
|
||||
}
|
||||
});
|
||||
|
||||
return group;
|
||||
}
|
||||
|
||||
default: {
|
||||
const group: GroupLibraryItems = new Map();
|
||||
group.set("", items);
|
||||
return group;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function filterItems(
|
||||
showSubitems: boolean,
|
||||
showPrimaryItems: boolean,
|
||||
showSecondaryItems: boolean,
|
||||
items: LibraryProps["items"]
|
||||
): LibraryProps["items"] {
|
||||
return [...items].filter((item) => {
|
||||
if (!showSubitems && !item.attributes.root_item) return false;
|
||||
if (
|
||||
showSubitems &&
|
||||
item.attributes.metadata.length > 0 &&
|
||||
item.attributes.metadata[0].__typename === "ComponentMetadataGroup" &&
|
||||
(item.attributes.metadata[0].subtype.data.attributes.slug ===
|
||||
"variant-set" ||
|
||||
item.attributes.metadata[0].subtype.data.attributes.slug ===
|
||||
"relation-set")
|
||||
)
|
||||
return false;
|
||||
if (item.attributes.primary && !showPrimaryItems) return false;
|
||||
if (!item.attributes.primary && !showSecondaryItems) return false;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
function sortBy(
|
||||
orderByType: number,
|
||||
items: LibraryProps["items"],
|
||||
currencies: GetCurrenciesQuery["currencies"]["data"]
|
||||
): LibraryProps["items"] {
|
||||
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 === null
|
||||
? "9999"
|
||||
: prettyDate(a.attributes.release_date);
|
||||
const dateB =
|
||||
b.attributes.release_date === null
|
||||
? "9999"
|
||||
: prettyDate(b.attributes.release_date);
|
||||
return dateA.localeCompare(dateB);
|
||||
});
|
||||
default:
|
||||
return items;
|
||||
}
|
||||
}
|
||||
|
@ -1,45 +1,32 @@
|
||||
import SubPanel from "components/Panels/SubPanel";
|
||||
import PanelHeader from "components/PanelComponents/PanelHeader";
|
||||
import { GetWebsiteInterfaceQuery } from "graphql/operations-types";
|
||||
import { GetStaticProps } from "next";
|
||||
import { getWebsiteInterface } from "graphql/operations";
|
||||
import AppLayout from "components/AppLayout";
|
||||
import PanelHeader from "components/PanelComponents/PanelHeader";
|
||||
import SubPanel from "components/Panels/SubPanel";
|
||||
import { GetStaticPropsContext } from "next";
|
||||
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
||||
|
||||
type MerchProps = {
|
||||
langui: GetWebsiteInterfaceQuery;
|
||||
};
|
||||
|
||||
interface MerchProps extends AppStaticProps {}
|
||||
export default function Merch(props: MerchProps): JSX.Element {
|
||||
const langui = props.langui.websiteInterfaces.data[0].attributes;
|
||||
const { langui } = props;
|
||||
const subPanel = (
|
||||
<SubPanel>
|
||||
<PanelHeader
|
||||
icon="store"
|
||||
title={langui.main_merch}
|
||||
title={langui.merch}
|
||||
description={langui.merch_description}
|
||||
/>
|
||||
</SubPanel>
|
||||
);
|
||||
|
||||
return (
|
||||
<AppLayout
|
||||
navTitle={langui.main_merch}
|
||||
langui={langui}
|
||||
subPanel={subPanel}
|
||||
/>
|
||||
);
|
||||
return <AppLayout navTitle={langui.merch} subPanel={subPanel} {...props} />;
|
||||
}
|
||||
|
||||
export const getStaticProps: GetStaticProps = async (context) => {
|
||||
if (context.locale) {
|
||||
const props: MerchProps = {
|
||||
langui: await getWebsiteInterface({
|
||||
language_code: context.locale,
|
||||
}),
|
||||
};
|
||||
return {
|
||||
props: props,
|
||||
};
|
||||
}
|
||||
return { props: {} };
|
||||
};
|
||||
export async function getStaticProps(
|
||||
context: GetStaticPropsContext
|
||||
): Promise<{ props: MerchProps }> {
|
||||
const props: MerchProps = {
|
||||
...(await getAppStaticProps(context)),
|
||||
};
|
||||
return {
|
||||
props: props,
|
||||
};
|
||||
}
|
||||
|
181
src/pages/news/[slug].tsx
Normal file
181
src/pages/news/[slug].tsx
Normal file
@ -0,0 +1,181 @@
|
||||
import AppLayout from "components/AppLayout";
|
||||
import Chip from "components/Chip";
|
||||
import ThumbnailHeader from "components/Content/ThumbnailHeader";
|
||||
import HorizontalLine from "components/HorizontalLine";
|
||||
import LanguageSwitcher from "components/LanguageSwitcher";
|
||||
import Markdawn from "components/Markdown/Markdawn";
|
||||
import TOC from "components/Markdown/TOC";
|
||||
import ReturnButton, {
|
||||
ReturnButtonType,
|
||||
} from "components/PanelComponents/ReturnButton";
|
||||
import ContentPanel from "components/Panels/ContentPanel";
|
||||
import SubPanel from "components/Panels/SubPanel";
|
||||
import RecorderChip from "components/RecorderChip";
|
||||
import ToolTip from "components/ToolTip";
|
||||
import { getPost, getPostsSlugs } from "graphql/operations";
|
||||
import { GetPostQuery, StrapiImage } from "graphql/operations-types";
|
||||
import {
|
||||
GetStaticPathsContext,
|
||||
GetStaticPathsResult,
|
||||
GetStaticPropsContext,
|
||||
} from "next";
|
||||
import { useRouter } from "next/router";
|
||||
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
||||
import {
|
||||
getLocalesFromLanguages,
|
||||
getStatusDescription,
|
||||
prettySlug,
|
||||
} from "queries/helpers";
|
||||
|
||||
interface PostProps extends AppStaticProps {
|
||||
post: GetPostQuery["posts"]["data"][number]["attributes"];
|
||||
postId: GetPostQuery["posts"]["data"][number]["id"];
|
||||
}
|
||||
|
||||
export default function LibrarySlug(props: PostProps): JSX.Element {
|
||||
const { post, langui } = props;
|
||||
const locales = getLocalesFromLanguages(post.translations_languages);
|
||||
const router = useRouter();
|
||||
|
||||
const thumbnail: StrapiImage | undefined =
|
||||
post.translations.length > 0 && post.translations[0].thumbnail.data
|
||||
? post.translations[0].thumbnail.data.attributes
|
||||
: post.thumbnail.data
|
||||
? post.thumbnail.data.attributes
|
||||
: undefined;
|
||||
|
||||
const subPanel = (
|
||||
<SubPanel>
|
||||
<ReturnButton
|
||||
href="/news"
|
||||
title={langui.news}
|
||||
langui={langui}
|
||||
displayOn={ReturnButtonType.desktop}
|
||||
horizontalLine
|
||||
/>
|
||||
|
||||
{post.translations.length > 0 && (
|
||||
<div className="grid grid-flow-col place-items-center place-content-center gap-2">
|
||||
<p className="font-headers">{langui.status}:</p>
|
||||
|
||||
<ToolTip
|
||||
content={getStatusDescription(post.translations[0].status, langui)}
|
||||
maxWidth={"20rem"}
|
||||
>
|
||||
<Chip>{post.translations[0].status}</Chip>
|
||||
</ToolTip>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{post.authors.data.length > 0 && (
|
||||
<div>
|
||||
<p className="font-headers">{"Authors"}:</p>
|
||||
<div className="grid place-items-center place-content-center gap-2">
|
||||
{post.authors.data.map((author) => (
|
||||
<RecorderChip key={author.id} langui={langui} recorder={author} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<HorizontalLine />
|
||||
|
||||
{post.translations.length > 0 && post.translations[0].body && (
|
||||
<TOC
|
||||
text={post.translations[0].body}
|
||||
title={post.translations[0].title}
|
||||
/>
|
||||
)}
|
||||
</SubPanel>
|
||||
);
|
||||
const contentPanel = (
|
||||
<ContentPanel>
|
||||
<ReturnButton
|
||||
href="/news"
|
||||
title={langui.news}
|
||||
langui={langui}
|
||||
displayOn={ReturnButtonType.mobile}
|
||||
className="mb-10"
|
||||
/>
|
||||
|
||||
<ThumbnailHeader
|
||||
thumbnail={thumbnail}
|
||||
title={
|
||||
post.translations.length > 0
|
||||
? post.translations[0].title
|
||||
: prettySlug(post.slug)
|
||||
}
|
||||
description={
|
||||
post.translations.length > 0
|
||||
? post.translations[0].excerpt
|
||||
: undefined
|
||||
}
|
||||
langui={langui}
|
||||
categories={post.categories}
|
||||
/>
|
||||
|
||||
<HorizontalLine />
|
||||
|
||||
{locales.includes(router.locale ?? "en") ? (
|
||||
<Markdawn text={post.translations[0].body} />
|
||||
) : (
|
||||
<LanguageSwitcher
|
||||
locales={locales}
|
||||
languages={props.languages}
|
||||
langui={props.langui}
|
||||
/>
|
||||
)}
|
||||
</ContentPanel>
|
||||
);
|
||||
|
||||
return (
|
||||
<AppLayout
|
||||
navTitle={langui.news}
|
||||
title={
|
||||
post.translations.length > 0
|
||||
? post.translations[0].title
|
||||
: prettySlug(post.slug)
|
||||
}
|
||||
contentPanel={contentPanel}
|
||||
subPanel={subPanel}
|
||||
thumbnail={thumbnail}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export async function getStaticProps(
|
||||
context: GetStaticPropsContext
|
||||
): Promise<{ props: PostProps }> {
|
||||
const slug = context.params?.slug?.toString() ?? "";
|
||||
const post = (
|
||||
await getPost({
|
||||
slug: slug,
|
||||
language_code: context.locale ?? "en",
|
||||
})
|
||||
).posts.data[0];
|
||||
const props: PostProps = {
|
||||
...(await getAppStaticProps(context)),
|
||||
post: post.attributes,
|
||||
postId: post.id,
|
||||
};
|
||||
return {
|
||||
props: props,
|
||||
};
|
||||
}
|
||||
|
||||
export async function getStaticPaths(
|
||||
context: GetStaticPathsContext
|
||||
): Promise<GetStaticPathsResult> {
|
||||
const posts = await getPostsSlugs({});
|
||||
const paths: GetStaticPathsResult["paths"] = [];
|
||||
posts.posts.data.map((item) => {
|
||||
context.locales?.map((local) => {
|
||||
paths.push({ params: { slug: item.attributes.slug }, locale: local });
|
||||
});
|
||||
});
|
||||
return {
|
||||
paths,
|
||||
fallback: false,
|
||||
};
|
||||
}
|
@ -1,45 +1,73 @@
|
||||
import SubPanel from "components/Panels/SubPanel";
|
||||
import PanelHeader from "components/PanelComponents/PanelHeader";
|
||||
import { GetWebsiteInterfaceQuery } from "graphql/operations-types";
|
||||
import { GetStaticProps } from "next";
|
||||
import { getWebsiteInterface } from "graphql/operations";
|
||||
import AppLayout from "components/AppLayout";
|
||||
import PostsPreview from "components/News/PostsPreview";
|
||||
import PanelHeader from "components/PanelComponents/PanelHeader";
|
||||
import ContentPanel, {
|
||||
ContentPanelWidthSizes,
|
||||
} from "components/Panels/ContentPanel";
|
||||
import SubPanel from "components/Panels/SubPanel";
|
||||
import { getPostsPreview } from "graphql/operations";
|
||||
import { GetPostsPreviewQuery } from "graphql/operations-types";
|
||||
import { GetStaticPropsContext } from "next";
|
||||
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
||||
import { prettyDate } from "queries/helpers";
|
||||
|
||||
type NewsProps = {
|
||||
langui: GetWebsiteInterfaceQuery;
|
||||
};
|
||||
interface NewsProps extends AppStaticProps {
|
||||
posts: GetPostsPreviewQuery["posts"]["data"];
|
||||
}
|
||||
|
||||
export default function News(props: NewsProps): JSX.Element {
|
||||
const langui = props.langui.websiteInterfaces.data[0].attributes;
|
||||
const { langui, posts } = props;
|
||||
|
||||
posts
|
||||
.sort((a, b) => {
|
||||
const dateA =
|
||||
a.attributes.date === null ? "9999" : prettyDate(a.attributes.date);
|
||||
const dateB =
|
||||
b.attributes.date === null ? "9999" : prettyDate(b.attributes.date);
|
||||
return dateA.localeCompare(dateB);
|
||||
})
|
||||
.reverse();
|
||||
|
||||
const subPanel = (
|
||||
<SubPanel>
|
||||
<PanelHeader
|
||||
icon="feed"
|
||||
title={langui.main_news}
|
||||
title={langui.news}
|
||||
description={langui.news_description}
|
||||
/>
|
||||
</SubPanel>
|
||||
);
|
||||
|
||||
const contentPanel = (
|
||||
<ContentPanel width={ContentPanelWidthSizes.large}>
|
||||
<div className="grid gap-8 items-end grid-cols-1 desktop:grid-cols-[repeat(auto-fill,_minmax(20rem,1fr))]">
|
||||
{posts.map((post) => (
|
||||
<PostsPreview key={post.id} post={post.attributes} />
|
||||
))}
|
||||
</div>
|
||||
</ContentPanel>
|
||||
);
|
||||
|
||||
return (
|
||||
<AppLayout
|
||||
navTitle={langui.main_news}
|
||||
langui={langui}
|
||||
navTitle={langui.news}
|
||||
subPanel={subPanel}
|
||||
contentPanel={contentPanel}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export const getStaticProps: GetStaticProps = async (context) => {
|
||||
if (context.locale) {
|
||||
const props: NewsProps = {
|
||||
langui: await getWebsiteInterface({
|
||||
language_code: context.locale,
|
||||
}),
|
||||
};
|
||||
return {
|
||||
props: props,
|
||||
};
|
||||
}
|
||||
return { props: {} };
|
||||
};
|
||||
export async function getStaticProps(
|
||||
context: GetStaticPropsContext
|
||||
): Promise<{ props: NewsProps }> {
|
||||
const props: NewsProps = {
|
||||
...(await getAppStaticProps(context)),
|
||||
posts: await (
|
||||
await getPostsPreview({ language_code: context.locale ?? "en" })
|
||||
).posts.data,
|
||||
};
|
||||
return {
|
||||
props: props,
|
||||
};
|
||||
}
|
||||
|
@ -1,93 +1,91 @@
|
||||
import { GetStaticProps } from "next";
|
||||
import AppLayout from "components/AppLayout";
|
||||
import ChronologyYearComponent from "components/Chronology/ChronologyYearComponent";
|
||||
import InsetBox from "components/InsetBox";
|
||||
import NavOption from "components/PanelComponents/NavOption";
|
||||
import ReturnButton, {
|
||||
ReturnButtonType,
|
||||
} from "components/PanelComponents/ReturnButton";
|
||||
import ContentPanel from "components/Panels/ContentPanel";
|
||||
import SubPanel from "components/Panels/SubPanel";
|
||||
import ChronologyYearComponent from "components/Chronology/ChronologyYearComponent";
|
||||
import { useAppLayout } from "contexts/AppLayoutContext";
|
||||
import { getChronologyItems, getEras } from "graphql/operations";
|
||||
import {
|
||||
GetChronologyItemsQuery,
|
||||
GetErasQuery,
|
||||
GetWebsiteInterfaceQuery,
|
||||
} from "graphql/operations-types";
|
||||
import {
|
||||
getEras,
|
||||
getChronologyItems,
|
||||
getWebsiteInterface,
|
||||
} from "graphql/operations";
|
||||
import NavOption from "components/PanelComponents/NavOption";
|
||||
import ReturnButton from "components/PanelComponents/ReturnButton";
|
||||
import HorizontalLine from "components/HorizontalLine";
|
||||
import AppLayout from "components/AppLayout";
|
||||
import { GetStaticPropsContext } from "next";
|
||||
import { useRouter } from "next/router";
|
||||
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
||||
import {
|
||||
prettySlug,
|
||||
prettyTestError,
|
||||
prettyTestWarning,
|
||||
} from "queries/helpers";
|
||||
import InsetBox from "components/InsetBox";
|
||||
import { useRouter } from "next/router";
|
||||
import ReactTooltip from "react-tooltip";
|
||||
|
||||
interface DataChronologyProps {
|
||||
chronologyItems: GetChronologyItemsQuery;
|
||||
chronologyEras: GetErasQuery;
|
||||
langui: GetWebsiteInterfaceQuery;
|
||||
interface ChronologyProps extends AppStaticProps {
|
||||
chronologyItems: GetChronologyItemsQuery["chronologyItems"]["data"];
|
||||
chronologyEras: GetErasQuery["chronologyEras"]["data"];
|
||||
}
|
||||
|
||||
export default function DataChronology(
|
||||
props: DataChronologyProps
|
||||
): JSX.Element {
|
||||
export default function Chronology(props: ChronologyProps): JSX.Element {
|
||||
useTesting(props);
|
||||
const langui = props.langui.websiteInterfaces.data[0].attributes;
|
||||
const chronologyItems = props.chronologyItems.chronologyItems;
|
||||
const chronologyEras = props.chronologyEras.chronologyEras;
|
||||
const { chronologyItems, chronologyEras, langui } = props;
|
||||
const appLayout = useAppLayout();
|
||||
|
||||
// Group by year the Chronology items
|
||||
let chronologyItemYearGroups: GetChronologyItemsQuery["chronologyItems"]["data"][number][][][] =
|
||||
const chronologyItemYearGroups: GetChronologyItemsQuery["chronologyItems"]["data"][number][][][] =
|
||||
[];
|
||||
|
||||
chronologyEras.data.map(() => {
|
||||
chronologyEras.map(() => {
|
||||
chronologyItemYearGroups.push([]);
|
||||
});
|
||||
|
||||
let currentChronologyEraIndex = 0;
|
||||
chronologyItems.data.map((item) => {
|
||||
chronologyItems.map((item) => {
|
||||
if (
|
||||
item.attributes.year >
|
||||
chronologyEras.data[currentChronologyEraIndex].attributes.ending_year
|
||||
chronologyEras[currentChronologyEraIndex].attributes.ending_year
|
||||
) {
|
||||
currentChronologyEraIndex++;
|
||||
currentChronologyEraIndex += 1;
|
||||
}
|
||||
if (
|
||||
!chronologyItemYearGroups[currentChronologyEraIndex].hasOwnProperty(
|
||||
Object.prototype.hasOwnProperty.call(
|
||||
chronologyItemYearGroups[currentChronologyEraIndex],
|
||||
item.attributes.year
|
||||
)
|
||||
) {
|
||||
chronologyItemYearGroups[currentChronologyEraIndex][
|
||||
item.attributes.year
|
||||
] = [item];
|
||||
].push(item);
|
||||
} else {
|
||||
chronologyItemYearGroups[currentChronologyEraIndex][
|
||||
item.attributes.year
|
||||
].push(item);
|
||||
] = [item];
|
||||
}
|
||||
});
|
||||
|
||||
const subPanel = (
|
||||
<SubPanel>
|
||||
<ReturnButton href="/data" title="Data" langui={langui} />
|
||||
<HorizontalLine />
|
||||
<ReturnButton
|
||||
href="/wiki"
|
||||
title={langui.wiki}
|
||||
langui={langui}
|
||||
displayOn={ReturnButtonType.desktop}
|
||||
horizontalLine
|
||||
/>
|
||||
|
||||
{props.chronologyEras.chronologyEras.data.map((era) => (
|
||||
{chronologyEras.map((era) => (
|
||||
<NavOption
|
||||
key={era.id}
|
||||
url={"#" + era.attributes.slug}
|
||||
url={`#${era.attributes.slug}`}
|
||||
title={
|
||||
era.attributes.title.length > 0
|
||||
? era.attributes.title[0].title
|
||||
: prettySlug(era.attributes.slug)
|
||||
}
|
||||
subtitle={
|
||||
era.attributes.starting_year + " → " + era.attributes.ending_year
|
||||
}
|
||||
subtitle={`${era.attributes.starting_year} → ${era.attributes.ending_year}`}
|
||||
border
|
||||
onClick={() => appLayout.setSubPanelOpen(false)}
|
||||
/>
|
||||
))}
|
||||
</SubPanel>
|
||||
@ -95,20 +93,28 @@ export default function DataChronology(
|
||||
|
||||
const contentPanel = (
|
||||
<ContentPanel>
|
||||
<ReturnButton
|
||||
href="/wiki"
|
||||
title={langui.wiki}
|
||||
langui={langui}
|
||||
displayOn={ReturnButtonType.mobile}
|
||||
className="mb-10"
|
||||
/>
|
||||
|
||||
{chronologyItemYearGroups.map((era, eraIndex) => (
|
||||
<>
|
||||
<InsetBox
|
||||
id={chronologyEras.data[eraIndex].attributes.slug}
|
||||
id={chronologyEras[eraIndex].attributes.slug}
|
||||
className="grid text-center my-8 gap-4"
|
||||
>
|
||||
<h2 className="text-2xl">
|
||||
{chronologyEras.data[eraIndex].attributes.title.length > 0
|
||||
? chronologyEras.data[eraIndex].attributes.title[0].title
|
||||
: prettySlug(chronologyEras.data[eraIndex].attributes.slug)}
|
||||
{chronologyEras[eraIndex].attributes.title.length > 0
|
||||
? chronologyEras[eraIndex].attributes.title[0].title
|
||||
: prettySlug(chronologyEras[eraIndex].attributes.slug)}
|
||||
</h2>
|
||||
<p className="whitespace-pre-line ">
|
||||
{chronologyEras.data[eraIndex].attributes.title.length > 0
|
||||
? chronologyEras.data[eraIndex].attributes.title[0].description
|
||||
{chronologyEras[eraIndex].attributes.title.length > 0
|
||||
? chronologyEras[eraIndex].attributes.title[0].description
|
||||
: ""}
|
||||
</p>
|
||||
</InsetBox>
|
||||
@ -117,57 +123,47 @@ export default function DataChronology(
|
||||
key={`${eraIndex}-${index}`}
|
||||
year={items[0].attributes.year}
|
||||
items={items}
|
||||
langui={langui}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
))}
|
||||
|
||||
<ReactTooltip
|
||||
id="ChronologyTooltip"
|
||||
place="top"
|
||||
type="light"
|
||||
effect="solid"
|
||||
delayShow={50}
|
||||
clickable={true}
|
||||
className="drop-shadow-shade-xl !opacity-100 mobile:after:!border-r-light !bg-light !rounded-lg desktop:after:!border-t-light text-left !text-black max-w-xs"
|
||||
/>
|
||||
</ContentPanel>
|
||||
);
|
||||
|
||||
return (
|
||||
<AppLayout
|
||||
navTitle="Chronology"
|
||||
langui={langui}
|
||||
contentPanel={contentPanel}
|
||||
subPanel={subPanel}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export const getStaticProps: GetStaticProps = async (context) => {
|
||||
if (context.locale) {
|
||||
const props: DataChronologyProps = {
|
||||
chronologyItems: await getChronologyItems({
|
||||
language_code: context.locale,
|
||||
}),
|
||||
chronologyEras: await getEras({ language_code: context.locale }),
|
||||
langui: await getWebsiteInterface({
|
||||
language_code: context.locale,
|
||||
}),
|
||||
};
|
||||
return {
|
||||
props: props,
|
||||
};
|
||||
}
|
||||
return { props: {} };
|
||||
};
|
||||
export async function getStaticProps(
|
||||
context: GetStaticPropsContext
|
||||
): Promise<{ props: ChronologyProps }> {
|
||||
const props: ChronologyProps = {
|
||||
...(await getAppStaticProps(context)),
|
||||
chronologyItems: (
|
||||
await getChronologyItems({
|
||||
language_code: context.locale ?? "en",
|
||||
})
|
||||
).chronologyItems.data,
|
||||
chronologyEras: (await getEras({ language_code: context.locale ?? "en" }))
|
||||
.chronologyEras.data,
|
||||
};
|
||||
return {
|
||||
props: props,
|
||||
};
|
||||
}
|
||||
|
||||
function useTesting({ chronologyItems, chronologyEras }: DataChronologyProps) {
|
||||
function useTesting(props: ChronologyProps) {
|
||||
const router = useRouter();
|
||||
chronologyEras.chronologyEras.data.map((era) => {
|
||||
const chronologyErasURL =
|
||||
"/admin/content-manager/collectionType/api::chronology-era.chronology-era/" +
|
||||
chronologyItems.chronologyItems.data[0].id;
|
||||
const { chronologyItems, chronologyEras } = props;
|
||||
chronologyEras.map((era) => {
|
||||
const chronologyErasURL = `/admin/content-manager/collectionType/api::chronology-era.chronology-era/${chronologyItems[0].id}`;
|
||||
|
||||
if (era.attributes.title.length === 0) {
|
||||
prettyTestError(
|
||||
@ -201,21 +197,12 @@ function useTesting({ chronologyItems, chronologyEras }: DataChronologyProps) {
|
||||
}
|
||||
});
|
||||
|
||||
chronologyItems.chronologyItems.data.map((item) => {
|
||||
const chronologyItemsURL =
|
||||
"/admin/content-manager/collectionType/api::chronology-item.chronology-item/" +
|
||||
chronologyItems.chronologyItems.data[0].id;
|
||||
chronologyItems.map((item) => {
|
||||
const chronologyItemsURL = `/admin/content-manager/collectionType/api::chronology-item.chronology-item/${chronologyItems[0].id}`;
|
||||
|
||||
const date = `${item.attributes.year}/${item.attributes.month}/${item.attributes.day}`;
|
||||
|
||||
if (!(item.attributes.events.length > 0)) {
|
||||
prettyTestError(
|
||||
router,
|
||||
"No events for this date",
|
||||
["chronologyItems", date],
|
||||
chronologyItemsURL
|
||||
);
|
||||
} else {
|
||||
if (item.attributes.events.length > 0) {
|
||||
item.attributes.events.map((event) => {
|
||||
if (!event.source.data) {
|
||||
prettyTestError(
|
||||
@ -234,6 +221,13 @@ function useTesting({ chronologyItems, chronologyEras }: DataChronologyProps) {
|
||||
);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
prettyTestError(
|
||||
router,
|
||||
"No events for this date",
|
||||
["chronologyItems", date],
|
||||
chronologyItemsURL
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -1,48 +1,35 @@
|
||||
import SubPanel from "components/Panels/SubPanel";
|
||||
import PanelHeader from "components/PanelComponents/PanelHeader";
|
||||
import { GetWebsiteInterfaceQuery } from "graphql/operations-types";
|
||||
import { GetStaticProps } from "next";
|
||||
import { getWebsiteInterface } from "graphql/operations";
|
||||
import ContentPanel from "components/Panels/ContentPanel";
|
||||
import AppLayout from "components/AppLayout";
|
||||
import NavOption from "components/PanelComponents/NavOption";
|
||||
import PanelHeader from "components/PanelComponents/PanelHeader";
|
||||
import SubPanel from "components/Panels/SubPanel";
|
||||
import { GetStaticPropsContext } from "next";
|
||||
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
||||
|
||||
type WikiProps = {
|
||||
langui: GetWebsiteInterfaceQuery;
|
||||
};
|
||||
interface WikiProps extends AppStaticProps {}
|
||||
|
||||
export default function Hubs(props: WikiProps): JSX.Element {
|
||||
const langui = props.langui.websiteInterfaces.data[0].attributes;
|
||||
export default function Wiki(props: WikiProps): JSX.Element {
|
||||
const { langui } = props;
|
||||
const subPanel = (
|
||||
<SubPanel>
|
||||
<PanelHeader
|
||||
icon="travel_explore"
|
||||
title={langui.main_wiki}
|
||||
title={langui.wiki}
|
||||
description={langui.wiki_description}
|
||||
/>
|
||||
<NavOption title="Chronology" url="/wiki/chronology" border />
|
||||
</SubPanel>
|
||||
);
|
||||
const contentPanel = <ContentPanel>Hello</ContentPanel>;
|
||||
|
||||
return (
|
||||
<AppLayout
|
||||
navTitle={langui.main_wiki}
|
||||
langui={langui}
|
||||
contentPanel={contentPanel}
|
||||
subPanel={subPanel}
|
||||
/>
|
||||
);
|
||||
return <AppLayout navTitle={langui.wiki} subPanel={subPanel} {...props} />;
|
||||
}
|
||||
|
||||
export const getStaticProps: GetStaticProps = async (context) => {
|
||||
if (context.locale) {
|
||||
const props: WikiProps = {
|
||||
langui: await getWebsiteInterface({
|
||||
language_code: context.locale,
|
||||
}),
|
||||
};
|
||||
return {
|
||||
props: props,
|
||||
};
|
||||
}
|
||||
return { props: {} };
|
||||
};
|
||||
export async function getStaticProps(
|
||||
context: GetStaticPropsContext
|
||||
): Promise<{ props: WikiProps }> {
|
||||
const props: WikiProps = {
|
||||
...(await getAppStaticProps(context)),
|
||||
};
|
||||
return {
|
||||
props: props,
|
||||
};
|
||||
}
|
||||
|
39
src/queries/getAppStaticProps.ts
Normal file
39
src/queries/getAppStaticProps.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import {
|
||||
getCurrencies,
|
||||
getLanguages,
|
||||
getWebsiteInterface,
|
||||
} from "graphql/operations";
|
||||
import {
|
||||
GetCurrenciesQuery,
|
||||
GetLanguagesQuery,
|
||||
GetWebsiteInterfaceQuery,
|
||||
} from "graphql/operations-types";
|
||||
import { GetStaticPropsContext } from "next";
|
||||
|
||||
export interface AppStaticProps {
|
||||
langui: GetWebsiteInterfaceQuery["websiteInterfaces"]["data"][number]["attributes"];
|
||||
currencies: GetCurrenciesQuery["currencies"]["data"];
|
||||
languages: GetLanguagesQuery["languages"]["data"];
|
||||
}
|
||||
|
||||
export async function getAppStaticProps(
|
||||
context: GetStaticPropsContext
|
||||
): Promise<AppStaticProps> {
|
||||
const languages = (await getLanguages({})).languages.data;
|
||||
languages.sort((a, b) =>
|
||||
a.attributes.localized_name.localeCompare(b.attributes.localized_name)
|
||||
);
|
||||
|
||||
const currencies = (await getCurrencies({})).currencies.data;
|
||||
currencies.sort((a, b) => a.attributes.code.localeCompare(b.attributes.code));
|
||||
|
||||
return {
|
||||
langui: (
|
||||
await getWebsiteInterface({
|
||||
language_code: context.locale ?? "en",
|
||||
})
|
||||
).websiteInterfaces.data[0].attributes,
|
||||
currencies: currencies,
|
||||
languages: languages,
|
||||
};
|
||||
}
|
@ -4,6 +4,9 @@ import {
|
||||
ImageQuality,
|
||||
} from "components/Img";
|
||||
import {
|
||||
Enum_Componentsetstextset_Status,
|
||||
GetCurrenciesQuery,
|
||||
GetLanguagesQuery,
|
||||
GetLibraryItemQuery,
|
||||
GetLibraryItemsPreviewQuery,
|
||||
GetWebsiteInterfaceQuery,
|
||||
@ -14,21 +17,39 @@ import { NextRouter } from "next/router";
|
||||
export function prettyDate(
|
||||
datePicker: GetLibraryItemsPreviewQuery["libraryItems"]["data"][number]["attributes"]["release_date"]
|
||||
): string {
|
||||
return (
|
||||
datePicker.year +
|
||||
"/" +
|
||||
datePicker.month.toString().padStart(2, "0") +
|
||||
"/" +
|
||||
datePicker.day.toString().padStart(2, "0")
|
||||
);
|
||||
return `${datePicker.year}/${datePicker.month
|
||||
.toString()
|
||||
.padStart(2, "0")}/${datePicker.day.toString().padStart(2, "0")}`;
|
||||
}
|
||||
|
||||
export function prettyPrice(
|
||||
pricePicker: GetLibraryItemsPreviewQuery["libraryItems"]["data"][number]["attributes"]["price"]
|
||||
pricePicker: GetLibraryItemsPreviewQuery["libraryItems"]["data"][number]["attributes"]["price"],
|
||||
currencies: GetCurrenciesQuery["currencies"]["data"],
|
||||
targetCurrencyCode?: string
|
||||
): string {
|
||||
if (!targetCurrencyCode) return "";
|
||||
let result = "";
|
||||
currencies.map((currency) => {
|
||||
if (currency.attributes.code === targetCurrencyCode) {
|
||||
const amountInTargetCurrency = convertPrice(pricePicker, currency);
|
||||
result =
|
||||
currency.attributes.symbol +
|
||||
amountInTargetCurrency.toLocaleString(undefined, {
|
||||
minimumFractionDigits: currency.attributes.display_decimals ? 2 : 0,
|
||||
maximumFractionDigits: currency.attributes.display_decimals ? 2 : 0,
|
||||
});
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
export function convertPrice(
|
||||
pricePicker: GetLibraryItemsPreviewQuery["libraryItems"]["data"][number]["attributes"]["price"],
|
||||
targetCurrency: GetCurrenciesQuery["currencies"]["data"][number]
|
||||
): number {
|
||||
return (
|
||||
pricePicker.currency.data.attributes.symbol +
|
||||
pricePicker.amount.toLocaleString()
|
||||
(pricePicker.amount * pricePicker.currency.data.attributes.rate_to_usd) /
|
||||
targetCurrency.attributes.rate_to_usd
|
||||
);
|
||||
}
|
||||
|
||||
@ -49,9 +70,9 @@ export function prettyinlineTitle(
|
||||
subtitle: string
|
||||
): string {
|
||||
let result = "";
|
||||
if (pretitle) result += pretitle + ": ";
|
||||
if (pretitle) result += `${pretitle}: `;
|
||||
result += title;
|
||||
if (subtitle) result += " - " + subtitle;
|
||||
if (subtitle) result += ` - ${subtitle}`;
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -61,64 +82,71 @@ export function prettyItemType(
|
||||
},
|
||||
langui: GetWebsiteInterfaceQuery["websiteInterfaces"]["data"][number]["attributes"]
|
||||
): string {
|
||||
const type = metadata.__typename;
|
||||
switch (metadata.__typename) {
|
||||
case "ComponentMetadataAudio":
|
||||
return langui.library_item_type_audio;
|
||||
return langui.audio;
|
||||
case "ComponentMetadataBooks":
|
||||
return langui.library_item_type_textual;
|
||||
return langui.textual;
|
||||
case "ComponentMetadataGame":
|
||||
return langui.library_item_type_game;
|
||||
return langui.game;
|
||||
case "ComponentMetadataVideo":
|
||||
return langui.library_item_type_video;
|
||||
return langui.video;
|
||||
case "ComponentMetadataGroup":
|
||||
return langui.group;
|
||||
case "ComponentMetadataOther":
|
||||
return langui.library_item_type_other;
|
||||
return langui.other;
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
export function prettyItemSubType(metadata: {
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
__typename: GetLibraryItemsPreviewQuery["libraryItems"]["data"][number]["attributes"]["metadata"][number]["__typename"];
|
||||
subtype?: any;
|
||||
platforms?: any;
|
||||
subitems_type?: any;
|
||||
}): string {
|
||||
switch (metadata.__typename) {
|
||||
case "ComponentMetadataAudio":
|
||||
case "ComponentMetadataBooks":
|
||||
case "ComponentMetadataVideo":
|
||||
case "ComponentMetadataOther": {
|
||||
return metadata.subtype.data.attributes.titles.length > 0
|
||||
? metadata.subtype.data.attributes.titles[0].title
|
||||
: prettySlug(metadata.subtype.data.attributes.slug);
|
||||
}
|
||||
case "ComponentMetadataGame":
|
||||
return metadata.platforms.data.length > 0
|
||||
? metadata.platforms.data[0].attributes.short
|
||||
: "";
|
||||
|
||||
case "ComponentMetadataGroup": {
|
||||
const firstPart =
|
||||
metadata.subtype.data.attributes.titles.length > 0
|
||||
? metadata.subtype.data.attributes.titles[0].title
|
||||
: prettySlug(metadata.subtype.data.attributes.slug);
|
||||
|
||||
const secondPart =
|
||||
metadata.subitems_type.data.attributes.titles.length > 0
|
||||
? metadata.subitems_type.data.attributes.titles[0].title
|
||||
: prettySlug(metadata.subitems_type.data.attributes.slug);
|
||||
return `${secondPart} ${firstPart}`;
|
||||
}
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
/* eslint-enable @typescript-eslint/no-explicit-any */
|
||||
}
|
||||
|
||||
export function prettyLanguage(code: string): string {
|
||||
switch (code) {
|
||||
case "en":
|
||||
return "English";
|
||||
case "es":
|
||||
return "Español";
|
||||
case "fr":
|
||||
return "Français";
|
||||
case "ja":
|
||||
return "日本語";
|
||||
case "en":
|
||||
return "English";
|
||||
case "xx":
|
||||
return "██";
|
||||
default:
|
||||
return code;
|
||||
}
|
||||
export function prettyLanguage(
|
||||
code: string,
|
||||
languages: GetLanguagesQuery["languages"]["data"]
|
||||
): string {
|
||||
let result = code;
|
||||
languages.forEach((language) => {
|
||||
if (language.attributes.code === code)
|
||||
result = language.attributes.localized_name;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
export function prettyTestWarning(
|
||||
@ -153,17 +181,19 @@ function prettyTestWritter(
|
||||
): void {
|
||||
const line = [
|
||||
level,
|
||||
process.env.NEXT_PUBLIC_URL_SELF + "/" + locale + asPath,
|
||||
`${process.env.NEXT_PUBLIC_URL_SELF}/${locale}${asPath}`,
|
||||
locale,
|
||||
subCategory?.join(" -> "),
|
||||
message,
|
||||
process.env.NEXT_PUBLIC_URL_CMS + url,
|
||||
];
|
||||
|
||||
if (level === TestingLevel.Warning) {
|
||||
console.warn(line.join("\t"));
|
||||
} else {
|
||||
console.error(line.join("\t"));
|
||||
if (process.env.ENABLE_TESTING_LOG) {
|
||||
if (level === TestingLevel.Warning) {
|
||||
console.warn(line.join("\t"));
|
||||
} else {
|
||||
console.error(line.join("\t"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -173,7 +203,7 @@ export function capitalizeString(string: string): string {
|
||||
}
|
||||
|
||||
let words = string.split(" ");
|
||||
words = words.map((word) => (word = capitalizeWord(word)));
|
||||
words = words.map((word) => capitalizeWord(word));
|
||||
return words.join(" ");
|
||||
}
|
||||
|
||||
@ -202,9 +232,13 @@ export function getOgImage(quality: ImageQuality, image: StrapiImage): OgImage {
|
||||
};
|
||||
}
|
||||
|
||||
export function sortContent(
|
||||
contents: GetLibraryItemQuery["libraryItems"]["data"][number]["attributes"]["contents"]
|
||||
) {
|
||||
export function sortContent(contents: {
|
||||
data: {
|
||||
attributes: {
|
||||
range: GetLibraryItemQuery["libraryItems"]["data"][number]["attributes"]["contents"]["data"][number]["attributes"]["range"];
|
||||
};
|
||||
}[];
|
||||
}) {
|
||||
contents.data.sort((a, b) => {
|
||||
if (
|
||||
a.attributes.range[0].__typename === "ComponentRangePageRange" &&
|
||||
@ -218,3 +252,63 @@ export function sortContent(
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
|
||||
export function getStatusDescription(
|
||||
status: string,
|
||||
langui: GetWebsiteInterfaceQuery["websiteInterfaces"]["data"][number]["attributes"]
|
||||
): string {
|
||||
switch (status) {
|
||||
case Enum_Componentsetstextset_Status.Incomplete:
|
||||
return langui.status_incomplete;
|
||||
|
||||
case Enum_Componentsetstextset_Status.Draft:
|
||||
return langui.status_draft;
|
||||
|
||||
case Enum_Componentsetstextset_Status.Review:
|
||||
return langui.status_review;
|
||||
|
||||
case Enum_Componentsetstextset_Status.Done:
|
||||
return langui.status_done;
|
||||
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
export function slugify(string: string | undefined): string {
|
||||
if (!string) {
|
||||
return "";
|
||||
}
|
||||
return string
|
||||
.replace(/[ÀÁÂÃÄÅàáâãä忯]/g, "a")
|
||||
.replace(/[çÇ]/gu, "c")
|
||||
.replace(/[ðÐ]/gu, "d")
|
||||
.replace(/[ÈÉÊËéèêë]/gu, "e")
|
||||
.replace(/[ÏïÎîÍíÌì]/gu, "i")
|
||||
.replace(/[Ññ]/gu, "n")
|
||||
.replace(/[øØœŒÕõÔôÓóÒò]/gu, "o")
|
||||
.replace(/[ÜüÛûÚúÙù]/gu, "u")
|
||||
.replace(/[ŸÿÝý]/gu, "y")
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9- ]/gu, "")
|
||||
.trim()
|
||||
.replace(/ /gu, "-");
|
||||
}
|
||||
|
||||
export function randomInt(min: number, max: number) {
|
||||
return Math.floor(Math.random() * (max - min)) + min;
|
||||
}
|
||||
|
||||
export function getLocalesFromLanguages(
|
||||
languages: {
|
||||
language: {
|
||||
data: {
|
||||
attributes: {
|
||||
code: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
}[]
|
||||
) {
|
||||
return languages.map((language) => language.language.data.attributes.code);
|
||||
}
|
||||
|
595
src/tailwind.css
595
src/tailwind.css
@ -2,89 +2,520 @@
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply box-border font-body font-medium scroll-smooth scroll-m-8;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
@apply font-headers font-black;
|
||||
}
|
||||
|
||||
a {
|
||||
@apply transition-colors underline-offset-2 decoration-dotted underline decoration-dark hover:text-dark;
|
||||
}
|
||||
|
||||
*::selection {
|
||||
@apply bg-dark text-light;
|
||||
}
|
||||
|
||||
/* SCROLLBARS STYLING */
|
||||
|
||||
* {
|
||||
@apply [scrollbar-color:theme(colors.dark)_transparent] [scrollbar-width:thin];
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar {
|
||||
@apply w-3 mobile:w-0;
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar-track {
|
||||
@apply bg-light;
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar-thumb {
|
||||
@apply bg-dark rounded-full border-[3px] border-solid border-light;
|
||||
}
|
||||
|
||||
/* CHANGE PROSE DEFAULTS */
|
||||
|
||||
.prose,
|
||||
.prose p,
|
||||
.prose h1,
|
||||
.prose h2,
|
||||
.prose h3,
|
||||
.prose h4,
|
||||
.prose h5,
|
||||
.prose h6,
|
||||
.prose a,
|
||||
.prose strong {
|
||||
@apply text-black;
|
||||
}
|
||||
|
||||
.prose a {
|
||||
@apply transition-colors underline-offset-2 decoration-dotted underline decoration-dark hover:text-dark;
|
||||
}
|
||||
|
||||
.prose footer {
|
||||
@apply border-t-[3px] border-dotted pt-6;
|
||||
}
|
||||
|
||||
.prose footer > div {
|
||||
@apply my-2 px-6 py-4 rounded-xl;
|
||||
}
|
||||
|
||||
.prose footer > div:target {
|
||||
@apply bg-mid shadow-inner-sm shadow-shade;
|
||||
}
|
||||
|
||||
.prose li::marker {
|
||||
@apply text-dark;
|
||||
}
|
||||
|
||||
.prose blockquote {
|
||||
@apply border-l-dark
|
||||
}
|
||||
|
||||
* {
|
||||
@apply box-border font-body font-medium scroll-smooth scroll-m-8;
|
||||
}
|
||||
|
||||
@layer components {
|
||||
.texture-paper-dots {
|
||||
@apply [background-image:var(--theme-texture-dots)] [background-blend-mode:var(--theme-texture-dots-blend)] bg-local bg-[length:10cm];
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
@apply font-headers font-black;
|
||||
}
|
||||
|
||||
a {
|
||||
@apply transition-colors underline-offset-2 decoration-dotted underline decoration-dark hover:text-dark cursor-pointer;
|
||||
}
|
||||
|
||||
*::selection {
|
||||
@apply bg-dark text-light;
|
||||
}
|
||||
|
||||
mark {
|
||||
@apply bg-mid px-2;
|
||||
}
|
||||
|
||||
/* SCROLLBARS STYLING */
|
||||
|
||||
* {
|
||||
@apply [scrollbar-color:theme(colors.dark)_transparent] [scrollbar-width:thin];
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar {
|
||||
@apply w-3 mobile:w-0;
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar-track {
|
||||
@apply bg-light;
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar-thumb {
|
||||
@apply bg-dark rounded-full border-[3px] border-solid border-light;
|
||||
}
|
||||
|
||||
/* CHANGE FORMATTED DEFAULTS */
|
||||
|
||||
.formatted h1,
|
||||
.formatted h2,
|
||||
.formatted h3,
|
||||
.formatted h4,
|
||||
.formatted h5,
|
||||
.formatted h6 {
|
||||
@apply text-center flex gap-3 justify-center;
|
||||
}
|
||||
|
||||
.formatted h1 {
|
||||
@apply text-4xl my-16;
|
||||
}
|
||||
|
||||
.formatted h1 + h2 {
|
||||
@apply -mt-10;
|
||||
}
|
||||
|
||||
.formatted h2 {
|
||||
@apply text-3xl my-12;
|
||||
}
|
||||
|
||||
.formatted h2 + h3 {
|
||||
@apply -mt-8;
|
||||
}
|
||||
|
||||
.formatted h3 {
|
||||
@apply text-2xl my-8;
|
||||
}
|
||||
|
||||
.formatted h3 + h4 {
|
||||
@apply -mt-6;
|
||||
}
|
||||
|
||||
.formatted h4 {
|
||||
@apply text-xl my-6;
|
||||
}
|
||||
|
||||
.formatted h5 {
|
||||
@apply text-lg my-4;
|
||||
}
|
||||
|
||||
.formatted p,
|
||||
.formatted strong {
|
||||
@apply my-2 text-justify;
|
||||
}
|
||||
|
||||
.formatted strong {
|
||||
@apply font-black;
|
||||
}
|
||||
|
||||
.formatted footer {
|
||||
@apply border-t-[3px] border-dotted pt-6;
|
||||
}
|
||||
|
||||
.formatted footer > div {
|
||||
@apply my-2 px-6 py-4 rounded-xl;
|
||||
}
|
||||
|
||||
.formatted footer > div:target {
|
||||
@apply bg-mid shadow-inner-sm shadow-shade;
|
||||
}
|
||||
|
||||
.formatted li::marker {
|
||||
@apply text-dark;
|
||||
}
|
||||
|
||||
.formatted blockquote {
|
||||
@apply border-l-dark;
|
||||
}
|
||||
|
||||
.formatted ul {
|
||||
@apply list-disc pl-4;
|
||||
}
|
||||
|
||||
.formatted ol {
|
||||
@apply list-decimal pl-4;
|
||||
}
|
||||
|
||||
.formatted blockquote {
|
||||
@apply border-2 border-mid rounded-lg p-5 text-center my-8;
|
||||
}
|
||||
|
||||
.formatted blockquote cite {
|
||||
@apply text-dark block;
|
||||
}
|
||||
|
||||
/* INPUT */
|
||||
|
||||
input,
|
||||
textarea {
|
||||
@apply rounded-full p-2 text-center bg-light outline outline-mid outline-2 outline-offset-[-2px] hover:outline-[transparent] text-dark hover:bg-mid transition-all placeholder:text-dark placeholder:opacity-60;
|
||||
}
|
||||
|
||||
input::placeholder {
|
||||
@apply text-dark opacity-60;
|
||||
}
|
||||
|
||||
input:focus-visible,
|
||||
textarea:focus-within {
|
||||
@apply outline-none bg-mid shadow-inner-sm shadow-shade;
|
||||
}
|
||||
|
||||
textarea {
|
||||
@apply rounded-2xl text-left p-6;
|
||||
}
|
||||
|
||||
input[type="submit"] {
|
||||
@apply grid place-content-center place-items-center border-[1px] border-dark text-dark rounded-full px-4 pt-[0.4rem] pb-[0.5rem] transition-all cursor-pointer hover:text-light hover:bg-dark hover:drop-shadow-shade-lg active:bg-black active:text-light active:drop-shadow-black-lg active:border-black;
|
||||
}
|
||||
|
||||
.texture-paper-dots {
|
||||
@apply [background-image:var(--theme-texture-dots)] [background-blend-mode:var(--theme-texture-dots-blend)] bg-local bg-[length:10cm];
|
||||
}
|
||||
|
||||
/* TIPPY */
|
||||
|
||||
.tippy-box[data-animation="fade"][data-state="hidden"] {
|
||||
@apply opacity-0;
|
||||
}
|
||||
[data-tippy-root] {
|
||||
max-width: calc(100vw - 10px);
|
||||
}
|
||||
.tippy-box {
|
||||
@apply relative bg-light drop-shadow-shade-xl rounded-lg transition-[transform,_visibility,_opacity];
|
||||
}
|
||||
.tippy-box[data-placement^="top"] > .tippy-arrow {
|
||||
@apply bottom-0;
|
||||
}
|
||||
.tippy-box[data-placement^="top"] > .tippy-arrow:before {
|
||||
bottom: -7px;
|
||||
left: 0;
|
||||
border-width: 8px 8px 0;
|
||||
border-top-color: initial;
|
||||
transform-origin: center top;
|
||||
}
|
||||
.tippy-box[data-placement^="bottom"] > .tippy-arrow {
|
||||
top: 0;
|
||||
}
|
||||
.tippy-box[data-placement^="bottom"] > .tippy-arrow:before {
|
||||
top: -7px;
|
||||
left: 0;
|
||||
border-width: 0 8px 8px;
|
||||
border-bottom-color: initial;
|
||||
transform-origin: center bottom;
|
||||
}
|
||||
.tippy-box[data-placement^="left"] > .tippy-arrow {
|
||||
right: 0;
|
||||
}
|
||||
.tippy-box[data-placement^="left"] > .tippy-arrow:before {
|
||||
border-width: 8px 0 8px 8px;
|
||||
border-left-color: initial;
|
||||
right: -7px;
|
||||
transform-origin: center left;
|
||||
}
|
||||
.tippy-box[data-placement^="right"] > .tippy-arrow {
|
||||
left: 0;
|
||||
}
|
||||
.tippy-box[data-placement^="right"] > .tippy-arrow:before {
|
||||
left: -7px;
|
||||
border-width: 8px 8px 8px 0;
|
||||
border-right-color: initial;
|
||||
transform-origin: center right;
|
||||
}
|
||||
.tippy-box[data-inertia][data-state="visible"] {
|
||||
transition-timing-function: cubic-bezier(0.54, 1.5, 0.38, 1.11);
|
||||
}
|
||||
.tippy-arrow {
|
||||
@apply h-4 w-4 text-light;
|
||||
}
|
||||
.tippy-arrow:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
border-color: transparent;
|
||||
border-style: solid;
|
||||
}
|
||||
.tippy-content {
|
||||
@apply relative px-6 py-4 z-10;
|
||||
}
|
||||
|
||||
/* LIGHTBOX */
|
||||
|
||||
@keyframes closeWindow {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.ril__outer {
|
||||
@apply h-full w-full touch-none outline-none bg-shade bg-opacity-50 [backdrop-filter:blur(2px)];
|
||||
}
|
||||
|
||||
.ril__outerClosing {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.ril__inner {
|
||||
@apply absolute inset-0;
|
||||
}
|
||||
|
||||
.ril__image,
|
||||
.ril__imagePrev,
|
||||
.ril__imageNext {
|
||||
@apply absolute inset-0 m-auto max-w-none touch-none;
|
||||
}
|
||||
|
||||
.ril__image {
|
||||
@apply drop-shadow-shade-2xl;
|
||||
}
|
||||
|
||||
.ril__navButtons {
|
||||
@apply absolute inset-y-0 w-5 h-8 px-10 py-8 cursor-pointer m-auto;
|
||||
}
|
||||
.ril__navButtons:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
.ril__navButtons:active {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.ril__navButtonPrev {
|
||||
left: 0;
|
||||
background: rgba(0, 0, 0, 0.2)
|
||||
url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgd2lkdGg9IjIwIiBoZWlnaHQ9IjM0Ij48cGF0aCBkPSJtIDE5LDMgLTIsLTIgLTE2LDE2IDE2LDE2IDEsLTEgLTE1LC0xNSAxNSwtMTUgeiIgZmlsbD0iI0ZGRiIvPjwvc3ZnPg==")
|
||||
no-repeat center;
|
||||
}
|
||||
|
||||
.ril__navButtonNext {
|
||||
right: 0;
|
||||
background: rgba(0, 0, 0, 0.2)
|
||||
url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgd2lkdGg9IjIwIiBoZWlnaHQ9IjM0Ij48cGF0aCBkPSJtIDEsMyAyLC0yIDE2LDE2IC0xNiwxNiAtMSwtMSAxNSwtMTUgLTE1LC0xNSB6IiBmaWxsPSIjRkZGIi8+PC9zdmc+")
|
||||
no-repeat center;
|
||||
}
|
||||
|
||||
.ril__caption,
|
||||
.ril__toolbar {
|
||||
@apply bg-shade bg-opacity-50 absolute inset-x-0 flex justify-between;
|
||||
}
|
||||
|
||||
.ril__caption {
|
||||
bottom: 0;
|
||||
max-height: 150px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.ril__captionContent {
|
||||
padding: 10px 20px;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.ril__toolbar {
|
||||
@apply top-0 h-12;
|
||||
}
|
||||
|
||||
.ril__toolbarSide {
|
||||
height: 50px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.ril__toolbarLeftSide {
|
||||
padding-left: 20px;
|
||||
padding-right: 0;
|
||||
flex: 0 1 auto;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.ril__toolbarRightSide {
|
||||
padding-left: 0;
|
||||
padding-right: 20px;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.ril__toolbarItem {
|
||||
display: inline-block;
|
||||
line-height: 50px;
|
||||
padding: 0;
|
||||
color: #fff;
|
||||
font-size: 120%;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.ril__toolbarItemChild {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.ril__builtinButton {
|
||||
width: 40px;
|
||||
height: 35px;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
opacity: 0.7;
|
||||
}
|
||||
.ril__builtinButton:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
.ril__builtinButton:active {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.ril__builtinButtonDisabled {
|
||||
cursor: default;
|
||||
opacity: 0.5;
|
||||
}
|
||||
.ril__builtinButtonDisabled:hover {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.ril__closeButton {
|
||||
background: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgd2lkdGg9IjIwIiBoZWlnaHQ9IjIwIj48cGF0aCBkPSJtIDEsMyAxLjI1LC0xLjI1IDcuNSw3LjUgNy41LC03LjUgMS4yNSwxLjI1IC03LjUsNy41IDcuNSw3LjUgLTEuMjUsMS4yNSAtNy41LC03LjUgLTcuNSw3LjUgLTEuMjUsLTEuMjUgNy41LC03LjUgLTcuNSwtNy41IHoiIGZpbGw9IiNGRkYiLz48L3N2Zz4=")
|
||||
no-repeat center;
|
||||
}
|
||||
|
||||
.ril__zoomInButton {
|
||||
background: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCI+PGcgc3Ryb2tlPSIjZmZmIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCI+PHBhdGggZD0iTTEgMTlsNi02Ii8+PHBhdGggZD0iTTkgOGg2Ii8+PHBhdGggZD0iTTEyIDV2NiIvPjwvZz48Y2lyY2xlIGN4PSIxMiIgY3k9IjgiIHI9IjciIGZpbGw9Im5vbmUiIHN0cm9rZT0iI2ZmZiIgc3Ryb2tlLXdpZHRoPSIyIi8+PC9zdmc+")
|
||||
no-repeat center;
|
||||
}
|
||||
|
||||
.ril__zoomOutButton {
|
||||
background: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCI+PGcgc3Ryb2tlPSIjZmZmIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCI+PHBhdGggZD0iTTEgMTlsNi02Ii8+PHBhdGggZD0iTTkgOGg2Ii8+PC9nPjxjaXJjbGUgY3g9IjEyIiBjeT0iOCIgcj0iNyIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZmZmIiBzdHJva2Utd2lkdGg9IjIiLz48L3N2Zz4=")
|
||||
no-repeat center;
|
||||
}
|
||||
|
||||
.ril__outerAnimating {
|
||||
animation-name: closeWindow;
|
||||
}
|
||||
|
||||
@keyframes pointFade {
|
||||
0%,
|
||||
19.999%,
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
20% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.ril__loadingCircle {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.ril__loadingCirclePoint {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
.ril__loadingCirclePoint::before {
|
||||
content: "";
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
width: 11%;
|
||||
height: 30%;
|
||||
background-color: #fff;
|
||||
border-radius: 30%;
|
||||
animation: pointFade 800ms infinite ease-in-out both;
|
||||
}
|
||||
.ril__loadingCirclePoint:nth-of-type(1) {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
.ril__loadingCirclePoint:nth-of-type(7) {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
.ril__loadingCirclePoint:nth-of-type(1)::before,
|
||||
.ril__loadingCirclePoint:nth-of-type(7)::before {
|
||||
animation-delay: -800ms;
|
||||
}
|
||||
.ril__loadingCirclePoint:nth-of-type(2) {
|
||||
transform: rotate(30deg);
|
||||
}
|
||||
.ril__loadingCirclePoint:nth-of-type(8) {
|
||||
transform: rotate(210deg);
|
||||
}
|
||||
.ril__loadingCirclePoint:nth-of-type(2)::before,
|
||||
.ril__loadingCirclePoint:nth-of-type(8)::before {
|
||||
animation-delay: -666ms;
|
||||
}
|
||||
.ril__loadingCirclePoint:nth-of-type(3) {
|
||||
transform: rotate(60deg);
|
||||
}
|
||||
.ril__loadingCirclePoint:nth-of-type(9) {
|
||||
transform: rotate(240deg);
|
||||
}
|
||||
.ril__loadingCirclePoint:nth-of-type(3)::before,
|
||||
.ril__loadingCirclePoint:nth-of-type(9)::before {
|
||||
animation-delay: -533ms;
|
||||
}
|
||||
.ril__loadingCirclePoint:nth-of-type(4) {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
.ril__loadingCirclePoint:nth-of-type(10) {
|
||||
transform: rotate(270deg);
|
||||
}
|
||||
.ril__loadingCirclePoint:nth-of-type(4)::before,
|
||||
.ril__loadingCirclePoint:nth-of-type(10)::before {
|
||||
animation-delay: -400ms;
|
||||
}
|
||||
.ril__loadingCirclePoint:nth-of-type(5) {
|
||||
transform: rotate(120deg);
|
||||
}
|
||||
.ril__loadingCirclePoint:nth-of-type(11) {
|
||||
transform: rotate(300deg);
|
||||
}
|
||||
.ril__loadingCirclePoint:nth-of-type(5)::before,
|
||||
.ril__loadingCirclePoint:nth-of-type(11)::before {
|
||||
animation-delay: -266ms;
|
||||
}
|
||||
.ril__loadingCirclePoint:nth-of-type(6) {
|
||||
transform: rotate(150deg);
|
||||
}
|
||||
.ril__loadingCirclePoint:nth-of-type(12) {
|
||||
transform: rotate(330deg);
|
||||
}
|
||||
.ril__loadingCirclePoint:nth-of-type(6)::before,
|
||||
.ril__loadingCirclePoint:nth-of-type(12)::before {
|
||||
animation-delay: -133ms;
|
||||
}
|
||||
.ril__loadingCirclePoint:nth-of-type(7) {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
.ril__loadingCirclePoint:nth-of-type(13) {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
.ril__loadingCirclePoint:nth-of-type(7)::before,
|
||||
.ril__loadingCirclePoint:nth-of-type(13)::before {
|
||||
animation-delay: 0ms;
|
||||
}
|
||||
|
||||
.ril__loadingContainer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
}
|
||||
.ril__imagePrev .ril__loadingContainer,
|
||||
.ril__imageNext .ril__loadingContainer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ril__errorContainer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #fff;
|
||||
}
|
||||
.ril__imagePrev .ril__errorContainer,
|
||||
.ril__imageNext .ril__errorContainer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ril__loadingContainer__icon {
|
||||
color: #fff;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%) translateY(-50%);
|
||||
}
|
||||
|
@ -18,6 +18,17 @@ const breakDektop = { min: "60rem" };
|
||||
const breakMobile = { max: "60rem" };
|
||||
const breakThin = { max: "25rem" };
|
||||
|
||||
const fontStandard = {
|
||||
body: "Zen Maru Gothic",
|
||||
headers: "Vollkorn",
|
||||
monospace: "monospace",
|
||||
};
|
||||
const fontDyslexic = {
|
||||
body: "OpenDyslexic",
|
||||
headers: "OpenDyslexic",
|
||||
monospace: "monospace",
|
||||
};
|
||||
|
||||
/* END CONFIG */
|
||||
|
||||
function withOpacity(variableName) {
|
||||
@ -41,9 +52,11 @@ module.exports = {
|
||||
black: withOpacity("--theme-color-black"),
|
||||
},
|
||||
fontFamily: {
|
||||
body: ["Zen Maru Gothic"],
|
||||
headers: ["Vollkorn"],
|
||||
monospace: ["monospace"],
|
||||
body: ["var(--theme-font-body)"],
|
||||
headers: ["var(--theme-font-headers)"],
|
||||
monospace: ["var(--theme-font-monospace)"],
|
||||
openDyslexic: ["OpenDyslexic"],
|
||||
zenMaruGothic: ["Zen Maru Gothic"],
|
||||
},
|
||||
screens: {
|
||||
desktop: breakDektop,
|
||||
@ -59,9 +72,6 @@ module.exports = {
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
require("@tailwindcss/typography"),
|
||||
|
||||
// Colored Dropshadow
|
||||
plugin(function ({ addUtilities }) {
|
||||
addUtilities({
|
||||
".set-theme-light": {
|
||||
@ -85,6 +95,21 @@ module.exports = {
|
||||
});
|
||||
}),
|
||||
|
||||
plugin(function ({ addUtilities }) {
|
||||
addUtilities({
|
||||
".set-theme-font-standard": {
|
||||
"--theme-font-body": fontStandard.body,
|
||||
"--theme-font-headers": fontStandard.headers,
|
||||
"--theme-font-monospace": fontStandard.monospace,
|
||||
},
|
||||
".set-theme-font-dyslexic": {
|
||||
"--theme-font-body": fontDyslexic.body,
|
||||
"--theme-font-headers": fontDyslexic.headers,
|
||||
"--theme-font-monospace": fontStandard.monospace,
|
||||
},
|
||||
});
|
||||
}),
|
||||
|
||||
plugin(function ({ addVariant, e }) {
|
||||
addVariant("webkit-scrollbar", ({ modifySelectors, separator }) => {
|
||||
modifySelectors(({ className }) => {
|
||||
|
Loading…
x
Reference in New Issue
Block a user