Added reader
|
@ -19,10 +19,12 @@
|
||||||
"meilisearch": "^0.27.0",
|
"meilisearch": "^0.27.0",
|
||||||
"next": "^12.3.0",
|
"next": "^12.3.0",
|
||||||
"nodemailer": "^6.7.8",
|
"nodemailer": "^6.7.8",
|
||||||
|
"rc-slider": "^10.0.1",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"react-hot-keys": "^2.7.2",
|
"react-hot-keys": "^2.7.2",
|
||||||
"react-swipeable": "^7.0.0",
|
"react-swipeable": "^7.0.0",
|
||||||
|
"react-zoom-pan-pinch": "^2.1.3",
|
||||||
"string-natural-compare": "^3.0.1",
|
"string-natural-compare": "^3.0.1",
|
||||||
"throttle-debounce": "^5.0.0",
|
"throttle-debounce": "^5.0.0",
|
||||||
"tippy.js": "^6.3.7",
|
"tippy.js": "^6.3.7",
|
||||||
|
@ -979,9 +981,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/runtime": {
|
"node_modules/@babel/runtime": {
|
||||||
"version": "7.17.2",
|
"version": "7.19.0",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.2.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.19.0.tgz",
|
||||||
"integrity": "sha512-hzeyJyMA1YGdJTuWU0e/j4wKXrU4OMFvY2MSlaI9B7VQb0r5cxTE3EAIS2Q7Tn2RIcDkRvTA/v2JsAEhxe99uw==",
|
"integrity": "sha512-eR8Lo9hnDS7tqkO7NsV+mKvCmv5boaXFSZ70DnfhcgiEne8hv9oCEd36Klw74EtizEqLsy4YnW8UWwpBVolHZA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"regenerator-runtime": "^0.13.4"
|
"regenerator-runtime": "^0.13.4"
|
||||||
},
|
},
|
||||||
|
@ -3675,6 +3677,11 @@
|
||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/classnames": {
|
||||||
|
"version": "2.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz",
|
||||||
|
"integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw=="
|
||||||
|
},
|
||||||
"node_modules/clean-stack": {
|
"node_modules/clean-stack": {
|
||||||
"version": "2.2.0",
|
"version": "2.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz",
|
||||||
|
@ -7676,6 +7683,38 @@
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/rc-slider": {
|
||||||
|
"version": "10.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/rc-slider/-/rc-slider-10.0.1.tgz",
|
||||||
|
"integrity": "sha512-igTKF3zBet7oS/3yNiIlmU8KnZ45npmrmHlUUio8PNbIhzMcsh+oE/r2UD42Y6YD2D/s+kzCQkzQrPD6RY435Q==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.10.1",
|
||||||
|
"classnames": "^2.2.5",
|
||||||
|
"rc-util": "^5.18.1",
|
||||||
|
"shallowequal": "^1.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.x"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.9.0",
|
||||||
|
"react-dom": ">=16.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/rc-util": {
|
||||||
|
"version": "5.24.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/rc-util/-/rc-util-5.24.4.tgz",
|
||||||
|
"integrity": "sha512-2a4RQnycV9eV7lVZPEJ7QwJRPlZNc06J7CwcwZo4vIHr3PfUqtYgl1EkUV9ETAc6VRRi8XZOMFhYG63whlIC9Q==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.18.3",
|
||||||
|
"react-is": "^16.12.0",
|
||||||
|
"shallowequal": "^1.1.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.9.0",
|
||||||
|
"react-dom": ">=16.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react": {
|
"node_modules/react": {
|
||||||
"version": "18.2.0",
|
"version": "18.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
|
||||||
|
@ -7726,6 +7765,19 @@
|
||||||
"react": "^16.8.3 || ^17 || ^18"
|
"react": "^16.8.3 || ^17 || ^18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-zoom-pan-pinch": {
|
||||||
|
"version": "2.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-zoom-pan-pinch/-/react-zoom-pan-pinch-2.1.3.tgz",
|
||||||
|
"integrity": "sha512-a5AChOWhjo0RmxsNZXGQIlNh3e3nLU6m4V6M+6dlbPNk5d+MtMxgKWyA5zpR06Lp3OZkZVF9nR8JeWSvKwck9g==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8",
|
||||||
|
"npm": ">=5"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^17.0.2",
|
||||||
|
"react-dom": "^17.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/read-cache": {
|
"node_modules/read-cache": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
||||||
|
@ -8043,6 +8095,11 @@
|
||||||
"integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==",
|
"integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/shallowequal": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ=="
|
||||||
|
},
|
||||||
"node_modules/shebang-command": {
|
"node_modules/shebang-command": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||||
|
@ -9755,9 +9812,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@babel/runtime": {
|
"@babel/runtime": {
|
||||||
"version": "7.17.2",
|
"version": "7.19.0",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.2.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.19.0.tgz",
|
||||||
"integrity": "sha512-hzeyJyMA1YGdJTuWU0e/j4wKXrU4OMFvY2MSlaI9B7VQb0r5cxTE3EAIS2Q7Tn2RIcDkRvTA/v2JsAEhxe99uw==",
|
"integrity": "sha512-eR8Lo9hnDS7tqkO7NsV+mKvCmv5boaXFSZ70DnfhcgiEne8hv9oCEd36Klw74EtizEqLsy4YnW8UWwpBVolHZA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"regenerator-runtime": "^0.13.4"
|
"regenerator-runtime": "^0.13.4"
|
||||||
}
|
}
|
||||||
|
@ -11860,6 +11917,11 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"classnames": {
|
||||||
|
"version": "2.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz",
|
||||||
|
"integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw=="
|
||||||
|
},
|
||||||
"clean-stack": {
|
"clean-stack": {
|
||||||
"version": "2.2.0",
|
"version": "2.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz",
|
||||||
|
@ -14751,6 +14813,27 @@
|
||||||
"integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==",
|
"integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"rc-slider": {
|
||||||
|
"version": "10.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/rc-slider/-/rc-slider-10.0.1.tgz",
|
||||||
|
"integrity": "sha512-igTKF3zBet7oS/3yNiIlmU8KnZ45npmrmHlUUio8PNbIhzMcsh+oE/r2UD42Y6YD2D/s+kzCQkzQrPD6RY435Q==",
|
||||||
|
"requires": {
|
||||||
|
"@babel/runtime": "^7.10.1",
|
||||||
|
"classnames": "^2.2.5",
|
||||||
|
"rc-util": "^5.18.1",
|
||||||
|
"shallowequal": "^1.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"rc-util": {
|
||||||
|
"version": "5.24.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/rc-util/-/rc-util-5.24.4.tgz",
|
||||||
|
"integrity": "sha512-2a4RQnycV9eV7lVZPEJ7QwJRPlZNc06J7CwcwZo4vIHr3PfUqtYgl1EkUV9ETAc6VRRi8XZOMFhYG63whlIC9Q==",
|
||||||
|
"requires": {
|
||||||
|
"@babel/runtime": "^7.18.3",
|
||||||
|
"react-is": "^16.12.0",
|
||||||
|
"shallowequal": "^1.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"react": {
|
"react": {
|
||||||
"version": "18.2.0",
|
"version": "18.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
|
||||||
|
@ -14788,6 +14871,12 @@
|
||||||
"integrity": "sha512-NI7KGfQ6gwNFN0Hor3vytYW3iRfMMaivGEuxcADOOfBCx/kqwXE8IfHFxEcxSUkxCYf38COLKYd9EMYZghqaUA==",
|
"integrity": "sha512-NI7KGfQ6gwNFN0Hor3vytYW3iRfMMaivGEuxcADOOfBCx/kqwXE8IfHFxEcxSUkxCYf38COLKYd9EMYZghqaUA==",
|
||||||
"requires": {}
|
"requires": {}
|
||||||
},
|
},
|
||||||
|
"react-zoom-pan-pinch": {
|
||||||
|
"version": "2.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-zoom-pan-pinch/-/react-zoom-pan-pinch-2.1.3.tgz",
|
||||||
|
"integrity": "sha512-a5AChOWhjo0RmxsNZXGQIlNh3e3nLU6m4V6M+6dlbPNk5d+MtMxgKWyA5zpR06Lp3OZkZVF9nR8JeWSvKwck9g==",
|
||||||
|
"requires": {}
|
||||||
|
},
|
||||||
"read-cache": {
|
"read-cache": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
||||||
|
@ -15026,6 +15115,11 @@
|
||||||
"integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==",
|
"integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"shallowequal": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ=="
|
||||||
|
},
|
||||||
"shebang-command": {
|
"shebang-command": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||||
|
|
|
@ -30,10 +30,12 @@
|
||||||
"meilisearch": "^0.27.0",
|
"meilisearch": "^0.27.0",
|
||||||
"next": "^12.3.0",
|
"next": "^12.3.0",
|
||||||
"nodemailer": "^6.7.8",
|
"nodemailer": "^6.7.8",
|
||||||
|
"rc-slider": "^10.0.1",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"react-hot-keys": "^2.7.2",
|
"react-hot-keys": "^2.7.2",
|
||||||
"react-swipeable": "^7.0.0",
|
"react-swipeable": "^7.0.0",
|
||||||
|
"react-zoom-pan-pinch": "^2.1.3",
|
||||||
"string-natural-compare": "^3.0.1",
|
"string-natural-compare": "^3.0.1",
|
||||||
"throttle-debounce": "^5.0.0",
|
"throttle-debounce": "^5.0.0",
|
||||||
"tippy.js": "^6.3.7",
|
"tippy.js": "^6.3.7",
|
||||||
|
@ -67,5 +69,11 @@
|
||||||
"tailwindcss": "^3.1.8",
|
"tailwindcss": "^3.1.8",
|
||||||
"ts-unused-exports": "^8.0.0",
|
"ts-unused-exports": "^8.0.0",
|
||||||
"typescript": "^4.8.3"
|
"typescript": "^4.8.3"
|
||||||
|
},
|
||||||
|
"overrides": {
|
||||||
|
"react-zoom-pan-pinch": {
|
||||||
|
"react": "$react",
|
||||||
|
"react-dom": "$react-dom"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 167 KiB |
After Width: | Height: | Size: 70 KiB |
After Width: | Height: | Size: 94 KiB |
After Width: | Height: | Size: 6.6 KiB |
After Width: | Height: | Size: 2.6 KiB |
|
@ -246,13 +246,12 @@ export const AppLayout = ({
|
||||||
<div
|
<div
|
||||||
id={Ids.SubPanel}
|
id={Ids.SubPanel}
|
||||||
className={cJoin(
|
className={cJoin(
|
||||||
`texture-paper-dots overflow-y-scroll border-r-[1px] border-dark/50 bg-light
|
`texture-paper-dots z-20 overflow-y-scroll border-r-[1px] border-dark/50
|
||||||
transition-transform duration-300 [scrollbar-width:none]
|
bg-light transition-transform duration-300 [scrollbar-width:none]
|
||||||
webkit-scrollbar:w-0`,
|
webkit-scrollbar:w-0`,
|
||||||
cIf(
|
cIf(
|
||||||
is1ColumnLayout,
|
is1ColumnLayout,
|
||||||
`z-10 justify-self-end border-r-0
|
"justify-self-end border-r-0 [grid-area:content]",
|
||||||
[grid-area:content]`,
|
|
||||||
"[grid-area:sub]"
|
"[grid-area:sub]"
|
||||||
),
|
),
|
||||||
cIf(is1ColumnLayout && isScreenAtLeastXs, "w-[min(30rem,90%)] border-l-[1px]"),
|
cIf(is1ColumnLayout && isScreenAtLeastXs, "w-[min(30rem,90%)] border-l-[1px]"),
|
||||||
|
@ -266,9 +265,9 @@ export const AppLayout = ({
|
||||||
{/* Main panel */}
|
{/* Main panel */}
|
||||||
<div
|
<div
|
||||||
className={cJoin(
|
className={cJoin(
|
||||||
`texture-paper-dots overflow-y-scroll border-r-[1px] border-dark/50 bg-light
|
`texture-paper-dots z-30 overflow-y-scroll border-r-[1px] border-dark/50
|
||||||
transition-transform duration-300 [scrollbar-width:none] webkit-scrollbar:w-0`,
|
bg-light transition-transform duration-300 [scrollbar-width:none] webkit-scrollbar:w-0`,
|
||||||
cIf(is1ColumnLayout, "z-10 justify-self-start [grid-area:content]", "[grid-area:main]"),
|
cIf(is1ColumnLayout, "justify-self-start [grid-area:content]", "[grid-area:main]"),
|
||||||
cIf(is1ColumnLayout && isScreenAtLeastXs, "w-[min(30rem,90%)]"),
|
cIf(is1ColumnLayout && isScreenAtLeastXs, "w-[min(30rem,90%)]"),
|
||||||
cIf(!mainPanelOpen && is1ColumnLayout, "-translate-x-full")
|
cIf(!mainPanelOpen && is1ColumnLayout, "-translate-x-full")
|
||||||
)}>
|
)}>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { DetailedHTMLProps, ImgHTMLAttributes } from "react";
|
import { DetailedHTMLProps, ImgHTMLAttributes } from "react";
|
||||||
import { UploadImageFragment } from "graphql/generated";
|
import { UploadImageFragment } from "graphql/generated";
|
||||||
import { getAssetURL, ImageQuality } from "helpers/img";
|
import { getAssetURL, getImgSizesByQuality, ImageQuality } from "helpers/img";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* ╭─────────────╮
|
* ╭─────────────╮
|
||||||
|
@ -11,18 +11,36 @@ interface Props
|
||||||
extends Omit<DetailedHTMLProps<ImgHTMLAttributes<HTMLImageElement>, HTMLImageElement>, "src"> {
|
extends Omit<DetailedHTMLProps<ImgHTMLAttributes<HTMLImageElement>, HTMLImageElement>, "src"> {
|
||||||
src: UploadImageFragment | string;
|
src: UploadImageFragment | string;
|
||||||
quality?: ImageQuality;
|
quality?: ImageQuality;
|
||||||
|
sizeMultiplicator?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||||
|
|
||||||
export const Img = ({
|
export const Img = ({
|
||||||
className,
|
className,
|
||||||
src: rawSrc,
|
src: propsSrc,
|
||||||
quality = ImageQuality.Small,
|
quality = ImageQuality.Small,
|
||||||
alt,
|
alt,
|
||||||
loading = "lazy",
|
loading = "lazy",
|
||||||
|
height,
|
||||||
|
width,
|
||||||
...otherProps
|
...otherProps
|
||||||
}: Props): JSX.Element => {
|
}: Props): JSX.Element => {
|
||||||
const src = typeof rawSrc === "string" ? rawSrc : getAssetURL(rawSrc.url, quality);
|
const src = typeof propsSrc === "string" ? propsSrc : getAssetURL(propsSrc.url, quality);
|
||||||
return <img className={className} src={src} alt={alt} loading={loading} {...otherProps} />;
|
const size =
|
||||||
|
typeof propsSrc === "string"
|
||||||
|
? { width, height }
|
||||||
|
: getImgSizesByQuality(propsSrc.width ?? 0, propsSrc.height ?? 0, quality);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<img
|
||||||
|
className={className}
|
||||||
|
src={src}
|
||||||
|
alt={alt}
|
||||||
|
loading={loading}
|
||||||
|
height={size.height}
|
||||||
|
width={size.width}
|
||||||
|
{...otherProps}
|
||||||
|
/>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -30,7 +30,7 @@ export const ContentPanel = ({
|
||||||
<div className="grid h-full">
|
<div className="grid h-full">
|
||||||
<main
|
<main
|
||||||
className={cJoin(
|
className={cJoin(
|
||||||
"justify-self-center px-4 pt-10 pb-20",
|
"justify-self-center px-4 pt-10 pb-20 relative",
|
||||||
cIf(isContentPanelAtLeast3xl, "px-10 pt-20 pb-32"),
|
cIf(isContentPanelAtLeast3xl, "px-10 pt-20 pb-32"),
|
||||||
width === ContentPanelWidthSizes.Default
|
width === ContentPanelWidthSizes.Default
|
||||||
? "max-w-2xl"
|
? "max-w-2xl"
|
||||||
|
|
|
@ -47,6 +47,34 @@ query getLibraryItemScans($slug: String, $language_code: String) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cover {
|
cover {
|
||||||
|
front {
|
||||||
|
data {
|
||||||
|
attributes {
|
||||||
|
...uploadImage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
spine {
|
||||||
|
data {
|
||||||
|
attributes {
|
||||||
|
...uploadImage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
back {
|
||||||
|
data {
|
||||||
|
attributes {
|
||||||
|
...uploadImage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
front {
|
||||||
|
data {
|
||||||
|
attributes {
|
||||||
|
...uploadImage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
full {
|
full {
|
||||||
data {
|
data {
|
||||||
attributes {
|
attributes {
|
||||||
|
@ -61,6 +89,20 @@ query getLibraryItemScans($slug: String, $language_code: String) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
inside_front {
|
||||||
|
data {
|
||||||
|
attributes {
|
||||||
|
...uploadImage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inside_back {
|
||||||
|
data {
|
||||||
|
attributes {
|
||||||
|
...uploadImage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
dust_jacket {
|
dust_jacket {
|
||||||
full {
|
full {
|
||||||
|
@ -108,6 +150,9 @@ query getLibraryItemScans($slug: String, $language_code: String) {
|
||||||
price {
|
price {
|
||||||
...pricePicker
|
...pricePicker
|
||||||
}
|
}
|
||||||
|
size {
|
||||||
|
width
|
||||||
|
}
|
||||||
categories(pagination: { limit: -1 }) {
|
categories(pagination: { limit: -1 }) {
|
||||||
data {
|
data {
|
||||||
id
|
id
|
||||||
|
@ -120,6 +165,7 @@ query getLibraryItemScans($slug: String, $language_code: String) {
|
||||||
metadata {
|
metadata {
|
||||||
__typename
|
__typename
|
||||||
... on ComponentMetadataBooks {
|
... on ComponentMetadataBooks {
|
||||||
|
page_order
|
||||||
subtype {
|
subtype {
|
||||||
data {
|
data {
|
||||||
attributes {
|
attributes {
|
||||||
|
|
|
@ -180,6 +180,17 @@ query localDataGetWebsiteInterfaces {
|
||||||
switch_to_grid_view
|
switch_to_grid_view
|
||||||
switch_to_folder_view
|
switch_to_folder_view
|
||||||
content_is_not_available
|
content_is_not_available
|
||||||
|
paper_texture
|
||||||
|
book_fold
|
||||||
|
lighting
|
||||||
|
side_pages
|
||||||
|
shadow
|
||||||
|
night_reader
|
||||||
|
single_page_view
|
||||||
|
double_page_view
|
||||||
|
reset_all_options
|
||||||
|
reading_layout
|
||||||
|
quality
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,10 +58,10 @@ const getImgSizesByMaxSize = (
|
||||||
): { width: number; height: number } => {
|
): { width: number; height: number } => {
|
||||||
if (width > height) {
|
if (width > height) {
|
||||||
if (width < maxSize) return { width: width, height: height };
|
if (width < maxSize) return { width: width, height: height };
|
||||||
return { width: maxSize, height: (height / width) * maxSize };
|
return { width: maxSize, height: Math.ceil((height / width) * maxSize) };
|
||||||
}
|
}
|
||||||
if (height < maxSize) return { width: width, height: height };
|
if (height < maxSize) return { width: width, height: height };
|
||||||
return { width: (width / height) * maxSize, height: maxSize };
|
return { width: Math.ceil((width / height) * maxSize), height: maxSize };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getImgSizesByQuality = (
|
export const getImgSizesByQuality = (
|
||||||
|
|
|
@ -20,3 +20,6 @@ export const randomInt = (min: number, max: number): number =>
|
||||||
Math.floor(Math.random() * (max - min)) + min;
|
Math.floor(Math.random() * (max - min)) + min;
|
||||||
|
|
||||||
export const isInteger = (value: string): boolean => /^[+-]?[0-9]+$/u.test(value);
|
export const isInteger = (value: string): boolean => /^[+-]?[0-9]+$/u.test(value);
|
||||||
|
|
||||||
|
export const clamp = (value: number, min: number, max: number): number =>
|
||||||
|
Math.min(Math.max(value, min), max);
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
|
import { useIsClient } from "usehooks-ts";
|
||||||
|
import { isDefined } from "helpers/others";
|
||||||
|
|
||||||
|
export const useFullscreen = (
|
||||||
|
id: string
|
||||||
|
): { isFullscreen: boolean; toggleFullscreen: () => void } => {
|
||||||
|
const [isFullscreen, setIsFullscreen] = useState(false);
|
||||||
|
const isClient = useIsClient();
|
||||||
|
|
||||||
|
const elem = useMemo(() => (isClient ? document.querySelector(`#${id}`) : null), [id, isClient]);
|
||||||
|
|
||||||
|
const toggleFullscreen = useCallback(() => {
|
||||||
|
if (elem) {
|
||||||
|
if (isFullscreen) {
|
||||||
|
document.exitFullscreen();
|
||||||
|
} else {
|
||||||
|
elem.requestFullscreen();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [elem, isFullscreen]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const onFullscreenChanged = () => {
|
||||||
|
setIsFullscreen(isDefined(document.fullscreenElement));
|
||||||
|
};
|
||||||
|
addEventListener("fullscreenchange", onFullscreenChanged);
|
||||||
|
return () => removeEventListener("fullscreenchange", onFullscreenChanged);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return { isFullscreen, toggleFullscreen };
|
||||||
|
};
|
|
@ -510,7 +510,9 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
||||||
<div id={intersectionIds[4]} className="grid w-full place-items-center gap-8">
|
<div id={intersectionIds[4]} className="grid w-full place-items-center gap-8">
|
||||||
<h2 className="-mb-6 text-2xl">{langui.contents}</h2>
|
<h2 className="-mb-6 text-2xl">{langui.contents}</h2>
|
||||||
{displayOpenScans && (
|
{displayOpenScans && (
|
||||||
<Button href={`/library/${item.slug}/scans`} text={langui.view_scans} />
|
<div className="grid grid-flow-col gap-4">
|
||||||
|
<Button href={`/library/${item.slug}/reader`} text={langui.view_scans} />
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="max-w- grid w-full gap-4">
|
<div className="max-w- grid w-full gap-4">
|
||||||
{filterHasAttributes(item.contents.data, ["attributes"] as const).map(
|
{filterHasAttributes(item.contents.data, ["attributes"] as const).map(
|
||||||
|
@ -735,7 +737,10 @@ const ContentLine = ({
|
||||||
{hasScanSet || isDefined(content) ? (
|
{hasScanSet || isDefined(content) ? (
|
||||||
<>
|
<>
|
||||||
{hasScanSet && (
|
{hasScanSet && (
|
||||||
<Button href={`/library/${parentSlug}/scans#${slug}`} text={langui.view_scans} />
|
<Button
|
||||||
|
href={`/library/${parentSlug}/reader?page=${rangeStart}`}
|
||||||
|
text={langui.view_scans}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
{isDefined(content) && (
|
{isDefined(content) && (
|
||||||
<Button href={`/contents/${content.slug}`} text={langui.open_content} />
|
<Button href={`/contents/${content.slug}`} text={langui.open_content} />
|
||||||
|
@ -787,7 +792,10 @@ const ContentLine = ({
|
||||||
{hasScanSet || isDefined(content) ? (
|
{hasScanSet || isDefined(content) ? (
|
||||||
<>
|
<>
|
||||||
{hasScanSet && (
|
{hasScanSet && (
|
||||||
<Button href={`/library/${parentSlug}/scans#${slug}`} text={langui.view_scans} />
|
<Button
|
||||||
|
href={`/library/${parentSlug}/reader?page=${rangeStart}`}
|
||||||
|
text={langui.view_scans}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
{isDefined(content) && (
|
{isDefined(content) && (
|
||||||
<Button href={`/contents/${content.slug}`} text={langui.open_content} />
|
<Button href={`/contents/${content.slug}`} text={langui.open_content} />
|
||||||
|
|
|
@ -1,636 +0,0 @@
|
||||||
import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next";
|
|
||||||
import { Fragment, useCallback, useMemo } from "react";
|
|
||||||
import { AppLayout, AppLayoutRequired } from "components/AppLayout";
|
|
||||||
import { ReturnButton } from "components/PanelComponents/ReturnButton";
|
|
||||||
import { ContentPanel, ContentPanelWidthSizes } from "components/Panels/ContentPanel";
|
|
||||||
import { SubPanel } from "components/Panels/SubPanel";
|
|
||||||
import { GetLibraryItemScansQuery, UploadImageFragment } from "graphql/generated";
|
|
||||||
import { getReadySdk } from "graphql/sdk";
|
|
||||||
import { prettyInlineTitle, prettySlug, prettyItemSubType } from "helpers/formatters";
|
|
||||||
import {
|
|
||||||
filterHasAttributes,
|
|
||||||
getStatusDescription,
|
|
||||||
isDefined,
|
|
||||||
isDefinedAndNotEmpty,
|
|
||||||
sortRangedContent,
|
|
||||||
} from "helpers/others";
|
|
||||||
import { useLightBox } from "hooks/useLightBox";
|
|
||||||
import { isUntangibleGroupItem } from "helpers/libraryItem";
|
|
||||||
import { PreviewCardCTAs } from "components/Library/PreviewCardCTAs";
|
|
||||||
import { PreviewCard } from "components/PreviewCard";
|
|
||||||
import { HorizontalLine } from "components/HorizontalLine";
|
|
||||||
import { getOpenGraph } from "helpers/openGraph";
|
|
||||||
import { Chip } from "components/Chip";
|
|
||||||
import { Img } from "components/Img";
|
|
||||||
import { Button } from "components/Inputs/Button";
|
|
||||||
import { RecorderChip } from "components/RecorderChip";
|
|
||||||
import { ToolTip } from "components/ToolTip";
|
|
||||||
import { getAssetFilename, getAssetURL, ImageQuality } from "helpers/img";
|
|
||||||
import { isInteger } from "helpers/numbers";
|
|
||||||
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
|
||||||
import { TranslatedProps } from "types/TranslatedProps";
|
|
||||||
import { TranslatedNavOption } from "components/PanelComponents/NavOption";
|
|
||||||
import { useIntersectionList } from "hooks/useIntersectionList";
|
|
||||||
import { useIs1ColumnLayout, useIsContentPanelNoMoreThan } from "hooks/useContainerQuery";
|
|
||||||
import { cIf, cJoin } from "helpers/className";
|
|
||||||
import { useAppLayout } from "contexts/AppLayoutContext";
|
|
||||||
import { getLangui } from "graphql/fetchLocalData";
|
|
||||||
|
|
||||||
/*
|
|
||||||
* ╭────────╮
|
|
||||||
* ──────────────────────────────────────────╯ PAGE ╰─────────────────────────────────────────────
|
|
||||||
*/
|
|
||||||
|
|
||||||
interface Props extends AppLayoutRequired {
|
|
||||||
item: NonNullable<
|
|
||||||
NonNullable<GetLibraryItemScansQuery["libraryItems"]>["data"][number]["attributes"]
|
|
||||||
>;
|
|
||||||
itemId: NonNullable<NonNullable<GetLibraryItemScansQuery["libraryItems"]>["data"][number]["id"]>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
|
||||||
const [openLightBox, LightBox] = useLightBox();
|
|
||||||
const is1ColumnLayout = useIs1ColumnLayout();
|
|
||||||
const { langui } = useAppLayout();
|
|
||||||
|
|
||||||
const ids = useMemo(
|
|
||||||
() =>
|
|
||||||
filterHasAttributes(item.contents?.data, ["attributes.slug"] as const).map(
|
|
||||||
(content) => content.attributes.slug
|
|
||||||
),
|
|
||||||
[item.contents?.data]
|
|
||||||
);
|
|
||||||
const currentIntersection = useIntersectionList(ids);
|
|
||||||
|
|
||||||
const subPanel = useMemo(
|
|
||||||
() => (
|
|
||||||
<SubPanel>
|
|
||||||
<ReturnButton
|
|
||||||
href={`/library/${item.slug}`}
|
|
||||||
title={langui.item}
|
|
||||||
className="mb-4"
|
|
||||||
displayOnlyOn="3ColumnsLayout"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className="grid place-items-center">
|
|
||||||
<div className={cIf(is1ColumnLayout, "w-3/4")}>
|
|
||||||
<PreviewCard
|
|
||||||
href={`/library/${item.slug}`}
|
|
||||||
title={item.title}
|
|
||||||
subtitle={item.subtitle}
|
|
||||||
thumbnail={item.thumbnail?.data?.attributes}
|
|
||||||
thumbnailAspectRatio="21/29.7"
|
|
||||||
thumbnailRounded={false}
|
|
||||||
topChips={
|
|
||||||
item.metadata && item.metadata.length > 0 && item.metadata[0]
|
|
||||||
? [prettyItemSubType(item.metadata[0])]
|
|
||||||
: []
|
|
||||||
}
|
|
||||||
bottomChips={filterHasAttributes(item.categories?.data, ["attributes"] as const).map(
|
|
||||||
(category) => category.attributes.short
|
|
||||||
)}
|
|
||||||
metadata={{
|
|
||||||
releaseDate: item.release_date,
|
|
||||||
price: item.price,
|
|
||||||
position: "Bottom",
|
|
||||||
}}
|
|
||||||
infoAppend={
|
|
||||||
!isUntangibleGroupItem(item.metadata?.[0]) && <PreviewCardCTAs id={itemId} />
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<HorizontalLine />
|
|
||||||
|
|
||||||
<p className="mb-4 font-headers text-2xl font-bold">{langui.contents}</p>
|
|
||||||
|
|
||||||
{filterHasAttributes(item.contents?.data, ["attributes"] as const).map((content, index) => (
|
|
||||||
<>
|
|
||||||
{content.attributes.scan_set && content.attributes.scan_set.length > 0 && (
|
|
||||||
<TranslatedNavOption
|
|
||||||
key={content.id}
|
|
||||||
url={`#${content.attributes.slug}`}
|
|
||||||
translations={filterHasAttributes(
|
|
||||||
content.attributes.content?.data?.attributes?.translations,
|
|
||||||
["language.data.attributes"] as const
|
|
||||||
).map((translation) => ({
|
|
||||||
language: translation.language.data.attributes.code,
|
|
||||||
title: prettyInlineTitle(
|
|
||||||
translation.pre_title,
|
|
||||||
translation.title,
|
|
||||||
translation.subtitle
|
|
||||||
),
|
|
||||||
subtitle:
|
|
||||||
content.attributes.range[0]?.__typename === "ComponentRangePageRange"
|
|
||||||
? `${content.attributes.range[0].starting_page}` +
|
|
||||||
`→` +
|
|
||||||
`${content.attributes.range[0].ending_page}`
|
|
||||||
: undefined,
|
|
||||||
}))}
|
|
||||||
fallback={{
|
|
||||||
title: prettySlug(content.attributes.slug, item.slug),
|
|
||||||
subtitle:
|
|
||||||
content.attributes.range[0]?.__typename === "ComponentRangePageRange"
|
|
||||||
? `${content.attributes.range[0].starting_page}` +
|
|
||||||
`→` +
|
|
||||||
`${content.attributes.range[0].ending_page}`
|
|
||||||
: undefined,
|
|
||||||
}}
|
|
||||||
border
|
|
||||||
active={index === currentIntersection}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
))}
|
|
||||||
</SubPanel>
|
|
||||||
),
|
|
||||||
[
|
|
||||||
currentIntersection,
|
|
||||||
item.categories?.data,
|
|
||||||
item.contents?.data,
|
|
||||||
item.metadata,
|
|
||||||
item.price,
|
|
||||||
item.release_date,
|
|
||||||
item.slug,
|
|
||||||
item.subtitle,
|
|
||||||
item.thumbnail?.data?.attributes,
|
|
||||||
item.title,
|
|
||||||
itemId,
|
|
||||||
langui,
|
|
||||||
is1ColumnLayout,
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
const contentPanel = useMemo(
|
|
||||||
() => (
|
|
||||||
<ContentPanel width={ContentPanelWidthSizes.Full}>
|
|
||||||
<LightBox />
|
|
||||||
|
|
||||||
<ReturnButton
|
|
||||||
href={`/library/${item.slug}`}
|
|
||||||
title={langui.item}
|
|
||||||
displayOnlyOn="1ColumnLayout"
|
|
||||||
className="mb-10"
|
|
||||||
/>
|
|
||||||
|
|
||||||
{item.images && <ScanSetCover images={item.images} openLightBox={openLightBox} />}
|
|
||||||
|
|
||||||
{item.contents?.data.map((content) => (
|
|
||||||
<Fragment key={content.id}>
|
|
||||||
{content.attributes?.scan_set?.[0] && (
|
|
||||||
<TranslatedScanSet
|
|
||||||
scanSet={content.attributes.scan_set}
|
|
||||||
openLightBox={openLightBox}
|
|
||||||
id={content.attributes.slug}
|
|
||||||
translations={filterHasAttributes(
|
|
||||||
content.attributes.content?.data?.attributes?.translations,
|
|
||||||
["language.data.attributes"] as const
|
|
||||||
).map((translation) => ({
|
|
||||||
language: translation.language.data.attributes.code,
|
|
||||||
title: prettyInlineTitle(
|
|
||||||
translation.pre_title,
|
|
||||||
translation.title,
|
|
||||||
translation.subtitle
|
|
||||||
),
|
|
||||||
}))}
|
|
||||||
fallback={{
|
|
||||||
title: prettySlug(content.attributes.slug, item.slug),
|
|
||||||
}}
|
|
||||||
content={content.attributes.content}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Fragment>
|
|
||||||
))}
|
|
||||||
</ContentPanel>
|
|
||||||
),
|
|
||||||
[LightBox, openLightBox, item.contents?.data, item.images, item.slug, langui]
|
|
||||||
);
|
|
||||||
|
|
||||||
return <AppLayout contentPanel={contentPanel} subPanel={subPanel} {...otherProps} />;
|
|
||||||
};
|
|
||||||
export default LibrarySlug;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* ╭──────────────────────╮
|
|
||||||
* ───────────────────────────────────╯ NEXT DATA FETCHING ╰──────────────────────────────────────
|
|
||||||
*/
|
|
||||||
|
|
||||||
export const getStaticProps: GetStaticProps = async (context) => {
|
|
||||||
const sdk = getReadySdk();
|
|
||||||
const langui = getLangui(context.locale);
|
|
||||||
const item = await sdk.getLibraryItemScans({
|
|
||||||
slug: context.params && isDefined(context.params.slug) ? context.params.slug.toString() : "",
|
|
||||||
language_code: context.locale ?? "en",
|
|
||||||
});
|
|
||||||
if (!item.libraryItems?.data[0]?.attributes || !item.libraryItems.data[0]?.id)
|
|
||||||
return { notFound: true };
|
|
||||||
sortRangedContent(item.libraryItems.data[0].attributes.contents);
|
|
||||||
|
|
||||||
const props: Props = {
|
|
||||||
item: item.libraryItems.data[0].attributes,
|
|
||||||
itemId: item.libraryItems.data[0].id,
|
|
||||||
openGraph: getOpenGraph(
|
|
||||||
langui,
|
|
||||||
item.libraryItems.data[0].attributes.title,
|
|
||||||
undefined,
|
|
||||||
item.libraryItems.data[0].attributes.thumbnail?.data?.attributes
|
|
||||||
),
|
|
||||||
};
|
|
||||||
return {
|
|
||||||
props: props,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
|
||||||
|
|
||||||
export const getStaticPaths: GetStaticPaths = async (context) => {
|
|
||||||
const sdk = getReadySdk();
|
|
||||||
const libraryItems = await sdk.getLibraryItemsSlugs({});
|
|
||||||
const paths: GetStaticPathsResult["paths"] = [];
|
|
||||||
filterHasAttributes(libraryItems.libraryItems?.data, ["attributes"] as const).map((item) => {
|
|
||||||
context.locales?.map((local) =>
|
|
||||||
paths.push({ params: { slug: item.attributes.slug }, locale: local })
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
paths,
|
|
||||||
fallback: "blocking",
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
* ╭──────────────────────╮
|
|
||||||
* ───────────────────────────────────╯ PRIVATE COMPONENTS ╰──────────────────────────────────────
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
* ╭─────────────╮
|
|
||||||
* ───────────────────────────────────────╯ COMPONENT ╰───────────────────────────────────────────
|
|
||||||
*/
|
|
||||||
|
|
||||||
interface ScanSetProps {
|
|
||||||
openLightBox: (images: string[], index?: number) => void;
|
|
||||||
scanSet: NonNullable<
|
|
||||||
NonNullable<
|
|
||||||
NonNullable<
|
|
||||||
NonNullable<
|
|
||||||
NonNullable<GetLibraryItemScansQuery["libraryItems"]>["data"][number]["attributes"]
|
|
||||||
>["contents"]
|
|
||||||
>["data"][number]["attributes"]
|
|
||||||
>["scan_set"]
|
|
||||||
>;
|
|
||||||
id: string;
|
|
||||||
title: string;
|
|
||||||
|
|
||||||
content: NonNullable<
|
|
||||||
NonNullable<
|
|
||||||
NonNullable<
|
|
||||||
NonNullable<GetLibraryItemScansQuery["libraryItems"]>["data"][number]["attributes"]
|
|
||||||
>["contents"]
|
|
||||||
>["data"][number]["attributes"]
|
|
||||||
>["content"];
|
|
||||||
}
|
|
||||||
|
|
||||||
const ScanSet = ({ openLightBox, scanSet, id, title, content }: ScanSetProps): JSX.Element => {
|
|
||||||
const is1ColumnLayout = useIsContentPanelNoMoreThan("2xl");
|
|
||||||
const { langui } = useAppLayout();
|
|
||||||
const [selectedScan, LanguageSwitcher, languageSwitcherProps] = useSmartLanguage({
|
|
||||||
items: scanSet,
|
|
||||||
languageExtractor: useCallback(
|
|
||||||
(item: NonNullable<ScanSetProps["scanSet"][number]>) => item.language?.data?.attributes?.code,
|
|
||||||
[]
|
|
||||||
),
|
|
||||||
transform: useCallback((item: NonNullable<ScanSetProps["scanSet"][number]>) => {
|
|
||||||
item.pages?.data.sort((a, b) => {
|
|
||||||
if (
|
|
||||||
a.attributes &&
|
|
||||||
b.attributes &&
|
|
||||||
isDefinedAndNotEmpty(a.attributes.url) &&
|
|
||||||
isDefinedAndNotEmpty(b.attributes.url)
|
|
||||||
) {
|
|
||||||
let aName = getAssetFilename(a.attributes.url);
|
|
||||||
let bName = getAssetFilename(b.attributes.url);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* If the number is a succession of 0s, make the number
|
|
||||||
* incrementally smaller than 0 (i.e: 00 becomes -1)
|
|
||||||
*/
|
|
||||||
if (aName.replaceAll("0", "").length === 0) {
|
|
||||||
aName = (1 - aName.length).toString(10);
|
|
||||||
}
|
|
||||||
if (bName.replaceAll("0", "").length === 0) {
|
|
||||||
bName = (1 - bName.length).toString(10);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isInteger(aName) && isInteger(bName)) {
|
|
||||||
return parseInt(aName, 10) - parseInt(bName, 10);
|
|
||||||
}
|
|
||||||
return a.attributes.url.localeCompare(b.attributes.url);
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
});
|
|
||||||
return item;
|
|
||||||
}, []),
|
|
||||||
});
|
|
||||||
|
|
||||||
const pages = useMemo(
|
|
||||||
() => filterHasAttributes(selectedScan?.pages?.data, ["attributes"]),
|
|
||||||
[selectedScan]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{selectedScan && isDefined(pages) && (
|
|
||||||
<div>
|
|
||||||
<div
|
|
||||||
className="flex flex-row flex-wrap place-items-center
|
|
||||||
gap-6 pt-10 text-base first-of-type:pt-0">
|
|
||||||
<h2 id={id} className="text-2xl">
|
|
||||||
{title}
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<Chip
|
|
||||||
text={
|
|
||||||
selectedScan.language?.data?.attributes?.code ===
|
|
||||||
selectedScan.source_language?.data?.attributes?.code
|
|
||||||
? langui.scan ?? "Scan"
|
|
||||||
: langui.scanlation ?? "Scanlation"
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex flex-row flex-wrap place-items-center gap-4 pb-6">
|
|
||||||
{content?.data?.attributes && isDefinedAndNotEmpty(content.data.attributes.slug) && (
|
|
||||||
<Button
|
|
||||||
href={`/contents/${content.data.attributes.slug}`}
|
|
||||||
text={langui.open_content}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{languageSwitcherProps.locales.size > 1 && (
|
|
||||||
<LanguageSwitcher {...languageSwitcherProps} />
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="grid place-content-center place-items-center">
|
|
||||||
<p className="font-headers font-bold">{langui.status}:</p>
|
|
||||||
<ToolTip
|
|
||||||
content={getStatusDescription(selectedScan.status, langui)}
|
|
||||||
maxWidth={"20rem"}>
|
|
||||||
<Chip text={selectedScan.status} />
|
|
||||||
</ToolTip>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{selectedScan.scanners && selectedScan.scanners.data.length > 0 && (
|
|
||||||
<div>
|
|
||||||
<p className="font-headers font-bold">{langui.scanners}:</p>
|
|
||||||
<div className="grid place-content-center place-items-center gap-2">
|
|
||||||
{filterHasAttributes(selectedScan.scanners.data, [
|
|
||||||
"id",
|
|
||||||
"attributes",
|
|
||||||
] as const).map((scanner) => (
|
|
||||||
<Fragment key={scanner.id}>
|
|
||||||
<RecorderChip recorder={scanner.attributes} />
|
|
||||||
</Fragment>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{selectedScan.cleaners && selectedScan.cleaners.data.length > 0 && (
|
|
||||||
<div>
|
|
||||||
<p className="font-headers font-bold">{langui.cleaners}:</p>
|
|
||||||
<div className="grid place-content-center place-items-center gap-2">
|
|
||||||
{filterHasAttributes(selectedScan.cleaners.data, [
|
|
||||||
"id",
|
|
||||||
"attributes",
|
|
||||||
] as const).map((cleaner) => (
|
|
||||||
<Fragment key={cleaner.id}>
|
|
||||||
<RecorderChip recorder={cleaner.attributes} />
|
|
||||||
</Fragment>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{selectedScan.typesetters && selectedScan.typesetters.data.length > 0 && (
|
|
||||||
<div>
|
|
||||||
<p className="font-headers font-bold">{langui.typesetters}:</p>
|
|
||||||
<div className="grid place-content-center place-items-center gap-2">
|
|
||||||
{filterHasAttributes(selectedScan.typesetters.data, [
|
|
||||||
"id",
|
|
||||||
"attributes",
|
|
||||||
] as const).map((typesetter) => (
|
|
||||||
<Fragment key={typesetter.id}>
|
|
||||||
<RecorderChip recorder={typesetter.attributes} />
|
|
||||||
</Fragment>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{isDefinedAndNotEmpty(selectedScan.notes) && (
|
|
||||||
<ToolTip content={selectedScan.notes}>
|
|
||||||
<Chip text={langui.notes ?? "Notes"} />
|
|
||||||
</ToolTip>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
className={cJoin(
|
|
||||||
`grid items-end gap-8 border-b-[3px] border-dotted pb-12
|
|
||||||
last-of-type:border-0`,
|
|
||||||
cIf(
|
|
||||||
is1ColumnLayout,
|
|
||||||
"grid-cols-2 gap-[4vmin]",
|
|
||||||
"grid-cols-[repeat(auto-fill,_minmax(10rem,1fr))]"
|
|
||||||
)
|
|
||||||
)}>
|
|
||||||
{pages.map((page, index) => (
|
|
||||||
<div
|
|
||||||
key={page.id}
|
|
||||||
className="cursor-pointer transition-transform
|
|
||||||
drop-shadow-shade-lg hover:scale-[1.02]"
|
|
||||||
onClick={() => {
|
|
||||||
const images = pages.map((image) =>
|
|
||||||
getAssetURL(image.attributes.url, ImageQuality.Large)
|
|
||||||
);
|
|
||||||
openLightBox(images, index);
|
|
||||||
}}>
|
|
||||||
<Img src={page.attributes} quality={ImageQuality.Small} />
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
|
||||||
|
|
||||||
const TranslatedScanSet = ({
|
|
||||||
translations,
|
|
||||||
fallback,
|
|
||||||
...otherProps
|
|
||||||
}: TranslatedProps<ScanSetProps, "title">): JSX.Element => {
|
|
||||||
const [selectedTranslation] = useSmartLanguage({
|
|
||||||
items: translations,
|
|
||||||
languageExtractor: useCallback((item: { language: string }): string => item.language, []),
|
|
||||||
});
|
|
||||||
|
|
||||||
return <ScanSet title={selectedTranslation?.title ?? fallback.title} {...otherProps} />;
|
|
||||||
};
|
|
||||||
|
|
||||||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
|
||||||
|
|
||||||
interface ScanSetCoverProps {
|
|
||||||
openLightBox: (images: string[], index?: number) => void;
|
|
||||||
images: NonNullable<
|
|
||||||
NonNullable<
|
|
||||||
NonNullable<GetLibraryItemScansQuery["libraryItems"]>["data"][number]["attributes"]
|
|
||||||
>["images"]
|
|
||||||
>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ScanSetCover = ({ openLightBox, images }: ScanSetCoverProps): JSX.Element => {
|
|
||||||
const is1ColumnLayout = useIsContentPanelNoMoreThan("4xl");
|
|
||||||
const { langui } = useAppLayout();
|
|
||||||
const [selectedScan, LanguageSwitcher, languageSwitcherProps] = useSmartLanguage({
|
|
||||||
items: images,
|
|
||||||
languageExtractor: useCallback(
|
|
||||||
(item: NonNullable<ScanSetCoverProps["images"][number]>) =>
|
|
||||||
item.language?.data?.attributes?.code,
|
|
||||||
[]
|
|
||||||
),
|
|
||||||
});
|
|
||||||
|
|
||||||
const coverImages = useMemo(() => {
|
|
||||||
const memo: UploadImageFragment[] = [];
|
|
||||||
if (selectedScan?.obi_belt?.full?.data?.attributes)
|
|
||||||
memo.push(selectedScan.obi_belt.full.data.attributes);
|
|
||||||
if (selectedScan?.obi_belt?.inside_full?.data?.attributes)
|
|
||||||
memo.push(selectedScan.obi_belt.inside_full.data.attributes);
|
|
||||||
if (selectedScan?.dust_jacket?.full?.data?.attributes)
|
|
||||||
memo.push(selectedScan.dust_jacket.full.data.attributes);
|
|
||||||
if (selectedScan?.dust_jacket?.inside_full?.data?.attributes)
|
|
||||||
memo.push(selectedScan.dust_jacket.inside_full.data.attributes);
|
|
||||||
if (selectedScan?.cover?.full?.data?.attributes)
|
|
||||||
memo.push(selectedScan.cover.full.data.attributes);
|
|
||||||
if (selectedScan?.cover?.inside_full?.data?.attributes)
|
|
||||||
memo.push(selectedScan.cover.inside_full.data.attributes);
|
|
||||||
return memo;
|
|
||||||
}, [selectedScan]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{coverImages.length > 0 && selectedScan && (
|
|
||||||
<div>
|
|
||||||
<div
|
|
||||||
className="flex flex-row flex-wrap place-items-center
|
|
||||||
gap-6 pt-10 text-base first-of-type:pt-0">
|
|
||||||
<h2 id={"cover"} className="text-2xl">
|
|
||||||
{langui.cover}
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<Chip
|
|
||||||
text={
|
|
||||||
selectedScan.language?.data?.attributes?.code ===
|
|
||||||
selectedScan.source_language?.data?.attributes?.code
|
|
||||||
? langui.scan ?? "Scan"
|
|
||||||
: langui.scanlation ?? "Scanlation"
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex flex-row flex-wrap place-items-center gap-4 pb-6">
|
|
||||||
<LanguageSwitcher {...languageSwitcherProps} />
|
|
||||||
|
|
||||||
<div className="grid place-content-center place-items-center">
|
|
||||||
<p className="font-headers font-bold">{langui.status}:</p>
|
|
||||||
<ToolTip
|
|
||||||
content={getStatusDescription(selectedScan.status, langui)}
|
|
||||||
maxWidth={"20rem"}>
|
|
||||||
<Chip text={selectedScan.status} />
|
|
||||||
</ToolTip>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{selectedScan.scanners && selectedScan.scanners.data.length > 0 && (
|
|
||||||
<div>
|
|
||||||
<p className="font-headers font-bold">{langui.scanners}:</p>
|
|
||||||
<div className="grid place-content-center place-items-center gap-2">
|
|
||||||
{filterHasAttributes(selectedScan.scanners.data, [
|
|
||||||
"id",
|
|
||||||
"attributes",
|
|
||||||
] as const).map((scanner) => (
|
|
||||||
<Fragment key={scanner.id}>
|
|
||||||
<RecorderChip recorder={scanner.attributes} />
|
|
||||||
</Fragment>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{selectedScan.cleaners && selectedScan.cleaners.data.length > 0 && (
|
|
||||||
<div>
|
|
||||||
<p className="font-headers font-bold">{langui.cleaners}:</p>
|
|
||||||
<div className="grid place-content-center place-items-center gap-2">
|
|
||||||
{filterHasAttributes(selectedScan.cleaners.data, [
|
|
||||||
"id",
|
|
||||||
"attributes",
|
|
||||||
] as const).map((cleaner) => (
|
|
||||||
<Fragment key={cleaner.id}>
|
|
||||||
<RecorderChip recorder={cleaner.attributes} />
|
|
||||||
</Fragment>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{selectedScan.typesetters && selectedScan.typesetters.data.length > 0 && (
|
|
||||||
<div>
|
|
||||||
<p className="font-headers font-bold">{langui.typesetters}:</p>
|
|
||||||
<div className="grid place-content-center place-items-center gap-2">
|
|
||||||
{filterHasAttributes(selectedScan.typesetters.data, [
|
|
||||||
"id",
|
|
||||||
"attributes",
|
|
||||||
] as const).map((typesetter) => (
|
|
||||||
<Fragment key={typesetter.id}>
|
|
||||||
<RecorderChip recorder={typesetter.attributes} />
|
|
||||||
</Fragment>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
className={cJoin(
|
|
||||||
`grid items-end gap-8 border-b-[3px] border-dotted pb-12
|
|
||||||
last-of-type:border-0`,
|
|
||||||
cIf(
|
|
||||||
is1ColumnLayout,
|
|
||||||
"grid-cols-2 gap-[4vmin]",
|
|
||||||
"grid-cols-[repeat(auto-fill,_minmax(10rem,1fr))]"
|
|
||||||
)
|
|
||||||
)}>
|
|
||||||
{coverImages.map((image, index) => (
|
|
||||||
<div
|
|
||||||
key={image.url}
|
|
||||||
className="cursor-pointer transition-transform
|
|
||||||
drop-shadow-shade-lg hover:scale-[1.02]"
|
|
||||||
onClick={() => {
|
|
||||||
const imgs = coverImages.map((img) => getAssetURL(img.url, ImageQuality.Large));
|
|
||||||
|
|
||||||
openLightBox(imgs, index);
|
|
||||||
}}>
|
|
||||||
<Img src={image} quality={ImageQuality.Small} />
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
223
src/tailwind.css
|
@ -285,3 +285,226 @@ input[type="submit"] {
|
||||||
.tippy-content {
|
.tippy-content {
|
||||||
@apply relative z-10 px-6 py-4;
|
@apply relative z-10 px-6 py-4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* RC SLIDER */
|
||||||
|
|
||||||
|
.rc-slider {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 14px;
|
||||||
|
padding: 5px 0;
|
||||||
|
border-radius: 6px;
|
||||||
|
touch-action: none;
|
||||||
|
box-sizing: border-box;
|
||||||
|
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
.rc-slider * {
|
||||||
|
box-sizing: border-box;
|
||||||
|
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
.rc-slider-rail {
|
||||||
|
@apply h-2 rounded-full bg-mid/80;
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.rc-slider-track {
|
||||||
|
@apply h-2 rounded-full bg-mid bg-dark/20 shadow-inner-sm shadow-shade;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
.rc-slider-handle {
|
||||||
|
@apply h-4 w-4 -mt-1 rounded-full bg-dark transition-shadow;
|
||||||
|
position: absolute;
|
||||||
|
cursor: grab;
|
||||||
|
touch-action: pan-x;
|
||||||
|
}
|
||||||
|
.rc-slider-handle-dragging.rc-slider-handle-dragging.rc-slider-handle-dragging {
|
||||||
|
@apply shadow-sm shadow-shade;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rc-slider-mark {
|
||||||
|
position: absolute;
|
||||||
|
top: 18px;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.rc-slider-mark-text {
|
||||||
|
position: absolute;
|
||||||
|
display: inline-block;
|
||||||
|
color: #999;
|
||||||
|
text-align: center;
|
||||||
|
vertical-align: middle;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.rc-slider-mark-text-active {
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
.rc-slider-step {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 4px;
|
||||||
|
background: transparent;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.rc-slider-dot {
|
||||||
|
position: absolute;
|
||||||
|
bottom: -2px;
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
vertical-align: middle;
|
||||||
|
background-color: #fff;
|
||||||
|
border: 2px solid #e9e9e9;
|
||||||
|
border-radius: 50%;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.rc-slider-dot-active {
|
||||||
|
border-color: #96dbfa;
|
||||||
|
}
|
||||||
|
.rc-slider-dot-reverse {
|
||||||
|
margin-right: -4px;
|
||||||
|
}
|
||||||
|
.rc-slider-disabled {
|
||||||
|
background-color: #e9e9e9;
|
||||||
|
}
|
||||||
|
.rc-slider-disabled .rc-slider-track {
|
||||||
|
background-color: #ccc;
|
||||||
|
}
|
||||||
|
.rc-slider-disabled .rc-slider-handle,
|
||||||
|
.rc-slider-disabled .rc-slider-dot {
|
||||||
|
background-color: #fff;
|
||||||
|
border-color: #ccc;
|
||||||
|
box-shadow: none;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
.rc-slider-disabled .rc-slider-mark-text,
|
||||||
|
.rc-slider-disabled .rc-slider-dot {
|
||||||
|
cursor: not-allowed !important;
|
||||||
|
}
|
||||||
|
.rc-slider-vertical {
|
||||||
|
width: 14px;
|
||||||
|
height: 100%;
|
||||||
|
padding: 0 5px;
|
||||||
|
}
|
||||||
|
.rc-slider-vertical .rc-slider-rail {
|
||||||
|
width: 4px;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.rc-slider-vertical .rc-slider-track {
|
||||||
|
bottom: 0;
|
||||||
|
left: 5px;
|
||||||
|
width: 4px;
|
||||||
|
}
|
||||||
|
.rc-slider-vertical .rc-slider-handle {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-left: -5px;
|
||||||
|
touch-action: pan-y;
|
||||||
|
}
|
||||||
|
.rc-slider-vertical .rc-slider-mark {
|
||||||
|
top: 0;
|
||||||
|
left: 18px;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.rc-slider-vertical .rc-slider-step {
|
||||||
|
width: 4px;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.rc-slider-vertical .rc-slider-dot {
|
||||||
|
margin-left: -2px;
|
||||||
|
}
|
||||||
|
.rc-slider-tooltip-zoom-down-enter,
|
||||||
|
.rc-slider-tooltip-zoom-down-appear {
|
||||||
|
display: block !important;
|
||||||
|
animation-duration: 0.3s;
|
||||||
|
animation-fill-mode: both;
|
||||||
|
animation-play-state: paused;
|
||||||
|
}
|
||||||
|
.rc-slider-tooltip-zoom-down-leave {
|
||||||
|
display: block !important;
|
||||||
|
animation-duration: 0.3s;
|
||||||
|
animation-fill-mode: both;
|
||||||
|
animation-play-state: paused;
|
||||||
|
}
|
||||||
|
.rc-slider-tooltip-zoom-down-enter.rc-slider-tooltip-zoom-down-enter-active,
|
||||||
|
.rc-slider-tooltip-zoom-down-appear.rc-slider-tooltip-zoom-down-appear-active {
|
||||||
|
animation-name: rcSliderTooltipZoomDownIn;
|
||||||
|
animation-play-state: running;
|
||||||
|
}
|
||||||
|
.rc-slider-tooltip-zoom-down-leave.rc-slider-tooltip-zoom-down-leave-active {
|
||||||
|
animation-name: rcSliderTooltipZoomDownOut;
|
||||||
|
animation-play-state: running;
|
||||||
|
}
|
||||||
|
.rc-slider-tooltip-zoom-down-enter,
|
||||||
|
.rc-slider-tooltip-zoom-down-appear {
|
||||||
|
transform: scale(0, 0);
|
||||||
|
animation-timing-function: cubic-bezier(0.23, 1, 0.32, 1);
|
||||||
|
}
|
||||||
|
.rc-slider-tooltip-zoom-down-leave {
|
||||||
|
animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06);
|
||||||
|
}
|
||||||
|
@keyframes rcSliderTooltipZoomDownIn {
|
||||||
|
0% {
|
||||||
|
transform: scale(0, 0);
|
||||||
|
transform-origin: 50% 100%;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: scale(1, 1);
|
||||||
|
transform-origin: 50% 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes rcSliderTooltipZoomDownOut {
|
||||||
|
0% {
|
||||||
|
transform: scale(1, 1);
|
||||||
|
transform-origin: 50% 100%;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: scale(0, 0);
|
||||||
|
transform-origin: 50% 100%;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.rc-slider-tooltip {
|
||||||
|
position: absolute;
|
||||||
|
top: -9999px;
|
||||||
|
left: -9999px;
|
||||||
|
visibility: visible;
|
||||||
|
box-sizing: border-box;
|
||||||
|
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
.rc-slider-tooltip * {
|
||||||
|
box-sizing: border-box;
|
||||||
|
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
.rc-slider-tooltip-hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.rc-slider-tooltip-placement-top {
|
||||||
|
padding: 4px 0 8px 0;
|
||||||
|
}
|
||||||
|
.rc-slider-tooltip-inner {
|
||||||
|
min-width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
padding: 6px 2px;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1;
|
||||||
|
text-align: center;
|
||||||
|
text-decoration: none;
|
||||||
|
background-color: #6c6c6c;
|
||||||
|
border-radius: 6px;
|
||||||
|
box-shadow: 0 0 4px #d9d9d9;
|
||||||
|
}
|
||||||
|
.rc-slider-tooltip-arrow {
|
||||||
|
position: absolute;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border-color: transparent;
|
||||||
|
border-style: solid;
|
||||||
|
}
|
||||||
|
.rc-slider-tooltip-placement-top .rc-slider-tooltip-arrow {
|
||||||
|
bottom: 4px;
|
||||||
|
left: 50%;
|
||||||
|
margin-left: -4px;
|
||||||
|
border-width: 4px 4px 0;
|
||||||
|
border-top-color: #6c6c6c;
|
||||||
|
}
|
||||||
|
|