mirror of
https://github.com/C9Glax/tranga-website.git
synced 2025-09-10 11:58:20 +02:00
install prettier
This commit is contained in:
@@ -24,31 +24,31 @@ export default tseslint.config({
|
|||||||
languageOptions: {
|
languageOptions: {
|
||||||
// other options...
|
// other options...
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
project: ["./tsconfig.node.json", "./tsconfig.app.json"],
|
||||||
tsconfigRootDir: import.meta.dirname,
|
tsconfigRootDir: import.meta.dirname,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
|
You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
// eslint.config.js
|
// eslint.config.js
|
||||||
import reactX from 'eslint-plugin-react-x'
|
import reactX from "eslint-plugin-react-x";
|
||||||
import reactDom from 'eslint-plugin-react-dom'
|
import reactDom from "eslint-plugin-react-dom";
|
||||||
|
|
||||||
export default tseslint.config({
|
export default tseslint.config({
|
||||||
plugins: {
|
plugins: {
|
||||||
// Add the react-x and react-dom plugins
|
// Add the react-x and react-dom plugins
|
||||||
'react-x': reactX,
|
"react-x": reactX,
|
||||||
'react-dom': reactDom,
|
"react-dom": reactDom,
|
||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
// other rules...
|
// other rules...
|
||||||
// Enable its recommended typescript rules
|
// Enable its recommended typescript rules
|
||||||
...reactX.configs['recommended-typescript'].rules,
|
...reactX.configs["recommended-typescript"].rules,
|
||||||
...reactDom.configs.recommended.rules,
|
...reactDom.configs.recommended.rules,
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
```
|
```
|
||||||
|
@@ -1,28 +1,28 @@
|
|||||||
import js from '@eslint/js'
|
import js from "@eslint/js";
|
||||||
import globals from 'globals'
|
import globals from "globals";
|
||||||
import reactHooks from 'eslint-plugin-react-hooks'
|
import reactHooks from "eslint-plugin-react-hooks";
|
||||||
import reactRefresh from 'eslint-plugin-react-refresh'
|
import reactRefresh from "eslint-plugin-react-refresh";
|
||||||
import tseslint from 'typescript-eslint'
|
import tseslint from "typescript-eslint";
|
||||||
|
|
||||||
export default tseslint.config(
|
export default tseslint.config(
|
||||||
{ ignores: ['dist'] },
|
{ ignores: ["dist"] },
|
||||||
{
|
{
|
||||||
extends: [js.configs.recommended, ...tseslint.configs.recommended],
|
extends: [js.configs.recommended, ...tseslint.configs.recommended],
|
||||||
files: ['**/*.{ts,tsx}'],
|
files: ["**/*.{ts,tsx}"],
|
||||||
languageOptions: {
|
languageOptions: {
|
||||||
ecmaVersion: 2020,
|
ecmaVersion: 2020,
|
||||||
globals: globals.browser,
|
globals: globals.browser,
|
||||||
},
|
},
|
||||||
plugins: {
|
plugins: {
|
||||||
'react-hooks': reactHooks,
|
"react-hooks": reactHooks,
|
||||||
'react-refresh': reactRefresh,
|
"react-refresh": reactRefresh,
|
||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
...reactHooks.configs.recommended.rules,
|
...reactHooks.configs.recommended.rules,
|
||||||
'react-refresh/only-export-components': [
|
"react-refresh/only-export-components": [
|
||||||
'warn',
|
"warn",
|
||||||
{ allowConstantExport: true },
|
{ allowConstantExport: true },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
)
|
);
|
||||||
|
117
tranga-website/package-lock.json
generated
117
tranga-website/package-lock.json
generated
@@ -26,6 +26,7 @@
|
|||||||
"eslint-plugin-react-hooks": "^5.1.0",
|
"eslint-plugin-react-hooks": "^5.1.0",
|
||||||
"eslint-plugin-react-refresh": "^0.4.19",
|
"eslint-plugin-react-refresh": "^0.4.19",
|
||||||
"globals": "^15.15.0",
|
"globals": "^15.15.0",
|
||||||
|
"prettier": "3.6.2",
|
||||||
"swagger-typescript-api": "^13.2.7",
|
"swagger-typescript-api": "^13.2.7",
|
||||||
"typescript": "~5.7.2",
|
"typescript": "~5.7.2",
|
||||||
"typescript-eslint": "^8.24.1",
|
"typescript-eslint": "^8.24.1",
|
||||||
@@ -980,9 +981,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@eslint/config-array": {
|
"node_modules/@eslint/config-array": {
|
||||||
"version": "0.19.2",
|
"version": "0.21.0",
|
||||||
"resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.2.tgz",
|
"resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz",
|
||||||
"integrity": "sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w==",
|
"integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -995,9 +996,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@eslint/config-helpers": {
|
"node_modules/@eslint/config-helpers": {
|
||||||
"version": "0.2.0",
|
"version": "0.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.1.tgz",
|
||||||
"integrity": "sha512-yJLLmLexii32mGrhW29qvU3QBVTu0GUmEf/J4XsBtVhp4JkIUFN/BjWqTF63yRvGApIDpZm5fa97LtYtINmfeQ==",
|
"integrity": "sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -1005,9 +1006,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@eslint/core": {
|
"node_modules/@eslint/core": {
|
||||||
"version": "0.12.0",
|
"version": "0.15.2",
|
||||||
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.12.0.tgz",
|
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz",
|
||||||
"integrity": "sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==",
|
"integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -1055,13 +1056,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@eslint/js": {
|
"node_modules/@eslint/js": {
|
||||||
"version": "9.23.0",
|
"version": "9.34.0",
|
||||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.23.0.tgz",
|
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.34.0.tgz",
|
||||||
"integrity": "sha512-35MJ8vCPU0ZMxo7zfev2pypqTwWTofFZO6m4KAtdoFhRpLJUpHTZZ+KB3C7Hb1d7bULYwO4lJXGCi5Se+8OMbw==",
|
"integrity": "sha512-EoyvqQnBNsV1CWaEJ559rxXL4c8V92gxirbawSmVUOWXlsRxxQXl6LmCpdUblgxgSkDIqKnhzba2SjRTI/A5Rw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://eslint.org/donate"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@eslint/object-schema": {
|
"node_modules/@eslint/object-schema": {
|
||||||
@@ -1075,13 +1079,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@eslint/plugin-kit": {
|
"node_modules/@eslint/plugin-kit": {
|
||||||
"version": "0.2.7",
|
"version": "0.3.5",
|
||||||
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.7.tgz",
|
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz",
|
||||||
"integrity": "sha512-JubJ5B2pJ4k4yGxaNLdbjrnk9d/iDz6/q8wOilpIowd6PJPgaxCuHBnBszq7Ce2TyMrywm5r4PnKm6V3iiZF+g==",
|
"integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint/core": "^0.12.0",
|
"@eslint/core": "^0.15.2",
|
||||||
"levn": "^0.4.1"
|
"levn": "^0.4.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -2494,9 +2498,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/acorn": {
|
"node_modules/acorn": {
|
||||||
"version": "8.14.1",
|
"version": "8.15.0",
|
||||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz",
|
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
||||||
"integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==",
|
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bin": {
|
"bin": {
|
||||||
@@ -3193,20 +3197,20 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/eslint": {
|
"node_modules/eslint": {
|
||||||
"version": "9.23.0",
|
"version": "9.34.0",
|
||||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.23.0.tgz",
|
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.34.0.tgz",
|
||||||
"integrity": "sha512-jV7AbNoFPAY1EkFYpLq5bslU9NLNO8xnEeQXwErNibVryjk67wHVmddTBilc5srIttJDBrB0eMHKZBFbSIABCw==",
|
"integrity": "sha512-RNCHRX5EwdrESy3Jc9o8ie8Bog+PeYvvSR8sDGoZxNFTvZ4dlxUB3WzQ3bQMztFrSRODGrLLj8g6OFuGY/aiQg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.2.0",
|
"@eslint-community/eslint-utils": "^4.2.0",
|
||||||
"@eslint-community/regexpp": "^4.12.1",
|
"@eslint-community/regexpp": "^4.12.1",
|
||||||
"@eslint/config-array": "^0.19.2",
|
"@eslint/config-array": "^0.21.0",
|
||||||
"@eslint/config-helpers": "^0.2.0",
|
"@eslint/config-helpers": "^0.3.1",
|
||||||
"@eslint/core": "^0.12.0",
|
"@eslint/core": "^0.15.2",
|
||||||
"@eslint/eslintrc": "^3.3.1",
|
"@eslint/eslintrc": "^3.3.1",
|
||||||
"@eslint/js": "9.23.0",
|
"@eslint/js": "9.34.0",
|
||||||
"@eslint/plugin-kit": "^0.2.7",
|
"@eslint/plugin-kit": "^0.3.5",
|
||||||
"@humanfs/node": "^0.16.6",
|
"@humanfs/node": "^0.16.6",
|
||||||
"@humanwhocodes/module-importer": "^1.0.1",
|
"@humanwhocodes/module-importer": "^1.0.1",
|
||||||
"@humanwhocodes/retry": "^0.4.2",
|
"@humanwhocodes/retry": "^0.4.2",
|
||||||
@@ -3217,9 +3221,9 @@
|
|||||||
"cross-spawn": "^7.0.6",
|
"cross-spawn": "^7.0.6",
|
||||||
"debug": "^4.3.2",
|
"debug": "^4.3.2",
|
||||||
"escape-string-regexp": "^4.0.0",
|
"escape-string-regexp": "^4.0.0",
|
||||||
"eslint-scope": "^8.3.0",
|
"eslint-scope": "^8.4.0",
|
||||||
"eslint-visitor-keys": "^4.2.0",
|
"eslint-visitor-keys": "^4.2.1",
|
||||||
"espree": "^10.3.0",
|
"espree": "^10.4.0",
|
||||||
"esquery": "^1.5.0",
|
"esquery": "^1.5.0",
|
||||||
"esutils": "^2.0.2",
|
"esutils": "^2.0.2",
|
||||||
"fast-deep-equal": "^3.1.3",
|
"fast-deep-equal": "^3.1.3",
|
||||||
@@ -3277,9 +3281,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/eslint-scope": {
|
"node_modules/eslint-scope": {
|
||||||
"version": "8.3.0",
|
"version": "8.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz",
|
||||||
"integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==",
|
"integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "BSD-2-Clause",
|
"license": "BSD-2-Clause",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -3294,9 +3298,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/eslint-visitor-keys": {
|
"node_modules/eslint-visitor-keys": {
|
||||||
"version": "4.2.0",
|
"version": "4.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
|
||||||
"integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==",
|
"integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -3307,15 +3311,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/espree": {
|
"node_modules/espree": {
|
||||||
"version": "10.3.0",
|
"version": "10.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
|
||||||
"integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==",
|
"integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "BSD-2-Clause",
|
"license": "BSD-2-Clause",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"acorn": "^8.14.0",
|
"acorn": "^8.15.0",
|
||||||
"acorn-jsx": "^5.3.2",
|
"acorn-jsx": "^5.3.2",
|
||||||
"eslint-visitor-keys": "^4.2.0"
|
"eslint-visitor-keys": "^4.2.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
@@ -5739,6 +5743,22 @@
|
|||||||
"node": ">= 0.8.0"
|
"node": ">= 0.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/prettier": {
|
||||||
|
"version": "3.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz",
|
||||||
|
"integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"prettier": "bin/prettier.cjs"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/prettier/prettier?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/prop-types": {
|
"node_modules/prop-types": {
|
||||||
"version": "15.8.1",
|
"version": "15.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||||
@@ -7140,21 +7160,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/yaml": {
|
|
||||||
"version": "2.7.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.1.tgz",
|
|
||||||
"integrity": "sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "ISC",
|
|
||||||
"optional": true,
|
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
|
||||||
"yaml": "bin.mjs"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 14"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/yargs": {
|
"node_modules/yargs": {
|
||||||
"version": "17.7.2",
|
"version": "17.7.2",
|
||||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
|
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
|
||||||
|
@@ -6,8 +6,10 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "tsc -b && vite build",
|
"build": "tsc -b && vite build",
|
||||||
"lint": "eslint .",
|
"lint": "eslint . --fix",
|
||||||
"preview": "vite preview"
|
"swagger-api": "swagger-typescript-api generate -p http://127.0.0.1:6531/swagger/v2/swagger.json -o ./src/apiClient --modular",
|
||||||
|
"prettier:check": "prettier . --check",
|
||||||
|
"prettier": "prettier . --write"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@emotion/react": "^11.14.0",
|
"@emotion/react": "^11.14.0",
|
||||||
@@ -28,6 +30,7 @@
|
|||||||
"eslint-plugin-react-hooks": "^5.1.0",
|
"eslint-plugin-react-hooks": "^5.1.0",
|
||||||
"eslint-plugin-react-refresh": "^0.4.19",
|
"eslint-plugin-react-refresh": "^0.4.19",
|
||||||
"globals": "^15.15.0",
|
"globals": "^15.15.0",
|
||||||
|
"prettier": "3.6.2",
|
||||||
"swagger-typescript-api": "^13.2.7",
|
"swagger-typescript-api": "^13.2.7",
|
||||||
"typescript": "~5.7.2",
|
"typescript": "~5.7.2",
|
||||||
"typescript-eslint": "^8.24.1",
|
"typescript-eslint": "^8.24.1",
|
||||||
|
@@ -1,85 +1,89 @@
|
|||||||
import Sheet from '@mui/joy/Sheet';
|
import Sheet from "@mui/joy/Sheet";
|
||||||
import './App.css'
|
import "./App.css";
|
||||||
import Settings from "./Components/Settings/Settings.tsx";
|
import Settings from "./Components/Settings/Settings.tsx";
|
||||||
import Header from "./Header.tsx";
|
import Header from "./Header.tsx";
|
||||||
import {createContext, ReactNode, useEffect, useState} from "react";
|
import { createContext, ReactNode, useEffect, useState } from "react";
|
||||||
import {V2} from "./apiClient/V2.ts";
|
import { V2 } from "./apiClient/V2.ts";
|
||||||
import { ApiContext } from './apiClient/ApiContext.tsx';
|
import { ApiContext } from "./apiClient/ApiContext.tsx";
|
||||||
import MangaList from "./Components/Mangas/MangaList.tsx";
|
import MangaList from "./Components/Mangas/MangaList.tsx";
|
||||||
import {FileLibrary, Manga, MangaConnector} from "./apiClient/data-contracts.ts";
|
import {
|
||||||
|
FileLibrary,
|
||||||
|
Manga,
|
||||||
|
MangaConnector,
|
||||||
|
} from "./apiClient/data-contracts.ts";
|
||||||
import Search from "./Components/Search.tsx";
|
import Search from "./Components/Search.tsx";
|
||||||
import {Typography} from "@mui/joy";
|
import { Typography } from "@mui/joy";
|
||||||
|
|
||||||
export const MangaConnectorContext = createContext<MangaConnector[]>([]);
|
export const MangaConnectorContext = createContext<MangaConnector[]>([]);
|
||||||
export const MangaContext = createContext<Manga[]>([]);
|
export const MangaContext = createContext<Manga[]>([]);
|
||||||
export const FileLibraryContext = createContext<FileLibrary[]>([]);
|
export const FileLibraryContext = createContext<FileLibrary[]>([]);
|
||||||
|
|
||||||
export default function App () {
|
export default function App() {
|
||||||
const apiUriStr = localStorage.getItem("apiUri") ?? window.location.href.substring(0, window.location.href.lastIndexOf("/")) + "/api";
|
const apiUriStr =
|
||||||
const [apiUri, setApiUri] = useState<string>(apiUriStr);
|
localStorage.getItem("apiUri") ??
|
||||||
const [Api, setApi] = useState<V2>(new V2({
|
window.location.href.substring(0, window.location.href.lastIndexOf("/")) +
|
||||||
baseUrl: apiUri
|
"/api";
|
||||||
}));
|
const [apiUri, setApiUri] = useState<string>(apiUriStr);
|
||||||
|
const [Api, setApi] = useState<V2>(
|
||||||
|
new V2({
|
||||||
|
baseUrl: apiUri,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
const [mangaConnectors, setMangaConnectors] = useState<MangaConnector[]>([]);
|
const [mangaConnectors, setMangaConnectors] = useState<MangaConnector[]>([]);
|
||||||
const [manga, setManga] = useState<Manga[]>([]);
|
const [manga, setManga] = useState<Manga[]>([]);
|
||||||
const [fileLibraries, setFileLibraries] = useState<FileLibrary[]>([]);
|
const [fileLibraries, setFileLibraries] = useState<FileLibrary[]>([]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
Api.mangaConnectorList().then(response => {
|
Api.mangaConnectorList().then((response) => {
|
||||||
if (response.ok)
|
if (response.ok) setMangaConnectors(response.data);
|
||||||
setMangaConnectors(response.data);
|
});
|
||||||
});
|
|
||||||
|
|
||||||
Api.fileLibraryList().then(response => {
|
Api.fileLibraryList().then((response) => {
|
||||||
if (response.ok)
|
if (response.ok) setFileLibraries(response.data);
|
||||||
setFileLibraries(response.data);
|
});
|
||||||
})
|
|
||||||
|
|
||||||
Api.queryMangaDownloadingList()
|
Api.queryMangaDownloadingList().then((response) => {
|
||||||
.then(response => {
|
if (response.ok) setManga(response.data);
|
||||||
if (response.ok)
|
});
|
||||||
setManga(response.data);
|
}, []);
|
||||||
});
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
localStorage.setItem("apiUri", apiUri);
|
||||||
|
if (Api.baseUrl != apiUri)
|
||||||
|
setApi(
|
||||||
|
new V2({
|
||||||
|
baseUrl: apiUri,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}, [apiUri]);
|
||||||
|
|
||||||
useEffect(() => {
|
return (
|
||||||
localStorage.setItem("apiUri", apiUri);
|
<ApiContext.Provider value={Api}>
|
||||||
if (Api.baseUrl != apiUri)
|
<FileLibraryContext value={fileLibraries}>
|
||||||
setApi(new V2({
|
<MangaConnectorContext.Provider value={mangaConnectors}>
|
||||||
baseUrl: apiUri
|
<MangaContext.Provider value={manga}>
|
||||||
}));
|
{Api ? (
|
||||||
}, [apiUri]);
|
<Sheet className={"app"}>
|
||||||
|
<Header>
|
||||||
return (
|
<Settings setApiUri={setApiUri} />
|
||||||
<ApiContext.Provider value={Api}>
|
</Header>
|
||||||
<FileLibraryContext value={fileLibraries}>
|
<Sheet className={"app-content"}>
|
||||||
<MangaConnectorContext.Provider value={mangaConnectors}>
|
<MangaList mangas={manga}>
|
||||||
<MangaContext.Provider value={manga}>
|
<Search />
|
||||||
{
|
</MangaList>
|
||||||
Api ?
|
</Sheet>
|
||||||
<Sheet className={"app"}>
|
</Sheet>
|
||||||
<Header>
|
) : (
|
||||||
<Settings setApiUri={setApiUri} />
|
<Loading />
|
||||||
</Header>
|
)}
|
||||||
<Sheet className={"app-content"}>
|
</MangaContext.Provider>
|
||||||
<MangaList mangas={manga}>
|
</MangaConnectorContext.Provider>
|
||||||
<Search />
|
</FileLibraryContext>
|
||||||
</MangaList>
|
</ApiContext.Provider>
|
||||||
</Sheet>
|
);
|
||||||
</Sheet>
|
|
||||||
: <Loading />
|
|
||||||
}
|
|
||||||
</MangaContext.Provider>
|
|
||||||
</MangaConnectorContext.Provider>
|
|
||||||
</FileLibraryContext>
|
|
||||||
</ApiContext.Provider>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function Loading () : ReactNode{
|
function Loading(): ReactNode {
|
||||||
return (
|
return <Typography>Loading</Typography>;
|
||||||
<Typography>Loading</Typography>
|
|
||||||
);
|
|
||||||
}
|
}
|
@@ -1,32 +1,34 @@
|
|||||||
import {Close, Done} from "@mui/icons-material";
|
import { Close, Done } from "@mui/icons-material";
|
||||||
import {CircularProgress, ColorPaletteProp} from "@mui/joy";
|
import { CircularProgress, ColorPaletteProp } from "@mui/joy";
|
||||||
import {ReactNode} from "react";
|
import { ReactNode } from "react";
|
||||||
|
|
||||||
export enum LoadingState {
|
export enum LoadingState {
|
||||||
none,
|
none,
|
||||||
loading,
|
loading,
|
||||||
success,
|
success,
|
||||||
failure
|
failure,
|
||||||
}
|
}
|
||||||
|
|
||||||
export function StateIndicator(state : LoadingState) : ReactNode {
|
export function StateIndicator(state: LoadingState): ReactNode {
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case LoadingState.loading:
|
case LoadingState.loading:
|
||||||
return (<CircularProgress />);
|
return <CircularProgress />;
|
||||||
case LoadingState.failure:
|
case LoadingState.failure:
|
||||||
return (<Close />);
|
return <Close />;
|
||||||
case LoadingState.success:
|
case LoadingState.success:
|
||||||
return (<Done />);
|
return <Done />;
|
||||||
default: return null;
|
default:
|
||||||
}
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function StateColor(state : LoadingState) : ColorPaletteProp | undefined {
|
export function StateColor(state: LoadingState): ColorPaletteProp | undefined {
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case LoadingState.failure:
|
case LoadingState.failure:
|
||||||
return "danger";
|
return "danger";
|
||||||
case LoadingState.success:
|
case LoadingState.success:
|
||||||
return "success";
|
return "success";
|
||||||
default: return undefined;
|
default:
|
||||||
}
|
return undefined;
|
||||||
|
}
|
||||||
}
|
}
|
@@ -1,42 +1,97 @@
|
|||||||
import {CSSProperties, ReactNode, useContext, useEffect, useRef, useState} from "react";
|
import {
|
||||||
import {ChapterMangaConnectorId, MangaConnector, MangaMangaConnectorId} from "../apiClient/data-contracts.ts";
|
CSSProperties,
|
||||||
import {Link, Tooltip, Typography} from "@mui/joy";
|
ReactNode,
|
||||||
import {MangaConnectorContext} from "../App.tsx";
|
useContext,
|
||||||
import {ApiContext} from "../apiClient/ApiContext.tsx";
|
useEffect,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from "react";
|
||||||
|
import {
|
||||||
|
ChapterMangaConnectorId,
|
||||||
|
MangaConnector,
|
||||||
|
MangaMangaConnectorId,
|
||||||
|
} from "../apiClient/data-contracts.ts";
|
||||||
|
import { Link, Tooltip, Typography } from "@mui/joy";
|
||||||
|
import { MangaConnectorContext } from "../App.tsx";
|
||||||
|
import { ApiContext } from "../apiClient/ApiContext.tsx";
|
||||||
|
|
||||||
export default function MangaConnectorLink({MangaConnectorId, imageStyle, printName} : {MangaConnectorId : MangaMangaConnectorId | ChapterMangaConnectorId, imageStyle? : CSSProperties, printName?: boolean}) : ReactNode{
|
export default function MangaConnectorLink({
|
||||||
const mangaConnectorContext = useContext(MangaConnectorContext);
|
MangaConnectorId,
|
||||||
const [mangaConnector, setMangaConnector] = useState<MangaConnector | undefined>(mangaConnectorContext?.find(c => c.name == MangaConnectorId.mangaConnectorName));
|
imageStyle,
|
||||||
const imageRef = useRef<HTMLImageElement | null>(null);
|
printName,
|
||||||
|
}: {
|
||||||
|
MangaConnectorId: MangaMangaConnectorId | ChapterMangaConnectorId;
|
||||||
|
imageStyle?: CSSProperties;
|
||||||
|
printName?: boolean;
|
||||||
|
}): ReactNode {
|
||||||
|
const mangaConnectorContext = useContext(MangaConnectorContext);
|
||||||
|
const [mangaConnector, setMangaConnector] = useState<
|
||||||
|
MangaConnector | undefined
|
||||||
|
>(
|
||||||
|
mangaConnectorContext?.find(
|
||||||
|
(c) => c.name == MangaConnectorId.mangaConnectorName,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
const imageRef = useRef<HTMLImageElement | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const connector = mangaConnectorContext?.find(c => c.name == MangaConnectorId.mangaConnectorName);
|
const connector = mangaConnectorContext?.find(
|
||||||
setMangaConnector(connector);
|
(c) => c.name == MangaConnectorId.mangaConnectorName,
|
||||||
if (imageRef?.current != null)
|
|
||||||
imageRef.current.setHTMLUnsafe(`<img ref=${imageRef} src=${mangaConnector?.iconUrl} style=${imageStyle}/>`);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Tooltip title={<Typography>{MangaConnectorId.mangaConnectorName}: <Link href={MangaConnectorId.websiteUrl as string}>{MangaConnectorId.websiteUrl}</Link></Typography>}>
|
|
||||||
<Link href={MangaConnectorId.websiteUrl as string}>
|
|
||||||
<img ref={imageRef} src={mangaConnector?.iconUrl} style={imageStyle}/>
|
|
||||||
{printName ? <Typography>{mangaConnector?.name}</Typography> : null}
|
|
||||||
</Link>
|
|
||||||
</Tooltip>
|
|
||||||
);
|
);
|
||||||
|
setMangaConnector(connector);
|
||||||
|
if (imageRef?.current != null)
|
||||||
|
imageRef.current.setHTMLUnsafe(
|
||||||
|
`<img ref=${imageRef} src=${mangaConnector?.iconUrl} style=${imageStyle}/>`,
|
||||||
|
);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tooltip
|
||||||
|
title={
|
||||||
|
<Typography>
|
||||||
|
{MangaConnectorId.mangaConnectorName}:{" "}
|
||||||
|
<Link href={MangaConnectorId.websiteUrl as string}>
|
||||||
|
{MangaConnectorId.websiteUrl}
|
||||||
|
</Link>
|
||||||
|
</Typography>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Link href={MangaConnectorId.websiteUrl as string}>
|
||||||
|
<img ref={imageRef} src={mangaConnector?.iconUrl} style={imageStyle} />
|
||||||
|
{printName ? <Typography>{mangaConnector?.name}</Typography> : null}
|
||||||
|
</Link>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function MangaConnectorLinkFromId({MangaConnectorIdId, imageStyle, printName} : {MangaConnectorIdId: string, imageStyle? : CSSProperties, printName?: boolean}) : ReactNode {
|
export function MangaConnectorLinkFromId({
|
||||||
const Api = useContext(ApiContext);
|
MangaConnectorIdId,
|
||||||
|
imageStyle,
|
||||||
|
printName,
|
||||||
|
}: {
|
||||||
|
MangaConnectorIdId: string;
|
||||||
|
imageStyle?: CSSProperties;
|
||||||
|
printName?: boolean;
|
||||||
|
}): ReactNode {
|
||||||
|
const Api = useContext(ApiContext);
|
||||||
|
|
||||||
const [node, setNode] = useState<ReactNode>(null);
|
const [node, setNode] = useState<ReactNode>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
Api.queryMangaMangaConnectorIdDetail(MangaConnectorIdId).then(response => {
|
Api.queryMangaMangaConnectorIdDetail(MangaConnectorIdId).then(
|
||||||
if (response.ok)
|
(response) => {
|
||||||
setNode(<MangaConnectorLink key={response.data.key} MangaConnectorId={response.data} imageStyle={{...imageStyle, width: "25px"}} printName={printName} />);
|
if (response.ok)
|
||||||
});
|
setNode(
|
||||||
}, []);
|
<MangaConnectorLink
|
||||||
|
key={response.data.key}
|
||||||
|
MangaConnectorId={response.data}
|
||||||
|
imageStyle={{ ...imageStyle, width: "25px" }}
|
||||||
|
printName={printName}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}, []);
|
||||||
|
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
@@ -1,21 +1,25 @@
|
|||||||
.manga-card {
|
.manga-card {
|
||||||
width: 220px;
|
width: 220px;
|
||||||
height: 300px;
|
height: 300px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.manga-cover-blur {
|
.manga-cover-blur {
|
||||||
background: linear-gradient(135deg, rgba(245, 169, 184, 0.85) 40%, rgba(91, 206, 250, 0.3));
|
background: linear-gradient(
|
||||||
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1);)
|
135deg,
|
||||||
backdrop-filter: blur(6px);
|
rgba(245, 169, 184, 0.85) 40%,
|
||||||
-webkit-backdrop-filter: blur(6px);
|
rgba(91, 206, 250, 0.3)
|
||||||
|
);
|
||||||
|
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1);
|
||||||
|
)backdrop-filter: blur(6px);
|
||||||
|
-webkit-backdrop-filter: blur(6px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.manga-card-badge-icon {
|
.manga-card-badge-icon {
|
||||||
width: 25px;
|
width: 25px;
|
||||||
height: 25px;
|
height: 25px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.manga-modal {
|
.manga-modal {
|
||||||
width: 90%;
|
width: 90%;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
}
|
}
|
@@ -1,105 +1,176 @@
|
|||||||
import {
|
import {
|
||||||
Badge,
|
Badge,
|
||||||
Box,
|
Box,
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
CardCover,
|
CardCover,
|
||||||
Chip,
|
Chip,
|
||||||
Link,
|
Link,
|
||||||
Modal,
|
Modal,
|
||||||
ModalDialog,
|
ModalDialog,
|
||||||
Stack, Tooltip,
|
Stack,
|
||||||
Typography
|
Tooltip,
|
||||||
|
Typography,
|
||||||
} from "@mui/joy";
|
} from "@mui/joy";
|
||||||
import {Manga} from "../../apiClient/data-contracts.ts";
|
import { Manga } from "../../apiClient/data-contracts.ts";
|
||||||
import {Dispatch, ReactNode, SetStateAction, useContext, useState} from "react";
|
import {
|
||||||
|
Dispatch,
|
||||||
|
ReactNode,
|
||||||
|
SetStateAction,
|
||||||
|
useContext,
|
||||||
|
useState,
|
||||||
|
} from "react";
|
||||||
import "./MangaCard.css";
|
import "./MangaCard.css";
|
||||||
import MangaConnectorBadge from "./MangaConnectorBadge.tsx";
|
import MangaConnectorBadge from "./MangaConnectorBadge.tsx";
|
||||||
import ModalClose from "@mui/joy/ModalClose";
|
import ModalClose from "@mui/joy/ModalClose";
|
||||||
import {ApiContext} from "../../apiClient/ApiContext.tsx";
|
import { ApiContext } from "../../apiClient/ApiContext.tsx";
|
||||||
import MarkdownPreview from '@uiw/react-markdown-preview';
|
import MarkdownPreview from "@uiw/react-markdown-preview";
|
||||||
import {MangaContext} from "../../App.tsx";
|
import { MangaContext } from "../../App.tsx";
|
||||||
import {MangaConnectorLinkFromId} from "../MangaConnectorLink.tsx";
|
import { MangaConnectorLinkFromId } from "../MangaConnectorLink.tsx";
|
||||||
|
|
||||||
export function MangaCardFromId({mangaId} : {mangaId: string}) {
|
export function MangaCardFromId({ mangaId }: { mangaId: string }) {
|
||||||
const mangas = useContext(MangaContext);
|
const mangas = useContext(MangaContext);
|
||||||
const manga = mangas.find(manga => manga.key === mangaId);
|
const manga = mangas.find((manga) => manga.key === mangaId);
|
||||||
|
|
||||||
return <MangaCard manga={manga} />
|
return <MangaCard manga={manga} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function MangaCard({manga, children} : {manga: Manga | undefined, children? : ReactNode}) {
|
export function MangaCard({
|
||||||
if (manga === undefined)
|
manga,
|
||||||
return PlaceHolderCard();
|
children,
|
||||||
|
}: {
|
||||||
|
manga: Manga | undefined;
|
||||||
|
children?: ReactNode;
|
||||||
|
}) {
|
||||||
|
if (manga === undefined) return PlaceHolderCard();
|
||||||
|
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MangaConnectorBadge manga={manga}>
|
<MangaConnectorBadge manga={manga}>
|
||||||
<Card className={"manga-card"} onClick={() => setOpen(true)}>
|
<Card className={"manga-card"} onClick={() => setOpen(true)}>
|
||||||
<CardCover className={"manga-cover"}>
|
<CardCover className={"manga-cover"}>
|
||||||
<MangaCover manga={manga} />
|
<MangaCover manga={manga} />
|
||||||
</CardCover>
|
</CardCover>
|
||||||
<CardCover className={"manga-cover-blur"} />
|
<CardCover className={"manga-cover-blur"} />
|
||||||
<CardContent className={"manga-content"}>
|
<CardContent className={"manga-content"}>
|
||||||
<Typography level={"title-lg"}>{manga?.name}</Typography>
|
<Typography level={"title-lg"}>{manga?.name}</Typography>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
<MangaModal manga={manga} open={open} setOpen={setOpen}>
|
<MangaModal manga={manga} open={open} setOpen={setOpen}>
|
||||||
{children}
|
{children}
|
||||||
</MangaModal>
|
</MangaModal>
|
||||||
</MangaConnectorBadge>
|
</MangaConnectorBadge>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function MangaModal({manga, open, setOpen, children}: {manga: Manga | undefined, open: boolean, setOpen: Dispatch<SetStateAction<boolean>>, children?: ReactNode}) {
|
export function MangaModal({
|
||||||
|
manga,
|
||||||
return (
|
open,
|
||||||
<Modal open={open} onClose={() => setOpen(false)} className={"manga-modal"}>
|
setOpen,
|
||||||
<ModalDialog style={{width:'100%'}}>
|
children,
|
||||||
<ModalClose />
|
}: {
|
||||||
<Tooltip title={<Stack spacing={1}>{manga?.altTitles?.map(title => <Chip>{title.title}</Chip>)}</Stack>}>
|
manga: Manga | undefined;
|
||||||
<Typography level={"h4"} width={"fit-content"}>{manga?.name}</Typography>
|
open: boolean;
|
||||||
</Tooltip>
|
setOpen: Dispatch<SetStateAction<boolean>>;
|
||||||
<Stack direction={"row"} spacing={2}>
|
children?: ReactNode;
|
||||||
<Box key={"Cover"} className={"manga-card"}>
|
}) {
|
||||||
<MangaCover manga={manga} />
|
return (
|
||||||
</Box>
|
<Modal open={open} onClose={() => setOpen(false)} className={"manga-modal"}>
|
||||||
<Stack key={"Description"} direction={"column"} sx={{width: "calc(100% - 230px)"}}>
|
<ModalDialog style={{ width: "100%" }}>
|
||||||
<Stack key={"Tags"} direction={"row"} flexWrap={"wrap"} useFlexGap spacing={0.5}>
|
<ModalClose />
|
||||||
{manga?.mangaConnectorIdsIds?.map((idid) => <MangaConnectorLinkFromId key={idid} MangaConnectorIdId={idid} />)}
|
<Tooltip
|
||||||
{manga?.mangaTags?.map((tag) => <Chip key={tag.tag}>{tag.tag}</Chip>)}
|
title={
|
||||||
{manga?.links?.map((link) => <Chip key={link.key}><Link href={link.linkUrl}>{link.linkProvider}</Link></Chip>)}
|
<Stack spacing={1}>
|
||||||
</Stack>
|
{manga?.altTitles?.map((title) => (
|
||||||
<Box sx={{flexGrow: 1}}>
|
<Chip>{title.title}</Chip>
|
||||||
<MarkdownPreview source={manga?.description} style={{background: "transparent"}}/>
|
))}
|
||||||
</Box>
|
</Stack>
|
||||||
<Stack sx={{justifySelf: "flex-end", alignSelf: "flex-end"}} spacing={2} direction={"row"}>{children}</Stack>
|
}
|
||||||
</Stack>
|
>
|
||||||
</Stack>
|
<Typography level={"h4"} width={"fit-content"}>
|
||||||
</ModalDialog>
|
{manga?.name}
|
||||||
</Modal>
|
</Typography>
|
||||||
);
|
</Tooltip>
|
||||||
|
<Stack direction={"row"} spacing={2}>
|
||||||
|
<Box key={"Cover"} className={"manga-card"}>
|
||||||
|
<MangaCover manga={manga} />
|
||||||
|
</Box>
|
||||||
|
<Stack
|
||||||
|
key={"Description"}
|
||||||
|
direction={"column"}
|
||||||
|
sx={{ width: "calc(100% - 230px)" }}
|
||||||
|
>
|
||||||
|
<Stack
|
||||||
|
key={"Tags"}
|
||||||
|
direction={"row"}
|
||||||
|
flexWrap={"wrap"}
|
||||||
|
useFlexGap
|
||||||
|
spacing={0.5}
|
||||||
|
>
|
||||||
|
{manga?.mangaConnectorIdsIds?.map((idid) => (
|
||||||
|
<MangaConnectorLinkFromId
|
||||||
|
key={idid}
|
||||||
|
MangaConnectorIdId={idid}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
{manga?.mangaTags?.map((tag) => (
|
||||||
|
<Chip key={tag.tag}>{tag.tag}</Chip>
|
||||||
|
))}
|
||||||
|
{manga?.links?.map((link) => (
|
||||||
|
<Chip key={link.key}>
|
||||||
|
<Link href={link.linkUrl}>{link.linkProvider}</Link>
|
||||||
|
</Chip>
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
<Box sx={{ flexGrow: 1 }}>
|
||||||
|
<MarkdownPreview
|
||||||
|
source={manga?.description}
|
||||||
|
style={{ background: "transparent" }}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Stack
|
||||||
|
sx={{ justifySelf: "flex-end", alignSelf: "flex-end" }}
|
||||||
|
spacing={2}
|
||||||
|
direction={"row"}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
</ModalDialog>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function PlaceHolderCard(){
|
function PlaceHolderCard() {
|
||||||
return (
|
return (
|
||||||
<Badge>
|
<Badge>
|
||||||
<Card className={"manga-card"}>
|
<Card className={"manga-card"}>
|
||||||
<CardCover className={"manga-cover"}>
|
<CardCover className={"manga-cover"}>
|
||||||
<img src={"/blahaj.png"} />
|
<img src={"/blahaj.png"} />
|
||||||
</CardCover>
|
</CardCover>
|
||||||
<CardCover className={"manga-cover-blur"} />
|
<CardCover className={"manga-cover-blur"} />
|
||||||
</Card>
|
</Card>
|
||||||
</Badge>
|
</Badge>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function MangaCover({manga}: {manga: Manga | undefined}) {
|
function MangaCover({ manga }: { manga: Manga | undefined }) {
|
||||||
const api = useContext(ApiContext);
|
const api = useContext(ApiContext);
|
||||||
const uri = manga ? `${api.baseUrl}/v2/Manga/${manga?.key}/Cover` : "blahaj.png";
|
const uri = manga
|
||||||
|
? `${api.baseUrl}/v2/Manga/${manga?.key}/Cover`
|
||||||
|
: "blahaj.png";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<img src={uri} style={{width: "100%", height: "100%", objectFit: "cover", borderRadius: "var(--CardCover-radius)"}} />
|
<img
|
||||||
);
|
src={uri}
|
||||||
|
style={{
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
objectFit: "cover",
|
||||||
|
borderRadius: "var(--CardCover-radius)",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
@@ -1,14 +1,23 @@
|
|||||||
import { Badge } from "@mui/joy";
|
import { Badge } from "@mui/joy";
|
||||||
import {Manga} from "../../apiClient/data-contracts.ts";
|
import { Manga } from "../../apiClient/data-contracts.ts";
|
||||||
import {ReactElement} from "react";
|
import { ReactElement } from "react";
|
||||||
import "./MangaCard.css"
|
import "./MangaCard.css";
|
||||||
import {MangaConnectorLinkFromId} from "../MangaConnectorLink.tsx";
|
import { MangaConnectorLinkFromId } from "../MangaConnectorLink.tsx";
|
||||||
|
|
||||||
export default function MangaConnectorBadge ({manga, children} : {manga: Manga, children? : ReactElement<any, any> | ReactElement<any,any>[] | undefined}) {
|
export default function MangaConnectorBadge({
|
||||||
|
manga,
|
||||||
return (
|
children,
|
||||||
<Badge badgeContent={manga.mangaConnectorIdsIds?.map(id => <MangaConnectorLinkFromId key={id} MangaConnectorIdId={id} />)}>
|
}: {
|
||||||
{children}
|
manga: Manga;
|
||||||
</Badge>
|
children?: ReactElement<any, any> | ReactElement<any, any>[] | undefined;
|
||||||
);
|
}) {
|
||||||
|
return (
|
||||||
|
<Badge
|
||||||
|
badgeContent={manga.mangaConnectorIdsIds?.map((id) => (
|
||||||
|
<MangaConnectorLinkFromId key={id} MangaConnectorIdId={id} />
|
||||||
|
))}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Badge>
|
||||||
|
);
|
||||||
}
|
}
|
@@ -1,100 +1,138 @@
|
|||||||
import {Manga} from "../../apiClient/data-contracts.ts";
|
import { Manga } from "../../apiClient/data-contracts.ts";
|
||||||
import {ChangeEvent, Dispatch, ReactNode, useContext, useEffect, useState} from "react";
|
import {
|
||||||
import {Button, Checkbox, Option, Select, Stack, Typography} from "@mui/joy";
|
ChangeEvent,
|
||||||
|
Dispatch,
|
||||||
|
ReactNode,
|
||||||
|
useContext,
|
||||||
|
useEffect,
|
||||||
|
useState,
|
||||||
|
} from "react";
|
||||||
|
import { Button, Checkbox, Option, Select, Stack, Typography } from "@mui/joy";
|
||||||
import Drawer from "@mui/joy/Drawer";
|
import Drawer from "@mui/joy/Drawer";
|
||||||
import ModalClose from "@mui/joy/ModalClose";
|
import ModalClose from "@mui/joy/ModalClose";
|
||||||
import {MangaConnectorLinkFromId} from "../MangaConnectorLink.tsx";
|
import { MangaConnectorLinkFromId } from "../MangaConnectorLink.tsx";
|
||||||
import Sheet from "@mui/joy/Sheet";
|
import Sheet from "@mui/joy/Sheet";
|
||||||
import {FileLibraryContext} from "../../App.tsx";
|
import { FileLibraryContext } from "../../App.tsx";
|
||||||
import {ApiContext} from "../../apiClient/ApiContext.tsx";
|
import { ApiContext } from "../../apiClient/ApiContext.tsx";
|
||||||
import {LoadingState, StateIndicator} from "../Loading.tsx";
|
import { LoadingState, StateIndicator } from "../Loading.tsx";
|
||||||
|
|
||||||
export default function ({manga} : {manga: Manga}) : ReactNode{
|
export default function ({ manga }: { manga: Manga }): ReactNode {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Button onClick={() => setOpen(true)}>Download</Button>
|
<Button onClick={() => setOpen(true)}>Download</Button>
|
||||||
<DownloadDrawer manga={manga} open={open} setOpen={setOpen} />
|
<DownloadDrawer manga={manga} open={open} setOpen={setOpen} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function DownloadDrawer({manga, open, setOpen}: {manga: Manga, open: boolean, setOpen: Dispatch<boolean>}): ReactNode {
|
function DownloadDrawer({
|
||||||
const fileLibraries = useContext(FileLibraryContext);
|
manga,
|
||||||
const Api = useContext(ApiContext);
|
open,
|
||||||
|
setOpen,
|
||||||
|
}: {
|
||||||
|
manga: Manga;
|
||||||
|
open: boolean;
|
||||||
|
setOpen: Dispatch<boolean>;
|
||||||
|
}): ReactNode {
|
||||||
|
const fileLibraries = useContext(FileLibraryContext);
|
||||||
|
const Api = useContext(ApiContext);
|
||||||
|
|
||||||
const onLibraryChange = (_ :any, value: ({} | null)) => {
|
const onLibraryChange = (_: any, value: {} | null) => {
|
||||||
if (value === undefined)
|
if (value === undefined) return;
|
||||||
return;
|
Api.mangaChangeLibraryCreate(manga.key as string, value as string);
|
||||||
Api.mangaChangeLibraryCreate(manga.key as string, value as string);
|
};
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Drawer open={open} onClose={() => setOpen(false)}>
|
<Drawer open={open} onClose={() => setOpen(false)}>
|
||||||
<ModalClose />
|
<ModalClose />
|
||||||
<Sheet sx={{width: "calc(95% - 60px)", margin: "30px"}}>
|
<Sheet sx={{ width: "calc(95% - 60px)", margin: "30px" }}>
|
||||||
<Typography>Download to Library:</Typography>
|
<Typography>Download to Library:</Typography>
|
||||||
<Select placeholder={"Library"} onChange={onLibraryChange} value={manga.libraryId}>
|
<Select
|
||||||
{fileLibraries?.map(library => (
|
placeholder={"Library"}
|
||||||
<Option value={library.key} key={library.key}><Typography>{library.libraryName}</Typography> <Typography>({library.basePath})</Typography></Option>
|
onChange={onLibraryChange}
|
||||||
))}
|
value={manga.libraryId}
|
||||||
</Select>
|
>
|
||||||
<Typography>Download from:</Typography>
|
{fileLibraries?.map((library) => (
|
||||||
<Stack>
|
<Option value={library.key} key={library.key}>
|
||||||
{manga.mangaConnectorIdsIds?.map(id => <DownloadCheckBox key={id} mangaConnectorIdId={id} />)}
|
<Typography>{library.libraryName}</Typography>{" "}
|
||||||
</Stack>
|
<Typography>({library.basePath})</Typography>
|
||||||
</Sheet>
|
</Option>
|
||||||
</Drawer>
|
))}
|
||||||
);
|
</Select>
|
||||||
|
<Typography>Download from:</Typography>
|
||||||
|
<Stack>
|
||||||
|
{manga.mangaConnectorIdsIds?.map((id) => (
|
||||||
|
<DownloadCheckBox key={id} mangaConnectorIdId={id} />
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
</Sheet>
|
||||||
|
</Drawer>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function DownloadCheckBox({mangaConnectorIdId} : {mangaConnectorIdId : string}) : ReactNode {
|
function DownloadCheckBox({
|
||||||
const Api = useContext(ApiContext);
|
mangaConnectorIdId,
|
||||||
const [useForDownloading, setUseForDownloading] = useState<boolean>(false);
|
}: {
|
||||||
const [loading, setLoading] = useState<LoadingState>(LoadingState.none);
|
mangaConnectorIdId: string;
|
||||||
|
}): ReactNode {
|
||||||
|
const Api = useContext(ApiContext);
|
||||||
|
const [useForDownloading, setUseForDownloading] = useState<boolean>(false);
|
||||||
|
const [loading, setLoading] = useState<LoadingState>(LoadingState.none);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setLoading(LoadingState.loading);
|
setLoading(LoadingState.loading);
|
||||||
Api.queryMangaMangaConnectorIdDetail(mangaConnectorIdId).then(response => {
|
Api.queryMangaMangaConnectorIdDetail(mangaConnectorIdId)
|
||||||
if (response.ok){
|
.then((response) => {
|
||||||
setUseForDownloading(response.data.useForDownload as boolean);
|
if (response.ok) {
|
||||||
setLoading(LoadingState.none);
|
setUseForDownloading(response.data.useForDownload as boolean);
|
||||||
}else
|
setLoading(LoadingState.none);
|
||||||
setLoading(LoadingState.failure);
|
} else setLoading(LoadingState.failure);
|
||||||
}).catch(_ => setLoading(LoadingState.failure));
|
})
|
||||||
}, []);
|
.catch((_) => setLoading(LoadingState.failure));
|
||||||
|
}, []);
|
||||||
|
|
||||||
const onSelected = (event: ChangeEvent<HTMLInputElement>) => {
|
const onSelected = (event: ChangeEvent<HTMLInputElement>) => {
|
||||||
setLoading(LoadingState.loading);
|
setLoading(LoadingState.loading);
|
||||||
const val = event.currentTarget.checked;
|
const val = event.currentTarget.checked;
|
||||||
Api.queryMangaMangaConnectorIdDetail(mangaConnectorIdId).then(response => {
|
Api.queryMangaMangaConnectorIdDetail(mangaConnectorIdId)
|
||||||
if (!response.ok){
|
.then((response) => {
|
||||||
setLoading(LoadingState.failure);
|
if (!response.ok) {
|
||||||
return;
|
setLoading(LoadingState.failure);
|
||||||
}
|
return;
|
||||||
Api.mangaSetAsDownloadFromCreate(response.data.objId, response.data.mangaConnectorName, val)
|
}
|
||||||
.then(response => {
|
Api.mangaSetAsDownloadFromCreate(
|
||||||
if (response.ok){
|
response.data.objId,
|
||||||
setUseForDownloading(val);
|
response.data.mangaConnectorName,
|
||||||
setLoading(LoadingState.success);
|
val,
|
||||||
}else
|
)
|
||||||
setLoading(LoadingState.failure);
|
.then((response) => {
|
||||||
}).catch(_ => setLoading(LoadingState.failure));
|
if (response.ok) {
|
||||||
}).catch(_ => setLoading(LoadingState.failure));
|
setUseForDownloading(val);
|
||||||
}
|
setLoading(LoadingState.success);
|
||||||
|
} else setLoading(LoadingState.failure);
|
||||||
|
})
|
||||||
|
.catch((_) => setLoading(LoadingState.failure));
|
||||||
|
})
|
||||||
|
.catch((_) => setLoading(LoadingState.failure));
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Checkbox
|
<Checkbox
|
||||||
indeterminateIcon={StateIndicator(LoadingState.loading)}
|
indeterminateIcon={StateIndicator(LoadingState.loading)}
|
||||||
indeterminate={loading === LoadingState.loading}
|
indeterminate={loading === LoadingState.loading}
|
||||||
disabled={loading === LoadingState.loading}
|
disabled={loading === LoadingState.loading}
|
||||||
checked={useForDownloading}
|
checked={useForDownloading}
|
||||||
onChange={onSelected}
|
onChange={onSelected}
|
||||||
label={
|
label={
|
||||||
<Typography>
|
<Typography>
|
||||||
<MangaConnectorLinkFromId MangaConnectorIdId={mangaConnectorIdId} printName={true} />
|
<MangaConnectorLinkFromId
|
||||||
</Typography>
|
MangaConnectorIdId={mangaConnectorIdId}
|
||||||
} />
|
printName={true}
|
||||||
);
|
/>
|
||||||
|
</Typography>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
@@ -1,3 +1,3 @@
|
|||||||
.manga-list {
|
.manga-list {
|
||||||
padding-top: 12px;
|
padding-top: 12px;
|
||||||
}
|
}
|
@@ -1,28 +1,38 @@
|
|||||||
import {ReactNode} from "react";
|
import { ReactNode } from "react";
|
||||||
import {MangaCard} from "./MangaCard.tsx";
|
import { MangaCard } from "./MangaCard.tsx";
|
||||||
import {Stack} from "@mui/joy";
|
import { Stack } from "@mui/joy";
|
||||||
import "./MangaList.css";
|
import "./MangaList.css";
|
||||||
import {Manga} from "../../apiClient/data-contracts.ts";
|
import { Manga } from "../../apiClient/data-contracts.ts";
|
||||||
import MangaDownloadDialog from "./MangaDownloadDialog.tsx";
|
import MangaDownloadDialog from "./MangaDownloadDialog.tsx";
|
||||||
import MangaMerge from "./MangaMerge.tsx";
|
import MangaMerge from "./MangaMerge.tsx";
|
||||||
|
|
||||||
export default function MangaList ({mangas, children} : {mangas: Manga[], children?: ReactNode}) {
|
export default function MangaList({
|
||||||
|
mangas,
|
||||||
return (
|
children,
|
||||||
<Stack className={"manga-list"} direction={"row"} useFlexGap={true} spacing={2} flexWrap={"wrap"} sx={
|
}: {
|
||||||
{
|
mangas: Manga[];
|
||||||
mx: 'calc(-1 * var(--ModalDialog-padding))',
|
children?: ReactNode;
|
||||||
px: 'var(--ModalDialog-padding)',
|
}) {
|
||||||
overflowY: 'scroll'
|
return (
|
||||||
}}>
|
<Stack
|
||||||
{children}
|
className={"manga-list"}
|
||||||
{mangas?.map(manga => (
|
direction={"row"}
|
||||||
<MangaCard key={manga.key} manga={manga}>
|
useFlexGap={true}
|
||||||
<MangaDownloadDialog manga={manga} />
|
spacing={2}
|
||||||
<MangaMerge manga={manga} />
|
flexWrap={"wrap"}
|
||||||
</MangaCard>
|
sx={{
|
||||||
))}
|
mx: "calc(-1 * var(--ModalDialog-padding))",
|
||||||
</Stack>
|
px: "var(--ModalDialog-padding)",
|
||||||
);
|
overflowY: "scroll",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
{mangas?.map((manga) => (
|
||||||
|
<MangaCard key={manga.key} manga={manga}>
|
||||||
|
<MangaDownloadDialog manga={manga} />
|
||||||
|
<MangaMerge manga={manga} />
|
||||||
|
</MangaCard>
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
}
|
}
|
@@ -1,105 +1,136 @@
|
|||||||
import {ReactNode, useContext, useEffect, useState} from "react";
|
import { ReactNode, useContext, useEffect, useState } from "react";
|
||||||
import {Manga} from "../../apiClient/data-contracts.ts";
|
import { Manga } from "../../apiClient/data-contracts.ts";
|
||||||
import Drawer from "@mui/joy/Drawer";
|
import Drawer from "@mui/joy/Drawer";
|
||||||
import ModalClose from "@mui/joy/ModalClose";
|
import ModalClose from "@mui/joy/ModalClose";
|
||||||
import {ApiContext} from "../../apiClient/ApiContext.tsx";
|
import { ApiContext } from "../../apiClient/ApiContext.tsx";
|
||||||
import {MangaCard} from "./MangaCard.tsx";
|
import { MangaCard } from "./MangaCard.tsx";
|
||||||
import {Alert, Button, Modal, ModalDialog, Stack, Tooltip, Typography} from "@mui/joy";
|
import {
|
||||||
import {KeyboardDoubleArrowRight, Warning} from "@mui/icons-material";
|
Alert,
|
||||||
import {LoadingState, StateIndicator} from "../Loading.tsx";
|
Button,
|
||||||
|
Modal,
|
||||||
|
ModalDialog,
|
||||||
|
Stack,
|
||||||
|
Tooltip,
|
||||||
|
Typography,
|
||||||
|
} from "@mui/joy";
|
||||||
|
import { KeyboardDoubleArrowRight, Warning } from "@mui/icons-material";
|
||||||
|
import { LoadingState, StateIndicator } from "../Loading.tsx";
|
||||||
|
|
||||||
export default function ({manga} : {manga : Manga | undefined}) : ReactNode {
|
export default function ({ manga }: { manga: Manga | undefined }): ReactNode {
|
||||||
const Api = useContext(ApiContext);
|
const Api = useContext(ApiContext);
|
||||||
|
|
||||||
const [similar, setSimilar] = useState<Manga[]>([]);
|
const [similar, setSimilar] = useState<Manga[]>([]);
|
||||||
const [open, setOpen] = useState<boolean>(false);
|
const [open, setOpen] = useState<boolean>(false);
|
||||||
|
|
||||||
useEffect(()=> {
|
useEffect(() => {
|
||||||
if (manga === undefined || !open)
|
if (manga === undefined || !open) return;
|
||||||
return;
|
Api.queryMangaSimilarNameList(manga.key as string).then((response) => {
|
||||||
Api.queryMangaSimilarNameList(manga.key as string).then(response => {
|
if (response.ok)
|
||||||
if (response.ok)
|
Api.mangaWithIDsCreate(response.data).then((response) => {
|
||||||
Api.mangaWithIDsCreate(response.data).then(response => {
|
if (response.ok) setSimilar(response.data);
|
||||||
if (response.ok)
|
|
||||||
setSimilar(response.data);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}, [open]);
|
});
|
||||||
|
}, [open]);
|
||||||
|
|
||||||
const exit = (manga : Manga) => {
|
const exit = (manga: Manga) => {
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
setSimilar(similar.filter(m => m.key != manga.key));
|
setSimilar(similar.filter((m) => m.key != manga.key));
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Button onClick={_ => setOpen(true)}>
|
<Button onClick={(_) => setOpen(true)}>Merge</Button>
|
||||||
Merge
|
<Drawer
|
||||||
|
size={"lg"}
|
||||||
|
open={open}
|
||||||
|
onClose={() => setOpen(false)}
|
||||||
|
anchor={"bottom"}
|
||||||
|
>
|
||||||
|
<ModalClose />
|
||||||
|
<Stack direction={"row"} spacing={2} flexWrap={"wrap"} useFlexGap>
|
||||||
|
{similar.map((similarManga) => (
|
||||||
|
<MangaCard manga={similarManga}>
|
||||||
|
<ConfirmationModal
|
||||||
|
manga={manga as Manga}
|
||||||
|
similarManga={similarManga}
|
||||||
|
exit={() => exit(similarManga)}
|
||||||
|
/>
|
||||||
|
</MangaCard>
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
</Drawer>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ConfirmationModal({
|
||||||
|
manga,
|
||||||
|
similarManga,
|
||||||
|
exit,
|
||||||
|
}: {
|
||||||
|
manga: Manga;
|
||||||
|
similarManga: Manga;
|
||||||
|
exit: () => void;
|
||||||
|
}): ReactNode {
|
||||||
|
const [open, setOpen] = useState<boolean>(false);
|
||||||
|
const Api = useContext(ApiContext);
|
||||||
|
|
||||||
|
const [loadingState, setLoadingState] = useState<LoadingState>(
|
||||||
|
LoadingState.none,
|
||||||
|
);
|
||||||
|
|
||||||
|
const merge = () => {
|
||||||
|
setLoadingState(LoadingState.loading);
|
||||||
|
Api.mangaMergeIntoPartialUpdate(
|
||||||
|
manga.key as string,
|
||||||
|
similarManga.key as string,
|
||||||
|
)
|
||||||
|
.then((response) => {
|
||||||
|
if (response.ok) {
|
||||||
|
setLoadingState(LoadingState.success);
|
||||||
|
setOpen(false);
|
||||||
|
exit();
|
||||||
|
} else setLoadingState(LoadingState.failure);
|
||||||
|
})
|
||||||
|
.catch((_) => setLoadingState(LoadingState.failure));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{loadingState == LoadingState.success ? (
|
||||||
|
<Alert
|
||||||
|
color="success"
|
||||||
|
startDecorator={StateIndicator(LoadingState.success)}
|
||||||
|
>
|
||||||
|
Merged Successfully!
|
||||||
|
</Alert>
|
||||||
|
) : (
|
||||||
|
<Button onClick={(_) => setOpen(true)}>Merge into</Button>
|
||||||
|
)}
|
||||||
|
<Modal open={open} onClose={(_) => setOpen(false)}>
|
||||||
|
<ModalDialog>
|
||||||
|
<ModalClose />
|
||||||
|
<Typography level={"h2"}>Confirm Merge</Typography>
|
||||||
|
<Stack direction={"row"} spacing={3} alignItems={"center"}>
|
||||||
|
<MangaCard manga={manga} />
|
||||||
|
<Typography color={"danger"} level={"h1"}>
|
||||||
|
<KeyboardDoubleArrowRight />
|
||||||
|
</Typography>
|
||||||
|
<MangaCard manga={similarManga} />
|
||||||
|
</Stack>
|
||||||
|
<Tooltip title={"THIS CAN NOT BE UNDONE!"}>
|
||||||
|
<Button
|
||||||
|
onClick={merge}
|
||||||
|
disabled={loadingState === LoadingState.loading}
|
||||||
|
endDecorator={StateIndicator(loadingState)}
|
||||||
|
color={"danger"}
|
||||||
|
startDecorator={<Warning />}
|
||||||
|
>
|
||||||
|
Merge
|
||||||
</Button>
|
</Button>
|
||||||
<Drawer size={"lg"} open={open} onClose={() => setOpen(false)} anchor={"bottom"}>
|
</Tooltip>
|
||||||
<ModalClose />
|
</ModalDialog>
|
||||||
<Stack direction={"row"} spacing={2} flexWrap={"wrap"} useFlexGap>
|
</Modal>
|
||||||
{similar.map(similarManga => <MangaCard manga={similarManga}>
|
</>
|
||||||
<ConfirmationModal manga={manga as Manga} similarManga={similarManga} exit={() => exit(similarManga)} />
|
);
|
||||||
</MangaCard>)}
|
|
||||||
</Stack>
|
|
||||||
</Drawer>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function ConfirmationModal({manga, similarManga, exit} : {manga : Manga, similarManga : Manga, exit: ()=>void}) : ReactNode {
|
|
||||||
const [open, setOpen] = useState<boolean>(false);
|
|
||||||
const Api = useContext(ApiContext);
|
|
||||||
|
|
||||||
const [loadingState, setLoadingState] = useState<LoadingState>(LoadingState.none);
|
|
||||||
|
|
||||||
const merge = () => {
|
|
||||||
setLoadingState(LoadingState.loading);
|
|
||||||
Api.mangaMergeIntoPartialUpdate(manga.key as string, similarManga.key as string).then(response => {
|
|
||||||
if (response.ok){
|
|
||||||
setLoadingState(LoadingState.success);
|
|
||||||
setOpen(false);
|
|
||||||
exit();
|
|
||||||
}else
|
|
||||||
setLoadingState(LoadingState.failure);
|
|
||||||
}).catch(_ => setLoadingState(LoadingState.failure));
|
|
||||||
}
|
|
||||||
|
|
||||||
return <>
|
|
||||||
{
|
|
||||||
loadingState == LoadingState.success ?
|
|
||||||
<Alert
|
|
||||||
color="success"
|
|
||||||
startDecorator={StateIndicator(LoadingState.success)}
|
|
||||||
>
|
|
||||||
Merged Successfully!
|
|
||||||
</Alert>
|
|
||||||
:
|
|
||||||
<Button onClick={_ => setOpen(true)}>
|
|
||||||
Merge into
|
|
||||||
</Button>
|
|
||||||
}
|
|
||||||
<Modal open={open} onClose={_ => setOpen(false)}>
|
|
||||||
<ModalDialog>
|
|
||||||
<ModalClose />
|
|
||||||
<Typography level={"h2"}>Confirm Merge</Typography>
|
|
||||||
<Stack direction={"row"} spacing={3} alignItems={"center"}>
|
|
||||||
<MangaCard manga={manga} />
|
|
||||||
<Typography color={"danger"} level={"h1"}><KeyboardDoubleArrowRight /></Typography>
|
|
||||||
<MangaCard manga={similarManga} />
|
|
||||||
</Stack>
|
|
||||||
<Tooltip title={"THIS CAN NOT BE UNDONE!"}>
|
|
||||||
<Button
|
|
||||||
onClick={merge}
|
|
||||||
disabled={loadingState === LoadingState.loading}
|
|
||||||
endDecorator={StateIndicator(loadingState)}
|
|
||||||
color={"danger"}
|
|
||||||
startDecorator={<Warning />}>
|
|
||||||
Merge
|
|
||||||
</Button>
|
|
||||||
</Tooltip>
|
|
||||||
</ModalDialog>
|
|
||||||
</Modal>
|
|
||||||
</>;
|
|
||||||
}
|
}
|
@@ -1,138 +1,175 @@
|
|||||||
import {Dispatch, KeyboardEventHandler, ReactNode, useContext, useState} from "react";
|
|
||||||
import {
|
import {
|
||||||
Badge,
|
Dispatch,
|
||||||
Button,
|
KeyboardEventHandler,
|
||||||
Card,
|
ReactNode,
|
||||||
CardContent,
|
useContext,
|
||||||
CardCover, Chip,
|
useState,
|
||||||
Input,
|
} from "react";
|
||||||
Modal,
|
import {
|
||||||
ModalDialog,
|
Badge,
|
||||||
Option,
|
Button,
|
||||||
Select,
|
Card,
|
||||||
Step,
|
CardContent,
|
||||||
StepIndicator,
|
CardCover,
|
||||||
Stepper,
|
Chip,
|
||||||
Typography
|
Input,
|
||||||
|
Modal,
|
||||||
|
ModalDialog,
|
||||||
|
Option,
|
||||||
|
Select,
|
||||||
|
Step,
|
||||||
|
StepIndicator,
|
||||||
|
Stepper,
|
||||||
|
Typography,
|
||||||
} from "@mui/joy";
|
} from "@mui/joy";
|
||||||
import ModalClose from "@mui/joy/ModalClose";
|
import ModalClose from "@mui/joy/ModalClose";
|
||||||
import {MangaConnectorContext} from "../App.tsx";
|
import { MangaConnectorContext } from "../App.tsx";
|
||||||
import {Manga, MangaConnector} from "../apiClient/data-contracts.ts";
|
import { Manga, MangaConnector } from "../apiClient/data-contracts.ts";
|
||||||
import MangaList from "./Mangas/MangaList.tsx";
|
import MangaList from "./Mangas/MangaList.tsx";
|
||||||
import {ApiContext} from "../apiClient/ApiContext.tsx";
|
import { ApiContext } from "../apiClient/ApiContext.tsx";
|
||||||
import {LoadingState, StateColor, StateIndicator} from "./Loading.tsx";
|
import { LoadingState, StateColor, StateIndicator } from "./Loading.tsx";
|
||||||
|
|
||||||
export default function () : ReactNode {
|
export default function (): ReactNode {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Badge badgeContent={"+"}>
|
<Badge badgeContent={"+"}>
|
||||||
<Card onClick={() => {if (!open) setOpen(true)}} className={"manga-card"}>
|
<Card
|
||||||
<CardCover className={"manga-cover"}>
|
onClick={() => {
|
||||||
<img src={"/blahaj.png"} />
|
if (!open) setOpen(true);
|
||||||
</CardCover>
|
}}
|
||||||
<CardCover className={"manga-cover-blur"} />
|
className={"manga-card"}
|
||||||
<CardContent>
|
>
|
||||||
Add
|
<CardCover className={"manga-cover"}>
|
||||||
</CardContent>
|
<img src={"/blahaj.png"} />
|
||||||
<CardContent>
|
</CardCover>
|
||||||
<SearchDialog open={open} setOpen={setOpen} />
|
<CardCover className={"manga-cover-blur"} />
|
||||||
</CardContent>
|
<CardContent>Add</CardContent>
|
||||||
</Card>
|
<CardContent>
|
||||||
</Badge>
|
<SearchDialog open={open} setOpen={setOpen} />
|
||||||
);
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</Badge>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function SearchDialog ({open, setOpen} : {open: boolean, setOpen: Dispatch<boolean>}) : ReactNode {
|
function SearchDialog({
|
||||||
const mangaConnectors = useContext(MangaConnectorContext);
|
open,
|
||||||
const Api = useContext(ApiContext);
|
setOpen,
|
||||||
|
}: {
|
||||||
|
open: boolean;
|
||||||
|
setOpen: Dispatch<boolean>;
|
||||||
|
}): ReactNode {
|
||||||
|
const mangaConnectors = useContext(MangaConnectorContext);
|
||||||
|
const Api = useContext(ApiContext);
|
||||||
|
|
||||||
const [selectedMangaConnector, setSelectedMangaConnector] = useState<MangaConnector | undefined>(undefined);
|
const [selectedMangaConnector, setSelectedMangaConnector] = useState<
|
||||||
const [searchTerm, setSearchTerm] = useState<string>();
|
MangaConnector | undefined
|
||||||
const [searchResults, setSearchResults] = useState<Manga[]>([]);
|
>(undefined);
|
||||||
|
const [searchTerm, setSearchTerm] = useState<string>();
|
||||||
|
const [searchResults, setSearchResults] = useState<Manga[]>([]);
|
||||||
|
|
||||||
const [loadingState, setLoadingState] = useState<LoadingState>(LoadingState.none);
|
const [loadingState, setLoadingState] = useState<LoadingState>(
|
||||||
|
LoadingState.none,
|
||||||
|
);
|
||||||
|
|
||||||
const doTheSearch = () => {
|
const doTheSearch = () => {
|
||||||
if (searchTerm === undefined || searchTerm.length < 1)
|
if (searchTerm === undefined || searchTerm.length < 1) return;
|
||||||
return;
|
if (!isUrl(searchTerm) && selectedMangaConnector === undefined) return;
|
||||||
if (!isUrl(searchTerm) && selectedMangaConnector === undefined)
|
|
||||||
return;
|
|
||||||
|
|
||||||
setLoadingState(LoadingState.loading);
|
setLoadingState(LoadingState.loading);
|
||||||
|
|
||||||
if (isUrl(searchTerm))
|
if (isUrl(searchTerm))
|
||||||
Api.searchUrlCreate(searchTerm)
|
Api.searchUrlCreate(searchTerm)
|
||||||
.then(response => {
|
.then((response) => {
|
||||||
if (response.ok){
|
if (response.ok) {
|
||||||
setSearchResults([response.data]);
|
setSearchResults([response.data]);
|
||||||
setLoadingState(LoadingState.success);
|
setLoadingState(LoadingState.success);
|
||||||
}else
|
} else setLoadingState(LoadingState.failure);
|
||||||
setLoadingState(LoadingState.failure);
|
})
|
||||||
}).catch(() => setLoadingState(LoadingState.failure));
|
.catch(() => setLoadingState(LoadingState.failure));
|
||||||
else
|
else
|
||||||
Api.searchDetail(selectedMangaConnector!.name, searchTerm)
|
Api.searchDetail(selectedMangaConnector!.name, searchTerm)
|
||||||
.then(response => {
|
.then((response) => {
|
||||||
if(response.ok){
|
if (response.ok) {
|
||||||
setSearchResults(response.data);
|
setSearchResults(response.data);
|
||||||
setLoadingState(LoadingState.success);
|
setLoadingState(LoadingState.success);
|
||||||
}else
|
} else setLoadingState(LoadingState.failure);
|
||||||
setLoadingState(LoadingState.failure);
|
})
|
||||||
}).catch(() => setLoadingState(LoadingState.failure));
|
.catch(() => setLoadingState(LoadingState.failure));
|
||||||
|
};
|
||||||
|
|
||||||
|
const isUrl = (url: string) => {
|
||||||
|
try {
|
||||||
|
new URL(url);
|
||||||
|
return true;
|
||||||
|
} catch (Error) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const isUrl = (url: string) => {
|
const keyDownCheck: KeyboardEventHandler<HTMLInputElement> = (e) => {
|
||||||
try{
|
if (e.key === "Enter") {
|
||||||
new URL(url);
|
doTheSearch();
|
||||||
return true;
|
|
||||||
}catch (Error){
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const keyDownCheck : KeyboardEventHandler<HTMLInputElement> = (e) => {
|
return (
|
||||||
if (e.key === "Enter") {
|
<Modal
|
||||||
doTheSearch();
|
sx={{ width: "100%", height: "100%" }}
|
||||||
}
|
open={open}
|
||||||
}
|
onClose={() => setOpen(false)}
|
||||||
|
>
|
||||||
return (
|
<ModalDialog sx={{ width: "80%" }}>
|
||||||
<Modal sx={{width: "100%", height: "100%"}} open={open} onClose={() => setOpen(false)}>
|
<ModalClose />
|
||||||
<ModalDialog sx={{width: "80%"}}>
|
<Stepper orientation={"vertical"}>
|
||||||
<ModalClose />
|
<Step indicator={<StepIndicator>1</StepIndicator>}>
|
||||||
<Stepper orientation={"vertical"}>
|
<Typography>Connector</Typography>
|
||||||
<Step indicator={<StepIndicator>1</StepIndicator>}>
|
<Select
|
||||||
<Typography>Connector</Typography>
|
disabled={loadingState == LoadingState.loading}
|
||||||
<Select
|
onChange={(_, v) =>
|
||||||
disabled={loadingState == LoadingState.loading}
|
setSelectedMangaConnector(v as MangaConnector)
|
||||||
onChange={(_, v) => setSelectedMangaConnector(v as MangaConnector)}>
|
}
|
||||||
{mangaConnectors?.map(con => (
|
>
|
||||||
<Option value={con}>
|
{mangaConnectors?.map((con) => (
|
||||||
<Typography><img src={con.iconUrl} style={{maxHeight: "var(--Icon-fontSize)"}} />{con.name}</Typography>
|
<Option value={con}>
|
||||||
</Option>
|
<Typography>
|
||||||
))}
|
<img
|
||||||
</Select>
|
src={con.iconUrl}
|
||||||
</Step>
|
style={{ maxHeight: "var(--Icon-fontSize)" }}
|
||||||
<Step indicator={<StepIndicator>2</StepIndicator>}>
|
/>
|
||||||
<Typography>Search</Typography>
|
{con.name}
|
||||||
<Input
|
</Typography>
|
||||||
disabled={loadingState == LoadingState.loading}
|
</Option>
|
||||||
onKeyDown={keyDownCheck}
|
))}
|
||||||
onChange={(e) => setSearchTerm(e.currentTarget.value)}
|
</Select>
|
||||||
endDecorator={<Button
|
</Step>
|
||||||
disabled={loadingState == LoadingState.loading}
|
<Step indicator={<StepIndicator>2</StepIndicator>}>
|
||||||
onClick={doTheSearch}
|
<Typography>Search</Typography>
|
||||||
endDecorator={StateIndicator(loadingState)}
|
<Input
|
||||||
color={StateColor(loadingState)}
|
disabled={loadingState == LoadingState.loading}
|
||||||
>Search</Button>}
|
onKeyDown={keyDownCheck}
|
||||||
/>
|
onChange={(e) => setSearchTerm(e.currentTarget.value)}
|
||||||
</Step>
|
endDecorator={
|
||||||
<Step indicator={<StepIndicator>3</StepIndicator>}>
|
<Button
|
||||||
<Typography>Result <Chip>{searchResults.length}</Chip></Typography>
|
disabled={loadingState == LoadingState.loading}
|
||||||
<MangaList mangas={searchResults} />
|
onClick={doTheSearch}
|
||||||
</Step>
|
endDecorator={StateIndicator(loadingState)}
|
||||||
</Stepper>
|
color={StateColor(loadingState)}
|
||||||
</ModalDialog>
|
>
|
||||||
</Modal>
|
Search
|
||||||
);
|
</Button>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Step>
|
||||||
|
<Step indicator={<StepIndicator>3</StepIndicator>}>
|
||||||
|
<Typography>
|
||||||
|
Result <Chip>{searchResults.length}</Chip>
|
||||||
|
</Typography>
|
||||||
|
<MangaList mangas={searchResults} />
|
||||||
|
</Step>
|
||||||
|
</Stepper>
|
||||||
|
</ModalDialog>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
}
|
}
|
@@ -1,133 +1,231 @@
|
|||||||
import {ReactNode, useContext, useState} from "react";
|
import { ReactNode, useContext, useState } from "react";
|
||||||
import { ApiContext } from "../../apiClient/ApiContext";
|
import { ApiContext } from "../../apiClient/ApiContext";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Input,
|
Input,
|
||||||
Modal,
|
Modal,
|
||||||
ModalDialog,
|
ModalDialog,
|
||||||
Stack,
|
Stack,
|
||||||
Tab,
|
Tab,
|
||||||
TabList,
|
TabList,
|
||||||
TabPanel,
|
TabPanel,
|
||||||
Tabs
|
Tabs,
|
||||||
} from "@mui/joy";
|
} from "@mui/joy";
|
||||||
import ModalClose from "@mui/joy/ModalClose";
|
import ModalClose from "@mui/joy/ModalClose";
|
||||||
import {GotifyRecord, NtfyRecord, PushoverRecord} from "../../apiClient/data-contracts.ts";
|
import {
|
||||||
import {LoadingState, StateColor, StateIndicator} from "../Loading.tsx";
|
GotifyRecord,
|
||||||
|
NtfyRecord,
|
||||||
|
PushoverRecord,
|
||||||
|
} from "../../apiClient/data-contracts.ts";
|
||||||
|
import { LoadingState, StateColor, StateIndicator } from "../Loading.tsx";
|
||||||
|
|
||||||
export default function ({open, setOpen} : {open: boolean, setOpen: (open: boolean) => void}) {
|
export default function ({
|
||||||
|
open,
|
||||||
return (
|
setOpen,
|
||||||
<Modal open={open} onClose={() => setOpen(false)}>
|
}: {
|
||||||
<ModalDialog>
|
open: boolean;
|
||||||
<ModalClose />
|
setOpen: (open: boolean) => void;
|
||||||
<Tabs sx={{width:'95%'}} defaultValue={"gotify"}>
|
}) {
|
||||||
<TabList>
|
return (
|
||||||
<Tab value={"gotify"}>Gotify</Tab>
|
<Modal open={open} onClose={() => setOpen(false)}>
|
||||||
<Tab value={"ntfy"}>Ntfy</Tab>
|
<ModalDialog>
|
||||||
<Tab value={"pushover"}>Pushover</Tab>
|
<ModalClose />
|
||||||
</TabList>
|
<Tabs sx={{ width: "95%" }} defaultValue={"gotify"}>
|
||||||
<Gotify />
|
<TabList>
|
||||||
<Ntfy />
|
<Tab value={"gotify"}>Gotify</Tab>
|
||||||
<Pushover />
|
<Tab value={"ntfy"}>Ntfy</Tab>
|
||||||
</Tabs>
|
<Tab value={"pushover"}>Pushover</Tab>
|
||||||
</ModalDialog>
|
</TabList>
|
||||||
</Modal>
|
<Gotify />
|
||||||
);
|
<Ntfy />
|
||||||
|
<Pushover />
|
||||||
|
</Tabs>
|
||||||
|
</ModalDialog>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function NotificationConnectorTab({ value, children, add, state }: { value: string, children: ReactNode, add: (data: any) => void, state: LoadingState }) {
|
function NotificationConnectorTab({
|
||||||
|
value,
|
||||||
|
children,
|
||||||
|
add,
|
||||||
|
state,
|
||||||
|
}: {
|
||||||
|
value: string;
|
||||||
|
children: ReactNode;
|
||||||
|
add: (data: any) => void;
|
||||||
|
state: LoadingState;
|
||||||
|
}) {
|
||||||
|
const IsLoading = (state: LoadingState): boolean =>
|
||||||
|
state === LoadingState.loading;
|
||||||
|
|
||||||
const IsLoading = (state : LoadingState) : boolean => state === LoadingState.loading;
|
return (
|
||||||
|
<TabPanel value={value}>
|
||||||
return (
|
<Stack spacing={1}>
|
||||||
<TabPanel value={value}>
|
{children}
|
||||||
<Stack spacing={1}>
|
<Button
|
||||||
{children}
|
onClick={add}
|
||||||
<Button onClick={add} endDecorator={StateIndicator(state)} loading={IsLoading(state)} disabled={IsLoading(state)} color={StateColor(state)}>Add</Button>
|
endDecorator={StateIndicator(state)}
|
||||||
</Stack>
|
loading={IsLoading(state)}
|
||||||
</TabPanel>
|
disabled={IsLoading(state)}
|
||||||
);
|
color={StateColor(state)}
|
||||||
|
>
|
||||||
|
Add
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
</TabPanel>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function Gotify() {
|
function Gotify() {
|
||||||
const Api = useContext(ApiContext);
|
const Api = useContext(ApiContext);
|
||||||
const [gotifyData, setGotifyData] = useState<GotifyRecord>({});
|
const [gotifyData, setGotifyData] = useState<GotifyRecord>({});
|
||||||
const [loadingState, setLoadingState] = useState<LoadingState>(LoadingState.none);
|
const [loadingState, setLoadingState] = useState<LoadingState>(
|
||||||
|
LoadingState.none,
|
||||||
|
);
|
||||||
|
|
||||||
const Add = () => {
|
const Add = () => {
|
||||||
setLoadingState(LoadingState.loading);
|
setLoadingState(LoadingState.loading);
|
||||||
Api.notificationConnectorGotifyUpdate(gotifyData)
|
Api.notificationConnectorGotifyUpdate(gotifyData)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
if (response.ok)
|
if (response.ok) setLoadingState(LoadingState.success);
|
||||||
setLoadingState(LoadingState.success);
|
else setLoadingState(LoadingState.failure);
|
||||||
else
|
})
|
||||||
setLoadingState(LoadingState.failure);
|
.catch((_) => setLoadingState(LoadingState.failure));
|
||||||
})
|
};
|
||||||
.catch(_ => setLoadingState(LoadingState.failure));
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NotificationConnectorTab value={"gotify"} add={Add} state={loadingState}>
|
<NotificationConnectorTab value={"gotify"} add={Add} state={loadingState}>
|
||||||
<Input placeholder={"Name"} value={gotifyData.name as string} onChange={(e) => setGotifyData({...gotifyData, name: e.target.value})} />
|
<Input
|
||||||
<Input placeholder={"https://[...]/message"} value={gotifyData.endpoint as string} onChange={(e) => setGotifyData({...gotifyData, endpoint: e.target.value})} />
|
placeholder={"Name"}
|
||||||
<Input placeholder={"Apptoken"} type={"password"} value={gotifyData.appToken as string} onChange={(e) => setGotifyData({...gotifyData, appToken: e.target.value})} />
|
value={gotifyData.name as string}
|
||||||
<Input placeholder={"Priority"} type={"number"} value={gotifyData.priority as number} onChange={(e) => setGotifyData({...gotifyData, priority: e.target.valueAsNumber})} />
|
onChange={(e) => setGotifyData({ ...gotifyData, name: e.target.value })}
|
||||||
</NotificationConnectorTab>
|
/>
|
||||||
);
|
<Input
|
||||||
|
placeholder={"https://[...]/message"}
|
||||||
|
value={gotifyData.endpoint as string}
|
||||||
|
onChange={(e) =>
|
||||||
|
setGotifyData({ ...gotifyData, endpoint: e.target.value })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
placeholder={"Apptoken"}
|
||||||
|
type={"password"}
|
||||||
|
value={gotifyData.appToken as string}
|
||||||
|
onChange={(e) =>
|
||||||
|
setGotifyData({ ...gotifyData, appToken: e.target.value })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
placeholder={"Priority"}
|
||||||
|
type={"number"}
|
||||||
|
value={gotifyData.priority as number}
|
||||||
|
onChange={(e) =>
|
||||||
|
setGotifyData({ ...gotifyData, priority: e.target.valueAsNumber })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</NotificationConnectorTab>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function Ntfy() {
|
function Ntfy() {
|
||||||
const Api = useContext(ApiContext);
|
const Api = useContext(ApiContext);
|
||||||
const [ntfyData, setNtfyData] = useState<NtfyRecord>({});
|
const [ntfyData, setNtfyData] = useState<NtfyRecord>({});
|
||||||
const [loadingState, setLoadingState] = useState<LoadingState>(LoadingState.none);
|
const [loadingState, setLoadingState] = useState<LoadingState>(
|
||||||
|
LoadingState.none,
|
||||||
|
);
|
||||||
|
|
||||||
const Add = () => {
|
const Add = () => {
|
||||||
setLoadingState(LoadingState.loading);
|
setLoadingState(LoadingState.loading);
|
||||||
Api.notificationConnectorNtfyUpdate(ntfyData)
|
Api.notificationConnectorNtfyUpdate(ntfyData)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
if (response.ok)
|
if (response.ok) setLoadingState(LoadingState.success);
|
||||||
setLoadingState(LoadingState.success);
|
else setLoadingState(LoadingState.failure);
|
||||||
else
|
})
|
||||||
setLoadingState(LoadingState.failure);
|
.catch((_) => setLoadingState(LoadingState.failure));
|
||||||
})
|
};
|
||||||
.catch(_ => setLoadingState(LoadingState.failure));
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NotificationConnectorTab value={"ntfy"} add={Add} state={loadingState}>
|
<NotificationConnectorTab value={"ntfy"} add={Add} state={loadingState}>
|
||||||
<Input placeholder={"Name"} value={ntfyData.name as string} onChange={(e) => setNtfyData({...ntfyData, name: e.target.value})} />
|
<Input
|
||||||
<Input placeholder={"Endpoint"} value={ntfyData.endpoint as string} onChange={(e) => setNtfyData({...ntfyData, endpoint: e.target.value})} />
|
placeholder={"Name"}
|
||||||
<Input placeholder={"Topic"} value={ntfyData.topic as string} onChange={(e) => setNtfyData({...ntfyData, topic: e.target.value})} />
|
value={ntfyData.name as string}
|
||||||
<Input placeholder={"Username"} value={ntfyData.username as string} onChange={(e) => setNtfyData({...ntfyData, username: e.target.value})} />
|
onChange={(e) => setNtfyData({ ...ntfyData, name: e.target.value })}
|
||||||
<Input placeholder={"Password"} type={"password"} value={ntfyData.password as string} onChange={(e) => setNtfyData({...ntfyData, password: e.target.value})} />
|
/>
|
||||||
<Input placeholder={"Priority"} type={"number"} value={ntfyData.priority as number} onChange={(e) => setNtfyData({...ntfyData, priority: e.target.valueAsNumber})} />
|
<Input
|
||||||
</NotificationConnectorTab>
|
placeholder={"Endpoint"}
|
||||||
);
|
value={ntfyData.endpoint as string}
|
||||||
|
onChange={(e) => setNtfyData({ ...ntfyData, endpoint: e.target.value })}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
placeholder={"Topic"}
|
||||||
|
value={ntfyData.topic as string}
|
||||||
|
onChange={(e) => setNtfyData({ ...ntfyData, topic: e.target.value })}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
placeholder={"Username"}
|
||||||
|
value={ntfyData.username as string}
|
||||||
|
onChange={(e) => setNtfyData({ ...ntfyData, username: e.target.value })}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
placeholder={"Password"}
|
||||||
|
type={"password"}
|
||||||
|
value={ntfyData.password as string}
|
||||||
|
onChange={(e) => setNtfyData({ ...ntfyData, password: e.target.value })}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
placeholder={"Priority"}
|
||||||
|
type={"number"}
|
||||||
|
value={ntfyData.priority as number}
|
||||||
|
onChange={(e) =>
|
||||||
|
setNtfyData({ ...ntfyData, priority: e.target.valueAsNumber })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</NotificationConnectorTab>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function Pushover() {
|
function Pushover() {
|
||||||
const Api = useContext(ApiContext);
|
const Api = useContext(ApiContext);
|
||||||
const [pushoverData, setPushoverData] = useState<PushoverRecord>({});
|
const [pushoverData, setPushoverData] = useState<PushoverRecord>({});
|
||||||
const [loadingState, setLoadingState] = useState<LoadingState>(LoadingState.none);
|
const [loadingState, setLoadingState] = useState<LoadingState>(
|
||||||
|
LoadingState.none,
|
||||||
|
);
|
||||||
|
|
||||||
const Add = () => {
|
const Add = () => {
|
||||||
setLoadingState(LoadingState.loading);
|
setLoadingState(LoadingState.loading);
|
||||||
Api.notificationConnectorPushoverUpdate(pushoverData)
|
Api.notificationConnectorPushoverUpdate(pushoverData)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
if (response.ok)
|
if (response.ok) setLoadingState(LoadingState.success);
|
||||||
setLoadingState(LoadingState.success);
|
else setLoadingState(LoadingState.failure);
|
||||||
else
|
})
|
||||||
setLoadingState(LoadingState.failure);
|
.catch((_) => setLoadingState(LoadingState.failure));
|
||||||
})
|
};
|
||||||
.catch(_ => setLoadingState(LoadingState.failure));
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NotificationConnectorTab value={"pushover"} add={Add} state={loadingState}>
|
<NotificationConnectorTab value={"pushover"} add={Add} state={loadingState}>
|
||||||
<Input placeholder={"Name"} value={pushoverData.name as string} onChange={(e) => setPushoverData({...pushoverData, name: e.target.value})} />
|
<Input
|
||||||
<Input placeholder={"User"} value={pushoverData.user as string} onChange={(e) => setPushoverData({...pushoverData, user: e.target.value})} />
|
placeholder={"Name"}
|
||||||
<Input placeholder={"AppToken"} type={"password"} value={pushoverData.appToken as string} onChange={(e) => setPushoverData({...pushoverData, appToken: e.target.value})} />
|
value={pushoverData.name as string}
|
||||||
</NotificationConnectorTab>
|
onChange={(e) =>
|
||||||
);
|
setPushoverData({ ...pushoverData, name: e.target.value })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
placeholder={"User"}
|
||||||
|
value={pushoverData.user as string}
|
||||||
|
onChange={(e) =>
|
||||||
|
setPushoverData({ ...pushoverData, user: e.target.value })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
placeholder={"AppToken"}
|
||||||
|
type={"password"}
|
||||||
|
value={pushoverData.appToken as string}
|
||||||
|
onChange={(e) =>
|
||||||
|
setPushoverData({ ...pushoverData, appToken: e.target.value })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</NotificationConnectorTab>
|
||||||
|
);
|
||||||
}
|
}
|
@@ -1,35 +1,43 @@
|
|||||||
import {ReactNode, useContext, useState} from "react";
|
import { ReactNode, useContext, useState } from "react";
|
||||||
import {SettingsContext, SettingsItem} from "./Settings.tsx";
|
import { SettingsContext, SettingsItem } from "./Settings.tsx";
|
||||||
import {ApiContext} from "../../apiClient/ApiContext.tsx";
|
import { ApiContext } from "../../apiClient/ApiContext.tsx";
|
||||||
import {ColorPaletteProp, Input} from "@mui/joy";
|
import { ColorPaletteProp, Input } from "@mui/joy";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import MarkdownPreview from "@uiw/react-markdown-preview";
|
import MarkdownPreview from "@uiw/react-markdown-preview";
|
||||||
|
|
||||||
export default function () : ReactNode {
|
export default function (): ReactNode {
|
||||||
const settings = useContext(SettingsContext);
|
const settings = useContext(SettingsContext);
|
||||||
const Api = useContext(ApiContext);
|
const Api = useContext(ApiContext);
|
||||||
|
|
||||||
const [scheme, setScheme] = useState<ColorPaletteProp>("neutral");
|
const [scheme, setScheme] = useState<ColorPaletteProp>("neutral");
|
||||||
const timerRef = React.useRef<ReturnType<typeof setTimeout>>(undefined);
|
const timerRef = React.useRef<ReturnType<typeof setTimeout>>(undefined);
|
||||||
const schemeChanged = (e : React.ChangeEvent<HTMLInputElement>) => {
|
const schemeChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
clearTimeout(timerRef.current);
|
clearTimeout(timerRef.current);
|
||||||
setScheme("warning");
|
setScheme("warning");
|
||||||
timerRef.current = setTimeout(() => {
|
timerRef.current = setTimeout(() => {
|
||||||
Api.settingsChapterNamingSchemePartialUpdate(e.target.value)
|
Api.settingsChapterNamingSchemePartialUpdate(e.target.value)
|
||||||
.then(response => {
|
.then((response) => {
|
||||||
if (response.ok)
|
if (response.ok) setScheme("success");
|
||||||
setScheme("success");
|
else setScheme("danger");
|
||||||
else
|
})
|
||||||
setScheme("danger");
|
.catch(() => setScheme("danger"));
|
||||||
})
|
}, 1000);
|
||||||
.catch(() => setScheme("danger"));
|
};
|
||||||
}, 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingsItem title={"Chapter Naming Scheme"}>
|
<SettingsItem title={"Chapter Naming Scheme"}>
|
||||||
<MarkdownPreview style={{backgroundColor: "transparent"}} source={"Placeholders:\n * %M Obj Name\n * %V Volume\n * %C Chapter\n * %T Title\n * %A Author (first in list)\n * %I Chapter Internal ID\n * %i Obj Internal ID\n * %Y Year (Obj)\n *\n * ?_(...) replace _ with a value from above:\n * Everything inside the braces will only be added if the value of %_ is not null"} />
|
<MarkdownPreview
|
||||||
<Input color={scheme} defaultValue={settings?.chapterNamingScheme as string} placeholder={"Scheme"} onChange={schemeChanged} />
|
style={{ backgroundColor: "transparent" }}
|
||||||
</SettingsItem>
|
source={
|
||||||
);
|
"Placeholders:\n * %M Obj Name\n * %V Volume\n * %C Chapter\n * %T Title\n * %A Author (first in list)\n * %I Chapter Internal ID\n * %i Obj Internal ID\n * %Y Year (Obj)\n *\n * ?_(...) replace _ with a value from above:\n * Everything inside the braces will only be added if the value of %_ is not null"
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
color={scheme}
|
||||||
|
defaultValue={settings?.chapterNamingScheme as string}
|
||||||
|
placeholder={"Scheme"}
|
||||||
|
onChange={schemeChanged}
|
||||||
|
/>
|
||||||
|
</SettingsItem>
|
||||||
|
);
|
||||||
}
|
}
|
@@ -1,33 +1,36 @@
|
|||||||
import {ReactNode, useContext, useState} from "react";
|
import { ReactNode, useContext, useState } from "react";
|
||||||
import {SettingsContext, SettingsItem} from "./Settings.tsx";
|
import { SettingsContext, SettingsItem } from "./Settings.tsx";
|
||||||
import {ApiContext} from "../../apiClient/ApiContext.tsx";
|
import { ApiContext } from "../../apiClient/ApiContext.tsx";
|
||||||
import {ColorPaletteProp, Input} from "@mui/joy";
|
import { ColorPaletteProp, Input } from "@mui/joy";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
|
||||||
export default function () : ReactNode {
|
export default function (): ReactNode {
|
||||||
const settings = useContext(SettingsContext);
|
const settings = useContext(SettingsContext);
|
||||||
const Api = useContext(ApiContext);
|
const Api = useContext(ApiContext);
|
||||||
|
|
||||||
const [color, setColor] = useState<ColorPaletteProp>("neutral");
|
const [color, setColor] = useState<ColorPaletteProp>("neutral");
|
||||||
const timerRef = React.useRef<ReturnType<typeof setTimeout>>(undefined);
|
const timerRef = React.useRef<ReturnType<typeof setTimeout>>(undefined);
|
||||||
const languageChanged = (e : React.ChangeEvent<HTMLInputElement>) => {
|
const languageChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
clearTimeout(timerRef.current);
|
clearTimeout(timerRef.current);
|
||||||
setColor("warning");
|
setColor("warning");
|
||||||
timerRef.current = setTimeout(() => {
|
timerRef.current = setTimeout(() => {
|
||||||
Api.settingsDownloadLanguagePartialUpdate(e.target.value)
|
Api.settingsDownloadLanguagePartialUpdate(e.target.value)
|
||||||
.then(response => {
|
.then((response) => {
|
||||||
if (response.ok)
|
if (response.ok) setColor("success");
|
||||||
setColor("success");
|
else setColor("danger");
|
||||||
else
|
})
|
||||||
setColor("danger");
|
.catch(() => setColor("danger"));
|
||||||
})
|
}, 1000);
|
||||||
.catch(() => setColor("danger"));
|
};
|
||||||
}, 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingsItem title={"Download Language"}>
|
<SettingsItem title={"Download Language"}>
|
||||||
<Input color={color} defaultValue={settings?.downloadLanguage as string} placeholder={"Language code (f.e. 'en')"} onChange={languageChanged} />
|
<Input
|
||||||
</SettingsItem>
|
color={color}
|
||||||
);
|
defaultValue={settings?.downloadLanguage as string}
|
||||||
|
placeholder={"Language code (f.e. 'en')"}
|
||||||
|
onChange={languageChanged}
|
||||||
|
/>
|
||||||
|
</SettingsItem>
|
||||||
|
);
|
||||||
}
|
}
|
@@ -1,33 +1,37 @@
|
|||||||
import {ReactNode, useContext, useState} from "react";
|
import { ReactNode, useContext, useState } from "react";
|
||||||
import {SettingsContext, SettingsItem} from "./Settings.tsx";
|
import { SettingsContext, SettingsItem } from "./Settings.tsx";
|
||||||
import {ColorPaletteProp, Input} from "@mui/joy";
|
import { ColorPaletteProp, Input } from "@mui/joy";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import {ApiContext} from "../../apiClient/ApiContext.tsx";
|
import { ApiContext } from "../../apiClient/ApiContext.tsx";
|
||||||
|
|
||||||
export default function () : ReactNode {
|
export default function (): ReactNode {
|
||||||
const settings = useContext(SettingsContext);
|
const settings = useContext(SettingsContext);
|
||||||
const Api = useContext(ApiContext);
|
const Api = useContext(ApiContext);
|
||||||
|
|
||||||
const [uriColor, setUriColor] = useState<ColorPaletteProp>("neutral");
|
const [uriColor, setUriColor] = useState<ColorPaletteProp>("neutral");
|
||||||
const timerRef = React.useRef<ReturnType<typeof setTimeout>>(undefined);
|
const timerRef = React.useRef<ReturnType<typeof setTimeout>>(undefined);
|
||||||
const uriChanged = (e : React.ChangeEvent<HTMLInputElement>) => {
|
const uriChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
clearTimeout(timerRef.current);
|
clearTimeout(timerRef.current);
|
||||||
setUriColor("warning");
|
setUriColor("warning");
|
||||||
timerRef.current = setTimeout(() => {
|
timerRef.current = setTimeout(() => {
|
||||||
Api.settingsFlareSolverrUrlCreate(e.target.value)
|
Api.settingsFlareSolverrUrlCreate(e.target.value)
|
||||||
.then(response => {
|
.then((response) => {
|
||||||
if (response.ok)
|
if (response.ok) setUriColor("success");
|
||||||
setUriColor("success");
|
else setUriColor("danger");
|
||||||
else
|
})
|
||||||
setUriColor("danger");
|
.catch(() => setUriColor("danger"));
|
||||||
})
|
}, 1000);
|
||||||
.catch(() => setUriColor("danger"));
|
};
|
||||||
}, 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingsItem title={"FlareSolverr"}>
|
<SettingsItem title={"FlareSolverr"}>
|
||||||
<Input color={uriColor} defaultValue={settings?.flareSolverrUrl as string} type={"url"} placeholder={"URL"} onChange={uriChanged} />
|
<Input
|
||||||
</SettingsItem>
|
color={uriColor}
|
||||||
);
|
defaultValue={settings?.flareSolverrUrl as string}
|
||||||
|
type={"url"}
|
||||||
|
placeholder={"URL"}
|
||||||
|
onChange={uriChanged}
|
||||||
|
/>
|
||||||
|
</SettingsItem>
|
||||||
|
);
|
||||||
}
|
}
|
@@ -1,13 +1,17 @@
|
|||||||
import {ReactNode, useContext} from "react";
|
import { ReactNode, useContext } from "react";
|
||||||
import {SettingsContext, SettingsItem} from "./Settings.tsx";
|
import { SettingsContext, SettingsItem } from "./Settings.tsx";
|
||||||
import {Slider} from "@mui/joy";
|
import { Slider } from "@mui/joy";
|
||||||
|
|
||||||
export default function () : ReactNode {
|
export default function (): ReactNode {
|
||||||
const settings = useContext(SettingsContext);
|
const settings = useContext(SettingsContext);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingsItem title={"Image Compression"}>
|
<SettingsItem title={"Image Compression"}>
|
||||||
<Slider sx={{marginTop: "20px"}} valueLabelDisplay={"auto"} defaultValue={settings?.imageCompression}></Slider>
|
<Slider
|
||||||
</SettingsItem>
|
sx={{ marginTop: "20px" }}
|
||||||
);
|
valueLabelDisplay={"auto"}
|
||||||
|
defaultValue={settings?.imageCompression}
|
||||||
|
></Slider>
|
||||||
|
</SettingsItem>
|
||||||
|
);
|
||||||
}
|
}
|
@@ -1,33 +1,33 @@
|
|||||||
import {Button} from "@mui/joy";
|
import { Button } from "@mui/joy";
|
||||||
import {SettingsItem} from "./Settings.tsx";
|
import { SettingsItem } from "./Settings.tsx";
|
||||||
import {useContext, useState} from "react";
|
import { useContext, useState } from "react";
|
||||||
import {ApiContext} from "../../apiClient/ApiContext.tsx";
|
import { ApiContext } from "../../apiClient/ApiContext.tsx";
|
||||||
import {LoadingState, StateColor, StateIndicator} from "../Loading.tsx";
|
import { LoadingState, StateColor, StateIndicator } from "../Loading.tsx";
|
||||||
|
|
||||||
export default function () {
|
export default function () {
|
||||||
const Api = useContext(ApiContext);
|
const Api = useContext(ApiContext);
|
||||||
|
|
||||||
const [unusedMangaState, setUnusedMangaState] = useState(LoadingState.none);
|
const [unusedMangaState, setUnusedMangaState] = useState(LoadingState.none);
|
||||||
const cleanUnusedManga = () => {
|
const cleanUnusedManga = () => {
|
||||||
setUnusedMangaState(LoadingState.loading);
|
setUnusedMangaState(LoadingState.loading);
|
||||||
Api.maintenanceCleanupNoDownloadMangaCreate()
|
Api.maintenanceCleanupNoDownloadMangaCreate()
|
||||||
.then(r => {
|
.then((r) => {
|
||||||
if (r.ok)
|
if (r.ok) setUnusedMangaState(LoadingState.success);
|
||||||
setUnusedMangaState(LoadingState.success);
|
else setUnusedMangaState(LoadingState.failure);
|
||||||
else
|
})
|
||||||
setUnusedMangaState(LoadingState.failure);
|
.catch((_) => setUnusedMangaState(LoadingState.failure));
|
||||||
}).catch(_ => setUnusedMangaState(LoadingState.failure));
|
};
|
||||||
}
|
|
||||||
|
|
||||||
|
return (
|
||||||
return (
|
<SettingsItem title={"Maintenance"}>
|
||||||
|
<Button
|
||||||
<SettingsItem title={"Maintenance"}>
|
disabled={unusedMangaState == LoadingState.loading}
|
||||||
<Button
|
color={StateColor(unusedMangaState)}
|
||||||
disabled={unusedMangaState == LoadingState.loading}
|
endDecorator={StateIndicator(unusedMangaState)}
|
||||||
color={StateColor(unusedMangaState)}
|
onClick={cleanUnusedManga}
|
||||||
endDecorator={StateIndicator(unusedMangaState)}
|
>
|
||||||
onClick={cleanUnusedManga}>Cleanup unused Manga</Button>
|
Cleanup unused Manga
|
||||||
</SettingsItem>
|
</Button>
|
||||||
);
|
</SettingsItem>
|
||||||
|
);
|
||||||
}
|
}
|
@@ -1,16 +1,22 @@
|
|||||||
import {ReactNode} from "react";
|
import { ReactNode } from "react";
|
||||||
import {SettingsItem} from "./Settings.tsx";
|
import { SettingsItem } from "./Settings.tsx";
|
||||||
import {Button} from "@mui/joy";
|
import { Button } from "@mui/joy";
|
||||||
import NotificationConnectors from "./AddNotificationConnector.tsx";
|
import NotificationConnectors from "./AddNotificationConnector.tsx";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
|
||||||
export default function () : ReactNode {
|
export default function (): ReactNode {
|
||||||
const [notificationConnectorsOpen, setNotificationConnectorsOpen] = React.useState(false);
|
const [notificationConnectorsOpen, setNotificationConnectorsOpen] =
|
||||||
|
React.useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingsItem title={"Notification Connectors"}>
|
<SettingsItem title={"Notification Connectors"}>
|
||||||
<Button onClick={() => setNotificationConnectorsOpen(true)}>Add Notification Connector</Button>
|
<Button onClick={() => setNotificationConnectorsOpen(true)}>
|
||||||
<NotificationConnectors open={notificationConnectorsOpen} setOpen={setNotificationConnectorsOpen} />
|
Add Notification Connector
|
||||||
</SettingsItem>
|
</Button>
|
||||||
);
|
<NotificationConnectors
|
||||||
|
open={notificationConnectorsOpen}
|
||||||
|
setOpen={setNotificationConnectorsOpen}
|
||||||
|
/>
|
||||||
|
</SettingsItem>
|
||||||
|
);
|
||||||
}
|
}
|
@@ -1,95 +1,117 @@
|
|||||||
import ModalClose from '@mui/joy/ModalClose';
|
import ModalClose from "@mui/joy/ModalClose";
|
||||||
import {
|
import {
|
||||||
Accordion,
|
Accordion,
|
||||||
AccordionDetails,
|
AccordionDetails,
|
||||||
AccordionGroup,
|
AccordionGroup,
|
||||||
AccordionSummary, Button, ColorPaletteProp,
|
AccordionSummary,
|
||||||
DialogContent,
|
Button,
|
||||||
DialogTitle, Input,
|
ColorPaletteProp,
|
||||||
Modal, ModalDialog
|
DialogContent,
|
||||||
|
DialogTitle,
|
||||||
|
Input,
|
||||||
|
Modal,
|
||||||
|
ModalDialog,
|
||||||
} from "@mui/joy";
|
} from "@mui/joy";
|
||||||
import './Settings.css';
|
import "./Settings.css";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import {createContext, Dispatch, ReactNode, useContext, useEffect, useState} from "react";
|
import {
|
||||||
import {TrangaSettings} from "../../apiClient/data-contracts.ts";
|
createContext,
|
||||||
import {ApiContext} from "../../apiClient/ApiContext.tsx";
|
Dispatch,
|
||||||
|
ReactNode,
|
||||||
|
useContext,
|
||||||
|
useEffect,
|
||||||
|
useState,
|
||||||
|
} from "react";
|
||||||
|
import { TrangaSettings } from "../../apiClient/data-contracts.ts";
|
||||||
|
import { ApiContext } from "../../apiClient/ApiContext.tsx";
|
||||||
import NotificationConnectors from "./NotificationConnectors.tsx";
|
import NotificationConnectors from "./NotificationConnectors.tsx";
|
||||||
import {SxProps} from "@mui/joy/styles/types";
|
import { SxProps } from "@mui/joy/styles/types";
|
||||||
import ImageCompression from "./ImageCompression.tsx";
|
import ImageCompression from "./ImageCompression.tsx";
|
||||||
import FlareSolverr from "./FlareSolverr.tsx";
|
import FlareSolverr from "./FlareSolverr.tsx";
|
||||||
import DownloadLanguage from "./DownloadLanguage.tsx";
|
import DownloadLanguage from "./DownloadLanguage.tsx";
|
||||||
import ChapterNamingScheme from "./ChapterNamingScheme.tsx";
|
import ChapterNamingScheme from "./ChapterNamingScheme.tsx";
|
||||||
import Maintenance from "./Maintenance.tsx";
|
import Maintenance from "./Maintenance.tsx";
|
||||||
|
|
||||||
export const SettingsContext = createContext<TrangaSettings|undefined>(undefined);
|
export const SettingsContext = createContext<TrangaSettings | undefined>(
|
||||||
|
undefined,
|
||||||
|
);
|
||||||
|
|
||||||
export default function Settings({setApiUri} : {setApiUri: Dispatch<React.SetStateAction<string>>}) {
|
export default function Settings({
|
||||||
const Api = useContext(ApiContext);
|
setApiUri,
|
||||||
const [settings, setSettings] = useState<TrangaSettings>();
|
}: {
|
||||||
|
setApiUri: Dispatch<React.SetStateAction<string>>;
|
||||||
|
}) {
|
||||||
|
const Api = useContext(ApiContext);
|
||||||
|
const [settings, setSettings] = useState<TrangaSettings>();
|
||||||
|
|
||||||
const [open, setOpen] = React.useState(false);
|
const [open, setOpen] = React.useState(false);
|
||||||
|
|
||||||
const [apiUriColor, setApiUriColor] = useState<ColorPaletteProp>("neutral");
|
const [apiUriColor, setApiUriColor] = useState<ColorPaletteProp>("neutral");
|
||||||
const timerRef = React.useRef<ReturnType<typeof setTimeout>>(undefined);
|
const timerRef = React.useRef<ReturnType<typeof setTimeout>>(undefined);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
Api.settingsList().then((response) => {
|
Api.settingsList().then((response) => {
|
||||||
setSettings(response.data);
|
setSettings(response.data);
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const apiUriChanged = (e : React.ChangeEvent<HTMLInputElement>) => {
|
const apiUriChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
clearTimeout(timerRef.current);
|
clearTimeout(timerRef.current);
|
||||||
setApiUriColor("warning");
|
setApiUriColor("warning");
|
||||||
timerRef.current = setTimeout(() => {
|
timerRef.current = setTimeout(() => {
|
||||||
setApiUri(e.target.value);
|
setApiUri(e.target.value);
|
||||||
setApiUriColor("success");
|
setApiUriColor("success");
|
||||||
}, 1000);
|
}, 1000);
|
||||||
}
|
};
|
||||||
|
|
||||||
const ModalStyle : SxProps = {
|
const ModalStyle: SxProps = {
|
||||||
width: "80%",
|
width: "80%",
|
||||||
height: "80%"
|
height: "80%",
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingsContext.Provider value={settings}>
|
<SettingsContext.Provider value={settings}>
|
||||||
<Button onClick={() => setOpen(true)}>Settings</Button>
|
<Button onClick={() => setOpen(true)}>Settings</Button>
|
||||||
<Modal open={open} onClose={() => setOpen(false)}>
|
<Modal open={open} onClose={() => setOpen(false)}>
|
||||||
<ModalDialog sx={ModalStyle}>
|
<ModalDialog sx={ModalStyle}>
|
||||||
<ModalClose />
|
<ModalClose />
|
||||||
<DialogTitle>Settings</DialogTitle>
|
<DialogTitle>Settings</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<AccordionGroup>
|
<AccordionGroup>
|
||||||
<SettingsItem title={"ApiUri"}>
|
<SettingsItem title={"ApiUri"}>
|
||||||
<Input
|
<Input
|
||||||
color={apiUriColor}
|
color={apiUriColor}
|
||||||
placeholder={"http(s)://"}
|
placeholder={"http(s)://"}
|
||||||
type={"url"}
|
type={"url"}
|
||||||
defaultValue={Api.baseUrl}
|
defaultValue={Api.baseUrl}
|
||||||
onChange={apiUriChanged} />
|
onChange={apiUriChanged}
|
||||||
</SettingsItem>
|
/>
|
||||||
<ImageCompression />
|
</SettingsItem>
|
||||||
<FlareSolverr />
|
<ImageCompression />
|
||||||
<DownloadLanguage />
|
<FlareSolverr />
|
||||||
<ChapterNamingScheme />
|
<DownloadLanguage />
|
||||||
<NotificationConnectors />
|
<ChapterNamingScheme />
|
||||||
<Maintenance />
|
<NotificationConnectors />
|
||||||
</AccordionGroup>
|
<Maintenance />
|
||||||
</DialogContent>
|
</AccordionGroup>
|
||||||
</ModalDialog>
|
</DialogContent>
|
||||||
</Modal>
|
</ModalDialog>
|
||||||
</SettingsContext.Provider>
|
</Modal>
|
||||||
);
|
</SettingsContext.Provider>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SettingsItem({title, children} : {title: string, children: ReactNode}) {
|
export function SettingsItem({
|
||||||
return (
|
title,
|
||||||
<Accordion>
|
children,
|
||||||
<AccordionSummary>{title}</AccordionSummary>
|
}: {
|
||||||
<AccordionDetails>
|
title: string;
|
||||||
{children}
|
children: ReactNode;
|
||||||
</AccordionDetails>
|
}) {
|
||||||
</Accordion>
|
return (
|
||||||
);
|
<Accordion>
|
||||||
|
<AccordionSummary>{title}</AccordionSummary>
|
||||||
|
<AccordionDetails>{children}</AccordionDetails>
|
||||||
|
</Accordion>
|
||||||
|
);
|
||||||
}
|
}
|
@@ -1,11 +1,11 @@
|
|||||||
.header {
|
.header {
|
||||||
position: sticky !important;
|
position: sticky !important;
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 60px;
|
height: 60px;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-flow: row nowrap;
|
flex-flow: row nowrap;
|
||||||
}
|
}
|
@@ -1,35 +1,91 @@
|
|||||||
import Sheet from "@mui/joy/Sheet";
|
import Sheet from "@mui/joy/Sheet";
|
||||||
import {Link, Stack, Typography} from "@mui/joy";
|
import { Link, Stack, Typography } from "@mui/joy";
|
||||||
import {ReactElement, useContext} from "react";
|
import { ReactElement, useContext } from "react";
|
||||||
import './Header.css';
|
import "./Header.css";
|
||||||
import {Article, GitHub} from "@mui/icons-material";
|
import { Article, GitHub } from "@mui/icons-material";
|
||||||
import {ApiContext} from "./apiClient/ApiContext.tsx";
|
import { ApiContext } from "./apiClient/ApiContext.tsx";
|
||||||
|
|
||||||
export default function Header({children} : {children? : ReactElement<any, any> | ReactElement<any,any>[] | undefined}) : ReactElement {
|
export default function Header({
|
||||||
const Api = useContext(ApiContext);
|
children,
|
||||||
|
}: {
|
||||||
|
children?: ReactElement<any, any> | ReactElement<any, any>[] | undefined;
|
||||||
|
}): ReactElement {
|
||||||
|
const Api = useContext(ApiContext);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Sheet className={"header"}>
|
<Sheet className={"header"}>
|
||||||
<Stack direction={"row"} spacing={2} sx={{width: "100%", alignItems: "center", justifyContent: "space-between"}} useFlexGap>
|
<Stack
|
||||||
<Stack sx={{flexGrow: 1, flexBasis: 1}} direction={"row"} spacing={2}>
|
direction={"row"}
|
||||||
{children}
|
spacing={2}
|
||||||
</Stack>
|
sx={{
|
||||||
<Stack sx={{flexGrow: 1, height: "100%", flexBasis: 1, justifyContent: "center"}} direction={"row"}>
|
width: "100%",
|
||||||
<img src={"/blahaj.png"} style={{cursor: "grab", maxHeight: "100%"}}/>
|
alignItems: "center",
|
||||||
<Typography level={"h2"} sx={{
|
justifyContent: "space-between",
|
||||||
background: "linear-gradient(110deg, var(--joy-palette-primary-solidBg), var(--joy-palette-success-400))",
|
}}
|
||||||
WebkitBackgroundClip: "text",
|
useFlexGap
|
||||||
WebkitTextFillColor: "transparent",
|
>
|
||||||
fontWeight: "bold",
|
<Stack sx={{ flexGrow: 1, flexBasis: 1 }} direction={"row"} spacing={2}>
|
||||||
cursor: "default"
|
{children}
|
||||||
}}>Tranga</Typography>
|
</Stack>
|
||||||
</Stack>
|
<Stack
|
||||||
<Stack sx={{flexGrow: 1, flexBasis: 1, justifyContent: "flex-end"}} direction={"row"} spacing={2}>
|
sx={{
|
||||||
<Link target={"_blank"} href={"https://github.com/C9Glax/tranga"} color={"neutral"} height={"min-content"} ><GitHub /> Server</Link>
|
flexGrow: 1,
|
||||||
<Link target={"_blank"} href={"https://github.com/C9Glax/tranga-website"} color={"neutral"} height={"min-content"} ><GitHub /> Website</Link>
|
height: "100%",
|
||||||
<Link target={"_blank"} href={Api.baseUrl + "/swagger"} color={"neutral"} height={"min-content"} ><Article />Swagger</Link>
|
flexBasis: 1,
|
||||||
</Stack>
|
justifyContent: "center",
|
||||||
</Stack>
|
}}
|
||||||
</Sheet>
|
direction={"row"}
|
||||||
);
|
>
|
||||||
|
<img
|
||||||
|
src={"/blahaj.png"}
|
||||||
|
style={{ cursor: "grab", maxHeight: "100%" }}
|
||||||
|
/>
|
||||||
|
<Typography
|
||||||
|
level={"h2"}
|
||||||
|
sx={{
|
||||||
|
background:
|
||||||
|
"linear-gradient(110deg, var(--joy-palette-primary-solidBg), var(--joy-palette-success-400))",
|
||||||
|
WebkitBackgroundClip: "text",
|
||||||
|
WebkitTextFillColor: "transparent",
|
||||||
|
fontWeight: "bold",
|
||||||
|
cursor: "default",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Tranga
|
||||||
|
</Typography>
|
||||||
|
</Stack>
|
||||||
|
<Stack
|
||||||
|
sx={{ flexGrow: 1, flexBasis: 1, justifyContent: "flex-end" }}
|
||||||
|
direction={"row"}
|
||||||
|
spacing={2}
|
||||||
|
>
|
||||||
|
<Link
|
||||||
|
target={"_blank"}
|
||||||
|
href={"https://github.com/C9Glax/tranga"}
|
||||||
|
color={"neutral"}
|
||||||
|
height={"min-content"}
|
||||||
|
>
|
||||||
|
<GitHub /> Server
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
target={"_blank"}
|
||||||
|
href={"https://github.com/C9Glax/tranga-website"}
|
||||||
|
color={"neutral"}
|
||||||
|
height={"min-content"}
|
||||||
|
>
|
||||||
|
<GitHub /> Website
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
target={"_blank"}
|
||||||
|
href={Api.baseUrl + "/swagger"}
|
||||||
|
color={"neutral"}
|
||||||
|
height={"min-content"}
|
||||||
|
>
|
||||||
|
<Article />
|
||||||
|
Swagger
|
||||||
|
</Link>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
</Sheet>
|
||||||
|
);
|
||||||
}
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
import { createContext } from "react";
|
import { createContext } from "react";
|
||||||
import {V2} from "./V2.ts";
|
import { V2 } from "./V2.ts";
|
||||||
|
|
||||||
export const ApiContext = createContext<V2>(new V2());
|
export const ApiContext = createContext<V2>(new V2());
|
@@ -1,31 +1,32 @@
|
|||||||
import {createContext, useContext, useState} from "react";
|
import { createContext, useContext, useState } from "react";
|
||||||
import {TrangaSettings} from "./data-contracts.ts";
|
import { TrangaSettings } from "./data-contracts.ts";
|
||||||
import {ApiContext} from "./ApiContext.tsx";
|
import { ApiContext } from "./ApiContext.tsx";
|
||||||
|
|
||||||
const [settingsPromise, setSettingsPromise] = useState<Promise<TrangaSettings | undefined>>();
|
const [settingsPromise, setSettingsPromise] =
|
||||||
|
useState<Promise<TrangaSettings | undefined>>();
|
||||||
const [settings, setSettings] = useState<TrangaSettings>();
|
const [settings, setSettings] = useState<TrangaSettings>();
|
||||||
|
|
||||||
const API = useContext(ApiContext);
|
const API = useContext(ApiContext);
|
||||||
|
|
||||||
export const SettingsContext = createContext<{ GetSettings: () => Promise<TrangaSettings | undefined> }>(
|
export const SettingsContext = createContext<{
|
||||||
{
|
GetSettings: () => Promise<TrangaSettings | undefined>;
|
||||||
GetSettings: () : Promise<TrangaSettings | undefined> => {
|
}>({
|
||||||
const promise = settingsPromise;
|
GetSettings: (): Promise<TrangaSettings | undefined> => {
|
||||||
if(promise) return promise;
|
const promise = settingsPromise;
|
||||||
const p = new Promise<TrangaSettings | undefined>((resolve, reject) => {
|
if (promise) return promise;
|
||||||
if (settings) resolve(settings);
|
const p = new Promise<TrangaSettings | undefined>((resolve, reject) => {
|
||||||
|
if (settings) resolve(settings);
|
||||||
|
|
||||||
console.log(`Fetching settings`);
|
console.log(`Fetching settings`);
|
||||||
API.settingsList()
|
API.settingsList()
|
||||||
.then(result => {
|
.then((result) => {
|
||||||
if (!result.ok)
|
if (!result.ok) throw new Error(`Error fetching settings`);
|
||||||
throw new Error(`Error fetching settings`);
|
setSettings(result.data);
|
||||||
setSettings(result.data);
|
resolve(result.data);
|
||||||
resolve(result.data);
|
})
|
||||||
}).catch(reject);
|
.catch(reject);
|
||||||
});
|
});
|
||||||
setSettingsPromise(p);
|
setSettingsPromise(p);
|
||||||
return p;
|
return p;
|
||||||
}
|
},
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
@@ -1,29 +1,25 @@
|
|||||||
import { createRoot } from 'react-dom/client'
|
import { createRoot } from "react-dom/client";
|
||||||
import './index.css'
|
import "./index.css";
|
||||||
import App from './App.tsx'
|
import App from "./App.tsx";
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import '@fontsource/inter';
|
import "@fontsource/inter";
|
||||||
import { CssVarsProvider } from '@mui/joy/styles';
|
import { CssVarsProvider } from "@mui/joy/styles";
|
||||||
import CssBaseline from '@mui/joy/CssBaseline';
|
import CssBaseline from "@mui/joy/CssBaseline";
|
||||||
import {StrictMode} from "react";
|
import { StrictMode } from "react";
|
||||||
import {trangaTheme} from "./theme.ts";
|
import { trangaTheme } from "./theme.ts";
|
||||||
|
|
||||||
export default function MyApp() {
|
export default function MyApp() {
|
||||||
return (
|
return (
|
||||||
<StrictMode>
|
<StrictMode>
|
||||||
<CssVarsProvider theme={trangaTheme}>
|
<CssVarsProvider theme={trangaTheme}>
|
||||||
{/* must be used under CssVarsProvider */}
|
{/* must be used under CssVarsProvider */}
|
||||||
<CssBaseline />
|
<CssBaseline />
|
||||||
|
|
||||||
{/* The rest of your application */}
|
{/* The rest of your application */}
|
||||||
<App />
|
<App />
|
||||||
</CssVarsProvider>
|
</CssVarsProvider>
|
||||||
</StrictMode>
|
</StrictMode>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
createRoot(document.getElementById("root")!).render(<MyApp />);
|
||||||
|
|
||||||
createRoot(document.getElementById('root')!).render(
|
|
||||||
<MyApp />
|
|
||||||
);
|
|
||||||
|
@@ -1,88 +1,87 @@
|
|||||||
import { extendTheme } from '@mui/joy/styles';
|
import { extendTheme } from "@mui/joy/styles";
|
||||||
|
|
||||||
|
|
||||||
export const trangaTheme = extendTheme({
|
export const trangaTheme = extendTheme({
|
||||||
"colorSchemes": {
|
colorSchemes: {
|
||||||
"light": {
|
light: {
|
||||||
"palette": {
|
palette: {
|
||||||
"primary": {
|
primary: {
|
||||||
"50": "#FCE5EA",
|
"50": "#FCE5EA",
|
||||||
"100": "#FBDDE3",
|
"100": "#FBDDE3",
|
||||||
"200": "#F9CBD4",
|
"200": "#F9CBD4",
|
||||||
"300": "#F7BAC6",
|
"300": "#F7BAC6",
|
||||||
"400": "#F5A9B8",
|
"400": "#F5A9B8",
|
||||||
"500": "#F5A9B8",
|
"500": "#F5A9B8",
|
||||||
"600": "#C48793",
|
"600": "#C48793",
|
||||||
"700": "#AC7681",
|
"700": "#AC7681",
|
||||||
"800": "#93656E",
|
"800": "#93656E",
|
||||||
"900": "#7B555C"
|
"900": "#7B555C",
|
||||||
},
|
|
||||||
"neutral": {
|
|
||||||
"50": "#E6E6E6",
|
|
||||||
"100": "#CCCCCC",
|
|
||||||
"200": "#B3B3B3",
|
|
||||||
"300": "#999999",
|
|
||||||
"400": "#808080",
|
|
||||||
"500": "#666666",
|
|
||||||
"600": "#4C4C4C",
|
|
||||||
"700": "#333333",
|
|
||||||
"800": "#191919",
|
|
||||||
"900": "#000",
|
|
||||||
"plainColor": "var(--joy-palette-neutral-50)",
|
|
||||||
"plainHoverBg": "var(--joy-palette-neutral-700)",
|
|
||||||
"outlinedColor": "var(--joy-palette-neutral-50)",
|
|
||||||
},
|
|
||||||
"success": {
|
|
||||||
"50": "#cef0fe",
|
|
||||||
"100": "#bdebfd",
|
|
||||||
"200": "#9de2fc",
|
|
||||||
"300": "#7cd8fb",
|
|
||||||
"400": "#5bcefa",
|
|
||||||
"500": "#5bcefa",
|
|
||||||
"600": "#49a5c8",
|
|
||||||
"700": "#4090af",
|
|
||||||
"800": "#2e677d",
|
|
||||||
"900": "#245264"
|
|
||||||
},
|
|
||||||
"danger": {
|
|
||||||
"50": "#f2c0b3",
|
|
||||||
"100": "#ea9680",
|
|
||||||
"200": "#e68166",
|
|
||||||
"300": "#dd5733",
|
|
||||||
"400": "#d52d00",
|
|
||||||
"500": "#d52d00",
|
|
||||||
"600": "#aa2400",
|
|
||||||
"700": "#951f00",
|
|
||||||
"800": "#6b1700",
|
|
||||||
"900": "#400d00"
|
|
||||||
},
|
|
||||||
"warning": {
|
|
||||||
"50": "#ffebdd",
|
|
||||||
"100": "#ffd7bb",
|
|
||||||
"200": "#ffc29a",
|
|
||||||
"300": "#ffae78",
|
|
||||||
"400": "#ff9a56",
|
|
||||||
"500": "#ff9a56",
|
|
||||||
"600": "#cc7b45",
|
|
||||||
"700": "#995c34",
|
|
||||||
"800": "#663e22",
|
|
||||||
"900": "#331f11"
|
|
||||||
},
|
|
||||||
"background": {
|
|
||||||
"body": "var(--joy-palette-neutral-900)",
|
|
||||||
"surface": "var(--joy-palette-neutral-900)",
|
|
||||||
"popup": "var(--joy-palette-neutral-800)"
|
|
||||||
},
|
|
||||||
"text": {
|
|
||||||
"primary": "var(--joy-palette-neutral-50)",
|
|
||||||
"secondary": "var(--joy-palette-success-200)",
|
|
||||||
"tertiary": "var(--joy-palette-primary-200)",
|
|
||||||
"icon": "var(--joy-palette-primary-50)"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"dark": {
|
neutral: {
|
||||||
"palette": {}
|
"50": "#E6E6E6",
|
||||||
}
|
"100": "#CCCCCC",
|
||||||
}
|
"200": "#B3B3B3",
|
||||||
})
|
"300": "#999999",
|
||||||
|
"400": "#808080",
|
||||||
|
"500": "#666666",
|
||||||
|
"600": "#4C4C4C",
|
||||||
|
"700": "#333333",
|
||||||
|
"800": "#191919",
|
||||||
|
"900": "#000",
|
||||||
|
plainColor: "var(--joy-palette-neutral-50)",
|
||||||
|
plainHoverBg: "var(--joy-palette-neutral-700)",
|
||||||
|
outlinedColor: "var(--joy-palette-neutral-50)",
|
||||||
|
},
|
||||||
|
success: {
|
||||||
|
"50": "#cef0fe",
|
||||||
|
"100": "#bdebfd",
|
||||||
|
"200": "#9de2fc",
|
||||||
|
"300": "#7cd8fb",
|
||||||
|
"400": "#5bcefa",
|
||||||
|
"500": "#5bcefa",
|
||||||
|
"600": "#49a5c8",
|
||||||
|
"700": "#4090af",
|
||||||
|
"800": "#2e677d",
|
||||||
|
"900": "#245264",
|
||||||
|
},
|
||||||
|
danger: {
|
||||||
|
"50": "#f2c0b3",
|
||||||
|
"100": "#ea9680",
|
||||||
|
"200": "#e68166",
|
||||||
|
"300": "#dd5733",
|
||||||
|
"400": "#d52d00",
|
||||||
|
"500": "#d52d00",
|
||||||
|
"600": "#aa2400",
|
||||||
|
"700": "#951f00",
|
||||||
|
"800": "#6b1700",
|
||||||
|
"900": "#400d00",
|
||||||
|
},
|
||||||
|
warning: {
|
||||||
|
"50": "#ffebdd",
|
||||||
|
"100": "#ffd7bb",
|
||||||
|
"200": "#ffc29a",
|
||||||
|
"300": "#ffae78",
|
||||||
|
"400": "#ff9a56",
|
||||||
|
"500": "#ff9a56",
|
||||||
|
"600": "#cc7b45",
|
||||||
|
"700": "#995c34",
|
||||||
|
"800": "#663e22",
|
||||||
|
"900": "#331f11",
|
||||||
|
},
|
||||||
|
background: {
|
||||||
|
body: "var(--joy-palette-neutral-900)",
|
||||||
|
surface: "var(--joy-palette-neutral-900)",
|
||||||
|
popup: "var(--joy-palette-neutral-800)",
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
primary: "var(--joy-palette-neutral-50)",
|
||||||
|
secondary: "var(--joy-palette-success-200)",
|
||||||
|
tertiary: "var(--joy-palette-primary-200)",
|
||||||
|
icon: "var(--joy-palette-primary-50)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dark: {
|
||||||
|
palette: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
@@ -1,10 +1,10 @@
|
|||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from "vite";
|
||||||
import react from '@vitejs/plugin-react'
|
import react from "@vitejs/plugin-react";
|
||||||
|
|
||||||
// https://vite.dev/config/
|
// https://vite.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [react()],
|
plugins: [react()],
|
||||||
server: {
|
server: {
|
||||||
host: '127.0.0.1',
|
host: "127.0.0.1",
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
Reference in New Issue
Block a user